carlos / movement Public

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

Plain git — no account needed to clone.

Download

Download this file

--- 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.