1(() => { 2// Simplified Supabase Client - No Authentication Required 3const SUPABASE_URL = 'https://nlxfcofiakviwoulbumt.supabase.co'; 4const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5seGZjb2ZpYWt2aXdvdWxidW10Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDM0MjM2NjAsImV4cCI6MjA1ODk5OTY2MH0.Is-JSFMT2Ce_ua8f9uoqAvKW_q5OUyhnoSoN-yiJA1E'; 5 6// Initialize Supabase client 7let supabase = null; 8 9// Load Supabase from CDN 10function loadSupabase() { 11 return new Promise((resolve, reject) => { 12 if (window.supabase) { 13 // Supabaseåºå·²ç»å è½½ï¼åå§å客æ·ç«¯ 14 supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); 15 resolve(); 16 return; 17 } 18 19 const script = document.createElement('script'); 20 script.src = 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2'; 21 script.onload = () => { 22 supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY); 23 resolve(); 24 }; 25 script.onerror = reject; 26 document.head.appendChild(script); 27 }); 28} 29 30// Game Statistics Manager - No Auth Required 31class GameStats {
32 static async saveGameResult(playerName, gameData) { 33 try { 34 const result = { 35 player_name: playerName || 'Anonymous', 36 score: gameData.score, 37 correct_answers: gameData.correctAnswers, 38 total_questions: gameData.totalQuestions, 39 accuracy: (gameData.correctAnswers / gameData.totalQuestions * 100).toFixed(2), 40 game_mode: gameData.mode || 'normal', 41 time_played: gameData.timePlayed || 0, 42 streak: gameData.streak || 0, 43 created_at: new Date().toISOString() 44 }; 45 46 const { data, error } = await supabase 47 .from('game_scores') 48 .insert(result) 49 .select(); 50 51 if (error) throw error; 52 53 return { success: true, data }; 54 } catch (error) { 55 console.error('Save game result error:', error); 56 return { success: false, error: error.message }; 57 } 58 } 59 60 static async getRecentGames(limit = 10) { 61 try { 62 const { data, error } = await supabase 63 .from('game_scores') 64 .select('*') 65 .order('created_at', { ascending: false }) 66 .limit(limit); 67 68 if (error) throw error; 69 return data; 70 } catch (error) { 71 console.error('Get recent games error:', error); 72 return []; 73 } 74 } 75} 76 77// Leaderboard Manager - No Auth Required 78class Leaderboard { 79 static async getTopScores(limit = 100) { 80 try { 81 // Ensure supabase is initialized 82 if (!supabase) { 83 console.error('Supabase not initialized. Call GameDB.loadSupabase() first.'); 84 return []; 85 } 86 87 const { data, error } = await supabase 88 .from('game_scores') 89 .select('*') 90 .order('score', { ascending: false }) 91 .limit(limit); 92 93 if (error) throw error; 94 return data; 95 } catch (error) { 96 console.error('Get leaderboard error:', error); 97 return []; 98 } 99 } 100 101 static async getDailyTopScores() { 102 try { 103 // Ensure supabase is initialized 104 if (!supabase) { 105 console.error('Supabase not initialized. Call GameDB.loadSupabase() first.'); 106 return []; 107 } 108 109 const today = new Date(); 110 today.setHours(0, 0, 0, 0); 111 112 const { data, error } = await supabase 113 .from('game_scores') 114 .select('*') 115 .gte('created_at', today.toISOString()) 116 .order('score', { ascending: false }) 117 .limit(50); 118 119 if (error) throw error; 120 return data; 121 } catch (error) { 122 console.error('Get daily leaderboard error:', error); 123 return []; 124 } 125 } 126 127 static async getWeeklyTopScores() { 128 try { 129 // Ensure supabase is initialized 130 if (!supabase) { 131 console.error('Supabase not initialized. Call GameDB.loadSupabase() first.'); 132 return []; 133 } 134 135 const weekAgo = new Date(); 136 weekAgo.setDate(weekAgo.getDate() - 7); 137 138 const { data, error } = await supabase 139 .from('game_scores') 140 .select('*') 141 .gte('created_at', weekAgo.toISOString()) 142 .order('score', { ascending: false }) 143 .limit(50); 144 145 if (error) throw error; 146 return data; 147 } catch (error) { 148 console.error('Get weekly leaderboard error:', error); 149 return []; 150 } 151 } 152} 153 154// Comments Manager - No Auth Required 155class Comments { 156 static async postComment(playerName, content) { 157 try { 158 const comment = { 159 player_name: playerName || 'Anonymous', 160 content,
161 likes: 0, 162 created_at: new Date().toISOString() 163 }; 164 165 const { data, error } = await supabase 166 .from('comments_public') 167 .insert(comment) 168 .select(); 169 170 if (error) throw error; 171 return { success: true, data }; 172 } catch (error) { 173 console.error('Post comment error:', error); 174 return { success: false, error: error.message }; 175 } 176 } 177 178 static async getComments(limit = 50, offset = 0) { 179 try { 180 const { data, error } = await supabase 181 .from('comments_public') 182 .select('*') 183 .order('created_at', { ascending: false }) 184 .range(offset, offset + limit - 1); 185 186 if (error) throw error; 187 return data || []; 188 } catch (error) { 189 console.error('Get comments error:', error); 190 return []; 191 } 192 } 193 194 static async getCommentsCount() { 195 try { 196 const { count, error } = await supabase 197 .from('comments_public') 198 .select('*', { count: 'exact', head: true }); 199 200 if (error) throw error; 201 return count || 0; 202 } catch (error) { 203 console.error('Get comments count error:', error); 204 return 0; 205 } 206 } 207 208 static async likeComment(commentId) { 209 try { 210 // Get current likes 211 const { data: comment } = await supabase 212 .from('comments_public') 213 .select('likes') 214 .eq('id', commentId) 215 .single(); 216 217 if (comment) { 218 // Increment likes 219 const { error } = await supabase 220 .from('comments_public') 221 .update({ likes: (comment.likes || 0) + 1 }) 222 .eq('id', commentId); 223 224 if (error) throw error; 225 return { success: true }; 226 } 227 228 return { success: false, error: 'Comment not found' }; 229 } catch (error) { 230 console.error('Like comment error:', error); 231 return { success: false, error: error.message }; 232 } 233 } 234} 235 236// Player Stats 237class PlayerStats { 238 static getStoredPlayerName() { 239 return localStorage.getItem('playerName') || ''; 240 } 241 242 static setPlayerName(name) { 243 localStorage.setItem('playerName', name); 244 } 245 246 static async getPlayerStats(playerName) { 247 if (!playerName) return null; 248 249 try { 250 const { data, error } = await supabase 251 .from('game_scores') 252 .select('*') 253 .eq('player_name', playerName) 254 .order('score', { ascending: false }); 255 256 if (error) throw error; 257 258 if (data && data.length > 0) { 259 const totalGames = data.length; 260 const totalScore = data.reduce((sum, game) => sum + game.score, 0); 261 const bestScore = data[0].score; 262 const avgAccuracy = data.reduce((sum, game) => sum + parseFloat(game.accuracy), 0) / totalGames; 263 const bestStreak = Math.max(...data.map(game => game.streak || 0)); 264 265 return { 266 totalGames, 267 totalScore, 268 bestScore, 269 avgAccuracy: avgAccuracy.toFixed(1), 270 bestStreak, 271 recentGames: data.slice(0, 5) 272 }; 273 } 274 275 return null; 276 } catch (error) { 277 console.error('Get player stats error:', error); 278 return null; 279 } 280 } 281} 282 283// Player Feedback Manager - No Auth Required 284class PlayerFeedback { 285 static async submitFeedback(feedbackData) { 286 try { 287 const feedback = { 288 type: feedbackData.type, 289 rating: feedbackData.rating, 290 email: feedbackData.email || null, 291 message: feedbackData.message, 292 user_agent: feedbackData.user_agent || navigator.userAgent, 293 screen_resolution: feedbackData.screen_resolution || `${window.screen.width}x${window.screen.height}`, 294 created_at: new Date().toISOString() 295 }; 296 297 const { data, error } = await supabase 298 .from('player_feedback') 299 .insert(feedback) 300 .select(); 301 302 if (error) throw error; 303 return { success: true, data }; 304 } catch (error) { 305 console.error('Submit feedback error:', error); 306 return { success: false, error: error.message }; 307 } 308 } 309 310 static async getFeedbackStats() { 311 try { 312 const { data, error } = await supabase 313 .from('player_feedback') 314 .select('type, rating') 315 .order('created_at', { ascending: false }) 316 .limit(100); 317 318 if (error) throw error; 319 320 // Calculate average rating and type distribution 321 const stats = { 322 totalFeedback: data.length, 323 averageRating: 0, 324 typeDistribution: {} 325 }; 326 327 if (data.length > 0) { 328 const totalRating = data.reduce((sum, item) =>
328 sum + (item.rating || 0), 0); 329 stats.averageRating = (totalRating / data.length).toFixed(1); 330 331 data.forEach(item => { 332 stats.typeDistribution[item.type] = (stats.typeDistribution[item.type] || 0) + 1; 333 }); 334 } 335 336 return stats; 337 } catch (error) { 338 console.error('Get feedback stats error:', error); 339 return null; 340 } 341 } 342} 343 344// Export for use in other files 345window.GameDB = { 346 loadSupabase, 347 GameStats, 348 Leaderboard, 349 Comments, 350 PlayerStats, 351 PlayerFeedback 352}; 353})();
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.