| 1 | { |
| 2 | "schemaVersion": 2, |
| 3 | "generatedAt": "2026-09-02T00:00:00Z", |
| 4 | "title": "Design System: Movement", |
| 5 | "extensions": { |
| 6 | "colorMeta": { |
| 7 | "stock": { |
| 8 | "role": "neutral", |
| 9 | "displayName": "Acid Stock", |
| 10 | "canonical": "#f0e34a", |
| 11 | "tonalRamp": ["#2b2707", "#4a4110", "#6b5f14", "#8d7d1c", "#b09c24", "#d0ba33", "#f0e34a", "#faf3ae"] |
| 12 | }, |
| 13 | "bone": { |
| 14 | "role": "neutral", |
| 15 | "displayName": "Bone", |
| 16 | "canonical": "#f7f4e6", |
| 17 | "tonalRamp": ["#2a281f", "#454236", "#615d4e", "#7f7a68", "#9e9884", "#c0baa4", "#dfd9c5", "#f7f4e6"] |
| 18 | }, |
| 19 | "ink": { |
| 20 | "role": "neutral", |
| 21 | "displayName": "Press Ink", |
| 22 | "canonical": "#17140f", |
| 23 | "tonalRamp": ["#17140f", "#2e291f", "#4a4437", "#6a6455", "#8b8574", "#aea896", "#d2ccba", "#f0ece0"] |
| 24 | }, |
| 25 | "blue": { |
| 26 | "role": "primary", |
| 27 | "displayName": "Ultramarine Ink", |
| 28 | "canonical": "#1b2a8f", |
| 29 | "tonalRamp": ["#0a0f36", "#101857", "#121d68", "#1b2a8f", "#2b3db5", "#5566d0", "#8f9ce5", "#ccd2f5"] |
| 30 | }, |
| 31 | "blue-strong": { |
| 32 | "role": "primary", |
| 33 | "displayName": "Ultramarine Deepened", |
| 34 | "canonical": "#121d68", |
| 35 | "tonalRamp": ["#080c2c", "#0c1244", "#121d68", "#1b2a8f", "#2b3db5", "#5566d0", "#8f9ce5", "#ccd2f5"] |
| 36 | }, |
| 37 | "cyan": { |
| 38 | "role": "primary", |
| 39 | "displayName": "Press Cyan (second ink, multiply only)", |
| 40 | "canonical": "#14a0c8", |
| 41 | "tonalRamp": ["#052a35", "#08414f", "#0a5a6f", "#0f7b96", "#14a0c8", "#3fbcdd", "#7fd4ea", "#c2ecf7"] |
| 42 | }, |
| 43 | "overprint": { |
| 44 | "role": "secondary", |
| 45 | "displayName": "Overprint Green (cyan multiplied into yellow stock)", |
| 46 | "canonical": "#2f6b46", |
| 47 | "tonalRamp": ["#0d2016", "#153322", "#1f5334", "#2f6b46", "#43895c", "#6dab82", "#a1cbaf", "#d5e8dc"] |
| 48 | }, |
| 49 | "dim-on-stock": { |
| 50 | "role": "neutral", |
| 51 | "displayName": "Thinned Ink on Stock", |
| 52 | "canonical": "#6b5f14", |
| 53 | "tonalRamp": ["#231f06", "#3b340a", "#524810", "#6b5f14", "#8d7d1c", "#b09c24", "#d0ba33", "#f0e34a"] |
| 54 | }, |
| 55 | "dim-on-bone": { |
| 56 | "role": "neutral", |
| 57 | "displayName": "Thinned Ink on Bone", |
| 58 | "canonical": "#6a6455", |
| 59 | "tonalRamp": ["#211f1a", "#37342c", "#4f4b3f", "#6a6455", "#8b8574", "#aea896", "#d2ccba", "#f0ece0"] |
| 60 | }, |
| 61 | "negative-fg": { |
| 62 | "role": "tertiary", |
| 63 | "displayName": "Refusal Red", |
| 64 | "canonical": "#8e2417", |
| 65 | "tonalRamp": ["#2c0b07", "#4a120c", "#6c1b11", "#8e2417", "#b23a2a", "#cf6d5e", "#e6a79c", "#f6dcd7"] |
| 66 | } |
| 67 | }, |
| 68 | "typographyMeta": { |
| 69 | "shout": { "displayName": "Shout", "purpose": "The first-viewport headline only. Uppercase, capped at 15ch, balanced wrap, crossed by the cyan overprint band." }, |
| 70 | "headline": { "displayName": "Headline", "purpose": "Receipt marks, ledger title, tab words, the torn stub's word." }, |
| 71 | "title": { "displayName": "Title", "purpose": "The slip heading above a form." }, |
| 72 | "body": { "displayName": "Body", "purpose": "The bill's two-column argument." }, |
| 73 | "reply": { "displayName": "Reply", "purpose": "The stance sheet's answer, max 68ch; first paragraph steps up to 1.15em." }, |
| 74 | "label": { "displayName": "Label", "purpose": "Field labels, tab addresses, status lines, table column heads." }, |
| 75 | "stamp": { "displayName": "Stamp", "purpose": "Button lettering only: uppercase, 0.14em tracking." } |
| 76 | }, |
| 77 | "shadows": [ |
| 78 | { "name": "tab-lift", "value": "0 10px 30px rgba(23, 20, 15, 0.32)", "purpose": "The only shadow in the system: a tear-off tab lifting off the wall on hover/focus. Offset and blur, because that is what a real shadow has. Suppressed below 46rem and under prefers-reduced-motion." } |
| 79 | ], |
| 80 | "motion": [ |
| 81 | { "name": "tab-lift-ease", "value": "260ms cubic-bezier(0.16, 1, 0.3, 1)", "purpose": "Transform and shadow on the tear-off tab. The only expressive motion in the build." }, |
| 82 | { "name": "stamp-ease", "value": "160ms ease-out", "purpose": "Background deepening on the stamp button's hover." } |
| 83 | ], |
| 84 | "breakpoints": [ |
| 85 | { "name": "stack", "value": "46rem", "purpose": "Below: the argument drops to one column and the tear strip stacks with horizontal dashed dividers; tab lift is replaced by a bone tint." }, |
| 86 | { "name": "split", "value": "60rem", "purpose": "Below: the stance sheet's reply/slip grid stacks to one column." }, |
| 87 | { "name": "full-bleed", "value": "46rem wide and 40rem tall", "purpose": "At or above both: the bill fills 100svh so the tear strip lands near the fold." } |
| 88 | ], |
| 89 | "textures": [ |
| 90 | { "name": "halftone-stock", "value": "inline SVG dot screen, 6px tile, r=0.65, ink at 13% opacity", "purpose": "Paper grain on the acid stock. Authored dot screen, never a raster texture." }, |
| 91 | { "name": "halftone-bone", "value": "inline SVG dot screen, 6px tile, r=0.6, ink at 7% opacity", "purpose": "Paper grain on the bone reading surface." } |
| 92 | ] |
| 93 | }, |
| 94 | "components": [ |
| 95 | { |
| 96 | "name": "Stamp (primary button)", |
| 97 | "kind": "button", |
| 98 | "refersTo": "stamp", |
| 99 | "description": "The primary action on every form surface: a blue block with the second ink struck across its face.", |
| 100 | "html": "<button class=\"ds-stamp\">Sign the list</button>", |
| 101 | "css": ".ds-stamp { position: relative; overflow: hidden; font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; font-weight: 700; font-size: 0.875rem; letter-spacing: 0.14em; text-transform: uppercase; color: #f0e34a; background: #1b2a8f; border: 0; border-radius: 0; padding: 0.85rem 1.75rem; cursor: pointer; transition: background 160ms ease-out; } .ds-stamp::after { content: \"\"; position: absolute; left: 1.15rem; right: 1.15rem; top: 56%; height: 7px; background: #14a0c8; mix-blend-mode: multiply; transform: translateY(1px); pointer-events: none; } .ds-stamp:hover:not(:disabled) { background: #121d68; } .ds-stamp:focus-visible { outline: 3px solid #1b2a8f; outline-offset: 2px; }" |
| 102 | }, |
| 103 | { |
| 104 | "name": "Stamp, disabled", |
| 105 | "kind": "button", |
| 106 | "refersTo": "stamp-disabled", |
| 107 | "description": "An unstruck stamp: the ground drops out to a dashed outline and the cyan strike is removed. Used until the proof-of-work module enables the form.", |
| 108 | "html": "<button class=\"ds-stamp-disabled\" disabled>Sign the list</button>", |
| 109 | "css": ".ds-stamp-disabled { font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; font-weight: 700; font-size: 0.875rem; letter-spacing: 0.14em; text-transform: uppercase; background: transparent; color: #6a6455; border: 2px dashed rgba(23, 20, 15, 0.55); border-radius: 0; padding: 0.85rem 1.75rem; cursor: not-allowed; }" |
| 110 | }, |
| 111 | { |
| 112 | "name": "Tear-off Tab", |
| 113 | "kind": "custom", |
| 114 | "refersTo": "tab", |
| 115 | "description": "The signature component and the primary action of the first surface: equal tabs hanging off a perforation, each carrying its own address. Never numbered.", |
| 116 | "html": "<div class=\"ds-tear\"><div class=\"ds-tear-perf\"></div><div class=\"ds-tear-tabs\"><a class=\"ds-tab\" href=\"#\"><span class=\"ds-tab-word\">Sceptical</span><span class=\"ds-tab-addr\">movement.bdj.oncarlos.com/sceptical</span></a><a class=\"ds-tab\" href=\"#\"><span class=\"ds-tab-word\">Curious</span><span class=\"ds-tab-addr\">movement.bdj.oncarlos.com/curious</span></a><a class=\"ds-tab\" href=\"#\"><span class=\"ds-tab-word\">All in</span><span class=\"ds-tab-addr\">movement.bdj.oncarlos.com/all-in</span></a></div></div>", |
| 117 | "css": ".ds-tear-perf { height: 2px; background-image: repeating-linear-gradient(to right, #17140f 0 7px, transparent 7px 14px); } .ds-tear-tabs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; border-bottom: 2px solid #17140f; } .ds-tab { display: flex; flex-direction: column; gap: 0.5rem; min-height: 6.5rem; padding: 0.9rem 1.1rem 1rem; background: #f0e34a; color: #17140f; text-decoration: none; border-right: 2px dashed #17140f; transform-origin: top center; transition: transform 260ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 260ms cubic-bezier(0.16, 1, 0.3, 1); } .ds-tab:last-child { border-right: 0; } .ds-tab:hover, .ds-tab:focus-visible { transform: translateY(-10px); box-shadow: 0 10px 30px rgba(23, 20, 15, 0.32); z-index: 1; } .ds-tab-word { font-family: \"Big Shoulders\", Haettenschweiler, \"Arial Narrow\", sans-serif; font-weight: 800; text-transform: uppercase; font-size: 2.75rem; line-height: 0.92; letter-spacing: -0.01em; } .ds-tab-addr { font-size: 0.8125rem; font-weight: 700; color: #1b2a8f; overflow-wrap: anywhere; }" |
| 118 | }, |
| 119 | { |
| 120 | "name": "Ruled Field", |
| 121 | "kind": "input", |
| 122 | "refersTo": "field-input", |
| 123 | "description": "A ruled line on paper, not a box on a screen. Label above in tracked bold; 2px ink underline; square focus registration mark.", |
| 124 | "html": "<label class=\"ds-field\"><span>Email address</span><input type=\"email\" placeholder=\"you@example.com\"></label>", |
| 125 | "css": ".ds-field { display: block; margin-bottom: 1.35rem; font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; } .ds-field > span { display: block; font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.06em; margin-bottom: 0.35rem; color: #17140f; } .ds-field input { width: 100%; box-sizing: border-box; font: inherit; color: #17140f; background: transparent; border: 0; border-bottom: 2px solid #17140f; border-radius: 0; padding: 0.45rem 0; } .ds-field input::placeholder { color: #6a6455; } .ds-field input:focus-visible { outline: 3px solid #1b2a8f; outline-offset: 2px; }" |
| 126 | }, |
| 127 | { |
| 128 | "name": "Tinted Textarea", |
| 129 | "kind": "input", |
| 130 | "refersTo": "field-textarea", |
| 131 | "description": "The one field variation: a faint ink tint that shows the field's extent. The tint is not a fill and never becomes a card.", |
| 132 | "html": "<label class=\"ds-field-area\"><span>How could you help?</span><textarea rows=\"5\"></textarea></label>", |
| 133 | "css": ".ds-field-area { display: block; margin-bottom: 1.35rem; font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; } .ds-field-area > span { display: block; font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.06em; margin-bottom: 0.35rem; color: #17140f; } .ds-field-area textarea { width: 100%; box-sizing: border-box; font: inherit; line-height: 1.5; color: #17140f; background: rgba(23, 20, 15, 0.05); border: 0; border-bottom: 2px solid #17140f; border-radius: 0; padding: 0.55rem 0.7rem; min-height: 7.5rem; resize: vertical; } .ds-field-area textarea:focus-visible { outline: 3px solid #1b2a8f; outline-offset: 2px; }" |
| 134 | }, |
| 135 | { |
| 136 | "name": "Shout with Overprint Band", |
| 137 | "kind": "custom", |
| 138 | "refersTo": "shout", |
| 139 | "description": "The first-viewport headline in condensed caps, crossed by the second ink multiplied into the stock and offset 2px off true.", |
| 140 | "html": "<div class=\"ds-stock\"><h1 class=\"ds-shout\">Software is being handed to machines. Decide where you stand.</h1></div>", |
| 141 | "css": ".ds-stock { background-color: #f0e34a; background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='6' height='6'%3E%3Ccircle cx='1' cy='1' r='0.65' fill='%2317140f' fill-opacity='0.13'/%3E%3Ccircle cx='4' cy='4' r='0.65' fill='%2317140f' fill-opacity='0.13'/%3E%3C/svg%3E\"); padding: 2rem; overflow: hidden; } .ds-shout { position: relative; font-family: \"Big Shoulders\", Haettenschweiler, \"Arial Narrow\", sans-serif; font-weight: 800; text-transform: uppercase; color: #1b2a8f; font-size: clamp(2.5rem, 9vw, 6rem); line-height: 0.84; letter-spacing: -0.02em; margin: 0; max-width: 15ch; text-wrap: balance; } .ds-shout::after { content: \"\"; position: absolute; left: -100vw; right: -100vw; top: 46%; height: 0.26em; background: #14a0c8; mix-blend-mode: multiply; transform: translateY(2px); z-index: 1; pointer-events: none; }" |
| 142 | }, |
| 143 | { |
| 144 | "name": "Torn Stub", |
| 145 | "kind": "card", |
| 146 | "refersTo": "tab", |
| 147 | "description": "The tab you tore off, carried onto the stance sheet. Its lower edge is ragged because that is where the perforation gave. The word is the heading; nothing labels it.", |
| 148 | "html": "<div class=\"ds-torn\"><p class=\"ds-torn-word\">Sceptical</p></div>", |
| 149 | "css": ".ds-torn { background: #f0e34a; width: fit-content; max-width: 100%; padding: 0.9rem 1.4rem 1.5rem; clip-path: polygon(0 0, 100% 0, 100% calc(100% - 9px), 96% 100%, 92% calc(100% - 7px), 88% 100%, 84% calc(100% - 8px), 80% 100%, 76% calc(100% - 6px), 72% 100%, 68% calc(100% - 9px), 64% 100%, 60% calc(100% - 7px), 56% 100%, 52% calc(100% - 8px), 48% 100%, 44% calc(100% - 6px), 40% 100%, 36% calc(100% - 9px), 32% 100%, 28% calc(100% - 7px), 24% 100%, 20% calc(100% - 8px), 16% 100%, 12% calc(100% - 6px), 8% 100%, 4% calc(100% - 9px), 0 100%); } .ds-torn-word { font-family: \"Big Shoulders\", Haettenschweiler, \"Arial Narrow\", sans-serif; font-weight: 800; text-transform: uppercase; font-size: 2.75rem; line-height: 0.9; color: #17140f; margin: 0; }" |
| 150 | }, |
| 151 | { |
| 152 | "name": "Receipt", |
| 153 | "kind": "card", |
| 154 | "refersTo": "notice-negative", |
| 155 | "description": "The terminal surface for every send, confirm, dead link and sign-in: a bone block between two ink rules with a cyan mark struck across the top rule.", |
| 156 | "html": "<div class=\"ds-receipt\"><p class=\"ds-receipt-mark\">Check your inbox</p><p>We sent one message to the address you gave. Open it and confirm.</p></div>", |
| 157 | "css": ".ds-receipt { position: relative; max-width: 46rem; margin: 0 auto; padding: 3rem 2.5rem; border-top: 2px solid #17140f; border-bottom: 2px solid #17140f; background: #f7f4e6; font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; color: #17140f; } .ds-receipt::before { content: \"\"; position: absolute; top: -3px; left: 2.5rem; width: 9rem; height: 8px; background: #14a0c8; mix-blend-mode: multiply; } .ds-receipt-mark { font-family: \"Big Shoulders\", Haettenschweiler, \"Arial Narrow\", sans-serif; font-weight: 800; text-transform: uppercase; font-size: 3rem; line-height: 0.92; color: #1b2a8f; margin: 0 0 0.85rem; } .ds-receipt p { margin: 0; font-size: 1.0625rem; line-height: 1.6; }" |
| 158 | }, |
| 159 | { |
| 160 | "name": "Ledger Table", |
| 161 | "kind": "custom", |
| 162 | "refersTo": "body", |
| 163 | "description": "The admin tally in the same hand: 2px ink head rule, 1px row rules, tracked uppercase column heads, tabular numerals.", |
| 164 | "html": "<table class=\"ds-ledger\"><thead><tr><th>Email</th><th>Stance</th><th>When</th></tr></thead><tbody><tr><td>ada@example.com</td><td>Sceptical</td><td>2026-09-01</td></tr><tr><td>grace@example.com</td><td>Curious</td><td>2026-09-02</td></tr></tbody></table>", |
| 165 | "css": ".ds-ledger { width: 100%; border-collapse: collapse; font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; font-size: 0.9375rem; font-variant-numeric: tabular-nums; color: #17140f; } .ds-ledger th { text-align: left; font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase; border-bottom: 2px solid #17140f; padding: 0 0.75rem 0.5rem 0; } .ds-ledger td { border-bottom: 1px solid rgba(23, 20, 15, 0.22); padding: 0.7rem 0.75rem 0.7rem 0; vertical-align: top; }" |
| 166 | }, |
| 167 | { |
| 168 | "name": "Refusal Notice", |
| 169 | "kind": "custom", |
| 170 | "refersTo": "notice-negative", |
| 171 | "description": "Form error. Ruled along its top edge, never a left-border callout and never a rounded alert.", |
| 172 | "html": "<p class=\"ds-notice\" role=\"alert\">That address could not be accepted.</p>", |
| 173 | "css": ".ds-notice { border-left: 0; border-top: 2px solid #8e2417; background: rgba(158, 42, 26, 0.14); color: #8e2417; padding: 0.75rem 0.9rem; margin: 0 0 1.25rem; font-family: \"Public Sans\", ui-sans-serif, system-ui, sans-serif; font-weight: 700; border-radius: 0; }" |
| 174 | } |
| 175 | ], |
| 176 | "narrative": { |
| 177 | "northStar": "The Flyposted Bill", |
| 178 | "overview": "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.\n\nThe 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.\n\nTypographically 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.", |
| 179 | "keyCharacteristics": [ |
| 180 | "Drenched stock, not accented white; two paper colours, never a screen white", |
| 181 | "Two inks that build where they cross; green only as a product of overprint", |
| 182 | "Zero radius everywhere — a printed sheet has corners", |
| 183 | "Perforations, dashed rules and torn edges instead of borders and shadows", |
| 184 | "Condensed uppercase display against a quiet body grotesque", |
| 185 | "Self-hosted faces only; no third-party font origin exists in this world" |
| 186 | ], |
| 187 | "rules": [ |
| 188 | { "name": "The Overprint Rule", "body": "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.", "section": "colors" }, |
| 189 | { "name": "The Misregistration Rule", "body": "Every overprint band sits 1–2px off true (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\".", "section": "colors" }, |
| 190 | { "name": "The Two-Stock Rule", "body": "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.", "section": "colors" }, |
| 191 | { "name": "The Self-Hosted Faces Rule", "body": "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.", "section": "typography" }, |
| 192 | { "name": "The Display-Is-Caps Rule", "body": "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.", "section": "typography" }, |
| 193 | { "name": "The No-Costume-Mono Rule", "body": "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.", "section": "typography" }, |
| 194 | { "name": "The One Shadow Rule", "body": "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.", "section": "elevation" }, |
| 195 | { "name": "The Halftone Rule", "body": "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.", "section": "elevation" }, |
| 196 | { "name": "The Equal Tabs Rule", "body": "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.", "section": "components" }, |
| 197 | { "name": "The Tab Carries Its Address Rule", "body": "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.", "section": "components" } |
| 198 | ], |
| 199 | "dos": [ |
| 200 | "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.", |
| 201 | "Do keep radius at 0 on every new surface and control, including restating border-radius: 0 on native controls.", |
| 202 | "Do put new reading surfaces on bone and reserve the acid stock for the bill, its tabs, and stubs torn from it.", |
| 203 | "Do draw dividers as perforations, dashed 2px rules, or solid 2px ink rules, and fields as ruled underlines.", |
| 204 | "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.", |
| 205 | "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." |
| 206 | ], |
| 207 | "donts": [ |
| 208 | "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.", |
| 209 | "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.", |
| 210 | "Don't add a second shadow, an elevated panel, or a card. Elevation exists only as a tab leaving the wall.", |
| 211 | "Don't number, rank or otherwise differentiate the three tabs.", |
| 212 | "Don't introduce white (#fff) as a surface, or a third stock colour.", |
| 213 | "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.", |
| 214 | "Don't replace the halftone with a raster texture, a gradient, or a bevel.", |
| 215 | "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." |
| 216 | ] |
| 217 | } |
| 218 | } |
| 219 | |