{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "3d-card",
  "title": "3D Card",
  "description": "Perspective card that turns toward the pointer while its layers lift to different depths.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/3d-card.tsx",
      "content": "\"use client\";\n\nimport { createContext, useContext, useRef } from \"react\";\nimport {\n  motion,\n  useMotionValue,\n  useMotionValueEvent,\n  useReducedMotion,\n  useSpring,\n  type MotionValue,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\n// 0 = flat, 1 = fully lifted. Every Card3DItem scales its own depth by it.\nconst LiftContext = createContext<MotionValue<number> | null>(null);\n\nconst SPRING = { stiffness: 180, damping: 20, mass: 0.6 };\nconst FOCUS_LIFT = 0.4;\n\ninterface Card3DProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Maximum rotation toward the pointer, in degrees */\n  maxTilt?: number;\n  /** CSS perspective distance in px (smaller = stronger 3D) */\n  perspective?: number;\n  /** Classes for the rotating surface (avoid overflow-hidden: it flattens the layers) */\n  cardClassName?: string;\n  /** Card content; wrap layers that should lift in Card3DItem */\n  children: React.ReactNode;\n}\n\n/**\n * Perspective card that rotates toward the pointer. Wrap parts of its\n * content in `Card3DItem` with a `depth` (px) and they lift off the surface\n * at different heights while the pointer is over the card. Keyboard focus\n * inside the card lifts the items to a gentle resting depth without tilting.\n */\nexport function Card3D({\n  maxTilt = 10,\n  perspective = 1000,\n  cardClassName,\n  className,\n  style,\n  children,\n  ...props\n}: Card3DProps) {\n  const reducedMotion = useReducedMotion();\n  const rotateX = useMotionValue(0);\n  const rotateY = useMotionValue(0);\n  const liftTarget = useMotionValue(0);\n  const lift = useSpring(liftTarget, SPRING);\n  const springX = useSpring(rotateX, SPRING);\n  const springY = useSpring(rotateY, SPRING);\n  const state = useRef({ hover: false, focus: false });\n\n  const settle = () => {\n    const { hover, focus } = state.current;\n    liftTarget.set(reducedMotion ? 0 : hover ? 1 : focus ? FOCUS_LIFT : 0);\n  };\n\n  return (\n    <div\n      {...props}\n      data-slot=\"3d-card\"\n      className={cn(\"group/card3d\", className)}\n      style={{ perspective, ...style }}\n      onPointerMove={(e) => {\n        props.onPointerMove?.(e);\n        if (reducedMotion) return;\n        const rect = e.currentTarget.getBoundingClientRect();\n        const x = (e.clientX - rect.left) / rect.width - 0.5;\n        const y = (e.clientY - rect.top) / rect.height - 0.5;\n        rotateX.set(-y * maxTilt * 2);\n        rotateY.set(x * maxTilt * 2);\n        state.current.hover = true;\n        settle();\n      }}\n      onPointerLeave={(e) => {\n        props.onPointerLeave?.(e);\n        rotateX.set(0);\n        rotateY.set(0);\n        state.current.hover = false;\n        settle();\n      }}\n      onFocus={(e) => {\n        props.onFocus?.(e);\n        state.current.focus = e.target.matches(\":focus-visible\");\n        settle();\n      }}\n      onBlur={(e) => {\n        props.onBlur?.(e);\n        if (e.currentTarget.contains(e.relatedTarget)) return;\n        state.current.focus = false;\n        settle();\n      }}\n    >\n      <motion.div\n        style={{ rotateX: springX, rotateY: springY }}\n        className={cn(\n          \"relative transform-3d [&_*]:transform-3d\",\n          cardClassName\n        )}\n      >\n        <LiftContext.Provider value={lift}>{children}</LiftContext.Provider>\n      </motion.div>\n    </div>\n  );\n}\n\ninterface Card3DItemProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** How far the item lifts toward the viewer, in px (translateZ) */\n  depth?: number;\n  /** Extra rotation around the X axis at full lift, in degrees */\n  rotateX?: number;\n  /** Extra rotation around the Y axis at full lift, in degrees */\n  rotateY?: number;\n  /** Extra rotation around the Z axis at full lift, in degrees */\n  rotateZ?: number;\n}\n\n/**\n * A layer inside `Card3D` that floats `depth` px above the card surface.\n */\nexport function Card3DItem({\n  depth = 40,\n  rotateX = 0,\n  rotateY = 0,\n  rotateZ = 0,\n  className,\n  children,\n  ...props\n}: Card3DItemProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const fallback = useMotionValue(0);\n  const lift = useContext(LiftContext) ?? fallback;\n\n  useMotionValueEvent(lift, \"change\", (v) => {\n    if (!ref.current) return;\n    ref.current.style.transform =\n      v < 0.001\n        ? \"\"\n        : `translateZ(${v * depth}px) rotateX(${v * rotateX}deg) rotateY(${v * rotateY}deg) rotateZ(${v * rotateZ}deg)`;\n  });\n\n  return (\n    <div\n      {...props}\n      ref={ref}\n      data-slot=\"3d-card-item\"\n      className={className}\n    >\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/3d-card.tsx"
    }
  ],
  "type": "registry:ui"
}