diff --git a/assets/favicon/apple-touch-icon.png b/assets/favicon/apple-touch-icon.png
new file mode 100644
index 0000000..184ab6e
Binary files /dev/null and b/assets/favicon/apple-touch-icon.png differ
diff --git a/assets/favicon/favicon-16.png b/assets/favicon/favicon-16.png
new file mode 100644
index 0000000..5905802
Binary files /dev/null and b/assets/favicon/favicon-16.png differ
diff --git a/assets/favicon/favicon-32.png b/assets/favicon/favicon-32.png
new file mode 100644
index 0000000..31e5f9f
Binary files /dev/null and b/assets/favicon/favicon-32.png differ
diff --git a/assets/favicon/favicon-48.png b/assets/favicon/favicon-48.png
new file mode 100644
index 0000000..acb60ac
Binary files /dev/null and b/assets/favicon/favicon-48.png differ
diff --git a/assets/favicon/favicon.ico b/assets/favicon/favicon.ico
new file mode 100644
index 0000000..dd1fcbf
Binary files /dev/null and b/assets/favicon/favicon.ico differ
diff --git a/assets/favicon/icon-192.png b/assets/favicon/icon-192.png
new file mode 100644
index 0000000..05b6f56
Binary files /dev/null and b/assets/favicon/icon-192.png differ
diff --git a/assets/favicon/icon-512.png b/assets/favicon/icon-512.png
new file mode 100644
index 0000000..e70f65d
Binary files /dev/null and b/assets/favicon/icon-512.png differ
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()