{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "FlowingMenu-JS-TW",
	"title": "FlowingMenu",
	"description": "Liquid flowing active indicator glides between menu items.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "FlowingMenu/FlowingMenu.jsx",
			"content": "import { useRef, useEffect, useState } from 'react';\nimport { gsap } from 'gsap';\n\nfunction FlowingMenu({\n  items = [],\n  speed = 15,\n  textColor = '#fff',\n  bgColor = '#120F17',\n  marqueeBgColor = '#fff',\n  marqueeTextColor = '#120F17',\n  borderColor = '#fff'\n}) {\n  return (\n    <div className=\"w-full h-full overflow-hidden\" style={{ backgroundColor: bgColor }}>\n      <nav className=\"flex flex-col h-full m-0 p-0\">\n        {items.map((item, idx) => (\n          <MenuItem\n            key={idx}\n            {...item}\n            speed={speed}\n            textColor={textColor}\n            marqueeBgColor={marqueeBgColor}\n            marqueeTextColor={marqueeTextColor}\n            borderColor={borderColor}\n            isFirst={idx === 0}\n          />\n        ))}\n      </nav>\n    </div>\n  );\n}\n\nfunction MenuItem({ link, text, image, speed, textColor, marqueeBgColor, marqueeTextColor, borderColor, isFirst }) {\n  const itemRef = useRef(null);\n  const marqueeRef = useRef(null);\n  const marqueeInnerRef = useRef(null);\n  const animationRef = useRef(null);\n  const [repetitions, setRepetitions] = useState(4);\n\n  const animationDefaults = { duration: 0.6, ease: 'expo' };\n\n  const findClosestEdge = (mouseX, mouseY, width, height) => {\n    const topEdgeDist = (mouseX - width / 2) ** 2 + mouseY ** 2;\n    const bottomEdgeDist = (mouseX - width / 2) ** 2 + (mouseY - height) ** 2;\n    return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom';\n  };\n\n  useEffect(() => {\n    const calculateRepetitions = () => {\n      if (!marqueeInnerRef.current) return;\n      const marqueeContent = marqueeInnerRef.current.querySelector('.marquee-part');\n      if (!marqueeContent) return;\n      const contentWidth = marqueeContent.offsetWidth;\n      const viewportWidth = window.innerWidth;\n      const needed = Math.ceil(viewportWidth / contentWidth) + 2;\n      setRepetitions(Math.max(4, needed));\n    };\n\n    calculateRepetitions();\n    window.addEventListener('resize', calculateRepetitions);\n    return () => window.removeEventListener('resize', calculateRepetitions);\n  }, [text, image]);\n\n  useEffect(() => {\n    const setupMarquee = () => {\n      if (!marqueeInnerRef.current) return;\n      const marqueeContent = marqueeInnerRef.current.querySelector('.marquee-part');\n      if (!marqueeContent) return;\n      const contentWidth = marqueeContent.offsetWidth;\n      if (contentWidth === 0) return;\n\n      if (animationRef.current) {\n        animationRef.current.kill();\n      }\n\n      animationRef.current = gsap.to(marqueeInnerRef.current, {\n        x: -contentWidth,\n        duration: speed,\n        ease: 'none',\n        repeat: -1\n      });\n    };\n\n    const timer = setTimeout(setupMarquee, 50);\n    return () => {\n      clearTimeout(timer);\n      if (animationRef.current) {\n        animationRef.current.kill();\n      }\n    };\n  }, [text, image, repetitions, speed]);\n\n  const handleMouseEnter = ev => {\n    if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return;\n    const rect = itemRef.current.getBoundingClientRect();\n    const edge = findClosestEdge(ev.clientX - rect.left, ev.clientY - rect.top, rect.width, rect.height);\n\n    gsap\n      .timeline({ defaults: animationDefaults })\n      .set(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' }, 0)\n      .set(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' }, 0)\n      .to([marqueeRef.current, marqueeInnerRef.current], { y: '0%' }, 0);\n  };\n\n  const handleMouseLeave = ev => {\n    if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return;\n    const rect = itemRef.current.getBoundingClientRect();\n    const edge = findClosestEdge(ev.clientX - rect.left, ev.clientY - rect.top, rect.width, rect.height);\n\n    gsap\n      .timeline({ defaults: animationDefaults })\n      .to(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' }, 0)\n      .to(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' }, 0);\n  };\n\n  return (\n    <div\n      className=\"flex-1 relative overflow-hidden text-center\"\n      ref={itemRef}\n      style={{ borderTop: isFirst ? 'none' : `1px solid ${borderColor}` }}\n    >\n      <a\n        className=\"flex items-center justify-center h-full relative cursor-pointer uppercase no-underline font-semibold text-[4vh]\"\n        href={link}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        style={{ color: textColor }}\n      >\n        {text}\n      </a>\n      <div\n        className=\"absolute top-0 left-0 w-full h-full overflow-hidden pointer-events-none translate-y-[101%]\"\n        ref={marqueeRef}\n        style={{ backgroundColor: marqueeBgColor }}\n      >\n        <div className=\"h-full w-fit flex\" ref={marqueeInnerRef}>\n          {[...Array(repetitions)].map((_, idx) => (\n            <div className=\"marquee-part flex items-center flex-shrink-0\" key={idx} style={{ color: marqueeTextColor }}>\n              <span className=\"whitespace-nowrap uppercase font-normal text-[4vh] leading-[1] px-[1vw]\">{text}</span>\n              <div\n                className=\"w-[200px] h-[7vh] my-[2em] mx-[2vw] py-[1em] rounded-[50px] bg-cover bg-center\"\n                style={{ backgroundImage: `url(${image})` }}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default FlowingMenu;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}