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>
162 lines
8.7 KiB
TypeScript
162 lines
8.7 KiB
TypeScript
// Server component: pure markup, no client state.
|
|
// Rendered into HomeView via a slot prop so its JSX doesn't bloat the
|
|
// HomeView client bundle.
|
|
|
|
import styles from './HomeView.module.css';
|
|
|
|
export function HowItWorksSection() {
|
|
// "exc" is the school's above-expected result. DfE names this differently
|
|
// by subject: "higher standard" for the reading and maths tests, "greater
|
|
// depth" for teacher-assessed writing. "nat" is the national expected %.
|
|
const miniCascade = [
|
|
{ subj: 'Reading', exp: 96, exc: 44, nat: 75, excLabel: 'Higher std' },
|
|
{ subj: 'Writing', exp: 81, exc: 26, nat: 72, excLabel: 'Greater depth' },
|
|
{ subj: 'Maths', exp: 85, exc: 41, nat: 74, excLabel: 'Higher std' },
|
|
];
|
|
/*
|
|
* The columns are deliberately unnamed.
|
|
*
|
|
* They previously carried two real school names — "Our Lady Queen of Heaven"
|
|
* and "St Mary's Catholic Primary" — beside invented results and invented
|
|
* Ofsted grades. On a site whose entire proposition is official DfE and
|
|
* Ofsted data, publishing made-up figures against identifiable schools is
|
|
* the one thing it cannot do, and no illustrative intent survives being
|
|
* screenshotted. Generic labels make the layout point just as well.
|
|
*/
|
|
const compareRows = [
|
|
{ label: 'Reading, Writing & Maths', a: '70%', b: '64%', aHi: true },
|
|
{ label: 'Higher standard (RWM)', a: '13%', b: '6%', aHi: true },
|
|
{ label: 'Ofsted', a: 'Outstanding', b: 'Good', aHi: true },
|
|
];
|
|
// Report card (Ofsted's framework from Nov 2025): one rating per area of
|
|
// school life on a five-point scale. Safeguarding is judged separately, as a
|
|
// binary met/not-met outcome — kept out of this list and shown on its own.
|
|
const reportCard: Array<{ area: string; grade: string; good: boolean }> = [
|
|
{ area: 'Achievement', grade: 'Strong', good: true },
|
|
{ area: 'Curriculum & teaching', grade: 'Expected standard', good: false },
|
|
{ area: 'Attendance & behaviour', grade: 'Strong', good: true },
|
|
];
|
|
|
|
return (
|
|
<section className={styles.howItWorks}>
|
|
{/* One header pattern across every band on this page: kicker, heading,
|
|
optional aside. The page previously ran four different treatments
|
|
(two uppercase micro-labels — one left, one centred — a large heading
|
|
with right-aligned grey text, and a kicker-plus-heading), which is
|
|
most of why a designed page read as a stack of unrelated strips. */}
|
|
<div className={styles.sectionHead}>
|
|
<p className={styles.sectionKicker}>Every school page</p>
|
|
<h2 className={styles.sectionHeading}>What you'll see on every school</h2>
|
|
<p className={styles.sectionAside}>Primary or secondary — the page adapts to the phase</p>
|
|
</div>
|
|
<div className={styles.hiwGrid}>
|
|
{/* Card 1 — Performance */}
|
|
<div className={styles.hiwCard}>
|
|
<div className={styles.hiwVisual}>
|
|
<div className={styles.hiwPhaseBlock}>
|
|
<div className={styles.hiwPhaseLabel}>Primary · Year 6 · <strong>Key Stage 2 SATs</strong></div>
|
|
<div className={styles.miniCascade}>
|
|
{miniCascade.map(({ subj, exp, exc, nat, excLabel }) => (
|
|
<div key={subj} className={styles.miniCascadeCol}>
|
|
<div className={styles.miniSubj}>{subj}</div>
|
|
<div className={styles.miniRowHead}><span>Expected</span><strong>{exp}%</strong></div>
|
|
<div className={styles.miniTrack}>
|
|
<div className={styles.miniNatPill} style={{ left: `${nat}%` }}>{nat}%</div>
|
|
<div className={styles.miniBarExp} style={{ width: `${exp}%` }} />
|
|
</div>
|
|
<div className={styles.miniRowHead}><span>{excLabel}</span><strong>{exc}%</strong></div>
|
|
<div className={styles.miniTrack}>
|
|
<div className={styles.miniBarExc} style={{ width: `${exc}%` }} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className={styles.hiwPhaseBlock}>
|
|
<div className={styles.hiwPhaseLabel}>Secondary · Year 11 · <strong>GCSE Attainment 8</strong></div>
|
|
<div className={styles.att8Row}>
|
|
<div className={styles.att8BarWrap}>
|
|
<div className={styles.att8BarHead}><span>This school</span><span>National avg 45.9</span></div>
|
|
<div className={styles.att8Track}>
|
|
<div className={styles.att8Fill} style={{ width: '54.8%' }} />
|
|
<div className={styles.att8NatLine} style={{ left: '45.9%' }} />
|
|
</div>
|
|
</div>
|
|
<div className={styles.att8Score}>
|
|
<div className={styles.att8Value}>54.8</div>
|
|
<div className={styles.att8Delta}>+8.9 vs national</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.hiwCardBody}>
|
|
<div className={styles.hiwStep}>Performance</div>
|
|
<div className={styles.hiwTitle}>Results against the national average</div>
|
|
<p className={styles.hiwDesc}>For primary schools, each subject's Expected and Exceeding percentages side by side. For secondary schools, GCSE Attainment 8 with the national benchmark overlaid.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Card 2 — Ofsted */}
|
|
<div className={styles.hiwCard}>
|
|
<div className={styles.hiwVisual}>
|
|
<div className={styles.ofstedPreview}>
|
|
<div className={styles.ofstedHead}>
|
|
<span className={styles.ofstedBullet} />
|
|
<span className={styles.ofstedTitle}>Latest Ofsted inspection</span>
|
|
</div>
|
|
<span className={styles.ofstedFramework}>Report card · five-point scale</span>
|
|
<div className={styles.rcList}>
|
|
{reportCard.map(({ area, grade, good }) => (
|
|
<div key={area} className={styles.rcRow}>
|
|
<span className={styles.rcArea}>{area}</span>
|
|
<span className={`${styles.rcChip} ${good ? styles.rcChipGood : styles.rcChipNeutral}`}>{grade}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className={styles.rcSafeguarding}>
|
|
<span className={styles.rcArea}>Safeguarding <span className={styles.rcSep}>· separate check</span></span>
|
|
<span className={`${styles.rcChip} ${styles.rcChipMet}`}>Met</span>
|
|
</div>
|
|
<div className={styles.ofstedMeta}>Full inspection · March 2026</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.hiwCardBody}>
|
|
<div className={styles.hiwStep}>Judgement</div>
|
|
<div className={styles.hiwTitle}>Ofsted at a glance</div>
|
|
<p className={styles.hiwDesc}>Since November 2025, Ofsted rates each area of school life on a five-point scale rather than one overall word. We show every area and the inspection date — and, for schools not yet reinspected, the older overall grade they still carry.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Card 3 — Compare */}
|
|
<div className={styles.hiwCard}>
|
|
<div className={styles.hiwVisual}>
|
|
<div className={styles.comparePreview}>
|
|
<div className={styles.compareHead}>
|
|
<div className={`${styles.compareHeadCell} ${styles.compareHeadLabel}`}>Metric</div>
|
|
<div className={styles.compareHeadCell}>School A</div>
|
|
<div className={styles.compareHeadCell}>School B</div>
|
|
</div>
|
|
{compareRows.map(({ label, a, b, aHi }) => (
|
|
<div key={label} className={styles.compareRow}>
|
|
<span className={styles.compareRowLabel}>{label}</span>
|
|
<span className={`${styles.compareRowVal} ${aHi ? styles.compareRowValHi : ''}`}>{a}</span>
|
|
<span className={styles.compareRowVal}>{b}</span>
|
|
</div>
|
|
))}
|
|
<div className={styles.compareFoot}>+ compare up to 5 schools</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.hiwCardBody}>
|
|
<div className={styles.hiwStep}>Compare</div>
|
|
<div className={styles.hiwTitle}>Side by side</div>
|
|
{/* One verb for one feature. The site previously called this
|
|
"compare" in the nav, "shortlist" in the footer and "pin" here,
|
|
which reads as three separate things it does not have. */}
|
|
<p className={styles.hiwDesc}>Compare up to five schools and every metric aligns in the same columns — works for primary and secondary alike.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|