The design system

One source of truth. Every colour, size, radius, shadow and curve on this site comes from assets/css/tokens.css. Nothing below that file hardcodes a value.

The site has a daytime and a bedtime

A parent and a four-year-old, side by side on a sofa at 7pm. One tablet propped on a cushion between them, curtains drawn, one warm lamp on. The child’s hands are on the screen; the parent’s voice is doing the work.

That sentence decided everything else. The making-of and the activities are daytime: white paper, ink, colour. The story is bedtime, a low-glare dusk surface with a Lights Down mode. Same voice, two worlds.

The four worlds

Studio

Tab 1. Pure white paper, moss and graphite. Daytime, curious, ink on a desk.

Forest

Tab 3. White paper with the full palette. Daytime, active, colourful.

Dusk

Tab 2. Deep spruce-teal with lamplight amber. Bedtime, low-glare by design.

Lights down

The story, one step deeper. Everything but the picture goes away.

Colour

A forest at dusk: moss, bark, lamplight and rosehip. Every value is OKLCH.

Ink

--moss-900
oklch(0.22 0.03 118) · #191c0c

Deep moss

--moss-700
oklch(0.38 0.07 118) · #3f4718

Moss — primary

--moss-500
oklch(0.58 0.115 118) · #758329

Pale moss

--moss-300
oklch(0.76 0.085 118) · #acb97a

Moss wash

--moss-100
oklch(0.94 0.03 118) · #e9eed8

Lights down

--dusk-900
oklch(0.18 0.04 210) · #00161b

Dusk — story surface

--dusk-700
oklch(0.28 0.05 205) · #003035

Mid dusk

--dusk-500
oklch(0.4 0.055 200) · #1c5153

Ember — the lamp

--ember-500
oklch(0.7 0.165 55) · #ea7d1c

Pale ember

--ember-300
oklch(0.84 0.1 65) · #f8bd84

Rosehip

--berry-500
oklch(0.55 0.15 25) · #b94642

Sky

--sky-400
oklch(0.72 0.09 235) · #69aed5

Bark — muted text

--bark-600
oklch(0.45 0.035 70) · #625240

Paper

--paper
oklch(1 0 0) · #ffffff

Contrast, measured

These ratios are computed from the tokens, not typed in by hand. Two of them fail, which is exactly why they are written down: white is never used on ember or on sky, and moss‑500 is a large-text colour only.

ForegroundBackgroundRatioVerdict
--moss-900 --paper 17.24:1 ✓ Body text
--bark-600 --paper 7.48:1 ✓ Body text
--moss-700 --paper 9.85:1 ✓ Body text
--moss-500 --paper 4.17:1 △ Large text only
--berry-500 --paper 5.23:1 ✓ Body text
--moss-900 --moss-100 14.55:1 ✓ Body text
--bark-600 --moss-100 6.32:1 ✓ Body text
--moss-900 --ember-500 6.13:1 ✓ Body text
--paper --ember-500 2.81:1 ✗ Never use
--moss-900 --sky-400 7.07:1 ✓ Body text
--paper --sky-400 2.44:1 ✗ Never use
--paper --dusk-700 14.31:1 ✓ Body text
--ember-500 --dusk-700 5.09:1 ✓ Body text
--ember-300 --dusk-700 8.60:1 ✓ Body text
--moss-300 --dusk-700 6.79:1 ✓ Body text
--paper --dusk-900 18.58:1 ✓ Body text
--ember-300 --dusk-900 11.17:1 ✓ Body text
--paper --moss-700 9.85:1 ✓ Body text
--paper --berry-500 5.23:1 ✓ Body text

Type

A contrast-axis pair. Caprasimo is a chunky rounded display face with hand-lettered poster weight; Faustina is a warm variable text serif. Both are self-hosted, latin subset, 75 KB in total.

The cub bear

Caprasimo 400 · --step-6 · clamp(3.2rem, 1.10rem + 10.2vw, 6rem)

How the Cub Bear Was Made

Caprasimo 400 · --step-4 · page titles

Everyone in the wood was asleep. Everyone except one small bear.

Faustina 400 · --step-1 · read-aloud, 1.75 line-height

Body copy sits at --step-0 with a 1.6 line-height and a 62-character measure. Read-aloud text deliberately starts a step higher, because a parent reads it at arm’s length across a cushion rather than at desk distance.

Faustina 400 · --step-0 · 62ch measure

Small label · --step‑‑1 · 600 weight

Faustina 600 · used sparingly, never as a section eyebrow

Space, shape and motion

Space

4px base, 4 → 128. Section rhythm is fluid: clamp(4rem, 9vw, 9rem).

Shape

10 / 18 / 28 / 44 / full. This brand is round, so the scale is generous and the badge is a circle.

Touch targets

Nielsen Norman’s research puts the minimum for ages 3–6 at about 2cm. Chrome is 64px, anything a child aims at is 96px, and the play button is 132px.

Motion

Custom ease-out curves only. No bounce, no elastic, never transition: all, never an entrance from scale(0). Every animation has a reduced-motion alternative.

ease-out-quart ease-out-expo ease-in-out

Components

The round tab

Moss Ember Berry

Buttons and chips

Player transport

Stop
Sound

Panels and notes

Panel. A full border and a surface — never a coloured stripe down one side.

Sunk panel, for a quiet aside.

Note

A titled aside, used for practical asides to the grown-up.

Rules this system keeps

  • White is never used on ember or on sky. Those pairs measure 2.81:1 and 2.44:1. Ember buttons carry moss-900 ink instead.
  • No drag, anywhere. Fine positioning fails for three- to six-year-olds, so volume is a three-step stepper and the player has no scrubber.
  • No hover-only affordance. Every hover state is gated behind (hover: hover) and (pointer: fine) and never carries information on its own.
  • Content is never gated on an animation. Reveals enhance an already-visible page, so a hidden tab, a headless render or a JS failure still ships something complete.
  • No uppercase tracked eyebrow above every section. One hairline rule and a title. Numbered markers appear only on the making-of page, because that section genuinely is a sequence.
  • Semantic z-index only. base, badge, sticky, scrim, dialog, toast. No 999.