# Customer logo marquee

**Last Updated:** 2026-04-01

Infinite horizontal strip of customer logos for social proof. **Do not** hand-roll `logo-slider` markup or `animation: slide` for this pattern — use the shared partial and data file so the loop uses a **-50%** transform seam (two identical sequences) and one animation name (`ordioCustomerLogoMarquee`).

## Files

| Role | Path |
|------|------|
| Canonical logo rows (presets) | `v2/data/customer-logos-marquee.php` |
| Markup + asset enqueue | `v2/sections/partials/customer-logo-marquee.php` |
| Styles (keyframes, fades, reduced motion, hover pause) | `v2/css/customer-logo-marquee.css` → `customer-logo-marquee.min.css` |
| Off-screen pause (IntersectionObserver) | `v2/js/customer-logo-marquee.js` → `customer-logo-marquee.min.js` |

The partial calls `ordio_customer_logo_marquee_enqueue_assets()` once per request (CSS + deferred JS with `filemtime` cache busting).

## Options (`$customer_logo_marquee_options`)

| Key | Description |
|-----|-------------|
| `preset` | `default` \| `comparison` \| `landing_compact` — which logo list from `customer-logos-marquee.php` (default: `default`). |
| `variant` | `homepage` \| `comparison` \| `landing_compact` — image treatment: grayscale / muted strip / compact heights (default: `homepage`). |
| `show_label` | bool (default: `true`). |
| `label_text` | string. |
| `label_preset` | `marketing` \| `compact` \| `comparison` — Tailwind bundle for the label when you **do not** pass `label_class` (default: `marketing`). |
| `label_class` | Optional Tailwind string; if present in the options array, it **replaces** `label_preset`. |
| `fade_bg` | Optional hex string — sets `--ordio-marquee-fade-bg` on the `<section>` for edge masks. **Default:** omit (CSS uses `transparent`) so the strip inherits the page background (dot grid, gradients, etc.). Pass a hex only when the strip sits on a **solid** block that differs from the body (rare); avoid wrapping the marquee in `bg-[#fbfbfb]` solely to match fades—prefer a transparent parent. |
| `duration_seconds` | int (default: `75`) — full loop duration. |
| `outer_class` | Extra classes on the `<section>` root (e.g. `mb-4`). |
| `section_id` | Optional `id` on the section. |
| `gradient_left_class` / `gradient_right_class` | Legacy Tailwind overrides for edge fades; prefer `fade_bg` + built-in masks. |

## Variant matrix (image treatment)

| `variant` | At rest | Hover |
|-----------|---------|--------|
| `homepage` | Grayscale, `object-fit: contain`, max width/height caps, `width: auto` (no flex stretch) | Full color |
| `comparison` | ~45% opacity + grayscale (readable muted strip) | Full opacity + color |
| `landing_compact` | Grayscale, tighter `max-height` for dense heroes | Full color |

## Label presets (when `label_class` is not passed)

| `label_preset` | Use case |
|----------------|----------|
| `marketing` | Default homepage-style: uppercase, tracking, `text-gray-600`, dark top margin variant. |
| `compact` | Dense landing blocks (e.g. `/v3` hero-adjacent): `mt-9`, block, centered. |
| `comparison` | Short label spacing above comparison strips. |

## Edge fades

Built-in left/right masks sit on the viewport edges (`z-index` above the track) and use `linear-gradient(90deg, …)` so the fade runs from the edge **inward**. Colors use `var(--ordio-marquee-fade-bg, transparent)` in CSS; the partial sets `--ordio-marquee-fade-bg` inline only when `fade_bg` is a valid hex. **Do not** wrap the marquee in a gray or white band just to match fades—keep the parent background transparent so the page background stays continuous.

## Anti-patterns

- Do **not** use `opacity-30` / `opacity-40` / `opacity-50` on `label_class` for the marquee label — labels become illegible. Use `text-gray-600` (or a `label_preset`) instead.
- Do **not** rely on `width: 100%` on logos inside flex rows; the component uses `object-fit: contain` and `width: auto`.
- Do **not** add `logo-slider` or `@keyframes slide` for this feature; keep styles in `customer-logo-marquee.css`.

## Integration

```php
<?php
$customer_logo_marquee_options = [
    'variant' => 'comparison',
    'show_label' => true,
    'label_text' => 'Täglich genutzt von +2.000+ Unternehmen',
    'label_class' => 'uppercase tracking-widest text-center text-xs mt-0 font-inter600 mb-6',
    'duration_seconds' => 75,
    'outer_class' => 'mb-4',
];
include __DIR__ . '/../sections/partials/customer-logo-marquee.php';
unset($customer_logo_marquee_options);
?>
```

Paths: from `v2/pages/*.php` use `__DIR__ . '/../sections/partials/customer-logo-marquee.php'`; from `v2/start-v2.php` use `__DIR__ . '/sections/partials/customer-logo-marquee.php'`.

## Accessibility and performance

- Section `aria-label` matches the visible label text.
- Duplicate strip uses `aria-hidden="true"` on the second sequence’s images.
- `prefers-reduced-motion: reduce` stops the marquee and shows a single row (see CSS).
- JS pauses animation when the track leaves the viewport (`[data-ordio-marquee-track="1"]`). Testimonials page observer also targets this selector (see `v2/js/testimonials-page.js`).

## CSS load order

Do not reintroduce global `.logo-slider` / `@keyframes slide` in `head.php` or shared page CSS; the marquee is scoped in `customer-logo-marquee.css`.

## Validation

```bash
php v2/scripts/dev-helpers/verify-customer-logo-marquee.php
```

After editing `v2/css/customer-logo-marquee.css` or `v2/js/customer-logo-marquee.js`, run `npm run minify`.

## QA (spot-check)

After changes: homepage, `/v3`, a comparison URL (e.g. `/alternativen/aplano-vergleich`) — edge fades match background, label contrast, logos not stretched, `prefers-reduced-motion` shows static row.
