/** * Generates placeholder PNG icons for the famapp PWA. * * Usage: node scripts/generate-icons.mjs * * Produces: * public/icon-192.png * public/icon-384.png * public/icon-512.png * public/icon-512-maskable.png (same image; OS applies its own mask) * public/icon-180.png (apple-touch-icon) * * All images are solid indigo (#4F46E5) squares — replace with a real * branded export from icon.svg when assets are finalised. */ import { deflateSync } from "zlib"; import { writeFileSync } from "fs"; import { resolve, dirname } from "path"; import { fileURLToPath } from "url"; const __dir = dirname(fileURLToPath(import.meta.url)); const publicDir = resolve(__dir, "../public"); // Build CRC-32 lookup table once. const CRC_TABLE = new Uint32Array(256); for (let i = 0; i < 256; i++) { let c = i; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; CRC_TABLE[i] = c; } function crc32(buf) { let crc = 0xffffffff; for (let i = 0; i < buf.length; i++) crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); return (crc ^ 0xffffffff) >>> 0; } function u32(n) { const b = Buffer.alloc(4); b.writeUInt32BE(n, 0); return b; } function pngChunk(type, data) { const typeBytes = Buffer.from(type, "ascii"); const crc = u32(crc32(Buffer.concat([typeBytes, data]))); return Buffer.concat([u32(data.length), typeBytes, data, crc]); } /** * Returns a Buffer containing a valid PNG of size×size filled with (r, g, b). */ function solidPNG(size, r, g, b) { const PNG_SIG = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); const ihdr = pngChunk( "IHDR", Buffer.concat([ u32(size), u32(size), Buffer.from([8, 2, 0, 0, 0]), // 8-bit RGB, no interlace ]), ); // One filter byte (0 = None) followed by size×3 RGB bytes per row. const rowLen = 1 + size * 3; const raw = Buffer.alloc(size * rowLen); for (let y = 0; y < size; y++) { const base = y * rowLen; raw[base] = 0; for (let x = 0; x < size; x++) { raw[base + 1 + x * 3] = r; raw[base + 2 + x * 3] = g; raw[base + 3 + x * 3] = b; } } const idat = pngChunk("IDAT", deflateSync(raw)); const iend = pngChunk("IEND", Buffer.alloc(0)); return Buffer.concat([PNG_SIG, ihdr, idat, iend]); } // Indigo-600 (#4F46E5) const [R, G, B] = [0x4f, 0x46, 0xe5]; const icons = [ { name: "icon-192.png", size: 192 }, { name: "icon-384.png", size: 384 }, { name: "icon-512.png", size: 512 }, { name: "icon-512-maskable.png", size: 512 }, { name: "icon-180.png", size: 180 }, ]; for (const { name, size } of icons) { const out = resolve(publicDir, name); writeFileSync(out, solidPNG(size, R, G, B)); console.log(` ✓ public/${name} (${size}×${size})`); }