| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * Renders App Store visionOS screenshots from HTML templates using Puppeteer. |
| 4 | * Apple requires exactly 3840x2160 for Apple Vision Pro screenshots (verify |
| 5 | * against the App Store Connect upload panel at build time — see |
| 6 | * assets/aso/appstore/shared.md and the app-store-optimization skill). |
| 7 | * |
| 8 | * Output: assets/aso/appstore/visionos/{01-chat,02-settings}-3840x2160.png |
| 9 | * |
| 10 | * Usage: node scripts/generate-visionos-graphics.mjs |
| 11 | */ |
| 12 | |
| 13 | import puppeteer from 'puppeteer'; |
| 14 | import { fileURLToPath } from 'url'; |
| 15 | import { dirname, join, resolve } from 'path'; |
| 16 | import { mkdirSync } from 'fs'; |
| 17 | |
| 18 | const __dirname = dirname(fileURLToPath(import.meta.url)); |
| 19 | const repoRoot = resolve(__dirname, '..'); |
| 20 | const templateDir = join(__dirname, 'appstore-templates'); |
| 21 | const outDir = join(repoRoot, 'assets', 'aso', 'appstore', 'visionos'); |
| 22 | |
| 23 | mkdirSync(outDir, { recursive: true }); |
| 24 | |
| 25 | const WIDTH = 3840; |
| 26 | const HEIGHT = 2160; |
| 27 | |
| 28 | const targets = [ |
| 29 | { template: '01-chat.html', output: '01-chat-3840x2160.png' }, |
| 30 | { template: '02-settings.html', output: '02-settings-3840x2160.png' }, |
| 31 | ]; |
| 32 | |
| 33 | console.log('Launching browser…'); |
| 34 | const browser = await puppeteer.launch({ |
| 35 | headless: true, |
| 36 | args: ['--no-sandbox', '--disable-setuid-sandbox'], |
| 37 | }); |
| 38 | |
| 39 | for (const { template, output } of targets) { |
| 40 | const page = await browser.newPage(); |
| 41 | await page.setViewport({ width: WIDTH, height: HEIGHT, deviceScaleFactor: 1 }); |
| 42 | |
| 43 | const url = `file://${join(templateDir, template)}`; |
| 44 | await page.goto(url, { waitUntil: 'networkidle0', timeout: 30_000 }); |
| 45 | |
| 46 | // Wait a tick for any final layout reflow after font load |
| 47 | await new Promise(r => setTimeout(r, 500)); |
| 48 | |
| 49 | const outputPath = join(outDir, output); |
| 50 | // App Store Connect rejects screenshots with an alpha channel — omitOmitBackground |
| 51 | // (default) plus a fully opaque .page background keeps the PNG opaque. |
| 52 | await page.screenshot({ path: outputPath, fullPage: false, type: 'png' }); |
| 53 | |
| 54 | console.log(`✓ ${output}`); |
| 55 | await page.close(); |
| 56 | } |
| 57 | |
| 58 | await browser.close(); |
| 59 | console.log(`\nDone — files in assets/aso/appstore/visionos/`); |