{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cover-flow-carousel",
  "description": "A classic cover-flow carousel: the centre cover faces you while the side covers angle inward with depth and a floor reflection. Drag to scrub with snap, click a side cover to bring it front, or let it auto-advance.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/new-york/carousel/cover-flow-carousel.tsx",
      "content": "\"use client\";\n\n/**\n * A classic cover-flow carousel: the centre cover faces you while the side covers angle inward with depth and a floor reflection. Drag to scrub with snap, click a side cover to bring it front, or let it auto-advance.\n */\n\nimport { useEffect, useRef } from \"react\";\n\nconst COVERS = [\n  { seed: \"flow-01\", title: \"Midnight Drive\" },\n  { seed: \"flow-02\", title: \"Glasshouse\" },\n  { seed: \"flow-03\", title: \"Low Tide\" },\n  { seed: \"flow-04\", title: \"Neon Fields\" },\n  { seed: \"flow-05\", title: \"Palerose\" },\n  { seed: \"flow-06\", title: \"Static Bloom\" },\n  { seed: \"flow-07\", title: \"Vantablack\" },\n  { seed: \"flow-08\", title: \"Golden Hour\" },\n  { seed: \"flow-09\", title: \"Undertow\" },\n  { seed: \"flow-10\", title: \"Northern Line\" },\n];\n\nconst C = {\n  persp: 1100, // CSS perspective distance (px)\n  cw: 250, // cover width (px)\n  ch: 250, // cover height (px)\n  gap: 118, // → right per step\n  push: 105, // extra shove away from the centre cover\n  rot: 55, // side-cover angle about Y (deg)\n  depth: 150, // how far the centre cover pops forward (px)\n  fall: 60, // how far each further step recedes (px)\n};\n\nconst CoverFlowCarousel = () => {\n  const cardRefs = useRef<(HTMLDivElement | null)[]>([]);\n  const pos = useRef(0);\n  const target = useRef(0);\n  const moved = useRef(0);\n\n  useEffect(() => {\n    const N = COVERS.length;\n\n    let dragging = false;\n    let lastX = 0;\n    let idle = 0;\n\n    const wrap = (p: number) => {\n      p = ((p % N) + N) % N;\n      return p > N / 2 ? p - N : p;\n    };\n\n    const layout = () => {\n      for (let i = 0; i < N; i++) {\n        const el = cardRefs.current[i];\n        if (!el) continue;\n        const p = wrap(i - pos.current);\n        const side = Math.max(-1, Math.min(1, p));\n        const x = p * C.gap + side * C.push;\n        const z =\n          (1 - Math.min(1, Math.abs(p))) * C.depth - Math.abs(p) * C.fall;\n        el.style.transform = `translate3d(${x}px, 0px, ${z}px) rotateY(${-side * C.rot}deg)`;\n        el.style.zIndex = String(200 - Math.round(Math.abs(p) * 10));\n        el.style.opacity = String(Math.abs(p) > 3.6 ? 0 : 1);\n      }\n    };\n\n    let raf = 0;\n    const tick = () => {\n      raf = requestAnimationFrame(tick);\n      if (!dragging && ++idle > 210) {\n        // ~3.5s of stillness → advance one cover\n        target.current = Math.round(target.current) + 1;\n        idle = 0;\n      }\n      pos.current += (target.current - pos.current) * 0.09;\n      layout();\n    };\n    tick();\n\n    const onDown = (e: PointerEvent) => {\n      dragging = true;\n      moved.current = 0;\n      lastX = e.clientX;\n      (e.target as HTMLElement).setPointerCapture?.(e.pointerId);\n    };\n    const onMove = (e: PointerEvent) => {\n      if (!dragging) return;\n      const dx = e.clientX - lastX;\n      lastX = e.clientX;\n      moved.current += Math.abs(dx);\n      target.current -= dx * 0.006;\n      idle = 0;\n    };\n    const onUp = () => {\n      if (!dragging) return;\n      dragging = false;\n      target.current = Math.round(target.current); // snap to a cover\n      idle = 0;\n    };\n\n    const scene = cardRefs.current[0]?.closest(\"[data-coverflow]\");\n    scene?.addEventListener(\"pointerdown\", onDown as EventListener);\n    window.addEventListener(\"pointermove\", onMove);\n    window.addEventListener(\"pointerup\", onUp);\n\n    return () => {\n      cancelAnimationFrame(raf);\n      scene?.removeEventListener(\"pointerdown\", onDown as EventListener);\n      window.removeEventListener(\"pointermove\", onMove);\n      window.removeEventListener(\"pointerup\", onUp);\n    };\n  }, []);\n\n  const bringToFront = (i: number) => {\n    if (moved.current > 6) return; // it was a drag, not a click\n    const N = COVERS.length;\n    let p = ((i - pos.current) % N) + (i - pos.current < 0 ? N : 0);\n    if (p > N / 2) p -= N;\n    target.current = Math.round(pos.current + p);\n  };\n\n  return (\n    <div\n      data-coverflow\n      className=\"relative flex h-[80vh] w-full cursor-grab select-none items-center justify-center overflow-hidden active:cursor-grabbing\"\n      style={{ perspective: `${C.persp}px` }}\n    >\n      <div\n        className=\"relative -mt-10\"\n        style={{ transformStyle: \"preserve-3d\" }}\n      >\n        {COVERS.map((cover, i) => (\n          <div\n            key={cover.seed}\n            ref={(el) => {\n              cardRefs.current[i] = el;\n            }}\n            onClick={() => bringToFront(i)}\n            className=\"absolute\"\n            style={{\n              width: C.cw,\n              height: C.ch * 1.55,\n              marginLeft: -C.cw / 2,\n              marginTop: -C.ch / 2,\n              willChange: \"transform, opacity\",\n            }}\n          >\n            <div className=\"overflow-hidden rounded-md shadow-[0_25px_50px_-12px_rgba(0,0,0,0.45)] ring-1 ring-white/20\">\n              {/* eslint-disable-next-line @next/next/no-img-element */}\n              <img\n                src={`https://picsum.photos/seed/${cover.seed}/500/500`}\n                alt={cover.title}\n                draggable={false}\n                style={{ width: C.cw, height: C.ch }}\n                className=\"object-cover\"\n              />\n            </div>\n            {/* floor reflection — a flipped copy fading out under the cover */}\n            <div\n              className=\"mt-1 overflow-hidden rounded-md opacity-40\"\n              style={{\n                transform: \"scaleY(-1)\",\n                maskImage:\n                  \"linear-gradient(to top, rgba(0,0,0,0.5), transparent 55%)\",\n                WebkitMaskImage:\n                  \"linear-gradient(to top, rgba(0,0,0,0.5), transparent 55%)\",\n              }}\n            >\n              {/* eslint-disable-next-line @next/next/no-img-element */}\n              <img\n                src={`https://picsum.photos/seed/${cover.seed}/500/500`}\n                alt=\"\"\n                aria-hidden\n                draggable={false}\n                style={{ width: C.cw, height: C.ch }}\n                className=\"object-cover\"\n              />\n            </div>\n            <p className=\"pointer-events-none absolute -bottom-2 left-0 right-0 text-center text-[10px] font-medium uppercase tracking-widest text-muted-foreground\">\n              {cover.title}\n            </p>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default CoverFlowCarousel;\n",
      "type": "registry:component",
      "target": "components/pixel-perfect/cover-flow-carousel.tsx"
    }
  ],
  "type": "registry:block"
}