// Generates every app/website/Android icon asset from the two master SVGs: // scripts/icon/icon.svg — full design (navy bg + gold frame + fanned cards) // scripts/icon/icon-foreground.svg — cards only, transparent (Android adaptive foreground) // Run: node scripts/icon/gen-icons.mjs import sharp from "sharp"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const here = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(here, "..", ".."); const fullSvg = fs.readFileSync(path.join(here, "icon.svg")); const fgSvg = fs.readFileSync(path.join(here, "icon-foreground.svg")); const R = (p) => path.join(root, p); const ensure = (p) => fs.mkdirSync(path.dirname(p), { recursive: true }); async function png(svg, size) { return sharp(svg, { density: 384 }).resize(size, size, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toBuffer(); } async function write(svg, size, dest) { ensure(R(dest)); fs.writeFileSync(R(dest), await png(svg, size)); console.log(" ", dest, `(${size})`); } // PNG-in-ICO container (16/32/48), accepted by all modern browsers. function buildIco(entries) { const header = Buffer.alloc(6); header.writeUInt16LE(0, 0); header.writeUInt16LE(1, 2); header.writeUInt16LE(entries.length, 4); const dir = Buffer.alloc(16 * entries.length); let offset = 6 + 16 * entries.length; entries.forEach((e, i) => { const b = i * 16; dir.writeUInt8(e.size >= 256 ? 0 : e.size, b); dir.writeUInt8(e.size >= 256 ? 0 : e.size, b + 1); dir.writeUInt16LE(1, b + 4); dir.writeUInt16LE(32, b + 6); dir.writeUInt32LE(e.buf.length, b + 8); dir.writeUInt32LE(offset, b + 12); offset += e.buf.length; }); return Buffer.concat([header, dir, ...entries.map((e) => e.buf)]); } const ANDROID = [ ["mipmap-mdpi", 48, 108], ["mipmap-hdpi", 72, 162], ["mipmap-xhdpi", 96, 216], ["mipmap-xxhdpi", 144, 324], ["mipmap-xxxhdpi", 192, 432], ]; async function run() { console.log("Web / PWA:"); // public/icon.svg = the vector master (manifest references it) fs.copyFileSync(path.join(here, "icon.svg"), R("public/icon.svg")); console.log(" public/icon.svg (vector)"); await write(fullSvg, 192, "public/icon-192.png"); await write(fullSvg, 512, "public/icon-512.png"); await write(fullSvg, 512, "public/icon-maskable.png"); await write(fullSvg, 180, "public/apple-touch-icon.png"); await write(fullSvg, 180, "src/app/apple-icon.png"); // favicon.ico (16/32/48) const ico = buildIco(await Promise.all([16, 32, 48].map(async (s) => ({ size: s, buf: await png(fullSvg, s) })))); ensure(R("src/app/favicon.ico")); fs.writeFileSync(R("src/app/favicon.ico"), ico); console.log(" src/app/favicon.ico (16/32/48)"); console.log("Android (Capacitor):"); for (const [dir, launcher, fg] of ANDROID) { await write(fullSvg, launcher, `android/app/src/main/res/${dir}/ic_launcher.png`); await write(fullSvg, launcher, `android/app/src/main/res/${dir}/ic_launcher_round.png`); await write(fgSvg, fg, `android/app/src/main/res/${dir}/ic_launcher_foreground.png`); } await write(fullSvg, 512, "android/app/src/main/ic_launcher-playstore.png"); console.log("Done."); } run().catch((e) => { console.error(e); process.exit(1); });