{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"lean-card","type":"registry:ui","title":"Lean Card","description":"A card that leans and drifts toward the pointer while its shadow slides away and the content counter-leans for depth.","dependencies":[],"registryDependencies":["utils"],"files":[{"path":"registry/ui/lean-card.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useRef, type ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface LeanCardProps {\n  children: ReactNode;\n  /** Max lean (skew) in degrees. */\n  lean?: number;\n  /** Px the card drifts toward the pointer. */\n  drift?: number;\n  /** 0 to 1: how much the content leans back the other way. */\n  counter?: number;\n  /** Px the shadow slides away from the pointer. */\n  shadowShift?: number;\n  /** Follow speed. Higher is snappier. */\n  stiffness?: number;\n  /** Soft light that follows the pointer. */\n  glare?: boolean;\n  className?: string;\n  /** Classes for the inner content layer. */\n  contentClassName?: string;\n}\n\nexport function LeanCard({\n  children,\n  lean = 7,\n  drift = 10,\n  counter = 0.6,\n  shadowShift = 22,\n  stiffness = 9,\n  glare = true,\n  className,\n  contentClassName,\n}: LeanCardProps) {\n  const root = useRef<HTMLDivElement>(null);\n  const card = useRef<HTMLDivElement>(null);\n  const inner = useRef<HTMLDivElement>(null);\n  const light = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const el = root.current!;\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n    const target = { x: 0, y: 0, h: 0 };\n    const cur = { x: 0, y: 0, h: 0 };\n    let raf = 0;\n    let prev = 0;\n\n    const apply = () => {\n      if (!card.current || !inner.current) return; // unmounted\n      const { x, y, h } = cur;\n      const a = -x * lean;\n      card.current!.style.transform = `translate3d(${x * drift}px, ${y * drift * 0.6}px, 0) skewX(${a}deg) rotateX(${-y * lean * 0.6}deg) scale(${1 + h * 0.02})`;\n      card.current!.style.boxShadow = `${-x * shadowShift}px ${18 - y * shadowShift * 0.6 + h * 10}px ${40 + h * 20}px -14px rgba(0,0,0,${0.35 + h * 0.2})`;\n      inner.current!.style.transform = `translate3d(${-x * drift * counter}px, ${-y * drift * counter * 0.6}px, 0) skewX(${-a * counter}deg)`;\n      if (light.current) {\n        light.current.style.opacity = String(h);\n        light.current.style.background = `radial-gradient(circle at ${50 + x * 50}% ${50 + y * 50}%, rgba(255,255,255,.14), transparent 60%)`;\n      }\n    };\n    const loop = (now: number) => {\n      const dt = Math.min(0.05, (now - (prev || now)) / 1000);\n      prev = now;\n      const k = 1 - Math.exp(-stiffness * dt);\n      let moving = false;\n      for (const key of [\"x\", \"y\", \"h\"] as const) {\n        cur[key] += (target[key] - cur[key]) * k;\n        if (Math.abs(target[key] - cur[key]) > 0.0005) moving = true;\n      }\n      apply();\n      raf = moving ? requestAnimationFrame(loop) : 0;\n      if (!moving) prev = 0;\n    };\n    const kick = () => {\n      if (!raf) raf = requestAnimationFrame(loop);\n    };\n    const move = (e: PointerEvent) => {\n      const r = el.getBoundingClientRect();\n      target.x = Math.max(-1, Math.min(1, ((e.clientX - r.left) / r.width) * 2 - 1));\n      target.y = Math.max(-1, Math.min(1, ((e.clientY - r.top) / r.height) * 2 - 1));\n      target.h = 1;\n      kick();\n    };\n    const leave = () => {\n      target.x = target.y = target.h = 0;\n      kick();\n    };\n    apply();\n    el.addEventListener(\"pointermove\", move);\n    el.addEventListener(\"pointerleave\", leave);\n    return () => {\n      cancelAnimationFrame(raf);\n      el.removeEventListener(\"pointermove\", move);\n      el.removeEventListener(\"pointerleave\", leave);\n    };\n  }, [lean, drift, counter, shadowShift, stiffness]);\n\n  return (\n    <div ref={root} className=\"p-4\" style={{ perspective: 900 }}>\n      <div\n        ref={card}\n        className={cn(\"relative overflow-hidden rounded-3xl border border-border bg-card\", className)}\n        style={{ transformOrigin: \"50% 100%\", willChange: \"transform\", boxShadow: \"0 18px 40px -14px rgba(0,0,0,.35)\" }}\n      >\n        <div ref={inner} className={cn(\"relative\", contentClassName)} style={{ willChange: \"transform\" }}>\n          {children}\n        </div>\n        {glare && <div ref={light} aria-hidden className=\"pointer-events-none absolute inset-0 opacity-0\" />}\n      </div>\n    </div>\n  );\n}\n"}]}