- ${escapeHtml(school.school_name)}
+ ${escapeHtml(school.school_name)}
${badge.label}
-
+
${phaseLabel}${school.local_authority ? ` · ${escapeHtml(school.local_authority)}` : ''}${distanceStr}
${metricHtml}
-
View Details →
+
View Details →
`;
marker.bindPopup(popupContent);
diff --git a/nextjs-app/components/LoadingSkeleton.module.css b/nextjs-app/components/LoadingSkeleton.module.css
index ba0da7f..5907874 100644
--- a/nextjs-app/components/LoadingSkeleton.module.css
+++ b/nextjs-app/components/LoadingSkeleton.module.css
@@ -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 {
diff --git a/nextjs-app/components/Logo.tsx b/nextjs-app/components/Logo.tsx
new file mode 100644
index 0000000..bd2adaf
--- /dev/null
+++ b/nextjs-app/components/Logo.tsx
@@ -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 (
+
+ {title ? {title} : null}
+ {BARS.map(([x, y, w, h, isSchool], i) => (
+
+ ))}
+
+ );
+}
diff --git a/nextjs-app/components/Modal.module.css b/nextjs-app/components/Modal.module.css
index fb4bf92..05b8275 100644
--- a/nextjs-app/components/Modal.module.css
+++ b/nextjs-app/components/Modal.module.css
@@ -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) {
diff --git a/nextjs-app/components/Navigation.module.css b/nextjs-app/components/Navigation.module.css
index 9a14b2d..2d0073b 100644
--- a/nextjs-app/components/Navigation.module.css
+++ b/nextjs-app/components/Navigation.module.css
@@ -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);
diff --git a/nextjs-app/components/Navigation.tsx b/nextjs-app/components/Navigation.tsx
index 24bbdc8..407261a 100644
--- a/nextjs-app/components/Navigation.tsx
+++ b/nextjs-app/components/Navigation.tsx
@@ -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() {
-
-
-
-
-
+
+
+
+ SchoolCompare
-
SchoolCompare
diff --git a/nextjs-app/components/Pagination.module.css b/nextjs-app/components/Pagination.module.css
index 919f34b..ecc7b71 100644
--- a/nextjs-app/components/Pagination.module.css
+++ b/nextjs-app/components/Pagination.module.css
@@ -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) {
diff --git a/nextjs-app/components/PerformanceChart.module.css b/nextjs-app/components/PerformanceChart.module.css
index 0930eaf..5608027 100644
--- a/nextjs-app/components/PerformanceChart.module.css
+++ b/nextjs-app/components/PerformanceChart.module.css
@@ -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;
}
diff --git a/nextjs-app/components/PerformanceChart.tsx b/nextjs-app/components/PerformanceChart.tsx
index 2ced5e9..9e43b34 100644
--- a/nextjs-app/components/PerformanceChart.tsx
+++ b/nextjs-app/components/PerformanceChart.tsx
@@ -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' && (
- Reading
- Writing
- Maths
+ Reading
+ Writing
+ Maths
)}
diff --git a/nextjs-app/components/RankingsView.module.css b/nextjs-app/components/RankingsView.module.css
index 27e1fbe..b85eba0 100644
--- a/nextjs-app/components/RankingsView.module.css
+++ b/nextjs-app/components/RankingsView.module.css
@@ -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 {
diff --git a/nextjs-app/components/SatsChart.module.css b/nextjs-app/components/SatsChart.module.css
index a2ba788..a489727 100644
--- a/nextjs-app/components/SatsChart.module.css
+++ b/nextjs-app/components/SatsChart.module.css
@@ -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 {
diff --git a/nextjs-app/components/SatsChart.tsx b/nextjs-app/components/SatsChart.tsx
index f25f0eb..6143f04 100644
--- a/nextjs-app/components/SatsChart.tsx
+++ b/nextjs-app/components/SatsChart.tsx
@@ -145,15 +145,15 @@ export default function SatsChart({ subjects }: SatsChartProps) {
-
+
Exceeding / high score
diff --git a/nextjs-app/components/SchoolCard.module.css b/nextjs-app/components/SchoolCard.module.css
index d4f1f12..c25dcf1 100644
--- a/nextjs-app/components/SchoolCard.module.css
+++ b/nextjs-app/components/SchoolCard.module.css
@@ -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(--status-below);
+ background: var(--status-below-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;
diff --git a/nextjs-app/components/SchoolHeroMap.module.css b/nextjs-app/components/SchoolHeroMap.module.css
index d7e33b9..8904521 100644
--- a/nextjs-app/components/SchoolHeroMap.module.css
+++ b/nextjs-app/components/SchoolHeroMap.module.css
@@ -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 {
diff --git a/nextjs-app/components/SchoolMap.module.css b/nextjs-app/components/SchoolMap.module.css
index e4a4e9c..cb61c65 100644
--- a/nextjs-app/components/SchoolMap.module.css
+++ b/nextjs-app/components/SchoolMap.module.css
@@ -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;
}
diff --git a/nextjs-app/components/SchoolRow.module.css b/nextjs-app/components/SchoolRow.module.css
index 20f03e8..9a1ceb8 100644
--- a/nextjs-app/components/SchoolRow.module.css
+++ b/nextjs-app/components/SchoolRow.module.css
@@ -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(--status-below); }
+.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(--status-below); color: var(--text-inverse); }
/* ── Ofsted badge variants ──────────────────────────────────────────────── */
/* ofsted1–4 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);
}
diff --git a/nextjs-app/components/SchoolSearchModal.module.css b/nextjs-app/components/SchoolSearchModal.module.css
index 2bbc8a5..288d776 100644
--- a/nextjs-app/components/SchoolSearchModal.module.css
+++ b/nextjs-app/components/SchoolSearchModal.module.css
@@ -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;
}
diff --git a/nextjs-app/components/SecondarySchoolRow.module.css b/nextjs-app/components/SecondarySchoolRow.module.css
index ae64de6..3f0a7d0 100644
--- a/nextjs-app/components/SecondarySchoolRow.module.css
+++ b/nextjs-app/components/SecondarySchoolRow.module.css
@@ -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(--status-below); }
/* 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(--status-below); color: var(--text-inverse); }
/* ── 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);
}
diff --git a/nextjs-app/components/SpecialSchoolNote.module.css b/nextjs-app/components/SpecialSchoolNote.module.css
index b9c7a36..19ab95f 100644
--- a/nextjs-app/components/SpecialSchoolNote.module.css
+++ b/nextjs-app/components/SpecialSchoolNote.module.css
@@ -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);
}
diff --git a/nextjs-app/components/compare/CompareAcademics.module.css b/nextjs-app/components/compare/CompareAcademics.module.css
index 42b36ef..e9bb90e 100644
--- a/nextjs-app/components/compare/CompareAcademics.module.css
+++ b/nextjs-app/components/compare/CompareAcademics.module.css
@@ -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 {
diff --git a/nextjs-app/components/compare/CompareAtAGlance.tsx b/nextjs-app/components/compare/CompareAtAGlance.tsx
index bc9322f..147ef06 100644
--- a/nextjs-app/components/compare/CompareAtAGlance.tsx
+++ b/nextjs-app/components/compare/CompareAtAGlance.tsx
@@ -88,7 +88,7 @@ export function CompareAtAGlance({
)}
{display.kind === 'transitional' && (
<>
-
+
No overall grade
Sub-judgements only
diff --git a/nextjs-app/components/compare/CompareOfsted.tsx b/nextjs-app/components/compare/CompareOfsted.tsx
index 9875a6b..0d0f86e 100644
--- a/nextjs-app/components/compare/CompareOfsted.tsx
+++ b/nextjs-app/components/compare/CompareOfsted.tsx
@@ -54,7 +54,7 @@ function ResultCell({ display }: { display: OfstedDisplay }) {
if (display.kind === 'transitional') {
return (
<>
-
+
No overall grade
diff --git a/nextjs-app/components/compare/TrendsExplorer.module.css b/nextjs-app/components/compare/TrendsExplorer.module.css
index e5e007b..481d709 100644
--- a/nextjs-app/components/compare/TrendsExplorer.module.css
+++ b/nextjs-app/components/compare/TrendsExplorer.module.css
@@ -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%;
diff --git a/nextjs-app/components/compare/compareSections.module.css b/nextjs-app/components/compare/compareSections.module.css
index af7712f..f78afaf 100644
--- a/nextjs-app/components/compare/compareSections.module.css
+++ b/nextjs-app/components/compare/compareSections.module.css
@@ -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(--status-below-bg);
+ color: var(--status-below);
}
.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(--status-below-bg);
+ color: var(--status-below);
}
.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;
}
diff --git a/nextjs-app/components/school/SchoolDetailShell.module.css b/nextjs-app/components/school/SchoolDetailShell.module.css
index d1c38fe..3286ded 100644
--- a/nextjs-app/components/school/SchoolDetailShell.module.css
+++ b/nextjs-app/components/school/SchoolDetailShell.module.css
@@ -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 {
diff --git a/nextjs-app/components/school/schoolSections.module.css b/nextjs-app/components/school/schoolSections.module.css
index 8ec039a..d863e09 100644
--- a/nextjs-app/components/school/schoolSections.module.css
+++ b/nextjs-app/components/school/schoolSections.module.css
@@ -13,7 +13,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;
@@ -22,7 +22,7 @@
}
.topBack:hover {
- color: var(--accent-coral-dark, #c85a3e);
+ color: var(--brand-strong);
text-decoration: underline;
text-underline-offset: 2px;
}
@@ -30,8 +30,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;
@@ -75,12 +75,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);
}
.headerHasMap .actions .btnAdd:hover {
@@ -124,13 +125,13 @@
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;
}
@@ -142,11 +143,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);
overflow-wrap: break-word;
}
@@ -159,15 +160,15 @@
.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;
overflow-wrap: break-word;
}
@@ -182,16 +183,16 @@
.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;
}
@@ -211,16 +212,17 @@
border: none;
font-size: 0.8125rem;
font-weight: 600;
- color: var(--accent-teal, #2d7d7d);
+ color: var(--brand);
cursor: pointer;
}
/* Gender split card — sits in the Pupils & Inclusion heroStatGrid */
.genderSplitValue {
+ font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 0.3rem;
- font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
+ font-family: var(--font-display);
font-size: 1.55rem;
font-weight: 700;
line-height: 1;
@@ -228,23 +230,23 @@
}
.genderSplitGirls {
- color: #a04a68;
+ color: var(--phase-nursery-text);
}
.genderSplitBoys {
- color: var(--accent-teal, #2d7d7d);
+ color: var(--phase-primary-text);
}
.genderSplitLabel {
- font-family: var(--font-body, inherit);
+ font-family: var(--font-ui);
font-size: 0.78rem;
font-weight: 500;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
letter-spacing: 0;
}
.genderSplitSep {
- color: var(--border-color, #c8beb0);
+ color: var(--border);
font-weight: 400;
font-size: 1.2rem;
padding: 0 0.1rem;
@@ -255,16 +257,16 @@
height: 6px;
border-radius: 999px;
overflow: hidden;
- background: var(--border-color, #e5dfd5);
+ background: var(--border);
width: 100%;
}
.genderBarGirls {
- background: #a04a68;
+ background: var(--phase-nursery-text);
}
.genderBarBoys {
- background: var(--accent-teal, #2d7d7d);
+ background: var(--phase-primary);
}
.actions {
@@ -284,22 +286,22 @@
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);
}
.btnRemove:hover {
@@ -313,13 +315,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;
@@ -341,9 +343,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;
@@ -351,8 +353,8 @@
}
.sectionNavBack:hover {
- background: var(--bg-secondary, #f3ede4);
- border-color: var(--accent-coral, #e07256);
+ background: var(--bg-secondary);
+ border-color: var(--brand);
}
/* The scrolling middle: section links only. */
@@ -394,7 +396,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;
@@ -412,19 +414,19 @@
}
.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);
}
/* ── Mobile: the scrolling links collapse into one "section" menu button ──
@@ -438,12 +440,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);
}
.sectionNavMenuCur {
@@ -459,7 +461,7 @@
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
}
.sectionNavMenuNow {
@@ -472,7 +474,7 @@
.sectionNavMenuChev {
flex: none;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
font-size: 0.7rem;
}
@@ -486,9 +488,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;
}
@@ -505,9 +507,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;
@@ -517,9 +519,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);
}
/* Compare CTA carried into the bar once the hero's button scrolls away. */
@@ -530,9 +532,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;
@@ -540,19 +542,19 @@
}
.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);
}
@media (max-width: 640px) {
@@ -570,8 +572,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;
@@ -580,7 +582,7 @@
}
.sectionNavAll:hover {
- background: var(--border-color, #e5dfd5);
+ background: var(--border);
}
@media (max-width: 640px) {
@@ -593,7 +595,7 @@
position: fixed;
inset: 0;
z-index: 1500;
- background: rgba(26, 22, 18, 0.28);
+ background: rgba(var(--shadow-rgb), 0.28);
}
.sectionsPanel {
@@ -604,18 +606,18 @@
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;
}
@@ -627,24 +629,24 @@
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);
}
/* On phones the menu becomes a bottom sheet. */
@@ -659,7 +661,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 {
@@ -689,8 +691,8 @@
/* Unified card for all content sections */
.card {
- 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.25rem 1.5rem;
margin-bottom: 1rem;
@@ -704,11 +706,11 @@
.sectionTitle {
font-size: 1.125rem;
font-weight: 600;
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
margin-bottom: 0.875rem;
padding-bottom: 0.5rem;
- border-bottom: 2px solid var(--border-color, #e5dfd5);
- font-family: var(--font-playfair), "Playfair Display", serif;
+ border-bottom: 2px solid var(--border);
+ font-family: var(--font-display);
display: flex;
align-items: center;
gap: 0.375rem;
@@ -722,21 +724,21 @@
display: inline-block;
width: 3px;
height: 1em;
- background: var(--accent-coral, #e07256);
+ background: var(--brand);
border-radius: 2px;
flex-shrink: 0;
}
.sectionSubtitle {
font-size: 0.85rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
margin: -0.5rem 0 1rem;
}
.subSectionTitle {
font-size: 0.875rem;
font-weight: 600;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
margin: 1.25rem 0 0.75rem;
}
@@ -748,8 +750,8 @@
}
.metricCard {
- background: var(--bg-secondary, #f3ede4);
- border: 1px solid var(--border-color, #e5dfd5);
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
border-radius: 6px;
padding: 0.75rem;
text-align: center;
@@ -757,7 +759,7 @@
.metricLabel {
font-size: 0.6875rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
margin-bottom: 0.25rem;
font-weight: 500;
text-transform: uppercase;
@@ -765,9 +767,10 @@
}
.metricValue {
+ font-variant-numeric: tabular-nums;
font-size: 1.25rem;
font-weight: 700;
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
display: flex;
align-items: center;
justify-content: center;
@@ -778,13 +781,13 @@
.metricHint {
font-size: 0.7rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
margin-top: 0.3rem;
font-style: italic;
}
/* ── Hero stat cards (RWM combined, Pupils & Inclusion, etc.) ── */
-/* Larger teal-tinted cards with Playfair serif numbers — reserved for
+/* Larger brand-tinted cards with display-weight numbers — reserved for
the top-of-section headline metrics. Use .metricCard for denser
secondary metrics. */
.heroStatGrid {
@@ -795,8 +798,9 @@
}
.heroStatCard {
- background: var(--accent-teal-bg, rgba(45, 125, 125, 0.12));
- border: 1px solid rgba(45, 125, 125, 0.2);
+ font-variant-numeric: tabular-nums;
+ background: var(--status-above-bg);
+ border: 1px solid rgba(var(--status-above-rgb), 0.2);
border-radius: 12px;
padding: 1rem 1.1rem;
display: flex;
@@ -810,7 +814,7 @@
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
display: flex;
align-items: center;
gap: 0.3rem;
@@ -818,11 +822,12 @@
}
.heroStatValue {
- font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
+ font-variant-numeric: tabular-nums;
+ font-family: var(--font-display);
font-size: 2.1rem;
font-weight: 700;
line-height: 1;
- color: var(--accent-teal, #2d7d7d);
+ color: var(--status-above);
display: flex;
align-items: baseline;
gap: 0.5rem;
@@ -831,8 +836,9 @@
}
.heroStatHint {
+ font-variant-numeric: tabular-nums;
font-size: 0.7rem;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
font-style: normal;
margin-top: 0;
}
@@ -849,29 +855,29 @@
/* Progress score colour coding */
.progressPositive {
- color: var(--accent-teal, #2d7d7d);
+ color: var(--status-above);
font-weight: 700;
}
.progressNegative {
- color: var(--accent-coral-dark, #b04a2e);
+ color: var(--status-below);
font-weight: 700;
}
/* ── Semantic status colours (unified) ────────────── */
.statusGood {
- background: var(--accent-teal-bg);
- color: var(--accent-teal, #2d7d7d);
+ background: var(--status-above-bg);
+ color: var(--status-above);
}
.statusWarn {
- background: var(--accent-gold-bg);
- color: var(--accent-gold-text, #7a6800);
+ background: var(--status-below-bg);
+ color: var(--status-below);
}
.statusBad {
- background: var(--accent-coral-bg);
- color: var(--accent-coral-dark, #b04a2e);
+ background: var(--status-below-bg);
+ color: var(--status-below);
}
/* Charts Section */
@@ -898,10 +904,10 @@
.metricGroupTitle {
font-size: 0.875rem;
font-weight: 600;
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
margin-bottom: 0.5rem;
padding-bottom: 0.375rem;
- border-bottom: 1px solid var(--border-color, #e5dfd5);
+ border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 0.375rem;
@@ -918,19 +924,19 @@
justify-content: space-between;
align-items: center;
padding: 0.375rem 0.625rem;
- background: var(--bg-secondary, #f3ede4);
+ background: var(--bg-secondary);
border-radius: 4px;
}
.metricName {
font-size: 0.75rem;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
}
.metricRow .metricValue {
font-size: 0.875rem;
font-weight: 600;
- color: var(--accent-teal, #2d7d7d);
+ color: var(--status-above);
}
/* History Table */
@@ -943,7 +949,7 @@
.historicalSubtitle {
font-size: 0.8rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
margin: 1.25rem 0 0.25rem;
}
@@ -954,7 +960,7 @@
}
.dataTable thead {
- background: var(--bg-secondary, #f3ede4);
+ background: var(--bg-secondary);
}
.dataTable th {
@@ -964,14 +970,14 @@
font-size: 0.6875rem;
text-transform: uppercase;
letter-spacing: 0.03em;
- color: var(--text-primary, #1a1612);
- border-bottom: 2px solid var(--border-color, #e5dfd5);
+ color: var(--text-primary);
+ border-bottom: 2px solid var(--border);
}
.dataTable td {
padding: 0.5rem 0.75rem;
- border-bottom: 1px solid var(--border-color, #e5dfd5);
- color: var(--text-secondary, #5c564d);
+ border-bottom: 1px solid var(--border);
+ color: var(--text-secondary);
}
.dataTable tbody tr:last-child td {
@@ -979,12 +985,12 @@
}
.dataTable tbody tr:hover {
- background: var(--bg-secondary, #f3ede4);
+ background: var(--bg-secondary);
}
.yearCell {
font-weight: 600;
- color: var(--accent-gold, #c9a227);
+ color: var(--status-below);
}
/* Ofsted */
@@ -1007,42 +1013,42 @@
}
.ofstedGrade1 {
- background: var(--accent-teal-bg);
- color: var(--accent-teal, #2d7d7d);
+ background: var(--status-above-bg);
+ color: var(--status-above);
}
.ofstedGrade2 {
- background: rgba(60, 140, 60, 0.12);
- color: #2f7a2f;
+ background: rgba(var(--status-above-rgb), 0.12);
+ color: var(--status-above);
}
.ofstedGrade3 {
- background: var(--accent-gold-bg);
- color: var(--accent-gold-text, #7a6800);
+ background: var(--status-below-bg);
+ color: var(--status-below);
}
.ofstedGrade4 {
- background: var(--accent-coral-bg);
- color: var(--accent-coral-dark, #b04a2e);
+ background: var(--status-below);
+ color: var(--text-inverse);
}
/* Report Card grade colours (5-level scale, lower = better) */
.rcGrade1 {
- background: var(--accent-teal-bg);
- color: var(--accent-teal, #2d7d7d);
+ background: var(--status-above-bg);
+ color: var(--status-above);
} /* Exceptional */
.rcGrade2 {
- background: rgba(60, 140, 60, 0.12);
- color: #2f7a2f;
+ background: rgba(var(--status-above-rgb), 0.12);
+ color: var(--status-above);
} /* Strong */
.rcGrade3 {
- background: var(--accent-gold-bg);
- color: var(--accent-gold-text, #7a6800);
+ background: var(--status-below-bg);
+ color: var(--status-below);
} /* Expected standard */
.rcGrade4 {
- background: rgba(249, 115, 22, 0.12);
- color: #c2410c;
+ background: rgba(var(--status-below-rgb), 0.12);
+ color: var(--status-below);
} /* Needs attention */
.rcGrade5 {
- background: var(--accent-coral-bg);
- color: var(--accent-coral-dark, #b04a2e);
+ background: var(--status-below);
+ color: var(--text-inverse);
} /* Urgent improvement */
/* Safeguarding value (used inside a standard metricCard) */
@@ -1052,8 +1058,8 @@
border-radius: 4px;
font-size: 0.8125rem;
font-weight: 600;
- background: var(--accent-teal-bg);
- color: var(--accent-teal, #2d7d7d);
+ background: var(--status-above-bg);
+ color: var(--status-above);
}
.safeguardingNotMet {
display: inline-block;
@@ -1061,8 +1067,8 @@
border-radius: 4px;
font-size: 0.8125rem;
font-weight: 700;
- background: var(--accent-coral-bg);
- color: var(--accent-coral-dark, #b04a2e);
+ background: var(--brand-bg);
+ color: var(--brand-strong);
}
/* ── Ofsted grade grids (Report Card + OEIF) ──
@@ -1102,25 +1108,25 @@
.ofstedDisclaimer {
font-size: 0.8rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
font-style: italic;
margin: 0 0 1rem;
}
.ofstedDate {
font-size: 0.85rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
}
.ofstedPrevious {
font-size: 0.8125rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
font-style: italic;
}
.ofstedReportLink {
font-size: 0.8125rem;
- color: var(--accent-teal, #2d7d7d);
+ color: var(--brand);
text-decoration: none;
margin-left: auto;
white-space: nowrap;
@@ -1154,19 +1160,19 @@
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
- background: var(--bg-secondary, #f3ede4);
- border: 2px solid var(--border-color, #e5dfd5);
+ background: var(--bg-secondary);
+ border: 2px solid var(--border);
flex-shrink: 0;
}
.deprivationDotFilled {
- background: var(--accent-teal, #2d7d7d);
- border-color: var(--accent-teal, #2d7d7d);
+ background: var(--brand);
+ border-color: var(--brand);
}
.deprivationDesc {
font-size: 0.875rem;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
line-height: 1.5;
margin: 0;
}
@@ -1175,20 +1181,20 @@
display: flex;
justify-content: space-between;
font-size: 0.7rem;
- color: var(--text-muted, #8a847a);
+ color: var(--text-muted);
margin-top: 0.25rem;
}
/* Progress note */
.ofstedAllSame {
font-size: 0.9375rem;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
margin: 0.5rem 0 0;
line-height: 1.5;
}
.ofstedAllSame strong {
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
}
.progressNote {
@@ -1284,7 +1290,7 @@
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
}
@media (max-width: 640px) {
@@ -1301,7 +1307,7 @@
gap: 0.75rem;
margin: 0.5rem 0 1.5rem;
padding: 0.9rem 1rem;
- background: var(--bg-secondary, #f3ede4);
+ background: var(--bg-secondary);
border-radius: 8px;
}
@@ -1310,14 +1316,14 @@
width: 22px;
height: 22px;
border-radius: 50%;
- background: var(--accent-coral, #e07256);
- color: #fff;
+ background: var(--brand);
+ color: var(--text-inverse);
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.8rem;
- font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
+ font-family: var(--font-display);
margin-top: 0.05rem;
}
@@ -1328,12 +1334,12 @@
.rwmBridgeText {
font-size: 0.85rem;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
line-height: 1.5;
}
.rwmBridgeText strong {
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
font-weight: 700;
}
@@ -1342,15 +1348,15 @@
gap: 0.35rem;
margin-top: 0.35rem;
flex-wrap: wrap;
- font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
- color: var(--text-muted, #6d685f);
+ font-family: var(--font-display);
+ color: var(--text-muted);
font-size: 0.78rem;
font-style: italic;
align-items: baseline;
}
.rwmBridgeMath strong {
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
font-weight: 700;
font-style: normal;
}
@@ -1363,7 +1369,7 @@
.progressScoresRow {
margin-top: 1.25rem;
padding-top: 1rem;
- border-top: 1px solid var(--border-color, #e5dfd5);
+ border-top: 1px solid var(--border);
}
.progressScoresGrid {
@@ -1381,13 +1387,14 @@
.progressScoreLabel {
font-size: 0.78rem;
font-weight: 500;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
}
.progressScoreValue {
+ font-variant-numeric: tabular-nums;
font-size: 0.9rem;
font-weight: 700;
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
font-variant-numeric: tabular-nums;
}
@@ -1398,26 +1405,27 @@
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
- background: var(--border-color, #e5dfd5);
- border: 1px solid var(--border-color, #e5dfd5);
+ background: var(--border);
+ border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
margin: 0;
}
.admissionsTile {
- background: var(--bg-card, #fff);
+ background: var(--bg-card);
padding: 1.15rem 1.25rem;
display: flex;
flex-direction: column;
}
.admissionsTileNum {
- font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
+ font-variant-numeric: tabular-nums;
+ font-family: var(--font-display);
font-size: 2.4rem;
font-weight: 700;
line-height: 1;
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
font-variant-numeric: tabular-nums;
margin: 0;
display: flex;
@@ -1427,10 +1435,10 @@
}
.admissionsTileSub {
- font-family: var(--font-dm-sans), "DM Sans", sans-serif;
+ font-family: var(--font-ui);
font-size: 0.85rem;
font-weight: 500;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
}
.admissionsTileLabel {
@@ -1439,11 +1447,11 @@
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
}
.admissionsTileAccent .admissionsTileNum {
- color: var(--accent-coral-dark, #c45a3f);
+ color: var(--brand-strong);
}
.admissionsVerdict {
@@ -1452,24 +1460,24 @@
}
.admissionsVerdictHeadline {
- font-family: var(--font-playfair), "Playfair Display", Georgia, serif;
+ font-family: var(--font-display);
font-size: 1.35rem;
font-weight: 700;
line-height: 1.2;
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
}
.admissionsVerdictOver {
- color: var(--accent-coral-dark, #c45a3f);
+ color: var(--brand-strong);
}
.admissionsVerdictUnder {
- color: var(--accent-teal, #2d7d7d);
+ color: var(--status-above);
}
.admissionsVerdictSub {
font-size: 0.8rem;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
line-height: 1.4;
margin-top: 0.2rem;
}
@@ -1500,7 +1508,7 @@
.admissionsSeg {
display: inline-flex;
- background: var(--bg-secondary, #f3ede4);
+ background: var(--bg-secondary);
border-radius: 999px;
padding: 3px;
gap: 2px;
@@ -1515,7 +1523,7 @@
font: inherit;
font-size: 0.8125rem;
font-weight: 600;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
padding: 0.4rem 0.9rem;
border-radius: 999px;
white-space: nowrap;
@@ -1523,13 +1531,13 @@
}
.admissionsSeg button[aria-pressed="true"] {
- background: var(--bg-card, #fff);
- color: var(--text-primary, #1a1612);
- box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06));
+ background: var(--bg-card);
+ color: var(--text-primary);
+ box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06));
}
.admissionsSeg button:hover[aria-pressed="false"] {
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
}
/* Stack both views in one grid cell so the card sizes to the taller view —
@@ -1566,21 +1574,21 @@
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
margin-bottom: 0.5rem;
}
.admissionsTrendSummary {
font-size: 1rem;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
margin: 1.25rem 0 0;
padding-top: 1.1rem;
- border-top: 1px solid var(--border-color, #e5dfd5);
+ border-top: 1px solid var(--border);
line-height: 1.5;
}
.admissionsTrendSummary strong {
- color: var(--text-primary, #1a1612);
+ color: var(--text-primary);
}
/* ── History accordion ── */
@@ -1591,7 +1599,7 @@
.historyToggle {
font-size: 0.8125rem;
font-weight: 600;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
cursor: pointer;
padding: 0.5rem 0;
list-style: none;
@@ -1618,17 +1626,17 @@
/* 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);
}
/* ── Secondary-only rules, merged from SecondarySchoolDetailView.module.css ── */
.badges {
@@ -1639,26 +1647,26 @@
}
.badge {
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;
}
.badgeSelective {
- background: rgba(180, 120, 0, 0.1);
- color: #8a6200;
+ background: rgba(var(--status-below-rgb), 0.1);
+ color: var(--status-below);
}
.badgeFaith {
- background: rgba(45, 125, 125, 0.1);
- color: var(--accent-teal, #2d7d7d);
+ background: rgba(var(--status-above-rgb), 0.1);
+ color: var(--phase-secondary-text);
}
/* ── Tab Navigation (sticky) ─────────────────────────── */
.tabNav {
position: sticky;
top: 4rem;
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 1rem;
@@ -1667,7 +1675,7 @@
white-space: nowrap;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
+ box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.04);
}
.tabNav::-webkit-scrollbar {
display: none;
@@ -1683,9 +1691,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;
@@ -1693,13 +1701,13 @@
margin-right: 0.25rem;
}
.backBtn:hover {
- background: var(--bg-secondary, #f3ede4);
- border-color: var(--accent-coral, #e07256);
+ background: var(--bg-secondary);
+ border-color: var(--brand);
}
.tabNavDivider {
width: 1px;
height: 1rem;
- background: var(--border-color, #e5dfd5);
+ background: var(--border);
margin: 0 0.25rem;
flex-shrink: 0;
}
@@ -1708,7 +1716,7 @@
padding: 0.3rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
- color: var(--text-secondary, #5c564d);
+ color: var(--text-secondary);
background: none;
border: none;
border-radius: 4px;
@@ -1718,23 +1726,23 @@
text-decoration: none;
}
.tabBtn:hover {
- background: var(--bg-secondary, #f3ede4);
- color: var(--text-primary, #1a1612);
+ background: var(--bg-secondary);
+ color: var(--text-primary);
}
.tabBtnActive {
- background: var(--accent-coral-dark, #b04a2e);
- color: white;
+ background: var(--brand-strong);
+ color: var(--brand-on);
font-weight: 600;
}
.tabBtnActive:hover {
- background: var(--accent-coral-darker, #9c3f26);
- color: white;
+ background: var(--brand-stronger);
+ color: var(--brand-on);
}
/* ── Progress 8 suspension banner ───────────────────── */
.p8Banner {
- background: rgba(180, 120, 0, 0.1);
- border: 1px solid rgba(180, 120, 0, 0.3);
- color: #8a6200;
+ background: rgba(var(--status-below-rgb), 0.1);
+ border: 1px solid rgba(var(--status-below-rgb), 0.3);
+ color: var(--status-below);
border-radius: 6px;
padding: 0.625rem 0.875rem;
font-size: 0.825rem;
@@ -1750,27 +1758,27 @@
line-height: 1.5;
}
.admissionsSelective {
- background: rgba(180, 120, 0, 0.1);
- color: #8a6200;
- border: 1px solid rgba(180, 120, 0, 0.25);
+ background: rgba(var(--status-below-rgb), 0.1);
+ color: var(--status-below);
+ border: 1px solid rgba(var(--status-below-rgb), 0.25);
}
.admissionsFaith {
- background: rgba(45, 125, 125, 0.08);
- color: var(--accent-teal, #2d7d7d);
- border: 1px solid rgba(45, 125, 125, 0.2);
+ background: rgba(var(--status-above-rgb), 0.08);
+ color: var(--phase-secondary-text);
+ border: 1px solid rgba(var(--status-above-rgb), 0.2);
}
.sixthFormNote {
margin-top: 1rem;
padding: 0.625rem 0.875rem;
- background: var(--bg-secondary, #f3ede4);
+ background: var(--bg-secondary);
border-radius: 6px;
font-size: 0.825rem;
- color: var(--text-secondary, #5c564d);
- border-left: 3px solid var(--accent-teal, #2d7d7d);
+ color: var(--text-secondary);
+ border-left: 3px solid var(--brand);
}
.genderSplitHint {
font-size: 0.7rem;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
margin-top: 0.35rem;
font-weight: 500;
}
@@ -1783,20 +1791,20 @@
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
margin-bottom: 0.5rem;
}
.att8VizTrack {
position: relative;
height: 14px;
- background: rgba(45, 125, 125, 0.08);
- border: 1px solid var(--border-color, #e5dfd5);
+ background: rgba(var(--status-above-rgb), 0.08);
+ border: 1px solid var(--border);
border-radius: 4px;
overflow: visible;
}
.att8VizFill {
height: 100%;
- background: var(--accent-teal, #2d7d7d);
+ background: var(--brand);
border-radius: 4px 0 0 4px;
transition: width 0.6s ease;
}
@@ -1805,7 +1813,7 @@
top: -4px;
bottom: -4px;
width: 2px;
- background: var(--accent-coral, #e07256);
+ background: var(--brand);
border-radius: 2px;
z-index: 2;
}
@@ -1813,8 +1821,8 @@
position: absolute;
top: -20px;
transform: translateX(-50%);
- background: var(--accent-coral-dark, #b04a2e);
- color: #fff;
+ background: var(--brand-strong);
+ color: var(--text-inverse);
font-size: 0.6rem;
font-weight: 700;
padding: 0.1rem 0.3rem;
@@ -1826,7 +1834,7 @@
justify-content: space-between;
margin-top: 0.25rem;
font-size: 0.6rem;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
}
/* ── Progress 8 number line ──────────────────────────── */
.p8Viz {
@@ -1837,14 +1845,14 @@
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
margin-bottom: 0.5rem;
}
.p8VizTrack {
position: relative;
height: 14px;
- background: rgba(45, 125, 125, 0.06);
- border: 1px solid var(--border-color, #e5dfd5);
+ background: rgba(var(--status-above-rgb), 0.06);
+ border: 1px solid var(--border);
border-radius: 4px;
overflow: visible;
}
@@ -1852,7 +1860,7 @@
position: absolute;
top: 0;
bottom: 0;
- background: rgba(45, 125, 125, 0.18);
+ background: rgba(var(--status-above-rgb), 0.18);
border-radius: 3px;
}
.p8VizZero {
@@ -1860,7 +1868,7 @@
top: -4px;
bottom: -4px;
width: 2px;
- background: var(--border-color, #e5dfd5);
+ background: var(--border);
z-index: 1;
}
.p8VizDot {
@@ -1870,20 +1878,20 @@
width: 12px;
height: 12px;
border-radius: 50%;
- background: var(--accent-teal, #2d7d7d);
+ background: var(--brand);
border: 2px solid white;
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 1px 4px rgba(var(--shadow-rgb), 0.2);
z-index: 3;
}
.p8VizDotNeg {
- background: var(--accent-coral, #e07256);
+ background: var(--brand);
}
.p8VizTicks {
display: flex;
justify-content: space-between;
margin-top: 0.25rem;
font-size: 0.6rem;
- color: var(--text-muted, #6d685f);
+ color: var(--text-muted);
}
/* ── Secondary variants ───────────────────────────────────────────────────
@@ -1896,7 +1904,7 @@
/* Secondary renders a slimmer gender bar on a translucent track. */
.genderBarSecondary {
height: 4px;
- background: rgba(0, 0, 0, 0.08);
+ background: var(--border);
margin-top: 0.45rem;
}
@@ -1911,8 +1919,9 @@
/* Secondary hero stats sit tighter and omit the Georgia fallback. */
.heroStatValueSecondary {
+ font-variant-numeric: tabular-nums;
gap: 0.4rem;
- font-family: var(--font-playfair), "Playfair Display", serif;
+ font-family: var(--font-display);
}
/* ── Secondary-scoped overrides ──────────────────────────────────────────
@@ -1922,9 +1931,9 @@
class that only SecondarySchoolSections carries. Same principle as the
variant classes above, applied at rule scope. */
-.secondaryScope .heroStatCard .heroStatLabel { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted, #6d685f); }
-.secondaryScope .heroStatCard .heroStatValue { font-family: var(--font-playfair), "Playfair Display", serif; font-size: 2.1rem; font-weight: 700; line-height: 1; color: var(--accent-teal, #2d7d7d); display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; }
-.secondaryScope .heroStatCard .heroStatHint { font-size: 0.7rem; color: var(--text-muted, #6d685f); font-style: normal; margin-top: 0; }
+.secondaryScope .heroStatCard .heroStatLabel { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
+.secondaryScope .heroStatCard .heroStatValue { font-family: var(--font-display); font-size: 2.1rem; font-weight: 700; line-height: 1; color: var(--status-above); display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; }
+.secondaryScope .heroStatCard .heroStatHint { font-size: 0.7rem; color: var(--text-muted); font-style: normal; margin-top: 0; }
.secondaryScope .historyDisclosure[open] .historyToggle::before { transform: rotate(90deg); }
@media (max-width: 480px) {
.secondaryScope .metricsGrid { grid-template-columns: 1fr 1fr; gap: 0.5rem; }
diff --git a/nextjs-app/lib/theme.ts b/nextjs-app/lib/theme.ts
new file mode 100644
index 0000000..31032c9
--- /dev/null
+++ b/nextjs-app/lib/theme.ts
@@ -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 = {
+ '--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(
+ ...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})`;
+}
diff --git a/nextjs-app/lib/utils.ts b/nextjs-app/lib/utils.ts
index 11e4f3f..fcbaf41 100644
--- a/nextjs-app/lib/utils.ts
+++ b/nextjs-app/lib/utils.ts
@@ -273,17 +273,29 @@ export function debounce 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 red–green 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)';
}
}
diff --git a/nextjs-app/next.config.js b/nextjs-app/next.config.js
index c334147..0139401 100644
--- a/nextjs-app/next.config.js
+++ b/nextjs-app/next.config.js
@@ -3,6 +3,15 @@ const nextConfig = {
// Enable standalone output for Docker
output: 'standalone',
+ // app/opengraph-image.tsx reads the Schibsted Grotesk files off disk at
+ // request time (Satori needs a font buffer; it has no system fallback).
+ // File tracing currently picks these up on its own, but that relies on the
+ // tracer resolving a runtime join() — declare them so a Next upgrade can't
+ // silently drop them and turn every link preview into a 500.
+ outputFileTracingIncludes: {
+ '/opengraph-image': ['./assets/**'],
+ },
+
// The /api/* and /sitemap.xml proxies to the FastAPI backend are route
// handlers (app/api/[...path]/route.ts, app/sitemap.xml/route.ts) rather
// than rewrites, so the backend host is read from FASTAPI_URL at runtime
@@ -72,7 +81,9 @@ const nextConfig = {
],
},
{
- source: '/favicon.svg',
+ // favicon.svg was replaced by app/icon.svg (the mark now has one
+ // source in components/Logo.tsx); this rule was caching a 404.
+ source: '/icon.svg',
headers: [
{
key: 'Cache-Control',
diff --git a/nextjs-app/public/favicon.svg b/nextjs-app/public/favicon.svg
deleted file mode 100644
index 7b533fc..0000000
--- a/nextjs-app/public/favicon.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/nextjs-app/public/icon-192.png b/nextjs-app/public/icon-192.png
new file mode 100644
index 0000000..3aeabc2
Binary files /dev/null and b/nextjs-app/public/icon-192.png differ
diff --git a/nextjs-app/public/icon-512.png b/nextjs-app/public/icon-512.png
new file mode 100644
index 0000000..969395c
Binary files /dev/null and b/nextjs-app/public/icon-512.png differ
diff --git a/nextjs-app/public/icon-maskable-512.png b/nextjs-app/public/icon-maskable-512.png
new file mode 100644
index 0000000..612182a
Binary files /dev/null and b/nextjs-app/public/icon-maskable-512.png differ
diff --git a/nextjs-app/public/manifest.json b/nextjs-app/public/manifest.json
index 740ecbf..413af82 100644
--- a/nextjs-app/public/manifest.json
+++ b/nextjs-app/public/manifest.json
@@ -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"
}
]
}