2025-11-21 12:25:19 +01:00
2025-11-21 12:25:19 +01:00

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

  1. Abhängigkeiten installieren:

    npm install
    
  2. Datenbank initialisieren:

    npx prisma generate
    npx prisma db push
    
  3. Optional: Cover-Bilder migrieren: Falls MP3-Dateien ohne Cover in der Datenbank sind:

    node scripts/migrate-covers.mjs
    
  4. Entwicklungsserver starten:

    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!

  1. Vorbereitung: Kopiere die Beispiel-Konfiguration:

    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:

    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:

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:

  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

Description
No description provided
Readme 1.9 MiB
Languages
TypeScript 90.7%
JavaScript 3.8%
CSS 3.3%
Dockerfile 1.2%
Shell 1%