Skip to content

Design Tokens

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.

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.

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
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
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.

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
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
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)

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 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
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

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.

When a family is set to none, zero rounded corners appear anywhere on their gallery surface. Several mechanisms enforce this:

  1. The radius tokens resolve to 0 under the none variant.
  2. A pre-commit lint rule blocks hardcoded border-radius literals and rounded-* utilities outside a small capsule allowlist.
  3. The capsule allowlist is a single named constant — only true capsules (circles, pills, scrubber tracks) are exempt.
  4. A contract test asserts every inline border-radius references a token and that the tokens resolve to 0 under the none variant.

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.

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.

To add a new museum’s token set:

  1. Create a new theme with the family’s palette
  2. Override the --sk-* tokens and shadcn core tokens
  3. Apply it conditionally per family

No component code changes are needed — the same components reskin from tokens alone.