{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spotlight-card",
  "type": "registry:ui",
  "title": "Spotlight Card",
  "description": "A card that reveals a soft radial light — inner glow and border highlight — under the pointer.",
  "author": "WANSTUDIO <https://wanstudio.my>",
  "dependencies": [],
  "files": [
    {
      "path": "registry/wancraft/spotlight-card.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport interface SpotlightCardProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /** Diameter of the spotlight in px. */\n  size?: number;\n  /** Spotlight colour (hex or any CSS colour). */\n  accent?: string;\n  /** Strength of the inner glow, 0–1. */\n  opacity?: number;\n  /** Also light up the 1px border under the pointer. */\n  borderGlow?: boolean;\n  /** Fade in / out duration in ms. */\n  fadeDuration?: number;\n  /** Corner radius in px. */\n  rounded?: number;\n}\n\n/**\n * A card that reveals a soft radial light — inner glow plus border highlight — under the pointer.\n * Position is written to CSS variables on pointer move, so the card never re-renders.\n * Keyboard focus inside the card shows a centred spotlight.\n */\nexport const SpotlightCard = React.forwardRef<HTMLDivElement, SpotlightCardProps>(function SpotlightCard(\n  {\n    size = 360,\n    accent = \"#FF7448\",\n    opacity = 0.16,\n    borderGlow = true,\n    fadeDuration = 400,\n    rounded = 20,\n    className = \"\",\n    style,\n    children,\n    onPointerMove,\n    onPointerDown,\n    onPointerLeave,\n    onFocus,\n    onBlur,\n    ...props\n  },\n  ref,\n) {\n  const innerRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(ref, () => innerRef.current as HTMLDivElement);\n\n  /** Moves the light (px, relative to the card) and switches it on. */\n  const light = (x: number, y: number) => {\n    const el = innerRef.current;\n    if (!el) return;\n    el.style.setProperty(\"--sc-x\", `${x}px`);\n    el.style.setProperty(\"--sc-y\", `${y}px`);\n    if (el.dataset.lit !== \"true\") el.dataset.lit = \"true\";\n  };\n\n  const lightAtPointer = (event: React.PointerEvent<HTMLDivElement>) => {\n    const rect = event.currentTarget.getBoundingClientRect();\n    light(event.clientX - rect.left, event.clientY - rect.top);\n  };\n\n  const dim = () => {\n    if (innerRef.current) innerRef.current.dataset.lit = \"false\";\n  };\n\n  const strength = `${Math.round(Math.min(Math.max(opacity, 0), 1) * 100)}%`;\n  const fade = { transitionDuration: `${Math.max(fadeDuration, 0)}ms` };\n\n  return (\n    <div\n      ref={innerRef}\n      className={`group/spotlight relative isolate border border-zinc-900/10 bg-white text-zinc-900 dark:border-white/10 dark:bg-[#15181b] dark:text-zinc-100 ${className}`}\n      style={{\n        borderRadius: rounded,\n        [\"--sc-accent\" as string]: accent,\n        [\"--sc-size\" as string]: `${size}px`,\n        [\"--sc-strength\" as string]: strength,\n        ...style,\n      }}\n      onPointerMove={(event) => {\n        onPointerMove?.(event);\n        lightAtPointer(event);\n      }}\n      onPointerDown={(event) => {\n        onPointerDown?.(event);\n        lightAtPointer(event);\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        dim();\n      }}\n      onFocus={(event) => {\n        onFocus?.(event);\n        // Keyboard users get a centred light; mouse focus is already lit by the pointer.\n        if ((event.target as Element).matches(\":focus-visible\")) {\n          const el = innerRef.current;\n          if (el) light(el.clientWidth / 2, el.clientHeight / 2);\n        }\n      }}\n      onBlur={(event) => {\n        onBlur?.(event);\n        if (!event.currentTarget.contains(event.relatedTarget as Node | null)) dim();\n      }}\n      {...props}\n    >\n      {/* Inner glow */}\n      <span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute inset-0 -z-10 opacity-0 transition-opacity ease-out group-data-[lit=true]/spotlight:opacity-100 motion-reduce:transition-none\"\n        style={{\n          ...fade,\n          borderRadius: \"inherit\",\n          background:\n            \"radial-gradient(var(--sc-size) circle at var(--sc-x, 50%) var(--sc-y, 50%), color-mix(in oklab, var(--sc-accent) var(--sc-strength), transparent), transparent 70%)\",\n        }}\n      />\n      {/* Border highlight: the same gradient, masked down to a 1px ring over the border. */}\n      {borderGlow && (\n        <span\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute -inset-px opacity-0 transition-opacity ease-out group-data-[lit=true]/spotlight:opacity-100 motion-reduce:transition-none\"\n          style={{\n            ...fade,\n            borderRadius: \"inherit\",\n            padding: 1,\n            background:\n              \"radial-gradient(calc(var(--sc-size) * 0.75) circle at calc(var(--sc-x, 50%) + 1px) calc(var(--sc-y, 50%) + 1px), var(--sc-accent), transparent 70%)\",\n            WebkitMask: \"linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)\",\n            WebkitMaskComposite: \"xor\",\n            mask: \"linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)\",\n          }}\n        />\n      )}\n      {children}\n    </div>\n  );\n});\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "cards"
  ],
  "docs": "Documentation: https://wancraft.my/components/spotlight-card"
}
