{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glowing-effect",
  "title": "Glowing Effect",
  "description": "Border glow that follows the pointer around a card's edge as it comes near.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/glowing-effect.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface GlowingEffectProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Distance in px outside the card at which the glow starts to fade in */\n  proximity?: number;\n  /** Length of the lit arc, in degrees of the border */\n  spread?: number;\n  /** Thickness of the glowing border in px */\n  borderWidth?: number;\n  /** Add a soft blurred halo behind the lit border */\n  halo?: boolean;\n}\n\nconst OFF = -1e5;\n\n/**\n * A glowing arc on a card's border that turns to face the pointer and fades\n * in as the pointer comes within `proximity` px. Place it as a direct child\n * of a `relative` card; it inherits the card's border radius. Keyboard focus\n * inside the card lights the whole border.\n */\nexport function GlowingEffect({\n  proximity = 64,\n  spread = 80,\n  borderWidth = 2,\n  halo = true,\n  className,\n  style,\n  ...props\n}: GlowingEffectProps) {\n  const ref = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const reduce = matchMedia(\"(prefers-reduced-motion: reduce)\");\n    let x = OFF;\n    let y = OFF;\n    let angle = 0;\n    let lit = false;\n    let raf = 0;\n\n    const frame = () => {\n      raf = 0;\n      if (reduce.matches) {\n        el.style.removeProperty(\"--glow-o\");\n        return;\n      }\n      const r = el.getBoundingClientRect();\n      const dx = Math.max(r.left - x, 0, x - r.right);\n      const dy = Math.max(r.top - y, 0, y - r.bottom);\n      const near = Math.max(0, 1 - Math.hypot(dx, dy) / Math.max(proximity, 1));\n      el.style.setProperty(\"--glow-o\", near.toFixed(3));\n      if (!near) {\n        lit = false;\n        return;\n      }\n      const target =\n        (Math.atan2(x - r.left - r.width / 2, r.top + r.height / 2 - y) * 180) / Math.PI;\n      // Ease along the shortest way round; snap when the glow first appears.\n      const d = ((((target - angle) % 360) + 540) % 360) - 180;\n      angle += !lit || Math.abs(d) < 0.5 ? d : d * 0.2;\n      lit = true;\n      el.style.setProperty(\"--glow-a\", `${angle.toFixed(2)}deg`);\n      if (angle !== target && Math.abs(d) >= 0.5) raf = requestAnimationFrame(frame);\n    };\n    const queue = () => {\n      if (!raf) raf = requestAnimationFrame(frame);\n    };\n    const onMove = (e: PointerEvent) => {\n      x = e.clientX;\n      y = e.clientY;\n      queue();\n    };\n    const onOut = (e: PointerEvent) => {\n      if (e.relatedTarget) return;\n      x = y = OFF;\n      queue();\n    };\n\n    const listen = (on: boolean) => {\n      if (on) {\n        document.addEventListener(\"pointermove\", onMove, { passive: true });\n        document.addEventListener(\"pointerout\", onOut);\n        window.addEventListener(\"scroll\", queue, { passive: true });\n        queue();\n      } else {\n        document.removeEventListener(\"pointermove\", onMove);\n        document.removeEventListener(\"pointerout\", onOut);\n        window.removeEventListener(\"scroll\", queue);\n      }\n    };\n    // Listen only while the card is on screen.\n    const io = new IntersectionObserver((entries) =>\n      listen(entries[entries.length - 1].isIntersecting)\n    );\n    io.observe(el);\n    return () => {\n      io.disconnect();\n      listen(false);\n      cancelAnimationFrame(raf);\n    };\n  }, [proximity]);\n\n  const arc = (ring: string) => (\n    <div className=\"absolute inset-0 rounded-[inherit] [mask-image:conic-gradient(from_calc(var(--glow-a)-var(--glow-s)/2),var(--glow-m),#000_calc(var(--glow-s)*0.35),#000_calc(var(--glow-s)*0.65),var(--glow-m)_var(--glow-s),var(--glow-m))]\">\n      <div className=\"absolute inset-0 rounded-[inherit] bg-[conic-gradient(from_var(--glow-a),var(--brand-from),var(--brand-via),var(--brand-to),var(--brand-via),var(--brand-from))] [mask:linear-gradient(#000_0_0)_content-box_exclude,linear-gradient(#000_0_0)]\" style={{ padding: ring }} />\n    </div>\n  );\n\n  return (\n    <div\n      {...props}\n      ref={ref}\n      aria-hidden\n      data-slot=\"glowing-effect\"\n      style={\n        { \"--glow-s\": `${spread}deg`, \"--glow-w\": `${borderWidth}px`, ...style } as React.CSSProperties\n      }\n      className={cn(\n        \"pointer-events-none absolute -inset-px rounded-[inherit] opacity-(--glow-o,0) transition-opacity duration-300 [--glow-a:0deg] [--glow-m:transparent]\",\n        \"motion-reduce:[:hover>&]:opacity-70 motion-reduce:[:hover>&]:[--glow-m:#000]\",\n        \"[:is(:focus-visible,:has(:focus-visible))>&]:opacity-100 [:is(:focus-visible,:has(:focus-visible))>&]:[--glow-m:#000]\",\n        className\n      )}\n    >\n      {halo && (\n        <div className=\"absolute -inset-1 rounded-[inherit] opacity-70 blur-[6px]\">\n          {arc(\"calc(var(--glow-w) + 6px)\")}\n        </div>\n      )}\n      {arc(\"var(--glow-w)\")}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/glowing-effect.tsx"
    }
  ],
  "type": "registry:ui"
}