summaryrefslogtreecommitdiffstats
path: root/_shared
diff options
context:
space:
mode:
authorAhmed <git@gumx.cc>2026-06-14 16:30:47 +0300
committerAhmed <git@gumx.cc>2026-06-14 16:30:47 +0300
commit307ff4912bac1095ebf382d70241f19409b2f8b8 (patch)
treea2c3d36634fa86705e48db4fc797437ba816e5e0 /_shared
parentfa568e13d04c0aacdb29ca252b783f1dcdb6bf23 (diff)
add: templating
Diffstat (limited to '_shared')
-rw-r--r--_shared/build.py94
-rw-r--r--_shared/footer.html12
-rw-r--r--_shared/style.css22
3 files changed, 128 insertions, 0 deletions
diff --git a/_shared/build.py b/_shared/build.py
new file mode 100644
index 0000000..935bf5c
--- /dev/null
+++ b/_shared/build.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+"""Build sites from body.html + meta into index.html using shared templates."""
+import json
+import os
+import sys
+
+FAVICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1IDUiPjxyZWN0IHg9IjEiIHk9IjAiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz48cmVjdCB4PSIyIiB5PSIxIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PHJlY3QgeD0iMCIgeT0iMiIgd2lkdGg9IjEiIGhlaWdodD0iMSIvPjxyZWN0IHg9IjEiIHk9IjIiIHdpZHRoPSIxIiBoZWlnaHQ9IjEiLz48cmVjdCB4PSIyIiB5PSIyIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIi8+PC9zdmc+"
+
+
+def render(title, breadcrumb, style, extra_css, body, footer):
+ css = style + ("\n" + extra_css if extra_css.strip() else "")
+ return f"""<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<link rel="icon" type="image/svg+xml" href="{FAVICON}">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>{title}</title>
+<style>
+{css}
+</style>
+</head>
+<body>
+<header>
+<h1><a href="https://gumx.cc">gumx</a> / {breadcrumb}</h1>
+</header>
+<main>
+{body}
+</main>
+{footer}
+</body>
+</html>
+"""
+
+
+def build_demo_body(demos_file):
+ demos = json.load(open(demos_file))
+ parts = []
+ for d in demos:
+ url = d.get("url", "#")
+ title = d.get("title", d.get("name", ""))
+ desc = d.get("description", "")
+ src = d.get("source", "")
+ src_link = f' / <a href="{src}">source</a>' if src else ""
+ parts.append(f'<p><a href="{url}">{title}</a>{src_link}</p>\n<p>{desc}</p>')
+ return "\n".join(parts)
+
+
+def build(sites_dir):
+ shared = os.path.join(sites_dir, "_shared")
+ style = open(os.path.join(shared, "style.css")).read()
+ footer = open(os.path.join(shared, "footer.html")).read()
+
+ for site in sorted(os.listdir(sites_dir)):
+ if site.startswith("_") or site == "fonts" or site == "hooks":
+ continue
+ site_dir = os.path.join(sites_dir, site)
+ if not os.path.isdir(site_dir):
+ continue
+
+ body_file = os.path.join(site_dir, "body.html")
+ demos_file = os.path.join(site_dir, "demos.json")
+
+ if site == "demo.gumx.cc" and os.path.exists(demos_file):
+ body = build_demo_body(demos_file)
+ elif os.path.exists(body_file):
+ body = open(body_file).read()
+ else:
+ continue
+
+ title = site
+ breadcrumb = site
+ meta_file = os.path.join(site_dir, "meta")
+ if os.path.exists(meta_file):
+ for line in open(meta_file):
+ k, _, v = line.strip().partition("=")
+ if k == "TITLE":
+ title = v.strip('"')
+ elif k == "BREADCRUMB":
+ breadcrumb = v.strip('"')
+
+ extra_css = ""
+ extra_file = os.path.join(site_dir, "extra.css")
+ if os.path.exists(extra_file):
+ extra_css = open(extra_file).read()
+
+ out = render(title, breadcrumb, style, extra_css, body, footer)
+ with open(os.path.join(site_dir, "index.html"), "w") as f:
+ f.write(out)
+ print(f"built: {site}")
+
+
+if __name__ == "__main__":
+ build(sys.argv[1] if len(sys.argv) > 1 else ".")
diff --git a/_shared/footer.html b/_shared/footer.html
new file mode 100644
index 0000000..3aedfc6
--- /dev/null
+++ b/_shared/footer.html
@@ -0,0 +1,12 @@
+<footer>
+<hr>
+<a href="https://twt.gumx.cc">twt</a> /
+<a href="https://git.gumx.cc">git</a> /
+<a href="https://mail.gumx.cc">mail</a> /
+<a href="https://irc.gumx.cc">irc</a> /
+<a href="https://files.gumx.cc">files</a> /
+<a href="https://vpn.gumx.cc">vpn</a> /
+<a href="https://pgp.gumx.cc">pgp</a> /
+<a href="https://demo.gumx.cc">demo</a> /
+<a href="https://wk.fo">wk.fo</a>
+</footer>
diff --git a/_shared/style.css b/_shared/style.css
new file mode 100644
index 0000000..0f7e7cf
--- /dev/null
+++ b/_shared/style.css
@@ -0,0 +1,22 @@
+@font-face { font-family: "Kawkab Mono"; src: url(/fonts/KawkabMono-Regular.woff2); font-weight: normal; }
+@font-face { font-family: "Kawkab Mono"; src: url(/fonts/KawkabMono-Bold.woff2); font-weight: bold; }
+* { unicode-bidi: plaintext; box-sizing: border-box; }
+html { color: black; background-color: white; }
+body { font-family: "Kawkab Mono"; font-size: 16px; line-height: 1.4; margin: 0; padding: 4rem 0; min-height: 100%; overflow-wrap: break-word; }
+main, header, footer { max-width: 800px; margin-inline: auto; padding: 0 2rem; }
+h1, header, footer { text-align: center; }
+main { text-align: justify; }
+p, h2, h3, h4 { margin: 1em 0 0 0; }
+ol { margin: 0.5em 0 0 0; }
+table { margin: auto; border-collapse: collapse; }
+th, td { border: 1px solid; padding: 0.3em 0.8em; }
+pre { margin: 1em 0; }
+pre code { border: thin solid; padding: 1em; display: block; text-align: start; overflow-x: scroll; }
+code { font-size: 85%; }
+hr { border: none; border-top: thin solid; margin: 1.25rem 0; }
+header { margin-bottom: 1em; }
+footer { margin-top: 3em; }
+a { color: inherit; }
+@media (max-width: 600px) { body { font-size: 0.9em; } h1 { font-size: 1.8em; } }
+@media (max-width: 400px) { body { font-size: 0.8em; } h1 { font-size: 1.6em; } }
+@media (prefers-color-scheme: dark) { html { filter: invert(1); } img { filter: invert(1); } }