diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..c0f52ae
--- /dev/null
+++ b/INSTALL.md
@@ -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;
+ }
+}
+```
diff --git a/app/main.py b/app/main.py
index 1623fe2..b251c38 100755
--- a/app/main.py
+++ b/app/main.py
@@ -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(
diff --git a/app/matcher.py b/app/matcher.py
index a751234..a55bfaa 100755
--- a/app/matcher.py
+++ b/app/matcher.py
@@ -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
diff --git a/app/schemas.py b/app/schemas.py
index 94fd538..c8e9cf6 100755
--- a/app/schemas.py
+++ b/app/schemas.py
@@ -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
diff --git a/app/seed.py b/app/seed.py
index 371dce2..45c97e5 100755
--- a/app/seed.py
+++ b/app/seed.py
@@ -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()
diff --git a/app/seed_lore_pack.py b/app/seed_lore_pack.py
new file mode 100644
index 0000000..03f65ee
--- /dev/null
+++ b/app/seed_lore_pack.py
@@ -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()
diff --git a/app/static/css/style.css b/app/static/css/style.css
index 580ffd7..6959cee 100755
--- a/app/static/css/style.css
+++ b/app/static/css/style.css
@@ -1,1765 +1,1465 @@
-:root {
- --bg-dark: #0b0b16;
- --bg-card: rgba(20, 22, 40, 0.9);
- --bg-card-hover: rgba(28, 31, 56, 0.95);
- --gold: #c9a86a;
- --gold-bright: #ffd700;
- --gold-soft: #e6c98f;
- --text-main: #f5f5f7;
- --text-muted: #b8b8cc;
- --parchment: #f6eedb;
- --wax-red: #8b0000;
- --success: #2ecc71;
- --danger: #e74c3c;
- --warning: #f39c12;
- --border-gold: rgba(201, 168, 106, 0.35);
- --header-height: 56px;
-}
-
-* {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- -webkit-tap-highlight-color: transparent;
-}
-
-body {
- background-color: var(--bg-dark);
- background-image:
- radial-gradient(circle at 15% 15%, rgba(201, 168, 106, 0.08) 0%, transparent 45%),
- radial-gradient(circle at 85% 85%, rgba(116, 0, 1, 0.08) 0%, transparent 50%);
- color: var(--text-main);
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Georgia", "Times New Roman", serif;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- overflow-x: hidden;
- line-height: 1.5;
-}
-
-/* =========================================
- HEADER & NAVIGATION (Mobile-First)
- ========================================= */
-.site-header {
- background: rgba(11, 11, 22, 0.85);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- border-bottom: 1px solid var(--border-gold);
- position: sticky;
- top: 0;
- z-index: 200;
- width: 100%;
-}
-
-.header-inner {
- max-width: 900px;
- margin: 0 auto;
- padding: 0 16px;
- height: var(--header-height);
- display: flex;
- justify-content: space-between;
- align-items: center;
- position: relative;
-}
-
-.brand-link {
- display: flex;
- align-items: center;
- gap: 8px;
- text-decoration: none;
- color: var(--gold);
- font-weight: bold;
- font-size: 1.15rem;
- letter-spacing: 0.5px;
-}
-
-.brand-sparkle {
- font-size: 1.25rem;
-}
-
-.header-right {
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-.lang-switch-group {
- display: inline-flex;
- background: rgba(20, 22, 40, 0.85);
- border: 1px solid var(--border-gold);
- border-radius: 20px;
- padding: 2px;
- overflow: hidden;
-}
-
-.lang-btn {
- background: transparent;
- border: none;
- color: var(--text-muted);
- font-size: 0.8rem;
- font-weight: 600;
- padding: 4px 9px;
- border-radius: 16px;
- cursor: pointer;
- transition: all 0.2s ease;
- min-height: 28px;
- display: flex;
- align-items: center;
- gap: 4px;
-}
-
-.lang-btn:hover {
- color: var(--gold);
-}
-
-.lang-btn.active {
- background: var(--gold);
- color: #0b0b16;
- box-shadow: 0 0 8px rgba(201, 168, 106, 0.4);
-}
-
-.audio-btn {
- background: rgba(201, 168, 106, 0.15);
- border: 1px solid var(--gold);
- color: var(--gold-soft);
- padding: 6px 12px;
- border-radius: 20px;
- cursor: pointer;
- font-size: 0.85rem;
- display: inline-flex;
- align-items: center;
- gap: 6px;
- transition: all 0.2s ease;
- min-height: 36px;
-}
-
-.audio-btn:hover {
- background: var(--gold);
- color: #000;
-}
-
-.menu-toggle-btn {
- display: none;
- background: transparent;
- border: 1px solid var(--border-gold);
- color: var(--gold);
- font-size: 1.3rem;
- width: 38px;
- height: 38px;
- border-radius: 8px;
- cursor: pointer;
- align-items: center;
- justify-content: center;
-}
-
-.nav-links {
- display: flex;
- gap: 18px;
- align-items: center;
-}
-
-.nav-link {
- color: var(--text-muted);
- text-decoration: none;
- font-size: 0.9rem;
- padding: 8px 12px;
- border-radius: 6px;
- transition: all 0.2s ease;
- white-space: nowrap;
-}
-
-.nav-link:hover {
- color: var(--gold-bright);
- background: rgba(201, 168, 106, 0.1);
-}
-
-/* =========================================
- CONTAINER & LAYOUT
- ========================================= */
-.container {
- flex: 1;
- max-width: 760px;
- width: 100%;
- margin: 20px auto;
- padding: 0 16px;
-}
-
-.admin-wrapper {
- max-width: 900px;
- width: 100%;
- margin: 20px auto;
- padding: 0 16px;
-}
-
-.quiz-wrapper {
- width: 100%;
-}
-
-.quiz-header {
- text-align: center;
- margin-bottom: 20px;
-}
-
-.glow-title {
- color: var(--gold);
- font-size: clamp(1.35rem, 5vw, 2.1rem);
- letter-spacing: 0.5px;
- text-shadow: 0 0 14px rgba(201, 168, 106, 0.4);
- margin-bottom: 6px;
- word-break: break-word;
-}
-
-.subtitle {
- color: var(--text-muted);
- font-size: clamp(0.85rem, 3vw, 1rem);
- font-style: italic;
- margin-bottom: 15px;
-}
-
-/* =========================================
- CARD COMPONENT
- ========================================= */
-.card {
- background: var(--bg-card);
- border: 1px solid var(--border-gold);
- border-radius: 14px;
- padding: clamp(16px, 4vw, 28px);
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.45);
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
- margin-bottom: 20px;
- width: 100%;
-}
-
-/* =========================================
- ANTIQUE HOGWARTS CLOSED ENVELOPE & LETTER REVEAL
- ========================================= */
-.intro-parchment-wrapper {
- background: transparent !important;
- border: none !important;
- box-shadow: none !important;
- padding: 0 !important;
- max-width: 600px;
- margin: 0 auto;
-}
-
-.antique-envelope-scene {
- position: relative;
- width: min(440px, 92vw);
- height: min(260px, 58vw);
- margin: 25px auto 35px;
- perspective: 1200px;
- perspective-origin: center top;
-}
-
-.envelope-base {
- position: relative;
- width: 100%;
- height: 100%;
- border-radius: 6px;
- box-shadow:
- 0 20px 45px rgba(0, 0, 0, 0.75),
- 0 4px 14px rgba(0, 0, 0, 0.5);
- transition: transform 0.6s ease;
-}
-
-/* Interior of envelope (visible when flap opens and letter rises) */
-.envelope-interior {
- position: absolute;
- inset: 0;
- border-radius: 6px;
- background-color: #2b1d12;
- background-image:
- radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.65) 100%);
- border: 2px solid #7a5c36;
- z-index: 1;
-}
-
-/* The Letter inside the envelope (slides up when opened) */
-.letter-sheet {
- position: absolute;
- bottom: 8px;
- left: 4%;
- width: 92%;
- height: 92%;
- background-color: #fcf8ee;
- background-image:
- radial-gradient(ellipse at 50% 25%, rgba(255, 255, 255, 0.6) 0%, rgba(210, 180, 130, 0.25) 100%);
- background-position: center;
- border-radius: 4px;
- border: 1.5px solid #8e6c43;
- padding: 16px 18px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
- z-index: 2;
- transition: transform 1.1s cubic-bezier(0.2, 0.8, 0.25, 1) 0.35s, box-shadow 0.8s ease, z-index 0.2s 0.35s;
- overflow: hidden;
- text-align: center;
- outline: 1px dashed rgba(130, 95, 55, 0.45);
- outline-offset: -5px;
-}
-
-/* Front Pocket of envelope (covers the lower portion of the letter) */
-.envelope-front-pocket {
- position: absolute;
- inset: 0;
- border-radius: 6px;
- background-color: #ebdcb9;
- background-image:
- radial-gradient(ellipse at 50% 65%, rgba(255, 255, 255, 0.35) 0%, rgba(160, 120, 60, 0.2) 100%);
- clip-path: polygon(0 32%, 50% 54%, 100% 32%, 100% 100%, 0 100%);
- z-index: 4;
- border: 2px solid #8e6c43;
- filter: drop-shadow(0 -3px 6px rgba(60, 40, 15, 0.3));
- display: flex;
- align-items: flex-end;
- justify-content: center;
- padding-bottom: 20px;
- pointer-events: none;
-}
-
-.envelope-sketch-border {
- position: absolute;
- bottom: 8px;
- left: 8px;
- right: 8px;
- height: 48%;
- border: 1px dashed rgba(120, 85, 45, 0.4);
- border-radius: 3px;
- pointer-events: none;
-}
-
-/* Handwritten Address in Emerald Ink on Envelope Front */
-.envelope-address-block {
- position: relative;
- z-index: 5;
- text-align: center;
- font-family: 'Marck Script', 'Caveat', cursive;
- color: #0d3820; /* Authentic Hogwarts Emerald Ink */
- padding: 0 15px;
- text-shadow: 0 0 1px rgba(13, 56, 32, 0.35);
-}
-
-.addr-school {
- font-size: clamp(0.92rem, 3.2vw, 1.15rem);
- font-weight: 700;
- color: #082816;
- margin-bottom: 2px;
-}
-
-.addr-recipient {
- font-size: clamp(1.15rem, 3.8vw, 1.4rem);
- font-weight: 700;
- color: #0b3a20;
- margin-bottom: 2px;
-}
-
-.addr-location {
- font-size: clamp(0.85rem, 2.8vw, 1.02rem);
- font-style: italic;
- color: #1e4530;
-}
-
-/* Top Triangular Flap (folds down over the pocket) */
-.envelope-closing-flap {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: #dfcd9f;
- background-image:
- radial-gradient(ellipse at 50% 25%, rgba(255, 255, 255, 0.4) 0%, rgba(150, 110, 55, 0.25) 100%);
- clip-path: polygon(0 0, 100% 0, 50% 57%);
- transform-origin: top center;
- transform-style: preserve-3d;
- transition: transform 0.75s cubic-bezier(0.4, 0, 0.2, 1);
- z-index: 6;
- border-top: 2px solid #8e6c43;
- filter: drop-shadow(0 5px 8px rgba(50, 35, 10, 0.5));
-}
-
-.flap-sketch-line {
- position: absolute;
- inset: 4px;
- clip-path: polygon(2% 2%, 98% 2%, 50% 54%);
- border-top: 1px dashed rgba(120, 85, 45, 0.4);
- pointer-events: none;
-}
-
-/* Sketched Wax Seal placed right over the flap tip */
-.sketched-wax-seal {
- position: absolute;
- top: 56%;
- left: 50%;
- transform: translate(-50%, -50%) rotate(-3deg);
- width: 72px;
- height: 72px;
- border-radius: 50%;
- cursor: pointer;
- z-index: 7;
- transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease, opacity 0.4s ease;
- box-shadow:
- 0 8px 20px rgba(0, 0, 0, 0.7),
- 0 2px 5px rgba(0, 0, 0, 0.45);
-}
-
-.sketched-wax-seal:hover {
- transform: translate(-50%, -50%) rotate(0deg) scale(1.1);
- box-shadow:
- 0 12px 28px rgba(0, 0, 0, 0.8),
- 0 0 16px rgba(218, 165, 32, 0.45);
-}
-
-.seal-crest-img {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- object-fit: cover;
- display: block;
- border: 3px solid #4a0d0d;
- box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.7);
-}
-
-.seal-emboss-ring {
- position: absolute;
- inset: 2px;
- border-radius: 50%;
- border: 1.5px solid rgba(255, 215, 0, 0.25);
- box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.2), inset 0 -3px 6px rgba(0, 0, 0, 0.6);
- pointer-events: none;
-}
-
-.seal-glow {
- position: absolute;
- inset: -6px;
- border-radius: 50%;
- background: radial-gradient(circle, rgba(255, 215, 0, 0.35) 0%, transparent 70%);
- opacity: 0;
- transition: opacity 0.3s ease;
- pointer-events: none;
-}
-
-.sketched-wax-seal:hover .seal-glow {
- opacity: 1;
-}
-
-/* Open state: Flap opens, seal dissolves, letter slides out */
-.envelope-opened .sketched-wax-seal {
- animation: breakSealAnim 0.7s forwards;
-}
-
-.envelope-opened .envelope-closing-flap {
- transform: rotateX(180deg);
- z-index: 1;
- filter: drop-shadow(0 -4px 6px rgba(50, 35, 10, 0.4));
-}
-
-.envelope-opened .letter-sheet {
- transform: translateY(-135px) scale(1.06);
- z-index: 10;
- box-shadow:
- 0 20px 48px rgba(0, 0, 0, 0.85),
- 0 0 25px rgba(201, 168, 106, 0.35);
-}
-
-@keyframes breakSealAnim {
- 0% { transform: translate(-50%, -50%) rotate(-3deg) scale(1); opacity: 1; filter: none; }
- 50% { transform: translate(-50%, -50%) rotate(10deg) scale(1.2); opacity: 0.8; filter: drop-shadow(0 0 25px #ffd700); }
- 100% { transform: translate(-50%, -50%) rotate(25deg) scale(0.3); opacity: 0; filter: drop-shadow(0 0 40px #ffd700); }
-}
-
-/* Letter Content Styles */
-.letter-header {
- margin-bottom: 8px;
-}
-
-.letter-crest-box {
- width: 48px;
- height: 48px;
- margin: 0 auto 4px;
- border-radius: 50%;
- overflow: hidden;
- box-shadow: 0 2px 6px rgba(90, 60, 20, 0.25);
- border: 1px solid rgba(139, 105, 60, 0.3);
-}
-
-.letter-crest-sketch {
- width: 100%;
- height: 100%;
- object-fit: cover;
- filter: sepia(0.65) contrast(1.15) brightness(0.92);
-}
-
-.letter-school-name {
- font-family: 'MedievalSharp', Georgia, serif;
- font-size: clamp(0.95rem, 2.8vw, 1.25rem);
- color: #2c1a0c;
- text-transform: uppercase;
- letter-spacing: 1.2px;
- font-weight: 700;
- margin-bottom: 2px;
- text-shadow: 0 1px 1px rgba(255, 255, 255, 0.6);
-}
-
-.letter-headmaster {
- font-family: 'Caveat', 'Georgia', cursive;
- font-size: 0.95rem;
- color: #4a331c;
- font-style: italic;
- line-height: 1.25;
- margin-bottom: 6px;
-}
-
-.letter-headmaster span {
- font-size: 0.82rem;
- color: #6a4c28;
-}
-
-.letter-ornament-line {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
- color: #8c6838;
- margin: 4px auto 8px;
- max-width: 260px;
-}
-
-.ornament-rule {
- flex: 1;
- height: 1px;
- background: linear-gradient(to right, transparent, #8c6838, transparent);
-}
-
-.ornament-leaf, .ornament-shield {
- font-size: 0.85rem;
-}
-
-/* Handwritten Body in Emerald Ink */
-.letter-body-handwritten {
- font-family: 'Marck Script', 'Caveat', cursive;
- color: #0f3d24; /* Authentic Hogwarts Emerald Ink */
- text-align: left;
- padding: 0 4px;
- line-height: 1.5;
- text-shadow: 0 0 1px rgba(15, 61, 36, 0.25);
-}
-
-.letter-salutation {
- font-size: clamp(1.15rem, 3.5vw, 1.4rem);
- font-weight: 700;
- margin-bottom: 6px;
- color: #0a301c;
-}
-
-.letter-paragraph {
- font-size: clamp(1.02rem, 3.0vw, 1.2rem);
- margin-bottom: 8px;
- text-indent: 1.2em;
-}
-
-.letter-paragraph strong {
- color: #052414;
- font-weight: 700;
-}
-
-/* Signature Block */
-.letter-signature-block {
- margin-top: 10px;
- text-align: right;
- padding-right: 8px;
- font-family: 'Marck Script', 'Caveat', cursive;
- color: #0f3d24;
-}
-
-.letter-closing {
- font-size: 1.1rem;
- font-style: italic;
- color: #2b4533;
- margin-bottom: 2px;
-}
-
-.letter-signature-ink {
- font-size: clamp(1.4rem, 4.2vw, 1.8rem);
- font-weight: 700;
- color: #082816;
- letter-spacing: 0.5px;
- transform: rotate(-2deg);
- display: inline-block;
- margin-bottom: 1px;
- text-shadow: 0 0 1px rgba(8, 40, 22, 0.35);
-}
-
-.letter-sign-title {
- font-size: 0.88rem;
- color: #4a6352;
- font-style: italic;
-}
-
-/* Controls Below Parchment */
-.intro-controls-panel {
- margin-top: 20px;
- text-align: center;
-}
-
-.intro-subnote {
- font-size: 0.95rem;
- color: var(--text-muted);
- line-height: 1.5;
- margin-bottom: 14px;
-}
-
-.btn-break-seal {
- font-size: 1.12rem;
- padding: 14px 28px;
- box-shadow: 0 6px 20px rgba(201, 168, 106, 0.4);
- transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-.btn-break-seal:hover {
- transform: translateY(-2px) scale(1.02);
- box-shadow: 0 10px 28px rgba(201, 168, 106, 0.6);
-}
-
-@media (max-width: 480px) {
- .antique-envelope-scene {
- width: min(320px, 92vw);
- height: min(205px, 58vw);
- margin: 15px auto 25px;
- }
- .sketched-wax-seal {
- width: 60px;
- height: 60px;
- top: 56%;
- }
- .envelope-opened .letter-sheet {
- transform: translateY(-100px) scale(1.04);
- }
- .envelope-front-pocket {
- padding-bottom: 14px;
- }
-}
-
-.difficulty-selection {
- margin: 18px 0 22px;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 10px;
-}
-
-.difficulty-label {
- font-size: 0.95rem;
- color: var(--gold-soft);
- font-weight: 600;
-}
-
-.difficulty-chips {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: center;
-}
-
-.diff-chip {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid var(--border-gold);
- color: var(--text-muted);
- border-radius: 20px;
- padding: 8px 16px;
- font-size: 0.88rem;
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- gap: 6px;
- transition: all 0.2s ease;
- font-family: inherit;
-}
-
-.diff-chip:hover {
- border-color: var(--gold);
- color: var(--text-main);
- transform: translateY(-1px);
- background: rgba(201, 168, 106, 0.12);
-}
-
-.diff-chip.active {
- background: rgba(201, 168, 106, 0.25);
- border-color: var(--gold);
- color: var(--gold-bright);
- box-shadow: 0 0 10px rgba(201, 168, 106, 0.35);
- font-weight: bold;
-}
-
-/* =========================================
- BUTTONS
- ========================================= */
-.btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 12px 22px;
- border-radius: 8px;
- font-size: 0.95rem;
- font-weight: bold;
- cursor: pointer;
- text-decoration: none;
- transition: all 0.2s ease;
- border: none;
- font-family: inherit;
- min-height: 46px;
- touch-action: manipulation;
-}
-
-.btn-primary {
- background: linear-gradient(135deg, var(--gold) 0%, #b8860b 100%);
- color: #0c0d14;
- box-shadow: 0 4px 15px rgba(201, 168, 106, 0.3);
-}
-
-.btn-primary:hover:not(:disabled) {
- background: linear-gradient(135deg, #ffd700 0%, var(--gold) 100%);
- box-shadow: 0 6px 20px rgba(201, 168, 106, 0.5);
- transform: translateY(-2px);
-}
-
-.btn-secondary {
- background: rgba(255, 255, 255, 0.08);
- color: var(--gold-soft);
- border: 1px solid var(--gold);
-}
-
-.btn-secondary:hover:not(:disabled) {
- background: rgba(201, 168, 106, 0.2);
-}
-
-.btn-danger {
- background: rgba(231, 76, 60, 0.2);
- color: #ff7675;
- border: 1px solid var(--danger);
-}
-
-.btn-danger:hover:not(:disabled) {
- background: var(--danger);
- color: #fff;
-}
-
-.btn-success {
- background: rgba(46, 204, 113, 0.2);
- color: #55efc4;
- border: 1px solid var(--success);
-}
-
-.btn-success:hover:not(:disabled) {
- background: var(--success);
- color: #000;
-}
-
-.btn-large {
- font-size: 1.05rem;
- padding: 14px 28px;
- min-height: 50px;
-}
-
-.btn-sm {
- font-size: 0.85rem;
- padding: 8px 14px;
- min-height: 38px;
-}
-
-.btn-group {
- display: flex;
- gap: 10px;
- justify-content: center;
- margin-top: 18px;
- width: 100%;
-}
-
-.btn:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- transform: none !important;
-}
-
-/* =========================================
- STATUS & PROGRESS
- ========================================= */
-.status-panel {
- margin-bottom: 16px;
- width: 100%;
-}
-
-.progress-info {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- font-size: clamp(0.8rem, 3vw, 0.95rem);
-}
-
-.points-badge {
- color: var(--gold-bright);
- font-weight: bold;
- text-shadow: 0 0 8px rgba(255, 215, 0, 0.4);
-}
-
-.progress-container {
- width: 100%;
- height: 10px;
- background: rgba(255, 255, 255, 0.08);
- border: 1px solid var(--border-gold);
- border-radius: 8px;
- overflow: hidden;
-}
-
-.progress-bar {
- height: 100%;
- width: 0%;
- background: linear-gradient(90deg, var(--gold) 0%, #ffd700 100%);
- box-shadow: 0 0 8px rgba(255, 215, 0, 0.5);
- transition: width 0.35s ease;
-}
-
-/* =========================================
- QUESTION CARD & ANSWERS
- ========================================= */
-.question-card {
- text-align: center;
-}
-
-.question-meta {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 14px;
- gap: 8px;
-}
-
-.meta-left {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-wrap: wrap;
-}
-
-.badge {
- padding: 4px 10px;
- border-radius: 12px;
- font-size: 0.75rem;
- font-weight: bold;
- background: rgba(201, 168, 106, 0.15);
- border: 1px solid var(--gold);
- color: var(--gold-soft);
- white-space: nowrap;
-}
-
-.badge-diff {
- background: rgba(255, 255, 255, 0.06);
- border: 1px solid rgba(255, 255, 255, 0.2);
- color: var(--text-main);
-}
-
-.badge-diff-easy {
- background: rgba(46, 204, 113, 0.15);
- border-color: #2ecc71;
- color: #2ecc71;
-}
-
-.badge-diff-medium {
- background: rgba(243, 156, 18, 0.15);
- border-color: #f39c12;
- color: #f39c12;
-}
-
-.badge-diff-hard {
- background: rgba(231, 76, 60, 0.15);
- border-color: #e74c3c;
- color: #e74c3c;
-}
-
-.badge-warning {
- background: rgba(243, 156, 18, 0.2);
- border-color: var(--warning);
- color: #f39c12;
-}
-
-.badge-success {
- background: rgba(46, 204, 113, 0.2);
- border-color: var(--success);
- color: #2ecc71;
-}
-
-.badge-danger {
- background: rgba(231, 76, 60, 0.2);
- border-color: var(--danger);
- color: #e74c3c;
-}
-
-.badge-muted {
- background: rgba(255, 255, 255, 0.1);
- border-color: rgba(255, 255, 255, 0.2);
- color: var(--text-muted);
-}
-
-.attempt-badge {
- font-size: 0.8rem;
- color: var(--text-muted);
- white-space: nowrap;
-}
-
-.timer-badge {
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- background: rgba(243, 156, 18, 0.15);
- border: 1px solid #f39c12;
- color: #ffd700;
- font-weight: 700;
- letter-spacing: 0.5px;
-}
-
-.question-text {
- font-size: clamp(1.15rem, 4.5vw, 1.45rem);
- line-height: 1.4;
- text-align: center;
- margin: 15px 0 20px;
- color: var(--text-main);
- word-break: break-word;
-}
-
-/* Freitext Input */
-.answer-form {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 100%;
-}
-
-.input-answer {
- width: 100%;
- max-width: 450px;
- padding: 14px;
- font-size: 16px; /* Prevents iOS auto-zoom */
- text-align: center;
- border-radius: 8px;
- border: 2px solid var(--border-gold);
- background: rgba(0, 0, 0, 0.35);
- color: var(--text-main);
- outline: none;
- transition: all 0.2s ease;
- font-family: inherit;
- min-height: 48px;
-}
-
-.input-answer:focus {
- border-color: var(--gold);
- box-shadow: 0 0 12px rgba(201, 168, 106, 0.4);
-}
-
-/* Multiple Choice Grid */
-.choice-group {
- width: 100%;
- margin-top: 5px;
-}
-
-.choice-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 12px;
- width: 100%;
-}
-
-.choice-btn {
- background: rgba(255, 255, 255, 0.05);
- border: 2px solid var(--border-gold);
- border-radius: 10px;
- padding: 12px 14px;
- color: var(--text-main);
- font-size: clamp(0.9rem, 3.5vw, 1.05rem);
- font-family: inherit;
- cursor: pointer;
- text-align: left;
- display: flex;
- align-items: center;
- gap: 10px;
- transition: all 0.2s ease;
- min-height: 52px;
- width: 100%;
- word-break: break-word;
- touch-action: manipulation;
-}
-
-.choice-btn:hover:not(:disabled) {
- background: rgba(201, 168, 106, 0.2);
- border-color: var(--gold);
- transform: translateY(-2px);
-}
-
-.choice-btn:active:not(:disabled) {
- transform: scale(0.98);
-}
-
-.choice-letter {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
- background: rgba(201, 168, 106, 0.2);
- border: 1px solid var(--gold);
- border-radius: 50%;
- color: var(--gold-bright);
- font-weight: bold;
- font-size: 0.85rem;
- flex-shrink: 0;
-}
-
-.choice-text {
- flex: 1;
-}
-
-.choice-selected {
- border-color: var(--gold-bright);
- background: rgba(201, 168, 106, 0.3);
-}
-
-.choice-correct {
- background: rgba(46, 204, 113, 0.25) !important;
- border-color: #2ecc71 !important;
- color: #55efc4 !important;
- box-shadow: 0 0 12px rgba(46, 204, 113, 0.6) !important;
-}
-
-.choice-wrong {
- background: rgba(231, 76, 60, 0.25) !important;
- border-color: #e74c3c !important;
- color: #ff7675 !important;
- box-shadow: 0 0 12px rgba(231, 76, 60, 0.5) !important;
-}
-
-.hint-box {
- margin-top: 15px;
- padding: 12px 14px;
- border-radius: 8px;
- background: rgba(201, 168, 106, 0.1);
- border-left: 4px solid var(--gold);
- font-size: 0.9rem;
- color: var(--gold-soft);
- text-align: left;
- word-break: break-word;
-}
-
-.feedback-box {
- margin-top: 15px;
- font-size: clamp(0.95rem, 3.5vw, 1.1rem);
- text-align: center;
- min-height: 28px;
- font-weight: bold;
- word-break: break-word;
-}
-
-.feedback-correct {
- color: var(--success);
- text-shadow: 0 0 8px rgba(46, 204, 113, 0.4);
-}
-
-.feedback-wrong {
- color: var(--danger);
-}
-
-.feedback-revealed {
- color: var(--warning);
- font-size: 0.9rem;
- margin-top: 4px;
-}
-
-/* =========================================
- ANIMATIONS
- ========================================= */
-.sparkle-effect {
- animation: sparkle-glow 0.8s ease forwards;
-}
-
-@keyframes sparkle-glow {
- 0% { box-shadow: 0 0 5px rgba(255, 215, 0, 0.2); }
- 50% { box-shadow: 0 0 25px rgba(255, 215, 0, 0.8), inset 0 0 15px rgba(255, 215, 0, 0.4); border-color: #ffd700; }
- 100% { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.45); }
-}
-
-.shake-effect {
- animation: shake 0.4s ease;
-}
-
-@keyframes shake {
- 0%, 100% { transform: translateX(0); }
- 20%, 60% { transform: translateX(-6px); }
- 40%, 80% { transform: translateX(6px); }
-}
-
-/* =========================================
- FINALE PAGE
- ========================================= */
-.finale-card {
- text-align: center;
-}
-
-.finale-header {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 10px;
- margin-bottom: 15px;
-}
-
-.sparkle-icon {
- font-size: 1.5rem;
-}
-
-.score-banner {
- background: rgba(201, 168, 106, 0.1);
- border: 1px dashed var(--gold);
- border-radius: 8px;
- padding: 14px;
- margin-bottom: 20px;
-}
-
-.score-highlight {
- font-size: clamp(1rem, 4vw, 1.25rem);
-}
-
-.gold-text {
- color: var(--gold-bright);
- font-weight: bold;
- font-size: 1.35rem;
-}
-
-.reward-box {
- background: rgba(0, 0, 0, 0.3);
- border-radius: 10px;
- padding: 18px 14px;
- margin-bottom: 20px;
- border-left: 4px solid var(--gold);
- text-align: left;
-}
-
-.reward-box h2 {
- color: var(--gold-soft);
- font-size: clamp(1.1rem, 4vw, 1.35rem);
- margin-bottom: 12px;
-}
-
-.reward-description {
- font-size: clamp(0.95rem, 3.5vw, 1.05rem);
- line-height: 1.6;
- margin-bottom: 12px;
- color: var(--text-main);
-}
-
-.love-note {
- font-style: italic;
- color: var(--gold);
- font-size: 1rem;
-}
-
-.time-highlight {
- margin-top: 8px;
- font-size: clamp(0.9rem, 3.2vw, 1.05rem);
- color: var(--text-muted);
-}
-
-/* Tiered evaluation styling */
-.tier-box {
- position: relative;
- border-radius: 14px;
- padding: 24px 18px;
- margin: 20px 0;
- border: 2px solid var(--border-gold);
- background: radial-gradient(circle at top, rgba(201, 168, 106, 0.12) 0%, rgba(15, 17, 30, 0.85) 100%);
- text-align: center;
-}
-
-.tier-badge-container {
- display: flex;
- justify-content: center;
- margin-bottom: 14px;
-}
-
-.tier-grade-badge {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 64px;
- height: 64px;
- border-radius: 50%;
- font-size: 2rem;
- font-weight: 900;
- text-shadow: 0 2px 4px rgba(0,0,0,0.6);
- box-shadow: 0 4px 15px rgba(0,0,0,0.5);
- border: 3px solid;
-}
-
-.tier-outstanding {
- background: radial-gradient(circle, #ffd700, #b8860b);
- color: #0b0b16;
- border-color: #fff275;
- box-shadow: 0 0 20px rgba(255, 215, 0, 0.6);
-}
-
-.tier-exceeds {
- background: radial-gradient(circle, #2ecc71, #27ae60);
- color: #0b0b16;
- border-color: #a8f5cc;
- box-shadow: 0 0 20px rgba(46, 204, 113, 0.5);
-}
-
-.tier-acceptable {
- background: radial-gradient(circle, #3498db, #2980b9);
- color: #ffffff;
- border-color: #a0d3f8;
- box-shadow: 0 0 20px rgba(52, 152, 219, 0.5);
-}
-
-.tier-poor {
- background: radial-gradient(circle, #e67e22, #d35400);
- color: #ffffff;
- border-color: #f7b278;
- box-shadow: 0 0 20px rgba(230, 126, 34, 0.5);
-}
-
-.tier-troll {
- background: radial-gradient(circle, #7f8c8d, #34495e);
- color: #ff7675;
- border-color: #e74c3c;
- box-shadow: 0 0 20px rgba(231, 76, 60, 0.5);
-}
-
-.tier-subtitle {
- font-style: italic;
- color: var(--gold-soft);
- margin-bottom: 12px;
- font-size: 1rem;
-}
-
-/* Leaderboard entry card & form on finale */
-.leaderboard-entry-card {
- background: rgba(14, 16, 32, 0.85);
- border: 1px dashed var(--gold);
- padding: 20px;
- margin-top: 20px;
- text-align: center;
-}
-
-.input-with-btn {
- display: flex;
- gap: 10px;
- max-width: 480px;
- margin: 0 auto;
- width: 100%;
-}
-
-.input-with-btn .input-text {
- flex: 1;
- text-align: left;
-}
-
-.btn-dice {
- background: rgba(201, 168, 106, 0.18);
- border: 1px solid var(--gold);
- color: var(--gold-bright);
- padding: 0 14px;
- border-radius: 8px;
- cursor: pointer;
- font-size: 0.95rem;
- font-family: inherit;
- font-weight: bold;
- display: inline-flex;
- align-items: center;
- gap: 6px;
- justify-content: center;
- transition: all 0.2s ease;
- min-height: 46px;
- white-space: nowrap;
-}
-
-.btn-dice:hover {
- background: var(--gold);
- color: #0b0b16;
- box-shadow: 0 0 12px rgba(201, 168, 106, 0.5);
- transform: translateY(-1px);
-}
-
-/* Leaderboard tables & filters */
-.diff-filter-tabs {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: center;
- margin: 15px 0;
-}
-
-.diff-filter-tabs .tab-btn {
- border-radius: 20px;
- padding: 8px 16px;
- font-size: 0.88rem;
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid var(--border-gold);
- color: var(--text-muted);
-}
-
-.diff-filter-tabs .tab-btn.active {
- background: rgba(201, 168, 106, 0.25);
- border-color: var(--gold);
- color: var(--gold-bright);
- box-shadow: 0 0 10px rgba(201, 168, 106, 0.35);
-}
-
-.table-responsive {
- width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
-}
-
-.lb-table {
- width: 100%;
- border-collapse: collapse;
- text-align: left;
- font-size: 0.9rem;
-}
-
-.lb-table th {
- background: rgba(201, 168, 106, 0.12);
- color: var(--gold-soft);
- padding: 10px 12px;
- border-bottom: 2px solid var(--border-gold);
- white-space: nowrap;
-}
-
-.lb-table td {
- padding: 10px 12px;
- border-bottom: 1px solid rgba(201, 168, 106, 0.15);
- color: var(--text-main);
-}
-
-.lb-table tr:hover td {
- background: rgba(201, 168, 106, 0.05);
-}
-
-.lb-rank {
- font-weight: bold;
- color: var(--gold);
-}
-
-.lb-rank-1 { color: #ffd700; font-size: 1.1rem; }
-.lb-rank-2 { color: #c0c0c0; font-size: 1.05rem; }
-.lb-rank-3 { color: #cd7f32; font-size: 1.05rem; }
-
-/* =========================================
- FORMS & INPUTS
- ========================================= */
-.magic-form {
- display: flex;
- flex-direction: column;
- gap: 16px;
- text-align: left;
- width: 100%;
-}
-
-.form-group {
- display: flex;
- flex-direction: column;
- gap: 6px;
- width: 100%;
-}
-
-.form-group label {
- font-size: 0.9rem;
- font-weight: bold;
- color: var(--gold-soft);
-}
-
-.input-text, .textarea, select.input-text {
- padding: 12px 14px;
- border-radius: 8px;
- border: 1px solid var(--border-gold);
- background: rgba(0, 0, 0, 0.4);
- color: var(--text-main);
- font-size: 16px; /* Prevents auto zoom */
- font-family: inherit;
- outline: none;
- width: 100%;
- min-height: 46px;
-}
-
-.input-text:focus, .textarea:focus {
- border-color: var(--gold);
- box-shadow: 0 0 8px rgba(201, 168, 106, 0.3);
-}
-
-.input-text-sm {
- padding: 8px 10px;
- border-radius: 6px;
- border: 1px solid var(--border-gold);
- background: rgba(0,0,0,0.3);
- color: var(--text-main);
- font-size: 15px;
- min-height: 38px;
- width: 100%;
-}
-
-.field-help {
- font-size: 0.8rem;
- color: var(--text-muted);
-}
-
-.info-alert {
- background: rgba(41, 128, 185, 0.15);
- border-left: 4px solid #3498db;
- padding: 14px;
- border-radius: 8px;
- margin-bottom: 20px;
- font-size: 0.9rem;
- line-height: 1.5;
-}
-
-.alert-danger {
- background: rgba(231, 76, 60, 0.2);
- border: 1px solid var(--danger);
- color: #ff7675;
- padding: 12px;
- border-radius: 6px;
- margin-bottom: 16px;
- text-align: center;
- font-size: 0.9rem;
-}
-
-/* =========================================
- ADMIN COCKPIT (Mobile Responsive)
- ========================================= */
-.admin-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- flex-wrap: wrap;
- gap: 12px;
-}
-
-.admin-top-actions {
- display: flex;
- gap: 8px;
-}
-
-/* Horizontal Scrollable Tabs on Mobile */
-.tabs-nav {
- display: flex;
- gap: 6px;
- border-bottom: 1px solid var(--border-gold);
- margin-bottom: 20px;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- scrollbar-width: none; /* Firefox */
- padding-bottom: 2px;
-}
-
-.tabs-nav::-webkit-scrollbar {
- display: none; /* Chrome/Safari */
-}
-
-.tab-btn {
- background: rgba(255, 255, 255, 0.05);
- border: none;
- color: var(--text-muted);
- padding: 10px 14px;
- border-top-left-radius: 8px;
- border-top-right-radius: 8px;
- cursor: pointer;
- font-size: 0.85rem;
- font-family: inherit;
- font-weight: bold;
- display: inline-flex;
- align-items: center;
- gap: 6px;
- white-space: nowrap;
- flex-shrink: 0;
- min-height: 42px;
- touch-action: manipulation;
-}
-
-.tab-btn.active {
- background: var(--bg-card);
- color: var(--gold);
- border: 1px solid var(--border-gold);
- border-bottom: 1px solid transparent;
-}
-
-.tab-content {
- display: none;
-}
-
-.tab-content.active {
- display: block;
-}
-
-.question-admin-card {
- margin-bottom: 14px;
- padding: 16px;
-}
-
-.q-header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 10px;
- font-size: 0.8rem;
- flex-wrap: wrap;
- gap: 8px;
-}
-
-.highlight-answer {
- color: var(--gold-bright);
- font-weight: bold;
- background: rgba(255, 215, 0, 0.1);
- padding: 2px 6px;
- border-radius: 4px;
- word-break: break-word;
-}
-
-.test-matcher-box {
- margin-top: 12px;
- padding: 10px;
- background: rgba(0, 0, 0, 0.25);
- border-radius: 6px;
- border: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.test-input-group {
- display: flex;
- gap: 6px;
- margin-top: 6px;
-}
-
-.test-result-feedback {
- margin-top: 8px;
- font-size: 0.85rem;
- word-break: break-word;
-}
-
-.q-actions {
- display: flex;
- gap: 8px;
- margin-top: 14px;
- flex-wrap: wrap;
-}
-
-.inline-edit-form {
- margin-top: 14px;
- padding-top: 14px;
- border-top: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.empty-state {
- text-align: center;
- padding: 30px 10px;
- color: var(--text-muted);
-}
-
-/* =========================================
- FOOTER
- ========================================= */
-.footer {
- text-align: center;
- padding: 16px;
- color: var(--text-muted);
- font-size: 0.75rem;
- border-top: 1px solid rgba(255, 255, 255, 0.05);
- margin-top: auto;
-}
-
-.footer-links {
- margin-top: 12px;
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 12px;
- flex-wrap: wrap;
-}
-
-.footer-link {
- color: var(--gold-soft);
- text-decoration: none;
- font-size: 0.82rem;
- font-weight: 600;
- padding: 4px 12px;
- border-radius: 16px;
- background: rgba(201, 168, 106, 0.12);
- border: 1px solid rgba(201, 168, 106, 0.35);
- transition: all 0.2s ease;
- display: inline-flex;
- align-items: center;
- gap: 5px;
-}
-
-.footer-link:hover {
- color: #0b0b16;
- background: var(--gold);
- border-color: var(--gold-bright);
- box-shadow: 0 0 12px rgba(201, 168, 106, 0.5);
- text-decoration: none;
- transform: translateY(-1px);
-}
-
-.footer-sep {
- display: none;
-}
-
-.legal-card {
- text-align: left;
- max-width: 760px;
- margin: 0 auto;
-}
-
-.legal-content {
- margin-top: 20px;
- line-height: 1.65;
- color: var(--text-main);
-}
-
-.legal-content h3 {
- color: var(--gold-soft);
- margin-top: 22px;
- margin-bottom: 8px;
- font-size: 1.15rem;
- border-bottom: 1px solid rgba(201, 168, 106, 0.2);
- padding-bottom: 4px;
-}
-
-.legal-content p, .legal-content ul {
- margin-bottom: 14px;
- color: var(--text-muted);
- font-size: 0.95rem;
-}
-
-.legal-content ul {
- padding-left: 20px;
-}
-
-.legal-content code {
- background: rgba(0, 0, 0, 0.35);
- padding: 2px 6px;
- border-radius: 4px;
- color: var(--gold-bright);
- font-size: 0.88em;
-}
-
-.hidden {
- display: none !important;
-}
-
-/* =========================================
- MOBILE RESPONSIVENESS (max-width: 640px)
- ========================================= */
-@media (max-width: 640px) {
- .container {
- margin: 10px auto 25px;
- padding: 0 10px;
- }
-
- .admin-wrapper {
- margin: 10px auto 25px;
- padding: 0 10px;
- }
-
- .card {
- padding: 16px 12px;
- border-radius: 12px;
- }
-
- /* Mobile Hamburger Menu */
- .menu-toggle-btn {
- display: inline-flex;
- }
-
- .nav-links {
- display: none;
- position: absolute;
- top: var(--header-height);
- left: 0;
- right: 0;
- background: rgba(11, 11, 22, 0.98);
- border-bottom: 2px solid var(--gold);
- flex-direction: column;
- padding: 14px 16px;
- gap: 8px;
- box-shadow: 0 15px 30px rgba(0,0,0,0.8);
- }
-
- .nav-links.nav-open {
- display: flex;
- }
-
- .nav-link {
- width: 100%;
- padding: 12px 14px;
- font-size: 1rem;
- background: rgba(255, 255, 255, 0.04);
- border: 1px solid rgba(201, 168, 106, 0.2);
- }
-
- .audio-label {
- display: none; /* Show only 🔇/🔊 on small phones */
- }
-
- .audio-btn {
- padding: 6px 10px;
- }
-
- /* Quiz Question layout */
- .choice-grid {
- grid-template-columns: 1fr; /* Single column on mobile */
- gap: 10px;
- }
-
- .choice-btn {
- min-height: 48px;
- padding: 10px 12px;
- font-size: 0.95rem;
- }
-
- .btn-group {
- flex-direction: column;
- }
-
- .btn-group .btn {
- width: 100%;
- }
-
- .test-input-group {
- flex-direction: column;
- }
-
- .test-input-group .btn {
- width: 100%;
- }
-
- .q-actions .btn {
- flex: 1 1 100%;
- }
-
- .admin-header {
- flex-direction: column;
- align-items: stretch;
- text-align: center;
- }
-
- .admin-top-actions {
- justify-content: center;
- }
-
- .lang-btn {
- padding: 4px 6px;
- font-size: 0.75rem;
- }
-
- .diff-chip {
- padding: 6px 10px;
- font-size: 0.8rem;
- }
-}
+:root {
+ --bg-dark: #070b14;
+ --bg-surface: #0f172a;
+ --bg-card: rgba(15, 23, 42, 0.78);
+ --bg-card-hover: rgba(30, 41, 59, 0.88);
+ --accent-primary: #38bdf8;
+ --accent-indigo: #6366f1;
+ --accent-violet: #8b5cf6;
+ --gold: #f59e0b;
+ --gold-bright: #fbbf24;
+ --gold-soft: #fcd34d;
+ --text-main: #f8fafc;
+ --text-muted: #94a3b8;
+ --success: #10b981;
+ --danger: #ef4444;
+ --warning: #f59e0b;
+ --border-subtle: rgba(148, 163, 184, 0.16);
+ --border-active: rgba(56, 189, 248, 0.55);
+ --border-gold: rgba(245, 158, 11, 0.35);
+ --header-height: 62px;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ -webkit-tap-highlight-color: transparent;
+}
+
+body {
+ background-color: var(--bg-dark);
+ background-image:
+ radial-gradient(circle at 12% 18%, rgba(56, 189, 248, 0.10) 0%, transparent 42%),
+ radial-gradient(circle at 88% 15%, rgba(99, 102, 241, 0.11) 0%, transparent 45%),
+ radial-gradient(circle at 50% 90%, rgba(245, 158, 11, 0.06) 0%, transparent 50%);
+ background-attachment: fixed;
+ color: var(--text-main);
+ font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ overflow-x: hidden;
+ line-height: 1.55;
+}
+
+/* =========================================
+ HEADER & NAVIGATION
+ ========================================= */
+.site-header {
+ background: rgba(7, 11, 20, 0.82);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ border-bottom: 1px solid var(--border-subtle);
+ position: sticky;
+ top: 0;
+ z-index: 200;
+ width: 100%;
+}
+
+.header-inner {
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 0 18px;
+ height: var(--header-height);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+}
+
+.brand-link {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ color: var(--text-main);
+ font-family: 'Space Grotesk', sans-serif;
+ font-weight: 700;
+ font-size: 1.25rem;
+ letter-spacing: -0.3px;
+}
+
+.brand-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: 9px;
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-indigo));
+ color: #fff;
+ font-size: 1.05rem;
+ box-shadow: 0 4px 12px rgba(56, 189, 248, 0.3);
+}
+
+.brand-accent {
+ color: var(--accent-primary);
+}
+
+.header-right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.lang-switch-group {
+ display: inline-flex;
+ background: rgba(15, 23, 42, 0.9);
+ border: 1px solid var(--border-subtle);
+ border-radius: 10px;
+ padding: 3px;
+}
+
+.lang-btn {
+ background: transparent;
+ border: none;
+ color: var(--text-muted);
+ font-size: 0.78rem;
+ font-weight: 700;
+ padding: 5px 10px;
+ border-radius: 7px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-family: inherit;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.lang-btn:hover {
+ color: var(--text-main);
+}
+
+.lang-btn.active {
+ background: rgba(56, 189, 248, 0.18);
+ color: var(--accent-primary);
+ border: 1px solid rgba(56, 189, 248, 0.35);
+}
+
+.menu-toggle-btn {
+ display: none;
+ background: rgba(30, 41, 59, 0.6);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-main);
+ font-size: 1.25rem;
+ width: 38px;
+ height: 38px;
+ border-radius: 9px;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+}
+
+.nav-links {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+}
+
+.nav-link {
+ color: var(--text-muted);
+ text-decoration: none;
+ font-size: 0.88rem;
+ font-weight: 600;
+ padding: 8px 13px;
+ border-radius: 8px;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+}
+
+.nav-link:hover {
+ color: var(--text-main);
+ background: rgba(148, 163, 184, 0.1);
+}
+
+/* =========================================
+ CONTAINER & HERO HEADER
+ ========================================= */
+.container {
+ flex: 1;
+ max-width: 780px;
+ width: 100%;
+ margin: 28px auto;
+ padding: 0 16px;
+}
+
+.admin-wrapper {
+ max-width: 940px;
+ width: 100%;
+ margin: 24px auto;
+ padding: 0 16px;
+}
+
+.quiz-wrapper {
+ width: 100%;
+}
+
+.quiz-header {
+ text-align: center;
+ margin-bottom: 24px;
+}
+
+.hero-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 5px 14px;
+ border-radius: 999px;
+ background: rgba(56, 189, 248, 0.1);
+ border: 1px solid rgba(56, 189, 248, 0.28);
+ color: var(--accent-primary);
+ font-size: 0.78rem;
+ font-weight: 700;
+ letter-spacing: 0.3px;
+ margin-bottom: 12px;
+}
+
+.glow-title {
+ font-family: 'Space Grotesk', sans-serif;
+ color: var(--text-main);
+ font-size: clamp(1.65rem, 5vw, 2.35rem);
+ font-weight: 700;
+ letter-spacing: -0.6px;
+ margin-bottom: 8px;
+ word-break: break-word;
+}
+
+.subtitle {
+ color: var(--text-muted);
+ font-size: clamp(0.9rem, 3vw, 1.02rem);
+ max-width: 580px;
+ margin: 0 auto 12px;
+}
+
+/* =========================================
+ CARD & DIRECT-START DASHBOARD
+ ========================================= */
+.card {
+ background: var(--bg-card);
+ border: 1px solid var(--border-subtle);
+ border-radius: 18px;
+ padding: clamp(18px, 4vw, 30px);
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+ margin-bottom: 20px;
+ width: 100%;
+}
+
+.start-dashboard-card {
+ padding: clamp(20px, 4.5vw, 32px);
+}
+
+.dashboard-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.config-section {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.config-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.config-step {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ background: rgba(99, 102, 241, 0.2);
+ border: 1px solid rgba(99, 102, 241, 0.4);
+ color: #a5b4fc;
+ font-family: 'Space Grotesk', sans-serif;
+ font-size: 0.78rem;
+ font-weight: 700;
+}
+
+.difficulty-label {
+ font-size: 0.92rem;
+ color: var(--text-main);
+ font-weight: 700;
+ letter-spacing: 0.2px;
+}
+
+.difficulty-chips {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 10px;
+}
+
+.diff-chip {
+ background: rgba(30, 41, 59, 0.55);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-muted);
+ border-radius: 12px;
+ padding: 11px 12px;
+ font-size: 0.88rem;
+ font-weight: 600;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ transition: all 0.18s ease;
+ font-family: inherit;
+}
+
+.diff-chip:hover {
+ border-color: rgba(148, 163, 184, 0.4);
+ color: var(--text-main);
+ background: rgba(30, 41, 59, 0.85);
+ transform: translateY(-1px);
+}
+
+.diff-chip.active {
+ background: rgba(56, 189, 248, 0.15);
+ border-color: var(--accent-primary);
+ color: #fff;
+ box-shadow: 0 0 18px rgba(56, 189, 248, 0.22);
+ font-weight: 700;
+}
+
+/* Category Selector Chips */
+.category-chips {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 10px;
+}
+
+.cat-chip {
+ background: rgba(30, 41, 59, 0.5);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-muted);
+ border-radius: 12px;
+ padding: 11px 12px;
+ font-size: 0.85rem;
+ font-weight: 600;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ transition: all 0.18s ease;
+ font-family: inherit;
+ text-align: left;
+}
+
+.cat-chip:hover {
+ border-color: rgba(148, 163, 184, 0.4);
+ color: var(--text-main);
+ background: rgba(30, 41, 59, 0.85);
+ transform: translateY(-1px);
+}
+
+.cat-chip.active {
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.22), rgba(56, 189, 248, 0.18));
+ border-color: var(--accent-primary);
+ color: #fff;
+ box-shadow: 0 0 16px rgba(56, 189, 248, 0.2);
+ font-weight: 700;
+}
+
+.dashboard-footer {
+ margin-top: 26px;
+ padding-top: 20px;
+ border-top: 1px solid var(--border-subtle);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+}
+
+.dashboard-rules {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 10px;
+ font-size: 0.82rem;
+ color: var(--text-muted);
+}
+
+/* =========================================
+ BUTTONS
+ ========================================= */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 12px 22px;
+ border-radius: 12px;
+ font-size: 0.95rem;
+ font-weight: 700;
+ cursor: pointer;
+ text-decoration: none;
+ transition: all 0.2s ease;
+ border: none;
+ font-family: inherit;
+ min-height: 46px;
+ touch-action: manipulation;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-indigo) 100%);
+ color: #fff;
+ box-shadow: 0 6px 20px rgba(56, 189, 248, 0.28);
+}
+
+.btn-primary:hover:not(:disabled) {
+ box-shadow: 0 8px 26px rgba(56, 189, 248, 0.45);
+ transform: translateY(-2px);
+ filter: brightness(1.06);
+}
+
+.btn-launch-quiz {
+ width: 100%;
+ max-width: 400px;
+ font-size: 1.06rem;
+ padding: 15px 28px;
+ border-radius: 14px;
+ letter-spacing: 0.2px;
+}
+
+.btn-secondary {
+ background: rgba(30, 41, 59, 0.75);
+ color: var(--text-main);
+ border: 1px solid var(--border-subtle);
+}
+
+.btn-secondary:hover:not(:disabled) {
+ background: rgba(51, 65, 85, 0.85);
+ border-color: rgba(148, 163, 184, 0.4);
+}
+
+.btn-danger {
+ background: rgba(239, 68, 68, 0.15);
+ color: #fca5a5;
+ border: 1px solid rgba(239, 68, 68, 0.4);
+}
+
+.btn-danger:hover:not(:disabled) {
+ background: var(--danger);
+ color: #fff;
+}
+
+.btn-success {
+ background: rgba(16, 185, 129, 0.18);
+ color: #6ee7b7;
+ border: 1px solid rgba(16, 185, 129, 0.4);
+}
+
+.btn-success:hover:not(:disabled) {
+ background: var(--success);
+ color: #06281e;
+}
+
+.btn-large {
+ font-size: 1.05rem;
+ padding: 14px 28px;
+ min-height: 52px;
+}
+
+.btn-sm {
+ font-size: 0.84rem;
+ padding: 8px 14px;
+ min-height: 36px;
+}
+
+.btn-group {
+ display: flex;
+ gap: 10px;
+ justify-content: center;
+ margin-top: 18px;
+ width: 100%;
+}
+
+.btn:disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+ transform: none !important;
+}
+
+/* =========================================
+ HUD STATUS, STREAK & PROGRESS BAR
+ ========================================= */
+.status-panel {
+ margin-bottom: 16px;
+ width: 100%;
+}
+
+.progress-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.hud-pill {
+ font-family: 'Space Grotesk', sans-serif;
+ font-size: 0.88rem;
+ font-weight: 700;
+ color: var(--text-muted);
+ background: rgba(15, 23, 42, 0.8);
+ border: 1px solid var(--border-subtle);
+ padding: 5px 12px;
+ border-radius: 10px;
+}
+
+.hud-center-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.streak-badge {
+ font-family: 'Space Grotesk', sans-serif;
+ font-size: 0.85rem;
+ font-weight: 700;
+ padding: 5px 12px;
+ border-radius: 10px;
+ background: rgba(30, 41, 59, 0.75);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-muted);
+ transition: all 0.25s ease;
+}
+
+.streak-badge.streak-hot {
+ background: rgba(245, 158, 11, 0.16);
+ border-color: rgba(245, 158, 11, 0.5);
+ color: var(--gold-bright);
+ box-shadow: 0 0 14px rgba(245, 158, 11, 0.25);
+}
+
+.streak-pop {
+ animation: streakPopAnim 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@keyframes streakPopAnim {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.22); }
+ 100% { transform: scale(1); }
+}
+
+.timer-badge {
+ font-family: 'Space Grotesk', ui-monospace, monospace;
+ font-size: 0.85rem;
+ font-weight: 700;
+ padding: 5px 12px;
+ border-radius: 10px;
+ background: rgba(15, 23, 42, 0.8);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-main);
+}
+
+.points-badge {
+ font-family: 'Space Grotesk', sans-serif;
+ font-size: 0.9rem;
+ font-weight: 700;
+ padding: 5px 13px;
+ border-radius: 10px;
+ background: rgba(56, 189, 248, 0.14);
+ border: 1px solid rgba(56, 189, 248, 0.35);
+ color: var(--accent-primary);
+}
+
+.progress-container {
+ width: 100%;
+ height: 8px;
+ background: rgba(30, 41, 59, 0.8);
+ border-radius: 999px;
+ overflow: hidden;
+}
+
+.progress-bar {
+ height: 100%;
+ width: 0%;
+ background: linear-gradient(90deg, var(--accent-primary) 0%, var(--accent-indigo) 100%);
+ box-shadow: 0 0 12px rgba(56, 189, 248, 0.5);
+ transition: width 0.35s ease;
+}
+
+/* =========================================
+ QUESTION CARD & ANSWERS
+ ========================================= */
+.question-card {
+ text-align: center;
+}
+
+.question-meta {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 18px;
+ gap: 8px;
+}
+
+.meta-left {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.badge {
+ padding: 5px 11px;
+ border-radius: 8px;
+ font-size: 0.76rem;
+ font-weight: 700;
+ background: rgba(99, 102, 241, 0.15);
+ border: 1px solid rgba(99, 102, 241, 0.35);
+ color: #c7d2fe;
+ white-space: nowrap;
+}
+
+.badge-diff {
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ color: var(--text-main);
+}
+
+.badge-diff-easy {
+ background: rgba(16, 185, 129, 0.14);
+ border-color: rgba(16, 185, 129, 0.4);
+ color: #34d399;
+}
+
+.badge-diff-medium {
+ background: rgba(245, 158, 11, 0.14);
+ border-color: rgba(245, 158, 11, 0.4);
+ color: #fbbf24;
+}
+
+.badge-diff-hard {
+ background: rgba(239, 68, 68, 0.14);
+ border-color: rgba(239, 68, 68, 0.4);
+ color: #f87171;
+}
+
+.badge-warning {
+ background: rgba(245, 158, 11, 0.2);
+ border-color: var(--warning);
+ color: #fbbf24;
+}
+
+.badge-success {
+ background: rgba(16, 185, 129, 0.2);
+ border-color: var(--success);
+ color: #34d399;
+}
+
+.badge-danger {
+ background: rgba(239, 68, 68, 0.2);
+ border-color: var(--danger);
+ color: #f87171;
+}
+
+.badge-muted {
+ background: rgba(148, 163, 184, 0.12);
+ border-color: rgba(148, 163, 184, 0.25);
+ color: var(--text-muted);
+}
+
+.attempt-badge {
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-muted);
+ white-space: nowrap;
+}
+
+.question-text {
+ font-family: 'Space Grotesk', sans-serif;
+ font-size: clamp(1.2rem, 4.2vw, 1.5rem);
+ line-height: 1.42;
+ text-align: center;
+ margin: 14px 0 24px;
+ color: var(--text-main);
+ font-weight: 700;
+ word-break: break-word;
+}
+
+/* Free-Text Input */
+.answer-form {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+}
+
+.input-answer {
+ width: 100%;
+ max-width: 480px;
+ padding: 14px 18px;
+ font-size: 16px;
+ text-align: center;
+ border-radius: 12px;
+ border: 1.5px solid var(--border-subtle);
+ background: rgba(7, 11, 20, 0.65);
+ color: var(--text-main);
+ outline: none;
+ transition: all 0.2s ease;
+ font-family: inherit;
+ min-height: 50px;
+}
+
+.input-answer:focus {
+ border-color: var(--accent-primary);
+ box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.18);
+}
+
+/* Multiple Choice Grid */
+.choice-group {
+ width: 100%;
+ margin-top: 4px;
+}
+
+.choice-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+ width: 100%;
+}
+
+.choice-btn {
+ background: rgba(30, 41, 59, 0.55);
+ border: 1.5px solid var(--border-subtle);
+ border-radius: 13px;
+ padding: 13px 15px;
+ color: var(--text-main);
+ font-size: clamp(0.92rem, 3.2vw, 1.0rem);
+ font-weight: 600;
+ font-family: inherit;
+ cursor: pointer;
+ text-align: left;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ transition: all 0.18s ease;
+ min-height: 56px;
+ width: 100%;
+ word-break: break-word;
+ touch-action: manipulation;
+}
+
+.choice-btn:hover:not(:disabled) {
+ background: rgba(56, 189, 248, 0.12);
+ border-color: var(--accent-primary);
+ transform: translateY(-2px);
+}
+
+.choice-btn:active:not(:disabled) {
+ transform: scale(0.99);
+}
+
+.choice-letter {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 30px;
+ height: 30px;
+ background: rgba(56, 189, 248, 0.14);
+ border: 1px solid rgba(56, 189, 248, 0.35);
+ border-radius: 8px;
+ color: var(--accent-primary);
+ font-family: 'Space Grotesk', sans-serif;
+ font-weight: 700;
+ font-size: 0.85rem;
+ flex-shrink: 0;
+}
+
+.choice-text {
+ flex: 1;
+}
+
+.choice-selected {
+ border-color: var(--accent-primary);
+ background: rgba(56, 189, 248, 0.22);
+}
+
+.choice-correct {
+ background: rgba(16, 185, 129, 0.22) !important;
+ border-color: #10b981 !important;
+ color: #6ee7b7 !important;
+ box-shadow: 0 0 16px rgba(16, 185, 129, 0.35) !important;
+}
+
+.choice-wrong {
+ background: rgba(239, 68, 68, 0.22) !important;
+ border-color: #ef4444 !important;
+ color: #fca5a5 !important;
+ box-shadow: 0 0 16px rgba(239, 68, 68, 0.3) !important;
+}
+
+.hint-box {
+ margin-top: 16px;
+ padding: 13px 16px;
+ border-radius: 12px;
+ background: rgba(245, 158, 11, 0.1);
+ border-left: 4px solid var(--gold);
+ font-size: 0.9rem;
+ color: var(--gold-soft);
+ text-align: left;
+ word-break: break-word;
+}
+
+.feedback-box {
+ margin-top: 16px;
+ font-size: clamp(0.95rem, 3.5vw, 1.08rem);
+ text-align: center;
+ min-height: 28px;
+ font-weight: 700;
+ word-break: break-word;
+}
+
+.feedback-correct {
+ color: #34d399;
+}
+
+.feedback-wrong {
+ color: #f87171;
+}
+
+.feedback-revealed {
+ color: var(--gold-bright);
+ font-size: 0.92rem;
+ margin-top: 6px;
+}
+
+/* =========================================
+ ANIMATIONS
+ ========================================= */
+.sparkle-effect {
+ animation: sparkle-glow 0.7s ease forwards;
+}
+
+@keyframes sparkle-glow {
+ 0% { box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45); }
+ 50% { box-shadow: 0 0 30px rgba(16, 185, 129, 0.45); border-color: #10b981; }
+ 100% { box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45); }
+}
+
+.shake-effect {
+ animation: shake 0.4s ease;
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 20%, 60% { transform: translateX(-6px); }
+ 40%, 80% { transform: translateX(6px); }
+}
+
+/* =========================================
+ FINALE & LEADERBOARD
+ ========================================= */
+.finale-card {
+ text-align: center;
+}
+
+.finale-header {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 16px;
+}
+
+.sparkle-icon {
+ font-size: 1.5rem;
+}
+
+.score-banner {
+ background: rgba(30, 41, 59, 0.6);
+ border: 1px solid var(--border-subtle);
+ border-radius: 14px;
+ padding: 18px;
+ margin-bottom: 20px;
+}
+
+.score-highlight {
+ font-size: clamp(1.05rem, 4vw, 1.28rem);
+}
+
+.gold-text {
+ color: var(--accent-primary);
+ font-family: 'Space Grotesk', sans-serif;
+ font-weight: 700;
+ font-size: 1.3rem;
+}
+
+.reward-box {
+ background: rgba(15, 23, 42, 0.7);
+ border-radius: 14px;
+ padding: 20px 18px;
+ margin-bottom: 20px;
+ border-left: 4px solid var(--accent-primary);
+ text-align: left;
+}
+
+.reward-box h2 {
+ color: var(--text-main);
+ font-size: clamp(1.15rem, 4vw, 1.4rem);
+ margin-bottom: 10px;
+}
+
+.reward-description {
+ font-size: clamp(0.94rem, 3.3vw, 1.02rem);
+ line-height: 1.6;
+ color: var(--text-muted);
+}
+
+.time-highlight {
+ margin-top: 8px;
+ font-size: 0.95rem;
+ color: var(--text-muted);
+}
+
+.tier-box {
+ position: relative;
+ border-radius: 16px;
+ padding: 24px 20px;
+ margin: 20px 0;
+ border: 1px solid var(--border-subtle);
+ background: radial-gradient(circle at top, rgba(56, 189, 248, 0.1) 0%, rgba(15, 23, 42, 0.85) 100%);
+ text-align: center;
+}
+
+.tier-badge-container {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 14px;
+}
+
+.tier-grade-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 64px;
+ height: 64px;
+ border-radius: 16px;
+ font-family: 'Space Grotesk', sans-serif;
+ font-size: 2rem;
+ font-weight: 800;
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
+ border: 2px solid;
+}
+
+.tier-outstanding {
+ background: linear-gradient(135deg, #fbbf24, #d97706);
+ color: #090d16;
+ border-color: #fde68a;
+}
+
+.tier-exceeds {
+ background: linear-gradient(135deg, #34d399, #059669);
+ color: #090d16;
+ border-color: #a7f3d0;
+}
+
+.tier-acceptable {
+ background: linear-gradient(135deg, #38bdf8, #2563eb);
+ color: #ffffff;
+ border-color: #bae6fd;
+}
+
+.tier-poor {
+ background: linear-gradient(135deg, #fb923c, #ea580c);
+ color: #ffffff;
+ border-color: #fed7aa;
+}
+
+.tier-troll {
+ background: linear-gradient(135deg, #64748b, #334155);
+ color: #fca5a5;
+ border-color: #ef4444;
+}
+
+.tier-subtitle {
+ color: var(--accent-primary);
+ font-weight: 600;
+ margin-bottom: 10px;
+ font-size: 0.95rem;
+}
+
+.leaderboard-entry-card {
+ background: rgba(15, 23, 42, 0.7);
+ border: 1px solid var(--border-subtle);
+ padding: 22px;
+ margin-top: 20px;
+ text-align: center;
+}
+
+.input-with-btn {
+ display: flex;
+ gap: 10px;
+ max-width: 500px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+.input-with-btn .input-text {
+ flex: 1;
+ text-align: left;
+}
+
+.btn-dice {
+ background: rgba(30, 41, 59, 0.8);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-main);
+ padding: 0 14px;
+ border-radius: 10px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ font-family: inherit;
+ font-weight: 700;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ justify-content: center;
+ transition: all 0.2s ease;
+ min-height: 46px;
+ white-space: nowrap;
+}
+
+.btn-dice:hover {
+ background: rgba(56, 189, 248, 0.15);
+ border-color: var(--accent-primary);
+ color: var(--accent-primary);
+}
+
+.diff-filter-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ justify-content: center;
+ margin: 16px 0;
+}
+
+.diff-filter-tabs .tab-btn {
+ border-radius: 10px;
+ padding: 8px 16px;
+ font-size: 0.86rem;
+ background: rgba(30, 41, 59, 0.6);
+ border: 1px solid var(--border-subtle);
+ color: var(--text-muted);
+ cursor: pointer;
+ font-weight: 600;
+}
+
+.diff-filter-tabs .tab-btn.active {
+ background: rgba(56, 189, 248, 0.16);
+ border-color: var(--accent-primary);
+ color: #fff;
+}
+
+.table-responsive {
+ width: 100%;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+.lb-table {
+ width: 100%;
+ border-collapse: collapse;
+ text-align: left;
+ font-size: 0.9rem;
+}
+
+.lb-table th {
+ background: rgba(30, 41, 59, 0.7);
+ color: var(--text-muted);
+ font-weight: 700;
+ padding: 11px 12px;
+ border-bottom: 1px solid var(--border-subtle);
+ white-space: nowrap;
+}
+
+.lb-table td {
+ padding: 11px 12px;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
+ color: var(--text-main);
+}
+
+.lb-table tr:hover td {
+ background: rgba(56, 189, 248, 0.05);
+}
+
+/* =========================================
+ FORMS & INPUTS
+ ========================================= */
+.magic-form {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ text-align: left;
+ width: 100%;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ width: 100%;
+}
+
+.form-group label {
+ font-size: 0.88rem;
+ font-weight: 700;
+ color: var(--text-main);
+}
+
+.input-text, .textarea, select.input-text {
+ padding: 12px 14px;
+ border-radius: 10px;
+ border: 1px solid var(--border-subtle);
+ background: rgba(7, 11, 20, 0.65);
+ color: var(--text-main);
+ font-size: 16px;
+ font-family: inherit;
+ outline: none;
+ width: 100%;
+ min-height: 46px;
+}
+
+.input-text:focus, .textarea:focus, select.input-text:focus {
+ border-color: var(--accent-primary);
+ box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.16);
+}
+
+.input-text-sm {
+ padding: 8px 10px;
+ border-radius: 8px;
+ border: 1px solid var(--border-subtle);
+ background: rgba(7, 11, 20, 0.55);
+ color: var(--text-main);
+ font-size: 15px;
+ min-height: 38px;
+ width: 100%;
+}
+
+.field-help {
+ font-size: 0.8rem;
+ color: var(--text-muted);
+}
+
+.info-alert {
+ background: rgba(56, 189, 248, 0.1);
+ border-left: 4px solid var(--accent-primary);
+ padding: 14px;
+ border-radius: 10px;
+ margin-bottom: 20px;
+ font-size: 0.88rem;
+ line-height: 1.5;
+ color: var(--text-main);
+}
+
+.alert-danger {
+ background: rgba(239, 68, 68, 0.15);
+ border: 1px solid var(--danger);
+ color: #fca5a5;
+ padding: 12px;
+ border-radius: 10px;
+ margin-bottom: 16px;
+ text-align: center;
+ font-size: 0.9rem;
+}
+
+/* =========================================
+ ADMIN COCKPIT
+ ========================================= */
+.admin-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ flex-wrap: wrap;
+ gap: 12px;
+}
+
+.admin-top-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.tabs-nav {
+ display: flex;
+ gap: 6px;
+ border-bottom: 1px solid var(--border-subtle);
+ margin-bottom: 20px;
+ overflow-x: auto;
+ scrollbar-width: none;
+ padding-bottom: 2px;
+}
+
+.tabs-nav::-webkit-scrollbar {
+ display: none;
+}
+
+.tab-btn {
+ background: rgba(30, 41, 59, 0.5);
+ border: 1px solid transparent;
+ color: var(--text-muted);
+ padding: 10px 14px;
+ border-radius: 10px 10px 0 0;
+ cursor: pointer;
+ font-size: 0.85rem;
+ font-family: inherit;
+ font-weight: 700;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ white-space: nowrap;
+ flex-shrink: 0;
+ min-height: 42px;
+}
+
+.tab-btn.active {
+ background: var(--bg-card);
+ color: var(--accent-primary);
+ border-color: var(--border-subtle);
+ border-bottom-color: transparent;
+}
+
+.tab-content {
+ display: none;
+}
+
+.tab-content.active {
+ display: block;
+}
+
+.question-admin-card {
+ margin-bottom: 14px;
+ padding: 18px;
+}
+
+.q-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 10px;
+ font-size: 0.8rem;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.highlight-answer {
+ color: var(--accent-primary);
+ font-weight: 700;
+ background: rgba(56, 189, 248, 0.1);
+ padding: 2px 8px;
+ border-radius: 6px;
+ word-break: break-word;
+}
+
+.test-matcher-box {
+ margin-top: 12px;
+ padding: 12px;
+ background: rgba(7, 11, 20, 0.45);
+ border-radius: 10px;
+ border: 1px solid var(--border-subtle);
+}
+
+.test-input-group {
+ display: flex;
+ gap: 8px;
+ margin-top: 6px;
+}
+
+.test-result-feedback {
+ margin-top: 8px;
+ font-size: 0.85rem;
+ word-break: break-word;
+}
+
+.q-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 14px;
+ flex-wrap: wrap;
+}
+
+.inline-edit-form {
+ margin-top: 14px;
+ padding-top: 14px;
+ border-top: 1px solid var(--border-subtle);
+}
+
+.empty-state {
+ text-align: center;
+ padding: 30px 10px;
+ color: var(--text-muted);
+}
+
+/* =========================================
+ FOOTER & LEGAL
+ ========================================= */
+.footer {
+ text-align: center;
+ padding: 20px 16px;
+ color: var(--text-muted);
+ font-size: 0.8rem;
+ border-top: 1px solid var(--border-subtle);
+ margin-top: auto;
+}
+
+.footer-links {
+ margin-top: 10px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.footer-link {
+ color: var(--text-muted);
+ text-decoration: none;
+ font-size: 0.8rem;
+ font-weight: 600;
+ padding: 4px 12px;
+ border-radius: 8px;
+ background: rgba(30, 41, 59, 0.5);
+ border: 1px solid var(--border-subtle);
+ transition: all 0.2s ease;
+}
+
+.footer-link:hover {
+ color: var(--text-main);
+ border-color: var(--accent-primary);
+}
+
+.footer-sep {
+ display: none;
+}
+
+.legal-card {
+ text-align: left;
+ max-width: 760px;
+ margin: 0 auto;
+}
+
+.legal-content {
+ margin-top: 20px;
+ line-height: 1.65;
+ color: var(--text-main);
+}
+
+.legal-content h3 {
+ color: var(--accent-primary);
+ margin-top: 22px;
+ margin-bottom: 8px;
+ font-size: 1.15rem;
+ border-bottom: 1px solid var(--border-subtle);
+ padding-bottom: 4px;
+}
+
+.legal-content p, .legal-content ul {
+ margin-bottom: 14px;
+ color: var(--text-muted);
+ font-size: 0.95rem;
+}
+
+.legal-content ul {
+ padding-left: 20px;
+}
+
+.legal-content code {
+ background: rgba(0, 0, 0, 0.35);
+ padding: 2px 6px;
+ border-radius: 4px;
+ color: var(--accent-primary);
+ font-size: 0.88em;
+}
+
+.hidden {
+ display: none !important;
+}
+
+/* =========================================
+ MOBILE RESPONSIVENESS
+ ========================================= */
+@media (max-width: 680px) {
+ .container {
+ margin: 14px auto 25px;
+ padding: 0 12px;
+ }
+
+ .card {
+ padding: 18px 14px;
+ border-radius: 14px;
+ }
+
+ .menu-toggle-btn {
+ display: inline-flex;
+ }
+
+ .nav-links {
+ display: none;
+ position: absolute;
+ top: var(--header-height);
+ left: 0;
+ right: 0;
+ background: rgba(7, 11, 20, 0.98);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-direction: column;
+ padding: 14px 16px;
+ gap: 8px;
+ box-shadow: 0 16px 32px rgba(0, 0, 0, 0.75);
+ }
+
+ .nav-links.nav-open {
+ display: flex;
+ }
+
+ .nav-link {
+ width: 100%;
+ padding: 12px 14px;
+ font-size: 0.96rem;
+ background: rgba(30, 41, 59, 0.5);
+ border: 1px solid var(--border-subtle);
+ }
+
+ .difficulty-chips {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .category-chips {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .choice-grid {
+ grid-template-columns: 1fr;
+ gap: 10px;
+ }
+
+ .btn-group {
+ flex-direction: column;
+ }
+
+ .btn-group .btn {
+ width: 100%;
+ }
+
+ .input-with-btn {
+ flex-direction: column;
+ }
+
+ .test-input-group {
+ flex-direction: column;
+ }
+}
+
+@media (max-width: 420px) {
+ .category-chips {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/app/static/js/quiz.js b/app/static/js/quiz.js
index 0c68e8d..5d6bab2 100755
--- a/app/static/js/quiz.js
+++ b/app/static/js/quiz.js
@@ -1,586 +1,606 @@
-let currentSessionId = null;
-let currentQuestionNumber = 1;
-let totalQuestions = 10;
-let currentScore = 0;
-let attemptsUsed = 0;
-const maxAttempts = 3;
-let selectedDifficulty = "all";
-let lastQuestionData = null;
-let timerStartTime = null;
-let timerInterval = null;
-let elapsedSeconds = 0;
-
-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
(Großhexenmeister, Träger des Merlin-Ordens Erster Klasse)",
- letterSalutation: "Liebe Hexe, lieber Zauberer,",
- letterParagraph1: "wir freuen uns, dir mitteilen zu dürfen, dass du zu den Magischen Z.A.G.-Prüfungen 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 1.000 Hauspunkte für dein Haus!",
- letterClosing: "Mit vorzüglicher Hochachtung,",
- letterSignature: "Minerva McGonagall",
- letterSignTitle: "Stellvertretende Schulleiterin",
- introDesc: "Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.
Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:",
- diffLabel: "Schwierigkeitsgrad wählen:",
- diffAll: "Alle gemischt",
- diffEasy: "Leicht",
- diffMed: "Mittel",
- diffHard: "Schwer",
- startBtn: "📜 Siegel brechen & Brief öffnen",
- startBtnCasting: "✨ Zauber wird gewirkt...",
- pointsLabel: "🏆 Hauspunkte",
- questionLabel: "Frage",
- ofLabel: "von",
- attemptLabel: "Versuch",
- checkBtn: "✨ Antwort prüfen",
- hintBtn: "💡 Hinweis anzeigen",
- hintHeading: "📜 Notiz aus der verbotenen Abteilung:",
- placeholder: "Deine Antwort hier 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:",
- 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
(Order of Merlin, First Class, Grand Sorc.)",
- letterSalutation: "Dear Witch or Wizard,",
- letterParagraph1: "we are pleased to inform you that you have been admitted to the Magical O.W.L. Examinations 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 1,000 House Points for your house!",
- letterClosing: "Yours sincerely,",
- letterSignature: "Minerva McGonagall",
- letterSignTitle: "Deputy Headmistress",
- introDesc: "An enchanted Hogwarts parchment letter lies before you.
Choose your difficulty and break the crimson magical seal:",
- diffLabel: "Choose difficulty level:",
- diffAll: "All mixed",
- diffEasy: "Easy",
- diffMed: "Medium",
- diffHard: "Hard",
- startBtn: "📜 Break Seal & Open Letter",
- startBtnCasting: "✨ Casting spell...",
- pointsLabel: "🏆 House Points",
- questionLabel: "Question",
- ofLabel: "of",
- attemptLabel: "Attempt",
- checkBtn: "✨ Check Answer",
- hintBtn: "💡 Show Hint",
- hintHeading: "📜 Note from the Restricted Section:",
- placeholder: "Type your answer here...",
- 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:",
- startError: "Error starting quiz: ",
- networkError: "Network error: "
- }
-};
-
-// Web Audio API Sound Generator for magic chimes and buzzers
-const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
-
-function playSound(type) {
- if (audioCtx.state === 'suspended') {
- 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);
- osc.connect(gain);
- gain.connect(audioCtx.destination);
- osc.start(audioCtx.currentTime + index * 0.08);
- osc.stop(audioCtx.currentTime + index * 0.08 + 0.35);
- });
- } 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.connect(gain);
- gain.connect(audioCtx.destination);
- osc.start();
- osc.stop(audioCtx.currentTime + 0.25);
- }
-}
-
-function selectDifficulty(diff) {
- selectedDifficulty = diff;
- document.querySelectorAll(".diff-chip").forEach(chip => {
- chip.classList.toggle("active", chip.getAttribute("data-diff") === diff);
- });
-}
-
-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 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) {
- renderQuestion(lastQuestionData);
- }
-}
-
-let sessionQuestionIds = [];
-
-function getRecentQuestionIds() {
- try {
- const stored = localStorage.getItem("quiz_recent_qids");
- return stored ? JSON.parse(stored) : [];
- } catch(e) {
- return [];
- }
-}
-
-function getWrongQuestionIds() {
- try {
- const stored = localStorage.getItem("quiz_wrong_qids");
- return stored ? JSON.parse(stored) : [];
- } catch(e) {
- return [];
- }
-}
-
-function recordQuestionWrong(qid) {
- if (!qid) return;
- try {
- const wrong = getWrongQuestionIds();
- if (!wrong.includes(qid)) {
- wrong.push(qid);
- if (wrong.length > 30) wrong.shift();
- localStorage.setItem("quiz_wrong_qids", JSON.stringify(wrong));
- }
- } catch(e) {}
-}
-
-function recordQuestionMastered(qid) {
- if (!qid) return;
- try {
- let wrong = getWrongQuestionIds();
- if (wrong.includes(qid)) {
- wrong = wrong.filter(id => id !== qid);
- localStorage.setItem("quiz_wrong_qids", JSON.stringify(wrong));
- }
- } catch(e) {}
-}
-
-function saveRecentQuestions(ids) {
- if (!ids || ids.length === 0) return;
- try {
- let recent = getRecentQuestionIds();
- recent = Array.from(new Set([...ids, ...recent])).slice(0, 20);
- localStorage.setItem("quiz_recent_qids", JSON.stringify(recent));
- } catch(e) {}
-}
-
-async function startGame() {
- const envelope = document.getElementById("envelope");
- const startBtn = document.getElementById("startBtn");
- const t = UI_TEXT[currentLang] || UI_TEXT.de;
-
- startBtn.disabled = true;
- startBtn.innerText = t.startBtnCasting;
- envelope.classList.add("envelope-opened");
- 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(() => {});
- }
-
- try {
- const recentIds = getRecentQuestionIds();
- const wrongIds = getWrongQuestionIds();
-
- const res = await fetch("/api/game/start", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- lang: currentLang,
- difficulty: selectedDifficulty,
- recent_ids: recentIds,
- wrong_ids: wrongIds
- })
- });
-
- if (!res.ok) {
- throw new Error(currentLang === "en" ? "Could not start game session." : "Konnte Spiel nicht starten.");
- }
-
- const data = await res.json();
- currentSessionId = data.session_id;
- currentQuestionNumber = data.question_number;
- totalQuestions = data.total_questions;
- currentScore = data.score;
- attemptsUsed = data.attempts_used;
-
- setTimeout(() => {
- document.getElementById("introCard").classList.add("hidden");
- document.getElementById("gameCard").classList.remove("hidden");
- startTimer();
- renderQuestion(data);
- }, 1800);
-
- } catch (err) {
- alert(t.startError + err.message);
- startBtn.disabled = false;
- startBtn.innerText = t.startBtn;
- envelope.classList.remove("envelope-opened");
- }
-}
-
-function startTimer() {
- timerStartTime = Date.now();
- elapsedSeconds = 0;
- if (timerInterval) clearInterval(timerInterval);
- timerInterval = setInterval(updateTimer, 1000);
- updateTimer();
-}
-
-function updateTimer() {
- if (!timerStartTime) return;
- elapsedSeconds = Math.floor((Date.now() - timerStartTime) / 1000);
- const m = Math.floor(elapsedSeconds / 60);
- const s = elapsedSeconds % 60;
- const badge = document.getElementById("timerBadge");
- if (badge) {
- badge.innerText = `⏱️ ${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
- }
-}
-
-function renderQuestion(qData) {
- lastQuestionData = qData;
- if (qData.id && !sessionQuestionIds.includes(qData.id)) {
- sessionQuestionIds.push(qData.id);
- }
- const t = UI_TEXT[currentLang] || UI_TEXT.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()}`;
-
- // Difficulty badge
- const badgeDiff = document.getElementById("badgeDifficulty");
- if (badgeDiff) {
- const diff = qData.difficulty || "medium";
- badgeDiff.className = `badge badge-diff badge-diff-${diff}`;
- let diffText = t.diffMed;
- let diffIcon = "🟡";
- if (diff === "easy") {
- diffText = t.diffEasy;
- diffIcon = "🟢";
- } else if (diff === "hard") {
- diffText = t.diffHard;
- diffIcon = "🔴";
- }
- badgeDiff.innerText = `${diffIcon} ${diffText}`;
- }
-
- document.getElementById("attemptBadge").innerText = `${t.attemptLabel} ${attemptsUsed + 1} ${t.ofLabel} ${maxAttempts}`;
- document.getElementById("frageText").innerText = qData.question;
-
- const textGroup = document.getElementById("textAnswerGroup");
- const choiceGroup = document.getElementById("choiceAnswerGroup");
- const choiceContainer = document.getElementById("choiceOptionsContainer");
- const input = document.getElementById("answerInput");
-
- document.getElementById("hintBox").classList.add("hidden");
- document.getElementById("feedbackBox").innerHTML = "";
-
- if (qData.question_type === "multiple_choice" && qData.options && qData.options.length > 0) {
- textGroup.classList.add("hidden");
- choiceGroup.classList.remove("hidden");
- choiceContainer.innerHTML = "";
-
- const letters = ["A", "B", "C", "D", "E", "F"];
- qData.options.forEach((opt, idx) => {
- const btn = document.createElement("button");
- btn.className = "choice-btn";
- btn.type = "button";
- btn.innerHTML = `${letters[idx] || "•"} ${opt}`;
- btn.onclick = () => submitAnswer(opt, btn);
- choiceContainer.appendChild(btn);
- });
- } else {
- choiceGroup.classList.add("hidden");
- textGroup.classList.remove("hidden");
- input.value = "";
- input.placeholder = t.placeholder;
- input.disabled = false;
- document.getElementById("checkBtn").disabled = false;
- input.focus();
- }
-
- const hintBtn = document.getElementById("hintBtn");
- if (hintBtn) hintBtn.disabled = false;
- const hintBtnMC = document.getElementById("hintBtnMC");
- if (hintBtnMC) hintBtnMC.disabled = false;
-
- updateProgress();
-}
-
-function updateProgress() {
- const percent = ((currentQuestionNumber - 1) / totalQuestions) * 100;
- document.getElementById("progressBar").style.width = percent + "%";
-}
-
-function checkAnswer() {
- const input = document.getElementById("answerInput");
- const answer = input.value.trim();
- if (!answer) {
- input.focus();
- return;
- }
- submitAnswer(answer);
-}
-
-async function submitAnswer(answer, clickedBtn = null) {
- const checkBtn = document.getElementById("checkBtn");
- const input = document.getElementById("answerInput");
- const t = UI_TEXT[currentLang] || UI_TEXT.de;
-
- 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) {
- clickedBtn.classList.add("choice-selected");
- }
-
- try {
- const res = await fetch("/api/game/check", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- session_id: currentSessionId,
- answer: answer
- })
- });
-
- if (!res.ok) {
- throw new Error(currentLang === "en" ? "Answer check failed." : "Fehler bei der Antwortprüfung.");
- }
-
- const result = await res.json();
- currentScore = result.score;
- document.getElementById("pointsCounter").innerText = `${t.pointsLabel}: ${currentScore}`;
-
- const frageBox = document.getElementById("frageBox");
- const feedbackBox = document.getElementById("feedbackBox");
-
- if (result.correct) {
- // Success!
- playSound('magic');
- 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 ptsText = (currentLang === "en")
- ? `✨ Correct! +${pts} House Points! ✨`
- : `✨ Richtig! +${pts} Hauspunkte für dein Haus! ✨`;
- feedbackBox.innerHTML = `${ptsText}`;
-
- setTimeout(() => {
- frageBox.classList.remove("sparkle-effect");
- if (result.is_finished) {
- finishGame();
- } else if (result.next_question) {
- currentQuestionNumber = result.next_question.question_number;
- attemptsUsed = 0;
- renderQuestion(result.next_question);
- }
- }, 1200);
-
- } else {
- // Incorrect
- playSound('wrong');
- if (clickedBtn) {
- clickedBtn.classList.remove("choice-selected");
- clickedBtn.classList.add("choice-wrong");
- }
- frageBox.classList.add("shake-effect");
- setTimeout(() => frageBox.classList.remove("shake-effect"), 500);
-
- attemptsUsed = result.attempts_used;
- 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);
- }
-
- feedbackBox.innerHTML = `
-
10 knifflige Rätsel aus der Zauberwelt warten auf dich
-
- Schulleiter: Albus Dumbledore
(Großhexenmeister, Träger des Merlin-Ordens 1. Klasse)
Liebe Hexe, lieber Zauberer,
-- wir freuen uns, dir mitteilen zu dürfen, dass du zu den Magischen Z.A.G.-Prüfungen an der Hogwarts-Schule zugelassen bist. -
-- Zehn anspruchsvolle Prüfungsfragen entscheiden über deinen Rang in den Chroniken. Zeige deinen Scharfsinn und sichere bis zu 1.000 Hauspunkte für dein Haus! -
-Hogwarts-Schule für Hexerei und Zauberei
-An den ehrenwerten Zauberschüler
-Auf dem Prüfungspfad • Schloss Hogwarts
-
-
-
-
- Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.
- Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:
-
Teste dein Wissen über Bücher, Filme, Geschöpfe und Zaubersprüche – direkt & ohne Umwege.
+Bereichere das magische Quiz mit kniffligen Harry-Potter- oder Escape-Rätseln!
+Bereichere PotterTrivia mit deinen eigenen Fragen zu Büchern, Filmen oder magischen Geschöpfen!