// Generates icon-192.png and icon-512.png without external dependencies // Uses a minimal PNG encoder written in pure JS import { writeFileSync } from 'fs'; import { deflateSync } from 'zlib'; function createPNG(size) { const r = Math.round(size * 0.22); // Draw pixels const pixels = new Uint8Array(size * size * 4); const setPixel = (x, y, R, G, B, A = 255) => { if (x < 0 || x >= size || y < 0 || y >= size) return; const i = (y * size + x) * 4; pixels[i] = R; pixels[i+1] = G; pixels[i+2] = B; pixels[i+3] = A; }; // Gradient background with rounded corners for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { // Rounded corner check const inCornerTL = x < r && y < r && (x-r)**2 + (y-r)**2 > r**2; const inCornerTR = x >= size-r && y < r && (x-(size-r))**2 + (y-r)**2 > r**2; const inCornerBL = x < r && y >= size-r && (x-r)**2 + (y-(size-r))**2 > r**2; const inCornerBR = x >= size-r && y >= size-r && (x-(size-r))**2 + (y-(size-r))**2 > r**2; if (inCornerTL || inCornerTR || inCornerBL || inCornerBR) continue; // Linear gradient: top-left #4ecdc4 → bottom-right #ff6b9d const t = (x + y) / (size * 2); const R = Math.round(0x4e + (0xff - 0x4e) * t); const G = Math.round(0xcd + (0x6b - 0xcd) * t); const B = Math.round(0xc4 + (0x9d - 0xc4) * t); setPixel(x, y, R, G, B); } } // Draw ⚒ as a simple hammer shape (dark color on gradient) const cx = Math.floor(size / 2); const cy = Math.floor(size / 2); const s = Math.floor(size * 0.18); // Hammer head (rectangle) for (let dy = -s; dy <= 0; dy++) { for (let dx = -s; dx <= s; dx++) { setPixel(cx + dx, cy + dy, 13, 13, 15); } } // Handle for (let dy = 0; dy <= s * 2; dy++) { for (let dx = -Math.floor(s*0.25); dx <= Math.floor(s*0.25); dx++) { setPixel(cx + dx, cy + dy, 13, 13, 15); } } // Encode as PNG const width = size, height = size; const signature = Buffer.from([137,80,78,71,13,10,26,10]); // IHDR const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4); ihdr[8] = 8; ihdr[9] = 2; // 8-bit RGB... wait we need RGBA // Use RGBA (color type 6) ihdr[9] = 6; const ihdrChunk = makeChunk('IHDR', ihdr); // IDAT - raw image data with filter bytes const raw = Buffer.alloc(height * (1 + width * 4)); for (let y = 0; y < height; y++) { raw[y * (1 + width * 4)] = 0; // filter type None for (let x = 0; x < width; x++) { const si = (y * width + x) * 4; const di = y * (1 + width * 4) + 1 + x * 4; raw[di] = pixels[si]; raw[di+1] = pixels[si+1]; raw[di+2] = pixels[si+2]; raw[di+3] = pixels[si+3] || (pixels[si] || pixels[si+1] || pixels[si+2] ? 255 : 0); } } const compressed = deflateSync(raw); const idatChunk = makeChunk('IDAT', compressed); const iendChunk = makeChunk('IEND', Buffer.alloc(0)); return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]); } function makeChunk(type, data) { const len = Buffer.alloc(4); len.writeUInt32BE(data.length); const typeB = Buffer.from(type); const crc = crc32(Buffer.concat([typeB, data])); const crcB = Buffer.alloc(4); crcB.writeUInt32BE(crc >>> 0); return Buffer.concat([len, typeB, data, crcB]); } function crc32(buf) { let crc = 0xFFFFFFFF; for (const b of buf) { crc ^= b; for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0); } return (crc ^ 0xFFFFFFFF) >>> 0; } writeFileSync('public/icon-192.png', createPNG(192)); writeFileSync('public/icon-512.png', createPNG(512)); console.log('Icons generated.');