{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tilt-card",
  "type": "registry:component",
  "title": "Tilt card",
  "description": "A card that tilts toward the pointer in 3D, with a sliding glare and parallax children, and springs flat when the pointer leaves.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/tilt-card.tsx",
      "type": "registry:ui",
      "target": "components/ui/tilt-card.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { motion, useMotionTemplate, useMotionValue, useReducedMotion, useSpring, useTransform } from \"motion/react\"\nimport { ArrowRightIcon, CheckIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\n/**\n * A card that tilts toward the pointer in 3D, with a sliding glare and parallax children, and springs flat when the pointer leaves.\n * The glare moves across the face like light on a foil print. Children\n * marked `data-depth` float at their own parallax rate. Touch scrolls\n * straight through it; reduced motion keeps it flat.\n */\ntype TiltCardProps = {\n  children?: React.ReactNode\n  /** Maximum rotation in degrees on either axis. */\n  maxTilt?: number\n  /** Glare strength from 0 (off) to 1. */\n  glare?: number\n  /** Perspective distance in pixels; smaller is more dramatic. */\n  perspective?: number\n  /** Pixels of travel for a child with `data-depth=\"1\"`; scale the number for more or less. */\n  parallax?: number\n  className?: string\n}\n\n/** The roll spring from the design spec: enough mass that the card settles rather than snaps. */\nconst ROLL = { stiffness: 260, damping: 28, mass: 0.9 }\n\nfunction TiltCard({ children, maxTilt = 12, glare = 0.6, perspective = 900, parallax = 10, className }: TiltCardProps) {\n  const reduce = useReducedMotion()\n  const ref = React.useRef<HTMLDivElement>(null)\n  // Pointer position across the card, -0.5 to 0.5 on each axis; the springs give it mass.\n  const px = useMotionValue(0)\n  const py = useMotionValue(0)\n  const lit = useMotionValue(0)\n  const sx = useSpring(px, ROLL)\n  const sy = useSpring(py, ROLL)\n  const shine = useSpring(lit, ROLL)\n  // The side under the pointer sinks, like a card pressed on a table.\n  const rotateX = useTransform(sy, [-0.5, 0.5], [maxTilt, -maxTilt])\n  const rotateY = useTransform(sx, [-0.5, 0.5], [-maxTilt, maxTilt])\n  const gx = useTransform(sx, [-0.5, 0.5], [0, 100])\n  const gy = useTransform(sy, [-0.5, 0.5], [0, 100])\n  const glareLayer = useMotionTemplate`radial-gradient(ellipse 70% 60% at ${gx}% ${gy}%, var(--tilt-glare), transparent 70%)`\n  const glareOpacity = useTransform(shine, (v) => v * Math.min(1, Math.max(0, glare)))\n\n  // Parallax: every descendant with data-depth is moved straight on the DOM as the springs settle, no re-render.\n  React.useEffect(() => {\n    const root = ref.current\n    if (!root) return\n    const layers = () => Array.from(root.querySelectorAll<HTMLElement>(\"[data-depth]\"))\n    if (reduce) {\n      for (const el of layers()) el.style.transform = \"\"\n      return\n    }\n    // Layers slide with the pointer and lift out of the card while it is lit, then settle back flat.\n    const update = () => {\n      const x = sx.get()\n      const y = sy.get()\n      const z = shine.get()\n      for (const el of layers()) {\n        const d = Number.parseFloat(el.dataset.depth ?? \"0\") || 0\n        el.style.transform = `translate3d(${(x * parallax * d).toFixed(2)}px, ${(y * parallax * d).toFixed(2)}px, ${(z * d * 14).toFixed(1)}px)`\n      }\n    }\n    update()\n    const offX = sx.on(\"change\", update)\n    const offY = sy.on(\"change\", update)\n    const offZ = shine.on(\"change\", update)\n    return () => {\n      offX()\n      offY()\n      offZ()\n    }\n  }, [sx, sy, shine, parallax, reduce])\n\n  const onMove = (e: React.PointerEvent<HTMLDivElement>) => {\n    if (reduce || e.pointerType === \"touch\") return\n    const r = e.currentTarget.getBoundingClientRect()\n    px.set(Math.min(0.5, Math.max(-0.5, (e.clientX - r.left) / r.width - 0.5)))\n    py.set(Math.min(0.5, Math.max(-0.5, (e.clientY - r.top) / r.height - 0.5)))\n    lit.set(1)\n  }\n  const onLeave = () => {\n    px.set(0)\n    py.set(0)\n    lit.set(0)\n  }\n\n  return (\n    <motion.div\n      ref={ref}\n      data-slot=\"tilt-card\"\n      onPointerMove={onMove}\n      onPointerLeave={onLeave}\n      onPointerCancel={onLeave}\n      style={\n        reduce\n          ? undefined\n          : ({\n              rotateX,\n              rotateY,\n              transformPerspective: perspective,\n              transformStyle: \"preserve-3d\",\n              \"--tilt-glare\": \"light-dark(color-mix(in oklch, var(--accent) 30%, transparent), color-mix(in oklch, var(--ink) 22%, transparent))\",\n            } as React.CSSProperties)\n      }\n      className={cn(\"relative mx-auto w-[340px] max-w-full rounded-2xl border border-border bg-surface p-6 shadow-raised\", className)}\n    >\n      {!reduce ? (\n        <motion.span aria-hidden className=\"pointer-events-none absolute inset-0 rounded-[inherit]\" style={{ background: glareLayer, opacity: glareOpacity }} />\n      ) : null}\n      <div className=\"relative [transform-style:preserve-3d]\">\n        {children ?? (\n          <>\n            <div data-depth=\"0.5\" className=\"flex items-center justify-between\">\n              <span className=\"rounded-full border border-accent-line bg-accent-soft px-2.5 py-0.5 text-xs font-medium text-accent-fg\">Lumen Pro</span>\n              <span className=\"text-xs text-fg-subtle\">Billed yearly</span>\n            </div>\n            <p data-depth=\"1\" className=\"mt-5 text-4xl font-medium tracking-[-0.04em] text-fg tabular-nums\">\n              $24<span className=\"text-base font-normal tracking-normal text-fg-muted\"> / month</span>\n            </p>\n            <p className=\"mt-1 text-sm leading-[1.6] text-fg-muted\">For teams closing more than 100 invoices a month.</p>\n            <ul data-depth=\"0.5\" className=\"mt-5 space-y-2.5\">\n              {[\"Unlimited invoices and customers\", \"Reminders that send themselves\", \"Multi-currency with live rates\", \"Priority support, 2h response\"].map((f) => (\n                <li key={f} className=\"flex items-center gap-2.5 text-sm text-fg\">\n                  <CheckIcon className=\"size-4 shrink-0 text-accent-fg\" strokeWidth={2.25} aria-hidden />\n                  {f}\n                </li>\n              ))}\n            </ul>\n            <div data-depth=\"1.2\" className=\"mt-6\">\n              <Button className=\"w-full\">\n                Start free trial <ArrowRightIcon />\n              </Button>\n            </div>\n          </>\n        )}\n      </div>\n    </motion.div>\n  )\n}\n\nexport { TiltCard }\nexport type { TiltCardProps }\n"
    }
  ]
}
