diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 63cdc41..a5e0d45 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -1156,10 +1156,52 @@ export default { if (!this.hasPreviousVocab) { return 0; } + const lessonType = String(this.lesson?.lessonType || '').toLowerCase(); + const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase(); + const isReviewLesson = ['review', 'vocab_review'].includes(lessonType) + || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode); + + if (isReviewLesson) { + // In Wiederholungslektionen soll altes Material frueher und staerker einfliesen. + // Start: ~35%, Ramp-up bis max ~75%. + const blendStart = Math.max(2, Math.ceil(this.trainerNewFocusTarget * 0.2)); + const rampWindow = Math.max(6, Math.ceil(this.trainerNewFocusTarget * 0.5)); + const progressPastBlendStart = Math.max(0, this.vocabTrainerCurrentAttempts - blendStart); + const normalizedRamp = Math.min(1, progressPastBlendStart / rampWindow); + return Math.min(0.75, 0.35 + (normalizedRamp * 0.4)); + } + const progressPastBlendStart = Math.max(0, this.vocabTrainerCurrentAttempts - this.trainerReviewBlendStart); const normalizedRamp = Math.min(1, progressPastBlendStart / this.trainerReviewRampWindow); return Math.min(0.55, normalizedRamp * 0.55); }, + typingCurrentLessonShare() { + if (!this.hasPreviousVocab) { + return 1; + } + const lessonType = String(this.lesson?.lessonType || '').toLowerCase(); + const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase(); + const isReviewLesson = ['review', 'vocab_review'].includes(lessonType) + || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode); + + if (isReviewLesson) { + return Math.max(0.25, 1 - this.currentReviewShare); + } + + // Normale Lektion: Progressives Streuen im Typing-Modus + // Start 100% aktuelle Lektion -> dann 90% -> später 50%. + const startRampAt = Math.max(3, Math.ceil(this.trainerNewFocusTarget * 0.3)); + const midRampAt = Math.max(startRampAt + 1, Math.ceil(this.trainerNewFocusTarget * 0.6)); + const attempts = Math.max(0, Number(this.vocabTrainerCurrentAttempts) || 0); + + if (attempts < startRampAt) { + return 1; + } + if (attempts < midRampAt) { + return 0.9; + } + return 0.5; + }, canAccessExercises() { if (!this.hasExercises) return false; if (this.exerciseNeedsReinforcement) return false; @@ -3349,10 +3391,19 @@ export default { this.vocabTrainerPhase = this.hasPreviousVocab && this.currentReviewShare > 0 ? 'mixed' : 'current'; - if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= this.trainerExerciseUnlockAttempts) { + const lessonType = String(this.lesson?.lessonType || '').toLowerCase(); + const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase(); + const isReviewLesson = ['review', 'vocab_review'].includes(lessonType) + || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode); + const switchAfterAttempts = isReviewLesson + ? Math.max(8, Math.ceil(this.trainerExerciseUnlockAttempts * 0.35)) + : this.trainerExerciseUnlockAttempts; + const requiredSuccessRate = isReviewLesson ? 75 : 80; + + if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= switchAfterAttempts) { const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100; - if (successRate >= 80) { - debugLog('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing'); + if (successRate >= requiredSuccessRate) { + debugLog('[VocabLessonView] Wechsle zu Typing (Abruf staerken)'); this.vocabTrainerMode = 'typing'; this.vocabTrainerAutoSwitchedToTyping = true; this.vocabTrainerPool = [...this.trainableLessonVocab, ...this.vocabTrainerMixedPool]; @@ -3508,9 +3559,13 @@ export default { } if (!dueRepeatVocab && this.vocabTrainerMode === 'typing') { - sourcePool = this.vocabTrainerPool; - if (this.vocabTrainerMixedPool.length > 0 && Math.random() < 0.35) { + const typingReviewShare = Math.max(0, 1 - this.typingCurrentLessonShare); + if (this.vocabTrainerMixedPool.length > 0 && typingReviewShare > 0 && Math.random() < typingReviewShare) { + sourcePool = this.vocabTrainerMixedPool; questionSource = 'review'; + } else { + sourcePool = this.trainableLessonVocab; + questionSource = 'current'; } } else if (!dueRepeatVocab && this.vocabTrainerMixedPool.length > 0 && this.currentReviewShare > 0 && Math.random() < this.currentReviewShare) { sourcePool = this.vocabTrainerMixedPool;