Design system
The Workshop Notebook. A warm-paper workbench for reading, judging and correcting the design of a game. Every token, every named rule and every shared component, rendered by the system itself.
Colours
paper#f7f3e9
oklch(0.965 0.014 88)dark #1c1913ground#efe9dc
oklch(0.935 0.018 86)dark #13100braised#fcfaf4
oklch(0.985 0.008 88)dark #26221cink#2c221b
oklch(0.260 0.020 55)dark #e7e2d9ink-hover#1b130d
oklch(0.195 0.018 58)dark #faf6efmuted#6e625a
oklch(0.505 0.020 58)dark #9a9289line#d8d2c7
oklch(0.865 0.016 82)dark #36312aline-strong#8e8279
oklch(0.615 0.020 62)dark #81776dfocus#9c470d
oklch(0.500 0.130 48)dark #d78958focus-ink#fbf8f2
oklch(0.980 0.008 88)dark #17130edanger#a72629
oklch(0.480 0.165 25)dark #e67a73data-1 … data-8 · inside diagrams and legends only, never in the chrome
Type scale
Elevation
0 1px 2px rgba(94, 72, 55, 0.12)0 2px 9px rgba(94, 72, 55, 0.14)0 10px 30px rgba(94, 72, 55, 0.18)Shared components
Primary Button
The single most likely action on a screen. Ink on paper, never accent-filled.
Ghost Button
Every secondary action. No fill until hover.
Text Input
Leaf fill on paper, rust border on focus. No glow.
Filter Chips
Exactly one selected per group. The selected chip is the only place the accent fills anything.
Game Switcher
Always present inside a game. Marks the current game with weight and aria-current, never with colour.
Hollow Reach
Read-Only Notice
States the limitation where the missing button would be, rather than leaving it to be inferred.
Prose Block
The game's own writing, in the serif, capped at 68ch.
The well does not give back what is thrown into it. Vael knows this, and still they go down: once a year, with the rope measured and a dead name in the mouth.
Breadcrumb
Says where you are as well as where you came from. Replaces every one-off back link.
Negative States
Empty, loading and refused in one shape. No box, no stripe, capped at 62ch. Loading appears only after 200ms.
Catalogue Table
The signature surface. Uniform 36px rows, serif names with the slug inline, named absences, tabular numbers, scrolling in its own container rather than squeezing.
| Quest | Zone | Requires | Level |
|---|---|---|---|
| The ash well ash-well | Vael Crypt | Mothwing cloak | 12 |
| Shadow market shadow-market | no zone | no requirement | 4 |
Page Header
One row, one order, on every screen: wordmark, game switcher, destinations, spacer, account, sign out.
Scales
Spacing
xs4pxsm8pxmd12pxlg16pxxl24pxxxl32pxxxxl40pxgutter24pxrail280pxpage-max1440px
Radius
sm3pxmd5pxpill999px
Density
header-height48pxbreadcrumb-height32pxrow-height36pxrow-compact28pxcontrol-height32pxprose-measure68chstate-measure62ch
Motion
ease-out-quartcubic-bezier(0.25, 1, 0.5, 1)duration-state120msduration-disclosure180ms
Breakpoints
narrow780pxrail-folds1100pxtable-min720px
Named rules
- The Two Exemptions Rule. The chrome is achromatic. Exactly two chromatic tokens exist outside the data palette, accent and danger, and a third is admitted only against this test: not 'would a colour help' but 'is there a fact on screen that nothing else says'.
- The Ink Button Rule. The primary button is ink on paper. A button filled with the accent is forbidden.
- The Two Grounds Rule. ground is darker than paper on purpose. The diagram lies on the desk; the reading panels are sheets on top of it.
- The Two Voices Rule. The tool speaks sans, the game speaks serif. If it would survive the game shipping, it is serif.
- The Rendered Prose Rule. A longtext value is the game's own writing and is markdown: a screen renders it and never shows the source. Two shapes, and the room decides which: a block in the Prose role, capped at the measure, where there is a column for it, and one clipped line in a 36-pixel row, with the whole of it on the thing's own page. The rendering is the server's, from the one renderer this product has, and it reaches the browser only on the browser's own route.
- The Band Rule. A screen made of bands is a stack of peers: one section each, one Headline heading each, separated by xxxl and by a 1px line rule above every heading after the first. Nothing is ranked under a band by type size, because no two adjacent roles are far enough apart to say so, and nothing is ranked by face, because a face change means a change of owner. Two bands may stand side by side at 1200px and above, and only when they encode the same unit and are read against each other; the pair shares one grid measurement, and falls back to one column when either side has no rows.
- The No Uppercase Rule. Labels are sentence case with light tracking. All-caps labels are the admin-panel tell.
- The Copyable Is Mono Rule. If a value is a machine identifier a person might paste, it is monospace.
- The Warm Shadow Rule. On the light ground every shadow is rgba(94, 72, 55, ...), the paper's own brown, and pure black is forbidden. On the dark ground it inverts: a warm shadow on a near-black desk is a rust halo rather than depth, so the dark set's three are black at .4, .45 and .5. The rule said "forbidden everywhere" for a whole build while the product and this page both did the opposite.
- The Earned Elevation Rule. A shadow answers a question about stacking, never about importance.
- The Named Absence Rule. Nothing is blank because a value is missing. Absent gets a word, empty gets a different word.
- The Drawn Boolean Rule. A yes or a no is a mark and not a word: a check in data-5 for true, a cross in danger for false, the em dash of a Named Absence where there is no value. Three states, three shapes, and the shape alone carries the meaning — the hue is the second thing that says it, which is what keeps the column legible without colour. The word stays as the cell's accessible name. The mark is centred under the heading that names it in a column and sits beside its label on a thing's own page. The two marks are two paths rather than an icon set: the shipped font subset carries no check, and two shapes do not justify a library.
- The Shadow Boundary Rule. An element selector in the global stylesheet does not reach inside a component's shadow root, and custom properties do. No component writes a bare button rule of its own; every one adopts the shared control stylesheet written in terms of var(--...).
- The Never Hidden Rule. Navigation is never hidden without being moved somewhere reachable. Below 780px the destinations join the game switcher's menu; exactly one of the two carries them at any width.
Do and don't
Do
- Do set every game-owned word in Literata and every tool-owned word in Fira Sans.
- Do keep rows at 36px and lists dense.
- Do tint every shadow on the light ground with rgba(94, 72, 55, ...), and let the dark ground use black.
- Do name absences in words: 'no zone', 'never reviewed', 'no requirement'.
- Do state read-only where the missing action would be.
- Do mark the current item with weight and aria-current.
- Do cap prose at 68ch.
- Do build every repeated pattern once, as a shared component.
- Do cap prose at 68ch, and nothing else.
- Do let wide content scroll inside its own container so the page never scrolls sideways.
- Do put a breadcrumb on every screen inside a game, and delete the back links it replaces.
Don't
- Don't build the generic admin panel: no icon sidebar, no card grid of counters.
- Don't reach for the dark blue SaaS dashboard.
- Don't borrow GitHub's chrome.
- Don't bury content the way the ad-choked game wiki does.
- Don't produce the empty beautiful landing page.
- Don't use #000 or #fff anywhere, including in a shadow.
- Don't fill a button, a badge or a header with the accent.
- Don't use border-left or border-right above 1px as a coloured stripe.
- Don't set a label in all caps.
- Don't nest a panel inside a panel.
- Don't leave a cell blank when the value is absent.
- Don't animate layout properties, and never use bounce or elastic easing.
- Don't add a modal before an inline alternative has been ruled out in writing.
- Don't put a measure meant for prose on the page.
- Don't hide navigation at a breakpoint without moving it somewhere.
- Don't write a bare element selector for a control inside a component and expect the global stylesheet to have styled it.
Generated from docs/design.md and docs/design-tokens.json by docs/design-system.mjs