{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "logo-marquee",
  "title": "Logo Marquee",
  "description": "A rolling strip of company logos on an arc, with the middle one big and sharp and the sides small and blurred.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/clover-ui/logo-marquee.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useReducedMotion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface LogoMarqueeLogo {\n  name: string;\n  src: string;\n  ratio?: number;\n}\n\nexport interface LogoMarqueeProps extends React.ComponentProps<\"ul\"> {\n  logos?: LogoMarqueeLogo[];\n  speed?: number;\n  label?: string;\n  shimmer?: boolean;\n  shimmerColor?: string;\n}\n\nconst BASE = \"https://cloverui.xyz/logos/\";\n\nexport const logoMarqueeLogos: LogoMarqueeLogo[] = (\n  [\n    [\"vercel\", \"Vercel\", 5.03],\n    [\"convex\", \"Convex\", 5.74],\n    [\"openai\", \"OpenAI\", 3.69],\n    [\"anthropic\", \"Anthropic\", 8.83],\n    [\"linear\", \"Linear\", 4],\n    [\"notion\", \"Notion\", 2.88],\n    [\"cursor\", \"Cursor\", 5.68],\n    [\"framer\", \"Framer\", 4.43],\n    [\"discord\", \"Discord\", 3.65],\n    [\"shopify\", \"Shopify\", 3.5],\n    [\"descript\", \"Descript\", 4.25],\n    [\"stripe\", \"Stripe\", 2.39],\n    [\"raycast\", \"Raycast\", 3.86],\n    [\"clerk\", \"Clerk\", 3.44],\n  ] as const\n).map(([file, name, ratio]) => ({ name, src: `${BASE}${file}.svg`, ratio }));\n\nconst SPOTS = [\n  { scale: 1, lift: 0, blur: 0, fade: 1 },\n  { scale: 0.74, lift: 0.55, blur: 1, fade: 0.55 },\n  { scale: 0.56, lift: 1.3, blur: 2, fade: 0 },\n];\n\nfunction wrap(offset: number, count: number) {\n  const half = Math.floor(count / 2);\n  return ((((offset + half) % count) + count) % count) - half;\n}\n\nfunction subscribePage(onChange: () => void) {\n  document.addEventListener(\"visibilitychange\", onChange);\n  return () => document.removeEventListener(\"visibilitychange\", onChange);\n}\n\nfunction LogoMarquee({\n  logos = logoMarqueeLogos,\n  speed = 1,\n  label = \"Logos of companies\",\n  shimmer = true,\n  shimmerColor,\n  className,\n  style,\n  ref,\n  ...props\n}: LogoMarqueeProps) {\n  const reduceMotion = useReducedMotion();\n  const pace = 1 / Math.max(speed, 0.1);\n  const listRef = React.useRef<HTMLUListElement>(null);\n  React.useImperativeHandle(ref, () => listRef.current!, []);\n  const [{ active, previous }, setStep] = React.useState({ active: 0, previous: 0 });\n  const [inView, setInView] = React.useState(false);\n  const pageShown = React.useSyncExternalStore(\n    subscribePage,\n    () => document.visibilityState === \"visible\",\n    () => true,\n  );\n  const visible = inView && pageShown;\n  const marks = React.useRef<(HTMLSpanElement | null)[]>([]);\n\n  React.useEffect(() => {\n    const list = listRef.current;\n    if (!list) return;\n    const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), {\n      threshold: 0.3,\n    });\n    observer.observe(list);\n    return () => observer.disconnect();\n  }, []);\n\n  React.useEffect(() => {\n    if (!visible || reduceMotion || logos.length < 2) return;\n    const timer = window.setInterval(\n      () => setStep((step) => ({ active: step.active - 1, previous: step.active })),\n      1750 * pace,\n    );\n    return () => window.clearInterval(timer);\n  }, [visible, reduceMotion, logos.length, pace]);\n\n  React.useEffect(() => {\n    if (!shimmer || !visible || reduceMotion || logos.length === 0) return;\n    const index = ((active % logos.length) + logos.length) % logos.length;\n    const animation = marks.current[index]?.animate(\n      [{ backgroundPosition: \"150% 0\" }, { backgroundPosition: \"-50% 0\" }],\n      {\n        duration: (logos.length > 1 ? 750 : 1400) * pace,\n        delay: (logos.length > 1 ? 950 : 250) * pace,\n        iterations: logos.length > 1 ? 1 : Infinity,\n        easing: \"ease-in-out\",\n      },\n    );\n    return () => animation?.cancel();\n  }, [shimmer, visible, reduceMotion, pace, logos.length, active]);\n\n  const shine = shimmerColor\n    ? `color-mix(in oklab, ${shimmerColor} 35%, white)`\n    : \"color-mix(in oklab, currentColor 45%, var(--background, white))\";\n\n  return (\n    <ul\n      ref={listRef}\n      aria-label={label}\n      className={cn(\n        \"text-foreground/80 relative h-[4.5em] w-full overflow-hidden text-2xl\",\n        className,\n      )}\n      style={style}\n      {...props}\n    >\n      {logos.map((logo, index) => {\n        const ratio = logo.ratio && logo.ratio > 0 ? logo.ratio : 3.5;\n        const mask = `url(\"${logo.src}\")`;\n        const offset = wrap(index - active, logos.length);\n        const spot = SPOTS[Math.min(Math.abs(offset), SPOTS.length - 1)];\n        const jump = Math.abs(offset - wrap(index - previous, logos.length)) > 1;\n        return (\n          <li\n            key={`${logo.name}-${index}`}\n            aria-hidden={offset !== 0 || undefined}\n            className=\"absolute top-1/2 left-1/2 flex h-[2.6em] w-[7em] items-center justify-center\"\n            style={{\n              transform: `translate3d(calc(-50% + ${offset * 7.5}em), calc(-50% + ${spot.lift}em), 0) scale(${spot.scale})`,\n              willChange: \"transform, filter, opacity\",\n              backfaceVisibility: \"hidden\",\n              filter: spot.blur ? `blur(${spot.blur}px)` : undefined,\n              opacity: spot.fade,\n              zIndex: SPOTS.length - Math.abs(offset),\n              transition:\n                reduceMotion || jump\n                  ? \"none\"\n                  : `transform ${900 * pace}ms cubic-bezier(0.37, 0, 0.63, 1), filter ${900 * pace}ms cubic-bezier(0.37, 0, 0.63, 1), opacity ${900 * pace}ms cubic-bezier(0.37, 0, 0.63, 1)`,\n            }}\n          >\n            <span\n              ref={(node) => {\n                marks.current[index] = node;\n              }}\n              role=\"img\"\n              aria-label={logo.name}\n              className=\"block max-h-full bg-current\"\n              style={{\n                backgroundImage: shimmer\n                  ? `linear-gradient(110deg, transparent 36%, ${shine} 47%, ${shine} 53%, transparent 64%)`\n                  : undefined,\n                backgroundSize: \"250% 100%\",\n                backgroundPosition: \"150% 0\",\n                backgroundRepeat: \"no-repeat\",\n                width: `${Math.min(6.5, 2.2 * Math.sqrt(ratio))}em`,\n                aspectRatio: ratio,\n                maskImage: mask,\n                WebkitMaskImage: mask,\n                maskSize: \"contain\",\n                WebkitMaskSize: \"contain\",\n                maskRepeat: \"no-repeat\",\n                WebkitMaskRepeat: \"no-repeat\",\n                maskPosition: \"center\",\n                WebkitMaskPosition: \"center\",\n              }}\n            />\n          </li>\n        );\n      })}\n    </ul>\n  );\n}\n\nexport { LogoMarquee };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}