{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tilt-card",
  "title": "3D Tilt Card",
  "description": "A 3D perspective tilt card that rotates toward the pointer with spring smoothing, parallax translateZ depth layers, and a pointer-following glare highlight",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "app/registry/tilt-card/tilt-card.tsx",
      "content": "/**\n * Spectrum UI — TiltCard\n *\n * A 3D perspective tilt card. The surface rotates toward the pointer with\n * spring smoothing, TiltCardItem children lift toward the viewer on their own\n * translateZ depth for a parallax effect, and a glare highlight follows the\n * pointer across the card. Tilt only engages for mouse pointers and is\n * disabled entirely under prefers-reduced-motion.\n *\n * Dependencies: framer-motion, @/lib/utils\n *\n * @example\n * <TiltCard className=\"max-w-sm p-6\">\n *   <TiltCardItem depth={60}>Floats above the card</TiltCardItem>\n * </TiltCard>\n */\n\n\"use client\"\n\nimport React, {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\"\nimport {\n  animate,\n  motion,\n  useMotionTemplate,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n} from \"framer-motion\"\nimport { cn } from \"@/lib/utils\"\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface TiltCardProps {\n  children: React.ReactNode\n  /** Maximum rotation toward the pointer in degrees. Default 12 */\n  maxTilt?: number\n  /** Invert the tilt so the card leans away from the pointer. Default false */\n  tiltReverse?: boolean\n  /** Scale applied while hovered. Default 1.02 */\n  scale?: number\n  /** CSS perspective distance in pixels. Default 1000 */\n  perspective?: number\n  /** Show the pointer-following glare highlight. Default true */\n  glare?: boolean\n  /** Color at the center of the glare gradient */\n  glareColor?: string\n  /** Classes for the outer perspective wrapper */\n  containerClassName?: string\n  /** Classes for the card surface */\n  className?: string\n}\n\nexport interface TiltCardItemProps {\n  children: React.ReactNode\n  /** Lift toward the viewer in pixels while the card is hovered. Default 0 */\n  depth?: number\n  className?: string\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/** Spring used while the pointer is tracking across the card */\nconst TRACK_SPRING = {\n  type: \"spring\",\n  stiffness: 260,\n  damping: 22,\n  mass: 0.6,\n} as const\n/** Softer spring so the card settles gently back to rest on pointer leave */\nconst RESET_SPRING = {\n  type: \"spring\",\n  stiffness: 140,\n  damping: 18,\n  mass: 1,\n} as const\n/** Normalized pointer position at rest (card center) */\nconst REST_POINT = 0.5\n/** Subtle press-down while the card is clicked */\nconst PRESS_SCALE = 0.99\n\nconst TiltCardContext = createContext<{ hovered: boolean }>({ hovered: false })\n\n// ─── Components ──────────────────────────────────────────────────────────────\n\nexport function TiltCard({\n  children,\n  maxTilt = 12,\n  tiltReverse = false,\n  scale = 1.02,\n  perspective = 1000,\n  glare = true,\n  glareColor = \"rgba(255, 255, 255, 0.35)\",\n  containerClassName,\n  className,\n}: TiltCardProps) {\n  const shouldReduceMotion = useReducedMotion()\n  const [hovered, setHovered] = useState(false)\n\n  // Smoothed normalized pointer position; animated toward each pointer sample\n  // with TRACK_SPRING and back to rest with the softer RESET_SPRING\n  const tiltX = useMotionValue(REST_POINT)\n  const tiltY = useMotionValue(REST_POINT)\n  const cardScale = useSpring(1, TRACK_SPRING)\n\n  const tiltSign = tiltReverse ? -1 : 1\n  const rotateX = useTransform(\n    tiltY,\n    [0, 1],\n    [maxTilt * tiltSign, -maxTilt * tiltSign],\n  )\n  const rotateY = useTransform(\n    tiltX,\n    [0, 1],\n    [-maxTilt * tiltSign, maxTilt * tiltSign],\n  )\n  const glarePosX = useTransform(tiltX, (value) => value * 100)\n  const glarePosY = useTransform(tiltY, (value) => value * 100)\n  const glareBackground = useMotionTemplate`radial-gradient(circle at ${glarePosX}% ${glarePosY}%, ${glareColor}, transparent 65%)`\n\n  // Stop any in-flight tilt animations if the card unmounts mid-gesture\n  useEffect(\n    () => () => {\n      tiltX.stop()\n      tiltY.stop()\n    },\n    [tiltX, tiltY],\n  )\n\n  const handlePointerMove = useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      if (event.pointerType !== \"mouse\" || shouldReduceMotion) return\n      const rect = event.currentTarget.getBoundingClientRect()\n      animate(tiltX, (event.clientX - rect.left) / rect.width, TRACK_SPRING)\n      animate(tiltY, (event.clientY - rect.top) / rect.height, TRACK_SPRING)\n    },\n    [tiltX, tiltY, shouldReduceMotion],\n  )\n\n  const handlePointerEnter = useCallback(\n    (event: React.PointerEvent<HTMLDivElement>) => {\n      if (event.pointerType !== \"mouse\" || shouldReduceMotion) return\n      setHovered(true)\n      cardScale.set(scale)\n    },\n    [cardScale, scale, shouldReduceMotion],\n  )\n\n  const handlePointerLeave = useCallback(() => {\n    setHovered(false)\n    cardScale.set(1)\n    animate(tiltX, REST_POINT, RESET_SPRING)\n    animate(tiltY, REST_POINT, RESET_SPRING)\n  }, [cardScale, tiltX, tiltY])\n\n  const handlePointerDown = useCallback(() => {\n    if (shouldReduceMotion) return\n    cardScale.set(PRESS_SCALE)\n  }, [cardScale, shouldReduceMotion])\n\n  const handlePointerUp = useCallback(() => {\n    cardScale.set(hovered ? scale : 1)\n  }, [cardScale, hovered, scale])\n\n  return (\n    <div\n      className={cn(\"relative\", containerClassName)}\n      style={{ perspective: `${perspective}px` }}\n    >\n      <motion.div\n        onPointerMove={handlePointerMove}\n        onPointerEnter={handlePointerEnter}\n        onPointerLeave={handlePointerLeave}\n        onPointerDown={handlePointerDown}\n        onPointerUp={handlePointerUp}\n        onPointerCancel={handlePointerUp}\n        style={{\n          rotateX: shouldReduceMotion ? 0 : rotateX,\n          rotateY: shouldReduceMotion ? 0 : rotateY,\n          scale: cardScale,\n          transformStyle: \"preserve-3d\",\n        }}\n        className={cn(\n          \"relative rounded-2xl border border-neutral-200 bg-white will-change-transform\",\n          \"shadow-[0px_1px_2px_0px_rgba(0,0,0,0.04),0px_2px_4px_0px_rgba(0,0,0,0.04)]\",\n          \"dark:border-neutral-800 dark:bg-neutral-900 dark:shadow-none\",\n          className,\n        )}\n      >\n        <TiltCardContext.Provider value={{ hovered }}>\n          <div style={{ transformStyle: \"preserve-3d\" }}>{children}</div>\n        </TiltCardContext.Provider>\n\n        {glare && !shouldReduceMotion && (\n          <motion.div\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-0 rounded-[inherit]\"\n            style={{ background: glareBackground, transform: \"translateZ(1px)\" }}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: hovered ? 1 : 0 }}\n            transition={{ duration: 0.3, ease: \"easeOut\" }}\n          />\n        )}\n      </motion.div>\n    </div>\n  )\n}\n\nexport function TiltCardItem({\n  children,\n  depth = 0,\n  className,\n}: TiltCardItemProps) {\n  const shouldReduceMotion = useReducedMotion()\n  const { hovered } = useContext(TiltCardContext)\n  const lifted = hovered && !shouldReduceMotion\n\n  return (\n    <div\n      className={cn(\n        \"transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none\",\n        className,\n      )}\n      style={{\n        transform: lifted ? `translateZ(${depth}px)` : \"translateZ(0px)\",\n        transformStyle: \"preserve-3d\",\n      }}\n    >\n      {children}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/spectrumui/tilt-card.tsx"
    }
  ],
  "type": "registry:component"
}
