Repository navigation
docs(catalog): every card on screen plays a small looping video instead of a live player - #7146
Conversation
…ad of a live player
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
There was a problem hiding this comment.
🟡 Changes recommended
Autoplay previews lack an accessible pause control and can play below the intended visibility threshold or while the tab is hidden.
3 open issues
What changed in this PR
Replaces live catalog-card players with lightweight looping videos to reduce browsing stutter, while keeping live players on individual item pages.
Changes:
- Adds viewport-driven playback, single-card playback on phones, and poster-only behavior for reduced motion or Save-Data.
- Extends preview generation to create 480 px silent videos and select posters from four candidate frames.
- Removes obsolete live-player styling and tile-reveal tests.
| File | Description |
|---|---|
| scripts/generate-catalog-previews.ts | Generates compact card videos and selects poster frames. |
| scripts/generate-catalog-pages.test.ts | Removes tests for the deleted live-player reveal helper. |
| docs/snippets/catalog-gallery.jsx | Replaces hover-driven players with viewport-driven video loops. |
| docs/catalog-gallery.css | Removes the unused live-player sizing rule. |
Review effort: Balanced
| if (!video) { | ||
| video = document.createElement('video'); | ||
| video.muted = true; | ||
| video.loop = true; |
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed at fb94ab73d619974fa0595aaa35d1ba437ee0dfc1. COMMENT review; no approval stamp.
The card-sized video path removes the live-player startup from the gallery, and the cleanup releases each detached video source (docs/snippets/catalog-gallery.jsx:118–128). I read the four changed files, the old playback implementation, and the preview-generation callers.
Important — the new autoplay needs a pause control (docs/snippets/catalog-gallery.jsx:128–142), agreeing with Copilot's existing comment. In Chromium after seven seconds, six videos were still playing with loop:true, controls:false; the gallery buttons were only Show All controls. A visitor cannot stop this motion while reading the cards without changing their system setting. Add one keyboard-accessible Pause previews toggle, combine it with the existing still guard, and keep that choice across filtering/expansion. Test that pausing stops every loop and scrolling cannot restart them. This is the behavior covered by WCAG 2.2.2.
Copilot's other two comments remain applicable: play() has no hidden-document guard (:128), and the observer tests isIntersecting, not intersectionRatio >= 0.25 (:162). Guard playback centrally and apply the ratio when maintaining onScreen; I have not reproduced a natural background-tab restart in a browser.
Browser evidence: python …/ular-7146-7148/behavior.py on the Mintlify branch preview, plus an exact-source component fixture. At 1440×900 six real CDN loops played at 480×270. Steady page rAF median was 33.3 ms (~30 fps); one Show All 25 expansion had a 50 ms worst frame and no long tasks; scrolling had a 33.4 ms worst frame and none. These are headless Chromium measurements on this box, not a reproduction of the author's normal-Chrome 417→34 ms baseline comparison or a guarantee of hardware decoding. A touch/mobile 390×844 context played one card before and after scrolling. Emulated reduced motion, including changing it after load, removed all card videos. Injected navigator.connection.saveData=true produced posters and no card videos; no physical phone or real Save-Data setting was exercised.
CDN HEAD audit: 895/896 URLs answered 200 (448 items × poster/loop). components/caption-emoji-pop.mp4 answered 403 on HEAD and GET; its JPEG exists, so the existing error handler leaves the poster. Nit: publish that missing loop or document the exception; the PR's “447 items/every card” description is slightly stale.
Need: yes · Size: ok · Reuse: existing native video, observer and preview encoder · Simpler: already substantially smaller · Structure: fine
Risk: medium — gallery-wide autoplay and browser lifecycle behavior change.
Verdict: COMMENT — fix the pause control before landing.
Reasoning: The measured playback behavior matches the stated 30 fps trade-off and the initial preference guards work, but the new indefinite motion lacks a user-controlled stop. No duplicate inline findings or approval stamp added.
— hf-review
…en tab or under 25% visible
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed at 96dc56344a0ae1d80e7abe2f0daa04a175a42498. COMMENT re-review; no approval stamp.
The previous pause-control finding is fixed. The native button exposes aria-pressed; source inspection shows that the shared playback effect releases all video sources when paused, and its localStorage choice survives rerender/reload. The other two earlier Copilot findings are also fixed: play() checks document.hidden centrally, and observer membership requires intersectionRatio >= 0.25. I reviewed the delta from fb94ab73 and the complete resulting gallery/CSS.
Browser verification: exact-source React/Chromium fixtures exercised keyboard pause/resume, expansion/scroll while paused, persisted pause after reload, and reduced-motion changes. An independent pass also exercised synthetic hidden/visible transitions and phone scrolling with one playing card. Synthetic visibility is not a natural background-tab test; no physical phone or new baseline performance comparison was run. The prior headless performance measurements remain scoped to the earlier head.
The current Mintlify branch preview also showed 6 videos → 0 on keyboard pause and 0 after reload. The repaired components/caption-emoji-pop.mp4 now answers a Range GET with 206, video/mp4 (64 bytes requested/returned); I did not repeat the full 896-URL audit. The description now says 448 items.
Need: yes · Size: ok · Reuse: existing playback effect, native button and localStorage · Simpler: already minimal · Structure: fine
Risk: low — focused fixes to the reviewed playback lifecycle.
Verdict: COMMENT — clean re-review; no remaining blocker found in the delta.
Reasoning: The reviewed playback paths honor pause; the fixture confirmed pause during expansion, scrolling and reload, the visibility checks are centralized, and the fixes retain the existing preference and cleanup behavior.
— hf-review
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 2 nits · head 96dc5634
Additive to @jrusso1020's re-review and Copilot's three threads, all addressed at this head (pause control with aria-pressed + localStorage, document.hidden guard in play(), intersectionRatio >= 0.25). Also checked: all 896 card .mp4/.jpg URLs the gallery builds from catalog-gallery-data.mdx answer 206 on the CDN, and the effect cleanup disconnects the observer, clears the phone interval, removes the visibility listener and releases every video source.
- nit
docs/snippets/catalog-gallery.jsx:6-7: the comment still sayswindow.__hfDocsPlayerLoadingdedupes the player script, which this PR removes. - nit
docs/snippets/catalog-gallery.jsx(playback effect deps):filtersis a dependency, so each filter change (and each keystroke once #7147's search box lands) tears down and re-creates every on-screen video, flashing the poster briefly. Cached, so cheap. Could be keyed on the visible item set instead if it shows.
Scope
Read the full diff (4 files). CI green at this head (CI, Docs, CodeQL, regression, preview-regression, Player perf, Catalog Previews). Did not repeat Rames's browser/frame-rate measurements; relied on them for playback behavior.


What
The catalog page plays a small looping video on every card on screen, instead of starting a live player when you hover a card.
catalog-card/): 47 KB per loop and 7 KB per poster on average, 448 items.aria-pressed, the choice holds through search, Show All and scrolling, and it is remembered on the device. Nothing plays in a background tab or on a card less than a quarter visible.Why
The catalog stutters today. Measured in a normally launched Chrome (CDP, 1440 px, 4 Nov):
Starting live compositions on the page is the work behind those freezes. A video is decoded by the device's video hardware, so a screenful of loops costs less than one live player did. The cost: with several videos playing, Chrome draws the page at 30 fps (the same effect we measured on the docs' Inspiration rows on 26 Oct). Visitors also see motion on every card without hovering, which is the point of a catalog.
Related work
The card files were uploaded to
s3://heygen-public/hyperframes-oss/docs/images/catalog-card/withupload:docs-images(immutable cache). The one missing loop Rames found (components/caption-emoji-pop.mp4, 403) was made with the same encoder settings and uploaded on 4 Nov; all 896 card files now answer 200. The catalog search box (separate PR) is stacked on this one.How
docs/snippets/catalog-gallery.jsx: the hover/intent player code (script loading from jsDelivr, per-tier caps, the reveal timing) is replaced by one IntersectionObserver that plays cards at 25% visible and unloads them when they leave. Phones pick the nearest card on scroll. Pausing when the tab is hidden stays.scripts/generate-catalog-previews.ts:writeCard()encodes the card loop (ffmpeg, 480 px, CRF 28, no audio, faststart) and picks the poster.scripts/generate-catalog-pages.test.ts: the "tile reveal" test goes with the function it tested.Test plan
bun test scripts/generate-catalog-pages.test.tspasses.