{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cursor-glow",
  "type": "registry:ui",
  "title": "Cursor Glow",
  "description": "A soft light that follows the pointer inside a container and catches its border.",
  "author": "WANSTUDIO <https://wanstudio.my>",
  "dependencies": [],
  "files": [
    {
      "path": "registry/wancraft/cursor-glow.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport interface CursorGlowProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /** Glow radius, in px. */\n  radius?: number;\n  /** Extra blur applied to the glow, in px. */\n  blur?: number;\n  /** Glow colour (any CSS colour). */\n  color?: string;\n  /** Peak opacity of the glow, 0–1. */\n  intensity?: number;\n  /** Smoothing factor per frame, 0.02 (floaty) – 1 (locked to the pointer). */\n  easing?: number;\n  /** Also light up the container's 1px border near the pointer. */\n  border?: boolean;\n}\n\n/**\n * A wrapper that renders a soft light following the mouse, clipped to its own bounds.\n * Position is eased in a requestAnimationFrame loop and written to CSS variables\n * (`--glow-x`, `--glow-y`, also usable by children) — React never re-renders on move.\n * Touch and pen input get no glow; reduced motion snaps the glow to the pointer.\n */\nexport const CursorGlow = React.forwardRef<HTMLDivElement, CursorGlowProps>(function CursorGlow(\n  {\n    radius = 220,\n    blur = 40,\n    color = \"#FF7448\",\n    intensity = 0.45,\n    easing = 0.15,\n    border = true,\n    className = \"\",\n    style,\n    children,\n    onPointerEnter,\n    onPointerMove,\n    onPointerLeave,\n    ...props\n  },\n  ref,\n) {\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(ref, () => rootRef.current as HTMLDivElement);\n\n  // Mutable animation state lives in a ref; it's only touched in handlers and rAF.\n  const state = React.useRef({ x: 0, y: 0, tx: 0, ty: 0, raf: 0, last: 0, active: false });\n  const easingRef = React.useRef(easing);\n  React.useEffect(() => {\n    easingRef.current = easing;\n  }, [easing]);\n\n  const reducedRef = React.useRef(false);\n  React.useEffect(() => {\n    const s = state.current;\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const sync = () => {\n      reducedRef.current = mq.matches;\n    };\n    sync();\n    mq.addEventListener(\"change\", sync);\n    return () => {\n      mq.removeEventListener(\"change\", sync);\n      cancelAnimationFrame(s.raf);\n    };\n  }, []);\n\n  const write = (x: number, y: number) => {\n    const el = rootRef.current;\n    if (!el) return;\n    el.style.setProperty(\"--glow-x\", `${x.toFixed(1)}px`);\n    el.style.setProperty(\"--glow-y\", `${y.toFixed(1)}px`);\n  };\n\n  const tick = (time: number) => {\n    const s = state.current;\n    const dt = Math.min(64, s.last ? time - s.last : 16.7);\n    s.last = time;\n    const e = reducedRef.current ? 1 : Math.min(Math.max(easingRef.current, 0.02), 1);\n    // Frame-rate independent exponential smoothing.\n    const k = 1 - Math.pow(1 - e, dt / 16.7);\n    s.x += (s.tx - s.x) * k;\n    s.y += (s.ty - s.y) * k;\n    write(s.x, s.y);\n    if (Math.abs(s.tx - s.x) > 0.2 || Math.abs(s.ty - s.y) > 0.2) {\n      s.raf = requestAnimationFrame(tick);\n    } else {\n      s.raf = 0;\n      s.last = 0;\n    }\n  };\n\n  const setTarget = (event: React.PointerEvent<HTMLDivElement>, snap: boolean) => {\n    const el = rootRef.current;\n    if (!el) return;\n    const rect = el.getBoundingClientRect();\n    const s = state.current;\n    s.tx = event.clientX - rect.left;\n    s.ty = event.clientY - rect.top;\n    if (snap) {\n      // Entering: start at the pointer rather than sweeping in from the last exit point.\n      s.x = s.tx;\n      s.y = s.ty;\n      write(s.x, s.y);\n    }\n    if (!s.raf) s.raf = requestAnimationFrame(tick);\n  };\n\n  const setActive = (active: boolean) => {\n    state.current.active = active;\n    rootRef.current?.style.setProperty(\"--glow-on\", active ? \"1\" : \"0\");\n  };\n\n  return (\n    <div\n      ref={rootRef}\n      className={`group/glow relative isolate overflow-hidden ${className}`}\n      style={{\n        [\"--glow-color\" as string]: color,\n        [\"--glow-on\" as string]: 0,\n        ...style,\n      }}\n      onPointerEnter={(event) => {\n        onPointerEnter?.(event);\n        if (event.pointerType !== \"mouse\") return;\n        setTarget(event, true);\n        setActive(true);\n      }}\n      onPointerMove={(event) => {\n        onPointerMove?.(event);\n        if (event.pointerType !== \"mouse\") return;\n        if (!state.current.active) {\n          setTarget(event, true);\n          setActive(true);\n        } else setTarget(event, false);\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        setActive(false);\n      }}\n      {...props}\n    >\n      <div\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute left-0 top-0 -z-10 rounded-full transition-opacity duration-500 ease-out will-change-transform motion-reduce:transition-none\"\n        style={{\n          width: radius * 2,\n          height: radius * 2,\n          transform: `translate3d(calc(var(--glow-x, -9999px) - ${radius}px), calc(var(--glow-y, -9999px) - ${radius}px), 0)`,\n          background: \"radial-gradient(closest-side, var(--glow-color), transparent)\",\n          filter: blur > 0 ? `blur(${blur}px)` : undefined,\n          opacity: `calc(var(--glow-on) * ${Math.min(Math.max(intensity, 0), 1)})`,\n        }}\n      />\n      {border && (\n        <div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0 rounded-[inherit] transition-opacity duration-500 ease-out motion-reduce:transition-none\"\n          style={{\n            padding: 1,\n            background: `radial-gradient(${radius}px circle at var(--glow-x, -9999px) var(--glow-y, -9999px), var(--glow-color), transparent 70%)`,\n            // Mask out the content box so only a 1px ring remains.\n            mask: \"linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)\",\n            maskComposite: \"exclude\",\n            opacity: \"var(--glow-on)\",\n          }}\n        />\n      )}\n      {children}\n    </div>\n  );\n});\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "backgrounds"
  ],
  "docs": "Documentation: https://wancraft.my/components/cursor-glow"
}
