# Autopilot Hero

> 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.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/autopilot-hero).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/autopilot-hero
- Screenshot: https://shadcn.exchange/shot/keyframeui/autopilot-hero/light.png
- Registry item: https://keyframeui.com/r/autopilot-hero.json
- Categories: blocks, hero-sections
- Listed: 2026-10-27, source last changed 2026-10-27

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/autopilot-hero.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`
- Registry items: `https://keyframeui.com/r/autopilot.json`, `https://keyframeui.com/r/release-pill.json`

## Notes from the registry

Live preview and the components it's built with: https://keyframeui.com/blocks/hero-sections/autopilot-hero

## Files

### `registry/blocks/autopilot-hero.tsx`

```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>
  );
}
```
