/* * The frozen design tokens, shared by every one of kuhy's web repos. * * Source of truth: ~/src/utils/unified-design-system/tokens.md (values) and * README.md (the CSS custom-property names). The same numbers ship for Dart in * ~/src/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 */ /* Motion ----------------------------------------------------------------- */ /* Frozen in ~/src/utils/unified-design-system/motion.md, which carries the * rationale per step. The same numbers ship for Dart as AppDuration / * AppCurve — a change here is a change there. Four steps only: a longer * duration vocabulary gets picked from at random, which is drift wearing a * token's name. */ --duration-instant: 0ms; /* the reduced-motion collapse target */ --duration-fast: 120ms; /* hover, press, checkbox — already under the cursor */ --duration-base: 200ms; /* default: enters/leaves/moves within the view */ --duration-slow: 320ms; /* sheets, dialogs, page transitions */ /* Named for what the motion does at its END — the part the eye reads. Never * ease-in-out on something that enters or exits: symmetric easing makes an * arrival look like it is braking and a departure look reluctant. */ --ease-standard: cubic-bezier(0.2, 0, 0, 1); /* starts and ends on screen */ --ease-decelerate: cubic-bezier(0, 0, 0, 1); /* entering: full speed -> rest */ --ease-accelerate: cubic-bezier(0.3, 0, 1, 1); /* leaving: no settle */ } @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; } } /* Mandatory, and it collapses the tokens themselves rather than asking every * consumer to branch. Easing is irrelevant at zero duration, so the curves are * left alone. Sound and haptics are NOT motion and are deliberately untouched * here: a user who suppressed animation did not ask to lose confirmation that * a tap landed. The sound opt-out is a separate, user-facing setting. */ @media (prefers-reduced-motion: reduce) { :root { --duration-fast: 0ms; --duration-base: 0ms; --duration-slow: 0ms; } } 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; } /* Print default (brennenputh.me/blog/printable-websites). Colour tokens are * dropped for paper: black on white, links spelled out, code boxed. Elements * marked .noPrint (nav, header, controls) vanish; .onlyPrint is the inverse * and is hidden on screen by the rule below the block. */ @media print { .noPrint, nav.noPrint, header.noPrint { display: none; } body { background: white; color: black; font-size: 12pt; } h1 { font-size: 16pt; } h2 { font-size: 14pt; } h3 { font-size: 13pt; } pre, code { border: 1px solid black; font-size: 11pt; break-inside: avoid; } figure, img { break-inside: avoid; } a { color: black; } a[href^="http"]::after { content: " (" attr(href) ")"; } } @media screen { .onlyPrint { display: none; } }