#!/usr/bin/env python3 """Generate conf/custom.css from the Australis source in this directory. Homepage serves exactly two files out of its config dir — custom.css and custom.js — with no static route for anything alongside them. A @font-face pointing at a vendored .woff2 would therefore 404, so the faces have to be inlined as data: URIs. That is the whole reason this build step exists. Usage: python3 stacks/homepage/theme/build.py Then: scripts/deploy-stack.sh esh-docker-vm homepage --conf """ import base64 import pathlib import re import sys HERE = pathlib.Path(__file__).resolve().parent SRC = HERE / "australis.css.in" OUT = HERE.parent / "conf" / "custom.css" # The canonical token file. Present on a box with the australis-design skill # installed; absent elsewhere, in which case the drift check is skipped rather # than failing the build. CANON = pathlib.Path.home() / ".claude/skills/australis-design/colors_and_type.css" # One VARIABLE face per family — the whole 100-900 axis in a single file, so # there is no per-weight fan-out. Latin subset only. FONTS = [ ("Inter", "Inter-Variable.woff2"), ("Space Grotesk", "SpaceGrotesk-Variable.woff2"), ("JetBrains Mono", "JetBrainsMono-Variable.woff2"), ] # Tokens the generated sheet cannot work without. A vendoring mistake that # drops the :root block produces a stylesheet that parses fine and renders # nothing, so assert on the values rather than trusting the copy. REQUIRED_TOKENS = ["--aus-black", "--aus-cyan", "--bg-1", "--font-mono"] def font_faces() -> str: out = [] for family, filename in FONTS: path = HERE / "fonts" / filename if not path.exists(): sys.exit(f"missing font: {path}") b64 = base64.b64encode(path.read_bytes()).decode("ascii") out.append( f"@font-face {{\n" f' font-family: "{family}";\n' f" src: url(data:font/woff2;base64,{b64}) format('woff2');\n" f" font-weight: 100 900;\n" # variable axis f" font-style: normal;\n" f" font-display: swap;\n" f"}}" ) return "\n".join(out) def check_canon_drift(css: str) -> None: """Warn if a token here no longer matches the Australis skill's value. Non-fatal on purpose: the skill is a per-workstation install, so a missing file is normal and must not break a build on a box that lacks it. A *value* mismatch is worth shouting about — that is the palette forking, which is exactly how the theme this one replaced went wrong. """ if not CANON.exists(): print(f"note: {CANON} not present — canonical drift check skipped") return canon = dict(re.findall(r"(--aus-[a-z0-9-]+):\s*(#[0-9a-fA-F]{6});", CANON.read_text())) ours = dict(re.findall(r"(--aus-[a-z0-9-]+):\s*(#[0-9a-fA-F]{6});", css)) if not canon: print(f"note: parsed no --aus-* tokens from {CANON} — drift check skipped") return drift = [(k, v, canon[k]) for k, v in ours.items() if k in canon and v.lower() != canon[k].lower()] missing = sorted(set(ours) - set(canon)) for key, mine, theirs in drift: print(f"DRIFT: {key} is {mine} here, {theirs} in the skill", file=sys.stderr) for key in missing: print(f"note: {key} is not a canonical token", file=sys.stderr) if drift: print( f"WARNING: {len(drift)} token(s) diverge from the canonical palette. " "Reconcile before deploying — a derived palette is how the previous " "theme forked from the design system.", file=sys.stderr, ) else: print(f"canonical palette check: {len(ours)} tokens match {CANON.name}") def main() -> None: if not SRC.exists(): sys.exit(f"missing source: {SRC}") src = SRC.read_text() if "/* @@FONTS@@ */" not in src: sys.exit(f"{SRC.name} lost its @@FONTS@@ placeholder") css = src.replace("/* @@FONTS@@ */", font_faces()) for token in REQUIRED_TOKENS: if token not in css: sys.exit(f"generated css has no {token} — the token block did not survive") check_canon_drift(css) OUT.write_text(css) print(f"wrote {OUT} ({len(css) / 1024:.0f} KB)") if __name__ == "__main__": main()