when-am-i / files / scripts / generate-screenshots.mts
generate-screenshots.mts
48a625b7 2026-06-19 Pavel Safronov | 1 | // Generate documentation screenshots of key app states. |
| 2 | // | |
| 3 | // Forces deterministic state via URL params (the same hook the app reads), so | |
| 4 | // shots are reproducible. Output: screenshots/app/. Run from the repo root: | |
| 5 | // npm run screenshot | |
| 6 | ||
| 7 | import { chromium } from 'playwright'; | |
| 8 | import { mkdir, rm } from 'node:fs/promises'; | |
| 9 | import path from 'node:path'; | |
| 10 | import { startDevServer, BASE_URL, REPO_ROOT } from './lib/server.mts'; | |
| 11 | ||
| 12 | const OUT_DIR = path.join(REPO_ROOT, 'screenshots', 'app'); | |
| 13 | ||
| 14 | const STATES = [ | |
76ecf55e 2026-06-21 Pavel Safronov | 15 | { name: '01-landing', query: '', desc: "Landing (today's date, no pin)" }, |
48a625b7 2026-06-19 Pavel Safronov | 16 | { |
143b640c 2026-06-19 Pavel Safronov | 17 | name: '02-boston-1650', |
| 18 | query: '?lat=42.32&lng=-71.06&zoom=10&year=1650&month=6&day=15', | |
| 19 | desc: 'Boston, 1650 — coexistence (Massachusett + colonial Julian)', | |
48a625b7 2026-06-19 Pavel Safronov | 20 | }, |
| 21 | { | |
68dcf3ed 2026-06-25 Pavel Safronov | 22 | name: '03-boston-1752', |
| 23 | query: '?lat=42.32&lng=-71.06&zoom=10&year=1752&month=9&day=20', | |
| 24 | desc: 'Boston, 1752 — the British Julian→Gregorian switch mid-September (day-of-year track splits)', | |
| 25 | }, | |
| 26 | { | |
| 27 | name: '04-boston-1800', | |
143b640c 2026-06-19 Pavel Safronov | 28 | query: '?lat=42.32&lng=-71.06&zoom=10&year=1800&month=6&day=15', |
48a625b7 2026-06-19 Pavel Safronov | 29 | desc: 'Boston, 1800 — Gregorian', |
| 30 | }, | |
| 31 | ]; | |
| 32 | ||
| 33 | async function run() { | |
| 34 | console.log('\n📸 Generating app screenshots…\n'); | |
| 35 | const server = await startDevServer(); | |
| 36 | try { | |
| 37 | await rm(OUT_DIR, { recursive: true, force: true }); | |
| 38 | await mkdir(OUT_DIR, { recursive: true }); | |
| 39 | ||
| 40 | const browser = await chromium.launch(); | |
| 41 | const page = await browser.newPage({ viewport: { width: 1100, height: 920 }, deviceScaleFactor: 2 }); | |
| 42 | ||
| 43 | for (const state of STATES) { | |
| 44 | await page.goto(`${BASE_URL}${state.query}`, { waitUntil: 'networkidle' }); | |
| 45 | await page.waitForTimeout(800); // let map tiles settle | |
| 46 | const file = path.join(OUT_DIR, `${state.name}.png`); | |
d1eb8ad5 2026-06-19 Pavel Safronov | 47 | await page.screenshot({ path: file, fullPage: true }); |
48a625b7 2026-06-19 Pavel Safronov | 48 | console.log(` ✅ ${state.name} — ${state.desc}`); |
| 49 | } | |
| 50 | ||
| 51 | await browser.close(); | |
| 52 | console.log('\n✨ Done — screenshots/app/\n'); | |
| 53 | } finally { | |
| 54 | server.stop(); | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | run().catch((err) => { | |
| 59 | console.error(err); | |
| 60 | process.exit(1); | |
| 61 | }); |