Warning: truncated output (original token count: 140222) Total output lines: 10408 🎓 ChessMentor Pro v5.9.1 - ChessNova Academy
…128222 tokens truncated…color: #a855f7;">${excellents}
💎 Excellents
${goods}
✅ Bons
${inaccuracies}
⚠️ Imprécis
${mistakes}
❌ Erreurs
${blunders}
💥 Gaffes

📋 Infos partie

Coups joués ${moves}
Durée ${duration}
${game.pgn ? `` : ''}
`; modal.style.display = 'flex'; } // === FIREBASE AUTH FUNCTIONS === async function loginWithGoogle() { if (!auth) { showAuthError('Firebase non configuré'); return; } const provider = new firebase.auth.GoogleAuthProvider(); try { // Try popup first (most reliable) const result = await auth.signInWithPopup(provider); if (result && result.user) { console.log('Google popup login success:', result.user.displayName); closeAuthModal(); } } catch (err) { // If popup blocked/failed, fallback to redirect if (['auth/popup-blocked', 'auth/popup-closed-by-user', 'auth/cancelled-popup-request', 'auth/web-storage-unsupported', 'auth/operation-not-supported-in-this-environment'].includes(err.code)) { console.warn('Popup blocked, falling back to redirect'); await auth.signInWithRedirect(provider); } else { console.error('Google login error:', err); const messages = { 'auth/unauthorized-domain': 'Ce domaine n’est pas autorisé dans Firebase Authentication. Ajoutez jira44.github.io dans les domaines autorisés du projet chessnova.', 'auth/operation-not-allowed': 'La connexion Google n’est pas activée dans Firebase Authentication > Méthodes de connexion.', 'auth/invalid-api-key': 'La clé Firebase de ChessNova est invalide.', 'auth/network-request-failed': 'Connexion réseau impossible au service Google/Firebase. Vérifiez votre réseau puis réessayez.' }; showAuthError(messages[err.code] || (err.code || 'Erreur') + ' : ' + (err.message || 'Connexion Google impossible.')); } } } // Handle redirect result on page load (fallback case) if (auth) { auth.getRedirectResult().then(function(result) { if (result && result.user) { console.log('Google redirect login success:', result.user.displayName); closeAuthModal(); } }).catch(function(err) { if (err.code !== 'auth/no-auth-event') { console.error('Redirect result error:', err); } }); } async function loginWithFirebaseEmail(email, password) { if (!auth) return false; try { await auth.signInWithEmailAndPassword(email, password); return true; } catch (err) { console.error('Email login error:', err); showAuthError(err.message); return false; } } async function registerWithFirebaseEmail(email, password, username) { if (!auth) return false; try { const cred = await auth.createUserWithEmailAndPassword(email, password); await cred.user.updateProfile({ displayName: username }); await createUserDoc(cred.user, username); return true; } catch (err) { console.error('Register error:', err); showAuthError(err.message); return false; } } async function logoutFirebase() { if (auth) await auth.signOut(); currentUser = null; sessionId = null; localStorage.removeItem('chessnova_session'); updateUserUI(); showToast('Déconnecté', 'info'); } // Firestore user functions async function createUserDoc(user, username) { if (!db) return; await db.collection('users').doc(user.uid).set({ username: username || user.displayName || user.email.split('@')[0], email: user.email, elo_rating: 1200, created_at: firebase.firestore.FieldValue.serverTimestamp(), xp: 0, level: 1, badges: [], currentStreak: 0, bestStreak: 0, totalWins: 0, totalGames: 0 }, { merge: true }); } async function loadUserFromFirestore(uid) { if (!db) return null; const doc = await db.collection('users').doc(uid).get(); return doc.exists ? { uid, ...doc.data() } : null; } async function updateUserStats(stats) { if (!db || !currentUser?.uid) return; await db.collection('users').doc(currentUser.uid).update(stats); } async function saveGameToFirestore(gameData) { if (!db || !currentUser?.uid) return; await db.collection('games').add({ odUID: currentUser.uid, ...gameData, created_at: firebase.firestore.FieldValue.serverTimestamp() }); } async function loadGamesFromFirestore() { if (!db || !currentUser?.uid) return []; const snapshot = await db.collection('games') .where('odUID', '==', currentUser.uid) .orderBy('created_at', 'desc') .limit(50) .get(); return snapshot.docs.map(d => ({ id: d.id, ...d.data() })); } async function syncLocalToCloud() { if (!db || !currentUser?.uid) return; const localProgress = JSON.parse(localStorage.getItem('chessnova_progress') || '{}'); if (localProgress.totalGames > 0) { await db.collection('users').doc(currentUser.uid).update({ xp: firebase.firestore.FieldValue.increment(localProgress.xp || 0), totalGames: firebase.firestore.FieldValue.increment(localProgress.totalGames || 0), totalWins: firebase.firestore.FieldValue.increment(localProgress.totalWins || 0), bestStreak: Math.max(currentUser.bestStreak || 0, localProgress.bestStreak || 0) }); // Reload user data after sync to reflect merged stats const updated = await loadUserFromFirestore(currentUser.uid); if (updated) { currentUser = updated; updateUserUI(); } localStorage.removeItem('chessnova_progress'); showToast('Progression synchronisée!', 'success'); } } // Firebase Auth State Listener if (auth) { auth.onAuthStateChanged(async (user) => { if (user) { let userData = await loadUserFromFirestore(user.uid); if (!userData) { await createUserDoc(user, user.displayName || user.email.split('@')[0]); userData = await loadUserFromFirestore(user.uid); } // Ensure username is set (fallback to Google displayName) if (!userData.username || userData.username === 'undefined') { userData.username = user.displayName || user.email.split('@')[0]; await db.collection('users').doc(user.uid).update({ username: userData.username }); } currentUser = userData; updateUserUI(); await syncLocalToCloud(); showToast('Connecté: ' + (userData?.username || user.email), 'success'); } else { currentUser = null; updateUserUI(); } }); } function showLoginForm() { document.getElementById('loginForm').style.display = 'block'; document.getElementById('registerForm').style.display = 'none'; document.querySelectorAll('.modal-tab').forEach((t, i) => t.classList.toggle('active', i === 0)); document.getElementById('authError').classList.remove('show'); } function showRegisterForm() { document.getElementById('loginForm').style.display = 'none'; document.getElementById('registerForm').style.display = 'block'; document.querySelectorAll('.modal-tab').forEach((t, i) => t.classList.toggle('active', i === 1)); document.getElementById('authError').classList.remove('show'); } function showAuthError(msg) { const el = document.getElementById('authError'); el.textContent = msg; el.classList.add('show'); } async function handleLogin(e) { e.preventDefault(); try { const identifier = document.getElementById('loginIdentifier').value; const isEmail = identifier.includes('@'); const res = await fetch(`${API_URL}/api/login`, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: isEmail ? identifier : undefined, username: !isEmail ? identifier : undefined, password: document.getElementById('loginPassword').value }) }); const data = await res.json(); if (data.success) { sessionId = data.sessionId; localStorage.setItem('chessnova_session', sessionId); currentUser = data.user; updateUserUI(); closeAuthModal(); showToast('Connecté!', 'success'); } else { showAuthError(data.error || 'Erreur'); } } catch (err) { console.error('Login error:', err); showAuthError('Erreur: ' + err.message); } } async function handleRegister(e) { e.preventDefault(); try { const res = await fetch(`${API_URL}/api/register`, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: document.getElementById('regUsername').value, email: document.getElementById('regEmail').value, password: document.getElementById('regPassword').value }) }); const data = await res.json(); if (data.success) { sessionId = data.sessionId; localStorage.setItem('chessnova_session', sessionId); currentUser = data.user; updateUserUI(); closeAuthModal(); showToast('Compte créé!', 'success'); } else { showAuthError(data.error || 'Erreur'); } } catch (err) { console.error('Register error:', err); showAuthError('Erreur: ' + err.message); } } async function logout() { // Firebase logout if (auth) { await logoutFirebase(); return; } // Old API logout if (sessionId) { await fetch(`${API_URL}/api/logout`, { method: 'POST', headers: { 'Authorization': `Bearer ${sessionId}` }}); } localStorage.removeItem('chessnova_session'); sessionId = null; currentUser = null; updateUserUI(); showToast('Déconnecté', 'success'); } async function checkSession() { if (!sessionId) return; try { const res = await fetch(`${API_URL}/api/profile`, { headers: { 'Authorization': `Bearer ${sessionId}` }}); const data = await res.json(); if (data.user) { currentUser = data.user; sessionStats.estimatedElo = data.user.elo_rating; updateUserUI(); } } catch (err) { console.log('Session invalide'); } } function updateUserUI() { const userArea = document.getElementById('userArea'); if (currentUser) { userArea.innerHTML = `
👤 ${currentUser.username}ELO ${currentUser.elo_rating}Déconnexion
`; } else { userArea.innerHTML = `🔑 Connexion`; } } async function saveGameToServer(result) { if (!currentUser || !sessionId) return; const duration = gameStartTime ? Math.floor((Date.now() - gameStartTime) / 1000) : 0; try { const res = await fetch(`${API_URL}/api/games`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${sessionId}` }, body: JSON.stringify({ result, opponent_type: 'ai', opponent_level: difficulty, pgn: game.pgn(), moves_count: game.history().length, accuracy: sessionStats.totalMoves > 0 ? (sessionStats.perfectMoves / sessionStats.totalMoves * 100) : 0, brilliant_moves: sessionStats.brilliantMoves, excellent_moves: sessionStats.excellentMoves, good_moves: sessionStats.goodMoves, inaccuracies: sessionStats.inaccuracies, mistakes: sessionStats.mistakes, blunders: sessionStats.blunders, duration_seconds: duration }) }); const data = await res.json(); if (data.success) { currentUser.elo_rating = data.elo_after; sessionStats.estimatedElo = data.elo_after; updateUserUI(); showToast(`ELO: ${data.elo_before} → ${data.elo_after}`, 'success'); } } catch (err) { console.error('Erreur sauvegarde:', err); } } async function updateOnlinePlayers() { const displayCount = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? String(value) : '—'; const renderUnavailable = () => { document.getElementById('online-players').textContent = '—'; document.getElementById('real-players').textContent = '—'; document.getElementById('simulated-players').textContent = '—'; document.getElementById('visitors-count').textContent = '—'; }; try { const res = await fetch(`${API_URL}/api/online`); if (!res.ok) throw new Error(`Online API returned HTTP ${res.status}`); const data = await res.json(); document.getElementById('online-players').textContent = displayCount(data.online); document.getElementById('real-players').textContent = displayCount(data.realPlayers); document.getElementById('simulated-players').textContent = displayCount(data.simulatedPlayers); document.getElementById('visitors-count').textContent = displayCount(data.visitors); } catch (err) { console.warn('Online player counts unavailable:', err); renderUnavailable(); } } // ======================================== // INITIALISATION // ======================================== window.addEventListener('DOMContentLoaded', function() { try { // Charger données const loaded = loadFromLocalStorage(); // Init interface initBoard(); initStockfish(); // Initialiser Stockfish WASM loadZupKnowledge(); // Charger les donnees ZUP AI SUPAIntegration.init(); // Initialize SUPA AI connection TournamentSystem.init(); // Initialize tournament system updateGameInfo(); updateStatsDisplay(); initEloChart(); // NE PAS appeler updateSuggestedMoves() ici - ça bloque le thread! // Les suggestions seront chargées quand Stockfish sera prêt (voir setupStockfishHandlers) // Pour l'instant, juste afficher un message d'attente const suggestedList = document.getElementById('suggested-moves-list'); if (suggestedList) { suggestedList.innerHTML = '

⏳ Chargement Stockfish...

'; } // Mettre à jour l'historique des coups updateMoveHistory(); // Appliquer difficulté document.querySelectorAll('.difficulty-btn').forEach(btn => btn.classList.remove('active')); const diffBtn = document.querySelector(`[onclick="setDifficulty('${difficulty}')"]`); if (diffBtn) diffBtn.classList.add('active'); // Initialiser le bandeau adversaire const initSettings = difficultyLevels[difficulty] || difficultyLevels['beginner']; updateOpponentBanner( initSettings.emoji || '🌱', initSettings.name || 'Mentor Débutant', initSettings.elo || 800, initSettings.color || '#22c55e', difficulty ); if (!loaded) { showToast('🎮 Bienvenue sur ChessNova Pro!', 'success'); } // Auto-save périodique setInterval(() => { if (autoSave && game.history().length > 0) { saveToLocalStorage(); } }, 30000); // Compteur de joueurs et auth updateOnlinePlayers(); setInterval(updateOnlinePlayers, 30000); checkSession(); gameStartTime = Date.now(); } catch (error) { console.error('Init error:', error); showToast('❌ Erreur initialisation', 'error'); } }); // Sauvegarde avant fermeture window.addEventListener('beforeunload', () => { if (autoSave) saveToLocalStorage(); }); // Gestion erreurs globales - silencieuse sauf erreurs critiques window.addEventListener('error', (e) => { // Log pour debug mais pas de toast pour les erreurs mineures console.error('Global error:', e.error); // Ne pas spammer l'utilisateur avec des erreurs mineures // Les erreurs critiques sont gérées localement dans chaque fonction }); // Gestion des rejets de promesses non gérés window.addEventListener('unhandledrejection', (e) => { console.warn('Unhandled promise rejection:', e.reason); e.preventDefault(); // Empêche l'erreur de se propager });