paint-spark-banksy-website/components/cursor-effects.tsx

179 lines
5.1 KiB
TypeScript

"use client"
import type React from "react"
import { useEffect, useState } from "react"
interface Particle {
id: number
x: number
y: number
type: "sparkle" | "rainbow"
color?: string
vx?: number
vy?: number
angle?: number
}
export function CursorEffects() {
const [particles, setParticles] = useState<Particle[]>([])
useEffect(() => {
let particleId = 0
let lastTime = Date.now()
const createParticles = (x: number, y: number) => {
const now = Date.now()
// Throttle particle creation to every 50ms
if (now - lastTime < 50) return
lastTime = now
const newParticles: Particle[] = []
for (let i = 0; i < 3; i++) {
const angle = Math.random() * Math.PI * 2
const speed = 2 + Math.random() * 3
newParticles.push({
id: particleId++,
x,
y,
type: "sparkle",
color: [
"#ff0000",
"#ff9a00",
"#d0de21",
"#4fdc4a",
"#3fdad8",
"#2fc9e2",
"#1c7fee",
"#5f15f2",
"#ba0cf8",
"#fb07d9",
][Math.floor(Math.random() * 10)],
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
angle: angle,
})
}
for (let i = 0; i < 2; i++) {
const angle = Math.random() * Math.PI * 2
const speed = 3 + Math.random() * 2
newParticles.push({
id: particleId++,
x,
y,
type: "rainbow",
color: [
"#ff0000",
"#ff9a00",
"#d0de21",
"#4fdc4a",
"#3fdad8",
"#2fc9e2",
"#1c7fee",
"#5f15f2",
"#ba0cf8",
"#fb07d9",
][Math.floor(Math.random() * 10)],
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
angle: angle,
})
}
setParticles((prev) => [...prev, ...newParticles])
setTimeout(() => {
setParticles((prev) => prev.filter((p) => !newParticles.find((np) => np.id === p.id)))
}, 2000)
}
const handleMouseMove = (e: MouseEvent) => {
createParticles(e.clientX, e.clientY)
}
const handleTouchMove = (e: TouchEvent) => {
// Prevent scrolling while creating sparkles
if (e.touches.length > 0) {
const touch = e.touches[0]
createParticles(touch.clientX, touch.clientY)
}
}
const handleTouchStart = (e: TouchEvent) => {
if (e.touches.length > 0) {
const touch = e.touches[0]
createParticles(touch.clientX, touch.clientY)
}
}
window.addEventListener("mousemove", handleMouseMove)
window.addEventListener("touchmove", handleTouchMove, { passive: true })
window.addEventListener("touchstart", handleTouchStart, { passive: true })
return () => {
window.removeEventListener("mousemove", handleMouseMove)
window.removeEventListener("touchmove", handleTouchMove)
window.removeEventListener("touchstart", handleTouchStart)
}
}, [])
return (
<div className="pointer-events-none fixed inset-0 z-50">
{particles.map((particle) => (
<div
key={particle.id}
className="absolute"
style={{
left: particle.x,
top: particle.y,
}}
>
{particle.type === "sparkle" ? (
<div
className="h-3 w-3 -translate-x-1/2 -translate-y-1/2"
style={
{
animation: "cursor-spark 1.5s ease-out forwards",
"--vx": particle.vx,
"--vy": particle.vy,
} as React.CSSProperties
}
>
<svg viewBox="0 0 24 24" fill={particle.color}>
<path d="M12 0L14.59 9.41L24 12L14.59 14.59L12 24L9.41 14.59L0 12L9.41 9.41L12 0Z" />
</svg>
</div>
) : (
<div
className="h-4 w-4 -translate-x-1/2 -translate-y-1/2"
style={
{
animation: "cursor-rainbow-star 1.8s ease-out forwards",
"--vx": particle.vx,
"--vy": particle.vy,
} as React.CSSProperties
}
>
<svg viewBox="0 0 24 24" fill="url(#rainbow-gradient)">
<defs>
<linearGradient id="rainbow-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#ff0000" />
<stop offset="20%" stopColor="#ff9a00" />
<stop offset="40%" stopColor="#4fdc4a" />
<stop offset="60%" stopColor="#2fc9e2" />
<stop offset="80%" stopColor="#5f15f2" />
<stop offset="100%" stopColor="#fb07d9" />
</linearGradient>
</defs>
<path d="M12 0L14.59 9.41L24 12L14.59 14.59L12 24L9.41 14.59L0 12L9.41 9.41L12 0Z" />
</svg>
</div>
)}
</div>
))}
</div>
)
}