diff --git a/src/App.jsx b/src/App.jsx
index 086d786..41af2a6 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -60,7 +60,10 @@ const TABS = [
{id:'pronoun', label:'Pronouns'},
];
-const FREE_LEVEL_IDS = new Set([2, 3, 4, 8, 9, 10, 18]);
+// Daily Practice pseudo-level: pinned to the top of the Prepositions tab once
+// the "Alle Präpositionen" lesson (id 10) is completed. Always free.
+const DAILY_ID = 10.5;
+const FREE_LEVEL_IDS = new Set([2, 3, 4, 8, 9, 10, 10.5, 18]);
const CORE_ARTICLE_CASES = ['nominative', 'accusative', 'dative'];
const PAYMENTS_ENABLED = import.meta.env.VITE_PAYMENTS_ENABLED === 'true';
const STRIPE_PLANS = PAYMENTS_ENABLED ? [
@@ -220,7 +223,7 @@ export default function App() {
const nextLevelInTab = (id) => {
const lvObj = LEVELS.find(l => l.id === id);
if (!lvObj) return null;
- const tabLevels = LEVELS.filter(l => l.tab === lvObj.tab).sort((a,b) => a.id - b.id);
+ const tabLevels = LEVELS.filter(l => l.tab === lvObj.tab && !l.daily).sort((a,b) => a.id - b.id);
const idx = tabLevels.findIndex(l => l.id === id);
if (idx === -1 || idx === tabLevels.length - 1) return null;
return tabLevels[idx + 1];
@@ -297,12 +300,30 @@ export default function App() {
return () => window.removeEventListener('keydown', h);
});
+ // One-tap mode: after an answer is revealed, auto-advance to the next
+ // question. Always on in Daily Practice; opt-in (st.auto) elsewhere. Correct
+ // answers flash briefly; wrong answers linger so the correction can register.
+ useEffect(() => {
+ if (scr !== 'practice' || !rev) return;
+ const quick = lv === DAILY_ID || !!st.auto;
+ if (!quick) return;
+ const t = setTimeout(() => { nextRef.current?.(); }, ok ? 750 : 1650);
+ return () => clearTimeout(t);
+ }, [scr, rev, ok, lv, st.auto]);
+
if (!loaded) return
;
const rc = res.filter(Boolean).length;
const cur = LEVELS.find(l=>l.id===lv);
const chartSection = scr === 'menu' ? tab : (q?.type === 'wechselContext' ? 'prep' : q?.type === 'chunk' ? 'article' : (q?.type || tab));
const nextInTab = nextLevelInTab(lv);
+ const quickActive = lv === DAILY_ID || !!st.auto;
+ // Daily Practice unlocks once the all-31 lesson (id 10) is passed:
+ // at least one full round with the app's 70% pass rate.
+ const prepMastery = st.sc?.[10];
+ const dailyUnlocked = !!(prepMastery && prepMastery.t >= ROUND && prepMastery.c / prepMastery.t >= 0.7);
+ const dailySc = st.sc?.[DAILY_ID];
+ const dailyPct = dailySc ? Math.round(dailySc.c / dailySc.t * 100) : null;
return (
@@ -314,6 +335,14 @@ export default function App() {
?
:
Beug
}
+ {scr === 'practice' && (
+
+ )}
{scr === 'practice' &&
}
{PAYMENTS_ENABLED && hasPaidAccess &&
Unlocked}
@@ -332,36 +361,47 @@ export default function App() {
{scr === 'menu' && (
- {TABS.map(t => (
-
- ))}
+ {TABS.map(t => (
+
+ ))}
+ {tab === 'prep' && dailyUnlocked && (
+
startLv(DAILY_ID)}>
+
+
⚡ Daily Practice
+
All 31 prepositions · one-tap · keep them sharp
+ {dailySc &&
{dailySc.c}/{dailySc.t}
}
+
+ {dailyPct !== null &&
=70?GREEN:RED}}>{dailyPct}%
}
+
+ )}
{tab === 'prep' && (
{ setLearnStep(0); setScr('learn'); }}>
+ onClick={()=>{ setLearnStep(0); setScr('learn'); }}>
📚 Lernen
Chunk the lists first — Akkusativ, Dativ, Wechsel, Genitiv
)}
- {LEVELS.filter(l => l.tab === tab).sort((a,b)=>a.id-b.id).map(l => {
- const u = unlocked(l.id);
- const s = st.sc[l.id];
- const p = s ? Math.round(s.c/s.t*100) : null;
- return (
-
startLv(l.id)}>
-
-
{l.name}
-
{l.desc}
+ {LEVELS.filter(l => l.tab === tab && !l.daily).sort((a,b)=>a.id-b.id).map(l => {
+ const u = unlocked(l.id);
+ const s = st.sc[l.id];
+ const p = s ? Math.round(s.c/s.t*100) : null;
+ return (
+
startLv(l.id)}>
+
+
{l.name}
+
{l.desc}
{s &&
{s.c}/{s.t}
}
{p !== null &&
=70?GREEN:RED}}>{p}%
}
@@ -437,11 +477,11 @@ export default function App() {
))}
-
- )}
-
+
+ )}
+
-
+
{learnStep > 0 && (
@@ -467,7 +507,7 @@ export default function App() {
color: rev?(ok?GREEN:RED):'#1a1a1a',
minWidth: q.type==='poss' ? 110 : 80,
}}>
- {rev ? q.ans : (inp || '\u00A0\u00A0\u00A0\u00A0\u00A0')}
+ {rev ? q.ans : (inp || ' ')}
)}
@@ -652,8 +692,8 @@ export default function App() {
)}
{/* SUMMARY */}
- {scr === 'summary' && (
-
+ {scr === 'summary' && (
+
Complete
{rc}
/{ROUND}
@@ -677,11 +717,11 @@ export default function App() {
-
- )}
+
+ )}
- {scr !== 'practice' &&
}
-
+ {scr !== 'practice' &&
}
+
{chart &&
setChart(false)}/>}