#!/usr/bin/env node // Generate the app icon set from one SVG mark, rendered through headless Chrome. // // node store/icons/make.js // // Writes into assets/images/: // icon.png 1024×1024 opaque (iOS + fallback) // android-icon-foreground.png 1024×1024 alpha (adaptive foreground, mark in the 66% safe zone) // android-icon-monochrome.png 1024×1024 alpha (themed icon, white mark) // splash-icon.png 512×512 alpha (splash, mark only) // // The mark is the session ring from the app: a thick accent ring with a filled // core — "squeeze". Colours from src/lib/theme.ts. After running, regenerate the // committed Android res with `npx expo prebuild -p android --no-install`. const fs = require("fs"); const path = require("path"); const { execFileSync } = require("child_process"); const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; const OUT = path.join(__dirname, "..", "..", "assets", "images"); const TMP = path.join(__dirname, ".tmp"); const C = { bg: "#160B0B", bg2: "#2A1112", accent: "#E7402E", accentDeep: "#B8271A", text: "#F7ECEA" }; // size: canvas px; scale: fraction of canvas the mark spans; color overrides for monochrome. function mark(size, scale, { ring = C.accent, core = C.accent, glow = true } = {}) { const c = size / 2; const R = (size * scale) / 2; // outer radius of the ring const stroke = R * 0.3; const ringR = R - stroke / 2; const coreR = R * 0.32; return ` ${glow ? `` : ""} `; } function svg(size, body, bg) { return ` ${bg ? ` ` : ""} ${body} `; } function render(svgText, size, out, transparent) { fs.mkdirSync(TMP, { recursive: true }); const file = path.join(TMP, path.basename(out, ".png") + ".html"); fs.writeFileSync( file, `${svgText}`, ); const args = [ "--headless=new", "--disable-gpu", "--hide-scrollbars", "--force-device-scale-factor=1", `--window-size=${size},${size}`, `--screenshot=${out}`, ]; if (transparent) args.push("--default-background-color=00000000"); args.push("file://" + file); execFileSync(CHROME, args, { stdio: "ignore" }); } function main() { // iOS: opaque, mark fills ~62% of the canvas (Apple masks the corners itself). render(svg(1024, mark(1024, 0.62), true), 1024, path.join(OUT, "icon.png"), false); // Android adaptive foreground: content must sit inside the central 66% safe zone. render(svg(1024, mark(1024, 0.46, { glow: false })), 1024, path.join(OUT, "android-icon-foreground.png"), true); // Android monochrome: single-colour silhouette, same safe zone. render(svg(1024, mark(1024, 0.46, { ring: "#FFFFFF", core: "#FFFFFF", glow: false })), 1024, path.join(OUT, "android-icon-monochrome.png"), true); // Splash: mark only, on transparent; app.json sets the background colour. render(svg(512, mark(512, 0.8, { glow: false })), 512, path.join(OUT, "splash-icon.png"), true); fs.rmSync(TMP, { recursive: true, force: true }); console.log("wrote icon.png, android-icon-foreground.png, android-icon-monochrome.png, splash-icon.png"); } main();