Files

167 lines
4.6 KiB
TypeScript

"use client"
import { motion, MotionStyle, Transition } from "motion/react"
import { useEffect, useRef } from "react"
import { cn } from "@/lib/utils"
// #region agent log
const logDebug = (location: string, message: string, data: any = {}) => {
fetch('http://127.0.0.1:7242/ingest/3016cf56-8a2e-4f75-a485-7bd5c60b0ff5', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ location, message, data, timestamp: Date.now(), runId: 'initial', hypothesisId: Object.keys(data)[0] || 'A' })
}).catch(() => {});
};
// #endregion
interface BorderBeamProps {
/**
* The size of the border beam.
*/
size?: number
/**
* The duration of the border beam.
*/
duration?: number
/**
* The delay of the border beam.
*/
delay?: number
/**
* The color of the border beam from.
*/
colorFrom?: string
/**
* The color of the border beam to.
*/
colorTo?: string
/**
* The motion transition of the border beam.
*/
transition?: Transition
/**
* The class name of the border beam.
*/
className?: string
/**
* The style of the border beam.
*/
style?: React.CSSProperties
/**
* Whether to reverse the animation direction.
*/
reverse?: boolean
/**
* The initial offset position (0-100).
*/
initialOffset?: number
/**
* The border width of the beam.
*/
borderWidth?: number
}
export const BorderBeam = ({
className,
size = 50,
delay = 0,
duration = 6,
colorFrom = "#FCC61D",
colorTo = "#0046FF",
transition,
style,
reverse = false,
initialOffset = 0,
borderWidth = 1,
}: BorderBeamProps) => {
const beamRef = useRef<HTMLDivElement>(null)
const parentRef = useRef<HTMLDivElement>(null)
const animationFrameRef = useRef(0)
const frameCountRef = useRef(0)
// #region agent log
useEffect(() => {
if (!beamRef.current || !parentRef.current) return;
const beam = beamRef.current;
const parent = parentRef.current;
const checkAnimation = () => {
frameCountRef.current++;
const beamRect = beam.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
const computedStyle = window.getComputedStyle(beam);
if (frameCountRef.current % 60 === 0) {
logDebug('border-beam.tsx:checkAnimation', 'Animation frame check', {
A: { frame: frameCountRef.current, beamX: beamRect.x, beamY: beamRect.y, beamWidth: beamRect.width, beamHeight: beamRect.height, parentWidth: parentRect.width, parentHeight: parentRect.height, transform: computedStyle.transform, willChange: computedStyle.willChange }
});
}
animationFrameRef.current = requestAnimationFrame(checkAnimation);
};
animationFrameRef.current = requestAnimationFrame(checkAnimation);
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, []);
// #endregion
return (
<div
ref={parentRef}
className="pointer-events-none absolute inset-0 rounded-[inherit] border-(length:--border-beam-width) border-transparent [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)] [mask-composite:intersect] [mask-clip:padding-box,border-box]"
style={
{
"--border-beam-width": `${borderWidth}px`,
isolation: "isolate",
contain: "layout style paint",
} as React.CSSProperties
}
>
<motion.div
ref={beamRef}
className={cn(
"absolute aspect-square",
"bg-gradient-to-l from-[var(--color-from)] via-[var(--color-to)] to-transparent",
className
)}
style={
{
width: size,
offsetPath: `rect(0 auto auto 0 round ${size}px)`,
"--color-from": colorFrom,
"--color-to": colorTo,
willChange: "transform",
transform: "translateZ(0)",
...style,
} as MotionStyle
}
initial={{ offsetDistance: `${initialOffset}%` }}
animate={{
offsetDistance: reverse
? [`${100 - initialOffset}%`, `${-initialOffset}%`]
: [`${initialOffset}%`, `${100 + initialOffset}%`],
}}
transition={{
repeat: Infinity,
ease: "linear",
duration,
delay: -delay,
...transition,
}}
onAnimationStart={() => {
// #region agent log
logDebug('border-beam.tsx:motion.div', 'BorderBeam animation started', { C: { duration, size, timestamp: performance.now() } });
// #endregion
}}
/>
</div>
)
}