# App Store Screenshot Team — Canonical Template (v1.3.0)

The reusable generation template for the App Store Screenshot Team. Copy
these files into your project at a pinned version — **do not edit the
template**. Change your own config instead.

Output dimensions are **locked** to the eight pairs App Store Connect
accepts as screenshot uploads. Every PNG written by the template is
guaranteed to upload without the rejection error
"One or more screenshots have invalid dimensions /
一张或多张截屏的尺寸存在错误".

### Why generated screenshots can still be rejected before v1.3.0

(The three failure modes below caused the "一张或多张截屏的尺寸存在错误"
rejection even with v1.1.0/v1.2.0's device-preset allow-list in place.)

Even when the device preset declares the correct pair, the final PNG can
land a pixel off — and App Store Connect rejects the whole set — for
three reasons:

1. `rsvg-convert -w W -h H` rounds to `W±1 x H±1` on some librsvg
   versions when the SVG viewBox does fractional math.
2. The input screenshot carries EXIF orientation, so the first decode
   silently swaps width/height.
3. An intermediate `-resize` / `-composite` leaves the canvas a pixel
   larger than the base.

**v1.3.0 closes every one:**

- Every `rsvg-convert` output is piped through `magick -resize WxH!` to
  force exact pixel dimensions (the `!` disables aspect preservation —
  a sub-0.1% stretch on a gradient is invisible).
- The input screenshot is read with `-auto-orient` so EXIF rotation is
  consumed before any geometry is computed.
- The final composite ends with `-gravity NorthWest -extent WxH` so the
  canvas is pinned to exactly the accepted pair regardless of upstream
  drift.
- `assert_accepted_dimensions()` runs on every output as the last line
  of defense — the build fails loud with a "closest accepted pair +
  delta" hint on any remaining mismatch.

**If you are upgrading, the fix is `curl -O` the new `generate.py`.
Nothing else changes.**

## Files

- `generate.py` — the canonical generator. Renders the default `headline-frame`
  composition (caption band on top of a rounded-corner device screenshot,
  unified by a continuous gradient).
- `screenshots.config.example.json` — example batch config covering 2 locales,
  2 device classes, 6 scenes.

## Install (one-time)

```bash
# Download the template into your project
mkdir -p scripts config
curl -o scripts/generate.py \
    https://www.teamsmarket.com/templates/app-store-screenshot-team/generate.py
curl -o config/screenshots.config.json \
    https://www.teamsmarket.com/templates/app-store-screenshot-team/screenshots.config.example.json

# Tool deps
# macOS
brew install imagemagick librsvg
# Linux
apt-get install -y imagemagick librsvg2-bin fonts-noto-cjk
```

## Single frame (ad-hoc)

```bash
python scripts/generate.py \
    --input screenshots/en-US/iphone-6.7-01_home.png \
    --device iphone-6.7 \
    --lang en-US \
    --title "Your Local Audio Library" \
    --subtitle "Import, organize, and process files on device — no account, no cloud."
```

Output lands at `output/{auto-timestamp}/en-US/iphone-6.7/iphone-6.7-01_home.png`
(at exactly 1284 × 2778 — the App Store Connect-accepted 6.7"/6.9" iPhone
slot) unless you pass `--output <path>`.

## Batch (from config)

```bash
python scripts/generate.py --config config/screenshots.config.json
```

Enumerates `devices × locales × scenes` and writes each frame to
`output/{run-id}/{lang}/{device}/{scene}.png`. Use `--run-id launch-2026-q2`
to override the auto-timestamp with a meaningful name.

## Inputs

Raw device screenshots live at:

```
screenshots/{lang}/{device}-{scene}.png
```

For example: `screenshots/en-US/iphone-6.5-01_home.png`.

## Supported devices

```bash
python scripts/generate.py --list-devices
```

Exactly the four App Store Connect-accepted iPhone slots and four iPad
slots — no others:

| Slug          | Portrait    | Landscape   | App Store Connect slot                          | Default |
|---------------|-------------|-------------|-------------------------------------------------|---------|
| `iphone-6.5`  | 1242 × 2688 | 2688 × 1242 | 6.5" — XS Max, 11 Pro Max                       |         |
| `iphone-6.7`  | 1284 × 2778 | 2778 × 1284 | 6.7"/6.9" — 12 → 17 Pro Max, all Plus models    | ✅       |
| `ipad-12.9`   | 2048 × 2732 | 2732 × 2048 | 12.9" iPad Pro                                  |         |
| `ipad-13`     | 2064 × 2752 | 2752 × 2064 | 13" iPad Pro (M4 / M5)                          | ✅       |

iPhone native screen resolutions like 1320 × 2868 (iPhone 17 Pro Max) and
1290 × 2796 (iPhone 14–16 Pro Max) are intentionally **not exposed** —
App Store Connect rejects uploads at those sizes. Modern Pro Max iPhones
ship into the 6.7"/6.9" slot at exactly 1284 × 2778.

Override any per-device **geometry** field in `config.devices.{slug}` —
`caption_h`, `caption_side_pad`, `inner_pad_x/top/bottom`, `radius`,
`title_size_max/min`, `subtitle_size_max/min`, `title_line_gap`,
`caption_title_top`. **`width` and `height` are locked** and cannot be
overridden — the template raises a clear error if you try, because any
other dimension would be rejected by App Store Connect at upload.

## Theme

Theme values are all optional; defaults match the included screenshot set.
Override globally in `config.theme` or per-run via CLI flags:

- `--bg-from`, `--bg-to` — background gradient endpoints
- `--accent-from`, `--accent-to` — accent bar gradient endpoints
- `--title-color`, `--subtitle-color` — text colors

## Output contract

Each run writes to a new subfolder so previous runs are preserved. Every
PNG is verified to be at one of the App Store Connect-accepted dimensions
before the run is considered successful — the template fails fast with a
clear error otherwise.

```
output/
├── 2026-04-23T10-14-02Z/
│   └── en-US/iphone-6.7/01_home.png   # 1284 × 2778
├── 2026-04-23T11-02-55Z/
│   └── ...
└── launch-2026-q2/
    └── ...
```

Submit from `output/{run-id}/` via Fastlane `deliver` or the App Store Connect
API. The upload always succeeds on the first attempt because the dimensions
are guaranteed valid.

## Upgrade policy

The template is versioned. To get new capabilities, bump the pinned version
and regenerate — review the visual diff, then ship. **Never edit the template
in place** — if you need a behavior the template doesn't expose, propose a
config knob to the canonical template upstream so every project benefits.

See: https://www.teamsmarket.com/teams/app-store-screenshot-team
