{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "GooeyNav-TS-CSS",
	"title": "GooeyNav",
	"description": "Navigation indicator morphs with gooey blob transitions between items.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "GooeyNav.css",
			"target": "@components/GooeyNav.css",
			"content": ":root {\n  --linear-ease: linear(\n    0,\n    0.068,\n    0.19 2.7%,\n    0.804 8.1%,\n    1.037,\n    1.199 13.2%,\n    1.245,\n    1.27 15.8%,\n    1.274,\n    1.272 17.4%,\n    1.249 19.1%,\n    0.996 28%,\n    0.949,\n    0.928 33.3%,\n    0.926,\n    0.933 36.8%,\n    1.001 45.6%,\n    1.013,\n    1.019 50.8%,\n    1.018 54.4%,\n    1 63.1%,\n    0.995 68%,\n    1.001 85%,\n    1\n  );\n}\n\n.gooey-nav-container {\n  position: relative;\n}\n\n.gooey-nav-container nav {\n  display: flex;\n  position: relative;\n  transform: translate3d(0, 0, 0.01px);\n}\n\n.gooey-nav-container nav ul {\n  display: flex;\n  gap: 2em;\n  list-style: none;\n  padding: 0 1em;\n  margin: 0;\n  position: relative;\n  z-index: 3;\n  color: white;\n  text-shadow: 0 1px 1px hsl(205deg 30% 10% / 0.2);\n}\n\n.gooey-nav-container nav ul li {\n  border-radius: 100vw;\n  position: relative;\n  cursor: pointer;\n  transition:\n    background-color 0.3s ease,\n    color 0.3s ease,\n    box-shadow 0.3s ease;\n  box-shadow: 0 0 0.5px 1.5px transparent;\n  color: white;\n}\n\n.gooey-nav-container nav ul li a {\n  display: inline-block;\n  padding: 0.6em 1em;\n}\n\n.gooey-nav-container nav ul li:focus-within:has(:focus-visible) {\n  box-shadow: 0 0 0.5px 1.5px white;\n}\n\n.gooey-nav-container nav ul li::after {\n  content: '';\n  position: absolute;\n  inset: 0;\n  border-radius: 10px;\n  background: white;\n  opacity: 0;\n  transform: scale(0);\n  transition: all 0.3s ease;\n  z-index: -1;\n}\n\n.gooey-nav-container nav ul li.active {\n  color: black;\n  text-shadow: none;\n}\n\n.gooey-nav-container nav ul li.active::after {\n  opacity: 1;\n  transform: scale(1);\n}\n\n.gooey-nav-container .effect {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 0;\n  height: 0;\n  opacity: 1;\n  pointer-events: none;\n  display: grid;\n  place-items: center;\n  z-index: 1;\n}\n\n.gooey-nav-container .effect.text {\n  color: white;\n  transition: color 0.3s ease;\n}\n\n.gooey-nav-container .effect.text.active {\n  color: black;\n}\n\n.gooey-nav-container .effect.filter {\n  filter: blur(7px) contrast(100) blur(0);\n  mix-blend-mode: lighten;\n}\n\n.gooey-nav-container .effect.filter::before {\n  content: '';\n  position: absolute;\n  inset: -75px;\n  z-index: -2;\n  background: black;\n}\n\n.gooey-nav-container .effect.filter::after {\n  content: '';\n  position: absolute;\n  inset: 0;\n  background: white;\n  transform: scale(0);\n  opacity: 0;\n  z-index: -1;\n  border-radius: 100vw;\n}\n\n.gooey-nav-container .effect.active::after {\n  animation: pill 0.3s ease both;\n}\n\n@keyframes pill {\n  to {\n    transform: scale(1);\n    opacity: 1;\n  }\n}\n\n.particle,\n.point {\n  display: block;\n  opacity: 0;\n  width: 20px;\n  height: 20px;\n  border-radius: 100%;\n  transform-origin: center;\n}\n\n.particle {\n  --time: 5s;\n  position: absolute;\n  top: calc(50% - 8px);\n  left: calc(50% - 8px);\n  animation: particle calc(var(--time)) ease 1 -350ms;\n}\n\n.point {\n  background: var(--color);\n  opacity: 1;\n  animation: point calc(var(--time)) ease 1 -350ms;\n}\n\n@keyframes particle {\n  0% {\n    transform: rotate(0deg) translate(calc(var(--start-x)), calc(var(--start-y)));\n    opacity: 1;\n    animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45);\n  }\n\n  70% {\n    transform: rotate(calc(var(--rotate) * 0.5)) translate(calc(var(--end-x) * 1.2), calc(var(--end-y) * 1.2));\n    opacity: 1;\n    animation-timing-function: ease;\n  }\n\n  85% {\n    transform: rotate(calc(var(--rotate) * 0.66)) translate(calc(var(--end-x)), calc(var(--end-y)));\n    opacity: 1;\n  }\n\n  100% {\n    transform: rotate(calc(var(--rotate) * 1.2)) translate(calc(var(--end-x) * 0.5), calc(var(--end-y) * 0.5));\n    opacity: 1;\n  }\n}\n\n@keyframes point {\n  0% {\n    transform: scale(0);\n    opacity: 0;\n    animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45);\n  }\n\n  25% {\n    transform: scale(calc(var(--scale) * 0.25));\n  }\n\n  38% {\n    opacity: 1;\n  }\n\n  65% {\n    transform: scale(var(--scale));\n    opacity: 1;\n    animation-timing-function: ease;\n  }\n\n  85% {\n    transform: scale(var(--scale));\n    opacity: 1;\n  }\n\n  100% {\n    transform: scale(0);\n    opacity: 0;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "GooeyNav.tsx",
			"content": "import React, { useRef, useEffect, useState } from 'react';\nimport './GooeyNav.css';\n\ninterface GooeyNavItem {\n  label: string;\n  href: string;\n}\n\nexport interface GooeyNavProps {\n  items: GooeyNavItem[];\n  animationTime?: number;\n  particleCount?: number;\n  particleDistances?: [number, number];\n  particleR?: number;\n  timeVariance?: number;\n  colors?: number[];\n  initialActiveIndex?: number;\n}\n\nconst GooeyNav: React.FC<GooeyNavProps> = ({\n  items,\n  animationTime = 600,\n  particleCount = 15,\n  particleDistances = [90, 10],\n  particleR = 100,\n  timeVariance = 300,\n  colors = [1, 2, 3, 1, 2, 3, 1, 4],\n  initialActiveIndex = 0\n}) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const navRef = useRef<HTMLUListElement>(null);\n  const filterRef = useRef<HTMLSpanElement>(null);\n  const textRef = useRef<HTMLSpanElement>(null);\n  const [activeIndex, setActiveIndex] = useState<number>(initialActiveIndex);\n\n  const noise = (n = 1) => n / 2 - Math.random() * n;\n\n  const getXY = (distance: number, pointIndex: number, totalPoints: number): [number, number] => {\n    const angle = ((360 + noise(8)) / totalPoints) * pointIndex * (Math.PI / 180);\n    return [distance * Math.cos(angle), distance * Math.sin(angle)];\n  };\n\n  const createParticle = (i: number, t: number, d: [number, number], r: number) => {\n    let rotate = noise(r / 10);\n    return {\n      start: getXY(d[0], particleCount - i, particleCount),\n      end: getXY(d[1] + noise(7), particleCount - i, particleCount),\n      time: t,\n      scale: 1 + noise(0.2),\n      color: colors[Math.floor(Math.random() * colors.length)],\n      rotate: rotate > 0 ? (rotate + r / 20) * 10 : (rotate - r / 20) * 10\n    };\n  };\n\n  const makeParticles = (element: HTMLElement) => {\n    const d: [number, number] = particleDistances;\n    const r = particleR;\n    const bubbleTime = animationTime * 2 + timeVariance;\n    element.style.setProperty('--time', `${bubbleTime}ms`);\n\n    for (let i = 0; i < particleCount; i++) {\n      const t = animationTime * 2 + noise(timeVariance * 2);\n      const p = createParticle(i, t, d, r);\n      element.classList.remove('active');\n\n      setTimeout(() => {\n        const particle = document.createElement('span');\n        const point = document.createElement('span');\n        particle.classList.add('particle');\n        particle.style.setProperty('--start-x', `${p.start[0]}px`);\n        particle.style.setProperty('--start-y', `${p.start[1]}px`);\n        particle.style.setProperty('--end-x', `${p.end[0]}px`);\n        particle.style.setProperty('--end-y', `${p.end[1]}px`);\n        particle.style.setProperty('--time', `${p.time}ms`);\n        particle.style.setProperty('--scale', `${p.scale}`);\n        particle.style.setProperty('--color', `var(--color-${p.color}, white)`);\n        particle.style.setProperty('--rotate', `${p.rotate}deg`);\n\n        point.classList.add('point');\n        particle.appendChild(point);\n        element.appendChild(particle);\n        requestAnimationFrame(() => {\n          element.classList.add('active');\n        });\n        setTimeout(() => {\n          try {\n            element.removeChild(particle);\n          } catch {\n            // Do nothing\n          }\n        }, t);\n      }, 30);\n    }\n  };\n\n  const updateEffectPosition = (element: HTMLElement) => {\n    if (!containerRef.current || !filterRef.current || !textRef.current) return;\n    const containerRect = containerRef.current.getBoundingClientRect();\n    const pos = element.getBoundingClientRect();\n\n    const styles = {\n      left: `${pos.x - containerRect.x}px`,\n      top: `${pos.y - containerRect.y}px`,\n      width: `${pos.width}px`,\n      height: `${pos.height}px`\n    };\n    Object.assign(filterRef.current.style, styles);\n    Object.assign(textRef.current.style, styles);\n    textRef.current.innerText = element.innerText;\n  };\n\n  const handleClick = (e: React.MouseEvent<HTMLAnchorElement>, index: number) => {\n    const liEl = e.currentTarget;\n    if (activeIndex === index) return;\n\n    setActiveIndex(index);\n    updateEffectPosition(liEl);\n\n    if (filterRef.current) {\n      const particles = filterRef.current.querySelectorAll('.particle');\n      particles.forEach(p => filterRef.current!.removeChild(p));\n    }\n\n    if (textRef.current) {\n      textRef.current.classList.remove('active');\n\n      void textRef.current.offsetWidth;\n      textRef.current.classList.add('active');\n    }\n\n    if (filterRef.current) {\n      makeParticles(filterRef.current);\n    }\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLAnchorElement>, index: number) => {\n    if (e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault();\n      const liEl = e.currentTarget.parentElement;\n      if (liEl) {\n        handleClick(\n          {\n            currentTarget: liEl\n          } as React.MouseEvent<HTMLAnchorElement>,\n          index\n        );\n      }\n    }\n  };\n\n  useEffect(() => {\n    if (!navRef.current || !containerRef.current) return;\n    const activeLi = navRef.current.querySelectorAll('li')[activeIndex] as HTMLElement;\n    if (activeLi) {\n      updateEffectPosition(activeLi);\n      textRef.current?.classList.add('active');\n    }\n\n    const resizeObserver = new ResizeObserver(() => {\n      const currentActiveLi = navRef.current?.querySelectorAll('li')[activeIndex] as HTMLElement;\n      if (currentActiveLi) {\n        updateEffectPosition(currentActiveLi);\n      }\n    });\n\n    resizeObserver.observe(containerRef.current);\n    return () => resizeObserver.disconnect();\n  }, [activeIndex]);\n\n  return (\n    <div className=\"gooey-nav-container\" ref={containerRef}>\n      <nav>\n        <ul ref={navRef}>\n          {items.map((item, index) => (\n            <li key={index} className={activeIndex === index ? 'active' : ''}>\n              <a href={item.href} onClick={e => handleClick(e, index)} onKeyDown={e => handleKeyDown(e, index)}>\n                {item.label}\n              </a>\n            </li>\n          ))}\n        </ul>\n      </nav>\n      <span className=\"effect filter\" ref={filterRef} />\n      <span className=\"effect text\" ref={textRef} />\n    </div>\n  );\n};\n\nexport default GooeyNav;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}