You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.9 KiB
189 lines
5.9 KiB
/** |
|
* Test text contrast ratios for WCAG AA compliance |
|
* WCAG AA requires: |
|
* - 4.5:1 for normal text |
|
* - 3:1 for large text (18pt+ or 14pt+ bold) and UI components |
|
*/ |
|
|
|
// Color definitions from tailwind.config.js |
|
const colors = { |
|
light: { |
|
bg: '#f1f5f9', |
|
surface: '#f8fafc', |
|
post: '#ffffff', |
|
border: '#cbd5e1', |
|
text: '#475569', |
|
'text-light': '#64748b', |
|
accent: '#94a3b8', |
|
highlight: '#e2e8f0' |
|
}, |
|
dark: { |
|
bg: '#0f172a', |
|
surface: '#1e293b', |
|
post: '#334155', |
|
border: '#475569', |
|
text: '#cbd5e1', |
|
'text-light': '#94a3b8', |
|
accent: '#64748b', |
|
highlight: '#475569' |
|
} |
|
}; |
|
|
|
/** |
|
* Convert hex to RGB |
|
*/ |
|
function hexToRgb(hex) { |
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); |
|
return result ? { |
|
r: parseInt(result[1], 16), |
|
g: parseInt(result[2], 16), |
|
b: parseInt(result[3], 16) |
|
} : null; |
|
} |
|
|
|
/** |
|
* Calculate relative luminance |
|
*/ |
|
function getLuminance(rgb) { |
|
const [r, g, b] = [rgb.r / 255, rgb.g / 255, rgb.b / 255].map(val => { |
|
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4); |
|
}); |
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b; |
|
} |
|
|
|
/** |
|
* Calculate contrast ratio |
|
*/ |
|
function getContrastRatio(color1, color2) { |
|
const rgb1 = hexToRgb(color1); |
|
const rgb2 = hexToRgb(color2); |
|
|
|
if (!rgb1 || !rgb2) return 0; |
|
|
|
const lum1 = getLuminance(rgb1); |
|
const lum2 = getLuminance(rgb2); |
|
|
|
const lighter = Math.max(lum1, lum2); |
|
const darker = Math.min(lum1, lum2); |
|
|
|
return (lighter + 0.05) / (darker + 0.05); |
|
} |
|
|
|
/** |
|
* Check if contrast meets WCAG AA standards |
|
*/ |
|
function checkContrast(ratio, isLargeText = false) { |
|
if (isLargeText) { |
|
return ratio >= 3.0; // Large text or UI components |
|
} |
|
return ratio >= 4.5; // Normal text |
|
} |
|
|
|
/** |
|
* Format ratio with pass/fail indicator |
|
*/ |
|
function formatResult(ratio, isLargeText = false) { |
|
const passes = checkContrast(ratio, isLargeText); |
|
const standard = isLargeText ? '3.0:1' : '4.5:1'; |
|
const status = passes ? '✓ PASS' : '✗ FAIL'; |
|
return `${ratio.toFixed(2)}:1 (${status} - requires ${standard})`; |
|
} |
|
|
|
console.log('='.repeat(80)); |
|
console.log('WCAG AA CONTRAST RATIO TEST'); |
|
console.log('='.repeat(80)); |
|
console.log(''); |
|
|
|
// Test Light Mode |
|
console.log('LIGHT MODE:'); |
|
console.log('-'.repeat(80)); |
|
|
|
const lightTests = [ |
|
{ name: 'Text on background', fg: colors.light.text, bg: colors.light.bg }, |
|
{ name: 'Text-light on background', fg: colors.light['text-light'], bg: colors.light.bg }, |
|
{ name: 'Text on post', fg: colors.light.text, bg: colors.light.post }, |
|
{ name: 'Text-light on post', fg: colors.light['text-light'], bg: colors.light.post }, |
|
{ name: 'Text on surface', fg: colors.light.text, bg: colors.light.surface }, |
|
{ name: 'Text-light on surface', fg: colors.light['text-light'], bg: colors.light.surface }, |
|
{ name: 'Text on highlight', fg: colors.light.text, bg: colors.light.highlight }, |
|
{ name: 'Text-light on highlight', fg: colors.light['text-light'], bg: colors.light.highlight }, |
|
{ name: 'Accent on background', fg: colors.light.accent, bg: colors.light.bg }, |
|
{ name: 'Accent on post', fg: colors.light.accent, bg: colors.light.post }, |
|
{ name: 'Text on accent (buttons)', fg: '#ffffff', bg: colors.light.accent }, |
|
]; |
|
|
|
lightTests.forEach(test => { |
|
const ratio = getContrastRatio(test.fg, test.bg); |
|
const normalResult = formatResult(ratio, false); |
|
const largeResult = formatResult(ratio, true); |
|
console.log(`${test.name.padEnd(35)} Normal: ${normalResult}`); |
|
if (!checkContrast(ratio, false) && checkContrast(ratio, true)) { |
|
console.log(`${' '.repeat(35)} Large: ${largeResult}`); |
|
} |
|
}); |
|
|
|
console.log(''); |
|
|
|
// Test Dark Mode |
|
console.log('DARK MODE:'); |
|
console.log('-'.repeat(80)); |
|
|
|
const darkTests = [ |
|
{ name: 'Text on background', fg: colors.dark.text, bg: colors.dark.bg }, |
|
{ name: 'Text-light on background', fg: colors.dark['text-light'], bg: colors.dark.bg }, |
|
{ name: 'Text on post', fg: colors.dark.text, bg: colors.dark.post }, |
|
{ name: 'Text-light on post', fg: colors.dark['text-light'], bg: colors.dark.post }, |
|
{ name: 'Text on surface', fg: colors.dark.text, bg: colors.dark.surface }, |
|
{ name: 'Text-light on surface', fg: colors.dark['text-light'], bg: colors.dark.surface }, |
|
{ name: 'Text on highlight', fg: colors.dark.text, bg: colors.dark.highlight }, |
|
{ name: 'Text-light on highlight', fg: colors.dark['text-light'], bg: colors.dark.highlight }, |
|
{ name: 'Accent on background', fg: colors.dark.accent, bg: colors.dark.bg }, |
|
{ name: 'Accent on post', fg: colors.dark.accent, bg: colors.dark.post }, |
|
{ name: 'Text on accent (buttons)', fg: '#ffffff', bg: colors.dark.accent }, |
|
]; |
|
|
|
darkTests.forEach(test => { |
|
const ratio = getContrastRatio(test.fg, test.bg); |
|
const normalResult = formatResult(ratio, false); |
|
const largeResult = formatResult(ratio, true); |
|
console.log(`${test.name.padEnd(35)} Normal: ${normalResult}`); |
|
if (!checkContrast(ratio, false) && checkContrast(ratio, true)) { |
|
console.log(`${' '.repeat(35)} Large: ${largeResult}`); |
|
} |
|
}); |
|
|
|
console.log(''); |
|
console.log('='.repeat(80)); |
|
|
|
// Summary |
|
let lightFailures = 0; |
|
let darkFailures = 0; |
|
|
|
lightTests.forEach(test => { |
|
const ratio = getContrastRatio(test.fg, test.bg); |
|
if (!checkContrast(ratio, false)) lightFailures++; |
|
}); |
|
|
|
darkTests.forEach(test => { |
|
const ratio = getContrastRatio(test.fg, test.bg); |
|
if (!checkContrast(ratio, false)) darkFailures++; |
|
}); |
|
|
|
console.log(`SUMMARY:`); |
|
console.log(`Light mode failures: ${lightFailures}`); |
|
console.log(`Dark mode failures: ${darkFailures}`); |
|
console.log(''); |
|
|
|
if (lightFailures === 0 && darkFailures === 0) { |
|
console.log('✓ All contrast ratios meet WCAG AA standards!'); |
|
} else { |
|
console.log('✗ Some contrast ratios need adjustment.'); |
|
console.log(''); |
|
console.log('RECOMMENDATIONS:'); |
|
if (lightFailures > 0) { |
|
console.log('- Light mode: Consider darkening text colors or lightening backgrounds'); |
|
} |
|
if (darkFailures > 0) { |
|
console.log('- Dark mode: Consider lightening text colors or darkening backgrounds'); |
|
} |
|
}
|
|
|