{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spotlight",
  "type": "registry:ui",
  "title": "Spotlight",
  "description": "The soft, angled glow that fades in behind hero copy: one rotated ellipse under a 151-unit Gaussian blur, absolutely positioned and sized as a percentage of its container. `fill` defaults to `currentColor`, so a text-colour class on the parent tints it.",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "decorative",
    "effect"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/aceternity/spotlight.tsx",
      "target": "components/ui/aceternity/spotlight.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport React, { forwardRef, useId } from \"react\";\n\n// @interlace/spotlight v1.2.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/spotlight\n// What changed since: https://ds.interlace.tools/c/spotlight#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — Spotlight\n *\n * The soft, angled glow that fades in behind hero copy: one rotated ellipse\n * under a 151-unit Gaussian blur, absolutely positioned and sized as a\n * percentage of its container. `fill` defaults to `currentColor`, so a\n * text-colour class on the parent tints it.\n *\n * Decorative by contract — `aria-hidden`, `focusable=\"false\"`,\n * `pointer-events-none`, and out of flow, so CLS is zero.\n *\n * Our reimplementation of the Aceternity UI \"Spotlight\". What is ours: the\n * reveal is self-contained — a `motion` fade with `duration` and `delay` props\n * rather than an external `animate-spotlight` keyframe a consumer has to\n * import; the fill is a token/`currentColor` rather than a literal; and the\n * blur filter id comes from `useId()` so two Spotlights on one page do not\n * collide on a shared `url(#filter)`.\n *\n * ## Anatomy\n *\n *   Spotlight                        (motion.svg — data-slot=\"spotlight\",\n *                                     viewBox \"0 0 3787 2842\")\n *     ├─ g filter=url(#spotlight-blur-…)\n *     │   └─ ellipse                 (rotated by a transform matrix)\n *     └─ defs > filter               (feFlood → feBlend → feGaussianBlur 151)\n *\n * ## Motion\n *\n * JS-driven — a `motion/react` opacity tween, invisible to the CSS reset in\n * `styles/preflight.css`. It is gated in JS: under\n * `prefers-reduced-motion: reduce` the component passes `initial={false}` and\n * `transition={undefined}`, so the glow is simply present at full opacity on\n * first paint with no tween scheduled. Nothing is lost visually; only the\n * 1.5s fade goes away. This is the only motion in the file — the glow does not\n * loop or drift.\n *\n * ## Types\n *\n * `SpotlightNativeProps` omits `onAnimationStart` / `onAnimationEnd` /\n * `onDrag*` and `values` from the native `<svg>` props, because `motion`\n * overloads all five for its own gesture and animation lifecycle. Extending\n * the native element without that `Omit` is a type conflict, not a choice.\n */\n\nimport { motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\n\n// =========================================\n// CONSTANTS\n// =========================================\n\n/**\n * The source artwork is authored at this viewBox; positions/radii below are\n * expressed in these units so the glow scales cleanly at any rendered size.\n */\nconst SPOTLIGHT_VIEWBOX = \"0 0 3787 2842\";\n\n/** Gaussian blur radius (in viewBox units) that turns the ellipse into a glow. */\nconst BLUR_DEVIATION = 151;\n\n// =========================================\n// TYPES\n// =========================================\n\n/**\n * Native `<svg>` props minus the four animation/drag handlers whose DOM\n * signatures collide with `motion`'s own (it overloads them for gesture +\n * animation lifecycle callbacks). Omitting them lets us extend the native\n * element — per the floor — without a type conflict.\n */\ntype SpotlightNativeProps = Omit<\n  React.ComponentPropsWithoutRef<\"svg\">,\n  | \"onAnimationStart\"\n  | \"onAnimationEnd\"\n  | \"onDrag\"\n  | \"onDragStart\"\n  | \"onDragEnd\"\n  // Native SVG `values` (a string, for <animate>/filter timing) collides with\n  // motion's `values` (a MotionValue map). Not meaningful on the root <svg>.\n  | \"values\"\n>;\n\nexport interface SpotlightProps extends SpotlightNativeProps {\n  /**\n   * Required stable selector for E2E tests. Has no runtime default — the\n   * consumer must provide one so omissions surface instead of being masked.\n   */\n  \"data-testid\": string;\n  /**\n   * Fill color of the glow. Accepts any CSS color *token* — a CSS custom\n   * property (`var(--brand-accent)`), a Tailwind-driven keyword, or\n   * `currentColor` to inherit the container's text color. Avoid raw hex/rgb\n   * literals so the design system stays the single source of color truth.\n   * @default \"currentColor\"\n   */\n  fill?: string;\n  /**\n   * Opacity of the glow once fully revealed, 0–1.\n   * @default 0.21\n   */\n  fillOpacity?: number;\n  /**\n   * Duration of the fade-in reveal, in seconds. Ignored under\n   * `prefers-reduced-motion` (the glow renders static at full opacity).\n   * @default 1.5\n   */\n  duration?: number;\n  /**\n   * Delay before the reveal starts, in seconds. Ignored under\n   * `prefers-reduced-motion`.\n   * @default 0.2\n   */\n  delay?: number;\n}\n\n// =========================================\n// COMPONENT\n// =========================================\n\nexport const Spotlight = forwardRef<SVGSVGElement, SpotlightProps>(\n  function Spotlight(\n    {\n      className,\n      fill = \"currentColor\",\n      fillOpacity = 0.21,\n      duration = 1.5,\n      delay = 0.2,\n      ...props\n    },\n    ref,\n  ) {\n    const reduceMotion = useReducedMotion();\n    // Namespace the filter id so multiple Spotlights on one page don't collide\n    // on a shared `url(#filter)` reference.\n    const rawId = useId();\n    const filterId = `spotlight-blur-${rawId.replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n    return (\n      <motion.svg\n        ref={ref}\n        data-slot=\"spotlight\"\n        aria-hidden=\"true\"\n        focusable=\"false\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox={SPOTLIGHT_VIEWBOX}\n        fill=\"none\"\n        // Reduced motion: render the glow already-present, no animation.\n        initial={reduceMotion ? false : { opacity: 0 }}\n        animate={{ opacity: 1 }}\n        transition={\n          reduceMotion ? undefined : { duration, delay, ease: \"easeOut\" }\n        }\n        className={cn(\n          \"pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%]\",\n          className,\n        )}\n        {...props}\n      >\n        <g filter={`url(#${filterId})`}>\n          <ellipse\n            cx=\"1924.71\"\n            cy=\"273.501\"\n            rx=\"1924.71\"\n            ry=\"273.501\"\n            transform=\"matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)\"\n            fill={fill}\n            fillOpacity={fillOpacity}\n          />\n        </g>\n        <defs>\n          <filter\n            id={filterId}\n            x=\"0.860352\"\n            y=\"0.838989\"\n            width=\"3785.16\"\n            height=\"2840.26\"\n            filterUnits=\"userSpaceOnUse\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feFlood floodOpacity=\"0\" result=\"BackgroundImageFix\" />\n            <feBlend\n              mode=\"normal\"\n              in=\"SourceGraphic\"\n              in2=\"BackgroundImageFix\"\n              result=\"shape\"\n            />\n            <feGaussianBlur\n              stdDeviation={BLUR_DEVIATION}\n              result=\"effect1_foregroundBlur\"\n            />\n          </filter>\n        </defs>\n      </motion.svg>\n    );\n  },\n);\n"
    }
  ],
  "meta": {
    "tier": "effect",
    "client": true,
    "minViewport": null,
    "loading": false,
    "version": "1.2.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/spotlight\n\nInstalled to `components/ui/aceternity/spotlight.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/aceternity/spotlight';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/spotlight\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
