#!/usr/bin/env python3 """WCAG AA contrast ratio verification for period palettes.""" def srgb_to_linear(c): s = c / 255.0 return s / 12.92 if s <= 0.04045 else ((s + 0.055) / 1.055) ** 2.4 def relative_luminance(hex_color): r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) return 0.2126 * srgb_to_linear(r) + 0.7152 * srgb_to_linear(g) + 0.0722 * srgb_to_linear(b) def contrast_ratio(h1, h2): l1, l2 = relative_luminance(h1), relative_luminance(h2) return (max(l1, l2) + 0.05) / (min(l1, l2) + 0.05) def rgb_distance(h1, h2): r1, g1, b1 = int(h1[0:2], 16), int(h1[2:4], 16), int(h1[4:6], 16) r2, g2, b2 = int(h2[0:2], 16), int(h2[2:4], 16), int(h2[4:6], 16) return ((r1-r2)**2 + (g1-g2)**2 + (b1-b2)**2) ** 0.5 # ============ EDIT THESE FOR YOUR PALETTE ============ DAY_TEXT = "3D2B1F" NIGHT_TEXT = "D0D8E0" periods = { "DEJEUNER": ("FFA726", "FFE0B2", DAY_TEXT), "DOUCHE": ("4DB6AC", "E0F2F1", DAY_TEXT), "AVANT-MIDI": ("AED581", "F1F8E9", DAY_TEXT), "DINER": ("FFD54F", "FFF9C4", DAY_TEXT), "APRES-MIDI": ("FFB74D", "FFE0B2", DAY_TEXT), "SOUPER": ("FF8A65", "FFCCBC", DAY_TEXT), "SOIREE": ("311B92", "4527A0", NIGHT_TEXT), "DODO": ("1A237E", "0D1520", NIGHT_TEXT), } period_order = ["DEJEUNER", "DOUCHE", "AVANT-MIDI", "DINER", "APRES-MIDI", "SOUPER", "SOIREE", "DODO"] print("=" * 75) print(f"{'Period':<14} {'Text':<8} {'Bg Top':<10} {'Ratio':>6} {'Bg Bot':<10} {'Ratio':>6} {'WCAG':>7}") print("=" * 75) all_pass = True for name, (top, bottom, text) in periods.items(): r_top = contrast_ratio(text, top) r_bot = contrast_ratio(text, bottom) aa_top = "AA+ " if r_top >= 4.5 else "AA-lg" if r_top >= 3.0 else "FAIL!" aa_bot = "AA+ " if r_bot >= 4.5 else "AA-lg" if r_bot >= 3.0 else "FAIL!" if r_top < 3.0 or r_bot < 3.0: all_pass = False print(f"{name:<14} #{text} #{top} {r_top:>5.1f}:1 {aa_top} #{bottom} {r_bot:>5.1f}:1 {aa_bot}") print() print("=" * 75) print("Adjacent period distinction (RGB distance)") print("=" * 75) for i in range(len(period_order) - 1): curr, next_p = period_order[i], period_order[i+1] _, curr_bot, _ = periods[curr] next_top, _, _ = periods[next_p] dist = rgb_distance(curr_bot, next_top) l1, l2 = relative_luminance(curr_bot), relative_luminance(next_top) lum_diff = abs(l1 - l2) status = "OK" if dist > 80 or lum_diff > 0.15 else "PROCHE" if dist > 40 else "TROP PROCHE!" print(f" {curr:<12} bas #{curr_bot} → {next_p:<12} haut #{next_top} dist={dist:5.0f} Δlum={lum_diff:.3f} [{status}]") print() print("WCAG AA (4.5:1 normal / 3:1 large):", "ALL PASS" if all_pass else "ISSUES FOUND!")