{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "emoji-spree-choice-chips",
  "type": "registry:component",
  "title": "Emoji Spree Choice Chips",
  "description": "A playful multi-select component with exploding emoji particles and smooth spring animations.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/watermelon/emoji-spree-choice-chips.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport React, { useState } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\n\nexport interface InterestItem {\n  id: string;\n  label: string;\n  emoji: string;\n}\n\ninterface Particle {\n  id: string;\n  emoji: string;\n  xOffset: number;\n  rotate: number;\n}\n\ninterface Props {\n  interests: InterestItem[];\n  onChange?: (selectedIds: string[]) => void;\n}\n\nexport const EmojiSpreeChips: React.FC<Props> = ({ interests, onChange }) => {\n  const [selected, setSelected] = useState<string[]>([]);\n  const [particles, setParticles] = useState<Particle[]>([]);\n  const [isPanning, setIsPanning] = useState(false);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  const spawnParticles = (emoji: string) => {\n    const newParticles: Particle[] = Array.from({ length: 3 }).map(() => ({\n      id: crypto.randomUUID(),\n      emoji,\n      xOffset: (Math.random() - 0.5) * 180,\n      rotate: (Math.random() - 0.5) * 40,\n    }));\n\n    setParticles(newParticles);\n\n    setTimeout(() => {\n      setParticles([]);\n    }, 1600);\n  };\n\n  const toggleInterest = (id: string, emoji: string) => {\n    setSelected((prev) => {\n      const exists = prev.includes(id);\n      const updated = exists ? prev.filter((i) => i !== id) : [...prev, id];\n\n      onChange?.(updated);\n\n      if (!exists) spawnParticles(emoji);\n\n      return updated;\n    });\n  };\n\n  const rows = React.useMemo(() => {\n    const result: InterestItem[][] = [[], [], []];\n    interests.forEach((item, index) => {\n      result[index % 3].push(item);\n    });\n    return result;\n  }, [interests]);\n\n  return (\n    <div className=\"relative isolate flex min-h-[500px] w-full max-w-4xl flex-col items-center overflow-hidden py-10 sm:min-h-[600px]\">\n      <h2 className=\"mb-6 w-full self-start px-6 text-2xl font-bold sm:mb-8 sm:text-3xl\">\n        Interests\n      </h2>\n\n      {/* Chips */}\n      <motion.div\n        ref={containerRef}\n        className={`relative z-20 w-full cursor-grab overflow-hidden mask-r-from-90% mask-l-from-90% px-6 active:cursor-grabbing ${\n          isPanning ? 'touch-none' : 'touch-pan-y'\n        }`}\n      >\n        <motion.div\n          drag=\"x\"\n          dragConstraints={containerRef}\n          onPanStart={() => setIsPanning(true)}\n          onPanEnd={() => setIsPanning(false)}\n          className=\"flex w-max flex-col gap-4 pr-12 sm:gap-5\"\n        >\n          {rows.map((row, rowIndex) => (\n            <div key={rowIndex} className=\"flex w-max gap-4 sm:gap-5\">\n              {row.map((item) => {\n                const isSelected = selected.includes(item.id);\n\n                return (\n                  <motion.button\n                    key={item.id}\n                    whileTap={{ scale: 0.95 }}\n                    transition={{ type: 'spring', stiffness: 260, damping: 18 }}\n                    onClick={() => toggleInterest(item.id, item.emoji)}\n                    className={`flex w-max items-center gap-2 rounded-full border px-4 py-1.5 text-base font-semibold whitespace-nowrap sm:gap-3 sm:px-5 sm:py-2 sm:text-lg ${\n                      isSelected\n                        ? 'border-neutral-300 bg-white dark:border-neutral-600 dark:bg-neutral-800'\n                        : 'border-neutral-200 bg-neutral-100 dark:border-neutral-800 dark:bg-neutral-900'\n                    }`}\n                  >\n                    <span>{item.emoji}</span>\n                    <span>{item.label}</span>\n                  </motion.button>\n                );\n              })}\n            </div>\n          ))}\n        </motion.div>\n      </motion.div>\n\n      {/* PARTICLES */}\n      <div className=\"pointer-events-none absolute inset-0\">\n        <AnimatePresence>\n          {particles.map((p, index) => (\n            <FloatingEmoji\n              key={p.id}\n              emoji={p.emoji}\n              delay={index * 0.08}\n              xOffset={p.xOffset}\n              rotate={p.rotate}\n            />\n          ))}\n        </AnimatePresence>\n      </div>\n\n      {/* Selected Pill */}\n      <div className=\"absolute bottom-8 left-1/2 z-20 -translate-x-1/2 sm:bottom-12\">\n        <AnimatePresence>\n          {selected.length > 0 && (\n            <motion.div\n              initial={{ opacity: 0, y: 50, scale: 0.9 }}\n              animate={{ opacity: 1, y: 0, scale: 1 }}\n              exit={{ opacity: 0, y: 40 }}\n              transition={{\n                type: 'spring',\n                stiffness: 200,\n                damping: 20,\n              }}\n              className=\"relative rounded-full border bg-white px-6 py-2.5 text-lg font-bold shadow-lg sm:px-10 sm:py-4 sm:text-xl dark:bg-neutral-900\"\n            >\n              {selected.length} Interests\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </div>\n    </div>\n  );\n};\n\n/* Floating Emoji Component */\nconst FloatingEmoji = ({\n  emoji,\n  delay,\n  xOffset,\n  rotate,\n}: {\n  emoji: string;\n  delay: number;\n  xOffset: number;\n  rotate: number;\n}) => {\n  const [phase, setPhase] = useState<'up' | 'down'>('up');\n  const isMobile = React.useSyncExternalStore(\n    (callback) => {\n      window.addEventListener('resize', callback);\n      return () => window.removeEventListener('resize', callback);\n    },\n    () => window.innerWidth < 640,\n    () => false\n  );\n\n  return (\n    <motion.div\n      initial={{ y: 0, x: 0, opacity: 0, scale: 0.6, rotate: 0 }}\n      animate={{\n        y: [0, isMobile ? -180 : -260, isMobile ? -180 : -260, 30],\n        x: [\n          0,\n          xOffset * (isMobile ? 0.6 : 1),\n          xOffset * (isMobile ? 0.5 : 0.8),\n        ],\n        opacity: [0, 1, 1, 0],\n        scale: [0.6, isMobile ? 2 : 3, isMobile ? 2 : 3, 0.6],\n        rotate: [0, rotate, rotate * 0.5],\n      }}\n      transition={{\n        duration: 1,\n        ease: 'easeInOut',\n        delay,\n      }}\n      onUpdate={(latest) => {\n        if (typeof latest.y === 'number') {\n          const threshold = isMobile ? -90 : -130;\n          if (latest.y < threshold) {\n            setPhase('up');\n          } else {\n            setPhase('down');\n          }\n        }\n      }}\n      className={`absolute bottom-20 left-1/2 -translate-x-1/2 text-4xl sm:text-6xl ${\n        phase === 'up' ? 'z-30' : 'z-10'\n      }`}\n    >\n      {emoji}\n    </motion.div>\n  );\n};\n"
    }
  ]
}
