feat(homepage): Australis Skyfall theme + Arbo-generated aurora background
Replaces the previous theme attempt, which was built on a misread: the ask was to use Arbo as an IMAGE-GEN ENGINE for the background, with the operator's Australis Skyfall design system supplying the palette. theme/ holds the source — colors/layout/typography vendored verbatim from the Skyfall handoff bundle, Supreme 400/500/700 woff2, the Homepage bindings in skyfall.css.in, and build.py which inlines fonts + tokens into conf/custom.css. custom.css is GENERATED; edit the .in file and rebuild. The build exists because Homepage serves only custom.css and custom.js out of its config dir, so a @font-face pointing at a vendored woff2 would 404 — the face has to arrive as a data: URI. The background image takes the other route: /app/public/images is a real static route, so compose.yaml now mounts images/ there read-only and settings.yaml points at /images/. Bindings map Skyfall's semantic layer onto Homepage's DOM: Sea surfaces, the depth recipe (hairline AND two-layer shadow, never one alone), uppercase eyebrow group headers, the sanctioned accent-rail on the active tab rather than a glow, and semantic status colour so a green pill means the service is actually serving. Background generated by Arbo (irv-ml1:8201) workflow t2i-ui-background, job 13f0891f4e42, seed 26, flux2-klein-9b, 2048x1152 — abstract, no subject, cool-temperature aurora. 1.6 MB PNG -> 22 KB WebP. Two deviations are documented rather than hidden: Skyfall forbids imagery behind body text (held at opacity 30 as mitigation), and service icons stay full-colour vendor logos. NOT DEPLOYED — live still runs the old theme. Prototype on :5199.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate conf/custom.css from the Skyfall 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
|
||||
OUT = HERE.parent / "conf" / "custom.css"
|
||||
|
||||
# Only the body/UI face is embedded. See the header comment in skyfall.css.in
|
||||
# for why Bespoke Sans and Victor Mono are deliberately left out.
|
||||
FONTS = [
|
||||
("Supreme", "Supreme-400.woff2", "400"),
|
||||
("Supreme", "Supreme-500.woff2", "500"),
|
||||
("Supreme", "Supreme-700.woff2", "600 700"), # Supreme ships no 600 cut
|
||||
]
|
||||
|
||||
# Vendored verbatim from the handoff bundle; order matters (colors defines the
|
||||
# families that layout and typography reference).
|
||||
TOKEN_FILES = ["colors.css", "layout.css", "typography.css"]
|
||||
|
||||
|
||||
def font_faces() -> str:
|
||||
out = []
|
||||
for family, filename, weight 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: {weight};\n"
|
||||
f" font-style: normal;\n"
|
||||
f" font-display: swap;\n"
|
||||
f"}}"
|
||||
)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def tokens() -> str:
|
||||
out = []
|
||||
for name in TOKEN_FILES:
|
||||
path = HERE / name
|
||||
if not path.exists():
|
||||
sys.exit(f"missing token file: {path}")
|
||||
text = path.read_text()
|
||||
# The bundle's fonts.css is not vendored, so typography.css's family
|
||||
# stacks would reference faces that never load. Nothing to strip here
|
||||
# today, but keep the read verbatim so drift is a plain diff.
|
||||
out.append(f"/* ---- {name} (vendored verbatim) ---- */\n{text.strip()}")
|
||||
return "\n\n".join(out)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
src = (HERE / "skyfall.css.in").read_text()
|
||||
if "@@FONTS@@" not in src or "@@TOKENS@@" not in src:
|
||||
sys.exit("skyfall.css.in lost one of its placeholders")
|
||||
css = src.replace("/* @@FONTS@@ */", font_faces())
|
||||
css = css.replace("/* @@TOKENS@@ */", tokens())
|
||||
# Guard the one rule that silently breaks the theme if it regresses.
|
||||
if not re.search(r"--surface-sunken", css):
|
||||
sys.exit("generated css has no surface tokens — token vendoring failed")
|
||||
OUT.write_text(css)
|
||||
print(f"wrote {OUT} ({len(css) / 1024:.0f} KB)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user