{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mouse-reactive-gradient",
  "type": "registry:component",
  "title": "Mouse Reactive Gradient",
  "description": "A surface with a soft radial gradient glow that follows the pointer, Apple-subtle spotlight style. Pointer position drives CSS custom properties directly (rAF-throttled), so it repaints without React re-renders or layout thrashing. Ships with a static fallback for touch and reduced-motion, and an opt-in subtle tilt.",
  "files": [
    {
      "path": "mouse-reactive-gradient.tsx",
      "target": "components/safari/mouse-reactive-gradient/mouse-reactive-gradient.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useRef,\n  type CSSProperties,\n  type ReactNode,\n} from \"react\";\n\nimport \"./mouse-reactive-gradient.css\";\n\nexport interface MouseReactiveGradientProps {\n  children?: ReactNode;\n  className?: string;\n  /**\n   * Gradient color. Accepts any valid CSS color, including `currentColor`.\n   * Defaults to the Safari accent color.\n   */\n  color?: string;\n  /** Glow radius in pixels. */\n  radius?: number;\n  /** Glow opacity at its center, 0-1. */\n  intensity?: number;\n  /**\n   * Enables a subtle 3D tilt that follows the pointer alongside the\n   * gradient. Off by default — the gradient is the primary effect.\n   */\n  tilt?: boolean;\n  /** Max tilt rotation in degrees, only used when `tilt` is true. */\n  tiltStrength?: number;\n}\n\n/**\n * A surface that renders a soft radial gradient following the pointer,\n * Apple-subtle spotlight/glow style. Pointer position is tracked as CSS\n * custom properties (`--sm-mrg-x` / `--sm-mrg-y`) so the gradient\n * repaints itself without touching layout — no React re-renders on\n * pointer move.\n *\n * Ported from the vanilla `mountProjectTilt` pointer-tracking core: same\n * pointer -> normalized 0-1 -> CSS variable pipeline, `pointer: fine` +\n * `prefers-reduced-motion` guards, and listener cleanup. Extended with\n * rAF-throttled updates, a configurable gradient (color/radius/intensity),\n * and an opt-in tilt transform.\n */\nexport function MouseReactiveGradient({\n  children,\n  className,\n  color = \"var(--sm-mrg-color, #5b8cff)\",\n  radius = 480,\n  intensity = 0.16,\n  tilt = false,\n  tiltStrength = 4,\n  ...rest\n}: MouseReactiveGradientProps) {\n  const surfaceRef = useRef<HTMLDivElement>(null);\n  const frameRef = useRef<number | null>(null);\n  const pendingRef = useRef<{ x: number; y: number } | null>(null);\n\n  const flush = useCallback(() => {\n    frameRef.current = null;\n    const el = surfaceRef.current;\n    const pending = pendingRef.current;\n    if (!el || !pending) return;\n\n    el.style.setProperty(\"--sm-mrg-x\", `${pending.x.toFixed(2)}%`);\n    el.style.setProperty(\"--sm-mrg-y\", `${pending.y.toFixed(2)}%`);\n\n    if (tilt) {\n      const rotateY = (pending.x / 100 - 0.5) * tiltStrength * 2;\n      const rotateX = (0.5 - pending.y / 100) * tiltStrength * 2;\n      el.style.setProperty(\"--sm-mrg-tilt-x\", `${rotateX.toFixed(2)}deg`);\n      el.style.setProperty(\"--sm-mrg-tilt-y\", `${rotateY.toFixed(2)}deg`);\n    }\n  }, [tilt, tiltStrength]);\n\n  useEffect(() => {\n    const el = surfaceRef.current;\n    if (!el) return undefined;\n\n    const precisePointer = window.matchMedia(\"(hover: hover) and (pointer: fine)\");\n    const reducedMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\n    // Touch devices and reduced-motion users get a static gradient anchored\n    // at the surface center — no pointer listeners are attached at all.\n    if (!precisePointer.matches || reducedMotion.matches) {\n      el.style.setProperty(\"--sm-mrg-x\", \"50%\");\n      el.style.setProperty(\"--sm-mrg-y\", \"50%\");\n      return undefined;\n    }\n\n    const handlePointerMove = (event: PointerEvent) => {\n      const rect = el.getBoundingClientRect();\n      if (rect.width === 0 || rect.height === 0) return;\n\n      const x = ((event.clientX - rect.left) / rect.width) * 100;\n      const y = ((event.clientY - rect.top) / rect.height) * 100;\n      pendingRef.current = {\n        x: Math.min(100, Math.max(0, x)),\n        y: Math.min(100, Math.max(0, y)),\n      };\n\n      if (frameRef.current === null) {\n        frameRef.current = requestAnimationFrame(flush);\n      }\n    };\n\n    const handlePointerLeave = () => {\n      pendingRef.current = { x: 50, y: 50 };\n      if (frameRef.current === null) {\n        frameRef.current = requestAnimationFrame(flush);\n      }\n    };\n\n    el.addEventListener(\"pointermove\", handlePointerMove);\n    el.addEventListener(\"pointerleave\", handlePointerLeave);\n\n    return () => {\n      el.removeEventListener(\"pointermove\", handlePointerMove);\n      el.removeEventListener(\"pointerleave\", handlePointerLeave);\n      if (frameRef.current !== null) {\n        cancelAnimationFrame(frameRef.current);\n        frameRef.current = null;\n      }\n    };\n  }, [flush]);\n\n  const style = {\n    \"--sm-mrg-color\": color,\n    \"--sm-mrg-radius\": `${radius}px`,\n    \"--sm-mrg-intensity\": intensity,\n    \"--sm-mrg-x\": \"50%\",\n    \"--sm-mrg-y\": \"50%\",\n    \"--sm-mrg-tilt-x\": \"0deg\",\n    \"--sm-mrg-tilt-y\": \"0deg\",\n  } as CSSProperties;\n\n  return (\n    <div\n      ref={surfaceRef}\n      className={`sm-mrg-surface relative overflow-hidden ${className ?? \"\"}`}\n      style={style}\n      {...rest}\n    >\n      <div\n        aria-hidden=\"true\"\n        className=\"sm-mrg-glow pointer-events-none absolute inset-0\"\n      />\n      {tilt ? (\n        <div className=\"sm-mrg-tilt-layer relative\">{children}</div>\n      ) : (\n        children\n      )}\n    </div>\n  );\n}\n"
    },
    {
      "path": "mouse-reactive-gradient.css",
      "target": "components/safari/mouse-reactive-gradient/mouse-reactive-gradient.css",
      "type": "registry:file",
      "content": "/*\n * Styles for MouseReactiveGradient. Pointer position and gradient config\n * arrive as CSS custom properties (--sm-mrg-*) set from the component;\n * this file only defines how they're painted, so pointer moves never\n * trigger a React re-render or layout work — just a background repaint.\n */\n\n.sm-mrg-surface {\n  isolation: isolate;\n}\n\n.sm-mrg-glow {\n  z-index: 0;\n  background: radial-gradient(\n    var(--sm-mrg-radius) circle at var(--sm-mrg-x) var(--sm-mrg-y),\n    color-mix(in srgb, var(--sm-mrg-color) calc(var(--sm-mrg-intensity) * 100%), transparent),\n    transparent 70%\n  );\n  will-change: background;\n}\n\n.sm-mrg-tilt-layer {\n  z-index: 1;\n  transform: perspective(800px) rotateX(var(--sm-mrg-tilt-x)) rotateY(var(--sm-mrg-tilt-y));\n  transition: transform 0.15s cubic-bezier(0.25, 0.1, 0.25, 1);\n  transform-style: preserve-3d;\n  will-change: transform;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .sm-mrg-tilt-layer {\n    transform: none;\n    transition: none;\n  }\n}\n"
    },
    {
      "path": "demo.tsx",
      "target": "components/safari/mouse-reactive-gradient/demo.tsx",
      "type": "registry:file",
      "content": "\"use client\";\n\nimport { MouseReactiveGradient } from \"./mouse-reactive-gradient\";\n\n/**\n * Isolated demo used by the future component catalog. Renders the\n * component with representative props — no external app state.\n */\nexport default function MouseReactiveGradientDemo() {\n  return (\n    <div className=\"flex items-center justify-center p-12\">\n      <MouseReactiveGradient\n        tilt\n        className=\"flex h-72 w-full max-w-md flex-col justify-end rounded-2xl border border-sm-border bg-sm-surface p-8\"\n      >\n        <h3 className=\"text-xl font-semibold tracking-tight text-sm-text\">\n          Move your cursor\n        </h3>\n        <p className=\"mt-2 text-sm text-sm-text-muted\">\n          The glow and tilt follow the pointer inside this card. Touch\n          devices and reduced-motion get a static fallback.\n        </p>\n      </MouseReactiveGradient>\n    </div>\n  );\n}\n"
    }
  ],
  "meta": {
    "name": "mouse-reactive-gradient",
    "title": "Mouse Reactive Gradient",
    "description": "A surface with a soft radial gradient glow that follows the pointer, Apple-subtle spotlight style. Pointer position drives CSS custom properties directly (rAF-throttled), so it repaints without React re-renders or layout thrashing. Ships with a static fallback for touch and reduced-motion, and an opt-in subtle tilt.",
    "type": "background",
    "status": "prototype",
    "version": "0.1.0",
    "visibility": "internal",
    "tier": "free",
    "dependencies": {},
    "registryDependencies": [],
    "files": [
      {
        "path": "mouse-reactive-gradient.tsx",
        "target": "components/safari/mouse-reactive-gradient/mouse-reactive-gradient.tsx",
        "type": "registry:component"
      },
      {
        "path": "mouse-reactive-gradient.css",
        "target": "components/safari/mouse-reactive-gradient/mouse-reactive-gradient.css",
        "type": "registry:file"
      },
      {
        "path": "demo.tsx",
        "target": "components/safari/mouse-reactive-gradient/demo.tsx",
        "type": "registry:file"
      }
    ],
    "compatibility": {
      "react": ">=19.0.0",
      "tailwind": ">=4.0.0",
      "ssr": "client-only",
      "browsers": [
        "chrome >= 100",
        "safari >= 16",
        "firefox >= 100"
      ]
    },
    "mobileStrategy": "static-fallback",
    "reducedMotion": "Guarded at mount via matchMedia('(prefers-reduced-motion: reduce)'). When it matches, no pointer listeners are attached at all — the gradient stays fixed at the surface center and the tilt transform (if enabled) is neutralized to `transform: none` in CSS, in addition to the global reduced-motion rule in the app stylesheet.",
    "performance": {
      "bundleCost": "~1.5KB gzip, zero runtime dependencies",
      "offscreenBehavior": "Listeners are scoped to the surface element itself (pointermove/pointerleave), not window/document, so there is no offscreen cost — the browser simply never fires events for a surface the pointer isn't over.",
      "notes": "Pointer updates are rAF-throttled (one pending update coalesced per frame) and only touch CSS custom properties on the surface node — no layout reads/writes in the hot path beyond the one getBoundingClientRect() per move, no state, no re-render. Verified mount/unmount cycles clean up all listeners and cancel any in-flight rAF (no leaks)."
    },
    "a11y": "Purely decorative background layer, marked aria-hidden. Does not trap focus, does not affect tab order, and children remain fully readable/interactive on top of it. No motion is imposed on users who have not opted into precise pointer input or who have prefers-reduced-motion set.",
    "knownLimitations": [
      "pointer: fine gating means the effect is intentionally inert on touch/coarse-pointer devices — this is a deliberate fallback, not a bug.",
      "Tilt uses a CSS transform on a wrapping layer around children; deeply nested 3D/transform-dependent children of their own may interact with transform-style: preserve-3d in unexpected ways.",
      "Gradient color uses color-mix(), which requires a modern browser baseline (already reflected in compatibility.browsers)."
    ],
    "license": "MIT",
    "origin": "Ported from mountProjectTilt() in viktorveverka-cz-codex/src/scripts/project-tilt.ts — reused the pointer-tracking core (pointermove -> normalized 0-1 coordinates -> CSS custom properties, pointer:fine + prefers-reduced-motion guards, full listener cleanup on teardown).",
    "author": "Viktor Veverka (https://viktorveverka.cz)",
    "relatedComponents": []
  }
}