feat: Add lore pack, UI adjustments, requirements.txt and INSTALL.md
This commit is contained in:
+89
@@ -0,0 +1,89 @@
|
||||
# Magisches Harry Potter Escape Quiz Portal
|
||||
|
||||
Interaktives Harry Potter Escape Quiz Portal (FastAPI & MariaDB).
|
||||
|
||||
## Systemanforderungen
|
||||
- Debian 12 / 13 (oder kompatibles Linux)
|
||||
- Python 3.13+ mit `python3-venv`
|
||||
- Nginx (Reverse Proxy mit SSL)
|
||||
- MariaDB Server auf \`db.waiswuaschd.de:3306\` (Datenbank: \`v2_db_quiz\`)
|
||||
|
||||
## 1. Installation
|
||||
```bash
|
||||
# Repository klonen
|
||||
cd /home/maxi/Projekt
|
||||
git clone https://gitea.sp13l.de/kobold-ai/quiz.git quiz.waiswuaschd.de
|
||||
cd quiz.waiswuaschd.de
|
||||
|
||||
# Python Virtualenv erstellen und Abh?ngigkeiten installieren
|
||||
python3 -m venv venv
|
||||
./venv/bin/pip install --upgrade pip
|
||||
./venv/bin/pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 2. Umgebungsvariablen (.env)
|
||||
Erstelle im Projektverzeichnis eine `.env` Datei:
|
||||
```ini
|
||||
DB_HOST=db.waiswuaschd.de
|
||||
DB_PORT=3306
|
||||
DB_NAME=v2_db_quiz
|
||||
DB_USER=v2_usr_quiz
|
||||
DB_PASS=Quiz_Pass_2026_Secure!
|
||||
SECRET_KEY=hogwarts_magic_super_secret_key_2026_dev_quiz
|
||||
ADMIN_USER=admin
|
||||
ADMIN_PASS=Alohomora2026!
|
||||
PORT=8001
|
||||
```
|
||||
|
||||
## 3. Systemd Service (/etc/systemd/system/quiz.service)
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Magisches Quiz Portal (FastAPI)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=maxi
|
||||
Group=maxi
|
||||
WorkingDirectory=/home/maxi/Projekt/quiz.waiswuaschd.de
|
||||
ExecStart=/home/maxi/Projekt/quiz.waiswuaschd.de/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8001 --workers 1
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
Aktivieren und starten:
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now quiz
|
||||
```
|
||||
|
||||
## 4. Nginx Reverse Proxy
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name quiz.dev.waiswuaschd.de;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name quiz.dev.waiswuaschd.de;
|
||||
|
||||
# SSL Zertifikate hier konfigurieren (z. B. via certbot)
|
||||
|
||||
location /static/ {
|
||||
alias /home/maxi/Projekt/quiz.waiswuaschd.de/app/static/;
|
||||
add_header Cache-Control "public, max-age=3600, must-revalidate";
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
+26
-5
@@ -20,7 +20,7 @@ from app.schemas import GameCheckRequest, SuggestionCreateRequest, GameStartRequ
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = FastAPI(title="Magisches Harry-Potter-Escape-Quiz")
|
||||
app = FastAPI(title="PotterTrivia – Das Harry-Potter-Wissensquiz")
|
||||
|
||||
SECRET_KEY = os.getenv("SECRET_KEY", "hogwarts_secret_default_key_2026")
|
||||
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, session_cookie="quiz_session")
|
||||
@@ -154,16 +154,37 @@ async def index(request: Request, db: Session = Depends(get_db)):
|
||||
context={"question_count": active_count}
|
||||
)
|
||||
|
||||
VALID_CATEGORIES = ["all", "books", "movies", "creatures", "spells", "characters", "hogwarts", "quidditch", "lore", "general"]
|
||||
|
||||
@app.post("/api/game/start")
|
||||
async def start_game(payload: GameStartRequest = None, db: Session = Depends(get_db)):
|
||||
lang = payload.lang if payload and payload.lang in ["de", "en"] else "de"
|
||||
difficulty = payload.difficulty if payload and payload.difficulty in ["easy", "medium", "hard"] else "all"
|
||||
category = payload.category if payload and payload.category in VALID_CATEGORIES else "all"
|
||||
|
||||
base_query = db.query(Question).filter(Question.status == "active")
|
||||
if category != "all":
|
||||
if category == "books":
|
||||
cat_query = base_query.filter((Question.category == "books") | (Question.question.like("%Kröter%")))
|
||||
else:
|
||||
cat_query = base_query.filter(Question.category == category)
|
||||
else:
|
||||
cat_query = base_query
|
||||
|
||||
query = db.query(Question).filter(Question.status == "active")
|
||||
if difficulty != "all":
|
||||
query = query.filter(Question.difficulty == difficulty)
|
||||
strict_pool = cat_query.filter(Question.difficulty == difficulty).all()
|
||||
if len(strict_pool) >= 10 or category == "all":
|
||||
pool = strict_pool
|
||||
else:
|
||||
# Keep all difficulty matches in this category and top up from the same category
|
||||
cat_all = cat_query.all()
|
||||
strict_ids = {q.id for q in strict_pool}
|
||||
extra = [q for q in cat_all if q.id not in strict_ids]
|
||||
random.shuffle(extra)
|
||||
pool = strict_pool + extra[:max(0, 10 - len(strict_pool))]
|
||||
else:
|
||||
pool = cat_query.all()
|
||||
|
||||
pool = query.all()
|
||||
if not pool:
|
||||
pool = db.query(Question).filter(Question.status == "active").all()
|
||||
if not pool:
|
||||
@@ -538,7 +559,7 @@ async def submit_suggestion(payload: SuggestionCreateRequest, db: Session = Depe
|
||||
opts.append(opt.strip())
|
||||
options_json = json.dumps(opts, ensure_ascii=False)
|
||||
|
||||
chosen_cat = payload.category if payload.category in ["hogwarts", "characters", "spells", "creatures", "quidditch", "lore", "general"] else "general"
|
||||
chosen_cat = payload.category if payload.category in ["books", "movies", "hogwarts", "characters", "spells", "creatures", "quidditch", "lore", "general"] else "general"
|
||||
diff = payload.difficulty if payload.difficulty in ["easy", "medium", "hard"] else "medium"
|
||||
|
||||
new_suggestion = Question(
|
||||
|
||||
@@ -115,4 +115,22 @@ def is_answer_correct(user_input: str, canonical: str, accepted_raw: str | None
|
||||
if dist <= max_allowed:
|
||||
return True
|
||||
|
||||
# Support compound answers connected by 'und' / 'and' (e.g. "Mantikor und Feuerkrabbe")
|
||||
for sep in (" und ", " and "):
|
||||
if sep in target:
|
||||
parts = [p.strip() for p in target.split(sep) if len(p.strip()) >= 3]
|
||||
if len(parts) == 2:
|
||||
u_words = u.split()
|
||||
matched_both = True
|
||||
for part in parts:
|
||||
if part in u:
|
||||
continue
|
||||
# Check fuzzy match against tokens or reversed order
|
||||
part_matched = any(levenshtein_distance(w, part) <= (1 if len(part) <= 6 else 2) for w in u_words)
|
||||
if not part_matched:
|
||||
matched_both = False
|
||||
break
|
||||
if matched_both:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Optional, List
|
||||
class GameStartRequest(BaseModel):
|
||||
lang: Optional[str] = "de"
|
||||
difficulty: Optional[str] = "all"
|
||||
category: Optional[str] = "all"
|
||||
recent_ids: Optional[List[int]] = None
|
||||
wrong_ids: Optional[List[int]] = None
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from app.database import engine, SessionLocal, Base
|
||||
from app.models import Question, AdminUser
|
||||
from app.auth import hash_password
|
||||
from app.seed_40 import seed_40_questions
|
||||
from app.seed_lore_pack import seed_lore_pack
|
||||
|
||||
def seed_db():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
@@ -29,6 +30,9 @@ def seed_db():
|
||||
print("Question table is empty, seeding 40 initial bilingual questions...")
|
||||
seed_40_questions()
|
||||
|
||||
# 3. Ensure Lore Pack (Books, Movies, Hard Creatures incl. Blast-Ended Skrewts) is present
|
||||
seed_lore_pack()
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@@ -0,0 +1,722 @@
|
||||
import json
|
||||
from app.database import SessionLocal
|
||||
from app.models import Question
|
||||
|
||||
LORE_PACK_QUESTIONS = [
|
||||
# --- CREATURES (Magische Geschöpfe) ---
|
||||
{
|
||||
"category": "creatures",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Aus welcher Kreuzung zweier magischer Geschöpfe züchtete Hagrid die Knallrümpfigen Kröter?",
|
||||
"canonical_answer": "Mantikor und Feuerkrabbe",
|
||||
"accepted_answers": json.dumps([
|
||||
"Mantikor und Feuerkrabbe",
|
||||
"Feuerkrabbe und Mantikor",
|
||||
"Mantikor Feuerkrabbe",
|
||||
"Feuerkrabbe Mantikor",
|
||||
"Manticore and Fire Crab",
|
||||
"Fire Crab and Manticore"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Eines ist ein hochgefährliches griechisches Ungeheuer mit Skorpionschwanz, das andere ein schildkrötenähnliches Wesen von den Fidschi-Inseln.",
|
||||
"question_en": "Which two magical creatures did Hagrid crossbreed to create the Blast-Ended Skrewts?",
|
||||
"answer_en": "Manticore and Fire Crab",
|
||||
"hint_en": "One is a deadly Greek beast with a scorpion tail, the other is a turtle-like creature native to Fiji."
|
||||
},
|
||||
{
|
||||
"category": "creatures",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welcher der drei Köpfe der dreiköpfigen Schlange 'Runespoor' ist laut Newt Scamander derjenige, der Pläne schmiedet?",
|
||||
"canonical_answer": "Der linke Kopf",
|
||||
"options": json.dumps(["Der linke Kopf", "Der mittlere Kopf", "Der rechte Kopf", "Alle drei gleichzeitig"], ensure_ascii=False),
|
||||
"hint": "Der mittlere Kopf ist der Träumer und der rechte Kopf der Kritiker.",
|
||||
"question_en": "According to Newt Scamander, which of the three heads of the Runespoor snake is the planner?",
|
||||
"answer_en": "The left head",
|
||||
"options_en": json.dumps(["The left head", "The middle head", "The right head", "All three simultaneously"], ensure_ascii=False),
|
||||
"hint_en": "The middle head is the dreamer and the right head is the critic."
|
||||
},
|
||||
{
|
||||
"category": "creatures",
|
||||
"difficulty": "medium",
|
||||
"question_type": "text",
|
||||
"question": "Wie heißt die riesige Spinne (Acromantula), die Hagrid schon während seiner Schulzeit in einem Schrank in Hogwarts versteckte?",
|
||||
"canonical_answer": "Aragog",
|
||||
"accepted_answers": json.dumps(["Aragog"], ensure_ascii=False),
|
||||
"hint": "Sein Name beginnt mit 'A' und er lebt später mit seiner großen Kolonie im Verbotenen Wald.",
|
||||
"question_en": "What is the name of the giant Acromantula spider that Hagrid hid in a cupboard at Hogwarts during his school days?",
|
||||
"answer_en": "Aragog",
|
||||
"hint_en": "His name starts with 'A' and he later lives with his large colony in the Forbidden Forest."
|
||||
},
|
||||
{
|
||||
"category": "creatures",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welche Gefahrenklasse (Ministeriumsklassifizierung) hat ein ausgewachsener Nundu laut 'Phantastische Tierwesen'?",
|
||||
"canonical_answer": "XXXXX (Bekannte Zauberertöter)",
|
||||
"options": json.dumps([
|
||||
"XXXXX (Bekannte Zauberertöter)",
|
||||
"XXXX (Gefährlich / erfordert Fachwissen)",
|
||||
"XXX (Für fähige Zauberer kein Problem)",
|
||||
"XX (Harmlos / kann gezähmt werden)"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Es braucht mindestens hundert zusammenarbeitende Zauberer, um einen einzigen Nundu zu überwältigen.",
|
||||
"question_en": "What Ministry of Magic classification does an adult Nundu hold according to 'Fantastic Beasts'?",
|
||||
"answer_en": "XXXXX (Known wizard killer)",
|
||||
"options_en": json.dumps([
|
||||
"XXXXX (Known wizard killer)",
|
||||
"XXXX (Dangerous / requires specialist knowledge)",
|
||||
"XXX (Competent wizard should cope)",
|
||||
"XX (Harmless / may be domesticated)"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "It takes at least a hundred skilled wizards working together to subdue a single Nundu."
|
||||
},
|
||||
{
|
||||
"category": "creatures",
|
||||
"difficulty": "easy",
|
||||
"question_type": "text",
|
||||
"question": "Welches kleine, pelzige Tierwesen wird magisch von allem Glitzernden und von Goldmünzen angezogen?",
|
||||
"canonical_answer": "Niffler",
|
||||
"accepted_answers": json.dumps(["Niffler", "Ein Niffler"], ensure_ascii=False),
|
||||
"hint": "Newt Scamander hat eines dieser langnasigen Beuteltiere in seinem Koffer.",
|
||||
"question_en": "Which small, furry magical creature is irresistibly drawn to anything shiny and gold coins?",
|
||||
"answer_en": "Niffler",
|
||||
"hint_en": "Newt Scamander keeps one of these long-snouted burrowing creatures in his case."
|
||||
},
|
||||
{
|
||||
"category": "creatures",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wie nennt man das unsichtbare geflügelte Pferd, das nur von Menschen gesehen werden kann, die den Tod bereits bewusst miterlebt haben?",
|
||||
"canonical_answer": "Thestral",
|
||||
"options": json.dumps(["Thestral", "Hippogreif", "Abraxaner", "Kelpie"], ensure_ascii=False),
|
||||
"hint": "Sie ziehen die Kutschen vom Bahnhof Hogsmeade zum Schloss Hogwarts.",
|
||||
"question_en": "What is the name of the invisible winged horse that can only be seen by those who have witnessed death?",
|
||||
"answer_en": "Thestral",
|
||||
"options_en": json.dumps(["Thestral", "Hippogriff", "Abraxan", "Kelpie"], ensure_ascii=False),
|
||||
"hint_en": "They pull the carriages from Hogsmeade station up to Hogwarts Castle."
|
||||
},
|
||||
|
||||
# --- BOOKS (Buchwissen) ---
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Wie lautet das vollständige Akronym der Organisation, die Hermine Granger im 4. Buch zur Befreiung der Hauselfen gründet?",
|
||||
"canonical_answer": "B.ELFE.R.",
|
||||
"accepted_answers": json.dumps(["B.ELFE.R.", "BELFER", "B.ELFE.R", "S.P.E.W.", "SPEW", "Bund für Elfenrechte"]),
|
||||
"hint": "Es steht für 'Bund für Elfenrechte' (im Englischen: S.P.E.W.).",
|
||||
"question_en": "What is the acronym of the organization Hermione Granger founds in her fourth year to campaign for house-elf rights?",
|
||||
"answer_en": "S.P.E.W.",
|
||||
"hint_en": "It stands for 'Society for the Promotion of Elfish Welfare' (in German: B.ELFE.R.)."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wer schenkt Harry im 4. Buch (im Gegensatz zum Film!) das Kraut 'Dianthuskraut' (Gillyweed) für die zweite Aufgabe des Trimagischen Turniers?",
|
||||
"canonical_answer": "Dobby",
|
||||
"options": json.dumps(["Dobby", "Neville Longbottom", "Alastor Moody", "Cedric Diggory"], ensure_ascii=False),
|
||||
"hint": "Er stiehlt es aus Professor Snapes Vorratskammer, nachdem er ein inszeniertes Gespräch von Barty Crouch Jr. belauscht hat.",
|
||||
"question_en": "In the Goblet of Fire book (unlike the movie!), who actually gives Harry the Gillyweed before the Second Task?",
|
||||
"answer_en": "Dobby",
|
||||
"options_en": json.dumps(["Dobby", "Neville Longbottom", "Alastor Moody", "Cedric Diggory"], ensure_ascii=False),
|
||||
"hint_en": "He steals it from Professor Snape's private stores after overhearing a staged conversation by Barty Crouch Jr."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Wie heißt der Poltergeist von Hogwarts, der in allen sieben Büchern für Chaos sorgt, aber aus den Verfilmungen komplett gestrichen wurde?",
|
||||
"canonical_answer": "Peeves",
|
||||
"accepted_answers": json.dumps(["Peeves", "Poltergeist Peeves"]),
|
||||
"hint": "Nur der Blutige Baron hat ihn wirklich unter Kontrolle.",
|
||||
"question_en": "What is the name of the Hogwarts poltergeist who causes chaos throughout all seven books but was completely cut from the films?",
|
||||
"answer_en": "Peeves",
|
||||
"hint_en": "Only the Bloody Baron can truly keep him in line."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welches Rätsel stellt Professor Snape in Band 1 als Schutz vor dem Stein der Weisen auf, das im ersten Kinofilm komplett fehlt?",
|
||||
"canonical_answer": "Ein Logikrätsel mit sieben Zaubertrank-Flaschen",
|
||||
"options": json.dumps([
|
||||
"Ein Logikrätsel mit sieben Zaubertrank-Flaschen",
|
||||
"Ein Duell gegen eine verzauberte Rüstung",
|
||||
"Ein Schachbrett aus flüssigem Quecksilber",
|
||||
"Eine Tür, die nur auf Parsel reagiert"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Hermine löst es mit reiner Logik, während ein schwarzes und ein violettes Feuer die Ausgänge blockieren.",
|
||||
"question_en": "Which obstacle guarding the Philosopher's Stone was created by Professor Snape in the first book, but omitted from the film?",
|
||||
"answer_en": "A logic puzzle with seven potion bottles",
|
||||
"options_en": json.dumps([
|
||||
"A logic puzzle with seven potion bottles",
|
||||
"A duel against an enchanted suit of armor",
|
||||
"A chessboard made of liquid mercury",
|
||||
"A door that only opens to Parseltongue"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "Hermione solves it using pure logic while black and purple flames block the exits."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Wie heißt die Hauselfe der Familie Barty Crouch, die beim Quidditch-Weltpokal mit Harrys Zauberstab in der Hand gefunden wird?",
|
||||
"canonical_answer": "Winky",
|
||||
"accepted_answers": json.dumps(["Winky", "Hauselfe Winky"]),
|
||||
"hint": "Ihr Name beginnt mit 'W' und sie verfällt nach ihrer Entlassung dem Butterbier.",
|
||||
"question_en": "What is the name of the Crouch family's female house-elf who is found holding Harry's wand at the Quidditch World Cup?",
|
||||
"answer_en": "Winky",
|
||||
"hint_en": "Her name starts with 'W' and she later develops a Butterbeer habit in the Hogwarts kitchens."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wie heißt Tom Riddles Mutter mit vollem Geburtsnamen, deren tragische Geschichte Harry in Dumbledores Denkarium im 6. Buch erfährt?",
|
||||
"canonical_answer": "Merope Gaunt",
|
||||
"options": json.dumps(["Merope Gaunt", "Morfin Gaunt", "Kendra Dumbledore", "Eileen Prince"], ensure_ascii=False),
|
||||
"hint": "Sie war eine Nachfahrin von Salazar Slytherin und verliebte sich in den Muggel Tom Riddle Senior.",
|
||||
"question_en": "What was the maiden name of Tom Riddle's mother, whose tragic story Harry learns in Dumbledore's Pensieve in book 6?",
|
||||
"answer_en": "Merope Gaunt",
|
||||
"options_en": json.dumps(["Merope Gaunt", "Morfin Gaunt", "Kendra Dumbledore", "Eileen Prince"], ensure_ascii=False),
|
||||
"hint_en": "She was a direct descendant of Salazar Slytherin and fell in love with the Muggle Tom Riddle Sr."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Auf welcher Krankenstation im St.-Mungo-Hospital begegnen Harry, Ron und Hermine im 5. Buch zufällig Gilderoy Lockhart wieder?",
|
||||
"canonical_answer": "Janus-Thickey-Station (Langzeit-Fluchschäden)",
|
||||
"options": json.dumps([
|
||||
"Janus-Thickey-Station (Langzeit-Fluchschäden)",
|
||||
"Dai-Llewellyn-Station (Tierbisse)",
|
||||
"Artefakt-Unfälle im Erdgeschoss",
|
||||
"Teekessel-Verbrennungsstation"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Auf derselben geschlossenen Station im 4. Stock liegen auch Frank und Alice Longbottom.",
|
||||
"question_en": "On which ward of St Mungo's Hospital do Harry, Ron, and Hermione bump into Gilderoy Lockhart in the fifth book?",
|
||||
"answer_en": "Janus Thickey Ward (Long-Term Spell Damage)",
|
||||
"options_en": json.dumps([
|
||||
"Janus Thickey Ward (Long-Term Spell Damage)",
|
||||
"Dai Llewellyn Ward (Creature Bites)",
|
||||
"Artifact Accidents on the Ground Floor",
|
||||
"Potion and Plant Poisoning Ward"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "Frank and Alice Longbottom are patients on the very same fourth-floor ward."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Welchen Beruf übt Charlie Weasley in Rumänien aus?",
|
||||
"canonical_answer": "Drachenwärter",
|
||||
"accepted_answers": json.dumps([
|
||||
"Drachenwärter", "Drachenforscher", "Drachenzüchter", "Drachenpfleger",
|
||||
"Er arbeitet mit Drachen", "Drachen", "Dragonologist", "Dragon keeper"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Er nimmt im 1. Buch den kleinen Norwegischen Stachelbuckel Norbert in seine Obhut.",
|
||||
"question_en": "What is Charlie Weasley's profession in Romania?",
|
||||
"answer_en": "Dragonkeeper",
|
||||
"hint_en": "In the first book, his friends fly down to take Hagrid's Norwegian Ridgeback Norbert into their care."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Was tut Harry im Buch 'Die Heiligtümer des Todes' mit dem Elderstab, bevor er ihn an seinen endgültigen Ort zurückbringt?",
|
||||
"canonical_answer": "Er repariert seinen eigenen zerbrochenen Zauberstab",
|
||||
"options": json.dumps([
|
||||
"Er repariert seinen eigenen zerbrochenen Zauberstab",
|
||||
"Er zerbricht ihn und wirft ihn von der Brücke",
|
||||
"Er schenkt ihn Ron Weasley",
|
||||
"Er übergibt ihn dem Zaubereiministerium"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Mit 'Reparo' gelingt ihm etwas, was Ollivander zuvor für unmöglich gehalten hatte.",
|
||||
"question_en": "In the Deathly Hallows book, what does Harry do with the Elder Wand before returning it to Dumbledore's tomb?",
|
||||
"answer_en": "He repairs his own broken holly and phoenix wand",
|
||||
"options_en": json.dumps([
|
||||
"He repairs his own broken holly and phoenix wand",
|
||||
"He snaps it in half and throws it off the bridge",
|
||||
"He gifts it to Ron Weasley",
|
||||
"He locks it inside Gringotts"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "Using 'Reparo', he accomplishes something Ollivander thought was impossible."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wie heißt der Kobold, der im 4. Buch von Ludo Bagman mit Leprechaun-Gold betrogen wird?",
|
||||
"canonical_answer": "Gornuk (bzw. Ragnok / Griphooks Kollegen)",
|
||||
"options": json.dumps(["Ragnok", "Bogrod", "Travers", "Yaxley"], ensure_ascii=False),
|
||||
"hint": "Er trifft sich im Eberkopf mit Ludo Bagman, um seine Schulden einzufordern.",
|
||||
"question_en": "Which goblin confronts Ludo Bagman in the Hog's Head inn after being paid in vanishing Leprechaun gold?",
|
||||
"answer_en": "Ragnok",
|
||||
"options_en": json.dumps(["Ragnok", "Bogrod", "Travers", "Yaxley"], ensure_ascii=False),
|
||||
"hint_en": "He meets Ludo Bagman in the Hog's Head to demand repayment of his bet."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "medium",
|
||||
"question_type": "text",
|
||||
"question": "Welches magische Radio-Programm hören Ron, Harry und Hermine im 7. Buch auf der Flucht, für das man ein wechselndes Passwort braucht?",
|
||||
"canonical_answer": "PotterWatch",
|
||||
"accepted_answers": json.dumps(["PotterWatch", "Potter Watch", "Potterwatch"]),
|
||||
"hint": "Moderiert wird die Sendung von Lee Jordan unter dem Decknamen 'Fluss' (River).",
|
||||
"question_en": "What is the name of the secret rebel radio broadcast that Harry, Ron, and Hermione listen to while on the run in book 7?",
|
||||
"answer_en": "Potterwatch",
|
||||
"hint_en": "The program is hosted by Lee Jordan under the codename 'River'."
|
||||
},
|
||||
{
|
||||
"category": "books",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wer war die ursprüngliche Besitzerin des Diadems von Ravenclaw und zugleich deren Tochter (die Graue Dame)?",
|
||||
"canonical_answer": "Helena Ravenclaw",
|
||||
"options": json.dumps(["Helena Ravenclaw", "Rowena Ravenclaw", "Helga Hufflepuff", "Millicent Bagnold"], ensure_ascii=False),
|
||||
"hint": "Sie stahl das Diadem ihrer Mutter und versteckte es in einem hohlen Baum in Albanien.",
|
||||
"question_en": "What is the real name of the Grey Lady, the ghost of Ravenclaw Tower and daughter of Rowena Ravenclaw?",
|
||||
"answer_en": "Helena Ravenclaw",
|
||||
"options_en": json.dumps(["Helena Ravenclaw", "Rowena Ravenclaw", "Helga Hufflepuff", "Millicent Bagnold"], ensure_ascii=False),
|
||||
"hint_en": "She stole her mother's diadem and hid it inside a hollow tree in Albania."
|
||||
},
|
||||
|
||||
# --- MOVIES (Filmwissen) ---
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "easy",
|
||||
"question_type": "text",
|
||||
"question": "Welcher britische Schauspieler verkörperte Harry Potter in allen acht Kinofilmen?",
|
||||
"canonical_answer": "Daniel Radcliffe",
|
||||
"accepted_answers": json.dumps(["Daniel Radcliffe", "Radcliffe"]),
|
||||
"hint": "Er war bei den Dreharbeiten zum ersten Film erst elf Jahre alt.",
|
||||
"question_en": "Which British actor portrayed Harry Potter across all eight feature films?",
|
||||
"answer_en": "Daniel Radcliffe",
|
||||
"hint_en": "He was only eleven years old when filming began on the first movie."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welcher Regisseur inszenierte den dritten Kinofilm 'Harry Potter und der Gefangene von Askaban' (2004)?",
|
||||
"canonical_answer": "Alfonso Cuarón",
|
||||
"options": json.dumps(["Alfonso Cuarón", "Chris Columbus", "David Yates", "Mike Newell"], ensure_ascii=False),
|
||||
"hint": "Der mexikanische Regisseur prägte den düsteren, künstlerischen Look des dritten Teils.",
|
||||
"question_en": "Which director helmed the third movie, 'Harry Potter and the Prisoner of Azkaban' (2004)?",
|
||||
"answer_en": "Alfonso Cuarón",
|
||||
"options_en": json.dumps(["Alfonso Cuarón", "Chris Columbus", "David Yates", "Mike Newell"], ensure_ascii=False),
|
||||
"hint_en": "The Mexican filmmaker gave the third installment its signature darker, atmospheric visual style."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "medium",
|
||||
"question_type": "text",
|
||||
"question": "Welcher weltberühmte Filmkomponist schrieb das ikonische Hauptthema 'Hedwig's Theme' für die ersten Harry-Potter-Filme?",
|
||||
"canonical_answer": "John Williams",
|
||||
"accepted_answers": json.dumps(["John Williams", "Williams"]),
|
||||
"hint": "Er komponierte auch die Musik für Star Wars, Indiana Jones und Jurassic Park.",
|
||||
"question_en": "Which legendary film composer wrote the iconic main theme 'Hedwig's Theme' for the first Harry Potter movies?",
|
||||
"answer_en": "John Williams",
|
||||
"hint_en": "He also composed the scores for Star Wars, Indiana Jones, and Jurassic Park."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welcher Schauspieler übernahm ab dem dritten Film die Rolle von Albus Dumbledore, nachdem Richard Harris verstorben war?",
|
||||
"canonical_answer": "Michael Gambon",
|
||||
"options": json.dumps(["Michael Gambon", "Ian McKellen", "Gary Oldman", "Ralph Fiennes"], ensure_ascii=False),
|
||||
"hint": "Er spielte Dumbledore vom dritten bis zum achten Film.",
|
||||
"question_en": "Which actor took over the role of Albus Dumbledore starting in the third film after Richard Harris passed away?",
|
||||
"answer_en": "Michael Gambon",
|
||||
"options_en": json.dumps(["Michael Gambon", "Ian McKellen", "Gary Oldman", "Ralph Fiennes"], ensure_ascii=False),
|
||||
"hint_en": "He portrayed Dumbledore from Prisoner of Azkaban through Deathly Hallows – Part 2."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "easy",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Über welches berühmte schottische Eisenbahnviadukt fährt der Hogwarts-Express in den Filmen?",
|
||||
"canonical_answer": "Glenfinnan-Viadukt",
|
||||
"options": json.dumps(["Glenfinnan-Viadukt", "Ribblehead-Viadukt", "Clifton-Brücke", "Forth Bridge"], ensure_ascii=False),
|
||||
"hint": "Es liegt in den schottischen Highlands am Loch Shiel.",
|
||||
"question_en": "Which famous Scottish railway viaduct does the Hogwarts Express cross in the movies?",
|
||||
"answer_en": "Glenfinnan Viaduct",
|
||||
"options_en": json.dumps(["Glenfinnan Viaduct", "Ribblehead Viaduct", "Clifton Bridge", "Forth Bridge"], ensure_ascii=False),
|
||||
"hint_en": "It is located in the Scottish Highlands overlooking Loch Shiel."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welche Augenfarbe hat Harry Potter in den Kinofilmen (weil Daniel Radcliffe die farbigen Kontaktlinsen nicht vertrug), obwohl sie in den Büchern leuchtend grün sind?",
|
||||
"canonical_answer": "Blau",
|
||||
"options": json.dumps(["Blau", "Braun", "Grau", "Bernsteinfarben"], ensure_ascii=False),
|
||||
"hint": "Daniel Radcliffes natürliche Augenfarbe.",
|
||||
"question_en": "What color are Harry Potter's eyes in the movies (because Daniel Radcliffe had an allergic reaction to contact lenses), even though they are bright green in the books?",
|
||||
"answer_en": "Blue",
|
||||
"options_en": json.dumps(["Blue", "Brown", "Hazel", "Amber"], ensure_ascii=False),
|
||||
"hint_en": "It is Daniel Radcliffe's natural eye color."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Welcher unvergessene britische Schauspieler verkörperte Severus Snape in allen acht Filmen?",
|
||||
"canonical_answer": "Alan Rickman",
|
||||
"accepted_answers": json.dumps(["Alan Rickman", "Rickman"]),
|
||||
"hint": "J.K. Rowling verriet ihm schon vor dem Erscheinen des letzten Buches das Geheimnis hinter Snapes 'Always'.",
|
||||
"question_en": "Which beloved British actor portrayed Severus Snape across all eight Harry Potter films?",
|
||||
"answer_en": "Alan Rickman",
|
||||
"hint_en": "J.K. Rowling privately told him the secret behind Snape's 'Always' years before the final book was published."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "An welchem historischen englischen Bahnhof wurden die Außenaufnahmen von Hogsmeade Station in den ersten Filmen gedreht?",
|
||||
"canonical_answer": "Goathland Station (North Yorkshire)",
|
||||
"options": json.dumps([
|
||||
"Goathland Station (North Yorkshire)",
|
||||
"Paddington Station (London)",
|
||||
"Waverley Station (Edinburgh)",
|
||||
"York Central Station"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Eine malerische Museumsbahnstation der North Yorkshire Moors Railway.",
|
||||
"question_en": "At which historic English heritage railway station were the Hogsmeade Station scenes filmed in the early movies?",
|
||||
"answer_en": "Goathland Station (North Yorkshire)",
|
||||
"options_en": json.dumps([
|
||||
"Goathland Station (North Yorkshire)",
|
||||
"Paddington Station (London)",
|
||||
"Waverley Station (Edinburgh)",
|
||||
"York Central Station"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "A picturesque station on the North Yorkshire Moors Railway."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Auf welchem echten Londoner Bahnhof wurden die Szenen am Gleis 9¾ gedreht?",
|
||||
"canonical_answer": "King's Cross",
|
||||
"options": json.dumps(["King's Cross", "Waterloo", "Victoria Station", "Euston"], ensure_ascii=False),
|
||||
"hint": "Die Außenfassade im 2. Film zeigt das benachbarte St Pancras Renaissance Hotel.",
|
||||
"question_en": "At which real-life London railway station were the Platform 9¾ scenes filmed?",
|
||||
"answer_en": "King's Cross",
|
||||
"options_en": json.dumps(["King's Cross", "Waterloo", "Victoria Station", "Euston"], ensure_ascii=False),
|
||||
"hint_en": "Today, fans can take photos with a luggage trolley disappearing into the brick wall there."
|
||||
},
|
||||
{
|
||||
"category": "movies",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wie heißt die fiktive Zauberrockband im 4. Film beim Weihnachtsball, deren Sänger von Pulp-Frontmann Jarvis Cocker gespielt wurde?",
|
||||
"canonical_answer": "The Weird Sisters (Schwestern des Schicksals)",
|
||||
"options": json.dumps([
|
||||
"The Weird Sisters (Schwestern des Schicksals)",
|
||||
"Celestina Warbeck & the Banshees",
|
||||
"The Hobgoblins",
|
||||
"Frog Choir"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Auch Mitglieder der Band Radiohead spielten in dieser Filmszene in der Band mit!",
|
||||
"question_en": "What is the name of the wizarding rock band at the Yule Ball in the fourth movie, fronted by Pulp's Jarvis Cocker?",
|
||||
"answer_en": "The Weird Sisters",
|
||||
"options_en": json.dumps([
|
||||
"The Weird Sisters",
|
||||
"Celestina Warbeck & the Banshees",
|
||||
"The Hobgoblins",
|
||||
"The Frog Choir"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "Members of Radiohead also appeared in the band lineup for that scene!"
|
||||
},
|
||||
|
||||
# --- SPELLS & POTIONS (Zaubersprüche & Tränke) ---
|
||||
{
|
||||
"category": "spells",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "Wie lautet der Gegenzauber zum Schwebezauber 'Levicorpus', mit dem man das Opfer wieder auf den Boden holt?",
|
||||
"canonical_answer": "Liberacorpus",
|
||||
"accepted_answers": json.dumps(["Liberacorpus", "Libera corpus"]),
|
||||
"hint": "Harry findet sowohl den Zauber als auch den Gegenzauber handschriftlich im Buch des Halbblutprinzen.",
|
||||
"question_en": "What is the counter-jinx to 'Levicorpus' that lowers the victim back to the ground?",
|
||||
"answer_en": "Liberacorpus",
|
||||
"hint_en": "Harry discovers both the spell and its counter-jinx written in the margins of the Half-Blood Prince's textbook."
|
||||
},
|
||||
{
|
||||
"category": "spells",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welcher seltene Stein aus dem Magen einer Ziege wirkt als universelles Gegengift gegen die meisten Zaubertrank-Gifte?",
|
||||
"canonical_answer": "Bezoar",
|
||||
"options": json.dumps(["Bezoar", "Mondstein", "Blutstein", "Flussstein"], ensure_ascii=False),
|
||||
"hint": "Snape fragt Harry danach in seiner allerersten Zaubertränkestunde, und Harry rettet damit später Rons Leben.",
|
||||
"question_en": "Which stone taken from the stomach of a goat acts as an antidote to most poisons?",
|
||||
"answer_en": "Bezoar",
|
||||
"options_en": json.dumps(["Bezoar", "Moonstone", "Bloodstone", "Runestone"], ensure_ascii=False),
|
||||
"hint_en": "Snape quizzes Harry about it in his very first Potions lesson, and Harry later uses one to save Ron's life."
|
||||
},
|
||||
{
|
||||
"category": "spells",
|
||||
"difficulty": "medium",
|
||||
"question_type": "text",
|
||||
"question": "Welcher dunkle Fluch, den der Halbblutprinz erfunden hat, schlitzt das Opfer wie mit einem unsichtbaren Schwert auf ('Für Feinde')?",
|
||||
"canonical_answer": "Sectumsempra",
|
||||
"accepted_answers": json.dumps(["Sectumsempra", "Sectum Sempra"]),
|
||||
"hint": "Harry wendet ihn im 6. Teil im Waschraum gegen Draco Malfoy an.",
|
||||
"question_en": "Which dark curse invented by the Half-Blood Prince slashes the target as if with an invisible sword ('For Enemies')?",
|
||||
"answer_en": "Sectumsempra",
|
||||
"hint_en": "Harry uses it against Draco Malfoy in the bathroom in year six."
|
||||
},
|
||||
{
|
||||
"category": "spells",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wie lange dauert die aufwendige Zubereitung des Vielsaft-Tranks (Polyjuice Potion) ungefähr?",
|
||||
"canonical_answer": "Einen ganzen Monat",
|
||||
"options": json.dumps(["Einen ganzen Monat", "Genau 24 Stunden", "Sieben Tage", "Nur eine Vollmondnacht"], ensure_ascii=False),
|
||||
"hint": "Unter anderem muss Florfliegenkraut 21 Tage lang schmoren.",
|
||||
"question_en": "Approximately how long does it take to brew Polyjuice Potion from start to finish?",
|
||||
"answer_en": "One full month",
|
||||
"options_en": json.dumps(["One full month", "Exactly 24 hours", "Seven days", "A single full-moon night"], ensure_ascii=False),
|
||||
"hint_en": "Lacewing flies alone must stew for twenty-one days."
|
||||
},
|
||||
|
||||
# --- HOGWARTS & PLACES ---
|
||||
{
|
||||
"category": "hogwarts",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wie gelangt man in die Schulküche von Hogwarts, die direkt unter der Großen Halle liegt?",
|
||||
"canonical_answer": "Man kitzelt die Birne auf einem Obst-Gemälde",
|
||||
"options": json.dumps([
|
||||
"Man kitzelt die Birne auf einem Obst-Gemälde",
|
||||
"Man klopft dreimal auf ein Essigfass",
|
||||
"Man sagt das Passwort 'Kürbispastete' zu einer Rüstung",
|
||||
"Man zieht an einer goldenen Bratpfanne"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Die Frucht kichert daraufhin und verwandelt sich in einen grünen Türgriff.",
|
||||
"question_en": "How do you open the secret entrance to the Hogwarts kitchens located beneath the Great Hall?",
|
||||
"answer_en": "Tickle the pear in the painting of a bowl of fruit",
|
||||
"options_en": json.dumps([
|
||||
"Tickle the pear in the painting of a bowl of fruit",
|
||||
"Tap a vinegar barrel in the rhythm of Helga Hufflepuff",
|
||||
"Whisper 'Pumpkin Pasties' to a suit of armor",
|
||||
"Pull on a golden ladle"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "The fruit giggles and transforms into a green door handle."
|
||||
},
|
||||
{
|
||||
"category": "hogwarts",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Was unterscheidet den Eingang zum Ravenclaw-Gemeinschaftsraum von denen der anderen drei Häuser?",
|
||||
"canonical_answer": "Man muss eine philosophische Rätselfrage des Türklopfers beantworten statt ein Passwort zu nennen",
|
||||
"options": json.dumps([
|
||||
"Man muss eine philosophische Rätselfrage des Türklopfers beantworten statt ein Passwort zu nennen",
|
||||
"Man kann nur auf einem Besen durch das Turmfenster hineinfliegen",
|
||||
"Man muss seinen Zauberstab auf eine Waage legen",
|
||||
"Die Tür öffnet sich nur bei Mondschein"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Der bronzene Adler-Türklopfer fragt z. B.: 'Was war zuerst da, der Phönix oder die Flamme?'",
|
||||
"question_en": "How does entering the Ravenclaw common room differ from the other three Hogwarts houses?",
|
||||
"answer_en": "You must answer a philosophical riddle posed by a bronze eagle knocker instead of giving a password",
|
||||
"options_en": json.dumps([
|
||||
"You must answer a philosophical riddle posed by a bronze eagle knocker instead of giving a password",
|
||||
"You can only enter by flying through the tower window on a broom",
|
||||
"You must weigh your wand on an enchanted scale",
|
||||
"The door only opens under moonlight"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "For example, the bronze eagle asks: 'Which came first, the phoenix or the flame?'"
|
||||
},
|
||||
{
|
||||
"category": "hogwarts",
|
||||
"difficulty": "medium",
|
||||
"question_type": "text",
|
||||
"question": "Unter welchem Baum auf dem Schulgelände von Hogwarts beginnt der geheime Tunnel, der direkt in die Heulende Hütte nach Hogsmeade führt?",
|
||||
"canonical_answer": "Peitschende Weide",
|
||||
"accepted_answers": json.dumps(["Peitschende Weide", "Die Peitschende Weide", "Whomping Willow"]),
|
||||
"hint": "Er wurde gepflanzt, als Remus Lupin als Schüler nach Hogwarts kam.",
|
||||
"question_en": "Beneath which violent tree on the Hogwarts grounds is the secret tunnel leading to the Shrieking Shack?",
|
||||
"answer_en": "Whomping Willow",
|
||||
"hint_en": "It was planted the year Remus Lupin arrived at Hogwarts as a student."
|
||||
},
|
||||
{
|
||||
"category": "hogwarts",
|
||||
"difficulty": "easy",
|
||||
"question_type": "text",
|
||||
"question": "Wie heißt das einzige komplett von Zauberern bewohnte Dorf in Großbritannien, das in der Nähe von Hogwarts liegt?",
|
||||
"canonical_answer": "Hogsmeade",
|
||||
"accepted_answers": json.dumps(["Hogsmeade"]),
|
||||
"hint": "Schüler ab der dritten Klasse dürfen es an bestimmten Wochenenden mit einer Unterschrift besuchen.",
|
||||
"question_en": "What is the name of the only all-wizarding village in Britain, located near Hogwarts?",
|
||||
"answer_en": "Hogsmeade",
|
||||
"hint_en": "Students in their third year and above may visit it on certain weekends with a signed permission slip."
|
||||
},
|
||||
|
||||
# --- QUIDDITCH ---
|
||||
{
|
||||
"category": "quidditch",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welches spektakuläre Flugmanöver führt Viktor Krum beim Finale der Quidditch-Weltmeisterschaft 1994 aus, um den gegnerischen Sucher Aidan Lynch in den Boden stürzen zu lassen?",
|
||||
"canonical_answer": "Wronski-Bluff (Wronski Feint)",
|
||||
"options": json.dumps([
|
||||
"Wronski-Bluff (Wronski Feint)",
|
||||
"Porskoff-Ploy",
|
||||
"Falkenkopf-Angriffsformation",
|
||||
"Faultier-Rolle (Sloth Grip Roll)"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Der Sucher täuscht im Sturzflug vor, den Goldenen Schnatz am Boden gesehen zu haben, und zieht im letzten Moment hoch.",
|
||||
"question_en": "Which famous diving maneuver does Viktor Krum execute during the 1994 Quidditch World Cup Final to trick Irish Seeker Aidan Lynch?",
|
||||
"answer_en": "Wronski Feint",
|
||||
"options_en": json.dumps([
|
||||
"Wronski Feint",
|
||||
"Porskoff Ploy",
|
||||
"Hawkshead Attacking Formation",
|
||||
"Sloth Grip Roll"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "The Seeker dives steeply toward the ground pretending to have spotted the Snitch, pulling up at the very last second."
|
||||
},
|
||||
{
|
||||
"category": "quidditch",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welches magische Wesen war vor der Erfindung des Goldenen Schnatzes das ursprüngliche Jagdziel beim Quidditch?",
|
||||
"canonical_answer": "Der Goldene Schnatzer (Golden Snidget)",
|
||||
"options": json.dumps([
|
||||
"Der Goldene Schnatzer (Golden Snidget)",
|
||||
"Der Fwuuper",
|
||||
"Der Augurey",
|
||||
"Der Jobberknoll"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Ein kugelrunder kleiner goldener Vogel, der dadurch fast ausgerottet wurde, bevor Bowman Wright den metallenen Schnatz schmiedete.",
|
||||
"question_en": "Before the invention of the metallic Golden Snitch, which small magical bird was hunted during Quidditch matches?",
|
||||
"answer_en": "Golden Snidget",
|
||||
"options_en": json.dumps([
|
||||
"Golden Snidget",
|
||||
"Fwooper",
|
||||
"Augurey",
|
||||
"Jobberknoll"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "The tiny spherical golden bird was nearly hunted to extinction before Bowman Wright forged the enchanted metal replacement."
|
||||
},
|
||||
{
|
||||
"category": "quidditch",
|
||||
"difficulty": "medium",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Welche beiden Nationalmannschaften standen sich im Finale der Quidditch-Weltmeisterschaft 1994 gegenüber?",
|
||||
"canonical_answer": "Irland und Bulgarien",
|
||||
"options": json.dumps([
|
||||
"Irland und Bulgarien",
|
||||
"England und Transsilvanien",
|
||||
"Schottland und Frankreich",
|
||||
"Irland und Rumänien"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Viktor Krum fing zwar den Schnatz, doch die andere Mannschaft gewann trotzdem mit 170 zu 160 Punkten!",
|
||||
"question_en": "Which two national teams faced off in the final of the 1994 Quidditch World Cup?",
|
||||
"answer_en": "Ireland and Bulgaria",
|
||||
"options_en": json.dumps([
|
||||
"Ireland and Bulgaria",
|
||||
"England and Transylvania",
|
||||
"Scotland and France",
|
||||
"Ireland and Romania"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "Viktor Krum caught the Snitch, yet the opposing team still won the match 170 to 160!"
|
||||
},
|
||||
{
|
||||
"category": "quidditch",
|
||||
"difficulty": "easy",
|
||||
"question_type": "text",
|
||||
"question": "Wie heißt das schnellste Rennbesen-Modell der Welt, das Harry im dritten Schuljahr anonym von seinem Paten Sirius Black geschenkt bekommt?",
|
||||
"canonical_answer": "Feuerblitz",
|
||||
"accepted_answers": json.dumps(["Feuerblitz", "Der Feuerblitz", "Firebolt"]),
|
||||
"hint": "Er löst Harrys zerstörten Nimbus 2000 ab.",
|
||||
"question_en": "What is the name of the world-class racing broom Harry receives anonymously from his godfather Sirius Black in his third year?",
|
||||
"answer_en": "Firebolt",
|
||||
"hint_en": "It replaces Harry's destroyed Nimbus 2000."
|
||||
},
|
||||
|
||||
# --- CHARACTERS ---
|
||||
{
|
||||
"category": "characters",
|
||||
"difficulty": "hard",
|
||||
"question_type": "text",
|
||||
"question": "In welches Tier verwandelt sich Rita Kimmkorn (Rita Skeeter) als nicht registrierter Animagus, um unbemerkt Gespräche zu belauschen?",
|
||||
"canonical_answer": "Käfer",
|
||||
"accepted_answers": json.dumps(["Käfer", "Ein Käfer", "Wasserkäfer", "Grüner Käfer", "Beetle"]),
|
||||
"hint": "Hermine fängt sie am Ende des 4. Schuljahres in einem unzerbrechlichen Schraubglas.",
|
||||
"question_en": "Into which insect does journalist Rita Skeeter transform as an unregistered Animagus to eavesdrop on conversations?",
|
||||
"answer_en": "Beetle",
|
||||
"hint_en": "Hermione traps her inside an unbreakable glass jar at the end of year four."
|
||||
},
|
||||
{
|
||||
"category": "characters",
|
||||
"difficulty": "hard",
|
||||
"question_type": "multiple_choice",
|
||||
"question": "Wer hinterließ die geheimnisvolle Notiz mit den Initialen 'R.A.B.' in der falschen Horkrux-Höhle?",
|
||||
"canonical_answer": "Regulus Arcturus Black",
|
||||
"options": json.dumps([
|
||||
"Regulus Arcturus Black",
|
||||
"Rodolphus Augustus Bell",
|
||||
"Rufus Albus Bones",
|
||||
"Rabastan Lestrange"
|
||||
], ensure_ascii=False),
|
||||
"hint": "Der jüngere Bruder von Sirius Black, der mit Hilfe des Hauselfen Kreacher das echte Medaillon stahl.",
|
||||
"question_en": "Who left the mysterious note signed 'R.A.B.' inside the fake Horcrux locket in the sea cave?",
|
||||
"answer_en": "Regulus Arcturus Black",
|
||||
"options_en": json.dumps([
|
||||
"Regulus Arcturus Black",
|
||||
"Rodolphus Augustus Bell",
|
||||
"Rufus Albus Bones",
|
||||
"Rabastan Lestrange"
|
||||
], ensure_ascii=False),
|
||||
"hint_en": "Sirius Black's younger brother, who stole the real locket with the help of the house-elf Kreacher."
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def seed_lore_pack():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
existing_questions = {q.question.strip().lower(): q for q in db.query(Question).all()}
|
||||
max_sort = db.query(Question.sort_order).order_by(Question.sort_order.desc()).first()
|
||||
next_sort = (max_sort[0] + 1) if max_sort and max_sort[0] else 100
|
||||
|
||||
added = 0
|
||||
for item in LORE_PACK_QUESTIONS:
|
||||
q_key = item["question"].strip().lower()
|
||||
if q_key in existing_questions:
|
||||
continue
|
||||
|
||||
new_q = Question(
|
||||
category=item["category"],
|
||||
difficulty=item["difficulty"],
|
||||
question_type=item["question_type"],
|
||||
question=item["question"],
|
||||
canonical_answer=item["canonical_answer"],
|
||||
accepted_answers=item.get("accepted_answers"),
|
||||
options=item.get("options"),
|
||||
hint=item.get("hint"),
|
||||
question_en=item.get("question_en"),
|
||||
answer_en=item.get("answer_en"),
|
||||
options_en=item.get("options_en"),
|
||||
hint_en=item.get("hint_en"),
|
||||
status="active",
|
||||
sort_order=next_sort,
|
||||
submitted_by="System Lore Pack"
|
||||
)
|
||||
db.add(new_q)
|
||||
next_sort += 1
|
||||
added += 1
|
||||
|
||||
db.commit()
|
||||
print(f"Lore Pack Seeding complete: {added} new questions added. Total active: {db.query(Question).filter(Question.status == 'active').count()}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
seed_lore_pack()
|
||||
+582
-882
File diff suppressed because it is too large
Load Diff
+185
-165
@@ -2,96 +2,123 @@ let currentSessionId = null;
|
||||
let currentQuestionNumber = 1;
|
||||
let totalQuestions = 10;
|
||||
let currentScore = 0;
|
||||
let currentStreak = 0;
|
||||
let attemptsUsed = 0;
|
||||
const maxAttempts = 3;
|
||||
let selectedDifficulty = "all";
|
||||
let selectedCategory = "all";
|
||||
let lastQuestionData = null;
|
||||
let timerStartTime = null;
|
||||
let timerInterval = null;
|
||||
let elapsedSeconds = 0;
|
||||
|
||||
const CATEGORY_LABELS = {
|
||||
de: {
|
||||
all: "✨ Alle Kategorien",
|
||||
books: "📖 Buchwissen",
|
||||
movies: "🎬 Filmwissen",
|
||||
creatures: "🐉 Magische Geschöpfe",
|
||||
spells: "🪄 Zaubersprüche & Tränke",
|
||||
characters: "🧙♂️ Charaktere",
|
||||
hogwarts: "🏰 Hogwarts & Orte",
|
||||
quidditch: "🧹 Quidditch",
|
||||
lore: "🔮 Artefakte & Lore",
|
||||
general: "⚡ Allgemein",
|
||||
harry_potter: "⚡ Harry Potter"
|
||||
},
|
||||
en: {
|
||||
all: "✨ All Categories",
|
||||
books: "📖 Book Lore",
|
||||
movies: "🎬 Movie Trivia",
|
||||
creatures: "🐉 Magical Creatures",
|
||||
spells: "🪄 Spells & Potions",
|
||||
characters: "🧙♂️ Characters",
|
||||
hogwarts: "🏰 Hogwarts & Places",
|
||||
quidditch: "🧹 Quidditch",
|
||||
lore: "🔮 Artifacts & Lore",
|
||||
general: "⚡ General",
|
||||
harry_potter: "⚡ Harry Potter"
|
||||
}
|
||||
};
|
||||
|
||||
const UI_TEXT = {
|
||||
de: {
|
||||
pageTitle: "✨ Magisches Hogwarts-Escape-Quiz ✨",
|
||||
pageSubtitle: "10 knifflige Rätsel aus der Zauberwelt warten auf dich",
|
||||
envelopeLetter: "Hogwarts-Schule für Hexerei und Zauberei",
|
||||
envelopeRecipient: "An den ehrenwerten Zauberschüler",
|
||||
envelopeLocation: "Auf dem Prüfungspfad • Schloss Hogwarts",
|
||||
letterSchoolTitle: "Hogwarts-Schule für Hexerei und Zauberei",
|
||||
letterHeadmaster: "Schulleiter: Albus Dumbledore<br><span>(Großhexenmeister, Träger des Merlin-Ordens Erster Klasse)</span>",
|
||||
letterSalutation: "Liebe Hexe, lieber Zauberer,",
|
||||
letterParagraph1: "wir freuen uns, dir mitteilen zu dürfen, dass du zu den <strong>Magischen Z.A.G.-Prüfungen</strong> des Zaubereiministeriums zugelassen bist.",
|
||||
letterParagraph2: "Zehn anspruchsvolle Prüfungsfragen entscheiden über deinen Rang in den Chroniken von Hogwarts. Zeige deinen Scharfsinn und sichere bis zu <strong>1.000 Hauspunkte</strong> für dein Haus!",
|
||||
letterClosing: "Mit vorzüglicher Hochachtung,",
|
||||
letterSignature: "Minerva McGonagall",
|
||||
letterSignTitle: "Stellvertretende Schulleiterin",
|
||||
introDesc: "Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.<br>Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:",
|
||||
diffLabel: "Schwierigkeitsgrad wählen:",
|
||||
diffAll: "Alle gemischt",
|
||||
pageTitle: "Harry Potter Wissensquiz",
|
||||
pageSubtitle: "Teste dein Wissen über Bücher, Filme, Geschöpfe und Zaubersprüche – direkt & ohne Umwege.",
|
||||
diffLabel: "Schwierigkeitsgrad",
|
||||
diffAll: "Alle Stufen",
|
||||
diffEasy: "Leicht",
|
||||
diffMed: "Mittel",
|
||||
diffHard: "Schwer",
|
||||
startBtn: "📜 Siegel brechen & Brief öffnen",
|
||||
startBtnCasting: "✨ Zauber wird gewirkt...",
|
||||
pointsLabel: "🏆 Hauspunkte",
|
||||
diffHard: "Schwer (Experte)",
|
||||
catLabel: "Themengebiet wählen",
|
||||
catAll: "Alle Kategorien",
|
||||
catBooks: "Buchwissen",
|
||||
catMovies: "Filmwissen",
|
||||
catCreatures: "Magische Geschöpfe",
|
||||
catSpells: "Zaubersprüche & Tränke",
|
||||
catChars: "Charaktere",
|
||||
catHogwarts: "Hogwarts & Orte",
|
||||
catQuidditch: "Quidditch",
|
||||
catLore: "Artefakte & Lore",
|
||||
dashboardRules: "<span>🎯 10 Fragen pro Runde</span><span>·</span><span>🔥 Streak-Zähler</span><span>·</span><span>💡 Bis zu 3 Versuche je Frage</span>",
|
||||
startQuizBtn: "⚡ Quiz jetzt starten ➔",
|
||||
startBtnCasting: "⚡ Runde wird geladen...",
|
||||
pointsLabel: "Pkt.",
|
||||
questionLabel: "Frage",
|
||||
ofLabel: "von",
|
||||
ofLabel: "/",
|
||||
attemptLabel: "Versuch",
|
||||
checkBtn: "✨ Antwort prüfen",
|
||||
hintBtn: "💡 Hinweis anzeigen",
|
||||
hintHeading: "📜 Notiz aus der verbotenen Abteilung:",
|
||||
placeholder: "Deine Antwort hier eingeben...",
|
||||
checkBtn: "✓ Antwort prüfen",
|
||||
hintBtn: "💡 Tipp anzeigen",
|
||||
hintHeading: "💡 Hinweis:",
|
||||
placeholder: "Deine Antwort eingeben...",
|
||||
noHint: "Kein Tipp vorhanden.",
|
||||
hintFailed: "Tipp konnte nicht geladen werden.",
|
||||
correctFeedback: "✨ Richtig! 1 Hauspunkt für dein Haus! ✨",
|
||||
wrongFeedback: "Leider falsch! Noch {attempts} Versuch(e).",
|
||||
exhaustedWrong: "Leider nicht richtig (3 Fehlversuche).",
|
||||
solutionLabel: "Richtige Lösung:",
|
||||
wrongFeedback: "Nicht ganz! Noch {attempts} Versuch(e).",
|
||||
exhaustedWrong: "Leider nicht gelöst (3 Fehlversuche).",
|
||||
solutionLabel: "Richtige Antwort:",
|
||||
startError: "Fehler beim Starten des Quiz: ",
|
||||
networkError: "Netzwerkfehler: "
|
||||
},
|
||||
en: {
|
||||
pageTitle: "✨ Magical Hogwarts Escape Quiz ✨",
|
||||
pageSubtitle: "10 tricky wizarding world puzzles await you",
|
||||
envelopeLetter: "Hogwarts School of Witchcraft and Wizardry",
|
||||
envelopeRecipient: "To the Esteemed Wizarding Student",
|
||||
envelopeLocation: "On the Path of Trials • Hogwarts Castle",
|
||||
letterSchoolTitle: "Hogwarts School of Witchcraft and Wizardry",
|
||||
letterHeadmaster: "Headmaster: Albus Dumbledore<br><span>(Order of Merlin, First Class, Grand Sorc.)</span>",
|
||||
letterSalutation: "Dear Witch or Wizard,",
|
||||
letterParagraph1: "we are pleased to inform you that you have been admitted to the <strong>Magical O.W.L. Examinations</strong> of the Ministry of Magic.",
|
||||
letterParagraph2: "Ten demanding examination questions will determine your rank in the Hogwarts archives. Show your wisdom and claim up to <strong>1,000 House Points</strong> for your house!",
|
||||
letterClosing: "Yours sincerely,",
|
||||
letterSignature: "Minerva McGonagall",
|
||||
letterSignTitle: "Deputy Headmistress",
|
||||
introDesc: "An enchanted Hogwarts parchment letter lies before you.<br>Choose your difficulty and break the crimson magical seal:",
|
||||
diffLabel: "Choose difficulty level:",
|
||||
diffAll: "All mixed",
|
||||
pageTitle: "Harry Potter Trivia Quiz",
|
||||
pageSubtitle: "Test your knowledge across books, movies, creatures, and spells — straight to the action.",
|
||||
diffLabel: "Difficulty Level",
|
||||
diffAll: "All Levels",
|
||||
diffEasy: "Easy",
|
||||
diffMed: "Medium",
|
||||
diffHard: "Hard",
|
||||
startBtn: "📜 Break Seal & Open Letter",
|
||||
startBtnCasting: "✨ Casting spell...",
|
||||
pointsLabel: "🏆 House Points",
|
||||
diffHard: "Hard (Expert)",
|
||||
catLabel: "Select Topic",
|
||||
catAll: "All Categories",
|
||||
catBooks: "Book Lore",
|
||||
catMovies: "Movie Trivia",
|
||||
catCreatures: "Magical Creatures",
|
||||
catSpells: "Spells & Potions",
|
||||
catChars: "Characters",
|
||||
catHogwarts: "Hogwarts & Places",
|
||||
catQuidditch: "Quidditch",
|
||||
catLore: "Artifacts & Lore",
|
||||
dashboardRules: "<span>🎯 10 Questions per Round</span><span>·</span><span>🔥 Answer Streak</span><span>·</span><span>💡 Up to 3 Attempts</span>",
|
||||
startQuizBtn: "⚡ Start Quiz Now ➔",
|
||||
startBtnCasting: "⚡ Loading Round...",
|
||||
pointsLabel: "Pts",
|
||||
questionLabel: "Question",
|
||||
ofLabel: "of",
|
||||
ofLabel: "/",
|
||||
attemptLabel: "Attempt",
|
||||
checkBtn: "✨ Check Answer",
|
||||
checkBtn: "✓ Check Answer",
|
||||
hintBtn: "💡 Show Hint",
|
||||
hintHeading: "📜 Note from the Restricted Section:",
|
||||
placeholder: "Type your answer here...",
|
||||
hintHeading: "💡 Hint:",
|
||||
placeholder: "Type your answer...",
|
||||
noHint: "No hint available.",
|
||||
hintFailed: "Could not load hint.",
|
||||
correctFeedback: "✨ Correct! 1 House Point for your House! ✨",
|
||||
wrongFeedback: "Incorrect! {attempts} attempt(s) remaining.",
|
||||
exhaustedWrong: "Unfortunately incorrect (3 failed attempts).",
|
||||
solutionLabel: "Correct solution:",
|
||||
wrongFeedback: "Not quite! {attempts} attempt(s) left.",
|
||||
exhaustedWrong: "Out of attempts (3 failed tries).",
|
||||
solutionLabel: "Correct answer:",
|
||||
startError: "Error starting quiz: ",
|
||||
networkError: "Network error: "
|
||||
}
|
||||
};
|
||||
|
||||
// Web Audio API Sound Generator for magic chimes and buzzers
|
||||
// Web Audio API Sound Generator for crisp trivia feedback
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
function playSound(type) {
|
||||
@@ -99,33 +126,31 @@ function playSound(type) {
|
||||
audioCtx.resume();
|
||||
}
|
||||
if (type === 'magic') {
|
||||
// Magical bright arpeggio chime
|
||||
const notes = [523.25, 659.25, 783.99, 1046.50]; // C5, E5, G5, C6
|
||||
notes.forEach((freq, index) => {
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = 'sine';
|
||||
osc.frequency.value = freq;
|
||||
gain.gain.setValueAtTime(0.15, audioCtx.currentTime + index * 0.08);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + index * 0.08 + 0.35);
|
||||
gain.gain.setValueAtTime(0.12, audioCtx.currentTime + index * 0.07);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + index * 0.07 + 0.3);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start(audioCtx.currentTime + index * 0.08);
|
||||
osc.stop(audioCtx.currentTime + index * 0.08 + 0.35);
|
||||
osc.start(audioCtx.currentTime + index * 0.07);
|
||||
osc.stop(audioCtx.currentTime + index * 0.07 + 0.3);
|
||||
});
|
||||
} else if (type === 'wrong') {
|
||||
// Soft error buzz
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(150, audioCtx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(80, audioCtx.currentTime + 0.25);
|
||||
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.25);
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.setValueAtTime(180, audioCtx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(95, audioCtx.currentTime + 0.22);
|
||||
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.22);
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + 0.25);
|
||||
osc.stop(audioCtx.currentTime + 0.22);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,64 +161,66 @@ function selectDifficulty(diff) {
|
||||
});
|
||||
}
|
||||
|
||||
function selectCategory(cat) {
|
||||
selectedCategory = cat;
|
||||
document.querySelectorAll(".cat-chip").forEach(chip => {
|
||||
chip.classList.toggle("active", chip.getAttribute("data-cat") === cat);
|
||||
});
|
||||
}
|
||||
|
||||
function updateStreakUI(animate = false) {
|
||||
const badge = document.getElementById("streakBadge");
|
||||
if (!badge) return;
|
||||
badge.innerText = currentStreak >= 2 ? `🔥 ${currentStreak} Streak` : `🔥 ${currentStreak}`;
|
||||
badge.classList.toggle("streak-hot", currentStreak >= 2);
|
||||
if (animate) {
|
||||
badge.classList.remove("streak-pop");
|
||||
void badge.offsetWidth;
|
||||
badge.classList.add("streak-pop");
|
||||
}
|
||||
}
|
||||
|
||||
function onLanguageChange(lang) {
|
||||
applyQuizTranslations(lang);
|
||||
}
|
||||
|
||||
function applyQuizTranslations(lang) {
|
||||
const t = UI_TEXT[lang] || UI_TEXT.de;
|
||||
const pageTitle = document.getElementById("pageTitle");
|
||||
const pageSubtitle = document.getElementById("pageSubtitle");
|
||||
const envelopeLetter = document.getElementById("envelopeLetter");
|
||||
const introDesc = document.getElementById("introDesc");
|
||||
const diffLabel = document.getElementById("diffLabel");
|
||||
const diffAllText = document.getElementById("diffAllText");
|
||||
const diffEasyText = document.getElementById("diffEasyText");
|
||||
const diffMedText = document.getElementById("diffMedText");
|
||||
const diffHardText = document.getElementById("diffHardText");
|
||||
const startBtn = document.getElementById("startBtn");
|
||||
const checkBtn = document.getElementById("checkBtn");
|
||||
const hintBtn = document.getElementById("hintBtn");
|
||||
const hintBtnMC = document.getElementById("hintBtnMC");
|
||||
const hintHeading = document.getElementById("hintHeading");
|
||||
const setTxt = (id, val) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el && val) el.innerText = val;
|
||||
};
|
||||
|
||||
setTxt("pageTitle", t.pageTitle);
|
||||
setTxt("pageSubtitle", t.pageSubtitle);
|
||||
setTxt("diffLabel", t.diffLabel);
|
||||
setTxt("diffAllText", t.diffAll);
|
||||
setTxt("diffEasyText", t.diffEasy);
|
||||
setTxt("diffMedText", t.diffMed);
|
||||
setTxt("diffHardText", t.diffHard);
|
||||
setTxt("catLabel", t.catLabel);
|
||||
setTxt("catAllText", t.catAll);
|
||||
setTxt("catBooksText", t.catBooks);
|
||||
setTxt("catMoviesText", t.catMovies);
|
||||
setTxt("catCreaturesText", t.catCreatures);
|
||||
setTxt("catSpellsText", t.catSpells);
|
||||
setTxt("catCharsText", t.catChars);
|
||||
setTxt("catHogwartsText", t.catHogwarts);
|
||||
setTxt("catQuidditchText", t.catQuidditch);
|
||||
setTxt("catLoreText", t.catLore);
|
||||
|
||||
const rulesEl = document.getElementById("dashboardRules");
|
||||
if (rulesEl) rulesEl.innerHTML = t.dashboardRules;
|
||||
|
||||
const startQuizBtn = document.getElementById("startQuizBtn");
|
||||
if (startQuizBtn && !startQuizBtn.disabled) startQuizBtn.innerText = t.startQuizBtn;
|
||||
|
||||
setTxt("checkBtn", t.checkBtn);
|
||||
setTxt("hintBtn", t.hintBtn);
|
||||
setTxt("hintBtnMC", t.hintBtn);
|
||||
setTxt("hintHeading", t.hintHeading);
|
||||
|
||||
const answerInput = document.getElementById("answerInput");
|
||||
|
||||
const letterSchoolTitle = document.getElementById("letterSchoolTitle");
|
||||
const letterHeadmaster = document.getElementById("letterHeadmaster");
|
||||
const letterSalutation = document.getElementById("letterSalutation");
|
||||
const letterParagraph1 = document.getElementById("letterParagraph1");
|
||||
const letterParagraph2 = document.getElementById("letterParagraph2");
|
||||
const letterClosing = document.getElementById("letterClosing");
|
||||
const letterSignature = document.getElementById("letterSignature");
|
||||
const letterSignTitle = document.getElementById("letterSignTitle");
|
||||
|
||||
const envelopeRecipient = document.getElementById("envelopeRecipient");
|
||||
const envelopeLocation = document.getElementById("envelopeLocation");
|
||||
|
||||
if (pageTitle) pageTitle.innerText = t.pageTitle;
|
||||
if (pageSubtitle) pageSubtitle.innerText = t.pageSubtitle;
|
||||
if (envelopeLetter) envelopeLetter.innerText = t.envelopeLetter;
|
||||
if (envelopeRecipient && t.envelopeRecipient) envelopeRecipient.innerText = t.envelopeRecipient;
|
||||
if (envelopeLocation && t.envelopeLocation) envelopeLocation.innerText = t.envelopeLocation;
|
||||
if (letterSchoolTitle && t.letterSchoolTitle) letterSchoolTitle.innerText = t.letterSchoolTitle;
|
||||
if (letterHeadmaster && t.letterHeadmaster) letterHeadmaster.innerHTML = t.letterHeadmaster;
|
||||
if (letterSalutation && t.letterSalutation) letterSalutation.innerText = t.letterSalutation;
|
||||
if (letterParagraph1 && t.letterParagraph1) letterParagraph1.innerHTML = t.letterParagraph1;
|
||||
if (letterParagraph2 && t.letterParagraph2) letterParagraph2.innerHTML = t.letterParagraph2;
|
||||
if (letterClosing && t.letterClosing) letterClosing.innerText = t.letterClosing;
|
||||
if (letterSignature && t.letterSignature) letterSignature.innerText = t.letterSignature;
|
||||
if (letterSignTitle && t.letterSignTitle) letterSignTitle.innerText = t.letterSignTitle;
|
||||
if (introDesc) introDesc.innerHTML = t.introDesc;
|
||||
if (diffLabel) diffLabel.innerText = t.diffLabel;
|
||||
if (diffAllText) diffAllText.innerText = t.diffAll;
|
||||
if (diffEasyText) diffEasyText.innerText = t.diffEasy;
|
||||
if (diffMedText) diffMedText.innerText = t.diffMed;
|
||||
if (diffHardText) diffHardText.innerText = t.diffHard;
|
||||
if (startBtn && !startBtn.disabled) startBtn.innerText = t.startBtn;
|
||||
if (checkBtn) checkBtn.innerText = t.checkBtn;
|
||||
if (hintBtn) hintBtn.innerText = t.hintBtn;
|
||||
if (hintBtnMC) hintBtnMC.innerText = t.hintBtn;
|
||||
if (hintHeading) hintHeading.innerText = t.hintHeading;
|
||||
if (answerInput) answerInput.placeholder = t.placeholder;
|
||||
|
||||
if (lastQuestionData) {
|
||||
@@ -254,29 +281,18 @@ function saveRecentQuestions(ids) {
|
||||
}
|
||||
|
||||
async function startGame() {
|
||||
const envelope = document.getElementById("envelope");
|
||||
const startBtn = document.getElementById("startBtn");
|
||||
const startQuizBtn = document.getElementById("startQuizBtn");
|
||||
const t = UI_TEXT[currentLang] || UI_TEXT.de;
|
||||
|
||||
startBtn.disabled = true;
|
||||
startBtn.innerText = t.startBtnCasting;
|
||||
envelope.classList.add("envelope-opened");
|
||||
if (startQuizBtn) {
|
||||
startQuizBtn.disabled = true;
|
||||
startQuizBtn.innerText = t.startBtnCasting;
|
||||
}
|
||||
|
||||
playSound('magic');
|
||||
sessionQuestionIds = [];
|
||||
|
||||
// Start background music if enabled or first click
|
||||
const bgm = document.getElementById("bgm");
|
||||
if (bgm && bgm.paused) {
|
||||
bgm.play().then(() => {
|
||||
const icon = document.getElementById("audioIcon");
|
||||
const text = document.getElementById("audioText");
|
||||
if (icon && text) {
|
||||
icon.innerText = "🔊";
|
||||
text.innerText = (currentLang === "de") ? "Musik: An" : "Music: On";
|
||||
}
|
||||
localStorage.setItem("bgm_enabled", "true");
|
||||
}).catch(() => {});
|
||||
}
|
||||
currentStreak = 0;
|
||||
updateStreakUI(false);
|
||||
|
||||
try {
|
||||
const recentIds = getRecentQuestionIds();
|
||||
@@ -288,6 +304,7 @@ async function startGame() {
|
||||
body: JSON.stringify({
|
||||
lang: currentLang,
|
||||
difficulty: selectedDifficulty,
|
||||
category: selectedCategory,
|
||||
recent_ids: recentIds,
|
||||
wrong_ids: wrongIds
|
||||
})
|
||||
@@ -304,18 +321,20 @@ async function startGame() {
|
||||
currentScore = data.score;
|
||||
attemptsUsed = data.attempts_used;
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById("introCard").classList.add("hidden");
|
||||
document.getElementById("gameCard").classList.remove("hidden");
|
||||
startTimer();
|
||||
renderQuestion(data);
|
||||
}, 1800);
|
||||
const introCard = document.getElementById("introCard");
|
||||
if (introCard) {
|
||||
introCard.classList.add("hidden");
|
||||
}
|
||||
document.getElementById("gameCard").classList.remove("hidden");
|
||||
startTimer();
|
||||
renderQuestion(data);
|
||||
|
||||
} catch (err) {
|
||||
alert(t.startError + err.message);
|
||||
startBtn.disabled = false;
|
||||
startBtn.innerText = t.startBtn;
|
||||
envelope.classList.remove("envelope-opened");
|
||||
if (startQuizBtn) {
|
||||
startQuizBtn.disabled = false;
|
||||
startQuizBtn.innerText = t.startQuizBtn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,12 +363,14 @@ function renderQuestion(qData) {
|
||||
sessionQuestionIds.push(qData.id);
|
||||
}
|
||||
const t = UI_TEXT[currentLang] || UI_TEXT.de;
|
||||
const catMap = CATEGORY_LABELS[currentLang] || CATEGORY_LABELS.de;
|
||||
|
||||
document.getElementById("questionCounter").innerText = `${t.questionLabel} ${qData.question_number} ${t.ofLabel} ${totalQuestions}`;
|
||||
document.getElementById("pointsCounter").innerText = `${t.pointsLabel}: ${currentScore}`;
|
||||
document.getElementById("badgeCategory").innerText = `⚡ ${qData.category.replace("_", " ").toUpperCase()}`;
|
||||
document.getElementById("pointsCounter").innerText = `🏆 ${currentScore} ${t.pointsLabel}`;
|
||||
|
||||
const rawCat = (qData.category || "general").toLowerCase();
|
||||
document.getElementById("badgeCategory").innerText = catMap[rawCat] || `⚡ ${rawCat.toUpperCase()}`;
|
||||
|
||||
// Difficulty badge
|
||||
const badgeDiff = document.getElementById("badgeDifficulty");
|
||||
if (badgeDiff) {
|
||||
const diff = qData.difficulty || "medium";
|
||||
@@ -360,7 +381,7 @@ function renderQuestion(qData) {
|
||||
diffText = t.diffEasy;
|
||||
diffIcon = "🟢";
|
||||
} else if (diff === "hard") {
|
||||
diffText = t.diffHard;
|
||||
diffText = currentLang === "en" ? "Hard" : "Schwer";
|
||||
diffIcon = "🔴";
|
||||
}
|
||||
badgeDiff.innerText = `${diffIcon} ${diffText}`;
|
||||
@@ -432,7 +453,6 @@ async function submitAnswer(answer, clickedBtn = null) {
|
||||
if (checkBtn) checkBtn.disabled = true;
|
||||
if (input) input.disabled = true;
|
||||
|
||||
// Disable all choice buttons
|
||||
const choiceBtns = document.querySelectorAll(".choice-btn");
|
||||
choiceBtns.forEach(b => b.disabled = true);
|
||||
if (clickedBtn) {
|
||||
@@ -455,29 +475,31 @@ async function submitAnswer(answer, clickedBtn = null) {
|
||||
|
||||
const result = await res.json();
|
||||
currentScore = result.score;
|
||||
document.getElementById("pointsCounter").innerText = `${t.pointsLabel}: ${currentScore}`;
|
||||
document.getElementById("pointsCounter").innerText = `🏆 ${currentScore} ${t.pointsLabel}`;
|
||||
|
||||
const frageBox = document.getElementById("frageBox");
|
||||
const feedbackBox = document.getElementById("feedbackBox");
|
||||
|
||||
if (result.correct) {
|
||||
// Success!
|
||||
playSound('magic');
|
||||
currentStreak += 1;
|
||||
updateStreakUI(true);
|
||||
|
||||
if (clickedBtn) {
|
||||
clickedBtn.classList.remove("choice-selected");
|
||||
clickedBtn.classList.add("choice-correct");
|
||||
}
|
||||
frageBox.classList.add("sparkle-effect");
|
||||
|
||||
// Mark question as mastered in history
|
||||
if (result.question_id) {
|
||||
recordQuestionMastered(result.question_id);
|
||||
}
|
||||
|
||||
const pts = result.points_earned || 100;
|
||||
const streakBonusText = currentStreak >= 3 ? ` · 🔥 ${currentStreak}er Serie!` : "";
|
||||
const ptsText = (currentLang === "en")
|
||||
? `✨ Correct! +${pts} House Points! ✨`
|
||||
: `✨ Richtig! +${pts} Hauspunkte für dein Haus! ✨`;
|
||||
? `✓ Correct! +${pts} Points${currentStreak >= 3 ? ` · 🔥 ${currentStreak} Streak!` : ""}`
|
||||
: `✓ Richtig! +${pts} Punkte${streakBonusText}`;
|
||||
feedbackBox.innerHTML = `<span class="feedback-correct">${ptsText}</span>`;
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -489,11 +511,13 @@ async function submitAnswer(answer, clickedBtn = null) {
|
||||
attemptsUsed = 0;
|
||||
renderQuestion(result.next_question);
|
||||
}
|
||||
}, 1200);
|
||||
}, 1100);
|
||||
|
||||
} else {
|
||||
// Incorrect
|
||||
playSound('wrong');
|
||||
currentStreak = 0;
|
||||
updateStreakUI(false);
|
||||
|
||||
if (clickedBtn) {
|
||||
clickedBtn.classList.remove("choice-selected");
|
||||
clickedBtn.classList.add("choice-wrong");
|
||||
@@ -505,7 +529,6 @@ async function submitAnswer(answer, clickedBtn = null) {
|
||||
document.getElementById("attemptBadge").innerText = `${t.attemptLabel} ${Math.min(attemptsUsed + 1, maxAttempts)} ${t.ofLabel} ${maxAttempts}`;
|
||||
|
||||
if (result.attempts_left === 0) {
|
||||
// Exhausted all attempts -> record for spaced repetition learning
|
||||
if (result.question_id) {
|
||||
recordQuestionWrong(result.question_id);
|
||||
}
|
||||
@@ -526,7 +549,6 @@ async function submitAnswer(answer, clickedBtn = null) {
|
||||
}, 2200);
|
||||
|
||||
} else {
|
||||
// Still attempts remaining
|
||||
const msg = t.wrongFeedback.replace("{attempts}", result.attempts_left);
|
||||
feedbackBox.innerHTML = `<span class="feedback-wrong">${msg}</span>`;
|
||||
if (checkBtn) checkBtn.disabled = false;
|
||||
@@ -534,7 +556,6 @@ async function submitAnswer(answer, clickedBtn = null) {
|
||||
input.disabled = false;
|
||||
input.select();
|
||||
}
|
||||
// Re-enable choice buttons
|
||||
choiceBtns.forEach(b => b.disabled = false);
|
||||
}
|
||||
}
|
||||
@@ -571,7 +592,6 @@ function finishGame() {
|
||||
window.location.href = `/finale?score=${currentScore}&total=${totalQuestions}&time=${elapsedSeconds}&lang=${currentLang}&diff=${encodeURIComponent(selectedDifficulty)}`;
|
||||
}
|
||||
|
||||
// Enter key to submit
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const input = document.getElementById("answerInput");
|
||||
if (input) {
|
||||
|
||||
+25
-17
@@ -1,18 +1,20 @@
|
||||
const SUGGESTION_TEXT = {
|
||||
de: {
|
||||
title: "✨ Neue Frage für das Quiz vorschlagen ✨",
|
||||
subtitle: "Bereichere das magische Quiz mit kniffligen Harry-Potter- oder Escape-Rätseln!",
|
||||
alert: "ℹ️ <strong>Hinweis zur Moderation:</strong> Eingereichte Fragen werden nicht sofort öffentlich sichtbar, sondern erhalten automatisch den Status <em>„Prüfung ausstehend“</em>. Unser Zaubereiministerium prüft und testet die Antwort, bevor die Frage freigeschaltet wird.",
|
||||
nameLabel: "Dein Name / Zauberer-Alias (optional):",
|
||||
title: "✍️ Neue Quiz-Frage einreichen",
|
||||
subtitle: "Bereichere PotterTrivia mit deinen eigenen Fragen zu Büchern, Filmen oder magischen Geschöpfen!",
|
||||
alert: "ℹ️ <strong>Hinweis zur Moderation:</strong> Eingereichte Fragen werden kurz auf Richtigkeit geprüft, bevor sie im öffentlichen Quiz-Pool freigeschaltet werden.",
|
||||
nameLabel: "Dein Name / Alias (optional):",
|
||||
namePlaceholder: "z. B. Hermine Granger",
|
||||
catLabel: "Kategorie / Themenbereich:",
|
||||
catHogwarts: "🏰 Hogwarts & Schulgelände",
|
||||
catCharacters: "🧙 Charaktere & Personen",
|
||||
catSpells: "⚡ Zaubersprüche & Magie",
|
||||
catCreatures: "🐉 Magische Tierwesen",
|
||||
catBooks: "📖 Buchwissen (Buch-Details)",
|
||||
catMovies: "🎬 Filmwissen (Kino & Drehorte)",
|
||||
catHogwarts: "🏰 Hogwarts & Orte",
|
||||
catCharacters: "🧙♂️ Charaktere & Personen",
|
||||
catSpells: "🪄 Zaubersprüche & Tränke",
|
||||
catCreatures: "🐉 Magische Geschöpfe",
|
||||
catQuidditch: "🧹 Quidditch & Sport",
|
||||
catLore: "🔮 Artefakte & Geschichte",
|
||||
catGeneral: "📜 Allgemeines Zauberwissen",
|
||||
catGeneral: "⚡ Allgemeines Wissen",
|
||||
frageLabel: "Deine Frage: *",
|
||||
fragePlaceholder: "z. B. Wie heißt der Schulleiter von Hogwarts?",
|
||||
diffLabel: "Schwierigkeitsgrad:",
|
||||
@@ -36,19 +38,21 @@ const SUGGESTION_TEXT = {
|
||||
successHeader: "✨ Vorschlag erfolgreich übermittelt! ✨"
|
||||
},
|
||||
en: {
|
||||
title: "✨ Suggest a New Quiz Question ✨",
|
||||
subtitle: "Enrich the magical quiz with tricky Harry Potter or escape puzzles!",
|
||||
alert: "ℹ️ <strong>Moderation Note:</strong> Submitted questions are not immediately public; they automatically receive the status <em>'Pending Review'</em>. The Ministry of Magic tests and verifies the answer before approval.",
|
||||
nameLabel: "Your Name / Wizard Alias (optional):",
|
||||
title: "✍️ Submit a New Quiz Question",
|
||||
subtitle: "Enrich PotterTrivia with your own questions about the books, movies, or magical creatures!",
|
||||
alert: "ℹ️ <strong>Moderation Note:</strong> Submitted questions are briefly reviewed for accuracy before going live in the public quiz pool.",
|
||||
nameLabel: "Your Name / Alias (optional):",
|
||||
namePlaceholder: "e.g. Hermione Granger",
|
||||
catLabel: "Category / Topic Area:",
|
||||
catHogwarts: "🏰 Hogwarts & School Grounds",
|
||||
catCharacters: "🧙 Characters & People",
|
||||
catSpells: "⚡ Spells & Magic",
|
||||
catBooks: "📖 Book Lore (Book Details)",
|
||||
catMovies: "🎬 Movie Trivia (Cinema & Filming)",
|
||||
catHogwarts: "🏰 Hogwarts & Places",
|
||||
catCharacters: "🧙♂️ Characters & People",
|
||||
catSpells: "🪄 Spells & Potions",
|
||||
catCreatures: "🐉 Magical Creatures",
|
||||
catQuidditch: "🧹 Quidditch & Sport",
|
||||
catLore: "🔮 Artifacts & Lore",
|
||||
catGeneral: "📜 General Wizarding Lore",
|
||||
catGeneral: "⚡ General Knowledge",
|
||||
frageLabel: "Your Question: *",
|
||||
fragePlaceholder: "e.g. Who is the Headmaster of Hogwarts?",
|
||||
diffLabel: "Difficulty Level:",
|
||||
@@ -92,6 +96,8 @@ function onLanguageChange(lang) {
|
||||
const submitBtn = document.getElementById("submitBtn");
|
||||
|
||||
const catLabel = document.getElementById("suggCatLabel");
|
||||
const catBooks = document.getElementById("catBooks");
|
||||
const catMovies = document.getElementById("catMovies");
|
||||
const catHogwarts = document.getElementById("catHogwarts");
|
||||
const catCharacters = document.getElementById("catCharacters");
|
||||
const catSpells = document.getElementById("catSpells");
|
||||
@@ -106,6 +112,8 @@ function onLanguageChange(lang) {
|
||||
if (nameLabel) nameLabel.innerText = t.nameLabel;
|
||||
if (nameInput) nameInput.placeholder = t.namePlaceholder;
|
||||
if (catLabel) catLabel.innerText = t.catLabel;
|
||||
if (catBooks) catBooks.innerText = t.catBooks;
|
||||
if (catMovies) catMovies.innerText = t.catMovies;
|
||||
if (catHogwarts) catHogwarts.innerText = t.catHogwarts;
|
||||
if (catCharacters) catCharacters.innerText = t.catCharacters;
|
||||
if (catSpells) catSpells.innerText = t.catSpells;
|
||||
|
||||
@@ -225,9 +225,15 @@
|
||||
<div class="form-group">
|
||||
<label for="category">Kategorie:</label>
|
||||
<select name="category" id="category" class="input-text">
|
||||
<option value="harry_potter" selected>Harry Potter / Escape</option>
|
||||
<option value="insider">Persönlich / Insider</option>
|
||||
<option value="general">Allgemeines Rätsel</option>
|
||||
<option value="books">📖 Buchwissen</option>
|
||||
<option value="movies">🎬 Filmwissen</option>
|
||||
<option value="creatures">🐉 Magische Geschöpfe</option>
|
||||
<option value="spells">🪄 Zaubersprüche & Tränke</option>
|
||||
<option value="characters" selected>🧙♂️ Charaktere</option>
|
||||
<option value="hogwarts">🏰 Hogwarts & Orte</option>
|
||||
<option value="quidditch">🧹 Quidditch</option>
|
||||
<option value="lore">🔮 Artefakte & Lore</option>
|
||||
<option value="general">⚡ Allgemein</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
+17
-67
@@ -3,48 +3,39 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
|
||||
<title>{% block title %}Magisches Harry-Potter-Escape-Quiz{% endblock %}</title>
|
||||
<title>{% block title %}PotterTrivia – Das Harry-Potter-Wissensquiz{% endblock %}</title>
|
||||
<link rel="icon" href="/static/favicon.ico">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@500;700&family=Marck+Script&family=MedievalSharp&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=3.5">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=4.0">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<audio id="bgm" src="/static/audio/music.mp3" loop preload="auto"></audio>
|
||||
|
||||
<header class="site-header">
|
||||
<div class="header-inner">
|
||||
<a href="/" class="brand-link">
|
||||
<span class="brand-sparkle">⚡</span>
|
||||
<span class="brand-title">Hogwarts Quiz</span>
|
||||
<span class="brand-badge">⚡</span>
|
||||
<span class="brand-title">Potter<span class="brand-accent">Trivia</span></span>
|
||||
</a>
|
||||
|
||||
<nav id="navLinks" class="nav-links">
|
||||
<a href="/" class="nav-link" id="navQuiz" onclick="closeMobileMenu()">🎯 Quiz</a>
|
||||
<a href="/bestenliste" class="nav-link" id="navLeaderboard" onclick="closeMobileMenu()">🏆 Bestenliste</a>
|
||||
<a href="/vorschlag" class="nav-link" id="navSuggest" onclick="closeMobileMenu()">✍️ Frage einreichen</a>
|
||||
<a href="/admin" class="nav-link" id="navAdmin" onclick="closeMobileMenu()">⚙️ Admin</a>
|
||||
</nav>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="lang-switch-group" role="group" aria-label="Sprachauswahl / Language">
|
||||
<button type="button" id="langDe" class="lang-btn active" onclick="setLanguage('de')" title="Deutsch">🇩🇪 DE</button>
|
||||
<button type="button" id="langEn" class="lang-btn" onclick="setLanguage('en')" title="English">🇬🇧 EN</button>
|
||||
</div>
|
||||
|
||||
<button id="audioToggle" class="audio-btn" title="Musik an/aus" onclick="toggleAudio()">
|
||||
<span id="audioIcon">🔇</span>
|
||||
<span id="audioText" class="audio-label">Musik: Aus</span>
|
||||
</button>
|
||||
|
||||
<button id="menuToggle" class="menu-toggle-btn" aria-label="Menü öffnen" onclick="toggleMobileMenu()">
|
||||
<span id="menuIcon">☰</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav id="navLinks" class="nav-links">
|
||||
<a href="/" class="nav-link" id="navQuiz" onclick="closeMobileMenu()">✨ Zum Quiz</a>
|
||||
<a href="https://bootle.dev.waiswuaschd.de" class="nav-link" id="navEscape" target="_blank" rel="noopener">🧪 Escape Adventure</a>
|
||||
<a href="/bestenliste" class="nav-link" id="navLeaderboard" onclick="closeMobileMenu()">🏆 Bestenliste</a>
|
||||
<a href="/vorschlag" class="nav-link" id="navSuggest" onclick="closeMobileMenu()">✍️ Frage einreichen</a>
|
||||
<a href="/admin" class="nav-link" id="navAdmin" onclick="closeMobileMenu()">🔒 Admin-Cockpit</a>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -53,7 +44,7 @@
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p id="footerText">⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡</p>
|
||||
<p id="footerText">⚡ PotterTrivia · Das unabhängige Harry-Potter-Wissensquiz</p>
|
||||
<div class="footer-links">
|
||||
<a href="/impressum" class="footer-link" id="footerImpressum">⚖️ Impressum</a>
|
||||
<span class="footer-sep">·</span>
|
||||
@@ -62,9 +53,6 @@
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const bgm = document.getElementById("bgm");
|
||||
const audioIcon = document.getElementById("audioIcon");
|
||||
const audioText = document.getElementById("audioText");
|
||||
const navLinks = document.getElementById("navLinks");
|
||||
const menuIcon = document.getElementById("menuIcon");
|
||||
|
||||
@@ -97,43 +85,17 @@
|
||||
const footerImpressum = document.getElementById("footerImpressum");
|
||||
const footerDatenschutz = document.getElementById("footerDatenschutz");
|
||||
|
||||
if (navQuiz) navQuiz.innerText = isDe ? "✨ Zum Quiz" : "✨ To Quiz";
|
||||
if (navQuiz) navQuiz.innerText = isDe ? "🎯 Quiz" : "🎯 Quiz";
|
||||
if (navLeaderboard) navLeaderboard.innerText = isDe ? "🏆 Bestenliste" : "🏆 Leaderboard";
|
||||
if (navSuggest) navSuggest.innerText = isDe ? "✍️ Frage einreichen" : "✍️ Submit Question";
|
||||
if (navAdmin) navAdmin.innerText = isDe ? "🔒 Admin-Cockpit" : "🔒 Admin Cockpit";
|
||||
if (navAdmin) navAdmin.innerText = isDe ? "⚙️ Admin" : "⚙️ Admin";
|
||||
if (footerText) {
|
||||
footerText.innerText = isDe
|
||||
? "⚡ Magisches Hogwarts-Escape-Quiz · Betrieben mit Liebe & Zauberkraft ⚡"
|
||||
: "⚡ Magical Hogwarts Escape Quiz · Powered by Love & Wizardry ⚡";
|
||||
? "⚡ PotterTrivia · Das unabhängige Harry-Potter-Wissensquiz"
|
||||
: "⚡ PotterTrivia · The Independent Wizarding World Trivia Quiz";
|
||||
}
|
||||
if (footerImpressum) footerImpressum.innerText = isDe ? "⚖️ Impressum" : "⚖️ Legal Notice";
|
||||
if (footerDatenschutz) footerDatenschutz.innerText = isDe ? "🛡️ Datenschutz" : "🛡️ Privacy Policy";
|
||||
if (audioText) {
|
||||
const isPlaying = bgm && !bgm.paused;
|
||||
if (isDe) {
|
||||
audioText.innerText = isPlaying ? "Musik: An" : "Musik: Aus";
|
||||
} else {
|
||||
audioText.innerText = isPlaying ? "Music: On" : "Music: Off";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAudio() {
|
||||
const isDe = currentLang === "de";
|
||||
if (bgm.paused) {
|
||||
bgm.play().then(() => {
|
||||
audioIcon.innerText = "🔊";
|
||||
audioText.innerText = isDe ? "Musik: An" : "Music: On";
|
||||
localStorage.setItem("bgm_enabled", "true");
|
||||
}).catch(err => {
|
||||
console.log("Audio play blocked:", err);
|
||||
});
|
||||
} else {
|
||||
bgm.pause();
|
||||
audioIcon.innerText = "🔇";
|
||||
audioText.innerText = isDe ? "Musik: Aus" : "Music: Off";
|
||||
localStorage.setItem("bgm_enabled", "false");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMobileMenu() {
|
||||
@@ -146,18 +108,6 @@
|
||||
menuIcon.innerText = "☰";
|
||||
}
|
||||
|
||||
// Auto-resume audio if previously enabled
|
||||
document.addEventListener("click", function initAudioOnInteraction() {
|
||||
if (localStorage.getItem("bgm_enabled") === "true" && bgm.paused) {
|
||||
bgm.play().then(() => {
|
||||
audioIcon.innerText = "🔊";
|
||||
audioText.innerText = (currentLang === "de") ? "Musik: An" : "Music: On";
|
||||
}).catch(() => {});
|
||||
}
|
||||
document.removeEventListener("click", initAudioOnInteraction);
|
||||
}, { once: true });
|
||||
|
||||
// Initialize UI on load
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
updateLanguageUI();
|
||||
});
|
||||
|
||||
+89
-103
@@ -1,121 +1,107 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}✨ Magisches Harry-Potter-Escape-Quiz ✨{% endblock %}
|
||||
{% block title %}⚡ PotterTrivia – Das Harry-Potter-Wissensquiz{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="quiz-wrapper">
|
||||
<header class="quiz-header">
|
||||
<h1 class="glow-title" id="pageTitle">✨ Magisches Hogwarts-Escape-Quiz ✨</h1>
|
||||
<p class="subtitle" id="pageSubtitle">10 knifflige Rätsel aus der Zauberwelt warten auf dich</p>
|
||||
<div class="hero-pill" id="heroPill">⚡ {{ question_count }} Fragen im Wissens-Pool · Buch & Film</div>
|
||||
<h1 class="glow-title" id="pageTitle">Harry Potter Wissensquiz</h1>
|
||||
<p class="subtitle" id="pageSubtitle">Teste dein Wissen über Bücher, Filme, Geschöpfe und Zaubersprüche – direkt & ohne Umwege.</p>
|
||||
</header>
|
||||
|
||||
<!-- Brief-Intro / Startmaske -->
|
||||
<div id="introCard" class="card intro-card intro-parchment-wrapper">
|
||||
<!-- Antiker geschlossener Hogwarts-Umschlag (öffnet sich bei Klick auf das Siegel) -->
|
||||
<div class="antique-envelope-scene" id="envelope">
|
||||
<div class="envelope-base">
|
||||
<!-- Dunkleres Pergament-Innenfutter -->
|
||||
<div class="envelope-interior"></div>
|
||||
|
||||
<!-- Der darin liegende Briefbogen (gleitet beim Öffnen majestätisch hervor) -->
|
||||
<div class="letter-sheet" id="letterSheet">
|
||||
<div class="sheet-parchment-layer"></div>
|
||||
<header class="letter-header">
|
||||
<div class="letter-crest-box">
|
||||
<img src="/static/img/hogwarts_seal.jpg" alt="Hogwarts Crest" class="letter-crest-sketch">
|
||||
</div>
|
||||
<h3 class="letter-school-name" id="letterSchoolTitle">Hogwarts-Schule für Hexerei und Zauberei</h3>
|
||||
<p class="letter-headmaster" id="letterHeadmaster">Schulleiter: Albus Dumbledore<br><span>(Großhexenmeister, Träger des Merlin-Ordens 1. Klasse)</span></p>
|
||||
<div class="letter-ornament-line">
|
||||
<span class="ornament-leaf">❦</span>
|
||||
<span class="ornament-rule"></span>
|
||||
<span class="ornament-shield">✦</span>
|
||||
<span class="ornament-rule"></span>
|
||||
<span class="ornament-leaf">❧</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="letter-body-handwritten">
|
||||
<p class="letter-salutation" id="letterSalutation">Liebe Hexe, lieber Zauberer,</p>
|
||||
<p class="letter-paragraph" id="letterParagraph1">
|
||||
wir freuen uns, dir mitteilen zu dürfen, dass du zu den <strong>Magischen Z.A.G.-Prüfungen</strong> an der Hogwarts-Schule zugelassen bist.
|
||||
</p>
|
||||
<p class="letter-paragraph" id="letterParagraph2">
|
||||
Zehn anspruchsvolle Prüfungsfragen entscheiden über deinen Rang in den Chroniken. Zeige deinen Scharfsinn und sichere bis zu <strong>1.000 Hauspunkte</strong> für dein Haus!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<footer class="letter-signature-block">
|
||||
<p class="letter-closing" id="letterClosing">Mit vorzüglicher Hochachtung,</p>
|
||||
<p class="letter-signature-ink" id="letterSignature">Minerva McGonagall</p>
|
||||
<p class="letter-sign-title" id="letterSignTitle">Stellvertretende Schulleiterin</p>
|
||||
</footer>
|
||||
<!-- Kompaktes Direct-Start Dashboard -->
|
||||
<div id="introCard" class="card start-dashboard-card">
|
||||
<div class="dashboard-grid">
|
||||
<!-- Sektion 1: Schwierigkeitsgrad -->
|
||||
<div class="config-section">
|
||||
<div class="config-header">
|
||||
<span class="config-step">1</span>
|
||||
<span class="difficulty-label" id="diffLabel">Schwierigkeitsgrad</span>
|
||||
</div>
|
||||
|
||||
<!-- Vordere Umschlagtasche mit handschriftlicher Smaragd-Anschrift -->
|
||||
<div class="envelope-front-pocket">
|
||||
<div class="envelope-sketch-border"></div>
|
||||
<div class="envelope-address-block">
|
||||
<p class="addr-line addr-school" id="envelopeLetter">Hogwarts-Schule für Hexerei und Zauberei</p>
|
||||
<p class="addr-line addr-recipient" id="envelopeRecipient">An den ehrenwerten Zauberschüler</p>
|
||||
<p class="addr-line addr-location" id="envelopeLocation">Auf dem Prüfungspfad • Schloss Hogwarts</p>
|
||||
</div>
|
||||
<div class="difficulty-chips" role="radiogroup" aria-label="Schwierigkeitsgrad">
|
||||
<button type="button" class="diff-chip active" data-diff="all" onclick="selectDifficulty('all')">
|
||||
<span class="diff-icon">🎲</span>
|
||||
<span class="diff-text" id="diffAllText">Alle Stufen</span>
|
||||
</button>
|
||||
<button type="button" class="diff-chip" data-diff="easy" onclick="selectDifficulty('easy')">
|
||||
<span class="diff-icon">🟢</span>
|
||||
<span class="diff-text" id="diffEasyText">Leicht</span>
|
||||
</button>
|
||||
<button type="button" class="diff-chip" data-diff="medium" onclick="selectDifficulty('medium')">
|
||||
<span class="diff-icon">🟡</span>
|
||||
<span class="diff-text" id="diffMedText">Mittel</span>
|
||||
</button>
|
||||
<button type="button" class="diff-chip" data-diff="hard" onclick="selectDifficulty('hard')">
|
||||
<span class="diff-icon">🔴</span>
|
||||
<span class="diff-text" id="diffHardText">Schwer (Experte)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Obere Klappe des Umschlags (überdeckt die Tasche und wird vom Siegel gehalten) -->
|
||||
<div class="envelope-closing-flap">
|
||||
<div class="flap-sketch-line"></div>
|
||||
<!-- Sektion 2: Kategorie-Filter -->
|
||||
<div class="config-section">
|
||||
<div class="config-header">
|
||||
<span class="config-step">2</span>
|
||||
<span class="difficulty-label" id="catLabel">Themengebiet wählen</span>
|
||||
</div>
|
||||
|
||||
<!-- Handgezeichnetes Wachssiegel über der Klappenspitze -->
|
||||
<div class="sketched-wax-seal" id="waxSeal" onclick="startGame()" title="Siegel brechen & Brief öffnen">
|
||||
<img src="/static/img/hogwarts_seal.jpg" alt="Hogwarts-Wappen Siegel" class="seal-crest-img">
|
||||
<div class="seal-emboss-ring"></div>
|
||||
<div class="seal-glow"></div>
|
||||
<div class="category-chips" role="radiogroup" aria-label="Themengebiet">
|
||||
<button type="button" class="cat-chip active" data-cat="all" onclick="selectCategory('all')">
|
||||
<span>✨</span> <span id="catAllText">Alle Kategorien</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="books" onclick="selectCategory('books')">
|
||||
<span>📖</span> <span id="catBooksText">Buchwissen</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="movies" onclick="selectCategory('movies')">
|
||||
<span>🎬</span> <span id="catMoviesText">Filmwissen</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="creatures" onclick="selectCategory('creatures')">
|
||||
<span>🐉</span> <span id="catCreaturesText">Magische Geschöpfe</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="spells" onclick="selectCategory('spells')">
|
||||
<span>🪄</span> <span id="catSpellsText">Zaubersprüche & Tränke</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="characters" onclick="selectCategory('characters')">
|
||||
<span>🧙♂️</span> <span id="catCharsText">Charaktere</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="hogwarts" onclick="selectCategory('hogwarts')">
|
||||
<span>🏰</span> <span id="catHogwartsText">Hogwarts & Orte</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="quidditch" onclick="selectCategory('quidditch')">
|
||||
<span>🧹</span> <span id="catQuidditchText">Quidditch</span>
|
||||
</button>
|
||||
<button type="button" class="cat-chip" data-cat="lore" onclick="selectCategory('lore')">
|
||||
<span>🔮</span> <span id="catLoreText">Artefakte & Lore</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auswahlbereich unter dem Pergament -->
|
||||
<div class="intro-content intro-controls-panel">
|
||||
<p class="intro-subnote" id="introDesc">
|
||||
Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.<br>
|
||||
Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:
|
||||
</p>
|
||||
|
||||
<!-- Difficulty Selector Chips -->
|
||||
<div class="difficulty-selection">
|
||||
<span class="difficulty-label" id="diffLabel">Schwierigkeitsgrad wählen:</span>
|
||||
<div class="difficulty-chips" role="radiogroup" aria-label="Schwierigkeitsgrad">
|
||||
<button type="button" class="diff-chip active" data-diff="all" onclick="selectDifficulty('all')">
|
||||
<span class="diff-icon">🎲</span> <span class="diff-text" id="diffAllText">Alle gemischt</span>
|
||||
</button>
|
||||
<button type="button" class="diff-chip" data-diff="easy" onclick="selectDifficulty('easy')">
|
||||
<span class="diff-icon">🟢</span> <span class="diff-text" id="diffEasyText">Leicht</span>
|
||||
</button>
|
||||
<button type="button" class="diff-chip" data-diff="medium" onclick="selectDifficulty('medium')">
|
||||
<span class="diff-icon">🟡</span> <span class="diff-text" id="diffMedText">Mittel</span>
|
||||
</button>
|
||||
<button type="button" class="diff-chip" data-diff="hard" onclick="selectDifficulty('hard')">
|
||||
<span class="diff-icon">🔴</span> <span class="diff-text" id="diffHardText">Schwer</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dashboard-footer">
|
||||
<div class="dashboard-rules" id="dashboardRules">
|
||||
<span>🎯 10 Fragen pro Runde</span>
|
||||
<span>·</span>
|
||||
<span>🔥 Streak-Zähler</span>
|
||||
<span>·</span>
|
||||
<span>💡 Bis zu 3 Versuche je Frage</span>
|
||||
</div>
|
||||
|
||||
<button id="startBtn" class="btn btn-primary btn-large btn-break-seal" onclick="startGame()">
|
||||
📜 Siegel brechen & Brief öffnen
|
||||
<button id="startQuizBtn" class="btn btn-primary btn-large btn-launch-quiz" onclick="startGame()">
|
||||
⚡ Quiz jetzt starten ➔
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spielbereich (initial versteckt) -->
|
||||
<div id="gameCard" class="game-area hidden">
|
||||
<!-- Fortschritt & Punkte & Zeit -->
|
||||
<!-- HUD: Fortschritt, Streak, Zeit & Punkte -->
|
||||
<div class="status-panel">
|
||||
<div class="progress-info">
|
||||
<span id="questionCounter">Frage 1 von 10</span>
|
||||
<span id="timerBadge" class="timer-badge">⏱️ 00:00</span>
|
||||
<span id="pointsCounter" class="points-badge">🏆 Hauspunkte: 0</span>
|
||||
<span id="questionCounter" class="hud-pill">Frage 1 / 10</span>
|
||||
<div class="hud-center-group">
|
||||
<span id="streakBadge" class="streak-badge" title="Antwort-Serie in Folge">🔥 0</span>
|
||||
<span id="timerBadge" class="timer-badge">⏱️ 00:00</span>
|
||||
</div>
|
||||
<span id="pointsCounter" class="points-badge">🏆 0 Pkt.</span>
|
||||
</div>
|
||||
<div class="progress-container">
|
||||
<div id="progressBar" class="progress-bar"></div>
|
||||
@@ -126,24 +112,24 @@
|
||||
<div id="frageBox" class="card question-card">
|
||||
<div class="question-meta">
|
||||
<div class="meta-left">
|
||||
<span id="badgeCategory" class="badge">⚡ Harry Potter</span>
|
||||
<span id="badgeCategory" class="badge">⚡ Allgemein</span>
|
||||
<span id="badgeDifficulty" class="badge badge-diff">🟢 Leicht</span>
|
||||
</div>
|
||||
<span id="attemptBadge" class="attempt-badge">Versuch 1 von 3</span>
|
||||
<span id="attemptBadge" class="attempt-badge">Versuch 1 / 3</span>
|
||||
</div>
|
||||
|
||||
<h2 id="frageText" class="question-text">Lade verzauberte Schriftrollen...</h2>
|
||||
<h2 id="frageText" class="question-text">Lade nächste Frage...</h2>
|
||||
|
||||
<!-- Freitext-Eingabe (wenn question_type == 'text') -->
|
||||
<div id="textAnswerGroup" class="answer-form">
|
||||
<input type="text" id="answerInput" class="input-answer" placeholder="Deine Antwort hier eingeben..." autocomplete="off">
|
||||
<input type="text" id="answerInput" class="input-answer" placeholder="Deine Antwort eingeben..." autocomplete="off">
|
||||
|
||||
<div class="btn-group">
|
||||
<button id="checkBtn" class="btn btn-primary" onclick="checkAnswer()">
|
||||
✨ Antwort prüfen
|
||||
✓ Antwort prüfen
|
||||
</button>
|
||||
<button id="hintBtn" class="btn btn-secondary" onclick="requestHint()">
|
||||
💡 Hinweis anzeigen
|
||||
💡 Tipp anzeigen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,15 +137,15 @@
|
||||
<!-- Multiple-Choice-Auswahl (wenn question_type == 'multiple_choice') -->
|
||||
<div id="choiceAnswerGroup" class="choice-group hidden">
|
||||
<div id="choiceOptionsContainer" class="choice-grid"></div>
|
||||
<div class="btn-group" style="margin-top: 15px;">
|
||||
<div class="btn-group" style="margin-top: 16px;">
|
||||
<button id="hintBtnMC" class="btn btn-secondary" onclick="requestHint()">
|
||||
💡 Hinweis anzeigen
|
||||
💡 Tipp anzeigen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="hintBox" class="hint-box hidden">
|
||||
<strong id="hintHeading">📜 Notiz aus der verbotenen Abteilung:</strong>
|
||||
<strong id="hintHeading">💡 Hinweis:</strong>
|
||||
<p id="hintText"></p>
|
||||
</div>
|
||||
|
||||
@@ -170,5 +156,5 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/static/js/quiz.js?v=3.2"></script>
|
||||
<script src="/static/js/quiz.js?v=4.0"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
{% block content %}
|
||||
<div class="quiz-wrapper">
|
||||
<div class="card vorschlag-card">
|
||||
<h1 class="glow-title" id="suggTitle">✨ Neue Frage für das Quiz vorschlagen ✨</h1>
|
||||
<p class="subtitle" id="suggSubtitle">Bereichere das magische Quiz mit kniffligen Harry-Potter- oder Escape-Rätseln!</p>
|
||||
<h1 class="glow-title" id="suggTitle">✍️ Neue Quiz-Frage einreichen</h1>
|
||||
<p class="subtitle" id="suggSubtitle">Bereichere PotterTrivia mit deinen eigenen Fragen zu Büchern, Filmen oder magischen Geschöpfen!</p>
|
||||
|
||||
<div class="info-alert" id="suggAlert">
|
||||
ℹ️ <strong>Hinweis zur Moderation:</strong> Eingereichte Fragen werden nicht sofort öffentlich sichtbar, sondern erhalten automatisch den Status <em>„Prüfung ausstehend“</em>. Unser Zaubereiministerium prüft und testet die Antwort, bevor die Frage freigeschaltet wird.
|
||||
ℹ️ <strong>Hinweis zur Moderation:</strong> Eingereichte Fragen werden kurz auf Richtigkeit geprüft, bevor sie im öffentlichen Quiz-Pool freigeschaltet werden.
|
||||
</div>
|
||||
|
||||
<form id="suggestionForm" class="magic-form" onsubmit="submitSuggestion(event)">
|
||||
@@ -26,13 +26,15 @@
|
||||
<div class="form-group">
|
||||
<label for="kategorie" id="suggCatLabel">Kategorie / Themenbereich:</label>
|
||||
<select id="kategorie" name="category" class="input-text">
|
||||
<option value="hogwarts" id="catHogwarts">🏰 Hogwarts & Schulgelände</option>
|
||||
<option value="characters" id="catCharacters" selected>🧙 Charaktere & Personen</option>
|
||||
<option value="spells" id="catSpells">⚡ Zaubersprüche & Magie</option>
|
||||
<option value="creatures" id="catCreatures">🐉 Magische Tierwesen</option>
|
||||
<option value="books" id="catBooks">📖 Buchwissen (Buch-Details)</option>
|
||||
<option value="movies" id="catMovies">🎬 Filmwissen (Kino & Drehorte)</option>
|
||||
<option value="creatures" id="catCreatures">🐉 Magische Geschöpfe</option>
|
||||
<option value="spells" id="catSpells">🪄 Zaubersprüche & Tränke</option>
|
||||
<option value="characters" id="catCharacters" selected>🧙♂️ Charaktere & Personen</option>
|
||||
<option value="hogwarts" id="catHogwarts">🏰 Hogwarts & Orte</option>
|
||||
<option value="quidditch" id="catQuidditch">🧹 Quidditch & Sport</option>
|
||||
<option value="lore" id="catLore">🔮 Artefakte & Geschichte</option>
|
||||
<option value="general" id="catGeneral">📜 Allgemeines Zauberwissen</option>
|
||||
<option value="general" id="catGeneral">⚡ Allgemeines Wissen</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
annotated-doc==0.0.5
|
||||
annotated-types==0.8.0
|
||||
anyio==4.15.1
|
||||
cffi==2.1.1
|
||||
click==8.5.0
|
||||
cryptography==50.0.1
|
||||
fastapi==0.141.1
|
||||
greenlet==3.5.6
|
||||
h11==0.16.0
|
||||
httptools==0.8.0
|
||||
idna==3.20
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.3
|
||||
pycparser==3.0
|
||||
pydantic==2.13.5
|
||||
pydantic_core==2.46.5
|
||||
PyMySQL==1.2.3
|
||||
python-dotenv==1.2.3
|
||||
python-multipart==0.0.32
|
||||
PyYAML==6.0.3
|
||||
SQLAlchemy==2.0.54
|
||||
starlette==1.6.0
|
||||
typing-inspection==0.4.4
|
||||
typing_extensions==4.16.0
|
||||
uvicorn==0.53.0
|
||||
uvloop==0.22.1
|
||||
watchfiles==1.3.0
|
||||
websockets==17.1
|
||||
Reference in New Issue
Block a user