{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spinner",
  "title": "Spinner",
  "description": "A lit pixel travelling a 3x3 grid, four brightness levels trailing behind it.",
  "registryDependencies": [
    "@afterglow/theme"
  ],
  "files": [
    {
      "path": "registry/terminal/ui/spinner.tsx",
      "content": "import type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst TRAIL = [0, 1, 2, 5, 8, 7, 6, 3];\nconst LAP_MS = 800;\nconst CELLS = 9;\nconst GLYPHS = [\"|\", \"/\", \"-\", \"\\\\\"];\n\nfunction Spinner({\n  className,\n  variant = \"pixel\",\n  ...props\n}: React.ComponentProps<\"span\"> & { variant?: \"pixel\" | \"line\" }) {\n  if (variant === \"line\") {\n    return (\n      <span\n        aria-label=\"Loading\"\n        className={cn(\n          // One character wide and one line tall, held there rather than\n          // measured from the glyph on screen. A bar is narrower than a\n          // backslash, so a box that hugs its content would shift the text\n          // beside it four times a second.\n          \"inline-block h-[1em] w-[1ch] shrink-0 overflow-hidden text-center align-[-0.15em] font-mono leading-none\",\n          className\n        )}\n        data-slot=\"spinner\"\n        data-variant=\"line\"\n        role=\"status\"\n        {...props}\n      >\n        {/* The strip is the animation. Each glyph gets a line of its own and\n            the column is pulled up a line at a time, so the four frames land\n            where they are written rather than where a browser decides. */}\n        <span aria-hidden=\"true\" className=\"grid animate-glyph\">\n          {GLYPHS.map((glyph) => (\n            <span className=\"h-[1em] leading-none\" key={glyph}>\n              {glyph}\n            </span>\n          ))}\n        </span>\n      </span>\n    );\n  }\n\n  return (\n    <span\n      aria-label=\"Loading\"\n      className={cn(\n        \"inline-grid size-4 grid-cols-3 grid-rows-3 gap-px\",\n        className\n      )}\n      data-slot=\"spinner\"\n      data-variant=\"pixel\"\n      role=\"status\"\n      {...props}\n    >\n      {Array.from({ length: CELLS }, (_, index) => {\n        const step = TRAIL.indexOf(index);\n\n        return (\n          <span\n            className={cn(\n              \"bg-phosphor\",\n\n              step === -1 ? \"opacity-[0.16]\" : \"animate-pixel\"\n            )}\n            // biome-ignore lint/suspicious/noArrayIndexKey: the cell index is its position.\n            key={index}\n            style={\n              step === -1\n                ? undefined\n                : { animationDelay: `${-(step * LAP_MS) / TRAIL.length}ms` }\n            }\n          />\n        );\n      })}\n    </span>\n  );\n}\n\nexport { Spinner };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}