# Shipping the video to a landing page

## Hosting

- Upload the **web encode** (`-web.mp4`), not the master: about half the bytes, visually close.
- Use the site's existing asset CDN and folder convention; serve `video/mp4` with range requests
  (check with `curl -I`: `accept-ranges: bytes`).
- If the CDN caches assets as immutable (`cache-control: public, max-age=31536000, immutable`),
  **every new version needs a new file name** (`…-mobile-v2.mp4`). Re-uploading to the same key
  leaves visitors on the old cut for a year.
- Check the key is free before uploading and never delete or overwrite production objects without
  being asked; leave superseded files in place and mention them.

## Markup

One `<video>`, one `<source>` per cut; the browser picks by media query at load and downloads only
that file:

```tsx
<div className="demo-frame">
  <video className="demo-video" width={1920} height={1080}
         autoPlay muted loop playsInline preload="auto"
         aria-label="One sentence describing what the video shows.">
    <source media="(min-width: 640px)" src={DESKTOP_16x9_URL} type="video/mp4" />
    <source src={MOBILE_4x5_URL} type="video/mp4" />
  </video>
</div>
```

```css
.demo-frame { max-width: 60rem; margin-inline: auto; padding: 0.5rem; border-radius: 1.75rem;
  background: #e4e4e7; box-shadow: 0 0 0 1px rgb(0 0 0 / 0.1), 0 8px 16px -8px rgb(0 0 0 / 0.18),
  0 28px 48px -16px rgb(0 0 0 / 0.28); }
.demo-video { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9;
  border-radius: 1.25rem; background: #fff; object-fit: cover; }
@media (max-width: 639px) { .demo-video { aspect-ratio: 4 / 5; } }
```

- `autoPlay` needs `muted` and `playsInline` (iOS). Check the server HTML really contains
  `muted=""`; some frameworks set it only as a property after hydration, which blocks autoplay.
- The `aspect-ratio` reserves space before the video loads, so nothing below it shifts.
- A white video on a white page needs a visible frame (a bezel like the site's device mockups),
  and usually a narrower width than the full content column.
- No `controls` if the brief is "just loop"; say once that autoplaying motion without a pause
  control conflicts with reduced-motion preferences, and offer a poster-only fallback for
  `prefers-reduced-motion: reduce`.

## Verify in a real browser

```js
// Playwright, per viewport (1440×900 and 390×844)
const v = page.locator(".demo-video");
await v.scrollIntoViewIfNeeded(); await page.waitForTimeout(4000);
await v.evaluate((el) => ({ src: el.currentSrc, w: el.videoWidth, h: el.videoHeight,
  paused: el.paused, t: el.currentTime, loop: el.loop, muted: el.muted, controls: el.controls }));
await v.evaluate((el) => { el.currentTime = el.duration - 0.3; });   // then check it wrapped to ~0.9 s
```

Expect the desktop file at ≥ 640 px and the 4:5 file below, `paused: false`, no page-wide
horizontal overflow, no console errors.

## Layout shift

If someone reports the page "jumping", measure before changing anything:

```js
new PerformanceObserver((list) => {
  for (const e of list.getEntries()) console.log(e.startTime, e.value,
    e.sources.map((s) => [s.node, s.previousRect, s.currentRect]));
}).observe({ type: "layout-shift", buffered: true });
```

Record the bounding boxes of the suspect sections over time as well. Shifts often come from
something next to the video (an auto-advancing preview that reuses DOM nodes between states, or
fonts that load late), not from the video itself.
