feat(design): adopt the Cohort identity — new palette, type, mark and dark theme
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m6s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 12s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 3m13s

Implements the direction agreed from the identity board: Route C ("Cohort")
with the paper ground from C1, the Schibsted Grotesk / Literata pairing from
C2, and the iris accent from C3. Dark theme is in scope from the start rather
than retrofitted.

The audit found three things wrong beyond taste:

* No brand asset set. og:image was absent entirely, so every link shared into
  a class WhatsApp group rendered as a bare grey card. apple-touch-icon pointed
  at an SVG, which iOS ignores, and the manifest shipped no PNGs, so Android
  installs had no icon. The header mark and the favicon had also drifted into
  two different logos.
* No colour discipline. --primary and --trend-down were the same coral, so the
  main CTA and "below average" shared a hue. 58 distinct hex values were spread
  across component CSS, and the chart palette was still Chart.js's stock demo
  colours.
* A dark theme that was declared but never built — themeColor announced a dark
  variant with no dark styling behind it.

What changed:

Colour now has exactly three jobs that never borrow each other's hues: brand
(iris) for interactive and identity, status (teal/amber) for above/below a
comparison point, and phase for categories. Teal/amber rather than green/red
keeps the above/below signal readable for every form of colour blindness.
Every chromatic literal in component CSS is now a token, and the JS-painted
surfaces (Chart.js, Leaflet) read the tokens through lib/theme so they follow
the theme instead of ignoring it.

The mark is the five-bar cohort spread — the same object as the distribution
strip inside a school row, built from opacity steps so it inverts cleanly.
components/Logo.tsx is the single source; the favicon, apple-icon and share
card all derive from its geometry.

globals.css drops 123 dead global classes left over from the vanilla-JS app
(only the btn family, .skip-link and .main were still referenced), along with
the noise overlay. It also gains prefers-reduced-motion support, which was
missing entirely, and a type scale so the 54 ad-hoc font sizes have somewhere
to converge.

Verified: tsc clean, 159 unit tests pass, production build succeeds and
prerenders /icon.svg, /apple-icon and /opengraph-image. Three e2e journeys
added for the asset set, the themeColor/background match, and the dark theme
actually repainting — all silent failures that nothing on the page reveals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tudor
2026-08-06 12:13:04 +01:00
co-authored by Claude Opus 5
parent c298a29b61
commit 8ab0ac0a04
56 changed files with 2019 additions and 3073 deletions
+76
View File
@@ -626,3 +626,79 @@ test('sticky section nav jumps to server-rendered sections', async ({ page }) =>
const after = await page.evaluate(() => window.scrollY); const after = await page.evaluate(() => window.scrollY);
expect(after).toBeGreaterThan(before); expect(after).toBeGreaterThan(before);
}); });
/**
* Brand surface. The identity work replaced a favicon-only asset set that
* broke iOS home-screen icons, Android PWA installs, and every link preview.
* These are silent failures — nothing on the page looks wrong — so they need
* a gate.
*/
test('the brand asset set is complete and served', async ({ page }) => {
const response = await page.goto('/');
expect(response?.ok()).toBe(true);
// The share card: without it, every link pasted into a chat renders bare.
const ogImage = page.locator('meta[property="og:image"]');
await expect(ogImage).toHaveCount(1);
const ogUrl = await ogImage.getAttribute('content');
expect(ogUrl).toBeTruthy();
const og = await page.request.get(ogUrl!);
expect(og.ok()).toBe(true);
expect(og.headers()['content-type']).toContain('image/png');
// iOS ignores SVG touch icons, so this must be a real raster.
const apple = await page.request.get('/apple-icon');
expect(apple.ok()).toBe(true);
expect(apple.headers()['content-type']).toContain('image/png');
// Android needs a maskable PNG or the install prompt has no icon.
for (const icon of ['/icon-192.png', '/icon-512.png', '/icon-maskable-512.png']) {
const res = await page.request.get(icon);
expect(res.ok(), `${icon} should be served`).toBe(true);
}
});
test('theme colour matches the page background in both themes', async ({ browser }) => {
// A mismatch here paints a stripe of the wrong colour across the top of the
// screen on mobile. Previously the dark themeColor was declared with no dark
// styling behind it at all.
for (const colorScheme of ['light', 'dark'] as const) {
const context = await browser.newContext({ colorScheme });
const page = await context.newPage();
await page.goto('/');
const declared = await page
.locator(`meta[name="theme-color"][media*="${colorScheme}"]`)
.getAttribute('content');
expect(declared, `theme-color declared for ${colorScheme}`).toBeTruthy();
const painted = await page.evaluate(() =>
getComputedStyle(document.documentElement).getPropertyValue('--bg-primary').trim()
);
expect(painted.toLowerCase()).toBe(declared!.toLowerCase());
await context.close();
}
});
test('the dark theme actually repaints the page', async ({ browser }) => {
// Guards the token layer: if a component reintroduces a hardcoded colour,
// the surface below stays light while everything around it flips.
const read = async (colorScheme: 'light' | 'dark') => {
const context = await browser.newContext({ colorScheme });
const page = await context.newPage();
await page.goto('/');
await expect(page.locator('h1').first()).toBeVisible({ timeout: 15_000 });
const values = await page.evaluate(() => {
const s = getComputedStyle(document.body);
return { bg: s.backgroundColor, fg: s.color };
});
await context.close();
return values;
};
const light = await read('light');
const dark = await read('dark');
expect(dark.bg).not.toBe(light.bg);
expect(dark.fg).not.toBe(light.fg);
});
+46
View File
@@ -0,0 +1,46 @@
import { ImageResponse } from 'next/og';
import { BARS } from '@/components/Logo';
// iOS ignores SVG touch icons — it needs a real raster, on an opaque ground,
// with no transparency (it composites its own rounded mask). Previously this
// pointed at favicon.svg, so add-to-home-screen produced a blank tile.
export const size = { width: 180, height: 180 };
export const contentType = 'image/png';
const COHORT = ['rgba(22,32,42,0.22)', 'rgba(22,32,42,0.38)', 'rgba(22,32,42,0.55)', '', 'rgba(22,32,42,0.22)'];
const SCALE = 3.6; // 40 → 144, leaving a 18px margin inside 180
export default function AppleIcon() {
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#f2f1ed',
}}
>
<div style={{ position: 'relative', width: 40 * SCALE, height: 40 * SCALE, display: 'flex' }}>
{BARS.map(([x, y, w, h, isSchool], i) => (
<div
key={x}
style={{
position: 'absolute',
left: x * SCALE,
top: y * SCALE,
width: w * SCALE,
height: h * SCALE,
borderRadius: 3,
background: isSchool ? '#584a9b' : COHORT[i],
}}
/>
))}
</div>
</div>
),
size
);
}
+285 -1926
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="26" width="6" height="12" rx="1" fill="#16202a" fill-opacity="0.22"/>
<rect x="10" y="16" width="6" height="22" rx="1" fill="#16202a" fill-opacity="0.38"/>
<rect x="18" y="8" width="6" height="30" rx="1" fill="#16202a" fill-opacity="0.55"/>
<rect x="26" y="14" width="6" height="24" rx="1" fill="#584a9b"/>
<rect x="34" y="28" width="4" height="10" rx="1" fill="#16202a" fill-opacity="0.22"/>
</svg>

After

Width:  |  Height:  |  Size: 521 B

+23 -16
View File
@@ -1,5 +1,5 @@
import type { Metadata, Viewport } from 'next'; import type { Metadata, Viewport } from 'next';
import { DM_Sans, Playfair_Display } from 'next/font/google'; import { Schibsted_Grotesk, Literata } from 'next/font/google';
import Script from 'next/script'; import Script from 'next/script';
import { Navigation } from '@/components/Navigation'; import { Navigation } from '@/components/Navigation';
import { Footer } from '@/components/Footer'; import { Footer } from '@/components/Footer';
@@ -7,17 +7,23 @@ import { ComparisonToast } from '@/components/ComparisonToast';
import { ComparisonProvider } from '@/context/ComparisonProvider'; import { ComparisonProvider } from '@/context/ComparisonProvider';
import './globals.css'; import './globals.css';
const dmSans = DM_Sans({ // Schibsted Grotesk carries the whole interface — headings, labels, and every
// figure. It was drawn for news brands, so its tabular numerals hold a column
// properly, which matters on a page that is mostly percentages.
const schibsted = Schibsted_Grotesk({
subsets: ['latin'], subsets: ['latin'],
weight: ['400', '500', '600', '700'], weight: ['400', '500', '600', '700'],
variable: '--font-dm-sans', variable: '--font-schibsted',
display: 'swap', display: 'swap',
}); });
const playfairDisplay = Playfair_Display({ // Literata is for prose only — the admissions guide, methodology notes, the
// editorial sections. It never touches a number or a control.
const literata = Literata({
subsets: ['latin'], subsets: ['latin'],
weight: ['600', '700'], weight: ['400', '600'],
variable: '--font-playfair', style: ['normal', 'italic'],
variable: '--font-literata',
display: 'swap', display: 'swap',
}); });
@@ -27,9 +33,11 @@ export const viewport: Viewport = {
// viewport-fit=cover lets us paint behind the notch / Dynamic Island so // viewport-fit=cover lets us paint behind the notch / Dynamic Island so
// env(safe-area-inset-*) values resolve to real numbers on iPhone. // env(safe-area-inset-*) values resolve to real numbers on iPhone.
viewportFit: 'cover', viewportFit: 'cover',
// These must match --bg-primary in globals.css for each theme, or the
// browser chrome and the page disagree at the top edge of the screen.
themeColor: [ themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#faf7f2' }, { media: '(prefers-color-scheme: light)', color: '#f2f1ed' },
{ media: '(prefers-color-scheme: dark)', color: '#1a1612' }, { media: '(prefers-color-scheme: dark)', color: '#10151b' },
], ],
}; };
@@ -47,11 +55,10 @@ export const metadata: Metadata = {
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',
icons: { // No `icons` key on purpose: setting it here would override the file
icon: '/favicon.svg', // conventions. app/icon.svg and app/apple-icon.tsx are the source, and
shortcut: '/favicon.svg', // app/opengraph-image.tsx supplies og:image and twitter:image.
apple: '/favicon.svg', metadataBase: new URL('https://schoolcompare.co.uk'),
},
openGraph: { openGraph: {
type: 'website', type: 'website',
title: 'SchoolCompare | Compare School Performance', title: 'SchoolCompare | Compare School Performance',
@@ -60,7 +67,8 @@ export const metadata: Metadata = {
siteName: 'SchoolCompare', siteName: 'SchoolCompare',
}, },
twitter: { twitter: {
card: 'summary', // summary_large_image now that there is an image worth showing.
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',
}, },
@@ -94,8 +102,7 @@ export default function RootLayout({
strategy="afterInteractive" strategy="afterInteractive"
/> />
</head> </head>
<body className={`${dmSans.variable} ${playfairDisplay.variable}`}> <body className={`${schibsted.variable} ${literata.variable}`}>
<div className="noise-overlay" />
<ComparisonProvider> <ComparisonProvider>
<a href="#main-content" className="skip-link">Skip to main content</a> <a href="#main-content" className="skip-link">Skip to main content</a>
<Navigation /> <Navigation />
+132
View File
@@ -0,0 +1,132 @@
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { ImageResponse } from 'next/og';
import { BARS } from '@/components/Logo';
/**
* The site had no og:image at all, so every link pasted into a class WhatsApp
* group or a Slack channel rendered as a bare grey text card — on the highest
* intent channel this product has.
*
* The card is the mark at poster scale: the cohort as a wide distribution with
* one school lit up. It states the proposition rather than decorating it.
*/
export const alt = 'SchoolCompare — compare every school in England against the national picture';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
const PAPER = '#f2f1ed';
const INK = '#16202a';
const MUTED = '#5c6570';
const BRAND = '#584a9b';
// A plausible national distribution, drawn once at poster scale. The lit bar
// is the same position the mark uses.
const SPREAD = [8, 12, 17, 24, 33, 44, 57, 70, 82, 92, 98, 100, 96, 88, 77, 65, 53, 41, 31, 22, 15, 10, 7, 5];
// Right of the peak: score runs left to right, so the lit school reads as
// above the national average. At index 9 it sat on the low side, which quietly
// said the opposite.
const LIT = 14;
async function font(file: string) {
return readFile(join(process.cwd(), 'assets', file));
}
export default async function OpengraphImage() {
const [regular, bold] = await Promise.all([
font('SchibstedGrotesk-Regular.ttf'),
font('SchibstedGrotesk-Bold.ttf'),
]);
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
background: PAPER,
padding: '58px 72px 52px',
fontFamily: 'Schibsted',
}}
>
{/* Lockup */}
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
<div style={{ position: 'relative', width: 40, height: 40, display: 'flex' }}>
{BARS.map(([x, y, w, h, isSchool], i) => (
<div
key={x}
style={{
position: 'absolute',
left: x,
top: y,
width: w,
height: h,
borderRadius: 1.5,
background: isSchool ? BRAND : INK,
opacity: isSchool ? 1 : [0.22, 0.38, 0.55, 1, 0.22][i],
}}
/>
))}
</div>
<div style={{ display: 'flex', fontSize: 29, fontWeight: 700, letterSpacing: '-0.028em' }}>
<span style={{ color: INK }}>School</span>
<span style={{ color: BRAND }}>Compare</span>
</div>
</div>
{/* Proposition. Sized so the headline holds two lines and the subhead
one — an orphaned word reads as an accident at this size. */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
<div
style={{
display: 'flex',
fontSize: 66,
fontWeight: 700,
letterSpacing: '-0.036em',
lineHeight: 1.03,
color: INK,
maxWidth: 1010,
}}
>
A score means nothing until you see the spread.
</div>
<div style={{ display: 'flex', fontSize: 26, color: MUTED, letterSpacing: '-0.005em' }}>
24,000+ schools in England · SATs, GCSEs, Ofsted and admissions
</div>
</div>
{/* The device, at poster scale. Same object as the mark above. */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 7, height: 120 }}>
{SPREAD.map((h, i) => (
<div
key={i}
style={{
flex: 1,
height: `${h}%`,
borderRadius: 2,
background: i === LIT ? BRAND : INK,
opacity: i === LIT ? 1 : 0.15,
}}
/>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 18, color: MUTED, letterSpacing: '0.09em' }}>
<span>NATIONAL SPREAD</span>
<span style={{ letterSpacing: '-0.005em' }}>schoolcompare.co.uk</span>
</div>
</div>
</div>
),
{
...size,
fonts: [
{ name: 'Schibsted', data: regular, weight: 400, style: 'normal' },
{ name: 'Schibsted', data: bold, weight: 700, style: 'normal' },
],
}
);
}
Binary file not shown.
Binary file not shown.
+14 -6
View File
@@ -9,6 +9,7 @@
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import { ChartOptions } from 'chart.js'; import { ChartOptions } from 'chart.js';
import '@/lib/chartSetup'; import '@/lib/chartSetup';
import { useThemeTokens, alpha } from '@/lib/theme';
import { formatAcademicYear } from '@/lib/utils'; import { formatAcademicYear } from '@/lib/utils';
import { fillAcademicYears } from '@/lib/compareChartData'; import { fillAcademicYears } from '@/lib/compareChartData';
import type { SchoolAdmissions } from '@/lib/types'; import type { SchoolAdmissions } from '@/lib/types';
@@ -38,6 +39,11 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
const yMin = Math.max(0, Math.floor((lo - padded) / 5) * 5); const yMin = Math.max(0, Math.floor((lo - padded) / 5) * 5);
const yMax = Math.min(100, Math.ceil((hi + padded) / 5) * 5); const yMax = Math.min(100, Math.ceil((hi + padded) / 5) * 5);
// Canvas can't read var(), so pull the resolved tokens through the bridge.
const [cBrand, cGrid, cInverse, cInverseText, cCard] = useThemeTokens(
'--brand', '--chart-grid', '--surface-inverse', '--text-inverse', '--bg-card'
);
const options: ChartOptions<'line'> = { const options: ChartOptions<'line'> = {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
@@ -49,7 +55,9 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
legend: { display: false }, legend: { display: false },
title: { display: false }, title: { display: false },
tooltip: { tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)', backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: 10, padding: 10,
titleFont: { size: 12 }, titleFont: { size: 12 },
bodyFont: { size: 12 }, bodyFont: { size: 12 },
@@ -62,7 +70,7 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
y: { y: {
min: yMin, min: yMin,
max: yMax, max: yMax,
grid: { color: 'rgba(0,0,0,0.05)' }, grid: { color: cGrid },
ticks: { font: { size: 11 }, maxTicksLimit: 5, callback: (v) => `${v}%` }, ticks: { font: { size: 11 }, maxTicksLimit: 5, callback: (v) => `${v}%` },
}, },
x: { x: {
@@ -80,14 +88,14 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
data: values, data: values,
clip: false as const, clip: false as const,
spanGaps: false, spanGaps: false,
borderColor: '#e07256', borderColor: cBrand,
backgroundColor: 'rgba(224,114,86,0.10)', backgroundColor: alpha('--brand', 0.10),
borderWidth: 2.5, borderWidth: 2.5,
tension: 0.3, tension: 0.3,
fill: true, fill: true,
pointRadius: values.map((_, i) => (i === lastIdx ? 5 : 3)), pointRadius: values.map((_, i) => (i === lastIdx ? 5 : 3)),
pointBackgroundColor: '#e07256', pointBackgroundColor: cBrand,
pointBorderColor: '#fff', pointBorderColor: cCard,
pointBorderWidth: values.map((_, i) => (i === lastIdx ? 2 : 0)), pointBorderWidth: values.map((_, i) => (i === lastIdx ? 2 : 0)),
pointHoverRadius: 6, pointHoverRadius: 6,
}, },
+79 -74
View File
@@ -27,7 +27,7 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-bottom: 0.85rem; margin-bottom: 0.85rem;
padding-left: 0.85rem; padding-left: 0.85rem;
} }
@@ -39,7 +39,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.1rem; gap: 0.1rem;
border-left: 2px solid var(--border-color, #e5dfd5); border-left: 2px solid var(--border);
} }
.navLink { .navLink {
@@ -51,33 +51,33 @@
border-left: 2px solid transparent; border-left: 2px solid transparent;
font-size: 0.88rem; font-size: 0.88rem;
font-weight: 500; font-weight: 500;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease; transition: color 0.15s ease, border-color 0.15s ease;
} }
.navLink:hover { .navLink:hover {
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.navLinkActive { .navLinkActive {
color: var(--accent-teal, #2d7d7d); color: var(--brand);
font-weight: 700; font-weight: 700;
border-left-color: var(--accent-teal, #2d7d7d); border-left-color: var(--brand);
} }
.navDot { .navDot {
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: var(--border-color, #e5dfd5); background: var(--border);
flex-shrink: 0; flex-shrink: 0;
transition: background 0.15s ease; transition: background 0.15s ease;
} }
.navLinkActive .navDot { .navLinkActive .navDot {
background: var(--accent-teal, #2d7d7d); background: var(--brand);
} }
/* ─── Hero ───────────────────────────────────────────── */ /* ─── Hero ───────────────────────────────────────────── */
@@ -95,8 +95,8 @@
font-weight: 600; font-weight: 600;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--accent-teal, #2d7d7d); color: var(--brand);
background: rgba(45, 125, 125, 0.1); background: rgba(var(--brand-rgb), 0.1);
padding: 0.3rem 0.7rem; padding: 0.3rem 0.7rem;
border-radius: 999px; border-radius: 999px;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -107,22 +107,22 @@
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
} }
.heroTitle { .heroTitle {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 2.75rem; font-size: 2.75rem;
font-weight: 700; font-weight: 700;
line-height: 1.1; line-height: 1.1;
letter-spacing: -0.015em; letter-spacing: -0.015em;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.85rem; margin-bottom: 0.85rem;
} }
.heroSub { .heroSub {
font-size: 1.05rem; font-size: 1.05rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
max-width: 640px; max-width: 640px;
margin: 0 auto; margin: 0 auto;
line-height: 1.55; line-height: 1.55;
@@ -132,7 +132,7 @@
.countdownSection { .countdownSection {
padding: 0 0 2.5rem; padding: 0 0 2.5rem;
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
@@ -145,7 +145,7 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.countdownRail { .countdownRail {
@@ -155,11 +155,11 @@
} }
.chip { .chip {
background: var(--bg-card, #fff); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
padding: 1rem 1.1rem 0.9rem; padding: 1rem 1.1rem 0.9rem;
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.2rem; gap: 0.2rem;
@@ -177,12 +177,12 @@
border-radius: 12px 12px 0 0; border-radius: 12px 12px 0 0;
} }
.chipDeadline::before { background: var(--accent-coral, #e07256); } .chipDeadline::before { background: var(--brand); }
.chipOffer::before { background: var(--accent-teal, #2d7d7d); } .chipOffer::before { background: var(--status-above); }
.chipUrgent { .chipUrgent {
border-color: rgba(224, 114, 86, 0.4); border-color: rgba(var(--brand-rgb), 0.4);
background: rgba(224, 114, 86, 0.04); background: rgba(var(--brand-rgb), 0.04);
} }
.chipTrack { .chipTrack {
@@ -196,8 +196,8 @@
margin-bottom: 0.15rem; margin-bottom: 0.15rem;
} }
.chipTrackDeadline { color: var(--accent-coral-dark, #b04a2e); } .chipTrackDeadline { color: var(--brand-strong); }
.chipTrackOffer { color: var(--accent-teal, #2d7d7d); } .chipTrackOffer { color: var(--status-above); }
.chipTrackDot { .chipTrackDot {
width: 5px; width: 5px;
@@ -208,7 +208,8 @@
} }
.chipDays { .chipDays {
font-family: var(--font-playfair), 'Playfair Display', serif; font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 2.6rem; font-size: 2.6rem;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
@@ -217,17 +218,17 @@
.chipDeadline .chipDays, .chipDeadline .chipDays,
.chipUrgent .chipDays { .chipUrgent .chipDays {
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
} }
.chipOffer .chipDays { .chipOffer .chipDays {
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
} }
.chipDaysUnit { .chipDaysUnit {
font-size: 0.78rem; font-size: 0.78rem;
font-weight: 500; font-weight: 500;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-left: 0.2rem; margin-left: 0.2rem;
vertical-align: bottom; vertical-align: bottom;
line-height: 2; line-height: 2;
@@ -236,14 +237,14 @@
.chipMilestone { .chipMilestone {
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
line-height: 1.25; line-height: 1.25;
margin-top: 0.1rem; margin-top: 0.1rem;
} }
.chipDate { .chipDate {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-top: 0.05rem; margin-top: 0.05rem;
} }
@@ -251,8 +252,8 @@
.track { .track {
margin-bottom: 3rem; margin-bottom: 3rem;
background: var(--bg-card, #fff); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 14px; border-radius: 14px;
overflow: hidden; overflow: hidden;
} }
@@ -263,7 +264,7 @@
justify-content: space-between; justify-content: space-between;
gap: 1.5rem; gap: 1.5rem;
padding: 1.75rem 2rem 1.5rem; padding: 1.75rem 2rem 1.5rem;
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -278,22 +279,22 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
margin-bottom: 0.35rem; margin-bottom: 0.35rem;
} }
.trackTitle { .trackTitle {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.4rem; margin-bottom: 0.4rem;
line-height: 1.2; line-height: 1.2;
} }
.trackSub { .trackSub {
font-size: 0.9rem; font-size: 0.9rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
line-height: 1.5; line-height: 1.5;
margin: 0; margin: 0;
} }
@@ -317,13 +318,13 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.trackDateVal { .trackDateVal {
font-size: 0.88rem; font-size: 0.88rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
/* ─── Timeline ───────────────────────────────────────── */ /* ─── Timeline ───────────────────────────────────────── */
@@ -356,27 +357,27 @@
width: 14px; width: 14px;
height: 14px; height: 14px;
border-radius: 50%; border-radius: 50%;
background: var(--bg-card, #fff); background: var(--bg-card);
border: 2px solid var(--border-color, #e5dfd5); border: 2px solid var(--border);
flex-shrink: 0; flex-shrink: 0;
transition: border-color 0.2s ease, background 0.2s ease; transition: border-color 0.2s ease, background 0.2s ease;
} }
.stepDeadline .stepDot { .stepDeadline .stepDot {
background: rgba(224, 114, 86, 0.15); background: rgba(var(--brand-rgb), 0.15);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
} }
.stepOffer .stepDot { .stepOffer .stepDot {
background: rgba(45, 125, 125, 0.15); background: rgba(var(--status-above-rgb), 0.15);
border-color: var(--accent-teal, #2d7d7d); border-color: var(--status-above);
} }
.stepLine { .stepLine {
width: 2px; width: 2px;
flex: 1; flex: 1;
min-height: 24px; min-height: 24px;
background: var(--border-color, #e5dfd5); background: var(--border);
margin: 4px 0; margin: 4px 0;
} }
@@ -395,27 +396,28 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.06em; letter-spacing: 0.06em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
} }
.stepDeadline .stepDate { color: var(--accent-coral-dark, #b04a2e); } .stepDeadline .stepDate { color: var(--brand-strong); }
.stepOffer .stepDate { color: var(--accent-teal, #2d7d7d); } .stepOffer .stepDate { color: var(--status-above); }
.stepTitle { .stepTitle {
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
line-height: 1.3; line-height: 1.3;
} }
.stepDeadline .stepTitle { color: var(--accent-coral-dark, #c45a3f); } .stepDeadline .stepTitle { color: var(--brand-strong); }
.stepOffer .stepTitle { color: var(--accent-teal, #2d7d7d); } .stepOffer .stepTitle { color: var(--status-above); }
.stepBody { .stepBody {
font-family: var(--font-prose);
font-size: 0.88rem; font-size: 0.88rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
line-height: 1.55; line-height: 1.55;
margin: 0; margin: 0;
} }
@@ -427,13 +429,13 @@
margin-top: 0.6rem; margin-top: 0.6rem;
font-size: 0.82rem; font-size: 0.82rem;
font-weight: 600; font-weight: 600;
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
text-decoration: none; text-decoration: none;
transition: gap 0.2s ease, color 0.2s ease; transition: gap 0.2s ease, color 0.2s ease;
} }
.stepTool:hover { .stepTool:hover {
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
gap: 0.6rem; gap: 0.6rem;
} }
@@ -455,9 +457,9 @@
display: flex; display: flex;
gap: 0.55rem; gap: 0.55rem;
padding: 0.6rem 0.75rem; padding: 0.6rem 0.75rem;
background: var(--accent-gold-bg, rgba(201, 162, 39, 0.12)); background: var(--status-below-bg);
border: 1px solid rgba(201, 162, 39, 0.28); border: 1px solid rgba(var(--status-below-rgb), 0.28);
border-left: 3px solid var(--accent-gold, #c9a227); border-left: 3px solid var(--status-below);
border-radius: 8px; border-radius: 8px;
} }
@@ -467,8 +469,8 @@
height: 1.05rem; height: 1.05rem;
margin-top: 0.1rem; margin-top: 0.1rem;
border-radius: 50%; border-radius: 50%;
background: var(--accent-gold, #c9a227); background: var(--status-below);
color: #fff; color: var(--text-inverse);
font-size: 0.72rem; font-size: 0.72rem;
font-weight: 800; font-weight: 800;
line-height: 1.05rem; line-height: 1.05rem;
@@ -482,14 +484,15 @@
.calloutTitle { .calloutTitle {
font-size: 0.82rem; font-size: 0.82rem;
font-weight: 700; font-weight: 700;
color: var(--accent-gold-text, #7a6800); color: var(--status-below);
line-height: 1.3; line-height: 1.3;
margin-bottom: 0.15rem; margin-bottom: 0.15rem;
} }
.calloutBody { .calloutBody {
font-family: var(--font-prose);
font-size: 0.82rem; font-size: 0.82rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
line-height: 1.5; line-height: 1.5;
margin: 0; margin: 0;
} }
@@ -501,10 +504,10 @@
} }
.tipsHeading { .tipsHeading {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 1.35rem; font-size: 1.35rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
} }
@@ -515,16 +518,17 @@
} }
.tipCard { .tipCard {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
padding: 1.25rem; padding: 1.25rem;
} }
.tipNumber { .tipNumber {
font-family: var(--font-playfair), 'Playfair Display', serif; font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 1.75rem; font-size: 1.75rem;
font-weight: 700; font-weight: 700;
color: #7d766b; /* 3.8:1 on cream — AA large-text for this 28px numeral */ color: var(--text-muted); /* 3.8:1 on cream — AA large-text for this 28px numeral */
line-height: 1; line-height: 1;
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
} }
@@ -532,14 +536,15 @@
.tipHeading { .tipHeading {
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.4rem; margin-bottom: 0.4rem;
line-height: 1.3; line-height: 1.3;
} }
.tipBody { .tipBody {
font-family: var(--font-prose);
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
line-height: 1.55; line-height: 1.55;
margin: 0; margin: 0;
} }
@@ -557,8 +562,8 @@
position: sticky; position: sticky;
top: 0; top: 0;
padding-top: 0; padding-top: 0;
background: var(--bg-primary, #faf8f3); background: var(--bg-primary);
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
margin: 0 -1.25rem 1rem; margin: 0 -1.25rem 1rem;
padding: 0.6rem 1.25rem; padding: 0.6rem 1.25rem;
z-index: 10; z-index: 10;
@@ -590,8 +595,8 @@
} }
.navLinkActive { .navLinkActive {
background: var(--text-primary, #1a1612); background: var(--text-primary);
color: #fff; color: var(--text-inverse);
border-left: none; border-left: none;
} }
@@ -43,7 +43,7 @@
min-height: 40px; min-height: 40px;
min-width: 0; min-width: 0;
padding: 4px 10px; padding: 4px 10px;
border: 1px solid rgba(0, 0, 0, .12); border: 1px solid var(--border);
border-radius: 999px; border-radius: 999px;
background: transparent; background: transparent;
cursor: pointer; cursor: pointer;
@@ -52,8 +52,8 @@
} }
.chip[aria-pressed="true"] { .chip[aria-pressed="true"] {
background: rgba(0, 0, 0, .06); background: var(--bg-secondary);
border-color: rgba(0, 0, 0, .35); border-color: var(--border-strong);
} }
.chipDot { .chipDot {
+14 -4
View File
@@ -15,6 +15,7 @@ import { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import { ChartOptions, ChartDataset, PointStyle } from 'chart.js'; import { ChartOptions, ChartDataset, PointStyle } from 'chart.js';
import '@/lib/chartSetup'; import '@/lib/chartSetup';
import { useSeriesColors, useThemeTokens } from '@/lib/theme';
import { buildCompareChart } from '@/lib/compareChartData'; import { buildCompareChart } from '@/lib/compareChartData';
import type { ComparisonData } from '@/lib/types'; import type { ComparisonData } from '@/lib/types';
import { import {
@@ -48,6 +49,13 @@ const POINT_STYLES: PointStyle[] = ['circle', 'triangle', 'rect', 'rectRot', 'st
export function ComparisonChart({ comparisonData, schools, metric, metricLabel, nationalByYear, isSecondary = false }: ComparisonChartProps) { export function ComparisonChart({ comparisonData, schools, metric, metricLabel, nationalByYear, isSecondary = false }: ComparisonChartProps) {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
// Canvas can't resolve var(), so the series come through the token bridge.
// Same index order as CHART_COLORS, so a school's line and its DOM swatch
// stay the same colour.
const seriesColors = useSeriesColors();
const [cRef, cGrid, cInverse, cInverseText] = useThemeTokens(
'--chart-reference', '--chart-grid', '--surface-inverse', '--text-inverse'
);
const [focusedUrn, setFocusedUrn] = useState<number | null>(null); const [focusedUrn, setFocusedUrn] = useState<number | null>(null);
// A focused school that leaves the basket must not linger. // A focused school that leaves the basket must not linger.
@@ -67,7 +75,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
const datasets: ChartDataset<'line'>[] = built.schoolDatasets.map((series) => { const datasets: ChartDataset<'line'>[] = built.schoolDatasets.map((series) => {
const school = schools[series.schoolIndex]; const school = schools[series.schoolIndex];
const color = CHART_COLORS[series.schoolIndex % CHART_COLORS.length]; const color = seriesColors[series.schoolIndex % seriesColors.length];
const dimmed = focusedUrn !== null && focusedUrn !== school.urn; const dimmed = focusedUrn !== null && focusedUrn !== school.urn;
return { return {
@@ -90,7 +98,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
datasets.push({ datasets.push({
label: built.englandDataset.label, label: built.englandDataset.label,
data: built.englandDataset.data, data: built.englandDataset.data,
borderColor: 'rgba(109, 104, 95, 0.9)', borderColor: cRef,
backgroundColor: 'transparent', backgroundColor: 'transparent',
borderWidth: 1.5, borderWidth: 1.5,
borderDash: built.englandDataset.borderDash, borderDash: built.englandDataset.borderDash,
@@ -138,7 +146,9 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
display: false, display: false,
}, },
tooltip: { tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)', backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: isMobile ? 10 : 12, padding: isMobile ? 10 : 12,
titleFont: { titleFont: {
size: isMobile ? 12 : 14, size: isMobile ? 12 : 14,
@@ -182,7 +192,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
...(isMobile && { maxTicksLimit: 5 }), ...(isMobile && { maxTicksLimit: 5 }),
}, },
grid: { grid: {
color: 'rgba(0, 0, 0, 0.05)', color: cGrid,
}, },
}, },
x: { x: {
@@ -23,11 +23,11 @@
flex-direction: column; flex-direction: column;
gap: 0; gap: 0;
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
background: var(--bg-primary, #faf7f2); background: var(--bg-primary);
color: var(--text-primary, #2c2420); color: var(--text-primary);
border-radius: 16px; border-radius: 16px;
box-shadow: 0 8px 32px rgba(44, 36, 32, 0.18), 0 2px 8px rgba(44, 36, 32, 0.08); box-shadow: 0 8px 32px rgba(var(--shadow-rgb), 0.18), 0 2px 8px rgba(var(--shadow-rgb), 0.08);
border: 1px solid var(--border-color, #e8ddd4); border: 1px solid var(--border);
min-width: 280px; min-width: 280px;
} }
@@ -37,8 +37,8 @@
justify-content: center; justify-content: center;
width: 28px; width: 28px;
height: 28px; height: 28px;
background: var(--accent-coral, #e07256); background: var(--brand);
color: white; color: var(--brand-on);
border-radius: 50%; border-radius: 50%;
font-weight: 700; font-weight: 700;
font-size: 0.9rem; font-size: 0.9rem;
@@ -62,13 +62,13 @@
gap: 0.5rem; gap: 0.5rem;
font-weight: 600; font-weight: 600;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-primary, #2c2420); color: var(--text-primary);
} }
.collapseBtn { .collapseBtn {
background: none; background: none;
border: none; border: none;
color: var(--text-muted, #8a7a72); color: var(--text-muted);
cursor: pointer; cursor: pointer;
padding: 0.25rem; padding: 0.25rem;
line-height: 1; line-height: 1;
@@ -79,7 +79,7 @@
} }
.collapseBtn:hover { .collapseBtn:hover {
color: var(--text-primary, #2c2420); color: var(--text-primary);
} }
.schoolList { .schoolList {
@@ -95,13 +95,13 @@
justify-content: space-between; justify-content: space-between;
gap: 0.5rem; gap: 0.5rem;
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: var(--radius-sm, 4px); border-radius: var(--radius-sm, 4px);
} }
.schoolName { .schoolName {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-primary, #2c2420); color: var(--text-primary);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@@ -111,7 +111,7 @@
.removeSchoolBtn { .removeSchoolBtn {
background: none; background: none;
border: none; border: none;
color: var(--text-muted, #8a7a72); color: var(--text-muted);
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
padding: 0 0.25rem; padding: 0 0.25rem;
@@ -121,7 +121,7 @@
} }
.removeSchoolBtn:hover { .removeSchoolBtn:hover {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.toastActions { .toastActions {
@@ -129,13 +129,13 @@
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
padding-top: 0.625rem; padding-top: 0.625rem;
border-top: 1px solid var(--border-color, #e8ddd4); border-top: 1px solid var(--border);
} }
.btnClearAll { .btnClearAll {
background: none; background: none;
border: none; border: none;
color: var(--text-muted, #8a7a72); color: var(--text-muted);
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
@@ -145,13 +145,13 @@
} }
.btnClearAll:hover { .btnClearAll:hover {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.btnCompare { .btnCompare {
flex: 1; flex: 1;
background: var(--accent-coral, #e07256); background: var(--brand);
color: white; color: var(--brand-on);
padding: 0.6rem 1.25rem; padding: 0.6rem 1.25rem;
border-radius: 25px; border-radius: 25px;
font-weight: 600; font-weight: 600;
@@ -164,7 +164,7 @@
.btnCompare:hover { .btnCompare:hover {
transform: translateY(-1px); transform: translateY(-1px);
background: var(--accent-coral-dark, #c9614a); background: var(--brand-strong);
} }
/* Hidden on phones — the bottom tab bar's Compare badge already /* Hidden on phones — the bottom tab bar's Compare badge already
+26 -26
View File
@@ -18,14 +18,14 @@
.header h1 { .header h1 {
font-size: 2.25rem; font-size: 2.25rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.subtitle { .subtitle {
font-size: 1rem; font-size: 1rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
margin: 0; margin: 0;
line-height: 1.6; line-height: 1.6;
max-width: 60ch; max-width: 60ch;
@@ -43,7 +43,7 @@
display: flex; display: flex;
gap: 0; gap: 0;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
width: fit-content; width: fit-content;
@@ -53,8 +53,8 @@
padding: 0.625rem 1.5rem; padding: 0.625rem 1.5rem;
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 500; font-weight: 500;
background: var(--bg-card, white); background: var(--bg-card);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
border: none; border: none;
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
@@ -62,21 +62,21 @@
} }
.phaseTab:not(:last-child) { .phaseTab:not(:last-child) {
border-right: 1px solid var(--border-color, #e5dfd5); border-right: 1px solid var(--border);
} }
.phaseTab:hover { .phaseTab:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.phaseTabActive { .phaseTabActive {
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
color: white; color: var(--brand-on);
font-weight: 600; font-weight: 600;
} }
.phaseTabActive:hover { .phaseTabActive:hover {
background: var(--accent-coral-darker, #9c3f26); background: var(--brand-stronger);
} }
/* Sticky school bar — column identity while scrolling; horizontal scroll on /* Sticky school bar — column identity while scrolling; horizontal scroll on
@@ -87,23 +87,23 @@
position: sticky; position: sticky;
top: 65px; top: 65px;
z-index: 10; z-index: 10;
background: var(--bg-primary, #faf7f2); background: var(--bg-primary);
display: flex; display: flex;
gap: 0.75rem; gap: 0.75rem;
overflow-x: auto; overflow-x: auto;
padding: 0.75rem 0; padding: 0.75rem 0;
border-bottom: 1px solid var(--border-light, #e5dfd5); border-bottom: 1px solid var(--border);
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
.schoolChip { .schoolChip {
flex: 1 1 0; flex: 1 1 0;
min-width: 180px; min-width: 180px;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-light, #e5dfd5); border: 1px solid var(--border);
border-top: 3px solid var(--accent-coral, #e07256); border-top: 3px solid var(--brand);
border-radius: 8px; border-radius: 8px;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
padding: 0.55rem 0.75rem; padding: 0.55rem 0.75rem;
display: flex; display: flex;
gap: 0.55rem; gap: 0.55rem;
@@ -126,12 +126,12 @@
font-weight: 600; font-weight: 600;
font-size: 0.92rem; font-size: 0.92rem;
line-height: 1.25; line-height: 1.25;
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-decoration: none; text-decoration: none;
} }
.chipName:hover { .chipName:hover {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
/* Full name on desktop, short name on the compact mobile pills. */ /* Full name on desktop, short name on the compact mobile pills. */
@@ -142,7 +142,7 @@
.chipMeta { .chipMeta {
display: block; display: block;
font-size: 0.78rem; font-size: 0.78rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -185,22 +185,22 @@
font-weight: 600; font-weight: 600;
letter-spacing: 0.06em; letter-spacing: 0.06em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.barCaptionCount { .barCaptionCount {
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 600; font-weight: 600;
line-height: 1.3; line-height: 1.3;
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
} }
.chipRemove { .chipRemove {
margin-left: auto; margin-left: auto;
border: none; border: none;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
color: var(--text-muted, #6d685f); color: var(--text-muted);
border-radius: 50%; border-radius: 50%;
width: 22px; width: 22px;
height: 22px; height: 22px;
@@ -212,9 +212,9 @@
.footnote { .footnote {
font-size: 0.78rem; font-size: 0.78rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-top: 2.5rem; margin-top: 2.5rem;
border-top: 1px solid var(--border-light, #e5dfd5); border-top: 1px solid var(--border);
padding-top: 1rem; padding-top: 1rem;
max-width: 75ch; max-width: 75ch;
} }
+6 -6
View File
@@ -23,16 +23,16 @@
} }
.good { .good {
background: var(--accent-teal-bg, rgba(45, 125, 125, 0.12)); background: var(--status-above-bg);
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
} }
.bad { .bad {
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12)); background: var(--brand-bg);
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.neutral { .neutral {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
+1 -1
View File
@@ -61,7 +61,7 @@
border-radius: 50%; border-radius: 50%;
transform: translateX(-50%); transform: translateX(-50%);
border: 2px solid var(--bg-card); border: 2px solid var(--bg-card);
box-shadow: 0 0 0 1px rgba(26, 22, 18, 0.08); box-shadow: 0 0 0 1px rgba(var(--shadow-rgb), 0.08);
} }
.pointLabel { .pointLabel {
+11 -11
View File
@@ -5,15 +5,15 @@
justify-content: center; justify-content: center;
padding: 4rem 2rem; padding: 4rem 2rem;
text-align: center; text-align: center;
background: var(--bg-card, white); background: var(--bg-card);
border: 2px solid var(--border-color, #e5dfd5); border: 2px solid var(--border);
border-radius: 16px; border-radius: 16px;
min-height: 400px; min-height: 400px;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
} }
.icon { .icon {
color: var(--text-muted, #8a847a); color: var(--text-muted);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
opacity: 0.7; opacity: 0.7;
} }
@@ -22,14 +22,14 @@
margin: 0 0 0.75rem 0; margin: 0 0 0.75rem 0;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.message { .message {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
font-size: 1rem; font-size: 1rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
max-width: 500px; max-width: 500px;
line-height: 1.6; line-height: 1.6;
} }
@@ -38,8 +38,8 @@
padding: 0.875rem 2rem; padding: 0.875rem 2rem;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
background: var(--accent-coral, #e07256); background: var(--brand);
color: white; color: var(--brand-on);
border: none; border: none;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
@@ -47,9 +47,9 @@
} }
.button:hover { .button:hover {
background: var(--accent-coral-dark, #c45a3f); background: var(--brand-strong);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3); box-shadow: 0 4px 12px rgba(var(--brand-rgb), 0.3);
} }
.button:active { .button:active {
+47 -47
View File
@@ -1,10 +1,10 @@
.filterBar { .filterBar {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
padding: 1.5rem; padding: 1.5rem;
margin-bottom: 2rem; margin-bottom: 2rem;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }
@@ -17,9 +17,9 @@
padding: 2.5rem; padding: 2.5rem;
max-width: 800px; max-width: 800px;
margin: 0 auto 3rem auto; margin: 0 auto 3rem auto;
box-shadow: 0 8px 24px rgba(26, 22, 18, 0.08); box-shadow: 0 8px 24px rgba(var(--shadow-rgb), 0.08);
border-width: 2px; border-width: 2px;
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
} }
.heroMode .omniInput { .heroMode .omniInput {
@@ -39,12 +39,12 @@
.searchHint { .searchHint {
margin: 0.875rem 0 0; margin: 0.875rem 0 0;
font-size: 0.95rem; font-size: 0.95rem;
color: var(--text-secondary, #5a554d); color: var(--text-secondary);
text-align: center; text-align: center;
} }
.searchHint strong { .searchHint strong {
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-weight: 600; font-weight: 600;
} }
@@ -68,8 +68,8 @@
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding: 0.625rem 1.375rem; padding: 0.625rem 1.375rem;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
color: #fff; color: var(--brand-on);
border: none; border: none;
border-radius: 999px; border-radius: 999px;
font-size: 0.9375rem; font-size: 0.9375rem;
@@ -80,7 +80,7 @@
} }
.nearMeBtn:hover:not(:disabled) { .nearMeBtn:hover:not(:disabled) {
background: #235f5f; background: var(--status-above);
transform: translateY(-1px); transform: translateY(-1px);
} }
@@ -93,8 +93,8 @@
display: inline-block; display: inline-block;
width: 14px; width: 14px;
height: 14px; height: 14px;
border: 2px solid rgba(255, 255, 255, 0.35); border: 2px solid rgba(var(--text-inverse-rgb), 0.35);
border-top-color: #fff; border-top-color: var(--brand-on);
border-radius: 50%; border-radius: 50%;
animation: nearMeSpin 0.7s linear infinite; animation: nearMeSpin 0.7s linear infinite;
flex-shrink: 0; flex-shrink: 0;
@@ -108,7 +108,7 @@
.geoError { .geoError {
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
margin: 0; margin: 0;
max-width: 340px; max-width: 340px;
text-align: center; text-align: center;
@@ -134,21 +134,21 @@
flex: 1; flex: 1;
padding: 0.875rem 1.25rem; padding: 0.875rem 1.25rem;
font-size: 1.05rem; font-size: 1.05rem;
border: 2px solid var(--border-color, #e5dfd5); border: 2px solid var(--border);
border-radius: 8px; border-radius: 8px;
outline: none; outline: none;
transition: all 0.2s ease; transition: all 0.2s ease;
background: var(--bg-card, white); background: var(--bg-card);
font-family: inherit; font-family: inherit;
} }
.omniInput:focus { .omniInput:focus {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: 0 0 0 3px var(--accent-coral-bg); box-shadow: 0 0 0 3px var(--brand-bg);
} }
.omniInput::placeholder { .omniInput::placeholder {
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.searchButton { .searchButton {
@@ -161,9 +161,9 @@
.spinner { .spinner {
width: 20px; width: 20px;
height: 20px; height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3); border: 2px solid rgba(var(--text-inverse-rgb), 0.3);
border-radius: 50%; border-radius: 50%;
border-top-color: white; border-top-color: var(--brand-on);
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
@@ -177,7 +177,7 @@
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 0.625rem; margin-top: 0.625rem;
padding-top: 0.625rem; padding-top: 0.625rem;
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
} }
.filterSelect { .filterSelect {
@@ -189,26 +189,26 @@
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
font-family: inherit; font-family: inherit;
border: 1.5px solid var(--border-color, #e5dfd5); border: 1.5px solid var(--border);
border-radius: 8px; border-radius: 8px;
background-color: var(--bg-card, white); background-color: var(--bg-card);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238a847a' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238a847a' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0.875rem center; background-position: right 0.875rem center;
background-size: 10px 6px; background-size: 10px 6px;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
color: var(--text-primary, #1a1612); color: var(--text-primary);
transition: border-color 0.15s ease, box-shadow 0.15s ease; transition: border-color 0.15s ease, box-shadow 0.15s ease;
} }
.filterSelect:hover { .filterSelect:hover {
border-color: var(--text-muted, #8a847a); border-color: var(--text-muted);
} }
.filterSelect:focus { .filterSelect:focus {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.12); box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12);
} }
.clearButton { .clearButton {
@@ -263,7 +263,7 @@
.radiusLabel { .radiusLabel {
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 500; font-weight: 500;
color: var(--text-secondary, #5a554d); color: var(--text-secondary);
white-space: nowrap; white-space: nowrap;
} }
@@ -275,7 +275,7 @@
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 0.875rem; margin-top: 0.875rem;
padding-top: 0.875rem; padding-top: 0.875rem;
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
} }
.controlsRow .advancedToggle { .controlsRow .advancedToggle {
@@ -297,14 +297,14 @@
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 500; font-weight: 500;
font-family: inherit; font-family: inherit;
border: 1.5px solid var(--border-color, #e5dfd5); border: 1.5px solid var(--border);
border-radius: 999px; border-radius: 999px;
background-color: var(--bg-card, white); background-color: var(--bg-card);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238a847a' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238a847a' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 0.65rem center; background-position: right 0.65rem center;
background-size: 10px 6px; background-size: 10px 6px;
color: var(--text-primary, #1a1612); color: var(--text-primary);
cursor: pointer; cursor: pointer;
outline: none; outline: none;
transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
@@ -312,13 +312,13 @@
} }
.controlSelect:hover { .controlSelect:hover {
border-color: var(--text-muted, #8a847a); border-color: var(--text-muted);
background-color: var(--bg-secondary, #f8f4ef); background-color: var(--bg-secondary);
} }
.controlSelect:focus { .controlSelect:focus {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.12); box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12);
} }
/* ── Advanced filters toggle ─────────────────────────── */ /* ── Advanced filters toggle ─────────────────────────── */
@@ -328,12 +328,12 @@
align-items: center; align-items: center;
gap: 0.375rem; gap: 0.375rem;
background: none; background: none;
border: 1.5px solid var(--border-color, #e5dfd5); border: 1.5px solid var(--border);
border-radius: 999px; border-radius: 999px;
padding: 0.4rem 0.875rem; padding: 0.4rem 0.875rem;
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 500; font-weight: 500;
color: var(--text-secondary, #5a554d); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
font-family: inherit; font-family: inherit;
transition: all 0.15s ease; transition: all 0.15s ease;
@@ -341,24 +341,24 @@
} }
.advancedToggle:hover { .advancedToggle:hover {
border-color: var(--text-muted, #8a847a); border-color: var(--text-muted);
background-color: var(--bg-secondary, #f8f4ef); background-color: var(--bg-secondary);
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
/* When filters are applied, promote the toggle to a coral pill so users /* When filters are applied, promote the toggle to a coral pill so users
can see at a glance that the result list is being narrowed. */ can see at a glance that the result list is being narrowed. */
.advancedToggleActive { .advancedToggleActive {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12)); background: var(--brand-bg);
color: var(--accent-coral, #e07256); color: var(--brand);
font-weight: 600; font-weight: 600;
} }
.advancedToggleActive:hover { .advancedToggleActive:hover {
border-color: var(--accent-coral-dark, #c45a3f); border-color: var(--brand-strong);
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.18)); background: var(--brand-bg);
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
} }
.chevronDown, .chevronDown,
+13 -13
View File
@@ -1,6 +1,6 @@
.footer { .footer {
background: var(--accent-navy, #2c3e50); background: var(--text-primary);
color: var(--bg-secondary, #f3ede4); color: var(--bg-secondary);
margin-top: auto; margin-top: auto;
} }
@@ -27,22 +27,22 @@
margin: 0; margin: 0;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 700; font-weight: 700;
color: var(--bg-primary, #faf7f2); color: var(--bg-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.description { .description {
margin: 0; margin: 0;
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.6; line-height: 1.6;
color: rgba(250, 247, 242, 0.7); color: rgba(var(--text-inverse-rgb), 0.7);
} }
.sectionTitle { .sectionTitle {
margin: 0; margin: 0;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
color: var(--accent-gold, #c9a227); color: var(--status-below);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
@@ -58,24 +58,24 @@
.link { .link {
font-size: 0.875rem; font-size: 0.875rem;
color: rgba(250, 247, 242, 0.7); color: rgba(var(--text-inverse-rgb), 0.7);
text-decoration: none; text-decoration: none;
transition: color 0.2s ease; transition: color 0.2s ease;
} }
.link:hover { .link:hover {
color: var(--accent-gold, #c9a227); color: var(--status-below);
} }
.linkDisabled { .linkDisabled {
font-size: 0.875rem; font-size: 0.875rem;
color: rgba(250, 247, 242, 0.4); color: rgba(var(--text-inverse-rgb), 0.4);
cursor: not-allowed; cursor: not-allowed;
} }
.bottom { .bottom {
padding-top: 2rem; padding-top: 2rem;
border-top: 1px solid rgba(250, 247, 242, 0.15); border-top: 1px solid rgba(var(--text-inverse-rgb), 0.15);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
@@ -85,15 +85,15 @@
.disclaimer { .disclaimer {
margin: 0; margin: 0;
font-size: 0.875rem; font-size: 0.875rem;
color: rgba(250, 247, 242, 0.6); color: rgba(var(--text-inverse-rgb), 0.6);
} }
.disclaimer .link { .disclaimer .link {
color: var(--accent-coral, #e07256); color: var(--brand);
} }
.disclaimer .link:hover { .disclaimer .link:hover {
color: var(--accent-gold, #c9a227); color: var(--status-below);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
+163 -154
View File
@@ -16,8 +16,8 @@
font-weight: 600; font-weight: 600;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--accent-teal, #2d7d7d); color: var(--brand);
background: rgba(45, 125, 125, 0.1); background: rgba(var(--brand-rgb), 0.1);
padding: 0.3rem 0.7rem; padding: 0.3rem 0.7rem;
border-radius: 999px; border-radius: 999px;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -28,37 +28,37 @@
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
} }
.heroTitle { .heroTitle {
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.85rem; margin-bottom: 0.85rem;
line-height: 1.08; line-height: 1.08;
letter-spacing: -0.015em; letter-spacing: -0.015em;
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
max-width: 840px; max-width: 840px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.heroEmph { .heroEmph {
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
font-style: italic; font-style: italic;
} }
.heroDescription { .heroDescription {
font-size: 1.05rem; font-size: 1.05rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
margin: 0 auto 0.5rem; margin: 0 auto 0.5rem;
max-width: 680px; max-width: 680px;
line-height: 1.55; line-height: 1.55;
} }
.heroDescription strong { .heroDescription strong {
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-weight: 700; font-weight: 700;
} }
@@ -111,7 +111,7 @@
.viewToggle { .viewToggle {
display: flex; display: flex;
gap: 0.2rem; gap: 0.2rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
padding: 0.2rem; padding: 0.2rem;
border-radius: 6px; border-radius: 6px;
} }
@@ -127,18 +127,18 @@
border: none; border: none;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.viewToggleBtn:hover { .viewToggleBtn:hover {
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.viewToggleBtn.active { .viewToggleBtn.active {
background: var(--bg-card, white); background: var(--bg-card);
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
box-shadow: 0 2px 4px rgba(26, 22, 18, 0.08); box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.08);
} }
.viewToggleBtn svg { .viewToggleBtn svg {
@@ -166,7 +166,7 @@
.mapContainer { .mapContainer {
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
height: 100%; height: 100%;
} }
@@ -184,17 +184,17 @@
} }
.compactList::-webkit-scrollbar-track { .compactList::-webkit-scrollbar-track {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 3px; border-radius: 3px;
} }
.compactList::-webkit-scrollbar-thumb { .compactList::-webkit-scrollbar-thumb {
background: var(--border-color, #e5dfd5); background: var(--border);
border-radius: 3px; border-radius: 3px;
} }
.compactList::-webkit-scrollbar-thumb:hover { .compactList::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #8a847a); background: var(--text-muted);
} }
/* Compact School Item */ /* Compact School Item */
@@ -204,15 +204,15 @@
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
padding: 0.625rem 0.75rem; padding: 0.625rem 0.75rem;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.compactItem:hover { .compactItem:hover {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: 0 2px 6px rgba(26, 22, 18, 0.05); box-shadow: 0 2px 6px rgba(var(--shadow-rgb), 0.05);
} }
.compactItemContent { .compactItemContent {
@@ -230,7 +230,7 @@
.compactItemName { .compactItemName {
font-weight: 600; font-weight: 600;
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -238,7 +238,7 @@
} }
.compactItemName:hover { .compactItemName:hover {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.distanceBadge { .distanceBadge {
@@ -246,8 +246,8 @@
padding: 0.0625rem 0.375rem; padding: 0.0625rem 0.375rem;
font-size: 0.6875rem; font-size: 0.6875rem;
font-weight: 600; font-weight: 600;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
color: white; color: var(--brand-on);
border-radius: 3px; border-radius: 3px;
} }
@@ -255,25 +255,25 @@
display: flex; display: flex;
gap: 0.375rem; gap: 0.375rem;
font-size: 0.6875rem; font-size: 0.6875rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
.compactItemMeta span:not(:last-child)::after { .compactItemMeta span:not(:last-child)::after {
content: '·'; content: '·';
margin-left: 0.375rem; margin-left: 0.375rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.compactItemStats { .compactItemStats {
display: flex; display: flex;
gap: 0.75rem; gap: 0.75rem;
font-size: 0.6875rem; font-size: 0.6875rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.compactStat strong { .compactStat strong {
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.compactItemActions { .compactItemActions {
@@ -292,8 +292,8 @@
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 700; font-weight: 700;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
/* Decorative coral bar under section headings */ /* Decorative coral bar under section headings */
@@ -302,14 +302,14 @@
display: block; display: block;
width: 50px; width: 50px;
height: 2px; height: 2px;
background: var(--accent-coral, #e07256); background: var(--brand);
border-radius: 1px; border-radius: 1px;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.sectionDescription { .sectionDescription {
font-size: 0.9375rem; font-size: 0.9375rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
margin: 0; margin: 0;
line-height: 1.5; line-height: 1.5;
} }
@@ -336,22 +336,22 @@
.emptyState { .emptyState {
text-align: center; text-align: center;
padding: 2.5rem 1.5rem; padding: 2.5rem 1.5rem;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
} }
.emptyStateTitle { .emptyStateTitle {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.375rem; margin-bottom: 0.375rem;
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.emptyStateDescription { .emptyStateDescription {
font-size: 0.9375rem; font-size: 0.9375rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
max-width: 380px; max-width: 380px;
margin: 0 auto; margin: 0 auto;
} }
@@ -419,9 +419,9 @@
/* Highlighted List Item */ /* Highlighted List Item */
.highlightedItem .compactItem { .highlightedItem .compactItem {
border-color: var(--accent-teal, #2d7d7d); border-color: var(--brand);
box-shadow: 0 0 0 1px var(--accent-teal, #2d7d7d); box-shadow: 0 0 0 1px var(--brand);
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
/* Mobile Bottom Sheet */ /* Mobile Bottom Sheet */
@@ -443,9 +443,9 @@
.bottomSheet { .bottomSheet {
position: relative; position: relative;
background: var(--bg-card, white); background: var(--bg-card);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 -4px 24px rgba(26, 22, 18, 0.15); box-shadow: 0 -4px 24px rgba(var(--shadow-rgb), 0.15);
pointer-events: auto; pointer-events: auto;
animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
} }
@@ -467,16 +467,16 @@
right: -12px; right: -12px;
width: 30px; width: 30px;
height: 30px; height: 30px;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 1.25rem; font-size: 1.25rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.1); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.1);
z-index: 10; z-index: 10;
} }
@@ -522,7 +522,7 @@
.quickSearchChip { .quickSearchChip {
padding: 0.375rem 0.875rem; padding: 0.375rem 0.875rem;
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-color, #e0ddd8); border: 1px solid var(--border);
border-radius: 999px; border-radius: 999px;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-secondary); color: var(--text-secondary);
@@ -531,9 +531,9 @@
} }
.quickSearchChip:hover { .quickSearchChip:hover {
background: var(--accent-coral-dark); background: var(--brand-strong);
color: white; color: var(--brand-on);
border-color: var(--accent-coral-dark); border-color: var(--brand-strong);
} }
.exploringRow { .exploringRow {
@@ -546,7 +546,7 @@
.exploringLabel { .exploringLabel {
font-size: 0.72rem; font-size: 0.72rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
font-weight: 500; font-weight: 500;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-transform: uppercase; text-transform: uppercase;
@@ -564,19 +564,19 @@
align-items: center; align-items: center;
gap: 0.4rem; gap: 0.4rem;
padding: 0.5rem 0.95rem; padding: 0.5rem 0.95rem;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 999px; border-radius: 999px;
font-size: 0.82rem; font-size: 0.82rem;
font-weight: 500; font-weight: 500;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
transition: all 0.15s ease; transition: all 0.15s ease;
} }
.exploringChip:hover { .exploringChip:hover {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
transform: translateY(-1px); transform: translateY(-1px);
} }
@@ -606,16 +606,16 @@
} }
.hiwHeading { .hiwHeading {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 1.75rem; font-size: 1.75rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin: 0; margin: 0;
} }
.hiwSub { .hiwSub {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.hiwGrid { .hiwGrid {
@@ -625,18 +625,18 @@
} }
.hiwCard { .hiwCard {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 14px; border-radius: 14px;
padding: 1.25rem; padding: 1.25rem;
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.9rem; gap: 0.9rem;
} }
.hiwVisual { .hiwVisual {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 10px; border-radius: 10px;
padding: 0.9rem; padding: 0.9rem;
min-height: 180px; min-height: 180px;
@@ -657,11 +657,11 @@
font-weight: 600; font-weight: 600;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.hiwPhaseLabel strong { .hiwPhaseLabel strong {
color: var(--accent-teal, #2d7d7d); color: var(--brand);
font-weight: 700; font-weight: 700;
} }
@@ -676,20 +676,20 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.hiwTitle { .hiwTitle {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
line-height: 1.25; line-height: 1.25;
} }
.hiwDesc { .hiwDesc {
font-size: 0.86rem; font-size: 0.86rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
line-height: 1.45; line-height: 1.45;
margin: 0; margin: 0;
} }
@@ -713,7 +713,7 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
} }
@@ -723,16 +723,16 @@
align-items: baseline; align-items: baseline;
gap: 0.2rem; gap: 0.2rem;
font-size: 0.44rem; font-size: 0.44rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.01em; letter-spacing: 0.01em;
white-space: nowrap; white-space: nowrap;
} }
.miniRowHead strong { .miniRowHead strong {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 0.62rem; font-size: 0.62rem;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-weight: 700; font-weight: 700;
letter-spacing: 0; letter-spacing: 0;
text-transform: none; text-transform: none;
@@ -741,7 +741,7 @@
.miniTrack { .miniTrack {
height: 5px; height: 5px;
border-radius: 2px; border-radius: 2px;
background: rgba(45, 125, 125, 0.08); background: rgba(var(--status-above-rgb), 0.08);
position: relative; position: relative;
overflow: visible; overflow: visible;
} }
@@ -749,21 +749,21 @@
.miniBarExp { .miniBarExp {
height: 100%; height: 100%;
border-radius: 2px; border-radius: 2px;
background: var(--accent-teal-light, #3a9e9e); background: var(--brand);
} }
.miniBarExc { .miniBarExc {
height: 100%; height: 100%;
border-radius: 2px; border-radius: 2px;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
} }
.miniNatPill { .miniNatPill {
position: absolute; position: absolute;
top: -9px; top: -9px;
transform: translateX(-50%); transform: translateX(-50%);
background: var(--accent-coral, #e07256); background: var(--brand);
color: #fff; color: var(--text-inverse);
font-size: 0.4rem; font-size: 0.4rem;
font-weight: 700; font-weight: 700;
padding: 0.05rem 0.2rem; padding: 0.05rem 0.2rem;
@@ -791,21 +791,21 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: 0.5rem; font-size: 0.5rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.att8Track { .att8Track {
height: 7px; height: 7px;
background: rgba(45, 125, 125, 0.08); background: rgba(var(--status-above-rgb), 0.08);
border-radius: 3px; border-radius: 3px;
position: relative; position: relative;
} }
.att8Fill { .att8Fill {
height: 100%; height: 100%;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
border-radius: 3px; border-radius: 3px;
} }
@@ -814,25 +814,28 @@
top: -2px; top: -2px;
bottom: -2px; bottom: -2px;
width: 1.5px; width: 1.5px;
background: rgba(224, 114, 86, 0.6); background: rgba(var(--brand-rgb), 0.6);
z-index: 2; z-index: 2;
} }
.att8Score { .att8Score {
font-variant-numeric: tabular-nums;
text-align: right; text-align: right;
} }
.att8Value { .att8Value {
font-family: var(--font-playfair), 'Playfair Display', serif; font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 700; font-weight: 700;
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
line-height: 1; line-height: 1;
} }
.att8Delta { .att8Delta {
font-variant-numeric: tabular-nums;
font-size: 0.55rem; font-size: 0.55rem;
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.04em; letter-spacing: 0.04em;
@@ -853,31 +856,31 @@
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding-bottom: 0.4rem; padding-bottom: 0.4rem;
border-bottom: 1.5px solid var(--border-color, #e5dfd5); border-bottom: 1.5px solid var(--border);
} }
.ofstedBullet { .ofstedBullet {
display: block; display: block;
width: 3px; width: 3px;
height: 1em; height: 1em;
background: var(--accent-coral, #e07256); background: var(--brand);
border-radius: 2px; border-radius: 2px;
flex-shrink: 0; flex-shrink: 0;
} }
.ofstedTitle { .ofstedTitle {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.ofstedFramework { .ofstedFramework {
align-self: flex-start; align-self: flex-start;
padding: 0.15rem 0.5rem; padding: 0.15rem 0.5rem;
border-radius: 4px; border-radius: 4px;
background: rgba(90, 58, 110, 0.12); background: rgba(var(--phase-secondary-rgb), 0.12);
color: #5a3a6e; color: var(--phase-secondary-text);
font-size: 0.5rem; font-size: 0.5rem;
font-weight: 700; font-weight: 700;
letter-spacing: 0.05em; letter-spacing: 0.05em;
@@ -899,7 +902,7 @@
.rcArea { .rcArea {
font-size: 0.62rem; font-size: 0.62rem;
color: var(--text-secondary, #4a453d); color: var(--text-secondary);
} }
.rcChip { .rcChip {
@@ -913,13 +916,13 @@
} }
.rcChipGood { .rcChipGood {
background: rgba(45, 125, 125, 0.12); background: rgba(var(--status-above-rgb), 0.12);
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
} }
.rcChipNeutral { .rcChipNeutral {
background: var(--accent-gold-bg, rgba(180, 150, 40, 0.12)); background: var(--status-below-bg);
color: var(--accent-gold-text, #7a6800); color: var(--status-below);
} }
/* Safeguarding is a separate binary met/not-met judgement, not a graded area, /* Safeguarding is a separate binary met/not-met judgement, not a graded area,
@@ -931,30 +934,30 @@
gap: 0.5rem; gap: 0.5rem;
margin-top: 0.05rem; margin-top: 0.05rem;
padding-top: 0.35rem; padding-top: 0.35rem;
border-top: 1px dashed var(--border-color, #e5dfd5); border-top: 1px dashed var(--border);
} }
.rcSep { .rcSep {
color: var(--text-muted, #8a847a); color: var(--text-muted);
font-style: italic; font-style: italic;
} }
.rcChipMet { .rcChipMet {
background: transparent; background: transparent;
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
border: 1px solid rgba(45, 125, 125, 0.4); border: 1px solid rgba(var(--status-above-rgb), 0.4);
} }
.ofstedMeta { .ofstedMeta {
font-size: 0.6rem; font-size: 0.6rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
/* Compare preview */ /* Compare preview */
.comparePreview { .comparePreview {
width: 100%; width: 100%;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
font-size: 0.6rem; font-size: 0.6rem;
@@ -963,23 +966,23 @@
.compareHead { .compareHead {
display: grid; display: grid;
grid-template-columns: 1fr repeat(2, 1fr); grid-template-columns: 1fr repeat(2, 1fr);
background: rgba(45, 125, 125, 0.1); background: rgba(var(--status-above-rgb), 0.1);
padding: 0.35rem 0.5rem; padding: 0.35rem 0.5rem;
gap: 0.35rem; gap: 0.35rem;
} }
.compareHeadCell { .compareHeadCell {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 0.62rem; font-size: 0.62rem;
font-weight: 700; font-weight: 700;
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
line-height: 1.2; line-height: 1.2;
} }
.compareHeadLabel { .compareHeadLabel {
font-family: inherit; font-family: inherit;
font-size: 0.48rem; font-size: 0.48rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
font-weight: 500; font-weight: 500;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
@@ -990,37 +993,37 @@
grid-template-columns: 1fr repeat(2, 1fr); grid-template-columns: 1fr repeat(2, 1fr);
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
gap: 0.35rem; gap: 0.35rem;
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
align-items: baseline; align-items: baseline;
} }
.compareRowLabel { .compareRowLabel {
font-size: 0.55rem; font-size: 0.55rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.compareRowVal { .compareRowVal {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 0.72rem; font-size: 0.72rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.compareRowValHi { .compareRowValHi {
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
} }
.compareFoot { .compareFoot {
font-size: 0.52rem; font-size: 0.52rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
padding: 0.35rem 0.5rem; padding: 0.35rem 0.5rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.06em; letter-spacing: 0.06em;
text-align: center; text-align: center;
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -1065,8 +1068,8 @@
grid-template-columns: 1.4fr 1fr; grid-template-columns: 1.4fr 1fr;
gap: 2rem; gap: 2rem;
padding: 1.75rem; padding: 1.75rem;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 14px; border-radius: 14px;
} }
@@ -1078,30 +1081,34 @@
.editorialKicker { .editorialKicker {
font-size: 0.68rem; font-size: 0.68rem;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
font-weight: 700; font-weight: 700;
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
} }
.editorialHeading { .editorialHeading {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 1.35rem; font-size: 1.35rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin: 0; margin: 0;
line-height: 1.25; line-height: 1.25;
} }
/* Running prose is the one place the serif appears. Slightly larger than the
UI around it because Literata's x-height sits lower than the grotesque's. */
.editorialText p { .editorialText p {
font-size: 0.92rem; font-family: var(--font-prose);
color: var(--text-secondary, #5c564d); font-size: 0.97rem;
line-height: 1.6; color: var(--text-secondary);
line-height: 1.65;
margin: 0; margin: 0;
max-width: 62ch;
} }
.factbox { .factbox {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 10px; border-radius: 10px;
padding: 1.25rem; padding: 1.25rem;
display: flex; display: flex;
@@ -1110,10 +1117,10 @@
} }
.factboxHeading { .factboxHeading {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-size: 1rem; font-size: 1rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin: 0 0 0.85rem; margin: 0 0 0.85rem;
} }
@@ -1122,7 +1129,7 @@
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: baseline;
padding: 0.5rem 0; padding: 0.5rem 0;
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
font-size: 0.85rem; font-size: 0.85rem;
gap: 0.5rem; gap: 0.5rem;
} }
@@ -1132,13 +1139,13 @@
} }
.factKey { .factKey {
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.factVal { .factVal {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-align: right; text-align: right;
} }
@@ -1168,7 +1175,7 @@
.sortSelect { .sortSelect {
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
border: 1px solid var(--border-color, #e0ddd8); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: var(--bg-card); background: var(--bg-card);
color: var(--text-secondary); color: var(--text-secondary);
@@ -1189,7 +1196,7 @@
gap: 0.375rem; gap: 0.375rem;
padding: 0.25rem 0.625rem; padding: 0.25rem 0.625rem;
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-color, #e0ddd8); border: 1px solid var(--border);
border-radius: 999px; border-radius: 999px;
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-secondary); color: var(--text-secondary);
@@ -1216,8 +1223,9 @@
} }
.loadMoreCount { .loadMoreCount {
font-variant-numeric: tabular-nums;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
margin: 0; margin: 0;
} }
@@ -1231,7 +1239,7 @@
.admissionsStrip { .admissionsStrip {
padding: 1.5rem 0 2rem; padding: 1.5rem 0 2rem;
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
} }
.stripHeader { .stripHeader {
@@ -1248,12 +1256,12 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.stripCta { .stripCta {
font-size: 0.82rem; font-size: 0.82rem;
color: var(--accent-teal, #2d7d7d); color: var(--brand);
font-weight: 600; font-weight: 600;
text-decoration: none; text-decoration: none;
} }
@@ -1269,11 +1277,11 @@
} }
.countdownChip { .countdownChip {
background: var(--bg-card, #fff); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
padding: 1rem 1.1rem 0.9rem; padding: 1rem 1.1rem 0.9rem;
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.2rem; gap: 0.2rem;
@@ -1292,16 +1300,16 @@
} }
.countdownChipDeadline::before { .countdownChipDeadline::before {
background: var(--accent-coral, #e07256); background: var(--brand);
} }
.countdownChipOffer::before { .countdownChipOffer::before {
background: var(--accent-teal, #2d7d7d); background: var(--status-above);
} }
.countdownChipUrgent { .countdownChipUrgent {
border-color: rgba(224, 114, 86, 0.4); border-color: rgba(var(--brand-rgb), 0.4);
background: rgba(224, 114, 86, 0.04); background: rgba(var(--brand-rgb), 0.04);
} }
.chipTrack { .chipTrack {
@@ -1316,11 +1324,11 @@
} }
.chipTrackDeadline { .chipTrackDeadline {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.chipTrackOffer { .chipTrackOffer {
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
} }
.chipTrackDot { .chipTrackDot {
@@ -1332,7 +1340,8 @@
} }
.chipDays { .chipDays {
font-family: var(--font-playfair), 'Playfair Display', serif; font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 2.6rem; font-size: 2.6rem;
font-weight: 700; font-weight: 700;
line-height: 1; line-height: 1;
@@ -1341,18 +1350,18 @@
.countdownChipDeadline .chipDays, .countdownChipDeadline .chipDays,
.countdownChipUrgent .chipDays { .countdownChipUrgent .chipDays {
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
} }
.countdownChipOffer .chipDays { .countdownChipOffer .chipDays {
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
} }
.chipDaysUnit { .chipDaysUnit {
font-family: 'DM Sans', sans-serif; font-family: var(--font-ui);
font-size: 0.78rem; font-size: 0.78rem;
font-weight: 500; font-weight: 500;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-left: 0.2rem; margin-left: 0.2rem;
vertical-align: bottom; vertical-align: bottom;
line-height: 2; line-height: 2;
@@ -1361,14 +1370,14 @@
.chipMilestone { .chipMilestone {
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
line-height: 1.25; line-height: 1.25;
margin-top: 0.1rem; margin-top: 0.1rem;
} }
.chipDate { .chipDate {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-top: 0.05rem; margin-top: 0.05rem;
} }
+10 -10
View File
@@ -573,10 +573,10 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm
rwmDelta == null rwmDelta == null
? {} ? {}
: rwmDelta >= 2 : rwmDelta >= 2
? { fontSize: '0.7rem', color: 'var(--accent-teal, #2d7d7d)', fontWeight: 600 } ? { fontSize: '0.7rem', color: 'var(--status-above)', fontWeight: 600 }
: rwmDelta <= -2 : rwmDelta <= -2
? { fontSize: '0.7rem', color: 'var(--accent-coral, #e07256)', fontWeight: 600 } ? { fontSize: '0.7rem', color: 'var(--brand)', fontWeight: 600 }
: { fontSize: '0.7rem', color: 'var(--text-muted, #8a847a)' }; : { fontSize: '0.7rem', color: 'var(--text-muted)' };
return ( return (
<div className={styles.compactItem}> <div className={styles.compactItem}>
@@ -601,13 +601,13 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm
fontWeight: 600, fontWeight: 600,
borderRadius: '3px', borderRadius: '3px',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--accent-teal-bg)', color: 'var(--accent-teal, #2d7d7d)' } : ...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } :
ofstedBadge.cssClass === 'ofsted2' ? { background: 'rgba(60,140,60,0.12)', color: '#2f7a2f' } : ofstedBadge.cssClass === 'ofsted2' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } :
ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--accent-gold-bg)', color: 'var(--accent-gold-text, #7a6800)' } : ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--status-below-bg)', color: 'var(--status-below)' } :
ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--accent-coral-bg)', color: 'var(--accent-coral, #e07256)' } : ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--brand-bg)', color: 'var(--brand)' } :
ofstedBadge.cssClass === 'ofstedRc' ? { background: '#5a3a6e', color: '#fff' } : ofstedBadge.cssClass === 'ofstedRc' ? { background: 'var(--phase-secondary-text)', color: 'var(--text-inverse)' } :
ofstedBadge.cssClass === 'ofstedPending' ? { background: '#e0e0e0', color: '#666' } : ofstedBadge.cssClass === 'ofstedPending' ? { background: 'var(--bg-secondary)', color: 'var(--text-muted)' } :
{ background: '#e0e0e0', color: '#666' }), { background: 'var(--bg-secondary)', color: 'var(--text-muted)' }),
}} }}
> >
{ofstedBadge.label} {ofstedBadge.label}
+10 -10
View File
@@ -11,7 +11,7 @@
/* font-size:0 hides the button's own "?" text node; the ::before glyph /* font-size:0 hides the button's own "?" text node; the ::before glyph
below carries the visible circled "?" at its own explicit size. */ below carries the visible circled "?" at its own explicit size. */
font-size: 0; font-size: 0;
color: var(--text-muted, #8a7a72); color: var(--text-muted);
cursor: help; cursor: help;
line-height: 1; line-height: 1;
user-select: none; user-select: none;
@@ -34,17 +34,17 @@
.icon:hover, .icon:hover,
.icon[aria-expanded='true'], .icon[aria-expanded='true'],
.icon:focus-visible { .icon:focus-visible {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.tooltip { .tooltip {
z-index: 9999; z-index: 9999;
width: max-content; width: max-content;
max-width: min(260px, calc(100vw - 24px)); max-width: min(260px, calc(100vw - 24px));
background: var(--bg-primary, #faf7f2); background: var(--bg-primary);
border: 1px solid var(--border-color, #e8ddd4); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
box-shadow: 0 4px 16px rgba(44, 36, 32, 0.15); box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.15);
padding: 0.6rem 0.75rem; padding: 0.6rem 0.75rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -52,26 +52,26 @@
} }
.arrow { .arrow {
fill: var(--bg-primary, #faf7f2); fill: var(--bg-primary);
stroke: var(--border-color, #e8ddd4); stroke: var(--border);
stroke-width: 1px; stroke-width: 1px;
} }
.label { .label {
font-weight: 600; font-weight: 600;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-primary, #2c2420); color: var(--text-primary);
} }
.plain { .plain {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-secondary, #5a4a44); color: var(--text-secondary);
line-height: 1.4; line-height: 1.4;
} }
.detail { .detail {
font-size: 0.7rem; font-size: 0.7rem;
color: var(--text-muted, #8a7a72); color: var(--text-muted);
line-height: 1.4; line-height: 1.4;
margin-top: 0.1rem; margin-top: 0.1rem;
} }
@@ -22,10 +22,10 @@ interface LeafletHeroMapInnerProps {
// reliably inside Leaflet's divIcon HTML string (no CSS-module dependency). // reliably inside Leaflet's divIcon HTML string (no CSS-module dependency).
const PIN_HTML = ` const PIN_HTML = `
<div style="position:relative;width:26px;height:26px"> <div style="position:relative;width:26px;height:26px">
<span style="position:absolute;left:50%;top:74%;width:32px;height:32px;transform:translate(-50%,-50%);border-radius:50%;background:rgba(224,114,86,.25)"></span> <span style="position:absolute;left:50%;top:74%;width:32px;height:32px;transform:translate(-50%,-50%);border-radius:50%;background:var(--brand-bg)"></span>
<svg width="26" height="34" viewBox="0 0 26 34" style="filter:drop-shadow(0 3px 4px rgba(0,0,0,.35))"> <svg width="26" height="34" viewBox="0 0 26 34" style="filter:drop-shadow(0 3px 4px rgba(0,0,0,.35))">
<path d="M13 0C5.8 0 0 5.8 0 13c0 9.2 13 21 13 21s13-11.8 13-21C26 5.8 20.2 0 13 0Z" fill="#e07256"/> <path d="M13 0C5.8 0 0 5.8 0 13c0 9.2 13 21 13 21s13-11.8 13-21C26 5.8 20.2 0 13 0Z" fill="var(--brand)"/>
<circle cx="13" cy="13" r="5" fill="#fff"/> <circle cx="13" cy="13" r="5" fill="var(--text-inverse)"/>
</svg> </svg>
</div>`; </div>`;
+16 -16
View File
@@ -48,21 +48,21 @@ function buildPopupBadge(school: School): PopupBadge {
// ofsted_framework is the raw event grouping, never "ReportCard"). // ofsted_framework is the raw event grouping, never "ReportCard").
if (school.ofsted_rc_date) { if (school.ofsted_rc_date) {
const rcYear = new Date(school.ofsted_rc_date).getFullYear(); const rcYear = new Date(school.ofsted_rc_date).getFullYear();
return { label: `Report Card · ${rcYear}`, style: 'background:#5a3a6e;color:#fff' }; return { label: `Report Card · ${rcYear}`, style: 'background:var(--phase-secondary-text);color:var(--text-inverse)' };
} }
const year = school.ofsted_date ? new Date(school.ofsted_date).getFullYear() : null; const year = school.ofsted_date ? new Date(school.ofsted_date).getFullYear() : null;
const yearStr = year ? ` · ${year}` : ''; const yearStr = year ? ` · ${year}` : '';
if (school.ofsted_grade) { if (school.ofsted_grade) {
const labels: Record<number, string> = { 1: 'Outstanding', 2: 'Good', 3: 'Req. Improvement', 4: 'Inadequate' }; const labels: Record<number, string> = { 1: 'Outstanding', 2: 'Good', 3: 'Req. Improvement', 4: 'Inadequate' };
const colours: Record<number, string> = { const colours: Record<number, string> = {
1: 'background:#d4f0ea;color:#2d7d7d', 1: 'background:var(--status-above-bg);color:var(--status-above)',
2: 'background:rgba(60,140,60,0.12);color:#2f7a2f', 2: 'background:var(--status-above-bg);color:var(--status-above)',
3: 'background:#fef3cd;color:#7a6800', 3: 'background:var(--status-below-bg);color:var(--status-below)',
4: 'background:#fde8e0;color:#e07256', 4: 'background:var(--brand-bg);color:var(--brand)',
}; };
return { label: `${labels[school.ofsted_grade]}${yearStr}`, style: colours[school.ofsted_grade] }; return { label: `${labels[school.ofsted_grade]}${yearStr}`, style: colours[school.ofsted_grade] };
} }
return { label: 'Not yet inspected', style: 'background:#e0e0e0;color:#666' }; return { label: 'Not yet inspected', style: 'background:var(--bg-secondary);color:var(--text-muted)' };
} }
export default function LeafletMapInner({ schools, center, zoom, referencePoint, onMarkerClick, nationalAvgRwm, laAverages }: LeafletMapInnerProps) { export default function LeafletMapInner({ schools, center, zoom, referencePoint, onMarkerClick, nationalAvgRwm, laAverages }: LeafletMapInnerProps) {
@@ -99,7 +99,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
const refIcon = L.divIcon({ const refIcon = L.divIcon({
html: `<div style=" html: `<div style="
width: 20px; height: 20px; width: 20px; height: 20px;
background: #e07256; background: var(--brand);
border: 3px solid white; border: 3px solid white;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 2px 8px rgba(0,0,0,0.35); box-shadow: 0 2px 8px rgba(0,0,0,0.35);
@@ -145,13 +145,13 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
const sign = diff >= 0 ? '+' : ''; const sign = diff >= 0 ? '+' : '';
// Att8 scores range 090 in 0.1 increments; ±0.5 is meaningful here // Att8 scores range 090 in 0.1 increments; ±0.5 is meaningful here
// vs primary RWM % where ±2 pts is the threshold // vs primary RWM % where ±2 pts is the threshold
const colour = diff >= 0.5 ? '#2d7d7d' : diff <= -0.5 ? '#e07256' : '#8a847a'; const colour = diff >= 0.5 ? 'var(--status-above)' : diff <= -0.5 ? 'var(--brand)' : 'var(--text-muted)';
const laName = escapeHtml(school.local_authority ?? 'LA'); const laName = escapeHtml(school.local_authority ?? 'LA');
deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${sign}${diff} vs ${laName} avg</div>`; deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${sign}${diff} vs ${laName} avg</div>`;
} }
metricHtml = `<div style="margin-bottom:4px"> metricHtml = `<div style="margin-bottom:4px">
<span style="font-size:20px;font-weight:700;color:#1a1612;font-family:Georgia,serif">${score.toFixed(1)}</span> <span style="font-size:20px;font-weight:700;color:var(--text-primary);font-family:Georgia,serif">${score.toFixed(1)}</span>
<span style="font-size:11px;color:#8a847a;margin-left:4px">Attainment 8</span> <span style="font-size:11px;color:var(--text-muted);margin-left:4px">Attainment 8</span>
${deltaLine} ${deltaLine}
</div>`; </div>`;
} else if (school.rwm_expected_pct != null) { } else if (school.rwm_expected_pct != null) {
@@ -159,7 +159,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
let deltaLine = ''; let deltaLine = '';
if (nationalAvgRwm != null) { if (nationalAvgRwm != null) {
const diff = Math.round(rwm - nationalAvgRwm); const diff = Math.round(rwm - nationalAvgRwm);
const colour = diff >= 2 ? '#2d7d7d' : diff <= -2 ? '#e07256' : '#8a847a'; const colour = diff >= 2 ? 'var(--status-above)' : diff <= -2 ? 'var(--brand)' : 'var(--text-muted)';
const text = const text =
diff >= 2 ? `+${diff} pts vs national` : diff >= 2 ? `+${diff} pts vs national` :
diff <= -2 ? `${diff} pts vs national` : diff <= -2 ? `${diff} pts vs national` :
@@ -167,8 +167,8 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${text}</div>`; deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${text}</div>`;
} }
metricHtml = `<div style="margin-bottom:4px"> metricHtml = `<div style="margin-bottom:4px">
<span style="font-size:20px;font-weight:700;color:#1a1612;font-family:Georgia,serif">${rwm}%</span> <span style="font-size:20px;font-weight:700;color:var(--text-primary);font-family:Georgia,serif">${rwm}%</span>
<span style="font-size:11px;color:#8a847a;margin-left:4px">Reading, Writing &amp; Maths</span> <span style="font-size:11px;color:var(--text-muted);margin-left:4px">Reading, Writing &amp; Maths</span>
${deltaLine} ${deltaLine}
</div>`; </div>`;
} }
@@ -177,14 +177,14 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
const popupContent = `<div style="font-family:system-ui,sans-serif;min-width:240px;max-width:280px;padding:0"> const popupContent = `<div style="font-family:system-ui,sans-serif;min-width:240px;max-width:280px;padding:0">
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:6px"> <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:6px">
<strong style="font-size:13px;color:#1a1612;line-height:1.3">${escapeHtml(school.school_name)}</strong> <strong style="font-size:13px;color:var(--text-primary);line-height:1.3">${escapeHtml(school.school_name)}</strong>
<span style="font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;white-space:nowrap;flex-shrink:0;${badge.style}">${badge.label}</span> <span style="font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;white-space:nowrap;flex-shrink:0;${badge.style}">${badge.label}</span>
</div> </div>
<div style="font-size:11px;color:#8a847a;margin-bottom:8px"> <div style="font-size:11px;color:var(--text-muted);margin-bottom:8px">
${phaseLabel}${school.local_authority ? ` · ${escapeHtml(school.local_authority)}` : ''}${distanceStr} ${phaseLabel}${school.local_authority ? ` · ${escapeHtml(school.local_authority)}` : ''}${distanceStr}
</div> </div>
${metricHtml} ${metricHtml}
<a href="${slug}" style="display:block;text-align:center;padding:6px;background:#2d7d7d;color:white;border-radius:5px;text-decoration:none;font-size:12px;font-weight:600;margin-top:8px">View Details →</a> <a href="${slug}" style="display:block;text-align:center;padding:6px;background:var(--status-above);color:white;border-radius:5px;text-decoration:none;font-size:12px;font-weight:600;margin-top:8px">View Details →</a>
</div>`; </div>`;
marker.bindPopup(popupContent); marker.bindPopup(popupContent);
@@ -5,19 +5,19 @@
} }
.skeletonCard { .skeletonCard {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
padding: 1.5rem; padding: 1.5rem;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
} }
.skeleton { .skeleton {
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
var(--bg-secondary, #f3ede4) 25%, var(--bg-secondary) 25%,
rgba(224, 114, 86, 0.08) 50%, rgba(var(--brand-rgb), 0.08) 50%,
var(--bg-secondary, #f3ede4) 75% var(--bg-secondary) 75%
); );
background-size: 200% 100%; background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite; animation: shimmer 1.5s ease-in-out infinite;
@@ -56,7 +56,7 @@
gap: 1rem; gap: 1rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
padding: 1rem; padding: 1rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 8px; border-radius: 8px;
} }
@@ -82,11 +82,11 @@
} }
.skeletonListItem { .skeletonListItem {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
padding: 1.5rem; padding: 1.5rem;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
} }
.listTitle { .listTitle {
+65
View File
@@ -0,0 +1,65 @@
/**
* The SchoolCompare mark.
*
* Five bars: a cohort, with one school picked out. It is the same object as
* the distribution strip inside a school row, at a different size — the logo
* teaches the chart and the chart reinforces the logo.
*
* Built from opacity steps over `currentColor` rather than fixed greys, so it
* inverts cleanly on any ground without a second artwork. The highlighted bar
* is the only element that takes the brand hue.
*
* This is the single source for the mark. The favicon and the generated icons
* derive from the same geometry (see BARS) — previously the header and the
* favicon had drifted into two different logos.
*/
/** x, y, width, height, and whether this bar is the highlighted school. */
export const BARS: ReadonlyArray<[number, number, number, number, boolean]> = [
[2, 26, 6, 12, false],
[10, 16, 6, 22, false],
[18, 8, 6, 30, false],
[26, 14, 6, 24, true],
[34, 28, 4, 10, false],
];
const COHORT_OPACITY = [0.22, 0.38, 0.55, 1, 0.22];
interface LogoMarkProps {
className?: string;
/** Rendered size in px. Defaults to inheriting via CSS. */
size?: number;
/** Colour of the highlighted bar. Defaults to the brand token. */
accent?: string;
title?: string;
}
export function LogoMark({ className, size, accent = 'var(--brand)', title }: LogoMarkProps) {
return (
<svg
className={className}
width={size}
height={size}
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role={title ? 'img' : undefined}
aria-hidden={title ? undefined : true}
aria-label={title}
>
{title ? <title>{title}</title> : null}
{BARS.map(([x, y, w, h, isSchool], i) => (
<rect
key={x}
x={x}
y={y}
width={w}
height={h}
rx={1}
fill={isSchool ? accent : 'currentColor'}
opacity={isSchool ? 1 : COHORT_OPACITY[i]}
/>
))}
</svg>
);
}
+13 -13
View File
@@ -1,7 +1,7 @@
.overlay { .overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
background: rgba(26, 22, 18, 0.6); background: rgba(var(--shadow-rgb), 0.6);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -20,15 +20,15 @@
} }
.modal { .modal {
background: var(--bg-card, white); background: var(--bg-card);
border-radius: 16px; border-radius: 16px;
box-shadow: 0 20px 40px rgba(26, 22, 18, 0.2); box-shadow: 0 20px 40px rgba(var(--shadow-rgb), 0.2);
max-height: 90vh; max-height: 90vh;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
animation: slideIn 0.3s ease; animation: slideIn 0.3s ease;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
} }
@keyframes slideIn { @keyframes slideIn {
@@ -62,22 +62,22 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 1.5rem; padding: 1.5rem;
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
} }
.title { .title {
margin: 0; margin: 0;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.closeButton { .closeButton {
padding: 0.5rem; padding: 0.5rem;
background: transparent; background: transparent;
border: none; border: none;
color: var(--text-muted, #8a847a); color: var(--text-muted);
cursor: pointer; cursor: pointer;
border-radius: 8px; border-radius: 8px;
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -87,8 +87,8 @@
} }
.closeButton:hover { .closeButton:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
color: var(--accent-coral, #e07256); color: var(--brand);
} }
.content { .content {
@@ -102,16 +102,16 @@
} }
.content::-webkit-scrollbar-track { .content::-webkit-scrollbar-track {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.content::-webkit-scrollbar-thumb { .content::-webkit-scrollbar-thumb {
background: var(--border-color, #e5dfd5); background: var(--border);
border-radius: 4px; border-radius: 4px;
} }
.content::-webkit-scrollbar-thumb:hover { .content::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #8a847a); background: var(--text-muted);
} }
@media (max-width: 640px) { @media (max-width: 640px) {
+38 -28
View File
@@ -2,9 +2,9 @@
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1000; z-index: 1000;
background: var(--bg-card, white); background: var(--bg-card);
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
} }
.container { .container {
@@ -27,7 +27,7 @@
margin: -0.375rem -0.5rem; margin: -0.375rem -0.5rem;
padding: 0.375rem 0.5rem; padding: 0.375rem 0.5rem;
text-decoration: none; text-decoration: none;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 700; font-weight: 700;
transition: color 0.2s ease; transition: color 0.2s ease;
@@ -35,14 +35,16 @@
} }
.logo:hover { .logo:hover {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
/* currentColor paints the four cohort bars; the mark supplies the brand hue
for the highlighted one itself. So this is ink, not brand. */
.logoIcon { .logoIcon {
display: inline-flex; display: inline-flex;
width: 36px; width: 32px;
height: 36px; height: 32px;
color: var(--accent-coral, #e07256); color: var(--text-primary);
} }
.logoIcon svg { .logoIcon svg {
@@ -51,9 +53,17 @@
} }
.logoText { .logoText {
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
font-weight: 700; font-weight: 700;
letter-spacing: -0.01em; letter-spacing: -0.022em;
}
.logoTextAccent {
color: var(--brand);
}
.logo:hover .logoTextAccent {
color: var(--brand-strong);
} }
.nav { .nav {
@@ -69,7 +79,7 @@
padding: 0.625rem 1rem; padding: 0.625rem 1rem;
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 500; font-weight: 500;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
border-radius: 6px; border-radius: 6px;
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -83,7 +93,7 @@
left: 1rem; left: 1rem;
right: 1rem; right: 1rem;
height: 2px; height: 2px;
background: var(--accent-coral, #e07256); background: var(--brand);
transform: scaleX(0); transform: scaleX(0);
transform-origin: left; transform-origin: left;
transition: transform 0.25s ease; transition: transform 0.25s ease;
@@ -91,8 +101,8 @@
} }
.navLink:hover { .navLink:hover {
color: var(--text-primary, #1a1612); color: var(--text-primary);
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.navLink:hover::after { .navLink:hover::after {
@@ -100,8 +110,8 @@
} }
.navLink.active { .navLink.active {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
background: var(--accent-coral-bg); background: var(--brand-bg);
} }
.navLink.active::after { .navLink.active::after {
@@ -117,11 +127,11 @@
padding: 0 0.375rem; padding: 0 0.375rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: white; color: var(--brand-on);
background: var(--accent-coral, #e07256); background: var(--brand);
border-radius: 9999px; border-radius: 9999px;
animation: badgePop 0.3s ease-out; animation: badgePop 0.3s ease-out;
box-shadow: 0 2px 6px rgba(224, 114, 86, 0.4); box-shadow: 0 2px 6px rgba(var(--brand-rgb), 0.4);
} }
@keyframes badgePop { @keyframes badgePop {
@@ -153,7 +163,7 @@
flex: 1; flex: 1;
min-height: 56px; min-height: 56px;
padding: 0.375rem 0.25rem; padding: 0.375rem 0.25rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
font-size: 0.6875rem; font-size: 0.6875rem;
font-weight: 500; font-weight: 500;
@@ -163,11 +173,11 @@
} }
.tab:active { .tab:active {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.tabActive { .tabActive {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.tabIconWrap { .tabIconWrap {
@@ -200,9 +210,9 @@
padding: 0 5px; padding: 0 5px;
font-size: 0.6875rem; font-size: 0.6875rem;
font-weight: 700; font-weight: 700;
color: white; color: var(--brand-on);
background: var(--accent-coral, #e07256); background: var(--brand);
border: 2px solid var(--bg-card, white); border: 2px solid var(--bg-card);
border-radius: 9999px; border-radius: 9999px;
animation: badgePop 0.3s ease-out; animation: badgePop 0.3s ease-out;
} }
@@ -230,9 +240,9 @@
left: 0; left: 0;
right: 0; right: 0;
z-index: 1000; z-index: 1000;
background: var(--bg-card, white); background: var(--bg-card);
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
box-shadow: 0 -2px 12px rgba(26, 22, 18, 0.06); box-shadow: 0 -2px 12px rgba(var(--shadow-rgb), 0.06);
/* Respect iPhone home-indicator (bottom) and notch (left/right in /* Respect iPhone home-indicator (bottom) and notch (left/right in
landscape) insets so the tab content never sits under system UI. */ landscape) insets so the tab content never sits under system UI. */
padding-bottom: env(safe-area-inset-bottom, 0); padding-bottom: env(safe-area-inset-bottom, 0);
+5 -6
View File
@@ -9,6 +9,7 @@ import { useEffect } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { useComparison } from '@/hooks/useComparison'; import { useComparison } from '@/hooks/useComparison';
import { LogoMark } from './Logo';
import styles from './Navigation.module.css'; import styles from './Navigation.module.css';
type IconProps = { className?: string }; type IconProps = { className?: string };
@@ -91,13 +92,11 @@ export function Navigation() {
<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">
<span className={styles.logoIcon}> <span className={styles.logoIcon}>
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <LogoMark />
<circle cx="20" cy="20" r="18" stroke="currentColor" strokeWidth="2" /> </span>
<path d="M20 6L20 34M8 14L32 14M6 20L34 20M8 26L32 26" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /> <span className={styles.logoText}>
<circle cx="20" cy="20" r="3" fill="currentColor" /> School<span className={styles.logoTextAccent}>Compare</span>
</svg>
</span> </span>
<span className={styles.logoText}>SchoolCompare</span>
</Link> </Link>
<nav className={styles.nav} aria-label="Main navigation"> <nav className={styles.nav} aria-label="Main navigation">
+19 -19
View File
@@ -8,7 +8,7 @@
.info { .info {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.controls { .controls {
@@ -21,18 +21,18 @@
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
background: var(--bg-card, white); background: var(--bg-card);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.navButton:hover:not(:disabled) { .navButton:hover:not(:disabled) {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.navButton:disabled { .navButton:disabled {
@@ -51,32 +51,32 @@
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.pageButton { .pageButton {
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.pageButton:hover { .pageButton:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.pageButtonActive { .pageButtonActive {
background: var(--accent-coral, #e07256); background: var(--brand);
color: white; color: var(--brand-on);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
} }
.pageButtonActive:hover { .pageButtonActive:hover {
background: var(--accent-coral-dark, #c45a3f); background: var(--brand-strong);
border-color: var(--accent-coral-dark, #c45a3f); border-color: var(--brand-strong);
} }
.ellipsis { .ellipsis {
@@ -84,7 +84,7 @@
align-items: center; align-items: center;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
@media (max-width: 640px) { @media (max-width: 640px) {
@@ -12,10 +12,10 @@
.trendSummary { .trendSummary {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
padding: 0.5rem 0.875rem; padding: 0.5rem 0.875rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-left: 3px solid var(--accent-teal, #2d7d7d); border-left: 3px solid var(--brand);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
align-self: flex-start; align-self: flex-start;
} }
@@ -34,7 +34,7 @@
.covidNote, .covidNote,
.chartHint { .chartHint {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
margin: 0; margin: 0;
font-style: italic; font-style: italic;
} }
@@ -52,7 +52,7 @@
font-weight: 700; font-weight: 700;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-bottom: 0.4rem; margin-bottom: 0.4rem;
} }
@@ -63,9 +63,9 @@
} }
.chip { .chip {
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
background: var(--bg-card, #fff); background: var(--bg-card);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
padding: 0.4rem 0.75rem; padding: 0.4rem 0.75rem;
border-radius: 999px; border-radius: 999px;
font-size: 0.8125rem; font-size: 0.8125rem;
@@ -78,7 +78,7 @@
} }
.chip:active { .chip:active {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.chip:disabled { .chip:disabled {
@@ -87,21 +87,21 @@
} }
.chipActive { .chipActive {
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
color: white; color: var(--brand-on);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
font-weight: 600; font-weight: 600;
} }
.chipActive:active { .chipActive:active {
background: var(--accent-coral-dark, #c45a3f); background: var(--brand-strong);
} }
.miniLegend { .miniLegend {
display: flex; display: flex;
gap: 0.875rem; gap: 0.875rem;
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
margin-top: -0.25rem; margin-top: -0.25rem;
} }
+37 -24
View File
@@ -11,6 +11,7 @@
'use client'; 'use client';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useThemeTokens, alpha } from '@/lib/theme';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import { ChartOptions, ChartDataset } from 'chart.js'; import { ChartOptions, ChartDataset } from 'chart.js';
import '@/lib/chartSetup'; import '@/lib/chartSetup';
@@ -124,9 +125,17 @@ export function PerformanceChart({
const gapYears = axisYears.filter(y => !byYear.has(y)); const gapYears = axisYears.filter(y => !byYear.has(y));
const hasGap = gapYears.length > 0; const hasGap = gapYears.length > 0;
// Chart.js paints to a canvas, so it needs resolved colours rather than
// var(). Reading them through the token bridge keeps the series on the
// brand palette and lets them follow the theme.
const [c1, c2, c3, c4, c5, c6, cGrid, cRef, cInverse, cInverseText] = useThemeTokens(
'--chart-1', '--chart-2', '--chart-3', '--chart-4', '--chart-5', '--chart-6',
'--chart-grid', '--chart-reference', '--surface-inverse', '--text-inverse'
);
// ── Datasets (full set; mobile filters them via the active chip) ───── // ── Datasets (full set; mobile filters them via the active chip) ─────
const refLineStyle = { const refLineStyle = {
borderColor: 'rgba(90,80,70,0.35)', borderColor: cRef,
backgroundColor: 'transparent', backgroundColor: 'transparent',
borderWidth: 1.5, borderWidth: 1.5,
borderDash: [6, 4] as number[], borderDash: [6, 4] as number[],
@@ -140,8 +149,8 @@ export function PerformanceChart({
{ {
label: 'Attainment 8', label: 'Attainment 8',
data: col('attainment_8_score'), data: col('attainment_8_score'),
borderColor: '#2d7d7d', borderColor: c1,
backgroundColor: 'rgba(45,125,125,0.08)', backgroundColor: alpha('--chart-1', 0.08),
borderWidth: 2.5, borderWidth: 2.5,
tension: 0.3, tension: 0.3,
pointRadius: 4, pointRadius: 4,
@@ -151,8 +160,8 @@ export function PerformanceChart({
{ {
label: 'English & Maths Grade 4+', label: 'English & Maths Grade 4+',
data: col('english_maths_standard_pass_pct'), data: col('english_maths_standard_pass_pct'),
borderColor: '#c9a227', borderColor: c2,
backgroundColor: 'rgba(201,162,39,0.08)', backgroundColor: alpha('--chart-2', 0.08),
borderWidth: 1.5, borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3, pointRadius: 3,
@@ -161,8 +170,8 @@ export function PerformanceChart({
{ {
label: 'Progress 8', label: 'Progress 8',
data: col('progress_8_score'), data: col('progress_8_score'),
borderColor: 'rgb(139,92,246)', borderColor: c3,
backgroundColor: 'rgba(139,92,246,0.08)', backgroundColor: alpha('--chart-3', 0.08),
borderWidth: 1.5, borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3, pointRadius: 3,
@@ -179,8 +188,8 @@ export function PerformanceChart({
{ {
label: 'Reading, Writing & Maths expected %', label: 'Reading, Writing & Maths expected %',
data: col('rwm_expected_pct'), data: col('rwm_expected_pct'),
borderColor: '#2d7d7d', borderColor: c1,
backgroundColor: 'rgba(45,125,125,0.08)', backgroundColor: alpha('--chart-1', 0.08),
borderWidth: 2.5, borderWidth: 2.5,
tension: 0.3, tension: 0.3,
pointRadius: 4, pointRadius: 4,
@@ -190,8 +199,8 @@ export function PerformanceChart({
{ {
label: 'Exceeding expected level', label: 'Exceeding expected level',
data: col('rwm_high_pct'), data: col('rwm_high_pct'),
borderColor: '#c9a227', borderColor: c2,
backgroundColor: 'rgba(201,162,39,0.08)', backgroundColor: alpha('--chart-2', 0.08),
borderWidth: 1.5, borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3, pointRadius: 3,
@@ -206,8 +215,8 @@ export function PerformanceChart({
{ {
label: 'Reading progress', label: 'Reading progress',
data: col('reading_progress'), data: col('reading_progress'),
borderColor: 'rgb(59,130,246)', borderColor: c4,
backgroundColor: 'rgba(59,130,246,0.08)', backgroundColor: alpha('--chart-4', 0.08),
borderWidth: 1.5, borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3, pointRadius: 3,
@@ -217,8 +226,8 @@ export function PerformanceChart({
{ {
label: 'Writing progress', label: 'Writing progress',
data: col('writing_progress'), data: col('writing_progress'),
borderColor: 'rgb(139,92,246)', borderColor: c3,
backgroundColor: 'rgba(139,92,246,0.08)', backgroundColor: alpha('--chart-3', 0.08),
borderWidth: 1.5, borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3, pointRadius: 3,
@@ -228,8 +237,8 @@ export function PerformanceChart({
{ {
label: 'Maths progress', label: 'Maths progress',
data: col('maths_progress'), data: col('maths_progress'),
borderColor: 'rgb(236,72,153)', borderColor: c5,
backgroundColor: 'rgba(236,72,153,0.08)', backgroundColor: alpha('--chart-5', 0.08),
borderWidth: 1.5, borderWidth: 1.5,
tension: 0.3, tension: 0.3,
pointRadius: 3, pointRadius: 3,
@@ -301,7 +310,9 @@ export function PerformanceChart({
}, },
title: { display: false }, title: { display: false },
tooltip: { tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)', backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: 12, padding: 12,
titleFont: { size: 13 }, titleFont: { size: 13 },
bodyFont: { size: 12 }, bodyFont: { size: 12 },
@@ -321,7 +332,7 @@ export function PerformanceChart({
type: 'linear', display: true, position: 'left', type: 'linear', display: true, position: 'left',
title: { display: true, text: isSecondary ? 'Score / %' : 'Percentage (%)', font: { size: 11 } }, title: { display: true, text: isSecondary ? 'Score / %' : 'Percentage (%)', font: { size: 11 } },
min: 0, max: isSecondary ? undefined : 100, min: 0, max: isSecondary ? undefined : 100,
grid: { color: 'rgba(0,0,0,0.05)' }, grid: { color: cGrid },
ticks: { font: { size: 11 } }, ticks: { font: { size: 11 } },
}, },
y1: { y1: {
@@ -346,7 +357,9 @@ export function PerformanceChart({
legend: { display: false }, legend: { display: false },
title: { display: false }, title: { display: false },
tooltip: { tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)', backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: 10, padding: 10,
titleFont: { size: 12 }, titleFont: { size: 12 },
bodyFont: { size: 11 }, bodyFont: { size: 11 },
@@ -364,7 +377,7 @@ export function PerformanceChart({
y: { y: {
type: 'linear', display: true, position: 'left', type: 'linear', display: true, position: 'left',
min: mobileYBounds.min, max: mobileYBounds.max, min: mobileYBounds.min, max: mobileYBounds.max,
grid: { color: 'rgba(0,0,0,0.05)' }, grid: { color: cGrid },
ticks: { font: { size: 10 }, maxTicksLimit: 5 }, ticks: { font: { size: 10 }, maxTicksLimit: 5 },
}, },
x: { x: {
@@ -429,9 +442,9 @@ export function PerformanceChart({
belong together. */} belong together. */}
{isMobile && activeChip === 'progress' && ( {isMobile && activeChip === 'progress' && (
<div className={styles.miniLegend}> <div className={styles.miniLegend}>
<span><span className={styles.miniDot} style={{ background: 'rgb(59,130,246)' }} />Reading</span> <span><span className={styles.miniDot} style={{ background: c4 }} />Reading</span>
<span><span className={styles.miniDot} style={{ background: 'rgb(139,92,246)' }} />Writing</span> <span><span className={styles.miniDot} style={{ background: c3 }} />Writing</span>
<span><span className={styles.miniDot} style={{ background: 'rgb(236,72,153)' }} />Maths</span> <span><span className={styles.miniDot} style={{ background: c5 }} />Maths</span>
</div> </div>
)} )}
+55 -54
View File
@@ -11,14 +11,14 @@
.header h1 { .header h1 {
font-size: 2.25rem; font-size: 2.25rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.subtitle { .subtitle {
font-size: 1rem; font-size: 1rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
margin: 0; margin: 0;
line-height: 1.6; line-height: 1.6;
} }
@@ -28,7 +28,7 @@
display: flex; display: flex;
gap: 0; gap: 0;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
width: fit-content; width: fit-content;
@@ -38,8 +38,8 @@
padding: 0.625rem 1.5rem; padding: 0.625rem 1.5rem;
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 500; font-weight: 500;
background: var(--bg-card, white); background: var(--bg-card);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
border: none; border: none;
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
@@ -47,34 +47,34 @@
} }
.phaseTab:not(:last-child) { .phaseTab:not(:last-child) {
border-right: 1px solid var(--border-color, #e5dfd5); border-right: 1px solid var(--border);
} }
.phaseTab:hover { .phaseTab:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.phaseTabActive { .phaseTabActive {
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
color: white; color: var(--brand-on);
font-weight: 600; font-weight: 600;
} }
.phaseTabActive:hover { .phaseTabActive:hover {
background: var(--accent-coral-darker, #9c3f26); background: var(--brand-stronger);
} }
/* Filters */ /* Filters */
.filters { .filters {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
padding: 1.5rem; padding: 1.5rem;
margin-bottom: 2rem; margin-bottom: 2rem;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1.5rem; gap: 1.5rem;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
} }
.filterGroup { .filterGroup {
@@ -88,7 +88,7 @@
.filterLabel { .filterLabel {
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
white-space: nowrap; white-space: nowrap;
} }
@@ -98,44 +98,44 @@
max-width: 100%; max-width: 100%;
padding: 0.625rem 1rem; padding: 0.625rem 1rem;
font-size: 0.9375rem; font-size: 0.9375rem;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
background: var(--bg-card, white); background: var(--bg-card);
color: var(--text-primary, #1a1612); color: var(--text-primary);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.filterSelect:hover { .filterSelect:hover {
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
} }
.filterSelect:focus { .filterSelect:focus {
outline: none; outline: none;
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: 0 0 0 3px var(--accent-coral-bg); box-shadow: 0 0 0 3px var(--brand-bg);
} }
.filterSelect optgroup { .filterSelect optgroup {
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
padding: 0.5rem 0; padding: 0.5rem 0;
} }
.filterSelect option { .filterSelect option {
font-weight: 400; font-weight: 400;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
padding: 0.375rem 1rem; padding: 0.375rem 1rem;
} }
/* Rankings Section */ /* Rankings Section */
.rankingsSection { .rankingsSection {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
padding: 2rem; padding: 2rem;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
} }
.tableWrapper { .tableWrapper {
@@ -151,15 +151,15 @@
} }
.rankingsTable thead { .rankingsTable thead {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
.rankingsTable th { .rankingsTable th {
padding: 1rem; padding: 1rem;
text-align: left; text-align: left;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
border-bottom: 2px solid var(--border-color, #e5dfd5); border-bottom: 2px solid var(--border);
white-space: nowrap; white-space: nowrap;
text-transform: uppercase; text-transform: uppercase;
font-size: 0.75rem; font-size: 0.75rem;
@@ -206,8 +206,8 @@
.rankingsTable td { .rankingsTable td {
padding: 1rem; padding: 1rem;
border-bottom: 1px solid var(--border-color, #e5dfd5); border-bottom: 1px solid var(--border);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.rankingsTable tbody tr:last-child td { .rankingsTable tbody tr:last-child td {
@@ -216,24 +216,24 @@
/* Alternating row backgrounds for visual rhythm */ /* Alternating row backgrounds for visual rhythm */
.rankingsTable tbody tr:nth-child(even) { .rankingsTable tbody tr:nth-child(even) {
background: rgba(243, 237, 228, 0.5); background: rgba(var(--text-inverse-rgb), 0.5);
} }
.rankingsTable tbody tr:hover { .rankingsTable tbody tr:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
/* Top 3 Highlighting with Gold */ /* Top 3 Highlighting with Gold */
.rank1 { .rank1 {
background: linear-gradient(90deg, rgba(201, 162, 39, 0.15) 0%, transparent 100%) !important; background: linear-gradient(90deg, rgba(var(--status-below-rgb), 0.15) 0%, transparent 100%) !important;
} }
.rank2 { .rank2 {
background: linear-gradient(90deg, rgba(192, 192, 192, 0.15) 0%, transparent 100%) !important; background: linear-gradient(90deg, rgba(var(--medal-silver-rgb), 0.15) 0%, transparent 100%) !important;
} }
.rank3 { .rank3 {
background: linear-gradient(90deg, rgba(205, 127, 50, 0.15) 0%, transparent 100%) !important; background: linear-gradient(90deg, rgba(var(--medal-bronze-rgb), 0.15) 0%, transparent 100%) !important;
} }
.rankCell { .rankCell {
@@ -241,7 +241,7 @@
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
/* Styled rank badges for top 3 */ /* Styled rank badges for top 3 */
@@ -254,8 +254,8 @@
border-radius: 50%; border-radius: 50%;
font-size: 1rem; font-size: 1rem;
font-weight: 700; font-weight: 700;
color: white; color: var(--brand-on);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.15);
position: relative; position: relative;
} }
@@ -265,30 +265,31 @@
inset: -2px; inset: -2px;
border-radius: 50%; border-radius: 50%;
border: 2px solid transparent; border: 2px solid transparent;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.4), transparent) border-box; background: linear-gradient(135deg, rgba(var(--text-inverse-rgb), 0.4), transparent) border-box;
mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0); mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
mask-composite: exclude; mask-composite: exclude;
-webkit-mask-composite: xor; -webkit-mask-composite: xor;
} }
.rankBadge1 { .rankBadge1 {
background: linear-gradient(135deg, #c9a227 0%, #e8c547 50%, #c9a227 100%); background: linear-gradient(135deg, var(--status-below) 0%, var(--medal-gold) 50%, var(--status-below) 100%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.2);
} }
.rankBadge2 { .rankBadge2 {
background: linear-gradient(135deg, #8c8c8c 0%, #c0c0c0 50%, #8c8c8c 100%); background: linear-gradient(135deg, var(--text-muted) 0%, var(--medal-silver) 50%, var(--text-muted) 100%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.2);
} }
.rankBadge3 { .rankBadge3 {
background: linear-gradient(135deg, #a5673f 0%, #cd7f32 50%, #a5673f 100%); background: linear-gradient(135deg, var(--status-below) 0%, var(--medal-bronze) 50%, var(--status-below) 100%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.2);
} }
.rankNumber { .rankNumber {
font-variant-numeric: tabular-nums;
font-size: 1rem; font-size: 1rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.schoolCell { .schoolCell {
@@ -296,18 +297,18 @@
} }
.schoolLink { .schoolLink {
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-decoration: none; text-decoration: none;
transition: color 0.2s ease; transition: color 0.2s ease;
} }
.schoolLink:hover { .schoolLink:hover {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
.areaCell, .areaCell,
.typeCell { .typeCell {
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
/* LA subline under the school name — mobile only (Area column hidden there). */ /* LA subline under the school name — mobile only (Area column hidden there). */
@@ -315,7 +316,7 @@
display: none; display: none;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 400; font-weight: 400;
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.valueCell { .valueCell {
@@ -324,7 +325,7 @@
} }
.valueCell strong { .valueCell strong {
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
font-weight: 700; font-weight: 700;
} }
@@ -346,7 +347,7 @@
.noResults { .noResults {
text-align: center; text-align: center;
padding: 4rem 2rem; padding: 4rem 2rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.noResults p { .noResults p {
+14 -14
View File
@@ -17,7 +17,7 @@
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.1em; letter-spacing: 0.1em;
color: var(--text-muted, #6d685f); color: var(--text-muted);
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
@@ -41,7 +41,7 @@
top: 0; top: 0;
height: 100%; height: 100%;
width: 1px; width: 1px;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
/* ── Per-bar national average marker ── /* ── Per-bar national average marker ──
@@ -54,7 +54,7 @@
bottom: -3px; bottom: -3px;
width: 2px; width: 2px;
transform: translateX(-50%); transform: translateX(-50%);
background: var(--accent-coral, #e07256); background: var(--brand);
border-radius: 2px; border-radius: 2px;
z-index: 4; z-index: 4;
pointer-events: none; pointer-events: none;
@@ -69,7 +69,7 @@
width: 5px; width: 5px;
height: 5px; height: 5px;
border-radius: 50%; border-radius: 50%;
background: var(--accent-coral, #e07256); background: var(--brand);
} }
.barHeaderRight { .barHeaderRight {
@@ -81,7 +81,7 @@
.natLabel { .natLabel {
font-size: 0.55rem; font-size: 0.55rem;
font-weight: 700; font-weight: 700;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.03em; letter-spacing: 0.03em;
} }
@@ -123,18 +123,18 @@
} }
.barExpected { .barExpected {
background: var(--accent-teal-light, #3a9e9e); background: var(--brand);
} }
.barExceeding { .barExceeding {
background: var(--accent-teal, #2d7d7d); background: var(--brand);
} }
.barLabel { .barLabel {
font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; font-family: var(--font-display);
font-size: 0.78rem; font-size: 0.78rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
white-space: nowrap; white-space: nowrap;
position: relative; position: relative;
z-index: 3; z-index: 3;
@@ -142,7 +142,7 @@
.barLabelSuffix { .barLabelSuffix {
font-weight: 500; font-weight: 500;
color: var(--text-muted, #6d685f); color: var(--text-muted);
font-size: 0.6rem; font-size: 0.6rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.06em; letter-spacing: 0.06em;
@@ -153,7 +153,7 @@
position: relative; position: relative;
height: 14px; height: 14px;
margin-top: 0.3rem; margin-top: 0.3rem;
border-top: 1px solid var(--border-color, #e5dfd5); border-top: 1px solid var(--border);
} }
.rulerTick { .rulerTick {
@@ -161,7 +161,7 @@
top: 0; top: 0;
width: 1px; width: 1px;
height: 3px; height: 3px;
background: var(--border-color, #e5dfd5); background: var(--border);
} }
.rulerLabel { .rulerLabel {
@@ -169,7 +169,7 @@
top: 5px; top: 5px;
font-size: 0.5rem; font-size: 0.5rem;
font-weight: 500; font-weight: 500;
color: var(--text-muted, #6d685f); color: var(--text-muted);
transform: translateX(-50%); transform: translateX(-50%);
letter-spacing: 0.01em; letter-spacing: 0.01em;
opacity: 0.7; opacity: 0.7;
@@ -198,7 +198,7 @@
gap: 0.35rem; gap: 0.35rem;
font-size: 0.65rem; font-size: 0.65rem;
font-weight: 500; font-weight: 500;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
.legendSwatch { .legendSwatch {
+3 -3
View File
@@ -145,15 +145,15 @@ export default function SatsChart({ subjects }: SatsChartProps) {
</div> </div>
<div className={styles.legend}> <div className={styles.legend}>
<div className={styles.legendItem}> <div className={styles.legendItem}>
<div className={styles.legendSwatch} style={{ background: 'var(--accent-teal-light, #3a9e9e)' }} /> <div className={styles.legendSwatch} style={{ background: 'var(--status-above)' }} />
Expected standard Expected standard
</div> </div>
<div className={styles.legendItem}> <div className={styles.legendItem}>
<div className={styles.legendSwatch} style={{ background: 'var(--accent-teal, #2d7d7d)' }} /> <div className={styles.legendSwatch} style={{ background: 'var(--status-above)' }} />
Exceeding / high score Exceeding / high score
</div> </div>
<div className={styles.legendItem}> <div className={styles.legendItem}>
<div className={styles.legendSwatch} style={{ background: 'var(--accent-coral, #e07256)', width: '3px', height: '12px', borderRadius: '2px' }} /> <div className={styles.legendSwatch} style={{ background: 'var(--brand)', width: '3px', height: '12px', borderRadius: '2px' }} />
National average National average
</div> </div>
</div> </div>
+25 -24
View File
@@ -1,6 +1,6 @@
.card { .card {
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-left: 3px solid transparent; border-left: 3px solid transparent;
border-radius: 10px; border-radius: 10px;
padding: 1rem 1.125rem; padding: 1rem 1.125rem;
@@ -8,13 +8,13 @@
} }
.card.cardInCompare { .card.cardInCompare {
border-color: var(--accent-teal, #2d7d7d); border-color: var(--brand);
box-shadow: 0 0 0 1px var(--accent-teal, #2d7d7d); box-shadow: 0 0 0 1px var(--brand);
} }
.card:hover { .card:hover {
border-left-color: var(--accent-coral, #e07256); border-left-color: var(--brand);
box-shadow: var(--shadow-medium, 0 4px 20px rgba(26, 22, 18, 0.1)); box-shadow: var(--shadow-medium, 0 4px 20px rgba(var(--shadow-rgb), 0.1));
transform: translateY(-1px); transform: translateY(-1px);
} }
@@ -31,24 +31,24 @@
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
line-height: 1.35; line-height: 1.35;
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.title a { .title a {
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-decoration: none; text-decoration: none;
transition: color 0.2s ease; transition: color 0.2s ease;
} }
.title a:hover { .title a:hover {
color: var(--accent-coral, #e07256); color: var(--brand);
} }
.distance { .distance {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--accent-teal, #2d7d7d); color: var(--brand);
white-space: nowrap; white-space: nowrap;
background: var(--accent-teal-bg); background: var(--brand-bg);
padding: 0.125rem 0.5rem; padding: 0.125rem 0.5rem;
border-radius: 12px; border-radius: 12px;
font-weight: 500; font-weight: 500;
@@ -63,9 +63,9 @@
.metaItem { .metaItem {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
padding: 0.125rem 0.5rem; padding: 0.125rem 0.5rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 3px; border-radius: 3px;
} }
@@ -75,9 +75,9 @@
gap: 0.625rem; gap: 0.625rem;
margin-bottom: 0.875rem; margin-bottom: 0.875rem;
padding: 0.625rem 0.75rem; padding: 0.625rem 0.75rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 6px; border-radius: 6px;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
} }
.metric { .metric {
@@ -88,12 +88,13 @@
.metricLabel { .metricLabel {
font-size: 0.6875rem; font-size: 0.6875rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} }
.metricValue { .metricValue {
font-variant-numeric: tabular-nums;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.375rem; gap: 0.375rem;
@@ -101,7 +102,7 @@
.metricValue strong { .metricValue strong {
font-size: 1rem; font-size: 1rem;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-weight: 700; font-weight: 700;
} }
@@ -126,18 +127,18 @@
} }
.trendUp { .trendUp {
color: var(--accent-teal, #2d7d7d); color: var(--status-above);
background: var(--accent-teal-bg); background: var(--status-above-bg);
} }
.trendDown { .trendDown {
color: var(--accent-coral, #e07256); color: var(--brand);
background: var(--accent-coral-bg); background: var(--brand-bg);
} }
.trendStable { .trendStable {
color: var(--text-muted, #8a847a); color: var(--text-muted);
background: rgba(138, 132, 122, 0.15); background: rgba(var(--muted-rgb), 0.15);
} }
.actions { .actions {
@@ -156,7 +157,7 @@
.metricHint { .metricHint {
font-size: 0.7rem; font-size: 0.7rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
display: block; display: block;
margin-top: 1px; margin-top: 1px;
font-weight: 400; font-weight: 400;
@@ -6,7 +6,7 @@
position: relative; position: relative;
width: 100%; width: 100%;
height: 210px; height: 210px;
background: #dfe6e2; background: var(--bg-secondary);
isolation: isolate; isolation: isolate;
} }
@@ -47,7 +47,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background: background:
linear-gradient(100deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, .5) 50%, rgba(255, 255, 255, 0) 60%) #e7e2da; linear-gradient(100deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, .5) 50%, rgba(255, 255, 255, 0) 60%) var(--bg-secondary);
background-size: 200% 100%; background-size: 200% 100%;
animation: shimmer 1.4s infinite; animation: shimmer 1.4s infinite;
} }
@@ -85,11 +85,11 @@
border-radius: 999px; border-radius: 999px;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
background: rgba(255, 255, 255, .85); background: rgba(255, 255, 255, .85);
-webkit-backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px); backdrop-filter: blur(6px);
box-shadow: 0 2px 10px rgba(0, 0, 0, .16); box-shadow: 0 2px 10px rgba(var(--shadow-rgb), .16);
opacity: 0; opacity: 0;
transform: translateY(4px); transform: translateY(4px);
transition: opacity .16s ease, transform .16s ease; transition: opacity .16s ease, transform .16s ease;
@@ -118,7 +118,7 @@
rgba(255, 255, 255, .35) 35%, rgba(255, 255, 255, .35) 35%,
rgba(255, 255, 255, .75) 62%, rgba(255, 255, 255, .75) 62%,
rgba(255, 255, 255, .95) 82%, rgba(255, 255, 255, .95) 82%,
var(--bg-card, #fff) 100%); var(--bg-card) 100%);
} }
.closeBtn { .closeBtn {
@@ -134,9 +134,9 @@
border: none; border: none;
border-radius: 8px; border-radius: 8px;
background: rgba(255, 255, 255, .92); background: rgba(255, 255, 255, .92);
color: var(--text-primary, #1a1612); color: var(--text-primary);
cursor: pointer; cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, .2); box-shadow: 0 2px 10px rgba(var(--shadow-rgb), .2);
} }
.closeBtn:hover { .closeBtn:hover {
+4 -4
View File
@@ -33,12 +33,12 @@
border: 2px solid rgba(0, 0, 0, 0.2); border: 2px solid rgba(0, 0, 0, 0.2);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
color: #333; color: var(--text-primary);
transition: background 0.15s ease, color 0.15s ease; transition: background 0.15s ease, color 0.15s ease;
} }
.fullscreenBtn:hover { .fullscreenBtn:hover {
background: #f4f4f4; background: var(--bg-secondary);
color: #000; color: #000;
} }
@@ -57,9 +57,9 @@
.spinner { .spinner {
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
border: 3px solid rgba(224, 114, 86, 0.3); border: 3px solid rgba(var(--brand-rgb), 0.3);
border-radius: 50%; border-radius: 50%;
border-top-color: var(--accent-coral, #e07256); border-top-color: var(--brand);
animation: spin 0.8s linear infinite; animation: spin 0.8s linear infinite;
} }
+36 -35
View File
@@ -2,8 +2,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-left: 3px solid transparent; border-left: 3px solid transparent;
border-radius: 8px; border-radius: 8px;
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
@@ -12,17 +12,17 @@
} }
.row:hover { .row:hover {
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
} }
/* Phase border colours */ /* Phase border colours */
.phasePrimary { border-left-color: var(--phase-primary, #5b8cbf); } .phasePrimary { border-left-color: var(--phase-primary); }
.phaseAllThrough { border-left-color: var(--phase-all-through, #7a9a6d); } .phaseAllThrough { border-left-color: var(--phase-all-through); }
.phaseNursery { border-left-color: var(--phase-nursery, #e0a0b0); } .phaseNursery { border-left-color: var(--phase-nursery); }
.rowInCompare { .rowInCompare {
border-left-color: var(--accent-teal, #2d7d7d); border-left-color: var(--brand);
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
@keyframes rowFadeIn { @keyframes rowFadeIn {
@@ -50,7 +50,7 @@
.schoolName { .schoolName {
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -60,7 +60,7 @@
} }
.schoolName:hover { .schoolName:hover {
color: var(--accent-coral, #e07256); color: var(--brand);
} }
/* Phase label pill */ /* Phase label pill */
@@ -96,8 +96,8 @@
line-height: 1.4; line-height: 1.4;
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
background: rgba(26, 22, 18, 0.05); background: rgba(var(--shadow-rgb), 0.05);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
/* Line 3: stats */ /* Line 3: stats */
@@ -115,10 +115,11 @@
} }
.statValue { .statValue {
font-variant-numeric: tabular-nums;
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.2rem; gap: 0.2rem;
@@ -126,7 +127,7 @@
.statLabel { .statLabel {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
white-space: nowrap; white-space: nowrap;
} }
@@ -137,9 +138,9 @@
margin-left: 1px; margin-left: 1px;
} }
.trendUp { color: var(--accent-teal, #2d7d7d); } .trendUp { color: var(--status-above); }
.trendDown { color: var(--accent-coral, #e07256); } .trendDown { color: var(--brand); }
.trendStable { color: var(--text-muted, #8a847a); } .trendStable { color: var(--text-muted); }
/* Line 4: location */ /* Line 4: location */
.line4 { .line4 {
@@ -148,13 +149,13 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 0; gap: 0;
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.line4 span:not(:last-child)::after { .line4 span:not(:last-child)::after {
content: '·'; content: '·';
margin: 0 0.4rem; margin: 0 0.4rem;
color: var(--border-color, #e5dfd5); color: var(--border);
} }
.distanceBadge { .distanceBadge {
@@ -162,8 +163,8 @@
padding: 0.0625rem 0.375rem; padding: 0.0625rem 0.375rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
color: white; color: var(--brand-on);
border-radius: 3px; border-radius: 3px;
} }
@@ -200,23 +201,23 @@
font-weight: 400; font-weight: 400;
} }
.ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); } .ofsted1 { background: var(--status-above-bg); color: var(--status-above); }
.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #2f7a2f; } .ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); }
.ofsted3 { background: var(--accent-gold-bg); color: var(--accent-gold-text, #7a6800); } .ofsted3 { background: var(--status-below-bg); color: var(--status-below); }
.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); } .ofsted4 { background: var(--brand-bg); color: var(--brand); }
/* ── Ofsted badge variants ──────────────────────────────────────────────── */ /* ── Ofsted badge variants ──────────────────────────────────────────────── */
/* ofsted14 already defined above; these cover the two new framework states */ /* ofsted14 already defined above; these cover the two new framework states */
.ofstedRc { background: #5a3a6e; color: #fff; } .ofstedRc { background: var(--phase-secondary-text); color: var(--text-inverse); }
/* Inspected but with no overall grade (OEIF post-Sept-2024) — neutral slate, /* Inspected but with no overall grade (OEIF post-Sept-2024) — neutral slate,
distinct from the grey "Not yet inspected" pending state. */ distinct from the grey "Not yet inspected" pending state. */
.ofstedInspected { background: #e6ecf2; color: #45586b; } .ofstedInspected { background: var(--phase-primary-bg); color: var(--phase-primary-text); }
.ofstedPending { background: #e0e0e0; color: #666; } .ofstedPending { background: var(--border); color: var(--text-muted); }
/* ── vs-national delta line (under RWM metric) ──────────────────────────── */ /* ── vs-national delta line (under RWM metric) ──────────────────────────── */
.vsNational { font-size: 0.7rem; color: var(--accent-teal, #2d7d7d); font-weight: 600; } .vsNational { font-size: 0.7rem; color: var(--status-above); font-weight: 600; }
.vsNationalNeg { font-size: 0.7rem; color: var(--accent-coral, #e07256); font-weight: 600; } .vsNationalNeg { font-size: 0.7rem; color: var(--brand); font-weight: 600; }
.vsNationalFlat { font-size: 0.7rem; color: var(--text-muted, #8a847a); } .vsNationalFlat { font-size: 0.7rem; color: var(--text-muted); }
/* ── Mobile ──────────────────────────────────────────── */ /* ── Mobile ──────────────────────────────────────────── */
@media (max-width: 640px) { @media (max-width: 640px) {
@@ -257,7 +258,7 @@
/* GIAS "Open, but proposed to close" marker */ /* GIAS "Open, but proposed to close" marker */
.attrClosing { .attrClosing {
background: #fdf6e3; background: var(--status-below-bg);
color: #8a6200; color: var(--status-below);
border: 1px solid #e2c96f; border: 1px solid var(--status-below-bg);
} }
@@ -3,9 +3,9 @@
} }
.warning { .warning {
background: var(--accent-gold-bg); background: var(--status-below-bg);
border: 1px solid var(--accent-gold, #c9a227); border: 1px solid var(--status-below);
color: var(--text-primary, #1a1612); color: var(--text-primary);
padding: 1rem; padding: 1rem;
border-radius: 8px; border-radius: 8px;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -21,21 +21,21 @@
width: 100%; width: 100%;
padding: 0.875rem 1rem; padding: 0.875rem 1rem;
font-size: 1rem; font-size: 1rem;
border: 2px solid var(--border-color, #e5dfd5); border: 2px solid var(--border);
border-radius: 8px; border-radius: 8px;
background: var(--bg-card, white); background: var(--bg-card);
color: var(--text-primary, #1a1612); color: var(--text-primary);
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.searchInput::placeholder { .searchInput::placeholder {
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.searchInput:focus { .searchInput:focus {
outline: none; outline: none;
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: 0 0 0 3px var(--accent-coral-bg); box-shadow: 0 0 0 3px var(--brand-bg);
} }
.searchSpinner { .searchSpinner {
@@ -45,9 +45,9 @@
transform: translateY(-50%); transform: translateY(-50%);
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
border: 2px solid rgba(224, 114, 86, 0.3); border: 2px solid rgba(var(--brand-rgb), 0.3);
border-radius: 50%; border-radius: 50%;
border-top-color: var(--accent-coral, #e07256); border-top-color: var(--brand);
animation: spin 0.6s linear infinite; animation: spin 0.6s linear infinite;
} }
@@ -71,17 +71,17 @@
} }
.results::-webkit-scrollbar-track { .results::-webkit-scrollbar-track {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 4px; border-radius: 4px;
} }
.results::-webkit-scrollbar-thumb { .results::-webkit-scrollbar-thumb {
background: var(--border-color, #e5dfd5); background: var(--border);
border-radius: 4px; border-radius: 4px;
} }
.results::-webkit-scrollbar-thumb:hover { .results::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #8a847a); background: var(--text-muted);
} }
.resultItem { .resultItem {
@@ -90,16 +90,16 @@
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.resultItem:hover { .resultItem:hover {
background: var(--bg-card, white); background: var(--bg-card);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
} }
.schoolInfo { .schoolInfo {
@@ -110,7 +110,7 @@
.schoolName { .schoolName {
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -125,7 +125,7 @@
display: flex; display: flex;
gap: 1rem; gap: 1rem;
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.schoolMeta span { .schoolMeta span {
@@ -138,14 +138,14 @@
.noResults { .noResults {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
font-size: 0.9375rem; font-size: 0.9375rem;
} }
.hint { .hint {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
font-size: 0.9375rem; font-size: 0.9375rem;
} }
@@ -2,8 +2,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-left: 3px solid transparent; border-left: 3px solid transparent;
border-radius: 8px; border-radius: 8px;
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
@@ -12,17 +12,17 @@
} }
.row:hover { .row:hover {
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
} }
/* Phase border colours */ /* Phase border colours */
.phaseSecondary { border-left-color: var(--phase-secondary, #9b6bb0); } .phaseSecondary { border-left-color: var(--phase-secondary); }
.phaseAllThrough { border-left-color: var(--phase-all-through, #7a9a6d); } .phaseAllThrough { border-left-color: var(--phase-all-through); }
.phasePost16 { border-left-color: var(--phase-post16, #c4915e); } .phasePost16 { border-left-color: var(--phase-post16); }
.rowInCompare { .rowInCompare {
border-left-color: var(--accent-teal, #2d7d7d); border-left-color: var(--brand);
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
} }
@keyframes rowFadeIn { @keyframes rowFadeIn {
@@ -50,7 +50,7 @@
.schoolName { .schoolName {
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -60,7 +60,7 @@
} }
.schoolName:hover { .schoolName:hover {
color: var(--accent-coral, #e07256); color: var(--brand);
} }
/* Line 2: context tags */ /* Line 2: context tags */
@@ -81,8 +81,8 @@
line-height: 1.4; line-height: 1.4;
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
background: rgba(26, 22, 18, 0.05); background: rgba(var(--shadow-rgb), 0.05);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
/* Line 3: KS4 stats */ /* Line 3: KS4 stats */
@@ -100,22 +100,24 @@
} }
.statValueLarge { .statValueLarge {
font-variant-numeric: tabular-nums;
font-size: 1.125rem; font-size: 1.125rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.statValue { .statValue {
font-variant-numeric: tabular-nums;
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-family: var(--font-playfair), 'Playfair Display', serif; font-family: var(--font-display);
} }
.statLabel { .statLabel {
font-size: 0.75rem; font-size: 0.75rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
white-space: nowrap; white-space: nowrap;
} }
@@ -125,8 +127,8 @@
white-space: nowrap; white-space: nowrap;
} }
.deltaPositive { color: #2f7a2f; } .deltaPositive { color: var(--status-above); }
.deltaNegative { color: var(--accent-coral, #e07256); } .deltaNegative { color: var(--brand); }
/* Line 4: location + distance */ /* Line 4: location + distance */
.line4 { .line4 {
@@ -135,13 +137,13 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 0; gap: 0;
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
} }
.line4 span:not(:last-child)::after { .line4 span:not(:last-child)::after {
content: '·'; content: '·';
margin: 0 0.4rem; margin: 0 0.4rem;
color: var(--border-color, #e5dfd5); color: var(--border);
} }
.distanceBadge { .distanceBadge {
@@ -149,8 +151,8 @@
padding: 0.0625rem 0.375rem; padding: 0.0625rem 0.375rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
background: var(--accent-teal, #2d7d7d); background: var(--brand);
color: white; color: var(--brand-on);
border-radius: 3px; border-radius: 3px;
} }
@@ -175,15 +177,15 @@
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
line-height: 1.4; line-height: 1.4;
background: rgba(26, 22, 18, 0.05); background: rgba(var(--shadow-rgb), 0.05);
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
} }
.selectiveTag { .selectiveTag {
background: rgba(180, 120, 0, 0.1); background: rgba(var(--status-below-rgb), 0.1);
color: #8a6200; color: var(--status-below);
} }
/* ── Ofsted badge ────────────────────────────────────── */ /* ── Ofsted badge ────────────────────────────────────── */
@@ -202,17 +204,17 @@
font-weight: 400; font-weight: 400;
} }
.ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); } .ofsted1 { background: var(--status-above-bg); color: var(--status-above); }
.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #2f7a2f; } .ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); }
.ofsted3 { background: var(--accent-gold-bg); color: var(--accent-gold-text, #7a6800); } .ofsted3 { background: var(--status-below-bg); color: var(--status-below); }
.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); } .ofsted4 { background: var(--brand-bg); color: var(--brand); }
/* ── Ofsted badge variants ──────────────────────────────────────────────── */ /* ── Ofsted badge variants ──────────────────────────────────────────────── */
.ofstedRc { background: #5a3a6e; color: #fff; } .ofstedRc { background: var(--phase-secondary-text); color: var(--text-inverse); }
/* Inspected but with no overall grade (OEIF post-Sept-2024) — neutral slate, /* Inspected but with no overall grade (OEIF post-Sept-2024) — neutral slate,
distinct from the grey "Not yet inspected" pending state. */ distinct from the grey "Not yet inspected" pending state. */
.ofstedInspected { background: #e6ecf2; color: #45586b; } .ofstedInspected { background: var(--phase-primary-bg); color: var(--phase-primary-text); }
.ofstedPending { background: #e0e0e0; color: #666; } .ofstedPending { background: var(--border); color: var(--text-muted); }
/* ── Right actions column ────────────────────────────── */ /* ── Right actions column ────────────────────────────── */
.rowActions { .rowActions {
@@ -268,7 +270,7 @@
} }
.closingTag { .closingTag {
background: #fdf6e3; background: var(--status-below-bg);
color: #8a6200; color: var(--status-below);
border: 1px solid #e2c96f; border: 1px solid var(--status-below-bg);
} }
@@ -1,15 +1,15 @@
/* Special-school / PRU / AP context note: soft, informational (teal), not a /* Special-school / PRU / AP context note: soft, informational (teal), not a
warning. Shared by both detail views so the styling can't drift. */ warning. Shared by both detail views so the styling can't drift. */
.note { .note {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-left: 4px solid var(--accent-teal, #2d7d7d); border-left: 4px solid var(--brand);
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
padding: 0.7rem 1rem; padding: 0.7rem 1rem;
margin: 0 0 1rem; margin: 0 0 1rem;
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.5; line-height: 1.5;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.note strong { .note strong {
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
@@ -1,6 +1,6 @@
.moreMeasures { .moreMeasures {
margin-top: 0.5rem; margin-top: 0.5rem;
border-top: 1px solid var(--border-light); border-top: 1px solid var(--border);
padding-top: 0.75rem; padding-top: 0.75rem;
} }
@@ -8,7 +8,7 @@
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
font-size: 0.88rem; font-size: 0.88rem;
color: var(--accent-coral-dark); color: var(--brand-strong);
} }
.stripNote { .stripNote {
@@ -88,7 +88,7 @@ export function CompareAtAGlance({
)} )}
{display.kind === 'transitional' && ( {display.kind === 'transitional' && (
<> <>
<span className={s.badge} style={{ backgroundColor: '#e2e8f0', color: '#475569' }}> <span className={s.badge} style={{ backgroundColor: 'var(--bg-secondary)', color: 'var(--text-secondary)' }}>
No overall grade No overall grade
</span> </span>
<span className={s.small}>Sub-judgements only</span> <span className={s.small}>Sub-judgements only</span>
@@ -54,7 +54,7 @@ function ResultCell({ display }: { display: OfstedDisplay }) {
if (display.kind === 'transitional') { if (display.kind === 'transitional') {
return ( return (
<> <>
<span className={s.badge} style={{ backgroundColor: '#e2e8f0', color: '#475569' }}> <span className={s.badge} style={{ backgroundColor: 'var(--bg-secondary)', color: 'var(--text-secondary)' }}>
No overall grade No overall grade
</span> </span>
<span className={s.small}> <span className={s.small}>
@@ -5,10 +5,10 @@
.explore summary { .explore summary {
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
color: var(--accent-coral-dark); color: var(--brand-strong);
padding: 0.85rem 1.1rem; padding: 0.85rem 1.1rem;
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-light); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
} }
@@ -17,7 +17,7 @@
} }
.inner { .inner {
border: 1px solid var(--border-light); border: 1px solid var(--border);
border-top: none; border-top: none;
border-radius: 0 0 8px 8px; border-radius: 0 0 8px 8px;
background: var(--bg-card); background: var(--bg-card);
@@ -43,7 +43,7 @@
font-size: 0.9rem; font-size: 0.9rem;
padding: 0.4rem 0.6rem; padding: 0.4rem 0.6rem;
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--border-light); border: 1px solid var(--border);
background: var(--bg-card); background: var(--bg-card);
color: var(--text-primary); color: var(--text-primary);
max-width: 100%; max-width: 100%;
@@ -9,12 +9,12 @@
} }
.sectionTitle { .sectionTitle {
font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; font-family: var(--font-display);
font-size: 1.45rem; font-size: 1.45rem;
font-weight: 700; font-weight: 700;
margin: 0; margin: 0;
padding-left: 0.75rem; padding-left: 0.75rem;
border-left: 3px solid var(--accent-coral-dark); border-left: 3px solid var(--brand-strong);
} }
.how { .how {
@@ -36,7 +36,7 @@
back into the shared grid. */ back into the shared grid. */
.measure { .measure {
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-light); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
padding: 0.75rem 0.85rem; padding: 0.75rem 0.85rem;
@@ -59,7 +59,7 @@
gap: 0.35rem 0.5rem; gap: 0.35rem 0.5rem;
flex-wrap: wrap; flex-wrap: wrap;
padding: 0.5rem 0; padding: 0.5rem 0;
border-top: 1px solid var(--border-light); border-top: 1px solid var(--border);
margin-top: 0.5rem; margin-top: 0.5rem;
font-size: 0.95rem; font-size: 0.95rem;
} }
@@ -113,18 +113,18 @@
} }
.chipGood { .chipGood {
background: rgba(45, 125, 125, 0.14); background: rgba(var(--status-above-rgb), 0.14);
color: var(--accent-teal); color: var(--status-above);
} }
.chipWarn { .chipWarn {
background: var(--accent-gold-bg); background: var(--status-below-bg);
color: var(--accent-gold-text); color: var(--status-below);
} }
.chipBad { .chipBad {
background: var(--accent-coral-bg); background: var(--brand-bg);
color: var(--accent-coral-dark); color: var(--brand-strong);
} }
.chipNeutral { .chipNeutral {
@@ -141,18 +141,18 @@
} }
.badgeGood { .badgeGood {
background: rgba(45, 125, 125, 0.14); background: rgba(var(--status-above-rgb), 0.14);
color: var(--accent-teal); color: var(--status-above);
} }
.badgeWarn { .badgeWarn {
background: var(--accent-gold-bg); background: var(--status-below-bg);
color: var(--accent-gold-text); color: var(--status-below);
} }
.badgeBad { .badgeBad {
background: var(--accent-coral-bg); background: var(--brand-bg);
color: var(--accent-coral-dark); color: var(--brand-strong);
} }
.rcList { .rcList {
@@ -200,7 +200,7 @@
.card { .card {
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-light); border: 1px solid var(--border);
border-radius: 16px; border-radius: 16px;
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
padding: 1.25rem 1.5rem; padding: 1.25rem 1.5rem;
@@ -208,7 +208,7 @@
} }
.link { .link {
color: var(--accent-coral-dark); color: var(--brand-strong);
} }
@media (min-width: 761px) { @media (min-width: 761px) {
@@ -230,14 +230,14 @@
.rowLabel { .rowLabel {
color: var(--text-secondary); color: var(--text-secondary);
padding: 0.85rem 0.5rem 0.85rem 0; padding: 0.85rem 0.5rem 0.85rem 0;
border-bottom: 1px solid var(--border-light); border-bottom: 1px solid var(--border);
} }
.cell { .cell {
display: block; display: block;
padding: 0.85rem 0.25rem; padding: 0.85rem 0.25rem;
border-top: none; border-top: none;
border-bottom: 1px solid var(--border-light); border-bottom: 1px solid var(--border);
margin-top: 0; margin-top: 0;
} }
@@ -19,7 +19,7 @@
padding: 0.25rem 0; padding: 0.25rem 0;
font-size: 1.0625rem; font-size: 1.0625rem;
font-weight: 600; font-weight: 600;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
background: none; background: none;
border: none; border: none;
cursor: pointer; cursor: pointer;
@@ -29,7 +29,7 @@
.topBack:hover { .topBack:hover {
color: var(--accent-coral-dark, #c85a3e); color: var(--brand-strong);
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
} }
@@ -38,8 +38,8 @@
/* Header Section */ /* Header Section */
.header { .header {
position: relative; position: relative;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
/* Padding lives on .headerContent so the map band can bleed to the edges. */ /* Padding lives on .headerContent so the map band can bleed to the edges. */
padding: 0; padding: 0;
@@ -88,12 +88,13 @@
.headerHasMap .actions .btnAdd { .headerHasMap .actions .btnAdd {
/* Sits on the map hero, which renders light in both themes. */
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
border-color: transparent; border-color: transparent;
-webkit-backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px); backdrop-filter: blur(6px);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16); box-shadow: 0 2px 10px rgba(var(--shadow-rgb), 0.16);
} }
@@ -116,14 +117,14 @@
padding: 0; padding: 0;
font: inherit; font: inherit;
font-weight: 600; font-weight: 600;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
} }
.mapLink:hover { .mapLink:hover {
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
text-decoration: underline; text-decoration: underline;
text-underline-offset: 2px; text-underline-offset: 2px;
} }
@@ -137,11 +138,11 @@
.schoolName { .schoolName {
font-size: clamp(2rem, 5vw, 3.25rem); font-size: clamp(2rem, 5vw, 3.25rem);
font-weight: 700; font-weight: 700;
color: var(--text-primary, #1a1612); color: var(--text-primary);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
line-height: 1.1; line-height: 1.1;
letter-spacing: -0.01em; letter-spacing: -0.01em;
font-family: var(--font-playfair), "Playfair Display", serif; font-family: var(--font-display);
} }
@@ -155,16 +156,16 @@
.metaItem { .metaItem {
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
padding: 0.125rem 0.5rem; padding: 0.125rem 0.5rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-radius: 3px; border-radius: 3px;
} }
.address { .address {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-muted, #8a847a); color: var(--text-muted);
margin: 0 0 0.75rem; margin: 0 0 0.75rem;
} }
@@ -180,18 +181,18 @@
.headerDetail { .headerDetail {
font-size: 0.8125rem; font-size: 0.8125rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
} }
.headerDetail strong { .headerDetail strong {
color: var(--text-primary, #1a1612); color: var(--text-primary);
font-weight: 600; font-weight: 600;
} }
.headerDetail a { .headerDetail a {
color: var(--accent-teal, #2d7d7d); color: var(--brand);
text-decoration: none; text-decoration: none;
} }
@@ -213,7 +214,7 @@
border: none; border: none;
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 600; font-weight: 600;
color: var(--accent-teal, #2d7d7d); color: var(--brand);
cursor: pointer; cursor: pointer;
} }
@@ -236,25 +237,25 @@
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
white-space: nowrap; white-space: nowrap;
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.08)); box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.08));
} }
.btnAdd { .btnAdd {
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
color: white; color: var(--brand-on);
} }
.btnAdd:hover { .btnAdd:hover {
background: var(--accent-coral-darker, #9c3f26); background: var(--brand-stronger);
transform: translateY(-1px); transform: translateY(-1px);
} }
.btnRemove { .btnRemove {
background: var(--accent-teal, #2d7d7d); background: var(--brand);
color: white; color: var(--brand-on);
} }
@@ -270,13 +271,13 @@
position: sticky; position: sticky;
top: 64px; /* global header height on desktop */ top: 64px; /* global header height on desktop */
z-index: 10; z-index: 10;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-top: none; border-top: none;
border-radius: 0 0 10px 10px; border-radius: 0 0 10px 10px;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin-bottom: 1rem; margin-bottom: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.04);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
@@ -291,9 +292,9 @@
padding: 0.3rem 0.625rem; padding: 0.3rem 0.625rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
background: none; background: none;
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
@@ -302,8 +303,8 @@
.sectionNavBack:hover { .sectionNavBack:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-color: var(--accent-coral, #e07256); border-color: var(--brand);
} }
@@ -334,7 +335,7 @@
padding: 0.3rem 0.625rem; padding: 0.3rem 0.625rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 4px;
transition: all 0.15s ease; transition: all 0.15s ease;
@@ -344,21 +345,21 @@
.sectionNavLink:hover { .sectionNavLink:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.sectionNavLinkActive { .sectionNavLinkActive {
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
color: white; color: var(--brand-on);
font-weight: 600; font-weight: 600;
} }
.sectionNavLinkActive:hover { .sectionNavLinkActive:hover {
background: var(--accent-coral-dark, #c45a3f); background: var(--brand-strong);
color: white; color: var(--brand-on);
} }
@@ -373,12 +374,12 @@
gap: 0.5rem; gap: 0.5rem;
min-height: 38px; min-height: 38px;
padding: 0.34rem 0.7rem; padding: 0.34rem 0.7rem;
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
font-family: var(--font-dm-sans), "DM Sans", sans-serif; font-family: var(--font-ui);
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
@@ -396,7 +397,7 @@
font-weight: 600; font-weight: 600;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted, #6d685f); color: var(--text-muted);
} }
@@ -411,7 +412,7 @@
.sectionNavMenuChev { .sectionNavMenuChev {
flex: none; flex: none;
color: var(--text-muted, #6d685f); color: var(--text-muted);
font-size: 0.7rem; font-size: 0.7rem;
} }
@@ -426,9 +427,9 @@
width: 38px; width: 38px;
height: 38px; height: 38px;
border-radius: 9px; border-radius: 9px;
border: 1px solid var(--accent-coral-dark, #b04a2e); border: 1px solid var(--brand-strong);
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
color: white; color: var(--brand-on);
cursor: pointer; cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease; transition: background 0.15s ease, border-color 0.15s ease;
} }
@@ -447,9 +448,9 @@
width: 16px; width: 16px;
height: 16px; height: 16px;
border-radius: 50%; border-radius: 50%;
background: var(--bg-card, white); background: var(--bg-card);
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
border: 1.5px solid var(--accent-coral, #e07256); border: 1.5px solid var(--brand);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -460,9 +461,9 @@
.sectionNavCompareIconIn { .sectionNavCompareIconIn {
background: var(--bg-card, white); background: var(--bg-card);
border-color: var(--accent-teal, #2d7d7d); border-color: var(--brand);
color: var(--accent-teal, #2d7d7d); color: var(--brand);
} }
@@ -474,9 +475,9 @@
padding: 0.34rem 0.7rem; padding: 0.34rem 0.7rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: white; color: var(--brand-on);
background: var(--accent-coral-dark, #b04a2e); background: var(--brand-strong);
border: 1px solid var(--accent-coral-dark, #b04a2e); border: 1px solid var(--brand-strong);
border-radius: 999px; border-radius: 999px;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
@@ -485,21 +486,21 @@
.sectionNavCompare:hover { .sectionNavCompare:hover {
background: var(--accent-coral-darker, #9c3f26); background: var(--brand-stronger);
border-color: var(--accent-coral-darker, #9c3f26); border-color: var(--brand-stronger);
} }
.sectionNavCompareIn { .sectionNavCompareIn {
background: var(--bg-card, white); background: var(--bg-card);
color: var(--accent-teal, #2d7d7d); color: var(--brand);
border-color: var(--accent-teal, #2d7d7d); border-color: var(--brand);
} }
.sectionNavCompareIn:hover { .sectionNavCompareIn:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border-color: var(--accent-teal, #2d7d7d); border-color: var(--brand);
} }
@@ -512,8 +513,8 @@
padding: 0.34rem 0.65rem; padding: 0.34rem 0.65rem;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
border: none; border: none;
border-radius: 999px; border-radius: 999px;
cursor: pointer; cursor: pointer;
@@ -523,7 +524,7 @@
.sectionNavAll:hover { .sectionNavAll:hover {
background: var(--border-color, #e5dfd5); background: var(--border);
} }
@@ -531,7 +532,7 @@
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 1500; z-index: 1500;
background: rgba(26, 22, 18, 0.28); background: rgba(var(--shadow-rgb), 0.28);
} }
@@ -543,19 +544,19 @@
width: 230px; width: 230px;
max-height: min(70vh, 460px); max-height: min(70vh, 460px);
overflow-y: auto; overflow-y: auto;
background: var(--bg-card, white); background: var(--bg-card);
border: 1px solid var(--border-color, #e5dfd5); border: 1px solid var(--border);
border-radius: 12px; border-radius: 12px;
box-shadow: 0 18px 44px rgba(26, 22, 18, 0.2); box-shadow: 0 18px 44px rgba(var(--shadow-rgb), 0.2);
padding: 0.35rem; padding: 0.35rem;
} }
.sectionsPanelHead { .sectionsPanelHead {
font-family: var(--font-playfair), "Playfair Display", Georgia, serif; font-family: var(--font-display);
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary, #1a1612); color: var(--text-primary);
padding: 0.4rem 0.6rem 0.5rem; padding: 0.4rem 0.6rem 0.5rem;
} }
@@ -568,44 +569,44 @@
padding: 0.55rem 0.6rem; padding: 0.55rem 0.6rem;
border-radius: 8px; border-radius: 8px;
font-size: 0.85rem; font-size: 0.85rem;
color: var(--text-secondary, #5c564d); color: var(--text-secondary);
text-decoration: none; text-decoration: none;
transition: background 0.12s ease; transition: background 0.12s ease;
} }
.sectionsItem:hover { .sectionsItem:hover {
background: var(--bg-secondary, #f3ede4); background: var(--bg-secondary);
color: var(--text-primary, #1a1612); color: var(--text-primary);
} }
.sectionsItemActive { .sectionsItemActive {
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12)); background: var(--brand-bg);
color: var(--accent-coral-dark, #c45a3f); color: var(--brand-strong);
font-weight: 600; font-weight: 600;
} }
.sectionsTick { .sectionsTick {
color: var(--accent-coral-dark, #b04a2e); color: var(--brand-strong);
} }
/* GIAS "Open, but proposed to close" notice strip */ /* GIAS "Open, but proposed to close" notice strip */
.closingStrip { .closingStrip {
background: #fdf6e3; background: var(--status-below-bg);
border-left: 4px solid #e2c96f; border-left: 4px solid var(--status-below-bg);
border-radius: 0 6px 6px 0; border-radius: 0 6px 6px 0;
padding: 0.55rem 0.9rem; padding: 0.55rem 0.9rem;
margin: 0.5rem 0; margin: 0.5rem 0;
font-size: 0.88rem; font-size: 0.88rem;
color: #6e5a00; color: var(--status-below);
max-width: 68ch; max-width: 68ch;
} }
.closingStrip strong { .closingStrip strong {
color: #8a6200; color: var(--status-below);
} }
@media (max-width: 640px) { @media (max-width: 640px) {
@@ -738,7 +739,7 @@
max-height: 74vh; max-height: 74vh;
border-radius: 16px 16px 0 0; border-radius: 16px 16px 0 0;
padding: 0.5rem 0.6rem calc(0.8rem + env(safe-area-inset-bottom, 0)); padding: 0.5rem 0.6rem calc(0.8rem + env(safe-area-inset-bottom, 0));
box-shadow: 0 -10px 40px rgba(26, 22, 18, 0.25); box-shadow: 0 -10px 40px rgba(var(--shadow-rgb), 0.25);
} }
.sectionsItem { .sectionsItem {
File diff suppressed because it is too large Load Diff
+163
View File
@@ -0,0 +1,163 @@
/**
* Bridge between the CSS token layer and the bits of the UI that are painted
* from JavaScript — Chart.js datasets and Leaflet markers.
*
* Those two can't use var(), so before this they carried their own hardcoded
* hexes. That is how the chart palette drifted off-brand (a stray purple and
* Flat UI's stock blue) and why charts would have ignored the dark theme
* entirely. Reading the computed custom property keeps one source of truth.
*/
'use client';
import { useEffect, useState } from 'react';
export type ThemeToken =
| '--brand'
| '--brand-strong'
| '--brand-bg'
| '--status-above'
| '--status-above-bg'
| '--status-below'
| '--status-below-bg'
| '--text-primary'
| '--text-secondary'
| '--text-muted'
| '--text-inverse'
| '--bg-card'
| '--bg-secondary'
| '--border'
| '--border-strong'
| '--surface-inverse'
| '--chart-1'
| '--chart-2'
| '--chart-3'
| '--chart-4'
| '--chart-5'
| '--chart-6'
| '--chart-grid'
| '--chart-reference'
| '--phase-primary'
| '--phase-secondary-text'
| '--medal-gold'
| '--medal-silver'
| '--medal-bronze';
/** Values used before hydration and in any non-DOM context (SSR, tests). */
const FALLBACK: Record<string, string> = {
'--brand': '#584a9b',
'--brand-strong': '#473c80',
'--brand-bg': 'rgba(88, 74, 155, 0.10)',
'--status-above': '#0e6e66',
'--status-above-bg': 'rgba(14, 110, 102, 0.10)',
'--status-below': '#9a5b00',
'--status-below-bg': 'rgba(154, 91, 0, 0.11)',
'--text-primary': '#16202a',
'--text-secondary': '#4a545f',
'--text-muted': '#5c6570',
'--text-inverse': '#f7f7f4',
'--bg-card': '#fcfcfa',
'--bg-secondary': '#e9e8e2',
'--border': '#dedcd5',
'--border-strong': '#cdcac1',
'--surface-inverse': '#16202a',
'--chart-1': '#584a9b',
'--chart-2': '#0e6e66',
'--chart-3': '#9a5b00',
'--chart-4': '#8a7cc9',
'--chart-5': '#3e9c92',
'--chart-6': '#c9903d',
'--chart-grid': '#dedcd5',
'--chart-reference': '#5c6570',
'--phase-primary': '#4a6072',
'--phase-secondary-text': '#4e4570',
'--medal-gold': '#a67c00',
'--medal-silver': '#6f7580',
'--medal-bronze': '#8a5a2b',
};
/** Read one token. Safe to call during SSR — returns the light-theme value. */
export function token(name: ThemeToken): string {
if (typeof window === 'undefined') return FALLBACK[name];
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
return value || FALLBACK[name];
}
/**
* Same as `token`, but the component re-renders when the OS theme flips so
* canvas-painted colours don't get stranded in the previous theme.
*/
export function useThemeTokens<T extends readonly ThemeToken[]>(
...names: T
): { [K in keyof T]: string } {
const read = () => names.map((n) => token(n)) as { [K in keyof T]: string };
const [values, setValues] = useState(read);
useEffect(() => {
// Only commit when something actually changed. In the light theme the
// computed values equal the fallbacks, so this is a no-op — which matters
// because a state update here re-renders the chart and makes Chart.js
// resize a canvas that may not be laid out yet.
const sync = () => setValues((prev) => (sameValues(prev, read()) ? prev : read()));
sync();
const mq = window.matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', sync);
return () => mq.removeEventListener('change', sync);
// `names` is a fixed-length literal tuple at every call site.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return values;
}
function sameValues(a: readonly string[], b: readonly string[]): boolean {
return a.length === b.length && a.every((v, i) => v === b[i]);
}
const SERIES = [
'--series-1', '--series-2', '--series-3', '--series-4',
'--series-5', '--series-6', '--series-7', '--series-8',
] as const;
const SERIES_FALLBACK = [
'#584a9b', '#0e6e66', '#9a5b00', '#a03a5e',
'#2f5f8f', '#4a6b2f', '#7a3f7a', '#97442a',
];
/**
* Resolved series colours for Chart.js datasets, in the same index order as
* CHART_COLORS — so a school keeps its colour between the canvas and the DOM
* swatch beside it. Re-reads when the OS theme flips.
*/
export function useSeriesColors(): string[] {
const read = () =>
typeof window === 'undefined'
? SERIES_FALLBACK
: SERIES.map((n, i) => {
const v = getComputedStyle(document.documentElement).getPropertyValue(n).trim();
return v || SERIES_FALLBACK[i];
});
const [colors, setColors] = useState(read);
useEffect(() => {
// See useThemeTokens: skip the commit when nothing changed, so mounting a
// chart doesn't cost an extra render.
const sync = () => setColors((prev) => (sameValues(prev, read()) ? prev : read()));
sync();
const mq = window.matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', sync);
return () => mq.removeEventListener('change', sync);
}, []);
return colors;
}
/** rgba() built from a token's hex, for fills that need their own alpha. */
export function alpha(name: ThemeToken, a: number): string {
const hex = token(name);
const m = /^#([0-9a-f]{6})$/i.exec(hex);
if (!m) return hex;
const n = parseInt(m[1], 16);
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
}
+40 -30
View File
@@ -273,17 +273,29 @@ export function debounce<T extends (...args: any[]) => any>(
// ============================================================================ // ============================================================================
/** /**
* Chart color palette (consistent with vanilla JS app) * Series palette for comparing up to eight schools at once.
*
* These were Chart.js's stock demo colours, which is why the comparison view
* never looked like the rest of the site. They now point at the --series-*
* tokens, so the palette lives in one place and follows the theme.
*
* Every step clears WCAG AA on both grounds, so the same value works as a
* chart line and as the legend text keyed to it — there is no longer a
* separate text ramp to keep in sync.
*
* These are var() strings: correct anywhere the value lands in the DOM
* (inline styles, CSS custom properties). Canvas can't resolve var(), so
* Chart.js datasets must use `useSeriesColors()` from lib/theme instead.
*/ */
export const CHART_COLORS = [ export const CHART_COLORS = [
'rgb(75, 192, 192)', 'var(--series-1)',
'rgb(255, 99, 132)', 'var(--series-2)',
'rgb(54, 162, 235)', 'var(--series-3)',
'rgb(255, 206, 86)', 'var(--series-4)',
'rgb(153, 102, 255)', 'var(--series-5)',
'rgb(255, 159, 64)', 'var(--series-6)',
'rgb(201, 203, 207)', 'var(--series-7)',
'rgb(255, 0, 255)', 'var(--series-8)',
]; ];
/** /**
@@ -294,44 +306,42 @@ export function getChartColor(index: number): string {
} }
/** /**
* Darker counterparts of CHART_COLORS for use as TEXT on the site's light * @deprecated The series palette is now AA on both grounds, so line and text
* backgrounds — the raw series colours fail WCAG AA (e.g. 1.9:1 on cream). * share one value. Kept as an alias so call sites can migrate incrementally.
* Same hue per index so text still keys to its chart line. All ≥4.5:1 on
* #f3ede4 and lighter.
*/ */
export const CHART_TEXT_COLORS = [ export const CHART_TEXT_COLORS = CHART_COLORS;
'#256868', // teal
'#c2255c', // pink/red
'#2069ad', // blue
'#8a5a00', // yellow → ochre
'#6d43c8', // purple
'#8f4e00', // orange → brown
'#5f5952', // grey
'#a300a3', // magenta
];
export function getChartTextColor(index: number): string { export function getChartTextColor(index: number): string {
return CHART_TEXT_COLORS[index % CHART_TEXT_COLORS.length]; return CHART_TEXT_COLORS[index % CHART_TEXT_COLORS.length];
} }
/** /**
* Convert RGB color to RGBA with opacity * Add opacity to a colour. Accepts `rgb(...)` or `#rrggbb` — the series
* palette resolves to hex now that it comes from the token layer, but the
* rgb() form is still used by callers passing Chart.js literals.
*/ */
export function rgbToRgba(rgb: string, alpha: number): string { export function rgbToRgba(color: string, alpha: number): string {
return rgb.replace('rgb', 'rgba').replace(')', `, ${alpha})`); const hex = /^#([0-9a-f]{6})$/i.exec(color.trim());
if (hex) {
const n = parseInt(hex[1], 16);
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
}
return color.replace('rgb', 'rgba').replace(')', `, ${alpha})`);
} }
/** /**
* Get trend color based on direction * Trend direction colour. Teal/amber rather than green/red so the signal
* survives redgreen colour blindness; `at` is deliberately neutral so a
* school sitting on the average doesn't read as a verdict.
*/ */
export function getTrendColor(trend: 'up' | 'down' | 'stable'): string { export function getTrendColor(trend: 'up' | 'down' | 'stable'): string {
switch (trend) { switch (trend) {
case 'up': case 'up':
return '#22c55e'; // green return 'var(--status-above)';
case 'down': case 'down':
return '#ef4444'; // red return 'var(--status-below)';
case 'stable': case 'stable':
return '#6b7280'; // gray return 'var(--status-at)';
} }
} }
-6
View File
@@ -1,6 +0,0 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="8" fill="#1a1612"/>
<circle cx="20" cy="20" r="14" stroke="#e07256" stroke-width="2"/>
<path d="M20 8L20 32M12 14L28 14M10 20L30 20M12 26L28 26" stroke="#e07256" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="20" cy="20" r="3" fill="#e07256"/>
</svg>

Before

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+21 -3
View File
@@ -4,13 +4,31 @@
"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",
"background_color": "#faf7f2", "background_color": "#f2f1ed",
"theme_color": "#faf7f2", "theme_color": "#f2f1ed",
"icons": [ "icons": [
{ {
"src": "/favicon.svg", "src": "/icon.svg",
"sizes": "any", "sizes": "any",
"type": "image/svg+xml" "type": "image/svg+xml"
},
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
} }
] ]
} }