feat(ui): add the ToBePlayed logo and name
Build, Test & Push Frontend / quality-check (push) Successful in 14s
Build, Test & Push Frontend / docker (push) Successful in 22s

The logo is a traced SVG whose colors follow the accent color;
violet shows it as designed. Favicon, PWA and Apple icons use
it with fixed colors. The header and landing page say
ToBePlayed instead of Game Log. Below 380 px the header shows
only the logo, and the home link keeps its name for screen
readers.
This commit is contained in:
lobo committed 2026-09-26 07:05:57 +02:00
1 parent 25d2f0d0bd
commit c52c5e02d2
13 files changed
+195 -16

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
import {useId} from 'react';
import {BODY, CHECKLIST, DPAD, OUTLINE, PROGRESS_BAR} from './logoPaths.js';
// Each color is [themed CSS, fixed hex]. The hex is the logo as designed; it also stands in where a browser
// cannot read color-mix, which would otherwise make that stop black.
const violet = ['var(--logo-violet, #7467f9)', '#7467f9'];
const blue = ['var(--logo-blue, #0192ef)', '#0192ef'];
const teal = ['var(--logo-teal, #17c7a4)', '#17c7a4'];
const mix = ([a], [b], percentOfB, hex) => [`color-mix(in oklch, ${a}, ${b} ${percentOfB}%)`, hex];
function Gradient({id, from, to, stops}) {
return (
<linearGradient id={id} gradientUnits="userSpaceOnUse" x1={from[0]} y1={from[1]} x2={to[0]} y2={to[1]}>
{stops.map(([offset, [color, hex]]) => <stop key={offset} offset={offset} stopColor={hex} style={{stopColor: color}}/>)}
</linearGradient>
);
}
/**
* The ToBePlayed logo. Its three colors come from --logo-violet, --logo-blue and --logo-teal, which style.css sets
* per accent color.
*/
export default function Logo({className, title}) {
// Every copy on the page needs its own ids, or a hidden copy can take the gradients of a visible one with it.
const id = useId().replace(/[^a-zA-Z0-9_-]/g, '');
const ref = (name) => `${name}-${id}`;
const url = (name) => `url(#${ref(name)})`;
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="255 225 690 445" className={className}
role={title ? 'img' : undefined} aria-label={title} aria-hidden={title ? undefined : true}>
<defs>
<clipPath id={ref('outline')}><path d={OUTLINE}/></clipPath>
<Gradient id={ref('ring')} from={[265, 660]} to={[935, 235]}
stops={[[0, violet], [0.15, violet], [0.42, blue], [0.7, teal], [1, teal]]}/>
<Gradient id={ref('list')} from={[674, 326]} to={[525, 489]}
stops={[[0, teal], [0.45, mix(blue, teal, 45, '#1dacad')], [0.7, blue], [1, mix(violet, blue, 50, '#4f6ad1')]]}/>
<Gradient id={ref('dpad')} from={[478, 336]} to={[363, 451]}
stops={[[0, blue], [1, mix(blue, violet, 20, '#2a83e4')]]}/>
<Gradient id={ref('buttons')} from={[843, 326]} to={[708, 463]}
stops={[[0, teal], [1, mix(teal, blue, 25, '#15b4ae')]]}/>
<Gradient id={ref('bar')} from={[476, 610]} to={[723, 610]}
stops={[[0, violet], [0.45, blue], [0.75, mix(blue, teal, 45, '#1dacad')], [1, teal]]}/>
</defs>
<path fill={url('ring')} d={OUTLINE}/>
<path fill="#2d3641" d={BODY}/>
<g fill="#192330" clipPath={url('outline')}>
<rect x="502" y="289" width="195" height="233"/>
<path d="M593.6 250H616.6L588 295H563Z"/>
<path d="M595.3 516H620.3L591.4 560H567.4Z"/>
</g>
<path fill={url('dpad')} d={DPAD}/>
<g fill={url('buttons')}>
<circle cx="775.5" cy="347.5" r="22"/>
<circle cx="729.5" cy="394" r="22"/>
<circle cx="821.5" cy="393.5" r="22"/>
<circle cx="775.5" cy="441" r="22"/>
</g>
<path fill={url('list')} fillRule="evenodd" d={CHECKLIST}/>
<path fill={url('bar')} fillRule="evenodd" d={PROGRESS_BAR}/>
</svg>
);
}