{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "logos",
  "title": "Logo Clouds",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/logos/logos-01.component.ts",
      "content": "// Static trusted-by logo grid: \"Trusted by teams at\" eyebrow over a responsive\n// grid of six grayscale wordmarks (2 / 3 / 6 columns by breakpoint). Each logo is\n// an inline SVG on currentColor so the muted-foreground to foreground hover swap\n// works. Port of the Vue/React block 1:1 — swap the SVGs for real customer logos.\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core'\nimport { cn } from '@/lib/utils'\n\ninterface CustomerLogo {\n  label: string\n  d: string\n}\n\n// Sample logo set sourced from simpleicons.org (MIT). Swap these paths and\n// labels for your own customer logos before shipping.\nconst LOGOS: CustomerLogo[] = [\n  {\n    label: 'Vercel',\n    d: 'm12 1.608 12 20.784H0Z',\n  },\n  {\n    label: 'Stripe',\n    d: 'M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z',\n  },\n  {\n    label: 'Linear',\n    d: 'M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z',\n  },\n  {\n    label: 'Notion',\n    d: 'M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z',\n  },\n  {\n    label: 'Figma',\n    d: 'M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z',\n  },\n  {\n    label: 'Loom',\n    d: 'M24 10.665h-7.018l6.078-3.509-1.335-2.312-6.078 3.509 3.508-6.077L16.843.94l-3.508 6.077V0h-2.67v7.018L7.156.94 4.844 2.275l3.509 6.077-6.078-3.508L.94 7.156l6.078 3.509H0v2.67h7.017L.94 16.844l1.335 2.313 6.077-3.508-3.509 6.077 2.312 1.335 3.509-6.078V24h2.67v-7.017l3.508 6.077 2.312-1.335-3.509-6.078 6.078 3.509 1.335-2.313-6.077-3.508h7.017v-2.67H24zm-12 4.966a3.645 3.645 0 1 1 0-7.29 3.645 3.645 0 0 1 0 7.29z',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-logos-01, [ui-logos-01]',\n  standalone: true,\n  // The block renders its own root <section>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  template: `\n    <section data-slot=\"logos-01\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-6xl px-6 py-14\">\n        <p class=\"text-muted-foreground text-center text-xs font-medium tracking-widest uppercase\">\n          Trusted by teams at\n        </p>\n        <div\n          class=\"mt-10 grid grid-cols-2 items-center justify-items-center gap-x-10 gap-y-8 sm:grid-cols-3 lg:grid-cols-6\"\n        >\n          @for (logo of logos; track logo.label) {\n            <div class=\"text-foreground/55 hover:text-foreground inline-flex items-center gap-2 transition-colors\">\n              <svg\n                viewBox=\"0 0 24 24\"\n                class=\"size-5 shrink-0\"\n                fill=\"currentColor\"\n                role=\"img\"\n                [attr.aria-label]=\"logo.label\"\n              >\n                <title>{{ logo.label }}</title>\n                <path [attr.d]=\"logo.d\" />\n              </svg>\n              <span class=\"text-base font-semibold tracking-tight\">{{ logo.label }}</span>\n            </div>\n          }\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiLogos01Component {\n  protected readonly logos = LOGOS\n\n  @Input('class') className?: string\n\n  get rootClass(): string {\n    return cn('bg-muted/30 border-y', this.className)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/logos-01.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/logos/logos-02.component.ts",
      "content": "// Single-row infinite logo marquee. The track renders the brand set twice\n// back-to-back and translates -50% over 30s so the loop seam is invisible; edges\n// fade via mask-image, hover pauses, prefers-reduced-motion disables. Port 1:1.\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core'\nimport { cn } from '@/lib/utils'\n\ninterface CustomerLogo {\n  label: string\n  d: string\n}\n\n// Single-row infinite marquee. The track is rendered twice back-to-back\n// and translated -50% over the duration so the seam between the two\n// copies is invisible.\nconst LOGOS: CustomerLogo[] = [\n  {\n    label: 'Vercel',\n    d: 'm12 1.608 12 20.784H0Z',\n  },\n  {\n    label: 'Stripe',\n    d: 'M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z',\n  },\n  {\n    label: 'Linear',\n    d: 'M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z',\n  },\n  {\n    label: 'Notion',\n    d: 'M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z',\n  },\n  {\n    label: 'Figma',\n    d: 'M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z',\n  },\n  {\n    label: 'Loom',\n    d: 'M24 10.665h-7.018l6.078-3.509-1.335-2.312-6.078 3.509 3.508-6.077L16.843.94l-3.508 6.077V0h-2.67v7.018L7.156.94 4.844 2.275l3.509 6.077-6.078-3.508L.94 7.156l6.078 3.509H0v2.67h7.017L.94 16.844l1.335 2.313 6.077-3.508-3.509 6.077 2.312 1.335 3.509-6.078V24h2.67v-7.017l3.508 6.077 2.312-1.335-3.509-6.078 6.078 3.509 1.335-2.313-6.077-3.508h7.017v-2.67H24zm-12 4.966a3.645 3.645 0 1 1 0-7.29 3.645 3.645 0 0 1 0 7.29z',\n  },\n]\n\nconst MARQUEE_STYLES = `\n@keyframes logos-marquee {\n  from {\n    transform: translateX(0);\n  }\n  to {\n    transform: translateX(-50%);\n  }\n}\n.logos02-track {\n  animation: logos-marquee 30s linear infinite;\n}\n.group:hover .logos02-track {\n  animation-play-state: paused;\n}\n@media (prefers-reduced-motion: reduce) {\n  .logos02-track {\n    animation: none !important;\n  }\n}\n`\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-logos-02, [ui-logos-02]',\n  standalone: true,\n  // The block renders its own root <section>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  styles: [MARQUEE_STYLES],\n  template: `\n    <section data-slot=\"logos-02\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-6xl px-6 py-14\">\n        <p class=\"text-muted-foreground mb-10 text-center text-xs font-medium tracking-widest uppercase\">\n          Trusted by teams at\n        </p>\n\n        <!-- Marquee viewport: edges fade out via mask-image so logos appear/disappear softly -->\n        <div\n          class=\"group relative overflow-hidden\"\n          style=\"\n            mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);\n            -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);\n          \"\n        >\n          <!-- Track: brands rendered twice so the -50% slide loops directly -->\n          <div class=\"logos02-track flex w-max gap-12 will-change-transform\">\n            @for (logo of doubled; track $index) {\n              <div\n                class=\"text-foreground/55 hover:text-foreground inline-flex shrink-0 items-center gap-2 transition-colors\"\n              >\n                <svg\n                  viewBox=\"0 0 24 24\"\n                  class=\"size-5 shrink-0\"\n                  fill=\"currentColor\"\n                  role=\"img\"\n                  [attr.aria-label]=\"logo.label\"\n                >\n                  <title>{{ logo.label }}</title>\n                  <path [attr.d]=\"logo.d\" />\n                </svg>\n                <span class=\"text-base font-semibold tracking-tight\">{{ logo.label }}</span>\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiLogos02Component {\n  protected readonly doubled = [...LOGOS, ...LOGOS]\n\n  @Input('class') className?: string\n\n  get rootClass(): string {\n    return cn('bg-muted/30 border-y', this.className)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/logos-02.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/logos/logos-03.component.ts",
      "content": "// Three-row logo marquee with alternating directions and offset speeds (32s left,\n// 26s right, 38s left) so the eye never locks onto one rhythm. Each track renders\n// twice for a seamless -50% loop; hover pauses all rows. Port of the Vue block 1:1.\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core'\nimport { cn } from '@/lib/utils'\n\ninterface CustomerLogo {\n  label: string\n  d: string\n}\n\n// Three rows of customer logos sliding in alternating directions. Each\n// row's track is rendered twice and translated by 50% so the loop has no\n// visible seam; speeds are slightly offset between rows so the eye\n// doesn't lock onto a single rhythm.\nconst ROW_1: CustomerLogo[] = [\n  { label: 'Vercel', d: 'm12 1.608 12 20.784H0Z' },\n  {\n    label: 'Stripe',\n    d: 'M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z',\n  },\n  {\n    label: 'Linear',\n    d: 'M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z',\n  },\n  {\n    label: 'Notion',\n    d: 'M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z',\n  },\n  {\n    label: 'Figma',\n    d: 'M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441c0 2.503-2.047 4.539-4.563 4.539zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z',\n  },\n  {\n    label: 'Loom',\n    d: 'M24 10.665h-7.018l6.078-3.509-1.335-2.312-6.078 3.509 3.508-6.077L16.843.94l-3.508 6.077V0h-2.67v7.018L7.156.94 4.844 2.275l3.509 6.077-6.078-3.508L.94 7.156l6.078 3.509H0v2.67h7.017L.94 16.844l1.335 2.313 6.077-3.508-3.509 6.077 2.312 1.335 3.509-6.078V24h2.67v-7.017l3.508 6.077 2.312-1.335-3.509-6.078 6.078 3.509 1.335-2.313-6.077-3.508h7.017v-2.67H24zm-12 4.966a3.645 3.645 0 1 1 0-7.29 3.645 3.645 0 0 1 0 7.29z',\n  },\n]\n\nconst ROW_2: CustomerLogo[] = [\n  {\n    label: 'React',\n    d: 'M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z',\n  },\n  {\n    label: 'Next.js',\n    d: 'M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z',\n  },\n  {\n    label: 'Vue.js',\n    d: 'M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z',\n  },\n  {\n    label: 'Tailwind',\n    d: 'M12.001,4.8c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 C13.666,10.618,15.027,12,18.001,12c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C16.337,6.182,14.976,4.8,12.001,4.8z M6.001,12c-3.2,0-5.2,1.6-6,4.8c1.2-1.6,2.6-2.2,4.2-1.8c0.913,0.228,1.565,0.89,2.288,1.624 c1.177,1.194,2.538,2.576,5.512,2.576c3.2,0,5.2-1.6,6-4.8c-1.2,1.6-2.6,2.2-4.2,1.8c-0.913-0.228-1.565-0.89-2.288-1.624 C10.337,13.382,8.976,12,6.001,12z',\n  },\n  {\n    label: 'Supabase',\n    d: 'M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z',\n  },\n  {\n    label: 'GitHub',\n    d: 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',\n  },\n]\n\nconst ROW_3: CustomerLogo[] = [\n  {\n    label: 'Anthropic',\n    d: 'M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z',\n  },\n  {\n    label: 'Sentry',\n    d: 'M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z',\n  },\n  {\n    label: 'PlanetScale',\n    d: 'M0 12C0 5.373 5.373 0 12 0c4.873 0 9.067 2.904 10.947 7.077l-15.87 15.87a11.981 11.981 0 0 1-1.935-1.099L14.99 12H12l-8.485 8.485A11.962 11.962 0 0 1 0 12Zm12.004 12L24 12.004C23.998 18.628 18.628 23.998 12.004 24Z',\n  },\n  {\n    label: 'Render',\n    d: 'M18.263.007c-3.121-.147-5.744 2.109-6.192 5.082-.018.138-.045.272-.067.405-.696 3.703-3.936 6.507-7.827 6.507-1.388 0-2.691-.356-3.825-.979a.2024.2024 0 0 0-.302.178V24H12v-8.999c0-1.656 1.338-3 2.987-3h2.988c3.382 0 6.103-2.817 5.97-6.244-.12-3.084-2.61-5.603-5.682-5.75',\n  },\n  {\n    label: 'Railway',\n    d: 'M.113 10.27A13.026 13.026 0 000 11.48h18.23c-.064-.125-.15-.237-.235-.347-3.117-4.027-4.793-3.677-7.19-3.78-.8-.034-1.34-.048-4.524-.048-1.704 0-3.555.005-5.358.01-.234.63-.459 1.24-.567 1.737h9.342v1.216H.113v.002zm18.26 2.426H.009c.02.326.05.645.094.961h16.955c.754 0 1.179-.429 1.315-.96zm-17.318 4.28s2.81 6.902 10.93 7.024c4.855 0 9.027-2.883 10.92-7.024H1.056zM11.988 0C7.5 0 3.593 2.466 1.531 6.108l4.75-.005v-.002c3.71 0 3.849.016 4.573.047l.448.016c1.563.052 3.485.22 4.996 1.364.82.621 2.007 1.99 2.712 2.965.654.902.842 1.94.396 2.934-.408.914-1.289 1.458-2.353 1.458H.391s.099.42.249.886h22.748A12.026 12.026 0 0024 12.005C24 5.377 18.621 0 11.988 0z',\n  },\n  {\n    label: 'Stripe',\n    d: 'M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z',\n  },\n]\n\nconst MARQUEE_STYLES = `\n@keyframes logos-marquee-left {\n  from {\n    transform: translateX(0);\n  }\n  to {\n    transform: translateX(-50%);\n  }\n}\n@keyframes logos-marquee-right {\n  from {\n    transform: translateX(-50%);\n  }\n  to {\n    transform: translateX(0);\n  }\n}\n.logos03-left-32 {\n  animation: logos-marquee-left 32s linear infinite;\n}\n.logos03-right-26 {\n  animation: logos-marquee-right 26s linear infinite;\n}\n.logos03-left-38 {\n  animation: logos-marquee-left 38s linear infinite;\n}\n.group:hover .logos03-track {\n  animation-play-state: paused;\n}\n@media (prefers-reduced-motion: reduce) {\n  .logos03-track {\n    animation: none !important;\n  }\n}\n`\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-logos-03, [ui-logos-03]',\n  standalone: true,\n  // The block renders its own root <section>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  styles: [MARQUEE_STYLES],\n  template: `\n    <section data-slot=\"logos-03\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-6xl px-6 py-14\">\n        <p class=\"text-muted-foreground mb-10 text-center text-xs font-medium tracking-widest uppercase\">\n          Powering teams everywhere\n        </p>\n\n        <div\n          class=\"group relative space-y-5 overflow-hidden\"\n          style=\"\n            mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);\n            -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);\n          \"\n        >\n          <!-- Row 1: scrolls left, 32s -->\n          <div class=\"logos03-track logos03-left-32 flex w-max gap-12 will-change-transform\">\n            @for (logo of doubledRow1; track $index) {\n              <div\n                class=\"text-foreground/55 hover:text-foreground inline-flex shrink-0 items-center gap-2 transition-colors\"\n              >\n                <svg\n                  viewBox=\"0 0 24 24\"\n                  class=\"size-5 shrink-0\"\n                  fill=\"currentColor\"\n                  role=\"img\"\n                  [attr.aria-label]=\"logo.label\"\n                >\n                  <title>{{ logo.label }}</title>\n                  <path [attr.d]=\"logo.d\" />\n                </svg>\n                <span class=\"text-base font-semibold tracking-tight\">{{ logo.label }}</span>\n              </div>\n            }\n          </div>\n\n          <!-- Row 2: scrolls right, 26s -->\n          <div class=\"logos03-track logos03-right-26 flex w-max gap-12 will-change-transform\">\n            @for (logo of doubledRow2; track $index) {\n              <div\n                class=\"text-foreground/55 hover:text-foreground inline-flex shrink-0 items-center gap-2 transition-colors\"\n              >\n                <svg\n                  viewBox=\"0 0 24 24\"\n                  class=\"size-5 shrink-0\"\n                  fill=\"currentColor\"\n                  role=\"img\"\n                  [attr.aria-label]=\"logo.label\"\n                >\n                  <title>{{ logo.label }}</title>\n                  <path [attr.d]=\"logo.d\" />\n                </svg>\n                <span class=\"text-base font-semibold tracking-tight\">{{ logo.label }}</span>\n              </div>\n            }\n          </div>\n\n          <!-- Row 3: scrolls left, 38s (slowest) -->\n          <div class=\"logos03-track logos03-left-38 flex w-max gap-12 will-change-transform\">\n            @for (logo of doubledRow3; track $index) {\n              <div\n                class=\"text-foreground/55 hover:text-foreground inline-flex shrink-0 items-center gap-2 transition-colors\"\n              >\n                <svg\n                  viewBox=\"0 0 24 24\"\n                  class=\"size-5 shrink-0\"\n                  fill=\"currentColor\"\n                  role=\"img\"\n                  [attr.aria-label]=\"logo.label\"\n                >\n                  <title>{{ logo.label }}</title>\n                  <path [attr.d]=\"logo.d\" />\n                </svg>\n                <span class=\"text-base font-semibold tracking-tight\">{{ logo.label }}</span>\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiLogos03Component {\n  protected readonly doubledRow1 = [...ROW_1, ...ROW_1]\n  protected readonly doubledRow2 = [...ROW_2, ...ROW_2]\n  protected readonly doubledRow3 = [...ROW_3, ...ROW_3]\n\n  @Input('class') className?: string\n\n  get rootClass(): string {\n    return cn('bg-muted/30 border-y', this.className)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/logos-03.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/logos/logo-cloud-categorized-grid.component.ts",
      "content": "// Categorized customer logo grid: vertical filter tabs over a card wall of\n// benchmark customers, each card opening a testimonial modal with quote,\n// author, and verified-production badge. Port of the Vue/React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  ArrowRight,\n  Brain,\n  Building2,\n  Cloud,\n  Globe,\n  LucideAngularModule,\n  Quote,\n  ShieldCheck,\n  Terminal,\n  X,\n  type LucideIconData,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\n\nexport type CustomerCategory = 'cloud' | 'devtools' | 'fintech' | 'ai'\nexport type VerticalCategory = 'all' | CustomerCategory\n\nexport interface CustomerEntity {\n  id: string\n  name: string\n  category: CustomerCategory\n  tier: string\n  scale: string\n  quote: string\n  author: string\n  title: string\n}\n\nconst customerList: CustomerEntity[] = [\n  // Infrastructure & Cloud\n  {\n    id: 'cloudflare',\n    name: 'Cloudflare',\n    category: 'cloud',\n    tier: 'Enterprise Scale',\n    scale: '300+ Edge Data Centers',\n    quote:\n      'UIPKGE allowed us to standardize our internal console UI across 40+ engineering squads with zero bundle overhead.',\n    author: 'Alex K.',\n    title: 'Principal Architect, Cloudflare Workers',\n  },\n  {\n    id: 'flyio',\n    name: 'Fly.io',\n    category: 'cloud',\n    tier: 'Core Partner',\n    scale: '50k+ Compute Machines',\n    quote: 'We copy the raw SFC components and style them directly to match our brand — no package upgrades to fight.',\n    author: 'Sam T.',\n    title: 'Lead Frontend Engineer',\n  },\n  {\n    id: 'railway',\n    name: 'Railway',\n    category: 'cloud',\n    tier: 'Growth Tier',\n    scale: '1.5M Deployed Services',\n    quote:\n      'AST-level source control gives our team complete confidence without ever fearing breaking upstream package releases.',\n    author: 'Elena R.',\n    title: 'VP of Product Experience',\n  },\n\n  // Developer Tooling\n  {\n    id: 'vercel',\n    name: 'Vercel',\n    category: 'devtools',\n    tier: 'Design Pioneer',\n    scale: '100M+ Monthly Visitors',\n    quote:\n      'The level of keyboard ergonomics, focus ring layering, and spring physics in UIPKGE meets the highest craft benchmarks.',\n    author: 'Rauno F.',\n    title: 'Design Engineering Advisor',\n  },\n  {\n    id: 'supabase',\n    name: 'Supabase',\n    category: 'devtools',\n    tier: 'Ecosystem Partner',\n    scale: '1M+ Databases Provisioned',\n    quote: 'Dual-framework parity means we can ship both Vue and React examples for our developer docs in minutes.',\n    author: 'Ant W.',\n    title: 'Co-Founder & CEO',\n  },\n  {\n    id: 'biome',\n    name: 'BiomeJS',\n    category: 'devtools',\n    tier: 'Open Source',\n    scale: '20M+ Weekly Downloads',\n    quote: 'The cleanest AST code patterns in any modern UI registry. 100% type-safe.',\n    author: 'Nico M.',\n    title: 'Core Maintainer',\n  },\n\n  // Fintech & Security\n  {\n    id: 'stripe',\n    name: 'Stripe',\n    category: 'fintech',\n    tier: 'Global Infrastructure',\n    scale: '$1T+ Payment Volume',\n    quote:\n      'Zero dependency bloat guarantees compliance with strict internal security and dependency auditing requirements.',\n    author: 'Marcus H.',\n    title: 'Staff Security Engineer',\n  },\n  {\n    id: 'ramp',\n    name: 'Ramp',\n    category: 'fintech',\n    tier: 'Scale Unicorn',\n    scale: '$10B+ Managed Spend',\n    quote: 'The dense KPI tiles and transaction workbenches are plug-and-play masterpieces.',\n    author: 'David P.',\n    title: 'Director of Frontend Engineering',\n  },\n  {\n    id: 'snyk',\n    name: 'Snyk Security',\n    category: 'fintech',\n    tier: 'Enterprise SecOps',\n    scale: '2.5M+ Repos Scanned',\n    quote: 'Owning the source code completely removes transitive dependency CVE vulnerabilities.',\n    author: 'Sarah L.',\n    title: 'Head of Developer Relations',\n  },\n\n  // AI & Machine Learning\n  {\n    id: 'replicate',\n    name: 'Replicate',\n    category: 'ai',\n    tier: 'AI Infrastructure',\n    scale: '500M+ Model Inferences',\n    quote: 'We built our interactive prompt tester in an afternoon using UIPKGE terminal and slider primitives.',\n    author: 'Ben F.',\n    title: 'Co-Founder',\n  },\n  {\n    id: 'modal',\n    name: 'Modal Labs',\n    category: 'ai',\n    tier: 'GPU Cloud',\n    scale: '10k+ GPU Clusters',\n    quote: 'The tactile feel and speed of the UI blocks matches the lightning speed of our Python container runs.',\n    author: 'Erik B.',\n    title: 'Co-Founder & CTO',\n  },\n  {\n    id: 'mistral',\n    name: 'Mistral AI',\n    category: 'ai',\n    tier: 'Frontier AI',\n    scale: 'Global Deployment',\n    quote: 'Phenomenal attention to typographic hierarchy and clean Geist font metrics.',\n    author: 'Arthur M.',\n    title: 'Co-Founder',\n  },\n]\n\ninterface CategoryMeta {\n  key: VerticalCategory\n  label: string\n  icon: LucideIconData\n}\n\nconst categoryOptions: CategoryMeta[] = [\n  { key: 'all', label: 'All Industries', icon: Globe },\n  { key: 'cloud', label: 'Cloud & Infra', icon: Cloud },\n  { key: 'devtools', label: 'DevTools & DX', icon: Terminal },\n  { key: 'fintech', label: 'Fintech & Security', icon: ShieldCheck },\n  { key: 'ai', label: 'AI & GPU Compute', icon: Brain },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-logo-cloud-categorized-grid, [ui-logo-cloud-categorized-grid]',\n  standalone: true,\n  // The block renders its own root <section>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n  ],\n  template: `\n    <section data-slot=\"logo-cloud-categorized-grid\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-7xl space-y-12\">\n        <!-- Section Header -->\n        <div class=\"mx-auto max-w-3xl space-y-4 text-center\">\n          <span ui-badge variant=\"secondary\" class=\"gap-1.5 px-3 py-1 font-mono text-xs shadow-xs\">\n            <lucide-icon [img]=\"Building2\" class=\"text-primary size-3.5\" />\n            Industry Benchmark Customers\n          </span>\n          <h2 class=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl\">\n            Powering the world's most rigorous engineering organizations.\n          </h2>\n          <p class=\"text-muted-foreground text-base\">\n            From frontier AI labs to global payment platforms, see how top tier engineering teams build on UIPKGE.\n          </p>\n\n          <!-- Vertical Category Filter Tabs -->\n          <div class=\"flex flex-wrap items-center justify-center gap-1 pt-4\">\n            @for (option of categories; track option.key) {\n              <button\n                type=\"button\"\n                class=\"flex items-center gap-1.5 rounded-lg border px-3 py-1.5 font-mono text-xs transition-colors\"\n                [class]=\"categoryTabClass(option.key)\"\n                [attr.aria-pressed]=\"selectedCategory() === option.key\"\n                (click)=\"selectedCategory.set(option.key)\"\n              >\n                <lucide-icon [img]=\"option.icon\" class=\"size-3.5\" />\n                <span>{{ option.label }}</span>\n              </button>\n            }\n          </div>\n        </div>\n\n        <!-- Categorized Customer Grid -->\n        <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3\">\n          @for (customer of filteredCustomers(); track customer.id) {\n            <div\n              ui-card\n              role=\"button\"\n              tabindex=\"0\"\n              [attr.aria-label]=\"'Read quote from ' + customer.name\"\n              class=\"border-border bg-card/90 hover:border-primary/40 group flex cursor-pointer flex-col justify-between space-y-4 rounded-2xl p-5 shadow-sm transition-colors hover:shadow-md\"\n              (click)=\"activeModalCustomer.set(customer)\"\n              (keydown.enter)=\"activeModalCustomer.set(customer)\"\n              (keydown.space)=\"activeModalCustomer.set(customer)\"\n            >\n              <!-- Card Top: Name & Tier Pill -->\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2.5\">\n                  <div\n                    class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg font-mono text-xs font-bold\"\n                  >\n                    {{ customer.name.charAt(0) }}\n                  </div>\n                  <div>\n                    <h3 class=\"text-foreground group-hover:text-primary font-mono text-sm font-bold transition-colors\">\n                      {{ customer.name }}\n                    </h3>\n                    <p class=\"text-muted-foreground text-xs\">{{ customer.scale }}</p>\n                  </div>\n                </div>\n                <span ui-badge variant=\"outline\" class=\"font-mono text-xs capitalize\">\n                  {{ customer.tier }}\n                </span>\n              </div>\n\n              <!-- Quote Excerpt -->\n              <p class=\"text-muted-foreground line-clamp-2 text-xs leading-relaxed italic\">\n                &ldquo;{{ customer.quote }}&rdquo;\n              </p>\n\n              <!-- Card Footer: Author & Read Case Study -->\n              <div class=\"border-border/60 flex items-center justify-between border-t pt-2 text-xs\">\n                <span class=\"text-muted-foreground font-mono text-xs\">{{ customer.author }}</span>\n                <span class=\"text-primary flex items-center gap-1 text-xs font-medium group-hover:underline\">\n                  <span>Read quote</span>\n                  <lucide-icon [img]=\"ArrowRight\" class=\"size-3\" />\n                </span>\n              </div>\n            </div>\n          }\n        </div>\n\n        <!-- Testimonial Detail Dialog Modal -->\n        @if (activeModalCustomer(); as active) {\n          <div\n            class=\"bg-background/80 fixed inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-md sm:p-6\"\n            role=\"dialog\"\n            aria-modal=\"true\"\n            [attr.aria-label]=\"active.name + ' testimonial'\"\n            (click)=\"closeModal()\"\n          >\n            <div\n              class=\"bg-card border-border animate-in fade-in-0 zoom-in-95 relative w-full max-w-xl space-y-6 rounded-2xl border p-6 text-left shadow-sm duration-200 sm:p-8\"\n              (click)=\"$event.stopPropagation()\"\n            >\n              <div class=\"border-border flex items-center justify-between border-b pb-4\">\n                <div class=\"flex items-center gap-3\">\n                  <div\n                    class=\"bg-primary/10 text-primary flex size-10 items-center justify-center rounded-xl font-mono text-sm font-bold\"\n                  >\n                    {{ active.name.charAt(0) }}\n                  </div>\n                  <div>\n                    <h3 class=\"text-foreground font-mono text-base font-bold\">{{ active.name }}</h3>\n                    <p class=\"text-muted-foreground font-mono text-xs\">{{ active.scale }}</p>\n                  </div>\n                </div>\n                <button\n                  ui-button\n                  type=\"button\"\n                  variant=\"ghost\"\n                  size=\"icon-sm\"\n                  aria-label=\"Close testimonial\"\n                  class=\"text-muted-foreground hover:text-foreground rounded-md p-1\"\n                  (click)=\"closeModal()\"\n                >\n                  <lucide-icon [img]=\"X\" class=\"size-4\" />\n                </button>\n              </div>\n\n              <div class=\"space-y-4\">\n                <lucide-icon [img]=\"Quote\" class=\"text-primary/40 size-6\" />\n                <p class=\"text-foreground text-sm leading-relaxed sm:text-base\">&ldquo;{{ active.quote }}&rdquo;</p>\n              </div>\n\n              <div\n                class=\"border-border bg-muted/20 -mx-6 -mb-6 flex items-center justify-between rounded-b-2xl border-t p-4 px-6 pt-4 sm:-mx-8 sm:-mb-8 sm:px-8\"\n              >\n                <div>\n                  <p class=\"text-foreground text-xs font-bold\">{{ active.author }}</p>\n                  <p class=\"text-muted-foreground font-mono text-xs\">{{ active.title }}</p>\n                </div>\n                <span ui-badge variant=\"outline\" class=\"border-success/20 bg-success/10 text-success font-mono text-xs\">\n                  Verified Production Customer\n                </span>\n              </div>\n            </div>\n          </div>\n        }\n      </div>\n    </section>\n  `,\n})\nexport class UiLogoCloudCategorizedGridComponent {\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Building2 = Building2\n  protected readonly Quote = Quote\n  protected readonly X = X\n\n  protected readonly categories = categoryOptions\n\n  @Input('class') className?: string\n  @Input() set initialCategory(v: VerticalCategory) {\n    this.selectedCategory.set(v ?? 'all')\n  }\n\n  readonly selectedCategory = signal<VerticalCategory>('all')\n  readonly activeModalCustomer = signal<CustomerEntity | null>(null)\n\n  readonly filteredCustomers = computed(() => {\n    const selected = this.selectedCategory()\n    if (selected === 'all') return customerList\n    return customerList.filter((c) => c.category === selected)\n  })\n\n  get rootClass(): string {\n    return cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', this.className)\n  }\n\n  categoryTabClass(key: VerticalCategory): string {\n    return cn(\n      this.selectedCategory() === key\n        ? 'border-primary bg-primary text-primary-foreground font-semibold shadow-xs'\n        : 'border-border bg-card/60 text-muted-foreground hover:text-foreground hover:bg-muted',\n    )\n  }\n\n  closeModal(): void {\n    this.activeModalCustomer.set(null)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/logo-cloud-categorized-grid.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/logos/logo-cloud-grayscale.component.ts",
      "content": "// Grayscale customer logo wall: muted wordmarks that resolve to full contrast\n// on hover, under a headline count and a segment line. Port of the Vue block 1:1.\nimport { ChangeDetectionStrategy, Component, Input } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\nconst customers: string[] = [\n  'Northwind',\n  'Halden',\n  'Verity',\n  'Calder',\n  'Ridgeway',\n  'Ashford',\n  'Brightmoor',\n  'Kesteven',\n  'Marlowe',\n  'Thornbury',\n  'Wexley',\n  'Alderton',\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-logo-cloud-grayscale, [ui-logo-cloud-grayscale]',\n  standalone: true,\n  // The block renders its own root <section>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [UiBadgeComponent, UiButtonComponent, UiSeparatorComponent],\n  template: `\n    <section data-slot=\"logo-cloud-grayscale\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-6xl px-6 py-16\">\n        <div class=\"text-center\">\n          <span ui-badge variant=\"secondary\">Customers</span>\n          <p class=\"mt-4 text-lg font-medium\">Running the close at 240 companies</p>\n          <p class=\"text-muted-foreground mt-1.5 text-sm\">\n            Freight, healthcare, retail, and manufacturing · median 1,100 staff\n          </p>\n        </div>\n\n        <!-- Muted by default, full contrast on hover: the wall reads as texture\n             until someone looks for a specific name. -->\n        <ul class=\"mt-10 grid grid-cols-2 gap-x-8 gap-y-6 sm:grid-cols-3 lg:grid-cols-6\">\n          @for (customer of customers; track customer) {\n            <li class=\"flex items-center justify-center\">\n              <span\n                class=\"text-muted-foreground/60 hover:text-foreground cursor-default text-sm font-semibold tracking-[0.18em] uppercase transition-colors\"\n              >\n                {{ customer }}\n              </span>\n            </li>\n          }\n        </ul>\n\n        <ui-separator class=\"my-10\" />\n\n        <div class=\"flex flex-wrap items-center justify-center gap-4 text-center\">\n          <p class=\"text-muted-foreground text-sm\">Six of the ten largest UK freight forwarders.</p>\n          <button ui-button variant=\"link\" class=\"h-auto p-0 text-sm\">Read their stories</button>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiLogoCloudGrayscaleComponent {\n  protected readonly customers = customers\n\n  @Input('class') className?: string\n\n  get rootClass(): string {\n    return cn('bg-background', this.className)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/logo-cloud-grayscale.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/logos/logo-ticker-infinite.component.ts",
      "content": "// Infinite partner logo ticker: category filter pills, pause / speed controls,\n// and a live hover telemetry card over a seamless double-rendered track.\n// Port of the Vue/React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport { LucideAngularModule, Pause, Play } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\n\nexport type TickerCategory = 'devtools' | 'fintech' | 'enterprise'\nexport type TickerCategoryFilter = 'all' | TickerCategory\n\nexport interface BrandPartner {\n  id: string\n  name: string\n  category: TickerCategory\n  role: string\n  metric: string\n  region: string\n}\n\nconst partners: BrandPartner[] = [\n  {\n    id: 'vercel',\n    name: 'Vercel Edge',\n    category: 'devtools',\n    role: 'Global Edge Ingestion',\n    metric: '99.95% SLA',\n    region: 'Global',\n  },\n  {\n    id: 'supabase',\n    name: 'Supabase Postgres',\n    category: 'devtools',\n    role: 'Real-Time Replication',\n    metric: '1.2M QPS',\n    region: 'US-East',\n  },\n  {\n    id: 'linear',\n    name: 'Linear Method',\n    category: 'enterprise',\n    role: 'Issue Sync Engine',\n    metric: '< 20ms Sync',\n    region: 'Global',\n  },\n  {\n    id: 'stripe',\n    name: 'Stripe Billing',\n    category: 'fintech',\n    role: 'Usage Metering Gateway',\n    metric: '$120M+ Processed',\n    region: 'Global',\n  },\n  {\n    id: 'resend',\n    name: 'Resend SMTP',\n    category: 'devtools',\n    role: 'Transactional Dispatch',\n    metric: '200M msgs/mo',\n    region: 'US-West',\n  },\n  {\n    id: 'cloudflare',\n    name: 'Cloudflare Workers',\n    category: 'devtools',\n    role: 'Zero-Trust Isolation',\n    metric: '300+ Cities',\n    region: 'Global',\n  },\n  {\n    id: 'planetscale',\n    name: 'PlanetScale DB',\n    category: 'enterprise',\n    role: 'Branching Schema',\n    metric: 'Zero Downtime',\n    region: 'EU-Central',\n  },\n  {\n    id: 'brex',\n    name: 'Brex Treasury',\n    category: 'fintech',\n    role: 'Ledger Reconciliation',\n    metric: 'Real-time API',\n    region: 'US-East',\n  },\n]\n\nconst categoryFilters: TickerCategoryFilter[] = ['all', 'devtools', 'fintech', 'enterprise']\n\nconst TICKER_STYLES = `\n@keyframes marquee {\n  0% {\n    transform: translateX(0%);\n  }\n  100% {\n    transform: translateX(-50%);\n  }\n}\n.animate-marquee {\n  animation: marquee 32s linear infinite;\n}\n.animate-marquee:hover {\n  animation-play-state: paused;\n}\n`\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-logo-ticker-infinite, [ui-logo-ticker-infinite]',\n  standalone: true,\n  // The block renders its own root <section>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent],\n  styles: [TICKER_STYLES],\n  template: `\n    <section data-slot=\"logo-ticker-infinite\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-7xl space-y-8 text-center\">\n        <!-- Section Header & Category Filter Controls -->\n        <div\n          class=\"border-border/60 flex flex-col items-center justify-between gap-4 border-b pb-6 text-left md:flex-row\"\n        >\n          <div class=\"space-y-1\">\n            <div class=\"flex items-center gap-2\">\n              <span class=\"bg-success size-2 rounded-full\"></span>\n              <p class=\"text-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                Production Verified Infrastructure\n              </p>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">\n              Trusted by modern engineering teams shipping high-throughput production workloads.\n            </p>\n          </div>\n\n          <!-- Filter & Control Buttons -->\n          <div class=\"flex flex-wrap items-center gap-2\">\n            <!-- Category Filter Pills -->\n            <div class=\"bg-background border-border flex items-center gap-1 rounded-lg border p-0.5\">\n              @for (cat of filters; track cat) {\n                <button\n                  type=\"button\"\n                  class=\"rounded-md px-2.5 py-1 font-mono text-xs capitalize transition-colors\"\n                  [class]=\"filterPillClass(cat)\"\n                  [attr.aria-pressed]=\"selectedCategory() === cat\"\n                  (click)=\"selectedCategory.set(cat)\"\n                >\n                  {{ cat }}\n                </button>\n              }\n            </div>\n\n            <!-- Marquee Speed / Pause Controls -->\n            <div class=\"flex items-center gap-1\">\n              <button\n                type=\"button\"\n                class=\"border-border bg-background text-muted-foreground hover:text-foreground rounded-lg border p-1.5 transition-colors\"\n                [attr.title]=\"isPaused() ? 'Resume Ticker' : 'Pause Ticker'\"\n                [attr.aria-label]=\"isPaused() ? 'Resume Ticker' : 'Pause Ticker'\"\n                (click)=\"isPaused.set(!isPaused())\"\n              >\n                @if (isPaused()) {\n                  <lucide-icon [img]=\"Play\" class=\"size-3.5 fill-current\" />\n                } @else {\n                  <lucide-icon [img]=\"Pause\" class=\"size-3.5\" />\n                }\n              </button>\n              <button\n                type=\"button\"\n                class=\"border-border bg-background text-muted-foreground hover:text-foreground rounded-lg border px-2 py-1 font-mono text-xs transition-colors\"\n                [attr.aria-label]=\"isFastSpeed() ? 'Normal ticker speed' : 'Double ticker speed'\"\n                (click)=\"isFastSpeed.set(!isFastSpeed())\"\n              >\n                {{ isFastSpeed() ? '2x' : '1x' }}\n              </button>\n            </div>\n          </div>\n        </div>\n\n        <!-- Infinite Marquee Track Viewport with Gradient Fade Edges -->\n        <div class=\"group relative overflow-hidden\">\n          <!-- Left & Right Gradient Shadows -->\n          <div\n            class=\"from-background via-background/80 pointer-events-none absolute inset-y-0 left-0 z-10 w-16 bg-gradient-to-r to-transparent sm:w-28\"\n          ></div>\n          <div\n            class=\"from-background via-background/80 pointer-events-none absolute inset-y-0 right-0 z-10 w-16 bg-gradient-to-l to-transparent sm:w-28\"\n          ></div>\n\n          <!-- Double Track Container for Seamless Infinite Scrolling -->\n          <div [class]=\"trackClass\">\n            <!-- Repeat partners array twice for infinite seamless loop -->\n            @for (item of doubledPartners(); track $index) {\n              <div\n                class=\"border-border bg-card/80 hover:bg-card hover:border-primary/50 relative flex shrink-0 cursor-pointer items-center gap-3 rounded-xl border px-4 py-2.5 shadow-xs backdrop-blur-md transition-colors duration-200\"\n                (mouseenter)=\"hoveredBrand.set(item)\"\n                (mouseleave)=\"hoveredBrand.set(null)\"\n              >\n                <div\n                  class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-lg font-mono text-xs font-bold\"\n                >\n                  {{ item.name.charAt(0) }}\n                </div>\n\n                <div class=\"text-left\">\n                  <p class=\"text-foreground font-mono text-xs font-semibold tracking-tight\">{{ item.name }}</p>\n                  <p class=\"text-muted-foreground text-xs\">{{ item.role }}</p>\n                </div>\n\n                <span\n                  ui-badge\n                  variant=\"outline\"\n                  class=\"border-success/20 bg-success/10 text-success ml-1 font-mono text-xs\"\n                >\n                  {{ item.metric }}\n                </span>\n              </div>\n            }\n          </div>\n        </div>\n\n        <!-- Live Hover Telemetry Card -->\n        @if (hoveredBrand(); as hovered) {\n          <div\n            class=\"border-border bg-card animate-in fade-in-0 mx-auto max-w-md rounded-xl border p-3 text-left shadow-lg duration-150\"\n          >\n            <div class=\"flex items-center justify-between font-mono text-xs\">\n              <span class=\"text-foreground font-bold\">{{ hovered.name }} Architecture Integration</span>\n              <span class=\"text-success\">{{ hovered.region }}</span>\n            </div>\n            <p class=\"text-muted-foreground mt-1 text-xs\">\n              Connected through {{ hovered.role }} &bull; Monitored with {{ hovered.metric }} verified telemetry.\n            </p>\n          </div>\n        }\n      </div>\n    </section>\n  `,\n})\nexport class UiLogoTickerInfiniteComponent {\n  protected readonly Pause = Pause\n  protected readonly Play = Play\n\n  protected readonly filters = categoryFilters\n\n  @Input('class') className?: string\n  @Input() set initialCategory(v: TickerCategoryFilter) {\n    this.selectedCategory.set(v ?? 'all')\n  }\n\n  readonly selectedCategory = signal<TickerCategoryFilter>('all')\n  readonly isPaused = signal(false)\n  readonly isFastSpeed = signal(false)\n  readonly hoveredBrand = signal<BrandPartner | null>(null)\n\n  readonly filteredPartners = computed(() => {\n    const selected = this.selectedCategory()\n    if (selected === 'all') return partners\n    return partners.filter((p) => p.category === selected)\n  })\n\n  readonly doubledPartners = computed(() => {\n    const list = this.filteredPartners()\n    return [...list, ...list]\n  })\n\n  get rootClass(): string {\n    return cn(\n      'border-border/80 bg-muted/20 relative overflow-hidden border-y px-4 py-14 sm:px-6 sm:py-20 lg:px-8',\n      this.className,\n    )\n  }\n\n  filterPillClass(cat: TickerCategoryFilter): string {\n    return cn(\n      this.selectedCategory() === cat\n        ? 'bg-primary text-primary-foreground font-semibold shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get trackClass(): string {\n    return cn(\n      'flex w-max items-center gap-4 py-3',\n      !this.isPaused() && 'animate-marquee',\n      this.isFastSpeed() ? '[animation-duration:15s]' : '[animation-duration:32s]',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/logo-ticker-infinite.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/logos/index.ts",
      "content": "export { UiLogos01Component } from './logos-01.component'\nexport { UiLogos02Component } from './logos-02.component'\nexport { UiLogos03Component } from './logos-03.component'\nexport {\n  UiLogoCloudCategorizedGridComponent,\n  type CustomerCategory,\n  type CustomerEntity,\n  type VerticalCategory,\n} from './logo-cloud-categorized-grid.component'\nexport { UiLogoCloudGrayscaleComponent } from './logo-cloud-grayscale.component'\nexport {\n  UiLogoTickerInfiniteComponent,\n  type BrandPartner,\n  type TickerCategory,\n  type TickerCategoryFilter,\n} from './logo-ticker-infinite.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/logos/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Logo-cloud section set: a static trusted-by grid, single- and tri-row infinite marquees, a categorized filter grid with testimonial modal, a grayscale-hover wall, and a ticker with sector filters and telemetry hover cards.",
  "categories": [
    "marketing"
  ]
}