/** * Rasterizes public/icon.svg into all required PNG icon sizes. * * Usage: node scripts/generate-icons.mjs * * Produces: * public/icon-16.png * public/icon-32.png * public/icon-180.png (apple-touch-icon) * public/icon-192.png * public/icon-384.png * public/icon-512.png * public/icon-512-maskable.png (square bg, content in safe zone) */ import sharp from "sharp"; import { readFileSync } from "fs"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; const __dirname = dirname(fileURLToPath(import.meta.url)); const pub = join(__dirname, "..", "public"); const svg = readFileSync(join(pub, "icon.svg")); const sizes = [ { name: "icon-16.png", size: 16 }, { name: "icon-32.png", size: 32 }, { name: "icon-180.png", size: 180 }, { name: "icon-192.png", size: 192 }, { name: "icon-384.png", size: 384 }, { name: "icon-512.png", size: 512 }, ]; for (const { name, size } of sizes) { await sharp(svg).resize(size, size).png().toFile(join(pub, name)); console.log(` ✓ public/${name} (${size}×${size})`); } // Maskable: remove rounded corners so the background fills the full canvas, // allowing the OS to apply any mask shape without clipping the icon corners. const maskableSvg = readFileSync(join(pub, "icon.svg"), "utf-8").replace('rx="96"', ""); await sharp(Buffer.from(maskableSvg)) .resize(512, 512) .png() .toFile(join(pub, "icon-512-maskable.png")); console.log(` ✓ public/icon-512-maskable.png (512×512 maskable)`);