{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "FlowingMenu-JS-CSS",
	"title": "FlowingMenu",
	"description": "Liquid flowing active indicator glides between menu items.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "FlowingMenu.css",
			"target": "@components/FlowingMenu.css",
			"content": ".menu-wrap {\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n\n.menu {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n}\n\n.menu__item {\n  flex: 1;\n  position: relative;\n  overflow: hidden;\n  text-align: center;\n  border-top: 1px solid;\n}\n\n.menu__item:first-child {\n  border-top: none;\n}\n\n.menu__item-link {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  height: 100%;\n  position: relative;\n  cursor: pointer;\n  text-transform: uppercase;\n  text-decoration: none;\n  white-space: nowrap;\n  font-weight: 600;\n  font-size: 4vh;\n}\n\n.menu__item-link:hover {\n  color: inherit;\n}\n\n.menu__item-link:focus:not(:focus-visible) {\n  color: inherit;\n}\n\n.marquee {\n  position: absolute;\n  top: 0;\n  left: 0;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n  pointer-events: none;\n  transform: translate3d(0, 101%, 0);\n}\n\n.marquee__inner-wrap {\n  height: 100%;\n  width: 100%;\n  overflow: hidden;\n}\n\n.marquee__inner {\n  display: flex;\n  align-items: center;\n  position: relative;\n  height: 100%;\n  width: fit-content;\n  will-change: transform;\n}\n\n.marquee__part {\n  display: flex;\n  align-items: center;\n  flex-shrink: 0;\n}\n\n.marquee span {\n  white-space: nowrap;\n  text-transform: uppercase;\n  font-weight: 400;\n  font-size: 4vh;\n  line-height: 1;\n  padding: 0 1vw;\n}\n\n.marquee__img {\n  width: 200px;\n  height: 7vh;\n  margin: 2em 2vw;\n  padding: 1em 0;\n  border-radius: 50px;\n  background-size: cover;\n  background-position: 50% 50%;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "FlowingMenu.jsx",
			"content": "import { useRef, useEffect, useState } from 'react';\nimport { gsap } from 'gsap';\n\nimport './FlowingMenu.css';\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=\"menu-wrap\" style={{ backgroundColor: bgColor }}>\n      <nav className=\"menu\">\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          />\n        ))}\n      </nav>\n    </div>\n  );\n}\n\nfunction MenuItem({ link, text, image, speed, textColor, marqueeBgColor, marqueeTextColor, borderColor }) {\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 = distMetric(mouseX, mouseY, width / 2, 0);\n    const bottomEdgeDist = distMetric(mouseX, mouseY, width / 2, height);\n    return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom';\n  };\n\n  const distMetric = (x, y, x2, y2) => {\n    const xDiff = x - x2;\n    const yDiff = y - y2;\n    return xDiff * xDiff + yDiff * yDiff;\n  };\n\n  useEffect(() => {\n    const calculateRepetitions = () => {\n      if (!marqueeInnerRef.current) return;\n\n      // Get the first marquee part to measure content width\n      const marqueeContent = marqueeInnerRef.current.querySelector('.marquee__part');\n      if (!marqueeContent) return;\n\n      const contentWidth = marqueeContent.offsetWidth;\n      const viewportWidth = window.innerWidth;\n\n      // Calculate how many copies we need to fill viewport + extra for seamless loop\n      // We need at least 2, but calculate based on content vs viewport\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\n      const marqueeContent = marqueeInnerRef.current.querySelector('.marquee__part');\n      if (!marqueeContent) return;\n\n      const contentWidth = marqueeContent.offsetWidth;\n      if (contentWidth === 0) return;\n\n      if (animationRef.current) {\n        animationRef.current.kill();\n      }\n\n      // Animate exactly one content width for seamless loop\n      animationRef.current = gsap.to(marqueeInnerRef.current, {\n        x: -contentWidth,\n        duration: speed,\n        ease: 'none',\n        repeat: -1\n      });\n    };\n\n    // Small delay to ensure DOM is ready after repetitions update\n    const timer = setTimeout(setupMarquee, 50);\n\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 x = ev.clientX - rect.left;\n    const y = ev.clientY - rect.top;\n    const edge = findClosestEdge(x, y, 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 x = ev.clientX - rect.left;\n    const y = ev.clientY - rect.top;\n    const edge = findClosestEdge(x, y, 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 className=\"menu__item\" ref={itemRef} style={{ borderColor }}>\n      <a\n        className=\"menu__item-link\"\n        href={link}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        style={{ color: textColor }}\n      >\n        {text}\n      </a>\n      <div className=\"marquee\" ref={marqueeRef} style={{ backgroundColor: marqueeBgColor }}>\n        <div className=\"marquee__inner-wrap\">\n          <div className=\"marquee__inner\" ref={marqueeInnerRef} aria-hidden=\"true\">\n            {[...Array(repetitions)].map((_, idx) => (\n              <div className=\"marquee__part\" key={idx} style={{ color: marqueeTextColor }}>\n                <span>{text}</span>\n                <div className=\"marquee__img\" style={{ backgroundImage: `url(${image})` }} />\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default FlowingMenu;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}