Homepage dual-video trailer carousel

Status: DONE Scope: Homepage trailer card (index.html), custom Sass, custom head include, new homepage JS, assets/media, .gitattributes Stable documentation owner: root README.md (proposed short “Homepage preview media” section). No existing stable doc owns the served preview clips; the recipe currently lives only in the completed todo/done/homepage-trailer-proxy-card.md.

Contents

Goal

On the homepage trailer card, show one of two muted loop clips chosen at random on each page load; when the current clip ends, automatically show the other, continuing to alternate; provide small dot controls at the bottom of the card so a visitor can switch clips manually. The second clip is the world-generation footage compressed with the same codec/approach as the existing homepage clip, stored in the site repo’s media folder.

Authority register

Operator decisions

Operator gates

Working assumptions

Non-goals

Verified evidence

Verified facts

Inferences

Assumptions / unverified areas

Refuted

Current architecture and source of truth

Problem and root cause

Capability gap: the card plays one static clip only. It cannot show a second clip, cannot start on a random clip, and cannot advance on end or accept manual switching. The review round also established that the second clip has its own full-trailer destination, so a single fixed card link would send a visitor from the world-generation preview to the wrong video.

Decision

Drive the card from one <video> element whose src is chosen and advanced by a small vanilla-JS module; each item owns its own link.

Premise / KISS gate

The <video> element already owns playback lifecycle; the simplest design lets it own the sequence too, driven by a small vanilla-JS item list. Versus a typical carousel, this removes: a library, CSS transforms/tracks, drag/touch handling, and a second DOM layer. It adds: one MP4, one small JS file, one dot row, and three data fields per item. The per-clip href is data on that same model, not a new abstraction. Existing machinery retained: the current whole-card anchor (a stated requirement) and the card’s CSS vignette (which is why no baked grade is needed). Knowingly given up: crossfade animation between clips; preloading the inactive clip (a switch may show a brief load gap).

Alternatives considered

  1. Vendor/import Slick, Swiper, or Splide — rejected: a new dependency and payload for switching between two items; none exists in the repo or theme (YAGNI).
  2. Two stacked <video> elements crossfading — rejected: duplicate fetching, more state and CSS, and crossfade was not requested.
  3. Build-time random selection via Liquid — rejected: Liquid has no per-request random; every visitor would see the same clip, failing D1.
  4. YouTube iframes for both clips — rejected: heavier and slower, losing the fast local loop D1 requires.
  5. Baked eq/vignette/fade to match the old clip — rejected (A3): the card already provides the overlay, and re-grading risks the finished master.
  6. Force 24 fps to shrink the file — rejected: drops ~20% of frames on fast motion for ~0.28 MB, and 24 fps is not a requirement (Refuted above).
  7. A reversible CSS opacity fade for the swap instead of a baked fade — viable but not needed for correctness; recorded as non-blocking polish, not part of the required change.

Required invariants

  1. The homepage autoplays a muted, playsinline preview without a user gesture.
  2. Exactly one clip is active; ended advances to the other and wraps.
  3. The first clip is chosen randomly on each page load.
  4. The dots reflect the active clip and switch clips by mouse and keyboard (real <button>s, ~44 px hit area).
  5. The card anchor’s href/aria-label always match the currently active clip’s destination; dots are never nested inside the anchor.
  6. No theme files are modified; styles use only _sass/custom/ and theme variables.
  7. Both MP4s are plain Git blobs (no LFS pointers), h264/yuv420p, faststart, no audio, 720-wide, each ≤3 MiB (3,145,728 bytes), and each preserves its own source frame rate: the existing clip stays 24 fps and the new clip is 30 fps.
  8. No colour grade, vignette, or baked fade is applied to the world-generation master.
  9. Without JavaScript the first clip still autoplays and loops, and no interactive controls render (dots hidden).
  10. Never reduce a clip’s frame rate for size, and do not treat a lower resolution as a quality fix.

Implementation tasks

Test-first and verification plan

Red evidence

Green evidence

Documentation plan

Rollout and rollback

Completion criteria

PASS requires: both MP4s present with an exact byte count ≤3,145,728 each (stat -c %s), h264/720-wide/no-audio with the existing clip at 24 fps and the new clip at 30 fps; no grade or baked fade in the new clip; alis-worldgen-loop.mp4 not LFS-tracked; bundle exec jekyll build clean with the expected _site outputs; runtime evidence of random start, ended advance + wrap, dot switching (mouse + keyboard), card href following the active clip, and the no-JS fallback still autoplaying and looping; an in-motion CRF review recorded; no theme files modified; README section added; no unrelated changes.

Review record

2026-10-06 - Investigation

2026-10-06 - Review round (PATCH) and revision

2026-10-06 - Operator confirmation (implementation still gated)

2026-10-06 - Second review round (PATCH) and revision

2026-10-06 - Third review round (PATCH) and revision

2026-10-06 - Implementation and verification

2026-10-06 - Review nit applied

2026-10-06 - Operator motion-quality confirmation