import React, { useRef, useState, useCallback } from 'react'; import { cn } from '../../utils'; interface TiltCardProps extends React.HTMLAttributes { children: React.ReactNode; glow?: boolean; tiltMax?: number; } export const TiltCard = React.forwardRef( ({ className, children, glow = false, tiltMax = 14, style, ...props }, _ref) => { const cardRef = useRef(null); const frameRef = useRef(0); const [tilt, setTilt] = useState({ rotX: 0, rotY: 0, scale: 1 }); const [glare, setGlare] = useState({ x: 50, y: 50, opacity: 0 }); // normalized -1..1 mouse position for the border gradient const [border, setBorder] = useState({ x: 0.5, y: 0.5, opacity: 0 }); const handleMouseMove = useCallback((e: React.MouseEvent) => { const card = cardRef.current; if (!card) return; const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const nx = x / rect.width; // 0..1 const ny = y / rect.height; cancelAnimationFrame(frameRef.current); frameRef.current = requestAnimationFrame(() => { setTilt({ rotX: -(ny - 0.5) * tiltMax * 2, rotY: (nx - 0.5) * tiltMax * 2, scale: 1.03, }); setGlare({ x: nx * 100, y: ny * 100, opacity: 0.2 }); setBorder({ x: nx, y: ny, opacity: 1 }); }); }, [tiltMax]); const handleMouseLeave = useCallback(() => { cancelAnimationFrame(frameRef.current); setTilt({ rotX: 0, rotY: 0, scale: 1 }); setGlare(g => ({ ...g, opacity: 0 })); setBorder(b => ({ ...b, opacity: 0 })); }, []); const isResting = tilt.rotX === 0 && tilt.rotY === 0; // Dynamic border: a conic gradient anchored to mouse position const borderAngle = Math.atan2(border.y - 0.5, border.x - 0.5) * (180 / Math.PI) + 90; return (
{/* Animated neon border */}
{/* Card body */}
{/* Glare highlight */}
{/* Laser shimmer sweep on hover */}
{/* Content — slight Z lift for depth */}
{children}
); } ); TiltCard.displayName = 'TiltCard';