3import { cn } from '@/lib/utils';
4import { motion } from 'motion/react';
5import { forwardRef, RefObject, useEffect, useId, useState } from 'react';
7export interface AnimatedBeamProps {
9 containerRef: RefObject<HTMLElement>; // Container ref
10 fromRef: RefObject<HTMLElement>;
11 toRef: RefObject<HTMLElement>;
17 gradientStartColor?: string;
18 gradientStopColor?: string;
29export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
35 reverse = false, // Include the reverse prop
36 duration = Math.random() * 3 + 4,
41 gradientStartColor = '#4d40ff',
42 gradientStopColor = '#4043ff',
51 const [pathD, setPathD] = useState('');
53 const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
54 const strokeDasharray = dotted ? `${dotSpacing} ${dotSpacing}` : 'none';
55 // Calculate the gradient coordinates based on the reverse prop
56 const gradientCoordinates = reverse
71 const updatePath = () => {
72 if (containerRef.current && fromRef.current && toRef.current) {
73 const containerRect = containerRef.current.getBoundingClientRect();
74 const rectA = fromRef.current.getBoundingClientRect();
75 const rectB = toRef.current.getBoundingClientRect();
77 const svgWidth = containerRect.width;
78 const svgHeight = containerRect.height;
79 setSvgDimensions({ width: svgWidth, height: svgHeight });
82 rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
84 rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
86 rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
88 rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
90 const controlY = startY - curvature;
91 const d = `M ${startX},${startY} Q ${
93 },${controlY} ${endX},${endY}`;
98 // Initialize ResizeObserver
99 const resizeObserver = new ResizeObserver((entries) => {
100 // For all entries, recalculate the path
101 for (let entry of entries) {
106 // Observe the container element
107 if (containerRef.current) {
108 resizeObserver.observe(containerRef.current);
111 // Call the updatePath initially to set the initial path
114 // Clean up the observer on component unmount
116 resizeObserver.disconnect();
132 width={svgDimensions.width}
133 height={svgDimensions.height}
134 xmlns='http://www.w3.org/2000/svg'
136 'pointer-events-none absolute left-0 top-0 transform-gpu stroke-2',
139 viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
145 strokeOpacity={pathOpacity}
147 strokeDasharray={strokeDasharray}
153 strokeDasharray={strokeDasharray}
159 strokeWidth: pathWidth * 1.5, // or any scale factor you prefer
163 duration: 2, // adjust as needed
164 delay: delay, // use the same delay as the gradient animation
169 className='transform-gpu'
171 gradientUnits={'userSpaceOnUse'}
179 x1: gradientCoordinates.x1,
180 x2: gradientCoordinates.x2,
181 y1: gradientCoordinates.y1,
182 y2: gradientCoordinates.y2,
187 ease: [0.16, 1, 0.3, 1], // https://easings.net/#easeOutExpo
192 <stop stopColor={gradientStartColor} stopOpacity='0'></stop>
193 <stop stopColor={gradientStartColor}></stop>
194 <stop offset='32.5%' stopColor={gradientStopColor}></stop>
197 stopColor={gradientStopColor}
206export const Circle = forwardRef<
208 { className?: string; children?: React.ReactNode }
209>(({ className, children }, ref) => {
214 'z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 bg-white p-3 shadow-[0_0_20px_-12px_rgba(0,0,0,0.8)]',