{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-beam",
  "title": "Animated Beam",
  "description": "Draws an animated connection between any two elements.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/animated-beam.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useState, type RefObject } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface AnimatedBeamProps {\n  containerRef: RefObject<HTMLElement | null>;\n  fromRef: RefObject<HTMLElement | null>;\n  toRef: RefObject<HTMLElement | null>;\n  /** Vertical bend of the beam in px (positive bends up) */\n  curvature?: number;\n  reverse?: boolean;\n  duration?: number;\n  delay?: number;\n  pathWidth?: number;\n  pathOpacity?: number;\n  gradientStartColor?: string;\n  gradientStopColor?: string;\n  className?: string;\n}\n\n/**\n * Animated gradient beam connecting two elements inside a shared\n * `relative` container. Pass refs to the container and both endpoints.\n */\nexport function AnimatedBeam({\n  containerRef,\n  fromRef,\n  toRef,\n  curvature = 0,\n  reverse = false,\n  duration = 5,\n  delay = 0,\n  pathWidth = 2,\n  pathOpacity = 0.15,\n  gradientStartColor = \"#8b5cf6\",\n  gradientStopColor = \"#22d3ee\",\n  className,\n}: AnimatedBeamProps) {\n  const id = useId();\n  const [pathD, setPathD] = useState(\"\");\n  const [size, setSize] = useState({ width: 0, height: 0 });\n  const reducedMotion = useReducedMotion();\n\n  const gradientCoordinates = reverse\n    ? { x1: [\"90%\", \"-10%\"], x2: [\"100%\", \"0%\"] }\n    : { x1: [\"10%\", \"110%\"], x2: [\"0%\", \"100%\"] };\n\n  useEffect(() => {\n    const update = () => {\n      const container = containerRef.current;\n      const from = fromRef.current;\n      const to = toRef.current;\n      if (!container || !from || !to) return;\n\n      const containerRect = container.getBoundingClientRect();\n      const rectA = from.getBoundingClientRect();\n      const rectB = to.getBoundingClientRect();\n\n      const startX = rectA.left - containerRect.left + rectA.width / 2;\n      const startY = rectA.top - containerRect.top + rectA.height / 2;\n      const endX = rectB.left - containerRect.left + rectB.width / 2;\n      const endY = rectB.top - containerRect.top + rectB.height / 2;\n\n      setSize({ width: containerRect.width, height: containerRect.height });\n      setPathD(\n        `M ${startX},${startY} Q ${(startX + endX) / 2},${\n          (startY + endY) / 2 - curvature\n        } ${endX},${endY}`\n      );\n    };\n\n    // ResizeObserver fires once on observe, which also draws the initial path\n    const observer = new ResizeObserver(update);\n    if (containerRef.current) observer.observe(containerRef.current);\n    return () => observer.disconnect();\n  }, [containerRef, fromRef, toRef, curvature]);\n\n  return (\n    <svg\n      aria-hidden\n      data-slot=\"animated-beam\"\n      fill=\"none\"\n      width={size.width}\n      height={size.height}\n      viewBox={`0 0 ${size.width} ${size.height}`}\n      className={cn(\n        \"pointer-events-none absolute top-0 left-0 transform-gpu\",\n        className\n      )}\n    >\n      <path\n        d={pathD}\n        stroke=\"currentColor\"\n        strokeWidth={pathWidth}\n        strokeOpacity={pathOpacity}\n        strokeLinecap=\"round\"\n        className=\"text-border\"\n      />\n      <path\n        d={pathD}\n        stroke={`url(#${id})`}\n        strokeWidth={pathWidth}\n        strokeLinecap=\"round\"\n      />\n      <defs>\n        <motion.linearGradient\n          id={id}\n          gradientUnits=\"userSpaceOnUse\"\n          initial={{ x1: \"0%\", x2: \"0%\", y1: \"0%\", y2: \"0%\" }}\n          animate={\n            reducedMotion\n              ? // Still beam spanning the whole path\n                {\n                  x1: reverse ? \"100%\" : \"0%\",\n                  x2: reverse ? \"0%\" : \"100%\",\n                  y1: \"0%\",\n                  y2: \"0%\",\n                }\n              : {\n                  x1: gradientCoordinates.x1,\n                  x2: gradientCoordinates.x2,\n                  y1: [\"0%\", \"0%\"],\n                  y2: [\"0%\", \"0%\"],\n                }\n          }\n          transition={\n            reducedMotion\n              ? { duration: 0 }\n              : {\n                  delay,\n                  duration,\n                  ease: [0.16, 1, 0.3, 1],\n                  repeat: Infinity,\n                }\n          }\n        >\n          <stop stopColor={gradientStartColor} stopOpacity=\"0\" />\n          <stop stopColor={gradientStartColor} />\n          <stop offset=\"32.5%\" stopColor={gradientStopColor} />\n          <stop offset=\"100%\" stopColor={gradientStopColor} stopOpacity=\"0\" />\n        </motion.linearGradient>\n      </defs>\n    </svg>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/animated-beam.tsx"
    }
  ],
  "type": "registry:ui"
}