// === 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.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 = `
Raison: ${compatibility.reason}
Score actuel: ${compatibility.score}% (minimum requis: ${compatibility.details?.minimumScore || 'N/A'}%)
💡 Suggestions d'amélioration:
${suggestions.map(suggestion => `- ${suggestion}
`).join('')}
`;
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.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;