{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radial-carousel",
  "type": "registry:component",
  "title": "Radial Carousel",
  "description": "A stunning radial carousel component with spring animations, drag-to-rotate interaction, and adaptive layout.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "components/watermelon/radial-carousel.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport React, { useState, useCallback, useEffect } from 'react';\nimport {\n  motion,\n  AnimatePresence,\n  type Variants,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from 'motion/react';\nimport { X } from 'lucide-react';\n\nexport interface GalleryItem {\n  id: string | number;\n  url: string;\n  title?: string;\n}\n\nexport interface RadialCarouselProps {\n  items: GalleryItem[];\n  radius?: number;\n  thumbnailSize?: number;\n  centerSize?: number;\n}\n\nexport const RadialCarousel: React.FC<RadialCarouselProps> = ({\n  items,\n  radius = 260,\n  thumbnailSize = 110,\n  centerSize = 400,\n}) => {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [activeIndex, setActiveIndex] = useState(0);\n\n  const [isPanning, setIsPanning] = useState(false);\n  const [responsiveSizes, setResponsiveSizes] = useState({\n    radius,\n    thumbnailSize,\n    centerSize,\n  });\n\n  useEffect(() => {\n    const updateSizes = () => {\n      const width = window.innerWidth;\n\n      if (width < 400) {\n        setResponsiveSizes({\n          radius: Math.min(radius, 110),\n          thumbnailSize: Math.min(thumbnailSize, 70),\n          centerSize: Math.min(centerSize, 260),\n        });\n      } else if (width < 640) {\n        setResponsiveSizes({\n          radius: Math.min(radius, 140),\n          thumbnailSize: Math.min(thumbnailSize, 80),\n          centerSize: Math.min(centerSize, 300),\n        });\n      } else if (width < 1024) {\n        setResponsiveSizes({\n          radius: Math.min(radius, 200),\n          thumbnailSize: Math.min(thumbnailSize, 90),\n          centerSize: Math.min(centerSize, 340),\n        });\n      } else {\n        setResponsiveSizes({ radius, thumbnailSize, centerSize });\n      }\n    };\n\n    updateSizes();\n    window.addEventListener('resize', updateSizes);\n    return () => window.removeEventListener('resize', updateSizes);\n  }, [radius, thumbnailSize, centerSize]);\n\n  const rotation = useMotionValue(0);\n\n  const smoothRotation = useSpring(rotation, {\n    bounce: 0.15,\n    duration: 0.1,\n  });\n\n  const toggleExpand = useCallback(() => {\n    setIsExpanded((prev) => !prev);\n  }, []);\n\n  const handleItemClick = (index: number) => {\n    setActiveIndex(index);\n    setIsExpanded(false);\n  };\n\n  const containerVariants: Variants = {\n    collapsed: { transition: { staggerChildren: 0.01, staggerDirection: -1 } },\n    expanded: { transition: { staggerChildren: 0.03, delayChildren: 0.1 } },\n  };\n\n  return (\n    <div className=\"relative flex h-[350px] w-full touch-pan-y items-center justify-center overflow-visible select-none sm:h-[450px]\">\n      <AnimatePresence mode=\"popLayout\">\n        {!isExpanded ? (\n          <motion.div\n            key=\"center-view\"\n            layout\n            transition={{ type: 'spring', bounce: 0.15, duration: 0.15 }}\n            className=\"relative z-10\"\n          >\n            <motion.div\n              layoutId={`card-${items[activeIndex].id}`}\n              style={{\n                width: responsiveSizes.centerSize,\n                height: responsiveSizes.centerSize,\n              }}\n              className=\"relative overflow-hidden rounded-[32px] border border-neutral-200 bg-white p-3 shadow-2xl transition-colors duration-300 sm:rounded-[42px] sm:p-4 dark:border-neutral-800 dark:bg-neutral-900\"\n            >\n              <motion.img\n                layoutId={`img-${items[activeIndex].id}`}\n                src={items[activeIndex].url}\n                alt={items[activeIndex].title}\n                className=\"h-full w-full rounded-[28px] object-cover sm:rounded-[36px]\"\n                draggable={false}\n              />\n\n              <button\n                onClick={toggleExpand}\n                className=\"absolute top-6 right-6 flex h-8 w-8 items-center justify-center rounded-full bg-neutral-100 shadow-xl backdrop-blur-xl transition-all duration-200 hover:scale-105 hover:bg-white active:scale-95 sm:top-8 sm:right-8 sm:h-10 sm:w-10 dark:bg-white/10 dark:hover:bg-white/20\"\n              >\n                <X\n                  size={20}\n                  className=\"text-neutral-500 sm:hidden dark:text-white\"\n                />\n                <X\n                  size={28}\n                  className=\"hidden text-neutral-500 sm:block dark:text-white\"\n                />\n              </button>\n            </motion.div>\n          </motion.div>\n        ) : (\n          <motion.div\n            key=\"radial-view\"\n            variants={containerVariants}\n            initial=\"collapsed\"\n            animate=\"expanded\"\n            exit=\"collapsed\"\n            className={`relative flex h-full w-full cursor-grab items-center justify-center active:cursor-grabbing ${\n              isPanning ? 'touch-none' : 'touch-pan-y'\n            }`}\n            onPanStart={() => setIsPanning(true)}\n            onPanEnd={() => setIsPanning(false)}\n            onPan={(_, info) => {\n              rotation.set(rotation.get() + info.delta.x * 0.5);\n            }}\n          >\n            {items.map((item, index) => {\n              const baseAngle =\n                (index / items.length) * (2 * Math.PI) - Math.PI / 2;\n              return (\n                <Item\n                  key={item.id}\n                  item={item}\n                  baseAngle={baseAngle}\n                  radius={responsiveSizes.radius}\n                  thumbnailSize={responsiveSizes.thumbnailSize}\n                  rotation={smoothRotation}\n                  onClick={() => handleItemClick(index)}\n                />\n              );\n            })}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n\ninterface ItemProps {\n  item: GalleryItem;\n  baseAngle: number;\n  radius: number;\n  thumbnailSize: number;\n  rotation: any;\n  onClick: () => void;\n}\n\nconst Item: React.FC<ItemProps> = ({\n  item,\n  baseAngle,\n  radius,\n  thumbnailSize,\n  rotation,\n  onClick,\n}) => {\n  const x = useTransform(rotation, (r: number) => {\n    const currentAngle = baseAngle + (r * Math.PI) / 180;\n    return Math.cos(currentAngle) * radius;\n  });\n\n  const y = useTransform(rotation, (r: number) => {\n    const currentAngle = baseAngle + (r * Math.PI) / 180;\n    return Math.sin(currentAngle) * radius;\n  });\n\n  const rotate = useTransform(rotation, (r: number) => {\n    const currentAngle = baseAngle + (r * Math.PI) / 180;\n    return (currentAngle * 180) / Math.PI + 90;\n  });\n\n  const itemVariants: Variants = {\n    collapsed: {\n      opacity: 0,\n      scale: 0.8,\n      transition: { type: 'spring', bounce: 0.4, duration: 0.5 },\n    },\n    expanded: {\n      scale: 1,\n      opacity: 1,\n      transition: { type: 'spring', bounce: 0.4, duration: 0.5 },\n    },\n  };\n\n  return (\n    <motion.div\n      variants={itemVariants}\n      style={{ x, y, rotate }}\n      onClick={onClick}\n      className=\"absolute cursor-pointer\"\n    >\n      <motion.div\n        layoutId={`card-${item.id}`}\n        style={{ width: thumbnailSize, height: thumbnailSize }}\n        className=\"overflow-hidden rounded-[18px] border border-neutral-200 bg-white p-1 shadow-2xl ring-1 ring-black/5 transition-colors duration-300 sm:rounded-[24px] dark:border-neutral-800 dark:bg-neutral-900 dark:ring-white/5\"\n      >\n        <motion.img\n          layoutId={`img-${item.id}`}\n          src={item.url}\n          alt={item.title}\n          className=\"h-full w-full rounded-[13px] object-cover sm:rounded-[18px]\"\n          draggable={false}\n        />\n      </motion.div>\n    </motion.div>\n  );\n};\n"
    }
  ]
}
