- 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 }; |