Compare commits

..
Author SHA1 Message Date
Tudor 9ee4d45a94 fix(brand): give both logo colourways one canvas so the aspect hint is right
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 10s
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 9s
LogoMark derived width from a single hardcoded 153/189, taken from mark.png,
while mark-on-dark.png was 98×112. Both call sites size the mark with
height:100%/width:auto, so the rendered size came out right in the end — but
the width/height attributes are the pre-load aspect hint, so the on-dark
artwork reserved the wrong box and reflowed on load. Any future call site
without that CSS would have rendered it stretched outright.

Per-variant attributes would not have fixed it. variant="auto" is a single
<img> whose srcset swaps the file underneath it, so one set of attributes has
to serve both colourways; the only correct fix is for the two files to share
a ratio.

They also had different artwork ratios (0.810 against 0.833) and different
padding, which the review did not reach: the logo changed size slightly
whenever the OS theme flipped. Both are now written onto one 176×208 canvas
with the artwork at the same height, centred, so they are drop-in swappable.

Measured on the built output, header mark, before → after:
  light   attr 31×38, rendered 30.75×38   →   attr 32×38, rendered 32.15×38
  dark    attr 31×38, rendered 33.25×38   →   attr 32×38, rendered 32.15×38
A 2.25px reflow and a cross-theme size jump both become 0.15px of rounding.

The share card reads the same file and had its own hardcoded 45×56, which the
re-crop would have stretched; it is now 47×56, with a comment tying it to the
canvas. The comment on ASPECT records that re-cropping either file means
re-normalising both.

Verified: tsc clean, 159/159 tests, build green, and both files report
naturalWidth 176 × naturalHeight 208 in the browser.
2026-08-08 22:39:28 +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 eecb84fb35 Merge pull request 'feat(brand): adopt the schoolcompare identity across the site' (#89) from feat/schoolcompare-brand into main
Stage (build -> staging -> E2E gate) / Build Backend (FastAPI) (push) Successful in 12s
Stage (build -> staging -> E2E gate) / Build Frontend (Next.js) (push) Successful in 49s
Stage (build -> staging -> E2E gate) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 13s
Stage (build -> staging -> E2E gate) / Deploy to Staging (push) Successful in 1s
Stage (build -> staging -> E2E gate) / E2E Journeys against Staging (push) Failing after 1m6s
Reviewed-on: #89
2026-08-07 17:58:55 +00:00
4 changed files with 23 additions and 3 deletions
+3 -1
View File
@@ -31,6 +31,8 @@ async function font(file: string) {
* The mark, as a data URI. Satori has no access to the public/ URL space, so
* the artwork is read off disk and inlined. This is the same file the header
* serves — public/brand/mark.png — so the card can never drift from the site.
* The 47×56 box below is that file's 176:208 ratio; re-crop the artwork and
* this has to move with it, or Satori will stretch it.
*/
async function markDataUri() {
const png = await readFile(join(process.cwd(), 'public', 'brand', 'mark.png'));
@@ -72,7 +74,7 @@ export default async function OpengraphImage() {
{/* Lockup */}
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={mark} width={45} height={56} alt="" />
<img src={mark} width={47} height={56} alt="" />
<div style={{ display: 'flex', fontSize: 34, fontWeight: 700, letterSpacing: '-0.04em' }}>
<span style={{ color: INK }}>school</span>
<span style={{ color: TEAL }}>compare</span>
+20 -2
View File
@@ -12,6 +12,10 @@
* ground is teal, because there the teal pin's silhouette
* disappears and only the white window survives.
*
* Both are written onto one 176×208 canvas with the artwork at the same
* height, so they are drop-in swappable — see ASPECT below for why that
* matters rather than merely being neat.
*
* `variant="auto"` serves the on-dark artwork to dark-theme viewers via a
* <picture> source, so this needs no JavaScript and no client boundary.
*
@@ -29,8 +33,22 @@
const MARK_LIGHT = '/brand/mark.png';
const MARK_ON_DARK = '/brand/mark-on-dark.png';
/** Intrinsic aspect of the artwork, used to derive width from height. */
const ASPECT = 153 / 189;
/**
* Intrinsic size of the artwork files, used to derive width from height.
*
* Both colourways are deliberately written onto the SAME canvas at the same
* artwork height, so this one ratio is correct for either of them. That is not
* tidiness — it is required. `variant="auto"` renders a single <img> whose
* srcset swaps the file underneath it, so the width/height attributes are
* shared by both colourways and cannot be varied per file. When the two were
* tightly cropped they had different ratios (0.810 and 0.875) and different
* padding, which meant a wrong aspect hint before load — a reflow on load
* under `width: auto` — and a visible jump in logo size whenever the OS theme
* flipped. Re-crop or replace one file and you must re-normalise both.
*/
const ART_WIDTH = 176;
const ART_HEIGHT = 208;
const ASPECT = ART_WIDTH / ART_HEIGHT;
interface LogoMarkProps {
className?: string;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 54 KiB