fix: header reflows under enlarged text; reopen the step-1 nowrap decision
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
This commit is contained in:
co-authored by
Claude Opus 5
parent
0f7595b602
commit
fce89d46eb
@@ -181,9 +181,11 @@ const isHome = path === '/';
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
/* 48px, not 44. Still clears the touch floor, and it reserves the height the
|
||||
two-line brand takes at >=76rem so the sticky header is one constant 81px
|
||||
across every width where it is sticky — which is what --header-h and
|
||||
scroll-padding-top are keyed to. One number instead of two bands. */
|
||||
two-line brand takes at >=76rem so the sticky header is 81px at every width
|
||||
where it is sticky — AT THE DEFAULT TEXT SIZE, which is what `--header-h`
|
||||
and `scroll-padding-top` are keyed to. One number instead of two bands.
|
||||
Above the default the masthead wraps and is taller on purpose, and the
|
||||
sticky gate at the 66rem block is what keeps that safe. */
|
||||
min-block-size: 48px;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
@@ -204,18 +206,13 @@ const isHome = path === '/';
|
||||
same words as the hero eyebrow (docs/01). */
|
||||
.brand-tagline {
|
||||
display: none;
|
||||
/* HELD AT 11px, BELOW THE `.eyebrow` THIS ELEMENT CARRIES. Measured at the
|
||||
LIVE token value, 14px, on 2026-08-31 — earlier figures here were taken at
|
||||
13px and understated all three costs. (1) `--header-h` is 81px and the
|
||||
header goes to 84.98px; `global.css` drives `scroll-padding-top` from that
|
||||
token. (2) The CTA lands past `.header-inner`'s content edge — 1.3px at
|
||||
1216 with the SIX nav items shipping today, and 67.8 / 43.8 / 51.8 / 51.8px
|
||||
at 1216 / 1240 / 1280 / 1440 with a seventh. It does not resolve as the
|
||||
viewport widens. Insights is that item; `showInsights` turns it on at two
|
||||
published articles. (3) ⚠️ AND AT 14px IT DOES GROW THE DOCUMENT: 20px of
|
||||
overflow at 1216 and 4px at 1280 with seven items. At 13px it did not,
|
||||
which is why this comment used to say a page-level check could not see it.
|
||||
At 11px all three are zero, with six items and with seven. */
|
||||
/* HELD AT 11px, BELOW THE `.eyebrow` THIS ELEMENT CARRIES. At 14px the header
|
||||
WRAPS and stands at 144.98px instead of 81.00px — at 1216 with six nav items,
|
||||
and at EVERY width from 1216 up with a seventh. Nothing overflows and the CTA
|
||||
stays on the content edge, so the cost is now 64px of header height on every
|
||||
page: larger than the pre-wrap cost it replaces, and visible rather than
|
||||
invisible. `docs/02` §Reflow carries the superseded figures. Insights is that
|
||||
seventh item; `showInsights` turns it on at two published articles. */
|
||||
font-size: var(--text-2xs);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -387,14 +384,30 @@ const isHome = path === '/';
|
||||
|
||||
Sticky only from here up, too. Below this the nav takes a second row and
|
||||
the header stands at 137px, which is more of a small viewport than a
|
||||
sticky header is worth. Deviation from docs/02 "Sticky"; recorded there. */
|
||||
sticky header is worth. Deviation from docs/02 "Sticky"; recorded there.
|
||||
That same reasoning is what the sticky gate below extends to text size: a
|
||||
tall header is not worth sticking whether the height comes from a narrow
|
||||
viewport or from large type. */
|
||||
@media (min-width: 66rem) {
|
||||
.site-header {
|
||||
position: sticky;
|
||||
inset-block-start: 0;
|
||||
}
|
||||
.header-inner {
|
||||
flex-wrap: nowrap;
|
||||
/* ⚠️ STICKY ONLY WHILE THE MASTHEAD IS ONE ROW. A media query cannot say so:
|
||||
its `rem` resolves against the browser's DEFAULT font size, a property's
|
||||
against the root element. TWO terms, both load-bearing —
|
||||
`100vw - 66rem` catches a root ABOVE the default (the row wraps and the
|
||||
header stands 244–351px); `1rem - 16px` catches a root BELOW it, where the
|
||||
80rem content cap shrinks faster than the header's px minimums and the row
|
||||
wraps at EVERY viewport width — 65px of `#main` sat behind the header at
|
||||
Chrome's "Very small" (9px) with only the first term. `* 100000` saturates
|
||||
because wrapping is a step and a ramp left 10–69px covered across roots
|
||||
18–30; `-100vh` bounds the result. Both terms are >= 0 at the default size,
|
||||
so this is exactly `0px`. `docs/02` §Reflow carries the measurements and
|
||||
the cases where the offset can still be short. */
|
||||
inset-block-start: clamp(
|
||||
-100vh,
|
||||
min(calc((100vw - 66rem) * 100000), calc((1rem - 16px) * 100000)),
|
||||
0px
|
||||
);
|
||||
}
|
||||
.brand {
|
||||
margin-inline-end: var(--space-5);
|
||||
@@ -402,12 +415,20 @@ const isHome = path === '/';
|
||||
.nav {
|
||||
flex-basis: auto;
|
||||
}
|
||||
/* nowrap, and flex:none so the nav is never squeezed below its content
|
||||
width. Measured before this: at 960-1250px the seven-item nav broke to
|
||||
two rows and the header stood at 141px instead of 81px. */
|
||||
/* ⚠️ NOTHING HERE MAY SAY `flex-wrap: nowrap`, ON `.header-inner` OR ON
|
||||
`.nav-list`. `.header-inner`'s was the binding one, measured necessary AND
|
||||
sufficient: a `nowrap` line cannot break, so at a 200% text size the row ran
|
||||
944px past a 1280px viewport with Practice, Fees, Contact and the CTA
|
||||
off-screen. `.nav-list`'s is INERT — identical at every width and text
|
||||
setting, six items and seven — and stays removed only so this prohibition is
|
||||
not contradicted by a `nowrap` in the same file. Wrapping is the only
|
||||
mechanism that reflows under all THREE ways a reader enlarges text, because
|
||||
it is driven by used sizes rather than by a query.
|
||||
⚠️ "It never wraps above this breakpoint" holds only with the WEBFONTS
|
||||
LOADED: under fallback metrics with a seventh nav item the header is 141px
|
||||
across 1056–1091, which is both a 60px swap-in shift and 44px of `#main`
|
||||
behind the sticky header. Latent — six items never wrap. `docs/02` §Reflow. */
|
||||
.nav-list {
|
||||
flex-wrap: nowrap;
|
||||
flex: none;
|
||||
/* 16px from 66rem, widening to 24px at 80rem — see that block; the 76rem
|
||||
block deliberately does NOT widen it. Measured with seven items at every
|
||||
width from 1024px up. */
|
||||
@@ -464,6 +485,20 @@ const isHome = path === '/';
|
||||
.brand-tagline {
|
||||
display: block;
|
||||
}
|
||||
/* THE STICKY GATE MOVES WITH THE TAGLINE, and this is the same threshold the
|
||||
binary search above produced: with the tagline the one-row masthead fits
|
||||
from 1207px = 75.4rem, so 66rem is no longer the width it needs. Gating the
|
||||
wider band on 66rem left the header sticky and wrapped from root 18 up —
|
||||
46px of `#main` behind it at 1216/1280, 51px at 1440, 69px at 1920 — while
|
||||
`/` measured clean throughout, because `/` is the one page that suppresses
|
||||
the tagline. Each band gates on the width ITS layout requires. */
|
||||
.site-header {
|
||||
inset-block-start: clamp(
|
||||
-100vh,
|
||||
min(calc((100vw - 76rem) * 100000), calc((1rem - 16px) * 100000)),
|
||||
0px
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
|
||||
Reference in New Issue
Block a user