Web host serves the WebAssembly app without prerendering and proxies /api, /hubs and /ics to the Api via YARP. MudBlazor shell with German UI, dark mode, drawer and bottom navigation, login and forced password change, manifest, icons and service worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""Erzeugt die PWA-Icons (PNG + SVG) für FamilyPlanner. Aufruf: python make_icons.py <zielordner>"""
|
|
import math
|
|
import os
|
|
import sys
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
BG = "#1565C0"
|
|
FG = "#FFFFFF"
|
|
HEART = "#FF7043"
|
|
|
|
|
|
def heart_points(cx, cy, scale):
|
|
pts = []
|
|
for i in range(120):
|
|
t = 2 * math.pi * i / 120
|
|
x = 16 * math.sin(t) ** 3
|
|
y = 13 * math.cos(t) - 5 * math.cos(2 * t) - 2 * math.cos(3 * t) - math.cos(4 * t)
|
|
pts.append((cx + x * scale, cy - y * scale))
|
|
return pts
|
|
|
|
|
|
def glyph(s, inset):
|
|
"""Koordinaten im 512er-Raster, skaliert auf das Inset (für maskable Icons kleiner)."""
|
|
f = (512 - 2 * inset) / 512
|
|
|
|
def p(x, y):
|
|
return (inset + x * f) * s / 512, (inset + y * f) * s / 512
|
|
|
|
roof = [p(256, 92), p(80, 244), p(432, 244)]
|
|
body = [p(132, 222), p(380, 424)]
|
|
heart = [p(x, y) for x, y in heart_points(256, 318, 5.2)]
|
|
return roof, body, heart, f
|
|
|
|
|
|
def draw_icon(size, maskable):
|
|
s = size * 4 # Supersampling
|
|
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(img)
|
|
if maskable:
|
|
d.rectangle([0, 0, s, s], fill=BG)
|
|
inset = 80
|
|
else:
|
|
d.rounded_rectangle([0, 0, s - 1, s - 1], radius=int(s * 0.22), fill=BG)
|
|
inset = 0
|
|
roof, body, heart, f = glyph(s, inset)
|
|
d.polygon(roof, fill=FG)
|
|
d.rounded_rectangle([body[0], body[1]], radius=int(s * 0.03 * f), fill=FG)
|
|
d.polygon(heart, fill=HEART)
|
|
return img.resize((size, size), Image.LANCZOS)
|
|
|
|
|
|
def write_svg(path):
|
|
heart = " ".join(f"{x:.1f},{y:.1f}" for x, y in heart_points(256, 318, 5.2))
|
|
svg = f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Familienplaner">
|
|
<rect width="512" height="512" rx="112" fill="{BG}"/>
|
|
<polygon points="256,92 80,244 432,244" fill="{FG}"/>
|
|
<rect x="132" y="222" width="248" height="202" rx="15" fill="{FG}"/>
|
|
<polygon points="{heart}" fill="{HEART}"/>
|
|
</svg>
|
|
"""
|
|
with open(path, "w", encoding="utf-8", newline="\n") as fh:
|
|
fh.write(svg)
|
|
|
|
|
|
def main(target):
|
|
os.makedirs(target, exist_ok=True)
|
|
for size in (48, 72, 96, 128, 144, 152, 192, 256, 384, 512):
|
|
draw_icon(size, False).save(os.path.join(target, f"icon-{size}.png"), optimize=True)
|
|
for size in (192, 512):
|
|
draw_icon(size, True).save(os.path.join(target, f"icon-maskable-{size}.png"), optimize=True)
|
|
draw_icon(32, False).save(os.path.join(target, "favicon-32.png"), optimize=True)
|
|
# Apple ignoriert Transparenz -> vollflächig
|
|
draw_icon(180, True).save(os.path.join(target, "apple-touch-icon.png"), optimize=True)
|
|
write_svg(os.path.join(target, "icon.svg"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1])
|