Hördle
Eine Web-App inspiriert von Heardle, bei der Nutzer täglich einen Song anhand kurzer Audio-Schnipsel erraten müssen.
Features
- Tägliches Rätsel: Jeden Tag ein neuer Song für alle Nutzer.
- Inkrementelle Hinweise: Startet mit 2 Sekunden, dann 4s, 7s, 11s, 16s, 30s, bis 60s (7 Versuche).
- Admin Dashboard:
- Upload von MP3-Dateien.
- Duplikatserkennung: Automatische Erkennung von bereits vorhandenen Songs mit Fuzzy-Matching (toleriert Variationen wie "AC/DC" vs "AC DC").
- Automatische Extraktion von ID3-Tags (Titel, Interpret).
- Intelligente Artist-Erkennung (unterstützt Multi-Artist-Tags).
- Bearbeitung von Metadaten.
- Sortierbare Song-Bibliothek (Titel, Interpret, Hinzugefügt am, Erscheinungsjahr, Aktivierungen, Rating).
- Play/Pause-Funktion zum Vorhören in der Bibliothek.
- Cover Art:
- Automatische Extraktion von Cover-Bildern aus MP3-Dateien.
- Anzeige des Covers nach Spielende (Sieg/Niederlage).
- Automatische Migration bestehender Songs.
- Teilen-Funktion:
- Ergebnisse können als Emoji-Grid geteilt werden.
- Stern-Symbol (⭐) bei korrekt beantworteter Bonusfrage.
- Automatische Anpassung für Genre- und Special-Rätsel.
- PWA Support: Installierbar als App auf Desktop und Mobilgeräten (Manifest & Icons).
- Persistenz: Spielstatus wird lokal im Browser gespeichert.
- Benachrichtigungen: Integration mit Gotify für Push-Nachrichten bei Spielabschluss.
- Genre-Management:
- Erstellen und Verwalten von Musik-Genres.
- Aktivierung/Deaktivierung: Genres können aktiviert oder deaktiviert werden (deaktivierte Genres sind nicht auf der Startseite sichtbar und ihre Routen sind nicht erreichbar).
- Manuelle Zuweisung von Genres zu Songs.
- KI-gestützte automatische Kategorisierung mit OpenRouter (Claude 3.5 Haiku).
- Genre-spezifische tägliche Rätsel.
- Special Curation & Scheduling:
- Erstellen von thematischen Special-Kategorien (z.B. "Weihnachtslieder", "80er Hits").
- Zeitsteuerung: Festlegen von Start- und Enddatum für Specials (automatische Aktivierung/Deaktivierung).
- Kuratierung: Angabe eines Kurators, der auf der Startseite genannt wird ("Curated by ...").
- Visueller Waveform-Editor zur präzisen Auswahl von Audio-Snippets.
- Segment-Marker zeigen Puzzle-Abschnitte (2s, 4s, 7s, etc.).
- Zoom & Pan für detaillierte Bearbeitung.
- Live-Vorschau beim Hovern über die Waveform.
- Playback-Cursor zeigt aktuelle Abspielposition.
- Einzelne Segmente zum Testen abspielen.
- Einzelne Segmente zum Testen abspielen.
- Manuelle Speicherung mit visueller Bestätigung.
- News & Announcements:
- Integriertes News-System für Ankündigungen (z.B. neue Specials, Features).
- Markdown Support: Formatierung von Texten, Links und Listen.
- Homepage Integration: Dezentrale Anzeige auf der Startseite (collapsible).
- Featured News: Hervorhebung wichtiger Ankündigungen.
- Special-Verknüpfung: Direkte Links zu Specials in News-Beiträgen.
- Verwaltung über das Admin-Dashboard.
White Labeling
Hördle ist "White Label Ready". Das bedeutet, du kannst das Branding (Name, Farben, Logos) komplett anpassen, ohne den Code zu ändern.
👉 Anleitung zur Anpassung (White Label Guide)
Die Konfiguration erfolgt einfach über Umgebungsvariablen und CSS-Variablen.
Spielregeln & Punktesystem
Das Ziel ist es, den Song mit so wenigen Hinweisen wie möglich zu erraten und dabei einen möglichst hohen Highscore zu erzielen.
- Start-Punktestand: 90 Punkte
- Richtige Antwort: +20 Punkte
- Falsche Antwort: -3 Punkte
- Überspringen (Skip): -5 Punkte
- Snippet erneut abspielen (Replay): -1 Punkt
- Bonus-Runde (Release-Jahr erraten): +10 Punkte (0 bei falscher Antwort)
- Aufgeben / Verloren: Der Punktestand wird auf 0 gesetzt.
- Minimum: Der Punktestand kann nicht unter 0 fallen.
Tech Stack
- Framework: Next.js 16 (App Router)
- Styling: Vanilla CSS
- Datenbank: SQLite (via Prisma ORM)
- Deployment: Docker & Docker Compose
Lokale Entwicklung
-
Abhängigkeiten installieren:
npm install -
Datenbank initialisieren:
npx prisma generate npx prisma db push -
Optional: Cover-Bilder migrieren: Falls MP3-Dateien ohne Cover in der Datenbank sind:
node scripts/migrate-covers.mjs -
Entwicklungsserver starten:
npm run devDie App läuft unter
http://localhost:3000.
Deployment mit Docker
Das Projekt ist für den Betrieb mit Docker optimiert.
-
Vorbereitung: Kopiere die Beispiel-Konfiguration:
cp docker-compose.example.yml docker-compose.ymlPasse die Umgebungsvariablen in der
docker-compose.ymlan:ADMIN_PASSWORD: Admin-Passwort als Bcrypt-Hash. Erstelle den Hash mit:node scripts/hash-password.js <dein-passwort>Wichtig: Indocker-compose.ymlmüssen alle$Zeichen im Hash verdoppelt werden ($$), damit sie nicht als Variablen interpretiert werden! Beispiel:$$2b$$10$$...TZ: Zeitzone für täglichen Puzzle-Wechsel und Datumsanzeige (Standard:Europe/Berlin)GOTIFY_URL: URL deines Gotify Servers (z.B.https://gotify.example.com)GOTIFY_APP_TOKEN: App Token für Gotify (z.B.A...)OPENROUTER_API_KEY: API-Key für OpenRouter (für KI-Kategorisierung, optional)
-
Starten:
docker compose up --build -dDie App ist unter
http://localhost:3010erreichbar (Port indocker-compose.ymlkonfiguriert). -
Daten-Persistenz:
- Die SQLite-Datenbank wird im Ordner
./datagespeichert. - Hochgeladene Songs und Cover liegen in
./public/uploads. - Beide Ordner werden als Docker Volumes eingebunden, sodass Daten auch bei Container-Neustarts erhalten bleiben.
- Beim Start des Containers wird automatisch ein Migrations-Skript ausgeführt, das fehlende Cover-Bilder aus den MP3s extrahiert.
- Die SQLite-Datenbank wird im Ordner
-
Admin-Zugang:
- URL:
/admin - Standard-Passwort:
admin123(Bitte indocker-compose.ymländern! Muss als Hash hinterlegt werden.)
- URL:
-
Special Curation & Scheduling verwenden:
- Erstelle ein Special im Admin-Dashboard:
- Gib Name, Max Attempts und Unlock Steps ein.
- Optional: Setze ein Startdatum (Launch Date) und Enddatum.
- Optional: Trage einen Kurator ein.
- Weise Songs dem Special zu (über die Song-Bibliothek).
- Klicke auf "Curate" neben dem Special.
- Nutze den Waveform-Editor um den perfekten Ausschnitt zu wählen:
- Klicken: Positioniert die Selektion
- Hovern: Zeigt Vorschau der neuen Position
- Zoom: 🔍+ / 🔍− Buttons für detaillierte Ansicht
- Pan: ← / → Buttons zum Verschieben der Ansicht
- Segment-Playback: Teste einzelne Puzzle-Abschnitte
- Save: Speichere Änderungen mit dem grünen Button
- Die Spieler hören dann nur den kuratierten Ausschnitt.
- Auf der Startseite werden zukünftige Specials unter "Coming soon" angezeigt (mit Datum und Kurator).
- Erstelle ein Special im Admin-Dashboard:
Nginx-Konfiguration (für Reverse Proxy)
Wenn du Nginx als Reverse Proxy verwendest, benötigst du spezielle Einstellungen für Audio-Streaming:
server {
listen 80;
server_name your-domain.com;
# Erhöhe Upload-Limit
client_max_body_size 50M;
location / {
proxy_pass http://localhost:3010;
proxy_http_version 1.1;
# Wichtig für Audio-Streaming: Range Requests weiterleiten
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;
proxy_no_cache $http_range $http_if_range;
# Standard Proxy Headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Eine vollständige Beispiel-Konfiguration findest du in nginx.conf.example.
iFrame-Einbindung
Hördle kann problemlos als iFrame in andere Webseiten eingebettet werden. Die App ist responsive und passt sich automatisch an die iFrame-Größe an.
Grundlegende Einbindung
<iframe
src="https://hoerdle.elpatron.me"
width="100%"
height="800"
frameborder="0"
allow="autoplay"
title="Hördle - Daily Music Quiz">
</iframe>
Genre-spezifische Einbindung
Einzelne Genres können direkt eingebunden werden:
<!-- Rock Genre -->
<iframe
src="https://hoerdle.elpatron.me/Rock"
width="100%"
height="800"
frameborder="0"
allow="autoplay"
title="Hördle Rock Quiz">
</iframe>
<!-- Pop Genre -->
<iframe
src="https://hoerdle.elpatron.me/Pop"
width="100%"
height="800"
frameborder="0"
allow="autoplay"
title="Hördle Pop Quiz">
</iframe>
Special-Einbindung
Auch thematische Specials können direkt eingebettet werden:
<iframe
src="https://hoerdle.elpatron.me/special/Weihnachtslieder"
width="100%"
height="800"
frameborder="0"
allow="autoplay"
title="Hördle Weihnachts-Special">
</iframe>
Empfohlene Einstellungen
- Mindesthöhe: 800px (damit alle Elemente sichtbar sind)
- Breite: 100% oder mindestens 600px
allow="autoplay": Erforderlich für Audio-Wiedergabe- Responsive: Die App passt sich automatisch an mobile Geräte an
Beispiel mit responsiver Höhe
<div style="position: relative; padding-bottom: 133%; height: 0; overflow: hidden;">
<iframe
src="https://hoerdle.elpatron.me"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
frameborder="0"
allow="autoplay"
title="Hördle">
</iframe>
</div>
Hinweise
- Der Spielfortschritt wird im LocalStorage des iFrames gespeichert
- Nutzer können innerhalb des iFrames zwischen Genres wechseln (Navigation bleibt erhalten)
- Die Teilen-Funktion funktioniert auch im iFrame
- Für beste Performance sollte der iFrame auf derselben Domain wie die Hauptseite gehostet werden (vermeidet CORS-Probleme)
Troubleshooting
Audio-Dateien lassen sich nicht abspielen (in Produktion mit Nginx)
Problem: MP3-Dateien funktionieren lokal, aber nicht hinter Nginx.
Lösung:
- Stelle sicher, dass Nginx Range Requests unterstützt (siehe Nginx-Konfiguration oben)
- Prüfe die Nginx-Logs:
sudo tail -f /var/log/nginx/error.log - Teste direkt ohne Nginx:
http://localhost:3010/uploads/dateiname.mp3 - Überprüfe die Response-Headers im Browser (Developer Tools → Network)
Wichtige Nginx-Einstellungen:
proxy_set_header Range $http_range;- Leitet Range Requests weiterproxy_buffering off;- Deaktiviert Buffering für große Dateienclient_max_body_size 50M;- Erlaubt große Uploads
Admin Login schlägt fehl (Docker)
Problem: "Wrong password" trotz korrekt generiertem Hash.
Ursache: Docker Compose interpretiert $ Zeichen im Hash als Variablen.
Lösung:
In der docker-compose.yml müssen alle $ Zeichen im Hash verdoppelt werden ($$).
Falsch: $2b$10$...
Richtig: $$2b$$10$$...
Das Skript node scripts/hash-password.js <pw> gibt nun auch direkt den passenden String für Docker Compose aus.
Lizenz
MIT