# Logo Marquee

> Logos drifting in a slow loop with faded edges. Pauses on hover, static under reduced motion.

- **Slug:** `logo-marquee`
- **Kind:** section · **Category:** Proof
- **Source:** original to Viewership UI
- **Docs page:** https://viewership.ai/ui/components/logo-marquee
- **All files in one bundle:** https://viewership.ai/ui/components/logo-marquee.bundle.txt
- **Structured JSON:** https://viewership.ai/ui/components/logo-marquee.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/ui/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/logo-marquee/`:

   - `LogoMarquee.astro` (raw: https://viewership.ai/ui/raw/logo-marquee/LogoMarquee.astro.txt)

npm packages: none.

## Usage

```astro
---
import LogoMarquee from "../components/viewership/logo-marquee/LogoMarquee.astro";
---

<LogoMarquee />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import LogoMarquee from "./LogoMarquee.astro";
---

<LogoMarquee
  title="As seen in the field"
  items={[
    { name: "Harbor & Pine" },
    { name: "Tallgrass" },
    { name: "Lumen Co." },
    { name: "Fieldnote" },
    { name: "Saltmarsh" },
    { name: "Bearing" },
    { name: "Kestrel" },
    { name: "Orchard Row" },
  ]}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` |  | Short lead-in line. |
| `items` | `{ name: string; src?: string }[]` |  | Logos. Six or more keep the loop seamless. |
| `duration` | `number` | `40` | Seconds per loop. |

## Source

### `src/components/viewership/logo-marquee/LogoMarquee.astro`

```astro
---
/**
 * LogoMarquee: logos drifting in a slow continuous loop, with faded edges.
 * Pauses on hover; under reduced motion it becomes a static wrapping row.
 *
 * @example
 * <LogoMarquee title="As seen in the field" items={[{ name: "Kestrel" }]} />
 */
interface Item {
  name: string;
  src?: string;
}
interface Props {
  title?: string;
  items: Item[];
  /** Seconds for one full loop. */
  duration?: number;
  class?: string;
}

const { title, items, duration = 40, class: className } = Astro.props;
---

<section class:list={["vs-logo-marquee", className]} aria-label={title ?? "Customers"} style={`--vs-marquee-duration: ${duration}s`}>
  <div class="vs-logo-marquee__inner">
    {title && <p class="vs-logo-marquee__title">{title}</p>}
    <div class="vs-logo-marquee__viewport">
      <div class="vs-logo-marquee__track">
        {
          [0, 1].map((copy) => (
            <ul class="vs-logo-marquee__list" aria-hidden={copy === 1 ? "true" : undefined}>
              {items.map((it) => (
                <li>{it.src ? <img src={it.src} alt={copy === 1 ? "" : it.name} height="28" loading="lazy" /> : <span class="vs-display">{it.name}</span>}</li>
              ))}
            </ul>
          ))
        }
      </div>
    </div>
  </div>
</section>

<style>
  .vs-logo-marquee {
    container-type: inline-size;
    background: var(--vs-page);
    color: var(--vs-ink);
    font-family: var(--vs-font-sans);
    font-stretch: var(--vs-font-stretch);
  }
  .vs-logo-marquee__inner {
    max-width: var(--vs-container);
    margin-inline: auto;
    padding: clamp(40px, 6cqi, 72px) var(--vs-gutter);
  }
  .vs-logo-marquee__title {
    margin: 0 0 28px;
    text-align: center;
    font-size: 0.875rem;
    color: var(--vs-ink-3);
  }
  .vs-logo-marquee__viewport {
    overflow: hidden;
    mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
    -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  }
  .vs-logo-marquee__track {
    display: flex;
    width: max-content;
    animation: vs-logo-marquee-scroll var(--vs-marquee-duration) linear infinite;
  }
  .vs-logo-marquee__track:hover {
    animation-play-state: paused;
  }
  .vs-logo-marquee__list {
    display: flex;
    flex: none;
    align-items: center;
    gap: clamp(40px, 6cqi, 80px);
    margin: 0;
    padding: 0 clamp(40px, 6cqi, 80px) 0 0;
    list-style: none;
    color: var(--vs-ink-2);
  }
  .vs-logo-marquee__list span {
    font-size: 1.5rem;
    white-space: nowrap;
  }
  .vs-logo-marquee__list img {
    display: block;
    width: auto;
    height: 28px;
    filter: grayscale(1);
    opacity: 0.75;
  }
  @keyframes vs-logo-marquee-scroll {
    to {
      transform: translateX(-50%);
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .vs-logo-marquee__track {
      width: auto;
      justify-content: center;
      animation: none;
    }
    .vs-logo-marquee__list[aria-hidden="true"] {
      display: none;
    }
    .vs-logo-marquee__list {
      flex-wrap: wrap;
      justify-content: center;
      row-gap: 20px;
      padding-right: 0;
    }
    .vs-logo-marquee__viewport {
      mask-image: none;
      -webkit-mask-image: none;
    }
  }
</style>
```
