shots: Mac App Store screenshots (mac size, APP_DESKTOP to the macOS version), a landscape layout (Mac window or bare; Mac, Play Chromebook, Play tablets), safe margins in every template checked by shots check from the render's layout boxes, a contact sheet timeout that scales with images, sizes and load; pitfall 80

This commit is contained in:
CRTkafa 2026-10-20 03:29:19 +07:00
parent 30488c512d
commit e59539fec8
26 changed files with 532 additions and 67 deletions

View file

@ -164,6 +164,11 @@ export interface ShotsConfig {
phone?: { w: number; h: number; dpr?: number; bar?: number };
/** Tablet frames: default 1032 x 1352 @2x (13" iPad minus its 24 px bar) with apple.ipad, else 800 x 1240 @2x. */
tablet?: { w: number; h: number; dpr?: number; bar?: number };
/** Landscape layout (Mac, Chromebook, and the Play tablet slots with playTablets): copy on one side, the frame on the
* other. Frames: <frames>/<lang>/landscape/<name>.png at `size` CSS px (default 1440 x 900 @2x). frame: 'window' (a
* Mac window around the game, the default for mac) or 'none' (the bare frame, the default for Play sizes). side:
* where the copy goes (default 'left'). playTablets: render tab7 / tab10 landscape (1920 x 1080 / 2560 x 1440). */
landscape?: { frame?: 'window' | 'none'; side?: 'left' | 'right'; playTablets?: boolean; size?: { w: number; h: number; dpr?: number } };
/** Optional capture of raw frames (web build, iOS Simulator, Android emulator / device, or your own folder). */
capture?: ShotsCapture;
/** A/B variants: other scene orders / copy, rendered into <out>/<variant>/<lang>/... (`shots render --variant b`). */
@ -574,6 +579,9 @@ export interface ShipConfig {
* tablet renders exist under the renders folder, else false. false = `shots render` / `shots check`, `validate` and
* `play images` never ask for them; true = missing tablet sets are errors. */
tablets?: boolean;
/** Also render Chromebook screenshots (size key `chromebook`, 1920 x 1080, landscape layout). The Play API has no
* Chromebook image type: add them in the Play Console's main store listing (play images says so). */
chromebook?: boolean;
/** Promo video: a YouTube URL (public or unlisted, ads off, embeddable). Play takes no video upload. Sent by `play listing`. */
video?: string;
/** Cloud Storage bucket of the Play Console report exports, e.g. "pubsite_prod_rev_<number>" (Play Console >
@ -626,6 +634,10 @@ export interface ShipConfig {
* Optional now, reportedly required from April 2027 for iOS 27.1 SDK builds. Not uploaded while the API has no
* display type for them (cli/shots/spec.ts IPHONE_DUO_DISPLAY). */
iphoneDuo?: boolean;
/** Also render, check and upload Mac App Store screenshots (size key `mac`, 2880 x 1800, 16:10; Apple also takes
* 2560 x 1600, 1440 x 900, 1280 x 800). Landscape layout from <frames>/<lang>/landscape/ (shots.landscape). Uploaded
* as APP_DESKTOP to the macOS version: `apple screenshots --platform macos`. */
mac?: boolean;
/** "Header and Search Results" creative assets (`storebound apple creative-assets`, iOS / iPadOS 27+). Files under
* <renders>/<lang>/creative/: header.{png,jpg,mov,m4v,mp4} (3840x1646, 21:9), search.{...} (3:2, 1920x1280 to
* 3840x2560) or universal.png (5244x2950) for both. `header` / `search` false skip that slot; `universal` is a
@ -1185,6 +1197,8 @@ export function validateConfig(c: unknown): string[] {
for (const k of ['copy', 'frames', 'out'] as const) if (!s[k]) e.push(`shots.${k}: missing`);
if (s.style !== undefined && s.style !== 'game' && s.style !== 'app') e.push('shots.style: game | app');
if (!s.scenes?.length) e.push('shots.scenes: at least one scene');
if (s.landscape?.frame !== undefined && s.landscape.frame !== 'window' && s.landscape.frame !== 'none') e.push("shots.landscape.frame: 'window' | 'none'");
if (s.landscape?.side !== undefined && s.landscape.side !== 'left' && s.landscape.side !== 'right') e.push("shots.landscape.side: 'left' | 'right'");
if (s.feature !== undefined) {
if (!s.feature || typeof s.feature.frame !== 'string' || !s.feature.frame) e.push('shots.feature.frame: a frame name');
if (s.feature?.fit !== undefined && s.feature.fit !== 'contain' && s.feature.fit !== 'bleed') e.push("shots.feature.fit: 'contain' | 'bleed'");