Files
PIG-Demo/src/components/site/SiteHeader.tsx
T
karti-ai 6c2f3899c8
ci / web (push) Successful in 2m42s
ci / python (push) Successful in 2m35s
Move the canonical repo to Gitea; GitHub becomes a private archive
git.karti.ai/PIG/PIG-Demo is now the source of truth, public and anonymously
cloneable. Every self-link in the site and the docs repoints there — Gitea
serves file paths at /src/branch/main/, not /blob/main/, so those needed
rewriting rather than a hostname swap.

CI moves with it. An archived GitHub repo is read-only and its Actions stop
firing, so leaving the workflow there would have meant a repo whose gates
silently never run. .github/ is deleted rather than kept for reference: a
workflow that can never execute is worse than no workflow, because it looks
like coverage.

The Gitea workflow is not a copy. That runner is aarch64 and installs pnpm
through corepack from `packageManager` rather than pnpm/action-setup, uses
checkout@v4 and setup-node@v4, and fetches uv from astral.sh directly. It is
also configured `container.network: host` — nothing here needs a service
container, but the comment says so, because that setting cost the sibling repo
three failed runs.

The header and footer icon changed from the GitHub mark to a neutral one. A
GitHub logo pointing at a Gitea instance is a small lie about where the code
lives, on a site whose argument is that you can go and check it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
2026-08-28 17:11:56 -07:00

556 lines
20 KiB
TypeScript

import * as React from 'react';
import { Link } from 'react-router-dom';
import { ArrowRight, ArrowUpRight, GitBranch, Menu } from 'lucide-react';
import { listDemos } from '@/lib/demo-kit/registry';
import type { DemoMeta } from '@/lib/demo-kit/types';
import { lineup, routes } from '@/content/lineup';
import type { VerticalEntry } from '@/content/verticals';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import {
Sheet,
SheetBody,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet';
import { ContrastToggle } from '@/components/site/ContrastToggle';
import { DemoIcon } from '@/components/site/DemoIcon';
import { PIG_URL, REPO_URL, VERIFIERS_WORDLE_URL } from '@/components/site/links';
import { ThemeToggle } from '@/components/site/ThemeToggle';
import { Wordmark } from '@/components/site/Wordmark';
import { cn } from '@/lib/utils';
/*
* This header is GENERATED. Nothing in it names a demo.
*
* Adding `src/demos/<slug>/` puts that demo in the Demos panel, in its
* vertical, and (if it is the first live one) behind the CTA, with zero edits
* to this file. The only hand-written lists here are the five concepts under
* "How it works", which are properties of the idea rather than of the lineup.
*/
/**
* The real upstream taskset, quoted rather than paraphrased.
*
* A mega-menu that contains source code instead of more links is the cheapest
* signal on the whole site that this is not a brochure — it is the first thing
* a technical buyer sees, and it is true before they have clicked anything.
*/
const TASKSET_SOURCE = `class WordleConfig(TextArenaConfig):
game: Literal["Wordle-v0"] = "Wordle-v0"
class WordleTaskset(TextArenaTaskset, vf.Taskset[TextArenaTask, WordleConfig]):
pass`;
const CONCEPTS: readonly { term: string; gloss: string }[] = [
{
term: 'Environment',
gloss: 'The task, the legal moves and the grader, packaged so anyone can install and run it.',
},
{
term: 'Rollout',
gloss: 'One episode. The model acts, the environment answers, and every turn is recorded.',
},
{
term: 'Reward',
gloss: 'The number the run is scored on. You write it, so you decide what "good" means.',
},
{
term: 'Harness',
gloss: 'The runner that plays a taskset against a model and keeps the receipts.',
},
{
term: 'Held-out grading',
gloss: 'Scored on problems the model has never seen, which is the only way the score means anything.',
},
];
/**
* The header's Verticals panel shows the LINEUP, not the registry's groups.
*
* `listVerticals()` only ever contains verticals that already have a demo —
* today that is one group, `reference`, which `src/content/verticals.ts` is
* explicit is not an industry and must never be presented as one. The twelve
* proposals each have a real page at `/verticals/<slug>`, so the panel links
* there. `VerticalEntry.slug` is the route's slug; the registry's `Vertical`
* key is a different, shorter string and is deliberately not used for URLs.
*/
type HeaderVertical = VerticalEntry;
function useLineup() {
return React.useMemo(() => {
// `listDemos` already returns a fresh array sorted by order then slug, and
// `listVerticals` already drops the empty verticals. Neither needs redoing
// here — if this file starts re-sorting the lineup, the header and the
// gallery will eventually disagree about what "first" means.
const demos = listDemos();
const live = demos.filter((demo) => demo.status === 'live');
const spec = demos.filter((demo) => demo.status === 'spec');
// Already sorted by rank in `@/content/lineup`. Copied rather than passed
// through, because the panel props are mutable arrays and `lineup` is the
// one every page reads.
const verticals: HeaderVertical[] = [...lineup];
// The CTA follows the lineup rather than naming a slug. Today the first
// live demo IS the word game, so this resolves to the wordle route; when a
// second one ships ahead of it, the button moves with it and this file does
// not change.
const primary = live[0] ?? demos[0];
const ctaHref = primary ? `/demos/${primary.slug}` : '/demos';
return { live, spec, verticals, ctaHref };
}, []);
}
/* ------------------------------------------------------------------ panels */
/**
* Spreads `...rest` onto the Link, and that is not tidiness.
*
* Both `NavigationMenuLink asChild` and `SheetClose asChild` work by cloning
* this element and merging their own `onClick` into it. A component that takes
* `demo` and ignores everything else silently drops those handlers: the row
* still navigates, and the mega panel or the mobile sheet stays open on top of
* the page you just arrived at.
*/
function DemoRow({
demo,
muted = false,
className,
...rest
}: { demo: DemoMeta; muted?: boolean } & Omit<React.ComponentProps<typeof Link>, 'to'>) {
return (
<Link
to={`/demos/${demo.slug}`}
className={cn(
'flex gap-3 rounded-lg p-2.5 transition-colors duration-1 ease-enter hover:bg-surface-2',
muted && 'opacity-70 hover:opacity-100',
className,
)}
{...rest}
>
<DemoIcon name={demo.icon} className="mt-0.5 size-4 text-accent-fg" />
<span className="flex min-w-0 flex-col gap-0.5">
<span className="flex items-center gap-2 text-sm font-medium text-fg">
{demo.title}
{demo.status === 'spec' ? (
<Badge variant="outline" className="font-normal">
Spec
</Badge>
) : null}
</span>
<span className="text-xs leading-relaxed text-muted">
{/*
A spec demo shows the BUYER, not the technology: "Head of Claims"
says who is meant to care, where a taskset name says nothing to the
person reading this in a boardroom.
*/}
{demo.status === 'spec' ? `For the ${demo.persona}` : demo.tagline}
</span>
</span>
</Link>
);
}
function PanelHeading({ children }: { children: React.ReactNode }) {
return (
<p className="px-2.5 pb-1 text-xs font-semibold uppercase tracking-wider text-muted">
{children}
</p>
);
}
function DemosPanel({ live, spec }: { live: DemoMeta[]; spec: DemoMeta[] }) {
return (
<div className="w-[min(92vw,720px)]">
<div className="grid grid-cols-2 gap-4 p-4">
<section aria-label="Live now" className="flex flex-col gap-0.5">
<PanelHeading>Live now</PanelHeading>
{live.length > 0 ? (
live.map((demo) => (
<NavigationMenuLink asChild key={demo.slug}>
<DemoRow demo={demo} />
</NavigationMenuLink>
))
) : (
<p className="p-2.5 text-xs text-muted">No interactive demos published yet.</p>
)}
</section>
<section
aria-label="Shipping next"
className="flex flex-col gap-0.5 border-l border-border pl-4"
>
<PanelHeading>Shipping next</PanelHeading>
{spec.length > 0 ? (
spec.map((demo) => (
<NavigationMenuLink asChild key={demo.slug}>
<DemoRow demo={demo} muted />
</NavigationMenuLink>
))
) : (
<p className="p-2.5 text-xs text-muted">Nothing queued.</p>
)}
</section>
</div>
<div className="flex items-center justify-between gap-4 border-t border-border bg-surface-2 px-5 py-3">
<p className="text-xs text-muted">
Every demo replays a real rollout from a real environment.
</p>
<NavigationMenuLink asChild>
<Link
to="/demos"
className="inline-flex shrink-0 items-center gap-1 text-xs font-medium text-accent-fg underline-offset-4 hover:underline"
>
Browse all demos
<ArrowRight aria-hidden="true" className="size-3.5" />
</Link>
</NavigationMenuLink>
</div>
</div>
);
}
function VerticalsPanel({ verticals }: { verticals: HeaderVertical[] }) {
return (
<div className="w-[min(92vw,720px)] p-4">
{verticals.length > 0 ? (
<div className="grid grid-cols-2 gap-x-4 gap-y-0.5">
{verticals.map((vertical) => (
<NavigationMenuLink asChild key={vertical.slug}>
<Link
to={routes.vertical(vertical.slug)}
className="flex gap-3 rounded-lg p-2.5 transition-colors duration-1 ease-enter hover:bg-surface-2"
>
<DemoIcon name={vertical.icon} className="mt-0.5 size-4 text-accent-fg" />
<span className="flex min-w-0 flex-col gap-0.5">
<span className="text-sm font-medium text-fg">{vertical.title}</span>
{/* The budget holder, not the reward sentence: a nav row has
one line, and "who buys this" is the more useful half. */}
<span className="text-xs leading-relaxed text-muted">{vertical.persona}</span>
</span>
</Link>
</NavigationMenuLink>
))}
</div>
) : (
<p className="p-2.5 text-xs text-muted">No verticals in the lineup yet.</p>
)}
</div>
);
}
function TasksetSource({ className }: { className?: string }) {
return (
<div className={cn('flex flex-col gap-2 rounded-lg border border-border bg-bg p-3', className)}>
<p className="text-xs font-semibold uppercase tracking-wider text-muted">
The actual taskset
</p>
{/*
Wrapped, not scrolled. The source is quoted verbatim — reformatting it
to fit would make it stop being a quote — and the widest line is a third
wider than this column at any font size worth reading. In a panel whose
entire job is "this is real code", a visible wrap beats a third of the
line hidden behind a scrollbar nobody in a boardroom will drag.
*/}
<pre className="overflow-x-auto whitespace-pre-wrap break-words text-[11px] leading-relaxed text-fg">
<code className="font-mono">{TASKSET_SOURCE}</code>
</pre>
<a
href={VERIFIERS_WORDLE_URL}
target="_blank"
rel="noreferrer noopener"
className="inline-flex items-center gap-1 text-xs font-medium text-accent-fg underline-offset-4 hover:underline"
>
verifiers/environments/wordle
<ArrowUpRight aria-hidden="true" className="size-3.5" />
</a>
</div>
);
}
function HowItWorksPanel({ ctaHref }: { ctaHref: string }) {
return (
<div
// 820px, not more: the panel hangs off the LEFT edge of the nav, which
// starts about 100px in, so anything wider pushes past the right edge of
// a 1024px laptop and gives the whole page a horizontal scrollbar.
className="w-[min(90vw,820px)] p-4"
>
<div className="grid grid-cols-[minmax(0,0.85fr)_minmax(0,1fr)] gap-4">
<dl className="flex flex-col gap-3">
{CONCEPTS.map((concept) => (
<div key={concept.term} className="flex flex-col gap-0.5">
<dt className="text-sm font-medium text-fg">{concept.term}</dt>
<dd className="text-xs leading-relaxed text-muted">{concept.gloss}</dd>
</div>
))}
<NavigationMenuLink asChild>
<Link
to={ctaHref}
className="mt-1 inline-flex items-center gap-1 text-xs font-medium text-accent-fg underline-offset-4 hover:underline"
>
See all five on one recorded run
<ArrowRight aria-hidden="true" className="size-3.5" />
</Link>
</NavigationMenuLink>
</dl>
<TasksetSource />
</div>
</div>
);
}
/* ------------------------------------------------------------------ mobile */
function MobileNav({
live,
spec,
verticals,
ctaHref,
}: {
live: DemoMeta[];
spec: DemoMeta[];
verticals: HeaderVertical[];
ctaHref: string;
}) {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="ghost" size="icon-touch" aria-label="Open menu">
<Menu aria-hidden="true" />
</Button>
</SheetTrigger>
{/*
The sheet is a flex column: header, a scrolling body, then a pinned
footer. Scrolling the BODY rather than the content root is what keeps a
long lineup reachable on a short phone, and `overscroll-contain` on it
stops the flick chaining into the page underneath.
*/}
<SheetContent side="right" className="w-[min(92vw,24rem)]">
<SheetHeader>
<SheetTitle>Menu</SheetTitle>
<SheetDescription className="sr-only">
Demos, verticals, and how these environments work.
</SheetDescription>
</SheetHeader>
<SheetBody>
<Accordion type="multiple" defaultValue={['demos']}>
<AccordionItem value="demos">
<AccordionTrigger>Demos</AccordionTrigger>
<AccordionContent className="flex flex-col gap-0.5">
<PanelHeading>Live now</PanelHeading>
{live.map((demo) => (
<SheetClose asChild key={demo.slug}>
<DemoRow demo={demo} />
</SheetClose>
))}
<PanelHeading>Shipping next</PanelHeading>
{spec.map((demo) => (
<SheetClose asChild key={demo.slug}>
<DemoRow demo={demo} muted />
</SheetClose>
))}
<SheetClose asChild>
<Link
to="/demos"
className="tap inline-flex items-center gap-1 p-2.5 text-xs font-medium text-accent-fg"
>
Browse all demos
<ArrowRight aria-hidden="true" className="size-3.5" />
</Link>
</SheetClose>
</AccordionContent>
</AccordionItem>
<AccordionItem value="verticals">
<AccordionTrigger>Verticals</AccordionTrigger>
<AccordionContent className="flex flex-col gap-0.5">
{verticals.map((vertical) => (
<SheetClose asChild key={vertical.slug}>
<Link
to={routes.vertical(vertical.slug)}
className="flex gap-3 rounded-lg p-2.5 hover:bg-surface-2"
>
<DemoIcon name={vertical.icon} className="mt-0.5 text-accent-fg" />
<span className="flex min-w-0 flex-col gap-0.5">
<span className="text-sm font-medium text-fg">{vertical.title}</span>
<span className="text-xs leading-relaxed text-muted">
{vertical.persona}
</span>
</span>
</Link>
</SheetClose>
))}
</AccordionContent>
</AccordionItem>
<AccordionItem value="how">
<AccordionTrigger>How it works</AccordionTrigger>
<AccordionContent className="flex flex-col gap-3">
<dl className="flex flex-col gap-3">
{CONCEPTS.map((concept) => (
<div key={concept.term} className="flex flex-col gap-0.5">
<dt className="text-sm font-medium text-fg">{concept.term}</dt>
<dd className="text-xs leading-relaxed text-muted">{concept.gloss}</dd>
</div>
))}
</dl>
<TasksetSource />
</AccordionContent>
</AccordionItem>
</Accordion>
<a
href={PIG_URL}
target="_blank"
rel="noreferrer noopener"
className="tap mt-2 flex items-center justify-between border-b border-border py-3 text-sm font-medium text-fg"
>
primeintellectgrowth.com
<ArrowUpRight aria-hidden="true" className="size-4 text-muted" />
</a>
<a
href={REPO_URL}
target="_blank"
rel="noreferrer noopener"
className="tap flex items-center justify-between border-b border-border py-3 text-sm font-medium text-fg"
>
Source on Gitea
<GitBranch aria-hidden="true" className="size-4 text-muted" />
</a>
<div className="flex items-center gap-1 pt-3">
<ThemeToggle />
<ContrastToggle />
</div>
</SheetBody>
<SheetFooter>
<SheetClose asChild>
<Button asChild size="lg" className="w-full">
<Link to={ctaHref}>Play the demo</Link>
</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
/* ------------------------------------------------------------------ header */
export function SiteHeader() {
const { live, spec, verticals, ctaHref } = useLineup();
return (
<header
// The height token already folds in --safe-top, so the padding and the
// height come from the same source and a notch cannot push the row off
// the bottom edge of the bar.
className="sticky top-0 z-50 h-[var(--app-header-h)] border-b border-border bg-surface/80 pt-[var(--safe-top)] backdrop-blur-md supports-[backdrop-filter]:bg-surface/70"
>
<div className="mx-auto flex h-full max-w-canvas items-center gap-2 px-4 pl-[max(1rem,var(--safe-left))] pr-[max(1rem,var(--safe-right))]">
<Link
to="/"
className="tap flex shrink-0 items-center rounded-md pr-2 text-base"
aria-label="PIG demo, home"
>
<Wordmark />
</Link>
{/*
`h-full` is not cosmetic. The viewport hangs off the Root's
`top-full`, so a Root only as tall as its 36px triggers drops the
panel INSIDE the header, over its own bottom border.
*/}
<NavigationMenu className="hidden h-full lg:flex" delayDuration={120}>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Demos</NavigationMenuTrigger>
<NavigationMenuContent>
<DemosPanel live={live} spec={spec} />
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Verticals</NavigationMenuTrigger>
<NavigationMenuContent>
<VerticalsPanel verticals={verticals} />
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>How it works</NavigationMenuTrigger>
<NavigationMenuContent>
<HowItWorksPanel ctaHref={ctaHref} />
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
href={PIG_URL}
target="_blank"
rel="noreferrer noopener"
className={cn(navigationMenuTriggerStyle(), 'text-muted hover:text-fg')}
>
primeintellectgrowth.com
<ArrowUpRight aria-hidden="true" className="size-3.5" />
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<div className="ml-auto flex items-center gap-1">
<div className="hidden items-center gap-1 lg:flex">
<ThemeToggle />
<ContrastToggle />
<Button asChild variant="ghost" size="icon">
<a
href={REPO_URL}
target="_blank"
rel="noreferrer noopener"
aria-label="Source on Gitea"
>
<GitBranch aria-hidden="true" />
</a>
</Button>
</div>
<Button asChild size="touch" className="hidden lg:inline-flex">
<Link to={ctaHref}>Play the demo</Link>
</Button>
<div className="lg:hidden">
<MobileNav live={live} spec={spec} verticals={verticals} ctaHref={ctaHref} />
</div>
</div>
</div>
</header>
);
}