Spacing & Layout Audit — LAYOUT.md

A real report, written by 57 · Spacing & Layout Audit, run against this repo — dogfood output, committed unedited. This is the artifact every brief ends in: findings that cite their evidence, ranked by severity, with a fix sketch each.

Read-only reverse-engineering of the spacing system across template.html, build.pySITE_CSS, studio.html, vitals.html. Every value is grepped from those files with counts; selectors and lines are cited.

Headline: the patch layer is essentially empty — one deliberate negative margin, zero layout !important, no absolute-position alignment hacks. That's real discipline and rare. The debt is elsewhere: there is no spacing scale (≈30 distinct values, and the 2nd- and 4th-most-common gaps — 9px and 7px — sit off any 4/8 grid), and the three hand-authored stylesheets diverge on the things that should be shared: container width (1060 / 940 / 760), gutter (22 / 20), section rhythm (60 / 46 / 40 / 34), and the nav breakpoint (720 / 680 / 640). Fix the system, not the instances.

Backlog reconciliation (2026-07-09). This audit's specific widths and line numbers predate the redesign (the landing .wrap is now 1120px, tokens moved to tokens.css). Dispositions: L1 (container widths) — FIXED: the four .wrap widths now come from --w-page/--w-doc/--w-read tokens with a single --gutter (24px), ending the 24/22/20 drift. L3 (nav breakpoint) — FIXED: unified to 720px (detail pages were 680). L4 (section rhythm) — FIXED (round 3): a 4pt spacing scale (--s1..--s9) plus --section/--section-tight now live in tokens.css, and every section pad routes through them (.blk.tight--section-tight, section.blk--s7, both footers→--s7/--s8+--s9) — “a section” derives from one scale. L6 (child margins, off-grid) — FIXED (values): the off-grid 22px margin-top and the two biggest off-grid gaps (9px ×13, 7px ×8) were snapped onto the scale (22→--s5, 9→8, 7→8; 35 declarations) so they’re on-grid; per the audit’s own guidance the 82 margin-top sites were left as margins (rewriting all to gap is high churn for “mild chaos”) — only put on-scale. L2 (max-width ladder) — ADDRESSED: the container ladder is tokenized (--w-page/--w-doc/ --w-read); the remaining grid-internal caps are deliberate per-component ceilings (the audit itself calls the differing widths “defensible”) and stay as-is to avoid shifting reflow. L5 (11 breakpoints) — value-unified, not tokenized: @media can’t take var(), so breakpoints are consolidated by value, not a token. The clean “patch layer” the audit praised is untouched — the fixes were systemic (tokens), not new instances. See FIXLOG.md.


1 · The spacing census

Distinct values in use (gap ∪ margin ∪ padding-axis)

0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 26, 30, 32, 34, 40, 44, 46, 56, 60, 66~30 distinct steps. A tokenized 4/8 scale has ~8.

Gaps, by frequency (the tell)

8px ×17   9px ×13   12px ×10   7px ×8   18px ×7   10px ×7   6px ×5
5px ×4    16px ×4   14px ×4    4px ×3   20px ×3   13px ×3   11px ×2 …

Against a strict 8pt grid {8,16,24,32,40,48}, only ≈24% of gap usage is on it; against a 4pt grid ≈41%. So ~59% of gaps are off even a 4pt grid — and the off-grid values aren't rare exceptions: 9px (×13) and 7px (×8) are the 2nd and 4th most common gaps in the whole codebase. This isn't an 8pt system with drift; it's continuous ~1px hand-tuning.

Paddings — 40+ distinct compound values

12px 14px, 9px 11px, 6px 12px, 9px 15px, 8px 14px, 8px 13px, 7px 12px, 6px 10px, 12px 18px, 12px 16px, 12px 15px, 11px 14px, 10px 14px, 9px 12px, 8px 11px, 5px 9px, 5px 8px, 4px 9px, 4px 10px, 2px 7px … Button/chip insets alone span a dozen near-identical pairs that a 3-step inset scale (sm/md/lg) would cover.

Margins — ~20 distinct

Top values: 12px ×11, 4px ×10, 22px ×10, 8px ×9, 14px ×9, 10px ×8, 16px ×7, 6px ×6, 2px ×6, 20px ×5, then 34, 30, 26, 24, 32, 18 …. 22px (off-grid) is a top-3 margin — it's also the page gutter (below).

The patch layer — a clean bill (strength)

  • Negative margins: one. top:-11px on .phase .step (build.py:397) —

a deliberate badge sitting on a card's top border, not a hack.

  • !important: zero in layout. The only four hits are

*{transition:none!important} inside @media(prefers-reduced-motion) — correct usage.

  • position:absolute: 6 total, all decorative — hero/dhero radial

::before gradients, the .pnode::after connector line, the .phase .step badge. None are alignment patches.

  • Transforms are interaction-onlyscale(.96/.97) on :active,

translateY(-1/-2/-3px) on card hover, rotate(90deg) on chevrons. No static positioning nudges.

There is nothing to delete here — the debt is missing tokens, not accumulated hacks.


2 · Alignment & container findings

L1 — Three "content widths," all named .wrap

File.wrap max-widthgutterLine
template.html (landing)1060px0 22px:48
build.py (detail pages)940px0 22px:332
studio.html / vitals.html760px0 20px:37 / :35

Different widths are defensible (2-col landing vs article vs reading tool), but three values share one class name with no token, and the gutter drifts 22 → 20px with no reason — so nav and content edges land on a 2px-different column between the marketing pages and the tools.

L2 — 13 distinct max-width values, no ladder

340, 420, 460, 560, 640, 680, 700, 720, 760, 820, 860, 940, 1060. Several double as breakpoints (below) and as component caps. There's no discernible container ladder — each grid picked its own ceiling.

L3 — The same nav hides at three breakpoints

The identical nav-links component collapses at 720px (landing, template.html:62), 680px (detail, build.py:346), and 640px (Studio/Vitals, studio.html:50, vitals.html:48). Between 640–720px the same site shows full nav on one page and a hidden nav on another. One shared breakpoint token fixes all three.

L4 — Section vertical rhythm is four different numbers

"A section" pads 60px 0 on the landing (.blk, template.html:111), 46px 0 when .tight (:112), 40px 0 on detail pages (section.blk, build.py:382), and the tools use header 34px + main 22px (studio.html:51,61). The hero is 66px 0 60px (:83). Vertical rhythm should be one scale; today it's ad-hoc per surface.

L5 — 11 breakpoints, none shared

420, 460, 560, 640, 680, 700, 720, 760, 820, 860 plus reduced-motion. Grids reflow at a dozen widths — .hero-in@860, .store@860→560, .method@820→460, .lenses@640, .val@820, .ways@820/760, .quotes@760, .proof@700, .pipe@760→420. A user dragging a window resize sees the page rearrange at ten unrelated points instead of 2–3 deliberate ones.

L6 — Components own their outer margins (partial)

82 margin-top declarations (39 landing / 25 detail / 10 studio / 8 vitals) push components down from the inside (margin-top:22px, :20px, :16px…) rather than parents composing space with gap. In a mostly flex/grid layout this is only mild chaos, but it's why the same visual "section gap" is written as 22px in one place and 26px in another — the value travels with the child.


3 · Density verdicts (per key screen)

ScreenDensityVerdict
Landing (/)Airy — .blk 60px 0, hero 66px, gap:44pxChosen & right. A marketing page should breathe; it does.
Catalog listDense — .card 16px 16px 14px, margin-bottom:10px, meta gap:10pxChosen & right for scanning 75 rows.
Brief/playbook detailMedium — section.blk 40px 0, .method gap:12pxChosen & right for reading.
Report StudioTight — main 22px, .find 11px 12px, margin-bottom:8pxChosen & right for a work tool.

Density is a genuine strength — each surface's tightness fits its job. The problem is never how much space but which arbitrary value: the airy landing and the tight tool are both built from the same unscaled grab-bag, so the rhythm reads as slightly off even when the density is correct.


4 · Fixes (one token beats forty edits)

  • Adopt a spacing scale and map to it. A 4pt base covers the real range:

4, 8, 12, 16, 24, 32, 40, 48, 64 (tokens --s1…--s9). Map today's ~30 values onto it: 2/3→ none/4, 5/6/7→ (round to 4 or 8), 9/10/11→8, 13/14/15→12 or 16, 18→16, 22→24, 26→24, 30/34→32, 46→48, 56→…, 60/66→64. The two biggest offenders (9px, 7px gaps) collapse into 8. Deletes ~20 values with almost no visible change.

  • One container token set. Define --w-page 1060, --w-doc 940,

--w-read 760, --gutter 22 (pick one gutter — 22 or 24 — and drop the 20). Point every .wrap/.topnav-in at them; the 2px edge drift (L1) disappears.

  • One breakpoint set. Reduce to ~3 shared tokens (e.g. sm 560,

md 760, lg 960) and route L3/L5 through them so the nav — and every grid — reflows at the same widths on every page.

  • One vertical-rhythm token. Replace the 60/46/40/34 section paddings (L4)

with --section (+ a --section-tight) so "a section" means one thing.

  • Prefer parent gap over child margin-top for new work (L6); no need to

rewrite 82 sites at once, but stop adding to them.

  • Patch layer: nothing to delete — it's already clean. Keep it that way by

fixing systems (above), not adding instances.

Ranked by screens straightened per token: #2 + #3 touch every page for two tiny token blocks; #1 is the deepest cleanup; #4 is quick polish.


Report only — nothing edited; every value grepped from source. The highest-leverage change is the shared container + breakpoint tokens (#2, #3) — they align all four surfaces for a few lines. Which fixes should I make?