Add comprehensive game suite with TTS integration and content modules
🎮 NEW GAMES: - River Run: Endless runner with word collection and guaranteed target spawning - Grammar Discovery: Focused grammar learning with 8-step rotation cycles - Letter Discovery: Letter-first alphabet learning with progression system - Enhanced Word Discovery: Shuffled practice mode with image support and auto-TTS 📚 NEW CONTENT MODULES: - WTA1B-1: English letters U,V,T with pets vocabulary and Chinese translation - SBS-1: English "To Be" introduction with comprehensive grammar lessons - French Beginner Story: Story content for English speakers learning French 🔊 TTS ENHANCEMENTS: - Story Reader: Multi-story support with TTS for sentences and individual words - Adventure Reader: Auto-TTS for vocabulary popups and sentence modals - Word Discovery: Immediate TTS playback with difficulty-based speed control - Integrated SettingsManager compatibility across all games 🎯 GAMEPLAY IMPROVEMENTS: - River Run: Target word guaranteed within 10 spawns, progressive spacing - Story Reader: Story selector dropdown with independent progress tracking - Adventure Reader: Fixed HUD overlap issue with proper viewport spacing - Enhanced punctuation preservation in Story Reader word parsing ✨ SYSTEM UPDATES: - Content scanner integration for all new modules - Game loader mappings for seamless content discovery - Simplified content titles: "WTA1B-1" and "SBS-1" for easy identification - Comprehensive test files for isolated game development 🎉 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
475006e912
commit
e50dd624a0
@ -437,9 +437,11 @@
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
user-select: none;
|
||||
padding-top: 20px; /* Add space to prevent overlap with top navigation */
|
||||
}
|
||||
|
||||
.game-hud {
|
||||
.adventure-reader-wrapper .game-hud {
|
||||
position: relative; /* Override the absolute positioning */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@ -448,6 +450,11 @@
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow-light);
|
||||
border: 2px solid var(--primary-color);
|
||||
top: auto; /* Reset top positioning */
|
||||
left: auto; /* Reset left positioning */
|
||||
right: auto; /* Reset right positioning */
|
||||
height: auto; /* Allow natural height */
|
||||
z-index: auto; /* Reset z-index */
|
||||
}
|
||||
|
||||
.hud-left {
|
||||
@ -1734,6 +1741,7 @@
|
||||
max-width: 1400px;
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
padding-top: 20px; /* Maintain top padding on large screens */
|
||||
}
|
||||
|
||||
.game-map {
|
||||
|
||||
479
js/content/SBS-level-1.js
Normal file
479
js/content/SBS-level-1.js
Normal file
@ -0,0 +1,479 @@
|
||||
// === ENGLISH LEARNING MODULE ===
|
||||
// Complete English learning module with Chinese translation and pronunciation
|
||||
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
|
||||
window.ContentModules.SBSLevel1 = {
|
||||
id: "sbs-level-1",
|
||||
name: "SBS-1",
|
||||
description: "English introduction lessons with Chinese translation and pronunciation",
|
||||
difficulty: "beginner",
|
||||
language: "en-US",
|
||||
userLanguage: "zh-CN",
|
||||
totalWords: 150,
|
||||
|
||||
// === GRAMMAR LESSONS SYSTEM ===
|
||||
grammar: {
|
||||
"to-be-verb": {
|
||||
title: "The Verb 'To Be' - 动词Be",
|
||||
explanation: "The verb 'be' is one of the most important verbs in English, used to describe states, identity, and location.",
|
||||
rules: [
|
||||
"I am - 我是 (first person singular)",
|
||||
"You are - 你是/你们是 (second person)",
|
||||
"He/She/It is - 他/她/它是 (third person singular)",
|
||||
"We are - 我们是 (first person plural)",
|
||||
"They are - 他们是 (third person plural)"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
english: "My name is Maria.",
|
||||
chinese: "我的名字是玛丽亚。",
|
||||
explanation: "Use 'is' because 'name' is third person singular",
|
||||
pronunciation: "/maɪ neɪm ɪz məˈriːə/"
|
||||
},
|
||||
{
|
||||
english: "I am from Mexico City.",
|
||||
chinese: "我来自墨西哥城。",
|
||||
explanation: "Use 'am' because the subject is 'I'",
|
||||
pronunciation: "/aɪ æm frʌm ˈmeksɪkoʊ ˈsɪti/"
|
||||
},
|
||||
{
|
||||
english: "Where are you from?",
|
||||
chinese: "你来自哪里?",
|
||||
explanation: "Use 'are' because the subject is 'you'",
|
||||
pronunciation: "/wer ɑr ju frʌm/"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "fill_blank",
|
||||
sentence: "My address _____ 235 Main Street.",
|
||||
options: ["am", "is", "are"],
|
||||
correct: "is",
|
||||
explanation: "Use 'is' because 'address' is third person singular"
|
||||
},
|
||||
{
|
||||
type: "translation",
|
||||
english: "What's your phone number?",
|
||||
chinese: "你的电话号码是多少?",
|
||||
focus: "Contraction What's = What is"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"contractions": {
|
||||
title: "Contractions - 缩写形式",
|
||||
explanation: "English often uses contractions to make conversation more natural and fluent.",
|
||||
rules: [
|
||||
"What's = What is - 什么是",
|
||||
"I'm = I am - 我是",
|
||||
"You're = You are - 你是",
|
||||
"He's/She's/It's = He/She/It is - 他/她/它是"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
english: "What's your name?",
|
||||
chinese: "你叫什么名字?",
|
||||
explanation: "What's is the contraction of What is",
|
||||
pronunciation: "/wʌts jʊr neɪm/"
|
||||
},
|
||||
{
|
||||
english: "I'm Nancy Lee.",
|
||||
chinese: "我是南希·李。",
|
||||
explanation: "I'm is the contraction of I am",
|
||||
pronunciation: "/aɪm ˈnænsi li/"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "contraction_match",
|
||||
full_form: "What is your address?",
|
||||
contracted: "What's your address?",
|
||||
chinese: "你的地址是什么?"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"personal-information": {
|
||||
title: "Personal Information - 个人信息",
|
||||
explanation: "Learn how to ask for and provide basic personal information in English.",
|
||||
rules: [
|
||||
"Name - 姓名: What's your name? My name is...",
|
||||
"Address - 地址: What's your address? My address is...",
|
||||
"Phone - 电话: What's your phone number? My phone number is...",
|
||||
"Origin - 来源: Where are you from? I'm from..."
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
english: "My name is David Carter.",
|
||||
chinese: "我的名字是大卫·卡特。",
|
||||
explanation: "Standard expression for introducing name",
|
||||
pronunciation: "/maɪ neɪm ɪz ˈdeɪvɪd ˈkɑrtər/"
|
||||
},
|
||||
{
|
||||
english: "I'm from San Francisco.",
|
||||
chinese: "我来自旧金山。",
|
||||
explanation: "Expression for stating origin",
|
||||
pronunciation: "/aɪm frʌm sæn frænˈsɪskoʊ/"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "dialogue_completion",
|
||||
prompt: "A: What's your name? B: _____",
|
||||
answer: "My name is [your name].",
|
||||
chinese: "A: 你叫什么名字? B: 我的名字是[你的名字]。"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"meeting-people": {
|
||||
title: "Meeting People - 与人见面",
|
||||
explanation: "Common phrases and expressions used when meeting new people.",
|
||||
rules: [
|
||||
"Hello - 你好 (formal greeting)",
|
||||
"Hi - 嗨 (informal greeting)",
|
||||
"Nice to meet you - 很高兴认识你",
|
||||
"Nice to meet you, too - 我也很高兴认识你"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
english: "Hello. My name is Peter Lewis.",
|
||||
chinese: "你好。我的名字是彼得·刘易斯。",
|
||||
explanation: "Formal introduction",
|
||||
pronunciation: "/həˈloʊ maɪ neɪm ɪz ˈpitər ˈluɪs/"
|
||||
},
|
||||
{
|
||||
english: "Hi. I'm Nancy Lee. Nice to meet you.",
|
||||
chinese: "嗨。我是南希·李。很高兴认识你。",
|
||||
explanation: "Informal introduction with greeting",
|
||||
pronunciation: "/haɪ aɪm ˈnænsi li naɪs tu mit ju/"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "role_play",
|
||||
scenario: "Meeting someone new",
|
||||
dialogue: "A: Hello. B: Hi. A: What's your name? B: My name is ____."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
vocabulary: {
|
||||
"name": {
|
||||
"user_language": "名字",
|
||||
"type": "noun",
|
||||
"pronunciation": "/neɪm/"
|
||||
},
|
||||
"address": {
|
||||
"user_language": "地址",
|
||||
"type": "noun",
|
||||
"pronunciation": "/əˈdres/"
|
||||
},
|
||||
"phone number": {
|
||||
"user_language": "电话号码",
|
||||
"type": "noun",
|
||||
"pronunciation": "/foʊn ˈnʌmbər/"
|
||||
},
|
||||
"telephone number": {
|
||||
"user_language": "电话号码",
|
||||
"type": "noun",
|
||||
"pronunciation": "/ˈteləfoʊn ˈnʌmbər/"
|
||||
},
|
||||
"apartment number": {
|
||||
"user_language": "公寓号码",
|
||||
"type": "noun",
|
||||
"pronunciation": "/əˈpɑrtmənt ˈnʌmbər/"
|
||||
},
|
||||
"e-mail address": {
|
||||
"user_language": "电子邮件地址",
|
||||
"type": "noun",
|
||||
"pronunciation": "/ˈiːmeɪl əˈdres/"
|
||||
},
|
||||
"first name": {
|
||||
"user_language": "名",
|
||||
"type": "noun",
|
||||
"pronunciation": "/fɜrst neɪm/"
|
||||
},
|
||||
"last name": {
|
||||
"user_language": "姓",
|
||||
"type": "noun",
|
||||
"pronunciation": "/læst neɪm/"
|
||||
},
|
||||
"hello": {
|
||||
"user_language": "你好",
|
||||
"type": "interjection",
|
||||
"pronunciation": "/həˈloʊ/"
|
||||
},
|
||||
"hi": {
|
||||
"user_language": "嗨",
|
||||
"type": "interjection",
|
||||
"pronunciation": "/haɪ/"
|
||||
},
|
||||
"nice to meet you": {
|
||||
"user_language": "很高兴认识你",
|
||||
"type": "phrase",
|
||||
"pronunciation": "/naɪs tu mit ju/"
|
||||
},
|
||||
"where": {
|
||||
"user_language": "哪里",
|
||||
"type": "adverb",
|
||||
"pronunciation": "/wer/"
|
||||
},
|
||||
"from": {
|
||||
"user_language": "来自",
|
||||
"type": "preposition",
|
||||
"pronunciation": "/frʌm/"
|
||||
},
|
||||
"alphabet": {
|
||||
"user_language": "字母表",
|
||||
"type": "noun",
|
||||
"pronunciation": "/ˈælfəbet/"
|
||||
},
|
||||
"numbers": {
|
||||
"user_language": "数字",
|
||||
"type": "noun",
|
||||
"pronunciation": "/ˈnʌmbərz/"
|
||||
}
|
||||
},
|
||||
|
||||
story: {
|
||||
title: "To Be: Introduction - 动词Be的介绍",
|
||||
totalSentences: 50,
|
||||
chapters: [
|
||||
{
|
||||
title: "Chapter 1: Vocabulary Preview - 第一章:词汇预览",
|
||||
sentences: [
|
||||
{
|
||||
id: 1,
|
||||
original: "Learn the alphabet: Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
|
||||
translation: "学习字母表:Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
|
||||
words: [
|
||||
{word: "Learn", translation: "学习", type: "verb", pronunciation: "/lɜrn/"},
|
||||
{word: "alphabet", translation: "字母表", type: "noun", pronunciation: "/ˈælfəbet/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
original: "Practice numbers: 0 1 2 3 4 5 6 7 8 9 10",
|
||||
translation: "练习数字:0 1 2 3 4 5 6 7 8 9 10",
|
||||
words: [
|
||||
{word: "Practice", translation: "练习", type: "verb", pronunciation: "/ˈpræktɪs/"},
|
||||
{word: "numbers", translation: "数字", type: "noun", pronunciation: "/ˈnʌmbərz/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
original: "This is Maria's name tag.",
|
||||
translation: "这是玛丽亚的姓名牌。",
|
||||
words: [
|
||||
{word: "This", translation: "这", type: "pronoun", pronunciation: "/ðɪs/"},
|
||||
{word: "is", translation: "是", type: "verb", pronunciation: "/ɪz/"},
|
||||
{word: "Maria's", translation: "玛丽亚的", type: "possessive", pronunciation: "/məˈriːəz/"},
|
||||
{word: "name", translation: "姓名", type: "noun", pronunciation: "/neɪm/"},
|
||||
{word: "tag", translation: "牌", type: "noun", pronunciation: "/tæg/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
original: "235 Main Street is an address.",
|
||||
translation: "主街235号是一个地址。",
|
||||
words: [
|
||||
{word: "235", translation: "235", type: "number", pronunciation: "/tu ˈθɜrti faɪv/"},
|
||||
{word: "Main", translation: "主要的", type: "adjective", pronunciation: "/meɪn/"},
|
||||
{word: "Street", translation: "街", type: "noun", pronunciation: "/strit/"},
|
||||
{word: "is", translation: "是", type: "verb", pronunciation: "/ɪz/"},
|
||||
{word: "an", translation: "一个", type: "article", pronunciation: "/æn/"},
|
||||
{word: "address", translation: "地址", type: "noun", pronunciation: "/əˈdres/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
original: "741-8906 is a telephone number.",
|
||||
translation: "741-8906是一个电话号码。",
|
||||
words: [
|
||||
{word: "741-8906", translation: "741-8906", type: "number", pronunciation: "/ˈsevən fɔr wʌn eɪt naɪn oʊ sɪks/"},
|
||||
{word: "is", translation: "是", type: "verb", pronunciation: "/ɪz/"},
|
||||
{word: "a", translation: "一个", type: "article", pronunciation: "/ə/"},
|
||||
{word: "telephone", translation: "电话", type: "noun", pronunciation: "/ˈteləfoʊn/"},
|
||||
{word: "number", translation: "号码", type: "noun", pronunciation: "/ˈnʌmbər/"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Chapter 2: What's Your Name? - 第二章:你叫什么名字?",
|
||||
sentences: [
|
||||
{
|
||||
id: 6,
|
||||
original: "What's your name?",
|
||||
translation: "你叫什么名字?",
|
||||
words: [
|
||||
{word: "What's", translation: "什么是", type: "contraction", pronunciation: "/wʌts/"},
|
||||
{word: "your", translation: "你的", type: "possessive", pronunciation: "/jʊr/"},
|
||||
{word: "name", translation: "名字", type: "noun", pronunciation: "/neɪm/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
original: "My name is Maria.",
|
||||
translation: "我的名字是玛丽亚。",
|
||||
words: [
|
||||
{word: "My", translation: "我的", type: "possessive", pronunciation: "/maɪ/"},
|
||||
{word: "name", translation: "名字", type: "noun", pronunciation: "/neɪm/"},
|
||||
{word: "is", translation: "是", type: "verb", pronunciation: "/ɪz/"},
|
||||
{word: "Maria", translation: "玛丽亚", type: "name", pronunciation: "/məˈriːə/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
original: "What's your address?",
|
||||
translation: "你的地址是什么?",
|
||||
words: [
|
||||
{word: "What's", translation: "什么是", type: "contraction", pronunciation: "/wʌts/"},
|
||||
{word: "your", translation: "你的", type: "possessive", pronunciation: "/jʊr/"},
|
||||
{word: "address", translation: "地址", type: "noun", pronunciation: "/əˈdres/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
original: "My address is 235 Main Street.",
|
||||
translation: "我的地址是主街235号。",
|
||||
words: [
|
||||
{word: "My", translation: "我的", type: "possessive", pronunciation: "/maɪ/"},
|
||||
{word: "address", translation: "地址", type: "noun", pronunciation: "/əˈdres/"},
|
||||
{word: "is", translation: "是", type: "verb", pronunciation: "/ɪz/"},
|
||||
{word: "235", translation: "235", type: "number", pronunciation: "/tu ˈθɜrti faɪv/"},
|
||||
{word: "Main", translation: "主要的", type: "adjective", pronunciation: "/meɪn/"},
|
||||
{word: "Street", translation: "街", type: "noun", pronunciation: "/strit/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
original: "I'm from Mexico City.",
|
||||
translation: "我来自墨西哥城。",
|
||||
words: [
|
||||
{word: "I'm", translation: "我是", type: "contraction", pronunciation: "/aɪm/"},
|
||||
{word: "from", translation: "来自", type: "preposition", pronunciation: "/frʌm/"},
|
||||
{word: "Mexico", translation: "墨西哥", type: "place", pronunciation: "/ˈmeksɪkoʊ/"},
|
||||
{word: "City", translation: "城", type: "noun", pronunciation: "/ˈsɪti/"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Chapter 3: Meeting People - 第三章:与人见面",
|
||||
sentences: [
|
||||
{
|
||||
id: 11,
|
||||
original: "Hello. My name is Peter Lewis.",
|
||||
translation: "你好。我的名字是彼得·刘易斯。",
|
||||
words: [
|
||||
{word: "Hello", translation: "你好", type: "interjection", pronunciation: "/həˈloʊ/"},
|
||||
{word: "My", translation: "我的", type: "possessive", pronunciation: "/maɪ/"},
|
||||
{word: "name", translation: "名字", type: "noun", pronunciation: "/neɪm/"},
|
||||
{word: "is", translation: "是", type: "verb", pronunciation: "/ɪz/"},
|
||||
{word: "Peter", translation: "彼得", type: "name", pronunciation: "/ˈpitər/"},
|
||||
{word: "Lewis", translation: "刘易斯", type: "name", pronunciation: "/ˈluɪs/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
original: "Hi. I'm Nancy Lee. Nice to meet you.",
|
||||
translation: "嗨。我是南希·李。很高兴认识你。",
|
||||
words: [
|
||||
{word: "Hi", translation: "嗨", type: "interjection", pronunciation: "/haɪ/"},
|
||||
{word: "I'm", translation: "我是", type: "contraction", pronunciation: "/aɪm/"},
|
||||
{word: "Nancy", translation: "南希", type: "name", pronunciation: "/ˈnænsi/"},
|
||||
{word: "Lee", translation: "李", type: "name", pronunciation: "/li/"},
|
||||
{word: "Nice", translation: "很好的", type: "adjective", pronunciation: "/naɪs/"},
|
||||
{word: "to", translation: "到", type: "preposition", pronunciation: "/tu/"},
|
||||
{word: "meet", translation: "遇见", type: "verb", pronunciation: "/mit/"},
|
||||
{word: "you", translation: "你", type: "pronoun", pronunciation: "/ju/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
original: "Nice to meet you, too.",
|
||||
translation: "我也很高兴认识你。",
|
||||
words: [
|
||||
{word: "Nice", translation: "很好的", type: "adjective", pronunciation: "/naɪs/"},
|
||||
{word: "to", translation: "到", type: "preposition", pronunciation: "/tu/"},
|
||||
{word: "meet", translation: "遇见", type: "verb", pronunciation: "/mit/"},
|
||||
{word: "you", translation: "你", type: "pronoun", pronunciation: "/ju/"},
|
||||
{word: "too", translation: "也", type: "adverb", pronunciation: "/tu/"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// === GRAMMAR-BASED FILL IN THE BLANKS ===
|
||||
fillInBlanks: [
|
||||
{
|
||||
sentence: "My name _____ David.",
|
||||
options: ["am", "is", "are"],
|
||||
correctAnswer: "is",
|
||||
explanation: "Use 'is' because 'name' is third person singular",
|
||||
grammarFocus: "to-be-verb"
|
||||
},
|
||||
{
|
||||
sentence: "I _____ from China.",
|
||||
options: ["am", "is", "are"],
|
||||
correctAnswer: "am",
|
||||
explanation: "Use 'am' because the subject is 'I'",
|
||||
grammarFocus: "to-be-verb"
|
||||
},
|
||||
{
|
||||
sentence: "_____ your phone number?",
|
||||
options: ["What", "What's", "Where"],
|
||||
correctAnswer: "What's",
|
||||
explanation: "What's = What is, used to ask for phone number",
|
||||
grammarFocus: "contractions"
|
||||
},
|
||||
{
|
||||
sentence: "Where _____ you from?",
|
||||
options: ["am", "is", "are"],
|
||||
correctAnswer: "are",
|
||||
explanation: "Use 'are' because the subject is 'you'",
|
||||
grammarFocus: "to-be-verb"
|
||||
},
|
||||
{
|
||||
sentence: "_____ to meet you.",
|
||||
options: ["Nice", "Good", "Fine"],
|
||||
correctAnswer: "Nice",
|
||||
explanation: "Standard expression for meeting people",
|
||||
grammarFocus: "meeting-people"
|
||||
}
|
||||
],
|
||||
|
||||
// === GRAMMAR CORRECTION EXERCISES ===
|
||||
corrections: [
|
||||
{
|
||||
incorrect: "My name are John.",
|
||||
correct: "My name is John.",
|
||||
explanation: "'Name' is third person singular, so use 'is'",
|
||||
grammarFocus: "to-be-verb"
|
||||
},
|
||||
{
|
||||
incorrect: "Where you are from?",
|
||||
correct: "Where are you from?",
|
||||
explanation: "In questions, the be verb comes before the subject",
|
||||
grammarFocus: "to-be-verb"
|
||||
},
|
||||
{
|
||||
incorrect: "What is you name?",
|
||||
correct: "What is your name?",
|
||||
explanation: "Use possessive 'your' not subject pronoun 'you'",
|
||||
grammarFocus: "personal-information"
|
||||
},
|
||||
{
|
||||
incorrect: "I are from Mexico.",
|
||||
correct: "I am from Mexico.",
|
||||
explanation: "Use 'am' with subject 'I'",
|
||||
grammarFocus: "to-be-verb"
|
||||
}
|
||||
]
|
||||
};
|
||||
1094
js/content/WTA1B1.js
Normal file
1094
js/content/WTA1B1.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,173 @@ window.ContentModules.ChineseLongStory = {
|
||||
language: "zh-CN",
|
||||
totalWords: 1200,
|
||||
|
||||
// === GRAMMAR LESSONS SYSTEM ===
|
||||
grammar: {
|
||||
"chinese-particles": {
|
||||
title: "Chinese Grammar Particles - 语法助词",
|
||||
explanation: "Chinese particles are essential grammatical markers that show relationships between words and add meaning to sentences.",
|
||||
rules: [
|
||||
"的 (de) - Possessive marker and adjective connector",
|
||||
"在 (zài) - Location and time marker 'at/in/on'",
|
||||
"里 (lǐ) - Inside/within location marker",
|
||||
"中 (zhōng) - In/among/middle position marker"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
chinese: "老人的故事",
|
||||
english: "the old man's story",
|
||||
explanation: "的 shows possession - 'old man's'",
|
||||
pronunciation: "lǎo rén de gù shì"
|
||||
},
|
||||
{
|
||||
chinese: "在山里",
|
||||
english: "in the mountains",
|
||||
explanation: "在...里 shows location 'in/inside'",
|
||||
pronunciation: "zài shān lǐ"
|
||||
},
|
||||
{
|
||||
chinese: "村庄中的龙",
|
||||
english: "the dragon in the village",
|
||||
explanation: "中 shows position 'in/among'",
|
||||
pronunciation: "cūn zhuāng zhōng de lóng"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "fill_blank",
|
||||
sentence: "这是小明___书包",
|
||||
options: ["的", "在", "里", "中"],
|
||||
correct: "的",
|
||||
explanation: "Use 的 for possession - 'Xiaoming's backpack'"
|
||||
},
|
||||
{
|
||||
type: "translation",
|
||||
chinese: "龙在水里",
|
||||
english: "The dragon is in the water",
|
||||
focus: "Location marker 在...里"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"chinese-word-order": {
|
||||
title: "Chinese Word Order - 中文语序",
|
||||
explanation: "Chinese follows Subject-Verb-Object order like English, but with important differences for time, place, and manner.",
|
||||
rules: [
|
||||
"Basic pattern: Subject + Time + Place + Verb + Object",
|
||||
"Time comes before place: '昨天在家' (yesterday at home)",
|
||||
"Manner often comes before verb: '慢慢地走' (slowly walk)",
|
||||
"Place words use specific markers: 在 (at), 里 (in), 上 (on)"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
chinese: "老人昨天在村庄里讲故事",
|
||||
english: "The old man told stories in the village yesterday",
|
||||
breakdown: "老人(S) + 昨天(Time) + 在村庄里(Place) + 讲(V) + 故事(O)",
|
||||
pronunciation: "lǎo rén zuó tiān zài cūn zhuāng lǐ jiǎng gù shì"
|
||||
},
|
||||
{
|
||||
chinese: "龙慢慢地飞向山顶",
|
||||
english: "The dragon slowly flew toward the mountain peak",
|
||||
breakdown: "龙(S) + 慢慢地(Manner) + 飞向(V) + 山顶(O)",
|
||||
pronunciation: "lóng màn màn de fēi xiàng shān dǐng"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "word_order",
|
||||
scrambled: ["在", "昨天", "老人", "家里", "休息"],
|
||||
correct: ["老人", "昨天", "在", "家里", "休息"],
|
||||
english: "The old man rested at home yesterday"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"measure-words": {
|
||||
title: "Chinese Measure Words - 量词",
|
||||
explanation: "Chinese uses specific measure words (classifiers) between numbers and nouns, similar to 'a piece of paper' in English.",
|
||||
rules: [
|
||||
"Pattern: Number + Measure Word + Noun",
|
||||
"个 (gè) - Most common, used for people and general objects",
|
||||
"条 (tiáo) - For long, thin things like dragons, rivers, roads",
|
||||
"座 (zuò) - For mountains, buildings, bridges",
|
||||
"本 (běn) - For books, magazines"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
chinese: "一条龙",
|
||||
english: "one dragon",
|
||||
explanation: "条 is used for long creatures like dragons",
|
||||
pronunciation: "yì tiáo lóng"
|
||||
},
|
||||
{
|
||||
chinese: "三座山",
|
||||
english: "three mountains",
|
||||
explanation: "座 is used for large structures like mountains",
|
||||
pronunciation: "sān zuò shān"
|
||||
},
|
||||
{
|
||||
chinese: "两个老人",
|
||||
english: "two old people",
|
||||
explanation: "个 is the general classifier for people",
|
||||
pronunciation: "liǎng gè lǎo rén"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "classifier_choice",
|
||||
chinese: "五___珠子",
|
||||
options: ["个", "条", "座", "本"],
|
||||
correct: "个",
|
||||
explanation: "珠子 (pearls) use 个 as the general classifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"chinese-tones": {
|
||||
title: "Chinese Tones - 声调",
|
||||
explanation: "Mandarin Chinese has 4 main tones that change word meaning. Tone is crucial for communication.",
|
||||
rules: [
|
||||
"First tone (ā) - High, flat tone",
|
||||
"Second tone (á) - Rising tone, like asking a question",
|
||||
"Third tone (ǎ) - Falling then rising, dip tone",
|
||||
"Fourth tone (à) - Sharp falling tone",
|
||||
"Neutral tone (a) - Light, quick, no specific pitch"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
chinese: "妈 (mā) - mother",
|
||||
tone: "First tone - high and flat",
|
||||
pronunciation: "mā"
|
||||
},
|
||||
{
|
||||
chinese: "麻 (má) - hemp/numb",
|
||||
tone: "Second tone - rising",
|
||||
pronunciation: "má"
|
||||
},
|
||||
{
|
||||
chinese: "马 (mǎ) - horse",
|
||||
tone: "Third tone - dip",
|
||||
pronunciation: "mǎ"
|
||||
},
|
||||
{
|
||||
chinese: "骂 (mà) - to scold",
|
||||
tone: "Fourth tone - falling",
|
||||
pronunciation: "mà"
|
||||
}
|
||||
],
|
||||
exercises: [
|
||||
{
|
||||
type: "tone_identification",
|
||||
word: "山",
|
||||
pronunciation: "shān",
|
||||
tone_options: ["First", "Second", "Third", "Fourth"],
|
||||
correct: "First",
|
||||
explanation: "山 (shān) uses first tone - high and flat"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
vocabulary: {
|
||||
"龙": {
|
||||
"user_language": "dragon",
|
||||
@ -336,5 +503,73 @@ window.ContentModules.ChineseLongStory = {
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// === GRAMMAR-BASED FILL IN THE BLANKS ===
|
||||
fillInBlanks: [
|
||||
{
|
||||
sentence: "这是老人___故事",
|
||||
options: ["的", "在", "里", "中"],
|
||||
correctAnswer: "的",
|
||||
explanation: "Use 的 to show possession - 'the old man's story'",
|
||||
grammarFocus: "chinese-particles"
|
||||
},
|
||||
{
|
||||
sentence: "龙___水里游泳",
|
||||
options: ["的", "在", "里", "中"],
|
||||
correctAnswer: "在",
|
||||
explanation: "Use 在 to show location - 'the dragon is swimming in the water'",
|
||||
grammarFocus: "chinese-particles"
|
||||
},
|
||||
{
|
||||
sentence: "一___龙飞向天空",
|
||||
options: ["个", "条", "座", "本"],
|
||||
correctAnswer: "条",
|
||||
explanation: "Use 条 for long creatures like dragons",
|
||||
grammarFocus: "measure-words"
|
||||
},
|
||||
{
|
||||
sentence: "三___山很高",
|
||||
options: ["个", "条", "座", "本"],
|
||||
correctAnswer: "座",
|
||||
explanation: "Use 座 for large structures like mountains",
|
||||
grammarFocus: "measure-words"
|
||||
},
|
||||
{
|
||||
sentence: "老人昨天___村庄里讲故事",
|
||||
options: ["在", "的", "里", "中"],
|
||||
correctAnswer: "在",
|
||||
explanation: "Word order: Subject + Time + Place (在 + location) + Verb + Object",
|
||||
grammarFocus: "chinese-word-order"
|
||||
},
|
||||
{
|
||||
sentence: "山___读音是第几声?",
|
||||
options: ["第一声", "第二声", "第三声", "第四声"],
|
||||
correctAnswer: "第一声",
|
||||
explanation: "山 (shān) uses first tone - high and flat",
|
||||
grammarFocus: "chinese-tones"
|
||||
}
|
||||
],
|
||||
|
||||
// === GRAMMAR CORRECTION EXERCISES ===
|
||||
corrections: [
|
||||
{
|
||||
incorrect: "龙里水游泳",
|
||||
correct: "龙在水里游泳",
|
||||
explanation: "Need 在 (at/in) before location marker 里",
|
||||
grammarFocus: "chinese-particles"
|
||||
},
|
||||
{
|
||||
incorrect: "老人在村庄昨天讲故事",
|
||||
correct: "老人昨天在村庄里讲故事",
|
||||
explanation: "Time (昨天) must come before place (在村庄里)",
|
||||
grammarFocus: "chinese-word-order"
|
||||
},
|
||||
{
|
||||
incorrect: "五龙飞在天空",
|
||||
correct: "五条龙飞在天空中",
|
||||
explanation: "Need measure word 条 for dragons and location marker 中",
|
||||
grammarFocus: "measure-words"
|
||||
}
|
||||
]
|
||||
};
|
||||
524
js/content/french-beginner-story.js
Normal file
524
js/content/french-beginner-story.js
Normal file
@ -0,0 +1,524 @@
|
||||
// === CHINESE BEGINNER STORY ===
|
||||
// Histoire chinoise pour débutants+ avec traduction française et prononciation pinyin
|
||||
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
|
||||
window.ContentModules.FrenchBeginnerStory = {
|
||||
id: "french-beginner-story",
|
||||
name: "Le Jardin Magique - The Magic Garden",
|
||||
description: "Simple French story for English speakers",
|
||||
difficulty: "beginner-plus",
|
||||
language: "fr-FR", // Target language = français
|
||||
userLanguage: "en-US", // User language = anglais
|
||||
totalWords: 15,
|
||||
type: "story_course",
|
||||
|
||||
// === GRAMMAIRE DE BASE ===
|
||||
grammar: {
|
||||
"basic-sentence-structure": {
|
||||
title: "French Basic Sentence Structure - Structure de Phrase Française",
|
||||
explanation: "French follows Subject-Verb-Object order like English, but with some important differences.",
|
||||
mainRules: [
|
||||
"Subject + Verb + Object (Je mange une pomme - I eat an apple)",
|
||||
"French verbs conjugate according to the subject (je mange, tu manges, il mange)",
|
||||
"Adjectives usually come after the noun (une fleur rouge - a red flower)",
|
||||
"French nouns have gender (masculine/feminine)"
|
||||
],
|
||||
examples: [
|
||||
{
|
||||
french: "J'aime les fleurs",
|
||||
english: "I love flowers",
|
||||
pronunciation: "ʒɛm le flœʁ",
|
||||
explanation: "Basic structure: Je(I) + aime(love) + les fleurs(flowers)"
|
||||
},
|
||||
{
|
||||
french: "Le jardin est très beau",
|
||||
english: "The garden is very beautiful",
|
||||
pronunciation: "lə ʒaʁdɛ̃ ɛ tʁɛ bo",
|
||||
explanation: "être(to be) + adjective structure"
|
||||
}
|
||||
],
|
||||
detailedExplanation: {
|
||||
"subject-verb-object": {
|
||||
title: "Ordre Sujet-Verbe-Objet",
|
||||
explanation: "Le chinois suit la même logique que le français pour l'ordre des mots de base.",
|
||||
pattern: "Sujet + Verbe + Objet",
|
||||
examples: [
|
||||
{
|
||||
chinese: "小猫吃鱼",
|
||||
english: "Le petit chat mange du poisson",
|
||||
pronunciation: "xiǎo māo chī yú",
|
||||
breakdown: "小猫(petit chat) + 吃(manger) + 鱼(poisson)"
|
||||
},
|
||||
{
|
||||
chinese: "我看书",
|
||||
english: "Je lis un livre",
|
||||
pronunciation: "wǒ kàn shū",
|
||||
breakdown: "我(je) + 看(regarder/lire) + 书(livre)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"adjectives": {
|
||||
title: "Utilisation des Adjectifs",
|
||||
explanation: "Les adjectifs peuvent être utilisés directement après 很 (très) sans verbe 'être'.",
|
||||
pattern: "Sujet + 很 + Adjectif",
|
||||
examples: [
|
||||
{
|
||||
chinese: "花很红",
|
||||
english: "La fleur est très rouge",
|
||||
pronunciation: "huā hěn hóng",
|
||||
breakdown: "花(fleur) + 很(très) + 红(rouge)"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
commonMistakes: [
|
||||
{
|
||||
mistake: "Conjuguer les verbes",
|
||||
wrong: "我吃了,你吃着,他吃的",
|
||||
correct: "我吃,你吃,他吃",
|
||||
explanation: "Les verbes chinois ne se conjuguent pas selon la personne"
|
||||
},
|
||||
{
|
||||
mistake: "Oublier 很 avec les adjectifs",
|
||||
wrong: "花园美",
|
||||
correct: "花园很美",
|
||||
explanation: "Utiliser 很 devant les adjectifs pour une phrase complète"
|
||||
}
|
||||
],
|
||||
practicePoints: [
|
||||
"Commencez par des phrases simples : Sujet + Verbe + Objet",
|
||||
"Utilisez 很 + adjectif pour décrire",
|
||||
"Pas de conjugaison = plus simple !",
|
||||
"Écoutez la mélodie de la langue chinoise"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// === VOCABULAIRE FRANÇAIS POUR APPRENANTS CHINOIS (15+ mots) ===
|
||||
vocabulary: {
|
||||
"fleur": {
|
||||
user_language: "flower",
|
||||
type: "noun",
|
||||
pronunciation: "flœʁ",
|
||||
gender: "feminine"
|
||||
},
|
||||
"jardin": {
|
||||
user_language: "garden",
|
||||
type: "noun",
|
||||
pronunciation: "ʒaʁdɛ̃",
|
||||
gender: "masculine"
|
||||
},
|
||||
"arbre": {
|
||||
user_language: "tree",
|
||||
type: "noun",
|
||||
pronunciation: "aʁbʁ",
|
||||
gender: "masculine"
|
||||
},
|
||||
"petit": {
|
||||
user_language: "small/little",
|
||||
type: "adjective",
|
||||
pronunciation: "pəti"
|
||||
},
|
||||
"grand": {
|
||||
user_language: "big/large",
|
||||
type: "adjective",
|
||||
pronunciation: "ɡʁɑ̃"
|
||||
},
|
||||
"beau": {
|
||||
user_language: "beautiful/handsome",
|
||||
type: "adjective",
|
||||
pronunciation: "bo"
|
||||
},
|
||||
"rouge": {
|
||||
user_language: "red",
|
||||
type: "adjective",
|
||||
pronunciation: "ʁuʒ"
|
||||
},
|
||||
"vert": {
|
||||
user_language: "green",
|
||||
type: "adjective",
|
||||
pronunciation: "vɛʁ"
|
||||
},
|
||||
"je": {
|
||||
user_language: "I",
|
||||
type: "pronoun",
|
||||
pronunciation: "ʒə"
|
||||
},
|
||||
"tu": {
|
||||
user_language: "you",
|
||||
type: "pronoun",
|
||||
pronunciation: "ty"
|
||||
},
|
||||
"regarder": {
|
||||
user_language: "to look/watch",
|
||||
type: "verb",
|
||||
pronunciation: "ʁəɡaʁde"
|
||||
},
|
||||
"aimer": {
|
||||
user_language: "to love/like",
|
||||
type: "verb",
|
||||
pronunciation: "ɛme"
|
||||
},
|
||||
"très": {
|
||||
user_language: "very",
|
||||
type: "adverb",
|
||||
pronunciation: "tʁɛ"
|
||||
},
|
||||
"avoir": {
|
||||
user_language: "to have",
|
||||
type: "verb",
|
||||
pronunciation: "avwaʁ"
|
||||
},
|
||||
"chat": {
|
||||
user_language: "cat",
|
||||
type: "noun",
|
||||
pronunciation: "ʃa",
|
||||
gender: "masculine"
|
||||
},
|
||||
"mignon": {
|
||||
user_language: "cute/adorable",
|
||||
type: "adjective",
|
||||
pronunciation: "miɲɔ̃"
|
||||
},
|
||||
"maintenant": {
|
||||
user_language: "now",
|
||||
type: "adverb",
|
||||
pronunciation: "mɛ̃tnɑ̃"
|
||||
}
|
||||
},
|
||||
|
||||
// === STRUCTURE PAR LETTRES POUR LETTER DISCOVERY ===
|
||||
letters: {
|
||||
"A": [
|
||||
{
|
||||
word: "arbre",
|
||||
translation: "tree",
|
||||
pronunciation: "aʁbʁ",
|
||||
type: "noun",
|
||||
gender: "masculine"
|
||||
},
|
||||
{
|
||||
word: "aimer",
|
||||
translation: "to love/like",
|
||||
pronunciation: "ɛme",
|
||||
type: "verb"
|
||||
},
|
||||
{
|
||||
word: "avoir",
|
||||
translation: "to have",
|
||||
pronunciation: "avwaʁ",
|
||||
type: "verb"
|
||||
}
|
||||
],
|
||||
"B": [
|
||||
{
|
||||
word: "beau",
|
||||
translation: "beautiful/handsome",
|
||||
pronunciation: "bo",
|
||||
type: "adjective"
|
||||
},
|
||||
{
|
||||
word: "beaucoup",
|
||||
translation: "a lot/much",
|
||||
pronunciation: "boku",
|
||||
type: "adverb"
|
||||
}
|
||||
],
|
||||
"C": [
|
||||
{
|
||||
word: "chat",
|
||||
translation: "cat",
|
||||
pronunciation: "ʃa",
|
||||
type: "noun",
|
||||
gender: "masculine"
|
||||
}
|
||||
],
|
||||
"F": [
|
||||
{
|
||||
word: "fleur",
|
||||
translation: "flower",
|
||||
pronunciation: "flœʁ",
|
||||
type: "noun",
|
||||
gender: "feminine"
|
||||
}
|
||||
],
|
||||
"G": [
|
||||
{
|
||||
word: "grand",
|
||||
translation: "big/large",
|
||||
pronunciation: "ɡʁɑ̃",
|
||||
type: "adjective"
|
||||
}
|
||||
],
|
||||
"J": [
|
||||
{
|
||||
word: "jardin",
|
||||
translation: "garden",
|
||||
pronunciation: "ʒaʁdɛ̃",
|
||||
type: "noun",
|
||||
gender: "masculine"
|
||||
},
|
||||
{
|
||||
word: "je",
|
||||
translation: "I",
|
||||
pronunciation: "ʒə",
|
||||
type: "pronoun"
|
||||
}
|
||||
],
|
||||
"M": [
|
||||
{
|
||||
word: "mignon",
|
||||
translation: "cute/adorable",
|
||||
pronunciation: "miɲɔ̃",
|
||||
type: "adjective"
|
||||
},
|
||||
{
|
||||
word: "maintenant",
|
||||
translation: "now",
|
||||
pronunciation: "mɛ̃tnɑ̃",
|
||||
type: "adverb"
|
||||
}
|
||||
],
|
||||
"P": [
|
||||
{
|
||||
word: "petit",
|
||||
translation: "small/little",
|
||||
pronunciation: "pəti",
|
||||
type: "adjective"
|
||||
}
|
||||
],
|
||||
"R": [
|
||||
{
|
||||
word: "rouge",
|
||||
translation: "red",
|
||||
pronunciation: "ʁuʒ",
|
||||
type: "adjective"
|
||||
},
|
||||
{
|
||||
word: "regarder",
|
||||
translation: "to look/watch",
|
||||
pronunciation: "ʁəɡaʁde",
|
||||
type: "verb"
|
||||
}
|
||||
],
|
||||
"T": [
|
||||
{
|
||||
word: "tu",
|
||||
translation: "you",
|
||||
pronunciation: "ty",
|
||||
type: "pronoun"
|
||||
},
|
||||
{
|
||||
word: "très",
|
||||
translation: "very",
|
||||
pronunciation: "tʁɛ",
|
||||
type: "adverb"
|
||||
}
|
||||
],
|
||||
"V": [
|
||||
{
|
||||
word: "vert",
|
||||
translation: "green",
|
||||
pronunciation: "vɛʁ",
|
||||
type: "adjective"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// === HISTOIRE SIMPLE ===
|
||||
story: {
|
||||
title: "Le Jardin Magique - 魔法花园",
|
||||
totalSentences: 8,
|
||||
chapters: [
|
||||
{
|
||||
title: "第一章:美丽的花园 (Chapitre 1: Le Beau Jardin)",
|
||||
sentences: [
|
||||
{
|
||||
id: 1,
|
||||
original: "J'ai un petit jardin.",
|
||||
translation: "I have a small garden.",
|
||||
words: [
|
||||
{word: "J'", translation: "I", type: "pronoun", pronunciation: "ʒ"},
|
||||
{word: "ai", translation: "have", type: "verb", pronunciation: "e"},
|
||||
{word: "un", translation: "a", type: "article", pronunciation: "œ̃"},
|
||||
{word: "petit", translation: "small", type: "adjective", pronunciation: "pəti"},
|
||||
{word: "jardin", translation: "garden", type: "noun", pronunciation: "ʒaʁdɛ̃"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
original: "Dans le jardin, il y a beaucoup de belles fleurs.",
|
||||
translation: "花园里有很多美丽的花。",
|
||||
words: [
|
||||
{word: "Dans", translation: "在", type: "preposition", pronunciation: "dɑ̃"},
|
||||
{word: "le", translation: "这个", type: "article", pronunciation: "lə"},
|
||||
{word: "jardin", translation: "花园", type: "noun", pronunciation: "ʒaʁdɛ̃"},
|
||||
{word: "il y a", translation: "有", type: "verb", pronunciation: "il i a"},
|
||||
{word: "beaucoup", translation: "很多", type: "adverb", pronunciation: "boku"},
|
||||
{word: "de", translation: "的", type: "preposition", pronunciation: "də"},
|
||||
{word: "belles", translation: "美丽的", type: "adjective", pronunciation: "bɛl"},
|
||||
{word: "fleurs", translation: "花", type: "noun", pronunciation: "flœʁ"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
original: "Il y a des fleurs rouges et des arbres verts.",
|
||||
translation: "有红花和绿树。",
|
||||
words: [
|
||||
{word: "Il y a", translation: "有", type: "verb", pronunciation: "il i a"},
|
||||
{word: "des", translation: "一些", type: "article", pronunciation: "de"},
|
||||
{word: "fleurs", translation: "花", type: "noun", pronunciation: "flœʁ"},
|
||||
{word: "rouges", translation: "红色的", type: "adjective", pronunciation: "ʁuʒ"},
|
||||
{word: "et", translation: "和", type: "conjunction", pronunciation: "e"},
|
||||
{word: "des", translation: "一些", type: "article", pronunciation: "de"},
|
||||
{word: "arbres", translation: "树", type: "noun", pronunciation: "aʁbʁ"},
|
||||
{word: "verts", translation: "绿色的", type: "adjective", pronunciation: "vɛʁ"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
original: "J'aime beaucoup mon jardin.",
|
||||
translation: "我很喜欢我的花园。",
|
||||
words: [
|
||||
{word: "J'", translation: "我", type: "pronoun", pronunciation: "ʒ"},
|
||||
{word: "aime", translation: "喜欢", type: "verb", pronunciation: "ɛm"},
|
||||
{word: "beaucoup", translation: "很", type: "adverb", pronunciation: "boku"},
|
||||
{word: "mon", translation: "我的", type: "pronoun", pronunciation: "mɔ̃"},
|
||||
{word: "jardin", translation: "花园", type: "noun", pronunciation: "ʒaʁdɛ̃"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "第二章:小猫来了 (Chapitre 2: Le Petit Chat Arrive)",
|
||||
sentences: [
|
||||
{
|
||||
id: 5,
|
||||
original: "一天,一只小猫来到花园。",
|
||||
translation: "Un jour, un petit chat est venu dans le jardin.",
|
||||
words: [
|
||||
{word: "一天", translation: "un jour", type: "noun", pronunciation: "yì tiān"},
|
||||
{word: "一只", translation: "un (classificateur)", type: "number", pronunciation: "yì zhī"},
|
||||
{word: "小猫", translation: "petit chat", type: "noun", pronunciation: "xiǎo māo"},
|
||||
{word: "来到", translation: "venir à", type: "verb", pronunciation: "lái dào"},
|
||||
{word: "花园", translation: "jardin", type: "noun", pronunciation: "huā yuán"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
original: "小猫看花,我看小猫。",
|
||||
translation: "Le petit chat regarde les fleurs, moi je regarde le petit chat.",
|
||||
words: [
|
||||
{word: "小猫", translation: "petit chat", type: "noun", pronunciation: "xiǎo māo"},
|
||||
{word: "看", translation: "regarder", type: "verb", pronunciation: "kàn"},
|
||||
{word: "花", translation: "fleur", type: "noun", pronunciation: "huā"},
|
||||
{word: "我", translation: "je", type: "pronoun", pronunciation: "wǒ"},
|
||||
{word: "看", translation: "regarder", type: "verb", pronunciation: "kàn"},
|
||||
{word: "小猫", translation: "petit chat", type: "noun", pronunciation: "xiǎo māo"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
original: "小猫很可爱。",
|
||||
translation: "Le petit chat est très mignon.",
|
||||
words: [
|
||||
{word: "小猫", translation: "petit chat", type: "noun", pronunciation: "xiǎo māo"},
|
||||
{word: "很", translation: "très", type: "adverb", pronunciation: "hěn"},
|
||||
{word: "可爱", translation: "mignon", type: "adjective", pronunciation: "kě ài"}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
original: "现在,花园更美了。",
|
||||
translation: "Maintenant, le jardin est encore plus beau.",
|
||||
words: [
|
||||
{word: "现在", translation: "maintenant", type: "adverb", pronunciation: "xiàn zài"},
|
||||
{word: "花园", translation: "jardin", type: "noun", pronunciation: "huā yuán"},
|
||||
{word: "更", translation: "encore plus", type: "adverb", pronunciation: "gèng"},
|
||||
{word: "美", translation: "beau", type: "adjective", pronunciation: "měi"},
|
||||
{word: "了", translation: "particule d'aspect", type: "particle", pronunciation: "le"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// === EXERCICES DE COMPRÉHENSION ===
|
||||
fillInBlanks: [
|
||||
{
|
||||
sentence: "J'___ un petit jardin",
|
||||
options: ["ai", "es", "regarde", "très"],
|
||||
correctAnswer: "ai",
|
||||
explanation: "使用 'ai' 表示拥有 - Use 'ai' to express possession (I have)"
|
||||
},
|
||||
{
|
||||
sentence: "Le jardin est ___ beau",
|
||||
options: ["ai", "très", "dans", "de"],
|
||||
correctAnswer: "très",
|
||||
explanation: "使用 'très' + 形容词 - Use 'très' + adjective"
|
||||
},
|
||||
{
|
||||
sentence: "Le petit chat ___ les fleurs",
|
||||
options: ["regarde", "ai", "très", "de"],
|
||||
correctAnswer: "regarde",
|
||||
explanation: "'regarde' 意思是看/观察 - 'regarde' means to look/observe"
|
||||
},
|
||||
{
|
||||
sentence: "J'___ beaucoup mon jardin",
|
||||
options: ["aime", "beau", "petit", "rouge"],
|
||||
correctAnswer: "aime",
|
||||
explanation: "'aime' 表示喜爱 - 'aime' expresses liking"
|
||||
},
|
||||
{
|
||||
sentence: "Dans le jardin, il y ___ beaucoup de fleurs",
|
||||
options: ["a", "regarde", "très", "petit"],
|
||||
correctAnswer: "a",
|
||||
explanation: "'il y a' 表示存在 - 'il y a' expresses existence"
|
||||
}
|
||||
],
|
||||
|
||||
// === CORRECTIONS D'ERREURS ===
|
||||
corrections: [
|
||||
{
|
||||
incorrect: "Je suis aimer le jardin",
|
||||
correct: "J'aime le jardin",
|
||||
explanation: "不需要 'suis',直接用动词 'aime' - No need for 'suis', use verb 'aime' directly"
|
||||
},
|
||||
{
|
||||
incorrect: "Le jardin beau",
|
||||
correct: "Le jardin est très beau",
|
||||
explanation: "需要动词 'est' 和副词 'très' - Need verb 'est' and adverb 'très'"
|
||||
},
|
||||
{
|
||||
incorrect: "Le petit chat a regardé les fleurs",
|
||||
correct: "Le petit chat regarde les fleurs",
|
||||
explanation: "简单动作用现在时 - Simple actions use present tense"
|
||||
}
|
||||
],
|
||||
|
||||
// === PHRASES D'EXEMPLE ===
|
||||
sentences: [
|
||||
{
|
||||
french: "J'ai un beau jardin",
|
||||
chinese: "我有一个美丽的花园",
|
||||
pronunciation: "ʒe œ̃ bo ʒaʁdɛ̃"
|
||||
},
|
||||
{
|
||||
french: "Le petit chat est très mignon",
|
||||
chinese: "小猫很可爱",
|
||||
pronunciation: "lə pəti ʃa ɛ tʁɛ miɲɔ̃"
|
||||
},
|
||||
{
|
||||
french: "Dans le jardin il y a des fleurs rouges et des arbres verts",
|
||||
chinese: "花园里有红花和绿树",
|
||||
pronunciation: "dɑ̃ lə ʒaʁdɛ̃ il i a de flœʁ ʁuʒ e dez‿aʁbʁ vɛʁ"
|
||||
},
|
||||
{
|
||||
french: "Je regarde le petit chat, le petit chat regarde les fleurs",
|
||||
chinese: "我看小猫,小猫看花",
|
||||
pronunciation: "ʒə ʁəɡaʁd lə pəti ʃa, lə pəti ʃa ʁəɡaʁd le flœʁ"
|
||||
}
|
||||
]
|
||||
};
|
||||
293
js/content/grammar-lesson-le.js
Normal file
293
js/content/grammar-lesson-le.js
Normal file
@ -0,0 +1,293 @@
|
||||
// === GRAMMAR LESSON: 了 ASPECT PARTICLE ===
|
||||
// Dedicated grammar course focused on the 了 particle in Chinese
|
||||
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
|
||||
window.ContentModules.GrammarLessonLe = {
|
||||
id: "grammar-lesson-le",
|
||||
name: "Grammar Lesson: 了 (le) Aspect Particle",
|
||||
description: "Complete lesson on the Chinese aspect particle 了 - completion and change of state",
|
||||
difficulty: "intermediate",
|
||||
language: "zh-CN",
|
||||
type: "grammar_course",
|
||||
|
||||
// === MAIN GRAMMAR LESSON ===
|
||||
grammar: {
|
||||
"le-aspect-particle": {
|
||||
title: "The 了 (le) Aspect Particle - 动态助词了",
|
||||
explanation: "了 is one of the most important particles in Chinese. It indicates completion of an action or a change of state. Unlike English past tense, 了 focuses on the aspect (how the action is viewed) rather than when it happened.",
|
||||
|
||||
mainRules: [
|
||||
"了 shows that an action has been completed",
|
||||
"了 indicates a change from one state to another",
|
||||
"了 can appear after the verb (了1) or at the end of sentence (了2)",
|
||||
"了 does NOT simply mean 'past tense' - it's about completion/change"
|
||||
],
|
||||
|
||||
detailedExplanation: {
|
||||
"completion": {
|
||||
title: "1. Completion of Action (动作完成)",
|
||||
explanation: "了 after a verb shows the action has been completed",
|
||||
pattern: "Subject + Verb + 了 + Object",
|
||||
examples: [
|
||||
{
|
||||
chinese: "我吃了饭",
|
||||
english: "I ate (have eaten) the meal",
|
||||
pronunciation: "wǒ chī le fàn",
|
||||
breakdown: "我(I) + 吃(eat) + 了(completed) + 饭(meal)",
|
||||
explanation: "The eating action is completed"
|
||||
},
|
||||
{
|
||||
chinese: "他买了一本书",
|
||||
english: "He bought a book",
|
||||
pronunciation: "tā mǎi le yì běn shū",
|
||||
breakdown: "他(he) + 买(buy) + 了(completed) + 一本书(a book)",
|
||||
explanation: "The buying action is finished"
|
||||
},
|
||||
{
|
||||
chinese: "老师讲了三个故事",
|
||||
english: "The teacher told three stories",
|
||||
pronunciation: "lǎo shī jiǎng le sān gè gù shì",
|
||||
breakdown: "老师(teacher) + 讲(tell) + 了(completed) + 三个故事(three stories)",
|
||||
explanation: "The telling action is complete"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"change-of-state": {
|
||||
title: "2. Change of State (状态变化)",
|
||||
explanation: "了 at the end of a sentence shows a change in situation or state",
|
||||
pattern: "Subject + Verb + Object + 了",
|
||||
examples: [
|
||||
{
|
||||
chinese: "天黑了",
|
||||
english: "It has gotten dark / It's dark now",
|
||||
pronunciation: "tiān hēi le",
|
||||
breakdown: "天(sky) + 黑(dark) + 了(change of state)",
|
||||
explanation: "Change from light to dark"
|
||||
},
|
||||
{
|
||||
chinese: "我饿了",
|
||||
english: "I'm hungry now / I've become hungry",
|
||||
pronunciation: "wǒ è le",
|
||||
breakdown: "我(I) + 饿(hungry) + 了(change of state)",
|
||||
explanation: "Change from not hungry to hungry"
|
||||
},
|
||||
{
|
||||
chinese: "下雨了",
|
||||
english: "It's raining now / It started to rain",
|
||||
pronunciation: "xià yǔ le",
|
||||
breakdown: "下雨(rain) + 了(change of state)",
|
||||
explanation: "Change from not raining to raining"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"double-le": {
|
||||
title: "3. Double 了 Construction",
|
||||
explanation: "Sometimes 了 appears both after the verb AND at the end of sentence",
|
||||
pattern: "Subject + Verb + 了 + Object + 了",
|
||||
examples: [
|
||||
{
|
||||
chinese: "我买了三本书了",
|
||||
english: "I have bought three books (and the situation has changed)",
|
||||
pronunciation: "wǒ mǎi le sān běn shū le",
|
||||
breakdown: "我 + 买了(completed buying) + 三本书 + 了(new situation)",
|
||||
explanation: "Action completed AND situation changed"
|
||||
},
|
||||
{
|
||||
chinese: "他吃了两个苹果了",
|
||||
english: "He has eaten two apples (and is now full/satisfied)",
|
||||
pronunciation: "tā chī le liǎng gè píng guǒ le",
|
||||
breakdown: "他 + 吃了(completed eating) + 两个苹果 + 了(new state)",
|
||||
explanation: "Eating completed AND state changed"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
commonMistakes: [
|
||||
{
|
||||
mistake: "Using 了 for all past actions",
|
||||
wrong: "我昨天了去学校",
|
||||
correct: "我昨天去了学校 / 我昨天去学校",
|
||||
explanation: "了 shows completion, not just past time. Don't add 了 randomly to past time expressions."
|
||||
},
|
||||
{
|
||||
mistake: "Forgetting 了 for completed actions",
|
||||
wrong: "我吃饭,现在很饱",
|
||||
correct: "我吃了饭,现在很饱",
|
||||
explanation: "Need 了 to show the eating is completed before being full"
|
||||
},
|
||||
{
|
||||
mistake: "Using 了 with ongoing actions",
|
||||
wrong: "我正在吃了饭",
|
||||
correct: "我正在吃饭",
|
||||
explanation: "Can't use 了 with 正在 (ongoing) - they're contradictory"
|
||||
}
|
||||
],
|
||||
|
||||
practicePoints: [
|
||||
"Ask yourself: Is the action completed? Use 了 after verb",
|
||||
"Ask yourself: Has the situation changed? Use 了 at end",
|
||||
"Remember: 了 ≠ past tense. It's about completion/change",
|
||||
"Pay attention to context - sometimes 了 is not needed even for past actions"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// === VOCABULARY FOR THE LESSON ===
|
||||
vocabulary: {
|
||||
"了": {
|
||||
translation: "aspect particle (completion/change)",
|
||||
type: "particle",
|
||||
pronunciation: "le",
|
||||
usage: "Shows completed action or change of state"
|
||||
},
|
||||
"吃": {
|
||||
translation: "to eat",
|
||||
type: "verb",
|
||||
pronunciation: "chī"
|
||||
},
|
||||
"买": {
|
||||
translation: "to buy",
|
||||
type: "verb",
|
||||
pronunciation: "mǎi"
|
||||
},
|
||||
"讲": {
|
||||
translation: "to tell/speak",
|
||||
type: "verb",
|
||||
pronunciation: "jiǎng"
|
||||
},
|
||||
"饭": {
|
||||
translation: "meal/food",
|
||||
type: "noun",
|
||||
pronunciation: "fàn"
|
||||
},
|
||||
"书": {
|
||||
translation: "book",
|
||||
type: "noun",
|
||||
pronunciation: "shū"
|
||||
},
|
||||
"故事": {
|
||||
translation: "story",
|
||||
type: "noun",
|
||||
pronunciation: "gù shì"
|
||||
},
|
||||
"天": {
|
||||
translation: "sky/day",
|
||||
type: "noun",
|
||||
pronunciation: "tiān"
|
||||
},
|
||||
"黑": {
|
||||
translation: "dark/black",
|
||||
type: "adjective",
|
||||
pronunciation: "hēi"
|
||||
},
|
||||
"饿": {
|
||||
translation: "hungry",
|
||||
type: "adjective",
|
||||
pronunciation: "è"
|
||||
},
|
||||
"下雨": {
|
||||
translation: "to rain",
|
||||
type: "verb",
|
||||
pronunciation: "xià yǔ"
|
||||
}
|
||||
},
|
||||
|
||||
// === FILL IN THE BLANKS EXERCISES ===
|
||||
fillInBlanks: [
|
||||
{
|
||||
sentence: "我吃___饭,现在很饱",
|
||||
options: ["了", "的", "在", "着"],
|
||||
correctAnswer: "了",
|
||||
explanation: "Use 了 to show the eating action is completed before being full",
|
||||
grammarFocus: "completion"
|
||||
},
|
||||
{
|
||||
sentence: "天黑___,我们回家吧",
|
||||
options: ["了", "的", "在", "着"],
|
||||
correctAnswer: "了",
|
||||
explanation: "Use 了 to show change of state - it has become dark",
|
||||
grammarFocus: "change-of-state"
|
||||
},
|
||||
{
|
||||
sentence: "他买___三本书___",
|
||||
options: ["了...了", "的...的", "在...在", "着...着"],
|
||||
correctAnswer: "了...了",
|
||||
explanation: "Double 了: action completed (买了) AND situation changed (了)",
|
||||
grammarFocus: "double-le"
|
||||
},
|
||||
{
|
||||
sentence: "我昨天___学校",
|
||||
options: ["去了", "了去", "去的", "的去"],
|
||||
correctAnswer: "去了",
|
||||
explanation: "了 comes after the verb to show completed action",
|
||||
grammarFocus: "word-order"
|
||||
},
|
||||
{
|
||||
sentence: "下雨___,路很湿",
|
||||
options: ["了", "的", "在", "着"],
|
||||
correctAnswer: "了",
|
||||
explanation: "Change of state: it has started raining (wasn't raining before)",
|
||||
grammarFocus: "change-of-state"
|
||||
}
|
||||
],
|
||||
|
||||
// === CORRECTION EXERCISES ===
|
||||
corrections: [
|
||||
{
|
||||
incorrect: "我昨天了去学校",
|
||||
correct: "我昨天去了学校",
|
||||
explanation: "了 should come after the verb, not before it",
|
||||
grammarFocus: "word-order"
|
||||
},
|
||||
{
|
||||
incorrect: "我正在吃了饭",
|
||||
correct: "我正在吃饭",
|
||||
explanation: "Cannot use 了 (completion) with 正在 (ongoing action)",
|
||||
grammarFocus: "aspect-conflict"
|
||||
},
|
||||
{
|
||||
incorrect: "我吃饭,现在很饱",
|
||||
correct: "我吃了饭,现在很饱",
|
||||
explanation: "Need 了 to show eating is completed before the result (being full)",
|
||||
grammarFocus: "completion"
|
||||
},
|
||||
{
|
||||
incorrect: "他很高了的人",
|
||||
correct: "他是很高的人",
|
||||
explanation: "Don't use 了 in descriptions with 的. 了 is for actions/changes, not permanent descriptions",
|
||||
grammarFocus: "inappropriate-usage"
|
||||
}
|
||||
],
|
||||
|
||||
// === TRANSLATION EXERCISES ===
|
||||
sentences: [
|
||||
{
|
||||
english: "I finished my homework",
|
||||
chinese: "我做完了作业",
|
||||
pronunciation: "wǒ zuò wán le zuò yè",
|
||||
grammarFocus: "completion"
|
||||
},
|
||||
{
|
||||
english: "It's gotten cold",
|
||||
chinese: "天气冷了",
|
||||
pronunciation: "tiān qì lěng le",
|
||||
grammarFocus: "change-of-state"
|
||||
},
|
||||
{
|
||||
english: "He bought two books and now has them",
|
||||
chinese: "他买了两本书了",
|
||||
pronunciation: "tā mǎi le liǎng běn shū le",
|
||||
grammarFocus: "double-le"
|
||||
},
|
||||
{
|
||||
english: "The teacher finished the lesson",
|
||||
chinese: "老师讲完了课",
|
||||
pronunciation: "lǎo shī jiǎng wán le kè",
|
||||
grammarFocus: "completion"
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -1,239 +0,0 @@
|
||||
const content = {
|
||||
vocabulary: {
|
||||
// Housing and Places
|
||||
central: { user_language: { user_language: "中心的;中央的", type: "noun" }, type: { user_language: "adjective", type: "noun" }, },
|
||||
avenue: { user_language: { user_language: "大街;林荫道", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
refrigerator: { user_language: { user_language: "冰箱", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
closet: { user_language: { user_language: "衣柜;壁橱", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
elevator: { user_language: { user_language: "电梯", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
building: { user_language: { user_language: "建筑物;大楼", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
"air conditioner": { user_language: { user_language: "空调", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
superintendent: { user_language: { user_language: "主管;负责人", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
"bus stop": { user_language: { user_language: "公交车站", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
jacuzzi: { user_language: { user_language: "按摩浴缸", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
machine: { user_language: { user_language: "机器;设备", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
"two and a half": { user_language: { user_language: "两个半", type: "noun" }, type: { user_language: "number", type: "noun" }, },
|
||||
"in the center of": { user_language: { user_language: "在……中心", type: "noun" }, type: { user_language: "preposition", type: "noun" }, },
|
||||
town: { user_language: { user_language: "城镇", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
"a lot of": { user_language: { user_language: "许多", type: "noun" }, type: { user_language: "determiner", type: "noun" }, },
|
||||
noise: { user_language: { user_language: "噪音", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
sidewalks: { user_language: { user_language: "人行道", type: "noun" }, type: { user_language: "noun", type: "noun" }, },
|
||||
"all day and all night": { user_language: { user_language: "整日整夜", type: "noun" }, type: { user_language: "adverb", type: "noun" }, },
|
||||
convenient: { user_language: { user_language: "便利的", type: "adjective" }, type: { user_language: "adjective", type: "noun" }, },
|
||||
upset: { user_language: { user_language: "失望的", type: "adjective" }, type: { user_language: "adjective", type: "noun" }, },
|
||||
|
||||
// Clothing and Accessories
|
||||
shirt: { user_language: "衬衫", type: "noun" },
|
||||
coat: { user_language: "外套、大衣", type: "noun" },
|
||||
dress: { user_language: "连衣裙", type: "noun" },
|
||||
skirt: { user_language: "短裙", type: "noun" },
|
||||
blouse: { user_language: "女式衬衫", type: "noun" },
|
||||
jacket: { user_language: "夹克、短外套", type: "noun" },
|
||||
sweater: { user_language: "毛衣、针织衫", type: "noun" },
|
||||
suit: { user_language: "套装、西装", type: "noun" },
|
||||
tie: { user_language: "领带", type: "noun" },
|
||||
pants: { user_language: "裤子", type: "noun" },
|
||||
jeans: { user_language: "牛仔裤", type: "noun" },
|
||||
belt: { user_language: "腰带、皮带", type: "noun" },
|
||||
hat: { user_language: "帽子", type: "noun" },
|
||||
glove: { user_language: "手套", type: "noun" },
|
||||
"purse/pocketbook": { user_language: "手提包、女式小包", type: "noun" },
|
||||
glasses: { user_language: "眼镜", type: "noun" },
|
||||
pajamas: { user_language: "睡衣", type: "noun" },
|
||||
socks: { user_language: "袜子", type: "noun" },
|
||||
shoes: { user_language: "鞋子", type: "noun" },
|
||||
bathrobe: { user_language: "浴袍", type: "noun" },
|
||||
"tee shirt": { user_language: "T恤", type: "phrase" },
|
||||
scarf: { user_language: "围巾", type: "noun" },
|
||||
wallet: { user_language: "钱包", type: "noun" },
|
||||
ring: { user_language: "戒指", type: "noun" },
|
||||
sandals: { user_language: "凉鞋", type: "noun" },
|
||||
slippers: { user_language: "拖鞋", type: "noun" },
|
||||
sneakers: { user_language: "运动鞋", type: "noun" },
|
||||
shorts: { user_language: "短裤", type: "noun" },
|
||||
"sweat pants": { user_language: "运动裤", type: "phrase" },
|
||||
|
||||
// Places and Areas
|
||||
"urban areas": { user_language: "cities", type: "phrase" },
|
||||
"suburban areas": { user_language: "places near cities", type: "phrase" },
|
||||
"rural areas": { user_language: "places in the countryside, far from cities", type: "phrase" },
|
||||
farmhouse: { user_language: "农舍", type: "noun" },
|
||||
hut: { user_language: "小屋", type: "noun" },
|
||||
houseboat: { user_language: "船屋", type: "noun" },
|
||||
"mobile home": { user_language: "移动房屋", type: "phrase" },
|
||||
trailer: { user_language: "拖车房", type: "noun" },
|
||||
|
||||
// Store Items
|
||||
jackets: { user_language: "夹克", type: "noun" },
|
||||
gloves: { user_language: "手套", type: "noun" },
|
||||
blouses: { user_language: "女式衬衫", type: "noun" },
|
||||
bracelets: { user_language: "手镯", type: "noun" },
|
||||
ties: { user_language: "领带", type: "noun" },
|
||||
},
|
||||
|
||||
sentences: [
|
||||
{
|
||||
english: { user_language: "Amy's apartment building is in the center of town.", type: "noun" },
|
||||
chinese: { user_language: "艾米的公寓楼在城镇中心。", type: "adjective" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "There's a lot of noise near Amy's apartment building.", type: "noun" },
|
||||
chinese: { user_language: "艾米的公寓楼附近有很多噪音。", type: "adjective" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "It's a very busy place, but it's a convenient place to live.", type: "noun" },
|
||||
chinese: { user_language: "那是个非常热闹的地方,但也是个居住很方便的地方。", type: "adjective" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "Around the corner from the building, there are two supermarkets.", type: "noun" },
|
||||
chinese: { user_language: "从这栋楼拐个弯,就有两家超市。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "I'm looking for a shirt.", type: "noun" },
|
||||
chinese: { user_language: "我在找一件衬衫。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "Shirts are over there.", type: "noun" },
|
||||
chinese: { user_language: "衬衫在那边。", type: "noun" },
|
||||
}
|
||||
],
|
||||
|
||||
texts: [
|
||||
{
|
||||
title: { user_language: "People's Homes", type: "noun" },
|
||||
content: { user_language: "Homes are different all around the world. This family is living in a farmhouse. This family is living in a hut. This family is living in a houseboat. These people are living in a mobile home (a trailer). What different kinds of homes are there in your country?", type: "noun" },
|
||||
},
|
||||
{
|
||||
title: { user_language: "Urban, Suburban, and Rural", type: "noun" },
|
||||
content: { user_language: "urban areas = cities, suburban areas = places near cities, rural areas = places in the countryside, far from cities. About 50% (percent) of the world's population is in urban and suburban areas. About 50% (percent) of the world's population is in rural areas.", type: "noun" },
|
||||
},
|
||||
{
|
||||
title: { user_language: "Global Exchange - RosieM", type: "noun" },
|
||||
content: { user_language: "My apartment is in a wonderful neighborhood. There's a big, beautiful park across from my apartment building. Around the corner, there's a bank, a post office, and a laundromat. There are also many restaurants and stores in my neighborhood. It's a noisy place, but it's a very interesting place. There are a lot of people on the sidewalks all day and all night. How about your neighborhood? Tell me about it.", type: "noun" },
|
||||
},
|
||||
{
|
||||
title: { user_language: "Clothing, Colors, and Cultures", type: "noun" },
|
||||
content: { user_language: "Blue and pink aren't children's clothing colors all around the world. The meanings of colors are sometimes very different in different cultures. For example, in some cultures, blue is a common clothing color for little boys, and pink is a common clothing color for little girls. In other cultures, other colors are common for boys and girls. There are also different colors for special days in different cultures. For example, white is the traditional color of a wedding dress in some cultures, but other colors are traditional in other cultures. For some people, white is a happy color. For others, it's a sad color. For some people, red is a beautiful and lucky color. For others, it's a very sad color. What are the meanings of different colors in YOUR culture?", type: "noun" },
|
||||
}
|
||||
],
|
||||
|
||||
grammar: {
|
||||
thereBe: {
|
||||
topic: { user_language: "There be 句型的用法", type: "adjective" },
|
||||
singular: {
|
||||
form: { user_language: "there is (there's) + 名词单数/不可数名词", type: "noun" },
|
||||
explanation: { user_language: "在某地方有什么人或东西", type: "noun" },
|
||||
examples: [
|
||||
"There's a bank.",
|
||||
"There's some water.",
|
||||
"There's a book store on Main Street."
|
||||
],
|
||||
forms: {
|
||||
positive: { user_language: "There's a stove in the kitchen.", type: "noun" },
|
||||
negative: { user_language: "There isn't a stove in the kitchen.", type: "noun" },
|
||||
question: { user_language: "Is there a stove in the kitchen?", type: "noun" },
|
||||
shortAnswers: { user_language: "Yes, there is. / No, there isn't.", type: "noun" },
|
||||
}
|
||||
},
|
||||
plural: {
|
||||
form: { user_language: "there are (there're) + 复数名词", type: "noun" },
|
||||
examples: [
|
||||
"There're two hospitals.",
|
||||
"There're many rooms in this apartment."
|
||||
],
|
||||
forms: {
|
||||
positive: { user_language: "There're two windows in the kitchen.", type: "noun" },
|
||||
negative: { user_language: "There aren't two windows in the kitchen.", type: "noun" },
|
||||
question: { user_language: "Are there two windows in the kitchen?", type: "noun" },
|
||||
shortAnswers: { user_language: "Yes, there are. / No, there aren't.", type: "noun" },
|
||||
}
|
||||
}
|
||||
},
|
||||
plurals: {
|
||||
topic: { user_language: "可数名词复数", type: "noun" },
|
||||
pronunciation: {
|
||||
rules: [
|
||||
{
|
||||
condition: { user_language: "在清辅音/-p,-k/后", type: "noun" },
|
||||
pronunciation: { user_language: "/-s/", type: "noun" },
|
||||
example: { user_language: "socks中-k是清辅音/-k/,所以-s读/-s/", type: "noun" },
|
||||
},
|
||||
{
|
||||
condition: { user_language: "在浊辅音和元音音标后", type: "noun" },
|
||||
pronunciation: { user_language: "/-z/", type: "noun" },
|
||||
example: { user_language: "jeans中-n是浊辅音/-n/, 所以-s读/-z/; tie的读音是/tai/,以元音结尾,所以-s读/-z/", type: "adjective" },
|
||||
},
|
||||
{
|
||||
condition: { user_language: "以/-s,-z,-ʃ,-ʒ,-tʃ,-dʒ/发音结尾的名词", type: "adjective" },
|
||||
pronunciation: { user_language: "/-iz/", type: "noun" },
|
||||
example: { user_language: "watches中-ch读/-tʃ/,所以-es读/-iz/", type: "noun" },
|
||||
}
|
||||
]
|
||||
},
|
||||
formation: {
|
||||
regular: {
|
||||
rule: { user_language: "一般在词尾加-s", type: "noun" },
|
||||
examples: ["shirts", "shoes"]
|
||||
},
|
||||
special: {
|
||||
rule: { user_language: "以-s,-sh,-ch,-x,以及辅音字母o结尾的词在词尾加-es", type: "adjective" },
|
||||
examples: ["boxes", "buses", "potatoes", "tomatoes", "heroes"]
|
||||
},
|
||||
irregular: {
|
||||
rule: { user_language: "特殊的复数形式", type: "adjective" },
|
||||
examples: {
|
||||
"man": { user_language: "men", type: "noun" },
|
||||
"woman": { user_language: "women", type: "noun" },
|
||||
"child": { user_language: "children", type: "noun" },
|
||||
"tooth": { user_language: "teeth", type: "noun" },
|
||||
"mouse": { user_language: "mice", type: "noun" },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
listening: {
|
||||
jMartShopping: {
|
||||
title: { user_language: "Attention, J-Mart Shoppers!", type: "noun" },
|
||||
items: [
|
||||
{ item: { user_language: "jackets", type: "noun" }, aisle: { user_language: "Aisle 9", type: "noun" }, },
|
||||
{ item: { user_language: "gloves", type: "noun" }, aisle: { user_language: "Aisle 7", type: "noun" }, },
|
||||
{ item: { user_language: "blouses", type: "noun" }, aisle: { user_language: "Aisle 9", type: "noun" }, },
|
||||
{ item: { user_language: "bracelets", type: "noun" }, aisle: { user_language: "Aisle 11", type: "noun" }, },
|
||||
{ item: { user_language: "ties", type: "noun" }, aisle: { user_language: "Aisle 5", type: "noun" }, }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
exercises: {
|
||||
sentenceCompletion: [
|
||||
"That's a very nice _______.",
|
||||
"Those are very nice _______."
|
||||
],
|
||||
questions: [
|
||||
"What different kinds of homes are there in your country?",
|
||||
"How about your neighborhood? Tell me about it.",
|
||||
"What are the meanings of different colors in YOUR culture?"
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Export pour le système de modules web
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
window.ContentModules.SBSLevel78New = {
|
||||
name: { user_language: "SBS Level 7-8 (New)", type: "noun" },
|
||||
description: { user_language: "Format simple et clair - Homes, Clothing & Cultures", type: "noun" },
|
||||
difficulty: { user_language: "intermediate", type: "noun" },
|
||||
vocabulary: content.vocabulary,
|
||||
sentences: content.sentences,
|
||||
texts: content.texts,
|
||||
grammar: content.grammar,
|
||||
listening: content.listening,
|
||||
exercises: content.exercises
|
||||
};
|
||||
|
||||
// Export Node.js (optionnel)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = content;
|
||||
}
|
||||
@ -1,239 +0,0 @@
|
||||
const content = {
|
||||
vocabulary: {
|
||||
// Housing and Places
|
||||
central: { user_language: "中心的;中央的", type: { user_language: "adjective", type: "noun" }, },
|
||||
avenue: { user_language: "大街;林荫道", type: { user_language: "noun", type: "noun" }, },
|
||||
refrigerator: { user_language: "冰箱", type: { user_language: "noun", type: "noun" }, },
|
||||
closet: { user_language: "衣柜;壁橱", type: { user_language: "noun", type: "noun" }, },
|
||||
elevator: { user_language: "电梯", type: { user_language: "noun", type: "noun" }, },
|
||||
building: { user_language: "建筑物;大楼", type: { user_language: "noun", type: "noun" }, },
|
||||
"air conditioner": { user_language: "空调", type: { user_language: "noun", type: "noun" }, },
|
||||
superintendent: { user_language: "主管;负责人", type: { user_language: "noun", type: "noun" }, },
|
||||
"bus stop": { user_language: "公交车站", type: { user_language: "noun", type: "noun" }, },
|
||||
jacuzzi: { user_language: "按摩浴缸", type: { user_language: "noun", type: "noun" }, },
|
||||
machine: { user_language: "机器;设备", type: { user_language: "noun", type: "noun" }, },
|
||||
"two and a half": { user_language: "两个半", type: { user_language: "number", type: "noun" }, },
|
||||
"in the center of": { user_language: "在……中心", type: { user_language: "preposition", type: "noun" }, },
|
||||
town: { user_language: "城镇", type: { user_language: "noun", type: "noun" }, },
|
||||
"a lot of": { user_language: "许多", type: { user_language: "determiner", type: "noun" }, },
|
||||
noise: { user_language: "噪音", type: { user_language: "noun", type: "noun" }, },
|
||||
sidewalks: { user_language: "人行道", type: { user_language: "noun", type: "noun" }, },
|
||||
"all day and all night": { user_language: "整日整夜", type: { user_language: "adverb", type: "noun" }, },
|
||||
convenient: { user_language: "便利的", type: { user_language: "adjective", type: "noun" }, },
|
||||
upset: { user_language: "失望的", type: { user_language: "adjective", type: "noun" }, },
|
||||
|
||||
// Clothing and Accessories
|
||||
shirt: { user_language: "衬衫", type: "noun" },
|
||||
coat: { user_language: "外套、大衣", type: "noun" },
|
||||
dress: { user_language: "连衣裙", type: "noun" },
|
||||
skirt: { user_language: "短裙", type: "noun" },
|
||||
blouse: { user_language: "女式衬衫", type: "noun" },
|
||||
jacket: { user_language: "夹克、短外套", type: "noun" },
|
||||
sweater: { user_language: "毛衣、针织衫", type: "noun" },
|
||||
suit: { user_language: "套装、西装", type: "noun" },
|
||||
tie: { user_language: "领带", type: "noun" },
|
||||
pants: { user_language: "裤子", type: "noun" },
|
||||
jeans: { user_language: "牛仔裤", type: "noun" },
|
||||
belt: { user_language: "腰带、皮带", type: "noun" },
|
||||
hat: { user_language: "帽子", type: "noun" },
|
||||
glove: { user_language: "手套", type: "noun" },
|
||||
"purse/pocketbook": "手提包、女式小包",
|
||||
glasses: { user_language: "眼镜", type: "noun" },
|
||||
pajamas: { user_language: "睡衣", type: "noun" },
|
||||
socks: { user_language: "袜子", type: "noun" },
|
||||
shoes: { user_language: "鞋子", type: "noun" },
|
||||
bathrobe: { user_language: "浴袍", type: "noun" },
|
||||
"tee shirt": { user_language: "T恤", type: "noun" },
|
||||
scarf: { user_language: "围巾", type: "noun" },
|
||||
wallet: { user_language: "钱包", type: "noun" },
|
||||
ring: { user_language: "戒指", type: "noun" },
|
||||
sandals: { user_language: "凉鞋", type: "noun" },
|
||||
slippers: { user_language: "拖鞋", type: "noun" },
|
||||
sneakers: { user_language: "运动鞋", type: "noun" },
|
||||
shorts: { user_language: "短裤", type: "noun" },
|
||||
"sweat pants": { user_language: "运动裤", type: "noun" },
|
||||
|
||||
// Places and Areas
|
||||
"urban areas": { user_language: "cities", type: "noun" },
|
||||
"suburban areas": { user_language: "places near cities", type: "noun" },
|
||||
"rural areas": { user_language: "places in the countryside, far from cities", type: "noun" },
|
||||
farmhouse: { user_language: "农舍", type: "noun" },
|
||||
hut: { user_language: "小屋", type: "noun" },
|
||||
houseboat: { user_language: "船屋", type: "noun" },
|
||||
"mobile home": { user_language: "移动房屋", type: "noun" },
|
||||
trailer: { user_language: "拖车房", type: "noun" },
|
||||
|
||||
// Store Items
|
||||
jackets: { user_language: "夹克", type: "noun" },
|
||||
gloves: { user_language: "手套", type: "noun" },
|
||||
blouses: { user_language: "女式衬衫", type: "noun" },
|
||||
bracelets: { user_language: "手镯", type: "noun" },
|
||||
ties: { user_language: "领带", type: "noun" },
|
||||
},
|
||||
|
||||
sentences: [
|
||||
{
|
||||
english: { user_language: "Amy's apartment building is in the center of town.", type: "noun" },
|
||||
chinese: { user_language: "艾米的公寓楼在城镇中心。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "There's a lot of noise near Amy's apartment building.", type: "noun" },
|
||||
chinese: { user_language: "艾米的公寓楼附近有很多噪音。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "It's a very busy place, but it's a convenient place to live.", type: "noun" },
|
||||
chinese: { user_language: "那是个非常热闹的地方,但也是个居住很方便的地方。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "Around the corner from the building, there are two supermarkets.", type: "noun" },
|
||||
chinese: { user_language: "从这栋楼拐个弯,就有两家超市。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "I'm looking for a shirt.", type: "noun" },
|
||||
chinese: { user_language: "我在找一件衬衫。", type: "noun" },
|
||||
},
|
||||
{
|
||||
english: { user_language: "Shirts are over there.", type: "noun" },
|
||||
chinese: { user_language: "衬衫在那边。", type: "noun" },
|
||||
}
|
||||
],
|
||||
|
||||
texts: [
|
||||
{
|
||||
title: { user_language: "People's Homes", type: "noun" },
|
||||
content: { user_language: "Homes are different all around the world. This family is living in a farmhouse. This family is living in a hut. This family is living in a houseboat. These people are living in a mobile home (a trailer). What different kinds of homes are there in your country?", type: "noun" },
|
||||
},
|
||||
{
|
||||
title: { user_language: "Urban, Suburban, and Rural", type: "noun" },
|
||||
content: { user_language: "urban areas = cities, suburban areas = places near cities, rural areas = places in the countryside, far from cities. About 50% (percent) of the world's population is in urban and suburban areas. About 50% (percent) of the world's population is in rural areas.", type: "noun" },
|
||||
},
|
||||
{
|
||||
title: { user_language: "Global Exchange - RosieM", type: "noun" },
|
||||
content: { user_language: "My apartment is in a wonderful neighborhood. There's a big, beautiful park across from my apartment building. Around the corner, there's a bank, a post office, and a laundromat. There are also many restaurants and stores in my neighborhood. It's a noisy place, but it's a very interesting place. There are a lot of people on the sidewalks all day and all night. How about your neighborhood? Tell me about it.", type: "noun" },
|
||||
},
|
||||
{
|
||||
title: { user_language: "Clothing, Colors, and Cultures", type: "noun" },
|
||||
content: { user_language: "Blue and pink aren't children's clothing colors all around the world. The meanings of colors are sometimes very different in different cultures. For example, in some cultures, blue is a common clothing color for little boys, and pink is a common clothing color for little girls. In other cultures, other colors are common for boys and girls. There are also different colors for special days in different cultures. For example, white is the traditional color of a wedding dress in some cultures, but other colors are traditional in other cultures. For some people, white is a happy color. For others, it's a sad color. For some people, red is a beautiful and lucky color. For others, it's a very sad color. What are the meanings of different colors in YOUR culture?", type: "noun" },
|
||||
}
|
||||
],
|
||||
|
||||
grammar: {
|
||||
thereBe: {
|
||||
topic: { user_language: "There be 句型的用法", type: "noun" },
|
||||
singular: {
|
||||
form: { user_language: "there is (there's) + 名词单数/不可数名词", type: "noun" },
|
||||
explanation: { user_language: "在某地方有什么人或东西", type: "noun" },
|
||||
examples: [
|
||||
"There's a bank.",
|
||||
"There's some water.",
|
||||
"There's a book store on Main Street."
|
||||
],
|
||||
forms: {
|
||||
positive: { user_language: "There's a stove in the kitchen.", type: "noun" },
|
||||
negative: { user_language: "There isn't a stove in the kitchen.", type: "noun" },
|
||||
question: { user_language: "Is there a stove in the kitchen?", type: "noun" },
|
||||
shortAnswers: { user_language: "Yes, there is. / No, there isn't.", type: "noun" },
|
||||
}
|
||||
},
|
||||
plural: {
|
||||
form: { user_language: "there are (there're) + 复数名词", type: "noun" },
|
||||
examples: [
|
||||
"There're two hospitals.",
|
||||
"There're many rooms in this apartment."
|
||||
],
|
||||
forms: {
|
||||
positive: { user_language: "There're two windows in the kitchen.", type: "noun" },
|
||||
negative: { user_language: "There aren't two windows in the kitchen.", type: "noun" },
|
||||
question: { user_language: "Are there two windows in the kitchen?", type: "noun" },
|
||||
shortAnswers: { user_language: "Yes, there are. / No, there aren't.", type: "noun" },
|
||||
}
|
||||
}
|
||||
},
|
||||
plurals: {
|
||||
topic: { user_language: "可数名词复数", type: "noun" },
|
||||
pronunciation: {
|
||||
rules: [
|
||||
{
|
||||
condition: { user_language: "在清辅音/-p,-k/后", type: "noun" },
|
||||
pronunciation: { user_language: "/-s/", type: "noun" },
|
||||
example: { user_language: "socks中-k是清辅音/-k/,所以-s读/-s/", type: "noun" },
|
||||
},
|
||||
{
|
||||
condition: { user_language: "在浊辅音和元音音标后", type: "noun" },
|
||||
pronunciation: { user_language: "/-z/", type: "noun" },
|
||||
example: { user_language: "jeans中-n是浊辅音/-n/, 所以-s读/-z/; tie的读音是/tai/,以元音结尾,所以-s读/-z/", type: "noun" },
|
||||
},
|
||||
{
|
||||
condition: { user_language: "以/-s,-z,-ʃ,-ʒ,-tʃ,-dʒ/发音结尾的名词", type: "noun" },
|
||||
pronunciation: { user_language: "/-iz/", type: "noun" },
|
||||
example: { user_language: "watches中-ch读/-tʃ/,所以-es读/-iz/", type: "noun" },
|
||||
}
|
||||
]
|
||||
},
|
||||
formation: {
|
||||
regular: {
|
||||
rule: { user_language: "一般在词尾加-s", type: "noun" },
|
||||
examples: ["shirts", "shoes"]
|
||||
},
|
||||
special: {
|
||||
rule: { user_language: "以-s,-sh,-ch,-x,以及辅音字母o结尾的词在词尾加-es", type: "noun" },
|
||||
examples: ["boxes", "buses", "potatoes", "tomatoes", "heroes"]
|
||||
},
|
||||
irregular: {
|
||||
rule: { user_language: "特殊的复数形式", type: "noun" },
|
||||
examples: {
|
||||
"man": { user_language: "men", type: "noun" },
|
||||
"woman": { user_language: "women", type: "noun" },
|
||||
"child": { user_language: "children", type: "noun" },
|
||||
"tooth": { user_language: "teeth", type: "noun" },
|
||||
"mouse": { user_language: "mice", type: "noun" },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
listening: {
|
||||
jMartShopping: {
|
||||
title: { user_language: "Attention, J-Mart Shoppers!", type: "noun" },
|
||||
items: [
|
||||
{ item: { user_language: "jackets", type: "noun" }, aisle: { user_language: "Aisle 9", type: "noun" }, },
|
||||
{ item: { user_language: "gloves", type: "noun" }, aisle: { user_language: "Aisle 7", type: "noun" }, },
|
||||
{ item: { user_language: "blouses", type: "noun" }, aisle: { user_language: "Aisle 9", type: "noun" }, },
|
||||
{ item: { user_language: "bracelets", type: "noun" }, aisle: { user_language: "Aisle 11", type: "noun" }, },
|
||||
{ item: { user_language: "ties", type: "noun" }, aisle: { user_language: "Aisle 5", type: "noun" }, }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
exercises: {
|
||||
sentenceCompletion: [
|
||||
"That's a very nice _______.",
|
||||
"Those are very nice _______."
|
||||
],
|
||||
questions: [
|
||||
"What different kinds of homes are there in your country?",
|
||||
"How about your neighborhood? Tell me about it.",
|
||||
"What are the meanings of different colors in YOUR culture?"
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Export pour le système de modules web
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
window.ContentModules.SBSLevel78New = {
|
||||
name: { user_language: "SBS Level 7-8 (New)", type: "noun" },
|
||||
description: { user_language: "Format simple et clair - Homes, Clothing & Cultures", type: "noun" },
|
||||
difficulty: { user_language: "intermediate", type: "noun" },
|
||||
vocabulary: content.vocabulary,
|
||||
sentences: content.sentences,
|
||||
texts: content.texts,
|
||||
grammar: content.grammar,
|
||||
listening: content.listening,
|
||||
exercises: content.exercises
|
||||
};
|
||||
|
||||
// Export Node.js (optionnel)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = content;
|
||||
}
|
||||
@ -1,160 +0,0 @@
|
||||
// === CONTENU DE TEST POUR LA COMPATIBILITÉ ===
|
||||
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
|
||||
// Contenu avec seulement 2 mots (devrait être incompatible avec whack-a-mole)
|
||||
window.ContentModules.TestMinimalContent = {
|
||||
id: "test-minimal-content",
|
||||
name: "Test Minimal (2 mots)",
|
||||
description: "Contenu minimal pour tester la compatibilité",
|
||||
difficulty: "easy",
|
||||
language: "en-US",
|
||||
|
||||
vocabulary: {
|
||||
"hello": "bonjour",
|
||||
"world": "monde"
|
||||
}
|
||||
};
|
||||
|
||||
// Contenu riche (devrait être compatible avec tous les jeux)
|
||||
window.ContentModules.TestRichContent = {
|
||||
id: "test-rich-content",
|
||||
name: "Test Riche (complet)",
|
||||
description: "Contenu riche pour tester la compatibilité maximale",
|
||||
difficulty: "medium",
|
||||
|
||||
vocabulary: {
|
||||
"apple": {
|
||||
translation: "pomme",
|
||||
prononciation: "apple",
|
||||
type: "noun",
|
||||
pronunciation: "audio/apple.mp3"
|
||||
},
|
||||
"book": {
|
||||
translation: "livre",
|
||||
prononciation: "book",
|
||||
type: "noun"
|
||||
},
|
||||
"car": {
|
||||
translation: "voiture",
|
||||
prononciation: "car",
|
||||
type: "noun"
|
||||
},
|
||||
"dog": {
|
||||
translation: "chien",
|
||||
prononciation: "dog",
|
||||
type: "noun"
|
||||
},
|
||||
"eat": {
|
||||
translation: "manger",
|
||||
prononciation: "eat",
|
||||
type: "verb"
|
||||
},
|
||||
"friend": {
|
||||
translation: "ami",
|
||||
prononciation: "friend",
|
||||
type: "noun"
|
||||
},
|
||||
"good": {
|
||||
translation: "bon",
|
||||
prononciation: "good",
|
||||
type: "adjective"
|
||||
},
|
||||
"house": {
|
||||
translation: "maison",
|
||||
prononciation: "house",
|
||||
type: "noun"
|
||||
}
|
||||
},
|
||||
|
||||
sentences: [
|
||||
{
|
||||
english: "I have a red apple",
|
||||
french: "J'ai une pomme rouge",
|
||||
prononciation: "ai hav a red apple"
|
||||
},
|
||||
{
|
||||
english: "The dog is in the house",
|
||||
french: "Le chien est dans la maison",
|
||||
prononciation: "ze dog iz in ze house"
|
||||
},
|
||||
{
|
||||
english: "My friend has a car",
|
||||
french: "Mon ami a une voiture",
|
||||
prononciation: "mai friend haz a car"
|
||||
},
|
||||
{
|
||||
english: "I like to read books",
|
||||
french: "J'aime lire des livres",
|
||||
prononciation: "ai laik tu rid books"
|
||||
},
|
||||
{
|
||||
english: "This is a good book",
|
||||
french: "C'est un bon livre",
|
||||
prononciation: "zis iz a gud book"
|
||||
}
|
||||
],
|
||||
|
||||
dialogues: [
|
||||
{
|
||||
title: "Au restaurant",
|
||||
conversation: [
|
||||
{ speaker: "Waiter", english: "What would you like to eat?", french: "Que voulez-vous manger ?" },
|
||||
{ speaker: "Customer", english: "I would like an apple", french: "Je voudrais une pomme" },
|
||||
{ speaker: "Waiter", english: "Good choice!", french: "Bon choix !" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
fillInBlanks: [
|
||||
{
|
||||
sentence: "I have a red _____",
|
||||
options: ["apple", "book", "car", "dog"],
|
||||
correctAnswer: "apple",
|
||||
explanation: "Apple fits the context"
|
||||
},
|
||||
{
|
||||
sentence: "The _____ is good",
|
||||
options: ["book", "apple", "house", "friend"],
|
||||
correctAnswer: "book",
|
||||
explanation: "Books can be described as good"
|
||||
}
|
||||
],
|
||||
|
||||
grammar: {
|
||||
articles: {
|
||||
title: "Articles (a, an, the)",
|
||||
explanation: "Use 'a' before consonants, 'an' before vowels",
|
||||
examples: [
|
||||
{ english: "a book", french: "un livre" },
|
||||
{ english: "an apple", french: "une pomme" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
audio: {
|
||||
withText: [
|
||||
{
|
||||
title: "Vocabulary pronunciation",
|
||||
transcript: "Apple, book, car, dog, eat, friend, good, house",
|
||||
translation: "Pomme, livre, voiture, chien, manger, ami, bon, maison"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Contenu avec seulement des phrases (bon pour text-reader, limité pour memory-match)
|
||||
window.ContentModules.TestSentenceOnly = {
|
||||
id: "test-sentence-only",
|
||||
name: "Test Phrases Seulement",
|
||||
description: "Contenu avec seulement des phrases pour tester la compatibilité spécialisée",
|
||||
difficulty: "medium",
|
||||
|
||||
sentences: [
|
||||
{ english: "The weather is nice today", french: "Le temps est beau aujourd'hui" },
|
||||
{ english: "I am going to school", french: "Je vais à l'école" },
|
||||
{ english: "She likes to read books", french: "Elle aime lire des livres" },
|
||||
{ english: "We are learning English", french: "Nous apprenons l'anglais" },
|
||||
{ english: "They play football every day", french: "Ils jouent au football tous les jours" }
|
||||
]
|
||||
};
|
||||
@ -1,16 +0,0 @@
|
||||
// === CONTENU DE TEST MINIMAL ===
|
||||
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
|
||||
window.ContentModules.TestMinimal = {
|
||||
id: "test-minimal",
|
||||
name: "Test Minimal (2 mots)",
|
||||
description: "Contenu minimal pour tester la compatibilité - seulement 2 mots",
|
||||
difficulty: "easy",
|
||||
language: "en-US",
|
||||
|
||||
vocabulary: {
|
||||
"hello": "bonjour",
|
||||
"world": "monde"
|
||||
}
|
||||
};
|
||||
@ -1,98 +0,0 @@
|
||||
// === CONTENU DE TEST RICHE ===
|
||||
|
||||
window.ContentModules = window.ContentModules || {};
|
||||
|
||||
window.ContentModules.TestRich = {
|
||||
id: "test-rich",
|
||||
name: "Test Riche (complet)",
|
||||
description: "Contenu riche pour tester la compatibilité maximale",
|
||||
difficulty: "medium",
|
||||
language: "en-US",
|
||||
|
||||
vocabulary: {
|
||||
"apple": {
|
||||
translation: "pomme",
|
||||
prononciation: "apple",
|
||||
type: "noun",
|
||||
pronunciation: "audio/apple.mp3"
|
||||
},
|
||||
"book": {
|
||||
translation: "livre",
|
||||
prononciation: "book",
|
||||
type: "noun"
|
||||
},
|
||||
"car": {
|
||||
translation: "voiture",
|
||||
prononciation: "car",
|
||||
type: "noun"
|
||||
},
|
||||
"dog": {
|
||||
translation: "chien",
|
||||
prononciation: "dog",
|
||||
type: "noun"
|
||||
},
|
||||
"eat": {
|
||||
translation: "manger",
|
||||
prononciation: "eat",
|
||||
type: "verb"
|
||||
},
|
||||
"friend": {
|
||||
translation: "ami",
|
||||
prononciation: "friend",
|
||||
type: "noun"
|
||||
}
|
||||
},
|
||||
|
||||
sentences: [
|
||||
{
|
||||
english: "I have a red apple",
|
||||
french: "J'ai une pomme rouge",
|
||||
prononciation: "ai hav a red apple"
|
||||
},
|
||||
{
|
||||
english: "The dog is in the house",
|
||||
french: "Le chien est dans la maison",
|
||||
prononciation: "ze dog iz in ze house"
|
||||
},
|
||||
{
|
||||
english: "My friend has a car",
|
||||
french: "Mon ami a une voiture",
|
||||
prononciation: "mai friend haz a car"
|
||||
},
|
||||
{
|
||||
english: "I like to read books",
|
||||
french: "J'aime lire des livres",
|
||||
prononciation: "ai laik tu rid books"
|
||||
}
|
||||
],
|
||||
|
||||
dialogues: [
|
||||
{
|
||||
title: "Au restaurant",
|
||||
conversation: [
|
||||
{ speaker: "Waiter", english: "What would you like to eat?", french: "Que voulez-vous manger ?" },
|
||||
{ speaker: "Customer", english: "I would like an apple", french: "Je voudrais une pomme" },
|
||||
{ speaker: "Waiter", english: "Good choice!", french: "Bon choix !" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
fillInBlanks: [
|
||||
{
|
||||
sentence: "I have a red _____",
|
||||
options: ["apple", "book", "car", "dog"],
|
||||
correctAnswer: "apple",
|
||||
explanation: "Apple fits the context"
|
||||
}
|
||||
],
|
||||
|
||||
audio: {
|
||||
withText: [
|
||||
{
|
||||
title: "Vocabulary pronunciation",
|
||||
transcript: "Apple, book, car, dog, eat, friend",
|
||||
translation: "Pomme, livre, voiture, chien, manger, ami"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@ -49,6 +49,11 @@ class ContentEngine {
|
||||
return this.migrator.migrateToNewFormat(rawContent);
|
||||
}
|
||||
|
||||
// Enrichir le contenu avec la structure letters si présente
|
||||
if (rawContent.letters) {
|
||||
rawContent = this.enrichContentWithLetters(rawContent);
|
||||
}
|
||||
|
||||
// Valider le nouveau format
|
||||
if (!this.validator.validate(rawContent)) {
|
||||
throw new Error('Format de contenu invalide');
|
||||
@ -57,6 +62,83 @@ class ContentEngine {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
// Enrichir le contenu avec les mots extraits de la structure letters
|
||||
enrichContentWithLetters(content) {
|
||||
logSh('🔤 Enrichissement du contenu avec structure letters...', 'INFO');
|
||||
|
||||
const enrichedContent = { ...content };
|
||||
|
||||
// Extraire tous les mots de la structure letters
|
||||
const letterWords = this.extractWordsFromLetters(content.letters);
|
||||
|
||||
// Si pas de vocabulaire existant, créer depuis letters
|
||||
if (!enrichedContent.vocabulary) {
|
||||
enrichedContent.vocabulary = {};
|
||||
}
|
||||
|
||||
// Ajouter les mots de letters au vocabulaire (sans écraser)
|
||||
letterWords.forEach(wordData => {
|
||||
if (!enrichedContent.vocabulary[wordData.word]) {
|
||||
enrichedContent.vocabulary[wordData.word] = {
|
||||
translation: wordData.translation,
|
||||
user_language: wordData.translation,
|
||||
type: wordData.type,
|
||||
pronunciation: wordData.pronunciation,
|
||||
prononciation: wordData.pronunciation,
|
||||
gender: wordData.gender
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Ajouter les métadonnées pour Letter Discovery
|
||||
enrichedContent.supportsLetterDiscovery = true;
|
||||
enrichedContent.totalLetters = Object.keys(content.letters).length;
|
||||
enrichedContent.totalLetterWords = letterWords.length;
|
||||
|
||||
logSh(`📝 ${letterWords.length} mots extraits de ${Object.keys(content.letters).length} lettres`, 'INFO');
|
||||
|
||||
return enrichedContent;
|
||||
}
|
||||
|
||||
// Extraire tous les mots de la structure letters
|
||||
extractWordsFromLetters(letters) {
|
||||
const allWords = [];
|
||||
|
||||
Object.keys(letters).forEach(letter => {
|
||||
const wordsForLetter = letters[letter];
|
||||
if (Array.isArray(wordsForLetter)) {
|
||||
wordsForLetter.forEach(wordData => {
|
||||
allWords.push({
|
||||
word: wordData.word,
|
||||
translation: wordData.translation,
|
||||
pronunciation: wordData.pronunciation,
|
||||
type: wordData.type,
|
||||
gender: wordData.gender,
|
||||
letter: letter
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return allWords;
|
||||
}
|
||||
|
||||
// Obtenir les mots pour une lettre spécifique
|
||||
getWordsForLetter(content, letter) {
|
||||
if (content.letters && content.letters[letter]) {
|
||||
return content.letters[letter];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Obtenir toutes les lettres disponibles
|
||||
getAvailableLetters(content) {
|
||||
if (content.letters) {
|
||||
return Object.keys(content.letters).sort();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
isOldFormat(content) {
|
||||
// Détecter l'ancien format (simple array vocabulary)
|
||||
return content.vocabulary && Array.isArray(content.vocabulary) &&
|
||||
|
||||
@ -98,12 +98,15 @@ class ContentScanner {
|
||||
async preloadKnownFiles() {
|
||||
const knownFiles = [
|
||||
'sbs-level-7-8-new.js', // Local JS file
|
||||
'SBS-level-1.js', // Side by Side Level 1 - English introduction with Chinese translation
|
||||
'english-class-demo.json', // Remote JSON file
|
||||
'english-exemple-commented.js', // Module JS complet nouvellement créé
|
||||
'story-test.js', // Story test module
|
||||
'story-prototype-1000words.js', // 1000-word story prototype
|
||||
'story-complete-1000words.js', // Complete 1000-word story with pronunciation
|
||||
'chinese-long-story.js', // Chinese story with English translation and pinyin
|
||||
'french-beginner-story.js', // French beginner story for English speakers
|
||||
'WTA1B1.js', // English letters and pets story with Chinese translation
|
||||
'story-prototype-optimized.js', // Optimized story with centralized vocabulary
|
||||
'test-compatibility.js', // Test content for compatibility system
|
||||
'test-minimal.js', // Minimal test content
|
||||
@ -285,6 +288,9 @@ class ContentScanner {
|
||||
'StoryPrototype1000words': 'story-prototype-1000words.js',
|
||||
'StoryComplete1000words': 'story-complete-1000words.js',
|
||||
'ChineseLongStory': 'chinese-long-story.js',
|
||||
'FrenchBeginnerStory': 'french-beginner-story.js',
|
||||
'WTA1B1': 'WTA1B1.js',
|
||||
'SBSLevel1': 'SBS-level-1.js',
|
||||
'StoryPrototypeOptimized': 'story-prototype-optimized.js',
|
||||
// AJOUT: Test compatibility modules
|
||||
'TestMinimalContent': 'test-compatibility.js',
|
||||
@ -483,7 +489,10 @@ class ContentScanner {
|
||||
getModuleName(contentId) {
|
||||
const mapping = {
|
||||
'sbs-level-7-8-new': 'SBSLevel78New',
|
||||
'sbs-level-1': 'SBSLevel1',
|
||||
'chinese-long-story': 'ChineseLongStory',
|
||||
'french-beginner-story': 'FrenchBeginnerStory',
|
||||
'wta1b1': 'WTA1B1',
|
||||
'story-prototype-optimized': 'StoryPrototypeOptimized',
|
||||
'test-compatibility': 'TestMinimalContent',
|
||||
'test-minimal': 'TestMinimal',
|
||||
@ -728,8 +737,11 @@ class ContentScanner {
|
||||
// Mapping spécifique pour certains noms de fichiers
|
||||
const specialMappings = {
|
||||
'sbs-level-7-8-new': 'SBSLevel78New',
|
||||
'sbs-level-1': 'SBSLevel1',
|
||||
'english-class-demo': 'EnglishClassDemo',
|
||||
'chinese-long-story': 'ChineseLongStory',
|
||||
'french-beginner-story': 'FrenchBeginnerStory',
|
||||
'wta1b1': 'WTA1B1',
|
||||
'story-prototype-optimized': 'StoryPrototypeOptimized'
|
||||
};
|
||||
|
||||
|
||||
@ -322,8 +322,11 @@ const GameLoader = {
|
||||
'adventure-reader': 'AdventureReader',
|
||||
'chinese-study': 'ChineseStudy',
|
||||
'story-reader': 'StoryReader',
|
||||
'grammar-discovery': 'GrammarDiscovery',
|
||||
'word-storm': 'WordStorm',
|
||||
'word-discovery': 'WordDiscovery'
|
||||
'word-discovery': 'WordDiscovery',
|
||||
'letter-discovery': 'LetterDiscovery',
|
||||
'river-run': 'RiverRun'
|
||||
};
|
||||
return names[gameType] || gameType;
|
||||
},
|
||||
@ -332,8 +335,13 @@ const GameLoader = {
|
||||
// Utilise la même logique que le ContentScanner
|
||||
const mapping = {
|
||||
'sbs-level-7-8-new': 'SBSLevel78New',
|
||||
'sbs-level-1': 'SBSLevel1',
|
||||
'basic-chinese': 'BasicChinese',
|
||||
'english-class-demo': 'EnglishClassDemo',
|
||||
'chinese-long-story': 'ChineseLongStory',
|
||||
'french-beginner-story': 'FrenchBeginnerStory',
|
||||
'wta1b1': 'WTA1B1',
|
||||
'story-prototype-optimized': 'StoryPrototypeOptimized',
|
||||
'test-minimal-content': 'TestMinimalContent',
|
||||
'test-rich-content': 'TestRichContent',
|
||||
'test-sentence-only': 'TestSentenceOnly',
|
||||
|
||||
@ -103,6 +103,24 @@ const AppNavigation = {
|
||||
name: 'Word Discovery',
|
||||
icon: '🔍',
|
||||
description: 'Learn new words with images and interactive practice!'
|
||||
},
|
||||
'letter-discovery': {
|
||||
enabled: true,
|
||||
name: 'Letter Discovery',
|
||||
icon: '🔤',
|
||||
description: 'Discover letters first, then explore words that start with each letter!'
|
||||
},
|
||||
'river-run': {
|
||||
enabled: true,
|
||||
name: 'River Run',
|
||||
icon: '🌊',
|
||||
description: 'Navigate the river and catch your target words while avoiding obstacles!'
|
||||
},
|
||||
'grammar-discovery': {
|
||||
enabled: true,
|
||||
name: 'Grammar Discovery',
|
||||
icon: '📚',
|
||||
description: 'Discover and learn grammar patterns through interactive examples!'
|
||||
}
|
||||
},
|
||||
content: {
|
||||
@ -148,6 +166,12 @@ const AppNavigation = {
|
||||
icon: '🐉',
|
||||
description: 'Chinese story with English translation and pinyin pronunciation'
|
||||
},
|
||||
'chinese-beginner-story': {
|
||||
enabled: true,
|
||||
name: 'Le Jardin Magique (Chinese → French)',
|
||||
icon: '🌸',
|
||||
description: 'Simple Chinese story for beginners with French translation'
|
||||
},
|
||||
'story-prototype-optimized': {
|
||||
enabled: true,
|
||||
name: 'The Magical Library (Optimized)',
|
||||
@ -668,6 +692,7 @@ const AppNavigation = {
|
||||
'basic-chinese': 'BasicChinese',
|
||||
'english-class-demo': 'EnglishClassDemo',
|
||||
'chinese-long-story': 'ChineseLongStory',
|
||||
'chinese-beginner-story': 'ChineseBeginnerStory',
|
||||
'test-minimal': 'TestMinimal',
|
||||
'test-rich': 'TestRich'
|
||||
};
|
||||
|
||||
@ -23,6 +23,13 @@ class AdventureReaderGame {
|
||||
this.isPlayerInvulnerable = false;
|
||||
this.invulnerabilityTimeout = null;
|
||||
|
||||
// TTS settings
|
||||
this.autoPlayTTS = true;
|
||||
this.ttsEnabled = true;
|
||||
|
||||
// Expose content globally for SettingsManager TTS language detection
|
||||
window.currentGameContent = this.content;
|
||||
|
||||
// Content extraction
|
||||
this.vocabulary = this.extractVocabulary(this.content);
|
||||
this.sentences = this.extractSentences(this.content);
|
||||
@ -703,6 +710,13 @@ class AdventureReaderGame {
|
||||
popup.style.display = 'block';
|
||||
popup.classList.add('show');
|
||||
|
||||
// Auto-play TTS for vocabulary
|
||||
if (this.autoPlayTTS && this.ttsEnabled) {
|
||||
setTimeout(() => {
|
||||
this.speakText(vocab.original_language, { rate: 0.8 });
|
||||
}, 400); // Small delay to let popup appear
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
popup.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
@ -747,6 +761,13 @@ class AdventureReaderGame {
|
||||
|
||||
modal.style.display = 'flex';
|
||||
modal.classList.add('show');
|
||||
|
||||
// Auto-play TTS for sentence
|
||||
if (this.autoPlayTTS && this.ttsEnabled) {
|
||||
setTimeout(() => {
|
||||
this.speakText(sentence.original_language, { rate: 0.8 });
|
||||
}, 600); // Longer delay for modal animation
|
||||
}
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
@ -1202,7 +1223,61 @@ class AdventureReaderGame {
|
||||
this.generateDecorations();
|
||||
}
|
||||
|
||||
// TTS Methods
|
||||
speakText(text, options = {}) {
|
||||
if (!text || !this.ttsEnabled) return;
|
||||
|
||||
// Use SettingsManager if available for better language support
|
||||
if (window.SettingsManager && window.SettingsManager.speak) {
|
||||
const ttsOptions = {
|
||||
lang: this.getContentLanguage(),
|
||||
rate: options.rate || 0.8,
|
||||
...options
|
||||
};
|
||||
|
||||
window.SettingsManager.speak(text, ttsOptions)
|
||||
.catch(error => {
|
||||
console.warn('🔊 SettingsManager TTS failed:', error);
|
||||
this.fallbackTTS(text, ttsOptions);
|
||||
});
|
||||
} else {
|
||||
this.fallbackTTS(text, options);
|
||||
}
|
||||
}
|
||||
|
||||
fallbackTTS(text, options = {}) {
|
||||
if ('speechSynthesis' in window && text) {
|
||||
// Cancel any ongoing speech
|
||||
speechSynthesis.cancel();
|
||||
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = this.getContentLanguage();
|
||||
utterance.rate = options.rate || 0.8;
|
||||
utterance.volume = 1.0;
|
||||
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
getContentLanguage() {
|
||||
// Get language from content or use sensible defaults
|
||||
if (this.content.language) {
|
||||
const langMap = {
|
||||
'chinese': 'zh-CN',
|
||||
'english': 'en-US',
|
||||
'french': 'fr-FR',
|
||||
'spanish': 'es-ES'
|
||||
};
|
||||
return langMap[this.content.language] || this.content.language;
|
||||
}
|
||||
return 'en-US'; // Default fallback
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Cancel any ongoing TTS
|
||||
if ('speechSynthesis' in window) {
|
||||
speechSynthesis.cancel();
|
||||
}
|
||||
this.container.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
1185
js/games/grammar-discovery.js
Normal file
1185
js/games/grammar-discovery.js
Normal file
File diff suppressed because it is too large
Load Diff
774
js/games/letter-discovery.js
Normal file
774
js/games/letter-discovery.js
Normal file
@ -0,0 +1,774 @@
|
||||
// === LETTER DISCOVERY GAME ===
|
||||
// Discover letters first, then explore words that start with each letter
|
||||
|
||||
class LetterDiscovery {
|
||||
constructor({ container, content, onScoreUpdate, onGameEnd }) {
|
||||
this.container = container;
|
||||
this.content = content;
|
||||
this.onScoreUpdate = onScoreUpdate;
|
||||
this.onGameEnd = onGameEnd;
|
||||
|
||||
// Game state
|
||||
this.currentPhase = 'letter-discovery'; // letter-discovery, word-exploration, practice
|
||||
this.currentLetterIndex = 0;
|
||||
this.discoveredLetters = [];
|
||||
this.currentLetter = null;
|
||||
this.currentWordIndex = 0;
|
||||
this.discoveredWords = [];
|
||||
this.score = 0;
|
||||
this.lives = 3;
|
||||
|
||||
// Content processing
|
||||
this.letters = [];
|
||||
this.letterWords = {}; // Map letter -> words starting with that letter
|
||||
|
||||
// Practice system
|
||||
this.practiceLevel = 1;
|
||||
this.practiceRound = 0;
|
||||
this.maxPracticeRounds = 8;
|
||||
this.practiceCorrectAnswers = 0;
|
||||
this.practiceErrors = 0;
|
||||
this.currentPracticeItems = [];
|
||||
|
||||
this.injectCSS();
|
||||
this.extractContent();
|
||||
this.init();
|
||||
}
|
||||
|
||||
injectCSS() {
|
||||
if (document.getElementById('letter-discovery-styles')) return;
|
||||
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.id = 'letter-discovery-styles';
|
||||
styleSheet.textContent = `
|
||||
.letter-discovery-wrapper {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.letter-discovery-hud {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 15px 20px;
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hud-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.hud-item {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.phase-indicator {
|
||||
background: rgba(255,255,255,0.2);
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
color: white;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.letter-discovery-main {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
backdrop-filter: blur(10px);
|
||||
min-height: 70vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.game-content {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Letter Display Styles */
|
||||
.letter-card {
|
||||
background: rgba(255,255,255,0.95);
|
||||
border-radius: 25px;
|
||||
padding: 60px 40px;
|
||||
margin: 30px auto;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
transform: scale(0.8);
|
||||
animation: letterAppear 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes letterAppear {
|
||||
to { transform: scale(1); }
|
||||
}
|
||||
|
||||
.letter-display {
|
||||
font-size: 8em;
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
font-family: 'Arial Black', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.letter-info {
|
||||
font-size: 1.5em;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.letter-pronunciation {
|
||||
font-size: 1.2em;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.letter-controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* Word Exploration Styles */
|
||||
.word-exploration-header {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.exploring-letter {
|
||||
font-size: 3em;
|
||||
color: white;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.word-progress {
|
||||
color: rgba(255,255,255,0.8);
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.word-card {
|
||||
background: rgba(255,255,255,0.95);
|
||||
border-radius: 20px;
|
||||
padding: 40px 30px;
|
||||
margin: 25px auto;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
|
||||
transform: translateY(20px);
|
||||
animation: wordSlideIn 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes wordSlideIn {
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
.word-text {
|
||||
font-size: 2.5em;
|
||||
color: #667eea;
|
||||
margin-bottom: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.word-translation {
|
||||
font-size: 1.3em;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.word-pronunciation {
|
||||
font-size: 1.1em;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Practice Challenge Styles */
|
||||
.practice-challenge {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.challenge-text {
|
||||
font-size: 1.8em;
|
||||
color: white;
|
||||
margin-bottom: 25px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.practice-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.practice-option {
|
||||
background: rgba(255,255,255,0.9);
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.practice-option:hover {
|
||||
background: rgba(255,255,255,1);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.practice-option.correct {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
animation: correctPulse 0.6s ease;
|
||||
}
|
||||
|
||||
.practice-option.incorrect {
|
||||
background: #F44336;
|
||||
color: white;
|
||||
animation: incorrectShake 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes correctPulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
@keyframes incorrectShake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-5px); }
|
||||
75% { transform: translateX(5px); }
|
||||
}
|
||||
|
||||
.practice-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 20px;
|
||||
color: white;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* Control Buttons */
|
||||
.discovery-btn {
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.discovery-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.discovery-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.audio-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2em;
|
||||
cursor: pointer;
|
||||
color: #667eea;
|
||||
margin-left: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.audio-btn:hover {
|
||||
transform: scale(1.2);
|
||||
color: #764ba2;
|
||||
}
|
||||
|
||||
/* Completion Message */
|
||||
.completion-message {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.completion-title {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
color: #00ff88;
|
||||
text-shadow: 0 2px 10px rgba(0,255,136,0.3);
|
||||
}
|
||||
|
||||
.completion-stats {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.letter-discovery-wrapper {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.letter-display {
|
||||
font-size: 6em;
|
||||
}
|
||||
|
||||
.word-text {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.challenge-text {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.practice-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleSheet);
|
||||
}
|
||||
|
||||
extractContent() {
|
||||
logSh('🔍 Letter Discovery - Extracting content...', 'INFO');
|
||||
|
||||
// Check if content has letter structure
|
||||
if (this.content.letters) {
|
||||
this.letters = Object.keys(this.content.letters);
|
||||
this.letterWords = this.content.letters;
|
||||
logSh(`📝 Found ${this.letters.length} letters with words`, 'INFO');
|
||||
} else {
|
||||
// Fallback: Create letter structure from vocabulary
|
||||
this.generateLetterStructure();
|
||||
}
|
||||
|
||||
if (this.letters.length === 0) {
|
||||
throw new Error('No letters found in content');
|
||||
}
|
||||
|
||||
logSh(`🎯 Letter Discovery ready: ${this.letters.length} letters`, 'INFO');
|
||||
}
|
||||
|
||||
generateLetterStructure() {
|
||||
logSh('🔧 Generating letter structure from vocabulary...', 'INFO');
|
||||
|
||||
const letterMap = {};
|
||||
|
||||
if (this.content.vocabulary) {
|
||||
Object.keys(this.content.vocabulary).forEach(word => {
|
||||
const firstLetter = word.charAt(0).toUpperCase();
|
||||
if (!letterMap[firstLetter]) {
|
||||
letterMap[firstLetter] = [];
|
||||
}
|
||||
|
||||
const wordData = this.content.vocabulary[word];
|
||||
letterMap[firstLetter].push({
|
||||
word: word,
|
||||
translation: typeof wordData === 'string' ? wordData : wordData.translation || wordData.user_language,
|
||||
pronunciation: wordData.pronunciation || wordData.prononciation,
|
||||
type: wordData.type,
|
||||
image: wordData.image,
|
||||
audioFile: wordData.audioFile
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.letters = Object.keys(letterMap).sort();
|
||||
this.letterWords = letterMap;
|
||||
|
||||
logSh(`📝 Generated ${this.letters.length} letters from vocabulary`, 'INFO');
|
||||
}
|
||||
|
||||
init() {
|
||||
this.container.innerHTML = `
|
||||
<div class="letter-discovery-wrapper">
|
||||
<div class="letter-discovery-hud">
|
||||
<div class="hud-group">
|
||||
<div class="hud-item">Score: <span id="score-display">${this.score}</span></div>
|
||||
<div class="hud-item">Lives: <span id="lives-display">${this.lives}</span></div>
|
||||
</div>
|
||||
<div class="phase-indicator" id="phase-indicator">Letter Discovery</div>
|
||||
<div class="hud-group">
|
||||
<div class="hud-item">Progress: <span id="progress-display">0/${this.letters.length}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="letter-discovery-main">
|
||||
<div class="game-content" id="game-content">
|
||||
<!-- Dynamic content here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.updateHUD();
|
||||
}
|
||||
|
||||
start() {
|
||||
this.showLetterCard();
|
||||
}
|
||||
|
||||
updateHUD() {
|
||||
const scoreDisplay = document.getElementById('score-display');
|
||||
const livesDisplay = document.getElementById('lives-display');
|
||||
const progressDisplay = document.getElementById('progress-display');
|
||||
const phaseIndicator = document.getElementById('phase-indicator');
|
||||
|
||||
if (scoreDisplay) scoreDisplay.textContent = this.score;
|
||||
if (livesDisplay) livesDisplay.textContent = this.lives;
|
||||
|
||||
if (this.currentPhase === 'letter-discovery') {
|
||||
if (progressDisplay) progressDisplay.textContent = `${this.currentLetterIndex}/${this.letters.length}`;
|
||||
if (phaseIndicator) phaseIndicator.textContent = 'Letter Discovery';
|
||||
} else if (this.currentPhase === 'word-exploration') {
|
||||
if (progressDisplay) progressDisplay.textContent = `${this.currentWordIndex}/${this.letterWords[this.currentLetter].length}`;
|
||||
if (phaseIndicator) phaseIndicator.textContent = `Exploring Letter "${this.currentLetter}"`;
|
||||
} else if (this.currentPhase === 'practice') {
|
||||
if (progressDisplay) progressDisplay.textContent = `Round ${this.practiceRound + 1}/${this.maxPracticeRounds}`;
|
||||
if (phaseIndicator) phaseIndicator.textContent = `Practice - Level ${this.practiceLevel}`;
|
||||
}
|
||||
}
|
||||
|
||||
showLetterCard() {
|
||||
if (this.currentLetterIndex >= this.letters.length) {
|
||||
this.showCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
const letter = this.letters[this.currentLetterIndex];
|
||||
const gameContent = document.getElementById('game-content');
|
||||
|
||||
gameContent.innerHTML = `
|
||||
<div class="letter-card">
|
||||
<div class="letter-display">${letter}</div>
|
||||
<div class="letter-info">Letter "${letter}"</div>
|
||||
<div class="letter-pronunciation">${this.getLetterPronunciation(letter)}</div>
|
||||
<div class="letter-controls">
|
||||
<button class="discovery-btn" onclick="window.currentLetterGame.discoverLetter()">
|
||||
🔍 Discover Letter
|
||||
</button>
|
||||
<button class="audio-btn" onclick="window.currentLetterGame.playLetterSound('${letter}')">
|
||||
🔊
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store reference for button callbacks
|
||||
window.currentLetterGame = this;
|
||||
|
||||
// Auto-play letter sound
|
||||
setTimeout(() => this.playLetterSound(letter), 500);
|
||||
}
|
||||
|
||||
getLetterPronunciation(letter) {
|
||||
// Basic letter pronunciation guide
|
||||
const pronunciations = {
|
||||
'A': 'ay', 'B': 'bee', 'C': 'see', 'D': 'dee', 'E': 'ee',
|
||||
'F': 'ef', 'G': 'gee', 'H': 'aych', 'I': 'eye', 'J': 'jay',
|
||||
'K': 'kay', 'L': 'el', 'M': 'em', 'N': 'en', 'O': 'oh',
|
||||
'P': 'pee', 'Q': 'cue', 'R': 'ar', 'S': 'ess', 'T': 'tee',
|
||||
'U': 'you', 'V': 'vee', 'W': 'double-you', 'X': 'ex', 'Y': 'why', 'Z': 'zee'
|
||||
};
|
||||
return pronunciations[letter] || letter.toLowerCase();
|
||||
}
|
||||
|
||||
playLetterSound(letter) {
|
||||
if (window.SettingsManager && window.SettingsManager.speak) {
|
||||
const speed = 0.8; // Slower for letters
|
||||
window.SettingsManager.speak(letter, {
|
||||
lang: this.content.language || 'en-US',
|
||||
rate: speed
|
||||
}).catch(error => {
|
||||
console.warn('🔊 TTS failed for letter:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
discoverLetter() {
|
||||
const letter = this.letters[this.currentLetterIndex];
|
||||
this.discoveredLetters.push(letter);
|
||||
this.score += 10;
|
||||
this.onScoreUpdate(this.score);
|
||||
|
||||
// Start word exploration for this letter
|
||||
this.currentLetter = letter;
|
||||
this.currentPhase = 'word-exploration';
|
||||
this.currentWordIndex = 0;
|
||||
|
||||
this.updateHUD();
|
||||
this.showWordExploration();
|
||||
}
|
||||
|
||||
showWordExploration() {
|
||||
const words = this.letterWords[this.currentLetter];
|
||||
|
||||
if (!words || this.currentWordIndex >= words.length) {
|
||||
// Finished exploring words for this letter
|
||||
this.currentPhase = 'letter-discovery';
|
||||
this.currentLetterIndex++;
|
||||
this.updateHUD();
|
||||
this.showLetterCard();
|
||||
return;
|
||||
}
|
||||
|
||||
const word = words[this.currentWordIndex];
|
||||
const gameContent = document.getElementById('game-content');
|
||||
|
||||
gameContent.innerHTML = `
|
||||
<div class="word-exploration-header">
|
||||
<div class="exploring-letter">Letter "${this.currentLetter}"</div>
|
||||
<div class="word-progress">Word ${this.currentWordIndex + 1} of ${words.length}</div>
|
||||
</div>
|
||||
<div class="word-card">
|
||||
<div class="word-text">${word.word}</div>
|
||||
<div class="word-translation">${word.translation}</div>
|
||||
${word.pronunciation ? `<div class="word-pronunciation">[${word.pronunciation}]</div>` : ''}
|
||||
<div class="letter-controls">
|
||||
<button class="discovery-btn" onclick="window.currentLetterGame.nextWord()">
|
||||
➡️ Next Word
|
||||
</button>
|
||||
<button class="audio-btn" onclick="window.currentLetterGame.playWordSound('${word.word}')">
|
||||
🔊
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add word to discovered list
|
||||
this.discoveredWords.push(word);
|
||||
|
||||
// Auto-play word sound
|
||||
setTimeout(() => this.playWordSound(word.word), 500);
|
||||
}
|
||||
|
||||
playWordSound(word) {
|
||||
if (window.SettingsManager && window.SettingsManager.speak) {
|
||||
const speed = 0.9;
|
||||
window.SettingsManager.speak(word, {
|
||||
lang: this.content.language || 'en-US',
|
||||
rate: speed
|
||||
}).catch(error => {
|
||||
console.warn('🔊 TTS failed for word:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
nextWord() {
|
||||
this.currentWordIndex++;
|
||||
this.score += 5;
|
||||
this.onScoreUpdate(this.score);
|
||||
this.updateHUD();
|
||||
this.showWordExploration();
|
||||
}
|
||||
|
||||
showCompletion() {
|
||||
const gameContent = document.getElementById('game-content');
|
||||
const totalWords = Object.values(this.letterWords).reduce((sum, words) => sum + words.length, 0);
|
||||
|
||||
gameContent.innerHTML = `
|
||||
<div class="completion-message">
|
||||
<div class="completion-title">🎉 All Letters Discovered!</div>
|
||||
<div class="completion-stats">
|
||||
Letters Discovered: ${this.discoveredLetters.length}<br>
|
||||
Words Learned: ${this.discoveredWords.length}<br>
|
||||
Final Score: ${this.score}
|
||||
</div>
|
||||
<div class="letter-controls">
|
||||
<button class="discovery-btn" onclick="window.currentLetterGame.startPractice()">
|
||||
🎮 Start Practice
|
||||
</button>
|
||||
<button class="discovery-btn" onclick="window.currentLetterGame.restart()">
|
||||
🔄 Play Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
startPractice() {
|
||||
this.currentPhase = 'practice';
|
||||
this.practiceLevel = 1;
|
||||
this.practiceRound = 0;
|
||||
this.practiceCorrectAnswers = 0;
|
||||
this.practiceErrors = 0;
|
||||
|
||||
// Create mixed practice from all discovered words
|
||||
this.currentPracticeItems = this.shuffleArray([...this.discoveredWords]);
|
||||
|
||||
this.updateHUD();
|
||||
this.showPracticeChallenge();
|
||||
}
|
||||
|
||||
showPracticeChallenge() {
|
||||
if (this.practiceRound >= this.maxPracticeRounds) {
|
||||
this.endPractice();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentItem = this.currentPracticeItems[this.practiceRound % this.currentPracticeItems.length];
|
||||
const gameContent = document.getElementById('game-content');
|
||||
|
||||
// Generate options (correct + 3 random)
|
||||
const allWords = this.discoveredWords.filter(w => w.word !== currentItem.word);
|
||||
const randomOptions = this.shuffleArray([...allWords]).slice(0, 3);
|
||||
const options = this.shuffleArray([currentItem, ...randomOptions]);
|
||||
|
||||
gameContent.innerHTML = `
|
||||
<div class="practice-challenge">
|
||||
<div class="challenge-text">What does "${currentItem.word}" mean?</div>
|
||||
<div class="practice-grid">
|
||||
${options.map((option, index) => `
|
||||
<button class="practice-option" onclick="window.currentLetterGame.selectPracticeAnswer(${index}, '${option.word}')">
|
||||
${option.translation}
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="practice-stats">
|
||||
<div class="stat-item">Correct: ${this.practiceCorrectAnswers}</div>
|
||||
<div class="stat-item">Errors: ${this.practiceErrors}</div>
|
||||
<div class="stat-item">Round: ${this.practiceRound + 1}/${this.maxPracticeRounds}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store correct answer for checking
|
||||
this.currentCorrectAnswer = currentItem.word;
|
||||
|
||||
// Auto-play word
|
||||
setTimeout(() => this.playWordSound(currentItem.word), 500);
|
||||
}
|
||||
|
||||
selectPracticeAnswer(selectedIndex, selectedWord) {
|
||||
const buttons = document.querySelectorAll('.practice-option');
|
||||
const isCorrect = selectedWord === this.currentCorrectAnswer;
|
||||
|
||||
if (isCorrect) {
|
||||
buttons[selectedIndex].classList.add('correct');
|
||||
this.practiceCorrectAnswers++;
|
||||
this.score += 10;
|
||||
this.onScoreUpdate(this.score);
|
||||
} else {
|
||||
buttons[selectedIndex].classList.add('incorrect');
|
||||
this.practiceErrors++;
|
||||
// Show correct answer
|
||||
buttons.forEach((btn, index) => {
|
||||
if (btn.textContent.trim() === this.discoveredWords.find(w => w.word === this.currentCorrectAnswer)?.translation) {
|
||||
btn.classList.add('correct');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.practiceRound++;
|
||||
this.updateHUD();
|
||||
this.showPracticeChallenge();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
endPractice() {
|
||||
const accuracy = Math.round((this.practiceCorrectAnswers / this.maxPracticeRounds) * 100);
|
||||
const gameContent = document.getElementById('game-content');
|
||||
|
||||
gameContent.innerHTML = `
|
||||
<div class="completion-message">
|
||||
<div class="completion-title">🏆 Practice Complete!</div>
|
||||
<div class="completion-stats">
|
||||
Accuracy: ${accuracy}%<br>
|
||||
Correct Answers: ${this.practiceCorrectAnswers}/${this.maxPracticeRounds}<br>
|
||||
Final Score: ${this.score}
|
||||
</div>
|
||||
<div class="letter-controls">
|
||||
<button class="discovery-btn" onclick="window.currentLetterGame.restart()">
|
||||
🔄 Play Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// End game
|
||||
setTimeout(() => {
|
||||
this.onGameEnd(this.score);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
shuffleArray(array) {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
restart() {
|
||||
this.currentPhase = 'letter-discovery';
|
||||
this.currentLetterIndex = 0;
|
||||
this.discoveredLetters = [];
|
||||
this.currentLetter = null;
|
||||
this.currentWordIndex = 0;
|
||||
this.discoveredWords = [];
|
||||
this.score = 0;
|
||||
this.lives = 3;
|
||||
this.practiceLevel = 1;
|
||||
this.practiceRound = 0;
|
||||
this.practiceCorrectAnswers = 0;
|
||||
this.practiceErrors = 0;
|
||||
this.currentPracticeItems = [];
|
||||
|
||||
this.updateHUD();
|
||||
this.start();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Cleanup
|
||||
if (window.currentLetterGame === this) {
|
||||
delete window.currentLetterGame;
|
||||
}
|
||||
|
||||
const styleSheet = document.getElementById('letter-discovery-styles');
|
||||
if (styleSheet) {
|
||||
styleSheet.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the game module
|
||||
window.GameModules = window.GameModules || {};
|
||||
window.GameModules.LetterDiscovery = LetterDiscovery;
|
||||
1001
js/games/river-run.js
Normal file
1001
js/games/river-run.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,8 @@ class StoryReader {
|
||||
|
||||
// Story data
|
||||
this.story = null;
|
||||
this.availableStories = [];
|
||||
this.currentStoryIndex = 0;
|
||||
this.vocabulary = {};
|
||||
|
||||
// UI state
|
||||
@ -31,6 +33,13 @@ class StoryReader {
|
||||
this.totalReadingTime = 0;
|
||||
this.readingTimer = null;
|
||||
|
||||
// TTS settings
|
||||
this.autoPlayTTS = true;
|
||||
this.ttsEnabled = true;
|
||||
|
||||
// Expose content globally for SettingsManager TTS language detection
|
||||
window.currentGameContent = this.content;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
@ -39,18 +48,30 @@ class StoryReader {
|
||||
logSh(`🔍 Story field exists: ${!!this.content.story}`, 'DEBUG');
|
||||
logSh(`🔍 RawContent exists: ${!!this.content.rawContent}`, 'DEBUG');
|
||||
|
||||
// Vérifier d'abord le contenu brut (rawContent) puis le contenu adapté
|
||||
const storyData = this.content.rawContent?.story || this.content.story;
|
||||
// Discover all available stories
|
||||
this.discoverAvailableStories();
|
||||
|
||||
if (!storyData) {
|
||||
if (this.availableStories.length === 0) {
|
||||
logSh('No story content found in content or rawContent', 'ERROR');
|
||||
this.showError('This content does not contain a story for reading.');
|
||||
this.showError('This content does not contain any stories for reading.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.story = storyData;
|
||||
// Get URL params to check if specific story is requested
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const requestedStory = urlParams.get('story');
|
||||
|
||||
if (requestedStory) {
|
||||
const storyIndex = this.availableStories.findIndex(story =>
|
||||
story.id === requestedStory || story.title.toLowerCase().includes(requestedStory.toLowerCase())
|
||||
);
|
||||
if (storyIndex !== -1) {
|
||||
this.currentStoryIndex = storyIndex;
|
||||
}
|
||||
}
|
||||
|
||||
this.selectStory(this.currentStoryIndex);
|
||||
this.vocabulary = this.content.rawContent?.vocabulary || this.content.vocabulary || {};
|
||||
this.calculateTotalSentences();
|
||||
|
||||
logSh(`📖 Story Reader initialized: "${this.story.title}" (${this.totalSentences} sentences)`, 'INFO');
|
||||
|
||||
@ -59,6 +80,62 @@ class StoryReader {
|
||||
this.renderCurrentSentence();
|
||||
}
|
||||
|
||||
discoverAvailableStories() {
|
||||
this.availableStories = [];
|
||||
|
||||
// Check main story field
|
||||
const mainStory = this.content.rawContent?.story || this.content.story;
|
||||
if (mainStory && mainStory.title) {
|
||||
this.availableStories.push({
|
||||
id: 'main',
|
||||
title: mainStory.title,
|
||||
data: mainStory,
|
||||
source: 'main'
|
||||
});
|
||||
}
|
||||
|
||||
// Check additionalStories field (like in WTA1B1)
|
||||
const additionalStories = this.content.rawContent?.additionalStories || this.content.additionalStories;
|
||||
if (additionalStories && Array.isArray(additionalStories)) {
|
||||
additionalStories.forEach((story, index) => {
|
||||
if (story && story.title) {
|
||||
this.availableStories.push({
|
||||
id: `additional_${index}`,
|
||||
title: story.title,
|
||||
data: story,
|
||||
source: 'additional'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logSh(`📚 Discovered ${this.availableStories.length} stories:`, this.availableStories.map(s => s.title), 'INFO');
|
||||
}
|
||||
|
||||
selectStory(storyIndex) {
|
||||
if (storyIndex >= 0 && storyIndex < this.availableStories.length) {
|
||||
this.currentStoryIndex = storyIndex;
|
||||
this.story = this.availableStories[storyIndex].data;
|
||||
this.calculateTotalSentences();
|
||||
|
||||
// Reset reading position for new story
|
||||
this.currentSentence = 0;
|
||||
this.wordsRead = 0;
|
||||
|
||||
// Update URL to include story parameter
|
||||
this.updateUrlForStory();
|
||||
|
||||
logSh(`📖 Selected story: "${this.story.title}" (${this.totalSentences} sentences)`, 'INFO');
|
||||
}
|
||||
}
|
||||
|
||||
updateUrlForStory() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
urlParams.set('story', this.availableStories[this.currentStoryIndex].id);
|
||||
const newUrl = `${window.location.pathname}?${urlParams.toString()}`;
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
this.container.innerHTML = `
|
||||
<div class="story-error">
|
||||
@ -77,8 +154,22 @@ class StoryReader {
|
||||
}
|
||||
|
||||
createInterface() {
|
||||
// Create story selector dropdown if multiple stories available
|
||||
const storySelector = this.availableStories.length > 1 ? `
|
||||
<div class="story-selector">
|
||||
<label for="story-select">📚 Select Story:</label>
|
||||
<select id="story-select">
|
||||
${this.availableStories.map((story, index) =>
|
||||
`<option value="${index}" ${index === this.currentStoryIndex ? 'selected' : ''}>${story.title}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
this.container.innerHTML = `
|
||||
<div class="story-reader-wrapper">
|
||||
${storySelector}
|
||||
|
||||
<!-- Header Controls -->
|
||||
<div class="story-header">
|
||||
<div class="story-title">
|
||||
@ -92,6 +183,7 @@ class StoryReader {
|
||||
</div>
|
||||
|
||||
<div class="story-controls">
|
||||
<button class="control-btn secondary" id="play-sentence-btn">🔊 Play Sentence</button>
|
||||
<button class="control-btn secondary" id="settings-btn">⚙️ Settings</button>
|
||||
<button class="control-btn secondary" id="toggle-translation-btn">🌐 Translations</button>
|
||||
</div>
|
||||
@ -115,6 +207,19 @@ class StoryReader {
|
||||
<option value="paragraph">Full Paragraph</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label>🔊 Auto-play TTS:</label>
|
||||
<input type="checkbox" id="auto-play-tts" ${this.autoPlayTTS ? 'checked' : ''}>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label>TTS Speed:</label>
|
||||
<select id="tts-speed-select">
|
||||
<option value="0.7">Slow (0.7x)</option>
|
||||
<option value="0.8">Normal (0.8x)</option>
|
||||
<option value="1.0" selected>Fast (1.0x)</option>
|
||||
<option value="1.2">Very Fast (1.2x)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chapter Info -->
|
||||
@ -136,6 +241,7 @@ class StoryReader {
|
||||
<div class="word-original" id="popup-word"></div>
|
||||
<div class="word-translation" id="popup-translation"></div>
|
||||
<div class="word-type" id="popup-type"></div>
|
||||
<button class="word-tts-btn" id="popup-tts-btn" onclick="this.parentElement.storyReader.speakWordFromPopup()">🔊</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -180,6 +286,43 @@ class StoryReader {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.story-selector {
|
||||
background: #f8fafc;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.story-selector label {
|
||||
font-weight: 600;
|
||||
color: #2d3748;
|
||||
font-size: 1.1em;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.story-selector select {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 2px solid #cbd5e0;
|
||||
border-radius: 6px;
|
||||
background: white;
|
||||
font-size: 1em;
|
||||
color: #2d3748;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.story-selector select:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.story-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -219,6 +362,33 @@ class StoryReader {
|
||||
.story-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 8px 12px;
|
||||
border: 2px solid #e2e8f0;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: #f7fafc;
|
||||
border-color: #cbd5e0;
|
||||
}
|
||||
|
||||
.control-btn.secondary {
|
||||
background: #f8fafc;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
.control-btn.secondary:hover {
|
||||
background: #e2e8f0;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
.settings-panel {
|
||||
@ -308,6 +478,13 @@ class StoryReader {
|
||||
color: #d69e2e;
|
||||
}
|
||||
|
||||
.punctuation {
|
||||
color: #2d3748;
|
||||
font-weight: normal;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.word-with-pronunciation {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@ -379,6 +556,28 @@ class StoryReader {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.word-tts-btn {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.word-tts-btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.story-navigation {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -463,12 +662,19 @@ class StoryReader {
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Story selector (if multiple stories)
|
||||
const storySelect = document.getElementById('story-select');
|
||||
if (storySelect) {
|
||||
storySelect.addEventListener('change', (e) => this.changeStory(parseInt(e.target.value)));
|
||||
}
|
||||
|
||||
// Navigation
|
||||
document.getElementById('prev-btn').addEventListener('click', () => this.previousSentence());
|
||||
document.getElementById('next-btn').addEventListener('click', () => this.nextSentence());
|
||||
document.getElementById('bookmark-btn').addEventListener('click', () => this.saveBookmark());
|
||||
|
||||
// Controls
|
||||
document.getElementById('play-sentence-btn').addEventListener('click', () => this.playSentenceTTS());
|
||||
document.getElementById('settings-btn').addEventListener('click', () => this.toggleSettings());
|
||||
document.getElementById('toggle-translation-btn').addEventListener('click', () => this.toggleTranslations());
|
||||
document.getElementById('pronunciation-toggle-btn').addEventListener('click', () => this.togglePronunciations());
|
||||
@ -476,6 +682,8 @@ class StoryReader {
|
||||
// Settings
|
||||
document.getElementById('font-size-select').addEventListener('change', (e) => this.changeFontSize(e.target.value));
|
||||
document.getElementById('reading-mode-select').addEventListener('change', (e) => this.changeReadingMode(e.target.value));
|
||||
document.getElementById('auto-play-tts').addEventListener('change', (e) => this.toggleAutoPlayTTS(e.target.checked));
|
||||
document.getElementById('tts-speed-select').addEventListener('change', (e) => this.changeTTSSpeed(e.target.value));
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@ -486,6 +694,7 @@ class StoryReader {
|
||||
this.nextSentence();
|
||||
}
|
||||
if (e.key === 't' || e.key === 'T') this.toggleTranslations();
|
||||
if (e.key === 's' || e.key === 'S') this.playSentenceTTS();
|
||||
});
|
||||
|
||||
// Click outside to close word popup
|
||||
@ -520,8 +729,8 @@ class StoryReader {
|
||||
const matchedWords = [];
|
||||
|
||||
words.forEach(token => {
|
||||
// Skip whitespace and punctuation tokens
|
||||
if (/^\s+$/.test(token) || /^[.,!?;:"'()[\]{}\-–—]+$/.test(token)) {
|
||||
// Handle whitespace tokens
|
||||
if (/^\s+$/.test(token)) {
|
||||
matchedWords.push({
|
||||
original: token,
|
||||
hasVocab: false,
|
||||
@ -530,6 +739,16 @@ class StoryReader {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle pure punctuation tokens (preserve them as non-clickable)
|
||||
if (/^[.,!?;:"'()[\]{}\-–—]+$/.test(token)) {
|
||||
matchedWords.push({
|
||||
original: token,
|
||||
hasVocab: false,
|
||||
isPunctuation: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean word (remove punctuation for matching)
|
||||
const cleanWord = token.toLowerCase().replace(/[.,!?;:"'()[\]{}\-–—]/g, '');
|
||||
|
||||
@ -616,6 +835,9 @@ class StoryReader {
|
||||
wordsHtml = matchedWords.map(wordInfo => {
|
||||
if (wordInfo.isWhitespace) {
|
||||
return wordInfo.original;
|
||||
} else if (wordInfo.isPunctuation) {
|
||||
// Render punctuation as non-clickable text
|
||||
return `<span class="punctuation">${wordInfo.original}</span>`;
|
||||
} else if (wordInfo.hasVocab) {
|
||||
const pronunciation = this.showPronunciation && wordInfo.pronunciation ?
|
||||
wordInfo.pronunciation : '';
|
||||
@ -647,6 +869,12 @@ class StoryReader {
|
||||
|
||||
// Update stats
|
||||
this.updateStats();
|
||||
|
||||
// Auto-play TTS if enabled
|
||||
if (this.autoPlayTTS && this.ttsEnabled) {
|
||||
// Small delay to let the sentence render
|
||||
setTimeout(() => this.playSentenceTTS(), 300);
|
||||
}
|
||||
}
|
||||
|
||||
showWordPopup(event) {
|
||||
@ -663,6 +891,10 @@ class StoryReader {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store reference to story reader for TTS button
|
||||
popup.storyReader = this;
|
||||
popup.currentWord = word;
|
||||
|
||||
document.getElementById('popup-word').textContent = word;
|
||||
document.getElementById('popup-translation').textContent = translation;
|
||||
|
||||
@ -760,6 +992,106 @@ class StoryReader {
|
||||
// Mode implementation can be extended later
|
||||
}
|
||||
|
||||
changeStory(storyIndex) {
|
||||
if (storyIndex !== this.currentStoryIndex) {
|
||||
// Save progress for current story before switching
|
||||
this.saveProgress();
|
||||
|
||||
// Select new story
|
||||
this.selectStory(storyIndex);
|
||||
|
||||
// Load progress for new story
|
||||
this.loadProgress();
|
||||
|
||||
// Update the interface title and progress
|
||||
this.updateStoryTitle();
|
||||
this.renderCurrentSentence();
|
||||
|
||||
logSh(`📖 Switched to story: "${this.story.title}"`, 'INFO');
|
||||
}
|
||||
}
|
||||
|
||||
updateStoryTitle() {
|
||||
const titleElement = document.querySelector('.story-title h2');
|
||||
if (titleElement) {
|
||||
titleElement.textContent = this.story.title;
|
||||
}
|
||||
}
|
||||
|
||||
// TTS Methods
|
||||
playSentenceTTS() {
|
||||
const sentenceData = this.getCurrentSentenceData();
|
||||
if (!sentenceData || !this.ttsEnabled) return;
|
||||
|
||||
const text = sentenceData.data.original;
|
||||
this.speakText(text);
|
||||
}
|
||||
|
||||
speakText(text, options = {}) {
|
||||
if (!text || !this.ttsEnabled) return;
|
||||
|
||||
// Use SettingsManager if available for better language support
|
||||
if (window.SettingsManager && window.SettingsManager.speak) {
|
||||
const ttsOptions = {
|
||||
lang: this.getContentLanguage(),
|
||||
rate: parseFloat(document.getElementById('tts-speed-select')?.value || '0.8'),
|
||||
...options
|
||||
};
|
||||
|
||||
window.SettingsManager.speak(text, ttsOptions)
|
||||
.catch(error => {
|
||||
console.warn('🔊 SettingsManager TTS failed:', error);
|
||||
this.fallbackTTS(text, ttsOptions);
|
||||
});
|
||||
} else {
|
||||
this.fallbackTTS(text, options);
|
||||
}
|
||||
}
|
||||
|
||||
fallbackTTS(text, options = {}) {
|
||||
if ('speechSynthesis' in window && text) {
|
||||
// Cancel any ongoing speech
|
||||
speechSynthesis.cancel();
|
||||
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = this.getContentLanguage();
|
||||
utterance.rate = options.rate || 0.8;
|
||||
utterance.volume = 1.0;
|
||||
|
||||
speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
getContentLanguage() {
|
||||
// Get language from content or use sensible defaults
|
||||
if (this.content.language) {
|
||||
const langMap = {
|
||||
'chinese': 'zh-CN',
|
||||
'english': 'en-US',
|
||||
'french': 'fr-FR',
|
||||
'spanish': 'es-ES'
|
||||
};
|
||||
return langMap[this.content.language] || this.content.language;
|
||||
}
|
||||
return 'en-US'; // Default fallback
|
||||
}
|
||||
|
||||
toggleAutoPlayTTS(enabled) {
|
||||
this.autoPlayTTS = enabled;
|
||||
logSh(`🔊 Auto-play TTS ${enabled ? 'enabled' : 'disabled'}`, 'INFO');
|
||||
}
|
||||
|
||||
changeTTSSpeed(speed) {
|
||||
logSh(`🔊 TTS speed changed to ${speed}x`, 'INFO');
|
||||
}
|
||||
|
||||
speakWordFromPopup() {
|
||||
const popup = document.getElementById('word-popup');
|
||||
if (popup && popup.currentWord) {
|
||||
this.speakText(popup.currentWord, { rate: 0.7 }); // Slower for individual words
|
||||
}
|
||||
}
|
||||
|
||||
updateStats() {
|
||||
const sentenceData = this.getCurrentSentenceData();
|
||||
if (sentenceData) {
|
||||
@ -774,18 +1106,35 @@ class StoryReader {
|
||||
wordsRead: this.wordsRead,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
localStorage.setItem(`story_progress_${this.content.name}`, JSON.stringify(progressData));
|
||||
const progressKey = this.getProgressKey();
|
||||
localStorage.setItem(progressKey, JSON.stringify(progressData));
|
||||
}
|
||||
|
||||
loadProgress() {
|
||||
const saved = localStorage.getItem(`story_progress_${this.content.name}`);
|
||||
const progressKey = this.getProgressKey();
|
||||
const saved = localStorage.getItem(progressKey);
|
||||
if (saved) {
|
||||
const data = JSON.parse(saved);
|
||||
this.currentSentence = data.currentSentence || 0;
|
||||
this.wordsRead = data.wordsRead || 0;
|
||||
try {
|
||||
const data = JSON.parse(saved);
|
||||
this.currentSentence = data.currentSentence || 0;
|
||||
this.wordsRead = data.wordsRead || 0;
|
||||
} catch (error) {
|
||||
logSh('Error loading progress:', error, 'WARN');
|
||||
this.currentSentence = 0;
|
||||
this.wordsRead = 0;
|
||||
}
|
||||
} else {
|
||||
// No saved progress - start fresh
|
||||
this.currentSentence = 0;
|
||||
this.wordsRead = 0;
|
||||
}
|
||||
}
|
||||
|
||||
getProgressKey() {
|
||||
const storyId = this.availableStories[this.currentStoryIndex]?.id || 'main';
|
||||
return `story_progress_${this.content.name}_${storyId}`;
|
||||
}
|
||||
|
||||
saveBookmark() {
|
||||
this.saveProgress();
|
||||
const toast = document.createElement('div');
|
||||
|
||||
@ -605,15 +605,6 @@ class WordDiscovery {
|
||||
}
|
||||
}
|
||||
|
||||
startPracticePhase() {
|
||||
this.currentPhase = 'practice';
|
||||
this.practiceLevel = 1;
|
||||
this.practiceRound = 0;
|
||||
this.practiceCorrectAnswers = 0;
|
||||
this.practiceErrors = 0;
|
||||
this.updatePhaseIndicator();
|
||||
this.showPracticeChallenge();
|
||||
}
|
||||
|
||||
updatePhaseIndicator() {
|
||||
const phaseIndicator = this.container.querySelector('.phase-indicator');
|
||||
@ -863,6 +854,11 @@ class WordDiscovery {
|
||||
this.practiceRound = 0;
|
||||
this.practiceCorrectAnswers = 0;
|
||||
this.practiceErrors = 0;
|
||||
|
||||
// SHUFFLE words again for new difficulty level
|
||||
this.currentPracticeWords = this.shuffleArray([...this.discoveredWords]);
|
||||
console.log(`🔀 Shuffled words for Level ${this.practiceLevel} - new variation order`);
|
||||
|
||||
this.updatePhaseIndicator();
|
||||
|
||||
setTimeout(() => {
|
||||
@ -950,8 +946,9 @@ class WordDiscovery {
|
||||
this.practiceCorrectAnswers = 0;
|
||||
this.practiceErrors = 0;
|
||||
|
||||
// Create mixed practice selection from all discovered words
|
||||
// SHUFFLE discovered words for varied practice order
|
||||
this.currentPracticeWords = this.shuffleArray([...this.discoveredWords]);
|
||||
console.log(`🔀 Shuffled ${this.currentPracticeWords.length} words for practice variation`);
|
||||
|
||||
this.updatePhaseIndicator();
|
||||
this.showMixedPracticeChallenge();
|
||||
|
||||
96
test-chinese-beginner-grammar.html
Normal file
96
test-chinese-beginner-grammar.html
Normal file
@ -0,0 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test Chinese Beginner + Grammar Discovery</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }
|
||||
#container { width: 100%; height: 85vh; border: 2px solid #ddd; border-radius: 10px; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; background: #667eea; color: white; cursor: pointer; }
|
||||
button:hover { background: #5a67d8; }
|
||||
.info { background: #f0fff4; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🌸 Test: Chinese Beginner Story + Grammar Discovery</h1>
|
||||
|
||||
<div class="info">
|
||||
<h3>🎯 Test du nouveau module "Le Jardin Magique"</h3>
|
||||
<p><strong>Objectif :</strong> Vérifier que le contenu chinois → français fonctionne avec Grammar Discovery</p>
|
||||
<p><strong>Attendu :</strong> Sélecteur de concept avec "Structure de Phrase de Base"</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startGame()">🚀 Start Grammar Discovery</button>
|
||||
<button onclick="restartGame()">🔄 Restart</button>
|
||||
</div>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
// Mock dependencies
|
||||
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
||||
window.Utils = { storage: { get: () => [], set: () => {} } };
|
||||
window.GameModules = {};
|
||||
window.ContentModules = {};
|
||||
|
||||
// Mock SettingsManager for TTS
|
||||
window.SettingsManager = {
|
||||
speak: (text, options = {}) => {
|
||||
console.log(`🔊 TTS: "${text}" (${options.lang || 'auto'} at ${options.rate || 1.0}x)`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="js/content/chinese-beginner-story.js"></script>
|
||||
<script src="js/games/grammar-discovery.js"></script>
|
||||
|
||||
<script>
|
||||
let currentGame = null;
|
||||
|
||||
function startGame() {
|
||||
try {
|
||||
const container = document.getElementById('container');
|
||||
const content = window.ContentModules.ChineseBeginnerStory;
|
||||
|
||||
if (!content) {
|
||||
console.error('❌ Chinese Beginner Story content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Content loaded:', content.name);
|
||||
console.log('📚 Grammar topics:', Object.keys(content.grammar || {}));
|
||||
console.log('🔤 Vocabulary count:', Object.keys(content.vocabulary || {}).length);
|
||||
console.log('📖 Story chapters:', content.story?.chapters?.length || 0);
|
||||
|
||||
currentGame = new window.GameModules.GrammarDiscovery({
|
||||
container: container,
|
||||
content: content,
|
||||
onScoreUpdate: score => console.log('📊 Score updated:', score),
|
||||
onGameEnd: score => console.log('🏁 Game ended with score:', score)
|
||||
});
|
||||
|
||||
console.log('🎮 Grammar Discovery started with Chinese Beginner Story!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting game:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
if (currentGame && currentGame.restart) {
|
||||
currentGame.restart();
|
||||
console.log('🔄 Game restarted');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start when page loads
|
||||
window.addEventListener('load', () => {
|
||||
console.log('🔧 Testing Chinese Beginner Story with Grammar Discovery...');
|
||||
console.log('📚 Expected: Concept selector with "Structure de Phrase de Base"');
|
||||
setTimeout(startGame, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
92
test-grammar-discovery.html
Normal file
92
test-grammar-discovery.html
Normal file
@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Grammar Discovery Test</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }
|
||||
#container { width: 100%; height: 80vh; border: 2px solid #ddd; border-radius: 10px; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; background: #667eea; color: white; cursor: pointer; }
|
||||
button:hover { background: #5a67d8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🎓 Grammar Discovery Game Test</h1>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startGame()">🚀 Start Game</button>
|
||||
<button onclick="restartGame()">🔄 Restart</button>
|
||||
</div>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
// Mock dependencies
|
||||
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
||||
window.Utils = { storage: { get: () => [], set: () => {} } };
|
||||
window.GameModules = {};
|
||||
window.ContentModules = {};
|
||||
|
||||
// Mock SettingsManager for TTS
|
||||
window.SettingsManager = {
|
||||
speak: (text, options = {}) => {
|
||||
console.log(`🔊 TTS: "${text}" (${options.lang || 'auto'} at ${options.rate || 1.0}x)`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="js/content/grammar-lesson-le.js"></script>
|
||||
<script src="js/games/grammar-discovery.js"></script>
|
||||
|
||||
<script>
|
||||
let currentGame = null;
|
||||
|
||||
function startGame() {
|
||||
try {
|
||||
const container = document.getElementById('container');
|
||||
const content = window.ContentModules.GrammarLessonLe;
|
||||
|
||||
if (!content) {
|
||||
console.error('❌ Grammar lesson content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Content loaded:', content.name);
|
||||
console.log('📚 Grammar topics:', Object.keys(content.grammar || {}));
|
||||
|
||||
currentGame = new window.GameModules.GrammarDiscovery({
|
||||
container: container,
|
||||
content: content,
|
||||
onScoreUpdate: score => console.log('📊 Score updated:', score),
|
||||
onGameEnd: score => console.log('🏁 Game ended with score:', score)
|
||||
});
|
||||
|
||||
console.log('🎮 Grammar Discovery game started!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting game:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
if (currentGame && currentGame.restart) {
|
||||
currentGame.restart();
|
||||
console.log('🔄 Game restarted');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start when page loads
|
||||
window.addEventListener('load', () => {
|
||||
console.log('🔧 Testing NEW CONCEPT SELECTOR Grammar Discovery...');
|
||||
console.log('🎯 Features:');
|
||||
console.log('- Dropdown to select specific grammar concept');
|
||||
console.log('- Preview with statistics for selected concept');
|
||||
console.log('- Laser focus on chosen concept only');
|
||||
console.log('- 8-step rotation system for focused learning');
|
||||
console.log('✅ Concept selector system ready!');
|
||||
setTimeout(startGame, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
106
test-letter-discovery.html
Normal file
106
test-letter-discovery.html
Normal file
@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test Letter Discovery Game</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }
|
||||
#container { width: 100%; height: 85vh; border: 2px solid #ddd; border-radius: 10px; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; background: #667eea; color: white; cursor: pointer; }
|
||||
button:hover { background: #5a67d8; }
|
||||
.info { background: #f0fff4; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔤 Test: Letter Discovery Game</h1>
|
||||
|
||||
<div class="info">
|
||||
<h3>🎯 Testing Letter Discovery with French Beginner Story</h3>
|
||||
<p><strong>Flow:</strong> Letter discovery → Word exploration → Practice</p>
|
||||
<p><strong>Expected:</strong> Letters A, B, C, F, G, J, M, P, R, T, V with their words</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startGame()">🚀 Start Letter Discovery</button>
|
||||
<button onclick="restartGame()">🔄 Restart</button>
|
||||
</div>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
// Mock dependencies
|
||||
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
||||
window.Utils = { storage: { get: () => [], set: () => {} } };
|
||||
window.GameModules = {};
|
||||
window.ContentModules = {};
|
||||
|
||||
// Mock SettingsManager for TTS
|
||||
window.SettingsManager = {
|
||||
speak: (text, options = {}) => {
|
||||
console.log(`🔊 TTS: "${text}" (${options.lang || 'auto'} at ${options.rate || 1.0}x)`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="js/content/french-beginner-story.js"></script>
|
||||
<script src="js/games/letter-discovery.js"></script>
|
||||
|
||||
<script>
|
||||
let currentGame = null;
|
||||
|
||||
function startGame() {
|
||||
try {
|
||||
const container = document.getElementById('container');
|
||||
const content = window.ContentModules.FrenchBeginnerStory;
|
||||
|
||||
if (!content) {
|
||||
console.error('❌ French Beginner Story content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Content loaded:', content.name);
|
||||
console.log('🔤 Letters available:', Object.keys(content.letters || {}));
|
||||
console.log('📚 Total letters:', Object.keys(content.letters || {}).length);
|
||||
|
||||
// Count words per letter
|
||||
if (content.letters) {
|
||||
Object.keys(content.letters).forEach(letter => {
|
||||
console.log(`📝 Letter ${letter}: ${content.letters[letter].length} words`);
|
||||
});
|
||||
}
|
||||
|
||||
currentGame = new window.GameModules.LetterDiscovery({
|
||||
container: container,
|
||||
content: content,
|
||||
onScoreUpdate: score => console.log('📊 Score updated:', score),
|
||||
onGameEnd: score => console.log('🏁 Game ended with score:', score)
|
||||
});
|
||||
|
||||
if (currentGame.start) {
|
||||
currentGame.start();
|
||||
}
|
||||
|
||||
console.log('🎮 Letter Discovery started successfully!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting game:', error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
if (currentGame && currentGame.restart) {
|
||||
currentGame.restart();
|
||||
console.log('🔄 Game restarted');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start when page loads
|
||||
window.addEventListener('load', () => {
|
||||
console.log('🔧 Testing Letter Discovery...');
|
||||
setTimeout(startGame, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
108
test-river-run.html
Normal file
108
test-river-run.html
Normal file
@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test River Run Game</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }
|
||||
#container { width: 100%; height: 85vh; border: 2px solid #ddd; border-radius: 10px; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; background: #4682B4; color: white; cursor: pointer; }
|
||||
button:hover { background: #5F9EA0; }
|
||||
.info { background: #E6F7FF; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #4682B4; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🌊 Test: River Run Game</h1>
|
||||
|
||||
<div class="info">
|
||||
<h3>🎯 Testing River Run with French Beginner Story</h3>
|
||||
<p><strong>Gameplay:</strong></p>
|
||||
<ul>
|
||||
<li>🛶 Click anywhere to move your boat</li>
|
||||
<li>🎯 Target word appears at top - find and catch it!</li>
|
||||
<li>❌ Avoid all other floating words (obstacles)</li>
|
||||
<li>⚡ Collect power-ups for bonuses</li>
|
||||
<li>📈 Speed increases as you progress</li>
|
||||
</ul>
|
||||
<p><strong>Expected:</strong> Endless river runner with French vocabulary</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startGame()">🚀 Start River Run</button>
|
||||
<button onclick="restartGame()">🔄 Restart</button>
|
||||
</div>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
// Mock dependencies
|
||||
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
||||
window.Utils = { storage: { get: () => [], set: () => {} } };
|
||||
window.GameModules = {};
|
||||
window.ContentModules = {};
|
||||
|
||||
// Mock SettingsManager for TTS
|
||||
window.SettingsManager = {
|
||||
speak: (text, options = {}) => {
|
||||
console.log(`🔊 TTS: "${text}" (${options.lang || 'auto'} at ${options.rate || 1.0}x)`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="js/content/french-beginner-story.js"></script>
|
||||
<script src="js/games/river-run.js"></script>
|
||||
|
||||
<script>
|
||||
let currentGame = null;
|
||||
|
||||
function startGame() {
|
||||
try {
|
||||
const container = document.getElementById('container');
|
||||
const content = window.ContentModules.FrenchBeginnerStory;
|
||||
|
||||
if (!content) {
|
||||
console.error('❌ French Beginner Story content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Content loaded:', content.name);
|
||||
console.log('🔤 Vocabulary available:', Object.keys(content.vocabulary || {}).length);
|
||||
|
||||
// Log some vocabulary examples
|
||||
if (content.vocabulary) {
|
||||
const words = Object.keys(content.vocabulary).slice(0, 5);
|
||||
console.log('📝 Sample words:', words.join(', '));
|
||||
}
|
||||
|
||||
currentGame = new window.GameModules.RiverRun({
|
||||
container: container,
|
||||
content: content,
|
||||
onScoreUpdate: score => console.log('📊 Score updated:', score),
|
||||
onGameEnd: score => console.log('🏁 Game ended with score:', score)
|
||||
});
|
||||
|
||||
console.log('🌊 River Run started! Click to begin sailing!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting game:', error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
if (currentGame && currentGame.restart) {
|
||||
currentGame.restart();
|
||||
console.log('🔄 Game restarted');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-load when page loads
|
||||
window.addEventListener('load', () => {
|
||||
console.log('🔧 Testing River Run...');
|
||||
console.log('💡 Instructions: Click anywhere on the river to move your boat!');
|
||||
setTimeout(startGame, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
98
test-word-discovery-quick.html
Normal file
98
test-word-discovery-quick.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Quick Word Discovery Test</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }
|
||||
#container { width: 100%; height: 85vh; border: 2px solid #ddd; border-radius: 10px; }
|
||||
.controls { margin-bottom: 20px; }
|
||||
button { padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; background: #667eea; color: white; cursor: pointer; }
|
||||
button:hover { background: #5a67d8; }
|
||||
.info { background: #f0fff4; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔍 Test: Word Discovery with Shuffle</h1>
|
||||
|
||||
<div class="info">
|
||||
<h3>🎯 Testing Word Discovery Game</h3>
|
||||
<p><strong>Objective:</strong> Verify that Word Discovery loads and shuffles work</p>
|
||||
<p><strong>Expected:</strong> Game starts with discovery phase, then shuffled practice</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startGame()">🚀 Start Word Discovery</button>
|
||||
<button onclick="restartGame()">🔄 Restart</button>
|
||||
</div>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script>
|
||||
// Mock dependencies
|
||||
window.logSh = (msg, level) => console.log(`[${level}] ${msg}`);
|
||||
window.Utils = { storage: { get: () => [], set: () => {} } };
|
||||
window.GameModules = {};
|
||||
window.ContentModules = {};
|
||||
|
||||
// Mock SettingsManager for TTS
|
||||
window.SettingsManager = {
|
||||
speak: (text, options = {}) => {
|
||||
console.log(`🔊 TTS: "${text}" (${options.lang || 'auto'} at ${options.rate || 1.0}x)`);
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="js/content/example-with-images.js"></script>
|
||||
<script src="js/games/word-discovery.js"></script>
|
||||
|
||||
<script>
|
||||
let currentGame = null;
|
||||
|
||||
function startGame() {
|
||||
try {
|
||||
const container = document.getElementById('container');
|
||||
const content = window.ContentModules.ExampleWithImages;
|
||||
|
||||
if (!content) {
|
||||
console.error('❌ Example With Images content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Content loaded:', content.name);
|
||||
console.log('🔤 Vocabulary count:', Object.keys(content.vocabulary || {}).length);
|
||||
|
||||
currentGame = new window.GameModules.WordDiscovery({
|
||||
container: container,
|
||||
content: content,
|
||||
onScoreUpdate: score => console.log('📊 Score updated:', score),
|
||||
onGameEnd: score => console.log('🏁 Game ended with score:', score)
|
||||
});
|
||||
|
||||
if (currentGame.start) {
|
||||
currentGame.start();
|
||||
}
|
||||
|
||||
console.log('🎮 Word Discovery started with shuffle functionality!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting game:', error);
|
||||
console.error('Stack trace:', error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function restartGame() {
|
||||
if (currentGame && currentGame.restart) {
|
||||
currentGame.restart();
|
||||
console.log('🔄 Game restarted');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start when page loads
|
||||
window.addEventListener('load', () => {
|
||||
console.log('🔧 Testing Word Discovery with shuffle...');
|
||||
setTimeout(startGame, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
159
test-wta1b1.html
Normal file
159
test-wta1b1.html
Normal file
@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test WTA1B1 Content Module</title>
|
||||
<style>
|
||||
body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }
|
||||
.info { background: #E6F7FF; padding: 15px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #1890FF; }
|
||||
.test-results { background: #F6FFED; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #52C41A; }
|
||||
.error { background: #FFF2F0; padding: 15px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #FF4D4F; }
|
||||
.vocab-sample, .grammar-sample { background: #FAFAFA; padding: 10px; border-radius: 5px; margin: 10px 0; font-family: monospace; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧪 Test: WTA1B1 Content Module</h1>
|
||||
|
||||
<div class="info">
|
||||
<h3>🎯 Testing WTA1B1 Integration</h3>
|
||||
<p><strong>Expected:</strong> English letters U, V, T & pets vocabulary with Chinese translation</p>
|
||||
<p><strong>Features:</strong> Grammar lessons, vocabulary, story content, corrections</p>
|
||||
</div>
|
||||
|
||||
<div id="test-results"></div>
|
||||
|
||||
<script>
|
||||
// Load WTA1B1 content
|
||||
const script = document.createElement('script');
|
||||
script.src = 'js/content/WTA1B1.js';
|
||||
script.onload = function() {
|
||||
runTests();
|
||||
};
|
||||
script.onerror = function() {
|
||||
showError('Failed to load WTA1B1.js');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
|
||||
function runTests() {
|
||||
const resultsDiv = document.getElementById('test-results');
|
||||
|
||||
try {
|
||||
// Check if module is loaded
|
||||
const content = window.ContentModules?.WTA1B1;
|
||||
if (!content) {
|
||||
throw new Error('WTA1B1 module not found in window.ContentModules');
|
||||
}
|
||||
|
||||
let results = '<div class="test-results">';
|
||||
results += '<h3>✅ WTA1B1 Module Loaded Successfully</h3>';
|
||||
|
||||
// Basic info
|
||||
results += `<p><strong>ID:</strong> ${content.id || 'Not set'}</p>`;
|
||||
results += `<p><strong>Name:</strong> ${content.name}</p>`;
|
||||
results += `<p><strong>Description:</strong> ${content.description}</p>`;
|
||||
results += `<p><strong>Language:</strong> ${content.language} → ${content.userLanguage}</p>`;
|
||||
results += `<p><strong>Difficulty:</strong> ${content.difficulty}</p>`;
|
||||
|
||||
// Test vocabulary
|
||||
if (content.vocabulary) {
|
||||
const vocabKeys = Object.keys(content.vocabulary);
|
||||
results += `<p><strong>Vocabulary:</strong> ${vocabKeys.length} words</p>`;
|
||||
|
||||
// Sample vocabulary
|
||||
const sampleWords = vocabKeys.slice(0, 5);
|
||||
results += '<div class="vocab-sample">';
|
||||
results += '<strong>Sample vocabulary:</strong><br>';
|
||||
sampleWords.forEach(word => {
|
||||
const data = content.vocabulary[word];
|
||||
results += `${word} → ${data.user_language || data.translation} (${data.pronunciation || 'no pronunciation'})<br>`;
|
||||
});
|
||||
results += '</div>';
|
||||
} else {
|
||||
results += '<p><strong>Vocabulary:</strong> ❌ Not found</p>';
|
||||
}
|
||||
|
||||
// Test grammar
|
||||
if (content.grammar) {
|
||||
const grammarTopics = Object.keys(content.grammar);
|
||||
results += `<p><strong>Grammar Topics:</strong> ${grammarTopics.length}</p>`;
|
||||
|
||||
results += '<div class="grammar-sample">';
|
||||
results += '<strong>Grammar topics:</strong><br>';
|
||||
grammarTopics.forEach(topic => {
|
||||
const grammarData = content.grammar[topic];
|
||||
results += `• ${grammarData.title}<br>`;
|
||||
});
|
||||
results += '</div>';
|
||||
} else {
|
||||
results += '<p><strong>Grammar:</strong> ❌ Not found</p>';
|
||||
}
|
||||
|
||||
// Test story
|
||||
if (content.story && content.story.chapters) {
|
||||
const totalChapters = content.story.chapters.length;
|
||||
const totalSentences = content.story.chapters.reduce((sum, chapter) =>
|
||||
sum + (chapter.sentences ? chapter.sentences.length : 0), 0);
|
||||
results += `<p><strong>Story:</strong> ${totalChapters} chapters, ${totalSentences} sentences</p>`;
|
||||
} else {
|
||||
results += '<p><strong>Story:</strong> ❌ Not found</p>';
|
||||
}
|
||||
|
||||
// Test exercises
|
||||
if (content.fillInBlanks) {
|
||||
results += `<p><strong>Fill-in-blanks:</strong> ${content.fillInBlanks.length} exercises</p>`;
|
||||
}
|
||||
|
||||
if (content.corrections) {
|
||||
results += `<p><strong>Corrections:</strong> ${content.corrections.length} exercises</p>`;
|
||||
}
|
||||
|
||||
// Content compatibility test
|
||||
results += '<h4>🎮 Game Compatibility Test</h4>';
|
||||
|
||||
// Test for River Run
|
||||
if (content.vocabulary) {
|
||||
results += '✅ River Run: Compatible (has vocabulary)<br>';
|
||||
} else {
|
||||
results += '❌ River Run: Not compatible (no vocabulary)<br>';
|
||||
}
|
||||
|
||||
// Test for Word Discovery
|
||||
if (content.vocabulary) {
|
||||
results += '✅ Word Discovery: Compatible (has vocabulary)<br>';
|
||||
} else {
|
||||
results += '❌ Word Discovery: Not compatible (no vocabulary)<br>';
|
||||
}
|
||||
|
||||
// Test for Grammar Discovery
|
||||
if (content.grammar) {
|
||||
results += '✅ Grammar Discovery: Compatible (has grammar)<br>';
|
||||
} else {
|
||||
results += '❌ Grammar Discovery: Not compatible (no grammar)<br>';
|
||||
}
|
||||
|
||||
// Test for Letter Discovery
|
||||
if (content.letters) {
|
||||
results += '✅ Letter Discovery: Compatible (has letters structure)<br>';
|
||||
} else {
|
||||
results += '⚠️ Letter Discovery: Partial compatibility (will auto-generate from vocabulary)<br>';
|
||||
}
|
||||
|
||||
results += '</div>';
|
||||
resultsDiv.innerHTML = results;
|
||||
|
||||
console.log('🎯 WTA1B1 Content Analysis:');
|
||||
console.log('Full content object:', content);
|
||||
|
||||
} catch (error) {
|
||||
showError('Test failed: ' + error.message);
|
||||
console.error('WTA1B1 test error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const resultsDiv = document.getElementById('test-results');
|
||||
resultsDiv.innerHTML = `<div class="error"><h3>❌ Error</h3><p>${message}</p></div>`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user