Files
hoerdle/README.md
2025-12-04 13:08:07 +01:00

364 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hördle
Eine Web-App inspiriert von Heardle, bei der Nutzer täglich einen Song anhand kurzer Audio-Schnipsel erraten müssen.
## Features
- **🌍 Mehrsprachigkeit (i18n):** Vollständige Unterstützung für Deutsch und Englisch mit automatischer Sprachumleitung und lokalisierten Inhalten.
- **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.
- **Kuratoren-Verwaltung:** Erstellen und Verwalten von Kurator-Accounts mit Zuweisung zu Genres und Specials.
- **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.
- 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.
- **Kurator-System:**
- **Kurator-Accounts:** Separate Login-Accounts für Kuratoren (nicht Admins).
- **Genre- & Special-Zuweisung:** Kuratoren können einzelnen Genres oder Specials zugewiesen werden.
- **Global-Kuratoren:** Optionale globale Kuratoren, die für alle Rätsel zuständig sind.
- **Kurator-Dashboard:** Eigene Dashboard-Seite (`/curator` oder `/de/curator`, `/en/curator`) für Kuratoren.
- **Song-Verwaltung:** Kuratoren können Songs hochladen, bearbeiten und Genres/Specials zuweisen.
- **Curate Specials:** Kuratoren können in einem eigenen Bereich („Curate Specials“) die Startzeiten der Songs in ihren zugewiesenen Specials über den Waveform-Editor einstellen streng begrenzt auf ihre eigenen Specials.
- **Batch-Edit:** Mehrere Titel gleichzeitig bearbeiten (Genre/Special Toggle, Artist ändern, Exclude Global Flag setzen).
- **Kommentar-Verwaltung:** Kuratoren können Spieler-Kommentare zu ihren Rätseln einsehen, als gelesen markieren und archivieren.
- **Spieler-Kommentare:**
- **Feedback an Kuratoren:** Spieler können nach Abschluss eines Rätsels optional eine Nachricht an die Kuratoren senden.
- **KI-gestützte Formulierungshilfe:** Nachrichten können vor dem Absenden auf Wunsch automatisch von einer KI umformuliert/verbessert werden.
- **Einklappbares Kommentar-Formular:** Das Nachrichtenformular ist dezent als einklappbarer Bereich eingebunden und stört den Spielfluss nicht.
- **Automatische Zuordnung:** Kommentare werden automatisch an relevante Kuratoren verteilt (Genre-Kuratoren, Special-Kuratoren, Global-Kuratoren).
- **Rate-Limiting:** Pro Spieler nur ein Kommentar pro Puzzle möglich.
- **Kontext-Informationen:** Kommentare enthalten vollständigen Rätsel-Kontext (Hördle #, Genre/Special, Titel/Artist).
- **Kommentar-Verwaltung:** Kuratoren sehen Kommentare in ihrem Dashboard mit Badge für neue/ungelesene Nachrichten.
- **Analytics:**
- **Plausible Analytics:** Integration mit Plausible Analytics für anonyme Nutzungsstatistiken.
- **Automatisches Domain-Tracking:** Unterstützt mehrere Domains mit automatischer Erkennung.
- **Privacy-First:** Keine Cookies, kein Cross-Site-Tracking.
- 👉 **[Plausible Setup-Dokumentation](docs/PLAUSIBLE_SETUP.md)**
## Internationalisierung (i18n)
Hördle unterstützt vollständige Mehrsprachigkeit für Deutsch und Englisch.
👉 **[Vollständige i18n-Dokumentation](docs/I18N.md)**
**Schnellstart:**
- Deutsche Version: `http://localhost:3000/de`
- Englische Version: `http://localhost:3000/en`
- Root (`/`) leitet automatisch zur Standardsprache (Englisch) um
## 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)](docs/WHITE_LABEL.md)**
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 (falscher Rateversuch) + -5 Punkte (Track-Verlängerung) = **-8 Punkte total**
- **Ü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.
**Hinweis:** Bei falschen Rateversuchen werden zusätzlich -5 Punkte für die automatische Verlängerung des Audio-Snippets (unlockSteps) abgezogen, um die Verwendung dieses Hilfsmittels zu reflektieren.
## 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` (leitet automatisch zu `/en` um).
## Deployment mit Docker
Das Projekt ist für den Betrieb mit Docker optimiert.
👉 **[White Labeling mit Docker? Hier klicken!](docs/WHITE_LABEL.md#docker-deployment)**
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 <dein-passwort>`
**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)
- `NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC`: URL zum Plausible Analytics Script (z.B. `https://plausible.example.com/js/script.js`, 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: `/de/admin` oder `/en/admin`
- Standard-Passwort: `admin123` (Bitte in `docker-compose.yml` ändern! Muss als Hash hinterlegt werden.)
5. **Kurator-Zugang:**
- URL: `/de/curator` oder `/en/curator`
- Kurator-Accounts werden vom Admin erstellt und verwaltet.
- Kuratoren können Songs hochladen und verwalten, sowie Kommentare von Spielern einsehen.
- **Batch-Edit-Funktionalität:**
- Mehrere Titel über Checkboxen auswählen
- Genre/Special Toggle (hinzufügen/entfernen)
- Artist-Änderung für alle ausgewählten Titel
- Exclude Global Flag setzen/entfernen (nur für Global-Kuratoren)
- Toolbar erscheint automatisch bei Auswahl von Titeln
6. **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).
- Die eigentliche Kuratierung (Auswahl des Ausschnitts) findet im **Kuratoren-Dashboard** statt:
- Logge dich als Kurator ein und gehe zu `/de/curator` oder `/en/curator`.
- Klicke im Dashboard auf **„Curate Specials“**, um eine Liste deiner zugewiesenen Specials zu sehen.
- Öffne ein Special und nutze dort 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
<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 (mit Locale-Präfix):
```html
<!-- Rock Genre (Deutsch) -->
<iframe
src="https://hoerdle.elpatron.me/de/Rock"
width="100%"
height="800"
frameborder="0"
allow="autoplay"
title="Hördle Rock Quiz">
</iframe>
<!-- Pop Genre (Englisch) -->
<iframe
src="https://hoerdle.elpatron.me/en/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:
```html
<!-- Weihnachtslieder (Deutsch) -->
<iframe
src="https://hoerdle.elpatron.me/de/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
```html
<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:**
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 <pw>` gibt nun auch direkt den passenden String für Docker Compose aus.
## Lizenz
MIT