Design Tokens
Token Architecture
Section titled “Token Architecture”Tokens are the contract between design and code. They live as CSS custom properties on :root and are scoped via the --sk- namespace.
Every family theme is dark-only and cinematic — no light mode. The token names are shared (a room is one component); each family sets its own values in its own theme. The tables below show one family’s values as a worked reference; the structure is identical across families.
Brand Palette (per family)
Section titled “Brand Palette (per family)”The accent is --sk-iris (each family sets its own hue). --sk-iris-a11y is the accessible small-text variant — use it for eyebrows, numerals, captions, and any text under ~16px. --sk-iris-dim is the subdued accent.
| Token | Role | Reference value |
|---|---|---|
--sk-iris |
accent | #946894 mauve |
--sk-iris-a11y |
accent, small text (≥4.5:1) | #B48AB4 (6.74:1) |
--sk-iris-dim |
subdued accent | #6b4f6b |
--sk-surface-0 |
ground | #0b0c0f |
--sk-mist / --sk-mist-strong are the accent at 20% / 55% opacity. The One Light Rule holds across every family: the accent appears at ≤10% presence, on the single most important affordance.
Font Roles (per family)
Section titled “Font Roles (per family)”Three roles, owned per family, so a shared component marks labels, titles, and prose by role and each family resolves its own faces. Label and body default to the platform fonts; the display face varies per family.
| Role | Token | Reference value |
|---|---|---|
| Display — titles, italic accents | --sk-font-display |
a per-family serif or display face |
| Label — eyebrows, meta, UI | --sk-font-label |
Raleway |
| Body — notes, subtitles, prose | --sk-font-body |
Noto Sans |
Cinema Tokens (theater)
Section titled “Cinema Tokens (theater)”| Token | Usage | Value |
|---|---|---|
--sk-cinema-stage |
the theater ground (inkier than --sk-surface-0) |
platform default #07070b |
--sk-cinema-scrim-rgb |
the theater scrim colour, as an RGB triplet | 8 8 12 — composed into a vertical top+bottom gradient in-component |
Ink Scale (Text)
Section titled “Ink Scale (Text)”| Token | Value | Contrast | Usage |
|---|---|---|---|
--sk-ink |
rgba(255, 255, 255, 0.92) |
passes all | Body text, headings |
--sk-ink-dim |
rgba(255, 255, 255, 0.72) |
9.8:1 | Secondary text |
--sk-ink-faint |
rgba(255, 255, 255, 0.55) |
5.8:1 | Tertiary text, metadata |
--sk-ink-ghost |
rgba(255, 255, 255, 0.35) |
— | Placeholder, decorative |
--sk-ink-trace |
rgba(255, 255, 255, 0.10) |
— | Borders, dividers |
A finer numeric ramp (--sk-ink-92 · -85 · -65 · -55 · -40 · -28 · -14 · -06, white at the named opacity) is defined platform-wide for plate-faithful chrome — the cinema room consumes it. The semantic names above are aliased onto it for back-compat, so both resolve in every theme.
Surfaces (Elevation)
Section titled “Surfaces (Elevation)”| Token | Value | Usage |
|---|---|---|
--sk-surface-0 |
#0b0c0f |
Base / page background |
--sk-surface-1 |
#111114 |
Card, panel |
--sk-surface-2 |
#151518 |
Raised card, dropdown |
--sk-surface-3 |
#1a1a1f |
Modal, dialog |
--sk-surface-4 |
#222228 |
Tooltip, highest elevation |
Glass / Overlay
Section titled “Glass / Overlay”| Token | Value | Usage |
|---|---|---|
--sk-glass |
rgba(10, 10, 12, 0.55) |
Frosted glass effect |
--sk-glass-blur |
10px |
Backdrop blur amount |
--sk-overlay |
rgba(0, 0, 0, 0.72) |
Modal overlays, image scrim |
Typography
Section titled “Typography”| Token | Value | Usage |
|---|---|---|
--sk-font-sans |
"Raleway", system-ui, ... |
Body text, UI labels |
--sk-font-mono |
ui-monospace, SFMono-Regular, ... |
Code, technical content |
--sk-title-size |
clamp(22px, 2.2vw, 32px) |
Responsive heading size |
--sk-subtitle-size |
12px |
Subtitle text |
--sk-meta-size |
12px |
Metadata text |
--sk-title-track |
0.06em |
Title letter spacing |
--sk-subtitle-track |
0.18em |
Subtitle letter spacing (wide) |
--sk-meta-track |
0.18em |
Metadata letter spacing (wide) |
Motion
Section titled “Motion”The Sugahara ease curve (0.2, 0.8, 0.2, 1) creates deliberate animation friction — a museum experience, not a productivity app. Durations range from 100ms (instant feedback) to 900ms (cinematic reveals).
| Token | Value | Usage |
|---|---|---|
--sk-ease |
cubic-bezier(0.2, 0.8, 0.2, 1) |
Standard easing — deliberate, weighted |
--sk-ease-out |
cubic-bezier(0, 0, 0.2, 1) |
Exit animations |
--sk-ease-in |
cubic-bezier(0.4, 0, 1, 1) |
Entry animations |
--sk-duration-instant |
100ms |
Immediate feedback |
--sk-duration-fast |
150ms |
Micro-interactions |
--sk-duration-normal |
260ms |
Standard transitions |
--sk-duration-slow |
500ms |
Page transitions, reveals |
--sk-duration-fade |
900ms |
Cinematic fades |
--sk-duration-cinematic |
8000ms |
Slow-zoom Ken Burns on tiles |
Shadows
Section titled “Shadows”Shadows use warm-black with iris tinting at higher elevations for a cinematic dark feel.
| Token | Usage |
|---|---|
--sk-shadow-2xs through --sk-shadow-2xl |
Standard elevation scale |
--sk-shadow-glow |
Iris-tinted glow for featured elements |
--sk-shadow-glow-sm |
Smaller iris glow |
--sk-shadow-cinematic |
Deep overlay shadow for image tiles |
Layout
Section titled “Layout”| Token | Value | Usage |
|---|---|---|
--sk-gutter |
0px |
Grid gutter (edge-to-edge) |
--sk-tile-radius |
0px |
(Legacy — replaced by the radius-variant tokens below.) |
--sk-overlay-pad |
clamp(22px, 3vw, 40px) |
Overlay content padding |
Border-Radius Variants (per-tenant)
Section titled “Border-Radius Variants (per-tenant)”Two tokens carry the tenant’s chosen border-radius, set on <body> via the data-radius attribute (orthogonal to data-theme). Photo containers use --sk-radius-photo; card-shaped chrome uses --sk-radius-box. Components MUST consume these tokens — never hardcode a numeric border-radius.
| Variant | --sk-radius-box |
--sk-radius-photo |
Visual character |
|---|---|---|---|
none |
0 |
0 |
Sharp / editorial — no rounded corners anywhere |
light (default) |
2px |
4px |
Standard — subtle softness |
round |
4px |
8px |
Soft / familial — pronounced rounding |
Each family is assigned one variant as part of its identity — from sharp and editorial to soft and familial.
The none guarantee
Section titled “The none guarantee”When a family is set to none, zero rounded corners appear anywhere on their gallery surface. Several mechanisms enforce this:
- The radius tokens resolve to
0under thenonevariant. - A pre-commit lint rule blocks hardcoded border-radius literals and
rounded-*utilities outside a small capsule allowlist. - The capsule allowlist is a single named constant — only true capsules (circles, pills, scrubber tracks) are exempt.
- A contract test asserts every inline
border-radiusreferences a token and that the tokens resolve to0under thenonevariant.
External iframes (embedded video player chrome) are out of scope — we control the iframe element’s outer corners (which obey the box token), not the player’s internal UI.
shadcn/ui Overrides
Section titled “shadcn/ui Overrides”The theme also overrides the standard shadcn/ui CSS custom properties (--background, --foreground, --primary, --card, etc.) to match the dark palette, consumed automatically by any shadcn/ui component.
Adding a New Family
Section titled “Adding a New Family”To add a new museum’s token set:
- Create a new theme with the family’s palette
- Override the
--sk-*tokens and shadcn core tokens - Apply it conditionally per family
No component code changes are needed — the same components reskin from tokens alone.