#!/usr/bin/env node /** * HVAC Community Events Reduced Motion Accessibility Fixes * * This script implements prefers-reduced-motion support across all CSS files * to ensure accessibility compliance for users with vestibular disorders. */ const fs = require('fs'); const path = require('path'); const CSS_DIR = './assets/css'; const motionRules = []; // Properties that should respect reduced motion preferences const MOTION_PROPERTIES = { animations: [ 'animation', 'animation-name', 'animation-duration', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', 'animation-fill-mode', 'animation-play-state' ], transitions: [ 'transition', 'transition-property', 'transition-duration', 'transition-timing-function', 'transition-delay' ], transforms: [ 'transform' ] }; // Selectors that commonly use motion effects const MOTION_SELECTORS = [ '.hvac-animate-fade-in', '.hvac-animate-scale-up', '.hvac-animate-pulse', '.hvac-animate-slide-in-right', '.hvac-animate-slide-in-left', '.hvac-animate-slide-in-bottom', '.hvac-card:hover', '.hvac-stat-card:hover', '.hvac-event-stat-card:hover', '.hvac-button:hover', '.hvac-email-submit:hover', '.hvac-attendee-item:hover', '.hvac-loading::after', '@keyframes', 'transform:', 'animation:', 'transition:' ]; function addReducedMotionSupport(filePath) { try { let content = fs.readFileSync(filePath, 'utf8'); let modifications = 0; const fileName = path.basename(filePath); console.log(`\nProcessing: ${fileName}`); // Skip files that already have reduced motion support if (content.includes('/* Reduced Motion Support Added */')) { console.log(' ✓ Reduced motion support already exists, skipping'); return 0; } // Only process files that contain motion effects const hasMotionEffects = MOTION_SELECTORS.some(selector => content.includes(selector.replace(':', '')) ); if (!hasMotionEffects) { console.log(' • No motion effects detected, skipping'); return 0; } // Comprehensive reduced motion media query const reducedMotionSupport = ` /* Reduced Motion Support Added - WCAG 2.1 Accessibility */ /* Respects user preference for reduced motion to prevent vestibular disorders */ @media (prefers-reduced-motion: reduce) { /* Disable all animations and transitions globally */ *, *::before, *::after { animation-duration: 0.001ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; transition-delay: 0s !important; scroll-behavior: auto !important; } /* Remove specific transform animations */ .hvac-animate-fade-in, .hvac-animate-scale-up, .hvac-animate-pulse, .hvac-animate-slide-in-right, .hvac-animate-slide-in-left, .hvac-animate-slide-in-bottom { animation: none !important; opacity: 1 !important; transform: none !important; } /* Disable hover transformations */ .hvac-card:hover, .hvac-stat-card:hover, .hvac-event-stat-card:hover, .hvac-button:hover, .hvac-email-submit:hover { transform: none !important; animation: none !important; } /* Keep essential visual feedback but remove motion */ .hvac-card:hover, .hvac-stat-card:hover, .hvac-event-stat-card:hover { border-color: var(--hvac-primary, #0274be) !important; box-shadow: 0 0 0 2px rgba(2, 116, 190, 0.2) !important; } /* Disable loading spinner animation but keep visibility */ .hvac-loading::after { animation: none !important; border-radius: 50% !important; border: 2px solid rgba(0, 0, 0, 0.2) !important; border-top-color: #333 !important; } /* Disable focus pulse animation */ .hvac-button:focus, .hvac-email-submit:focus, .hvac-content button[type="submit"]:focus { animation: none !important; } /* Ensure smooth scrolling is disabled */ html { scroll-behavior: auto !important; } /* Disable CSS Grid/Flexbox animations if any */ .hvac-dashboard-stats .hvac-stat-card:nth-child(n), .hvac-event-summary-stats .hvac-event-stat-card:nth-child(n) { animation: none !important; opacity: 1 !important; } } /* Provide alternative visual feedback for reduced motion users */ @media (prefers-reduced-motion: reduce) { /* Enhanced border feedback instead of transform */ .hvac-content button:hover, .hvac-content input[type="submit"]:hover, .hvac-content a:hover { outline: 2px solid var(--hvac-primary, #0274be) !important; outline-offset: 2px !important; } /* Enhanced color changes for interactive elements */ .hvac-attendee-item:hover { background-color: var(--hvac-primary-light, #e6f3fb) !important; border-left: 4px solid var(--hvac-primary, #0274be) !important; } /* Static loading indicator */ .hvac-loading { opacity: 0.7 !important; } .hvac-loading::after { content: "Loading..." !important; display: inline-block !important; font-size: 12px !important; color: #666 !important; border: none !important; background: none !important; border-radius: 0 !important; width: auto !important; height: auto !important; position: static !important; margin-left: 8px !important; } }`; // Add reduced motion support before any existing @media queries or at the end if (content.includes('@media')) { // Insert before the first @media query const firstMediaIndex = content.indexOf('@media'); content = content.slice(0, firstMediaIndex) + reducedMotionSupport + '\n\n' + content.slice(firstMediaIndex); } else { // Add at the end content += reducedMotionSupport; } modifications++; // Add marker at the beginning content = `/* Reduced Motion Support Added - ${new Date().toISOString().split('T')[0]} */\n${content}`; // Write the updated content fs.writeFileSync(filePath, content, 'utf8'); console.log(` ✅ Added comprehensive reduced motion support`); motionRules.push({ file: fileName, modifications: modifications }); return modifications; } catch (error) { console.error(`Error processing ${filePath}:`, error.message); return 0; } } function createMotionPreferenceDetection() { const jsPath = './assets/js/reduced-motion-detection.js'; const jsContent = `/** * Reduced Motion Preference Detection * Adds CSS class to HTML element for reduced motion users */ (function() { 'use strict'; // Check for reduced motion preference const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); function handleReducedMotionChange(mediaQuery) { const htmlElement = document.documentElement; if (mediaQuery.matches) { htmlElement.classList.add('reduced-motion'); console.log('Reduced motion preference detected - animations disabled'); } else { htmlElement.classList.remove('reduced-motion'); } } // Set initial state handleReducedMotionChange(prefersReducedMotion); // Listen for changes if (prefersReducedMotion.addEventListener) { prefersReducedMotion.addEventListener('change', handleReducedMotionChange); } else if (prefersReducedMotion.addListener) { // Fallback for older browsers prefersReducedMotion.addListener(handleReducedMotionChange); } // Additional safety: Disable animations if user has motion sensitivity if (navigator.userAgent.includes('MotionSensitive') || navigator.userAgent.includes('AccessibilityMode')) { document.documentElement.classList.add('reduced-motion'); } })();`; // Create js directory if it doesn't exist const jsDir = path.dirname(jsPath); if (!fs.existsSync(jsDir)) { fs.mkdirSync(jsDir, { recursive: true }); } if (!fs.existsSync(jsPath)) { fs.writeFileSync(jsPath, jsContent, 'utf8'); console.log('✓ Created reduced-motion-detection.js'); } } function main() { console.log('HVAC Community Events - Reduced Motion Accessibility Fixes'); console.log('='.repeat(60)); if (!fs.existsSync(CSS_DIR)) { console.error(`CSS directory not found: ${CSS_DIR}`); process.exit(1); } // Focus on files that are likely to have animations const targetFiles = [ 'hvac-animations.css', 'hvac-common.css', 'hvac-dashboard.css', 'hvac-registration.css', 'hvac-event-summary.css', 'hvac-email-attendees.css', 'hvac-mobile-nav.css', 'hvac-certificates.css', 'hvac-attendee-profile.css' ].map(file => path.join(CSS_DIR, file)); // Filter to only existing files const existingFiles = targetFiles.filter(filePath => fs.existsSync(filePath)); let totalModifications = 0; // Process each CSS file existingFiles.forEach(filePath => { totalModifications += addReducedMotionSupport(filePath); }); // Create JavaScript detection helper createMotionPreferenceDetection(); console.log('\n' + '='.repeat(60)); console.log('REDUCED MOTION FIX SUMMARY'); console.log('='.repeat(60)); if (motionRules.length > 0) { motionRules.forEach(rule => { console.log(`${rule.file}: Reduced motion support added`); }); } console.log(`\nTotal files processed: ${existingFiles.length}`); console.log(`Total files with reduced motion support: ${totalModifications}`); if (totalModifications > 0) { console.log('\n✅ Reduced motion accessibility fixes applied successfully!'); console.log('\nKey improvements:'); console.log('• Added @media (prefers-reduced-motion: reduce) queries'); console.log('• Disabled all animations and transitions for sensitive users'); console.log('• Provided alternative visual feedback without motion'); console.log('• Created JavaScript detection helper'); console.log('• Enhanced accessibility for vestibular disorders'); console.log('\n📋 WCAG 2.1 Compliance:'); console.log('• Success Criterion 2.3.3 (Animation from Interactions) - Level AAA'); console.log('• Success Criterion 2.2.2 (Pause, Stop, Hide) - Level A'); console.log('• Supports users with vestibular motion disorders'); console.log('\n💡 Implementation Notes:'); console.log('• Include reduced-motion-detection.js in your HTML pages'); console.log('• Test with browser dev tools: Rendering > Emulate CSS prefers-reduced-motion'); console.log('• Verify all animations are disabled when preference is set'); console.log('• Alternative visual feedback maintains usability'); } else { console.log('\n✅ All relevant CSS files already have reduced motion support'); } } if (require.main === module) { main(); } module.exports = { addReducedMotionSupport, MOTION_PROPERTIES };