const fs = require('fs'); const path = require('path'); class AccessibilityAnalyzer { constructor() { this.accessibilityIssues = []; this.accessibilityFeatures = []; this.colorContrast = []; } analyzeAccessibility() { console.log('♿ ACCESSIBILITY ANALYSIS'); console.log('='.repeat(50)); const cssFiles = [ './assets/css/hvac-common.css', './assets/css/hvac-dashboard.css', './assets/css/hvac-registration.css', './assets/css/hvac-certificates.css', './assets/css/hvac-email-attendees.css', './assets/css/hvac-event-summary.css', './assets/css/hvac-attendee-profile.css', './assets/css/hvac-mobile-nav.css' ]; cssFiles.forEach(file => { if (fs.existsSync(file)) { this.analyzeFile(file); } }); this.generateAccessibilityReport(); } analyzeFile(filePath) { const content = fs.readFileSync(filePath, 'utf8'); const fileName = path.basename(filePath); console.log(`\nšŸ” ${fileName}:`); // Check for focus management this.checkFocusManagement(content, fileName); // Check for keyboard navigation this.checkKeyboardNavigation(content, fileName); // Check for screen reader support this.checkScreenReaderSupport(content, fileName); // Check color contrast and color dependencies this.checkColorAccessibility(content, fileName); // Check for motion and animation accessibility this.checkMotionAccessibility(content, fileName); // Check for text scaling support this.checkTextScaling(content, fileName); } checkFocusManagement(content, fileName) { const focusPatterns = { ':focus': /:focus(?![a-zA-Z-])/g, ':focus-within': /:focus-within/g, ':focus-visible': /:focus-visible/g, 'outline': /outline:\s*[^;]+/g, 'skip-links': /skip-link/g }; let focusScore = 0; Object.entries(focusPatterns).forEach(([pattern, regex]) => { const matches = content.match(regex); if (matches) { console.log(` āœ… ${pattern}: ${matches.length} instances`); focusScore += matches.length; this.accessibilityFeatures.push({ file: fileName, feature: `Focus Management - ${pattern}`, count: matches.length }); } }); if (focusScore === 0) { console.log(` āŒ No focus management detected`); this.accessibilityIssues.push({ file: fileName, issue: 'No focus styles defined', severity: 'high', impact: 'Keyboard users cannot see focused elements', wcagCriteria: '2.4.7 (Focus Visible)' }); } else { console.log(` šŸ“Š Focus management score: ${focusScore}`); } // Check for proper focus removal (should be avoided) const focusRemoval = content.match(/outline:\s*(none|0)/g); if (focusRemoval) { console.log(` āš ļø Focus removal detected: ${focusRemoval.length} instances`); this.accessibilityIssues.push({ file: fileName, issue: 'Focus indicators removed', severity: 'high', impact: 'Breaks keyboard navigation', wcagCriteria: '2.4.7 (Focus Visible)' }); } } checkKeyboardNavigation(content, fileName) { const keyboardPatterns = { 'tabindex': /tabindex/g, 'keyboard navigation': /user-is-tabbing|keyboard-nav/g, 'interactive elements': /:hover.*:focus|:focus.*:hover/g }; Object.entries(keyboardPatterns).forEach(([pattern, regex]) => { const matches = content.match(regex); if (matches) { console.log(` āœ… ${pattern}: ${matches.length} instances`); this.accessibilityFeatures.push({ file: fileName, feature: `Keyboard Navigation - ${pattern}`, count: matches.length }); } }); // Check for hover-only interactions (accessibility issue) const hoverOnly = content.match(/:hover(?!.*:focus)/g); if (hoverOnly) { console.log(` āš ļø Hover-only interactions: ${hoverOnly.length}`); this.accessibilityIssues.push({ file: fileName, issue: 'Hover-only interactions', severity: 'medium', impact: 'Inaccessible to keyboard users', wcagCriteria: '2.1.1 (Keyboard)' }); } } checkScreenReaderSupport(content, fileName) { const srPatterns = { 'visually-hidden': /visually-hidden|sr-only|screen-reader/g, 'aria attributes': /aria-[a-z]+/g, 'role attributes': /role\s*=/g }; Object.entries(srPatterns).forEach(([pattern, regex]) => { const matches = content.match(regex); if (matches) { console.log(` āœ… ${pattern}: ${matches.length} instances`); this.accessibilityFeatures.push({ file: fileName, feature: `Screen Reader - ${pattern}`, count: matches.length }); } }); } checkColorAccessibility(content, fileName) { // Extract color values const colors = { hex: content.match(/#[0-9a-fA-F]{3,6}/g) || [], rgb: content.match(/rgb\([^)]+\)/g) || [], rgba: content.match(/rgba\([^)]+\)/g) || [], hsl: content.match(/hsl\([^)]+\)/g) || [], named: content.match(/:\s*(red|blue|green|black|white|gray|grey|yellow|orange|purple|pink|brown)\s*[;}]/g) || [] }; const totalColors = Object.values(colors).flat().length; if (totalColors > 0) { console.log(` šŸŽØ Color usage: ${totalColors} color declarations`); // Check for high contrast media query const highContrast = content.includes('prefers-contrast') || content.includes('high-contrast'); if (highContrast) { console.log(` āœ… High contrast support detected`); this.accessibilityFeatures.push({ file: fileName, feature: 'High Contrast Support', count: 1 }); } // Check for color-only information const colorOnlyWarnings = this.checkColorOnlyInformation(content); if (colorOnlyWarnings.length > 0) { this.accessibilityIssues.push({ file: fileName, issue: 'Potential color-only information', severity: 'medium', impact: 'Information may not be accessible to colorblind users', wcagCriteria: '1.4.1 (Use of Color)' }); } } } checkColorOnlyInformation(content) { // Simple heuristic check for color-only information const suspiciousPatterns = [ /color:\s*red.*error|error.*color:\s*red/gi, /color:\s*green.*success|success.*color:\s*green/gi, /background.*red.*warning|warning.*background.*red/gi ]; return suspiciousPatterns.filter(pattern => content.match(pattern)); } checkMotionAccessibility(content, fileName) { const motionPatterns = { 'animations': /@keyframes|animation:|animation-/g, 'transitions': /transition:/g, 'transforms': /transform:/g, 'reduced motion': /prefers-reduced-motion/g }; let hasMotion = false; let hasReducedMotionSupport = false; Object.entries(motionPatterns).forEach(([pattern, regex]) => { const matches = content.match(regex); if (matches) { if (pattern === 'reduced motion') { hasReducedMotionSupport = true; console.log(` āœ… ${pattern}: ${matches.length} instances`); this.accessibilityFeatures.push({ file: fileName, feature: 'Reduced Motion Support', count: matches.length }); } else { hasMotion = true; console.log(` šŸŽ¬ ${pattern}: ${matches.length} instances`); } } }); if (hasMotion && !hasReducedMotionSupport) { console.log(` āš ļø Motion without reduced motion support`); this.accessibilityIssues.push({ file: fileName, issue: 'Animations without reduced motion support', severity: 'medium', impact: 'May cause issues for users with vestibular disorders', wcagCriteria: '2.3.3 (Animation from Interactions)' }); } } checkTextScaling(content, fileName) { // Check for relative units that support text scaling const relativeUnits = { 'rem': (content.match(/\d+(\.\d+)?rem/g) || []).length, 'em': (content.match(/\d+(\.\d+)?em/g) || []).length, '%': (content.match(/\d+(\.\d+)?%/g) || []).length }; const absoluteUnits = { 'px': (content.match(/\d+px/g) || []).length }; const totalRelative = Object.values(relativeUnits).reduce((a, b) => a + b, 0); const totalAbsolute = Object.values(absoluteUnits).reduce((a, b) => a + b, 0); if (totalRelative > 0) { console.log(` āœ… Relative units: ${totalRelative} uses`); console.log(` rem: ${relativeUnits.rem}, em: ${relativeUnits.em}, %: ${relativeUnits['%']}`); } if (totalAbsolute > 0) { console.log(` šŸ“ Absolute units: ${totalAbsolute} px values`); const ratio = totalRelative / (totalRelative + totalAbsolute); if (ratio < 0.5) { this.accessibilityIssues.push({ file: fileName, issue: 'Heavy use of absolute units', severity: 'low', impact: 'May not scale well with user font size preferences', wcagCriteria: '1.4.4 (Resize text)' }); } } } generateAccessibilityReport() { console.log('\n' + '='.repeat(80)); console.log('♿ ACCESSIBILITY ANALYSIS REPORT'); console.log('='.repeat(80)); // WCAG Compliance Overview console.log('\nšŸ“‹ WCAG 2.1 COMPLIANCE OVERVIEW:'); const wcagAreas = { 'Perceivable': ['Focus Visible', 'Use of Color', 'Resize text'], 'Operable': ['Keyboard', 'Focus Visible', 'Animation from Interactions'], 'Understandable': ['Focus Visible'], 'Robust': ['Valid HTML/CSS'] }; Object.entries(wcagAreas).forEach(([principle, criteria]) => { console.log(`\n ${principle}:`); criteria.forEach(criterion => { const relatedIssues = this.accessibilityIssues.filter(issue => issue.wcagCriteria && issue.wcagCriteria.includes(criterion) ); if (relatedIssues.length === 0) { console.log(` āœ… ${criterion} - No major issues detected`); } else { console.log(` āš ļø ${criterion} - ${relatedIssues.length} issues found`); } }); }); // Critical Issues if (this.accessibilityIssues.length > 0) { console.log('\nāŒ ACCESSIBILITY ISSUES:'); const highSeverity = this.accessibilityIssues.filter(i => i.severity === 'high'); const mediumSeverity = this.accessibilityIssues.filter(i => i.severity === 'medium'); const lowSeverity = this.accessibilityIssues.filter(i => i.severity === 'low'); if (highSeverity.length > 0) { console.log('\n 🚨 HIGH SEVERITY:'); highSeverity.forEach((issue, i) => { console.log(` ${i + 1}. ${issue.file}: ${issue.issue}`); console.log(` Impact: ${issue.impact}`); console.log(` WCAG: ${issue.wcagCriteria}`); }); } if (mediumSeverity.length > 0) { console.log('\n āš ļø MEDIUM SEVERITY:'); mediumSeverity.forEach((issue, i) => { console.log(` ${i + 1}. ${issue.file}: ${issue.issue}`); console.log(` Impact: ${issue.impact}`); console.log(` WCAG: ${issue.wcagCriteria}`); }); } if (lowSeverity.length > 0) { console.log('\n ā„¹ļø LOW SEVERITY:'); lowSeverity.forEach((issue, i) => { console.log(` ${i + 1}. ${issue.file}: ${issue.issue}`); console.log(` Impact: ${issue.impact}`); console.log(` WCAG: ${issue.wcagCriteria}`); }); } } // Positive Features if (this.accessibilityFeatures.length > 0) { console.log('\nāœ… ACCESSIBILITY FEATURES DETECTED:'); const featureSummary = new Map(); this.accessibilityFeatures.forEach(feature => { const key = feature.feature; if (featureSummary.has(key)) { featureSummary.set(key, featureSummary.get(key) + feature.count); } else { featureSummary.set(key, feature.count); } }); Array.from(featureSummary.entries()).forEach(([feature, count]) => { console.log(` • ${feature}: ${count} implementations`); }); } // Recommendations console.log('\nšŸ’” ACCESSIBILITY RECOMMENDATIONS:'); console.log(' 1. Add focus styles to all interactive elements'); console.log(' 2. Implement skip links for keyboard navigation'); console.log(' 3. Add prefers-reduced-motion support for animations'); console.log(' 4. Use semantic HTML with proper ARIA attributes'); console.log(' 5. Test with screen readers (NVDA, JAWS, VoiceOver)'); console.log(' 6. Verify color contrast ratios meet WCAG AA standards'); console.log(' 7. Ensure all functionality is keyboard accessible'); console.log(' 8. Test with browser zoom up to 200%'); console.log(' 9. Consider implementing high contrast mode support'); console.log(' 10. Add proper error handling and messaging'); // Summary console.log('\nšŸ“Š ACCESSIBILITY SUMMARY:'); console.log(` High Severity Issues: ${this.accessibilityIssues.filter(i => i.severity === 'high').length}`); console.log(` Medium Severity Issues: ${this.accessibilityIssues.filter(i => i.severity === 'medium').length}`); console.log(` Low Severity Issues: ${this.accessibilityIssues.filter(i => i.severity === 'low').length}`); console.log(` Positive Features: ${this.accessibilityFeatures.length}`); const overallScore = Math.max(0, 100 - ( (this.accessibilityIssues.filter(i => i.severity === 'high').length * 20) + (this.accessibilityIssues.filter(i => i.severity === 'medium').length * 10) + (this.accessibilityIssues.filter(i => i.severity === 'low').length * 5) )); console.log(` Overall Accessibility Score: ${overallScore}/100`); } } const analyzer = new AccessibilityAnalyzer(); analyzer.analyzeAccessibility();