{
    "name": "animated-stack",
    "title": "Animated Stack",
    "description": "A smooth stacked card component that expands on hover and collapses with animation.",
    "type": "registry:ui",
    "dependencies": ["gsap", "@gsap/react"],
    "files": [
        {
            "path": "components/gsap/animated-stack.tsx",
            "type": "registry:ui",
            "target": "~/components/gsap/animated-stack.tsx",
            "content": "\"use client\";\n\nimport { Children, ReactNode, useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { gsap } from \"gsap\";\n\ntype AnimatedStackProps = {\n    children: ReactNode;\n    visibleCount?: number;\n    gap?: number;\n    offset?: number;\n    direction?: \"up\" | \"down\";\n};\n\nexport const AnimatedStack = ({\n    children,\n    visibleCount = 3,\n    gap = 8,\n    offset = 8,\n    direction = \"up\",\n}: AnimatedStackProps) => {\n    const containerRef = useRef<HTMLDivElement>(null);\n    const itemsRef = useRef<HTMLDivElement[]>([]);\n    const prevItemCount = useRef(0);\n    const timeoutRef = useRef<NodeJS.Timeout>(null);\n    const lastItemHeight = useRef(0);\n    const [expanded, setExpanded] = useState(false);\n\n    itemsRef.current = [];\n\n    const registerItemRef = (el: HTMLDivElement) => {\n        if (el && !itemsRef.current.includes(el)) itemsRef.current.push(el);\n    };\n\n    const calculatePositions = useCallback(\n        (arr: number[]) =>\n            arr\n                .reduce((acc, val) => [...acc, acc.at(-1)! + val + gap], [0])\n                .slice(0, -1)\n                .reverse(),\n        [gap],\n    );\n\n    useEffect(() => {\n        const items = itemsRef.current;\n        const total = items.length;\n\n        const heights = items.map((el) => el.getBoundingClientRect().height);\n        lastItemHeight.current = heights.at(-1) || 0;\n        const positions = calculatePositions(heights);\n\n        items.forEach((el, i) => {\n            const rev = total - 1 - i;\n            let y = 0;\n            let opacity = 1,\n                scale = 1;\n\n            if (expanded) {\n                y = (positions[i] || 0) * (direction == \"down\" ? 1 : -1);\n            } else {\n                if (rev >= visibleCount) {\n                    y = offset * (visibleCount - 1);\n                    opacity = 0;\n                } else if (i !== total - 1) {\n                    y = rev * offset * (direction == \"down\" ? 1 : -1);\n                    scale = 1 - (total - i) * 0.015;\n                }\n            }\n\n            const isNew = total > prevItemCount.current && i === total - 1;\n\n            if (isNew) {\n                gsap.fromTo(\n                    el,\n                    { opacity: 0, y: y + 20 * (direction == \"down\" ? -1 : 1) },\n                    { opacity: 1, y, duration: 0.8, ease: \"power2.out\" },\n                );\n            } else {\n                gsap.to(el, { y, opacity, scaleX: scale, duration: 0.8, ease: \"power4.inOut\" });\n            }\n        });\n        prevItemCount.current = total;\n    }, [children, expanded, visibleCount, direction, calculatePositions, gap, offset]);\n\n    return (\n        <div\n            ref={containerRef}\n            className=\"relative w-full overflow-visible\"\n            style={{ height: lastItemHeight.current }}\n            onMouseEnter={() => {\n                if (timeoutRef.current) clearTimeout(timeoutRef.current);\n                setExpanded(true);\n            }}\n            onMouseLeave={() => {\n                timeoutRef.current = setTimeout(() => setExpanded(false), 1500);\n            }}>\n            {Children.map(children, (child) => (\n                <div ref={registerItemRef} className=\"absolute w-full\">\n                    {child}\n                </div>\n            ))}\n        </div>\n    );\n};\n"
        }
    ]
}
