diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index 07dfe10..ae31757 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -121,6 +121,33 @@ test('the home page has a real title', async ({ page }) => { expect(await page.title()).toMatch(/schoolcompare/i); }); +test('the site states its independence and never claims to be official', async ({ page }) => { + await page.goto('/'); + + // Carried in the footer, so it appears on every page rather than only here. + await expect( + page.getByText(/an independent site\.\s*not affiliated with the department for education/i) + ).toBeVisible(); + + /* + * Describing the DATA as official is accurate and appears throughout + * ("official DfE figures", "the official figure isn't in"). Describing the + * SITE as official is not: schoolcompare republishes government data, it is + * not a government service. The difference is the grammatical subject, so + * this looks for text that opens with "Official" as a standalone label — + * the shape a value-prop title or heading takes, and the shape the old + * "Official & trusted" took. + */ + const claims = await page.evaluate(() => + [...document.querySelectorAll('body *')] + .flatMap(el => [...el.childNodes] + .filter(n => n.nodeType === 3) + .map(n => (n.textContent ?? '').trim())) + .filter(t => /^Official\b/i.test(t) && !/^Official\s+(DfE|data|figures)/i.test(t)) + ); + expect(claims, `text presenting the site itself as official: ${claims.join(' | ')}`).toEqual([]); +}); + test('the landing bands share one vertical rhythm', async ({ page }) => { await page.goto('/'); const gaps = await page.evaluate(() => { diff --git a/nextjs-app/components/FilterBar.module.css b/nextjs-app/components/FilterBar.module.css index 8a085b2..c062e4a 100644 --- a/nextjs-app/components/FilterBar.module.css +++ b/nextjs-app/components/FilterBar.module.css @@ -460,6 +460,30 @@ } } +/* + * Narrow phones: the button drops below the input instead of sharing the row. + * + * "Search schools" is a fixed 134px with `white-space: nowrap`, so on a 390px + * screen it took 48% of the row and left the input 102px of text space for a + * 194px placeholder — the field showed "School name or" and stopped. At 320px + * the input was down to 32px, which is too narrow to read what you are typing. + * + * Wrapping rather than restructuring: the pill stays one element, so it keeps + * its border, shadow and :focus-within ring, and the button gets a full-width + * tap target on the way. Giving the button a 100% flex-basis is what forces + * the wrap; the input keeps `flex: 1` and so takes the whole first row. + */ +@media (max-width: 480px) { + .heroMode .omniBoxContainer { + flex-wrap: wrap; + gap: 0.5rem; + } + .heroMode .searchButton { + flex: 1 1 100%; + width: 100%; + } +} + @media (max-width: 420px) { /* Below this the pin costs more room than it earns. */ .heroMode .omniIcon { diff --git a/nextjs-app/components/Footer.module.css b/nextjs-app/components/Footer.module.css index 53ebe8d..c085ff2 100644 --- a/nextjs-app/components/Footer.module.css +++ b/nextjs-app/components/Footer.module.css @@ -92,6 +92,22 @@ max-width: 34ch; } +/* + * Quieter than .description, but by size only — not by contrast. + * + * --on-sunken-faint measured 4.71:1 here, which clears AA by 0.21. That is a + * fine margin for decorative text and the wrong one for a disclaimer, whose + * whole job is to be legible to someone checking whether this is a government + * site. --on-sunken-muted is 5.66:1 on the same band. + */ +.independence { + margin: 0; + font-size: var(--step--2); + line-height: 1.6; + color: var(--on-sunken-muted); + max-width: 38ch; +} + .sectionTitle { margin: 0; font-family: var(--font-display); diff --git a/nextjs-app/components/Footer.tsx b/nextjs-app/components/Footer.tsx index aa6a8c6..8267979 100644 --- a/nextjs-app/components/Footer.tsx +++ b/nextjs-app/components/Footer.tsx @@ -35,6 +35,14 @@ export function Footer() {

Compare primary and secondary schools across England.

+ {/* + Says once, plainly, what the landing page's value props only + imply: we publish official data, we are not an official body. + Cheap to state and expensive to be wrong about. + */} +

+ An independent site. Not affiliated with the Department for Education or Ofsted. +

, tintClass: styles.propIconTrust, - title: 'Official & trusted', + /* + * "Built on official data", never "Official". + * + * The previous title was "Official & trusted", whose grammatical subject + * is this site — it reads as a claim that schoolcompare is itself an + * official service. It is not: it is an independent site that republishes + * official figures. The distinction is the difference between describing + * the data and describing ourselves, and only the first is true. + * + * Everywhere else the word appears ("official DfE figures", "the official + * figure isn't in") it already qualifies the data, which is correct and + * should stay. + */ + title: 'Built on official data', body: 'Every figure comes from DfE performance tables and Ofsted.', }, {