# Reloop Digital — design system 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` | Secondary and supporting text. | Every hairline, underline and drawn rule is Ink at reduced opacity. The opacities actually 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`) - `0.55 / 0.60` — supporting text (`#10151f8c`, `#10151f99`) **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 (not the site-standard 0.60) 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. - **Sub-module scale for small spacing: 8 / 20 / 40 / 80.** Nothing else. Anything smaller than 8 is optical correction, not spacing. --- ## 3. Page construction ### 3.1 Vertical rhythm - `.section` — 120px top and bottom. Modifiers: `.filled` (Ink background, Paper text), `.no-top-padding`, `.no-padding`. - Between major sections inside one container: 120px, carried by `.ds-kick` (80px on small screens). - Between a heading and its body: 16px. Between body blocks: 28px. ### 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: eyebrow (uppercase, tracked) .rl-say-eyebrow claim (64px Manrope, one volt mark) .rl-say-claim supporting columns .rl-say-proof ### 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, 700), Inter (body, 300–700), IBM Plex Mono (labels, kickers, tokens, captions — 400/500). - **Baseline: 24px.** Re-synchronises with the 40px module every 120px — three modules, five lines. - **Measure: 45–75 characters**, target 66. `.measure-body` caps at 640px. - Weight carries hierarchy before size does. A 500 weight at 15px outranks a 400 weight at 16px and costs less vertical space. | Role | Class | Size / line | |-------------------|--------------------|-------------------| | Page title | `.big-text` | 64 / 80, Manrope 700 | | Section claim | `.rl-say-claim` | 64 / 80, Manrope 700 | | Section heading | `.ds-h2` | 1.5rem / 32, 500 | | Card heading | `.rl-ai-head` | 22 / 1.3, Manrope 700 | | Lede | `.ds-lede` | 1.125rem / 28 | | Body | `.measure-body` | inherits, max 640px | | Supporting | `.ds-sub` | 1rem / 24, muted | | Editorial support | `.rl-say-proof` | 1.125rem / 32, muted, 52ch | | Kicker / label | `.ds-kick`, `.ft-label` | 12 / 24, mono, .14em tracking | | Caption / token | `.ds-mo-token` | 14 / 24, mono | --- ## 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 24px baseline, re-syncing with the module every 120px. --- ## 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 - `.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 - `.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: 120 → 60 (≤991) ⚠ see drift §11 - 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. - **Readability beat arithmetic.** The 24px baseline does not divide the 40px module. A 20px baseline would, but at 17px body copy that is 1.18 leading and lines collide. Cost: a three-module sync instead of one. - **The measure runs long.** Body text sits near 75 characters — the top of the range rather than the 66 optimum. - **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. - **`.section` mobile padding is 60px** at ≤991 and ≤767 — off-module, breaks rule 1. Should be 40 or 80. Open — 27 Aug 2026. - **Eyebrow font is inconsistent.** `.rl-say-eyebrow` uses Inter; `.ds-kick`, `.ft-label` and `.ds-mo-token` use IBM Plex Mono. Same role, two fonts. Open — 27 Aug 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 - **'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.*