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:
parent
30488c512d
commit
e59539fec8
26 changed files with 532 additions and 67 deletions
|
|
@ -6,7 +6,7 @@ import type { Palette, ShipConfig, ShotScene, ShotsConfig } from '../lib/config'
|
|||
* Dynamic Island, medium display (APP_IPHONE_61, only with apple.iphoneMedium), ipad13 = 13" iPad
|
||||
* (APP_IPAD_PRO_3GEN_129, only for apps that run on iPad), play = Play phone 9:16, tab7 / tab10 = Play 7" / 10"
|
||||
* tablets (tablet frames), feature = Play feature graphic. Rules per size: spec.ts. */
|
||||
export const SIZES: Record<string, { w: number; h: number; device?: 'tablet'; feature?: true }> = {
|
||||
export const SIZES: Record<string, { w: number; h: number; device?: 'tablet' | 'landscape'; feature?: true }> = {
|
||||
ios69: { w: 1320, h: 2868 },
|
||||
ios65: { w: 1284, h: 2778 },
|
||||
'iphone-medium': { w: 1206, h: 2622 },
|
||||
|
|
@ -14,12 +14,59 @@ export const SIZES: Record<string, { w: number; h: number; device?: 'tablet'; fe
|
|||
// iPhone Duo (foldable), only with apple.iphoneDuo: near-tablet aspect, so the tablet frames fill it
|
||||
'iphone-duo-outer': { w: 1398, h: 2034, device: 'tablet' },
|
||||
'iphone-duo-inner': { w: 2007, h: 2853, device: 'tablet' },
|
||||
// Mac App Store (APP_DESKTOP, only with apple.mac): 16:10, landscape layout from <frames>/<lang>/landscape/
|
||||
mac: { w: 2880, h: 1800, device: 'landscape' },
|
||||
play: { w: 1080, h: 1920 },
|
||||
tab7: { w: 1080, h: 1920, device: 'tablet' },
|
||||
tab10: { w: 1440, h: 2560, device: 'tablet' },
|
||||
// Play Chromebook screenshots (only with play.chromebook): 16:9 landscape; the Play API has no Chromebook image type,
|
||||
// so they stay in the renders folder for the Play Console (play images says so)
|
||||
chromebook: { w: 1920, h: 1080, device: 'landscape' },
|
||||
feature: { w: 1024, h: 500, feature: true },
|
||||
};
|
||||
|
||||
/** Landscape renders of the Play tablet slots (shots.landscape.playTablets): Play takes any 320-3840 px size up to 2:1. */
|
||||
export const PLAY_TABLET_LANDSCAPE: Record<string, { w: number; h: number }> = { tab7: { w: 1920, h: 1080 }, tab10: { w: 2560, h: 1440 } };
|
||||
|
||||
/** Pixel size and layout of one size key for a project: the landscape layout for mac / chromebook, and for the Play
|
||||
* tablet slots with shots.landscape.playTablets. frame = the device drawn around a landscape frame ('window' = a Mac
|
||||
* window, 'none' = the bare frame with rounded corners); default window on Mac, none on Play. Pure. */
|
||||
export function renderDims(size: string, shots: Pick<ShotsConfig, 'landscape'> | undefined): { w: number; h: number; landscape: boolean; frame: 'window' | 'none' } {
|
||||
const s = SIZES[size]!;
|
||||
const tabLand = !!shots?.landscape?.playTablets && !!PLAY_TABLET_LANDSCAPE[size];
|
||||
const landscape = s.device === 'landscape' || tabLand;
|
||||
const d = tabLand ? PLAY_TABLET_LANDSCAPE[size]! : s;
|
||||
const frame = shots?.landscape?.frame ?? (size === 'mac' ? 'window' : 'none');
|
||||
return { w: d.w, h: d.h, landscape, frame };
|
||||
}
|
||||
|
||||
/** Safe margins every template keeps around its text and logo (fractions of the image), and the minimum `shots check`
|
||||
* accepts (a little under the target, so rounding never fails a render). */
|
||||
export const SAFE_MARGIN = { x: 0.07, top: 0.05 } as const;
|
||||
export const SAFE_MARGIN_MIN = { x: 0.06, top: 0.04 } as const;
|
||||
export interface TextBox { el: string; x: number; y: number; w: number; h: number }
|
||||
|
||||
/** Text / logo boxes (from the page's own layout, report.json `boxes`) closer to the left, right or top edge than
|
||||
* SAFE_MARGIN_MIN. Pure. */
|
||||
export function marginViolations(boxes: TextBox[], W: number, H: number): string[] {
|
||||
const mx = W * SAFE_MARGIN_MIN.x, mt = H * SAFE_MARGIN_MIN.top, out: string[] = [];
|
||||
for (const b of boxes) {
|
||||
const sides: string[] = [];
|
||||
if (b.x < mx - 0.5) sides.push(`left ${Math.round(b.x)} px`);
|
||||
if (W - (b.x + b.w) < mx - 0.5) sides.push(`right ${Math.round(W - b.x - b.w)} px`);
|
||||
if (b.y < mt - 0.5) sides.push(`top ${Math.round(b.y)} px`);
|
||||
if (sides.length) out.push(`${b.el} is too close to the edge (${sides.join(', ')}; minimum ${Math.round(mx)} px left / right, ${Math.round(mt)} px top)`);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Timeout for the contact sheet page: it decodes every image of the language, so it scales with their count and the
|
||||
* sizes (big Mac / iPad tiles), times the machine's load factor (max(1-min, 5-min load) / cores, 1..5). Never a
|
||||
* fixed 30 s. Pure. */
|
||||
export function sheetTimeoutMs(images: number, sizes: number, load = 1): number {
|
||||
return Math.round((30_000 + images * 2_000 + sizes * 5_000) * Math.min(5, Math.max(1, load)));
|
||||
}
|
||||
|
||||
export const PALETTES: Record<string, Palette> = {
|
||||
fire: { bg: ['#ff3d2e', '#ff7a1a', '#ffc43a'], glow: 'rgba(255,240,150,.95)', ink: '#6e1300', shadow: 'rgba(120,10,0,.45)' },
|
||||
violet: { bg: ['#2b1676', '#6a2fe0', '#ff5fb8'], glow: 'rgba(255,170,240,.75)', ink: '#22095e', shadow: 'rgba(30,0,80,.55)' },
|
||||
|
|
@ -123,7 +170,8 @@ export function frameDims(config: Pick<ShipConfig, 'apple' | 'shots'>): Record<'
|
|||
export const PLAY_TABLET_SIZES: readonly string[] = ['tab7', 'tab10'];
|
||||
|
||||
/** Size keys this project renders by default: Apple sizes only with an apple section (iPad only with apple.ipad, the
|
||||
* 6.3" iPhone only with apple.iphoneMedium, the iPhone Duo sizes only with apple.iphoneDuo),
|
||||
* 6.3" iPhone only with apple.iphoneMedium, the iPhone Duo sizes only with apple.iphoneDuo, mac only with apple.mac,
|
||||
* chromebook only with play.chromebook),
|
||||
* Play sizes only with a play section (both when neither is configured); the Play tablet sizes only when `tablets`
|
||||
* (resolved play.tablets, see resolvedTablets in report.ts; unset here = play.tablets ?? true). */
|
||||
export function defaultSizes(config: Pick<ShipConfig, 'apple' | 'play'>, tablets?: boolean): string[] {
|
||||
|
|
@ -131,6 +179,8 @@ export function defaultSizes(config: Pick<ShipConfig, 'apple' | 'play'>, tablets
|
|||
const tabs = tablets ?? config.play?.tablets ?? true;
|
||||
return Object.keys(SIZES).filter(k => (k === 'ipad13' ? apple && !!config.apple?.ipad : k === 'iphone-medium' ? apple && !!config.apple?.iphoneMedium
|
||||
: k.startsWith('iphone-duo') ? apple && !!config.apple?.iphoneDuo
|
||||
: k === 'mac' ? apple && !!config.apple?.mac
|
||||
: k === 'chromebook' ? play && !!config.play?.chromebook
|
||||
: k.startsWith('ios') ? apple : PLAY_TABLET_SIZES.includes(k) ? play && tabs : play));
|
||||
}
|
||||
|
||||
|
|
@ -173,10 +223,10 @@ export interface SheetTile { file: string; x: number; y: number; w: number; h: n
|
|||
/** Contact sheet layout: one block per size (its scenes side by side, all at the same height; the feature graphic at
|
||||
* half height), blocks packed onto shelves up to `maxW` wide so a project with one or two scenes gets a compact grid
|
||||
* instead of a tall strip. Pure. */
|
||||
export function sheetLayout(rows: { size: string; files: string[] }[], o: { h?: number; gap?: number; maxW?: number } = {}): { w: number; h: number; tiles: SheetTile[] } {
|
||||
export function sheetLayout(rows: { size: string; files: string[]; dims?: { w: number; h: number } }[], o: { h?: number; gap?: number; maxW?: number } = {}): { w: number; h: number; tiles: SheetTile[] } {
|
||||
const H0 = o.h ?? 900, gap = o.gap ?? 24;
|
||||
const blocks = rows.filter(r => r.files.length).map(r => {
|
||||
const d = SIZES[r.size]!, h = d.feature ? Math.round(H0 / 2) : H0, w = Math.round(h * d.w / d.h);
|
||||
const d = r.dims ?? SIZES[r.size]!, h = SIZES[r.size]!.feature || d.w > d.h ? Math.round(H0 / 2) : H0, w = Math.round(h * d.w / d.h);
|
||||
return { r, h, tw: w, w: r.files.length * w + (r.files.length - 1) * gap };
|
||||
});
|
||||
const maxW = Math.max(o.maxW ?? 3600, ...blocks.map(b => b.w));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue