diff --git a/driver-pwa/frontend/public/icons/icon-192.png b/driver-pwa/frontend/public/icons/icon-192.png index 5e2dc3b..ab76006 100644 Binary files a/driver-pwa/frontend/public/icons/icon-192.png and b/driver-pwa/frontend/public/icons/icon-192.png differ diff --git a/driver-pwa/frontend/public/icons/icon-512.png b/driver-pwa/frontend/public/icons/icon-512.png index 5e2dc3b..20799d3 100644 Binary files a/driver-pwa/frontend/public/icons/icon-512.png and b/driver-pwa/frontend/public/icons/icon-512.png differ diff --git a/driver-pwa/frontend/scripts/gen-icons.mjs b/driver-pwa/frontend/scripts/gen-icons.mjs new file mode 100644 index 0000000..a2fd789 --- /dev/null +++ b/driver-pwa/frontend/scripts/gen-icons.mjs @@ -0,0 +1,76 @@ +// Генерация PWA-иконок «Премиум Водитель» без сторонних либ (zlib PNG-энкодер). +// Дизайн: кремовый фон #F3EEE3, тёмная скруглённая плитка #1C1B19, +// внутри белая «карта» #F6F2E9 с тёмной полосой (финансовый мотив). Без жёлтого/текста. +import zlib from "node:zlib"; +import fs from "node:fs"; +import path from "node:path"; + +const CREAM = [243, 238, 227]; +const INK = [28, 27, 25]; +const CARD = [246, 242, 233]; + +function crc32(buf) { + let c = ~0; + for (let i = 0; i < buf.length; i++) { + c ^= buf[i]; + for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1)); + } + return (~c) >>> 0; +} +function chunk(type, data) { + const t = Buffer.from(type, "ascii"); + const len = Buffer.alloc(4); len.writeUInt32BE(data.length, 0); + const body = Buffer.concat([t, data]); + const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(body), 0); + return Buffer.concat([len, body, crc]); +} +function encodePng(N, rgba) { + const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(N, 0); ihdr.writeUInt32BE(N, 4); ihdr[8] = 8; ihdr[9] = 6; + const stride = N * 4; + const raw = Buffer.alloc(N * (stride + 1)); + for (let y = 0; y < N; y++) { + raw[y * (stride + 1)] = 0; + rgba.copy(raw, y * (stride + 1) + 1, y * stride, y * stride + stride); + } + return Buffer.concat([sig, chunk("IHDR", ihdr), chunk("IDAT", zlib.deflateSync(raw)), chunk("IEND", Buffer.alloc(0))]); +} +function inRoundRect(px, py, x0, y0, x1, y1, r) { + if (px < x0 || px >= x1 || py < y0 || py >= y1) return false; + const cx = px < x0 + r ? x0 + r : px >= x1 - r ? x1 - r : px; + const cy = py < y0 + r ? y0 + r : py >= y1 - r ? y1 - r : py; + const dx = px - cx, dy = py - cy; + return dx * dx + dy * dy <= r * r; +} + +function draw(N) { + const buf = Buffer.alloc(N * N * 4); + // tile (centered ~64%) + const tile = Math.round(N * 0.64), tx0 = Math.round((N - tile) / 2), ty0 = tx0; + const tx1 = tx0 + tile, ty1 = ty0 + tile, tr = Math.round(tile * 0.24); + // card inside tile + const cw = Math.round(tile * 0.62), ch = Math.round(tile * 0.42); + const cx0 = Math.round((N - cw) / 2), cy0 = Math.round((N - ch) / 2); + const cx1 = cx0 + cw, cy1 = cy0 + ch, cr = Math.round(ch * 0.18); + // stripe near top of card + const sy0 = cy0 + Math.round(ch * 0.22), sy1 = sy0 + Math.round(ch * 0.14); + for (let y = 0; y < N; y++) { + for (let x = 0; x < N; x++) { + let col = CREAM; + if (inRoundRect(x, y, tx0, ty0, tx1, ty1, tr)) col = INK; + if (inRoundRect(x, y, cx0, cy0, cx1, cy1, cr)) col = CARD; + if (x >= cx0 && x < cx1 && y >= sy0 && y < sy1) col = INK; + const o = (y * N + x) * 4; + buf[o] = col[0]; buf[o + 1] = col[1]; buf[o + 2] = col[2]; buf[o + 3] = 255; + } + } + return encodePng(N, buf); +} + +const dir = path.resolve("public/icons"); +fs.mkdirSync(dir, { recursive: true }); +for (const N of [192, 512]) { + fs.writeFileSync(path.join(dir, `icon-${N}.png`), draw(N)); + console.log(`wrote icon-${N}.png`); +}