From 75e32a80bf7392b4f5361a4e59cb98102b811b5e Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 2 Oct 2026 15:19:13 +0200 Subject: [PATCH 1/8] readme --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index e69de29..c292ff9 100644 --- a/readme.md +++ b/readme.md @@ -0,0 +1 @@ +sdfkjsdkjhfakjshfd From f2942df1b96c0f8983f900c30304141d93c8475e Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 08:00:50 +0200 Subject: [PATCH 2/8] image folder --- index.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29 From 45470f8cd92ed47e083ee762d803f50f07fdaa0e Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 10:27:28 +0200 Subject: [PATCH 3/8] =?UTF-8?q?readme=20ver=C3=A4ndert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes images/logo.svg | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 2 + 3 files changed, 150 insertions(+) create mode 100644 .DS_Store create mode 100644 images/logo.svg diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..dc8b8565f60c1108e96f7e8e6aa716aba776c692 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8-BN@c6g@6@Em+$k7B3;z7cim+m735}gK4%jsXdfJuKGg0iO=KA z?&c6I9!2a7?0&QJvzz%K`@UY{L+3TLI*9~!Sd~$I;e2J%tdNVw7pj^wI#RA?zDV6l<&y!ds6R;OKMJypPKnxHA z#K0CZU=IV)+``(aWMY6A_?ZFRA0#wH*J5r^ZynI#^%?y&L=@2RErBR4x)yVT-~r(# z70{$|`^4ZT9qiJ^xfXMSCY^D;GR$LFE*>vjuMT#p!x?uCQcnyJ17!xPX6WGge+9qH z#z%fRg+{~xG4RhA;I)B2@SrGrwtg!Q&squX2^tE<<*0ywUb+Op0QZq2?KFOgI>fma WbAvbw+I2c0T?8~C)DZ)}z`z%zmrL9L literal 0 HcmV?d00001 diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 0000000..9a59969 --- /dev/null +++ b/images/logo.svg @@ -0,0 +1,148 @@ + + + + diff --git a/readme.md b/readme.md index c292ff9..1a6195c 100644 --- a/readme.md +++ b/readme.md @@ -1 +1,3 @@ +eine änderung hier mal. + sdfkjsdkjhfakjshfd From fac4f10062f04866fb626b79063b1d459160cc5b Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 10:47:29 +0200 Subject: [PATCH 4/8] cms begonnen zu bauen --- .DS_Store | Bin 6148 -> 12292 bytes ascii/ascii-logo.md | 28 +++ cms.py | 482 +++++++++++++++++++++++++++++++++++++++++ css/style.css | 1 + md/footer.md | 6 + md/startseite.md | 5 + output/css/style.css | 1 + output/index.html | 20 ++ start.command | 4 + start.sh | 4 + templates/default.html | 20 ++ 11 files changed, 571 insertions(+) create mode 100644 ascii/ascii-logo.md create mode 100755 cms.py create mode 100644 css/style.css create mode 100644 md/footer.md create mode 100644 md/startseite.md create mode 100644 output/css/style.css create mode 100644 output/index.html create mode 100755 start.command create mode 100755 start.sh create mode 100644 templates/default.html diff --git a/.DS_Store b/.DS_Store index dc8b8565f60c1108e96f7e8e6aa716aba776c692..f912345d14e1a99293a46850160b35b11351f22d 100644 GIT binary patch literal 12292 zcmeGiTWl0n^qy%cGj@P!El-*3*79hu&?mJ8!R@xZq!naa`k+{McZPQC>`dL6-2zQb zjqw4RXhe<1590H)QKA83Q1pugAJH@^rNv6U9;?(n$y4)6wWA`$d=2;-@HKF!X}}F0d``T-)70S0>ubQ*KpqXy z??VtPg&`MCFz9?bu%TOMI!?FHPQgB&0~}h=g&`MCFzB!%kHdSPQZOKJ-E;U&eIVF5y1i>2l$Pz0bc`oG(cm2*4bCL(yi?5TM)^dea~dh zzQSF_;w-tlr?*e+@6qC>5z+Rm1JWD|55QXx-JVp_c<}9xn|dl?SOZc?JgUc2del~1 zbe&E{+8isMGsb zY1>BVGS*HtX(6l#)i5!I$=UM*ODb2^-Ty#n!Wx}))lf8Jun$k+hOO<<)TB@XRYIo`O{i0)3l=WAXR$2j&d#jTlZt6iq%?byOVaC_9U zw~eSq=9R+oW}fe}hN4Mz+bAX~G92wZ@BFYxrz{&3%{G&cHXbgQ$RlY+TEo-`{uV4l!dGPMt|GL8bt_H`pL2SSjs==P@)jBlWU`Dj;m5y| z>>(z3io8e;laI-9@;Uj2oF-?ZD{d<);f zS@;dk!|!kbF2QB^i!0$4aZ9+R+%m43tKsUndTt}PiQCTgaxrd@8)9N`Zj>Usy<%aw z+=qCsyNxh+<0kXFTVT`XEf0z9x4gUkIGZ}y-DPv<%?~bJQQgqAuH}juGONuPs@DM= zo6qtT$L1Zo$hBAIiDIA&okHC^okL19sq^wvbawf@VnCXKZo|-J%MmR?mtp9ARe^xS zp{FvmrZyl-1?Y!@!@If)QE-vwHKHgLqDQi^^)jNwMZ&GP4D~Pm-?sGc$r*BvoF^B^ zMJR$|kRSk+unHQ`vNytJwC;B3K&_!xwM@zI5z<3Vwp0;g=js?#Q-exR7hf=}6Mh zO=A%0)cQG0rz&}pj}ARywzatlR{qFaZyRQg8KEsr zJiF^2%XrgGOWZCP8>?@?Y;9eA{Xb-G52Bn{rp?^GfP+~w`2pDv$fa^v_5<>b3&@^N z2)dgeko|z1f9rYc2V{>W=LBRY?D&nZfh-Nsv>Ce(c5aLR>kU+6V;ydwnpR)u2FfE* zo-L?%)X#SK*$#hmbpx{<+0N!~t^BRk|8r~Qv14!c;?Sv8TixtM){Lt;jQlh5WHLPb z-mTjWI{!Oy-`NUO(mQ|+)Bpd^ndCRV27C?No*IC{wn$qTWy{=Jw0nzOk+5i9m diff --git a/ascii/ascii-logo.md b/ascii/ascii-logo.md new file mode 100644 index 0000000..7598ce1 --- /dev/null +++ b/ascii/ascii-logo.md @@ -0,0 +1,28 @@ + + + &&&& &&&&& + +xX&&XX+++x + &&$x+xX++x+:; + &;;:$&;:;X&& + ::::::::::; + &&$$$$$$$$$$$;::::x::+:; + &&XXX;::::::::::::::::::xxx + &&$x+x;::::::::::::::::::X& + &&X+++x;::::::::::::::::::X& + &&Xxxxx;::::::::::::::::::X& + &&&&&&+;::++;::;++;::;++:x&& + &$::&&X:;&&&&;:X&&::X& + &&xX&&$:;&&&&Xx&&&:;X& + &&&&&&&&& &&&& &&&& + + +██╗ ██╗███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███╗ ███╗ +██║ ██║██╔════╝██╔════╝ ██╔═══██╗██╔════╝ ██╔══██╗██║ ██║██║██║ ██╔══██╗ ██║ ██║████╗ ████║ +███████║█████╗ ██║ ███╗ ██║ ██║███████╗ ██████╔╝██║ ██║██║██║ ██║ ██║ ██║ ██║██╔████╔██║ +██╔══██║██╔══╝ ██║ ██║ ██║ ██║╚════██║ ██╔══██╗██║ ██║██║██║ ██║ ██║ ╚██╗ ██╔╝██║╚██╔╝██║ +██║ ██║██║ ╚██████╔╝███████╗╚██████╔╝███████║ ██████╔╝╚██████╔╝██║███████╗██████╔╝ ╚████╔╝ ██║ ╚═╝ ██║ +╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚═══╝ ╚═╝ ╚═╝ + +DEBIAN 13 VM auf w4d-gpu unterm Schreibtisch + + diff --git a/cms.py b/cms.py new file mode 100755 index 0000000..61612d3 --- /dev/null +++ b/cms.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +"""Mini-CMS: MD-Dateien in md/ per Browseroberflaeche bearbeiten +und als statische Seite (HTML + CSS) nach output/ exportieren. +Nur Python-Standardbibliothek, kein Internet noetig. + +Aufruf: + python3 cms.py -> Server + UI starten (http://localhost:8500) + python3 cms.py export -> nur exportieren, kein Server + python3 cms.py port 9000 -> Server auf anderem Port +""" + +import json +import os +import re +import shutil +import sys +import threading +import webbrowser +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import unquote, urlparse + +ROOT = os.path.dirname(os.path.abspath(__file__)) +MD_DIR = os.path.join(ROOT, "md") +TPL_DIR = os.path.join(ROOT, "templates") +CSS_DIR = os.path.join(ROOT, "css") +OUT_DIR = os.path.join(ROOT, "output") +PORT = 8500 + +for d in (MD_DIR, TPL_DIR, CSS_DIR, OUT_DIR): + os.makedirs(d, exist_ok=True) + +SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$", re.IGNORECASE) +START_PAGE = "startseite" +FOOTER_PAGE = "footer" # Baustein: wird in jede Seite eingefuegt, nicht eigenstaendig exportiert + + +# ---------------------------------------------------------------- markdown + +def md_to_html(text): + """Minimaler Markdown->HTML-Konverter: + Ueberschriften, **fett**, *kursiv*, `code`, [Links](url), + Listen, Code-Bloecke, > Zitate, --- Trennlinien, Absaetze. + """ + lines = text.replace("\r\n", "\n").split("\n") + out = [] + i = 0 + in_code = False + in_list = False + list_tag = None + + def inline(s): + s = re.sub(r"`([^`]+)`", r"\1", s) + s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s) + s = re.sub(r"\*([^*]+)\*", r"\1", s) + s = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'\1', s) + return s + + def close_list(): + nonlocal in_list, list_tag + if in_list: + out.append("" % list_tag) + in_list = False + list_tag = None + + while i < len(lines): + line = lines[i] + + if line.strip().startswith("```"): + close_list() + if not in_code: + out.append("
")
+                in_code = True
+            else:
+                out.append("
") + in_code = False + i += 1 + continue + + if in_code: + out.append(_escape(line)) + i += 1 + continue + + stripped = line.strip() + + if not stripped: + close_list() + i += 1 + continue + + m = re.match(r"^(#{1,6})\s+(.*)$", stripped) + if m: + close_list() + level = len(m.group(1)) + out.append("%s" % (level, inline(_escape(m.group(2))), level)) + i += 1 + continue + + if re.match(r"^-{3,}$", stripped): + close_list() + out.append("
") + i += 1 + continue + + m = re.match(r"^([-*+])\s+(.*)$", stripped) + if m: + if not in_list or list_tag != "ul": + close_list() + out.append("
    ") + in_list, list_tag = True, "ul" + out.append("
  • %s
  • " % inline(_escape(m.group(2)))) + i += 1 + continue + + m = re.match(r"^\d+\.\s+(.*)$", stripped) + if m: + if not in_list or list_tag != "ol": + close_list() + out.append("
      ") + in_list, list_tag = True, "ol" + out.append("
    1. %s
    2. " % inline(_escape(m.group(1)))) + i += 1 + continue + + if stripped.startswith(">"): + close_list() + quote = [stripped.lstrip("> ")] + while i + 1 < len(lines) and lines[i + 1].strip().startswith(">"): + i += 1 + quote.append(lines[i].strip().lstrip("> ")) + out.append("

      %s

      " % inline(_escape(" ".join(quote)))) + i += 1 + continue + + close_list() + para = [stripped] + while (i + 1 < len(lines) and lines[i + 1].strip() + and not re.match(r"^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|-{3,}$)", + lines[i + 1].strip())): + i += 1 + para.append(lines[i].strip()) + out.append("

      %s

      " % inline(_escape(" ".join(para)))) + i += 1 + + close_list() + return "\n".join(out) + + +def _escape(s): + return (s.replace("&", "&").replace("<", "<").replace(">", ">")) + + +# ------------------------------------------------------------------- seiten + +def parse_md(raw): + """Trennt Front-Matter (titel: ...) vom Inhalt.""" + meta = {} + body = raw + if raw.startswith("---"): + parts = raw.split("---", 2) + if len(parts) >= 3: + for line in parts[1].strip().splitlines(): + if ":" in line: + k, v = line.split(":", 1) + meta[k.strip().lower()] = v.strip() + body = parts[2].lstrip("\n") + return meta, body + + +def md_path(slug): + return os.path.join(MD_DIR, slug + ".md") + + +def list_pages(): + pages = [] + for f in sorted(os.listdir(MD_DIR)): + if f.endswith(".md") and f[:-3] != FOOTER_PAGE: + pages.append(f[:-3]) + return pages + + +def load_page(slug): + with open(md_path(slug), encoding="utf-8") as fh: + return fh.read() + + +def save_page(slug, raw): + with open(md_path(slug), "w", encoding="utf-8") as fh: + fh.write(raw) + + +def delete_page(slug): + p = md_path(slug) + if os.path.exists(p): + os.remove(p) + + +# ------------------------------------------------------------------ export + +def render_page(raw, template, footer_html=""): + meta, body = parse_md(raw) + html = md_to_html(body) + page = (template + .replace("{{titel}}", _escape(meta.get("titel", ""))) + .replace("{{inhalt}}", html)) + if "{{footer}}" in template: + page = page.replace("{{footer}}", footer_html) + elif footer_html and "" in page: + # Template ohne {{footer}}-Platzhalter: Baustein automatisch anhaengen + idx = page.rindex("") + page = page[:idx] + "
      \n" + footer_html + "\n
      \n" + page[idx:] + return page, meta.get("titel", "") + + +def load_template(): + files = [f for f in os.listdir(TPL_DIR) if f.endswith(".html")] + if not files: + raise SystemExit("Kein Template in templates/ gefunden (z.B. default.html).") + with open(os.path.join(TPL_DIR, files[0]), encoding="utf-8") as fh: + return fh.read() + + +def export_site(): + """Baut output/: pro MD-Datei eine HTML-Seite, CSS wird kopiert.""" + template = load_template() + # Output leeren und neu aufbauen + if os.path.isdir(OUT_DIR): + shutil.rmtree(OUT_DIR) + os.makedirs(os.path.join(OUT_DIR, "css"), exist_ok=True) + + for f in os.listdir(CSS_DIR): + if f.endswith(".css"): + shutil.copy2(os.path.join(CSS_DIR, f), os.path.join(OUT_DIR, "css", f)) + + footer_html = "" + if os.path.exists(md_path(FOOTER_PAGE)): + _, fbody = parse_md(load_page(FOOTER_PAGE)) + footer_html = md_to_html(fbody) + + exported = [] + for slug in list_pages(): + page, _ = render_page(load_page(slug), template, footer_html) + # startseite.md wird zur index.html + out_name = "index.html" if slug == START_PAGE else slug + ".html" + with open(os.path.join(OUT_DIR, out_name), "w", encoding="utf-8") as fh: + fh.write(page) + exported.append(out_name) + return exported + + +# ------------------------------------------------------------------- server + +class Handler(BaseHTTPRequestHandler): + def log_message(self, *args): + pass + + def _send(self, code, body, ctype="application/json; charset=utf-8"): + data = body.encode("utf-8") if isinstance(body, str) else body + self.send_response(code) + self.send_header("Content-Type", ctype) + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def _json(self, obj, code=200): + self._send(code, json.dumps(obj)) + + def _body(self): + n = int(self.headers.get("Content-Length", 0)) + return json.loads(self.rfile.read(n).decode("utf-8")) if n else {} + + def do_GET(self): + path = urlparse(self.path).path + + if path in ("/", "/admin"): + self._send(200, ADMIN_HTML, "text/html; charset=utf-8") + elif path == "/api/pages": + self._json({"pages": list_pages(), "start": START_PAGE, + "footer": os.path.exists(md_path(FOOTER_PAGE))}) + elif path.startswith("/api/page/"): + slug = unquote(path[len("/api/page/"):]) + if slug == FOOTER_PAGE or slug in list_pages(): + self._json({"slug": slug, "raw": load_page(slug)}) + else: + self._json({"error": "Seite nicht gefunden"}, 404) + elif path == "/api/export": + self._json({"exported": export_site()}) + else: + self._json({"error": "unbekannter Pfad"}, 404) + + def do_POST(self): + path = urlparse(self.path).path + try: + data = self._body() + except (ValueError, json.JSONDecodeError): + return self._json({"error": "ungueltige Anfrage"}, 400) + + if path == "/api/page": + slug = (data.get("slug") or "").strip() + raw = data.get("raw") or "" + if not SLUG_RE.match(slug): + return self._json({"error": "Ungueltiger Name (a-z, 0-9, -)."}, 400) + save_page(slug, raw) + self._json({"ok": True, "slug": slug}) + elif path == "/api/delete": + slug = data.get("slug", "") + if slug != FOOTER_PAGE and slug not in list_pages(): + return self._json({"error": "Seite nicht gefunden"}, 404) + if slug in (START_PAGE, FOOTER_PAGE): + return self._json({"error": "Startseite und Footer-Baustein koennen nicht geloescht werden."}, 400) + delete_page(slug) + self._json({"ok": True}) + elif path == "/api/preview": + meta, body = parse_md(data.get("raw") or "") + self._json({"html": md_to_html(body)}) + else: + self._json({"error": "unbekannter Pfad"}, 404) + + +ADMIN_HTML = """ + + + +Mini-CMS + + + +
      +

      Seiten

      +
      +
      +

      Neue Seite:

      + + +
      +
      +
      + - + + + + +
      +
      + +
      +
      + + +""" + + +def main(): + global PORT + if len(sys.argv) > 1 and sys.argv[1] == "export": + files = export_site() + print("Exportiert nach output/:") + for f in files: + print(" -", f) + return + + if len(sys.argv) > 2 and sys.argv[1] == "port": + PORT = int(sys.argv[2]) + + server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler) + url = "http://localhost:%d" % PORT + print("Mini-CMS laeuft unter", url) + print("Beenden mit Strg+C.") + threading.Timer(1.0, lambda: webbrowser.open(url)).start() + try: + server.serve_forever() + except KeyboardInterrupt: + print("\nServer gestoppt.") + + +if __name__ == "__main__": + main() diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..e3ec04d --- /dev/null +++ b/css/style.css @@ -0,0 +1 @@ +/* Stylesheet der Website - hier das Layout gestalten. */ diff --git a/md/footer.md b/md/footer.md new file mode 100644 index 0000000..cd5fb52 --- /dev/null +++ b/md/footer.md @@ -0,0 +1,6 @@ +--- +titel: Footer +--- + +Dieser Footer-Baustein wird automatisch an das Ende **aller** Seiten gesetzt. +Zum Beispiel mit Links: [Startseite](index.html) diff --git a/md/startseite.md b/md/startseite.md new file mode 100644 index 0000000..578ade4 --- /dev/null +++ b/md/startseite.md @@ -0,0 +1,5 @@ +--- +titel: startseite +--- + +Neue Seite. hier kommen alle inhalte hin. diff --git a/output/css/style.css b/output/css/style.css new file mode 100644 index 0000000..e3ec04d --- /dev/null +++ b/output/css/style.css @@ -0,0 +1 @@ +/* Stylesheet der Website - hier das Layout gestalten. */ diff --git a/output/index.html b/output/index.html new file mode 100644 index 0000000..8c182bd --- /dev/null +++ b/output/index.html @@ -0,0 +1,20 @@ + + + + + + startseite + + + +
      +

      startseite

      +
      +
      +

      Neue Seite. hier kommen alle inhalte hin.

      +
      +
      +

      Dieser Footer-Baustein wird automatisch an das Ende aller Seiten gesetzt. Zum Beispiel mit Links: Startseite

      +
      + + diff --git a/start.command b/start.command new file mode 100755 index 0000000..fa11e95 --- /dev/null +++ b/start.command @@ -0,0 +1,4 @@ +#!/bin/sh +# Mini-CMS per Doppelklick starten (macOS) +cd "$(dirname "$0")" || exit 1 +python3 cms.py "$@" diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..28a04a6 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Mini-CMS starten (macOS und Linux) +cd "$(dirname "$0")" || exit 1 +exec python3 cms.py "$@" diff --git a/templates/default.html b/templates/default.html new file mode 100644 index 0000000..f916d9b --- /dev/null +++ b/templates/default.html @@ -0,0 +1,20 @@ + + + + + + {{titel}} + + + +
      +

      {{titel}}

      +
      +
      + {{inhalt}} +
      +
      + {{footer}} +
      + + From 92f8e00fb5c3d58764e8aded25791a4650fa7c58 Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 10:54:47 +0200 Subject: [PATCH 5/8] =?UTF-8?q?header=20template=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 12292 -> 12292 bytes cms.py | 52 +++++++++++++++++++++++++++-------------- images/hfglogo.png | Bin 0 -> 7009 bytes md/header.md | 9 +++++++ output/index.html | 7 +++++- templates/default.html | 3 ++- 6 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 images/hfglogo.png create mode 100644 md/header.md diff --git a/.DS_Store b/.DS_Store index f912345d14e1a99293a46850160b35b11351f22d..336b29d7dea97cdfd6f70b45d2ea4df56bec281e 100644 GIT binary patch delta 20 bcmZokXi3%Sjxa;PpjPiN_PnZWF delta 42 ycmZokXi3H$P!i&;tM)a19Os diff --git a/cms.py b/cms.py index 61612d3..3edc84d 100755 --- a/cms.py +++ b/cms.py @@ -31,7 +31,8 @@ for d in (MD_DIR, TPL_DIR, CSS_DIR, OUT_DIR): SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$", re.IGNORECASE) START_PAGE = "startseite" -FOOTER_PAGE = "footer" # Baustein: wird in jede Seite eingefuegt, nicht eigenstaendig exportiert +HEADER_PAGE = "header" # Baustein: wird ueber den Inhalt jeder Seite gesetzt +FOOTER_PAGE = "footer" # Baustein: wird unter den Inhalt jeder Seite gesetzt # ---------------------------------------------------------------- markdown @@ -174,7 +175,7 @@ def md_path(slug): def list_pages(): pages = [] for f in sorted(os.listdir(MD_DIR)): - if f.endswith(".md") and f[:-3] != FOOTER_PAGE: + if f.endswith(".md") and f[:-3] not in (HEADER_PAGE, FOOTER_PAGE): pages.append(f[:-3]) return pages @@ -197,12 +198,26 @@ def delete_page(slug): # ------------------------------------------------------------------ export -def render_page(raw, template, footer_html=""): +def block_html(slug): + """Rendert einen Baustein (header/footer) als HTML, leer wenn nicht vorhanden.""" + if os.path.exists(md_path(slug)): + _, body = parse_md(load_page(slug)) + return md_to_html(body) + return "" + + +def render_page(raw, template, header_html="", footer_html=""): meta, body = parse_md(raw) html = md_to_html(body) page = (template .replace("{{titel}}", _escape(meta.get("titel", ""))) .replace("{{inhalt}}", html)) + if "{{header}}" in template: + page = page.replace("{{header}}", header_html) + elif header_html and "" in page: + # Template ohne {{header}}-Platzhalter: Baustein automatisch setzen + idx = page.index("") + len("") + page = page[:idx] + "\n
      \n" + header_html + "\n
      \n" + page[idx:] if "{{footer}}" in template: page = page.replace("{{footer}}", footer_html) elif footer_html and "" in page: @@ -232,14 +247,12 @@ def export_site(): if f.endswith(".css"): shutil.copy2(os.path.join(CSS_DIR, f), os.path.join(OUT_DIR, "css", f)) - footer_html = "" - if os.path.exists(md_path(FOOTER_PAGE)): - _, fbody = parse_md(load_page(FOOTER_PAGE)) - footer_html = md_to_html(fbody) + footer_html = block_html(FOOTER_PAGE) + header_html = block_html(HEADER_PAGE) exported = [] for slug in list_pages(): - page, _ = render_page(load_page(slug), template, footer_html) + page, _ = render_page(load_page(slug), template, header_html, footer_html) # startseite.md wird zur index.html out_name = "index.html" if slug == START_PAGE else slug + ".html" with open(os.path.join(OUT_DIR, out_name), "w", encoding="utf-8") as fh: @@ -276,10 +289,11 @@ class Handler(BaseHTTPRequestHandler): self._send(200, ADMIN_HTML, "text/html; charset=utf-8") elif path == "/api/pages": self._json({"pages": list_pages(), "start": START_PAGE, + "header": os.path.exists(md_path(HEADER_PAGE)), "footer": os.path.exists(md_path(FOOTER_PAGE))}) elif path.startswith("/api/page/"): slug = unquote(path[len("/api/page/"):]) - if slug == FOOTER_PAGE or slug in list_pages(): + if slug in (HEADER_PAGE, FOOTER_PAGE) or slug in list_pages(): self._json({"slug": slug, "raw": load_page(slug)}) else: self._json({"error": "Seite nicht gefunden"}, 404) @@ -304,10 +318,10 @@ class Handler(BaseHTTPRequestHandler): self._json({"ok": True, "slug": slug}) elif path == "/api/delete": slug = data.get("slug", "") - if slug != FOOTER_PAGE and slug not in list_pages(): + if slug not in (HEADER_PAGE, FOOTER_PAGE) and slug not in list_pages(): return self._json({"error": "Seite nicht gefunden"}, 404) - if slug in (START_PAGE, FOOTER_PAGE): - return self._json({"error": "Startseite und Footer-Baustein koennen nicht geloescht werden."}, 400) + if slug in (START_PAGE, HEADER_PAGE, FOOTER_PAGE): + return self._json({"error": "Startseite und Bausteine koennen nicht geloescht werden."}, 400) delete_page(slug) self._json({"ok": True}) elif path == "/api/preview": @@ -384,12 +398,14 @@ async function loadPages() { if (p === current) a.className = "active"; $("pages").appendChild(a); } - if (d.footer) { - const a = document.createElement("a"); - a.textContent = "footer (Baustein)"; - a.onclick = () => openPage("footer"); - if (current === "footer") a.className = "active"; - $("pages").appendChild(a); + for (const b of [["header", d.header], ["footer", d.footer]]) { + if (b[1]) { + const a = document.createElement("a"); + a.textContent = b[0] + " (Baustein)"; + a.onclick = () => openPage(b[0]); + if (current === b[0]) a.className = "active"; + $("pages").appendChild(a); + } } } diff --git a/images/hfglogo.png b/images/hfglogo.png new file mode 100644 index 0000000000000000000000000000000000000000..84161e76ef31c6fd95e95f7ab4d387b14996ba0b GIT binary patch literal 7009 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 literal 0 HcmV?d00001 diff --git a/md/header.md b/md/header.md new file mode 100644 index 0000000..71ee792 --- /dev/null +++ b/md/header.md @@ -0,0 +1,9 @@ +--- +titel: Header +--- + +# Meine Website + +Dieser Header-Baustein wird automatisch ueber den Inhalt **aller** Seiten gesetzt. + +- [Startseite](index.html) diff --git a/output/index.html b/output/index.html index 8c182bd..c7efd00 100644 --- a/output/index.html +++ b/output/index.html @@ -8,9 +8,14 @@
      -

      startseite

      +

      Meine Website

      +

      Dieser Header-Baustein wird automatisch ueber den Inhalt aller Seiten gesetzt.

      +
      +

      startseite

      Neue Seite. hier kommen alle inhalte hin.

      diff --git a/templates/default.html b/templates/default.html index f916d9b..2b85aa2 100644 --- a/templates/default.html +++ b/templates/default.html @@ -8,9 +8,10 @@
      -

      {{titel}}

      + {{header}}
      +

      {{titel}}

      {{inhalt}}
      From dc1b35f344292884932a688ad9c9ace70d0295d2 Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 11:51:03 +0200 Subject: [PATCH 6/8] css start --- .DS_Store | Bin 12292 -> 14340 bytes cms.py | 53 +++++++++- css/style.css | 220 ++++++++++++++++++++++++++++++++++++++++- images/pferd.png | Bin 0 -> 5090 bytes md/header.md | 10 +- md/startseite.md | 28 +++++- output/css/style.css | 220 ++++++++++++++++++++++++++++++++++++++++- output/index.html | 24 ++++- templates/default.html | 1 + 9 files changed, 546 insertions(+), 10 deletions(-) create mode 100644 images/pferd.png diff --git a/.DS_Store b/.DS_Store index 336b29d7dea97cdfd6f70b45d2ea4df56bec281e..786dd3c12f47031303a9409fda9bd0cda8e90923 100644 GIT binary patch delta 297 zcmZokXem%&U|?W$DortDU@!nOIe-{M3-ADmHU?IX|~x zVxjtE105DtMxX@CdGK?oh85qz+fhvkOuN6;X7RDwaBfXai!<;}d;mu!VCowYeZ{|^0$Ua$s z@#bl?Gmn4{`{qiy zNsJR8v@mjP=22M4K6#=>xC~GqD-Z*L1~-s!MYeV0#rMpU`Au|nK++%^8bAyn0J52& dGz*jlxfvvGFgeF$;^v1U)=ZO4)GI+&005PbDHQ+! diff --git a/cms.py b/cms.py index 3edc84d..ce2f038 100755 --- a/cms.py +++ b/cms.py @@ -37,10 +37,12 @@ FOOTER_PAGE = "footer" # Baustein: wird unter den Inhalt jeder Seite gesetzt # ---------------------------------------------------------------- markdown -def md_to_html(text): +def md_to_html(text, headings=None): """Minimaler Markdown->HTML-Konverter: Ueberschriften, **fett**, *kursiv*, `code`, [Links](url), Listen, Code-Bloecke, > Zitate, --- Trennlinien, Absaetze. + Ueberschriften bekommen id-Anker; in headings (Liste) werden + optional (anker, text) aller Ueberschriften gesammelt. """ lines = text.replace("\r\n", "\n").split("\n") out = [] @@ -48,6 +50,7 @@ def md_to_html(text): in_code = False in_list = False list_tag = None + used_ids = {} def inline(s): s = re.sub(r"`([^`]+)`", r"\1", s) @@ -89,11 +92,28 @@ def md_to_html(text): i += 1 continue + # Rohe HTML-Zeilen (z.B.
      , ) unveraendert durchreichen + if stripped.startswith("<"): + close_list() + out.append(stripped) + i += 1 + continue + m = re.match(r"^(#{1,6})\s+(.*)$", stripped) if m: close_list() level = len(m.group(1)) - out.append("%s" % (level, inline(_escape(m.group(2))), level)) + text_h = m.group(2) + hid = _slugify(text_h) + if hid in used_ids: + used_ids[hid] += 1 + hid = "%s-%d" % (hid, used_ids[hid]) + else: + used_ids[hid] = 0 + if headings is not None: + headings.append((hid, text_h)) + out.append('%s' + % (level, hid, inline(_escape(text_h)), level)) i += 1 continue @@ -136,7 +156,7 @@ def md_to_html(text): close_list() para = [stripped] while (i + 1 < len(lines) and lines[i + 1].strip() - and not re.match(r"^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|-{3,}$)", + and not re.match(r"^(#{1,6}\s|[-*+]\s|\d+\.\s|>|```|-{3,}$|<)", lines[i + 1].strip())): i += 1 para.append(lines[i].strip()) @@ -151,6 +171,23 @@ def _escape(s): return (s.replace("&", "&").replace("<", "<").replace(">", ">")) +def _slugify(s): + """Ueberschriftentext -> HTML-id (ankertauglich, klein, ohne Sonderzeichen).""" + s = s.lower() + for k, v in (("ä", "ae"), ("ö", "oe"), ("ü", "ue"), ("ß", "ss")): + s = s.replace(k, v) + s = re.sub(r"[^a-z0-9]+", "-", s).strip("-") + return s or "abschnitt" + + +def heading_nav(headings): + """Baut die Anker-Linkliste (Text-Links auf die Ueberschriften der Seite).""" + if not headings: + return "" + return "".join('%s' % (hid, _escape(text)) + for hid, text in headings) + + # ------------------------------------------------------------------- seiten def parse_md(raw): @@ -208,10 +245,18 @@ def block_html(slug): def render_page(raw, template, header_html="", footer_html=""): meta, body = parse_md(raw) - html = md_to_html(body) + headings = [] + html = md_to_html(body, headings) + anker_html = heading_nav(headings) page = (template .replace("{{titel}}", _escape(meta.get("titel", ""))) .replace("{{inhalt}}", html)) + if "{{anker}}" in template: + page = page.replace("{{anker}}", anker_html) + elif anker_html and "{{header}}" in template: + # Template ohne {{anker}}-Platzhalter: Anker ans Ende des Headers + page = page.replace("{{header}}", + header_html + '\n") if "{{header}}" in template: page = page.replace("{{header}}", header_html) elif header_html and "" in page: diff --git a/css/style.css b/css/style.css index e3ec04d..27bd819 100644 --- a/css/style.css +++ b/css/style.css @@ -1 +1,219 @@ -/* Stylesheet der Website - hier das Layout gestalten. */ +/* ---- Pixel-Pferd im Header ---- */ + +header { + text-align: left; +} + +.pixel-horse-container { + width: 100px; + height: 100px; + position: relative; + margin: 0 auto; /* zentriert im Header */ + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; +} + +.pixel-horse { + position: absolute; + top: 0; + left: 0; + width: 100px; + height: 100px; + opacity: 0; + animation: run 0.5s steps(1) infinite; +} + +.pixel-horse:nth-child(1) { animation-delay: 0s; opacity: 1; } +.pixel-horse:nth-child(2) { animation-delay: 0.125s; } +.pixel-horse:nth-child(3) { animation-delay: 0.25s; } +.pixel-horse:nth-child(4) { animation-delay: 0.375s; } + +@keyframes run { + 0%, 24.9% { opacity: 1; } + 25%, 49.9% { opacity: 0; } + 50%, 74.9% { opacity: 1; } + 75%, 99.9% { opacity: 0; } + 100% { opacity: 1; } +} + +/* ================================================================ + Linux-Terminal Dark Theme + Nur System-Fonts (lokal verfuegbar, keine Web-Fonts, DSGVO-konform) + ================================================================ */ + +:root { + --bg: #1a1a1a; /* sehr dunkles Grau */ + --text: #ffffff; /* normaler Text weiss */ + --dim: #909090; /* gedimmter Text (Footer, Zitate) */ + --heading: #ff9500; /* Ueberschriften orange */ + --link: #66aaff; /* Links, gut lesbar auf schwarz */ + --green: #00ff00; /* Prompt, Code */ + --border: #333333; +} + +html { + background: var(--bg); +} + +body { + /* Terminal-/Monospace-Fonts des Systems, kein Web-Font, DSGVO-konform */ + font-family: "Ubuntu Mono", "DejaVu Sans Mono", Menlo, "Liberation Mono", + "Noto Sans Mono", Inconsolata, Monaco, "Courier New", monospace; + background: var(--bg); + color: var(--text); + line-height: 1.6; + font-size: 14px; + margin: 0; + padding: 2rem; + box-sizing: border-box; +} + +/* ---- Header = Terminal-Titelleiste ---- */ + +header { + max-width: 1200px; /* gleiche Spalte wie main/footer */ + margin: 0 auto 1.5rem; /* in der Seitenmitte */ + text-align: left; +} + +/* ---- Hauptbereich als Terminal-Fenster ---- */ + +main { + max-width: 1200px; + margin: 0 auto; /* in der Seitenmitte */ + text-align: left; + padding: 0 1.5rem 1.5rem 0; +} + +/* Shell-Prompt ganz oben auf der Seite */ +body::before { + content: "user@hfg_linux_group:~$ "; + color: var(--green); + display: block; + max-width: 1200px; + margin: 0 auto 1.5rem; +} + +/* ---- Typografie ---- */ + +h1, h2, h3, h4, h5, h6 { + color: var(--heading); + text-shadow: 0 0 8px rgba(255, 149, 0, 0.35); + letter-spacing: 1px; + font-size: 1rem; /* ueberall gleiche Schriftgroesse */ + font-weight: normal; + margin: 1.5rem 0 .8rem; +} + +main > h1:first-of-type { + margin-top: 0; +} + +p { + margin: .8rem 0; +} + +/* ---- Anker-Navigation im Header (Links auf die Ueberschriften) ---- */ + +.anker-nav { + margin-top: 1rem; + display: flex; + flex-wrap: wrap; + gap: .25rem 1.5rem; +} + +.anker-nav a { + color: var(--link); +} + +.anker-nav a:hover { + color: #ffffff; +} + +/* ---- Links ---- */ + +a { + color: var(--link); + text-decoration: none; +} + +a:hover { + color: #ffffff; + text-decoration: underline; + text-shadow: 0 0 8px rgba(102, 170, 255, 0.6); +} + +/* ---- Kein Bold: alle Schrift normal ---- */ + +strong, b, th { + font-weight: normal; +} + +/* ---- Listen, Zitate, Trennlinien ---- */ + +ul, ol { + margin: .8rem 0; + padding-left: 1.5rem; +} + +li { + margin: .25rem 0; +} + +li::marker { + color: var(--green); +} + +blockquote { + border-left: 3px solid var(--green); + margin: 1rem 0; + padding: .5rem 1rem; + color: var(--dim); + background: rgba(0, 255, 0, 0.05); +} + +hr { + border: none; + border-top: 1px dashed var(--border); + margin: 1.5rem 0; +} + +/* ---- Code ---- */ + +code { + color: var(--green); + background: #242424; + padding: .1rem .3rem; + border-radius: 3px; +} + +pre { + background: #141414; + border-radius: 4px; + padding: 1rem; + overflow-x: auto; +} + +pre code { + background: none; + padding: 0; +} + +/* ---- Footer ---- */ + +footer { + max-width: 1600px; + margin: 1.5rem auto 0; /* in der Seitenmitte, bündig zu main */ + text-align: left; + border-top: 1px solid var(--border); + padding-top: 1rem; + color: var(--dim); +} + +/* ---- Textauswahl invertiert, wie im Terminal ---- */ + +::selection { + background: var(--link); + color: #000000; +} diff --git a/images/pferd.png b/images/pferd.png new file mode 100644 index 0000000000000000000000000000000000000000..29a50857d0f9733ad9473b58d181d355ba52eeca GIT binary patch literal 5090 zcmeHLZBSF`6@FQ|4GRO*(k-lz#A=nXB1oA90)$kGi>|Jr?pjDe0z{HkkqDTNZ@ z23=yXYlnb=q&w}3bS)907)SytcvH+KcU?6hQ37nhxDp6KO@NTlOP$W_Z1>0hs9l)$ z$DMcP+_~>P?>W!&oO9mO>3h@seb@Q|0Q`6DOx*{7_s;-$Meg%M)G~3fIv-tLJGPTj z3P3>r^5dmqUOb2z-#ES_^Z2_=`f*NPNde$+IFW~nj+D~!juk{QOBllGq_qG9F?XeI ze~;TdHN5*`t7W^&ZgC?`S&eU<3rXC1NI8&x+ShRE=;n)*D{tJoaOI5f!_k|50~cy) z{Nif11n&!#|7Oik6xGpdQul0b^nE+(&fyL3q<;EaXt(h2UHbdlgPRDFy0fslQN2g& zBnS`&E7p?KcT3LgYqlU&6g>;|@x53n23>0Fe7peIxBdqJ>^%J=0M>0x0igPiE7h2_ z>@*p&3|Be2SUg*k(!B10(XgnKnAmEP)@09~hV+=KyaNpqq0;=xOxqEJ>LiaSDm>QH zEopQn;Zj43-9VL3VN5ZQYH3pn$VDabfq>2(vMCX=+@Ln2Oty=WmC@j*b>XOu*0AyaJ$s zNt_~|A}H>+VlfZ>X-&kj?uxlYJl-Ozuq(?RHKY@hY0oGI`uF1jaHW}s_Str%CY?A+`LyrjEnJ;}{rjk5khL}jC zCz$}<{O6~#Q&WThc6>$UwfDwo?_G5!>d{=G+~t*Sv<8XQ8qlp$Vg;Y-d1ALvn$FPZ%^Y=cqO%I#Vv zn@*0ujJj=I6IiM4#;8IhVx@W8+z_dcnKa{((-E1>NphL%7hzld2J>UZR>4>;mW%E!?t*(>J zKHCBdD?PysM3yaXA&%x}t5D3o5a8uwhb6=*Cu&viTrZFt+NrEmW`Js;pQLq%3lP>KdOsWXQk^6|fc`qeftj)OUPK zJ)Su66#%a5)TJ9Xp2TB8!8CW?rgSq@oe}TGEwk0k^vrNL&w0Wk<xaM*Ck_}}mOlgpo5=kBGEIzMG79=S&1+l}{w0>Z!b*WXD+ z{u7gnferss)J!yQlwPth$g;*?!(;Iv zldCJ{n#NT8`TU+$QQ5v1@~i?iG5#V@ZaaW4b0_Frw`0TzJwnKGpc#!%{ldpUVaKX_ z!lID!{@RNGm6PMCO^9tSbdzlT)!oUM;I7D2C zJamZ?I0sXN4nfV;`3GFjF+>v8$X9T`}qe(~Gz2l{$yK&~EGNYI=5s zis{$BJ!F{adG=ESSya~H=Iq9MfzEB^X{4BrfXlaS%Q8bKU=iT}J_%}6M{8}E8 zsbgVj6oyTin8hPCDQSJb z(@nHam%Fz1B}1wrnY-Po%#%`aa}yz-~R|8|bh z<^=?Nr0IMbG}%LV6H2t)?VMRavZYJEFPn5V@r zjo2@EUz$9B-Rdu&n9IcVT;^plY>iWG69XfN>9ROtvPWw|JGQX(?KJdCX=RP#Px^qD XB4S<0_U4aK7s0L_dsEv}a%=tpHC8;x literal 0 HcmV?d00001 diff --git a/md/header.md b/md/header.md index 71ee792..856ab9f 100644 --- a/md/header.md +++ b/md/header.md @@ -2,7 +2,15 @@ titel: Header --- -# Meine Website +
      + + + + +
      + +# hfg_linux_usergroup +# free-software-for-art-and-design Dieser Header-Baustein wird automatisch ueber den Inhalt **aller** Seiten gesetzt. diff --git a/md/startseite.md b/md/startseite.md index 578ade4..aece968 100644 --- a/md/startseite.md +++ b/md/startseite.md @@ -2,4 +2,30 @@ titel: startseite --- -Neue Seite. hier kommen alle inhalte hin. +## Was ist die hfg_linux_usergroup + +Die hfg_linux_usergroup ist eine diverse Gemeinschaft der HFG Offenbach, die sich für freie Software, Open Source und digitale Souveränität einsetzt. + +Hintergrund ist die zentrale Bedeutung von Open Source für Kunst und Gestaltung: Es ermöglicht Demokratisierung, Transparenz, Chancengleichheit und Weiterentwicklung. Durch offene Lizenzen und frei zugängliche Software werden Wissen und kreative Prozesse entmonopolisiert. Künstler:innen und Designer:innen sind so nicht länger abhängig von proprietärer Software – die oft mit hohen Kosten, geschlossenen Systemen, Abhängigkeiten oder Abos einhergeht. Hinzu kommen ethische Bedenken wie Datenschutz oder Überwachungskapitalismus. +Open Source fördert nicht nur gestalterische Freiheit, sondern auch eine kritische Reflexion über Technologie selbst. + +## Nächste Treffen +Die nächsten Termine für den Herbst/Winter 2026 werden aktuell geplant: + +hfg_linux_usergroup_date_1_music +hfg_linux_usergroup_date_2_image-editing +hfg_linux_usergroup_date_3_coding-development +hfg_linux_usergroup_date_4_3d-film-fotogrammetrie + +## Wer ist die hfg_linux_usergroup + +Alle, die sich für Linux, Open Source und die Förderung von Wissen in Kunst und Design interessieren, können Teil der Gemeinschaft werden oder an den Treffen teilnehmen. Die hfg_linux_usergroup wurde von den beiden HFG-Lehrenden Sebastian Kujas und Florian Albrecht-Schoeck gegründet und wird aktuell von ihnen moderiert. + +## Was sind die Ziele der hfg_linux_usergroup +Ziel ist es, den Einsatz freier Software in Kunst und Design zu fördern. Durch Workshops, den Aufbau von Wissensdatenbanken, Austausch und Debatten sollen Eigenermächtigung und Verantwortung für Technologie wiederhergestellt werden. Dazu entwickeln wir gemeinsam ein eigenes Linux-basiertes Betriebssystem und unterstützen bei allen Schritten des Wechsels zu freier Software. + +## Das hfg_OS_ +Das hfg_OS ist ein auf Debian 13 (Trixie) basierendes, Linux-basiertes Betriebssystem. Es wurde speziell für die Bedürfnisse von Künstler:innen und Designer:innen angepasst und wird kontinuierlich weiterentwickelt. + +Bald verfügbar: hfg_OS_Falbella-2026-10 + diff --git a/output/css/style.css b/output/css/style.css index e3ec04d..27bd819 100644 --- a/output/css/style.css +++ b/output/css/style.css @@ -1 +1,219 @@ -/* Stylesheet der Website - hier das Layout gestalten. */ +/* ---- Pixel-Pferd im Header ---- */ + +header { + text-align: left; +} + +.pixel-horse-container { + width: 100px; + height: 100px; + position: relative; + margin: 0 auto; /* zentriert im Header */ + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; +} + +.pixel-horse { + position: absolute; + top: 0; + left: 0; + width: 100px; + height: 100px; + opacity: 0; + animation: run 0.5s steps(1) infinite; +} + +.pixel-horse:nth-child(1) { animation-delay: 0s; opacity: 1; } +.pixel-horse:nth-child(2) { animation-delay: 0.125s; } +.pixel-horse:nth-child(3) { animation-delay: 0.25s; } +.pixel-horse:nth-child(4) { animation-delay: 0.375s; } + +@keyframes run { + 0%, 24.9% { opacity: 1; } + 25%, 49.9% { opacity: 0; } + 50%, 74.9% { opacity: 1; } + 75%, 99.9% { opacity: 0; } + 100% { opacity: 1; } +} + +/* ================================================================ + Linux-Terminal Dark Theme + Nur System-Fonts (lokal verfuegbar, keine Web-Fonts, DSGVO-konform) + ================================================================ */ + +:root { + --bg: #1a1a1a; /* sehr dunkles Grau */ + --text: #ffffff; /* normaler Text weiss */ + --dim: #909090; /* gedimmter Text (Footer, Zitate) */ + --heading: #ff9500; /* Ueberschriften orange */ + --link: #66aaff; /* Links, gut lesbar auf schwarz */ + --green: #00ff00; /* Prompt, Code */ + --border: #333333; +} + +html { + background: var(--bg); +} + +body { + /* Terminal-/Monospace-Fonts des Systems, kein Web-Font, DSGVO-konform */ + font-family: "Ubuntu Mono", "DejaVu Sans Mono", Menlo, "Liberation Mono", + "Noto Sans Mono", Inconsolata, Monaco, "Courier New", monospace; + background: var(--bg); + color: var(--text); + line-height: 1.6; + font-size: 14px; + margin: 0; + padding: 2rem; + box-sizing: border-box; +} + +/* ---- Header = Terminal-Titelleiste ---- */ + +header { + max-width: 1200px; /* gleiche Spalte wie main/footer */ + margin: 0 auto 1.5rem; /* in der Seitenmitte */ + text-align: left; +} + +/* ---- Hauptbereich als Terminal-Fenster ---- */ + +main { + max-width: 1200px; + margin: 0 auto; /* in der Seitenmitte */ + text-align: left; + padding: 0 1.5rem 1.5rem 0; +} + +/* Shell-Prompt ganz oben auf der Seite */ +body::before { + content: "user@hfg_linux_group:~$ "; + color: var(--green); + display: block; + max-width: 1200px; + margin: 0 auto 1.5rem; +} + +/* ---- Typografie ---- */ + +h1, h2, h3, h4, h5, h6 { + color: var(--heading); + text-shadow: 0 0 8px rgba(255, 149, 0, 0.35); + letter-spacing: 1px; + font-size: 1rem; /* ueberall gleiche Schriftgroesse */ + font-weight: normal; + margin: 1.5rem 0 .8rem; +} + +main > h1:first-of-type { + margin-top: 0; +} + +p { + margin: .8rem 0; +} + +/* ---- Anker-Navigation im Header (Links auf die Ueberschriften) ---- */ + +.anker-nav { + margin-top: 1rem; + display: flex; + flex-wrap: wrap; + gap: .25rem 1.5rem; +} + +.anker-nav a { + color: var(--link); +} + +.anker-nav a:hover { + color: #ffffff; +} + +/* ---- Links ---- */ + +a { + color: var(--link); + text-decoration: none; +} + +a:hover { + color: #ffffff; + text-decoration: underline; + text-shadow: 0 0 8px rgba(102, 170, 255, 0.6); +} + +/* ---- Kein Bold: alle Schrift normal ---- */ + +strong, b, th { + font-weight: normal; +} + +/* ---- Listen, Zitate, Trennlinien ---- */ + +ul, ol { + margin: .8rem 0; + padding-left: 1.5rem; +} + +li { + margin: .25rem 0; +} + +li::marker { + color: var(--green); +} + +blockquote { + border-left: 3px solid var(--green); + margin: 1rem 0; + padding: .5rem 1rem; + color: var(--dim); + background: rgba(0, 255, 0, 0.05); +} + +hr { + border: none; + border-top: 1px dashed var(--border); + margin: 1.5rem 0; +} + +/* ---- Code ---- */ + +code { + color: var(--green); + background: #242424; + padding: .1rem .3rem; + border-radius: 3px; +} + +pre { + background: #141414; + border-radius: 4px; + padding: 1rem; + overflow-x: auto; +} + +pre code { + background: none; + padding: 0; +} + +/* ---- Footer ---- */ + +footer { + max-width: 1600px; + margin: 1.5rem auto 0; /* in der Seitenmitte, bündig zu main */ + text-align: left; + border-top: 1px solid var(--border); + padding-top: 1rem; + color: var(--dim); +} + +/* ---- Textauswahl invertiert, wie im Terminal ---- */ + +::selection { + background: var(--link); + color: #000000; +} diff --git a/output/index.html b/output/index.html index c7efd00..009cb96 100644 --- a/output/index.html +++ b/output/index.html @@ -8,15 +8,35 @@
      -

      Meine Website

      +
      + + + + +
      +

      hfg_linux_usergroup

      +

      free-software-for-art-and-design

      Dieser Header-Baustein wird automatisch ueber den Inhalt aller Seiten gesetzt.

      +

      startseite

      -

      Neue Seite. hier kommen alle inhalte hin.

      +

      Was ist die hfg_linux_usergroup

      +

      Die hfg_linux_usergroup ist eine diverse Gemeinschaft der HFG Offenbach, die sich für freie Software, Open Source und digitale Souveränität einsetzt.

      +

      Hintergrund ist die zentrale Bedeutung von Open Source für Kunst und Gestaltung: Es ermöglicht Demokratisierung, Transparenz, Chancengleichheit und Weiterentwicklung. Durch offene Lizenzen und frei zugängliche Software werden Wissen und kreative Prozesse entmonopolisiert. Künstler:innen und Designer:innen sind so nicht länger abhängig von proprietärer Software – die oft mit hohen Kosten, geschlossenen Systemen, Abhängigkeiten oder Abos einhergeht. Hinzu kommen ethische Bedenken wie Datenschutz oder Überwachungskapitalismus. Open Source fördert nicht nur gestalterische Freiheit, sondern auch eine kritische Reflexion über Technologie selbst.

      +

      Nächste Treffen

      +

      Die nächsten Termine für den Herbst/Winter 2026 werden aktuell geplant:

      +

      hfg_linux_usergroup_date_1_music hfg_linux_usergroup_date_2_image-editing hfg_linux_usergroup_date_3_coding-development hfg_linux_usergroup_date_4_3d-film-fotogrammetrie

      +

      Wer ist die hfg_linux_usergroup

      +

      Alle, die sich für Linux, Open Source und die Förderung von Wissen in Kunst und Design interessieren, können Teil der Gemeinschaft werden oder an den Treffen teilnehmen. Die hfg_linux_usergroup wurde von den beiden HFG-Lehrenden Sebastian Kujas und Florian Albrecht-Schoeck gegründet und wird aktuell von ihnen moderiert.

      +

      Was sind die Ziele der hfg_linux_usergroup

      +

      Ziel ist es, den Einsatz freier Software in Kunst und Design zu fördern. Durch Workshops, den Aufbau von Wissensdatenbanken, Austausch und Debatten sollen Eigenermächtigung und Verantwortung für Technologie wiederhergestellt werden. Dazu entwickeln wir gemeinsam ein eigenes Linux-basiertes Betriebssystem und unterstützen bei allen Schritten des Wechsels zu freier Software.

      +

      Das hfg_OS_

      +

      Das hfg_OS ist ein auf Debian 13 (Trixie) basierendes, Linux-basiertes Betriebssystem. Es wurde speziell für die Bedürfnisse von Künstler:innen und Designer:innen angepasst und wird kontinuierlich weiterentwickelt.

      +

      Bald verfügbar: hfg_OS_Falbella-2026-10

      Dieser Footer-Baustein wird automatisch an das Ende aller Seiten gesetzt. Zum Beispiel mit Links: Startseite

      diff --git a/templates/default.html b/templates/default.html index 2b85aa2..46e26ed 100644 --- a/templates/default.html +++ b/templates/default.html @@ -9,6 +9,7 @@
      {{header}} +

      {{titel}}

      From 5acf8ad0e7b9a4ef2a86f635155016df1a3ba42e Mon Sep 17 00:00:00 2001 From: Florian Date: Sat, 3 Oct 2026 12:03:55 +0200 Subject: [PATCH 7/8] ul und li navigation build geht --- cms.py | 34 +++++++++++++++++++++++++++++---- css/style.css | 43 +++++++++++++++++++++++++++++++++--------- md/header.md | 7 ------- md/startseite.md | 20 ++++++++++---------- output/css/style.css | 43 +++++++++++++++++++++++++++++++++--------- output/index.html | 28 +++++++++++++-------------- templates/default.html | 1 + 7 files changed, 122 insertions(+), 54 deletions(-) diff --git a/cms.py b/cms.py index ce2f038..9fda398 100755 --- a/cms.py +++ b/cms.py @@ -111,7 +111,7 @@ def md_to_html(text, headings=None): else: used_ids[hid] = 0 if headings is not None: - headings.append((hid, text_h)) + headings.append((level, hid, text_h)) out.append('%s' % (level, hid, inline(_escape(text_h)), level)) i += 1 @@ -181,11 +181,37 @@ def _slugify(s): def heading_nav(headings): - """Baut die Anker-Linkliste (Text-Links auf die Ueberschriften der Seite).""" + """Baut die Anker-Navigation als verschachtelte ul/li-Liste: + Eine Ueberschrift mit einem # mehr als die vorherige wird als + Unterpunkt eingerueckt. + headings: Liste von (level, anker, text). + """ if not headings: return "" - return "".join('%s' % (hid, _escape(text)) - for hid, text in headings) + parts = [] + levels = [] + for level, hid, text in headings: + if not levels: + # erste Ueberschrift: aeusserste Liste oeffnen + parts.append("
        ") + levels.append(level) + elif level > levels[-1]: + # tiefer -> neue verschachtelte Liste im letzten
      • + parts.append("
          ") + levels.append(level) + else: + # gleiche oder hoehere Ebene -> Listen schliessen bis Ebene passt + while len(levels) > 1 and levels[-1] > level: + parts.append("
        ") + levels.pop() + parts.append("
      • ") + parts.append('
      • %s' % (hid, _escape(text))) + parts.append("
      • ") + while len(levels) > 1: + parts.append("
      ") + levels.pop() + parts.append("
") + return "".join(parts) # ------------------------------------------------------------------- seiten diff --git a/css/style.css b/css/style.css index 27bd819..fcf3d28 100644 --- a/css/style.css +++ b/css/style.css @@ -53,6 +53,7 @@ header { } html { + font-size: 14px; /* Basis: alle Schriften 14 px (auch 1rem) */ background: var(--bg); } @@ -63,7 +64,6 @@ body { background: var(--bg); color: var(--text); line-height: 1.6; - font-size: 14px; margin: 0; padding: 2rem; box-sizing: border-box; @@ -87,12 +87,25 @@ main { } /* Shell-Prompt ganz oben auf der Seite */ -body::before { - content: "user@hfg_linux_group:~$ "; - color: var(--green); - display: block; +.prompt { max-width: 1200px; margin: 0 auto 1.5rem; + color: var(--green); +} + +/* Blinkender Terminal-Cursor hinter dem Prompt */ +.cursor { + display: inline-block; + width: .6em; + height: 1.1em; + background: var(--green); + vertical-align: text-bottom; + animation: blink 1s steps(1) infinite; +} + +@keyframes blink { + 0%, 49.9% { opacity: 1; } + 50%, 100% { opacity: 0; } } /* ---- Typografie ---- */ @@ -101,7 +114,7 @@ h1, h2, h3, h4, h5, h6 { color: var(--heading); text-shadow: 0 0 8px rgba(255, 149, 0, 0.35); letter-spacing: 1px; - font-size: 1rem; /* ueberall gleiche Schriftgroesse */ + font-size: 1rem; /* 14 px, ueberall gleiche Schriftgroesse */ font-weight: normal; margin: 1.5rem 0 .8rem; } @@ -118,9 +131,21 @@ p { .anker-nav { margin-top: 1rem; - display: flex; - flex-wrap: wrap; - gap: .25rem 1.5rem; +} + +.anker-nav ul { + list-style: none; + margin: 0; + padding-left: 0; +} + +/* Unterpunkte einruecken (Ueberschrift mit einem # mehr) */ +.anker-nav ul ul { + padding-left: 1.5rem; +} + +.anker-nav li { + margin: .25rem 0; } .anker-nav a { diff --git a/md/header.md b/md/header.md index 856ab9f..874e583 100644 --- a/md/header.md +++ b/md/header.md @@ -8,10 +8,3 @@ titel: Header - -# hfg_linux_usergroup -# free-software-for-art-and-design - -Dieser Header-Baustein wird automatisch ueber den Inhalt **aller** Seiten gesetzt. - -- [Startseite](index.html) diff --git a/md/startseite.md b/md/startseite.md index aece968..c977b89 100644 --- a/md/startseite.md +++ b/md/startseite.md @@ -1,30 +1,30 @@ --- -titel: startseite +titel: Willkommen --- -## Was ist die hfg_linux_usergroup +# Was ist die hfg_linux_usergroup Die hfg_linux_usergroup ist eine diverse Gemeinschaft der HFG Offenbach, die sich für freie Software, Open Source und digitale Souveränität einsetzt. Hintergrund ist die zentrale Bedeutung von Open Source für Kunst und Gestaltung: Es ermöglicht Demokratisierung, Transparenz, Chancengleichheit und Weiterentwicklung. Durch offene Lizenzen und frei zugängliche Software werden Wissen und kreative Prozesse entmonopolisiert. Künstler:innen und Designer:innen sind so nicht länger abhängig von proprietärer Software – die oft mit hohen Kosten, geschlossenen Systemen, Abhängigkeiten oder Abos einhergeht. Hinzu kommen ethische Bedenken wie Datenschutz oder Überwachungskapitalismus. Open Source fördert nicht nur gestalterische Freiheit, sondern auch eine kritische Reflexion über Technologie selbst. -## Nächste Treffen +# Nächste Treffen Die nächsten Termine für den Herbst/Winter 2026 werden aktuell geplant: -hfg_linux_usergroup_date_1_music -hfg_linux_usergroup_date_2_image-editing -hfg_linux_usergroup_date_3_coding-development -hfg_linux_usergroup_date_4_3d-film-fotogrammetrie +## hfg_linux_usergroup_date_1_music +## hfg_linux_usergroup_date_2_image-editing +## hfg_linux_usergroup_date_3_coding-development +## hfg_linux_usergroup_date_4_3d-film-fotogrammetrie -## Wer ist die hfg_linux_usergroup +# Wer ist die hfg_linux_usergroup Alle, die sich für Linux, Open Source und die Förderung von Wissen in Kunst und Design interessieren, können Teil der Gemeinschaft werden oder an den Treffen teilnehmen. Die hfg_linux_usergroup wurde von den beiden HFG-Lehrenden Sebastian Kujas und Florian Albrecht-Schoeck gegründet und wird aktuell von ihnen moderiert. -## Was sind die Ziele der hfg_linux_usergroup +# Was sind die Ziele der hfg_linux_usergroup Ziel ist es, den Einsatz freier Software in Kunst und Design zu fördern. Durch Workshops, den Aufbau von Wissensdatenbanken, Austausch und Debatten sollen Eigenermächtigung und Verantwortung für Technologie wiederhergestellt werden. Dazu entwickeln wir gemeinsam ein eigenes Linux-basiertes Betriebssystem und unterstützen bei allen Schritten des Wechsels zu freier Software. -## Das hfg_OS_ +# Das hfg_OS_ Das hfg_OS ist ein auf Debian 13 (Trixie) basierendes, Linux-basiertes Betriebssystem. Es wurde speziell für die Bedürfnisse von Künstler:innen und Designer:innen angepasst und wird kontinuierlich weiterentwickelt. Bald verfügbar: hfg_OS_Falbella-2026-10 diff --git a/output/css/style.css b/output/css/style.css index 27bd819..fcf3d28 100644 --- a/output/css/style.css +++ b/output/css/style.css @@ -53,6 +53,7 @@ header { } html { + font-size: 14px; /* Basis: alle Schriften 14 px (auch 1rem) */ background: var(--bg); } @@ -63,7 +64,6 @@ body { background: var(--bg); color: var(--text); line-height: 1.6; - font-size: 14px; margin: 0; padding: 2rem; box-sizing: border-box; @@ -87,12 +87,25 @@ main { } /* Shell-Prompt ganz oben auf der Seite */ -body::before { - content: "user@hfg_linux_group:~$ "; - color: var(--green); - display: block; +.prompt { max-width: 1200px; margin: 0 auto 1.5rem; + color: var(--green); +} + +/* Blinkender Terminal-Cursor hinter dem Prompt */ +.cursor { + display: inline-block; + width: .6em; + height: 1.1em; + background: var(--green); + vertical-align: text-bottom; + animation: blink 1s steps(1) infinite; +} + +@keyframes blink { + 0%, 49.9% { opacity: 1; } + 50%, 100% { opacity: 0; } } /* ---- Typografie ---- */ @@ -101,7 +114,7 @@ h1, h2, h3, h4, h5, h6 { color: var(--heading); text-shadow: 0 0 8px rgba(255, 149, 0, 0.35); letter-spacing: 1px; - font-size: 1rem; /* ueberall gleiche Schriftgroesse */ + font-size: 1rem; /* 14 px, ueberall gleiche Schriftgroesse */ font-weight: normal; margin: 1.5rem 0 .8rem; } @@ -118,9 +131,21 @@ p { .anker-nav { margin-top: 1rem; - display: flex; - flex-wrap: wrap; - gap: .25rem 1.5rem; +} + +.anker-nav ul { + list-style: none; + margin: 0; + padding-left: 0; +} + +/* Unterpunkte einruecken (Ueberschrift mit einem # mehr) */ +.anker-nav ul ul { + padding-left: 1.5rem; +} + +.anker-nav li { + margin: .25rem 0; } .anker-nav a { diff --git a/output/index.html b/output/index.html index 009cb96..454fa86 100644 --- a/output/index.html +++ b/output/index.html @@ -3,10 +3,11 @@ - startseite + Willkommen +
user@hfg_linux_group:~$ free-software-for-art-and-design
@@ -14,27 +15,24 @@
-

hfg_linux_usergroup

-

free-software-for-art-and-design

-

Dieser Header-Baustein wird automatisch ueber den Inhalt aller Seiten gesetzt.

- - +
-

startseite

-

Was ist die hfg_linux_usergroup

+

Willkommen

+

Was ist die hfg_linux_usergroup

Die hfg_linux_usergroup ist eine diverse Gemeinschaft der HFG Offenbach, die sich für freie Software, Open Source und digitale Souveränität einsetzt.

Hintergrund ist die zentrale Bedeutung von Open Source für Kunst und Gestaltung: Es ermöglicht Demokratisierung, Transparenz, Chancengleichheit und Weiterentwicklung. Durch offene Lizenzen und frei zugängliche Software werden Wissen und kreative Prozesse entmonopolisiert. Künstler:innen und Designer:innen sind so nicht länger abhängig von proprietärer Software – die oft mit hohen Kosten, geschlossenen Systemen, Abhängigkeiten oder Abos einhergeht. Hinzu kommen ethische Bedenken wie Datenschutz oder Überwachungskapitalismus. Open Source fördert nicht nur gestalterische Freiheit, sondern auch eine kritische Reflexion über Technologie selbst.

-

Nächste Treffen

+

Nächste Treffen

Die nächsten Termine für den Herbst/Winter 2026 werden aktuell geplant:

-

hfg_linux_usergroup_date_1_music hfg_linux_usergroup_date_2_image-editing hfg_linux_usergroup_date_3_coding-development hfg_linux_usergroup_date_4_3d-film-fotogrammetrie

-

Wer ist die hfg_linux_usergroup

+

hfg_linux_usergroup_date_1_music

+

hfg_linux_usergroup_date_2_image-editing

+

hfg_linux_usergroup_date_3_coding-development

+

hfg_linux_usergroup_date_4_3d-film-fotogrammetrie

+

Wer ist die hfg_linux_usergroup

Alle, die sich für Linux, Open Source und die Förderung von Wissen in Kunst und Design interessieren, können Teil der Gemeinschaft werden oder an den Treffen teilnehmen. Die hfg_linux_usergroup wurde von den beiden HFG-Lehrenden Sebastian Kujas und Florian Albrecht-Schoeck gegründet und wird aktuell von ihnen moderiert.

-

Was sind die Ziele der hfg_linux_usergroup

+

Was sind die Ziele der hfg_linux_usergroup

Ziel ist es, den Einsatz freier Software in Kunst und Design zu fördern. Durch Workshops, den Aufbau von Wissensdatenbanken, Austausch und Debatten sollen Eigenermächtigung und Verantwortung für Technologie wiederhergestellt werden. Dazu entwickeln wir gemeinsam ein eigenes Linux-basiertes Betriebssystem und unterstützen bei allen Schritten des Wechsels zu freier Software.

-

Das hfg_OS_

+

Das hfg_OS_

Das hfg_OS ist ein auf Debian 13 (Trixie) basierendes, Linux-basiertes Betriebssystem. Es wurde speziell für die Bedürfnisse von Künstler:innen und Designer:innen angepasst und wird kontinuierlich weiterentwickelt.

Bald verfügbar: hfg_OS_Falbella-2026-10

diff --git a/templates/default.html b/templates/default.html index 46e26ed..7e03409 100644 --- a/templates/default.html +++ b/templates/default.html @@ -7,6 +7,7 @@ +
user@hfg_linux_group:~$ free-software-for-art-and-design
{{header}} From 78a269165bae9397353ebab3394004b8e247b1e5 Mon Sep 17 00:00:00 2001 From: florian <134+florian@noreply.localhost> Date: Sat, 3 Oct 2026 10:11:27 +0000 Subject: [PATCH 8/8] readme.md aktualisiert --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 1a6195c..648f639 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1 @@ -eine änderung hier mal. - -sdfkjsdkjhfakjshfd +ich baue ein cms. \ No newline at end of file