Commit Graph
10 Commits
Author SHA1 Message Date
TudorandClaude Opus 5 4043270a77 fix(home): stop implying we are official, and fix the mobile hero and search
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 48s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m6s
Three things, all reported from the live mobile view.

WE ARE NOT AN OFFICIAL SERVICE

The value prop read "Official & trusted", whose grammatical subject is this
site — it reads as a claim that schoolcompare is an official service. It is
not; it is an independent site that republishes official figures. Now "Built
on official data", which describes the data instead.

Checked every other use of the word: all nine describe the data ("official DfE
figures", "the official figure isn't in") and are correct as they stand. That
one title was the only place the site described itself.

The footer now says it outright — "An independent site. Not affiliated with
the Department for Education or Ofsted." — so it appears on every page rather
than being left to inference. An e2e test asserts both halves: that the
statement is present, and that no text presents the site itself as official.

The disclaimer first measured 4.71:1 against a 4.5 floor. That is a fine
margin for decoration and the wrong one for a line whose job is to be legible
to someone checking whether this is a government site; it is now 5.59:1,
quieter than the copy around it by size rather than by contrast.

THE ARTWORK SAT UNDER THE SEARCH ON PHONES

The DOM keeps .heroContent first so the desktop overlay does not depend on
source order, which left the band stranded at the bottom of the panel, reading
as a strip stuck on the end rather than a hero image. `order` moves it above
the copy on phones; it is decorative and aria-hidden, so no reading order
changes. Measured on a 667px viewport — the shortest phone still in use — the
search button lands at 418px, comfortably inside the fold.

THE SEARCH INPUT WAS UNUSABLE ON PHONES

"Search schools" is a fixed 134px with white-space: nowrap, so it took 48% of
the row. Measured on staging:

  390px   input text space 102px   placeholder needs 194px
  360px                     72px
  320px                     32px

At 320px you could not see what you were typing. The button now wraps to its
own full-width row below 480px, which fixes 360px and up — 390px goes from
102px to 226px of text space. The pill stays a single element, so it keeps its
border, shadow and :focus-within ring, and the button gains a full-width tap
target.

320px is still ~30px short. Closing it needs a shorter placeholder, which three
other tests match on by exact string; left alone deliberately rather than
churn them for a width that is effectively gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 22:59:12 +01:00
TudorandClaude Opus 5 97ac5c9cef fix(a11y): clear the four AA failures blocking the staging E2E gate
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 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 5m29s
Main's staging E2E has been red since #90. Two of the four failures were the
staging container serving a stale image at the moment the gate ran — apple-icon
and the header lockup both check out against staging now. The other two were
real, and both are the same mistake: a colour pairing verified against one
ground while the element sits on another.

  * .heroEyebrow  --brand on its own 10% tint over Sand is 4.19:1. The token
    clears AA on Sand, but the tint darkens the ground under the label, and
    that composite was never the thing measured. --brand-strong is 5.85:1.

  * .hiwVisual    The preview panel was grounded in Sand while everything
    inside it is a translucent status tint — and those tints are specced to
    clear AA over --bg-card. Over Sand the report-card chips landed at 4.22:1
    and 4.29:1. The ratio depended on a background two levels up. Moving the
    panel to the card surface puts them at 5.69:1 and 5.81:1; a border keeps
    it reading as an inset frame now that panel and card share a colour.

  * Footer .sectionTitle  --sage flips with the theme; the footer band does
    not (it is teal in both). So the pairing only held in one of them — the
    dark sage measured 4.08:1, on every page. Now --on-sunken-muted, which is
    what the --on-sunken-* family exists for, as Footer.tsx's own header
    comment already says.

  * .compareHeadLabel  --text-muted on the header row's tint is 4.45:1 in the
    dark theme. Under by a hair, same cause. Now --text-secondary.

The harness gained the footer, because it had no footer and so could not have
caught the one failure that appeared on all three pages. Rewriting fragments
now uses each CSS module's own hash — the footer rewritten with HomeView's
prefix renders unstyled, which would have made any contrast measured on it
meaningless while still reporting a number.

Verified: 0 AA failures in both themes across the assembled page, measured with
the same probe the e2e test uses, against the real compiled CSS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 18:08:38 +01:00
Tudor b11ee3c8de feat(brand): use the supplied logo artwork instead of a reconstruction
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) Successful in 2m7s
The mark shipped so far was my SVG approximation, and it was wrong: the real
mark is a teardrop pin with a white window and a path flowing out of its base,
carrying three leaves — not a circle with a separate tail.

Both colourways are extracted from the supplied sheet, which has a genuinely
transparent background, so these are the artwork rather than a trace:

  public/brand/mark.png          teal pin, white window and path, green leaves
  public/brand/mark-on-dark.png  white pin with the counter knocked through

Two colourways are needed, not one. Rendered against every real ground, the
teal pin holds up on Warm White, white cards, Sand and both dark-theme grounds
— but it vanishes on the teal footer band, where only the white window
survives. The header therefore serves the teal artwork and swaps to the
on-dark artwork for dark-theme viewers through a <picture> source, needing no
JavaScript; the footer forces on-dark, because its band is teal in both
themes.

Everything downstream now derives from those two files: the favicon
(app/icon.png, replacing icon.svg), the touch icon (app/apple-icon.png,
replacing the generated apple-icon.tsx), the three PWA rasters, and the share
card, which reads public/brand/mark.png off disk so it can never drift from
the header. The logo sizing CSS keyed off a square box, which would have
squashed a 153:189 artwork — height now drives and width follows.

The wordmark stays live text in Manrope. The sheet's wordmark is a raster with
visible edge fringing, and the written style guide specifies Manrope; live
text also stays selectable, scales cleanly and recolours with the theme.

KNOWN LIMITATION: the largest instance on the sheet is 153×189. That is ample
for the header at 38px, the favicon and the share card, but short of the 512px
PWA icon, which is upscaled and slightly soft. A vector would fix it and is a
single swap — every consumer goes through components/Logo.tsx or public/brand.

Verified: tsc clean, 159/159 tests, build green, /icon.png and /apple-icon.png
emit as static routes, and the header, footer, share card and all five icons
were rendered and inspected in both themes.
2026-08-08 22:22:58 +01:00
Tudor accc562b90 feat(brand): adopt the schoolcompare identity across the site
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 13s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 4m29s
Implements the supplied brand guideline: the pin-and-leaf mark, the lowercase
schoolcompare wordmark, Manrope for headings and Inter for everything a person
reads a number from, the deep-teal/coral/mustard/sage palette, and the rounder
geometry the guideline asks for.

The landing hero is rebuilt to the concept: a Sand panel carrying the
proposition, the search and a trust line, with the brand landscape bleeding to
the panel's right edge, followed by the four value props.

Concept vs reality — where the guideline describes something this product does
not have, the real feature takes its place and the decision is recorded in a
code comment next to it:

- The Thriving Score is not implemented. Four of its five sub-scores (pastoral,
  enrichment, community, facilities) have no source in DfE or Ofsted data, and
  its band names are Ofsted's own, which would sit beside the real Ofsted grade
  and be indistinguishable from it.
- Guides, About, Favourites and Sign in are not added to the nav — there are no
  such pages and no accounts system. Rankings and Admissions, which the
  guideline omits, keep their places because they exist.
- The fourth value prop is "Never miss a deadline" rather than "Save & revisit",
  which would need shortlisting.

Two deviations from the supplied swatches, both forced by contrast and both
confined to text and fills. Coral #F97360 under a white label measures 2.75:1
against a 4.5 floor, so --action is a darkened #BE3C27 and the original coral
survives as --coral for tints, borders and illustration. Mustard #F2C94C is
1.5:1 as text, so --mustard-ink carries labels while --mustard stays exact for
fills and rings. Every token clears AA on all three light grounds; Sand is the
binding one, so several values sit a step darker than white alone would need.

The guideline is light-only, but the site already ships a dark theme, so it is
rebuilt from the same tokens rather than dropped.

Verified: tsc clean, 159/159 unit tests, production build green, and the
generated share card and app icon render from the new mark. The hero and the
site chrome were rendered from the compiled CSS and the prerendered markup to
confirm Manrope and Inter actually resolve — a font token that silently falls
back has shipped here before.
2026-08-07 18:05:49 +01:00
TudorandClaude Opus 5 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>
2026-08-06 15:38:31 +01:00
TudorandClaude Opus 5 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>
2026-08-06 12:13:04 +01:00
Tudor SitaruandClaude Opus 4.6 ac2d64caaf feat(home): implement redesigned homepage
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 13s
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 13s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
- Hero: Playfair heading with coral italic accent, teal eyebrow pill,
  richer sub-copy describing both primary and secondary coverage
- Discovery: geolocation "Schools near me" button (reverse-geocodes via
  postcodes.io → /?postcode=…&radius=1), plus Start exploring chips
  linking to /rankings and /compare
- How it works: 3-card grid showing miniature real-UI previews for
  Performance (primary SATs cascade + secondary Att8 bar), Ofsted
  inspection card, and side-by-side Compare table
- Editorial: text column + factbox (totalSchools, LA count, coverage
  dates) rendered inside a white card below the how-it-works section
- Footer: expanded to 3 columns (brand blurb, Product, Resources);
  links updated to / /rankings /compare and real gov.uk/ofsted URLs
- All new sections visible only on landing (no search active)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 21:02:18 +01:00
tudorandClaude Opus 4.6 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>
2026-03-25 20:28:03 +00:00
TudorandClaude Opus 4.5 c2ec067495 Apply warm editorial design system across all components
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m19s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m22s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Replace generic blue/gray colors with warm editorial palette:
- Navigation: coral active states, branded logo colors
- Footer: navy background, gold section titles
- FilterBar: coral search button and focus states
- SchoolCard: coral left accent on hover, teal/coral buttons
- HomeView: gradient hero section, Playfair Display headings
- RankingsView: gold top-3 highlights, warm table styling
- ComparisonView: teal card borders, coral buttons

Consistent use of CSS variables and Playfair Display serif font
for headings throughout.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 10:11:23 +00:00
TudorandClaude Sonnet 4.5 ff7f5487e6 Complete Next.js migration with SSR and Docker deployment
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m26s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 1m48s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
- 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>
2026-02-02 20:34:35 +00:00