upskill-event-manager/accessibility-analysis.js
bengizmo 993a820a84 feat: Add comprehensive development artifacts to repository
- 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>
2025-08-11 12:26:11 -03:00

408 lines
No EOL
16 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();