2024-11-14 21:21:00 +07:00

220 lines
6.1 KiB
TypeScript

// @ts-nocheck
'use client';
import { AnimatePresence, motion } from 'framer-motion';
import React, { useEffect, useId, useRef, useState } from 'react';
import { cn } from '../../lib/utils';
import { SparklesCore } from './sparkles';
export const Cover = ({
children,
className,
}: {
children?: React.ReactNode;
className?: string;
}) => {
const [hovered, setHovered] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const [containerWidth, setContainerWidth] = useState(0);
const [beamPositions, setBeamPositions] = useState<number[]>([]);
useEffect(() => {
if (ref.current) {
setContainerWidth(ref.current?.clientWidth ?? 0);
const height = ref.current?.clientHeight ?? 0;
const numberOfBeams = Math.floor(height / 10); // Adjust the divisor to control the spacing
const positions = Array.from(
{ length: numberOfBeams },
(_, i) => (i + 1) * (height / (numberOfBeams + 1)),
);
setBeamPositions(positions);
}
}, [ref.current]);
return (
<div
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
ref={ref}
className='group/cover relative inline-block rounded-sm bg-neutral-100 px-2 py-2 transition duration-200 hover:bg-neutral-900 dark:bg-neutral-900'
>
<AnimatePresence>
{hovered && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
opacity: {
duration: 0.2,
},
}}
className='absolute inset-0 h-full w-full overflow-hidden'
>
<motion.div
animate={{
translateX: ['-50%', '0%'],
}}
transition={{
translateX: {
duration: 10,
ease: 'linear',
repeat: Infinity,
},
}}
className='flex h-full w-[200%]'
>
<SparklesCore
background='transparent'
minSize={0.4}
maxSize={1}
particleDensity={500}
className='h-full w-full'
particleColor='#FFFFFF'
/>
<SparklesCore
background='transparent'
minSize={0.4}
maxSize={1}
particleDensity={500}
className='h-full w-full'
particleColor='#FFFFFF'
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{beamPositions.map((position, index) => (
<Beam
key={index}
hovered={hovered}
duration={Math.random() * 2 + 1}
delay={Math.random() * 2 + 1}
width={containerWidth}
style={{
top: `${position}px`,
}}
/>
))}
<motion.span
key={String(hovered)}
animate={{
scale: hovered ? 0.8 : 1,
x: hovered ? [0, -30, 30, -30, 30, 0] : 0,
y: hovered ? [0, 30, -30, 30, -30, 0] : 0,
}}
exit={{
filter: 'none',
scale: 1,
x: 0,
y: 0,
}}
transition={{
duration: 0.2,
x: {
duration: 0.2,
repeat: Infinity,
repeatType: 'loop',
},
y: {
duration: 0.2,
repeat: Infinity,
repeatType: 'loop',
},
scale: {
duration: 0.2,
},
filter: {
duration: 0.2,
},
}}
className={cn(
'relative z-20 inline-block text-neutral-900 transition duration-200 group-hover/cover:text-white dark:text-white',
className,
)}
>
{children}
</motion.span>
<CircleIcon className='absolute -right-[2px] -top-[2px]' />
<CircleIcon className='absolute -bottom-[2px] -right-[2px]' delay={0.4} />
<CircleIcon className='absolute -left-[2px] -top-[2px]' delay={0.8} />
<CircleIcon className='absolute -bottom-[2px] -left-[2px]' delay={1.6} />
</div>
);
};
export const Beam = ({
className,
delay,
duration,
hovered,
width = 600,
...svgProps
}: {
className?: string;
delay?: number;
duration?: number;
hovered?: boolean;
width?: number;
} & React.ComponentProps<typeof motion.svg>) => {
const id = useId();
return (
<motion.svg
width={width ?? '600'}
height='1'
viewBox={`0 0 ${width ?? '600'} 1`}
fill='none'
xmlns='http://www.w3.org/2000/svg'
className={cn('absolute inset-x-0 w-full', className)}
{...svgProps}
>
<motion.path d={`M0 0.5H${width ?? '600'}`} stroke={`url(#svgGradient-${id})`} />
<defs>
<motion.linearGradient
id={`svgGradient-${id}`}
key={String(hovered)}
gradientUnits='userSpaceOnUse'
initial={{
x1: '0%',
x2: hovered ? '-10%' : '-5%',
y1: 0,
y2: 0,
}}
animate={{
x1: '110%',
x2: hovered ? '100%' : '105%',
y1: 0,
y2: 0,
}}
transition={{
duration: hovered ? 0.5 : (duration ?? 2),
ease: 'linear',
repeat: Infinity,
delay: hovered ? Math.random() * (1 - 0.2) + 0.2 : 0,
repeatDelay: hovered ? Math.random() * (2 - 1) + 1 : (delay ?? 1),
}}
>
<stop stopColor='#2EB9DF' stopOpacity='0' />
<stop stopColor='#3b82f6' />
<stop offset='1' stopColor='#3b82f6' stopOpacity='0' />
</motion.linearGradient>
</defs>
</motion.svg>
);
};
export const CircleIcon = ({ className, delay }: { className?: string; delay?: number }) => {
return (
<div
className={cn(
`group pointer-events-none h-2 w-2 animate-pulse rounded-full bg-neutral-600 opacity-20 group-hover/cover:hidden group-hover/cover:bg-white group-hover/cover:opacity-100 dark:bg-white`,
className,
)}
></div>
);
};