1// Magic UI Game Implementation 2(function() { 3 'use strict'; 4 5 class MagicGame { 6 constructor() { 7 this.currentMode = 'easy'; 8 this.currentVideoIndex = 0; 9 this.currentScore = 0; 10 this.currentStreak = 0; 11 this.correctAnswersCount = 0; 12 this.attempts = 5; 13 this.maxAttempts = 5; 14 this.totalQuestions = 25; 15 this.correctAnswer = ''; 16 this.players = []; 17 this.gameActive = false; 18 19 this.init(); 20 } 21 22 async init() { 23 // Load players database 24 await this.loadPlayers(); 25 26 // Set up event listeners 27 this.setupEventListeners(); 28 29 // Set Easy Mode as default 30 this.selectMode('easy'); 31 32 // Initialize first video 33 this.loadVideo(); 34 } 35 36 async loadPlayers() { 37 try { 38 const response = await fetch('players-database.json'); 39 const data = await response.json(); 40 this.players = data.players || []; 41 console.log('Loaded', this.players.length, 'players'); 42 } catch (error) { 43 console.error('Error loading players:', error); 44 // Fallback players if database fails 45 this.players = [ 46 "LeBron James", "Stephen Curry", "Kevin Durant", "Giannis Antetokounmpo", 47 "Kawhi Leonard", "James Harden", "Anthony Davis", "Damian Lillard", 48 "Joel Embiid", "Luka Doncic", "Jayson Tatum", "Jimmy Butler", 49 "Paul George", "Bradley Beal", "Kyrie Irving", "Trae Young", 50 "Zion Williamson", "Donovan Mitchell", "Devin Booker", "Karl-Anthony Towns" 51 ]; 52 } 53 } 54 55 setupEventListeners() { 56 // Difficulty selector buttons 57 document.querySelectorAll('.magic-diff-btn').forEach(btn => { 58 btn.addEventListener('click', () => { 59 this.selectMode(btn.dataset.mode); 60 }); 61 }); 62 63 // Submit guess button 64 const submitBtn = document.getElementById('submit-guess'); 65 if (submitBtn) { 66 submitBtn.addEventListener('click', () => this.submitGuess()); 67 } 68 69 // Skip button 70 const skipBtn = document.getElementById('skip-question'); 71 if (skipBtn) { 72 skipBtn.addEventListener('click', () => this.skipQuestion()); 73 } 74 75 // Next button 76 const nextBtn = document.getElementById('next-question'); 77 if (nextBtn) { 78 nextBtn.addEventListener('click', () => this.nextQuestion()); 79 } 80 81 // Input field 82 const input = document.getElementById('player-guess'); 83 if (input) { 84 input.addEventListener('keypress', (e) => { 85 if (e.key === 'Enter') { 86 this.submitGuess(); 87 } 88 }); 89 90 // Autocomplete 91 input.addEventListener('input', (e) => { 92 this.showSuggestions(e.target.value); 93 }); 94 } 95 } 96 97 selectMode(mode) { 98 this.currentMode = mode; 99 100 // Update UI 101 document.querySelectorAll('.magic-diff-btn').forEach(btn => { 102 btn.classList.toggle('active', btn.dataset.mode === mode); 103 }); 104 105 // Reset game state 106 this.resetGame(); 107 108 // Load new video 109 this.loadVideo(); 110 } 111 112 resetGame() { 113 this.currentVideoIndex = 0; 114 this.currentScore = 0; 115 this.currentStreak = 0; 116 this.maxStreak = 0; 117 this.correctAnswersCount = 0; 118 this.attempts = this.maxAttempts; 119 this.gameStartTime = Date.now(); 120 this.updateStats(); 121 } 122 123 loadVideo() { 124 const video = document.getElementById('game-video'); 125 const loadingIndicator = document.getElementById('loading-indicator'); 126 127 if (!video) return; 128 129 // Show loading 130 if (loadingIndicator) { 131 loadingIndicator.style.display = 'flex'; 132 } 133 134 // Generate random player for this question 135 this.correctAnswer = this.players[Math.floor(Math.random() * this.players.length)]; 136 137 // Construct video URL using R2 CDN 138 const videoPath = `videos/${this.currentMode}/video_${this.currentVideoIndex + 1}.mp4`; 139 const videoUrl = `https://images.guess-the-jumpshot.com/${videoPath}`; 140 141 video.src = videoUrl; 142 143 // Handle video load 144 video.onloadeddata = () => { 145 if (loadingIndicator) { 146 loadingIndicator.style.display = 'none'; 147 } 148 video.play().catch(e => {
149 console.log('Video autoplay failed:', e); 150 // Show placeholder or fallback 151 this.showVideoPlaceholder(); 152 }); 153 }; 154 155 // Handle video error 156 video.onerror = () => { 157 console.error('Video failed to load:', videoUrl); 158 if (loadingIndicator) { 159 loadingIndicator.style.display = 'none'; 160 } 161 this.showVideoPlaceholder(); 162 }; 163 164 // Reset attempts for new question 165 this.attempts = this.maxAttempts; 166 this.updateStats(); 167 168 // Clear feedback 169 const feedback = document.getElementById('game-feedback'); 170 if (feedback) { 171 feedback.textContent = ''; 172 feedback.className = 'magic-feedback'; 173 } 174 175 // Reset input 176 const input = document.getElementById('player-guess'); 177 if (input) { 178 input.value = ''; 179 input.disabled = false; 180 } 181 182 // Show/hide buttons 183 document.getElementById('submit-guess').style.display = 'inline-flex'; 184 document.getElementById('skip-question').style.display = 'inline-flex'; 185 document.getElementById('next-question').style.display = 'none'; 186 } 187 188 showVideoPlaceholder() { 189 const video = document.getElementById('game-video'); 190 if (video) { 191 video.style.display = 'none'; 192 } 193 194 // Create or show placeholder 195 let placeholder = document.querySelector('.video-placeholder'); 196 if (!placeholder) { 197 const container = document.querySelector('.magic-video-container'); 198 if (container) { 199 placeholder = document.createElement('div'); 200 placeholder.className = 'video-placeholder'; 201 placeholder.innerHTML = ` 202 <div style="text-align: center; padding: 40px;"> 203 <div style="font-size: 64px; margin-bottom: 20px;">ð</div> 204 <h3 style="color: white; margin-bottom: 10px;">Video Loading...</h3> 205 <p style="color: rgba(255,255,255,0.7);">Imagine ${this.correctAnswer} shooting!</p> 206 </div> 207 `; 208 container.appendChild(placeholder); 209 } 210 } else { 211 placeholder.style.display = 'block'; 212 } 213 } 214 215 submitGuess() { 216 const input = document.getElementById('player-guess'); 217 const feedback = document.getElementById('game-feedback'); 218 219 if (!input || !feedback) return; 220 221 const guess = input.value.trim(); 222 if (!guess) { 223 feedback.textContent = 'Please enter a player name!'; 224 feedback.className = 'magic-feedback error'; 225 return; 226 } 227 228 // Check if correct (case insensitive) 229 const isCorrect = guess.toLowerCase() === this.correctAnswer.toLowerCase(); 230 231 if (isCorrect) { 232 // Correct answer 233 this.currentScore += this.attempts * 100; 234 this.currentStreak++; 235 this.correctAnswersCount++; 236 237 // Track max streak 238 if (this.currentStreak > (this.maxStreak || 0)) { 239 this.maxStreak = this.currentStreak; 240 } 241 242 feedback.textContent = `â Correct! It's ${this.correctAnswer}! (+${this.attempts * 100} points)`; 243 feedback.className = 'magic-feedback success'; 244 245 input.disabled = true; 246 247 // Show next button 248 document.getElementById('submit-guess').style.display = 'none'; 249 document.getElementById('skip-question').style.display = 'none'; 250 document.getElementById('next-question').style.display = 'inline-flex'; 251 } else { 252 // Wrong answer 253 this.attempts--; 254 255 if (this.attempts > 0) {
256 feedback.textContent = `â Wrong! ${this.attempts} attempts remaining`; 257 feedback.className = 'magic-feedback error'; 258 input.value = ''; 259 } else { 260 // No attempts left 261 feedback.textContent = `â Game Over! The correct answer was ${this.correctAnswer}`; 262 feedback.className = 'magic-feedback error'; 263 264 input.disabled = true; 265 this.currentStreak = 0; 266 267 // Show next button 268 document.getElementById('submit-guess').style.display = 'none'; 269 document.getElementById('skip-question').style.display = 'none'; 270 document.getElementById('next-question').style.display = 'inline-flex'; 271 } 272 } 273 274 this.updateStats(); 275 } 276 277 skipQuestion() { 278 const feedback = document.getElementById('game-feedback'); 279 if (feedback) { 280 feedback.textContent = `âï¸ Skipped! The answer was ${this.correctAnswer}`; 281 feedback.className = 'magic-feedback warning'; 282 } 283 284 this.currentStreak = 0; 285 286 // Disable input 287 const input = document.getElementById('player-guess'); 288 if (input) { 289 input.disabled = true; 290 } 291 292 // Show next button 293 document.getElementById('submit-guess').style.display = 'none'; 294 document.getElementById('skip-question').style.display = 'none'; 295 document.getElementById('next-question').style.display = 'inline-flex'; 296 297 this.updateStats(); 298 } 299 300 nextQuestion() { 301 this.currentVideoIndex++; 302 303 if (this.currentVideoIndex >= this.totalQuestions) { 304 // Game complete 305 this.showResults(); 306 } else { 307 // Load next video 308 this.loadVideo(); 309 } 310 } 311 312 showSuggestions(query) { 313 const suggestionsDiv = document.getElementById('suggestions'); 314 if (!suggestionsDiv) return; 315 316 if (!query) { 317 suggestionsDiv.innerHTML = ''; 318 suggestionsDiv.style.display = 'none'; 319 return; 320 } 321 322 const filtered = this.players.filter(player => 323 player.toLowerCase().includes(query.toLowerCase()) 324 ).slice(0, 5); 325 326 if (filtered.length > 0) { 327 suggestionsDiv.innerHTML = filtered.map(player => 328 `<div class="suggestion-item" onclick="magicGame.selectSuggestion('${player}')">${player}</div>` 329 ).join(''); 330 suggestionsDiv.style.display = 'block'; 331 } else { 332 suggestionsDiv.style.display = 'none'; 333 } 334 } 335 336 selectSuggestion(player) { 337 const input = document.getElementById('player-guess'); 338 const suggestionsDiv = document.getElementById('suggestions'); 339 340 if (input) { 341 input.value = player; 342 } 343 if (suggestionsDiv) { 344 suggestionsDiv.style.display = 'none'; 345 } 346 } 347 348 updateStats() { 349 // Update current question number 350 const current = document.getElementById('current'); 351 if (current) current.textContent = this.currentVideoIndex + 1; 352 353 // Update total questions 354 const total = document.getElementById('total'); 355 if (total) total.textContent = this.totalQuestions; 356 357 // Update tries 358 const tries = document.getElementById('tries'); 359 if (tries) tries.textContent = this.attempts; 360 361 // Update score 362 const score = document.getElementById('current-score'); 363 if (score) score.textContent = this.currentScore; 364 } 365 366 async showResults() { 367 // Calculate game statistics 368 // Note: Score can vary from 100-500 per correct answer, so we need to track correct answers differently 369 const totalCorrect = this.correctAnswersCount || 0; 370 const accuracy = ((totalCorrect / this.totalQuestions) * 100).toFixed(1); 371 const timePlayed = Math.floor((Date.now() - this.gameStartTime) / 1000); 372 373 // Store game data for later use 374 this.lastGameData = { 375 score: this.currentScore, 376 correctAnswers: totalCorrect, 377 totalQuestions: this.totalQuestions, 378 mode: this.currentMode, 379 timePlayed: timePlayed, 380 streak: this.maxStreak || this.currentStreak, 381 accuracy: accuracy 382 }; 383 384 // Get stored player name 385 let playerName = ''; 386 if (window.GameDB && window.GameDB.PlayerStats) { 387 playerName = window.GameDB.PlayerStats.getStoredPlayerName(); 388 } else { 389 playerName = localStorage.getItem('playerName') || ''; 390 } 391 392 // Save game result to database immediately if player has name 393 if (playerName && window.GameDB && window.GameDB.GameStats) { 394 const result = await window.GameDB.GameStats.saveGameResult(player
394Name, this.lastGameData); 395 if (result.success) { 396 console.log('Game result saved successfully'); 397 } 398 } 399 400 // Create results display 401 const nameDisplay = playerName ? `<p>Player: <strong>${playerName}</strong></p>` : ''; 402 403 const resultsHTML = ` 404 <div class="magic-results" style="padding: 2rem; text-align: center;"> 405 <h2 style="font-size: 2.5rem; margin-bottom: 1rem; background: linear-gradient(90deg, #8B5CF6, #F97316); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;"> 406 ð Game Complete! 407 </h2> 408 ${nameDisplay} 409 <div class="magic-results-stats" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1.5rem; margin: 2rem 0;"> 410 <div class="stat-item" style="background: var(--glass-white); padding: 1.5rem; border-radius: 12px; border: 1px solid var(--glass-white-border);"> 411 <h3 style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Final Score</h3> 412 <p style="font-size: 2rem; font-weight: bold; color: var(--text-primary);">${this.currentScore}</p> 413 </div> 414 <div class="stat-item" style="background: var(--glass-white); padding: 1.5rem; border-radius: 12px; border: 1px solid var(--glass-white-border);"> 415 <h3 style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Accuracy</h3> 416 <p style="font-size: 2rem; font-weight: bold; color: var(--text-primary);">${accuracy}%</p> 417 </div> 418 <div class="stat-item" style="background: var(--glass-white); padding: 1.5rem; border-radius: 12px; border: 1px solid var(--glass-white-border);"> 419 <h3 style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Best Streak</h3> 420 <p style="font-size: 2rem; font-weight: bold; color: var(--text-primary);">${this.maxStreak || this.currentStreak} ð¥</p> 421 </div> 422 <div class="stat-item" style="background: var(--glass-white); padding: 1.5rem; border-radius: 12px; border: 1px solid var(--glass-white-border);"> 423 <h3 style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 0.5rem;">Time</h3> 424 <p style="font-size: 2rem; font-weight: bold; color: var(--text-primary);">${Math.floor(timePlayed / 60)}:${(timePlayed % 60).toString().padStart(2, '0')}</p> 425 </div> 426 </div> 427 <div class="magic-results-actions" style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;"> 428 <button onclick="magicGame.resetGame(); location.reload();" class="magic-btn magic-btn-primary"> 429 <i data-lucide="refresh-cw" size="18"></i> 430 Play Again 431 </button> 432 <a href="/leaderboard" class="magic-btn magic-btn-secondary"> 433 <i data-lucide="trophy" size="18"></i> 434 View Leaderboard 435 </a> 436 ${!playerName ? '<button onclick="magicGame.promptForNameWithModal()" class="magic-btn magic-btn-outline"><i data-lucide="user" size="18"></i> Save Your Score</button>' : ''} 437 </div> 438 </div> 439 `; 440 441 // Replace game content with results 442 const gameContent = document.querySelector('.magic-game-content'); 443 if (gameContent) { 444 gameContent.innerHTML = resultsHTML; 445 // Re-initialize Lucide icons for the new buttons 446 if (window.lucide) { 447 window.lucide.createIcons(); 448 } 449 } 450 } 451 452 async promptForNameWithModal() { 453 // Use the new Magic Modal to get player name 454 if (window.magicModal && this.lastGameData) { 455 window.magicModal.setCallback(async (name) => { 456 if (name && window.GameDB && window.GameDB.GameStats) { 457 // Save the game result with the new name 458 const result = await window.GameDB.GameStats.saveGameResult(name, this.lastGameData); 459 if (result.success) { 460 console.log('Game result saved successfully with name:', name); 461 } 462 } 463 }); 464 465 window.magicModal.showNamePrompt( 466 this.currentScore, 467 this.lastGameData.correctAnswers, 468 this.lastGameData.totalQuestions, 469 this.lastGameData.accuracy 470 ); 471 } else { 472 // Fallback to prompt if modal not available 473 const playerName = prompt('Enter your name to save it for future games:'); 474 if (playerName && playerName.trim()) { 475 const name = playerName.trim(); 476 if (window.GameDB) { 477 window.GameDB.PlayerStats.setPlayerName(name); 478 // Save the game result 479 if (window.GameDB.GameStats && this.lastGameData) { 480 const result = await window.GameDB.GameStats.saveGameResult(name, this.lastGameData); 481 if (result.success) { 482 console.log('Game result saved successfully'); 483 // Redirect to leaderboard with new score indicator 484 setTimeout(() => { 485 window.location.href = '/leaderboard?new=true';
486 }, 500); 487 } 488 } 489 } else { 490 localStorage.setItem('playerName', name); 491 } 492 } 493 } 494 } 495 } 496 497 // Initialize game when DOM is ready 498 if (document.readyState === 'loading') { 499 document.addEventListener('DOMContentLoaded', () => { 500 window.magicGame = new MagicGame(); 501 }); 502 } else { 503 window.magicGame = new MagicGame(); 504 } 505})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.