## 2026-10-12 (evening) — make_hero_clip.py: the home page's laptop clip - `scripts/make_hero_clip.py` assembles the hero clip from the operator's raw app recordings: `NN-appname.ext` in, scaled and centre-cropped to the laptop screen's 1.626:1, crossfaded, out as **mp4 (h264) + webm (VP9) + a webp poster**. Runs on DooPlex; Windows has no ffmpeg. It is not part of any build, gate or deploy — the site has no build step, so its output is committed like any other picture. - **Why mp4+webm and not a GIF**, measured on DooPlex 2026-10-12 with six crossfaded scenes: | | 11.4s @ 904×556 | 11.4s @ 1130×696 | |---|---:|---:| | mp4 (h264 crf30) | **331 KB** | 775 KB | | webm (VP9 crf44) | 411 KB | 1 196 KB | | animated webp | — | 2 551 KB | | GIF | **2 676 KB for 6s** | — | A GIF is ~11× the mp4 for the same seconds, and dithers to 256 colours on a dark UI. - Proven end to end against six placeholder scenes built from the repo's own app screenshots: 21s loop → mp4 480 KB, webm 609 KB, poster 83 KB. ## 2026-10-12 (afternoon) — gate 22 also refuses an unscoped bare element rule - `site_gates.py` gate 22 gains a second half: a rule in `site.css` whose selector starts with a bare `nav`, `header`, `main` or `aside` and is not scoped `body:not(.shell)` / `.shell` is a failure, because those elements ARE the shell (`