{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tilt-card",
  "type": "registry:ui",
  "title": "Tilt Card",
  "description": "The TiltCard component tilts toward the pointer in 3D and tracks it with a specular highlight, giving flat cards a tactile, physical feel.",
  "author": "Claude Code (https://claude.com/claude-code)",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/demo/card/tilt-card.tsx",
      "content": "\"use client\";\n\nimport { useRef, type ReactNode } from \"react\";\nimport {\n  motion,\n  useMotionTemplate,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n} from \"motion/react\";\n\ninterface TiltCardProps {\n  children: ReactNode;\n  /** Maximum rotation in degrees at the edges of the card. */\n  maxTilt?: number;\n  /** Scale applied while the pointer is over the card. */\n  hoverScale?: number;\n  /** Strength of the specular highlight, 0 to 1. */\n  glareOpacity?: number;\n  className?: string;\n}\n\n/**\n * A card that tilts toward the pointer in 3D, with a specular highlight that\n * tracks the cursor.\n */\nexport function TiltCard({\n  children,\n  maxTilt = 12,\n  hoverScale = 1.03,\n  glareOpacity = 0.25,\n  className = \"\",\n}: TiltCardProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const prefersReducedMotion = useReducedMotion();\n\n  // Pointer position as CSS percentages, fed straight into the glare gradient.\n  const px = useMotionValue(\"50%\");\n  const py = useMotionValue(\"50%\");\n\n  const spring = { stiffness: 260, damping: 24, mass: 0.6 };\n  const rotateX = useSpring(useMotionValue(0), spring);\n  const rotateY = useSpring(useMotionValue(0), spring);\n  const scale = useSpring(useMotionValue(1), spring);\n\n  /*\n   * Visibility of the highlight, separate from its position.\n   *\n   * Without this the gradient was painted the whole time, parked at the\n   * card's centre — so the card sat there with a permanent bright blob on it\n   * whether or not a pointer was anywhere near. A highlight is a response to\n   * the pointer, so it starts at zero and fades in on enter.\n   */\n  const glareStrength = useSpring(useMotionValue(0), {\n    stiffness: 180,\n    damping: 26,\n  });\n\n  const glare = useMotionTemplate`radial-gradient(circle at ${px} ${py}, rgba(255,255,255,${glareOpacity}), transparent 60%)`;\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    const element = ref.current;\n    if (!element || prefersReducedMotion) return;\n\n    const rect = element.getBoundingClientRect();\n    const x = (event.clientX - rect.left) / rect.width;\n    const y = (event.clientY - rect.top) / rect.height;\n\n    px.set(`${x * 100}%`);\n    py.set(`${y * 100}%`);\n\n    // Invert X so the card leans toward the cursor rather than away from it.\n    rotateX.set(-(y - 0.5) * 2 * maxTilt);\n    rotateY.set((x - 0.5) * 2 * maxTilt);\n  };\n\n  const enter = () => {\n    if (prefersReducedMotion) return;\n    scale.set(hoverScale);\n    glareStrength.set(1);\n  };\n\n  const reset = () => {\n    rotateX.set(0);\n    rotateY.set(0);\n    scale.set(1);\n    glareStrength.set(0);\n    // Recentre so the next hover fades in from the middle rather than\n    // sliding in from wherever the pointer last left.\n    px.set(\"50%\");\n    py.set(\"50%\");\n  };\n\n  return (\n    <div style={{ perspective: 1000 }} className={className}>\n      <motion.div\n        ref={ref}\n        onPointerMove={handlePointerMove}\n        onPointerEnter={enter}\n        onPointerLeave={reset}\n        style={{ rotateX, rotateY, scale, transformStyle: \"preserve-3d\" }}\n        className=\"relative overflow-hidden rounded-2xl border bg-card p-6 shadow-lg\"\n      >\n        <motion.div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0\"\n          style={{ background: glare, opacity: glareStrength }}\n        />\n        <div style={{ transform: \"translateZ(40px)\" }}>{children}</div>\n      </motion.div>\n    </div>\n  );\n}\n\nexport default TiltCard;\n",
      "type": "registry:ui",
      "target": "components/ui/tilt-card.tsx"
    }
  ]
}
