The step-1 header decision is formally reopened (AGENTS.md (ak)): its record attributed the residual to the wrong cause and read a measured 944px functional failure as a robustness margin. Mechanism — wrapping, plus a gated sticky offset: - `flex-wrap: nowrap` removed from `.header-inner` (measured necessary AND sufficient) and from `.nav-list` (measured inert; removed so the prohibition is not contradicted by a `nowrap` in the same file), with a dead `flex: none`. - `inset-block-start` becomes a two-band, two-term saturating clamp() so the header is sticky only while the masthead is one row. A media query cannot express this: its `rem` resolves against the browser DEFAULT font size, a property's against the root element. The second term catches a root BELOW the default, where the 80rem content cap shrinks faster than the header's px minimums; without it 65px of `#main` sat behind the header at 9px. - Wrapping is the only mechanism that reflows under all THREE enlargement paths, because Chrome's minimum-font-size setting is invisible to @media, to @container and to every length unit. Measured, 22 pages x 16 widths = 352 page-widths per method: root-style 32px 175/352 -> 0/352 (1280px: 944px overflow -> 0) minimum-font-size 32 219/352 -> 88/352 (residual is the footer email) default-font-size 32 0/352 -> 0/352 default (root 16) 0/352 -> 0/352 Nav items and CTA on-screen in 1408/1408. A further 762 points across roots 9-32, both thresholds, the band seam and all five Chrome presets: 0 failing. Normal-settings identity: 0 differences on 352 page-widths across six metrics, with six nav items and with a seventh injected. Header 81.00px at all eight widths >= 1056, CTA gap 0.00px. Lighthouse: 22 pages, no category below 95, CLS 0.000 on every page. --header-h is reworded as a FLOOR, not a constant; value unchanged. Also in this step, per ruling: - /bio/ print `font-weight` frozen at 400 — the circulated PDF's typography changes only when its content is deliberately revised, never as a side effect of a screen refactor. Declaration byte-identical; the constraint is recorded. - CLAUDE.md: the two-simulation rule for enlarged text, the zsh no-word-splitting rule, the third (minimum-font-size) mechanism, and "state the grid with the count". Two rounds of adversarial-reviewer, eleven findings, all resolved; round 2's blocking finding was a defect in round 1's own fix. Two suggested fixes declined with reasons in (ak). claims-auditor deliberately not run — D20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
54 KiB
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 indocs/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.
--goldis never a text colour on cream. Rules, borders, dividers, icon strokes, and on-dark text only.--gold-don cream only at 24 px+ / 19 px bold, and only for decorative display text — never for anything a reader must parse.- Body text on cream is
--inkor--ink-soft. Metadata may use--muted. - Secondary text on dark is
--gold-lor--creamat ≥ 70% opacity. - 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-4xland 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 at68ch— the old site ran full-bleed paragraphs at 1400 px, which is unreadable. - Eyebrows: mono, 14 px (
--text-eyebrow),0.18emtracking, 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,.eyebrowinglobal.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-weightincluded; without that they rendered at 400 beneath ap.eyebrowof 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-eyebrowis 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-xsand--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 insrc/— the header tagline (git grep 'var(--text-2xs)' -- srcreturns one hit,SiteHeader.astro; 21 rendered instances, every onespan.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 inSiteHeader.astro. Three blocks moved to--text-eyebrowon 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: thefont-sizediffered, and the missing sixth wasfont-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 becausenpm run bio:pdfholds it to one page. Measured 2026-08-31: the@media printblock 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 freezefont-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 thecmp -ldiffering-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 printboundary. 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.mdR16): 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 — seebio.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 —.fineand.sheet-contact p. The first eight are mono and uppercase or tracked at0.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).ArticleCardand 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, easingcubic-bezier(.2,.7,.2,1). Transform and opacity only — never layout properties. - Implement with
animation-timeline: view(), behind@supports. Amended 2026-08-26: theIntersectionObserveralternative 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, and05-backend-spec.mdspecifiesscript-src 'self'with nounsafe-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
@supportsgate is load-bearing, not defensive: without it, a browser that ignoresanimation-timelineruns 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
fromstate —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.
@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 320–375 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 getalt="". -
Colour never carries meaning alone.
-
All functionality reachable by keyboard; focus order matches visual order.
-
Forms: real
<label>elements,aria-describedbyfor hints, errors announced withrole="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 grids83 px 153 px .credentialsmade explicitrepeat(2, minmax(0, 1fr));.featurepadding clamped andoverflow-wrap: anywhereon its title;.contact-actionflex: 0 1 auto+min-inline-size: 03 px 63 px 65 pxcorrected to63 pxon 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 built38 px 68 px 108 px .designation-partwhite-space: nowrapremoved (the separator is held by an NBSP instead) andoverflow-wrap: anywhereon.hero-h0 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 threePracticeCardchips 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 ofarticle.cardwithscrollWidth > clientWidth, a different predicate on a different element;adversarial-reviewerre-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
nowrapwas 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-5xlcomputes 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: anywhereis 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', thendocumentElement.scrollWidth - documentElement.clientWidth, plus an enumeration of every element wider thanclientWidthto name the offender.✅ 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'sPage.setFontSizes({standard: 32}), reported 0 everywhere, and the prose generalised that zero. All three are re-measured below.mechanism remin a media queryrem/emin a PROPERTYgetComputedStylerootsees the enlargement? documentElement.style.fontSize='32px'(root style)16 px → 66rem= 1056 px32 px 32 px media query no, property yes Page.setFontSizes({standard:32})(the reader's default font size)32 px → 66rem= 2112 px32 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 px16 px 32 px NOTHING IN CSS SEES IT (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
66remreally 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 byadversarial-reviewer.)Two consequences, both measured 2026-09-01 and both load-bearing:
- In a media query
remresolves 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 —@containerrem/emDO resolve against the root element, measured — which is why the sticky gate below is a property and not a query. - ⚠️ UNDER A MINIMUM-FONT-SIZE SETTING,
getComputedStyle(el).fontSizeAND THEemUNIT DISAGREE. Blink floors the reported computed font size to 32 px whileemandremkeep resolving at 16 px — soscroll-padding-top: 6.0625emcomputes to 97 px, not 194 px, a66em-wide box measures 1056 px, not 2112 px, andmin(0px, calc(100vw - 66em))is 0px. Media queries, container queries and length units are all blind to it. Only used-value layout — wrapping — responds, which is why wrapping had to be the mechanism and why one residual below cannot be closed in CSS.
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 88 of 352 — all of it the FOOTER, see the residual below 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 220 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-ctaat 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: nowrapis gone from.header-innerand from.nav-listabove 66 rem, along with the deadflex: nonebeside 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 }only0/0 0/0 0/0 0/0 0/0 .header-inner { flex-wrap: nowrap }only0/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'snowrapwas 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": thenowrapprohibition inSiteHeader.astrowould otherwise contradict anowrapstill sitting in the file, and the override only ever re-statednowrapover a base rule that already wraps. Found byadversarial-reviewer; the mechanism sentence about min-content was true and was not the binding constraint. (flexis a flex-ITEM property and.navcomputesdisplay: block, measured at 1056 / 1280 / 1920 px — soflex: nonesetflex-shrink: 0on 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 computedflex-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 thenowrapwas 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.59–350.86 px at a 200 % root size against 81 px normally. A sticky box that size covers
#mainafter "Skip to content", so stickiness has to go wherever the header can exceedscroll-padding-top. The gate is a property, not a query, because in a propertyremtracks 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 byadversarial-reviewerand independently reproduced: 20 of 300 (page × viewport × root) points left#mainbehind the header, 10–69 px, across roots 18–30 — 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.- 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* 100000saturation, bounded by-100vh. - 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-contentis80rem, so the content column shrinks to 720 px while the header'smin-inline-size: 44px/min-block-size: 44pxfloors do not, and the row wraps at every viewport width. The header stood at 120 px against a 54.56 pxscroll-padding-top— 65 px of#mainbehind 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:
@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 - 16pxis 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 matchedmin-width: 66remguarantees100vw ≥ 1056 pxand a matchedmin-width: 76remguarantees100vw ≥ 1216 px, because100vwcounts a classic scrollbar and the query width does not, so the term can only be more positive.Measured after: 0.00 px of
#maincovered — 0 of 300 grid points across roots 16–32 × viewports 1056–1920 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 1180–1260 × roots 16–32). ⚠️covered: 0is 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 computedinset-block-startis0pxand 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 computedinset-block-startis0pxat 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, headertop=0pxin every row:condition header offset short by status minimumFontSize=32164.58–270.56 px 97 px 68–174 px live, unfixable in CSS fallback font metrics + 7th nav item, default text size, 1056–1091 px 141 px 97 px 44 px latent on showInsightsdefaultFontSize=9120 px 54.56 px 65 pxfixed by the 1rem - 16pxtermroot-style 10 px 69 px 60.63 px 8 pxfixed by the same term --header-hIS 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 ascroll-padding-topthat 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 waswhite-space: nowrapon.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.TEN FIXES ACROSS THE TWO PASSES — no
overflow-xwas added anywhere. Eight are narrow-width (2026-08-31); the last two are the large-text nav (2026-09-01). All but one are cause-specific; the exception is marked as the backstop it is:Element Was 320 px Cause SiteHeader .header-innerflex-wrap: nowrapremoved above 66 rem944 px at 1280 px, root 32 Necessary and sufficient, measured. A nowrapflex line cannot break, so the brand / nav / CTA row could not reflow at allSiteHeader .nav-listflex-wrap: nowrapand a deadflex: noneremoved above 66 rem0 px — inert Kept removed so the nowrapprohibition is not contradicted by anowrapin the same file. Byte-identical to shipped at every width and every text setting, six items and sevenSiteHeader .site-headerinset-block-start: 0→ a saturatingclamp()gate, in two bands (66 rem, and 76 rem where the tagline shows)(consequence of the row above) Wrapping makes the header 244.59–350.86 px at root 32; sticky at that height covered #mainby up to 157 px after the skip link. Round 1's single-bandmin()ramped instead of stepping and left 20 of 300 grid points covered by 10–69 pxSiteHeader .brand-namewhite-space: nowrapremoved63 px, all 22 pages Two words glued into one unbreakable box /bio/ .rates-listoverflow-wrap: anywhere152 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-hoverflow-wrap: anywhere36 px "contract," at 104 px held .hero-copyopen/ .feature-bodyoverflow-wrap: anywhere26 px "party-appointed" at 202 px CredentialRow .credential-labeloverflow-wrap: anywhere38 px "designation" needs 143 px in an 88 px track SiteFooter .footer-brandflex-wrap: wrap12 px Flex item at min-width: autocannot shrink below "Lajevardi"PracticeCard .cardpaddingclamped(the cause under Pill)The space scale is rem-based, so --space-6is 64 px a side at root 32 — 128 px of padding in a ~224 px boxPilloverflow-wrap: anywhere33 px → 1 px ⚠️ SYMPTOM, NOT CAUSE. A one-word pill cannot wrap at a space that is not there flex-wrap: wrapon the footer brand rather thananywhere, because it reflows without hyphenating a person's name.⚠️
Pill'sanywhereIS A BACKSTOP AND THE TRADE IS RECORDED RATHER THAN HIDDEN. It removes the min-content floor of a shrink-to-fitinline-flexbox, so the pill collapses to whatever its parent gives it. The real cause wasPracticeCard'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.anywhereis 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.Six instrument findings, and each one hid a real defect. The last three are from the 2026-09-01 pass and all three produced a CLEAN-LOOKING result:
- ⚠️ A UNIFORM PASS FROM A TAUTOLOGY. The skip-link probe reported
0.0 px of #main coveredon 946 of 946 page-widths, before and after, under every mechanism.global.csssetsscroll-behavior: smooth, which makesscrollIntoView()asynchronous, so a synchronous read afterwards measuresscrollY = 0; and atscrollY = 0the target sits exactly at the header's bottom edge, socoveredis 0 by construction. Passbehavior: 'instant'and assert thatscrollYequals its predicted value — once fixed the same probe reported 10–26 px covered on 22 of 22 pages, a pre-existing defect the tautology had hidden. - ⚠️ A RUNNING TRANSITION MAKES A PROPERTY READ RETURN THE OLD VALUE.
Forcing reduced motion — by CDP emulation or by
--force-prefers-reduced-motion— madedocumentElement.style.fontSize = '32px'read back as 16 px, while the inlinestyleattribute plainly saidfont-size: 32px. This codebase's reduced-motion block setstransition-duration: 0.01ms !importanton*, which starts a transition on every property change, and a synchronousgetComputedStylethen returns the transition's start value. The instrument now injectstransition:none;animation:noneinstead. Same family as a running transition outranks an!importantauthor declaration. - 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 — nomin-inline-size, nomin-block-size, nofont-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.
The original three:
overflow-wrap: break-wordpermits a break at layout time but does not reduce min-content size —anywheredoes. Five of the fixes are this distinction.global.css:166setsbreak-wordonpANDh1–h6— (this line said "h1–h6and nothing else, so body copy inherits no wrapping at all", which was wrong in a way that mattered:.feature-bodyis a<p>and already hadbreak-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.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. ReadscrollWidth - clientWidthper element as well as per document.- Under
Emulation.setDeviceMetricsOverride({mobile: true})the LAYOUT viewport expands to fit the content, soinnerWidthbecomes 472 at a requested 320 andscrollWidth - innerWidthreads 0 while the reader is still scrolling sideways. The honest formula isdocumentElement.scrollWidth - documentElement.clientWidth, which is correct in both modes.
⚠️ THE ONE RESIDUAL THAT REMAINS, AND IT IS THE FOOTER RATHER THAN THE HEADER. Under Chrome's minimum font size only, document overflow remains at four widths — 14 px at 320, 38 px at 1024, 30 px at 1056, 19 px at 1100, on all 22 pages, falling to 0 from 1200 px up. Every instance is
SiteFooter:info@smlcompany.cahas no break opportunity and needs 310 px inside a 224–243 px footer-contact column, and at 320 px the.footer-colbox itself measures 310 px inside a 272 px content area. The FOOTER'S OWN overflow is byte-identical before and after —div.footer-grid78 px at 1056 and 67 px at 1100,footer.site-footer30 px and 19 px, unchanged — and the 320 px and 1024 px document figures are unchanged too, at 14 and 38. ⚠️ The 1056 px and 1100 px document figures DID change, 414 → 30 and 370 → 19, and this sentence claimed all four were identical. They changed because the header stopped contributing its own 414 px and 370 px, not because the footer moved; a document figure and a footer contribution are different measurements and only the second is unchanged. Found byadversarial-reviewer. It is out of the scope Pouya set for this step ("the header and its consumers"), so it was measured and left rather than improvised: the 1024–1100 px half looks like oneoverflow-wrap: anywhereon the footer contact links, the 320 px half is a column-sizing question and is not the same fix. Not header-side: no header element extends the document at any width under any mechanism, and the nav items and CTA are on-screen in 1408 of 1408 measurements (four text settings × 22 pages × 16 widths). (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 here for the final build was the two-similar-totals mistake this section keeps warning about.) (The Practice dropdown panel's contents do overflow their own box by 84 px under this setting, but the panel sits inside a closed<details>and never extends the document — checked by taking the furthest-right box rather than the largest content overflow.)⚠️ AND ONE THAT CANNOT BE CLOSED IN CSS AT ALL. Under minimum font size the sticky gate above cannot fire — both of its terms evaluate to
0becauseremstill resolves at 16 px there, so100vw - 66remis positive and1rem - 16pxis exactly zero — and the header stays sticky at 164.58–270.56 px and the skip link lands with 68–174 px of#mainbehind it, on 22 of 22 pages from 1056 px up. It was 10–26 px before, so this pass widened it. (These read "228–271 px" and "132–174 px" for one revision; both omitted the single-row band — 68 px at 1200 px and on/from 1216 px up.adversarial-reviewermeasured the widths the first sweep had not.) The trade is deliberate and is the right way round: what it bought is the removal of 830 px of horizontal overflow with four nav items and the CTA unreachable under that same setting. Vertical offset leaves every target reachable by scrolling; the horizontal case did not. No media query, container query or length unit can detect this mechanism (see the table above), the only candidates left are a fitted magic number tied to the current nav item count or a normal-settings behaviour change, and Pouya's constraint was to stop and report rather than ship either. Carried ondocs/06's cutover checklist.⚠️ AND ONE MORE LATENT CASE, AT THE DEFAULT TEXT SIZE — NO READER SETTING INVOLVED: FALLBACK FONT METRICS PLUS THE SEVENTH NAV ITEM. With every
.woff2blocked — what a reader on the Slow 4G profiledocs/04budgets against sees first, since the fonts arefont-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:- 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.59–86.97 px in the same band, so a ~5.6 px shift already existed.
- ⚠️ AND 44 px OF
#mainBEHIND THE STICKY HEADER after the skip link, because 141 px exceeds the 97 pxscroll-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 1086–1090 were checked and clear; they are all 141 px. It is a contiguous 36 px band, 1056–1091. Both errors found by
adversarial-reviewer.)It is latent, not shipped: with six items there is no wrap at any width, and
showInsightsturns 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-matchedsize-adjustfallback, which is a font-stack change well outside "the header and its consumers". Both are Pouya's call; carried ondocs/06.What is still latent, measured and not a defect today:
.hero-hhas 16 scoped rules, one per page, and only 2 carryoverflow-wrap: anywhere(/and/about/). The other 14 measure 0 because their headlines' longest words are shorter. The single-source fix would be to changeglobal.css'sh1–h6rule frombreak-wordtoanywhereand 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. - In a media query
-
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.5654–1.5657 against the master's 1.5657. -
A touch-target measurement of the wrong box is not a finding. The eight cards on
/report 26–39 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.