From ab84bbbc719df60418c8378bd895fa1fb69ac81d Mon Sep 17 00:00:00 2001 From: StillHammer Date: Sat, 18 Oct 2025 16:22:33 +0800 Subject: [PATCH] Reduce game sizes and fix Mario level display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reduce RiverRun game height from 100vh to 75vh for better screen fit - Reduce AdventureReader game height from 100vh to 75vh - Fix Mario level number display (was showing currentLevel + 1 twice) - Updated HUD level display in Renderer.js - Updated finish line flag level display in Renderer.js - Add portable setup files and documentation - Add new game modules: SentenceInvaders, ThematicQuestions - Add new content: wte2 book, sbs chapters 2-3, wte2-2 chapter - Update various game modules for improved compatibility ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 3 + 00_COMMENCE_ICI.txt | 122 ++ CHECKLIST_USB.txt | 163 ++ DOWNLOAD_NODEJS.bat | 58 + LISEZMOI.txt | 53 + PORTABLE_SETUP.txt | 105 ++ QUICK_START.txt | 53 + README_PORTABLE.md | 247 +++ START_PORTABLE.bat | 85 + content/books/sbs.json | 42 +- content/books/wte2.json | 55 + content/chapters/SBS2.txt | 442 +++++ content/chapters/SBS3.txt | 363 +++++ content/chapters/WTE2-2.txt | 395 +++++ content/chapters/sbs-2.json | 692 ++++++++ content/chapters/sbs-3.json | 757 +++++++++ content/chapters/sbs-7-8.json | 28 + content/chapters/wte2-2.json | 646 ++++++++ src/core/GameLoader.js | 5 +- .../MarioEducational/PhysicsEngine.js | 31 +- src/gameHelpers/MarioEducational/Renderer.js | 84 +- .../MarioEducational/enemies/Catapult.js | 202 ++- .../MarioEducational/enemies/PiranhaPlant.js | 14 +- src/games/AdventureReader.js | 2 +- src/games/ChineseStudy.js | 5 + src/games/GrammarDiscovery.js | 26 +- src/games/LetterDiscovery.js | 16 +- src/games/MarioEducational.js | 42 +- src/games/RiverRun.js | 2 +- src/games/SentenceInvaders.js | 1446 +++++++++++++++++ src/games/ThematicQuestions.js | 1196 ++++++++++++++ src/games/WhackAMole.js | 156 +- 32 files changed, 7291 insertions(+), 245 deletions(-) create mode 100644 00_COMMENCE_ICI.txt create mode 100644 CHECKLIST_USB.txt create mode 100644 DOWNLOAD_NODEJS.bat create mode 100644 LISEZMOI.txt create mode 100644 PORTABLE_SETUP.txt create mode 100644 QUICK_START.txt create mode 100644 README_PORTABLE.md create mode 100644 START_PORTABLE.bat create mode 100644 content/books/wte2.json create mode 100644 content/chapters/SBS2.txt create mode 100644 content/chapters/SBS3.txt create mode 100644 content/chapters/WTE2-2.txt create mode 100644 content/chapters/sbs-2.json create mode 100644 content/chapters/sbs-3.json create mode 100644 content/chapters/wte2-2.json create mode 100644 src/games/SentenceInvaders.js create mode 100644 src/games/ThematicQuestions.js diff --git a/.gitignore b/.gitignore index dbc5150..61d64e6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +# Portable Node.js (for USB deployment) +nodejs-portable/ + # Build outputs dist/ build/ diff --git a/00_COMMENCE_ICI.txt b/00_COMMENCE_ICI.txt new file mode 100644 index 0000000..80d692f --- /dev/null +++ b/00_COMMENCE_ICI.txt @@ -0,0 +1,122 @@ + + โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— + โ•‘ โ•‘ + โ•‘ CLASS GENERATOR 2.0 - VERSION PORTABLE โ•‘ + โ•‘ โ•‘ + โ•‘ ๐Ÿ‘‰ COMMENCE PAR LIRE CE FICHIER ๐Ÿ‘ˆ โ•‘ + โ•‘ โ•‘ + โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐ŸŽฏ TU VEUX L'UTILISER EN COURS DEMAIN? โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + ๐Ÿ“‹ ร‰TAPES ร€ SUIVRE MAINTENANT (5 minutes): + + 1๏ธโƒฃ Double-clic sur: DOWNLOAD_NODEJS.bat + โ†’ Tรฉlรฉcharge Node.js portable + + 2๏ธโƒฃ Double-clic sur: CHECKLIST_USB.txt + โ†’ Checklist complรจte pour prรฉparer ta clรฉ USB + + 3๏ธโƒฃ Tester avec: START_PORTABLE.bat + โ†’ Vรฉrifie que รงa marche avant demain! + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ“š DOCUMENTATION DISPONIBLE โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + Pour dรฉbutants (FRANร‡AIS): + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + ๐Ÿ“„ LISEZMOI.txt โ†’ Guide rapide en franรงais + ๐Ÿ“‹ CHECKLIST_USB.txt โ†’ ร‰tapes de prรฉparation + โšก QUICK_START.txt โ†’ Dรฉmarrage rapide + + Pour avancรฉs (Dร‰TAILLร‰): + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + ๐Ÿ“– PORTABLE_SETUP.txt โ†’ Setup dรฉtaillรฉ + ๐Ÿ“š README_PORTABLE.md โ†’ Documentation complรจte (Markdown) + ๐Ÿ“ README.md โ†’ Documentation gรฉnรฉrale du projet + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿš€ FICHIERS DE LANCEMENT โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + Version Portable (pour clรฉ USB): + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + ๐ŸŽฏ START_PORTABLE.bat โ†’ UTILISE CELUI-CI en cours! + ๐Ÿ“ฅ DOWNLOAD_NODEJS.bat โ†’ Tรฉlรฉcharge Node.js portable + + Version Standard (si Node.js installรฉ): + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + โ–ถ๏ธ start.bat โ†’ Version classique (ton PC) + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ’ก QUELLE VERSION UTILISER? โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + ๐Ÿ“ Sur TON PC (Node.js installรฉ): + โ†’ Utilise: start.bat + โ†’ Ou commande: npm start + + ๐Ÿ“ En COURS / CLร‰ USB (pas de Node.js): + โ†’ Utilise: START_PORTABLE.bat + โ†’ Prรฉpare d'abord avec: DOWNLOAD_NODEJS.bat + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โš ๏ธ RAPPELS IMPORTANTS โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + โœ… Fonctionne SANS internet en cours + โœ… Fonctionne SANS installation (droits admin) + โœ… Fonctionne depuis une CLร‰ USB + + โŒ AI ne marche PAS sans internet (normal!) + โŒ Besoin de Windows 10 minimum + โŒ Besoin de ~150 MB sur la clรฉ USB + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐ŸŽ“ DEMAIN EN COURS (Procรฉdure rapide) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + 1. Brancher la clรฉ USB + 2. Aller dans le dossier Class_Generator + 3. Double-clic: START_PORTABLE.bat + 4. Attendre 10 secondes + 5. Ouvrir Chrome: http://localhost:8080 + 6. โœ… ร‡a marche! + + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ†˜ BESOIN D'AIDE? โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + + Lis dans cet ordre: + 1. LISEZMOI.txt (franรงais, simple) + 2. CHECKLIST_USB.txt (prรฉparation USB) + 3. PORTABLE_SETUP.txt (dรฉtails techniques) + + Problรจme? Voir section "Dรฉpannage" dans PORTABLE_SETUP.txt + + + โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— + โ•‘ โ•‘ + โ•‘ ๐Ÿš€ PRรŠT ร€ COMMENCER? โ•‘ + โ•‘ โ•‘ + โ•‘ ๐Ÿ‘‰ Double-clic sur: DOWNLOAD_NODEJS.bat โ•‘ + โ•‘ ๐Ÿ‘‰ Puis lis: CHECKLIST_USB.txt โ•‘ + โ•‘ โ•‘ + โ•‘ Bonne chance pour demain! ๐ŸŽ“ โ•‘ + โ•‘ โ•‘ + โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + + +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Version: 2.0 Portable | Crรฉรฉ le: 2025-10-18 | Pour: Cours +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ diff --git a/CHECKLIST_USB.txt b/CHECKLIST_USB.txt new file mode 100644 index 0000000..8d338af --- /dev/null +++ b/CHECKLIST_USB.txt @@ -0,0 +1,163 @@ +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + CHECKLIST - PRร‰PARATION CLร‰ USB POUR DEMAIN +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +๐ŸŽฏ OBJECTIF: Utiliser Class Generator sur le PC de cours (Windows 10) + +๐Ÿ“‹ ร‰TAPES ร€ FAIRE CE SOIR (sur ton PC) +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +โ˜ 1. Tร‰Lร‰CHARGER NODE.JS PORTABLE + โ€ข Double-clic: DOWNLOAD_NODEJS.bat + โ€ข Tรฉlรฉcharger: "Windows Binary (.zip)" - LTS + โ€ข Extraire le fichier ZIP + โ€ข Renommer le dossier en: nodejs-portable + โ€ข Copier dans: Class_generator/ + + Vรฉrification: + โœ“ Le fichier Class_generator/nodejs-portable/node.exe existe? + +โ˜ 2. TESTER EN LOCAL (important!) + โ€ข Double-clic: START_PORTABLE.bat + โ€ข Vรฉrifier que รงa dรฉmarre sans erreur + โ€ข Ouvrir: http://localhost:8080 + โ€ข Tester la navigation + โ€ข Fermer le serveur (Ctrl+C) + + Vรฉrification: + โœ“ L'application s'ouvre dans le navigateur? + โœ“ Pas d'erreurs dans la console? + +โ˜ 3. PRร‰PARER LA CLร‰ USB + โ€ข Brancher ta clรฉ USB + โ€ข Vรฉrifier l'espace libre: minimum 150 MB + โ€ข Crรฉer un dossier: "Class_Generator" (optionnel) + โ€ข Copier TOUT le dossier sur la clรฉ + + Vรฉrification: + โœ“ Taille copiรฉe: ~95-100 MB? + โœ“ Le fichier START_PORTABLE.bat est sur la clรฉ? + โœ“ Le dossier nodejs-portable/ est sur la clรฉ? + +โ˜ 4. TESTER DEPUIS LA CLร‰ USB (important!) + โ€ข Aller dans le dossier sur la clรฉ USB + โ€ข Double-clic: START_PORTABLE.bat + โ€ข Vรฉrifier que รงa dรฉmarre + โ€ข Ouvrir: http://localhost:8080 + โ€ข Tester la navigation + โ€ข Fermer le serveur + + Vรฉrification: + โœ“ ร‡a marche depuis la clรฉ USB? + โœ“ Pas plus lent que depuis le disque dur? + + +๐Ÿ“ฆ FICHIERS ร€ AVOIR SUR LA CLร‰ USB +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +ESSENTIELS (sans รงa, รงa ne marche pas): +โ˜‘ nodejs-portable/ โ† Node.js portable +โ˜‘ src/ โ† Code source +โ˜‘ content/ โ† Contenu (livres, chapitres) +โ˜‘ node_modules/ โ† Dรฉpendances (dotenv) +โ˜‘ START_PORTABLE.bat โ† Launcher portable +โ˜‘ server.js โ† Serveur HTTP +โ˜‘ package.json โ† Config npm +โ˜‘ index.html โ† Page d'accueil +โ˜‘ .env โ† Clรฉs API (optionnel si pas d'internet) + +UTILES (documentation): +โ˜‘ LISEZMOI.txt โ† Guide rapide franรงais +โ˜‘ QUICK_START.txt โ† Dรฉmarrage rapide +โ˜‘ PORTABLE_SETUP.txt โ† Setup dรฉtaillรฉ +โ˜‘ README_PORTABLE.md โ† Doc complรจte + + +๐Ÿš€ DEMAIN EN COURS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +1. Brancher la clรฉ USB +2. Ouvrir le dossier Class_Generator +3. Double-clic: START_PORTABLE.bat +4. Attendre 10 secondes +5. Ouvrir Chrome/Edge: http://localhost:8080 +6. โœ… Prรฉsenter l'application! + +Pour arrรชter: +โ€ข Appuyer sur Ctrl+C dans la fenรชtre du serveur +โ€ข Ou fermer la fenรชtre + + +โš ๏ธ PROBLรˆMES POSSIBLES ET SOLUTIONS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Problรจme: "Node.js portable not found!" +โ†’ nodejs-portable/ n'est pas copiรฉ +โ†’ Vรฉrifier que nodejs-portable/node.exe existe + +Problรจme: "Port 8080 already in use" +โ†’ Ouvrir Gestionnaire des tรขches +โ†’ Tuer tous les processus "node.exe" +โ†’ Relancer + +Problรจme: Antivirus bloque +โ†’ Autoriser node.exe temporairement +โ†’ Ou demander ร  l'admin du PC + +Problรจme: Pas d'internet +โ†’ Normal! L'AI ne marchera pas +โ†’ Utiliser les jeux et contenus locaux + + +๐Ÿ’ก RAPPELS IMPORTANTS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +โœ“ SANS INTERNET: + โ€ข Interface complรจte โœ… + โ€ข Navigation โœ… + โ€ข Contenu local โœ… + โ€ข Jeux sans AI โœ… + โ€ข Flashcards โœ… + โ€ข AI exercises โŒ + โ€ข LLM scoring โŒ + +โœ“ PAS BESOIN: + โ€ข Droits administrateur + โ€ข Installation de Node.js + โ€ข npm install + โ€ข Internet (en cours) + +โœ“ ATTENTION: + โ€ข Le fichier .env contient tes clรฉs API + โ€ข Ne pas perdre la clรฉ USB! + โ€ข Ne pas partager avec n'importe qui + + +๐Ÿ” Vร‰RIFICATION FINALE +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Coche chaque point: + +โ˜ nodejs-portable/node.exe existe +โ˜ START_PORTABLE.bat testรฉ et fonctionne +โ˜ Application accessible sur http://localhost:8080 +โ˜ Navigation testรฉe (plusieurs pages) +โ˜ Fonctionne depuis la clรฉ USB +โ˜ Clรฉ USB prรชte pour demain +โ˜ Batterie du PC chargรฉe (au cas oรน) + + +โœ… TOUT EST BON? TU ES PRรŠT! +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Demain: +1. Arriver en cours +2. Brancher la clรฉ USB +3. START_PORTABLE.bat +4. http://localhost:8080 +5. ๐Ÿš€ C'est parti! + + +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +Prรฉparรฉ le 2025-10-18 | Bonne chance pour demain! ๐ŸŽ“ +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• diff --git a/DOWNLOAD_NODEJS.bat b/DOWNLOAD_NODEJS.bat new file mode 100644 index 0000000..17909a8 --- /dev/null +++ b/DOWNLOAD_NODEJS.bat @@ -0,0 +1,58 @@ +@echo off +title Download Node.js Portable +cd /d "%~dp0" + +echo. +echo ======================================== +echo Node.js Portable Downloader +echo ======================================== +echo. + +:: Check if already downloaded +if exist "nodejs-portable\node.exe" ( + echo Node.js portable is already installed! + echo Location: %~dp0nodejs-portable\ + echo. + for /f "delims=" %%i in ('"%~dp0nodejs-portable\node.exe" --version') do set NODE_VERSION=%%i + echo Version: %NODE_VERSION% + echo. + echo You can now use START_PORTABLE.bat to launch the app! + pause + exit /b 0 +) + +echo This script will download Node.js LTS portable for Windows +echo. +echo What you need: +echo - Internet connection +echo - ~50 MB of free space +echo - A few minutes +echo. +echo Press any key to continue or Ctrl+C to cancel... +pause >nul + +echo. +echo ======================================== +echo Downloading Node.js... +echo ======================================== +echo. +echo Opening download page in your browser... +echo. +echo INSTRUCTIONS: +echo 1. Download "Windows Binary (.zip)" - LTS version +echo 2. Extract the ZIP file +echo 3. Rename the extracted folder to "nodejs-portable" +echo 4. Move "nodejs-portable" into this folder: +echo %~dp0 +echo. +echo Once done, run START_PORTABLE.bat to launch! +echo. + +:: Open the official Node.js download page +start https://nodejs.org/en/download/prebuilt-binaries + +echo. +echo Download page opened in your browser! +echo Follow the instructions above. +echo. +pause diff --git a/LISEZMOI.txt b/LISEZMOI.txt new file mode 100644 index 0000000..dabad4f --- /dev/null +++ b/LISEZMOI.txt @@ -0,0 +1,53 @@ +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + CLASS GENERATOR 2.0 - VERSION PORTABLE +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +๐Ÿ“š TU VEUX UTILISER ร‡A EN COURS DEMAIN? + +๐Ÿ‘‰ Suis ces รฉtapes MAINTENANT (5 minutes): + +1. Double-clic sur: DOWNLOAD_NODEJS.bat + โ†’ Tรฉlรฉcharge Node.js portable (~50 MB) + โ†’ Extraire le ZIP + โ†’ Renommer en "nodejs-portable" + โ†’ Copier dans CE dossier + +2. Copier TOUT ce dossier sur ta clรฉ USB + +3. DEMAIN EN COURS: + โ†’ Brancher la clรฉ USB + โ†’ Double-clic sur: START_PORTABLE.bat + โ†’ Ouvrir Chrome: http://localhost:8080 + โ†’ โœ… ร‡a marche! + + +๐Ÿ“ FICHIERS IMPORTANTS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +START_PORTABLE.bat โ†’ ๐Ÿš€ UTILISE ร‡A pour lancer l'app +DOWNLOAD_NODEJS.bat โ†’ ๐Ÿ“ฅ Tรฉlรฉcharge Node.js (une fois) +QUICK_START.txt โ†’ โšก Guide rapide +PORTABLE_SETUP.txt โ†’ ๐Ÿ“– Instructions dรฉtaillรฉes +README_PORTABLE.md โ†’ ๐Ÿ“š Documentation complรจte + + +โš ๏ธ RAPPELS IMPORTANTS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +โ€ข SANS INTERNET = Pas d'AI (normal!) +โ€ข Windows 10 seulement +โ€ข Pas besoin de droits admin +โ€ข Taille totale: ~95 MB + + +๐Ÿ’ก BESOIN D'AIDE? +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +1. Lire: QUICK_START.txt +2. Lire: PORTABLE_SETUP.txt +3. Lire: README_PORTABLE.md + + +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +Crรฉรฉ le 2025-10-18 | Prรชt pour utilisation en cours! +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• diff --git a/PORTABLE_SETUP.txt b/PORTABLE_SETUP.txt new file mode 100644 index 0000000..301bca2 --- /dev/null +++ b/PORTABLE_SETUP.txt @@ -0,0 +1,105 @@ +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + CLASS GENERATOR - PORTABLE EDITION SETUP +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +๐Ÿ“‹ PRร‰REQUIS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +โ€ข Clรฉ USB ou disque externe (minimum 150 MB libre) +โ€ข Windows 10 (pas besoin de droits admin!) +โ€ข PAS besoin d'internet pour utiliser (seulement pour setup initial) + +๐Ÿ”ง INSTALLATION (ร€ FAIRE UNE SEULE FOIS SUR TON PC) +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +1. Tร‰Lร‰CHARGER NODE.JS PORTABLE + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + Option A - Automatique (recommandรฉ): + โ€ข Double-cliquer sur: DOWNLOAD_NODEJS.bat + โ€ข Attendre le tรฉlรฉchargement automatique + โ€ข โœ… Terminรฉ! + + Option B - Manuel: + โ€ข Aller sur: https://nodejs.org/en/download/ + โ€ข Tรฉlรฉcharger: "Windows Binary (.zip)" - version LTS + โ€ข Extraire le fichier ZIP + โ€ข Renommer le dossier en "nodejs-portable" + โ€ข Copier le dossier "nodejs-portable" dans ce dossier + +2. Vร‰RIFIER LA STRUCTURE + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Ton dossier doit ressembler ร  รงa: + + Class_generator/ + โ”œโ”€โ”€ nodejs-portable/ โ† Le dossier Node.js + โ”‚ โ”œโ”€โ”€ node.exe โ† Important! + โ”‚ โ”œโ”€โ”€ npm + โ”‚ โ””โ”€โ”€ node_modules/ + โ”œโ”€โ”€ src/ + โ”œโ”€โ”€ content/ + โ”œโ”€โ”€ START_PORTABLE.bat โ† Lance รงa! + โ”œโ”€โ”€ server.js + โ””โ”€โ”€ package.json + +3. PREMIER LANCEMENT + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + โ€ข Double-cliquer sur: START_PORTABLE.bat + โ€ข La premiรจre fois, รงa va installer dotenv (~10 secondes) + โ€ข Ensuite le serveur dรฉmarre! + +๐Ÿš€ UTILISATION EN COURS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +1. Brancher ta clรฉ USB +2. Double-cliquer sur START_PORTABLE.bat +3. Ouvrir le navigateur: http://localhost:8080 +4. Utiliser l'application! + +โš ๏ธ LIMITATIONS SANS INTERNET +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Sans connexion internet, ces features ne marcheront PAS: +โ€ข โŒ AI exercises (OpenAI/DeepSeek API) +โ€ข โŒ LLM scoring +โ€ข โŒ Translation API +โ€ข โŒ Online content loading + +Ce qui MARCHE hors-ligne: +โ€ข โœ… Interface principale +โ€ข โœ… Navigation +โ€ข โœ… Contenu local (fichiers JSON dans content/) +โ€ข โœ… Jeux sans AI +โ€ข โœ… Flashcards locaux +โ€ข โœ… Sauvegarde locale (dossier saves/) + +๐Ÿ“ฆ TAILLE TOTALE +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Projet actuel: 42 MB +Node.js portable: ~50 MB +node_modules: 1 MB +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +TOTAL: ~95 MB + +๐Ÿ’ก Dร‰PANNAGE +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Problรจme: "Node.js portable not found!" +โ†’ Solution: Vรฉrifier que le dossier "nodejs-portable" existe + et contient node.exe + +Problรจme: "Port 8080 already in use" +โ†’ Solution: Ouvrir le Gestionnaire des tรขches + Arrรชter tous les processus "node.exe" + Relancer START_PORTABLE.bat + +Problรจme: Page blanche dans le navigateur +โ†’ Solution: Vรฉrifier que l'URL est bien http://localhost:8080 + (pas https!) + +Problรจme: AI ne fonctionne pas +โ†’ Solution: Normal sans internet! Utiliser les jeux locaux + +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +Crรฉรฉ le 2025-10-18 | Class Generator 2.0 Portable Edition +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• diff --git a/QUICK_START.txt b/QUICK_START.txt new file mode 100644 index 0000000..a659b9f --- /dev/null +++ b/QUICK_START.txt @@ -0,0 +1,53 @@ +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + CLASS GENERATOR - Dร‰MARRAGE RAPIDE +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +๐ŸŽฏ PREMIรˆRE UTILISATION (sur ton PC avec internet) +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +1. Double-cliquer sur: DOWNLOAD_NODEJS.bat +2. Tรฉlรฉcharger Node.js (รงa ouvre ton navigateur) +3. Extraire et renommer en "nodejs-portable" +4. Copier dans ce dossier +5. Double-cliquer sur: START_PORTABLE.bat +6. โœ… ร‡a marche! + +Temps total: ~5 minutes + + +๐Ÿš€ UTILISATION EN COURS (PC sans internet) +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +1. Brancher ta clรฉ USB +2. Double-cliquer sur: START_PORTABLE.bat +3. Ouvrir Chrome/Edge: http://localhost:8080 +4. โœ… L'app est lancรฉe! + +Temps: ~10 secondes + + +๐Ÿ“ FICHIERS IMPORTANTS +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +START_PORTABLE.bat โ†’ Lance l'application (utilise รงa!) +DOWNLOAD_NODEJS.bat โ†’ Tรฉlรฉcharge Node.js (une fois seulement) +PORTABLE_SETUP.txt โ†’ Instructions dรฉtaillรฉes +start.bat โ†’ Ancien launcher (ignore รงa) + + +โš ๏ธ RAPPEL: PAS D'AI SANS INTERNET +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +En cours (sans internet): +โ€ข โŒ Exercises AI (OpenAI API) +โ€ข โœ… Interface et navigation +โ€ข โœ… Contenu local +โ€ข โœ… Jeux basiques + + +๐Ÿ’ก BESOIN D'AIDE? +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + +Lire: PORTABLE_SETUP.txt (plus de dรฉtails) + +โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• diff --git a/README_PORTABLE.md b/README_PORTABLE.md new file mode 100644 index 0000000..576d33e --- /dev/null +++ b/README_PORTABLE.md @@ -0,0 +1,247 @@ +# Class Generator 2.0 - Portable Edition + +**Version portable pour utilisation sur clรฉ USB sans installation** + +## ๐ŸŽฏ Objectif + +Cette version permet d'utiliser Class Generator sur n'importe quel PC Windows **sans installer Node.js** et **sans droits administrateur**. + +## ๐Ÿ“ฆ Configuration Requise + +- **Clรฉ USB**: Minimum 150 MB d'espace libre +- **OS**: Windows 10 ou supรฉrieur +- **Droits**: Aucun droit admin nรฉcessaire +- **Internet**: Seulement pour le setup initial (une fois) + +## ๐Ÿš€ Installation (Une fois sur ton PC) + +### Mรฉthode Automatique (Recommandรฉe) + +1. Double-cliquer sur `DOWNLOAD_NODEJS.bat` +2. Tรฉlรฉcharger Node.js depuis la page qui s'ouvre +3. Extraire le fichier ZIP +4. Renommer le dossier en `nodejs-portable` +5. Copier `nodejs-portable` dans le dossier du projet +6. โœ… Terminรฉ! + +### Mรฉthode Manuelle + +1. Aller sur https://nodejs.org/en/download/ +2. Tรฉlรฉcharger **"Windows Binary (.zip)"** - Version LTS +3. Extraire le ZIP +4. Renommer le dossier extrait en `nodejs-portable` +5. Copier dans le dossier du projet + +### Structure Finale + +``` +Class_generator/ +โ”œโ”€โ”€ nodejs-portable/ โ† Dossier Node.js portable +โ”‚ โ”œโ”€โ”€ node.exe โ† Important! +โ”‚ โ”œโ”€โ”€ npm +โ”‚ โ””โ”€โ”€ node_modules/ +โ”œโ”€โ”€ src/ +โ”œโ”€โ”€ content/ +โ”œโ”€โ”€ START_PORTABLE.bat โ† Lance l'application +โ”œโ”€โ”€ server.js +โ””โ”€โ”€ package.json +``` + +## ๐Ÿ’ป Utilisation en Cours (Sans Internet) + +### Dรฉmarrage + +1. **Brancher la clรฉ USB** +2. **Double-cliquer** sur `START_PORTABLE.bat` +3. **Attendre** que le serveur dรฉmarre (~5 secondes) +4. **Ouvrir le navigateur**: http://localhost:8080 + +### Arrรชt + +- Appuyer sur `Ctrl+C` dans la fenรชtre du serveur +- Ou simplement fermer la fenรชtre + +## โšก Caractรฉristiques + +### โœ… Fonctionne Hors-ligne + +- Interface complรจte +- Navigation et routing +- Contenu local (fichiers JSON) +- Jeux sans AI +- Flashcards locaux +- Sauvegarde locale (dossier `saves/`) +- Debug panel (F12) + +### โŒ Nรฉcessite Internet + +- AI exercises (OpenAI/DeepSeek API) +- LLM scoring et validation +- Translation API +- Tรฉlรฉchargement de contenu en ligne + +## ๐Ÿ“Š Tailles de Fichiers + +| Composant | Taille | +|-----------|--------| +| Projet (code + content) | ~42 MB | +| Node.js portable | ~50 MB | +| node_modules (dotenv) | ~1 MB | +| **TOTAL** | **~95 MB** | + +## ๐Ÿ”ง Dรฉpannage + +### Erreur: "Node.js portable not found!" + +**Cause**: Le dossier `nodejs-portable` n'existe pas ou est mal placรฉ + +**Solution**: +1. Vรฉrifier que `nodejs-portable/node.exe` existe +2. Relancer `DOWNLOAD_NODEJS.bat` si nรฉcessaire + +### Erreur: "Port 8080 already in use" + +**Cause**: Un autre serveur utilise le port 8080 + +**Solution**: +1. Ouvrir le **Gestionnaire des tรขches** (Ctrl+Shift+Esc) +2. Chercher les processus `node.exe` +3. Les terminer tous +4. Relancer `START_PORTABLE.bat` + +### Page blanche dans le navigateur + +**Cause**: Mauvaise URL ou serveur pas dรฉmarrรฉ + +**Solution**: +1. Vรฉrifier l'URL: `http://localhost:8080` (pas https!) +2. Vรฉrifier que la fenรชtre du serveur est ouverte +3. Attendre 10 secondes aprรจs le lancement + +### AI ne fonctionne pas + +**Cause**: Pas de connexion internet + +**Solution**: C'est normal! Les features AI nรฉcessitent internet pour accรฉder aux API (OpenAI, DeepSeek). Utiliser les jeux et contenus locaux. + +## ๐Ÿ” Sรฉcuritรฉ + +### โš ๏ธ Attention aux Clรฉs API + +Le fichier `.env` contient des clรฉs API sensibles. Si tu copies ce dossier: +- **Ne pas** partager la clรฉ USB +- **Ne pas** laisser la clรฉ USB sans surveillance +- **Supprimer** le fichier `.env` avant de donner ร  quelqu'un + +### Version Sans Clรฉs API + +Pour crรฉer une version "propre" sans clรฉs API: +1. Renommer `.env` en `.env.backup` +2. Crรฉer un nouveau `.env` vide ou avec des clรฉs de test +3. Copier sur la clรฉ USB + +## ๐Ÿ“ Fichiers du Systรจme Portable + +| Fichier | Description | +|---------|-------------| +| `START_PORTABLE.bat` | Lance l'application (utilise celui-ci!) | +| `DOWNLOAD_NODEJS.bat` | Tรฉlรฉcharge Node.js portable (une fois) | +| `PORTABLE_SETUP.txt` | Instructions dรฉtaillรฉes (texte brut) | +| `QUICK_START.txt` | Guide rapide | +| `README_PORTABLE.md` | Ce fichier | +| `start.bat` | Ancien launcher (ne pas utiliser) | + +## ๐ŸŽ“ Utilisation Pรฉdagogique + +### En Classe + +1. **Prรฉparation**: Installer Node.js portable chez toi +2. **En cours**: Brancher USB, lancer `START_PORTABLE.bat` +3. **Dรฉmo**: Les รฉtudiants accรจdent via leur navigateur +4. **Arrรชt**: Fermer le serveur, dรฉbrancher + +### Partage avec ร‰tudiants + +Si tu veux que les รฉtudiants aient leur propre copie: +1. Crรฉer une version sans `.env` (pas de clรฉs API) +2. Les features AI ne marcheront pas pour eux +3. Ils auront accรจs aux contenus et jeux locaux + +## ๐Ÿ”„ Mises ร  Jour + +Pour mettre ร  jour le projet: + +1. **Sur ton PC de dev**: + - Faire tes modifications + - Tester avec `npm start` + +2. **Copier sur USB**: + - Ne pas toucher ร  `nodejs-portable/` + - Copier les fichiers modifiรฉs + - Garder `.env` et `node_modules/` + +## ๐Ÿ“ Notes Techniques + +### Pourquoi Node.js Portable? + +- **Pas de droits admin** requis +- **Fonctionne depuis USB** sans installation +- **Isolรฉ du systรจme** - pas de conflit avec d'autres versions +- **Portable** - mรชme setup sur tous les PC + +### Diffรฉrences avec la Version Standard + +| Aspect | Version Standard | Version Portable | +|--------|-----------------|------------------| +| Installation | `npm install` | Copier `nodejs-portable/` | +| Lancement | `npm start` | `START_PORTABLE.bat` | +| Node.js | Global (installรฉ) | Local (dans le dossier) | +| Droits admin | Possibles | Pas nรฉcessaires | +| Mobilitรฉ | Non | Oui (clรฉ USB) | + +### Path et Variables d'Environnement + +`START_PORTABLE.bat` ajoute temporairement `nodejs-portable/` au PATH: +```batch +set PATH=%~dp0nodejs-portable;%PATH% +``` + +Cette modification est **temporaire** et n'affecte pas le systรจme. + +## ๐ŸŽฏ Cas d'Usage + +### โœ… Parfait Pour + +- Dรฉmonstrations en cours +- Utilisation sur PC publics (bibliothรจque, salle info) +- Tests sur diffรฉrents PC +- Backup portable du projet +- Partage rapide avec collรจgues + +### โŒ Pas Idรฉal Pour + +- Dรฉveloppement intensif (utiliser version standard) +- Serveur de production (utiliser dรฉploiement cloud) +- Utilisation quotidienne (installer Node.js normalement) + +## ๐Ÿ†˜ Support + +### Problรจme avec la Version Portable? + +1. Lire `PORTABLE_SETUP.txt` +2. Vรฉrifier la section Dรฉpannage ci-dessus +3. Vรฉrifier que `nodejs-portable/node.exe` existe +4. Essayer de relancer en mode administrateur (si possible) + +### Retour ร  la Version Standard + +Si tu prรฉfรจres utiliser Node.js installรฉ: +1. Installer Node.js normalement +2. Utiliser `start.bat` (pas `START_PORTABLE.bat`) +3. Supprimer le dossier `nodejs-portable/` (optionnel) + +--- + +**Version**: 2.0 Portable +**Derniรจre mise ร  jour**: 2025-10-18 +**Crรฉรฉ pour**: Utilisation en cours sans installation diff --git a/START_PORTABLE.bat b/START_PORTABLE.bat new file mode 100644 index 0000000..aa1b7fd --- /dev/null +++ b/START_PORTABLE.bat @@ -0,0 +1,85 @@ +@echo off +title Class Generator - Portable Edition +cd /d "%~dp0" + +echo. +echo ======================================== +echo Class Generator - Portable Edition +echo ======================================== +echo. + +:: Check if portable Node.js exists +if not exist "nodejs-portable\node.exe" ( + echo [ERROR] Node.js portable not found! + echo. + echo Please follow the setup instructions in PORTABLE_SETUP.txt + echo. + pause + exit /b 1 +) + +:: Add portable Node.js to PATH for this session only +set PATH=%~dp0nodejs-portable;%PATH% + +:: Verify Node.js is working +echo [1/3] Checking Node.js... +"%~dp0nodejs-portable\node.exe" --version >nul 2>&1 +if %errorlevel% neq 0 ( + echo [ERROR] Node.js is not working properly + pause + exit /b 1 +) +echo Node.js OK! + +:: Check if node_modules exists +echo [2/3] Checking dependencies... +if not exist "node_modules" ( + echo Installing dependencies... + "%~dp0nodejs-portable\node.exe" "%~dp0nodejs-portable\node_modules\npm\bin\npm-cli.js" install +) else ( + echo Dependencies OK! +) + +:: Kill any existing servers on port 8080 +echo [3/4] Cleaning up old servers... +taskkill /f /im node.exe >nul 2>&1 +timeout /t 1 /nobreak >nul + +:: Start the server in background +echo [4/4] Starting server... +echo. +echo ======================================== +echo Server starting on http://localhost:8080 +echo Opening browser automatically... +echo Press Ctrl+C to stop +echo ======================================== +echo. + +:: Start server in background +start /b "" "%~dp0nodejs-portable\node.exe" server.js + +:: Wait for server to start +timeout /t 2 /nobreak >nul + +:: Try to open Firefox, fallback to default browser +echo Opening browser... +start firefox http://localhost:8080 >nul 2>&1 +if %errorlevel% neq 0 ( + echo Firefox not found, opening default browser... + start http://localhost:8080 +) + +echo. +echo Browser opened! Server is running in background. +echo Press any key to STOP the server and close... +pause >nul + +:: Stop the server +echo. +echo Stopping server... +taskkill /f /im node.exe >nul 2>&1 + +:: If we get here, the server stopped +echo. +echo Server stopped +pause diff --git a/content/books/sbs.json b/content/books/sbs.json index 1798524..5ac2efb 100644 --- a/content/books/sbs.json +++ b/content/books/sbs.json @@ -20,7 +20,7 @@ ], "content_tags": ["vocabulary", "grammar", "conversation", "practical-english"], "total_chapters": 12, - "available_chapters": ["7-8"], + "available_chapters": ["2", "3", "7-8"], "completion_criteria": { "overall_progress": 80, "chapters_completed": 8, @@ -28,6 +28,46 @@ } }, "chapters": [ + { + "id": "sbs-2", + "chapter_number": "2", + "name": "Locations & Classrooms", + "description": "Learn classroom objects, home locations, and practice Where questions with to be", + "estimated_hours": 15, + "difficulty": "beginner", + "prerequisites": ["sbs-1"], + "learning_objectives": [ + "Master classroom object vocabulary", + "Learn home and location vocabulary", + "Practice Where questions with to be", + "Understand subject pronouns and contractions", + "Learn greetings and basic conversations" + ], + "vocabulary_count": 49, + "phrases_count": 14, + "dialogs_count": 4, + "exercises_count": 4 + }, + { + "id": "sbs-3", + "chapter_number": "3", + "name": "Present Continuous Tense", + "description": "Learn everyday activities and master present continuous tense", + "estimated_hours": 18, + "difficulty": "beginner", + "prerequisites": ["sbs-2"], + "learning_objectives": [ + "Master present continuous tense formation", + "Learn everyday activity vocabulary", + "Practice What are you doing questions", + "Understand verb -ing forms", + "Learn to describe ongoing actions" + ], + "vocabulary_count": 45, + "phrases_count": 17, + "dialogs_count": 4, + "exercises_count": 3 + }, { "id": "sbs-7-8", "chapter_number": "7-8", diff --git a/content/books/wte2.json b/content/books/wte2.json new file mode 100644 index 0000000..be2457e --- /dev/null +++ b/content/books/wte2.json @@ -0,0 +1,55 @@ +{ + "id": "wte2", + "name": "Welcome to English 2", + "description": "Welcome to English 2 - Oxford University Press - A beginner English course for young learners", + "difficulty": "beginner", + "language": "en-US", + "metadata": { + "version": "1.0", + "created": "2025-10-18", + "updated": "2025-10-18", + "source": "Welcome to English 2 - Oxford University Press", + "target_level": "beginner", + "total_estimated_hours": 40, + "prerequisites": [], + "learning_objectives": [ + "Learn basic English vocabulary for young learners", + "Master the English alphabet (letters A-Z)", + "Practice phonics and pronunciation", + "Use simple sentence structures", + "Ask and answer basic questions", + "Read simple stories" + ], + "content_tags": ["beginner", "young-learners", "alphabet", "phonics", "vocabulary", "basic-grammar"], + "total_chapters": 12, + "available_chapters": ["2"], + "completion_criteria": { + "overall_progress": 80, + "chapters_completed": 8, + "vocabulary_mastery": 85 + } + }, + "chapters": [ + { + "id": "wte2-2", + "chapter_number": "2", + "name": "Our Pet Friends & Letters W-Z", + "description": "Learn furniture, prepositions of place, where questions, and letters W-Z", + "estimated_hours": 4, + "difficulty": "beginner", + "prerequisites": ["wte2-1"], + "learning_objectives": [ + "Use prepositions: in, on, under, near", + "Ask and answer 'Where is/are...?' questions", + "Identify furniture vocabulary", + "Use contractions: isn't, aren't, let's", + "Recognize and write letters W, X, Y, Z", + "Read short stories about finding pets" + ], + "vocabulary_count": 41, + "phrases_count": 17, + "dialogs_count": 2, + "exercises_count": 3 + } + ] +} diff --git a/content/chapters/SBS2.txt b/content/chapters/SBS2.txt new file mode 100644 index 0000000..d6bce00 --- /dev/null +++ b/content/chapters/SBS2.txt @@ -0,0 +1,442 @@ +# SBS2 PDF - Text Extraction + +## Page 1: In the Classroom + +### Vocabulary List: +1. pen +2. book +3. pencil +4. notebook +5. bookshelf +6. globe +7. map +8. board +9. wall +10. clock +11. bulletin board +12. computer +13. table +14. chair +15. ruler +16. desk +17. dictionary + +--- + +## Page 2: Where Is It? + +### Grammar Examples: +(Where is) Where's the book? +(It is) It's on the desk. + +### Dialogue Examples: +- Where's the book? โ†’ It's on the desk. +- Where's the map? โ†’ It's on the wall. +- Where's the computer? โ†’ It's on the table. + +### Exercises: +1. Where's the pen? +2. Where's the board? +3. Where's the globe? +4. Where's the ruler? +5. Where's the pencil? +6. Where's the clock? +7. Where's the notebook? +8. Where's the dictionary? +9. Where's the bulletin board? + +### Activity: +**Make a List!** +Work with another student. Make a list of all the objects in your classroom. Present your list to the class. Who has the best list? + +--- + +## Page 3: At Home + +### Vocabulary List: +1. living room +2. dining room +3. kitchen +4. bedroom +5. bathroom +6. attic +7. yard +8. garage +9. basement + +--- + +## Page 4: Where Are You? + +### Grammar Chart: + +**Where** + +| am/is/are | Pronouns | Full Form | Contraction | Location | +|-----------|----------|-----------|-------------|----------| +| am | I | (I am) | I'm | in the kitchen. | +| is | he/she/it | (He is)/(She is)/(It is) | He's/She's/It's | | +| are | we/you/they | (We are)/(You are)/(They are) | We're/You're/They're | | + +### Dialogue Examples: +- Where are you? โ†’ I'm in the kitchen. +- Where are you? โ†’ We're in the living room. +- Where are Mr. and Mrs. Jones? โ†’ They're in the yard. + +### Exercises: +1. Where are you? +2. Where are you? +3. Where are Jim and Pam? +4. Where are you? +5. Where are Mr. and Mrs. Park? +6. Where are you? +7. Where are you? +8. Where are you and Ben? +9. Where are Mr. and Mrs. Hernandez? + +--- + +## Page 5: Where's Bob? + +### Dialogue Examples: +- Where's* Bob? โ†’ He's in the living room. +- Where's Mary? โ†’ She's in the bedroom. +- Where's the car? โ†’ It's in the garage. + +*Where's = Where is + +### Exercises: +1. Where's Tim? +2. Where's Rosa? +3. Where's the newspaper? +4. Where's Peggy? +5. Where's the telephone book? +6. Where's Harry? +7. Where's Ellen? +8. Where's Kevin? +9. Where's the cell phone? + +--- + +## Page 6: READING + +### THE STUDENTS IN MY ENGLISH CLASS + +The students in my English class are very interesting. Henry is Chinese. He's from Shanghai. Linda is Puerto Rican. She's from San Juan. Mr. and Mrs. Kim are Korean. They're from Seoul. + +George is Greek. He's from Athens. Carla is Italian. She's from Rome. Mr. and Mrs. Sato are Japanese. They're from Tokyo. My friend Maria and I are Mexican. We're from Mexico City. + +Yes, the students in my English class are very interesting. We're from many different countries . . . and we're friends. + +### READING CHECK-UP +**True or False?** +1. Linda is Korean. +2. George is Greek. +3. Henry is from Mexico City. +4. Mr. Kim is from Seoul. +5. Carla is Chinese. +6. The students in the class are from many countries. + +### How About You? +Tell about the students in YOUR English class. Where are they from? + +--- + +### How to Say It! + +**Greeting People** +- A: Hi. How are you? +- B: Fine. And you? +- A: Fine, thanks. + +Practice conversations with other students. + +--- + +## Page 7: Where Are They? + +**Ask and answer questions based on these pictures.** + +1. ________ Albert? (RESTAURANT) +2. ________ Carmen? (BANK) +3. ________ Walter and Mary? (SUPERMARKET) +4. ________ you? (LIBRARY) +5. ________ you? (PARK) +6. ________ Kate? (MOVIE THEATER) +7. ________ Mr. and Mrs. Lee? (POST OFFICE) +8. ________ monkey? (ZOO) +9. ________ (HOSPITAL) + +**Now add people and places of your own.** + +10. __________________? +11. __________________? +12. __________________? + +--- + +## Page 8: READING + +### ALL THE STUDENTS IN MY ENGLISH CLASS ARE ABSENT TODAY + +All the students in my English class are absent today. George is absent. He's in the hospital. Maria is absent. She's at the dentist. Mr. and Mrs. Sato are absent. They're at the social security office. Even our English teacher is absent. He's home in bed! + +What a shame! Everybody in my English class is absent today. Everybody except me. + +### READING CHECK-UP +**True or False?** +1. George is absent. +2. Maria is absent. +3. Mr. and Mrs. Sato are absent. +4. The English teacher is absent. + +### How About You? +Tell about YOUR English class: +- Which students are in class today? +- Which students are absent today? +- Where are they? + +--- + +## LISTENING + +### WHAT'S THE WORD? +Listen and choose the correct answer. +1. a. bank b. park +2. a. hospital b. library +3. a. She's b. She's +4. a. She's b. She's +5. a. We're b. They're +6. a. We're b. They're + +### WHERE ARE THEY? +Listen and choose the correct place. +1. a. living room b. dining room +2. a. bathroom b. bedroom +3. a. garage b. yard +4. a. bathroom b. bedroom +5. a. kitchen b. living room +6. a. bedroom b. basement + +--- + +## Page 9: PRONUNCIATION - Reduced "and" + +### Mr. and Mrs. + +**Listen. Then say it.** +- Mr. and Mrs. Jones +- Mr. and Mrs. Park +- Jim and Pam +- You and Ben + +**Say it. Then listen.** +- Mr. and Mrs. Lee +- Mr. and Mrs. Miller +- Walter and Mary +- Jim and I + +### Projects: + +**SIDE by SIDE JOURNAL** +Draw a picture of your apartment or house. Label the rooms. + +**Project** +Work with another student. Draw a picture of your classroom. Label all the objects. + +--- + +## CHAPTER SUMMARY + +### GRAMMAR ่ฏญๆณ• + +**SUBJECT PRONOUNS ไธปๆ ผไปฃ่ฏ** +**TO BE + LOCATION TO BE + ๅœฐ็‚น** + +| Where | am/is/are | Pronouns | Full Form | Contraction | Location | +|-------|-----------|----------|-----------|-------------|----------| +| | am | I? | (I am) | I'm | | +| | is | he?/she?/it? | (He is)/(She is)/(It is) | He's/She's/It's | in the kitchen. | +| | are | we?/you?/they? | (We are)/(You are)/(They are) | We're/You're/They're | | + +### KEY VOCABULARY ๅ…ณ้”ฎ่ฏๆฑ‡ + +**CLASSROOM OBJECTS ๆ•™ๅฎค็‰ฉๅ“** +- board +- book +- bookshelf +- bulletin board +- chair +- clock +- computer +- desk +- dictionary +- globe +- map +- notebook +- pen +- pencil +- ruler +- table +- wall + +**PLACES AT HOME ๅฎถไธญๅธƒๅฑ€** +- attic +- basement +- bathroom +- bedroom +- dining room +- garage +- kitchen +- living room +- yard + +**PLACES AROUND TOWN ๅธ‚ๅ†…ๅœบๆ‰€** +- bank +- hospital +- library +- movie theater +- park +- post office +- restaurant +- supermarket +- zoo + +**GREETING PEOPLE ้—ฎๅ€™** +- Hi. How are you? +- Fine. And you? +- Fine, thanks. + +--- + +## TEACHER'S COURSE NOTES + +### Lesson 1 - First Class (็ฌฌไธ€ๆฌก่ฏพ) + +**Key Vocabulary (้‡็‚น่ฏๆฑ‡):** +- pen: ้’ข็ฌ” +- pencil: ้“…็ฌ” +- book: ไนฆ +- desk: ไนฆๆกŒ๏ผ›ๅŠžๅ…ฌๆกŒ +- computer: ่ฎก็ฎ—ๆœบ๏ผ›็”ต่„‘ +- bank: ้“ถ่กŒ +- supermarket: ่ถ…ๅธ‚ +- post office: ้‚ฎๅฑ€ +- restaurant: ้ค้ฆ†๏ผ›้ฅญๅบ— +- library: ๅ›พไนฆ้ฆ† +- living room: ๅฎขๅŽ…๏ผ›่ตทๅฑ…ๅฎค +- dining room: ้คๅŽ…๏ผ›้ฅญๅŽ… +- kitchen: ๅŽจๆˆฟ +- bedroom: ๅงๅฎค +- bathroom: ๆตดๅฎค๏ผ›ๆด—ๆ‰‹้—ด + +**Key Sentences (้‡็‚นๅฅๅญ):** +- Where are you? We are in the kitchen. +- Where are they? They are in yard. + +**Key Content (้‡็‚นๅ†…ๅฎน):** +1. ไบบ็งฐไปฃ่ฏ็š„็”จๆณ• (Usage of personal pronouns) +2. ๅœฐ็‚น็š„็”จๆณ• (Usage of locations) + +**Homework Review Suggestions (่ฏพๅŽๅคไน ๅปบ่ฎฎ):** +1. ่ฏปP7P8็š„ๅ•่ฏ๏ผŒ่ฏปP9็š„ไธŠ้ขไธ‰ๅน…ๅ›พไธ‹้ข1-9ๅน…ๅ›พ๏ผŒๅฎŒๆˆๆฏๅคฉ็š„่ฏญ้Ÿณๆ‰“ๅกใ€‚ +2. P9ไธŠ้ข็š„ไธ‰ๅน…ๅ›พ่ƒŒไผš๏ผŒๅ‘้€่ฏญ้Ÿณๆ‰“ๅกใ€‚ + +--- + +### Lesson 2 - Second Class + +**Key Vocabulary (้‡็‚น่ฏๆฑ‡):** +- pen: ้’ข็ฌ” +- pencil: ้“…็ฌ” +- book: ไนฆ +- desk: ไนฆๆกŒ๏ผ›ๅŠžๅ…ฌๆกŒ +- computer: ่ฎก็ฎ—ๆœบ๏ผ›็”ต่„‘ +- bank: ้“ถ่กŒ +- supermarket: ่ถ…ๅธ‚ +- post office: ้‚ฎๅฑ€ +- restaurant: ้ค้ฆ†๏ผ›้ฅญๅบ— +- library: ๅ›พไนฆ้ฆ† +- living room: ๅฎขๅŽ…๏ผ›่ตทๅฑ…ๅฎค +- dining room: ้คๅŽ…๏ผ›้ฅญๅŽ… +- kitchen: ๅŽจๆˆฟ +- bedroom: ๅงๅฎค +- bathroom: ๆตดๅฎค๏ผ›ๆด—ๆ‰‹้—ด +- clock: ๆ—ถ้’Ÿ๏ผŒ้’Ÿ +- bulletin board: ๅธƒๅ‘Šๆ ๏ผŒๅ…ฌๅ‘Šๆฟ +- computer: ่ฎก็ฎ—ๆœบ๏ผŒ็”ต่„‘ +- globe: ๅœฐ็ƒไปช๏ผ›ๅœฐ็ƒ๏ผ›ไธ–็•Œ +- notebook: ็ฌ”่ฎฐๆœฌ +- bookshelf: ไนฆๆžถ +- dictionary: ่ฏๅ…ธ๏ผŒๅญ—ๅ…ธ +- attic: ้˜ๆฅผ๏ผŒ้กถๆฅผ +- yard: ้™ขๅญ๏ผ›ๅœบๅœฐ +- garage: ่ฝฆๅบ“ +- basement: ๅœฐไธ‹ๅฎค +- movie theater: ็”ตๅฝฑ้™ข + +**Key Sentences (้‡็‚นๅฅๅญ):** +- Where are you? We are in the kitchen. +- Where are they? They are in the yard. + +**Key Content (้‡็‚นๅ†…ๅฎน):** +1. ไบบ็งฐไปฃ่ฏ็š„็”จๆณ• (Usage of personal pronouns) +2. ๅœฐ็‚น็š„็”จๆณ• (Usage of locations) + +**Homework Review Suggestions (่ฏพๅŽๅคไน ๅปบ่ฎฎ):** +1. ่ฏปP7P8P10็š„ๅ•่ฏ๏ผˆ็›ดๆŽฅ่ฏปๅฐฑๅฏไปฅๅ•ฆ๏ผŒไธ็”จๆ‹ผ่ฏป๏ผ‰๏ผŒ่ฏปP9P11็š„ๅฏน่ฏ๏ผŒ่ฏปP13ๅฐ็Ÿญๆ–‡๏ผŒๅฎŒๆˆๆฏๆ—ฅ่ฏญ้Ÿณๆ‰“ๅกใ€‚ +2. ๆŠ„ๅ†™้‡็‚นๅ•่ฏไธ€ไธช2้๏ผŒๅฅๅญไธ€ไธช2้๏ผŒไธญๆ–‡ๆ„ๆ€1้๏ผŒไธ‹่Š‚่ฏพ้ป˜ๅ†™ใ€‚ +3. ้ข„ไน P13-P14็š„็”Ÿๅ•่ฏใ€‚ +4. ๅฎŒๆˆ็ปƒไน ๅ†ŒP7-P8ใ€‚ +5. ้Ÿณๆ ‡็ปƒไน Day 1ๅฎŒๆˆใ€‚้Ÿณๆ ‡ๅ•่ฏๆœ—่ฏป๏ผŒๅฎŒๆˆๆฏๆ—ฅ่ฏญ้Ÿณๆ‰“ๅกใ€‚ + +--- + +### Lesson 3 - Third Class + +**Key Vocabulary (้‡็‚น่ฏๆฑ‡):** +- dictionary: ่ฏๅ…ธ๏ผŒๅญ—ๅ…ธ +- attic: ้˜ๆฅผ๏ผŒ้กถๆฅผ +- yard: ้™ขๅญ๏ผ›ๅœบๅœฐ +- garage: ่ฝฆๅบ“ +- basement: ๅœฐไธ‹ๅฎค +- movie theater: ็”ตๅฝฑ้™ข +- hospital: ๅŒป้™ข +- absent: ็ผบๅธญ็š„ +- dentist: ็‰™็ง‘ๅŒป็”Ÿ +- social security office: ็คพไผšไฟ้šœๅŠžๅ…ฌๅฎค +- shame: ็พž่€ป๏ผŒ็พžๆ„ง๏ผ›ๆ†พไบ‹ +- except: ้™คไบ† +- everybody: ๆฏไธชไบบ๏ผŒๆ‰€ๆœ‰ไบบ +- interesting: ๆœ‰่ถฃ็š„๏ผŒๆœ‰ๆ„ๆ€็š„ +- different: ไธๅŒ็š„ +- countries: country๏ผˆๅ›ฝๅฎถ๏ผ›ไนกๆ‘๏ผ‰็š„ๅคๆ•ฐๅฝขๅผ +- Chinese: ไธญๅ›ฝ็š„๏ผ›ไธญๅ›ฝไบบ +- Japanese: ๆ—ฅๆœฌ็š„๏ผ›ๆ—ฅๆœฌไบบ +- Korean: ้Ÿฉๅ›ฝ็š„๏ผ›้Ÿฉๅ›ฝไบบ +- Italian: ๆ„ๅคงๅˆฉ็š„๏ผ›ๆ„ๅคงๅˆฉไบบ +- Mexican: ๅขจ่ฅฟๅ“ฅ็š„๏ผ›ๅขจ่ฅฟๅ“ฅไบบ +- Greek: ๅธŒ่…Š็š„๏ผ›ๅธŒ่…Šไบบ + +**Key Sentence Patterns (้‡็‚นๅฅๅž‹):** +1. All the students in my English class are absent today. +2. What a shame! +3. Everybody except me. +4. The students in my English class are very interesting. +5. We're from many different countries. + +**Key Content (้‡็‚นๅ†…ๅฎน):** +1. ไบบ็งฐไปฃ่ฏ็š„็”จๆณ• (Usage of personal pronouns) +2. ๅœฐ็‚น็š„็”จๆณ• (Usage of locations) +3. ๅ›ฝๅฎถๅŠๅ›ฝๅฎถ็š„ไบบ่กจ่พพๅฝขๅผ (Expression of countries and nationalities) +4. ๆ—ฅๅธธๆ‰“ๆ‹›ๅ‘ผ็”จ่ฏญ (Daily greeting expressions) + +**Homework Review Suggestions (่ฏพๅŽๅคไน ๅปบ่ฎฎ):** +1. ่ฏปP7P8P10็š„ๅ•่ฏ๏ผŒๆฏไธชไธ€้ไธ็”จๆ‹ผ่ฏป๏ผŒP13่ฏพๆ–‡P15่ฏพๆ–‡๏ผŒๅฎŒๆˆๆฏๆ—ฅ่ฏญ้Ÿณๆ‰“ๅกใ€‚ +2. ๆŠ„ๅ†™้‡็‚นๅ•่ฏไธ€ไธช2้๏ผŒๅฅๅญไธ€ไธช2้๏ผŒไธญๆ–‡ๆ„ๆ€1้ใ€‚ +3. P13็š„่ฏพๆ–‡่ฏป็†Ÿ๏ผŒ็ฌฌไบŒๆฎต่ƒŒไผš๏ผŒๅฎŒๆˆ่ฏญ้Ÿณๆ‰“ๅกใ€‚ +4. ็ปƒไน ๅ†ŒP6-P10ๅฎŒๆˆใ€‚๏ผˆๅฌๅŠ›ไธๅš๏ผ‰ +5. ้Ÿณๆ ‡Day 2็ปƒไน ๏ผŒๅฎŒๆˆ่ฏญ้Ÿณๆ‰“ๅกใ€‚ + +**Teacher's Comments:** +่ฟ™ๅ‘จ่กจๆ‰ฌLeonๅ’ŒJoeyๅฐๆœ‹ๅ‹ๅฌๅ†™ๅ…จๅฏน๏ผŒ่€Œไธ”่ƒฝๅคŸๅšๅˆฐๆฏๅคฉๆ‰“ๅก๏ผŒๅพˆๆฃ’ๅ“ฆ๏ผŒ็ปง็ปญไฟๆŒ๐Ÿ‘๏ผŒๅ…ถไป–ๅฐๆœ‹ๅ‹ไนŸ่ฆๅŠ ๆฒนๅ•ฆ๏ผŒๅ…ปๆˆ่‰ฏๅฅฝ็š„ไน ๆƒฏๅ“ฆ \ No newline at end of file diff --git a/content/chapters/SBS3.txt b/content/chapters/SBS3.txt new file mode 100644 index 0000000..2383197 --- /dev/null +++ b/content/chapters/SBS3.txt @@ -0,0 +1,363 @@ +# SBS Chapter 3 - Present Continuous Tense + +## Everyday Activities + +--- + +## VOCABULARY PREVIEW + +### Everyday Activities (ๆ—ฅๅธธๆดปๅŠจ) + +1. eating +2. drinking +3. cooking +4. reading +5. studying +6. teaching +7. singing +8. sleeping +9. swimming +10. planting +11. watching TV +12. listening to music +13. playing cards +14. playing baseball +15. playing the piano + +--- + +## Page 18: What Are You Doing? + +### Grammar Chart + +**What** + +| am/is/are | Pronouns | Question | Full Form | Contraction | Activity | +|-----------|----------|----------|-----------|-------------|----------| +| am | I | doing? | (I am) | I'm | eating. | +| is | he/she/it | | (He is)/(She is)/(It is) | He's/She's/It's | | +| are | we/you/they | | (We are)/(You are)/(They are) | We're/You're/They're | | + +### Dialogue Examples + +**Scene 1:** +- What are you doing? โ†’ I'm reading. +- What are you doing? โ†’ We're cooking. +- What are Mary and Fred doing? โ†’ They're studying English. + +**Scene 2:** +- What's Tom doing? โ†’ He's eating. +- What's Martha doing? โ†’ She's watching TV. +- What's your dog doing? โ†’ It's sleeping. + +--- + +## Page 19: Practice Exercises + +### Complete the dialogues: + +1. **A.** What are you doing? + **B.** __________ reading the newspaper. + +2. **A.** __________ Mr. and Mrs. Lane doing? + **B.** __________ cooking dinner. + +3. **A.** __________ you and Judy doing? + **B.** __________ eating dinner. + +4. **A.** __________ Rita doing? + **B.** __________ studying English. + +5. **A.** __________ Henry doing? + **B.** __________ sleeping. + +6. **A.** __________ Carol and Ken doing? + **B.** __________ watching TV. + +7. **A.** __________ Irene doing? + **B.** __________ playing the piano. + +8. **A.** What are YOU doing? + **B.** I'm __________. + +--- + +## Page 20-21: What's Everybody Doing? + +### Example Dialogue: +- **A.** Where's Walter? +- **B.** He's in the kitchen. +- **A.** What's he doing? +- **B.** He's eating breakfast. + +### Practice Situations: + +1. **Karen** + - park + - eating lunch + +2. **Mr. and Mrs. Clark** + - dining room + - eating dinner + +3. **you** + - bedroom + - playing the guitar + +4. **you** + - living room + - playing cards + +5. **Gary and Jane** + - yard + - playing baseball + +6. **Miss Baker** + - cafeteria + - drinking milk + +7. **you** + - library + - studying English + +8. **Ms. Johnson** + - classroom + - teaching mathematics + +10. **Martha** + - hospital + - watching TV + +11. **Martin** + - bathroom + - singing + +12. **your friend** + - park + - listening to music + +### How to Say It! + +**Checking Understanding** +- A. Where's Walter? +- B. He's in the kitchen. +- A. In the kitchen? +- B. Yes. + +Practice conversations with other students. + +--- + +### Action Game! + +**Instructions:** +- "What am I doing?" โ†’ "You're playing the guitar." + +Pantomime an everyday activity for the class. Ask students, "What am I doing?" + +--- + +## Page 22: READING + +### IN THE PARK + +The Jones family is in the park today. The sun is shining, and the birds are singing. It's a beautiful day! + +Mr. Jones is reading the newspaper. Mrs. Jones is listening to the radio. Sally and Patty Jones are studying. And Tommy Jones is playing the guitar. + +The Jones family is very happy today. It's a beautiful day, and they're in the park. + +--- + +### AT HOME IN THE YARD + +The Chen family is at home in the yard today. The sun is shining, and the birds are singing. It's a beautiful day! + +Mr. Chen is planting flowers. Mrs. Chen is drinking lemonade and reading a book. Emily and Jason Chen are playing with the dog. And Jennifer Chen is sleeping. + +The Chen family is very happy today. It's a beautiful day, and they're at home in the yard. + +--- + +## Page 23: READING CHECK-UP + +### True or False? + +1. The Jones family is at home in the yard today. +2. Mr. Chen is planting flowers. +3. Patty Jones is studying. +4. Jason Chen is reading a book. +5. The Chen family is singing. +6. The Jones family and the Chen family are very happy today. + +--- + +### Q & A + +**Using this model, make questions and answers based on the stories on page 22.** + +- **A.** What's Mr. Jones doing? +- **B.** He's reading the newspaper. + +--- + +## LISTENING + +### Listen and choose the correct answer. + +1. a. She's studying. + b. I'm studying. + +2. a. He's eating. + b. She's eating. + +3. a. He's watching TV. + b. She's watching TV. + +4. a. We're cooking dinner. + b. They're cooking dinner. + +5. a. We're planting flowers. + b. They're planting flowers. + +6. a. You're playing baseball. + b. We're playing baseball. + +--- + +## IN YOUR OWN WORDS + +### For Writing and Discussion + +**AT THE BEACH** + +The Martinez family is at the beach today. Using this picture, tell a story about the Martinez family. + +*[Image shows the Martinez family at the beach with various family members doing different activities]* + +--- + +## Page 24: PRONUNCIATION + +### Reduced "What are" & "Where are" + +**Listen. Then say it.** +- What are you doing? +- What are Jim and Jane doing? +- Where are Mary and Fred? +- Where are you and Judy? + +**Say it. Then listen.** +- What are they doing? +- What are Carol and Ken doing? +- Where are Mr. and Mrs. Lane? +- Where are you and Henry? + +--- + +### SIDE by SIDE JOURNAL + +What are you doing now? +What are your friends doing? +Write about it in your journal. + +--- + +## CHAPTER SUMMARY + +### GRAMMAR ่ฏญๆณ• + +**PRESENT CONTINUOUS TENSE ็Žฐๅœจ่ฟ›่กŒๆ—ถ** + +| What | am/is/are | Pronouns | Question | Full Form | Contraction | Activity | +|------|-----------|----------|----------|-----------|-------------|----------| +| | am | I | doing? | (I am) | I'm | | +| | is | he/she/it | | (He is)/(She is)/(It is) | He's/She's/It's | eating. | +| | are | we/you/they | | (We are)/(You are)/(They are) | We're/You're/They're | | + +--- + +### KEY VOCABULARY ๅ…ณ้”ฎ่ฏๆฑ‡ + +**EVERYDAY ACTIVITIES ๆ—ฅๅธธๆดปๅŠจ** + +- cooking dinner +- drinking milk/lemonade +- eating breakfast/lunch/dinner +- listening to music/the radio +- planting flowers +- playing cards +- playing baseball +- playing the guitar/the piano +- reading a book/the newspaper +- singing +- sleeping +- studying English +- swimming +- teaching +- watching TV + +**CHECKING UNDERSTANDING** +- In the kitchen? + +--- + +## Page 25: Side by Side Gazette + +### FACT FILE + +**Titles** +- **Mr.** is a title for a man. +- **Ms., Mrs.,** and **Miss** are titles for a woman. + +--- + +### Nicknames + +"My name is David. My nickname is Dave." + +#### COMMON NICKNAMES + +| Name | Nickname | Name | Nickname | +|------|----------|------|----------| +| James | Jim | Elizabeth | Liz, Betty | +| Peter | Pete | Jennifer | Jenny | +| Robert | Bob | Judith | Judy | +| Timothy | Tim | Katherine | Kathy, Kate | +| Thomas | Tom | Patricia | Patty | +| William | Bill | Susan | Sue | + +--- + +### Global Exchange + +**SungHee:** Hello. My name is Sung Hee. I'm Korean. I'm from Seoul. I'm a student. Right now I'm in my English class. I'm looking for a keypal in a different country. + +**DanielR:** Hi, Sung Hee! My name is Daniel. My nickname is Danny. My last name is Rivera. I'm Mexican. I'm from Mexico City. I'm a student. Right now I'm at home. I'm at my computer, and I'm listening to music. I'm also looking for a keypal. Tell me about your school and your English class. + +--- + +**Activity:** Send a message over the Internet. Tell about yourself. Find a keypal. + +--- + +### BUILD YOUR VOCABULARY! + +**Playing Instruments, Sports, and Games** + +I'm playing _________. + +#### Instruments +- the violin +- the clarinet +- the trumpet + +#### Sports +- soccer +- tennis +- basketball + +#### Games +- chess +- checkers +- tic tac toe \ No newline at end of file diff --git a/content/chapters/WTE2-2.txt b/content/chapters/WTE2-2.txt new file mode 100644 index 0000000..bace051 --- /dev/null +++ b/content/chapters/WTE2-2.txt @@ -0,0 +1,395 @@ +# Welcome to English 2 - Unit 2: Our Pet Friends + Letters W X Y Z + +--- + +## Unit 2: Our Pet Friends + +### Page 7: Where are the children's pets? Read. + +**Vocabulary - Furniture (ๅฎถๅ…ท่ฏๆฑ‡):** +1. a sofa - ๆฒ™ๅ‘ +2. a table - ๆกŒๅญ +3. a chair - ๆค…ๅญ +4. a box - ็›’ๅญ๏ผ›็ฎฑๅญ +5. a cupboard - ๆฉฑๆŸœ๏ผ›็ข—ๆŸœ +6. a shelf - ๆžถๅญ๏ผ›ๆๆฟ + +**Dialogue:** + +**Scene 1:** +- **Tom (1):** Where is the cat? +- **Mary (2):** It is on the chair. +- **Betty (5):** Where are the turtles? + +**Scene 2:** +- **Sally (3):** Where are the hamsters? +- **Peter (4):** One is in the cupboard. One is near the sofa. +- **Charlie (6):** They are under the sofa. + +--- + +### Page 8: Help the children find the other pets. Ask and answer questions. + +**Prepositions (ไป‹่ฏ):** +- on - ๅœจโ€ฆโ€ฆไธŠ้ข +- in - ๅœจโ€ฆโ€ฆ้‡Œ้ข +- near - ๅœจโ€ฆโ€ฆ้™„่ฟ‘ +- under - ๅœจโ€ฆโ€ฆไธ‹้ข + +**Practice Structure:** + +Where | is/are | the _______ ? | It is / They are | _______ . + +**Characters:** Tom and Mary are asking about pets (hamster, bird, rabbit, turtles) + +--- + +## Page 9: Learn to Read - Where is Bob? + +### Story: Where is Bob? Read and find out. +### ๆ•…ไบ‹๏ผš้ฒๅ‹ƒๅœจๅ“ช้‡Œ๏ผŸ้˜…่ฏปๅนถๆ‰พๅ‡บ็ญ”ๆกˆใ€‚ + +**Key Vocabulary (้‡็‚น่ฏๆฑ‡):** +- dog - ็‹— +- sad - ไผคๅฟƒ็š„๏ผ›้šพ่ฟ‡็š„ +- find - ๆ‰พๅˆฐ๏ผ›ๅ‘็Žฐ +- cupboard - ๆฉฑๆŸœ +- bag - ๅŒ…๏ผ›่ข‹ๅญ +- sofa - ๆฒ™ๅ‘ +- bed - ๅบŠ + +**1.** Ricky is Bob's dog. +็‘žๅฅ‡ๆ˜ฏ้ฒๅ‹ƒ็š„็‹—ใ€‚ +Ricky is sad. +็‘žๅฅ‡ๅพˆไผคๅฟƒใ€‚ +He cannot find Bob. +ไป–ๆ‰พไธๅˆฐ้ฒๅ‹ƒใ€‚ + +**2.** Is Bob under the cupboard? +้ฒๅ‹ƒๅœจๆฉฑๆŸœไธ‹้ขๅ—๏ผŸ +No, he isn't. +ไธ๏ผŒไป–ไธๅœจใ€‚ + +**3.** Where is Bob? +้ฒๅ‹ƒๅœจๅ“ช้‡Œ๏ผŸ +Is he in the bag? +ไป–ๅœจๅŒ…้‡Œๅ—๏ผŸ +No, Ricky. Bob isn't in the bag. +ไธ๏ผŒ็‘žๅฅ‡ใ€‚้ฒๅ‹ƒไธๅœจๅŒ…้‡Œใ€‚ + +**4.** Is Bob on the sofa? +้ฒๅ‹ƒๅœจๆฒ™ๅ‘ไธŠๅ—๏ผŸ +No, Bob isn't on the sofa. +ไธ๏ผŒ้ฒๅ‹ƒไธๅœจๆฒ™ๅ‘ไธŠใ€‚ + +**Note:** +- isn't = is not +- Let's = Let us + +--- + +### Page 10: Story Continues +### ็ฌฌ10้กต๏ผšๆ•…ไบ‹็ปง็ปญ + +**5.** Come on, Ricky. +ๆฅๅง๏ผŒ็‘žๅฅ‡ใ€‚ +Let's find Bob. +ๆˆ‘ไปฌไธ€่ตทๆ‰พ้ฒๅ‹ƒๅงใ€‚ + +**6.** Look, Ricky. +็œ‹๏ผŒ็‘žๅฅ‡ใ€‚ +Bob is on the bed! +้ฒๅ‹ƒๅœจๅบŠไธŠ๏ผ +Ricky! +็‘žๅฅ‡๏ผ + +--- + +### Page 10: Comprehension Exercise +### ็ฌฌ10้กต๏ผš็†่งฃ็ปƒไน  + +**In the story, where does Ricky look for Bob? Tick (โœ“).** +**ๅœจๆ•…ไบ‹ไธญ๏ผŒ็‘žๅฅ‡ๅœจๅ“ช้‡Œๆ‰พ้ฒๅ‹ƒ๏ผŸๆ‰“ๅ‹พ(โœ“)ใ€‚** + +| | bag (ๅŒ…) | cupboard (ๆฉฑๆŸœ) | sofa (ๆฒ™ๅ‘) | +|---|-----|----------|------| +| 1 | in (ๅœจโ€ฆโ€ฆ้‡Œ้ข) | | | +| 2 | on (ๅœจโ€ฆโ€ฆไธŠ้ข) | | | +| 3 | under (ๅœจโ€ฆโ€ฆไธ‹้ข) | | | + +--- + +### Phonics +### ่ฏญ้Ÿณ + +**Say the sound and the words.** +**่ฏปๅ‡บ่ฟ™ไธช้Ÿณๅ’Œๅ•่ฏใ€‚** + +**Sound: a** +**้Ÿณ๏ผša** + +Words: +- cat - ็Œซ +- fat - ่ƒ–็š„ +- sad - ไผคๅฟƒ็š„ +- Dad - ็ˆธ็ˆธ + +**Can you say these words?** +**ไฝ ่ƒฝ่ฏป่ฟ™ไบ›ๅ•่ฏๅ—๏ผŸ** +- hat - ๅธฝๅญ +- bad - ๅ็š„ + +--- + +## Page 11: Practice Activity +## ็ฌฌ11้กต๏ผš็ปƒไน ๆดปๅŠจ + +### The pets are hiding. Can you find them? Ask and answer questions. +### ๅฎ ็‰ฉไปฌ่—่ตทๆฅไบ†ใ€‚ไฝ ่ƒฝๆ‰พๅˆฐๅฎƒไปฌๅ—๏ผŸ้—ฎ็ญ”้—ฎ้ข˜ใ€‚ + +**Pets to find (่ฆๆ‰พ็š„ๅฎ ็‰ฉ):** +1. cat - ็Œซ +2. hamsters - ไป“้ผ  +3. rabbit - ๅ…”ๅญ +4. turtles - ไนŒ้พŸ + +**Scene (ๅœบๆ™ฏ):** A bedroom with bed, cupboard, and box +**ๅœบๆ™ฏ๏ผš** ไธ€ไธชๆœ‰ๅบŠใ€ๆฉฑๆŸœๅ’Œ็›’ๅญ็š„ๅงๅฎค + +**Example Dialogue (ๅฏน่ฏ็คบไพ‹):** + +- Where is the cat? **Is it** near the cupboard? + ็Œซๅœจๅ“ช้‡Œ๏ผŸ**ๅฎƒๅœจ**ๆฉฑๆŸœ้™„่ฟ‘ๅ—๏ผŸ +- **No, it isn't.** + **ไธ๏ผŒๅฎƒไธๅœจใ€‚** +- **Is it** in the box? + **ๅฎƒๅœจ**็›’ๅญ้‡Œๅ—๏ผŸ +- **Yes, it is.** + **ๆ˜ฏ็š„๏ผŒๅฎƒๅœจใ€‚** +- Where are the hamsters? **Are they** ...? + ไป“้ผ ๅœจๅ“ช้‡Œ๏ผŸ**ๅฎƒไปฌๅœจ**โ€ฆโ€ฆๅ—๏ผŸ +- **Yes, they are.** / **No, they aren't.** + **ๆ˜ฏ็š„๏ผŒๅฎƒไปฌๅœจใ€‚** / **ไธ๏ผŒๅฎƒไปฌไธๅœจใ€‚** + +**Note (ๆณจ้‡Š):** aren't = are not + +--- + +### Task +### ไปปๅŠก + +Play a game. Ask your friends questions to find the missing pets. +็Žฉๆธธๆˆใ€‚้—ฎไฝ ็š„ๆœ‹ๅ‹้—ฎ้ข˜ๆฅๆ‰พๅˆฐๅคฑ่ธช็š„ๅฎ ็‰ฉใ€‚ + +--- + +## Page 12: Language Fun +## ็ฌฌ12้กต๏ผš่ฏญ่จ€่ถฃๅ‘ณ + +### Play a guessing game. +### ็Žฉ็Œœ่ฐœๆธธๆˆใ€‚ + +**Steps (ๆญฅ้ชค):** + +**1.** Close your eyes. +้—ญไธŠไฝ ็š„็œผ็›ใ€‚ + +**2.** Where is the rabbit? +ๅ…”ๅญๅœจๅ“ช้‡Œ๏ผŸ + +**3.** Is it under a desk? +ๅฎƒๅœจๆกŒๅญไธ‹้ขๅ—๏ผŸ +No, it isn't. +ไธ๏ผŒๅฎƒไธๅœจใ€‚ + +**4.** Is it in a drawer? +ๅฎƒๅœจๆŠฝๅฑ‰้‡Œๅ—๏ผŸ +Yes, it is. Well done! +ๆ˜ฏ็š„๏ผŒๅฎƒๅœจใ€‚ๅšๅพ—ๅฅฝ๏ผ + +**Vocabulary (่ฏๆฑ‡):** +- eyes - ็œผ็› +- desk - ไนฆๆกŒ +- drawer - ๆŠฝๅฑ‰ +- well done - ๅšๅพ—ๅฅฝ + +--- + +--- + +# UNIT 8: Letters Ww Xx Yy Zz +# ็ฌฌ8ๅ•ๅ…ƒ๏ผšๅญ—ๆฏ Ww Xx Yy Zz + +## Letter Ww +## ๅญ—ๆฏ Ww + +### Listen and repeat. +### ๅฌๅนถ่ทŸ่ฏปใ€‚ + +**Ww** + +**Key word (ๅ…ณ้”ฎ่ฏ):** wise wolf - ่ชๆ˜Ž็š„็‹ผ + +### Listen, point, and repeat. +### ๅฌใ€ๆŒ‡ๅนถ่ทŸ่ฏปใ€‚ + +**Words starting with W (ไปฅWๅผ€ๅคด็š„ๅ•่ฏ):** +1. wolf - ็‹ผ +2. web - ็ฝ‘๏ผ›่œ˜่››็ฝ‘ +3. water - ๆฐด +4. watch - ๆ‰‹่กจ๏ผ›่ง‚็œ‹ + +--- + +### Trace, write, and say. + +**W** (capital) +**w** (lowercase) + +### Listen. Then write Ww or cross it out. + +*[Practice exercise with 6 items]* + +### Listen and chant. + +"Do you see a web?" + +--- + +## Letter Xx +## ๅญ—ๆฏ Xx + +### Listen and repeat. +### ๅฌๅนถ่ทŸ่ฏปใ€‚ + +**Xx** + +**Key word (ๅ…ณ้”ฎ่ฏ):** fox in a box - ็›’ๅญ้‡Œ็š„็‹็‹ธ + +### Listen, point, and repeat. +### ๅฌใ€ๆŒ‡ๅนถ่ทŸ่ฏปใ€‚ + +**Words with X (ๅซๆœ‰X็š„ๅ•่ฏ):** +1. fox - ็‹็‹ธ +2. box - ็›’ๅญ๏ผ›็ฎฑๅญ +3. six - ๅ…ญ +4. wax - ่œก + +--- + +### Trace, write, and say. + +**X** (capital) +**x** (lowercase) + +### Match. Then write Xx. + +1. box +2. six (number 6) +3. fox +4. wax (yellow substance) + +### Listen and chant. + +"Do you see the box?" + +--- + +## Letter Yy +## ๅญ—ๆฏ Yy + +### Listen and repeat. +### ๅฌๅนถ่ทŸ่ฏปใ€‚ + +**Yy** + +**Key word (ๅ…ณ้”ฎ่ฏ):** yellow yo-yo - ้ป„่‰ฒ็š„ๆ‚ ๆ‚ ็ƒ + +### Listen, point, and repeat. +### ๅฌใ€ๆŒ‡ๅนถ่ทŸ่ฏปใ€‚ + +**Words starting with Y (ไปฅYๅผ€ๅคด็š„ๅ•่ฏ):** +1. yo-yo - ๆ‚ ๆ‚ ็ƒ +2. yak - ็‰ฆ็‰› +3. yogurt - ้…ธๅฅถ +4. yacht - ๆธธ่‰‡ + +--- + +### Trace, write, and say. + +**Y** (capital) +**y** (lowercase) + +### Listen and write. Then match. + +**Practice with words:** +1. yak +2. yo-yo +3. yogurt +4. yacht +5. kite +6. yacht + +### Listen and chant. + +"I don't have a yo-yo." + +--- + +## Letter Zz + +### Listen and repeat. + +**Zz** + +**Key word:** zigzag zipper + +### Listen, point, and repeat. + +**Words starting with Z:** +1. zipper +2. zero (number 0) +3. zoo +4. zebra + +--- + +### Trace, write, and say. + +**Z** (capital) +**z** (lowercase) + +### Connect. Then write Zz. + +*[Maze activity connecting items that start with Z]* + +### Listen and chant. + +"Do you want a zebra?" + +--- + +## Unit 8 Vocabulary Summary +## ็ฌฌ8ๅ•ๅ…ƒ่ฏๆฑ‡ๆ€ป็ป“ + +**Ww:** wolf (็‹ผ), web (็ฝ‘), water (ๆฐด), watch (ๆ‰‹่กจ) +**Xx:** fox (็‹็‹ธ), box (็›’ๅญ), six (ๅ…ญ), wax (่œก) +**Yy:** yo-yo (ๆ‚ ๆ‚ ็ƒ), yak (็‰ฆ็‰›), yogurt (้…ธๅฅถ), yacht (ๆธธ่‰‡) +**Zz:** zipper (ๆ‹‰้“พ), zero (้›ถ), zoo (ๅŠจ็‰ฉๅ›ญ), zebra (ๆ–‘้ฉฌ) + +--- + +## Additional Notes +## ่กฅๅ……่ฏดๆ˜Ž + +### Contractions learned (ๅญฆไน ็š„็ผฉๅ†™): +- isn't = is not (ไธๆ˜ฏ) +- aren't = are not (ไธๆ˜ฏ๏ผŒๅคๆ•ฐ) +- Let's = Let us (่ฎฉๆˆ‘ไปฌ) + +### Grammar focus (่ฏญๆณ•้‡็‚น): +- Where is/are...? (โ€ฆโ€ฆๅœจๅ“ช้‡Œ๏ผŸ) +- Is it / Are they + preposition (in/on/under/near)? (ๅฎƒ/ๅฎƒไปฌๅœจโ€ฆโ€ฆๅ—๏ผŸ+ ไป‹่ฏ) +- Yes, it is. / No, it isn't. (ๆ˜ฏ็š„๏ผŒๅฎƒๅœจใ€‚/ ไธ๏ผŒๅฎƒไธๅœจใ€‚) +- Yes, they are. / No, they aren't. (ๆ˜ฏ็š„๏ผŒๅฎƒไปฌๅœจใ€‚/ ไธ๏ผŒๅฎƒไปฌไธๅœจใ€‚) \ No newline at end of file diff --git a/content/chapters/sbs-2.json b/content/chapters/sbs-2.json new file mode 100644 index 0000000..25c2df2 --- /dev/null +++ b/content/chapters/sbs-2.json @@ -0,0 +1,692 @@ +{ + "id": "sbs-2", + "book_id": "sbs", + "name": "Locations & Classrooms", + "description": "Side by Side Level 2 - Classroom objects, home locations, and where questions", + "difficulty": "beginner", + "language": "en-US", + "chapter_number": "2", + "metadata": { + "version": "1.0", + "created": "2025-10-18", + "updated": "2025-10-18", + "source": "Side by Side English Learning Series", + "target_level": "beginner", + "estimated_hours": 15, + "prerequisites": ["sbs-1"], + "learning_objectives": [ + "Master classroom object vocabulary", + "Learn home and location vocabulary", + "Practice 'Where' questions with to be", + "Understand subject pronouns and contractions", + "Learn greetings and basic conversations" + ], + "content_tags": ["vocabulary", "locations", "classroom", "beginner-english", "where-questions"], + "completion_criteria": { + "vocabulary_mastery": 80, + "quiz_score": 75, + "games_completed": 3 + } + }, + "vocabulary": { + "pen": { "user_language": "้’ข็ฌ”", "type": "noun", "pronunciation": "/pen/" }, + "book": { "user_language": "ไนฆ", "type": "noun", "pronunciation": "/bสŠk/" }, + "pencil": { "user_language": "้“…็ฌ”", "type": "noun", "pronunciation": "/หˆpensษ™l/" }, + "notebook": { "user_language": "็ฌ”่ฎฐๆœฌ", "type": "noun", "pronunciation": "/หˆnoสŠtbสŠk/" }, + "bookshelf": { "user_language": "ไนฆๆžถ", "type": "noun", "pronunciation": "/หˆbสŠkสƒelf/" }, + "globe": { "user_language": "ๅœฐ็ƒไปช๏ผ›ๅœฐ็ƒ๏ผ›ไธ–็•Œ", "type": "noun", "pronunciation": "/ษกloสŠb/" }, + "map": { "user_language": "ๅœฐๅ›พ", "type": "noun", "pronunciation": "/mรฆp/" }, + "board": { "user_language": "้ป‘ๆฟ", "type": "noun", "pronunciation": "/bษ”หrd/" }, + "wall": { "user_language": "ๅข™", "type": "noun", "pronunciation": "/wษ”หl/" }, + "clock": { "user_language": "ๆ—ถ้’Ÿ๏ผŒ้’Ÿ", "type": "noun", "pronunciation": "/klษ‘หk/" }, + "bulletin board": { "user_language": "ๅธƒๅ‘Šๆ ๏ผŒๅ…ฌๅ‘Šๆฟ", "type": "noun", "pronunciation": "/หˆbสŠlษ™tษชn bษ”หrd/" }, + "computer": { "user_language": "่ฎก็ฎ—ๆœบ๏ผŒ็”ต่„‘", "type": "noun", "pronunciation": "/kษ™mหˆpjuหtษ™r/" }, + "table": { "user_language": "ๆกŒๅญ", "type": "noun", "pronunciation": "/หˆteษชbษ™l/" }, + "chair": { "user_language": "ๆค…ๅญ", "type": "noun", "pronunciation": "/tสƒer/" }, + "ruler": { "user_language": "ๅฐบๅญ", "type": "noun", "pronunciation": "/หˆruหlษ™r/" }, + "desk": { "user_language": "ไนฆๆกŒ๏ผ›ๅŠžๅ…ฌๆกŒ", "type": "noun", "pronunciation": "/desk/" }, + "dictionary": { "user_language": "่ฏๅ…ธ๏ผŒๅญ—ๅ…ธ", "type": "noun", "pronunciation": "/หˆdษชkสƒษ™neri/" }, + "living room": { "user_language": "ๅฎขๅŽ…๏ผ›่ตทๅฑ…ๅฎค", "type": "noun", "pronunciation": "/หˆlษชvษชล‹ ruหm/" }, + "dining room": { "user_language": "้คๅŽ…๏ผ›้ฅญๅŽ…", "type": "noun", "pronunciation": "/หˆdaษชnษชล‹ ruหm/" }, + "kitchen": { "user_language": "ๅŽจๆˆฟ", "type": "noun", "pronunciation": "/หˆkษชtสƒษชn/" }, + "bedroom": { "user_language": "ๅงๅฎค", "type": "noun", "pronunciation": "/หˆbedruหm/" }, + "bathroom": { "user_language": "ๆตดๅฎค๏ผ›ๆด—ๆ‰‹้—ด", "type": "noun", "pronunciation": "/หˆbรฆฮธruหm/" }, + "attic": { "user_language": "้˜ๆฅผ๏ผŒ้กถๆฅผ", "type": "noun", "pronunciation": "/หˆรฆtษชk/" }, + "yard": { "user_language": "้™ขๅญ๏ผ›ๅœบๅœฐ", "type": "noun", "pronunciation": "/jษ‘หrd/" }, + "garage": { "user_language": "่ฝฆๅบ“", "type": "noun", "pronunciation": "/ษกษ™หˆrษ‘หส’/" }, + "basement": { "user_language": "ๅœฐไธ‹ๅฎค", "type": "noun", "pronunciation": "/หˆbeษชsmษ™nt/" }, + "bank": { "user_language": "้“ถ่กŒ", "type": "noun", "pronunciation": "/bรฆล‹k/" }, + "hospital": { "user_language": "ๅŒป้™ข", "type": "noun", "pronunciation": "/หˆhษ‘หspษชtl/" }, + "library": { "user_language": "ๅ›พไนฆ้ฆ†", "type": "noun", "pronunciation": "/หˆlaษชbreri/" }, + "movie theater": { "user_language": "็”ตๅฝฑ้™ข", "type": "noun", "pronunciation": "/หˆmuหvi หˆฮธiหษ™tษ™r/" }, + "park": { "user_language": "ๅ…ฌๅ›ญ", "type": "noun", "pronunciation": "/pษ‘หrk/" }, + "post office": { "user_language": "้‚ฎๅฑ€", "type": "noun", "pronunciation": "/poสŠst หˆษ”หfษชs/" }, + "restaurant": { "user_language": "้ค้ฆ†๏ผ›้ฅญๅบ—", "type": "noun", "pronunciation": "/หˆrestrษ‘หnt/" }, + "supermarket": { "user_language": "่ถ…ๅธ‚", "type": "noun", "pronunciation": "/หˆsuหpษ™rmษ‘หrkษชt/" }, + "zoo": { "user_language": "ๅŠจ็‰ฉๅ›ญ", "type": "noun", "pronunciation": "/zuห/" }, + "absent": { "user_language": "็ผบๅธญ็š„", "type": "adjective", "pronunciation": "/หˆรฆbsษ™nt/" }, + "dentist": { "user_language": "็‰™็ง‘ๅŒป็”Ÿ", "type": "noun", "pronunciation": "/หˆdentษชst/" }, + "social security office": { "user_language": "็คพไผšไฟ้šœๅŠžๅ…ฌๅฎค", "type": "noun", "pronunciation": "/หˆsoสŠสƒษ™l sษชหˆkjสŠrษ™ti หˆษ”หfษชs/" }, + "shame": { "user_language": "็พž่€ป๏ผŒ็พžๆ„ง๏ผ›ๆ†พไบ‹", "type": "noun", "pronunciation": "/สƒeษชm/" }, + "except": { "user_language": "้™คไบ†", "type": "preposition", "pronunciation": "/ษชkหˆsept/" }, + "everybody": { "user_language": "ๆฏไธชไบบ๏ผŒๆ‰€ๆœ‰ไบบ", "type": "pronoun", "pronunciation": "/หˆevribษ‘หdi/" }, + "interesting": { "user_language": "ๆœ‰่ถฃ็š„๏ผŒๆœ‰ๆ„ๆ€็š„", "type": "adjective", "pronunciation": "/หˆษชntrษ™stษชล‹/" }, + "different": { "user_language": "ไธๅŒ็š„", "type": "adjective", "pronunciation": "/หˆdษชfrษ™nt/" }, + "countries": { "user_language": "ๅ›ฝๅฎถ๏ผˆๅคๆ•ฐ๏ผ‰", "type": "noun", "pronunciation": "/หˆkสŒntriz/" }, + "Chinese": { "user_language": "ไธญๅ›ฝ็š„๏ผ›ไธญๅ›ฝไบบ", "type": "adjective/noun", "pronunciation": "/tสƒaษชหˆniหz/" }, + "Japanese": { "user_language": "ๆ—ฅๆœฌ็š„๏ผ›ๆ—ฅๆœฌไบบ", "type": "adjective/noun", "pronunciation": "/หŒdส’รฆpษ™หˆniหz/" }, + "Korean": { "user_language": "้Ÿฉๅ›ฝ็š„๏ผ›้Ÿฉๅ›ฝไบบ", "type": "adjective/noun", "pronunciation": "/kษ™หˆriษ™n/" }, + "Italian": { "user_language": "ๆ„ๅคงๅˆฉ็š„๏ผ›ๆ„ๅคงๅˆฉไบบ", "type": "adjective/noun", "pronunciation": "/ษชหˆtรฆljษ™n/" }, + "Mexican": { "user_language": "ๅขจ่ฅฟๅ“ฅ็š„๏ผ›ๅขจ่ฅฟๅ“ฅไบบ", "type": "adjective/noun", "pronunciation": "/หˆmeksษชkษ™n/" }, + "Greek": { "user_language": "ๅธŒ่…Š็š„๏ผ›ๅธŒ่…Šไบบ", "type": "adjective/noun", "pronunciation": "/ษกriหk/" }, + "Puerto Rican": { "user_language": "ๆณขๅคš้ปŽๅ„็š„๏ผ›ๆณขๅคš้ปŽๅ„ไบบ", "type": "adjective/noun", "pronunciation": "/หŒpwertษ™ หˆriหkษ™n/" } + }, + "phrases": { + "Where's the book?": { "user_language": "ไนฆๅœจๅ“ช้‡Œ๏ผŸ", "context": "location-question", "pronunciation": "/werz รฐษ™ bสŠk/" }, + "It's on the desk": { "user_language": "ๅฎƒๅœจๆกŒๅญไธŠใ€‚", "context": "location-answer", "pronunciation": "/ษชts ษ‘หn รฐษ™ desk/" }, + "Where are you?": { "user_language": "ไฝ ๅœจๅ“ช้‡Œ๏ผŸ", "context": "location-question", "pronunciation": "/wer ษ‘หr juห/" }, + "I'm in the kitchen": { "user_language": "ๆˆ‘ๅœจๅŽจๆˆฟใ€‚", "context": "location-answer", "pronunciation": "/aษชm ษชn รฐษ™ หˆkษชtสƒษชn/" }, + "We're in the living room": { "user_language": "ๆˆ‘ไปฌๅœจๅฎขๅŽ…ใ€‚", "context": "location-answer", "pronunciation": "/wir ษชn รฐษ™ หˆlษชvษชล‹ ruหm/" }, + "They're in the yard": { "user_language": "ไป–ไปฌๅœจ้™ขๅญ้‡Œใ€‚", "context": "location-answer", "pronunciation": "/รฐer ษชn รฐษ™ jษ‘หrd/" }, + "He's in the living room": { "user_language": "ไป–ๅœจๅฎขๅŽ…ใ€‚", "context": "location-answer", "pronunciation": "/hiหz ษชn รฐษ™ หˆlษชvษชล‹ ruหm/" }, + "She's in the bedroom": { "user_language": "ๅฅนๅœจๅงๅฎคใ€‚", "context": "location-answer", "pronunciation": "/สƒiหz ษชn รฐษ™ หˆbedruหm/" }, + "It's in the garage": { "user_language": "ๅฎƒๅœจ่ฝฆๅบ“้‡Œใ€‚", "context": "location-answer", "pronunciation": "/ษชts ษชn รฐษ™ ษกษ™หˆrษ‘หส’/" }, + "Hi. How are you?": { "user_language": "ๅ—จ๏ผŒไฝ ๅฅฝๅ—๏ผŸ", "context": "greeting", "pronunciation": "/haษช haสŠ ษ‘หr juห/" }, + "Fine. And you?": { "user_language": "ๅพˆๅฅฝใ€‚ไฝ ๅ‘ข๏ผŸ", "context": "greeting-response", "pronunciation": "/faษชn รฆnd juห/" }, + "Fine, thanks": { "user_language": "ๅพˆๅฅฝ๏ผŒ่ฐข่ฐข", "context": "greeting-response", "pronunciation": "/faษชn ฮธรฆล‹ks/" }, + "What a shame!": { "user_language": "็œŸ้—ๆ†พ๏ผ", "context": "expression", "pronunciation": "/wสŒt ษ™ สƒeษชm/" }, + "Everybody except me": { "user_language": "้™คไบ†ๆˆ‘ไน‹ๅค–็š„ๆ‰€ๆœ‰ไบบ", "context": "expression", "pronunciation": "/หˆevribษ‘หdi ษชkหˆsept miห/" } + }, + "dialogs": { + "classroom_location": { + "title": "Where Is It?", + "participants": ["Student A", "Student B"], + "lines": [ + { "speaker": "Student A", "text": "Where's the book?", "user_language": "ไนฆๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Student B", "text": "It's on the desk.", "user_language": "ๅฎƒๅœจๆกŒๅญไธŠใ€‚" }, + { "speaker": "Student A", "text": "Where's the map?", "user_language": "ๅœฐๅ›พๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Student B", "text": "It's on the wall.", "user_language": "ๅฎƒๅœจๅข™ไธŠใ€‚" }, + { "speaker": "Student A", "text": "Where's the computer?", "user_language": "็”ต่„‘ๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Student B", "text": "It's on the table.", "user_language": "ๅฎƒๅœจๆกŒๅญไธŠใ€‚" } + ] + }, + "home_location": { + "title": "Where Are You?", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "Where are you?", "user_language": "ไฝ ๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "I'm in the kitchen.", "user_language": "ๆˆ‘ๅœจๅŽจๆˆฟใ€‚" }, + { "speaker": "Person A", "text": "Where are you?", "user_language": "ไฝ ไปฌๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "We're in the living room.", "user_language": "ๆˆ‘ไปฌๅœจๅฎขๅŽ…ใ€‚" }, + { "speaker": "Person A", "text": "Where are Mr. and Mrs. Jones?", "user_language": "็ผๆ–ฏๅ…ˆ็”Ÿๅ’Œๅคชๅคชๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "They're in the yard.", "user_language": "ไป–ไปฌๅœจ้™ขๅญ้‡Œใ€‚" } + ] + }, + "people_location": { + "title": "Where's Bob?", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "Where's Bob?", "user_language": "้ฒๅ‹ƒๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "He's in the living room.", "user_language": "ไป–ๅœจๅฎขๅŽ…ใ€‚" }, + { "speaker": "Person A", "text": "Where's Mary?", "user_language": "็Ž›ไธฝๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "She's in the bedroom.", "user_language": "ๅฅนๅœจๅงๅฎคใ€‚" }, + { "speaker": "Person A", "text": "Where's the car?", "user_language": "่ฝฆๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "It's in the garage.", "user_language": "ๅฎƒๅœจ่ฝฆๅบ“้‡Œใ€‚" } + ] + }, + "greeting": { + "title": "Greeting People", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "Hi. How are you?", "user_language": "ๅ—จ๏ผŒไฝ ๅฅฝๅ—๏ผŸ" }, + { "speaker": "Person B", "text": "Fine. And you?", "user_language": "ๅพˆๅฅฝใ€‚ไฝ ๅ‘ข๏ผŸ" }, + { "speaker": "Person A", "text": "Fine, thanks.", "user_language": "ๅพˆๅฅฝ๏ผŒ่ฐข่ฐขใ€‚" } + ] + } + }, + "texts": [ + { + "title": "The Students in My English Class", + "original_language": "The students in my English class are very interesting. Henry is Chinese. He's from Shanghai. Linda is Puerto Rican. She's from San Juan. Mr. and Mrs. Kim are Korean. They're from Seoul. George is Greek. He's from Athens. Carla is Italian. She's from Rome. Mr. and Mrs. Sato are Japanese. They're from Tokyo. My friend Maria and I are Mexican. We're from Mexico City. Yes, the students in my English class are very interesting. We're from many different countries . . . and we're friends.", + "user_language": "ๆˆ‘่‹ฑ่ฏญ็ญ็š„ๅญฆ็”Ÿ้žๅธธๆœ‰่ถฃใ€‚ไบจๅˆฉๆ˜ฏไธญๅ›ฝไบบใ€‚ไป–ๆฅ่‡ชไธŠๆตทใ€‚็ณ่พพๆ˜ฏๆณขๅคš้ปŽๅ„ไบบใ€‚ๅฅนๆฅ่‡ชๅœฃ่ƒกๅฎ‰ใ€‚้‡‘ๅ…ˆ็”Ÿๅ’Œๅคชๅคชๆ˜ฏ้Ÿฉๅ›ฝไบบใ€‚ไป–ไปฌๆฅ่‡ช้ฆ–ๅฐ”ใ€‚ไน”ๆฒปๆ˜ฏๅธŒ่…Šไบบใ€‚ไป–ๆฅ่‡ช้›…ๅ…ธใ€‚ๅกๆ‹‰ๆ˜ฏๆ„ๅคงๅˆฉไบบใ€‚ๅฅนๆฅ่‡ช็ฝ—้ฉฌใ€‚ไฝ่—คๅ…ˆ็”Ÿๅ’Œๅคชๅคชๆ˜ฏๆ—ฅๆœฌไบบใ€‚ไป–ไปฌๆฅ่‡ชไธœไบฌใ€‚ๆˆ‘็š„ๆœ‹ๅ‹็Ž›ไธฝไบšๅ’Œๆˆ‘ๆ˜ฏๅขจ่ฅฟๅ“ฅไบบใ€‚ๆˆ‘ไปฌๆฅ่‡ชๅขจ่ฅฟๅ“ฅๅŸŽใ€‚ๆ˜ฏ็š„๏ผŒๆˆ‘่‹ฑ่ฏญ็ญ็š„ๅญฆ็”Ÿ้žๅธธๆœ‰่ถฃใ€‚ๆˆ‘ไปฌๆฅ่‡ช่ฎธๅคšไธๅŒ็š„ๅ›ฝๅฎถโ€ฆโ€ฆ่€Œไธ”ๆˆ‘ไปฌๆ˜ฏๆœ‹ๅ‹ใ€‚" + }, + { + "title": "All the Students in My English Class Are Absent Today", + "original_language": "All the students in my English class are absent today. George is absent. He's in the hospital. Maria is absent. She's at the dentist. Mr. and Mrs. Sato are absent. They're at the social security office. Even our English teacher is absent. He's home in bed! What a shame! Everybody in my English class is absent today. Everybody except me.", + "user_language": "ไปŠๅคฉๆˆ‘่‹ฑ่ฏญ็ญ็š„ๆ‰€ๆœ‰ๅญฆ็”Ÿ้ƒฝ็ผบๅธญไบ†ใ€‚ไน”ๆฒป็ผบๅธญไบ†ใ€‚ไป–ๅœจๅŒป้™ขใ€‚็Ž›ไธฝไบš็ผบๅธญไบ†ใ€‚ๅฅนๅœจ็œ‹็‰™ๅŒปใ€‚ไฝ่—คๅ…ˆ็”Ÿๅ’Œๅคชๅคช็ผบๅธญไบ†ใ€‚ไป–ไปฌๅœจ็คพไผšไฟ้šœๅŠžๅ…ฌๅฎคใ€‚็”š่‡ณๆˆ‘ไปฌ็š„่‹ฑ่ฏญ่€ๅธˆไนŸ็ผบๅธญไบ†ใ€‚ไป–ๅœจๅฎถๅงๅบŠไผ‘ๆฏ๏ผ็œŸ้—ๆ†พ๏ผไปŠๅคฉๆˆ‘่‹ฑ่ฏญ็ญ็š„ๆ‰€ๆœ‰ไบบ้ƒฝ็ผบๅธญไบ†ใ€‚้™คไบ†ๆˆ‘ไน‹ๅค–็š„ๆ‰€ๆœ‰ไบบใ€‚" + } + ], + "grammar": { + "where-questions": { + "title": "Where Questions with To Be", + "explanation": "Use 'Where' to ask about location. Combine with am/is/are depending on the subject.", + "examples": [ + { + "english": "Where is the book? / Where's the book?", + "translation": "ไนฆๅœจๅ“ช้‡Œ๏ผŸ", + "explanation": "Use 'is' for singular subjects (the book). 'Where's' is the contraction." + }, + { + "english": "Where are you?", + "translation": "ไฝ ๅœจๅ“ช้‡Œ๏ผŸ", + "explanation": "Use 'are' with 'you' (singular or plural)" + }, + { + "english": "Where are they?", + "translation": "ไป–ไปฌๅœจๅ“ช้‡Œ๏ผŸ", + "explanation": "Use 'are' for plural subjects (they)" + } + ] + }, + "subject-pronouns": { + "title": "Subject Pronouns with To Be", + "explanation": "Subject pronouns replace names. Use the correct form of 'be' with each pronoun.", + "examples": [ + { + "english": "I am / I'm", + "translation": "ๆˆ‘ๆ˜ฏ / ๆˆ‘ๅœจ", + "explanation": "Use 'am' only with 'I'. Contraction: I'm" + }, + { + "english": "He is / He's, She is / She's, It is / It's", + "translation": "ไป–ๆ˜ฏ/ไป–ๅœจ๏ผŒๅฅนๆ˜ฏ/ๅฅนๅœจ๏ผŒๅฎƒๆ˜ฏ/ๅฎƒๅœจ", + "explanation": "Use 'is' with he, she, it. Contractions: He's, She's, It's" + }, + { + "english": "We are / We're, You are / You're, They are / They're", + "translation": "ๆˆ‘ไปฌๆ˜ฏ/ๆˆ‘ไปฌๅœจ๏ผŒไฝ ไปฌๆ˜ฏ/ไฝ ไปฌๅœจ๏ผŒไป–ไปฌๆ˜ฏ/ไป–ไปฌๅœจ", + "explanation": "Use 'are' with we, you, they. Contractions: We're, You're, They're" + } + ] + }, + "prepositions-location": { + "title": "Prepositions of Location", + "explanation": "Use 'on' for surfaces, 'in' for enclosed spaces, 'at' for specific locations.", + "examples": [ + { + "english": "The book is on the desk", + "translation": "ไนฆๅœจๆกŒๅญไธŠ", + "explanation": "Use 'on' for objects resting on a surface" + }, + { + "english": "I'm in the kitchen", + "translation": "ๆˆ‘ๅœจๅŽจๆˆฟ", + "explanation": "Use 'in' for being inside a room or enclosed space" + }, + { + "english": "She's at the dentist", + "translation": "ๅฅนๅœจ็œ‹็‰™ๅŒป", + "explanation": "Use 'at' for specific locations or places you visit" + } + ] + }, + "nationalities": { + "title": "Nationalities and Countries", + "explanation": "Use nationality adjectives to describe where people are from. They can be both adjectives and nouns.", + "examples": [ + { + "english": "Henry is Chinese. He's from Shanghai.", + "translation": "ไบจๅˆฉๆ˜ฏไธญๅ›ฝไบบใ€‚ไป–ๆฅ่‡ชไธŠๆตทใ€‚", + "explanation": "'Chinese' can be an adjective or noun. Use 'from' to indicate city/country of origin." + }, + { + "english": "We're from many different countries", + "translation": "ๆˆ‘ไปฌๆฅ่‡ช่ฎธๅคšไธๅŒ็š„ๅ›ฝๅฎถ", + "explanation": "Use 'from' + country/city to show origin" + } + ] + } + }, + "fillInBlanks": [ + { + "sentence": "Where ___ the book?", + "options": ["is", "are", "am", "be"], + "correctAnswer": "is", + "explanation": "Use 'is' with singular subjects like 'the book'", + "grammarFocus": "where-questions" + }, + { + "sentence": "It's ___ the desk", + "options": ["on", "in", "at", "to"], + "correctAnswer": "on", + "explanation": "Use 'on' for objects resting on a surface", + "grammarFocus": "prepositions-location" + }, + { + "sentence": "Where ___ you?", + "options": ["are", "is", "am", "be"], + "correctAnswer": "are", + "explanation": "Always use 'are' with 'you'", + "grammarFocus": "where-questions" + }, + { + "sentence": "I'm ___ the kitchen", + "options": ["in", "on", "at", "to"], + "correctAnswer": "in", + "explanation": "Use 'in' for being inside a room", + "grammarFocus": "prepositions-location" + }, + { + "sentence": "They're ___ the yard", + "options": ["in", "on", "at", "to"], + "correctAnswer": "in", + "explanation": "Use 'in' for being inside an enclosed space like a yard", + "grammarFocus": "prepositions-location" + }, + { + "sentence": "The ___ is on the wall", + "options": ["map", "computer", "book", "pen"], + "correctAnswer": "map", + "explanation": "Maps are typically hung on walls", + "grammarFocus": "classroom-vocabulary" + }, + { + "sentence": "Where's the ___? It's in the garage", + "options": ["car", "book", "pen", "clock"], + "correctAnswer": "car", + "explanation": "Cars are kept in garages", + "grammarFocus": "location-vocabulary" + }, + { + "sentence": "Henry is ___. He's from Shanghai", + "options": ["Chinese", "Korean", "Japanese", "Greek"], + "correctAnswer": "Chinese", + "explanation": "Shanghai is a city in China, so Henry is Chinese", + "grammarFocus": "nationalities" + }, + { + "sentence": "We're from many different ___", + "options": ["countries", "country", "city", "cities"], + "correctAnswer": "countries", + "explanation": "Use plural 'countries' after 'many'", + "grammarFocus": "nationalities" + }, + { + "sentence": "All the students are ___ today", + "options": ["absent", "interesting", "different", "convenient"], + "correctAnswer": "absent", + "explanation": "'Absent' means not present or not in class", + "grammarFocus": "vocabulary" + }, + { + "sentence": "She's at the ___", + "options": ["dentist", "bedroom", "kitchen", "yard"], + "correctAnswer": "dentist", + "explanation": "Use 'at the dentist' to mean visiting the dentist's office", + "grammarFocus": "location-vocabulary" + }, + { + "sentence": "The ___ is on the desk", + "options": ["pen", "map", "clock", "globe"], + "correctAnswer": "pen", + "explanation": "Pens are commonly placed on desks", + "grammarFocus": "classroom-vocabulary" + }, + { + "sentence": "___ a shame! Everybody is absent", + "options": ["What", "How", "Where", "Who"], + "correctAnswer": "What", + "explanation": "Use 'What a shame!' as an expression of disappointment", + "grammarFocus": "expressions" + }, + { + "sentence": "Everybody except ___", + "options": ["me", "I", "my", "mine"], + "correctAnswer": "me", + "explanation": "Use object pronoun 'me' after 'except'", + "grammarFocus": "pronouns" + }, + { + "sentence": "The students in my class are very ___", + "options": ["interesting", "interested", "interest", "interests"], + "correctAnswer": "interesting", + "explanation": "Use 'interesting' to describe something/someone that creates interest", + "grammarFocus": "adjectives" + } + ], + "corrections": [ + { + "correct": "Where is the book?", + "incorrect": "Where the book is?", + "explanation": "In questions, put the verb (is) before the subject (the book) after the question word", + "grammarFocus": "question-formation" + }, + { + "correct": "It's on the desk", + "incorrect": "It on the desk", + "explanation": "Always include the verb 'is' (or contraction 'It's') in complete sentences", + "grammarFocus": "to-be" + }, + { + "correct": "I'm in the kitchen", + "incorrect": "I'm in kitchen", + "explanation": "Use 'the' before specific room names", + "grammarFocus": "articles" + }, + { + "correct": "Where are you?", + "incorrect": "Where is you?", + "explanation": "Always use 'are' with 'you', never 'is'", + "grammarFocus": "subject-verb-agreement" + }, + { + "correct": "They're in the yard", + "incorrect": "They in the yard", + "explanation": "Include the verb 'are' (or contraction 'They're')", + "grammarFocus": "to-be" + }, + { + "correct": "We're from many different countries", + "incorrect": "We're from many different country", + "explanation": "Use plural 'countries' after 'many'", + "grammarFocus": "plurals" + }, + { + "correct": "Henry is Chinese", + "incorrect": "Henry is China", + "explanation": "Use the nationality adjective 'Chinese', not the country name 'China'", + "grammarFocus": "nationalities" + }, + { + "correct": "He's from Shanghai", + "incorrect": "He from Shanghai", + "explanation": "Include the verb 'is' (or contraction 'He's') before 'from'", + "grammarFocus": "to-be" + }, + { + "correct": "What a shame!", + "incorrect": "What shame!", + "explanation": "Use the article 'a' in this expression: 'What a shame!'", + "grammarFocus": "expressions" + }, + { + "correct": "Everybody except me", + "incorrect": "Everybody except I", + "explanation": "Use the object pronoun 'me' after the preposition 'except'", + "grammarFocus": "pronouns" + } + ], + "exercises": { + "classroom_objects": { + "type": "location_practice", + "instructions": "Ask and answer: Where's the [object]?", + "items": [ + { "question": "Where's the pen?", "answer": "It's on the desk.", "user_language_q": "้’ข็ฌ”ๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๆกŒๅญไธŠใ€‚" }, + { "question": "Where's the board?", "answer": "It's on the wall.", "user_language_q": "้ป‘ๆฟๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๅข™ไธŠใ€‚" }, + { "question": "Where's the globe?", "answer": "It's on the table.", "user_language_q": "ๅœฐ็ƒไปชๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๆกŒๅญไธŠใ€‚" }, + { "question": "Where's the ruler?", "answer": "It's on the desk.", "user_language_q": "ๅฐบๅญๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๆกŒๅญไธŠใ€‚" }, + { "question": "Where's the clock?", "answer": "It's on the wall.", "user_language_q": "ๆ—ถ้’Ÿๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๅข™ไธŠใ€‚" } + ] + }, + "people_locations": { + "type": "location_practice", + "instructions": "Ask and answer: Where are [people]?", + "items": [ + { "question": "Where's Albert?", "location": "restaurant", "answer": "He's at the restaurant.", "user_language_q": "้˜ฟๅฐ”ไผฏ็‰นๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ไป–ๅœจ้ค้ฆ†ใ€‚" }, + { "question": "Where's Carmen?", "location": "bank", "answer": "She's at the bank.", "user_language_q": "ๅก้—จๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฅนๅœจ้“ถ่กŒใ€‚" }, + { "question": "Where are Walter and Mary?", "location": "supermarket", "answer": "They're at the supermarket.", "user_language_q": "ๆฒƒๅฐ”็‰นๅ’Œ็Ž›ไธฝๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ไป–ไปฌๅœจ่ถ…ๅธ‚ใ€‚" }, + { "question": "Where's Kate?", "location": "movie theater", "answer": "She's at the movie theater.", "user_language_q": "ๅ‡ฏ็‰นๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฅนๅœจ็”ตๅฝฑ้™ขใ€‚" }, + { "question": "Where are Mr. and Mrs. Lee?", "location": "post office", "answer": "They're at the post office.", "user_language_q": "ๆŽๅ…ˆ็”Ÿๅ’Œๅคชๅคชๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ไป–ไปฌๅœจ้‚ฎๅฑ€ใ€‚" } + ] + }, + "reading_check": { + "type": "true_false", + "instructions": "The Students in My English Class - True or False?", + "items": [ + { "statement": "Linda is Korean", "answer": false, "user_language": "็ณ่พพๆ˜ฏ้Ÿฉๅ›ฝไบบ" }, + { "statement": "George is Greek", "answer": true, "user_language": "ไน”ๆฒปๆ˜ฏๅธŒ่…Šไบบ" }, + { "statement": "Henry is from Mexico City", "answer": false, "user_language": "ไบจๅˆฉๆฅ่‡ชๅขจ่ฅฟๅ“ฅๅŸŽ" }, + { "statement": "Mr. Kim is from Seoul", "answer": true, "user_language": "้‡‘ๅ…ˆ็”Ÿๆฅ่‡ช้ฆ–ๅฐ”" }, + { "statement": "Carla is Chinese", "answer": false, "user_language": "ๅกๆ‹‰ๆ˜ฏไธญๅ›ฝไบบ" }, + { "statement": "The students in the class are from many countries", "answer": true, "user_language": "็ญไธŠ็š„ๅญฆ็”Ÿๆฅ่‡ช่ฎธๅคšๅ›ฝๅฎถ" } + ] + }, + "reading_check_2": { + "type": "true_false", + "instructions": "All the Students Are Absent - True or False?", + "items": [ + { "statement": "George is absent", "answer": true, "user_language": "ไน”ๆฒป็ผบๅธญไบ†" }, + { "statement": "Maria is absent", "answer": true, "user_language": "็Ž›ไธฝไบš็ผบๅธญไบ†" }, + { "statement": "Mr. and Mrs. Sato are absent", "answer": true, "user_language": "ไฝ่—คๅ…ˆ็”Ÿๅ’Œๅคชๅคช็ผบๅธญไบ†" }, + { "statement": "The English teacher is absent", "answer": true, "user_language": "่‹ฑ่ฏญ่€ๅธˆ็ผบๅธญไบ†" } + ] + } + }, + "thematic_questions": { + "classroom_objects": [ + { + "id": "q1", + "question": "Where's the book?", + "question_user_language": "ไนฆๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's on the desk", + "The book is on the desk", + "On the desk" + ], + "theme": "classroom_objects" + }, + { + "id": "q2", + "question": "Where's the map?", + "question_user_language": "ๅœฐๅ›พๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's on the wall", + "The map is on the wall", + "On the wall" + ], + "theme": "classroom_objects" + }, + { + "id": "q3", + "question": "Where's the clock?", + "question_user_language": "ๆ—ถ้’Ÿๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's on the wall", + "The clock is on the wall", + "On the wall" + ], + "theme": "classroom_objects" + }, + { + "id": "q4", + "question": "Where's the computer?", + "question_user_language": "็”ต่„‘ๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's on the table", + "The computer is on the table", + "On the table" + ], + "theme": "classroom_objects" + }, + { + "id": "q5", + "question": "Where's the globe?", + "question_user_language": "ๅœฐ็ƒไปชๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's on the desk", + "The globe is on the desk", + "On the bookshelf" + ], + "theme": "classroom_objects" + } + ], + "rooms_and_locations": [ + { + "id": "q6", + "question": "Where are you?", + "question_user_language": "ไฝ ๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "I'm in the kitchen", + "I'm in the living room", + "I'm in the bedroom" + ], + "theme": "rooms_and_locations" + }, + { + "id": "q7", + "question": "Where are Mr. and Mrs. Jones?", + "question_user_language": "็ผๆ–ฏๅ…ˆ็”Ÿๅ’Œๅคชๅคชๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're in the yard", + "They're in the living room", + "They're in the dining room" + ], + "theme": "rooms_and_locations" + }, + { + "id": "q8", + "question": "Where are you and your friend?", + "question_user_language": "ไฝ ๅ’Œไฝ ็š„ๆœ‹ๅ‹ๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "We're in the park", + "We're in the library", + "We're at home" + ], + "theme": "rooms_and_locations" + }, + { + "id": "q9", + "question": "Where's the car?", + "question_user_language": "่ฝฆๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's in the garage", + "The car is in the garage", + "In the garage" + ], + "theme": "rooms_and_locations" + }, + { + "id": "q10", + "question": "Where are the students?", + "question_user_language": "ๅญฆ็”Ÿไปฌๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're in the classroom", + "They're at school", + "They're in the library" + ], + "theme": "rooms_and_locations" + } + ], + "people_locations": [ + { + "id": "q11", + "question": "Where's Bob?", + "question_user_language": "้ฒๅ‹ƒๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's in the living room", + "He's at home", + "He's in the kitchen" + ], + "theme": "people_locations" + }, + { + "id": "q12", + "question": "Where's Mary?", + "question_user_language": "็Ž›ไธฝๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's in the bedroom", + "She's at the library", + "She's in the bathroom" + ], + "theme": "people_locations" + }, + { + "id": "q13", + "question": "Where's your English teacher?", + "question_user_language": "ไฝ ็š„่‹ฑ่ฏญ่€ๅธˆๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's in the hospital", + "She's at home", + "He's in bed" + ], + "theme": "people_locations" + }, + { + "id": "q14", + "question": "Where are Walter and Mary?", + "question_user_language": "ๆฒƒๅฐ”็‰นๅ’Œ็Ž›ไธฝๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're at the supermarket", + "They're at the bank", + "They're in the park" + ], + "theme": "people_locations" + } + ], + "nationalities": [ + { + "id": "q15", + "question": "Where's Henry from?", + "question_user_language": "ไบจๅˆฉๆฅ่‡ชๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's from Shanghai", + "He's from China", + "Shanghai" + ], + "theme": "nationalities" + }, + { + "id": "q16", + "question": "What nationality is George?", + "question_user_language": "ไน”ๆฒปๆ˜ฏไป€ไนˆๅ›ฝ็ฑ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's Greek", + "George is Greek", + "Greek" + ], + "theme": "nationalities" + }, + { + "id": "q17", + "question": "Where are Mr. and Mrs. Kim from?", + "question_user_language": "้‡‘ๅ…ˆ็”Ÿๅ’Œๅคชๅคชๆฅ่‡ชๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're from Seoul", + "They're from Korea", + "Seoul" + ], + "theme": "nationalities" + }, + { + "id": "q18", + "question": "What nationality is Carla?", + "question_user_language": "ๅกๆ‹‰ๆ˜ฏไป€ไนˆๅ›ฝ็ฑ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's Italian", + "Carla is Italian", + "Italian" + ], + "theme": "nationalities" + } + ] + }, + "statistics": { + "vocabulary_count": 49, + "phrases_count": 14, + "dialogs_count": 4, + "texts_count": 2, + "exercises_count": 4, + "fillInBlanks_count": 15, + "corrections_count": 10, + "thematic_questions_count": 18, + "estimated_completion_time": 15 + } +} diff --git a/content/chapters/sbs-3.json b/content/chapters/sbs-3.json new file mode 100644 index 0000000..071c964 --- /dev/null +++ b/content/chapters/sbs-3.json @@ -0,0 +1,757 @@ +{ + "id": "sbs-3", + "book_id": "sbs", + "name": "Present Continuous Tense", + "description": "Side by Side Level 3 - Everyday activities and present continuous tense", + "difficulty": "beginner", + "language": "en-US", + "chapter_number": "3", + "metadata": { + "version": "1.0", + "created": "2025-10-18", + "updated": "2025-10-18", + "source": "Side by Side English Learning Series", + "target_level": "beginner", + "estimated_hours": 18, + "prerequisites": ["sbs-2"], + "learning_objectives": [ + "Master present continuous tense formation", + "Learn everyday activity vocabulary", + "Practice What are you doing questions", + "Understand verb -ing forms", + "Learn to describe ongoing actions" + ], + "content_tags": ["grammar", "present-continuous", "activities", "beginner-english", "daily-activities"], + "completion_criteria": { + "vocabulary_mastery": 80, + "quiz_score": 75, + "games_completed": 3 + } + }, + "vocabulary": { + "eating": { "user_language": "ๅƒ", "type": "verb-ing", "pronunciation": "/หˆiหtษชล‹/" }, + "drinking": { "user_language": "ๅ–", "type": "verb-ing", "pronunciation": "/หˆdrษชล‹kษชล‹/" }, + "cooking": { "user_language": "ๅš้ฅญ", "type": "verb-ing", "pronunciation": "/หˆkสŠkษชล‹/" }, + "reading": { "user_language": "่ฏป๏ผŒ้˜…่ฏป", "type": "verb-ing", "pronunciation": "/หˆriหdษชล‹/" }, + "studying": { "user_language": "ๅญฆไน ", "type": "verb-ing", "pronunciation": "/หˆstสŒdiษชล‹/" }, + "teaching": { "user_language": "ๆ•™ๅญฆ", "type": "verb-ing", "pronunciation": "/หˆtiหtสƒษชล‹/" }, + "singing": { "user_language": "ๅ”ฑๆญŒ", "type": "verb-ing", "pronunciation": "/หˆsษชล‹ษชล‹/" }, + "sleeping": { "user_language": "็ก่ง‰", "type": "verb-ing", "pronunciation": "/หˆsliหpษชล‹/" }, + "swimming": { "user_language": "ๆธธๆณณ", "type": "verb-ing", "pronunciation": "/หˆswษชmษชล‹/" }, + "planting": { "user_language": "็งๆค", "type": "verb-ing", "pronunciation": "/หˆplรฆntษชล‹/" }, + "watching TV": { "user_language": "็œ‹็”ต่ง†", "type": "verb-ing phrase", "pronunciation": "/หˆwษ‘หtสƒษชล‹ หŒtiหหˆviห/" }, + "listening to music": { "user_language": "ๅฌ้Ÿณไน", "type": "verb-ing phrase", "pronunciation": "/หˆlษชsษ™nษชล‹ tuห หˆmjuหzษชk/" }, + "playing cards": { "user_language": "ๆ‰“็‰Œ", "type": "verb-ing phrase", "pronunciation": "/หˆpleษชษชล‹ kษ‘หrdz/" }, + "playing baseball": { "user_language": "ๆ‰“ๆฃ’็ƒ", "type": "verb-ing phrase", "pronunciation": "/หˆpleษชษชล‹ หˆbeษชsbษ”หl/" }, + "playing the piano": { "user_language": "ๅผน้’ข็ด", "type": "verb-ing phrase", "pronunciation": "/หˆpleษชษชล‹ รฐษ™ piหˆรฆnoสŠ/" }, + "playing the guitar": { "user_language": "ๅผนๅ‰ไป–", "type": "verb-ing phrase", "pronunciation": "/หˆpleษชษชล‹ รฐษ™ ษกษชหˆtษ‘หr/" }, + "breakfast": { "user_language": "ๆ—ฉ้ค", "type": "noun", "pronunciation": "/หˆbrekfษ™st/" }, + "lunch": { "user_language": "ๅˆ้ค", "type": "noun", "pronunciation": "/lสŒntสƒ/" }, + "dinner": { "user_language": "ๆ™š้ค", "type": "noun", "pronunciation": "/หˆdษชnษ™r/" }, + "newspaper": { "user_language": "ๆŠฅ็บธ", "type": "noun", "pronunciation": "/หˆnuหzpeษชpษ™r/" }, + "radio": { "user_language": "ๆ”ถ้Ÿณๆœบ", "type": "noun", "pronunciation": "/หˆreษชdioสŠ/" }, + "lemonade": { "user_language": "ๆŸ ๆชฌๆฐด", "type": "noun", "pronunciation": "/หŒlemษ™หˆneษชd/" }, + "milk": { "user_language": "็‰›ๅฅถ", "type": "noun", "pronunciation": "/mษชlk/" }, + "flowers": { "user_language": "่Šฑ", "type": "noun", "pronunciation": "/หˆflaสŠษ™rz/" }, + "cafeteria": { "user_language": "่‡ชๅŠฉ้คๅŽ…", "type": "noun", "pronunciation": "/หŒkรฆfษ™หˆtษชriษ™/" }, + "mathematics": { "user_language": "ๆ•ฐๅญฆ", "type": "noun", "pronunciation": "/หŒmรฆฮธษ™หˆmรฆtษชks/" }, + "sun": { "user_language": "ๅคช้˜ณ", "type": "noun", "pronunciation": "/sสŒn/" }, + "birds": { "user_language": "้ธŸ", "type": "noun", "pronunciation": "/bษœหrdz/" }, + "shining": { "user_language": "็…ง่€€", "type": "verb-ing", "pronunciation": "/หˆสƒaษชnษชล‹/" }, + "beautiful": { "user_language": "็พŽไธฝ็š„", "type": "adjective", "pronunciation": "/หˆbjuหtษ™fษ™l/" }, + "happy": { "user_language": "ๅฟซไน็š„", "type": "adjective", "pronunciation": "/หˆhรฆpi/" }, + "violin": { "user_language": "ๅฐๆ็ด", "type": "noun", "pronunciation": "/หŒvaษชษ™หˆlษชn/" }, + "clarinet": { "user_language": "ๅ•็ฐง็ฎก", "type": "noun", "pronunciation": "/หŒklรฆrษ™หˆnet/" }, + "trumpet": { "user_language": "ๅฐๅท", "type": "noun", "pronunciation": "/หˆtrสŒmpษชt/" }, + "soccer": { "user_language": "่ถณ็ƒ", "type": "noun", "pronunciation": "/หˆsษ‘หkษ™r/" }, + "tennis": { "user_language": "็ฝ‘็ƒ", "type": "noun", "pronunciation": "/หˆtenษชs/" }, + "basketball": { "user_language": "็ฏฎ็ƒ", "type": "noun", "pronunciation": "/หˆbรฆskษชtbษ”หl/" }, + "chess": { "user_language": "ๅ›ฝ้™…่ฑกๆฃ‹", "type": "noun", "pronunciation": "/tสƒes/" }, + "checkers": { "user_language": "่ทณๆฃ‹", "type": "noun", "pronunciation": "/หˆtสƒekษ™rz/" }, + "nickname": { "user_language": "ๆ˜ต็งฐ", "type": "noun", "pronunciation": "/หˆnษชkneษชm/" }, + "title": { "user_language": "็งฐๅ‘ผ", "type": "noun", "pronunciation": "/หˆtaษชtษ™l/" }, + "keypal": { "user_language": "็ฝ‘ๅ‹", "type": "noun", "pronunciation": "/หˆkiหpรฆl/" }, + "computer": { "user_language": "็”ต่„‘", "type": "noun", "pronunciation": "/kษ™mหˆpjuหtษ™r/" }, + "beach": { "user_language": "ๆตทๆปฉ", "type": "noun", "pronunciation": "/biหtสƒ/" }, + "dog": { "user_language": "็‹—", "type": "noun", "pronunciation": "/dษ”หษก/" } + }, + "phrases": { + "What are you doing?": { "user_language": "ไฝ ๅœจๅšไป€ไนˆ๏ผŸ", "context": "present-continuous-question", "pronunciation": "/wสŒt ษ‘หr juห หˆduหษชล‹/" }, + "I'm reading": { "user_language": "ๆˆ‘ๅœจ่ฏปไนฆ", "context": "present-continuous-answer", "pronunciation": "/aษชm หˆriหdษชล‹/" }, + "We're cooking": { "user_language": "ๆˆ‘ไปฌๅœจๅš้ฅญ", "context": "present-continuous-answer", "pronunciation": "/wษชr หˆkสŠkษชล‹/" }, + "They're studying English": { "user_language": "ไป–ไปฌๅœจๅญฆ่‹ฑ่ฏญ", "context": "present-continuous-answer", "pronunciation": "/รฐeษชr หˆstสŒdiษชล‹ หˆษชล‹ษกlษชสƒ/" }, + "What's Tom doing?": { "user_language": "ๆฑคๅง†ๅœจๅšไป€ไนˆ๏ผŸ", "context": "present-continuous-question", "pronunciation": "/wสŒts tษ‘หm หˆduหษชล‹/" }, + "He's eating": { "user_language": "ไป–ๅœจๅƒไธœ่ฅฟ", "context": "present-continuous-answer", "pronunciation": "/hiหz หˆiหtษชล‹/" }, + "She's watching TV": { "user_language": "ๅฅนๅœจ็œ‹็”ต่ง†", "context": "present-continuous-answer", "pronunciation": "/สƒiหz หˆwษ‘หtสƒษชล‹ หŒtiหหˆviห/" }, + "It's sleeping": { "user_language": "ๅฎƒๅœจ็ก่ง‰", "context": "present-continuous-answer", "pronunciation": "/ษชts หˆsliหpษชล‹/" }, + "Where's Walter?": { "user_language": "ๆฒƒๅฐ”็‰นๅœจๅ“ช้‡Œ๏ผŸ", "context": "location-question", "pronunciation": "/werz หˆwษ”หltษ™r/" }, + "He's in the kitchen": { "user_language": "ไป–ๅœจๅŽจๆˆฟ", "context": "location-answer", "pronunciation": "/hiหz ษชn รฐษ™ หˆkษชtสƒษชn/" }, + "What's he doing?": { "user_language": "ไป–ๅœจๅšไป€ไนˆ๏ผŸ", "context": "activity-question", "pronunciation": "/wสŒts hiห หˆduหษชล‹/" }, + "He's eating breakfast": { "user_language": "ไป–ๅœจๅƒๆ—ฉ้ค", "context": "activity-answer", "pronunciation": "/hiหz หˆiหtษชล‹ หˆbrekfษ™st/" }, + "The sun is shining": { "user_language": "้˜ณๅ…‰็ฟ็ƒ‚", "context": "description", "pronunciation": "/รฐษ™ sสŒn ษชz หˆสƒaษชnษชล‹/" }, + "The birds are singing": { "user_language": "้ธŸๅ„ฟๅœจๆญŒๅ”ฑ", "context": "description", "pronunciation": "/รฐษ™ bษœหrdz ษ‘หr หˆsษชล‹ษชล‹/" }, + "It's a beautiful day": { "user_language": "ไปŠๅคฉๅคฉๆฐ”็œŸๅฅฝ", "context": "expression", "pronunciation": "/ษชts ษ™ หˆbjuหtษ™fษ™l deษช/" }, + "In the kitchen?": { "user_language": "ๅœจๅŽจๆˆฟ๏ผŸ", "context": "checking-understanding", "pronunciation": "/ษชn รฐษ™ หˆkษชtสƒษชn/" }, + "Right now": { "user_language": "็Žฐๅœจ๏ผŒๆญคๅˆป", "context": "time-expression", "pronunciation": "/raษชt naสŠ/" } + }, + "dialogs": { + "what_doing_basic": { + "title": "What Are You Doing?", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "What are you doing?", "user_language": "ไฝ ๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "I'm reading.", "user_language": "ๆˆ‘ๅœจ่ฏปไนฆใ€‚" }, + { "speaker": "Person A", "text": "What are you doing?", "user_language": "ไฝ ไปฌๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "We're cooking.", "user_language": "ๆˆ‘ไปฌๅœจๅš้ฅญใ€‚" }, + { "speaker": "Person A", "text": "What are Mary and Fred doing?", "user_language": "็Ž›ไธฝๅ’Œๅผ—้›ทๅพทๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "They're studying English.", "user_language": "ไป–ไปฌๅœจๅญฆ่‹ฑ่ฏญใ€‚" } + ] + }, + "what_doing_third_person": { + "title": "What's He/She Doing?", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "What's Tom doing?", "user_language": "ๆฑคๅง†ๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "He's eating.", "user_language": "ไป–ๅœจๅƒไธœ่ฅฟใ€‚" }, + { "speaker": "Person A", "text": "What's Martha doing?", "user_language": "็Ž›่ŽŽๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "She's watching TV.", "user_language": "ๅฅนๅœจ็œ‹็”ต่ง†ใ€‚" }, + { "speaker": "Person A", "text": "What's your dog doing?", "user_language": "ไฝ ็š„็‹—ๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "It's sleeping.", "user_language": "ๅฎƒๅœจ็ก่ง‰ใ€‚" } + ] + }, + "where_and_what": { + "title": "Where & What", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "Where's Walter?", "user_language": "ๆฒƒๅฐ”็‰นๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "He's in the kitchen.", "user_language": "ไป–ๅœจๅŽจๆˆฟใ€‚" }, + { "speaker": "Person A", "text": "What's he doing?", "user_language": "ไป–ๅœจๅšไป€ไนˆ๏ผŸ" }, + { "speaker": "Person B", "text": "He's eating breakfast.", "user_language": "ไป–ๅœจๅƒๆ—ฉ้คใ€‚" } + ] + }, + "checking_understanding": { + "title": "Checking Understanding", + "participants": ["Person A", "Person B"], + "lines": [ + { "speaker": "Person A", "text": "Where's Walter?", "user_language": "ๆฒƒๅฐ”็‰นๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Person B", "text": "He's in the kitchen.", "user_language": "ไป–ๅœจๅŽจๆˆฟใ€‚" }, + { "speaker": "Person A", "text": "In the kitchen?", "user_language": "ๅœจๅŽจๆˆฟ๏ผŸ" }, + { "speaker": "Person B", "text": "Yes.", "user_language": "ๆ˜ฏ็š„ใ€‚" } + ] + } + }, + "texts": [ + { + "title": "In the Park", + "original_language": "The Jones family is in the park today. The sun is shining, and the birds are singing. It's a beautiful day! Mr. Jones is reading the newspaper. Mrs. Jones is listening to the radio. Sally and Patty Jones are studying. And Tommy Jones is playing the guitar. The Jones family is very happy today. It's a beautiful day, and they're in the park.", + "user_language": "็ผๆ–ฏไธ€ๅฎถไปŠๅคฉๅœจๅ…ฌๅ›ญ้‡Œใ€‚้˜ณๅ…‰ๆ˜Žๅชš๏ผŒ้ธŸๅ„ฟๅœจๆญŒๅ”ฑใ€‚ๅคšไนˆ็พŽๅฅฝ็š„ไธ€ๅคฉ๏ผ็ผๆ–ฏๅ…ˆ็”Ÿๅœจ่ฏปๆŠฅ็บธใ€‚็ผๆ–ฏๅคชๅคชๅœจๅฌๆ”ถ้Ÿณๆœบใ€‚่ŽŽ่މๅ’Œๅธ•่’‚ยท็ผๆ–ฏๅœจๅญฆไน ใ€‚ๆฑค็ฑณยท็ผๆ–ฏๅœจๅผนๅ‰ไป–ใ€‚็ผๆ–ฏไธ€ๅฎถไปŠๅคฉ้žๅธธๅฟซไนใ€‚ๅคฉๆฐ”็œŸๅฅฝ๏ผŒไป–ไปฌๅœจๅ…ฌๅ›ญ้‡Œใ€‚" + }, + { + "title": "At Home in the Yard", + "original_language": "The Chen family is at home in the yard today. The sun is shining, and the birds are singing. It's a beautiful day! Mr. Chen is planting flowers. Mrs. Chen is drinking lemonade and reading a book. Emily and Jason Chen are playing with the dog. And Jennifer Chen is sleeping. The Chen family is very happy today. It's a beautiful day, and they're at home in the yard.", + "user_language": "้™ˆๅฎถไบบไปŠๅคฉๅœจๅฎถ้‡Œ็š„้™ขๅญ้‡Œใ€‚้˜ณๅ…‰ๆ˜Žๅชš๏ผŒ้ธŸๅ„ฟๅœจๆญŒๅ”ฑใ€‚ๅคšไนˆ็พŽๅฅฝ็š„ไธ€ๅคฉ๏ผ้™ˆๅ…ˆ็”Ÿๅœจ็ง่Šฑใ€‚้™ˆๅคชๅคชๅœจๅ–ๆŸ ๆชฌๆฐดๅ’Œ่ฏปไนฆใ€‚่‰พ็ฑณไธฝๅ’Œๆฐๆฃฎยท้™ˆๅœจๅ’Œ็‹—็Žฉใ€‚่ฉนๅฆฎๅผ—ยท้™ˆๅœจ็ก่ง‰ใ€‚้™ˆๅฎถไบบไปŠๅคฉ้žๅธธๅฟซไนใ€‚ๅคฉๆฐ”็œŸๅฅฝ๏ผŒไป–ไปฌๅœจๅฎถ้‡Œ็š„้™ขๅญ้‡Œใ€‚" + } + ], + "grammar": { + "present-continuous": { + "title": "Present Continuous Tense (็Žฐๅœจ่ฟ›่กŒๆ—ถ)", + "explanation": "Use present continuous to describe actions happening right now. Form: am/is/are + verb-ing", + "examples": [ + { + "english": "I am eating / I'm eating", + "translation": "ๆˆ‘ๆญฃๅœจๅƒ", + "explanation": "Use 'am' with 'I' + verb-ing form" + }, + { + "english": "He is sleeping / He's sleeping", + "translation": "ไป–ๆญฃๅœจ็ก่ง‰", + "explanation": "Use 'is' with he/she/it + verb-ing form" + }, + { + "english": "We are cooking / We're cooking", + "translation": "ๆˆ‘ไปฌๆญฃๅœจๅš้ฅญ", + "explanation": "Use 'are' with we/you/they + verb-ing form" + }, + { + "english": "What are you doing?", + "translation": "ไฝ ๅœจๅšไป€ไนˆ๏ผŸ", + "explanation": "Question form: What + am/is/are + subject + doing?" + } + ] + }, + "verb-ing-formation": { + "title": "Forming -ing Verbs", + "explanation": "To make the -ing form, add -ing to the base verb. Some spelling rules apply.", + "examples": [ + { + "english": "read โ†’ reading", + "translation": "่ฏป โ†’ ๆญฃๅœจ่ฏป", + "explanation": "Simply add -ing to most verbs" + }, + { + "english": "eat โ†’ eating, cook โ†’ cooking", + "translation": "ๅƒ โ†’ ๆญฃๅœจๅƒ๏ผŒๅš้ฅญ โ†’ ๆญฃๅœจๅš้ฅญ", + "explanation": "For regular verbs, just add -ing" + }, + { + "english": "study โ†’ studying, play โ†’ playing", + "translation": "ๅญฆไน  โ†’ ๆญฃๅœจๅญฆไน ๏ผŒ็Žฉ โ†’ ๆญฃๅœจ็Žฉ", + "explanation": "Keep the 'y' and add -ing" + } + ] + }, + "questions-present-continuous": { + "title": "Present Continuous Questions", + "explanation": "To ask questions, put am/is/are before the subject, followed by verb-ing", + "examples": [ + { + "english": "What are you doing?", + "translation": "ไฝ ๅœจๅšไป€ไนˆ๏ผŸ", + "explanation": "Pattern: What + are + you + doing?" + }, + { + "english": "What is Tom doing? / What's Tom doing?", + "translation": "ๆฑคๅง†ๅœจๅšไป€ไนˆ๏ผŸ", + "explanation": "Pattern: What + is + name + doing?" + }, + { + "english": "Where are they playing?", + "translation": "ไป–ไปฌๅœจๅ“ช้‡Œ็Žฉ๏ผŸ", + "explanation": "Can combine Where/What with present continuous" + } + ] + } + }, + "fillInBlanks": [ + { + "sentence": "What ___ you doing?", + "options": ["are", "is", "am", "be"], + "correctAnswer": "are", + "explanation": "Use 'are' with 'you' in present continuous questions", + "grammarFocus": "present-continuous" + }, + { + "sentence": "I ___ reading the newspaper", + "options": ["'m", "is", "are", "be"], + "correctAnswer": "'m", + "explanation": "Use 'I'm' (I am) in present continuous", + "grammarFocus": "present-continuous" + }, + { + "sentence": "He's ___ breakfast", + "options": ["eating", "eat", "eats", "to eat"], + "correctAnswer": "eating", + "explanation": "Use verb-ing form after am/is/are", + "grammarFocus": "verb-ing-formation" + }, + { + "sentence": "They're ___ English", + "options": ["studying", "study", "studies", "to study"], + "correctAnswer": "studying", + "explanation": "Use verb-ing form with 'are'", + "grammarFocus": "verb-ing-formation" + }, + { + "sentence": "What's Tom ___?", + "options": ["doing", "do", "does", "to do"], + "correctAnswer": "doing", + "explanation": "Use 'doing' in What questions with present continuous", + "grammarFocus": "questions-present-continuous" + }, + { + "sentence": "She ___ watching TV", + "options": ["'s", "'re", "'m", "be"], + "correctAnswer": "'s", + "explanation": "Use 'is' (she's) with third person singular", + "grammarFocus": "present-continuous" + }, + { + "sentence": "We're ___ dinner", + "options": ["cooking", "cook", "cooks", "to cook"], + "correctAnswer": "cooking", + "explanation": "Use verb-ing form in present continuous", + "grammarFocus": "verb-ing-formation" + }, + { + "sentence": "The sun ___ shining", + "options": ["is", "are", "am", "be"], + "correctAnswer": "is", + "explanation": "Use 'is' with singular subjects like 'sun'", + "grammarFocus": "present-continuous" + }, + { + "sentence": "The birds are ___", + "options": ["singing", "sing", "sings", "to sing"], + "correctAnswer": "singing", + "explanation": "Use verb-ing after 'are'", + "grammarFocus": "verb-ing-formation" + }, + { + "sentence": "Mr. Jones is ___ the newspaper", + "options": ["reading", "read", "reads", "to read"], + "correctAnswer": "reading", + "explanation": "Use verb-ing form with 'is'", + "grammarFocus": "verb-ing-formation" + }, + { + "sentence": "What are Mary and Fred ___?", + "options": ["doing", "do", "does", "to do"], + "correctAnswer": "doing", + "explanation": "Use 'doing' in What questions", + "grammarFocus": "questions-present-continuous" + }, + { + "sentence": "Tommy is playing ___ guitar", + "options": ["the", "a", "an", "โ€”"], + "correctAnswer": "the", + "explanation": "Always use 'the' before musical instruments", + "grammarFocus": "articles" + }, + { + "sentence": "They're playing ___", + "options": ["baseball", "the baseball", "a baseball", "an baseball"], + "correctAnswer": "baseball", + "explanation": "Don't use 'the' with sports names", + "grammarFocus": "articles" + }, + { + "sentence": "Jennifer ___ sleeping", + "options": ["is", "are", "am", "be"], + "correctAnswer": "is", + "explanation": "Use 'is' with singular third person (Jennifer)", + "grammarFocus": "present-continuous" + }, + { + "sentence": "Mrs. Chen is ___ to music", + "options": ["listening", "listen", "listens", "to listen"], + "correctAnswer": "listening", + "explanation": "Use verb-ing form for present continuous", + "grammarFocus": "verb-ing-formation" + } + ], + "corrections": [ + { + "correct": "What are you doing?", + "incorrect": "What you doing?", + "explanation": "Always include am/is/are in present continuous questions", + "grammarFocus": "questions-present-continuous" + }, + { + "correct": "I'm reading", + "incorrect": "I reading", + "explanation": "Must include 'am' (I'm) before the -ing verb", + "grammarFocus": "present-continuous" + }, + { + "correct": "He's eating breakfast", + "incorrect": "He eating breakfast", + "explanation": "Include 'is' (He's) in present continuous", + "grammarFocus": "present-continuous" + }, + { + "correct": "They're studying English", + "incorrect": "They studying English", + "explanation": "Include 'are' (They're) before -ing verb", + "grammarFocus": "present-continuous" + }, + { + "correct": "She's watching TV", + "incorrect": "She's watch TV", + "explanation": "Use -ing form (watching), not base form (watch)", + "grammarFocus": "verb-ing-formation" + }, + { + "correct": "Tommy is playing the guitar", + "incorrect": "Tommy is playing guitar", + "explanation": "Always use 'the' before musical instruments", + "grammarFocus": "articles" + }, + { + "correct": "They're playing baseball", + "incorrect": "They're playing the baseball", + "explanation": "Don't use 'the' with sports names", + "grammarFocus": "articles" + }, + { + "correct": "What's Tom doing?", + "incorrect": "What Tom doing?", + "explanation": "Include 'is' (What's = What is) in questions", + "grammarFocus": "questions-present-continuous" + }, + { + "correct": "The sun is shining", + "incorrect": "The sun shining", + "explanation": "Include 'is' before -ing verb", + "grammarFocus": "present-continuous" + }, + { + "correct": "I'm listening to music", + "incorrect": "I'm listening music", + "explanation": "Use 'listen to' (not just 'listen') when there's an object", + "grammarFocus": "verb-preposition" + } + ], + "exercises": { + "complete_dialogues": { + "type": "fill_in_conversation", + "instructions": "Complete the dialogues with the correct words", + "items": [ + { + "question": "A: What are you doing?\nB: __________ reading the newspaper.", + "answer": "I'm", + "user_language": "A: ไฝ ๅœจๅšไป€ไนˆ๏ผŸ\nB: __________ ๅœจ่ฏปๆŠฅ็บธใ€‚" + }, + { + "question": "A: __________ Mr. and Mrs. Lane doing?\nB: __________ cooking dinner.", + "answer": "What are / They're", + "user_language": "A: ่Žฑๆฉๅ…ˆ็”Ÿๅ’Œๅคชๅคชๅœจๅšไป€ไนˆ๏ผŸ\nB: ไป–ไปฌๅœจๅšๆ™š้ฅญใ€‚" + }, + { + "question": "A: __________ Rita doing?\nB: __________ studying English.", + "answer": "What's / She's", + "user_language": "A: ไธฝๅก”ๅœจๅšไป€ไนˆ๏ผŸ\nB: ๅฅนๅœจๅญฆ่‹ฑ่ฏญใ€‚" + }, + { + "question": "A: __________ Henry doing?\nB: __________ sleeping.", + "answer": "What's / He's", + "user_language": "A: ไบจๅˆฉๅœจๅšไป€ไนˆ๏ผŸ\nB: ไป–ๅœจ็ก่ง‰ใ€‚" + } + ] + }, + "reading_check": { + "type": "true_false", + "instructions": "In the Park & At Home in the Yard - True or False?", + "items": [ + { "statement": "The Jones family is at home in the yard today", "answer": false, "user_language": "็ผๆ–ฏไธ€ๅฎถไปŠๅคฉๅœจๅฎถ้‡Œ็š„้™ขๅญ้‡Œ" }, + { "statement": "Mr. Chen is planting flowers", "answer": true, "user_language": "้™ˆๅ…ˆ็”Ÿๅœจ็ง่Šฑ" }, + { "statement": "Patty Jones is studying", "answer": true, "user_language": "ๅธ•่’‚ยท็ผๆ–ฏๅœจๅญฆไน " }, + { "statement": "Jason Chen is reading a book", "answer": false, "user_language": "ๆฐๆฃฎยท้™ˆๅœจ่ฏปไนฆ" }, + { "statement": "The Chen family is singing", "answer": false, "user_language": "้™ˆๅฎถไบบๅœจๅ”ฑๆญŒ" }, + { "statement": "The Jones family and the Chen family are very happy today", "answer": true, "user_language": "็ผๆ–ฏไธ€ๅฎถๅ’Œ้™ˆๅฎถไบบไปŠๅคฉ้ƒฝๅพˆๅฟซไน" } + ] + }, + "location_and_activity": { + "type": "conversation_practice", + "instructions": "Ask and answer: Where is [person]? What's he/she doing?", + "items": [ + { + "person": "Karen", + "location": "park", + "activity": "eating lunch", + "user_language_person": "ๅ‡ฏไผฆ", + "user_language_location": "ๅ…ฌๅ›ญ", + "user_language_activity": "ๅƒๅˆ้ค" + }, + { + "person": "Mr. and Mrs. Clark", + "location": "dining room", + "activity": "eating dinner", + "user_language_person": "ๅ…‹ๆ‹‰ๅ…‹ๅ…ˆ็”Ÿๅ’Œๅคชๅคช", + "user_language_location": "้คๅŽ…", + "user_language_activity": "ๅƒๆ™š้ค" + }, + { + "person": "Miss Baker", + "location": "cafeteria", + "activity": "drinking milk", + "user_language_person": "่ดๅ…‹ๅฐๅง", + "user_language_location": "่‡ชๅŠฉ้คๅŽ…", + "user_language_activity": "ๅ–็‰›ๅฅถ" + }, + { + "person": "Ms. Johnson", + "location": "classroom", + "activity": "teaching mathematics", + "user_language_person": "็บฆ็ฟฐ้€Šๅฅณๅฃซ", + "user_language_location": "ๆ•™ๅฎค", + "user_language_activity": "ๆ•™ๆ•ฐๅญฆ" + }, + { + "person": "Martin", + "location": "bathroom", + "activity": "singing", + "user_language_person": "้ฉฌไธ", + "user_language_location": "ๆตดๅฎค", + "user_language_activity": "ๅ”ฑๆญŒ" + } + ] + } + }, + "cultural_notes": { + "titles": { + "title": "Titles in English", + "explanation": "English uses titles before names to show respect and marital status", + "examples": [ + { + "term": "Mr.", + "usage": "Title for a man", + "chinese": "ๅ…ˆ็”Ÿ" + }, + { + "term": "Mrs.", + "usage": "Title for a married woman", + "chinese": "ๅคชๅคช๏ผˆๅทฒๅฉš๏ผ‰" + }, + { + "term": "Ms.", + "usage": "Title for a woman (married or unmarried)", + "chinese": "ๅฅณๅฃซ" + }, + { + "term": "Miss", + "usage": "Title for an unmarried woman", + "chinese": "ๅฐๅง๏ผˆๆœชๅฉš๏ผ‰" + } + ] + }, + "nicknames": { + "title": "Common English Nicknames", + "explanation": "Many English names have short, informal versions called nicknames", + "examples": [ + { "full": "James", "nickname": "Jim", "chinese": "่ฉนๅง†ๆ–ฏ โ†’ ๅ‰ๅง†" }, + { "full": "Robert", "nickname": "Bob", "chinese": "็ฝ—ไผฏ็‰น โ†’ ้ฒๅ‹ƒ" }, + { "full": "William", "nickname": "Bill", "chinese": "ๅจๅป‰ โ†’ ๆฏ”ๅฐ”" }, + { "full": "Elizabeth", "nickname": "Liz, Betty", "chinese": "ไผŠไธฝ่ŽŽ็™ฝ โ†’ ่މๅ…นใ€่ด่’‚" }, + { "full": "Jennifer", "nickname": "Jenny", "chinese": "็ๅฆฎๅผ— โ†’ ็ๅฆฎ" }, + { "full": "Patricia", "nickname": "Patty", "chinese": "ๅธ•็‰นไธฝๅค โ†’ ๅธ•่’‚" } + ] + } + }, + "thematic_questions": { + "present_continuous_you": [ + { + "id": "q1", + "question": "What are you doing?", + "question_user_language": "ไฝ ๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "I'm reading", + "I'm studying English", + "I'm cooking dinner" + ], + "theme": "present_continuous_you" + }, + { + "id": "q2", + "question": "What are you doing right now?", + "question_user_language": "ไฝ ็Žฐๅœจๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "I'm watching TV", + "I'm eating lunch", + "I'm listening to music" + ], + "theme": "present_continuous_you" + }, + { + "id": "q3", + "question": "What are you and your friend doing?", + "question_user_language": "ไฝ ๅ’Œไฝ ็š„ๆœ‹ๅ‹ๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "We're playing cards", + "We're studying", + "We're cooking dinner" + ], + "theme": "present_continuous_you" + }, + { + "id": "q4", + "question": "What are Mary and Fred doing?", + "question_user_language": "็Ž›ไธฝๅ’Œๅผ—้›ทๅพทๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're studying English", + "They're playing baseball", + "They're watching TV" + ], + "theme": "present_continuous_you" + }, + { + "id": "q5", + "question": "What are the students doing?", + "question_user_language": "ๅญฆ็”Ÿไปฌๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're studying", + "They're reading", + "They're listening to the teacher" + ], + "theme": "present_continuous_you" + } + ], + "present_continuous_third_person": [ + { + "id": "q6", + "question": "What's Tom doing?", + "question_user_language": "ๆฑคๅง†ๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's eating", + "He's reading the newspaper", + "He's sleeping" + ], + "theme": "present_continuous_third_person" + }, + { + "id": "q7", + "question": "What's Martha doing?", + "question_user_language": "็Ž›่ŽŽๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's watching TV", + "She's cooking", + "She's studying" + ], + "theme": "present_continuous_third_person" + }, + { + "id": "q8", + "question": "What's your dog doing?", + "question_user_language": "ไฝ ็š„็‹—ๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It's sleeping", + "It's playing", + "It's eating" + ], + "theme": "present_continuous_third_person" + }, + { + "id": "q9", + "question": "What's Tommy doing?", + "question_user_language": "ๆฑค็ฑณๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's playing the guitar", + "He's playing baseball", + "He's singing" + ], + "theme": "present_continuous_third_person" + }, + { + "id": "q10", + "question": "What's Mrs. Jones doing?", + "question_user_language": "็ผๆ–ฏๅคชๅคชๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's listening to the radio", + "She's reading a book", + "She's cooking" + ], + "theme": "present_continuous_third_person" + } + ], + "reading_comprehension": [ + { + "id": "q11", + "question": "What's Mr. Jones doing in the park?", + "question_user_language": "็ผๆ–ฏๅ…ˆ็”Ÿๅœจๅ…ฌๅ›ญ้‡Œๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's reading the newspaper", + "Reading the newspaper", + "He's reading" + ], + "theme": "reading_comprehension" + }, + { + "id": "q12", + "question": "What's Mr. Chen doing in the yard?", + "question_user_language": "้™ˆๅ…ˆ็”Ÿๅœจ้™ขๅญ้‡Œๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's planting flowers", + "Planting flowers", + "He's gardening" + ], + "theme": "reading_comprehension" + }, + { + "id": "q13", + "question": "What are Emily and Jason Chen doing?", + "question_user_language": "่‰พ็ฑณไธฝๅ’Œๆฐๆฃฎยท้™ˆๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They're playing with the dog", + "Playing with the dog", + "They're playing" + ], + "theme": "reading_comprehension" + }, + { + "id": "q14", + "question": "What's Jennifer Chen doing?", + "question_user_language": "่ฉนๅฆฎๅผ—ยท้™ˆๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's sleeping", + "Sleeping", + "She's taking a nap" + ], + "theme": "reading_comprehension" + } + ], + "location_and_activity": [ + { + "id": "q15", + "question": "Where's Walter and what's he doing?", + "question_user_language": "ๆฒƒๅฐ”็‰นๅœจๅ“ช้‡Œ๏ผŒไป–ๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "He's in the kitchen. He's eating breakfast", + "In the kitchen, eating breakfast", + "He's eating breakfast in the kitchen" + ], + "theme": "location_and_activity" + }, + { + "id": "q16", + "question": "Where's Karen and what's she doing?", + "question_user_language": "ๅ‡ฏไผฆๅœจๅ“ช้‡Œ๏ผŒๅฅนๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's in the park. She's eating lunch", + "In the park, eating lunch", + "She's eating lunch in the park" + ], + "theme": "location_and_activity" + }, + { + "id": "q17", + "question": "Where's Ms. Johnson and what's she doing?", + "question_user_language": "็บฆ็ฟฐ้€Šๅฅณๅฃซๅœจๅ“ช้‡Œ๏ผŒๅฅนๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "She's in the classroom. She's teaching mathematics", + "In the classroom, teaching mathematics", + "She's teaching in the classroom" + ], + "theme": "location_and_activity" + }, + { + "id": "q18", + "question": "Where are you and what are you doing?", + "question_user_language": "ไฝ ๅœจๅ“ช้‡Œ๏ผŒไฝ ๅœจๅšไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "I'm at home. I'm studying", + "At home, studying", + "I'm studying at home" + ], + "theme": "location_and_activity" + } + ] + }, + "statistics": { + "vocabulary_count": 45, + "phrases_count": 17, + "dialogs_count": 4, + "texts_count": 2, + "exercises_count": 3, + "fillInBlanks_count": 15, + "corrections_count": 10, + "thematic_questions_count": 18, + "estimated_completion_time": 18 + } +} diff --git a/content/chapters/sbs-7-8.json b/content/chapters/sbs-7-8.json index f35fac6..a241dd0 100644 --- a/content/chapters/sbs-7-8.json +++ b/content/chapters/sbs-7-8.json @@ -139,6 +139,33 @@ ] } }, + "texts": [ + { + "title": "My New Apartment", + "original_language": "I live in a two-bedroom apartment on Central Avenue. It's in the center of town, so it's very convenient. There's a bus stop right outside the building. My apartment has a nice refrigerator, a big closet, and an air conditioner. The building has an elevator and a jacuzzi. The superintendent is very helpful. There's a lot of noise on the sidewalks all day and all night, but I like living here because everything is convenient.", + "user_language": "ๆˆ‘ไฝๅœจไธญๅคฎๅคง้“็š„ไธ€้—ดไธคๅฑ…ๅฎคๅ…ฌๅฏ“ใ€‚ๅฎƒๅœจๅธ‚ไธญๅฟƒ๏ผŒๆ‰€ไปฅ้žๅธธไพฟๅˆฉใ€‚ๅคงๆฅผๅค–้ขๅฐฑๆœ‰ไธ€ไธชๅ…ฌไบค่ฝฆ็ซ™ใ€‚ๆˆ‘็š„ๅ…ฌๅฏ“ๆœ‰ไธ€ไธชๆผ‚ไบฎ็š„ๅ†ฐ็ฎฑใ€ไธ€ไธชๅคง่กฃๆŸœๅ’Œไธ€ๅฐ็ฉบ่ฐƒใ€‚ๅคงๆฅผๆœ‰็”ตๆขฏๅ’ŒๆŒ‰ๆ‘ฉๆตด็ผธใ€‚็‰ฉไธšไธป็ฎก้žๅธธไนไบŽๅŠฉไบบใ€‚ไบบ่กŒ้“ไธŠๆ•ดๆ—ฅๆ•ดๅคœ้ƒฝๆœ‰ๅพˆๅคšๅ™ช้Ÿณ๏ผŒไฝ†ๆˆ‘ๅ–œๆฌขไฝๅœจ่ฟ™้‡Œ๏ผŒๅ› ไธบไธ€ๅˆ‡้ƒฝๅพˆๆ–นไพฟใ€‚" + }, + { + "title": "Getting Dressed for Work", + "original_language": "Every morning, I get dressed for work. Today I'm wearing a blue shirt, black pants, and brown shoes. I also have a jacket because it's cold outside. I need to wear a suit for my job interview tomorrow, so I'm wearing a nice tie too. My shoes are very comfortable. I always wear socks and a belt. In winter, I wear gloves and a scarf. In summer, I wear shorts and sneakers on the weekend.", + "user_language": "ๆฏๅคฉๆ—ฉไธŠ๏ผŒๆˆ‘้ƒฝ่ฆ็ฉฟๅฅฝ่กฃๆœๅŽปไธŠ็ญใ€‚ไปŠๅคฉๆˆ‘็ฉฟ็€ไธ€ไปถ่“่‰ฒ่กฌ่กซใ€้ป‘่‰ฒ่ฃคๅญๅ’Œๆฃ•่‰ฒ้ž‹ๅญใ€‚ๅ› ไธบๅค–้ขๅพˆๅ†ท๏ผŒๆˆ‘่ฟ˜็ฉฟไบ†ไธ€ไปถๅคนๅ…‹ใ€‚ๆ˜Žๅคฉๆˆ‘่ฆๅ‚ๅŠ ๅทฅไฝœ้ข่ฏ•๏ผŒ้œ€่ฆ็ฉฟๅฅ—่ฃ…๏ผŒๆ‰€ไปฅๆˆ‘ไนŸๆˆดไบ†ไธ€ๆกๆผ‚ไบฎ็š„้ข†ๅธฆใ€‚ๆˆ‘็š„้ž‹ๅญ้žๅธธ่ˆ’้€‚ใ€‚ๆˆ‘ๆ€ปๆ˜ฏ็ฉฟ่ขœๅญๅ’Œ็ณป่…ฐๅธฆใ€‚ๅ†ฌๅคฉ๏ผŒๆˆ‘ๆˆดๆ‰‹ๅฅ—ๅ’Œๅ›ดๅทพใ€‚ๅคๅคฉ๏ผŒๅ‘จๆœซๆˆ‘็ฉฟ็Ÿญ่ฃคๅ’Œ่ฟๅŠจ้ž‹ใ€‚" + }, + { + "title": "How I Feel Today", + "original_language": "I feel happy today because it's Friday. Yesterday I felt tired because I worked all day and all night. Sometimes I feel worried about my job, but today I feel excited. My friend feels sad because she has a test tomorrow. Are you hungry? I'm very hungry because I haven't eaten all day. It's hot outside, so I feel thirsty. My feet hurt from walking, and my fingers hurt from typing on the computer.", + "user_language": "ๆˆ‘ไปŠๅคฉๆ„Ÿๅˆฐๅพˆๅฟซไน๏ผŒๅ› ไธบไปŠๅคฉๆ˜ฏๆ˜ŸๆœŸไบ”ใ€‚ๆ˜จๅคฉๆˆ‘ๆ„Ÿๅˆฐๅพˆ็ดฏ๏ผŒๅ› ไธบๆˆ‘ๆ•ดๆ—ฅๆ•ดๅคœ้ƒฝๅœจๅทฅไฝœใ€‚ๆœ‰ๆ—ถๆˆ‘ไผšๆ‹…ๅฟƒๆˆ‘็š„ๅทฅไฝœ๏ผŒไฝ†ไปŠๅคฉๆˆ‘ๆ„Ÿๅˆฐๅพˆๅ…ดๅฅ‹ใ€‚ๆˆ‘็š„ๆœ‹ๅ‹ๆ„Ÿๅˆฐ้šพ่ฟ‡๏ผŒๅ› ไธบๅฅนๆ˜Žๅคฉๆœ‰่€ƒ่ฏ•ใ€‚ไฝ ้ฅฟๅ—๏ผŸๆˆ‘้žๅธธ้ฅฟ๏ผŒๅ› ไธบๆˆ‘ไธ€ๆ•ดๅคฉ้ƒฝๆฒกๅƒไธœ่ฅฟใ€‚ๅค–้ขๅพˆ็ƒญ๏ผŒๆ‰€ไปฅๆˆ‘ๆ„Ÿๅˆฐๅฃๆธดใ€‚ๆˆ‘็š„่„šๅ› ไธบ่ตฐ่ทฏ่€Œ็–ผ๏ผŒๆˆ‘็š„ๆ‰‹ๆŒ‡ๅ› ไธบๅœจ็”ต่„‘ไธŠๆ‰“ๅญ—่€Œ็–ผใ€‚" + }, + { + "title": "My Daily Technology Use", + "original_language": "I use my computer and laptop every day for work. I need to check my email in the morning. I have internet access at home and at the office. I use my phone and tablet to browse websites and use apps. I spend a lot of time on social media. I need a password for my email and all my apps. Do you have internet access? I need to check my email right now. Technology is very convenient, but sometimes my eyes hurt from looking at the computer screen all day.", + "user_language": "ๆˆ‘ๆฏๅคฉ้ƒฝไฝฟ็”จ็”ต่„‘ๅ’Œ็ฌ”่ฎฐๆœฌ็”ต่„‘ๅทฅไฝœใ€‚ๆ—ฉไธŠๆˆ‘้œ€่ฆๆŸฅ็œ‹ๆˆ‘็š„็”ตๅญ้‚ฎไปถใ€‚ๆˆ‘ๅœจๅฎถ้‡Œๅ’ŒๅŠžๅ…ฌๅฎค้ƒฝๆœ‰็ฝ‘็ปœ่ฟžๆŽฅใ€‚ๆˆ‘ไฝฟ็”จๆ‰‹ๆœบๅ’Œๅนณๆฟ็”ต่„‘ๆต่งˆ็ฝ‘็ซ™ๅ’Œไฝฟ็”จๅบ”็”จ็จ‹ๅบใ€‚ๆˆ‘่Šฑๅพˆๅคšๆ—ถ้—ดๅœจ็คพไบคๅช’ไฝ“ไธŠใ€‚ๆˆ‘็š„็”ตๅญ้‚ฎไปถๅ’Œๆ‰€ๆœ‰ๅบ”็”จ็จ‹ๅบ้ƒฝ้œ€่ฆๅฏ†็ ใ€‚ไฝ ๆœ‰็ฝ‘็ปœ่ฟžๆŽฅๅ—๏ผŸๆˆ‘็Žฐๅœจ้œ€่ฆๆŸฅ็œ‹ๆˆ‘็š„็”ตๅญ้‚ฎไปถใ€‚็ง‘ๆŠ€้žๅธธๆ–นไพฟ๏ผŒไฝ†ๆœ‰ๆ—ถๆˆ‘็š„็œผ็›ๅ› ไธบๆ•ดๅคฉ็œ‹็”ต่„‘ๅฑๅน•่€Œ็–ผใ€‚" + }, + { + "title": "Describing People", + "original_language": "My friend has long hair and beautiful eyes. She has a nice face and a friendly smile. She's tall with long arms and legs. My boss is wearing a suit today with a red tie. He has short hair and wears glasses. My sister is wearing a dress and boots. She has a scarf around her neck because it's cold. The superintendent of my building is wearing jeans and a sweater. He has strong shoulders and big hands.", + "user_language": "ๆˆ‘็š„ๆœ‹ๅ‹ๆœ‰้•ฟๅคดๅ‘ๅ’Œ็พŽไธฝ็š„็œผ็›ใ€‚ๅฅนๆœ‰ไธ€ๅผ ๆผ‚ไบฎ็š„่„ธๅ’Œๅ‹ๅฅฝ็š„็ฌ‘ๅฎนใ€‚ๅฅนไธชๅญ้ซ˜๏ผŒ่ƒณ่†Šๅ’Œ่…ฟ้ƒฝๅพˆ้•ฟใ€‚ๆˆ‘็š„่€ๆฟไปŠๅคฉ็ฉฟ็€ๅฅ—่ฃ…๏ผŒๆ‰“็€็บข่‰ฒ้ข†ๅธฆใ€‚ไป–ๆœ‰็Ÿญๅ‘๏ผŒๆˆด็€็œผ้•œใ€‚ๆˆ‘็š„ๅฆนๅฆน็ฉฟ็€่ฟž่กฃ่ฃ™ๅ’Œ้ดๅญใ€‚ๅฅน่„–ๅญไธŠๅ›ด็€ๅ›ดๅทพ๏ผŒๅ› ไธบๅคฉๆฐ”ๅพˆๅ†ทใ€‚ๆˆ‘ไปฌๆฅผ็š„็‰ฉไธšไธป็ฎก็ฉฟ็€็‰›ไป”่ฃคๅ’Œๆฏ›่กฃใ€‚ไป–ๆœ‰ๅผบๅฃฎ็š„่‚ฉ่†€ๅ’Œๅคงๆ‰‹ใ€‚" + } + ], "exercises": { "vocabulary_matching": { "type": "matching", @@ -398,6 +425,7 @@ "vocabulary_count": 67, "phrases_count": 10, "dialogs_count": 2, + "texts_count": 5, "exercises_count": 2, "fillInBlanks_count": 15, "corrections_count": 10, diff --git a/content/chapters/wte2-2.json b/content/chapters/wte2-2.json new file mode 100644 index 0000000..652d32f --- /dev/null +++ b/content/chapters/wte2-2.json @@ -0,0 +1,646 @@ +{ + "id": "wte2-2", + "book_id": "wte2", + "name": "Our Pet Friends & Letters W-Z", + "description": "Welcome to English 2 Unit 2 - Learn furniture, prepositions of place, where questions, and letters W-Z", + "difficulty": "beginner", + "language": "en-US", + "chapter_number": "2", + "metadata": { + "version": "1.0", + "created": "2025-10-18", + "updated": "2025-10-18", + "source": "Welcome to English 2 - Oxford University Press", + "target_level": "beginner", + "estimated_hours": 4, + "prerequisites": ["wte2-1"], + "learning_objectives": [ + "Use prepositions: in, on, under, near", + "Ask and answer 'Where is/are...?' questions", + "Identify furniture vocabulary", + "Use contractions: isn't, aren't, let's", + "Recognize and write letters W, X, Y, Z", + "Read short stories about finding pets" + ], + "content_tags": ["furniture", "pets", "prepositions", "location", "letters", "phonics", "where-questions"], + "completion_criteria": { + "vocabulary_mastery": 80, + "quiz_score": 75, + "games_completed": 3 + } + }, + "letters": { + "W": [ + { "word": "wolf", "translation": "็‹ผ", "type": "noun", "pronunciation": "/wสŠlf/" }, + { "word": "web", "translation": "็ฝ‘๏ผ›่œ˜่››็ฝ‘", "type": "noun", "pronunciation": "/web/" }, + { "word": "water", "translation": "ๆฐด", "type": "noun", "pronunciation": "/หˆwษ”หtษ™r/" }, + { "word": "watch", "translation": "ๆ‰‹่กจ๏ผ›่ง‚็œ‹", "type": "noun/verb", "pronunciation": "/wษ‘หtสƒ/" } + ], + "X": [ + { "word": "fox", "translation": "็‹็‹ธ", "type": "noun", "pronunciation": "/fษ‘หks/" }, + { "word": "box", "translation": "็›’ๅญ๏ผ›็ฎฑๅญ", "type": "noun", "pronunciation": "/bษ‘หks/" }, + { "word": "six", "translation": "ๅ…ญ", "type": "number", "pronunciation": "/sษชks/" }, + { "word": "wax", "translation": "่œก", "type": "noun", "pronunciation": "/wรฆks/" } + ], + "Y": [ + { "word": "yo-yo", "translation": "ๆ‚ ๆ‚ ็ƒ", "type": "noun", "pronunciation": "/หˆjoสŠjoสŠ/" }, + { "word": "yak", "translation": "็‰ฆ็‰›", "type": "noun", "pronunciation": "/jรฆk/" }, + { "word": "yogurt", "translation": "้…ธๅฅถ", "type": "noun", "pronunciation": "/หˆjoสŠษกษ™rt/" }, + { "word": "yacht", "translation": "ๆธธ่‰‡", "type": "noun", "pronunciation": "/jษ‘หt/" } + ], + "Z": [ + { "word": "zipper", "translation": "ๆ‹‰้“พ", "type": "noun", "pronunciation": "/หˆzษชpษ™r/" }, + { "word": "zero", "translation": "้›ถ", "type": "number", "pronunciation": "/หˆzษชroสŠ/" }, + { "word": "zoo", "translation": "ๅŠจ็‰ฉๅ›ญ", "type": "noun", "pronunciation": "/zuห/" }, + { "word": "zebra", "translation": "ๆ–‘้ฉฌ", "type": "noun", "pronunciation": "/หˆziหbrษ™/" } + ] + }, + "vocabulary": { + "sofa": { "user_language": "ๆฒ™ๅ‘", "type": "noun", "pronunciation": "/หˆsoสŠfษ™/" }, + "table": { "user_language": "ๆกŒๅญ", "type": "noun", "pronunciation": "/หˆteษชbษ™l/" }, + "chair": { "user_language": "ๆค…ๅญ", "type": "noun", "pronunciation": "/tสƒer/" }, + "box": { "user_language": "็›’ๅญ๏ผ›็ฎฑๅญ", "type": "noun", "pronunciation": "/bษ‘หks/" }, + "cupboard": { "user_language": "ๆฉฑๆŸœ๏ผ›็ข—ๆŸœ", "type": "noun", "pronunciation": "/หˆkสŒbษ™rd/" }, + "shelf": { "user_language": "ๆžถๅญ๏ผ›ๆๆฟ", "type": "noun", "pronunciation": "/สƒelf/" }, + "bag": { "user_language": "ๅŒ…๏ผ›่ข‹ๅญ", "type": "noun", "pronunciation": "/bรฆษก/" }, + "bed": { "user_language": "ๅบŠ", "type": "noun", "pronunciation": "/bed/" }, + "desk": { "user_language": "ไนฆๆกŒ", "type": "noun", "pronunciation": "/desk/" }, + "drawer": { "user_language": "ๆŠฝๅฑ‰", "type": "noun", "pronunciation": "/drษ”หr/" }, + "dog": { "user_language": "็‹—", "type": "noun", "pronunciation": "/dษ”หษก/" }, + "cat": { "user_language": "็Œซ", "type": "noun", "pronunciation": "/kรฆt/" }, + "hamster": { "user_language": "ไป“้ผ ", "type": "noun", "pronunciation": "/หˆhรฆmstษ™r/" }, + "rabbit": { "user_language": "ๅ…”ๅญ", "type": "noun", "pronunciation": "/หˆrรฆbษชt/" }, + "turtle": { "user_language": "ไนŒ้พŸ", "type": "noun", "pronunciation": "/หˆtษœหrtl/" }, + "bird": { "user_language": "้ธŸ", "type": "noun", "pronunciation": "/bษœหrd/" }, + "on": { "user_language": "ๅœจโ€ฆโ€ฆไธŠ้ข", "type": "preposition", "pronunciation": "/ษ‘หn/" }, + "in": { "user_language": "ๅœจโ€ฆโ€ฆ้‡Œ้ข", "type": "preposition", "pronunciation": "/ษชn/" }, + "under": { "user_language": "ๅœจโ€ฆโ€ฆไธ‹้ข", "type": "preposition", "pronunciation": "/หˆสŒndษ™r/" }, + "near": { "user_language": "ๅœจโ€ฆโ€ฆ้™„่ฟ‘", "type": "preposition", "pronunciation": "/nษชr/" }, + "sad": { "user_language": "ไผคๅฟƒ็š„๏ผ›้šพ่ฟ‡็š„", "type": "adjective", "pronunciation": "/sรฆd/" }, + "find": { "user_language": "ๆ‰พๅˆฐ๏ผ›ๅ‘็Žฐ", "type": "verb", "pronunciation": "/faษชnd/" }, + "eyes": { "user_language": "็œผ็›", "type": "noun", "pronunciation": "/aษชz/" }, + "wolf": { "user_language": "็‹ผ", "type": "noun", "pronunciation": "/wสŠlf/" }, + "web": { "user_language": "็ฝ‘๏ผ›่œ˜่››็ฝ‘", "type": "noun", "pronunciation": "/web/" }, + "water": { "user_language": "ๆฐด", "type": "noun", "pronunciation": "/หˆwษ”หtษ™r/" }, + "watch": { "user_language": "ๆ‰‹่กจ๏ผ›่ง‚็œ‹", "type": "noun/verb", "pronunciation": "/wษ‘หtสƒ/" }, + "fox": { "user_language": "็‹็‹ธ", "type": "noun", "pronunciation": "/fษ‘หks/" }, + "six": { "user_language": "ๅ…ญ", "type": "number", "pronunciation": "/sษชks/" }, + "wax": { "user_language": "่œก", "type": "noun", "pronunciation": "/wรฆks/" }, + "yo-yo": { "user_language": "ๆ‚ ๆ‚ ็ƒ", "type": "noun", "pronunciation": "/หˆjoสŠjoสŠ/" }, + "yak": { "user_language": "็‰ฆ็‰›", "type": "noun", "pronunciation": "/jรฆk/" }, + "yogurt": { "user_language": "้…ธๅฅถ", "type": "noun", "pronunciation": "/หˆjoสŠษกษ™rt/" }, + "yacht": { "user_language": "ๆธธ่‰‡", "type": "noun", "pronunciation": "/jษ‘หt/" }, + "zipper": { "user_language": "ๆ‹‰้“พ", "type": "noun", "pronunciation": "/หˆzษชpษ™r/" }, + "zero": { "user_language": "้›ถ", "type": "number", "pronunciation": "/หˆzษชroสŠ/" }, + "zoo": { "user_language": "ๅŠจ็‰ฉๅ›ญ", "type": "noun", "pronunciation": "/zuห/" }, + "zebra": { "user_language": "ๆ–‘้ฉฌ", "type": "noun", "pronunciation": "/หˆziหbrษ™/" }, + "fat": { "user_language": "่ƒ–็š„", "type": "adjective", "pronunciation": "/fรฆt/" }, + "hat": { "user_language": "ๅธฝๅญ", "type": "noun", "pronunciation": "/hรฆt/" }, + "bad": { "user_language": "ๅ็š„", "type": "adjective", "pronunciation": "/bรฆd/" } + }, + "phrases": { + "Where is the cat?": { "user_language": "็Œซๅœจๅ“ช้‡Œ๏ผŸ", "context": "location-question", "pronunciation": "/wer ษชz รฐษ™ kรฆt/" }, + "It is on the chair": { "user_language": "ๅฎƒๅœจๆค…ๅญไธŠใ€‚", "context": "location-answer", "pronunciation": "/ษชt ษชz ษ‘หn รฐษ™ tสƒer/" }, + "Where are the turtles?": { "user_language": "ไนŒ้พŸๅœจๅ“ช้‡Œ๏ผŸ", "context": "location-question", "pronunciation": "/wer ษ‘หr รฐษ™ หˆtษœหrtlz/" }, + "They are under the sofa": { "user_language": "ๅฎƒไปฌๅœจๆฒ™ๅ‘ไธ‹้ขใ€‚", "context": "location-answer", "pronunciation": "/รฐeษช ษ‘หr หˆสŒndษ™r รฐษ™ หˆsoสŠfษ™/" }, + "One is in the cupboard": { "user_language": "ไธ€ๅชๅœจๆฉฑๆŸœ้‡Œใ€‚", "context": "location-answer", "pronunciation": "/wสŒn ษชz ษชn รฐษ™ หˆkสŒbษ™rd/" }, + "One is near the sofa": { "user_language": "ไธ€ๅชๅœจๆฒ™ๅ‘้™„่ฟ‘ใ€‚", "context": "location-answer", "pronunciation": "/wสŒn ษชz nษชr รฐษ™ หˆsoสŠfษ™/" }, + "Is it in the box?": { "user_language": "ๅฎƒๅœจ็›’ๅญ้‡Œๅ—๏ผŸ", "context": "yes-no-question", "pronunciation": "/ษชz ษชt ษชn รฐษ™ bษ‘หks/" }, + "Yes, it is": { "user_language": "ๆ˜ฏ็š„๏ผŒๅฎƒๅœจใ€‚", "context": "affirmative", "pronunciation": "/jes ษชt ษชz/" }, + "No, it isn't": { "user_language": "ไธ๏ผŒๅฎƒไธๅœจใ€‚", "context": "negative", "pronunciation": "/noสŠ ษชt หˆษชzษ™nt/" }, + "Are they under the bed?": { "user_language": "ๅฎƒไปฌๅœจๅบŠไธ‹้ขๅ—๏ผŸ", "context": "yes-no-question", "pronunciation": "/ษ‘หr รฐeษช หˆสŒndษ™r รฐษ™ bed/" }, + "Yes, they are": { "user_language": "ๆ˜ฏ็š„๏ผŒๅฎƒไปฌๅœจใ€‚", "context": "affirmative", "pronunciation": "/jes รฐeษช ษ‘หr/" }, + "No, they aren't": { "user_language": "ไธ๏ผŒๅฎƒไปฌไธๅœจใ€‚", "context": "negative", "pronunciation": "/noสŠ รฐeษช หˆษ‘หrษ™nt/" }, + "He cannot find Bob": { "user_language": "ไป–ๆ‰พไธๅˆฐ้ฒๅ‹ƒใ€‚", "context": "statement", "pronunciation": "/hi หˆkรฆnษ‘หt faษชnd bษ‘หb/" }, + "Let's find Bob": { "user_language": "ๆˆ‘ไปฌไธ€่ตทๆ‰พ้ฒๅ‹ƒๅงใ€‚", "context": "suggestion", "pronunciation": "/lets faษชnd bษ‘หb/" }, + "Come on, Ricky": { "user_language": "ๆฅๅง๏ผŒ็‘žๅฅ‡ใ€‚", "context": "imperative", "pronunciation": "/kสŒm ษ‘หn หˆrษชki/" }, + "Well done!": { "user_language": "ๅšๅพ—ๅฅฝ๏ผ", "context": "praise", "pronunciation": "/wel dสŒn/" }, + "Close your eyes": { "user_language": "้—ญไธŠไฝ ็š„็œผ็›ใ€‚", "context": "imperative", "pronunciation": "/kloสŠz jสŠr aษชz/" } + }, + "dialogs": { + "finding_pets": { + "title": "Finding the Pets", + "participants": ["Tom", "Mary", "Sally", "Peter", "Betty", "Charlie"], + "lines": [ + { "speaker": "Tom", "text": "Where is the cat?", "user_language": "็Œซๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Mary", "text": "It is on the chair.", "user_language": "ๅฎƒๅœจๆค…ๅญไธŠใ€‚" }, + { "speaker": "Sally", "text": "Where are the hamsters?", "user_language": "ไป“้ผ ๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Peter", "text": "One is in the cupboard. One is near the sofa.", "user_language": "ไธ€ๅชๅœจๆฉฑๆŸœ้‡Œใ€‚ไธ€ๅชๅœจๆฒ™ๅ‘้™„่ฟ‘ใ€‚" }, + { "speaker": "Betty", "text": "Where are the turtles?", "user_language": "ไนŒ้พŸๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Charlie", "text": "They are under the sofa.", "user_language": "ๅฎƒไปฌๅœจๆฒ™ๅ‘ไธ‹้ขใ€‚" } + ] + }, + "guessing_game": { + "title": "Guessing Game", + "participants": ["Teacher", "Student"], + "lines": [ + { "speaker": "Teacher", "text": "Close your eyes.", "user_language": "้—ญไธŠไฝ ็š„็œผ็›ใ€‚" }, + { "speaker": "Teacher", "text": "Where is the rabbit?", "user_language": "ๅ…”ๅญๅœจๅ“ช้‡Œ๏ผŸ" }, + { "speaker": "Student", "text": "Is it under a desk?", "user_language": "ๅฎƒๅœจๆกŒๅญไธ‹้ขๅ—๏ผŸ" }, + { "speaker": "Teacher", "text": "No, it isn't.", "user_language": "ไธ๏ผŒๅฎƒไธๅœจใ€‚" }, + { "speaker": "Student", "text": "Is it in a drawer?", "user_language": "ๅฎƒๅœจๆŠฝๅฑ‰้‡Œๅ—๏ผŸ" }, + { "speaker": "Teacher", "text": "Yes, it is. Well done!", "user_language": "ๆ˜ฏ็š„๏ผŒๅฎƒๅœจใ€‚ๅšๅพ—ๅฅฝ๏ผ" } + ] + } + }, + "texts": [ + { + "title": "Where is Bob?", + "original_language": "Ricky is Bob's dog. Ricky is sad. He cannot find Bob. Is Bob under the cupboard? No, he isn't. Where is Bob? Is he in the bag? No, Ricky. Bob isn't in the bag. Is Bob on the sofa? No, Bob isn't on the sofa. Come on, Ricky. Let's find Bob. Look, Ricky. Bob is on the bed! Ricky!", + "user_language": "็‘žๅฅ‡ๆ˜ฏ้ฒๅ‹ƒ็š„็‹—ใ€‚็‘žๅฅ‡ๅพˆไผคๅฟƒใ€‚ไป–ๆ‰พไธๅˆฐ้ฒๅ‹ƒใ€‚้ฒๅ‹ƒๅœจๆฉฑๆŸœไธ‹้ขๅ—๏ผŸไธ๏ผŒไป–ไธๅœจใ€‚้ฒๅ‹ƒๅœจๅ“ช้‡Œ๏ผŸไป–ๅœจๅŒ…้‡Œๅ—๏ผŸไธ๏ผŒ็‘žๅฅ‡ใ€‚้ฒๅ‹ƒไธๅœจๅŒ…้‡Œใ€‚้ฒๅ‹ƒๅœจๆฒ™ๅ‘ไธŠๅ—๏ผŸไธ๏ผŒ้ฒๅ‹ƒไธๅœจๆฒ™ๅ‘ไธŠใ€‚ๆฅๅง๏ผŒ็‘žๅฅ‡ใ€‚ๆˆ‘ไปฌไธ€่ตทๆ‰พ้ฒๅ‹ƒๅงใ€‚็œ‹๏ผŒ็‘žๅฅ‡ใ€‚้ฒๅ‹ƒๅœจๅบŠไธŠ๏ผ็‘žๅฅ‡๏ผ" + } + ], + "grammar": { + "where-questions": { + "title": "Where Questions", + "explanation": "Use 'Where is...?' for singular and 'Where are...?' for plural to ask about location.", + "examples": [ + { + "english": "Where is the cat?", + "translation": "็Œซๅœจๅ“ช้‡Œ๏ผŸ", + "explanation": "Use 'is' for singular subjects (the cat)" + }, + { + "english": "Where are the turtles?", + "translation": "ไนŒ้พŸๅœจๅ“ช้‡Œ๏ผŸ", + "explanation": "Use 'are' for plural subjects (the turtles)" + } + ] + }, + "prepositions-of-place": { + "title": "Prepositions of Place", + "explanation": "Prepositions tell us where something is located: in, on, under, near.", + "examples": [ + { + "english": "The cat is on the chair", + "translation": "็Œซๅœจๆค…ๅญไธŠ", + "explanation": "Use 'on' for objects resting on a surface" + }, + { + "english": "The hamster is in the cupboard", + "translation": "ไป“้ผ ๅœจๆฉฑๆŸœ้‡Œ", + "explanation": "Use 'in' for being inside an enclosed space" + }, + { + "english": "The turtles are under the sofa", + "translation": "ไนŒ้พŸๅœจๆฒ™ๅ‘ไธ‹้ข", + "explanation": "Use 'under' for being beneath something" + }, + { + "english": "One is near the sofa", + "translation": "ไธ€ๅชๅœจๆฒ™ๅ‘้™„่ฟ‘", + "explanation": "Use 'near' for being close to something" + } + ] + }, + "yes-no-questions": { + "title": "Yes/No Questions with Location", + "explanation": "Use 'Is it...?' for singular and 'Are they...?' for plural yes/no questions about location.", + "examples": [ + { + "english": "Is it in the box? - Yes, it is. / No, it isn't.", + "translation": "ๅฎƒๅœจ็›’ๅญ้‡Œๅ—๏ผŸ- ๆ˜ฏ็š„๏ผŒๅฎƒๅœจใ€‚/ ไธ๏ผŒๅฎƒไธๅœจใ€‚", + "explanation": "Use 'Is it' for singular, answer with 'Yes, it is' or 'No, it isn't'" + }, + { + "english": "Are they under the bed? - Yes, they are. / No, they aren't.", + "translation": "ๅฎƒไปฌๅœจๅบŠไธ‹้ขๅ—๏ผŸ- ๆ˜ฏ็š„๏ผŒๅฎƒไปฌๅœจใ€‚/ ไธ๏ผŒๅฎƒไปฌไธๅœจใ€‚", + "explanation": "Use 'Are they' for plural, answer with 'Yes, they are' or 'No, they aren't'" + } + ] + }, + "contractions": { + "title": "Contractions", + "explanation": "Contractions combine two words into one: isn't = is not, aren't = are not, let's = let us.", + "examples": [ + { + "english": "isn't = is not", + "translation": "ไธๆ˜ฏ๏ผˆๅ•ๆ•ฐ๏ผ‰", + "explanation": "Negative contraction for 'is'" + }, + { + "english": "aren't = are not", + "translation": "ไธๆ˜ฏ๏ผˆๅคๆ•ฐ๏ผ‰", + "explanation": "Negative contraction for 'are'" + }, + { + "english": "Let's = Let us", + "translation": "่ฎฉๆˆ‘ไปฌ", + "explanation": "Suggestion contraction" + } + ] + } + }, + "fillInBlanks": [ + { + "sentence": "The cat is ___ the chair", + "options": ["on", "in", "under", "near"], + "correctAnswer": "on", + "explanation": "Use 'on' for objects resting on a surface", + "grammarFocus": "prepositions-of-place" + }, + { + "sentence": "The hamster is ___ the cupboard", + "options": ["in", "on", "under", "near"], + "correctAnswer": "in", + "explanation": "Use 'in' for being inside an enclosed space", + "grammarFocus": "prepositions-of-place" + }, + { + "sentence": "The turtles are ___ the sofa", + "options": ["under", "on", "in", "near"], + "correctAnswer": "under", + "explanation": "Use 'under' for being beneath something", + "grammarFocus": "prepositions-of-place" + }, + { + "sentence": "One hamster is ___ the sofa", + "options": ["near", "on", "in", "under"], + "correctAnswer": "near", + "explanation": "Use 'near' for being close to something", + "grammarFocus": "prepositions-of-place" + }, + { + "sentence": "___ is the cat?", + "options": ["Where", "What", "Who", "How"], + "correctAnswer": "Where", + "explanation": "Use 'Where' to ask about location", + "grammarFocus": "where-questions" + }, + { + "sentence": "___ are the turtles?", + "options": ["Where", "What", "Who", "How"], + "correctAnswer": "Where", + "explanation": "Use 'Where' to ask about location", + "grammarFocus": "where-questions" + }, + { + "sentence": "Is Bob ___ the bag? No, he isn't", + "options": ["in", "on", "under", "near"], + "correctAnswer": "in", + "explanation": "Use 'in' for being inside something like a bag", + "grammarFocus": "prepositions-of-place" + }, + { + "sentence": "Bob is ___ the bed!", + "options": ["on", "in", "under", "near"], + "correctAnswer": "on", + "explanation": "Use 'on' for being on top of a surface like a bed", + "grammarFocus": "prepositions-of-place" + }, + { + "sentence": "Is it in the box? Yes, it ___", + "options": ["is", "isn't", "are", "aren't"], + "correctAnswer": "is", + "explanation": "Answer 'Yes, it is' for affirmative singular", + "grammarFocus": "yes-no-questions" + }, + { + "sentence": "Are they under the desk? No, they ___", + "options": ["aren't", "are", "isn't", "is"], + "correctAnswer": "aren't", + "explanation": "Answer 'No, they aren't' for negative plural", + "grammarFocus": "yes-no-questions" + }, + { + "sentence": "___ find Bob!", + "options": ["Let's", "Lets", "Let", "Let us"], + "correctAnswer": "Let's", + "explanation": "Use the contraction 'Let's' for suggestions", + "grammarFocus": "contractions" + }, + { + "sentence": "Ricky cannot ___ Bob", + "options": ["find", "finds", "finding", "found"], + "correctAnswer": "find", + "explanation": "Use base form of verb after 'cannot'", + "grammarFocus": "verbs" + } + ], + "corrections": [ + { + "correct": "Where are the turtles?", + "incorrect": "Where is the turtles?", + "explanation": "Use 'are' with plural nouns like 'turtles'", + "grammarFocus": "where-questions" + }, + { + "correct": "The cat is on the chair", + "incorrect": "The cat are on the chair", + "explanation": "Use 'is' with singular nouns like 'the cat'", + "grammarFocus": "subject-verb-agreement" + }, + { + "correct": "Are they under the sofa?", + "incorrect": "Is they under the sofa?", + "explanation": "Use 'Are' with plural pronoun 'they'", + "grammarFocus": "yes-no-questions" + }, + { + "correct": "Where is the dog?", + "incorrect": "Where the dog is?", + "explanation": "In questions, put 'is' before the subject after the question word", + "grammarFocus": "question-formation" + }, + { + "correct": "Bob isn't in the bag", + "incorrect": "Bob is not in the bag", + "explanation": "Use the contraction 'isn't' instead of 'is not'", + "grammarFocus": "contractions" + }, + { + "correct": "No, it isn't", + "incorrect": "Yes, it isn't", + "explanation": "Use 'No' with negative answers", + "grammarFocus": "yes-no-questions" + }, + { + "correct": "The hamsters are in the cupboard", + "incorrect": "The hamsters is in the cupboard", + "explanation": "Use 'are' with plural nouns like 'hamsters'", + "grammarFocus": "subject-verb-agreement" + }, + { + "correct": "It is on the shelf", + "incorrect": "It are on the shelf", + "explanation": "Use 'is' with singular pronoun 'it'", + "grammarFocus": "subject-verb-agreement" + } + ], + "exercises": { + "pet_location_practice": { + "type": "location_practice", + "instructions": "Ask and answer: Where is/are the pet(s)?", + "items": [ + { "question": "Where is the cat?", "answer": "It is on the chair.", "user_language_q": "็Œซๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๆค…ๅญไธŠใ€‚" }, + { "question": "Where are the turtles?", "answer": "They are under the sofa.", "user_language_q": "ไนŒ้พŸๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒไปฌๅœจๆฒ™ๅ‘ไธ‹้ขใ€‚" }, + { "question": "Where is the hamster?", "answer": "It is in the cupboard.", "user_language_q": "ไป“้ผ ๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๆฉฑๆŸœ้‡Œใ€‚" }, + { "question": "Where is the rabbit?", "answer": "It is in the drawer.", "user_language_q": "ๅ…”ๅญๅœจๅ“ช้‡Œ๏ผŸ", "user_language_a": "ๅฎƒๅœจๆŠฝๅฑ‰้‡Œใ€‚" } + ] + }, + "story_comprehension": { + "type": "true_false", + "instructions": "Where is Bob? - True or False?", + "items": [ + { "statement": "Bob is in the bag", "answer": false, "user_language": "้ฒๅ‹ƒๅœจๅŒ…้‡Œ" }, + { "statement": "Bob is on the sofa", "answer": false, "user_language": "้ฒๅ‹ƒๅœจๆฒ™ๅ‘ไธŠ" }, + { "statement": "Bob is under the cupboard", "answer": false, "user_language": "้ฒๅ‹ƒๅœจๆฉฑๆŸœไธ‹้ข" }, + { "statement": "Bob is on the bed", "answer": true, "user_language": "้ฒๅ‹ƒๅœจๅบŠไธŠ" }, + { "statement": "Ricky is sad", "answer": true, "user_language": "็‘žๅฅ‡ๅพˆไผคๅฟƒ" }, + { "statement": "Ricky can find Bob easily", "answer": false, "user_language": "็‘žๅฅ‡ๅพˆๅฎนๆ˜“ๆ‰พๅˆฐ้ฒๅ‹ƒ" } + ] + }, + "letter_matching": { + "type": "matching", + "instructions": "Match each word with the correct starting letter", + "items": [ + { "word": "wolf", "letter": "W", "user_language": "็‹ผ" }, + { "word": "fox", "letter": "X", "user_language": "็‹็‹ธ" }, + { "word": "yo-yo", "letter": "Y", "user_language": "ๆ‚ ๆ‚ ็ƒ" }, + { "word": "zebra", "letter": "Z", "user_language": "ๆ–‘้ฉฌ" }, + { "word": "water", "letter": "W", "user_language": "ๆฐด" }, + { "word": "box", "letter": "X", "user_language": "็›’ๅญ" }, + { "word": "yacht", "letter": "Y", "user_language": "ๆธธ่‰‡" }, + { "word": "zoo", "letter": "Z", "user_language": "ๅŠจ็‰ฉๅ›ญ" } + ] + } + }, + "thematic_questions": { + "location_questions_furniture": [ + { + "id": "tq1", + "question": "Where is the cat?", + "question_user_language": "็Œซๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It is on the chair.", + "The cat is on the chair.", + "On the chair." + ], + "theme": "location_questions_furniture" + }, + { + "id": "tq2", + "question": "Where are the turtles?", + "question_user_language": "ไนŒ้พŸๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "They are under the sofa.", + "The turtles are under the sofa.", + "Under the sofa." + ], + "theme": "location_questions_furniture" + }, + { + "id": "tq3", + "question": "Where is the hamster?", + "question_user_language": "ไป“้ผ ๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It is in the cupboard.", + "The hamster is in the cupboard.", + "In the cupboard." + ], + "theme": "location_questions_furniture" + }, + { + "id": "tq4", + "question": "Where is the book?", + "question_user_language": "ไนฆๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It is on the table.", + "The book is on the table.", + "On the table." + ], + "theme": "location_questions_furniture" + }, + { + "id": "tq5", + "question": "Where is the bag?", + "question_user_language": "ๅŒ…ๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It is near the sofa.", + "The bag is near the sofa.", + "Near the sofa." + ], + "theme": "location_questions_furniture" + } + ], + "yes_no_location": [ + { + "id": "tq6", + "question": "Is the cat on the chair?", + "question_user_language": "็Œซๅœจๆค…ๅญไธŠๅ—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Yes, it is.", + "Yes, the cat is on the chair.", + "Yes." + ], + "theme": "yes_no_location" + }, + { + "id": "tq7", + "question": "Is Bob in the bag?", + "question_user_language": "้ฒๅ‹ƒๅœจๅŒ…้‡Œๅ—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "No, he isn't.", + "No, Bob isn't in the bag.", + "No." + ], + "theme": "yes_no_location" + }, + { + "id": "tq8", + "question": "Are the turtles under the sofa?", + "question_user_language": "ไนŒ้พŸๅœจๆฒ™ๅ‘ไธ‹้ขๅ—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Yes, they are.", + "Yes, the turtles are under the sofa.", + "Yes." + ], + "theme": "yes_no_location" + }, + { + "id": "tq9", + "question": "Is the rabbit in the drawer?", + "question_user_language": "ๅ…”ๅญๅœจๆŠฝๅฑ‰้‡Œๅ—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Yes, it is.", + "Yes, the rabbit is in the drawer.", + "Yes." + ], + "theme": "yes_no_location" + }, + { + "id": "tq10", + "question": "Is the dog under the desk?", + "question_user_language": "็‹—ๅœจๆกŒๅญไธ‹้ขๅ—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "No, it isn't.", + "No, the dog isn't under the desk.", + "No." + ], + "theme": "yes_no_location" + } + ], + "story_comprehension": [ + { + "id": "tq11", + "question": "Who is Bob's dog?", + "question_user_language": "่ฐๆ˜ฏ้ฒๅ‹ƒ็š„็‹—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Ricky is Bob's dog.", + "Ricky.", + "His name is Ricky." + ], + "theme": "story_comprehension" + }, + { + "id": "tq12", + "question": "How does Ricky feel?", + "question_user_language": "็‘žๅฅ‡ๆ„Ÿ่ง‰ๆ€Žไนˆๆ ท๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Ricky is sad.", + "He is sad.", + "Sad." + ], + "theme": "story_comprehension" + }, + { + "id": "tq13", + "question": "Where is Bob at the end of the story?", + "question_user_language": "ๆ•…ไบ‹ๆœ€ๅŽ้ฒๅ‹ƒๅœจๅ“ช้‡Œ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Bob is on the bed.", + "He is on the bed.", + "On the bed." + ], + "theme": "story_comprehension" + }, + { + "id": "tq14", + "question": "What does Ricky want to find?", + "question_user_language": "็‘žๅฅ‡ๆƒณๆ‰พไป€ไนˆ๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Ricky wants to find Bob.", + "He wants to find Bob.", + "Bob." + ], + "theme": "story_comprehension" + } + ], + "vocabulary_letters": [ + { + "id": "tq15", + "question": "What letter does 'wolf' start with?", + "question_user_language": "'wolf' ไปฅไป€ไนˆๅญ—ๆฏๅผ€ๅคด๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It starts with W.", + "W.", + "The letter W." + ], + "theme": "vocabulary_letters" + }, + { + "id": "tq16", + "question": "What letter does 'zebra' start with?", + "question_user_language": "'zebra' ไปฅไป€ไนˆๅญ—ๆฏๅผ€ๅคด๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "It starts with Z.", + "Z.", + "The letter Z." + ], + "theme": "vocabulary_letters" + }, + { + "id": "tq17", + "question": "What word starts with Y?", + "question_user_language": "ไป€ไนˆๅ•่ฏไปฅ Y ๅผ€ๅคด๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Yo-yo starts with Y.", + "Yo-yo.", + "Yacht." + ], + "theme": "vocabulary_letters" + }, + { + "id": "tq18", + "question": "Can you name something that has the letter X?", + "question_user_language": "ไฝ ่ƒฝ่ฏดๅ‡บไธ€ไธชๅซๆœ‰ๅญ—ๆฏ X ็š„ๅ•่ฏๅ—๏ผŸ", + "tts_enabled": true, + "example_responses": [ + "Box has the letter X.", + "Box.", + "Fox.", + "Six." + ], + "theme": "vocabulary_letters" + } + ] + }, + "statistics": { + "vocabulary_count": 41, + "phrases_count": 17, + "dialogs_count": 2, + "texts_count": 1, + "exercises_count": 3, + "fillInBlanks_count": 12, + "corrections_count": 8, + "thematic_questions_count": 18, + "estimated_completion_time": 4 + } +} diff --git a/src/core/GameLoader.js b/src/core/GameLoader.js index a58c6b6..a8cc844 100644 --- a/src/core/GameLoader.js +++ b/src/core/GameLoader.js @@ -27,6 +27,7 @@ class GameLoader extends Module { '../games/StoryReader.js', '../games/LetterDiscovery.js', '../games/QuizGame.js', + '../games/ThematicQuestions.js', '../games/AdventureReader.js', '../games/WizardSpellCaster.js', '../games/WordStorm.js', @@ -34,11 +35,11 @@ class GameLoader extends Module { '../games/WordDiscovery.js', '../games/GrammarDiscovery.js', '../games/FillTheBlank.js', - '../games/StoryBuilder.js', '../games/RiverRun.js', '../games/ChineseStudy.js', '../games/WhackAMoleHard.js', - '../games/MarioEducational.js' + '../games/MarioEducational.js', + '../games/SentenceInvaders.js' // All current games with Module architecture ]; diff --git a/src/gameHelpers/MarioEducational/PhysicsEngine.js b/src/gameHelpers/MarioEducational/PhysicsEngine.js index 926e047..ab8bf5f 100644 --- a/src/gameHelpers/MarioEducational/PhysicsEngine.js +++ b/src/gameHelpers/MarioEducational/PhysicsEngine.js @@ -157,11 +157,36 @@ export class PhysicsEngine { } }); - // Boulder collisions (grounded boulders only) + // Boulder collisions (grounded boulders act as solid platforms) boulders.forEach(boulder => { if (boulder.hasLanded && this.isColliding(mario, boulder)) { - console.log(`๐Ÿชจ Mario hit by grounded boulder - restarting level`); - if (onMarioDeath) onMarioDeath(); + // Determine collision direction based on overlap + const overlapLeft = (mario.x + mario.width) - boulder.x; + const overlapRight = (boulder.x + boulder.width) - mario.x; + const overlapTop = (mario.y + mario.height) - boulder.y; + const overlapBottom = (boulder.y + boulder.height) - mario.y; + + // Find the smallest overlap to determine collision side + const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom); + + if (minOverlap === overlapTop && mario.velocityY > 0) { + // Landing on top of boulder + mario.y = boulder.y - mario.height; + mario.velocityY = 0; + mario.onGround = true; + } else if (minOverlap === overlapBottom && mario.velocityY < 0) { + // Hitting boulder from below + mario.y = boulder.y + boulder.height; + mario.velocityY = 0; + } else if (minOverlap === overlapLeft && mario.velocityX > 0) { + // Hitting boulder from left + mario.x = boulder.x - mario.width; + mario.velocityX = 0; + } else if (minOverlap === overlapRight && mario.velocityX < 0) { + // Hitting boulder from right + mario.x = boulder.x + boulder.width; + mario.velocityX = 0; + } } }); diff --git a/src/gameHelpers/MarioEducational/Renderer.js b/src/gameHelpers/MarioEducational/Renderer.js index a680e6e..9f68302 100644 --- a/src/gameHelpers/MarioEducational/Renderer.js +++ b/src/gameHelpers/MarioEducational/Renderer.js @@ -224,7 +224,7 @@ export class Renderer { return; } - const pipeHeight = 40; // Reduced from 60 + const pipeHeight = 25; // Much smaller pipe const pipeY = plant.y + plant.height - pipeHeight; // Pipe @@ -233,18 +233,18 @@ export class Renderer { // Pipe rim ctx.fillStyle = '#3A9F3A'; - ctx.fillRect(plant.x - 3, pipeY, plant.width + 6, 8); // Reduced from -5, +10, 10 + ctx.fillRect(plant.x - 2, pipeY, plant.width + 4, 5); // Pipe border ctx.strokeStyle = '#000'; - ctx.lineWidth = 2; + ctx.lineWidth = 1; ctx.strokeRect(plant.x, pipeY, plant.width, pipeHeight); - ctx.strokeRect(plant.x - 3, pipeY, plant.width + 6, 8); + ctx.strokeRect(plant.x - 2, pipeY, plant.width + 4, 5); // Plant head (if extended) if (plant.extended > 0) { const headY = pipeY - plant.extended; - const headSize = 20; // Reduced from 30 + const headSize = 10; // Much smaller head (radius, so diameter = 20px) // Head ctx.fillStyle = '#FF0000'; @@ -256,20 +256,20 @@ export class Renderer { // Spots ctx.fillStyle = '#FFF'; ctx.beginPath(); - ctx.arc(plant.x + plant.width / 2 - 7, headY - 3, 4, 0, Math.PI * 2); - ctx.arc(plant.x + plant.width / 2 + 7, headY - 3, 4, 0, Math.PI * 2); + ctx.arc(plant.x + plant.width / 2 - 4, headY - 2, 2, 0, Math.PI * 2); + ctx.arc(plant.x + plant.width / 2 + 4, headY - 2, 2, 0, Math.PI * 2); ctx.fill(); // Mouth (open/close animation) const mouthOpen = Math.sin(Date.now() / 200) > 0; if (mouthOpen) { ctx.fillStyle = '#000'; - ctx.fillRect(plant.x + plant.width / 2 - 8, headY + 5, 16, 6); + ctx.fillRect(plant.x + plant.width / 2 - 5, headY + 3, 10, 3); } // Stem ctx.fillStyle = '#2D882D'; - ctx.fillRect(plant.x + plant.width / 2 - 3, headY, 6, plant.extended); + ctx.fillRect(plant.x + plant.width / 2 - 2, headY, 4, plant.extended); } }); } @@ -318,26 +318,46 @@ export class Renderer { */ renderBoulders(ctx, boulders) { boulders.forEach(boulder => { - // Boulder body - ctx.fillStyle = '#808080'; - ctx.beginPath(); - ctx.arc(boulder.x, boulder.y, boulder.radius, 0, Math.PI * 2); - ctx.fill(); + // Support both circle (radius) and rectangle (width/height) boulders + if (boulder.radius) { + // Circle boulder + ctx.fillStyle = boulder.color || '#808080'; + ctx.beginPath(); + ctx.arc(boulder.x, boulder.y, boulder.radius, 0, Math.PI * 2); + ctx.fill(); - // Cracks/texture - ctx.strokeStyle = '#606060'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(boulder.x - boulder.radius * 0.5, boulder.y - boulder.radius * 0.3); - ctx.lineTo(boulder.x + boulder.radius * 0.5, boulder.y + boulder.radius * 0.3); - ctx.stroke(); + // Cracks/texture + ctx.strokeStyle = '#606060'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(boulder.x - boulder.radius * 0.5, boulder.y - boulder.radius * 0.3); + ctx.lineTo(boulder.x + boulder.radius * 0.5, boulder.y + boulder.radius * 0.3); + ctx.stroke(); - // Border - ctx.strokeStyle = '#000'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.arc(boulder.x, boulder.y, boulder.radius, 0, Math.PI * 2); - ctx.stroke(); + // Border + ctx.strokeStyle = '#000'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(boulder.x, boulder.y, boulder.radius, 0, Math.PI * 2); + ctx.stroke(); + } else { + // Rectangle boulder (parabolic arc style) + ctx.fillStyle = boulder.color || '#696969'; + ctx.fillRect(boulder.x, boulder.y, boulder.width, boulder.height); + + // Cracks/texture + ctx.strokeStyle = '#505050'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(boulder.x + boulder.width * 0.2, boulder.y + boulder.height * 0.3); + ctx.lineTo(boulder.x + boulder.width * 0.8, boulder.y + boulder.height * 0.7); + ctx.stroke(); + + // Border + ctx.strokeStyle = '#000'; + ctx.lineWidth = 2; + ctx.strokeRect(boulder.x, boulder.y, boulder.width, boulder.height); + } }); } @@ -524,8 +544,8 @@ export class Renderer { ctx.lineWidth = 2; ctx.font = 'bold 16px Arial'; ctx.textAlign = 'center'; - ctx.strokeText(`${currentLevel + 1}`, finishLine.x + 35, finishLine.y + 25); - ctx.fillText(`${currentLevel + 1}`, finishLine.x + 35, finishLine.y + 25); + ctx.strokeText(`${currentLevel}`, finishLine.x + 35, finishLine.y + 25); + ctx.fillText(`${currentLevel}`, finishLine.x + 35, finishLine.y + 25); } /** @@ -578,10 +598,6 @@ export class Renderer { // Text style ctx.fillStyle = '#FFF'; ctx.font = 'bold 16px Arial'; - ctx.textAlign = 'left'; - - // Lives - ctx.fillText(`โค๏ธ Lives: ${gameState.lives}`, 10, 25); // Score ctx.textAlign = 'center'; @@ -589,7 +605,7 @@ export class Renderer { // Level ctx.textAlign = 'right'; - ctx.fillText(`Level: ${gameState.currentLevel + 1}`, config.canvasWidth - 10, 25); + ctx.fillText(`Level: ${gameState.currentLevel}`, config.canvasWidth - 10, 25); } /** diff --git a/src/gameHelpers/MarioEducational/enemies/Catapult.js b/src/gameHelpers/MarioEducational/enemies/Catapult.js index f2376d7..f4ce4b2 100644 --- a/src/gameHelpers/MarioEducational/enemies/Catapult.js +++ b/src/gameHelpers/MarioEducational/enemies/Catapult.js @@ -150,7 +150,7 @@ export class Catapult { * @param {Array} stones - Stones array (for onagers) * @param {Function} playSound - Sound callback */ - static update(catapults, mario, boulders, stones, playSound) { + static update(catapults, mario, boulders, stones, playSound, canvasHeight) { const currentTime = Date.now(); catapults.forEach(catapult => { @@ -159,56 +159,82 @@ export class Catapult { // Check if it's time to shoot if (currentTime - catapult.lastShot > catapult.shootCooldown) { - const distanceToMario = Math.abs(catapult.x - mario.x); + // ONAGER: Check minimum range - don't fire if Mario is too close + if (catapult.isOnager) { + const distanceToMario = Math.abs(catapult.x - mario.x); + const minimumRange = 300; - // Catapult shoots boulders (single target) - if (!catapult.isOnager && distanceToMario < 600) { - // Calculate trajectory to Mario - const dx = mario.x - catapult.x; - const dy = mario.y - catapult.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - const speed = 8; - const velocityX = (dx / distance) * speed; - const velocityY = (dy / distance) * speed; - - boulders.push({ - x: catapult.x + catapult.width / 2, - y: catapult.y, - velocityX: velocityX, - velocityY: velocityY, - radius: 20, - type: 'boulder', - launched: true - }); - - catapult.lastShot = currentTime; - if (playSound) playSound('jump'); // Boulder launch sound - console.log(`๐Ÿชจ Catapult launched boulder towards Mario!`); + if (distanceToMario < minimumRange) { + console.log(`๐Ÿ›๏ธ Onager held fire - Mario too close! Distance: ${distanceToMario.toFixed(0)}px`); + return; + } } - // Onager shoots stone rain (area attack) - else if (catapult.isOnager && distanceToMario < 800) { - // Create stone rain above Mario's area - const stoneCount = 8 + Math.floor(Math.random() * 5); // 8-12 stones - for (let i = 0; i < stoneCount; i++) { - const offsetX = (Math.random() - 0.5) * 400; // Spread 400px around Mario + // Target Mario's position with imperfect aim + const aimOffset = 100 + Math.random() * 150; // 100-250 pixel spread + const aimDirection = Math.random() < 0.5 ? -1 : 1; + const targetX = mario.x + (aimOffset * aimDirection); + const targetY = canvasHeight - 50; // Ground level + + if (catapult.isOnager) { + // ONAGER: Fire 8 small stones in spread pattern with parabolic trajectory + for (let stone = 0; stone < 8; stone++) { + const randomSpreadX = (Math.random() - 0.5) * 400; // ยฑ200px spread + const randomSpreadY = (Math.random() - 0.5) * 100; // ยฑ50px spread + const stoneTargetX = targetX + randomSpreadX; + const stoneTargetY = targetY + randomSpreadY; + + // Calculate parabolic trajectory + const deltaX = stoneTargetX - catapult.x; + const deltaY = stoneTargetY - catapult.y; + const time = 5 + Math.random() * 2; // 5-7 seconds flight time + const velocityX = deltaX / (time * 60); + const velocityY = (deltaY - 0.5 * 0.015 * time * time * 60 * 60) / (time * 60); stones.push({ - x: mario.x + offsetX, - y: -50 - Math.random() * 100, // Start above screen - velocityX: (Math.random() - 0.5) * 2, - velocityY: 2 + Math.random() * 3, - width: 15 + Math.random() * 10, - height: 15 + Math.random() * 10, + x: catapult.x + 30 + (Math.random() - 0.5) * 20, + y: catapult.y - 10 + (Math.random() - 0.5) * 10, + width: 8, + height: 8, + velocityX: velocityX, + velocityY: velocityY, + color: '#A0522D', type: 'stone', - rotation: Math.random() * Math.PI * 2 + sourceCatapultX: catapult.x, + sourceCatapultY: catapult.y }); } catapult.lastShot = currentTime; - if (playSound) playSound('enemy_defeat'); // Different sound for stone rain - console.log(`โ˜„๏ธ Onager launched stone rain (${stoneCount} stones)!`); + if (playSound) playSound('enemy_defeat'); + console.log(`๐Ÿ›๏ธ Onager fired 8 stones in spread pattern!`); + } else { + // CATAPULT: Fire single boulder with parabolic trajectory + const deltaX = targetX - catapult.x; + const deltaY = targetY - catapult.y; + const time = 7.5; // 7.5 seconds flight time + const velocityX = deltaX / (time * 60); + const velocityY = (deltaY - 0.5 * 0.01 * time * time * 60 * 60) / (time * 60); + + boulders.push({ + x: catapult.x + 30, + y: catapult.y - 10, + width: 25, + height: 25, + velocityX: velocityX, + velocityY: velocityY, + color: '#696969', + type: 'boulder', + hasLanded: false, + sourceCatapultX: catapult.x, + sourceCatapultY: catapult.y, + health: 2, + maxHealth: 2 + }); + + catapult.lastShot = currentTime; + if (playSound) playSound('jump'); + console.log(`๐Ÿน Catapult fired boulder towards x=${targetX.toFixed(0)}`); } } }); @@ -224,52 +250,69 @@ export class Catapult { * @returns {Array} - Updated boulders array */ static updateBoulders(boulders, mario, platforms, walls, onImpact) { - const GRAVITY = 0.3; - const updatedBoulders = []; + if (!boulders || !Array.isArray(boulders)) return; + + const GRAVITY = 0.01; // Ultra-light gravity for parabolic arc + const canvasHeight = 690; // Default canvas height + + // Iterate backwards to safely remove boulders + for (let index = boulders.length - 1; index >= 0; index--) { + const boulder = boulders[index]; + + if (boulder.hasLanded) continue; // Don't update landed boulders - boulders.forEach((boulder, index) => { // Apply physics boulder.velocityY += GRAVITY; boulder.x += boulder.velocityX; boulder.y += boulder.velocityY; + // Check ground collision + const groundLevel = canvasHeight - 50; + if (boulder.y + boulder.height >= groundLevel) { + if (onImpact) { + onImpact(boulder, index, boulder.x, groundLevel - boulder.height, null, -1, 'ground'); + } + continue; + } + + let hasHit = false; + // Check collision with platforms - let hitPlatform = false; platforms.forEach((platform, platformIndex) => { - if (this._isCollidingCircleRect(boulder, platform)) { - hitPlatform = true; + if (!hasHit && this._isCollidingRectRect(boulder, platform)) { if (onImpact) { - onImpact(boulder, index, boulder.x, boulder.y, platform, platformIndex, 'platform'); + onImpact(boulder, index, boulder.x, platform.y - boulder.height, platform, platformIndex, 'platform'); } + hasHit = true; } }); - // Check collision with walls - let hitWall = false; - walls.forEach((wall, wallIndex) => { - if (this._isCollidingCircleRect(boulder, wall)) { - hitWall = true; - if (onImpact) { - onImpact(boulder, index, boulder.x, boulder.y, wall, wallIndex, 'wall'); + // Check collision with walls (boulder destroys wall) + if (!hasHit) { + walls.forEach((wall, wallIndex) => { + if (!hasHit && this._isCollidingRectRect(boulder, wall)) { + if (onImpact) { + onImpact(boulder, index, boulder.x, boulder.y, wall, wallIndex, 'wall'); + } + hasHit = true; } - } - }); + }); + } // Check collision with Mario - if (this._isCollidingCircleRect(boulder, mario)) { + if (!hasHit && this._isCollidingRectRect(boulder, mario)) { if (onImpact) { onImpact(boulder, index, boulder.x, boulder.y, mario, -1, 'mario'); } - return; // Remove boulder + hasHit = true; + continue; } - // Remove if out of bounds or hit something - if (!hitPlatform && !hitWall && boulder.y < 1000) { - updatedBoulders.push(boulder); + // Remove boulders that go off-screen + if (boulder.x < -100 || boulder.x > 4000 || boulder.y > canvasHeight + 100) { + boulders.splice(index, 1); } - }); - - return updatedBoulders; + } } /** @@ -281,7 +324,10 @@ export class Catapult { * @returns {Array} - Updated stones array */ static updateStones(stones, mario, platforms, onImpact) { - const GRAVITY = 0.5; + if (!stones || !Array.isArray(stones)) return []; + + const GRAVITY = 0.015; // Lighter gravity for parabolic arc + const canvasHeight = 690; const updatedStones = []; stones.forEach((stone, index) => { @@ -289,29 +335,39 @@ export class Catapult { stone.velocityY += GRAVITY; stone.x += stone.velocityX; stone.y += stone.velocityY; - stone.rotation += 0.1; + + // Check ground collision + const groundLevel = canvasHeight - 50; + if (stone.y + stone.height >= groundLevel) { + if (onImpact) { + onImpact(stone, index, 'ground'); + } + return; // Don't keep this stone + } + + let hasHit = false; // Check collision with platforms - let hitPlatform = false; platforms.forEach(platform => { - if (this._isCollidingRectRect(stone, platform)) { - hitPlatform = true; + if (!hasHit && this._isCollidingRectRect(stone, platform)) { if (onImpact) { onImpact(stone, index, 'platform'); } + hasHit = true; } }); // Check collision with Mario - if (this._isCollidingRectRect(stone, mario)) { + if (!hasHit && this._isCollidingRectRect(stone, mario)) { if (onImpact) { onImpact(stone, index, 'mario'); } - return; // Remove stone + hasHit = true; + return; // Don't keep this stone } // Keep stone if not hit and still on screen - if (!hitPlatform && stone.y < 1000) { + if (!hasHit && stone.x >= -100 && stone.x <= 4000 && stone.y <= canvasHeight + 100) { updatedStones.push(stone); } }); diff --git a/src/gameHelpers/MarioEducational/enemies/PiranhaPlant.js b/src/gameHelpers/MarioEducational/enemies/PiranhaPlant.js index 79264b9..b8324f8 100644 --- a/src/gameHelpers/MarioEducational/enemies/PiranhaPlant.js +++ b/src/gameHelpers/MarioEducational/enemies/PiranhaPlant.js @@ -57,16 +57,8 @@ export class PiranhaPlant { const currentTime = Date.now(); plants.forEach(plant => { - // If plant is flattened, count down and make it a platform + // If plant is flattened, it stays dead permanently if (plant.flattened) { - if (plant.flattenedTimer > 0) { - plant.flattenedTimer--; - } else { - // Plant recovers after timer expires - plant.flattened = false; - plant.extended = 0; - plant.extending = true; - } return; // Don't animate or shoot while flattened } @@ -122,9 +114,9 @@ export class PiranhaPlant { if (!plant.visible) continue; // Only check collision when plant is extended - if (plant.extended > 15) { + if (plant.extended > 10) { const headY = plant.y - plant.extended; - const headRadius = 20; + const headRadius = 10; // Simple circle-rectangle collision const closestX = Math.max(mario.x, Math.min(plant.x + plant.width / 2, mario.x + mario.width)); diff --git a/src/games/AdventureReader.js b/src/games/AdventureReader.js index 9152c39..0b777fa 100644 --- a/src/games/AdventureReader.js +++ b/src/games/AdventureReader.js @@ -423,7 +423,7 @@ class AdventureReader extends Module { style.id = cssId; style.textContent = ` .adventure-reader-wrapper { - height: 100vh; + height: 75vh; display: flex; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; diff --git a/src/games/ChineseStudy.js b/src/games/ChineseStudy.js index 2ad2ce7..cac9de4 100644 --- a/src/games/ChineseStudy.js +++ b/src/games/ChineseStudy.js @@ -51,6 +51,11 @@ class ChineseStudy extends Module { return 0; } + // Only compatible with Chinese content + if (content.language !== 'zh-CN') { + return 0; + } + let score = 30; if (typeof content.vocabulary === 'object') { diff --git a/src/games/GrammarDiscovery.js b/src/games/GrammarDiscovery.js index 0bde951..67415f4 100644 --- a/src/games/GrammarDiscovery.js +++ b/src/games/GrammarDiscovery.js @@ -781,19 +781,22 @@ class GrammarDiscovery extends Module { ๐Ÿ’ก Simple Examples
- ${this._simpleExamples.map((example, index) => ` + ${this._simpleExamples.map((example, index) => { + const text = example.chinese || example.text || example.sentence || ''; + const safeText = text.replace(/'/g, "\\'"); + return `
-
${example.chinese || example.text || example.sentence}
-
${example.english || example.translation}
+
${text}
+
${example.english || example.translation || ''}
${example.pronunciation || example.prononciation || ''}
${example.explanation || example.breakdown || ''}
-
- `).join('')} + `;}).join('')}
@@ -870,19 +873,22 @@ class GrammarDiscovery extends Module { ๐Ÿงฉ Complex Examples
- ${this._complexExamples.map((example, index) => ` + ${this._complexExamples.map((example, index) => { + const text = example.chinese || example.text || example.sentence || ''; + const safeText = text.replace(/'/g, "\\'"); + return `
-
${example.chinese || example.text || example.sentence}
-
${example.english || example.translation}
+
${text}
+
${example.english || example.translation || ''}
${example.pronunciation || example.prononciation || ''}
${example.explanation || example.breakdown || ''}
-
- `).join('')} + `;}).join('')}
diff --git a/src/games/LetterDiscovery.js b/src/games/LetterDiscovery.js index 29e1244..c109f3d 100644 --- a/src/games/LetterDiscovery.js +++ b/src/games/LetterDiscovery.js @@ -71,13 +71,9 @@ class LetterDiscovery extends Module { static getCompatibilityScore(content) { const letters = content?.letters || content?.rawContent?.letters; - // Try to create letters from vocabulary if direct letters not found - let lettersData = letters; - if (!lettersData && content?.vocabulary) { - lettersData = this._createLettersFromVocabulary(content.vocabulary); - } - - if (!lettersData || Object.keys(lettersData).length === 0) { + // ONLY accept content with explicit letters structure + // Do NOT create letters from vocabulary as fallback + if (!letters || Object.keys(letters).length === 0) { return { score: 0, reason: 'No letter structure found', @@ -86,12 +82,12 @@ class LetterDiscovery extends Module { }; } - const letterCount = letters ? Object.keys(letters).length : 0; - const totalWords = letters ? Object.values(letters).reduce((sum, words) => sum + (words?.length || 0), 0) : 0; + const letterCount = Object.keys(letters).length; + const totalWords = Object.values(letters).reduce((sum, words) => sum + (words?.length || 0), 0); if (totalWords === 0) { return { - score: 0.2, + score: 0, reason: 'Letters found but no words', requirements: ['letters with words'], details: `Found ${letterCount} letters but no associated words` diff --git a/src/games/MarioEducational.js b/src/games/MarioEducational.js index 727fa17..79e3583 100644 --- a/src/games/MarioEducational.js +++ b/src/games/MarioEducational.js @@ -1965,20 +1965,33 @@ class MarioEducational extends Module { } _updateCatapults() { - Catapult.update(this._catapults, this._mario, this._boulders, this._stones, (sound) => soundSystem.play(sound)); + Catapult.update(this._catapults, this._mario, this._boulders, this._stones, (sound) => soundSystem.play(sound), this._config.canvasHeight); } _updateBoulders() { - this._boulders = Catapult.updateBoulders( + Catapult.updateBoulders( this._boulders, this._mario, this._platforms, this._walls, - (boulder, idx, x, y, obj, objIdx, type) => { - this._addParticles(x, y, '#808080'); - if (type === 'mario') this._restartLevel(); + (boulder, idx, impactX, impactY, obj, objIdx, type) => { + this._handleBoulderImpact(boulder, idx, impactX, impactY, obj, objIdx, type); } ); } _handleBoulderImpact(boulder, boulderIndex, impactX, impactY, hitObject = null, hitObjectIndex = -1, hitType = 'platform') { + // Special case: Boulder hit Mario while flying + if (hitType === 'mario') { + console.log(`๐Ÿ’€ Flying boulder hit Mario - restarting level`); + this._addParticles(this._mario.x, this._mario.y, '#FF0000'); // Red death particles + soundSystem.play('enemy_defeat'); + + // Remove the boulder + this._boulders.splice(boulderIndex, 1); + + // Restart level + this._restartLevel(); + return; + } + // Set boulder as landed at impact point boulder.x = impactX; boulder.y = impactY; @@ -2004,23 +2017,8 @@ class MarioEducational extends Module { } }); - // Reset Mario to level start if he's in explosion radius (50 pixels) - const marioDistance = Math.sqrt( - Math.pow(this._mario.x - (boulder.x + boulder.width/2), 2) + - Math.pow(this._mario.y - (boulder.y + boulder.height/2), 2) - ); - if (marioDistance < 50) { - console.log(`๐Ÿ’€ Mario killed by boulder explosion! Respawning at level start. Distance: ${marioDistance.toFixed(0)}`); - this._addParticles(this._mario.x, this._mario.y, '#FF0000'); // Red death particles - soundSystem.play('enemy_defeat'); - - // Reset Mario to start position like other enemy deaths - const level = this._levelData[this._currentLevelIndex]; - this._mario.x = level.startX; - this._mario.y = level.startY; - this._mario.velocityX = 0; - this._mario.velocityY = 0; - } + // Note: Mario is NOT killed by boulder landing explosion + // Only flying boulders kill Mario (handled above) // Boulder lands only on platforms, ground, stairs - NOT on walls or other boulders (they're destroyed on impact) if (hitObject && (hitType === 'platform' || hitType === 'stair')) { diff --git a/src/games/RiverRun.js b/src/games/RiverRun.js index 60e0457..3efce3f 100644 --- a/src/games/RiverRun.js +++ b/src/games/RiverRun.js @@ -1182,7 +1182,7 @@ class RiverRun extends Module { background: linear-gradient(180deg, #87CEEB 0%, #4682B4 50%, #2F4F4F 100%); position: relative; overflow: hidden; - height: 100vh; + height: 75vh; cursor: crosshair; } diff --git a/src/games/SentenceInvaders.js b/src/games/SentenceInvaders.js new file mode 100644 index 0000000..b4e4b41 --- /dev/null +++ b/src/games/SentenceInvaders.js @@ -0,0 +1,1446 @@ +import Module from '../core/Module.js'; +import { soundSystem } from '../gameHelpers/MarioEducational/SoundSystem.js'; + +/** + * SentenceInvaders - Space Invaders-style game with TTS sentences + * Aliens descend from the sky speaking sentences, players must select correct translations + */ +class SentenceInvaders extends Module { + constructor(name, dependencies, config = {}) { + super(name, ['eventBus']); + + // Validate dependencies + if (!dependencies.eventBus || !dependencies.content) { + throw new Error('SentenceInvaders requires eventBus and content dependencies'); + } + + this._eventBus = dependencies.eventBus; + this._content = dependencies.content; + this._config = { + container: null, + maxSentences: 40, + fallSpeedVhPerSecond: 10, // Slower than WordStorm (sentences take longer to read) + spawnRate: 5000, // ms between spawns (slower for TTS) + sentenceLifetime: 12000, // ms before sentence disappears + startingLives: 3, + ...config + }; + + // Game state + this._sentences = null; + this._score = 0; + this._level = 1; + this._lives = this._config.startingLives; + this._combo = 0; + this._isGamePaused = false; + this._isGameOver = false; + this._gameStartTime = null; + + // Game mechanics + this._fallingAliens = []; + this._currentSentenceIndex = 0; + this._spawnInterval = null; + this._activeTTS = null; // Track active TTS + this._aliensKilled = 0; + this._aliensMissed = 0; + + Object.seal(this); + } + + /** + * Get game metadata + * @returns {Object} Game metadata + */ + static getMetadata() { + return { + name: 'Sentence Invaders', + description: 'Space Invaders with TTS sentences - listen and translate!', + difficulty: 'intermediate', + category: 'action', + estimatedTime: 8, // minutes + skills: ['listening', 'translation', 'speed', 'concentration'] + }; + } + + /** + * Calculate compatibility score with content + * @param {Object} content - Content to check compatibility with + * @returns {Object} Compatibility score and details + */ + static getCompatibilityScore(content) { + let sentenceCount = 0; + + // Count from conversations (new format) + if (content?.conversations) { + sentenceCount += Object.values(content.conversations) + .filter(c => c.sentences).length; + } + + // Count from dialogs (SBS format) + if (content?.dialogs) { + const dialogLines = Object.values(content.dialogs) + .flatMap(d => d.lines || []) + .filter(line => line.text && line.user_language); + sentenceCount += dialogLines.length; + } + + // Count from phrases + if (content?.phrases) { + sentenceCount += Object.keys(content.phrases).length; + } + + // Count from grammar examples + if (content?.grammar) { + const grammarSentences = Object.values(content.grammar) + .flatMap(g => g.examples || []) + .filter(ex => (ex.target_language || ex.chinese) && (ex.user_language || ex.translation)); + sentenceCount += grammarSentences.length; + } + + // Count from vocabulary example sentences + if (content?.vocabulary) { + const vocabSentences = Object.values(content.vocabulary) + .filter(v => v.example_sentence && v.example_sentence.target && v.example_sentence.user); + sentenceCount += vocabSentences.length; + } + + if (sentenceCount < 10) { + return { + score: 0, + reason: `Insufficient sentences (${sentenceCount}/10 required)`, + requirements: ['dialogs', 'phrases', 'grammar', 'conversations'], + minSentences: 10, + details: 'Sentence Invaders needs at least 10 sentences for meaningful gameplay' + }; + } + + // Perfect score at 30+ sentences, partial score for 10-29 + const score = Math.min(sentenceCount / 30, 1); + + return { + score, + reason: `${sentenceCount} sentences available`, + requirements: ['dialogs', 'phrases', 'grammar', 'conversations'], + minSentences: 10, + optimalSentences: 30, + details: `Can create dynamic gameplay with ${Math.min(sentenceCount, 40)} sentences` + }; + } + + async init() { + this._validateNotDestroyed(); + + try { + // Validate container + if (!this._config.container) { + throw new Error('Game container is required'); + } + + // Extract and validate sentences + this._sentences = this._extractSentences(); + if (this._sentences.length < 10) { + throw new Error(`Insufficient sentences: need 10, got ${this._sentences.length}`); + } + + // Set up event listeners + this._eventBus.on('game:pause', this._handlePause.bind(this), this.name); + this._eventBus.on('game:resume', this._handleResume.bind(this), this.name); + + // Initialize sound system + soundSystem.initialize(); + + // Inject CSS + this._injectCSS(); + + // Initialize game interface + this._createGameInterface(); + this._setupEventListeners(); + + // Start the game + this._gameStartTime = Date.now(); + this._startSpawning(); + + // Emit game ready event + this._eventBus.emit('game:ready', { + gameId: 'sentence-invaders', + instanceId: this.name, + sentences: this._sentences.length + }, this.name); + + this._setInitialized(); + + } catch (error) { + this._showError(error.message); + throw error; + } + } + + async destroy() { + this._validateNotDestroyed(); + + // Clear intervals + if (this._spawnInterval) { + clearInterval(this._spawnInterval); + this._spawnInterval = null; + } + + // Stop any active TTS + if ('speechSynthesis' in window) { + window.speechSynthesis.cancel(); + } + + // Remove CSS + this._removeCSS(); + + // Clean up event listeners + if (this._config.container) { + this._config.container.innerHTML = ''; + } + + // Emit game end event + this._eventBus.emit('game:ended', { + gameId: 'sentence-invaders', + instanceId: this.name, + score: this._score, + level: this._level, + combo: this._combo, + duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0 + }, this.name); + + this._setDestroyed(); + } + + /** + * Get current game state + * @returns {Object} Current game state + */ + getGameState() { + this._validateInitialized(); + + return { + score: this._score, + level: this._level, + lives: this._lives, + combo: this._combo, + isGameOver: this._isGameOver, + isPaused: this._isGamePaused, + duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0, + fallingAliensCount: this._fallingAliens.length + }; + } + + // Private methods + _extractSentences() { + const sentences = []; + + // 1. Extract from conversations (new format) + const conversations = this._content?.conversations || {}; + for (const conv of Object.values(conversations)) { + if (conv.sentences && conv.sentences.original && conv.sentences.user_language) { + sentences.push({ + original: conv.sentences.original, + translation: conv.sentences.user_language, + type: 'dialogue', + context: conv.context || '' + }); + } + } + + // 2. Extract from dialogs (SBS format) + const dialogs = this._content?.dialogs || {}; + for (const dialog of Object.values(dialogs)) { + if (dialog.lines && Array.isArray(dialog.lines)) { + dialog.lines.forEach(line => { + if (line.text && line.user_language) { + sentences.push({ + original: line.text, + translation: line.user_language, + type: 'dialogue', + context: dialog.title || '' + }); + } + }); + } + } + + // 3. Extract from phrases + const phrases = this._content?.phrases || {}; + for (const [phraseText, data] of Object.entries(phrases)) { + if (data.user_language) { + sentences.push({ + original: phraseText, + translation: data.user_language, + type: 'phrase', + context: data.context || '' + }); + } + } + + // 4. Extract from grammar examples + const grammar = this._content?.grammar || {}; + for (const rule of Object.values(grammar)) { + if (rule.examples) { + rule.examples.forEach(ex => { + // Handle both formats: target_language/user_language AND chinese/translation + const original = ex.target_language || ex.chinese; + const translation = ex.user_language || ex.translation; + + if (original && translation) { + sentences.push({ + original: original, + translation: translation, + type: 'grammar' + }); + } + }); + } + } + + // 5. Extract from vocabulary example sentences (if available) + const vocab = this._content?.vocabulary || {}; + for (const [word, data] of Object.entries(vocab)) { + if (data.example_sentence && data.example_sentence.target && data.example_sentence.user) { + sentences.push({ + original: data.example_sentence.target, + translation: data.example_sentence.user, + type: 'vocabulary' + }); + } + } + + // Limit and shuffle + return this._shuffleArray(sentences).slice(0, this._config.maxSentences); + } + + _injectCSS() { + const cssId = `sentence-invaders-styles-${this.name}`; + if (document.getElementById(cssId)) return; + + const style = document.createElement('style'); + style.id = cssId; + style.textContent = ` + .sentence-invaders-game { + height: 100vh; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + position: relative; + background: linear-gradient(180deg, #0a0e27 0%, #1a1f3a 100%); + } + + .sentence-invaders-hud { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + position: relative; + z-index: 100; + } + + .hud-section { + display: flex; + gap: 20px; + align-items: center; + } + + .hud-stat { + display: flex; + flex-direction: column; + align-items: center; + min-width: 60px; + } + + .hud-label { + font-size: 0.8rem; + opacity: 0.9; + margin-bottom: 2px; + } + + .hud-value { + font-size: 1.2rem; + font-weight: bold; + } + + .pause-btn { + padding: 8px 15px; + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + transition: all 0.3s ease; + } + + .pause-btn:hover { + background: rgba(255, 255, 255, 0.3); + } + + .sentence-invaders-area { + position: relative; + height: calc(80vh - 180px); + background: linear-gradient(180deg, #0a0e27 0%, #1a1f3a 100%); + overflow: hidden; + border-radius: 20px 20px 0 0; + margin: 10px 10px 0 10px; + box-shadow: inset 0 0 50px rgba(102, 126, 234, 0.2); + } + + /* Starfield background */ + .sentence-invaders-area::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + radial-gradient(2px 2px at 20% 30%, white, transparent), + radial-gradient(2px 2px at 60% 70%, white, transparent), + radial-gradient(1px 1px at 50% 50%, white, transparent), + radial-gradient(1px 1px at 80% 10%, white, transparent), + radial-gradient(2px 2px at 90% 60%, white, transparent), + radial-gradient(1px 1px at 33% 80%, white, transparent); + background-size: 200% 200%; + animation: starfield 60s linear infinite; + opacity: 0.6; + } + + @keyframes starfield { + from { background-position: 0% 0%; } + to { background-position: 0% 100%; } + } + + .falling-alien { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + cursor: default; + user-select: none; + transform: translateX(-50%); + z-index: 10; + } + + .alien-body { + font-size: 3rem; + filter: drop-shadow(0 0 10px rgba(102, 126, 234, 0.8)); + animation: alienFloat 2s ease-in-out infinite; + } + + @keyframes alienFloat { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-10px); } + } + + .speech-bubble { + position: relative; + background: rgba(255, 255, 255, 0.95); + color: #1a1f3a; + padding: 8px 12px; + border-radius: 15px; + margin-top: 10px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + min-width: 50px; + text-align: center; + } + + .speech-bubble::before { + content: ''; + position: absolute; + top: -8px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid rgba(255, 255, 255, 0.95); + } + + .tts-replay-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 12px; + padding: 6px 12px; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4); + } + + .tts-replay-btn:hover { + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.6); + } + + .tts-replay-btn:active { + transform: scale(0.95); + } + + .falling-alien.exploding .alien-body { + animation: alienExplode 0.8s ease-out forwards; + } + + .falling-alien.exploding .speech-bubble { + animation: fadeOut 0.8s ease-out forwards; + } + + @keyframes alienExplode { + 0% { + transform: scale(1) rotate(0deg); + opacity: 1; + filter: drop-shadow(0 0 10px rgba(102, 126, 234, 0.8)); + } + 25% { + transform: scale(1.5) rotate(15deg); + filter: drop-shadow(0 0 30px rgba(16, 185, 129, 0.8)); + } + 50% { + transform: scale(2) rotate(-10deg); + opacity: 0.7; + filter: drop-shadow(0 0 50px rgba(245, 158, 11, 0.9)); + } + 100% { + transform: scale(0.1) rotate(360deg); + opacity: 0; + } + } + + @keyframes fadeOut { + to { opacity: 0; transform: scale(0.5); } + } + + .sentence-invaders-answer-panel { + position: relative; + background: rgba(0, 0, 0, 0.9); + padding: 15px; + border-top: 3px solid #667eea; + border-radius: 0 0 20px 20px; + margin: 0 10px 10px 10px; + z-index: 100; + } + + .sentence-invaders-answer-panel.wrong-flash { + animation: wrongFlash 0.5s ease-in-out; + } + + .answer-buttons-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 12px; + max-width: 700px; + margin: 0 auto; + } + + .sentence-invaders-answer-btn { + padding: 12px 18px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 16px; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + line-height: 1.4; + } + + .sentence-invaders-answer-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); + } + + .sentence-invaders-answer-btn:active { + transform: translateY(0); + } + + .sentence-invaders-answer-btn.correct { + background: linear-gradient(135deg, #10b981 0%, #059669 100%); + animation: correctPulse 0.6s ease-out; + } + + .sentence-invaders-answer-btn.incorrect { + background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); + animation: incorrectShake 0.6s ease-out; + } + + .points-popup { + position: absolute; + font-size: 2rem; + font-weight: bold; + color: #10b981; + pointer-events: none; + z-index: 1000; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + animation: pointsFloat 1.5s ease-out forwards; + } + + .level-up-popup { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.9); + color: white; + padding: 30px; + border-radius: 15px; + text-align: center; + z-index: 1000; + animation: levelUpAppear 2s ease-out forwards; + } + + @keyframes wrongFlash { + 0%, 100% { background: rgba(0, 0, 0, 0.9); } + 50% { background: rgba(239, 68, 68, 0.6); } + } + + @keyframes correctPulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } + } + + @keyframes incorrectShake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-5px); } + 75% { transform: translateX(5px); } + } + + @keyframes pointsFloat { + 0% { + transform: translateY(0) scale(1); + opacity: 1; + } + 30% { + transform: translateY(-20px) scale(1.3); + opacity: 1; + } + 100% { + transform: translateY(-80px) scale(0.5); + opacity: 0; + } + } + + @keyframes levelUpAppear { + 0% { + transform: translate(-50%, -50%) scale(0.5); + opacity: 0; + } + 20% { + transform: translate(-50%, -50%) scale(1.2); + opacity: 1; + } + 80% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } + 100% { + transform: translate(-50%, -50%) scale(1); + opacity: 0; + } + } + + @keyframes screenShake { + 0%, 100% { transform: translateX(0); } + 10% { transform: translateX(-3px) translateY(1px); } + 20% { transform: translateX(3px) translateY(-1px); } + 30% { transform: translateX(-2px) translateY(2px); } + 40% { transform: translateX(2px) translateY(-2px); } + 50% { transform: translateX(-1px) translateY(1px); } + 60% { transform: translateX(1px) translateY(-1px); } + 70% { transform: translateX(-2px) translateY(0px); } + 80% { transform: translateX(2px) translateY(1px); } + 90% { transform: translateX(-1px) translateY(-1px); } + } + + @media (max-width: 768px) { + .alien-body { + font-size: 2.5rem; + } + + .answer-buttons-grid { + grid-template-columns: 1fr; + gap: 10px; + } + } + `; + + document.head.appendChild(style); + } + + _removeCSS() { + const cssId = `sentence-invaders-styles-${this.name}`; + const existingStyle = document.getElementById(cssId); + if (existingStyle) { + existingStyle.remove(); + } + } + + _createGameInterface() { + this._config.container.innerHTML = ` +
+
+
+
+
Score
+
0
+
+
+
Level
+
1
+
+
+ +
+
+
Lives
+
3
+
+
+
Combo
+
0
+
+
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+ `; + } + + _setupEventListeners() { + // Pause button + const pauseBtn = document.getElementById('pause-btn'); + if (pauseBtn) { + pauseBtn.addEventListener('click', () => this._togglePause()); + } + + // Exit button + const exitButton = document.getElementById('exit-invaders'); + if (exitButton) { + exitButton.addEventListener('click', () => { + this._eventBus.emit('game:exit-request', { instanceId: this.name }, this.name); + }); + } + + // Answer button clicks + this._config.container.addEventListener('click', (event) => { + if (event.target.matches('.sentence-invaders-answer-btn')) { + const answer = event.target.textContent; + this._checkAnswer(answer); + } + + if (event.target.matches('.restart-btn')) { + this._restartGame(); + } + }); + + // Keyboard support + document.addEventListener('keydown', (event) => { + if (event.key >= '1' && event.key <= '4') { + const btnIndex = parseInt(event.key) - 1; + const buttons = document.querySelectorAll('.sentence-invaders-answer-btn'); + if (buttons[btnIndex]) { + buttons[btnIndex].click(); + } + } + + if (event.key === ' ' || event.key === 'Escape') { + event.preventDefault(); + this._togglePause(); + } + }); + } + + _startSpawning() { + this._spawnInterval = setInterval(() => { + if (!this._isGamePaused && !this._isGameOver) { + this._spawnFallingAlien(); + } + }, this._config.spawnRate); + } + + _spawnFallingAlien() { + if (this._sentences.length === 0) return; + + const sentence = this._sentences[this._currentSentenceIndex % this._sentences.length]; + this._currentSentenceIndex++; + + // Generate options for THIS specific sentence + const options = this._generateOptionsForSentence(sentence); + + const gameArea = document.getElementById('game-area'); + const alienElement = document.createElement('div'); + alienElement.className = 'falling-alien'; + alienElement.innerHTML = ` +
๐Ÿ‘พ
+
+ +
+ `; + + alienElement.style.left = Math.random() * 80 + 10 + '%'; + alienElement.style.top = '0vh'; + + gameArea.appendChild(alienElement); + + // Auto-play TTS on spawn + this._playTTS(sentence.original); + + // Setup replay button + const replayBtn = alienElement.querySelector('.tts-replay-btn'); + replayBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._playTTS(sentence.original); + }); + + // Start position check + const positionCheck = setInterval(() => { + if (!alienElement.parentNode) { + clearInterval(positionCheck); + return; + } + + const answerPanel = document.getElementById('answer-panel'); + if (!answerPanel) { + clearInterval(positionCheck); + return; + } + + const alienRect = alienElement.getBoundingClientRect(); + const answerPanelRect = answerPanel.getBoundingClientRect(); + + if (alienRect.bottom >= answerPanelRect.top) { + clearInterval(positionCheck); + if (alienElement.parentNode) { + this._missAlien(alienElement); + } + } + }, 50); + + const fallingAlienData = { + element: alienElement, + sentence: sentence, + options: options, + startTime: Date.now(), + positionCheck: positionCheck + }; + + this._fallingAliens.push(fallingAlienData); + + // Update answer panel with oldest alien's options + this._updateAnswerPanelForOldestAlien(); + + // Animate falling + this._animateFalling(alienElement); + } + + _playTTS(text) { + if (!('speechSynthesis' in window)) { + console.warn('TTS not supported in this browser'); + return; + } + + // Cancel any ongoing speech + window.speechSynthesis.cancel(); + + const utterance = new SpeechSynthesisUtterance(text); + + // Try to use the target language from content + const targetLang = this._content?.language?.target || 'en-US'; + utterance.lang = targetLang; + utterance.rate = 0.85; // Slightly slower for learning + utterance.pitch = 1.0; + utterance.volume = 1.0; + + this._activeTTS = utterance; + window.speechSynthesis.speak(utterance); + } + + _animateFalling(alienElement) { + const gameArea = document.getElementById('game-area'); + if (!gameArea) return; + + const gameAreaHeight = gameArea.offsetHeight; + const viewportHeight = window.innerHeight; + const gameAreaHeightVh = (gameAreaHeight / viewportHeight) * 100; + const fallDurationMs = (gameAreaHeightVh / this._config.fallSpeedVhPerSecond) * 1000; + + alienElement.style.transition = `top ${fallDurationMs}ms linear`; + setTimeout(() => { + alienElement.style.top = `${gameAreaHeight}px`; + }, 50); + } + + _generateOptionsForSentence(sentence) { + const buttons = []; + + // Add correct answer + buttons.push(sentence.translation); + + // Add 3 random incorrect answers + while (buttons.length < 4) { + const randomSentence = this._sentences[Math.floor(Math.random() * this._sentences.length)]; + if (!buttons.includes(randomSentence.translation)) { + buttons.push(randomSentence.translation); + } + } + + // Shuffle and return + return this._shuffleArray(buttons); + } + + _updateAnswerPanelForOldestAlien() { + const activeAliens = this._fallingAliens.filter(fa => fa.element.parentNode); + + if (activeAliens.length === 0) return; + + const oldestAlien = activeAliens[0]; + const options = oldestAlien.options; + + const answerButtons = document.getElementById('answer-buttons'); + if (answerButtons) { + answerButtons.innerHTML = options.map(answer => + `` + ).join(''); + } + } + + _checkAnswer(selectedAnswer) { + const activeAliens = this._fallingAliens.filter(fa => fa.element.parentNode); + + for (let i = 0; i < activeAliens.length; i++) { + const alien = activeAliens[i]; + if (alien.sentence.translation === selectedAnswer) { + this._correctAnswer(alien); + return; + } + } + + // Wrong answer + this._wrongAnswer(); + } + + _correctAnswer(alien) { + // Play success sound + soundSystem.play('coin'); + + // Clear position check + if (alien.positionCheck) { + clearInterval(alien.positionCheck); + } + + // Remove with explosion + if (alien.element.parentNode) { + alien.element.classList.add('exploding'); + + // Screen shake + const gameArea = document.getElementById('game-area'); + if (gameArea) { + gameArea.style.animation = 'none'; + gameArea.offsetHeight; + gameArea.style.animation = 'screenShake 0.3s ease-in-out'; + setTimeout(() => { + gameArea.style.animation = ''; + }, 300); + } + + setTimeout(() => { + if (alien.element.parentNode) { + alien.element.remove(); + } + }, 800); + } + + // Remove from tracking + this._fallingAliens = this._fallingAliens.filter(fa => fa !== alien); + + // Update score and combo + this._combo++; + this._aliensKilled++; + const points = 15 + (this._combo * 3); // Higher base points for sentences + this._score += points; + + // Increase speed based on combo + const speedMultiplier = Math.min(1 + (this._combo * 0.03), 2); + this._config.fallSpeedVhPerSecond = 10 * speedMultiplier; + + this._updateHUD(); + this._showPointsPopup(points, alien.element); + this._updateAnswerPanelForOldestAlien(); + + // Vibration feedback + if (navigator.vibrate) { + navigator.vibrate([50, 30, 50]); + } + + // Level up check + if (this._score > 0 && this._score % 150 === 0) { + this._levelUp(); + } + + this._eventBus.emit('sentence-invaders:correct-answer', { + gameId: 'sentence-invaders', + instanceId: this.name, + sentence: alien.sentence, + points, + combo: this._combo, + score: this._score + }, this.name); + } + + _wrongAnswer() { + soundSystem.play('enemy_defeat'); + + this._combo = 0; + this._config.fallSpeedVhPerSecond = 10; // Reset speed + + // Flash answer panel + const answerPanel = document.getElementById('answer-panel'); + if (answerPanel) { + answerPanel.classList.add('wrong-flash'); + setTimeout(() => { + answerPanel.classList.remove('wrong-flash'); + }, 500); + } + + // Screen flash + const gameArea = document.getElementById('game-area'); + if (gameArea) { + const overlay = document.createElement('div'); + overlay.style.cssText = ` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(239, 68, 68, 0.3); + pointer-events: none; + animation: wrongFlash 0.4s ease-in-out; + z-index: 100; + `; + gameArea.appendChild(overlay); + setTimeout(() => { + if (overlay.parentNode) overlay.remove(); + }, 400); + } + + this._updateHUD(); + + if (navigator.vibrate) { + navigator.vibrate([200, 100, 200, 100, 200]); + } + + this._eventBus.emit('sentence-invaders:wrong-answer', { + gameId: 'sentence-invaders', + instanceId: this.name, + score: this._score + }, this.name); + } + + _showPointsPopup(points, alienElement) { + const popup = document.createElement('div'); + popup.textContent = `+${points}`; + popup.className = 'points-popup'; + popup.style.left = alienElement.style.left; + popup.style.top = alienElement.offsetTop + 'px'; + + const gameArea = document.getElementById('game-area'); + if (gameArea) { + gameArea.appendChild(popup); + setTimeout(() => { + if (popup.parentNode) popup.remove(); + }, 1500); + } + } + + _missAlien(alienElement) { + const alien = this._fallingAliens.find(fa => fa.element === alienElement); + + if (alien && alien.positionCheck) { + clearInterval(alien.positionCheck); + } + + soundSystem.play('enemy_defeat'); + + // Explosion animation + if (alienElement.parentNode) { + alienElement.classList.add('exploding'); + + const gameArea = document.getElementById('game-area'); + if (gameArea) { + gameArea.style.animation = 'none'; + gameArea.offsetHeight; + gameArea.style.animation = 'screenShake 0.5s ease-in-out'; + setTimeout(() => { + gameArea.style.animation = ''; + }, 500); + } + + setTimeout(() => { + if (alienElement.parentNode) { + alienElement.remove(); + } + }, 800); + } + + this._fallingAliens = this._fallingAliens.filter(fa => fa.element !== alienElement); + + // Lose life + this._lives--; + this._combo = 0; + this._aliensMissed++; + + this._updateHUD(); + this._updateAnswerPanelForOldestAlien(); + + if (navigator.vibrate) { + navigator.vibrate([200, 100, 200, 100, 200]); + } + + if (this._lives <= 0) { + this._gameOver(); + } + + this._eventBus.emit('sentence-invaders:alien-missed', { + gameId: 'sentence-invaders', + instanceId: this.name, + lives: this._lives, + score: this._score + }, this.name); + } + + _levelUp() { + this._level++; + + // Increase difficulty + this._config.fallSpeedVhPerSecond = Math.min(40, this._config.fallSpeedVhPerSecond * 1.05); + this._config.spawnRate = Math.max(1500, this._config.spawnRate / 1.05); + + // Restart intervals + if (this._spawnInterval) { + clearInterval(this._spawnInterval); + this._startSpawning(); + } + + this._updateHUD(); + + // Show level up message + const gameArea = document.getElementById('game-area'); + const levelUpMsg = document.createElement('div'); + levelUpMsg.className = 'level-up-popup'; + levelUpMsg.innerHTML = ` +

๐Ÿ‘พ LEVEL UP! ๐Ÿ‘พ

+

Level ${this._level}

+

Aliens invade faster!

+ `; + gameArea.appendChild(levelUpMsg); + + setTimeout(() => { + if (levelUpMsg.parentNode) { + levelUpMsg.remove(); + } + }, 2000); + + this._eventBus.emit('sentence-invaders:level-up', { + gameId: 'sentence-invaders', + instanceId: this.name, + level: this._level, + score: this._score + }, this.name); + } + + _togglePause() { + this._isGamePaused = !this._isGamePaused; + + // Pause/resume TTS + if ('speechSynthesis' in window) { + if (this._isGamePaused) { + window.speechSynthesis.pause(); + } else { + window.speechSynthesis.resume(); + } + } + + const pauseBtn = document.getElementById('pause-btn'); + if (pauseBtn) { + pauseBtn.textContent = this._isGamePaused ? 'โ–ถ๏ธ Resume' : 'โธ๏ธ Pause'; + } + + if (this._isGamePaused) { + this._eventBus.emit('game:paused', { instanceId: this.name }, this.name); + } else { + this._eventBus.emit('game:resumed', { instanceId: this.name }, this.name); + } + } + + _gameOver() { + this._isGameOver = true; + + // Clear intervals + if (this._spawnInterval) { + clearInterval(this._spawnInterval); + this._spawnInterval = null; + } + + // Stop TTS + if ('speechSynthesis' in window) { + window.speechSynthesis.cancel(); + } + + // Clear aliens + this._fallingAliens.forEach(fa => { + if (fa.positionCheck) { + clearInterval(fa.positionCheck); + } + if (fa.element.parentNode) { + fa.element.remove(); + } + }); + this._fallingAliens = []; + + this._showGameOverScreen(); + + this._eventBus.emit('game:completed', { + gameId: 'sentence-invaders', + instanceId: this.name, + score: this._score, + level: this._level, + duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0 + }, this.name); + } + + _showGameOverScreen() { + const duration = this._gameStartTime ? Math.round((Date.now() - this._gameStartTime) / 1000) : 0; + + const gameKey = 'sentence-invaders'; + const currentScore = this._score; + const bestScore = parseInt(localStorage.getItem(`${gameKey}-best-score`) || '0'); + const isNewBest = currentScore > bestScore; + + if (isNewBest) { + localStorage.setItem(`${gameKey}-best-score`, currentScore.toString()); + } + + const accuracy = (this._aliensKilled + this._aliensMissed) > 0 + ? Math.round((this._aliensKilled / (this._aliensKilled + this._aliensMissed)) * 100) + : 0; + + this._showVictoryPopup({ + gameTitle: 'Sentence Invaders', + currentScore, + bestScore: isNewBest ? currentScore : bestScore, + isNewBest, + stats: { + 'Level Reached': this._level, + 'Duration': `${duration}s`, + 'Aliens Destroyed': this._aliensKilled, + 'Accuracy': `${accuracy}%` + } + }); + } + + _restartGame() { + // Reset state + this._score = 0; + this._level = 1; + this._lives = this._config.startingLives; + this._combo = 0; + this._isGamePaused = false; + this._isGameOver = false; + this._currentSentenceIndex = 0; + this._gameStartTime = Date.now(); + this._aliensKilled = 0; + this._aliensMissed = 0; + + // Reset config + this._config.fallSpeedVhPerSecond = 10; + this._config.spawnRate = 5000; + + // Clear intervals + if (this._spawnInterval) { + clearInterval(this._spawnInterval); + } + + // Clear aliens + this._fallingAliens.forEach(fa => { + if (fa.positionCheck) { + clearInterval(fa.positionCheck); + } + if (fa.element.parentNode) { + fa.element.remove(); + } + }); + this._fallingAliens = []; + + // Update and restart + this._updateHUD(); + this._startSpawning(); + } + + _updateHUD() { + const scoreDisplay = document.getElementById('score-display'); + const levelDisplay = document.getElementById('level-display'); + const livesDisplay = document.getElementById('lives-display'); + const comboDisplay = document.getElementById('combo-display'); + + if (scoreDisplay) scoreDisplay.textContent = this._score; + if (levelDisplay) levelDisplay.textContent = this._level; + if (livesDisplay) livesDisplay.textContent = this._lives; + if (comboDisplay) comboDisplay.textContent = this._combo; + } + + _showError(message) { + if (this._config.container) { + this._config.container.innerHTML = ` +
+
โŒ
+

Sentence Invaders Error

+

${message}

+ +
+ `; + } + } + + _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; + } + + _handlePause() { + this._isGamePaused = true; + if ('speechSynthesis' in window) { + window.speechSynthesis.pause(); + } + const pauseBtn = document.getElementById('pause-btn'); + if (pauseBtn) { + pauseBtn.textContent = 'โ–ถ๏ธ Resume'; + } + } + + _handleResume() { + this._isGamePaused = false; + if ('speechSynthesis' in window) { + window.speechSynthesis.resume(); + } + const pauseBtn = document.getElementById('pause-btn'); + if (pauseBtn) { + pauseBtn.textContent = 'โธ๏ธ Pause'; + } + } + + _showVictoryPopup({ gameTitle, currentScore, bestScore, isNewBest, stats }) { + const popup = document.createElement('div'); + popup.className = 'victory-popup'; + popup.innerHTML = ` +
+
+
๐Ÿ‘พ
+

${gameTitle} Complete!

+ ${isNewBest ? '
๐ŸŽ‰ New Best Score!
' : ''} +
+ +
+
+
Your Score
+
${currentScore}
+
+
+
Best Score
+
${bestScore}
+
+
+ +
+ ${Object.entries(stats).map(([key, value]) => ` +
+
${key}
+
${value}
+
+ `).join('')} +
+ +
+ + + +
+
+ `; + + document.body.appendChild(popup); + + requestAnimationFrame(() => { + popup.classList.add('show'); + }); + + popup.querySelector('#play-again-btn').addEventListener('click', () => { + popup.remove(); + this._restartGame(); + }); + + popup.querySelector('#different-game-btn').addEventListener('click', () => { + popup.remove(); + if (window.app && window.app.getCore().router) { + window.app.getCore().router.navigate('/games'); + } else { + window.location.href = '/#/games'; + } + }); + + popup.querySelector('#main-menu-btn').addEventListener('click', () => { + popup.remove(); + if (window.app && window.app.getCore().router) { + window.app.getCore().router.navigate('/'); + } else { + window.location.href = '/'; + } + }); + + popup.addEventListener('click', (e) => { + if (e.target === popup) { + popup.remove(); + if (window.app && window.app.getCore().router) { + window.app.getCore().router.navigate('/games'); + } else { + window.location.href = '/#/games'; + } + } + }); + } +} + +export default SentenceInvaders; diff --git a/src/games/ThematicQuestions.js b/src/games/ThematicQuestions.js new file mode 100644 index 0000000..5e8abb1 --- /dev/null +++ b/src/games/ThematicQuestions.js @@ -0,0 +1,1196 @@ +import Module from '../core/Module.js'; + +/** + * ThematicQuestionsGame - Listening and speaking practice with self-assessment + * Students listen to questions (TTS), answer them, can reveal text if needed, + * view example responses, and self-assess their spoken answers + */ +class ThematicQuestionsGame extends Module { + constructor(name, dependencies, config = {}) { + super(name, ['eventBus']); + + // Validate dependencies + if (!dependencies.eventBus || !dependencies.content) { + throw new Error('ThematicQuestionsGame requires eventBus and content dependencies'); + } + + this._eventBus = dependencies.eventBus; + this._content = dependencies.content; + this._config = { + container: null, + autoPlayTTS: false, // Auto-play question on load + ...config + }; + + // Game state + this._questions = []; + this._currentIndex = 0; + this._score = 0; + this._correctCount = 0; + this._incorrectCount = 0; + this._showingExamples = false; + this._hasAnswered = false; + this._gameStartTime = null; + this._questionStartTime = null; + + Object.seal(this); + } + + /** + * Get game metadata + * @returns {Object} Game metadata + */ + static getMetadata() { + return { + name: 'Thematic Questions', + description: 'Listen to questions and practice speaking with self-assessment', + difficulty: 'beginner', + category: 'listening', + estimatedTime: 10, // minutes + skills: ['listening', 'speaking', 'comprehension', 'self-assessment'] + }; + } + + /** + * Calculate compatibility score with content + * @param {Object} content - Content to check compatibility with + * @returns {Object} Compatibility score and details + */ + static getCompatibilityScore(content) { + const thematicQuestions = content?.thematic_questions || {}; + + // Count total questions across all themes + let totalQuestions = 0; + for (const theme of Object.values(thematicQuestions)) { + if (Array.isArray(theme)) { + totalQuestions += theme.length; + } + } + + if (totalQuestions < 5) { + return { + score: 0, + reason: `Insufficient questions (${totalQuestions}/5 required)`, + requirements: ['thematic_questions'], + minQuestions: 5, + details: 'Thematic Questions needs at least 5 questions to play' + }; + } + + // Perfect score at 20+ questions, partial score for 5-19 + const score = Math.min(totalQuestions / 20, 1); + + return { + score, + reason: `${totalQuestions} thematic questions available`, + requirements: ['thematic_questions'], + minQuestions: 5, + optimalQuestions: 20, + details: `Can create practice session with ${totalQuestions} questions` + }; + } + + async init() { + this._validateNotDestroyed(); + + try { + // Validate container + if (!this._config.container) { + throw new Error('Game container is required'); + } + + // Extract and validate questions + this._questions = this._extractQuestions(); + if (this._questions.length === 0) { + throw new Error('No thematic questions found in content'); + } + + // Set up event listeners + this._eventBus.on('game:pause', this._handlePause.bind(this), this.name); + this._eventBus.on('game:resume', this._handleResume.bind(this), this.name); + + // Inject CSS + this._injectCSS(); + + // Initialize game interface + this._createGameInterface(); + this._setupEventListeners(); + + // Start the game + this._gameStartTime = Date.now(); + this._showQuestion(); + + // Emit game ready event + this._eventBus.emit('game:ready', { + gameId: 'thematic-questions', + instanceId: this.name, + questionsCount: this._questions.length + }, this.name); + + this._setInitialized(); + + } catch (error) { + this._showError(error.message); + throw error; + } + } + + async destroy() { + this._validateNotDestroyed(); + + // Remove CSS + this._removeCSS(); + + // Clean up event listeners + if (this._config.container) { + this._config.container.innerHTML = ''; + } + + // Emit game end event + this._eventBus.emit('game:ended', { + gameId: 'thematic-questions', + instanceId: this.name, + score: this._score, + questionsAnswered: this._currentIndex, + totalQuestions: this._questions.length, + correctCount: this._correctCount, + incorrectCount: this._incorrectCount, + duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0 + }, this.name); + + this._setDestroyed(); + } + + /** + * Get current game state + * @returns {Object} Current game state + */ + getGameState() { + this._validateInitialized(); + + return { + score: this._score, + currentQuestion: this._currentIndex, + totalQuestions: this._questions.length, + correctCount: this._correctCount, + incorrectCount: this._incorrectCount, + isComplete: this._currentIndex >= this._questions.length, + duration: this._gameStartTime ? Date.now() - this._gameStartTime : 0 + }; + } + + // Private methods + + _extractQuestions() { + const thematicQuestions = this._content?.thematic_questions || {}; + const allQuestions = []; + + // Flatten all themes into single array + for (const [themeName, questions] of Object.entries(thematicQuestions)) { + if (Array.isArray(questions)) { + questions.forEach(q => { + allQuestions.push({ + ...q, + themeName: themeName + }); + }); + } + } + + // Shuffle questions randomly (Fisher-Yates algorithm) + return this._shuffleArray(allQuestions); + } + + _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; + } + + _injectCSS() { + const cssId = `thematic-questions-styles-${this.name}`; + if (document.getElementById(cssId)) return; + + const style = document.createElement('style'); + style.id = cssId; + style.textContent = ` + .thematic-questions-game { + padding: 20px; + max-width: 800px; + margin: 0 auto; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + } + + .tq-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; + padding: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 12px; + color: white; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); + } + + .tq-stats { + display: flex; + gap: 30px; + } + + .tq-stat { + text-align: center; + } + + .tq-stat-label { + display: block; + font-size: 0.8rem; + opacity: 0.9; + margin-bottom: 5px; + } + + .tq-stat-value { + display: block; + font-size: 1.5rem; + font-weight: bold; + } + + .question-card { + background: white; + border-radius: 12px; + padding: 40px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + min-height: 400px; + display: flex; + flex-direction: column; + } + + .question-theme { + display: inline-block; + padding: 6px 12px; + background: #e3f2fd; + color: #1976d2; + border-radius: 20px; + font-size: 0.85rem; + margin-bottom: 20px; + font-weight: 500; + } + + .question-progress { + text-align: center; + color: #6c757d; + font-size: 0.9rem; + margin-bottom: 10px; + } + + .question-display { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + margin-bottom: 30px; + } + + .listening-prompt { + font-size: 1.5rem; + color: #667eea; + margin-bottom: 40px; + font-weight: 600; + padding: 20px; + background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%); + border-radius: 12px; + animation: pulse 2s ease-in-out infinite; + } + + @keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.02); } + } + + .question-text { + font-size: 2rem; + color: #333; + margin-bottom: 15px; + font-weight: 600; + line-height: 1.4; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + border-left: 4px solid #28a745; + transition: all 0.4s ease; + } + + .question-text.hidden { + display: none; + } + + .question-text.visible { + display: block; + animation: slideDown 0.4s ease; + } + + .question-translation { + font-size: 1.2rem; + color: #6c757d; + font-style: italic; + margin-bottom: 25px; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border-left: 4px solid #dc3545; + transition: all 0.4s ease; + } + + .question-translation.hidden { + display: none; + } + + .question-translation.visible { + display: block; + animation: slideDown 0.4s ease; + } + + @keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .reveal-controls { + display: flex; + justify-content: center; + gap: 15px; + margin-bottom: 20px; + } + + .btn-reveal { + padding: 12px 24px; + background: #17a2b8; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 8px; + } + + .btn-reveal:hover:not(:disabled) { + background: #138496; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(23, 162, 184, 0.3); + } + + .btn-reveal:disabled { + cursor: not-allowed; + opacity: 0.5; + } + + .tts-controls { + display: flex; + justify-content: center; + gap: 15px; + margin-bottom: 30px; + } + + .btn-tts { + padding: 12px 24px; + background: #667eea; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 8px; + } + + .btn-tts:hover { + background: #5568d3; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); + } + + .btn-tts:active { + transform: translateY(0); + } + + .btn-show-examples { + padding: 12px 24px; + background: #28a745; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; + } + + .btn-show-examples:hover { + background: #218838; + transform: translateY(-2px); + } + + .btn-show-examples.active { + background: #ffc107; + color: #000; + } + + .examples-container { + background: #f8f9fa; + border-radius: 8px; + padding: 20px; + margin-bottom: 30px; + border-left: 4px solid #667eea; + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease, padding 0.3s ease, margin 0.3s ease; + } + + .examples-container.visible { + max-height: 500px; + margin-bottom: 30px; + padding: 20px; + } + + .examples-container.hidden { + padding: 0; + margin: 0; + } + + .examples-title { + font-size: 1.1rem; + font-weight: 600; + color: #333; + margin-bottom: 15px; + } + + .example-item { + padding: 10px 15px; + background: white; + border-radius: 6px; + margin-bottom: 10px; + font-size: 1rem; + color: #495057; + border-left: 3px solid #667eea; + } + + .example-item:last-child { + margin-bottom: 0; + } + + .assessment-section { + text-align: center; + margin-top: 20px; + } + + .assessment-title { + font-size: 1.1rem; + color: #333; + margin-bottom: 15px; + font-weight: 500; + } + + .assessment-buttons { + display: flex; + gap: 15px; + justify-content: center; + } + + .btn-correct { + padding: 15px 40px; + background: #28a745; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1.1rem; + font-weight: 600; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 10px; + } + + .btn-correct:hover { + background: #218838; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(40, 167, 69, 0.3); + } + + .btn-incorrect { + padding: 15px 40px; + background: #dc3545; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1.1rem; + font-weight: 600; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 10px; + } + + .btn-incorrect:hover { + background: #c82333; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(220, 53, 69, 0.3); + } + + .btn-next { + display: block; + margin: 20px auto 0; + padding: 12px 30px; + background: #667eea; + color: white; + border: none; + border-radius: 8px; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + } + + .btn-next:hover { + background: #5568d3; + transform: translateY(-2px); + } + + .feedback-message { + text-align: center; + padding: 15px; + border-radius: 8px; + margin: 20px 0; + font-size: 1.1rem; + font-weight: 500; + } + + .feedback-message.correct { + background: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; + } + + .feedback-message.incorrect { + background: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; + } + + .tq-error { + text-align: center; + padding: 40px; + background: #f8d7da; + border: 1px solid #f5c6cb; + border-radius: 12px; + color: #721c24; + } + + .error-icon { + font-size: 3rem; + margin-bottom: 20px; + } + + @media (max-width: 768px) { + .tq-header { + flex-direction: column; + gap: 20px; + } + + .tq-stats { + gap: 20px; + } + + .question-card { + padding: 20px; + } + + .question-text { + font-size: 1.5rem; + } + + .question-translation { + font-size: 1rem; + } + + .assessment-buttons { + flex-direction: column; + } + + .btn-correct, .btn-incorrect { + width: 100%; + } + } + `; + + document.head.appendChild(style); + } + + _removeCSS() { + const cssId = `thematic-questions-styles-${this.name}`; + const existingStyle = document.getElementById(cssId); + if (existingStyle) { + existingStyle.remove(); + } + } + + _createGameInterface() { + this._config.container.innerHTML = ` +
+
+
+
+ โœ… Correct + 0 +
+
+ โŒ Incorrect + 0 +
+
+ Progress + + 0/${this._questions.length} + +
+
+ +
+
+
+ `; + } + + _setupEventListeners() { + // Exit button + const exitButton = this._config.container.querySelector('#exit-game'); + if (exitButton) { + exitButton.addEventListener('click', () => { + this._eventBus.emit('game:exit-request', { instanceId: this.name }, this.name); + }); + } + + // Event delegation for dynamic buttons + this._config.container.addEventListener('click', (event) => { + if (event.target.matches('#tts-btn') || event.target.closest('#tts-btn')) { + this._handleTTS(); + } + + if (event.target.matches('#reveal-english-btn') || event.target.closest('#reveal-english-btn')) { + this._revealText('english'); + } + + if (event.target.matches('#reveal-chinese-btn') || event.target.closest('#reveal-chinese-btn')) { + this._revealText('chinese'); + } + + if (event.target.matches('#show-examples-btn') || event.target.closest('#show-examples-btn')) { + this._toggleExamples(); + } + + if (event.target.matches('#btn-correct') || event.target.closest('#btn-correct')) { + this._handleSelfAssessment(true); + } + + if (event.target.matches('#btn-incorrect') || event.target.closest('#btn-incorrect')) { + this._handleSelfAssessment(false); + } + + if (event.target.matches('#next-btn') || event.target.closest('#next-btn')) { + this._nextQuestion(); + } + }); + } + + _showQuestion() { + if (this._currentIndex >= this._questions.length) { + this._showResults(); + return; + } + + const question = this._questions[this._currentIndex]; + const content = document.getElementById('tq-content'); + + this._showingExamples = false; + this._hasAnswered = false; + this._questionStartTime = Date.now(); + + content.innerHTML = ` +
+
+ Question ${this._currentIndex + 1} of ${this._questions.length} +
+ +
${this._formatThemeName(question.themeName)}
+ +
+
+ ๐ŸŽง Listen to the question and answer it +
+ + + +
+ +
+ + +
+ +
+ + +
+ + + +
+
Was your answer correct?
+
+ + +
+
+ +
+
+ `; + + // Auto-play TTS on question load for listening exercise + if (question.tts_enabled) { + setTimeout(() => this._handleTTS(), 200); + } + } + + _formatThemeName(theme) { + return theme + .split('_') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } + + _revealText(language) { + if (language === 'english') { + const textElement = document.getElementById('question-text-en'); + const btn = document.getElementById('reveal-english-btn'); + if (textElement && btn) { + textElement.classList.remove('hidden'); + textElement.classList.add('visible'); + btn.disabled = true; + btn.style.opacity = '0.5'; + } + } else if (language === 'chinese') { + const textElement = document.getElementById('question-text-zh'); + const btn = document.getElementById('reveal-chinese-btn'); + if (textElement && btn) { + textElement.classList.remove('hidden'); + textElement.classList.add('visible'); + btn.disabled = true; + btn.style.opacity = '0.5'; + } + } + } + + _toggleExamples() { + const container = document.getElementById('examples-container'); + const btn = document.getElementById('show-examples-btn'); + + if (!container || !btn) return; + + this._showingExamples = !this._showingExamples; + + if (this._showingExamples) { + container.classList.remove('hidden'); + container.classList.add('visible'); + btn.classList.add('active'); + btn.innerHTML = ` + ๐Ÿ‘๏ธ + Hide Examples + `; + } else { + container.classList.remove('visible'); + container.classList.add('hidden'); + btn.classList.remove('active'); + btn.innerHTML = ` + ๐Ÿ’ก + Show Examples + `; + } + } + + _handleSelfAssessment(isCorrect) { + if (this._hasAnswered) return; + + this._hasAnswered = true; + const question = this._questions[this._currentIndex]; + + // Update stats + if (isCorrect) { + this._correctCount++; + this._score += 100; + } else { + this._incorrectCount++; + } + + // Show feedback + this._showFeedback(isCorrect); + this._updateStats(); + + // Record time spent + const timeSpent = this._questionStartTime ? Date.now() - this._questionStartTime : 0; + + // Emit answer event + this._eventBus.emit('thematic-questions:answer', { + gameId: 'thematic-questions', + instanceId: this.name, + questionNumber: this._currentIndex + 1, + question: question.question, + isCorrect, + score: this._score, + timeSpent + }, this.name); + } + + _showFeedback(isCorrect) { + const assessmentSection = document.getElementById('assessment-section'); + const feedbackContainer = document.getElementById('feedback-container'); + + if (!assessmentSection || !feedbackContainer) return; + + // Hide assessment buttons + assessmentSection.style.display = 'none'; + + // Show feedback + const feedbackClass = isCorrect ? 'correct' : 'incorrect'; + const feedbackIcon = isCorrect ? '๐ŸŽ‰' : '๐Ÿ’ช'; + const feedbackText = isCorrect + ? 'Great job! Your answer was correct!' + : 'Keep practicing! Try to review the examples.'; + + feedbackContainer.innerHTML = ` + + + `; + } + + _nextQuestion() { + this._currentIndex++; + this._showQuestion(); + } + + _showResults() { + const accuracy = this._questions.length > 0 + ? Math.round((this._correctCount / this._questions.length) * 100) + : 0; + const totalTime = this._gameStartTime ? Date.now() - this._gameStartTime : 0; + + // Store best score + const gameKey = 'thematic-questions'; + const currentScore = this._score; + const bestScore = parseInt(localStorage.getItem(`${gameKey}-best-score`) || '0'); + const isNewBest = currentScore > bestScore; + + if (isNewBest) { + localStorage.setItem(`${gameKey}-best-score`, currentScore.toString()); + } + + // Show victory popup + this._showVictoryPopup({ + gameTitle: 'Thematic Questions', + currentScore, + bestScore: isNewBest ? currentScore : bestScore, + isNewBest, + stats: { + 'Questions': `${this._questions.length}`, + 'Correct': `${this._correctCount}`, + 'Incorrect': `${this._incorrectCount}`, + 'Accuracy': `${accuracy}%`, + 'Total Time': `${Math.round(totalTime / 1000)}s` + } + }); + + // Emit completion event + this._eventBus.emit('game:completed', { + gameId: 'thematic-questions', + instanceId: this.name, + score: this._score, + correctCount: this._correctCount, + incorrectCount: this._incorrectCount, + totalQuestions: this._questions.length, + accuracy, + duration: totalTime + }, this.name); + } + + _updateStats() { + const correctElement = document.getElementById('tq-correct'); + const incorrectElement = document.getElementById('tq-incorrect'); + const currentElement = document.getElementById('tq-current'); + + if (correctElement) correctElement.textContent = this._correctCount; + if (incorrectElement) incorrectElement.textContent = this._incorrectCount; + if (currentElement) currentElement.textContent = this._currentIndex + 1; + } + + _handleTTS() { + const question = this._questions[this._currentIndex]; + if (question && question.tts_enabled && question.question) { + this._playAudio(question.question); + } + } + + async _playAudio(text) { + if ('speechSynthesis' in window) { + // Cancel any ongoing speech + speechSynthesis.cancel(); + + const utterance = new SpeechSynthesisUtterance(text); + + // Get language from chapter content, fallback to en-US + const chapterLanguage = this._content?.language || 'en-US'; + utterance.lang = chapterLanguage; + utterance.rate = 0.85; + utterance.pitch = 1.0; + utterance.volume = 1.0; + + // Try to find a suitable voice for the language + const voices = await this._getVoices(); + if (voices.length > 0) { + const langPrefix = chapterLanguage.split('-')[0]; + const matchingVoice = voices.find(voice => + voice.lang.startsWith(langPrefix) && (voice.name.includes('Neural') || voice.default) + ) || voices.find(voice => voice.lang.startsWith(langPrefix)); + + if (matchingVoice) { + utterance.voice = matchingVoice; + console.log('๐Ÿ”Š Using voice:', matchingVoice.name, matchingVoice.lang); + } else { + console.warn(`๐Ÿ”Š No voice found for: ${chapterLanguage}`); + } + } + + // Visual feedback + const ttsBtn = document.getElementById('tts-btn'); + if (ttsBtn) { + const originalHTML = ttsBtn.innerHTML; + ttsBtn.innerHTML = '๐Ÿ”„Speaking...'; + ttsBtn.disabled = true; + + utterance.onend = () => { + ttsBtn.innerHTML = originalHTML; + ttsBtn.disabled = false; + }; + + utterance.onerror = () => { + ttsBtn.innerHTML = originalHTML; + ttsBtn.disabled = false; + }; + } + + speechSynthesis.speak(utterance); + } else { + console.warn('๐Ÿ”Š Speech Synthesis not supported'); + alert('Text-to-speech is not available in this browser'); + } + } + + _getVoices() { + return new Promise((resolve) => { + let voices = window.speechSynthesis.getVoices(); + + if (voices.length > 0) { + resolve(voices); + return; + } + + const voicesChangedHandler = () => { + voices = window.speechSynthesis.getVoices(); + if (voices.length > 0) { + window.speechSynthesis.removeEventListener('voiceschanged', voicesChangedHandler); + resolve(voices); + } + }; + + window.speechSynthesis.addEventListener('voiceschanged', voicesChangedHandler); + + setTimeout(() => { + window.speechSynthesis.removeEventListener('voiceschanged', voicesChangedHandler); + resolve(window.speechSynthesis.getVoices()); + }, 1000); + }); + } + + _showVictoryPopup({ gameTitle, currentScore, bestScore, isNewBest, stats }) { + const popup = document.createElement('div'); + popup.className = 'victory-popup'; + popup.innerHTML = ` +
+
+
๐ŸŽค
+

${gameTitle} Complete!

+ ${isNewBest ? '
๐ŸŽ‰ New Best Score!
' : ''} +
+ +
+
+
Your Score
+
${currentScore}
+
+
+
Best Score
+
${bestScore}
+
+
+ +
+ ${Object.entries(stats).map(([key, value]) => ` +
+
${key}
+
${value}
+
+ `).join('')} +
+ +
+ + + +
+
+ `; + + document.body.appendChild(popup); + + requestAnimationFrame(() => { + popup.classList.add('show'); + }); + + // Event listeners + popup.querySelector('#play-again-btn').addEventListener('click', () => { + popup.remove(); + this._restartGame(); + }); + + popup.querySelector('#different-game-btn').addEventListener('click', () => { + popup.remove(); + if (window.app && window.app.getCore().router) { + window.app.getCore().router.navigate('/games'); + } else { + window.location.href = '/#/games'; + } + }); + + popup.querySelector('#main-menu-btn').addEventListener('click', () => { + popup.remove(); + if (window.app && window.app.getCore().router) { + window.app.getCore().router.navigate('/'); + } else { + window.location.href = '/'; + } + }); + + popup.addEventListener('click', (e) => { + if (e.target === popup) { + popup.remove(); + if (window.app && window.app.getCore().router) { + window.app.getCore().router.navigate('/games'); + } else { + window.location.href = '/#/games'; + } + } + }); + } + + _restartGame() { + this._currentIndex = 0; + this._score = 0; + this._correctCount = 0; + this._incorrectCount = 0; + this._showingExamples = false; + this._hasAnswered = false; + this._gameStartTime = Date.now(); + this._updateStats(); + this._showQuestion(); + } + + _showError(message) { + if (this._config.container) { + this._config.container.innerHTML = ` +
+
โŒ
+

Game Error

+

${message}

+ +
+ `; + } + } + + _handlePause() { + this._eventBus.emit('game:paused', { instanceId: this.name }, this.name); + } + + _handleResume() { + this._eventBus.emit('game:resumed', { instanceId: this.name }, this.name); + } +} + +export default ThematicQuestionsGame; diff --git a/src/games/WhackAMole.js b/src/games/WhackAMole.js index 463da0f..de9b605 100644 --- a/src/games/WhackAMole.js +++ b/src/games/WhackAMole.js @@ -211,86 +211,88 @@ class WhackAMole extends Module { style.id = cssId; style.textContent = ` .whack-game-wrapper { - padding: 20px; - max-width: 800px; + padding: 10px; + max-width: 650px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - border-radius: 15px; + border-radius: 10px; color: white; - min-height: 600px; + min-height: auto; + max-height: 90vh; + overflow: hidden; } .whack-game-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 25px; - padding: 20px; + margin-bottom: 10px; + padding: 10px; background: rgba(255, 255, 255, 0.1); - border-radius: 12px; + border-radius: 8px; backdrop-filter: blur(10px); } .game-stats { display: flex; - gap: 30px; + gap: 10px; align-items: center; } .stat-item { text-align: center; background: rgba(255, 255, 255, 0.1); - padding: 12px 20px; - border-radius: 10px; - min-width: 80px; + padding: 6px 12px; + border-radius: 6px; + min-width: 60px; } .stat-value { display: block; - font-size: 1.8rem; + font-size: 1.2rem; font-weight: bold; - margin-bottom: 5px; + margin-bottom: 2px; } .stat-label { - font-size: 0.9rem; + font-size: 0.7rem; opacity: 0.9; } .target-display { background: rgba(255, 255, 255, 0.2); - padding: 15px 25px; - border-radius: 12px; + padding: 8px 15px; + border-radius: 8px; text-align: center; border: 2px solid rgba(255, 255, 255, 0.3); } .target-label { - font-size: 0.9rem; + font-size: 0.7rem; opacity: 0.9; - margin-bottom: 5px; + margin-bottom: 2px; } .target-word { - font-size: 1.5rem; + font-size: 1rem; font-weight: bold; } .game-controls { display: flex; - gap: 15px; + gap: 8px; align-items: center; flex-wrap: wrap; } .control-btn { - padding: 10px 18px; + padding: 6px 12px; border: 2px solid rgba(255, 255, 255, 0.3); - border-radius: 8px; + border-radius: 6px; background: rgba(255, 255, 255, 0.1); color: white; - font-size: 0.9rem; + font-size: 0.75rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; @@ -316,12 +318,12 @@ class WhackAMole extends Module { .whack-game-board { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 15px; - margin: 30px 0; - padding: 20px; + gap: 10px; + margin: 10px 0; + padding: 10px; background: rgba(0, 0, 0, 0.2); - border-radius: 15px; - min-height: 300px; + border-radius: 10px; + min-height: auto; } .whack-hole { @@ -346,14 +348,14 @@ class WhackAMole extends Module { left: 50%; transform: translate(-50%, -50%) scale(0); background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); - border-radius: 12px; - padding: 15px; + border-radius: 8px; + padding: 8px; color: white; text-align: center; font-weight: 600; - font-size: 1.1rem; + font-size: 0.85rem; transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); cursor: pointer; max-width: 80%; word-wrap: break-word; @@ -370,31 +372,31 @@ class WhackAMole extends Module { .whack-mole:hover { transform: translate(-50%, -50%) scale(1.1); - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); } .pronunciation { - font-size: 0.8rem; + font-size: 0.65rem; color: rgba(255, 255, 255, 0.8); font-style: italic; - margin-bottom: 5px; + margin-bottom: 3px; font-weight: 400; } .feedback-area { text-align: center; - padding: 20px; + padding: 10px; background: rgba(255, 255, 255, 0.1); - border-radius: 12px; - margin-top: 20px; + border-radius: 8px; + margin-top: 10px; backdrop-filter: blur(10px); } .instruction { - font-size: 1.1rem; + font-size: 0.85rem; font-weight: 500; - padding: 15px; - border-radius: 8px; + padding: 8px; + border-radius: 6px; transition: all 0.3s ease; } @@ -417,7 +419,7 @@ class WhackAMole extends Module { .score-popup { position: fixed; - font-size: 1.5rem; + font-size: 1.2rem; font-weight: bold; pointer-events: none; z-index: 1000; @@ -444,48 +446,48 @@ class WhackAMole extends Module { display: flex; align-items: center; justify-content: center; - border-radius: 15px; + border-radius: 10px; z-index: 1000; } .game-over-content { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; - padding: 40px; - border-radius: 15px; + padding: 20px; + border-radius: 10px; text-align: center; - max-width: 400px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + max-width: 350px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); } .game-over-content h2 { - margin: 0 0 20px 0; - font-size: 2.5rem; + margin: 0 0 15px 0; + font-size: 1.8rem; } .final-stats { - margin: 20px 0; - padding: 20px; + margin: 15px 0; + padding: 15px; background: rgba(255, 255, 255, 0.1); - border-radius: 10px; + border-radius: 8px; backdrop-filter: blur(10px); } .stat-row { display: flex; justify-content: space-between; - margin: 10px 0; - font-size: 1.1rem; + margin: 8px 0; + font-size: 0.9rem; } .game-over-btn { - margin: 10px; - padding: 12px 25px; + margin: 8px; + padding: 8px 16px; border: 2px solid white; - border-radius: 8px; + border-radius: 6px; background: transparent; color: white; - font-size: 1rem; + font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; @@ -499,11 +501,11 @@ class WhackAMole extends Module { .game-error { text-align: center; - padding: 40px; + padding: 20px; background: linear-gradient(135deg, #f87171 0%, #ef4444 100%); color: white; - border-radius: 15px; - height: 100vh; + border-radius: 10px; + height: 90vh; display: flex; flex-direction: column; align-items: center; @@ -511,21 +513,21 @@ class WhackAMole extends Module { } .game-error h3 { - font-size: 2rem; - margin-bottom: 20px; + font-size: 1.5rem; + margin-bottom: 15px; } .back-btn { - padding: 12px 25px; + padding: 8px 16px; background: white; color: #ef4444; border: none; - border-radius: 8px; - font-size: 1rem; + border-radius: 6px; + font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: all 0.3s ease; - margin-top: 20px; + margin-top: 15px; } .back-btn:hover { @@ -569,27 +571,27 @@ class WhackAMole extends Module { @media (max-width: 768px) { .whack-game-wrapper { - padding: 15px; + padding: 8px; } .whack-game-header { flex-direction: column; - gap: 20px; - padding: 15px; + gap: 8px; + padding: 8px; } .game-stats { - gap: 20px; + gap: 8px; } .whack-game-board { - gap: 10px; - padding: 15px; + gap: 8px; + padding: 8px; } .whack-mole { - font-size: 0.9rem; - padding: 10px; + font-size: 0.75rem; + padding: 6px; } .game-controls { @@ -597,8 +599,8 @@ class WhackAMole extends Module { } .control-btn { - padding: 8px 15px; - font-size: 0.8rem; + padding: 5px 10px; + font-size: 0.7rem; } } `;