{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"tilt-card","type":"registry:component","title":"Tilt Card","description":"3D perspective tilt on hover with cursor-tracked glare.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/tilt-card.tsx","type":"registry:component","target":"@components/motion/tilt-card.tsx","content":"\"use client\";\n\nimport { motion, useMotionTemplate, useMotionValue, useReducedMotion, useSpring } from \"motion/react\";\nimport { useRef, type ReactNode } from \"react\";\nimport { SPRING_MOUSE } from \"@/lib/ease\";\nimport { useHoverCapable } from \"@/lib/hooks/use-hover-capable\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface TiltCardProps {\n  children: ReactNode;\n  max?: number;\n  glare?: boolean;\n  className?: string;\n}\n\nexport function TiltCard({ children, max = 12, glare = true, className }: TiltCardProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const reduce = useReducedMotion();\n  const canHover = useHoverCapable();\n  // Decorative cursor-follow: skip on touch (phantom hover) and reduced motion.\n  const enabled = !reduce && canHover;\n  const rx = useMotionValue(0);\n  const ry = useMotionValue(0);\n  const gx = useMotionValue(50);\n  const gy = useMotionValue(50);\n\n  const srx = useSpring(rx, SPRING_MOUSE);\n  const sry = useSpring(ry, SPRING_MOUSE);\n\n  const onMove = (e: React.MouseEvent<HTMLDivElement>) => {\n    const el = ref.current;\n    if (!el || !enabled) return;\n    const rect = el.getBoundingClientRect();\n    const px = (e.clientX - rect.left) / rect.width;\n    const py = (e.clientY - rect.top) / rect.height;\n    ry.set((px - 0.5) * max);\n    rx.set((0.5 - py) * max);\n    gx.set(px * 100);\n    gy.set(py * 100);\n  };\n\n  const onLeave = () => {\n    rx.set(0);\n    ry.set(0);\n  };\n\n  const transform = useMotionTemplate`perspective(1000px) rotateX(${srx}deg) rotateY(${sry}deg)`;\n  const glareBg = useMotionTemplate`radial-gradient(circle at ${gx}% ${gy}%, var(--foreground), transparent 50%)`;\n\n  return (\n    <motion.div\n      ref={ref}\n      onMouseMove={onMove}\n      onMouseLeave={onLeave}\n      style={{ transform, transformStyle: \"preserve-3d\" }}\n      className={cn(\"relative overflow-hidden rounded-2xl will-change-transform\", className)}\n    >\n      {children}\n      {glare && enabled ? (\n        <motion.div\n          aria-hidden\n          style={{ background: glareBg }}\n          className=\"pointer-events-none absolute inset-0 opacity-15\"\n        />\n      ) : null}\n    </motion.div>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Easing curves mirror the CSS custom properties in\n// globals.css; springs are the canonical physics used across components.\n// Strong custom variants — defaults like `ease-in`/`ease-out` feel weak.\n\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.16, 1, 0.3, 1)\";\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n  mass: 0.6,\n} as const;\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 30,\n  mass: 0.55,\n} as const;\n\n/** Overlay panel entrances — modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 40,\n  mass: 0.5,\n} as const;\n\n/** Shared-layout glides — pills, indicators and panels morphing between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  stiffness: 360,\n  damping: 32,\n  mass: 0.6,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */\nexport const SPRING_MOUSE = {\n  stiffness: 200,\n  damping: 15,\n  mass: 0.3,\n} as const;\n"},{"path":"lib/hooks/use-hover-capable.ts","type":"registry:hook","target":"@lib/hooks/use-hover-capable.ts","content":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/**\n * Returns true only on devices that have a true hover (mouse / trackpad).\n * Touch devices fire phantom `:hover` on tap that sticks until tap-elsewhere\n * — gate hover-only effects (scale lifts, magnetic pulls) behind this.\n */\nexport function useHoverCapable() {\n  const [canHover, setCanHover] = useState(false);\n\n  useEffect(() => {\n    if (typeof window === \"undefined\" || !window.matchMedia) return;\n    const mq = window.matchMedia(\"(hover: hover) and (pointer: fine)\");\n    const update = () => setCanHover(mq.matches);\n    update();\n    mq.addEventListener?.(\"change\", update);\n    return () => mq.removeEventListener?.(\"change\", update);\n  }, []);\n\n  return canHover;\n}\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}