carlos / movement Public

Clone
git clone https://amadan.net/carlos/movement

Plain git — no account needed to clone.

Download

Download this file

1---
2name: Movement
3description: A flyposted bill — acid stock, two inks, and the green that only exists where they overprint.
4colors:
5 stock: "#f0e34a"
6 bone: "#f7f4e6"
7 ink: "#17140f"
8 blue: "#1b2a8f"
9 blue-strong: "#121d68"
10 cyan: "#14a0c8"
11 overprint: "#2f6b46"
12 dim-on-stock: "#6b5f14"
13 dim-on-bone: "#6a6455"
14 faint: "#8b8574"
15 rule: "rgba(23, 20, 15, 0.22)"
16 rule-on-stock: "rgba(23, 20, 15, 0.3)"
17 rule-strong: "rgba(23, 20, 15, 0.55)"
18 negative-fg: "#8e2417"
19 negative-bg: "rgba(158, 42, 26, 0.14)"
20typography:
21 shout:
22 fontFamily: "Big Shoulders, Haettenschweiler, Arial Narrow, sans-serif"
23 fontSize: "clamp(4rem, 16vw, 10.5rem)"
24 fontWeight: 800
25 lineHeight: 0.84
26 letterSpacing: "-0.02em"
27 headline:
28 fontFamily: "Big Shoulders, Haettenschweiler, Arial Narrow, sans-serif"
29 fontSize: "clamp(1.9rem, 5vw, 3.25rem)"
30 fontWeight: 800
31 lineHeight: 0.92
32 letterSpacing: "normal"
33 title:
34 fontFamily: "Big Shoulders, Haettenschweiler, Arial Narrow, sans-serif"
35 fontSize: "clamp(1.35rem, 3vw, 1.9rem)"
36 fontWeight: 800
37 lineHeight: 1
38 letterSpacing: "normal"
39 body:
40 fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
41 fontSize: "clamp(1rem, 1.15vw, 1.125rem)"
42 fontWeight: 400
43 lineHeight: 1.55
44 letterSpacing: "normal"
45 reply:
46 fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
47 fontSize: "clamp(1.0625rem, 1.3vw, 1.1875rem)"
48 fontWeight: 400
49 lineHeight: 1.62
50 letterSpacing: "normal"
51 label:
52 fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
53 fontSize: "0.8125rem"
54 fontWeight: 700
55 lineHeight: 1.3
56 letterSpacing: "0.06em"
57 stamp:
58 fontFamily: "Public Sans, ui-sans-serif, system-ui, -apple-system, sans-serif"
59 fontSize: "0.875rem"
60 fontWeight: 700
61 lineHeight: 1.2
62 letterSpacing: "0.14em"
63rounded:
64 none: "0"
65spacing:
66 hair: "0.35rem"
67 tight: "0.5rem"
68 field: "1.35rem"
69 block: "1.25rem"
70 gutter: "clamp(1.5rem, 4vw, 3.5rem)"
71 band: "clamp(1.75rem, 4vw, 3rem)"
72 page-x: "clamp(1.25rem, 4vw, 3.5rem)"
73 page-y: "clamp(1.25rem, 3vw, 2.5rem)"
74components:
75 stamp:
76 backgroundColor: "{colors.blue}"
77 textColor: "{colors.stock}"
78 typography: "{typography.stamp}"
79 rounded: "{rounded.none}"
80 padding: "0.85rem 1.75rem"
81 stamp-hover:
82 backgroundColor: "{colors.blue-strong}"
83 textColor: "{colors.stock}"
84 stamp-disabled:
85 backgroundColor: "transparent"
86 textColor: "{colors.dim-on-bone}"
87 rounded: "{rounded.none}"
88 tab:
89 backgroundColor: "{colors.stock}"
90 textColor: "{colors.ink}"
91 typography: "{typography.headline}"
92 rounded: "{rounded.none}"
93 padding: "0.9rem 1.1rem 1rem"
94 height: "clamp(5rem, 7vw, 6.5rem)"
95 field-input:
96 backgroundColor: "transparent"
97 textColor: "{colors.ink}"
98 typography: "{typography.body}"
99 rounded: "{rounded.none}"
100 padding: "0.45rem 0"
101 field-textarea:
102 backgroundColor: "rgba(23, 20, 15, 0.05)"
103 textColor: "{colors.ink}"
104 typography: "{typography.body}"
105 rounded: "{rounded.none}"
106 padding: "0.55rem 0.7rem"
107 notice-negative:
108 backgroundColor: "{colors.negative-bg}"
109 textColor: "{colors.negative-fg}"
110 rounded: "{rounded.none}"
111 padding: "0.75rem 0.9rem"
112---
113
114# Design System: Movement
115
116## Overview
117
118**Creative North Star: "The Flyposted Bill"**
119
120This is a sheet pasted on a wall, photographed as it is: cheap coloured stock,
121two inks, a heavy impression, and the halftone dots of a job that was printed
122rather than rendered. Nothing here is a card floating on a canvas. The stock is
123the surface — full-bleed acid yellow on the bill itself — and the reading
124surfaces drop to bone because acid yellow is a shout and a shout cannot also be
125a paragraph. Depth is ink and perforation. The one exception is a tear-off tab
126lifting off the wall, which is a thing paper actually does.
127
128The two inks are the whole colour theory. Ultramarine is the first ink; cyan is
129the second; the green is not a chosen colour at all but the consequence of cyan
130multiplied into yellow stock, offset a couple of pixels off true because a
131two-colour job never registers perfectly. That misregistration is the tell that
132the thing was printed, and it is load-bearing: remove the offset or swap the
133blend and the world stops being a press and becomes a gradient.
134
135Typographically it is a condensed display face shouting in caps against a plain
136grotesque doing the reading. The composition is hard: ruled columns, ruled
137underlines for fields, square dashes, zero radius anywhere. It refuses the
138centred single-field waitlist page — the page sorts you before it asks you for
139anything, and the primary action on the first surface is tearing off a tab, not
140pressing a button.
141
142**Key Characteristics:**
143- Drenched stock, not accented white; two paper colours, never a screen white
144- Two inks that build where they cross; green only as a product of overprint
145- Zero radius everywhere — a printed sheet has corners
146- Perforations, dashed rules and torn edges instead of borders and shadows
147- Condensed uppercase display against a quiet body grotesque
148- Self-hosted faces only; no third-party font origin exists in this world
149
150## Colors
151
152Two paper stocks and two inks, plus the third colour the inks make between them.
153
154### Primary
155- **Ultramarine Ink** (`{colors.blue}`): the first ink. Every headline that
156 carries a claim, every outbound link's 3px underline, the stamp button's
157 ground, the caret, the selection ground, and the focus outline. It is the
158 voice of the press.
159- **Press Cyan** (`{colors.cyan}`): the second ink, and never used as a flat
160 colour on its own. It exists only as an overprint band multiplied into
161 whatever is underneath: the stripe across the shout, the strike across the
162 stamp's face, the mark struck across a receipt's top rule.
163
164### Secondary
165- **Overprint Green** (`{colors.overprint}`): what cyan makes of yellow stock.
166 Recorded as a token because status tones are tinted from it, but it is a
167 *result*, not a paint. Where it appears on a surface it is produced by
168 multiply, not painted on.
169
170### Neutral
171- **Acid Stock** (`{colors.stock}`): the bill's paper. Full-bleed on the first
172 surface, and the ground of every tear-off tab and every torn stub carried
173 onto a later page. Also the text colour on ultramarine.
174- **Bone** (`{colors.bone}`): the reading paper. Every stance sheet, receipt,
175 sign-in and ledger sits on it. Warm, never white.
176- **Press Ink** (`{colors.ink}`): body text, perforations, table head rules,
177 field underlines.
178- **Thinned Ink on Stock** (`{colors.dim-on-stock}`) and **Thinned Ink on Bone**
179 (`{colors.dim-on-bone}`): secondary text, one per stock. The bill page
180 swaps the muted token to the yellow-safe one on its own surface.
181- **Rules** (`{colors.rule}`, `{colors.rule-on-stock}`, `{colors.rule-strong}`):
182 hairlines and column rules; the stock variant is heavier because yellow
183 swallows a light rule.
184
185### Named Rules
186**The Overprint Rule.** Green is never picked from a swatch. Wherever ink
187crosses ink, the upper layer is `mix-blend-mode: multiply` so the inks *build*
188and darken. `screen` is a defect in this world, not an option; it lightens and
189turns a press into a light-box.
190
191**The Misregistration Rule.** Every overprint band sits 1–2px off true
192(`transform: translateY(2px)` on the shout, `1px` on the stamp). The offset is
193the evidence of a two-colour job. It is not a rendering bug and must not be
194"corrected".
195
196**The Two-Stock Rule.** There are exactly two grounds: acid stock for the bill
197and its tabs, bone for everything meant to be read. No third surface colour, and
198no white anywhere — white is a screen colour and this is a printed thing.
199
200## Typography
201
202**Display Font:** Big Shoulders 800 (with Haettenschweiler, Arial Narrow)
203**Body Font:** Public Sans 400/700 (with ui-sans-serif, system-ui)
204
205**Character:** A tall condensed grotesque shouting in uppercase against a plain,
206unfussy text face that never raises its voice. The display face is only ever
207used at weight 800, uppercase, with negative tracking and sub-1 line height, so
208it sets as a solid block of ink rather than a line of letters.
209
210### The ramp
211
212Nine steps, declared once in `theme.css`. Four display and five text. Nothing
213in the app sets a literal font size — a bill has a handful of sorts in the
214case, not a continuum.
215
216| Token | Value | Used by |
217|---|---|---|
218| `--t-shout` | `clamp(4rem, 16vw, 10.5rem)` | the first-viewport headline, and nothing else |
219| `--t-display-1` | `clamp(2rem, 5vw, 3.25rem)` | ledger title, receipt mark |
220| `--t-display-2` | `clamp(1.5rem, 3.2vw, 2.75rem)` | tab word, torn stub's word |
221| `--t-display-3` | `clamp(1.35rem, 3vw, 1.9rem)` | the slip's heading above a form |
222| `--t-lede` | `clamp(1.0625rem, 1.3vw, 1.1875rem)` | stance reply, receipt body, tear instruction |
223| `--t-body` | `clamp(1rem, 1.15vw, 1.125rem)` | the bill's two-column argument |
224| `--t-base` | `0.9375rem` | ledger table |
225| `--t-sm` | `0.875rem` | imprint line, stamp label |
226| `--t-xs` | `0.8125rem` | field labels, tab addresses, status lines |
227| `--t-xxs` | `0.75rem` | ledger column heads |
228
229Display steps carry weight 800, uppercase, negative tracking and sub-1 line
230height (0.84 on the shout, 0.9–1 below it). Text steps run 1.5–1.62.
231
232Two relative multipliers are not steps and are allowed to stay relative: the
233stance reply's first paragraph at `1.15em`, and the ledger's tally figure at
234`1.5em`. Both size against their own context rather than against the ramp.
235
236### Named Rules
237**The Self-Hosted Faces Rule.** Both families are served from
238`static/fonts/*.woff2` with `font-display: swap`. This is a constraint, not a
239preference: the app ships `default-src 'self'`, so a third-party font link is
240blocked at the browser. Never introduce a font CDN, and never add a face without
241vendoring the woff2.
242
243**The Display-Is-Caps Rule.** The condensed face appears only uppercase, only at
244800, and only for shouts, marks, titles and tab words. It is never used for
245running text, labels or numbers in prose — except the ledger's tally figures,
246where it sets the count as a struck number beside body-face copy.
247
248**The Nine Sorts Rule.** Every size comes from the ramp above, as a `--t-*`
249token. A literal `font-size` anywhere in the app is a defect, not a local
250decision — the first version of this stylesheet had twenty-one of them across
251twelve unrelated values, and the ramp documented here did not exist until the
252design detector pointed out that the record described a system the code had
253never had. The two `em` multipliers named above are the only exceptions.
254
255**The No-Costume-Mono Rule.** A monospace stack is declared but is not used on
256any surface. Monospace here would be a costume for "technical" on pages that are
257not about machines. Tabular figures in the ledger come from
258`font-variant-numeric: tabular-nums`, not from a mono face.
259
260## Layout
261
262One column of measured width per surface, never a centred narrow strip. The bill
263and the stance sheet share a `78rem` max width with fluid page padding
264(`{spacing.page-y} {spacing.page-x}`); the ledger sits at `66rem`, the receipt at
265`46rem`.
266
267The bill fills the viewport (`min-height: 100svh`) only above `46rem` wide *and*
268`40rem` tall, so the tear strip lands near the fold on real screens without
269stranding it on short ones. Its argument sets in two hard columns divided by a
2701px column rule, collapsing to one column below `46rem`.
271
272The stance sheet is a two-up grid under a single 2px rule: reply
273(`minmax(0, 1.55fr)`) beside slip (`minmax(19rem, 1fr)`), stacking below `60rem`.
274The tear strip is three equal grid columns, stacking to one below `46rem` where
275the tabs' vertical dashed dividers become horizontal ones.
276
277Rhythm is fluid rather than stepped: `clamp()` bands do the vertical spacing
278(`{spacing.band}` between major blocks, `{spacing.gutter}` for gutters), with
279fixed small steps only inside components (`{spacing.field}` between fields,
280`{spacing.hair}` label-to-input).
281
282## Elevation & Depth
283
284The system is flat. Depth is ink density, rules, perforation and torn edges — not
285layering. Both stocks carry an authored inline-SVG halftone: a two-dot screen at
286a 6px pitch, at 13% opacity on the acid stock and 7% on bone. It is a dot screen,
287not a texture image, and it is what makes the stock read as paper.
288
289There is exactly one shadow, and it belongs to a tab lifting off the wall on
290hover or focus. It carries an offset *and* a blur because that is what a real
291shadow has.
292
293### Shadow Vocabulary
294- **Tab lift** (`box-shadow: 0 10px 30px rgba(23, 20, 15, 0.28→0.32)`, paired
295 with `translateY(-10px)` over `260ms cubic-bezier(0.16, 1, 0.3, 1)`): the only
296 shadow any app surface may use. Suppressed below `46rem` (where the tab tints
297 to bone instead) and under `prefers-reduced-motion: reduce`.
298
299### Named Rules
300**The One Shadow Rule.** A shadow exists in this world only to show paper
301leaving the wall. Nothing else is elevated: no panels, no menus, no hovering
302cards. A hard offset shadow with no blur belongs to a different world and is not
303this one.
304
305**The Halftone Rule.** Paper texture is the 6px inline-SVG dot screen at low
306opacity, multiplied into the stock. Never a raster texture, never a bevel,
307emboss, gradient or inner shadow.
308
309## Shapes
310
311Radius is zero, everywhere, on purpose: `--rst-radius`, `--rst-radius-sm` and
312`--rst-radius-pill` are all `0`, and every app-authored control restates
313`border-radius: 0`. A printed sheet has corners.
314
315Form language is drawn with rules, not boxes:
316- **Perforation:** a 2px `repeating-linear-gradient` at 7px on / 7px off, so the
317 dashes stay square. Not a dashed border.
318- **Dashed dividers:** 2px dashed ink between tear-off tabs.
319- **Torn edge:** a 28-point `clip-path` polygon giving the carried-over stub a
320 ragged lower edge, because that is where the perforation gave.
321- **Rules:** 2px solid ink for structural divisions (strip foot, sheet head,
322 receipt top and bottom, table head); 1px translucent ink for column rules and
323 table rows.
324- **Fields:** ruled underlines, never outlined boxes.
325
326## Components
327
328### Stamp (the primary button)
329A blue block with the second ink struck across its face.
330- **Shape:** square (`0`)
331- **Primary:** ultramarine ground, acid-stock lettering, uppercase 700 at
332 `0.875rem` with `0.14em` tracking, padding `0.85rem 1.75rem`; a 7px cyan bar
333 multiplied across at 56% height and offset 1px.
334- **Hover:** ground deepens to `{colors.blue-strong}` over `160ms ease-out`.
335- **Disabled:** ground drops out entirely to a 2px dashed ink outline with
336 thinned lettering, and the cyan strike is removed — an unstruck stamp.
337
338### Tear-off Tab (signature component)
339The primary action of the first surface: three equal tabs hanging off a
340perforation, each printed on the stock and carrying its own address.
341- **Shape:** square, no border except the 2px dashed divider between tabs and a
342 2px solid rule closing the strip's foot.
343- **Content:** the position word in display caps, and beneath it the URL that tab
344 leads to in `0.8125rem` blue label type. Nothing else.
345- **State:** lifts `-10px` with the single system shadow on hover and
346 focus-visible; on narrow screens it tints to bone instead of lifting.
347
348**The Equal Tabs Rule.** Tabs are never numbered, ranked, ordered by emphasis, or
349given a "recommended" treatment. Three positions numbered 1-2-3 imply a rank;
350these three are equal by design, and the only differences between them are the
351word and the address.
352
353**The Tab Carries Its Address Rule.** A tear-off tab exists to be the thing you
354walk away holding, so it prints its own destination. A separate "take one" call
355to action beside it says the same thing twice.
356
357### Fields
358Ruled lines on paper, one grammar for all of them.
359- **Style:** transparent ground, no border except a 2px solid ink underline,
360 padding `0.45rem 0`, radius `0`. Label above in 700 `0.8125rem` with `0.06em`
361 tracking.
362- **Textarea:** the one variation — a faint `rgba(23,20,15,0.05)` ink tint and
363 boxed padding, present to show the field's extent, not as a fill. It keeps the
364 same square corners and vertical-only resize.
365- **Focus:** the global registration mark — a 3px ultramarine outline offset 2px,
366 square.
367- **Error:** a notice ruled 2px along its *top* edge in the negative tone over a
368 tinted ground; never a left-border callout, never a rounded alert.
369
370### Receipt
371The terminal surface for every send, confirm, dead link and sign-in: a bone block
372between two 2px ink rules, with a cyan mark struck across the top rule and the
373outcome set as a display-caps mark in ultramarine.
374
375### Ledger (admin)
376The same hand, operating rather than persuading: display-caps title in
377ultramarine, tallies where the figure is set in the display face at `1.5em`
378beside body-face copy, and a `border-collapse` table with a 2px ink head rule,
3791px row rules, uppercase `0.75rem`/`0.12em` column heads and tabular numerals.
380
381### Browser surfaces
382Not decoration — the cheapest place to stay consistent. Selection is the inks
383swapped (ultramarine ground, stock lettering), caret is ultramarine, the
384scrollbar is thin ink on transparent, and focus is a square 3px ultramarine
385registration mark.
386
387## Do's and Don'ts
388
389### Do:
390- **Do** build every ink-over-ink crossing with `mix-blend-mode: multiply` and a
391 1–2px offset, so the colour is produced by the press rather than picked.
392- **Do** keep radius at `0` on every new surface and control, including
393 restating `border-radius: 0` on native controls.
394- **Do** put new reading surfaces on bone and reserve the acid stock for the
395 bill, its tabs, and stubs torn from it.
396- **Do** draw dividers as perforations, dashed 2px rules, or solid 2px ink rules,
397 and fields as ruled underlines.
398- **Do** vendor any new face into `static/fonts/` as woff2; the CSP serves
399 `default-src 'self'` and an external font link is blocked at the browser.
400- **Do** keep `tokens.css` byte-identical to the framework's copy and make all
401 visual changes in `theme.css` (palette, faces, shape, browser surfaces) or
402 `bill.css` (composition). `internal/movementtest/vendored_test.go` records that
403 divergence deliberately.
404
405### Don't:
406- **Don't** use `screen`, `overlay`, `lighten`, or a flat green fill to make the
407 overprint colour. Cyan on its own is never a surface colour.
408- **Don't** "fix" the 2px band offset, the ragged `clip-path` edge, or the square
409 perforation dashes — each is authored, and each is the evidence that this was
410 printed.
411- **Don't** add a second shadow, an elevated panel, or a card. Elevation exists
412 only as a tab leaving the wall.
413- **Don't** number, rank or otherwise differentiate the three tabs.
414- **Don't** introduce white (`#fff`) as a surface, or a third stock colour.
415- **Don't** set running text, labels or UI copy in the monospace stack, and don't
416 set the display face in anything but uppercase 800.
417- **Don't** replace the halftone with a raster texture, a gradient, or a bevel.
418- **Don't** centre a single email field as a surface strategy; forms sit beside
419 the argument they belong to, in the sheet's two-up grid.
420