mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-12 23:50:58 +01:00
awesome-mcp-explorer needs six mutually distinguishable colours for its A-F
grades, and the design system had no answer -- one accent, and semantic roles
that deliberately cluster in one hue wedge. Raised as its own decision (per
PHASE3-PROMPT) and resolved by designing the ramp rather than mapping grades
onto success/warning/danger, which would have collapsed six steps into three.
Chosen by search, not by eye, under four hard constraints:
- pairwise CIE dE >= 20 under normal vision AND deuteranopia, protanopia and
tritanopia (worst observed 26.0)
- >= 3:1 against BOTH backgrounds, since the ramp does not re-theme. This is
the binding constraint: such colours only exist in L* 44-59.
- monotonic lightness 58 -> 44, so hue and lightness encode redundantly and
the ramp survives greyscale as an ordinal scale
- muted saturation, to sit with the rest of the palette
scripts/ramp_check.py re-runs the whole proof and exits non-zero on a
regression, so this is a gate rather than a paragraph. A seventh hue is not
available (best candidate reaches dE 17.9); group the tail into "other".
This ramp is the only token group that leaves the warm wedge, deliberately:
hue separation is the entire point of a categorical scale.
Also: tokens.test.ts guards every frozen value against drift, and
tokens.md was split (screen-size.md) to hold the 250-line cap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U4Rj3s7K2qzrLNgJqqPhno
134 lines
4.6 KiB
CSS
134 lines
4.6 KiB
CSS
/*
|
|
* The frozen design tokens, shared by every one of kuhy's web repos.
|
|
*
|
|
* Source of truth: ~/utils/unified-design-system/tokens.md (values) and
|
|
* README.md (the CSS custom-property names). The same numbers ship for Dart in
|
|
* ~/utils/design_system, so a change here is a change there — do not edit one
|
|
* side alone, and do not introduce a value that is not on a scale below.
|
|
*
|
|
* Dark is the default; light is an override, matching the README.
|
|
*/
|
|
|
|
:root {
|
|
/* Neutrals ------------------------------------------------------------- */
|
|
--bg: #211d1b;
|
|
--surface-1: #2b2624;
|
|
--surface-2: #38312e;
|
|
--border: #463e3a;
|
|
--text: #eceae9;
|
|
--text-muted: #aaa09a;
|
|
|
|
/* Accent + semantic roles ---------------------------------------------- */
|
|
--accent: #b8862e;
|
|
--info: #b8862e; /* = accent */
|
|
--success: #8a9a3c;
|
|
--warning: #e0a63c;
|
|
--danger: #e2585f;
|
|
|
|
/* Text/icons ON a filled accent/success/warning/danger surface. Never
|
|
* --text: every semantic fill sits in the same mid-light band, where
|
|
* near-white measures 2.2:1-3.6:1 against a 4.5:1 floor. */
|
|
--on-fill: #211d1b;
|
|
/* Text/icons on a dark translucent scrim/overlay — fixed, not theme-tied,
|
|
* because the scrim itself is always dark. */
|
|
--on-scrim: #eceae9;
|
|
|
|
/* Categorical ramp ------------------------------------------------------ */
|
|
/* For charts, tags and category dots — N mutually distinguishable colours
|
|
* with no inherent meaning. NEVER for semantic state; that stays
|
|
* success/warning/danger. Use them in ramp order for ordered data and by
|
|
* position for unordered data — never "the green one because green means
|
|
* good", which is the semantic vocabulary leaking back in.
|
|
*
|
|
* These deliberately leave the warm wedge every other token sits in: a
|
|
* categorical ramp has no meaning to lean on, so hue separation IS the
|
|
* requirement. Chosen by search under a >=20 CIE dE floor across normal
|
|
* vision and all three CVD types, >=3:1 on BOTH backgrounds, and a monotonic
|
|
* lightness staircase (58 -> 44) so the ramp survives greyscale. Do not edit
|
|
* a value here without re-running unified-design-system/scripts/ramp_check.py.
|
|
* They do not flip with prefers-color-scheme — they must hold on both. */
|
|
--cat-1: #c57293;
|
|
--cat-2: #398fc0;
|
|
--cat-3: #c85a32;
|
|
--cat-4: #228736;
|
|
--cat-5: #8d58bb;
|
|
--cat-6: #686d2c;
|
|
|
|
/* Corner radius --------------------------------------------------------- */
|
|
--radius-sm: 8px; /* buttons, inputs, chips */
|
|
--radius-md: 12px; /* cards */
|
|
--radius-lg: 16px; /* sheets, dialogs */
|
|
|
|
/* Spacing (4px base) ---------------------------------------------------- */
|
|
--space-1: 4px;
|
|
--space-2: 8px;
|
|
--space-3: 16px;
|
|
--space-4: 24px;
|
|
--space-5: 32px;
|
|
--space-6: 48px;
|
|
|
|
/* Typography ------------------------------------------------------------ */
|
|
--font-sans: -apple-system, "Segoe UI", Roboto, system-ui, sans-serif;
|
|
--font-mono: ui-monospace, "Cascadia Code", monospace;
|
|
|
|
--text-display: 32px;
|
|
--text-title: 24px;
|
|
--text-subtitle: 20px;
|
|
--text-body: 16px; /* the floor for anything a user actually reads */
|
|
--text-label: 14px; /* UI chrome only */
|
|
--text-caption: 12px; /* UI chrome only */
|
|
|
|
--tracking-display: -0.2px;
|
|
--tracking-title: -0.1px;
|
|
--tracking-label: 0.05em;
|
|
--tracking-caption: 0.1em;
|
|
|
|
/* Prose line-length cap (rule 21): ~65-70 characters. */
|
|
--measure: 40rem;
|
|
|
|
/* Shadows: light surfaces only, and only on floating/overlay elements —
|
|
* never inline cards or list rows. Blur = 2x offset, tinted from ink. */
|
|
--shadow-1: 0 2px 4px rgba(33, 29, 27, 0.12); /* dropdown */
|
|
--shadow-2: 0 4px 8px rgba(33, 29, 27, 0.16); /* modal */
|
|
|
|
/* Focus ring: the platform default is black, invisible on --bg. */
|
|
--focus-ring: #b8862e; /* = accent */
|
|
}
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
:root {
|
|
--bg: #f6f4f3;
|
|
--surface-1: #fcfbfb;
|
|
--surface-2: #fcfbfb;
|
|
--border: #e0dad7;
|
|
--text: #211d1b;
|
|
--text-muted: #70625b;
|
|
/* --on-fill / --on-scrim stay fixed — the fills and the scrim do not
|
|
* change with page theme. */
|
|
}
|
|
}
|
|
|
|
/* Dark surfaces take no shadow at all; elevation is a fill step. Light
|
|
* surfaces opt in explicitly via --shadow-1 / --shadow-2. */
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--shadow-1: none;
|
|
--shadow-2: none;
|
|
}
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-body);
|
|
}
|
|
|
|
/* Ships here rather than being left to each consumer: an unstyled focus ring
|
|
* is the accessibility regression this layer exists to prevent, and several of
|
|
* these apps are keyboard-only by necessity. */
|
|
:focus-visible {
|
|
outline: 2px solid var(--focus-ring);
|
|
outline-offset: 1px;
|
|
}
|