Files
resume/components/ui/meteors.tsx
evan 6fec90ea71 feat: build resume website with MagicUI components
- 6 sections: Hero, About, Experience, Skills, Projects, Contact
- MagicUI: Globe, Particles, Meteors, AnimatedList, IconCloud, BentoGrid
- Dark mode support, scroll-triggered animations
- Static export ready for deployment
2026-04-14 15:09:48 +08:00

62 lines
1.6 KiB
TypeScript

"use client"
import React, { useEffect, useState } from "react"
import { cn } from "@/lib/utils"
interface MeteorsProps {
number?: number
minDelay?: number
maxDelay?: number
minDuration?: number
maxDuration?: number
angle?: number
className?: string
}
export const Meteors = ({
number = 20,
minDelay = 0.2,
maxDelay = 1.2,
minDuration = 2,
maxDuration = 10,
angle = 215,
className,
}: MeteorsProps) => {
const [meteorStyles, setMeteorStyles] = useState<Array<React.CSSProperties>>(
[]
)
useEffect(() => {
const styles = [...new Array(number)].map(() => ({
"--angle": -angle + "deg",
top: "-5%",
left: `calc(0% + ${Math.floor(Math.random() * window.innerWidth)}px)`,
animationDelay: Math.random() * (maxDelay - minDelay) + minDelay + "s",
animationDuration:
Math.floor(Math.random() * (maxDuration - minDuration) + minDuration) +
"s",
}))
setMeteorStyles(styles)
}, [number, minDelay, maxDelay, minDuration, maxDuration, angle])
return (
<>
{[...meteorStyles].map((style, idx) => (
// Meteor Head
<span
key={idx}
style={{ ...style }}
className={cn(
"animate-meteor pointer-events-none absolute size-0.5 rotate-(--angle) rounded-full bg-zinc-500 shadow-[0_0_0_1px_#ffffff10]",
className
)}
>
{/* Meteor Tail */}
<div className="pointer-events-none absolute top-1/2 -z-10 h-px w-12.5 -translate-y-1/2 bg-linear-to-r from-zinc-500 to-transparent" />
</span>
))}
</>
)
}