# Capturing the real UI

The ad is only as convincing as its captures. Capture the running product (local dev server or
production), never redraw its UI by hand.

## Sizes

| Use | Viewport | DPR | Shot |
|---|---|---|---|
| phone screen (`hook.screen`, `proof.after`, `gallery.variants`) | 390 × 844 | 3 | the scrollable content element, full height |
| desktop UI (`ui.states`) | 1600 × 1000 | 2 | viewport |
| wall cards (`gallery.wall`) | 390 × 844 | 2 | top of each output, cropped to 3:4, resized to 540 × 720 |

Phone captures are placed at 390 CSS px wide, so a DPR-3 capture stays sharp at any scale the
ad uses. Crop tall captures to what the ad can show (about 6000 px at DPR 3) and save as JPEG q90;
the page loads faster and renders the same.

## capture.cjs recipes

```bash
# element screenshot of a phone-width page
node $S/scripts/capture.cjs --url http://localhost:3000/p/demo --out a/phone.png \
  --width 390 --height 844 --dpr 3 --selector "main"

# desktop app with a first-run tour skipped and a dish selected
node $S/scripts/capture.cjs --url http://localhost:3000/editor --out a/ui-2.png \
  --width 1600 --height 1000 --dpr 2 --storage "onboarding-tour=done" --click "text=House Double"
```

The script waits for fonts and images, hides `nextjs-portal` (Next.js dev badge) plus anything in
`--hide`, and parks the pointer in a corner so hover states don't leak into the shot. Look at
every capture before using it; dev badges, cookie banners, toasts and "unsaved changes" chrome
are the usual leftovers.

## Pages behind a login

Add a temporary route in the app (for example `app/ad-capture/page.tsx`) that renders the real
components with demo data and no auth: the editor component with a hand-built record, the public
view component with sample content. Read options from the query string so one route produces
every state (`?preset=a`, `?price=19`, `?mode=editor`). Delete the route when the captures are
done and say so in the report. Don't commit it.

## One value changed (proof beat)

Capture the same screen twice through the real rendering path, changing exactly one value
(price, status, count), for example with a query parameter on the temporary route. Then:

```bash
python3 $S/scripts/diff-box.py a/phone.png a/phone-after.png --dpr 3
# css px [364, 537, 8, 13]  → widen to the whole value: box: [345, 533, 28, 20]
```

The diff often covers only the digits that changed; widen the box to frame the whole value.
If the layout shifted between the two captures (the diff box is huge), the change was not
isolated; fix the data, not the box.

## UI click coordinates

`ui.click` and `ui.rings` are CSS px of the desktop capture. Get them from the element's
bounding box while capturing (`locator.boundingBox()` in a one-off script) or by measuring the
capture at half size. The click should hit what really opens the "after" state (the same click
you performed with `--click`).

## Demo data hygiene

- Use one coherent fictional customer across all captures (same restaurant, same shop).
- Strip details that compete with a beat's point (a dietary tag next to the price that changes).
  Do it in the demo data and re-capture; don't paint over pixels.
- Keep real product chrome (buttons, labels, the product's own fonts); that is the evidence.
