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.
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.
E:\Repos_Alis\promotion\tmp\resources\world_generation_raw\WorldGeneration.mp4, and it must be compressed with the same codecs/approach already used for the homepage video, then placed in a relative media folder inside this site repo.
https://youtu.be/zZOI2uBskSA.
href/aria-label; closes Q5; resolves A7.https://youtu.be/zZOI2uBskSA (_posts/2026-10-06-n000005-build-the-game.md, whose thumbnail n5_BuildTheGame.webp is a frame of WorldGeneration.mp4). - gates: the anchor href/aria-label value for the second clip. Public-facing, so it is confirmed before deploy; the plan ships the per-clip plumbing regardless.<button>s overlaid at the card bottom, so a dot click switches the clip without navigating. Each carousel item also carries the anchor href/aria-label to apply when it becomes active (D5).eq/vignette/fade recipe to the world-generation clip - the card CSS already supplies a vignette/gradient overlay, and re-grading a finished master with large spatial text risks readability for no functional gain.ended (no crossfade). No baked fades; the earlier plan relied on baked fades as the transition and that dependency is removed.<video src> is retained as the no-JS/error fallback, so the first clip still plays.loop in the HTML for the no-JS fallback, and have JS set video.loop = false on init so ended can fire for the carousel. Without this, no-JS degrades from looping to play-once.https://youtu.be/zZOI2uBskSA.src, href, label); no generic carousel abstraction and no second <video> element._layouts, _includes outside custom, _sass/minimal-mistakes).Verified facts
_sass, _includes, _layouts, assets, _config.yml, and the installed gem minimal-mistakes-jekyll 4.26.2 for slick|splide|swiper|carousel|slider → no matches. The theme ships Magnific Popup (lightbox) and FitVids only; _includes/video renders a provider iframe, not a carousel. (D3 answered: nothing to reuse.)index.html (unchanged by the concurrent commit) still wraps a single <video autoplay muted loop playsinline preload="metadata"><source src="/assets/media/alis-trailer-loop.mp4"></video> plus .alis-trailer-copy inside an <a> to https://www.youtube.com/watch?v=eIJHYsPgNnM with aria-label="Watch ALIS 1.0.0 trailer on YouTube". No carousel code exists in the repo.main was fast-forwarded to origin/main and the todo was committed as 61ea270 "Add dual-video trailer carousel to homepage". That commit’s diff contains only this todo file (git show --stat 61ea270 → 1 file changed, 201 insertions); its message describes an implementation that is not in the tree. The earlier “4 commits behind” condition no longer exists (git rev-parse HEAD origin/main are equal; git branch -vv shows [origin/main]).assets/media/alis-trailer-loop.mp4: h264, 720x406, 24 fps, 41.167 s, 2,301,411 bytes (~447 kbps), no audio.WorldGeneration.mp4: h264 1920x1080 30 fps + aac audio, 68.331 s, 134,823,232 bytes (~15.8 Mbps). 30 fps ⇒ ~2050 source frames; forcing 24 fps drops ~410 frames (~20%).scale=720:-2:flags=lanczos,fps=30,format=yuv420p, -an, libx264 high/3.1, -preset slow, -movflags +faststart), no colour grade:
stat -c '%s %n'); du -h is informational only._posts/2026-10-06-n000005-build-the-game.md ends with Watch https://youtu.be/zZOI2uBskSA; tmp/resources/.../n5_BuildTheGame.webp and frame_49_990.png are frames of WorldGeneration.mp4. No other file in the site repo maps this clip to a URL..gitattributes tracks *.mp4 in Git LFS and exempts only assets/media/alis-trailer-loop.mp4 (git check-attr -a → filter: unset, diff: unset, merge: unset)._includes/head/custom.html loads /assets/css/trailer.css?v=1791283677 and is the established custom-head script/style hook. _includes/head.html adds js to <html>._sass/custom/_trailer_custom.scss, imported by both _sass/minimal-mistakes.scss and assets/css/trailer.scss. The card already applies a radial vignette + bottom gradient via .alis-trailer-card a::after.package.json, or HTML proofer exists (find .github → empty).Inferences
WorldGeneration.mp4 is the “Build the Game” trailer (n000005), so its YouTube destination is https://youtu.be/zZOI2uBskSA. The thumbnail match is strong and the URL is operator-confirmed by D5.Assumptions / unverified areas
autoplay / ended / play() behaviour across target browsers is runtime-verified in the plan below.Refuted
href is sufficient” — the second clip has its own trailer (D5, evidence above).index.html (layout home) owns the homepage trailer card markup._includes/head/custom.html owns custom head assets (trailer CSS + analytics + nav highlight)._sass/custom/_trailer_custom.scss owns card styles, surfaced on the homepage through the separately built assets/css/trailer.css (cache-busted in the head include).assets/media/ owns served media; .gitattributes owns LFS/blob policy.todo/done/homepage-trailer-proxy-card.md is a historical record of the single-clip card and is left untouched.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.
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.
index.html: keep a real fallback source and the looping no-JS behaviour:
<video class="alis-trailer-video"
src="/assets/media/alis-trailer-loop.mp4"
autoplay muted loop playsinline preload="metadata"></video>
plus a sibling .alis-trailer-dots group of two <button>s inside the existing .alis-trailer-card, hidden until JS runs.
assets/js/alis-trailer.js:
{ src, href, label } for both clips (single owner of carousel data; not a generic carousel model);video.loop = false on init so ended fires;video.src, load(), play();ended, advance (i+1) % n;href and aria-label to the active item’s, and sync .is-active + aria-current on the dots;assets/media/alis-worldgen-loop.mp4 with clean compression only (no eq, no vignette, no baked fade), at its own source 30 fps, CRF 34 default, and require an in-motion review before accepting that CRF. If rejected, fall to CRF 32 and then to a shorter proxy (D6); never reduce frame rate (see verification plan). The existing 24 fps clip is not re-encoded._sass/custom/_trailer_custom.scss using theme variables; ~44x44 px hit area with a small visual dot._includes/head/custom.html with defer and the same ?v= cache-bust used for trailer.css..gitattributes gains an exemption so the new MP4 stays a plain Git blob.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).
<video> elements crossfading — rejected: duplicate fetching, more state and CSS, and crossfade was not requested.eq/vignette/fade to match the old clip — rejected (A3): the card already provides the overlay, and re-grading risks the finished master.playsinline preview without a user gesture.ended advances to the other and wraps.<button>s, ~44 px hit area).href/aria-label always match the currently active clip’s destination; dots are never nested inside the anchor._sass/custom/ and theme variables.https://youtu.be/zZOI2uBskSA and the first item’s to the existing 1.0.0 trailer (D5).ffmpeg -i "/mnt/e/Repos_Alis/promotion/tmp/resources/world_generation_raw/WorldGeneration.mp4" \
-vf "scale=720:-2:flags=lanczos,fps=30,format=yuv420p" \
-an -c:v libx264 -profile:v high -level 3.1 -crf 34 -preset slow -movflags +faststart \
assets/media/alis-worldgen-loop.mp4
Measured result: 2,493,452 bytes (2.4 MiB). Fallback ladder if the in-motion review shows artefacts on the fast flythroughs or text: (1) full clip, CRF 32 / 720 / 30 fps → 3,051,822 bytes (2.9 MiB); (2) if still unacceptable, shorten the homepage proxy and encode at CRF 30 or better, measuring until the exact file length is ≤3,145,728 bytes (measured examples: 45 s at CRF 30 → 2,548,280 bytes; 45 s at CRF 32 → 2,052,365 bytes). Do not return to CRF 34 once it has been rejected: shortening does not fix CRF-34 artefacts. Keep the full-length version on YouTube. Never reduce the frame rate, and do not treat a lower resolution as a quality fix.
.gitattributes next to the existing one:
assets/media/alis-worldgen-loop.mp4 -filter -diff -merge -text
index.html: add class alis-trailer-video, keep src/loop for the no-JS fallback, and add the sibling .alis-trailer-dots group with two <button type="button" class="alis-trailer-dot" data-clip="0|1" aria-label="..."> elements and hidden on the group.assets/js/alis-trailer.js per Decision: item list with src/href/label; loop=false; random start that skips a needless reload; ended advance; anchor href/aria-label sync; dot click; active/aria-current sync; unhide dots._sass/custom/_trailer_custom.scss using theme variables: .alis-trailer-dots position:absolute; bottom; z-index:3; left:50%; transform:translateX(-50%); render only when not hidden (:not([hidden]){display:flex} or a [hidden]{display:none} rule); each button ≥44x44 px with a small ::before dot; add transition:none for the dot under the existing prefers-reduced-motion block._includes/head/custom.html with defer and ?v=1791283677.README.md per Documentation plan./ always shows the same single clip, there are no dot controls, and there is no advance at clip end. After: reloading several times shows both clips across loads; a dot row is present and tracks the active clip; forcing ended (or seeking to the end) switches to the other clip and wraps.src and loop attribute (no-JS fallback); ffprobe reports 30 fps for the new clip; the anchor href equals the active item’s destination after a switch.package.json, and no CI. State the limitation and rely on the runtime browser evidence plus the Jekyll build; do not add a test framework for a two-item switch.<a>.stat -c '%s %n' assets/media/alis-trailer-loop.mp4 assets/media/alis-worldgen-loop.mp4 → each exact byte count must be ≤3,145,728 (du -h is informational only), and ffprobe -hide_banner -v error -show_entries stream=codec_name,width,height,r_frame_rate -show_entries format=duration -of default=noprint_wrappers=1 <file> (h264, 720-wide, no audio; existing clip 24 fps, new clip 30 fps).git check-attr -a -- assets/media/alis-worldgen-loop.mp4 shows no filter: lfs; file assets/media/alis-worldgen-loop.mp4 reports MP4 data.bundle exec jekyll build succeeds; _site/index.html keeps src+loop on the video and contains the dots; _site/assets/media/alis-worldgen-loop.mp4, _site/assets/js/alis-trailer.js, and dot rules in _site/assets/css/trailer.css exist.bundle exec jekyll serve --host 0.0.0.0 --force_polling, open /, reload several times for the random start, drive ended to confirm advance + wrap, confirm the card href follows the active clip, click and keyboard-tab the dots, check a narrow mobile width, emulate prefers-reduced-motion, and disable JavaScript to confirm the first clip still autoplays and loops with no dots.<a>, not descendants (no nested interactive content).README.md — add a short “Homepage preview media” section./docs is not a convention here)..gitattributes plain-blob requirement; the destination the card link uses for each clip.index.html and alis-trailer.js own the runtime contract; README names the files but does not restate the markup or JS. The completed todo/done/homepage-trailer-proxy-card.md is a historical record and is left untouched.HEAD now equals origin/main at 61ea270, so the earlier stale-baseline risk is gone. Before implementing, re-run git fetch and confirm git status / git log --oneline main..origin/main is still clean..gitattributes, index.html, JS, Sass, head include, and README; deploy as usual (GitHub Pages). Both clips must be plain blobs before the branch deploys.alis-trailer-loop.mp4.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.
<video> with JS-swapped src.PATCH: core architecture (one <video> + small vanilla JS, random first item, ended → next, two dots, no carousel dependency) accepted.REJECTED by evidence; A6, A7, A8 added; Q5 opened as blocking.href/aria-label; preserve the new clip’s 30 fps (24 fps drops ~410 frames for ~0.28 MB); no destructive re-grade/fades (card CSS already overlays); keep the src fallback and, beyond the review, keep loop in HTML with loop=false set by JS so the no-JS fallback still loops; ~44 px dot hit area.loop in HTML (not merely src) is what preserves the pre-existing looping fallback.main was fast-forwarded to origin/main and this todo was committed as 61ea270, whose message claims an implementation the diff does not contain. No carousel source exists yet; the reviewer’s stale-baseline gate is satisfied.https://youtu.be/zZOI2uBskSA, and explicitly withheld implementation authorization pending an explicit go. No code was changed in this round.du -h, 3,145,728 bytes), matching the prior card’s convention.[RESOLVED by D5].du -h cannot enforce an exact 3 MiB byte limit … After these two corrections: PASS on the plan.”stat -c '%s %n' (≤3,145,728 bytes); du -h is informational only. Applied in Evidence, Documentation plan, and Completion criteria.main == origin/main at fcd5724, worktree clean.assets/media/alis-worldgen-loop.mp4 (new), .gitattributes (exemption), index.html (single <video src> fallback + dots, loop kept for no-JS), assets/js/alis-trailer.js (new), _sass/custom/_trailer_custom.scss (dot styles), _includes/head/custom.html (deferred script), README.md (Homepage preview media). No theme files touched. Nothing was staged or committed.ffprobe → h264, 720x406 (SAR 406:405, DAR 16:9), 30 fps, no audio, 68.3 s; stat -c %s → 2,493,452 ≤ 3,145,728.git check-attr -a → filter: unset; file → MP4 data; no git lfs ls-files match.bundle exec jekyll build → done in 20.9 s; _site contains the new mp4, the JS, dot rules in trailer.css, and src+loop+dots in index.html.bundle exec jekyll serve --host 127.0.0.1 --port 4011 --force_polling, script tmp/verify-trailer.cjs): 13/13 checks passed — dots revealed; JS set loop=false; both clips observed across 12 reloads; the real ended event advanced trailer → worldgen; actionability-checked dot clicks switched currentSrc together with the anchor href/aria-label and opened no popup; a keyboard activation worked; a real click on the card body opened exactly the active clip’s link (https://youtu.be/zZOI2uBskSA); the active dot carried aria-current; the no-JS context kept src+loop with dots hidden; zero page/console errors.## Implementation tasks).todo/done/homepage-trailer-proxy-card.md records the first clip’s recipe as fps=24,eq=brightness=-0.04:contrast=1.08:saturation=0.92,vignette=PI/5,fade=t=in:st=0:d=0.5,fade=t=out:st=40.3:d=0.8 at CRF 34, so the no-grade/no-fade rule applies only to new or re-encoded previews.README is excluded from the Jekyll build, so no rebuild was needed.assets/media/alis-worldgen-loop.mp4; the todo is not moved to todo/done/ until that gate passes.todo/done/.