Design
Tokens and layout
The numbers every game shares (space, type, the page’s frame, buttons, motion) and the color jobs each game maps its own palette onto, read live from the stylesheets.
On this page
A token is a named value in CSS, a custom property such as --gutter or --ink, that a page’s rules read instead of writing a number or a color themselves. Tokens come in two kinds. The house’s set sizes, spacing and motion, and they’re the same in every game. A game can’t change them. The look’s set colors, faces and corners, and each game declares its own, mapped onto the same jobs.
Every value in this chapter is read from the stylesheets as the page is drawn, so it’s always the one in use.
Space: one rhythm#
Every root declares the same five spaces (One skeleton). Here they are as the hub declares them:
| Token | Value |
|---|---|
--s1 | 4px |
--s2 | 8px |
--s3 | 16px |
--s4 | 24px |
--s5 | 40px |
static/css/site.css--s2 goes inside a group, --s3 between rows and groups, --s4 between sections and --s5 around a page. npm run check:tokens fails any stylesheet that gives one of them another value.
The page’s frame#
The layout tokens are declared once, in games/static/games/house/house.css, and every page on the house kit inherits them. No other stylesheet may set one.
| Token | Computer | Phone (640px or narrower) | Phone on its side |
|---|---|---|---|
--gutter | max(24px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)) | max(16px, env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)) | |
--frame | 10px | 0px | |
--bar-h | 44px | 30px | |
--bar-top | 12px | 8px | |
--bar-gap | 24px | 16px | |
--page-max | 960px | ||
--page-max-wide | 1180px |
games/static/games/house/house.css--gutteris where the page’s words and controls start, from each side of the window. It grows to clear a phone’s notch where there is one (Every screen fits).--frameis the margin round a framed table (the felt), inside the gutter. On a phone the felt is the screen.--bar-h,--bar-topand--bar-gapare the top bar’s height, its distance from the top, and the room under it on a home or lobby.--page-maxis a home’s or lobby’s column.--page-max-wideis for pages of panels side by side, such as a waiting room.
Safe areas and the app#
Every page asks for viewport-fit=cover, so on a notched phone on its side the page may run to the glass, and keeps clear of the notch, the rounded corners and the home bar itself (ADR 0029):
--gutteris the larger of its size and the side insets, so everything drawn from it clears the notch with nothing to remember.- A dock or a foot at the bottom pads by the larger of its padding and the bottom inset; a bar or a felt that fills a side pads by the side insets the same way.
- The app’s status bar is black and opaque on every page, since the pages run from the cream paper to the dark felt, and a translucent bar would lose its clock on the paper.
- In the app, the arrow at the top left is the only way home, so every screen has it, and nothing links outside the site.
Type roles#
Each role has one size, weight and line height (Type roles). A game picks the faces; the sizes are the house’s.
| Role | Size | Weight | Line height | Used for |
|---|---|---|---|---|
| Display | --t-display (the family’s root sets it; check:layout holds it, below) |
the game’s --display-weight |
1.1 | The page’s one title |
| Title | 28px | display | A sheet’s title | |
| Heading | 20px | display | 1.2 | A section’s or panel’s heading |
| Bar | 17px (15px on a phone) | display | What you’re playing, in the bar | |
| Body | 16px | 400 | 1.45 | Text |
| Lead | 16px | 400 | 1.45 | The line under a title, in soft ink |
| Label | 16px | 600 | 1.45 | Names a control or a value |
| Tool | 15px | 700 | The bar’s quiet tools | |
| Small | 14px | A small button’s words | ||
| Note | 14px | 400 | 1.4 | Hints, captions, a status line |
| Kicker | 12px | 700 | Over a title, upper case, letter-spaced .14em | |
| Figure | 20px (18px on a phone) | 800 | The glance’s numbers: the countdown and the score, in tabular figures |
The Heading, Body, Label and Note sizes are declared by every root (here, the hub’s), and check:tokens holds them all to one value. The rest are house.css’s alone.
Fine print at 12–13px is only for table furniture (Readable).
Buttons, panels and sheets#
| Token | Computer | Phone | 400px or narrower | 560px or narrower | Touch screen |
|---|---|---|---|---|---|
--btn-h | 44px | ||||
--btn-pad | 16px | 12px | |||
--tool-h | 36px | 44px | |||
--tool-pad | 10px | 8px | |||
--panel-pad | 16px | ||||
--panel-gap | 16px | ||||
--panel-radius | 12px | ||||
--sheet-pad | 24px | 16px |
games/static/games/house/house.css--btn-h and --btn-pad are a move’s or a page’s button. --tool-h and --tool-pad are a quiet tool in the bar or a dock’s small button, a finger’s height on a touch screen. --panel-radius is the one layout token a game may set: corners are personality.
What check:layout holds#
npm run check:layout measures the pages as drawn, in a browser, against these numbers. That includes the page’s one title, whose size each family’s root sets but every family keeps to the same two sizes:
tools/house/layout.mjs RULES · lines 40–61export const RULES = {
gutter: w => (phone(w) ? 16 : 24),
barHeight: 44,
type: {
title: { size: w => (phone(w) ? 32 : 44) },
barTitle: { size: w => (phone(w) ? 15 : 17) },
section: { size: () => 20 },
lead: { size: () => 16 },
column: { size: () => 16, name: 'body' },
note: { size: () => 14 },
label: { size: () => 16, weight: () => 600 },
kicker: { size: () => 12, weight: () => 700 },
tool: { size: () => 15, weight: () => 700 },
},
// The way up, top left of every bar (house/nav.js): a finger square, one
// per screen, at the bar's start.
back: { size: 44 },
button: { height: 44, padX: w => (phone(w) ? 12 : 16) },
tool: { height: w => (w < 768 ? 44 : 36) },
panelPad: 16,
sheet: { pad: w => (w <= 560 ? 16 : 24) },
};
The Register’s masthead is the one title off the scale, listed in EXCEPTIONS beside it.
Motion#
Things move from one vocabulary of named motions (Motion explains), each a token here and the same number in MOTION in games/static/games/house/motion.js. npm run check:house fails if the two disagree. Under reduced motion, everything that travels takes no time, and short fades stay.
| Token | Value | Reduced motion |
|---|---|---|
--m-press | 120ms | |
--m-lift | 150ms | |
--m-quick | 150ms | |
--m-glide | 250ms | 0ms |
--m-stagger | 22ms | 0ms |
--m-reflow | 220ms | 0ms |
--m-flight | 380ms | 0ms |
--m-flight-long | 580ms | 0ms |
--m-deal | 250ms | 0ms |
--m-deal-gap | 40ms | 0ms |
--m-flip | 240ms | 0ms |
--m-sweep-hold | 900ms | |
--m-sweep-hold-relaxed | 1350ms | |
--m-sweep | 350ms | 0ms |
--m-arrive | 350ms | 0ms |
--m-pop | 200ms | 0ms |
--m-nope | 360ms | 0ms |
--m-pulse | 1200ms | |
--m-sheet-in | 150ms | |
--m-sheet-out | 150ms | |
--m-banner | 2000ms | |
--m-announce | 2000ms | |
--m-toast | 4500ms | |
--m-toast-action | 6000ms | |
--m-splash | 3400ms | |
--m-prompt | 2600ms | |
--m-beckon | 1600ms | |
--m-beckon-idle | 3000ms | |
--m-celebrate | 1000ms | 0ms |
--m-hold | 1200ms | |
--m-hold-key | 2000ms | |
--m-catch-up | 1500ms | |
--m-emote | 3200ms |
games/static/games/house/house.css| Motion | For |
|---|---|
| press, lift | A button going down; a box or card lifting on hover |
| glide, stagger, reflow | Your own move, place to place; a run’s stagger; a hand closing up |
| flight, flight-long | Someone else’s (or a helper’s) move across the table |
| deal, deal-gap | Cards from the dealer to each place |
| flip | A card turning over, through its edge |
| sweep-hold, sweep | A finished trick held, then gathered to its winner |
| arrive, pop, nope, pulse | A card joining your hand; a mark placed; a refused move; a place that just changed |
| sheet-in, sheet-out, toast, toast-action | Sheets and toasts coming and going, and how long a toast stays |
| banner, announce, splash, prompt | A game moment; a moment everyone must see; a new hand; something new that’s yours to do |
| beckon, beckon-idle | Your move’s ring, and how long a routine turn waits before it |
| celebrate | A solved puzzle settling |
| hold, hold-key, catch-up | How long each move by someone else holds, a deciding one longer; when your turn catches up |
| emote | A reaction rising up the screen |
Color jobs#
The house has six color jobs (Colors have jobs). Every look maps its palette onto them, under its own kit’s names. The names differ by kit, but the jobs don’t, and the house kit reads either through its fallbacks (below).
| Job | Means | The room kit and card room | The paper (the hub, this guide, the puzzles) | The house kit reads |
|---|---|---|---|---|
| Ink | Words, outlined buttons | --ink |
--ink |
--house-ink |
| Soft | Supporting words, 4.5:1 on its surface | --soft |
--soft |
--house-soft |
| Accent | The one primary fill | --btn (its words --btn-ink) |
--accent (its words --accent-ink), each puzzle’s own |
the button classes a game gives configure() |
| Turn | Yours, your turn, picked | --turn (its words --turn-ink) |
--pick |
--house-turn |
| Good | Solved, done | --good |
--good |
|
| Bad | A mistake, a danger | --bad |
--bad |
The surfaces and faces line up the same way:
| What | The room kit and card room | The paper | The house kit reads |
|---|---|---|---|
| A sheet’s or card’s ground | --sheet |
--paper-2 |
--house-surface |
| A container’s edge | --rail, --panel-edge |
--paper-edge |
--house-edge |
| The words’ face | --ui-font |
--ui |
--house-font |
| The display face | --display |
--display |
--house-display |
A game whose names differ declares the --house-* ones itself (the puzzle kit and the hub do). Every name the house kit reads falls back to the room’s, so a table game declares nothing extra:
games/static/games/house/house.css fallbacks · lines 131–149:where(.house-scrim, .house-toasts, .house-banner, .house-splash, .house-announce, .house-cheer, .house-options, .house-section, .house-setting, .house-seg) {
--_surface: var(--house-surface, var(--sheet));
--_ink: var(--house-ink, var(--ink));
--_soft: var(--house-soft, var(--soft));
--_edge: var(--house-edge, var(--rail));
--_radius: var(--house-radius, var(--rail-radius, 14px));
--_line: var(--house-line, color-mix(in srgb, var(--_ink) 14%, transparent));
--_field-edge: var(--house-field-edge, var(--field-edge, 1.5px solid color-mix(in srgb, currentColor 50%, transparent)));
--_btn-radius: var(--house-btn-radius, var(--btn-radius, 8px));
--_display: var(--house-display, var(--display));
--_display-weight: var(--house-display-weight, var(--display-weight, 400));
--_font: var(--house-font, var(--ui-font));
--_focus: var(--house-focus, var(--focus, var(--_ink)));
--_turn: var(--house-turn, var(--turn, var(--_ink)));
--_turn-ink: var(--house-turn-ink, var(--turn-ink, var(--_surface)));
--_toast-edge: var(--house-toast-edge, var(--chip-edge, var(--_edge)));
--_scrim: var(--house-scrim, rgba(0, 0, 0, .55));
--_shadow: var(--house-shadow, 0 24px 64px rgba(0, 0, 0, .45));
}
The paper#
The hub, this guide and the pencil puzzles are printed on one palette, declared once and never set again (ADR 0036). It stays light whatever the reader’s dark-mode setting.
| Token | Value |
|---|---|
--paper | #f4efe3 |
--paper-2 | #fffdf7 |
--paper-edge | #ddd5c3 |
--desk | #e9e2d0 |
--ink | #22262b |
--soft | #5b5f66 |
--line | #c9c1ae |
--line-bold | #22262b |
--good | #2f7a57 |
--bad | #b5372c |
games/static/games/house/paper.cssChoosing isn’t doing#
Every root declares how a pick looks, drawn in the color of the words around it, so a pick never wears the fill (Choosing isn’t doing). On the paper:
| Token | Value |
|---|---|
--pick-tint | color-mix(in srgb, currentColor 9%, transparent) |
--pick-ring | inset 0 0 0 2px currentColor |
--pick-weight | 700 |
games/static/games/puzzles/kit/puzzles.cssThe card room’s tint is a little stronger, for its darker felt:
| Token | Value |
|---|---|
--pick-tint | color-mix(in srgb, currentColor 16%, transparent) |
--pick-ring | inset 0 0 0 2px currentColor |
--pick-weight | 700 |
games/static/games/cards/themes.css