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.
| Foreground | Background | Ratio | Verdict |
|---|---|---|---|
--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
How the Cub Bear Was Made
Everyone in the wood was asleep. Everyone except one small bear.
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.
Small label · --step‑‑1 · 600 weight
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.
Components
The round tab
Buttons and chips
Player transport
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.