76 lines
2.0 KiB
TypeScript
76 lines
2.0 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
|
|
interface Point {
|
|
x: number;
|
|
y: number;
|
|
id: number;
|
|
size: number;
|
|
color: string;
|
|
velocity: { x: number; y: number };
|
|
life: number;
|
|
}
|
|
|
|
export const FairyDust: React.FC = () => {
|
|
const [points, setPoints] = useState<Point[]>([]);
|
|
|
|
useEffect(() => {
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
const colors = ['#FF00CC', '#3333FF', '#7000FF', '#FFFFFF', '#00FFFF'];
|
|
const newPoint: Point = {
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
id: Date.now() + Math.random(),
|
|
size: Math.random() * 4 + 2,
|
|
color: colors[Math.floor(Math.random() * colors.length)],
|
|
velocity: {
|
|
x: (Math.random() - 0.5) * 2,
|
|
y: (Math.random() - 0.5) * 2
|
|
},
|
|
life: 1.0
|
|
};
|
|
setPoints(prev => [...prev.slice(-40), newPoint]); // Limit trail length
|
|
};
|
|
|
|
window.addEventListener('mousemove', handleMouseMove);
|
|
return () => window.removeEventListener('mousemove', handleMouseMove);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
setPoints(prev => prev
|
|
.map(p => ({
|
|
...p,
|
|
x: p.x + p.velocity.x,
|
|
y: p.y + p.velocity.y,
|
|
life: p.life - 0.05
|
|
}))
|
|
.filter(p => p.life > 0)
|
|
);
|
|
}, 16); // 60fps
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="fixed inset-0 pointer-events-none z-[9999]">
|
|
{points.map(point => (
|
|
<div
|
|
key={point.id}
|
|
style={{
|
|
position: 'absolute',
|
|
left: point.x,
|
|
top: point.y,
|
|
width: point.size,
|
|
height: point.size,
|
|
backgroundColor: point.color,
|
|
borderRadius: '50%',
|
|
opacity: point.life,
|
|
transform: `scale(${point.life})`,
|
|
boxShadow: `0 0 ${point.size * 2}px ${point.color}`,
|
|
transition: 'opacity 0.1s linear'
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}; |