From b7dc826f0ffdac605e2b5b424b314e7a5af2ac16 Mon Sep 17 00:00:00 2001 From: vladtechno Date: Thu, 18 Jun 2026 20:10:33 +1000 Subject: [PATCH] feat(driver-pwa): real MVP PWA icons (192/512, cream+card motif) + generator Co-Authored-By: claude-flow --- driver-pwa/frontend/public/icons/icon-192.png | Bin 70 -> 909 bytes driver-pwa/frontend/public/icons/icon-512.png | Bin 70 -> 4982 bytes driver-pwa/frontend/scripts/gen-icons.mjs | 76 ++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 driver-pwa/frontend/scripts/gen-icons.mjs diff --git a/driver-pwa/frontend/public/icons/icon-192.png b/driver-pwa/frontend/public/icons/icon-192.png index 5e2dc3b1a35acd6f6a6b3e6749923baab2f4d432..ab76006faee57ca38b110f8d2c74b2ea647f9eb4 100644 GIT binary patch literal 909 zcmb_bUr3Wt6#w0QbNg62KP_x!ZXb*w2w}d&5_6xmn6uC@qnOGDd&tn#z%WeSFp<%@X4GBYlAjh(o@=duxD`o zY~a0TwRZH{+G=cd|Cz)&gJ6+~W5nHp19>QuZ@0S`U5W;mo903@8fXnevCq)O?UtFH zxMz3}>m#~UTl6YpTQNO<-I3#uFTT*c9YiP?%4G+n-r}cUN}BV;*riyG^AY;HA1-q> zjII5ouPdTJRQs(~3Rb*bt*L0zLV#;3UgyvRp3vi!41px_F4UQgk;k)H;89-R&J){O z8NZ%55!Xoo0udC^;0^1#1_!W!)PDl0RO;@9q$ga>@P}5$h5q3?a<6*2i*UrNo*E^K zdNJm2WJMF{@4S2(=urY=1VSOyj8{Ez@sI0%W@*aUXrqs0;l~z`FSjMF$syV~54g`j zY}s50rF55b{~7Ei^oCHXLirnvXifih@Ot|i(uv^J7+?q25X|%w@RK(+;xW{P9reFw zK)T`H3QQ8wJ?=QaIpJRrl!%dJC?OFjZz$6ccqHD_~ zLUTtnNfR4o2>3j$OX*<0*ZX>BAv9M+`-*nJxm3OIwGEu>lVkTDYQm1p!{A*X;;A<9 qs$y^|19Yk*8#4A&y4^4L?~h0_A8Xh*Bl4m!9Vg7k%Z&p^ZO&hIMJMzC literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$f6gBW;AdcIWo&3! S*`o-QVDNPHb6Mw<&;$UpBoA!> diff --git a/driver-pwa/frontend/public/icons/icon-512.png b/driver-pwa/frontend/public/icons/icon-512.png index 5e2dc3b1a35acd6f6a6b3e6749923baab2f4d432..20799d3bbf4d3f95fd0502a34fdddd437bdb8203 100644 GIT binary patch literal 4982 zcmeHLeNa=`6+iD0!5|PmR$xIPil8HY;8=m}LJ;|q3P>OXG}Z*QyW15EPFszEhffi( zr5Mqb(xS_k#Ygzc?oJ7vLU=+|b{(rbeOc+~k}&wudDS7H55fbbi6xo3-Elh8j^iKx z_3r%Uo^#JV=iT$Wzx&Jdv?Nz&KW6}NP2L)x0f1VDC@gfee4Z*O7QibyIX+gTIRE&Y z4X1)1RwWAa_lDh_yL$D^#xl;}1tfm+imz{ckd>WvchKirWU=4$m7{Z$H|L(!%{jn< zX~{DL$S4HBi3=Q*N$!Op<_FYcA&Zn8&gRgsfAmd`$1rlKwOlms8iA&3;UN zt`9;UrLJ*nga*Gu?l3FCPUAb68x1SrRIAGUmRjz<6e-BT6!=nF5+4UkM1lUYOKkXt zZLlOW&{w8q6E`Hml8iuapcLGR?CSj(i2KH=g%qCjWkCcU=OvWwj*7$3haHbe?kpjgJ_!U00Qv@Q;MPO^N^ zQV}YZ7p$)raL82V@#tO-IiPof?sna=*(k&$V@yb~2cimw0ChkVIi`=On(_i(mTIYS zimn};;o?r}ayL^nlYbAhSW*s7<|;OE;t*<#rE?v660p!C{)mas5Xf+&?p#kLC{8MG z!I;pH^89z8z?Fj#)1w1Fln}9P^Ku%KH(htV&q}UbN(TCx=|ltU^L4ZMBq(2h*`=idw!7l~pQ#4QJ`v z3dm^+2qG`C50-rMNBV8+*__WYLcph`LnfFSND0kEdvD!`#IHueO{(9}SAL33Vy&f>EEx?x)Gng(BTw0wRHCb4lC6EF7 z7?PJ|c(|WKYLqgN=%*|(y$Mr?T4!#@Ja?0JJijTmhuV9bH4mt$8O7(UKpF&PdWXW# zS3nBWLW$8Ma{HL-o{Cj&1jo9HybHQ#7{!3)`GK9fH4vH4OjF zo}jKB*eAIez1m88rl=Bp#k{;$);F$U_}g%slP)24qCqq!KWq2VEYN9Udk5CqO=?+F zza!(OCB~j?LS;G8npC=JQ7u(?w_bPi_Ch?DA;VjE>+&+j4wl%RGzz*j!rtpUZRN=q z2JRdphJVs-Hz8DO^2B4AaCezC19$2L8qJR0?MOknHOw|~V7B78@)BxvwAL}FUC?IY zFlh@&*B1fo#BxTs^aW?!|3$(7z-s!vsP`8`#EWP?W^zkc5{FmA0e9=4p{EC&x2KPZ z;4a^~Qmejv@u&5}Ezlin{e>Qkmp`N1ZJtn=Y}MKfxiHi@te&420wG*lfd8A( z8}z^Z&u8P@b4JzYAWxTP&8N!?G}7KIq@cuFmJ}e18S#7-!Vs9zOKeH!z@zN9MwYYi zHWBm3@NFW#FA?8qKjKziXjE)(=KKM(^gZ$atMBEf2WAho4PS!pIBTQpip6BPUnuKP zqnov*4tXR6ji8ZFeHXie9D{wV*$pIIK;zn}`GV)`-UPo9x`tA^7Sg60`8! z!Iyci@C=*&9WIP#M#@yg)RPsql_&Qx%gU#Ssfl?#bk_)eremYi>%eQs>6`l zMorMAo@cr+y#0-viZX)`u9jP0FP zEN6yZ35s}bvJ%!?8Vp;Qze6}7AJ0PhBGvbRVjk}m7u!XY17{Vbo%$i7*&MwE-+~o( zdch(p$Ogto$K%P&jRa^Dsyt*F*v~v*Ked-rBQosbkgFNK?e3jG3(*upzP8WPa#0_! zU(|oAJP*@Ks39eci}2RzUpWiL~V|i;dMeoT5*bXyvII{ zkKTt*5%Q3usSvd_`XgLR$b;oU5O6VI6DgszXS5!`%jEeRlW6}h?^vaflU)kM>EoQt zy==%tF=}j}KXaJfjd>l3u?i|9Qao;ssRl*2(oE(vuJ;~VhwXWS>51VNI4{i4w)<2%cwHlWx-^KA01+^LVGw;C;vgW?R@C zq^?O9{AdN3w=v^raT@miP^wiqZ?G%pZ|PCTA}Qvt@U|*#C*Hi$HvfQGb|RE+f8xgh zX`|q@>0>(eiIM|B^eA_Gro8(0N*JV^yt@ePVCw66LU{Xz^WQ&QJ)L{!;7=F$hYJ2= Qc~67ngtYjMEwbbP1nfUtJpcdz literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$f6gBW;AdcIWo&3! S*`o-QVDNPHb6Mw<&;$UpBoA!> 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`); +}