#!/usr/bin/env node /** * HVAC Community Events CSS Focus Management Fixes * * This script adds proper focus management styles to ensure WCAG 2.1 compliance * and keyboard accessibility across all CSS files. */ const fs = require('fs'); const path = require('path'); const CSS_DIR = './assets/css'; const focusRules = []; // Focus styles to add for different element types const FOCUS_PATTERNS = { // Interactive elements that need focus indicators buttons: [ '.hvac-button:focus', '.hvac-content .button:focus', '.hvac-content button:focus', '.hvac-content input[type="submit"]:focus', '.hvac-email-submit:focus', '.hvac-filter-submit:focus', '.hvac-certificate-actions button:focus', '.hvac-certificate-actions a:focus' ], // Form inputs inputs: [ '.hvac-form-input:focus', '.hvac-content input[type="text"]:focus', '.hvac-content input[type="email"]:focus', '.hvac-content input[type="password"]:focus', '.hvac-content input[type="url"]:focus', '.hvac-content textarea:focus', '.hvac-content select:focus', '.hvac-email-form-row input:focus', '.hvac-email-form-row textarea:focus', '.hvac-filter-group input:focus', '.hvac-filter-group select:focus' ], // Links and navigation links: [ '.hvac-content a:focus', '.hvac-event-link:focus', '.hvac-certificate-link:focus', '.hvac-attendee-profile-icon:focus', '.hvac-dashboard-nav a:focus', '.hvac-email-navigation a:focus' ], // Interactive elements interactive: [ '.hvac-attendee-checkbox:focus', '.hvac-select-all-container input[type="checkbox"]:focus', '.hvac-modal-close:focus', '.hvac-certificate-table tr:focus' ] }; // Focus styles definitions const FOCUS_STYLES = { buttons: ` outline: 2px solid #005fcc; outline-offset: 2px; box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2); border-radius: 4px;`, inputs: ` outline: 2px solid #005fcc; outline-offset: 2px; border-color: #005fcc; box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2);`, links: ` outline: 2px solid #005fcc; outline-offset: 2px; text-decoration: underline; background-color: rgba(0, 95, 204, 0.1); border-radius: 2px;`, interactive: ` outline: 2px solid #005fcc; outline-offset: 2px; box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.2);` }; // Skip focus outline for mouse users (but keep for keyboard) const MOUSE_FOCUS_RESET = ` /* Reset focus for mouse users while preserving keyboard accessibility */ .js-focus-visible :focus:not(.focus-visible) { outline: none; box-shadow: none; } /* Ensure focus is visible for keyboard users */ .js-focus-visible .focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; }`; function addFocusStylesToFile(filePath) { try { let content = fs.readFileSync(filePath, 'utf8'); let modifications = 0; console.log(`\nProcessing: ${path.basename(filePath)}`); // Check if focus styles already exist if (content.includes('/* Focus Management Styles */')) { console.log(' ✓ Focus styles already exist, skipping'); return 0; } // Add focus styles section const focusSection = ` /* Focus Management Styles - WCAG 2.1 Compliance */ /* Added for keyboard accessibility and screen reader support */ /* Button Focus Styles */ ${FOCUS_PATTERNS.buttons.join(',\n')} {${FOCUS_STYLES.buttons} } /* Input Focus Styles */ ${FOCUS_PATTERNS.inputs.join(',\n')} {${FOCUS_STYLES.inputs} } /* Link Focus Styles */ ${FOCUS_PATTERNS.links.join(',\n')} {${FOCUS_STYLES.links} } /* Interactive Element Focus Styles */ ${FOCUS_PATTERNS.interactive.join(',\n')} {${FOCUS_STYLES.interactive} } /* High Contrast Mode Support */ @media (prefers-contrast: high) { .hvac-content *:focus { outline: 3px solid #000000; outline-offset: 2px; background-color: #ffff00; color: #000000; } } /* Focus-visible polyfill support */ ${MOUSE_FOCUS_RESET} `; // Add the focus section before the last closing brace or at the end if (content.includes('@media print')) { // Insert before print styles content = content.replace('@media print', focusSection + '\n@media print'); } else { // Add at the end content += focusSection; } modifications++; // Write the updated content fs.writeFileSync(filePath, content, 'utf8'); console.log(` ✓ Added ${modifications} focus management sections`); focusRules.push({ file: path.basename(filePath), modifications: modifications }); return modifications; } catch (error) { console.error(`Error processing ${filePath}:`, error.message); return 0; } } function addFocusIndicatorsToAnimations() { const animationsPath = path.join(CSS_DIR, 'hvac-animations.css'); try { let content = fs.readFileSync(animationsPath, 'utf8'); // Check if focus-specific animations already exist if (content.includes('/* Focus Animation Styles */')) { console.log(' ✓ Animations file already has focus styles'); return 0; } const focusAnimations = ` /* Focus Animation Styles */ /* Smooth transitions for focus indicators */ .hvac-content *:focus { transition: outline 0.2s ease-out, box-shadow 0.2s ease-out, background-color 0.2s ease-out; } /* Focus indicator animation for better visibility */ @keyframes focus-pulse { 0% { box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.3); } 50% { box-shadow: 0 0 0 5px rgba(0, 95, 204, 0.2); } 100% { box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.3); } } /* Apply focus pulse to critical interactive elements */ .hvac-button:focus, .hvac-email-submit:focus, .hvac-content button[type="submit"]:focus { animation: focus-pulse 2s ease-in-out infinite; } /* Disable focus animations for reduced motion users */ @media (prefers-reduced-motion: reduce) { .hvac-content *:focus { transition: none; animation: none; } }`; // Add before the reduced motion section content = content.replace( '/* Disable animations for users who prefer reduced motion */', focusAnimations + '\n\n/* Disable animations for users who prefer reduced motion */' ); fs.writeFileSync(animationsPath, content, 'utf8'); console.log(' ✓ Added focus animations to hvac-animations.css'); return 1; } catch (error) { console.error(`Error updating animations file:`, error.message); return 0; } } function main() { console.log('HVAC Community Events - CSS Focus Management Fixes'); console.log('='.repeat(60)); if (!fs.existsSync(CSS_DIR)) { console.error(`CSS directory not found: ${CSS_DIR}`); process.exit(1); } const cssFiles = fs.readdirSync(CSS_DIR) .filter(file => file.endsWith('.css')) .map(file => path.join(CSS_DIR, file)); let totalModifications = 0; // Process each CSS file cssFiles.forEach(filePath => { totalModifications += addFocusStylesToFile(filePath); }); // Add focus-specific animations totalModifications += addFocusIndicatorsToAnimations(); console.log('\n' + '='.repeat(60)); console.log('FOCUS MANAGEMENT FIX SUMMARY'); console.log('='.repeat(60)); focusRules.forEach(rule => { console.log(`${rule.file}: ${rule.modifications} focus sections added`); }); console.log(`\nTotal files processed: ${cssFiles.length}`); console.log(`Total focus modifications: ${totalModifications}`); if (totalModifications > 0) { console.log('\n✅ Focus management fixes applied successfully!'); console.log('\nKey improvements:'); console.log('• Added WCAG 2.1 compliant focus indicators'); console.log('• Implemented keyboard navigation support'); console.log('• Added high contrast mode compatibility'); console.log('• Included focus-visible polyfill support'); console.log('• Added smooth focus transitions'); console.log('• Respect user motion preferences'); console.log('\n📋 Next Steps:'); console.log('• Test keyboard navigation across all pages'); console.log('• Verify screen reader compatibility'); console.log('• Check high contrast mode display'); console.log('• Add focus-visible.js polyfill to pages'); } else { console.log('\n✅ All CSS files already have focus management styles'); } } if (require.main === module) { main(); } module.exports = { addFocusStylesToFile, FOCUS_PATTERNS, FOCUS_STYLES };