A hero where the product demos itself. Under the headline, an Autopilot cursor takes a 3am page, reads the trace, writes the fix and opens the pull request in a working incident console, and steps aside when a visitor reaches for the mouse. With a navbar and a Release Pill over the headline.
registry/blocks/autopilot-hero.tsx
"use client";
import * as React from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { Autopilot, type AutopilotChapter } from "@/components/ui/autopilot";
import { ReleasePill, type Release } from "@/components/ui/release-pill";
/* ── Content ─────────────────────────────────────────────────
* Everything you'd change to make the hero yours. The incident
* console under the headline is plain markup further down; the
* data it shows and the cursor's lines are here too.
* ─────────────────────────────────────────────────────────── */
/** The one accent: the cursor, the primary buttons and focus rings. */
const ACCENT = "#6e56cf";
const BRAND = { name: "Nightjar", href: "#" };
const NAV_LINKS = [
{ label: "Product", href: "#product" },
{ label: "Customers", href: "#customers" },
{ label: "Pricing", href: "#pricing" },
{ label: "Docs", href: "#docs" },
];
const SIGN_IN = { label: "Sign in", href: "#sign-in" };
const NAV_CTA = { label: "Start free", href: "#start" };
const RELEASES: Release[] = [
{
version: "v3.0",
title: "Autofix for Kubernetes",
date: "Oct 21",
href: "#changelog-v3-0",
summary: "Rolls back a bad deploy first, then opens the real fix as a pull request.",
},
{ version: "v2.9", title: "Runbooks from Slack threads", date: "Oct 7", href: "#changelog-v2-9" },
{ version: "v2.8", title: "OpenTelemetry traces", date: "Sep 23", href: "#changelog-v2-8" },
{ version: "v2.7", title: "SOC 2 Type II", date: "Sep 9", href: "#changelog-v2-7" },
];
const CHANGELOG_HREF = "#changelog";
const HEADLINE = "Sleep through the incident.";
const SUBLINE =
"Nightjar is the AI on-call engineer. It takes the page, reads the trace, writes the fix and opens the pull request. You review it over coffee.";
const PRIMARY_CTA = { label: "Try it on your repo", href: "#start" };
const SECONDARY_CTA = { label: "Book a demo", href: "#demo" };
const FOOTNOTE = "Free for 14 days. Works with GitHub, Datadog and PagerDuty.";
/* The incident console the cursor works in. */
const APP_URL = "app.nightjar.dev/incidents";
/** The console's clock as the night goes on: waiting, investigating, fixed, shipped. */
const CLOCK = ["3:12 AM", "3:13 AM", "3:15 AM", "3:16 AM"];
type Severity = "SEV-1" | "SEV-2" | "SEV-3";
const INCIDENTS: { id: string; code: string; title: string; service: string; severity: Severity; opened: string }[] = [
{ id: "checkout", code: "INC-2041", title: "5xx spike on /v2/checkout", service: "checkout-api", severity: "SEV-1", opened: "now" },
{ id: "search", code: "INC-2039", title: "Indexer queue lag over 90s", service: "search", severity: "SEV-3", opened: "26m" },
{ id: "auth", code: "INC-2036", title: "p99 latency up on /token", service: "auth", severity: "SEV-3", opened: "1h" },
];
/** What the console says about an incident nobody needs to wake up for. */
const WATCHING =
"Below the paging threshold, so nobody gets woken up. Nightjar keeps watching and pages a human only if it gets worse.";
const TRACE = {
total: 48, // ms
spans: [
{ id: "request", name: "POST /v2/checkout", start: 0, end: 48, depth: 0 },
{ id: "auth", name: "auth.verify", start: 2, end: 11, depth: 1 },
{ id: "cart", name: "cart.load", start: 12, end: 29, depth: 1 },
{ id: "totals", name: "cart.totals", start: 30, end: 33, depth: 2 },
],
/** The span that threw, and what Nightjar found in it. */
cause: {
span: "totals",
error: "TypeError: Cannot read properties of undefined (reading 'code')",
at: "applyTax · src/cart/totals.ts:42",
finding: "Since deploy #8812, 14 min ago. Carts saved before May have no currency.",
},
};
const FIX = {
file: "src/cart/totals.ts",
lines: [
{ kind: " ", n: 41, text: "export function applyTax(cart: Cart) {" },
{ kind: "-", n: 42, text: " const rate = TAX_RATES[cart.currency.code];" },
{ kind: "+", n: 42, text: " const code = cart.currency?.code ?? DEFAULT_CURRENCY;" },
{ kind: "+", n: 43, text: " const rate = TAX_RATES[code];" },
{ kind: " ", n: 44, text: " return cart.subtotal * (1 + rate);" },
],
title: "Default the currency on legacy carts",
number: 4821,
checks: "3 checks passed",
};
/** The cursor's label as it works. */
const SAYS = {
page: "Paged: checkout is failing",
trace: "Reading the trace",
cause: "It broke in deploy #8812",
fix: "Writing the fix",
title: "Naming the pull request",
open: "Opening the pull request",
done: "Fixed. Back to sleep",
};
/** The chapters, as the strip under the console names them. */
const CHAPTERS = ["Page", "Trace", "Fix", "Ship"];
/* ── Hero ────────────────────────────────────────────────── */
type ConsoleState = {
/** The incident open in the console. */
incident: string | null;
/** The span picked in its trace. */
span: string | null;
/** The fix is written. */
fix: boolean;
/** The pull request's title, as typed. */
title: string;
/** The pull request is open. */
pr: boolean;
};
const QUIET: ConsoleState = { incident: null, span: null, fix: false, title: "", pr: false };
const OPENED: ConsoleState = { ...QUIET, incident: "checkout" };
const FOUND: ConsoleState = { ...OPENED, span: TRACE.cause.span };
const WRITTEN: ConsoleState = { ...FOUND, fix: true, title: FIX.title };
const focusRing =
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--accent)";
export function AutopilotHero() {
const [state, setState] = React.useState(QUIET);
const update = (patch: Partial<ConsoleState>) => setState((current) => ({ ...current, ...patch }));
// Each chapter starts from a known state, so a takeover or a jump replays it cleanly.
const chapters: AutopilotChapter[] = [
{
title: CHAPTERS[0],
setup: () => setState(QUIET),
steps: [
{ wait: 500 },
{ move: "incident-checkout", say: SAYS.page },
{ wait: 250 },
{ click: "incident-checkout" },
{ wait: 400 },
],
},
{
title: CHAPTERS[1],
setup: () => setState(OPENED),
steps: [
{ move: "span-request", say: SAYS.trace },
{ wait: 350 },
{ move: "span-totals" },
{ click: "span-totals" },
{ wait: 1100, say: SAYS.cause },
],
},
{
title: CHAPTERS[2],
setup: () => setState(FOUND),
steps: [
{ click: "write-fix", say: SAYS.fix },
{ wait: 900 },
{ type: FIX.title, into: "pr-title", say: SAYS.title },
{ wait: 300 },
],
},
{
title: CHAPTERS[3],
setup: () => setState(WRITTEN),
steps: [
{ click: "open-pr", say: SAYS.open },
{ wait: 1800, say: SAYS.done },
],
},
];
return (
<section
style={{ "--accent": ACCENT } as React.CSSProperties}
// The Release Pill draws with the shadcn theme colors; point them at this palette.
className="relative isolate overflow-hidden bg-white text-zinc-950 [--background:#ffffff] [--foreground:#09090b] [--muted-foreground:#71717a] dark:bg-zinc-950 dark:text-white dark:[--background:#09090b] dark:[--foreground:#fafafa] dark:[--muted-foreground:#a1a1aa]"
>
<Backdrop />
<Navbar />
<div className="mx-auto max-w-6xl px-6 pt-12 text-center sm:pt-16">
<ReleasePill releases={RELEASES} changelogHref={CHANGELOG_HREF} />
<h1 className="mx-auto mt-6 max-w-[13ch] text-5xl/[1.02] font-semibold tracking-[-0.035em] text-balance sm:text-6xl/[1] lg:text-7xl/[0.98]">
{HEADLINE}
</h1>
<p className="mx-auto mt-6 max-w-2xl text-base/7 text-pretty text-zinc-600 sm:text-lg/8 dark:text-zinc-400">
{SUBLINE}
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<a
href={PRIMARY_CTA.href}
className={`group inline-flex h-11 items-center gap-2 rounded-full bg-(--accent) px-5 text-sm font-medium text-white transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.98] ${focusRing}`}
>
{PRIMARY_CTA.label}
<ArrowIcon className="size-4 transition-transform duration-200 group-hover:translate-x-0.5" />
</a>
<a
href={SECONDARY_CTA.href}
className={`inline-flex h-11 items-center rounded-full bg-white/70 px-5 text-sm font-medium ring-1 ring-zinc-950/10 backdrop-blur-sm transition-colors hover:bg-zinc-950/[0.04] dark:bg-white/[0.04] dark:ring-white/15 dark:hover:bg-white/10 ${focusRing}`}
>
{SECONDARY_CTA.label}
</a>
</div>
<p className="mt-5 text-sm text-zinc-500">{FOOTNOTE}</p>
</div>
<div className="mx-auto mt-10 max-w-6xl px-6 pb-20 sm:mt-12 sm:pb-24">
<Autopilot
cursors={[{ id: "agent", label: BRAND.name, color: ACCENT, home: [0.7, 0.62] }]}
chapters={chapters}
chapterStrip
park="top-right"
className="text-zinc-950 dark:text-white"
stageClassName="overflow-hidden rounded-2xl border border-zinc-950/10 bg-white shadow-[0_32px_80px_-32px_rgb(9_9_11/0.35)] dark:border-white/10 dark:bg-zinc-900 dark:shadow-[0_32px_80px_-24px_rgb(0_0_0/0.9)]"
>
<IncidentConsole state={state} update={update} />
</Autopilot>
</div>
</section>
);
}
/** A faint grid that fades out from the top, and a glow of the accent behind the console. */
function Backdrop() {
return (
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10">
<div className="absolute inset-0 bg-[linear-gradient(to_right,rgb(9_9_11/0.06)_1px,transparent_1px),linear-gradient(to_bottom,rgb(9_9_11/0.06)_1px,transparent_1px)] bg-size-[56px_56px] [mask-image:radial-gradient(ellipse_75%_50%_at_50%_0%,#000_35%,transparent_100%)] dark:bg-[linear-gradient(to_right,rgb(255_255_255/0.07)_1px,transparent_1px),linear-gradient(to_bottom,rgb(255_255_255/0.07)_1px,transparent_1px)]" />
<div className="absolute top-[46%] left-1/2 h-[26rem] w-[min(60rem,100%)] -translate-x-1/2 rounded-full bg-(--accent)/20 blur-[110px] dark:bg-(--accent)/30" />
</div>
);
}
/* ── Navbar ──────────────────────────────────────────────── */
function Navbar() {
const [open, setOpen] = React.useState(false);
const reduced = useReducedMotion();
const rootRef = React.useRef<HTMLElement>(null);
const buttonRef = React.useRef<HTMLButtonElement>(null);
const menuId = React.useId();
// Escape or a press outside closes the menu.
React.useEffect(() => {
if (!open) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key !== "Escape") return;
setOpen(false);
buttonRef.current?.focus();
};
const onPointerDown = (event: PointerEvent) => {
if (!rootRef.current?.contains(event.target as Node)) setOpen(false);
};
document.addEventListener("keydown", onKeyDown);
document.addEventListener("pointerdown", onPointerDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
document.removeEventListener("pointerdown", onPointerDown);
};
}, [open]);
return (
<header ref={rootRef} className="relative z-30 mx-auto max-w-6xl px-6">
<div className="flex h-16 items-center justify-between gap-6">
<a href={BRAND.href} className={`-m-1 flex items-center gap-2.5 rounded-lg p-1 font-semibold tracking-tight ${focusRing}`}>
<Logo />
{BRAND.name}
</a>
<nav aria-label="Main" className="hidden items-center gap-1 text-sm md:flex">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className={`rounded-full px-3 py-2 text-zinc-600 transition-colors hover:text-zinc-950 dark:text-zinc-400 dark:hover:text-white ${focusRing}`}
>
{link.label}
</a>
))}
</nav>
<div className="hidden items-center gap-2 text-sm md:flex">
<a
href={SIGN_IN.href}
className={`rounded-full px-3 py-2 text-zinc-600 transition-colors hover:text-zinc-950 dark:text-zinc-400 dark:hover:text-white ${focusRing}`}
>
{SIGN_IN.label}
</a>
<a
href={NAV_CTA.href}
className={`inline-flex h-9 items-center rounded-full bg-zinc-950 px-4 font-medium text-white transition-colors hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200 ${focusRing}`}
>
{NAV_CTA.label}
</a>
</div>
<button
ref={buttonRef}
type="button"
aria-expanded={open}
aria-controls={menuId}
aria-label={open ? "Close menu" : "Open menu"}
onClick={() => setOpen((value) => !value)}
className={`-mr-2 grid size-10 place-items-center rounded-full text-zinc-700 transition-colors hover:bg-zinc-950/5 md:hidden dark:text-zinc-300 dark:hover:bg-white/10 ${focusRing}`}
>
<MenuIcon open={open} />
</button>
</div>
<AnimatePresence initial={false}>
{open ? (
<motion.div
id={menuId}
initial={{ opacity: 0, y: reduced ? 0 : -6 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: reduced ? 0 : -6 }}
transition={{ duration: reduced ? 0.12 : 0.2, ease: [0.22, 1, 0.36, 1] }}
className="absolute inset-x-4 top-14 rounded-2xl border border-zinc-950/10 bg-white p-2 shadow-[0_24px_48px_-16px_rgb(9_9_11/0.25)] md:hidden dark:border-white/10 dark:bg-zinc-900"
>
<nav aria-label="Main">
<ul>
{NAV_LINKS.map((link) => (
<li key={link.href}>
<a
href={link.href}
onClick={() => setOpen(false)}
className={`flex h-11 items-center rounded-xl px-3 font-medium transition-colors hover:bg-zinc-950/[0.04] dark:hover:bg-white/5 ${focusRing}`}
>
{link.label}
</a>
</li>
))}
</ul>
</nav>
<div className="mt-2 grid grid-cols-2 gap-2 border-t border-zinc-950/10 pt-2 dark:border-white/10">
<a
href={SIGN_IN.href}
onClick={() => setOpen(false)}
className={`flex h-11 items-center justify-center rounded-xl text-sm font-medium ring-1 ring-zinc-950/10 dark:ring-white/15 ${focusRing}`}
>
{SIGN_IN.label}
</a>
<a
href={NAV_CTA.href}
onClick={() => setOpen(false)}
className={`flex h-11 items-center justify-center rounded-xl bg-zinc-950 text-sm font-medium text-white dark:bg-white dark:text-zinc-950 ${focusRing}`}
>
{NAV_CTA.label}
</a>
</div>
</motion.div>
) : null}
</AnimatePresence>
</header>
);
}
/* ── Incident console ────────────────────────────────────── */
function IncidentConsole({
state,
update,
}: {
state: ConsoleState;
update: (patch: Partial<ConsoleState>) => void;
}) {
const incident = INCIDENTS.find((item) => item.id === state.incident);
const step = state.pr ? 3 : state.fix ? 2 : state.incident ? 1 : 0;
return (
<div className="flex h-[36rem] flex-col text-left text-[13px] sm:h-[34rem] md:h-[31rem] lg:h-[30rem]">
<div className="flex h-10 shrink-0 items-center gap-3 border-b border-zinc-950/10 px-4 dark:border-white/10">
<span aria-hidden className="flex gap-1.5">
<span className="size-2.5 rounded-full bg-zinc-950/10 dark:bg-white/15" />
<span className="size-2.5 rounded-full bg-zinc-950/10 dark:bg-white/15" />
<span className="size-2.5 rounded-full bg-zinc-950/10 dark:bg-white/15" />
</span>
<span className="flex items-center gap-1.5 font-mono text-[11px] text-zinc-500 tabular-nums dark:text-zinc-400">
<MoonIcon className="size-3" />
{CLOCK[step]}
</span>
<span className="mx-auto hidden truncate rounded-md bg-zinc-950/[0.04] px-3 py-1 font-mono text-[11px] text-zinc-500 sm:block dark:bg-white/5 dark:text-zinc-400">
{APP_URL}
</span>
{/* Kept clear for the cursor, which parks top right while the visitor has control. */}
<span aria-hidden className="w-16 shrink-0 sm:w-28" />
</div>
<div className="grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)] lg:grid-cols-[17rem_minmax(0,1fr)] lg:grid-rows-1">
<div className="border-b border-zinc-950/10 lg:border-r lg:border-b-0 dark:border-white/10">
<p className="hidden items-center justify-between px-4 pt-4 pb-1.5 text-[11px] font-medium tracking-wide text-zinc-500 uppercase lg:flex dark:text-zinc-400">
Open incidents
<span className="tabular-nums">{INCIDENTS.length}</span>
</p>
<ul className="p-2">
{INCIDENTS.map((item, i) => (
<li key={item.id} className={i > 0 ? "hidden lg:block" : undefined}>
<button
type="button"
data-autopilot={`incident-${item.id}`}
aria-current={state.incident === item.id ? "true" : undefined}
onClick={() => update({ incident: item.id })}
className={`flex w-full items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-[background-color,scale] duration-150 hover:bg-zinc-950/[0.04] active:scale-[0.99] aria-[current=true]:bg-zinc-950/[0.06] data-[autopilot-hover]:bg-zinc-950/[0.04] data-[autopilot-press]:scale-[0.99] dark:hover:bg-white/5 dark:aria-[current=true]:bg-white/[0.08] dark:data-[autopilot-hover]:bg-white/5 ${focusRing}`}
>
<SeverityDot severity={item.severity} live={i === 0 && !state.incident} />
<span className="min-w-0 flex-1">
<span className="block truncate font-medium">{item.title}</span>
<span className="mt-0.5 block truncate text-[11px] text-zinc-500 dark:text-zinc-400">
{item.code} · {item.service} · {item.opened}
</span>
</span>
<SeverityTag severity={item.severity} />
</button>
</li>
))}
</ul>
</div>
<div className="min-h-0 p-3 sm:p-5">
{!incident ? (
<Waiting />
) : incident.id === OPENED.incident ? (
<Investigation state={state} update={update} />
) : (
<Watching code={incident.code} title={incident.title} />
)}
</div>
</div>
</div>
);
}
function Waiting() {
return (
<div className="grid h-full place-items-center text-center">
<div className="max-w-60">
<span className="mx-auto grid size-10 place-items-center rounded-full bg-zinc-950/[0.04] text-zinc-500 dark:bg-white/5 dark:text-zinc-400">
<MoonIcon className="size-4" />
</span>
<p className="mt-3 font-medium">{INCIDENTS.length} open incidents</p>
<p className="mt-1 text-zinc-500 dark:text-zinc-400">One just paged. Open it to watch {BRAND.name} work.</p>
</div>
</div>
);
}
function Watching({ code, title }: { code: string; title: string }) {
return (
<div className="flex h-full flex-col rounded-xl border border-dashed border-zinc-950/15 p-4 dark:border-white/15">
<p className="text-[11px] text-zinc-500 dark:text-zinc-400">{code}</p>
<p className="mt-0.5 font-medium">{title}</p>
<p className="mt-3 max-w-sm text-zinc-600 text-pretty dark:text-zinc-400">{WATCHING}</p>
<svg aria-hidden viewBox="0 0 200 40" preserveAspectRatio="none" className="mt-auto h-16 w-full text-(--accent)">
<polyline
points="0,30 20,28 40,31 60,26 80,27 100,22 120,24 140,19 160,21 180,18 200,20"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
vectorEffect="non-scaling-stroke"
/>
</svg>
</div>
);
}
function Investigation({ state, update }: { state: ConsoleState; update: (patch: Partial<ConsoleState>) => void }) {
const incident = INCIDENTS.find((item) => item.id === OPENED.incident)!;
const status = state.pr
? { label: "Mitigated", tone: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", dot: "bg-emerald-500" }
: state.fix
? { label: "Fix ready", tone: "bg-(--accent)/10 text-(--accent) dark:text-[color-mix(in_oklab,var(--accent),white_45%)]", dot: "bg-(--accent)" }
: { label: "Investigating", tone: "bg-amber-500/10 text-amber-700 dark:text-amber-300", dot: "bg-amber-500" };
return (
<div className="flex h-full flex-col gap-3 sm:gap-4">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-[11px] text-zinc-500 dark:text-zinc-400">
{incident.code} · {incident.service}
</p>
<p className="mt-0.5 hidden truncate text-sm font-semibold lg:block">{incident.title}</p>
</div>
<span
className={`inline-flex shrink-0 items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium ${status.tone}`}
>
<span aria-hidden className={`size-1.5 rounded-full ${status.dot}`} />
{status.label}
</span>
</div>
<div className="grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)] gap-3 md:grid-cols-2 md:grid-rows-1 md:gap-4">
<TraceCard picked={state.span} folded={state.fix} onPick={(span) => update({ span })} />
<FixCard state={state} update={update} />
</div>
</div>
);
}
function TraceCard({
picked,
folded,
onPick,
}: {
picked: string | null;
/** Below md, once the fix is written, the trace folds down to its finding to make room. */
folded: boolean;
onPick: (span: string) => void;
}) {
const span = TRACE.spans.find((item) => item.id === picked);
const cause = TRACE.cause;
return (
<div className="flex min-h-0 flex-col overflow-hidden rounded-xl border border-zinc-950/10 bg-zinc-950/[0.015] dark:border-white/10 dark:bg-white/[0.02]">
<div className="flex h-9 shrink-0 items-center justify-between gap-3 border-b border-zinc-950/10 px-3 dark:border-white/10">
<span className="font-medium">Trace</span>
<span className="truncate font-mono text-[11px] text-zinc-500 tabular-nums dark:text-zinc-400">
{TRACE.total} ms · 500
</span>
</div>
<ul className={`p-1.5 ${folded ? "max-md:hidden" : ""}`}>
{TRACE.spans.map((item) => {
const failed = item.id === cause.span;
return (
<li key={item.id}>
<button
type="button"
data-autopilot={`span-${item.id}`}
aria-pressed={picked === item.id}
onClick={() => onPick(item.id)}
className={`grid w-full grid-cols-[minmax(0,7.5rem)_minmax(0,1fr)_2.75rem] items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-zinc-950/[0.04] aria-pressed:bg-zinc-950/[0.06] data-[autopilot-hover]:bg-zinc-950/[0.04] dark:hover:bg-white/5 dark:aria-pressed:bg-white/[0.08] dark:data-[autopilot-hover]:bg-white/5 ${focusRing}`}
>
<span
className={`truncate font-mono text-[11px] ${failed ? "text-rose-600 dark:text-rose-400" : ""}`}
style={{ paddingLeft: item.depth * 10 }}
>
{item.name}
</span>
<span aria-hidden className="relative h-1.5 rounded-full bg-zinc-950/[0.05] dark:bg-white/[0.06]">
<span
className={`absolute inset-y-0 rounded-full ${failed ? "bg-rose-500" : "bg-(--accent)/60"}`}
style={{
left: `${(item.start / TRACE.total) * 100}%`,
width: `max(4px, ${((item.end - item.start) / TRACE.total) * 100}%)`,
}}
/>
</span>
<span className="text-right font-mono text-[11px] text-zinc-500 tabular-nums dark:text-zinc-400">
{item.end - item.start} ms
</span>
</button>
</li>
);
})}
</ul>
{span ? (
<div
className={`mx-1.5 mb-1.5 rounded-lg px-2.5 py-2 text-[12px] ${
span.id === cause.span
? "bg-rose-500/[0.07] dark:bg-rose-400/10"
: "bg-zinc-950/[0.03] dark:bg-white/[0.04]"
} ${folded ? "max-md:mt-1.5" : ""}`}
>
{span.id === cause.span ? (
<>
<p className="font-mono text-[11px] leading-4 text-rose-700 dark:text-rose-300">{cause.error}</p>
<p className="mt-1 hidden font-mono text-[11px] text-zinc-500 sm:block dark:text-zinc-400">{cause.at}</p>
<p className="mt-1.5 text-zinc-600 text-pretty dark:text-zinc-300">{cause.finding}</p>
</>
) : (
<p className="text-zinc-600 dark:text-zinc-300">
<span className="font-mono text-[11px]">{span.name}</span> took {span.end - span.start} ms
with no errors.
</p>
)}
</div>
) : null}
</div>
);
}
function FixCard({ state, update }: { state: ConsoleState; update: (patch: Partial<ConsoleState>) => void }) {
const reduced = useReducedMotion();
const found = state.span === TRACE.cause.span;
const canOpen = state.title.trim() !== "";
return (
<div className="flex min-h-0 flex-col overflow-hidden rounded-xl border border-zinc-950/10 bg-zinc-950/[0.015] dark:border-white/10 dark:bg-white/[0.02]">
<div className="flex h-9 shrink-0 items-center justify-between gap-3 border-b border-zinc-950/10 px-3 dark:border-white/10">
<span className="font-medium">Fix</span>
<span className="truncate font-mono text-[11px] text-zinc-500 dark:text-zinc-400">{FIX.file}</span>
</div>
{!state.fix ? (
<div className="flex flex-1 items-center justify-between gap-3 p-3 md:flex-col md:justify-center md:p-4 md:text-center">
<p className="max-w-56 text-[12px] text-zinc-500 text-pretty md:text-[13px] dark:text-zinc-400">
{found ? "Root cause found. The fix is one line." : "Pick the span that failed to see why."}
</p>
<button
type="button"
data-autopilot="write-fix"
onClick={() => update({ fix: true, span: TRACE.cause.span })}
className={`inline-flex h-8 shrink-0 items-center gap-1.5 rounded-full bg-(--accent) px-3.5 text-xs font-medium text-white transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.97] data-[autopilot-hover]:brightness-110 data-[autopilot-press]:scale-[0.97] ${focusRing}`}
>
<SparkIcon className="size-3.5" />
Write the fix
</button>
</div>
) : (
<>
<div className="min-h-0 flex-1 overflow-hidden px-1.5 py-2 font-mono text-[11px] leading-[18px]">
{FIX.lines.map((line, i) => (
<motion.div
key={i}
initial={reduced ? false : { opacity: 0, x: -6 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.25, delay: i * 0.07, ease: [0.22, 1, 0.36, 1] }}
className={`flex gap-2 rounded px-1.5 ${
line.kind === "+"
? "bg-emerald-500/10 text-emerald-800 dark:text-emerald-300"
: line.kind === "-"
? "bg-rose-500/10 text-rose-800 dark:text-rose-300"
: "text-zinc-600 dark:text-zinc-400"
}`}
>
<span aria-hidden className="w-4 shrink-0 text-right text-zinc-400 tabular-nums select-none dark:text-zinc-500">
{line.n}
</span>
<span className="w-2 shrink-0 select-none">{line.kind === " " ? "" : line.kind}</span>
<span className="min-w-0 truncate whitespace-pre">{line.text}</span>
</motion.div>
))}
</div>
<div className="shrink-0 border-t border-zinc-950/10 p-2 dark:border-white/10">
{state.pr ? (
<motion.p
initial={reduced ? false : { opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
className="flex h-8 items-center gap-2 px-1.5 text-[12px]"
>
<PullRequestIcon className="size-3.5 text-emerald-600 dark:text-emerald-400" />
<span className="font-medium">#{FIX.number} opened</span>
<span className="truncate text-zinc-500 dark:text-zinc-400">{FIX.checks}</span>
</motion.p>
) : (
<div className="flex gap-2">
<input
data-autopilot="pr-title"
value={state.title}
onChange={(event) => update({ title: event.target.value })}
onKeyDown={(event) => {
if (event.key === "Enter" && canOpen) update({ pr: true });
}}
placeholder="Pull request title"
aria-label="Pull request title"
className="h-8 min-w-0 flex-1 rounded-lg border border-zinc-950/10 bg-white px-2.5 text-base outline-none placeholder:text-zinc-400 focus:border-(--accent) focus:ring-3 focus:ring-(--accent)/20 data-[autopilot-focus]:border-(--accent) data-[autopilot-focus]:ring-3 data-[autopilot-focus]:ring-(--accent)/20 sm:text-xs dark:border-white/10 dark:bg-zinc-950"
/>
<button
type="button"
data-autopilot="open-pr"
disabled={!canOpen}
onClick={() => update({ pr: true })}
className={`inline-flex h-8 shrink-0 items-center gap-1.5 rounded-lg bg-(--accent) px-3 text-xs font-medium text-white transition-[filter,scale,opacity] duration-150 hover:brightness-110 active:scale-[0.97] disabled:opacity-40 data-[autopilot-hover]:brightness-110 data-[autopilot-press]:scale-[0.97] ${focusRing}`}
>
<PullRequestIcon className="size-3.5" />
Open PR
</button>
</div>
)}
</div>
</>
)}
</div>
);
}
/* ── Small parts ─────────────────────────────────────────── */
const SEVERITY: Record<Severity, { dot: string; tag: string }> = {
"SEV-1": { dot: "bg-rose-500", tag: "bg-rose-500/10 text-rose-700 dark:text-rose-300" },
"SEV-2": { dot: "bg-amber-500", tag: "bg-amber-500/10 text-amber-700 dark:text-amber-300" },
"SEV-3": { dot: "bg-zinc-400", tag: "bg-zinc-950/5 text-zinc-600 dark:bg-white/10 dark:text-zinc-300" },
};
function SeverityDot({ severity, live }: { severity: Severity; live: boolean }) {
return (
<span aria-hidden className="relative mt-1.5 flex size-2 shrink-0">
{live ? (
<span
className={`absolute inset-0 animate-ping rounded-full opacity-60 motion-reduce:animate-none ${SEVERITY[severity].dot}`}
/>
) : null}
<span className={`relative size-2 rounded-full ${SEVERITY[severity].dot}`} />
</span>
);
}
function SeverityTag({ severity }: { severity: Severity }) {
return (
<span className={`shrink-0 rounded px-1.5 py-px font-mono text-[10px] leading-4 font-medium ${SEVERITY[severity].tag}`}>
{severity}
</span>
);
}
function Logo() {
return (
<span aria-hidden className="grid size-7 place-items-center rounded-lg bg-(--accent) text-white">
<MoonIcon className="size-4" />
</span>
);
}
function MoonIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden className={className}>
<path d="M13.6 10.2A6 6 0 0 1 5.8 2.4a6 6 0 1 0 7.8 7.8Z" />
</svg>
);
}
function ArrowIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden className={className}>
<path d="M3 8h10M9 4l4 4-4 4" />
</svg>
);
}
function SparkIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden className={className}>
<path d="M8 1.5c.3 2.9 1.6 4.2 4.5 4.5-2.9.3-4.2 1.6-4.5 4.5-.3-2.9-1.6-4.2-4.5-4.5C6.4 5.7 7.7 4.4 8 1.5Z" />
<path d="M12.5 10c.15 1.3.7 1.85 2 2-1.3.15-1.85.7-2 2-.15-1.3-.7-1.85-2-2 1.3-.15 1.85-.7 2-2Z" />
</svg>
);
}
function PullRequestIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" aria-hidden className={className}>
<circle cx="4" cy="3.5" r="1.5" />
<circle cx="4" cy="12.5" r="1.5" />
<circle cx="12" cy="12.5" r="1.5" />
<path d="M4 5v6M12 11V6.5a2 2 0 0 0-2-2H7.5M9 3 7.5 4.5 9 6" />
</svg>
);
}
function MenuIcon({ open }: { open: boolean }) {
return (
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" aria-hidden className="size-5">
<path d={open ? "M5 5l10 10" : "M3.5 7h13"} />
<path d={open ? "M15 5 5 15" : "M3.5 13h13"} />
</svg>
);
}
Live preview and the components it's built with: https://keyframeui.com/blocks/hero-sections/autopilot-hero