#!/usr/bin/env node // Generates minimal PNG icons for the PWA manifest. // Uses pure Node.js — no canvas or image library needed. // Output: valid PNG icons with an "H" monogram on black background with volt (#faff69) color. // For production, replace with properly designed icons. const fs = require('fs') const path = require('path') const zlib = require('zlib') function makePNG(size, bgHex, fgHex) { // PNG signature const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]) // IHDR chunk const ihdrData = Buffer.alloc(13) ihdrData.writeUInt32BE(size, 0) ihdrData.writeUInt32BE(size, 4) ihdrData[8] = 8 // bit depth ihdrData[9] = 2 // color type RGB ihdrData[10] = 0 // compression ihdrData[11] = 0 // filter ihdrData[12] = 0 // interlace const ihdr = makeChunk('IHDR', ihdrData) // IDAT chunk — raw RGB pixel data, filtered (filter byte 0 = none per row) const bg = hexToRgb(bgHex) const fg = hexToRgb(fgHex) const rawRows = [] const margin = Math.floor(size * 0.2) for (let y = 0; y < size; y++) { const row = [0] // filter byte for (let x = 0; x < size; x++) { // Simple "H" monogram in the center const inMargin = x >= margin && x < size - margin && y >= margin && y < size - margin const isStroke = inMargin && ((x < margin + Math.floor(size * 0.08) || x > size - margin - Math.floor(size * 0.08)) || (Math.abs(y - size / 2) < Math.floor(size * 0.06))) const c = isStroke ? fg : bg row.push(c[0], c[1], c[2]) } rawRows.push(Buffer.from(row)) } const rawData = Buffer.concat(rawRows) const compressed = zlib.deflateSync(rawData) const idat = makeChunk('IDAT', compressed) // IEND const iend = makeChunk('IEND', Buffer.alloc(0)) return Buffer.concat([sig, ihdr, idat, iend]) } function makeChunk(type, data) { const len = Buffer.alloc(4) len.writeUInt32BE(data.length) const typeBytes = Buffer.from(type) const crc = crc32(Buffer.concat([typeBytes, data])) const crcBuf = Buffer.alloc(4) crcBuf.writeInt32BE(crc) return Buffer.concat([len, typeBytes, data, crcBuf]) } function hexToRgb(hex) { const n = parseInt(hex.replace('#', ''), 16) return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff] } function crc32(buf) { let crc = 0xffffffff const table = makeCRCTable() for (const b of buf) crc = (crc >>> 8) ^ table[(crc ^ b) & 0xff] return (crc ^ 0xffffffff) | 0 } function makeCRCTable() { const t = new Int32Array(256) for (let i = 0; i < 256; i++) { let c = i for (let j = 0; j < 8; j++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1 t[i] = c } return t } const iconsDir = path.join(__dirname, '..', 'public', 'icons') fs.mkdirSync(iconsDir, { recursive: true }) fs.writeFileSync(path.join(iconsDir, 'icon-192.png'), makePNG(192, '#000000', '#faff69')) fs.writeFileSync(path.join(iconsDir, 'icon-512.png'), makePNG(512, '#000000', '#faff69')) console.log('Icons generated: icon-192.png, icon-512.png')