fix(brand): match the below-average tint to its own hue, and finish the casing
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 6s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 43s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 3m19s

Three review findings.

--status-below-bg carried the raw --coral tuple (249,115,96) instead of the
hue it belongs to (#A9481F → 169,72,31), so "below average" chips rendered a
coral-pink wash behind terracotta text — the one solid/tint pair in the file
whose hues disagreed. Alpha drops 0.15 → 0.12 to match --status-above-bg, so
the two chips read as equal weight; left at 0.15 the darker terracotta tint
takes the label to 4.45:1 on Warm White, under the 4.5 floor. At 0.12 it is
4.86:1 on cards and 4.67:1 on Warm White.

Auditing the whole file for the same class of error turned up one more:
--action-bg was also a --coral tint while --action is the darkened #BE3C27.
It is unused today and the dark-theme value was already correct, but it is
aligned so a future coral ghost button gets a matching label and ground. The
five phase pairs also "mismatch" by the same test and are deliberately left
alone: those pair a pastel ground (sage, sky, mustard, lavender) with a deeper
text hue, which is exactly what the guideline's Chips/Tags show.

The lowercase wordmark had only reached the visual chrome. Every user-visible
occurrence now matches: page title and template, appleWebApp title, authors,
OpenGraph and Twitter titles and siteName, the school page's siteName, the PWA
manifest name and short_name, the footer copyright line, the nav's aria-label,
the Web Share API strings, and the editorial paragraph. Comments in lib/*.ts
and AdmissionsView still say "SchoolCompare"; they describe the codebase rather
than the mark, so they are left to keep this diff to the reported problem.

The comment explaining why the font classes belong on <html> still described
--font-schibsted, which no longer exists. It now names --font-manrope and
--font-inter, and records that the e2e typeface assertion is the only guard —
this failure mode throws nothing and fails no unit test.

Verified: tsc clean, 159/159 tests, build green, and the rendered title is now
"School Admissions Guide | schoolcompare". Status and phase labels re-checked
on their own composited tints over both light grounds — all clear AA.
This commit is contained in:
Tudor
2026-08-07 18:33:30 +01:00
parent accc562b90
commit 39e5c11274
8 changed files with 34 additions and 25 deletions
+11 -3
View File
@@ -1,5 +1,5 @@
/* /*
* SchoolCompare — "Find the right school. For their future." * schoolcompare — "Find the right school. For their future."
* *
* Implements the schoolcompare brand guideline. Colour has four jobs and they * Implements the schoolcompare brand guideline. Colour has four jobs and they
* never borrow each other's hues: * never borrow each other's hues:
@@ -51,7 +51,9 @@
--action: #BE3C27; /* darkened coral — 5.4:1 under a white label */ --action: #BE3C27; /* darkened coral — 5.4:1 under a white label */
--action-strong: #A33320; --action-strong: #A33320;
--action-stronger: #8C2B1B; --action-stronger: #8C2B1B;
--action-bg: rgba(249, 115, 96, 0.14); /* Tint of --action, not of --coral, so a coral ghost button's label and
ground share a hue. Unused today; the dark value was already correct. */
--action-bg: rgba(190, 60, 39, 0.14);
--action-on: #FFFFFF; --action-on: #FFFFFF;
/* ── Palette accents: exact brand values, for fills and tints only ── */ /* ── Palette accents: exact brand values, for fills and tints only ── */
@@ -67,7 +69,13 @@
--status-above: #36743F; --status-above: #36743F;
--status-above-bg: rgba(54, 116, 63, 0.12); --status-above-bg: rgba(54, 116, 63, 0.12);
--status-below: #A9481F; --status-below: #A9481F;
--status-below-bg: rgba(249, 115, 96, 0.15); /* The tint is the solid hue at low alpha, exactly as every other pair in
this file is. It briefly carried the raw --coral tuple instead, which
put a coral-pink wash behind terracotta text — the one mismatched pair
in the palette. Alpha matches --status-above-bg so the two chips read
as equal weight; at 0.15 the darker terracotta tint drops the label to
4.45:1 on Warm White. */
--status-below-bg: rgba(169, 72, 31, 0.12);
--status-at: #5F6A75; --status-at: #5F6A75;
--status-at-bg: rgba(95, 106, 117, 0.10); --status-at-bg: rgba(95, 106, 117, 0.10);
+14 -13
View File
@@ -43,16 +43,16 @@ export const viewport: Viewport = {
export const metadata: Metadata = { export const metadata: Metadata = {
appleWebApp: { appleWebApp: {
capable: true, capable: true,
title: 'SchoolCompare', title: 'schoolcompare',
statusBarStyle: 'default', statusBarStyle: 'default',
}, },
title: { title: {
default: 'SchoolCompare | Compare School Performance', default: 'schoolcompare | Compare School Performance',
template: '%s | SchoolCompare', template: '%s | schoolcompare',
}, },
description: 'Compare primary and secondary school SATs and GCSE performance across England', description: 'Compare primary and secondary school SATs and GCSE performance across England',
keywords: 'school comparison, KS2 results, KS4 results, primary school, secondary school, England schools, SATs results, GCSE results', keywords: 'school comparison, KS2 results, KS4 results, primary school, secondary school, England schools, SATs results, GCSE results',
authors: [{ name: 'SchoolCompare' }], authors: [{ name: 'schoolcompare' }],
manifest: '/manifest.json', manifest: '/manifest.json',
// No `icons` key on purpose: setting it here would override the file // No `icons` key on purpose: setting it here would override the file
// conventions. app/icon.svg and app/apple-icon.tsx are the source, and // conventions. app/icon.svg and app/apple-icon.tsx are the source, and
@@ -60,15 +60,15 @@ export const metadata: Metadata = {
metadataBase: new URL('https://schoolcompare.co.uk'), metadataBase: new URL('https://schoolcompare.co.uk'),
openGraph: { openGraph: {
type: 'website', type: 'website',
title: 'SchoolCompare | Compare School Performance', title: 'schoolcompare | Compare School Performance',
description: 'Compare primary and secondary school SATs and GCSE performance across England', description: 'Compare primary and secondary school SATs and GCSE performance across England',
url: 'https://schoolcompare.co.uk', url: 'https://schoolcompare.co.uk',
siteName: 'SchoolCompare', siteName: 'schoolcompare',
}, },
twitter: { twitter: {
// summary_large_image now that there is an image worth showing. // summary_large_image now that there is an image worth showing.
card: 'summary_large_image', card: 'summary_large_image',
title: 'SchoolCompare | Compare School Performance', title: 'schoolcompare | Compare School Performance',
description: 'Compare primary and secondary school SATs and GCSE performance across England', description: 'Compare primary and secondary school SATs and GCSE performance across England',
}, },
}; };
@@ -80,12 +80,13 @@ export default function RootLayout({
}>) { }>) {
return ( return (
// The font variable classes must sit on <html>, not <body>. globals.css // The font variable classes must sit on <html>, not <body>. globals.css
// declares --font-display/--font-ui on :root as var(--font-schibsted), // declares --font-display on :root as var(--font-manrope) and --font-ui as
// and a custom property's var() references resolve on the element that // var(--font-inter), and a custom property's var() references resolve on
// declares it. With the classes on <body>, --font-schibsted was undefined // the element that declares it. With the classes on <body>, those two are
// at :root, so --font-display computed to the guaranteed-invalid value and // undefined at :root, so --font-display computes to the guaranteed-invalid
// every font-family that referenced it silently fell back — the whole site // value and every font-family referencing it silently falls back — which
// rendered in Times. // once shipped the whole site in Times. Nothing throws and no unit test
// fails, so the e2e typeface assertion is the only guard.
<html lang="en" className={`${manrope.variable} ${inter.variable}`}> <html lang="en" className={`${manrope.variable} ${inter.variable}`}>
<head> <head>
<link rel="preconnect" href="https://analytics.schoolcompare.co.uk" /> <link rel="preconnect" href="https://analytics.schoolcompare.co.uk" />
+1 -1
View File
@@ -98,7 +98,7 @@ export async function generateMetadata({ params }: SchoolPageProps): Promise<Met
description, description,
type: 'website', type: 'website',
url: `https://schoolcompare.co.uk${canonicalPath}`, url: `https://schoolcompare.co.uk${canonicalPath}`,
siteName: 'SchoolCompare', siteName: 'schoolcompare',
}, },
twitter: { twitter: {
card: 'summary', card: 'summary',
+3 -3
View File
@@ -224,11 +224,11 @@ export function ComparisonView({
const url = window.location.href; const url = window.location.href;
const count = selectedSchools.length; const count = selectedSchools.length;
const shareData = { const shareData = {
title: 'School comparison · SchoolCompare', title: 'School comparison · schoolcompare',
text: text:
count > 0 count > 0
? `Comparing ${count} school${count === 1 ? '' : 's'} on SchoolCompare` ? `Comparing ${count} school${count === 1 ? '' : 's'} on schoolcompare`
: 'SchoolCompare', : 'schoolcompare',
url, url,
}; };
if ( if (
+1 -1
View File
@@ -20,7 +20,7 @@ export function EditorialSection({ totalSchools, localAuthorityCount }: Editoria
and demographics each in its own table, each with its own jargon. and demographics each in its own table, each with its own jargon.
</p> </p>
<p> <p>
SchoolCompare brings it all into one place. Every school page shows performance against the national schoolcompare brings it all into one place. Every school page shows performance against the national
average, explains what the numbers mean, and lets you shortlist schools side by side. Built for average, explains what the numbers mean, and lets you shortlist schools side by side. Built for
parents, governors, journalists, and anyone who wants to understand a school without reading a parents, governors, journalists, and anyone who wants to understand a school without reading a
full inspection report. full inspection report.
+1 -1
View File
@@ -87,7 +87,7 @@ export function Footer() {
<div className={styles.bottom}> <div className={styles.bottom}>
<p className={styles.copyright}> <p className={styles.copyright}>
© {currentYear} SchoolCompare.co.uk © {currentYear} schoolcompare.co.uk
</p> </p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -100,7 +100,7 @@ export function Navigation() {
<> <>
<header className={styles.header}> <header className={styles.header}>
<div className={styles.container}> <div className={styles.container}>
<Link href="/" className={styles.logo} aria-label="SchoolCompare home"> <Link href="/" className={styles.logo} aria-label="schoolcompare home">
{/* {/*
LogoMark's defaults are already correct for this ground: the pin LogoMark's defaults are already correct for this ground: the pin
takes var(--brand) and the leaf is knocked out in var(--bg-card), takes var(--brand) and the leaf is knocked out in var(--bg-card),
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "SchoolCompare", "name": "schoolcompare",
"short_name": "SchoolCompare", "short_name": "schoolcompare",
"description": "Compare primary and secondary school performance across England", "description": "Compare primary and secondary school performance across England",
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",