The header now shows the login page's wordmark letters (without the rule) instead of "Jarl" set in Cinzel, so the bundled Cinzel font, its preload and its licence stanza in the .deb's copyright file are removed: the web UI needs no web font. The README opens with the login page's banner, the logo above the wordmark (artwork/banner.svg). scripts/banner.py builds it from the web UI's logo.js and brand.js, with light and dark colors, and logo.py runs it after tracing the logo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
3.6 KiB
Python
Executable File
80 lines
3.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Builds artwork/banner.svg, the README's banner: the logo above the "JARL"
|
|
# wordmark, laid out as on the web UI's login page. It reads both from the
|
|
# web UI's own sources, so it matches the login page:
|
|
#
|
|
# internal/web/static/assets/logo.js the logo's paths (from scripts/logo.py)
|
|
# internal/web/static/assets/brand.js the wordmark's letters and rule
|
|
#
|
|
# The letters and rule take the UI's text and accent colors, switching to
|
|
# the dark theme's when the viewer prefers dark. Needs only Python; run from
|
|
# the repository root after changing either file (scripts/logo.py runs it):
|
|
#
|
|
# python3 scripts/banner.py
|
|
import re
|
|
import sys
|
|
|
|
ASSETS = "internal/web/static/assets"
|
|
OUT = "artwork/banner.svg"
|
|
|
|
# The UI's --text and --accent (style.css), light and dark.
|
|
STYLE = (".t{fill:#1d2330}.a{fill:#2f5fb3}.l{fill:none;stroke:#2f5fb3;stroke-width:2.6}"
|
|
".d{fill:#2f5fb3;font:700 12.5px system-ui,sans-serif;letter-spacing:.5px}"
|
|
"@media (prefers-color-scheme:dark){.t{fill:#e3e7ee}.a,.d{fill:#6d9be8}.l{stroke:#6d9be8}}")
|
|
CLASSES = {"fill": "a", "line": "l", "dns": "d"}
|
|
|
|
# The login page's sizes in pixels (style.css): a 104px logo, 10px above a
|
|
# 220px wordmark whose viewBox is 288 units wide.
|
|
UNIT = 288 / 220
|
|
LOGO = round(104 * UNIT, 1)
|
|
GAP = round(10 * UNIT, 1)
|
|
|
|
|
|
def attrs(src):
|
|
"""Turns a JS object literal's body ({ cx: 106, "text-anchor": "middle" })
|
|
into SVG attributes."""
|
|
out = []
|
|
for key, val in re.findall(r'"?([\w-]+)"?:\s*("[^"]*"|[\d.]+)', src):
|
|
val = val.strip('"')
|
|
out.append(f'class="{CLASSES[val]}"' if key == "class" else f'{key}="{val}"')
|
|
return " ".join(out)
|
|
|
|
|
|
def main():
|
|
logo = open(f"{ASSETS}/logo.js").read()
|
|
view_box = re.search(r'viewBox = "([^"]*)"', logo).group(1)
|
|
transform = re.search(r'transform = "([^"]*)"', logo).group(1)
|
|
layers = re.findall(r'\["(#\w+)", "([^"]*)"\]', logo)
|
|
|
|
brand = open(f"{ASSETS}/brand.js").read()
|
|
letters = re.findall(r'\{ x: (\d+), d: "([^"]*)" \}', brand)
|
|
rule = brand[brand.index("const ruleG"):]
|
|
rule = rule[:rule.index(");\n")]
|
|
rule_at = re.search(r'class: "wordmark-rule", transform: "([^"]*)"', rule).group(1)
|
|
shapes = re.findall(r'el\("(path|circle|text)", \{ ([^}]*) \}(?:, "([^"]*)")?\)', rule)
|
|
mark_box = re.search(r'viewBox: "(-4 -4 288 162)"', brand).group(1)
|
|
if len(layers) < 2 or len(letters) != 4 or len(shapes) < 5:
|
|
sys.exit("banner.py: logo.js or brand.js no longer looks as expected; update the script")
|
|
|
|
width, height = 288, LOGO + GAP + 162
|
|
svg = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height:g}" role="img" aria-label="Jarl">',
|
|
f"<!-- Generated by scripts/banner.py from {ASSETS}/logo.js and brand.js. -->",
|
|
f"<title>Jarl</title><style>{STYLE}</style>",
|
|
f'<svg x="{(width - LOGO) / 2:g}" width="{LOGO:g}" height="{LOGO:g}" viewBox="{view_box}">'
|
|
f'<g transform="{transform}">'
|
|
+ "".join(f'<path fill="{fill}" d="{d}"/>' for fill, d in layers) + "</g></svg>",
|
|
f'<svg y="{LOGO + GAP:g}" width="288" height="162" viewBox="{mark_box}">'
|
|
'<g class="t" fill-rule="evenodd">'
|
|
+ "".join(f'<path transform="translate({x} 0)" d="{d}"/>' for x, d in letters) + "</g>"
|
|
f'<g transform="{rule_at}">'
|
|
+ "".join(f"<{tag} {attrs(a)}>{text}</{tag}>" if text else f"<{tag} {attrs(a)}/>" for tag, a, text in shapes)
|
|
+ "</g></svg>",
|
|
"</svg>"]
|
|
with open(OUT, "w") as f:
|
|
f.write("\n".join(svg) + "\n")
|
|
print(f"{OUT}: {width}x{height:g}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|