Accessibility
A floor we measure, never a matter of taste.
Contrast is computed (WCAG and APCA), motion is optional, and meaning never rests on color alone.
Contrast (computed)
Body text ≥ 4.5:1, UI and large text ≥ 3:1, in both Day and Night. Colored text uses a darker
-text variant; the bright token is reserved for fills and dots. The /design-system-review skill's
contrast.py audits every WCAG pairing; lumen/system/scripts/apca.py (self-tested against the apca-w3
reference values) covers the saturated mid-tones WCAG underrates.
APCA on the saturated fills (computed 2026-06-09):
| Label on fill | WCAG | APCA Lc | Read |
|---|---|---|---|
white on --primary #EA580C (gradient start) | 3.56 | 67.6 | ✅ bold UI labels |
white on the gradient end (color-mix ≈ #EA640A) | 3.32 | 65.2 | ✅ near-uniform with the start |
white on --glow #E89200 (the old gradient end — retired) | 2.46 ❌ | 52.9 ❌ | why the gradient was fixed |
white on --destructive #DC2626 | 4.83 | 77.0 | ✅ |
#1A0E04 on Night --primary #FF8A33 | 8.06 | 57.5 | ✅ WCAG-strong; APCA-borderline for small labels — keep Night button labels ≥ 14px semibold |
Bright is for fills, not text
--primary (#EA580C) is a fill/dot color. For small colored text use --primary-text (#A8430A), which clears
AA on the light ground. Every saturated hue ships a matching -text step — including --data-4-text.
Never use --glow under a light text label.
Beyond color
Status never depends on hue alone — it pairs color with an icon and a label, so it survives the grayscale test and works for color-vision deficiencies.
| State | Color | Icon | Survives grayscale? |
|---|---|---|---|
| On track | success | check | ✓ |
| Watch | warning | triangle | ✓ |
| Critical | danger | octagon | ✓ |
Motion
Every animation respects prefers-reduced-motion. Energy is earned — tied to a real signal — never
ambient noise. Under reduced-motion, transitions collapse to near-instant and looping animation stops.