From 92c485d86fa358c5258583d8fa3ed7c3c2c0e99c Mon Sep 17 00:00:00 2001 From: DylanBarnes Date: Wed, 15 Jul 2026 15:49:18 +0000 Subject: [PATCH] =?UTF-8?q?Terminal-Design=20f=C3=BCr=20U19=20Spielstand-V?= =?UTF-8?q?ersion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 274 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 249 insertions(+), 25 deletions(-) diff --git a/app/app.py b/app/app.py index 9f1599b..02c3fb7 100644 --- a/app/app.py +++ b/app/app.py @@ -14,11 +14,234 @@ def get_db(): return db -BASE_CSS = """... (unverändert, deine bestehende CSS bleibt) ...""" -BASE_JS = """... (unverändert) ...""" +BASE_CSS = """ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500&display=swap'); + +:root { + --void: #030507; + --panel: #0a1512; + --panel-2: #0d1a16; + --line: rgba(127, 219, 182, 0.22); + --text: #d7fbe8; + --text-dim: #77988b; + --amber: #ffb454; + --amber-dim: #8a5e2a; + --danger: #ff6b5e; + --success: #4ade9a; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + min-height: 100vh; + background: + radial-gradient(ellipse at 50% -10%, rgba(74, 222, 154, 0.06), transparent 60%), + var(--void); + color: var(--text); + font-family: 'IBM Plex Sans', sans-serif; + position: relative; + overflow-x: hidden; +} + +/* feine Scanlines über der gesamten Seite */ +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background: repeating-linear-gradient( + to bottom, + rgba(255,255,255,0.015) 0px, + rgba(255,255,255,0.015) 1px, + transparent 2px, + transparent 3px + ); + z-index: 2; + mix-blend-mode: overlay; +} + +/* Vignette */ +body::after { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background: radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,0.55) 100%); + z-index: 1; +} + +.shell { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + padding: 2.5rem 1rem; + position: relative; + z-index: 3; +} + +.console-strip { + width: min(760px, 100%); + display: flex; + justify-content: space-between; + align-items: center; + font-family: 'IBM Plex Mono', monospace; + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-dim); + border: 1px solid var(--line); + border-bottom: none; + border-radius: 10px 10px 0 0; + padding: 0.55rem 1rem; + background: var(--panel-2); +} + +.console-strip .dot { + display: inline-block; + width: 6px; height: 6px; + border-radius: 50%; + background: var(--success); + margin-right: 0.4em; + box-shadow: 0 0 6px var(--success); +} + +.card { + width: min(760px, 100%); + background: var(--panel); + border: 1px solid var(--line); + border-radius: 0 0 10px 10px; + padding: 2.2rem 2.2rem 2.4rem; + box-shadow: 0 30px 80px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.03); +} + +.eyebrow { + font-family: 'IBM Plex Mono', monospace; + font-size: 0.75rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--amber); + margin: 0 0 0.6rem; +} + +.card h1 { + font-family: 'IBM Plex Mono', monospace; + margin: 0 0 1rem; + font-size: 1.7rem; + font-weight: 600; + color: var(--text); + letter-spacing: 0.01em; +} +.card h1::after { + content: "_"; + color: var(--amber); + animation: blink 1.1s steps(1) infinite; +} +@keyframes blink { 50% { opacity: 0; } } + +.card p { + line-height: 1.75; + color: #b9d6c9; + font-size: 1rem; +} + +.actions, .links { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 1.5rem; +} + +.btn, .card a, .card button { + font-family: 'IBM Plex Mono', monospace; + font-size: 0.85rem; + letter-spacing: 0.04em; + text-transform: uppercase; + display: inline-flex; + align-items: center; + gap: 0.4em; + background: transparent; + color: var(--amber); + border: 1px solid var(--amber-dim); + border-radius: 6px; + padding: 0.65rem 1rem; + text-decoration: none; + cursor: pointer; + transition: all .18s ease; +} +.btn::before, .card a::before, .card button::before { content: "▸"; opacity: .6; } +.btn:hover, .card a:hover, .card button:hover { + background: rgba(255,180,84,0.08); + border-color: var(--amber); + color: #ffd08a; +} +.btn.secondary, .card a.secondary { + color: var(--text-dim); + border-color: var(--line); +} +.btn.secondary:hover, .card a.secondary:hover { + color: var(--text); + border-color: rgba(127,219,182,0.5); + background: rgba(127,219,182,0.06); +} + +form { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + align-items: center; + margin-top: 1.2rem; +} +form::before { + content: ">"; + font-family: 'IBM Plex Mono', monospace; + color: var(--success); + font-weight: 700; +} +input { + flex: 1 1 220px; + font-family: 'IBM Plex Mono', monospace; + border: 1px solid var(--line); + border-radius: 6px; + padding: 0.7rem 0.9rem; + background: #050b09; + color: var(--text); +} +input::placeholder { color: #4d6a5f; } +input:focus { + outline: none; + border-color: var(--success); + box-shadow: 0 0 0 3px rgba(74,222,154,0.15); +} + +.status-line { + font-family: 'IBM Plex Mono', monospace; + font-size: 0.75rem; + color: var(--text-dim); + margin-top: 1.4rem; + padding-top: 1rem; + border-top: 1px dashed var(--line); +} + +@media (prefers-reduced-motion: reduce) { + .card h1::after { animation: none; opacity: 1; } +} +""" + +BASE_JS = """ +window.addEventListener('DOMContentLoaded', () => { + const firstInput = document.querySelector('input'); + if (firstInput) firstInput.focus(); +}); +""" -def render_page(title, body_html): +def render_page(title, body_html, eyebrow=None): + spieler = session.get("spieler") + status = f'{html.escape(spieler)}' if spieler else 'NOTSTROM AKTIV' + eyebrow_html = f'

{html.escape(eyebrow)}

' if eyebrow else "" + return f""" @@ -29,7 +252,12 @@ def render_page(title, body_html):
+
+ {status} + SCHIFF-OS // NOTBETRIEB +
+ {eyebrow_html} {body_html}
@@ -66,12 +294,10 @@ def start(): ).fetchone() if s: weiterspielen_html = f""" -

Willkommen zurück, {html.escape(session['spieler'])}!

- Weiterspielen - Neues Abenteuer + Weiterspielen + Neues Abenteuer
-
""" db.close() @@ -80,12 +306,12 @@ def start():

Du bist der einzige Überlebende an Bord. Erkunde das Schiff und aktiviere das Notsignal.

{weiterspielen_html}
- - + +
-

{anzahl['n']} Abenteurer waren schon hier.

+

{anzahl['n']} Abenteurer waren schon an Bord.

""" - return render_page("Verlassenes Raumschiff", body) + return render_page("Verlassenes Raumschiff", body, eyebrow="SCHIFF-OS v2.4 // BOOT") @app.route("/raum/", methods=["GET", "POST"]) @@ -93,7 +319,6 @@ def start(): def raum(id): db = get_db() - # Spielstand fortschreiben, sobald jemand angemeldet ist if "spieler" in session: db.execute( "UPDATE spielstand SET raum_id=? WHERE spieler=?", @@ -107,12 +332,11 @@ def raum(id): return render_page( "Raum nicht gefunden", '

Raum nicht gefunden

Der angeforderte Raum existiert nicht.

' - '' + '', + eyebrow="FEHLER" ) - unterwegs_html = "" - if "spieler" in session: - unterwegs_html = f'

Unterwegs als: {html.escape(session["spieler"])}

' + eyebrow = f"SEKTOR {id:02d}" if r["raetsel_frage"]: if request.method == "POST": @@ -121,26 +345,27 @@ def raum(id): return render_page( "Notsignal aktiviert", '

Notsignal erfolgreich aktiviert

Rettung ist unterwegs.

' - '' + '', + eyebrow="MISSION ERFÜLLT" ) db.close() return render_page( "Falscher Code", '

Falscher Code

Der KI-Kern reagiert nicht.

' - '' + '', + eyebrow=eyebrow ) db.close() body = f"""

{html.escape(r['name'])}

{html.escape(r['beschreibung'])}

- {unterwegs_html}
- +
""" - return render_page(html.escape(r["name"]), body) + return render_page(html.escape(r["name"]), body, eyebrow=eyebrow) ausgaenge = db.execute( "SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?", (id,) @@ -150,19 +375,18 @@ def raum(id): body = f"""

{html.escape(r['name'])}

{html.escape(r['beschreibung'])}

- {unterwegs_html} " - return render_page(html.escape(r["name"]), body) + return render_page(html.escape(r["name"]), body, eyebrow=eyebrow) @app.route("/reset") @app.route("/app/reset") def reset(): - session.pop("spieler", None) # Session vergessen (DB bleibt!) + session.pop("spieler", None) return redirect("/app/")