{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "BounceCards-TS-CSS", "type": "registry:block", "title": "BounceCards", "description": "Cards bounce that bounce in on mount.", "dependencies": [ "gsap" ], "files": [ { "path": "public/ts/default/src/ts-default/Components/BounceCards/BounceCards.tsx", "content": "import { useEffect } from 'react';\r\nimport { gsap } from 'gsap';\r\nimport './BounceCards.css';\r\n\r\ninterface BounceCardsProps {\r\n className?: string;\r\n images?: string[];\r\n containerWidth?: number;\r\n containerHeight?: number;\r\n animationDelay?: number;\r\n animationStagger?: number;\r\n easeType?: string;\r\n transformStyles?: string[];\r\n enableHover?: boolean;\r\n}\r\n\r\nexport default function BounceCards({\r\n className = '',\r\n images = [],\r\n containerWidth = 400,\r\n containerHeight = 400,\r\n animationDelay = 0.5,\r\n animationStagger = 0.06,\r\n easeType = 'elastic.out(1, 0.8)',\r\n transformStyles = [\r\n 'rotate(10deg) translate(-170px)',\r\n 'rotate(5deg) translate(-85px)',\r\n 'rotate(-3deg)',\r\n 'rotate(-10deg) translate(85px)',\r\n 'rotate(2deg) translate(170px)'\r\n ],\r\n enableHover = false\r\n}: BounceCardsProps) {\r\n useEffect(() => {\r\n gsap.fromTo(\r\n '.card',\r\n { scale: 0 },\r\n {\r\n scale: 1,\r\n stagger: animationStagger,\r\n ease: easeType,\r\n delay: animationDelay\r\n }\r\n );\r\n }, [animationStagger, easeType, animationDelay]);\r\n\r\n const getNoRotationTransform = (transformStr: string): string => {\r\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\r\n if (hasRotate) {\r\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\r\n } else if (transformStr === 'none') {\r\n return 'rotate(0deg)';\r\n } else {\r\n return `${transformStr} rotate(0deg)`;\r\n }\r\n };\r\n\r\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\r\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\r\n const match = baseTransform.match(translateRegex);\r\n if (match) {\r\n const currentX = parseFloat(match[1]);\r\n const newX = currentX + offsetX;\r\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\r\n } else {\r\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\r\n }\r\n };\r\n\r\n const pushSiblings = (hoveredIdx: number) => {\r\n if (!enableHover) return;\r\n\r\n images.forEach((_, i) => {\r\n gsap.killTweensOf(`.card-${i}`);\r\n\r\n const baseTransform = transformStyles[i] || 'none';\r\n\r\n if (i === hoveredIdx) {\r\n const noRotation = getNoRotationTransform(baseTransform);\r\n gsap.to(`.card-${i}`, {\r\n transform: noRotation,\r\n duration: 0.4,\r\n ease: 'back.out(1.4)',\r\n overwrite: 'auto'\r\n });\r\n } else {\r\n const offsetX = i < hoveredIdx ? -160 : 160;\r\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\r\n\r\n const distance = Math.abs(hoveredIdx - i);\r\n const delay = distance * 0.05;\r\n\r\n gsap.to(`.card-${i}`, {\r\n transform: pushedTransform,\r\n duration: 0.4,\r\n ease: 'back.out(1.4)',\r\n delay,\r\n overwrite: 'auto'\r\n });\r\n }\r\n });\r\n };\r\n\r\n const resetSiblings = () => {\r\n if (!enableHover) return;\r\n\r\n images.forEach((_, i) => {\r\n gsap.killTweensOf(`.card-${i}`);\r\n const baseTransform = transformStyles[i] || 'none';\r\n gsap.to(`.card-${i}`, {\r\n transform: baseTransform,\r\n duration: 0.4,\r\n ease: 'back.out(1.4)',\r\n overwrite: 'auto'\r\n });\r\n });\r\n };\r\n\r\n return (\r\n \r\n {images.map((src, idx) => (\r\n pushSiblings(idx)}\r\n onMouseLeave={resetSiblings}\r\n >\r\n {`card-${idx}`}\r\n \r\n ))}\r\n \r\n );\r\n}\r\n", "type": "registry:component" }, { "path": "public/ts/default/src/ts-default/Components/BounceCards/BounceCards.css", "content": ".bounceCardsContainer {\r\n position: relative;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n width: 400px;\r\n height: 400px;\r\n}\r\n\r\n.card {\r\n position: absolute;\r\n width: 200px;\r\n aspect-ratio: 1;\r\n border: 5px solid #fff;\r\n border-radius: 25px;\r\n overflow: hidden;\r\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\r\n}\r\n\r\n.card .image {\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n}\r\n", "type": "registry:item" } ] }