Leaderboard

This commit is contained in:
Evelyn Sucitro 2025-12-01 13:03:50 +07:00
parent 8d4895bdab
commit 8f79205dfb
3 changed files with 153 additions and 452 deletions

View File

@ -35,7 +35,40 @@ body {
pointer-events: none; pointer-events: none;
} }
/* ========== LEADERBOARD CONTAINER ========== */ /* Back Button */
.btn-back {
position: fixed;
top: 30px;
left: 30px;
z-index: 100;
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(30, 0, 50, 0.8);
border: 2px solid rgba(0, 255, 255, 0.4);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
}
.btn-back:hover {
background: rgba(0, 234, 255, 0.2);
border-color: rgba(0, 255, 255, 0.8);
box-shadow: 0 0 30px rgba(0, 255, 255, 0.6);
transform: translateX(-5px);
}
.btn-back svg {
width: 24px;
height: 24px;
fill: #00eaff;
filter: drop-shadow(0 0 5px rgba(0, 234, 255, 0.8));
}
/* Container */
.container { .container {
position: relative; position: relative;
z-index: 2; z-index: 2;
@ -44,7 +77,6 @@ body {
padding: 40px 100px; padding: 40px 100px;
width: 98%; width: 98%;
max-width: 1600px; max-width: 1600px;
border: 2px solid rgba(0, 255, 255, 0.4); border: 2px solid rgba(0, 255, 255, 0.4);
box-shadow: box-shadow:
0 0 25px rgba(0, 255, 255, 0.4), 0 0 25px rgba(0, 255, 255, 0.4),
@ -70,7 +102,7 @@ body {
} }
} }
/* ========== TITLE ========== */ /* Title */
h1 { h1 {
text-align: center; text-align: center;
font-size: 2.2rem; font-size: 2.2rem;
@ -95,20 +127,56 @@ h1::before {
filter: drop-shadow(0 0 10px #ffd700); filter: drop-shadow(0 0 10px #ffd700);
} }
/* ========== LEADERBOARD LIST ========== */ /* Stats Container */
.stats-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-bottom: 30px;
}
.stat-box {
background: rgba(30, 0, 50, 0.5);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 12px;
padding: 16px 12px;
text-align: center;
transition: all 0.3s ease;
}
.stat-box:hover {
border-color: rgba(0, 255, 255, 0.5);
box-shadow: 0 0 15px rgba(0, 255, 255, 0.3);
}
.stat-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: rgba(200, 200, 255, 0.6);
margin-bottom: 6px;
}
.stat-value {
font-size: 22px;
font-weight: bold;
color: #00eaff;
text-shadow: 0 0 10px rgba(0, 234, 255, 0.6);
}
/* Leaderboard List */
.leaderboard-list { .leaderboard-list {
list-style: none; list-style: none;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 14px;
/* Scroll settings */
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
padding-right: 10px; padding-right: 10px;
padding-bottom: 5px;
margin-bottom: 20px;
} }
/* Custom Scrollbar Styling */
.leaderboard-list::-webkit-scrollbar { .leaderboard-list::-webkit-scrollbar {
width: 10px; width: 10px;
} }
@ -130,7 +198,7 @@ h1::before {
box-shadow: 0 0 15px rgba(0, 234, 255, 0.8); box-shadow: 0 0 15px rgba(0, 234, 255, 0.8);
} }
/* ========== LEADERBOARD ITEM ========== */ /* Leaderboard Item */
.leaderboard-item { .leaderboard-item {
display: flex; display: flex;
align-items: center; align-items: center;
@ -149,7 +217,7 @@ h1::before {
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
} }
/* ========== RANK 1 - CHAMPION ========== */ /* Rank Styles */
.leaderboard-item.rank-1 { .leaderboard-item.rank-1 {
background: linear-gradient(135deg, rgba(0, 234, 255, 0.25), rgba(0, 255, 136, 0.2)); background: linear-gradient(135deg, rgba(0, 234, 255, 0.25), rgba(0, 255, 136, 0.2));
border: 2px solid rgba(0, 234, 255, 0.6); border: 2px solid rgba(0, 234, 255, 0.6);
@ -158,35 +226,19 @@ h1::before {
inset 0 0 25px rgba(0, 234, 255, 0.15); inset 0 0 25px rgba(0, 234, 255, 0.15);
} }
.leaderboard-item.rank-1:hover {
box-shadow:
0 0 35px rgba(0, 234, 255, 0.6),
0 0 60px rgba(0, 255, 136, 0.4);
}
/* ========== RANK 2 - RUNNER UP ========== */
.leaderboard-item.rank-2 { .leaderboard-item.rank-2 {
background: linear-gradient(135deg, rgba(255, 0, 255, 0.2), rgba(204, 0, 255, 0.15)); background: linear-gradient(135deg, rgba(255, 0, 255, 0.2), rgba(204, 0, 255, 0.15));
border: 2px solid rgba(255, 0, 255, 0.5); border: 2px solid rgba(255, 0, 255, 0.5);
box-shadow: 0 0 18px rgba(255, 0, 255, 0.3); box-shadow: 0 0 18px rgba(255, 0, 255, 0.3);
} }
.leaderboard-item.rank-2:hover {
box-shadow: 0 0 30px rgba(255, 0, 255, 0.5);
}
/* ========== RANK 3 - THIRD PLACE ========== */
.leaderboard-item.rank-3 { .leaderboard-item.rank-3 {
background: linear-gradient(135deg, rgba(138, 43, 226, 0.25), rgba(75, 0, 130, 0.2)); background: linear-gradient(135deg, rgba(138, 43, 226, 0.25), rgba(75, 0, 130, 0.2));
border: 2px solid rgba(138, 43, 226, 0.5); border: 2px solid rgba(138, 43, 226, 0.5);
box-shadow: 0 0 15px rgba(138, 43, 226, 0.3); box-shadow: 0 0 15px rgba(138, 43, 226, 0.3);
} }
.leaderboard-item.rank-3:hover { /* Rank Badge */
box-shadow: 0 0 25px rgba(138, 43, 226, 0.5);
}
/* ========== RANK BADGE ========== */
.rank-badge { .rank-badge {
width: 45px; width: 45px;
height: 45px; height: 45px;
@ -197,7 +249,6 @@ h1::before {
font-size: 17px; font-size: 17px;
font-weight: bold; font-weight: bold;
flex-shrink: 0; flex-shrink: 0;
transition: all 0.3s ease;
} }
.rank-1 .rank-badge { .rank-1 .rank-badge {
@ -236,18 +287,17 @@ h1::before {
} }
} }
/* ========== PLAYER INFO ========== */ /* Player Info */
.player-info { .player-info {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 4px;
min-width: 0; min-width: 0;
} }
.player-name { .player-name {
font-weight: 700; font-weight: 700;
font-size: 18px; font-size: 20px;
color: #e1e8ff; color: #e1e8ff;
text-shadow: 0 0 8px rgba(0, 234, 255, 0.3); text-shadow: 0 0 8px rgba(0, 234, 255, 0.3);
white-space: nowrap; white-space: nowrap;
@ -262,21 +312,10 @@ h1::before {
background-clip: text; background-clip: text;
text-shadow: none; text-shadow: none;
filter: drop-shadow(0 0 6px rgba(0, 234, 255, 0.5)); filter: drop-shadow(0 0 6px rgba(0, 234, 255, 0.5));
font-size: 19px; font-size: 22px;
} }
.player-level { /* Score */
font-size: 12px;
color: rgba(200, 200, 255, 0.6);
text-transform: uppercase;
letter-spacing: 1px;
}
.rank-1 .player-level {
color: rgba(0, 255, 136, 0.8);
}
/* ========== SCORE ========== */
.player-score { .player-score {
font-weight: 700; font-weight: 700;
font-size: 18px; font-size: 18px;
@ -319,236 +358,81 @@ h1::before {
color: rgba(200, 200, 255, 0.5); color: rgba(200, 200, 255, 0.5);
} }
/* ========== YOUR RANK HIGHLIGHT ========== */ /* Your Rank Fixed Section */
.leaderboard-item.your-rank { .your-rank-container {
border-color: #00ff88 !important; background: rgba(20, 0, 40, 0.9);
box-shadow:
0 0 25px rgba(0, 255, 136, 0.6),
inset 0 0 20px rgba(0, 255, 136, 0.15) !important;
}
.leaderboard-item.your-rank::after {
content: 'YOU';
position: absolute;
right: -8px;
top: -10px;
background: linear-gradient(135deg, #00ff88, #00eaff);
color: #0c001a;
font-size: 11px;
font-weight: bold;
padding: 5px 10px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 255, 136, 0.8);
letter-spacing: 1px;
}
/* ========== EMPTY STATE ========== */
.leaderboard-empty {
text-align: center;
padding: 40px 20px;
color: rgba(200, 200, 255, 0.5);
font-size: 16px;
border: 2px dashed rgba(0, 255, 255, 0.2);
border-radius: 14px; border-radius: 14px;
background: rgba(30, 0, 50, 0.3); padding: 15px 25px;
border: 2px solid rgba(0, 255, 136, 0.6);
box-shadow:
0 0 25px rgba(0, 255, 136, 0.4),
inset 0 0 20px rgba(0, 255, 136, 0.15);
} }
.leaderboard-empty::before { .your-rank-item {
content: '🎮'; display: flex;
display: block; align-items: center;
font-size: 50px; gap: 20px;
margin-bottom: 12px; padding: 18px 25px;
opacity: 0.6; border-radius: 14px;
filter: drop-shadow(0 0 10px rgba(0, 234, 255, 0.4)); background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(0, 234, 255, 0.1));
border: 2px solid rgba(0, 255, 136, 0.5);
box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
} }
/* ========== STATS SECTION (OPTIONAL) ========== */ /* Responsive */
.stats-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-bottom: 30px;
}
.stat-box {
background: rgba(30, 0, 50, 0.5);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 12px;
padding: 16px 12px;
text-align: center;
transition: all 0.3s ease;
}
.stat-box:hover {
border-color: rgba(0, 255, 255, 0.5);
box-shadow: 0 0 15px rgba(0, 255, 255, 0.3);
}
.stat-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: rgba(200, 200, 255, 0.6);
margin-bottom: 6px;
}
.stat-value {
font-size: 22px;
font-weight: bold;
color: #00eaff;
text-shadow: 0 0 10px rgba(0, 234, 255, 0.6);
}
/* ========== RESPONSIVE ========== */
@media (max-width: 768px) { @media (max-width: 768px) {
.container { .container {
padding: 35px 30px; padding: 35px 30px;
max-width: 90%;
} }
h1 { h1 {
font-size: 2rem;
gap: 12px;
}
h1::before {
font-size: 1.8rem; font-size: 1.8rem;
} }
.leaderboard-item {
padding: 16px 20px;
gap: 16px;
}
.rank-badge {
width: 40px;
height: 40px;
font-size: 16px;
}
.player-name {
font-size: 17px;
}
.rank-1 .player-name {
font-size: 18px;
}
.score-value {
font-size: 20px;
}
.rank-1 .score-value {
font-size: 23px;
}
.stats-container { .stats-container {
gap: 12px; grid-template-columns: repeat(3, 1fr);
gap: 10px;
} }
.stat-box { .btn-back {
padding: 14px 10px; width: 45px;
height: 45px;
top: 20px;
left: 20px;
} }
.stat-value { .btn-back svg {
font-size: 20px; width: 20px;
height: 20px;
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.container { .container {
padding: 25px 20px; padding: 25px 20px;
max-width: 95%;
} }
h1 { h1 {
font-size: 1.6rem; font-size: 1.5rem;
gap: 8px;
margin-bottom: 25px;
}
h1::before {
font-size: 1.4rem;
} }
.leaderboard-item { .leaderboard-item {
padding: 12px 14px; padding: 14px 18px;
gap: 10px; gap: 15px;
} }
.rank-badge { .rank-badge {
width: 32px; width: 38px;
height: 32px; height: 38px;
font-size: 13px; font-size: 15px;
} }
.player-name { .player-name {
font-size: 14px;
}
.rank-1 .player-name {
font-size: 15px;
}
.player-level {
font-size: 10px;
}
.score-value {
font-size: 15px;
}
.rank-1 .score-value {
font-size: 17px;
}
.score-label {
font-size: 9px;
}
.stats-container {
gap: 8px;
margin-bottom: 20px;
}
.stat-box {
padding: 10px 6px;
}
.stat-value {
font-size: 16px; font-size: 16px;
} }
.stat-label {
font-size: 9px;
}
}
@media (max-width: 360px) {
.container {
padding: 20px 15px;
}
h1 {
font-size: 1.4rem;
}
.leaderboard-item {
padding: 10px 12px;
gap: 8px;
}
.rank-badge {
width: 28px;
height: 28px;
font-size: 12px;
}
.player-name {
font-size: 13px;
}
.score-value { .score-value {
font-size: 14px; font-size: 18px;
} }
} }

View File

@ -4,36 +4,41 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leaderboard - 2048</title> <title>Leaderboard - 2048</title>
<link rel="stylesheet" href="leaderboard.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div id="particles"></div> <div id="particles"></div>
<button class="btn-back" onclick="location.href='2048.html'">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
</svg>
</button>
<div class="container"> <div class="container">
<h1>Leaderboard</h1> <h1>Leaderboard</h1>
<div class="stats-container"> <div class="stats-container">
<div class="stat-box"> <div class="stat-box">
<div class="stat-label">Players</div> <div class="stat-label">Players</div>
<div class="stat-value" id="totalPlayers">142</div> <div class="stat-value" id="totalPlayers">10</div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<div class="stat-label">Your Rank</div> <div class="stat-label">Your Rank</div>
<div class="stat-value" id="yourRank">--</div> <div class="stat-value" id="yourRank">5</div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<div class="stat-label">High Score</div> <div class="stat-label">High Score</div>
<div class="stat-value" id="highScore">0</div> <div class="stat-value" id="highScore">9,850</div>
</div> </div>
</div> </div>
<ul class="leaderboard-list" id="leaderboardList"> <ul class="leaderboard-list" id="leaderboardList">
<li class="leaderboard-item rank-1"> <li class="leaderboard-item rank-1">
<div class="rank-badge">1</div> <div class="rank-badge">1</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 1</div> <div class="player-name">CyberKing</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">9,850</div> <div class="score-value">9,850</div>
@ -44,7 +49,7 @@
<li class="leaderboard-item rank-2"> <li class="leaderboard-item rank-2">
<div class="rank-badge">2</div> <div class="rank-badge">2</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 2</div> <div class="player-name">NeonMaster</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">8,200</div> <div class="score-value">8,200</div>
@ -55,7 +60,7 @@
<li class="leaderboard-item rank-3"> <li class="leaderboard-item rank-3">
<div class="rank-badge">3</div> <div class="rank-badge">3</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 3</div> <div class="player-name">PixelHunter</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">6,950</div> <div class="score-value">6,950</div>
@ -66,7 +71,7 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">4</div> <div class="rank-badge">4</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 4</div> <div class="player-name">StarGazer</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">5,420</div> <div class="score-value">5,420</div>
@ -77,7 +82,7 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">5</div> <div class="rank-badge">5</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 5</div> <div class="player-name">You</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">4,890</div> <div class="score-value">4,890</div>
@ -88,7 +93,7 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">6</div> <div class="rank-badge">6</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 6</div> <div class="player-name">ByteWarrior</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">4,320</div> <div class="score-value">4,320</div>
@ -99,7 +104,7 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">7</div> <div class="rank-badge">7</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 7</div> <div class="player-name">DataDragon</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">3,850</div> <div class="score-value">3,850</div>
@ -110,7 +115,7 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">8</div> <div class="rank-badge">8</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 8</div> <div class="player-name">SyntaxSage</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">3,120</div> <div class="score-value">3,120</div>
@ -121,7 +126,7 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">9</div> <div class="rank-badge">9</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 9</div> <div class="player-name">LogicLord</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">2,780</div> <div class="score-value">2,780</div>
@ -132,31 +137,29 @@
<li class="leaderboard-item rank-other"> <li class="leaderboard-item rank-other">
<div class="rank-badge">10</div> <div class="rank-badge">10</div>
<div class="player-info"> <div class="player-info">
<div class="player-name">Player 10</div> <div class="player-name">BugSlayer</div>
</div> </div>
<div class="player-score"> <div class="player-score">
<div class="score-value">2,340</div> <div class="score-value">2,340</div>
<div class="score-label">Points</div> <div class="score-label">Points</div>
</div> </div>
</li> </li>
<li class="leaderboard-item rank-other your-rank">
<div class="rank-badge">12</div>
<div class="player-info">
<div class="player-name">You</div>
</div>
<div class="player-score">
<div class="score-value">1,840</div>
<div class="score-label">Points</div>
</div>
</li>
</ul> </ul>
<button class="btn-back" onclick="location.href='2048.html'"> <div class="your-rank-container">
Back to Game <div class="your-rank-item">
</button> <div class="rank-badge" style="background: linear-gradient(135deg, #00ff88, #00eaff); color: #0c001a; box-shadow: 0 0 20px rgba(0, 255, 136, 0.8);">5</div>
<div class="player-info">
<div class="player-name" style="color: #00ff88; filter: drop-shadow(0 0 8px rgba(0, 255, 136, 0.6));">You</div>
</div>
<div class="player-score" style="color: #00ff88; text-shadow: 0 0 15px rgba(0, 255, 136, 0.8);">
<div class="score-value" style="font-size: 24px;">4,890</div>
<div class="score-label">Points</div>
</div>
</div>
</div>
</div> </div>
<script src="leaderboard.js"></script> <script src="script.js"></script>
</body> </body>
</html> </html>

View File

@ -1,15 +1,13 @@
// ========== PARTICLES ANIMATION ========== // Particles Animation
const particlesContainer = document.getElementById('particles'); const particlesContainer = document.getElementById('particles');
function createParticle() { function createParticle() {
const particle = document.createElement('div'); const particle = document.createElement('div');
particle.className = 'particle'; particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%'; particle.style.left = Math.random() * 100 + '%';
particle.style.top = Math.random() * 100 + '%'; particle.style.top = Math.random() * 100 + '%';
const size = 3 + Math.random() * 4; const size = 3 + Math.random() * 4;
particle.style.width = size + 'px'; particle.style.width = size + 'px';
particle.style.height = size + 'px'; particle.style.height = size + 'px';
@ -56,188 +54,4 @@ setInterval(createParticle, 300);
for (let i = 0; i < 25; i++) { for (let i = 0; i < 25; i++) {
setTimeout(createParticle, i * 100); 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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
};