# 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). - 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. - **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. - 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. - Manuelle Speicherung mit visueller Bestätigung. ## 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 1. Abhängigkeiten installieren: ```bash npm install ``` 2. Datenbank initialisieren: ```bash npx prisma generate npx prisma db push ``` 3. **Optional: Cover-Bilder migrieren:** Falls MP3-Dateien ohne Cover in der Datenbank sind: ```bash node scripts/migrate-covers.mjs ``` 4. Entwicklungsserver starten: ```bash npm run dev ``` Die App läuft unter `http://localhost:3000`. ## Deployment mit Docker Das Projekt ist für den Betrieb mit Docker optimiert. 1. **Vorbereitung:** Kopiere die Beispiel-Konfiguration: ```bash cp docker-compose.example.yml docker-compose.yml ``` Passe die Umgebungsvariablen in der `docker-compose.yml` an: - `ADMIN_PASSWORD`: Admin-Passwort als Bcrypt-Hash. Erstelle den Hash mit: `node scripts/hash-password.js ` **Wichtig:** In `docker-compose.yml` mü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) 2. **Starten:** ```bash docker compose up --build -d ``` Die App ist unter `http://localhost:3010` erreichbar (Port in `docker-compose.yml` konfiguriert). 3. **Daten-Persistenz:** - Die SQLite-Datenbank wird im Ordner `./data` gespeichert. - 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. 4. **Admin-Zugang:** - URL: `/admin` - Standard-Passwort: `admin123` (Bitte in `docker-compose.yml` ändern! Muss als Hash hinterlegt werden.) 5. **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). ## Nginx-Konfiguration (für Reverse Proxy) Wenn du Nginx als Reverse Proxy verwendest, benötigst du spezielle Einstellungen für Audio-Streaming: ```nginx 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 ```html ``` ### Genre-spezifische Einbindung Einzelne Genres können direkt eingebunden werden: ```html ``` ### Special-Einbindung Auch thematische Specials können direkt eingebettet werden: ```html ``` ### 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 ```html
``` ### 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:** 1. Stelle sicher, dass Nginx Range Requests unterstützt (siehe Nginx-Konfiguration oben) 2. Prüfe die Nginx-Logs: `sudo tail -f /var/log/nginx/error.log` 3. Teste direkt ohne Nginx: `http://localhost:3010/uploads/dateiname.mp3` 4. Überprüfe die Response-Headers im Browser (Developer Tools → Network) **Wichtige Nginx-Einstellungen:** - `proxy_set_header Range $http_range;` - Leitet Range Requests weiter - `proxy_buffering off;` - Deaktiviert Buffering für große Dateien - `client_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 ` gibt nun auch direkt den passenden String für Docker Compose aus. ## Lizenz MIT