Heartbead und Indicators
This commit is contained in:
BIN
data/button.bin
Normal file
BIN
data/button.bin
Normal file
Binary file not shown.
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
Reference in New Issue
Block a user