Marquee
An infinite strip (logos, taglines) that pauses on hover.
ScrollVars·one rAF in·
CSS variables out·
<div id="logo-strip" class="sv-marquee sv-marquee-controlled [--sv-marquee-duration:24s] [--sv-gap:64px] py-8">
<div class="sv-marquee-track">
{logos}{/* duplicate once, aria-hidden */}
</div>
<button type="button" class="sv-marquee-pause" aria-pressed="false"
data-sv-toggle="sv-paused" data-sv-target=".sv-marquee-track">Pause animation</button>
</div>
<!-- <ScrollVarsBoot /> already wires this; a plain HTML page calls
toggles() itself: SV.toggles(document.getElementById('logo-strip')) -->
<div class="sv-marquee sv-marquee-controlled">
<div class="sv-marquee-track">
…content… …content again (aria-hidden)…
</div>
<!-- WCAG 2.2.2: auto-moving content past 5s needs a pause/stop control -->
<button type="button" class="sv-marquee-pause" aria-pressed="false"
data-sv-toggle="sv-paused" data-sv-target=".sv-marquee-track">Pause animation</button>
</div>
<script>toggles()</script> <!-- wires the button: aria-pressed, sv-paused on the track -->
/* the preset (styles/ui.css): */
.sv-marquee { overflow: hidden; display: flex; }
.sv-marquee-track { display: flex; gap: var(--sv-gap, 48px); width: max-content;
padding-right: var(--sv-gap, 48px);
animation: sv-marquee var(--sv-marquee-duration, 30s) linear infinite; }
.sv-marquee:hover .sv-marquee-track,
.sv-marquee:focus-within .sv-marquee-track { animation-play-state: paused; }
.sv-marquee-controlled > .sv-marquee-track.sv-paused { animation-play-state: paused; }
/* while the track holds focus, present it statically and wrapped (WCAG
2.4.7): pausing alone can leave a focused item translated off the strip */
.sv-marquee-track:focus-within { animation: none; width: auto; flex-wrap: wrap; }
.sv-marquee-track:focus-within > .sv-marquee-dup { display: none; }
@keyframes sv-marquee { to { translate: -50% 0; } }
/* the same sheet's reduced-motion override, last so it wins on source order:
without width/flex-wrap the strip keeps overflowing past the viewport
with nothing left to scroll it into view */
@media (prefers-reduced-motion: reduce) {
.sv-marquee-track { animation: none; width: auto; flex-wrap: wrap; }
.sv-marquee-track > .sv-marquee-dup { display: none; }
}
/* the same under html[data-sv-motion="reduce"], the site's own switch */
:where([data-sv-motion="reduce"]) .sv-marquee-track { animation: none; width: auto; flex-wrap: wrap; }
:where([data-sv-motion="reduce"]) .sv-marquee-track > .sv-marquee-dup { display: none; }
<Marquee speed={24}>
{logos.map(l => <img key={l.id} src={l.src} alt={l.name} className="h-8" />)}
</Marquee>