{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spotlight-card",
  "type": "registry:ui",
  "title": "Spotlight Card",
  "description": "Card with a cursor-following spotlight gradient effect on hover.",
  "dependencies": [
    "@saasflare/ui",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/spotlight-card.tsx",
      "type": "registry:ui",
      "content": "\"use client\"\n\n/**\n * @fileoverview Card with a mouse-following spotlight gradient highlight.\n * @author Saasflare™\n * Renders a card with a radial gradient that tracks the cursor position,\n * creating a spotlight effect. Built on top of the Saasflare Card primitive.\n * @module packages/ui/components/ui/spotlight-card\n * @package ui\n *\n * @component\n * @example\n * import { SpotlightCard } from '@saasflare/ui';\n * <SpotlightCard>\n *   <h3>Feature Title</h3>\n *   <p>Feature description text</p>\n * </SpotlightCard>\n *\n * @example\n * // Custom spotlight color\n * <SpotlightCard spotlightColor=\"var(--chart-1)\" spotlightSize={300}>\n *   <p>Highlighted content</p>\n * </SpotlightCard>\n */\n\nimport { useEffect, useRef, useState, type ReactNode } from \"react\"\nimport { m } from \"motion/react\"\nimport { cn } from \"@saasflare/ui\"\nimport { useSaasflareMotion, spring } from \"@saasflare/ui\"\nimport { useSaasflareProps, type SaasflareComponentProps } from \"@saasflare/ui\"\n\n/** Motion-reserved DOM handlers that collide with Motion's own props. */\ntype MotionConflicts = \"onDrag\" | \"onDragStart\" | \"onDragEnd\" | \"onAnimationStart\" | \"onAnimationEnd\"\n\n/** Props for the SpotlightCard component. */\nexport interface SpotlightCardProps\n  extends Omit<React.ComponentProps<\"div\">, MotionConflicts | keyof SaasflareComponentProps>,\n    SaasflareComponentProps {\n  /** Card content. */\n  children: ReactNode\n  /** Spotlight gradient color. Default: `\"var(--primary)\"` */\n  spotlightColor?: string\n  /** Spotlight diameter in pixels. Default: `250` */\n  spotlightSize?: number\n  /** Spotlight opacity (0–1). Default: `0.08` */\n  spotlightOpacity?: number\n  /** Additional class names. */\n  className?: string\n}\n\n/**\n * Card with a radial gradient that follows the mouse cursor.\n *\n * - Gradient overlay tracks the mouse within the card boundaries\n * - Fades out when the mouse leaves\n * - Uses CSS for the gradient (no canvas, no heavy re-renders)\n * - Falls back to a plain card when motion is disabled (`animated={false}` or reduced motion)\n *\n * @component\n * @package ui\n */\nexport function SpotlightCard({\n  children,\n  spotlightColor = \"var(--primary)\",\n  spotlightSize = 250,\n  spotlightOpacity = 0.08,\n  className,\n  surface,\n  radius,\n  animated,\n  iconWeight,\n  ...props\n}: SpotlightCardProps) {\n  const sf = useSaasflareProps({ surface, radius, animated, iconWeight })\n  const motion = useSaasflareMotion(sf.animated, spring)\n  const cardRef = useRef<HTMLDivElement>(null)\n  const overlayRef = useRef<HTMLDivElement>(null)\n  const rectRef = useRef<DOMRect | null>(null)\n  const [isHovered, setIsHovered] = useState(false)\n\n  /* Hot path stays out of React: the cursor position is written as CSS\n   * variables on the overlay (no per-frame re-render), and the card rect is\n   * cached for the hover duration (gBCR per mousemove forces sync layout).\n   * Cache invalidates on enter/scroll/resize. */\n  useEffect(() => {\n    const card = cardRef.current\n    if (!card || motion.disabled) return\n    const invalidate = () => {\n      rectRef.current = null\n    }\n    const onMove = (e: MouseEvent) => {\n      const overlay = overlayRef.current\n      if (!overlay) return\n      const rect = rectRef.current ?? (rectRef.current = card.getBoundingClientRect())\n      overlay.style.setProperty(\"--spotlight-x\", `${e.clientX - rect.left}px`)\n      overlay.style.setProperty(\"--spotlight-y\", `${e.clientY - rect.top}px`)\n    }\n    card.addEventListener(\"mousemove\", onMove, { passive: true })\n    window.addEventListener(\"scroll\", invalidate, { passive: true, capture: true })\n    window.addEventListener(\"resize\", invalidate, { passive: true })\n    return () => {\n      card.removeEventListener(\"mousemove\", onMove)\n      window.removeEventListener(\"scroll\", invalidate, { capture: true })\n      window.removeEventListener(\"resize\", invalidate)\n    }\n  }, [motion.disabled])\n\n  return (\n    <m.div\n      ref={cardRef}\n      {...props}\n      onMouseEnter={\n        motion.disabled\n          ? undefined\n          : () => {\n              rectRef.current = null\n              setIsHovered(true)\n            }\n      }\n      onMouseLeave={motion.disabled ? undefined : () => setIsHovered(false)}\n      data-slot=\"spotlight-card\"\n      data-surface={sf.surface}\n      data-radius={sf.radius}\n      data-animated={String(sf.animated)}\n      className={cn(\n        \"relative overflow-hidden rounded-xl border surface-card p-6 text-card-foreground\",\n        \"transition-all duration-200 hover:-translate-y-px hover:shadow-md\",\n        \"motion-reduce:hover:transform-none\",\n        className,\n      )}\n    >\n      {/* Spotlight gradient overlay */}\n      {!motion.disabled && (\n        <div\n          ref={overlayRef}\n          className=\"pointer-events-none absolute inset-0 transition-opacity duration-300\"\n          style={{\n            opacity: isHovered ? spotlightOpacity : 0,\n            background: `radial-gradient(${spotlightSize}px circle at var(--spotlight-x, 50%) var(--spotlight-y, 50%), ${spotlightColor} 0%, transparent 70%)`,\n          }}\n          aria-hidden=\"true\"\n        />\n      )}\n\n      {/* Content */}\n      <div className=\"relative z-10\">{children}</div>\n    </m.div>\n  )\n}\n",
      "target": "components/ui/spotlight-card.tsx"
    }
  ],
  "$meta": {
    "source": "https://ui.saasflare.io/r/spotlight-card.json"
  }
}
