diff --git a/backend/retroClient.js b/backend/retroClient.js index 8ac8efc..1d83deb 100644 --- a/backend/retroClient.js +++ b/backend/retroClient.js @@ -3,216 +3,254 @@ 'use strict'; const { - buildAuthorization, - getUserProfile, - getUserRecentAchievements, - getGame + buildAuthorization, + getUserProfile, + getUserRecentAchievements, + getGame, + getGameInfoAndUserProgress } = require('@retroachievements/api'); class RetroClient { - constructor() { - const RA_USER = process.env.RA_USER; - const RA_KEY = process.env.RA_KEY; - if (!RA_USER || !RA_KEY) { - throw new Error('RetroAchievements username and API key must be set in .env file'); + constructor() { + const RA_USER = process.env.RA_USER; + const RA_KEY = process.env.RA_KEY; + + if (!RA_USER || !RA_KEY) { + throw new Error('RetroAchievements username and API key must be set in .env file'); + } + + this.authorization = buildAuthorization({ + username: RA_USER, + webApiKey: RA_KEY + }); + console.log('🎮 RetroAchievements API client initialized'); } - this.authorization = buildAuthorization({ - username: RA_USER, - webApiKey: RA_KEY - }); + // Helper method to build correct image URLs + buildImageUrl(imagePath, baseUrl) { + if (!imagePath) return null; - console.log('🎮 RetroAchievements API client initialized'); - } + const toStr = (v) => (typeof v === 'string' ? v : String(v)); - // Helper method to build correct image URLs - buildImageUrl(imagePath, baseUrl) { - if (!imagePath) return null; + // If full URL provided, return as-is + if (/^https?:\/\//i.test(imagePath)) { + return imagePath; + } - const toStr = (v) => (typeof v === 'string' ? v : String(v)); + const pathStr = toStr(imagePath); + // Handle full server-relative paths that start with / + if (pathStr.startsWith('/')) { + // Ensure .png for badges + if (pathStr.startsWith('/Badge/')) { + const name = pathStr.split('/').pop().replace(/\.(png|jpe?g|gif|webp)$/i, ''); + return `https://media.retroachievements.org/Badge/${name}.png`; + } + return `https://media.retroachievements.org${pathStr}`; + } - // If full URL provided, return as-is - if (/^https?:\/\//i.test(imagePath)) { - return imagePath; + // Normalize base URL (strip trailing slashes) + const normalizedBase = toStr(baseUrl || '').replace(/\/+$/, ''); + + // Special handling for badges - always ensure .png extension + const isBadgeContext = /\/Badge(\/|$)/i.test(normalizedBase); + if (isBadgeContext) { + const cleanImagePath = pathStr.replace(/\.(png|jpe?g|gif|webp)$/i, ''); + return `${normalizedBase}/${cleanImagePath}.png`; + } + + // For other images (UserPic, Images), build normally + return `${normalizedBase}/${pathStr}`; } - const pathStr = toStr(imagePath); - - // Handle full server-relative paths that start with / - if (pathStr.startsWith('/')) { - // Ensure .png for badges - if (pathStr.startsWith('/Badge/')) { - const name = pathStr.split('/').pop().replace(/\.(png|jpe?g|gif|webp)$/i, ''); - return `https://media.retroachievements.org/Badge/${name}.png`; - } - return `https://media.retroachievements.org${pathStr}`; - } - - // Normalize base URL (strip trailing slashes) - const normalizedBase = toStr(baseUrl || '').replace(/\/+$/, ''); - - // Special handling for badges - always ensure .png extension - const isBadgeContext = /\/Badge(\/|$)/i.test(normalizedBase); - if (isBadgeContext) { - const cleanImagePath = pathStr.replace(/\.(png|jpe?g|gif|webp)$/i, ''); - return `${normalizedBase}/${cleanImagePath}.png`; - } - - // For other images (UserPic, Images), build normally - return `${normalizedBase}/${pathStr}`; - } - - async getUserProfile(username) { - try { - console.log(`📊 Fetching profile for user: ${username}`); - const profile = await getUserProfile(this.authorization, { username }); - return profile; - } catch (error) { - console.error(`❌ Error fetching user profile for ${username}:`, error.message); - throw new Error(`Failed to fetch user profile: ${error.message}`); - } - } - - async getUserRecentAchievements(username, count = 10) { - try { - console.log(`🏆 Fetching recent achievements for user: ${username}`); - const achievements = await getUserRecentAchievements(this.authorization, { - username, - count - }); - return achievements; - } catch (error) { - console.error(`❌ Error fetching recent achievements for ${username}:`, error.message); - throw new Error(`Failed to fetch recent achievements: ${error.message}`); - } - } - - async getGameInfo(gameId) { - try { - console.log(`🎮 Fetching game info for ID: ${gameId}`); - const game = await getGame(this.authorization, { gameId }); - return game; - } catch (error) { - console.error(`❌ Error fetching game info for ID ${gameId}:`, error.message); - throw new Error(`Failed to fetch game info: ${error.message}`); - } - } - - async getUserData(username) { - try { - const profile = await this.getUserProfile(username); - - let lastGame = null; - let lastAchievement = null; - - if (profile.lastGameId) { + async getUserProfile(username) { try { - lastGame = await this.getGameInfo(profile.lastGameId); - } catch (gameError) { - console.warn(`⚠️ Could not fetch last game info: ${gameError.message}`); + console.log(`📊 Fetching profile for user: ${username}`); + const profile = await getUserProfile(this.authorization, { + username + }); + return profile; + } catch (error) { + console.error(`❌ Error fetching user profile for ${username}:`, error.message); + throw new Error(`Failed to fetch user profile: ${error.message}`); } - } + } - try { - const recentAchievements = await this.getUserRecentAchievements(username, 1); - if (recentAchievements && recentAchievements.length > 0) { - lastAchievement = recentAchievements; + async getUserRecentAchievements(username, count = 10) { + try { + console.log(`🏆 Fetching recent achievements for user: ${username}`); + const achievements = await getUserRecentAchievements(this.authorization, { + username, + count + }); + return achievements; + } catch (error) { + console.error(`❌ Error fetching recent achievements for ${username}:`, error.message); + throw new Error(`Failed to fetch recent achievements: ${error.message}`); } - } catch (achievementError) { - console.warn(`⚠️ Could not fetch recent achievements: ${achievementError.message}`); - } - - return { - user: { - username: profile.user, - displayName: profile.user, - avatar: this.buildImageUrl( - profile.userPic, - 'https://media.retroachievements.org/UserPic' - ), - motto: profile.motto || '', - totalPoints: profile.totalPoints || 0, - totalTruePoints: profile.totalTruePoints || 0, - memberSince: profile.memberSince || null, - rank: profile.rank || null - }, - lastGame: lastGame - ? { - id: lastGame.gameId, - title: lastGame.title, - icon: this.buildImageUrl( - lastGame.gameIcon, - 'https://media.retroachievements.org/Images' - ), - consoleName: lastGame.consoleName - } - : null, - lastAchievement: lastAchievement - ? { - id: lastAchievement.achievementId, - title: lastAchievement.title, - description: lastAchievement.description, - points: lastAchievement.points, - trueRatio: lastAchievement.trueRatio, - gameTitle: lastAchievement.gameTitle, - gameIcon: this.buildImageUrl( - lastAchievement.gameIcon, - 'https://media.retroachievements.org/Images' - ), - badgeName: this.buildImageUrl( - lastAchievement.badgeName, - 'https://media.retroachievements.org/Badge' - ), - dateEarned: lastAchievement.dateEarned - } - : null - }; - } catch (error) { - console.error(`❌ Error in getUserData for ${username}:`, error.message); - throw error; } - } - async getOverlayData(username, count = 5) { - try { - const recentAchievements = await this.getUserRecentAchievements(username, count); - - const formattedAchievements = recentAchievements.map((achievement) => { - // Debug log to see what we're getting from the API - console.log( - `🔍 Processing badge for achievement "${achievement.title}": badgeName="${achievement.badgeName}"` - ); - - return { - id: achievement.achievementId, - title: achievement.title, - description: achievement.description, - points: achievement.points, - trueRatio: achievement.trueRatio || achievement.points, - gameTitle: achievement.gameTitle, - gameIcon: this.buildImageUrl( - achievement.gameIcon, - 'https://media.retroachievements.org/Images' - ), - badgeName: this.buildImageUrl( - achievement.badgeName, - 'https://media.retroachievements.org/Badge' - ), - dateEarned: achievement.dateEarned - }; - }); - - return { - username, - recentAchievements: formattedAchievements, - lastUpdated: new Date().toISOString() - }; - } catch (error) { - console.error(`❌ Error in getOverlayData for ${username}:`, error.message); - throw error; + async getGameInfo(gameId) { + try { + console.log(`🎮 Fetching game info for ID: ${gameId}`); + const game = await getGame(this.authorization, { + gameId + }); + return game; + } catch (error) { + console.error(`❌ Error fetching game info for ID ${gameId}:`, error.message); + throw new Error(`Failed to fetch game info: ${error.message}`); + } + } + + async getUserData(username) { + try { + const profile = await this.getUserProfile(username); + let lastGame = null; + let lastAchievement = null; + + if (profile.lastGameId) { + try { + lastGame = await this.getGameInfo(profile.lastGameId); + } catch (gameError) { + console.warn(`⚠️ Could not fetch last game info: ${gameError.message}`); + } + } + + try { + const recentAchievements = await this.getUserRecentAchievements(username, 1); + if (recentAchievements && recentAchievements.length > 0) { + lastAchievement = recentAchievements[0]; + } + } catch (achievementError) { + console.warn(`⚠️ Could not fetch recent achievements: ${achievementError.message}`); + } + + return { + user: { + username: profile.user, + displayName: profile.user, + avatar: this.buildImageUrl( + profile.userPic, + 'https://media.retroachievements.org/UserPic' + ), + motto: profile.motto || '', + totalPoints: profile.totalPoints || 0, + totalTruePoints: profile.totalTruePoints || 0, + memberSince: profile.memberSince || null, + rank: profile.rank || null + }, + lastGame: lastGame ? + { + id: lastGame.gameId, + title: lastGame.title, + icon: this.buildImageUrl( + lastGame.gameIcon, + 'https://media.retroachievements.org/Images' + ), + consoleName: lastGame.consoleName + } : + null, + lastAchievement: lastAchievement ? + { + id: lastAchievement.achievementId, + title: lastAchievement.title, + description: lastAchievement.description, + points: lastAchievement.points, + trueRatio: lastAchievement.trueRatio, + gameTitle: lastAchievement.gameTitle, + gameIcon: this.buildImageUrl( + lastAchievement.gameIcon, + 'https://media.retroachievements.org/Images' + ), + badgeName: this.buildImageUrl( + lastAchievement.badgeName, + 'https://media.retroachievements.org/Badge' + ), + dateEarned: lastAchievement.dateEarned + } : + null + }; + } catch (error) { + console.error(`❌ Error in getUserData for ${username}:`, error.message); + throw error; + } + } + + async getOverlayData(username, count = 5) { + try { + const recentAchievements = await this.getUserRecentAchievements(username, count); + let gameProgress = null; + let gameId = null; + + // First, try to get gameId from the most recent achievement. This is the most "live" data. + if (recentAchievements && recentAchievements.length > 0) { + gameId = recentAchievements[0].gameId; + } else { + // If no recent achievements, fall back to the user's profile to find their last played game. + console.log(`🤔 No recent achievements for ${username}. Checking profile for last game.`); + try { + const profile = await this.getUserProfile(username); + if (profile.lastGameId) { + gameId = profile.lastGameId; + } + } catch (profileError) { + console.warn(`⚠️ Could not fetch profile to find last game: ${profileError.message}`); + } + } + + // If we found a gameId from either method, fetch its progress. + if (gameId) { + try { + const progressData = await getGameInfoAndUserProgress(this.authorization, { + username, + gameId + }); + + gameProgress = { + title: progressData.title, + icon: this.buildImageUrl(progressData.imageIcon, 'https://media.retroachievements.org/Images'), + consoleName: progressData.consoleName, + numAwarded: progressData.numAwardedToUser, + totalAchievements: progressData.numAchievements, + completion: progressData.userCompletion, + }; + } catch (progressError) { + console.warn(`⚠️ Could not fetch game progress for gameId ${gameId}: ${progressError.message}`); + } + } + + const formattedAchievements = recentAchievements.map((achievement) => { + return { + id: achievement.achievementId, + title: achievement.title, + description: achievement.description, + points: achievement.points, + trueRatio: achievement.trueRatio || achievement.points, + gameTitle: achievement.gameTitle, + gameIcon: this.buildImageUrl( + achievement.gameIcon, + 'https://media.retroachievements.org/Images' + ), + badgeName: this.buildImageUrl( + achievement.badgeName, + 'https://media.retroachievements.org/Badge' + ), + dateEarned: achievement.dateEarned + }; + }); + + return { + username, + gameProgress, + recentAchievements: formattedAchievements, + lastUpdated: new Date().toISOString() + }; + } catch (error) { + console.error(`❌ Error in getOverlayData for ${username}:`, error.message); + throw error; + } } - } } module.exports = new RetroClient(); diff --git a/public/css/overlay.css b/public/css/overlay.css index 9465210..8fa38e9 100644 --- a/public/css/overlay.css +++ b/public/css/overlay.css @@ -57,6 +57,34 @@ body { 100% { opacity: 1; } } +/* Waiting for Activity Display */ +.waiting-display { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(30, 60, 114, 0.9); + padding: 40px; + border-radius: 12px; + text-align: center; + border: 2px solid rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + width: 80%; + max-width: 450px; +} + +.waiting-display h2 { + margin-bottom: 15px; + font-size: 1.5em; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); +} + +.waiting-display p { + opacity: 0.8; + line-height: 1.5; +} + /* Current Game */ .current-game { background: linear-gradient(135deg, rgba(30, 60, 114, 0.9), rgba(42, 82, 152, 0.9)); @@ -92,6 +120,39 @@ body { font-size: 1.1em; } +/* Game Progress Bar */ +.game-progress { + margin-top: 15px; +} + +.progress-stats { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 1em; + margin-bottom: 8px; + opacity: 0.9; + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7); +} + +.progress-bar-container { + background: rgba(0, 0, 0, 0.3); + border-radius: 20px; + height: 12px; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.4); +} + +.progress-bar-fill { + width: 0%; + height: 100%; + background: linear-gradient(90deg, #1e90ff, #00bfff); + border-radius: 20px; + transition: width 0.5s ease-in-out; +} + /* Achievements Container */ .achievements-container { flex: 1; @@ -117,26 +178,24 @@ body { display: flex; flex-direction: column; gap: 12px; - max-height: 400px; + max-height: 400px; /* Adjust as needed */ overflow-y: auto; padding-right: 10px; + list-style-type: none; } /* Custom Scrollbar */ .achievements-list::-webkit-scrollbar { width: 6px; } - .achievements-list::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); border-radius: 3px; } - .achievements-list::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 3px; } - .achievements-list::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } @@ -245,12 +304,10 @@ body { margin-bottom: 10px; font-size: 1.3em; } - .error-content p { margin-bottom: 20px; opacity: 0.9; } - .error-content button { background: rgba(255, 255, 255, 0.2); color: white; @@ -260,7 +317,6 @@ body { cursor: pointer; transition: background 0.3s; } - .error-content button:hover { background: rgba(255, 255, 255, 0.3); } @@ -275,12 +331,10 @@ body { .overlay-container { padding: 10px; } - .game-header { flex-direction: column; text-align: center; } - .achievement-item { flex-direction: column; text-align: center; diff --git a/public/js/overlay.js b/public/js/overlay.js index 5671a85..bc1e81b 100644 --- a/public/js/overlay.js +++ b/public/js/overlay.js @@ -1,5 +1,4 @@ // overlay.js - WebSocket client for real-time achievement updates - class RetroPulseOverlay { constructor() { this.ws = null; @@ -7,16 +6,29 @@ class RetroPulseOverlay { this.reconnectInterval = null; this.reconnectDelay = 3000; // 3 seconds this.lastUpdateTime = null; - + // DOM elements this.connectionStatus = document.getElementById('connection-status'); + this.errorDisplay = document.getElementById('error-display'); + this.errorMessage = document.getElementById('error-message'); + this.waitingForActivity = document.getElementById('waiting-for-activity'); + + // Game elements this.currentGame = document.getElementById('current-game'); this.gameIcon = document.getElementById('game-icon'); this.gameTitle = document.getElementById('game-title'); this.consoleName = document.getElementById('console-name'); + + // Progress bar elements + this.gameProgressSection = document.getElementById('game-progress-section'); + this.progressText = document.getElementById('progress-text'); + this.progressPercentage = document.getElementById('progress-percentage'); + this.progressBar = document.getElementById('progress-bar'); + + // Achievements elements + this.achievementsContainer = document.getElementById('achievements-container'); this.achievementsList = document.getElementById('achievements-list'); - this.errorDisplay = document.getElementById('error-display'); - this.errorMessage = document.getElementById('error-message'); + this.init(); } @@ -24,7 +36,7 @@ class RetroPulseOverlay { init() { // Extract username from URL path const pathParts = window.location.pathname.split('/'); - this.username = pathParts[pathParts.length - 1]; + this.username = decodeURIComponent(pathParts[pathParts.length - 1]); if (!this.username || this.username === 'overlay') { this.showError('No username specified in URL'); @@ -40,15 +52,14 @@ class RetroPulseOverlay { // Determine WebSocket URL (ws:// for http, wss:// for https) const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws?user=${encodeURIComponent(this.username)}`; - + console.log(`🔌 Connecting to WebSocket: ${wsUrl}`); + this.updateConnectionStatus('connecting'); this.ws = new WebSocket(wsUrl); this.ws.onopen = () => { console.log('✅ WebSocket connected'); this.updateConnectionStatus('connected'); - - // Clear reconnect interval if it exists if (this.reconnectInterval) { clearInterval(this.reconnectInterval); this.reconnectInterval = null; @@ -67,9 +78,7 @@ class RetroPulseOverlay { this.ws.onclose = (event) => { console.log('❌ WebSocket disconnected:', event.code, event.reason); this.updateConnectionStatus('disconnected'); - - // Don't reconnect if it was a clean close - if (event.code !== 1000) { + if (event.code !== 1000) { // Don't reconnect on clean close this.scheduleReconnect(); } }; @@ -77,6 +86,7 @@ class RetroPulseOverlay { this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.updateConnectionStatus('disconnected'); + // The onclose event will fire next, which will handle reconnecting. }; } catch (error) { @@ -84,15 +94,22 @@ class RetroPulseOverlay { this.showError(`Connection failed: ${error.message}`); } } + + scheduleReconnect() { + if (this.reconnectInterval) return; // Reconnect already scheduled + console.log(`⏱️ Scheduling reconnect in ${this.reconnectDelay / 1000} seconds...`); + this.reconnectInterval = setInterval(() => { + console.log('⌛ Reconnecting...'); + this.connect(); + }, this.reconnectDelay); + } handleMessage(message) { console.log('📨 Received message:', message); - if (message.error) { this.showError(message.error); return; } - if (message.type === 'initial' || message.type === 'update') { this.updateOverlay(message.data); } @@ -100,143 +117,116 @@ class RetroPulseOverlay { updateOverlay(data) { if (!data) return; - console.log('🔄 Updating overlay with data:', data); - // Update last update time this.lastUpdateTime = new Date(); - - // Hide error display this.errorDisplay.classList.add('hidden'); - // Update achievements - if (data.recentAchievements && data.recentAchievements.length > 0) { - this.updateAchievements(data.recentAchievements); - - // Update game info from the latest achievement - const latestAchievement = data.recentAchievements[0]; - if (latestAchievement.gameTitle) { - this.updateGameInfo({ - title: latestAchievement.gameTitle, - icon: latestAchievement.gameIcon - }); + const hasGameProgress = data.gameProgress !== null; + const hasAchievements = data.recentAchievements && data.recentAchievements.length > 0; + + if (hasGameProgress || hasAchievements) { + // We have data, so hide the waiting message + this.waitingForActivity.classList.add('hidden'); + + // Update Game Info and Progress Bar + if (hasGameProgress) { + this.currentGame.classList.remove('hidden'); + this.updateGameInfo(data.gameProgress); + } else { + this.currentGame.classList.add('hidden'); + } + + // Update achievements list + if (hasAchievements) { + this.achievementsContainer.classList.remove('hidden'); + this.updateAchievements(data.recentAchievements); + } else { + this.achievementsContainer.classList.add('hidden'); } } else { - // No achievements yet - this.achievementsList.innerHTML = '
No recent achievements
'; + // No data to display, show the waiting message + this.waitingForActivity.classList.remove('hidden'); + this.currentGame.classList.add('hidden'); + this.achievementsContainer.classList.add('hidden'); } } updateGameInfo(gameData) { - if (!gameData) return; + this.currentGame.classList.remove('hidden'); - this.gameTitle.textContent = gameData.title || 'Unknown Game'; + this.gameTitle.textContent = gameData.title; + this.consoleName.textContent = gameData.consoleName || ''; - if (gameData.icon) { + if(gameData.icon) { this.gameIcon.src = gameData.icon; - this.gameIcon.onerror = () => { - this.gameIcon.style.display = 'none'; - }; + this.gameIcon.style.display = 'block'; + } else { + this.gameIcon.style.display = 'none'; } - this.currentGame.classList.remove('hidden'); + // Update progress bar + if (gameData.hasOwnProperty('completion') && gameData.totalAchievements > 0) { + this.progressText.textContent = `Completion: ${gameData.numAwarded} / ${gameData.totalAchievements}`; + this.progressPercentage.textContent = gameData.completion; + this.progressBar.style.width = gameData.completion; + this.gameProgressSection.classList.remove('hidden'); + } else { + this.gameProgressSection.classList.add('hidden'); + } } updateAchievements(achievements) { - // Clear existing achievements - this.achievementsList.innerHTML = ''; + // To avoid flickering, only update if content has changed. + // A simple check on the first achievement ID can work. + const firstExisting = this.achievementsList.querySelector('.achievement-item'); + if (firstExisting && firstExisting.dataset.id == achievements[0].id) { + console.log('🧘 Achievements are already up-to-date.'); + return; + } + + console.log('✨ Updating achievements list.'); + this.achievementsList.innerHTML = ''; // Clear existing + achievements.forEach(ach => { + const item = document.createElement('li'); + item.className = 'achievement-item'; + item.dataset.id = ach.id; // For update checking - achievements.forEach((achievement, index) => { - const achievementElement = this.createAchievementElement(achievement); - - // Add a small delay for staggered animation - setTimeout(() => { - this.achievementsList.appendChild(achievementElement); - }, index * 100); + const earnedDate = new Date(ach.dateEarned).toLocaleString(); + + item.innerHTML = ` + Badge +
+
${ach.title}
+
${ach.description}
+
+ ${ach.points} Pts + ${earnedDate} +
+
+ `; + this.achievementsList.appendChild(item); }); } - createAchievementElement(achievement) { - const item = document.createElement('div'); - item.className = 'achievement-item'; - - // Format the date - const dateEarned = achievement.dateEarned ? - new Date(achievement.dateEarned).toLocaleDateString() : - 'Recently'; - - item.innerHTML = ` - Achievement Badge -
-
${this.escapeHtml(achievement.title || 'Unknown Achievement')}
-
${this.escapeHtml(achievement.description || '')}
-
- ${achievement.points || 0} pts - ${dateEarned} -
-
- `; - - return item; - } - updateConnectionStatus(status) { - this.connectionStatus.className = `connection-status ${status}`; - - const statusTexts = { - connecting: 'Connecting...', - connected: 'Live', - disconnected: 'Disconnected' - }; - - this.connectionStatus.querySelector('.status-text').textContent = statusTexts[status] || status; - } - - scheduleReconnect() { - if (this.reconnectInterval) return; - - console.log(`⏰ Scheduling reconnect in ${this.reconnectDelay}ms`); - - this.reconnectInterval = setTimeout(() => { - console.log('🔄 Attempting to reconnect...'); - this.connect(); - }, this.reconnectDelay); - - // Increase delay for next reconnect (max 30 seconds) - this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 30000); + this.connectionStatus.className = 'connection-status'; // Reset classes + this.connectionStatus.classList.add(status); + this.connectionStatus.textContent = status; } showError(message) { - console.error('❌ Overlay error:', message); + console.error('Overlay Error:', message); this.errorMessage.textContent = message; this.errorDisplay.classList.remove('hidden'); - this.updateConnectionStatus('disconnected'); - } - - // Utility function to escape HTML - escapeHtml(text) { - const map = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return text.replace(/[&<>"']/g, (m) => map[m]); + // Hide other elements + this.currentGame.classList.add('hidden'); + this.achievementsContainer.classList.add('hidden'); + this.waitingForActivity.classList.add('hidden'); } } -// Initialize overlay when page loads +// Initialize the overlay logic document.addEventListener('DOMContentLoaded', () => { new RetroPulseOverlay(); }); - -// Handle page visibility for OBS -document.addEventListener('visibilitychange', () => { - if (!document.hidden) { - console.log('🔄 Page became visible, overlay active'); - } -}); diff --git a/public/overlay.html b/public/overlay.html index 8ed01f6..ea8d4a5 100644 --- a/public/overlay.html +++ b/public/overlay.html @@ -3,41 +3,55 @@ - RetroPulse - Overlay + RetroPulse Overlay -
+
-
- Connecting... -
+
Connecting...
- - - - -
-

Recent Achievements

-
- + + -
+ + + +