From 8f79205dfb33ad041a46659277e9cbc880db2e69 Mon Sep 17 00:00:00 2001 From: Evelyn Sucitro Date: Mon, 1 Dec 2025 13:03:50 +0700 Subject: [PATCH] Leaderboard --- Leaderboard.css | 352 ++++++++++++++++------------------------------- Leaderboard.html | 63 +++++---- Leaderboard.js | 190 +------------------------ 3 files changed, 153 insertions(+), 452 deletions(-) diff --git a/Leaderboard.css b/Leaderboard.css index 3598ad2..0b14c99 100644 --- a/Leaderboard.css +++ b/Leaderboard.css @@ -35,7 +35,40 @@ body { 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 { position: relative; z-index: 2; @@ -44,7 +77,6 @@ body { padding: 40px 100px; width: 98%; max-width: 1600px; - border: 2px solid rgba(0, 255, 255, 0.4); box-shadow: 0 0 25px rgba(0, 255, 255, 0.4), @@ -70,7 +102,7 @@ body { } } -/* ========== TITLE ========== */ +/* Title */ h1 { text-align: center; font-size: 2.2rem; @@ -95,20 +127,56 @@ h1::before { 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 { list-style: none; display: flex; flex-direction: column; gap: 14px; - - /* Scroll settings */ max-height: 500px; overflow-y: auto; padding-right: 10px; + padding-bottom: 5px; + margin-bottom: 20px; } -/* Custom Scrollbar Styling */ .leaderboard-list::-webkit-scrollbar { width: 10px; } @@ -130,7 +198,7 @@ h1::before { box-shadow: 0 0 15px rgba(0, 234, 255, 0.8); } -/* ========== LEADERBOARD ITEM ========== */ +/* Leaderboard Item */ .leaderboard-item { display: flex; align-items: center; @@ -149,7 +217,7 @@ h1::before { box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); } -/* ========== RANK 1 - CHAMPION ========== */ +/* Rank Styles */ .leaderboard-item.rank-1 { 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); @@ -158,35 +226,19 @@ h1::before { 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 { 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); 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 { 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); box-shadow: 0 0 15px rgba(138, 43, 226, 0.3); } -.leaderboard-item.rank-3:hover { - box-shadow: 0 0 25px rgba(138, 43, 226, 0.5); -} - -/* ========== RANK BADGE ========== */ +/* Rank Badge */ .rank-badge { width: 45px; height: 45px; @@ -197,7 +249,6 @@ h1::before { font-size: 17px; font-weight: bold; flex-shrink: 0; - transition: all 0.3s ease; } .rank-1 .rank-badge { @@ -236,18 +287,17 @@ h1::before { } } -/* ========== PLAYER INFO ========== */ +/* Player Info */ .player-info { flex: 1; display: flex; - flex-direction: column; - gap: 4px; + align-items: center; min-width: 0; } .player-name { font-weight: 700; - font-size: 18px; + font-size: 20px; color: #e1e8ff; text-shadow: 0 0 8px rgba(0, 234, 255, 0.3); white-space: nowrap; @@ -262,21 +312,10 @@ h1::before { background-clip: text; text-shadow: none; filter: drop-shadow(0 0 6px rgba(0, 234, 255, 0.5)); - font-size: 19px; + font-size: 22px; } -.player-level { - 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 ========== */ +/* Score */ .player-score { font-weight: 700; font-size: 18px; @@ -319,236 +358,81 @@ h1::before { color: rgba(200, 200, 255, 0.5); } -/* ========== YOUR RANK HIGHLIGHT ========== */ -.leaderboard-item.your-rank { - border-color: #00ff88 !important; - 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); +/* Your Rank Fixed Section */ +.your-rank-container { + background: rgba(20, 0, 40, 0.9); 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 { - content: '🎮'; - display: block; - font-size: 50px; - margin-bottom: 12px; - opacity: 0.6; - filter: drop-shadow(0 0 10px rgba(0, 234, 255, 0.4)); +.your-rank-item { + display: flex; + align-items: center; + gap: 20px; + padding: 18px 25px; + border-radius: 14px; + 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) ========== */ -.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 ========== */ +/* Responsive */ @media (max-width: 768px) { .container { padding: 35px 30px; - max-width: 90%; } h1 { - font-size: 2rem; - gap: 12px; - } - - h1::before { 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 { - gap: 12px; + grid-template-columns: repeat(3, 1fr); + gap: 10px; } - .stat-box { - padding: 14px 10px; + .btn-back { + width: 45px; + height: 45px; + top: 20px; + left: 20px; } - .stat-value { - font-size: 20px; + .btn-back svg { + width: 20px; + height: 20px; } } @media (max-width: 480px) { .container { padding: 25px 20px; - max-width: 95%; } h1 { - font-size: 1.6rem; - gap: 8px; - margin-bottom: 25px; - } - - h1::before { - font-size: 1.4rem; + font-size: 1.5rem; } .leaderboard-item { - padding: 12px 14px; - gap: 10px; + padding: 14px 18px; + gap: 15px; } .rank-badge { - width: 32px; - height: 32px; - font-size: 13px; + width: 38px; + height: 38px; + font-size: 15px; } .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; } - .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 { - font-size: 14px; + font-size: 18px; } } \ No newline at end of file diff --git a/Leaderboard.html b/Leaderboard.html index 5418e1c..18feccb 100644 --- a/Leaderboard.html +++ b/Leaderboard.html @@ -4,36 +4,41 @@ Leaderboard - 2048 - +
+ +

Leaderboard

Players
-
142
+
10
Your Rank
-
--
+
5
High Score
-
0
+
9,850
- +
+
+
5
+
+
You
+
+
+
4,890
+
Points
+
+
+
- + \ No newline at end of file diff --git a/Leaderboard.js b/Leaderboard.js index 67aa8ec..437af15 100644 --- a/Leaderboard.js +++ b/Leaderboard.js @@ -1,15 +1,13 @@ -// ========== PARTICLES ANIMATION ========== +// 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'; @@ -56,188 +54,4 @@ 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 ` -
  • -
    ${rank}
    -
    -
    ${escapeHtml(player.name)}
    -
    Level ${player.level || Math.floor(player.score / 100)}
    -
    -
    -
    ${player.score.toLocaleString()}
    -
    Points
    -
    -
  • - `; - }).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 -}; \ No newline at end of file +} \ No newline at end of file