- Added a tabbed interface in VocabLessonView for 'Learn' and 'Exercises' sections, improving user navigation. - Implemented logic to display important vocabulary and cultural notes in the learning section. - Updated exercise result display to include correct answers and alternatives for better user feedback. - Enhanced VocabService to extract correct answers and alternatives from exercise data, supporting the new UI features. - Added new translations for vocabulary-related terms in both English and German, ensuring consistency across the application.
686 lines
20 KiB
Vue
686 lines
20 KiB
Vue
<template>
|
|
<div class="vocab-lesson-view">
|
|
<div v-if="loading">{{ $t('general.loading') }}</div>
|
|
<div v-else-if="lesson">
|
|
<div class="lesson-header">
|
|
<button @click="back" class="btn-back">{{ $t('general.back') }}</button>
|
|
<h2>{{ lesson.title }}</h2>
|
|
</div>
|
|
|
|
<p v-if="lesson.description" class="lesson-description">{{ lesson.description }}</p>
|
|
|
|
<!-- Tabs für Lernen und Übungen -->
|
|
<div class="lesson-tabs">
|
|
<button
|
|
:class="{ active: activeTab === 'learn' }"
|
|
@click="activeTab = 'learn'"
|
|
class="tab-button"
|
|
>
|
|
{{ $t('socialnetwork.vocab.courses.learn') }}
|
|
</button>
|
|
<button
|
|
:class="{ active: activeTab === 'exercises' }"
|
|
@click="activeTab = 'exercises'"
|
|
class="tab-button"
|
|
:disabled="!hasExercises"
|
|
>
|
|
{{ $t('socialnetwork.vocab.courses.exercises') }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Lernen-Tab -->
|
|
<div v-if="activeTab === 'learn'" class="learn-section">
|
|
<h3>{{ $t('socialnetwork.vocab.courses.learnVocabulary') }}</h3>
|
|
|
|
<!-- Kulturelle Notizen -->
|
|
<div v-if="lesson.culturalNotes" class="cultural-notes">
|
|
<h4>{{ $t('socialnetwork.vocab.courses.culturalNotes') }}</h4>
|
|
<p>{{ lesson.culturalNotes }}</p>
|
|
</div>
|
|
|
|
<!-- Wichtige Begriffe aus den Übungen -->
|
|
<div v-if="importantVocab.length > 0" class="vocab-list">
|
|
<h4>{{ $t('socialnetwork.vocab.courses.importantVocab') }}</h4>
|
|
<div class="vocab-items">
|
|
<div v-for="(vocab, index) in importantVocab" :key="index" class="vocab-item">
|
|
<strong>{{ vocab.learning }}</strong>
|
|
<span class="separator">→</span>
|
|
<span>{{ vocab.reference }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hinweis wenn keine Vokabeln vorhanden -->
|
|
<div v-else class="no-vocab-info">
|
|
<p>{{ $t('socialnetwork.vocab.courses.noVocabInfo') }}</p>
|
|
</div>
|
|
|
|
<!-- Button um zu Übungen zu wechseln -->
|
|
<div v-if="hasExercises" class="continue-to-exercises">
|
|
<button @click="activeTab = 'exercises'" class="btn-continue">
|
|
{{ $t('socialnetwork.vocab.courses.startExercises') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Übungen-Tab -->
|
|
<div v-if="activeTab === 'exercises' && lesson.grammarExercises && lesson.grammarExercises.length > 0" class="grammar-exercises">
|
|
<h3>{{ $t('socialnetwork.vocab.courses.grammarExercises') }}</h3>
|
|
<div v-for="exercise in lesson.grammarExercises" :key="exercise.id" class="exercise-item">
|
|
<h4>{{ exercise.title }}</h4>
|
|
<p v-if="exercise.instruction" class="exercise-instruction">{{ exercise.instruction }}</p>
|
|
|
|
<!-- Multiple Choice Übung -->
|
|
<div v-if="getExerciseType(exercise) === 'multiple_choice'" class="multiple-choice-exercise">
|
|
<p class="exercise-question">{{ getQuestionText(exercise) }}</p>
|
|
<div class="options">
|
|
<label v-for="(option, index) in getOptions(exercise)" :key="index" class="option-label">
|
|
<input
|
|
type="radio"
|
|
:name="'exercise-' + exercise.id"
|
|
:value="index"
|
|
v-model="exerciseAnswers[exercise.id]"
|
|
/>
|
|
<span>{{ option }}</span>
|
|
</label>
|
|
</div>
|
|
<button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id] && exerciseAnswers[exercise.id] !== 0">
|
|
{{ $t('socialnetwork.vocab.courses.checkAnswer') }}
|
|
</button>
|
|
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
|
<strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
|
|
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
|
|
{{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
|
|
</p>
|
|
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
|
|
{{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
|
|
</p>
|
|
<p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gap Fill Übung -->
|
|
<div v-else-if="getExerciseType(exercise) === 'gap_fill'" class="gap-fill-exercise">
|
|
<p class="exercise-text" v-html="formatGapFill(exercise)"></p>
|
|
<div class="gap-inputs">
|
|
<input
|
|
v-for="(gap, index) in getGapCount(exercise)"
|
|
:key="index"
|
|
v-model="exerciseAnswers[exercise.id][index]"
|
|
:placeholder="$t('socialnetwork.vocab.courses.enterAnswer')"
|
|
class="gap-input"
|
|
/>
|
|
</div>
|
|
<button @click="checkAnswer(exercise.id)" :disabled="!hasAllGapsFilled(exercise)">
|
|
{{ $t('socialnetwork.vocab.courses.checkAnswer') }}
|
|
</button>
|
|
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
|
<strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
|
|
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
|
|
{{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
|
|
</p>
|
|
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
|
|
{{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
|
|
</p>
|
|
<p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transformation Übung -->
|
|
<div v-else-if="getExerciseType(exercise) === 'transformation'" class="transformation-exercise">
|
|
<p class="exercise-question">{{ getQuestionText(exercise) }}</p>
|
|
<input
|
|
v-model="exerciseAnswers[exercise.id]"
|
|
:placeholder="$t('socialnetwork.vocab.courses.enterAnswer')"
|
|
class="transformation-input"
|
|
/>
|
|
<button @click="checkAnswer(exercise.id)" :disabled="!exerciseAnswers[exercise.id]">
|
|
{{ $t('socialnetwork.vocab.courses.checkAnswer') }}
|
|
</button>
|
|
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
|
<strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
|
|
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
|
|
{{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
|
|
</p>
|
|
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
|
|
{{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
|
|
</p>
|
|
<p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fallback für unbekannte Typen -->
|
|
<div v-else class="unknown-exercise">
|
|
<p>Übungstyp: {{ getExerciseType(exercise) }}</p>
|
|
<pre>{{ JSON.stringify(exercise, null, 2) }}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<p>{{ $t('socialnetwork.vocab.courses.noExercises') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapGetters } from 'vuex';
|
|
import apiClient from '@/utils/axios.js';
|
|
|
|
export default {
|
|
name: 'VocabLessonView',
|
|
props: {
|
|
courseId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
lessonId: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
lesson: null,
|
|
exerciseAnswers: {},
|
|
exerciseResults: {},
|
|
activeTab: 'learn' // Standardmäßig "Lernen"-Tab
|
|
};
|
|
},
|
|
computed: {
|
|
...mapGetters(['user']),
|
|
hasExercises() {
|
|
return this.lesson && this.lesson.grammarExercises && this.lesson.grammarExercises.length > 0;
|
|
},
|
|
importantVocab() {
|
|
// Extrahiere wichtige Begriffe aus den Übungen
|
|
if (!this.lesson || !this.lesson.grammarExercises) return [];
|
|
|
|
const vocabMap = new Map();
|
|
|
|
this.lesson.grammarExercises.forEach(exercise => {
|
|
// Extrahiere aus questionData
|
|
const qData = this.getQuestionData(exercise);
|
|
const aData = this.getAnswerData(exercise);
|
|
|
|
if (qData && aData) {
|
|
// Für Multiple Choice: Extrahiere Optionen und richtige Antwort
|
|
if (this.getExerciseType(exercise) === 'multiple_choice') {
|
|
const correct = Array.isArray(aData.correct) ? aData.correct[0] : aData.correct;
|
|
const question = qData.text || '';
|
|
|
|
// Versuche die Frage zu analysieren (z.B. "Wie sagt man X auf Bisaya?")
|
|
const match = question.match(/['"]([^'"]+)['"]/);
|
|
if (match) {
|
|
const germanWord = match[1];
|
|
vocabMap.set(correct, { learning: correct, reference: germanWord });
|
|
} else if (correct) {
|
|
// Fallback: Verwende die richtige Antwort als Lernwort
|
|
vocabMap.set(correct, { learning: correct, reference: correct });
|
|
}
|
|
}
|
|
|
|
// Für Gap Fill: Extrahiere richtige Antworten
|
|
if (this.getExerciseType(exercise) === 'gap_fill' && aData.correct) {
|
|
const correct = Array.isArray(aData.correct) ? aData.correct[0] : aData.correct;
|
|
vocabMap.set(correct, { learning: correct, reference: correct });
|
|
}
|
|
}
|
|
});
|
|
|
|
return Array.from(vocabMap.values());
|
|
}
|
|
},
|
|
computed: {
|
|
...mapGetters(['user'])
|
|
},
|
|
watch: {
|
|
courseId() {
|
|
this.loadLesson();
|
|
},
|
|
lessonId() {
|
|
this.loadLesson();
|
|
}
|
|
},
|
|
methods: {
|
|
async loadLesson() {
|
|
this.loading = true;
|
|
// Setze Antworten und Ergebnisse zurück
|
|
this.exerciseAnswers = {};
|
|
this.exerciseResults = {};
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
|
|
this.lesson = res.data;
|
|
// Initialisiere Übungen aus der Lektion oder lade sie separat
|
|
if (this.lesson && this.lesson.id) {
|
|
if (this.lesson.grammarExercises && this.lesson.grammarExercises.length > 0) {
|
|
// Übungen sind bereits in der Lektion enthalten
|
|
this.initializeExercises(this.lesson.grammarExercises);
|
|
} else {
|
|
// Lade Übungen separat (Fallback)
|
|
await this.loadGrammarExercises();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Konnte Lektion nicht laden:', e);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
initializeExercises(exercises) {
|
|
// Initialisiere Antwort-Arrays für Gap Fill Übungen
|
|
exercises.forEach(exercise => {
|
|
const exerciseType = this.getExerciseType(exercise);
|
|
if (exerciseType === 'gap_fill') {
|
|
const gapCount = this.getGapCount(exercise);
|
|
this.exerciseAnswers[exercise.id] = new Array(gapCount).fill('');
|
|
} else {
|
|
this.exerciseAnswers[exercise.id] = '';
|
|
}
|
|
this.exerciseResults[exercise.id] = null;
|
|
});
|
|
},
|
|
async loadGrammarExercises() {
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}/grammar-exercises`);
|
|
const exercises = res.data || [];
|
|
this.lesson.grammarExercises = exercises;
|
|
this.initializeExercises(exercises);
|
|
} catch (e) {
|
|
console.error('Konnte Grammatik-Übungen nicht laden:', e);
|
|
this.lesson.grammarExercises = [];
|
|
}
|
|
},
|
|
getExerciseType(exercise) {
|
|
// Hole den Typ aus questionData oder exerciseType
|
|
if (exercise.questionData) {
|
|
const qData = typeof exercise.questionData === 'string'
|
|
? JSON.parse(exercise.questionData)
|
|
: exercise.questionData;
|
|
return qData.type;
|
|
}
|
|
// Fallback: Prüfe exerciseTypeId
|
|
const typeMap = {
|
|
1: 'gap_fill',
|
|
2: 'multiple_choice',
|
|
3: 'sentence_building',
|
|
4: 'transformation',
|
|
5: 'conjugation',
|
|
6: 'declension'
|
|
};
|
|
return typeMap[exercise.exerciseTypeId] || 'unknown';
|
|
},
|
|
getQuestionData(exercise) {
|
|
if (!exercise.questionData) return null;
|
|
return typeof exercise.questionData === 'string'
|
|
? JSON.parse(exercise.questionData)
|
|
: exercise.questionData;
|
|
},
|
|
getAnswerData(exercise) {
|
|
if (!exercise.answerData) return null;
|
|
return typeof exercise.answerData === 'string'
|
|
? JSON.parse(exercise.answerData)
|
|
: exercise.answerData;
|
|
},
|
|
getQuestionText(exercise) {
|
|
const qData = this.getQuestionData(exercise);
|
|
if (!qData) return exercise.title;
|
|
|
|
if (qData.question) return qData.question;
|
|
if (qData.text) return qData.text;
|
|
return exercise.title;
|
|
},
|
|
getOptions(exercise) {
|
|
const qData = this.getQuestionData(exercise);
|
|
return qData?.options || [];
|
|
},
|
|
formatGapFill(exercise) {
|
|
const qData = this.getQuestionData(exercise);
|
|
if (!qData || !qData.text) return '';
|
|
|
|
// Ersetze {gap} mit Platzhaltern
|
|
return qData.text.replace(/\{gap\}/g, '<span class="gap">_____</span>');
|
|
},
|
|
getGapCount(exercise) {
|
|
const qData = this.getQuestionData(exercise);
|
|
if (!qData) return 0;
|
|
|
|
// Zähle {gap} im Text
|
|
const matches = qData.text?.match(/\{gap\}/g);
|
|
return matches ? matches.length : (qData.gaps || 1);
|
|
},
|
|
hasAllGapsFilled(exercise) {
|
|
const answers = this.exerciseAnswers[exercise.id];
|
|
if (!answers || !Array.isArray(answers)) return false;
|
|
const gapCount = this.getGapCount(exercise);
|
|
return answers.length === gapCount && answers.every(a => a && a.trim());
|
|
},
|
|
async checkAnswer(exerciseId) {
|
|
try {
|
|
const exercise = this.lesson.grammarExercises.find(e => e.id === exerciseId);
|
|
if (!exercise) return;
|
|
|
|
const exerciseType = this.getExerciseType(exercise);
|
|
let answer = this.exerciseAnswers[exerciseId];
|
|
|
|
// Formatiere Antwort je nach Typ
|
|
if (exerciseType === 'gap_fill') {
|
|
// Gap Fill: Array von Antworten
|
|
if (!Array.isArray(answer)) {
|
|
answer = [answer];
|
|
}
|
|
} else if (exerciseType === 'multiple_choice') {
|
|
// Multiple Choice: Index als Zahl
|
|
answer = Number(answer);
|
|
} else if (exerciseType === 'transformation') {
|
|
// Transformation: String
|
|
answer = String(answer || '').trim();
|
|
}
|
|
|
|
const res = await apiClient.post(`/api/vocab/grammar-exercises/${exerciseId}/check`, { answer });
|
|
this.exerciseResults[exerciseId] = res.data;
|
|
} catch (e) {
|
|
console.error('Fehler beim Prüfen der Antwort:', e);
|
|
alert(e.response?.data?.error || 'Fehler beim Prüfen der Antwort');
|
|
}
|
|
},
|
|
back() {
|
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`);
|
|
}
|
|
},
|
|
async mounted() {
|
|
await this.loadLesson();
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.vocab-lesson-view {
|
|
padding: 20px;
|
|
}
|
|
|
|
.lesson-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.btn-back {
|
|
padding: 8px 16px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background: white;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.lesson-description {
|
|
color: #666;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.grammar-exercises {
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.exercise-item {
|
|
background: white;
|
|
padding: 15px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.exercise-instruction {
|
|
color: #666;
|
|
font-style: italic;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.exercise-question {
|
|
font-weight: 600;
|
|
margin-bottom: 15px;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
.exercise-text {
|
|
margin-bottom: 15px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.gap {
|
|
display: inline-block;
|
|
min-width: 100px;
|
|
border-bottom: 2px solid #333;
|
|
margin: 0 5px;
|
|
padding: 0 5px;
|
|
}
|
|
|
|
.multiple-choice-exercise .options {
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.option-label {
|
|
display: block;
|
|
padding: 10px;
|
|
margin: 8px 0;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.option-label:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.option-label input[type="radio"] {
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.gap-fill-exercise .gap-inputs {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.gap-input {
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.transformation-input {
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.exercise-item button {
|
|
padding: 10px 20px;
|
|
background: #4CAF50;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
margin-top: 10px;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.exercise-item button:hover:not(:disabled) {
|
|
background: #45a049;
|
|
}
|
|
|
|
.exercise-item button:disabled {
|
|
background: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.exercise-result {
|
|
margin-top: 15px;
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.exercise-result.correct {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.exercise-result.wrong {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
.exercise-explanation {
|
|
margin-top: 10px;
|
|
font-style: italic;
|
|
}
|
|
|
|
.unknown-exercise {
|
|
padding: 15px;
|
|
background: #fff3cd;
|
|
border: 1px solid #ffc107;
|
|
border-radius: 4px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.unknown-exercise pre {
|
|
margin-top: 10px;
|
|
font-size: 0.85em;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
/* Tabs */
|
|
.lesson-tabs {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin: 20px 0;
|
|
border-bottom: 2px solid #ddd;
|
|
}
|
|
|
|
.tab-button {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
color: #666;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -2px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.tab-button:hover:not(:disabled) {
|
|
color: #333;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.tab-button.active {
|
|
color: #007bff;
|
|
border-bottom-color: #007bff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.tab-button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Lernen-Sektion */
|
|
.learn-section {
|
|
margin-top: 20px;
|
|
padding: 20px;
|
|
background: #f9f9f9;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.learn-section h3 {
|
|
margin-top: 0;
|
|
color: #333;
|
|
}
|
|
|
|
.cultural-notes {
|
|
margin: 20px 0;
|
|
padding: 15px;
|
|
background: #e7f3ff;
|
|
border-left: 4px solid #007bff;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.cultural-notes h4 {
|
|
margin-top: 0;
|
|
color: #007bff;
|
|
}
|
|
|
|
.vocab-list {
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.vocab-list h4 {
|
|
margin-bottom: 15px;
|
|
color: #333;
|
|
}
|
|
|
|
.vocab-items {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
|
|
.vocab-item {
|
|
padding: 10px;
|
|
background: white;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.vocab-item strong {
|
|
color: #007bff;
|
|
}
|
|
|
|
.separator {
|
|
color: #999;
|
|
}
|
|
|
|
.no-vocab-info {
|
|
padding: 15px;
|
|
background: #fff3cd;
|
|
border-left: 4px solid #ffc107;
|
|
border-radius: 4px;
|
|
color: #856404;
|
|
}
|
|
|
|
.continue-to-exercises {
|
|
margin-top: 30px;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn-continue {
|
|
padding: 12px 24px;
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-size: 1.1em;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.btn-continue:hover {
|
|
background: #0056b3;
|
|
}
|
|
</style>
|