diff --git a/packages/core/templates/partials/slide-styles.eta b/packages/core/templates/partials/slide-styles.eta index 2d1f973..b12ea78 100644 --- a/packages/core/templates/partials/slide-styles.eta +++ b/packages/core/templates/partials/slide-styles.eta @@ -660,6 +660,15 @@ animation: morkdeck-marquee-scroll 48s linear infinite; } + /* Pause the marquee whenever its slide is not the in-view slide. + The presentation toggles `.is-in-view` from the existing + IntersectionObserver, so overview thumbnails, off-slot slides in + presenter mode, and slides scrolled past in audience mode all + drop their compositing layer until they return to view. */ + morkdeck-slide:not(.is-in-view) .marquee-track { + animation-play-state: paused; + } + morkdeck-slide .marquee-item, morkdeck-slide .marquee-sep { font-family: var(--morkdeck-fontFamily-mono); diff --git a/packages/wc/components/presentation/wc.ts b/packages/wc/components/presentation/wc.ts index 949c696..35e227e 100644 --- a/packages/wc/components/presentation/wc.ts +++ b/packages/wc/components/presentation/wc.ts @@ -965,6 +965,15 @@ export class PresentationWC extends LitElement { } fireUpdates(entries: IntersectionObserverEntry[]) { + // Visibility-class toggle first, before any role/overview gating — + // this is read by the marquee CSS to pause its animation on slides + // that aren't currently in view (overview thumbnails, off-slot + // slides in presenter mode, slides scrolled out of the audience + // viewport). Driving it from the existing observer is essentially + // free; the alternative was a second IO per slide. + for (const entry of entries) { + entry.target.classList.toggle("is-in-view", entry.isIntersecting); + } // IO drives audience-mode scroll navigation only. if (this.role !== "audience") return; // Overview shows many slides at once in a grid; whichever happens