Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Netzwerk A1 — German Vocabulary Practice | Flashcards, Quizzes & Hardcore Mode</title> | |
| <!-- SEO Meta Tags --> | |
| <meta name="description" | |
| content="Master German vocabulary from Netzwerk A1 Kapitel 1 & 2 with interactive flashcards, MCQ quizzes, matching games, typing challenges, and hardcore practice modes. 170+ words across 6 levels with smart mistake repetition for better memorization."> | |
| <meta name="keywords" | |
| content="German vocabulary, Netzwerk A1, learn German, German flashcards, German quiz, Kapitel 1, Kapitel 2, A1 German, German practice, Deutsch lernen, German language, vocabulary trainer, MCQ German, German words"> | |
| <meta name="author" content="Abdullah Tarar"> | |
| <meta name="robots" content="index, follow"> | |
| <meta name="language" content="English"> | |
| <meta name="revisit-after" content="7 days"> | |
| <meta name="theme-color" content="#0e0c1a"> | |
| <!-- Open Graph / Facebook --> | |
| <meta property="og:type" content="website"> | |
| <meta property="og:title" content="Netzwerk A1 — German Vocabulary Practice Tool"> | |
| <meta property="og:description" | |
| content="Interactive German vocabulary trainer with flashcards, quizzes, matching games & hardcore practice modes. 170+ words, 6 levels, smart mistake repetition."> | |
| <meta property="og:site_name" content="Netzwerk A1 Practice"> | |
| <meta property="og:locale" content="en_US"> | |
| <!-- Twitter Card --> | |
| <meta name="twitter:card" content="summary_large_image"> | |
| <meta name="twitter:title" content="Netzwerk A1 — German Vocabulary Practice Tool"> | |
| <meta name="twitter:description" | |
| content="Master German vocabulary with interactive flashcards, quizzes, matching games & hardcore practice modes. 170+ words across 6 levels."> | |
| <!-- Canonical --> | |
| <link rel="canonical" href="./"> | |
| <!-- Structured Data (JSON-LD) --> | |
| <script type="application/ld+json"> | |
| { | |
| "@context": "https://schema.org", | |
| "@type": "WebApplication", | |
| "name": "Netzwerk A1 German Vocabulary Practice", | |
| "description": "An interactive German vocabulary learning tool for Netzwerk A1 (Kapitel 1 and 2) featuring flashcards, multiple-choice quizzes, matching games, typing challenges, fill-in-the-blank exercises, and hardcore practice modes with smart mistake repetition.", | |
| "applicationCategory": "EducationalApplication", | |
| "operatingSystem": "Any", | |
| "offers": { | |
| "@type": "Offer", | |
| "price": "0", | |
| "priceCurrency": "USD" | |
| }, | |
| "author": { | |
| "@type": "Person", | |
| "name": "Abdullah Tarar", | |
| "sameAs": "https://instagram.com/abdullahtarar.3" | |
| }, | |
| "inLanguage": ["en", "de"], | |
| "educationalLevel": "Beginner", | |
| "learningResourceType": "Interactive Resource", | |
| "keywords": "German, vocabulary, A1, Netzwerk, flashcards, quiz, language learning" | |
| } | |
| </script> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700;900&family=DM+Mono:wght@300;400;500&family=DM+Sans:wght@300;400;500&display=swap" | |
| rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg: #0e0c1a; | |
| --surface: #16132b; | |
| --card: #1e1a35; | |
| --border: #2e2850; | |
| --accent: #e8c547; | |
| --accent2: #7c6fff; | |
| --accent3: #ff6b6b; | |
| --accent4: #4ecdc4; | |
| --text: #f0eeff; | |
| --muted: #8a82aa; | |
| --success: #4ecdc4; | |
| --error: #ff6b6b; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'DM Sans', sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Background noise texture */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E"); | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| /* Gradient blobs */ | |
| .blob { | |
| position: fixed; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| opacity: 0.12; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| .blob1 { | |
| width: 400px; | |
| height: 400px; | |
| background: var(--accent2); | |
| top: -100px; | |
| left: -100px; | |
| } | |
| .blob2 { | |
| width: 300px; | |
| height: 300px; | |
| background: var(--accent); | |
| bottom: -50px; | |
| right: -50px; | |
| } | |
| .blob3 { | |
| width: 200px; | |
| height: 200px; | |
| background: var(--accent3); | |
| top: 50%; | |
| left: 60%; | |
| } | |
| /* Header */ | |
| header { | |
| position: relative; | |
| z-index: 10; | |
| padding: 40px 40px 30px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| flex-wrap: wrap; | |
| gap: 20px; | |
| } | |
| .logo { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 2rem; | |
| font-weight: 900; | |
| letter-spacing: -1px; | |
| } | |
| .logo span { | |
| color: var(--accent); | |
| } | |
| .score-display { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.85rem; | |
| color: var(--muted); | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| padding: 8px 18px; | |
| border-radius: 100px; | |
| display: flex; | |
| gap: 20px; | |
| } | |
| .score-display b { | |
| color: var(--accent); | |
| } | |
| /* Mode selector */ | |
| .mode-bar { | |
| position: relative; | |
| z-index: 10; | |
| display: flex; | |
| gap: 8px; | |
| padding: 20px 40px; | |
| border-bottom: 1px solid var(--border); | |
| overflow-x: auto; | |
| scrollbar-width: none; | |
| } | |
| .mode-bar::-webkit-scrollbar { | |
| display: none; | |
| } | |
| /* Level selector bar */ | |
| .level-bar { | |
| position: relative; | |
| z-index: 10; | |
| background: linear-gradient(135deg, rgba(124, 111, 255, 0.05) 0%, transparent 100%); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .level-btn { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| letter-spacing: 0.05em; | |
| padding: 10px 16px; | |
| border-radius: 100px; | |
| border: 1.5px solid var(--border); | |
| background: transparent; | |
| color: var(--muted); | |
| cursor: pointer; | |
| white-space: nowrap; | |
| transition: all 0.2s; | |
| } | |
| .level-btn:hover:not(.locked) { | |
| border-color: var(--accent2); | |
| color: var(--text); | |
| } | |
| .level-btn.locked { | |
| opacity: 0.4; | |
| cursor: default; | |
| } | |
| .level-btn.active { | |
| background: var(--accent2); | |
| color: white; | |
| border-color: var(--accent2); | |
| font-weight: 600; | |
| } | |
| .level-btn.level-1 { | |
| --accent-color: #7c6fff; | |
| } | |
| .level-btn.level-2 { | |
| --accent-color: #e8c547; | |
| } | |
| .level-btn.level-3 { | |
| --accent-color: #ff6b6b; | |
| } | |
| .level-btn.level-4 { | |
| --accent-color: #4ecdc4; | |
| } | |
| .level-btn.level-5 { | |
| --accent-color: #a8e6cf; | |
| } | |
| .level-btn.level-6 { | |
| --accent-color: #ffd93d; | |
| } | |
| .mode-btn { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| letter-spacing: 0.05em; | |
| padding: 8px 18px; | |
| border-radius: 100px; | |
| border: 1px solid var(--border); | |
| background: transparent; | |
| color: var(--muted); | |
| cursor: pointer; | |
| white-space: nowrap; | |
| transition: all 0.2s; | |
| } | |
| .mode-btn:hover { | |
| border-color: var(--accent2); | |
| color: var(--text); | |
| } | |
| .mode-btn.active { | |
| background: var(--accent); | |
| color: var(--bg); | |
| border-color: var(--accent); | |
| font-weight: 500; | |
| } | |
| /* Main area */ | |
| main { | |
| position: relative; | |
| z-index: 10; | |
| padding: 40px; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| } | |
| .section-title { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 1.5rem; | |
| margin-bottom: 8px; | |
| color: var(--text); | |
| } | |
| .section-sub { | |
| font-size: 0.85rem; | |
| color: var(--muted); | |
| margin-bottom: 30px; | |
| font-family: 'DM Mono', monospace; | |
| } | |
| /* Flashcard */ | |
| .flashcard-wrap { | |
| perspective: 1200px; | |
| width: 100%; | |
| max-width: 580px; | |
| margin: 0 auto 30px; | |
| } | |
| .flashcard { | |
| width: 100%; | |
| aspect-ratio: 1.6; | |
| position: relative; | |
| transform-style: preserve-3d; | |
| transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); | |
| cursor: pointer; | |
| } | |
| .flashcard.flipped { | |
| transform: rotateY(180deg); | |
| } | |
| .flashcard-face { | |
| position: absolute; | |
| inset: 0; | |
| backface-visibility: hidden; | |
| border-radius: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 40px; | |
| text-align: center; | |
| border: 1px solid var(--border); | |
| } | |
| .flashcard-front { | |
| background: var(--card); | |
| background-image: linear-gradient(135deg, rgba(124, 111, 255, 0.08) 0%, transparent 60%); | |
| } | |
| .flashcard-back { | |
| background: var(--surface); | |
| background-image: linear-gradient(135deg, rgba(232, 197, 71, 0.08) 0%, transparent 60%); | |
| transform: rotateY(180deg); | |
| } | |
| .card-tag { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.7rem; | |
| color: var(--muted); | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| margin-bottom: 20px; | |
| } | |
| .card-word { | |
| font-family: 'Playfair Display', serif; | |
| font-size: clamp(1.5rem, 4vw, 2.5rem); | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 12px; | |
| line-height: 1.2; | |
| } | |
| .card-example { | |
| font-size: 0.85rem; | |
| color: var(--muted); | |
| font-style: italic; | |
| line-height: 1.5; | |
| } | |
| .card-translation { | |
| font-family: 'Playfair Display', serif; | |
| font-size: clamp(1.2rem, 3vw, 2rem); | |
| color: var(--accent); | |
| font-weight: 700; | |
| } | |
| .card-hint { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.75rem; | |
| color: var(--accent2); | |
| margin-top: 12px; | |
| } | |
| .flip-hint { | |
| text-align: center; | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| font-family: 'DM Mono', monospace; | |
| margin-bottom: 24px; | |
| } | |
| .card-controls { | |
| display: flex; | |
| gap: 12px; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| margin-bottom: 30px; | |
| } | |
| .btn { | |
| font-family: 'DM Sans', sans-serif; | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| padding: 10px 24px; | |
| border-radius: 100px; | |
| border: 1px solid var(--border); | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| background: var(--card); | |
| color: var(--text); | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| } | |
| .btn.primary { | |
| background: var(--accent); | |
| color: var(--bg); | |
| border-color: var(--accent); | |
| } | |
| .btn.success { | |
| background: rgba(78, 205, 196, 0.15); | |
| border-color: var(--success); | |
| color: var(--success); | |
| } | |
| .btn.danger { | |
| background: rgba(255, 107, 107, 0.15); | |
| border-color: var(--error); | |
| color: var(--error); | |
| } | |
| .progress-bar { | |
| height: 3px; | |
| background: var(--border); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin-bottom: 20px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--accent2), var(--accent)); | |
| border-radius: 10px; | |
| transition: width 0.4s ease; | |
| } | |
| .card-counter { | |
| text-align: center; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| margin-bottom: 16px; | |
| } | |
| /* Quiz */ | |
| .quiz-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 40px; | |
| margin-bottom: 20px; | |
| background-image: linear-gradient(135deg, rgba(124, 111, 255, 0.05) 0%, transparent 50%); | |
| } | |
| .quiz-question { | |
| font-family: 'Playfair Display', serif; | |
| font-size: clamp(1.3rem, 3vw, 1.8rem); | |
| font-weight: 700; | |
| margin-bottom: 10px; | |
| color: var(--text); | |
| } | |
| .quiz-context { | |
| font-size: 0.83rem; | |
| color: var(--muted); | |
| font-style: italic; | |
| margin-bottom: 30px; | |
| } | |
| .quiz-options { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 12px; | |
| } | |
| @media (max-width: 500px) { | |
| .quiz-options { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .quiz-option { | |
| padding: 14px 20px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| background: var(--surface); | |
| color: var(--text); | |
| font-size: 0.9rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| text-align: left; | |
| font-family: 'DM Sans', sans-serif; | |
| } | |
| .quiz-option:hover:not(:disabled) { | |
| border-color: var(--accent2); | |
| background: rgba(124, 111, 255, 0.1); | |
| transform: translateY(-2px); | |
| } | |
| .quiz-option.correct { | |
| border-color: var(--success); | |
| background: rgba(78, 205, 196, 0.15); | |
| color: var(--success); | |
| } | |
| .quiz-option.wrong { | |
| border-color: var(--error); | |
| background: rgba(255, 107, 107, 0.15); | |
| color: var(--error); | |
| } | |
| .quiz-option:disabled { | |
| cursor: default; | |
| } | |
| .feedback-msg { | |
| text-align: center; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.85rem; | |
| padding: 12px; | |
| border-radius: 10px; | |
| margin-top: 16px; | |
| display: none; | |
| } | |
| .feedback-msg.show { | |
| display: block; | |
| } | |
| .feedback-msg.ok { | |
| background: rgba(78, 205, 196, 0.1); | |
| color: var(--success); | |
| } | |
| .feedback-msg.bad { | |
| background: rgba(255, 107, 107, 0.1); | |
| color: var(--error); | |
| } | |
| /* Matching */ | |
| .matching-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 12px; | |
| } | |
| .match-col { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .match-item { | |
| padding: 12px 18px; | |
| border-radius: 12px; | |
| border: 1.5px solid var(--border); | |
| background: var(--surface); | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-size: 0.88rem; | |
| text-align: center; | |
| user-select: none; | |
| } | |
| .match-item:hover:not(.matched):not(.selected) { | |
| border-color: var(--accent2); | |
| background: rgba(124, 111, 255, 0.1); | |
| } | |
| .match-item.selected { | |
| border-color: var(--accent); | |
| background: rgba(232, 197, 71, 0.1); | |
| color: var(--accent); | |
| } | |
| .match-item.matched { | |
| border-color: var(--success); | |
| background: rgba(78, 205, 196, 0.1); | |
| color: var(--success); | |
| opacity: 0.7; | |
| cursor: default; | |
| } | |
| .match-item.wrong-flash { | |
| border-color: var(--error); | |
| background: rgba(255, 107, 107, 0.15); | |
| } | |
| /* Type input */ | |
| .type-wrap { | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| margin-top: 24px; | |
| } | |
| .type-input { | |
| flex: 1; | |
| min-width: 200px; | |
| padding: 12px 20px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| background: var(--surface); | |
| color: var(--text); | |
| font-size: 1rem; | |
| font-family: 'DM Sans', sans-serif; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| .type-input:focus { | |
| border-color: var(--accent2); | |
| } | |
| .type-input.correct-input { | |
| border-color: var(--success); | |
| } | |
| .type-input.wrong-input { | |
| border-color: var(--error); | |
| } | |
| /* Vocabulary browser */ | |
| .vocab-filters { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| margin-bottom: 20px; | |
| } | |
| .filter-btn { | |
| padding: 6px 14px; | |
| border-radius: 100px; | |
| border: 1px solid var(--border); | |
| background: transparent; | |
| color: var(--muted); | |
| font-size: 0.78rem; | |
| font-family: 'DM Mono', monospace; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .filter-btn.active { | |
| background: var(--accent2); | |
| color: white; | |
| border-color: var(--accent2); | |
| } | |
| .vocab-search { | |
| width: 100%; | |
| padding: 12px 20px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| background: var(--surface); | |
| color: var(--text); | |
| font-size: 0.9rem; | |
| font-family: 'DM Sans', sans-serif; | |
| outline: none; | |
| margin-bottom: 20px; | |
| transition: border-color 0.2s; | |
| } | |
| .vocab-search:focus { | |
| border-color: var(--accent2); | |
| } | |
| .vocab-search::placeholder { | |
| color: var(--muted); | |
| } | |
| .vocab-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .vocab-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 14px 20px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border); | |
| background: var(--card); | |
| transition: all 0.2s; | |
| gap: 16px; | |
| flex-wrap: wrap; | |
| } | |
| .vocab-item:hover { | |
| border-color: var(--border); | |
| transform: translateX(4px); | |
| } | |
| .vocab-de { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 1rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| flex: 1; | |
| } | |
| .vocab-en { | |
| font-size: 0.85rem; | |
| color: var(--muted); | |
| flex: 1; | |
| text-align: right; | |
| } | |
| .vocab-badge { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.65rem; | |
| padding: 3px 10px; | |
| border-radius: 100px; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| color: var(--muted); | |
| white-space: nowrap; | |
| } | |
| .vocab-badge.kap1 { | |
| border-color: var(--accent2); | |
| color: var(--accent2); | |
| } | |
| .vocab-badge.kap2 { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| } | |
| .vocab-badge.high { | |
| border-color: var(--accent3); | |
| color: var(--accent3); | |
| } | |
| /* Fill-in-blank */ | |
| .fib-sentence { | |
| font-size: 1.2rem; | |
| line-height: 2; | |
| margin-bottom: 20px; | |
| color: var(--text); | |
| } | |
| .blank-input { | |
| display: inline-block; | |
| border: none; | |
| border-bottom: 2px solid var(--accent); | |
| background: transparent; | |
| color: var(--accent); | |
| font-size: 1.1rem; | |
| font-family: 'DM Sans', sans-serif; | |
| width: 120px; | |
| outline: none; | |
| text-align: center; | |
| padding: 0 4px; | |
| transition: border-color 0.2s; | |
| } | |
| .blank-input:focus { | |
| border-color: var(--accent2); | |
| } | |
| .blank-input.correct-b { | |
| border-color: var(--success); | |
| color: var(--success); | |
| } | |
| .blank-input.wrong-b { | |
| border-color: var(--error); | |
| color: var(--error); | |
| } | |
| /* Result screen */ | |
| .result-screen { | |
| text-align: center; | |
| padding: 60px 20px; | |
| } | |
| .result-emoji { | |
| font-size: 4rem; | |
| margin-bottom: 20px; | |
| } | |
| .result-title { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 2rem; | |
| font-weight: 900; | |
| margin-bottom: 10px; | |
| color: var(--accent); | |
| } | |
| .result-sub { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.9rem; | |
| color: var(--muted); | |
| margin-bottom: 30px; | |
| } | |
| /* Animations */ | |
| @keyframes fadeUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes pulse { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| } | |
| @keyframes pop { | |
| 0% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.05); | |
| } | |
| 100% { | |
| transform: scale(1); | |
| } | |
| } | |
| .fadeUp { | |
| animation: fadeUp 0.4s ease forwards; | |
| } | |
| .animate-pop { | |
| animation: pop 0.3s ease; | |
| } | |
| .hidden { | |
| display: none ; | |
| } | |
| /* Section container */ | |
| .practice-section { | |
| animation: fadeUp 0.4s ease; | |
| } | |
| /* Category pills for vocab */ | |
| .cat-label { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.7rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| color: var(--muted); | |
| padding: 16px 0 8px; | |
| } | |
| /* Days of week section */ | |
| .days-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); | |
| gap: 10px; | |
| margin-bottom: 30px; | |
| } | |
| .day-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 14px 12px; | |
| text-align: center; | |
| transition: all 0.2s; | |
| cursor: default; | |
| } | |
| .day-card:hover { | |
| border-color: var(--accent2); | |
| transform: translateY(-3px); | |
| } | |
| .day-de { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 0.95rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| .day-en { | |
| font-size: 0.75rem; | |
| color: var(--muted); | |
| margin-top: 4px; | |
| } | |
| .months-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); | |
| gap: 10px; | |
| margin-bottom: 30px; | |
| } | |
| .month-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 12px; | |
| text-align: center; | |
| transition: all 0.2s; | |
| cursor: default; | |
| } | |
| .month-card:hover { | |
| border-color: var(--accent); | |
| transform: translateY(-3px); | |
| } | |
| .month-de { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 0.88rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| .month-num { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.7rem; | |
| color: var(--accent); | |
| margin-top: 4px; | |
| } | |
| .seasons-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, 1fr); | |
| gap: 14px; | |
| margin-bottom: 30px; | |
| } | |
| .season-card { | |
| padding: 20px; | |
| border-radius: 16px; | |
| border: 1px solid var(--border); | |
| background: var(--card); | |
| transition: all 0.2s; | |
| cursor: default; | |
| } | |
| .season-card:hover { | |
| transform: translateY(-3px); | |
| } | |
| .season-icon { | |
| font-size: 2rem; | |
| margin-bottom: 8px; | |
| } | |
| .season-de { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| } | |
| .season-en { | |
| font-size: 0.8rem; | |
| color: var(--muted); | |
| margin-top: 4px; | |
| } | |
| .info-box { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-left: 3px solid var(--accent); | |
| border-radius: 12px; | |
| padding: 16px 20px; | |
| margin-bottom: 16px; | |
| font-size: 0.88rem; | |
| line-height: 1.6; | |
| color: var(--muted); | |
| } | |
| .info-box strong { | |
| color: var(--text); | |
| } | |
| .greeting-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-bottom: 24px; | |
| } | |
| @media (max-width: 480px) { | |
| .greeting-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .greeting-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 14px 18px; | |
| transition: all 0.2s; | |
| cursor: default; | |
| } | |
| .greeting-card:hover { | |
| border-color: var(--accent2); | |
| transform: translateX(4px); | |
| } | |
| .greeting-de { | |
| font-family: 'Playfair Display', serif; | |
| font-weight: 700; | |
| font-size: 0.95rem; | |
| color: var(--text); | |
| } | |
| .greeting-en { | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| margin-top: 3px; | |
| } | |
| /* Jobs grid */ | |
| .jobs-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .job-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 14px 16px; | |
| transition: all 0.2s; | |
| cursor: default; | |
| } | |
| .job-card:hover { | |
| border-color: var(--accent4); | |
| transform: translateY(-2px); | |
| } | |
| .job-de { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 0.88rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| .job-de-f { | |
| font-size: 0.76rem; | |
| color: var(--accent2); | |
| margin-top: 2px; | |
| font-family: 'DM Mono', monospace; | |
| } | |
| .job-en { | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| margin-top: 4px; | |
| } | |
| /* Hobbies */ | |
| .hobby-grid { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| margin-bottom: 24px; | |
| } | |
| .hobby-pill { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 10px 18px; | |
| border-radius: 100px; | |
| border: 1px solid var(--border); | |
| background: var(--card); | |
| transition: all 0.2s; | |
| cursor: default; | |
| } | |
| .hobby-pill:hover { | |
| border-color: var(--accent4); | |
| transform: scale(1.03); | |
| } | |
| .hobby-de { | |
| font-weight: 500; | |
| font-size: 0.88rem; | |
| } | |
| .hobby-en { | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| } | |
| /* Confetti burst (CSS only) */ | |
| .confetti-wrap { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 9999; | |
| } | |
| .confetti-piece { | |
| position: absolute; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 2px; | |
| animation: confettiFall 1.2s ease-in forwards; | |
| opacity: 0; | |
| } | |
| @keyframes confettiFall { | |
| 0% { | |
| opacity: 1; | |
| transform: translateY(-20px) rotate(0deg); | |
| } | |
| 100% { | |
| opacity: 0; | |
| transform: translateY(60vh) rotate(360deg); | |
| } | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--border); | |
| border-radius: 10px; | |
| } | |
| /* Developer Footer */ | |
| .developer-footer { | |
| position: fixed; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| z-index: 100; | |
| background: linear-gradient(135deg, rgba(30, 26, 53, 0.97) 0%, rgba(14, 12, 26, 0.98) 100%); | |
| border-top: 1px solid var(--border); | |
| padding: 10px 20px; | |
| text-align: center; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.75rem; | |
| color: var(--muted); | |
| backdrop-filter: blur(12px); | |
| } | |
| .developer-footer a { | |
| color: var(--accent); | |
| text-decoration: none; | |
| font-weight: 500; | |
| transition: all 0.2s; | |
| } | |
| .developer-footer a:hover { | |
| color: var(--accent2); | |
| text-shadow: 0 0 8px rgba(124, 111, 255, 0.4); | |
| } | |
| /* Practice/Hardcore Status Bar */ | |
| .quiz-status-bar { | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| margin-bottom: 20px; | |
| padding: 12px 16px; | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 14px; | |
| } | |
| .status-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| padding: 4px 12px; | |
| border-radius: 100px; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| } | |
| .status-item .status-val { | |
| font-weight: 600; | |
| color: var(--text); | |
| } | |
| .status-item.mistakes .status-val { | |
| color: var(--error); | |
| } | |
| .status-item.correct-stat .status-val { | |
| color: var(--success); | |
| } | |
| /* Timer */ | |
| .quiz-timer { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.85rem; | |
| color: var(--accent); | |
| justify-content: center; | |
| margin-bottom: 16px; | |
| } | |
| .quiz-timer .timer-icon { | |
| font-size: 1rem; | |
| } | |
| /* Memory Tip */ | |
| .memory-tip { | |
| margin-top: 12px; | |
| padding: 10px 16px; | |
| border-radius: 10px; | |
| background: linear-gradient(135deg, rgba(232, 197, 71, 0.08) 0%, rgba(124, 111, 255, 0.06) 100%); | |
| border: 1px solid rgba(232, 197, 71, 0.2); | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| color: var(--accent); | |
| display: none; | |
| } | |
| .memory-tip.show { | |
| display: block; | |
| animation: fadeUp 0.3s ease; | |
| } | |
| /* Hardcore Result Detail */ | |
| .result-detail-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 12px; | |
| max-width: 400px; | |
| margin: 20px auto; | |
| } | |
| .result-stat-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 16px; | |
| text-align: center; | |
| } | |
| .result-stat-card .stat-label { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.7rem; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 6px; | |
| } | |
| .result-stat-card .stat-value { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--accent); | |
| } | |
| .result-stat-card.error-stat .stat-value { | |
| color: var(--error); | |
| } | |
| .result-stat-card.success-stat .stat-value { | |
| color: var(--success); | |
| } | |
| /* Wrong Words List */ | |
| .wrong-words-list { | |
| max-width: 500px; | |
| margin: 20px auto; | |
| text-align: left; | |
| } | |
| .wrong-words-list .ww-title { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 10px; | |
| } | |
| .wrong-word-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 10px 16px; | |
| border-radius: 10px; | |
| border: 1px solid rgba(255, 107, 107, 0.2); | |
| background: rgba(255, 107, 107, 0.05); | |
| margin-bottom: 6px; | |
| font-size: 0.85rem; | |
| } | |
| .wrong-word-item .ww-de { | |
| font-weight: 600; | |
| color: var(--text); | |
| } | |
| .wrong-word-item .ww-en { | |
| color: var(--muted); | |
| font-size: 0.78rem; | |
| } | |
| .wrong-word-item .ww-tip { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.68rem; | |
| color: var(--accent); | |
| } | |
| /* Practice empty state */ | |
| .practice-empty { | |
| text-align: center; | |
| padding: 60px 20px; | |
| } | |
| .practice-empty .empty-icon { | |
| font-size: 4rem; | |
| margin-bottom: 16px; | |
| } | |
| .practice-empty .empty-title { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 8px; | |
| } | |
| .practice-empty .empty-sub { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.85rem; | |
| color: var(--muted); | |
| } | |
| /* Pad bottom for footer */ | |
| body { | |
| padding-bottom: 45px; | |
| } | |
| /* Confirmation Modal */ | |
| .confirm-overlay { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.72); | |
| z-index: 300; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| backdrop-filter: blur(8px); | |
| animation: fadeUp 0.15s ease; | |
| } | |
| .confirm-overlay.hidden { | |
| display: none; | |
| } | |
| .confirm-box { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 22px; | |
| padding: 36px 28px 28px; | |
| max-width: 360px; | |
| width: 90%; | |
| text-align: center; | |
| box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5); | |
| } | |
| .confirm-box .cbox-emoji { | |
| font-size: 2.8rem; | |
| margin-bottom: 12px; | |
| } | |
| .confirm-box .cbox-title { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 8px; | |
| } | |
| .confirm-box .cbox-msg { | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.8rem; | |
| color: var(--muted); | |
| margin-bottom: 24px; | |
| line-height: 1.6; | |
| } | |
| .confirm-btns { | |
| display: flex; | |
| gap: 10px; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| /* Hardcore Level Picker */ | |
| .hc-level-picker { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| margin-bottom: 20px; | |
| } | |
| .hc-level-btn { | |
| padding: 8px 18px; | |
| border-radius: 100px; | |
| border: 1px solid var(--border); | |
| background: var(--surface); | |
| color: var(--muted); | |
| font-family: 'DM Mono', monospace; | |
| font-size: 0.78rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .hc-level-btn:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| } | |
| .hc-level-btn.active { | |
| background: var(--accent); | |
| color: #0e0c1a; | |
| border-color: var(--accent); | |
| font-weight: 600; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="blob blob1"></div> | |
| <div class="blob blob2"></div> | |
| <div class="blob blob3"></div> | |
| <header> | |
| <div class="logo">Netz<span>werk</span> A1</div> | |
| <div class="score-display"> | |
| Score: <b id="totalScore">0</b> pts | Streak: <b id="streakCount">0</b> 🔥 | |
| </div> | |
| </header> | |
| <div class="mode-bar"> | |
| <button class="mode-btn active" onclick="showSection('flashcards')">📚 Flashcards</button> | |
| <button class="mode-btn" onclick="showSection('quiz')">🧠 Quiz</button> | |
| <button class="mode-btn" onclick="showSection('matching')">🔗 Matching</button> | |
| <button class="mode-btn" onclick="showSection('typeit')">✍️ Type It</button> | |
| <button class="mode-btn" onclick="showSection('fillin')">📝 Fill-in-Blank</button> | |
| <button class="mode-btn" onclick="showSection('practice')">🔄 Practice</button> | |
| <button class="mode-btn" onclick="showSection('hardcore')">🔥 Hardcore</button> | |
| <button class="mode-btn" onclick="showSection('allhardcore')">💀 All Hardcore</button> | |
| <button class="mode-btn" onclick="showSection('reference')">📖 Reference</button> | |
| </div> | |
| <div class="level-bar" id="levelBar"> | |
| <div | |
| style="font-family:'DM Mono',monospace;font-size:0.75rem;color:var(--muted);padding:8px 40px;text-transform:uppercase;letter-spacing:0.05em;"> | |
| 📊 Your Level Progress</div> | |
| <div style="display:flex;gap:8px;padding:12px 40px;overflow-x:auto;scrollbar-width:none;"> | |
| <button class="level-btn level-1" onclick="selectLevel(1)" id="levelBtn1">🎯 Level 1</button> | |
| <button class="level-btn level-2 locked" onclick="selectLevel(2)" id="levelBtn2">🔒 Level 2</button> | |
| <button class="level-btn level-3 locked" onclick="selectLevel(3)" id="levelBtn3">🔒 Level 3</button> | |
| <button class="level-btn level-4 locked" onclick="selectLevel(4)" id="levelBtn4">🔒 Level 4</button> | |
| <button class="level-btn level-5 locked" onclick="selectLevel(5)" id="levelBtn5">🔒 Level 5</button> | |
| <button class="level-btn level-6 locked" onclick="selectLevel(6)" id="levelBtn6">🔒 Level 6</button> | |
| </div> | |
| </div> | |
| <main> | |
| <!-- ═══════════ FLASHCARDS ═══════════ --> | |
| <div id="sec-flashcards" class="practice-section"> | |
| <div class="section-title">Flashcards</div> | |
| <div class="section-sub">Click the card to reveal the translation</div> | |
| <div class="vocab-filters" style="margin-bottom:20px"> | |
| <button class="filter-btn active" onclick="setCardFilter('all',this)">All</button> | |
| <button class="filter-btn" onclick="setCardFilter('1',this)">Kapitel 1</button> | |
| <button class="filter-btn" onclick="setCardFilter('2',this)">Kapitel 2</button> | |
| <button class="filter-btn" onclick="setCardFilter('high',this)">⭐ High-Freq</button> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="cardProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="cardCounter">Card 1 of ?</div> | |
| <div class="flashcard-wrap"> | |
| <div class="flashcard" id="mainCard" onclick="flipCard()"> | |
| <div class="flashcard-face flashcard-front"> | |
| <div class="card-tag" id="cardTag">GERMAN</div> | |
| <div class="card-word" id="cardWord">—</div> | |
| <div class="card-example" id="cardExample"></div> | |
| </div> | |
| <div class="flashcard-face flashcard-back"> | |
| <div class="card-tag">ENGLISH</div> | |
| <div class="card-translation" id="cardTranslation">—</div> | |
| <div class="card-hint" id="cardHint"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flip-hint">↑ Click card to flip</div> | |
| <div class="card-controls"> | |
| <button class="btn danger" onclick="cardKnew(false)">✗ Still Learning</button> | |
| <button class="btn" onclick="flipCard()">Flip</button> | |
| <button class="btn success" onclick="cardKnew(true)">✓ Got It!</button> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="btn" onclick="prevCard()">← Prev</button> | |
| <button class="btn primary" onclick="shuffleCards()">🔀 Shuffle</button> | |
| <button class="btn" onclick="nextCard()">Next →</button> | |
| </div> | |
| </div> | |
| <!-- ═══════════ QUIZ ═══════════ --> | |
| <div id="sec-quiz" class="practice-section hidden"> | |
| <div class="section-title">Multiple Choice Quiz</div> | |
| <div class="section-sub">Select the correct English translation</div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="quizProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="quizCounter">Question 1 of 20</div> | |
| <div class="quiz-card" id="quizCard"> | |
| <div class="quiz-question" id="quizQ">Loading...</div> | |
| <div class="quiz-context" id="quizCtx"></div> | |
| <div class="quiz-options" id="quizOpts"></div> | |
| <div class="feedback-msg" id="quizFeedback"></div> | |
| </div> | |
| <div class="card-controls"> | |
| <button class="btn primary" id="quizNextBtn" onclick="nextQuiz()" style="display:none">Next Question →</button> | |
| <button class="btn" onclick="resetQuiz()">↺ Restart Quiz</button> | |
| </div> | |
| <div id="quizResult" class="result-screen hidden"> | |
| <div class="result-emoji" id="quizEmoji">🎉</div> | |
| <div class="result-title" id="quizScore">0 / 20</div> | |
| <div class="result-sub" id="quizMsg">Keep practicing!</div> | |
| <button class="btn primary" onclick="resetQuiz()">Play Again</button> | |
| </div> | |
| </div> | |
| <!-- ═══════════ MATCHING ═══════════ --> | |
| <div id="sec-matching" class="practice-section hidden"> | |
| <div class="section-title">Matching Game</div> | |
| <div class="section-sub">Click a German word, then its English match</div> | |
| <div class="card-controls" style="margin-bottom:20px"> | |
| <button class="btn primary" onclick="resetMatching()">New Round</button> | |
| <span id="matchScore" style="font-family:'DM Mono',monospace;font-size:0.85rem;color:var(--muted)">Matched: | |
| 0/8</span> | |
| </div> | |
| <div class="matching-grid"> | |
| <div class="match-col" id="matchLeft"></div> | |
| <div class="match-col" id="matchRight"></div> | |
| </div> | |
| <div id="matchDone" class="result-screen hidden"> | |
| <div class="result-emoji">🎊</div> | |
| <div class="result-title">Perfect Match!</div> | |
| <div class="result-sub">You matched all pairs correctly.</div> | |
| <button class="btn primary" onclick="resetMatching()">Play Again</button> | |
| </div> | |
| </div> | |
| <!-- ═══════════ TYPE IT ═══════════ --> | |
| <div id="sec-typeit" class="practice-section hidden"> | |
| <div class="section-title">Type It</div> | |
| <div class="section-sub">Type the German word for the English prompt</div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="typeProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="typeCounter">Word 1 of ?</div> | |
| <div class="quiz-card" id="typeCard"> | |
| <div class="card-tag">TRANSLATE TO GERMAN</div> | |
| <div class="quiz-question" id="typeQ" style="margin-top:12px">—</div> | |
| <div class="quiz-context" id="typeCtx"></div> | |
| <div class="type-wrap"> | |
| <input class="type-input" id="typeInput" placeholder="Type German here..." | |
| onkeydown="if(event.key==='Enter')checkType()"> | |
| <button class="btn primary" onclick="checkType()">Check</button> | |
| <button class="btn" onclick="skipType()">Skip</button> | |
| </div> | |
| <div class="feedback-msg" id="typeFeedback"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn" onclick="resetType()">↺ Restart</button> | |
| </div> | |
| </div> | |
| <!-- ═══════════ FILL-IN-BLANK ═══════════ --> | |
| <div id="sec-fillin" class="practice-section hidden"> | |
| <div class="section-title">Fill in the Blank</div> | |
| <div class="section-sub">Complete the German sentence</div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="fibProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="fibCounter">Sentence 1 of ?</div> | |
| <div class="quiz-card" id="fibCard"> | |
| <div class="fib-sentence" id="fibSentence"></div> | |
| <div class="card-controls" style="justify-content:flex-start;margin-top:10px;margin-bottom:0"> | |
| <button class="btn primary" onclick="checkFib()">Check</button> | |
| <button class="btn" onclick="skipFib()">Skip</button> | |
| <button class="btn" onclick="hintFib()">💡 Hint</button> | |
| </div> | |
| <div class="feedback-msg" id="fibFeedback"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn" onclick="resetFib()">↺ Restart</button> | |
| </div> | |
| </div> | |
| <!-- ═══════════ REFERENCE ═══════════ --> | |
| <div id="sec-reference" class="practice-section hidden"> | |
| <div class="section-title">Reference Guide</div> | |
| <div class="section-sub">Browse all vocabulary organized by topic</div> | |
| <!-- Greetings --> | |
| <div class="cat-label">👋 Greetings & Farewells — Kapitel 1</div> | |
| <div class="greeting-grid"> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Hallo!</div> | |
| <div class="greeting-en">Hello!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Guten Morgen!</div> | |
| <div class="greeting-en">Good morning!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Guten Tag!</div> | |
| <div class="greeting-en">Good day! Hello!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Guten Abend!</div> | |
| <div class="greeting-en">Good evening!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Gute Nacht!</div> | |
| <div class="greeting-en">Good night!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Tschüs!</div> | |
| <div class="greeting-en">Bye!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Auf Wiedersehen!</div> | |
| <div class="greeting-en">Goodbye!</div> | |
| </div> | |
| <div class="greeting-card"> | |
| <div class="greeting-de">Bis bald!</div> | |
| <div class="greeting-en">See you soon!</div> | |
| </div> | |
| </div> | |
| <div class="info-box"> | |
| <strong>How are you?</strong><br> | |
| Wie geht's? / Wie geht es Ihnen? → Danke, gut. / Es geht. / Sehr gut! / Nicht schlecht. | |
| </div> | |
| <!-- Days --> | |
| <div class="cat-label">📅 Days of the Week — Kapitel 2</div> | |
| <div class="days-grid"> | |
| <div class="day-card"> | |
| <div class="day-de">der Montag</div> | |
| <div class="day-en">Monday</div> | |
| </div> | |
| <div class="day-card"> | |
| <div class="day-de">der Dienstag</div> | |
| <div class="day-en">Tuesday</div> | |
| </div> | |
| <div class="day-card"> | |
| <div class="day-de">der Mittwoch</div> | |
| <div class="day-en">Wednesday</div> | |
| </div> | |
| <div class="day-card"> | |
| <div class="day-de">der Donnerstag</div> | |
| <div class="day-en">Thursday</div> | |
| </div> | |
| <div class="day-card"> | |
| <div class="day-de">der Freitag</div> | |
| <div class="day-en">Friday</div> | |
| </div> | |
| <div class="day-card"> | |
| <div class="day-de">der Samstag</div> | |
| <div class="day-en">Saturday</div> | |
| </div> | |
| <div class="day-card"> | |
| <div class="day-de">der Sonntag</div> | |
| <div class="day-en">Sunday</div> | |
| </div> | |
| </div> | |
| <!-- Months --> | |
| <div class="cat-label">📆 Months — Kapitel 2</div> | |
| <div class="months-grid"> | |
| <div class="month-card"> | |
| <div class="month-de">Januar</div> | |
| <div class="month-num">01</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">Februar</div> | |
| <div class="month-num">02</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">März</div> | |
| <div class="month-num">03</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">April</div> | |
| <div class="month-num">04</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">Mai</div> | |
| <div class="month-num">05</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">Juni</div> | |
| <div class="month-num">06</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">Juli</div> | |
| <div class="month-num">07</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">August</div> | |
| <div class="month-num">08</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">September</div> | |
| <div class="month-num">09</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">Oktober</div> | |
| <div class="month-num">10</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">November</div> | |
| <div class="month-num">11</div> | |
| </div> | |
| <div class="month-card"> | |
| <div class="month-de">Dezember</div> | |
| <div class="month-num">12</div> | |
| </div> | |
| </div> | |
| <!-- Seasons --> | |
| <div class="cat-label">🍂 Seasons — Kapitel 2</div> | |
| <div class="seasons-grid"> | |
| <div class="season-card"> | |
| <div class="season-icon">🌸</div> | |
| <div class="season-de">der Frühling</div> | |
| <div class="season-en">spring</div> | |
| </div> | |
| <div class="season-card"> | |
| <div class="season-icon">☀️</div> | |
| <div class="season-de">der Sommer</div> | |
| <div class="season-en">summer</div> | |
| </div> | |
| <div class="season-card"> | |
| <div class="season-icon">🍂</div> | |
| <div class="season-de">der Herbst</div> | |
| <div class="season-en">fall / autumn</div> | |
| </div> | |
| <div class="season-card"> | |
| <div class="season-icon">❄️</div> | |
| <div class="season-de">der Winter</div> | |
| <div class="season-en">winter</div> | |
| </div> | |
| </div> | |
| <!-- Hobbies --> | |
| <div class="cat-label">🎯 Hobbies — Kapitel 2</div> | |
| <div class="hobby-grid"> | |
| <div class="hobby-pill"><span>🗨️</span><span class="hobby-de">chatten</span><span class="hobby-en">to | |
| chat</span></div> | |
| <div class="hobby-pill"><span>📷</span><span class="hobby-de">fotografieren</span><span class="hobby-en">to | |
| photograph</span></div> | |
| <div class="hobby-pill"><span>🏃</span><span class="hobby-de">joggen</span><span class="hobby-en">to jog</span> | |
| </div> | |
| <div class="hobby-pill"><span>🍳</span><span class="hobby-de">kochen</span><span class="hobby-en">to cook</span> | |
| </div> | |
| <div class="hobby-pill"><span>✈️</span><span class="hobby-de">reisen</span><span class="hobby-en">to | |
| travel</span></div> | |
| <div class="hobby-pill"><span>🏊</span><span class="hobby-de">schwimmen</span><span class="hobby-en">to | |
| swim</span></div> | |
| <div class="hobby-pill"><span>🎵</span><span class="hobby-de">singen</span><span class="hobby-en">to sing</span> | |
| </div> | |
| <div class="hobby-pill"><span>💃</span><span class="hobby-de">tanzen</span><span class="hobby-en">to | |
| dance</span></div> | |
| <div class="hobby-pill"><span>📚</span><span class="hobby-de">lesen</span><span class="hobby-en">to read</span> | |
| </div> | |
| <div class="hobby-pill"><span>🎮</span><span class="hobby-de">spielen</span><span class="hobby-en">to | |
| play</span></div> | |
| <div class="hobby-pill"><span>🎬</span><span class="hobby-de">Kino</span><span class="hobby-en">movie | |
| theater</span></div> | |
| <div class="hobby-pill"><span>🎨</span><span class="hobby-de">malen</span><span class="hobby-en">to paint</span> | |
| </div> | |
| </div> | |
| <!-- Jobs --> | |
| <div class="cat-label">💼 Jobs & Professions — Kapitel 2</div> | |
| <div class="jobs-grid"> | |
| <div class="job-card"> | |
| <div class="job-de">der Arzt</div> | |
| <div class="job-de-f">die Ärztin</div> | |
| <div class="job-en">physician</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Architekt</div> | |
| <div class="job-de-f">die Architektin</div> | |
| <div class="job-en">architect</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Ingenieur</div> | |
| <div class="job-de-f">die Ingenieurin</div> | |
| <div class="job-en">engineer</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Journalist</div> | |
| <div class="job-de-f">die Journalistin</div> | |
| <div class="job-en">journalist</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Professor</div> | |
| <div class="job-de-f">die Professorin</div> | |
| <div class="job-en">professor</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Taxifahrer</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">taxi driver</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Techniker</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">technician</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Boxer</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">boxer</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der DJ</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">DJ</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Fotograf</div> | |
| <div class="job-de-f">die Fotografin</div> | |
| <div class="job-en">photographer</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Hausmeister</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">caretaker</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Jurist</div> | |
| <div class="job-de-f">die Juristin</div> | |
| <div class="job-en">attorney</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Lehrer</div> | |
| <div class="job-de-f">die Lehrerin</div> | |
| <div class="job-en">teacher</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Programmierer</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">programmer</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Elektriker</div> | |
| <div class="job-de-f">–</div> | |
| <div class="job-en">electrician</div> | |
| </div> | |
| <div class="job-card"> | |
| <div class="job-de">der Student</div> | |
| <div class="job-de-f">die Studentin</div> | |
| <div class="job-en">student</div> | |
| </div> | |
| </div> | |
| <div class="info-box"> | |
| <strong>Was sind Sie von Beruf?</strong> — What is your profession?<br> | |
| <strong>Ich bin Arzt / Ich bin Lehrerin.</strong> — I am a doctor / I am a teacher. | |
| </div> | |
| <!-- Key Verbs --> | |
| <div class="cat-label">🔤 Key Verbs — Both Chapters</div> | |
| <div class="vocab-list"> | |
| <div class="vocab-item"><span class="vocab-de">heißen</span><span class="vocab-en">to be named (Ich | |
| heiße...)</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">kommen</span><span class="vocab-en">to come from</span><span | |
| class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">wohnen</span><span class="vocab-en">to live (in a | |
| place)</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">sprechen</span><span class="vocab-en">to speak</span><span | |
| class="vocab-badge high">⭐ High-Freq</span></div> | |
| <div class="vocab-item"><span class="vocab-de">lernen</span><span class="vocab-en">to learn</span><span | |
| class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">verstehen</span><span class="vocab-en">to understand</span><span | |
| class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">buchstabieren</span><span class="vocab-en">to spell</span><span | |
| class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">sein (ich bin)</span><span class="vocab-en">to be</span><span | |
| class="vocab-badge high">⭐ High-Freq</span></div> | |
| <div class="vocab-item"><span class="vocab-de">haben (er hat)</span><span class="vocab-en">to have</span><span | |
| class="vocab-badge high">⭐ High-Freq</span></div> | |
| <div class="vocab-item"><span class="vocab-de">arbeiten</span><span class="vocab-en">to work</span><span | |
| class="vocab-badge kap2">Kap 2</span></div> | |
| <div class="vocab-item"><span class="vocab-de">studieren</span><span class="vocab-en">to study</span><span | |
| class="vocab-badge kap2">Kap 2</span></div> | |
| <div class="vocab-item"><span class="vocab-de">machen</span><span class="vocab-en">to do, to make</span><span | |
| class="vocab-badge kap2">Kap 2</span></div> | |
| <div class="vocab-item"><span class="vocab-de">fahren (er fährt)</span><span class="vocab-en">to | |
| drive</span><span class="vocab-badge kap2">Kap 2</span></div> | |
| <div class="vocab-item"><span class="vocab-de">geben (es gibt)</span><span class="vocab-en">to give / there | |
| is</span><span class="vocab-badge kap2">Kap 2</span></div> | |
| <div class="vocab-item"><span class="vocab-de">lieben</span><span class="vocab-en">to love</span><span | |
| class="vocab-badge kap2">Kap 2</span></div> | |
| </div> | |
| <!-- Question words --> | |
| <div class="cat-label">❓ Question Words</div> | |
| <div class="vocab-list"> | |
| <div class="vocab-item"><span class="vocab-de">Wie?</span><span class="vocab-en">How?</span><span | |
| class="vocab-badge high">⭐</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Was?</span><span class="vocab-en">What?</span><span | |
| class="vocab-badge high">⭐</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Wer?</span><span class="vocab-en">Who?</span><span | |
| class="vocab-badge high">⭐</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Wo?</span><span class="vocab-en">Where?</span><span | |
| class="vocab-badge high">⭐</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Woher?</span><span class="vocab-en">From where?</span><span | |
| class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Wann?</span><span class="vocab-en">When?</span><span | |
| class="vocab-badge kap2">Kap 2</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Welche/r/s?</span><span class="vocab-en">Which?</span><span | |
| class="vocab-badge kap1">Kap 1</span></div> | |
| </div> | |
| <!-- Countries & Languages --> | |
| <div class="cat-label">🌍 Countries & Languages — Kapitel 1</div> | |
| <div class="vocab-list"> | |
| <div class="vocab-item"><span class="vocab-de">Deutschland → Deutsch</span><span class="vocab-en">Germany → | |
| German</span><span class="vocab-badge high">⭐</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Österreich → Deutsch</span><span class="vocab-en">Austria → | |
| German</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">die Schweiz → Deutsch/Rätoromanisch</span><span | |
| class="vocab-en">Switzerland → German/Romansh</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Frankreich → Französisch</span><span class="vocab-en">France → | |
| French</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Spanien → Spanisch</span><span class="vocab-en">Spain → | |
| Spanish</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Italien → Italienisch</span><span class="vocab-en">Italy → | |
| Italian</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Russland → Russisch</span><span class="vocab-en">Russia → | |
| Russian</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Japan → Japanisch</span><span class="vocab-en">Japan → | |
| Japanese</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">China → Chinesisch</span><span class="vocab-en">China → | |
| Chinese</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">die USA → Englisch</span><span class="vocab-en">USA → | |
| English</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Großbritannien → Englisch</span><span class="vocab-en">Great | |
| Britain → English</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Polen → Polnisch</span><span class="vocab-en">Poland → | |
| Polish</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">die Türkei → Türkisch</span><span class="vocab-en">Turkey → | |
| Turkish</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| <div class="vocab-item"><span class="vocab-de">Schweden → Schwedisch</span><span class="vocab-en">Sweden → | |
| Swedish</span><span class="vocab-badge kap1">Kap 1</span></div> | |
| </div> | |
| <!-- Vocab browser --> | |
| <div class="cat-label">🔍 Full Vocabulary Search</div> | |
| <input class="vocab-search" id="vocabSearch" placeholder="Search German or English..." oninput="searchVocab()"> | |
| <div class="vocab-filters"> | |
| <button class="filter-btn active" onclick="filterVocab('all',this)">All</button> | |
| <button class="filter-btn" onclick="filterVocab('1',this)">Kapitel 1</button> | |
| <button class="filter-btn" onclick="filterVocab('2',this)">Kapitel 2</button> | |
| <button class="filter-btn" onclick="filterVocab('high',this)">⭐ High-Freq</button> | |
| </div> | |
| <div class="vocab-list" id="vocabBrowser"></div> | |
| </div> | |
| <!-- ═══════════ PRACTICE (Repeat Mistakes) ═══════════ --> | |
| <div id="sec-practice" class="practice-section hidden"> | |
| <div class="section-title">🔄 Practice — Repeat Mistakes</div> | |
| <div class="section-sub">Words you got wrong appear 3× each. Keep going until zero mistakes!</div> | |
| <div id="practiceEmpty" class="practice-empty"> | |
| <div class="empty-icon">✅</div> | |
| <div class="empty-title">No Mistakes Yet!</div> | |
| <div class="empty-sub">Go take the Quiz first. Any words you get wrong will appear here for extra practice. | |
| </div> | |
| </div> | |
| <div id="practiceActive" class="hidden"> | |
| <div class="quiz-timer" id="practiceTimerDisplay">⏱ <span id="practiceTimer">00:00</span></div> | |
| <div class="quiz-status-bar"> | |
| <div class="status-item">📋 Remaining: <span class="status-val" id="practiceRemaining">0</span></div> | |
| <div class="status-item">✏️ Attempted: <span class="status-val" id="practiceAttempted">0</span></div> | |
| <div class="status-item mistakes">❌ Mistakes: <span class="status-val" id="practiceMistakes">0</span></div> | |
| <div class="status-item correct-stat">✅ Correct: <span class="status-val" id="practiceCorrectCount">0</span> | |
| </div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="practiceProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="practiceCounter">Question 1 of ?</div> | |
| <div class="quiz-card" id="practiceCard"> | |
| <div class="quiz-question" id="practiceQ">Loading...</div> | |
| <div class="quiz-context" id="practiceCtx"></div> | |
| <div class="quiz-options" id="practiceOpts"></div> | |
| <div class="feedback-msg" id="practiceFeedback"></div> | |
| <div class="memory-tip" id="practiceMemoryTip"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn primary" id="practiceNextBtn" onclick="nextPractice()" style="display:none">Next Question | |
| →</button> | |
| <button class="btn" onclick="resetPractice()">↺ Restart Practice</button> | |
| </div> | |
| </div> | |
| <div id="practiceResult" class="result-screen hidden"> | |
| <div class="result-emoji" id="practiceResEmoji">🎉</div> | |
| <div class="result-title" id="practiceResTitle">Practice Complete!</div> | |
| <div class="result-sub" id="practiceResMsg">You mastered all mistaken words!</div> | |
| <div class="result-detail-grid" id="practiceResultStats"></div> | |
| <div class="wrong-words-list" id="practiceWrongList"></div> | |
| <div class="card-controls" style="margin-top:20px"> | |
| <button class="btn primary" onclick="resetPractice()">🔄 Try Again</button> | |
| <button class="btn" onclick="showSection('quiz')">🧠 Back to Quiz</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════════ HARDCORE PRACTICE ═══════════ --> | |
| <div id="sec-hardcore" class="practice-section hidden"> | |
| <div class="section-title">🔥 Hardcore Practice — Level Test</div> | |
| <div class="section-sub">All levels unlocked. Pick any level and master every word!</div> | |
| <!-- Level Picker --> | |
| <div class="hc-level-picker" id="hcLevelPicker"> | |
| <button class="hc-level-btn active" onclick="selectHCLevel(1,this)">Level 1</button> | |
| <button class="hc-level-btn" onclick="selectHCLevel(2,this)">Level 2</button> | |
| <button class="hc-level-btn" onclick="selectHCLevel(3,this)">Level 3</button> | |
| <button class="hc-level-btn" onclick="selectHCLevel(4,this)">Level 4</button> | |
| <button class="hc-level-btn" onclick="selectHCLevel(5,this)">Level 5</button> | |
| <button class="hc-level-btn" onclick="selectHCLevel(6,this)">Level 6</button> | |
| </div> | |
| <div id="hardcoreActive"> | |
| <div class="quiz-timer" id="hardcoreTimerDisplay">⏱ <span id="hardcoreTimer">00:00</span></div> | |
| <div class="quiz-status-bar"> | |
| <div class="status-item">📋 Remaining: <span class="status-val" id="hardcoreRemaining">0</span></div> | |
| <div class="status-item">✏️ Attempted: <span class="status-val" id="hardcoreAttempted">0</span></div> | |
| <div class="status-item mistakes">❌ Mistakes: <span class="status-val" id="hardcoreMistakeCount">0</span> | |
| </div> | |
| <div class="status-item correct-stat">✅ Correct: <span class="status-val" id="hardcoreCorrectCount">0</span> | |
| </div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="hardcoreProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="hardcoreCounter">Question 1 of ?</div> | |
| <div class="quiz-card" id="hardcoreCard"> | |
| <div class="quiz-question" id="hardcoreQ">Loading...</div> | |
| <div class="quiz-context" id="hardcoreCtx"></div> | |
| <div class="quiz-options" id="hardcoreOpts"></div> | |
| <div class="feedback-msg" id="hardcoreFeedback"></div> | |
| <div class="memory-tip" id="hardcoreMemoryTip"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn primary" id="hardcoreNextBtn" onclick="nextHardcore()" style="display:none">Next Question | |
| →</button> | |
| <button class="btn" onclick="confirmHCStop()">⏹ Stop</button> | |
| <button class="btn" onclick="confirmHCRestart()">↺ Restart</button> | |
| </div> | |
| </div> | |
| <div id="hardcoreResult" class="result-screen hidden"> | |
| <div class="result-emoji" id="hardcoreResEmoji">🏆</div> | |
| <div class="result-title" id="hardcoreResTitle">Hardcore Complete!</div> | |
| <div class="result-sub" id="hardcoreResMsg">Here are your results:</div> | |
| <div class="result-detail-grid" id="hardcoreResultStats"></div> | |
| <div class="wrong-words-list" id="hardcoreWrongList"></div> | |
| <div class="card-controls" style="margin-top:20px"> | |
| <button class="btn primary" id="hcPracticeMistakesBtn" onclick="startHCMistakePractice()" | |
| style="display:none">🔄 Practice Mistakes</button> | |
| <button class="btn primary" onclick="resetHardcore()">🔥 Try Again</button> | |
| <button class="btn" onclick="showSection('quiz')">🧠 Back to Quiz</button> | |
| </div> | |
| </div> | |
| <!-- Practice Mistakes Sub-Section --> | |
| <div id="hcMistakePractice" class="hidden"> | |
| <div class="section-title" style="font-size:1.2rem">🔄 Hardcore Mistakes Practice</div> | |
| <div class="section-sub">Each wrong word appears 3×. Go until zero mistakes!</div> | |
| <div class="quiz-timer" id="hcmpTimerDisplay">⏱ <span id="hcmpTimer">00:00</span></div> | |
| <div class="quiz-status-bar"> | |
| <div class="status-item">📋 Remaining: <span class="status-val" id="hcmpRemaining">0</span></div> | |
| <div class="status-item">✏️ Attempted: <span class="status-val" id="hcmpAttempted">0</span></div> | |
| <div class="status-item mistakes">❌ Mistakes: <span class="status-val" id="hcmpMistakes">0</span></div> | |
| <div class="status-item correct-stat">✅ Correct: <span class="status-val" id="hcmpCorrect">0</span></div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="hcmpProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="hcmpCounter">Round 1 — Question 1 of ?</div> | |
| <div class="quiz-card"> | |
| <div class="quiz-question" id="hcmpQ">Loading...</div> | |
| <div class="quiz-context" id="hcmpCtx"></div> | |
| <div class="quiz-options" id="hcmpOpts"></div> | |
| <div class="feedback-msg" id="hcmpFeedback"></div> | |
| <div class="memory-tip" id="hcmpMemoryTip"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn primary" id="hcmpNextBtn" onclick="nextHCMP()" style="display:none">Next Question | |
| →</button> | |
| <button class="btn" onclick="startHCMistakePractice()">↺ Restart Practice</button> | |
| </div> | |
| </div> | |
| <!-- Practice Mistakes Result --> | |
| <div id="hcMistakePracticeResult" class="result-screen hidden"> | |
| <div class="result-emoji" id="hcmpResEmoji">🏆</div> | |
| <div class="result-title" id="hcmpResTitle">Mistakes Mastered!</div> | |
| <div class="result-sub" id="hcmpResMsg">You fixed all your mistakes!</div> | |
| <div class="result-detail-grid" id="hcmpResultStats"></div> | |
| <div class="wrong-words-list" id="hcmpWrongList"></div> | |
| <div class="card-controls" style="margin-top:20px"> | |
| <button class="btn primary" onclick="startHCMistakePractice()">🔄 Practice Again</button> | |
| <button class="btn primary" onclick="resetHardcore()">🔥 Retake Test</button> | |
| <button class="btn" onclick="showSection('quiz')">🧠 Back to Quiz</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ═══════════ ALL HARDCORE (Full Vocab All Levels) ═══════════ --> | |
| <div id="sec-allhardcore" class="practice-section hidden"> | |
| <div class="section-title">💀 All Hardcore — Complete Memory Test</div> | |
| <div class="section-sub">Every single word from ALL levels. The ultimate vocabulary challenge!</div> | |
| <div id="allhardcoreActive"> | |
| <div class="quiz-timer" id="allhardcoreTimerDisplay">⏱ <span id="allhardcoreTimer">00:00</span></div> | |
| <div class="quiz-status-bar"> | |
| <div class="status-item">📋 Remaining: <span class="status-val" id="allhardcoreRemaining">0</span></div> | |
| <div class="status-item">✏️ Attempted: <span class="status-val" id="allhardcoreAttempted">0</span></div> | |
| <div class="status-item mistakes">❌ Mistakes: <span class="status-val" id="allhardcoreMistakeCount">0</span> | |
| </div> | |
| <div class="status-item correct-stat">✅ Correct: <span class="status-val" | |
| id="allhardcoreCorrectCount">0</span></div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="allhardcoreProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="allhardcoreCounter">Question 1 of ?</div> | |
| <div class="quiz-card" id="allhardcoreCard"> | |
| <div class="quiz-question" id="allhardcoreQ">Loading...</div> | |
| <div class="quiz-context" id="allhardcoreCtx"></div> | |
| <div class="quiz-options" id="allhardcoreOpts"></div> | |
| <div class="feedback-msg" id="allhardcoreFeedback"></div> | |
| <div class="memory-tip" id="allhardcoreMemoryTip"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn primary" id="allhardcoreNextBtn" onclick="nextAllHardcore()" style="display:none">Next | |
| Question →</button> | |
| <button class="btn" onclick="confirmAHStop()">⏹ Stop</button> | |
| <button class="btn" onclick="confirmAHRestart()">↺ Restart</button> | |
| </div> | |
| </div> | |
| <div id="allhardcoreResult" class="result-screen hidden"> | |
| <div class="result-emoji" id="allhardcoreResEmoji">💀</div> | |
| <div class="result-title" id="allhardcoreResTitle">All Hardcore Complete!</div> | |
| <div class="result-sub" id="allhardcoreResMsg">Here are your results:</div> | |
| <div class="result-detail-grid" id="allhardcoreResultStats"></div> | |
| <div class="wrong-words-list" id="allhardcoreWrongList"></div> | |
| <div class="card-controls" style="margin-top:20px"> | |
| <button class="btn primary" onclick="resetAllHardcore()">💀 Try Again</button> | |
| <button class="btn primary" id="ahPracticeMistakesBtn" onclick="startAHMistakePractice()" | |
| style="display:none">🔄 Practice Mistakes</button> | |
| <button class="btn" onclick="showSection('quiz')">🧠 Back to Quiz</button> | |
| </div> | |
| </div> | |
| <!-- Practice Mistakes Sub-Section --> | |
| <div id="ahMistakePractice" class="hidden"> | |
| <div class="section-title" style="font-size:1.2rem">🔄 Practicing Your Mistakes</div> | |
| <div class="section-sub">Each wrong word appears 3×. Keep going until zero mistakes!</div> | |
| <div class="quiz-timer" id="ahmpTimerDisplay">⏱ <span id="ahmpTimer">00:00</span></div> | |
| <div class="quiz-status-bar"> | |
| <div class="status-item">📋 Remaining: <span class="status-val" id="ahmpRemaining">0</span></div> | |
| <div class="status-item">✏️ Attempted: <span class="status-val" id="ahmpAttempted">0</span></div> | |
| <div class="status-item mistakes">❌ Mistakes: <span class="status-val" id="ahmpMistakes">0</span></div> | |
| <div class="status-item correct-stat">✅ Correct: <span class="status-val" id="ahmpCorrect">0</span></div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="ahmpProgress" style="width:0%"></div> | |
| </div> | |
| <div class="card-counter" id="ahmpCounter">Round 1 — Question 1 of ?</div> | |
| <div class="quiz-card"> | |
| <div class="quiz-question" id="ahmpQ">Loading...</div> | |
| <div class="quiz-context" id="ahmpCtx"></div> | |
| <div class="quiz-options" id="ahmpOpts"></div> | |
| <div class="feedback-msg" id="ahmpFeedback"></div> | |
| <div class="memory-tip" id="ahmpMemoryTip"></div> | |
| </div> | |
| <div class="card-controls" style="margin-top:16px"> | |
| <button class="btn primary" id="ahmpNextBtn" onclick="nextAHMP()" style="display:none">Next Question | |
| →</button> | |
| <button class="btn" onclick="startAHMistakePractice()">↺ Restart Practice</button> | |
| </div> | |
| </div> | |
| <!-- Practice Mistakes Result --> | |
| <div id="ahMistakePracticeResult" class="result-screen hidden"> | |
| <div class="result-emoji" id="ahmpResEmoji">🏆</div> | |
| <div class="result-title" id="ahmpResTitle">Mistakes Mastered!</div> | |
| <div class="result-sub" id="ahmpResMsg">You fixed all your mistakes!</div> | |
| <div class="result-detail-grid" id="ahmpResultStats"></div> | |
| <div class="wrong-words-list" id="ahmpWrongList"></div> | |
| <div class="card-controls" style="margin-top:20px"> | |
| <button class="btn primary" onclick="startAHMistakePractice()">🔄 Practice Again</button> | |
| <button class="btn primary" onclick="resetAllHardcore()">💀 Retake Full Test</button> | |
| <button class="btn" onclick="showSection('quiz')">🧠 Back to Quiz</button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Developer Footer --> | |
| <div class="developer-footer"> | |
| Developed by <a href="https://instagram.com/abdullahtarar.3" target="_blank" rel="noopener noreferrer">Abdullah | |
| Tarar</a> | |
| </div> | |
| <!-- Confirmation Modal (shared) --> | |
| <div class="confirm-overlay hidden" id="confirmOverlay"> | |
| <div class="confirm-box"> | |
| <div class="cbox-emoji" id="confirmEmoji">⚠️</div> | |
| <div class="cbox-title" id="confirmTitle">Are you sure?</div> | |
| <div class="cbox-msg" id="confirmMsg">This action cannot be undone.</div> | |
| <div class="confirm-btns"> | |
| <button class="btn primary" id="confirmYesBtn" onclick="confirmYes()">Yes, proceed</button> | |
| <button class="btn" onclick="confirmNo()">Cancel</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="confetti-wrap" id="confettiWrap"></div> | |
| <script> | |
| // ═══════════════════════════════════════════════════════════ | |
| // DATA — Comprehensive vocab from Chapters 1 & 2 | |
| // ═══════════════════════════════════════════════════════════ | |
| const vocab = [ | |
| // ════════════════════════════════════════════════════════════ | |
| // LEVEL 1: GREETINGS & BASICS (Greeting pairs & essential politeness) | |
| // ════════════════════════════════════════════════════════════ | |
| { de: "Hallo!", en: "Hello!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Start of conversation", gender: "-" }, | |
| { de: "Tschüs!", en: "Bye!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Informal goodbye", gender: "-" }, | |
| { de: "Guten Morgen!", en: "Good morning!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Morning = Morgen", gender: "-" }, | |
| { de: "Guten Tag!", en: "Good day! Hello!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Daytime greeting", gender: "-" }, | |
| { de: "Guten Abend!", en: "Good evening!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Evening = Abend", gender: "-" }, | |
| { de: "Gute Nacht!", en: "Good night!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Before sleep", gender: "-" }, | |
| { de: "Auf Wiedersehen!", en: "Goodbye!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "Formal: Wiedersehen=re-seeing", gender: "-" }, | |
| { de: "Bis bald!", en: "See you soon!", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "bald=soon", gender: "-" }, | |
| { de: "danke", en: "thank you", ex: "Danke, gut!", ch: 1, high: true, level: 1, category: "politeness", memory: "Dance=Danke (memory trick)", gender: "-" }, | |
| { de: "bitte", en: "please", ex: "Bitte langsam sprechen.", ch: 1, high: true, level: 1, category: "politeness", memory: "Bitte is basic politeness", gender: "-" }, | |
| { de: "sehr", en: "very", ex: "Sehr gut!", ch: 1, high: true, level: 1, category: "politeness", memory: "sehr=so/very much", gender: "-" }, | |
| { de: "gut", en: "good", ex: "Es geht mir gut.", ch: 1, high: true, level: 1, category: "politeness", memory: "Good mood & health", gender: "-" }, | |
| { de: "Wie geht's?", en: "How's it going?", ex: "", ch: 1, high: true, level: 1, category: "greetings", memory: "geht's = how s it going?", gender: "-" }, | |
| { de: "ein bisschen", en: "a little", ex: "", ch: 1, high: true, level: 1, category: "descriptions", memory: "little piece (bisschen)", gender: "-" }, | |
| { de: "und", en: "and", ex: "", ch: 1, high: true, level: 1, category: "connectors", memory: "Connect ideas", gender: "-" }, | |
| { de: "auch", en: "also", ex: "", ch: 1, high: true, level: 1, category: "connectors", memory: "auch=also/too", gender: "-" }, | |
| { de: "nicht", en: "not", ex: "Ich verstehe das nicht.", ch: 1, high: true, level: 1, category: "negation", memory: "NICHT = nope!", gender: "-" }, | |
| { de: "ich", en: "I", ex: "", ch: 1, high: true, level: 1, category: "pronouns", memory: "ich=I/me", gender: "-" }, | |
| { de: "es", en: "it", ex: "Es geht mir gut.", ch: 1, high: true, level: 1, category: "pronouns", memory: "es=it (neutral)", gender: "-" }, | |
| { de: "du", en: "you (informal)", ex: "", ch: 1, high: true, level: 1, category: "pronouns", memory: "du=you,casual", gender: "-" }, | |
| { de: "Sie", en: "you (formal)", ex: "Wie heißen Sie?", ch: 1, high: true, level: 1, category: "pronouns", memory: "Sie=formal you (capital S)", gender: "-" }, | |
| { de: "Wie bitte?", en: "Come again? / Pardon?", ex: "", ch: 1, high: true, level: 1, category: "politeness", memory: "Politely ask to repeat", gender: "-" }, | |
| { de: "Entschuldigung", en: "excuse me / apology", ex: "", ch: 1, high: true, level: 1, category: "politeness", memory: "entschuldigen=apologize", gender: "f" }, | |
| { de: "langsam", en: "slow(ly)", ex: "Bitte sprechen Sie langsam.", ch: 1, high: true, level: 1, category: "descriptions", memory: "lang=long, slow rhythm", gender: "-" }, | |
| { de: "noch einmal", en: "once more", ex: "", ch: 1, high: false, level: 1, category: "phrases", memory: "More practice requested", gender: "-" }, | |
| // ════════════════════════════════════════════════════════════ | |
| // LEVEL 2: IDENTITY & ORIGINS (Names, where from, languages) | |
| // ════════════════════════════════════════════════════════════ | |
| { de: "heißen", en: "to be named", ex: "Ich heiße Anna.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "heiß=hot > name is HOT topic", gender: "-" }, | |
| { de: "der Name", en: "name", ex: "", ch: 1, high: true, level: 2, category: "nouns-personal", memory: "Name=Name (cognate)", gender: "m" }, | |
| { de: "kommen", en: "to come (from)", ex: "Woher kommst du?", ch: 1, high: true, level: 2, category: "verbs-core", memory: "kommen=come", gender: "-" }, | |
| { de: "aus", en: "from (origin)", ex: "Er kommt aus Italien.", ch: 1, high: true, level: 2, category: "prepositions", memory: "aus=aus-outside,from", gender: "-" }, | |
| { de: "wohnen", en: "to live (in a place)", ex: "Ich wohne in Berlin.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "Wohn=dwelling, home", gender: "-" }, | |
| { de: "in", en: "in", ex: "Er lebt in Frankfurt.", ch: 1, high: true, level: 2, category: "prepositions", memory: "in=inside", gender: "-" }, | |
| { de: "sein", en: "to be", ex: "Ich bin Gregor.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "sein=to be (irregular)", gender: "-" }, | |
| { de: "bin", en: "am (1st person of sein)", ex: "Ich bin Gregor.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "I bin=I am", gender: "-" }, | |
| { de: "ist", en: "is (3rd person of sein)", ex: "Das ist Julia.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "is/ist sounds similar", gender: "-" }, | |
| { de: "bist", en: "are (you inf.)", ex: "Du bist Anna.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "bist=bijection with are", gender: "-" }, | |
| { de: "Deutschland", en: "Germany", ex: "", ch: 1, high: true, level: 2, category: "countries", memory: "Deutschland=Deutsch+land", gender: "n" }, | |
| { de: "Deutsch", en: "German (language)", ex: "", ch: 1, high: true, level: 2, category: "languages", memory: "Deutsch=language name", gender: "-" }, | |
| { de: "Österreich", en: "Austria", ex: "", ch: 1, high: true, level: 2, category: "countries", memory: "Öster=eastern, Reich=realm", gender: "n" }, | |
| { de: "die Schweiz", en: "Switzerland", ex: "", ch: 1, high: true, level: 2, category: "countries", memory: "Schweiz sounds like Swiss", gender: "f" }, | |
| { de: "Frankreich", en: "France", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Frank's Reich (kingdom)", gender: "n" }, | |
| { de: "Spanien", en: "Spain", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Span-ien = Spain", gender: "n" }, | |
| { de: "Italien", en: "Italy", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Itali-en = Italy", gender: "n" }, | |
| { de: "Großbritannien", en: "Great Britain", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Groß=Great, Britan-nien", gender: "n" }, | |
| { de: "Russland", en: "Russia", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Russ=Russian/Rus", gender: "n" }, | |
| { de: "Japan", en: "Japan", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Japan=Japan", gender: "n" }, | |
| { de: "China", en: "China", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "China=China", gender: "n" }, | |
| { de: "Polen", en: "Poland", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Pol-en = Poland", gender: "n" }, | |
| { de: "die Türkei", en: "Turkey", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Türk=Turkish/Turk", gender: "f" }, | |
| { de: "Schweden", en: "Sweden", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Swede-n = Sweden", gender: "n" }, | |
| { de: "die Ukraine", en: "Ukraine", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "Ukraine=Ukraine", gender: "f" }, | |
| { de: "die USA", en: "USA", ex: "", ch: 1, high: false, level: 2, category: "countries", memory: "USA=USA (plural)", gender: "f" }, | |
| { de: "Englisch", en: "English", ex: "", ch: 1, high: true, level: 2, category: "languages", memory: "English=Englisch", gender: "-" }, | |
| { de: "Französisch", en: "French", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Frankreich → Französisch", gender: "-" }, | |
| { de: "Spanisch", en: "Spanish", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Spanien → Spanisch", gender: "-" }, | |
| { de: "Italienisch", en: "Italian", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Italien → Italienisch", gender: "-" }, | |
| { de: "Russisch", en: "Russian", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Russland → Russisch", gender: "-" }, | |
| { de: "Japanisch", en: "Japanese", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Japan → Japanisch", gender: "-" }, | |
| { de: "Chinesisch", en: "Chinese", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "China → Chinesisch", gender: "-" }, | |
| { de: "Türkisch", en: "Turkish", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Türkei → Türkisch", gender: "-" }, | |
| { de: "Polnisch", en: "Polish", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Polen → Polnisch", gender: "-" }, | |
| { de: "Arabisch", en: "Arabic", ex: "", ch: 1, high: false, level: 2, category: "languages", memory: "Arab → Arabisch", gender: "-" }, | |
| { de: "die Sprache", en: "language", ex: "Ich spreche viele Sprachen.", ch: 1, high: true, level: 2, category: "nouns-general", memory: "Sprach-e = speech", gender: "f" }, | |
| { de: "das Land", en: "land, country", ex: "", ch: 1, high: true, level: 2, category: "nouns-general", memory: "Land = land (cognate)", gender: "n" }, | |
| { de: "sprechen", en: "to speak", ex: "Er spricht Deutsch.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "sprechen=speak", gender: "-" }, | |
| { de: "lernen", en: "to learn", ex: "Ich lerne Deutsch.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "lernen=learn", gender: "-" }, | |
| { de: "verstehen", en: "to understand", ex: "Ich verstehe das nicht.", ch: 1, high: true, level: 2, category: "verbs-core", memory: "ver-stehen=grasp fully", gender: "-" }, | |
| { de: "können", en: "to be able to / can", ex: "Kannst du das buchstabieren?", ch: 1, high: true, level: 2, category: "verbs-modal", memory: "können=to can/be able", gender: "-" }, | |
| { de: "kennen", en: "to know (a person)", ex: "", ch: 1, high: true, level: 2, category: "verbs-core", memory: "kennen=know (person/thing)", gender: "-" }, | |
| { de: "er", en: "he", ex: "", ch: 1, high: true, level: 2, category: "pronouns", memory: "Er=he (male)", gender: "-" }, | |
| { de: "sie", en: "she / they", ex: "", ch: 1, high: true, level: 2, category: "pronouns", memory: "sie=she or they (context)", gender: "-" }, | |
| { de: "die Person", en: "person", ex: "", ch: 1, high: false, level: 2, category: "nouns-personal", memory: "Person=person (cognate)", gender: "f" }, | |
| { de: "die Frau", en: "woman / Mrs.", ex: "", ch: 1, high: true, level: 2, category: "nouns-titles", memory: "Frau=woman/Mrs (married)", gender: "f" }, | |
| { de: "der Herr", en: "gentleman / Mr.", ex: "", ch: 1, high: true, level: 2, category: "nouns-titles", memory: "Herr=Mr./gentleman", gender: "m" }, | |
| // ════════════════════════════════════════════════════════════ | |
| // LEVEL 3: NUMBERS, SPELLING & INFORMATION | |
| // ════════════════════════════════════════════════════════════ | |
| { de: "die Zahl", en: "number", ex: "", ch: 1, high: true, level: 3, category: "nouns-general", memory: "Zahl=count, number", gender: "f" }, | |
| { de: "der Buchstabe", en: "letter (of alphabet)", ex: "", ch: 1, high: false, level: 3, category: "nouns-general", memory: "Buchstabe=book-letter", gender: "m" }, | |
| { de: "das Alphabet", en: "alphabet", ex: "", ch: 1, high: false, level: 3, category: "nouns-general", memory: "Alphabet=alphabet (cognate)", gender: "n" }, | |
| { de: "buchstabieren", en: "to spell", ex: "Kannst du das buchstabieren?", ch: 1, high: false, level: 3, category: "verbs-action", memory: "spell letter-by-letter", gender: "-" }, | |
| { de: "schreiben", en: "to write", ex: "", ch: 1, high: true, level: 3, category: "verbs-action", memory: "schreiben=write (with sh-)", gender: "-" }, | |
| { de: "fragen", en: "to ask", ex: "", ch: 1, high: true, level: 3, category: "verbs-action", memory: "fragen=ask (question)", gender: "-" }, | |
| { de: "hören", en: "to hear / to listen", ex: "", ch: 1, high: true, level: 3, category: "verbs-action", memory: "hören=hear/listen", gender: "-" }, | |
| { de: "die Telefonnummer", en: "phone number", ex: "", ch: 1, high: true, level: 3, category: "nouns-info", memory: "Telefon=telephone + Nummer", gender: "f" }, | |
| { de: "die Handynummer", en: "cell phone number", ex: "", ch: 1, high: false, level: 3, category: "nouns-info", memory: "Handy=mobile phone", gender: "f" }, | |
| { de: "die E-Mail-Adresse", en: "e-mail address", ex: "", ch: 1, high: false, level: 3, category: "nouns-info", memory: "E-Mail=electronic mail", gender: "f" }, | |
| { de: "die Webseite", en: "website", ex: "", ch: 1, high: false, level: 3, category: "nouns-info", memory: "Web-Seite=web page", gender: "f" }, | |
| { de: "die Postleitzahl", en: "zip code", ex: "", ch: 1, high: false, level: 3, category: "nouns-info", memory: "Postleitz-ahl=postal code number", gender: "f" }, | |
| { de: "die Hausnummer", en: "house number", ex: "", ch: 1, high: false, level: 3, category: "nouns-info", memory: "Haus-Nummer=house number", gender: "f" }, | |
| { de: "notieren", en: "to note, to jot down", ex: "", ch: 1, high: false, level: 3, category: "verbs-action", memory: "notieren=note/write down", gender: "-" }, | |
| { de: "wer?", en: "who?", ex: "", ch: 1, high: true, level: 3, category: "question-words", memory: "wer=who? (W-questions)", gender: "-" }, | |
| { de: "wie?", en: "how?", ex: "", ch: 1, high: true, level: 3, category: "question-words", memory: "wie=how?/what way", gender: "-" }, | |
| { de: "wo?", en: "where?", ex: "", ch: 1, high: true, level: 3, category: "question-words", memory: "wo=where? (location)", gender: "-" }, | |
| { de: "woher?", en: "from where?", ex: "", ch: 1, high: true, level: 3, category: "question-words", memory: "woher=from where? (origin)", gender: "-" }, | |
| { de: "was?", en: "what?", ex: "", ch: 1, high: true, level: 3, category: "question-words", memory: "was=what? (object)", gender: "-" }, | |
| { de: "welche?", en: "which?", ex: "", ch: 1, high: true, level: 3, category: "question-words", memory: "welche=which? (selection)", gender: "-" }, | |
| { de: "laut", en: "loud(ly)", ex: "Er spricht laut.", ch: 1, high: false, level: 3, category: "descriptions", memory: "laut=loud/aloud", gender: "-" }, | |
| { de: "der Reiseführer", en: "travel guide", ex: "", ch: 1, high: false, level: 3, category: "nouns-objects", memory: "Reise-Führer=travel guide", gender: "m" }, | |
| { de: "der Film", en: "film, movie", ex: "", ch: 1, high: false, level: 3, category: "nouns-media", memory: "Film=film/movie (cognate)", gender: "m" }, | |
| { de: "wir", en: "we", ex: "", ch: 1, high: true, level: 3, category: "pronouns", memory: "wir=we (plural)", gender: "-" }, | |
| { de: "ihr", en: "you (plural)", ex: "", ch: 1, high: true, level: 3, category: "pronouns", memory: "ihr=you all (informal)", gender: "-" }, | |
| // ════════════════════════════════════════════════════════════ | |
| // LEVEL 4: HOBBIES & ACTIVITIES | |
| // ════════════════════════════════════════════════════════════ | |
| { de: "tanzen", en: "to dance", ex: "", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "tanzen=dance (trance-like)", gender: "-" }, | |
| { de: "singen", en: "to sing", ex: "", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "singen=sing", gender: "-" }, | |
| { de: "spielen", en: "to play", ex: "", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "spielen=play/game", gender: "-" }, | |
| { de: "lesen", en: "to read", ex: "Er liest viel.", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "lesen=read (light/letters)", gender: "-" }, | |
| { de: "kochen", en: "to cook", ex: "", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "kochen=cook/boil", gender: "-" }, | |
| { de: "reisen", en: "to travel", ex: "", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "reisen=travel/journey", gender: "-" }, | |
| { de: "schwimmen", en: "to swim", ex: "", ch: 2, high: true, level: 4, category: "verbs-hobbies", memory: "schwimmen=swim", gender: "-" }, | |
| { de: "joggen", en: "to jog", ex: "", ch: 2, high: false, level: 4, category: "verbs-hobbies", memory: "joggen=jog (English loanword)", gender: "-" }, | |
| { de: "fotografieren", en: "to photograph", ex: "", ch: 2, high: false, level: 4, category: "verbs-hobbies", memory: "fotografieren=photograph/photo", gender: "-" }, | |
| { de: "chatten", en: "to chat", ex: "", ch: 2, high: false, level: 4, category: "verbs-hobbies", memory: "chatten=chat (English origin)", gender: "-" }, | |
| { de: "malen", en: "to draw, to paint", ex: "", ch: 2, high: false, level: 4, category: "verbs-hobbies", memory: "malen=paint/color", gender: "-" }, | |
| { de: "lieben", en: "to love", ex: "Ich liebe Bücher.", ch: 2, high: true, level: 4, category: "verbs-emotions", memory: "lieben=love (lieb-)", gender: "-" }, | |
| { de: "gern", en: "like to (gladly)", ex: "Hörst du gern Musik?", ch: 2, high: true, level: 4, category: "adverbs", memory: "gern=gladly/with pleasure", gender: "-" }, | |
| { de: "das Hobby", en: "hobby", ex: "", ch: 2, high: true, level: 4, category: "nouns-activities", memory: "Hobby=hobby (English)", gender: "n" }, | |
| { de: "das Kino", en: "movie theater", ex: "Ich gehe ins Kino.", ch: 2, high: true, level: 4, category: "nouns-places", memory: "Kino=cinema/movie house", gender: "n" }, | |
| { de: "das Buch", en: "book", ex: "", ch: 2, high: true, level: 4, category: "nouns-objects", memory: "Buch=book", gender: "n" }, | |
| { de: "die Musik", en: "music", ex: "", ch: 2, high: true, level: 4, category: "nouns-activities", memory: "Musik=music (cognate)", gender: "f" }, | |
| { de: "die Leute", en: "people", ex: "", ch: 2, high: true, level: 4, category: "nouns-people", memory: "Leute=folks/people", gender: "" }, | |
| { de: "die Stadt", en: "city", ex: "", ch: 2, high: true, level: 4, category: "nouns-places", memory: "Stadt=city/town", gender: "f" }, | |
| { de: "der Fußball", en: "soccer ball / soccer", ex: "", ch: 2, high: false, level: 4, category: "nouns-sports", memory: "Fuß-ball=foot-ball", gender: "m" }, | |
| { de: "das Foto", en: "photo", ex: "", ch: 2, high: false, level: 4, category: "nouns-objects", memory: "Foto=photo (short)", gender: "n" }, | |
| { de: "die Spaghetti", en: "spaghetti", ex: "", ch: 2, high: false, level: 4, category: "nouns-food", memory: "Spaghetti=spaghetti (Italian)", gender: "" }, | |
| { de: "das Wochenende", en: "weekend", ex: "", ch: 2, high: true, level: 4, category: "nouns-time", memory: "Wochen-ende=week end", gender: "n" }, | |
| { de: "super", en: "super, awesome", ex: "", ch: 2, high: false, level: 4, category: "descriptions", memory: "Super=super/great", gender: "-" }, | |
| { de: "lustig", en: "funny", ex: "", ch: 2, high: false, level: 4, category: "descriptions", memory: "Lust=joy/fun > lustig", gender: "-" }, | |
| { de: "beliebt", en: "popular", ex: "", ch: 2, high: false, level: 4, category: "descriptions", memory: "beliebt=beloved/popular", gender: "-" }, | |
| { de: "gehen", en: "to go", ex: "Ich gehe ins Kino.", ch: 2, high: true, level: 4, category: "verbs-movement", memory: "gehen=to go/walk", gender: "-" }, | |
| { de: "ja", en: "yes", ex: "Ja, sehr gern.", ch: 2, high: true, level: 4, category: "responses", memory: "Ja=Yes (affirmative)", gender: "-" }, | |
| { de: "nein", en: "no", ex: "", ch: 2, high: true, level: 4, category: "responses", memory: "Nein=No (negative)", gender: "-" }, | |
| { de: "aber", en: "but", ex: "", ch: 2, high: true, level: 4, category: "connectors", memory: "Aber=but (contrast)", gender: "-" }, | |
| { de: "oder", en: "or", ex: "", ch: 2, high: true, level: 4, category: "connectors", memory: "Oder=or (choice)", gender: "-" }, | |
| { de: "mit", en: "with", ex: "", ch: 2, high: true, level: 4, category: "prepositions", memory: "Mit=with (company)", gender: "-" }, | |
| { de: "von", en: "of / from", ex: "Das Hobby von Ben.", ch: 2, high: true, level: 4, category: "prepositions", memory: "Von=of/from (origin)", gender: "-" }, | |
| { de: "nach", en: "to (direction)", ex: "Ich reise nach Paris.", ch: 2, high: true, level: 4, category: "prepositions", memory: "Nach=to (destination)", gender: "-" }, | |
| // ════════════════════════════════════════════════════════════ | |
| // LEVEL 5: DAYS, MONTHS & PLANNING | |
| // ════════════════════════════════════════════════════════════ | |
| { de: "der Montag", en: "Monday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Montag=Moon-day (Monday)", gender: "m" }, | |
| { de: "der Dienstag", en: "Tuesday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Dienstag=service day", gender: "m" }, | |
| { de: "der Mittwoch", en: "Wednesday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Mitte=middle of week", gender: "m" }, | |
| { de: "der Donnerstag", en: "Thursday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Donner=thunder (Thor's day)", gender: "m" }, | |
| { de: "der Freitag", en: "Friday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Frei=free (day off idea)", gender: "m" }, | |
| { de: "der Samstag", en: "Saturday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Samstag (Jewish Sabbath origin)", gender: "m" }, | |
| { de: "der Sonntag", en: "Sunday", ex: "", ch: 2, high: true, level: 5, category: "days", memory: "Sonne=sun (Sunday)", gender: "m" }, | |
| { de: "der Januar", en: "January", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Januar=January (Janus)", gender: "m" }, | |
| { de: "der Februar", en: "February", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Februar=February", gender: "m" }, | |
| { de: "der März", en: "March", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "März=March (Mars), ä=ae", gender: "m" }, | |
| { de: "der April", en: "April", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "April=April (cognate)", gender: "m" }, | |
| { de: "der Mai", en: "May", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Mai=May (cognate)", gender: "m" }, | |
| { de: "der Juni", en: "June", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Juni=June (Juno)", gender: "m" }, | |
| { de: "der Juli", en: "July", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Juli=July (Julius)", gender: "m" }, | |
| { de: "der August", en: "August", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "August=August (Caesar)", gender: "m" }, | |
| { de: "der September", en: "September", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "September=7th month (Latin)", gender: "m" }, | |
| { de: "der Oktober", en: "October", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Oktober=8th month (Latin)", gender: "m" }, | |
| { de: "der November", en: "November", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "November=9th month (Latin)", gender: "m" }, | |
| { de: "der Dezember", en: "December", ex: "", ch: 2, high: true, level: 5, category: "months", memory: "Dezember=10th month (Latin)", gender: "m" }, | |
| { de: "der Frühling", en: "spring", ex: "", ch: 2, high: true, level: 5, category: "seasons", memory: "Früh=early > spring (early growth)", gender: "m" }, | |
| { de: "der Sommer", en: "summer", ex: "", ch: 2, high: true, level: 5, category: "seasons", memory: "Sommer=summer (warm)", gender: "m" }, | |
| { de: "der Herbst", en: "fall / autumn", ex: "", ch: 2, high: true, level: 5, category: "seasons", memory: "Herbst=harvest season", gender: "m" }, | |
| { de: "der Winter", en: "winter", ex: "", ch: 2, high: true, level: 5, category: "seasons", memory: "Winter=winter (cold)", gender: "m" }, | |
| { de: "die Jahreszeit", en: "season", ex: "", ch: 2, high: false, level: 5, category: "nouns-time", memory: "Jahr=year, Zeit=time", gender: "f" }, | |
| { de: "wann?", en: "when?", ex: "", ch: 2, high: true, level: 5, category: "question-words", memory: "wann=when? (time question)", gender: "-" }, | |
| { de: "oft", en: "often", ex: "Ich arbeite oft am Samstag.", ch: 2, high: true, level: 5, category: "adverbs", memory: "Oft=often/frequently", gender: "-" }, | |
| { de: "leider", en: "unfortunately", ex: "Am Mittwoch geht es leider nicht.", ch: 2, high: true, level: 5, category: "adverbs", memory: "Leider=sadly/unfortunately", gender: "-" }, | |
| { de: "bis", en: "to / until", ex: "von Montag bis Freitag", ch: 2, high: true, level: 5, category: "prepositions", memory: "Bis=until/to (endpoint)", gender: "-" }, | |
| { de: "hier", en: "here", ex: "", ch: 2, high: true, level: 5, category: "adverbs", memory: "Hier=here (location)", gender: "-" }, | |
| { de: "nachts", en: "at night", ex: "", ch: 2, high: false, level: 5, category: "adverbs-time", memory: "Nach+ts=during the night", gender: "-" }, | |
| { de: "der Monat", en: "month", ex: "", ch: 2, high: true, level: 5, category: "nouns-time", memory: "Monat=month (measure)", gender: "m" }, | |
| { de: "die Woche", en: "week", ex: "", ch: 2, high: true, level: 5, category: "nouns-time", memory: "Woche=week", gender: "f" }, | |
| { de: "der Tag", en: "day", ex: "", ch: 2, high: true, level: 5, category: "nouns-time", memory: "Tag=day/light", gender: "m" }, | |
| { de: "das Jahr", en: "year", ex: "", ch: 2, high: true, level: 5, category: "nouns-time", memory: "Jahr=year", gender: "n" }, | |
| { de: "da", en: "there", ex: "Da ist eine Katze.", ch: 2, high: false, level: 5, category: "adverbs", memory: "Da=there (location)", gender: "-" }, | |
| // ════════════════════════════════════════════════════════════ | |
| // LEVEL 6: PROFESSIONS & WORK | |
| // ════════════════════════════════════════════════════════════ | |
| { de: "der Arzt", en: "physician (m)", ex: "Der Arzt arbeitet in der Klinik.", ch: 2, high: true, level: 6, category: "professions", memory: "Arzt=doctor (medical)", gender: "m" }, | |
| { de: "die Ärztin", en: "physician (f)", ex: "", ch: 2, high: true, level: 6, category: "professions", memory: "Ärztin=female doctor (ä sound)", gender: "f" }, | |
| { de: "der Lehrer", en: "teacher (m)", ex: "", ch: 2, high: true, level: 6, category: "professions", memory: "Lehrer=teacher (lehren=teach)", gender: "m" }, | |
| { de: "die Lehrerin", en: "teacher (f)", ex: "", ch: 2, high: true, level: 6, category: "professions", memory: "Lehrerin=female teacher", gender: "f" }, | |
| { de: "der Student", en: "student (m)", ex: "", ch: 2, high: true, level: 6, category: "professions", memory: "Student=student (studying)", gender: "m" }, | |
| { de: "die Studentin", en: "student (f)", ex: "", ch: 2, high: true, level: 6, category: "professions", memory: "Studentin=female student", gender: "f" }, | |
| { de: "der Architekt", en: "architect", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Architekt=architect (design)", gender: "m" }, | |
| { de: "der Ingenieur", en: "engineer", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Ingenieur=engineer (ingeniería)", gender: "m" }, | |
| { de: "der Techniker", en: "technician", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Techniker=technician (tech)", gender: "m" }, | |
| { de: "der Taxifahrer", en: "taxi driver", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Taxi-Fahrer=taxi driver", gender: "m" }, | |
| { de: "der Boxer", en: "boxer", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Boxer=boxer (sport)", gender: "m" }, | |
| { de: "der DJ", en: "DJ", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "DJ=disc jockey (English)", gender: "m" }, | |
| { de: "die Journalistin", en: "journalist (f)", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Journalistin=female journalist", gender: "f" }, | |
| { de: "die Professorin", en: "professor (f)", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Professorin=female prof", gender: "f" }, | |
| { de: "die Fotografin", en: "photographer (f)", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Fotografin=female photographer", gender: "f" }, | |
| { de: "der Programmierer", en: "programmer", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Programmierer=coder/programmer", gender: "m" }, | |
| { de: "der Elektriker", en: "electrician", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Elektriker=electrical worker", gender: "m" }, | |
| { de: "der Hausmeister", en: "caretaker, janitor", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Haus-Meister=house master", gender: "m" }, | |
| { de: "die Juristin", en: "attorney (f)", ex: "", ch: 2, high: false, level: 6, category: "professions", memory: "Juristin=female lawyer", gender: "f" }, | |
| { de: "arbeiten", en: "to work", ex: "Ich bin Techniker. Ich arbeite bei BMW.", ch: 2, high: true, level: 6, category: "verbs-work", memory: "arbeiten=to work/labor", gender: "-" }, | |
| { de: "studieren", en: "to study", ex: "Sie studiert Medizin.", ch: 2, high: true, level: 6, category: "verbs-work", memory: "studieren=to study", gender: "-" }, | |
| { de: "machen", en: "to do, to make", ex: "Was machst du?", ch: 2, high: true, level: 6, category: "verbs-action", memory: "machen=make/do", gender: "-" }, | |
| { de: "haben", en: "to have", ex: "Wir haben genug Platz.", ch: 2, high: true, level: 6, category: "verbs-core", memory: "haben=to have", gender: "-" }, | |
| { de: "fahren", en: "to drive", ex: "Er fährt Auto.", ch: 2, high: true, level: 6, category: "verbs-movement", memory: "fahren=to drive/travel", gender: "-" }, | |
| { de: "produzieren", en: "to produce", ex: "", ch: 2, high: false, level: 6, category: "verbs-work", memory: "produzieren=to produce", gender: "-" }, | |
| { de: "antworten", en: "to answer", ex: "", ch: 2, high: false, level: 6, category: "verbs-communication", memory: "antworten=to answer", gender: "-" }, | |
| { de: "berichten", en: "to report", ex: "", ch: 2, high: false, level: 6, category: "verbs-communication", memory: "berichten=to report", gender: "-" }, | |
| { de: "der Beruf", en: "occupation, profession", ex: "Was sind Sie von Beruf?", ch: 2, high: true, level: 6, category: "nouns-work", memory: "Beruf=career/job", gender: "m" }, | |
| { de: "die Firma", en: "company, firm", ex: "Ich arbeite bei einer großen Firma.", ch: 2, high: true, level: 6, category: "nouns-work", memory: "Firma=company/firm", gender: "f" }, | |
| { de: "die Uni", en: "university", ex: "Sie studiert an der Uni.", ch: 2, high: true, level: 6, category: "nouns-education", memory: "Uni=university (short)", gender: "f" }, | |
| { de: "die Klinik", en: "clinic", ex: "Der Arzt arbeitet in der Klinik.", ch: 2, high: false, level: 6, category: "nouns-places", memory: "Klinik=clinic/hospital", gender: "f" }, | |
| { de: "der Patient", en: "patient", ex: "", ch: 2, high: false, level: 6, category: "nouns-people", memory: "Patient=patient (medical)", gender: "m" }, | |
| { de: "der Mensch", en: "person, human", ex: "", ch: 2, high: true, level: 6, category: "nouns-people", memory: "Mensch=person/human", gender: "m" }, | |
| { de: "das Auto", en: "car", ex: "Er fährt Auto.", ch: 2, high: true, level: 6, category: "nouns-transport", memory: "Auto=automobile/car", gender: "n" }, | |
| { de: "die Stunde", en: "hour", ex: "Ich arbeite acht Stunden.", ch: 2, high: true, level: 6, category: "nouns-time", memory: "Stunde=hour (statt=standing)", gender: "f" }, | |
| { de: "bei", en: "at (a company)", ex: "Ich bin Techniker bei BMW.", ch: 2, high: true, level: 6, category: "prepositions", memory: "bei=at/near (location)", gender: "-" }, | |
| { de: "für", en: "for", ex: "", ch: 2, high: true, level: 6, category: "prepositions", memory: "für=for (purpose)", gender: "-" }, | |
| { de: "pro", en: "per", ex: "pro Jahr = per year", ch: 2, high: false, level: 6, category: "prepositions", memory: "Pro=per/for each", gender: "-" }, | |
| { de: "von", en: "of / from", ex: "Der Chef von der Firma.", ch: 2, high: true, level: 6, category: "prepositions", memory: "von=from/of (possession)", gender: "-" }, | |
| { de: "viel", en: "a lot / much", ex: "Ich lese viel.", ch: 2, high: true, level: 6, category: "descriptions", memory: "viel=much/lots (vowel i)", gender: "-" }, | |
| { de: "der Arbeitsplatz", en: "workplace", ex: "Mein Arbeitsplatz ist schön.", ch: 2, high: false, level: 6, category: "nouns-work", memory: "Arbeits-Platz=work place", gender: "m" }, | |
| { de: "die Arbeitszeit", en: "working hours", ex: "Meine Arbeitszeit ist 9-17 Uhr.", ch: 2, high: false, level: 6, category: "nouns-work", memory: "Arbeits-Zeit=work time", gender: "f" }, | |
| { de: "der Kurs", en: "course", ex: "Ich belege einen Deutschkurs.", ch: 2, high: false, level: 6, category: "nouns-education", memory: "Kurs=course (class)", gender: "m" }, | |
| { de: "der Kalender", en: "calendar", ex: "Der Termin ist im Kalender.", ch: 2, high: false, level: 6, category: "nouns-objects", memory: "Kalender=calendar", gender: "m" }, | |
| { de: "das Wörterbuch", en: "dictionary", ex: "Ich nutze ein Wörterbuch.", ch: 2, high: false, level: 6, category: "nouns-objects", memory: "Wörter-Buch=word book", gender: "n" }, | |
| { de: "das Plakat", en: "poster, placard", ex: "Das Plakat ist schön.", ch: 2, high: false, level: 6, category: "nouns-objects", memory: "Plakat=poster/announcement", gender: "n" }, | |
| { de: "die Schule", en: "school", ex: "Ich gehe zur Schule.", ch: 2, high: true, level: 6, category: "nouns-education", memory: "Schule=school", gender: "f" }, | |
| { de: "der Freund", en: "friend (m)", ex: "Mein Freund heißt Max.", ch: 2, high: true, level: 6, category: "nouns-people", memory: "Freund=friend (male)", gender: "m" }, | |
| { de: "die Freundin", en: "friend / girlfriend (f)", ex: "Meine Freundin heißt Anna.", ch: 2, high: true, level: 6, category: "nouns-people", memory: "Freundin=friend (female)", gender: "f" }, | |
| { de: "der Kollege", en: "colleague (m)", ex: "Mein Kollege ist sehr freundlich.", ch: 2, high: true, level: 6, category: "nouns-people", memory: "Kollege=colleague/coworker", gender: "m" }, | |
| { de: "die Kollegin", en: "colleague (f)", ex: "Meine Kollegin arbeitet hier.", ch: 2, high: true, level: 6, category: "nouns-people", memory: "Kollegin=female colleague", gender: "f" }, | |
| { de: "die Adresse", en: "address", ex: "Meine Adresse ist...", ch: 2, high: true, level: 6, category: "nouns-info", memory: "Adresse=address (cognate)", gender: "f" }, | |
| { de: "der Vorname", en: "first name", ex: "Mein Vorname ist Peter.", ch: 2, high: true, level: 6, category: "nouns-personal", memory: "Vor-Name=first name", gender: "m" }, | |
| { de: "der Nachname", en: "last name", ex: "Mein Nachname ist Schmidt.", ch: 2, high: true, level: 6, category: "nouns-personal", memory: "Nach-Name=last name", gender: "m" }, | |
| { de: "das Geburtsdatum", en: "date of birth", ex: "Mein Geburtsdatum ist 1990.", ch: 2, high: false, level: 6, category: "nouns-personal", memory: "Geburts-Datum=birth date", gender: "n" }, | |
| { de: "der Geburtsort", en: "birthplace", ex: "Mein Geburtsort ist Berlin.", ch: 2, high: false, level: 6, category: "nouns-personal", memory: "Geburts-Ort=birth place", gender: "m" }, | |
| { de: "der Wohnort", en: "hometown / place of residence", ex: "Mein Wohnort ist München.", ch: 2, high: false, level: 6, category: "nouns-personal", memory: "Wohn-Ort=living place", gender: "m" }, | |
| { de: "das Formular", en: "form", ex: "Füllen Sie das Formular aus.", ch: 2, high: false, level: 6, category: "nouns-objects", memory: "Formular=form/questionnaire", gender: "n" }, | |
| { de: "das Profil", en: "profile", ex: "Sein Profil ist öffentlich.", ch: 2, high: false, level: 6, category: "nouns-objects", memory: "Profil=profile (cognate)", gender: "n" }, | |
| { de: "willkommen", en: "welcome", ex: "Willkommen in Deutschland!", ch: 2, high: false, level: 6, category: "greetings", memory: "Willkommen=you are welcome", gender: "-" }, | |
| { de: "männlich", en: "male, masculine", ex: "Ist er männlich oder weiblich?", ch: 2, high: false, level: 6, category: "descriptions", memory: "Mann=man > männlich", gender: "-" }, | |
| { de: "weiblich", en: "female, feminine", ex: "Sie ist weiblich.", ch: 2, high: false, level: 6, category: "descriptions", memory: "Weib=woman > weiblich", gender: "-" }, | |
| { de: "online", en: "online", ex: "Ich bin online.", ch: 2, high: false, level: 6, category: "descriptions", memory: "online=connected (English)", gender: "-" }, | |
| { de: "der Kilometer", en: "kilometer", ex: "Es sind 100 Kilometer.", ch: 2, high: false, level: 6, category: "nouns-measurement", memory: "Kilometer=kilometer (metric)", gender: "m" }, | |
| { de: "der Platz", en: "space, place", ex: "Wir haben viel Platz.", ch: 2, high: false, level: 6, category: "nouns-objects", memory: "Platz=space/place/square", gender: "m" }, | |
| // Additional items to reach comprehensive coverage | |
| { de: "der Anzug", en: "suit", ex: "", ch: 1, high: false, level: 2, category: "clothing", memory: "Anzug=outfit/suit", gender: "m" }, | |
| { de: "die Autobahn", en: "highway", ex: "", ch: 1, high: true, level: 1, category: "nouns-transport", memory: "Auto-Bahn=car highway", gender: "f" }, | |
| { de: "das Frühstück", en: "breakfast", ex: "", ch: 1, high: true, level: 1, category: "food", memory: "Früh=early, Stück=piece", gender: "n" }, | |
| { de: "der Rucksack", en: "backpack", ex: "", ch: 1, high: false, level: 3, category: "nouns-objects", memory: "Rück-Sack=back sack", gender: "m" }, | |
| { de: "das Würstchen", en: "sausage", ex: "", ch: 1, high: false, level: 1, category: "food", memory: "Würst(chen)=sausage", gender: "n" }, | |
| { de: "das Butterbrot", en: "sandwich", ex: "", ch: 1, high: false, level: 1, category: "food", memory: "Butter-Brot=butter bread", gender: "n" }, | |
| { de: "der Walzer", en: "waltz", ex: "", ch: 1, high: false, level: 4, category: "dance", memory: "Walzer=waltz (dance)", gender: "m" }, | |
| { de: "das Restaurant", en: "restaurant", ex: "", ch: 2, high: true, level: 4, category: "nouns-places", memory: "Restaurant=restaurant (cognate)", gender: "n" }, | |
| { de: "das Café", en: "café", ex: "", ch: 2, high: false, level: 4, category: "nouns-places", memory: "Café=café (French loanword)", gender: "n" }, | |
| { de: "das Museum", en: "museum", ex: "", ch: 2, high: false, level: 4, category: "nouns-places", memory: "Museum=museum (cognate)", gender: "n" }, | |
| { de: "das Schwimmbad", en: "swimming pool", ex: "", ch: 2, high: false, level: 4, category: "nouns-places", memory: "Schwimm-Bad=swim bath", gender: "n" }, | |
| { de: "das Theater", en: "theater", ex: "", ch: 2, high: false, level: 4, category: "nouns-places", memory: "Theater=theater (cognate)", gender: "n" }, | |
| { de: "das Fußballstadion", en: "soccer stadium", ex: "", ch: 2, high: false, level: 4, category: "nouns-places", memory: "Fußball-Stadion=soccer arena", gender: "n" }, | |
| { de: "die Lieblingsmusik", en: "favorite music", ex: "", ch: 2, high: false, level: 6, category: "nouns-entertainment", memory: "Lieblings=favorite, Musik=music", gender: "f" }, | |
| { de: "der Lieblingsfilm", en: "favorite film", ex: "", ch: 2, high: false, level: 6, category: "nouns-entertainment", memory: "Lieblings=favorite, Film=movie", gender: "m" }, | |
| ]; | |
| // Fill-in-blank sentences | |
| const fibData = [ | |
| { sentence: "Wie ___ du? Ich heiße Anna.", blank: "heißt", hint: "3rd person of heißen", level: 2 }, | |
| { sentence: "Woher ___ du? Ich komme aus Deutschland.", blank: "kommst", hint: "2nd person of kommen", level: 2 }, | |
| { sentence: "Ich ___ in Berlin.", blank: "wohne", hint: "1st person of wohnen", level: 2 }, | |
| { sentence: "___ du Deutsch? Ja, ich spreche Deutsch.", blank: "Sprichst", hint: "2nd person of sprechen", level: 2 }, | |
| { sentence: "Guten ___! — Guten Morgen!", blank: "Morgen", hint: "morning greeting", level: 1 }, | |
| { sentence: "Auf ___! (Goodbye)", blank: "Wiedersehen", hint: "formal goodbye", level: 1 }, | |
| { sentence: "Wie geht ___? — Danke, gut.", blank: "es", hint: "pronoun 'it'", level: 1 }, | |
| { sentence: "Ich ___ gern Musik.", blank: "höre", hint: "1st person of hören", level: 4 }, | |
| { sentence: "Was machst du am ___? — Samstag. (Saturday)", blank: "Samstag", hint: "day of the week", level: 5 }, | |
| { sentence: "Er ___ bei BMW.", blank: "arbeitet", hint: "3rd person of arbeiten", level: 6 }, | |
| { sentence: "Sie ___ Medizin an der Uni.", blank: "studiert", hint: "3rd person of studieren", level: 6 }, | |
| { sentence: "Ich ___ Bücher sehr gern.", blank: "lese", hint: "1st person of lesen (irreg.)", level: 4 }, | |
| { sentence: "Es gibt 12 ___ im Jahr.", blank: "Monate", hint: "plural of Monat", level: 5 }, | |
| { sentence: "Der ___ ist nach dem Sommer. (autumn)", blank: "Herbst", hint: "season", level: 5 }, | |
| { sentence: "Sie ist ___ von Beruf. (doctor, f)", blank: "Ärztin", hint: "female physician", level: 6 }, | |
| { sentence: "Ich ___ nicht gut Englisch.", blank: "spreche", hint: "1st person of sprechen", level: 2 }, | |
| { sentence: "___ Sie Ihren Namen bitte! (Spell)", blank: "Buchstabieren", hint: "imperative of buchstabieren", level: 3 }, | |
| { sentence: "Wir haben am ___ frei. (Sunday)", blank: "Sonntag", hint: "last day of the week", level: 5 }, | |
| { sentence: "Ich fahre gern ___. (car)", blank: "Auto", hint: "das ___", level: 6 }, | |
| { sentence: "Der ___ kommt nach dem Winter. (spring)", blank: "Frühling", hint: "season", level: 5 }, | |
| ]; | |
| // ═══════════════════════════════════════════════════════════ | |
| // STATE | |
| // ═══════════════════════════════════════════════════════════ | |
| let score = 0, streak = 0; | |
| // Level state | |
| let unlockedLevels = [1]; | |
| let currentLevel = 1; | |
| let levelProgress = { | |
| 1: { completed: false, score: 0 }, | |
| 2: { completed: false, score: 0 }, | |
| 3: { completed: false, score: 0 }, | |
| 4: { completed: false, score: 0 }, | |
| 5: { completed: false, score: 0 }, | |
| 6: { completed: false, score: 0 } | |
| }; | |
| // Load progress from localStorage | |
| function loadProgress() { | |
| const saved = localStorage.getItem('netzwerkProgress'); | |
| if (saved) { | |
| const data = JSON.parse(saved); | |
| unlockedLevels = data.unlockedLevels || [1]; | |
| currentLevel = data.currentLevel || 1; | |
| levelProgress = data.levelProgress || levelProgress; | |
| score = data.score || 0; | |
| streak = data.streak || 0; | |
| document.getElementById('totalScore').textContent = score; | |
| document.getElementById('streakCount').textContent = streak; | |
| } | |
| } | |
| function saveProgress() { | |
| localStorage.setItem('netzwerkProgress', JSON.stringify({ | |
| unlockedLevels, currentLevel, levelProgress, score, streak | |
| })); | |
| } | |
| function selectLevel(levelNum) { | |
| if (!unlockedLevels.includes(levelNum)) return; | |
| currentLevel = levelNum; | |
| updateLevelButtons(); | |
| initCards(); | |
| initQuiz(); | |
| resetMatching(); | |
| initType(); | |
| initFib(); | |
| saveProgress(); | |
| } | |
| function updateLevelButtons() { | |
| for (let i = 1; i <= 6; i++) { | |
| const btn = document.getElementById('levelBtn' + i); | |
| if (!btn) continue; | |
| btn.classList.remove('active', 'locked'); | |
| if (unlockedLevels.includes(i)) { | |
| if (i === currentLevel) { | |
| btn.classList.add('active'); | |
| } | |
| btn.textContent = (i === 1 ? '🎯' : '⭐') + ' Level ' + i; | |
| } else { | |
| btn.classList.add('locked'); | |
| btn.textContent = '🔒 Level ' + i; | |
| } | |
| } | |
| } | |
| function unlockLevel(levelNum) { | |
| if (!unlockedLevels.includes(levelNum)) { | |
| unlockedLevels.push(levelNum); | |
| updateLevelButtons(); | |
| saveProgress(); | |
| } | |
| } | |
| // Flashcard state | |
| let cardFilter = 'all'; | |
| let filteredCards = [], cardIdx = 0, cardFlipped = false; | |
| // Quiz state | |
| let quizPool = [], quizIdx = 0, quizCorrect = 0, quizAnswered = false; | |
| // Mistaken words tracking (for Practice mode) | |
| let mistakenWords = []; | |
| // Practice state | |
| let practicePool = [], practiceIdx = 0, practiceCorrect = 0, practiceMistakeCount = 0, practiceAttempted = 0; | |
| let practiceAnswered = false, practiceTimerInterval = null, practiceStartTime = null; | |
| let practiceNewMistakes = []; | |
| let practiceRound = 1; | |
| // Hardcore state | |
| let hardcorePool = [], hardcoreIdx = 0, hardcoreCorrect = 0, hardcoreMistakeCount = 0, hardcoreAttempted = 0; | |
| let hardcoreAnswered = false, hardcoreTimerInterval = null, hardcoreStartTime = null; | |
| let hardcoreWrongWords = []; | |
| // All Hardcore state (ALL levels) | |
| let ahPool = [], ahIdx = 0, ahCorrect = 0, ahMistakeCount = 0, ahAttempted = 0; | |
| let ahAnswered = false, ahTimerInterval = null, ahStartTime = null; | |
| let ahWrongWords = []; | |
| // Match state | |
| let matchLeft = [], matchRight = [], matchSelectedLeft = null, matchSelectedRight = null, matchedCount = 0; | |
| // Type state | |
| let typePool = [], typeIdx = 0; | |
| // Fib state | |
| let fibPool = [], fibIdx = 0; | |
| // Vocab browser state | |
| let vocabFilterVal = 'all'; | |
| // ═══════════════════════════════════════════════════════════ | |
| // UTILITY | |
| // ═══════════════════════════════════════════════════════════ | |
| function shuffle(arr) { | |
| const a = [...arr]; | |
| for (let i = a.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [a[i], a[j]] = [a[j], a[i]]; | |
| } | |
| return a; | |
| } | |
| function addScore(pts) { | |
| score += pts; | |
| streak++; | |
| document.getElementById('totalScore').textContent = score; | |
| document.getElementById('streakCount').textContent = streak; | |
| if (streak % 5 === 0) spawnConfetti(); | |
| } | |
| function resetStreak() { streak = 0; document.getElementById('streakCount').textContent = 0; } | |
| function spawnConfetti() { | |
| const wrap = document.getElementById('confettiWrap'); | |
| const colors = ['#e8c547', '#7c6fff', '#ff6b6b', '#4ecdc4', '#fff']; | |
| for (let i = 0; i < 30; i++) { | |
| const el = document.createElement('div'); | |
| el.className = 'confetti-piece'; | |
| el.style.left = Math.random() * 100 + 'vw'; | |
| el.style.top = '-20px'; | |
| el.style.background = colors[Math.floor(Math.random() * colors.length)]; | |
| el.style.animationDelay = Math.random() * 0.5 + 's'; | |
| el.style.animationDuration = (1 + Math.random() * 0.6) + 's'; | |
| wrap.appendChild(el); | |
| setTimeout(() => el.remove(), 2000); | |
| } | |
| } | |
| function showSection(id) { | |
| document.querySelectorAll('.practice-section').forEach(s => s.classList.add('hidden')); | |
| document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); | |
| document.getElementById('sec-' + id).classList.remove('hidden'); | |
| event.target.classList.add('active'); | |
| if (id === 'flashcards') initCards(); | |
| if (id === 'quiz') initQuiz(); | |
| if (id === 'matching') resetMatching(); | |
| if (id === 'typeit') initType(); | |
| if (id === 'fillin') initFib(); | |
| if (id === 'practice') initPractice(); | |
| if (id === 'hardcore') initHardcore(); | |
| if (id === 'allhardcore') initAllHardcore(); | |
| if (id === 'reference') renderVocabBrowser(); | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // FLASHCARDS | |
| // ═══════════════════════════════════════════════════════════ | |
| function getFilteredVocab() { | |
| return vocab.filter(v => { | |
| const correctLevel = v.level === currentLevel; | |
| if (cardFilter === '1') return v.ch === 1 && correctLevel; | |
| if (cardFilter === '2') return v.ch === 2 && correctLevel; | |
| if (cardFilter === 'high') return v.high && correctLevel; | |
| return correctLevel; | |
| }); | |
| } | |
| function setCardFilter(f, btn) { | |
| cardFilter = f; | |
| document.querySelectorAll('#sec-flashcards .filter-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| initCards(); | |
| } | |
| function initCards() { | |
| filteredCards = shuffle(getFilteredVocab()); | |
| cardIdx = 0; | |
| cardFlipped = false; | |
| showCard(); | |
| } | |
| function showCard() { | |
| const card = document.getElementById('mainCard'); | |
| card.classList.remove('flipped'); | |
| cardFlipped = false; | |
| const v = filteredCards[cardIdx]; | |
| document.getElementById('cardTag').textContent = 'LEVEL ' + v.level + ' • ' + (v.category || 'vocab').toUpperCase(); | |
| document.getElementById('cardWord').textContent = v.de; | |
| document.getElementById('cardExample').textContent = v.ex || ''; | |
| document.getElementById('cardTranslation').textContent = v.en; | |
| document.getElementById('cardHint').innerHTML = (v.memory ? '💡 ' + v.memory + '<br>' : '') + (v.high ? '⭐ High-Frequency' : ''); | |
| const pct = ((cardIdx + 1) / filteredCards.length * 100).toFixed(0); | |
| document.getElementById('cardProgress').style.width = pct + '%'; | |
| document.getElementById('cardCounter').textContent = `Card ${cardIdx + 1} of ${filteredCards.length}`; | |
| } | |
| function flipCard() { | |
| const card = document.getElementById('mainCard'); | |
| cardFlipped = !cardFlipped; | |
| card.classList.toggle('flipped', cardFlipped); | |
| } | |
| function nextCard() { | |
| cardIdx = (cardIdx + 1) % filteredCards.length; | |
| showCard(); | |
| } | |
| function prevCard() { | |
| cardIdx = (cardIdx - 1 + filteredCards.length) % filteredCards.length; | |
| showCard(); | |
| } | |
| function shuffleCards() { | |
| filteredCards = shuffle(filteredCards); | |
| cardIdx = 0; | |
| showCard(); | |
| } | |
| function cardKnew(knew) { | |
| if (!cardFlipped) flipCard(); | |
| setTimeout(() => { | |
| if (knew) { addScore(2); } | |
| else { resetStreak(); } | |
| nextCard(); | |
| }, 400); | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // QUIZ | |
| // ═══════════════════════════════════════════════════════════ | |
| function initQuiz() { | |
| quizPool = shuffle(vocab.filter(v => v.level === currentLevel)).slice(0, 20); | |
| quizIdx = 0; | |
| quizCorrect = 0; | |
| quizAnswered = false; | |
| document.getElementById('quizResult').classList.add('hidden'); | |
| document.getElementById('quizCard').style.display = ''; | |
| document.getElementById('quizNextBtn').style.display = 'none'; | |
| showQuiz(); | |
| } | |
| function showQuiz() { | |
| if (quizIdx >= quizPool.length) { | |
| showQuizResult(); return; | |
| } | |
| quizAnswered = false; | |
| const v = quizPool[quizIdx]; | |
| document.getElementById('quizQ').textContent = v.de; | |
| document.getElementById('quizCtx').textContent = v.ex ? `e.g. "${v.ex}"` : (v.ch === 1 ? 'Kapitel 1' : 'Kapitel 2'); | |
| // 4 options: 1 correct + 3 wrong | |
| let wrong = shuffle(vocab.filter(w => w.en !== v.en && w.level === currentLevel)).slice(0, 3).map(w => w.en); | |
| let options = shuffle([v.en, ...wrong]); | |
| const optDiv = document.getElementById('quizOpts'); | |
| optDiv.innerHTML = ''; | |
| options.forEach(opt => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'quiz-option'; | |
| btn.textContent = opt; | |
| btn.onclick = () => selectQuizOpt(btn, opt, v.en); | |
| optDiv.appendChild(btn); | |
| }); | |
| const fb = document.getElementById('quizFeedback'); | |
| fb.className = 'feedback-msg'; | |
| fb.textContent = ''; | |
| document.getElementById('quizNextBtn').style.display = 'none'; | |
| const pct = (quizIdx / quizPool.length * 100).toFixed(0); | |
| document.getElementById('quizProgress').style.width = pct + '%'; | |
| document.getElementById('quizCounter').textContent = `Question ${quizIdx + 1} of ${quizPool.length}`; | |
| } | |
| function selectQuizOpt(btn, chosen, correct) { | |
| if (quizAnswered) return; | |
| quizAnswered = true; | |
| document.querySelectorAll('.quiz-option').forEach(b => { b.disabled = true; }); | |
| const fb = document.getElementById('quizFeedback'); | |
| if (chosen === correct) { | |
| btn.classList.add('correct'); | |
| fb.textContent = '✓ Richtig! Correct!'; | |
| fb.className = 'feedback-msg show ok'; | |
| quizCorrect++; | |
| addScore(5); | |
| } else { | |
| btn.classList.add('wrong'); | |
| fb.textContent = `✗ The correct answer is: "${correct}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| resetStreak(); | |
| // Track mistaken word for Practice mode | |
| const mistakeWord = quizPool[quizIdx]; | |
| if (mistakeWord && !mistakenWords.find(w => w.de === mistakeWord.de)) { | |
| mistakenWords.push(mistakeWord); | |
| } | |
| // RE-INSERT mistaken word 3 more times (shuffled into remaining pool) | |
| if (mistakeWord) { | |
| const remaining = quizPool.slice(quizIdx + 1); | |
| for (let r = 0; r < 3; r++) { | |
| const pos = Math.floor(Math.random() * (remaining.length + 1)); | |
| remaining.splice(pos, 0, { ...mistakeWord }); | |
| } | |
| quizPool = [...quizPool.slice(0, quizIdx + 1), ...remaining]; | |
| } | |
| // highlight correct | |
| document.querySelectorAll('.quiz-option').forEach(b => { | |
| if (b.textContent === correct) b.classList.add('correct'); | |
| }); | |
| } | |
| document.getElementById('quizNextBtn').style.display = ''; | |
| // Update counter to reflect new pool size | |
| document.getElementById('quizCounter').textContent = `Question ${quizIdx + 1} of ${quizPool.length}`; | |
| quizIdx++; | |
| } | |
| function nextQuiz() { showQuiz(); } | |
| function resetQuiz() { initQuiz(); } | |
| function showQuizResult() { | |
| document.getElementById('quizCard').style.display = 'none'; | |
| document.getElementById('quizNextBtn').style.display = 'none'; | |
| const res = document.getElementById('quizResult'); | |
| res.classList.remove('hidden'); | |
| const pct = Math.round(quizCorrect / quizPool.length * 100); | |
| document.getElementById('quizScore').textContent = `${quizCorrect} / ${quizPool.length} (${pct}%)`; | |
| // Check for level unlock | |
| if (pct >= 70 && currentLevel < 6) { | |
| unlockLevel(currentLevel + 1); | |
| document.getElementById('quizMsg').textContent = '🎉 Level ' + (currentLevel + 1) + ' Unlocked! Great job!'; | |
| } else { | |
| document.getElementById('quizMsg').textContent = pct >= 80 ? 'Ausgezeichnet! Excellent work!' : pct >= 50 ? 'Gut gemacht! Keep practicing!' : 'Weiter üben! Keep studying!'; | |
| } | |
| document.getElementById('quizEmoji').textContent = pct >= 80 ? '🎉' : pct >= 70 ? '⭐' : pct >= 50 ? '💪' : '📚'; | |
| if (pct >= 70) spawnConfetti(); | |
| levelProgress[currentLevel].completed = true; | |
| levelProgress[currentLevel].score = pct; | |
| saveProgress(); | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // MATCHING | |
| // ═══════════════════════════════════════════════════════════ | |
| function resetMatching() { | |
| matchSelectedLeft = null; matchSelectedRight = null; matchedCount = 0; | |
| document.getElementById('matchDone').classList.add('hidden'); | |
| document.getElementById('matchLeft').style.display = ''; | |
| document.getElementById('matchRight').style.display = ''; | |
| const pool = shuffle(vocab.filter(v => v.level === currentLevel)).slice(0, 8); | |
| matchLeft = pool.map(v => ({ ...v, id: v.de })); | |
| matchRight = shuffle(pool.map(v => ({ ...v, id: v.de }))); | |
| renderMatchCol('matchLeft', matchLeft, 'left'); | |
| renderMatchCol('matchRight', matchRight, 'right'); | |
| document.getElementById('matchScore').textContent = 'Matched: 0/8'; | |
| } | |
| function renderMatchCol(elId, items, side) { | |
| const el = document.getElementById(elId); | |
| el.innerHTML = ''; | |
| items.forEach(item => { | |
| const div = document.createElement('div'); | |
| div.className = 'match-item'; | |
| div.textContent = side === 'left' ? item.de : item.en; | |
| div.dataset.id = item.id; | |
| div.onclick = () => selectMatch(div, side, item.id); | |
| el.appendChild(div); | |
| }); | |
| } | |
| function selectMatch(el, side, id) { | |
| if (el.classList.contains('matched')) return; | |
| if (side === 'left') { | |
| document.querySelectorAll('#matchLeft .match-item').forEach(b => b.classList.remove('selected')); | |
| el.classList.add('selected'); | |
| matchSelectedLeft = id; | |
| } else { | |
| document.querySelectorAll('#matchRight .match-item').forEach(b => b.classList.remove('selected')); | |
| el.classList.add('selected'); | |
| matchSelectedRight = id; | |
| } | |
| if (matchSelectedLeft && matchSelectedRight) { | |
| if (matchSelectedLeft === matchSelectedRight) { | |
| // correct | |
| document.querySelectorAll(`.match-item[data-id="${matchSelectedLeft}"]`).forEach(el => { | |
| el.classList.remove('selected'); | |
| el.classList.add('matched'); | |
| }); | |
| matchedCount++; | |
| addScore(3); | |
| document.getElementById('matchScore').textContent = `Matched: ${matchedCount}/8`; | |
| if (matchedCount === 8) { | |
| setTimeout(() => { | |
| document.getElementById('matchLeft').style.display = 'none'; | |
| document.getElementById('matchRight').style.display = 'none'; | |
| document.getElementById('matchDone').classList.remove('hidden'); | |
| spawnConfetti(); | |
| }, 400); | |
| } | |
| } else { | |
| // wrong flash | |
| document.querySelectorAll('.match-item.selected').forEach(el => { | |
| el.classList.add('wrong-flash'); | |
| setTimeout(() => { el.classList.remove('wrong-flash', 'selected'); }, 600); | |
| }); | |
| resetStreak(); | |
| } | |
| matchSelectedLeft = null; matchSelectedRight = null; | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // TYPE IT | |
| // ═══════════════════════════════════════════════════════════ | |
| function initType() { | |
| typePool = shuffle(vocab.filter(v => v.level === currentLevel)); | |
| typeIdx = 0; | |
| showType(); | |
| } | |
| function showType() { | |
| if (typeIdx >= typePool.length) { typeIdx = 0; typePool = shuffle(vocab.filter(v => v.level === currentLevel)); } | |
| const v = typePool[typeIdx]; | |
| document.getElementById('typeQ').textContent = v.en; | |
| document.getElementById('typeCtx').textContent = v.ex ? `Context: "${v.ex}"` : (v.ch === 1 ? 'Kapitel 1' : 'Kapitel 2'); | |
| const inp = document.getElementById('typeInput'); | |
| inp.value = ''; | |
| inp.className = 'type-input'; | |
| inp.focus(); | |
| const fb = document.getElementById('typeFeedback'); | |
| fb.className = 'feedback-msg'; | |
| fb.textContent = ''; | |
| const pct = (typeIdx / typePool.length * 100).toFixed(0); | |
| document.getElementById('typeProgress').style.width = pct + '%'; | |
| document.getElementById('typeCounter').textContent = `Word ${typeIdx + 1} of ${typePool.length}`; | |
| } | |
| function normalize(s) { return s.trim().toLowerCase().replace(/[äàá]/g, 'a').replace(/[öò]/g, 'o').replace(/[üù]/g, 'u').replace(/ß/g, 'ss'); } | |
| function checkType() { | |
| const v = typePool[typeIdx]; | |
| const inp = document.getElementById('typeInput'); | |
| const val = inp.value.trim(); | |
| const fb = document.getElementById('typeFeedback'); | |
| // Accept any part of the de field that matches | |
| const correct = normalize(v.de) === normalize(val) || v.de.toLowerCase().includes(val.toLowerCase()) && val.length > 2; | |
| // Strict: the user's answer must match the core word | |
| const coreDE = v.de.replace(/^(der|die|das|den|dem)\s+/i, '').trim(); | |
| const isOK = normalize(val) === normalize(v.de) || normalize(val) === normalize(coreDE); | |
| if (isOK) { | |
| inp.className = 'type-input correct-input'; | |
| fb.textContent = `✓ Richtig! "${v.de}" = "${v.en}"`; | |
| fb.className = 'feedback-msg show ok'; | |
| addScore(4); | |
| setTimeout(() => { typeIdx++; showType(); }, 1200); | |
| } else { | |
| inp.className = 'type-input wrong-input'; | |
| fb.textContent = `✗ Answer: "${v.de}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| resetStreak(); | |
| setTimeout(() => { typeIdx++; showType(); }, 1800); | |
| } | |
| } | |
| function skipType() { typeIdx++; showType(); } | |
| function resetType() { initType(); } | |
| // ═══════════════════════════════════════════════════════════ | |
| // FILL IN THE BLANK | |
| // ═══════════════════════════════════════════════════════════ | |
| function initFib() { | |
| fibPool = shuffle(fibData.filter(f => f.level === currentLevel)); | |
| fibIdx = 0; | |
| showFib(); | |
| } | |
| function showFib() { | |
| if (fibIdx >= fibPool.length) { fibIdx = 0; fibPool = shuffle(fibData.filter(f => f.level === currentLevel)); } | |
| const item = fibPool[fibIdx]; | |
| const sentenceHtml = item.sentence.replace('___', '<input class="blank-input" id="fibInput" autocomplete="off">'); | |
| document.getElementById('fibSentence').innerHTML = sentenceHtml; | |
| const inp = document.getElementById('fibInput'); | |
| inp.addEventListener('keydown', e => { if (e.key === 'Enter') checkFib(); }); | |
| inp.focus(); | |
| const fb = document.getElementById('fibFeedback'); | |
| fb.className = 'feedback-msg'; | |
| fb.textContent = ''; | |
| const pct = (fibIdx / fibPool.length * 100).toFixed(0); | |
| document.getElementById('fibProgress').style.width = pct + '%'; | |
| document.getElementById('fibCounter').textContent = `Sentence ${fibIdx + 1} of ${fibPool.length}`; | |
| } | |
| function checkFib() { | |
| const item = fibPool[fibIdx]; | |
| const inp = document.getElementById('fibInput'); | |
| const val = inp.value.trim(); | |
| const fb = document.getElementById('fibFeedback'); | |
| if (normalize(val) === normalize(item.blank)) { | |
| inp.className = 'blank-input correct-b'; | |
| fb.textContent = `✓ Richtig! "${item.blank}"`; | |
| fb.className = 'feedback-msg show ok'; | |
| addScore(6); | |
| setTimeout(() => { fibIdx++; showFib(); }, 1200); | |
| } else { | |
| inp.className = 'blank-input wrong-b'; | |
| fb.textContent = `✗ Answer: "${item.blank}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| resetStreak(); | |
| setTimeout(() => { fibIdx++; showFib(); }, 1800); | |
| } | |
| } | |
| function skipFib() { fibIdx++; showFib(); } | |
| function resetFib() { initFib(); } | |
| function hintFib() { | |
| const item = fibPool[fibIdx]; | |
| const fb = document.getElementById('fibFeedback'); | |
| fb.textContent = `💡 Hint: ${item.hint}`; | |
| fb.className = 'feedback-msg show'; | |
| fb.style.color = 'var(--accent)'; | |
| fb.style.background = 'rgba(232,197,71,0.08)'; | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // VOCAB BROWSER | |
| // ═══════════════════════════════════════════════════════════ | |
| function filterVocab(f, btn) { | |
| vocabFilterVal = f; | |
| document.querySelectorAll('#sec-reference .filter-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| renderVocabBrowser(); | |
| } | |
| function searchVocab() { renderVocabBrowser(); } | |
| function renderVocabBrowser() { | |
| const search = (document.getElementById('vocabSearch')?.value || '').toLowerCase(); | |
| const filtered = vocab.filter(v => { | |
| const matchSearch = !search || v.de.toLowerCase().includes(search) || v.en.toLowerCase().includes(search); | |
| const matchFilter = vocabFilterVal === 'all' ? true : vocabFilterVal === '1' ? v.ch === 1 : vocabFilterVal === '2' ? v.ch === 2 : v.high; | |
| return matchSearch && matchFilter; | |
| }); | |
| const el = document.getElementById('vocabBrowser'); | |
| if (!el) return; | |
| el.innerHTML = filtered.length === 0 ? '<div style="color:var(--muted);font-family:DM Mono,monospace;padding:20px">No results found.</div>' : | |
| filtered.map(v => ` | |
| <div class="vocab-item"> | |
| <span class="vocab-de">${v.de}</span> | |
| <span class="vocab-en">${v.en}${v.ex ? `<span style="color:var(--accent2);font-style:italic"> — ${v.ex}</span>` : ''}</span> | |
| <span class="vocab-badge ${v.high ? 'high' : v.ch === 1 ? 'kap1' : 'kap2'}">${v.high ? '⭐ High' : 'Kap ' + v.ch}</span> | |
| </div>`).join(''); | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // PRACTICE MODE (Repeat Mistakes 3×) | |
| // ═══════════════════════════════════════════════════════════ | |
| function formatTime(seconds) { | |
| const m = Math.floor(seconds / 60).toString().padStart(2, '0'); | |
| const s = (seconds % 60).toString().padStart(2, '0'); | |
| return m + ':' + s; | |
| } | |
| function startPracticeTimer() { | |
| if (practiceTimerInterval) clearInterval(practiceTimerInterval); | |
| practiceStartTime = Date.now(); | |
| practiceTimerInterval = setInterval(() => { | |
| const elapsed = Math.floor((Date.now() - practiceStartTime) / 1000); | |
| document.getElementById('practiceTimer').textContent = formatTime(elapsed); | |
| }, 1000); | |
| } | |
| function stopPracticeTimer() { | |
| if (practiceTimerInterval) { clearInterval(practiceTimerInterval); practiceTimerInterval = null; } | |
| } | |
| function initPractice() { | |
| // Check if there are mistaken words | |
| if (mistakenWords.length === 0) { | |
| document.getElementById('practiceEmpty').classList.remove('hidden'); | |
| document.getElementById('practiceActive').classList.add('hidden'); | |
| document.getElementById('practiceResult').classList.add('hidden'); | |
| return; | |
| } | |
| resetPractice(); | |
| } | |
| function resetPractice() { | |
| if (mistakenWords.length === 0) { | |
| initPractice(); | |
| return; | |
| } | |
| document.getElementById('practiceEmpty').classList.add('hidden'); | |
| document.getElementById('practiceActive').classList.remove('hidden'); | |
| document.getElementById('practiceResult').classList.add('hidden'); | |
| // Each mistaken word appears 3 times | |
| practicePool = []; | |
| for (let i = 0; i < 3; i++) { | |
| practicePool = practicePool.concat([...mistakenWords]); | |
| } | |
| practicePool = shuffle(practicePool); | |
| practiceIdx = 0; | |
| practiceCorrect = 0; | |
| practiceMistakeCount = 0; | |
| practiceAttempted = 0; | |
| practiceAnswered = false; | |
| practiceNewMistakes = []; | |
| practiceRound = 1; | |
| updatePracticeStatus(); | |
| startPracticeTimer(); | |
| showPracticeQuestion(); | |
| } | |
| function updatePracticeStatus() { | |
| document.getElementById('practiceRemaining').textContent = practicePool.length - practiceIdx; | |
| document.getElementById('practiceAttempted').textContent = practiceAttempted; | |
| document.getElementById('practiceMistakes').textContent = practiceMistakeCount; | |
| document.getElementById('practiceCorrectCount').textContent = practiceCorrect; | |
| } | |
| function showPracticeQuestion() { | |
| if (practiceIdx >= practicePool.length) { | |
| // Check if there were new mistakes this round | |
| if (practiceNewMistakes.length > 0) { | |
| // Re-queue new mistakes × 3 | |
| practicePool = []; | |
| for (let i = 0; i < 3; i++) { | |
| practicePool = practicePool.concat([...practiceNewMistakes]); | |
| } | |
| practicePool = shuffle(practicePool); | |
| practiceNewMistakes = []; | |
| practiceIdx = 0; | |
| practiceRound++; | |
| updatePracticeStatus(); | |
| showPracticeQuestion(); | |
| return; | |
| } | |
| showPracticeResult(); | |
| return; | |
| } | |
| practiceAnswered = false; | |
| const v = practicePool[practiceIdx]; | |
| document.getElementById('practiceQ').textContent = v.de; | |
| document.getElementById('practiceCtx').textContent = v.ex ? `e.g. "${v.ex}"` : (v.category || '').replace(/-/g, ' '); | |
| let wrong = shuffle(vocab.filter(w => w.en !== v.en && w.level === currentLevel)).slice(0, 3).map(w => w.en); | |
| let options = shuffle([v.en, ...wrong]); | |
| const optDiv = document.getElementById('practiceOpts'); | |
| optDiv.innerHTML = ''; | |
| options.forEach(opt => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'quiz-option'; | |
| btn.textContent = opt; | |
| btn.onclick = () => selectPracticeOpt(btn, opt, v.en, v); | |
| optDiv.appendChild(btn); | |
| }); | |
| const fb = document.getElementById('practiceFeedback'); | |
| fb.className = 'feedback-msg'; | |
| fb.textContent = ''; | |
| document.getElementById('practiceMemoryTip').className = 'memory-tip'; | |
| document.getElementById('practiceNextBtn').style.display = 'none'; | |
| const pct = (practiceIdx / practicePool.length * 100).toFixed(0); | |
| document.getElementById('practiceProgress').style.width = pct + '%'; | |
| document.getElementById('practiceCounter').textContent = `Round ${practiceRound} — Question ${practiceIdx + 1} of ${practicePool.length}`; | |
| updatePracticeStatus(); | |
| } | |
| function selectPracticeOpt(btn, chosen, correct, wordObj) { | |
| if (practiceAnswered) return; | |
| practiceAnswered = true; | |
| practiceAttempted++; | |
| document.querySelectorAll('#practiceOpts .quiz-option').forEach(b => { b.disabled = true; }); | |
| const fb = document.getElementById('practiceFeedback'); | |
| if (chosen === correct) { | |
| btn.classList.add('correct'); | |
| fb.textContent = '✓ Richtig! You remembered it!'; | |
| fb.className = 'feedback-msg show ok'; | |
| practiceCorrect++; | |
| addScore(3); | |
| } else { | |
| btn.classList.add('wrong'); | |
| fb.textContent = `✗ Correct answer: "${correct}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| practiceMistakeCount++; | |
| resetStreak(); | |
| // Add to new mistakes for next round | |
| if (!practiceNewMistakes.find(w => w.de === wordObj.de)) { | |
| practiceNewMistakes.push(wordObj); | |
| } | |
| // RE-INSERT mistaken word 3 more times into remaining pool | |
| const remaining = practicePool.slice(practiceIdx + 1); | |
| for (let r = 0; r < 3; r++) { | |
| const pos = Math.floor(Math.random() * (remaining.length + 1)); | |
| remaining.splice(pos, 0, { ...wordObj }); | |
| } | |
| practicePool = [...practicePool.slice(0, practiceIdx + 1), ...remaining]; | |
| document.querySelectorAll('#practiceOpts .quiz-option').forEach(b => { | |
| if (b.textContent === correct) b.classList.add('correct'); | |
| }); | |
| } | |
| // Show memory tip | |
| if (wordObj.memory) { | |
| const tip = document.getElementById('practiceMemoryTip'); | |
| tip.textContent = '💡 Memory Tip: ' + wordObj.memory; | |
| tip.className = 'memory-tip show'; | |
| } | |
| document.getElementById('practiceNextBtn').style.display = ''; | |
| document.getElementById('practiceCounter').textContent = `Round ${practiceRound} — Question ${practiceIdx + 1} of ${practicePool.length}`; | |
| practiceIdx++; | |
| updatePracticeStatus(); | |
| } | |
| function nextPractice() { showPracticeQuestion(); } | |
| function showPracticeResult() { | |
| stopPracticeTimer(); | |
| const elapsed = Math.floor((Date.now() - practiceStartTime) / 1000); | |
| document.getElementById('practiceActive').classList.add('hidden'); | |
| const res = document.getElementById('practiceResult'); | |
| res.classList.remove('hidden'); | |
| const accuracy = practiceAttempted > 0 ? Math.round(practiceCorrect / practiceAttempted * 100) : 100; | |
| if (practiceMistakeCount === 0) { | |
| document.getElementById('practiceResEmoji').textContent = '🏆'; | |
| document.getElementById('practiceResTitle').textContent = 'Perfect Practice!'; | |
| document.getElementById('practiceResMsg').textContent = 'Zero mistakes! You\'ve mastered these words!'; | |
| spawnConfetti(); | |
| } else { | |
| document.getElementById('practiceResEmoji').textContent = '💪'; | |
| document.getElementById('practiceResTitle').textContent = 'Practice Complete!'; | |
| document.getElementById('practiceResMsg').textContent = `You had ${practiceMistakeCount} mistake(s). Try again to get a perfect score!`; | |
| } | |
| document.getElementById('practiceResultStats').innerHTML = ` | |
| <div class="result-stat-card success-stat"><div class="stat-label">Correct</div><div class="stat-value">${practiceCorrect}</div></div> | |
| <div class="result-stat-card error-stat"><div class="stat-label">Mistakes</div><div class="stat-value">${practiceMistakeCount}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Accuracy</div><div class="stat-value">${accuracy}%</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Time Spent</div><div class="stat-value">${formatTime(elapsed)}</div></div> | |
| `; | |
| // Show wrong words if any | |
| if (practiceNewMistakes.length > 0) { | |
| document.getElementById('practiceWrongList').innerHTML = ` | |
| <div class="ww-title">Words to Review:</div> | |
| ${practiceNewMistakes.map(w => ` | |
| <div class="wrong-word-item"> | |
| <span class="ww-de">${w.de}</span> | |
| <span class="ww-en">${w.en}</span> | |
| <span class="ww-tip">${w.memory || ''}</span> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| document.getElementById('practiceWrongList').innerHTML = ''; | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // HARDCORE MODE (Full Vocabulary Final Test) | |
| // ═══════════════════════════════════════════════════════════ | |
| function startHardcoreTimer() { | |
| if (hardcoreTimerInterval) clearInterval(hardcoreTimerInterval); | |
| hardcoreStartTime = Date.now(); | |
| hardcoreTimerInterval = setInterval(() => { | |
| const elapsed = Math.floor((Date.now() - hardcoreStartTime) / 1000); | |
| document.getElementById('hardcoreTimer').textContent = formatTime(elapsed); | |
| }, 1000); | |
| } | |
| function stopHardcoreTimer() { | |
| if (hardcoreTimerInterval) { clearInterval(hardcoreTimerInterval); hardcoreTimerInterval = null; } | |
| } | |
| // Confirm modal helpers | |
| let _confirmCallback = null; | |
| function showConfirm(emoji, title, msg, onYes) { | |
| document.getElementById('confirmEmoji').textContent = emoji; | |
| document.getElementById('confirmTitle').textContent = title; | |
| document.getElementById('confirmMsg').textContent = msg; | |
| _confirmCallback = onYes; | |
| document.getElementById('confirmOverlay').classList.remove('hidden'); | |
| } | |
| function confirmYes() { | |
| document.getElementById('confirmOverlay').classList.add('hidden'); | |
| if (_confirmCallback) { _confirmCallback(); _confirmCallback = null; } | |
| } | |
| function confirmNo() { | |
| document.getElementById('confirmOverlay').classList.add('hidden'); | |
| _confirmCallback = null; | |
| } | |
| // Hardcore level selection (all levels unlocked) | |
| let hardcoreSelectedLevel = 1; | |
| function selectHCLevel(level, btn) { | |
| hardcoreSelectedLevel = level; | |
| document.querySelectorAll('.hc-level-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| // Only auto-restart if quiz is not in progress | |
| if (hardcoreAttempted === 0) resetHardcore(); | |
| } | |
| function confirmHCStop() { | |
| showConfirm('⏹', 'Stop the test?', | |
| `You have attempted ${hardcoreAttempted} question(s). Your progress will be saved as partial stats.`, | |
| () => showHardcoreResult(true) | |
| ); | |
| } | |
| function confirmHCRestart() { | |
| showConfirm('↺', 'Restart the test?', | |
| 'All your current progress will be lost and the test will restart from the beginning.', | |
| () => resetHardcore() | |
| ); | |
| } | |
| function initHardcore() { | |
| resetHardcore(); | |
| } | |
| function resetHardcore() { | |
| const levelWords = vocab.filter(v => v.level === hardcoreSelectedLevel); | |
| hardcorePool = shuffle(levelWords); | |
| hardcoreIdx = 0; | |
| hardcoreCorrect = 0; | |
| hardcoreMistakeCount = 0; | |
| hardcoreAttempted = 0; | |
| hardcoreAnswered = false; | |
| hardcoreWrongWords = []; | |
| document.getElementById('hardcoreActive').classList.remove('hidden'); | |
| document.getElementById('hardcoreResult').classList.add('hidden'); | |
| document.getElementById('hcMistakePractice').classList.add('hidden'); | |
| document.getElementById('hcMistakePracticeResult').classList.add('hidden'); | |
| document.getElementById('hcLevelPicker').style.display = ''; | |
| updateHardcoreStatus(); | |
| startHardcoreTimer(); | |
| showHardcoreQuestion(); | |
| } | |
| function updateHardcoreStatus() { | |
| document.getElementById('hardcoreRemaining').textContent = hardcorePool.length - hardcoreIdx; | |
| document.getElementById('hardcoreAttempted').textContent = hardcoreAttempted; | |
| document.getElementById('hardcoreMistakeCount').textContent = hardcoreMistakeCount; | |
| document.getElementById('hardcoreCorrectCount').textContent = hardcoreCorrect; | |
| } | |
| function showHardcoreQuestion() { | |
| if (hardcoreIdx >= hardcorePool.length) { | |
| showHardcoreResult(); | |
| return; | |
| } | |
| hardcoreAnswered = false; | |
| const v = hardcorePool[hardcoreIdx]; | |
| document.getElementById('hardcoreQ').textContent = v.de; | |
| document.getElementById('hardcoreCtx').textContent = v.ex ? `e.g. "${v.ex}"` : `Level ${v.level} • ${(v.category || '').replace(/-/g, ' ')}`; | |
| // Harder options: prefer same category | |
| let sameCategory = vocab.filter(w => w.en !== v.en && w.category === v.category && w.level === currentLevel); | |
| let otherWords = vocab.filter(w => w.en !== v.en && w.level === currentLevel); | |
| let wrongPool = sameCategory.length >= 3 ? sameCategory : otherWords; | |
| let wrong = shuffle(wrongPool).slice(0, 3).map(w => w.en); | |
| // Ensure we have 3 unique wrong answers | |
| while (wrong.length < 3) { | |
| const extra = shuffle(otherWords).find(w => !wrong.includes(w.en) && w.en !== v.en); | |
| if (extra) wrong.push(extra.en); | |
| else break; | |
| } | |
| let options = shuffle([v.en, ...wrong]); | |
| const optDiv = document.getElementById('hardcoreOpts'); | |
| optDiv.innerHTML = ''; | |
| options.forEach(opt => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'quiz-option'; | |
| btn.textContent = opt; | |
| btn.onclick = () => selectHardcoreOpt(btn, opt, v.en, v); | |
| optDiv.appendChild(btn); | |
| }); | |
| const fb = document.getElementById('hardcoreFeedback'); | |
| fb.className = 'feedback-msg'; | |
| fb.textContent = ''; | |
| document.getElementById('hardcoreMemoryTip').className = 'memory-tip'; | |
| document.getElementById('hardcoreNextBtn').style.display = 'none'; | |
| const pct = (hardcoreIdx / hardcorePool.length * 100).toFixed(0); | |
| document.getElementById('hardcoreProgress').style.width = pct + '%'; | |
| document.getElementById('hardcoreCounter').textContent = `Question ${hardcoreIdx + 1} of ${hardcorePool.length}`; | |
| updateHardcoreStatus(); | |
| } | |
| function selectHardcoreOpt(btn, chosen, correct, wordObj) { | |
| if (hardcoreAnswered) return; | |
| hardcoreAnswered = true; | |
| hardcoreAttempted++; | |
| document.querySelectorAll('#hardcoreOpts .quiz-option').forEach(b => { b.disabled = true; }); | |
| const fb = document.getElementById('hardcoreFeedback'); | |
| if (chosen === correct) { | |
| btn.classList.add('correct'); | |
| fb.textContent = '✓ Richtig! Excellent!'; | |
| fb.className = 'feedback-msg show ok'; | |
| hardcoreCorrect++; | |
| addScore(5); | |
| } else { | |
| btn.classList.add('wrong'); | |
| fb.textContent = `✗ Correct answer: "${correct}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| hardcoreMistakeCount++; | |
| resetStreak(); | |
| if (!hardcoreWrongWords.find(w => w.de === wordObj.de)) { | |
| hardcoreWrongWords.push(wordObj); | |
| } | |
| // Also add to global mistakenWords for Practice mode | |
| if (!mistakenWords.find(w => w.de === wordObj.de)) { | |
| mistakenWords.push(wordObj); | |
| } | |
| document.querySelectorAll('#hardcoreOpts .quiz-option').forEach(b => { | |
| if (b.textContent === correct) b.classList.add('correct'); | |
| }); | |
| } | |
| // Always show memory tip in Hardcore mode | |
| if (wordObj.memory) { | |
| const tip = document.getElementById('hardcoreMemoryTip'); | |
| tip.textContent = '💡 Remember: ' + wordObj.memory; | |
| tip.className = 'memory-tip show'; | |
| } | |
| document.getElementById('hardcoreNextBtn').style.display = ''; | |
| document.getElementById('hardcoreCounter').textContent = `Question ${hardcoreIdx + 1} of ${hardcorePool.length}`; | |
| hardcoreIdx++; | |
| updateHardcoreStatus(); | |
| } | |
| function nextHardcore() { showHardcoreQuestion(); } | |
| function showHardcoreResult(partial = false) { | |
| stopHardcoreTimer(); | |
| const elapsed = Math.floor((Date.now() - hardcoreStartTime) / 1000); | |
| const attempted = hardcoreAttempted; | |
| document.getElementById('hardcoreActive').classList.add('hidden'); | |
| document.getElementById('hcLevelPicker').style.display = 'none'; | |
| const res = document.getElementById('hardcoreResult'); | |
| res.classList.remove('hidden'); | |
| const accuracy = attempted > 0 ? Math.round(hardcoreCorrect / attempted * 100) : 100; | |
| if (partial) { | |
| document.getElementById('hardcoreResEmoji').textContent = '⏹'; | |
| document.getElementById('hardcoreResTitle').textContent = 'Test Stopped'; | |
| document.getElementById('hardcoreResMsg').textContent = `You answered ${attempted} of ${hardcorePool.length} questions before stopping.`; | |
| } else if (accuracy === 100) { | |
| document.getElementById('hardcoreResEmoji').textContent = '🏆'; | |
| document.getElementById('hardcoreResTitle').textContent = 'PERFECT SCORE!'; | |
| document.getElementById('hardcoreResMsg').textContent = 'You are a vocabulary master! Unbeatable! 🔥'; | |
| spawnConfetti(); | |
| } else if (accuracy >= 80) { | |
| document.getElementById('hardcoreResEmoji').textContent = '🌟'; | |
| document.getElementById('hardcoreResTitle').textContent = 'Ausgezeichnet!'; | |
| document.getElementById('hardcoreResMsg').textContent = 'Excellent performance! Almost perfect!'; | |
| spawnConfetti(); | |
| } else if (accuracy >= 60) { | |
| document.getElementById('hardcoreResEmoji').textContent = '💪'; | |
| document.getElementById('hardcoreResTitle').textContent = 'Gut gemacht!'; | |
| document.getElementById('hardcoreResMsg').textContent = 'Good job! Keep practicing the words below.'; | |
| } else { | |
| document.getElementById('hardcoreResEmoji').textContent = '📚'; | |
| document.getElementById('hardcoreResTitle').textContent = 'Keep Studying!'; | |
| document.getElementById('hardcoreResMsg').textContent = 'Review the words below and try Practice Mistakes!'; | |
| } | |
| document.getElementById('hardcoreResultStats').innerHTML = ` | |
| <div class="result-stat-card success-stat"><div class="stat-label">Correct</div><div class="stat-value">${hardcoreCorrect}</div></div> | |
| <div class="result-stat-card error-stat"><div class="stat-label">Mistakes</div><div class="stat-value">${hardcoreMistakeCount}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Accuracy</div><div class="stat-value">${accuracy}%</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Time Spent</div><div class="stat-value">${formatTime(elapsed)}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">${partial ? 'Answered' : 'Total Qs'}</div><div class="stat-value">${partial ? attempted + ' / ' + hardcorePool.length : hardcorePool.length}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Level</div><div class="stat-value">Lv ${hardcoreSelectedLevel}</div></div> | |
| `; | |
| if (hardcoreWrongWords.length > 0) { | |
| document.getElementById('hcPracticeMistakesBtn').style.display = ''; | |
| document.getElementById('hardcoreWrongList').innerHTML = ` | |
| <div class="ww-title">❌ Words to Review (${hardcoreWrongWords.length}):</div> | |
| ${hardcoreWrongWords.map(w => ` | |
| <div class="wrong-word-item"> | |
| <span class="ww-de">${w.de}</span> | |
| <span class="ww-en">${w.en}</span> | |
| <span class="ww-tip">💡 ${w.memory || 'No tip'}</span> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| document.getElementById('hcPracticeMistakesBtn').style.display = 'none'; | |
| document.getElementById('hardcoreWrongList').innerHTML = '<div style="text-align:center;color:var(--success);font-family:DM Mono,monospace;font-size:0.85rem;padding:16px">🎉 No wrong answers! Perfect!</div>'; | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // ALL HARDCORE CONFIRMATIONS | |
| function confirmAHStop() { | |
| showConfirm('⏹', 'Stop the test?', | |
| `You have attempted ${ahAttempted} question(s). Partial stats will be shown.`, | |
| () => showAHResult(true) | |
| ); | |
| } | |
| function confirmAHRestart() { | |
| showConfirm('↺', 'Restart the test?', | |
| 'All your current progress will be lost and the full test will restart.', | |
| () => resetAllHardcore() | |
| ); | |
| } | |
| // ALL HARDCORE MODE (Full Vocabulary ALL Levels) | |
| // ═══════════════════════════════════════════════════════════ | |
| function startAHTimer() { | |
| if (ahTimerInterval) clearInterval(ahTimerInterval); | |
| ahStartTime = Date.now(); | |
| ahTimerInterval = setInterval(() => { | |
| const elapsed = Math.floor((Date.now() - ahStartTime) / 1000); | |
| document.getElementById('allhardcoreTimer').textContent = formatTime(elapsed); | |
| }, 1000); | |
| } | |
| function stopAHTimer() { | |
| if (ahTimerInterval) { clearInterval(ahTimerInterval); ahTimerInterval = null; } | |
| } | |
| function initAllHardcore() { | |
| resetAllHardcore(); | |
| } | |
| function resetAllHardcore() { | |
| // ALL vocab from ALL levels — the ultimate test | |
| ahPool = shuffle([...vocab]); | |
| ahIdx = 0; | |
| ahCorrect = 0; | |
| ahMistakeCount = 0; | |
| ahAttempted = 0; | |
| ahAnswered = false; | |
| ahWrongWords = []; | |
| document.getElementById('allhardcoreActive').classList.remove('hidden'); | |
| document.getElementById('allhardcoreResult').classList.add('hidden'); | |
| document.getElementById('ahMistakePractice').classList.add('hidden'); | |
| document.getElementById('ahMistakePracticeResult').classList.add('hidden'); | |
| updateAHStatus(); | |
| startAHTimer(); | |
| showAHQuestion(); | |
| } | |
| function updateAHStatus() { | |
| document.getElementById('allhardcoreRemaining').textContent = ahPool.length - ahIdx; | |
| document.getElementById('allhardcoreAttempted').textContent = ahAttempted; | |
| document.getElementById('allhardcoreMistakeCount').textContent = ahMistakeCount; | |
| document.getElementById('allhardcoreCorrectCount').textContent = ahCorrect; | |
| } | |
| function showAHQuestion() { | |
| if (ahIdx >= ahPool.length) { | |
| showAHResult(); | |
| return; | |
| } | |
| ahAnswered = false; | |
| const v = ahPool[ahIdx]; | |
| document.getElementById('allhardcoreQ').textContent = v.de; | |
| document.getElementById('allhardcoreCtx').textContent = v.ex ? `e.g. "${v.ex}"` : `Level ${v.level} • ${(v.category || '').replace(/-/g, ' ')}`; | |
| // Harder options: prefer same category across all levels | |
| let sameCategory = vocab.filter(w => w.en !== v.en && w.category === v.category); | |
| let otherWords = vocab.filter(w => w.en !== v.en); | |
| let wrongPool = sameCategory.length >= 3 ? sameCategory : otherWords; | |
| let wrong = shuffle(wrongPool).slice(0, 3).map(w => w.en); | |
| while (wrong.length < 3) { | |
| const extra = shuffle(otherWords).find(w => !wrong.includes(w.en) && w.en !== v.en); | |
| if (extra) wrong.push(extra.en); | |
| else break; | |
| } | |
| let options = shuffle([v.en, ...wrong]); | |
| const optDiv = document.getElementById('allhardcoreOpts'); | |
| optDiv.innerHTML = ''; | |
| options.forEach(opt => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'quiz-option'; | |
| btn.textContent = opt; | |
| btn.onclick = () => selectAHOpt(btn, opt, v.en, v); | |
| optDiv.appendChild(btn); | |
| }); | |
| const fb = document.getElementById('allhardcoreFeedback'); | |
| fb.className = 'feedback-msg'; | |
| fb.textContent = ''; | |
| document.getElementById('allhardcoreMemoryTip').className = 'memory-tip'; | |
| document.getElementById('allhardcoreNextBtn').style.display = 'none'; | |
| const pct = (ahIdx / ahPool.length * 100).toFixed(0); | |
| document.getElementById('allhardcoreProgress').style.width = pct + '%'; | |
| document.getElementById('allhardcoreCounter').textContent = `Question ${ahIdx + 1} of ${ahPool.length}`; | |
| updateAHStatus(); | |
| } | |
| function selectAHOpt(btn, chosen, correct, wordObj) { | |
| if (ahAnswered) return; | |
| ahAnswered = true; | |
| ahAttempted++; | |
| document.querySelectorAll('#allhardcoreOpts .quiz-option').forEach(b => { b.disabled = true; }); | |
| const fb = document.getElementById('allhardcoreFeedback'); | |
| if (chosen === correct) { | |
| btn.classList.add('correct'); | |
| fb.textContent = '✓ Richtig! Excellent!'; | |
| fb.className = 'feedback-msg show ok'; | |
| ahCorrect++; | |
| addScore(5); | |
| } else { | |
| btn.classList.add('wrong'); | |
| fb.textContent = `✗ Correct answer: "${correct}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| ahMistakeCount++; | |
| resetStreak(); | |
| if (!ahWrongWords.find(w => w.de === wordObj.de)) { | |
| ahWrongWords.push(wordObj); | |
| } | |
| // Also add to global mistakenWords for Practice mode | |
| if (!mistakenWords.find(w => w.de === wordObj.de)) { | |
| mistakenWords.push(wordObj); | |
| } | |
| document.querySelectorAll('#allhardcoreOpts .quiz-option').forEach(b => { | |
| if (b.textContent === correct) b.classList.add('correct'); | |
| }); | |
| } | |
| // Always show memory tip | |
| if (wordObj.memory) { | |
| const tip = document.getElementById('allhardcoreMemoryTip'); | |
| tip.textContent = '💡 Remember: ' + wordObj.memory; | |
| tip.className = 'memory-tip show'; | |
| } | |
| document.getElementById('allhardcoreNextBtn').style.display = ''; | |
| document.getElementById('allhardcoreCounter').textContent = `Question ${ahIdx + 1} of ${ahPool.length}`; | |
| ahIdx++; | |
| updateAHStatus(); | |
| } | |
| function nextAllHardcore() { showAHQuestion(); } | |
| function showAHResult(partial = false) { | |
| stopAHTimer(); | |
| const elapsed = Math.floor((Date.now() - ahStartTime) / 1000); | |
| const attempted = ahAttempted; | |
| document.getElementById('allhardcoreActive').classList.add('hidden'); | |
| const res = document.getElementById('allhardcoreResult'); | |
| res.classList.remove('hidden'); | |
| const accuracy = ahAttempted > 0 ? Math.round(ahCorrect / ahAttempted * 100) : 100; | |
| if (partial) { | |
| document.getElementById('allhardcoreResEmoji').textContent = '⏹'; | |
| document.getElementById('allhardcoreResTitle').textContent = 'Test Stopped'; | |
| document.getElementById('allhardcoreResMsg').textContent = `You answered ${attempted} of ${ahPool.length} questions before stopping.`; | |
| } else if (accuracy === 100) { | |
| document.getElementById('allhardcoreResEmoji').textContent = '👑'; | |
| document.getElementById('allhardcoreResTitle').textContent = 'LEGENDARY!'; | |
| document.getElementById('allhardcoreResMsg').textContent = 'You mastered EVERY word! You are unstoppable! 💀🔥'; | |
| spawnConfetti(); | |
| } else if (accuracy >= 80) { | |
| document.getElementById('allhardcoreResEmoji').textContent = '🏆'; | |
| document.getElementById('allhardcoreResTitle').textContent = 'Ausgezeichnet!'; | |
| document.getElementById('allhardcoreResMsg').textContent = 'Incredible performance across all levels!'; | |
| spawnConfetti(); | |
| } else if (accuracy >= 60) { | |
| document.getElementById('allhardcoreResEmoji').textContent = '💪'; | |
| document.getElementById('allhardcoreResTitle').textContent = 'Gut gemacht!'; | |
| document.getElementById('allhardcoreResMsg').textContent = 'Good effort! Review the words below.'; | |
| } else { | |
| document.getElementById('allhardcoreResEmoji').textContent = '📚'; | |
| document.getElementById('allhardcoreResTitle').textContent = 'Keep Studying!'; | |
| document.getElementById('allhardcoreResMsg').textContent = 'Go back to flashcards and practice more!'; | |
| } | |
| document.getElementById('allhardcoreResultStats').innerHTML = ` | |
| <div class="result-stat-card success-stat"><div class="stat-label">Correct</div><div class="stat-value">${ahCorrect}</div></div> | |
| <div class="result-stat-card error-stat"><div class="stat-label">Mistakes</div><div class="stat-value">${ahMistakeCount}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Accuracy</div><div class="stat-value">${accuracy}%</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Time Spent</div><div class="stat-value">${formatTime(elapsed)}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">${partial ? 'Answered' : 'Total Qs'}</div><div class="stat-value">${partial ? attempted + ' / ' + ahPool.length : ahPool.length}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">All Levels</div><div class="stat-value">1-6</div></div> | |
| `; | |
| if (ahWrongWords.length > 0) { | |
| // Show Practice Mistakes button | |
| document.getElementById('ahPracticeMistakesBtn').style.display = ''; | |
| document.getElementById('allhardcoreWrongList').innerHTML = ` | |
| <div class="ww-title">❌ Words to Review (${ahWrongWords.length}):</div> | |
| ${ahWrongWords.map(w => ` | |
| <div class="wrong-word-item"> | |
| <span class="ww-de">${w.de}</span> | |
| <span class="ww-en">${w.en}</span> | |
| <span class="ww-tip">💡 ${w.memory || 'No tip'}</span> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| document.getElementById('ahPracticeMistakesBtn').style.display = 'none'; | |
| document.getElementById('allhardcoreWrongList').innerHTML = '<div style="text-align:center;color:var(--success);font-family:DM Mono,monospace;font-size:0.85rem;padding:16px">🎉 No wrong answers! Legendary!</div>'; | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // ALL HARDCORE — PRACTICE MISTAKES (post-quiz loop) | |
| // ═══════════════════════════════════════════════════════════ | |
| let ahmpPool = [], ahmpIdx = 0, ahmpCorrectCount = 0, ahmpMistakeCount = 0, ahmpAttemptedCount = 0; | |
| let ahmpAnswered = false, ahmpTimerInterval = null, ahmpStartTime = null; | |
| let ahmpNewMistakes = [], ahmpRound = 1; | |
| function startAHMistakePractice() { | |
| if (ahWrongWords.length === 0) return; | |
| // Hide result & active quiz, show mistake practice | |
| document.getElementById('allhardcoreResult').classList.add('hidden'); | |
| document.getElementById('allhardcoreActive').classList.add('hidden'); | |
| document.getElementById('ahMistakePracticeResult').classList.add('hidden'); | |
| document.getElementById('ahMistakePractice').classList.remove('hidden'); | |
| // Build pool: each wrong word ×3, shuffled | |
| ahmpPool = []; | |
| for (let i = 0; i < 3; i++) { | |
| ahmpPool = ahmpPool.concat([...ahWrongWords]); | |
| } | |
| ahmpPool = shuffle(ahmpPool); | |
| ahmpIdx = 0; | |
| ahmpCorrectCount = 0; | |
| ahmpMistakeCount = 0; | |
| ahmpAttemptedCount = 0; | |
| ahmpAnswered = false; | |
| ahmpNewMistakes = []; | |
| ahmpRound = 1; | |
| // Start timer | |
| if (ahmpTimerInterval) clearInterval(ahmpTimerInterval); | |
| ahmpStartTime = Date.now(); | |
| ahmpTimerInterval = setInterval(() => { | |
| const elapsed = Math.floor((Date.now() - ahmpStartTime) / 1000); | |
| document.getElementById('ahmpTimer').textContent = formatTime(elapsed); | |
| }, 1000); | |
| updateAHMPStatus(); | |
| showAHMPQuestion(); | |
| } | |
| function updateAHMPStatus() { | |
| document.getElementById('ahmpRemaining').textContent = ahmpPool.length - ahmpIdx; | |
| document.getElementById('ahmpAttempted').textContent = ahmpAttemptedCount; | |
| document.getElementById('ahmpMistakes').textContent = ahmpMistakeCount; | |
| document.getElementById('ahmpCorrect').textContent = ahmpCorrectCount; | |
| } | |
| function showAHMPQuestion() { | |
| if (ahmpIdx >= ahmpPool.length) { | |
| // End of round: check if there are new mistakes | |
| if (ahmpNewMistakes.length > 0) { | |
| // Re-queue new mistakes ×3 | |
| ahmpPool = []; | |
| for (let i = 0; i < 3; i++) { | |
| ahmpPool = ahmpPool.concat([...ahmpNewMistakes]); | |
| } | |
| ahmpPool = shuffle(ahmpPool); | |
| ahmpNewMistakes = []; | |
| ahmpIdx = 0; | |
| ahmpRound++; | |
| updateAHMPStatus(); | |
| showAHMPQuestion(); | |
| return; | |
| } | |
| // All clear! | |
| showAHMPResult(); | |
| return; | |
| } | |
| ahmpAnswered = false; | |
| const v = ahmpPool[ahmpIdx]; | |
| document.getElementById('ahmpQ').textContent = v.de; | |
| document.getElementById('ahmpCtx').textContent = v.ex ? `e.g. "${v.ex}"` : `Level ${v.level} • ${(v.category || '').replace(/-/g, ' ')}`; | |
| let wrong = shuffle(vocab.filter(w => w.en !== v.en)).slice(0, 3).map(w => w.en); | |
| let options = shuffle([v.en, ...wrong]); | |
| const optDiv = document.getElementById('ahmpOpts'); | |
| optDiv.innerHTML = ''; | |
| options.forEach(opt => { | |
| const btn = document.createElement('button'); | |
| btn.className = 'quiz-option'; | |
| btn.textContent = opt; | |
| btn.onclick = () => selectAHMPOpt(btn, opt, v.en, v); | |
| optDiv.appendChild(btn); | |
| }); | |
| document.getElementById('ahmpFeedback').className = 'feedback-msg'; | |
| document.getElementById('ahmpFeedback').textContent = ''; | |
| document.getElementById('ahmpMemoryTip').className = 'memory-tip'; | |
| document.getElementById('ahmpNextBtn').style.display = 'none'; | |
| const pct = (ahmpIdx / ahmpPool.length * 100).toFixed(0); | |
| document.getElementById('ahmpProgress').style.width = pct + '%'; | |
| document.getElementById('ahmpCounter').textContent = `Round ${ahmpRound} — Question ${ahmpIdx + 1} of ${ahmpPool.length}`; | |
| updateAHMPStatus(); | |
| } | |
| function selectAHMPOpt(btn, chosen, correct, wordObj) { | |
| if (ahmpAnswered) return; | |
| ahmpAnswered = true; | |
| ahmpAttemptedCount++; | |
| document.querySelectorAll('#ahmpOpts .quiz-option').forEach(b => { b.disabled = true; }); | |
| const fb = document.getElementById('ahmpFeedback'); | |
| if (chosen === correct) { | |
| btn.classList.add('correct'); | |
| fb.textContent = '✓ Richtig! You remembered it!'; | |
| fb.className = 'feedback-msg show ok'; | |
| ahmpCorrectCount++; | |
| addScore(3); | |
| } else { | |
| btn.classList.add('wrong'); | |
| fb.textContent = `✗ Correct answer: "${correct}"`; | |
| fb.className = 'feedback-msg show bad'; | |
| ahmpMistakeCount++; | |
| resetStreak(); | |
| if (!ahmpNewMistakes.find(w => w.de === wordObj.de)) { | |
| ahmpNewMistakes.push(wordObj); | |
| } | |
| document.querySelectorAll('#ahmpOpts .quiz-option').forEach(b => { | |
| if (b.textContent === correct) b.classList.add('correct'); | |
| }); | |
| } | |
| if (wordObj.memory) { | |
| const tip = document.getElementById('ahmpMemoryTip'); | |
| tip.textContent = '💡 Remember: ' + wordObj.memory; | |
| tip.className = 'memory-tip show'; | |
| } | |
| document.getElementById('ahmpNextBtn').style.display = ''; | |
| ahmpIdx++; | |
| updateAHMPStatus(); | |
| } | |
| function nextAHMP() { showAHMPQuestion(); } | |
| function showAHMPResult() { | |
| if (ahmpTimerInterval) { clearInterval(ahmpTimerInterval); ahmpTimerInterval = null; } | |
| const elapsed = Math.floor((Date.now() - ahmpStartTime) / 1000); | |
| document.getElementById('ahMistakePractice').classList.add('hidden'); | |
| const res = document.getElementById('ahMistakePracticeResult'); | |
| res.classList.remove('hidden'); | |
| const accuracy = ahmpAttemptedCount > 0 ? Math.round(ahmpCorrectCount / ahmpAttemptedCount * 100) : 100; | |
| if (ahmpMistakeCount === 0) { | |
| document.getElementById('ahmpResEmoji').textContent = '🏆'; | |
| document.getElementById('ahmpResTitle').textContent = 'All Mistakes Fixed!'; | |
| document.getElementById('ahmpResMsg').textContent = 'Zero mistakes! You\'ve mastered every word! 🔥'; | |
| spawnConfetti(); | |
| } else { | |
| document.getElementById('ahmpResEmoji').textContent = '💪'; | |
| document.getElementById('ahmpResTitle').textContent = 'Practice Complete!'; | |
| document.getElementById('ahmpResMsg').textContent = `${ahmpMistakeCount} mistake(s) remaining. Try again!`; | |
| } | |
| document.getElementById('ahmpResultStats').innerHTML = ` | |
| <div class="result-stat-card success-stat"><div class="stat-label">Correct</div><div class="stat-value">${ahmpCorrectCount}</div></div> | |
| <div class="result-stat-card error-stat"><div class="stat-label">Mistakes</div><div class="stat-value">${ahmpMistakeCount}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Accuracy</div><div class="stat-value">${accuracy}%</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Time Spent</div><div class="stat-value">${formatTime(elapsed)}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Rounds</div><div class="stat-value">${ahmpRound}</div></div> | |
| <div class="result-stat-card"><div class="stat-label">Total Qs</div><div class="stat-value">${ahmpAttemptedCount}</div></div> | |
| `; | |
| if (ahmpNewMistakes.length > 0) { | |
| document.getElementById('ahmpWrongList').innerHTML = ` | |
| <div class="ww-title">Still struggling with:</div> | |
| ${ahmpNewMistakes.map(w => ` | |
| <div class="wrong-word-item"> | |
| <span class="ww-de">${w.de}</span> | |
| <span class="ww-en">${w.en}</span> | |
| <span class="ww-tip">💡 ${w.memory || ''}</span> | |
| </div> | |
| `).join('')} | |
| `; | |
| } else { | |
| document.getElementById('ahmpWrongList').innerHTML = ''; | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════ | |
| // INIT | |
| // ═══════════════════════════════════════════════════════════ | |
| loadProgress(); | |
| updateLevelButtons(); | |
| initCards(); | |
| </script> | |
| </body> | |
| </html> |