v1 #1
169
data/index.css
169
data/index.css
@@ -53,6 +53,32 @@ body {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaderboard-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 90px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
.settings-btn {
|
.settings-btn {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
@@ -82,7 +108,7 @@ body {
|
|||||||
.heartbeat-indicators {
|
.heartbeat-indicators {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 90px;
|
right: 160px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@@ -93,6 +119,56 @@ body {
|
|||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.logo {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-btn {
|
||||||
|
top: 15px;
|
||||||
|
right: 60px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-btn {
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heartbeat-indicators {
|
||||||
|
top: 15px;
|
||||||
|
right: 90px;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heartbeat-indicator {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heartbeat-indicator::before {
|
||||||
|
font-size: 8px;
|
||||||
|
top: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: clamp(1.2rem, 3vw, 1.8rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: clamp(0.7rem, 1.5vw, 0.9rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.heartbeat-indicator {
|
.heartbeat-indicator {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -300,7 +376,7 @@ body {
|
|||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -344,7 +420,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time-display {
|
.time-display {
|
||||||
font-size: clamp(3rem, 9vw, 10rem);
|
font-size: clamp(3rem, 13vw, 13rem);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: clamp(10px, 1vh, 15px) 0;
|
margin: clamp(10px, 1vh, 15px) 0;
|
||||||
font-family: "Courier New", monospace;
|
font-family: "Courier New", monospace;
|
||||||
@@ -353,7 +429,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
font-size: clamp(1.5rem, 3vw, 3rem);
|
font-size: clamp(1.5rem, 4vw, 5rem);
|
||||||
margin: clamp(8px, 1vh, 12px) 0;
|
margin: clamp(8px, 1vh, 12px) 0;
|
||||||
padding: clamp(6px, 1vh, 10px) clamp(12px, 2vw, 18px);
|
padding: clamp(6px, 1vh, 10px) clamp(12px, 2vw, 18px);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -428,20 +504,40 @@ body {
|
|||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
padding: clamp(10px, 1.5vh, 15px);
|
padding: clamp(10px, 1.5vh, 15px);
|
||||||
margin: 1vh 0 0 0;
|
margin: 1vh 0 0 0;
|
||||||
width: 50%;
|
width: clamp(320px, 80vw, 960px);
|
||||||
max-width: 50%;
|
max-width: 960px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: clamp(12px, 2vh, 20px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leaderboard-container {
|
||||||
|
text-align: left;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: clamp(12px, 2vh, 20px);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#leaderboard-container {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.best-times h3 {
|
.best-times h3 {
|
||||||
font-size: clamp(0.9rem, 1.8vw, 1.1rem);
|
font-size: clamp(0.9rem, 1.8vw, 1.1rem);
|
||||||
margin-bottom: clamp(5px, 0.5vh, 8px);
|
margin: 0 auto;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-family: "Segoe UI", Arial, sans-serif;
|
font-family: "Segoe UI", Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.best-time-row {
|
.best-time-row {
|
||||||
@@ -468,10 +564,13 @@ body {
|
|||||||
font-size: clamp(1.1rem, 2.2vw, 1.4rem);
|
font-size: clamp(1.1rem, 2.2vw, 1.4rem);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
padding: clamp(8px, 1.5vh, 12px) clamp(12px, 2vw, 16px);
|
padding: clamp(12px, 2vh, 16px) clamp(16px, 3vw, 24px);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
min-height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaderboard-entry:hover {
|
.leaderboard-entry:hover {
|
||||||
@@ -502,6 +601,60 @@ body {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.gold {
|
||||||
|
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
||||||
|
border-color: #ffd700;
|
||||||
|
color: #b8860b;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.gold .rank {
|
||||||
|
color: #7a4d00;
|
||||||
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.gold .time {
|
||||||
|
color: #0f5132;
|
||||||
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.silver {
|
||||||
|
background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);
|
||||||
|
border-color: #c0c0c0;
|
||||||
|
color: #696969;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(192, 192, 192, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.silver .rank {
|
||||||
|
color: #4b5563;
|
||||||
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.silver .time {
|
||||||
|
color: #0f5132;
|
||||||
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.bronze {
|
||||||
|
background: linear-gradient(135deg, #cd7f32 0%, #e6a85c 100%);
|
||||||
|
border-color: #cd7f32;
|
||||||
|
color: #8b4513;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(205, 127, 50, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.bronze .rank {
|
||||||
|
color: #7a3410;
|
||||||
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.bronze .time {
|
||||||
|
color: #0f5132;
|
||||||
|
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.no-times {
|
.no-times {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img src="/pictures/erlebniss.png" class="logo" alt="NinjaCross Logo" />
|
<img src="/pictures/erlebniss.png" class="logo" alt="NinjaCross Logo" />
|
||||||
|
<a href="/leaderboard.html" class="leaderboard-btn">🏆</a>
|
||||||
<a href="/settings" class="settings-btn">⚙️</a>
|
<a href="/settings" class="settings-btn">⚙️</a>
|
||||||
|
|
||||||
<div class="heartbeat-indicators">
|
<div class="heartbeat-indicators">
|
||||||
@@ -73,33 +74,7 @@
|
|||||||
|
|
||||||
<div class="best-times">
|
<div class="best-times">
|
||||||
<h3>🏆 Lokales Leaderboard</h3>
|
<h3>🏆 Lokales Leaderboard</h3>
|
||||||
<div id="leaderboard-container">
|
<div id="leaderboard-container"></div>
|
||||||
<div class="leaderboard-entry">
|
|
||||||
<span class="rank">1.</span>
|
|
||||||
<span class="name">Max Mustermann</span>
|
|
||||||
<span class="time">23.45</span>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-entry">
|
|
||||||
<span class="rank">2.</span>
|
|
||||||
<span class="name">Anna Schmidt</span>
|
|
||||||
<span class="time">24.67</span>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-entry">
|
|
||||||
<span class="rank">3.</span>
|
|
||||||
<span class="name">Tom Weber</span>
|
|
||||||
<span class="time">25.89</span>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-entry">
|
|
||||||
<span class="rank">4.</span>
|
|
||||||
<span class="name">Lisa Müller</span>
|
|
||||||
<span class="time">26.12</span>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-entry">
|
|
||||||
<span class="rank">5.</span>
|
|
||||||
<span class="name">Paul Fischer</span>
|
|
||||||
<span class="time">27.34</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -396,10 +371,25 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Erstelle zwei Reihen für 2x3 Layout
|
||||||
|
const row1 = document.createElement("div");
|
||||||
|
row1.className = "leaderboard-row";
|
||||||
|
const row2 = document.createElement("div");
|
||||||
|
row2.className = "leaderboard-row";
|
||||||
|
|
||||||
leaderboardData.forEach((entry, index) => {
|
leaderboardData.forEach((entry, index) => {
|
||||||
const entryDiv = document.createElement("div");
|
const entryDiv = document.createElement("div");
|
||||||
entryDiv.className = "leaderboard-entry";
|
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");
|
const rankSpan = document.createElement("span");
|
||||||
rankSpan.className = "rank";
|
rankSpan.className = "rank";
|
||||||
rankSpan.textContent = entry.rank + ".";
|
rankSpan.textContent = entry.rank + ".";
|
||||||
@@ -415,8 +405,19 @@
|
|||||||
entryDiv.appendChild(rankSpan);
|
entryDiv.appendChild(rankSpan);
|
||||||
entryDiv.appendChild(nameSpan);
|
entryDiv.appendChild(nameSpan);
|
||||||
entryDiv.appendChild(timeSpan);
|
entryDiv.appendChild(timeSpan);
|
||||||
container.appendChild(entryDiv);
|
|
||||||
|
// Erste 3 Einträge in die erste Reihe, nächste 3 in die zweite Reihe
|
||||||
|
if (index < 3) {
|
||||||
|
row1.appendChild(entryDiv);
|
||||||
|
} else if (index < 6) {
|
||||||
|
row2.appendChild(entryDiv);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
container.appendChild(row1);
|
||||||
|
if (leaderboardData.length > 3) {
|
||||||
|
container.appendChild(row2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDisplay() {
|
function updateDisplay() {
|
||||||
@@ -452,7 +453,7 @@
|
|||||||
s1.textContent = "Bereit für den Start!";
|
s1.textContent = "Bereit für den Start!";
|
||||||
break;
|
break;
|
||||||
case "running":
|
case "running":
|
||||||
s1.textContent = "Läuft - Du schaffst das!";
|
s1.textContent = "Läuft - Gib alles!";
|
||||||
break;
|
break;
|
||||||
case "finished":
|
case "finished":
|
||||||
s1.textContent = "Geschafft!";
|
s1.textContent = "Geschafft!";
|
||||||
@@ -478,7 +479,7 @@
|
|||||||
s2.textContent = "Bereit für den Start!";
|
s2.textContent = "Bereit für den Start!";
|
||||||
break;
|
break;
|
||||||
case "running":
|
case "running":
|
||||||
s2.textContent = "Läuft - Du schaffst das!";
|
s2.textContent = "Läuft - Gib alles!";
|
||||||
break;
|
break;
|
||||||
case "finished":
|
case "finished":
|
||||||
s2.textContent = "Geschafft!";
|
s2.textContent = "Geschafft!";
|
||||||
|
|||||||
367
data/leaderboard.css
Normal file
367
data/leaderboard.css
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", Arial, sans-serif;
|
||||||
|
background: linear-gradient(0deg, #0d1733 0%, #223c83 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: visible;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #49bae4 0%, #223c83 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: "Segoe UI", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
min-height: 150px;
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.leaderboard-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: start;
|
||||||
|
grid-auto-rows: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-row {
|
||||||
|
margin-bottom: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 15px 0;
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.gold {
|
||||||
|
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
||||||
|
border-color: #ffd700;
|
||||||
|
color: #b8860b;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.silver {
|
||||||
|
background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);
|
||||||
|
border-color: #c0c0c0;
|
||||||
|
color: #696969;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(192, 192, 192, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry.bronze {
|
||||||
|
background: linear-gradient(135deg, #cd7f32 0%, #e6a85c 100%);
|
||||||
|
border-color: #cd7f32;
|
||||||
|
color: #8b4513;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 15px rgba(205, 127, 50, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .rank {
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 40px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .name {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .time {
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
min-width: 100px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-entries {
|
||||||
|
text-align: center;
|
||||||
|
color: #6c757d;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
color: #49bae4;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding: 40px;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Notification Toast */
|
||||||
|
.notification-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 400px;
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||||
|
0 10px 10px -5px rgba(0, 0, 0, 0.04),
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
z-index: 99999;
|
||||||
|
display: none;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
pointer-events: auto;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-toast.show {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #10b981, #059669);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
margin-top: -4px;
|
||||||
|
margin-right: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-close:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast Types */
|
||||||
|
.notification-toast.success .notification-icon {
|
||||||
|
background: linear-gradient(135deg, #10b981, #059669);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-toast.error .notification-icon {
|
||||||
|
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-toast.info .notification-icon {
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-toast.warning .notification-icon {
|
||||||
|
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsiveness */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .name {
|
||||||
|
margin: 0;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .rank {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .time {
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile notification adjustments */
|
||||||
|
.notification-toast {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
left: 10px;
|
||||||
|
max-width: none;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.header h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry {
|
||||||
|
padding: 12px 15px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .rank {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-entry .time {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
||||||
@@ -353,7 +353,7 @@ void setupBackendRoutes(AsyncWebServer &server) {
|
|||||||
// Andere Logik wie in getBestLocs
|
// Andere Logik wie in getBestLocs
|
||||||
});
|
});
|
||||||
|
|
||||||
// Lokales Leaderboard API
|
// Lokales Leaderboard API (für Hauptseite - 6 Einträge)
|
||||||
server.on("/api/leaderboard", HTTP_GET, [](AsyncWebServerRequest *request) {
|
server.on("/api/leaderboard", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
// Sortiere nach Zeit (beste zuerst)
|
// Sortiere nach Zeit (beste zuerst)
|
||||||
std::sort(localTimes.begin(), localTimes.end(),
|
std::sort(localTimes.begin(), localTimes.end(),
|
||||||
@@ -364,10 +364,10 @@ void setupBackendRoutes(AsyncWebServer &server) {
|
|||||||
DynamicJsonDocument doc(2048);
|
DynamicJsonDocument doc(2048);
|
||||||
JsonArray leaderboard = doc.createNestedArray("leaderboard");
|
JsonArray leaderboard = doc.createNestedArray("leaderboard");
|
||||||
|
|
||||||
// Nimm die besten 5
|
// Nimm die besten 6
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (const auto &time : localTimes) {
|
for (const auto &time : localTimes) {
|
||||||
if (count >= 5)
|
if (count >= 6)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
JsonObject entry = leaderboard.createNestedObject();
|
JsonObject entry = leaderboard.createNestedObject();
|
||||||
@@ -403,6 +403,58 @@ void setupBackendRoutes(AsyncWebServer &server) {
|
|||||||
request->send(200, "application/json", result);
|
request->send(200, "application/json", result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Erweiterte Leaderboard API (für Leaderboard-Seite - 10 Einträge)
|
||||||
|
server.on(
|
||||||
|
"/api/leaderboard-full", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
// Sortiere nach Zeit (beste zuerst)
|
||||||
|
std::sort(localTimes.begin(), localTimes.end(),
|
||||||
|
[](const LocalTime &a, const LocalTime &b) {
|
||||||
|
return a.timeMs < b.timeMs;
|
||||||
|
});
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(2048);
|
||||||
|
JsonArray leaderboard = doc.createNestedArray("leaderboard");
|
||||||
|
|
||||||
|
// Nimm die besten 10
|
||||||
|
int count = 0;
|
||||||
|
for (const auto &time : localTimes) {
|
||||||
|
if (count >= 10)
|
||||||
|
break;
|
||||||
|
|
||||||
|
JsonObject entry = leaderboard.createNestedObject();
|
||||||
|
entry["rank"] = count + 1;
|
||||||
|
entry["name"] = time.name;
|
||||||
|
entry["uid"] = time.uid;
|
||||||
|
entry["time"] = time.timeMs / 1000.0;
|
||||||
|
|
||||||
|
// Format time inline
|
||||||
|
float seconds = time.timeMs / 1000.0;
|
||||||
|
int totalSeconds = (int)seconds;
|
||||||
|
int minutes = totalSeconds / 60;
|
||||||
|
int remainingSeconds = totalSeconds % 60;
|
||||||
|
int milliseconds = (int)((seconds - totalSeconds) * 100);
|
||||||
|
|
||||||
|
String timeFormatted;
|
||||||
|
if (minutes > 0) {
|
||||||
|
timeFormatted =
|
||||||
|
String(minutes) + ":" + (remainingSeconds < 10 ? "0" : "") +
|
||||||
|
String(remainingSeconds) + "." +
|
||||||
|
(milliseconds < 10 ? "0" : "") + String(milliseconds);
|
||||||
|
} else {
|
||||||
|
timeFormatted = String(remainingSeconds) + "." +
|
||||||
|
(milliseconds < 10 ? "0" : "") +
|
||||||
|
String(milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry["timeFormatted"] = timeFormatted;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
String result;
|
||||||
|
serializeJson(doc, result);
|
||||||
|
request->send(200, "application/json", result);
|
||||||
|
});
|
||||||
|
|
||||||
// Add more routes as needed
|
// Add more routes as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ void IndividualMode(const char *action, int press, int lane,
|
|||||||
sendTimeToOnlineAPI(1, getStart1UID(), currentTime / 1000.0);
|
sendTimeToOnlineAPI(1, getStart1UID(), currentTime / 1000.0);
|
||||||
} else {
|
} else {
|
||||||
// Kein User gefunden - speichere Zeit ohne UID und Namen
|
// Kein User gefunden - speichere Zeit ohne UID und Namen
|
||||||
addLocalTime("", "Anonym", currentTime);
|
addLocalTime("", "Spieler " + String((localTimes.size() + 1)),
|
||||||
|
currentTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +119,8 @@ void IndividualMode(const char *action, int press, int lane,
|
|||||||
sendTimeToOnlineAPI(2, getStart2UID(), currentTime / 1000.0);
|
sendTimeToOnlineAPI(2, getStart2UID(), currentTime / 1000.0);
|
||||||
} else {
|
} else {
|
||||||
// Kein User gefunden - speichere Zeit ohne UID und Namen
|
// Kein User gefunden - speichere Zeit ohne UID und Namen
|
||||||
addLocalTime("", "Anonym", currentTime);
|
addLocalTime("", "Spieler " + String((localTimes.size() + 1)),
|
||||||
|
currentTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ void loop() {
|
|||||||
loopRFID();
|
loopRFID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// loopBattery(); // Batterie-Loop aufrufen
|
|
||||||
|
|
||||||
// Kurze Pause um anderen Tasks Zeit zu geben
|
// Kurze Pause um anderen Tasks Zeit zu geben
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ void setupRoutes() {
|
|||||||
request->send(SPIFFS, "/settings.html", "text/html");
|
request->send(SPIFFS, "/settings.html", "text/html");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on("/leaderboard", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
request->send(SPIFFS, "/leaderboard.html", "text/html");
|
||||||
|
});
|
||||||
|
|
||||||
server.on("/firmware.bin", HTTP_GET, [](AsyncWebServerRequest *request) {
|
server.on("/firmware.bin", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
if (SPIFFS.exists("/firmware.bin")) {
|
if (SPIFFS.exists("/firmware.bin")) {
|
||||||
request->send(SPIFFS, "/firmware.bin", "application/octet-stream");
|
request->send(SPIFFS, "/firmware.bin", "application/octet-stream");
|
||||||
|
|||||||
Reference in New Issue
Block a user