storebound/skills/store-screenshots/SKILL.md

114 lines
10 KiB
Markdown

---
name: store-screenshots
description: Produce store screenshots and the Play feature graphic for a mobile game in every required size and many languages - capture raw frames from the running game (web build via playwright, iOS Simulator via simctl, Android emulator or device via adb, or a folder of frames from any engine), write per-language marketing copy (incl. RTL Arabic and CJK fonts), render captioned scenes with device frames, review them in a local gallery, A/B variants for store listing experiments / Product Page Optimization, JPEG copies for Play, and upload to both stores. Use when the user needs App Store / Google Play screenshots, preview images, a feature graphic, localized screenshot captions, or screenshot sizes.
---
# Store screenshots
CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun <toolchain>/cli/index.ts`). Needs `ffmpeg`, and `playwright` in the project
(`bun add -d playwright`; it uses Playwright's Chromium or an installed Google Chrome) for rendering and web capture.
iOS Simulator capture needs macOS + Xcode (`xcrun`); Android capture needs `adb` (platform-tools).
## Sizes (what each store wants)
| size key | pixels | where |
|---|---|---|
| ios69 | 1320 x 2868 | App Store 6.9" (`APP_IPHONE_67`), required |
| ios65 | 1284 x 2778 | App Store 6.5" (`APP_IPHONE_65`) |
| ipad13 | 2064 x 2752 | App Store 13" iPad (`APP_IPAD_PRO_3GEN_129`), required when the app runs on iPad; only with `apple.ipad: true` |
| play | 1080 x 1920 | Play phone (2-8, ratio at most 2:1) |
| tab7 | 1080 x 1920 | Play 7" tablet (tablet frames, Android tablet body) |
| tab10 | 1440 x 2560 | Play 10" tablet |
| feature | 1024 x 500 | Play feature graphic (required) |
Sources (re-check before a release; Apple renames display classes often):
- Apple "Screenshot specifications": https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications
(13" iPad: 2064 x 2752 or 2048 x 2732, landscape too, "Required if app runs on iPad"; 1-10 per size; JPEG / PNG,
no alpha or transparency).
- ASC API `ScreenshotDisplayType`: https://developer.apple.com/documentation/appstoreconnectapi/screenshotdisplaytype
(there is no "13 inch" value: the 13" iPad set is `APP_IPAD_PRO_3GEN_129`).
- Play graphics: https://support.google.com/googleplay/android-developer/answer/9866151 (screenshots 320-3840 px, at
most 2:1, JPEG or 24-bit PNG, 8 MB; feature graphic 1024 x 500).
**iPad.** Set `apple.ipad: true` only if the app runs on iPad (iPhone-only apps skip the iPad set). Then the default
sizes include `ipad13` (3:4 layout), `apple screenshots` uploads it, `validate` requires it, and tablet frames default
to the 13" iPad screen (1032 x 1352 CSS px @2x below a 24 px bar), so an `iPad Pro 13-inch` / `iPad Air 13-inch`
simulator screenshot fits after the bar is cropped. Play tablet sizes (tab7 / tab10) reuse the same tablet frames, drawn
in a plain Android tablet body instead of the iPad one.
## Steps
1. **Game switches.** Give the game URL params for screenshots: language, fixed seed, no ads / analytics / purchase
UI, skip tutorial, jump to a level, freeze at a moment. Frames must show real gameplay (store rule).
2. **Capture.** Configure `shots.capture` with a `source` (or pass `--source`), then
`storebound shots capture --lang en --dry` to see the plan, and without `--dry` to capture. Frames land in
`<shots.frames>/<lang>/<phone|tablet>/<shot>.png`, local, not committed.
- `web` (default): `baseUrl`, `query` with `{lang}`, shots: `path` / `waitFor` / `eval` / `delayMs` / `device`.
Phone 440 x 902 @3x and tablet 800 x 1240 @2x by default; the template draws the status bar above.
- `ios-sim` (Unity, Godot, Flutter, native, Capacitor: any Simulator build): `ios.device` (simulator name or UDID,
e.g. a 6.9" iPhone), `ios.bundleId`, optional `ios.app` (.app to install), `ios.args`. Per shot: `args` (launch
arguments, read them with UserDefaults / your engine) and / or `url` (deep link). Language goes in as
`-AppleLanguages` / `-AppleLocale`; status bar is forced to 9:41, full battery. After a cold boot the app is
launched once and given `ios.warmupMs` (default 8000) before the first shot; without it the first frame of a
fresh simulator is often blank. A simulator storebound boots is shut down afterwards (`--keep-booted` keeps it).
For iPad shots set `ios.devices.tablet` (e.g. "iPad Pro 13-inch (M5)") and `device: 'tablet' | 'both'` per shot.
- `android-emu`: `android.package`, optional `serial`, `activity`, `apk`. Per shot: `url` (VIEW intent) and / or
typed `extras`. Demo-mode status bar; per-app language on Android 13+ (older: pass `{lang}` in an extra).
Nothing is wiped or rebooted.
- `folder`: frames made any other way; `shots capture` (and `shots check`) only verify that every frame the scenes
use exists with the right aspect ratio and resolution.
`{lang}`, `{locale}`, `{shot}` are replaced in args, extras and URLs. Simulator / emulator screenshots lose
`cropTop` rows (the status bar) so they fit the template: by default the frame's bar share per device class
(162 px on a 6.9" iPhone, 48 px on a 13" iPad with `apple.ipad`); set `cropTop: 120` or `{ phone, tablet }` for
other devices, and `shots.phone` / `shots.tablet` to that device's screen in points. Capture warns when a frame's
aspect does not match. Give the game time to load: `delayMs` (simulator / emulator; 2500 default is short for a
cold start) or `waitFor` (web).
3. **Copy.** `store/shots/copy.json`: per language, per scene: `eyebrow`, `title` [white line, gold line], `sub`,
`badges` [[big, small]]. Short and true (only claim what is in the game); about 15 Latin characters per title line.
Natural marketing tone per language, not literal translation; use the game's own terms.
4. **Fonts.** `storebound shots fonts` downloads title/body fonts and a Noto subset for every non-Latin script found in
the copy (CJK, Arabic, Hebrew, Devanagari, Thai, Cyrillic/Greek). Re-run after copy edits.
5. **Scenes.** `shots.scenes`: palette (fire, violet, blast, green, night, sky, gold, candy or custom), frames (one
front phone + optional back phones with x / dy / scale / rotate), badge slots (y, side, icon, hot, stars).
6. **Render.** `storebound shots render --lang en` first, review `store/renders/en/sheet.png` (contact sheet, sizes
packed into a grid), then
all languages in the background: `storebound shots render > .storebound/render.log 2>&1 &`.
7. **JPEG for Play.** `storebound shots jpeg` (q92, about 4x smaller than PNG; App Store stays PNG).
8. **Check.** `storebound shots check` (read-only, exit 1 on errors): pixel sizes (Apple: a listed size, portrait or
landscape; Play screenshots 320-3840 px at most 2:1; feature graphic exactly 1024 x 500), alpha, Play JPEG /
24-bit PNG and 8 MB, counts per set (Apple 1-10, Play 2-8), text overflow flags (warnings). `--variants` also checks
A/B variant folders. `storebound validate` runs the same checks on the renders folder and lists errors as
blockers next to the store state (`--no-local` skips them).
9. **Review.** `storebound shots gallery` writes `<renders>/index.html`: one self-contained page (no network, open it
from disk) with every language x size x scene, app previews with posters, variant tabs, filters (language, size,
only issues, search) and light / dark. Tiles with store check errors are outlined red, warnings amber, with the
message under the tile. Show the owner this page (or a screenshot of it) before uploading.
10. **Upload.** `storebound play images` and `storebound apple screenshots` (both idempotent, background + log).
## A/B variants and store experiments
`shots.variants.<id>`: `scenes` (order / subset), `copy` (a JSON merged over the base copy, only what changes),
`palettes` (per scene). `storebound shots render --variant b` (or `--variants` for all) writes
`<renders>/b/<lang>/...`; `shots check --variants`, `shots gallery` (tabs) and `shots jpeg --variant b` cover them.
`play images --variant b` / `apple screenshots --variant b` upload a variant to the MAIN listing (replacing the base
images there), so only do that after a test has a winner. What the stores offer, honestly:
- **Google Play store listing experiments** (Play Console > Grow users > Store listing experiments): up to 2 variants
against the current listing; one default-graphics experiment or up to 5 localized experiments at a time; icon,
feature graphic, screenshots (+ descriptions for localized ones); stops automatically after 6 months. Console only:
the Play Developer API has no experiments endpoint, so storebound renders and checks the variant images and you
upload them in the experiment form yourself.
https://support.google.com/googleplay/android-developer/answer/6227309
- **App Store Product Page Optimization** (App Store Connect > your app > Product Page Optimization): up to 3
treatments against the original, one test at a time, up to 90 days; icon, screenshots and app previews, localized
per treatment; new metadata goes through App Review; alternate icons must ship in the binary; shown on iOS / iPadOS
15+. https://developer.apple.com/app-store/product-page-optimization/
The App Store Connect API has the resources (`appStoreVersionExperiments` v2, treatments, treatment localizations
with their own screenshot / preview sets), but storebound does not create or start experiments: set the test up in
App Store Connect and upload the treatment screenshots there (the variant folder already has the right sizes).
- Test one thing per experiment (usually the first screenshot or the order), give it real traffic, and do not read
results early.
## Gotchas
- Output is 24-bit RGB PNG: Play rejects alpha / palette PNGs, Apple rejects alpha.
- Arabic: `rtl: true` on the language; numbers stay LTR inside RTL text automatically.
- Japanese breaks by phrase, Korean at spaces; long words shrink the line rather than wrap.
- Check every language's sheet for overflow before uploading; German / Russian titles run long.
- Play: 8 per type max; Apple: 10 per set max.
- Do not include prices, "free" badges for paid items, or other platforms' UI.