# Steward
diff --git a/artwork/banner.svg b/artwork/banner.svg
new file mode 100644
index 0000000..d91fbf3
--- /dev/null
+++ b/artwork/banner.svg
@@ -0,0 +1,6 @@
+
diff --git a/docs/development.md b/docs/development.md
index 6caf9b4..f4d797f 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -24,7 +24,8 @@ scripts/e2e.sh end-to-end test with real DHCP clients in Docker
scripts/mkdeb.sh the Debian packages (make deb)
scripts/logo.py the web UI's logo from artwork/logo.svg
scripts/optionsdoc.py docs/options.md from the option catalog
-artwork/logo.svg the logo's master
+scripts/banner.py the README's banner from the web UI's logo and wordmark
+artwork/logo.svg the logo's master; artwork/banner.svg the README's banner
```
[design.md](design.md) describes how the pieces fit.
@@ -83,8 +84,11 @@ same helpers as Jarl's:
The logo's master is `artwork/logo.svg`; `scripts/logo.py` writes
`assets/logo.svg` (the favicon) and `assets/logo.js` (drawn inline by
-`brand.js`). The STEWARD wordmark is drawn by `wordmark()` in `brand.js`,
-in the letters of Jarl's wordmark.
+`brand.js`), and then runs `scripts/banner.py`. The STEWARD wordmark is
+drawn by `wordmark()` in `brand.js`, in the letters of Jarl's wordmark.
+`artwork/banner.svg`, the README's banner, is the login page's logo and
+wordmark, generated by `scripts/banner.py`. It draws the rule with the same
+numbers as `brand.js`, so change both together.
## Conventions
diff --git a/scripts/banner.py b/scripts/banner.py
new file mode 100755
index 0000000..341db36
--- /dev/null
+++ b/scripts/banner.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# Builds artwork/banner.svg, the README's banner: the logo above the
+# "STEWARD" wordmark and its DHCP rule, 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 shapes (from scripts/logo.py)
+# internal/web/static/assets/brand.js the wordmark's letters and width
+#
+# The rule is drawn here with the same numbers as wordmark() in brand.js;
+# keep the two in step. 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 json
+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}"
+ ".g{fill:#2f5fb3;font:700 17px system-ui,sans-serif;letter-spacing:.5px}"
+ "@media (prefers-color-scheme:dark){.t{fill:#e3e7ee}.a,.g{fill:#6d9be8}.l{stroke:#6d9be8}}")
+
+
+def main():
+ logo = open(f"{ASSETS}/logo.js").read()
+ view_box = re.search(r'viewBox = "([^"]*)"', logo).group(1)
+ shapes = [json.loads(m) for m in re.findall(r"^ (\[.*\]),$", logo, re.M)]
+
+ brand = open(f"{ASSETS}/brand.js").read()
+ letters = re.findall(r'\{ x: (\d+), d: "([^"]*)" \}', brand)
+ w = int(re.search(r"^const W = (\d+);", brand, re.M).group(1))
+ if len(shapes) < 5 or len(letters) != 7:
+ sys.exit("banner.py: logo.js or brand.js no longer looks as expected; update the script")
+
+ # The login page's sizes in pixels (style.css): a 104px logo, 10px above
+ # a 290px wordmark whose viewBox is W+8 units wide.
+ mark_w, mark_h = w + 8, 162
+ unit = mark_w / 290
+ logo_size, gap = round(104 * unit, 1), round(10 * unit, 1)
+ height = logo_size + gap + mark_h
+
+ c = w / 2
+ rule = (f''
+ f''
+ f''
+ f''
+ f''
+ f'DHCP')
+
+ def shape(tag, attrs):
+ return f"<{tag} " + " ".join(f'{k}="{v}"' for k, v in attrs.items()) + "/>"
+
+ svg = [f'',
+ f"",
+ f"Steward",
+ f''
+ + "".join(shape(t, a) for t, a in shapes) + "",
+ f''
+ ''
+ + "".join(f'' for x, d in letters) + ""
+ f'{rule}',
+ ""]
+ with open(OUT, "w") as f:
+ f.write("\n".join(svg) + "\n")
+ print(f"{OUT}: {mark_w}x{height:g}")
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/logo.py b/scripts/logo.py
index 9f2736e..0245819 100644
--- a/scripts/logo.py
+++ b/scripts/logo.py
@@ -8,13 +8,15 @@
# logo appears together with the page
#
# The master is hand-written SVG: elements whose attributes apply to
-# their and children. Run from the repository root after
-# changing it:
+# their and children. It then runs scripts/banner.py to
+# rebuild the README's banner. Run from the repository root after changing
+# it:
#
# python3 scripts/logo.py
import json
import os
import shutil
+import subprocess
import sys
import xml.etree.ElementTree as ET
@@ -51,6 +53,7 @@ def main():
shutil.copyfile(MASTER, os.path.join(OUT, "logo.svg"))
for name in ("logo.svg", "logo.js"):
print(f"{OUT}/{name}: {os.path.getsize(os.path.join(OUT, name))} bytes")
+ subprocess.run([sys.executable, "scripts/banner.py"], check=True)
if __name__ == "__main__":