capture-flow 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,6 +25,24 @@ npm install --save-dev playwright
25
25
  npx playwright install chromium
26
26
  ```
27
27
 
28
+ ## Install as an agent skill
29
+
30
+ The repository ships a `capture-flow` skill so coding agents know the manifest
31
+ contract, capture rules, and failure fixes without reading this README first.
32
+
33
+ ```bash
34
+ npx skills add adelpro/capture-flow
35
+ ```
36
+
37
+ Add `-g` for a global install, or `-a claude-code -a codex -a opencode` to target
38
+ specific agents. The skill also ships inside the npm package, so a local install works:
39
+
40
+ ```bash
41
+ npx skills add ./node_modules/capture-flow
42
+ ```
43
+
44
+ Skill source: [`skills/capture-flow/SKILL.md`](skills/capture-flow/SKILL.md).
45
+
28
46
  ## Quick start
29
47
 
30
48
  A **flow** describes how to capture. A **manifest** describes how to frame it.
@@ -130,7 +148,7 @@ Because steps are plain commands, a native flow is just as natural as a web one:
130
148
  | `slides[].templateSlide` | no | Which template slide layout to use (defaults to the slide's index). |
131
149
  | `slides[].captures` | yes | Device family → image path. |
132
150
 
133
- Device families: `iphone`, `iphone-landscape`, `ipad`, `ipad-landscape`, `android-phone`, `android-landscape`, `android-tablet`, `mac`, `watch`.
151
+ Device families: `iphone`, `iphone-landscape`, `ipad`, `ipad-landscape`, `android-phone`, `android-landscape`, `android-tablet`, `mac`, `watch`, `apple-tv`, `vision`, `play-feature`.
134
152
 
135
153
  Store sizes: `ios-6.9`, `ios-6.9-b`, `ios-6.5`, `ios-6.5-b`, `ios-6.3`, `ios-6.1`, `ipad-13`, `ipad-12.9`, `ipad-11`, `play-phone`, `play-phone-hd`, `play-tablet-7`, `play-tablet-10`, plus the corresponding `-land` ids. Run `build` with an unknown id to get the full list in the error.
136
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capture-flow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Turn raw simulator and browser captures into App Store and Google Play screenshots.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,7 +31,8 @@
31
31
  "files": [
32
32
  "bin",
33
33
  "src",
34
- "vendor"
34
+ "vendor",
35
+ "skills"
35
36
  ],
36
37
  "scripts": {
37
38
  "build": "tsx src/cli.ts build",
@@ -0,0 +1,248 @@
1
+ ---
2
+ name: capture-flow
3
+ description: Turn raw device or browser captures into App Store and Google Play screenshot sets. Use when asked to generate, polish, resize, frame, or validate store screenshots, to add headlines and device frames to app screens, or to set up a repeatable screenshot pipeline with Playwright, simctl, adb, or Maestro.
4
+ metadata:
5
+ author: adelpro
6
+ version: "0.1.1"
7
+ ---
8
+
9
+ # capture-flow
10
+
11
+ `capture-flow` is a CLI that takes plain screenshots plus a small JSON manifest and
12
+ renders exact-size store assets with device frames and composed copy.
13
+
14
+ It has three stages:
15
+
16
+ 1. **capture** — run your own screenshot commands (Playwright, `simctl`, `adb`, Maestro, anything).
17
+ 2. **build** — map each raw capture to a slide and render every store size.
18
+ 3. **verify** — check that every PNG is the exact size, 8-bit RGB, and has no alpha.
19
+
20
+ Do not invent your own resizing pipeline. Capture at native resolution, let `build` do the rest.
21
+
22
+ ## Setup
23
+
24
+ ```bash
25
+ npm install --save-dev capture-flow
26
+ npx capture-flow --help
27
+ ```
28
+
29
+ Node.js **22.18+** is required. Playwright, Xcode, or Android tools are only needed for
30
+ the capture stage; `build` and `verify` run on Node alone.
31
+
32
+ If the project already has `capture-flow` in `package.json`, use `npx capture-flow`
33
+ or the local `node_modules/.bin/capture-flow`.
34
+
35
+ ## Workflow
36
+
37
+ 1. **Decide the target set** — device families, store sizes, languages, and slide count.
38
+ 2. **Write a capture command** that saves full-resolution PNGs into a folder.
39
+ 3. **Write `flow.json`** to run that command (or run it manually first).
40
+ 4. **Write `shots.json`** mapping captures to slides.
41
+ 5. **Run** everything and read the warnings.
42
+ 6. **Fix** overflow / missing-screen warnings, rebuild.
43
+ 7. **Verify** and inspect the first image of each output folder.
44
+
45
+ ```bash
46
+ npx capture-flow run --flow flow.json
47
+ npx capture-flow verify --dir store-assets
48
+ ```
49
+
50
+ Use `--json` on `build`, `run`, or `verify` when another program consumes the result.
51
+
52
+ ## Capture rules
53
+
54
+ These prevent the most common bad output.
55
+
56
+ - Capture at the device's **native pixel size**, never upscale. The renderer can scale
57
+ down but cannot invent detail.
58
+ - Match the frame's screen aspect ratio. A mismatched capture is cropped or distorted.
59
+ - No transparency. `verify` rejects alpha; use an opaque background when capturing.
60
+ - One file per screen per family. Name them predictably (`iphone-01.png`, `android-01.png`, …).
61
+ - Hide dev tooling, cursors, toasts, cookie banners, and consent dialogs before the shot.
62
+ - Wait for real content (fonts, data, animation end) before `screenshot()`. A skeleton
63
+ or empty state is not a store screenshot.
64
+ - Keep text in screenshots readable at thumbnail size; avoid dense tables.
65
+
66
+ Reference dimensions (portrait):
67
+
68
+ | Family | Capture at | Typical CSS viewport |
69
+ | --- | --- | --- |
70
+ | `iphone` | 1320 × 2868 | 440 × 956 @3 |
71
+ | `ipad` | 2064 × 2752 | 768 × 1024 @2.6875 |
72
+ | `android-phone` | 1080 × 1920 or higher (9:16) | 360 × 640 @3 |
73
+ | `android-tablet` | 1600 × 2560 (16:10) | 800 × 1280 @2 |
74
+ | `mac` | 2880 × 1800 | — |
75
+
76
+ Native capture recipes for Playwright, iOS Simulator, and Android are in
77
+ [references/capture-recipes.md](references/capture-recipes.md).
78
+
79
+ ## flow.json
80
+
81
+ A flow is an ordered list of shell commands. Paths are relative to the flow file.
82
+
83
+ ```json
84
+ {
85
+ "manifest": "./shots.json",
86
+ "out": "./store-assets",
87
+ "captures": [
88
+ { "name": "web (Playwright)", "command": "node capture.mjs" },
89
+ { "name": "iOS", "command": "xcrun simctl io booted screenshot captures/ios-01.png" }
90
+ ]
91
+ }
92
+ ```
93
+
94
+ | Field | Required | Meaning |
95
+ | --- | --- | --- |
96
+ | `captures[].command` | yes | Shell command, run with the system shell. |
97
+ | `captures[].name` | no | Label in the log. |
98
+ | `captures[].cwd` | no | Working directory, relative to the flow file. |
99
+ | `captures[].env` | no | Extra environment variables for the step. |
100
+ | `manifest` | no | Manifest used by `run`. |
101
+ | `out` | no | Output directory used by `run`. |
102
+
103
+ Steps stop at the first non-zero exit code. Use `capture-flow capture --flow flow.json`
104
+ to run only the capture stage.
105
+
106
+ ## shots.json
107
+
108
+ ```json
109
+ {
110
+ "app": "My App",
111
+ "template": "noor",
112
+ "languages": ["ar-SA"],
113
+ "sizes": ["ios-6.9", "ios-6.3", "ipad-13", "play-phone", "play-tablet-10"],
114
+ "slides": [
115
+ {
116
+ "templateSlide": 0,
117
+ "headline": "استمع *بخشوع*",
118
+ "subtitle": "تلاوات كاملة ومؤشر طيفي.",
119
+ "captures": {
120
+ "iphone": "captures/iphone-01.png",
121
+ "android-phone": "captures/android-01.png",
122
+ "ipad": "captures/ipad-01.png",
123
+ "android-tablet": "captures/android-tablet-01.png"
124
+ }
125
+ }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ | Field | Required | Meaning |
131
+ | --- | --- | --- |
132
+ | `app` | yes | Project name; also used in the report and fastlane paths. |
133
+ | `template` | yes | Built-in template id. |
134
+ | `languages` | yes | Store locales, e.g. `["en-US", "ar-SA"]`. `ar-SA` exports as `ar` on Google Play. |
135
+ | `slides` | yes | Ordered slides; each maps device family → capture. |
136
+ | `out` | no | Output directory, relative to the manifest. Default `store-assets`. |
137
+ | `structure` | no | `stores` (default), `flat`, or `fastlane`. |
138
+ | `sizes` | no | Subset of size ids; defaults to the default size of every family used. |
139
+ | `slides[].headline` | yes | `*word*` marks the accent run; `\n` is a hard break. |
140
+ | `slides[].subtitle` | no | Secondary copy. |
141
+ | `slides[].templateSlide` | no | Which template layout to use; defaults to the slide index. |
142
+ | `slides[].captures` | yes | Device family → image path (`png`, `jpg`, `jpeg`, `webp`). |
143
+
144
+ Families: `iphone`, `iphone-landscape`, `ipad`, `ipad-landscape`, `android-phone`,
145
+ `android-landscape`, `android-tablet`, `mac`, `watch`, `apple-tv`, `vision`, `play-feature`.
146
+
147
+ Store limits: App Store accepts up to 10 slides per size, Google Play up to 8.
148
+ Respect them before designing a long story.
149
+
150
+ Store sizes: `ios-6.9`, `ios-6.9-b`, `ios-6.5`, `ios-6.5-b`, `ios-6.3`, `ios-6.1`,
151
+ `ipad-13`, `ipad-12.9`, `ipad-11`, `play-phone`, `play-phone-hd`, `play-tablet-7`,
152
+ `play-tablet-10`, plus `-land` variants. Full pixel dimensions are in
153
+ [references/store-sizes.md](references/store-sizes.md).
154
+
155
+ Every slide must supply every family you want in the output. A slide without a family
156
+ produces a placeholder warning and an image with an empty frame — treat that as a bug.
157
+
158
+ ## Choosing a template
159
+
160
+ | Category | Templates |
161
+ | --- | --- |
162
+ | `elegant` | `desert-dune`, `noir-gold`, `paper-and-ink` |
163
+ | `dark` | `midnight-neon`, `noor`, `obsidian` |
164
+ | `minimal` | `launchpad`, `mono-swiss`, `sage` |
165
+ | `gradient` | `aurora`, `glass-prism`, `sunset-boulevard` |
166
+ | `playful` | `bubblegum`, `clay-garden` |
167
+ | `bold` | `citrus-pop`, `loud-and-clear`, `split-tone` |
168
+ | `panorama` | `panorama-peaks`, `panorama-ribbon`, `panorama-waves` |
169
+
170
+ - Arabic-first copy: `noor` (Cairo + Noto Sans Arabic, emerald and gold).
171
+ - Native tablet composition: `mono-swiss`, `noir-gold`, `obsidian`, `panorama-*`.
172
+ - Phone-first templates adapt to iPad/Android tablet by swapping the frame and
173
+ reflowing the tall layout. This is acceptable but less deliberate.
174
+
175
+ A template owns the palette and typefaces. There are deliberately no per-manifest colour
176
+ or font overrides — mixing arbitrary colours into a designed system usually breaks it.
177
+ To customize, copy a template from `node_modules/capture-flow/vendor/wajha/templates/builtin/`,
178
+ edit its `theme`/`typeset`, and register it in your own build. Ask the user before
179
+ forking a template.
180
+
181
+ `templateSlide` selects a composition (hero, tilted, pair, hanging, …). Layouts differ
182
+ per template. To see the available layouts and their default copy, read the template
183
+ file:
184
+
185
+ ```bash
186
+ sed -n '/slides: \[/,/^ ]/p' node_modules/capture-flow/vendor/wajha/templates/builtin/noor.ts
187
+ ```
188
+
189
+ Out-of-range values fail with `"<template>" has <n>` — that number is the layout count.
190
+
191
+ ## Output
192
+
193
+ The default `stores` structure is store-upload ready:
194
+
195
+ ```text
196
+ store-assets/
197
+ App Store/
198
+ ar-SA/
199
+ iPhone 6.9/01.png … 10.png
200
+ iPhone 6.3/01.png …
201
+ iPad 13/01.png …
202
+ Google Play/
203
+ ar/
204
+ Phone/01.png …
205
+ Tablet 10/01.png …
206
+ capture-flow-report.json
207
+ ```
208
+
209
+ `capture-flow-report.json` lists every file with its `width`, `height`, `family`,
210
+ `lang`, and `slide`, plus `warnings`. Read it instead of guessing what was produced.
211
+
212
+ `flat` writes `01-iphone-en-US.png`-style names; `fastlane` writes
213
+ `fastlane/screenshots/<locale>/`.
214
+
215
+ ## Warnings and failures
216
+
217
+ | Symptom | Fix |
218
+ | --- | --- |
219
+ | `text overflows at WxH` | Shorten copy, remove a line break, use a different `templateSlide`, or pick a template with more copy room. |
220
+ | `uses a placeholder screen` | That slide has no capture for the family. Add one or drop the family/size. |
221
+ | `has no screenshot` | Slide → family mapping is incomplete for an exported size. |
222
+ | `Unknown device family` | Use a family from the list above. |
223
+ | `Unknown export size` | Use a size id from the table; `-land` is a suffix, not a family. |
224
+ | `capture not found` | Manifest paths are relative to the manifest file, not the terminal's cwd. |
225
+ | `verify` alpha error | Flatten the capture onto an opaque background; PNG must be 8-bit RGB. |
226
+ | Wrong dimensions in `verify` | Do not post-process built files; re-run `build` for that size. |
227
+
228
+ ## Library use
229
+
230
+ ```ts
231
+ import { build, verify, runCaptureFlow } from 'capture-flow';
232
+
233
+ await runCaptureFlow('flow.json');
234
+ const report = await build({ manifestPath: 'shots.json', outDir: 'store-assets' });
235
+ const result = verify('store-assets');
236
+ ```
237
+
238
+ The package ships TypeScript, so run it through `tsx` or a bundler.
239
+
240
+ ## Example
241
+
242
+ A complete, real setup (Playwright capture of a web app, Arabic RTL, five device
243
+ sizes, 20 output files) lives in the repository:
244
+
245
+ <https://github.com/adelpro/capture-flow/tree/main/examples/open-tilawa>
246
+
247
+ It shows consent-dialog dismissal, waiting for hydration, live audio state, and the
248
+ `عرض الكل` reciter flow. Adapt it rather than starting from a blank page.
@@ -0,0 +1,155 @@
1
+ # Capture recipes
2
+
3
+ `capture-flow` never takes screenshots itself. Each flow step is a shell command, so
4
+ use whatever produces correct native-resolution images.
5
+
6
+ Before building, check every raw capture:
7
+
8
+ ```bash
9
+ magick identify -format '%f %wx%h %[channels]\n' captures/*.png
10
+ # macOS without ImageMagick:
11
+ sips -g pixelWidth -g pixelHeight captures/iphone-01.png
12
+ ```
13
+
14
+ The output must be opaque (no alpha). Flatten if needed:
15
+
16
+ ```bash
17
+ magick in.png -background '#000000' -alpha remove -alpha off out.png
18
+ ```
19
+
20
+ ## Web app with Playwright
21
+
22
+ Install Playwright outside the project if you do not want it in dependencies:
23
+
24
+ ```bash
25
+ npm install --prefix /tmp/pw playwright
26
+ PLAYWRIGHT_BROWSERS_PATH=/tmp/pw npx --prefix /tmp/pw playwright install chromium
27
+ ```
28
+
29
+ `capture.mjs`:
30
+
31
+ ```js
32
+ import { chromium } from 'playwright';
33
+
34
+ const browser = await chromium.launch();
35
+ const context = await browser.newContext({
36
+ viewport: { width: 440, height: 956 },
37
+ deviceScaleFactor: 3, // 440×956 → 1320×2868
38
+ isMobile: true,
39
+ hasTouch: true,
40
+ locale: 'ar-SA',
41
+ timezoneId: 'Asia/Riyadh',
42
+ colorScheme: 'dark',
43
+ serviceWorkers: 'block', // stale caches hide new UI
44
+ });
45
+ const page = await context.newPage();
46
+
47
+ // Seed settings before the app boots (consent, theme, onboarding).
48
+ await context.addInitScript(() => {
49
+ localStorage.setItem('app:telemetry', 'off');
50
+ localStorage.setItem('app:onboarded', 'true');
51
+ });
52
+
53
+ await page.goto('https://example.com', { waitUntil: 'commit', timeout: 120_000 });
54
+ await page.getByRole('button', { name: 'تشغيل' }).waitFor({ timeout: 120_000 });
55
+ await page.waitForTimeout(700); // let fonts, audio state, and animation settle
56
+
57
+ await page.screenshot({ path: 'captures/iphone-01.png' });
58
+ await browser.close();
59
+ ```
60
+
61
+ Rules of thumb:
62
+
63
+ - `waitUntil: 'commit'` plus an explicit UI wait beats `networkidle` on hydrated apps.
64
+ - Hydration on a cold CDN can take 20 s. Give `waitFor` a real timeout.
65
+ - Take one browser context per family, or resize the viewport and repeat.
66
+ - Keep `omitBackground` off; transparency fails `verify`.
67
+ - If a consent dialog appears, dismiss it once with a real click **and** seed the
68
+ setting, so retries stay clean.
69
+ - For audio/video states, start playback and wait for the visible time counter to move.
70
+
71
+ ### When the app depends on a service worker or peer transport
72
+
73
+ If playback in the browser uses a transport the headless run cannot provide (WebTorrent,
74
+ WebRTC), point the request at a local/web seed instead of screenshotting an empty player:
75
+
76
+ ```js
77
+ await context.route('**/webtorrent/**', async (route) => {
78
+ const url = new URL(route.request().url());
79
+ await route.continue({ url: url.toString().replace('/webtorrent/', '/media/') });
80
+ });
81
+ ```
82
+
83
+ Document the fallback in the capture script so the next person knows why it exists.
84
+
85
+ ## iOS Simulator
86
+
87
+ ```bash
88
+ xcrun simctl boot "iPhone 17 Pro Max" 2>/dev/null || true
89
+ xcrun simctl bootstatus booted -b
90
+
91
+ # Deterministic status bar.
92
+ xcrun simctl status_bar booted override \
93
+ --time "9:41" --batteryState charged --batteryLevel 100 \
94
+ --wifiBars 3 --cellularBars 4
95
+
96
+ # Drive the app: deep link, Maestro, or XCTest.
97
+ xcrun simctl openurl booted "myapp://player"
98
+
99
+ xcrun simctl io booted screenshot captures/iphone-01.png
100
+ xcrun simctl status_bar booted clear
101
+ ```
102
+
103
+ `simctl io screenshot` captures at the simulated device's native pixel size. Pick a
104
+ device whose native resolution matches the size you export (iPhone 17 Pro Max →
105
+ 1320 × 2868). For iPad, boot an iPad Pro 13″ simulator.
106
+
107
+ ## Android emulator or device
108
+
109
+ ```bash
110
+ adb shell screencap -p /sdcard/shot.png
111
+ adb pull /sdcard/shot.png captures/android-01.png
112
+ # or, without a temp file:
113
+ adb exec-out screencap -p > captures/android-01.png
114
+ ```
115
+
116
+ Deterministic status bar:
117
+
118
+ ```bash
119
+ adb shell settings put global sysui_demo_allowed 1
120
+ adb shell am broadcast -a com.android.systemui.demo -e command enter
121
+ adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 0941
122
+ adb shell am broadcast -a com.android.systemui.demo -e command battery -e level 100 -e plugged false
123
+ adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show -e level 4
124
+ ```
125
+
126
+ Use an emulator profile with the right resolution (1080 × 2400 for a modern phone,
127
+ 1600 × 2560 for a 10-inch tablet in landscape) and set the locale:
128
+
129
+ ```bash
130
+ adb shell "setprop persist.sys.locale ar-SA; setprop persist.sys.language ar; setprop persist.sys.country SA"
131
+ adb shell am force-stop com.example.app
132
+ ```
133
+
134
+ ## Maestro
135
+
136
+ ```yaml
137
+ appId: com.example.app
138
+ ---
139
+ - launchApp:
140
+ clearState: false
141
+ - tapOn: "Get started"
142
+ - takeScreenshot: captures/android-01
143
+ ```
144
+
145
+ `takeScreenshot` writes `<name>.png` in the working directory of the Maestro run.
146
+
147
+ ## Native app deep links
148
+
149
+ Prefer a deep link or launch argument over scripted tapping: it is stable across UI
150
+ changes and skips onboarding.
151
+
152
+ ```bash
153
+ xcrun simctl openurl booted "myapp://track/123?autoplay=1"
154
+ adb shell am start -a android.intent.action.VIEW -d "myapp://track/123?autoplay=1" com.example.app
155
+ ```
@@ -0,0 +1,91 @@
1
+ # Store sizes
2
+
3
+ Pixel sizes exported by `capture-flow build`. `default` means the size is produced
4
+ when `sizes` is omitted and the family has a capture. `required` marks the size the
5
+ store currently requires.
6
+
7
+ ## App Store — iPhone
8
+
9
+ | id | pixels | devices | default | required |
10
+ | --- | --- | --- | --- | --- |
11
+ | `ios-6.9` | 1320 × 2868 | iPhone 17 Pro Max, 16 Pro Max | yes | yes |
12
+ | `ios-6.9-b` | 1290 × 2796 | iPhone 16 Plus, 15 Pro Max, 15 Plus, 14 Pro Max | no | no |
13
+ | `ios-6.5` | 1284 × 2778 | iPhone 14 Plus, 13 Pro Max, 12 Pro Max | no | no |
14
+ | `ios-6.5-b` | 1242 × 2688 | iPhone 11 Pro Max, XS Max | no | no |
15
+ | `ios-6.3` | 1206 × 2622 | iPhone 17 Pro, 17, 16 Pro | yes | yes |
16
+ | `ios-6.1` | 1170 × 2532 | iPhone 17e, 16e, 14, 13, 13 Pro, 12, 12 Pro | no | no |
17
+
18
+ ## App Store — iPad
19
+
20
+ | id | pixels | devices | default | required |
21
+ | --- | --- | --- | --- | --- |
22
+ | `ipad-13` | 2064 × 2752 | iPad Pro 13″ (M4, M5), iPad Air 13″ | yes | yes |
23
+ | `ipad-12.9` | 2048 × 2732 | iPad Pro 12.9″ (2nd–6th gen) | no | no |
24
+ | `ipad-11` | 1668 × 2420 | iPad Pro 11″ (M4), iPad Air 11″ | no | no |
25
+
26
+ ## Google Play — phone
27
+
28
+ | id | pixels | devices | default | required |
29
+ | --- | --- | --- | --- | --- |
30
+ | `play-phone` | 1080 × 1920 | all Android phones (9:16) | yes | yes |
31
+ | `play-phone-hd` | 1440 × 2560 | all Android phones (9:16, high resolution) | no | no |
32
+
33
+ ## Google Play — tablet
34
+
35
+ | id | pixels | devices | default | required |
36
+ | --- | --- | --- | --- | --- |
37
+ | `play-tablet-7` | 1080 × 1920 | 7-inch Android tablets | yes | no |
38
+ | `play-tablet-10` | 1440 × 2560 | 10-inch Android tablets | yes | no |
39
+
40
+ ## App Store — Mac
41
+
42
+ | id | pixels | default | required |
43
+ | --- | --- | --- | --- |
44
+ | `mac-2880` | 2880 × 1800 | yes | yes |
45
+ | `mac-2560` | 2560 × 1600 | no | no |
46
+ | `mac-1440` | 1440 × 900 | no | no |
47
+ | `mac-1280` | 1280 × 800 | no | no |
48
+
49
+ ## Apple Watch
50
+
51
+ | id | pixels | default | required |
52
+ | --- | --- | --- | --- |
53
+ | `watch-ultra-3` | 422 × 514 | yes | yes |
54
+ | `watch-11` | 416 × 496 | no | no |
55
+ | `watch-ultra-2` | 410 × 502 | no | no |
56
+ | `watch-9` | 396 × 484 | no | no |
57
+ | `watch-6` | 368 × 448 | no | no |
58
+ | `watch-3` | 312 × 390 | no | no |
59
+
60
+ ## Apple TV and Vision Pro
61
+
62
+ | id | pixels | default | required |
63
+ | --- | --- | --- | --- |
64
+ | `tv-4k` | 3840 × 2160 | yes | yes |
65
+ | `tv-hd` | 1920 × 1080 | no | no |
66
+ | `vision-pro` | 3840 × 2160 | yes | yes |
67
+
68
+ ## Google Play feature graphic
69
+
70
+ | id | pixels | default | required |
71
+ | --- | --- | --- | --- |
72
+ | `play-feature` | 1024 × 500 | yes | yes |
73
+
74
+ ## Landscape
75
+
76
+ Append `-land` and swap width and height. Supported for `iphone*`, `ipad*`, and
77
+ `play-phone*` sizes (for example `ios-6.9-land` = 2868 × 1320). Use the matching
78
+ `*-landscape` family with the capture taken in landscape.
79
+
80
+ ## Capture aspect ratios
81
+
82
+ The device screen area uses these ratios. Match them to avoid cropping:
83
+
84
+ | Family | Screen aspect (w:h) | Notes |
85
+ | --- | --- | --- |
86
+ | `iphone` | 1320:2868 | 19.5:9, capture at 1320 × 2868 |
87
+ | `ipad` | 3:4 | 2064 × 2752 |
88
+ | `android-phone` | 20:9 | 1080 × 2400 or 1080 × 1920; 9:16 export crops top/bottom |
89
+ | `android-tablet` | 16:10 | 1600 × 2560 |
90
+ | `mac` | 16:10 | 2880 × 1800 |
91
+ | `watch` | 422:514 | — |