Warning: truncated output (original token count: 140222)
Total output lines: 10408
🎓 ChessMentor Pro v5.9.1 - ChessNova Academy
🎓 Connexion ChessNova
— ou —
Continuer sans compte
🏆 Classement des Joueurs
Fermer
…128222 tokens truncated…color: #a855f7;">${excellents}
💎 Excellents
${inaccuracies}
⚠️ Imprécis
📋 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
});