Daily Practice per section: mixed drills, stricter unlock, tick-and-skip one-tap

- One-tap mode no longer shows the answer when you're right: a green tick
  flashes and it skips straight to the next question. Wrong answers still
  show the full correction and now wait for an explicit Next tap.
- Every tab (Prepositions, Articles, Possessives, Pronouns) gets its own
  pinned Daily Practice card. Sessions mix questions from every lesson in
  the tab via a shuffle-bag over lesson ids.
- Daily Practice is locked until every lesson in its section has been
  passed (one full round at 70%); the locked card shows x/y progress.
- Finishing a daily session offers 'Continue: <section> Daily Practice'
  cycling to the next tab with an unlocked daily, with play again and
  back to menu as secondary options.
This commit is contained in:
2026-07-21 07:09:40 +00:00
parent 80f7e0eca2
commit c4c28f46ed
3 changed files with 159 additions and 44 deletions
+26 -4
View File
@@ -237,25 +237,29 @@ export const LEVELS = [
{id:5, tab:'article', name:'Genitiv', desc:'trotz, während, wegen, statt...', cases:['genitive'], preps:PREPS_BY_CASE.genitive, type:'article', artType:'definite'},
{id:6, tab:'article', name:'Alles gemischt', desc:'All prepositions, definite articles', cases:['accusative','dative','genitive'], preps:[...SENTENCE_ACC_PREPS,...PREPS_BY_CASE.dative,...PREPS_BY_CASE.genitive,...PREPS_BY_CASE.wechsel], type:'article', artType:'definite', mixed:true},
{id:7, tab:'article', name:'ein-Wörter', desc:'Everything with ein/eine/einem...', cases:['accusative','dative','genitive'], preps:[...SENTENCE_ACC_PREPS,...PREPS_BY_CASE.dative,...PREPS_BY_CASE.genitive,...PREPS_BY_CASE.wechsel], type:'article', artType:'indefinite', mixed:true},
// Daily Practice pseudo-levels (one per tab): a one-tap drill that mixes
// questions from every lesson in the tab. Rendered pinned at the top of each
// tab by App.jsx (not inline in the level list) and locked until every
// lesson in the tab has been passed.
{id:7.5, tab:'article', name:'Daily Practice', desc:'Every article lesson, mixed · one-tap', daily:true},
{id:8, tab:'prep', name:'Häufige Präpositionen', desc:'16 most common prepositions', preps:COMMON_PREPS, type:'prep'},
{id:9, tab:'prep', name:'Seltene Präpositionen', desc:'15 less common prepositions', preps:RARE_PREPS, type:'prep'},
{id:10, tab:'prep', name:'Alle Präpositionen', desc:'All 31 prepositions mixed', preps:ALL_PREPS, type:'prep'},
// Daily Practice: unlocked once "Alle Präpositionen" is completed. Drills all
// 31 prepositions and defaults to one-tap mode. Rendered pinned at the top of
// the Prepositions tab by App.jsx, not inline in the level list.
{id:10.5, tab:'prep', name:'Daily Practice', desc:'All 31 prepositions · one-tap daily drill', preps:ALL_PREPS, type:'prep', daily:true},
{id:10.5, tab:'prep', name:'Daily Practice', desc:'Every preposition lesson, mixed · one-tap', daily:true},
{id:18, tab:'prep', name:'Wechsel im Kontext', desc:'Real sentences: Akkusativ or Dativ?', preps:PREPS_BY_CASE.wechsel, type:'wechselContext'},
{id:19, tab:'prep', name:'Kontraktionen', desc:'Contractions: am, ins, zum, beim…', type:'contraction'},
{id:11, tab:'poss', name:'Häufige Possessive', desc:'mein · dein · sein — Akkusativ & Dativ', possessives:['mein','dein','sein'], cases:['accusative','dative'], type:'poss'},
{id:12, tab:'poss', name:'Schwierige Possessive', desc:'ihr · unser · euer — Akkusativ & Dativ', possessives:['ihr','unser','euer'], cases:['accusative','dative'], type:'poss'},
{id:13, tab:'poss', name:'Alle gemischt', desc:'mein, dein, sein, ihr, unser, euer — incl. Genitiv', possessives:['mein','dein','sein','ihr','unser','euer'], cases:['accusative','dative','genitive'], type:'poss'},
{id:13.5, tab:'poss', name:'Daily Practice', desc:'Every possessive lesson, mixed · one-tap', daily:true},
{id:14, tab:'pronoun', name:'Singular Pronomen', desc:'mich, mir, dich, dir…', pronouns:SG_PRONOUN_KEYS, cases:['accusative','dative'], type:'pronoun'},
{id:15, tab:'pronoun', name:'Plural Pronomen', desc:'uns, euch, ihnen…', pronouns:PL_PRONOUN_KEYS, cases:['accusative','dative'], type:'pronoun'},
{id:16, tab:'pronoun', name:'Sie (formal)', desc:'Formal Sie — singular and plural', pronouns:FORMAL_PRONOUN_KEYS, cases:['accusative','dative'], type:'pronoun'},
{id:17, tab:'pronoun', name:'Alle gemischt', desc:'All 10 pronouns, Akkusativ & Dativ', pronouns:ALL_PRONOUN_KEYS, cases:['accusative','dative'], type:'pronoun'},
{id:17.5, tab:'pronoun', name:'Daily Practice', desc:'Every pronoun lesson, mixed · one-tap', daily:true},
];
// Contractions (preposition + article). pref:true = preferred even in writing.
@@ -440,7 +444,25 @@ export function newState() {
}
// A practice session: one shuffle-bag + anti-repeat memory per round.
// Daily levels mix questions from every lesson in their tab: a shuffle-bag
// deals the lesson ids so no lesson repeats back-to-back, and each lesson
// keeps its own state (their bag pools differ, so they can't share one).
export function createSession(levelId) {
const lv = LEVELS.find(l => l.id === levelId);
if (lv?.daily) {
const lessonIds = LEVELS.filter(l => l.tab === lv.tab && !l.daily).map(l => l.id);
const meta = newState();
const states = {};
return {
next: () => {
const id = drawBag(meta, 'lvl', lessonIds, meta.last.lvl);
meta.last.lvl = id;
if (!states[id]) states[id] = newState();
return gen(id, states[id]);
},
state: meta,
};
}
const state = newState();
return { next: () => gen(levelId, state), state };
}