Merge remote changes with local development
🔧 Resolved conflicts by accepting remote versions 📁 Added new content: basic-chinese.js, english-class-demo.js, chinese-study.js ✨ Updated game configurations and core modules
This commit is contained in:
commit
855cfe2f94
110
.gitignore
vendored
Normal file
110
.gitignore
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.tgz
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Audio files (can be large)
|
||||||
|
audio/*.mp3
|
||||||
|
audio/*.wav
|
||||||
|
audio/*.ogg
|
||||||
|
audio/*.m4a
|
||||||
|
|
||||||
|
# Image files (can be large)
|
||||||
|
images/*.jpg
|
||||||
|
images/*.jpeg
|
||||||
|
images/*.png
|
||||||
|
images/*.gif
|
||||||
|
images/*.bmp
|
||||||
|
images/*.svg
|
||||||
|
|
||||||
|
# Video files
|
||||||
|
videos/*.mp4
|
||||||
|
videos/*.avi
|
||||||
|
videos/*.mov
|
||||||
|
videos/*.wmv
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
*.old
|
||||||
|
|
||||||
|
# Cache directories
|
||||||
|
.cache/
|
||||||
|
.parcel-cache/
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# ESLint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# User-specific content that shouldn't be shared
|
||||||
|
user-content/
|
||||||
|
personal-notes.txt
|
||||||
|
config/user-settings.json
|
||||||
|
|
||||||
|
# AI models and large datasets (if any)
|
||||||
|
models/
|
||||||
|
datasets/
|
||||||
|
*.model
|
||||||
|
*.dat
|
||||||
330
CLAUDE.md
Normal file
330
CLAUDE.md
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Interactive English learning platform for children (8-9 years old) built as a modular Single Page Application. The system provides 9 different educational games that work with various content modules through a flexible architecture.
|
||||||
|
|
||||||
|
## Key Architecture Patterns
|
||||||
|
|
||||||
|
### Core System Flow
|
||||||
|
1. **AppNavigation** (`js/core/navigation.js`) - Central SPA navigation controller
|
||||||
|
2. **ContentScanner** (`js/core/content-scanner.js`) - Auto-discovers available content modules
|
||||||
|
3. **GameLoader** (`js/core/game-loader.js`) - Dynamically loads game and content modules
|
||||||
|
4. **Content Engine** (`js/core/content-engine.js`) - Processes and adapts content for games
|
||||||
|
|
||||||
|
### Module Loading System
|
||||||
|
- Games and content are loaded dynamically via `GameLoader.loadGame(gameType, contentType)`
|
||||||
|
- All modules register themselves on global objects: `window.GameModules` and `window.ContentModules`
|
||||||
|
- Content is discovered automatically by `ContentScanner` scanning `js/content/` directory
|
||||||
|
- Games follow consistent constructor pattern: `new GameClass({ container, content, onScoreUpdate, onGameEnd })`
|
||||||
|
|
||||||
|
### URL-Based Navigation
|
||||||
|
- Single HTML file (`index.html`) handles all navigation via URL parameters
|
||||||
|
- Routes: `?page=home|games|levels|play&game=<gameType>&content=<contentType>`
|
||||||
|
- Browser back/forward supported through `popstate` events
|
||||||
|
- Navigation history maintained in `AppNavigation.navigationHistory`
|
||||||
|
- Breadcrumb navigation with clickable path elements
|
||||||
|
- Keyboard shortcuts (ESC = go back)
|
||||||
|
|
||||||
|
## Content Module Format
|
||||||
|
|
||||||
|
### Rich Content Schema (New Architecture)
|
||||||
|
|
||||||
|
Content modules support rich, multimedia educational content with optional properties. The system adapts games and exercises based on available content features:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
window.ContentModules.ModuleName = {
|
||||||
|
name: "Display Name",
|
||||||
|
description: "Description text",
|
||||||
|
difficulty: "easy|medium|hard|beginner|intermediate|advanced",
|
||||||
|
language: "chinese|english|french|spanish", // Target learning language
|
||||||
|
hskLevel: "HSK1|HSK2|HSK3|HSK4|HSK5|HSK6", // Chinese proficiency level
|
||||||
|
|
||||||
|
// Rich vocabulary with optional multimedia
|
||||||
|
vocabulary: {
|
||||||
|
"word_or_character": {
|
||||||
|
translation: "English translation",
|
||||||
|
pinyin: "pronunciation guide", // Optional: Chinese pinyin
|
||||||
|
type: "noun|verb|adjective|greeting|number", // Word classification
|
||||||
|
pronunciation: "audio/word.mp3", // Optional: audio file
|
||||||
|
difficulty: "HSK1|HSK2|...", // Optional: individual word difficulty
|
||||||
|
strokeOrder: ["stroke1", "stroke2"], // Optional: character writing order
|
||||||
|
examples: ["example sentence 1"], // Optional: usage examples
|
||||||
|
grammarNotes: "special usage rules" // Optional: grammar context
|
||||||
|
}
|
||||||
|
// OR simple format for basic content:
|
||||||
|
// "word": "simple translation"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Grammar rules and explanations
|
||||||
|
grammar: {
|
||||||
|
topic_name: {
|
||||||
|
title: "Grammar Rule Title",
|
||||||
|
explanation: "Detailed explanation",
|
||||||
|
examples: [
|
||||||
|
{ chinese: "中文例子", english: "English example", pinyin: "zhōng wén lì zi" }
|
||||||
|
],
|
||||||
|
exercises: [/* grammar-specific exercises */]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Audio content with/without text
|
||||||
|
audio: {
|
||||||
|
withText: [
|
||||||
|
{
|
||||||
|
title: "Audio Lesson Title",
|
||||||
|
audioFile: "audio/lesson1.mp3",
|
||||||
|
transcript: "Full text transcript",
|
||||||
|
translation: "English translation",
|
||||||
|
timestamps: [{ time: 5.2, text: "specific segment" }] // Optional
|
||||||
|
}
|
||||||
|
],
|
||||||
|
withoutText: [
|
||||||
|
{
|
||||||
|
title: "Listening Challenge",
|
||||||
|
audioFile: "audio/challenge1.mp3",
|
||||||
|
questions: [
|
||||||
|
{ question: "What did they say?", type: "ai_interpreted" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Poetry and cultural content
|
||||||
|
poems: [
|
||||||
|
{
|
||||||
|
title: "Poem Title",
|
||||||
|
content: "Full poem text",
|
||||||
|
translation: "English translation",
|
||||||
|
audioFile: "audio/poem1.mp3", // Optional
|
||||||
|
culturalContext: "Historical background"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Fill-in-the-blank exercises
|
||||||
|
fillInBlanks: [
|
||||||
|
{
|
||||||
|
sentence: "I _____ to school every day",
|
||||||
|
options: ["go", "goes", "going", "went"], // Multiple choice options
|
||||||
|
correctAnswer: "go",
|
||||||
|
explanation: "Present tense with 'I'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sentence: "The weather is _____ today",
|
||||||
|
type: "open_ended", // AI-interpreted answers
|
||||||
|
acceptedAnswers: ["nice", "good", "beautiful", "sunny"],
|
||||||
|
aiPrompt: "Evaluate if answer describes weather positively"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Sentence correction exercises
|
||||||
|
corrections: [
|
||||||
|
{
|
||||||
|
incorrect: "I are happy today",
|
||||||
|
correct: "I am happy today",
|
||||||
|
explanation: "Use 'am' with pronoun 'I'",
|
||||||
|
type: "grammar_correction"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Reading comprehension with AI evaluation
|
||||||
|
comprehension: [
|
||||||
|
{
|
||||||
|
text: "Long reading passage...",
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "What is the main idea?",
|
||||||
|
type: "ai_interpreted",
|
||||||
|
evaluationPrompt: "Check if answer captures main theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Multiple choice question?",
|
||||||
|
type: "multiple_choice",
|
||||||
|
options: ["A", "B", "C", "D"],
|
||||||
|
correctAnswer: "B"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Matching exercises (connect lines between columns)
|
||||||
|
matching: [
|
||||||
|
{
|
||||||
|
title: "Match Words to Meanings",
|
||||||
|
leftColumn: ["apple", "book", "car"],
|
||||||
|
rightColumn: ["苹果", "书", "车"],
|
||||||
|
correctPairs: [
|
||||||
|
{ left: "apple", right: "苹果" },
|
||||||
|
{ left: "book", right: "书" },
|
||||||
|
{ left: "car", right: "车" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Standard content (backward compatibility)
|
||||||
|
sentences: [{ english: "...", chinese: "...", pinyin: "..." }],
|
||||||
|
texts: [{ title: "...", content: "...", translation: "..." }],
|
||||||
|
dialogues: [{ conversation: [...] }]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Adaptivity System
|
||||||
|
|
||||||
|
The platform automatically adapts available games and exercises based on content richness:
|
||||||
|
|
||||||
|
**Content Analysis:**
|
||||||
|
- System scans each content module for available features
|
||||||
|
- Generates compatibility scores for each game type
|
||||||
|
- Recommends optimal learning activities
|
||||||
|
- Handles graceful degradation when content is incomplete
|
||||||
|
|
||||||
|
**Adaptive Game Selection:**
|
||||||
|
- **Rich vocabulary** → Enable advanced matching games, pronunciation practice
|
||||||
|
- **Audio files present** → Enable listening exercises, pronunciation challenges
|
||||||
|
- **Grammar rules** → Enable correction exercises, structured lessons
|
||||||
|
- **Fill-in-blanks data** → Enable cloze tests with multiple choice or AI evaluation
|
||||||
|
- **Minimal content** → Fall back to basic vocabulary games
|
||||||
|
|
||||||
|
**Missing Content Handling:**
|
||||||
|
- Display helpful messages: "Add audio files to enable pronunciation practice"
|
||||||
|
- Suggest content enrichment opportunities
|
||||||
|
- Gracefully disable incompatible game modes
|
||||||
|
- Provide content creation tools for missing elements
|
||||||
|
|
||||||
|
**Example Adaptive Behavior:**
|
||||||
|
```javascript
|
||||||
|
// Content with only basic vocabulary
|
||||||
|
{ vocabulary: { "hello": "你好" } }
|
||||||
|
→ Enable: Basic matching, simple quiz
|
||||||
|
→ Disable: Audio practice, grammar exercises
|
||||||
|
→ Suggest: "Add pinyin and audio for pronunciation practice"
|
||||||
|
|
||||||
|
// Rich multimedia content
|
||||||
|
{ vocabulary: { "hello": { translation: "你好", pinyin: "nǐ hǎo", pronunciation: "audio/hello.mp3" } } }
|
||||||
|
→ Enable: All vocabulary games, audio practice, pronunciation scoring
|
||||||
|
→ Unlock: Advanced difficulty levels, speed challenges
|
||||||
|
```
|
||||||
|
|
||||||
|
## Game Module Format
|
||||||
|
|
||||||
|
Game modules must export to `window.GameModules` with this pattern:
|
||||||
|
```javascript
|
||||||
|
class GameName {
|
||||||
|
constructor({ container, content, onScoreUpdate, onGameEnd }) {
|
||||||
|
this.container = container;
|
||||||
|
this.content = content;
|
||||||
|
this.onScoreUpdate = onScoreUpdate;
|
||||||
|
this.onGameEnd = onGameEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() { /* Initialize game */ }
|
||||||
|
destroy() { /* Cleanup */ }
|
||||||
|
restart() { /* Reset game state */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
window.GameModules = window.GameModules || {};
|
||||||
|
window.GameModules.GameName = GameName;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration System
|
||||||
|
|
||||||
|
- Main config: `config/games-config.json` - defines available games and content
|
||||||
|
- Games can be enabled/disabled via `games.{gameType}.enabled`
|
||||||
|
- Content modules auto-detected but can be configured in `content` section
|
||||||
|
- UI settings, scoring rules, and feature flags also in main config
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
Open `index.html` in a web browser - no build process required. All modules load dynamically.
|
||||||
|
|
||||||
|
### Adding New Games
|
||||||
|
1. Create `js/games/{game-name}.js` with proper module export
|
||||||
|
2. Add game configuration to `config/games-config.json`
|
||||||
|
3. Update `AppNavigation.getDefaultConfig()` if needed
|
||||||
|
|
||||||
|
### Adding New Content
|
||||||
|
1. Create `js/content/{content-name}.js` with proper module export
|
||||||
|
2. Add filename to `ContentScanner.contentFiles` array
|
||||||
|
3. Content will be auto-discovered on next app load
|
||||||
|
|
||||||
|
### Content Creation Tool
|
||||||
|
- Built-in content creator at `js/tools/content-creator.js`
|
||||||
|
- Accessible via "Créateur de Contenu" button on home page
|
||||||
|
- Generates properly formatted content modules
|
||||||
|
|
||||||
|
## Key Files by Function
|
||||||
|
|
||||||
|
**Navigation & Loading:**
|
||||||
|
- `js/core/navigation.js` - SPA navigation controller (452 lines)
|
||||||
|
- `js/core/game-loader.js` - Dynamic module loading (336 lines)
|
||||||
|
- `js/core/content-scanner.js` - Auto content discovery (376 lines)
|
||||||
|
|
||||||
|
**Content Processing:**
|
||||||
|
- `js/core/content-engine.js` - Content processing engine (484 lines)
|
||||||
|
- `js/core/content-factory.js` - Exercise generation (553 lines)
|
||||||
|
- `js/core/content-parsers.js` - Content parsing utilities (484 lines)
|
||||||
|
|
||||||
|
**Game Implementations:**
|
||||||
|
- `js/games/whack-a-mole.js` - Standard version (623 lines)
|
||||||
|
- `js/games/whack-a-mole-hard.js` - Difficult version (643 lines)
|
||||||
|
- `js/games/memory-match.js` - Memory pairs game (403 lines)
|
||||||
|
- `js/games/quiz-game.js` - Quiz system (354 lines)
|
||||||
|
- `js/games/fill-the-blank.js` - Sentence completion (418 lines)
|
||||||
|
- `js/games/text-reader.js` - Guided text reading (366 lines)
|
||||||
|
- `js/games/adventure-reader.js` - RPG-style adventure (949 lines)
|
||||||
|
|
||||||
|
## Important Implementation Details
|
||||||
|
|
||||||
|
### Scoring System
|
||||||
|
- Games call `this.onScoreUpdate(score)` to update display
|
||||||
|
- Final scores saved to localStorage with key pattern: `score_{gameType}_{contentType}`
|
||||||
|
- Best scores tracked and displayed in game-end modal
|
||||||
|
- Points per correct answer, malus per error, speed bonus
|
||||||
|
- Score history and achievement badges
|
||||||
|
|
||||||
|
### Content Compatibility
|
||||||
|
- `ContentScanner` evaluates content compatibility with each game type
|
||||||
|
- Compatibility scoring helps recommend best content for each game
|
||||||
|
- Games should handle various content formats gracefully
|
||||||
|
|
||||||
|
### Memory Management
|
||||||
|
- `GameLoader.cleanup()` called before loading new games
|
||||||
|
- Games should implement `destroy()` method for proper cleanup
|
||||||
|
- Previous game instances must be cleaned up to prevent memory leaks
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- Content loading errors logged but don't crash the application
|
||||||
|
- Fallback mechanisms for missing content or games
|
||||||
|
- User-friendly error messages via `Utils.showToast()`
|
||||||
|
|
||||||
|
## Design Guidelines
|
||||||
|
|
||||||
|
### Visual Design Principles
|
||||||
|
- Modern, clean design optimized for children (8-9 years old)
|
||||||
|
- Large, tactile buttons (minimum 44px for touch interfaces)
|
||||||
|
- High contrast colors for accessibility
|
||||||
|
- Smooth, non-aggressive animations
|
||||||
|
- Emoji icons combined with text labels
|
||||||
|
|
||||||
|
### Color Palette
|
||||||
|
- **Primary**: Blue (#3B82F6) - Trust, learning
|
||||||
|
- **Secondary**: Green (#10B981) - Success, validation
|
||||||
|
- **Accent**: Orange (#F59E0B) - Energy, attention
|
||||||
|
- **Error**: Red (#EF4444) - Clear error indication
|
||||||
|
- **Neutral**: Gray (#6B7280) - Text, backgrounds
|
||||||
|
|
||||||
|
### Accessibility Features
|
||||||
|
- Full keyboard navigation support
|
||||||
|
- Alternative text for all images
|
||||||
|
- Adjustable font sizes
|
||||||
|
- High contrast mode compatibility
|
||||||
|
- Screen reader friendly markup
|
||||||
|
|
||||||
|
### Responsive Design
|
||||||
|
- Mobile/tablet adaptation
|
||||||
|
- Touch-friendly interface
|
||||||
|
- Portrait/landscape orientation support
|
||||||
|
- Fluid layouts that work on various screen sizes
|
||||||
@ -49,6 +49,16 @@
|
|||||||
"minAge": 8,
|
"minAge": 8,
|
||||||
"maxAge": 14,
|
"maxAge": 14,
|
||||||
"estimatedTime": 10
|
"estimatedTime": 10
|
||||||
|
},
|
||||||
|
"chinese-study": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "Chinese Study",
|
||||||
|
"icon": "🇨🇳",
|
||||||
|
"description": "Learn Chinese characters, pinyin and vocabulary",
|
||||||
|
"difficulty": "medium",
|
||||||
|
"minAge": 12,
|
||||||
|
"maxAge": 99,
|
||||||
|
"estimatedTime": 15
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
@ -123,6 +133,24 @@
|
|||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"vocabulary_count": 85,
|
"vocabulary_count": 85,
|
||||||
"topics": ["homes", "clothing", "neighborhoods", "grammar", "culture"]
|
"topics": ["homes", "clothing", "neighborhoods", "grammar", "culture"]
|
||||||
|
},
|
||||||
|
"basic-chinese": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "Basic Chinese",
|
||||||
|
"icon": "🇨🇳",
|
||||||
|
"description": "Essential Chinese characters, pinyin and vocabulary",
|
||||||
|
"difficulty": "beginner",
|
||||||
|
"vocabulary_count": 25,
|
||||||
|
"topics": ["greetings", "numbers", "family", "basic_characters"]
|
||||||
|
},
|
||||||
|
"english-class-demo": {
|
||||||
|
"enabled": true,
|
||||||
|
"name": "English Class Demo",
|
||||||
|
"icon": "🇬🇧",
|
||||||
|
"description": "Complete rich content example with all exercise types",
|
||||||
|
"difficulty": "mixed",
|
||||||
|
"vocabulary_count": 50,
|
||||||
|
"topics": ["vocabulary", "grammar", "audio", "poems", "exercises", "ai_evaluation"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
303
css/games.css
303
css/games.css
@ -181,6 +181,63 @@
|
|||||||
50% { transform: rotateY(180deg) scale(1.1); }
|
50% { transform: rotateY(180deg) scale(1.1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === ANIMATIONS DE RÉUSSITE === */
|
||||||
|
@keyframes success-match {
|
||||||
|
0% { transform: rotateY(180deg) scale(1); }
|
||||||
|
25% { transform: rotateY(180deg) scale(1.15); }
|
||||||
|
50% { transform: rotateY(180deg) scale(1.05); }
|
||||||
|
75% { transform: rotateY(180deg) scale(1.1); }
|
||||||
|
100% { transform: rotateY(180deg) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes success-glow {
|
||||||
|
0% { box-shadow: 0 0 5px #22C55E; }
|
||||||
|
50% { box-shadow: 0 0 20px #22C55E, 0 0 30px #22C55E; }
|
||||||
|
100% { box-shadow: 0 0 5px #22C55E; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes success-sparkle {
|
||||||
|
0% { opacity: 0; transform: scale(0) rotate(0deg); }
|
||||||
|
50% { opacity: 1; transform: scale(1) rotate(180deg); }
|
||||||
|
100% { opacity: 0; transform: scale(0) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-card.success-animation .card-inner {
|
||||||
|
animation: success-match 0.8s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-card.success-animation .card-back {
|
||||||
|
animation: success-glow 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Particules de réussite */
|
||||||
|
.success-particle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #22C55E;
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 100;
|
||||||
|
animation: success-sparkle 1.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-particle::before {
|
||||||
|
content: '✨';
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
left: -5px;
|
||||||
|
font-size: 20px;
|
||||||
|
animation: success-sparkle 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-particle.particle-1 { animation-delay: 0s; transform: translate(-20px, -20px); }
|
||||||
|
.success-particle.particle-2 { animation-delay: 0.1s; transform: translate(20px, -20px); }
|
||||||
|
.success-particle.particle-3 { animation-delay: 0.2s; transform: translate(-20px, 20px); }
|
||||||
|
.success-particle.particle-4 { animation-delay: 0.3s; transform: translate(20px, 20px); }
|
||||||
|
|
||||||
.game-controls {
|
.game-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
@ -1496,15 +1553,15 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.whack-mole .word {
|
.whack-mole .word {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-header h3 {
|
.game-header h3 {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.score-display {
|
.score-display {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
@ -1514,12 +1571,248 @@
|
|||||||
padding: 30px 20px;
|
padding: 30px 20px;
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-end-modal h3 {
|
.game-end-modal h3 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-end-buttons {
|
.game-end-buttons {
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === OPTIMISATION POUR ÉCRANS LARGES 1920x1080+ === */
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
/* Memory Match - laisser le CSS original fonctionner */
|
||||||
|
|
||||||
|
/* Autres jeux en mode horizontal */
|
||||||
|
.quiz-game-wrapper,
|
||||||
|
.fill-blank-wrapper,
|
||||||
|
.text-reader-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 40px;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whack-a-mole en layout horizontal simple */
|
||||||
|
.game-container:has(.whack-game-board) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 40px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Board du whack-a-mole centré */
|
||||||
|
.whack-game-board {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Infos whack-a-mole sur le côté */
|
||||||
|
.game-container:has(.whack-game-board) .game-info {
|
||||||
|
flex: 0 0 300px;
|
||||||
|
order: 2;
|
||||||
|
margin: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone principale du jeu à gauche */
|
||||||
|
.game-main-area {
|
||||||
|
min-width: 0; /* Pour éviter overflow */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panneau latéral à droite */
|
||||||
|
.game-sidebar {
|
||||||
|
background: var(--card-background);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 25px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
position: sticky;
|
||||||
|
top: 80px;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Memory Match - juste augmenter la grille pour 1920x1080 */
|
||||||
|
.memory-match-wrapper {
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-grid {
|
||||||
|
gap: 20px;
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forcer la visibilité des cartes */
|
||||||
|
.memory-card {
|
||||||
|
min-width: 100px !important;
|
||||||
|
min-height: 100px !important;
|
||||||
|
width: auto !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-front,
|
||||||
|
.card-back {
|
||||||
|
border: 2px solid #e5e7eb !important;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
background: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-front {
|
||||||
|
background: linear-gradient(135deg, #3B82F6, #10B981) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quiz - Options en 2x2 */
|
||||||
|
.options-area {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiz-option {
|
||||||
|
padding: 25px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Questions plus grandes */
|
||||||
|
.question-text {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill the Blank - Phrase plus grande */
|
||||||
|
.sentence-container {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2;
|
||||||
|
padding: 40px;
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blank-input {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 10px 15px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text Reader - Zone de lecture plus large */
|
||||||
|
.current-sentence {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 2.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reading-area {
|
||||||
|
padding: 50px;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adventure Reader - Carte plus grande */
|
||||||
|
.adventure-reader-wrapper {
|
||||||
|
max-width: 1400px;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-map {
|
||||||
|
width: calc(100vw - 450px);
|
||||||
|
height: 70vh;
|
||||||
|
max-width: 1000px;
|
||||||
|
max-height: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adventure-sidebar {
|
||||||
|
min-width: 350px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Whack-a-mole - Trous plus petits pour mieux voir l'ensemble */
|
||||||
|
.whack-hole {
|
||||||
|
max-width: 100px;
|
||||||
|
max-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.whack-mole .word {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.whack-game-board {
|
||||||
|
max-width: 500px;
|
||||||
|
gap: 15px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.whack-game-board.hard-mode {
|
||||||
|
max-width: 650px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Game header plus compact horizontalement */
|
||||||
|
.game-header {
|
||||||
|
padding: 20px 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-header h3 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout spécifique pour les wrappers de jeux */
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
/* Headers restent en haut, pleine largeur */
|
||||||
|
.memory-match-wrapper,
|
||||||
|
.quiz-game-wrapper,
|
||||||
|
.fill-blank-wrapper,
|
||||||
|
.text-reader-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-match-wrapper > .game-main-content,
|
||||||
|
.quiz-game-wrapper > .game-main-content,
|
||||||
|
.fill-blank-wrapper > .game-main-content,
|
||||||
|
.text-reader-wrapper > .game-main-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone principale du jeu - centrée */
|
||||||
|
.game-main-content {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar pour les infos - à droite */
|
||||||
|
.game-sidebar-info {
|
||||||
|
flex: 0 0 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Regroupement des éléments de jeu */
|
||||||
|
.memory-grid,
|
||||||
|
.question-area,
|
||||||
|
.options-area,
|
||||||
|
.sentence-container,
|
||||||
|
.reading-area,
|
||||||
|
.whack-game-board {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -46,6 +46,14 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Optimisation pour écrans larges */
|
||||||
|
@media (min-width: 1440px) {
|
||||||
|
.container {
|
||||||
|
max-width: 1600px;
|
||||||
|
padding: 20px 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* === PAGES === */
|
/* === PAGES === */
|
||||||
.page {
|
.page {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@ -2,26 +2,38 @@
|
|||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
padding: 15px 25px;
|
padding: 8px 20px;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
position: sticky;
|
position: fixed;
|
||||||
top: 20px;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb.hidden {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb.visible {
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item {
|
.breadcrumb-item {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
padding: 8px 16px;
|
padding: 6px 12px;
|
||||||
border-radius: 20px;
|
border-radius: 16px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: var(--transition);
|
transition: var(--transition);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -72,23 +84,31 @@
|
|||||||
transition: all 0.3s ease-in;
|
transition: all 0.3s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === BODY PADDING FOR FIXED BREADCRUMB === */
|
||||||
|
body {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
/* === NAVIGATION RESPONSIVE === */
|
/* === NAVIGATION RESPONSIVE === */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
padding: 12px 20px;
|
padding: 6px 15px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 10px;
|
||||||
position: static;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item {
|
.breadcrumb-item {
|
||||||
padding: 6px 12px;
|
padding: 4px 10px;
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-item:not(:last-child)::after {
|
|
||||||
right: -12px;
|
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item:not(:last-child)::after {
|
||||||
|
right: -10px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === CONTENT SCANNING STYLES === */
|
/* === CONTENT SCANNING STYLES === */
|
||||||
@ -238,17 +258,21 @@
|
|||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
padding: 10px 15px;
|
padding: 5px 12px;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item {
|
.breadcrumb-item {
|
||||||
padding: 5px 10px;
|
padding: 4px 8px;
|
||||||
font-size: 0.8rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item:not(:last-child)::after {
|
.breadcrumb-item:not(:last-child)::after {
|
||||||
right: -10px;
|
right: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-top: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-stats {
|
.content-stats {
|
||||||
|
|||||||
115
js/content/basic-chinese.js
Normal file
115
js/content/basic-chinese.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Basic Chinese content for Chinese Study Mode
|
||||||
|
|
||||||
|
const basicChineseContent = {
|
||||||
|
vocabulary: {
|
||||||
|
// Basic greetings and common words
|
||||||
|
"你好": "hello (nǐ hǎo)",
|
||||||
|
"再见": "goodbye (zài jiàn)",
|
||||||
|
"谢谢": "thank you (xiè xiè)",
|
||||||
|
"对不起": "sorry (duì bu qǐ)",
|
||||||
|
"请": "please (qǐng)",
|
||||||
|
"是": "yes/to be (shì)",
|
||||||
|
"不": "no/not (bù)",
|
||||||
|
"我": "I/me (wǒ)",
|
||||||
|
"你": "you (nǐ)",
|
||||||
|
"他": "he/him (tā)",
|
||||||
|
"她": "she/her (tā)",
|
||||||
|
|
||||||
|
// Numbers 1-10
|
||||||
|
"一": "one (yī)",
|
||||||
|
"二": "two (èr)",
|
||||||
|
"三": "three (sān)",
|
||||||
|
"四": "four (sì)",
|
||||||
|
"五": "five (wǔ)",
|
||||||
|
"六": "six (liù)",
|
||||||
|
"七": "seven (qī)",
|
||||||
|
"八": "eight (bā)",
|
||||||
|
"九": "nine (jiǔ)",
|
||||||
|
"十": "ten (shí)",
|
||||||
|
|
||||||
|
// Basic family
|
||||||
|
"家": "family/home (jiā)",
|
||||||
|
"爸爸": "father (bà ba)",
|
||||||
|
"妈妈": "mother (mā ma)",
|
||||||
|
"儿子": "son (ér zi)",
|
||||||
|
"女儿": "daughter (nǚ ér)"
|
||||||
|
},
|
||||||
|
|
||||||
|
sentences: [
|
||||||
|
{
|
||||||
|
chinese: "你好!",
|
||||||
|
english: "Hello!",
|
||||||
|
pinyin: "Nǐ hǎo!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chinese: "我是学生。",
|
||||||
|
english: "I am a student.",
|
||||||
|
pinyin: "Wǒ shì xué shēng."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chinese: "谢谢你!",
|
||||||
|
english: "Thank you!",
|
||||||
|
pinyin: "Xiè xiè nǐ!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chinese: "这是我的家。",
|
||||||
|
english: "This is my home.",
|
||||||
|
pinyin: "Zhè shì wǒ de jiā."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
dialogues: [
|
||||||
|
{
|
||||||
|
title: "Basic Greeting",
|
||||||
|
conversation: [
|
||||||
|
{ speaker: "A", chinese: "你好!", english: "Hello!", pinyin: "Nǐ hǎo!" },
|
||||||
|
{ speaker: "B", chinese: "你好!你叫什么名字?", english: "Hello! What's your name?", pinyin: "Nǐ hǎo! Nǐ jiào shén me míng zi?" },
|
||||||
|
{ speaker: "A", chinese: "我叫小明。你呢?", english: "My name is Xiaoming. And you?", pinyin: "Wǒ jiào Xiǎo Míng. Nǐ ne?" },
|
||||||
|
{ speaker: "B", chinese: "我叫小红。很高兴认识你!", english: "My name is Xiaohong. Nice to meet you!", pinyin: "Wǒ jiào Xiǎo Hóng. Hěn gāo xìng rèn shi nǐ!" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
texts: [
|
||||||
|
{
|
||||||
|
title: "Learning Chinese",
|
||||||
|
content: "Chinese is one of the most spoken languages in the world. It uses characters instead of letters. Each character can represent a word or part of a word. Learning Chinese characters, their pronunciation (pinyin), and meanings is the foundation of studying Chinese.",
|
||||||
|
chinese: "学习中文是很有趣的。中文使用汉字,不是字母。每个汉字都有意思。"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
culturalNotes: [
|
||||||
|
{
|
||||||
|
topic: "Chinese Characters",
|
||||||
|
note: "Chinese characters are logograms, where each character represents a word or morpheme. There are thousands of characters, but you only need about 2000-3000 to read most modern Chinese texts."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
topic: "Pinyin",
|
||||||
|
note: "Pinyin is the romanization system used to help learn Chinese pronunciation. It uses the Latin alphabet with tone marks to indicate the four main tones in Mandarin Chinese."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
topic: "Tones",
|
||||||
|
note: "Mandarin Chinese has four main tones plus a neutral tone. The tone changes the meaning of the word, so it's important to learn them correctly."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export for web module system
|
||||||
|
window.ContentModules = window.ContentModules || {};
|
||||||
|
window.ContentModules.BasicChinese = {
|
||||||
|
name: "Basic Chinese",
|
||||||
|
description: "Essential Chinese characters, pinyin and vocabulary for beginners",
|
||||||
|
difficulty: "beginner",
|
||||||
|
vocabulary: basicChineseContent.vocabulary,
|
||||||
|
sentences: basicChineseContent.sentences,
|
||||||
|
dialogues: basicChineseContent.dialogues,
|
||||||
|
texts: basicChineseContent.texts,
|
||||||
|
culturalNotes: basicChineseContent.culturalNotes,
|
||||||
|
language: "chinese",
|
||||||
|
hskLevel: "HSK1"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Node.js export (optional)
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = basicChineseContent;
|
||||||
|
}
|
||||||
287
js/content/english-class-demo.js
Normal file
287
js/content/english-class-demo.js
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
// Demo English Class - Rich Content Example
|
||||||
|
// Shows all possible content types with dummy data
|
||||||
|
|
||||||
|
const englishClassDemoContent = {
|
||||||
|
// Rich vocabulary with all optional features
|
||||||
|
vocabulary: {
|
||||||
|
"apple": {
|
||||||
|
translation: "pomme",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "audio/apple.mp3",
|
||||||
|
difficulty: "beginner",
|
||||||
|
examples: ["I eat an apple every day", "The apple is red and sweet"],
|
||||||
|
grammarNotes: "Count noun - can be singular or plural"
|
||||||
|
},
|
||||||
|
"run": {
|
||||||
|
translation: "courir",
|
||||||
|
type: "verb",
|
||||||
|
pronunciation: "audio/run.mp3",
|
||||||
|
difficulty: "beginner",
|
||||||
|
examples: ["I run in the park", "She runs very fast"],
|
||||||
|
grammarNotes: "Regular verb: run, runs, running, ran"
|
||||||
|
},
|
||||||
|
"beautiful": {
|
||||||
|
translation: "beau/belle",
|
||||||
|
type: "adjective",
|
||||||
|
pronunciation: "audio/beautiful.mp3",
|
||||||
|
difficulty: "intermediate",
|
||||||
|
examples: ["The sunset is beautiful", "She has beautiful eyes"],
|
||||||
|
grammarNotes: "Can be used before noun or after 'be'"
|
||||||
|
},
|
||||||
|
"hello": {
|
||||||
|
translation: "bonjour",
|
||||||
|
type: "greeting",
|
||||||
|
pronunciation: "audio/hello.mp3",
|
||||||
|
difficulty: "beginner",
|
||||||
|
examples: ["Hello, how are you?", "Hello everyone!"],
|
||||||
|
grammarNotes: "Common greeting used anytime"
|
||||||
|
},
|
||||||
|
// Simple format examples (backward compatibility)
|
||||||
|
"cat": "chat",
|
||||||
|
"dog": "chien",
|
||||||
|
"house": "maison"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Grammar rules and explanations
|
||||||
|
grammar: {
|
||||||
|
presentSimple: {
|
||||||
|
title: "Present Simple Tense",
|
||||||
|
explanation: "Used for habits, facts, and general truths. Form: Subject + base verb (+ s for he/she/it)",
|
||||||
|
examples: [
|
||||||
|
{ english: "I walk to school", french: "Je marche à l'école" },
|
||||||
|
{ english: "She walks to school", french: "Elle marche à l'école" },
|
||||||
|
{ english: "They walk to school", french: "Ils marchent à l'école" }
|
||||||
|
],
|
||||||
|
exercises: [
|
||||||
|
"Complete: I _____ (play) tennis every Sunday",
|
||||||
|
"Transform: He walk to work → He _____ to work"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
articles: {
|
||||||
|
title: "Articles: A, An, The",
|
||||||
|
explanation: "A/An = indefinite articles (one of many). The = definite article (specific one)",
|
||||||
|
examples: [
|
||||||
|
{ english: "I see a cat", french: "Je vois un chat" },
|
||||||
|
{ english: "I see an elephant", french: "Je vois un éléphant" },
|
||||||
|
{ english: "I see the cat from yesterday", french: "Je vois le chat d'hier" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Audio content with transcripts
|
||||||
|
audio: {
|
||||||
|
withText: [
|
||||||
|
{
|
||||||
|
title: "Daily Routine Conversation",
|
||||||
|
audioFile: "audio/daily_routine.mp3",
|
||||||
|
transcript: "A: What time do you wake up? B: I usually wake up at 7 AM. A: That's early! I wake up at 8:30. B: I like to exercise before work. A: That's a good habit!",
|
||||||
|
translation: "A: À quelle heure te réveilles-tu? B: Je me réveille habituellement à 7h. A: C'est tôt! Je me réveille à 8h30. B: J'aime faire de l'exercice avant le travail. A: C'est une bonne habitude!",
|
||||||
|
timestamps: [
|
||||||
|
{ time: 0.5, text: "What time do you wake up?" },
|
||||||
|
{ time: 3.2, text: "I usually wake up at 7 AM" },
|
||||||
|
{ time: 6.8, text: "That's early! I wake up at 8:30" },
|
||||||
|
{ time: 11.1, text: "I like to exercise before work" },
|
||||||
|
{ time: 14.5, text: "That's a good habit!" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Weather Report",
|
||||||
|
audioFile: "audio/weather.mp3",
|
||||||
|
transcript: "Today's weather: It's sunny and warm with a high of 25 degrees. Light winds from the south. Perfect day for outdoor activities!",
|
||||||
|
translation: "Météo d'aujourd'hui: Il fait ensoleillé et chaud avec un maximum de 25 degrés. Vents légers du sud. Journée parfaite pour les activités extérieures!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
withoutText: [
|
||||||
|
{
|
||||||
|
title: "Mystery Conversation",
|
||||||
|
audioFile: "audio/mystery.mp3",
|
||||||
|
questions: [
|
||||||
|
{ question: "How many people are speaking?", type: "ai_interpreted" },
|
||||||
|
{ question: "What are they talking about?", type: "ai_interpreted" },
|
||||||
|
{ question: "What is the mood of the conversation?", type: "ai_interpreted" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Poetry with cultural context
|
||||||
|
poems: [
|
||||||
|
{
|
||||||
|
title: "Roses Are Red",
|
||||||
|
content: "Roses are red,\nViolets are blue,\nSugar is sweet,\nAnd so are you.",
|
||||||
|
translation: "Les roses sont rouges,\nLes violettes sont bleues,\nLe sucre est doux,\nEt toi aussi.",
|
||||||
|
audioFile: "audio/roses_poem.mp3",
|
||||||
|
culturalContext: "Traditional English nursery rhyme pattern, often used to teach basic rhyming and poetry structure to children."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Twinkle, Twinkle",
|
||||||
|
content: "Twinkle, twinkle, little star,\nHow I wonder what you are.\nUp above the world so high,\nLike a diamond in the sky.",
|
||||||
|
audioFile: "audio/twinkle.mp3",
|
||||||
|
culturalContext: "Famous children's lullaby, one of the most recognizable songs in English-speaking countries."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Fill-in-the-blank exercises
|
||||||
|
fillInBlanks: [
|
||||||
|
{
|
||||||
|
sentence: "I _____ to school every day",
|
||||||
|
options: ["go", "goes", "going", "went"],
|
||||||
|
correctAnswer: "go",
|
||||||
|
explanation: "Present simple with 'I' uses base form of verb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sentence: "She _____ a book right now",
|
||||||
|
options: ["read", "reads", "reading", "is reading"],
|
||||||
|
correctAnswer: "is reading",
|
||||||
|
explanation: "Present continuous for actions happening now"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sentence: "The weather is _____ today",
|
||||||
|
type: "open_ended",
|
||||||
|
acceptedAnswers: ["nice", "good", "beautiful", "sunny", "warm", "pleasant", "lovely"],
|
||||||
|
aiPrompt: "Evaluate if the answer is a positive adjective that could describe good weather"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sentence: "I feel _____ when I listen to music",
|
||||||
|
type: "open_ended",
|
||||||
|
acceptedAnswers: ["happy", "relaxed", "calm", "peaceful", "good", "better"],
|
||||||
|
aiPrompt: "Check if the answer describes a positive emotion or feeling"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Sentence correction exercises
|
||||||
|
corrections: [
|
||||||
|
{
|
||||||
|
incorrect: "I are happy today",
|
||||||
|
correct: "I am happy today",
|
||||||
|
explanation: "Use 'am' with pronoun 'I', not 'are'",
|
||||||
|
type: "grammar_correction"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incorrect: "She don't like apples",
|
||||||
|
correct: "She doesn't like apples",
|
||||||
|
explanation: "Use 'doesn't' with he/she/it, not 'don't'",
|
||||||
|
type: "grammar_correction"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
incorrect: "I can to swim",
|
||||||
|
correct: "I can swim",
|
||||||
|
explanation: "After modal verbs like 'can', use base form without 'to'",
|
||||||
|
type: "grammar_correction"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Reading comprehension with AI evaluation
|
||||||
|
comprehension: [
|
||||||
|
{
|
||||||
|
text: "Sarah is a 25-year-old teacher who lives in London. Every morning, she wakes up at 6:30 AM and goes for a jog in the park near her house. After jogging, she has breakfast and reads the news. She loves her job because she enjoys working with children and helping them learn. On weekends, Sarah likes to visit museums and try new restaurants with her friends.",
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "What is Sarah's profession?",
|
||||||
|
type: "multiple_choice",
|
||||||
|
options: ["Doctor", "Teacher", "Engineer", "Artist"],
|
||||||
|
correctAnswer: "Teacher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "What does Sarah do every morning?",
|
||||||
|
type: "ai_interpreted",
|
||||||
|
evaluationPrompt: "Check if answer mentions waking up early, jogging, and having breakfast"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Why does Sarah love her job?",
|
||||||
|
type: "ai_interpreted",
|
||||||
|
evaluationPrompt: "Verify answer mentions working with children and helping them learn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "How would you describe Sarah's lifestyle?",
|
||||||
|
type: "ai_interpreted",
|
||||||
|
evaluationPrompt: "Accept answers mentioning active, healthy, social, or organized lifestyle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Matching exercises
|
||||||
|
matching: [
|
||||||
|
{
|
||||||
|
title: "Match Animals to Their Sounds",
|
||||||
|
leftColumn: ["Cat", "Dog", "Cow", "Bird"],
|
||||||
|
rightColumn: ["Woof", "Meow", "Tweet", "Moo"],
|
||||||
|
correctPairs: [
|
||||||
|
{ left: "Cat", right: "Meow" },
|
||||||
|
{ left: "Dog", right: "Woof" },
|
||||||
|
{ left: "Cow", right: "Moo" },
|
||||||
|
{ left: "Bird", right: "Tweet" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Match Colors in English and French",
|
||||||
|
leftColumn: ["Red", "Blue", "Green", "Yellow"],
|
||||||
|
rightColumn: ["Bleu", "Vert", "Rouge", "Jaune"],
|
||||||
|
correctPairs: [
|
||||||
|
{ left: "Red", right: "Rouge" },
|
||||||
|
{ left: "Blue", right: "Bleu" },
|
||||||
|
{ left: "Green", right: "Vert" },
|
||||||
|
{ left: "Yellow", right: "Jaune" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Standard content (backward compatibility)
|
||||||
|
sentences: [
|
||||||
|
{ english: "Hello, how are you?", french: "Bonjour, comment allez-vous?" },
|
||||||
|
{ english: "I like to read books", french: "J'aime lire des livres" },
|
||||||
|
{ english: "The weather is nice today", french: "Il fait beau aujourd'hui" },
|
||||||
|
{ english: "Can you help me please?", french: "Pouvez-vous m'aider s'il vous plaît?" }
|
||||||
|
],
|
||||||
|
|
||||||
|
texts: [
|
||||||
|
{
|
||||||
|
title: "My Daily Routine",
|
||||||
|
content: "I wake up at 7 AM every day. First, I brush my teeth and take a shower. Then I have breakfast with my family. After breakfast, I go to work by bus. I work from 9 AM to 5 PM. In the evening, I cook dinner and watch TV. I go to bed at 10 PM.",
|
||||||
|
translation: "Je me réveille à 7h tous les jours. D'abord, je me brosse les dents et prends une douche. Ensuite je prends le petit déjeuner avec ma famille. Après le petit déjeuner, je vais au travail en bus. Je travaille de 9h à 17h. Le soir, je cuisine le dîner et regarde la télé. Je me couche à 22h."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "The Four Seasons",
|
||||||
|
content: "There are four seasons in a year: spring, summer, autumn, and winter. Spring is warm and flowers bloom. Summer is hot and sunny. Autumn is cool and leaves change colors. Winter is cold and it sometimes snows.",
|
||||||
|
translation: "Il y a quatre saisons dans une année: le printemps, l'été, l'automne et l'hiver. Le printemps est chaud et les fleurs fleurissent. L'été est chaud et ensoleillé. L'automne est frais et les feuilles changent de couleur. L'hiver est froid et il neige parfois."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
dialogues: [
|
||||||
|
{
|
||||||
|
title: "At the Restaurant",
|
||||||
|
conversation: [
|
||||||
|
{ speaker: "Waiter", english: "Good evening! Welcome to our restaurant.", french: "Bonsoir! Bienvenue dans notre restaurant." },
|
||||||
|
{ speaker: "Customer", english: "Thank you. Can I see the menu please?", french: "Merci. Puis-je voir le menu s'il vous plaît?" },
|
||||||
|
{ speaker: "Waiter", english: "Of course! Here you are. What would you like to drink?", french: "Bien sûr! Voici. Que voulez-vous boire?" },
|
||||||
|
{ speaker: "Customer", english: "I'll have a glass of water, please.", french: "Je prendrai un verre d'eau, s'il vous plaît." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export for web module system
|
||||||
|
window.ContentModules = window.ContentModules || {};
|
||||||
|
window.ContentModules.EnglishClassDemo = {
|
||||||
|
name: "English Class Demo",
|
||||||
|
description: "Complete example with all content types - vocabulary, grammar, audio, poems, exercises",
|
||||||
|
difficulty: "mixed",
|
||||||
|
language: "english",
|
||||||
|
vocabulary: englishClassDemoContent.vocabulary,
|
||||||
|
grammar: englishClassDemoContent.grammar,
|
||||||
|
audio: englishClassDemoContent.audio,
|
||||||
|
poems: englishClassDemoContent.poems,
|
||||||
|
fillInBlanks: englishClassDemoContent.fillInBlanks,
|
||||||
|
corrections: englishClassDemoContent.corrections,
|
||||||
|
comprehension: englishClassDemoContent.comprehension,
|
||||||
|
matching: englishClassDemoContent.matching,
|
||||||
|
sentences: englishClassDemoContent.sentences,
|
||||||
|
texts: englishClassDemoContent.texts,
|
||||||
|
dialogues: englishClassDemoContent.dialogues
|
||||||
|
};
|
||||||
|
|
||||||
|
// Node.js export (optional)
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = englishClassDemoContent;
|
||||||
|
}
|
||||||
@ -5,7 +5,9 @@ class ContentScanner {
|
|||||||
this.discoveredContent = new Map();
|
this.discoveredContent = new Map();
|
||||||
this.contentFiles = [
|
this.contentFiles = [
|
||||||
// Liste des fichiers de contenu à scanner automatiquement
|
// Liste des fichiers de contenu à scanner automatiquement
|
||||||
'sbs-level-7-8-new.js'
|
'sbs-level-7-8-new.js',
|
||||||
|
'basic-chinese.js',
|
||||||
|
'english-class-demo.js'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -271,7 +271,8 @@ const GameLoader = {
|
|||||||
'temp-games': 'TempGames',
|
'temp-games': 'TempGames',
|
||||||
'fill-the-blank': 'FillTheBlank',
|
'fill-the-blank': 'FillTheBlank',
|
||||||
'text-reader': 'TextReader',
|
'text-reader': 'TextReader',
|
||||||
'adventure-reader': 'AdventureReader'
|
'adventure-reader': 'AdventureReader',
|
||||||
|
'chinese-study': 'ChineseStudy'
|
||||||
};
|
};
|
||||||
return names[gameType] || gameType;
|
return names[gameType] || gameType;
|
||||||
},
|
},
|
||||||
@ -279,7 +280,9 @@ const GameLoader = {
|
|||||||
getContentModuleName(contentType) {
|
getContentModuleName(contentType) {
|
||||||
// Utilise la même logique que le ContentScanner
|
// Utilise la même logique que le ContentScanner
|
||||||
const mapping = {
|
const mapping = {
|
||||||
'sbs-level-7-8-new': 'SBSLevel78New'
|
'sbs-level-7-8-new': 'SBSLevel78New',
|
||||||
|
'basic-chinese': 'BasicChinese',
|
||||||
|
'english-class-demo': 'EnglishClassDemo'
|
||||||
};
|
};
|
||||||
return mapping[contentType] || this.toPascalCase(contentType);
|
return mapping[contentType] || this.toPascalCase(contentType);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -117,6 +117,9 @@ const AppNavigation = {
|
|||||||
this.goBack();
|
this.goBack();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Scroll pour masquer/afficher la breadcrumb
|
||||||
|
this.setupScrollBehavior();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleInitialRoute() {
|
handleInitialRoute() {
|
||||||
@ -133,6 +136,45 @@ const AppNavigation = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setupScrollBehavior() {
|
||||||
|
let lastScrollY = window.scrollY;
|
||||||
|
let breadcrumb = null;
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!breadcrumb) {
|
||||||
|
breadcrumb = document.querySelector('.breadcrumb');
|
||||||
|
if (!breadcrumb) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentScrollY = window.scrollY;
|
||||||
|
|
||||||
|
// Si on scroll vers le bas et qu'on a scrollé plus de 50px
|
||||||
|
if (currentScrollY > lastScrollY && currentScrollY > 50) {
|
||||||
|
breadcrumb.classList.add('hidden');
|
||||||
|
breadcrumb.classList.remove('visible');
|
||||||
|
}
|
||||||
|
// Si on scroll vers le haut ou qu'on est près du top
|
||||||
|
else if (currentScrollY < lastScrollY || currentScrollY <= 50) {
|
||||||
|
breadcrumb.classList.remove('hidden');
|
||||||
|
breadcrumb.classList.add('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScrollY = currentScrollY;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Throttle scroll event pour les performances
|
||||||
|
let ticking = false;
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (!ticking) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
handleScroll();
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Navigation vers une page
|
// Navigation vers une page
|
||||||
navigateTo(page, game = null, content = null) {
|
navigateTo(page, game = null, content = null) {
|
||||||
const params = { page };
|
const params = { page };
|
||||||
|
|||||||
279
js/games/chinese-study.js
Normal file
279
js/games/chinese-study.js
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
// === CHINESE STUDY MODE ===
|
||||||
|
|
||||||
|
class ChineseStudyGame {
|
||||||
|
constructor(options) {
|
||||||
|
this.container = options.container;
|
||||||
|
this.content = options.content;
|
||||||
|
this.onScoreUpdate = options.onScoreUpdate || (() => {});
|
||||||
|
this.onGameEnd = options.onGameEnd || (() => {});
|
||||||
|
|
||||||
|
this.score = 0;
|
||||||
|
this.isRunning = false;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.createInterface();
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
createInterface() {
|
||||||
|
this.container.innerHTML = `
|
||||||
|
<div class="chinese-study-container">
|
||||||
|
<div class="game-header">
|
||||||
|
<h2>🇨🇳 Chinese Study Mode</h2>
|
||||||
|
<div class="score-display">Score: <span id="score">0</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="study-modes">
|
||||||
|
<div class="mode-card" data-mode="characters">
|
||||||
|
<div class="mode-icon">📝</div>
|
||||||
|
<h3>Character Recognition</h3>
|
||||||
|
<p>Learn Chinese characters and their meanings</p>
|
||||||
|
<button class="mode-btn">Start Learning</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mode-card" data-mode="pinyin">
|
||||||
|
<div class="mode-icon">🗣️</div>
|
||||||
|
<h3>Pinyin Practice</h3>
|
||||||
|
<p>Master Chinese pronunciation with pinyin</p>
|
||||||
|
<button class="mode-btn">Start Learning</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mode-card" data-mode="vocabulary">
|
||||||
|
<div class="mode-icon">📚</div>
|
||||||
|
<h3>Vocabulary Cards</h3>
|
||||||
|
<p>Study vocabulary with flashcards</p>
|
||||||
|
<button class="mode-btn">Start Learning</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mode-card" data-mode="writing">
|
||||||
|
<div class="mode-icon">✍️</div>
|
||||||
|
<h3>Stroke Order</h3>
|
||||||
|
<p>Learn proper character writing</p>
|
||||||
|
<button class="mode-btn">Start Learning</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<h3>🚧 Coming Soon!</h3>
|
||||||
|
<p>This is a placeholder for the Chinese study mode. Different learning modules will be implemented here.</p>
|
||||||
|
<p>Available content: ${this.getContentInfo()}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="game-controls">
|
||||||
|
<button onclick="AppNavigation.goBack()" class="back-btn">← Back to Games</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.addStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
const modeCards = this.container.querySelectorAll('.mode-card');
|
||||||
|
modeCards.forEach(card => {
|
||||||
|
card.addEventListener('click', (e) => {
|
||||||
|
const mode = card.dataset.mode;
|
||||||
|
this.showPlaceholder(mode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showPlaceholder(mode) {
|
||||||
|
const modeNames = {
|
||||||
|
characters: 'Character Recognition',
|
||||||
|
pinyin: 'Pinyin Practice',
|
||||||
|
vocabulary: 'Vocabulary Cards',
|
||||||
|
writing: 'Stroke Order'
|
||||||
|
};
|
||||||
|
|
||||||
|
const placeholderDiv = this.container.querySelector('.placeholder-content');
|
||||||
|
placeholderDiv.innerHTML = `
|
||||||
|
<h3>📱 ${modeNames[mode]} Mode</h3>
|
||||||
|
<p>This learning module is being developed!</p>
|
||||||
|
<p>It will include interactive exercises for ${mode} practice.</p>
|
||||||
|
<button onclick="location.reload()" class="retry-btn">← Back to Study Modes</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add some score for interaction
|
||||||
|
this.score += 5;
|
||||||
|
this.onScoreUpdate(this.score);
|
||||||
|
this.container.querySelector('#score').textContent = this.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
getContentInfo() {
|
||||||
|
if (!this.content) return 'No content loaded';
|
||||||
|
|
||||||
|
let info = [];
|
||||||
|
if (this.content.vocabulary) info.push(`${Object.keys(this.content.vocabulary).length} vocabulary items`);
|
||||||
|
if (this.content.sentences) info.push(`${this.content.sentences.length} sentences`);
|
||||||
|
if (this.content.texts) info.push(`${this.content.texts.length} texts`);
|
||||||
|
|
||||||
|
return info.length > 0 ? info.join(', ') : 'Basic Chinese content';
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display {
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #059669;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.study-modes {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-card:hover {
|
||||||
|
border-color: #dc2626;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 20px rgba(220, 38, 38, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
background: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-content {
|
||||||
|
background: #fef3c7;
|
||||||
|
border: 2px solid #f59e0b;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-content h3 {
|
||||||
|
color: #92400e;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-content p {
|
||||||
|
color: #78350f;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-controls {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn, .retry-btn {
|
||||||
|
background: #6b7280;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover, .retry-btn:hover {
|
||||||
|
background: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.study-modes {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chinese-study-container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-header h2 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.isRunning = true;
|
||||||
|
console.log('Chinese Study Mode initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.isRunning = false;
|
||||||
|
// Clean up any intervals, event listeners, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
this.score = 0;
|
||||||
|
this.onScoreUpdate(this.score);
|
||||||
|
this.container.querySelector('#score').textContent = this.score;
|
||||||
|
this.createInterface();
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export to global scope
|
||||||
|
window.GameModules = window.GameModules || {};
|
||||||
|
window.GameModules.ChineseStudy = ChineseStudyGame;
|
||||||
@ -284,9 +284,12 @@ class MemoryMatchGame {
|
|||||||
this.matchedPairs++;
|
this.matchedPairs++;
|
||||||
this.score += 100;
|
this.score += 100;
|
||||||
this.showFeedback('Great match! 🎉', 'success');
|
this.showFeedback('Great match! 🎉', 'success');
|
||||||
|
|
||||||
|
// Trigger success animation
|
||||||
|
this.triggerSuccessAnimation(firstIndex, secondIndex);
|
||||||
|
|
||||||
if (this.matchedPairs === this.totalPairs) {
|
if (this.matchedPairs === this.totalPairs) {
|
||||||
this.gameComplete();
|
setTimeout(() => this.gameComplete(), 800);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No match, flip back and apply penalty
|
// No match, flip back and apply penalty
|
||||||
@ -394,6 +397,54 @@ class MemoryMatchGame {
|
|||||||
this.updateStats();
|
this.updateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
triggerSuccessAnimation(cardIndex1, cardIndex2) {
|
||||||
|
// Get card elements
|
||||||
|
const card1 = document.querySelector(`[data-card-index="${cardIndex1}"]`);
|
||||||
|
const card2 = document.querySelector(`[data-card-index="${cardIndex2}"]`);
|
||||||
|
|
||||||
|
if (!card1 || !card2) return;
|
||||||
|
|
||||||
|
// Add success animation class
|
||||||
|
card1.classList.add('success-animation');
|
||||||
|
card2.classList.add('success-animation');
|
||||||
|
|
||||||
|
// Create sparkle particles for both cards
|
||||||
|
this.createSparkleParticles(card1);
|
||||||
|
this.createSparkleParticles(card2);
|
||||||
|
|
||||||
|
// Remove animation class after animation completes
|
||||||
|
setTimeout(() => {
|
||||||
|
card1.classList.remove('success-animation');
|
||||||
|
card2.classList.remove('success-animation');
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSparkleParticles(cardElement) {
|
||||||
|
const rect = cardElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Create 4 sparkle particles around the card
|
||||||
|
for (let i = 1; i <= 4; i++) {
|
||||||
|
const particle = document.createElement('div');
|
||||||
|
particle.className = `success-particle particle-${i}`;
|
||||||
|
|
||||||
|
// Position relative to card
|
||||||
|
particle.style.position = 'fixed';
|
||||||
|
particle.style.left = (rect.left + rect.width / 2) + 'px';
|
||||||
|
particle.style.top = (rect.top + rect.height / 2) + 'px';
|
||||||
|
particle.style.pointerEvents = 'none';
|
||||||
|
particle.style.zIndex = '1000';
|
||||||
|
|
||||||
|
document.body.appendChild(particle);
|
||||||
|
|
||||||
|
// Remove particle after animation
|
||||||
|
setTimeout(() => {
|
||||||
|
if (particle.parentNode) {
|
||||||
|
particle.parentNode.removeChild(particle);
|
||||||
|
}
|
||||||
|
}, 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.container.innerHTML = '';
|
this.container.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user