- Restore the SHARP-ER in the homepage hero section - Match the design for the 3-image section in the homepage
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import { Link } from "react-router";
|
|
|
|
import { cx, FOCUS_RING } from "./styles";
|
|
import type { NavSurface } from "./types";
|
|
|
|
/** 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;
|
|
}
|
|
|
|
/**
|
|
* 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 images are decorative. */
|
|
label: string;
|
|
surface: NavSurface;
|
|
/**
|
|
* Run the "-ER" reveal and its breathing loop. Off on the home page, whose
|
|
* hero plays the same reveal once - a second, looping copy beside it is
|
|
* jarring. Anywhere else the two animations run as designed.
|
|
*/
|
|
animate?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
/** The SHARP-ER wordmark, linking home. Sits at the inline start of the bar. */
|
|
export function Brand({ href, label, surface, animate = true, className }: BrandProps) {
|
|
const { base, suffix } = BRAND_LOCKUP[surface];
|
|
|
|
return (
|
|
<Link
|
|
to={href}
|
|
aria-label={label}
|
|
className={cx(
|
|
"inline-flex shrink-0 items-center rounded-control",
|
|
FOCUS_RING,
|
|
className,
|
|
)}
|
|
>
|
|
{/* `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). Without
|
|
`animate` the box and the reveal classes are dropped and the full
|
|
lockup simply sits there. */}
|
|
<span
|
|
style={{ flex: `${suffix.drawnWidth} 1 0%` }}
|
|
className={cx("block min-w-0", animate && "sh-brand-suffix")}
|
|
>
|
|
<img
|
|
src={suffix.src}
|
|
alt=""
|
|
width={suffix.width}
|
|
height={suffix.height}
|
|
loading="eager"
|
|
decoding="async"
|
|
draggable={false}
|
|
className={cx("block h-auto w-full", animate && "sh-brand-suffix__mark")}
|
|
/>
|
|
</span>
|
|
</span>
|
|
</Link>
|
|
);
|
|
}
|