2026-08-06 12:13:04 +01:00
|
|
|
|
/**
|
2026-08-07 18:05:49 +01:00
|
|
|
|
* The schoolcompare mark.
|
2026-08-06 12:13:04 +01:00
|
|
|
|
*
|
2026-08-07 18:05:49 +01:00
|
|
|
|
* A location pin with a leaf growing inside it: where a school is, and a child
|
|
|
|
|
|
* growing there. The pin's tail sweeps left as the guideline's "signature path
|
|
|
|
|
|
* shape" — the journey a parent takes to find the right school — so the mark
|
|
|
|
|
|
* and the illustration style share one gesture.
|
2026-08-06 12:13:04 +01:00
|
|
|
|
*
|
2026-08-07 18:05:49 +01:00
|
|
|
|
* This is the single source for the mark. app/icon.svg, app/apple-icon.tsx and
|
|
|
|
|
|
* app/opengraph-image.tsx all derive from the same geometry (see MARK_PATHS),
|
|
|
|
|
|
* because the header and the favicon had previously drifted into two different
|
|
|
|
|
|
* logos.
|
2026-08-06 12:13:04 +01:00
|
|
|
|
*
|
2026-08-07 18:05:49 +01:00
|
|
|
|
* Drawn on a 48×48 grid. The pin takes the brand hue and the leaf is knocked
|
|
|
|
|
|
* out of it, so the mark needs exactly two colours and works on any ground.
|
2026-08-06 12:13:04 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
2026-08-07 18:05:49 +01:00
|
|
|
|
/** Geometry shared by every rendering of the mark, on a 0 0 48 48 viewBox. */
|
|
|
|
|
|
export const MARK_PATHS = {
|
|
|
|
|
|
/** The path tail, stroked — 8.5 wide with a round cap. */
|
|
|
|
|
|
tail: 'M17 30 C15 35.6 11.6 40.2 7.8 42.8',
|
|
|
|
|
|
/** The pin head. */
|
|
|
|
|
|
head: { cx: 26.5, cy: 18.5, r: 16.5 },
|
|
|
|
|
|
/** Leaf stem, stroked — 2.6 wide with a round cap. */
|
|
|
|
|
|
stem: 'M26.5 29.5 C26.5 25 26.6 21 27 18',
|
|
|
|
|
|
/** Upper leaf, filled. */
|
|
|
|
|
|
leafUpper: 'M27 19.6 C28.2 13.8 32.4 9.8 37.6 9.4 C38 15.6 33.8 20.8 27 19.6 Z',
|
|
|
|
|
|
/** Lower leaf, filled. */
|
|
|
|
|
|
leafLower: 'M26.4 24.6 C25.2 20 21.2 17 16.4 17.1 C16.2 21.8 19.8 25.8 26.4 24.6 Z',
|
|
|
|
|
|
} as const;
|
2026-08-06 12:13:04 +01:00
|
|
|
|
|
2026-08-07 18:05:49 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* The mark as a standalone SVG string.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Satori (next/og) renders `<img>` with a data URI far more reliably than it
|
|
|
|
|
|
* renders inline SVG children, so the generated icon and share card both draw
|
|
|
|
|
|
* the mark this way — from the same geometry as the React component above,
|
|
|
|
|
|
* which is the whole point of MARK_PATHS.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function markSvg(pin: string, leaf: string): string {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">',
|
|
|
|
|
|
`<path d="${MARK_PATHS.tail}" stroke="${pin}" stroke-width="8.5" stroke-linecap="round" fill="none"/>`,
|
|
|
|
|
|
`<circle cx="${MARK_PATHS.head.cx}" cy="${MARK_PATHS.head.cy}" r="${MARK_PATHS.head.r}" fill="${pin}"/>`,
|
|
|
|
|
|
`<path d="${MARK_PATHS.stem}" stroke="${leaf}" stroke-width="2.6" stroke-linecap="round" fill="none"/>`,
|
|
|
|
|
|
`<path d="${MARK_PATHS.leafUpper}" fill="${leaf}"/>`,
|
|
|
|
|
|
`<path d="${MARK_PATHS.leafLower}" fill="${leaf}"/>`,
|
|
|
|
|
|
'</svg>',
|
|
|
|
|
|
].join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The same string as a data URI, ready for `<img src>`. */
|
|
|
|
|
|
export function markDataUri(pin: string, leaf: string): string {
|
|
|
|
|
|
return `data:image/svg+xml;base64,${Buffer.from(markSvg(pin, leaf)).toString('base64')}`;
|
|
|
|
|
|
}
|
2026-08-06 12:13:04 +01:00
|
|
|
|
|
|
|
|
|
|
interface LogoMarkProps {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
/** Rendered size in px. Defaults to inheriting via CSS. */
|
|
|
|
|
|
size?: number;
|
2026-08-07 18:05:49 +01:00
|
|
|
|
/** The pin. Defaults to the brand token so it inverts with the theme. */
|
|
|
|
|
|
pin?: string;
|
|
|
|
|
|
/** The leaf knocked out of the pin. */
|
|
|
|
|
|
leaf?: string;
|
2026-08-06 12:13:04 +01:00
|
|
|
|
title?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-07 18:05:49 +01:00
|
|
|
|
export function LogoMark({
|
|
|
|
|
|
className,
|
|
|
|
|
|
size,
|
|
|
|
|
|
pin = 'var(--brand)',
|
|
|
|
|
|
leaf = 'var(--bg-card)',
|
|
|
|
|
|
title,
|
|
|
|
|
|
}: LogoMarkProps) {
|
2026-08-06 12:13:04 +01:00
|
|
|
|
return (
|
|
|
|
|
|
<svg
|
|
|
|
|
|
className={className}
|
|
|
|
|
|
width={size}
|
|
|
|
|
|
height={size}
|
2026-08-07 18:05:49 +01:00
|
|
|
|
viewBox="0 0 48 48"
|
2026-08-06 12:13:04 +01:00
|
|
|
|
fill="none"
|
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
|
role={title ? 'img' : undefined}
|
|
|
|
|
|
aria-hidden={title ? undefined : true}
|
|
|
|
|
|
aria-label={title}
|
|
|
|
|
|
>
|
|
|
|
|
|
{title ? <title>{title}</title> : null}
|
2026-08-07 18:05:49 +01:00
|
|
|
|
<path
|
|
|
|
|
|
d={MARK_PATHS.tail}
|
|
|
|
|
|
stroke={pin}
|
|
|
|
|
|
strokeWidth={8.5}
|
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<circle cx={MARK_PATHS.head.cx} cy={MARK_PATHS.head.cy} r={MARK_PATHS.head.r} fill={pin} />
|
|
|
|
|
|
<path
|
|
|
|
|
|
d={MARK_PATHS.stem}
|
|
|
|
|
|
stroke={leaf}
|
|
|
|
|
|
strokeWidth={2.6}
|
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<path d={MARK_PATHS.leafUpper} fill={leaf} />
|
|
|
|
|
|
<path d={MARK_PATHS.leafLower} fill={leaf} />
|
2026-08-06 12:13:04 +01:00
|
|
|
|
</svg>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|