Files
hermes-scripts-backup/skills/software-development/android-compose-development/scripts/verify_wcag_contrast.py
T
2026-08-28 11:43:34 -04:00

69 lines
2.7 KiB
Python

#!/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!")