2026-01-24 13:21:36 +01:00
2025-12-06 01:35:01 +01:00
2026-01-24 13:21:36 +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

  • 🌍 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

Internationalisierung (i18n)

Hördle unterstützt vollständige Mehrsprachigkeit für Deutsch und Englisch.

👉 Vollständige i18n-Dokumentation

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)

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:

    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 (leitet automatisch zu /en um).

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)
    • NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC: URL zum Plausible Analytics Script (z.B. https://plausible.example.com/js/script.js, 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: /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:

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 (mit Locale-Präfix):

<!-- 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:

<!-- 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

<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 5.8 MiB
Languages
TypeScript 88.7%
Shell 6.3%
JavaScript 3.2%
CSS 1.2%
Dockerfile 0.6%