Style Flask app room pages
This commit is contained in:
parent
29a7c365db
commit
dd2f96b731
1 changed files with 161 additions and 27 deletions
188
app/app.py
188
app/app.py
|
|
@ -1,63 +1,197 @@
|
||||||
from flask import Flask, request
|
from flask import Flask, request
|
||||||
|
import html
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
DB_PATH = Path(__file__).with_name("adventure.db")
|
||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
db = sqlite3.connect("adventure.db")
|
db = sqlite3.connect(DB_PATH)
|
||||||
db.row_factory = sqlite3.Row
|
db.row_factory = sqlite3.Row
|
||||||
return db
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
BASE_CSS = """
|
||||||
|
:root {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #07121d 0%, #102a43 45%, #1e3a5f 100%);
|
||||||
|
color: #e2e8f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.shell {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
width: min(760px, 100%);
|
||||||
|
background: rgba(7, 18, 29, 0.88);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
.card h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
.card p {
|
||||||
|
line-height: 1.7;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
.actions, .links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
.btn, .card a, .card button {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
|
||||||
|
color: #f8fafc;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.8rem 1.1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
box-shadow: 0 10px 25px rgba(6, 182, 212, 0.25);
|
||||||
|
}
|
||||||
|
.btn:hover, .card a:hover, .card button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 12px 30px rgba(37, 99, 235, 0.35);
|
||||||
|
}
|
||||||
|
.btn.secondary {
|
||||||
|
background: linear-gradient(135deg, #475569 0%, #64748b 100%);
|
||||||
|
box-shadow: 0 10px 25px rgba(100, 116, 139, 0.25);
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
flex: 1 1 220px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
background: rgba(15, 23, 42, 0.9);
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
outline: 2px solid rgba(6, 182, 212, 0.45);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
BASE_JS = """
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const firstInput = document.querySelector('input');
|
||||||
|
if (firstInput) firstInput.focus();
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def render_page(title, body_html):
|
||||||
|
return f"""<!doctype html>
|
||||||
|
<html lang=\"de\">
|
||||||
|
<head>
|
||||||
|
<meta charset=\"utf-8\">
|
||||||
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
|
||||||
|
<title>{html.escape(title)}</title>
|
||||||
|
<style>{BASE_CSS}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class=\"shell\">
|
||||||
|
<section class=\"card\">
|
||||||
|
{body_html}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<script>{BASE_JS}</script>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
|
@app.route("/app")
|
||||||
|
@app.route("/app/")
|
||||||
def start():
|
def start():
|
||||||
return """<style>
|
body = """
|
||||||
body{background:#07121d;color:#d9e9f0;font-family:system-ui,sans-serif;
|
<h1>Verlassenes Raumschiff</h1>
|
||||||
max-width:760px;margin:3rem auto;padding:1.2rem;line-height:1.75}
|
<p>Du bist der einzige Überlebende an Bord. Erkunde das Schiff und aktiviere das Notsignal.</p>
|
||||||
a,button{display:inline-block;background:#0f172a;color:#e2e8f0;
|
<div class="actions">
|
||||||
border:1px solid #334155;border-radius:12px;padding:.75rem 1.2rem;
|
<a class="btn" href="/app/raum/1">Mission starten</a>
|
||||||
margin:.4rem .3rem 0 0;text-decoration:none;font-weight:700}
|
</div>
|
||||||
a:hover,button:hover{background:#1e293b}
|
"""
|
||||||
h1{font-size:2.4rem;margin-bottom:.5rem}
|
return render_page("Verlassenes Raumschiff", body)
|
||||||
p{max-width:700px}
|
|
||||||
.container{padding:.8rem 1rem;border:1px solid #18324c;border-radius:18px;
|
|
||||||
background:rgba(15,23,42,.9);box-shadow:0 18px 50px rgba(0,0,0,.25)}
|
|
||||||
</style>""" + '<div class="container"><h1>Verlassenes Raumschiff</h1>' \
|
|
||||||
'<p>Du bist der einzige Überlebende an Bord. Erkunde das Schiff und aktiviere das Notsignal.</p>' \
|
|
||||||
'<a href="/app/raum/1">Mission starten</a></div>'
|
|
||||||
|
|
||||||
@app.route("/raum/<int:id>", methods=["GET", "POST"])
|
@app.route("/raum/<int:id>", methods=["GET", "POST"])
|
||||||
|
@app.route("/app/raum/<int:id>", methods=["GET", "POST"])
|
||||||
def raum(id):
|
def raum(id):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
r = db.execute("SELECT * FROM raeume WHERE id=?", (id,)).fetchone()
|
r = db.execute("SELECT * FROM raeume WHERE id=?", (id,)).fetchone()
|
||||||
if not r:
|
if not r:
|
||||||
db.close()
|
db.close()
|
||||||
return '<h1>Raum nicht gefunden</h1>'
|
return render_page(
|
||||||
|
"Raum nicht gefunden",
|
||||||
|
'<h1>Raum nicht gefunden</h1><p>Der angeforderte Raum existiert nicht.</p><div class="actions"><a class="btn secondary" href="/app/">Zurück zum Start</a></div>'
|
||||||
|
)
|
||||||
|
|
||||||
if r["raetsel_frage"]:
|
if r["raetsel_frage"]:
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if request.form.get("antwort", "").strip() == r["raetsel_antwort"]:
|
if request.form.get("antwort", "").strip() == r["raetsel_antwort"]:
|
||||||
db.close()
|
db.close()
|
||||||
return '<h1>Notsignal erfolgreich aktiviert. Rettung ist unterwegs.</h1>'
|
return render_page(
|
||||||
|
"Notsignal aktiviert",
|
||||||
|
'<h1>Notsignal erfolgreich aktiviert</h1><p>Rettung ist unterwegs.</p><div class="actions"><a class="btn" href="/app/">Zurück zum Start</a></div>'
|
||||||
|
)
|
||||||
db.close()
|
db.close()
|
||||||
return '<h1>Falscher Code</h1><p>Der KI-Kern reagiert nicht.</p>' \
|
return render_page(
|
||||||
'<a href="/app/raum/' + str(id) + '">nochmal versuchen</a>'
|
"Falscher Code",
|
||||||
|
'<h1>Falscher Code</h1><p>Der KI-Kern reagiert nicht.</p><div class="actions"><a class="btn secondary" href="/app/raum/' + str(id) + '">nochmal versuchen</a></div>'
|
||||||
|
)
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
return '<h1>' + r["name"] + '</h1>' \
|
body = f"""
|
||||||
'<p>' + r["beschreibung"] + '</p>' \
|
<h1>{html.escape(r['name'])}</h1>
|
||||||
'<form method="post">' + r["raetsel_frage"] + \
|
<p>{html.escape(r['beschreibung'])}</p>
|
||||||
' <input name="antwort"><button>OK</button></form>'
|
<form method="post">
|
||||||
|
<input id="antwort" name="antwort" placeholder="Antwort eingeben" autocomplete="off">
|
||||||
|
<button type="submit">OK</button>
|
||||||
|
</form>
|
||||||
|
"""
|
||||||
|
return render_page(html.escape(r["name"]), body)
|
||||||
|
|
||||||
ausgaenge = db.execute(
|
ausgaenge = db.execute(
|
||||||
"SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?", (id,)
|
"SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?", (id,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
html = '<h1>' + r["name"] + '</h1>'
|
body = f"""
|
||||||
html += '<p>' + r["beschreibung"] + '</p>'
|
<h1>{html.escape(r['name'])}</h1>
|
||||||
|
<p>{html.escape(r['beschreibung'])}</p>
|
||||||
|
<div class="links">
|
||||||
|
"""
|
||||||
for a in ausgaenge:
|
for a in ausgaenge:
|
||||||
html += '<a href="/app/raum/' + str(a["nach_raum"]) + '">' + a["richtung"] + '</a> '
|
body += f'<a class="btn secondary" href="/app/raum/{a["nach_raum"]}">{html.escape(a["richtung"])}</a>'
|
||||||
return html
|
body += "</div>"
|
||||||
|
return render_page(html.escape(r["name"]), body)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=9004)
|
app.run(host="0.0.0.0", port=9004)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue