From 111b89291681e1c67cd7051eda912e0e9d4c0907 Mon Sep 17 00:00:00 2001 From: Andrea Manzi Date: Mon, 26 Oct 2026 18:51:11 +0100 Subject: [PATCH] Il favicon: un Regolino in pixel art con la corona (Codex, tools/favicon.py), da 16 a 512 pixel e per l'iPhone Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr --- assets/favicon/apple-touch-icon.png | Bin 0 -> 772 bytes assets/favicon/favicon-16.png | Bin 0 -> 341 bytes assets/favicon/favicon-32.png | Bin 0 -> 364 bytes assets/favicon/favicon-48.png | Bin 0 -> 407 bytes assets/favicon/favicon.ico | Bin 0 -> 1268 bytes assets/favicon/icon-192.png | Bin 0 -> 835 bytes assets/favicon/icon-512.png | Bin 0 -> 2315 bytes index.html | 5 + tools/favicon.py | 149 ++++++++++++++++++++++++++++ 9 files changed, 154 insertions(+) create mode 100644 assets/favicon/apple-touch-icon.png create mode 100644 assets/favicon/favicon-16.png create mode 100644 assets/favicon/favicon-32.png create mode 100644 assets/favicon/favicon-48.png create mode 100644 assets/favicon/favicon.ico create mode 100644 assets/favicon/icon-192.png create mode 100644 assets/favicon/icon-512.png create mode 100644 tools/favicon.py diff --git a/assets/favicon/apple-touch-icon.png b/assets/favicon/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..184ab6e050e40d013b0fe7bcdf9632f573e3013a GIT binary patch literal 772 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD2}o{QKQWbof$6lTi(^Q|t+#g#JwgK|4t$K; z!NS*=C%EO=H5H{iAr{}>slmLB*$bK%cR%=b;ONSt)t&;296JQmx5wO?)7-XTqgm#M znh$n-?Zx+h|Gjp4_xE3aiuG&mRNs4X?@@Df!Q|Wfn2t1B2(UkLkm+D9RNw=05G;wd zo&Wql{yTL4(z4a_7w@v>39%2$YhQ<~viam!BNpy9*Yx;pUbDlleHS{;RVJ~bZcRe_ z+%v~xERhU8xMha)mz(=<&-AZ(b^PUyn;XHh3=lg7*kvWY?l-9WFd-t(ptP`J`IeU# zSKF8R*Cl^1&zyW+u`t>ZXe-2}3#P`kCG0HSwQMR(ys!b}g9WRWt#@Xdyl(pM{=dfS z_SXO2xni>6SVDJBz?Rhq$&m)9_8} z$M9+Mrtienz|H_VIi3+1U{{PA)DNZw7_j^+Z%9XR=VWPkX zbWB3g*Y<>iOSET1?ycA~#o}7U=j3N1^Vh4{O{xBB^ked5VH@tpkGC~J6Tso{28$UH zm*d;l*s;96-#+<$r+=h`{B}`rxPz0&s#j01EB>6{`VkT#@PN;5`nq4l29YwL;eeF1 c3>^M5lrv7;8ltbv4@}(*p00i_>zopr0G(q~!2kdN literal 0 HcmV?d00001 diff --git a/assets/favicon/favicon-16.png b/assets/favicon/favicon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..5905802591d5c0762583ad4e53f1aa769699ac49 GIT binary patch literal 341 zcmV-b0jmCqP)1tTEg*F2B4l&31YAP? zK>xux=!MJWYiZvl-+SEq+}-yD9EGEywrkgONa1Lxb+gnasGDWayAWRjy*!=+@UvNT zyd=*yWmT(aGH?CY^I2zeG?`OYwX)aLzV{!J4e{-8n#UZa*;*Tj=Z3TKlx#e0_qrY+ z&ExjAo^(KTc_Y?F#ir@@;H2yS19ruhvZ|G5n({hZ17N2Tnf)*ZvE~{E8 n39wIZAKw6S6Oa9k-9G#RT*{U1BeJC~00000NkvXXu0mjf>-w6; literal 0 HcmV?d00001 diff --git a/assets/favicon/favicon-32.png b/assets/favicon/favicon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..31e5f9f9c225552573015632b435c99820bf3e11 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=hEVFdBQhIEGZ*N={K=(7E#Tzp;UV z!JHjGCkW2A;CV2~UEH1BhliO*g7fE}AM=XXK)`#tpGf5lkZgPBW3Em{enzn*QN48! z9UtDeG!R{HvoYbrGR7}k8r~iLTx=;R0R%jg_a|Q!zx}B-vEm6+R^!6S+zEF5FAEp1 z^HtrwVOmeKI`4x|mD_UJxawa$lvFvnxLH+ocfh417c@C_&o)bJXch4J^W$V`#n*hx z4;2%pGn;t-%~1T1c>3$XscaIQQ<*n}c;}p0u=@DZ&kKXa7qqh|I;=V09#_pEdnRJq zp#wk=uFVdQ I&MBb@06b5YVgLXD literal 0 HcmV?d00001 diff --git a/assets/favicon/favicon-48.png b/assets/favicon/favicon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..acb60ac4b8718794949ff46b6ba6aa64aaa75a16 GIT binary patch literal 407 zcmV;I0cie-P)=Sqs(x%YnzEZK2^;3egiFC!r5pu8)>NL#sKBD>)%o zD3l~KT;>pyY(`6=A>AXxm7GWglq8$cBsAob5UCyf8Ou`O?22Vau_g~nI`X4QXvlTy zaRC7zO{$bUDE2ZikTkzGnuG>b5<*Tk$jKwxQww{?JbNv6Q=_a2wtsLXMO?`eIcbeH zr-ww3jF==uEwFU^HLT*J&FNv&BO53%+MFH=0056en{q4=u!8^q002ovPDHLkV1fj8 BqKp6l literal 0 HcmV?d00001 diff --git a/assets/favicon/favicon.ico b/assets/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..dd1fcbfe4509501ed24df0a517120d33abfa0a13 GIT binary patch literal 1268 zcmZQzU}Rus5D;Jh0tJR>Mg|5m5K95VpAO_t1Y!dN2>%R_t-;K|&>7(8&dVjm1r+7= z^l%9R(jXHAIM{%s{OA9P3=E7)o-U3d6?3Le+URvSK%{Mdi%ygCy#>jg`Uy=X+Kh^m z7pIkIq`ZCbfLmnKwjCBL@7`f@TCV40X6}7J^$F`Qw(AS$oj!Y3^!5HH|MSoNzhBHJ zu5;w^M5CQ+-~MWhEY((ZySkR`79VGfqyP8i3^lpaZQnNKckRmDo3iV|ug4#kgwOF2 zzvBCPX1eXi=RL<<4}InMSh3`jk?k@5&%Kib?wg5EmOPrWEIXHX!t|Bzwryv2EUJ6@ zq$%rm=DWYU|ITly-MTn0>U{g*pn1=w8J=W2pYz@P&Ou@B4yR8>vNPBjg_Qmz)}Frm zYi{k<%m3e&3+(7g6tm1{?{J*y*f`^`&ZB3acp6eaRu#m>e|g5g*`xjVf>+DF+Jqm- zuc-XZQlS6wMtaUR{tvt{r>4H^Ne@^A3^WE$S3j3^P6r@2tpE<%;;O1Iz@UwT z1Z{GP0)x(#r~i!&3=HP%_&Gsvwgu0FN$%qA>^?0r4%rQtOAX{@?gyVc00apyFLf&M z3%4`Id@?AEQ8av@c))4Rp`)fY8h@4^PLIhIfZQ7h6h700GbB z{mECwZ+~iSY)rIx!j#pxa58s-UH{9%#p`@kcW;>1!z{;CaQ(rj%5Ax9T=lOWN~)Y( z+^nj)8>m$PWQjwGXGW&nkpl-BB%KUCK32c}@aq1j2a?x30_tPA%&?U~hs7>uC(wlk z9hVr~Ube^yZ3ywsIk8~%@u$TS5;ckp%sZT#1Qf14H!!GhW?(D0+GRX#DbTwH1|d4u zr+~(O(6o~H@|JPVRz)j;4;PPQ3sl`Xd^S1Z!0mbo8%2iCmS>hs86r z-b1eERSk+SIP-}qHLq7u5n)==wDk7+g&*4eJU!lCGvML5HA#Tsn%?Z7$;bXF6Nna2 zo-hDMOE}Zdc3`wjhDVEF#ER4Z+c)~(sX5`{!E2=8W~*nqgF#rci(OgblbnLqt_h2O z{Fqm?W5-f&^{}revL-MZzM6PdVDg-{8XlfX{tW?(Dhqk}!#)C|X3~;*LIzKg6HZh& zEb(e?WZdd@MQfz@)QIXUqLxU%_4uQqDF4=IL zJ2v&ngsU?4JNr~VZ^FVb1csX^KVWs`*!^1qvW)xl6SX$9o>KOCs8yFlI6c_}6+-J*nTK1KQJ^k@_@{2#x=J8Y3*~fx(EPtJuTXur4VZQ@J9g?;g z6Q5>?@A;my_RF99{b8}|@AIJ=30CT`ec3&Ih1$M=uR3R+-~D{?W4PSE%vZl_KKv;! z*;C%q2zLP7;0rzP?hCrb?@{J~3&E{!Fnao)!#lnD-Tpfz;m>k@8~m~jInQ^K@q;)A zhGJu(^O0N3Zf`DqT=?>5xO~}~|2%td+4c1Kr?>^;hq^`P?q?mm(L$v&S!fY>VlD28M)$;IHgJ_bq)tK`Pg5 z<*(nFx!aCo8s@-Yuwms^)~#mNS9V`at9}EG5hM?>Ffh-q+TT!8w&K>ge~VxISpHjn z@#D)kXJ@TF`|SoJEDGToI2hQrt@+n*Ev;1V&b}*Oc~{q+^RKBF@4oc=wQM3hED^Ck zC(4F<%DHb#Hk*IXi`Cov;Oe#6xAMQd`+Dhe`2NQ_d*iQe4t=>M(8@Z6{Xo0|D6zn8 z0vUG31!kistobTzTc8~%z$3#?wb+OX)~_VeeRuh;*+_xj3t`>OwoEC21h zzki*4P4gX~eYj0*0Ll{OC=9Ql`{6+1%v(%Kd#fvzzy7LUd)~JG055LOqH89_85m(B z)0MI8JXU_k3PkXQY{{Q&x*9$+*=l}UA#8UO=Rrw2G%+xUB4+km+(Ql1y3Sq(F zi(?s#Ui~^~egaQ007D)lT#1bh%wU+7`HeRN6b$z9=jJ#mEI+FC<^T5A?T>)TA44%w z))MO!2Vf!sCb1jdo&OkpV|yolettUg6>~w^_sCaY=hs(0{#pG`rF3IN>$&&86u(Hn z0lG&>2zS~bXe-n@!hZU9_llUn%WrGh7Xn>0^DqCGx4+%>9h2TxWeej^4WuMTRM(&< z>;s9K7ws2-GiZ%vF&6{Vme;v2{>1L)oO}PP^9%DEz{H`VLRKjOi9w=W`e*l~TIN~1 zq?DhZpT7CR*La<<$!A`xZ-4WAe{8<<^Y^!WLAfNTp7|~b1r|IKGE z`S<_zuZ25+VT|7p4BtV+iG;`jx&E6`FElsz`}I3t|Nr8j=PTxlx^If#rGI?zd-W8U zf>vIa|NCFq*8b0LHMDg^415;keU$}Br!t(!% zFBT<#``^vCXq|B7x4(Vx`ugj6PS4B#1{l5H4@^JQOC)$q+R+5bT(Zm!|JT26PZFnF d@#6TCJFRfO&?V2Eg1|6h@O1TaS?83{1OT9vpN#+j literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 3d93daf..db2b592 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,11 @@ La Leggenda del Regolo + + + + + diff --git a/tools/favicon.py b/tools/favicon.py new file mode 100644 index 0000000..23f9aa9 --- /dev/null +++ b/tools/favicon.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Disegna il Regolino, pixel per pixel, senza immagini sorgenti. + + python3 tools/favicon.py + +Scrive assets/favicon/ e /tmp/regolo-shots/favicon-anteprima.png. +La matrice 16×16 è il disegno originale: ogni simbolo è un pixel. +Tutti gli ingrandimenti sono interi e usano NEAREST, anche nell'ICO. +Colori e carattere riprendono regolo() in src/engine/briscola-render.js. +""" + +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + + +ROOT = Path(__file__).resolve().parent.parent +OUTPUT = ROOT / "assets" / "favicon" +PREVIEW = Path("/tmp/regolo-shots/favicon-anteprima.png") +BACKGROUND = "#1b1420" +PALETTE = { + ".": (0, 0, 0, 0), + "#": "#38271f", # contorno del dorso + "G": "#dba32b", # oro + "H": "#ffe396", # luce sulla corona + "R": "#aa4034", # rubino + "L": "#94b365", # testa verde salvia + "S": "#487348", # ombra / spira + "V": "#a3bc72", # luce sulla spira + "B": "#d0cd83", # ventre + "W": "#fff0cd", # occhi crema +} + +# Corona a tre punte, grande testa sorridente, collo e coda arrotolata. +# Nessun tratto viene interpolato: anche gli occhi sono singoli pixel. +PIXELS = ( + "....#..##..#....", + "...#H##HH##H#...", + "...#HGGGGGGH#...", + "....#GGRGGG#....", + "...##########...", + "..#LLLLLLLLLL#..", + ".#LLWWLLWWLLLL#.", + ".#LLW#LL#WLLLS#.", + ".#LLLLLLLLLLLS#.", + "..#L#LLLLL#LS#..", + "...#L#####LS#...", + "....###BS###....", + "..###SSBSLSS##..", + ".#SVV##BS##SSS#.", + ".#SVVVVVVVVSS#..", + "..###########...", +) + + +def sprite(): + """Interpreta la matrice letterale, mantenendo solo alpha 0 o 255.""" + if len(PIXELS) != 16 or any(len(row) != 16 for row in PIXELS): + raise ValueError("Il disegno deve contenere esattamente 16×16 pixel") + result = Image.new("RGBA", (16, 16)) + for y, row in enumerate(PIXELS): + for x, symbol in enumerate(row): + color = PALETTE[symbol] + if isinstance(color, str): + color = tuple(bytes.fromhex(color[1:])) + (255,) + result.putpixel((x, y), color) + return result + + +def enlarge(source, factor): + return source.resize( + (source.width * factor, source.height * factor), Image.Resampling.NEAREST + ) + + +def preview(icons): + """Mostra ogni PNG a 1× e 8×, sia su chiaro sia su scuro. + + Le sei righe includono anche le icone grandi: il foglio è volutamente + ampio, così la scala dichiarata resta esatta per tutti i file. + """ + margin, gap, label_height = 24, 24, 32 + largest = max(icon.width for _, icon in icons) + panel_width = margin * 2 + largest + gap + largest * 8 + height = margin + sum(label_height + icon.height * 8 + gap for _, icon in icons) + sheet = Image.new("RGB", (panel_width * 2, height)) + draw = ImageDraw.Draw(sheet) + font = ImageFont.load_default(size=16) + for panel, (background, foreground) in enumerate( + (("#f5f1e9", "#38271f"), (BACKGROUND, "#fff0cd")) + ): + left = panel * panel_width + draw.rectangle((left, 0, left + panel_width - 1, height - 1), fill=background) + y = margin + for name, icon in icons: + draw.text( + (left + margin, y), + f"{name} | {icon.width} x {icon.height} | 1x e 8x", + fill=foreground, + font=font, + ) + y += label_height + sheet.paste(icon, (left + margin, y), icon) + magnified = enlarge(icon, 8) + sheet.paste(magnified, (left + margin + icon.width + gap, y), magnified) + y += icon.height * 8 + gap + PREVIEW.parent.mkdir(parents=True, exist_ok=True) + sheet.save(PREVIEW, optimize=True) + + +def main(): + OUTPUT.mkdir(parents=True, exist_ok=True) + base = sprite() + icons = [] + for size in (16, 32, 48): + icon = enlarge(base, size // 16) + name = f"favicon-{size}.png" + icon.save(OUTPUT / name, optimize=True) + icons.append((name, icon)) + + # 16×9 = 144: margini di 18 px, centratura esatta sul fondo opaco. + apple = Image.new("RGBA", (180, 180), BACKGROUND) + apple.alpha_composite(enlarge(base, 9), (18, 18)) + apple = apple.convert("RGB") + apple.save(OUTPUT / "apple-touch-icon.png", optimize=True) + icons.append(("apple-touch-icon.png", apple.convert("RGBA"))) + + for size in (192, 512): + icon = enlarge(base, size // 16) + name = f"icon-{size}.png" + icon.save(OUTPUT / name, optimize=True) + icons.append((name, icon)) + + # Fornire tutti i frame evita il ridimensionamento implicito di Pillow. + icons[2][1].save( + OUTPUT / "favicon.ico", + format="ICO", + sizes=[(16, 16), (32, 32), (48, 48)], + append_images=[icons[0][1], icons[1][1]], + ) + preview(icons) + for name, _ in icons: + print(OUTPUT / name) + print(OUTPUT / "favicon.ico") + print(PREVIEW) + + +if __name__ == "__main__": + main()