Skip to content

docs(catalog): every card on screen plays a small looping video instead of a live player - #7146

Merged
ukimsanov merged 4 commits into
mainfrom
docs/catalog-video-grid
Nov 4, 2026
Merged

ukimsanov merged 4 commits into
mainfrom
docs/catalog-video-grid

Conversation

@ukimsanov

@ukimsanov ukimsanov commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

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.

  • Each card is a 480 px wide, silent loop with a poster frame, served from the CDN (catalog-card/): 47 KB per loop and 7 KB per poster on average, 448 items.
  • Phones play only the card nearest the middle of the screen.
  • Visitors who turn on reduced motion or Save-Data see posters only.
  • A "Pause previews" button under the intro stops every loop (WCAG 2.2.2). It is a real button with 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.
  • The live player, with its controls, stays on each item's own page.
  • The preview generator now also writes these card files, choosing as poster the busiest of four frames, so a card never shows a blank frame between beats.

Why

The catalog stutters today. Measured in a normally launched Chrome (CDP, 1440 px, 4 Nov):

Live catalog This branch
Click "Show All 25" 417 ms freeze (one 403 ms long task) worst frame 34 ms, no long tasks
Scrolling freezes up to 234 ms, 4 long tasks worst frame 34 ms, no long tasks
Still page 120 fps, nothing plays 30 fps while the loops play

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/ with upload: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

  • Chrome over CDP, local docs against the real CDN: the table above; 0 page errors; no layout shift on Show All.
  • Sample card files answer 200 from static.heygen.ai.
  • Reduced motion → posters only; a phone viewport plays one card.
  • Pause previews (keyboard Enter): 6 playing → 0 video elements; stays at 0 after scrolling, Show All, a search and a reload; Play previews brings back 6.
  • bun test scripts/generate-catalog-pages.test.ts passes.
  • On a real phone: not run.

Copilot AI balanced review requested due to automatic review settings November 4, 2026 05:49
@mintlify

mintlify Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Nov 4, 2026, 6:27 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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;
Comment thread docs/snippets/catalog-gallery.jsx Resolved
Comment thread docs/snippets/catalog-gallery.jsx Resolved

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 says window.__hfDocsPlayerLoading dedupes the player script, which this PR removes.
  • nit docs/snippets/catalog-gallery.jsx (playback effect deps): filters is 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.

@ukimsanov
ukimsanov added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit b3dd766 Nov 4, 2026
119 checks passed
@ukimsanov
ukimsanov deleted the docs/catalog-video-grid branch November 4, 2026 18:26

This branch was successfully deployed

1 active deployment
staging - docs — 96dc5634 Deployed Nov 4, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants