Files
adr-sml/docs/02-design-system.md
Pouya LajevardiandClaude Opus 5 bd282aa47d
Build and deploy / build-and-deploy (push) Failing after 4s
feat: production run — Q61 ramp, /404/, CloudFront router, cutover runbook
Five items of Pouya's production run, 2026-09-01.

Q61 — scroll-padding-top becomes a max() ramp on `10lh - 83px`, with the
plain calc() first as the fallback for engines without `lh`. Hidden focus
stops under minimumFontSize=32: 290 of 1,455 -> 0, control build still
290. Default settings byte-identical (0 differences over 352 page-widths x
17 fields). The 12 residual cells at minimumFontSize=16/20 are pre-existing
and unchanged-or-better; reported, not widened, per instruction.

Intake backend + CloudFront — docs/09-cutover-runbook.md is the
copy-paste sequence for admin execution: every command followed by its
verification and expected output, rollback per part, and Part 10 is Q60's
TTL test. infra/cloudfront/router.js is the trailing-slash function
(30-case suite; 8 fail against the pre-review version, incl. a
protocol-relative open redirect). infra/cloudfront/configure.mjs is
dry-run-by-default and idempotent. scripts/intake-env.mjs emits the six
Lambda env vars from src/data/site.ts.

Four launch blockers found by reading the running system:
  - handler.mjs wrote pk/sk; the live table's key is submissionId with no
    sort key, so every submission would have failed validation silently
  - the Lambda invoke permission is scoped to the old route path
  - 22 of 23 pages 403 without the router function
  - there was no 404 page; src/pages/404.astro adds it

Claims audit (D20 cutover pass) — five gloss over-reaches corrected on
/practice/energy/, /practice/insurance/ (x2), /practice/technology/ and
/med-arb/. Three findings left open for Pouya: Q62, the /med-arb/ gloss,
and Q60.

Q62 — one frozen-tripwire pattern added under the freeze's own breach
exception, with a probe and four negative fixtures. check:claims exits 1
until the false /legal/privacy/ sentence is corrected, so both deploy
paths are blocked by a mechanism rather than by memory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
2026-09-02 06:52:20 -04:00

1226 lines
80 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 02 — Design system
Authority: `AGENTS.md` §3 D7 — *keep the palette and the infinity mark;
modernize the execution.* The look is not up for redesign. What follows is the
system that preserves it while fixing what the old build got wrong.
---
## What carries over unchanged
- The **palette**: cream, ink, maroon, gold.
- The **infinity mark** — SML Company Ltd.'s actual logo, and the metaphor holds:
a dispute is a loop, and the work is redrawing the loop into a line.
**It is a shaded ribbon, not a stroked curve**: a band of variable width that
twists in three dimensions, maroon flowing into champagne, passing over itself
at the crossing. Ink bounding box **2668 × 1704 = 1.5657:1**
`[verified 2026-08-26 — measured against the master]`. Source of truth:
`src/assets/brand/sml-infinity-mark.png`; provenance in
`docs/reference/brand-assets.md`.
- The **type pairing**: Instrument Serif for display, Geist for text, Geist Mono
for eyebrows and labels.
- The **editorial register** — generous whitespace, restrained colour, serif
display type at large sizes.
## What changes
| Was | Is | Why |
|---|---|---|
| Google Fonts at runtime | Self-hosted, subset, preloaded | Removes a render-blocking third-party round trip from a page that collects legal inquiries |
| Fixed px type sizes | Fluid `clamp()` scale | One scale from 360 px to 1600 px with no breakpoint jumps |
| Ad hoc spacing values | 8 px base scale | Consistent vertical rhythm; no magic numbers |
| Gold used as a text colour on cream | Gold restricted to decorative and on-dark | **It fails WCAG AA at 2.10:1.** Measured, not assumed |
| Scroll-reveal on every element, always on | Reveal on major sections only, gated behind `prefers-reduced-motion` | Motion that reads as confident rather than decorative; accessible by default |
| 2.2 MB single file, ~1 MB of base64 logos *(both figures under review — `AGENTS.md` Q34; and "470 KB PNG", which this row used to assert, has no source anywhere in the repo and has been removed)* | Optimized SVG mark, AVIF/WebP photography | The mark is geometry, so it should be vector. That holds whatever the old file weighed |
| React 18 dev build + Babel Standalone in the browser | Static HTML, near-zero JS | The reason the site is invisible to crawlers |
---
## Colour
Tokens live in `src/styles/tokens.css`. Never write a raw hex value in a
component.
| Token | Value | Use |
|---|---|---|
| `--cream` | `#faf7f2` | Page background |
| `--cream-2` | `#f3ede0` | Alternating section background |
| `--cream-3` | `#ebe3d1` | Cards and insets on cream |
| `--ink` | `#1a1614` | Body text; dark section backgrounds |
| `--ink-soft` | `#3a322c` | Secondary text |
| `--muted` | `#6e6359` | Metadata, captions — **on cream only** |
| `--maroon` | `#5a1a1c` | Primary action, accents, dark panels |
| `--maroon-d` | `#3d1112` | Hover on maroon |
| `--maroon-l` | `#7a2a2c` | Links on cream |
| `--gold` | `#c9a876` | Rules, dividers, on-dark accent — **never text on cream** |
| `--gold-d` | `#a88858` | Large decorative text on cream only |
| `--gold-l` | `#e2c89a` | Text on ink or maroon |
### Contrast — measured, 2026-08-25
Against `--cream` `#faf7f2`:
| Colour | Ratio | AA body (4.5) | AA large (3.0) |
|---|---|---|---|
| `--ink` `#1a1614` | **16.81** | pass | pass |
| `--ink-soft` `#3a322c` | **11.75** | pass | pass |
| `--maroon` `#5a1a1c` | **12.29** | pass | pass |
| `--maroon-l` `#7a2a2c` | **8.95** | pass | pass |
| `--muted` `#6e6359` | **5.47** | pass | pass |
| `--gold-d` `#a88858` | **3.11** | **FAIL** | pass |
| `--gold` `#c9a876` | **2.10** | **FAIL** | **FAIL** |
Against `--maroon` `#5a1a1c`: cream 12.29 · gold-l 8.11 · gold 5.84 — all pass.
Against `--ink` `#1a1614`: cream 16.81 · gold-l 11.09 · gold 8.00 — all pass.
`--muted` on ink measures **3.07 and fails**; use `--gold-l` or cream at reduced
opacity for secondary text on dark.
**Hard rules.**
1. `--gold` is never a text colour on cream. Rules, borders, dividers, icon
strokes, and on-dark text only.
2. `--gold-d` on cream only at 24 px+ / 19 px bold, and only for decorative
display text — never for anything a reader must parse.
3. Body text on cream is `--ink` or `--ink-soft`. Metadata may use `--muted`.
4. Secondary text on dark is `--gold-l` or `--cream` at ≥ 70% opacity.
5. **No dark mode.** A single committed light identity is the right call for a
legal practice, and halving the surface area halves the ways contrast breaks.
---
## Typography
**Faces.** Instrument Serif (display) · Geist 300/400/500/600 (text) ·
Geist Mono 400/500 (eyebrows, labels, data).
Self-host all three. Subset to Latin + Latin Extended-A. `font-display: swap`.
Preload only the two faces used above the fold — Instrument Serif regular and
Geist 400.
**Scale.** Fluid, `clamp()`, `1.25` ratio at the small end widening to `1.333` at
the display end. Tokens `--text-2xs` through `--text-6xl` in `tokens.css`, plus
`--text-eyebrow` at 14 px — the same value as `--text-sm` and deliberately not an
alias of it; see the eyebrow rule below.
**Rules.**
- Display type (`--font-serif`) at `--text-4xl` and above only. It has almost no
hinting at small sizes and looks weak below 32 px.
- Display line-height `0.95``1.05`; letter-spacing `-0.02em`.
- Body line-height `1.6`. Measure capped at `68ch` — the old site ran full-bleed
paragraphs at 1400 px, which is unreadable.
- Eyebrows: mono, **14 px** (`--text-eyebrow`), `0.18em` tracking, uppercase,
weight 500, always paired with a real heading. Pouya raised it **12 → 13 → 14 px
on 2026-08-31**, in two passes, because uppercase mono at this tracking reads
smaller than it measures and 13 px was still getting lost against the display
type. There is **one definition**, `.eyebrow` in `global.css`. An eyebrow is not
a heading and never carries the `<h*>` — but a real heading may carry the class,
and **fourteen do, in two groups**: the footer's four column headings
(`SiteFooter.astro`) and `/bio/`'s ten block headings.
⚠️ **"ONE DEFINITION" HAS ONE CARVE-OUT AND IT IS NAMED RATHER THAN IMPLIED:**
`/contact/`'s `<label>`/`<legend>` set and its direct-contact `<dt>`s repeat the
treatment instead of taking the class, because they need `--text-secondary`
(11.75 : 1) rather than `.eyebrow`'s `--text-meta` (5.47 : 1) — a form label is
operative text. **Everything else about them matches, `font-weight` included**;
without that they rendered at 400 beneath a `p.eyebrow` of the same size and
colour, which is the divergence a carve-out is meant to bound rather than hide.
Do not widen this to any other element.
**14 px is the same value as `--text-sm`, and `--text-eyebrow` is deliberately
not an alias of it.** The two move for different reasons; aliasing would mean a
future change to body-meta type silently moved every eyebrow on the site.
(This bullet said "13 px is not a rung on the scale — it sits between
`--text-xs` and `--text-sm`" for one revision. At 14 px it is that rung.)
- **THE 11 px FLOOR HAS EXACTLY ONE CONSUMER, AND THAT IS THE CLAIM THAT IS
TRUE.** Pouya's ruling, 2026-08-31: *"form labels are operative text, and the
site should have exactly one small-text floor."* `--text-2xs` (11 px) now has
**one consumer in `src/`** — the header tagline (`git grep 'var(--text-2xs)' --
src` returns one hit, `SiteHeader.astro`; 21 rendered instances, every one
`span.eyebrow.brand-tagline`) — and nothing else may use it without a
measurement recorded beside it.
⚠️ **THIS BULLET SAID "THE SMALL-TEXT FLOOR IS 14 px, AND THERE IS EXACTLY ONE
EXCEPTION" FOR ONE REVISION, AND THAT WAS FALSE** — falsified by this document
thirty lines below, which concedes a 12 px family. The ruling asked for the
record that the tagline is the only sub-14 px text on the site; **it is not, so
that is not what is recorded here.** What is true is the sentence above: one
consumer of the 11 px token. `--text-xs` (12 px) is a separate rung and is
enumerated below.
**The exception, and why it is deliberate rather than residual:** the tagline is
ornamental and layout-constrained. ⚠️ **THE COST WAS RE-MEASURED ON 2026-09-01
AND IT IS A DIFFERENT COST NOW — the masthead may wrap, so what used to overflow
invisibly is visible instead.** At `--text-eyebrow` (14 px) the header stands at
**144.98 px** rather than 81 px — at 1216 px with six nav items, and at **every
width from 1216 px up** with a seventh (measured at 1216 / 1240 / 1280 / 1360 /
1440 / 1600 / 1760 / 1920) — because the row wraps. *(This listed four widths
ending at 1440 for one revision; that list was carried over from the superseded
CTA-past-the-edge measurement, which really did stop at 1440.)* **Document overflow
is 0 and the CTA sits exactly on `.header-inner`'s content edge in every one of
those cases**, where before the fix they were 20 px of overflow at 1216 px, 4 px
at 1280 px, and the CTA 67.8 / 43.8 / 51.8 / 51.8 px past the content edge. So
the two costs this bullet used to cite are **gone**, replaced by one that is
larger and easier to see: 64 px of header height on every page. *(13 px took the
header to 83.4 px; that figure was taken before the masthead could wrap.)*
Insights is that seventh item. The measurements are in `SiteHeader.astro`.
**Three blocks moved to `--text-eyebrow` on 2026-08-31 and their carve-outs are
superseded.** (a) `/contact/`'s `<label>`/`<legend>` set — *raise, do not
ratify*, superseding the "accepted, not ratified" note this bullet carried for
one revision. (b) `/contact/`'s direct-contact `<dl>` terms, which are label
text on the same page and would otherwise have been left a step behind the
labels beside them. (c) `/bio/`'s ten block headings and its strap, which were
**copies of five of `.eyebrow`'s six declarations, at 11 px** — the same escape
the footer's column headings turned out to be — and now carry the class. Not
byte-for-byte: the `font-size` differed, and **the missing sixth was
`font-weight`**, which is precisely the one the print block now has to freeze.
⚠️ **(c)'s carve-out was justified by a reason that did not apply.** This bullet
said the `/bio/` sheet was held at 11 px because `npm run bio:pdf` holds it to
one page. Measured 2026-08-31: the `@media print` block sets both elements to
**7 pt**, so the screen size never reached the PDF and the one-page constraint
was never what kept them small. **Print does now freeze `font-weight: 400`**,
because taking `.eyebrow`'s 500 changed the printed sheet — the PDF grows from
**89,496 to 91,151 bytes, +1,655** — and the ruling that moved them was about
size. *(This read "63,743 bytes" for one revision. That is the `cmp -l`
differing-byte count, not the size delta: the content stream is Flate-compressed,
so a one-property change scrambles most of the file. Two figures, and the wrong
one answered a question nobody asked.)* ⚠️ **THAT FREEZE IS NOW A RULED
CONSTRAINT, 2026-09-01, NOT A DECISION AWAITING POUYA: the circulated PDF's
typography changes only when its CONTENT is deliberately revised, never as a
side effect of a screen refactor.** So the print block keeps 400 whatever the
screen does, and `.eyebrow`'s 500 stops at the `@media print` boundary. **The
reason it is a rule and not a preference** is that the PDF is the one artefact
this project's apparatus sees only when someone re-renders it (`AGENTS.md`
R16): a refactor that silently re-typesets it changes a document already in a
reader's hands, and the diff that would have shown it is a 1,655-byte
compressed blob nobody reads. A screen change that reaches print is therefore
a defect by construction, not a judgement call — see `bio.astro`.
**`--text-xs` (12 px) IS A SEPARATE RUNG AND IT WAS NOT IN SCOPE ON 2026-08-31.
Ten declaration sites, all of them:** `Pill`, `Breadcrumbs`, `CredentialRow`,
`DefinitionGrid`, `ArticleCard`, `ProcessStep` (two), `insights/[...slug].astro`,
and **`/bio/`'s two — `.fine` and `.sheet-contact p`**. The first eight are mono
and uppercase or tracked at `0.06em`, a third of the eyebrow's tracking, which is
what makes them a different treatment. **`/bio/`'s two are neither uppercase nor
tracked**, so they are not in that family at all — they are simply 12 px text,
and an earlier draft of this bullet defined the exception in a way that excluded
them and therefore missed them.
**Rendered count, and say what it is a number of:** at 1280 px / root 16 across
all 22 pages, **130 elements carrying their own text compute to 12 px** (149 if
elements that merely inherit the size are counted). `ArticleCard` and the article
meta contribute **zero** today — no article is published, so neither renders.
Every instance measures ≥ 5.01 : 1.
- Italic display (`.it`) is the one flourish the design allows. One italic phrase
per headline, at most.
- Never skip a heading level. `<h1>` once per page.
---
## Spacing and layout
8 px base: `--space-1` `4px` · `-2` `8` · `-3` `12` · `-4` `16` · `-5` `24` ·
`-6` `32` · `-7` `48` · `-8` `64` · `-9` `96` · `-10` `128` · `-11` `160`.
Content width `1280px`; prose measure `68ch`; wide media `1440px`.
Gutters: `24px` mobile, `48px` desktop.
Section rhythm: `--space-9` (96 px) mobile, `--space-11` (160 px) desktop —
`--section-y` in `tokens.css`. *Corrected 2026-08-26:* the curve was
`6vw + 2rem`, which reaches 160 px only at a **2133 px** viewport, so the
desktop half of this line was never delivered (measured 108.8 px at 1280 px,
128 px at 1600 px). It is now `9vw + 1rem`, which reaches 160 px at 1600 px. If
you change the curve, re-measure — a `clamp()` whose upper bound is unreachable
reads exactly like one that works.
Grid: 12 columns desktop, 6 tablet, 4 mobile, `--space-5` gutter.
---
## Motion
The old build animated nearly everything on scroll. The replacement is
deliberate and quiet.
- **Reveal on section entry only** — not on every child element. Sub-element
stagger is limited to card grids, and capped at six children.
- Duration `600ms`, easing `cubic-bezier(.2,.7,.2,1)`. Transform and opacity
only — never layout properties.
- Implement with **`animation-timeline: view()`**, behind `@supports`. *Amended
2026-08-26:* the `IntersectionObserver` alternative this line used to offer
first is now ruled out, not merely second choice. It has to run inline in
`<head>` to avoid a flash, and `05-backend-spec.md` specifies `script-src
'self'` with no `unsafe-inline` — so the only script on the site would have
been the one thing the site's own CSP refuses to execute, and a per-build hash
drifts from the policy pinning it. The CSS route ships **zero** JavaScript.
Not a framework, not a library, not a script.
- **Content is visible without the feature.** The `@supports` gate is
load-bearing, not defensive: without it, a browser that ignores
`animation-timeline` runs the animation once against the document timeline at
load; with it, that browser gets no animation and fully visible content. The
old build had this exactly backwards and shipped a blank page.
- **And without a print timeline.** A scroll-driven animation has no timeline
when printing, so a revealed element renders at its `from` state — `opacity:
0`. Measured 2026-08-26: before the print override existed, printing a page to
PDF dropped four card headings from the output entirely. `/about/` is written
to be printed by people evaluating an appointment.
- Hover transitions `250ms`.
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
```
Nothing animates infinitely. Nothing autoplays. Nothing moves on page load
except the reveal of the hero.
---
## Components
| Component | Notes |
|---|---|
| `InfinityMark` | ⚠️ **Currently a raster — a documented, temporary exception to this rule** (`AGENTS.md` Q38, R13). The mark is gradient-mesh artwork rather than flat vector paths. An SVG *is* held and it renders faithfully — **and it renders faithfully because it IS the raster**: 257,278 bytes wrapping **seven embedded base64 PNGs**, against **3,063 B** for the AVIF a Retina browser takes in the header — **84×**. *Figures re-stated 2026-08-27 because a second, larger call site now exists and the single number had become misleading:* the mark ships at **two intrinsic widths**, 64 px (header, footer) and 232 px (the home page's approach section, which renders at 225.5 px). At 232 px a DPR-2 device takes **14,555 B** and DPR-3 **22,639 B**, so the ratio against the SVG there is ~11×, not 84×. The full ladders are in `docs/reference/brand-assets.md`; do not quote one number as if it covered both. Inlining it would breach `CLAUDE.md`'s no-base64-images rule. *Pouya settled the characterisation on 2026-08-27: a first draft called the file "a raster in a vector wrapper", a later draft withdrew that as unfair, and **the withdrawal went too far.** Both halves are true — the fidelity is real, and it is bought with embedded raster, which is exactly why fidelity was never the question.* The exception is about payload and composition. It renders AVIF/WebP with a PNG fallback; a Retina device takes 3,063 B of AVIF, `alt=""` when decorative, aspect ratio pinned to `667 / 426`. **Restore this rule the moment the commissioned vector master lands.** Until then the rule stands unchanged for every other mark or icon: inline SVG, `currentColor`, `aria-hidden` when decorative, never a PNG |
| `SiteHeader` | **Sticky from 66 rem (1056 px) up; static below it.** Practice dropdown as CSS-only `<details>`. **(a)** The one-row header holds the brand, **seven** nav items and the CTA — Insights is the seventh, arriving on its own at build step 7. Binary search on the built page puts the true fit at **1047 px**; 66 rem is the clean token above it. Below 66 rem the nav takes its own row and the header measures **141 px** at tablet widths and **189 px** at 320375 px. **(b)** "Condenses on scroll" is a hairline rule and a shadow, **not a size change** — a `position: sticky` header stays in normal flow, so shrinking its padding lifts every page below it, against the CLS < 0.05 budget. Longhands only; see the component on what the minifier does to the `animation` shorthand. *(This row has been wrong twice, instructively. It first said 60 rem / "~115 px", a height the header never took. It then said 64 rem "with 32 px of clearance" — that 32 px was `.header-inner`'s own `column-gap` mistaken for slack; the real figure at 1024 px with seven items was **21.6 px**, and nothing overflowed only because flexbox crushed the logo inside the brand block. **Measure slack, not gaps.**)* |
| `SiteFooter` | Three-column sitemap, contact block, designations, entity line |
| `Eyebrow` | Mono label with optional maroon dot |
| `SectionHeading` | Eyebrow + display heading + optional lede, one measure |
| `Button` | Variants `primary` (maroon) · `ghost` (outlined) · `gold` (ink bg, gold-l text). Renders `<a>` or `<button>` correctly |
| `Pill` | Small bordered label for designations and sector chips |
| `CredentialRow` | Three or four credential slots. **Never matter counts**`AGENTS.md` §4 |
| `PracticeCard` | Sector chip, heading, one paragraph, arrow link |
| `ProcessStep` | Numbered step, timing, body |
| `ArticleCard` | Title, description, date, topic pills, reading time |
| `Prose` | Long-form wrapper. Owns all typographic defaults for MDX |
| `SEO` | Title, description, canonical, OG, Twitter, JSON-LD — see `04-seo-spec.md` |
**Focus states.** Every interactive element gets a visible focus ring:
`outline: 2px solid var(--maroon); outline-offset: 3px`. Use `:focus-visible`.
Never `outline: none` without a replacement — the old build removed it globally.
---
## Accessibility floor
Not a polish pass. A build requirement.
- One `<h1>` per page; heading levels never skipped.
- Landmarks: `<header>`, `<nav>`, `<main>`, `<footer>`. Skip-to-content link first
in tab order.
- Every image has `alt`. Decorative images get `alt=""`.
- Colour never carries meaning alone.
- All functionality reachable by keyboard; focus order matches visual order.
- Forms: real `<label>` elements, `aria-describedby` for hints, errors announced
with `role="alert"` and tied to their field.
- Touch targets ≥ 44 × 44 px.
- Test at 200% zoom and at 320 px width. **Both measured 2026-08-27 on `/`:
document overflow 0 at 320, 360, 390, 414, 640, 768, 900, 1024, 1056, 1200,
1216, 1280, 1440 and 1920 CSS px, with zero elements extending past the
viewport.** Page zoom at 200% of 1280 is the 640 column and at 400% is the
320 column, so WCAG 1.4.4 and 1.4.10 are both covered by that sweep.
**A stricter case is not fully clean, and it is recorded rather than left to
be discovered.** With the reader's *default font size* at 200% — root at
32 px, a real accessibility setting and not page zoom — `/` measured **234 px**
of overflow at 390. Brought down in three measured steps:
⚠️ **THE THREE TABLES BELOW ARE A HISTORY OF HOW THE NUMBER CAME DOWN, NOT THE
CURRENT STATE.** Every residual in their final rows — the 3 px, the 23 px and the
63 px — went to **0 on 2026-08-31**, on all 22 pages at 320 / 360 / 390 / 414 /
640 / 1024 px. The current state is §Reflow below, which also carries what is
still NOT zero at 1280 px and 1920 px. Read the rows as dated steps.
| Fix | 390 px | 320 px |
|---|---|---|
| as first built | 234 px | 304 px |
| `minmax(min(Nrem, 100%), 1fr)` on three grids | 83 px | 153 px |
| `.credentials` made explicit `repeat(2, minmax(0, 1fr))`; `.feature` padding clamped and `overflow-wrap: anywhere` on its title; `.contact-action` `flex: 0 1 auto` + `min-inline-size: 0` | **3 px** | **63 px** |
**`65 px` corrected to `63 px` on 2026-08-28**, re-measured independently on
the same page and setting. Two pixels, and it is recorded because a table that
reads as the site-wide record has to be re-measurable rather than remembered.
**`/about/` added 2026-08-28** — step 3, and the first page to be measured
against this table rather than establishing it:
| Page and fix | 390 px | 360 px | 320 px |
|---|---|---|---|
| `/about/` as first built | 38 px | 68 px | 108 px |
| `.designation-part` `white-space: nowrap` removed (the separator is held by an NBSP instead) and `overflow-wrap: anywhere` on `.hero-h` | **0 px** | **23 px** | **63 px** |
**Step 4's three pages added 2026-08-28**, and they needed no fix of their own
— the guards this table produced were applied while writing them rather than
after measuring:
| Page | 390 px | 360 px | 320 px |
|---|---|---|---|
| `/mediation/` | 3 px | 23 px | 63 px |
| `/arbitration/` | 3 px | 23 px | 63 px |
| `/med-arb/` | **0 px** | 23 px | 63 px |
At the **default** root size all three are **0 px at every width measured**.
⚠️ **THE ELEMENT-LEVEL SWEEP BELOW IS ALSO HISTORICAL.** It read: *"the
element-level sweep at 320/root-32, predicate `right > clientWidth`, names three
`PracticeCard` chips on `/mediation/` — Construction, Technology, Shareholder —
plus the header and footer brand elements, which appear on all five pages."*
Re-run with that exact predicate on 2026-08-31: **0 elements**, on all five pages
at 320 / 360 / 390 with root 32. The chips were fixed at their cause
(`PracticeCard`'s padding) and the brand elements at theirs; see §Reflow. *(This read "six" for one pass. Six is the count of
`article.card` with `scrollWidth > clientWidth`, a different predicate on a
different element; `adversarial-reviewer` re-ran it and no predicate yields
six chips. A number in this table has to be re-runnable, which is the whole
reason the table exists.)* All three are byte-identical to `/`'s — same
widths, same offsets, same text — so step 4 introduced no new element-level
overflow.
Two findings from that page specifically. The `nowrap` was **introduced as a
fix** for an orphaned `·` at the end of a wrapped line, and it made the whole
designation item unbreakable — a fix that created a reflow regression, caught
only because this table existed to regress against. And **the type scale is
rem-based**, so an `<h1>` at `--text-5xl` computes to 88 px at root 32 and a
single unbreakable 9-character name ("Lajevardi") exceeds the 224 px content
box at 320 px; `overflow-wrap: anywhere` is the only remedy that reduces
min-content size. `/about/` now measures equal to or better than `/` at all
three widths. *(Its 320/360 residual was recorded here as "the same header
decision" until 2026-08-31, when that attribution turned out to be wrong and the
residual went to 0 — see below.)*
Command, so the numbers are re-runnable rather than quoted: headless Chrome
over the built `dist`, `document.documentElement.style.fontSize = '32px'`, then
`documentElement.scrollWidth - documentElement.clientWidth`, plus an
enumeration of every element that either **is wider than `clientWidth`** or
**whose own content overflows its own box** (`scrollWidth - clientWidth`), to
name the offender. ⚠️ **Both halves of that predicate are load-bearing and the
second was added on 2026-09-01, after the first half alone reported "no offender"
at three of the four widths that were failing** — the footer email spilled text
out of a box that was itself inside the viewport. A sweep that enumerates only
over-wide *elements* will name no cause for a whole class of real overflow.
⚠️ **AND THE RECIPE ABOVE HAS A UNIFORM-PASS TRAP THAT IS NOT VISIBLE IN IT.**
Run `document.documentElement.style.fontSize = '32px'` with
`prefers-reduced-motion: reduce` in force — launch flag *or* CDP emulation — and
it reports **0 of 352 with the enlargement never applied**: brand width, header
height and document height all byte-identical to root 16. The reduced-motion
reset emits `*,::before,::after{transition-duration:.01ms!important}` over the
initial `transition-property: all`, so the root font-size change becomes a
`CSSTransition` and a synchronous read returns its START value.
`document.getAnimations()` shows `CSSTransition: font-size, running, t=0`; after
one `requestAnimationFrame` it is 32 px. **Kill motion with an injected
`transition:none;animation:none` sheet rather than by emulating reduced motion,
and assert the applied root size per cell** — that assertion is the only thing
that distinguishes this from a clean sweep. Found by `adversarial-reviewer`,
round 2, running this section's own published recipe.
**THE LARGE-TEXT NAV OVERFLOW IS FIXED, 2026-09-01.** Pouya reopened the
step-1 header decision for the purpose — *"its record attributed the residual to
the wrong cause and characterized a measured 944 px functional failure as a
robustness margin"* — and ruled *fix, do not accept*. What follows is the
current state. The narrow case was ruled a defect on 2026-08-31 (*"the
152/112/82 px overflows at 320/360/390 with 32 px root text violate WCAG 1.4.10
and are not accepted"*) and fixed then.
**THREE MECHANISMS ENLARGE TEXT, AND THEY ARE NOT EQUIVALENT — this is the
measurement the whole record turned on.** Every figure in this section before
2026-09-01 came from `documentElement.style.fontSize = '32px'`. A later sweep
used Chrome's `Page.setFontSizes({standard: 32})`, reported 0 everywhere, and
the prose generalised that zero. All three are re-measured below.
| mechanism | `rem` in a media query | `rem`/`em` in a PROPERTY | `getComputedStyle` root | sees the enlargement? |
|---|---|---|---|---|
| `documentElement.style.fontSize='32px'` (root style) | **16 px → `66rem` = 1056 px** | 32 px | 32 px | media query **no**, property **yes** |
| `Page.setFontSizes({standard:32})` (the reader's *default font size*) | 32 px → `66rem` = **2112 px** | 32 px | 32 px | **yes, both** — the breakpoint moves with the type |
| `--blink-settings=minimumFontSize=32` (the reader's *minimum font size*) | 16 px → `66rem` = **1056 px** | **16 px** | **32 px** | **NO `rem`/`em` CONSTRUCT SEES IT** — the columns of this table are all `rem`/`em`, and that is the limit of what the row measures. ⚠️ **The FONT-METRIC units DO see it** (`ch`, `ex`, `cap`, `lh`, `rlh`); see the table below |
*(Row 1's media-query cell read "32 px → 2112 px" for one revision, which
contradicted its own verdict column, the paragraph below it, and measurement —
and it deleted the one number that explains why the failure is reachable at all.
If `66rem` really resolved to 2112 px under root scaling, the 66 rem desktop
masthead would never be reached at 1280 px and the 944 px overflow could not
happen. Found by `adversarial-reviewer`.)*
Two consequences, both measured 2026-09-01 and both load-bearing:
1. **In a media query `rem` resolves against the browser's DEFAULT font size,
not the root element's.** So raising the *default* moves the breakpoints
along with the type and the desktop masthead is never reached; setting the
*root* doubles the type and leaves the breakpoints where they were, which is
the layout the desktop nav was measured in. **A container query is the one
construct that differs** — `@container` `rem`/`em` DO resolve against the
root element, measured — which is why the sticky gate below is a property and
not a query.
2. ⚠️ **UNDER A MINIMUM-FONT-SIZE SETTING, `getComputedStyle(el).fontSize` AND
THE `em` UNIT DISAGREE.** Blink floors the *reported computed* font size to
32 px while `em` and `rem` keep resolving at 16 px — so `scroll-padding-top:
6.0625em` computes to **97 px, not 194 px**, a `66em`-wide box measures
**1056 px, not 2112 px**, and `min(0px, calc(100vw - 66em))` is **0px**.
⚠️ **AND THE SENTENCE THAT USED TO SIT HERE WAS FALSE AND LOAD-BEARING: it
said "media queries, container queries and length units are all blind to it"
and that "only used-value layout — wrapping — responds".** It is the
`rem`/`em` FAMILY that is blind, not CSS. The **font-metric** units read the
*used* font size and therefore double, and they do so in all three
constructs — measured 2026-09-01 on `/fees/` @1280, headless Chrome, one
probe element per cell:
| construct | `rem`, `em`, `ic`, `px` | `ch`, `ex`, `cap`, `lh`, `rlh` |
|---|---|---|
| property value | blind (16 px → 16 px) | **responds**`ch` 10.608 → 21.216 px, `ex` 8.48 → 16.96, `cap` 11.36 → 22.72, `lh` 25.59 → 51.19 |
| `@media (min-width: N<unit>)` | blind — threshold never flips | **responds**`100ch`, `110ex`, `80cap`, `45lh` all flip |
| `@container (min-width: N<unit>)` | blind | **responds**`45ex`, `32cap` flip |
So wrapping is still the right mechanism for *reflow* — it needs no
threshold and no fitted constant — but **"no CSS can detect this setting" is
not true, and the residual below is therefore not provably unfixable.**
Found by `adversarial-reviewer` and reproduced independently.
**Before and after, 22 pages × 16 widths (320 → 1920 px) = 352 page-widths per
setting, document overflow `documentElement.scrollWidth
documentElement.clientWidth`. Both columns measured on the SAME grid**, from a
build of the previous commit and a build of the working tree, by the same probe:
| text setting | before | after |
|---|---|---|
| default (root 16) | 0 of 352 | **0 of 352** |
| root style 32 px | **175 of 352** | **0 of 352** |
| default font size 32 | 0 of 352 | **0 of 352** |
| minimum font size 32 | **219 of 352** | **0 of 352** — the header fix left 88, all of them the footer; the footer fix below closed them, 2026-09-01 |
✅ **SO: ZERO DOCUMENT OVERFLOW UNDER ALL FOUR METHODS AT EVERY WIDTH
MEASURED — AND THE SCOPE OF THAT SENTENCE IS EXACTLY THE GRID ABOVE.** 22 built
pages × those 16 widths × four enlargement methods = **1,408 page-widths**,
document overflow by `documentElement.scrollWidth documentElement.clientWidth`,
webfonts loaded, six nav items, `prefers-reduced-motion` neutralised by an
injected sheet rather than emulated. Plus **762** further points for the sticky
gate (roots 915, roots 1632, the band seam, both thresholds, all five Chrome
presets), 0 failing.
⚠️ **It is not a claim about anything outside that grid, and "no document
overflow" is not "no accessibility defect".** Three known cases sit outside it
deliberately. The **fallback-metrics case** below needs the webfonts blocked and
a seventh nav item. The **focus-obscuring case** below was a **WCAG 2.2
SC 2.4.11 (AA) failure**, now **fixed and closed**`AGENTS.md` Q61 — and it is
the sharpest illustration of the warning above: **document overflow could not see
it at all**, because nothing overflowed. The header simply covered what had
focus, on 290 of 1,455 stops, while every zero in the table above stayed a zero.
A residual at `minimumFontSize=16` and `=20` remains, is pre-existing, and is
likewise invisible to this grid. And a
**footer nav label** still overruns its own column by 24 px at 640 px under
minimum font size with 7.7 px of clearance, again with zero document overflow.
None of the three is contradicted by the zeros above, and none of them is
measured by them — which is the reason each is recorded in its own right rather
than summarised into the table. Widths
between the sampled ones are not measured either — the grid is 16 columns, not a
continuum, and the two thresholds and the band seam were swept precisely because
a 16-column grid can step over a 36 px band.
**The wide-width figures that were the defect, root style 32 px, and what they
are now.** Every one is 0 of 22 pages after the fix, and the nav items and the
CTA are on-screen at every width:
| width | before | after |
|---|---|---|
| 1056 | 649 px, 22 of 22 | **0** |
| 1100 | 606 px, 22 of 22 | **0** |
| 1216 | 928 px / 491 px on `/`, 22 of 22 | **0** |
| 1240 | 904 px / 468 px on `/` | **0** |
| 1280 | **944 px** / 508 px on `/` | **0** |
| 1440 | 784 px / 351 px on `/` | **0** |
| 1600 | 624 px / 193 px on `/` | **0** |
| 1920 | 304 px, 21 of 22 | **0** |
*(Only 1280 and 1920 were in the earlier record, so it never showed that the
failure ran the whole width of the sticky range. The same run under minimum font
size was 414 / 370 / 854 / 830 / 830 / 750 / 670 / 510 px, of which the record
carried only the 830.)*
⚠️ **AND IT WAS NOT A MARGIN — IT WAS UNREACHABLE NAVIGATION.** The furthest
element was `div.header-cta` at **2224 px** against a 1280 px viewport; the nav
clipped mid-word after "Arbitration", and **Practice, Fees, Contact and the
header CTA sat entirely off-screen** behind a horizontal scrollbar — the route
to the inquiry form among them. WCAG 1.4.4 with loss of functionality. The
paragraph struck here called it *"a robustness margin rather than a failure.
Revisit if a real reader hits it."*
**THE FIX IS TWO DECLARATIONS, AND THE SECOND ONE IS THERE BECAUSE THE FIRST
ONE HAS A CONSEQUENCE.**
**(1) The masthead may wrap.** `flex-wrap: nowrap` is gone from `.header-inner`
and from `.nav-list` above 66 rem, along with the dead `flex: none` beside it.
⚠️ **WHICH OF THE TWO WAS THE CAUSE IS NOW MEASURED, AND THIS PARAGRAPH FIRST
GOT IT WRONG.** It said *"that pair, not any one declaration, was the cause"*.
Restoring each declaration alone on the fixed build, root 32, `/about/`
document overflow / number of nav links and CTA off-screen:
| restored above 66 rem | 1056 | 1216 | 1280 | 1440 | 1920 |
|---|---|---|---|---|---|
| neither (shipped) | 0/0 | 0/0 | 0/0 | 0/0 | 0/0 |
| `.nav-list { flex-wrap: nowrap }` only | 0/0 | 0/0 | 0/0 | 0/0 | 0/0 |
| `.header-inner { flex-wrap: nowrap }` only | 0/0 | 273/1 | 209/1 | 49/1 | 0/0 |
| both (= the state before) | 649/3 | 928/5 | 944/5 | 784/4 | 304/1 |
**`.header-inner`'s `nowrap` was necessary and sufficient.** And the second row
is the one that matters for honesty: **`.nav-list`'s removal is inert** — byte-
identical to shipped at every width, under a 200 % root size *and* under minimum
font size, **with six nav items and with seven**. It is kept removed for two
reasons that are not "it fixed the overflow": the `nowrap` prohibition in
`SiteHeader.astro` would otherwise contradict a `nowrap` still sitting in the
file, and the override only ever re-stated `nowrap` over a base rule that already
wraps. Found by `adversarial-reviewer`; the mechanism sentence about min-content
was true and was not the binding constraint. *(`flex` is a flex-ITEM property and `.nav`
computes `display: block`, measured at 1056 / 1280 / 1920 px — so `flex: none`
set `flex-shrink: 0` on something that is not a flex item and had **no effect**.
Removing it leaves `.nav`'s and `.nav-list`'s geometry byte-identical at all
three widths; the only trace was the computed `flex-shrink`, 0 → 1. Its comment
claimed it was what stopped the nav being squeezed, which it never was.)*
**It cost nothing at any normal size: 0 geometry
differences across 22 pages × 16 widths (352 page-widths)**, header exactly **81.00 px** at every
width from 1056 px up, brand on one line, nav on one line, CTA exactly on
`.header-inner`'s content edge (gap 0.00 px) — **and the same with a seventh nav
item injected**, at 1024 / 1047 / 1056 / 1071 / 1100 / 1150 / 1200 / 1216 / 1240
/ 1280 / 1440 / 1920 px, where all seven share one line from 1056 px up. So the
`nowrap` was not load-bearing at any width above the breakpoint; the 66 rem
breakpoint is what keeps the row intact, and it already accounts for seven items.
**(2) The masthead is sticky only while it is one row**, and this half took two
attempts — the first one shipped a regression, which is recorded because the
reason it failed is the useful part.
Wrapping makes the header **taller** at enlarged text: **244.59350.86 px** at a
200 % root size against 81 px normally. A sticky box that size covers `#main`
after "Skip to content", so stickiness has to go wherever the header can exceed
`scroll-padding-top`. The gate is a **property**, not a query, because in a
property `rem` tracks the root element.
⚠️ **ROUND 1 WROTE `min(0px, calc(100vw - 66rem))` AND IT WAS WRONG IN TWO WAYS
THAT ONLY AN INTERMEDIATE ROOT SIZE EXPOSES.** Both were found by
`adversarial-reviewer` and independently reproduced: **20 of 300**
(page × viewport × root) points left `#main` behind the header, 1069 px, across
roots 1830 — while root 16 and root 32, the only two sizes the first sweep
measured, were both clean. **WCAG 1.4.4 is a requirement up TO 200 %, not AT
200 %**, and a two-point sweep cannot see a diagonal band.
1. **It RAMPED where it needed to STEP.** One pixel inside the threshold the term
lifts the header by one pixel — but the header has already gained a whole row,
so it still covered `#main`. Wrapping is a step function; the response has to
be one too. Hence the `* 100000` saturation, bounded by `-100vh`.
2. **It used the wrong threshold in the wider band.** The tagline appears at
76 rem, and with it the one-row masthead fits only from **1207 px = 75.4 rem**
— the step-1 binary search already recorded that number. Gating the wide band
on 66 rem therefore fired far too late. **`/` measured clean throughout, and
that is what identified the driver:** `/` is the one page that suppresses the
masthead tagline.
⚠️ **AND ROUND 2 FOUND THAT THE GATE WAS ONE-SIDED — it could only fire when the
root was LARGER than the default.** Chrome's "Font size" control has **five**
presets (Very small 9 px, Small 12, Medium 16, Large 20, Very large 24) and the
sweeps had used 16 and 32 — 32 is not one of them. At **9 px the masthead wraps
for the opposite reason**: `--width-content` is `80rem`, so the content column
shrinks to 720 px while the header's `min-inline-size: 44px` / `min-block-size:
44px` floors do not, and the row wraps at **every** viewport width. The header
stood at **120 px against a 54.56 px `scroll-padding-top`** — **65 px of `#main`
behind it on the 21 pages that render the tagline**, at 1056 through 1920 px.
`/` measured 12 px, and that 12 px is **pre-existing**: the previous build is 67 px
against the same 54.56 px offset on all 22 pages. So the wrap fix widened a
pre-existing 12 px defect to 65 px, and the second term closes both.
**The shipped form is two bands, each gating on the width ITS layout requires,
and two terms, each catching one direction:**
```css
@media (min-width: 66rem) { .site-header {
position: sticky;
inset-block-start: clamp(-100vh,
min(calc((100vw - 66rem) * 100000), calc((1rem - 16px) * 100000)), 0px); } }
@media (min-width: 76rem) { .site-header {
inset-block-start: clamp(-100vh,
min(calc((100vw - 76rem) * 100000), calc((1rem - 16px) * 100000)), 0px); } }
```
`1rem - 16px` is negative exactly when the root is below the CSS initial font
size — the 16 px the whole rem scale is built on — and it also catches the
root-style equivalent, where the previous form left the header sticky at 69 px
against a 60.63 px offset at root 10. **The cost, measured and accepted:** a
reader whose default text size is "Small" (12 px) loses the sticky header, where
it currently works — but only just: the header is 73 px against a 72.75 px offset,
a **0.25 px** margin. Trading a 0.25 px margin for a provable gate is the right
way round.
Both thresholds are the existing layout breakpoints, and both sit just above the
measured one-row fit width (1047 px ≈ 65.4 rem without the tagline, 1207 px ≈
75.4 rem with it), so the gate fires **at or before** the wrap rather than after
it. **At every normal size both evaluate to exactly `0px`** — a matched
`min-width: 66rem` guarantees `100vw ≥ 1056 px` and a matched `min-width: 76rem`
guarantees `100vw ≥ 1216 px`, because `100vw` counts a classic scrollbar and the
query width does not, so the term can only be more positive.
**Measured after: 0.00 px of `#main` covered — 0 of 300 grid points across roots
1632 × viewports 10561920 on three pages, 0 of 176 rows at the default size and
at a 200 % root size over all 22 pages, 0 of 144 cases sitting directly on the
two thresholds, and 0 of 198 at the SEAM where the two bands meet** (viewports
11801260 × roots 1632). ⚠️ **`covered: 0` is also what a non-sticky header
gives, so the seam was checked the other way round as well:** at the default size
across 1180 / 1200 / 1210 / 1215 / 1216 / 1217 / 1220 / 1240 / 1260 / 1440 /
1920 px the computed `inset-block-start` is `0px` and the header is still pinned
at top 0.00 when scrolled — stickiness is preserved, not quietly lost. ⚠️ **Saturation leaves a transition band, and it is
arithmetic rather than measured:** a finite factor means the response is only
a near-step, so the band is about (header `scroll-padding-top`) / factor ≈
**0.0007 px** of viewport width. CDP takes integer viewport widths, so that band
was not driven directly — it is bounded by the arithmetic, not by a probe.
*(Lightning CSS folds the factor into the units and emits
`clamp(-100vh,10000000vw - 6600000rem,0px)`. Verified equivalent by measurement,
not by reading: the computed `inset-block-start` is `0px` at every sticky width at
the default size, and the header pins at top 0.00.)*
⚠️ **"AN OFFSET TOO SHORT CANNOT ARISE" WAS WRITTEN HERE AND IN TWO SHIPPED
COMMENTS, AND IT IS FALSE. THE EXCEPTIONS ARE ENUMERATED RATHER THAN WAVED AT**,
because a comment that says "cannot" is the kind that stops the next reader
looking. The gate handles **root-relative** enlargement in both directions.
What it cannot see, measured, header height against computed
`scroll-padding-top`, header `top` = `0px` in every row:
| condition | header | offset | short by | status |
|---|---|---|---|---|
| `minimumFontSize=32` | 164.58270.56 px | 97 px | **68174 px** | 🛑 **OPEN — needs a fresh ruling.** The 2026-09-01 acceptance rested on two facts that are both false; and the real cost is **290 entirely-obscured focus stops, SC 2.4.11 AA**, not a short skip-link landing. **A pure-CSS detector DOES exist.** See below |
| fallback font metrics + 7th nav item, **default** text size, 10561091 px | 141 px | 97 px | **44 px** | latent on `showInsights`. **HARD GATE: `AGENTS.md` R20 — the seventh item does not ship until this is fixed** |
| `defaultFontSize=9` | 120 px | 54.56 px | ~~65 px~~ | **fixed** by the `1rem - 16px` term |
| root-style 10 px | 69 px | 60.63 px | ~~8 px~~ | **fixed** by the same term |
**`--header-h` IS A FLOOR, NOT A CONSTANT**, and that is the sentence that
had to change. It is the header's height at the **default** text size; above the
default the masthead is deliberately taller, and declaration (2) is what makes
that safe — where the gate can see the header exceed the token it is not sticky,
so a `scroll-padding-top` that is too *generous* is harmless. ⚠️ **It does NOT
make a short offset impossible: the two cases the gate cannot see are tabulated
above.** The token previously asserted "one constant 81 px across
every width where it is sticky", and that sentence is what made a 944 px
overflow read as settled.
**What the narrow-width fix DID establish, and it is worth keeping separate.**
The narrow residual had been charged to that same `flex-wrap: nowrap` — and for
the narrow case that attribution was **wrong**. The cause there was
`white-space: nowrap` on `.brand-name`, a different declaration in the same
component, and removing it **changes nothing at any real size**: the brand name
is one line at all 16 widths at root 16, the sticky header still measures exactly
81 px at every width from 66 rem up, and the CTA still lands exactly on
`.header-inner`'s content edge. **A residual defended by the wrong cause is
defended forever**, because the reason given is one nobody wants to re-open —
which is exactly what happened for four days.
**ELEVEN FIXES ACROSS THREE PASSES, IN TWELVE ROWS — no `overflow-x` was added
anywhere.** Eight are narrow-width (2026-08-31), two are the large-text nav
(2026-09-01), and the eleventh is the footer email (2026-09-01, the pass that
closed the last recorded overflow). ⚠️ **The twelfth row is `.nav-list`, and it
is a row without being a fix:** it is here because its removal was *measured*,
and it is not counted because the measurement was that it changes nothing. Say
so rather than leaving the arithmetic to a reader who counts rows — the heading
read "TEN FIXES" over eleven rows until 2026-09-01 for exactly this reason. All
but one of the eleven are cause-specific; the exception is marked as the
backstop it is:
| Element | Was | 320 px | Cause |
|---|---|---|---|
| `SiteHeader .header-inner` | `flex-wrap: nowrap` removed above 66 rem | **944 px** at 1280 px, root 32 | **Necessary and sufficient, measured.** A `nowrap` flex line cannot break, so the brand / nav / CTA row could not reflow at all |
| `SiteHeader .nav-list` | `flex-wrap: nowrap` and a dead `flex: none` removed above 66 rem | **0 px — inert** | Kept removed so the `nowrap` prohibition is not contradicted by a `nowrap` in the same file. Byte-identical to shipped at every width and every text setting, six items and seven |
| `SiteHeader .site-header` | `inset-block-start: 0` → a saturating `clamp()` gate, in **two** bands (66 rem, and 76 rem where the tagline shows) | *(consequence of the row above)* | Wrapping makes the header 244.59350.86 px at root 32; sticky at that height covered `#main` by up to 157 px after the skip link. **Round 1's single-band `min()` ramped instead of stepping and left 20 of 300 grid points covered by 1069 px** |
| `SiteHeader .brand-name` | `white-space: nowrap` removed | **63 px, all 22 pages** | Two words glued into one unbreakable box |
| `/bio/ .rates-list` | `overflow-wrap: anywhere` | **152 px** | `https://adr.smlcompany.ca/fees/` has no break opportunity, and the single grid track it sized stretched all five rows to 424 px |
| `/ .hero-h` | `overflow-wrap: anywhere` | 36 px | "contract," at 104 px held `.hero-copy` open |
| `/ .feature-body` | `overflow-wrap: anywhere` | 26 px | "party-appointed" at 202 px |
| `CredentialRow .credential-label` | `overflow-wrap: anywhere` | 38 px | "designation" needs 143 px in an 88 px track |
| `SiteFooter .footer-contact a[href^='mailto:']` | `overflow-wrap: anywhere` | **14 px** | `info@smlcompany.ca` has no break opportunity and demands 310 px. Minimum-font-size only; also 38 / 30 / 19 px at 1024 / 1056 / 1100 px, by a different mechanism — see the footer block above |
| `SiteFooter .footer-brand` | `flex-wrap: wrap` | 12 px | Flex item at `min-width: auto` cannot shrink below "Lajevardi" |
| `PracticeCard .card` | `padding` clamped | *(the cause under `Pill`)* | The space scale is rem-based, so `--space-6` is 64 px a side at root 32 — 128 px of padding in a ~224 px box |
| `Pill` | `overflow-wrap: anywhere` | 33 px → **1 px** | ⚠️ **SYMPTOM, NOT CAUSE.** A one-word pill cannot wrap at a space that is not there |
`flex-wrap: wrap` on the footer brand rather than `anywhere`, because it reflows
without hyphenating a person's name.
⚠️ **`Pill`'s `anywhere` IS A BACKSTOP AND THE TRADE IS RECORDED RATHER THAN
HIDDEN.** It removes the min-content floor of a shrink-to-fit `inline-flex` box,
so the pill collapses to whatever its parent gives it. The real cause was
`PracticeCard`'s padding: with it clamped, "Construction" at root 32 / 320 px
went from **94 × 220 px broken into six two-character lines** to **158 × 85 px on
two lines**, and root 16 is unchanged at 121 × 27 px on one line. **`anywhere` is
still load-bearing** — removing it leaves 1 px of document overflow on `/`,
`/mediation/` and `/practice/`, and renders the pill 240 px wide inside a 224 px
card, escaping its own rounded border. Two lines is the better of those. The
clamp does not change any normal size: 10vw holds 32 px from 320 px up.
**TEN instrument findings, and each one hid a real defect. Items 46 are from the
2026-09-01 header pass and all three produced a CLEAN-LOOKING result; 710 are
from the footer pass the same day. Each is recorded once — the footer block above
points here rather than restating them.** ⚠️ **Finding 2 RECURRED in that footer
pass**, in a probe written by the person who had written finding 2 down: it is
described where it did its damage, beside the footer fix, and not duplicated as
an eleventh item.
4. ⚠️ **A UNIFORM PASS FROM A TAUTOLOGY.** The skip-link probe reported
`0.0 px of #main covered` on **946 of 946** page-widths, before and after,
under every mechanism. `global.css` sets `scroll-behavior: smooth`, which
makes `scrollIntoView()` **asynchronous**, so a synchronous read afterwards
measures `scrollY = 0`; and at `scrollY = 0` the target sits exactly at the
header's bottom edge, so `covered` is 0 **by construction**. Pass
`behavior: 'instant'` and assert that `scrollY` equals its predicted value —
once fixed the same probe reported 1026 px covered on 22 of 22 pages, a
pre-existing defect the tautology had hidden.
5. ⚠️ **A RUNNING TRANSITION MAKES A PROPERTY READ RETURN THE OLD VALUE.**
Forcing reduced motion — by CDP emulation *or* by
`--force-prefers-reduced-motion` — made `documentElement.style.fontSize =
'32px'` read back as **16 px**, while the inline `style` attribute plainly
said `font-size: 32px`. This codebase's reduced-motion block sets
`transition-duration: 0.01ms !important` on `*`, which starts a transition on
**every** property change, and a synchronous `getComputedStyle` then returns
the transition's *start* value. The instrument now injects
`transition:none;animation:none` instead. Same family as *a running
transition outranks an `!important` author declaration*.
6. **A DOM-INJECTED ELEMENT RECEIVES NO SCOPED STYLES.** Astro scopes component
CSS with a `data-astro-cid-*` attribute, so the hand-built
`<a class="nav-link">` used to test a seventh nav item got **none** of
`.nav-link`'s rules — no `min-inline-size`, no `min-block-size`, no
`font-size` — and measured narrower and shorter than a real item, which is
the direction that makes a width test pass. **Clone a real node** and assert
its computed styles match a sibling's.
7. ⚠️ **A CAUSE READ OFF A TRUNCATED LIST.** The header harness capped its
offender array at `slice(0, 20)` while reporting `offenderCount` beside it,
and **44 of the 88 failing rows exceeded the cap** — so the enumeration that
was supposed to name the footer's cause was silently missing 34 of the 54
boxes at 320 px. The truncation was visible only to a reader who compared the
two numbers. Same family as the `tail -3` rule in `CLAUDE.md`, through a
different pipe.
8. ⚠️ **`grep -c` COUNTS LINES, NOT OCCURRENCES, AND MINIFIED CSS IS ONE LINE.**
`grep -c 'overflow-wrap:anywhere' dist/_astro/*.css` returned `1` for a file
that holds **three** such declarations. It was being used to confirm the new
rule had reached `dist/`, which it could not do. Extract and print the bytes
instead — the rule that actually settled it was reading the emitted selector,
`.footer-contact[data-astro-cid-nns7i3if] a[data-astro-cid-nns7i3if][href^=mailto\:]`.
9. ⚠️ **`getClientRects().length` IS 1 FOR A `display: flex` ELEMENT HOWEVER MANY
LINES OF TEXT IT HOLDS** — it is one block box, not a fragmented inline. The
wrap probe therefore reported `lines=1` for the footer email while its own
`height` said **102.38 px = 2 × 51.19**, i.e. two lines. Line counts come from
`Range` rects over the text, which do fragment per line. **Two metrics of the
same quantity disagreeing is the cheapest defect detector in this section** —
it is what caught this one.
10. ⚠️ **`Page.captureScreenshot`'s `clip` IS IN PAGE COORDINATES, NOT VIEWPORT
COORDINATES.** Given a `getBoundingClientRect()` taken after a scroll, it
captured a **blank cream plate** — of a footer that is ink. The screenshot
existed to be the independent second method for a break the numbers could not
judge, so a silently wrong one is worse than none. Add `window.scrollX/Y`, or
pass `captureBeyondViewport: true` and page coordinates.
The original three:
1. **`overflow-wrap: break-word` permits a break at layout time but does not
reduce min-content size** — `anywhere` does. Five of the fixes are this
distinction. `global.css:166` sets `break-word` on **`p` AND `h1``h6`** —
*(this line said "`h1``h6` and nothing else, so body copy inherits no
wrapping at all", which was wrong in a way that mattered: `.feature-body`
**is** a `<p>` and already had `break-word`, so the premise given for its fix
was false while the fix itself was right)*. Nothing else gets one, so a
`<span>`, `<li>`, `<dd>` or `<a>` inherits no wrapping.
2. **`getBoundingClientRect()` reports BORDER BOXES, so an element sweep cannot
see text spilling outside its own box.** `.credential-label`'s words ran 86 px
past an 88 px cell while every element's rect stayed inside the viewport —
the document was 38 px over and the offender was invisible to the predicate
this table's own command uses. Read `scrollWidth - clientWidth` per element as
well as per document.
3. **Under `Emulation.setDeviceMetricsOverride({mobile: true})` the LAYOUT
viewport expands to fit the content**, so `innerWidth` becomes 472 at a
requested 320 and `scrollWidth - innerWidth` reads **0** while the reader is
still scrolling sideways. The honest formula is
`documentElement.scrollWidth - documentElement.clientWidth`, which is correct
in both modes.
✅ **THE FOOTER RESIDUAL IS FIXED, 2026-09-01 — ONE DECLARATION, AND THE
EARLIER RECORD MISDIAGNOSED HALF OF IT.** Under Chrome's minimum font size the
document overflowed at four widths on all 22 pages — **14 px at 320, 38 px at
1024, 30 px at 1056, 19 px at 1100** — and 0 from 1200 px up. One cause at all
four: `info@smlcompany.ca` has no break opportunity, so its **min-content width
is 310 px**.
| Element | Was | Fix | Effect |
|---|---|---|---|
| `SiteFooter .footer-contact a[href^='mailto:']` | `overflow-wrap: normal` | `overflow-wrap: anywhere` | **88 of 352 → 0 of 352** |
⚠️ **TWO MECHANISMS, ONE CAUSE — and this is the part the earlier record got
wrong.** It said the 10241100 px half "looks like one `overflow-wrap: anywhere`
on the footer contact links" and that "the 320 px half is a column-sizing
question and is not the same fix". **It is the same fix.** The column sizing at
320 px is *driven by* that same 310 px:
- **At 320 px** `.footer-grid`'s implicit track is `auto`, whose minimum is
min-content, so the track — and every `.footer-col` in it — measures **310 px
inside a 272 px content area**. The boxes themselves cross the viewport edge:
**54 elements** did, right edge 334 px against a 320 px client width.
- **At 1024 / 1056 / 1100 px** the contact column is `minmax(0, 1fr)` — a **0**
minimum, so the track does not grow — and the box stays **224 / 232 / 243 px**
while the text spills **86 / 78 / 67 px outside it**. **No element's border box
crosses the viewport edge at all** at those widths.
Reducing one min-content width closes both halves.
⚠️ **AND IT CLOSED A THIRD CASE THAT THE FOUR-METHOD TABLE REPORTED AS CLEAN —
"MEASURE THE ELEMENTS, NOT ONLY THE PAGE", WHICH IS ALREADY A RULE IN THIS
SECTION.** With the fix disabled as a control, over 420 points (3 pages × 10
widths × 6 minimum-font-size presets and 8 root sizes), **69 failed: 12 by
document overflow and 57 by the link overrunning its OWN box while the document
measured 0.** Those 57 sit at **root style 26 / 28 / 30 / 32 px** and at
**`minimumFontSize` 24 and 32** — and root style 32 px is a row the table above
reports as **0 of 352**, truthfully, because it is a document measurement.
**Why it never reached the viewport edge, measured rather than surmised:**
`.wrap` carries a **96 px** right gutter at that size, and the spill is smaller
than the gutter. At root 32 on `/about/` the link's box ends at 928 px and its
text at **1007.3 px** against a **1024 px** client width — 79 px of spill sitting
inside 96 px of padding. The spill shrinks as the viewport widens (79 / 71 / 60 /
35 / 15 / 0 px at 1024 / 1056 / 1100 / 1200 / 1280 / 1440), which is why the case
disappears at 1440 px rather than at a breakpoint.
**After the fix: 0 of 420, and 0 clipping at every preset and every root size.**
The control is what makes that number mean anything — a sweep returning zero on
every cell is the shape this section warns about twice, so it was re-run with the
declaration forced back to `normal` and it failed 69.
⚠️ **AND THE ELEMENT SWEEP THAT LOOKED FOR THE CAUSE REPORTED "NONE" AT THREE
OF THE FOUR WIDTHS** — instrument finding 2 in this section, committed again by
the person who wrote it down. `getBoundingClientRect()` reports **border boxes**,
so a `right > clientWidth` predicate cannot see text spilling out of a box that
is itself inside the viewport, which is precisely the 10241100 px mechanism.
The document figure was right and the offender list was empty, and those two
facts together are the signature of this bug. **Read `scrollWidth clientWidth`
per element as well as per document.**
⚠️ **And the harness's offender list was truncated at 20 entries on 44 of the 88
failing rows**, so it could not have named the cause either — instrument finding
**7** below.
**What the fix costs, measured rather than asserted.** At the default text size,
**nothing**: the address is one line, **44.00 px** tall — exactly the touch-target
floor — at all 16 widths, and the normal-settings identity check is 0 differences
on 352 page-widths. Under minimum font size it wraps to two lines and the break
is mid-token — `info@smlcompany` / `.ca` at 320 px, `info@smlcomp` / `any.ca` at
1024 px, `info@smlcompa` / `ny.ca` at 1100 px — read out of the DOM one character
at a time and confirmed on a screenshot, not inferred from a width. The `href`
is untouched (`mailto:info@smlcompany.ca`), every painted glyph is inside the
viewport in **176 of 176** rows, and the link never clips its own box. A
mid-token break in an address is cosmetic; a document that scrolls sideways is a
WCAG 1.4.10 failure.
**`anywhere`, NOT `break-word` — and that was established with a NEGATIVE
CONTROL rather than from the rule.** `break-word` permits a break at layout time
without reducing min-content size, so it leaves the defect untouched: injected in
place of `anywhere` it failed **88 of 176** rows, the same rows as the unfixed
baseline. Both the baseline and the control had to fail for the trial to mean
anything — **a trial in which every candidate passes is a broken trial**, and
this section already carries five instrument findings of exactly that shape.
**On the link, not on the column, and not in the markup.** Three candidates all
reach 0 of 176: `anywhere` on the email link, on `.footer-contact`, and on
`.footer-col a`. The address is the only string in the footer that produces
**document** overflow, so the rule sits on the address and the two broader
selectors were declined as broader than the cause.
⚠️ **THE REASON FIRST GIVEN FOR THAT SCOPING WAS THE WRONG TEST, AND IT IS WORTH
MORE THAN THE SCOPING.** It said *"the address is the only string in the footer
with no break opportunity — the location line, the response sentence and the
sixteen nav labels all have spaces"*. **A space is not the test. Min-content is
set by the longest WORD**, so a label with spaces still overruns its track when
one of its words does not fit. `adversarial-reviewer` found one that does, and it
reproduced at a width the review had not sampled:
| width | element | own-box overrun | clearance to the next column | document |
|---|---|---|---|---|
| 640 px | `.footer-col a` "Construction & Infrastructure" | **24 px** out of a 176 px box | **7.7 px** | 0 |
| 640 px | `.footer-col a` "Shareholder & Family Business" | 2 px | 7.7 px | 0 |
| 700 px | `.footer-col a` "Construction & Infrastructure" | 4 px out of a 196 px box | 27.7 px | 0 |
`minimumFontSize=32`, all 22 pages, 12 widths from 320 to 1024 px = 264 rows;
clean at every other width sampled, including 768 and 834 px. **"Infrastructure"
is 14 characters and does not fit a 176 px track at 32 px** — the space in the
label is irrelevant to that.
⚠️ **AND THE 12-WIDTH GRID STEPPED OVER TWO MORE**: round 2 found the same label
at **17 px @660 and 11 px @680**, widths this grid does not sample. That is this
section's own caveat holding — *the grid is 16 columns, not a continuum* — and it
is the second time in one session that a between-columns width mattered.
**A FOURTH CASE OF THE SAME FAMILY, and it should be ruled on together with the
footer label rather than separately.** `minimumFontSize=32`,
`/practice/construction/` @320 px: `nav.crumbs > ol` overruns its own 272 px box
by **7 px**, furthest text edge 302.9 px against a 320 px client width,
`overflow-x: visible`, no clipping, **document overflow 0**. Clean at 360 / 390 /
414 px and on the other five practice pages. **No success criterion fails**, so
like the footer label it is recorded rather than fixed — but if
`.footer-col a { overflow-wrap: anywhere }` is ruled in, the same call covers
`.crumbs` and the ruling should be taken once for both. Raised by
`adversarial-reviewer`, round 2.
**Not fixed here, and the reason is a scope judgement rather than a measurement.**
It produces **no document overflow at any width**, so it fails no success
criterion; `.footer-col a { overflow-wrap: anywhere }` is measured to close it
and to be inert at normal settings. What it would change is how six practice-area
names break on a marketing surface under enlargement, which is a copy-adjacent
call. ⚠️ **The maintenance risk is the real finding: 7.7 px of clearance is one
label rename away from two columns colliding**, and nothing in the build measures
it. Pouya's to rule on; `docs/06` carries it. `word-break: break-all` also
reaches 0 and was declined for the same reason: it breaks where a normal
opportunity exists. **And NOT a `<wbr>` after the `@`**, which would give a
prettier break: it splits a §4-registered fact across an element boundary, so
`info@smlcompany.ca` would no longer be greppable in `dist/` — the surface
`npm run check:claims` reads — and its longest unbreakable run (`smlcompany.ca`,
~224 px) does not fit the 224 px column at 1024 px anyway, so it would need
`anywhere` as a backstop regardless.
**Not header-side, and that survives the fix:** no header element extends the
document at any width under any of the four mechanisms, and the nav items and CTA
are on-screen in **1408 of 1408** measurements. *(This said "946 of 946" for one
revision. 946 is a different sweep — the four runs made with the tautological
skip probe in instrument finding 4 — and reusing it for the final build was the
two-similar-totals mistake this section keeps warning about.)* *(The Practice
dropdown panel's contents overflow their own box by 84 px under minimum font
size and 40 px at the default, on all 22 pages — unchanged by this fix, and it
never extends the document because the panel sits inside a **closed**
`<details>`. Re-checked after the fix rather than carried forward.)*
🛑 **THE MINIMUM-FONT-SIZE STICKY RESIDUAL IS BACK OPEN, AND IT IS NOW A
CONFORMANCE FAILURE RATHER THAN A CONVENIENCE LOSS. IT NEEDS A FRESH RULING —
THE ONE OF 2026-09-01 WAS TAKEN ON TWO FACTS THAT ARE BOTH WRONG.** Pouya ruled
it accepted on the basis *"you proved no CSS mechanism can see
minimum-font-size; the only fix is JS and zero-JS is a founding decision."*
`adversarial-reviewer` attacked both halves of that and both attacks reproduced.
**(1) THE PREMISE IS FALSE.** The proof was of `rem`/`em` and of queries written
in them. The font-metric units `ch`, `ex`, `cap`, `lh` and `rlh` all read the
*used* font size and double under the setting — in property values, in `@media`
and in `@container`. The table at the top of this section carries the
measurement. **A pure-CSS detector for this mechanism exists**, so the residual
is not unfixable and "the only fix is JS" does not hold.
**(2) THE COST WAS UNDERSTATED, AND BY A CATEGORY RATHER THAN A NUMBER.** The
record described it as the skip link landing short — 68174 px of `#main` behind
the header — and argued *"one of the two is a WCAG 1.4.10 failure while the other
degrades the convenience of a skip link that still works."* What is actually
happening is that **ordinary keyboard focus lands entirely behind an opaque
sticky header**, which is **WCAG 2.2 SC 2.4.11 Focus Not Obscured (Minimum),
Level AA** — the same conformance level as the 1.4.10 failure it was traded
against. **So the asymmetry that was the whole argument does not exist: both
sides are AA failures.**
**Measured, and the instrument is checked in three ways.** Shift+Tab walk — the
ordinary way a keyboard user returns to a link they passed — 6 pages × 5 widths
≥ 1056 px, 70 steps per cell, elements inside the header excluded:
| build | mode | focus stops | entirely hidden | zones |
|---|---|---|---|---|
| working tree | default | 1,455 | **0** | — |
| working tree | `minimumFontSize=32` | 1,455 | **290** | 254 footer, **36 `#main`** |
| `fce89d4~1` (pre-header-fix) | `minimumFontSize=32` | 1,455 | **0** | — |
| `fce89d4~1` (pre-header-fix) | default | 1,455 | **0** | — |
⚠️ **THE THIRD ROW IS THE ONE THAT MATTERS: THE HEADER FIX CREATED THIS.** Before
it, `flex-wrap: nowrap` kept the masthead one row under this setting, so it was
short enough that focus landed clear; the overflow went sideways instead. After
it the masthead wraps to 164.58270.56 px and covers the 97 px landing. This is
a **regression introduced by the 2026-09-01 header fix**, not a condition it
inherited — and the ruling to accept it was taken without that comparison
existing.
**Instrument checks, because a focus-obscuring count is easy to fake in both
directions.** *(a)* The default-mode run returns **0**, so the predicate is not
tautological. *(b)* ⚠️ **Geometry alone was WRONG and said 43.** All 43 were the
**skip link**, which is stacked deliberately *above* the header and is not
covered by it at all — so `elementFromPoint` at the focused box's centre is
authoritative and geometry only nominates candidates. Under the setting there
are 333 geometric candidates and **290** survive the hit test. *(c)* Sampled
hits agree 6 of 6, e.g. `/` @1056: focused `a` "Technology, AI & Data" at
97 → 199.4 px inside a header at 0 → 228.6 px, `position: sticky`,
`inset-block-start: 0px`, background `rgb(250, 247, 242)` — opaque cream.
✅ **FIXED AND CLOSED — Q61 RULED *fix now*, 2026-09-01. Candidate B SHIPPED.**
Two candidates existed, and the second was better on the exact ground that had
deferred the first. The measurements below are the pre-fix state; the acceptance
sweep that closed it is at the end of this block.
*Candidate A, round 1:* a third gate term `calc((15px - 1ch) * 100000)`,
un-sticking the header under the setting while leaving `inset-block-start: 0px`
at the default. **Deferred:** 15 px is fitted to Geist's `ch`, so it needs
checking under fallback metrics and Chrome's other font presets, and the
reviewer's own injection of it was flaky.
*Candidate B, round 2 — recommended:* `scroll-padding-top`, the property that
already exists for this job, rather than un-sticking anything. Two declarations
in the existing `@media (min-width: 66rem)` block:
```css
html {
scroll-padding-top: calc(var(--header-h) + var(--space-4)); /* fallback: no `lh` */
scroll-padding-top: max(
calc(var(--header-h) + var(--space-4)),
calc(10lh - 83px)
);
}
```
**Verified independently — every value read from `getComputedStyle`, not
reasoned:**
| condition | `1lh` on `<html>` | computed offset | header | verdict |
|---|---|---|---|---|
| default, fonts loaded | 18 px | **97 px — byte-identical to shipped** | 81 px | clears |
| `minimumFontSize=32`, fonts loaded | 37 px | **287 px** | 270.56 px | clears |
| default, **every `.woff2` blocked** | **18 px** | **97 px** | 81 px | clears |
| `minimumFontSize=32`, **`.woff2` blocked** | **37 px** | **287 px** | 270.56 px | clears |
| `minimumFontSize=32`, family Georgia | 36 px | 277 px | 270.56 px | clears |
| `minimumFontSize=32`, family Verdana | 39 px | 307 px | 270.56 px | clears |
⚠️ **Rows 3 and 4 are why B beats A: `1lh` on `<html>` is immune to the webfont
fallback.** `<html>` keeps the UA default family — `--font-sans` is set on
`body`, and `<html>`'s computed `font-family` measures `Times` — so blocking every
font file changes nothing, and the fallback-metrics caveat that deferred
candidate A does not apply to B at all.
**What was said against B before it shipped, and what the sweep did with each.**
`10lh - 83px` is two fitted constants — the difference from A being that a
`max()` ramp degrades by pixels if they are off, where A's `* 100000` step flips
stickiness the wrong way. **The first declaration is load-bearing:** an engine
without `lh` support drops the whole `max()` as invalid and `scroll-padding-top`
falls back to `var(--space-4)` = 16 px, which is *worse* than no fix — so it is
written first, as a plain declaration, and must stay there. The focus probe is
**Chrome-only**, which is still true and is a limit on the evidence rather than
on the fix. The two remaining objections were discharged by measurement:
**THE ACCEPTANCE SWEEP — 777 cells over 37 settings, plus the focus walk.**
Per cell: navigate, kill transitions *before* any root-size change, set the
root, `location.hash = 'main'`, then read how much of `#main` sits behind the
header. The cell count is asserted, so a silently-truncated grid cannot pass.
| setting | before | after |
|---|---|---|
| root style 9..32 (504 cells) | 0 | 0 |
| `defaultFontSize` 9 / 12 / 16 / 20 / 24 (105) | 0 | 0 |
| `minimumFontSize=9`, `=12` (42) | 0 | 0 |
| `minimumFontSize=16` (21) | 6 cells, max 52 px | **6 cells, max 52 px** |
| `minimumFontSize=20` (21) | 12 cells, max 64 px | **6 cells, max 14 px** |
| `minimumFontSize=24` (21) | 15 cells, max 75 px | **0** |
| `minimumFontSize=32` (21) | 15 cells, max 174 px | **0** |
| fonts blocked (21) | 0 | 0 |
| fonts blocked + `minFont=32` (21) | 15 cells, max 174 px | **0** |
| **total** | **63 of 777** | **12 of 777** |
**The focus walk, which is the criterion itself: 290 entirely-hidden stops of
1,455 → 0.** Same grid (6 pages × 5 widths ≥ 1056 px, 30 cells, 344
header-internal stops skipped), hit-tested with `elementFromPoint`. **The
pre-fix tree was rebuilt in a git worktree and put through the identical probe:
it still reports 290** — footer 254, `#main` 36, geometry candidates 333 against
the fixed build's 43. That control is what makes the 0 a result rather than a
hope: both modes reading 0 with identical step counts is the shape `CLAUDE.md`
warns *ends* a check instead of starting one.
**Default settings unchanged: 0 differences over 352 page-widths × 17 fields =
5,984 comparisons**, full-page geometry fingerprint included, positive control
detecting exactly 1 injected difference.
⚠️ **AND THE FIRST ATTEMPT AT THAT COMPARISON REPORTED 4,224 DIFFERENCES.** It
keyed rows on `page`/`width`; this harness names them `url`/`w`, so every row
collapsed onto one map entry and was compared against an arbitrary single row.
It was caught only because the result was *uniformly bad*. **The fix is not the
corrected field names — it is that the comparison now asserts the key fields
exist and are unique before comparing anything.**
⚠️ **ONE COLUMN OF THE SWEEP WAS MEASURING NOTHING, AND TWO METRICS
DISAGREEING IS WHAT SURFACED IT.** A summary column reported
`min(scroll-padding-top headerH)` of **71.89 px** on root-style rows whose
`covered` read **0**. Cause: `insetBlockStart` is **900 px** in those rows —
the header is `position: sticky` and **not pinned**, because that is exactly how
the saturating `clamp()` gate un-sticks it. Comparing the offset to the header
height is meaningless where the header is not pinned. `covered` — real geometry
after a real hash navigation — is the valid metric. **A `position` of `sticky`
is not the same fact as "the header is pinned", and any future probe that
compares an offset against a header height has to read
`inset-block-start` too.**
**THE 12 REMAINING CELLS ARE PRE-EXISTING, REDUCED, AND DELIBERATELY NOT
FIXED.** `minimumFontSize=16` leaves 52 px of `#main` behind the header on
`/about/` and `/contact/` at 1280, 1440 and 1920 px; `=20` leaves 14 px on the
same six. Every one is unchanged or better than before the fix and none is new.
**Why the ramp cannot see them, which is the part worth keeping:** the setting
*floors* computed sizes, so at `minimumFontSize=16` the root is genuinely 16 and
`1lh` on `<html>` is 18 px — identical to the default — while the header grows to
**149.38 px** because the **sub-16 px** type (the 11 px tagline, the 14 px
eyebrow and nav) is floored up. **The ramp reads a quantity the setting did not
move.** Closing it needs a different measurement — `1lh` taken from an element
that carries the small type, or a floor on the header — which is a header change
with site-wide reach. Pouya's instruction on this step was *"if the sweep
surfaces anything beyond this one declaration's blast radius, stop and report —
do not widen"*, so it is reported: `docs/06` carries it as its own item. It is
**not** an SC 2.4.11 failure, which is about a component *entirely* hidden.
**`docs/06`'s item is ticked and carries the figures.** The decision was
`AGENTS.md` **Q61**, now closed.
⚠️ **AND ONE MORE LATENT CASE, AT THE DEFAULT TEXT SIZE — NO READER SETTING
INVOLVED: FALLBACK FONT METRICS PLUS THE SEVENTH NAV ITEM.** With every `.woff2`
blocked — what a reader on the Slow 4G profile `docs/04` budgets against sees
first, since the fonts are `font-display: swap` — and a seventh nav item cloned
from a real node, the header measures **141 px at every width from 1056 to
1091 px**, and 81 px from 1092 px up. With the webfonts loaded it is 81 px at all
of them. **Two consequences, and the record first carried only the smaller one:**
1. **A 60 px collapse** when Geist swaps in — a CLS contribution on all 22 pages,
against the CLS < 0.05 budget. **Larger, not new:** the previous build gives
86.5986.97 px in the same band, so a ~5.6 px shift already existed.
2. ⚠️ **AND 44 px OF `#main` BEHIND THE STICKY HEADER** after the skip link,
because 141 px exceeds the 97 px `scroll-padding-top`. **This one IS new** —
the previous build's 86.97 px stayed under 97 px, so it covered **0**. Framing
this case as "a CLS contribution" understated it, and a reader ruling on a
layout shift would weigh it differently from one ruling on the skip link.
*(The band was recorded as "1056 / 1064 / 1072 / 1084 … 81 px from 1092 up",
which reads as though 10861090 were checked and clear; they are all 141 px. It
is a contiguous **36 px** band, 10561091. Both errors found by
`adversarial-reviewer`.)*
**It is latent, not shipped:** with six items there is no wrap at any width, and
`showInsights` turns the seventh on only at two published articles. **Not fixed
here, and deliberately:** the two candidates are raising the desktop breakpoint
past 1091 px, which changes the layout at normal settings in that band, or giving
Geist a metric-matched `size-adjust` fallback, which is a font-stack change well
outside "the header and its consumers".
⚠️ **AND IT IS NOW GATED RATHER THAN MERELY PARKED — Pouya's ruling,
2026-09-01: NO SEVENTH NAV ITEM SHIPS UNTIL THIS IS FIXED.** Fixing it is a
**prerequisite of publishing the second Insights article**, because publishing
article #2 is what adds the item — `SiteHeader` computes `showInsights` from the
collection, so nothing else stands between that editorial decision and both
consequences above. The gate is recorded in three places on purpose: `AGENTS.md`
R20, `docs/06`'s `/insights/` state item, and the comment on `showInsights` in
`SiteHeader.astro` — the last of those being the only one a person editing an
article's front matter is likely to be looking at. The fix itself is *"to be
designed then, not now"*.
**What is still latent, measured and not a defect today:** `.hero-h` has **16
scoped rules, one per page, and only 2 carry `overflow-wrap: anywhere`** (`/` and
`/about/`). The other 14 measure 0 because their headlines' longest words are
shorter. The single-source fix would be to change `global.css`'s `h1``h6` rule
from `break-word` to `anywhere` and delete both overrides; that is a design-system
change with a site-wide blast radius on intrinsic sizing and it has not been
measured, so it is recorded here rather than done.
- **Measure the elements, not only the page.** A document-level overflow check
passes while a flex child absorbs the deficit by being crushed — that is how
step 1 shipped a logo at aspect 0.891 under a green "0 overflow at every
width". Assert the rendered geometry of the thing you care about. On `/`
every infinity-mark instance measures 1.56541.5657 against the master's
1.5657.
- **A touch-target measurement of the wrong box is not a finding.** The eight
cards on `/` report 2639 px-tall `<a>` elements and are fine: each card's
whole box is the link's hit area via `::after { inset: 0 }`, verified by
hit-testing nine points per card at three widths (24 cards, 9/9). Hit-test
before enlarging anything. WCAG 2.5.8's inline exception also applies to a
link sitting mid-sentence, and one such link on `/` is deliberately left at
164 x 21.
- Every page must be readable and navigable with JavaScript disabled.