jeffemmett-website-redesign/components/cursor-effect.tsx

80 lines
2.4 KiB
TypeScript

'use client'
import { useEffect, useState } from 'react'
export function CursorEffect() {
const [position, setPosition] = useState({ x: 0, y: 0 })
const [isHovering, setIsHovering] = useState(false)
const [trails, setTrails] = useState<Array<{ x: number; y: number; id: number }>>([])
useEffect(() => {
let trailId = 0
const handleMouseMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY })
// Add trail
const newTrail = { x: e.clientX, y: e.clientY, id: trailId++ }
setTrails(prev => [...prev.slice(-8), newTrail])
// Check if hovering over interactive element
const target = e.target as HTMLElement
const isInteractive = target.closest('a, button, [role="button"]')
setIsHovering(!!isInteractive)
}
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
return (
<>
{/* Main cursor */}
<div
className="pointer-events-none fixed z-[9999] mix-blend-difference"
style={{
left: `${position.x}px`,
top: `${position.y}px`,
transform: 'translate(-50%, -50%)',
transition: 'width 0.2s, height 0.2s',
}}
>
<div
className={`rounded-full bg-primary transition-all duration-200 ${
isHovering ? 'h-12 w-12' : 'h-6 w-6'
}`}
/>
</div>
{/* Trail effect */}
{trails.map((trail, index) => (
<div
key={trail.id}
className="pointer-events-none fixed z-[9998] rounded-full bg-accent mix-blend-screen"
style={{
left: `${trail.x}px`,
top: `${trail.y}px`,
transform: 'translate(-50%, -50%)',
width: `${4 + index}px`,
height: `${4 + index}px`,
opacity: (index + 1) / trails.length * 0.3,
transition: 'opacity 0.5s',
}}
/>
))}
{/* Outer ring */}
<div
className="pointer-events-none fixed z-[9997] rounded-full border-2 border-primary/30 transition-all duration-300"
style={{
left: `${position.x}px`,
top: `${position.y}px`,
transform: 'translate(-50%, -50%)',
width: isHovering ? '64px' : '48px',
height: isHovering ? '64px' : '48px',
}}
/>
</>
)
}