Two changes, both about finding out whether the colour system actually works.
Deleted the six categorical tint roles. Evidence: zero component usage — not one
reference outside the token file and the registry. They were hand-picked literals
of exactly the kind the palette exists to prevent, and the open question 'how
many categorical colours does the product need, and does a person choose one or
does the product assign it' is unanswered, so naming them now was guessing. The
eight palette hues stay; a categorical role gets named when a feature needs to
distinguish things.
That deletion turned a guard check vacuous, which is its own finding: the drift
check named four specific pairs (--tint-purple vs --accent-tint, etc.) and would
have passed silently forever once those tokens were gone. Replaced with the
general rule — no two family steps may resolve to the same palette step — which
catches the same class for any future family without naming pairs. Falsified by
pointing --danger-tint at the purple ramp.
Added /proving-ground: a fictional fleet-monitoring dashboard, deliberately
nothing to do with Buzz. Specimen pages are honest but sympathetic — one
component at a time, on a chosen backdrop, at a chosen size. A dashboard puts
forty decisions next to each other, which is where a missing role shows up. It
is also the surface a generated theme has to be judged on: 'Neon Tokyo' can look
fine as twelve swatches and be unusable as a page.
Built only from role tokens, and it held: no new roles were needed, contrast
passes both modes, and the layout works at 1440 and 620. Page-local parts live
in the feature rather than shared/ui on purpose — this page exists to find out
what repeats, so promoting its pieces first would be guessing.
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
The colour system had three layers, and the middle one held two different kinds
of thing under one name. --neutral-1..12 is a real scale; --accent-2 and
--danger-9 are five specific jobs (tint, tint-hover, border, fill, text) wearing
ramp-style numbering. So --accent-2 read as 'a light purple' when it meant 'the
surface an accent tint sits on'.
Underneath it was 114 hand-picked hex values with nothing enforcing that two
tokens doing the same job agreed. They drifted: --accent-2 and --tint-purple were
the same purple in light mode and two different purples in dark. I introduced
that yesterday and did not notice.
Four layers now:
0 palette --palette-purple-3 every hue, 12 steps, authored per mode
1 families --accent-tint the five jobs a hue does, named not numbered
2 roles --bg-accent-tint what a thing is in the interface
3 components bg-accent-tint Tailwind utilities
Dark stops being freehand. A family points at a palette step and .dark redefines
the whole palette, so 50 hand-picked dark values are simply gone.
This also answers a concrete trap. Tailwind's purple gets MORE saturated as it
descends (950 is 87% saturation), so a dark tint drawn from its bottom end reads
as oversaturated, and the only way to reach a muted deep purple was to write
purple-950/50 in a component. That value is correct — it composites to within a
few percent of the palette step now in use — but as an expression it has no name,
no light/dark pair, and nothing the contrast guard can measure. The palette's
dark ramps are authored for dark surfaces, so it is a step you pick.
New scripts/check-color.mjs enforces it: rejects opacity modifiers on colour
utilities, color-mix() and alpha rgb()/hsl() in component code, components
reaching past the role layer, and the layering itself — palette steps must be
literals, family steps must reference the palette, two tokens doing one job must
resolve to one step, and dark must not restate families. Falsified by
reintroducing the original drift and each other failure mode in turn.
Values are Radix Colors (MIT), transcribed rather than depended on; Radix is not
on Block's Tech Radar so this is a values-only copy with no package. Its
twelve-step contract is the one this system already described in comments, step
for step. Two documented divergences, both found by the contrast guard: family
text takes step 12 rather than the 11 Radix names 'low-contrast text' (Radix
sizes 11 for WCAG 4.5:1; every hue's 11 measured Lc 55-61 against our Lc 60
target), and dark neutral surfaces stay hand-authored because they were sized
against the real panel stack.
The colour page now renders the palette, so picking a step is visual.
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>