bondingcurve-website/components/cursor-trails.tsx

155 lines
4.4 KiB
TypeScript

"use client"
import { useEffect, useRef } from "react"
interface Trail {
points: { x: number; y: number }[]
color: string
opacity: number
createdAt: number
}
export function CursorTrails() {
const canvasRef = useRef<HTMLCanvasElement>(null)
const trailsRef = useRef<Trail[]>([])
const mouseRef = useRef({ x: 0, y: 0, lastX: 0, lastY: 0 })
const animationRef = useRef<number>()
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")
if (!ctx) return
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
resize()
window.addEventListener("resize", resize)
const colors = [
"rgba(0, 255, 255, 0.6)", // Cyan
"rgba(255, 0, 255, 0.6)", // Magenta
"rgba(138, 43, 226, 0.6)", // Purple
"rgba(255, 140, 0, 0.6)", // Orange
"rgba(20, 20, 30, 0.8)", // Near-black
"rgba(0, 200, 200, 0.6)", // Teal
"rgba(255, 100, 50, 0.6)", // Orange-red
]
let colorIndex = 0
let frameCount = 0
const handleMouseMove = (e: MouseEvent) => {
mouseRef.current.lastX = mouseRef.current.x
mouseRef.current.lastY = mouseRef.current.y
mouseRef.current.x = e.clientX
mouseRef.current.y = e.clientY
const dx = mouseRef.current.x - mouseRef.current.lastX
const dy = mouseRef.current.y - mouseRef.current.lastY
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance > 5 && frameCount % 2 === 0) {
const points: { x: number; y: number }[] = []
const steps = 20
// Create a perpendicular vector for sine wave oscillation
const perpX = -dy / distance
const perpY = dx / distance
// Random frequency and amplitude for varied squiggles
const frequency = 0.3 + Math.random() * 0.5
const amplitude = 15 + Math.random() * 25
for (let i = 0; i <= steps; i++) {
const t = i / steps
// Base position along the line
const baseX = mouseRef.current.lastX + dx * t
const baseY = mouseRef.current.lastY + dy * t
// Add sine wave oscillation perpendicular to movement direction
const wave = Math.sin(t * Math.PI * frequency * 4) * amplitude * (1 - t * 0.3)
points.push({
x: baseX + perpX * wave,
y: baseY + perpY * wave,
})
}
trailsRef.current.push({
points,
color: colors[colorIndex],
opacity: 1,
createdAt: Date.now(),
})
colorIndex = (colorIndex + 1) % colors.length
}
frameCount++
}
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
const now = Date.now()
trailsRef.current = trailsRef.current.filter((trail) => {
const age = now - trail.createdAt
const maxAge = 2000
if (age > maxAge) return false
trail.opacity = 1 - age / maxAge
ctx.beginPath()
ctx.strokeStyle = trail.color.replace(/[\d.]+\)$/, `${trail.opacity * 0.6})`)
ctx.lineWidth = 2 + trail.opacity * 2
ctx.lineCap = "round"
ctx.lineJoin = "round"
if (trail.points.length > 0) {
ctx.moveTo(trail.points[0].x, trail.points[0].y)
for (let i = 1; i < trail.points.length - 1; i++) {
const xc = (trail.points[i].x + trail.points[i + 1].x) / 2
const yc = (trail.points[i].y + trail.points[i + 1].y) / 2
ctx.quadraticCurveTo(trail.points[i].x, trail.points[i].y, xc, yc)
}
if (trail.points.length > 1) {
const last = trail.points[trail.points.length - 1]
ctx.lineTo(last.x, last.y)
}
}
// Add glow effect
ctx.shadowBlur = 15 * trail.opacity
ctx.shadowColor = trail.color
ctx.stroke()
ctx.shadowBlur = 0
return true
})
animationRef.current = requestAnimationFrame(animate)
}
window.addEventListener("mousemove", handleMouseMove)
animate()
return () => {
window.removeEventListener("mousemove", handleMouseMove)
window.removeEventListener("resize", resize)
if (animationRef.current) {
cancelAnimationFrame(animationRef.current)
}
}
}, [])
return (
<canvas ref={canvasRef} className="pointer-events-none fixed inset-0 z-50" style={{ mixBlendMode: "screen" }} />
)
}