{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "logo-marquee",
  "type": "registry:block",
  "title": "Logo marquee",
  "description": "A quiet, continuously moving row of brand marks with a pause control.",
  "categories": [
    "blocks"
  ],
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/button.json",
    "https://irisui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/registry/blocks/logo-marquee/logo-marquee.tsx",
      "content": "\"use client\";\n\nimport { useId, useState } from \"react\";\nimport { Pause, Play } from \"lucide-react\";\nimport { Button } from \"../../button/button\";\nimport { cn } from \"../../lib/utils\";\n\nexport interface LogoMarqueeBrand {\n  name: string;\n  /** Monochrome SVG, drawn as a mask in the text color. Pass an empty string for a name-only wordmark. */\n  icon: string;\n  /** Single brand color for the mark in color tone. Leave out for brands whose mark is black or white. */\n  color?: string;\n  /** Full color SVG for multicolor marks, used in color tone instead of `icon`. */\n  colorIcon?: string;\n}\n\nexport type LogoMarqueeTone = \"color\" | \"mono\";\n\nexport interface LogoMarqueeProps {\n  title?: string;\n  description?: string;\n  brands?: LogoMarqueeBrand[];\n  /** `color` shows each mark in its official colors; `mono` draws every mark in the text color. */\n  tone?: LogoMarqueeTone;\n}\n\nconst exampleBrands: LogoMarqueeBrand[] = [\n  \"CapyDB\",\n  \"notdocs\",\n  \"MyRoomie\",\n  \"ErmisAI\",\n  \"Anasa\",\n  \"Entrolytics\",\n  \"OpsisID\",\n].map((name) => ({ name, icon: \"\" }));\n\n/* Below 640 px of container width the section tightens: one column intro, smaller type and marks. */\nconst markClass = \"block size-[22px] flex-none @max-[640px]/logos:size-5\";\n\nfunction BrandMark({ brand, tone }: { brand: LogoMarqueeBrand; tone: LogoMarqueeTone }) {\n  if (!brand.icon && !brand.colorIcon) return null; // a name-only brand is its own wordmark\n  if (tone === \"color\" && brand.colorIcon) {\n    return (\n      // oxlint-disable-next-line nextjs/no-img-element -- brand SVGs come from the consumer and may be on any origin, which next/image would reject\n      <img\n        className={cn(markClass, \"object-contain\")}\n        src={brand.colorIcon}\n        alt=\"\"\n        width={22}\n        height={22}\n        draggable={false}\n      />\n    );\n  }\n  return (\n    <span\n      className={cn(markClass, \"bg-current [mask-position:center] [mask-repeat:no-repeat] [mask-size:contain]\")}\n      style={{\n        maskImage: `url(\"${brand.icon}\")`,\n        WebkitMaskImage: `url(\"${brand.icon}\")`,\n        color: tone === \"color\" ? brand.color : undefined,\n      }}\n      aria-hidden=\"true\"\n    />\n  );\n}\n\nfunction BrandList({\n  brands,\n  tone,\n  duplicate = false,\n}: {\n  brands: LogoMarqueeBrand[];\n  tone: LogoMarqueeTone;\n  duplicate?: boolean;\n}) {\n  return (\n    // Under reduced motion the row stops, so the copy that closes the loop goes away and the names wrap and centre.\n    <ul\n      className={cn(\n        \"m-0 flex flex-none list-none items-center gap-x-14 p-0 pr-14 @max-[640px]/logos:gap-x-10 @max-[640px]/logos:pr-10\",\n        \"motion-reduce:flex-wrap motion-reduce:justify-center motion-reduce:gap-y-5 motion-reduce:px-6\",\n        duplicate && \"motion-reduce:hidden\",\n      )}\n      aria-label={duplicate ? undefined : \"Brands\"}\n      aria-hidden={duplicate || undefined}\n    >\n      {brands.map((brand, index) => (\n        // In color tone the marks carry the brand color and the names read at full contrast.\n        <li\n          className={cn(\n            \"inline-flex flex-none items-center gap-2.5 whitespace-nowrap\",\n            tone === \"color\" ? \"text-ink\" : \"text-ink-2\",\n          )}\n          key={`${brand.name}-${index}`}\n        >\n          <BrandMark brand={brand} tone={tone} />\n          <span className=\"text-lg/[inherit] font-medium tracking-body @max-[640px]/logos:text-base/[inherit]\">\n            {brand.name}\n          </span>\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport function LogoMarquee({\n  title = \"Products from one studio\",\n  description = \"From managed Postgres to a newsroom desk, each product has its own people and its own interface.\",\n  brands = exampleBrands,\n  tone = \"color\",\n}: LogoMarqueeProps) {\n  const titleId = useId();\n  const [paused, setPaused] = useState(false);\n\n  return (\n    <section\n      className=\"@container/logos w-full min-w-0 overflow-hidden rounded-surface border border-line bg-surface font-body text-sm leading-body tracking-body text-ink\"\n      data-tone={tone}\n      aria-labelledby={titleId}\n    >\n      <div className=\"grid grid-cols-[minmax(0,1fr)_minmax(0,0.72fr)] items-end gap-12 px-10 pt-10 pb-9 @max-[640px]/logos:grid-cols-1 @max-[640px]/logos:gap-3 @max-[640px]/logos:px-5 @max-[640px]/logos:pt-7 @max-[640px]/logos:pb-6\">\n        <h2\n          id={titleId}\n          className=\"m-0 max-w-[540px] font-display text-3xl leading-display font-normal tracking-display text-balance @max-[640px]/logos:text-2xl\"\n        >\n          {title}\n        </h2>\n        <p className=\"mx-0 mt-0 mb-[3px] max-w-[36ch] text-base leading-body text-pretty text-ink-2 @max-[640px]/logos:text-sm\">\n          {description}\n        </p>\n      </div>\n\n      <section\n        className=\"group/marquee relative overflow-hidden border-y border-line-soft py-8 [mask-image:linear-gradient(to_right,transparent,#000_7%,#000_93%,transparent)] @max-[640px]/logos:py-6\"\n        aria-label=\"Brands\"\n      >\n        {/* Hovering the row holds it still, so a name can be read. */}\n        <div\n          className={cn(\n            \"flex w-max animate-logo-marquee-travel will-change-transform group-hover/marquee:[animation-play-state:paused]\",\n            \"motion-reduce:w-auto motion-reduce:animate-none motion-reduce:justify-center motion-reduce:will-change-auto\",\n            paused && \"[animation-play-state:paused]\",\n          )}\n        >\n          <BrandList brands={brands} tone={tone} />\n          <BrandList brands={brands} tone={tone} duplicate />\n        </div>\n      </section>\n\n      <div className=\"flex items-center justify-end gap-4 px-4 py-2.5 @max-[640px]/logos:px-3 motion-reduce:hidden\">\n        <Button\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"text-ink-2 enabled:hover:text-ink-2\"\n          aria-label={paused ? \"Play logo motion\" : \"Pause logo motion\"}\n          onClick={() => setPaused((value) => !value)}\n        >\n          {paused ? (\n            <>\n              <Play size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n              Play\n            </>\n          ) : (\n            <>\n              <Pause size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n              Pause\n            </>\n          )}\n        </Button>\n        <span className=\"sr-only\" aria-live=\"polite\">\n          {paused ? \"Motion paused\" : \"\"}\n        </span>\n      </div>\n    </section>\n  );\n}\n\nexport default LogoMarquee;\n",
      "type": "registry:component",
      "target": "@components/iris/blocks/logo-marquee/logo-marquee.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/logo-marquee",
  "meta": {
    "kind": "block",
    "group": "Blocks",
    "export": "LogoMarquee",
    "docs": "https://irisui.dev/components/logo-marquee"
  }
}
