Workspace / component / animations
Laser Flow
paxfx add blocks/component-animations-laser-flowlocal onlyTier: medium. Reduced motion: Beams hold fixed diagonal positions.
"use client";
import { type CSSProperties, type ReactNode } from "react";
import { cn, useInView, useReducedMotion } from "@pax-sh/blocks";
export interface LaserFlowProps {
/** Number of beams (1 to 8). */
count?: number;
/** Beam tilt in degrees. */
angle?: number;
/** Seconds for one full sweep. */
speed?: number;
className?: string;
children?: ReactNode;
}
/**
* Diagonal beams of light sweep across the surface on transform-only CSS
* keyframes. Each beam is a skewed gradient sliver whose duration and
* negative delay derive deterministically from its index, so the pattern
* never syncs and needs no JavaScript per frame. Reduced motion holds the
* beams at fixed diagonal positions.
*/
export function LaserFlow({
count = 4,
angle = 18,
speed = 7,
className,
children,
}: LaserFlowProps) {
const reduced = useReducedMotion();
const [ref, inView] = useInView<HTMLDivElement>({ threshold: 0.2 });
const beams = Math.min(8, Math.max(1, Math.round(count)));
const animate = inView && !reduced;
const beamStyle = (i: number): CSSProperties => {
const duration = speed * (1 + ((i * 29) % 35) / 100);
const delay = -duration * (((i * 47) % 100) / 100);
const staticShift = -150 + ((i * 83) % 100) * 3;
return {
left: `${8 + (i * 90) / beams}%`,
width: `${9 + ((i * 13) % 3) * 3}%`,
transform: animate ? undefined : `translateX(${staticShift}%) skewX(${-angle}deg)`,
animation: animate
? `pax-laser-sweep ${duration.toFixed(2)}s linear ${delay.toFixed(2)}s infinite`
: undefined,
opacity: 0.5 + ((i * 17) % 40) / 100,
};
};
return (
<div
ref={ref}
className={cn(
"relative isolate min-h-[240px] overflow-hidden rounded-2xl border border-[var(--pax-border,rgba(18,17,15,0.12))]",
"bg-[var(--pax-night,#0b0d10)] text-[var(--pax-paper,#f6f2ea)]",
className,
)}
data-pax-asset="component-animations-laser-flow"
style={{ "--pax-laser-skew": `${-angle}deg` } as CSSProperties}
>
<style>{`
@keyframes pax-laser-sweep {
0% { transform: translateX(-400%) skewX(var(--pax-laser-skew, -18deg)); }
100% { transform: translateX(400%) skewX(var(--pax-laser-skew, -18deg)); }
}
`}</style>
<div aria-hidden="true" className="pointer-events-none absolute inset-[-20%] z-[1]">
{Array.from({ length: beams }, (_, i) => (
<span
key={i}
className="absolute bottom-0 top-0 will-change-transform"
style={{
...beamStyle(i),
background:
"linear-gradient(90deg, transparent 0%, rgba(200,255,0,0.16) 30%, rgba(246,242,234,0.34) 50%, rgba(200,255,0,0.16) 70%, transparent 100%)",
}}
/>
))}
</div>
<div className="relative z-[2]">
{children ?? (
<div className="flex min-h-[240px] flex-col justify-end p-8">
<p className="m-0 text-xs font-medium uppercase tracking-[0.22em] text-[var(--pax-mute,#6b645c)]">
Laser flow
</p>
<h3 className="mt-3 max-w-md text-2xl font-semibold leading-snug tracking-tight [text-wrap:balance]">
Beams sweep the surface on nothing but compositor transforms.
</h3>
<p className="mt-3 max-w-sm text-sm leading-relaxed text-[var(--pax-mute,#6b645c)]">
Count, angle, and speed are props. Zero JavaScript per frame.
</p>
</div>
)}
</div>
</div>
);
}