167 lines
4.6 KiB
TypeScript
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>
|
|
)
|
|
}
|