U18: Lord-Business-Finale mit Polizei-Fuehrerschein-Kontrolle (Blaulicht-Intro + 3 Fahrschul-Fragen im gruenen Pruefstil)
213
app.py
|
|
@ -201,7 +201,8 @@ def adventure_seite(inhalt, akzent="#f59e0b"):
|
||||||
def start():
|
def start():
|
||||||
# Fortschritt für einen frischen Durchlauf zurücksetzen
|
# Fortschritt für einen frischen Durchlauf zurücksetzen
|
||||||
for key in [k for k in list(session.keys())
|
for key in [k for k in list(session.keys())
|
||||||
if k.startswith("raum_") or k.startswith("chal_")]:
|
if k.startswith("raum_") or k.startswith("chal_")
|
||||||
|
or k.startswith("fs40")]:
|
||||||
session.pop(key, None)
|
session.pop(key, None)
|
||||||
inhalt = """
|
inhalt = """
|
||||||
<div class="adv-card">
|
<div class="adv-card">
|
||||||
|
|
@ -408,6 +409,212 @@ def challenge_raum(db, r):
|
||||||
return adventure_seite(html, akzent="#f87171")
|
return adventure_seite(html, akzent="#f87171")
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Polizei-Führerschein-Kontrolle vor dem Lord-Business-Finale (U18) ────────
|
||||||
|
# Die drei Aufgaben stammen aus der deutschen Führerschein-Theorieprüfung.
|
||||||
|
# "richtig" = Menge der (1-basierten) anzukreuzenden Optionen. Mehrfachauswahl!
|
||||||
|
FS_FRAGEN = [
|
||||||
|
{
|
||||||
|
"frage": "Welches Verhalten ist richtig?",
|
||||||
|
"bild": "/fs_frage1.png",
|
||||||
|
"optionen": ["erst nach dem Bus abbiegen",
|
||||||
|
"vor dem Motorrad abbiegen",
|
||||||
|
"als Erster abbiegen"],
|
||||||
|
"richtig": {3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frage": "Wann kann es hier zu einer gefährlichen Situation kommen?",
|
||||||
|
"bild": "/fs_frage2.png",
|
||||||
|
"optionen": ["Wenn der Radfahrer am Ende des Radweges anhält",
|
||||||
|
"Wenn ich unvermindert weiterfahre",
|
||||||
|
"Wenn der Radfahrer auf meine Fahrbahn wechselt"],
|
||||||
|
"richtig": {2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frage": "Warum ist man nach dem Konsum der Droge „Crystal Meth“ fahruntauglich?",
|
||||||
|
"bild": None,
|
||||||
|
"optionen": ["Weil plötzliche Erschöpfung eintreten kann",
|
||||||
|
"Weil Wahnvorstellungen auftreten können",
|
||||||
|
"Weil die eigene Leistungsfähigkeit überschätzt werden kann"],
|
||||||
|
"richtig": {1, 2, 3},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
FS_IDX_KEY = "fs40_idx"
|
||||||
|
FS_DONE_KEY = "fs40_done"
|
||||||
|
|
||||||
|
|
||||||
|
def _blaulicht_intro():
|
||||||
|
"""Grelles deutsches Blaulicht + Polizei-Ansage vor der Kontrolle."""
|
||||||
|
inhalt = """
|
||||||
|
<style>
|
||||||
|
.blitz-overlay {
|
||||||
|
position:fixed; inset:0; z-index:5; pointer-events:none;
|
||||||
|
animation:blaulicht 0.9s steps(1,end) infinite;
|
||||||
|
}
|
||||||
|
@keyframes blaulicht {
|
||||||
|
0% { background:rgba(10,40,255,0.00); box-shadow:none; }
|
||||||
|
10% { background:rgba(30,90,255,0.55); box-shadow:inset 0 0 240px 80px #1e50ff; }
|
||||||
|
20% { background:rgba(10,40,255,0.00); box-shadow:none; }
|
||||||
|
50% { background:rgba(10,40,255,0.00); box-shadow:none; }
|
||||||
|
60% { background:rgba(30,90,255,0.55); box-shadow:inset 0 0 240px 80px #1e50ff; }
|
||||||
|
70% { background:rgba(10,40,255,0.00); box-shadow:none; }
|
||||||
|
100% { background:rgba(10,40,255,0.00); box-shadow:none; }
|
||||||
|
}
|
||||||
|
.polizei-card { position:relative; z-index:6; text-align:center;
|
||||||
|
border:2px solid #1e50ff !important;
|
||||||
|
box-shadow:0 0 40px rgba(30,80,255,0.6) !important; }
|
||||||
|
.polizei-siren { font-size:60px; margin-bottom:6px;
|
||||||
|
animation:sirene 0.9s ease-in-out infinite; }
|
||||||
|
@keyframes sirene {
|
||||||
|
0%,100% { filter:drop-shadow(0 0 4px #1e50ff); transform:scale(1); }
|
||||||
|
50% { filter:drop-shadow(0 0 22px #1e50ff); transform:scale(1.12); }
|
||||||
|
}
|
||||||
|
.polizei-badge { display:inline-block; background:#0b2a8a; color:#dbe5ff;
|
||||||
|
border:1px solid #1e50ff; border-radius:8px;
|
||||||
|
padding:6px 14px; font-weight:800; letter-spacing:2px;
|
||||||
|
margin-bottom:14px; }
|
||||||
|
</style>
|
||||||
|
<div class="blitz-overlay"></div>
|
||||||
|
<div class="adv-card polizei-card">
|
||||||
|
<div class="polizei-siren">🚨</div>
|
||||||
|
<span class="polizei-badge">🚔 POLIZEI · VERKEHRSKONTROLLE</span>
|
||||||
|
<h1>Anhalten – Fahrzeugkontrolle!</h1>
|
||||||
|
<p class="adv-desc">Kurz vor dem Kragle-Tresor stoppt dich eine Streife.
|
||||||
|
Die Beamten mustern deinen Führerschein misstrauisch:
|
||||||
|
<em>„Diesen Lappen haben Sie doch nie im Leben ehrlich bestanden!
|
||||||
|
Wenn er echt ist, beweisen Sie es: Lösen Sie diese drei Aufgaben
|
||||||
|
aus der Theorieprüfung – dann dürfen Sie weiter.“</em></p>
|
||||||
|
<p class="adv-desc">🚦 <strong>Achtung:</strong> Bei manchen Fragen ist
|
||||||
|
<strong>mehr als eine Antwort</strong> richtig. Kreuze <strong>alle</strong>
|
||||||
|
zutreffenden an – ein falsches Kreuz und du fällst durch.</p>
|
||||||
|
<a href="/app/raum/40?fs=1" class="exit-link">Führerschein beweisen 📝</a>
|
||||||
|
</div>"""
|
||||||
|
return adventure_seite(inhalt, akzent="#1e50ff")
|
||||||
|
|
||||||
|
|
||||||
|
def _fs_frage_screen(idx, feedback=""):
|
||||||
|
"""Eine Führerschein-Frage im (bewusst hässlichen) grünen Prüfungs-Stil."""
|
||||||
|
f = FS_FRAGEN[idx]
|
||||||
|
bild_html = ""
|
||||||
|
if f["bild"]:
|
||||||
|
bild_html = ('<div class="fs-bild"><img src="%s" alt="Verkehrssituation"></div>'
|
||||||
|
% f["bild"])
|
||||||
|
checks = ""
|
||||||
|
for i, opt in enumerate(f["optionen"], start=1):
|
||||||
|
checks += ("""
|
||||||
|
<label class="fs-opt">
|
||||||
|
<input type="checkbox" name="opt%d" value="1">
|
||||||
|
<span class="fs-box"></span>
|
||||||
|
<span class="fs-text">%s</span>
|
||||||
|
</label>""" % (i, esc(opt)))
|
||||||
|
fb = ""
|
||||||
|
if feedback:
|
||||||
|
fb = '<div class="fs-feedback">%s</div>' % esc(feedback)
|
||||||
|
inhalt = """
|
||||||
|
<style>
|
||||||
|
.fs-shell { background:#c3d64a; border:3px solid #6b8e23;
|
||||||
|
border-radius:6px; padding:0; overflow:hidden;
|
||||||
|
box-shadow:0 6px 0 #4d6b16; }
|
||||||
|
.fs-head { background:#6b8e23; color:#f2ffd0; padding:12px 18px;
|
||||||
|
display:flex; justify-content:space-between; align-items:center;
|
||||||
|
font-weight:800; letter-spacing:.5px;
|
||||||
|
border-bottom:3px solid #3f5a10; }
|
||||||
|
.fs-head .fs-nr { background:#3f5a10; color:#eaffb0; border-radius:4px;
|
||||||
|
padding:4px 10px; font-size:14px; }
|
||||||
|
.fs-body { padding:18px; color:#1e2b05; }
|
||||||
|
.fs-frage { font-size:20px; font-weight:800; color:#243a06;
|
||||||
|
margin-bottom:14px; }
|
||||||
|
.fs-bild { margin:0 0 16px; border:3px solid #6b8e23; border-radius:4px;
|
||||||
|
overflow:hidden; background:#000; }
|
||||||
|
.fs-bild img { display:block; width:100%; height:auto; }
|
||||||
|
.fs-opts { display:flex; flex-direction:column; gap:10px; }
|
||||||
|
.fs-opt { display:flex; align-items:flex-start; gap:12px;
|
||||||
|
background:#e4f09a; border:2px solid #7c9a2e; border-radius:4px;
|
||||||
|
padding:12px 14px; cursor:pointer; transition:.12s;
|
||||||
|
font-size:16px; color:#243a06; }
|
||||||
|
.fs-opt:hover { background:#eef7b6; border-color:#4d6b16; }
|
||||||
|
.fs-opt input { position:absolute; opacity:0; width:0; height:0; }
|
||||||
|
.fs-box { width:22px; height:22px; flex-shrink:0; background:#fff;
|
||||||
|
border:2px solid #4d6b16; border-radius:3px; position:relative;
|
||||||
|
margin-top:1px; }
|
||||||
|
.fs-opt input:checked + .fs-box { background:#3f5a10; }
|
||||||
|
.fs-opt input:checked + .fs-box::after { content:"✔"; color:#d4ff5a;
|
||||||
|
position:absolute; top:-3px; left:2px; font-size:18px;
|
||||||
|
font-weight:900; }
|
||||||
|
.fs-btn { margin-top:18px; width:100%; background:#3f5a10; color:#eaffb0;
|
||||||
|
border:none; border-bottom:4px solid #263806; border-radius:5px;
|
||||||
|
padding:14px; font-size:17px; font-weight:800; cursor:pointer;
|
||||||
|
font-family:inherit; letter-spacing:.5px; }
|
||||||
|
.fs-btn:hover { background:#4d6b16; }
|
||||||
|
.fs-feedback { background:#a11c1c; color:#ffe0e0; border:2px solid #7a0f0f;
|
||||||
|
border-radius:4px; padding:10px 14px; margin-bottom:14px;
|
||||||
|
font-weight:700; }
|
||||||
|
.fs-hint { margin-top:12px; font-size:13px; color:#3f5a10; text-align:center; }
|
||||||
|
</style>
|
||||||
|
<div class="adv-card" style="background:transparent;border:none;padding:0;box-shadow:none">
|
||||||
|
<div class="fs-shell">
|
||||||
|
<div class="fs-head">
|
||||||
|
<span>🚔 THEORIEPRÜFUNG · POLIZEIKONTROLLE</span>
|
||||||
|
<span class="fs-nr">Frage __NR__ / __GES__</span>
|
||||||
|
</div>
|
||||||
|
<div class="fs-body">
|
||||||
|
__FB__
|
||||||
|
<p class="fs-frage">__FRAGE__</p>
|
||||||
|
__BILD__
|
||||||
|
<form method="post">
|
||||||
|
<div class="fs-opts">__CHECKS__</div>
|
||||||
|
<button type="submit" class="fs-btn">Antwort abgeben ▶</button>
|
||||||
|
</form>
|
||||||
|
<p class="fs-hint">Mehrfachauswahl möglich – kreuze alle richtigen Antworten an.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
html = (inhalt
|
||||||
|
.replace("__NR__", str(idx + 1))
|
||||||
|
.replace("__GES__", str(len(FS_FRAGEN)))
|
||||||
|
.replace("__FB__", fb)
|
||||||
|
.replace("__FRAGE__", esc(f["frage"]))
|
||||||
|
.replace("__BILD__", bild_html)
|
||||||
|
.replace("__CHECKS__", checks))
|
||||||
|
return adventure_seite(html, akzent="#6b8e23")
|
||||||
|
|
||||||
|
|
||||||
|
def fuehrerschein_kontrolle():
|
||||||
|
"""Steuert die dreiteilige Polizei-Führerschein-Kontrolle vor dem Finale."""
|
||||||
|
# Noch nicht gestartet → Blaulicht-Intro (oder Start per ?fs=1)
|
||||||
|
if FS_IDX_KEY not in session:
|
||||||
|
if request.args.get("fs") == "1":
|
||||||
|
session[FS_IDX_KEY] = 0
|
||||||
|
session.modified = True
|
||||||
|
return redirect("/app/raum/40")
|
||||||
|
return _blaulicht_intro()
|
||||||
|
|
||||||
|
idx = session.get(FS_IDX_KEY, 0)
|
||||||
|
feedback = ""
|
||||||
|
if request.method == "POST" and idx < len(FS_FRAGEN):
|
||||||
|
gewaehlt = set()
|
||||||
|
for i in range(1, len(FS_FRAGEN[idx]["optionen"]) + 1):
|
||||||
|
if request.form.get("opt%d" % i):
|
||||||
|
gewaehlt.add(i)
|
||||||
|
if gewaehlt == FS_FRAGEN[idx]["richtig"]:
|
||||||
|
idx += 1
|
||||||
|
session[FS_IDX_KEY] = idx
|
||||||
|
session.modified = True
|
||||||
|
elif not gewaehlt:
|
||||||
|
feedback = ("🚨 Kein Kreuz gesetzt! „So kommen Sie hier nicht durch.“ "
|
||||||
|
"Kreuze alle richtigen Antworten an.")
|
||||||
|
else:
|
||||||
|
feedback = ("🚨 Durchgefallen bei dieser Aufgabe! Die Beamten schütteln "
|
||||||
|
"den Kopf: „Nochmal – und diesmal richtig.“")
|
||||||
|
|
||||||
|
if idx >= len(FS_FRAGEN):
|
||||||
|
session[FS_DONE_KEY] = True
|
||||||
|
session.modified = True
|
||||||
|
return redirect("/app/raum/40")
|
||||||
|
|
||||||
|
return _fs_frage_screen(idx, feedback)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/raum/<int:id>", methods=["GET", "POST"])
|
@app.route("/raum/<int:id>", methods=["GET", "POST"])
|
||||||
def raum(id):
|
def raum(id):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
@ -428,6 +635,10 @@ def raum(id):
|
||||||
|
|
||||||
# ═══ Räume mit Fragen: thema (Prüf-Weg) · ziel (Finale) ═══
|
# ═══ Räume mit Fragen: thema (Prüf-Weg) · ziel (Finale) ═══
|
||||||
if typ in ("thema", "ziel"):
|
if typ in ("thema", "ziel"):
|
||||||
|
# ── Polizei-Führerschein-Kontrolle vor dem Lord-Business-Finale ──
|
||||||
|
if typ == "ziel" and id == 40 and not session.get(FS_DONE_KEY):
|
||||||
|
db.close()
|
||||||
|
return fuehrerschein_kontrolle()
|
||||||
fragen = db.execute(
|
fragen = db.execute(
|
||||||
"SELECT * FROM fragen WHERE raum_id=? ORDER BY id", (id,)
|
"SELECT * FROM fragen WHERE raum_id=? ORDER BY id", (id,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
|
||||||
BIN
fs_frage1.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
fs_frage2.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
fuhrerschein1 3.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
führerschein1 frame.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
führerschein2 23.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
führerschein2 frame.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
führerschein3 123.png
Normal file
|
After Width: | Height: | Size: 413 KiB |