import { deflateSync } from "zlib"; import { writeFileSync, mkdirSync } from "fs"; const CRC_TABLE = (() => { const t = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c; } return t; })(); 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 chunk(type, data) { const typeBytes = Buffer.from(type, "ascii"); const crcVal = crc32(Buffer.concat([typeBytes, data])); const out = Buffer.allocUnsafe(4 + 4 + data.length + 4); out.writeUInt32BE(data.length, 0); typeBytes.copy(out, 4); data.copy(out, 8); out.writeUInt32BE(crcVal, 8 + data.length); return out; } // Purple #6c63ff = rgb(108, 99, 255) // With a white checkmark drawn on it function createIcon(size) { const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); const ihdr = Buffer.allocUnsafe(13); ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4); ihdr[8] = 8; // bit depth ihdr[9] = 2; // color type RGB ihdr[10] = ihdr[11] = ihdr[12] = 0; // Draw pixels: purple bg with white checkmark const pixels = Buffer.alloc(size * size * 3); const scale = size / 64; // Fill purple for (let i = 0; i < size * size; i++) { pixels[i * 3] = 108; pixels[i * 3 + 1] = 99; pixels[i * 3 + 2] = 255; } // Draw white checkmark: ✓ // Checkmark path: (14,34)→(26,46)→(50,18) scaled to icon size function setPixel(x, y) { const xi = Math.round(x * scale); const yi = Math.round(y * scale); const thick = Math.max(2, Math.round(4 * scale)); for (let dy = -thick; dy <= thick; dy++) { for (let dx = -thick; dx <= thick; dx++) { const px = xi + dx; const py = yi + dy; if (px >= 0 && px < size && py >= 0 && py < size) { pixels[(py * size + px) * 3] = 255; pixels[(py * size + px) * 3 + 1] = 255; pixels[(py * size + px) * 3 + 2] = 255; } } } } // Left leg: (14,34) → (26,46) const steps = 40; for (let i = 0; i <= steps; i++) { setPixel(14 + (26 - 14) * (i / steps), 34 + (46 - 34) * (i / steps)); } // Right leg: (26,46) → (50,18) for (let i = 0; i <= steps; i++) { setPixel(26 + (50 - 26) * (i / steps), 46 + (18 - 46) * (i / steps)); } // Build scanlines with filter=0 prefix const raw = Buffer.allocUnsafe(size * (1 + size * 3)); for (let y = 0; y < size; y++) { raw[y * (1 + size * 3)] = 0; pixels.copy(raw, y * (1 + size * 3) + 1, y * size * 3, (y + 1) * size * 3); } const compressed = deflateSync(raw); return Buffer.concat([sig, chunk("IHDR", ihdr), chunk("IDAT", compressed), chunk("IEND", Buffer.alloc(0))]); } mkdirSync("public/icons", { recursive: true }); writeFileSync("public/icons/icon-192.png", createIcon(192)); writeFileSync("public/icons/icon-512.png", createIcon(512)); console.log("Icons generated: public/icons/icon-{192,512}.png");