{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tilt-card",
  "title": "Tilt Card",
  "description": "Card that tilts in 3D toward the pointer for a parallax hover.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/tilt-card/tilt-card.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/** Props for {@link TiltCard}. */\nexport type TiltCardProps = React.ComponentPropsWithoutRef<\"div\"> & {\n  /** Peak rotation in degrees applied at the card edges. Defaults to `12`. */\n  maxTilt?: number;\n};\n\nfunction usePrefersReducedMotion(): boolean {\n  const [reduced, setReduced] = React.useState(false);\n\n  React.useEffect(() => {\n    if (\n      typeof window === \"undefined\" ||\n      typeof window.matchMedia !== \"function\"\n    ) {\n      return;\n    }\n\n    const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const onChange = (): void => {\n      setReduced(query.matches);\n    };\n\n    onChange();\n    query.addEventListener(\"change\", onChange);\n\n    return () => {\n      query.removeEventListener(\"change\", onChange);\n    };\n  }, []);\n\n  return reduced;\n}\n\nfunction clamp(value: number, max: number): number {\n  return Math.min(Math.max(value, -max), max);\n}\n\n/**\n * Card that tilts in 3D toward the pointer for a parallax hover effect.\n *\n * Respects `prefers-reduced-motion`: the card stays flat.\n *\n * @example\n * ```tsx\n * <TiltCard className=\"rounded-xl border bg-card p-6\">Hover me</TiltCard>\n * ```\n */\nexport const TiltCard = ({\n  children,\n  className,\n  maxTilt = 12,\n  ref,\n  style,\n  ...props\n}: TiltCardProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const reduced = usePrefersReducedMotion();\n  const [transform, setTransform] = React.useState<string>();\n\n  const handlePointerMove = (\n    event: React.PointerEvent<HTMLDivElement>,\n  ): void => {\n    if (reduced) {\n      return;\n    }\n\n    const bounds = event.currentTarget.getBoundingClientRect();\n    const offsetX = (event.clientX - bounds.left) / bounds.width - 0.5;\n    const offsetY = (event.clientY - bounds.top) / bounds.height - 0.5;\n    const rotateY = clamp(offsetX * maxTilt * 2, maxTilt);\n    const rotateX = clamp(-offsetY * maxTilt * 2, maxTilt);\n\n    setTransform(\n      `perspective(800px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,\n    );\n  };\n\n  return (\n    <div\n      className={cn(\n        \"transition-transform duration-200 ease-out will-change-transform\",\n        className,\n      )}\n      onPointerLeave={() => {\n        setTransform(undefined);\n      }}\n      onPointerMove={handlePointerMove}\n      ref={ref}\n      style={{ transform, ...style }}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n};\nTiltCard.displayName = \"TiltCard\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.2.1",
  "stability": "stable"
}
