f31f6f84048c89e63e48822e2bdec05afa28b0b2
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8d50afef1e |
fix(design): meet AA on tinted surfaces, and stop the footer inverting in dark
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m11s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 12s
PR Checks / Build Frontend (no push) (pull_request) Successful in 52s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 3m6s
Full audit across home, search, rankings, admissions, compare, and primary / secondary / special / no-data school pages, in both themes, measuring computed styles rather than reading CSS. Contrast: the status hues were specced against --bg-primary, but they are used as chip text on their own tint, which sits on cards and secondary surfaces rather than the page ground. Measured in the wild they were 3.85–4.44:1 — under AA — on Ofsted badges, delta chips, report-card chips and metric values, i.e. most of the product's actual signal. Darkened the light hues (#0e6e66 → #0b625a, #9a5b00 → #7f4a00) and lifted the dark teal (#3fb3a4 → #4fc0b0) so each clears AA on its own tint, which is the worst case rather than the easy one. Chart and series ramps follow. The footer was painting itself with a text token and lettering itself with a background token: `background: var(--text-primary); color: var(--bg-secondary)`. That reads correctly in one theme and inverts in the other — in dark mode it became a light slab at the bottom of a dark page, with amber section headings at 1.98:1. Added --surface-sunken and its on-* companions, which stay dark in BOTH themes (deliberately not --surface-inverse, whose whole job is to flip), and moved the footer onto them. Section headings were also using a status hue purely as decoration; they are now a muted on-surface token. The "Open full map" pill was a fixed white background with a themed text colour, so in dark mode it rendered light violet on white at 1.84:1. Both sides are token-driven now. Tabular numerals are now the default for .main rather than per-component opt-in, with prose opting back out — a handful of figures (miniNatPill, compareRowVal, factVal) had been missed by the class-name-based pass. Added a two-theme AA gate over home, rankings and admissions. It waits for `transition: color` to settle first: an earlier measurement pass read mid-transition values and reported seven failures that did not exist at rest. Worth stating plainly — most of what a naive audit flags here is its own artifact, and the check has to account for that to be worth having. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
45a3e7fb8f |
fix(design): correct the font assertion and make the Leaflet overrides win
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 2m10s
Two follow-ups from watching the staging E2E run and re-auditing the live site. The typeface gate failed, but the site was right. Staging now serves "Schibsted Grotesk", "Schibsted Grotesk Fallback", …, -apple-system, sans-serif — exactly what it should. My assertion was /Times|serif$/, and `serif$` matches the tail of `sans-serif`, so a healthy stack could never pass. It now checks the FIRST family in the stack (the only one actually requested) and matches Times as a whole word. Also asserts running prose resolves to Literata, which the earlier version never covered. The Leaflet overrides from the previous fix never took effect. leaflet.css is imported from a client component, so its chunk loads after globals.css; at equal specificity (both bare `.leaflet-container`) the later sheet wins. Verified on the live page: my rule sat at stylesheet index 0, Leaflet's at index 3, and the attribution link was still #0078A8. Prefixing the overrides with `html` takes them to 0,1,1, which beats a bare class regardless of load order — injecting that on the live page turned the background to rgb(233,232,226) and the link to rgb(88,74,155). The palette gate would have caught this, except it skipped everything inside .leaflet-container to avoid flagging OSM tile imagery — which also skipped Leaflet's own chrome. Narrowed the exemption to .leaflet-tile-pane so the map controls are now covered. Also dropped the duplicated family name from the font tokens: next/font already expands the variable to the family plus its metric-matched fallback, so the stack was naming Schibsted Grotesk twice. It looked like a safety net but wasn't — an unresolvable var() with no fallback invalidates the whole declaration, so the literal after it never gets a turn. Verified against live staging: body and headings resolve to Schibsted Grotesk, prose to Literata, the search input is --text-primary rather than pure black, and the higher-specificity Leaflet rules win when injected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2433101fa0 |
fix(design): resolve the font tokens, and pull form controls and map chrome onto the palette
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m3s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 50s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m22s
Staging audit of the Cohort identity found the whole site rendering in Times. Root cause: next/font's variable classes were on <body>, while globals.css declares --font-display/--font-ui/--font-prose on :root as `var(--font-schibsted), ...`. A custom property's var() references resolve on the element that declares it, so at :root --font-schibsted was undefined, --font-display computed to the guaranteed-invalid value, and every font-family referencing it fell back. Nothing threw and the build was green — the only symptom was visual. Verified the mechanism in a browser both ways round: class on <body> gives an empty token and a Times body font; class on <html> resolves to Schibsted Grotesk. The classes now sit on <html>. Two colour escapes from the same audit: * Form controls don't inherit font or colour from their parent, so the omni search input and the map's "Open full map" button rendered in the system font at pure black. Nearly invisible against --text-primary in light mode and completely invisible on the dark ground. Added a base inherit rule. * Leaflet ships its own palette — a #ddd tile backdrop, #333 attribution text and a #0078A8 link blue that was the most saturated colour anywhere on the site. The map chrome now uses tokens; the tiles stay as OSM renders them. Three e2e gates added, because the existing suite passed while the site was entirely in Times: * the font tokens resolve to a non-empty value and the applied family is Schibsted, not a serif fallback * no visible element renders in the browser's default black * every rendered colour comes from the token palette — the manual audit, turned into a gate Also made the og:image check environment-relative: metadataBase pins canonical URLs to the production host, so the absolute URL pointed off-environment when the suite ran against staging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8c3a5cc4e9 |
fix(design): keep below/attention off the brand hue, harden the share-card fonts
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 1m8s
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 <noreply@anthropic.com> |
||
|
|
8ab0ac0a04 |
feat(design): adopt the Cohort identity — new palette, type, mark and dark theme
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m6s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 12s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 3m13s
Implements the direction agreed from the identity board: Route C ("Cohort")
with the paper ground from C1, the Schibsted Grotesk / Literata pairing from
C2, and the iris accent from C3. Dark theme is in scope from the start rather
than retrofitted.
The audit found three things wrong beyond taste:
* No brand asset set. og:image was absent entirely, so every link shared into
a class WhatsApp group rendered as a bare grey card. apple-touch-icon pointed
at an SVG, which iOS ignores, and the manifest shipped no PNGs, so Android
installs had no icon. The header mark and the favicon had also drifted into
two different logos.
* No colour discipline. --primary and --trend-down were the same coral, so the
main CTA and "below average" shared a hue. 58 distinct hex values were spread
across component CSS, and the chart palette was still Chart.js's stock demo
colours.
* A dark theme that was declared but never built — themeColor announced a dark
variant with no dark styling behind it.
What changed:
Colour now has exactly three jobs that never borrow each other's hues: brand
(iris) for interactive and identity, status (teal/amber) for above/below a
comparison point, and phase for categories. Teal/amber rather than green/red
keeps the above/below signal readable for every form of colour blindness.
Every chromatic literal in component CSS is now a token, and the JS-painted
surfaces (Chart.js, Leaflet) read the tokens through lib/theme so they follow
the theme instead of ignoring it.
The mark is the five-bar cohort spread — the same object as the distribution
strip inside a school row, built from opacity steps so it inverts cleanly.
components/Logo.tsx is the single source; the favicon, apple-icon and share
card all derive from its geometry.
globals.css drops 123 dead global classes left over from the vanilla-JS app
(only the btn family, .skip-link and .main were still referenced), along with
the noise overlay. It also gains prefers-reduced-motion support, which was
missing entirely, and a type scale so the 54 ad-hoc font sizes have somewhere
to converge.
Verified: tsc clean, 159 unit tests pass, production build succeeds and
prerenders /icon.svg, /apple-icon and /opengraph-image. Three e2e journeys
added for the asset set, the themeColor/background match, and the dark theme
actually repainting — all silent failures that nothing on the page reveals.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
b0c5b6bb57 |
fix(compare): anchor grade-5 & EBacc rows; restore secondary-button outline
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m4s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 48s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 8s
SF1 (expert sign-off): the secondary academics 'Grade 5+ in English & maths' and 'EBacc entry' rows rendered as bare numbers — the one place the 'every number anchored against the England average' promise broke. Both now show the official England anchor (already in the payload) + an Above/Close/Below verdict chip, matching Attainment 8 (which also gains a chip for consistency). Button affordance: globals.css has a duplicate .btn block whose 'border: none' overrode the base '1px solid transparent', so .btn-secondary/.btn-active's 'border-color' had no width — every outline button (the modal's '+ Compare', search-result '+ Compare', 'Comparing') rendered as borderless teal text. Give the outline variants the full 'border' shorthand so the outline renders regardless of the clobbered base. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB |
||
|
|
a1fa4fe874 |
fix(a11y): WCAG AA colour contrast across every route (P0.1)
Live axe-core inventory found 13 failing fg/bg pairs (3.12:1 coral text/ fills, 4.0-4.5 teal near-misses, 2.7 gold badge, 1.9 chart-colour text). All replacement values validated ≥4.5:1 against every background they sit on: - --accent-coral-dark deepened to #b04a2e and used for coral text roles (nav active, back/map links, kickers, chips) and coral fills under white text (btn-primary, phase tabs, section-nav compare, chart chips); new --accent-coral-darker #9c3f26 for their hovers. Decorative coral (pins, bars, borders, focus ring) keeps the brand #e07256. - --accent-teal darkened to #296f6f (rankings values, metric rows, ofsted grades, eyebrows all pass). - Ofsted green #3c8c3c→#2f7a2f; off-token gold #b8920e→--accent-gold-text. - Gender-split pink text #b45778→#a04a68; admissions tip numerals to a 3.8:1 muted brown (28px display text). - New CHART_TEXT_COLORS: AA-dark counterparts of the Chart.js series palette for compare-card values (swatch dots keep true series colour). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9133ecdcd4 |
feat(mobile): iOS polish — theme-color, safe-area, dvh, tap-highlight
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 52s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
MOB-19: Add a viewport Viewport export with viewportFit: 'cover' and themeColor entries for light (#faf7f2) / dark (#1a1612), plus the appleWebApp metadata for the home-screen status bar style and title. Manifest's stale #3b82f6 theme_color updated to match brand cream. MOB-20: Apply env(safe-area-inset-*) to the sticky chrome — the top header gets max(padding, inset-left/right) so the logo and tab links clear the notch in landscape; the bottom tab bar already had inset-bottom and now also gets inset-left/right. MOB-21: Replace 100vh with 100dvh in body min-height, modal max-heights, the map view container, and the fullscreen map. Older engines fall back via the duplicated vh declaration. MOB-22: Set -webkit-tap-highlight-color: transparent on body to suppress the iOS Safari grey flash; add a generic touch-pointer :active rule (opacity 0.7) so taps still register visually on plain anchors and bare buttons. Components with their own :active styling are unaffected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2a8ff29ccd |
feat(nav): mobile bottom tab bar and ≥44px logo tap target
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 49s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 56s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 2m6s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
MOB-01: At ≤640px the top inline nav is hidden and replaced by a fixed bottom tab bar (Search / Compare / Rankings / Admissions) with icon + label, 56px tap targets, env(safe-area-inset-bottom) padding, and the Compare count rendered as a badge on the icon. Eliminates horizontal page overflow on every route (docW was 401–429 at vw 390; now docW === vw). MOB-02: Logo link gains a padded hit area so the touch target is ≥44×44 (was 36×36), without resizing the visual mark. ComparisonToast lifted above the new bottom bar on mobile so the two do not stack on top of each other. body gets a bottom padding equal to the bar height + safe-area inset so page content is never hidden. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ed244ef743 |
fix(mobile): address iPhone layout issues across rankings, detail, compare
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 14s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 45s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- rankings: hide Type/Action columns on mobile so metric value stays visible; ensure filter selects and table wrapper stay within viewport - school detail: add min-width:0 / max-width:100% containment so internal overflow-x wrappers actually clip rather than pushing the page wider; explicit line-height on Ofsted grade badges to fix glyph clipping - compare: sticky first column on the Detailed Comparison table so the Year labels remain visible while horizontally scrolling school columns - search: shorten placeholder to "School name or postcode" so it fits mobile input width - globals: overflow-x:clip safety net on .main wrapper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
4db36b9099 |
feat(ui): add phase indicators to school list rows
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Add coloured left-border and phase label pill to visually differentiate school phases (Primary, Secondary, All-through, Post-16, Nursery) in search result lists. Colours are accessible (WCAG AA) and don't clash with existing Ofsted/trend semantic colours. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
8aca0a7a53 |
feat(ui): site-wide UX/UI audit — unified buttons, tokens, accessibility
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 35s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m10s
Build and Push Docker Images / Build Integrator (push) Successful in 57s
Build and Push Docker Images / Build Kestra Init (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
- Add shared button system (.btn-primary/secondary/tertiary/active) in globals.css
- Replace 40+ hardcoded rgba() values with design tokens across all CSS modules
- Add skip link, :focus-visible indicators, and ARIA landmarks
- Standardise button labels ("+ Compare" / "✓ Comparing") across all components
- Add collapse/minimize toggle to ComparisonToast
- Fix heading hierarchy (h3→h2 in ComparisonView)
- Add aria-live on search results, aria-label on trend SVGs
- Add "Search" nav link, fix footer empty section, unify max-widths
- Darken --text-muted for WCAG AA compliance (4.6:1 contrast ratio)
- Net reduction of ~180 lines through button style deduplication
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||
|
|
c0f44cd29d |
Fix header z-index to prevent map overlap when scrolling
Increased header z-index from 100 to 1000 to ensure it stays above Leaflet map elements (which typically use z-index 400-600). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
85709d99ca |
Condense spacing throughout the website for denser layout
Reduced padding, margins, and gaps across all components: - Header: smaller logo, tighter navigation - FilterBar: compact hero, smaller inputs and toggles - SchoolCard: reduced padding, smaller fonts and metrics - HomeView: tighter grid gaps, smaller section headers - Map view: condensed compact list items Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
18964a34a2 |
Add visual polish and micro-interactions for editorial feel
Phase 1 - Critical Fixes: - EmptyState: warm palette, coral button, Playfair Display title - Pagination: design system colors, coral active state - LoadingSkeleton: warm shimmer with coral tint Phase 2 - Signature Patterns: - Navigation: sliding underline hover effect on links - globals.css: increased noise texture opacity for paper feel - RankingsView: alternating row backgrounds - HomeView: decorative coral bar under section headings Phase 3 - Polish: - SchoolCard: SVG trend icons replacing unicode arrows - RankingsView: styled metallic rank badges replacing emoji medals Phase 4 - Micro-interactions: - Navigation badge: pop animation when count changes - HomeView grid: staggered entry animation for cards Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
2e62853b70 |
Fix: Add missing CSS variables for Add to Compare button
The "Add to Compare" button on individual school pages was invisible because the CSS variables --primary, --primary-dark, --success, and --border-light were not defined in globals.css. Added these variables mapped to the existing color palette: - --primary: coral accent (#e07256) - --primary-dark: dark coral (#c45a3f) - --success: teal accent (#2d7d7d) - --border-light: border color (#e5dfd5) The button was already in the DOM but had no background color. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
ef4932b553 |
Match original warm editorial design
- Copy complete original styles.css (1900+ lines) to globals.css - Add Google Fonts (DM Sans and Playfair Display) via next/font - Use CSS variables for fonts - Restore warm color palette (#faf7f2 bg, coral/teal accents) - Restore noise overlay texture - Restore all original animations and transitions - Match original card styles, buttons, modals Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
ff7f5487e6 |
Complete Next.js migration with SSR and Docker deployment
- Migrate from vanilla JavaScript SPA to Next.js 16 with App Router - Add server-side rendering for all pages (Home, Compare, Rankings) - Create individual school pages with dynamic routing (/school/[urn]) - Implement Chart.js and Leaflet map integrations - Add comprehensive SEO with sitemap, robots.txt, and JSON-LD - Set up Docker multi-service architecture (PostgreSQL, FastAPI, Next.js) - Update CI/CD pipeline to build both backend and frontend images - Fix Dockerfile to include devDependencies for TypeScript compilation - Add Jest testing configuration - Implement performance optimizations (code splitting, caching) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |