#!/usr/bin/env node // Compose store screenshots from raw simulator captures. // // node store/screenshots/make.js // // Inputs: store/screenshots/raw/.png (iPhone 6.9" captures, 1320×2868) // Outputs: store/screenshots/out/ios/.png 1320×2868 (App Store, 6.9") // store/screenshots/out/play/.png 1080×1920 (Google Play phone) // store/screenshots/out/play/feature.png 1024×500 (Play feature graphic) // // Renders HTML through headless Chrome so we can use real typography. Colours // come from src/lib/theme.ts. const fs = require("fs"); const path = require("path"); const { execFileSync } = require("child_process"); const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; const ROOT = __dirname; const RAW = path.join(ROOT, "raw"); const OUT = path.join(ROOT, "out"); const TMP = path.join(ROOT, ".tmp"); const C = { bg: "#160B0B", surface: "#241213", border: "#42201F", text: "#F7ECEA", muted: "#A98A86", accent: "#E7402E", push: "#FF9D3B", }; const slides = [ { raw: "home.png", h: "Kegels that get
harder as you do", sub: "Holds grow automatically every 3 sessions", }, { raw: "session-hold.png", h: "Guided by timer
and haptics", sub: "Train with the screen off or eyes closed", }, { raw: "session-push.png", h: "Squeezes and
reverse kegels", sub: "Both sides of the pelvic floor, every session", }, { raw: "streak.png", h: "Build the habit", sub: "Day streak, best streak, two-week history", }, { raw: "settings.png", h: "Free. Offline.
No account.", sub: "Nothing leaves your phone. No ads, ever.", }, ]; const dataUri = (file) => "data:image/png;base64," + fs.readFileSync(file).toString("base64"); function slideHtml({ w, h, cap, sub, img, phoneW, capSize, subSize, top, gap }) { return `
${cap}
${sub}
`; } function featureHtml(icon) { return `
Him.
Kegel exercises for men.
Free, offline, no account.
`; } function render(html, w, h, out) { fs.mkdirSync(TMP, { recursive: true }); const file = path.join(TMP, path.basename(out, ".png") + ".html"); fs.writeFileSync(file, html); execFileSync(CHROME, [ "--headless=new", "--disable-gpu", "--hide-scrollbars", "--force-device-scale-factor=1", `--window-size=${w},${h}`, `--screenshot=${out}`, "file://" + file, ], { stdio: "ignore" }); } function main() { for (const s of slides) { const f = path.join(RAW, s.raw); if (!fs.existsSync(f)) throw new Error(`missing raw capture: ${f}`); } fs.mkdirSync(path.join(OUT, "ios"), { recursive: true }); fs.mkdirSync(path.join(OUT, "play"), { recursive: true }); slides.forEach((s, i) => { const img = dataUri(path.join(RAW, s.raw)); // App Store 6.9": caption then phone; phone overflows the bottom edge on purpose. render( slideHtml({ w: 1320, h: 2868, cap: s.h, sub: s.sub, img, phoneW: 1100, capSize: 104, subSize: 46, top: 150, gap: 96 }), 1320, 2868, path.join(OUT, "ios", `${i + 1}.png`), ); // Play phone (16:9): same layout, smaller phone. render( slideHtml({ w: 1080, h: 1920, cap: s.h, sub: s.sub, img, phoneW: 720, capSize: 78, subSize: 36, top: 110, gap: 70 }), 1080, 1920, path.join(OUT, "play", `${i + 1}.png`), ); }); const icon = dataUri(path.join(ROOT, "..", "..", "assets", "images", "icon.png")); render(featureHtml(icon), 1024, 500, path.join(OUT, "play", "feature.png")); fs.rmSync(TMP, { recursive: true, force: true }); console.log(`wrote ${slides.length} iOS + ${slides.length} Play slides and feature.png to ${OUT}`); } main();