feat: animate the logo in the navbar

This commit is contained in:
2026-09-23 22:47:04 +02:00
parent c0daacf1ba
commit 93b2fcb2ae
7 changed files with 346 additions and 305 deletions
+67 -20
View File
@@ -3,21 +3,43 @@ import { Link } from "react-router";
import { cx, FOCUS_RING } from "./styles";
import type { NavSurface } from "./types";
/* Exported from Figma logo/sharp-er (317:5571) and logo/sharp-er-dark
(317:7812). Intrinsic 334x46; the navigation draws it at 100px wide, which
is the designed size in every navigation frame (desktop and mobile). */
const LOGO_SRC: Record<NavSurface, string> = {
light: "/assets/sharp-er-logo.png",
dark: "/assets/sharp-er-logo-dark.png",
};
/** One half of the animated brand lockup, drawn as its own SVG file. */
interface BrandMark {
src: string;
/** Intrinsic size of the file, for the aspect ratio the browser reserves. */
width: number;
height: number;
/** The mark's own drawn width, so the pair flexes to one scale. */
drawnWidth: number;
}
const LOGO_INTRINSIC_WIDTH = 334;
const LOGO_INTRINSIC_HEIGHT = 46;
/**
* The SHARP-ER lockup, split into its two drawn pieces so the "-ER" can slide
* out from behind the P and fade in once the page has settled (Navigation.css).
* Each surface ships its own export of the two pieces: white on the dark
* surface, the two-tone ink on the light surface. Both exports of a piece are
* the exact same box (218x46 and 112x46), so the pair shares one scale; each
* mark takes the share of the row its box gives it, and the suffix rides flush
* against the P's right edge.
*/
const BRAND_LOCKUP: Record<
NavSurface,
{ base: BrandMark; suffix: BrandMark }
> = {
light: {
base: { src: "/assets/sharp-wordmark.svg", width: 218, height: 46, drawnWidth: 218 },
suffix: { src: "/assets/sharp-er-suffix.svg", width: 112, height: 46, drawnWidth: 112 },
},
dark: {
base: { src: "/assets/sharp-wordmark-inverse.svg", width: 218, height: 46, drawnWidth: 218 },
suffix: { src: "/assets/sharp-er-suffix-inverse.svg", width: 112, height: 46, drawnWidth: 112 },
},
};
export interface BrandProps {
/** Locale-prefixed home path, e.g. "/en". */
href: string;
/** Accessible name of the link; the image itself is decorative. */
/** Accessible name of the link; the images are decorative. */
label: string;
surface: NavSurface;
className?: string;
@@ -25,6 +47,8 @@ export interface BrandProps {
/** The SHARP-ER wordmark, linking home. Sits at the inline start of the bar. */
export function Brand({ href, label, surface, className }: BrandProps) {
const { base, suffix } = BRAND_LOCKUP[surface];
return (
<Link
to={href}
@@ -35,16 +59,39 @@ export function Brand({ href, label, surface, className }: BrandProps) {
className,
)}
>
<img
src={LOGO_SRC[surface]}
alt=""
width={LOGO_INTRINSIC_WIDTH}
height={LOGO_INTRINSIC_HEIGHT}
loading="eager"
decoding="async"
draggable={false}
className="block h-auto w-[100px]"
/>
{/* `dir="ltr"` because a Latin wordmark never mirrors; its position on
the bar still follows the page's direction. */}
<span dir="ltr" className="flex w-[100px] items-start">
<img
src={base.src}
alt=""
width={base.width}
height={base.height}
loading="eager"
decoding="async"
draggable={false}
style={{ flex: `${base.drawnWidth} 1 0%` }}
className="block h-auto min-w-0"
/>
{/* The clipping box starts at the P's right edge, so the mark inside
it can slide out from behind the P (Navigation.css). */}
<span
style={{ flex: `${suffix.drawnWidth} 1 0%` }}
className="sh-brand-suffix block min-w-0"
>
<img
src={suffix.src}
alt=""
width={suffix.width}
height={suffix.height}
loading="eager"
decoding="async"
draggable={false}
className="sh-brand-suffix__mark block h-auto w-full"
/>
</span>
</span>
</Link>
);
}