{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "vigilance",
  "title": "Dead man's switch",
  "description": "A train cab's vigilance control for supervised agent runs: the run keeps going only while someone checks in. Miss the window and it warns, then pauses.",
  "dependencies": [
    "@base-ui/react",
    "cn"
  ],
  "registryDependencies": [
    "https://bevel-ui.vercel.app/r/bevel.json"
  ],
  "files": [
    {
      "path": "registry/bevel/ui/vigilance.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { Progress as ProgressPrimitive } from \"@base-ui/react/progress\"\nimport { cn } from \"cn\"\n\ntype VigilanceStage = \"clear\" | \"warning\" | \"stopping\" | \"paused\"\n\ntype VigilanceContextValue = {\n  stage: VigilanceStage\n  remaining: number\n  interval: number\n  checkIn: () => void\n  resume: () => void\n}\n\nconst VigilanceContext = React.createContext<VigilanceContextValue | null>(null)\n\nfunction useVigilance() {\n  const context = React.useContext(VigilanceContext)\n  if (!context) throw new Error(\"Vigilance parts must be inside <Vigilance>\")\n  return context\n}\n\ntype VigilanceProps = React.ComponentProps<\"div\"> & {\n  /** Time allowed between check-ins, in milliseconds. */\n  interval: number\n  /** Share of the interval left when the warning lamp lights. */\n  warnAt?: number\n  /** Share left when the final warning is announced. */\n  stopAt?: number\n  /** Count down only while the supervised run is going. */\n  running?: boolean\n  /** The window ran out: pause the run. */\n  onTrip?: () => void\n  onCheckIn?: () => void\n  onResume?: () => void\n}\n\n/**\n * A dead man's switch for a supervised run: it keeps going only while\n * someone checks in. Miss the window and it warns, then stops.\n */\nfunction Vigilance({\n  className,\n  interval,\n  warnAt = 0.4,\n  stopAt = 0.15,\n  running = true,\n  onTrip,\n  onCheckIn,\n  onResume,\n  children,\n  ...props\n}: VigilanceProps) {\n  // The clock starts on mount, not during render, so a prerendered page and\n  // the browser agree. Until then the full window shows.\n  const [deadline, setDeadline] = React.useState<number | null>(null)\n  const [now, setNow] = React.useState(0)\n  React.useEffect(() => {\n    const t = Date.now()\n    setNow(t)\n    setDeadline(t + interval)\n    // Only on mount: check-ins and resumes set the deadline after that.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [])\n  const [paused, setPaused] = React.useState(false)\n  const tripped = React.useRef(false)\n\n  // Stopping holds the time left; starting again carries it over, so time\n  // spent stopped doesn't count against the window.\n  const held = React.useRef<number | null>(null)\n  React.useEffect(() => {\n    const t = Date.now()\n    if (deadline === null) return\n    if (!running) {\n      held.current = Math.max(0, deadline - t)\n    } else if (held.current !== null) {\n      setDeadline(t + held.current)\n      setNow(t)\n      held.current = null\n    }\n    // Only a change of `running` moves the deadline.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [running])\n\n  React.useEffect(() => {\n    if (!running || paused || deadline === null) return\n    const timer = window.setInterval(() => setNow(Date.now()), 250)\n    return () => window.clearInterval(timer)\n  }, [running, paused, deadline === null])\n\n  const remaining = deadline === null ? interval : Math.max(0, deadline - now)\n  React.useEffect(() => {\n    if (running && !paused && remaining === 0 && !tripped.current) {\n      tripped.current = true\n      setPaused(true)\n      onTrip?.()\n    }\n  }, [remaining, running, paused, onTrip])\n\n  const share = remaining / interval\n  const stage: VigilanceStage = paused ? \"paused\" : share <= stopAt ? \"stopping\" : share <= warnAt ? \"warning\" : \"clear\"\n\n  const context = React.useMemo<VigilanceContextValue>(\n    () => ({\n      stage,\n      remaining,\n      interval,\n      checkIn: () => {\n        if (paused) return\n        const t = Date.now()\n        setNow(t)\n        setDeadline(t + interval)\n        onCheckIn?.()\n      },\n      resume: () => {\n        const t = Date.now()\n        tripped.current = false\n        setPaused(false)\n        setNow(t)\n        setDeadline(t + interval)\n        onResume?.()\n      },\n    }),\n    [stage, remaining, interval, paused, onCheckIn, onResume]\n  )\n\n  return (\n    <VigilanceContext value={context}>\n      <div\n        role=\"group\"\n        data-slot=\"vigilance\"\n        data-stage={stage}\n        className={cn(\n          \"bevel-metal bevel-object flex w-72 flex-col gap-4 rounded-(--bevel-radius-md) p-4 text-(--bevel-ivory)\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </VigilanceContext>\n  )\n}\n\nconst lamps: { stage: VigilanceStage; label: string }[] = [\n  { stage: \"clear\", label: \"Clear\" },\n  { stage: \"warning\", label: \"Warning\" },\n  { stage: \"stopping\", label: \"Stopping\" },\n]\n\n/**\n * Three lamps and the stage in words. Steady, never flashing. The final\n * warning and the pause are announced assertively.\n */\nfunction VigilanceLamps({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { stage, remaining } = useVigilance()\n  const seconds = Math.ceil(remaining / 1000)\n  const urgent = stage === \"stopping\" || stage === \"paused\"\n  return (\n    <div data-slot=\"vigilance-lamps\" className={cn(\"flex flex-col gap-2\", className)} {...props}>\n      <div className=\"flex justify-between gap-2\">\n        {lamps.map((lamp) => (\n          <span\n            key={lamp.stage}\n            data-slot=\"vigilance-lamp\"\n            data-stage={stage}\n            data-lit={\n              lamp.stage === stage || (lamp.stage === \"stopping\" && stage === \"paused\") ? \"\" : undefined\n            }\n            className=\"bevel-deboss flex items-center gap-1.5 text-(length:--bevel-fine) font-semibold tracking-(--bevel-tracking-print) uppercase\"\n          >\n            <span aria-hidden className=\"bevel-led size-2.5 rounded-full\" />\n            {lamp.label}\n          </span>\n        ))}\n      </div>\n      <div className=\"flex min-h-5 items-baseline justify-between gap-3 text-sm\">\n        {/* Only the stage is live, so it's announced once per change, not every second. */}\n        <p role={urgent ? \"alert\" : \"status\"}>\n          {stage === \"paused\"\n            ? \"Paused: no check-in.\"\n            : stage === \"stopping\"\n              ? \"Check in now or the run pauses.\"\n              : stage === \"warning\"\n                ? \"Check in soon.\"\n                : \"Running.\"}\n        </p>\n        {stage !== \"paused\" && (\n          <span aria-hidden className=\"font-mono text-(length:--bevel-fine) tabular-nums opacity-80\">\n            {seconds}s\n          </span>\n        )}\n      </div>\n    </div>\n  )\n}\n\n/** Time left until the next check-in. */\nfunction VigilanceTimer({ className, ...props }: Omit<ProgressPrimitive.Root.Props, \"value\">) {\n  const { remaining, interval } = useVigilance()\n  return (\n    <ProgressPrimitive.Root\n      value={Math.round(remaining / 1000)}\n      max={Math.round(interval / 1000)}\n      aria-label=\"Time until next check-in\"\n      getAriaValueText={(_, v) => `${v ?? 0} seconds left`}\n      className={cn(\"w-full\", className)}\n      {...props}\n    >\n      <ProgressPrimitive.Track className=\"bevel-recessed relative h-2 overflow-hidden rounded-full\">\n        <span\n          aria-hidden\n          className=\"absolute inset-0 origin-left rounded-full bg-(--bevel-led) transition-[scale] duration-(--vigilance-tick) ease-linear motion-reduce:transition-none in-data-[stage=clear]:[--bevel-led:var(--vigilance-clear)] in-data-[stage=warning]:[--bevel-led:var(--vigilance-warning)] in-data-[stage=stopping]:[--bevel-led:var(--vigilance-stopping)]\"\n          style={{ scale: `${remaining / interval} 1` }}\n        />\n      </ProgressPrimitive.Track>\n    </ProgressPrimitive.Root>\n  )\n}\n\n/** The check-in button. While paused it becomes Resume. */\nfunction VigilanceButton({ className, children, ...props }: Omit<ButtonPrimitive.Props, \"onClick\">) {\n  const { stage, checkIn, resume } = useVigilance()\n  const paused = stage === \"paused\"\n  return (\n    <ButtonPrimitive\n      data-slot=\"vigilance-button\"\n      onClick={paused ? resume : checkIn}\n      className={cn(\n        \"bevel-raised mx-auto grid size-20 place-items-center rounded-full text-center text-(length:--bevel-label) font-black tracking-(--bevel-tracking-label) text-(--bevel-ivory-foreground) uppercase outline-none select-none\",\n        \"transition-[translate,box-shadow] duration-(--bevel-duration-press) ease-(--bevel-ease-out) active:translate-y-0.5 active:bevel-pressed motion-reduce:transition-none\",\n        \"focus-visible:ring-3 focus-visible:ring-(--bevel-ivory)/50 focus-visible:ring-offset-2 focus-visible:ring-offset-(--vigilance-panel)\",\n        className\n      )}\n      {...props}\n    >\n      {paused ? \"Resume\" : (children ?? \"I'm here\")}\n    </ButtonPrimitive>\n  )\n}\n\nexport { Vigilance, VigilanceLamps, VigilanceTimer, VigilanceButton }\nexport type { VigilanceProps, VigilanceStage }\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    ":root": {
      "--vigilance-panel": "oklch(0.26 0.008 260)",
      "--vigilance-panel-edge": "oklch(0.17 0.008 260)",
      "--vigilance-clear": "oklch(0.8 0.17 150)",
      "--vigilance-warning": "oklch(0.83 0.16 80)",
      "--vigilance-stopping": "oklch(0.66 0.21 27)",
      "--vigilance-off": "oklch(0.36 0.01 260)",
      "--vigilance-tick": "250ms"
    },
    "[data-slot=\"vigilance\"]": {
      "--bevel-material": "var(--vigilance-panel)",
      "--bevel-material-edge": "var(--vigilance-panel-edge)",
      "--bevel-material-recess": "var(--vigilance-panel-edge)"
    },
    "[data-slot=\"vigilance-button\"]": {
      "--bevel-material": "var(--bevel-brass)",
      "--bevel-material-edge": "var(--bevel-brass-edge)"
    },
    "[data-slot=\"vigilance-lamp\"]": {
      "--bevel-led": "var(--vigilance-off)"
    },
    "[data-slot=\"vigilance-lamp\"][data-lit][data-stage=\"clear\"]": {
      "--bevel-led": "var(--vigilance-clear)"
    },
    "[data-slot=\"vigilance-lamp\"][data-lit][data-stage=\"warning\"]": {
      "--bevel-led": "var(--vigilance-warning)"
    },
    "[data-slot=\"vigilance-lamp\"][data-lit][data-stage=\"stopping\"], [data-slot=\"vigilance-lamp\"][data-lit][data-stage=\"paused\"]": {
      "--bevel-led": "var(--vigilance-stopping)"
    }
  },
  "categories": [
    "coordination"
  ],
  "type": "registry:ui"
}