/* ============================================ ANALYTICS & EVENT TRACKING ============================================ */ /** * Manager d'analytics pour tracker les événements */ class AnalyticsManager { constructor() { this.sessionId = generateUUID(); this.events = []; this.enabled = true; this.endpoint = window.ANALYTICS_ENDPOINT || null; } /** * Enregistrer un événement */ track(eventName, data = {}) { if (!this.enabled) return; const event = { type: eventName, timestamp: Date.now(), sessionId: this.sessionId, userId: getUserId(), playerName: getPlayerName(), data: data }; this.events.push(event); // Logger en debug DEBUG.log(`Event tracked: ${eventName}`, data); // Envoyer au serveur si configuré if (this.endpoint) { this.sendToServer(event); } } /** * Démarrage du jeu */ trackGameStart(difficulty) { this.track('game_started', { difficulty: difficulty, mode: DIFFICULTY_MODES[difficulty].name }); } /** * Retournement de carte */ trackFlip(cardIndex, totalFlips) { this.track('card_flipped', { cardIndex: cardIndex, totalFlips: totalFlips }); } /** * Paire trouvée */ trackMatch(pictogrammeId, totalMatches, totalMoves) { this.track('pair_matched', { pictogrammeId: pictogrammeId, totalMatches: totalMatches, totalMoves: totalMoves }); } /** * Paire incorrecte */ trackMismatch(pictogramme1Id, pictogramme2Id, move) { this.track('pair_mismatched', { pictogramme1: pictogramme1Id, pictogramme2: pictogramme2Id, move: move }); } /** * Fin du jeu */ trackGameEnd(gameData) { this.track('game_completed', { difficulty: gameData.difficulty, finalScore: gameData.finalScore, moves: gameData.moves, duration: gameData.duration, won: gameData.won, achievements: gameData.achievements || [] }); } /** * Changement de difficulté */ trackDifficultyChange(newDifficulty) { this.track('difficulty_changed', { newDifficulty: newDifficulty }); } /** * Affichage du classement */ trackLeaderboardView(filter) { this.track('leaderboard_viewed', { filter: filter }); } /** * Envoyer les événements au serveur */ sendToServer(event) { if (!this.endpoint) return; fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }).catch(err => { // Silencieusement échouer en offline DEBUG.log('Analytics offline:', err); }); } /** * Envoyer batch d'événements */ sendBatch() { if (!this.endpoint || this.events.length === 0) return; fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ batch: true, events: this.events, sessionId: this.sessionId }) }).then(() => { this.events = []; // Vider après envoi DEBUG.log('Analytics batch sent'); }).catch(err => { DEBUG.log('Analytics batch failed:', err); }); } /** * Obtenir le résumé de la session */ getSessionSummary() { return { sessionId: this.sessionId, userId: getUserId(), playerName: getPlayerName(), eventCount: this.events.length, events: this.events }; } /** * Exporter les données en JSON */ exportJSON() { const data = { exportDate: new Date().toISOString(), sessionSummary: this.getSessionSummary(), allEvents: this.events }; return JSON.stringify(data, null, 2); } /** * Télécharger les données */ downloadData() { const data = this.exportJSON(); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `analytics-${generateUUID().slice(0, 8)}.json`; link.click(); URL.revokeObjectURL(url); } /** * Nettoyer les événements */ clear() { this.events = []; DEBUG.log('Analytics cleared'); } } /** * Heatmap Analytics - Tracker l'interaction avec les cartes */ class HeatmapAnalytics { constructor() { this.heatmap = new Map(); } /** * Enregistrer un clic de carte */ recordCardClick(cardIndex) { if (!this.heatmap.has(cardIndex)) { this.heatmap.set(cardIndex, 0); } this.heatmap.set(cardIndex, this.heatmap.get(cardIndex) + 1); } /** * Obtenir les données de heatmap */ getHeatmap() { const sorted = Array.from(this.heatmap.entries()) .sort((a, b) => b[1] - a[1]); return Object.fromEntries(sorted); } /** * Calculer la carte la plus cliquée */ getMostClickedCard() { let maxClicks = 0; let maxCard = -1; for (const [card, clicks] of this.heatmap) { if (clicks > maxClicks) { maxClicks = clicks; maxCard = card; } } return { card: maxCard, clicks: maxClicks }; } } /** * Performance Monitor - Analyser les performances du jeu */ class PerformanceMonitor { constructor() { this.metrics = {}; this.startTime = performance.now(); } /** * Marquer un point de début */ mark(name) { this.metrics[name] = { start: performance.now(), end: null, duration: null }; } /** * Marquer la fin */ measure(name) { if (this.metrics[name]) { this.metrics[name].end = performance.now(); this.metrics[name].duration = this.metrics[name].end - this.metrics[name].start; return this.metrics[name].duration; } return null; } /** * Obtenir les métriques */ getMetrics() { return this.metrics; } /** * Afficher les métriques en console */ logMetrics() { console.table(this.metrics); } /** * FPS Monitor */ monitorFPS(callback) { let lastTime = performance.now(); let frames = 0; const checkFPS = () => { frames++; const currentTime = performance.now(); if (currentTime - lastTime >= 1000) { const fps = frames; callback(fps); frames = 0; lastTime = currentTime; } requestAnimationFrame(checkFPS); }; checkFPS(); } } /** * A/B Testing Manager */ class ABTestingManager { constructor() { this.tests = {}; } /** * Créer un test A/B */ createTest(testName, variants) { const userId = getUserId(); const variantIndex = parseInt(userId.slice(0, 8), 16) % variants.length; this.tests[testName] = { variants: variants, selectedVariant: variants[variantIndex], variantIndex: variantIndex }; return this.tests[testName].selectedVariant; } /** * Obtenir le variant */ getVariant(testName) { return this.tests[testName]?.selectedVariant || null; } /** * Tracker une conversion */ trackConversion(testName) { const test = this.tests[testName]; if (test) { DEBUG.log(`Conversion tracked for test: ${testName}, variant: ${test.selectedVariant}`); } } } // ============================================ // INITIALISATION GLOBALES // ============================================ // Créer les managers globalement const analyticsManager = new AnalyticsManager(); const heatmapAnalytics = new HeatmapAnalytics(); const performanceMonitor = new PerformanceMonitor(); const abTestingManager = new ABTestingManager(); // Exporter pour utilisation window.AnalyticsManager = AnalyticsManager; window.HeatmapAnalytics = HeatmapAnalytics; window.PerformanceMonitor = PerformanceMonitor; window.ABTestingManager = ABTestingManager; // Event listener pour envoyer les analytics avant de quitter window.addEventListener('beforeunload', () => { analyticsManager.sendBatch(); }); // Log de session au démarrage DEBUG.log('Analytics session started:', analyticsManager.sessionId);