Files
zomas f5c763be7b Gitea theme: capital theta (circle+H) logo + homepage senza badge
- Logo/favicon ridisegnati come theta maiuscola: cerchio (anello)
  con 'H' centrata, clearance verificata (non tocca l'anello); ring
  ispessito per la favicon; centratura ottica verificata.
- Rimosso il badge 'Self-hosted Git' dal hero della homepage (era
  esteticamente di troppo) - CSS e markup.
- Aggiornati generate_theta.py, README, home.tmpl; caricati sul VPS in
  staging (md5 locali == remoti); tarball ricostruito.
- Salvataggio iniziale dell'intero admin pack in git.
2026-09-01 16:54:15 +02:00

140 lines
5.5 KiB
Python

#!/usr/bin/env python3
# Logo/favicon Gitea: THETA MAIUSCOLA = cerchio (anello) con una "H" al centro.
# La H non tocca l'anello (clearance sicura). Niente coda.
# Emette sia gli SVG (logo/favicon) che i PNG (logo/favicon/apple-touch-icon)
# partendo dalla STESSA geometria, così SVG e PNG combaciano.
import os
from PIL import Image, ImageDraw
OUT = "custom/public/assets/img"
# --- geometria normalizzata (frazioni del lato quadrato) ---
INSET = 0.04 # bordo del riquadro arrotondato
RAD = 0.22 # raggio angoli del riquadro
CX, CY = 0.5, 0.5 # centro (simmetrico, nessuna coda)
RO = 0.41 # raggio esterno anello
RING = 0.05 # spessore anello -> raggio interno RI = RO - RING
# H centrale
HH = 0.26 # mezza altezza della H (stelo da CY-HH a CY+HH)
SPAC = 0.145 # distanza orizzontale dal centro agli steli
STEM = 0.05 # spessore steli
CROSS = 0.042 # spessore barra orizzontale
C1, C2 = (124, 58, 237), (14, 165, 233) # viola -> azzurro
RI = RO - RING
def lerp(a, b, t):
return int(a + (b - a) * t)
def grad_color(t):
return (lerp(C1[0], C2[0], t), lerp(C1[1], C2[1], t), lerp(C1[2], C2[2], t))
def theta_geo(size):
cx, cy = CX * size, CY * size
ro, ri = RO * size, RI * size
stem = STEM * size
cross = CROSS * size
# steli: centro x = cx ± SPAC
left_x0 = (CX - SPAC - STEM / 2) * size
left_x1 = (CX - SPAC + STEM / 2) * size
right_x0 = (CX + SPAC - STEM / 2) * size
right_x1 = (CX + SPAC + STEM / 2) * size
st_y0, st_y1 = (CY - HH) * size, (CY + HH) * size
# barra: attraversa tutta la larghezza della H, centrata su CY
cb_y0, cb_y1 = CY * size - cross / 2, CY * size + cross / 2
return dict(cx=cx, cy=cy, ro=ro, ri=ri,
left_x0=left_x0, left_x1=left_x1,
right_x0=right_x0, right_x1=right_x1,
st_y0=st_y0, st_y1=st_y1,
cb_x0=left_x0, cb_x1=right_x1, cb_y0=cb_y0, cb_y1=cb_y1,
stem=stem, cross=cross)
def circle_path(r, cx, cy, size):
lx, rx = cx - r, cx + r
return (f"M {lx:.2f} {cy:.2f} A {r:.2f} {r:.2f} 0 1 0 {rx:.2f} {cy:.2f} "
f"A {r:.2f} {r:.2f} 0 1 0 {lx:.2f} {cy:.2f} Z")
def svg_theta(size):
g = theta_geo(size)
inset, rad = INSET * size, RAD * size
w = size
def e(x):
return f"{x:.2f}"
outer = circle_path(g["ro"], g["cx"], g["cy"], size)
inner = circle_path(g["ri"], g["cx"], g["cy"], size)
lines = []
lines.append(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {size} {size}" width="{size}" height="{size}">')
lines.append(' <defs><linearGradient id="zg" x1="0" y1="0" x2="0" y2="1">'
'<stop offset="0" stop-color="#7c3aed"/><stop offset="1" stop-color="#0ea5e9"/></linearGradient></defs>')
lines.append(f' <rect x="{e(inset)}" y="{e(inset)}" width="{e(w-2*inset)}" height="{e(w-2*inset)}" rx="{e(rad)}" fill="url(#zg)"/>')
# anello (theta maiuscola = cerchio)
lines.append(f' <path fill-rule="evenodd" fill="#fff" d="{outer}{inner}"/>')
# H centrale (steli + barra)
lines.append(f' <rect x="{e(g["left_x0"])}" y="{e(g["st_y0"])}" width="{e(g["left_x1"]-g["left_x0"])}" height="{e(g["st_y1"]-g["st_y0"])}" fill="#fff"/>')
lines.append(f' <rect x="{e(g["right_x0"])}" y="{e(g["st_y0"])}" width="{e(g["right_x1"]-g["right_x0"])}" height="{e(g["st_y1"]-g["st_y0"])}" fill="#fff"/>')
lines.append(f' <rect x="{e(g["cb_x0"])}" y="{e(g["cb_y0"])}" width="{e(g["cb_x1"]-g["cb_x0"])}" height="{e(g["cb_y1"]-g["cb_y0"])}" fill="#fff"/>')
lines.append('</svg>')
return "\n".join(lines) + "\n"
def draw_theta_png(size, apple=False):
g = theta_geo(size)
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
inset, rad = INSET * size, RAD * size
# sfondo: riquadro arrotondato con gradiente verticale
sq = Image.new("L", (size, size), 0)
ImageDraw.Draw(sq).rounded_rectangle(
[inset, inset, size - inset, size - inset], radius=rad, fill=255)
grad = Image.new("RGBA", (size, size), (0, 0, 0, 0))
gd = ImageDraw.Draw(grad)
for i in range(size * 2):
t = i / (size * 2 - 1)
gd.line([(i, 0), (i, size)], fill=grad_color(t))
grad.putalpha(sq)
img.alpha_composite(grad)
# theta+H come maschera bianca
mask = Image.new("L", (size, size), 0)
md = ImageDraw.Draw(mask)
md.ellipse([g["cx"] - g["ro"], g["cy"] - g["ro"],
g["cx"] + g["ro"], g["cy"] + g["ro"]], fill=255)
md.ellipse([g["cx"] - g["ri"], g["cy"] - g["ri"],
g["cx"] + g["ri"], g["cy"] + g["ri"]], fill=0)
md.rectangle([g["left_x0"], g["st_y0"], g["left_x1"], g["st_y1"]], fill=255)
md.rectangle([g["right_x0"], g["st_y0"], g["right_x1"], g["st_y1"]], fill=255)
md.rectangle([g["cb_x0"], g["cb_y0"], g["cb_x1"], g["cb_y1"]], fill=255)
white = Image.new("RGBA", (size, size), (255, 255, 255, 255))
white.putalpha(mask)
img.alpha_composite(white)
if apple:
base = Image.new("RGBA", img.size, (255, 255, 255, 255))
base.alpha_composite(img)
return base.convert("RGB")
return img
def make_assets():
os.makedirs(OUT, exist_ok=True)
open(f"{OUT}/logo.svg", "w").write(svg_theta(64))
open(f"{OUT}/favicon.svg", "w").write(svg_theta(64))
draw_theta_png(220).save(f"{OUT}/logo.png")
draw_theta_png(64).save(f"{OUT}/favicon.png")
draw_theta_png(180, apple=True).save(f"{OUT}/apple-touch-icon.png")
print("theta (capital: circle + H) assets written to", OUT)
if __name__ == "__main__":
make_assets()