313 lines
11 KiB
Markdown
313 lines
11 KiB
Markdown
# 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)](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
|
|
- **Ü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.
|
|
|
|
👉 **[White Labeling mit Docker? Hier klicken!](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)
|
|
|
|
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
|
|
<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:
|
|
|
|
```html
|
|
<!-- 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:
|
|
|
|
```html
|
|
<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
|
|
|
|
```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
|