Design system · Prism

Steel at rest, colour on touch.

Prism is the design system behind this site. At rest everything is monochrome — charcoal ground, white text, a muted steel accent. On hover, any interactive element draws one fresh random pastel. Colour is never a brand hue and never a resting state; it’s a reward for touch. Try the headline.

OKLCH tokens · Geist / Geist Mono / Newsreader · no framework, no build step · one style block per page · AA by default

Principles

01
1

Editorial, not appy.

Generous whitespace, a strong type hierarchy, hairline rules instead of boxes and shadows. Content leads; chrome recedes.

2

Colour is earned.

One steel accent at rest. A random pastel on hover — the Prism interaction. If everything is accented, nothing is.

3

Mono for metadata, serif for voice.

Geist Mono carries eyebrows, years, counts and labels. Newsreader italic carries the human voice. Geist does everything else.

4

Accessible by default.

AA contrast, visible focus, 44px touch targets, reduced-motion and high-contrast honoured, semantic landmarks everywhere.

5

Motion is a whisper.

Short, eased, optional. Nothing bounces or demands attention — and everything switches off under prefers-reduced-motion.

Colour

02

Nine tokens, defined once in a shared stylesheet, in OKLCH. Neutrals are true greys — zero chroma — so the pastels have the only say. Steel is the single accent that’s allowed to rest. The swatches and the values below are read live from that stylesheet — edit it once and this page rewrites itself.

--bg oklch(0.165 0 0) Page ground — neutral charcoal, no warm cast
--surface oklch(0.215 0 0) Raised surfaces: cards, menus, back-to-top
--fg oklch(0.96 0 0) Primary text and headings
--fg-mute oklch(0.72 0 0) Body and secondary text
--fg-faint oklch(0.60 0 0) Eyebrows, counts, captions, colophon
--line oklch(0.34 0 0) Hairline borders and dividers, always 1px
--accent oklch(0.66 0.006 255) Steel — dots, italic display words, underlines. The token the pastel is written into at runtime
--accent-text oklch(0.66 0.006 255) Accent for small text and links — must pass AA
--accent-soft oklch(0.66 0.006 255 / 0.16) Selection background, faint accent washes

Prism — the signature interaction

03

Named for a prism splitting neutral light: steel in, a random band of colour out. On every hover the site deals one fresh pastel — a hue drawn at random from all 360°, kept light enough to read on the dark ground.

There is no per-component CSS. A few lines of JavaScript write the pastel straight into the accent tokens on the hovered element, and clear them on mouse-out. Because the whole site already styles its hover states with var(--accent), every component prisms automatically — links, buttons, cards, diagrams, the headline above.

Two rules keep it honest: on dark surfaces the pastel may colour text; on light fills it goes to the fill or border instead, never the text. And one object gets one pastel — a card and its title never split the spectrum.

Hover anything — every touch deals a new colour

hover me again every time is different no two alike 360 hues steel at rest

The spectrum it draws from

The whole mechanism

/* motion.js — on mouseover */
const h = Math.floor(Math.random() * 360);
const pastel = `hsl(${h} 70% 82%)`;

el.style.setProperty('--accent', pastel);
el.style.setProperty('--accent-text', pastel);

/* every var(--accent) hover rule
   on the site now prisms — for free */

Typography

04

Display Geist 500 · clamp(38–64px) · -0.024em

The decisions that matter sit below the UI.

Voice Newsreader italic 400 · steel accent

The serif carries the human voice.

Metadata Geist Mono · 11.5px caps · 0.08em

Eyebrows · years · counts · labels

Body Geist 400 · 16px / 1.55 · 46ch measure

Body copy stays quiet and readable: sixteen pixels, a relaxed leading, and a measure that never stretches past what an eye can comfortably track back from.

Components

05

All live — these are the real shared components, not pictures of them. Every one prisms on hover.

Buttons

The signature morph: a rounded rectangle at rest that becomes a pill on hover. Primary fills; secondary only ever strokes.

Segmented control

Selected is always black, never accent — so a hovered and a selected segment never look alike.

Tags

Design systems AI-first Accessibility Research

Body copy, sentence case, hairline pill. Mono is reserved for true metadata, so tags never use it.

Live eyebrow

Available now · London, UK

The pulsing dot marks something live. It opens the hero of every page — and stops pulsing under reduced motion.

Arrow bullets

  • The house bullet is an arrow, never a disc
  • Geist 500, steel, hanging indent
  • One arrow style everywhere on the site

Lists, footer links and “next” affordances all share the same → — one arrow, one weight, one colour.

Link rows

Full-width rows keep a grey underline; only the arrow carries the accent, and it slides on hover.

Motion & access

06

Signature moves

  • CTA radius morph — rectangle to pill, 0.18s ease-out
  • Prism — one fresh pastel per hover, cleared on leave
  • Headline words reveal line by line, thicken on hover
  • Footer links lean toward the cursor (magnetic)
  • Live dot pulse — 1.8s, ease-out, infinite
  • Durations 0.15–0.35s; nothing ever bounces

Non-negotiables

  • AA contrast everywhere — small accent text uses its own token
  • prefers-reduced-motion switches every animation off
  • prefers-contrast deepens muted text, lines and accents
  • Visible focus: 2px accent outline, offset 3px
  • Touch targets ≥44px, even on 28px visual controls
  • Skip link, semantic landmarks, stepped headings