Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>German Akkusativ Practice</title> | |
| <style> | |
| :root { | |
| --bg-gradient: linear-gradient(135deg, #1e3c72, #2a5298); | |
| --card-bg: #ffffff; | |
| --text-dark: #333333; | |
| --primary: #007bff; | |
| --primary-hover: #0056b3; | |
| --success: #28a745; | |
| --success-hover: #218838; | |
| --danger: #dc3545; | |
| --danger-bg: #ffe6e6; | |
| --gray: #6c757d; | |
| --light-gray: #f8f9fa; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: var(--bg-gradient); | |
| color: white; | |
| margin: 0; | |
| padding: 20px 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 700px; | |
| padding: 20px; | |
| box-sizing: border-box; | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
| } | |
| .card { | |
| background: var(--card-bg); | |
| color: var(--text-dark); | |
| padding: 30px; | |
| border-radius: 12px; | |
| box-shadow: 0 8px 20px rgba(0,0,0,0.2); | |
| margin-bottom: 20px; | |
| display: none; | |
| } | |
| .card.active { | |
| display: block; | |
| animation: fadeIn 0.4s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Revision & Settings Box Specifics */ | |
| .revision-box { | |
| background: var(--light-gray); | |
| padding: 15px; | |
| border-left: 5px solid var(--primary); | |
| border-radius: 5px; | |
| margin-bottom: 20px; | |
| font-size: 15px; | |
| line-height: 1.6; | |
| } | |
| .compact-revision { | |
| background: #eef5ff; | |
| padding: 12px; | |
| font-size: 14px; | |
| margin-bottom: 15px; | |
| border-left: 4px solid var(--primary); | |
| border-radius: 5px; | |
| line-height: 1.5; | |
| } | |
| .settings-group { | |
| background: var(--light-gray); | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin: 20px 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| border: 1px solid #e0e0e0; | |
| } | |
| .settings-toggle { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| } | |
| input[type="checkbox"] { | |
| width: 18px; | |
| height: 18px; | |
| cursor: pointer; | |
| } | |
| /* Buttons */ | |
| button { | |
| padding: 12px 24px; | |
| border: none; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: bold; | |
| transition: all 0.2s; | |
| width: 100%; | |
| margin-top: 15px; | |
| } | |
| .start-btn { background: var(--success); color: white; } | |
| .start-btn:hover { background: var(--success-hover); } | |
| .next-btn { background: var(--primary); color: white; } | |
| .next-btn:hover { background: var(--primary-hover); } | |
| .next-btn:disabled { background: var(--gray); cursor: not-allowed; } | |
| /* Inputs */ | |
| input[type="text"] { | |
| padding: 12px; | |
| width: 100%; | |
| font-size: 16px; | |
| border: 2px solid #ddd; | |
| border-radius: 6px; | |
| box-sizing: border-box; | |
| margin-top: 10px; | |
| transition: border-color 0.2s; | |
| } | |
| input[type="text"]:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| } | |
| input[type="text"]:disabled { | |
| background: #eee; | |
| } | |
| /* Quiz Elements */ | |
| .progress { | |
| text-align: right; | |
| font-size: 14px; | |
| color: var(--gray); | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| } | |
| #question { | |
| font-size: 20px; | |
| font-weight: bold; | |
| margin: 0 0 5px 0; | |
| } | |
| .hint { | |
| color: var(--gray); | |
| font-style: italic; | |
| margin-top: 0; | |
| margin-bottom: 15px; | |
| } | |
| .timer { | |
| font-size: 18px; | |
| font-weight: bold; | |
| color: var(--danger); | |
| text-align: center; | |
| margin-top: 15px; | |
| height: 24px; | |
| } | |
| .feedback-area { | |
| text-align: center; | |
| font-size: 17px; | |
| margin-top: 15px; | |
| min-height: 50px; | |
| } | |
| .correct { color: var(--success); font-weight: bold;} | |
| .wrong { color: var(--danger); font-weight: bold;} | |
| .highlight-mistake { | |
| background-color: var(--danger-bg); | |
| color: var(--danger); | |
| text-decoration: underline; | |
| padding: 0 4px; | |
| border-radius: 4px; | |
| font-weight: bold; | |
| } | |
| /* Summary Results Elements */ | |
| .result-score { | |
| text-align: center; | |
| font-size: 32px; | |
| font-weight: bold; | |
| color: var(--primary); | |
| margin: 10px 0 20px 0; | |
| } | |
| .summary-container { | |
| max-height: 350px; | |
| overflow-y: auto; | |
| border-top: 2px solid #eee; | |
| border-bottom: 2px solid #eee; | |
| padding: 10px 0; | |
| margin-bottom: 20px; | |
| text-align: left; | |
| } | |
| .mistake-card { | |
| background: #fafafa; | |
| border-left: 4px solid var(--danger); | |
| padding: 12px; | |
| margin-bottom: 15px; | |
| border-radius: 4px; | |
| } | |
| .mistake-q { | |
| font-weight: bold; | |
| font-size: 15px; | |
| margin-bottom: 8px; | |
| } | |
| .mistake-row { | |
| font-size: 15px; | |
| margin-bottom: 4px; | |
| } | |
| .perfect-score { | |
| text-align: center; | |
| color: var(--success); | |
| font-size: 20px; | |
| font-weight: bold; | |
| padding: 20px 0; | |
| } | |
| /* Scrollbar styling for summary container */ | |
| .summary-container::-webkit-scrollbar { width: 8px; } | |
| .summary-container::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px;} | |
| .summary-container::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px;} | |
| .summary-container::-webkit-scrollbar-thumb:hover { background: #aaa; } | |
| .developer-brand { | |
| text-align: center; | |
| margin-top: 30px; | |
| font-size: 14px; | |
| color: rgba(255, 255, 255, 0.7); | |
| letter-spacing: 1px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🇩🇪 German Akkusativ Practice</h1> | |
| <!-- STATE 1: Intro & Settings --> | |
| <div id="intro-screen" class="card active"> | |
| <h2>📘 Quick Revision</h2> | |
| <div class="revision-box"> | |
| <b>Sentence Structure:</b> Subject + Verb + Object<br><br> | |
| <b>Akkusativ Change (The Object):</b><br> | |
| der ➜ <b>den</b><br> | |
| die ➜ die<br> | |
| das ➜ das<br><br> | |
| <b>Verb Endings:</b><br> | |
| ich ➜ e | du ➜ st | er/sie ➜ t<br> | |
| wir ➜ en | ihr ➜ t | sie ➜ en | |
| </div> | |
| <div class="settings-group"> | |
| <label class="settings-toggle"> | |
| <input type="checkbox" id="timerToggle" > | |
| Enable 30s Timer per question | |
| </label> | |
| <label class="settings-toggle"> | |
| <input type="checkbox" id="showGuideToggle" checked> | |
| Show grammar guide during practice | |
| </label> | |
| </div> | |
| <button class="start-btn" onclick="startPractice()">Start Practice</button> | |
| </div> | |
| <!-- STATE 2: Active Practice --> | |
| <div id="practice-screen" class="card"> | |
| <!-- Optional compact guide shown during practice --> | |
| <div id="practice-guide" class="compact-revision" style="display: none;"> | |
| <b>Akkusativ:</b> der ➜ <b>den</b> | die ➜ die | das ➜ das<br> | |
| <b>Verbs:</b> ich ➜ e | du ➜ st | er/sie/es ➜ t | wir/sie ➜ en | ihr ➜ t | |
| </div> | |
| <div class="progress" id="progressText">Question 1 / 25</div> | |
| <p id="question"></p> | |
| <p class="hint" id="hint"></p> | |
| <input type="text" id="answer" placeholder="Write full German sentence..."> | |
| <button class="next-btn" id="submitBtn" onclick="checkAnswer()">Submit</button> | |
| <div class="feedback-area" id="feedback"></div> | |
| <div class="timer" id="timer"></div> | |
| </div> | |
| <!-- STATE 3: Results --> | |
| <div id="result-screen" class="card"> | |
| <h2 style="text-align: center;">Practice Complete! 🎉</h2> | |
| <div class="result-score" id="result"></div> | |
| <div id="summary-area"></div> | |
| <button class="start-btn" onclick="resetApp()">Try Again</button> | |
| </div> | |
| <!-- Add this right below your result-screen div --> | |
| <div class="developer-brand"> | |
| Developed by <b>Abdullah Tarar</b> | |
| </div> | |
| </div> <!-- This is the closing tag for <div class="container"> --> | |
| </div> | |
| <script> | |
| const sentences = [ | |
| {q:"1. I hear the birds.", hint:"(hören – die Vögel)", a:"Ich höre die Vögel"}, | |
| {q:"2. She cleans the table.", hint:"(putzen – der Tisch)", a:"Sie putzt den Tisch"}, | |
| {q:"3. They open the box.", hint:"(öffnen – die Kiste)", a:"Sie öffnen die Kiste"}, | |
| {q:"4. We buy the fruit.", hint:"(kaufen – das Obst)", a:"Wir kaufen das Obst"}, | |
| {q:"5. He needs the ticket.", hint:"(brauchen – das Ticket)", a:"Er braucht das Ticket"}, | |
| {q:"6. I take the umbrella.", hint:"(nehmen – der Regenschirm)", a:"Ich nehme den Regenschirm"}, | |
| {q:"7. She sees the clouds.", hint:"(sehen – die Wolken)", a:"Sie sieht die Wolken"}, | |
| {q:"8. They read the book.", hint:"(lesen – das Buch)", a:"Sie lesen das Buch"}, | |
| {q:"9. We watch the sky.", hint:"(beobachten – der Himmel)", a:"Wir beobachten den Himmel"}, | |
| {q:"10. He carries the bag.", hint:"(trägen – die Tasche)", a:"Er trägt die Tasche"}, | |
| {q:"11. I close the window.", hint:"(schließen – das Fenster)", a:"Ich schließe das Fenster"}, | |
| {q:"12. She writes the message.", hint:"(schreiben – die Nachricht)", a:"Sie schreibt die Nachricht"}, | |
| {q:"13. They build the house.", hint:"(bauen – das Haus)", a:"Sie bauen das Haus"}, | |
| {q:"14. We drink the juice.", hint:"(trinken – der Saft)", a:"Wir trinken den Saft"}, | |
| {q:"15. He finds the wallet.", hint:"(finden – die Geldbörse)", a:"Er findet die Geldbörse"}, | |
| {q:"16. I wash the dishes.", hint:"(waschen – das Geschirr)", a:"Ich wasche das Geschirr"}, | |
| {q:"17. She uses the phone.", hint:"(benutzen – das Handy)", a:"Sie benutzt das Handy"}, | |
| {q:"18. They draw the picture.", hint:"(zeichnen – das Bild)", a:"Sie zeichnen das Bild"}, | |
| {q:"19. We eat the apple.", hint:"(essen – der Apfel)", a:"Wir essen den Apfel"}, | |
| {q:"20. He saves the document.", hint:"(speichern – das Dokument)", a:"Er speichert das Dokument"}, | |
| {q:"21. I like the song.", hint:"(mögen – das Lied)", a:"Ich mag das Lied"}, | |
| {q:"22. She opens the door.", hint:"(öffnen – die Tür)", a:"Sie öffnet die Tür"}, | |
| {q:"23. They cook the rice.", hint:"(kochen – der Reis)", a:"Sie kochen den Reis"}, | |
| {q:"24. We cut the bread.", hint:"(schneiden – das Brot)", a:"Wir schneiden das Brot"}, | |
| {q:"25. He washes the shirt.", hint:"(wäschen – das Hemd)", a:"Er wäscht das Hemd"} | |
| ]; | |
| let current = 0; | |
| let score = 0; | |
| let timeLeft = 30; | |
| let timerInterval; | |
| let mistakesList = []; | |
| // Listen for "Enter" key on the input field | |
| document.getElementById("answer").addEventListener("keypress", function(event) { | |
| if (event.key === "Enter") { | |
| event.preventDefault(); | |
| if(!document.getElementById("submitBtn").disabled) { | |
| checkAnswer(); | |
| } | |
| } | |
| }); | |
| function switchScreen(screenId) { | |
| document.getElementById("intro-screen").classList.remove("active"); | |
| document.getElementById("practice-screen").classList.remove("active"); | |
| document.getElementById("result-screen").classList.remove("active"); | |
| document.getElementById(screenId).classList.add("active"); | |
| } | |
| function startPractice() { | |
| current = 0; | |
| score = 0; | |
| mistakesList = []; // Reset mistakes history | |
| // Toggle grammar guide | |
| let showGuide = document.getElementById("showGuideToggle").checked; | |
| document.getElementById("practice-guide").style.display = showGuide ? "block" : "none"; | |
| switchScreen("practice-screen"); | |
| loadQuestion(); | |
| } | |
| function loadQuestion() { | |
| if (current >= sentences.length) { | |
| showResult(); | |
| return; | |
| } | |
| // Enable inputs | |
| let answerInput = document.getElementById("answer"); | |
| let submitBtn = document.getElementById("submitBtn"); | |
| answerInput.disabled = false; | |
| submitBtn.disabled = false; | |
| answerInput.value = ""; | |
| answerInput.focus(); | |
| document.getElementById("progressText").innerHTML = `Question ${current + 1} / ${sentences.length}`; | |
| document.getElementById("question").innerHTML = sentences[current].q; | |
| document.getElementById("hint").innerHTML = sentences[current].hint; | |
| document.getElementById("feedback").innerHTML = ""; | |
| if (document.getElementById("timerToggle").checked) { | |
| startTimer(); | |
| } else { | |
| document.getElementById("timer").innerHTML = ""; | |
| } | |
| } | |
| function startTimer() { | |
| clearInterval(timerInterval); | |
| timeLeft = 30; | |
| document.getElementById("timer").innerHTML = "⏳ Time: " + timeLeft + "s"; | |
| timerInterval = setInterval(() => { | |
| timeLeft--; | |
| document.getElementById("timer").innerHTML = "⏳ Time: " + timeLeft + "s"; | |
| if (timeLeft <= 0) { | |
| clearInterval(timerInterval); | |
| handleTimeUp(); | |
| } | |
| }, 1000); | |
| } | |
| // Visual Diff Function: Compares user input to correct answer word-by-word | |
| function getHighlightedMistakeHTML(userText, correctText) { | |
| if (!userText) return "<span class='highlight-mistake'>(No answer)</span>"; | |
| let uOrig = userText.trim().split(/\s+/); | |
| let uLower = userText.trim().toLowerCase().replace(/[.!]/g, '').split(/\s+/); | |
| let cLower = correctText.trim().toLowerCase().replace(/[.!]/g, '').split(/\s+/); | |
| let result = []; | |
| for (let i = 0; i < uOrig.length; i++) { | |
| // If the words don't match, highlight the user's word | |
| if (uLower[i] !== cLower[i]) { | |
| result.push(`<span class="highlight-mistake">${uOrig[i]}</span>`); | |
| } else { | |
| result.push(uOrig[i]); | |
| } | |
| } | |
| return result.join(" "); | |
| } | |
| function handleTimeUp() { | |
| disableInputs(); | |
| mistakesList.push({ | |
| q: sentences[current].q, | |
| userHTML: "<span class='wrong'>(Time Out)</span>", | |
| correct: sentences[current].a | |
| }); | |
| document.getElementById("feedback").innerHTML = `<span class='wrong'>✘ Time up!</span><br>Correct: ${sentences[current].a}`; | |
| current++; | |
| setTimeout(loadQuestion, 3000); | |
| } | |
| function checkAnswer() { | |
| clearInterval(timerInterval); | |
| disableInputs(); | |
| let userInput = document.getElementById("answer").value; | |
| // Clean up strings to ignore case and trailing punctuation for the score check | |
| let userClean = userInput.trim().toLowerCase().replace(/[.!]/g, ''); | |
| let correctClean = sentences[current].a.toLowerCase().replace(/[.!]/g, ''); | |
| let delay = 1500; // default delay for correct answer | |
| if (userClean === correctClean) { | |
| score++; | |
| document.getElementById("feedback").innerHTML = "<span class='correct'>✔ Correct!</span>"; | |
| } else { | |
| delay = 3500; // longer delay so they can read the mistakes | |
| let highlightedUserHTML = getHighlightedMistakeHTML(userInput, sentences[current].a); | |
| mistakesList.push({ | |
| q: sentences[current].q, | |
| userHTML: highlightedUserHTML, | |
| correct: sentences[current].a | |
| }); | |
| document.getElementById("feedback").innerHTML = ` | |
| <span class='wrong'>✘ Incorrect:</span> ${highlightedUserHTML}<br> | |
| <span class='correct'>✔ Correct:</span> ${sentences[current].a} | |
| `; | |
| } | |
| current++; | |
| setTimeout(loadQuestion, delay); | |
| } | |
| function disableInputs() { | |
| document.getElementById("answer").disabled = true; | |
| document.getElementById("submitBtn").disabled = true; | |
| } | |
| function showResult() { | |
| clearInterval(timerInterval); | |
| switchScreen("result-screen"); | |
| document.getElementById("result").innerHTML = `${score} / ${sentences.length}`; | |
| // Generate Summary | |
| let summaryHTML = ""; | |
| if (mistakesList.length === 0) { | |
| summaryHTML = "<div class='perfect-score'>Perfect! You made no mistakes. 🌟</div>"; | |
| } else { | |
| summaryHTML = `<div class="summary-container">`; | |
| mistakesList.forEach(item => { | |
| summaryHTML += ` | |
| <div class="mistake-card"> | |
| <div class="mistake-q">${item.q}</div> | |
| <div class="mistake-row"><b>You wrote:</b> ${item.userHTML}</div> | |
| <div class="mistake-row" style="color: var(--success);"><b>Correct:</b> ${item.correct}</div> | |
| </div> | |
| `; | |
| }); | |
| summaryHTML += `</div>`; | |
| } | |
| document.getElementById("summary-area").innerHTML = summaryHTML; | |
| } | |
| function resetApp() { | |
| switchScreen("intro-screen"); | |
| } | |
| </script> | |
| </body> | |
| </html> |