Add Local leaderboard, CSS optimiztion
This commit is contained in:
227
data/leaderboard.html
Normal file
227
data/leaderboard.html
Normal file
@@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<!-- Meta Tags -->
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/x-icon" href="/pictures/favicon.ico" />
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="leaderboard.css" />
|
||||
<title>Ninjacross Timer - Leaderboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Modern Notification Toast -->
|
||||
<div
|
||||
id="notificationBubble"
|
||||
class="notification-toast"
|
||||
style="display: none"
|
||||
>
|
||||
<div class="notification-icon">
|
||||
<span id="notificationIcon">✓</span>
|
||||
</div>
|
||||
<div class="notification-body">
|
||||
<div class="notification-title" id="notificationTitle">Erfolg</div>
|
||||
<div class="notification-message" id="notificationText">Bereit</div>
|
||||
</div>
|
||||
<button class="notification-close" onclick="hideNotification()">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path
|
||||
d="M8 8.707l3.646 3.647.708-.707L8.707 8l3.647-3.646-.707-.708L8 7.293 4.354 3.646l-.707.708L7.293 8l-3.646 3.646.707.708L8 8.707z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Zurück Button -->
|
||||
<a href="/" class="back-btn">🏠</a>
|
||||
|
||||
<div class="container">
|
||||
<!-- Header Section -->
|
||||
<div class="header">
|
||||
<h1>🏆 Leaderboard</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- Leaderboard Section -->
|
||||
<div id="leaderboard-container" class="leaderboard-container">
|
||||
<div class="loading">Lade Leaderboard...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript Code -->
|
||||
<script>
|
||||
let leaderboardData = [];
|
||||
let lastUpdateTime = null;
|
||||
|
||||
// Seite laden
|
||||
window.onload = function () {
|
||||
loadLeaderboard();
|
||||
// Leaderboard alle 5 Sekunden aktualisieren
|
||||
setInterval(loadLeaderboard, 5000);
|
||||
};
|
||||
|
||||
// Leaderboard laden
|
||||
async function loadLeaderboard() {
|
||||
try {
|
||||
const response = await fetch("/api/leaderboard-full");
|
||||
const data = await response.json();
|
||||
leaderboardData = data.leaderboard || [];
|
||||
lastUpdateTime = new Date();
|
||||
updateLeaderboardDisplay();
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden des Leaderboards:", error);
|
||||
showMessage("Fehler beim Laden des Leaderboards", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// Leaderboard anzeigen
|
||||
function updateLeaderboardDisplay() {
|
||||
const container = document.getElementById("leaderboard-container");
|
||||
container.innerHTML = "";
|
||||
|
||||
if (leaderboardData.length === 0) {
|
||||
container.innerHTML =
|
||||
'<div class="no-entries">Noch keine Zeiten erfasst</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Alle Einträge anzeigen
|
||||
const displayData = leaderboardData;
|
||||
|
||||
// Erstelle zwei Reihen
|
||||
const row1 = document.createElement("div");
|
||||
row1.className = "leaderboard-row";
|
||||
const row2 = document.createElement("div");
|
||||
row2.className = "leaderboard-row";
|
||||
|
||||
displayData.forEach((entry, index) => {
|
||||
const entryDiv = document.createElement("div");
|
||||
entryDiv.className = "leaderboard-entry";
|
||||
|
||||
// Podium-Plätze hervorheben
|
||||
if (index === 0) {
|
||||
entryDiv.classList.add("gold");
|
||||
} else if (index === 1) {
|
||||
entryDiv.classList.add("silver");
|
||||
} else if (index === 2) {
|
||||
entryDiv.classList.add("bronze");
|
||||
}
|
||||
|
||||
const rankSpan = document.createElement("span");
|
||||
rankSpan.className = "rank";
|
||||
rankSpan.textContent = entry.rank + ".";
|
||||
|
||||
const nameSpan = document.createElement("span");
|
||||
nameSpan.className = "name";
|
||||
nameSpan.textContent = entry.name;
|
||||
|
||||
const timeSpan = document.createElement("span");
|
||||
timeSpan.className = "time";
|
||||
timeSpan.textContent = entry.timeFormatted;
|
||||
|
||||
entryDiv.appendChild(rankSpan);
|
||||
entryDiv.appendChild(nameSpan);
|
||||
entryDiv.appendChild(timeSpan);
|
||||
|
||||
// Erste 5 Einträge in die erste Reihe, nächste 5 in die zweite Reihe
|
||||
if (index < 5) {
|
||||
row1.appendChild(entryDiv);
|
||||
} else {
|
||||
row2.appendChild(entryDiv);
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(row1);
|
||||
if (displayData.length > 5) {
|
||||
container.appendChild(row2);
|
||||
}
|
||||
}
|
||||
|
||||
// Moderne Notification anzeigen
|
||||
function showMessage(message, type = "info") {
|
||||
console.log("showMessage called:", message, type);
|
||||
const toast = document.getElementById("notificationBubble");
|
||||
const icon = document.getElementById("notificationIcon");
|
||||
const title = document.getElementById("notificationTitle");
|
||||
const text = document.getElementById("notificationText");
|
||||
|
||||
if (!toast || !icon || !title || !text) {
|
||||
console.error("Notification elements not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any existing timeout
|
||||
if (window.notificationTimeout) {
|
||||
clearTimeout(window.notificationTimeout);
|
||||
}
|
||||
|
||||
// Set content
|
||||
text.textContent = message;
|
||||
|
||||
// Set type-specific styling and content
|
||||
toast.className = "notification-toast";
|
||||
switch (type) {
|
||||
case "success":
|
||||
toast.classList.add("success");
|
||||
icon.textContent = "✓";
|
||||
title.textContent = "Erfolg";
|
||||
break;
|
||||
case "error":
|
||||
toast.classList.add("error");
|
||||
icon.textContent = "✕";
|
||||
title.textContent = "Fehler";
|
||||
break;
|
||||
case "info":
|
||||
toast.classList.add("info");
|
||||
icon.textContent = "ℹ";
|
||||
title.textContent = "Information";
|
||||
break;
|
||||
case "warning":
|
||||
toast.classList.add("warning");
|
||||
icon.textContent = "⚠";
|
||||
title.textContent = "Warnung";
|
||||
break;
|
||||
default:
|
||||
toast.classList.add("info");
|
||||
icon.textContent = "ℹ";
|
||||
title.textContent = "Information";
|
||||
}
|
||||
|
||||
// Show toast with animation
|
||||
toast.style.display = "flex";
|
||||
// Force reflow
|
||||
toast.offsetHeight;
|
||||
// Add show class after a small delay to ensure display is set
|
||||
setTimeout(() => {
|
||||
toast.classList.add("show");
|
||||
}, 10);
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
window.notificationTimeout = setTimeout(() => {
|
||||
hideNotification();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Notification verstecken mit Animation
|
||||
function hideNotification() {
|
||||
const toast = document.getElementById("notificationBubble");
|
||||
if (!toast) return;
|
||||
|
||||
// Clear timeout if exists
|
||||
if (window.notificationTimeout) {
|
||||
clearTimeout(window.notificationTimeout);
|
||||
}
|
||||
|
||||
// Remove show class for animation
|
||||
toast.classList.remove("show");
|
||||
|
||||
// Hide after animation completes
|
||||
setTimeout(() => {
|
||||
toast.style.display = "none";
|
||||
}, 400); // Match CSS transition duration
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user