/** * Calculate relative luminance of a color * Based on WCAG 2.1 specification */ export function getRelativeLuminance(r: number, g: number, b: number): number { const [rs, gs, bs] = [r, g, b].map((c) => { const sRGB = c / 255; return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4); }); return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs; } /** * Calculate contrast ratio between two colors * Returns ratio from 1 to 21 */ export function getContrastRatio( fg: { r: number; g: number; b: number }, bg: { r: number; g: number; b: number } ): number { const l1 = getRelativeLuminance(fg.r, fg.g, fg.b); const l2 = getRelativeLuminance(bg.r, bg.g, bg.b); const lighter = Math.max(l1, l2); const darker = Math.min(l1, l2); return (lighter + 0.05) / (darker + 0.05); } /** * Parse hex color to RGB */ export function hexToRgb(hex: string): { r: number; g: number; b: number } | null { 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; } /** * Check if a contrast ratio meets WCAG standards */ export function checkWCAGCompliance(ratio: number) { return { aa: { normalText: ratio >= 4.5, largeText: ratio >= 3, ui: ratio >= 3, }, aaa: { normalText: ratio >= 7, largeText: ratio >= 4.5, }, }; }