Heartbead und Indicators

This commit is contained in:
Carsten Graf
2025-07-03 20:47:44 +02:00
parent ed0be38350
commit da8b21fda9
10 changed files with 740 additions and 64 deletions

BIN
data/button.bin Normal file

Binary file not shown.

View File

@@ -79,6 +79,46 @@ html {
transform: scale(1.1);
}
.heartbeat-indicators {
position: fixed;
top: 20px;
right: 90px;
display: flex;
gap: 15px;
z-index: 1000;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 25px;
padding: 10px 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.heartbeat-indicator {
width: 20px;
height: 20px;
border-radius: 50%;
background: #e74c3c;
transition: all 0.3s ease;
position: relative;
}
.heartbeat-indicator::before {
content: attr(data-label);
position: absolute;
top: -25px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
font-weight: bold;
white-space: nowrap;
color: rgba(255, 255, 255, 0.8);
}
.heartbeat-indicator.active {
background: #2ecc71;
box-shadow: 0 0 10px rgba(46, 204, 113, 0.5);
}
.header {
text-align: center;
margin-bottom: 2vh;
@@ -281,14 +321,21 @@ html {
font-size: clamp(0.9rem, 1.8vw, 1.1rem);
}
/* Responsive Logo Anpassungen für kleine Bildschirme */
@media (max-width: 768px) {
.timer-container {
grid-template-columns: 1fr;
gap: clamp(10px, 2vh, 15px);
padding: 0 3vw;
max-height: 55vh;
.logo {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 1001;
}
.header {
margin-top: 60px; /* Platz für das Logo schaffen */
margin-bottom: 1.5vh;
}
.settings-btn {
top: 10px;
right: 10px;
@@ -298,6 +345,13 @@ html {
padding: 8px;
}
.timer-container {
grid-template-columns: 1fr;
gap: clamp(10px, 2vh, 15px);
padding: 0 3vw;
max-height: 50vh; /* Reduziert wegen des zusätzlichen Platzes oben */
}
.header h1 {
font-size: clamp(1.5rem, 4vw, 2rem);
}
@@ -317,6 +371,14 @@ html {
}
@media (max-width: 480px) {
.logo {
top: 8px;
}
.header {
margin-top: 65px; /* Etwas mehr Platz auf sehr kleinen Bildschirmen */
}
.settings-btn {
top: 8px;
right: 8px;
@@ -332,10 +394,22 @@ html {
.timer-container {
padding: 0 2vw;
max-height: 45vh;
}
.swimmer-name {
font-size: clamp(1rem, 4vw, 1.5rem);
padding: clamp(6px, 1vh, 10px) clamp(8px, 1.5vw, 12px);
}
}
/* Für sehr kleine Bildschirme (iPhone SE, etc.) */
@media (max-width: 375px) {
.header {
margin-top: 70px;
}
.timer-container {
max-height: 40vh;
}
}

View File

@@ -14,6 +14,13 @@
<a href="/settings" class="settings-btn">⚙️</a>
<div class="heartbeat-indicators">
<div class="heartbeat-indicator" id="heartbeat1" data-label="Start1"></div>
<div class="heartbeat-indicator" id="heartbeat2" data-label="Stop1"></div>
<div class="heartbeat-indicator" id="heartbeat3" data-label="Start2"></div>
<div class="heartbeat-indicator" id="heartbeat4" data-label="Stop2"></div>
</div>
<div class="header">
<h1>🏊‍♀️ NinjaCross Timer</h1>
<p>Professioneller Zeitmesser für Ninjacross Wettkämpfe</p>
@@ -69,6 +76,20 @@
let name2 = "";
const ws = new WebSocket(`ws://${window.location.host}/ws`);
// Heartbeat timeout tracker
const heartbeatTimeouts = {
start1: 0,
stop1: 0,
start2: 0,
stop2: 0,
};
// Set all heartbeats to red initially
["heartbeat1", "heartbeat2", "heartbeat3", "heartbeat4"].forEach(id => {
document.getElementById(id).classList.remove('active');
//document.getElementById(id).style.backgroundColor = "red";
});
// Handle WebSocket events
ws.onopen = () => {
console.log("WebSocket connected");
@@ -83,6 +104,22 @@ ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// Heartbeat-Handling
if (data.button && data.mac && data.timestamp) {
let indicatorId = null;
if (data.button === "start1") indicatorId = "heartbeat1";
else if (data.button === "stop1") indicatorId = "heartbeat2";
else if (data.button === "start2") indicatorId = "heartbeat3";
else if (data.button === "stop2") indicatorId = "heartbeat4";
if (indicatorId) {
//heartbeatStatus[deviceId].active = true;
//document.getElementById(indicatorId).style.backgroundColor = "limegreen";
heartbeatTimeouts[data.button] = Date.now();
document.getElementById(indicatorId).classList.add('active');
}
}
if ((data.firstname == "" || data.lastname == "") && data.lane == "start1") {
name1 = "";
}
@@ -200,8 +237,23 @@ ws.onmessage = (event) => {
setInterval(syncFromBackend, 1000);
// Smooth update every 50ms
setInterval(updateDisplay, 50);
setInterval(updateDisplay, 50);
// Heartbeat timeout check (every second)
setInterval(() => {
const now = Date.now();
[
{button: "start1", id: "heartbeat1"},
{button: "stop1", id: "heartbeat2"},
{button: "start2", id: "heartbeat3"},
{button: "stop2", id: "heartbeat4"},
].forEach(({button, id}) => {
if (now - heartbeatTimeouts[button] > 10000) {
document.getElementById(id).classList.remove('active');
//document.getElementById(id).style.backgroundColor = "red";
}
});
}, 1000);
// Initial load
syncFromBackend();

Binary file not shown.