replicalab / frontend /src /components /TiltCard.tsx
maxxie114's picture
Initial HF Spaces deployment
80d8c84
import { useRef, useState, type ReactNode, type MouseEvent } from 'react';
import { motion } from 'framer-motion';
import { cn } from '@/lib/utils';
interface TiltCardProps {
children: ReactNode;
className?: string;
glareColor?: string;
tiltDegree?: number;
}
export default function TiltCard({
children,
className,
glareColor = 'rgba(255,255,255,0.15)',
tiltDegree = 10,
}: TiltCardProps) {
const cardRef = useRef<HTMLDivElement>(null);
const [transform, setTransform] = useState({ rotateX: 0, rotateY: 0 });
const [glare, setGlare] = useState({ x: 50, y: 50, opacity: 0 });
function handleMouseMove(e: MouseEvent<HTMLDivElement>) {
if (!cardRef.current) return;
const rect = cardRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
setTransform({
rotateX: (0.5 - y) * tiltDegree,
rotateY: (x - 0.5) * tiltDegree,
});
setGlare({ x: x * 100, y: y * 100, opacity: 0.08 });
}
function handleMouseLeave() {
setTransform({ rotateX: 0, rotateY: 0 });
setGlare({ x: 50, y: 50, opacity: 0 });
}
return (
<motion.div
ref={cardRef}
className={cn('relative overflow-hidden', className)}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
animate={{
rotateX: transform.rotateX,
rotateY: transform.rotateY,
}}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
style={{ perspective: 800, transformStyle: 'preserve-3d' }}
>
{children}
<div
className="pointer-events-none absolute inset-0 rounded-[inherit] transition-opacity duration-300"
style={{
background: `radial-gradient(circle at ${glare.x}% ${glare.y}%, ${glareColor}, transparent 60%)`,
opacity: glare.opacity,
}}
/>
</motion.div>
);
}