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.
147 lines
5.8 KiB
TypeScript
147 lines
5.8 KiB
TypeScript
/**
|
|
* Brand illustration — the landing hero's landscape.
|
|
*
|
|
* Guideline rules for illustration: no people or faces, soft shapes, rounded
|
|
* corners, minimal detail, maximum clarity. The scene reads as "the journey to
|
|
* a school" — a winding path up through layered hills to a small schoolhouse,
|
|
* with a pin marking where you are.
|
|
*
|
|
* Deliberately server-safe: no 'use client', no hooks, no event handlers, so
|
|
* it renders in the RSC pass and never reaches the client bundle.
|
|
*/
|
|
|
|
/*
|
|
* These hex values mirror the brand palette in app/globals.css — --sky,
|
|
* --sage, --brand, --coral, --sand and the tints/shades the scene needs
|
|
* around them. They are written literally on purpose: an illustration is
|
|
* artwork, not themed UI. Recolouring it per theme would break the picture,
|
|
* so it keeps one fixed, light palette in both themes (it sits on a Sand
|
|
* panel in light mode and reads as a framed image in dark mode).
|
|
*/
|
|
const SKY_HIGH = '#DAEDF8';
|
|
const SKY_LOW = '#EFF8FB';
|
|
const CLOUD = '#FFFFFF';
|
|
|
|
const HILL_FAR = '#D6EDE2'; // --sage, lightened
|
|
const HILL_MID = '#A7D7C5'; // --sage, exact
|
|
const HILL_NEAR = '#7FC3AC';
|
|
const HILL_FRONT = '#5BA88F'; // --sage darkened toward --brand
|
|
|
|
const PATH_FILL = '#FAF6EE'; // --sand, lightened
|
|
const PATH_EDGE = '#E6DAC2'; // --sand, darkened
|
|
|
|
const SCHOOL_WALL = '#FCE8C3'; // cream
|
|
const SCHOOL_ROOF = '#F0A868'; // warm orange
|
|
const SCHOOL_DOOR = '#0F766E'; // --brand, exact
|
|
const SCHOOL_WINDOW = '#C7EBF5'; // --sky, exact
|
|
const SCHOOL_CLOCK = '#FAFAF8'; // --bg-primary (Warm White)
|
|
const FLAGPOLE = '#8FA3AE';
|
|
const PENNANT = '#F97360'; // --coral, exact
|
|
|
|
const TREE_DARK = '#2E7D6B';
|
|
const TREE_MID = '#3E8C74';
|
|
const TREE_LIGHT = '#4A9E85';
|
|
const TREE_PALE = '#7FC3AC';
|
|
|
|
const PIN = '#F97360'; // --coral, exact
|
|
const PIN_EYE = '#FFFFFF';
|
|
|
|
/** Scoped so the gradient id can't collide with another inline SVG. */
|
|
const SKY_GRADIENT_ID = 'sc-hero-sky';
|
|
|
|
export function HeroIllustration() {
|
|
return (
|
|
<svg viewBox="0 0 540 520" preserveAspectRatio="xMidYMax slice" aria-hidden="true" focusable="false">
|
|
<defs>
|
|
<linearGradient id={SKY_GRADIENT_ID} x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0" stopColor={SKY_HIGH} />
|
|
<stop offset="1" stopColor={SKY_LOW} />
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
{/* Sky */}
|
|
<rect width="540" height="520" fill={`url(#${SKY_GRADIENT_ID})`} />
|
|
|
|
{/* Two soft clouds, each a pair of overlapping ellipses */}
|
|
<g fill={CLOUD} opacity="0.85">
|
|
<ellipse cx="96" cy="86" rx="32" ry="15" />
|
|
<ellipse cx="122" cy="79" rx="23" ry="18" />
|
|
<ellipse cx="418" cy="58" rx="27" ry="13" />
|
|
<ellipse cx="439" cy="52" rx="19" ry="14" />
|
|
</g>
|
|
|
|
{/* Four layered rolling hills, palest and furthest first */}
|
|
<path d="M0 300 C 90 264 168 294 246 276 C 330 256 410 282 540 254 L540 520 L0 520 Z" fill={HILL_FAR} />
|
|
<path d="M0 344 C 104 308 186 340 268 324 C 356 306 452 332 540 306 L540 520 L0 520 Z" fill={HILL_MID} />
|
|
<path d="M0 408 C 118 378 214 410 306 392 C 400 374 470 398 540 382 L540 520 L0 520 Z" fill={HILL_NEAR} />
|
|
<path d="M0 468 C 130 446 236 474 340 458 C 432 444 486 462 540 452 L540 520 L0 520 Z" fill={HILL_FRONT} />
|
|
|
|
{/* Winding cream path, with a dotted edge for a little texture */}
|
|
<path
|
|
d="M214 520 C 202 466 254 448 276 424 C 298 400 274 378 254 366 C 230 352 244 328 276 316"
|
|
stroke={PATH_FILL}
|
|
strokeWidth="18"
|
|
fill="none"
|
|
strokeLinecap="round"
|
|
/>
|
|
<path
|
|
d="M214 520 C 202 466 254 448 276 424 C 298 400 274 378 254 366 C 230 352 244 328 276 316"
|
|
stroke={PATH_EDGE}
|
|
strokeWidth="18"
|
|
fill="none"
|
|
strokeLinecap="butt"
|
|
strokeLinejoin="round"
|
|
strokeDasharray="0.5 26"
|
|
opacity="0.5"
|
|
/>
|
|
|
|
{/* The school at the top of the path */}
|
|
<g transform="translate(276 232)">
|
|
<rect x="12" y="34" width="98" height="58" rx="4" fill={SCHOOL_WALL} />
|
|
<path d="M2 36 L61 6 L120 36 Z" fill={SCHOOL_ROOF} />
|
|
<rect x="54" y="62" width="20" height="30" rx="2" fill={SCHOOL_DOOR} />
|
|
<g fill={SCHOOL_WINDOW}>
|
|
<rect x="24" y="46" width="16" height="14" rx="2" />
|
|
<rect x="86" y="46" width="16" height="14" rx="2" />
|
|
<rect x="24" y="70" width="16" height="12" rx="2" />
|
|
<rect x="86" y="70" width="16" height="12" rx="2" />
|
|
</g>
|
|
{/* Flagpole and coral pennant */}
|
|
<rect x="59.5" y="-14" width="2.5" height="20" fill={FLAGPOLE} />
|
|
<path d="M62 -13 L78 -8.5 L62 -4 Z" fill={PENNANT} />
|
|
{/* Clock in the gable */}
|
|
<circle cx="61" cy="24" r="6.5" fill={SCHOOL_CLOCK} />
|
|
<path d="M61 20.5 v7 M57.5 24 h7" stroke={SCHOOL_DOOR} strokeWidth="1.5" strokeLinecap="round" />
|
|
</g>
|
|
|
|
{/* Scattered rounded trees */}
|
|
<g fill={TREE_DARK}>
|
|
<circle cx="104" cy="372" r="27" />
|
|
<circle cx="136" cy="386" r="19" />
|
|
<circle cx="470" cy="336" r="23" />
|
|
</g>
|
|
<g fill={TREE_LIGHT}>
|
|
<circle cx="78" cy="394" r="21" />
|
|
<circle cx="492" cy="358" r="17" />
|
|
<circle cx="176" cy="420" r="18" />
|
|
</g>
|
|
<g fill={TREE_MID}>
|
|
<ellipse cx="44" cy="336" rx="15" ry="22" />
|
|
<ellipse cx="508" cy="296" rx="13" ry="18" />
|
|
<ellipse cx="150" cy="342" rx="12" ry="17" />
|
|
</g>
|
|
<g fill={TREE_PALE} opacity="0.9">
|
|
<circle cx="410" cy="420" r="16" />
|
|
<circle cx="438" cy="430" r="12" />
|
|
<circle cx="120" cy="470" r="14" />
|
|
</g>
|
|
|
|
{/* Location pin — you are here, the path leads up to the school */}
|
|
<g transform="translate(196 372)">
|
|
<path d="M21 48 C21 48 42 26 42 15.5 A21 21 0 1 0 0 15.5 C0 26 21 48 21 48 Z" fill={PIN} />
|
|
<circle cx="21" cy="15.5" r="8" fill={PIN_EYE} />
|
|
</g>
|
|
</svg>
|
|
);
|
|
}
|