Major Changes: - Moved legacy system to Legacy/ folder for archival - Built new modular architecture with strict separation of concerns - Created core system: Module, EventBus, ModuleLoader, Router - Added Application bootstrap with auto-start functionality - Implemented development server with ES6 modules support - Created comprehensive documentation and project context - Converted SBS-7-8 content to JSON format - Copied all legacy games and content to new structure New Architecture Features: - Sealed modules with WeakMap private data - Strict dependency injection system - Event-driven communication only - Inviolable responsibility patterns - Auto-initialization without commands - Component-based UI foundation ready Technical Stack: - Vanilla JS/HTML/CSS only - ES6 modules with proper imports/exports - HTTP development server (no file:// protocol) - Modular CSS with component scoping - Comprehensive error handling and debugging Ready for Phase 2: Converting legacy modules to new architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1585 lines
51 KiB
JavaScript
1585 lines
51 KiB
JavaScript
// === 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; |