/** * Génère des icônes PWA simples (dégradé bleu + pastille blanche). * Usage : node scripts/gen-icons.mjs */ import { writeFileSync, mkdirSync } from 'node:fs' import { resolve } from 'node:path' import { deflateSync } from 'node:zlib' const OUT = resolve(import.meta.dirname, '../web/public') mkdirSync(OUT, { recursive: true }) function crc32(buf) { const table = [] 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 crc = 0xffffffff for (const b of buf) crc = table[(crc ^ b) & 0xff] ^ (crc >>> 8) return (crc ^ 0xffffffff) >>> 0 } function chunk(type, data) { const len = Buffer.alloc(4) len.writeUInt32BE(data.length) const body = Buffer.concat([Buffer.from(type, 'ascii'), data]) const crc = Buffer.alloc(4) crc.writeUInt32BE(crc32(body)) return Buffer.concat([len, body, crc]) } function encodePng(width, height, rgba) { const raw = Buffer.alloc(height * (1 + width * 4)) for (let y = 0; y < height; y++) { raw[y * (1 + width * 4)] = 0 rgba.copy(raw, y * (1 + width * 4) + 1, y * width * 4, (y + 1) * width * 4) } const ihdr = Buffer.alloc(13) ihdr.writeUInt32BE(width, 0) ihdr.writeUInt32BE(height, 4) ihdr[8] = 8 ihdr[9] = 6 return Buffer.concat([ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw)), chunk('IEND', Buffer.alloc(0)), ]) } function drawIcon(size) { const buf = Buffer.alloc(size * size * 4) const c0 = [56, 189, 248] const c1 = [99, 102, 241] const cx = size / 2 const cy = size / 2 const rInner = size * 0.3 const rPad = size * 0.16 for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { const i = (y * size + x) * 4 const dx = x - cx const dy = y - cy const dist = Math.sqrt(dx * dx + dy * dy) // Fond arrondi : on coupe les coins let transparent = false if (x < rPad && y < rPad) transparent = Math.hypot(rPad - x, rPad - y) > rPad else if (x >= size - rPad && y < rPad) transparent = Math.hypot(x - (size - rPad), rPad - y) > rPad else if (x < rPad && y >= size - rPad) transparent = Math.hypot(rPad - x, y - (size - rPad)) > rPad else if (x >= size - rPad && y >= size - rPad) transparent = Math.hypot(x - (size - rPad), y - (size - rPad)) > rPad if (transparent) { buf[i + 3] = 0 continue } const t = (x + y) / (size * 2) let r = c0[0] + (c1[0] - c0[0]) * t let g = c0[1] + (c1[1] - c0[1]) * t let b = c0[2] + (c1[2] - c0[2]) * t if (dist < rInner) { r = 255 g = 255 b = 255 } else if (dist < rInner + size * 0.025) { r = Math.min(255, r + 50) g = Math.min(255, g + 50) b = Math.min(255, b + 50) } buf[i] = Math.round(r) buf[i + 1] = Math.round(g) buf[i + 2] = Math.round(b) buf[i + 3] = 255 } } return buf } for (const size of [192, 512]) { const png = encodePng(size, size, drawIcon(size)) writeFileSync(resolve(OUT, `icon-${size}.png`), png) console.log(`icon-${size}.png (${png.length} octets)`) }