From 8c4768747bd23115b18fbad9ef366dcf30ea29cc Mon Sep 17 00:00:00 2001 From: kobold-ai Date: Mon, 21 Sep 2026 18:49:40 +0200 Subject: [PATCH] Design: Implement CodePen-style opening envelope with Hogwarts aesthetics, central wax seal, and subtle top-left return address --- app/static/css/style.css | 630 ++++++++---------- app/static/js/quiz.js | 1349 +++++++++++++++++++------------------- app/templates/base.html | 4 +- app/templates/index.html | 87 +-- 4 files changed, 971 insertions(+), 1099 deletions(-) diff --git a/app/static/css/style.css b/app/static/css/style.css index ba8ad65..5696c1b 100755 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -243,214 +243,185 @@ body { margin: 0 auto; } -.antique-envelope-scene { - position: relative; - width: min(480px, 94vw); - height: min(280px, 60vw); - margin: 25px auto 35px; - perspective: 1200px; - perspective-origin: center top; - transition: margin-top 0.8s cubic-bezier(0.25, 1, 0.5, 1); -} - -.antique-envelope-scene.envelope-opened { - margin-top: 155px; - margin-bottom: 25px; -} - -.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: #d1be9b; - background-image: - radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.55) 100%), - url('/static/img/parchment_texture.jpg'); - background-size: cover; - border: 2px solid #7a5c36; - z-index: 1; -} - -/* The Letter inside the envelope (slides up when opened) */ -.letter-sheet { - position: absolute; - top: 15px; - left: 4%; - width: 92%; - height: 440px; - max-height: 75vh; - background-color: #f7f0df; - background-image: - radial-gradient(ellipse at 50% 25%, rgba(255, 255, 255, 0.6) 0%, rgba(140, 100, 50, 0.15) 100%), - url('/static/img/parchment_texture.jpg'); - background-size: cover; - background-position: center; - border-radius: 4px; - border: 1.5px solid #8e6c43; - padding: 16px 20px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35); - z-index: 2; - /* In closed state: 100% completely hidden, invisible, non-rendered */ - visibility: hidden; - opacity: 0; - transform: translateY(50px) scale(0.92); - pointer-events: none; - transition: transform 0.9s cubic-bezier(0.2, 0.85, 0.25, 1) 0.35s, - opacity 0.6s ease 0.35s, - visibility 0.6s ease 0.35s, - box-shadow 0.8s ease; - overflow-y: auto; - text-align: center; - outline: 1px dashed rgba(130, 95, 55, 0.45); - outline-offset: -5px; - scrollbar-width: thin; - scrollbar-color: #8c6838 transparent; -} - -.letter-sheet::-webkit-scrollbar { - width: 6px; -} - -.letter-sheet::-webkit-scrollbar-thumb { - background-color: #8c6838; - border-radius: 4px; -} - /* ========================================= - ENVELOPE BACK WITH V-FLAP CLOSURE - - Solid back body - - Sender address in top-left - - Prominent triangular V-flap - - Sketched wax seal at apex + CODEPEN ENVELOPE WITH HOGWARTS AESTHETICS + - 4-Flap Envelope Back Geometry + - Realistic Parchment Textures + - Wax Seal in the Exact Center (Mitte) + - Small Handwriting Return Address (Top-Left) + - Clean Inner Letter Sheet ========================================= */ -/* Solid Envelope Back Body */ -.envelope-back-body { - position: absolute; - inset: 0; - border-radius: 6px; - background-color: #ebd8ae; - background-image: - radial-gradient(ellipse at 50% 60%, rgba(255, 255, 255, 0.22) 0%, rgba(130, 95, 45, 0.22) 100%), - url('/static/img/parchment_texture.jpg'); - background-size: cover; - border: 1.5px solid #8e6c43; - z-index: 3; +.envelope-outer-wrap { + width: 100%; + max-width: 480px; + margin: 15px auto 25px; +} + +.envelope-form { + width: 100%; + perspective: 1200px; +} + +.env-wrap { + position: relative; overflow: hidden; + width: 100%; + padding-top: 86%; /* Aspect ratio for envelope body + rising letter */ + background: transparent; } -/* Absender oben links auf der Rückseite (sonst kein Text) */ -.envelope-sender-block { +/* Interior Back Wall of the Envelope (Bottom 55%) */ +.env-body-back { position: absolute; - top: 14px; - left: 16px; - z-index: 8; - text-align: left; - font-family: 'Marck Script', 'Caveat', cursive; - color: #0d3820; /* Authentic Hogwarts Emerald Ink */ - max-width: 48%; - line-height: 1.25; - pointer-events: none; - transition: opacity 0.4s ease; -} - -.envelope-opened .envelope-sender-block { - opacity: 0; -} - -.sender-label { - display: block; - font-size: clamp(0.72rem, 2.2vw, 0.85rem); - font-family: 'MedievalSharp', serif; - color: #4a6352; - text-transform: uppercase; - letter-spacing: 0.8px; - margin-bottom: 2px; -} - -.sender-name { - font-size: clamp(0.92rem, 2.9vw, 1.15rem); - font-weight: 700; - color: #082816; - margin-bottom: 1px; - text-shadow: 0 0 1px rgba(8, 40, 22, 0.35); -} - -.sender-location { - font-size: clamp(0.78rem, 2.4vw, 0.95rem); - font-style: italic; - color: #1e4530; -} - -/* Distinct Triangular V-Closure Flap */ -.envelope-closing-flap.envelope-v-flap { - position: absolute; - top: 0; + bottom: 0; left: 0; width: 100%; - height: 100%; - background-color: #dfcd9b; + height: 55%; + background-color: #2e2014; background-image: - radial-gradient(ellipse at 50% 18%, rgba(255, 255, 255, 0.35) 0%, rgba(125, 90, 40, 0.28) 100%), + radial-gradient(ellipse at 50% 50%, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.8) 100%), url('/static/img/parchment_texture.jpg'); background-size: cover; - clip-path: polygon(0 0, 100% 0, 50% 56%); - transform-origin: top center; - transform-style: preserve-3d; - transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 6; - border-top: 2px solid #7c5830; - filter: drop-shadow(0 7px 14px rgba(35, 18, 5, 0.65)); -} - -.envelope-opened .envelope-closing-flap.envelope-v-flap { - transform: rotateX(180deg); + border-radius: 0 0 6px 6px; + border: 1.5px solid #6b4a24; + box-shadow: 0 16px 36px rgba(0, 0, 0, 0.75); z-index: 1; - filter: drop-shadow(0 -4px 6px rgba(50, 35, 10, 0.4)); } -.v-flap-sketch-line { +/* Top V-Flap (Hinged at 45% Top Edge of Envelope Body) */ +.env-top { position: absolute; - inset: 4px; - clip-path: polygon(2% 2%, 98% 2%, 50% 53.5%); - border-top: 1.5px dashed rgba(110, 75, 35, 0.45); + width: 100%; + height: 33%; + top: 45%; + z-index: 8; + transform-origin: top center; + transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1), z-index 0s 0.25s; +} + +.env-top:before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + background-color: #ebd8ae; + background-image: + radial-gradient(ellipse at 50% 20%, rgba(255, 255, 255, 0.3) 0%, rgba(130, 95, 45, 0.2) 100%), + url('/static/img/parchment_texture.jpg'); + background-size: cover; + clip-path: polygon(50% 100%, 0 0, 100% 0); + filter: drop-shadow(0px 8px 12px rgba(35, 18, 5, 0.6)); + border-top: 1px solid #7c5830; +} + +/* Sketched Dashed Line on Top Flap */ +.env-top:after { + content: ''; + position: absolute; + top: 2px; + left: 4%; + width: 92%; + height: 94%; + clip-path: polygon(50% 96%, 2% 2%, 98% 2%); + border-top: 1.5px dashed rgba(110, 75, 35, 0.4); pointer-events: none; } -/* Sketched Wax Seal placed right at the tip of the V-flap */ +/* Bottom Pocket Wrap */ +.env-bottom-wrap { + height: 55%; + width: 100%; + z-index: 4; + bottom: 0; + position: absolute; + filter: drop-shadow(0px -4px 6px rgba(30, 15, 5, 0.3)); +} + +/* Bottom Flap */ +.env-bottom { + clip-path: polygon(50% 50%, 100% 0, 100% 100%, 0 100%, 0 0); + background-color: #e5cf9f; + background-image: + radial-gradient(ellipse at 50% 80%, rgba(255, 255, 255, 0.25) 0%, rgba(120, 85, 40, 0.25) 100%), + url('/static/img/parchment_texture.jpg'); + background-size: cover; + width: 100%; + height: 100%; + position: relative; + border-radius: 0 0 6px 6px; +} + +/* Side Flaps Inside Bottom Pocket */ +.env-bottom:before, .env-bottom:after { + content: ''; + position: absolute; + width: 50%; + height: 100%; + background-size: cover; +} + +/* Left Side Flap */ +.env-bottom:before { + left: 0; + clip-path: polygon(100% 50%, 0 0, 0 100%); + background-color: #dfc794; + background-image: + linear-gradient(135deg, rgba(0, 0, 0, 0.12) 0%, rgba(255, 255, 255, 0.15) 100%), + url('/static/img/parchment_texture.jpg'); + box-shadow: inset -2px 0 6px rgba(60, 40, 15, 0.2); +} + +/* Right Side Flap */ +.env-bottom:after { + right: 0; + clip-path: polygon(0 50%, 100% 0, 100% 100%); + background-color: #dbc38f; + background-image: + linear-gradient(-135deg, rgba(0, 0, 0, 0.12) 0%, rgba(255, 255, 255, 0.15) 100%), + url('/static/img/parchment_texture.jpg'); + box-shadow: inset 2px 0 6px rgba(60, 40, 15, 0.2); +} + +/* Small Handwriting Return Address in Top Left ("Abs.: Hogwarts Schule...") */ +.env-sender-line { + position: absolute; + top: 47%; + left: 14px; + font-family: 'Caveat', 'Marck Script', cursive; + font-size: clamp(0.76rem, 2.1vw, 0.88rem); + color: #0d3820; /* Authentic Hogwarts Emerald Ink */ + font-weight: 600; + z-index: 7; + pointer-events: none; + letter-spacing: 0.2px; + text-shadow: 0 0 1px rgba(13, 56, 32, 0.25); + transition: opacity 0.4s ease; + white-space: nowrap; +} + +/* Hand-Sketched Hogwarts Wax Seal in the Exact Mitte (Center) */ .sketched-wax-seal { position: absolute; - top: 56%; + top: 72.5%; /* Exact point where top flap and pocket meet */ left: 50%; transform: translate(-50%, -50%) rotate(-3deg); - width: 74px; - height: 74px; + width: 68px; + height: 68px; border-radius: 50%; cursor: pointer; - z-index: 7; + z-index: 9; 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 22px rgba(0, 0, 0, 0.75), - 0 2px 6px rgba(0, 0, 0, 0.45); + 0 8px 20px rgba(0, 0, 0, 0.75), + 0 2px 5px rgba(0, 0, 0, 0.5); } .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); + 0 12px 26px rgba(0, 0, 0, 0.85), + 0 0 16px rgba(218, 165, 32, 0.5); } .seal-crest-img { @@ -459,8 +430,8 @@ body { border-radius: 50%; object-fit: cover; display: block; - border: 3px solid #4a0d0d; - box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.7); + border: 2.5px solid #4a0d0d; + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.7); } .seal-emboss-ring { @@ -486,162 +457,114 @@ body { opacity: 1; } -/* Open state: Flap opens, seal dissolves, letter slides out */ +/* Inner Letter Sheet (Clean, No Distracting Text - "Den Text lassen wir erstmal weg") */ +.env-letter-sheet { + width: calc(100% - 2.5rem); + left: 1.25rem; + position: absolute; + height: 68%; + top: 46%; /* Tucked safely inside pocket when closed */ + background-color: #f7f0de; + background-image: + radial-gradient(ellipse at 50% 30%, rgba(255, 255, 255, 0.7) 0%, rgba(140, 100, 50, 0.15) 100%), + url('/static/img/parchment_texture.jpg'); + background-size: cover; + border-radius: 5px; + border: 1px solid #8e6c43; + outline: 1px dashed rgba(130, 95, 55, 0.4); + outline-offset: -5px; + z-index: 3; /* Behind bottom pocket (z-index: 4), in front of interior back (z-index: 1) */ + transition: transform 0.7s cubic-bezier(0.2, 0.85, 0.25, 1) 0.15s, box-shadow 0.6s ease; + transform: translateY(0); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 16px 20px; + text-align: center; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.letter-crest-small { + width: 48px; + height: 48px; + border-radius: 50%; + border: 2px solid #7c5830; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); + margin-bottom: 8px; + object-fit: cover; +} + +.letter-title { + font-family: 'Cinzel', serif; + font-size: clamp(0.95rem, 2.8vw, 1.2rem); + color: #2b1704; + letter-spacing: 1px; + font-weight: 700; + margin-bottom: 3px; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); +} + +.letter-subtitle { + font-family: 'MedievalSharp', serif; + font-size: clamp(0.75rem, 2vw, 0.9rem); + color: #5a3d1e; + font-style: italic; + margin-bottom: 14px; +} + +.btn-launch-express { + background: linear-gradient(135deg, #a62b2b 0%, #6e1010 100%); + color: #ffd700; + border: 1.5px solid #ffd700; + border-radius: 25px; + padding: 9px 18px; + font-family: 'Cinzel', serif; + font-size: clamp(0.78rem, 2.2vw, 0.92rem); + font-weight: 700; + cursor: pointer; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5), 0 0 10px rgba(255, 215, 0, 0.3); + transition: all 0.3s ease; + text-decoration: none; + display: inline-block; +} + +.btn-launch-express:hover { + transform: scale(1.04); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6), 0 0 18px rgba(255, 215, 0, 0.6); + color: #ffffff; +} + +/* ========================================= + OPENED STATE (via .envelope-opened class OR hover) + ========================================= */ + +.envelope-opened .env-top, +.envelope-form:hover .env-top { + transform: rotateX(-180deg); + z-index: 2; + transition-delay: 0s; +} + .envelope-opened .sketched-wax-seal { - animation: breakSealAnim 0.7s forwards; + opacity: 0; + transform: translate(-50%, -50%) scale(0.3) rotate(25deg); pointer-events: none; } -.envelope-opened .envelope-closing-flap.flap-top { - transform: rotateX(180deg); - z-index: 1; - filter: drop-shadow(0 -4px 6px rgba(50, 35, 10, 0.4)); +.envelope-form:hover .sketched-wax-seal { + opacity: 0.2; + transform: translate(-50%, -50%) scale(0.85); } -.envelope-opened .letter-sheet { - visibility: visible; - opacity: 1; - pointer-events: auto; - transform: translateY(-145px) scale(1.02); - z-index: 25; - box-shadow: - 0 24px 60px rgba(0, 0, 0, 0.85), - 0 0 35px rgba(201, 168, 106, 0.4); +.envelope-opened .env-letter-sheet, +.envelope-form:hover .env-letter-sheet { + transform: translateY(-56%); + box-shadow: 0 18px 40px rgba(0, 0, 0, 0.8), 0 0 25px rgba(218, 165, 32, 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.0rem, 2.9vw, 1.16rem); - margin-bottom: 8px; - text-indent: 1.2em; - line-height: 1.48; -} - -.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; +.envelope-opened .env-sender-line { + opacity: 0; } /* Controls Below Parchment */ @@ -739,44 +662,37 @@ body { } @media (max-width: 480px) { - .antique-envelope-scene { - width: min(340px, 94vw); - height: min(215px, 60vw); - margin: 15px auto 25px; - } - .antique-envelope-scene.envelope-opened { - margin-top: 135px; - margin-bottom: 20px; + .envelope-outer-wrap { + max-width: 360px; + margin: 10px auto 20px; } .sketched-wax-seal { - width: 62px; - height: 62px; - top: 56%; + width: 58px; + height: 58px; } - .envelope-sender-block { - top: 10px; - left: 12px; - max-width: 55%; + .env-sender-line { + font-size: 0.72rem; + left: 10px; } - .letter-sheet { - top: 10px; - left: 2%; - width: 96%; - height: 400px; - max-height: 72vh; - padding: 12px 14px; - } - .envelope-opened .letter-sheet { - transform: translateY(-120px) scale(1.01); - } - .letter-paragraph { - font-size: 0.96rem; - line-height: 1.42; + .letter-crest-small { + width: 40px; + height: 40px; margin-bottom: 6px; } + .letter-title { + font-size: 0.96rem; + } + .letter-subtitle { + font-size: 0.76rem; + margin-bottom: 10px; + } + .btn-launch-express { + padding: 7px 14px; + font-size: 0.76rem; + } .btn-launch-quiz { - padding: 12px 20px; - font-size: 1.0rem; + padding: 12px 18px; + font-size: 0.95rem; } } diff --git a/app/static/js/quiz.js b/app/static/js/quiz.js index 83aa3eb..7591ce8 100755 --- a/app/static/js/quiz.js +++ b/app/static/js/quiz.js @@ -1,684 +1,665 @@ -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", - senderLabel: "Absender:", - envelopeSender: "Hogwarts-Schule für Hexerei und Zauberei", - envelopeSenderLocation: "Schulleitung • 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: "die schwebenden Kerzen der Großen Halle flackern in unruhigem Schein, und der Sprechende Hut hat nach Jahrhunderten des Schweigens ein wohlbehütetes Geheimnis preisgegeben: Tief in den uralten Gewölben des Schlosses – verborgen hinter den wachsamen Wasserspeiern und den staubigen Bänden der Verbotenen Abteilung – wurde eine magische Prüfungsurkunde versiegelt.", - letterParagraph2: "Zehn arkanische Prüfungen des Scharfsinns, der Zauberkunde und der Entschlossenheit liegen vor dir. Nur wer wahren Mut beweist und die uralten Rätsel meistert, sichert sich seinen ehrenvollen Platz in den Chroniken von Hogwarts und trägt bis zu 1.000 Hauspunkte für sein Haus davon!", - letterParagraph3: "Halte deinen Zauberstab bereit, besinne dich auf deine Flüche und Gegenzauber und nimm dich vor verhexten Trugschlüssen in Acht. Wir erwarten deine Eule nicht später als mit dem nächsten Glockenschlag des Uhrenturms.", - letterClosing: "Mit vorzüglicher Hochachtung,", - letterSignature: "Minerva McGonagall", - letterSignTitle: "Stellvertretende Schulleiterin", - introDescClosed: "Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.
Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:", - introDescOpened: "Lies die Botschaft der Schulleitung aufmerksam.
Sobald du bereit bist, besteige den Hogwarts-Express zur Prüfung:", - 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", - breakSealBtn: "📜 Siegel brechen & Brief öffnen", - startQuizBtn: "🚂 Hogwarts-Express besteigen & Prüfung beginnen ➔", - closeLetterBtn: "↩ Brief wieder schließen", - startBtn: "📜 Siegel brechen & Brief öffnen", - startBtnCasting: "✨ Zauberstab wird vorbereitet...", - 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", - senderLabel: "Sender:", - envelopeSender: "Hogwarts School of Witchcraft and Wizardry", - envelopeSenderLocation: "Headmaster's Office • 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: "The floating candles of the Great Hall flicker with restless anticipation, and the Sorting Hat has stirred after centuries of silence to unveil a closely guarded secret: Deep within the ancient castle vaults—past the watchful gargoyles and the dust of the Restricted Section—a legendary trial has been unsealed.", - letterParagraph2: "Ten arcane trials of wit, magical lore, and courage lie ahead of you. Only those who prove true valor and decipher the hidden mysteries shall claim their place in the eternal Hogwarts archives and earn up to 1,000 House Points for their house!", - letterParagraph3: "Ready your wand, recall your spells and counter-curses, and beware of enchanted trickery. We await your owl by no later than the next chime of the clock tower.", - letterClosing: "Yours sincerely,", - letterSignature: "Minerva McGonagall", - letterSignTitle: "Deputy Headmistress", - introDescClosed: "An enchanted Hogwarts parchment letter lies before you.
Choose your difficulty and break the crimson magical seal:", - introDescOpened: "Read the message from Hogwarts leadership carefully.
When you are ready, board the Hogwarts Express for your trial:", - 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", - breakSealBtn: "📜 Break Seal & Open Letter", - startQuizBtn: "🚂 Board Hogwarts Express & Begin Trials ➔", - closeLetterBtn: "↩ Close letter again", - startBtn: "📜 Break Seal & Open Letter", - startBtnCasting: "✨ Preparing wand...", - 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 letterParagraph3 = document.getElementById("letterParagraph3"); - const letterClosing = document.getElementById("letterClosing"); - const letterSignature = document.getElementById("letterSignature"); - const letterSignTitle = document.getElementById("letterSignTitle"); - - const senderLabel = document.getElementById("senderLabel"); - const envelopeSender = document.getElementById("envelopeSender"); - const envelopeSenderLocation = document.getElementById("envelopeSenderLocation"); - const breakSealBtn = document.getElementById("breakSealBtn"); - const startQuizBtn = document.getElementById("startQuizBtn"); - const closeLetterBtn = document.getElementById("closeLetterBtn"); - - if (pageTitle) pageTitle.innerText = t.pageTitle; - if (pageSubtitle) pageSubtitle.innerText = t.pageSubtitle; - if (senderLabel && t.senderLabel) senderLabel.innerText = t.senderLabel; - if (envelopeSender && t.envelopeSender) envelopeSender.innerText = t.envelopeSender; - if (envelopeSenderLocation && t.envelopeSenderLocation) envelopeSenderLocation.innerText = t.envelopeSenderLocation; - 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 (letterParagraph3 && t.letterParagraph3) letterParagraph3.innerHTML = t.letterParagraph3; - if (letterClosing && t.letterClosing) letterClosing.innerText = t.letterClosing; - if (letterSignature && t.letterSignature) letterSignature.innerText = t.letterSignature; - if (letterSignTitle && t.letterSignTitle) letterSignTitle.innerText = t.letterSignTitle; - - const envelope = document.getElementById("envelope"); - if (introDesc) { - if (envelope && envelope.classList.contains("envelope-opened") && t.introDescOpened) { - introDesc.innerHTML = t.introDescOpened; - } else if (t.introDescClosed) { - introDesc.innerHTML = t.introDescClosed; - } else if (t.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 (breakSealBtn && !breakSealBtn.disabled) breakSealBtn.innerText = t.breakSealBtn || t.startBtn; - if (startQuizBtn && !startQuizBtn.disabled) startQuizBtn.innerText = t.startQuizBtn; - if (closeLetterBtn) closeLetterBtn.innerText = t.closeLetterBtn; - 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) {} -} - -function breakSealAndOpenLetter() { - const envelope = document.getElementById("envelope"); - if (!envelope || envelope.classList.contains("envelope-opened")) return; - - playSound('magic'); - envelope.classList.add("envelope-opened"); - - // 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(() => {}); - } - - const breakSealBtn = document.getElementById("breakSealBtn"); - const startBtn = document.getElementById("startBtn"); - const letterActions = document.getElementById("letterActions"); - const introDesc = document.getElementById("introDesc"); - const t = UI_TEXT[currentLang] || UI_TEXT.de; - - if (breakSealBtn) breakSealBtn.classList.add("hidden"); - if (startBtn) startBtn.classList.add("hidden"); - if (letterActions) { - letterActions.classList.remove("hidden"); - letterActions.classList.add("fade-in-action"); - } - if (introDesc && t.introDescOpened) { - introDesc.innerHTML = t.introDescOpened; - } -} - -function closeLetter() { - const envelope = document.getElementById("envelope"); - if (!envelope) return; - - envelope.classList.remove("envelope-opened"); - - const breakSealBtn = document.getElementById("breakSealBtn"); - const startBtn = document.getElementById("startBtn"); - const letterActions = document.getElementById("letterActions"); - const introDesc = document.getElementById("introDesc"); - const t = UI_TEXT[currentLang] || UI_TEXT.de; - - if (letterActions) letterActions.classList.add("hidden"); - if (breakSealBtn) breakSealBtn.classList.remove("hidden"); - if (startBtn) startBtn.classList.remove("hidden"); - if (introDesc && t.introDescClosed) { - introDesc.innerHTML = t.introDescClosed; - } -} - -async function startQuizFromLetter() { - const startQuizBtn = document.getElementById("startQuizBtn"); - const t = UI_TEXT[currentLang] || UI_TEXT.de; - - if (startQuizBtn) { - startQuizBtn.disabled = true; - startQuizBtn.innerText = t.startBtnCasting; - } - - playSound('magic'); - sessionQuestionIds = []; - - 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; - - const introCard = document.getElementById("introCard"); - if (introCard) { - introCard.style.transition = "opacity 0.4s ease, transform 0.4s ease"; - introCard.style.opacity = "0"; - introCard.style.transform = "scale(0.97)"; - } - - setTimeout(() => { - if (introCard) { - introCard.classList.add("hidden"); - introCard.style.opacity = ""; - introCard.style.transform = ""; - } - document.getElementById("gameCard").classList.remove("hidden"); - startTimer(); - renderQuestion(data); - }, 400); - - } catch (err) { - alert(t.startError + err.message); - if (startQuizBtn) { - startQuizBtn.disabled = false; - startQuizBtn.innerText = t.startQuizBtn; - } - } -} - -// Fallback alias for startGame -function startGame() { - const envelope = document.getElementById("envelope"); - if (envelope && !envelope.classList.contains("envelope-opened")) { - breakSealAndOpenLetter(); - } else { - startQuizFromLetter(); - } -} - -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 = ` -
${t.exhaustedWrong}
-
${t.solutionLabel} ${result.revealed_answer}
- `; - - setTimeout(() => { - if (result.is_finished) { - finishGame(); - } else if (result.next_question) { - currentQuestionNumber = result.next_question.question_number; - attemptsUsed = 0; - renderQuestion(result.next_question); - } - }, 2200); - - } else { - // Still attempts remaining - const msg = t.wrongFeedback.replace("{attempts}", result.attempts_left); - feedbackBox.innerHTML = `${msg}`; - if (checkBtn) checkBtn.disabled = false; - if (input) { - input.disabled = false; - input.select(); - } - // Re-enable choice buttons - choiceBtns.forEach(b => b.disabled = false); - } - } - - } catch (err) { - alert(t.networkError + err.message); - if (checkBtn) checkBtn.disabled = false; - if (input) input.disabled = false; - choiceBtns.forEach(b => b.disabled = false); - } -} - -async function requestHint() { - if (!currentSessionId) return; - const hintBox = document.getElementById("hintBox"); - const hintText = document.getElementById("hintText"); - const t = UI_TEXT[currentLang] || UI_TEXT.de; - - try { - const res = await fetch(`/api/game/hint?session_id=${encodeURIComponent(currentSessionId)}`); - const data = await res.json(); - hintText.innerText = data.hint || t.noHint; - hintBox.classList.remove("hidden"); - } catch (err) { - hintText.innerText = t.hintFailed; - hintBox.classList.remove("hidden"); - } -} - -function finishGame() { - if (timerInterval) clearInterval(timerInterval); - saveRecentQuestions(sessionQuestionIds); - document.getElementById("progressBar").style.width = "100%"; - window.location.href = `/finale?score=${currentScore}&total=${totalQuestions}&time=${elapsedSeconds}&lang=${currentLang}&diff=${encodeURIComponent(selectedDifficulty)}`; -} - -// Enter key to submit -document.addEventListener("DOMContentLoaded", () => { - const input = document.getElementById("answerInput"); - if (input) { - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - e.preventDefault(); - checkAnswer(); - } - }); - } - applyQuizTranslations(currentLang); -}); +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", + senderLine: "Abs.: Hogwarts Schule für Hexerei und Zauberei", + letterTitle: "Hogwarts-Schule", + letterSubtitle: "für Hexerei und Zauberei", + introDescClosed: "Ein verzauberter Pergamentbrief aus Hogwarts liegt vor dir.
Wähle deinen Schwierigkeitsgrad und brich das rote Zaubersiegel:", + introDescOpened: "Lies die Botschaft der Schulleitung aufmerksam.
Sobald du bereit bist, besteige den Hogwarts-Express zur Prüfung:", + 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", + breakSealBtn: "📜 Siegel brechen & Brief öffnen", + startQuizBtn: "🚂 Hogwarts-Express besteigen & Prüfung beginnen ➔", + closeLetterBtn: "↩ Brief wieder schließen", + startBtn: "📜 Siegel brechen & Brief öffnen", + startBtnCasting: "✨ Zauberstab wird vorbereitet...", + 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", + senderLine: "Sender: Hogwarts School of Witchcraft and Wizardry", + letterTitle: "Hogwarts School", + letterSubtitle: "of Witchcraft and Wizardry", + introDescClosed: "An enchanted Hogwarts parchment letter lies before you.
Choose your difficulty and break the crimson magical seal:", + introDescOpened: "Read the message from Hogwarts leadership carefully.
When you are ready, board the Hogwarts Express for your trial:", + 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", + breakSealBtn: "📜 Break Seal & Open Letter", + startQuizBtn: "🚂 Board Hogwarts Express & Begin Trials ➔", + closeLetterBtn: "↩ Close letter again", + startBtn: "📜 Break Seal & Open Letter", + startBtnCasting: "✨ Preparing wand...", + 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 envSenderLine = document.getElementById("envSenderLine"); + const letterTitle = document.getElementById("letterTitle"); + const letterSubtitle = document.getElementById("letterSubtitle"); + const startQuizBtnInLetter = document.getElementById("startQuizBtnInLetter"); + const breakSealBtn = document.getElementById("breakSealBtn"); + const startQuizBtn = document.getElementById("startQuizBtn"); + const closeLetterBtn = document.getElementById("closeLetterBtn"); + + if (pageTitle) pageTitle.innerText = t.pageTitle; + if (pageSubtitle) pageSubtitle.innerText = t.pageSubtitle; + if (envSenderLine && t.senderLine) envSenderLine.innerText = t.senderLine; + if (letterTitle && t.letterTitle) letterTitle.innerText = t.letterTitle; + if (letterSubtitle && t.letterSubtitle) letterSubtitle.innerText = t.letterSubtitle; + if (startQuizBtnInLetter && t.startQuizBtn) startQuizBtnInLetter.innerText = t.startQuizBtn; + + const envelope = document.getElementById("envelope"); + if (introDesc) { + if (envelope && envelope.classList.contains("envelope-opened") && t.introDescOpened) { + introDesc.innerHTML = t.introDescOpened; + } else if (t.introDescClosed) { + introDesc.innerHTML = t.introDescClosed; + } else if (t.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 (breakSealBtn && !breakSealBtn.disabled) breakSealBtn.innerText = t.breakSealBtn || t.startBtn; + if (startQuizBtn && !startQuizBtn.disabled) startQuizBtn.innerText = t.startQuizBtn; + if (closeLetterBtn) closeLetterBtn.innerText = t.closeLetterBtn; + 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) {} +} + +function breakSealAndOpenLetter() { + const envelope = document.getElementById("envelope"); + if (!envelope || envelope.classList.contains("envelope-opened")) return; + + playSound('magic'); + envelope.classList.add("envelope-opened"); + + // 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(() => {}); + } + + const breakSealBtn = document.getElementById("breakSealBtn"); + const startBtn = document.getElementById("startBtn"); + const letterActions = document.getElementById("letterActions"); + const introDesc = document.getElementById("introDesc"); + const t = UI_TEXT[currentLang] || UI_TEXT.de; + + if (breakSealBtn) breakSealBtn.classList.add("hidden"); + if (startBtn) startBtn.classList.add("hidden"); + if (letterActions) { + letterActions.classList.remove("hidden"); + letterActions.classList.add("fade-in-action"); + } + if (introDesc && t.introDescOpened) { + introDesc.innerHTML = t.introDescOpened; + } +} + +function closeLetter() { + const envelope = document.getElementById("envelope"); + if (!envelope) return; + + envelope.classList.remove("envelope-opened"); + + const breakSealBtn = document.getElementById("breakSealBtn"); + const startBtn = document.getElementById("startBtn"); + const letterActions = document.getElementById("letterActions"); + const introDesc = document.getElementById("introDesc"); + const startQuizBtn = document.getElementById("startQuizBtn"); + const startQuizBtnInLetter = document.getElementById("startQuizBtnInLetter"); + const t = UI_TEXT[currentLang] || UI_TEXT.de; + + if (startQuizBtn) { + startQuizBtn.disabled = false; + startQuizBtn.innerText = t.startQuizBtn; + } + if (startQuizBtnInLetter) { + startQuizBtnInLetter.disabled = false; + startQuizBtnInLetter.innerText = t.startQuizBtn; + } + + if (letterActions) letterActions.classList.add("hidden"); + if (breakSealBtn) breakSealBtn.classList.remove("hidden"); + if (startBtn) startBtn.classList.remove("hidden"); + if (introDesc && t.introDescClosed) { + introDesc.innerHTML = t.introDescClosed; + } +} + +async function startQuizFromLetter() { + const startQuizBtn = document.getElementById("startQuizBtn"); + const startQuizBtnInLetter = document.getElementById("startQuizBtnInLetter"); + const t = UI_TEXT[currentLang] || UI_TEXT.de; + + if (startQuizBtn) { + startQuizBtn.disabled = true; + startQuizBtn.innerText = t.startBtnCasting; + } + if (startQuizBtnInLetter) { + startQuizBtnInLetter.disabled = true; + startQuizBtnInLetter.innerText = t.startBtnCasting; + } + + playSound('magic'); + sessionQuestionIds = []; + + 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; + + const introCard = document.getElementById("introCard"); + if (introCard) { + introCard.style.transition = "opacity 0.4s ease, transform 0.4s ease"; + introCard.style.opacity = "0"; + introCard.style.transform = "scale(0.97)"; + } + + setTimeout(() => { + if (introCard) { + introCard.classList.add("hidden"); + introCard.style.opacity = ""; + introCard.style.transform = ""; + } + document.getElementById("gameCard").classList.remove("hidden"); + startTimer(); + renderQuestion(data); + }, 400); + + } catch (err) { + alert(t.startError + err.message); + if (startQuizBtn) { + startQuizBtn.disabled = false; + startQuizBtn.innerText = t.startQuizBtn; + } + } +} + +// Fallback alias for startGame +function startGame() { + const envelope = document.getElementById("envelope"); + if (envelope && !envelope.classList.contains("envelope-opened")) { + breakSealAndOpenLetter(); + } else { + startQuizFromLetter(); + } +} + +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 = ` +
${t.exhaustedWrong}
+
${t.solutionLabel} ${result.revealed_answer}
+ `; + + setTimeout(() => { + if (result.is_finished) { + finishGame(); + } else if (result.next_question) { + currentQuestionNumber = result.next_question.question_number; + attemptsUsed = 0; + renderQuestion(result.next_question); + } + }, 2200); + + } else { + // Still attempts remaining + const msg = t.wrongFeedback.replace("{attempts}", result.attempts_left); + feedbackBox.innerHTML = `${msg}`; + if (checkBtn) checkBtn.disabled = false; + if (input) { + input.disabled = false; + input.select(); + } + // Re-enable choice buttons + choiceBtns.forEach(b => b.disabled = false); + } + } + + } catch (err) { + alert(t.networkError + err.message); + if (checkBtn) checkBtn.disabled = false; + if (input) input.disabled = false; + choiceBtns.forEach(b => b.disabled = false); + } +} + +async function requestHint() { + if (!currentSessionId) return; + const hintBox = document.getElementById("hintBox"); + const hintText = document.getElementById("hintText"); + const t = UI_TEXT[currentLang] || UI_TEXT.de; + + try { + const res = await fetch(`/api/game/hint?session_id=${encodeURIComponent(currentSessionId)}`); + const data = await res.json(); + hintText.innerText = data.hint || t.noHint; + hintBox.classList.remove("hidden"); + } catch (err) { + hintText.innerText = t.hintFailed; + hintBox.classList.remove("hidden"); + } +} + +function finishGame() { + if (timerInterval) clearInterval(timerInterval); + saveRecentQuestions(sessionQuestionIds); + document.getElementById("progressBar").style.width = "100%"; + window.location.href = `/finale?score=${currentScore}&total=${totalQuestions}&time=${elapsedSeconds}&lang=${currentLang}&diff=${encodeURIComponent(selectedDifficulty)}`; +} + +// Enter key to submit +document.addEventListener("DOMContentLoaded", () => { + const input = document.getElementById("answerInput"); + if (input) { + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + checkAnswer(); + } + }); + } + applyQuizTranslations(currentLang); +}); diff --git a/app/templates/base.html b/app/templates/base.html index b065469..51e333c 100755 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -7,8 +7,8 @@ - - + + {% block head %}{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html index f959ace..016bb2b 100755 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -12,69 +12,44 @@
-
-
- -
+ +
+
+
- -
-
-
-
- Hogwarts Crest -
-

Hogwarts-Schule für Hexerei und Zauberei

-

Schulleiter: Albus Dumbledore
(Großhexenmeister, Träger des Merlin-Ordens 1. Klasse)

-
- ❦ - - ✦ - - ❧ -
-
+ +
-
-

Liebe Hexe, lieber Zauberer,

-

- die schwebenden Kerzen der Großen Halle flackern in unruhigem Schein, und der Sprechende Hut hat nach Jahrhunderten des Schweigens ein wohlbehütetes Geheimnis preisgegeben: Tief in den uralten Gewölben des Schlosses – verborgen hinter den wachsamen Wasserspeiern und den staubigen Bänden der Verbotenen Abteilung – wurde eine magische Prüfungsurkunde versiegelt. -

-

- Zehn arkanische Prüfungen des Scharfsinns, der Zauberkunde und der Entschlossenheit liegen vor dir. Nur wer wahren Mut beweist und die uralten Rätsel meistert, sichert sich seinen ehrenvollen Platz in den Chroniken von Hogwarts und trägt bis zu 1.000 Hauspunkte für sein Haus davon! -

-

- Halte deinen Zauberstab bereit, besinne dich auf deine Flüche und Gegenzauber und nimm dich vor verhexten Trugschlüssen in Acht. Wir erwarten deine Eule nicht später als mit dem nächsten Glockenschlag des Uhrenturms. -

+ +
+ Hogwarts-Wappen +

Hogwarts-Schule

+

für Hexerei und Zauberei

+
-
-

Mit vorzüglicher Hochachtung,

-

Minerva McGonagall

-

Stellvertretende Schulleiterin

-
-
+ +
- -
- -
- Absender: -

Hogwarts-Schule für Hexerei und Zauberei

-

Schulleitung • Schloss Hogwarts

+ +
+
-
- -
-
-
+ +
+ Abs.: Hogwarts Schule für Hexerei und Zauberei +
+ + +
+ Hogwarts-Wappen Siegel +
+
+
- -
- Hogwarts-Wappen Siegel -
-
@@ -183,5 +158,5 @@ {% endblock %} {% block scripts %} - + {% endblock %}