anpassung style + readme
This commit is contained in:
parent
88cd686a49
commit
1bd6f47f73
5 changed files with 280 additions and 377 deletions
101
README.md
101
README.md
|
|
@ -20,14 +20,7 @@ Der bisher dokumentierte Serverpfad lautet:
|
|||
Glueck-Auf/
|
||||
├── flask_app/
|
||||
│ ├── app.py Flask-Anwendung und Routen
|
||||
│ ├── templates/
|
||||
│ │ ├── 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
|
||||
│ └── init_db.py Legt die SQLite-Datenbank für das Spiel an
|
||||
│
|
||||
├── index.html Startseite der bisherigen statischen Website
|
||||
├── ueber_mich.html Persönliche Vorstellungsseite
|
||||
|
|
@ -71,81 +64,43 @@ Die Anwendung läuft anschließend auf:
|
|||
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
|
||||
|
||||
| Route | Methoden | Inhalt |
|
||||
| --- | --- | --- |
|
||||
| `/` und `/app/` | GET | Startseite des Stadion-Escape-Rooms |
|
||||
| `/raetsel` und `/app/raetsel` | GET, POST | Rätsel am Stadiontor |
|
||||
| `/frage` und `/app/frage` | GET, POST | Alternative Adressen für dieselbe Rätselseite |
|
||||
|
||||
Die Routen funktionieren jeweils auch mit abschließendem Schrägstrich.
|
||||
| `/` und `/app/` | GET, POST | Startseite mit Namenseingabe |
|
||||
| `/raum/<raum_id>` und `/app/raum/<raum_id>` | GET, POST | Räume, Ausgänge und Rätsel des Stadion-Escape-Rooms |
|
||||
|
||||
## Aufbau der Flask-App
|
||||
|
||||
### `flask_app/app.py`
|
||||
|
||||
- Erstellt die Flask-Anwendung.
|
||||
- Rendert die HTML-Seiten mit Jinja.
|
||||
- Nimmt die Antwort des Rätsel-Formulars per `POST` entgegen.
|
||||
- Vergleicht die Eingabe mit dem gesuchten Code `4`.
|
||||
- Übergibt die passende Rückmeldung an das Template.
|
||||
- Stellt zukünftige statische Dateien aus `flask_app/assets/` unter
|
||||
`/assets` bereit.
|
||||
- Baut die HTML-Seiten direkt in Python-Strings zusammen.
|
||||
- Enthält das gemeinsame Styling direkt in der Konstante `STYLE`.
|
||||
- Nimmt den Namen und Rätselantworten per `POST` entgegen.
|
||||
- Speichert Spielstart und Abschlusszeit in SQLite.
|
||||
- Zeigt nach dem gelösten Rätsel eine Highscore-Liste an.
|
||||
|
||||
### Templates
|
||||
### `flask_app/init_db.py`
|
||||
|
||||
`base.html` enthält das gemeinsame HTML-Grundgerüst, die Navigation und das
|
||||
aktuelle Styling. `index.html` und `raetsel.html` erweitern dieses
|
||||
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.
|
||||
Legt die SQLite-Datenbank `adventure.db` an und füllt sie mit den Räumen,
|
||||
Ausgängen und der Rätselantwort.
|
||||
|
||||
### Ablauf des Rätsels
|
||||
|
||||
1. Die Startseite wird über `/` oder `/app/` aufgerufen.
|
||||
2. Der Link „Zum Stadiontor“ führt zur Rätselseite.
|
||||
3. Das Formular sendet die eingegebene Antwort mit der Methode `POST` an
|
||||
dieselbe Route.
|
||||
4. Flask liest den Wert aus `request.form["antwort"]`.
|
||||
5. Bei der Antwort `4` wird eine Erfolgsmeldung angezeigt, bei jeder anderen
|
||||
Eingabe eine Fehlermeldung.
|
||||
2. Nach der Namenseingabe wird ein Spielstand in der Datenbank angelegt.
|
||||
3. Der Link „Zum Stadiontor“ führt in den ersten Raum.
|
||||
4. Über Ausgänge bewegt man sich durch Tunnel, Kabine, Kiosk und Nordkurve.
|
||||
5. Im Rätselraum liest Flask `request.form["antwort"]`.
|
||||
6. Bei der richtigen Antwort wird die Abschlusszeit gespeichert und der
|
||||
Highscore angezeigt.
|
||||
|
||||
Die Rückmeldung wird in `app.py` erzeugt und als Variable `ergebnis` an
|
||||
`raetsel.html` übergeben. Jinja zeigt den Ergebnisbereich nur an, wenn diese
|
||||
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`.
|
||||
Das HTML-Grundgerüst, die Navigation und das blaue Stadion-Design werden direkt
|
||||
in `app.py` erzeugt. Es gibt keine Jinja-Templates für die Flask-App.
|
||||
|
||||
## Statische Website
|
||||
|
||||
|
|
@ -161,8 +116,7 @@ anderem:
|
|||
|
||||
Die statische Website und die Flask-App sind derzeit getrennte Bereiche des
|
||||
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
|
||||
`flask_app/assets/` vorgesehen.
|
||||
zu den statischen HTML-Seiten.
|
||||
|
||||
### `index.html` – Startseite
|
||||
|
||||
|
|
@ -284,10 +238,8 @@ verwenden `../img/logo.png`.
|
|||
|
||||
| Datei oder Ziel | Verlinkt beziehungsweise verwendet in | Zweck |
|
||||
| --- | --- | --- |
|
||||
| `flask_app/templates/index.html` | Flask-Routen `/` und `/app/` | Start des Stadion-Escape-Rooms |
|
||||
| `flask_app/templates/raetsel.html` | Rätsel- und Frage-Routen | Formular und Auswertung des Stadiontor-Codes |
|
||||
| `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 |
|
||||
| `flask_app/app.py` | Flask-Routen `/`, `/app/` und `/app/raum/<raum_id>` | Logik, HTML-Ausgabe und Styling des Stadion-Escape-Rooms |
|
||||
| `flask_app/init_db.py` | Lokale Vorbereitung der Flask-App | Erstellt die SQLite-Datenbank |
|
||||
| `index.html` | Navigation aller statischen Seiten | Rückkehr zur statischen Startseite |
|
||||
| `ueber_mich.html` | Navigation, Startseiten-Button und Startseiten-Card | Vorstellungsseite |
|
||||
| `eis_projekt.html` | Navigation und Startseiten-Card | Projektseite |
|
||||
|
|
@ -301,13 +253,12 @@ verwenden `../img/logo.png`.
|
|||
| `js/textanalyse.js` | `uebungen/textanalyse.html` | Textanalyse-Logik |
|
||||
| `img/Hintergrund.jpg` | `css/style.css` über die Klasse `.hero` | Hero-Hintergrundbild |
|
||||
| `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
|
||||
|
||||
- Python 3
|
||||
- Flask
|
||||
- Jinja
|
||||
- HTML5
|
||||
- CSS3 mit Custom Properties, Grid, Flexbox und Animationen
|
||||
- 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
|
||||
installiert werden.
|
||||
- Die Styles der Flask-App befinden sich aktuell direkt in `base.html`.
|
||||
- Die Unterordner in `flask_app/assets/` sind vorbereitet, enthalten aber
|
||||
noch keine eigenen CSS-, Bild- oder JavaScript-Dateien.
|
||||
- Die Styles der Flask-App befinden sich direkt in `flask_app/app.py`.
|
||||
- Das Kontaktformular der statischen Website sendet keine Daten.
|
||||
- Die Projektseite enthält noch keinen vollständig ausgearbeiteten Inhalt.
|
||||
- Die Angaben im Impressum sind fiktive Platzhalterdaten und nicht für eine
|
||||
|
|
|
|||
297
flask_app/app.py
297
flask_app/app.py
|
|
@ -9,39 +9,241 @@ app.secret_key = "stadion-escape-secret"
|
|||
|
||||
|
||||
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>
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: #e5e7eb;
|
||||
font-family: system-ui, sans-serif;
|
||||
max-width: 640px;
|
||||
margin: 3rem auto;
|
||||
padding: 0 1rem;
|
||||
line-height: 1.6;
|
||||
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;
|
||||
}
|
||||
a, button {
|
||||
display: inline-block;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 8px;
|
||||
padding: .45em 1em;
|
||||
margin: .3em .3em 0 0;
|
||||
background: #1e293b;
|
||||
color: #e5e7eb;
|
||||
|
||||
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;
|
||||
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 {
|
||||
padding: .45em .7em;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #334155;
|
||||
background: #020617;
|
||||
color: #e5e7eb;
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
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>
|
||||
</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
|
||||
|
||||
|
||||
def seite_html(inhalt, klasse="puzzle"):
|
||||
return STYLE + '<section class="' + klasse + '">' + inhalt + "</section>" + HTML_END
|
||||
|
||||
|
||||
def highscore_html(db):
|
||||
eintraege = db.execute(
|
||||
"""
|
||||
|
|
@ -65,10 +271,10 @@ def highscore_html(db):
|
|||
if not eintraege:
|
||||
return ""
|
||||
|
||||
html = "<h2>Highscore</h2><ol>"
|
||||
html = '<div class="highscore"><h2>Highscore</h2><ol>'
|
||||
for eintrag in eintraege:
|
||||
html += "<li>" + escape(eintrag["name"]) + " - " + str(round(eintrag["dauer"], 1)) + " Sekunden</li>"
|
||||
html += "</ol>"
|
||||
html += "</ol></div>"
|
||||
return html
|
||||
|
||||
|
||||
|
|
@ -92,19 +298,23 @@ def start():
|
|||
session["spieler_id"] = spieler_id
|
||||
session["spieler_name"] = name
|
||||
|
||||
return STYLE + (
|
||||
return seite_html(
|
||||
"<h1>Eintrittskarte gelöst</h1>"
|
||||
"<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 + (
|
||||
"<h1>Stadion Escape</h1>"
|
||||
return seite_html(
|
||||
'<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>"
|
||||
'<form method="post">'
|
||||
'<label for="name">Name auf der Eintrittskarte</label>'
|
||||
'<input name="name" placeholder="Dein Name" autocomplete="off">'
|
||||
"<button>Abenteuer starten</button>"
|
||||
"</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:
|
||||
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 request.method == "POST":
|
||||
|
|
@ -141,22 +353,23 @@ def raum(raum_id):
|
|||
)
|
||||
html += highscore_html(db)
|
||||
db.close()
|
||||
return STYLE + html
|
||||
return STYLE + (
|
||||
return seite_html(html)
|
||||
return seite_html(
|
||||
'<h1>Leider falsch</h1>'
|
||||
'<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 += "<p>" + r["beschreibung"] + "</p>"
|
||||
html = "<h1>" + escape(r["name"]) + "</h1>"
|
||||
html += "<p>" + escape(r["beschreibung"]) + "</p>"
|
||||
html += '<form method="post">'
|
||||
html += "<p>" + r["raetsel_frage"] + "</p>"
|
||||
html += '<input name="antwort" autocomplete="off">'
|
||||
html += '<p class="clue">' + escape(r["raetsel_frage"]) + "</p>"
|
||||
html += '<label for="antwort">Deine Antwort</label>'
|
||||
html += '<input id="antwort" name="antwort" autocomplete="off">'
|
||||
html += "<button>OK</button>"
|
||||
html += "</form>"
|
||||
db.close()
|
||||
return STYLE + html
|
||||
return seite_html(html)
|
||||
|
||||
ausgaenge = db.execute(
|
||||
"SELECT richtung, nach_raum FROM ausgaenge WHERE von_raum=?",
|
||||
|
|
@ -164,14 +377,14 @@ def raum(raum_id):
|
|||
).fetchall()
|
||||
db.close()
|
||||
|
||||
html = "<h1>" + r["name"] + "</h1>"
|
||||
html += "<p>" + r["beschreibung"] + "</p>"
|
||||
html = "<h1>" + escape(r["name"]) + "</h1>"
|
||||
html += "<p>" + escape(r["beschreibung"]) + "</p>"
|
||||
for a in ausgaenge:
|
||||
html += '<a href="/app/raum/' + a["nach_raum"] + '">'
|
||||
html += a["richtung"]
|
||||
html += '<a class="btn" href="/app/raum/' + escape(a["nach_raum"]) + '">'
|
||||
html += escape(a["richtung"])
|
||||
html += "</a> "
|
||||
|
||||
return STYLE + html
|
||||
return seite_html(html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
Loading…
Reference in a new issue