Badminton ELO-Ranking-App für Einzel- und Doppelspiele — gehostet im Browser, Daten in Supabase.
- Einzel- & Doppel-ELO — separate Ratings, K-Faktor 32, Startwert 1000
- Match eintragen — Einzel per Spieler-Auswahl, Doppel per Klick-Zuweisung zu Teams
- Match löschen — ELO aller Spieler wird anschließend vollständig neu berechnet
- Rangliste — getrennte Tabellen für Einzel und Doppel
- ELO-Verlauf — interaktives Liniendiagramm (Chart.js) für alle Spieler
- Spieler-Profil — Modal mit persönlicher Statistik, ELO-Trend und letzten 15 Spielen
- Spielverlauf — chronologische Match-History
- Schreib-Schutz — Änderungen nur mit
APP_SECRET, Lesen bleibt öffentlich - Demo-Modus —
?demo=truelädt Beispieldaten ohne Datenbankverbindung - Offline-Fallback — Daten werden im
localStoragegecacht - Mobile-optimiert — scrollbare Tab-Leiste, Bottom-Sheet-Modal, 44 px Touch-Targets
- Kostenloses Projekt auf supabase.com erstellen
- SQL-Schema einspielen (
supabase/schema.sqlim SQL-Editor ausführen — Passwort ersetzen!) - Project URL und anon public key aus den Projekteinstellungen kopieren
cp config.example.js config.jsconfig.js öffnen und alle drei Werte eintragen:
var CONFIG = {
SUPABASE_URL: 'https://DEIN_PROJEKT.supabase.co',
SUPABASE_ANON_KEY: 'DEIN_ANON_KEY',
APP_SECRET: 'DEIN_GEHEIMES_PASSWORT', // muss mit dem Wert in schema.sql übereinstimmen
};
config.jsist in.gitignoreeingetragen und wird nie ins Repository hochgeladen.
index.html direkt im Browser öffnen oder auf einem beliebigen Webserver hosten — kein Build-Schritt nötig.
Die Supabase-RLS-Policies unterscheiden zwischen Lesen und Schreiben:
| Aktion | Berechtigung |
|---|---|
| Rangliste / History lesen | ✅ jeder |
| Match eintragen / löschen | 🔒 nur mit APP_SECRET |
| Spieler anlegen | 🔒 nur mit APP_SECRET |
Die App sendet APP_SECRET als x-app-secret HTTP-Header bei jedem Schreibzugriff. Wer keine config.js hat, kann nur lesen.
Bestehende Datenbank nachrüsten: supabase/migrate_write_secret.sql im Supabase SQL-Editor ausführen.
Zum Testen der UI ohne Datenbankverbindung:
index.html?demo=true
Lädt 6 Beispielspieler und 12 Beispiel-Matches. Alle Features funktionieren lokal — nichts wird gespeichert. Ein gelbes Banner signalisiert den Demo-Modus.
├── index.html # Haupt-HTML (ES-Module, kein Bundler)
├── app.js # Orchestrierung: Events, Datenladen, API-Aufrufe
├── style.css # Styles (responsiv, mobile-first)
├── config.example.js # Konfigurationsvorlage (ohne Secrets)
├── config.js # Lokale Konfiguration (gitignored)
├── src/
│ ├── elo.js # Reine ELO-Berechnungen (keine DOM-Abhängigkeiten)
│ ├── api.js # Supabase REST-API (camelCase ↔ snake_case Mapping)
│ ├── state.js # Globaler App-State + localStorage-Persistenz
│ ├── ui.js # DOM-Rendering (Rangliste, History, Profil, …)
│ ├── chart.js # Chart.js-Diagramme (ELO-Verlauf, Profil-Chart)
│ └── demo.js # Beispieldaten für den Demo-Modus
├── supabase/
│ ├── schema.sql # Datenbankschema inkl. RLS-Policies (Neu-Setup)
│ └── migrate_write_secret.sql # Migration für bestehende DBs
├── src/
│ ├── elo.test.js # Unit-Tests für ELO-Logik (Vitest)
│ └── api.test.js # Unit-Tests für API-Mapping (Vitest)
├── .github/
│ └── workflows/
│ ├── ci.yml # GitHub Actions: npm test bei jedem Push/PR
│ └── deploy.yml # GitHub Actions: Deploy auf GitHub Pages
└── .gitignore
elo.js → api.js → state.js → ui.js → app.js
(Logik) (HTTP) (State) (DOM) (Orchestrierung)
Jede Schicht kennt nur die Schicht darunter. app.js verbindet alles und exportiert globale Funktionen für HTML-onclick-Attribute.
| Tabelle | Wichtige Spalten |
|---|---|
players |
id TEXT, name, elo, matches, wins, losses, doubles_elo, doubles_matches, doubles_wins, doubles_losses |
matches |
id BIGSERIAL, date, type (singles/doubles), winner_id, loser_id, winner_name, loser_name, elo_change |
Erwarteter Score: E = 1 / (1 + 10^((ELO_Gegner - ELO_Spieler) / 400))
Neues ELO: ELO_neu = ELO_alt + K × (Ergebnis - E)
K-Faktor: 32
Startwert: 1000
Bei Doppel wird der Durchschnitts-ELO jedes Teams für die Erwartungsberechnung verwendet. Nach jedem Match-Löschvorgang werden alle ELO-Werte aus der kompletten History neu berechnet, um Datendrift zu vermeiden.
npm install
npm test # einmalig
npm run test:watch # im Watch-Modus48 Unit-Tests (21 ELO-Logik + 27 API-Mapping) via Vitest. CI prüft jeden Push und PR gegen main.
git checkout -b feature/mein-feature
# … entwickeln …
git push -u origin feature/mein-feature
# Pull Request gegen main öffnen