Design system

One brand, two surfaces, three layers. The marketing site and the product are deliberately different — different type, different greys, different radii — and share exactly what a brand shares: the cobalt, the mark, the status hues, the product palettes, the motion curves and the 4px unit. Every value on these pages is read from the built stylesheet at load; nothing is typed in.

v0.1.0
01 · The model

One brand, two surfaces, three layers

A consumer loads the foundation plus exactly one surface. Each surface may redefine a name the other also uses — --gray-500, --space-4, --radius-md, --dur-slow, .sx-eyebrow — and that is intended: the two apps are different, and a page only ever sees one of them. The foundation never collides with either.

02 · The one exact value

The accent is exact

The app's stylesheet marks --blue-500 "THE accent — exact, never substitute"; the landing's calls it "the cobalt block". Both surfaces' --accent must resolve to it, and npm run check fails if either stops doing so.

Blue 500 — the accent --blue-500 — Foundation. Exact, never a close-enough periwinkle.
accent (app surface) --accent — An alias of the step above; the landing's --accent aliases the same step.
Blue 400 — strokes and marks --blue-400 — The app's --accent-bright: the wordmark's S's, the crypto category, the focus ring.
Blue 300 — on dark grounds --blue-300 — The landing's kicker and keyword colour on --lp-bg-dark; both surfaces' --accent-soft.
03 · Drift

Where the two apps disagree

The headline differences between the landing and the app, as found in their source. Most are intended — the surfaces are meant to differ — and each surface keeps its own value in its own layer rather than one of them changing silently. Where both values are in the stylesheet this page loads, they are read live; the landing-only values are named and shown live on the landing page.

  1. 01
    The grey ramps differ. The landing's --gray-* is the cool neutral ramp; the app's --gray-* is navy-tinted so a blue accent sits on it without the page reading as two colour families. The foundation keeps both, as --neutral-cool-* and --neutral-navy-*, and each surface aliases its own.
    Cool (landing) above, navy (app) below; step 500 is — against —. Both ramps, step for step, on foundation › neutrals.
  2. 02
    The all-caps label is tracked 0.14em on the landing and 0.06em in the app. Both call it --tracking-label. The app's round-12 brief dropped it from 0.14em because "the wide-tracked eyebrow was the loudest terminal-ism in the old system"; the landing's design-system eyebrow still carries the old value. App, live: —. Landing: 0.14em, live on landing › type.
  3. 03
    The eyebrow is mono on the landing and Inter in the app. Both surfaces emit a .sx-eyebrow. The landing's is the old design-system kicker — IBM Plex Mono, 11px, 0.14em caps, muted; the app's is Inter, 11px, 600, 0.06em caps, faint ("never mono — it's a word, not a number"). The app's, live: Settlement calendar.
  4. 04
    Two radius scales. The landing is sharp: 0 / 2 / 3 / 5 / 8px and a 999px pill (plus a 12px and a 24px the page CSS used as literals, now --radius-xl and --radius-2xl). The app is distinctly rounder — "roundness is most of what separates a consumer market UI from a trading terminal": — / — / — / — / — / — and —. The same names, different values, one per surface.
  5. 05
    The slow duration is 360ms on the landing and 340ms in the app. Fast and base are shared to the digit (—, —, both foundation), and so is the ease-out curve. --dur-slow is 20ms apart with no stated reason, so it lives in each surface's layer; the app's, live: —.
  6. 06
    Containers: 1200 / 1400 on the landing, 1280 in the app. The landing's --container and --container-wide (plus a 660px prose measure) against the app's --container-max, live: —, with a gutter that steps 16 → 24 → 32px (now — at this width).
  7. 07
    The app's market red is not the brand's oxblood. --up aliases --sage-500, but --down is a separate, muted red chosen so the breaking-news alarm can be louder than price direction — while the app also declares --oxblood-500 and never uses it. Both, live:
    --down — --oxblood-500 — --cat-breaking —
  8. 08
    Inter arrives two ways. The landing loads Inter 300–700 from Google Fonts (with Archivo and IBM Plex Mono); the app self-hosts one variable file, app/fonts/Inter-var.woff2, and exposes it as --font-inter. These reference pages load no webfont for the app surface — the stack below --font-inter is what you are reading — and the landing page loads the landing's three faces exactly as the site does.
  9. 09
    Two silhouettes of one mark. The four lines are identical everywhere (foundation), but the landing strokes them at — with — caps and the app at — with — caps on a padded viewBox. Neither app says which is canonical. All three on foundation › mark.
04 · Pages

The reference

Every swatch shows its label, its CSS variable in the code face (click to copy) and its live resolved value. The app page has a theme toggle; the landing page shows every specimen on both of its grounds.

05 · Using it

Load the foundation and one surface

Stylesheets

dist/css/landing.css and dist/css/app.css are each complete: tokens, typography roles and the component recipes, with foundation.css inlined. Link one of them and nothing else. The parts are also published on their own — landing.tokens.css, landing.roles.css, app.tokens.css, app.roles.css — and the app's Tailwind @theme mappings as app.tailwind.css.

Data

The resolved, flat tokens as JSON in dist/json/ and as an ES module with types in dist/js/tokens.mjs, for anything that is not CSS — a chart, a native app, a Figma sync.

Source of truth

The JSON under tokens/. dist/ is generated by npm run build and committed; never hand-edit it. npm run check runs the ten rules — the accent, the 4px grid, WCAG contrast in both themes, unique names, fresh dist, no dangling variables — and npm run serve serves these pages from the repo root.