"""Custom palette: one brand hex in, light + dark accent tokens out, readable in both modes.""" import importlib.util import subprocess import sys from pathlib import Path PALETTE = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "palette.py" spec = importlib.util.spec_from_file_location("palette", PALETTE) palette = importlib.util.module_from_spec(spec) spec.loader.exec_module(palette) # Fixed Tebra Sky base colours the derived accents sit on (from dashboard.html). LIGHT_BG, DARK_BG = "#eaf6fb", "#182a38" LIGHT_INK, DARK_INK = "#1c2b33", "#e6eef4" LOG_ERROR = "#f68c82" # dimmest log text colour def contrast(a, b): def lum(h): c = [int(h[i:i + 2], 16) / 255 for i in (1, 3, 5)] c = [x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055) ** 2.4 for x in c] return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2] hi, lo = sorted([lum(a), lum(b)], reverse=True) return (hi + 0.05) / (lo + 0.05) def test_derived_palette_is_readable_in_light_and_dark(): for brand in ["#3d9bd0", "#ffd400", "#0b1f4d", "#7a3cff", "#e4002b", "#00a651", "#ffffff", "#000000"]: t = palette.derive(brand) for mode, bg, ink in [("light", LIGHT_BG, LIGHT_INK), ("dark", DARK_BG, DARK_INK)]: m = t[mode] assert contrast("#ffffff", m["--accent"]) >= 3, (brand, mode, "button text") assert contrast("#ffffff", m["--accent-2"]) >= 3, (brand, mode, "button text, light end") assert contrast(m["--accent"], bg) >= 3, (brand, mode, "accent text") assert contrast(ink, m["--accent-soft"]) >= 4.5, (brand, mode, "secondary button text") assert contrast(LOG_ERROR, m["--accent-dark"]) >= 4.5, (brand, mode, "log text") def test_brand_kept_when_it_already_passes(): assert palette.derive("#1f6fb2")["light"]["--accent"] == "#1f6fb2" def test_cli_prints_both_token_blocks_and_contrast_table(): out = subprocess.run([sys.executable, str(PALETTE), "#7a3cff"], capture_output=True, text=True, check=True).stdout assert "body{" in out and "body.dark{" in out and "PASS" in out and "FAIL" not in out