Gold is the primary in both themes, Bricolage Grotesque carries the wordmark, titles and the score, and Movie A / Movie B wear the blue and magenta of three-strip Technicolor on their posters instead of colouring whole rows. The loop closes on screen - connectors light, a gold line draws back to Movie A, the row glows, then the result arrives - in place of confetti, on every play mode, scaled to the chain's length. Every page shares one header and hairline lists; copy is sentence case with one verb per flow, and the Home hero shows a real finished loop. Also clamps link counts at zero: an opponent who had not moved yet showed "-1 links".
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
import { Link } from 'react-router';
|
|
import { ArrowLeft } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface PageHeaderProps {
|
|
title: ReactNode;
|
|
/** One line, kept under 65ch. */
|
|
description?: ReactNode;
|
|
/** Buttons or links that belong to the whole page, right-aligned. */
|
|
actions?: ReactNode;
|
|
back?: { to: string; label: string };
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* The one page-title treatment: display face, left-aligned, no icon, no
|
|
* eyebrow, no rule. A description sits under it in muted ink; page-level
|
|
* actions sit to the right and wrap under on narrow screens.
|
|
*/
|
|
export default function PageHeader({
|
|
title,
|
|
description,
|
|
actions,
|
|
back,
|
|
className,
|
|
}: PageHeaderProps) {
|
|
return (
|
|
<div className={cn('mb-6 space-y-2', className)}>
|
|
{back && (
|
|
<Link
|
|
to={back.to}
|
|
className="inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
>
|
|
<ArrowLeft className="h-4 w-4" aria-hidden="true" />
|
|
{back.label}
|
|
</Link>
|
|
)}
|
|
<div className="flex flex-wrap items-start justify-between gap-x-6 gap-y-3">
|
|
<h1 className="font-display text-2xl font-semibold tracking-tight">{title}</h1>
|
|
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
|
</div>
|
|
{description && (
|
|
<p className="max-w-prose text-sm text-muted-foreground">{description}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|