#!/usr/bin/env python3 """Render static/logo.svg into the home-screen icons. python3 tools/make_icons.py Writes static/icon-180.png (what iOS uses on the home screen) and static/icon-512.png (Android / the web manifest): the white mark on the app's dark background color. iOS flattens a transparent PNG onto white rather than black, which made the white-on-transparent mark invisible on the home screen -- so the background needs to be baked into the PNG itself. The PNGs are committed, so this only needs running if the logo changes. It rasterises with headless Chromium, which is a heavy thing to install for one job -- if you have librsvg to hand, this does the same: rsvg-convert -w 512 -h 512 --background-color '#0a0d14' static/logo.svg -o static/icon-512.png Otherwise: pip install playwright && playwright install chromium """ import sys from pathlib import Path STATIC = Path(__file__).resolve().parent.parent / "static" LOGO = STATIC / "logo.svg" SIZES = (180, 512) MARGIN = 0.14 # breathing room, so iOS's rounded mask never clips it BACKGROUND = "#0a0d14" # matches theme_color/background_color in the manifest PAGE = """ """ def main(): try: from playwright.sync_api import sync_playwright except ImportError: sys.exit("needs playwright: pip install playwright && playwright install chromium") if not LOGO.exists(): sys.exit(f"no logo at {LOGO}") scratch = STATIC / "_icon.html" try: with sync_playwright() as p: browser = p.chromium.launch(args=["--no-sandbox"]) for size in SIZES: inner = round(size * (1 - 2 * MARGIN)) scratch.write_text(PAGE.format(size=size, inner=inner, background=BACKGROUND)) page = browser.new_page(viewport={"width": size, "height": size}) page.goto(scratch.as_uri()) page.wait_for_timeout(120) # let the SVG paint target = STATIC / f"icon-{size}.png" page.screenshot(path=target) page.close() print(f"wrote {target} ({target.stat().st_size} bytes)") browser.close() finally: scratch.unlink(missing_ok=True) if __name__ == "__main__": main()