Class_generator/js/games/chinese-study.js
StillHammer 30a2028da6 Remove Text Reader game and enhance Story Reader
- Enhanced Story Reader with text-to-story conversion methods
- Added support for simple texts and sentences in Story Reader
- Removed Text Reader game file (js/games/text-reader.js)
- Updated all configuration files to remove Text Reader references
- Modified game compatibility system to use Story Reader instead
- Updated test fixtures to reflect game changes
- Cleaned up debug/test HTML files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-20 11:22:56 +08:00

1585 lines
51 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === CHINESE STUDY MODE ===
class ChineseStudyGame {
constructor(options) {
this.container = options.container;
this.content = options.content;
this.onScoreUpdate = options.onScoreUpdate || (() => {});
this.onGameEnd = options.onGameEnd || (() => {});
// Game state
this.vocabulary = [];
this.currentMode = null;
this.currentIndex = 0;
this.score = 0;
this.correctAnswers = 0;
this.isRunning = false;
this.studyState = 'menu'; // 'menu', 'playing', 'review'
// Extract vocabulary
this.vocabulary = this.extractVocabulary(this.content);
this.init();
}
init() {
// Check if we have enough vocabulary
if (!this.vocabulary || this.vocabulary.length === 0) {
logSh('No Chinese vocabulary found for Chinese Study Game', 'ERROR');
this.showInitError();
return;
}
this.createGameInterface();
}
showInitError() {
this.container.innerHTML = `
<div class="game-error">
<h3>❌ Error loading</h3>
<p>This content doesn't have Chinese vocabulary for the Chinese Study Game.</p>
<p>The game needs vocabulary with Chinese characters, translations, and optional pinyin.</p>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn">← Back</button>
</div>
`;
this.addStyles();
}
extractVocabulary(content) {
let vocabulary = [];
logSh('🔍 Extracting Chinese vocabulary from:', content?.name || 'content', 'INFO');
// Priority 1: Use raw module content (simple format)
if (content.rawContent) {
logSh('📦 Using raw module content', 'INFO');
return this.extractVocabularyFromRaw(content.rawContent);
}
// Priority 2: Ultra-modular format (vocabulary object) - ONLY format supported
if (content.vocabulary && typeof content.vocabulary === 'object' && !Array.isArray(content.vocabulary)) {
logSh('✨ Ultra-modular format detected (vocabulary object)', 'INFO');
vocabulary = Object.entries(content.vocabulary).map(([word, data]) => {
// Support ultra-modular format ONLY
if (typeof data === 'object' && data.user_language) {
return {
chinese: word, // Clé = caractère chinois
translation: data.user_language.split('')[0], // First translation
fullTranslation: data.user_language, // Complete translation
pronunciation: data.pronunciation || '', // Pinyin
type: data.type || 'general',
hskLevel: data.hskLevel || null,
examples: data.examples || [],
strokeOrder: data.strokeOrder || []
};
}
// Legacy fallback - simple string (temporary, will be removed)
else if (typeof data === 'string') {
return {
chinese: word,
translation: data.split('')[0],
fullTranslation: data,
pronunciation: '',
type: 'general',
hskLevel: null
};
}
return null;
}).filter(Boolean);
}
// No other formats supported - ultra-modular only
return this.finalizeVocabulary(vocabulary);
}
extractVocabularyFromRaw(rawContent) {
logSh('🔧 Extracting from raw content:', rawContent.name || 'Module', 'INFO');
let vocabulary = [];
// Extract from vocabulary object in raw content
if (rawContent.vocabulary && typeof rawContent.vocabulary === 'object') {
vocabulary = Object.entries(rawContent.vocabulary).map(([word, data]) => {
if (typeof data === 'object' && data.user_language) {
return {
chinese: word,
translation: data.user_language.split('')[0],
fullTranslation: data.user_language,
pronunciation: data.pronunciation || '',
type: data.type || 'general',
hskLevel: data.hskLevel || null,
examples: data.examples || [],
strokeOrder: data.strokeOrder || []
};
} else if (typeof data === 'string') {
return {
chinese: word,
translation: data.split('')[0],
fullTranslation: data,
pronunciation: '',
type: 'general',
hskLevel: null
};
}
return null;
}).filter(Boolean);
}
return vocabulary;
}
finalizeVocabulary(vocabulary) {
if (vocabulary.length === 0) {
logSh('⚠️ No valid vocabulary found', 'WARNING');
return [];
}
// Shuffle vocabulary
vocabulary = vocabulary.sort(() => Math.random() - 0.5);
logSh(`✅ Vocabulary extraction complete: ${vocabulary.length} items`, 'INFO');
return vocabulary;
}
createGameInterface() {
if (this.studyState === 'menu') {
this.createModeSelection();
} else if (this.studyState === 'playing') {
this.createStudyMode();
}
this.addStyles();
}
createModeSelection() {
const hasPinyin = this.vocabulary.some(item => item.pronunciation);
const hasHSK = this.vocabulary.some(item => item.hskLevel);
this.container.innerHTML = `
<div class="chinese-study-container">
<div class="game-header">
<h2>🇨🇳 Chinese Study Mode</h2>
<div class="game-stats">
<div class="score-display">Score: <span id="score">${this.score}</span></div>
<div class="vocab-count">${this.vocabulary.length} characters available</div>
${hasHSK ? '<div class="hsk-indicator">📊 HSK levels included</div>' : ''}
${hasPinyin ? '<div class="pinyin-indicator">🗣️ Pinyin available</div>' : ''}
</div>
</div>
<div class="study-modes">
<div class="mode-card" data-mode="flashcards">
<div class="mode-icon">📚</div>
<h3>Flashcards</h3>
<p>Study characters with flip cards</p>
<button class="mode-btn">Start Learning</button>
</div>
<div class="mode-card" data-mode="recognition" ${!hasPinyin ? 'data-disabled="true"' : ''}>
<div class="mode-icon">🧠</div>
<h3>Character Recognition</h3>
<p>Match characters to their meanings</p>
${!hasPinyin ? '<div class="mode-requirement">Requires pinyin data</div>' : ''}
<button class="mode-btn" ${!hasPinyin ? 'disabled' : ''}>Start Learning</button>
</div>
<div class="mode-card" data-mode="pinyin" ${!hasPinyin ? 'data-disabled="true"' : ''}>
<div class="mode-icon">🗣️</div>
<h3>Pinyin Practice</h3>
<p>Learn pronunciation with pinyin</p>
${!hasPinyin ? '<div class="mode-requirement">Requires pinyin data</div>' : ''}
<button class="mode-btn" ${!hasPinyin ? 'disabled' : ''}>Start Learning</button>
</div>
<div class="mode-card" data-mode="hsk" ${!hasHSK ? 'data-disabled="true"' : ''}>
<div class="mode-icon">📊</div>
<h3>HSK Review</h3>
<p>Study by HSK difficulty levels</p>
${!hasHSK ? '<div class="mode-requirement">Requires HSK level data</div>' : ''}
<button class="mode-btn" ${!hasHSK ? 'disabled' : ''}>Start Learning</button>
</div>
</div>
<div class="vocabulary-preview">
<h4>📖 Vocabulary Preview</h4>
<div class="preview-items">
${this.vocabulary.slice(0, 6).map(item => `
<div class="preview-item">
<span class="chinese">${item.chinese}</span>
<span class="translation">${item.translation}</span>
${item.pronunciation ? `<span class="pinyin">${item.pronunciation}</span>` : ''}
${item.hskLevel ? `<span class="hsk-badge">${item.hskLevel}</span>` : ''}
</div>
`).join('')}
${this.vocabulary.length > 6 ? `<div class="more-items">... and ${this.vocabulary.length - 6} more</div>` : ''}
</div>
</div>
<div class="game-controls">
<button onclick="AppNavigation.navigateTo('games')" class="back-btn">← Back to Games</button>
</div>
</div>
`;
this.setupModeListeners();
}
createStudyMode() {
const currentItem = this.vocabulary[this.currentIndex];
const progress = Math.round(((this.currentIndex + 1) / this.vocabulary.length) * 100);
let modeContent = '';
switch (this.currentMode) {
case 'flashcards':
modeContent = this.createFlashcardMode(currentItem);
break;
case 'recognition':
modeContent = this.createRecognitionMode(currentItem);
break;
case 'pinyin':
modeContent = this.createPinyinMode(currentItem);
break;
case 'hsk':
modeContent = this.createHSKMode(currentItem);
break;
}
this.container.innerHTML = `
<div class="chinese-study-container study-mode-active">
<div class="study-header">
<div class="mode-title">
<h2>${this.getModeTitle()}</h2>
<button class="back-to-menu-btn" onclick="this.backToMenu()">← Menu</button>
</div>
<div class="progress-section">
<div class="progress-bar">
<div class="progress-fill" style="width: ${progress}%"></div>
</div>
<div class="progress-text">${this.currentIndex + 1} / ${this.vocabulary.length}</div>
<div class="score-display">Score: <span id="score">${this.score}</span></div>
</div>
</div>
<div class="study-content">
${modeContent}
</div>
<div class="study-controls">
<button class="prev-btn" onclick="this.previousItem()" ${this.currentIndex === 0 ? 'disabled' : ''}>
← Previous
</button>
<button class="next-btn" onclick="this.nextItem()" ${this.currentIndex === this.vocabulary.length - 1 ? 'disabled' : ''}>
Next →
</button>
</div>
</div>
`;
this.setupStudyListeners();
}
setupModeListeners() {
const modeCards = this.container.querySelectorAll('.mode-card:not([data-disabled])');
modeCards.forEach(card => {
card.addEventListener('click', (e) => {
const mode = card.dataset.mode;
this.startMode(mode);
});
});
}
setupStudyListeners() {
// Bind this context to methods for onclick handlers
window.chineseStudyInstance = this;
// Override global onclick handlers
this.container.querySelector('.back-to-menu-btn').onclick = () => this.backToMenu();
this.container.querySelector('.prev-btn').onclick = () => this.previousItem();
this.container.querySelector('.next-btn').onclick = () => this.nextItem();
// Setup mode-specific listeners
this.setupModeSpecificListeners();
}
setupModeSpecificListeners() {
if (this.currentMode === 'flashcards') {
const flashcard = this.container.querySelector('.flashcard');
if (flashcard) {
flashcard.addEventListener('click', () => this.flipCard());
}
} else if (this.currentMode === 'recognition') {
const options = this.container.querySelectorAll('.option-btn');
options.forEach(option => {
option.addEventListener('click', (e) => this.selectOption(e.target.dataset.translation));
});
} else if (this.currentMode === 'pinyin') {
const pinyinInput = this.container.querySelector('.pinyin-input');
if (pinyinInput) {
pinyinInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.checkPinyinAnswer();
});
}
const checkBtn = this.container.querySelector('.check-pinyin-btn');
if (checkBtn) {
checkBtn.onclick = () => this.checkPinyinAnswer();
}
}
}
startMode(mode) {
this.currentMode = mode;
this.studyState = 'playing';
this.currentIndex = 0;
this.correctAnswers = 0;
this.createGameInterface();
}
backToMenu() {
this.studyState = 'menu';
this.currentMode = null;
this.createGameInterface();
}
getModeTitle() {
const titles = {
flashcards: '📚 Flashcards',
recognition: '🧠 Character Recognition',
pinyin: '🗣️ Pinyin Practice',
hsk: '📊 HSK Review'
};
return titles[this.currentMode] || 'Chinese Study';
}
createFlashcardMode(item) {
return `
<div class="flashcard-container">
<div class="flashcard" data-flipped="false">
<div class="flashcard-front">
<div class="chinese-character">${item.chinese}</div>
<div class="card-hint">Click to reveal translation</div>
</div>
<div class="flashcard-back">
<div class="translation">${item.translation}</div>
${item.pronunciation ? `<div class="pronunciation">${item.pronunciation}</div>` : ''}
${item.type ? `<div class="word-type">${item.type}</div>` : ''}
${item.hskLevel ? `<div class="hsk-level">${item.hskLevel}</div>` : ''}
</div>
</div>
<div class="flashcard-actions">
<button class="know-btn" onclick="chineseStudyInstance.markAsKnown(true)">✅ I know this</button>
<button class="dont-know-btn" onclick="chineseStudyInstance.markAsKnown(false)">❌ Need practice</button>
</div>
</div>
`;
}
createRecognitionMode(item) {
// Create wrong options
const wrongOptions = this.vocabulary
.filter(v => v.chinese !== item.chinese)
.sort(() => Math.random() - 0.5)
.slice(0, 3)
.map(v => v.translation);
const allOptions = [...wrongOptions, item.translation].sort(() => Math.random() - 0.5);
return `
<div class="recognition-container">
<div class="question-section">
<div class="chinese-character">${item.chinese}</div>
${item.pronunciation ? `<div class="pronunciation-hint">${item.pronunciation}</div>` : ''}
<div class="question-text">What does this character mean?</div>
</div>
<div class="options-grid">
${allOptions.map(option => `
<button class="option-btn" data-translation="${option}">
${option}
</button>
`).join('')}
</div>
<div class="result-feedback" style="display: none;"></div>
</div>
`;
}
createPinyinMode(item) {
return `
<div class="pinyin-container">
<div class="character-section">
<div class="chinese-character">${item.chinese}</div>
<div class="translation-hint">${item.translation}</div>
</div>
<div class="pinyin-exercise">
<label class="pinyin-label">Enter the pinyin pronunciation:</label>
<input type="text" class="pinyin-input" placeholder="Type pinyin here..." />
<button class="check-pinyin-btn">Check Answer</button>
</div>
<div class="pinyin-feedback" style="display: none;"></div>
${item.pronunciation ? `<div class="correct-answer" style="display: none;">Correct: ${item.pronunciation}</div>` : ''}
</div>
`;
}
createHSKMode(item) {
const hskInfo = item.hskLevel || 'No HSK level';
return `
<div class="hsk-container">
<div class="hsk-header">
<div class="hsk-level-badge">${hskInfo}</div>
<div class="character-difficulty">Chinese Character Study</div>
</div>
<div class="character-study">
<div class="chinese-character">${item.chinese}</div>
<div class="character-details">
<div class="translation">${item.translation}</div>
${item.pronunciation ? `<div class="pronunciation">${item.pronunciation}</div>` : ''}
${item.type ? `<div class="word-type">Type: ${item.type}</div>` : ''}
${item.examples && item.examples.length > 0 ? `
<div class="examples">
<h5>Examples:</h5>
${item.examples.slice(0, 2).map(ex => `<div class="example">${ex}</div>`).join('')}
</div>
` : ''}
</div>
</div>
<div class="hsk-actions">
<button class="difficulty-btn easy" onclick="chineseStudyInstance.markDifficulty('easy')">😊 Easy</button>
<button class="difficulty-btn medium" onclick="chineseStudyInstance.markDifficulty('medium')">🤔 Medium</button>
<button class="difficulty-btn hard" onclick="chineseStudyInstance.markDifficulty('hard')">😅 Hard</button>
</div>
</div>
`;
}
// Navigation methods
nextItem() {
if (this.currentIndex < this.vocabulary.length - 1) {
this.currentIndex++;
this.createStudyMode();
}
}
previousItem() {
if (this.currentIndex > 0) {
this.currentIndex--;
this.createStudyMode();
}
}
// Flashcard methods
flipCard() {
const flashcard = this.container.querySelector('.flashcard');
const isFlipped = flashcard.dataset.flipped === 'true';
flashcard.dataset.flipped = (!isFlipped).toString();
}
markAsKnown(known) {
const points = known ? 10 : 5;
this.score += points;
this.correctAnswers += known ? 1 : 0;
this.onScoreUpdate(this.score);
this.updateScoreDisplay();
// Auto-advance after a short delay
setTimeout(() => {
if (this.currentIndex < this.vocabulary.length - 1) {
this.nextItem();
} else {
this.endStudySession();
}
}, 1000);
}
// Recognition mode methods
selectOption(selectedTranslation) {
const currentItem = this.vocabulary[this.currentIndex];
const isCorrect = selectedTranslation === currentItem.translation;
const feedback = this.container.querySelector('.result-feedback');
if (isCorrect) {
this.score += 15;
this.correctAnswers++;
feedback.innerHTML = '✅ Correct! Well done!';
feedback.className = 'result-feedback correct';
} else {
this.score = Math.max(0, this.score - 5);
feedback.innerHTML = `❌ Incorrect. The correct answer is: ${currentItem.translation}`;
feedback.className = 'result-feedback incorrect';
}
feedback.style.display = 'block';
this.onScoreUpdate(this.score);
this.updateScoreDisplay();
// Disable all option buttons
const options = this.container.querySelectorAll('.option-btn');
options.forEach(btn => btn.disabled = true);
// Auto-advance after a delay
setTimeout(() => {
if (this.currentIndex < this.vocabulary.length - 1) {
this.nextItem();
} else {
this.endStudySession();
}
}, 2000);
}
// Pinyin mode methods
checkPinyinAnswer() {
const input = this.container.querySelector('.pinyin-input');
const userAnswer = input.value.trim().toLowerCase();
const currentItem = this.vocabulary[this.currentIndex];
const correctPinyin = currentItem.pronunciation ? currentItem.pronunciation.toLowerCase() : '';
const feedback = this.container.querySelector('.pinyin-feedback');
const correctAnswer = this.container.querySelector('.correct-answer');
if (correctPinyin && this.normalizePinyin(userAnswer) === this.normalizePinyin(correctPinyin)) {
this.score += 20;
this.correctAnswers++;
feedback.innerHTML = '🎉 Excellent pronunciation!';
feedback.className = 'pinyin-feedback correct';
} else {
this.score = Math.max(0, this.score - 3);
feedback.innerHTML = '🤔 Not quite right. Try again or see the correct answer below.';
feedback.className = 'pinyin-feedback incorrect';
if (correctAnswer) correctAnswer.style.display = 'block';
}
feedback.style.display = 'block';
input.disabled = true;
this.container.querySelector('.check-pinyin-btn').disabled = true;
this.onScoreUpdate(this.score);
this.updateScoreDisplay();
// Auto-advance after a delay
setTimeout(() => {
if (this.currentIndex < this.vocabulary.length - 1) {
this.nextItem();
} else {
this.endStudySession();
}
}, 3000);
}
normalizePinyin(pinyin) {
// Remove tone marks and accents for easier comparison
return pinyin.replace(/[āáǎàēéěèīíǐìōóǒòūúǔùüǖǘǚǜ]/g, (match) => {
const toneMap = {
'ā': 'a', 'á': 'a', 'ǎ': 'a', 'à': 'a',
'ē': 'e', 'é': 'e', 'ě': 'e', 'è': 'e',
'ī': 'i', 'í': 'i', 'ǐ': 'i', 'ì': 'i',
'ō': 'o', 'ó': 'o', 'ǒ': 'o', 'ò': 'o',
'ū': 'u', 'ú': 'u', 'ǔ': 'u', 'ù': 'u',
'ü': 'u', 'ǖ': 'u', 'ǘ': 'u', 'ǚ': 'u', 'ǜ': 'u'
};
return toneMap[match] || match;
}).replace(/\s+/g, '');
}
// HSK mode methods
markDifficulty(difficulty) {
const points = {
'easy': 5,
'medium': 8,
'hard': 12
};
this.score += points[difficulty];
this.correctAnswers++;
this.onScoreUpdate(this.score);
this.updateScoreDisplay();
// Visual feedback
const buttons = this.container.querySelectorAll('.difficulty-btn');
buttons.forEach(btn => btn.disabled = true);
const selectedBtn = this.container.querySelector(`.difficulty-btn.${difficulty}`);
selectedBtn.style.backgroundColor = '#10b981';
selectedBtn.style.color = 'white';
// Auto-advance after a delay
setTimeout(() => {
if (this.currentIndex < this.vocabulary.length - 1) {
this.nextItem();
} else {
this.endStudySession();
}
}, 1500);
}
updateScoreDisplay() {
const scoreElement = this.container.querySelector('#score');
if (scoreElement) {
scoreElement.textContent = this.score;
}
}
endStudySession() {
const accuracy = Math.round((this.correctAnswers / this.vocabulary.length) * 100);
this.container.innerHTML = `
<div class="chinese-study-container">
<div class="study-complete">
<h2>🎓 Study Session Complete!</h2>
<div class="final-stats">
<div class="stat-item">
<div class="stat-value">${this.score}</div>
<div class="stat-label">Final Score</div>
</div>
<div class="stat-item">
<div class="stat-value">${this.correctAnswers}/${this.vocabulary.length}</div>
<div class="stat-label">Correct</div>
</div>
<div class="stat-item">
<div class="stat-value">${accuracy}%</div>
<div class="stat-label">Accuracy</div>
</div>
</div>
<div class="completion-message">
${accuracy >= 80 ? '🌟 Excellent work! You\'ve mastered these characters!' :
accuracy >= 60 ? '👍 Good job! Keep practicing to improve further.' :
'💪 Nice effort! More practice will help you improve.'}
</div>
<div class="final-actions">
<button onclick="chineseStudyInstance.restart()" class="restart-btn">🔄 Study Again</button>
<button onclick="chineseStudyInstance.backToMenu()" class="menu-btn">📚 Back to Modes</button>
<button onclick="AppNavigation.navigateTo('games')" class="back-btn">← Back to Games</button>
</div>
</div>
</div>
`;
this.addStyles();
// Trigger game end callback
this.onGameEnd({
score: this.score,
accuracy: accuracy,
mode: this.currentMode,
totalItems: this.vocabulary.length,
correctAnswers: this.correctAnswers
});
}
addStyles() {
const style = document.createElement('style');
style.textContent = `
.chinese-study-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
font-family: 'Arial', sans-serif;
}
.game-header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 20px;
}
.game-header h2 {
color: #dc2626;
font-size: 2.2em;
margin-bottom: 15px;
}
.game-stats {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
margin-top: 10px;
}
.score-display, .vocab-count, .hsk-indicator, .pinyin-indicator {
font-size: 1em;
padding: 5px 12px;
border-radius: 20px;
font-weight: bold;
}
.score-display {
background: #10b981;
color: white;
}
.vocab-count {
background: #3b82f6;
color: white;
}
.hsk-indicator, .pinyin-indicator {
background: #f59e0b;
color: white;
font-size: 0.9em;
}
.study-modes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.mode-card {
background: linear-gradient(135deg, #fff 0%, #f8fafc 100%);
border: 2px solid #e5e7eb;
border-radius: 16px;
padding: 24px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
position: relative;
}
.mode-card:not([data-disabled]):hover {
border-color: #dc2626;
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(220, 38, 38, 0.15);
}
.mode-card[data-disabled="true"] {
opacity: 0.6;
cursor: not-allowed;
background: #f9fafb;
}
.mode-requirement {
background: #fee2e2;
color: #dc2626;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8em;
margin-bottom: 10px;
}
.mode-icon {
font-size: 3em;
margin-bottom: 12px;
}
.mode-card h3 {
color: #374151;
margin-bottom: 8px;
font-size: 1.3em;
}
.mode-card p {
color: #6b7280;
margin-bottom: 16px;
line-height: 1.5;
}
.mode-btn {
background: #dc2626;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: background 0.3s ease;
}
.mode-btn:hover:not(:disabled) {
background: #b91c1c;
}
.mode-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.vocabulary-preview {
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 20px;
margin-bottom: 30px;
}
.vocabulary-preview h4 {
color: #374151;
margin-bottom: 15px;
text-align: center;
}
.preview-items {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.preview-item {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 4px;
}
.preview-item .chinese {
font-size: 1.4em;
font-weight: bold;
color: #dc2626;
}
.preview-item .translation {
color: #374151;
font-size: 0.9em;
}
.preview-item .pinyin {
color: #6b7280;
font-size: 0.8em;
font-style: italic;
}
.preview-item .hsk-badge {
background: #f59e0b;
color: white;
padding: 2px 6px;
border-radius: 10px;
font-size: 0.7em;
font-weight: bold;
}
.more-items {
grid-column: 1/-1;
text-align: center;
color: #6b7280;
font-style: italic;
padding: 10px;
}
/* Study Mode Styles */
.study-mode-active {
max-width: 800px;
}
.study-header {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
color: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 30px;
}
.mode-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.mode-title h2 {
margin: 0;
font-size: 1.8em;
}
.back-to-menu-btn {
background: rgba(255,255,255,0.2);
color: white;
border: 1px solid rgba(255,255,255,0.3);
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s ease;
}
.back-to-menu-btn:hover {
background: rgba(255,255,255,0.3);
}
.progress-section {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
.progress-bar {
flex: 1;
min-width: 200px;
height: 8px;
background: rgba(255,255,255,0.3);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: white;
transition: width 0.3s ease;
}
.progress-text {
font-weight: bold;
min-width: 60px;
}
.study-content {
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 30px;
margin-bottom: 20px;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
/* Flashcard Styles */
.flashcard-container {
width: 100%;
max-width: 400px;
text-align: center;
}
.flashcard {
width: 100%;
height: 250px;
position: relative;
transform-style: preserve-3d;
transition: transform 0.6s;
cursor: pointer;
margin-bottom: 20px;
}
.flashcard[data-flipped="true"] {
transform: rotateY(180deg);
}
.flashcard-front, .flashcard-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border: 2px solid #e5e7eb;
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.flashcard-back {
transform: rotateY(180deg);
background: #f8fafc;
}
.chinese-character {
font-size: 4em;
font-weight: bold;
color: #dc2626;
margin-bottom: 10px;
}
.card-hint {
color: #6b7280;
font-style: italic;
}
.translation {
font-size: 1.5em;
color: #374151;
margin-bottom: 10px;
}
.pronunciation {
color: #6b7280;
font-style: italic;
margin-bottom: 8px;
}
.word-type, .hsk-level {
background: #e5e7eb;
color: #374151;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8em;
margin-bottom: 4px;
}
.hsk-level {
background: #f59e0b;
color: white;
}
.flashcard-actions {
display: flex;
gap: 15px;
justify-content: center;
}
.know-btn, .dont-know-btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.know-btn {
background: #10b981;
color: white;
}
.know-btn:hover {
background: #059669;
}
.dont-know-btn {
background: #f59e0b;
color: white;
}
.dont-know-btn:hover {
background: #d97706;
}
/* Recognition Mode Styles */
.recognition-container {
width: 100%;
text-align: center;
}
.question-section {
margin-bottom: 30px;
}
.pronunciation-hint {
color: #6b7280;
font-style: italic;
margin-bottom: 15px;
}
.question-text {
font-size: 1.2em;
color: #374151;
margin-bottom: 20px;
}
.options-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.option-btn {
background: #f8fafc;
border: 2px solid #e5e7eb;
border-radius: 8px;
padding: 15px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s ease;
}
.option-btn:hover:not(:disabled) {
border-color: #dc2626;
background: #fef2f2;
}
.option-btn:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.result-feedback {
padding: 15px;
border-radius: 8px;
font-weight: bold;
margin-bottom: 15px;
}
.result-feedback.correct {
background: #d1fae5;
color: #065f46;
border: 1px solid #10b981;
}
.result-feedback.incorrect {
background: #fee2e2;
color: #991b1b;
border: 1px solid #ef4444;
}
/* Pinyin Mode Styles */
.pinyin-container {
width: 100%;
text-align: center;
}
.character-section {
margin-bottom: 30px;
}
.translation-hint {
font-size: 1.2em;
color: #6b7280;
margin-top: 10px;
}
.pinyin-exercise {
margin-bottom: 20px;
}
.pinyin-label {
display: block;
font-weight: bold;
color: #374151;
margin-bottom: 10px;
}
.pinyin-input {
padding: 12px;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 1.1em;
width: 250px;
max-width: 100%;
margin-bottom: 15px;
margin-right: 10px;
}
.pinyin-input:focus {
outline: none;
border-color: #dc2626;
}
.check-pinyin-btn {
background: #dc2626;
color: white;
border: none;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
}
.check-pinyin-btn:hover:not(:disabled) {
background: #b91c1c;
}
.check-pinyin-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.pinyin-feedback {
padding: 15px;
border-radius: 8px;
font-weight: bold;
margin-bottom: 15px;
}
.pinyin-feedback.correct {
background: #d1fae5;
color: #065f46;
border: 1px solid #10b981;
}
.pinyin-feedback.incorrect {
background: #fee2e2;
color: #991b1b;
border: 1px solid #ef4444;
}
.correct-answer {
background: #f0f9ff;
color: #0c4a6e;
border: 1px solid #3b82f6;
padding: 10px;
border-radius: 8px;
font-weight: bold;
}
/* HSK Mode Styles */
.hsk-container {
width: 100%;
text-align: center;
}
.hsk-header {
margin-bottom: 30px;
}
.hsk-level-badge {
background: #f59e0b;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
display: inline-block;
margin-bottom: 10px;
}
.character-difficulty {
color: #6b7280;
font-size: 1em;
}
.character-study {
margin-bottom: 30px;
}
.character-details {
background: #f8fafc;
border-radius: 8px;
padding: 20px;
margin-top: 15px;
}
.character-details .translation {
font-size: 1.3em;
margin-bottom: 10px;
}
.character-details .word-type {
background: #e5e7eb;
color: #374151;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.9em;
display: inline-block;
margin-bottom: 15px;
}
.examples {
text-align: left;
margin-top: 15px;
}
.examples h5 {
color: #374151;
margin-bottom: 8px;
text-align: center;
}
.example {
background: white;
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 8px;
margin-bottom: 5px;
color: #6b7280;
}
.hsk-actions {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.difficulty-btn {
padding: 12px 20px;
border: 2px solid #e5e7eb;
border-radius: 8px;
background: white;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
min-width: 100px;
}
.difficulty-btn.easy {
border-color: #10b981;
color: #10b981;
}
.difficulty-btn.easy:hover:not(:disabled) {
background: #10b981;
color: white;
}
.difficulty-btn.medium {
border-color: #f59e0b;
color: #f59e0b;
}
.difficulty-btn.medium:hover:not(:disabled) {
background: #f59e0b;
color: white;
}
.difficulty-btn.hard {
border-color: #ef4444;
color: #ef4444;
}
.difficulty-btn.hard:hover:not(:disabled) {
background: #ef4444;
color: white;
}
.difficulty-btn:disabled {
cursor: not-allowed;
opacity: 0.7;
}
/* Study Controls */
.study-controls {
display: flex;
justify-content: space-between;
gap: 15px;
}
.prev-btn, .next-btn {
background: #6b7280;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: background 0.3s ease;
flex: 1;
max-width: 150px;
}
.prev-btn:hover:not(:disabled), .next-btn:hover:not(:disabled) {
background: #4b5563;
}
.prev-btn:disabled, .next-btn:disabled {
background: #9ca3af;
cursor: not-allowed;
}
/* Study Complete Styles */
.study-complete {
text-align: center;
padding: 40px 20px;
}
.study-complete h2 {
color: #dc2626;
font-size: 2.5em;
margin-bottom: 30px;
}
.final-stats {
display: flex;
justify-content: center;
gap: 30px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.stat-item {
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 20px;
min-width: 120px;
}
.stat-value {
font-size: 2em;
font-weight: bold;
color: #dc2626;
margin-bottom: 5px;
}
.stat-label {
color: #6b7280;
font-size: 0.9em;
}
.completion-message {
background: #f0f9ff;
border: 1px solid #3b82f6;
border-radius: 12px;
padding: 20px;
color: #1e40af;
font-size: 1.1em;
font-weight: bold;
margin-bottom: 30px;
}
.final-actions {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.restart-btn, .menu-btn, .back-btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: background 0.3s ease;
font-size: 1em;
}
.restart-btn {
background: #10b981;
color: white;
}
.restart-btn:hover {
background: #059669;
}
.menu-btn {
background: #3b82f6;
color: white;
}
.menu-btn:hover {
background: #2563eb;
}
.back-btn {
background: #6b7280;
color: white;
}
.back-btn:hover {
background: #4b5563;
}
.game-controls {
text-align: center;
}
.game-error {
text-align: center;
padding: 40px 20px;
background: #fee2e2;
border: 1px solid #ef4444;
border-radius: 12px;
}
.game-error h3 {
color: #991b1b;
margin-bottom: 15px;
}
.game-error p {
color: #7f1d1d;
margin-bottom: 10px;
}
@media (max-width: 768px) {
.chinese-study-container {
padding: 15px;
}
.game-header h2 {
font-size: 1.8em;
}
.study-modes {
grid-template-columns: 1fr;
}
.preview-items {
grid-template-columns: 1fr;
}
.game-stats {
flex-direction: column;
align-items: center;
gap: 10px;
}
.chinese-character {
font-size: 3em;
}
.flashcard {
height: 200px;
}
.options-grid {
grid-template-columns: 1fr;
}
.final-stats {
flex-direction: column;
align-items: center;
gap: 15px;
}
.final-actions {
flex-direction: column;
align-items: center;
}
.hsk-actions {
flex-direction: column;
align-items: center;
}
.study-controls {
flex-direction: column;
}
.prev-btn, .next-btn {
max-width: none;
}
.mode-title {
flex-direction: column;
gap: 10px;
text-align: center;
}
.progress-section {
flex-direction: column;
gap: 10px;
}
}
`;
document.head.appendChild(style);
}
start() {
this.isRunning = true;
logSh('Chinese Study Mode initialized with ultra-modular format', 'INFO');
}
destroy() {
this.isRunning = false;
// Clean up global references
if (window.chineseStudyInstance === this) {
delete window.chineseStudyInstance;
}
logSh('Chinese Study Mode destroyed', 'INFO');
}
restart() {
this.score = 0;
this.correctAnswers = 0;
this.currentIndex = 0;
this.studyState = 'menu';
this.currentMode = null;
this.onScoreUpdate(this.score);
// Re-shuffle vocabulary
this.vocabulary = this.vocabulary.sort(() => Math.random() - 0.5);
this.createGameInterface();
logSh('Chinese Study Mode restarted', 'INFO');
}
}
// Export to global scope
window.GameModules = window.GameModules || {};
window.GameModules.ChineseStudy = ChineseStudyGame;