anpassung style + readme

This commit is contained in:
Mara Zöllner 2026-07-01 17:25:18 +02:00
parent 88cd686a49
commit 1bd6f47f73
5 changed files with 280 additions and 377 deletions

101
README.md
View file

@ -20,14 +20,7 @@ Der bisher dokumentierte Serverpfad lautet:
Glueck-Auf/ Glueck-Auf/
├── flask_app/ ├── flask_app/
│ ├── app.py Flask-Anwendung und Routen │ ├── app.py Flask-Anwendung und Routen
│ ├── templates/ │ └── init_db.py Legt die SQLite-Datenbank für das Spiel an
│ │ ├── base.html Gemeinsames Layout und Styling
│ │ ├── index.html Startseite des Stadion-Escape-Rooms
│ │ └── raetsel.html Rätselseite mit Formular
│ └── assets/
│ ├── css/ Platz für ausgelagerte Stylesheets
│ ├── img/ Platz für Bilder
│ └── js/ Platz für JavaScript
├── index.html Startseite der bisherigen statischen Website ├── index.html Startseite der bisherigen statischen Website
├── ueber_mich.html Persönliche Vorstellungsseite ├── ueber_mich.html Persönliche Vorstellungsseite
@ -71,81 +64,43 @@ Die Anwendung läuft anschließend auf:
http://localhost:9007/ http://localhost:9007/
``` ```
Der Entwicklungsserver lauscht auf `0.0.0.0` und verwendet Port `9007`. Der Entwicklungsserver lauscht auf `0.0.0.0` und verwendet Port `9000`.
## Flask-Routen ## Flask-Routen
| Route | Methoden | Inhalt | | Route | Methoden | Inhalt |
| --- | --- | --- | | --- | --- | --- |
| `/` und `/app/` | GET | Startseite des Stadion-Escape-Rooms | | `/` und `/app/` | GET, POST | Startseite mit Namenseingabe |
| `/raetsel` und `/app/raetsel` | GET, POST | Rätsel am Stadiontor | | `/raum/<raum_id>` und `/app/raum/<raum_id>` | GET, POST | Räume, Ausgänge und Rätsel des Stadion-Escape-Rooms |
| `/frage` und `/app/frage` | GET, POST | Alternative Adressen für dieselbe Rätselseite |
Die Routen funktionieren jeweils auch mit abschließendem Schrägstrich.
## Aufbau der Flask-App ## Aufbau der Flask-App
### `flask_app/app.py` ### `flask_app/app.py`
- Erstellt die Flask-Anwendung. - Erstellt die Flask-Anwendung.
- Rendert die HTML-Seiten mit Jinja. - Baut die HTML-Seiten direkt in Python-Strings zusammen.
- Nimmt die Antwort des Rätsel-Formulars per `POST` entgegen. - Enthält das gemeinsame Styling direkt in der Konstante `STYLE`.
- Vergleicht die Eingabe mit dem gesuchten Code `4`. - Nimmt den Namen und Rätselantworten per `POST` entgegen.
- Übergibt die passende Rückmeldung an das Template. - Speichert Spielstart und Abschlusszeit in SQLite.
- Stellt zukünftige statische Dateien aus `flask_app/assets/` unter - Zeigt nach dem gelösten Rätsel eine Highscore-Liste an.
`/assets` bereit.
### Templates ### `flask_app/init_db.py`
`base.html` enthält das gemeinsame HTML-Grundgerüst, die Navigation und das Legt die SQLite-Datenbank `adventure.db` an und füllt sie mit den Räumen,
aktuelle Styling. `index.html` und `raetsel.html` erweitern dieses Ausgängen und der Rätselantwort.
Basis-Template mit Jinja-Blöcken.
Die Startseite führt in den Stadiongang ein. Auf der Rätselseite kann der Code
für das Stadiontor eingegeben werden. Die Auswertung erfolgt durch Flask auf
dem Server und wird anschließend auf derselben Seite angezeigt.
### Ablauf des Rätsels ### Ablauf des Rätsels
1. Die Startseite wird über `/` oder `/app/` aufgerufen. 1. Die Startseite wird über `/` oder `/app/` aufgerufen.
2. Der Link „Zum Stadiontor“ führt zur Rätselseite. 2. Nach der Namenseingabe wird ein Spielstand in der Datenbank angelegt.
3. Das Formular sendet die eingegebene Antwort mit der Methode `POST` an 3. Der Link „Zum Stadiontor“ führt in den ersten Raum.
dieselbe Route. 4. Über Ausgänge bewegt man sich durch Tunnel, Kabine, Kiosk und Nordkurve.
4. Flask liest den Wert aus `request.form["antwort"]`. 5. Im Rätselraum liest Flask `request.form["antwort"]`.
5. Bei der Antwort `4` wird eine Erfolgsmeldung angezeigt, bei jeder anderen 6. Bei der richtigen Antwort wird die Abschlusszeit gespeichert und der
Eingabe eine Fehlermeldung. Highscore angezeigt.
Die Rückmeldung wird in `app.py` erzeugt und als Variable `ergebnis` an Das HTML-Grundgerüst, die Navigation und das blaue Stadion-Design werden direkt
`raetsel.html` übergeben. Jinja zeigt den Ergebnisbereich nur an, wenn diese in `app.py` erzeugt. Es gibt keine Jinja-Templates für die Flask-App.
Variable vorhanden ist.
### Gemeinsames Template `base.html`
Das Basis-Template stellt die wiederverwendbaren Bestandteile der Flask-Seiten
bereit:
- HTML-Grundgerüst und deutsche Spracheinstellung,
- responsive Meta-Angaben,
- Google Fonts,
- Navigation zur Start- und Rätselseite,
- gemeinsames blaues Stadion-Design,
- responsive Darstellung für kleinere Bildschirme,
- Jinja-Blöcke für Seitentitel und Seiteninhalt.
Die beiden Unterseiten verwenden `{% extends "base.html" %}`. Dadurch müssen
Navigation, Layout und Styling nicht in jeder Datei wiederholt werden.
### Vorbereitete Assets
Flask wurde mit folgendem Verzeichnis für statische Dateien eingerichtet:
```text
flask_app/assets/
```
Dateien aus diesem Verzeichnis wären unter `/assets/...` erreichbar. Die
Unterordner `css`, `img` und `js` sind bereits angelegt, derzeit aber noch
leer. Das CSS der Flask-App steht deshalb momentan direkt in `base.html`.
## Statische Website ## Statische Website
@ -161,8 +116,7 @@ anderem:
Die statische Website und die Flask-App sind derzeit getrennte Bereiche des Die statische Website und die Flask-App sind derzeit getrennte Bereiche des
Projekts. Die Dateien in den Verzeichnissen `css/`, `img/` und `js/` gehören Projekts. Die Dateien in den Verzeichnissen `css/`, `img/` und `js/` gehören
zu den statischen HTML-Seiten. Für die Flask-App ist das Verzeichnis zu den statischen HTML-Seiten.
`flask_app/assets/` vorgesehen.
### `index.html` Startseite ### `index.html` Startseite
@ -284,10 +238,8 @@ verwenden `../img/logo.png`.
| Datei oder Ziel | Verlinkt beziehungsweise verwendet in | Zweck | | Datei oder Ziel | Verlinkt beziehungsweise verwendet in | Zweck |
| --- | --- | --- | | --- | --- | --- |
| `flask_app/templates/index.html` | Flask-Routen `/` und `/app/` | Start des Stadion-Escape-Rooms | | `flask_app/app.py` | Flask-Routen `/`, `/app/` und `/app/raum/<raum_id>` | Logik, HTML-Ausgabe und Styling des Stadion-Escape-Rooms |
| `flask_app/templates/raetsel.html` | Rätsel- und Frage-Routen | Formular und Auswertung des Stadiontor-Codes | | `flask_app/init_db.py` | Lokale Vorbereitung der Flask-App | Erstellt die SQLite-Datenbank |
| `flask_app/templates/base.html` | Beide Flask-Seiten | Gemeinsames Layout, Navigation und Styling |
| `flask_app/assets/` | Flask-Konfiguration in `app.py` | Vorbereiteter Bereich für statische Flask-Dateien |
| `index.html` | Navigation aller statischen Seiten | Rückkehr zur statischen Startseite | | `index.html` | Navigation aller statischen Seiten | Rückkehr zur statischen Startseite |
| `ueber_mich.html` | Navigation, Startseiten-Button und Startseiten-Card | Vorstellungsseite | | `ueber_mich.html` | Navigation, Startseiten-Button und Startseiten-Card | Vorstellungsseite |
| `eis_projekt.html` | Navigation und Startseiten-Card | Projektseite | | `eis_projekt.html` | Navigation und Startseiten-Card | Projektseite |
@ -301,13 +253,12 @@ verwenden `../img/logo.png`.
| `js/textanalyse.js` | `uebungen/textanalyse.html` | Textanalyse-Logik | | `js/textanalyse.js` | `uebungen/textanalyse.html` | Textanalyse-Logik |
| `img/Hintergrund.jpg` | `css/style.css` über die Klasse `.hero` | Hero-Hintergrundbild | | `img/Hintergrund.jpg` | `css/style.css` über die Klasse `.hero` | Hero-Hintergrundbild |
| `img/logo.png` | Alle statischen HTML-Seiten | Favicon | | `img/logo.png` | Alle statischen HTML-Seiten | Favicon |
| Google Fonts | Flask-Templates und statische Seiten | Schriftarten Bebas Neue und Barlow | | Google Fonts | Flask-App und statische Seiten | Schriftarten Bebas Neue und Barlow |
## Verwendete Technologien ## Verwendete Technologien
- Python 3 - Python 3
- Flask - Flask
- Jinja
- HTML5 - HTML5
- CSS3 mit Custom Properties, Grid, Flexbox und Animationen - CSS3 mit Custom Properties, Grid, Flexbox und Animationen
- JavaScript mit DOM-Manipulation, Events und `localStorage` - JavaScript mit DOM-Manipulation, Events und `localStorage`
@ -317,9 +268,7 @@ verwenden `../img/logo.png`.
- Es gibt derzeit keine `requirements.txt`; Flask muss lokal manuell - Es gibt derzeit keine `requirements.txt`; Flask muss lokal manuell
installiert werden. installiert werden.
- Die Styles der Flask-App befinden sich aktuell direkt in `base.html`. - Die Styles der Flask-App befinden sich direkt in `flask_app/app.py`.
- Die Unterordner in `flask_app/assets/` sind vorbereitet, enthalten aber
noch keine eigenen CSS-, Bild- oder JavaScript-Dateien.
- Das Kontaktformular der statischen Website sendet keine Daten. - Das Kontaktformular der statischen Website sendet keine Daten.
- Die Projektseite enthält noch keinen vollständig ausgearbeiteten Inhalt. - Die Projektseite enthält noch keinen vollständig ausgearbeiteten Inhalt.
- Die Angaben im Impressum sind fiktive Platzhalterdaten und nicht für eine - Die Angaben im Impressum sind fiktive Platzhalterdaten und nicht für eine

View file

@ -9,39 +9,241 @@ app.secret_key = "stadion-escape-secret"
STYLE = """ STYLE = """
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Glück Auf Stadion Escape</title>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:wght@300;400;500&display=swap" rel="stylesheet">
<style> <style>
*, *::before, *::after {
box-sizing: border-box;
}
body { body {
background: #0f172a; margin: 0;
color: #e5e7eb; min-height: 100vh;
font-family: system-ui, sans-serif; font-family: "Barlow", sans-serif;
max-width: 640px; color: #f5f8ff;
margin: 3rem auto; background:
padding: 0 1rem; linear-gradient(rgba(0, 20, 60, 0.9), rgba(0, 31, 68, 0.96)),
line-height: 1.6; radial-gradient(circle at 50% 120%, rgba(255, 255, 255, 0.16), transparent 34%),
#001f44;
} }
a, button {
display: inline-block; nav {
border: 1px solid #334155; display: flex;
border-radius: 8px; justify-content: space-between;
padding: .45em 1em; align-items: center;
margin: .3em .3em 0 0; gap: 2rem;
background: #1e293b; padding: 1.2rem 2.5rem;
color: #e5e7eb; background: rgba(0, 20, 60, 0.88);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.logo {
color: #fff;
font-family: "Bebas Neue", sans-serif;
font-size: 1.7rem;
letter-spacing: 0.12em;
text-decoration: none; text-decoration: none;
font: inherit;
cursor: pointer;
} }
nav a:not(.logo) {
color: rgba(255, 255, 255, 0.52);
font-size: 0.82rem;
letter-spacing: 0.12em;
text-decoration: none;
text-transform: uppercase;
}
nav a:hover {
color: #fff;
}
main {
position: relative;
min-height: calc(100vh - 76px);
display: grid;
align-items: center;
padding: 4rem 3rem;
overflow: hidden;
}
main::before {
content: "";
position: absolute;
inset: auto 8% 0;
height: 42%;
border: 1px solid rgba(122, 179, 255, 0.22);
border-bottom: 0;
border-radius: 50% 50% 0 0;
opacity: 0.75;
}
.scene,
.puzzle {
position: relative;
z-index: 1;
max-width: 760px;
}
.puzzle {
width: min(720px, 100%);
margin: 0 auto;
padding: 2rem;
border: 1px solid rgba(122, 179, 255, 0.28);
background: rgba(0, 31, 68, 0.72);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.28);
}
h1 {
margin-top: 0;
margin-bottom: 1.5rem;
font-family: "Bebas Neue", sans-serif;
font-size: clamp(3rem, 8vw, 7rem);
line-height: 0.92;
letter-spacing: 0.03em;
color: #fff;
text-shadow: 0 4px 40px rgba(0, 80, 200, 0.7);
}
h2 {
margin-top: 2rem;
font-family: "Bebas Neue", sans-serif;
font-size: 2.2rem;
letter-spacing: 0.06em;
}
h1 span {
color: #7ab3ff;
}
p {
max-width: 560px;
color: rgba(255, 255, 255, 0.68);
font-size: 1.05rem;
font-weight: 300;
line-height: 1.8;
}
.tag,
label {
color: rgba(255, 255, 255, 0.46);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.tag {
margin-bottom: 1.2rem;
}
.clue,
.result,
.highscore {
margin: 1.4rem 0;
padding: 1rem 1.2rem;
border-left: 3px solid #7ab3ff;
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.82);
}
label {
display: block;
margin-bottom: 0.6rem;
}
form {
margin-top: 1.4rem;
}
input { input {
padding: .45em .7em; width: 100%;
border-radius: 8px; padding: 0.95rem 1rem;
border: 1px solid #334155; border: 1px solid rgba(255, 255, 255, 0.18);
background: #020617; background: rgba(255, 255, 255, 0.06);
color: #e5e7eb; color: #fff;
outline: none;
font-size: 16px;
} }
input:focus {
border-color: rgba(122, 179, 255, 0.9);
box-shadow: 0 0 0 3px rgba(122, 179, 255, 0.12);
}
a.btn,
button {
display: inline-block;
margin-top: 1rem;
padding: 0.82rem 2.2rem;
color: #001f44;
background: #fff;
border: 0;
text-decoration: none;
font-family: "Barlow", sans-serif;
font-size: 0.82rem;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
cursor: pointer;
transition: background 0.2s, color 0.2s, transform 0.15s;
}
a.btn:hover,
button:hover {
color: #fff;
background: #0057c2;
transform: translateY(-2px);
}
.secondary {
padding-inline: 1.6rem;
background: rgba(255, 255, 255, 0.14);
color: #fff;
}
ol { ol {
padding-left: 1.4rem; padding-left: 1.4rem;
} }
li {
margin-bottom: 0.45rem;
color: rgba(255, 255, 255, 0.78);
}
@media (max-width: 640px) {
nav {
align-items: flex-start;
flex-direction: column;
padding: 1rem 1.5rem;
}
main {
padding: 3rem 1.5rem;
}
.puzzle {
padding: 1.4rem;
}
}
</style> </style>
</head>
<body>
<nav>
<a class="logo" href="/app/">Glück Auf</a>
<a href="/app/raum/stadiontor">Stadiontor</a>
</nav>
<main>
"""
HTML_END = """
</main>
</body>
</html>
""" """
@ -51,6 +253,10 @@ def get_db():
return db return db
def seite_html(inhalt, klasse="puzzle"):
return STYLE + '<section class="' + klasse + '">' + inhalt + "</section>" + HTML_END
def highscore_html(db): def highscore_html(db):
eintraege = db.execute( eintraege = db.execute(
""" """
@ -65,10 +271,10 @@ def highscore_html(db):
if not eintraege: if not eintraege:
return "" return ""
html = "<h2>Highscore</h2><ol>" html = '<div class="highscore"><h2>Highscore</h2><ol>'
for eintrag in eintraege: for eintrag in eintraege:
html += "<li>" + escape(eintrag["name"]) + " - " + str(round(eintrag["dauer"], 1)) + " Sekunden</li>" html += "<li>" + escape(eintrag["name"]) + " - " + str(round(eintrag["dauer"], 1)) + " Sekunden</li>"
html += "</ol>" html += "</ol></div>"
return html return html
@ -92,19 +298,23 @@ def start():
session["spieler_id"] = spieler_id session["spieler_id"] = spieler_id
session["spieler_name"] = name session["spieler_name"] = name
return STYLE + ( return seite_html(
"<h1>Eintrittskarte gelöst</h1>" "<h1>Eintrittskarte gelöst</h1>"
"<p>Willkommen, " + escape(name) + ". Dein Weg durchs Stadion beginnt.</p>" "<p>Willkommen, " + escape(name) + ". Dein Weg durchs Stadion beginnt.</p>"
'<a href="/app/raum/stadiontor">Zum Stadiontor</a>' '<a class="btn" href="/app/raum/stadiontor">Zum Stadiontor</a>'
) )
return STYLE + ( return seite_html(
"<h1>Stadion Escape</h1>" '<p class="tag">Escape Room im Stadiongang</p>'
"<h1>Glück Auf<br><span>im Stadion</span></h1>"
"<p>Trage deinen Namen auf der Eintrittskarte ein. Ab dann läuft deine Zeit.</p>" "<p>Trage deinen Namen auf der Eintrittskarte ein. Ab dann läuft deine Zeit.</p>"
'<form method="post">' '<form method="post">'
'<label for="name">Name auf der Eintrittskarte</label>'
'<input name="name" placeholder="Dein Name" autocomplete="off">' '<input name="name" placeholder="Dein Name" autocomplete="off">'
"<button>Abenteuer starten</button>" "<button>Abenteuer starten</button>"
"</form>" "</form>"
'<p><a class="btn secondary" href="/app/raum/stadiontor">Direkt zum Stadiontor</a></p>',
"scene",
) )
@ -116,7 +326,9 @@ def raum(raum_id):
if r is None: if r is None:
db.close() db.close()
return STYLE + '<h1>Raum nicht gefunden</h1><a href="/app/">Zurück zum Start</a>', 404 return seite_html(
'<h1>Raum nicht gefunden</h1><a class="btn" href="/app/">Zurück zum Start</a>'
), 404
if r["raetsel_frage"]: if r["raetsel_frage"]:
if request.method == "POST": if request.method == "POST":
@ -141,22 +353,23 @@ def raum(raum_id):
) )
html += highscore_html(db) html += highscore_html(db)
db.close() db.close()
return STYLE + html return seite_html(html)
return STYLE + ( return seite_html(
'<h1>Leider falsch</h1>' '<h1>Leider falsch</h1>'
'<p>Erwin schüttelt den Kopf. Die Botschaft stimmt noch nicht.</p>' '<p>Erwin schüttelt den Kopf. Die Botschaft stimmt noch nicht.</p>'
'<a href="/app/raum/' + raum_id + '">Nochmal versuchen</a>' '<a class="btn" href="/app/raum/' + escape(raum_id) + '">Nochmal versuchen</a>'
) )
html = "<h1>" + r["name"] + "</h1>" html = "<h1>" + escape(r["name"]) + "</h1>"
html += "<p>" + r["beschreibung"] + "</p>" html += "<p>" + escape(r["beschreibung"]) + "</p>"
html += '<form method="post">' html += '<form method="post">'
html += "<p>" + r["raetsel_frage"] + "</p>" html += '<p class="clue">' + escape(r["raetsel_frage"]) + "</p>"
html += '<input name="antwort" autocomplete="off">' html += '<label for="antwort">Deine Antwort</label>'
html += '<input id="antwort" name="antwort" autocomplete="off">'
html += "<button>OK</button>" html += "<button>OK</button>"
html += "</form>" html += "</form>"
db.close() db.close()
return STYLE + html return seite_html(html)
ausgaenge = db.execute( ausgaenge = db.execute(
"SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?", "SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?",
@ -164,14 +377,14 @@ def raum(raum_id):
).fetchall() ).fetchall()
db.close() db.close()
html = "<h1>" + r["name"] + "</h1>" html = "<h1>" + escape(r["name"]) + "</h1>"
html += "<p>" + r["beschreibung"] + "</p>" html += "<p>" + escape(r["beschreibung"]) + "</p>"
for a in ausgaenge: for a in ausgaenge:
html += '<a href="/app/raum/' + a["nach_raum"] + '">' html += '<a class="btn" href="/app/raum/' + escape(a["nach_raum"]) + '">'
html += a["richtung"] html += escape(a["richtung"])
html += "</a> " html += "</a> "
return STYLE + html return seite_html(html)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -1,212 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Glück Auf Stadion Escape{% endblock %}</title>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Barlow:wght@300;400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: "Barlow", sans-serif;
color: #f5f8ff;
background:
linear-gradient(rgba(0, 20, 60, 0.9), rgba(0, 31, 68, 0.96)),
radial-gradient(circle at 50% 120%, rgba(255, 255, 255, 0.16), transparent 34%),
#001f44;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
padding: 1.2rem 2.5rem;
background: rgba(0, 20, 60, 0.88);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.logo {
color: #fff;
font-family: "Bebas Neue", sans-serif;
font-size: 1.7rem;
letter-spacing: 0.12em;
text-decoration: none;
}
nav a:not(.logo) {
color: rgba(255, 255, 255, 0.52);
font-size: 0.82rem;
letter-spacing: 0.12em;
text-decoration: none;
text-transform: uppercase;
}
nav a:hover {
color: #fff;
}
main {
position: relative;
min-height: calc(100vh - 76px);
display: grid;
align-items: center;
padding: 4rem 3rem;
overflow: hidden;
}
main::before {
content: "";
position: absolute;
inset: auto 8% 0;
height: 42%;
border: 1px solid rgba(122, 179, 255, 0.22);
border-bottom: 0;
border-radius: 50% 50% 0 0;
opacity: 0.75;
}
.scene,
.puzzle {
position: relative;
z-index: 1;
max-width: 760px;
}
.puzzle {
width: min(720px, 100%);
margin: 0 auto;
padding: 2rem;
border: 1px solid rgba(122, 179, 255, 0.28);
background: rgba(0, 31, 68, 0.72);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.28);
}
h1 {
margin-top: 0;
margin-bottom: 1.5rem;
font-family: "Bebas Neue", sans-serif;
font-size: clamp(3rem, 8vw, 7rem);
line-height: 0.92;
letter-spacing: 0.03em;
color: #fff;
text-shadow: 0 4px 40px rgba(0, 80, 200, 0.7);
}
h1 span {
color: #7ab3ff;
}
p {
max-width: 560px;
color: rgba(255, 255, 255, 0.68);
font-size: 1.05rem;
font-weight: 300;
line-height: 1.8;
}
.tag,
label {
color: rgba(255, 255, 255, 0.46);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.tag {
margin-bottom: 1.2rem;
}
.clue,
.result {
margin: 1.4rem 0;
padding: 1rem 1.2rem;
border-left: 3px solid #7ab3ff;
background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.82);
}
label {
display: block;
margin-bottom: 0.6rem;
}
input {
width: 100%;
padding: 0.95rem 1rem;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.06);
color: #fff;
outline: none;
font-size: 16px;
}
input:focus {
border-color: rgba(122, 179, 255, 0.9);
box-shadow: 0 0 0 3px rgba(122, 179, 255, 0.12);
}
.btn,
button {
display: inline-block;
margin-top: 1rem;
padding: 0.82rem 2.2rem;
color: #001f44;
background: #fff;
border: 0;
text-decoration: none;
font-family: "Barlow", sans-serif;
font-size: 0.82rem;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
cursor: pointer;
transition: background 0.2s, color 0.2s, transform 0.15s;
}
.btn:hover,
button:hover {
color: #fff;
background: #0057c2;
transform: translateY(-2px);
}
.secondary {
padding-inline: 1.6rem;
background: rgba(255, 255, 255, 0.14);
color: #fff;
}
@media (max-width: 640px) {
nav {
align-items: flex-start;
flex-direction: column;
padding: 1rem 1.5rem;
}
main {
padding: 3rem 1.5rem;
}
.puzzle {
padding: 1.4rem;
}
}
</style>
</head>
<body>
<nav>
<a class="logo" href="/app/">Glück Auf</a>
<a href="/app/raetsel">Stadiontor</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>

View file

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% block title %}Glück Auf Stadion Escape{% endblock %}
{% block content %}
<section class="scene">
<p class="tag">Escape Room im Stadiongang</p>
<h1>Glück Auf<br><span>im Stadion</span></h1>
<p>
Der Gang unter der Tribüne ist fast leer. Aus dem Innenraum
dringt Flutlicht, aber das Drehkreuz zum Spielfeld bleibt gesperrt.
</p>
<p>
Auf einer blauen Tafel steht ein Hinweis:
Der erste Code steckt in der Zahl, die jeder Fan sofort erkennt.
</p>
<a class="btn" href="/app/raetsel">Zum Stadiontor</a>
</section>
{% endblock %}

View file

@ -1,28 +0,0 @@
{% extends "base.html" %}
{% block title %}Rätsel am Stadiontor{% endblock %}
{% block content %}
<section class="puzzle">
<h1>Das Stadiontor</h1>
<p>
Das Drehkreuz ist verriegelt. Über dem Eingang leuchtet eine besonders blaue Markierung: Nordkurve.
</p>
<p class="clue">
Hinweis: Schau auf das Logo des Vereins. Ein wahrer Fan erkennt die
gesuchte Zahl sofort.
</p>
<form method="post">
<label for="antwort">Code am Stadiontor</label>
<input id="antwort" name="antwort" required>
<button type="submit">Tor öffnen</button>
</form>
{% if ergebnis %}
<p class="result">{{ ergebnis }}</p>
{% endif %}
<p><a class="btn secondary" href="/app/">Zurück in den Gang</a></p>
</section>
{% endblock %}