{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spotlight-card",
  "type": "registry:ui",
  "title": "Spotlight Card",
  "description": "A surface with a soft highlight that follows the pointer and fades out on leave.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/loomui/spotlight-card.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface SpotlightCardProps extends React.ComponentProps<\"div\"> {\n  /** Diameter of the surface highlight in pixels. */\n  size?: number\n  /** Fill of the surface wash. Any CSS colour. Keep the alpha low. */\n  color?: string\n  /** Fill of the border highlight. Brighter than `color` on purpose. */\n  borderColor?: string\n  /** Mark the whole card as a target: pointer cursor and a focus ring. */\n  interactive?: boolean\n  /** Render a plain card with no highlight. */\n  disabled?: boolean\n}\n\n/** Shows only the 1px ring of a layer, hiding the fill inside it. */\nconst RING_MASK: React.CSSProperties = {\n  padding: 1,\n  WebkitMask:\n    \"linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)\",\n  WebkitMaskComposite: \"xor\",\n  mask: \"linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)\",\n  maskComposite: \"exclude\",\n}\n\nexport function SpotlightCard({\n  children,\n  className,\n  size = 320,\n  color = \"color-mix(in oklch, currentColor 10%, transparent)\",\n  borderColor = \"color-mix(in oklch, var(--primary, currentColor) 60%, transparent)\",\n  interactive = false,\n  disabled = false,\n  style,\n  ...props\n}: SpotlightCardProps) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  const frame = React.useRef(0)\n\n  // Pointer position is written to CSS custom properties on the next frame.\n  // Nothing here goes through state, so moving the mouse costs no renders.\n  const handlePointerMove = React.useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      if (disabled) {\n        return\n      }\n      const node = ref.current\n      if (!node) {\n        return\n      }\n\n      const { clientX, clientY } = event\n      cancelAnimationFrame(frame.current)\n      frame.current = requestAnimationFrame(() => {\n        const rect = node.getBoundingClientRect()\n        node.style.setProperty(\"--spotlight-x\", `${clientX - rect.left}px`)\n        node.style.setProperty(\"--spotlight-y\", `${clientY - rect.top}px`)\n      })\n    },\n    [disabled]\n  )\n\n  React.useEffect(() => () => cancelAnimationFrame(frame.current), [])\n\n  const highlight = (fill: string) =>\n    `radial-gradient(var(--spotlight-size) circle at var(--spotlight-x, 50%) var(--spotlight-y, 50%), ${fill}, transparent 70%)`\n\n  const layer =\n    \"pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-[180ms] ease-[var(--ease-out-quart)] group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none\"\n\n  return (\n    <div\n      ref={ref}\n      data-slot=\"spotlight-card\"\n      onPointerMove={handlePointerMove}\n      className={cn(\n        \"group relative isolate overflow-hidden rounded-xl border\",\n        interactive &&\n          \"focus-within:ring-ring/50 cursor-pointer focus-within:ring-2\",\n        className\n      )}\n      style={\n        {\n          \"--spotlight-size\": `${size}px`,\n          ...style,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      {!disabled ? (\n        <>\n          {/* The lit edge is what makes the card read as live. The surface\n              wash on its own is too soft to register. */}\n          <span\n            aria-hidden=\"true\"\n            className={layer}\n            style={{ ...RING_MASK, background: highlight(borderColor) }}\n          />\n          <span\n            aria-hidden=\"true\"\n            className={layer}\n            style={{ background: highlight(color) }}\n          />\n        </>\n      ) : null}\n\n      <div className=\"relative\">{children}</div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "cssVars": {
    "theme": {
      "ease-out-quart": "cubic-bezier(0.165, 0.84, 0.44, 1)"
    }
  }
}