PageSourceSearch

https://guess-the-jumpshot.com/magic-game.js

js guess-the-jumpshot.com collected 2026-10-04 00:21:13 UTC 21,861 bytes, 505 lines download raw bytes

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.