fix: rewrite 5 broken background components from Aceternity sources

- Aurora: remove invert filter (broken on dark bg), use dark gradient directly
- Beams: use animated linearGradient positions instead of pathLength animation
- Beams Collision: add collision detection, explosion particles, AnimatePresence
- Gemini Effect: rewrite as SVG motion.path with animated pathLength (was canvas)
- Noise: change mixBlendMode from overlay to screen (overlay invisible on dark bg)
This commit is contained in:
Fringg
2026-02-25 07:31:16 +03:00
parent 1a702a68b9
commit de97a030d2
5 changed files with 320 additions and 216 deletions

View File

@@ -1,63 +1,179 @@
import { useRef, useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import React, { useRef, useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { cn } from '@/lib/utils';
interface Props {
settings: Record<string, unknown>;
}
interface Beam {
id: number;
x: number;
interface BeamOptions {
initialX: number;
translateX: number;
duration: number;
delay: number;
rotate: number;
repeatDelay: number;
delay?: number;
className?: string;
}
export default function BackgroundBeamsCollision({ settings: _settings }: Props) {
const [beams, setBeams] = useState<Beam[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
const BEAMS: BeamOptions[] = [
{ initialX: 10, translateX: 10, duration: 7, repeatDelay: 3, delay: 2 },
{ initialX: 600, translateX: 600, duration: 3, repeatDelay: 3, delay: 4 },
{ initialX: 100, translateX: 100, duration: 7, repeatDelay: 7, className: 'h-6' },
{ initialX: 400, translateX: 400, duration: 5, repeatDelay: 14, delay: 4 },
{ initialX: 800, translateX: 800, duration: 11, repeatDelay: 2, className: 'h-20' },
{ initialX: 1000, translateX: 1000, duration: 4, repeatDelay: 2, className: 'h-12' },
{ initialX: 1200, translateX: 1200, duration: 6, repeatDelay: 4, delay: 2, className: 'h-6' },
];
useEffect(() => {
const w = containerRef.current?.offsetWidth ?? window.innerWidth;
const count = Math.floor(w / 100);
setBeams(
Array.from({ length: count }, (_, i) => ({
id: i,
x: (i / count) * 100 + Math.random() * 10,
duration: 4 + Math.random() * 8,
delay: Math.random() * 4,
rotate: -5 + Math.random() * 10,
})),
);
}, []);
function Explosion(props: React.HTMLProps<HTMLDivElement>) {
const spans = Array.from({ length: 20 }, (_, i) => ({
id: i,
directionX: Math.floor(Math.random() * 80 - 40),
directionY: Math.floor(Math.random() * -50 - 10),
}));
return (
<div ref={containerRef} className="absolute inset-0 overflow-hidden">
{beams.map((beam) => (
<motion.div
key={beam.id}
className="absolute top-0 h-full w-px"
style={{ left: `${beam.x}%`, rotate: `${beam.rotate}deg` }}
initial={{ y: '-100%', opacity: 0 }}
animate={{ y: '200%', opacity: [0, 1, 1, 0] }}
transition={{
duration: beam.duration,
delay: beam.delay,
repeat: Infinity,
ease: 'linear',
}}
>
<div
className="h-32 w-px"
style={{
background:
'linear-gradient(180deg, transparent, rgba(var(--color-accent-500), 0.5), rgba(var(--color-accent-400), 0.8), transparent)',
}}
/>
</motion.div>
<div {...props} className={cn('absolute z-50 h-2 w-2', props.className)}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.5, ease: 'easeOut' }}
className="absolute -inset-x-10 top-0 m-auto h-2 w-10 rounded-full bg-gradient-to-r from-transparent via-indigo-500 to-transparent blur-sm"
/>
{spans.map((span) => (
<motion.span
key={span.id}
initial={{ x: 0, y: 0, opacity: 1 }}
animate={{ x: span.directionX, y: span.directionY, opacity: 0 }}
transition={{ duration: Math.random() * 1.5 + 0.5, ease: 'easeOut' }}
className="absolute h-1 w-1 rounded-full bg-gradient-to-b from-indigo-500 to-purple-500"
/>
))}
{/* Bottom collision line */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-accent-500/20 to-transparent" />
</div>
);
}
function CollisionMechanism({
containerRef,
parentRef,
beamOptions,
}: {
containerRef: React.RefObject<HTMLDivElement | null>;
parentRef: React.RefObject<HTMLDivElement | null>;
beamOptions: BeamOptions;
}) {
const beamRef = useRef<HTMLDivElement>(null);
const [collision, setCollision] = useState<{
detected: boolean;
coordinates: { x: number; y: number } | null;
}>({ detected: false, coordinates: null });
const [beamKey, setBeamKey] = useState(0);
const [cycleCollisionDetected, setCycleCollisionDetected] = useState(false);
useEffect(() => {
const checkCollision = () => {
if (beamRef.current && containerRef.current && parentRef.current && !cycleCollisionDetected) {
const beamRect = beamRef.current.getBoundingClientRect();
const containerRect = containerRef.current.getBoundingClientRect();
const parentRect = parentRef.current.getBoundingClientRect();
if (beamRect.bottom >= containerRect.top) {
const relativeX = beamRect.left - parentRect.left + beamRect.width / 2;
const relativeY = beamRect.bottom - parentRect.top;
setCollision({ detected: true, coordinates: { x: relativeX, y: relativeY } });
setCycleCollisionDetected(true);
}
}
};
const interval = setInterval(checkCollision, 50);
return () => clearInterval(interval);
}, [cycleCollisionDetected, containerRef, parentRef]);
useEffect(() => {
if (collision.detected && collision.coordinates) {
setTimeout(() => {
setCollision({ detected: false, coordinates: null });
setCycleCollisionDetected(false);
}, 2000);
setTimeout(() => {
setBeamKey((prev) => prev + 1);
}, 2000);
}
}, [collision]);
return (
<>
<motion.div
key={beamKey}
ref={beamRef}
animate="animate"
initial={{
translateY: '-200px',
translateX: `${beamOptions.initialX}px`,
rotate: 0,
}}
variants={{
animate: {
translateY: '1800px',
translateX: `${beamOptions.translateX}px`,
rotate: 0,
},
}}
transition={{
duration: beamOptions.duration,
repeat: Infinity,
repeatType: 'loop',
ease: 'linear',
delay: beamOptions.delay ?? 0,
repeatDelay: beamOptions.repeatDelay,
}}
className={cn(
'absolute left-0 top-20 m-auto h-14 w-px rounded-full bg-gradient-to-t from-indigo-500 via-purple-500 to-transparent',
beamOptions.className,
)}
/>
<AnimatePresence>
{collision.detected && collision.coordinates && (
<Explosion
key={`${collision.coordinates.x}-${collision.coordinates.y}`}
style={{
left: `${collision.coordinates.x}px`,
top: `${collision.coordinates.y}px`,
transform: 'translate(-50%, -50%)',
}}
/>
)}
</AnimatePresence>
</>
);
}
export default function BackgroundBeamsCollision({ settings: _settings }: Props) {
const containerRef = useRef<HTMLDivElement>(null);
const parentRef = useRef<HTMLDivElement>(null);
return (
<div ref={parentRef} className="absolute inset-0 overflow-hidden">
{BEAMS.map((beam) => (
<CollisionMechanism
key={`${beam.initialX}-beam`}
beamOptions={beam}
containerRef={containerRef}
parentRef={parentRef}
/>
))}
{/* Bottom collision line */}
<div
ref={containerRef}
className="pointer-events-none absolute inset-x-0 bottom-0 w-full"
style={{
boxShadow:
'0 0 24px rgba(34, 42, 53, 0.06), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.04), 0 0 4px rgba(34, 42, 53, 0.08), 0 16px 68px rgba(47, 48, 55, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1) inset',
}}
/>
</div>
);
}