Build a single, self-contained `index.html` — one file, no build step, no frameworks, no external CSS or JS libraries. All CSS in one . Must arm synchronously so there is no flash of unanimated content. - Return immediately if matchMedia('(prefers-reduced-motion: reduce)').matches (no class set, no entrance rule matches, page renders as the finished design). - Add class 'anim' to documentElement synchronously. - boot(): setTimeout(start, 900) as a ceiling, and document.fonts.ready.then(start,start) so the reveal never plays against invisible text; fall back to start() if no fonts API. Wire boot on DOMContentLoaded if readyState === 'loading'. - start(): guard against re-entry, clear the boot timer, addEventListener ('animationend', onEnd, true), set a 2600ms safety timeout to clean(), add 'go'. - onEnd(e): if e.animationName === 'pillIn' && e.target.classList.contains('btn-ghost') -> clean() (the secondary CTA is last on the timeline). - clean(): run once; clear the safety timer, remove the animationend listener, remove both 'anim' and 'go' so every entrance rule stops matching. B) BURGER — at end of , before the video script. - Grab #burger and #menu; bail if missing. - set(open): toggle 'open' on the menu, set aria-expanded, set aria-label to 'Close menu' / 'Open menu'. - Burger click: stopPropagation, toggle. - Click inside the menu on an : close. - Document click outside both menu and burger while open: close. - Escape while open: close and return focus to the burger. C) BACKGROUND VIDEO — LAST element in . It must come after the video markup; placing it in silently no-ops because the elements do not exist yet. - Grab #bgVideoA and #bgVideoB; bail if missing. - Reduced motion: removeAttribute('autoplay') on A, pause both, and set A.currentTime = 0 inside try/catch (autoplay may already have advanced a frame or two; hold on the first, which is the still the hero was composed against). Then return — no cross-fade, no playback. - Otherwise: FADE = 0.9; cur = A, nxt = B, swapping = false. play(v) helper calls v.play() and swallows a rejected promise (some browsers ignore the autoplay attribute until play() is called). Call play(A) up front. - tick(): return if swapping or !cur.duration; return if cur.duration - cur.currentTime > FADE. Otherwise: swapping = true; keep a reference `out = cur`; nxt.currentTime = 0; play(nxt); nxt.classList.add('is-active'); out.classList.remove('is-active'); swap cur/nxt; setTimeout(FADE*1000 + 100) -> out.pause(); out.currentTime = 0; swapping = false; - Attach tick to 'timeupdate' on BOTH videos. - With no JS the loop attribute still carries it: one visible cut per pass, never a freeze. ============================================================ 11. ACCEPTANCE ============================================================ - Desktop 1280x800 reproduces the reference render exactly; scaling the window scales the whole composition proportionally with no reflow above 1160px. - The globe plays continuously with no visible seam at the 10s loop point. - The headline stays two lines and never breaks at the hyphen of "Cross-border" at any width down to 320px. - Nav folds into the burger at 1160px; the panel anchors under it, top-right. - prefers-reduced-motion: reduce -> a still first frame, no entrance, no transitions. - No horizontal scrollbar at any width; html/body keep overflow:hidden.