From 8ab0ac0a0458fff5745a3cc5f285c31ae0df503e Mon Sep 17 00:00:00 2001 From: Tudor Date: Thu, 6 Aug 2026 12:13:04 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feat(design):=20adopt=20the=20Cohort=20iden?= =?UTF-8?q?tity=20=E2=80=94=20new=20palette,=20type,=20mark=20and=20dark?= =?UTF-8?q?=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the direction agreed from the identity board: Route C ("Cohort") with the paper ground from C1, the Schibsted Grotesk / Literata pairing from C2, and the iris accent from C3. Dark theme is in scope from the start rather than retrofitted. The audit found three things wrong beyond taste: * No brand asset set. og:image was absent entirely, so every link shared into a class WhatsApp group rendered as a bare grey card. apple-touch-icon pointed at an SVG, which iOS ignores, and the manifest shipped no PNGs, so Android installs had no icon. The header mark and the favicon had also drifted into two different logos. * No colour discipline. --primary and --trend-down were the same coral, so the main CTA and "below average" shared a hue. 58 distinct hex values were spread across component CSS, and the chart palette was still Chart.js's stock demo colours. * A dark theme that was declared but never built — themeColor announced a dark variant with no dark styling behind it. What changed: Colour now has exactly three jobs that never borrow each other's hues: brand (iris) for interactive and identity, status (teal/amber) for above/below a comparison point, and phase for categories. Teal/amber rather than green/red keeps the above/below signal readable for every form of colour blindness. Every chromatic literal in component CSS is now a token, and the JS-painted surfaces (Chart.js, Leaflet) read the tokens through lib/theme so they follow the theme instead of ignoring it. The mark is the five-bar cohort spread — the same object as the distribution strip inside a school row, built from opacity steps so it inverts cleanly. components/Logo.tsx is the single source; the favicon, apple-icon and share card all derive from its geometry. globals.css drops 123 dead global classes left over from the vanilla-JS app (only the btn family, .skip-link and .main were still referenced), along with the noise overlay. It also gains prefers-reduced-motion support, which was missing entirely, and a type scale so the 54 ad-hoc font sizes have somewhere to converge. Verified: tsc clean, 159 unit tests pass, production build succeeds and prerenders /icon.svg, /apple-icon and /opengraph-image. Three e2e journeys added for the asset set, the themeColor/background match, and the dark theme actually repainting — all silent failures that nothing on the page reveals. Co-Authored-By: Claude Opus 5 --- e2e/tests/journeys.spec.ts | 76 + nextjs-app/app/apple-icon.tsx | 46 + nextjs-app/app/globals.css | 2211 +++-------------- nextjs-app/app/icon.svg | 7 + nextjs-app/app/layout.tsx | 39 +- nextjs-app/app/opengraph-image.tsx | 132 + nextjs-app/assets/SchibstedGrotesk-Bold.ttf | Bin 0 -> 101164 bytes .../assets/SchibstedGrotesk-Regular.ttf | Bin 0 -> 100692 bytes .../components/AdmissionsTrendChart.tsx | 20 +- .../components/AdmissionsView.module.css | 153 +- .../components/ComparisonChart.module.css | 6 +- nextjs-app/components/ComparisonChart.tsx | 18 +- .../components/ComparisonToast.module.css | 38 +- .../components/ComparisonView.module.css | 52 +- nextjs-app/components/DeltaChip.module.css | 12 +- nextjs-app/components/DotStrip.module.css | 2 +- nextjs-app/components/EmptyState.module.css | 22 +- nextjs-app/components/FilterBar.module.css | 94 +- nextjs-app/components/Footer.module.css | 26 +- nextjs-app/components/HomeView.module.css | 317 +-- nextjs-app/components/HomeView.tsx | 20 +- nextjs-app/components/InfoPopover.module.css | 20 +- nextjs-app/components/LeafletHeroMapInner.tsx | 6 +- nextjs-app/components/LeafletMapInner.tsx | 32 +- .../components/LoadingSkeleton.module.css | 20 +- nextjs-app/components/Logo.tsx | 65 + nextjs-app/components/Modal.module.css | 26 +- nextjs-app/components/Navigation.module.css | 66 +- nextjs-app/components/Navigation.tsx | 11 +- nextjs-app/components/Pagination.module.css | 38 +- .../components/PerformanceChart.module.css | 28 +- nextjs-app/components/PerformanceChart.tsx | 61 +- nextjs-app/components/RankingsView.module.css | 109 +- nextjs-app/components/SatsChart.module.css | 28 +- nextjs-app/components/SatsChart.tsx | 6 +- nextjs-app/components/SchoolCard.module.css | 49 +- .../components/SchoolHeroMap.module.css | 14 +- nextjs-app/components/SchoolMap.module.css | 8 +- nextjs-app/components/SchoolRow.module.css | 71 +- .../components/SchoolSearchModal.module.css | 46 +- .../components/SecondarySchoolRow.module.css | 76 +- .../components/SpecialSchoolNote.module.css | 8 +- .../compare/CompareAcademics.module.css | 4 +- .../components/compare/CompareAtAGlance.tsx | 2 +- .../components/compare/CompareOfsted.tsx | 2 +- .../compare/TrendsExplorer.module.css | 8 +- .../compare/compareSections.module.css | 40 +- .../school/SchoolDetailShell.module.css | 165 +- .../school/schoolSections.module.css | 529 ++-- nextjs-app/lib/theme.ts | 163 ++ nextjs-app/lib/utils.ts | 70 +- nextjs-app/public/favicon.svg | 6 - nextjs-app/public/icon-192.png | Bin 0 -> 2612 bytes nextjs-app/public/icon-512.png | Bin 0 -> 6949 bytes nextjs-app/public/icon-maskable-512.png | Bin 0 -> 5251 bytes nextjs-app/public/manifest.json | 24 +- 56 files changed, 2019 insertions(+), 3073 deletions(-) create mode 100644 nextjs-app/app/apple-icon.tsx create mode 100644 nextjs-app/app/icon.svg create mode 100644 nextjs-app/app/opengraph-image.tsx create mode 100644 nextjs-app/assets/SchibstedGrotesk-Bold.ttf create mode 100644 nextjs-app/assets/SchibstedGrotesk-Regular.ttf create mode 100644 nextjs-app/components/Logo.tsx create mode 100644 nextjs-app/lib/theme.ts delete mode 100644 nextjs-app/public/favicon.svg create mode 100644 nextjs-app/public/icon-192.png create mode 100644 nextjs-app/public/icon-512.png create mode 100644 nextjs-app/public/icon-maskable-512.png diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index 3321d18..6c4fbf2 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -626,3 +626,79 @@ test('sticky section nav jumps to server-rendered sections', async ({ page }) => const after = await page.evaluate(() => window.scrollY); expect(after).toBeGreaterThan(before); }); + +/** + * Brand surface. The identity work replaced a favicon-only asset set that + * broke iOS home-screen icons, Android PWA installs, and every link preview. + * These are silent failures — nothing on the page looks wrong — so they need + * a gate. + */ +test('the brand asset set is complete and served', async ({ page }) => { + const response = await page.goto('/'); + expect(response?.ok()).toBe(true); + + // The share card: without it, every link pasted into a chat renders bare. + const ogImage = page.locator('meta[property="og:image"]'); + await expect(ogImage).toHaveCount(1); + const ogUrl = await ogImage.getAttribute('content'); + expect(ogUrl).toBeTruthy(); + const og = await page.request.get(ogUrl!); + expect(og.ok()).toBe(true); + expect(og.headers()['content-type']).toContain('image/png'); + + // iOS ignores SVG touch icons, so this must be a real raster. + const apple = await page.request.get('/apple-icon'); + expect(apple.ok()).toBe(true); + expect(apple.headers()['content-type']).toContain('image/png'); + + // Android needs a maskable PNG or the install prompt has no icon. + for (const icon of ['/icon-192.png', '/icon-512.png', '/icon-maskable-512.png']) { + const res = await page.request.get(icon); + expect(res.ok(), `${icon} should be served`).toBe(true); + } +}); + +test('theme colour matches the page background in both themes', async ({ browser }) => { + // A mismatch here paints a stripe of the wrong colour across the top of the + // screen on mobile. Previously the dark themeColor was declared with no dark + // styling behind it at all. + for (const colorScheme of ['light', 'dark'] as const) { + const context = await browser.newContext({ colorScheme }); + const page = await context.newPage(); + await page.goto('/'); + + const declared = await page + .locator(`meta[name="theme-color"][media*="${colorScheme}"]`) + .getAttribute('content'); + expect(declared, `theme-color declared for ${colorScheme}`).toBeTruthy(); + + const painted = await page.evaluate(() => + getComputedStyle(document.documentElement).getPropertyValue('--bg-primary').trim() + ); + expect(painted.toLowerCase()).toBe(declared!.toLowerCase()); + + await context.close(); + } +}); + +test('the dark theme actually repaints the page', async ({ browser }) => { + // Guards the token layer: if a component reintroduces a hardcoded colour, + // the surface below stays light while everything around it flips. + const read = async (colorScheme: 'light' | 'dark') => { + const context = await browser.newContext({ colorScheme }); + const page = await context.newPage(); + await page.goto('/'); + await expect(page.locator('h1').first()).toBeVisible({ timeout: 15_000 }); + const values = await page.evaluate(() => { + const s = getComputedStyle(document.body); + return { bg: s.backgroundColor, fg: s.color }; + }); + await context.close(); + return values; + }; + + const light = await read('light'); + const dark = await read('dark'); + expect(dark.bg).not.toBe(light.bg); + expect(dark.fg).not.toBe(light.fg); +}); diff --git a/nextjs-app/app/apple-icon.tsx b/nextjs-app/app/apple-icon.tsx new file mode 100644 index 0000000..ce161d4 --- /dev/null +++ b/nextjs-app/app/apple-icon.tsx @@ -0,0 +1,46 @@ +import { ImageResponse } from 'next/og'; +import { BARS } from '@/components/Logo'; + +// iOS ignores SVG touch icons — it needs a real raster, on an opaque ground, +// with no transparency (it composites its own rounded mask). Previously this +// pointed at favicon.svg, so add-to-home-screen produced a blank tile. +export const size = { width: 180, height: 180 }; +export const contentType = 'image/png'; + +const COHORT = ['rgba(22,32,42,0.22)', 'rgba(22,32,42,0.38)', 'rgba(22,32,42,0.55)', '', 'rgba(22,32,42,0.22)']; +const SCALE = 3.6; // 40 → 144, leaving a 18px margin inside 180 + +export default function AppleIcon() { + return new ImageResponse( + ( +
+
+ {BARS.map(([x, y, w, h, isSchool], i) => ( +
+ ))} +
+
+ ), + size + ); +} diff --git a/nextjs-app/app/globals.css b/nextjs-app/app/globals.css index 1b66d32..67ae427 100644 --- a/nextjs-app/app/globals.css +++ b/nextjs-app/app/globals.css @@ -1,81 +1,239 @@ -/* - * SchoolCompare.co.uk - * A warm, editorial design inspired by quality publications +/* + * SchoolCompare.co.uk — "Cohort" + * + * A school's number means nothing on its own; its position in the national + * distribution does. That idea drives the identity: the logo is the same + * five-bar spread that appears inside every school row. + * + * Colour has exactly three jobs and they never borrow each other's hues: + * brand (iris) interactive + identity. Never means "good". + * status (teal / amber) above / below the comparison point. Never a CTA. + * phase (desaturated) category chips, deliberately subordinate. + * + * Teal/amber rather than green/red so the above/below signal survives every + * form of colour blindness — the audience includes parents reading closely. */ :root { - /* Warm, sophisticated palette */ - --bg-primary: #faf7f2; - --bg-secondary: #f3ede4; - --bg-card: #ffffff; - --bg-accent: #1a1612; - - --text-primary: #1a1612; - --text-secondary: #5c564d; - --text-muted: #6d685f; /* Darkened for WCAG AA (4.6:1 on cream) */ - --text-inverse: #faf7f2; - - --accent-coral: #e07256; - --accent-coral-dark: #b04a2e; /* AA: 4.5+ as text on white/cream tints; 5.4:1 under white text */ - --accent-coral-darker: #9c3f26; /* hover for coral-dark fills */ - --accent-teal: #296f6f; /* AA: 4.8+ as text on the cream/tint backgrounds */ - --accent-teal-light: #3a9e9e; - --accent-gold: #c9a227; - --accent-gold-text: #7a6800; /* WCAG AA safe for text on white/cream */ - --accent-navy: #2c3e50; + color-scheme: light; - /* Semantic background tints (replaces hardcoded rgba values) */ - --accent-coral-bg: rgba(224, 114, 86, 0.12); - --accent-teal-bg: rgba(45, 125, 125, 0.12); - --accent-gold-bg: rgba(201, 162, 39, 0.12); + /* ── Ground ─────────────────────────────────────────────────────── */ + --bg-primary: #f2f1ed; /* paper, not cream — warm enough to read as a document */ + --bg-secondary: #e9e8e2; + --bg-card: #fcfcfa; + --surface-inverse: #16202a; /* dark fills: skip link, active nav, tooltips */ - /* Trend colours */ - --trend-up: #16a34a; - --trend-down: var(--accent-coral); - --trend-stable: var(--text-muted); + /* ── Ink ────────────────────────────────────────────────────────── */ + --text-primary: #16202a; + --text-secondary: #4a545f; + --text-muted: #5c6570; /* 5.3:1 on --bg-primary */ + --text-inverse: #f7f7f4; + + /* ── Line ───────────────────────────────────────────────────────── */ + --border: #dedcd5; + --border-strong: #cdcac1; + + /* ── Brand: iris ────────────────────────────────────────────────── */ + --brand: #584a9b; /* 6.9:1 on --bg-primary */ + --brand-strong: #473c80; /* hover on brand fills */ + --brand-stronger: #3a3069; /* active/pressed */ + --brand-bg: rgba(88, 74, 155, 0.10); + --brand-on: #fcfcfa; /* text/icon sitting on a brand fill */ + + /* ── Status: above / at / below the comparison point ────────────── */ + --status-above: #0e6e66; /* 5.2:1 on --bg-primary */ + --status-above-bg: rgba(14, 110, 102, 0.10); + --status-below: #9a5b00; /* 4.8:1 on --bg-primary */ + --status-below-bg: rgba(154, 91, 0, 0.11); + --status-at: var(--text-muted); + --status-at-bg: rgba(92, 101, 112, 0.10); + + /* ── Charts: the same three hues, extended by lightness only ────── */ + --chart-1: #584a9b; + --chart-2: #0e6e66; + --chart-3: #9a5b00; + --chart-4: #8a7cc9; + --chart-5: #3e9c92; + --chart-6: #c9903d; + --chart-grid: #dedcd5; + --chart-reference: #5c6570; /* national/LA reference lines */ + + /* ── Series: up to 8 schools compared at once ─────────────────────── + Categorical needs distinguishable hues, so this ramp is wider than the + three-hue rule — but every step is held to the same mid-dark tone and + moderate saturation, and every one clears AA on --bg-primary, so they + read as one family and work as legend text as well as chart lines. */ + --series-1: #584a9b; + --series-2: #0e6e66; + --series-3: #9a5b00; + --series-4: #a03a5e; + --series-5: #2f5f8f; + --series-6: #4a6b2f; + --series-7: #7a3f7a; + --series-8: #97442a; + + /* ── Phase: category, desaturated so it stays under the status hues ── */ + --phase-primary: #4a6072; + --phase-primary-bg: rgba(74, 96, 114, 0.10); + --phase-primary-text: #3b4e5e; + --phase-secondary: #5f5480; + --phase-secondary-bg: rgba(95, 84, 128, 0.10); + --phase-secondary-text: #4e4570; + --phase-all-through: #4b6b57; + --phase-all-through-bg: rgba(75, 107, 87, 0.10); + --phase-all-through-text: #3d5847; + --phase-post16: #7a6242; + --phase-post16-bg: rgba(122, 98, 66, 0.10); + --phase-post16-text: #645036; + --phase-nursery: #7a5560; + --phase-nursery-bg: rgba(122, 85, 96, 0.10); + --phase-nursery-text: #64454f; + + /* ── Medals: rankings podium. Metal, not palette — kept legible on + both grounds rather than literally gold/silver/bronze. ───────── */ + --medal-gold: #a67c00; + --medal-silver: #6f7580; + --medal-bronze: #8a5a2b; + + /* ── Channels ─────────────────────────────────────────────────────── + CSS can't interpolate a hex token into rgba(), so any tint that needs + an arbitrary alpha reads the raw channels from here. Keep these in + sync with the hex tokens above — they are the same colours. */ + --brand-rgb: 88, 74, 155; + --status-above-rgb: 14, 110, 102; + --status-below-rgb: 154, 91, 0; + --ink-rgb: 22, 32, 42; + --text-inverse-rgb: 247, 247, 244; + --shadow-rgb: 22, 32, 42; + --muted-rgb: 92, 101, 112; + --phase-secondary-rgb: 95, 84, 128; + --medal-silver-rgb: 111, 117, 128; + --medal-bronze-rgb: 138, 90, 43; + + /* ── Elevation ──────────────────────────────────────────────────── */ + --shadow-soft: 0 1px 2px rgba(22, 32, 42, 0.04), 0 2px 8px rgba(22, 32, 42, 0.05); + --shadow-medium: 0 4px 20px rgba(22, 32, 42, 0.09); + --shadow-strong: 0 8px 40px rgba(22, 32, 42, 0.14); + --scrim: rgba(22, 32, 42, 0.55); + + /* ── Type ───────────────────────────────────────────────────────── */ + --font-display: var(--font-schibsted), 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, sans-serif; + --font-ui: var(--font-schibsted), 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, sans-serif; + --font-data: var(--font-schibsted), 'Schibsted Grotesk', -apple-system, BlinkMacSystemFont, sans-serif; + --font-prose: var(--font-literata), 'Literata', Georgia, serif; + + /* Type scale, 1.2 ratio off a 1rem base. New work should use these + rather than inventing another font-size. */ + --step--2: 0.694rem; + --step--1: 0.833rem; + --step-0: 1rem; + --step-1: 1.2rem; + --step-2: 1.44rem; + --step-3: 1.728rem; + --step-4: 2.074rem; + --step-5: 2.488rem; + + /* ── Geometry & motion ──────────────────────────────────────────── */ + --radius-sm: 3px; + --radius-md: 6px; + --radius-lg: 10px; + --radius-xl: 16px; - /* Button/Action colors */ - --primary: #e07256; - --primary-dark: #c45a3f; - --success: #2d7d7d; - --border-light: #e5dfd5; - - /* Chart colors */ - --chart-1: #e07256; - --chart-2: #2d7d7d; - --chart-3: #c9a227; - --chart-4: #7b68a6; - --chart-5: #3498db; - - --border-color: #e5dfd5; - --shadow-soft: 0 2px 8px rgba(26, 22, 18, 0.06); - --shadow-medium: 0 4px 20px rgba(26, 22, 18, 0.1); - --shadow-strong: 0 8px 40px rgba(26, 22, 18, 0.15); - - --radius-sm: 4px; - --radius-md: 8px; - --radius-lg: 16px; - --radius-xl: 24px; - --transition: 0.2s ease; --transition-slow: 0.4s ease; +} - /* Phase indicators */ - --phase-primary: #5b8cbf; - --phase-primary-bg: rgba(91, 140, 191, 0.10); - --phase-primary-text: #3d6a99; - --phase-secondary: #9b6bb0; - --phase-secondary-bg: rgba(155, 107, 176, 0.10); - --phase-secondary-text: #7a4f93; - --phase-all-through: #7a9a6d; - --phase-all-through-bg: rgba(122, 154, 109, 0.10); - --phase-all-through-text: #5a7a4d; - --phase-post16: #c4915e; - --phase-post16-bg: rgba(196, 145, 94, 0.10); - --phase-post16-text: #9a6d3a; - --phase-nursery: #e0a0b0; - --phase-nursery-bg: rgba(224, 160, 176, 0.10); - --phase-nursery-text: #b06070; +/* + * Dark theme. Not an inversion — the accent and both status hues are lifted + * independently so each still clears AA on the dark ground, and --brand-on + * flips to dark because the brand fill is now the lighter of the pair. + * + * Tokens only. Never style a component from inside this block, or the two + * themes drift apart the first time someone edits one of them. + */ +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + + --bg-primary: #10151b; + --bg-secondary: #161c24; + --bg-card: #171d25; + --surface-inverse: #e4e9ee; + + --text-primary: #e4e9ee; + --text-secondary: #b4bec8; + --text-muted: #8d99a6; /* 6.4:1 on --bg-primary */ + --text-inverse: #10151b; + + --border: #28313b; + --border-strong: #38434f; + + --brand: #9c8ce8; /* 6.5:1 on --bg-primary */ + --brand-strong: #b0a3ee; + --brand-stronger: #c2b8f3; + --brand-bg: rgba(156, 140, 232, 0.14); + --brand-on: #10151b; + + --status-above: #3fb3a4; /* 7.2:1 */ + --status-above-bg: rgba(63, 179, 164, 0.14); + --status-below: #d99b2e; /* 7.7:1 */ + --status-below-bg: rgba(217, 155, 46, 0.14); + --status-at-bg: rgba(141, 153, 166, 0.14); + + --chart-1: #9c8ce8; + --chart-2: #3fb3a4; + --chart-3: #d99b2e; + --chart-4: #6f5fc4; + --chart-5: #2b8a7e; + --chart-6: #a97420; + --chart-grid: #28313b; + --chart-reference: #8d99a6; + + --series-1: #9c8ce8; + --series-2: #3fb3a4; + --series-3: #d99b2e; + --series-4: #e07a9d; + --series-5: #6fa8dc; + --series-6: #94c46b; + --series-7: #c48bc4; + --series-8: #e08a6b; + + --phase-primary: #8aa4b8; + --phase-primary-bg: rgba(138, 164, 184, 0.14); + --phase-primary-text: #a3bacd; + --phase-secondary: #a294c4; + --phase-secondary-bg: rgba(162, 148, 196, 0.14); + --phase-secondary-text: #b7abd6; + --phase-all-through: #8bb098; + --phase-all-through-bg: rgba(139, 176, 152, 0.14); + --phase-all-through-text: #a2c2ad; + --phase-post16: #bfa27c; + --phase-post16-bg: rgba(191, 162, 124, 0.14); + --phase-post16-text: #d0b795; + --phase-nursery: #bf98a3; + --phase-nursery-bg: rgba(191, 152, 163, 0.14); + --phase-nursery-text: #d1aeb8; + + --medal-gold: #d4a72c; + --medal-silver: #a8b0bb; + --medal-bronze: #c08552; + + --brand-rgb: 156, 140, 232; + --status-above-rgb: 63, 179, 164; + --status-below-rgb: 217, 155, 46; + --ink-rgb: 228, 233, 238; + --text-inverse-rgb: 16, 21, 27; + --shadow-rgb: 0, 0, 0; + --muted-rgb: 141, 153, 166; + --phase-secondary-rgb: 162, 148, 196; + --medal-silver-rgb: 168, 176, 187; + --medal-bronze-rgb: 192, 133, 82; + + --shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3); + --shadow-medium: 0 4px 20px rgba(0, 0, 0, 0.45); + --shadow-strong: 0 8px 40px rgba(0, 0, 0, 0.6); + --scrim: rgba(4, 7, 10, 0.7); + } } * { @@ -89,7 +247,7 @@ html { } body { - font-family: var(--font-dm-sans), 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif; + font-family: var(--font-ui); background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; @@ -102,9 +260,23 @@ body { -webkit-tap-highlight-color: transparent; } +/* Every digit that could line up in a column does. This is a data product; + proportional numerals in a results table read as amateur. */ +table, +[data-numeric] { + font-variant-numeric: tabular-nums; +} + +/* Prose gets the serif; the interface never does. */ +.prose { + font-family: var(--font-prose); + font-size: var(--step-0); + line-height: 1.65; +} + /* Provide a baseline press feedback for the most common interactive elements — replaces the suppressed default tap highlight. Buttons and - .btn-* classes carry their own :active styles already; this handles + .btn-* classes carry their own :active states already; this handles plain anchors used as inline links and bare button elements. */ @media (hover: none) and (pointer: coarse) { a:active, @@ -127,7 +299,7 @@ body { left: 1rem; z-index: 10000; padding: 0.5rem 1rem; - background: var(--bg-accent); + background: var(--surface-inverse); color: var(--text-inverse); font-size: 0.875rem; font-weight: 600; @@ -139,9 +311,9 @@ body { top: 0.5rem; } -/* Focus indicators — branded and visible on cream background */ +/* Focus indicators — brand hue, visible on both grounds */ :focus-visible { - outline: 2px solid var(--accent-coral); + outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--radius-sm); } @@ -155,14 +327,14 @@ body { justify-content: center; gap: 0.375rem; padding: 0.5rem 1rem; - font-family: inherit; + font-family: var(--font-ui); font-size: 0.875rem; font-weight: 600; line-height: 1; - border-radius: 6px; + border-radius: var(--radius-md); border: 1px solid transparent; cursor: pointer; - transition: all var(--transition); + transition: background var(--transition), border-color var(--transition), color var(--transition); text-decoration: none; white-space: nowrap; } @@ -171,53 +343,52 @@ body { cursor: not-allowed; } -/* Primary: coral background — main CTAs (Search, Compare Now) */ +/* Primary: brand fill — main CTAs (Search, Compare now) */ .btn-primary { - background: var(--accent-coral-dark); - color: white; - border-color: var(--accent-coral-dark); + background: var(--brand); + color: var(--brand-on); + border-color: var(--brand); } .btn-primary:hover:not(:disabled) { - background: var(--accent-coral-darker); - border-color: var(--accent-coral-darker); + background: var(--brand-strong); + border-color: var(--brand-strong); +} +.btn-primary:active:not(:disabled) { + background: var(--brand-stronger); + border-color: var(--brand-stronger); } -/* Secondary: teal outline — supporting actions (+ Compare) */ -/* NOTE: a duplicate `.btn` block further down this file sets `border: none`, - which wins over the base `.btn`'s `1px solid transparent`. The outline - variants below therefore declare the full `border` shorthand explicitly so - they don't depend on the base border-width — otherwise `border-color` alone - has no width and the outline never renders (buttons read as plain text). */ +/* Secondary: brand outline — supporting actions (Add to shortlist) */ .btn-secondary { background: transparent; - color: var(--accent-teal); - border: 1px solid var(--accent-teal); + color: var(--brand); + border: 1px solid var(--brand); } .btn-secondary:hover:not(:disabled) { - background: var(--accent-teal-bg); + background: var(--brand-bg); } -/* Tertiary: subtle gray — low-emphasis (View, Clear) */ +/* Tertiary: quiet — low-emphasis (View, Clear) */ .btn-tertiary { background: var(--bg-secondary); color: var(--text-secondary); - border: 1px solid var(--border-color); + border: 1px solid var(--border); } .btn-tertiary:hover:not(:disabled) { - background: var(--border-color); + background: var(--border); color: var(--text-primary); } -/* Danger/active: for remove/destructive actions or active toggle state */ +/* Active toggle state — the shortlist button once a school is on the list */ .btn-active { - background: var(--accent-teal-bg); - color: var(--accent-teal); - border: 1px solid var(--accent-teal); + background: var(--brand-bg); + color: var(--brand); + border: 1px solid var(--brand); } .btn-active:hover:not(:disabled) { background: transparent; - color: var(--accent-coral-dark); - border-color: var(--accent-coral-dark); + color: var(--text-secondary); + border-color: var(--border-strong); } /* Small variant */ @@ -226,97 +397,7 @@ body { font-size: 0.8125rem; } -/* Subtle noise texture overlay - editorial paper feel */ -.noise-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - opacity: 0.06; - z-index: 1000; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); -} - -/* Header */ -.header { - background: var(--bg-card); - border-bottom: 1px solid var(--border-color); - position: sticky; - top: 0; - z-index: 1000; -} - -.header-content { - max-width: 1400px; - margin: 0 auto; - padding: 0.625rem 1.5rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.logo { - display: flex; - align-items: center; - gap: 0.5rem; - text-decoration: none; - color: inherit; -} - -.logo-icon { - width: 32px; - height: 32px; - color: var(--accent-coral); -} - -.logo-text { - display: flex; - flex-direction: column; -} - -.logo-title { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: 1.125rem; - font-weight: 700; - color: var(--text-primary); - line-height: 1.2; -} - -.logo-subtitle { - font-size: 0.625rem; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.1em; -} - -.nav { - display: flex; - gap: 0.25rem; -} - -.nav-link { - padding: 0.4rem 0.875rem; - text-decoration: none; - color: var(--text-secondary); - font-weight: 500; - font-size: 0.8125rem; - border-radius: var(--radius-md); - transition: var(--transition); -} - -.nav-link:hover { - color: var(--text-primary); - background: var(--bg-secondary); -} - -.nav-link.active { - background: var(--bg-accent); - color: var(--text-inverse); -} - -/* Main Content */ +/* Main content column */ .main { max-width: 1400px; margin: 0 auto; @@ -325,1746 +406,24 @@ body { overflow-x: clip; } -.view { - display: none; - animation: fadeIn 0.3s ease; -} - -.view.active { - display: block; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Hero Section */ -.hero { - text-align: center; - padding: 1.5rem 0 1rem; -} - -.hero-title { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: clamp(2rem, 5vw, 3.5rem); - font-weight: 700; - color: var(--text-primary); - margin-bottom: 0.75rem; - line-height: 1.2; -} - -.hero-subtitle { - font-size: 1.1rem; - color: var(--text-secondary); - max-width: 600px; - margin: 0 auto; -} - -/* Search Section */ -.search-section { - max-width: 700px; - margin: 2rem auto 3rem; -} - -/* Search Mode Toggle */ -.search-mode-toggle { - display: flex; - justify-content: center; - gap: 0; - margin-bottom: 1.5rem; - background: var(--bg-card); - border: 2px solid var(--border-color); - border-radius: var(--radius-lg); - padding: 4px; - max-width: 400px; - margin-left: auto; - margin-right: auto; -} - -.search-mode-btn { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 0.75rem 1.25rem; - font-size: 0.95rem; - font-family: inherit; - font-weight: 500; - border: none; - border-radius: var(--radius-md); - background: transparent; - color: var(--text-muted); - cursor: pointer; - transition: var(--transition); -} - -.search-mode-btn:hover { - color: var(--text-primary); -} - -.search-mode-btn.active { - background: var(--accent-coral); - color: white; - box-shadow: 0 2px 8px rgba(224, 114, 86, 0.3); -} - -.search-mode-btn svg { - flex-shrink: 0; -} - -/* Search Panels */ -.search-panel { - display: none; -} - -.search-panel.active { - display: block; -} - -.search-container { - position: relative; - margin-bottom: 1rem; -} - -.search-input { - width: 100%; - padding: 1rem 1.25rem 1rem 3.5rem; - font-size: 1rem; - font-family: inherit; - border: 2px solid var(--border-color); - border-radius: var(--radius-lg); - background: var(--bg-card); - color: var(--text-primary); - transition: var(--transition); -} - -.search-input:focus { - outline: none; - border-color: var(--accent-coral); - box-shadow: 0 0 0 4px rgba(224, 114, 86, 0.1); -} - -.search-input::placeholder { - color: var(--text-muted); -} - -.search-icon { - position: absolute; - left: 1.25rem; - top: 50%; - transform: translateY(-50%); - width: 20px; - height: 20px; - color: var(--text-muted); -} - -.filter-row { - display: flex; - gap: 1rem; - justify-content: center; -} - -.filter-select { - padding: 0.6rem 2rem 0.6rem 1rem; - font-size: 0.9rem; - font-family: inherit; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-card); - color: var(--text-primary); - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235c564d' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 0.75rem center; - transition: var(--transition); -} - -/* Location Search */ -.location-input-group { - margin-bottom: 1rem; - display: flex; - gap: 0.75rem; - align-items: center; - justify-content: center; - flex-wrap: nowrap; -} - -.postcode-input { - width: auto; - max-width: 180px; - padding: 0.6rem 1rem; - font-size: 0.95rem; -} - -.postcode-input::placeholder { - text-transform: none; -} - -.radius-select { - width: auto; - min-width: 100px; -} - -.location-btn { - padding: 0.6rem 1.25rem; - font-size: 0.9rem; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.location-clear-btn { - padding: 0.6rem 1rem; - font-size: 0.9rem; - background: var(--bg-secondary); - color: var(--text-secondary); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - cursor: pointer; - transition: var(--transition); -} - -.location-clear-btn:hover { - background: var(--border-color); - color: var(--text-primary); -} - -.location-info { - text-align: center; - margin-bottom: 1rem; - padding: 0.75rem 1.25rem; - background: var(--accent-teal); - color: var(--text-inverse); - border-radius: var(--radius-md); - font-size: 0.9rem; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.location-info svg { - width: 16px; - height: 16px; -} - -.distance-badge { - display: inline-block; - padding: 0.25rem 0.5rem; - background: var(--accent-teal); - color: var(--text-inverse); - border-radius: var(--radius-sm); - font-size: 0.75rem; - font-weight: 600; - margin-left: 0.5rem; -} - -.filter-select:focus { - outline: none; - border-color: var(--accent-teal); -} - -/* View Toggle */ -.view-toggle { - display: flex; - gap: 0.5rem; - justify-content: center; - margin-bottom: 1.5rem; -} - -.view-toggle-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 0.6rem 1rem; - font-size: 0.9rem; - font-family: inherit; - font-weight: 500; - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - background: var(--bg-card); - color: var(--text-muted); - cursor: pointer; - transition: var(--transition); -} - -.view-toggle-btn:hover { - color: var(--text-primary); - border-color: var(--text-muted); -} - -.view-toggle-btn.active { - background: var(--accent-teal); - color: white; - border-color: var(--accent-teal); -} - -.view-toggle-btn svg { - flex-shrink: 0; -} - -/* Results Container */ -.results-container { - display: block; -} - -.results-container .results-map { - display: none; -} - -.results-container.map-view { - display: grid; - grid-template-columns: 1fr 400px; - gap: 1.5rem; - height: 600px; -} - -.results-container.map-view .results-map { - display: block; - border-radius: var(--radius-lg); - overflow: hidden; - border: 1px solid var(--border-color); - height: 100%; - position: relative; - z-index: 1; -} - -.results-container.map-view .schools-grid { - display: flex; - flex-direction: column; - gap: 0.75rem; - overflow-y: auto; - height: 100%; - padding-right: 0.5rem; -} - -.results-container.map-view .schools-grid::-webkit-scrollbar { - width: 6px; -} - -.results-container.map-view .schools-grid::-webkit-scrollbar-track { - background: var(--bg-main); - border-radius: 3px; -} - -.results-container.map-view .schools-grid::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 3px; -} - -.results-container.map-view .schools-grid::-webkit-scrollbar-thumb:hover { - background: var(--text-muted); -} - -/* Highlighted card in map view */ -.school-card.highlighted, -.school-list-item.highlighted { - border-color: var(--accent-teal); - box-shadow: 0 0 0 2px rgba(45, 125, 125, 0.2); -} - -/* Compact school list items for map view */ -.school-list-item { - display: flex; - align-items: center; - gap: 1rem; - padding: 0.875rem 1rem; - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - cursor: pointer; - transition: var(--transition); -} - -.school-list-item:hover { - border-color: var(--text-muted); -} - -.school-list-item-content { - flex: 1; - min-width: 0; -} - -.school-list-item-header { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.25rem; -} - -.school-list-item-name { - font-size: 0.95rem; - font-weight: 600; - color: var(--text-primary); - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.school-list-item-header .distance-badge { - flex-shrink: 0; - font-size: 0.75rem; - padding: 0.15rem 0.5rem; -} - -.school-list-item-meta { - display: flex; - gap: 0.5rem; - font-size: 0.8rem; - color: var(--text-muted); - margin-bottom: 0.375rem; -} - -.school-list-item-meta span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.school-list-item-stats { - display: flex; - gap: 1rem; - font-size: 0.8rem; - color: var(--text-secondary); -} - -.school-list-item-stat strong { - color: var(--text-primary); -} - -.school-list-item-actions { - display: flex; - flex-direction: column; - gap: 0.375rem; - flex-shrink: 0; -} - -.school-list-item-actions .btn { - padding: 0.4rem 0.75rem; - font-size: 0.75rem; - white-space: nowrap; -} - -.btn-compare { - background: var(--accent-coral-dark); - color: white; - border: 1px solid var(--accent-coral-dark); -} - -.btn-compare:hover { - background: var(--accent-coral-darker); - border-color: var(--accent-coral-darker); -} - -.btn-compare.active { - background: var(--text-muted); - border-color: var(--text-muted); -} - -/* Search location marker on map */ -.search-location-marker { - background: transparent; -} - -/* Schools Grid */ -.schools-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: 1.5rem; -} - -/* Featured Schools Header */ -.featured-header { - grid-column: 1 / -1; - text-align: center; - padding: 1rem 0 2rem; -} - -.featured-header h3 { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: 1.5rem; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 0.5rem; -} - -.featured-header p { - color: var(--text-muted); - font-size: 0.95rem; -} - -.school-card.featured { - border-color: var(--accent-coral); - border-width: 2px; -} - -.school-card { - display: flex; - flex-direction: column; - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); - padding: 1.5rem; - cursor: pointer; - transition: var(--transition); - position: relative; - overflow: hidden; - z-index: 0; -} - -.school-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 4px; - height: 100%; - background: var(--accent-coral); - transform: scaleY(0); - transition: var(--transition); -} - -.school-card:hover { - border-color: var(--accent-coral); - box-shadow: var(--shadow-medium); - transform: translateY(-2px); -} - -.school-card:hover::before { - transform: scaleY(1); -} - -.school-name { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: 1.15rem; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 0.5rem; -} - -.school-meta { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin-bottom: 1rem; -} - -.school-tag { - font-size: 0.75rem; - padding: 0.25rem 0.6rem; - background: var(--bg-secondary); - color: var(--text-secondary); - border-radius: var(--radius-sm); -} - -.school-tag.type { - background: rgba(45, 125, 125, 0.1); - color: var(--accent-teal); -} - -.school-tag.faith { - background: rgba(138, 43, 226, 0.1); - color: #8a2be2; -} - -.school-address { - font-size: 0.85rem; - color: var(--text-muted); - margin-bottom: 0.5rem; -} - -.school-details { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - margin-bottom: 1rem; - flex-grow: 1; -} - -.age-range { - font-size: 0.75rem; - color: var(--text-secondary); - padding: 0.2rem 0.5rem; - background: var(--bg-secondary); - border-radius: var(--radius-sm); -} - -.school-stats { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.5rem; - padding-top: 1rem; - border-top: 1px solid var(--border-color); - margin-top: auto; -} - -.stat { - text-align: center; -} - -.stat-value { - font-size: 1.1rem; - font-weight: 700; - color: var(--text-primary); - display: flex; - align-items: center; - justify-content: center; - gap: 0.25rem; -} - -.stat-value.positive { - color: var(--accent-teal); -} - -.stat-value.negative { - color: var(--accent-coral-dark); -} - -/* Trend indicators */ -.trend-indicator { - font-size: 0.75rem; - cursor: help; -} - -.trend-up { - color: var(--accent-teal); -} - -.trend-down { - color: var(--accent-coral-dark); -} - -.trend-stable { - color: var(--text-muted); - font-size: 0.6rem; -} - -.stat-label { - font-size: 0.65rem; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.03em; -} - -/* School Card Map */ -.school-map { - height: 150px; - margin-top: 1rem; - border-radius: var(--radius-md); - overflow: hidden; - cursor: pointer; - border: 1px solid var(--border-color); - transition: var(--transition); -} - -.school-map:hover { - border-color: var(--accent-coral); - box-shadow: var(--shadow-small); -} - -/* Fullscreen Map Modal */ -.map-modal-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - z-index: 2000; - display: flex; - align-items: center; - justify-content: center; - padding: 1rem; - animation: fadeIn 0.2s ease; -} - -.map-modal { - background: var(--bg-card); - border-radius: var(--radius-lg); - width: 100%; - max-width: 900px; - max-height: 90vh; - display: flex; - flex-direction: column; - overflow: hidden; - box-shadow: var(--shadow-large); - animation: slideUp 0.3s ease; -} - -.map-modal-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem 1.5rem; - border-bottom: 1px solid var(--border-color); -} - -.map-modal-header h3 { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: 1.25rem; - font-weight: 600; - color: var(--text-primary); - margin: 0; -} - -.map-modal-close { - background: none; - border: none; - font-size: 1.75rem; - color: var(--text-muted); - cursor: pointer; - padding: 0.25rem 0.5rem; - line-height: 1; - transition: var(--transition); -} - -.map-modal-close:hover { - color: var(--accent-coral-dark); -} - -.map-modal-content { - height: 500px; - width: 100%; -} - @media (max-width: 768px) { - .map-modal { - max-height: 80vh; - } - - .map-modal-content { - height: 400px; - } - - .results-container.map-view { - grid-template-columns: 1fr; - grid-template-rows: 350px auto; - height: auto; - } - - .results-container.map-view .results-map { - height: 350px; - } - - .results-container.map-view .schools-grid { - height: auto; - max-height: 400px; - overflow-y: auto; - } -} - -/* Section Titles */ -.section-title { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: 2rem; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 0.5rem; -} - -.section-subtitle { - font-size: 1rem; - color: var(--text-secondary); - margin-bottom: 2rem; -} - -/* Compare View */ -.compare-header { - text-align: center; - padding: 2rem 0; -} - -.selected-schools { - display: flex; - flex-wrap: wrap; - gap: 1rem; - margin-bottom: 2rem; - min-height: 100px; - padding: 1.5rem; - background: var(--bg-secondary); - border-radius: var(--radius-lg); - justify-content: center; - align-items: center; -} - -.empty-selection { - text-align: center; - color: var(--text-muted); -} - -.empty-icon { - width: 48px; - height: 48px; - margin: 0 auto 0.5rem; - color: var(--text-muted); -} - -.selected-school-tag { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.6rem 1rem; - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - font-size: 0.9rem; - color: var(--text-primary); - animation: slideIn 0.2s ease; -} - -@keyframes slideIn { - from { opacity: 0; transform: scale(0.9); } - to { opacity: 1; transform: scale(1); } -} - -.selected-school-tag .remove { - width: 18px; - height: 18px; - border: none; - background: var(--bg-secondary); - border-radius: 50%; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); -} - -.selected-school-tag .remove:hover { - background: var(--accent-coral); - color: white; -} - -.compare-search-section { - max-width: 500px; - margin: 0 auto 2rem; - position: relative; -} - -.compare-results { - position: absolute; - top: 100%; - left: 0; - right: 0; - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - box-shadow: var(--shadow-medium); - max-height: 300px; - overflow-y: auto; - z-index: 50; - display: none; -} - -.compare-results.active { - display: block; -} - -.compare-results-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.6rem 1rem; - background: var(--bg-secondary); - border-bottom: 1px solid var(--border-color); - font-size: 0.8rem; - color: var(--text-muted); - position: sticky; - top: 0; -} - -.compare-results-close { - width: 24px; - height: 24px; - border: none; - background: transparent; - border-radius: var(--radius-sm); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - color: var(--text-muted); - transition: var(--transition); -} - -.compare-results-close:hover { - background: var(--accent-coral); - color: white; -} - -.compare-result-item { - padding: 0.75rem 1rem; - cursor: pointer; - border-bottom: 1px solid var(--border-color); - transition: var(--transition); -} - -.compare-result-item:last-child { - border-bottom: none; -} - -.compare-result-item:hover { - background: var(--bg-secondary); -} - -.compare-result-item .name { - font-weight: 500; - color: var(--text-primary); -} - -.compare-result-item .location { - font-size: 0.8rem; - color: var(--text-muted); -} - -/* Charts Section */ -.charts-section { - margin-top: 2rem; -} - -.metric-selector { - display: flex; - align-items: center; - gap: 1rem; - margin-bottom: 1.5rem; -} - -.metric-selector label { - font-weight: 500; - color: var(--text-secondary); -} - -.chart-container { - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); - padding: 2rem; - margin-bottom: 2rem; -} - -/* Data Table */ -.data-table-container { - overflow-x: auto; - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg); -} - -.data-table { - width: 100%; - border-collapse: collapse; -} - -.data-table th, -.data-table td { - padding: 1rem; - text-align: left; - border-bottom: 1px solid var(--border-color); -} - -.data-table th { - background: var(--bg-secondary); - font-weight: 600; - font-size: 0.85rem; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-secondary); -} - -.data-table tr:last-child td { - border-bottom: none; -} - -.data-table tr:hover td { - background: rgba(224, 114, 86, 0.03); -} - -/* Rankings View */ -.rankings-header { - text-align: center; - padding: 2rem 0; -} - -.rankings-controls { - display: flex; - justify-content: center; - gap: 1rem; - margin-bottom: 2rem; -} - -.rankings-list { - max-width: 800px; - margin: 0 auto; -} - -.ranking-item { - display: flex; - align-items: center; - gap: 1.5rem; - padding: 1.25rem 1.5rem; - background: var(--bg-card); - border: 1px solid var(--border-color); - border-radius: var(--radius-md); - margin-bottom: 0.75rem; - transition: var(--transition); - cursor: pointer; -} - -.ranking-item:hover { - border-color: var(--accent-coral); - box-shadow: var(--shadow-soft); -} - -.ranking-position { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - font-weight: 700; - font-size: 1rem; -} - -.ranking-position.top-3 { - background: linear-gradient(135deg, var(--accent-gold), #d4af37); - color: white; -} - -.ranking-position:not(.top-3) { - background: var(--bg-secondary); - color: var(--text-secondary); -} - -.ranking-info { - flex: 1; -} - -.ranking-name { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 0.25rem; -} - -.ranking-location { - font-size: 0.85rem; - color: var(--text-muted); -} - -.ranking-score { - text-align: right; -} - -.ranking-score-value { - font-size: 1.5rem; - font-weight: 700; - color: var(--accent-teal); -} - -.ranking-score-label { - font-size: 0.7rem; - color: var(--text-muted); - text-transform: uppercase; -} - -/* Modal */ -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 2000; - display: none; - align-items: center; - justify-content: center; - padding: 2rem; -} - -.modal.active { - display: flex; -} - -.modal-backdrop { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(26, 22, 18, 0.6); - backdrop-filter: blur(4px); -} - -.modal-content { - position: relative; - background: var(--bg-card); - border-radius: var(--radius-xl); - max-width: 800px; - width: 100%; - max-height: 90vh; - overflow-y: auto; - box-shadow: var(--shadow-strong); - animation: modalIn 0.3s ease; -} - -@keyframes modalIn { - from { opacity: 0; transform: scale(0.95) translateY(20px); } - to { opacity: 1; transform: scale(1) translateY(0); } -} - -.modal-close { - position: absolute; - top: 1rem; - right: 1rem; - width: 40px; - height: 40px; - border: none; - background: var(--bg-secondary); - border-radius: 50%; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition); - z-index: 10; -} - -.modal-close:hover { - background: var(--accent-coral); - color: white; -} - -.modal-close svg { - width: 20px; - height: 20px; -} - -.modal-header { - position: relative; - padding: 2rem 2rem 1rem; - border-bottom: 1px solid var(--border-color); -} - -.modal-header h2 { - font-family: var(--font-playfair), 'Playfair Display', Georgia, serif; - font-size: 1.75rem; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 0.5rem; - padding-right: 10rem; -} - -.modal-meta { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; -} - -.modal-details { - margin-top: 0.75rem; - font-size: 0.875rem; - color: var(--text-secondary); -} - -.modal-details .modal-address { - margin-bottom: 0.25rem; -} - -.modal-compare-btn { - position: absolute; - top: 1rem; - right: 4rem; -} - -.modal-details .modal-age-range { - color: var(--text-muted); -} - -.modal-body { - padding: 2rem; -} - -.modal-chart-container { - margin-bottom: 2rem; -} - -.modal-stats { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.modal-stats-section { - padding: 1rem; - background: var(--bg-secondary); - border-radius: var(--radius-md); -} - -.modal-stats-section h4 { - margin: 0 0 0.75rem 0; - font-size: 0.85rem; - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.modal-stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); - gap: 0.75rem; -} - -.modal-stat { - text-align: center; - padding: 1rem; - background: var(--bg-secondary); - border-radius: var(--radius-md); -} - -.modal-stat-value { - font-size: 1.5rem; - font-weight: 700; - color: var(--text-primary); -} - -.modal-stat-label { - font-size: 0.75rem; - color: var(--text-muted); - text-transform: uppercase; - margin-top: 0.25rem; -} - -.modal-map-container { - margin-top: 2rem; -} - -.modal-map-container h4 { - font-size: 1rem; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 0.75rem; -} - -.modal-map { - height: 200px; - border-radius: var(--radius-md); - overflow: hidden; - cursor: pointer; -} - - -/* Buttons */ -.btn { - padding: 0.75rem 1.5rem; - font-size: 0.9rem; - font-family: inherit; - font-weight: 600; - border: none; - border-radius: var(--radius-md); - cursor: pointer; - transition: var(--transition); -} - -.btn-primary { - background: var(--accent-coral-dark); - color: white; -} - -.btn-primary:hover { - background: var(--accent-coral-darker); - transform: translateY(-1px); -} - -/* Footer */ -.footer { - margin-top: 3rem; - border-top: 1px solid var(--border-color); - color: var(--text-muted); - font-size: 0.85rem; -} - -.footer-content { - max-width: 600px; - margin: 0 auto; - padding: 2rem; -} - -.footer-contact { - margin-bottom: 1.5rem; - text-align: center; -} - -.footer-source { - text-align: center; - padding-top: 1.5rem; - border-top: 1px solid var(--border-color); -} - -.footer a { - color: var(--accent-teal); - text-decoration: none; -} - -.footer a:hover { - text-decoration: underline; -} - -/* Loading State */ -.loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 3rem; - color: var(--text-muted); -} - -.loading-spinner { - width: 40px; - height: 40px; - border: 3px solid var(--border-color); - border-top-color: var(--accent-coral); - border-radius: 50%; - animation: spin 0.8s linear infinite; - margin-bottom: 1rem; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* Empty State */ -.empty-state { - text-align: center; - padding: 4rem 2rem; - color: var(--text-muted); -} - -.empty-state svg { - width: 64px; - height: 64px; - margin-bottom: 1rem; - opacity: 0.5; -} - -/* Skeleton Loading */ -.skeleton { - pointer-events: none; -} - -.school-card.skeleton { - background: var(--bg-card); - padding: 1.5rem; -} - -.skeleton-line { - height: 1rem; - background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--border-color) 50%, var(--bg-secondary) 75%); - background-size: 200% 100%; - animation: shimmer 1.5s infinite; - border-radius: var(--radius-sm); - margin-bottom: 0.75rem; -} - -.skeleton-line.short { - height: 0.75rem; -} - -.ranking-item.skeleton { - background: var(--bg-card); -} - -.skeleton-circle { - width: 40px; - height: 40px; - border-radius: 50%; - background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--border-color) 50%, var(--bg-secondary) 75%); - background-size: 200% 100%; - animation: shimmer 1.5s infinite; -} - -.skeleton-content { - flex: 1; - padding: 0 1rem; -} - -.skeleton-score { - width: 60px; - height: 40px; - background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--border-color) 50%, var(--bg-secondary) 75%); - background-size: 200% 100%; - animation: shimmer 1.5s infinite; - border-radius: var(--radius-sm); -} - -@keyframes shimmer { - 0% { background-position: 200% 0; } - 100% { background-position: -200% 0; } -} - -/* Pagination */ -.pagination-info { - grid-column: 1 / -1; - display: flex; - justify-content: center; - align-items: center; - gap: 1rem; - padding: 2rem; - color: var(--text-muted); - font-size: 0.9rem; -} - -.btn-load-more { - padding: 0.6rem 1.5rem; - font-size: 0.9rem; - font-family: inherit; - font-weight: 500; - border: 2px solid var(--accent-coral); - border-radius: var(--radius-md); - background: transparent; - color: var(--accent-coral); - cursor: pointer; - transition: var(--transition); -} - -.btn-load-more:hover { - background: var(--accent-coral); - color: white; -} - -/* Responsive */ -@media (max-width: 768px) { - .header-content { - flex-direction: column; - gap: 1rem; - } - - .nav { - width: 100%; - justify-content: center; - } - .main { padding: 1rem; } - - .hero-title { - font-size: 1.75rem; - } - - .filter-row { - flex-direction: column; - } - - .location-input-group { - flex-direction: column; - width: 100%; - } - - .postcode-input { - max-width: 100%; - width: 100%; - } - - .radius-select { - width: 100%; - } - - .location-btn, - .location-clear-btn { - width: 100%; - justify-content: center; - } - - .schools-grid { - grid-template-columns: 1fr; - } - - .modal-content { - margin: 1rem; - max-height: calc(100dvh - 2rem); - } - - .modal-header { - display: flex; - flex-direction: column; - } - - .modal-header h2 { - padding-right: 0; - order: 1; - } - - .modal-meta { - order: 2; - } - - .modal-details { - order: 3; - } - - .modal-compare-btn { - position: static; - order: 4; - margin-top: 1rem; - width: 100%; - } - - .modal-body { - padding: 1rem; - } - - .modal-chart-container { - margin-bottom: 1.5rem; - padding: 0.5rem; - background: var(--bg-secondary); - border-radius: var(--radius-md); - } - - .modal-chart-container canvas { - max-height: 280px; - } - - .modal-stats-grid { - grid-template-columns: repeat(2, 1fr); - gap: 0.5rem; - } - - .modal-stat { - padding: 0.75rem 0.5rem; - } - - .modal-stat-value { - font-size: 1.25rem; - } - - .modal-stat-label { - font-size: 0.65rem; - } - - .rankings-controls { - flex-direction: column; - align-items: stretch; - } } -/* Extra small screens */ -@media (max-width: 480px) { - .modal-content { - margin: 0.5rem; - max-height: calc(100dvh - 1rem); +/* Honour the OS setting. Transitions collapse to near-instant rather than + zero so state changes still register as changes. */ +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; } - - .modal-header { - padding: 1rem; - } - - .modal-header h2 { - font-size: 1.25rem; - } - - .modal-chart-container canvas { - max-height: 260px; - } - - .modal-stats-grid { - grid-template-columns: repeat(2, 1fr); - } - - .modal-stat-value { - font-size: 1.1rem; - } - - .modal-map { - height: 160px; + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; } } - -/* ============================================================================= - TOOLTIP SYSTEM - ============================================================================= */ - -/* Info Icon Trigger */ -.info-trigger { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - margin-left: 0.25rem; - background: none; - border: none; - cursor: help; - color: var(--text-muted); - opacity: 0.6; - transition: var(--transition); - vertical-align: middle; - border-radius: 50%; -} - -.info-trigger:hover, -.info-trigger:focus { - color: var(--accent-teal); - opacity: 1; -} - -.info-trigger:focus { - outline: none; - box-shadow: 0 0 0 2px var(--accent-teal); -} - -.info-trigger:focus:not(:focus-visible) { - box-shadow: none; -} - -.info-trigger:focus-visible { - box-shadow: 0 0 0 2px var(--accent-teal); -} - -/* Info Icon SVG */ -.info-icon { - width: 10px; - height: 10px; - flex-shrink: 0; -} - -.modal-stat-label .info-icon { - width: 14px; - height: 14px; -} - -/* Tooltip Container */ -.tooltip { - position: absolute; - z-index: 3000; - min-width: 200px; - max-width: 280px; - padding: 0.75rem 1rem; - background: var(--bg-accent); - color: var(--text-inverse); - border-radius: var(--radius-md); - box-shadow: var(--shadow-medium); - font-family: var(--font-dm-sans), 'DM Sans', sans-serif; - font-size: 0.8125rem; - line-height: 1.5; - text-transform: none; - letter-spacing: normal; - text-align: left; - opacity: 0; - visibility: hidden; - transition: opacity 150ms ease, visibility 150ms ease; - pointer-events: none; -} - -.tooltip.visible { - opacity: 1; - visibility: visible; - pointer-events: auto; -} - -/* Tooltip Arrow - Top Placement (arrow points down) */ -.tooltip[data-placement="top"]::after { - content: ''; - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - border: 8px solid transparent; - border-top-color: var(--bg-accent); -} - -/* Tooltip Arrow - Bottom Placement (arrow points up) */ -.tooltip[data-placement="bottom"]::after { - content: ''; - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - border: 8px solid transparent; - border-bottom-color: var(--bg-accent); -} - -/* Tooltip Title */ -.tooltip-title { - font-weight: 600; - margin-bottom: 0.25rem; - font-size: 0.875rem; -} - -/* Tooltip Note (for context like national average) */ -.tooltip-note { - margin-top: 0.5rem; - padding-top: 0.5rem; - border-top: 1px solid rgba(250, 247, 242, 0.2); - font-size: 0.75rem; - opacity: 0.8; -} - -/* Warning Trigger Button */ -.warning-trigger { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - margin-left: 0.5rem; - background: none; - border: none; - cursor: help; - color: var(--accent-coral); - opacity: 0.8; - transition: var(--transition); - vertical-align: middle; - border-radius: 4px; -} - -.warning-trigger:hover, -.warning-trigger:focus { - opacity: 1; -} - -.warning-trigger:focus { - outline: none; - box-shadow: 0 0 0 2px var(--accent-coral); -} - -.warning-trigger:focus:not(:focus-visible) { - box-shadow: none; -} - -.warning-trigger:focus-visible { - box-shadow: 0 0 0 2px var(--accent-coral); -} - -/* Warning Icon SVG */ -.warning-icon { - width: 14px; - height: 14px; - flex-shrink: 0; -} - -/* Warning Tooltip Styling */ -.tooltip.tooltip-warning { - background: #8b4513; - border-left: 3px solid var(--accent-coral); -} - -.tooltip.tooltip-warning[data-placement="top"]::after { - border-top-color: #8b4513; -} - -.tooltip.tooltip-warning[data-placement="bottom"]::after { - border-bottom-color: #8b4513; -} - -/* Label wrapper for inline icon */ -.stat-label-with-info { - display: inline-flex; - align-items: center; - justify-content: center; -} diff --git a/nextjs-app/app/icon.svg b/nextjs-app/app/icon.svg new file mode 100644 index 0000000..d9ebdf2 --- /dev/null +++ b/nextjs-app/app/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/nextjs-app/app/layout.tsx b/nextjs-app/app/layout.tsx index c72cd69..1d979ec 100644 --- a/nextjs-app/app/layout.tsx +++ b/nextjs-app/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata, Viewport } from 'next'; -import { DM_Sans, Playfair_Display } from 'next/font/google'; +import { Schibsted_Grotesk, Literata } from 'next/font/google'; import Script from 'next/script'; import { Navigation } from '@/components/Navigation'; import { Footer } from '@/components/Footer'; @@ -7,17 +7,23 @@ import { ComparisonToast } from '@/components/ComparisonToast'; import { ComparisonProvider } from '@/context/ComparisonProvider'; import './globals.css'; -const dmSans = DM_Sans({ +// Schibsted Grotesk carries the whole interface — headings, labels, and every +// figure. It was drawn for news brands, so its tabular numerals hold a column +// properly, which matters on a page that is mostly percentages. +const schibsted = Schibsted_Grotesk({ subsets: ['latin'], weight: ['400', '500', '600', '700'], - variable: '--font-dm-sans', + variable: '--font-schibsted', display: 'swap', }); -const playfairDisplay = Playfair_Display({ +// Literata is for prose only — the admissions guide, methodology notes, the +// editorial sections. It never touches a number or a control. +const literata = Literata({ subsets: ['latin'], - weight: ['600', '700'], - variable: '--font-playfair', + weight: ['400', '600'], + style: ['normal', 'italic'], + variable: '--font-literata', display: 'swap', }); @@ -27,9 +33,11 @@ export const viewport: Viewport = { // viewport-fit=cover lets us paint behind the notch / Dynamic Island so // env(safe-area-inset-*) values resolve to real numbers on iPhone. viewportFit: 'cover', + // These must match --bg-primary in globals.css for each theme, or the + // browser chrome and the page disagree at the top edge of the screen. themeColor: [ - { media: '(prefers-color-scheme: light)', color: '#faf7f2' }, - { media: '(prefers-color-scheme: dark)', color: '#1a1612' }, + { media: '(prefers-color-scheme: light)', color: '#f2f1ed' }, + { media: '(prefers-color-scheme: dark)', color: '#10151b' }, ], }; @@ -47,11 +55,10 @@ export const metadata: Metadata = { keywords: 'school comparison, KS2 results, KS4 results, primary school, secondary school, England schools, SATs results, GCSE results', authors: [{ name: 'SchoolCompare' }], manifest: '/manifest.json', - icons: { - icon: '/favicon.svg', - shortcut: '/favicon.svg', - apple: '/favicon.svg', - }, + // No `icons` key on purpose: setting it here would override the file + // conventions. app/icon.svg and app/apple-icon.tsx are the source, and + // app/opengraph-image.tsx supplies og:image and twitter:image. + metadataBase: new URL('https://schoolcompare.co.uk'), openGraph: { type: 'website', title: 'SchoolCompare | Compare School Performance', @@ -60,7 +67,8 @@ export const metadata: Metadata = { siteName: 'SchoolCompare', }, twitter: { - card: 'summary', + // summary_large_image now that there is an image worth showing. + card: 'summary_large_image', title: 'SchoolCompare | Compare School Performance', description: 'Compare primary and secondary school SATs and GCSE performance across England', }, @@ -94,8 +102,7 @@ export default function RootLayout({ strategy="afterInteractive" /> - -
+ Skip to main content diff --git a/nextjs-app/app/opengraph-image.tsx b/nextjs-app/app/opengraph-image.tsx new file mode 100644 index 0000000..f79e1ec --- /dev/null +++ b/nextjs-app/app/opengraph-image.tsx @@ -0,0 +1,132 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { ImageResponse } from 'next/og'; +import { BARS } from '@/components/Logo'; + +/** + * The site had no og:image at all, so every link pasted into a class WhatsApp + * group or a Slack channel rendered as a bare grey text card — on the highest + * intent channel this product has. + * + * The card is the mark at poster scale: the cohort as a wide distribution with + * one school lit up. It states the proposition rather than decorating it. + */ +export const alt = 'SchoolCompare — compare every school in England against the national picture'; +export const size = { width: 1200, height: 630 }; +export const contentType = 'image/png'; + +const PAPER = '#f2f1ed'; +const INK = '#16202a'; +const MUTED = '#5c6570'; +const BRAND = '#584a9b'; + +// A plausible national distribution, drawn once at poster scale. The lit bar +// is the same position the mark uses. +const SPREAD = [8, 12, 17, 24, 33, 44, 57, 70, 82, 92, 98, 100, 96, 88, 77, 65, 53, 41, 31, 22, 15, 10, 7, 5]; +// Right of the peak: score runs left to right, so the lit school reads as +// above the national average. At index 9 it sat on the low side, which quietly +// said the opposite. +const LIT = 14; + +async function font(file: string) { + return readFile(join(process.cwd(), 'assets', file)); +} + +export default async function OpengraphImage() { + const [regular, bold] = await Promise.all([ + font('SchibstedGrotesk-Regular.ttf'), + font('SchibstedGrotesk-Bold.ttf'), + ]); + + return new ImageResponse( + ( +
+ {/* Lockup */} +
+
+ {BARS.map(([x, y, w, h, isSchool], i) => ( +
+ ))} +
+
+ School + Compare +
+
+ + {/* Proposition. Sized so the headline holds two lines and the subhead + one — an orphaned word reads as an accident at this size. */} +
+
+ A score means nothing until you see the spread. +
+
+ 24,000+ schools in England · SATs, GCSEs, Ofsted and admissions +
+
+ + {/* The device, at poster scale. Same object as the mark above. */} +
+
+ {SPREAD.map((h, i) => ( +
+ ))} +
+
+ NATIONAL SPREAD + schoolcompare.co.uk +
+
+
+ ), + { + ...size, + fonts: [ + { name: 'Schibsted', data: regular, weight: 400, style: 'normal' }, + { name: 'Schibsted', data: bold, weight: 700, style: 'normal' }, + ], + } + ); +} diff --git a/nextjs-app/assets/SchibstedGrotesk-Bold.ttf b/nextjs-app/assets/SchibstedGrotesk-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5425be0b61a2508b7e6d13a28c1c1e502e2ef8c7 GIT binary patch literal 101164 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 X>ys+cOR-6B5f^DCo|TbW?qvQSq0SX8 literal 0 HcmV?d00001 diff --git a/nextjs-app/assets/SchibstedGrotesk-Regular.ttf b/nextjs-app/assets/SchibstedGrotesk-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..97ef234607ff17f144d6a5f467fe50c93549ab16 GIT binary patch literal 100692 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 = { responsive: true, maintainAspectRatio: false, @@ -49,7 +55,9 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis legend: { display: false }, title: { display: false }, tooltip: { - backgroundColor: 'rgba(26,22,18,0.92)', + backgroundColor: cInverse, + titleColor: cInverseText, + bodyColor: cInverseText, padding: 10, titleFont: { size: 12 }, bodyFont: { size: 12 }, @@ -62,7 +70,7 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis y: { min: yMin, max: yMax, - grid: { color: 'rgba(0,0,0,0.05)' }, + grid: { color: cGrid }, ticks: { font: { size: 11 }, maxTicksLimit: 5, callback: (v) => `${v}%` }, }, x: { @@ -80,14 +88,14 @@ export default function AdmissionsTrendChart({ history }: { history: SchoolAdmis data: values, clip: false as const, spanGaps: false, - borderColor: '#e07256', - backgroundColor: 'rgba(224,114,86,0.10)', + borderColor: cBrand, + backgroundColor: alpha('--brand', 0.10), borderWidth: 2.5, tension: 0.3, fill: true, pointRadius: values.map((_, i) => (i === lastIdx ? 5 : 3)), - pointBackgroundColor: '#e07256', - pointBorderColor: '#fff', + pointBackgroundColor: cBrand, + pointBorderColor: cCard, pointBorderWidth: values.map((_, i) => (i === lastIdx ? 2 : 0)), pointHoverRadius: 6, }, diff --git a/nextjs-app/components/AdmissionsView.module.css b/nextjs-app/components/AdmissionsView.module.css index 06b457d..f80a0c5 100644 --- a/nextjs-app/components/AdmissionsView.module.css +++ b/nextjs-app/components/AdmissionsView.module.css @@ -27,7 +27,7 @@ font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-bottom: 0.85rem; padding-left: 0.85rem; } @@ -39,7 +39,7 @@ display: flex; flex-direction: column; gap: 0.1rem; - border-left: 2px solid var(--border-color, #e5dfd5); + border-left: 2px solid var(--border); } .navLink { @@ -51,33 +51,33 @@ border-left: 2px solid transparent; font-size: 0.88rem; font-weight: 500; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); text-decoration: none; cursor: pointer; transition: color 0.15s ease, border-color 0.15s ease; } .navLink:hover { - color: var(--text-primary, #1a1612); + color: var(--text-primary); } .navLinkActive { - color: var(--accent-teal, #2d7d7d); + color: var(--brand); font-weight: 700; - border-left-color: var(--accent-teal, #2d7d7d); + border-left-color: var(--brand); } .navDot { width: 6px; height: 6px; border-radius: 50%; - background: var(--border-color, #e5dfd5); + background: var(--border); flex-shrink: 0; transition: background 0.15s ease; } .navLinkActive .navDot { - background: var(--accent-teal, #2d7d7d); + background: var(--brand); } /* ─── Hero ───────────────────────────────────────────── */ @@ -95,8 +95,8 @@ font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; - color: var(--accent-teal, #2d7d7d); - background: rgba(45, 125, 125, 0.1); + color: var(--brand); + background: rgba(var(--brand-rgb), 0.1); padding: 0.3rem 0.7rem; border-radius: 999px; margin-bottom: 1rem; @@ -107,22 +107,22 @@ width: 6px; height: 6px; border-radius: 50%; - background: var(--accent-teal, #2d7d7d); + background: var(--brand); } .heroTitle { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 2.75rem; font-weight: 700; line-height: 1.1; letter-spacing: -0.015em; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.85rem; } .heroSub { font-size: 1.05rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); max-width: 640px; margin: 0 auto; line-height: 1.55; @@ -132,7 +132,7 @@ .countdownSection { padding: 0 0 2.5rem; - border-bottom: 1px solid var(--border-color, #e5dfd5); + border-bottom: 1px solid var(--border); margin-bottom: 2.5rem; } @@ -145,7 +145,7 @@ font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .countdownRail { @@ -155,11 +155,11 @@ } .chip { - background: var(--bg-card, #fff); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 12px; padding: 1rem 1.1rem 0.9rem; - box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); + box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06); display: flex; flex-direction: column; gap: 0.2rem; @@ -177,12 +177,12 @@ border-radius: 12px 12px 0 0; } -.chipDeadline::before { background: var(--accent-coral, #e07256); } -.chipOffer::before { background: var(--accent-teal, #2d7d7d); } +.chipDeadline::before { background: var(--brand); } +.chipOffer::before { background: var(--status-above); } .chipUrgent { - border-color: rgba(224, 114, 86, 0.4); - background: rgba(224, 114, 86, 0.04); + border-color: rgba(var(--brand-rgb), 0.4); + background: rgba(var(--brand-rgb), 0.04); } .chipTrack { @@ -196,8 +196,8 @@ margin-bottom: 0.15rem; } -.chipTrackDeadline { color: var(--accent-coral-dark, #b04a2e); } -.chipTrackOffer { color: var(--accent-teal, #2d7d7d); } +.chipTrackDeadline { color: var(--brand-strong); } +.chipTrackOffer { color: var(--status-above); } .chipTrackDot { width: 5px; @@ -208,7 +208,8 @@ } .chipDays { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-variant-numeric: tabular-nums; + font-family: var(--font-display); font-size: 2.6rem; font-weight: 700; line-height: 1; @@ -217,17 +218,17 @@ .chipDeadline .chipDays, .chipUrgent .chipDays { - color: var(--accent-coral-dark, #c45a3f); + color: var(--brand-strong); } .chipOffer .chipDays { - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); } .chipDaysUnit { font-size: 0.78rem; font-weight: 500; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-left: 0.2rem; vertical-align: bottom; line-height: 2; @@ -236,14 +237,14 @@ .chipMilestone { font-size: 0.85rem; font-weight: 600; - color: var(--text-primary, #1a1612); + color: var(--text-primary); line-height: 1.25; margin-top: 0.1rem; } .chipDate { font-size: 0.75rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-top: 0.05rem; } @@ -251,8 +252,8 @@ .track { margin-bottom: 3rem; - background: var(--bg-card, #fff); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 14px; overflow: hidden; } @@ -263,7 +264,7 @@ justify-content: space-between; gap: 1.5rem; padding: 1.75rem 2rem 1.5rem; - border-bottom: 1px solid var(--border-color, #e5dfd5); + border-bottom: 1px solid var(--border); flex-wrap: wrap; } @@ -278,22 +279,22 @@ font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); margin-bottom: 0.35rem; } .trackTitle { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.4rem; line-height: 1.2; } .trackSub { font-size: 0.9rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); line-height: 1.5; margin: 0; } @@ -317,13 +318,13 @@ font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .trackDateVal { font-size: 0.88rem; font-weight: 600; - color: var(--text-primary, #1a1612); + color: var(--text-primary); } /* ─── Timeline ───────────────────────────────────────── */ @@ -356,27 +357,27 @@ width: 14px; height: 14px; border-radius: 50%; - background: var(--bg-card, #fff); - border: 2px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 2px solid var(--border); flex-shrink: 0; transition: border-color 0.2s ease, background 0.2s ease; } .stepDeadline .stepDot { - background: rgba(224, 114, 86, 0.15); - border-color: var(--accent-coral, #e07256); + background: rgba(var(--brand-rgb), 0.15); + border-color: var(--brand); } .stepOffer .stepDot { - background: rgba(45, 125, 125, 0.15); - border-color: var(--accent-teal, #2d7d7d); + background: rgba(var(--status-above-rgb), 0.15); + border-color: var(--status-above); } .stepLine { width: 2px; flex: 1; min-height: 24px; - background: var(--border-color, #e5dfd5); + background: var(--border); margin: 4px 0; } @@ -395,27 +396,28 @@ font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-bottom: 0.2rem; } -.stepDeadline .stepDate { color: var(--accent-coral-dark, #b04a2e); } -.stepOffer .stepDate { color: var(--accent-teal, #2d7d7d); } +.stepDeadline .stepDate { color: var(--brand-strong); } +.stepOffer .stepDate { color: var(--status-above); } .stepTitle { font-size: 0.95rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.3rem; line-height: 1.3; } -.stepDeadline .stepTitle { color: var(--accent-coral-dark, #c45a3f); } -.stepOffer .stepTitle { color: var(--accent-teal, #2d7d7d); } +.stepDeadline .stepTitle { color: var(--brand-strong); } +.stepOffer .stepTitle { color: var(--status-above); } .stepBody { + font-family: var(--font-prose); font-size: 0.88rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); line-height: 1.55; margin: 0; } @@ -427,13 +429,13 @@ margin-top: 0.6rem; font-size: 0.82rem; font-weight: 600; - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); text-decoration: none; transition: gap 0.2s ease, color 0.2s ease; } .stepTool:hover { - color: var(--accent-coral-dark, #c45a3f); + color: var(--brand-strong); gap: 0.6rem; } @@ -455,9 +457,9 @@ display: flex; gap: 0.55rem; padding: 0.6rem 0.75rem; - background: var(--accent-gold-bg, rgba(201, 162, 39, 0.12)); - border: 1px solid rgba(201, 162, 39, 0.28); - border-left: 3px solid var(--accent-gold, #c9a227); + background: var(--status-below-bg); + border: 1px solid rgba(var(--status-below-rgb), 0.28); + border-left: 3px solid var(--status-below); border-radius: 8px; } @@ -467,8 +469,8 @@ height: 1.05rem; margin-top: 0.1rem; border-radius: 50%; - background: var(--accent-gold, #c9a227); - color: #fff; + background: var(--status-below); + color: var(--text-inverse); font-size: 0.72rem; font-weight: 800; line-height: 1.05rem; @@ -482,14 +484,15 @@ .calloutTitle { font-size: 0.82rem; font-weight: 700; - color: var(--accent-gold-text, #7a6800); + color: var(--status-below); line-height: 1.3; margin-bottom: 0.15rem; } .calloutBody { + font-family: var(--font-prose); font-size: 0.82rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); line-height: 1.5; margin: 0; } @@ -501,10 +504,10 @@ } .tipsHeading { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 1.35rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 1.25rem; } @@ -515,16 +518,17 @@ } .tipCard { - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); border-radius: 12px; padding: 1.25rem; } .tipNumber { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-variant-numeric: tabular-nums; + font-family: var(--font-display); font-size: 1.75rem; font-weight: 700; - color: #7d766b; /* 3.8:1 on cream — AA large-text for this 28px numeral */ + color: var(--text-muted); /* 3.8:1 on cream — AA large-text for this 28px numeral */ line-height: 1; margin-bottom: 0.6rem; } @@ -532,14 +536,15 @@ .tipHeading { font-size: 0.95rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.4rem; line-height: 1.3; } .tipBody { + font-family: var(--font-prose); font-size: 0.875rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); line-height: 1.55; margin: 0; } @@ -557,8 +562,8 @@ position: sticky; top: 0; padding-top: 0; - background: var(--bg-primary, #faf8f3); - border-bottom: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-primary); + border-bottom: 1px solid var(--border); margin: 0 -1.25rem 1rem; padding: 0.6rem 1.25rem; z-index: 10; @@ -590,8 +595,8 @@ } .navLinkActive { - background: var(--text-primary, #1a1612); - color: #fff; + background: var(--text-primary); + color: var(--text-inverse); border-left: none; } diff --git a/nextjs-app/components/ComparisonChart.module.css b/nextjs-app/components/ComparisonChart.module.css index 43acaf0..3fec9a1 100644 --- a/nextjs-app/components/ComparisonChart.module.css +++ b/nextjs-app/components/ComparisonChart.module.css @@ -43,7 +43,7 @@ min-height: 40px; min-width: 0; padding: 4px 10px; - border: 1px solid rgba(0, 0, 0, .12); + border: 1px solid var(--border); border-radius: 999px; background: transparent; cursor: pointer; @@ -52,8 +52,8 @@ } .chip[aria-pressed="true"] { - background: rgba(0, 0, 0, .06); - border-color: rgba(0, 0, 0, .35); + background: var(--bg-secondary); + border-color: var(--border-strong); } .chipDot { diff --git a/nextjs-app/components/ComparisonChart.tsx b/nextjs-app/components/ComparisonChart.tsx index 23a96f5..cd8af93 100644 --- a/nextjs-app/components/ComparisonChart.tsx +++ b/nextjs-app/components/ComparisonChart.tsx @@ -15,6 +15,7 @@ import { useEffect, useState } from 'react'; import { Line } from 'react-chartjs-2'; import { ChartOptions, ChartDataset, PointStyle } from 'chart.js'; import '@/lib/chartSetup'; +import { useSeriesColors, useThemeTokens } from '@/lib/theme'; import { buildCompareChart } from '@/lib/compareChartData'; import type { ComparisonData } from '@/lib/types'; import { @@ -48,6 +49,13 @@ const POINT_STYLES: PointStyle[] = ['circle', 'triangle', 'rect', 'rectRot', 'st export function ComparisonChart({ comparisonData, schools, metric, metricLabel, nationalByYear, isSecondary = false }: ComparisonChartProps) { const isMobile = useIsMobile(); + // Canvas can't resolve var(), so the series come through the token bridge. + // Same index order as CHART_COLORS, so a school's line and its DOM swatch + // stay the same colour. + const seriesColors = useSeriesColors(); + const [cRef, cGrid, cInverse, cInverseText] = useThemeTokens( + '--chart-reference', '--chart-grid', '--surface-inverse', '--text-inverse' + ); const [focusedUrn, setFocusedUrn] = useState(null); // A focused school that leaves the basket must not linger. @@ -67,7 +75,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel, const datasets: ChartDataset<'line'>[] = built.schoolDatasets.map((series) => { const school = schools[series.schoolIndex]; - const color = CHART_COLORS[series.schoolIndex % CHART_COLORS.length]; + const color = seriesColors[series.schoolIndex % seriesColors.length]; const dimmed = focusedUrn !== null && focusedUrn !== school.urn; return { @@ -90,7 +98,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel, datasets.push({ label: built.englandDataset.label, data: built.englandDataset.data, - borderColor: 'rgba(109, 104, 95, 0.9)', + borderColor: cRef, backgroundColor: 'transparent', borderWidth: 1.5, borderDash: built.englandDataset.borderDash, @@ -138,7 +146,9 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel, display: false, }, tooltip: { - backgroundColor: 'rgba(0, 0, 0, 0.8)', + backgroundColor: cInverse, + titleColor: cInverseText, + bodyColor: cInverseText, padding: isMobile ? 10 : 12, titleFont: { size: isMobile ? 12 : 14, @@ -182,7 +192,7 @@ export function ComparisonChart({ comparisonData, schools, metric, metricLabel, ...(isMobile && { maxTicksLimit: 5 }), }, grid: { - color: 'rgba(0, 0, 0, 0.05)', + color: cGrid, }, }, x: { diff --git a/nextjs-app/components/ComparisonToast.module.css b/nextjs-app/components/ComparisonToast.module.css index fea8a4f..cbdf970 100644 --- a/nextjs-app/components/ComparisonToast.module.css +++ b/nextjs-app/components/ComparisonToast.module.css @@ -23,11 +23,11 @@ flex-direction: column; gap: 0; padding: 1rem 1.25rem; - background: var(--bg-primary, #faf7f2); - color: var(--text-primary, #2c2420); + background: var(--bg-primary); + color: var(--text-primary); border-radius: 16px; - box-shadow: 0 8px 32px rgba(44, 36, 32, 0.18), 0 2px 8px rgba(44, 36, 32, 0.08); - border: 1px solid var(--border-color, #e8ddd4); + box-shadow: 0 8px 32px rgba(var(--shadow-rgb), 0.18), 0 2px 8px rgba(var(--shadow-rgb), 0.08); + border: 1px solid var(--border); min-width: 280px; } @@ -37,8 +37,8 @@ justify-content: center; width: 28px; height: 28px; - background: var(--accent-coral, #e07256); - color: white; + background: var(--brand); + color: var(--brand-on); border-radius: 50%; font-weight: 700; font-size: 0.9rem; @@ -62,13 +62,13 @@ gap: 0.5rem; font-weight: 600; font-size: 0.875rem; - color: var(--text-primary, #2c2420); + color: var(--text-primary); } .collapseBtn { background: none; border: none; - color: var(--text-muted, #8a7a72); + color: var(--text-muted); cursor: pointer; padding: 0.25rem; line-height: 1; @@ -79,7 +79,7 @@ } .collapseBtn:hover { - color: var(--text-primary, #2c2420); + color: var(--text-primary); } .schoolList { @@ -95,13 +95,13 @@ justify-content: space-between; gap: 0.5rem; padding: 0.3rem 0.5rem; - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); border-radius: var(--radius-sm, 4px); } .schoolName { font-size: 0.8rem; - color: var(--text-primary, #2c2420); + color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -111,7 +111,7 @@ .removeSchoolBtn { background: none; border: none; - color: var(--text-muted, #8a7a72); + color: var(--text-muted); cursor: pointer; font-size: 1rem; padding: 0 0.25rem; @@ -121,7 +121,7 @@ } .removeSchoolBtn:hover { - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } .toastActions { @@ -129,13 +129,13 @@ align-items: center; gap: 0.75rem; padding-top: 0.625rem; - border-top: 1px solid var(--border-color, #e8ddd4); + border-top: 1px solid var(--border); } .btnClearAll { background: none; border: none; - color: var(--text-muted, #8a7a72); + color: var(--text-muted); font-size: 0.85rem; font-weight: 500; cursor: pointer; @@ -145,13 +145,13 @@ } .btnClearAll:hover { - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } .btnCompare { flex: 1; - background: var(--accent-coral, #e07256); - color: white; + background: var(--brand); + color: var(--brand-on); padding: 0.6rem 1.25rem; border-radius: 25px; font-weight: 600; @@ -164,7 +164,7 @@ .btnCompare:hover { transform: translateY(-1px); - background: var(--accent-coral-dark, #c9614a); + background: var(--brand-strong); } /* Hidden on phones — the bottom tab bar's Compare badge already diff --git a/nextjs-app/components/ComparisonView.module.css b/nextjs-app/components/ComparisonView.module.css index 932b132..dfd5611 100644 --- a/nextjs-app/components/ComparisonView.module.css +++ b/nextjs-app/components/ComparisonView.module.css @@ -18,14 +18,14 @@ .header h1 { font-size: 2.25rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.5rem; - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); } .subtitle { font-size: 1rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); margin: 0; line-height: 1.6; max-width: 60ch; @@ -43,7 +43,7 @@ display: flex; gap: 0; margin-bottom: 1.5rem; - border: 1px solid var(--border-color, #e5dfd5); + border: 1px solid var(--border); border-radius: 8px; overflow: hidden; width: fit-content; @@ -53,8 +53,8 @@ padding: 0.625rem 1.5rem; font-size: 0.9375rem; font-weight: 500; - background: var(--bg-card, white); - color: var(--text-secondary, #5c564d); + background: var(--bg-card); + color: var(--text-secondary); border: none; cursor: pointer; transition: all 0.15s ease; @@ -62,21 +62,21 @@ } .phaseTab:not(:last-child) { - border-right: 1px solid var(--border-color, #e5dfd5); + border-right: 1px solid var(--border); } .phaseTab:hover { - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); } .phaseTabActive { - background: var(--accent-coral-dark, #b04a2e); - color: white; + background: var(--brand-strong); + color: var(--brand-on); font-weight: 600; } .phaseTabActive:hover { - background: var(--accent-coral-darker, #9c3f26); + background: var(--brand-stronger); } /* Sticky school bar — column identity while scrolling; horizontal scroll on @@ -87,23 +87,23 @@ position: sticky; top: 65px; z-index: 10; - background: var(--bg-primary, #faf7f2); + background: var(--bg-primary); display: flex; gap: 0.75rem; overflow-x: auto; padding: 0.75rem 0; - border-bottom: 1px solid var(--border-light, #e5dfd5); + border-bottom: 1px solid var(--border); -webkit-overflow-scrolling: touch; } .schoolChip { flex: 1 1 0; min-width: 180px; - background: var(--bg-card, white); - border: 1px solid var(--border-light, #e5dfd5); - border-top: 3px solid var(--accent-coral, #e07256); + background: var(--bg-card); + border: 1px solid var(--border); + border-top: 3px solid var(--brand); border-radius: 8px; - box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); + box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06)); padding: 0.55rem 0.75rem; display: flex; gap: 0.55rem; @@ -126,12 +126,12 @@ font-weight: 600; font-size: 0.92rem; line-height: 1.25; - color: var(--text-primary, #1a1612); + color: var(--text-primary); text-decoration: none; } .chipName:hover { - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } /* Full name on desktop, short name on the compact mobile pills. */ @@ -142,7 +142,7 @@ .chipMeta { display: block; font-size: 0.78rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -185,22 +185,22 @@ font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .barCaptionCount { font-size: 0.95rem; font-weight: 600; line-height: 1.3; - color: var(--text-primary, #1a1612); + color: var(--text-primary); } } .chipRemove { margin-left: auto; border: none; - background: var(--bg-secondary, #f3ede4); - color: var(--text-muted, #6d685f); + background: var(--bg-secondary); + color: var(--text-muted); border-radius: 50%; width: 22px; height: 22px; @@ -212,9 +212,9 @@ .footnote { font-size: 0.78rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-top: 2.5rem; - border-top: 1px solid var(--border-light, #e5dfd5); + border-top: 1px solid var(--border); padding-top: 1rem; max-width: 75ch; } diff --git a/nextjs-app/components/DeltaChip.module.css b/nextjs-app/components/DeltaChip.module.css index 01ca30b..974270e 100644 --- a/nextjs-app/components/DeltaChip.module.css +++ b/nextjs-app/components/DeltaChip.module.css @@ -23,16 +23,16 @@ } .good { - background: var(--accent-teal-bg, rgba(45, 125, 125, 0.12)); - color: var(--accent-teal, #2d7d7d); + background: var(--status-above-bg); + color: var(--status-above); } .bad { - background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12)); - color: var(--accent-coral-dark, #b04a2e); + background: var(--brand-bg); + color: var(--brand-strong); } .neutral { - background: var(--bg-secondary, #f3ede4); - color: var(--text-muted, #8a847a); + background: var(--bg-secondary); + color: var(--text-muted); } diff --git a/nextjs-app/components/DotStrip.module.css b/nextjs-app/components/DotStrip.module.css index 2b7f380..0742d3e 100644 --- a/nextjs-app/components/DotStrip.module.css +++ b/nextjs-app/components/DotStrip.module.css @@ -61,7 +61,7 @@ border-radius: 50%; transform: translateX(-50%); border: 2px solid var(--bg-card); - box-shadow: 0 0 0 1px rgba(26, 22, 18, 0.08); + box-shadow: 0 0 0 1px rgba(var(--shadow-rgb), 0.08); } .pointLabel { diff --git a/nextjs-app/components/EmptyState.module.css b/nextjs-app/components/EmptyState.module.css index c2cfa38..28f64b6 100644 --- a/nextjs-app/components/EmptyState.module.css +++ b/nextjs-app/components/EmptyState.module.css @@ -5,15 +5,15 @@ justify-content: center; padding: 4rem 2rem; text-align: center; - background: var(--bg-card, white); - border: 2px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 2px solid var(--border); border-radius: 16px; min-height: 400px; - box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); + box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06)); } .icon { - color: var(--text-muted, #8a847a); + color: var(--text-muted); margin-bottom: 1.5rem; opacity: 0.7; } @@ -22,14 +22,14 @@ margin: 0 0 0.75rem 0; font-size: 1.5rem; font-weight: 700; - color: var(--text-primary, #1a1612); - font-family: var(--font-playfair), 'Playfair Display', serif; + color: var(--text-primary); + font-family: var(--font-display); } .message { margin: 0 0 2rem 0; font-size: 1rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); max-width: 500px; line-height: 1.6; } @@ -38,8 +38,8 @@ padding: 0.875rem 2rem; font-size: 1rem; font-weight: 600; - background: var(--accent-coral, #e07256); - color: white; + background: var(--brand); + color: var(--brand-on); border: none; border-radius: 8px; cursor: pointer; @@ -47,9 +47,9 @@ } .button:hover { - background: var(--accent-coral-dark, #c45a3f); + background: var(--brand-strong); transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(224, 114, 86, 0.3); + box-shadow: 0 4px 12px rgba(var(--brand-rgb), 0.3); } .button:active { diff --git a/nextjs-app/components/FilterBar.module.css b/nextjs-app/components/FilterBar.module.css index ac74702..50772b4 100644 --- a/nextjs-app/components/FilterBar.module.css +++ b/nextjs-app/components/FilterBar.module.css @@ -1,10 +1,10 @@ .filterBar { - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 10px; padding: 1.5rem; margin-bottom: 2rem; - box-shadow: var(--shadow-soft, 0 2px 8px rgba(26, 22, 18, 0.06)); + box-shadow: var(--shadow-soft, 0 2px 8px rgba(var(--shadow-rgb), 0.06)); transition: opacity 0.2s ease; } @@ -17,9 +17,9 @@ padding: 2.5rem; max-width: 800px; margin: 0 auto 3rem auto; - box-shadow: 0 8px 24px rgba(26, 22, 18, 0.08); + box-shadow: 0 8px 24px rgba(var(--shadow-rgb), 0.08); border-width: 2px; - border-color: var(--accent-coral, #e07256); + border-color: var(--brand); } .heroMode .omniInput { @@ -39,12 +39,12 @@ .searchHint { margin: 0.875rem 0 0; font-size: 0.95rem; - color: var(--text-secondary, #5a554d); + color: var(--text-secondary); text-align: center; } .searchHint strong { - color: var(--text-primary, #1a1612); + color: var(--text-primary); font-weight: 600; } @@ -68,8 +68,8 @@ align-items: center; gap: 0.5rem; padding: 0.625rem 1.375rem; - background: var(--accent-teal, #2d7d7d); - color: #fff; + background: var(--brand); + color: var(--brand-on); border: none; border-radius: 999px; font-size: 0.9375rem; @@ -80,7 +80,7 @@ } .nearMeBtn:hover:not(:disabled) { - background: #235f5f; + background: var(--status-above); transform: translateY(-1px); } @@ -93,8 +93,8 @@ display: inline-block; width: 14px; height: 14px; - border: 2px solid rgba(255, 255, 255, 0.35); - border-top-color: #fff; + border: 2px solid rgba(var(--text-inverse-rgb), 0.35); + border-top-color: var(--brand-on); border-radius: 50%; animation: nearMeSpin 0.7s linear infinite; flex-shrink: 0; @@ -108,7 +108,7 @@ .geoError { font-size: 0.8125rem; - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); margin: 0; max-width: 340px; text-align: center; @@ -134,21 +134,21 @@ flex: 1; padding: 0.875rem 1.25rem; font-size: 1.05rem; - border: 2px solid var(--border-color, #e5dfd5); + border: 2px solid var(--border); border-radius: 8px; outline: none; transition: all 0.2s ease; - background: var(--bg-card, white); + background: var(--bg-card); font-family: inherit; } .omniInput:focus { - border-color: var(--accent-coral, #e07256); - box-shadow: 0 0 0 3px var(--accent-coral-bg); + border-color: var(--brand); + box-shadow: 0 0 0 3px var(--brand-bg); } .omniInput::placeholder { - color: var(--text-muted, #8a847a); + color: var(--text-muted); } .searchButton { @@ -161,9 +161,9 @@ .spinner { width: 20px; height: 20px; - border: 2px solid rgba(255, 255, 255, 0.3); + border: 2px solid rgba(var(--text-inverse-rgb), 0.3); border-radius: 50%; - border-top-color: white; + border-top-color: var(--brand-on); animation: spin 0.8s linear infinite; } @@ -177,7 +177,7 @@ flex-wrap: wrap; margin-top: 0.625rem; padding-top: 0.625rem; - border-top: 1px solid var(--border-color, #e5dfd5); + border-top: 1px solid var(--border); } .filterSelect { @@ -189,26 +189,26 @@ font-size: 0.875rem; font-weight: 500; font-family: inherit; - border: 1.5px solid var(--border-color, #e5dfd5); + border: 1.5px solid var(--border); border-radius: 8px; - background-color: var(--bg-card, white); + background-color: var(--bg-card); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238a847a' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.875rem center; background-size: 10px 6px; cursor: pointer; outline: none; - color: var(--text-primary, #1a1612); + color: var(--text-primary); transition: border-color 0.15s ease, box-shadow 0.15s ease; } .filterSelect:hover { - border-color: var(--text-muted, #8a847a); + border-color: var(--text-muted); } .filterSelect:focus { - border-color: var(--accent-coral, #e07256); - box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.12); + border-color: var(--brand); + box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12); } .clearButton { @@ -263,7 +263,7 @@ .radiusLabel { font-size: 0.8125rem; font-weight: 500; - color: var(--text-secondary, #5a554d); + color: var(--text-secondary); white-space: nowrap; } @@ -275,7 +275,7 @@ flex-wrap: wrap; margin-top: 0.875rem; padding-top: 0.875rem; - border-top: 1px solid var(--border-color, #e5dfd5); + border-top: 1px solid var(--border); } .controlsRow .advancedToggle { @@ -297,14 +297,14 @@ font-size: 0.8125rem; font-weight: 500; font-family: inherit; - border: 1.5px solid var(--border-color, #e5dfd5); + border: 1.5px solid var(--border); border-radius: 999px; - background-color: var(--bg-card, white); + background-color: var(--bg-card); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238a847a' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 0.65rem center; background-size: 10px 6px; - color: var(--text-primary, #1a1612); + color: var(--text-primary); cursor: pointer; outline: none; transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; @@ -312,13 +312,13 @@ } .controlSelect:hover { - border-color: var(--text-muted, #8a847a); - background-color: var(--bg-secondary, #f8f4ef); + border-color: var(--text-muted); + background-color: var(--bg-secondary); } .controlSelect:focus { - border-color: var(--accent-coral, #e07256); - box-shadow: 0 0 0 3px rgba(224, 114, 86, 0.12); + border-color: var(--brand); + box-shadow: 0 0 0 3px rgba(var(--brand-rgb), 0.12); } /* ── Advanced filters toggle ─────────────────────────── */ @@ -328,12 +328,12 @@ align-items: center; gap: 0.375rem; background: none; - border: 1.5px solid var(--border-color, #e5dfd5); + border: 1.5px solid var(--border); border-radius: 999px; padding: 0.4rem 0.875rem; font-size: 0.8125rem; font-weight: 500; - color: var(--text-secondary, #5a554d); + color: var(--text-secondary); cursor: pointer; font-family: inherit; transition: all 0.15s ease; @@ -341,24 +341,24 @@ } .advancedToggle:hover { - border-color: var(--text-muted, #8a847a); - background-color: var(--bg-secondary, #f8f4ef); - color: var(--text-primary, #1a1612); + border-color: var(--text-muted); + background-color: var(--bg-secondary); + color: var(--text-primary); } /* When filters are applied, promote the toggle to a coral pill so users can see at a glance that the result list is being narrowed. */ .advancedToggleActive { - border-color: var(--accent-coral, #e07256); - background: var(--accent-coral-bg, rgba(224, 114, 86, 0.12)); - color: var(--accent-coral, #e07256); + border-color: var(--brand); + background: var(--brand-bg); + color: var(--brand); font-weight: 600; } .advancedToggleActive:hover { - border-color: var(--accent-coral-dark, #c45a3f); - background: var(--accent-coral-bg, rgba(224, 114, 86, 0.18)); - color: var(--accent-coral-dark, #c45a3f); + border-color: var(--brand-strong); + background: var(--brand-bg); + color: var(--brand-strong); } .chevronDown, diff --git a/nextjs-app/components/Footer.module.css b/nextjs-app/components/Footer.module.css index 2843146..8380a98 100644 --- a/nextjs-app/components/Footer.module.css +++ b/nextjs-app/components/Footer.module.css @@ -1,6 +1,6 @@ .footer { - background: var(--accent-navy, #2c3e50); - color: var(--bg-secondary, #f3ede4); + background: var(--text-primary); + color: var(--bg-secondary); margin-top: auto; } @@ -27,22 +27,22 @@ margin: 0; font-size: 1.25rem; font-weight: 700; - color: var(--bg-primary, #faf7f2); - font-family: var(--font-playfair), 'Playfair Display', serif; + color: var(--bg-primary); + font-family: var(--font-display); } .description { margin: 0; font-size: 0.875rem; line-height: 1.6; - color: rgba(250, 247, 242, 0.7); + color: rgba(var(--text-inverse-rgb), 0.7); } .sectionTitle { margin: 0; font-size: 0.875rem; font-weight: 600; - color: var(--accent-gold, #c9a227); + color: var(--status-below); text-transform: uppercase; letter-spacing: 0.05em; } @@ -58,24 +58,24 @@ .link { font-size: 0.875rem; - color: rgba(250, 247, 242, 0.7); + color: rgba(var(--text-inverse-rgb), 0.7); text-decoration: none; transition: color 0.2s ease; } .link:hover { - color: var(--accent-gold, #c9a227); + color: var(--status-below); } .linkDisabled { font-size: 0.875rem; - color: rgba(250, 247, 242, 0.4); + color: rgba(var(--text-inverse-rgb), 0.4); cursor: not-allowed; } .bottom { padding-top: 2rem; - border-top: 1px solid rgba(250, 247, 242, 0.15); + border-top: 1px solid rgba(var(--text-inverse-rgb), 0.15); display: flex; flex-direction: column; gap: 0.5rem; @@ -85,15 +85,15 @@ .disclaimer { margin: 0; font-size: 0.875rem; - color: rgba(250, 247, 242, 0.6); + color: rgba(var(--text-inverse-rgb), 0.6); } .disclaimer .link { - color: var(--accent-coral, #e07256); + color: var(--brand); } .disclaimer .link:hover { - color: var(--accent-gold, #c9a227); + color: var(--status-below); } @media (max-width: 768px) { diff --git a/nextjs-app/components/HomeView.module.css b/nextjs-app/components/HomeView.module.css index cbe4fc0..fffce2b 100644 --- a/nextjs-app/components/HomeView.module.css +++ b/nextjs-app/components/HomeView.module.css @@ -16,8 +16,8 @@ font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; - color: var(--accent-teal, #2d7d7d); - background: rgba(45, 125, 125, 0.1); + color: var(--brand); + background: rgba(var(--brand-rgb), 0.1); padding: 0.3rem 0.7rem; border-radius: 999px; margin-bottom: 1rem; @@ -28,37 +28,37 @@ width: 6px; height: 6px; border-radius: 50%; - background: var(--accent-teal, #2d7d7d); + background: var(--brand); } .heroTitle { font-size: 3rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.85rem; line-height: 1.08; letter-spacing: -0.015em; - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); max-width: 840px; margin-left: auto; margin-right: auto; } .heroEmph { - color: var(--accent-coral-dark, #c45a3f); + color: var(--brand-strong); font-style: italic; } .heroDescription { font-size: 1.05rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); margin: 0 auto 0.5rem; max-width: 680px; line-height: 1.55; } .heroDescription strong { - color: var(--text-primary, #1a1612); + color: var(--text-primary); font-weight: 700; } @@ -111,7 +111,7 @@ .viewToggle { display: flex; gap: 0.2rem; - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); padding: 0.2rem; border-radius: 6px; } @@ -127,18 +127,18 @@ border: none; border-radius: 5px; cursor: pointer; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); transition: all 0.2s ease; } .viewToggleBtn:hover { - color: var(--text-primary, #1a1612); + color: var(--text-primary); } .viewToggleBtn.active { - background: var(--bg-card, white); - color: var(--accent-coral-dark, #b04a2e); - box-shadow: 0 2px 4px rgba(26, 22, 18, 0.08); + background: var(--bg-card); + color: var(--brand-strong); + box-shadow: 0 2px 4px rgba(var(--shadow-rgb), 0.08); } .viewToggleBtn svg { @@ -166,7 +166,7 @@ .mapContainer { border-radius: 10px; overflow: hidden; - border: 1px solid var(--border-color, #e5dfd5); + border: 1px solid var(--border); height: 100%; } @@ -184,17 +184,17 @@ } .compactList::-webkit-scrollbar-track { - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); border-radius: 3px; } .compactList::-webkit-scrollbar-thumb { - background: var(--border-color, #e5dfd5); + background: var(--border); border-radius: 3px; } .compactList::-webkit-scrollbar-thumb:hover { - background: var(--text-muted, #8a847a); + background: var(--text-muted); } /* Compact School Item */ @@ -204,15 +204,15 @@ align-items: center; gap: 0.75rem; padding: 0.625rem 0.75rem; - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 8px; transition: all 0.2s ease; } .compactItem:hover { - border-color: var(--accent-coral, #e07256); - box-shadow: 0 2px 6px rgba(26, 22, 18, 0.05); + border-color: var(--brand); + box-shadow: 0 2px 6px rgba(var(--shadow-rgb), 0.05); } .compactItemContent { @@ -230,7 +230,7 @@ .compactItemName { font-weight: 600; font-size: 0.8125rem; - color: var(--text-primary, #1a1612); + color: var(--text-primary); text-decoration: none; white-space: nowrap; overflow: hidden; @@ -238,7 +238,7 @@ } .compactItemName:hover { - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } .distanceBadge { @@ -246,8 +246,8 @@ padding: 0.0625rem 0.375rem; font-size: 0.6875rem; font-weight: 600; - background: var(--accent-teal, #2d7d7d); - color: white; + background: var(--brand); + color: var(--brand-on); border-radius: 3px; } @@ -255,25 +255,25 @@ display: flex; gap: 0.375rem; font-size: 0.6875rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); margin-bottom: 0.25rem; } .compactItemMeta span:not(:last-child)::after { content: '·'; margin-left: 0.375rem; - color: var(--text-muted, #8a847a); + color: var(--text-muted); } .compactItemStats { display: flex; gap: 0.75rem; font-size: 0.6875rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); } .compactStat strong { - color: var(--text-primary, #1a1612); + color: var(--text-primary); } .compactItemActions { @@ -292,8 +292,8 @@ font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; - color: var(--text-primary, #1a1612); - font-family: var(--font-playfair), 'Playfair Display', serif; + color: var(--text-primary); + font-family: var(--font-display); } /* Decorative coral bar under section headings */ @@ -302,14 +302,14 @@ display: block; width: 50px; height: 2px; - background: var(--accent-coral, #e07256); + background: var(--brand); border-radius: 1px; margin-top: 0.5rem; } .sectionDescription { font-size: 0.9375rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); margin: 0; line-height: 1.5; } @@ -336,22 +336,22 @@ .emptyState { text-align: center; padding: 2.5rem 1.5rem; - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 10px; } .emptyStateTitle { font-size: 1.25rem; font-weight: 600; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin-bottom: 0.375rem; - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); } .emptyStateDescription { font-size: 0.9375rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); max-width: 380px; margin: 0 auto; } @@ -419,9 +419,9 @@ /* Highlighted List Item */ .highlightedItem .compactItem { - border-color: var(--accent-teal, #2d7d7d); - box-shadow: 0 0 0 1px var(--accent-teal, #2d7d7d); - background: var(--bg-secondary, #f3ede4); + border-color: var(--brand); + box-shadow: 0 0 0 1px var(--brand); + background: var(--bg-secondary); } /* Mobile Bottom Sheet */ @@ -443,9 +443,9 @@ .bottomSheet { position: relative; - background: var(--bg-card, white); + background: var(--bg-card); border-radius: 12px; - box-shadow: 0 -4px 24px rgba(26, 22, 18, 0.15); + box-shadow: 0 -4px 24px rgba(var(--shadow-rgb), 0.15); pointer-events: auto; animation: slideUpSheet 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; } @@ -467,16 +467,16 @@ right: -12px; width: 30px; height: 30px; - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); cursor: pointer; - box-shadow: 0 2px 8px rgba(26, 22, 18, 0.1); + box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.1); z-index: 10; } @@ -522,7 +522,7 @@ .quickSearchChip { padding: 0.375rem 0.875rem; background: var(--bg-card); - border: 1px solid var(--border-color, #e0ddd8); + border: 1px solid var(--border); border-radius: 999px; font-size: 0.875rem; color: var(--text-secondary); @@ -531,9 +531,9 @@ } .quickSearchChip:hover { - background: var(--accent-coral-dark); - color: white; - border-color: var(--accent-coral-dark); + background: var(--brand-strong); + color: var(--brand-on); + border-color: var(--brand-strong); } .exploringRow { @@ -546,7 +546,7 @@ .exploringLabel { font-size: 0.72rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; @@ -564,19 +564,19 @@ align-items: center; gap: 0.4rem; padding: 0.5rem 0.95rem; - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 999px; font-size: 0.82rem; font-weight: 500; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); text-decoration: none; transition: all 0.15s ease; } .exploringChip:hover { - border-color: var(--accent-coral, #e07256); - color: var(--accent-coral-dark, #c45a3f); + border-color: var(--brand); + color: var(--brand-strong); transform: translateY(-1px); } @@ -606,16 +606,16 @@ } .hiwHeading { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 1.75rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin: 0; } .hiwSub { font-size: 0.875rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .hiwGrid { @@ -625,18 +625,18 @@ } .hiwCard { - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 14px; padding: 1.25rem; - box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); + box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06); display: flex; flex-direction: column; gap: 0.9rem; } .hiwVisual { - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); border-radius: 10px; padding: 0.9rem; min-height: 180px; @@ -657,11 +657,11 @@ font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .hiwPhaseLabel strong { - color: var(--accent-teal, #2d7d7d); + color: var(--brand); font-weight: 700; } @@ -676,20 +676,20 @@ font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } .hiwTitle { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 1.1rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); line-height: 1.25; } .hiwDesc { font-size: 0.86rem; - color: var(--text-secondary, #5c564d); + color: var(--text-secondary); line-height: 1.45; margin: 0; } @@ -713,7 +713,7 @@ font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-bottom: 0.1rem; } @@ -723,16 +723,16 @@ align-items: baseline; gap: 0.2rem; font-size: 0.44rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.01em; white-space: nowrap; } .miniRowHead strong { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 0.62rem; - color: var(--text-primary, #1a1612); + color: var(--text-primary); font-weight: 700; letter-spacing: 0; text-transform: none; @@ -741,7 +741,7 @@ .miniTrack { height: 5px; border-radius: 2px; - background: rgba(45, 125, 125, 0.08); + background: rgba(var(--status-above-rgb), 0.08); position: relative; overflow: visible; } @@ -749,21 +749,21 @@ .miniBarExp { height: 100%; border-radius: 2px; - background: var(--accent-teal-light, #3a9e9e); + background: var(--brand); } .miniBarExc { height: 100%; border-radius: 2px; - background: var(--accent-teal, #2d7d7d); + background: var(--brand); } .miniNatPill { position: absolute; top: -9px; transform: translateX(-50%); - background: var(--accent-coral, #e07256); - color: #fff; + background: var(--brand); + color: var(--text-inverse); font-size: 0.4rem; font-weight: 700; padding: 0.05rem 0.2rem; @@ -791,21 +791,21 @@ display: flex; justify-content: space-between; font-size: 0.5rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; } .att8Track { height: 7px; - background: rgba(45, 125, 125, 0.08); + background: rgba(var(--status-above-rgb), 0.08); border-radius: 3px; position: relative; } .att8Fill { height: 100%; - background: var(--accent-teal, #2d7d7d); + background: var(--brand); border-radius: 3px; } @@ -814,25 +814,28 @@ top: -2px; bottom: -2px; width: 1.5px; - background: rgba(224, 114, 86, 0.6); + background: rgba(var(--brand-rgb), 0.6); z-index: 2; } .att8Score { + font-variant-numeric: tabular-nums; text-align: right; } .att8Value { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-variant-numeric: tabular-nums; + font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); line-height: 1; } .att8Delta { + font-variant-numeric: tabular-nums; font-size: 0.55rem; - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; @@ -853,31 +856,31 @@ align-items: center; gap: 0.5rem; padding-bottom: 0.4rem; - border-bottom: 1.5px solid var(--border-color, #e5dfd5); + border-bottom: 1.5px solid var(--border); } .ofstedBullet { display: block; width: 3px; height: 1em; - background: var(--accent-coral, #e07256); + background: var(--brand); border-radius: 2px; flex-shrink: 0; } .ofstedTitle { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 0.85rem; font-weight: 600; - color: var(--text-primary, #1a1612); + color: var(--text-primary); } .ofstedFramework { align-self: flex-start; padding: 0.15rem 0.5rem; border-radius: 4px; - background: rgba(90, 58, 110, 0.12); - color: #5a3a6e; + background: rgba(var(--phase-secondary-rgb), 0.12); + color: var(--phase-secondary-text); font-size: 0.5rem; font-weight: 700; letter-spacing: 0.05em; @@ -899,7 +902,7 @@ .rcArea { font-size: 0.62rem; - color: var(--text-secondary, #4a453d); + color: var(--text-secondary); } .rcChip { @@ -913,13 +916,13 @@ } .rcChipGood { - background: rgba(45, 125, 125, 0.12); - color: var(--accent-teal, #2d7d7d); + background: rgba(var(--status-above-rgb), 0.12); + color: var(--status-above); } .rcChipNeutral { - background: var(--accent-gold-bg, rgba(180, 150, 40, 0.12)); - color: var(--accent-gold-text, #7a6800); + background: var(--status-below-bg); + color: var(--status-below); } /* Safeguarding is a separate binary met/not-met judgement, not a graded area, @@ -931,30 +934,30 @@ gap: 0.5rem; margin-top: 0.05rem; padding-top: 0.35rem; - border-top: 1px dashed var(--border-color, #e5dfd5); + border-top: 1px dashed var(--border); } .rcSep { - color: var(--text-muted, #8a847a); + color: var(--text-muted); font-style: italic; } .rcChipMet { background: transparent; - color: var(--accent-teal, #2d7d7d); - border: 1px solid rgba(45, 125, 125, 0.4); + color: var(--status-above); + border: 1px solid rgba(var(--status-above-rgb), 0.4); } .ofstedMeta { font-size: 0.6rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } /* Compare preview */ .comparePreview { width: 100%; - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 8px; overflow: hidden; font-size: 0.6rem; @@ -963,23 +966,23 @@ .compareHead { display: grid; grid-template-columns: 1fr repeat(2, 1fr); - background: rgba(45, 125, 125, 0.1); + background: rgba(var(--status-above-rgb), 0.1); padding: 0.35rem 0.5rem; gap: 0.35rem; } .compareHeadCell { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 0.62rem; font-weight: 700; - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); line-height: 1.2; } .compareHeadLabel { font-family: inherit; font-size: 0.48rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; @@ -990,37 +993,37 @@ grid-template-columns: 1fr repeat(2, 1fr); padding: 0.3rem 0.5rem; gap: 0.35rem; - border-top: 1px solid var(--border-color, #e5dfd5); + border-top: 1px solid var(--border); align-items: baseline; } .compareRowLabel { font-size: 0.55rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; } .compareRowVal { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 0.72rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); } .compareRowValHi { - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); } .compareFoot { font-size: 0.52rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); padding: 0.35rem 0.5rem; - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); text-transform: uppercase; letter-spacing: 0.06em; text-align: center; - border-top: 1px solid var(--border-color, #e5dfd5); + border-top: 1px solid var(--border); } @media (max-width: 768px) { @@ -1065,8 +1068,8 @@ grid-template-columns: 1.4fr 1fr; gap: 2rem; padding: 1.75rem; - background: var(--bg-card, white); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 14px; } @@ -1078,30 +1081,34 @@ .editorialKicker { font-size: 0.68rem; - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; } .editorialHeading { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 1.35rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin: 0; line-height: 1.25; } +/* Running prose is the one place the serif appears. Slightly larger than the + UI around it because Literata's x-height sits lower than the grotesque's. */ .editorialText p { - font-size: 0.92rem; - color: var(--text-secondary, #5c564d); - line-height: 1.6; + font-family: var(--font-prose); + font-size: 0.97rem; + color: var(--text-secondary); + line-height: 1.65; margin: 0; + max-width: 62ch; } .factbox { - background: var(--bg-secondary, #f3ede4); + background: var(--bg-secondary); border-radius: 10px; padding: 1.25rem; display: flex; @@ -1110,10 +1117,10 @@ } .factboxHeading { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-size: 1rem; font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); margin: 0 0 0.85rem; } @@ -1122,7 +1129,7 @@ justify-content: space-between; align-items: baseline; padding: 0.5rem 0; - border-bottom: 1px solid var(--border-color, #e5dfd5); + border-bottom: 1px solid var(--border); font-size: 0.85rem; gap: 0.5rem; } @@ -1132,13 +1139,13 @@ } .factKey { - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .factVal { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-family: var(--font-display); font-weight: 700; - color: var(--text-primary, #1a1612); + color: var(--text-primary); text-align: right; } @@ -1168,7 +1175,7 @@ .sortSelect { padding: 0.375rem 0.75rem; - border: 1px solid var(--border-color, #e0ddd8); + border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-card); color: var(--text-secondary); @@ -1189,7 +1196,7 @@ gap: 0.375rem; padding: 0.25rem 0.625rem; background: var(--bg-secondary); - border: 1px solid var(--border-color, #e0ddd8); + border: 1px solid var(--border); border-radius: 999px; font-size: 0.8rem; color: var(--text-secondary); @@ -1216,8 +1223,9 @@ } .loadMoreCount { + font-variant-numeric: tabular-nums; font-size: 0.875rem; - color: var(--text-muted, #8a847a); + color: var(--text-muted); margin: 0; } @@ -1231,7 +1239,7 @@ .admissionsStrip { padding: 1.5rem 0 2rem; - border-top: 1px solid var(--border-color, #e5dfd5); + border-top: 1px solid var(--border); } .stripHeader { @@ -1248,12 +1256,12 @@ font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - color: var(--text-muted, #6d685f); + color: var(--text-muted); } .stripCta { font-size: 0.82rem; - color: var(--accent-teal, #2d7d7d); + color: var(--brand); font-weight: 600; text-decoration: none; } @@ -1269,11 +1277,11 @@ } .countdownChip { - background: var(--bg-card, #fff); - border: 1px solid var(--border-color, #e5dfd5); + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 12px; padding: 1rem 1.1rem 0.9rem; - box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06); + box-shadow: 0 2px 8px rgba(var(--shadow-rgb), 0.06); display: flex; flex-direction: column; gap: 0.2rem; @@ -1292,16 +1300,16 @@ } .countdownChipDeadline::before { - background: var(--accent-coral, #e07256); + background: var(--brand); } .countdownChipOffer::before { - background: var(--accent-teal, #2d7d7d); + background: var(--status-above); } .countdownChipUrgent { - border-color: rgba(224, 114, 86, 0.4); - background: rgba(224, 114, 86, 0.04); + border-color: rgba(var(--brand-rgb), 0.4); + background: rgba(var(--brand-rgb), 0.04); } .chipTrack { @@ -1316,11 +1324,11 @@ } .chipTrackDeadline { - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } .chipTrackOffer { - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); } .chipTrackDot { @@ -1332,7 +1340,8 @@ } .chipDays { - font-family: var(--font-playfair), 'Playfair Display', serif; + font-variant-numeric: tabular-nums; + font-family: var(--font-display); font-size: 2.6rem; font-weight: 700; line-height: 1; @@ -1341,18 +1350,18 @@ .countdownChipDeadline .chipDays, .countdownChipUrgent .chipDays { - color: var(--accent-coral-dark, #c45a3f); + color: var(--brand-strong); } .countdownChipOffer .chipDays { - color: var(--accent-teal, #2d7d7d); + color: var(--status-above); } .chipDaysUnit { - font-family: 'DM Sans', sans-serif; + font-family: var(--font-ui); font-size: 0.78rem; font-weight: 500; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-left: 0.2rem; vertical-align: bottom; line-height: 2; @@ -1361,14 +1370,14 @@ .chipMilestone { font-size: 0.85rem; font-weight: 600; - color: var(--text-primary, #1a1612); + color: var(--text-primary); line-height: 1.25; margin-top: 0.1rem; } .chipDate { font-size: 0.75rem; - color: var(--text-muted, #6d685f); + color: var(--text-muted); margin-top: 0.05rem; } diff --git a/nextjs-app/components/HomeView.tsx b/nextjs-app/components/HomeView.tsx index 4662a26..ae9c459 100644 --- a/nextjs-app/components/HomeView.tsx +++ b/nextjs-app/components/HomeView.tsx @@ -573,10 +573,10 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm rwmDelta == null ? {} : rwmDelta >= 2 - ? { fontSize: '0.7rem', color: 'var(--accent-teal, #2d7d7d)', fontWeight: 600 } + ? { fontSize: '0.7rem', color: 'var(--status-above)', fontWeight: 600 } : rwmDelta <= -2 - ? { fontSize: '0.7rem', color: 'var(--accent-coral, #e07256)', fontWeight: 600 } - : { fontSize: '0.7rem', color: 'var(--text-muted, #8a847a)' }; + ? { fontSize: '0.7rem', color: 'var(--brand)', fontWeight: 600 } + : { fontSize: '0.7rem', color: 'var(--text-muted)' }; return (
@@ -601,13 +601,13 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm fontWeight: 600, borderRadius: '3px', whiteSpace: 'nowrap', - ...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--accent-teal-bg)', color: 'var(--accent-teal, #2d7d7d)' } : - ofstedBadge.cssClass === 'ofsted2' ? { background: 'rgba(60,140,60,0.12)', color: '#2f7a2f' } : - ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--accent-gold-bg)', color: 'var(--accent-gold-text, #7a6800)' } : - ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--accent-coral-bg)', color: 'var(--accent-coral, #e07256)' } : - ofstedBadge.cssClass === 'ofstedRc' ? { background: '#5a3a6e', color: '#fff' } : - ofstedBadge.cssClass === 'ofstedPending' ? { background: '#e0e0e0', color: '#666' } : - { background: '#e0e0e0', color: '#666' }), + ...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } : + ofstedBadge.cssClass === 'ofsted2' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } : + ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--status-below-bg)', color: 'var(--status-below)' } : + ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--brand-bg)', color: 'var(--brand)' } : + ofstedBadge.cssClass === 'ofstedRc' ? { background: 'var(--phase-secondary-text)', color: 'var(--text-inverse)' } : + ofstedBadge.cssClass === 'ofstedPending' ? { background: 'var(--bg-secondary)', color: 'var(--text-muted)' } : + { background: 'var(--bg-secondary)', color: 'var(--text-muted)' }), }} > {ofstedBadge.label} diff --git a/nextjs-app/components/InfoPopover.module.css b/nextjs-app/components/InfoPopover.module.css index 297a184..1c01cfb 100644 --- a/nextjs-app/components/InfoPopover.module.css +++ b/nextjs-app/components/InfoPopover.module.css @@ -11,7 +11,7 @@ /* font-size:0 hides the button's own "?" text node; the ::before glyph below carries the visible circled "?" at its own explicit size. */ font-size: 0; - color: var(--text-muted, #8a7a72); + color: var(--text-muted); cursor: help; line-height: 1; user-select: none; @@ -34,17 +34,17 @@ .icon:hover, .icon[aria-expanded='true'], .icon:focus-visible { - color: var(--accent-coral-dark, #b04a2e); + color: var(--brand-strong); } .tooltip { z-index: 9999; width: max-content; max-width: min(260px, calc(100vw - 24px)); - background: var(--bg-primary, #faf7f2); - border: 1px solid var(--border-color, #e8ddd4); + background: var(--bg-primary); + border: 1px solid var(--border); border-radius: 10px; - box-shadow: 0 4px 16px rgba(44, 36, 32, 0.15); + box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.15); padding: 0.6rem 0.75rem; display: flex; flex-direction: column; @@ -52,26 +52,26 @@ } .arrow { - fill: var(--bg-primary, #faf7f2); - stroke: var(--border-color, #e8ddd4); + fill: var(--bg-primary); + stroke: var(--border); stroke-width: 1px; } .label { font-weight: 600; font-size: 0.75rem; - color: var(--text-primary, #2c2420); + color: var(--text-primary); } .plain { font-size: 0.75rem; - color: var(--text-secondary, #5a4a44); + color: var(--text-secondary); line-height: 1.4; } .detail { font-size: 0.7rem; - color: var(--text-muted, #8a7a72); + color: var(--text-muted); line-height: 1.4; margin-top: 0.1rem; } diff --git a/nextjs-app/components/LeafletHeroMapInner.tsx b/nextjs-app/components/LeafletHeroMapInner.tsx index 0f8d7c4..7f2377a 100644 --- a/nextjs-app/components/LeafletHeroMapInner.tsx +++ b/nextjs-app/components/LeafletHeroMapInner.tsx @@ -22,10 +22,10 @@ interface LeafletHeroMapInnerProps { // reliably inside Leaflet's divIcon HTML string (no CSS-module dependency). const PIN_HTML = `
- + - - + +
`; diff --git a/nextjs-app/components/LeafletMapInner.tsx b/nextjs-app/components/LeafletMapInner.tsx index b88d1a5..0f0c488 100644 --- a/nextjs-app/components/LeafletMapInner.tsx +++ b/nextjs-app/components/LeafletMapInner.tsx @@ -48,21 +48,21 @@ function buildPopupBadge(school: School): PopupBadge { // ofsted_framework is the raw event grouping, never "ReportCard"). if (school.ofsted_rc_date) { const rcYear = new Date(school.ofsted_rc_date).getFullYear(); - return { label: `Report Card · ${rcYear}`, style: 'background:#5a3a6e;color:#fff' }; + return { label: `Report Card · ${rcYear}`, style: 'background:var(--phase-secondary-text);color:var(--text-inverse)' }; } const year = school.ofsted_date ? new Date(school.ofsted_date).getFullYear() : null; const yearStr = year ? ` · ${year}` : ''; if (school.ofsted_grade) { const labels: Record = { 1: 'Outstanding', 2: 'Good', 3: 'Req. Improvement', 4: 'Inadequate' }; const colours: Record = { - 1: 'background:#d4f0ea;color:#2d7d7d', - 2: 'background:rgba(60,140,60,0.12);color:#2f7a2f', - 3: 'background:#fef3cd;color:#7a6800', - 4: 'background:#fde8e0;color:#e07256', + 1: 'background:var(--status-above-bg);color:var(--status-above)', + 2: 'background:var(--status-above-bg);color:var(--status-above)', + 3: 'background:var(--status-below-bg);color:var(--status-below)', + 4: 'background:var(--brand-bg);color:var(--brand)', }; return { label: `${labels[school.ofsted_grade]}${yearStr}`, style: colours[school.ofsted_grade] }; } - return { label: 'Not yet inspected', style: 'background:#e0e0e0;color:#666' }; + return { label: 'Not yet inspected', style: 'background:var(--bg-secondary);color:var(--text-muted)' }; } export default function LeafletMapInner({ schools, center, zoom, referencePoint, onMarkerClick, nationalAvgRwm, laAverages }: LeafletMapInnerProps) { @@ -99,7 +99,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint, const refIcon = L.divIcon({ html: `
${sign}${diff} vs ${laName} avg
`; } metricHtml = `
- ${score.toFixed(1)} - Attainment 8 + ${score.toFixed(1)} + Attainment 8 ${deltaLine}
`; } else if (school.rwm_expected_pct != null) { @@ -159,7 +159,7 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint, let deltaLine = ''; if (nationalAvgRwm != null) { const diff = Math.round(rwm - nationalAvgRwm); - const colour = diff >= 2 ? '#2d7d7d' : diff <= -2 ? '#e07256' : '#8a847a'; + const colour = diff >= 2 ? 'var(--status-above)' : diff <= -2 ? 'var(--brand)' : 'var(--text-muted)'; const text = diff >= 2 ? `+${diff} pts vs national` : diff <= -2 ? `${diff} pts vs national` : @@ -167,8 +167,8 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint, deltaLine = `
${text}
`; } metricHtml = `
- ${rwm}% - Reading, Writing & Maths + ${rwm}% + Reading, Writing & Maths ${deltaLine}
`; } @@ -177,14 +177,14 @@ export default function LeafletMapInner({ schools, center, zoom, referencePoint, const popupContent = `
- ${escapeHtml(school.school_name)} + ${escapeHtml(school.school_name)} ${badge.label}
-
`; 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
-
+
Expected standard
-
+
Exceeding / high score
-
+
National average
diff --git a/nextjs-app/components/SchoolCard.module.css b/nextjs-app/components/SchoolCard.module.css index d4f1f12..8fb2897 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(--brand); + background: var(--brand-bg); } .trendStable { - color: var(--text-muted, #8a847a); - background: rgba(138, 132, 122, 0.15); + color: var(--text-muted); + background: rgba(var(--muted-rgb), 0.15); } .actions { @@ -156,7 +157,7 @@ .metricHint { font-size: 0.7rem; - color: var(--text-muted, #8a847a); + color: var(--text-muted); display: block; margin-top: 1px; font-weight: 400; 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..fd4bed7 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(--brand); } +.trendStable { color: var(--text-muted); } /* Line 4: location */ .line4 { @@ -148,13 +149,13 @@ flex-wrap: wrap; gap: 0; font-size: 0.8rem; - color: var(--text-muted, #8a847a); + color: var(--text-muted); } .line4 span:not(:last-child)::after { content: '·'; margin: 0 0.4rem; - color: var(--border-color, #e5dfd5); + color: var(--border); } .distanceBadge { @@ -162,8 +163,8 @@ padding: 0.0625rem 0.375rem; font-size: 0.75rem; font-weight: 600; - background: var(--accent-teal, #2d7d7d); - color: white; + background: var(--brand); + color: var(--brand-on); border-radius: 3px; } @@ -200,23 +201,23 @@ font-weight: 400; } -.ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); } -.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #2f7a2f; } -.ofsted3 { background: var(--accent-gold-bg); color: var(--accent-gold-text, #7a6800); } -.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); } +.ofsted1 { background: var(--status-above-bg); color: var(--status-above); } +.ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); } +.ofsted3 { background: var(--status-below-bg); color: var(--status-below); } +.ofsted4 { background: var(--brand-bg); color: var(--brand); } /* ── Ofsted badge variants ──────────────────────────────────────────────── */ /* 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..8ecccf5 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(--brand); } /* Line 4: location + distance */ .line4 { @@ -135,13 +137,13 @@ flex-wrap: wrap; gap: 0; font-size: 0.8rem; - color: var(--text-muted, #8a847a); + color: var(--text-muted); } .line4 span:not(:last-child)::after { content: '·'; margin: 0 0.4rem; - color: var(--border-color, #e5dfd5); + color: var(--border); } .distanceBadge { @@ -149,8 +151,8 @@ padding: 0.0625rem 0.375rem; font-size: 0.75rem; font-weight: 600; - background: var(--accent-teal, #2d7d7d); - color: white; + background: var(--brand); + color: var(--brand-on); border-radius: 3px; } @@ -175,15 +177,15 @@ font-size: 0.75rem; font-weight: 600; line-height: 1.4; - background: rgba(26, 22, 18, 0.05); - color: var(--text-secondary, #5c564d); + background: rgba(var(--shadow-rgb), 0.05); + color: var(--text-secondary); border-radius: 4px; white-space: nowrap; } .selectiveTag { - background: rgba(180, 120, 0, 0.1); - color: #8a6200; + background: rgba(var(--status-below-rgb), 0.1); + color: var(--status-below); } /* ── Ofsted badge ────────────────────────────────────── */ @@ -202,17 +204,17 @@ font-weight: 400; } -.ofsted1 { background: var(--accent-teal-bg); color: var(--accent-teal, #2d7d7d); } -.ofsted2 { background: rgba(60, 140, 60, 0.12); color: #2f7a2f; } -.ofsted3 { background: var(--accent-gold-bg); color: var(--accent-gold-text, #7a6800); } -.ofsted4 { background: var(--accent-coral-bg); color: var(--accent-coral, #e07256); } +.ofsted1 { background: var(--status-above-bg); color: var(--status-above); } +.ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); } +.ofsted3 { background: var(--status-below-bg); color: var(--status-below); } +.ofsted4 { background: var(--brand-bg); color: var(--brand); } /* ── Ofsted badge variants ──────────────────────────────────────────────── */ -.ofstedRc { background: #5a3a6e; color: #fff; } +.ofstedRc { background: var(--phase-secondary-text); color: var(--text-inverse); } /* Inspected but with no overall grade (OEIF post-Sept-2024) — neutral slate, distinct from the grey "Not yet inspected" pending state. */ -.ofstedInspected { background: #e6ecf2; color: #45586b; } -.ofstedPending { background: #e0e0e0; color: #666; } +.ofstedInspected { background: var(--phase-primary-bg); color: var(--phase-primary-text); } +.ofstedPending { background: var(--border); color: var(--text-muted); } /* ── Right actions column ────────────────────────────── */ .rowActions { @@ -268,7 +270,7 @@ } .closingTag { - background: #fdf6e3; - color: #8a6200; - border: 1px solid #e2c96f; + background: var(--status-below-bg); + color: var(--status-below); + border: 1px solid var(--status-below-bg); } 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..fc5df84 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(--brand-bg); + color: var(--brand-strong); } .chipNeutral { @@ -141,18 +141,18 @@ } .badgeGood { - background: rgba(45, 125, 125, 0.14); - color: var(--accent-teal); + background: rgba(var(--status-above-rgb), 0.14); + color: var(--status-above); } .badgeWarn { - background: var(--accent-gold-bg); - color: var(--accent-gold-text); + background: var(--status-below-bg); + color: var(--status-below); } .badgeBad { - background: var(--accent-coral-bg); - color: var(--accent-coral-dark); + background: var(--brand-bg); + color: var(--brand-strong); } .rcList { @@ -200,7 +200,7 @@ .card { background: var(--bg-card); - border: 1px solid var(--border-light); + border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow-soft); padding: 1.25rem 1.5rem; @@ -208,7 +208,7 @@ } .link { - color: var(--accent-coral-dark); + color: var(--brand-strong); } @media (min-width: 761px) { @@ -230,14 +230,14 @@ .rowLabel { color: var(--text-secondary); padding: 0.85rem 0.5rem 0.85rem 0; - border-bottom: 1px solid var(--border-light); + border-bottom: 1px solid var(--border); } .cell { display: block; padding: 0.85rem 0.25rem; border-top: none; - border-bottom: 1px solid var(--border-light); + border-bottom: 1px solid var(--border); margin-top: 0; } 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..fc781f8 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(--brand-strong); 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(--brand-bg); + color: var(--brand-strong); } /* 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(--brand-bg); + color: var(--brand-strong); } /* 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(--brand-bg); + color: var(--brand-strong); } /* 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/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 0000000000000000000000000000000000000000..3aeabc24ccffcb90a093e07bfb47416fc9daf5da GIT binary patch literal 2612 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 W%!B}}UbW!{l{`**ov3jMN&gGFExd35 literal 0 HcmV?d00001 diff --git a/nextjs-app/public/icon-512.png b/nextjs-app/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..969395cefcd2a4a1c8ac0f8122d8a17f0b707521 GIT binary patch literal 6949 zcmeHM_g7O}l)mr~P(c)V(uF5T6(gVo1PqG60~MqQ7^+4=2?QxYda$7&O*%pZ#ef(> z5kmE;!HqOUt)?J})TrO<3gWPmO0e z1Hv~hXy0l}zmqU`Rl%W#x&|HTzqYui|4MK6vG#OVQxA_Zo=Eru@eZfDND`_ai@8Ru zodST#?&47ZXu5L{01hSsK-^U!0C;>10FNrsk1^rOWrngPFd95g@LM)gJq~DObo~rZ&?y8(w+g{^Rlq$F^Ooh zV!VrU1unpUVS(lWCb#F)ezZK<z4?YfB5m$G z_wD)=jDUq;r75(@m_Pr9duZ(msnO>QBUe;7W5)VKkeD%DIzEp0hKa|SZr?VGFUMER z`Qhx)vCOp$>S;NhF;4WDQmU*UuG%1F!c&1dKg5A6ZO>Rig1vgcM~6}`C<8K=XwWfE zbJc1xx1MiLQg;~S7eD{lXyf#(Y ziCM_Z^_v}ilarWs7t->;1Qbq-s{-@8o}^m>#TA_8_T{-yuUxTJwH}wckgn>OaoE?> z^geTqA3=+ZK2zty@*yDUFdW^)p&J<3K{hXA+)oZ|V)lX8YtY&a<{&?Hm+h+Wu;3m_M1tJ2Pr7y$tRl>z?3q(wuE=ojDk zInlj|$I@xwsG#!1?VmyzBq)V%A-7KN7mWK$Yu@WnBIU=s3 zQX&F?svfsNNeEGvdEkMef$b~Cd;E`nDAv$m#MWODJ7N;JFrW;?8LCxJy=O#3vi&A7 z(zKIa!^Bz#N1Lft88c1w1lE_E?3v|3nEG^zVj=qFN&L$fy4l4K@-G6w`EtBXS=nrl zou@mG2BwmhhUXfG6ydnJX>MqdE)D9Y74bBuL@8n|Zq!q<-rcPb`sdf-B+XpLO zgj4K!OLf%PxqJoz?TKO9E$r{%feMXzfp2>Bk5qd6;4-}+%Vb{2uMku)udPuU+WBlE zj*|oc$7Eoaj@OMaW2V|mJ)>g-g0*sgxY$U-)2nm2ki(guf?JGv%xSNk%{uauuf(~P znYJxNj-%kBxB!qyg;F@k#{O+AdFQh>D6pd=dtyL!!eC3J9B)1D^gdQ{h`gPzu1MKT zwnN{csmLuIos9V`Ye?5J;hQNv@`GF<7`rV@GV%5&V6{c~CG!Sxk; z7xxJO`%YTVf4#ND4fHZ$k6h9Z$r8NG6M*#yyH4zI>8}!wO$4Kw8m)l1FQDCbKM&edTt-s7XBAt_E{5M!IM5J@i<)K zzX;7OE*GXpTuquTeMOg;{W=&|U{mP#CUc>kbq}9X+=HsKzL{iAzWZ1bfIS8#Q}8sT z2X%Ga@IbR|@n8o_rK?xH!)-2Vx3BMFnNzTbA|)tYMh{5ZBbcT_gW`}sx7n7f_$;`# zi&rK1ZbI(8}$fJTg!>jnp$b)ekMxIBz| zqO6=M+N7b0A=rnm?^sY*TVtjeJpI;i5{Egse+2RMJdkECq0jOgNsv<=wmDH z_oGT{3ALXGy_KK-Zan2qto7@)(vr$X&hz^}d{~%|C^E7bCk09)n?SSJEiJ;2;^+;U zV&7gLOMRtlDDXWQiu>MA_`4J}w&SjXbHkhbkukz9B zCZH6U98eS>KhE)_V>sF*WcMX0fjxO4xi2{+K+li)vvS-dP0cGBH_bOfhl8vPA9@>m$q zNP8`029MdqXZ5qi$5#8i*BHCwg<#o9zk7Wj8u0ze1oc#<5DzfVMOUEd&cYuu<=bA@ z8)^xpLmRuU?wrl_ay)4f{1Ue+onuAR0n+He+upI&+S>cVe1x*%$;6*Y-C|H8aOcr z4CviH0$`kOcF?+P?JKiBa<#ADhvN}acRTenfh1ywa2}1&JNs5!6)GLbL7Blv=33a6 zHBFU=;uHpjlw&*}PCP~wCSwj}Zn1F9L)HBlCA+JNcE z|M!SpJR7qFTy-*tIkHssO5{^cs-p@3ETybbVz-FO70$B-&Vw#Z>q-^=N!Ci$COeXR z1{v<_6*)t&DSJoX-)t)rAC#`4-Yy$}6Mt9))y%PZFvf$jRjuP+3h>z^d1nalpa-1J zcXKnwN2(+q%MaE=^Z0CLyi9qiw4~&~i7prHHJBtU9(mdf0Dk7oZ%~4w`J>p$QiLZh z$d9Hp;0+QZ<>m9;hK!%DhSVKBJ40AVJFth3i=WQGag2ygpSkfB_>F;O6PJk5pL5<% zw=h*Unw7lg&U7$;%CRn4si~`v8+LsXa2MVOknfF=2mR5+du2nctyn_iCtok&a~l>U z2C{LaM*8HLd>uMcpU2{}NdM*l$I&bdCPHm~>ju_KAU{LsJF@9~`m7G-{* zA^Zc38h)r!u6vCA4KnRqa5}~J2L2AM8@drWdZu@Tm2WEgoiON+Z$8zmFYr5*@Sz-s zWFKu}_k9Va_SH+8m~qv+r=bK?TzVT-l|Ne3E!n5U&?Z-SbD8`2jjk#4Gau-UV_Dko zwxYI*o*{UeQrEd=zu#tKTMB;#$o$Lt`N{Y{Ds@_*x9(Cl_~%?%tohU{=s7Oubf z!02tJ9?kim<71q_-A7obt&%Hti@k*0K)N^-J3AX=!1W%Uv~y6a*00G5ZqZjOJ9%sXeB<&>MDV7iruxG1=OXGp3~g(wR47;_CZimG zsgCN0L+qOS;I5_U+m{&{Dcm0HEaX>S{u0WzId6$#7~RGp@fFV6*NK*xwc%m^2V>Qr z^9{gYtdm;d>Hg;vPZ|be938FGrGg7Ir&O&!1p4{iK-5Z8WQCNg9k*?)vV9_nZ{LZ$ zObD_$&RIVk!lmbamiS0>#{LQRZLG6$EivRSguZH>qi4$E> z6>fb&K1qY*`m|sBlQM205Ysa+%Q;r&)27?d#e(cEF4N_|}ml$=? z+VjMHk6vr0&1b$vFWi-lR&{rIf*ZBshcUb1FVos$9P{R53ZYFUbyQb(mSSyy%B4Z6 zNDk=aBvU_obEi!Yctq~-!xK(HqTW3Fkva>Tl<^SnENI_0Jd?9&mh80FhM^cq^jWIvhFU&DUHU z{ty=Ns=%fmC0)5qJRzgrHs7&ERH|?6on;Cp>*Jg%rN?-`Bsy#t+xt#p2He58-TiLZy!1H#3&4KFdrH7WkNALTxuUs)^)WNmn4UYTFY0 zra@XG7?$Pzf_mS)nGCz+!G9tatAO6}9}}@cpj3 zA=yeq*{&^T@)mM-DZm=_*Bw<+B6i#9^$S!ux%i^vns3++D7Wzn0V})X@eJ-%ec!!2h z!R$gFt9m4o=x|ay%TsJ2NgkJ3T;OaxOs-!NDdevGGOv4#?mCt3lcbY19ERm&feIs+ zH}P#{O~`BM-ETHM9c=F6ptyGq%Mf16He~r*LL@Q2f1K(zTs{#oC-2to;#?34j<&PA zxA1wV?HoV{xde}R_PLQhd~m~-Hqf?8^AW2tzFT~LPLqQrpW#nID~VE;B6Ce?DTX(^ zd5KC6FBv&tN=16mWwou-*T%#Bg2#6yVtx+v{Ni&_Ha%}Q$PCxLL8XvanS-mfDD83Y z&}r0D*wS^q){FL&hF!!(GQ*L)*_L2~(eJR(ft43l@ZO;|DGV~#i*fdbds+B`w}r-H zple}pd9^)w*0OD6(QW5T&bi?6t|yTJuDv`4=Z<{xG#~p}VFOfZ@?C><{@N9&uYOpP z`JOJ1os;kM(Kgk-$4zp5xDz}ct&UUV1yk}p4XRhr!i>45 zwVy!yWHZiPee1Mx0CWYiGNUMgif&s)4x~Ii%SFYe(qw9gHI1a|!2`yvxob^2+%;ct zr=>0`{?9o1o_tM(QpNaWOMNk8ZbMoxG5Pa}{eonNvdd1|vuNdteAPBjPczecdh;sr z)Oc6D`h*;1)p&e*AX>BT9c@*_xYaM`W$>mEb(AER$0 zV_RNC$Rq&XwRovPdNYh P3$V7Zy;yO<`{928KLKvm literal 0 HcmV?d00001 diff --git a/nextjs-app/public/icon-maskable-512.png b/nextjs-app/public/icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..612182a24afedc462831f9014aff76768aa1a400 GIT binary patch literal 5251 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 Date: Thu, 6 Aug 2026 12:29:53 +0100 Subject: [PATCH 2/2] fix(design): keep below/attention off the brand hue, harden the share-card fonts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-up on #86. The blind coral -> brand rename recreated the exact collision this PR set out to remove: coral had been both the primary CTA and the "below average" signal, so every negative indicator followed --primary onto iris. Sixteen rules moved back onto the status ramp — delta chips, trend-down arrows, progress-negative values, statusBad, chipBad/badgeBad, and the urgent deadline chips. The Ofsted scale had also lost its worst step, with grade 4 landing on brand while 1-2 were teal and 3 was amber. It now escalates by weight rather than by reaching for another hue: a tinted amber chip for "requires improvement", a solid amber one for "inadequate" (5.1:1 light, 7.7:1 dark). Report-card grade 5 follows the same rule. globals.css now describes status as valence — teal above/good, amber below/needs-attention — which is what it has to mean for an urgent deadline, rather than the narrower "comparison point only" the first draft claimed. On the share-card fonts: /opengraph-image is prerendered, so the font read happens in the builder stage where assets/ exists, and the baked PNG ships inside .next/standalone/.next/server/app/. File tracing independently places the fonts at .next/standalone/assets, which the existing standalone COPY carries to /app/assets. So the reported ENOENT doesn't occur — but it depends on the tracer resolving a runtime join(), and a miss would be a silent 500 rather than a build failure. Declared outputFileTracingIncludes for the route and made the Dockerfile COPY explicit so neither is left to inference. Also repointed the immutable Cache-Control rule from the deleted favicon.svg to app/icon.svg, where it was caching a 404. Verified: tsc clean, 159/159 tests, clean rebuild prerenders all three image routes with the fonts present in standalone. Co-Authored-By: Claude Opus 5 --- nextjs-app/Dockerfile | 6 ++++++ nextjs-app/app/globals.css | 8 ++++++-- nextjs-app/components/AdmissionsView.module.css | 6 +++--- nextjs-app/components/DeltaChip.module.css | 4 ++-- nextjs-app/components/HomeView.module.css | 6 +++--- nextjs-app/components/HomeView.tsx | 2 +- nextjs-app/components/LeafletMapInner.tsx | 2 +- nextjs-app/components/SchoolCard.module.css | 4 ++-- nextjs-app/components/SchoolRow.module.css | 4 ++-- .../components/SecondarySchoolRow.module.css | 4 ++-- .../components/compare/compareSections.module.css | 8 ++++---- .../components/school/schoolSections.module.css | 14 +++++++------- nextjs-app/next.config.js | 13 ++++++++++++- 13 files changed, 51 insertions(+), 30 deletions(-) diff --git a/nextjs-app/Dockerfile b/nextjs-app/Dockerfile index 93d19e7..2f4665f 100644 --- a/nextjs-app/Dockerfile +++ b/nextjs-app/Dockerfile @@ -47,6 +47,12 @@ COPY --from=builder /app/public ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static +# Fonts for the generated share card. File tracing already places these in +# .next/standalone/assets, so the COPY above carries them — this makes the +# dependency explicit rather than implicit in the tracer's behaviour, because +# a miss here is a silent 500 on /opengraph-image, not a build failure. +COPY --from=builder /app/assets ./assets + # Set correct permissions RUN chown -R nextjs:nodejs /app diff --git a/nextjs-app/app/globals.css b/nextjs-app/app/globals.css index 67ae427..7d4e77f 100644 --- a/nextjs-app/app/globals.css +++ b/nextjs-app/app/globals.css @@ -6,10 +6,14 @@ * five-bar spread that appears inside every school row. * * Colour has exactly three jobs and they never borrow each other's hues: - * brand (iris) interactive + identity. Never means "good". - * status (teal / amber) above / below the comparison point. Never a CTA. + * brand (iris) interactive + identity. Never carries valence. + * status (teal / amber) valence — teal is above/good, amber is + * below/needs-attention. Never a CTA. * phase (desaturated) category chips, deliberately subordinate. * + * Escalate within status by weight, not by reaching for a new hue: a tinted + * amber chip for "requires improvement", a solid amber one for "inadequate". + * * Teal/amber rather than green/red so the above/below signal survives every * form of colour blindness — the audience includes parents reading closely. */ diff --git a/nextjs-app/components/AdmissionsView.module.css b/nextjs-app/components/AdmissionsView.module.css index f80a0c5..c4dc43d 100644 --- a/nextjs-app/components/AdmissionsView.module.css +++ b/nextjs-app/components/AdmissionsView.module.css @@ -181,8 +181,8 @@ .chipOffer::before { background: var(--status-above); } .chipUrgent { - border-color: rgba(var(--brand-rgb), 0.4); - background: rgba(var(--brand-rgb), 0.04); + border-color: rgba(var(--status-below-rgb), 0.4); + background: rgba(var(--status-below-rgb), 0.04); } .chipTrack { @@ -218,7 +218,7 @@ .chipDeadline .chipDays, .chipUrgent .chipDays { - color: var(--brand-strong); + color: var(--status-below); } .chipOffer .chipDays { diff --git a/nextjs-app/components/DeltaChip.module.css b/nextjs-app/components/DeltaChip.module.css index 974270e..849679c 100644 --- a/nextjs-app/components/DeltaChip.module.css +++ b/nextjs-app/components/DeltaChip.module.css @@ -28,8 +28,8 @@ } .bad { - background: var(--brand-bg); - color: var(--brand-strong); + background: var(--status-below-bg); + color: var(--status-below); } .neutral { diff --git a/nextjs-app/components/HomeView.module.css b/nextjs-app/components/HomeView.module.css index fffce2b..aec98f9 100644 --- a/nextjs-app/components/HomeView.module.css +++ b/nextjs-app/components/HomeView.module.css @@ -1308,8 +1308,8 @@ } .countdownChipUrgent { - border-color: rgba(var(--brand-rgb), 0.4); - background: rgba(var(--brand-rgb), 0.04); + border-color: rgba(var(--status-below-rgb), 0.4); + background: rgba(var(--status-below-rgb), 0.04); } .chipTrack { @@ -1350,7 +1350,7 @@ .countdownChipDeadline .chipDays, .countdownChipUrgent .chipDays { - color: var(--brand-strong); + color: var(--status-below); } .countdownChipOffer .chipDays { diff --git a/nextjs-app/components/HomeView.tsx b/nextjs-app/components/HomeView.tsx index ae9c459..22a56c7 100644 --- a/nextjs-app/components/HomeView.tsx +++ b/nextjs-app/components/HomeView.tsx @@ -604,7 +604,7 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare, nationalAvgRwm ...(ofstedBadge.cssClass === 'ofsted1' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } : ofstedBadge.cssClass === 'ofsted2' ? { background: 'var(--status-above-bg)', color: 'var(--status-above)' } : ofstedBadge.cssClass === 'ofsted3' ? { background: 'var(--status-below-bg)', color: 'var(--status-below)' } : - ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--brand-bg)', color: 'var(--brand)' } : + ofstedBadge.cssClass === 'ofsted4' ? { background: 'var(--status-below)', color: 'var(--text-inverse)' } : ofstedBadge.cssClass === 'ofstedRc' ? { background: 'var(--phase-secondary-text)', color: 'var(--text-inverse)' } : ofstedBadge.cssClass === 'ofstedPending' ? { background: 'var(--bg-secondary)', color: 'var(--text-muted)' } : { background: 'var(--bg-secondary)', color: 'var(--text-muted)' }), diff --git a/nextjs-app/components/LeafletMapInner.tsx b/nextjs-app/components/LeafletMapInner.tsx index 0f0c488..5bfe7db 100644 --- a/nextjs-app/components/LeafletMapInner.tsx +++ b/nextjs-app/components/LeafletMapInner.tsx @@ -58,7 +58,7 @@ function buildPopupBadge(school: School): PopupBadge { 1: 'background:var(--status-above-bg);color:var(--status-above)', 2: 'background:var(--status-above-bg);color:var(--status-above)', 3: 'background:var(--status-below-bg);color:var(--status-below)', - 4: 'background:var(--brand-bg);color:var(--brand)', + 4: 'background:var(--status-below);color:var(--text-inverse)', }; return { label: `${labels[school.ofsted_grade]}${yearStr}`, style: colours[school.ofsted_grade] }; } diff --git a/nextjs-app/components/SchoolCard.module.css b/nextjs-app/components/SchoolCard.module.css index 8fb2897..c25dcf1 100644 --- a/nextjs-app/components/SchoolCard.module.css +++ b/nextjs-app/components/SchoolCard.module.css @@ -132,8 +132,8 @@ } .trendDown { - color: var(--brand); - background: var(--brand-bg); + color: var(--status-below); + background: var(--status-below-bg); } .trendStable { diff --git a/nextjs-app/components/SchoolRow.module.css b/nextjs-app/components/SchoolRow.module.css index fd4bed7..9a1ceb8 100644 --- a/nextjs-app/components/SchoolRow.module.css +++ b/nextjs-app/components/SchoolRow.module.css @@ -139,7 +139,7 @@ } .trendUp { color: var(--status-above); } -.trendDown { color: var(--brand); } +.trendDown { color: var(--status-below); } .trendStable { color: var(--text-muted); } /* Line 4: location */ @@ -204,7 +204,7 @@ .ofsted1 { background: var(--status-above-bg); color: var(--status-above); } .ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); } .ofsted3 { background: var(--status-below-bg); color: var(--status-below); } -.ofsted4 { background: var(--brand-bg); color: var(--brand); } +.ofsted4 { background: var(--status-below); color: var(--text-inverse); } /* ── Ofsted badge variants ──────────────────────────────────────────────── */ /* ofsted1–4 already defined above; these cover the two new framework states */ diff --git a/nextjs-app/components/SecondarySchoolRow.module.css b/nextjs-app/components/SecondarySchoolRow.module.css index 8ecccf5..3f0a7d0 100644 --- a/nextjs-app/components/SecondarySchoolRow.module.css +++ b/nextjs-app/components/SecondarySchoolRow.module.css @@ -128,7 +128,7 @@ } .deltaPositive { color: var(--status-above); } -.deltaNegative { color: var(--brand); } +.deltaNegative { color: var(--status-below); } /* Line 4: location + distance */ .line4 { @@ -207,7 +207,7 @@ .ofsted1 { background: var(--status-above-bg); color: var(--status-above); } .ofsted2 { background: rgba(var(--status-above-rgb), 0.12); color: var(--status-above); } .ofsted3 { background: var(--status-below-bg); color: var(--status-below); } -.ofsted4 { background: var(--brand-bg); color: var(--brand); } +.ofsted4 { background: var(--status-below); color: var(--text-inverse); } /* ── Ofsted badge variants ──────────────────────────────────────────────── */ .ofstedRc { background: var(--phase-secondary-text); color: var(--text-inverse); } diff --git a/nextjs-app/components/compare/compareSections.module.css b/nextjs-app/components/compare/compareSections.module.css index fc5df84..f78afaf 100644 --- a/nextjs-app/components/compare/compareSections.module.css +++ b/nextjs-app/components/compare/compareSections.module.css @@ -123,8 +123,8 @@ } .chipBad { - background: var(--brand-bg); - color: var(--brand-strong); + background: var(--status-below-bg); + color: var(--status-below); } .chipNeutral { @@ -151,8 +151,8 @@ } .badgeBad { - background: var(--brand-bg); - color: var(--brand-strong); + background: var(--status-below-bg); + color: var(--status-below); } .rcList { diff --git a/nextjs-app/components/school/schoolSections.module.css b/nextjs-app/components/school/schoolSections.module.css index fc781f8..d863e09 100644 --- a/nextjs-app/components/school/schoolSections.module.css +++ b/nextjs-app/components/school/schoolSections.module.css @@ -860,7 +860,7 @@ } .progressNegative { - color: var(--brand-strong); + color: var(--status-below); font-weight: 700; } @@ -876,8 +876,8 @@ } .statusBad { - background: var(--brand-bg); - color: var(--brand-strong); + background: var(--status-below-bg); + color: var(--status-below); } /* Charts Section */ @@ -1025,8 +1025,8 @@ color: var(--status-below); } .ofstedGrade4 { - background: var(--brand-bg); - color: var(--brand-strong); + background: var(--status-below); + color: var(--text-inverse); } /* Report Card grade colours (5-level scale, lower = better) */ @@ -1047,8 +1047,8 @@ color: var(--status-below); } /* Needs attention */ .rcGrade5 { - background: var(--brand-bg); - color: var(--brand-strong); + background: var(--status-below); + color: var(--text-inverse); } /* Urgent improvement */ /* Safeguarding value (used inside a standard metricCard) */ 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',