{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "CardSwap-JS-TW",
	"title": "CardSwap",
	"description": "Cards animate position swapping with smooth layout transitions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "CardSwap/CardSwap.jsx",
			"content": "import React, { Children, cloneElement, forwardRef, isValidElement, useEffect, useMemo, useRef } from 'react';\nimport gsap from 'gsap';\n\nexport const Card = forwardRef(({ customClass, ...rest }, ref) => (\n  <div\n    ref={ref}\n    {...rest}\n    className={`absolute top-1/2 left-1/2 rounded-xl border border-white bg-black [transform-style:preserve-3d] [will-change:transform] [backface-visibility:hidden] ${customClass ?? ''} ${rest.className ?? ''}`.trim()}\n  />\n));\nCard.displayName = 'Card';\n\nconst makeSlot = (i, distX, distY, total) => ({\n  x: i * distX,\n  y: -i * distY,\n  z: -i * distX * 1.5,\n  zIndex: total - i\n});\n\nconst placeNow = (el, slot, skew) =>\n  gsap.set(el, {\n    x: slot.x,\n    y: slot.y,\n    z: slot.z,\n    xPercent: -50,\n    yPercent: -50,\n    skewY: skew,\n    transformOrigin: 'center center',\n    zIndex: slot.zIndex,\n    force3D: true\n  });\n\nconst CardSwap = ({\n  width = 500,\n  height = 400,\n  cardDistance = 60,\n  verticalDistance = 70,\n  delay = 5000,\n  pauseOnHover = false,\n  onCardClick,\n  skewAmount = 6,\n  easing = 'elastic',\n  children\n}) => {\n  const config =\n    easing === 'elastic'\n      ? {\n          ease: 'elastic.out(0.6,0.9)',\n          durDrop: 2,\n          durMove: 2,\n          durReturn: 2,\n          promoteOverlap: 0.9,\n          returnDelay: 0.05\n        }\n      : {\n          ease: 'power1.inOut',\n          durDrop: 0.8,\n          durMove: 0.8,\n          durReturn: 0.8,\n          promoteOverlap: 0.45,\n          returnDelay: 0.2\n        };\n\n  const childArr = useMemo(() => Children.toArray(children), [children]);\n  const refs = useMemo(\n    () => childArr.map(() => React.createRef()),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [childArr.length]\n  );\n\n  const order = useRef(Array.from({ length: childArr.length }, (_, i) => i));\n\n  const tlRef = useRef(null);\n  const intervalRef = useRef();\n  const container = useRef(null);\n\n  useEffect(() => {\n    const total = refs.length;\n    refs.forEach((r, i) => placeNow(r.current, makeSlot(i, cardDistance, verticalDistance, total), skewAmount));\n\n    const swap = () => {\n      if (order.current.length < 2) return;\n\n      const [front, ...rest] = order.current;\n      const elFront = refs[front].current;\n      const tl = gsap.timeline();\n      tlRef.current = tl;\n\n      tl.to(elFront, {\n        y: '+=500',\n        duration: config.durDrop,\n        ease: config.ease\n      });\n\n      tl.addLabel('promote', `-=${config.durDrop * config.promoteOverlap}`);\n      rest.forEach((idx, i) => {\n        const el = refs[idx].current;\n        const slot = makeSlot(i, cardDistance, verticalDistance, refs.length);\n        tl.set(el, { zIndex: slot.zIndex }, 'promote');\n        tl.to(\n          el,\n          {\n            x: slot.x,\n            y: slot.y,\n            z: slot.z,\n            duration: config.durMove,\n            ease: config.ease\n          },\n          `promote+=${i * 0.15}`\n        );\n      });\n\n      const backSlot = makeSlot(refs.length - 1, cardDistance, verticalDistance, refs.length);\n      tl.addLabel('return', `promote+=${config.durMove * config.returnDelay}`);\n      tl.call(\n        () => {\n          gsap.set(elFront, { zIndex: backSlot.zIndex });\n        },\n        undefined,\n        'return'\n      );\n      tl.to(\n        elFront,\n        {\n          x: backSlot.x,\n          y: backSlot.y,\n          z: backSlot.z,\n          duration: config.durReturn,\n          ease: config.ease\n        },\n        'return'\n      );\n\n      tl.call(() => {\n        order.current = [...rest, front];\n      });\n    };\n\n    swap();\n    intervalRef.current = window.setInterval(swap, delay);\n\n    if (pauseOnHover) {\n      const node = container.current;\n      const pause = () => {\n        tlRef.current?.pause();\n        clearInterval(intervalRef.current);\n      };\n      const resume = () => {\n        tlRef.current?.play();\n        intervalRef.current = window.setInterval(swap, delay);\n      };\n      node.addEventListener('mouseenter', pause);\n      node.addEventListener('mouseleave', resume);\n      return () => {\n        node.removeEventListener('mouseenter', pause);\n        node.removeEventListener('mouseleave', resume);\n        clearInterval(intervalRef.current);\n      };\n    }\n    return () => clearInterval(intervalRef.current);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [cardDistance, verticalDistance, delay, pauseOnHover, skewAmount, easing]);\n\n  const rendered = childArr.map((child, i) =>\n    isValidElement(child)\n      ? cloneElement(child, {\n          key: i,\n          ref: refs[i],\n          style: { width, height, ...(child.props.style ?? {}) },\n          onClick: e => {\n            child.props.onClick?.(e);\n            onCardClick?.(i);\n          }\n        })\n      : child\n  );\n\n  return (\n    <div\n      ref={container}\n      className=\"absolute bottom-0 right-0 transform translate-x-[5%] translate-y-[20%] origin-bottom-right perspective-[900px] overflow-visible max-[768px]:translate-x-[25%] max-[768px]:translate-y-[25%] max-[768px]:scale-[0.75] max-[480px]:translate-x-[25%] max-[480px]:translate-y-[25%] max-[480px]:scale-[0.55]\"\n      style={{ width, height }}\n    >\n      {rendered}\n    </div>\n  );\n};\n\nexport default CardSwap;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}