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

/**
* 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');
}
}