diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..786dd3c Binary files /dev/null and b/.DS_Store differ 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..9fda398 --- /dev/null +++ b/cms.py @@ -0,0 +1,569 @@ +#!/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" +HEADER_PAGE = "header" # Baustein: wird ueber den Inhalt jeder Seite gesetzt +FOOTER_PAGE = "footer" # Baustein: wird unter den Inhalt jeder Seite gesetzt + + +# ---------------------------------------------------------------- markdown + +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 = [] + i = 0 + in_code = False + in_list = False + list_tag = None + used_ids = {} + + 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 + + # 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)) + 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((level, hid, text_h)) + out.append('%s' + % (level, hid, inline(_escape(text_h)), 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("") + return "".join(parts) + + +# ------------------------------------------------------------------- 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] not in (HEADER_PAGE, 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 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) + 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: + # 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: + # Template ohne {{footer}}-Platzhalter: Baustein automatisch anhaengen + idx = page.rindex("") + page = page[:idx] + "\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 = block_html(FOOTER_PAGE) + header_html = block_html(HEADER_PAGE) + + exported = [] + for slug in list_pages(): + 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: + 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, + "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 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) + 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 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, 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": + 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..fcf3d28 --- /dev/null +++ b/css/style.css @@ -0,0 +1,244 @@ +/* ---- 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 { + font-size: 14px; /* Basis: alle Schriften 14 px (auch 1rem) */ + 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; + 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 */ +.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 ---- */ + +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; /* 14 px, 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; +} + +.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 { + 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/hfglogo.png b/images/hfglogo.png new file mode 100644 index 0000000..84161e7 Binary files /dev/null and b/images/hfglogo.png differ 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/images/pferd.png b/images/pferd.png new file mode 100644 index 0000000..29a5085 Binary files /dev/null and b/images/pferd.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29 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/header.md b/md/header.md new file mode 100644 index 0000000..874e583 --- /dev/null +++ b/md/header.md @@ -0,0 +1,10 @@ +--- +titel: Header +--- + +
+ + + + +
diff --git a/md/startseite.md b/md/startseite.md new file mode 100644 index 0000000..c977b89 --- /dev/null +++ b/md/startseite.md @@ -0,0 +1,31 @@ +--- +titel: 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 +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 new file mode 100644 index 0000000..fcf3d28 --- /dev/null +++ b/output/css/style.css @@ -0,0 +1,244 @@ +/* ---- 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 { + font-size: 14px; /* Basis: alle Schriften 14 px (auch 1rem) */ + 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; + 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 */ +.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 ---- */ + +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; /* 14 px, 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; +} + +.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 { + 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 new file mode 100644 index 0000000..454fa86 --- /dev/null +++ b/output/index.html @@ -0,0 +1,43 @@ + + + + + + Willkommen + + + +
user@hfg_linux_group:~$ free-software-for-art-and-design
+
+
+ + + + +
+ +
+
+

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

+

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/readme.md b/readme.md index e69de29..648f639 100644 --- a/readme.md +++ b/readme.md @@ -0,0 +1 @@ +ich baue ein cms. \ No newline at end of file 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..7e03409 --- /dev/null +++ b/templates/default.html @@ -0,0 +1,23 @@ + + + + + + {{titel}} + + + +
user@hfg_linux_group:~$ free-software-for-art-and-design
+
+ {{header}} + +
+
+

{{titel}}

+ {{inhalt}} +
+ + +