{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "logo-orbit",
  "type": "registry:ui",
  "title": "Logo Orbit",
  "description": "Logos spaced evenly around a ring that rotates continuously, each mark counter-rotated so it always stays upright. Hover pauses the orbit.",
  "dependencies": [
    "gsap",
    "@gsap/react"
  ],
  "registryDependencies": [],
  "cssVars": {
    "theme": {}
  },
  "files": [
    {
      "path": "components/tweenui/logo-orbit.tsx",
      "type": "registry:component",
      "target": "components/tweenui/logo-orbit.tsx",
      "content": "'use client';\n\nimport { useRef, type ComponentPropsWithoutRef } from 'react';\nimport { useGSAP } from '@gsap/react';\nimport gsap from 'gsap';\nimport { cn } from '@/lib/utils';\n\ngsap.registerPlugin(useGSAP);\n\nconst prefersReducedMotion = () =>\n  typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\nconst LogoImage = ({ src, srcDark, alt }: LogoOrbitLogo) => {\n  if (!srcDark) {\n    return <img src={src} alt={alt} className=\"size-full object-contain\" />;\n  }\n  return (\n    <>\n      <img src={src} alt={alt} className=\"size-full object-contain dark:hidden\" />\n      <img\n        src={srcDark}\n        alt=\"\"\n        aria-hidden=\"true\"\n        className=\"hidden size-full object-contain dark:block\"\n      />\n    </>\n  );\n};\n\nexport interface LogoOrbitLogo {\n  src: string;\n  /** Optional mark shown under `.dark`. */\n  srcDark?: string;\n  alt: string;\n}\n\nexport interface LogoOrbitProps extends ComponentPropsWithoutRef<'div'> {\n  /** Logos placed evenly around the ring. */\n  logos: LogoOrbitLogo[];\n  /** Ring diameter in pixels. */\n  size?: number;\n  /** Rotation speed multiplier (1 = one turn every 20s). */\n  speed?: number;\n  /** Pause the orbit while the pointer is over the ring. */\n  pauseOnHover?: boolean;\n}\n\nconst LogoOrbit = ({\n  logos,\n  size = 320,\n  speed = 1,\n  pauseOnHover = true,\n  className,\n  ...props\n}: LogoOrbitProps) => {\n  const ringRef = useRef<HTMLDivElement>(null);\n  const hubRef = useRef<HTMLDivElement>(null);\n  const pivotRefs = useRef<(HTMLDivElement | null)[]>([]);\n  const itemRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n  useGSAP(\n    () => {\n      const ring = ringRef.current;\n      const hub = hubRef.current;\n      if (!ring || !hub) return;\n\n      ring.style.setProperty('--orbit-size', `${size}px`);\n\n      const hubSize = hub.offsetWidth || 20;\n      const hubOffset = (size - hubSize) / 2;\n      gsap.set(hub, { x: hubOffset, y: hubOffset, rotation: 0 });\n\n      const pivots = pivotRefs.current.filter((el): el is HTMLDivElement => el !== null);\n      const items = itemRefs.current.filter((el): el is HTMLDivElement => el !== null);\n      const count = pivots.length;\n      if (count === 0) return;\n\n      // Distribute the logos evenly around the ring and keep each one upright.\n      const space = 360 / count;\n      const pivotOriginY = size / 2 + 10;\n      pivots.forEach((pivot, i) => {\n        const pivotHalf = pivot.offsetWidth / 2 || 10;\n        gsap.set(pivot, {\n          rotation: i * space,\n          transformOrigin: `${pivotHalf}px ${pivotOriginY}px`,\n        });\n      });\n      items.forEach((item, i) => {\n        gsap.set(item, { rotation: -i * space, transformOrigin: 'center center' });\n      });\n\n      if (prefersReducedMotion()) return;\n\n      const timeScale = Math.min(20, Math.max(0.05, speed));\n      const tl = gsap.timeline({ repeat: -1 });\n      tl.to(hub, { rotation: 360, duration: 20, ease: 'none' });\n      tl.to(items, { rotation: '-=360', duration: 20, ease: 'none' }, 0);\n      tl.timeScale(timeScale);\n\n      if (!pauseOnHover) return;\n\n      const pause = () => tl.pause();\n      const resume = () => tl.resume();\n      ring.addEventListener('pointerenter', pause);\n      ring.addEventListener('pointerleave', resume);\n      return () => {\n        ring.removeEventListener('pointerenter', pause);\n        ring.removeEventListener('pointerleave', resume);\n      };\n    },\n    { scope: ringRef, dependencies: [size, speed, logos.length, pauseOnHover] }\n  );\n\n  return (\n    <div\n      ref={ringRef}\n      data-logo-orbit\n      className={cn(\n        'relative mx-auto aspect-square rounded-full border-2 border-[#045f64]/15 motion-reduce:transition-none dark:border-[#045f64]/40',\n        className\n      )}\n      style={{ width: size, height: size }}\n      {...props}\n    >\n      <div ref={hubRef} className=\"absolute size-5\">\n        {logos.map((logo, i) => (\n          <div\n            key={`${logo.alt}-${i}`}\n            ref={(el) => {\n              pivotRefs.current[i] = el;\n            }}\n            data-orbit-pivot\n            className=\"absolute top-[calc(var(--orbit-size)*-0.5)]\"\n          >\n            <div\n              ref={(el) => {\n                itemRefs.current[i] = el;\n              }}\n              className=\"absolute top-[-20px] grid size-16 place-items-center overflow-hidden rounded-full border border-[#045f64]/10 bg-white p-3 shadow-[0_1px_2px_rgba(16,24,40,0.12)] dark:border-[#045f64]/40 dark:bg-[#12161F]\"\n            >\n              <LogoImage src={logo.src} srcDark={logo.srcDark} alt={logo.alt} />\n            </div>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default LogoOrbit;\n"
    }
  ],
  "docs": "Docs, props and examples: https://tween-ui.vercel.app/component/logo-orbit"
}