// Generates PWA icons without external deps: neon diamond on dark bg. 'use strict'; const zlib = require('zlib'); const fs = require('fs'); const path = require('path'); function crc32(buf) { let table = crc32.table; if (!table) { table = crc32.table = 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; table[n] = c >>> 0; } } let c = 0xFFFFFFFF; for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xFF] ^ (c >>> 8); return (c ^ 0xFFFFFFFF) >>> 0; } function chunk(type, data) { const len = Buffer.alloc(4); len.writeUInt32BE(data.length); const t = Buffer.from(type, 'ascii'); const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(Buffer.concat([t, data]))); return Buffer.concat([len, t, data, crc]); } function png(w, h, rgba) { const stride = w * 4; const raw = Buffer.alloc((stride + 1) * h); for (let y = 0; y < h; y++) { raw[y * (stride + 1)] = 0; rgba.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride); } const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = 6; const sig = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); return Buffer.concat([ sig, chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw, { level: 9 })), chunk('IEND', Buffer.alloc(0)) ]); } function lerp(a, b, t) { return a + (b - a) * t; } function makeIcon(size) { const px = Buffer.alloc(size * size * 4); const cx = size / 2, cy = size / 2; const maxD = Math.hypot(cx, cy); const diaR = size * 0.34; for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { const i = (y * size + x) * 4; const dx = x - cx, dy = y - cy; const d = Math.hypot(dx, dy) / maxD; // background: vertical dark gradient let r = lerp(10, 5, y / size); let g = lerp(10, 5, y / size); let b = lerp(31, 12, y / size); // soft purple glow ring const glow = Math.max(0, 1 - d * 2.2) ** 2; r += 124 * glow * 0.35; g += 92 * glow * 0.35; b += 255 * glow * 0.4; // neon diamond const ad = Math.abs(dx) + Math.abs(dy); if (ad < diaR) { const t = ad / diaR; // gradient edge cyan -> center violet -> white core if (t > 0.82) { r = 224; g = 250; b = 255; } // rim highlight else if (t > 0.55) { r = 0; g = 229; b = 255; } // cyan edge else if (t > 0.25) { r = 124; g = 92; b = 255; } // violet body else { r = 233; g = 213; b = 255; } // white core // subtle scanline shading if (((x + y) >> 3) % 2 === 0 && t <= 0.82) { r *= 0.92; g *= 0.92; b *= 0.95; } } px[i] = Math.min(255, r | 0); px[i + 1] = Math.min(255, g | 0); px[i + 2] = Math.min(255, b | 0); px[i + 3] = 255; } } return png(size, size, px); } const outDir = path.join(__dirname, '..', 'icons'); if (!fs.existsSync(outDir)) fs.mkdirSync(outDir); for (const s of [192, 512]) { const f = path.join(outDir, `icon-${s}.png`); fs.writeFileSync(f, makeIcon(s)); console.log('wrote', f, fs.statSync(f).size, 'bytes'); }