Main's staging E2E has been red since #90. Two of the four failures were the
staging container serving a stale image at the moment the gate ran — apple-icon
and the header lockup both check out against staging now. The other two were
real, and both are the same mistake: a colour pairing verified against one
ground while the element sits on another.
* .heroEyebrow --brand on its own 10% tint over Sand is 4.19:1. The token
clears AA on Sand, but the tint darkens the ground under the label, and
that composite was never the thing measured. --brand-strong is 5.85:1.
* .hiwVisual The preview panel was grounded in Sand while everything
inside it is a translucent status tint — and those tints are specced to
clear AA over --bg-card. Over Sand the report-card chips landed at 4.22:1
and 4.29:1. The ratio depended on a background two levels up. Moving the
panel to the card surface puts them at 5.69:1 and 5.81:1; a border keeps
it reading as an inset frame now that panel and card share a colour.
* Footer .sectionTitle --sage flips with the theme; the footer band does
not (it is teal in both). So the pairing only held in one of them — the
dark sage measured 4.08:1, on every page. Now --on-sunken-muted, which is
what the --on-sunken-* family exists for, as Footer.tsx's own header
comment already says.
* .compareHeadLabel --text-muted on the header row's tint is 4.45:1 in the
dark theme. Under by a hair, same cause. Now --text-secondary.
The harness gained the footer, because it had no footer and so could not have
caught the one failure that appeared on all three pages. Rewriting fragments
now uses each CSS module's own hash — the footer rewritten with HomeView's
prefix renders unstyled, which would have made any contrast measured on it
meaningless while still reporting a number.
Verified: 0 AA failures in both themes across the assembled page, measured with
the same probe the e2e test uses, against the real compiled CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The homepage made four statements that were not true, carried elements that
asked nothing of anyone, and had a hero illustration that broke in both the
places it had to work.
Claims, all verified against the code or the API:
* "24,000+ schools" (three places) against a real 27,230. The fact box meant
to show the live figure rendered its own fallback on every request, because
DataInfoResponse declared a `total_schools` field the API has never sent —
it sends `unique_schools`. The fetch succeeded; only that field was
undefined, so nothing threw and nothing failed. The interface, not the
code, was the thing that was wrong.
* "Up to three schools side by side" against MAX_SCHOOLS = 5, contradicting a
card 400px below it that correctly said five.
* "Class sizes" — data the codebase has never held. That copy line was the
only hit in a full-repo grep.
* Invented results and Ofsted grades attributed to two real, named schools
in the compare preview.
Also one feature, three words: Compare (nav), shortlist (footer), pin (cards).
Settled on Compare everywhere. And <title> was the bare string "Home".
Cut: the trust line (repeated the coverage figure one paragraph after the hero
gave it, behind three decorative dots), the "Start exploring" row (three links
to two destinations already in the nav), the six-row coverage table, and three
of the four countdown cards — which gave the page's largest numeral to dates up
to 245 days away, two of them offer days, which cannot be missed. All four
dates remain, at proportionate weight. Value-prop titles drop from <h2> to <p>;
they were outranking the page's real headings in the document outline.
Rhythm: the gaps between the seven landing bands were 24/32/24/16/48/32/16px,
each band setting its own margin, with four different section-header
treatments between them. The page container now owns one gap, and there is one
header pattern. An e2e test asserts the gaps are identical.
Illustration: it kept a fixed light palette in both themes, which left a pale
sky slab as the brightest object on a near-black page, out-shouting the H1 and
the search box. It now reads from --ill-* tokens with a dark re-grade. And the
hero slot ranges from 1.34:1 to 4.9:1 across breakpoints, which no single
composition survives under `slice` — at 860x176 a 540x520 scene shows only its
bottom 110 units, so the schoolhouse was cropped away entirely on phones,
leaving hills and a pin pointing at nothing. There are now two compositions,
each drawn against the crop window its own breakpoint produces, with CSS
showing one. Both are static server-rendered SVG.
The deadline bar renders on the server rather than on hydrate. The effect-based
version needed a reserved height, and one guessed number cannot cover a block
whose supporting line wraps differently at every width — measured, it was short
at all four, shifting the page up to 108px on a phone.
Verified on the built output through an offline render harness (no local
server): real compiled CSS, real rendered markup, four widths, both themes.
tsc clean, 159/159 unit tests, build green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/* --brand on its own 10% tint over Sand measures 4.19:1 — the tint darkens
the ground under the label, and the token was specced against the ground,
not against the tint. --brand-strong on the same composite is 5.85:1. */
color:var(--brand-strong);
background:rgba(var(--brand-rgb),0.1);
padding:0.3rem0.7rem;
border-radius:999px;
@@ -779,8 +782,19 @@
gap:0.9rem;
}
/*
* The preview panel sits on the card surface, not on Sand.
*
* Everything inside it is a translucent status or brand tint, and those tokens
* are specced to clear AA over --bg-card. Over Sand the same tints composite
* darker, which put the report-card chips at 4.22:1 and 4.29:1 against a 4.5
* floor — the ratio depended on a ground two levels up. On the card surface
* they are 5.69:1 and 5.81:1. The border keeps it reading as an inset frame
* now that panel and card share a colour.
*/
.hiwVisual{
background:var(--bg-secondary);
background:var(--bg-card);
border:1pxsolidvar(--border);
border-radius:10px;
padding:0.9rem;
min-height:180px;
@@ -1126,7 +1140,10 @@
.compareHeadLabel{
font-family:inherit;
font-size:0.48rem;
color:var(--text-muted);
/* --text-muted on the header row's tint is 4.45:1 in the dark theme —
under the floor by a hair, because the tint darkens the ground beneath
the label. --text-secondary clears it on both grounds. */
color:var(--text-secondary);
font-weight:500;
letter-spacing:0.08em;
text-transform:uppercase;
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.