# Arealytics Pro app launch — EDM

Source for the app-launch email. The HTML in `dist/` is generated, not hand-edited —
change `src/build_email.py` and rebuild, or your next edit will be overwritten.

## Layout

```
src/build_email.py    the template. Palette, type scale, cards and hero all live here
src/make_qr.py        regenerates the store QR codes
assets/               logos, QR codes, app screenshot
dist/                 generated HTML — do not edit by hand
```

## Build

```bash
python3 -m pip install pillow qrcode          # only needed for make_qr.py
python3 src/build_email.py                    # -> dist/*.html
BOLD_ALL=1 python3 src/build_email.py         # variant with bold card headlines
```

Two builds come out of each run:

- `arealytics-app-edm-ESP.html` — placeholders intact, for the ESP
- `arealytics-app-edm-PREVIEW.html` — images base64-embedded, opens in any browser

**Never send the PREVIEW build.** Gmail and Outlook strip `data:` URIs, so the QR
codes and logo silently vanish. It exists to be looked at, nothing else.

## Placeholders in the ESP build

| Token | Replace with |
|---|---|
| `{{LOGO}}` | hosted `assets/arealytics-logo-white-2x.png` (600px, displays at 158px) |
| `{{PHONE}}` | hosted app home-screen render — **see note below** |
| `{{QR_APPLE}}` | hosted `assets/email-qr-app-store-300.png` |
| `{{QR_GOOGLE}}` | hosted `assets/email-qr-google-play-300.png` |
| `{{UNSUBSCRIBE}}` | ESP unsubscribe merge tag |
| `{{PREFERENCES}}` | ESP preference-centre merge tag |

**Outstanding:** `assets/app-home-screen-2x.png` is upscaled from a 270px screenshot
in the release notes. It is soft. Replace it with a real 2x export from design before
this goes out.

## Store links

- App Store — `https://apps.apple.com/au/app/arealytics/id6759286922`
- Google Play — `https://play.google.com/store/apps/details?id=com.arealytics.app`

AU storefront on the Apple link is deliberate: `/us/` redirects Australian users but
costs a hop and shows US pricing on the way. QR codes carry no campaign tracking —
Google Play accepts a `referrer` param, Apple needs a provider token.

## Design constraints that are easy to break

**Palette is four colours.** Cobalt `#2350C0`, green `#00C896`, near-black `#171717`,
white. Deep cobalt `#16307A` and tint `#EFF3FE` are shades of cobalt, not new hues.
Do not mix a new colour to solve a contrast problem — use a different brand colour at
that size.

**Green cannot carry small type.** 1.95:1 on the card tint, 3.26:1 on cobalt. It
clears AA only at display sizes. Eyebrows take cobalt on light grounds, white on
cobalt.

**Two-column blocks use `display:inline-block` divs with MSO ghost tables**, not
`display:block` on `<td>`. Setting `display:block` on a table cell does not reliably
stack columns in Chromium-based clients — it generates anonymous table boxes and the
columns stay side by side. Inline-block wraps naturally with no media query.

**Poppins does not load in Gmail or Outlook.** They fall back to Century Gothic (Win)
/ Avenir Next (Mac) at normal weight. Body copy is ExtraLight 200 at 12px, so roughly
half the list sees a heavier, more legible email than the preview shows. Nothing in
the design depends on ExtraLight to be readable — keep it that way.

**Outlook ignores `border-radius`.** Cards and bands square off. Acceptable.

## Verifying a change

Render and measure — do not eyeball a screenshot:

```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    b = p.chromium.launch()
    for w in (700, 390, 320):
        pg = b.new_page(viewport={"width": w, "height": 900})
        pg.goto("file:///abs/path/dist/arealytics-app-edm-PREVIEW.html")
        a = pg.query_selector("img[alt*='App Store']").bounding_box()
        g = pg.query_selector("img[alt*='Google Play']").bounding_box()
        print(w, "stacked" if abs(a["y"] - g["y"]) > 20 else "side-by-side")
```

`height / line-height` gives a headline's true line count. Bold sets wider than
ExtraLight, so any weight change re-wraps headlines — re-measure, then resize.

QR codes: decode with `pyzbar` after downscaling to the display size. Current codes
are error-correction level H and scan down to 120px.
