mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user