// === NAVIGATION SYSTEM === const AppNavigation = { currentPage: 'home', navigationHistory: ['home'], gamesConfig: null, contentScanner: new ContentScanner(), scannedContent: null, init() { this.loadGamesConfig(); this.initContentScanner(); this.setupEventListeners(); this.handleInitialRoute(); }, async loadGamesConfig() { // Direct use of default config (no fetch) logSh('📁 Using default configuration', 'INFO'); this.gamesConfig = this.getDefaultConfig(); }, async initContentScanner() { try { logSh('🔍 Initializing content scanner...', 'INFO'); this.scannedContent = await this.contentScanner.scanAllContent(); logSh(`✅ ${this.scannedContent.found.length} content modules detected automatically`, 'INFO'); } catch (error) { logSh('Content scan error:', error, 'ERROR'); } }, getDefaultConfig() { return { games: { 'whack-a-mole': { enabled: true, name: 'Whack-a-Mole', icon: '🔨', description: 'Hit the right answers!' }, 'whack-a-mole-hard': { enabled: true, name: 'Whack-a-Mole Hard', icon: '💥', description: '3 moles at once, 5x3 grid, harder!' }, 'memory-match': { enabled: true, name: 'Memory Match', icon: '🧠', description: 'Find matching English-French pairs!' }, 'quiz-game': { enabled: true, name: 'Quiz Game', icon: '❓', description: 'Answer vocabulary questions!' }, 'fill-the-blank': { enabled: true, name: 'Fill the Blank', icon: '📝', description: 'Complete sentences by filling in the blanks!' }, 'adventure-reader': { enabled: true, name: 'Adventure Reader', icon: '⚔️', description: 'Zelda-style adventure with vocabulary!' }, 'story-reader': { enabled: true, name: 'Story Reader', icon: '📚', description: 'Read long stories with sentence chunking and word-by-word translation' }, 'word-storm': { enabled: true, name: 'Word Storm', icon: '🌪️', description: 'Catch falling words before they hit the ground!' }, 'word-discovery': { enabled: true, name: 'Word Discovery', icon: '🔍', description: 'Learn new words with images and interactive practice!' }, 'wizard-spell-caster': { enabled: true, name: 'Wizard Spell Caster', icon: '🧙‍♂️', description: 'Cast spells by forming correct sentences! (Advanced - 11+ years)' }, 'letter-discovery': { enabled: true, name: 'Letter Discovery', icon: '🔤', description: 'Discover letters first, then explore words that start with each letter!' }, 'river-run': { enabled: true, name: 'River Run', icon: '🌊', description: 'Navigate the river and catch your target words while avoiding obstacles!' }, 'grammar-discovery': { enabled: true, name: 'Grammar Discovery', icon: '📚', description: 'Discover and learn grammar patterns through interactive examples!' } }, content: { 'sbs-level-8': { enabled: true, name: 'SBS Level 8', icon: '📚', description: 'SBS textbook vocabulary' }, 'animals': { enabled: false, name: 'Animals', icon: '🐱', description: 'Animal vocabulary' }, 'colors': { enabled: false, name: 'Colors & Numbers', icon: '🌈', description: 'Colors and numbers' }, 'story-prototype-1000words': { enabled: true, name: 'The Magical Library (1000 words)', icon: '✨', description: '1000-word adventure story with sentence-by-sentence chunking' }, 'story-test': { enabled: true, name: 'Story Test - Short Adventure', icon: '📖', description: 'Simple test story for Story Reader (8 sentences)' }, 'story-complete-1000words': { enabled: true, name: 'The Secret Garden Adventure', icon: '🌸', description: 'Complete 1000-word story with full pronunciation and translation' }, 'chinese-long-story': { enabled: true, name: 'The Dragon\'s Pearl (Chinese)', icon: '🐉', description: 'Chinese story with English translation and pinyin pronunciation' }, 'chinese-beginner-story': { enabled: true, name: 'Le Jardin Magique (Chinese → French)', icon: '🌸', description: 'Simple Chinese story for beginners with French translation' }, 'story-prototype-optimized': { enabled: true, name: 'The Magical Library (Optimized)', icon: '⚡', description: 'Story with smart vocabulary matching and game compatibility' }, 'nce1-lesson63-64': { enabled: true, name: 'NCE1-Lesson63-64', icon: '👨‍⚕️', description: 'Medical dialogue and prohibition commands with modal verbs' }, 'nce2-lesson3': { enabled: true, name: 'NCE2-Lesson3', icon: '✉️', description: 'Please Send Me a Card - Past tense and travel vocabulary' }, 'nce2-lesson30': { enabled: true, name: 'NCE2-Lesson30', icon: '⚽', description: 'Football or Polo? - Articles and quantifiers' } } }; }, setupEventListeners() { // URL navigation window.addEventListener('popstate', () => { this.handleInitialRoute(); }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this.goBack(); } }); // Scroll pour masquer/afficher la breadcrumb this.setupScrollBehavior(); }, async handleInitialRoute() { const params = Utils.getUrlParams(); if (params.page === 'play' && params.game && params.content) { this.showGamePage(params.game, params.content); } else if (params.page === 'games' && params.content) { await this.showGamesPage(params.content); } else if (params.page === 'levels') { this.showLevelsPage(); } else { this.showHomePage(); } }, setupScrollBehavior() { let lastScrollY = window.scrollY; let breadcrumb = null; const handleScroll = () => { if (!breadcrumb) { breadcrumb = document.querySelector('.breadcrumb'); if (!breadcrumb) return; } const currentScrollY = window.scrollY; // Si on scroll vers le bas et qu'on a scrollé plus de 50px if (currentScrollY > lastScrollY && currentScrollY > 50) { breadcrumb.classList.add('hidden'); breadcrumb.classList.remove('visible'); } // Si on scroll vers le haut ou qu'on est près du top else if (currentScrollY < lastScrollY || currentScrollY <= 50) { breadcrumb.classList.remove('hidden'); breadcrumb.classList.add('visible'); } lastScrollY = currentScrollY; }; // Throttle scroll event pour les performances let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { handleScroll(); ticking = false; }); ticking = true; } }); }, // Navigate to a page navigateTo(page, game = null, content = null) { logSh(`🧭 Navigating to: ${page} ${game ? `(game: ${game})` : ''} ${content ? `(content: ${content})` : ''}`, 'INFO'); const params = { page }; if (game) params.game = game; if (content) params.content = content; Utils.setUrlParams(params); // Update history if (this.currentPage !== page) { this.navigationHistory.push(page); } this.currentPage = page; // Display appropriate page switch(page) { case 'games': if (!content) { logSh(`⚠️ Pas de contenu spécifié pour la page games, retour aux levels`, 'WARN'); this.showLevelsPage(); } else { // Utiliser l'async pour les jeux pour le système de compatibilité this.showGamesPage(content).catch(error => { logSh(`❌ Erreur lors de l'affichage des jeux: ${error.message}`, 'ERROR'); // Retour aux levels en cas d'erreur this.showLevelsPage(); }); } break; case 'levels': this.showLevelsPage(); break; case 'play': this.showGamePage(game, content); break; case 'settings': this.showSettingsPage(); break; default: this.showHomePage(); } this.updateBreadcrumb(); }, // Go back goBack() { logSh(`⬅️ Going back from: ${this.currentPage}`, 'INFO'); if (this.navigationHistory.length > 1) { this.navigationHistory.pop(); // Remove current page const previousPage = this.navigationHistory[this.navigationHistory.length - 1]; logSh(`📍 Going back to: ${previousPage}`, 'DEBUG'); const params = Utils.getUrlParams(); if (previousPage === 'levels') { this.navigateTo('levels', params.game); } else if (previousPage === 'games') { // Récupérer le content depuis les paramètres URL ou retourner aux levels const urlContent = params.content; if (urlContent) { this.navigateTo('games', null, urlContent); } else { logSh(`⚠️ Pas de content pour revenir aux jeux, retour aux levels`, 'WARN'); this.navigateTo('levels'); } } else { this.navigateTo('home'); } } }, // Display home page showHomePage() { logSh('🏠 Displaying home page', 'INFO'); this.hideAllPages(); document.getElementById('home-page').classList.add('active'); this.currentPage = 'home'; this.updateBreadcrumb(); }, showSettingsPage() { logSh('⚙️ Displaying settings page', 'INFO'); this.hideAllPages(); document.getElementById('settings-page').classList.add('active'); this.currentPage = 'settings'; this.updateBreadcrumb(); // Initialize settings if SettingsManager is available if (window.SettingsManager) { // Ensure SettingsManager is initialized for this page setTimeout(() => { window.SettingsManager.init(); }, 100); } }, // Display games selection page async showGamesPage(contentType) { logSh(`🎮 Displaying games selection page for content: ${contentType}`, 'INFO'); this.hideAllPages(); document.getElementById('games-page').classList.add('active'); this.currentPage = 'games'; this.selectedContent = contentType; // Update description first const contentInfo = this.gamesConfig?.content[contentType]; if (contentInfo) { document.getElementById('game-description').textContent = `Select a game to play with "${contentInfo.name}"`; } else { document.getElementById('game-description').textContent = `Select a game to play with this content`; } this.updateBreadcrumb(); // Render games grid (async) await this.renderGamesGrid(contentType); }, // Display levels selection page (now the first step) showLevelsPage() { logSh('📚 Displaying levels selection page', 'INFO'); this.hideAllPages(); document.getElementById('levels-page').classList.add('active'); this.renderLevelsGrid(); this.currentPage = 'levels'; this.updateBreadcrumb(); }, // Display game page async showGamePage(gameType, contentType) { this.hideAllPages(); document.getElementById('game-page').classList.add('active'); this.currentPage = 'play'; Utils.showLoading(); try { await GameLoader.loadGame(gameType, contentType); this.updateBreadcrumb(); } catch (error) { logSh('Game loading error:', error, 'ERROR'); Utils.showToast('Error loading game', 'error'); this.goBack(); } finally { Utils.hideLoading(); } }, // Hide all pages hideAllPages() { document.querySelectorAll('.page').forEach(page => { page.classList.remove('active'); }); }, // Render games grid async renderGamesGrid(contentType) { logSh(`🎲 Generating games grid for content: ${contentType}...`, 'DEBUG'); const grid = document.getElementById('games-grid'); grid.innerHTML = '
🔍 Analyzing game compatibility...
'; if (!this.gamesConfig) { logSh('❌ No games configuration available', 'ERROR'); return; } // DEBUG: Log détaillé du contenu logSh(`🔍 DEBUG: Recherche contenu avec ID: "${contentType}"`, 'DEBUG'); logSh(`🔍 DEBUG: Contenu scanné disponible: ${this.scannedContent?.found?.length || 0} items`, 'DEBUG'); if (this.scannedContent?.found) { this.scannedContent.found.forEach(content => { logSh(`🔍 DEBUG: Contenu trouvé - ID: "${content.id}", Name: "${content.name}"`, 'DEBUG'); }); } // Récupérer les informations du contenu let contentInfo = null; if (this.scannedContent && this.scannedContent.found) { // Recherche directe par ID contentInfo = this.scannedContent.found.find(content => content.id === contentType); // Si pas trouvé, essayer de chercher par nom de fichier ou nom de module if (!contentInfo) { contentInfo = this.scannedContent.found.find(content => content.filename.replace('.js', '') === contentType || content.filename.replace('.js', '').toLowerCase() === contentType || content.id.toLowerCase() === contentType.toLowerCase() ); } } if (!contentInfo) { logSh(`⚠️ Content info not found for ${contentType}`, 'WARN'); logSh(`🔍 DEBUG: IDs disponibles: ${this.scannedContent?.found?.map(c => c.id).join(', ')}`, 'DEBUG'); logSh(`🔍 DEBUG: Noms de fichiers: ${this.scannedContent?.found?.map(c => c.filename).join(', ')}`, 'DEBUG'); } else { logSh(`✅ DEBUG: Contenu trouvé: ${contentInfo.name} (ID: ${contentInfo.id})`, 'DEBUG'); } const enabledGames = Object.entries(this.gamesConfig.games).filter(([key, game]) => game.enabled); logSh(`🎯 ${enabledGames.length} enabled games found`, 'INFO'); // Clear loading grid.innerHTML = ''; // Analyser la compatibilité et séparer les jeux const compatibleGames = []; const incompatibleGames = []; enabledGames.forEach(([key, game]) => { let compatibility = null; if (contentInfo && this.compatibilityChecker) { // Récupérer le module JavaScript réel pour le test de compatibilité const moduleName = this.getModuleName(contentType); const actualContentModule = window.ContentModules?.[moduleName]; if (actualContentModule) { compatibility = this.compatibilityChecker.checkCompatibility(actualContentModule, key); logSh(`🎯 ${game.name} compatibility: ${compatibility.compatible ? '✅' : '❌'} (score: ${compatibility.score}%) - ${compatibility.reason}`, 'DEBUG'); } else { logSh(`⚠️ Module JavaScript non trouvé: ${moduleName}`, 'WARN'); // Pas de compatibilité = compatible par défaut (comportement de fallback) compatibility = { compatible: true, score: 50, reason: "Module not loaded - default compatibility" }; } } const gameData = { key, game, compatibility }; if (!compatibility || compatibility.compatible) { compatibleGames.push(gameData); } else { incompatibleGames.push(gameData); } }); // Afficher d'abord les jeux compatibles if (compatibleGames.length > 0) { const compatibleSection = document.createElement('div'); compatibleSection.className = 'compatible-games-section'; if (incompatibleGames.length > 0) { compatibleSection.innerHTML = '

🎯 Jeux recommandés

'; } compatibleGames .sort((a, b) => (b.compatibility?.score || 50) - (a.compatibility?.score || 50)) .forEach(({ key, game, compatibility }) => { const card = this.createGameCard(key, game, contentType, compatibility); compatibleSection.appendChild(card); }); grid.appendChild(compatibleSection); } // Puis afficher les jeux incompatibles (avec avertissement) if (incompatibleGames.length > 0) { const incompatibleSection = document.createElement('div'); incompatibleSection.className = 'incompatible-games-section'; incompatibleSection.innerHTML = '

⚠️ Jeux avec limitations

'; incompatibleGames.forEach(({ key, game, compatibility }) => { const card = this.createGameCard(key, game, contentType, compatibility); incompatibleSection.appendChild(card); }); grid.appendChild(incompatibleSection); } // Message si aucun contenu if (compatibleGames.length === 0 && incompatibleGames.length === 0) { grid.innerHTML = '
Aucun jeu disponible
'; } }, // Create a game card createGameCard(gameKey, gameInfo, contentType, compatibility = null) { const card = document.createElement('div'); // Classes CSS selon la compatibilité let cardClass = 'game-card'; let compatibilityBadge = ''; let compatibilityInfo = ''; let clickable = true; if (compatibility) { if (compatibility.compatible) { cardClass += ' compatible'; if (compatibility.score >= 80) { compatibilityBadge = '
🎯 Excellent
'; } else if (compatibility.score >= 60) { compatibilityBadge = '
✅ Recommandé
'; } else { compatibilityBadge = '
👍 Compatible
'; } } else { cardClass += ' incompatible'; compatibilityBadge = '
⚠️ Limité
'; clickable = false; // Désactiver le clic pour les jeux incompatibles } // Informations détaillées de compatibilité compatibilityInfo = `
Score: ${compatibility.score}%
${compatibility.reason}
`; } else { // Pas d'analyse de compatibilité compatibilityBadge = '
❓ Non analysé
'; } card.className = cardClass; card.innerHTML = `
${gameInfo.icon}
${compatibilityBadge}
${gameInfo.name}
${gameInfo.description}
${compatibilityInfo} ${!clickable ? '
Ce jeu nécessite plus de contenu pour fonctionner correctement
' : ''} `; if (clickable) { card.addEventListener('click', () => { logSh(`🎮 Clicked on game card: ${gameInfo.name} (${gameKey}) with content: ${contentType}`, 'INFO'); Utils.animateElement(card, 'pulse'); this.navigateTo('play', gameKey, contentType); }); } else { // Pour les jeux incompatibles, montrer les suggestions d'amélioration card.addEventListener('click', () => { this.showCompatibilityHelp(gameKey, gameInfo, compatibility); }); } return card; }, // Afficher l'aide de compatibilité pour les jeux incompatibles showCompatibilityHelp(gameKey, gameInfo, compatibility) { if (!this.compatibilityChecker || !compatibility) return; const modal = document.createElement('div'); modal.className = 'compatibility-help-modal'; // Récupérer les suggestions d'amélioration const contentInfo = this.scannedContent?.found?.find(content => content.id === this.selectedContent); const suggestions = contentInfo ? this.compatibilityChecker.getImprovementSuggestions(contentInfo, gameKey) : ['Enrichir le contenu du module']; modal.innerHTML = ` `; document.body.appendChild(modal); // Event listeners modal.querySelector('.close-btn').addEventListener('click', () => modal.remove()); modal.querySelector('.close-modal-btn').addEventListener('click', () => modal.remove()); modal.querySelector('.try-anyway-btn').addEventListener('click', () => { modal.remove(); logSh(`🎮 Forcing game launch: ${gameInfo.name} despite compatibility issues`, 'WARN'); this.navigateTo('play', gameKey, this.selectedContent); }); // Fermer avec ESC const handleEscape = (e) => { if (e.key === 'Escape') { modal.remove(); document.removeEventListener('keydown', handleEscape); } }; document.addEventListener('keydown', handleEscape); }, // Render levels grid async renderLevelsGrid() { const grid = document.getElementById('levels-grid'); grid.innerHTML = '
🔍 Searching for available content...
'; try { // Get all available content automatically const availableContent = await this.contentScanner.getAvailableContent(); if (availableContent.length === 0) { grid.innerHTML = '
No content found
'; return; } // Clear loading grid.innerHTML = ''; logSh(`📋 Displaying ${availableContent.length} content modules`, 'INFO'); // Create cards for each found content availableContent.forEach(content => { const card = this.createLevelCard(content.id, content); grid.appendChild(card); }); } catch (error) { logSh('Levels render error:', error, 'ERROR'); grid.innerHTML = '
❌ Error loading content
'; } }, // Convertir contentType vers nom de module JavaScript getModuleName(contentType) { const mapping = { 'sbs-level-7-8-new': 'SBSLevel78New', 'basic-chinese': 'BasicChinese', 'english-class-demo': 'EnglishClassDemo', 'chinese-long-story': 'ChineseLongStory', 'chinese-beginner-story': 'ChineseBeginnerStory', 'test-minimal': 'TestMinimal', 'test-rich': 'TestRich' }; return mapping[contentType] || this.toPascalCase(contentType); }, // Convertir kebab-case vers PascalCase toPascalCase(str) { return str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(''); }, // Create a level card createLevelCard(contentKey, contentInfo) { const card = document.createElement('div'); card.className = 'level-card'; // Calculate stats to display const stats = []; if (contentInfo.stats) { if (contentInfo.stats.vocabularyCount > 0) { stats.push(`📚 ${contentInfo.stats.vocabularyCount} words`); } if (contentInfo.stats.sentenceCount > 0) { stats.push(`💬 ${contentInfo.stats.sentenceCount} sentences`); } if (contentInfo.stats.dialogueCount > 0) { stats.push(`🎭 ${contentInfo.stats.dialogueCount} dialogues`); } } card.innerHTML = `
${contentInfo.icon}
${contentInfo.name}
${contentInfo.description}
${contentInfo.difficulty} ${contentInfo.metadata.totalItems} items ~${contentInfo.metadata.estimatedTime}min
${stats.length > 0 ? `
${stats.join(' • ')}
` : ''} `; card.addEventListener('click', () => { logSh(`📚 Clicked on content card: ${contentInfo.name} (${contentKey})`, 'INFO'); Utils.animateElement(card, 'pulse'); this.navigateTo('games', null, contentKey); }); return card; }, // Update breadcrumb updateBreadcrumb() { logSh(`🍞 Updating breadcrumb for page: ${this.currentPage}`, 'DEBUG'); const breadcrumb = document.getElementById('breadcrumb'); breadcrumb.innerHTML = ''; const params = Utils.getUrlParams(); // Add back button if not on home if (this.currentPage !== 'home' && this.navigationHistory.length > 1) { const backButton = document.createElement('button'); backButton.className = 'back-button'; backButton.innerHTML = '← Back'; backButton.onclick = () => this.goBack(); breadcrumb.appendChild(backButton); } // Home const homeItem = this.createBreadcrumbItem('🏠 Home', 'home', this.currentPage === 'home'); breadcrumb.appendChild(homeItem); // Levels if (['levels', 'games', 'play'].includes(this.currentPage)) { const levelsItem = this.createBreadcrumbItem('📚 Levels', 'levels', this.currentPage === 'levels'); breadcrumb.appendChild(levelsItem); } // Games (pour un contenu spécifique) if (['games', 'play'].includes(this.currentPage) && params.content) { // Récupérer le nom du contenu depuis les résultats du scan let contentName = params.content; if (this.scannedContent?.found) { const foundContent = this.scannedContent.found.find(c => c.id === params.content); if (foundContent) { contentName = foundContent.name; } } const gamesItem = this.createBreadcrumbItem(`🎮 ${contentName}`, 'games', this.currentPage === 'games', params.content); breadcrumb.appendChild(gamesItem); } // Current game if (this.currentPage === 'play' && params.content) { const contentInfo = this.gamesConfig?.content[params.content]; const playText = contentInfo ? `🎯 ${contentInfo.name}` : 'Game'; const playItem = this.createBreadcrumbItem(playText, 'play', true); breadcrumb.appendChild(playItem); } }, // Create a breadcrumb element createBreadcrumbItem(text, page, isActive, content = null) { const item = document.createElement('button'); item.className = `breadcrumb-item ${isActive ? 'active' : ''}`; item.textContent = text; item.dataset.page = page; if (!isActive) { item.addEventListener('click', () => { logSh(`🍞 Clicked on breadcrumb: ${text} → ${page}`, 'INFO'); if (page === 'home') { this.navigateTo('home'); } else if (page === 'games' && content) { this.navigateTo('games', null, content); } else if (page === 'levels') { this.navigateTo('levels'); } }); } return item; } }; // Global functions for HTML window.navigateTo = (page, game, content) => AppNavigation.navigateTo(page, game, content); window.goBack = () => AppNavigation.goBack(); // Export window.AppNavigation = AppNavigation;