{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "logo-cloud-marquee",
  "title": "Logo Cloud Marquee",
  "description": "Auto-scrolling marquee of editable logos with progressive-blur edge fades.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "payload-components/source/blocks/shared/logoCloudFields.ts",
      "content": "import type { Field } from 'payload'\n\n/**\n * Shared field core for the Logo Cloud kit family.\n *\n * Every logo-cloud variant (logo-cloud-grid, logo-cloud-hover,\n * logo-cloud-marquee, logo-cloud-inline, logo-cloud-inline-wrap, …) spreads\n * these fields first and then appends its own variant-specific shape (for\n * example the hover variant adds a CTA link group). Editing the shared\n * heading/logos shape here updates every installed logo-cloud block at once,\n * so the family never drifts field-by-field across a repo.\n *\n * Each logo is an editable Media upload plus an accessible name and an\n * optional link, so editors manage the wall of logos from the admin instead\n * of shipping hardcoded brand SVGs.\n *\n * Installed once per repo at `src/blocks/shared/logoCloudFields.ts`; re-running\n * `payload-components add logo-cloud-*` never overwrites a copy you have already edited.\n */\nexport const logoCloudFields: Field[] = [\n  {\n    name: 'heading',\n    type: 'text',\n    required: true,\n  },\n  {\n    name: 'logos',\n    type: 'array',\n    required: true,\n    minRows: 2,\n    maxRows: 12,\n    admin: {\n      initCollapsed: true,\n    },\n    fields: [\n      {\n        name: 'logo',\n        type: 'upload',\n        relationTo: 'media',\n        required: true,\n      },\n      {\n        name: 'name',\n        type: 'text',\n        required: true,\n      },\n      {\n        name: 'href',\n        type: 'text',\n      },\n    ],\n  },\n]\n",
      "type": "registry:file",
      "target": "~/src/blocks/shared/logoCloudFields.ts"
    },
    {
      "path": "payload-components/source/components/ui/infinite-slider.tsx",
      "content": "'use client'\n\nimport type { ReactNode } from 'react'\n\nimport { animate, motion, useMotionValue, useReducedMotion } from 'motion/react'\nimport { useEffect, useRef, useState } from 'react'\n\nimport { cn } from '@/utilities/ui'\n\n/**\n * Continuously scrolling row, ported from the motion-primitives InfiniteSlider\n * (MIT) into the payload-components family. Self-contained: the only runtime\n * dependency is `motion`; element width is measured with a local\n * ResizeObserver instead of an extra package.\n *\n * Used by the Logo Cloud Marquee block to scroll an editable wall of logos.\n */\n\nfunction useElementWidth() {\n  const ref = useRef<HTMLDivElement | null>(null)\n  const [width, setWidth] = useState(0)\n\n  useEffect(() => {\n    const element = ref.current\n    if (!element) return\n\n    const observer = new ResizeObserver((entries) => {\n      const entry = entries[0]\n      if (entry) setWidth(entry.contentRect.width)\n    })\n    observer.observe(element)\n\n    return () => observer.disconnect()\n  }, [])\n\n  return [ref, width] as const\n}\n\nexport type InfiniteSliderProps = {\n  children: ReactNode\n  className?: string\n  gap?: number\n  reverse?: boolean\n  speed?: number\n  speedOnHover?: number\n}\n\nexport function InfiniteSlider({\n  children,\n  className,\n  gap = 16,\n  reverse = false,\n  speed = 100,\n  speedOnHover,\n}: InfiniteSliderProps) {\n  const [currentSpeed, setCurrentSpeed] = useState(speed)\n  const [ref, width] = useElementWidth()\n  const translation = useMotionValue(0)\n  const [isTransitioning, setIsTransitioning] = useState(false)\n  const [key, setKey] = useState(0)\n  const shouldReduceMotion = useReducedMotion()\n\n  useEffect(() => {\n    // Respect the user's reduced-motion preference: skip the infinite scroll\n    // and leave the row static (WCAG 2.2.2 Pause/Stop/Hide, 2.3.3).\n    if (shouldReduceMotion) return\n\n    const contentSize = width + gap\n    const from = reverse ? -contentSize / 2 : 0\n    const to = reverse ? 0 : -contentSize / 2\n\n    const controls = isTransitioning\n      ? animate(translation, [translation.get(), to], {\n          duration: Math.abs((translation.get() - to) / currentSpeed),\n          ease: 'linear',\n          onComplete: () => {\n            setIsTransitioning(false)\n            setKey((prev) => prev + 1)\n          },\n        })\n      : animate(translation, [from, to], {\n          duration: contentSize / currentSpeed,\n          ease: 'linear',\n          onRepeat: () => {\n            translation.set(from)\n          },\n          repeat: Infinity,\n          repeatDelay: 0,\n          repeatType: 'loop',\n        })\n\n    return controls?.stop\n  }, [key, translation, currentSpeed, width, gap, isTransitioning, reverse, shouldReduceMotion])\n\n  const hoverProps =\n    speedOnHover && !shouldReduceMotion\n      ? {\n          onHoverEnd: () => {\n            setIsTransitioning(true)\n            setCurrentSpeed(speed)\n          },\n          onHoverStart: () => {\n            setIsTransitioning(true)\n            setCurrentSpeed(speedOnHover)\n          },\n        }\n      : {}\n\n  return (\n    <div className={cn('overflow-hidden', className)}>\n      <motion.div\n        className=\"flex w-max\"\n        ref={ref}\n        style={{ gap: `${gap}px`, x: translation }}\n        {...hoverProps}\n      >\n        {children}\n        {children}\n      </motion.div>\n    </div>\n  )\n}\n",
      "type": "registry:file",
      "target": "~/src/components/ui/infinite-slider.tsx"
    },
    {
      "path": "payload-components/source/components/ui/progressive-blur.tsx",
      "content": "'use client'\n\nimport type { HTMLMotionProps } from 'motion/react'\n\nimport { motion } from 'motion/react'\n\nimport { cn } from '@/utilities/ui'\n\n/**\n * Layered directional blur, ported from the motion-primitives ProgressiveBlur\n * (MIT) into the payload-components family. Only runtime dependency is `motion`.\n *\n * Used by the Logo Cloud Marquee block to fade the scrolling logos into the\n * card edges.\n */\n\nexport const GRADIENT_ANGLES = {\n  bottom: 180,\n  left: 270,\n  right: 90,\n  top: 0,\n}\n\nexport type ProgressiveBlurProps = {\n  blurIntensity?: number\n  blurLayers?: number\n  className?: string\n  direction?: keyof typeof GRADIENT_ANGLES\n} & HTMLMotionProps<'div'>\n\nexport function ProgressiveBlur({\n  blurIntensity = 0.25,\n  blurLayers = 8,\n  className,\n  direction = 'bottom',\n  ...props\n}: ProgressiveBlurProps) {\n  const layers = Math.max(blurLayers, 2)\n  const segmentSize = 1 / (blurLayers + 1)\n\n  return (\n    <div className={cn('relative', className)}>\n      {Array.from({ length: layers }).map((_, index) => {\n        const angle = GRADIENT_ANGLES[direction]\n        const gradientStops = [\n          index * segmentSize,\n          (index + 1) * segmentSize,\n          (index + 2) * segmentSize,\n          (index + 3) * segmentSize,\n        ].map(\n          (pos, posIndex) =>\n            `rgba(255, 255, 255, ${posIndex === 1 || posIndex === 2 ? 1 : 0}) ${pos * 100}%`,\n        )\n\n        const gradient = `linear-gradient(${angle}deg, ${gradientStops.join(', ')})`\n\n        return (\n          <motion.div\n            className=\"absolute inset-0 rounded-[inherit]\"\n            key={index}\n            style={{\n              WebkitMaskImage: gradient,\n              backdropFilter: `blur(${index * blurIntensity}px)`,\n              maskImage: gradient,\n            }}\n            {...props}\n          />\n        )\n      })}\n    </div>\n  )\n}\n",
      "type": "registry:file",
      "target": "~/src/components/ui/progressive-blur.tsx"
    },
    {
      "path": "payload-components/source/blocks/LogoCloudMarquee/config.ts",
      "content": "import type { Block } from 'payload'\n\nimport { logoCloudFields } from '@/blocks/shared/logoCloudFields'\n\nexport const LogoCloudMarquee: Block = {\n  slug: 'logoCloudMarquee',\n  // Existing apps must migrate stored data before adopting this identifier:\n  // https://www.payload-components.xyz/docs/registry#installed-source-and-migrations\n  dbName: 'pc_log_clo_mar',\n  interfaceName: 'LogoCloudMarqueeBlock',\n  fields: [\n    // Shared logo-cloud core (heading + logos). Edit the shared shape in\n    // @/blocks/shared/logoCloudFields to update every logo-cloud variant.\n    ...logoCloudFields,\n  ],\n  labels: {\n    plural: 'Logo Cloud Marquee Blocks',\n    singular: 'Logo Cloud Marquee',\n  },\n}\n",
      "type": "registry:file",
      "target": "~/src/blocks/LogoCloudMarquee/config.ts"
    },
    {
      "path": "payload-components/source/blocks/LogoCloudMarquee/Component.tsx",
      "content": "import React from 'react'\n\nimport type { LogoCloudMarqueeBlock as LogoCloudMarqueeBlockData } from '@/payload-types'\n\nimport { Media } from '@/components/Media'\nimport { InfiniteSlider } from '@/components/ui/infinite-slider'\nimport { ProgressiveBlur } from '@/components/ui/progressive-blur'\nimport { cn } from '@/utilities/ui'\n\ntype Props = LogoCloudMarqueeBlockData & {\n  id?: string\n  className?: string\n  disableInnerContainer?: boolean\n}\n\nexport const LogoCloudMarqueeBlock: React.FC<Props> = ({\n  className,\n  disableInnerContainer,\n  heading,\n  id,\n  logos,\n}) => {\n  return (\n    <section className={cn('container', className)} id={id ? `block-${id}` : undefined}>\n      <div className=\"overflow-hidden rounded-frame border border-border/70 bg-card/35 px-6 py-10 sm:px-8 lg:px-12 lg:py-14\">\n        <div\n          className={cn('flex flex-col items-center gap-6 md:flex-row md:gap-0', {\n            'mx-auto max-w-6xl': !disableInnerContainer,\n          })}\n        >\n          <div className=\"md:max-w-44 md:border-e md:border-border/70 md:pe-6\">\n            <p className=\"text-center text-sm text-muted-foreground md:text-end\">{heading}</p>\n          </div>\n\n          <div className=\"relative w-full py-6 md:w-[calc(100%-11rem)]\">\n            <InfiniteSlider gap={112} speed={40} speedOnHover={20}>\n              {logos?.map((item, index) => {\n                const logo = <Media resource={item.logo} imgClassName=\"h-7 w-auto object-contain\" />\n\n                return (\n                  <div\n                    className=\"flex items-center justify-center\"\n                    key={item.id ?? `${item.name}-${index}`}\n                  >\n                    {item.href ? <a href={item.href}>{logo}</a> : logo}\n                  </div>\n                )\n              })}\n            </InfiniteSlider>\n\n            <div\n              aria-hidden\n              className=\"pointer-events-none absolute inset-y-0 left-0 w-20 bg-gradient-to-r from-card/80 to-transparent\"\n            />\n            <div\n              aria-hidden\n              className=\"pointer-events-none absolute inset-y-0 right-0 w-20 bg-gradient-to-l from-card/80 to-transparent\"\n            />\n            <ProgressiveBlur\n              blurIntensity={1}\n              className=\"pointer-events-none absolute left-0 top-0 h-full w-20\"\n              direction=\"left\"\n            />\n            <ProgressiveBlur\n              blurIntensity={1}\n              className=\"pointer-events-none absolute right-0 top-0 h-full w-20\"\n              direction=\"right\"\n            />\n          </div>\n        </div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:file",
      "target": "~/src/blocks/LogoCloudMarquee/Component.tsx"
    }
  ],
  "meta": {
    "payloadComponent": {
      "installCommand": "payload-components add logo-cloud-marquee",
      "postInstall": [
        "generate:types",
        "generate:importmap"
      ],
      "requiresPayloadComponentWrapper": true,
      "supportedTargets": [
        "payload-website-starter",
        "payload-blocks-app"
      ]
    }
  },
  "docs": "Payload Components installs this as a Payload CMS block for the Payload website starter shape.\n\nRecommended install:\n\n```bash\npnpm payload-components add logo-cloud-marquee\n```\n\nDirect shadcn install URL:\n\n```bash\npnpm dlx shadcn@latest add https://www.payload-components.xyz/r/logo-cloud-marquee.json\n```\n\nDirect shadcn installs only copy the block source files and shadcn UI dependencies. Use `payload-components add logo-cloud-marquee` when you also want Payload layout registration, `RenderBlocks` wiring, `generate:types`, and `generate:importmap` handled for you.",
  "type": "registry:block"
}