{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loading-state",
  "type": "registry:component",
  "title": "Loading state",
  "description": "Pixel-grid loader with shimmer and elapsed time.",
  "categories": [
    "ai-interface"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json"
  ],
  "files": [
    {
      "path": "src/registry/components/loading-state/loading-state.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * LOADING STATE — pixel-grid loader for long-running work\n *\n * Variants:\n *   Drive  — square cells, chevron wavefront driving right;\n *            the 650ms cycle is shorter than the sweep, so\n *            two fronts are always in flight\n *   Dots   — same wavefront, circular cells\n *   Orbit  — a comet lapping the grid perimeter\n *   Surfer — the Drive loader paired with a video card below\n *\n * Paired with a shimmering label and a live elapsed timer\n * in mono tabular figures. Reduced motion freezes the grid\n * to its dim state; the timer still ticks.\n * ───────────────────────────────────────────────────────── */\n\nconst chevron = Array.from({ length: 9 }, (_, i) => {\n  const r = Math.floor(i / 3),\n    c = i % 3;\n  return (c + Math.abs(r - 1)) * 90;\n});\n\nconst ORBIT_ORDER = [0, 1, 2, 5, 8, 7, 6, 3];\nconst orbit = Array.from({ length: 9 }, (_, i) => {\n  const k = ORBIT_ORDER.indexOf(i);\n  return k === -1 ? null : k * 110;\n});\n\nconst PATTERNS: Record<string, { delays: (number | null)[]; dur: number; round: boolean }> = {\n  Drive: { delays: chevron, dur: 650, round: false },\n  Dots: { delays: chevron, dur: 650, round: true },\n  Orbit: { delays: orbit, dur: 950, round: false },\n};\n\nfunction LoaderGrid({ delays, dur, round }: { delays: (number | null)[]; dur: number; round: boolean }) {\n  return (\n    <span aria-hidden className=\"grid shrink-0 grid-cols-[repeat(3,4px)] gap-[1.5px]\">\n      {delays.map((delay, index) => (\n        <span\n          key={index}\n          className={`size-[4px] bg-ink ${round ? \"rounded-full\" : \"rounded-[1px]\"} ${delay === null ? \"\" : \"animate-loading-state-pixel-on\"}`}\n          style={\n            delay === null\n              ? { opacity: 0.07 }\n              : { opacity: 0.15, animationDuration: `${dur}ms`, animationDelay: `${delay}ms` }\n          }\n        />\n      ))}\n    </span>\n  );\n}\n\nfunction useElapsed() {\n  const [ds, setDs] = useState(0);\n  useEffect(() => {\n    const t = setInterval(() => setDs((d) => d + 1), 100);\n    return () => clearInterval(t);\n  }, []);\n  const total = ds / 10;\n  if (total < 60) return `${total.toFixed(1)}s`;\n  return `${Math.floor(total / 60)}m ${(total % 60).toFixed(1)}s`;\n}\n\nexport default function LoadingState({\n  label,\n  variant = \"Drive\",\n  /** the video for the Surfer variant's card; without one (or if it fails to\n   *  load) the card shows the loader and \"Video unavailable\" */\n  videoSrc,\n}: {\n  label?: string;\n  variant?: string;\n  videoSrc?: string;\n}) {\n  const elapsed = useElapsed();\n  const surfer = variant === \"Surfer\";\n  const resolvedLabel = label ?? (surfer ? \"Transcribing the recording\" : \"Clustering stories\");\n  const [videoOk, setVideoOk] = useState(true);\n  const { delays, dur, round } = PATTERNS[variant] ?? PATTERNS.Drive;\n\n  const labelEl = (\n    <span\n      className=\"bg-clip-text text-[13px] font-medium text-transparent\"\n      style={{\n        backgroundImage: \"linear-gradient(90deg, var(--ink-3) 35%, var(--ink) 50%, var(--ink-3) 65%)\",\n        backgroundSize: \"200% 100%\",\n        animation: \"shimmer-text 1.4s linear infinite\",\n      }}\n    >\n      {resolvedLabel}\n    </span>\n  );\n  const elapsedEl = <span className=\"font-mono text-[12px] text-ink-3 tabular-nums\">{elapsed}</span>;\n\n  if (surfer) {\n    return (\n      <div role=\"status\" className=\"flex w-fit flex-col items-start\">\n        <div className=\"flex items-center gap-2.5\">\n          <LoaderGrid {...PATTERNS.Drive} />\n          {labelEl}\n          {elapsedEl}\n        </div>\n\n        {/* the context card follows the status text it is illustrating */}\n        <div\n          className=\"mt-2 w-56 overflow-hidden rounded-[10px] shadow-overlay\"\n          style={{ animation: \"pop-in 200ms cubic-bezier(0.16,1,0.3,1) both\", transformOrigin: \"top left\" }}\n        >\n          <div className=\"relative aspect-video w-full\" style={{ background: \"var(--tooltip-bg)\" }}>\n            {videoSrc && videoOk ? (\n              <video\n                src={videoSrc}\n                autoPlay\n                muted\n                loop\n                playsInline\n                onError={() => setVideoOk(false)}\n                className=\"h-full w-full object-cover\"\n              />\n            ) : (\n              <div className=\"flex h-full w-full flex-col items-center justify-center gap-1.5\">\n                <LoaderGrid {...PATTERNS.Drive} />\n                <span className=\"px-3 text-center font-mono text-[10px]\" style={{ color: \"var(--tooltip-muted)\" }}>\n                  Video unavailable\n                </span>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <output className=\"flex w-fit items-center gap-2.5\">\n      <LoaderGrid delays={delays} dur={dur} round={round} />\n      {labelEl}\n      {elapsedEl}\n    </output>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/iris/loading-state/loading-state.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/loading-state",
  "meta": {
    "kind": "component",
    "group": "AI interface",
    "export": "LoadingState",
    "docs": "https://irisui.dev/components/loading-state"
  }
}
