feat(design): adopt the Cohort identity — palette, type, mark and dark theme #86

Merged
tudor merged 2 commits from design/cohort-identity into main 2026-08-06 11:36:02 +00:00
56 changed files with 2019 additions and 3073 deletions
Showing only changes of commit 8ab0ac0a04 - Show all commits
+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);
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 { DM_Sans, Playfair_Display } from 'next/font/google';
import { Schibsted_Grotesk, Literata } from 'next/font/google';
import Script from 'next/script';
import { Navigation } from '@/components/Navigation';
import { Footer } from '@/components/Footer';
@@ -7,17 +7,23 @@ import { ComparisonToast } from '@/components/ComparisonToast';
import { ComparisonProvider } from '@/context/ComparisonProvider';
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'],
weight: ['400', '500', '600', '700'],
variable: '--font-dm-sans',
variable: '--font-schibsted',
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'],
weight: ['600', '700'],
variable: '--font-playfair',
weight: ['400', '600'],
style: ['normal', 'italic'],
variable: '--font-literata',
display: 'swap',
});
@@ -27,9 +33,11 @@ export const viewport: Viewport = {
// viewport-fit=cover lets us paint behind the notch / Dynamic Island so
// env(safe-area-inset-*) values resolve to real numbers on iPhone.
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: [
{ media: '(prefers-color-scheme: light)', color: '#faf7f2' },
{ media: '(prefers-color-scheme: dark)', color: '#1a1612' },
{ media: '(prefers-color-scheme: light)', color: '#f2f1ed' },
{ 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',
authors: [{ name: 'SchoolCompare' }],
manifest: '/manifest.json',
icons: {
icon: '/favicon.svg',
shortcut: '/favicon.svg',
apple: '/favicon.svg',
},
// No `icons` key on purpose: setting it here would override the file
// conventions. app/icon.svg and app/apple-icon.tsx are the source, and
// app/opengraph-image.tsx supplies og:image and twitter:image.
metadataBase: new URL('https://schoolcompare.co.uk'),
openGraph: {
type: 'website',
title: 'SchoolCompare | Compare School Performance',
@@ -60,7 +67,8 @@ export const metadata: Metadata = {
siteName: 'SchoolCompare',
},
twitter: {
card: 'summary',
// summary_large_image now that there is an image worth showing.
card: 'summary_large_image',
title: 'SchoolCompare | Compare School Performance',
description: 'Compare primary and secondary school SATs and GCSE performance across England',
},
@@ -94,8 +102,7 @@ export default function RootLayout({
strategy="afterInteractive"
/>
</head>
<body className={`${dmSans.variable} ${playfairDisplay.variable}`}>
<div className="noise-overlay" />
<body className={`${schibsted.variable} ${literata.variable}`}>
<ComparisonProvider>
<a href="#main-content" className="skip-link">Skip to main content</a>
<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 { ChartOptions } from 'chart.js';
import '@/lib/chartSetup';
import { useThemeTokens, alpha } from '@/lib/theme';
import { formatAcademicYear } from '@/lib/utils';
import { fillAcademicYears } from '@/lib/compareChartData';
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 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'> = {
responsive: true,
maintainAspectRatio: false,
@@ -49,7 +55,9 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
legend: { display: false },
title: { display: false },
tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)',
backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: 10,
titleFont: { size: 12 },
bodyFont: { size: 12 },
@@ -62,7 +70,7 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
y: {
min: yMin,
max: yMax,
grid: { color: 'rgba(0,0,0,0.05)' },
grid: { color: cGrid },
ticks: { font: { size: 11 }, maxTicksLimit: 5, callback: (v) => `${v}%` },
},
x: {
@@ -80,14 +88,14 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis
data: values,
clip: false as const,
spanGaps: false,
borderColor: '#e07256',
backgroundColor: 'rgba(224,114,86,0.10)',
borderColor: cBrand,
backgroundColor: alpha('--brand', 0.10),
borderWidth: 2.5,
tension: 0.3,
fill: true,
pointRadius: values.map((_, i) => (i === lastIdx ? 5 : 3)),
pointBackgroundColor: '#e07256',
pointBorderColor: '#fff',
pointBackgroundColor: cBrand,
pointBorderColor: cCard,
pointBorderWidth: values.map((_, i) => (i === lastIdx ? 2 : 0)),
pointHoverRadius: 6,
},
+79 -74
View File
@@ -27,7 +27,7 @@
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-bottom: 0.85rem;
padding-left: 0.85rem;
}
@@ -39,7 +39,7 @@
display: flex;
flex-direction: column;
gap: 0.1rem;
border-left: 2px solid var(--border-color, #e5dfd5);
border-left: 2px solid var(--border);
}
.navLink {
@@ -51,33 +51,33 @@
border-left: 2px solid transparent;
font-size: 0.88rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
text-decoration: none;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
}
.navLink:hover {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
.navLinkActive {
color: var(--accent-teal, #2d7d7d);
color: var(--brand);
font-weight: 700;
border-left-color: var(--accent-teal, #2d7d7d);
border-left-color: var(--brand);
}
.navDot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--border-color, #e5dfd5);
background: var(--border);
flex-shrink: 0;
transition: background 0.15s ease;
}
.navLinkActive .navDot {
background: var(--accent-teal, #2d7d7d);
background: var(--brand);
}
/* ─── Hero ───────────────────────────────────────────── */
@@ -95,8 +95,8 @@
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent-teal, #2d7d7d);
background: rgba(45, 125, 125, 0.1);
color: var(--brand);
background: rgba(var(--brand-rgb), 0.1);
padding: 0.3rem 0.7rem;
border-radius: 999px;
margin-bottom: 1rem;
@@ -107,22 +107,22 @@
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-teal, #2d7d7d);
background: var(--brand);
}
.heroTitle {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 2.75rem;
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.015em;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.85rem;
}
.heroSub {
font-size: 1.05rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
max-width: 640px;
margin: 0 auto;
line-height: 1.55;
@@ -132,7 +132,7 @@
.countdownSection {
padding: 0 0 2.5rem;
border-bottom: 1px solid var(--border-color, #e5dfd5);
border-bottom: 1px solid var(--border);
margin-bottom: 2.5rem;
}
@@ -145,7 +145,7 @@
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.countdownRail {
@@ -155,11 +155,11 @@
}
.chip {
background: var(--bg-card, #fff);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
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;
flex-direction: column;
gap: 0.2rem;
@@ -177,12 +177,12 @@
border-radius: 12px 12px 0 0;
}
.chipDeadline::before { background: var(--accent-coral, #e07256); }
.chipOffer::before { background: var(--accent-teal, #2d7d7d); }
.chipDeadline::before { background: var(--brand); }
.chipOffer::before { background: var(--status-above); }
.chipUrgent {
border-color: rgba(224, 114, 86, 0.4);
background: rgba(224, 114, 86, 0.04);
border-color: rgba(var(--brand-rgb), 0.4);
background: rgba(var(--brand-rgb), 0.04);
}
.chipTrack {
@@ -196,8 +196,8 @@
margin-bottom: 0.15rem;
}
.chipTrackDeadline { color: var(--accent-coral-dark, #b04a2e); }
.chipTrackOffer { color: var(--accent-teal, #2d7d7d); }
.chipTrackDeadline { color: var(--brand-strong); }
.chipTrackOffer { color: var(--status-above); }
.chipTrackDot {
width: 5px;
@@ -208,7 +208,8 @@
}
.chipDays {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 2.6rem;
font-weight: 700;
line-height: 1;
@@ -217,17 +218,17 @@
.chipDeadline .chipDays,
.chipUrgent .chipDays {
color: var(--accent-coral-dark, #c45a3f);
color: var(--brand-strong);
}
.chipOffer .chipDays {
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
}
.chipDaysUnit {
font-size: 0.78rem;
font-weight: 500;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-left: 0.2rem;
vertical-align: bottom;
line-height: 2;
@@ -236,14 +237,14 @@
.chipMilestone {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
line-height: 1.25;
margin-top: 0.1rem;
}
.chipDate {
font-size: 0.75rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-top: 0.05rem;
}
@@ -251,8 +252,8 @@
.track {
margin-bottom: 3rem;
background: var(--bg-card, #fff);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
}
@@ -263,7 +264,7 @@
justify-content: space-between;
gap: 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;
}
@@ -278,22 +279,22 @@
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
margin-bottom: 0.35rem;
}
.trackTitle {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.4rem;
line-height: 1.2;
}
.trackSub {
font-size: 0.9rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
line-height: 1.5;
margin: 0;
}
@@ -317,13 +318,13 @@
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.trackDateVal {
font-size: 0.88rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
/* ─── Timeline ───────────────────────────────────────── */
@@ -356,27 +357,27 @@
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--bg-card, #fff);
border: 2px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 2px solid var(--border);
flex-shrink: 0;
transition: border-color 0.2s ease, background 0.2s ease;
}
.stepDeadline .stepDot {
background: rgba(224, 114, 86, 0.15);
border-color: var(--accent-coral, #e07256);
background: rgba(var(--brand-rgb), 0.15);
border-color: var(--brand);
}
.stepOffer .stepDot {
background: rgba(45, 125, 125, 0.15);
border-color: var(--accent-teal, #2d7d7d);
background: rgba(var(--status-above-rgb), 0.15);
border-color: var(--status-above);
}
.stepLine {
width: 2px;
flex: 1;
min-height: 24px;
background: var(--border-color, #e5dfd5);
background: var(--border);
margin: 4px 0;
}
@@ -395,27 +396,28 @@
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-bottom: 0.2rem;
}
.stepDeadline .stepDate { color: var(--accent-coral-dark, #b04a2e); }
.stepOffer .stepDate { color: var(--accent-teal, #2d7d7d); }
.stepDeadline .stepDate { color: var(--brand-strong); }
.stepOffer .stepDate { color: var(--status-above); }
.stepTitle {
font-size: 0.95rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.3rem;
line-height: 1.3;
}
.stepDeadline .stepTitle { color: var(--accent-coral-dark, #c45a3f); }
.stepOffer .stepTitle { color: var(--accent-teal, #2d7d7d); }
.stepDeadline .stepTitle { color: var(--brand-strong); }
.stepOffer .stepTitle { color: var(--status-above); }
.stepBody {
font-family: var(--font-prose);
font-size: 0.88rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
line-height: 1.55;
margin: 0;
}
@@ -427,13 +429,13 @@
margin-top: 0.6rem;
font-size: 0.82rem;
font-weight: 600;
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
text-decoration: none;
transition: gap 0.2s ease, color 0.2s ease;
}
.stepTool:hover {
color: var(--accent-coral-dark, #c45a3f);
color: var(--brand-strong);
gap: 0.6rem;
}
@@ -455,9 +457,9 @@
display: flex;
gap: 0.55rem;
padding: 0.6rem 0.75rem;
background: var(--accent-gold-bg, rgba(201, 162, 39, 0.12));
border: 1px solid rgba(201, 162, 39, 0.28);
border-left: 3px solid var(--accent-gold, #c9a227);
background: var(--status-below-bg);
border: 1px solid rgba(var(--status-below-rgb), 0.28);
border-left: 3px solid var(--status-below);
border-radius: 8px;
}
@@ -467,8 +469,8 @@
height: 1.05rem;
margin-top: 0.1rem;
border-radius: 50%;
background: var(--accent-gold, #c9a227);
color: #fff;
background: var(--status-below);
color: var(--text-inverse);
font-size: 0.72rem;
font-weight: 800;
line-height: 1.05rem;
@@ -482,14 +484,15 @@
.calloutTitle {
font-size: 0.82rem;
font-weight: 700;
color: var(--accent-gold-text, #7a6800);
color: var(--status-below);
line-height: 1.3;
margin-bottom: 0.15rem;
}
.calloutBody {
font-family: var(--font-prose);
font-size: 0.82rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
line-height: 1.5;
margin: 0;
}
@@ -501,10 +504,10 @@
}
.tipsHeading {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 1.25rem;
}
@@ -515,16 +518,17 @@
}
.tipCard {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 12px;
padding: 1.25rem;
}
.tipNumber {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 1.75rem;
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;
margin-bottom: 0.6rem;
}
@@ -532,14 +536,15 @@
.tipHeading {
font-size: 0.95rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.4rem;
line-height: 1.3;
}
.tipBody {
font-family: var(--font-prose);
font-size: 0.875rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
line-height: 1.55;
margin: 0;
}
@@ -557,8 +562,8 @@
position: sticky;
top: 0;
padding-top: 0;
background: var(--bg-primary, #faf8f3);
border-bottom: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-primary);
border-bottom: 1px solid var(--border);
margin: 0 -1.25rem 1rem;
padding: 0.6rem 1.25rem;
z-index: 10;
@@ -590,8 +595,8 @@
}
.navLinkActive {
background: var(--text-primary, #1a1612);
color: #fff;
background: var(--text-primary);
color: var(--text-inverse);
border-left: none;
}
@@ -43,7 +43,7 @@
min-height: 40px;
min-width: 0;
padding: 4px 10px;
border: 1px solid rgba(0, 0, 0, .12);
border: 1px solid var(--border);
border-radius: 999px;
background: transparent;
cursor: pointer;
@@ -52,8 +52,8 @@
}
.chip[aria-pressed="true"] {
background: rgba(0, 0, 0, .06);
border-color: rgba(0, 0, 0, .35);
background: var(--bg-secondary);
border-color: var(--border-strong);
}
.chipDot {
+14 -4
View File
@@ -15,6 +15,7 @@ import { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import { ChartOptions, ChartDataset, PointStyle } from 'chart.js';
import '@/lib/chartSetup';
import { useSeriesColors, useThemeTokens } from '@/lib/theme';
import { buildCompareChart } from '@/lib/compareChartData';
import type { ComparisonData } from '@/lib/types';
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) {
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);
// 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 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;
return {
@@ -90,7 +98,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
datasets.push({
label: built.englandDataset.label,
data: built.englandDataset.data,
borderColor: 'rgba(109, 104, 95, 0.9)',
borderColor: cRef,
backgroundColor: 'transparent',
borderWidth: 1.5,
borderDash: built.englandDataset.borderDash,
@@ -138,7 +146,9 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
display: false,
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: isMobile ? 10 : 12,
titleFont: {
size: isMobile ? 12 : 14,
@@ -182,7 +192,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel,
...(isMobile && { maxTicksLimit: 5 }),
},
grid: {
color: 'rgba(0, 0, 0, 0.05)',
color: cGrid,
},
},
x: {
@@ -23,11 +23,11 @@
flex-direction: column;
gap: 0;
padding: 1rem 1.25rem;
background: var(--bg-primary, #faf7f2);
color: var(--text-primary, #2c2420);
background: var(--bg-primary);
color: var(--text-primary);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(44, 36, 32, 0.18), 0 2px 8px rgba(44, 36, 32, 0.08);
border: 1px solid var(--border-color, #e8ddd4);
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);
min-width: 280px;
}
@@ -37,8 +37,8 @@
justify-content: center;
width: 28px;
height: 28px;
background: var(--accent-coral, #e07256);
color: white;
background: var(--brand);
color: var(--brand-on);
border-radius: 50%;
font-weight: 700;
font-size: 0.9rem;
@@ -62,13 +62,13 @@
gap: 0.5rem;
font-weight: 600;
font-size: 0.875rem;
color: var(--text-primary, #2c2420);
color: var(--text-primary);
}
.collapseBtn {
background: none;
border: none;
color: var(--text-muted, #8a7a72);
color: var(--text-muted);
cursor: pointer;
padding: 0.25rem;
line-height: 1;
@@ -79,7 +79,7 @@
}
.collapseBtn:hover {
color: var(--text-primary, #2c2420);
color: var(--text-primary);
}
.schoolList {
@@ -95,13 +95,13 @@
justify-content: space-between;
gap: 0.5rem;
padding: 0.3rem 0.5rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: var(--radius-sm, 4px);
}
.schoolName {
font-size: 0.8rem;
color: var(--text-primary, #2c2420);
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -111,7 +111,7 @@
.removeSchoolBtn {
background: none;
border: none;
color: var(--text-muted, #8a7a72);
color: var(--text-muted);
cursor: pointer;
font-size: 1rem;
padding: 0 0.25rem;
@@ -121,7 +121,7 @@
}
.removeSchoolBtn:hover {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.toastActions {
@@ -129,13 +129,13 @@
align-items: center;
gap: 0.75rem;
padding-top: 0.625rem;
border-top: 1px solid var(--border-color, #e8ddd4);
border-top: 1px solid var(--border);
}
.btnClearAll {
background: none;
border: none;
color: var(--text-muted, #8a7a72);
color: var(--text-muted);
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
@@ -145,13 +145,13 @@
}
.btnClearAll:hover {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.btnCompare {
flex: 1;
background: var(--accent-coral, #e07256);
color: white;
background: var(--brand);
color: var(--brand-on);
padding: 0.6rem 1.25rem;
border-radius: 25px;
font-weight: 600;
@@ -164,7 +164,7 @@
.btnCompare:hover {
transform: translateY(-1px);
background: var(--accent-coral-dark, #c9614a);
background: var(--brand-strong);
}
/* Hidden on phones — the bottom tab bar's Compare badge already
+26 -26
View File
@@ -18,14 +18,14 @@
.header h1 {
font-size: 2.25rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.5rem;
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
}
.subtitle {
font-size: 1rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
margin: 0;
line-height: 1.6;
max-width: 60ch;
@@ -43,7 +43,7 @@
display: flex;
gap: 0;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
width: fit-content;
@@ -53,8 +53,8 @@
padding: 0.625rem 1.5rem;
font-size: 0.9375rem;
font-weight: 500;
background: var(--bg-card, white);
color: var(--text-secondary, #5c564d);
background: var(--bg-card);
color: var(--text-secondary);
border: none;
cursor: pointer;
transition: all 0.15s ease;
@@ -62,21 +62,21 @@
}
.phaseTab:not(:last-child) {
border-right: 1px solid var(--border-color, #e5dfd5);
border-right: 1px solid var(--border);
}
.phaseTab:hover {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
.phaseTabActive {
background: var(--accent-coral-dark, #b04a2e);
color: white;
background: var(--brand-strong);
color: var(--brand-on);
font-weight: 600;
}
.phaseTabActive:hover {
background: var(--accent-coral-darker, #9c3f26);
background: var(--brand-stronger);
}
/* Sticky school bar — column identity while scrolling; horizontal scroll on
@@ -87,23 +87,23 @@
position: sticky;
top: 65px;
z-index: 10;
background: var(--bg-primary, #faf7f2);
background: var(--bg-primary);
display: flex;
gap: 0.75rem;
overflow-x: auto;
padding: 0.75rem 0;
border-bottom: 1px solid var(--border-light, #e5dfd5);
border-bottom: 1px solid var(--border);
-webkit-overflow-scrolling: touch;
}
.schoolChip {
flex: 1 1 0;
min-width: 180px;
background: var(--bg-card, white);
border: 1px solid var(--border-light, #e5dfd5);
border-top: 3px solid var(--accent-coral, #e07256);
background: var(--bg-card);
border: 1px solid var(--border);
border-top: 3px solid var(--brand);
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;
display: flex;
gap: 0.55rem;
@@ -126,12 +126,12 @@
font-weight: 600;
font-size: 0.92rem;
line-height: 1.25;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-decoration: none;
}
.chipName:hover {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
/* Full name on desktop, short name on the compact mobile pills. */
@@ -142,7 +142,7 @@
.chipMeta {
display: block;
font-size: 0.78rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -185,22 +185,22 @@
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.barCaptionCount {
font-size: 0.95rem;
font-weight: 600;
line-height: 1.3;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
}
.chipRemove {
margin-left: auto;
border: none;
background: var(--bg-secondary, #f3ede4);
color: var(--text-muted, #6d685f);
background: var(--bg-secondary);
color: var(--text-muted);
border-radius: 50%;
width: 22px;
height: 22px;
@@ -212,9 +212,9 @@
.footnote {
font-size: 0.78rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-top: 2.5rem;
border-top: 1px solid var(--border-light, #e5dfd5);
border-top: 1px solid var(--border);
padding-top: 1rem;
max-width: 75ch;
}
+6 -6
View File
@@ -23,16 +23,16 @@
}
.good {
background: var(--accent-teal-bg, rgba(45, 125, 125, 0.12));
color: var(--accent-teal, #2d7d7d);
background: var(--status-above-bg);
color: var(--status-above);
}
.bad {
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12));
color: var(--accent-coral-dark, #b04a2e);
background: var(--brand-bg);
color: var(--brand-strong);
}
.neutral {
background: var(--bg-secondary, #f3ede4);
color: var(--text-muted, #8a847a);
background: var(--bg-secondary);
color: var(--text-muted);
}
+1 -1
View File
@@ -61,7 +61,7 @@
border-radius: 50%;
transform: translateX(-50%);
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 {
+11 -11
View File
@@ -5,15 +5,15 @@
justify-content: center;
padding: 4rem 2rem;
text-align: center;
background: var(--bg-card, white);
border: 2px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 2px solid var(--border);
border-radius: 16px;
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 {
color: var(--text-muted, #8a847a);
color: var(--text-muted);
margin-bottom: 1.5rem;
opacity: 0.7;
}
@@ -22,14 +22,14 @@
margin: 0 0 0.75rem 0;
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--text-primary);
font-family: var(--font-display);
}
.message {
margin: 0 0 2rem 0;
font-size: 1rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
max-width: 500px;
line-height: 1.6;
}
@@ -38,8 +38,8 @@
padding: 0.875rem 2rem;
font-size: 1rem;
font-weight: 600;
background: var(--accent-coral, #e07256);
color: white;
background: var(--brand);
color: var(--brand-on);
border: none;
border-radius: 8px;
cursor: pointer;
@@ -47,9 +47,9 @@
}
.button:hover {
background: var(--accent-coral-dark, #c45a3f);
background: var(--brand-strong);
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 {
+47 -47
View File
@@ -1,10 +1,10 @@
.filterBar {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.5rem;
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;
}
@@ -17,9 +17,9 @@
padding: 2.5rem;
max-width: 800px;
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-color: var(--accent-coral, #e07256);
border-color: var(--brand);
}
.heroMode .omniInput {
@@ -39,12 +39,12 @@
.searchHint {
margin: 0.875rem 0 0;
font-size: 0.95rem;
color: var(--text-secondary, #5a554d);
color: var(--text-secondary);
text-align: center;
}
.searchHint strong {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
font-weight: 600;
}
@@ -68,8 +68,8 @@
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1.375rem;
background: var(--accent-teal, #2d7d7d);
color: #fff;
background: var(--brand);
color: var(--brand-on);
border: none;
border-radius: 999px;
font-size: 0.9375rem;
@@ -80,7 +80,7 @@
}
.nearMeBtn:hover:not(:disabled) {
background: #235f5f;
background: var(--status-above);
transform: translateY(-1px);
}
@@ -93,8 +93,8 @@
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(255, 255, 255, 0.35);
border-top-color: #fff;
border: 2px solid rgba(var(--text-inverse-rgb), 0.35);
border-top-color: var(--brand-on);
border-radius: 50%;
animation: nearMeSpin 0.7s linear infinite;
flex-shrink: 0;
@@ -108,7 +108,7 @@
.geoError {
font-size: 0.8125rem;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
margin: 0;
max-width: 340px;
text-align: center;
@@ -134,21 +134,21 @@
flex: 1;
padding: 0.875rem 1.25rem;
font-size: 1.05rem;
border: 2px solid var(--border-color, #e5dfd5);
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
transition: all 0.2s ease;
background: var(--bg-card, white);
background: var(--bg-card);
font-family: inherit;
}
.omniInput:focus {
border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px var(--accent-coral-bg);
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-bg);
}
.omniInput::placeholder {
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.searchButton {
@@ -161,9 +161,9 @@
.spinner {
width: 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-top-color: white;
border-top-color: var(--brand-on);
animation: spin 0.8s linear infinite;
}
@@ -177,7 +177,7 @@
flex-wrap: wrap;
margin-top: 0.625rem;
padding-top: 0.625rem;
border-top: 1px solid var(--border-color, #e5dfd5);
border-top: 1px solid var(--border);
}
.filterSelect {
@@ -189,26 +189,26 @@
font-size: 0.875rem;
font-weight: 500;
font-family: inherit;
border: 1.5px solid var(--border-color, #e5dfd5);
border: 1.5px solid var(--border);
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-repeat: no-repeat;
background-position: right 0.875rem center;
background-size: 10px 6px;
cursor: pointer;
outline: none;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.filterSelect:hover {
border-color: var(--text-muted, #8a847a);
border-color: var(--text-muted);
}
.filterSelect:focus {
border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.12);
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12);
}
.clearButton {
@@ -263,7 +263,7 @@
.radiusLabel {
font-size: 0.8125rem;
font-weight: 500;
color: var(--text-secondary, #5a554d);
color: var(--text-secondary);
white-space: nowrap;
}
@@ -275,7 +275,7 @@
flex-wrap: wrap;
margin-top: 0.875rem;
padding-top: 0.875rem;
border-top: 1px solid var(--border-color, #e5dfd5);
border-top: 1px solid var(--border);
}
.controlsRow .advancedToggle {
@@ -297,14 +297,14 @@
font-size: 0.8125rem;
font-weight: 500;
font-family: inherit;
border: 1.5px solid var(--border-color, #e5dfd5);
border: 1.5px solid var(--border);
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-repeat: no-repeat;
background-position: right 0.65rem center;
background-size: 10px 6px;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
cursor: pointer;
outline: none;
transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
@@ -312,13 +312,13 @@
}
.controlSelect:hover {
border-color: var(--text-muted, #8a847a);
background-color: var(--bg-secondary, #f8f4ef);
border-color: var(--text-muted);
background-color: var(--bg-secondary);
}
.controlSelect:focus {
border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.12);
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12);
}
/* ── Advanced filters toggle ─────────────────────────── */
@@ -328,12 +328,12 @@
align-items: center;
gap: 0.375rem;
background: none;
border: 1.5px solid var(--border-color, #e5dfd5);
border: 1.5px solid var(--border);
border-radius: 999px;
padding: 0.4rem 0.875rem;
font-size: 0.8125rem;
font-weight: 500;
color: var(--text-secondary, #5a554d);
color: var(--text-secondary);
cursor: pointer;
font-family: inherit;
transition: all 0.15s ease;
@@ -341,24 +341,24 @@
}
.advancedToggle:hover {
border-color: var(--text-muted, #8a847a);
background-color: var(--bg-secondary, #f8f4ef);
color: var(--text-primary, #1a1612);
border-color: var(--text-muted);
background-color: var(--bg-secondary);
color: var(--text-primary);
}
/* 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. */
.advancedToggleActive {
border-color: var(--accent-coral, #e07256);
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12));
color: var(--accent-coral, #e07256);
border-color: var(--brand);
background: var(--brand-bg);
color: var(--brand);
font-weight: 600;
}
.advancedToggleActive:hover {
border-color: var(--accent-coral-dark, #c45a3f);
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.18));
color: var(--accent-coral-dark, #c45a3f);
border-color: var(--brand-strong);
background: var(--brand-bg);
color: var(--brand-strong);
}
.chevronDown,
+13 -13
View File
@@ -1,6 +1,6 @@
.footer {
background: var(--accent-navy, #2c3e50);
color: var(--bg-secondary, #f3ede4);
background: var(--text-primary);
color: var(--bg-secondary);
margin-top: auto;
}
@@ -27,22 +27,22 @@
margin: 0;
font-size: 1.25rem;
font-weight: 700;
color: var(--bg-primary, #faf7f2);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--bg-primary);
font-family: var(--font-display);
}
.description {
margin: 0;
font-size: 0.875rem;
line-height: 1.6;
color: rgba(250, 247, 242, 0.7);
color: rgba(var(--text-inverse-rgb), 0.7);
}
.sectionTitle {
margin: 0;
font-size: 0.875rem;
font-weight: 600;
color: var(--accent-gold, #c9a227);
color: var(--status-below);
text-transform: uppercase;
letter-spacing: 0.05em;
}
@@ -58,24 +58,24 @@
.link {
font-size: 0.875rem;
color: rgba(250, 247, 242, 0.7);
color: rgba(var(--text-inverse-rgb), 0.7);
text-decoration: none;
transition: color 0.2s ease;
}
.link:hover {
color: var(--accent-gold, #c9a227);
color: var(--status-below);
}
.linkDisabled {
font-size: 0.875rem;
color: rgba(250, 247, 242, 0.4);
color: rgba(var(--text-inverse-rgb), 0.4);
cursor: not-allowed;
}
.bottom {
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;
flex-direction: column;
gap: 0.5rem;
@@ -85,15 +85,15 @@
.disclaimer {
margin: 0;
font-size: 0.875rem;
color: rgba(250, 247, 242, 0.6);
color: rgba(var(--text-inverse-rgb), 0.6);
}
.disclaimer .link {
color: var(--accent-coral, #e07256);
color: var(--brand);
}
.disclaimer .link:hover {
color: var(--accent-gold, #c9a227);
color: var(--status-below);
}
@media (max-width: 768px) {
+163 -154
View File
@@ -16,8 +16,8 @@
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent-teal, #2d7d7d);
background: rgba(45, 125, 125, 0.1);
color: var(--brand);
background: rgba(var(--brand-rgb), 0.1);
padding: 0.3rem 0.7rem;
border-radius: 999px;
margin-bottom: 1rem;
@@ -28,37 +28,37 @@
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-teal, #2d7d7d);
background: var(--brand);
}
.heroTitle {
font-size: 3rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.85rem;
line-height: 1.08;
letter-spacing: -0.015em;
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
max-width: 840px;
margin-left: auto;
margin-right: auto;
}
.heroEmph {
color: var(--accent-coral-dark, #c45a3f);
color: var(--brand-strong);
font-style: italic;
}
.heroDescription {
font-size: 1.05rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
margin: 0 auto 0.5rem;
max-width: 680px;
line-height: 1.55;
}
.heroDescription strong {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
font-weight: 700;
}
@@ -111,7 +111,7 @@
.viewToggle {
display: flex;
gap: 0.2rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
padding: 0.2rem;
border-radius: 6px;
}
@@ -127,18 +127,18 @@
border: none;
border-radius: 5px;
cursor: pointer;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
transition: all 0.2s ease;
}
.viewToggleBtn:hover {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
.viewToggleBtn.active {
background: var(--bg-card, white);
color: var(--accent-coral-dark, #b04a2e);
box-shadow: 0 2px 4px rgba(26, 22, 18, 0.08);
background: var(--bg-card);
color: var(--brand-strong);
box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.08);
}
.viewToggleBtn svg {
@@ -166,7 +166,7 @@
.mapContainer {
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
height: 100%;
}
@@ -184,17 +184,17 @@
}
.compactList::-webkit-scrollbar-track {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 3px;
}
.compactList::-webkit-scrollbar-thumb {
background: var(--border-color, #e5dfd5);
background: var(--border);
border-radius: 3px;
}
.compactList::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #8a847a);
background: var(--text-muted);
}
/* Compact School Item */
@@ -204,15 +204,15 @@
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
transition: all 0.2s ease;
}
.compactItem:hover {
border-color: var(--accent-coral, #e07256);
box-shadow: 0 2px 6px rgba(26, 22, 18, 0.05);
border-color: var(--brand);
box-shadow: 0 2px 6px rgba(var(--shadow-rgb), 0.05);
}
.compactItemContent {
@@ -230,7 +230,7 @@
.compactItemName {
font-weight: 600;
font-size: 0.8125rem;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
@@ -238,7 +238,7 @@
}
.compactItemName:hover {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.distanceBadge {
@@ -246,8 +246,8 @@
padding: 0.0625rem 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
background: var(--accent-teal, #2d7d7d);
color: white;
background: var(--brand);
color: var(--brand-on);
border-radius: 3px;
}
@@ -255,25 +255,25 @@
display: flex;
gap: 0.375rem;
font-size: 0.6875rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
margin-bottom: 0.25rem;
}
.compactItemMeta span:not(:last-child)::after {
content: '·';
margin-left: 0.375rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.compactItemStats {
display: flex;
gap: 0.75rem;
font-size: 0.6875rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.compactStat strong {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
.compactItemActions {
@@ -292,8 +292,8 @@
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: var(--text-primary, #1a1612);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--text-primary);
font-family: var(--font-display);
}
/* Decorative coral bar under section headings */
@@ -302,14 +302,14 @@
display: block;
width: 50px;
height: 2px;
background: var(--accent-coral, #e07256);
background: var(--brand);
border-radius: 1px;
margin-top: 0.5rem;
}
.sectionDescription {
font-size: 0.9375rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
margin: 0;
line-height: 1.5;
}
@@ -336,22 +336,22 @@
.emptyState {
text-align: center;
padding: 2.5rem 1.5rem;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
}
.emptyStateTitle {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.375rem;
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
}
.emptyStateDescription {
font-size: 0.9375rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
max-width: 380px;
margin: 0 auto;
}
@@ -419,9 +419,9 @@
/* Highlighted List Item */
.highlightedItem .compactItem {
border-color: var(--accent-teal, #2d7d7d);
box-shadow: 0 0 0 1px var(--accent-teal, #2d7d7d);
background: var(--bg-secondary, #f3ede4);
border-color: var(--brand);
box-shadow: 0 0 0 1px var(--brand);
background: var(--bg-secondary);
}
/* Mobile Bottom Sheet */
@@ -443,9 +443,9 @@
.bottomSheet {
position: relative;
background: var(--bg-card, white);
background: var(--bg-card);
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;
animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@@ -467,16 +467,16 @@
right: -12px;
width: 30px;
height: 30px;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
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;
}
@@ -522,7 +522,7 @@
.quickSearchChip {
padding: 0.375rem 0.875rem;
background: var(--bg-card);
border: 1px solid var(--border-color, #e0ddd8);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.875rem;
color: var(--text-secondary);
@@ -531,9 +531,9 @@
}
.quickSearchChip:hover {
background: var(--accent-coral-dark);
color: white;
border-color: var(--accent-coral-dark);
background: var(--brand-strong);
color: var(--brand-on);
border-color: var(--brand-strong);
}
.exploringRow {
@@ -546,7 +546,7 @@
.exploringLabel {
font-size: 0.72rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
font-weight: 500;
letter-spacing: 0.04em;
text-transform: uppercase;
@@ -564,19 +564,19 @@
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.95rem;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.82rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
text-decoration: none;
transition: all 0.15s ease;
}
.exploringChip:hover {
border-color: var(--accent-coral, #e07256);
color: var(--accent-coral-dark, #c45a3f);
border-color: var(--brand);
color: var(--brand-strong);
transform: translateY(-1px);
}
@@ -606,16 +606,16 @@
}
.hiwHeading {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 1.75rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin: 0;
}
.hiwSub {
font-size: 0.875rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.hiwGrid {
@@ -625,18 +625,18 @@
}
.hiwCard {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
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;
flex-direction: column;
gap: 0.9rem;
}
.hiwVisual {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 10px;
padding: 0.9rem;
min-height: 180px;
@@ -657,11 +657,11 @@
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.hiwPhaseLabel strong {
color: var(--accent-teal, #2d7d7d);
color: var(--brand);
font-weight: 700;
}
@@ -676,20 +676,20 @@
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.hiwTitle {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 1.1rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
line-height: 1.25;
}
.hiwDesc {
font-size: 0.86rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
line-height: 1.45;
margin: 0;
}
@@ -713,7 +713,7 @@
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-bottom: 0.1rem;
}
@@ -723,16 +723,16 @@
align-items: baseline;
gap: 0.2rem;
font-size: 0.44rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.01em;
white-space: nowrap;
}
.miniRowHead strong {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 0.62rem;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
font-weight: 700;
letter-spacing: 0;
text-transform: none;
@@ -741,7 +741,7 @@
.miniTrack {
height: 5px;
border-radius: 2px;
background: rgba(45, 125, 125, 0.08);
background: rgba(var(--status-above-rgb), 0.08);
position: relative;
overflow: visible;
}
@@ -749,21 +749,21 @@
.miniBarExp {
height: 100%;
border-radius: 2px;
background: var(--accent-teal-light, #3a9e9e);
background: var(--brand);
}
.miniBarExc {
height: 100%;
border-radius: 2px;
background: var(--accent-teal, #2d7d7d);
background: var(--brand);
}
.miniNatPill {
position: absolute;
top: -9px;
transform: translateX(-50%);
background: var(--accent-coral, #e07256);
color: #fff;
background: var(--brand);
color: var(--text-inverse);
font-size: 0.4rem;
font-weight: 700;
padding: 0.05rem 0.2rem;
@@ -791,21 +791,21 @@
display: flex;
justify-content: space-between;
font-size: 0.5rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.att8Track {
height: 7px;
background: rgba(45, 125, 125, 0.08);
background: rgba(var(--status-above-rgb), 0.08);
border-radius: 3px;
position: relative;
}
.att8Fill {
height: 100%;
background: var(--accent-teal, #2d7d7d);
background: var(--brand);
border-radius: 3px;
}
@@ -814,25 +814,28 @@
top: -2px;
bottom: -2px;
width: 1.5px;
background: rgba(224, 114, 86, 0.6);
background: rgba(var(--brand-rgb), 0.6);
z-index: 2;
}
.att8Score {
font-variant-numeric: tabular-nums;
text-align: right;
}
.att8Value {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 700;
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
line-height: 1;
}
.att8Delta {
font-variant-numeric: tabular-nums;
font-size: 0.55rem;
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
@@ -853,31 +856,31 @@
align-items: center;
gap: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1.5px solid var(--border-color, #e5dfd5);
border-bottom: 1.5px solid var(--border);
}
.ofstedBullet {
display: block;
width: 3px;
height: 1em;
background: var(--accent-coral, #e07256);
background: var(--brand);
border-radius: 2px;
flex-shrink: 0;
}
.ofstedTitle {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 0.85rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
.ofstedFramework {
align-self: flex-start;
padding: 0.15rem 0.5rem;
border-radius: 4px;
background: rgba(90, 58, 110, 0.12);
color: #5a3a6e;
background: rgba(var(--phase-secondary-rgb), 0.12);
color: var(--phase-secondary-text);
font-size: 0.5rem;
font-weight: 700;
letter-spacing: 0.05em;
@@ -899,7 +902,7 @@
.rcArea {
font-size: 0.62rem;
color: var(--text-secondary, #4a453d);
color: var(--text-secondary);
}
.rcChip {
@@ -913,13 +916,13 @@
}
.rcChipGood {
background: rgba(45, 125, 125, 0.12);
color: var(--accent-teal, #2d7d7d);
background: rgba(var(--status-above-rgb), 0.12);
color: var(--status-above);
}
.rcChipNeutral {
background: var(--accent-gold-bg, rgba(180, 150, 40, 0.12));
color: var(--accent-gold-text, #7a6800);
background: var(--status-below-bg);
color: var(--status-below);
}
/* Safeguarding is a separate binary met/not-met judgement, not a graded area,
@@ -931,30 +934,30 @@
gap: 0.5rem;
margin-top: 0.05rem;
padding-top: 0.35rem;
border-top: 1px dashed var(--border-color, #e5dfd5);
border-top: 1px dashed var(--border);
}
.rcSep {
color: var(--text-muted, #8a847a);
color: var(--text-muted);
font-style: italic;
}
.rcChipMet {
background: transparent;
color: var(--accent-teal, #2d7d7d);
border: 1px solid rgba(45, 125, 125, 0.4);
color: var(--status-above);
border: 1px solid rgba(var(--status-above-rgb), 0.4);
}
.ofstedMeta {
font-size: 0.6rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
/* Compare preview */
.comparePreview {
width: 100%;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
font-size: 0.6rem;
@@ -963,23 +966,23 @@
.compareHead {
display: grid;
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;
gap: 0.35rem;
}
.compareHeadCell {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 0.62rem;
font-weight: 700;
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
line-height: 1.2;
}
.compareHeadLabel {
font-family: inherit;
font-size: 0.48rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
@@ -990,37 +993,37 @@
grid-template-columns: 1fr repeat(2, 1fr);
padding: 0.3rem 0.5rem;
gap: 0.35rem;
border-top: 1px solid var(--border-color, #e5dfd5);
border-top: 1px solid var(--border);
align-items: baseline;
}
.compareRowLabel {
font-size: 0.55rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.compareRowVal {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 0.72rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
.compareRowValHi {
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
}
.compareFoot {
font-size: 0.52rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
padding: 0.35rem 0.5rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
text-transform: uppercase;
letter-spacing: 0.06em;
text-align: center;
border-top: 1px solid var(--border-color, #e5dfd5);
border-top: 1px solid var(--border);
}
@media (max-width: 768px) {
@@ -1065,8 +1068,8 @@
grid-template-columns: 1.4fr 1fr;
gap: 2rem;
padding: 1.75rem;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
}
@@ -1078,30 +1081,34 @@
.editorialKicker {
font-size: 0.68rem;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.editorialHeading {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin: 0;
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 {
font-size: 0.92rem;
color: var(--text-secondary, #5c564d);
line-height: 1.6;
font-family: var(--font-prose);
font-size: 0.97rem;
color: var(--text-secondary);
line-height: 1.65;
margin: 0;
max-width: 62ch;
}
.factbox {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 10px;
padding: 1.25rem;
display: flex;
@@ -1110,10 +1117,10 @@
}
.factboxHeading {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-size: 1rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin: 0 0 0.85rem;
}
@@ -1122,7 +1129,7 @@
justify-content: space-between;
align-items: baseline;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border-color, #e5dfd5);
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
gap: 0.5rem;
}
@@ -1132,13 +1139,13 @@
}
.factKey {
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.factVal {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-align: right;
}
@@ -1168,7 +1175,7 @@
.sortSelect {
padding: 0.375rem 0.75rem;
border: 1px solid var(--border-color, #e0ddd8);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg-card);
color: var(--text-secondary);
@@ -1189,7 +1196,7 @@
gap: 0.375rem;
padding: 0.25rem 0.625rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color, #e0ddd8);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 0.8rem;
color: var(--text-secondary);
@@ -1216,8 +1223,9 @@
}
.loadMoreCount {
font-variant-numeric: tabular-nums;
font-size: 0.875rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
margin: 0;
}
@@ -1231,7 +1239,7 @@
.admissionsStrip {
padding: 1.5rem 0 2rem;
border-top: 1px solid var(--border-color, #e5dfd5);
border-top: 1px solid var(--border);
}
.stripHeader {
@@ -1248,12 +1256,12 @@
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.stripCta {
font-size: 0.82rem;
color: var(--accent-teal, #2d7d7d);
color: var(--brand);
font-weight: 600;
text-decoration: none;
}
@@ -1269,11 +1277,11 @@
}
.countdownChip {
background: var(--bg-card, #fff);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
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;
flex-direction: column;
gap: 0.2rem;
@@ -1292,16 +1300,16 @@
}
.countdownChipDeadline::before {
background: var(--accent-coral, #e07256);
background: var(--brand);
}
.countdownChipOffer::before {
background: var(--accent-teal, #2d7d7d);
background: var(--status-above);
}
.countdownChipUrgent {
border-color: rgba(224, 114, 86, 0.4);
background: rgba(224, 114, 86, 0.04);
border-color: rgba(var(--brand-rgb), 0.4);
background: rgba(var(--brand-rgb), 0.04);
}
.chipTrack {
@@ -1316,11 +1324,11 @@
}
.chipTrackDeadline {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.chipTrackOffer {
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
}
.chipTrackDot {
@@ -1332,7 +1340,8 @@
}
.chipDays {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-variant-numeric: tabular-nums;
font-family: var(--font-display);
font-size: 2.6rem;
font-weight: 700;
line-height: 1;
@@ -1341,18 +1350,18 @@
.countdownChipDeadline .chipDays,
.countdownChipUrgent .chipDays {
color: var(--accent-coral-dark, #c45a3f);
color: var(--brand-strong);
}
.countdownChipOffer .chipDays {
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
}
.chipDaysUnit {
font-family: 'DM Sans', sans-serif;
font-family: var(--font-ui);
font-size: 0.78rem;
font-weight: 500;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-left: 0.2rem;
vertical-align: bottom;
line-height: 2;
@@ -1361,14 +1370,14 @@
.chipMilestone {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
line-height: 1.25;
margin-top: 0.1rem;
}
.chipDate {
font-size: 0.75rem;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-top: 0.05rem;
}
+10 -10
View File
@@ -573,10 +573,10 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm
rwmDelta == null
? {}
: rwmDelta >= 2
? { fontSize: '0.7rem', color: 'var(--accent-teal, #2d7d7d)', fontWeight: 600 }
? { fontSize: '0.7rem', color: 'var(--status-above)', fontWeight: 600 }
: rwmDelta <= -2
? { fontSize: '0.7rem', color: 'var(--accent-coral, #e07256)', fontWeight: 600 }
: { fontSize: '0.7rem', color: 'var(--text-muted, #8a847a)' };
? { fontSize: '0.7rem', color: 'var(--brand)', fontWeight: 600 }
: { fontSize: '0.7rem', color: 'var(--text-muted)' };
return (
<div className={styles.compactItem}>
@@ -601,13 +601,13 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm
fontWeight: 600,
borderRadius: '3px',
whiteSpace: 'nowrap',
...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--accent-teal-bg)', color: 'var(--accent-teal, #2d7d7d)' } :
ofstedBadge.cssClass === 'ofsted2' ? { background: 'rgba(60,140,60,0.12)', color: '#2f7a2f' } :
ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--accent-gold-bg)', color: 'var(--accent-gold-text, #7a6800)' } :
ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--accent-coral-bg)', color: 'var(--accent-coral, #e07256)' } :
ofstedBadge.cssClass === 'ofstedRc' ? { background: '#5a3a6e', color: '#fff' } :
ofstedBadge.cssClass === 'ofstedPending' ? { background: '#e0e0e0', color: '#666' } :
{ background: '#e0e0e0', color: '#666' }),
...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } :
ofstedBadge.cssClass === 'ofsted2' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } :
ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--status-below-bg)', color: 'var(--status-below)' } :
ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--brand-bg)', color: 'var(--brand)' } :
ofstedBadge.cssClass === 'ofstedRc' ? { background: 'var(--phase-secondary-text)', color: 'var(--text-inverse)' } :
ofstedBadge.cssClass === 'ofstedPending' ? { background: 'var(--bg-secondary)', color: 'var(--text-muted)' } :
{ background: 'var(--bg-secondary)', color: 'var(--text-muted)' }),
}}
>
{ofstedBadge.label}
+10 -10
View File
@@ -11,7 +11,7 @@
/* font-size:0 hides the button's own "?" text node; the ::before glyph
below carries the visible circled "?" at its own explicit size. */
font-size: 0;
color: var(--text-muted, #8a7a72);
color: var(--text-muted);
cursor: help;
line-height: 1;
user-select: none;
@@ -34,17 +34,17 @@
.icon:hover,
.icon[aria-expanded='true'],
.icon:focus-visible {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.tooltip {
z-index: 9999;
width: max-content;
max-width: min(260px, calc(100vw - 24px));
background: var(--bg-primary, #faf7f2);
border: 1px solid var(--border-color, #e8ddd4);
background: var(--bg-primary);
border: 1px solid var(--border);
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;
display: flex;
flex-direction: column;
@@ -52,26 +52,26 @@
}
.arrow {
fill: var(--bg-primary, #faf7f2);
stroke: var(--border-color, #e8ddd4);
fill: var(--bg-primary);
stroke: var(--border);
stroke-width: 1px;
}
.label {
font-weight: 600;
font-size: 0.75rem;
color: var(--text-primary, #2c2420);
color: var(--text-primary);
}
.plain {
font-size: 0.75rem;
color: var(--text-secondary, #5a4a44);
color: var(--text-secondary);
line-height: 1.4;
}
.detail {
font-size: 0.7rem;
color: var(--text-muted, #8a7a72);
color: var(--text-muted);
line-height: 1.4;
margin-top: 0.1rem;
}
@@ -22,10 +22,10 @@ interface LeafletHeroMapInnerProps {
// reliably inside Leaflet's divIcon HTML string (no CSS-module dependency).
const PIN_HTML = `
<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))">
<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"/>
<circle cx="13" cy="13" r="5" fill="#fff"/>
<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="var(--text-inverse)"/>
</svg>
</div>`;
+16 -16
View File
@@ -48,21 +48,21 @@ function buildPopupBadge(school: School): PopupBadge {
// ofsted_framework is the raw event grouping, never "ReportCard").
if (school.ofsted_rc_date) {
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 yearStr = year ? ` · ${year}` : '';
if (school.ofsted_grade) {
const labels: Record<number, string> = { 1: 'Outstanding', 2: 'Good', 3: 'Req. Improvement', 4: 'Inadequate' };
const colours: Record<number, string> = {
1: 'background:#d4f0ea;color:#2d7d7d',
2: 'background:rgba(60,140,60,0.12);color:#2f7a2f',
3: 'background:#fef3cd;color:#7a6800',
4: 'background:#fde8e0;color:#e07256',
1: 'background:var(--status-above-bg);color:var(--status-above)',
2: 'background:var(--status-above-bg);color:var(--status-above)',
3: 'background:var(--status-below-bg);color:var(--status-below)',
4: 'background:var(--brand-bg);color:var(--brand)',
};
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) {
@@ -99,7 +99,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
const refIcon = L.divIcon({
html: `<div style="
width: 20px; height: 20px;
background: #e07256;
background: var(--brand);
border: 3px solid white;
border-radius: 50%;
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 ? '+' : '';
// Att8 scores range 090 in 0.1 increments; ±0.5 is meaningful here
// 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');
deltaLine = `<div style="font-size:11px;font-weight:600;color:${colour}">${sign}${diff} vs ${laName} avg</div>`;
}
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:11px;color:#8a847a;margin-left:4px">Attainment 8</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:var(--text-muted);margin-left:4px">Attainment 8</span>
${deltaLine}
</div>`;
} else if (school.rwm_expected_pct != null) {
@@ -159,7 +159,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint,
let deltaLine = '';
if (nationalAvgRwm != null) {
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 =
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>`;
}
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:11px;color:#8a847a;margin-left:4px">Reading, Writing &amp; Maths</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:var(--text-muted);margin-left:4px">Reading, Writing &amp; Maths</span>
${deltaLine}
</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">
<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>
</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}
</div>
${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>`;
marker.bindPopup(popupContent);
@@ -5,19 +5,19 @@
}
.skeletonCard {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
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 {
background: linear-gradient(
90deg,
var(--bg-secondary, #f3ede4) 25%,
rgba(224, 114, 86, 0.08) 50%,
var(--bg-secondary, #f3ede4) 75%
var(--bg-secondary) 25%,
rgba(var(--brand-rgb), 0.08) 50%,
var(--bg-secondary) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
@@ -56,7 +56,7 @@
gap: 1rem;
margin-bottom: 1.5rem;
padding: 1rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 8px;
}
@@ -82,11 +82,11 @@
}
.skeletonListItem {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
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 {
+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 {
position: fixed;
inset: 0;
background: rgba(26, 22, 18, 0.6);
background: rgba(var(--shadow-rgb), 0.6);
display: flex;
align-items: center;
justify-content: center;
@@ -20,15 +20,15 @@
}
.modal {
background: var(--bg-card, white);
background: var(--bg-card);
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;
overflow: hidden;
display: flex;
flex-direction: column;
animation: slideIn 0.3s ease;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
}
@keyframes slideIn {
@@ -62,22 +62,22 @@
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid var(--border-color, #e5dfd5);
border-bottom: 1px solid var(--border);
}
.title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--text-primary);
font-family: var(--font-display);
}
.closeButton {
padding: 0.5rem;
background: transparent;
border: none;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
@@ -87,8 +87,8 @@
}
.closeButton:hover {
background: var(--bg-secondary, #f3ede4);
color: var(--accent-coral, #e07256);
background: var(--bg-secondary);
color: var(--brand);
}
.content {
@@ -102,16 +102,16 @@
}
.content::-webkit-scrollbar-track {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
.content::-webkit-scrollbar-thumb {
background: var(--border-color, #e5dfd5);
background: var(--border);
border-radius: 4px;
}
.content::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #8a847a);
background: var(--text-muted);
}
@media (max-width: 640px) {
+38 -28
View File
@@ -2,9 +2,9 @@
position: sticky;
top: 0;
z-index: 1000;
background: var(--bg-card, white);
border-bottom: 1px solid var(--border-color, #e5dfd5);
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06);
background: var(--bg-card);
border-bottom: 1px solid var(--border);
box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06);
}
.container {
@@ -27,7 +27,7 @@
margin: -0.375rem -0.5rem;
padding: 0.375rem 0.5rem;
text-decoration: none;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 700;
transition: color 0.2s ease;
@@ -35,14 +35,16 @@
}
.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 {
display: inline-flex;
width: 36px;
height: 36px;
color: var(--accent-coral, #e07256);
width: 32px;
height: 32px;
color: var(--text-primary);
}
.logoIcon svg {
@@ -51,9 +53,17 @@
}
.logoText {
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
font-weight: 700;
letter-spacing: -0.01em;
letter-spacing: -0.022em;
}
.logoTextAccent {
color: var(--brand);
}
.logo:hover .logoTextAccent {
color: var(--brand-strong);
}
.nav {
@@ -69,7 +79,7 @@
padding: 0.625rem 1rem;
font-size: 0.9375rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
text-decoration: none;
border-radius: 6px;
transition: all 0.2s ease;
@@ -83,7 +93,7 @@
left: 1rem;
right: 1rem;
height: 2px;
background: var(--accent-coral, #e07256);
background: var(--brand);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.25s ease;
@@ -91,8 +101,8 @@
}
.navLink:hover {
color: var(--text-primary, #1a1612);
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary);
background: var(--bg-secondary);
}
.navLink:hover::after {
@@ -100,8 +110,8 @@
}
.navLink.active {
color: var(--accent-coral-dark, #b04a2e);
background: var(--accent-coral-bg);
color: var(--brand-strong);
background: var(--brand-bg);
}
.navLink.active::after {
@@ -117,11 +127,11 @@
padding: 0 0.375rem;
font-size: 0.75rem;
font-weight: 600;
color: white;
background: var(--accent-coral, #e07256);
color: var(--brand-on);
background: var(--brand);
border-radius: 9999px;
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 {
@@ -153,7 +163,7 @@
flex: 1;
min-height: 56px;
padding: 0.375rem 0.25rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
text-decoration: none;
font-size: 0.6875rem;
font-weight: 500;
@@ -163,11 +173,11 @@
}
.tab:active {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
.tabActive {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.tabIconWrap {
@@ -200,9 +210,9 @@
padding: 0 5px;
font-size: 0.6875rem;
font-weight: 700;
color: white;
background: var(--accent-coral, #e07256);
border: 2px solid var(--bg-card, white);
color: var(--brand-on);
background: var(--brand);
border: 2px solid var(--bg-card);
border-radius: 9999px;
animation: badgePop 0.3s ease-out;
}
@@ -230,9 +240,9 @@
left: 0;
right: 0;
z-index: 1000;
background: var(--bg-card, white);
border-top: 1px solid var(--border-color, #e5dfd5);
box-shadow: 0 -2px 12px rgba(26, 22, 18, 0.06);
background: var(--bg-card);
border-top: 1px solid var(--border);
box-shadow: 0 -2px 12px rgba(var(--shadow-rgb), 0.06);
/* Respect iPhone home-indicator (bottom) and notch (left/right in
landscape) insets so the tab content never sits under system UI. */
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 { usePathname } from 'next/navigation';
import { useComparison } from '@/hooks/useComparison';
import { LogoMark } from './Logo';
import styles from './Navigation.module.css';
type IconProps = { className?: string };
@@ -91,13 +92,11 @@ export function Navigation() {
<div className={styles.container}>
<Link href="/" className={styles.logo} aria-label="SchoolCompare home">
<span className={styles.logoIcon}>
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<circle cx="20" cy="20" r="18" stroke="currentColor" strokeWidth="2" />
<path d="M20 6L20 34M8 14L32 14M6 20L34 20M8 26L32 26" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<circle cx="20" cy="20" r="3" fill="currentColor" />
</svg>
<LogoMark />
</span>
<span className={styles.logoText}>
School<span className={styles.logoTextAccent}>Compare</span>
</span>
<span className={styles.logoText}>SchoolCompare</span>
</Link>
<nav className={styles.nav} aria-label="Main navigation">
+19 -19
View File
@@ -8,7 +8,7 @@
.info {
font-size: 0.875rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.controls {
@@ -21,18 +21,18 @@
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
background: var(--bg-card, white);
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.navButton:hover:not(:disabled) {
background: var(--bg-secondary, #f3ede4);
border-color: var(--accent-coral, #e07256);
color: var(--text-primary, #1a1612);
background: var(--bg-secondary);
border-color: var(--brand);
color: var(--text-primary);
}
.navButton:disabled {
@@ -51,32 +51,32 @@
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
font-weight: 500;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.pageButton {
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.pageButton:hover {
background: var(--bg-secondary, #f3ede4);
border-color: var(--accent-coral, #e07256);
color: var(--text-primary, #1a1612);
background: var(--bg-secondary);
border-color: var(--brand);
color: var(--text-primary);
}
.pageButtonActive {
background: var(--accent-coral, #e07256);
color: white;
border-color: var(--accent-coral, #e07256);
background: var(--brand);
color: var(--brand-on);
border-color: var(--brand);
}
.pageButtonActive:hover {
background: var(--accent-coral-dark, #c45a3f);
border-color: var(--accent-coral-dark, #c45a3f);
background: var(--brand-strong);
border-color: var(--brand-strong);
}
.ellipsis {
@@ -84,7 +84,7 @@
align-items: center;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
@media (max-width: 640px) {
@@ -12,10 +12,10 @@
.trendSummary {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
padding: 0.5rem 0.875rem;
background: var(--bg-secondary, #f3ede4);
border-left: 3px solid var(--accent-teal, #2d7d7d);
background: var(--bg-secondary);
border-left: 3px solid var(--brand);
border-radius: 0 4px 4px 0;
align-self: flex-start;
}
@@ -34,7 +34,7 @@
.covidNote,
.chartHint {
font-size: 0.75rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
margin: 0;
font-style: italic;
}
@@ -52,7 +52,7 @@
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-bottom: 0.4rem;
}
@@ -63,9 +63,9 @@
}
.chip {
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card, #fff);
color: var(--text-secondary, #5c564d);
border: 1px solid var(--border);
background: var(--bg-card);
color: var(--text-secondary);
padding: 0.4rem 0.75rem;
border-radius: 999px;
font-size: 0.8125rem;
@@ -78,7 +78,7 @@
}
.chip:active {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
.chip:disabled {
@@ -87,21 +87,21 @@
}
.chipActive {
background: var(--accent-coral-dark, #b04a2e);
color: white;
border-color: var(--accent-coral, #e07256);
background: var(--brand-strong);
color: var(--brand-on);
border-color: var(--brand);
font-weight: 600;
}
.chipActive:active {
background: var(--accent-coral-dark, #c45a3f);
background: var(--brand-strong);
}
.miniLegend {
display: flex;
gap: 0.875rem;
font-size: 0.75rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
margin-top: -0.25rem;
}
+37 -24
View File
@@ -11,6 +11,7 @@
'use client';
import { useMemo, useState } from 'react';
import { useThemeTokens, alpha } from '@/lib/theme';
import { Line } from 'react-chartjs-2';
import { ChartOptions, ChartDataset } from 'chart.js';
import '@/lib/chartSetup';
@@ -124,9 +125,17 @@ export function PerformanceChart({
const gapYears = axisYears.filter(y => !byYear.has(y));
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) ─────
const refLineStyle = {
borderColor: 'rgba(90,80,70,0.35)',
borderColor: cRef,
backgroundColor: 'transparent',
borderWidth: 1.5,
borderDash: [6, 4] as number[],
@@ -140,8 +149,8 @@ export function PerformanceChart({
{
label: 'Attainment 8',
data: col('attainment_8_score'),
borderColor: '#2d7d7d',
backgroundColor: 'rgba(45,125,125,0.08)',
borderColor: c1,
backgroundColor: alpha('--chart-1', 0.08),
borderWidth: 2.5,
tension: 0.3,
pointRadius: 4,
@@ -151,8 +160,8 @@ export function PerformanceChart({
{
label: 'English & Maths Grade 4+',
data: col('english_maths_standard_pass_pct'),
borderColor: '#c9a227',
backgroundColor: 'rgba(201,162,39,0.08)',
borderColor: c2,
backgroundColor: alpha('--chart-2', 0.08),
borderWidth: 1.5,
tension: 0.3,
pointRadius: 3,
@@ -161,8 +170,8 @@ export function PerformanceChart({
{
label: 'Progress 8',
data: col('progress_8_score'),
borderColor: 'rgb(139,92,246)',
backgroundColor: 'rgba(139,92,246,0.08)',
borderColor: c3,
backgroundColor: alpha('--chart-3', 0.08),
borderWidth: 1.5,
tension: 0.3,
pointRadius: 3,
@@ -179,8 +188,8 @@ export function PerformanceChart({
{
label: 'Reading, Writing & Maths expected %',
data: col('rwm_expected_pct'),
borderColor: '#2d7d7d',
backgroundColor: 'rgba(45,125,125,0.08)',
borderColor: c1,
backgroundColor: alpha('--chart-1', 0.08),
borderWidth: 2.5,
tension: 0.3,
pointRadius: 4,
@@ -190,8 +199,8 @@ export function PerformanceChart({
{
label: 'Exceeding expected level',
data: col('rwm_high_pct'),
borderColor: '#c9a227',
backgroundColor: 'rgba(201,162,39,0.08)',
borderColor: c2,
backgroundColor: alpha('--chart-2', 0.08),
borderWidth: 1.5,
tension: 0.3,
pointRadius: 3,
@@ -206,8 +215,8 @@ export function PerformanceChart({
{
label: 'Reading progress',
data: col('reading_progress'),
borderColor: 'rgb(59,130,246)',
backgroundColor: 'rgba(59,130,246,0.08)',
borderColor: c4,
backgroundColor: alpha('--chart-4', 0.08),
borderWidth: 1.5,
tension: 0.3,
pointRadius: 3,
@@ -217,8 +226,8 @@ export function PerformanceChart({
{
label: 'Writing progress',
data: col('writing_progress'),
borderColor: 'rgb(139,92,246)',
backgroundColor: 'rgba(139,92,246,0.08)',
borderColor: c3,
backgroundColor: alpha('--chart-3', 0.08),
borderWidth: 1.5,
tension: 0.3,
pointRadius: 3,
@@ -228,8 +237,8 @@ export function PerformanceChart({
{
label: 'Maths progress',
data: col('maths_progress'),
borderColor: 'rgb(236,72,153)',
backgroundColor: 'rgba(236,72,153,0.08)',
borderColor: c5,
backgroundColor: alpha('--chart-5', 0.08),
borderWidth: 1.5,
tension: 0.3,
pointRadius: 3,
@@ -301,7 +310,9 @@ export function PerformanceChart({
},
title: { display: false },
tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)',
backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: 12,
titleFont: { size: 13 },
bodyFont: { size: 12 },
@@ -321,7 +332,7 @@ export function PerformanceChart({
type: 'linear', display: true, position: 'left',
title: { display: true, text: isSecondary ? 'Score / %' : 'Percentage (%)', font: { size: 11 } },
min: 0, max: isSecondary ? undefined : 100,
grid: { color: 'rgba(0,0,0,0.05)' },
grid: { color: cGrid },
ticks: { font: { size: 11 } },
},
y1: {
@@ -346,7 +357,9 @@ export function PerformanceChart({
legend: { display: false },
title: { display: false },
tooltip: {
backgroundColor: 'rgba(26,22,18,0.92)',
backgroundColor: cInverse,
titleColor: cInverseText,
bodyColor: cInverseText,
padding: 10,
titleFont: { size: 12 },
bodyFont: { size: 11 },
@@ -364,7 +377,7 @@ export function PerformanceChart({
y: {
type: 'linear', display: true, position: 'left',
min: mobileYBounds.min, max: mobileYBounds.max,
grid: { color: 'rgba(0,0,0,0.05)' },
grid: { color: cGrid },
ticks: { font: { size: 10 }, maxTicksLimit: 5 },
},
x: {
@@ -429,9 +442,9 @@ export function PerformanceChart({
belong together. */}
{isMobile && activeChip === 'progress' && (
<div className={styles.miniLegend}>
<span><span className={styles.miniDot} style={{ background: 'rgb(59,130,246)' }} />Reading</span>
<span><span className={styles.miniDot} style={{ background: 'rgb(139,92,246)' }} />Writing</span>
<span><span className={styles.miniDot} style={{ background: 'rgb(236,72,153)' }} />Maths</span>
<span><span className={styles.miniDot} style={{ background: c4 }} />Reading</span>
<span><span className={styles.miniDot} style={{ background: c3 }} />Writing</span>
<span><span className={styles.miniDot} style={{ background: c5 }} />Maths</span>
</div>
)}
+55 -54
View File
@@ -11,14 +11,14 @@
.header h1 {
font-size: 2.25rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.5rem;
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
}
.subtitle {
font-size: 1rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
margin: 0;
line-height: 1.6;
}
@@ -28,7 +28,7 @@
display: flex;
gap: 0;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
width: fit-content;
@@ -38,8 +38,8 @@
padding: 0.625rem 1.5rem;
font-size: 0.9375rem;
font-weight: 500;
background: var(--bg-card, white);
color: var(--text-secondary, #5c564d);
background: var(--bg-card);
color: var(--text-secondary);
border: none;
cursor: pointer;
transition: all 0.15s ease;
@@ -47,34 +47,34 @@
}
.phaseTab:not(:last-child) {
border-right: 1px solid var(--border-color, #e5dfd5);
border-right: 1px solid var(--border);
}
.phaseTab:hover {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
.phaseTabActive {
background: var(--accent-coral-dark, #b04a2e);
color: white;
background: var(--brand-strong);
color: var(--brand-on);
font-weight: 600;
}
.phaseTabActive:hover {
background: var(--accent-coral-darker, #9c3f26);
background: var(--brand-stronger);
}
/* Filters */
.filters {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
display: flex;
flex-wrap: wrap;
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 {
@@ -88,7 +88,7 @@
.filterLabel {
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
white-space: nowrap;
}
@@ -98,44 +98,44 @@
max-width: 100%;
padding: 0.625rem 1rem;
font-size: 0.9375rem;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-card, white);
color: var(--text-primary, #1a1612);
background: var(--bg-card);
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
}
.filterSelect:hover {
border-color: var(--accent-coral, #e07256);
border-color: var(--brand);
}
.filterSelect:focus {
outline: none;
border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px var(--accent-coral-bg);
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-bg);
}
.filterSelect optgroup {
font-weight: 700;
color: var(--text-primary, #1a1612);
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary);
background: var(--bg-secondary);
padding: 0.5rem 0;
}
.filterSelect option {
font-weight: 400;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
padding: 0.375rem 1rem;
}
/* Rankings Section */
.rankingsSection {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
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 {
@@ -151,15 +151,15 @@
}
.rankingsTable thead {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
.rankingsTable th {
padding: 1rem;
text-align: left;
font-weight: 600;
color: var(--text-primary, #1a1612);
border-bottom: 2px solid var(--border-color, #e5dfd5);
color: var(--text-primary);
border-bottom: 2px solid var(--border);
white-space: nowrap;
text-transform: uppercase;
font-size: 0.75rem;
@@ -206,8 +206,8 @@
.rankingsTable td {
padding: 1rem;
border-bottom: 1px solid var(--border-color, #e5dfd5);
color: var(--text-secondary, #5c564d);
border-bottom: 1px solid var(--border);
color: var(--text-secondary);
}
.rankingsTable tbody tr:last-child td {
@@ -216,24 +216,24 @@
/* Alternating row backgrounds for visual rhythm */
.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 {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
/* Top 3 Highlighting with Gold */
.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 {
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 {
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 {
@@ -241,7 +241,7 @@
align-items: center;
gap: 0.5rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
/* Styled rank badges for top 3 */
@@ -254,8 +254,8 @@
border-radius: 50%;
font-size: 1rem;
font-weight: 700;
color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
color: var(--brand-on);
box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.15);
position: relative;
}
@@ -265,30 +265,31 @@
inset: -2px;
border-radius: 50%;
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-composite: exclude;
-webkit-mask-composite: xor;
}
.rankBadge1 {
background: linear-gradient(135deg, #c9a227 0%, #e8c547 50%, #c9a227 100%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
background: linear-gradient(135deg, var(--status-below) 0%, var(--medal-gold) 50%, var(--status-below) 100%);
text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.2);
}
.rankBadge2 {
background: linear-gradient(135deg, #8c8c8c 0%, #c0c0c0 50%, #8c8c8c 100%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
background: linear-gradient(135deg, var(--text-muted) 0%, var(--medal-silver) 50%, var(--text-muted) 100%);
text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.2);
}
.rankBadge3 {
background: linear-gradient(135deg, #a5673f 0%, #cd7f32 50%, #a5673f 100%);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
background: linear-gradient(135deg, var(--status-below) 0%, var(--medal-bronze) 50%, var(--status-below) 100%);
text-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.2);
}
.rankNumber {
font-variant-numeric: tabular-nums;
font-size: 1rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.schoolCell {
@@ -296,18 +297,18 @@
}
.schoolLink {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-decoration: none;
transition: color 0.2s ease;
}
.schoolLink:hover {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
.areaCell,
.typeCell {
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
/* LA subline under the school name — mobile only (Area column hidden there). */
@@ -315,7 +316,7 @@
display: none;
font-size: 0.75rem;
font-weight: 400;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.valueCell {
@@ -324,7 +325,7 @@
}
.valueCell strong {
color: var(--accent-teal, #2d7d7d);
color: var(--status-above);
font-weight: 700;
}
@@ -346,7 +347,7 @@
.noResults {
text-align: center;
padding: 4rem 2rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.noResults p {
+14 -14
View File
@@ -17,7 +17,7 @@
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
margin-bottom: 0.75rem;
}
@@ -41,7 +41,7 @@
top: 0;
height: 100%;
width: 1px;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
}
/* ── Per-bar national average marker ──
@@ -54,7 +54,7 @@
bottom: -3px;
width: 2px;
transform: translateX(-50%);
background: var(--accent-coral, #e07256);
background: var(--brand);
border-radius: 2px;
z-index: 4;
pointer-events: none;
@@ -69,7 +69,7 @@
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--accent-coral, #e07256);
background: var(--brand);
}
.barHeaderRight {
@@ -81,7 +81,7 @@
.natLabel {
font-size: 0.55rem;
font-weight: 700;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
text-transform: uppercase;
letter-spacing: 0.03em;
}
@@ -123,18 +123,18 @@
}
.barExpected {
background: var(--accent-teal-light, #3a9e9e);
background: var(--brand);
}
.barExceeding {
background: var(--accent-teal, #2d7d7d);
background: var(--brand);
}
.barLabel {
font-family: var(--font-playfair), 'Playfair Display', Georgia, serif;
font-family: var(--font-display);
font-size: 0.78rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
white-space: nowrap;
position: relative;
z-index: 3;
@@ -142,7 +142,7 @@
.barLabelSuffix {
font-weight: 500;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.06em;
@@ -153,7 +153,7 @@
position: relative;
height: 14px;
margin-top: 0.3rem;
border-top: 1px solid var(--border-color, #e5dfd5);
border-top: 1px solid var(--border);
}
.rulerTick {
@@ -161,7 +161,7 @@
top: 0;
width: 1px;
height: 3px;
background: var(--border-color, #e5dfd5);
background: var(--border);
}
.rulerLabel {
@@ -169,7 +169,7 @@
top: 5px;
font-size: 0.5rem;
font-weight: 500;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
transform: translateX(-50%);
letter-spacing: 0.01em;
opacity: 0.7;
@@ -198,7 +198,7 @@
gap: 0.35rem;
font-size: 0.65rem;
font-weight: 500;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
.legendSwatch {
+3 -3
View File
@@ -145,15 +145,15 @@ export default function SatsChart({ subjects }: SatsChartProps) {
</div>
<div className={styles.legend}>
<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
</div>
<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
</div>
<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
</div>
</div>
+25 -24
View File
@@ -1,6 +1,6 @@
.card {
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-left: 3px solid transparent;
border-radius: 10px;
padding: 1rem 1.125rem;
@@ -8,13 +8,13 @@
}
.card.cardInCompare {
border-color: var(--accent-teal, #2d7d7d);
box-shadow: 0 0 0 1px var(--accent-teal, #2d7d7d);
border-color: var(--brand);
box-shadow: 0 0 0 1px var(--brand);
}
.card:hover {
border-left-color: var(--accent-coral, #e07256);
box-shadow: var(--shadow-medium, 0 4px 20px rgba(26, 22, 18, 0.1));
border-left-color: var(--brand);
box-shadow: var(--shadow-medium, 0 4px 20px rgba(var(--shadow-rgb), 0.1));
transform: translateY(-1px);
}
@@ -31,24 +31,24 @@
font-size: 1rem;
font-weight: 600;
line-height: 1.35;
font-family: var(--font-playfair), 'Playfair Display', serif;
font-family: var(--font-display);
}
.title a {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-decoration: none;
transition: color 0.2s ease;
}
.title a:hover {
color: var(--accent-coral, #e07256);
color: var(--brand);
}
.distance {
font-size: 0.75rem;
color: var(--accent-teal, #2d7d7d);
color: var(--brand);
white-space: nowrap;
background: var(--accent-teal-bg);
background: var(--brand-bg);
padding: 0.125rem 0.5rem;
border-radius: 12px;
font-weight: 500;
@@ -63,9 +63,9 @@
.metaItem {
font-size: 0.75rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
padding: 0.125rem 0.5rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 3px;
}
@@ -75,9 +75,9 @@
gap: 0.625rem;
margin-bottom: 0.875rem;
padding: 0.625rem 0.75rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 6px;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
}
.metric {
@@ -88,12 +88,13 @@
.metricLabel {
font-size: 0.6875rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.metricValue {
font-variant-numeric: tabular-nums;
display: flex;
align-items: center;
gap: 0.375rem;
@@ -101,7 +102,7 @@
.metricValue strong {
font-size: 1rem;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
font-weight: 700;
}
@@ -126,18 +127,18 @@
}
.trendUp {
color: var(--accent-teal, #2d7d7d);
background: var(--accent-teal-bg);
color: var(--status-above);
background: var(--status-above-bg);
}
.trendDown {
color: var(--accent-coral, #e07256);
background: var(--accent-coral-bg);
color: var(--brand);
background: var(--brand-bg);
}
.trendStable {
color: var(--text-muted, #8a847a);
background: rgba(138, 132, 122, 0.15);
color: var(--text-muted);
background: rgba(var(--muted-rgb), 0.15);
}
.actions {
@@ -156,7 +157,7 @@
.metricHint {
font-size: 0.7rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
display: block;
margin-top: 1px;
font-weight: 400;
@@ -6,7 +6,7 @@
position: relative;
width: 100%;
height: 210px;
background: #dfe6e2;
background: var(--bg-secondary);
isolation: isolate;
}
@@ -47,7 +47,7 @@
width: 100%;
height: 100%;
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%;
animation: shimmer 1.4s infinite;
}
@@ -85,11 +85,11 @@
border-radius: 999px;
font-size: 13px;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
background: rgba(255, 255, 255, .85);
-webkit-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;
transform: translateY(4px);
transition: opacity .16s ease, transform .16s ease;
@@ -118,7 +118,7 @@
rgba(255, 255, 255, .35) 35%,
rgba(255, 255, 255, .75) 62%,
rgba(255, 255, 255, .95) 82%,
var(--bg-card, #fff) 100%);
var(--bg-card) 100%);
}
.closeBtn {
@@ -134,9 +134,9 @@
border: none;
border-radius: 8px;
background: rgba(255, 255, 255, .92);
color: var(--text-primary, #1a1612);
color: var(--text-primary);
cursor: pointer;
box-shadow: 0 2px 10px rgba(0, 0, 0, .2);
box-shadow: 0 2px 10px rgba(var(--shadow-rgb), .2);
}
.closeBtn:hover {
+4 -4
View File
@@ -33,12 +33,12 @@
border: 2px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
cursor: pointer;
color: #333;
color: var(--text-primary);
transition: background 0.15s ease, color 0.15s ease;
}
.fullscreenBtn:hover {
background: #f4f4f4;
background: var(--bg-secondary);
color: #000;
}
@@ -57,9 +57,9 @@
.spinner {
width: 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-top-color: var(--accent-coral, #e07256);
border-top-color: var(--brand);
animation: spin 0.8s linear infinite;
}
+36 -35
View File
@@ -2,8 +2,8 @@
display: flex;
align-items: center;
gap: 1rem;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-left: 3px solid transparent;
border-radius: 8px;
padding: 1rem 1.25rem;
@@ -12,17 +12,17 @@
}
.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 */
.phasePrimary { border-left-color: var(--phase-primary, #5b8cbf); }
.phaseAllThrough { border-left-color: var(--phase-all-through, #7a9a6d); }
.phaseNursery { border-left-color: var(--phase-nursery, #e0a0b0); }
.phasePrimary { border-left-color: var(--phase-primary); }
.phaseAllThrough { border-left-color: var(--phase-all-through); }
.phaseNursery { border-left-color: var(--phase-nursery); }
.rowInCompare {
border-left-color: var(--accent-teal, #2d7d7d);
background: var(--bg-secondary, #f3ede4);
border-left-color: var(--brand);
background: var(--bg-secondary);
}
@keyframes rowFadeIn {
@@ -50,7 +50,7 @@
.schoolName {
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
@@ -60,7 +60,7 @@
}
.schoolName:hover {
color: var(--accent-coral, #e07256);
color: var(--brand);
}
/* Phase label pill */
@@ -96,8 +96,8 @@
line-height: 1.4;
border-radius: 4px;
white-space: nowrap;
background: rgba(26, 22, 18, 0.05);
color: var(--text-secondary, #5c564d);
background: rgba(var(--shadow-rgb), 0.05);
color: var(--text-secondary);
}
/* Line 3: stats */
@@ -115,10 +115,11 @@
}
.statValue {
font-variant-numeric: tabular-nums;
font-size: 0.9375rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--text-primary);
font-family: var(--font-display);
display: inline-flex;
align-items: center;
gap: 0.2rem;
@@ -126,7 +127,7 @@
.statLabel {
font-size: 0.75rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
white-space: nowrap;
}
@@ -137,9 +138,9 @@
margin-left: 1px;
}
.trendUp { color: var(--accent-teal, #2d7d7d); }
.trendDown { color: var(--accent-coral, #e07256); }
.trendStable { color: var(--text-muted, #8a847a); }
.trendUp { color: var(--status-above); }
.trendDown { color: var(--brand); }
.trendStable { color: var(--text-muted); }
/* Line 4: location */
.line4 {
@@ -148,13 +149,13 @@
flex-wrap: wrap;
gap: 0;
font-size: 0.8rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.line4 span:not(:last-child)::after {
content: '·';
margin: 0 0.4rem;
color: var(--border-color, #e5dfd5);
color: var(--border);
}
.distanceBadge {
@@ -162,8 +163,8 @@
padding: 0.0625rem 0.375rem;
font-size: 0.75rem;
font-weight: 600;
background: var(--accent-teal, #2d7d7d);
color: white;
background: var(--brand);
color: var(--brand-on);
border-radius: 3px;
}
@@ -200,23 +201,23 @@
font-weight: 400;
}
.ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); }
.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #2f7a2f; }
.ofsted3 { background: var(--accent-gold-bg); color: var(--accent-gold-text, #7a6800); }
.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); }
.ofsted1 { background: var(--status-above-bg); color: var(--status-above); }
.ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); }
.ofsted3 { background: var(--status-below-bg); color: var(--status-below); }
.ofsted4 { background: var(--brand-bg); color: var(--brand); }
/* ── Ofsted badge variants ──────────────────────────────────────────────── */
/* 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,
distinct from the grey "Not yet inspected" pending state. */
.ofstedInspected { background: #e6ecf2; color: #45586b; }
.ofstedPending { background: #e0e0e0; color: #666; }
.ofstedInspected { background: var(--phase-primary-bg); color: var(--phase-primary-text); }
.ofstedPending { background: var(--border); color: var(--text-muted); }
/* ── vs-national delta line (under RWM metric) ──────────────────────────── */
.vsNational { font-size: 0.7rem; color: var(--accent-teal, #2d7d7d); font-weight: 600; }
.vsNationalNeg { font-size: 0.7rem; color: var(--accent-coral, #e07256); font-weight: 600; }
.vsNationalFlat { font-size: 0.7rem; color: var(--text-muted, #8a847a); }
.vsNational { font-size: 0.7rem; color: var(--status-above); font-weight: 600; }
.vsNationalNeg { font-size: 0.7rem; color: var(--brand); font-weight: 600; }
.vsNationalFlat { font-size: 0.7rem; color: var(--text-muted); }
/* ── Mobile ──────────────────────────────────────────── */
@media (max-width: 640px) {
@@ -257,7 +258,7 @@
/* GIAS "Open, but proposed to close" marker */
.attrClosing {
background: #fdf6e3;
color: #8a6200;
border: 1px solid #e2c96f;
background: var(--status-below-bg);
color: var(--status-below);
border: 1px solid var(--status-below-bg);
}
@@ -3,9 +3,9 @@
}
.warning {
background: var(--accent-gold-bg);
border: 1px solid var(--accent-gold, #c9a227);
color: var(--text-primary, #1a1612);
background: var(--status-below-bg);
border: 1px solid var(--status-below);
color: var(--text-primary);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
@@ -21,21 +21,21 @@
width: 100%;
padding: 0.875rem 1rem;
font-size: 1rem;
border: 2px solid var(--border-color, #e5dfd5);
border: 2px solid var(--border);
border-radius: 8px;
background: var(--bg-card, white);
color: var(--text-primary, #1a1612);
background: var(--bg-card);
color: var(--text-primary);
transition: all 0.2s ease;
}
.searchInput::placeholder {
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.searchInput:focus {
outline: none;
border-color: var(--accent-coral, #e07256);
box-shadow: 0 0 0 3px var(--accent-coral-bg);
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-bg);
}
.searchSpinner {
@@ -45,9 +45,9 @@
transform: translateY(-50%);
width: 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-top-color: var(--accent-coral, #e07256);
border-top-color: var(--brand);
animation: spin 0.6s linear infinite;
}
@@ -71,17 +71,17 @@
}
.results::-webkit-scrollbar-track {
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 4px;
}
.results::-webkit-scrollbar-thumb {
background: var(--border-color, #e5dfd5);
background: var(--border);
border-radius: 4px;
}
.results::-webkit-scrollbar-thumb:hover {
background: var(--text-muted, #8a847a);
background: var(--text-muted);
}
.resultItem {
@@ -90,16 +90,16 @@
align-items: center;
gap: 1rem;
padding: 1rem;
background: var(--bg-secondary, #f3ede4);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 10px;
transition: all 0.2s ease;
}
.resultItem:hover {
background: var(--bg-card, white);
border-color: var(--accent-coral, #e07256);
box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06));
background: var(--bg-card);
border-color: var(--brand);
box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
}
.schoolInfo {
@@ -110,7 +110,7 @@
.schoolName {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.25rem;
white-space: nowrap;
overflow: hidden;
@@ -125,7 +125,7 @@
display: flex;
gap: 1rem;
font-size: 0.875rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.schoolMeta span {
@@ -138,14 +138,14 @@
.noResults {
text-align: center;
padding: 2rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
font-size: 0.9375rem;
}
.hint {
text-align: center;
padding: 2rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
font-size: 0.9375rem;
}
@@ -2,8 +2,8 @@
display: flex;
align-items: center;
gap: 1rem;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-left: 3px solid transparent;
border-radius: 8px;
padding: 1rem 1.25rem;
@@ -12,17 +12,17 @@
}
.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 */
.phaseSecondary { border-left-color: var(--phase-secondary, #9b6bb0); }
.phaseAllThrough { border-left-color: var(--phase-all-through, #7a9a6d); }
.phasePost16 { border-left-color: var(--phase-post16, #c4915e); }
.phaseSecondary { border-left-color: var(--phase-secondary); }
.phaseAllThrough { border-left-color: var(--phase-all-through); }
.phasePost16 { border-left-color: var(--phase-post16); }
.rowInCompare {
border-left-color: var(--accent-teal, #2d7d7d);
background: var(--bg-secondary, #f3ede4);
border-left-color: var(--brand);
background: var(--bg-secondary);
}
@keyframes rowFadeIn {
@@ -50,7 +50,7 @@
.schoolName {
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
@@ -60,7 +60,7 @@
}
.schoolName:hover {
color: var(--accent-coral, #e07256);
color: var(--brand);
}
/* Line 2: context tags */
@@ -81,8 +81,8 @@
line-height: 1.4;
border-radius: 4px;
white-space: nowrap;
background: rgba(26, 22, 18, 0.05);
color: var(--text-secondary, #5c564d);
background: rgba(var(--shadow-rgb), 0.05);
color: var(--text-secondary);
}
/* Line 3: KS4 stats */
@@ -100,22 +100,24 @@
}
.statValueLarge {
font-variant-numeric: tabular-nums;
font-size: 1.125rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--text-primary);
font-family: var(--font-display);
}
.statValue {
font-variant-numeric: tabular-nums;
font-size: 0.9375rem;
font-weight: 700;
color: var(--text-primary, #1a1612);
font-family: var(--font-playfair), 'Playfair Display', serif;
color: var(--text-primary);
font-family: var(--font-display);
}
.statLabel {
font-size: 0.75rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
white-space: nowrap;
}
@@ -125,8 +127,8 @@
white-space: nowrap;
}
.deltaPositive { color: #2f7a2f; }
.deltaNegative { color: var(--accent-coral, #e07256); }
.deltaPositive { color: var(--status-above); }
.deltaNegative { color: var(--brand); }
/* Line 4: location + distance */
.line4 {
@@ -135,13 +137,13 @@
flex-wrap: wrap;
gap: 0;
font-size: 0.8rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
}
.line4 span:not(:last-child)::after {
content: '·';
margin: 0 0.4rem;
color: var(--border-color, #e5dfd5);
color: var(--border);
}
.distanceBadge {
@@ -149,8 +151,8 @@
padding: 0.0625rem 0.375rem;
font-size: 0.75rem;
font-weight: 600;
background: var(--accent-teal, #2d7d7d);
color: white;
background: var(--brand);
color: var(--brand-on);
border-radius: 3px;
}
@@ -175,15 +177,15 @@
font-size: 0.75rem;
font-weight: 600;
line-height: 1.4;
background: rgba(26, 22, 18, 0.05);
color: var(--text-secondary, #5c564d);
background: rgba(var(--shadow-rgb), 0.05);
color: var(--text-secondary);
border-radius: 4px;
white-space: nowrap;
}
.selectiveTag {
background: rgba(180, 120, 0, 0.1);
color: #8a6200;
background: rgba(var(--status-below-rgb), 0.1);
color: var(--status-below);
}
/* ── Ofsted badge ────────────────────────────────────── */
@@ -202,17 +204,17 @@
font-weight: 400;
}
.ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); }
.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #2f7a2f; }
.ofsted3 { background: var(--accent-gold-bg); color: var(--accent-gold-text, #7a6800); }
.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); }
.ofsted1 { background: var(--status-above-bg); color: var(--status-above); }
.ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); }
.ofsted3 { background: var(--status-below-bg); color: var(--status-below); }
.ofsted4 { background: var(--brand-bg); color: var(--brand); }
/* ── 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,
distinct from the grey "Not yet inspected" pending state. */
.ofstedInspected { background: #e6ecf2; color: #45586b; }
.ofstedPending { background: #e0e0e0; color: #666; }
.ofstedInspected { background: var(--phase-primary-bg); color: var(--phase-primary-text); }
.ofstedPending { background: var(--border); color: var(--text-muted); }
/* ── Right actions column ────────────────────────────── */
.rowActions {
@@ -268,7 +270,7 @@
}
.closingTag {
background: #fdf6e3;
color: #8a6200;
border: 1px solid #e2c96f;
background: var(--status-below-bg);
color: var(--status-below);
border: 1px solid var(--status-below-bg);
}
@@ -1,15 +1,15 @@
/* Special-school / PRU / AP context note: soft, informational (teal), not a
warning. Shared by both detail views so the styling can't drift. */
.note {
background: var(--bg-secondary, #f3ede4);
border-left: 4px solid var(--accent-teal, #2d7d7d);
background: var(--bg-secondary);
border-left: 4px solid var(--brand);
border-radius: 0 6px 6px 0;
padding: 0.7rem 1rem;
margin: 0 0 1rem;
font-size: 0.9rem;
line-height: 1.5;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.note strong {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
}
@@ -1,6 +1,6 @@
.moreMeasures {
margin-top: 0.5rem;
border-top: 1px solid var(--border-light);
border-top: 1px solid var(--border);
padding-top: 0.75rem;
}
@@ -8,7 +8,7 @@
cursor: pointer;
font-weight: 600;
font-size: 0.88rem;
color: var(--accent-coral-dark);
color: var(--brand-strong);
}
.stripNote {
@@ -88,7 +88,7 @@ export function CompareAtAGlance({
)}
{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
</span>
<span className={s.small}>Sub-judgements only</span>
@@ -54,7 +54,7 @@ function ResultCell({ display }: { display: OfstedDisplay }) {
if (display.kind === 'transitional') {
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
</span>
<span className={s.small}>
@@ -5,10 +5,10 @@
.explore summary {
cursor: pointer;
font-weight: 600;
color: var(--accent-coral-dark);
color: var(--brand-strong);
padding: 0.85rem 1.1rem;
background: var(--bg-card);
border: 1px solid var(--border-light);
border: 1px solid var(--border);
border-radius: 8px;
}
@@ -17,7 +17,7 @@
}
.inner {
border: 1px solid var(--border-light);
border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 8px 8px;
background: var(--bg-card);
@@ -43,7 +43,7 @@
font-size: 0.9rem;
padding: 0.4rem 0.6rem;
border-radius: 8px;
border: 1px solid var(--border-light);
border: 1px solid var(--border);
background: var(--bg-card);
color: var(--text-primary);
max-width: 100%;
@@ -9,12 +9,12 @@
}
.sectionTitle {
font-family: var(--font-playfair), 'Playfair Display', Georgia, serif;
font-family: var(--font-display);
font-size: 1.45rem;
font-weight: 700;
margin: 0;
padding-left: 0.75rem;
border-left: 3px solid var(--accent-coral-dark);
border-left: 3px solid var(--brand-strong);
}
.how {
@@ -36,7 +36,7 @@
back into the shared grid. */
.measure {
background: var(--bg-card);
border: 1px solid var(--border-light);
border: 1px solid var(--border);
border-radius: 12px;
box-shadow: var(--shadow-soft);
padding: 0.75rem 0.85rem;
@@ -59,7 +59,7 @@
gap: 0.35rem 0.5rem;
flex-wrap: wrap;
padding: 0.5rem 0;
border-top: 1px solid var(--border-light);
border-top: 1px solid var(--border);
margin-top: 0.5rem;
font-size: 0.95rem;
}
@@ -113,18 +113,18 @@
}
.chipGood {
background: rgba(45, 125, 125, 0.14);
color: var(--accent-teal);
background: rgba(var(--status-above-rgb), 0.14);
color: var(--status-above);
}
.chipWarn {
background: var(--accent-gold-bg);
color: var(--accent-gold-text);
background: var(--status-below-bg);
color: var(--status-below);
}
.chipBad {
background: var(--accent-coral-bg);
color: var(--accent-coral-dark);
background: var(--brand-bg);
color: var(--brand-strong);
}
.chipNeutral {
@@ -141,18 +141,18 @@
}
.badgeGood {
background: rgba(45, 125, 125, 0.14);
color: var(--accent-teal);
background: rgba(var(--status-above-rgb), 0.14);
color: var(--status-above);
}
.badgeWarn {
background: var(--accent-gold-bg);
color: var(--accent-gold-text);
background: var(--status-below-bg);
color: var(--status-below);
}
.badgeBad {
background: var(--accent-coral-bg);
color: var(--accent-coral-dark);
background: var(--brand-bg);
color: var(--brand-strong);
}
.rcList {
@@ -200,7 +200,7 @@
.card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border: 1px solid var(--border);
border-radius: 16px;
box-shadow: var(--shadow-soft);
padding: 1.25rem 1.5rem;
@@ -208,7 +208,7 @@
}
.link {
color: var(--accent-coral-dark);
color: var(--brand-strong);
}
@media (min-width: 761px) {
@@ -230,14 +230,14 @@
.rowLabel {
color: var(--text-secondary);
padding: 0.85rem 0.5rem 0.85rem 0;
border-bottom: 1px solid var(--border-light);
border-bottom: 1px solid var(--border);
}
.cell {
display: block;
padding: 0.85rem 0.25rem;
border-top: none;
border-bottom: 1px solid var(--border-light);
border-bottom: 1px solid var(--border);
margin-top: 0;
}
@@ -19,7 +19,7 @@
padding: 0.25rem 0;
font-size: 1.0625rem;
font-weight: 600;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
background: none;
border: none;
cursor: pointer;
@@ -29,7 +29,7 @@
.topBack:hover {
color: var(--accent-coral-dark, #c85a3e);
color: var(--brand-strong);
text-decoration: underline;
text-underline-offset: 2px;
}
@@ -38,8 +38,8 @@
/* Header Section */
.header {
position: relative;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 10px;
/* Padding lives on .headerContent so the map band can bleed to the edges. */
padding: 0;
@@ -88,12 +88,13 @@
.headerHasMap .actions .btnAdd {
/* Sits on the map hero, which renders light in both themes. */
background: rgba(255, 255, 255, 0.9);
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
border-color: transparent;
-webkit-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;
font: inherit;
font-weight: 600;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
cursor: pointer;
white-space: nowrap;
}
.mapLink:hover {
color: var(--accent-coral-dark, #c45a3f);
color: var(--brand-strong);
text-decoration: underline;
text-underline-offset: 2px;
}
@@ -137,11 +138,11 @@
.schoolName {
font-size: clamp(2rem, 5vw, 3.25rem);
font-weight: 700;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
margin-bottom: 0.5rem;
line-height: 1.1;
letter-spacing: -0.01em;
font-family: var(--font-playfair), "Playfair Display", serif;
font-family: var(--font-display);
}
@@ -155,16 +156,16 @@
.metaItem {
font-size: 0.8125rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
padding: 0.125rem 0.5rem;
background: var(--bg-secondary, #f3ede4);
background: var(--bg-secondary);
border-radius: 3px;
}
.address {
font-size: 0.875rem;
color: var(--text-muted, #8a847a);
color: var(--text-muted);
margin: 0 0 0.75rem;
}
@@ -180,18 +181,18 @@
.headerDetail {
font-size: 0.8125rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
}
.headerDetail strong {
color: var(--text-primary, #1a1612);
color: var(--text-primary);
font-weight: 600;
}
.headerDetail a {
color: var(--accent-teal, #2d7d7d);
color: var(--brand);
text-decoration: none;
}
@@ -213,7 +214,7 @@
border: none;
font-size: 0.8125rem;
font-weight: 600;
color: var(--accent-teal, #2d7d7d);
color: var(--brand);
cursor: pointer;
}
@@ -236,25 +237,25 @@
cursor: pointer;
transition: all 0.2s ease;
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 {
background: var(--accent-coral-dark, #b04a2e);
color: white;
background: var(--brand-strong);
color: var(--brand-on);
}
.btnAdd:hover {
background: var(--accent-coral-darker, #9c3f26);
background: var(--brand-stronger);
transform: translateY(-1px);
}
.btnRemove {
background: var(--accent-teal, #2d7d7d);
color: white;
background: var(--brand);
color: var(--brand-on);
}
@@ -270,13 +271,13 @@
position: sticky;
top: 64px; /* global header height on desktop */
z-index: 10;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 10px 10px;
padding: 0.5rem 0.75rem;
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;
align-items: center;
gap: 0.5rem;
@@ -291,9 +292,9 @@
padding: 0.3rem 0.625rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
background: none;
border: 1px solid var(--border-color, #e5dfd5);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
@@ -302,8 +303,8 @@
.sectionNavBack:hover {
background: var(--bg-secondary, #f3ede4);
border-color: var(--accent-coral, #e07256);
background: var(--bg-secondary);
border-color: var(--brand);
}
@@ -334,7 +335,7 @@
padding: 0.3rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
text-decoration: none;
border-radius: 4px;
transition: all 0.15s ease;
@@ -344,21 +345,21 @@
.sectionNavLink:hover {
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary, #1a1612);
background: var(--bg-secondary);
color: var(--text-primary);
}
.sectionNavLinkActive {
background: var(--accent-coral-dark, #b04a2e);
color: white;
background: var(--brand-strong);
color: var(--brand-on);
font-weight: 600;
}
.sectionNavLinkActive:hover {
background: var(--accent-coral-dark, #c45a3f);
color: white;
background: var(--brand-strong);
color: var(--brand-on);
}
@@ -373,12 +374,12 @@
gap: 0.5rem;
min-height: 38px;
padding: 0.34rem 0.7rem;
background: var(--bg-secondary, #f3ede4);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
font-family: var(--font-dm-sans), "DM Sans", sans-serif;
color: var(--text-primary, #1a1612);
font-family: var(--font-ui);
color: var(--text-primary);
}
@@ -396,7 +397,7 @@
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
}
@@ -411,7 +412,7 @@
.sectionNavMenuChev {
flex: none;
color: var(--text-muted, #6d685f);
color: var(--text-muted);
font-size: 0.7rem;
}
@@ -426,9 +427,9 @@
width: 38px;
height: 38px;
border-radius: 9px;
border: 1px solid var(--accent-coral-dark, #b04a2e);
background: var(--accent-coral-dark, #b04a2e);
color: white;
border: 1px solid var(--brand-strong);
background: var(--brand-strong);
color: var(--brand-on);
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
}
@@ -447,9 +448,9 @@
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--bg-card, white);
color: var(--accent-coral-dark, #c45a3f);
border: 1.5px solid var(--accent-coral, #e07256);
background: var(--bg-card);
color: var(--brand-strong);
border: 1.5px solid var(--brand);
display: flex;
align-items: center;
justify-content: center;
@@ -460,9 +461,9 @@
.sectionNavCompareIconIn {
background: var(--bg-card, white);
border-color: var(--accent-teal, #2d7d7d);
color: var(--accent-teal, #2d7d7d);
background: var(--bg-card);
border-color: var(--brand);
color: var(--brand);
}
@@ -474,9 +475,9 @@
padding: 0.34rem 0.7rem;
font-size: 0.75rem;
font-weight: 600;
color: white;
background: var(--accent-coral-dark, #b04a2e);
border: 1px solid var(--accent-coral-dark, #b04a2e);
color: var(--brand-on);
background: var(--brand-strong);
border: 1px solid var(--brand-strong);
border-radius: 999px;
cursor: pointer;
white-space: nowrap;
@@ -485,21 +486,21 @@
.sectionNavCompare:hover {
background: var(--accent-coral-darker, #9c3f26);
border-color: var(--accent-coral-darker, #9c3f26);
background: var(--brand-stronger);
border-color: var(--brand-stronger);
}
.sectionNavCompareIn {
background: var(--bg-card, white);
color: var(--accent-teal, #2d7d7d);
border-color: var(--accent-teal, #2d7d7d);
background: var(--bg-card);
color: var(--brand);
border-color: var(--brand);
}
.sectionNavCompareIn:hover {
background: var(--bg-secondary, #f3ede4);
border-color: var(--accent-teal, #2d7d7d);
background: var(--bg-secondary);
border-color: var(--brand);
}
@@ -512,8 +513,8 @@
padding: 0.34rem 0.65rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary);
background: var(--bg-secondary);
border: none;
border-radius: 999px;
cursor: pointer;
@@ -523,7 +524,7 @@
.sectionNavAll:hover {
background: var(--border-color, #e5dfd5);
background: var(--border);
}
@@ -531,7 +532,7 @@
position: fixed;
inset: 0;
z-index: 1500;
background: rgba(26, 22, 18, 0.28);
background: rgba(var(--shadow-rgb), 0.28);
}
@@ -543,19 +544,19 @@
width: 230px;
max-height: min(70vh, 460px);
overflow-y: auto;
background: var(--bg-card, white);
border: 1px solid var(--border-color, #e5dfd5);
background: var(--bg-card);
border: 1px solid var(--border);
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;
}
.sectionsPanelHead {
font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
font-family: var(--font-display);
font-size: 0.9rem;
font-weight: 600;
color: var(--text-primary, #1a1612);
color: var(--text-primary);
padding: 0.4rem 0.6rem 0.5rem;
}
@@ -568,44 +569,44 @@
padding: 0.55rem 0.6rem;
border-radius: 8px;
font-size: 0.85rem;
color: var(--text-secondary, #5c564d);
color: var(--text-secondary);
text-decoration: none;
transition: background 0.12s ease;
}
.sectionsItem:hover {
background: var(--bg-secondary, #f3ede4);
color: var(--text-primary, #1a1612);
background: var(--bg-secondary);
color: var(--text-primary);
}
.sectionsItemActive {
background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12));
color: var(--accent-coral-dark, #c45a3f);
background: var(--brand-bg);
color: var(--brand-strong);
font-weight: 600;
}
.sectionsTick {
color: var(--accent-coral-dark, #b04a2e);
color: var(--brand-strong);
}
/* GIAS "Open, but proposed to close" notice strip */
.closingStrip {
background: #fdf6e3;
border-left: 4px solid #e2c96f;
background: var(--status-below-bg);
border-left: 4px solid var(--status-below-bg);
border-radius: 0 6px 6px 0;
padding: 0.55rem 0.9rem;
margin: 0.5rem 0;
font-size: 0.88rem;
color: #6e5a00;
color: var(--status-below);
max-width: 68ch;
}
.closingStrip strong {
color: #8a6200;
color: var(--status-below);
}
@media (max-width: 640px) {
@@ -738,7 +739,7 @@
max-height: 74vh;
border-radius: 16px 16px 0 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 {
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 = [
'rgb(75, 192, 192)',
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 206, 86)',
'rgb(153, 102, 255)',
'rgb(255, 159, 64)',
'rgb(201, 203, 207)',
'rgb(255, 0, 255)',
'var(--series-1)',
'var(--series-2)',
'var(--series-3)',
'var(--series-4)',
'var(--series-5)',
'var(--series-6)',
'var(--series-7)',
'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
* backgrounds — the raw series colours fail WCAG AA (e.g. 1.9:1 on cream).
* Same hue per index so text still keys to its chart line. All ≥4.5:1 on
* #f3ede4 and lighter.
* @deprecated The series palette is now AA on both grounds, so line and text
* share one value. Kept as an alias so call sites can migrate incrementally.
*/
export const CHART_TEXT_COLORS = [
'#256868', // teal
'#c2255c', // pink/red
'#2069ad', // blue
'#8a5a00', // yellow → ochre
'#6d43c8', // purple
'#8f4e00', // orange → brown
'#5f5952', // grey
'#a300a3', // magenta
];
export const CHART_TEXT_COLORS = CHART_COLORS;
export function getChartTextColor(index: number): string {
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 {
return rgb.replace('rgb', 'rgba').replace(')', `, ${alpha})`);
export function rgbToRgba(color: string, alpha: number): string {
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 {
switch (trend) {
case 'up':
return '#22c55e'; // green
return 'var(--status-above)';
case 'down':
return '#ef4444'; // red
return 'var(--status-below)';
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",
"start_url": "/",
"display": "standalone",
"background_color": "#faf7f2",
"theme_color": "#faf7f2",
"background_color": "#f2f1ed",
"theme_color": "#f2f1ed",
"icons": [
{
"src": "/favicon.svg",
"src": "/icon.svg",
"sizes": "any",
"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"
}
]
}