From 39f7eb88049edb785ce4317979198d39c15a2a20 Mon Sep 17 00:00:00 2001 From: Claudio Schaad Date: Thu, 27 Aug 2026 17:54:17 +0200 Subject: [PATCH] Add a real PWA icon (notes glyph) instead of a flat color placeholder The generated icons were solid brand-blue squares with no glyph. gen-icons.py now procedurally draws a page/notes icon (folded corner, text lines) with anti-aliasing, kept within the maskable-icon safe zone so Android's adaptive-icon masks don't clip it. Also adds an apple-touch-icon and wires favicon/apple-touch-icon links into index.html, and marks manifest icons purpose "any maskable". Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01C3DGg1pvmvf81zXs4pk1J7 --- client/index.html | 2 + client/public/icons/apple-touch-icon.png | Bin 0 -> 747 bytes client/public/icons/icon-192.png | Bin 413 -> 828 bytes client/public/icons/icon-512.png | Bin 1496 -> 2854 bytes client/scripts/gen-icons.py | 104 ++++++++++++++++++++--- client/vite.config.ts | 8 +- 6 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 client/public/icons/apple-touch-icon.png diff --git a/client/index.html b/client/index.html index 4088862..1faa727 100644 --- a/client/index.html +++ b/client/index.html @@ -4,6 +4,8 @@ + + Reading Notes diff --git a/client/public/icons/apple-touch-icon.png b/client/public/icons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ccfa88df2778a7a1ca62a3dfb85553c57c5f7657 GIT binary patch literal 747 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD2}o{QKQWbofoYSci(^Q|t+%&ry_f?<8Uo87 zslD06y*(j$I&a2AFOHRKMc%kb^WIn@%)|9kWJ-eqN0h3H)XX2oj=UU77Fm-e)9=kW zzP;(;hl=T^&zudf4oWJvYkVLe)8t&h!FN#6f~6hE5kloQIitt|IUW$jAz?C8ZzX&# zsHqd)&szTI`W$J>(_dw}1Ag3hpm8(?4$26tt*YUHUD?Pf}ht zpXXsgy2_Sq&!7GHIoFX*=IJi8E^TFZ3ooPDA|7si1rr$EBMVp*nE8wzAP5fTcC?SZq2F7$8}&q;K&KqX!=1?h8{7OjPPybh<%VSBCLKgR-bh;)#ajZx&4x z8ISiAwx}@oMLyyHYBsiBeto0;y$2q4>eKZL4luIIkV?28v$%D4`LBPme5><#AO1*} zIqSD&%e~Wy-Mw-!vjqA2{$*U+TYEBbw|cTg&nCA>iw6zNeCWiQHxD;NZ;Wn)%4k<> zS1(=@D2Aa9sJGAPQAYXvS#vdhOR+0keCk`guReV{6U^}y6AmR#X~nbvo23vj4f(#$ zvnBW}Cf&A?In3x@H1!q(pT(g)HV-`ZUb|^5Q_xaYe_-O>-^pG}B0h7=XoVvs4rp3{ sr4?AxP=O>J7?-6ToRXTHXMW*3n<+JW*P1Kqfr*&G)78&qol`;+076tBasU7T literal 0 HcmV?d00001 diff --git a/client/public/icons/icon-192.png b/client/public/icons/icon-192.png index c793da3b88740ca958edf13769fdd72bdb42cf27..c2bf8178e0fc8f33e9fe35190149e343ddb94984 100644 GIT binary patch literal 828 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE2}s`E_d9@rftlIU#WAGf*4sOV*}{nu2R_yb zr?=Ozem}?eli^aNK+CyHn%->oxP1s9E9`x*+4HKhE1Iq-4Nen^`OdbrL44eu;0kDve16T+o zfJe>GFl!l^EyvGJ_WaRz@9v%o<6oPXpUv-&=VLhKm;N{6_~qSK`OduUpMO{Xvj6kR z?6p5?c2ApPwAh95t@*n1zq+sHo!S58&%3wBXREJoW60f5U6%6kPyf7Xhm(2>MHow~ zimS91s=GRTz1A^r)eBF-3m;$hKp0O8JpDtzEaB*w$Mur2L$2WkV}>|G>lJ1JTZW6w z0SOKpzcNhuedHQ*z&qh+euq>ht_AE1E-@Uvz#L%NaOef&3KoZnUl>A679>a z*fNN+Yb=p#;9?R9<#%vp;9A4J;Ewt>xrUwy;)&7ZLRA^ve;vkFg;=AzR_oqj% zZ9X!1iC_5r?#a#9tB)`vn{~Y6cm+=5zc=1tXF9Rn09ip17xRf~h4+oPo%Acah8x+A z1-!>=6&hq7Gm`EJt!<|t3Mn*fDzC9(=2-CNYP50#!>v_*`FsKnH&@H@9lLDZz;Nnb z-Q!bP0l+XkK5ik~{ literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE2}s`E_d9@rficC?#WAGf*4s;tybKH+M>Z5U zxwIrRakrFD){2<+s&TtVL!*HJ+aZSp1)dflOM-(rQGtgAC;((NLZl=(z*1~XAgPwd z10bnH1+XrJB3kH&TSBJ&c=huvVM6gGHV@!802)dMuQEw<$>f^UoPNXr1fH&bF6*2U FngDpWV$w7WM8 z(CGp)4Q4PFGH~JnM!{LisT+n}V4)nz5tkN>E(9}_j&byTUUJ1G!Poxj_TKY+o*&=O z_j$g*_UupG66NJ7^#lN~m}qqp0ELhOhI9qeS55+Wj>f1Xw&h(L9ZQX zn=VI2Zrzw~JnHcMX*6HoIO@?Q&+Dbw>J2gx2fzX_M70noMCKo=IG9`tG=K<{01^-c zA6493H;)C(hUTW;Y>L(SH`Y;$X18VsrlB zltQ83Y#f^&?wJ^fkh*mr2kl2gul0}2ylq&&c#oIz-GDBbvg7@e9BB*!a8|E3uu$CcJzgw`o7I&Xc+U} zXr(wu=8s0hFW(f)TyiC}sZ3QbZ5nc}1fP`Kx`UR9CMK{_B0T3hN>==Lxk9Zn(fD0Ono&X7Pqg#hgdvPf zQuP&%60mqaeF;UCU|CE%p+j;}h;AC=9`3=ONXil>Cp&$!sxS!!FP?*vLB7$ z5MQ7%;*duIVaSC%=G*_V8B$k4@tQ;1!=BmkTdlEJERt~j!Dj*sV-M`b0Q@zkWZ0#oD8UYTe@ z&ujZ@t;Z76%hsDdA%-Dd;2RM^3k{US?<4@`?Y?>)EMsnj1YL}kg5M_`&J7Y0a!5JL zzBovZ`L}v*SwnApdC}X)9lIldw(n|8Ob_)EVgyu9nDLr?4FB68r2UBpr^?6S_hrJv zU6LjR7ZVxNnGa1taNdHj!jC3N&L=0y)(1nhfS`h4Z=9~#?;s(rZqaFnPLs;ybJ%fj zs_onC0p#VX-D{RS)`hm-SAWg_Wi6>(?c(RrcIL0EiLm9%-*4sKASXy+NO565_~Yv5 z)^~W(BdzYim6#It1Lw%r$?cp6$z`bq%U#HgS96PuRk$j-yX^eM%Iwx|RKhqj-ZWL^ z$*&+T75Sq6`jn;h;T^Fo%4Mx@$m#Jvrtp)fohde1!Te8^M|u3 0 and (top - y) < 0 and (x - (right - fold)) + (y - top) > fold: + in_page = False + + if in_page: + # Shadow triangle for the folded flap, just inside the cut edge. + dx = x - (right - fold) + dy = y - top + if dx > 0 and dy >= 0 and dx + dy <= fold and dx + dy >= fold * 0.55: + return FOLD_SHADE + + page_w = right - left + line_h = size * 0.028 + line_specs = [ + (0.46, 0.70), + (0.60, 0.70), + (0.74, 0.46), + ] + for rel_y, rel_w in line_specs: + cy = top + (bottom - top) * rel_y + half_h = line_h / 2 + half_w = page_w * rel_w / 2 + cx = left + page_w / 2 + if line_contains(x, y, cx, cy, half_w, half_h): + return LINE_BLUE + return PAGE_WHITE + + return BRAND_BLUE + + +def make_png(path, size): raw = bytearray() - for _ in range(height): + step = 1.0 / SUPERSAMPLE + for y in range(size): raw.append(0) # filter type 0 for each scanline - for _ in range(width): - raw += bytes([r, g, b]) + for x in range(size): + r_acc = g_acc = b_acc = 0 + for sy in range(SUPERSAMPLE): + for sx in range(SUPERSAMPLE): + sample_x = x + (sx + 0.5) * step + sample_y = y + (sy + 0.5) * step + r, g, b = pixel_color(sample_x, sample_y, size) + r_acc += r + g_acc += g + b_acc += b + n = SUPERSAMPLE * SUPERSAMPLE + raw += bytes([round(r_acc / n), round(g_acc / n), round(b_acc / n)]) + compressed = zlib.compress(bytes(raw), 9) def chunk(tag, data): @@ -18,15 +101,16 @@ def make_png(path, size, rgb): ) sig = b"\x89PNG\r\n\x1a\n" - ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0) + ihdr = struct.pack(">IIBBBBB", size, size, 8, 2, 0, 0, 0) png = sig + chunk(b"IHDR", ihdr) + chunk(b"IDAT", compressed) + chunk(b"IEND", b"") os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "wb") as f: f.write(png) + if __name__ == "__main__": - color = (31, 111, 235) # brand blue - make_png("public/icons/icon-192.png", 192, color) - make_png("public/icons/icon-512.png", 512, color) + make_png("public/icons/icon-192.png", 192) + make_png("public/icons/icon-512.png", 512) + make_png("public/icons/apple-touch-icon.png", 180) print("Icons generated") diff --git a/client/vite.config.ts b/client/vite.config.ts index 59d7fc4..657825d 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ react(), VitePWA({ registerType: "autoUpdate", - includeAssets: ["icons/icon-192.png", "icons/icon-512.png"], + includeAssets: ["icons/icon-192.png", "icons/icon-512.png", "icons/apple-touch-icon.png"], manifest: { name: "Reading Notes", short_name: "Notes", @@ -20,12 +20,14 @@ export default defineConfig({ { src: "icons/icon-192.png", sizes: "192x192", - type: "image/png" + type: "image/png", + purpose: "any maskable" }, { src: "icons/icon-512.png", sizes: "512x512", - type: "image/png" + type: "image/png", + purpose: "any maskable" } ] }