{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flywheel",
  "type": "registry:ui",
  "title": "Flywheel",
  "description": "Four machined CSS spinners — ring, dots-orbit, segmented, dual-ring — selected by variant. Conic-gradient comet head cooling to a blue-silver tail over a recessed groove; currentColor-driven, zero dependencies, lit resting frame under reduced motion.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/loaders/flywheel/flywheel.tsx",
      "type": "registry:ui",
      "target": "components/crucible/flywheel.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface FlywheelProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /**\n   * Which spinner to render.\n   * - `ring` (default): a machined groove with a white-hot comet head that\n   *   cools to a blue-silver tail, spun with heavy inertia and a faint\n   *   flicker at speed — the Crucible signature.\n   * - `dots-orbit`: a comet of beads orbiting, each breathing on a sine so\n   *   the ring has life even at rest.\n   * - `segmented`: a spoked wheel of alternating solid/gap segments with a\n   *   hot leading spoke.\n   * - `dual-ring`: two filament heads counter-rotating at different rates.\n   * @default \"ring\"\n   */\n  variant?: \"ring\" | \"dots-orbit\" | \"segmented\" | \"dual-ring\";\n  /** Overall size in px. @default 40 */\n  size?: number;\n  /** Rotation speed multiplier. 1 = default, 2 = twice as fast. @default 1 */\n  speed?: number;\n  /** Accessible label announced to screen readers. @default \"Loading\" */\n  label?: string;\n  /** Accent color for the hot head / active ring. @default \"#ffffff\" */\n  accentColor?: string;\n}\n\n// Grain tile — a tiny fractal-noise SVG, tiled at low opacity over the whole\n// widget to kill gradient banding and give the glow a shader-grade texture.\n// Built once at module scope (pure string ops — SSR-safe, deterministic).\nconst GRAIN_SVG =\n  \"<svg xmlns='http://www.w3.org/2000/svg' width='90' height='90'>\" +\n  \"<filter id='fwn'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter>\" +\n  \"<rect width='100%' height='100%' filter='url(#fwn)'/></svg>\";\nconst GRAIN_URI = `url(\"data:image/svg+xml,${encodeURIComponent(GRAIN_SVG)}\")`;\n\nconst KEYFRAMES = `\n@property --fw-spin {\n  syntax: '<angle>';\n  inherits: false;\n  initial-value: 0deg;\n}\n@keyframes crucible-flywheel-spin { to { transform: rotate(360deg); } }\n@keyframes crucible-flywheel-spin-reverse { to { transform: rotate(-360deg); } }\n@keyframes crucible-flywheel-flicker {\n  0%, 21%, 23%, 46%, 48%, 100% { opacity: 1; }\n  22%, 47% { opacity: 0.62; }\n}\n/* A sine-ish breath: dots swell and dim, never fully off — idle life. */\n@keyframes crucible-flywheel-dot {\n  0%, 100% { opacity: 0.18; transform: scale(0.7); }\n  50% { opacity: 1; transform: scale(1.15); }\n}\n/* The head bloom breathes slightly so the tip flares, not just glows flat. */\n@keyframes crucible-flywheel-bloom {\n  0%, 100% { opacity: 0.85; transform: translate(-50%, -50%) scale(1); }\n  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.18); }\n}\n[data-crucible=\"flywheel\"] .fw-rotor,\n[data-crucible=\"flywheel\"] .fw-dot { will-change: transform; }\n@media (prefers-reduced-motion: reduce) {\n  /* Pin an explicit, legible resting pose — a lit comet, never a blank box\n     and never frozen mid-blur. All animated props are committed here. */\n  [data-crucible=\"flywheel\"] .fw-rotor {\n    animation: none !important;\n    transform: rotate(-42deg) !important;\n  }\n  [data-crucible=\"flywheel\"] .fw-rotor-slow {\n    transform: rotate(38deg) !important;\n  }\n  [data-crucible=\"flywheel\"] .fw-spin-inner { animation: none !important; }\n  [data-crucible=\"flywheel\"] .fw-stroke,\n  [data-crucible=\"flywheel\"] .fw-head { animation: none !important; opacity: 1 !important; }\n  [data-crucible=\"flywheel\"] .fw-bloom {\n    animation: none !important;\n    opacity: 0.9 !important;\n    transform: translate(-50%, -50%) scale(1) !important;\n  }\n  [data-crucible=\"flywheel\"] .fw-dot {\n    animation: none !important;\n    opacity: 0.85 !important;\n    transform: scale(1) !important;\n  }\n}\n`;\n\nconst DOT_COUNT = 8;\n\n// Shared machined groove behind a rotating stroke: a recessed channel with an\n// inner shadow, a dark outer rim and a soft top rim-light so it reads as cut\n// metal, not a CSS border.\nfunction Groove({ thickness = \"var(--fw-thick)\", inset = \"0\" }: { thickness?: string; inset?: string }) {\n  return (\n    <>\n      <span\n        aria-hidden\n        className=\"pointer-events-none absolute rounded-full\"\n        style={{\n          inset,\n          border: `${thickness} solid transparent`,\n          background: `linear-gradient(to bottom, color-mix(in oklab, currentColor 24%, transparent), color-mix(in oklab, currentColor 7%, transparent)) border-box`,\n          WebkitMask: \"linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0)\",\n          WebkitMaskComposite: \"xor\",\n          mask: \"linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0)\",\n          maskComposite: \"exclude\",\n        }}\n      />\n      <span\n        aria-hidden\n        className=\"pointer-events-none absolute rounded-full\"\n        style={{\n          inset,\n          boxShadow:\n            \"inset 0 1px 1.5px rgba(0,0,0,0.65), inset 0 -1px 1px rgba(255,255,255,0.09), inset 0 0 3px rgba(0,0,0,0.5)\",\n        }}\n      />\n    </>\n  );\n}\n\n// The comet head: a blurred bloom flaring behind a sharp white-hot core, both\n// pinned to the top-centre point of the ring where the conic head sits.\nfunction Head({ bloom = true }: { bloom?: boolean }) {\n  return (\n    <>\n      {bloom && (\n        <span\n          aria-hidden\n          className=\"fw-bloom pointer-events-none absolute rounded-full\"\n          style={{\n            top: 0,\n            left: \"50%\",\n            width: \"calc(var(--fw-thick) * 5)\",\n            height: \"calc(var(--fw-thick) * 5)\",\n            transform: \"translate(-50%, -50%)\",\n            background: `radial-gradient(circle, var(--fw-accent) 0%, color-mix(in oklab, var(--fw-hot) 70%, transparent) 35%, transparent 70%)`,\n            filter: \"blur(2.5px) brightness(1.35)\",\n            animation: \"crucible-flywheel-bloom calc(var(--fw-dur) * 2) ease-in-out infinite\",\n          }}\n        />\n      )}\n      <span\n        aria-hidden\n        className=\"fw-head pointer-events-none absolute rounded-full\"\n        style={{\n          top: 0,\n          left: \"50%\",\n          width: \"calc(var(--fw-thick) * 1.7)\",\n          height: \"calc(var(--fw-thick) * 1.7)\",\n          transform: \"translate(-50%, -50%)\",\n          background: `radial-gradient(circle, var(--fw-accent) 0%, var(--fw-hot) 55%, transparent 78%)`,\n          boxShadow:\n            \"0 0 4px 1px color-mix(in oklab, var(--fw-accent) 80%, transparent), 0 0 9px 2px color-mix(in oklab, var(--fw-hot) 45%, transparent)\",\n        }}\n      />\n    </>\n  );\n}\n\n// Conic hot filament, masked to the ring band. Head at 12 o'clock (0deg),\n// cooling backward into a blue-silver tail that fades to nothing.\nconst cometConic = `conic-gradient(from 0deg,\n  var(--fw-accent) 0%,\n  color-mix(in oklab, var(--fw-hot) 55%, transparent) 2.5%,\n  transparent 6%,\n  transparent 58%,\n  color-mix(in oklab, var(--fw-trace) 45%, transparent) 70%,\n  var(--fw-cool) 86%,\n  var(--fw-hot) 96%,\n  var(--fw-accent) 100%\n)`;\n\nconst ringMask = (t: string) =>\n  `radial-gradient(farthest-side, transparent calc(100% - ${t}), #000 calc(100% - ${t}))`;\n\nfunction RingVariant() {\n  return (\n    <>\n      <Groove />\n      <div\n        className=\"fw-rotor absolute inset-0\"\n        style={{\n          animation:\n            \"crucible-flywheel-spin calc(var(--fw-dur) * 1.4) cubic-bezier(0.62, 0.02, 0.34, 1) infinite\",\n        }}\n      >\n        <span\n          aria-hidden\n          className=\"fw-stroke absolute inset-0 rounded-full\"\n          style={{\n            background: cometConic,\n            WebkitMask: ringMask(\"var(--fw-thick)\"),\n            mask: ringMask(\"var(--fw-thick)\"),\n            animation: \"crucible-flywheel-flicker calc(var(--fw-dur) * 1.4) linear infinite\",\n          }}\n        />\n        <Head />\n      </div>\n    </>\n  );\n}\n\nfunction DotsOrbitVariant() {\n  return (\n    <div\n      className=\"fw-rotor absolute inset-0\"\n      style={{\n        animation:\n          \"crucible-flywheel-spin calc(var(--fw-dur) * 1.15) cubic-bezier(0.5, 0.05, 0.5, 0.95) infinite\",\n      }}\n    >\n      {Array.from({ length: DOT_COUNT }, (_, i) => {\n        const angle = (360 / DOT_COUNT) * i;\n        // Head-most bead runs hottest; brightness cools around the ring so the\n        // orbit reads as a comet of beads, not a uniform necklace.\n        const headness = Math.round(100 - (i / DOT_COUNT) * 82);\n        const dotColor = `color-mix(in oklab, var(--fw-accent) ${headness}%, var(--fw-cool))`;\n        return (\n          // Position (rotate + radial offset) lives on this static wrapper so\n          // it never collides with the breath's own `transform: scale(...)`.\n          <span\n            key={i}\n            className=\"absolute top-1/2 left-1/2 h-[15%] w-[15%]\"\n            style={{\n              transform: `rotate(${angle}deg) translate(calc(var(--fw-size) * 0.36)) translate(-50%, -50%)`,\n            }}\n          >\n            <span\n              className=\"fw-dot block h-full w-full rounded-full\"\n              style={{\n                background: `radial-gradient(circle at 40% 35%, var(--fw-accent) 0%, ${dotColor} 45%, color-mix(in oklab, ${dotColor} 40%, transparent) 78%, transparent 100%)`,\n                boxShadow: `0 0 ${headness > 60 ? 6 : 3}px ${headness > 60 ? 1 : 0}px color-mix(in oklab, ${dotColor} ${Math.round(headness * 0.6)}%, transparent)`,\n                animation: \"crucible-flywheel-dot calc(var(--fw-dur) * 1.15) ease-in-out infinite\",\n                animationDelay: `calc(var(--fw-dur) * 1.15 / ${DOT_COUNT} * ${i} * -1)`,\n              }}\n            />\n          </span>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction SegmentedVariant() {\n  // Spoked wheel: a hot leading spoke, cool blue-silver teeth behind it.\n  const spokes = `conic-gradient(from 0deg,\n    var(--fw-accent) 0deg 22deg, transparent 22deg 45deg,\n    color-mix(in oklab, var(--fw-cool) 92%, var(--fw-hot)) 45deg 67deg, transparent 67deg 90deg,\n    color-mix(in oklab, var(--fw-cool) 78%, transparent) 90deg 112deg, transparent 112deg 135deg,\n    color-mix(in oklab, var(--fw-cool) 62%, transparent) 135deg 157deg, transparent 157deg 180deg,\n    color-mix(in oklab, var(--fw-cool) 50%, transparent) 180deg 202deg, transparent 202deg 225deg,\n    color-mix(in oklab, var(--fw-cool) 40%, transparent) 225deg 247deg, transparent 247deg 270deg,\n    color-mix(in oklab, var(--fw-cool) 32%, transparent) 270deg 292deg, transparent 292deg 315deg,\n    color-mix(in oklab, var(--fw-cool) 24%, transparent) 315deg 337deg, transparent 337deg 360deg\n  )`;\n  return (\n    <>\n      <Groove />\n      <div\n        className=\"fw-rotor absolute inset-0\"\n        style={{\n          animation:\n            \"crucible-flywheel-spin calc(var(--fw-dur) * 1.1) cubic-bezier(0.6, 0.04, 0.36, 1) infinite\",\n        }}\n      >\n        <span\n          aria-hidden\n          className=\"fw-stroke absolute inset-0 rounded-full\"\n          style={{\n            background: spokes,\n            WebkitMask: ringMask(\"calc(var(--fw-thick) * 1.15)\"),\n            mask: ringMask(\"calc(var(--fw-thick) * 1.15)\"),\n            animation: \"crucible-flywheel-flicker calc(var(--fw-dur) * 1.1) linear infinite\",\n          }}\n        />\n        <Head />\n      </div>\n    </>\n  );\n}\n\nfunction DualRingVariant() {\n  const conicHalf = (lead: string, trail: string) => `conic-gradient(from 0deg,\n    var(--fw-accent) 0%,\n    transparent 5%,\n    transparent 44%,\n    ${lead} 50%,\n    transparent 55%,\n    transparent 90%,\n    ${trail} 100%\n  )`;\n  return (\n    <>\n      <Groove />\n      <div\n        className=\"fw-rotor absolute inset-0\"\n        style={{\n          animation:\n            \"crucible-flywheel-spin calc(var(--fw-dur) * 1.25) cubic-bezier(0.55, 0.05, 0.45, 0.95) infinite\",\n        }}\n      >\n        <span\n          aria-hidden\n          className=\"fw-stroke absolute inset-0 rounded-full\"\n          style={{\n            background: conicHalf(\"var(--fw-hot)\", \"var(--fw-cool)\"),\n            WebkitMask: ringMask(\"var(--fw-thick)\"),\n            mask: ringMask(\"var(--fw-thick)\"),\n            animation: \"crucible-flywheel-flicker calc(var(--fw-dur) * 1.25) linear infinite\",\n          }}\n        />\n        <Head />\n      </div>\n      <div className=\"fw-rotor fw-rotor-slow absolute inset-[24%]\">\n        <Groove thickness=\"calc(var(--fw-thick) * 0.7)\" />\n        <div\n          className=\"fw-spin-inner absolute inset-0\"\n          style={{\n            animation:\n              \"crucible-flywheel-spin-reverse calc(var(--fw-dur) * 0.72) cubic-bezier(0.55, 0.05, 0.45, 0.95) infinite\",\n          }}\n        >\n          <span\n            aria-hidden\n            className=\"fw-stroke absolute inset-0 rounded-full\"\n            style={{\n              background: conicHalf(\n                \"color-mix(in oklab, var(--fw-cool) 85%, var(--fw-hot))\",\n                \"color-mix(in oklab, var(--fw-cool) 60%, transparent)\"\n              ),\n              WebkitMask: ringMask(\"calc(var(--fw-thick) * 0.7)\"),\n              mask: ringMask(\"calc(var(--fw-thick) * 0.7)\"),\n              opacity: 0.85,\n            }}\n          />\n        </div>\n      </div>\n    </>\n  );\n}\n\n/**\n * Flywheel — four machined CSS spinner variants selected by `variant`.\n * The track groove is currentColor-driven (wrap in a `text-*` class to\n * recolor it); the white-hot comet head is independently themeable via\n * `accentColor` and cools into a blue-silver tail. Zero dependencies,\n * SSR-safe, GPU-light.\n *\n * Crucible signature (`variant=\"ring\"`, the default): a recessed groove with\n * a conic-gradient filament — a white-hot head blooming at the tip, cooling\n * back through silver to a faint slate trace — spun with heavy inertia and a\n * flicker at speed, over a low-opacity grain that kills banding. Under\n * reduced motion every variant pins a lit resting frame — no spin, never\n * blank.\n */\nexport function Flywheel({\n  variant = \"ring\",\n  size = 40,\n  speed = 1,\n  label = \"Loading\",\n  accentColor = \"#ffffff\",\n  className,\n  style,\n  ...props\n}: FlywheelProps) {\n  const duration = `${1.1 / Math.max(speed, 0.01)}s`;\n\n  return (\n    <div\n      data-crucible=\"flywheel\"\n      role=\"status\"\n      aria-label={label}\n      className={cn(\"relative inline-block\", className)}\n      style={\n        {\n          width: size,\n          height: size,\n          \"--fw-size\": `${size}px`,\n          \"--fw-dur\": duration,\n          \"--fw-thick\": \"max(2px, calc(var(--fw-size) * 0.078))\",\n          \"--fw-accent\": accentColor,\n          // Silver the head cools through, and the cool blue-silver tail —\n          // both track the accent so custom accents stay harmonious.\n          \"--fw-hot\": `color-mix(in oklab, ${accentColor} 68%, #cdd9ee)`,\n          \"--fw-cool\": `color-mix(in oklab, ${accentColor} 26%, #7f97ba)`,\n          \"--fw-trace\": `color-mix(in oklab, ${accentColor} 15%, #5f7896)`,\n          \"--fw-grain\": GRAIN_URI,\n          ...style,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      <style>{KEYFRAMES}</style>\n      {variant === \"ring\" && <RingVariant />}\n      {variant === \"dots-orbit\" && <DotsOrbitVariant />}\n      {variant === \"segmented\" && <SegmentedVariant />}\n      {variant === \"dual-ring\" && <DualRingVariant />}\n      {/* Grain overlay — tiled fractal noise at very low opacity to kill the\n          banding in the conic tail and match a shader look. */}\n      <span\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 rounded-full\"\n        style={{\n          background: \"var(--fw-grain)\",\n          backgroundSize: \"90px 90px\",\n          opacity: 0.07,\n          mixBlendMode: \"overlay\",\n        }}\n      />\n      <span className=\"sr-only\">{label}</span>\n    </div>\n  );\n}\n"
    }
  ]
}