What changed, and why
The landing was a static pitch. This embeds the demo reel so the home page shows the game in motion, and adds a dark variant so it fits whichever theme you're in.
Two pieces carry the change. The reel now renders in both themes from one source (marketing/reel/reel.html parametrized by ?theme=), and pages/index.vue embeds the two renders so they crossfade — and keep playing — when you toggle the theme. The interesting bit is that last part: the swap is mid-playback, not a restart.
The embed: two renders, stacked
Both MP4s are mounted at once, absolutely stacked in a fixed-ratio frame. Each is a muted autoplay loop; the one matching the theme is faded in via the is-on class, the other faded out. They serve from public/reel/.
The reel panel — light + dark <video>, shown by theme.
pages/index.vue · 173 lines
⋯ 32 lines hidden (lines 1–32)
⋯ 123 lines hidden (lines 51–173)
The frame holds the native 1000:560; both videos stack and crossfade on opacity.
pages/index.vue · 173 lines
⋯ 155 lines hidden (lines 1–155)
⋯ 1 line hidden (lines 173–173)
Keeping it playing through the theme swap
The point of the request: switching theme should look like the reel re-coloring, not a new clip starting. Both videos autoplay from mount, so they run in lockstep. When the theme toggles, we copy currentTime from the outgoing video to the incoming one and call play() — so the now-visible render picks up at the exact same frame and continues. The CSS opacity crossfade hides the handoff.
On a theme change: sync currentTime old→new, then play the now-visible video.
pages/index.vue · 173 lines
⋯ 117 lines hidden (lines 1–117)
⋯ 37 lines hidden (lines 137–173)
Reduced motion
The videos carry autoplay so they start without waiting on JS. But autoplay can't stop an already-playing video, so reduced-motion is handled explicitly: when the preference is detected (after mount, via matchMedia), both videos are paused and reset to frame 0 — which is the title card, the same image as the poster. So a reduced-motion visitor sees a still, theme-correct frame, no motion.
Pause + rest on the title-card frame when prefers-reduced-motion is set.
pages/index.vue · 173 lines
⋯ 137 lines hidden (lines 1–137)
⋯ 23 lines hidden (lines 151–173)
One reel source, both themes
Rather than maintain two near-identical reels, reel.html takes a ?theme= query. The dark palette is a .dark override of the same CSS variables (the app's near-black tokens + a warmer center glow), so every element — text, meter, die, bursts — adapts automatically. The die's subtle fill/facets, which were hardcoded terracotta, now use color-mix on --accent so they re-tint with the theme too.
The dark palette override + theme-aware die fill/facets.
marketing/reel/reel.html · 409 lines
⋯ 34 lines hidden (lines 1–34)
⋯ 363 lines hidden (lines 47–409)
A one-liner at the end of the script reads ?theme=dark and adds the .dark class; capture.mjs renders each theme by loading the page with each value.
Capture loops over both themes into frames-<theme>/ (frame-identical timing).