feat(design): the scheme set, the colour contract and the token utilities (E28 S3a)

Four schemes (Midnight; Forest cyan, Slate fuchsia, Matrix lime), YouTube
and Starship retired with no data migration: normalizeTheme maps a stored
retired scheme to Midnight. A vitest contract measures every scheme x mode
(text 4.5:1, control edge 3:1, status text, the accent's 30-degree hue gap);
it caught light-mode danger at 4.43:1, now red-700. The state layers, the
strong edge, the touch target and the fades are index.css tokens and
utilities the lab now reads. Backdrops recoloured to the new accents, with
a grey overlay grain that actually breaks the banding (longest flat run
15-26px -> 4-8px). The glass tuner is retired.
This commit is contained in:
2026-09-24 04:33:13 +02:00
parent 9c4a999e54
commit ad136c3fe8
26 changed files with 326 additions and 851 deletions
+13 -22
View File
@@ -1,5 +1,6 @@
// The decided values the lab's reference renderings share (batches A and B, 2026-09-24). They are
// the lab's stand-ins until S3 moves each into index.css / the ui/ primitives; then these go.
// The decided values the lab's reference renderings share (batches A and B, 2026-09-24). The colour
// rules are real now (E28 S3a): each constant below names the index.css utility or token that holds
// it, so the lab renders what the app ships. The height ladder moves into ui/Button in S3b.
/** B1 compact ladder: explicit heights, so one toolbar row lines up at 28. */
export const H = {
@@ -12,25 +13,15 @@ export const H = {
bar: "h-7",
} as const;
/** A4: the input edge, ≥ 3:1 against the page in every scheme and mode. */
export const STRONG = "color-mix(in srgb, var(--fg) 55%, var(--bg))";
/** A4: the input edge, ≥ 3:1 against the page in every scheme and mode (index.css --edge-strong). */
export const STRONG = "var(--edge-strong)";
/** B3 state layers: the text colour at 10% (hover) / 16% (pressed) over whatever is underneath;
* a filled control mixes its own text colour into the fill at 14% / 24%. */
export const LAYER =
"hover:bg-[color-mix(in_srgb,var(--fg)_10%,transparent)] active:bg-[color-mix(in_srgb,var(--fg)_16%,transparent)]";
export const FILLED =
"bg-accent text-accent-fg hover:bg-[color-mix(in_srgb,var(--accent-fg)_14%,var(--accent))] active:bg-[color-mix(in_srgb,var(--accent-fg)_24%,var(--accent))]";
export const DANGER =
"bg-danger text-danger-fg hover:bg-[color-mix(in_srgb,var(--danger-fg)_14%,var(--danger))] active:bg-[color-mix(in_srgb,var(--danger-fg)_24%,var(--danger))]";
/** B3 state layers (index.css utilities). */
export const LAYER = "state-layer";
export const FILLED = "state-filled";
export const DANGER = "state-danger";
/** B9: the header + fade edge — a crisp band and a 1rem gradient of the colour it sits on. Literal
* strings per colour: Tailwind only generates classes it can read whole. */
export const FADE_BELOW =
"after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-4 after:bg-[linear-gradient(to_bottom,var(--bg),transparent)] after:content-['']";
export const FADE_BELOW_PANEL =
"after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-4 after:bg-[linear-gradient(to_bottom,var(--surface),transparent)] after:content-['']";
export const FADE_ABOVE_PANEL =
"before:pointer-events-none before:absolute before:inset-x-0 before:bottom-full before:h-4 before:bg-[linear-gradient(to_top,var(--surface),transparent)] before:content-['']";
export const FADE_EDGES_X =
"linear-gradient(to right, transparent, #000 28px, #000 calc(100% - 28px), transparent)";
/** B9: the header + fade edge (index.css utilities; the host is positioned). */
export const FADE_BELOW = "fade-below";
export const FADE_BELOW_PANEL = "fade-below-panel";
export const FADE_ABOVE_PANEL = "fade-above-panel";