{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spotlight-card",
  "title": "Spotlight Card",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A card whose surface and border pick up a soft glow that follows the pointer, driven by CSS variables with no re-render per move. Keyboard focus shows a steady glow.",
  "dependencies": [
    "motion@^12"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/spotlight-card.tsx",
      "content": "// Ballmac UI: Spotlight Card. https://ui.ballmac.com/components/spotlight-card\n\"use client\"\n\nimport * as React from \"react\"\nimport { useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype SpotlightCardProps = React.ComponentProps<\"div\"> & {\n  /** Radius of the glow in pixels. */\n  size?: number\n}\n\n// Shows only the element's border box ring: the padding box is cut out of the full box.\nconst borderOnlyMask: React.CSSProperties = {\n  WebkitMask: \"linear-gradient(black, black) padding-box, linear-gradient(black, black)\",\n  WebkitMaskComposite: \"xor\",\n  mask: \"linear-gradient(black, black) padding-box exclude, linear-gradient(black, black)\",\n}\n\nfunction isFocusVisible(element: Element) {\n  try {\n    return element.matches(\":focus-visible\")\n  } catch {\n    return false\n  }\n}\n\nfunction SpotlightCard({\n  size = 320,\n  className,\n  style,\n  children,\n  onPointerMove,\n  onFocus,\n  ...props\n}: SpotlightCardProps) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  const reduceMotion = useReducedMotion()\n\n  // Written straight to CSS variables: no React render per pointer move.\n  const moveTo = (x: string, y: string) => {\n    ref.current?.style.setProperty(\"--spotlight-x\", x)\n    ref.current?.style.setProperty(\"--spotlight-y\", y)\n  }\n\n  return (\n    <div\n      ref={ref}\n      data-slot=\"spotlight-card\"\n      className={cn(\n        \"group/spotlight relative isolate rounded-xl border bg-card p-6 text-card-foreground shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        className\n      )}\n      style={\n        {\n          \"--spotlight-size\": `${size}px`,\n          \"--spotlight-x\": \"50%\",\n          \"--spotlight-y\": \"0%\",\n          ...style,\n        } as React.CSSProperties\n      }\n      onPointerMove={(event) => {\n        if (!reduceMotion && event.pointerType !== \"touch\") {\n          const rect = event.currentTarget.getBoundingClientRect()\n          moveTo(`${event.clientX - rect.left}px`, `${event.clientY - rect.top}px`)\n        }\n        onPointerMove?.(event)\n      }}\n      onFocus={(event) => {\n        // Keyboard focus gets a steady glow from the top edge.\n        if (isFocusVisible(event.target)) moveTo(\"50%\", \"0%\")\n        onFocus?.(event)\n      }}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        data-slot=\"spotlight-card-surface\"\n        className=\"pointer-events-none absolute inset-0 -z-10 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/spotlight:opacity-100 group-focus-visible/spotlight:opacity-100 group-has-[:focus-visible]/spotlight:opacity-100\"\n        style={{\n          background:\n            \"radial-gradient(var(--spotlight-size) circle at var(--spotlight-x) var(--spotlight-y), color-mix(in oklch, var(--ring) 11%, transparent), transparent 70%)\",\n        }}\n      />\n      <span\n        aria-hidden=\"true\"\n        data-slot=\"spotlight-card-border\"\n        className=\"pointer-events-none absolute -inset-px rounded-[inherit] border border-transparent opacity-0 transition-opacity duration-200 group-hover/spotlight:opacity-100 group-focus-visible/spotlight:opacity-100 group-has-[:focus-visible]/spotlight:opacity-100\"\n        style={{\n          ...borderOnlyMask,\n          background:\n            \"radial-gradient(calc(var(--spotlight-size) * 0.75) circle at var(--spotlight-x) var(--spotlight-y), color-mix(in oklch, var(--ring) 75%, transparent), transparent 70%) border-box\",\n        }}\n      />\n      {children}\n    </div>\n  )\n}\n\nexport { SpotlightCard, type SpotlightCardProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/spotlight-card.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "card",
      "hover",
      "glow",
      "pointer",
      "feature",
      "motion"
    ],
    "version": "1.0.0",
    "updated": "2026-09-28",
    "ai": {
      "summary": "A drop-in card surface (rounded-xl, border, bg-card, p-6) with a pointer-following glow in the --ring color. Put any content inside; if the card contains a link or button, focusing it lights the card.",
      "whenToUse": [
        "Feature grids on landing pages",
        "Pricing or plan cards that should respond to hover",
        "Clickable tiles in a dashboard or settings index"
      ],
      "whenNotToUse": [
        "Dense data tables or long lists of cards (the effect loses meaning when everything glows)",
        "Plain content containers with no interaction (use card)"
      ],
      "composesWith": [
        "badge",
        "button",
        "border-beam"
      ],
      "a11y": [],
      "customization": [
        "size: glow radius in px (default 320)",
        "Glow color follows --ring; override per card with className like [--ring:var(--chart-2)]",
        "Surface styles are ordinary classes: replace p-6, rounded-xl or bg-card through className",
        "Parts: [data-slot=spotlight-card-surface] and [data-slot=spotlight-card-border]"
      ]
    },
    "examples": [
      "spotlight-card-demo",
      "spotlight-card-grid"
    ],
    "url": "https://ui.ballmac.com/components/spotlight-card"
  },
  "categories": [
    "motion"
  ],
  "type": "registry:ui"
}