243 lines
7.9 KiB
JavaScript
243 lines
7.9 KiB
JavaScript
// ========== PARTICLES ANIMATION ==========
|
|
const particlesContainer = document.getElementById('particles');
|
|
|
|
function createParticle() {
|
|
const particle = document.createElement('div');
|
|
particle.className = 'particle';
|
|
|
|
|
|
particle.style.left = Math.random() * 100 + '%';
|
|
particle.style.top = Math.random() * 100 + '%';
|
|
|
|
|
|
const size = 3 + Math.random() * 4;
|
|
particle.style.width = size + 'px';
|
|
particle.style.height = size + 'px';
|
|
|
|
const duration = 3 + Math.random() * 5;
|
|
const delay = Math.random() * 2;
|
|
const moveX = (Math.random() - 0.5) * 200;
|
|
|
|
const animationName = `float-${Date.now()}-${Math.random()}`;
|
|
const keyframes = `
|
|
@keyframes ${animationName} {
|
|
0% {
|
|
transform: translateY(0) translateX(0);
|
|
opacity: 0;
|
|
}
|
|
10% {
|
|
opacity: 1;
|
|
}
|
|
90% {
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
transform: translateY(-100vh) translateX(${moveX}px);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
`;
|
|
|
|
const style = document.createElement('style');
|
|
style.innerHTML = keyframes;
|
|
document.head.appendChild(style);
|
|
|
|
particle.style.animation = `${animationName} ${duration}s ${delay}s ease-in-out forwards`;
|
|
|
|
particlesContainer.appendChild(particle);
|
|
|
|
setTimeout(() => {
|
|
particle.remove();
|
|
style.remove();
|
|
}, (duration + delay) * 1000);
|
|
}
|
|
|
|
setInterval(createParticle, 300);
|
|
|
|
for (let i = 0; i < 25; i++) {
|
|
setTimeout(createParticle, i * 100);
|
|
}
|
|
|
|
// ========== LEADERBOARD DATA MANAGEMENT ==========
|
|
|
|
// Get leaderboard data from localStorage
|
|
function getLeaderboardData() {
|
|
const data = localStorage.getItem('leaderboard2048');
|
|
return data ? JSON.parse(data) : [];
|
|
}
|
|
|
|
// Save leaderboard data to localStorage
|
|
function saveLeaderboardData(data) {
|
|
localStorage.setItem('leaderboard2048', JSON.stringify(data));
|
|
}
|
|
|
|
// Get current logged-in user
|
|
function getCurrentUser() {
|
|
return localStorage.getItem('currentUser') || null;
|
|
}
|
|
|
|
// Add or update score for a player
|
|
function addScore(playerName, score) {
|
|
let leaderboard = getLeaderboardData();
|
|
|
|
// Find if player already exists
|
|
const existingIndex = leaderboard.findIndex(p => p.name === playerName);
|
|
|
|
if (existingIndex >= 0) {
|
|
// Update only if new score is higher
|
|
if (score > leaderboard[existingIndex].score) {
|
|
leaderboard[existingIndex].score = score;
|
|
leaderboard[existingIndex].level = Math.floor(score / 100);
|
|
leaderboard[existingIndex].date = new Date().toISOString();
|
|
}
|
|
} else {
|
|
// Add new player
|
|
leaderboard.push({
|
|
name: playerName,
|
|
score: score,
|
|
level: Math.floor(score / 100),
|
|
date: new Date().toISOString()
|
|
});
|
|
}
|
|
|
|
saveLeaderboardData(leaderboard);
|
|
}
|
|
|
|
// ========== RENDER LEADERBOARD ==========
|
|
function renderLeaderboard() {
|
|
let leaderboardData = getLeaderboardData();
|
|
const currentUser = getCurrentUser();
|
|
const list = document.getElementById('leaderboardList');
|
|
const emptyState = document.getElementById('emptyState');
|
|
|
|
// Sort by score (highest first)
|
|
leaderboardData.sort((a, b) => b.score - a.score);
|
|
|
|
// Update stats
|
|
const totalPlayers = leaderboardData.length;
|
|
const topScore = leaderboardData.length > 0 ? leaderboardData[0].score : 0;
|
|
|
|
document.getElementById('totalPlayers').textContent = totalPlayers;
|
|
document.getElementById('topScore').textContent = topScore.toLocaleString();
|
|
|
|
// Find current user's rank
|
|
let userRank = '--';
|
|
if (currentUser) {
|
|
const userIndex = leaderboardData.findIndex(p => p.name === currentUser);
|
|
if (userIndex >= 0) {
|
|
userRank = userIndex + 1;
|
|
}
|
|
}
|
|
document.getElementById('yourRank').textContent = userRank;
|
|
|
|
// Check if there's data
|
|
if (leaderboardData.length === 0) {
|
|
list.style.display = 'none';
|
|
if (emptyState) emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
list.style.display = 'flex';
|
|
if (emptyState) emptyState.style.display = 'none';
|
|
|
|
// Render top 10 players
|
|
const top10 = leaderboardData.slice(0, 10);
|
|
|
|
list.innerHTML = top10.map((player, index) => {
|
|
const rank = index + 1;
|
|
let rankClass = 'rank-other';
|
|
|
|
if (rank === 1) rankClass = 'rank-1';
|
|
else if (rank === 2) rankClass = 'rank-2';
|
|
else if (rank === 3) rankClass = 'rank-3';
|
|
|
|
const isCurrentUser = currentUser && player.name === currentUser;
|
|
const yourRankClass = isCurrentUser ? 'your-rank' : '';
|
|
|
|
return `
|
|
<li class="leaderboard-item ${rankClass} ${yourRankClass}">
|
|
<div class="rank-badge">${rank}</div>
|
|
<div class="player-info">
|
|
<div class="player-name">${escapeHtml(player.name)}</div>
|
|
<div class="player-level">Level ${player.level || Math.floor(player.score / 100)}</div>
|
|
</div>
|
|
<div class="player-score">
|
|
<div class="score-value">${player.score.toLocaleString()}</div>
|
|
<div class="score-label">Points</div>
|
|
</div>
|
|
</li>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// Escape HTML to prevent XSS
|
|
function escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text.replace(/[&<>"']/g, m => map[m]);
|
|
}
|
|
|
|
// ========== SAMPLE DATA (FOR TESTING) ==========
|
|
function initSampleData() {
|
|
const existingData = getLeaderboardData();
|
|
|
|
// Only add sample data if leaderboard is empty
|
|
if (existingData.length === 0) {
|
|
const sampleData = [
|
|
{ name: "CyberKing", score: 9850, level: 98, date: new Date().toISOString() },
|
|
{ name: "NeonMaster", score: 8200, level: 82, date: new Date().toISOString() },
|
|
{ name: "PixelHunter", score: 6950, level: 69, date: new Date().toISOString() },
|
|
{ name: "StarGazer", score: 5420, level: 54, date: new Date().toISOString() },
|
|
{ name: "CodeNinja", score: 4890, level: 48, date: new Date().toISOString() },
|
|
{ name: "ByteWarrior", score: 4320, level: 43, date: new Date().toISOString() },
|
|
{ name: "DataDragon", score: 3850, level: 38, date: new Date().toISOString() },
|
|
{ name: "SyntaxSage", score: 3120, level: 31, date: new Date().toISOString() },
|
|
{ name: "LogicLord", score: 2780, level: 27, date: new Date().toISOString() },
|
|
{ name: "BugSlayer", score: 2340, level: 23, date: new Date().toISOString() }
|
|
];
|
|
|
|
saveLeaderboardData(sampleData);
|
|
console.log('Sample data initialized');
|
|
}
|
|
}
|
|
|
|
// ========== CLEAR LEADERBOARD (FOR TESTING) ==========
|
|
function clearLeaderboard() {
|
|
if (confirm('Are you sure you want to clear all leaderboard data?')) {
|
|
localStorage.removeItem('leaderboard2048');
|
|
renderLeaderboard();
|
|
console.log('Leaderboard cleared');
|
|
}
|
|
}
|
|
|
|
// ========== REFRESH LEADERBOARD ==========
|
|
function refreshLeaderboard() {
|
|
renderLeaderboard();
|
|
console.log('Leaderboard refreshed');
|
|
}
|
|
|
|
// ========== INITIALIZE ON PAGE LOAD ==========
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
|
|
renderLeaderboard();
|
|
|
|
console.log('Leaderboard initialized');
|
|
console.log('Available functions:');
|
|
console.log('- addScore(name, score) - Add/update a score');
|
|
console.log('- clearLeaderboard() - Clear all data');
|
|
console.log('- refreshLeaderboard() - Refresh display');
|
|
});
|
|
|
|
// ========== EXPOSE FUNCTIONS TO WINDOW (FOR EXTERNAL USE) ==========
|
|
window.leaderboard = {
|
|
addScore: addScore,
|
|
getLeaderboardData: getLeaderboardData,
|
|
clearLeaderboard: clearLeaderboard,
|
|
refreshLeaderboard: refreshLeaderboard,
|
|
initSampleData: initSampleData
|
|
}; |