VELOX
VELOX
CELERITAS CUM CONSILIO
About
German Education Flashcards
Smart review: missed words show up more until you master them.
Focus hard words
Next card
Reset progress
Word mastery
(this word)
0%
Topic progress
(mastered words)
0%
â¦
Tap to flip
Tap card to flip
Space
flip ·
1
wrong ·
2
right
â¦
â¦
Mark your result:
â I missed it
â I got it
Flip
â
`; return; } top.forEach(r=>{ const div = document.createElement("div"); div.className = "vx-item"; div.innerHTML = `
${escapeHtml(r.de)}
${escapeHtml(r.en)}
missed ${r.misses}Ã
`; div.onclick = () => { current = { ti: activeTopic, wi: r.wi }; showCard(); }; hardList.appendChild(div); }); } function showCard(){ const t = DATA[activeTopic]; const w = t.words[current.wi]; const s = getStat(activeTopic, current.wi); // front/back frontWord.textContent = w.de; backWord.textContent = w.de; backMeaning.textContent = w.en; // meta pills metaPills.innerHTML = ""; const pills = [ {txt: t.topic}, {txt: `Correct: ${s.c}`}, {txt: `Wrong: ${s.w}`}, {txt: `Streak: ${s.streak}`} ]; pills.forEach(p=>{ const span = document.createElement("span"); span.className = "vx-pill"; span.textContent = p.txt; metaPills.appendChild(span); }); // hint frontHint.textContent = "Tap to flip ⢠Then mark right/wrong"; streakEl.textContent = s.streak >= 3 ? `ð¥ streak ${s.streak}` : (s.streak > 0 ? `streak ${s.streak}` : ""); // info cardInfo.textContent = `Topic: ${t.topic} ⢠Card ${current.wi+1}/${t.words.length}`; // reset flip flipInner.classList.remove("is-flipped"); renderBars(); renderHardList(); saveState(); } // Weighted selection: // - hard words get higher weight (wrong count + low mastery) // - if FocusHard ON -> even more boost to missed words function pickNext(forceNewTopicCard){ const ti = activeTopic; const words = DATA[ti].words; if(!words.length) return; const focusHard = !!state.settings.focusHard; const now = Date.now(); const weights = words.map((_, wi)=>{ const s = getStat(ti, wi); const m = masteryForWord(s); const attempts = s.c + s.w; // spaced-ish: if last seen recently, reduce weight a bit const age = s.last ? clamp((now - s.last) / 60000, 0, 30) : 30; // minutes const recencyFactor = 0.6 + (age / 30) * 0.6; // 0.6..1.2 // core difficulty weight const missBoost = 1 + (s.w * 0.9); const lowMasteryBoost = 1 + ((1 - m) * 2.2); // if never seen, give decent weight too const newBoost = attempts === 0 ? 2.0 : 1.0; // FocusHard: super boost misses & low mastery const focusBoost = focusHard ? (1 + s.w * 1.2 + (1 - m) * 1.5) : 1.0; // mastered words get reduced weight (but not zero) const masteredPenalty = isMastered(s) ? 0.25 : 1.0; return Math.max(0.02, recencyFactor * missBoost * lowMasteryBoost * newBoost * focusBoost * masteredPenalty); }); const pickIndex = weightedRandomIndex(weights); current = { ti, wi: pickIndex }; showCard(); } function weightedRandomIndex(weights){ let sum = 0; for(const w of weights) sum += w; let r = Math.random() * sum; for(let i=0; i
",">") .replaceAll('"',""") .replaceAll("'","'"); } // ====== EVENTS ====== flipInner.addEventListener("click", () => { flipInner.classList.toggle("is-flipped"); }); flipBtn.addEventListener("click", () => { flipInner.classList.toggle("is-flipped"); }); wrongBtn.addEventListener("click", () => mark(false)); rightBtn.addEventListener("click", () => mark(true)); shuffleBtn.addEventListener("click", () => pickNext(false)); focusHardEl.addEventListener("change", () => { state.settings.focusHard = !!focusHardEl.checked; saveState(); renderHardList(); }); resetBtn.addEventListener("click", () => { if(!confirm("Reset all progress for these flashcards on this device?")) return; localStorage.removeItem(STORAGE_KEY); state = loadState(); focusHardEl.checked = false; activeTopic = 0; state.lastTopic = 0; renderTabs(); pickNext(true); }); // keyboard shortcuts (mobile friendly too) document.addEventListener("keydown", (e) => { if(!root.contains(document.activeElement) && !root.matches(":hover")) return; if(e.code === "Space"){ e.preventDefault(); flipInner.classList.toggle("is-flipped"); } if(e.key === "1"){ mark(false); } if(e.key === "2"){ mark(true); } }); // ====== INIT ====== renderTabs(); pickNext(true); })();