# Reloop Digital — design system (v4.2 · 14 Oct 2026) Site: reloop.digital Platform: Webflow (no build step — see §12) This is the source of truth for how this site is built. Any agent — human or otherwise — working on reloop.digital reads this before making a change. It is not documentation written after the fact. Where the site and this file disagree, that disagreement is a bug and is logged in §11. If you are creating a NEW page: read §2, §3, §6 and §13, then use the page skeleton in §3.4. Reuse the classes in §6 — do not invent new ones for something that already exists. --- ## 1. Tokens Six colours. Always bind to the variable; never hardcode a hex in a style. | Token | Value | Variable | Used for | |------------|----------------------|----------------|----------| | Ink | `#10151F` | `--ink` | All type and rules. Blue-black, hue 222°, never pure black. | | Paper | `#F7F7F5` | `--paper` | Page background. Warm, so it reads as paper rather than screen. | | Mist | `#E8EAEE` | `--mist` | Chips, buttons and panels that sit back from the page. | | Volt | `#BFFF00` | `--volt` | Highlighter. Emphasis, markers, hover fills. | | Signal Red | `#E62D2D` | `--signal-red` | One job only: the scribbled overline under a link on hover. | | Ink Muted | `rgba(16,21,31,0.6)` | `--ink-muted` | Legacy. Not for text: 4.43:1 on Paper and 4.46:1 on Mist, which fails AA at 14px and below. | | Ink Muted Text | `#5C5F66` | `--ink-muted-text` | All supporting text: labels, kickers, captions, dates, metadata, idle tabs, hover dims. 5.96:1 on Paper, 5.4:1 on Mist, 5.5:1 on Volt. Reference the variable, never the hex, so Under the Hood can theme it. | Every hairline, underline and drawn rule is Ink at reduced opacity. Opacity is for lines only — never for text. The opacities in use, in order of weight: - `0.06` — construction/diagram grid lines - `0.12` — section-dividing hairlines (`.ds-kick`, `.ds-md`) - `0.15` — the strong divider (`.rl-say`, `.rl-ai-inner`) - `0.20` — dimension leaders on diagrams - `0.35` — inline link underline (`#10151f59`) Supporting text is not on this list. It is `--ink-muted-text`, a solid colour, because translucent ink at 0.45–0.60 lands at 2.9–4.5:1 and fails AA at the 11–14px sizes labels use. The one exception is type on a Volt field (§1a). **Volt on the homepage, as of v4:** the nav CTA, the hero highlighter, the film play square, the one flat-Volt claim word, and the closing CTA field. Testimonial cards, the principles recommendation cell and the service chips are Mist (chips go Volt on hover only). Nothing else. **Volt is rationed.** It is the only saturated colour on a neutral site, so its value is scarcity. If more than roughly one element in six on a screen is Volt, it has stopped being emphasis and become pattern. ### 1a. Volt has two budgets Volt appears in two structurally different ways, and they are rationed separately. Confusing them is how the accent gets diluted. **Volt as a MARK.** A stroke, chip, dot or accent shape *inside* an otherwise neutral element: the highlighter under a payoff word, the one Volt shape in a principle mark, the accent box in a diagram, `.ds-num`, `.ds-mdmark`, `.rl-ai-notemark`. The element is still Mist or Paper; Volt is annotation. Budget: **at most one mark per heading, per figure, per card.** **Volt as a FIELD.** The element's whole background is Volt and the type on it is Ink: `.pg-cell-volt`, `.svc-volt`, `.cta-volt`, the 240x240 "Email me" square in the mobile drawer. This is not a louder mark — it is a different class of object, which is exactly why it works in a grid of neutral ones. Budget: **at most one field per screenful of scroll, and never two adjacent.** **The budgets do not stack.** A Volt mark inside a Volt field is invisible or muddy, so when an element becomes a field, its marks come off. When the closing CTA became `.cta-volt`, it dropped the highlighter span it used to carry on "what you're building", and dropped the Scribbly hover Lottie with it. A field never sits inside or beside another field. **Why Ink on Volt carries body-length text.** Measured, not assumed: | Pair | Contrast | Verdict | |--------------------------|----------|---------| | Ink `#10151F` on Volt | 15.25:1 | AAA. Same as Ink on Mist (15.17:1), which is why a Mist panel swaps to Volt with no type changes. | | Ink at 0.60 on Volt | 4.54:1 | AA by a hair. Too thin for 12px uppercase with tracking. | | Ink at 0.72 on Volt | 6.74:1 | The floor for supporting text on a field. Use `.pg-attr-volt`. | | Paper/white on Volt | 1.20:1 | Unusable. There is no white-on-Volt treatment, ever. | So on a Volt field: Ink for primary type, Ink at 0.72 (or `--ink-muted-text`, 5.5:1) for supporting type, and an Ink fill with Volt text for any button that sits on it (`.cta-volt-btn`; the inverse of `.qs-label-volt`). **Where fields earn their place.** The closing CTA, one outcome panel per case study, a single availability or status chip. Where they do not: navigation, footer, hover states on a repeating card, or more than one cell in one grid. **The base of every page is a Volt field.** The `Call To Action` component — the last block before the footer on every page that has one — is a `.cta-volt` panel: Ink headline, Ink button with Volt text, no mark of any kind. It is the site's single standing field, which is why nothing else may put a field near the bottom of a page. Checked 27 Aug 2026: the nearest competing fields are `.svc-volt` on `/services` and a `.pg-cell-volt` on `/process`, both a full section clear of the CTA. `/contact` carries no CTA component. One field did collide, and it is worth recording how it was missed. The 'Art Backed by Science' panel on `/about` had been a Volt field since 18 Aug — but it was made one by an `!important` override in that page's own head code, not by any Volt class, so a class-name sweep of the site reported `/about` clean. It sat two sections above the new Volt CTA. Reverted to Ink on 27 Aug. **Audit for fields by rendering the page, not by grepping for class names** — page-level custom code can create a field that no class inventory will show. --- ## 2. Grid - **Module: 40px.** Every width, height and offset is a whole number of modules. - **Container: `.main-container`** — max-width 1480px, padding 0 60px, left-aligned (`margin-left:0; margin-right:auto`). Drops to 40px padding ≤991, 20px ≤479. - **Column canon: 1, 2, 3, 4 or 6.** No other division exists. Five columns would give 208px and seven 125.7px — neither is a whole module. If a layout seems to want five, it wants six with one left open. - **Gutters: 40px, 80px or 120px.** All three are whole modules. 64px appears in older grids and is legacy, not canon. Which one is not free choice — it is whatever makes the columns land on the module. For N equal columns in a run of R modules you need `R - (N-1)*g` to divide by N. A 27-module run in three columns has no solution at g=2 (80px) and works at g=3 (120px); a 33-module run in two columns works at g=1 or g=3 but never at g=2. Do this arithmetic before picking a gutter, or the rules will sit a few pixels off and the whole grid will read as approximate. - **Fields bleed off one edge** and terminate on a column line. Symmetric insets read as a boxed panel, which is the habit this system rejects. - **Horizontal rules land on document-absolute multiples of 40** — not merely 40 apart from each other. Alignment is measured against the page, not the parent. - **Spacing scale: 20 / 40 / 160.** 20 inside a group, 40 between groups, 160 between sections. 80 and 120 survive only as section padding (80 + 80 = 160). Anything smaller than 20 is optical correction, not spacing, and is justified as such (the 18px under the red mark, the 39px under a 1px hairline). - **Baseline: 20px** — half a module. Every line-height is 20, 40, 60 or 80, so text and layout share one grid and agree on every line. --- ## 3. Page construction ### 3.1 Vertical rhythm - `.section` — 80px top and bottom, so two sections meet 160px apart. 40/40 at ≤991 and ≤767. Modifiers: `.filled` (Ink background, Paper text), `.no-top-padding`, `.no-padding`. - Three distances, used everywhere: - **20** inside a group — label → heading, title → body, paragraph → paragraph - **40** between groups — heading → content, card → card, block → block - **160** between sections - Wrappers inside a section (`.pg-wrap`, `.rl-say`, `.rlf-cta`) carry no margin of their own; the section padding sets the break. Where two topics share one section (Selected work + Principles), the second opens with a 160px top margin. ### 3.2 Section opening pattern Every documented section opens the same way, top to bottom: kicker (mono, numbered, hairline above) .ds-kick heading .ds-h2 standfirst .ds-sub content On editorial/marketing sections the equivalent is: header rule (hairline + 160×8 ink bar) on the opening block eyebrow (mono label) .rl-say-eyebrow / .pg-eyebrow claim (28/40 Manrope 500, one volt mark) .rl-say-claim / .pg-title supporting columns .rl-say-proof **The header rule** (v4, from the Swiss proposal reference): every homepage section opens with a 1px Ink-15% hairline across the content column and a 160×8 Ink bar flush right, sitting on the line; content starts 40px below (39px padding + the 1px line). Applied to `.rec-head`, `.section-header`, `.pg-wrap` and `.rl-say`. It is the one repeated structural mark on the page — do not add a second divider style. ### 3.3 Global furniture (inherited, do not rebuild) Every page carries these — a new page gets them for free: - **Top Navigation** component (Webflow component, site-wide) - **Call To Action** component ("Tell me what you're building…") - **Footer** component - **Background layers**: a p5 canvas drawing the 40px grid + left margin rule; `#rl-pockets` (faint random 40px cells); the page-transition curtain. These sit behind content. New sections must not fight them — give any section that needs its own background `position:relative` and a z-index. ### 3.4 New page skeleton .relative-containor └ .static-bg-color └ [Top Navigation component] └ .section ← hero: h1.big-text + p.measure-body └ .main-container └ .section (+ modifier) ← body sections └ .main-container └ [Call To Action component] └ [Footer component] --- ## 4. Type - **Families**: Manrope (display/headings, **500**), Inter (body, 400/500), IBM Plex Mono (labels, kickers, tokens — 500). Manrope 600 is not loaded; 700/800 are retired for headings. - **Scale: an octave (×2).** Body 14 → heading 28 → display 56. Seven sizes total: 11, 12, 14, 16, 28, 56 (+36/48 only as responsive steps of display). Bound to the **Type** variable collection: Size Label/Small/Body/Title/H2/ Display and Line 1 (20) / Line 2 (40) / Line 3 (60) / Line 4 (80). - **Line-height formula:** `lh = 20 × ceil(size × k / 20)`, k = 1.4 for running text, 1.1 for display. Sizes are chosen so `size × k` lands just under a multiple of 20. - **Baseline: 20px.** Divides the module. (v3's 24px baseline is retired — it existed only because body copy was 17px.) - **Measure:** body max 440px (11 modules, ~30× the type size, ~60–65 chars). - **Tracking (Inter, rsms dynamic metrics):** `t = -0.016 + 0.21·e^(-0.18·size)` em. Manrope headings −0.02em (28) / −0.03em (56). Mono labels +0.06em, caps. - Hierarchy comes from size jumps, position and space — not weight. Body 400, titles 500, headings 500. Never 300 at 14px. At most three sizes per screen. | Role | Classes (homepage, v4) | Size / line, weight | |-----------|----------------------------------------------------------|---------------------| | Display | `.big-text` (every page), `.cta-volt-title`, `.rlf-h` | 56 / 60, Manrope 500, −0.03em (48/60 ≤991, 36/40 ≤767) | | Lead | `.about-intro-lead`, `.cs-lead`, `.svc-lede`, `.cs-callout`, hero `.measure-body` | 20 / 30, Inter 400, max 560 — intros only, one per section | | Heading | `.section-header`, `.pg-title`, `.rl-say-claim` | 28 / 40, Manrope 500, −0.02em | | Title | `.project-thumbnail-title`, `.pg-name`, `.pg-quote`, `.rec-quote` | 16 / 20, Inter 500 (quotes 400) | | Body | `.body`, `.measure-body`, `.small-text`, `.pg-body`, `.rl-say-proof`, `.rl-clients`, `.ft-link`, nav | 14 / 20, Inter 400 | | Small | `.rec-name` (500), `.rec-role` | 12 / 20, Inter | | Label | `.rec-head`, `.pg-eyebrow`, `.rl-say-eyebrow`, `.pg-num`, `.pg-attr-volt`, `.ft-label`, `.rlf-kicker` | 11 / 20, Plex Mono 500, caps, +0.06em | All pages are migrated (9 Oct 2026). The site-wide mapping lives in the hosted site script `rlrhythmtype` (CSS block `#rl-type-site`, selectors prefixed `:root body` so they beat page-level !important rules). New classes must be set to one of these seven roles in the Designer, or added to that mapping. Allowed type: Display 56/60 · Heading 28/40 · Lead 20/30 · Title 16/20 · Body 14/20 · Small 12/20 · Label 11/20 mono. Nothing below weight 400. --- ## 5. The rules 1. Every width, height and offset is a whole number of 40px modules. Nothing lands between lines. 2. Columns come from 1, 2, 3, 4 or 6. No other division exists here. 3. Fields bleed off one edge and stop on a column line. Never boxed in on all four sides. 4. Running text stays between 45 and 75 characters a line, with 66 as the target. 5. Type sits on a 20px baseline — half a module. Every line-height is 20, 40, 60 or 80. --- ## 6. Component inventory Reuse these. Do not create a new class for something already here. ### Emphasis and marks — the site's signature - **Highlighter** — ``. A textured marker stroke (webp), NOT a flat Volt background. This is the correct way to emphasise words in a heading. One per heading, on the payoff. - **Flat Volt span** — `.rl-claim-volt`. Exists, but the highlighter is preferred; use flat Volt only where the marker texture cannot read. - **Muted span** — `.rl-claim-muted` (Ink 55%) for de-emphasising a clause. Do not two-tone a heading unless the muted part is a *substantial* clause — greying only "I" and "and" reads as a mistake. - **Inline prose link** — `.rl-inline-link`: inherits colour, 1px underline at Ink 35%, offset 3px, darkens to 90% on hover. - **Scribbled overline** — Signal Red, drawn on link hover by site script. ### Buttons and chips (all 40px tall — one module) - `.rl-btn` + `.rl-btn--primary` (Ink fill, Paper text; Volt on hover) - `.rl-btn` + `.rl-btn--secondary` (Mist fill, Ink text) - `.marquee-inner-box` — service chip: Mist, 40px, 0 20px, Volt on hover only. The script-flagged `[data-rl-core]` chips are Mist too (v4); a row of eleven Volt chips was a field, not a mark. - `.svc-all` — "All services →" link-chip, same metrics - `.rl-actions` — the row that holds buttons; 20px gap, 40px top margin ### Text blocks - `.rl-clients` / `.rl-cli-a` — the client-name line with underlined links - `.ds-rule` + `.ds-num` — numbered rule row; number sits on a Volt chip - `.ds-trrow` + `.ds-trk` + `.ds-trv` — trade-off row (280px label column) - `.ds-md` + `.ds-mdmark` — callout with a 24px Volt square - `.ds-chip` — colour swatch, 120px tall, 1px Ink 10% ring ### Grids - `.pg-grid` — principles / process cards: **3 × 320px, 40px gaps** (8-module squares, one module apart; 26 modules wide). 2 × 320 at ≤991, 1 column at ≤767. `.pg-cell` keeps 1:1 and 40px padding: number top, title + body bottom. **No icons** — the per-principle marks were retired 8 Oct 2026; the number is the only top-of-card element. - `.rl-say-cols` — 2 columns, 64px gutter (legacy; 80 will not divide a 33-module run — use 40 or 120) - `.rl-ai-cols` — 3 columns, 64/48 gaps, → 1 column ≤991 - `.ds-hero` — 2 columns, 80px gutter, centred, → 1 column ≤991 - `.ds-swatches-1` — 5 → 3 (≤991) → 2 (≤767) - `.ds-rules` — 2 → 1 (≤991), 80px gutter - `.ds-mo-grid` — 2 → 1 (≤767) ### Documentation specimens (/design-system only) - `ds-sec` — neutral wrapper, one per documented section - `ds-ty` + `ds-tyrow` + `ds-tymeta` — the specimen row: a 280px mono label column and a live specimen beside it. Same shape as `.ds-trrow`; one pattern serves type, components, imagery, spacing, responsive and voice. - `ds-tynote` — the small grey note under a specimen - `ds-sp-display` / `-card` / `-h2` / `-lede` / `-body` / `-sub` / `-mono` / `-cap` — type at real size without the layout the real class carries. A specimen needs the type but not the 120px top margin. - `ds-sp-inter` / `ds-sp-plex` — family specimens at display size - `ds-figwide` — a figure at full content width, drawn at true module scale - `ds-imgs` + `ds-imgfig` — the imagery specimen grid, 3 → 2 (≤991) → 1 (≤767) Specimen blocks and figures carry `position:relative; z-index:1` and a Paper ground (page footer code). `#rl-pockets` builds its occupancy map from a selector listing `p, h1-h6, li` and friends, so text inside a `div` — which is what a specimen row is — is invisible to it and random cells land mid-paragraph. This is the §3.3 remedy, not a workaround. ### Diagrams Architectural construction-drawing style: 40px grid at Ink 6%, dashed construction circles at Ink 30%, centre marks, dimension leaders with mono labels, one Volt cell marking the significant module. **A figure about the grid is BUILT on the grid, not drawn as a picture of it.** The column-canon and spacing figures on /design-system are real CSS grids: `grid-template-columns: repeat(33, 40px)`, `grid-auto-rows: 40px`, a repeating linear-gradient for the rules, and bars placed by span class. Gutters are real spacer elements, so DOM order alone produces the layout and every row sums to 33 by construction. The reason is measurable. A drawn figure sized `width:100%` is 1:1 at exactly one viewport, because the container is fluid from 992 to 1600 and then locks at 1360px of content. Measured on the first attempt: cells rendered 40.00px at 1440 and 41.21px at 1600 and above — a figure claiming to show a 40px module while drawing 41.21px ones. Built from real tracks the cell is 40px at every width and cannot be anything else. A figure still has to START on a row line, so `.ds-gfig` is in the page's SNAP_SELECTOR; without it the whole figure sat 2px under the background grid. Where a drawn figure IS the right answer (the logo construction diagram, Fig. 01), render at true module scale — a 480x320 viewBox is 12x8 real modules — pin its width to the artwork's own size rather than 100%, and snap it to the page grid. Let it scroll, never scale. ### Webflow: inline styles become generated classes Do not use inline `style` attributes when inserting markup through the API. Webflow converts each one into an auto-named class (`inline-div-0`, `inline-div-1`, ...) and the numbering RESTARTS per insert, so two separate inserts collide: a `width:8px` from one figure landed on another figure's 15-module bars and silently broke the layout. Use named classes for everything. Second trap: if any class in a `class` attribute does not already exist as a style, Webflow strips the ENTIRE attribute for that element — not just the unknown name. Spacer divs written as `class="ds-ggap ds-s3"` arrived as bare `
` because `ds-ggap` had not been created yet. Create every class first, then insert. --- ## 6b. Imagery There is no photography. Rendered graphics and stock imagery signal effort without carrying information. Everything is either built on the grid or is a real artefact of real work. - **Article graphics** — main 2400×1280, thumbnail 800×800. Grid pitch is 80px on BOTH canvases; do not use a finer unit to fit more detail into the thumbnail. Flat solid rectangles only, one 80px cell per unit, flush inside the cell, 1px inset so a hairline of grid separates adjacent same-colour squares. Ground `#F7F7F5`, grid line `#E0E1E0`, shapes Ink, exactly ONE shape or row in Volt. Position with integer column arithmetic (`left_col = centre_col - i`), never `centre_x - width/2` — continuous maths offsets odd-width rows by half a cell, invisible by eye and obvious the moment you check every edge against a multiple of 80. Full spec and failure history: `claude/reloop-blog-illustration-style-2026-08.md`. - **Case study images** — real product screens, never device mockups. 7:5, Mist placeholder ground, 1px Ink 8% hairline instead of a shadow, zoom cursor, 55% opacity until hover. - **Client marks** — WebP, supplied artwork, Mist ground, 40px inset. Never recoloured, never cropped to a circle. - **Textures** — two only: the Volt highlighter stroke and the Signal Red scribble. Both scans of real marker. The one place a human hand shows. - **Format** — WebP for raster, SVG for marks and diagrams, PNG only where an article graphic needs exact flat colour. OG image 1200×630. - **Never** — stock photography, gradients, drop shadows, 3D renders, device frames, abstract AI-generated imagery, any image whose job is to fill space. --- ## 7. Motion One curve, three durations, one rule. --ease: cubic-bezier(.22, 1, .36, 1) /* everywhere; things settle */ --dur-micro: 240ms /* hover, focus, chip state */ --dur-panel: 400ms /* drawers, reveals, anything that opens */ --dur-page: 520ms /* page-level moves */ **Motion moves in modules.** Nothing travels a distance that isn't made of 40px — 8, 20, 40, 80. That is what makes movement feel like it belongs to this grid rather than easing applied on top of one. Existing motion: the page-transition curtain (40px cells sweeping, Paper/Mist, ~1 in 250 Volt), `#rl-pockets`, hover transitions at 150–165ms. Any animated background must: idle when off-screen or the tab is hidden, render one static frame under `prefers-reduced-motion`, and ease per-element rather than per-frame (a value that can jump between frames reads as flicker). --- ## 8. Responsive Webflow breakpoints: **≥1920, ≥1440, ≥1280, ≤991, ≤767, ≤479.** - `.main-container` padding: 60 → 40 (≤991) → 20 (≤479) - `.section` padding: 80 → 40 (≤991, ≤767) - Multi-column grids collapse at ≤991; three-ups may need ≤767 as well - Mobile drawer rows are 64px (a deliberate touch-target exception, §10) - Every grid needs an explicit collapse. A grid with no breakpoint variant will hold its desktop column count on a phone — this has bitten the swatch grid before. --- ## 9. Voice - First person, present tense. Plain words over industry vocabulary. - Claims are specific and falsifiable; no adjective does work a fact could do. - Em-dashes for asides. Sentences may be short. Fragments are allowed. - Headline pattern: a flat statement, then the turn — "Building got cheap. Choosing what to build didn't." - Never "guide", "leader", or "transitioning" — these were removed deliberately from positioning copy. --- ## 10. Known trade-offs Documented deliberately. A system that only records its intentions is marketing. - **Hero spacing is still script-driven.** The site rhythm script writes inline `!important` margins (80px) under `.big-text` and `.rl-clients`, so the hero reads 80/40/80 where the system wants 40/20/40. - **Cards tighten before the grid breaks.** In three columns, card text drops to about 34 characters on narrower screens, under the 45 minimum. - **The mobile drawer keeps its own rhythm.** 64px rows are a touch-target choice, off-module and deliberate. - **The logo mark is not on the module.** R67.5 / R37.5 with centres 104 apart — the mark predates the grid. Diagrams of it place the *frame* on the module, not the curves. --- ## 11. Drift log Places where the live site currently contradicts this file. These are bugs. - **Page sketches still hardcode a white ground.** The per-page p5 grid sketch (11 pages, a different variant on each) declares `BACKGROUND_COLOR = 255` and paints it opaquely over `.static-bg-color` every frame. It is currently neutralised, not fixed: the site-wide `Paper grid canvas blend` script sets `canvas.p5Canvas{mix-blend-mode:multiply}`, which makes white a no-op so the ground resolves to the real `--paper`. Change each sketch to read the token (or simply `clear()`), then retire the script. Open — 27 Aug 2026. - **`rl-build` carries its own `PAPER='#FFFFFF'`** in the site footer code. It is only the fallback used when no ancestor has an opaque background, and `.body` always does, so it never fires today — but it is a second hardcoded paper value. Open — 27 Aug 2026. - **`.marquee-2` is dead** — opaque white at base, `#303030` at ≤991, and used by no live page. Delete it rather than maintain it. Open — 27 Aug 2026. - **Homepage overrides live in a page script.** `rltypeguard` (homepage footer, v1.1.1) forces the scale over older injected CSS (`.measure-body`, `.pg-cell` text, nav line-height), draws the header rule, sets the volt discipline, fixes the red mark to 20px, hides the principle marks and sets the two 160px section breaks. Each rule should move into its class (or the injecting script should be cleaned) and the guard retired. Open — 8 Oct 2026. - **The principle marks are still injected** by the homepage head script (`marks()`), then hidden by the guard. Delete `marks()` / `MARKS`. Open — 8 Oct 2026. - **v4 type is enforced by script, not by classes.** Every page now renders the seven roles (audited 20 pages, 9 Oct 2026), but many classes still carry v3 values underneath and `#rl-type-site` overrides them. Move values into the classes and retire the mapping. Open — 9 Oct 2026. - **`.rl-say-cols` uses a 64px gutter**, outside the 40/80 canon. Legacy. Open — 27 Aug 2026. - **`#rl-pockets` blobs cross the content edge and grow linear arms.** Measured on `/ai-driven-design`: 49 of 134 cells landed outside the content box (out to x=1420 against a 1380 edge), and blobs formed straight runs of up to 7 cells vertically and 10 horizontally. A 400px unbroken hairline at a random position reads as a rule, not as a pocket of grid — it is the last thing on that page that does not resolve to the module. Fix is in the site footer code: drop `BLEED` from 7 so clusters stay inside the content box, and lower `MAXC` so blobs stay compact rather than linear. Deliberate effect, so this is a design call, not a bug. Open — 27 Aug 2026. ### Resolved - **Supporting text is solid, not translucent (v4.2).** 165 labels on 21 pages were set as Ink at 0.45–0.60 and failed AA. Every Webflow class, page code block and injecting script (`Reloop keylines` 2.15.1, `reloopdstabs` 1.0.1) now reads `--ink-muted-text`. All 108 published pages measured at 0 contrast failures. Case-study `h4` kickers now render as the mono label (they were falling to 16px Inter — `rlrhythmtype` 1.0.11). 14 Oct 2026. - **Baseline and module agree (v4).** Body moved from 17/24 to 14/20, so the baseline is 20px and every line-height is a multiple of 20 — the 24-vs-40 trade-off in v3 §10 is gone. Homepage measured at 0 headings off the 20px grid. 8 Oct 2026. - **Eyebrows are all Plex Mono** (`.rl-say-eyebrow` moved off Inter). 8 Oct 2026. - **`.section` mobile padding is 40px** (was 60, off-module). 8 Oct 2026. - **Homepage section rhythm is one number.** Section-to-section gaps measured 120 / 280 / 240 / 240 / 360 before; 160 throughout after. 8 Oct 2026. - **'Art Backed by Science' is Ink again.** The `/about` panel was a Volt field from 18 Aug; when the closing CTA became the site's standing field it left two Volt fields two sections apart on one page. Back to Paper type on Ink, no module grid, and the pixel-staircase mark inverted — Paper cells with the single accent cell in Volt, so the panel keeps exactly one Volt mark. 27 Aug 2026. - **The closing CTA is a Volt field.** Was Paper with a 64px Big Text headline, a Volt highlighter on "what you're building", and a text link wrapped in the Scribbly hover Lottie. Now a Volt panel with an Ink headline and an Ink button with Volt text (`.cta-volt`, `.cta-volt-title`, `.cta-volt-btn`), applied at the component so it lands on every page at once. The highlighter and the Lottie came off under the no-stacking rule in 1a. 27 Aug 2026. - **The grid figures are now the grid, not pictures of it.** Rebuilt as real CSS grids on 40px tracks with bars placed by span class and gutters as real spacer elements. Verified at 1440, 1920 and 2600: every bar row resolves to identical module positions — 0+32/32+1, 0+15/18+15, 0+9/12+9/24+9, 0+6/9+6/18+6/27+6, 0+3/6+3/12+3/18+3/24+3/30+3 — and every track is exactly 40px. Fixed 27 Aug 2026. See §6 Diagrams for why, and for the two Webflow traps found on the way. - **The /design-system page now documents type, imagery, the grid, spacing, components, responsive and voice.** Seven sections added 27 Aug 2026, all as live specimens rather than descriptions — real classes, real assets, figures drawn at true module scale. The page previously covered only marks, colour, the rules, motion and trade-offs, which meant the two things asked for most (what the fonts are, what the images should look like) existed nowhere. - **Mono fallback fixed.** `.ds-kick` and `.ds-mo-token` now declare `IBM Plex Mono, monospace`. Fixed 27 Aug 2026. - **The design system page's own sketch bled its rules.** Same fault as `/ai-driven-design`: swatch rules drawn `0 -> width`. Now content edges. Fixed 27 Aug 2026. - **Paper swatch on /design-system read `#FFFFFF`** and described itself as pure white, three weeks after the token went warm. Fixed 27 Aug 2026. - **`/ai-driven-design` rules did not resolve to one structure.** Four separate causes, all measured at 1440: · `.sw-cols` used `1.6fr 1fr` plus an arbitrary `padding-right:120px`, so the aside rule landed at x=829 (+9 off module) and stopped at 1260 while every other rule on the page ended at 1380. Now `minmax(0,1fr) 480px` with no padding — the aside is a fixed 12 modules, so its rule is module-aligned at any container width. · `.sw-3col` used an 80px gutter in a 1080px run. Three equal columns cannot divide that on-module (3c + 2g = 27 modules has no solution at g=2), so the Context/Rules/Skills rules sat at 687 and 1073, off by 13px. Gutter is now 120px → 280px columns at 300/700/1100. · `.rl-ai-cols` used a 64px gutter → columns at 60/521/983. Now 120px → 60/540/1020, so the principle columns and the `.sw-3col` columns finally read as the same grid. · The page's own p5 sketch drew the `.sw-row` rules `0 → width`, making the canvas rules the only ones on the page that bled past the content box; and it ran the aside's vertical rule down to the FIRST `.sw-row` anywhere on the page, which — after the "Design for the AI era" section was moved in above those rows — put a 900px rule straight through that section's columns and body copy. Both now respect the content edges and the aside's own bounds. Verified: every long rule on the page runs 60 → 1380. Fixed 27 Aug 2026. - **Paper is now warm.** `--paper` is `#F7F7F5`, matching what the `/design-system` page has always claimed. Verified at production: every page renders a dominant ground of `rgb(247,247,245)` and 0% pure white. Rebound in the same pass: `.menu-button`, `.swiss-graphic`, `.text-field`, `.textarea-field` and the `.marquee` medium/small breakpoints, all of which had hardcoded white. Fixed 27 Aug 2026. - **Contact form fields were un-styled Webflow defaults** — white fill, `#ccc` border, 3px radius, none of which exist in this system. Now Paper fill, 1px Ink hairline at 0.20, no radius, 0.60 on focus — matching the Volt contact panel that serves every other page. Fixed 27 Aug 2026. - **Nav and footer are now module-aligned.** Nav links are 40px boxes with 8/20 padding and 8px gaps; footer sections are 80px with 80/8 gaps and 160px columns. Fixed 27 Aug 2026 (was the largest open trade-off). - **`.marquee-inner-box` at the wide breakpoint** previously set 28px padding (off-module), 20px type and an ink-fill hover. Now 20px padding, .875rem, Volt hover — consistent with base. Fixed by 27 Aug 2026. - **Token names now match the system.** Were `Primary`, `Secondary`, `Grey`, `Accent Volt`, `Hover`. Renamed to `Ink`, `Paper`, `Mist`, `Volt`, `Ink Muted`. Fixed 8 Aug 2026. - **Signal Red had no variable** and was hardcoded. Now `--signal-red`. Fixed 8 Aug 2026. --- ## 12. How this file is used This site is built in Webflow, so nothing compiles. There is no build step that reads this file and fails a check — and claiming otherwise would be the exact kind of unfalsifiable statement this system exists to avoid. What it does do: it is the context an AI agent is given before it touches the site. Tokens, module, canon and measure arrive as constraints rather than preferences, which is why the tenth screen matches the first. The drift log is the honest half — it records where the site has got ahead of its own rules. Site-wide CSS that is not in Webflow classes lives in Project Settings → Custom Code → Head. It contains the nav layout and mobile-drawer fixes, the contact panel, and the page-transition script. Edit it surgically; it is one block and a careless overwrite loses all of it. --- ## 13. Checklist before publishing a new page - [ ] Every offset, height and gap is 8 / 20 / 40 / 80 or a multiple of 40 - [ ] Columns are 1, 2, 3, 4 or 6 — never 5 or 7 - [ ] Body text sits between 45 and 75 characters - [ ] Reused existing classes; no near-duplicate new ones - [ ] Colours bound to variables, no hardcoded hex - [ ] At most one Volt mark per heading; Volt still scarce on the screen - [ ] At most one Volt field per screenful; no field adjacent to another; no Volt mark inside a Volt field - [ ] Supporting type on a Volt field is Ink at 0.72, not 0.60; no white on Volt - [ ] Every multi-column grid has an explicit ≤991 collapse (and ≤767 if 3-up) - [ ] Checked at 1440, 991, 767 and 390 — no horizontal scroll - [ ] Page carries nav, CTA and footer components - [ ] New drift, if any, added to §11 rather than left unsaid *Last verified against the live site: 27 August 2026.*