--- 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 /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 `///.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 `/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.`: `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 `/b//...`; `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.