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:
StillHammer 2025-09-15 14:45:13 +08:00
commit 855cfe2f94
13 changed files with 1607 additions and 35 deletions

110
.gitignore vendored Normal file
View 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
View 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

View File

@ -49,6 +49,16 @@
"minAge": 8,
"maxAge": 14,
"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": {
@ -123,6 +133,24 @@
"difficulty": "intermediate",
"vocabulary_count": 85,
"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": {

View File

@ -181,6 +181,63 @@
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 {
display: flex;
gap: 15px;
@ -1523,3 +1580,239 @@
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;
}
}

View File

@ -46,6 +46,14 @@ body {
position: relative;
}
/* Optimisation pour écrans larges */
@media (min-width: 1440px) {
.container {
max-width: 1600px;
padding: 20px 40px;
}
}
/* === PAGES === */
.page {
display: none;

View File

@ -2,26 +2,38 @@
.breadcrumb {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 15px 25px;
padding: 8px 20px;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
margin-bottom: 20px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
gap: 8px;
flex-wrap: wrap;
position: sticky;
top: 20px;
position: fixed;
top: 0;
left: 0;
right: 0;
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 {
background: transparent;
border: 2px solid transparent;
color: var(--text-secondary);
padding: 8px 16px;
border-radius: 20px;
font-size: 0.9rem;
padding: 6px 12px;
border-radius: 16px;
font-size: 0.85rem;
font-weight: 500;
transition: var(--transition);
position: relative;
@ -72,22 +84,30 @@
transition: all 0.3s ease-in;
}
/* === BODY PADDING FOR FIXED BREADCRUMB === */
body {
padding-top: 50px;
}
/* === NAVIGATION RESPONSIVE === */
@media (max-width: 768px) {
.breadcrumb {
padding: 12px 20px;
margin-bottom: 15px;
position: static;
padding: 6px 15px;
margin-bottom: 10px;
}
.breadcrumb-item {
padding: 6px 12px;
font-size: 0.85rem;
padding: 4px 10px;
font-size: 0.8rem;
}
.breadcrumb-item:not(:last-child)::after {
right: -12px;
font-size: 0.8rem;
right: -10px;
font-size: 0.75rem;
}
body {
padding-top: 40px;
}
}
@ -238,17 +258,21 @@
@media (max-width: 480px) {
.breadcrumb {
padding: 10px 15px;
gap: 8px;
padding: 5px 12px;
gap: 6px;
}
.breadcrumb-item {
padding: 5px 10px;
font-size: 0.8rem;
padding: 4px 8px;
font-size: 0.75rem;
}
.breadcrumb-item:not(:last-child)::after {
right: -10px;
right: -8px;
}
body {
padding-top: 35px;
}
.content-stats {

115
js/content/basic-chinese.js Normal file
View 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;
}

View 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;
}

View File

@ -5,7 +5,9 @@ class ContentScanner {
this.discoveredContent = new Map();
this.contentFiles = [
// 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'
];
}

View File

@ -271,7 +271,8 @@ const GameLoader = {
'temp-games': 'TempGames',
'fill-the-blank': 'FillTheBlank',
'text-reader': 'TextReader',
'adventure-reader': 'AdventureReader'
'adventure-reader': 'AdventureReader',
'chinese-study': 'ChineseStudy'
};
return names[gameType] || gameType;
},
@ -279,7 +280,9 @@ const GameLoader = {
getContentModuleName(contentType) {
// Utilise la même logique que le ContentScanner
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);
},

View File

@ -117,6 +117,9 @@ const AppNavigation = {
this.goBack();
}
});
// Scroll pour masquer/afficher la breadcrumb
this.setupScrollBehavior();
},
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
navigateTo(page, game = null, content = null) {
const params = { page };

279
js/games/chinese-study.js Normal file
View 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;

View File

@ -285,8 +285,11 @@ class MemoryMatchGame {
this.score += 100;
this.showFeedback('Great match! 🎉', 'success');
// Trigger success animation
this.triggerSuccessAnimation(firstIndex, secondIndex);
if (this.matchedPairs === this.totalPairs) {
this.gameComplete();
setTimeout(() => this.gameComplete(), 800);
}
} else {
// No match, flip back and apply penalty
@ -394,6 +397,54 @@ class MemoryMatchGame {
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() {
this.container.innerHTML = '';
}