- Add 26 documentation files including test reports, deployment guides, and troubleshooting documentation - Include 3 CSV data files for trainer imports and user registration tracking - Add 43 JavaScript test files covering mobile optimization, Safari compatibility, and E2E testing - Include 18 PHP utility files for debugging, geocoding, and data analysis - Add 12 shell scripts for deployment verification, user management, and database operations - Update .gitignore with whitelist patterns for development files, documentation, and CSV data 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			362 lines
		
	
	
		
			No EOL
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
		
			No EOL
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| #!/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 }; |