Fix the logo's pop-in with a traced vector logo

The logo was an 880 KB SVG wrapping a 1254px PNG, downloaded again on
every page load. Keep that PNG as the master (artwork/logo.png) and have
scripts/logo.py trace it with potrace into a 6 KB logo.svg (favicon,
README) and logo.js, which brand.js draws inline in the header and on the
login page so the logo arrives with the page. Remove the unused
logo-white.svg.

Static files now carry content ETags, so reloads get 304s instead of
downloading the UI again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-08 18:01:22 +00:00
1 parent cf49df0346
commit 0f04dbb4b1
15 files changed
+215 -23049

No files matched your search

+66 -64
View File
@@ -1,70 +1,72 @@
#!/usr/bin/env python3
# Generates the Jarl logo: internal/web/static/assets/logo.svg (white figure
# on a Nordic-blue disc) and logo-white.svg (the figure alone, for dark
# backgrounds). Shapes are given for the left half and the centre line and
# mirrored, so the figure is exactly symmetrical. Run from the repository
# root: python3 scripts/logo.py
import re, sys
def mirror_path(d):
toks = re.findall(r'[MLQCZHV]|-?\d+(?:\.\d+)?', d)
out=[]; cmd=None; nums=[]
def flush():
if cmd is None: return
if cmd in 'MLQC':
v=[float(n) for n in nums]
out.append(cmd+" "+" ".join(f"{128-v[i]:g} {v[i+1]:g}" for i in range(0,len(v),2)))
elif cmd=='H': out.append("H "+" ".join(f"{128-float(n):g}" for n in nums))
elif cmd=='V': out.append("V "+" ".join(nums))
elif cmd=='Z': out.append("Z")
for t in toks:
if t.isalpha(): flush(); cmd=t; nums=[]
else: nums.append(t)
flush()
return " ".join(out)
LEFT = ['M33 46 L37 46 Q35 64 30 78 Q26 88 24 100 Q22 84 26 72 Q31 60 33 46 Z', 'M38 47 L42 47 Q41 66 37 82 Q34 92 33 104 Q31 88 34 76 Q37 62 38 47 Z', 'M41 51 L44.5 55 L44 63 L41.5 59 Z', 'M47 58 Q52.5 55 58 59.5 Q52.5 63 47 58 Z M51.4 59 L53 57.4 L54.6 59 L53 60.6 Z', 'M45 61.5 L54.5 65 L57.5 72.5 L50 73 L45.5 67.5 Z', 'M63 76 L57 75 L48 78 Q42 82 38 92 Q45 85 50 83 L57 80.5 L63 80 Z']
CENTER = ['M40 46 H88 L86.5 50 L71.5 55.5 L66.5 62.5 L61.5 62.5 L56.5 55.5 L41.5 50 Z', 'M61.5 63 H66.5 L69 72.5 L64 75.5 L59 72.5 Z', 'M38 69 L45 73.5 L63 79.5 H65 L83 73.5 L90 69 Q95 84 92 96 L88 104 L86 101 L83 110 L80 106 L76 116 L73 111 L69 121 L66 116 L64 127 L62 116 L59 121 L55 111 L52 116 L48 106 L45 110 L42 101 L40 104 L36 96 Q33 84 38 69 Z']
CUTS = [('M40 80 C42.5 86 37 91 39.5 99 C40.5 102 39 104 40 106', 2.0), ('M44.5 86 C47 92 42 97 44.5 106', 1.7), ('M48.5 85.5 C50.5 90 47 94 48.5 99', 1.5), ('M50.5 92 C53 99 48.5 104 51 113', 2.0), ('M54.5 86.5 C57 94 52.5 101 55 109', 1.7), ('M58 95 C59.5 101 56.5 106 58.5 116', 1.8), ('M60.8 85.5 C62.3 92 59.8 98 61.2 105', 1.5), ('M46 101.5 C47 104 45.5 106.5 46.5 109.5', 1.4), ('M62.2 109 C62.7 113 61.7 116 62.4 119.5', 1.4), ('M56.5 77.6 Q49.5 79.2 43 85.5', 1.3), ('M58.6 81.6 Q49.5 83.8 41.5 95', 2.4)]
CENTER_CUTS = [('M60.4 82.2 H67.6', 1.8)]
CROWN = "M36 44 L35 28 L32 18 L41 25 L50 30 L55 21 L64 9 L73 21 L78 30 L87 25 L96 18 L93 28 L92 44 Z"
def figure(color="#fff", mask_id="cut"):
shapes = CENTER + [p for d in LEFT for p in (d, mirror_path(d))]
cuts = [(p, w) for d, w in CUTS for p in (d, mirror_path(d))] + CENTER_CUTS
mask = (f'<mask id="{mask_id}" maskUnits="userSpaceOnUse" x="0" y="0" width="128" height="128">'
'<rect width="128" height="128" fill="#fff"/>'
'<g fill="none" stroke="#000" stroke-linecap="round">' + "".join(f'<path stroke-width="{w}" d="{c}"/>' for c, w in cuts) + '</g></mask>')
return f'''<path fill="none" stroke="{color}" stroke-width="3.6" stroke-linejoin="miter" stroke-miterlimit="8" d="{CROWN}"/>
<path fill="{color}" d="M37 37 H91 V40.5 H37 Z"/>
<path fill="{color}" d="M32 18 L27.5 9 L37 13.5 Z M96 18 L100.5 9 L91 13.5 Z M64 9 L61 3.5 L64 0.5 L67 3.5 Z"/>
<g fill="none" stroke="{color}" stroke-width="2.3"><circle cx="64" cy="19" r="3"/><path d="M64 22 V28.5 M55.3 31.5 H72.7"/><circle cx="52.2" cy="31.5" r="3"/><circle cx="75.8" cy="31.5" r="3"/></g>
<circle cx="64" cy="31.5" r="3.2" fill="{color}"/>
<defs>{mask}</defs>
<g fill="{color}" fill-rule="evenodd" mask="url(#{mask_id})">
''' + "\n".join(f' <path d="{d}"/>' for d in shapes) + "\n </g>"
DESC = """<!-- Jarl: a Viking ruler in a spear-pointed crown, drawn like a woodcut.
The crown holds a DNS tree: the root node with its branches below.
Generated by scripts/logo.py, which mirrors the left half, so it is
exactly symmetrical; the beard's strands are cut out with a mask. -->"""
# Builds the web UI's logo from the master image, artwork/logo.png (a blue
# and white figure on a transparent background), by tracing it into vector
# paths with potrace:
#
# internal/web/static/assets/logo.svg the favicon, and the README's logo
# internal/web/static/assets/logo.js the same paths for the web UI, which
# draws them inline (brand.js), so the
# logo appears together with the page
#
# Both are a few KB, where the PNG is 650 KB. Needs potrace and ImageMagick
# (convert). Run from the repository root after changing the master:
#
# python3 scripts/logo.py
import os
import re
import subprocess
import sys
import tempfile
MASTER = "artwork/logo.png"
OUT = "internal/web/static/assets"
BLUE = "#1d5287" # the master's blue
WHITE = "#fff"
PAD = 6 # pixels of the master around the figure
def main(outdir):
logo = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-labelledby="t">
<title id="t">Jarl</title>
{DESC}
<circle cx="64" cy="64" r="64" fill="#2f5fb3"/>
<g transform="translate(64 66) scale(0.8) translate(-64 -64)">
{figure("#fff", "jarl-cut")}
</g>
</svg>
'''
white = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="20 -1 88 130" role="img" aria-labelledby="t">
<title id="t">Jarl</title>
{DESC}
{figure("#fff", "jarl-cut-white")}
</svg>
'''
open(outdir + "/logo.svg", "w").write(logo)
open(outdir + "/logo-white.svg", "w").write(white)
def run(*args):
return subprocess.run(args, check=True, capture_output=True, text=True).stdout
def trace(pbm):
"""Traces a bitmap into one SVG path (in the master's pixels, y up)."""
svg = run("potrace", "-b", "svg", "--flat", "-t", "4", "-a", "1.0", "-O", "0.4", "-u", "1", pbm, "-o", "-")
return " ".join(re.search(r'<path d="([^"]*)"', svg, re.S).group(1).split())
def main():
with tempfile.TemporaryDirectory() as tmp:
base, white = os.path.join(tmp, "base.pbm"), os.path.join(tmp, "white.pbm")
# The figure's whole shape (opaque pixels), drawn in blue...
run("convert", MASTER, "-alpha", "extract", "-threshold", "50%", "-negate", base)
# ...and its white parts on top; the blue lines show between them.
run("convert", MASTER, "-background", "black", "-alpha", "remove", "-alpha", "off",
"-colorspace", "gray", "-threshold", "75%", "-negate", white)
layers = [(BLUE, trace(base)), (WHITE, trace(white))]
height = int(run("identify", "-format", "%h", MASTER))
w, h, x, y = map(int, re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)",
run("convert", MASTER, "-alpha", "extract", "-threshold", "50%", "-format", "%@", "info:")).groups())
side = max(w, h) + 2 * PAD
view_box = f"{x + w / 2 - side / 2:g} {y + h / 2 - side / 2:g} {side:g} {side:g}"
transform = f"translate(0 {height}) scale(1 -1)"
paths = "".join(f'<path fill="{fill}" d="{d}"/>' for fill, d in layers)
with open(os.path.join(OUT, "logo.svg"), "w") as f:
f.write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{view_box}">'
f'<!-- Generated by scripts/logo.py from {MASTER}. -->'
f'<g transform="{transform}">{paths}</g></svg>\n')
with open(os.path.join(OUT, "logo.js"), "w") as f:
f.write(f"// The Jarl logo, traced from {MASTER} by scripts/logo.py; don't edit.\n"
f"// brand.js draws it; logo.svg holds the same paths.\n"
f'export const viewBox = "{view_box}";\n'
f'export const transform = "{transform}";\n'
"export const layers = [\n"
+ "".join(f' ["{fill}", "{d}"],\n' for fill, d in layers)
+ "];\n")
for name in ("logo.svg", "logo.js"):
print(f"{OUT}/{name}: {os.path.getsize(os.path.join(OUT, name))} bytes")
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "internal/web/static/assets")
sys.exit(main())