{
  "name": "sliding-cards",
  "type": "registry:ui",
  "title": "Sliding Cards",
  "description": "Interactive 3D gesture-driven card stack with swipeable physics, rotational tilt, and depth layering.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/cards/sliding-cards/sliding-cards.tsx",
      "type": "registry:ui",
      "target": "components/ui/sliding-cards.tsx",
      "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Layers, Shield, Cpu, Compass } from \"lucide-react\";\n\nexport type CardContent = {\n  id: string | number;\n  title: string;\n  description: string;\n  tag?: string;\n  icon?: React.ReactNode;\n  bgClass?: string;\n};\n\nexport interface SlidingCardsProps {\n  cards?: CardContent[];\n  className?: string;\n  onCardClick?: (index: number) => void;\n}\n\nconst defaultCards: CardContent[] = [\n  {\n    id: 1,\n    title: \"Quantum Layering\",\n    description: \"Multithreaded state propagation across neural compute trees.\",\n    tag: \"Core Engine\",\n    icon: <Cpu className=\"w-5 h-5 text-indigo-500\" />,\n    bgClass: \"from-indigo-500/10 via-purple-500/5 to-transparent\",\n  },\n  {\n    id: 2,\n    title: \"Zero-Knowledge Auth\",\n    description: \"Hardware cryptographic enclave verification tokens.\",\n    tag: \"Security\",\n    icon: <Shield className=\"w-5 h-5 text-emerald-500\" />,\n    bgClass: \"from-emerald-500/10 via-teal-500/5 to-transparent\",\n  },\n  {\n    id: 3,\n    title: \"Global Spatial Mesh\",\n    description: \"Sub-millisecond real-time distributed edge consensus.\",\n    tag: \"Network\",\n    icon: <Compass className=\"w-5 h-5 text-amber-500\" />,\n    bgClass: \"from-amber-500/10 via-orange-500/5 to-transparent\",\n  },\n  {\n    id: 4,\n    title: \"Polymorphic Layouts\",\n    description: \"Dynamic fluid typography and viewport physics binding.\",\n    tag: \"UI Core\",\n    icon: <Layers className=\"w-5 h-5 text-pink-500\" />,\n    bgClass: \"from-pink-500/10 via-rose-500/5 to-transparent\",\n  },\n];\n\nexport const SlidingCards: React.FC<SlidingCardsProps> = ({\n  cards = defaultCards,\n  className,\n  onCardClick,\n}) => {\n  const cardStackRef = useRef<HTMLDivElement>(null);\n  const cardsRef = useRef<HTMLElement[]>([]);\n  const [deck, setDeck] = useState(cards);\n\n  useEffect(() => {\n    const cardStack = cardStackRef.current;\n    if (!cardStack) return;\n    cardsRef.current = Array.from(cardStack.querySelectorAll(\".sliding-card-item\"));\n\n    let isSwiping = false;\n    let startX = 0;\n    let currentX = 0;\n    let animationFrameId: number | null = null;\n\n    const duration = 280;\n    const getActiveCard = () => cardsRef.current[0];\n\n    const updatePositions = () => {\n      cardsRef.current.forEach((card, i) => {\n        const offset = i;\n        card.style.zIndex = `${50 - offset}`;\n        card.style.transform = `perspective(800px) translateZ(${-14 * offset}px) translateY(${8 * offset}px) translateX(0px) rotateY(0deg)`;\n        card.style.opacity = `${1 - offset * 0.15}`;\n      });\n    };\n\n    const applySwipeStyles = (deltaX: number) => {\n      const card = getActiveCard();\n      if (!card) return;\n      const rotate = deltaX * 0.12;\n      const opacity = 1 - Math.min(Math.abs(deltaX) / 140, 1) * 0.5;\n      card.style.transform = `perspective(800px) translateZ(0px) translateY(0px) translateX(${deltaX}px) rotateY(${rotate}deg)`;\n      card.style.opacity = `${opacity}`;\n    };\n\n    const handleStart = (clientX: number) => {\n      if (isSwiping) return;\n      isSwiping = true;\n      startX = currentX = clientX;\n      const card = getActiveCard();\n      if (card) card.style.transition = \"none\";\n    };\n\n    const handleMove = (clientX: number) => {\n      if (!isSwiping) return;\n      if (animationFrameId) cancelAnimationFrame(animationFrameId);\n      animationFrameId = requestAnimationFrame(() => {\n        currentX = clientX;\n        const deltaX = currentX - startX;\n        applySwipeStyles(deltaX);\n        if (Math.abs(deltaX) > 70) handleEnd();\n      });\n    };\n\n    const handleEnd = () => {\n      if (!isSwiping) return;\n      if (animationFrameId) cancelAnimationFrame(animationFrameId);\n\n      const deltaX = currentX - startX;\n      const threshold = 40;\n      const card = getActiveCard();\n\n      if (card) {\n        card.style.transition = `transform ${duration}ms cubic-bezier(0.16, 1, 0.3, 1), opacity ${duration}ms ease`;\n\n        if (Math.abs(deltaX) > threshold) {\n          const direction = Math.sign(deltaX);\n          card.style.transform = `perspective(800px) translateZ(0px) translateY(0px) translateX(${direction * 240}px) rotateY(${direction * 15}deg)`;\n          card.style.opacity = \"0\";\n\n          setTimeout(() => {\n            cardsRef.current = [...cardsRef.current.slice(1), card];\n            updatePositions();\n          }, duration);\n        } else {\n          applySwipeStyles(0);\n          updatePositions();\n        }\n      }\n\n      isSwiping = false;\n      startX = currentX = 0;\n    };\n\n    const onPointerDown = (e: PointerEvent) => handleStart(e.clientX);\n    const onPointerMove = (e: PointerEvent) => handleMove(e.clientX);\n    const onPointerUp = () => handleEnd();\n\n    cardStack.addEventListener(\"pointerdown\", onPointerDown);\n    window.addEventListener(\"pointermove\", onPointerMove);\n    window.addEventListener(\"pointerup\", onPointerUp);\n\n    updatePositions();\n\n    return () => {\n      cardStack.removeEventListener(\"pointerdown\", onPointerDown);\n      window.removeEventListener(\"pointermove\", onPointerMove);\n      window.removeEventListener(\"pointerup\", onPointerUp);\n      if (animationFrameId) cancelAnimationFrame(animationFrameId);\n    };\n  }, [deck]);\n\n  return (\n    <div\n      ref={cardStackRef}\n      className={cn(\n        \"relative w-64 h-64 grid place-content-center touch-none select-none isolate cursor-grab active:cursor-grabbing\",\n        className\n      )}\n    >\n      {deck.map((card, index) => (\n        <article\n          key={card.id}\n          onClick={() => onCardClick?.(index)}\n          className={cn(\n            \"sliding-card-item absolute inset-2 p-5 flex flex-col justify-between rounded-2xl border border-zinc-200/80 dark:border-zinc-800/80 bg-white dark:bg-zinc-900 shadow-xl backdrop-blur-md bg-gradient-to-br transition-all\",\n            card.bgClass\n          )}\n        >\n          <div className=\"flex items-center justify-between\">\n            <span className=\"p-2 rounded-xl bg-zinc-100 dark:bg-zinc-800 border border-zinc-200/60 dark:border-zinc-700/60\">\n              {card.icon}\n            </span>\n            {card.tag && (\n              <span className=\"text-[10px] font-mono uppercase tracking-wider px-2 py-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 border border-zinc-200/60 dark:border-zinc-700/60\">\n                {card.tag}\n              </span>\n            )}\n          </div>\n\n          <div className=\"flex flex-col gap-1 mt-3\">\n            <h4 className=\"text-sm font-semibold text-zinc-900 dark:text-zinc-100 tracking-tight\">\n              {card.title}\n            </h4>\n            <p className=\"text-xs text-zinc-500 dark:text-zinc-400 line-clamp-2 leading-relaxed\">\n              {card.description}\n            </p>\n          </div>\n\n          <div className=\"flex items-center justify-between pt-2 border-t border-zinc-100 dark:border-zinc-800/80 text-[10px] font-mono text-zinc-400\">\n            <span>Swipe left or right</span>\n            <span>0{index + 1}/0{deck.length}</span>\n          </div>\n        </article>\n      ))}\n    </div>\n  );\n};\n\nexport default SlidingCards;\n"
    }
  ],
  "$schema": "https://www.cooki-ui.design/schema/registry-item.json"
}