---
name: Movement
description: A flyposted bill — acid stock, two inks, and the green that only exists where they overprint.
colors:
  stock: "#f0e34a"
  bone: "#f7f4e6"
  ink: "#17140f"
  blue: "#1b2a8f"
  blue-strong: "#121d68"
  cyan: "#14a0c8"
  overprint: "#2f6b46"
  dim-on-stock: "#6b5f14"
  dim-on-bone: "#6a6455"
  faint: "#8b8574"
  rule: "rgba(23, 20, 15, 0.22)"
  rule-on-stock: "rgba(23, 20, 15, 0.3)"
  rule-strong: "rgba(23, 20, 15, 0.55)"
  negative-fg: "#8e2417"
  negative-bg: "rgba(158, 42, 26, 0.14)"
typography:
  shout:
    fontFamily: "Big Shoulders, Haettenschweiler, Arial Narrow, sans-serif"
    fontSize: "clamp(4rem, 16vw, 10.5rem)"
    fontWeight: 800
    lineHeight: 0.84
    letterSpacing: "-0.02em"
  headline:
    fontFamily: "Big Shoulders, Haettenschweiler, Arial Narrow, sans-serif"
    fontSize: "clamp(1.9rem, 5vw, 3.25rem)"
    fontWeight: 800
    lineHeight: 0.92
    letterSpacing: "normal"
  title:
    fontFamily: "Big Shoulders, Haettenschweiler, Arial Narrow, sans-serif"
    fontSize: "clamp(1.35rem, 3vw, 1.9rem)"
    fontWeight: 800
    lineHeight: 1
    letterSpacing: "normal"
  body:
    fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
    fontSize: "clamp(1rem, 1.15vw, 1.125rem)"
    fontWeight: 400
    lineHeight: 1.55
    letterSpacing: "normal"
  reply:
    fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
    fontSize: "clamp(1.0625rem, 1.3vw, 1.1875rem)"
    fontWeight: 400
    lineHeight: 1.62
    letterSpacing: "normal"
  label:
    fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
    fontSize: "0.8125rem"
    fontWeight: 700
    lineHeight: 1.3
    letterSpacing: "0.06em"
  stamp:
    fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
    fontSize: "0.875rem"
    fontWeight: 700
    lineHeight: 1.2
    letterSpacing: "0.14em"
rounded:
  none: "0"
spacing:
  hair: "0.35rem"
  tight: "0.5rem"
  field: "1.35rem"
  block: "1.25rem"
  gutter: "clamp(1.5rem, 4vw, 3.5rem)"
  band: "clamp(1.75rem, 4vw, 3rem)"
  page-x: "clamp(1.25rem, 4vw, 3.5rem)"
  page-y: "clamp(1.25rem, 3vw, 2.5rem)"
components:
  stamp:
    backgroundColor: "{colors.blue}"
    textColor: "{colors.stock}"
    typography: "{typography.stamp}"
    rounded: "{rounded.none}"
    padding: "0.85rem 1.75rem"
  stamp-hover:
    backgroundColor: "{colors.blue-strong}"
    textColor: "{colors.stock}"
  stamp-disabled:
    backgroundColor: "transparent"
    textColor: "{colors.dim-on-bone}"
    rounded: "{rounded.none}"
  tab:
    backgroundColor: "{colors.stock}"
    textColor: "{colors.ink}"
    typography: "{typography.headline}"
    rounded: "{rounded.none}"
    padding: "0.9rem 1.1rem 1rem"
    height: "clamp(5rem, 7vw, 6.5rem)"
  field-input:
    backgroundColor: "transparent"
    textColor: "{colors.ink}"
    typography: "{typography.body}"
    rounded: "{rounded.none}"
    padding: "0.45rem 0"
  field-textarea:
    backgroundColor: "rgba(23, 20, 15, 0.05)"
    textColor: "{colors.ink}"
    typography: "{typography.body}"
    rounded: "{rounded.none}"
    padding: "0.55rem 0.7rem"
  notice-negative:
    backgroundColor: "{colors.negative-bg}"
    textColor: "{colors.negative-fg}"
    rounded: "{rounded.none}"
    padding: "0.75rem 0.9rem"
---

# Design System: Movement

## Overview

**Creative North Star: "The Flyposted Bill"**

This is a sheet pasted on a wall, photographed as it is: cheap coloured stock,
two inks, a heavy impression, and the halftone dots of a job that was printed
rather than rendered. Nothing here is a card floating on a canvas. The stock is
the surface — full-bleed acid yellow on the bill itself — and the reading
surfaces drop to bone because acid yellow is a shout and a shout cannot also be
a paragraph. Depth is ink and perforation. The one exception is a tear-off tab
lifting off the wall, which is a thing paper actually does.

The two inks are the whole colour theory. Ultramarine is the first ink; cyan is
the second; the green is not a chosen colour at all but the consequence of cyan
multiplied into yellow stock, offset a couple of pixels off true because a
two-colour job never registers perfectly. That misregistration is the tell that
the thing was printed, and it is load-bearing: remove the offset or swap the
blend and the world stops being a press and becomes a gradient.

Typographically it is a condensed display face shouting in caps against a plain
grotesque doing the reading. The composition is hard: ruled columns, ruled
underlines for fields, square dashes, zero radius anywhere. It refuses the
centred single-field waitlist page — the page sorts you before it asks you for
anything, and the primary action on the first surface is tearing off a tab, not
pressing a button.

**Key Characteristics:**
- Drenched stock, not accented white; two paper colours, never a screen white
- Two inks that build where they cross; green only as a product of overprint
- Zero radius everywhere — a printed sheet has corners
- Perforations, dashed rules and torn edges instead of borders and shadows
- Condensed uppercase display against a quiet body grotesque
- Self-hosted faces only; no third-party font origin exists in this world

## Colors

Two paper stocks and two inks, plus the third colour the inks make between them.

### Primary
- **Ultramarine Ink** (`{colors.blue}`): the first ink. Every headline that
  carries a claim, every outbound link's 3px underline, the stamp button's
  ground, the caret, the selection ground, and the focus outline. It is the
  voice of the press.
- **Press Cyan** (`{colors.cyan}`): the second ink, and never used as a flat
  colour on its own. It exists only as an overprint band multiplied into
  whatever is underneath: the stripe across the shout, the strike across the
  stamp's face, the mark struck across a receipt's top rule.

### Secondary
- **Overprint Green** (`{colors.overprint}`): what cyan makes of yellow stock.
  Recorded as a token because status tones are tinted from it, but it is a
  *result*, not a paint. Where it appears on a surface it is produced by
  multiply, not painted on.

### Neutral
- **Acid Stock** (`{colors.stock}`): the bill's paper. Full-bleed on the first
  surface, and the ground of every tear-off tab and every torn stub carried
  onto a later page. Also the text colour on ultramarine.
- **Bone** (`{colors.bone}`): the reading paper. Every stance sheet, receipt,
  sign-in and ledger sits on it. Warm, never white.
- **Press Ink** (`{colors.ink}`): body text, perforations, table head rules,
  field underlines.
- **Thinned Ink on Stock** (`{colors.dim-on-stock}`) and **Thinned Ink on Bone**
  (`{colors.dim-on-bone}`): secondary text, one per stock. The bill page
  swaps the muted token to the yellow-safe one on its own surface.
- **Rules** (`{colors.rule}`, `{colors.rule-on-stock}`, `{colors.rule-strong}`):
  hairlines and column rules; the stock variant is heavier because yellow
  swallows a light rule.

### Named Rules
**The Overprint Rule.** Green is never picked from a swatch. Wherever ink
crosses ink, the upper layer is `mix-blend-mode: multiply` so the inks *build*
and darken. `screen` is a defect in this world, not an option; it lightens and
turns a press into a light-box.

**The Misregistration Rule.** Every overprint band sits 1–2px off true
(`transform: translateY(2px)` on the shout, `1px` on the stamp). The offset is
the evidence of a two-colour job. It is not a rendering bug and must not be
"corrected".

**The Two-Stock Rule.** There are exactly two grounds: acid stock for the bill
and its tabs, bone for everything meant to be read. No third surface colour, and
no white anywhere — white is a screen colour and this is a printed thing.

## Typography

**Display Font:** Big Shoulders 800 (with Haettenschweiler, Arial Narrow)
**Body Font:** Public Sans 400/700 (with ui-sans-serif, system-ui)

**Character:** A tall condensed grotesque shouting in uppercase against a plain,
unfussy text face that never raises its voice. The display face is only ever
used at weight 800, uppercase, with negative tracking and sub-1 line height, so
it sets as a solid block of ink rather than a line of letters.

### The ramp

Nine steps, declared once in `theme.css`. Four display and five text. Nothing
in the app sets a literal font size — a bill has a handful of sorts in the
case, not a continuum.

| Token | Value | Used by |
|---|---|---|
| `--t-shout` | `clamp(4rem, 16vw, 10.5rem)` | the first-viewport headline, and nothing else |
| `--t-display-1` | `clamp(2rem, 5vw, 3.25rem)` | ledger title, receipt mark |
| `--t-display-2` | `clamp(1.5rem, 3.2vw, 2.75rem)` | tab word, torn stub's word |
| `--t-display-3` | `clamp(1.35rem, 3vw, 1.9rem)` | the slip's heading above a form |
| `--t-lede` | `clamp(1.0625rem, 1.3vw, 1.1875rem)` | stance reply, receipt body, tear instruction |
| `--t-body` | `clamp(1rem, 1.15vw, 1.125rem)` | the bill's two-column argument |
| `--t-base` | `0.9375rem` | ledger table |
| `--t-sm` | `0.875rem` | imprint line, stamp label |
| `--t-xs` | `0.8125rem` | field labels, tab addresses, status lines |
| `--t-xxs` | `0.75rem` | ledger column heads |

Display steps carry weight 800, uppercase, negative tracking and sub-1 line
height (0.84 on the shout, 0.9–1 below it). Text steps run 1.5–1.62.

Two relative multipliers are not steps and are allowed to stay relative: the
stance reply's first paragraph at `1.15em`, and the ledger's tally figure at
`1.5em`. Both size against their own context rather than against the ramp.

### Named Rules
**The Self-Hosted Faces Rule.** Both families are served from
`static/fonts/*.woff2` with `font-display: swap`. This is a constraint, not a
preference: the app ships `default-src 'self'`, so a third-party font link is
blocked at the browser. Never introduce a font CDN, and never add a face without
vendoring the woff2.

**The Display-Is-Caps Rule.** The condensed face appears only uppercase, only at
800, and only for shouts, marks, titles and tab words. It is never used for
running text, labels or numbers in prose — except the ledger's tally figures,
where it sets the count as a struck number beside body-face copy.

**The Nine Sorts Rule.** Every size comes from the ramp above, as a `--t-*`
token. A literal `font-size` anywhere in the app is a defect, not a local
decision — the first version of this stylesheet had twenty-one of them across
twelve unrelated values, and the ramp documented here did not exist until the
design detector pointed out that the record described a system the code had
never had. The two `em` multipliers named above are the only exceptions.

**The No-Costume-Mono Rule.** A monospace stack is declared but is not used on
any surface. Monospace here would be a costume for "technical" on pages that are
not about machines. Tabular figures in the ledger come from
`font-variant-numeric: tabular-nums`, not from a mono face.

## Layout

One column of measured width per surface, never a centred narrow strip. The bill
and the stance sheet share a `78rem` max width with fluid page padding
(`{spacing.page-y} {spacing.page-x}`); the ledger sits at `66rem`, the receipt at
`46rem`.

The bill fills the viewport (`min-height: 100svh`) only above `46rem` wide *and*
`40rem` tall, so the tear strip lands near the fold on real screens without
stranding it on short ones. Its argument sets in two hard columns divided by a
1px column rule, collapsing to one column below `46rem`.

The stance sheet is a two-up grid under a single 2px rule: reply
(`minmax(0, 1.55fr)`) beside slip (`minmax(19rem, 1fr)`), stacking below `60rem`.
The tear strip is three equal grid columns, stacking to one below `46rem` where
the tabs' vertical dashed dividers become horizontal ones.

Rhythm is fluid rather than stepped: `clamp()` bands do the vertical spacing
(`{spacing.band}` between major blocks, `{spacing.gutter}` for gutters), with
fixed small steps only inside components (`{spacing.field}` between fields,
`{spacing.hair}` label-to-input).

## Elevation & Depth

The system is flat. Depth is ink density, rules, perforation and torn edges — not
layering. Both stocks carry an authored inline-SVG halftone: a two-dot screen at
a 6px pitch, at 13% opacity on the acid stock and 7% on bone. It is a dot screen,
not a texture image, and it is what makes the stock read as paper.

There is exactly one shadow, and it belongs to a tab lifting off the wall on
hover or focus. It carries an offset *and* a blur because that is what a real
shadow has.

### Shadow Vocabulary
- **Tab lift** (`box-shadow: 0 10px 30px rgba(23, 20, 15, 0.28→0.32)`, paired
  with `translateY(-10px)` over `260ms cubic-bezier(0.16, 1, 0.3, 1)`): the only
  shadow any app surface may use. Suppressed below `46rem` (where the tab tints
  to bone instead) and under `prefers-reduced-motion: reduce`.

### Named Rules
**The One Shadow Rule.** A shadow exists in this world only to show paper
leaving the wall. Nothing else is elevated: no panels, no menus, no hovering
cards. A hard offset shadow with no blur belongs to a different world and is not
this one.

**The Halftone Rule.** Paper texture is the 6px inline-SVG dot screen at low
opacity, multiplied into the stock. Never a raster texture, never a bevel,
emboss, gradient or inner shadow.

## Shapes

Radius is zero, everywhere, on purpose: `--rst-radius`, `--rst-radius-sm` and
`--rst-radius-pill` are all `0`, and every app-authored control restates
`border-radius: 0`. A printed sheet has corners.

Form language is drawn with rules, not boxes:
- **Perforation:** a 2px `repeating-linear-gradient` at 7px on / 7px off, so the
  dashes stay square. Not a dashed border.
- **Dashed dividers:** 2px dashed ink between tear-off tabs.
- **Torn edge:** a 28-point `clip-path` polygon giving the carried-over stub a
  ragged lower edge, because that is where the perforation gave.
- **Rules:** 2px solid ink for structural divisions (strip foot, sheet head,
  receipt top and bottom, table head); 1px translucent ink for column rules and
  table rows.
- **Fields:** ruled underlines, never outlined boxes.

## Components

### Stamp (the primary button)
A blue block with the second ink struck across its face.
- **Shape:** square (`0`)
- **Primary:** ultramarine ground, acid-stock lettering, uppercase 700 at
  `0.875rem` with `0.14em` tracking, padding `0.85rem 1.75rem`; a 7px cyan bar
  multiplied across at 56% height and offset 1px.
- **Hover:** ground deepens to `{colors.blue-strong}` over `160ms ease-out`.
- **Disabled:** ground drops out entirely to a 2px dashed ink outline with
  thinned lettering, and the cyan strike is removed — an unstruck stamp.

### Tear-off Tab (signature component)
The primary action of the first surface: three equal tabs hanging off a
perforation, each printed on the stock and carrying its own address.
- **Shape:** square, no border except the 2px dashed divider between tabs and a
  2px solid rule closing the strip's foot.
- **Content:** the position word in display caps, and beneath it the URL that tab
  leads to in `0.8125rem` blue label type. Nothing else.
- **State:** lifts `-10px` with the single system shadow on hover and
  focus-visible; on narrow screens it tints to bone instead of lifting.

**The Equal Tabs Rule.** Tabs are never numbered, ranked, ordered by emphasis, or
given a "recommended" treatment. Three positions numbered 1-2-3 imply a rank;
these three are equal by design, and the only differences between them are the
word and the address.

**The Tab Carries Its Address Rule.** A tear-off tab exists to be the thing you
walk away holding, so it prints its own destination. A separate "take one" call
to action beside it says the same thing twice.

### Fields
Ruled lines on paper, one grammar for all of them.
- **Style:** transparent ground, no border except a 2px solid ink underline,
  padding `0.45rem 0`, radius `0`. Label above in 700 `0.8125rem` with `0.06em`
  tracking.
- **Textarea:** the one variation — a faint `rgba(23,20,15,0.05)` ink tint and
  boxed padding, present to show the field's extent, not as a fill. It keeps the
  same square corners and vertical-only resize.
- **Focus:** the global registration mark — a 3px ultramarine outline offset 2px,
  square.
- **Error:** a notice ruled 2px along its *top* edge in the negative tone over a
  tinted ground; never a left-border callout, never a rounded alert.

### Receipt
The terminal surface for every send, confirm, dead link and sign-in: a bone block
between two 2px ink rules, with a cyan mark struck across the top rule and the
outcome set as a display-caps mark in ultramarine.

### Ledger (admin)
The same hand, operating rather than persuading: display-caps title in
ultramarine, tallies where the figure is set in the display face at `1.5em`
beside body-face copy, and a `border-collapse` table with a 2px ink head rule,
1px row rules, uppercase `0.75rem`/`0.12em` column heads and tabular numerals.

### Browser surfaces
Not decoration — the cheapest place to stay consistent. Selection is the inks
swapped (ultramarine ground, stock lettering), caret is ultramarine, the
scrollbar is thin ink on transparent, and focus is a square 3px ultramarine
registration mark.

## Do's and Don'ts

### Do:
- **Do** build every ink-over-ink crossing with `mix-blend-mode: multiply` and a
  1–2px offset, so the colour is produced by the press rather than picked.
- **Do** keep radius at `0` on every new surface and control, including
  restating `border-radius: 0` on native controls.
- **Do** put new reading surfaces on bone and reserve the acid stock for the
  bill, its tabs, and stubs torn from it.
- **Do** draw dividers as perforations, dashed 2px rules, or solid 2px ink rules,
  and fields as ruled underlines.
- **Do** vendor any new face into `static/fonts/` as woff2; the CSP serves
  `default-src 'self'` and an external font link is blocked at the browser.
- **Do** keep `tokens.css` byte-identical to the framework's copy and make all
  visual changes in `theme.css` (palette, faces, shape, browser surfaces) or
  `bill.css` (composition). `internal/movementtest/vendored_test.go` records that
  divergence deliberately.

### Don't:
- **Don't** use `screen`, `overlay`, `lighten`, or a flat green fill to make the
  overprint colour. Cyan on its own is never a surface colour.
- **Don't** "fix" the 2px band offset, the ragged `clip-path` edge, or the square
  perforation dashes — each is authored, and each is the evidence that this was
  printed.
- **Don't** add a second shadow, an elevated panel, or a card. Elevation exists
  only as a tab leaving the wall.
- **Don't** number, rank or otherwise differentiate the three tabs.
- **Don't** introduce white (`#fff`) as a surface, or a third stock colour.
- **Don't** set running text, labels or UI copy in the monospace stack, and don't
  set the display face in anything but uppercase 800.
- **Don't** replace the halftone with a raster texture, a gradient, or a bevel.
- **Don't** centre a single email field as a surface strategy; forms sit beside
  the argument they belong to, in the sheet's two-up grid.
