diff --git a/API.md b/API.md
index a0702b8..8828ac7 100644
--- a/API.md
+++ b/API.md
@@ -7,87 +7,87 @@ All API endpoints return JSON unless otherwise noted.
## Static Files
-| Route | Method | Description | Response Type |
-|------------------|--------|------------------------------|--------------|
-| `/` | GET | Main page | HTML |
-| `/settings` | GET | Settings page | HTML |
-| `/rfid` | GET | RFID page | HTML |
-| `/firmware.bin` | GET | Firmware file (SPIFFS) | Binary |
+| Route | Method | Description | Response Type |
+| --------------- | ------ | ---------------------- | ------------- |
+| `/` | GET | Main page | HTML |
+| `/settings` | GET | Settings page | HTML |
+| `/rfid` | GET | RFID page | HTML |
+| `/firmware.bin` | GET | Firmware file (SPIFFS) | Binary |
---
## Timer & Data
-| Route | Method | Description | Request Body/Params | Response Example |
-|-------------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/data` | GET | Get current timer and status data | – | `{...}` |
-| `/api/reset-best` | POST | Reset best times | – | `{ "success": true }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| ----------------- | ------ | --------------------------------- | ------------------- | --------------------- |
+| `/api/data` | GET | Get current timer and status data | – | `{...}` |
+| `/api/reset-best` | POST | Reset best times | – | `{ "success": true }` |
---
## Button Learning
-| Route | Method | Description | Request Body/Params | Response Example |
-|------------------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/unlearn-button` | POST | Remove all button assignments | – | `{ "success": true }` |
-| `/api/start-learning` | POST | Start button learning mode | – | `{ "success": true }` |
-| `/api/stop-learning` | POST | Stop button learning mode | – | `{ "success": true }` |
-| `/api/learn/status` | GET | Get learning mode status | – | `{ "active": true, "step": 1 }` |
-| `/api/buttons/status` | GET | Get button assignment and voltage | – | `{ "lane1Start": true, "lane1StartVoltage": 3.3, ... }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| --------------------- | ------ | --------------------------------- | ------------------- | ------------------------------------------------------- |
+| `/api/unlearn-button` | POST | Remove all button assignments | – | `{ "success": true }` |
+| `/api/start-learning` | POST | Start button learning mode | – | `{ "success": true }` |
+| `/api/stop-learning` | POST | Stop button learning mode | – | `{ "success": true }` |
+| `/api/learn/status` | GET | Get learning mode status | – | `{ "active": true, "step": 1 }` |
+| `/api/buttons/status` | GET | Get button assignment and voltage | – | `{ "lane1Start": true, "lane1StartVoltage": 3.3, ... }` |
---
## Settings
-| Route | Method | Description | Request Body/Params | Response Example |
-|----------------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/set-max-time` | POST | Set max timer and display time | `maxTime`, `maxTimeDisplay` (form params, seconds) | `{ "success": true }` |
-| `/api/get-settings` | GET | Get current timer settings | – | `{ "maxTime": 300, "maxTimeDisplay": 20 }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| ------------------- | ------ | ------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
+| `/api/set-max-time` | POST | Set max timer and display time | `maxTime`, `maxTimeDisplay`, `minTimeForLeaderboard` (form params, seconds) | `{ "success": true }` |
+| `/api/get-settings` | GET | Get current timer settings | – | `{ "maxTime": 300, "maxTimeDisplay": 20, "minTimeForLeaderboard": 5 }` |
---
## WiFi Configuration
-| Route | Method | Description | Request Body/Params | Response Example |
-|-------------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/set-wifi` | POST | Set WiFi SSID and password | `ssid`, `password` (form params) | `{ "success": true }` |
-| `/api/get-wifi` | GET | Get current WiFi SSID and password | – | `{ "ssid": "...", "password": "..." }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| --------------- | ------ | ---------------------------------- | -------------------------------- | -------------------------------------- |
+| `/api/set-wifi` | POST | Set WiFi SSID and password | `ssid`, `password` (form params) | `{ "success": true }` |
+| `/api/get-wifi` | GET | Get current WiFi SSID and password | – | `{ "ssid": "...", "password": "..." }` |
---
## Location Configuration
-| Route | Method | Description | Request Body/Params | Response Example |
-|----------------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/set-location` | POST | Set location name and ID | `id`, `name` (form params) | `{ "success": true }` |
-| `/api/get-location` | GET | Get current location | – | `{ "locationid": "..." }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| ------------------- | ------ | ------------------------ | -------------------------- | ------------------------- |
+| `/api/set-location` | POST | Set location name and ID | `id`, `name` (form params) | `{ "success": true }` |
+| `/api/get-location` | GET | Get current location | – | `{ "locationid": "..." }` |
---
## Button Update & Mode
-| Route | Method | Description | Request Body/Params | Response Example |
-|----------------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/updateButtons` | GET | Trigger MQTT update for buttons | – | `{ "success": true }` |
-| `/api/set-mode` | POST | Set operational mode | `mode` (form param: "individual" or "wettkampf") | `{ "success": true }` |
-| `/api/get-mode` | GET | Get current operational mode | – | `{ "mode": "individual" }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| -------------------- | ------ | ------------------------------- | ------------------------------------------------ | -------------------------- |
+| `/api/updateButtons` | GET | Trigger MQTT update for buttons | – | `{ "success": true }` |
+| `/api/set-mode` | POST | Set operational mode | `mode` (form param: "individual" or "wettkampf") | `{ "success": true }` |
+| `/api/get-mode` | GET | Get current operational mode | – | `{ "mode": "individual" }` |
---
## System Info
-| Route | Method | Description | Request Body/Params | Response Example |
-|---------------|--------|-------------------------------------|--------------------|------------------|
-| `/api/info` | GET | Get system info (IP, MAC, memory, license, etc.) | – | `{ "ip": "...", "ipSTA": "...", "channel": 1, "mac": "...", "freeMemory": 123456, "connectedButtons": 3, "isOnline": true, "valid": "Ja", "tier": 1 }` |
+| Route | Method | Description | Request Body/Params | Response Example |
+| ----------- | ------ | ------------------------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `/api/info` | GET | Get system info (IP, MAC, memory, license, etc.) | – | `{ "ip": "...", "ipSTA": "...", "channel": 1, "mac": "...", "freeMemory": 123456, "connectedButtons": 3, "isOnline": true, "valid": "Ja", "tier": 1 }` |
---
## WebSocket
-| Route | Description |
-|---------|------------------------------------|
-| `/ws` | WebSocket endpoint for live updates|
+| Route | Description |
+| ----- | ----------------------------------- |
+| `/ws` | WebSocket endpoint for live updates |
---
-**All API endpoints return JSON unless otherwise noted. POST requests expect form parameters (not JSON body).**
\ No newline at end of file
+**All API endpoints return JSON unless otherwise noted. POST requests expect form parameters (not JSON body).**
diff --git a/Bedienungsanleitung_NinjaCross_Timer.html b/Bedienungsanleitung_NinjaCross_Timer.html
new file mode 100644
index 0000000..a8ed284
--- /dev/null
+++ b/Bedienungsanleitung_NinjaCross_Timer.html
@@ -0,0 +1,674 @@
+
+
+
+
+NinjaCross Timer - Bedienungsanleitung
+
+
+
+
+
+NinjaCross Timer - Bedienungsanleitung
+
+
+
Version: 1.0
+
Hersteller: AquaMaster MQTT
+
Datum: 2024
+
+
+1. Einleitung
+
+Der NinjaCross Timer ist ein professionelles Zeitmessgerät für Ninjacross-Wettkämpfe. Das System ermöglicht die präzise Zeitmessung für bis zu zwei Bahnen gleichzeitig und bietet zahlreiche Features wie RFID-Erkennung, lokales Leaderboard und Internet-Konnektivität über WiFi und MQTT.
+
+2. Systemübersicht
+
+2.1 Komponenten
+
+
+ESP32 Master : Hauptprozessor mit Web-Interface
+4 Wireless-Buttons : Start/Stop Buttons für 2 Bahnen
+RFID-Reader : Optional - für Nutzeridentifikation
+Internet-Verbindung : Über WiFi für Cloud-Synchronisation
+
+
+2.2 Anzeigen und Status
+
+
+
+Komponente
+Beschreibung
+
+
+Heartbeat-Indikatoren
+4 grüne/rote Punkte zeigen die Verbindung der Buttons an (Start1, Stop1, Start2, Stop2)
+
+
+Timer-Anzeige
+Live-Zeit für beide Bahnen
+
+
+Status-Anzeige
+Bereit, Läuft, Geschafft, Standby
+
+
+Leaderboard
+Top 6 Zeiten lokal gespeichert
+
+
+Batterie-Warnung
+Banner bei niedriger Batterie der Buttons
+
+
+
+3. Erste Inbetriebnahme
+
+3.1 Einschalten und Netzwerkverbindung
+
+
+Einschalten : Master einschalten
+Access Point finden : Suchen Sie nach dem WiFi-Netzwerk mit dem Namen NinjaCross-XXXXX (die letzten Zeichen sind eindeutig für Ihr Gerät)
+Verbinden : Das Netzwerk ist standardmäßig ohne Passwort
+IP-Adresse : Das Gerät hat die feste IP 192.168.10.1
+Alternative : Sie können auch ninjacross.local im Browser verwenden (mDNS)
+
+
+
+
Wichtig: Der Access Point benötigt kein Passwort.
+
+
+3.2 Web-Interface öffnen
+
+Öffnen Sie Ihren Webbrowser und geben Sie eine der folgenden Adressen ein:
+
+
+http://192.168.10.1 (direkte IP)
+http://ninjacross.local (falls mDNS unterstützt wird)
+
+
+4. Hauptoberfläche
+
+4.1 Timer-Ansicht
+
+Die Hauptseite zeigt:
+
+
+Bahn 1 : Links - Timer und Status
+Bahn 2 : Rechts - Timer und Status
+Heartbeat-Indikatoren : Oben - Verbindungsstatus der Buttons
+Leaderboard : Unten - Top 6 lokale Zeiten
+Navigation :
+
+ 🏆 = Leaderboard (Volansicht)
+ ⚙️ = Einstellungen
+
+
+
+
+4.2 Timer-Bedienung
+
+
+Standby : "Drücke beide Buttons einmal" - Buttons initialisieren
+Bereit : Beide Buttons sind verbunden (grüne Heartbeats)
+Armiert : Startbutton gedrückt - Timer startet bei freigegebenem Button
+Läuft : Timer läuft - Zeit wird live angezeigt
+Geschafft : Stop-Button gedrückt - Zeit wird gespeichert
+
+
+
+
Tipp: Die Anzeige blendet automatisch die Schwimmer-Namen ein, wenn sie via RFID erkannt werden.
+
+
+5. Button-Konfiguration
+
+5.1 Anlernmodus
+
+Der erste Schritt ist das Anlernen Ihrer Wireless-Buttons:
+
+
+Öffnen Sie die Einstellungen (⚙️)
+Scrollen Sie zu "Button-Konfiguration"
+Klicken Sie auf "🎯 Anlernmodus starten"
+Folgen Sie den Anweisungen:
+
+ Drücken Sie den Button für Bahn 1 Start
+ Drücken Sie den Button für Bahn 1 Stop
+ Drücken Sie den Button für Bahn 2 Start
+ Drücken Sie den Button für Bahn 2 Stop
+
+
+Die Anzeige zeigt automatisch an, welchen Button Sie drücken müssen
+Nach erfolgreicher Konfiguration erhalten Sie eine Bestätigung
+
+
+
+
Erfolg: Nach dem Anlernen sollten alle 4 Heartbeat-Indikatoren grün leuchten.
+
+
+5.2 Buttons verlernen
+
+Um alle Button-Zuweisungen zu löschen:
+
+
+Einstellungen öffnen
+"❌ Buttons verlernen" klicken
+Bestätigung erfordert
+
+
+5.3 Button-Status anzeigen
+
+Klicken Sie auf "📊 Button-Status anzeigen" um zu sehen:
+
+
+Welche Buttons konfiguriert sind
+Batteriestand jedes Buttons in Prozent
+
+
+6. RFID-Benutzerverwaltung
+
+6.1 RFID-Karte registrieren
+
+Die RFID-Funktion ermöglicht die automatische Zuordnung von Zeiten zu Nutzern:
+
+
+Öffnen Sie "RFID" (🏷️) aus dem Einstellungsmenü
+Klicken Sie auf "📡 Read Chip"
+Halten Sie die RFID-Karte an den Reader des Masters
+Die UID wird automatisch eingefügt
+Geben Sie den Namen ein
+Klicken Sie auf "💾 Speichern"
+
+
+
+
Funktionsweise: Beim nächsten Scannen der RFID-Karte an einem Button wird automatisch der Name angezeigt und die Zeit diesem Nutzer zugeordnet.
+
+
+6.2 Kontinuierliches Lesen
+
+Der "Read Chip" Button startet einen kontinuierlichen Lesemodus:
+
+
+Statusleiste zeigt: "RFID Lesen gestartet - Karte auflegen!"
+Alle erkannten Karten werden automatisch übernommen
+Nach erfolgreichem Lesen wird die Eingabe fokussiert
+
+
+7. Einstellungen
+
+7.1 Datum & Uhrzeit
+
+Die Uhrzeit kann manuell oder automatisch gesetzt werden:
+
+
+Manuell : Datum und Uhrzeit eingeben, dann "🕐 Uhrzeit setzen"
+Automatisch : "💻 Browser-Zeit übernehmen" verwendet die Zeit Ihres Computers
+
+
+7.2 Modus
+
+
+
+Modus
+Beschreibung
+
+
+👤 Individual
+Beide Bahnen arbeiten unabhängig - ideale für Training
+
+
+🏆 Wettkampf
+Beide Bahnen starten synchron - für Wettkämpfe
+
+
+
+7.3 Lane-Konfiguration
+
+Die Bahnen können identisch oder unterschiedlich konfiguriert werden:
+
+
+⚖️ Identische Lanes : Beide Bahnen sind gleich
+⚡ Unterschiedliche Lanes : Bahnen mit unterschiedlichen Schwierigkeiten
+
+ 🟢 Leicht: Standard-Konfiguration
+ 🔴 Schwer: Anspruchsvollere Hindernisse
+
+
+
+
+7.4 Grundeinstellungen
+
+
+
+Einstellung
+Standard
+Beschreibung
+
+
+Maximale Zeit
+300 Sekunden
+Nach dieser Zeit wird eine Bahn automatisch zurückgesetzt
+
+
+Anzeigedauer
+20 Sekunden
+Wie lange die letzte Zeit angezeigt bleibt
+
+
+Min. Zeit Leaderboard
+5 Sekunden
+Zeiten unter diesem Wert werden nicht gespeichert (Missbrauchsschutz)
+
+
+
+7.5 WLAN-Konfiguration (Lizenz Level 3 erforderlich)
+
+
+
Wichtig: Um das System mit einem bestehenden WLAN zu verbinden wird eine Lizenz Level 3 oder höher.
+
+
+Zur Konfiguration:
+
+
+WLAN Name (SSID) eingeben
+WLAN Passwort eingeben
+Aktueller STA IP-Status wird angezeigt
+Nach dem Speichern startet das Gerät neu
+
+
+
+
Dual-Mode: Das Gerät kann gleichzeitig Access Point (für direkte Verbindung) und WiFi Station (für Internet) betreiben.
+
+
+7.6 Standort (Lizenz Level 3 erforderlich)
+
+Wählen Sie Ihren Standort aus einem Dropdown-Menü:
+
+
+Beim Eingeben einer gültigen Lizenz werden verfügbare Standorte aus der API geladen
+Ohne Lizenz werden Fallback-Standorte angezeigt
+Der gewählte Standort wird lokal gespeichert
+
+
+7.7 OTA Update (Lizenz Level 2 erforderlich)
+
+
+
Lizenz erforderlich: OTA-Updates benötigen Lizenz Level 2 oder höher.
+
+
+
+Klicken Sie auf "🔄 Update durchführen"
+Bestätigen Sie die Abfrage
+Das Gerät lädt die neueste Firmware herunter und installiert sie automatisch
+Während des Updates darf der Strom nicht unterbrochen werden!
+
+
+7.8 Buttons Updaten
+
+Sendet eine Update-Nachricht über MQTT an alle konfigurierten Buttons:
+
+
+Klicken Sie auf "📡 Buttons Updaten"
+Die Buttons erhalten die aktuelle Konfiguration
+Nutzen Sie dies nach Button-Wartung oder Konfigurationsänderungen
+
+
+8. Leaderboard
+
+8.1 Lokales Leaderboard
+
+Die Hauptseite zeigt die Top 6 Zeiten:
+
+
+🏆 Gold für Platz 1
+🥈 Silber für Platz 2
+🥉 Bronze für Platz 3
+Platz 4-6 in Standard-Darstellung
+
+
+8.2 Volle Leaderboard-Ansicht
+
+Öffnen Sie die Leaderboard-Seite (🏆):
+
+
+Zeigt alle erfassten Zeiten
+Gruppiert in 2 Zeilen zu je 5 Einträgen
+Wird alle 5 Sekunden automatisch aktualisiert
+
+
+8.3 Beste Zeiten zurücksetzen
+
+Einstellungen → "🏆 Zeiten verwalten" → "🔄 Beste Zeiten zurücksetzen"
+
+
+
Achtung: Diese Aktion kann nicht rückgängig gemacht werden!
+
+
+9. System-Information
+
+Die Einstellungsseite zeigt folgende Systemdaten:
+
+
+
+Information
+Beschreibung
+
+
+IP-Adresse
+Access Point IP (meist 192.168.10.1)
+
+
+Kanal
+WiFi-Kanal
+
+
+MAC-Adresse
+Eindeutige Geräte-ID
+
+
+Internet
+Ja/Nein - Verbindung zum Internet
+
+
+Freier Speicher
+Verfügbarer RAM in Bytes
+
+
+Verbundene Buttons
+Anzahl konfigurierter Buttons (0-4)
+
+
+Lizenz gültig
+Status der Lizenz
+
+
+Lizenz Level
+0-3 - Bestimmt verfügbare Features
+
+
+
+10. Lizenz-System
+
+10.1 Lizenz-Level
+
+
+
+Level
+Features
+
+
+0 (Basis)
+Standard-Timer, lokales Leaderboard, RFID
+
+
+1
+Alle Level 0 Features
+
+
+2
+Level 1 + OTA Updates
+
+
+3
+Level 2 + WLAN-Station Mode, Standort-Konfiguration
+
+
+
+10.2 Lizenz eingeben
+
+
+Einstellungen → "🔧 Lizenz"
+Lizenzschlüssel eingeben
+"💾 Lizenz speichern" klicken
+System-Information aktualisiert sich automatisch
+
+
+11. Batterie-Überwachung
+
+Das System überwacht kontinuierlich die Batteriestände der Wireless-Buttons:
+
+
+Warnung : Bei Batteriestand ≤ 15% erscheint ein Banner
+Anzeige : "⚠️ Niedrige Batterie erkannt!" mit Geräteliste
+Detailliert : Über Button-Status-Anzeige werden alle Batteriestände angezeigt
+
+
+
+
Tipp: Der Banner blendet automatisch aus, sobald alle Batterien wieder über 15% sind.
+
+
+12. API & Technische Details
+
+12.1 API-Endpunkte
+
+
+
+Endpoint
+Method
+Funktion
+
+
+/api/data
+GET
+Timer und Status abrufen
+
+
+/api/reset-best
+POST
+Beste Zeiten zurücksetzen
+
+
+/api/start-learning
+POST
+Anlernmodus starten
+
+
+/api/learn/status
+GET
+Anlern-Status abrufen
+
+
+/api/buttons/status
+GET
+Button-Konfiguration und Batterie
+
+
+/api/set-max-time
+POST
+Timer-Einstellungen setzen
+
+
+/api/get-settings
+GET
+Einstellungen abrufen
+
+
+/api/set-wifi
+POST
+WiFi konfigurieren
+
+
+/api/set-mode
+POST
+Modus setzen (Individual/Wettkampf)
+
+
+/api/info
+GET
+System-Informationen
+
+
+/ws
+WebSocket
+Live-Updates für Timer
+
+
+
+12.2 WebSocket-Daten
+
+Der WebSocket liefert Echtzeit-Updates:
+
+
+Button-Status und Heartbeats
+Timer-Daten (live)
+RFID-Erkennung
+Batterie-Status
+
+
+13. Troubleshooting
+
+13.1 Buttons verbinden sich nicht
+
+
+Heartbeat rot : Button außerhalb der Reichweite oder Batterie leer
+Lösung : Batterien prüfen, Button näher zum Master bringen
+Neu anlernen : Einstellungen → Buttons verlernen → Anlernmodus starten
+
+
+13.2 WiFi-Verbindung funktioniert nicht
+
+
+Standard: Nutzen Sie den Access Point NinjaCross-XXXXX
+Mit Lizenz Level 3: Konfigurieren Sie das WLAN in den Einstellungen
+Falls Netzwerk nicht gefunden wird: Gerät neustarten
+
+
+13.3 IP-Adresse unbekannt
+
+
+192.168.10.1 ist die Standard IP
+Alternative: ninjacross.local
+Router-Konfiguration: DHCP-Range darf 192.168.10.1 nicht blocken
+
+
+13.4 Timer startet nicht
+
+
+Prüfen Sie alle 4 Heartbeat-Indikatoren (müssen grün sein)
+Start-Button muss vor dem Drücken des Stop-Buttons gedrückt werden
+Bahn muss "Bereit" Status zeigen
+
+
+13.5 RFID wird nicht erkannt
+
+
+RFID-Lesemodus aktivieren: "📡 Read Chip" klicken
+Karte langsam über den Reader führen
+Neu versuchen wenn nach 5 Sekunden nichts passiert
+
+
+
+
Wichtig: Bei andauernden Problemen Gerät neustarten oder Support kontaktieren.
+
+
+14. Wartung
+
+14.1 Regelmäßige Wartung
+
+
+Täglich : Batteriestände prüfen
+Wöchentlich : Leaderboard zurücksetzen (falls gewünscht)
+Monatlich : OTA Update prüfen
+Jährlich : Firmware aktualisieren
+
+
+14.2 Firmware-Updates
+
+
+Lizenz Level 2+ erforderlich
+Einstellungen → OTA Update
+Keine Unterbrechung während des Updates
+Update dauert ca. 1-2 Minuten
+
+
+15. Support & Kontakt
+
+Bei Fragen oder Problemen:
+
+
+Dokumentation prüfen
+Troubleshooting-Abschnitt beachten
+System-Informationen für Support bereitstellen
+
+
+
+
Hinweis: Diese Anleitung basiert auf der aktuellen Firmware-Version. Neuere Versionen könnten abweichende Features haben.
+
+
+16. Anhang
+
+16.1 Tastenkombinationen im Web-Interface
+
+
+Enter in UID-Feld: Sprung zum Namensfeld
+Browser-Refresh: Aktualisiert alle Daten
+
+
+16.2 Unterstützte Browser
+
+
+Chrome/Edge (empfohlen)
+Firefox
+Safari
+Mobile Browser (iOS/Android)
+
+
+16.3 Technische Spezifikationen
+
+
+
+Komponente
+Spezifikation
+
+
+ESP32 Version
+ESP32-WROOM oder kompatibel
+
+
+WiFi
+2.4 GHz, WPA2
+
+
+Protokoll
+MQTT für Kommunikation
+
+
+RFID
+13.56 MHz, NFC-kompatibel
+
+
+Timer-Genauigkeit
+Millisekunden
+
+
+
+
+
+
+Ende der Bedienungsanleitung
+NinjaCross Timer v1.0
+
+
+
+
diff --git a/apientpoints b/apientpoints
index 9e32638..2d01232 100644
--- a/apientpoints
+++ b/apientpoints
@@ -9,7 +9,7 @@ POST /api/unlearn-button
→ Verlernt alle Button-Zuordnungen
POST /api/set-max-time
-→ Setzt die maximale Zeit und maxTimeDisplay
+→ Setzt die maximale Zeit, maxTimeDisplay und minTimeForLeaderboard
GET /api/get-settings
→ Gibt die aktuellen Einstellungen zurück
diff --git a/data/firmware.bin b/data/firmware.bin
deleted file mode 100644
index 1c22703..0000000
Binary files a/data/firmware.bin and /dev/null differ
diff --git a/data/index.css b/data/index.css
index bb1197d..99e32f7 100644
--- a/data/index.css
+++ b/data/index.css
@@ -11,8 +11,8 @@ html {
}
body {
- font-family: "Arial", sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ font-family: "Segoe UI", Arial, sans-serif;
+ background: linear-gradient(0deg, #0d1733 0%, #223c83 100%);
height: 100vh;
width: 100vw;
display: flex;
@@ -38,8 +38,8 @@ body {
text-decoration: none;
display: block;
cursor: pointer;
- padding-left: 5px;
- padding-right: 5px;
+ padding: 5px;
+ background:rgba(255, 255, 255, 0.6);
}
.logo:hover {
@@ -53,6 +53,32 @@ body {
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 {
position: fixed;
top: 20px;
@@ -82,7 +108,7 @@ body {
.heartbeat-indicators {
position: fixed;
top: 20px;
- right: 90px;
+ right: 160px;
display: flex;
gap: 15px;
z-index: 1000;
@@ -93,11 +119,61 @@ body {
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 {
width: 20px;
height: 20px;
border-radius: 50%;
- background: #e74c3c;
+ background: #f50f0f;
transition: all 0.3s ease;
position: relative;
}
@@ -115,8 +191,8 @@ body {
}
.heartbeat-indicator.active {
- background: #2ecc71;
- box-shadow: 0 0 10px rgba(46, 204, 113, 0.5);
+ background: #00ff15;
+ box-shadow: 0 0 10px rgba(73, 186, 228, 0.5);
}
/* Batterie-Banner Styling */
@@ -125,7 +201,7 @@ body {
top: -100px;
left: 0;
width: 100%;
- background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
+ background: linear-gradient(135deg, #f59d0f 0%, #e67e22 100%);
color: white;
padding: 15px 20px;
text-align: center;
@@ -261,6 +337,9 @@ body {
font-size: clamp(1.8rem, 4vw, 2.5rem);
margin-bottom: 0.5vh;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
+ font-weight: bold;
+ text-transform: uppercase;
+ font-family: "Segoe UI", Arial, sans-serif;
}
.header p {
@@ -297,15 +376,20 @@ body {
transition: transform 0.3s ease;
display: flex;
flex-direction: column;
- justify-content: center;
+ justify-content: flex-start;
height: 100%;
overflow: hidden;
+ position: relative;
}
.lane h2 {
font-size: clamp(1.2rem, 2.5vw, 1.8rem);
margin-bottom: clamp(10px, 1vh, 15px);
color: #fff;
+ font-weight: bold;
+ text-transform: uppercase;
+ font-family: "Segoe UI", Arial, sans-serif;
+ flex-shrink: 0;
}
.swimmer-name {
@@ -338,43 +422,75 @@ body {
}
.time-display {
- font-size: clamp(3rem, 9vw, 10rem);
+ font-size: clamp(3rem, 13vw, 13rem);
font-weight: bold;
margin: clamp(10px, 1vh, 15px) 0;
font-family: "Courier New", monospace;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
line-height: 1;
+ position: relative;
+ z-index: 1;
+ flex-shrink: 0;
+ order: 1;
}
.status {
- font-size: clamp(3rem, 1.8vw, 1.2rem);
+ font-size: clamp(1.5rem, 4vw, 5rem);
margin: clamp(8px, 1vh, 12px) 0;
padding: clamp(6px, 1vh, 10px) clamp(12px, 2vw, 18px);
border-radius: 20px;
display: inline-block;
font-weight: 600;
+ position: relative;
+ z-index: 2;
+}
+
+.status:not(.large-status) {
+ position: relative;
+ order: 2;
+ margin-top: auto;
+}
+
+.status.large-status {
+ font-size: clamp(1.8rem, 5vw, 5rem);
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 10;
+ margin: 0 !important;
+ padding: clamp(8px, 1.5vh, 15px) clamp(15px, 3vw, 30px);
+ white-space: normal;
+ pointer-events: none;
+ text-align: center;
+ background-color: rgba(0, 0, 0, 0.85) !important;
+ backdrop-filter: blur(5px);
+ width: calc(100% - 40px);
+ max-width: calc(100% - 40px);
+ word-wrap: break-word;
+ line-height: 1.3;
+ overflow: visible;
}
.status.finished {
- background-color: rgba(52, 152, 219, 0.3);
- border: 2px solid #3498db;
+ background-color: rgba(73, 186, 228, 0.3);
+ border: 2px solid #49bae4;
}
.status.ready {
- background-color: rgba(46, 204, 113, 0.3);
- border: 2px solid #2ecc71;
+ background-color: rgb(0 165 3 / 54%);
+ border: 2px solid #06ff00;
animation: pulse 1s infinite;
}
.status.armed {
- background-color: rgb(197, 194, 0);
- border: 2px solid #fbff00;
+ background-color: rgba(245, 157, 15, 0.3);
+ border: 2px solid #f59d0f;
animation: pulse 1s infinite;
}
.status.running {
- background-color: rgba(231, 76, 60, 0.3);
- border: 2px solid #e74c3c;
+ background-color: rgb(255 91 0 / 65%);
+ border: 2px solid #f59d0f;
}
@keyframes pulse {
@@ -390,8 +506,8 @@ body {
}
.status.standby {
- background-color: rgba(255, 193, 7, 0.3);
- border: 2px solid #ffc107;
+ background-color: rgba(220, 242, 250, 0.3);
+ border: 2px solid #DCF2FA;
animation: standbyBlink 2s infinite;
}
@@ -422,17 +538,40 @@ body {
border-radius: 15px;
padding: clamp(10px, 1.5vh, 15px);
margin: 1vh 0 0 0;
- width: 50%;
- max-width: 50%;
+ width: clamp(320px, 80vw, 960px);
+ max-width: 960px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.2);
flex-shrink: 0;
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 {
font-size: clamp(0.9rem, 1.8vw, 1.1rem);
- margin-bottom: clamp(5px, 0.5vh, 8px);
+ margin: 0 auto;
+ font-weight: bold;
+ text-transform: uppercase;
+ font-family: "Segoe UI", Arial, sans-serif;
+ text-align: center;
}
.best-time-row {
@@ -446,9 +585,121 @@ body {
border-radius: 8px;
}
+/* Leaderboard Styles */
+#leaderboard-container {
+ text-align: left;
+}
+
+.leaderboard-entry {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin: clamp(8px, 1vh, 12px) 0;
+ font-size: clamp(1.1rem, 2.2vw, 1.4rem);
+ font-weight: 600;
+ background: rgba(255, 255, 255, 0.15);
+ padding: clamp(12px, 2vh, 16px) clamp(16px, 3vw, 24px);
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ transition: all 0.3s ease;
+ min-height: 50px;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.leaderboard-entry:hover {
+ background: rgba(255, 255, 255, 0.25);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
+}
+
+.leaderboard-entry .rank {
+ color: #ffd700;
+ font-weight: bold;
+ min-width: 30px;
+ font-size: clamp(1.2rem, 2.4vw, 1.5rem);
+}
+
+.leaderboard-entry .name {
+ flex: 1;
+ margin: 0 15px;
+ color: #ffffff;
+ font-weight: 600;
+}
+
+.leaderboard-entry .time {
+ color: #00ff88;
+ font-weight: bold;
+ font-family: 'Courier New', monospace;
+ min-width: 80px;
+ 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 {
+ text-align: center;
+ color: rgba(255, 255, 255, 0.7);
+ font-style: italic;
+ font-size: clamp(0.9rem, 1.8vw, 1.1rem);
+ padding: 20px;
+}
+
.learning-mode {
- background: rgba(255, 193, 7, 0.2);
- border: 2px solid #ffc107;
+ background: rgba(245, 157, 15, 0.2);
+ border: 2px solid #f59d0f;
border-radius: 15px;
padding: clamp(15px, 2vh, 20px);
margin: 2vh 0;
@@ -463,9 +714,12 @@ body {
}
.learning-mode h3 {
- color: #ffc107;
+ color: #f59d0f;
margin-bottom: 10px;
font-size: clamp(1rem, 2vw, 1.3rem);
+ font-weight: bold;
+ text-transform: uppercase;
+ font-family: "Segoe UI", Arial, sans-serif;
}
.learning-mode p {
diff --git a/data/index.html b/data/index.html
index c3e5607..26b031f 100644
--- a/data/index.html
+++ b/data/index.html
@@ -15,14 +15,16 @@
⚠️ Niedrige Batterie erkannt!
- Geräte mit niedriger Batterie:
+ Deine Geräte mit niedriger Batterie:
+
×
-
+
+ 🏆
⚙️
@@ -42,46 +44,37 @@
📚 Lernmodus aktiv
-
- Bitte drücken Sie den Button für:
-
+
Drücke jetzt den Button für:
🏊♀️ Bahn 1
-
00.00
- Standby: Bitte beide 1x betätigen
+ Standby: Drücke beide Buttons einmal
+
00.00
🏊♂️ Bahn 2
-
00.00
- Standby: Bitte beide 1x betätigen
+ Standby: Drücke beide Buttons einmal
+
00.00
-
🏆 Beste Zeiten des Tages
-
- Bahn 1:
- --.-
-
-
- Bahn 2:
- --.-
-
+
🏆 Lokales Leaderboard
+