{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tilt-card",
  "type": "registry:ui",
  "title": "Tilt Card",
  "description": "A card that leans toward the pointer and catches the light with a soft moving glare.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/tilt-card.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface TiltCardProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Maximum rotation in degrees when the pointer is at an edge. */\n  maxTilt?: number;\n  /** Scale applied while the pointer is over the card. */\n  scale?: number;\n  /** CSS perspective distance in pixels. */\n  perspective?: number;\n  /** Show a light sheen that follows the pointer. */\n  glare?: boolean;\n}\n\nexport function TiltCard({\n  maxTilt = 12,\n  scale = 1.03,\n  perspective = 900,\n  glare = true,\n  className,\n  style,\n  children,\n  onPointerEnter,\n  onPointerMove,\n  onPointerLeave,\n  ...props\n}: TiltCardProps) {\n  const ref = React.useRef<HTMLDivElement>(null);\n\n  // Pointer position is written straight to CSS variables, so moving the\n  // pointer never re-renders the card.\n  const handlePointerEnter = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerEnter?.(event);\n    ref.current?.style.setProperty(\"--tilt-scale\", String(scale));\n    ref.current?.style.setProperty(\"--glare-opacity\", \"1\");\n  };\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(event);\n    const el = ref.current;\n    if (!el || window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n      return;\n    }\n\n    const rect = el.getBoundingClientRect();\n    const x = (event.clientX - rect.left) / rect.width;\n    const y = (event.clientY - rect.top) / rect.height;\n\n    el.style.setProperty(\"--tilt-x\", `${(0.5 - y) * maxTilt * 2}deg`);\n    el.style.setProperty(\"--tilt-y\", `${(x - 0.5) * maxTilt * 2}deg`);\n    el.style.setProperty(\"--glare-x\", `${x * 100}%`);\n    el.style.setProperty(\"--glare-y\", `${y * 100}%`);\n  };\n\n  const handlePointerLeave = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerLeave?.(event);\n    const el = ref.current;\n    if (!el) return;\n\n    el.style.setProperty(\"--tilt-x\", \"0deg\");\n    el.style.setProperty(\"--tilt-y\", \"0deg\");\n    el.style.setProperty(\"--tilt-scale\", \"1\");\n    el.style.setProperty(\"--glare-opacity\", \"0\");\n  };\n\n  return (\n    <div\n      ref={ref}\n      className={cn(\n        \"relative rounded-2xl transition-transform duration-200 ease-out will-change-transform\",\n        className,\n      )}\n      style={{\n        transform: `perspective(${perspective}px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg)) scale(var(--tilt-scale, 1))`,\n        ...style,\n      }}\n      onPointerEnter={handlePointerEnter}\n      onPointerMove={handlePointerMove}\n      onPointerLeave={handlePointerLeave}\n      {...props}\n    >\n      {children}\n      {glare ? (\n        <div\n          aria-hidden\n          className=\"pointer-events-none absolute inset-0 rounded-[inherit] mix-blend-soft-light transition-opacity duration-200\"\n          style={{\n            opacity: \"var(--glare-opacity, 0)\",\n            background:\n              \"radial-gradient(circle at var(--glare-x, 50%) var(--glare-y, 50%), rgba(255, 255, 255, 0.55), transparent 55%)\",\n          }}\n        />\n      ) : null}\n    </div>\n  );\n}\n"
    }
  ]
}