{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-island",
  "type": "registry:component",
  "title": "Scroll Island",
  "description": "Floating scroll island providing progress, actions, and contextual indicators.",
  "dependencies": [
    "lucide-react",
    "motion",
    "react-use-measure"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "components/watermelon/scroll-island.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport { useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { AnimatePresence, motion, MotionConfig } from 'motion/react';\nimport { ChevronDown } from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport useMeasure from 'react-use-measure';\n\nexport interface Topic {\n  id: string;\n  title: string;\n  content: string;\n}\n\nexport interface ScrollIslandProps {\n  topics: Topic[];\n}\n\ndeclare module 'react' {\n  interface StyleHTMLAttributes<T> extends React.HTMLAttributes<T> {\n    jsx?: boolean;\n    global?: boolean;\n  }\n}\n\nexport function ScrollIsland({ topics }: ScrollIslandProps) {\n  const [isOpen, setIsOpen] = useState(false);\n  const [scrollProgress, setScrollProgress] = useState(0);\n  const [activeTopicId, setActiveTopicId] = useState<string | null>(null);\n  const [mounted, setMounted] = useState(false);\n  const [ref, bounds] = useMeasure({ offsetSize: true });\n\n  const contentRef = useRef<HTMLDivElement>(null);\n  const [islandPosition, setIslandPosition] = useState({\n    left: 0,\n    top: 0,\n    width: 0,\n  });\n\n  useEffect(() => {\n    requestAnimationFrame(() => setMounted(true));\n\n    const el = contentRef.current;\n    if (!el) return;\n\n    const updatePosition = () => {\n      const { left, top, width } = el.getBoundingClientRect();\n      setIslandPosition({ left, top, width });\n    };\n    const observer = new ResizeObserver(updatePosition);\n    observer.observe(el);\n    updatePosition();\n\n    const handleScroll = () => {\n      const scrollTop = el.scrollTop;\n      const scrollHeight = el.scrollHeight - el.clientHeight;\n\n      if (scrollHeight > 0) {\n        const progress = (scrollTop / scrollHeight) * 100;\n        setScrollProgress(Math.min(100, Math.max(0, progress)));\n      }\n    };\n\n    el.addEventListener('scroll', handleScroll);\n    handleScroll();\n\n    return () => {\n      observer.disconnect();\n      el.removeEventListener('scroll', handleScroll);\n    };\n  }, []);\n\n  const handleTopicClick = (id: string) => {\n    setActiveTopicId(id);\n    setTimeout(() => setActiveTopicId(null), 1800);\n    setIsOpen(false);\n  };\n\n  const islandUI = (\n    <>\n      <MotionConfig\n        transition={{\n          type: 'spring',\n          bounce: 0.2,\n          duration: 0.7,\n        }}\n      >\n        <div\n          className=\"pointer-events-none fixed z-9999 flex justify-center pt-6\"\n          style={islandPosition}\n        >\n          <motion.div\n            className={cn(\n              'pointer-events-auto flex flex-col items-center overflow-hidden border border-white/10 bg-neutral-900 shadow-2xl',\n            )}\n            initial={{\n              borderRadius: 32,\n            }}\n            animate={{\n              height: bounds.height > 0 ? bounds.height : 'auto',\n              width: isOpen ? 400 : 240,\n              borderRadius: isOpen ? 24 : 32,\n            }}\n          >\n            <div\n              ref={ref}\n              className={cn(\n                'flex flex-col items-center  px-4 w-full',\n                isOpen && '',\n              )}\n            >\n              <div\n                className=\"group flex h-13 w-full cursor-pointer items-center justify-between gap-8 select-none\"\n                onClick={() => setIsOpen(!isOpen)}\n              >\n                <div className=\"flex items-center gap-2\">\n                  <motion.div\n                    layout\n                    className=\"relative h-7 w-7 shrink-0 rounded-full\"\n                    style={{\n                      background: `conic-gradient(white ${scrollProgress}%, #333 0)`,\n                    }}\n                  >\n                    <div className=\"absolute inset-[2.5px] rounded-full bg-black\" />\n                    <div className=\"absolute inset-0 flex items-center justify-center\"></div>\n                  </motion.div>\n\n                  <motion.span\n                    layout\n                    className=\"text-lg font-medium text-white\"\n                  >\n                    Index\n                  </motion.span>\n\n                  <motion.div layout animate={{ rotate: isOpen ? 180 : 0 }}>\n                    <ChevronDown\n                      size={20}\n                      className=\"text-neutral-400 group-hover:text-white\"\n                    />\n                  </motion.div>\n                </div>\n\n                <motion.div\n                  layout\n                  className=\"flex items-center justify-center rounded-full bg-zinc-800 px-2.5 text-lg tabular-nums font-bold text-zinc-200\"\n                >\n                  {Math.round(scrollProgress)}%\n                </motion.div>\n              </div>\n\n              <AnimatePresence mode=\"popLayout\">\n                {isOpen && (\n                  <motion.div\n                    initial={{ opacity: 0 }}\n                    animate={{ opacity: 1 }}\n                    exit={{ opacity: 0 }}\n                    className=\"custom-scrollbar w-full overflow-y-auto pt-2 pb-4\"\n                    style={{\n                      maxHeight:\n                        `max(0px, min(60dvh, calc(100dvh - ${islandPosition.top + 92}px)))`,\n                    }}\n                  >\n                    <div className=\"mx-2 mb-2 h-px bg-white/5\" />\n                    {topics.map((topic) => (\n                      <button\n                        key={topic.id}\n                        onClick={() => {\n                          document.getElementById(topic.id)?.scrollIntoView({\n                            behavior: 'smooth',\n                            block: 'center',\n                          });\n                          handleTopicClick(topic.id);\n                        }}\n                        className=\"w-full truncate rounded-xl py-2 text-left text-sm text-zinc-400 hover:text-white\"\n                      >\n                        {topic.title}\n                      </button>\n                    ))}\n                  </motion.div>\n                )}\n              </AnimatePresence>\n            </div>\n          </motion.div>\n        </div>\n\n        <AnimatePresence>\n          {isOpen && (\n            <motion.div\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              className=\"fixed inset-0 z-9998 bg-black/40 backdrop-blur-sm\"\n              onClick={() => setIsOpen(false)}\n            />\n          )}\n        </AnimatePresence>\n      </MotionConfig>\n    </>\n  );\n\n  return (\n    <div className=\"relative w-full\">\n      <main\n        ref={contentRef}\n        className=\"px- mx-auto h-[calc(100vh-6rem)] max-w-4xl overflow-y-auto pt-32 pb-20\"\n      >\n        {topics.map((topic) => (\n          <div\n            key={topic.id}\n            id={topic.id}\n            className={`mb-20 scroll-mt-32 rounded-2xl p-2 transition-all duration-500 ${activeTopicId === topic.id\n                ? 'animate-flash bg-zinc-100 dark:bg-zinc-900'\n                : ''\n              }`}\n          >\n            <h4 className=\"mb-6 text-3xl font-bold text-zinc-900 dark:text-zinc-50\">\n              {topic.title}\n            </h4>\n            <p className=\"text-lg leading-relaxed text-zinc-600 dark:text-zinc-400\">\n              {topic.content}\n            </p>\n          </div>\n        ))}\n      </main>\n\n      {mounted && createPortal(islandUI, document.body)}\n\n      <style jsx global>{`\n        @keyframes flash {\n          0%,\n          100% {\n            background-color: transparent;\n          }\n          50% {\n            background-color: rgba(255, 255, 255, 0.05);\n          }\n        }\n        .animate-flash {\n          animation: flash 0.6s ease-in-out 3;\n        }\n        .custom-scrollbar::-webkit-scrollbar {\n          width: 3px;\n        }\n        .custom-scrollbar::-webkit-scrollbar-thumb {\n          background: #333;\n          border-radius: 10px;\n        }\n      `}</style>\n    </div>\n  );\n}\n"
    }
  ]
}
