1"use client";2
3import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";4import clsx from "clsx";5import { ReactNode } from "react";6
7interface ThreeDTiltCardProps {8 children: ReactNode;9 className?: string;10 intensity?: number;11 depth?: number;12 border?: boolean;13}14
15export function ThreeDTiltCard({16 children,17 className,18 intensity = 0.45,19 depth = 80,20 border = true,21}: ThreeDTiltCardProps) {22 const x = useMotionValue(0);23 const y = useMotionValue(0);24 const z = useMotionValue(0);25
26 const springConfig = { stiffness: 120, damping: 18, mass: 0.6 };27 const springX = useSpring(x, springConfig);28 const springY = useSpring(y, springConfig);29 const springZ = useSpring(z, { stiffness: 100, damping: 15 });30
31 const rotateX = useTransform(32 springY,33 [-intensity, intensity],34 ["12deg", "-12deg"],35 );36 const rotateY = useTransform(37 springX,38 [-intensity, intensity],39 ["-12deg", "12deg"],40 );41
42 const childTranslateX = useTransform(43 springX,44 [-intensity, intensity],45 [depth * 0.2, -depth * 0.2],46 );47 const childTranslateY = useTransform(48 springY,49 [-intensity, intensity],50 [depth * 0.2, -depth * 0.2],51 );52
53 const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {54 const rect = e.currentTarget.getBoundingClientRect();55 const xPct = (e.clientX - rect.left) / rect.width - 0.5;56 const yPct = (e.clientY - rect.top) / rect.height - 0.5;57 x.set(xPct * 2);58 y.set(yPct * 2);59 z.set(depth);60 };61
62 const resetTilt = () => {63 x.set(0);64 y.set(0);65 z.set(0);66 };67
68 return (69 <motion.div70 onMouseMove={handleMouseMove}71 onMouseLeave={resetTilt}72 style={{73 rotateX,74 rotateY,75 transformStyle: "preserve-3d",76 perspective: 1200,77 }}78 whileHover={{ scale: 1.03 }}79 transition={{ type: "spring", stiffness: 160, damping: 18 }}80 className={clsx(81 "group relative rounded-2xl bg-neutral-50 dark:bg-neutral-900",82 "shadow-[0_6px_20px_rgba(0,0,0,0.08)] dark:shadow-[0_6px_20px_rgba(255,255,255,0.04)]",83 "transition-all duration-500 ease-[cubic-bezier(0.19,1,0.22,1)]",84 className,85 )}86 >87 {}88 <div89 className={clsx(90 "pointer-events-none absolute inset-px z-0 rounded-2xl blur-xl transition-opacity duration-700",91 "opacity-0 group-hover:opacity-100",92 "bg-linear-to-br from-neutral-100/50 via-neutral-200/20 to-neutral-300/10",93 "dark:from-neutral-800/40 dark:via-neutral-900/20 dark:to-neutral-950/10",94 )}95 />96
97 {}98 <motion.div99 style={{ rotateX, rotateY }}100 className="pointer-events-none absolute inset-0 z-10 rounded-2xl bg-linear-to-tl from-white/15 via-transparent to-transparent dark:from-white/5"101 />102
103 {}104 <div105 style={{ transformStyle: "preserve-3d" }}106 className={clsx(107 "relative z-20 h-full w-full rounded-2xl p-6 backdrop-blur-md",108 border &&109 "border border-neutral-200 shadow-inner dark:border-neutral-800",110 )}111 >112 <motion.div113 style={{114 translateX: childTranslateX,115 translateY: childTranslateY,116 translateZ: springZ,117 transformStyle: "preserve-3d",118 }}119 className="transition-transform duration-700 ease-[cubic-bezier(0.19,1,0.22,1)]"120 >121 {children}122 </motion.div>123 </div>124
125 {}126 <div className="pointer-events-none absolute inset-0 -z-10 rounded-2xl shadow-[inset_0_0_35px_rgba(0,0,0,0.04)] dark:shadow-[inset_0_0_35px_rgba(255,255,255,0.03)]" />127 </motion.div>128 );129}