# 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
