<!DOCTYPE html>
<html lang="de" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LibreDeck – Spielkarten aus deinen Playlists</title>
    <meta name="description" content="Erstelle druckfertige Spielkarten aus deinen Deezer-Playlists. Mit QR-Code auf der Vorderseite und Künstler, Titel & Jahr auf der Rückseite.">
    <link rel="icon" type="image/png" th:href="@{/images/favicon.png}">
    <link rel="stylesheet" th:href="@{/css/style.css}">
</head>
<body>

<!-- ── Hero ── -->
<section class="hero">
    <div class="hero-inner">
        <div class="hero-text">
            <p class="hero-eyebrow">Kostenlos · Kein Account nötig</p>
            <h1>Spielkarten aus deinen<br><span class="accent">Lieblings&#8209;Playlists</span></h1>
            <p class="hero-sub">
                Füge einen Playlist-Link ein und lade ein druckfertiges PDF herunter.
                Jede Karte hat vorne einen QR-Code zum Abspielen und hinten
                Künstler, Titel und Erscheinungsjahr.
            </p>
            <a href="#generator" class="btn btn-lg">Jetzt Karten erstellen</a>
        </div>

        <div class="hero-brand" aria-hidden="true">
            <img th:src="@{/images/logo.png}" alt="LibreDeck" class="hero-brand-logo">
        </div>
    </div>
</section>

<!-- ── Service Topbar ── -->
<div class="service-topbar">
    <div class="topbar-inner section-inner">
        <button class="topbar-nav" id="btnTopbarPrev">&#8592;</button>
        <div class="topbar-service">
            <img class="topbar-icon" id="topbarIcon" src="https://open.spotify.com/favicon.ico" alt="">
            <span class="topbar-service-name" id="topbarServiceName">Spotify</span>
        </div>
        <button class="topbar-nav" id="btnTopbarNext">&#8594;</button>
    </div>
</div>

<!-- ── How it works ── -->
<section class="how-section">
    <div class="section-inner">
        <h2 class="section-title">So funktioniert es</h2>

        <div class="steps-viewport">
            <!-- Spotify steps -->
            <div class="steps how-steps" id="howSpotify">
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/><polyline points="16 11 18 13 22 9"/>
                        </svg>
                    </div>
                    <h3>Mit Spotify verbinden</h3>
                    <p>Klicke auf „Mit Spotify verbinden" und melde dich einmalig pro Sitzung an.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>
                        </svg>
                    </div>
                    <h3>Playlist auswählen</h3>
                    <p>Deine Playlists erscheinen direkt zur Auswahl – einfach anklicken und PDF wird erstellt.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>
                        </svg>
                    </div>
                    <h3>Ausdrucken &amp; spielen</h3>
                    <p>PDF doppelseitig drucken, Karten ausschneiden – fertig.</p>
                </div>
            </div>

            <!-- Tidal steps -->
            <div class="steps how-steps" id="howTidal" style="visibility:hidden;transform:translateX(100%)">
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/><polyline points="16 11 18 13 22 9"/>
                        </svg>
                    </div>
                    <h3>Mit Tidal verbinden</h3>
                    <p>Klicke auf „Mit Tidal verbinden" und melde dich einmalig pro Sitzung an.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>
                        </svg>
                    </div>
                    <h3>Playlist auswählen</h3>
                    <p>Deine Tidal-Playlists erscheinen zur Auswahl – einfach anklicken.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>
                        </svg>
                    </div>
                    <h3>Ausdrucken &amp; spielen</h3>
                    <p>PDF doppelseitig drucken, Karten ausschneiden – fertig.</p>
                </div>
            </div>

            <!-- Apple Music steps -->
            <div class="steps how-steps" id="howApple" style="visibility:hidden;transform:translateX(100%)">
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/><polyline points="16 11 18 13 22 9"/>
                        </svg>
                    </div>
                    <h3>Mit Apple Music verbinden</h3>
                    <p>Klicke auf „Mit Apple Music verbinden" und melde dich mit deiner Apple ID an.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/>
                        </svg>
                    </div>
                    <h3>Playlist auswählen</h3>
                    <p>Deine Apple Music-Playlists erscheinen zur Auswahl – oder gib direkt einen Playlist-Link ein.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>
                        </svg>
                    </div>
                    <h3>Ausdrucken &amp; spielen</h3>
                    <p>PDF doppelseitig drucken, Karten ausschneiden – fertig.</p>
                </div>
            </div>

            <!-- YouTube steps -->
            <div class="steps how-steps" id="howYoutube" style="visibility:hidden;transform:translateX(100%)">
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M22.54 6.42a2.78 2.78 0 0 0-1.95-1.96C18.88 4 12 4 12 4s-6.88 0-8.59.46A2.78 2.78 0 0 0 1.46 6.42 29 29 0 0 0 1 12a29 29 0 0 0 .46 5.58 2.78 2.78 0 0 0 1.95 1.96C5.12 20 12 20 12 20s6.88 0 8.59-.46a2.78 2.78 0 0 0 1.96-1.96A29 29 0 0 0 23 12a29 29 0 0 0-.46-5.58z"/><polygon points="9.75 15.02 15.5 12 9.75 8.98 9.75 15.02"/>
                        </svg>
                    </div>
                    <h3>Playlist öffnen</h3>
                    <p>Öffne eine YouTube-Playlist im Browser und kopiere den Link aus der Adresszeile.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/>
                        </svg>
                    </div>
                    <h3>PDF generieren</h3>
                    <p>Link hier einfügen und auf „PDF erstellen" klicken.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>
                        </svg>
                    </div>
                    <h3>Ausdrucken &amp; spielen</h3>
                    <p>PDF doppelseitig drucken, Karten ausschneiden – fertig.</p>
                </div>
            </div>

            <!-- Deezer steps -->
            <div class="steps how-steps" id="howDeezer" style="visibility:hidden;transform:translateX(100%)">
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>
                        </svg>
                    </div>
                    <h3>Playlist öffnen</h3>
                    <p>Öffne eine Deezer-Playlist in der App oder im Browser und kopiere den Link.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/>
                        </svg>
                    </div>
                    <h3>PDF generieren</h3>
                    <p>Link hier einfügen und auf „PDF erstellen" klicken. Das PDF wird direkt heruntergeladen.</p>
                </div>
                <div class="step-arrow" aria-hidden="true">→</div>
                <div class="step">
                    <div class="step-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
                            <polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>
                        </svg>
                    </div>
                    <h3>Ausdrucken &amp; spielen</h3>
                    <p>PDF doppelseitig drucken, Karten ausschneiden – fertig.</p>
                </div>
            </div>
        </div>
    </div>
</section>

<!-- ── Card detail ── -->
<section class="detail-section">
    <div class="section-inner detail-inner">
        <div class="detail-text">
            <h2>Was drauf steht</h2>
            <ul class="detail-list">
                <li>
                    <span class="detail-icon front-icon">V</span>
                    <div>
                        <strong>Vorderseite</strong>
                        <p>QR-Code, der direkt zum Track auf Deezer führt – einfach scannen und Musik läuft.</p>
                    </div>
                </li>
                <li>
                    <span class="detail-icon back-icon">R</span>
                    <div>
                        <strong>Rückseite</strong>
                        <p>Erscheinungsjahr, Künstlername und Titel – alles was du zum Raten und Einordnen brauchst.</p>
                    </div>
                </li>
                <li>
                    <span class="detail-icon print-icon">✓</span>
                    <div>
                        <strong>Druckfertig</strong>
                        <p>35 Karten à 40 × 40 mm auf einer A4-Seite</p>
                    </div>
                </li>
            </ul>
        </div>
        <div class="detail-preview" aria-hidden="true">
            <div class="preview-stack">
                <div class="card-mockup card-back small">
                    <div class="mock-year">2007</div>
                    <div class="mock-divider"></div>
                    <div class="mock-artist">Amy Winehouse</div>
                    <div class="mock-title">Rehab</div>
                    <div class="mock-brand">libredeck</div>
                </div>
                <div class="card-mockup card-back small">
                    <div class="mock-year">2013</div>
                    <div class="mock-divider"></div>
                    <div class="mock-artist">Daft Punk</div>
                    <div class="mock-title">Get Lucky</div>
                    <div class="mock-brand">libredeck</div>
                </div>
                <div class="card-mockup card-back small">
                    <div class="mock-year">1982</div>
                    <div class="mock-divider"></div>
                    <div class="mock-artist">Michael Jackson</div>
                    <div class="mock-title">Billie Jean</div>
                    <div class="mock-brand">libredeck</div>
                </div>
                <div class="card-mockup card-back small">
                    <div class="mock-year">1994</div>
                    <div class="mock-divider"></div>
                    <div class="mock-artist">Nirvana</div>
                    <div class="mock-title">Come as You Are</div>
                    <div class="mock-brand">libredeck</div>
                </div>
                <div class="card-mockup card-back small">
                    <div class="mock-year">1977</div>
                    <div class="mock-divider"></div>
                    <div class="mock-artist">Bee Gees</div>
                    <div class="mock-title">Stayin' Alive</div>
                    <div class="mock-brand">libredeck</div>
                </div>
                <div class="card-mockup card-back small">
                    <div class="mock-year">2019</div>
                    <div class="mock-divider"></div>
                    <div class="mock-artist">The Weeknd</div>
                    <div class="mock-title">Blinding Lights</div>
                    <div class="mock-brand">libredeck</div>
                </div>
            </div>
        </div>
    </div>
</section>

<!-- ── Generator ── -->
<section class="generator-section" id="generator">
    <div class="section-inner">
        <h2 class="section-title">PDF erstellen</h2>
        <p class="section-sub">Wähle deinen Streaming-Dienst und starte direkt.</p>

        <div class="generator-card">

            <div class="gen-viewport">

                <!-- Spotify Panel -->
                <div class="service-panel" id="panelSpotify">
                    <div id="spotifyConnectArea">
                        <p class="service-hint">Verbinde dich einmalig mit Spotify – dann kannst du deine Playlists direkt auswählen.</p>
                        <a id="spotifyAuthLink" href="/spotify/auth" class="btn btn-lg spotify-btn">Mit Spotify verbinden</a>
                    </div>
                    <div id="spotifyPickerSection" style="display:none">
                        <div class="picker-header">
                            <span class="picker-label" style="margin:0">Deine Playlists</span>
                            <button class="btn-refresh" onclick="loadSpotifyPlaylists()" title="Aktualisieren">&#8635;</button>
                        </div>
                        <div class="playlist-carousel" id="spotifyCarousel">
                            <button class="carousel-btn carousel-btn--prev" id="spotifyPrev" disabled>&#8249;</button>
                            <div class="carousel-viewport">
                                <div class="carousel-track" id="spotifyTrack"></div>
                            </div>
                            <button class="carousel-btn carousel-btn--next" id="spotifyNext" disabled>&#8250;</button>
                        </div>
                        <div class="spotify-url-row" style="margin-top:.75rem">
                            <input type="url" id="spotifyUrlInput" class="url-input"
                                   placeholder="https://open.spotify.com/playlist/…" autocomplete="off"/>
                            <button class="btn btn-lg" onclick="submitSpotifyUrl()">Laden</button>
                        </div>
                    </div>
                </div>

                <!-- Tidal Panel -->
                <div class="service-panel" id="panelTidal" style="visibility:hidden;transform:translateX(100%)">
                    <div id="tidalConnectArea">
                        <p class="service-hint">Verbinde dich einmalig mit Tidal – dann kannst du deine Playlists direkt auswählen.</p>
                        <a id="tidalAuthLink" href="/tidal/auth" class="btn btn-lg tidal-btn">Mit Tidal verbinden</a>
                    </div>
                    <div id="tidalPickerSection" style="display:none">
                        <div class="picker-header">
                            <span class="picker-label" style="margin:0">Deine Playlists</span>
                            <button class="btn-refresh" onclick="loadTidalPlaylists()" title="Aktualisieren">&#8635;</button>
                        </div>
                        <div class="playlist-carousel" id="tidalCarousel">
                            <button class="carousel-btn carousel-btn--prev" id="tidalPrev" disabled>&#8249;</button>
                            <div class="carousel-viewport">
                                <div class="carousel-track" id="tidalTrack"></div>
                            </div>
                            <button class="carousel-btn carousel-btn--next" id="tidalNext" disabled>&#8250;</button>
                        </div>
                        <div class="spotify-url-row" style="margin-top:.75rem">
                            <input type="url" id="tidalUrlInput" class="url-input"
                                   placeholder="https://tidal.com/browse/playlist/…" autocomplete="off"/>
                            <button class="btn btn-lg" onclick="submitTidalUrl()">Laden</button>
                        </div>
                    </div>
                </div>

                <!-- Apple Music Panel -->
                <div class="service-panel" id="panelApple" style="visibility:hidden;transform:translateX(100%)">
                    <div id="appleNotConfigured" style="display:none">
                        <p class="service-hint">Apple Music ist auf diesem Server nicht konfiguriert.</p>
                    </div>
                    <div id="appleConnectArea" style="display:none">
                        <p class="service-hint">Verbinde dich einmalig mit Apple Music – dann kannst du deine Playlists direkt auswählen.</p>
                        <button id="btnAppleConnect" class="btn btn-lg" onclick="connectAppleMusic()">Mit Apple Music verbinden</button>
                    </div>
                    <div id="applePickerSection" style="display:none">
                        <div class="picker-header">
                            <span class="picker-label" style="margin:0">Deine Playlists</span>
                            <button class="btn-refresh" onclick="loadApplePlaylists()" title="Aktualisieren">&#8635;</button>
                        </div>
                        <div class="playlist-carousel" id="appleCarousel">
                            <button class="carousel-btn carousel-btn--prev" id="applePrev" disabled>&#8249;</button>
                            <div class="carousel-viewport">
                                <div class="carousel-track" id="appleTrack"></div>
                            </div>
                            <button class="carousel-btn carousel-btn--next" id="appleNext" disabled>&#8250;</button>
                        </div>
                        <div class="spotify-url-row" style="margin-top:.75rem">
                            <input type="url" id="appleUrlInput" class="url-input"
                                   placeholder="https://music.apple.com/us/playlist/…" autocomplete="off"/>
                            <button class="btn btn-lg" onclick="submitAppleUrl()">Laden</button>
                        </div>
                        <div style="max-width:600px;margin:.5rem auto 0;text-align:right">
                            <button class="btn-cancel" style="font-size:.8rem" onclick="disconnectAppleMusic()">Verbindung trennen</button>
                        </div>
                    </div>
                </div>

                <!-- YouTube Panel -->
                <div class="service-panel" id="panelYoutube" style="visibility:hidden;transform:translateX(100%)">
                    <div class="input-row">
                        <input type="url" id="youtubeUrlInput" class="url-input"
                               placeholder="https://www.youtube.com/playlist?list=…" autocomplete="off"/>
                        <button class="btn btn-lg" onclick="submitYoutubeUrl()">Daten ermitteln</button>
                    </div>
                    <p class="youtube-notice">
                        Das Erscheinungsjahr wird über MusicBrainz ermittelt – bei langen Playlists kann das einige Minuten dauern.
                        Playlists mit mehr als 500 Einträgen werden nicht unterstützt.
                    </p>
                    <details class="hint-details">
                        <summary>Wo finde ich den Playlist-Link?</summary>
                        <ol class="hint-steps">
                            <li>Playlist auf YouTube im Browser öffnen</li>
                            <li>Link aus der Adresszeile kopieren</li>
                            <li>Link oben einfügen</li>
                        </ol>
                        <p class="hint-example">Beispiel: <code>https://www.youtube.com/playlist?list=PLxxxxxx</code></p>
                    </details>
                </div>

                <!-- Deezer Panel -->
                <div class="service-panel" id="panelDeezer" style="visibility:hidden;transform:translateX(100%)">
                    <div class="input-row">
                        <input type="url" id="playlistUrl" class="url-input"
                               placeholder="https://www.deezer.com/playlist/…" autocomplete="off"/>
                        <button id="btnGenerate" class="btn btn-lg">Daten ermitteln</button>
                    </div>
                    <details class="hint-details">
                        <summary>Wo finde ich den Playlist-Link?</summary>
                        <ol class="hint-steps">
                            <li>Playlist in der Deezer-App bzw. im Browser öffnen</li>
                            <li>Auf <strong>„…"</strong> → <strong>„Link teilen / kopieren"</strong> tippen</li>
                            <li>Link oben einfügen</li>
                        </ol>
                        <p class="hint-example">Deezer: <code>https://www.deezer.com/playlist/1234567890</code></p>
                    </details>
                </div>

            </div>

            <div id="errorMsg" class="error-msg" style="display:none"></div>
            <div th:if="${error}" class="error-msg" th:text="${error}"></div>
        </div>

        <!-- ── Review section (shown after year lookup, always) ── -->
        <div id="reviewSection" class="review-section" style="display:none">
            <h3 class="review-heading">Titel &amp; Erscheinungsjahre prüfen</h3>
            <p class="review-sub">
                Gefundene Jahre sind bereits eingetragen. Passe sie bei Bedarf an oder
                leere das Feld – dann wird die Karte nicht erstellt.
            </p>
            <div class="review-actions" id="reviewActionButtons">
                <button class="btn-outline" onclick="document.getElementById('bulkCheckDialog').style.display='flex'">Alle prüfen</button>
                <button id="btnPlay"    class="btn btn-lg btn-play">Direkt spielen</button>
                <button id="btnConfirm" class="btn btn-lg">PDF erstellen</button>
            </div>
            <div id="bulkProgress" class="bulk-progress" style="display:none">
                <div class="progress-bar-bg" style="flex:1">
                    <div class="progress-bar-fill" id="bulkProgressFill"></div>
                </div>
                <span id="bulkProgressLabel" class="bulk-progress-label" style="font-size:.85rem;color:var(--text-muted);white-space:nowrap">0 von 0 geprüft</span>
                <button class="btn-cancel" onclick="cancelBulkCheck()">Abbrechen</button>
            </div>
            <div id="reviewTable" class="review-table"></div>
        </div>
    </div>
</section>

<!-- ── Footer ── -->
<footer class="site-footer">
    <div class="section-inner footer-inner">
        <img th:src="@{/images/logo.png}" alt="LibreDeck" class="footer-logo-img">
        <p>Keine Datenspeicherung · Kein Account erforderlich</p>
    </div>
</footer>

<!-- ── Bulk-check warning dialog ── -->
<div id="bulkCheckDialog" style="display:none" role="dialog" aria-modal="true" aria-labelledby="bulkCheckTitle">
    <div class="dialog-backdrop" onclick="document.getElementById('bulkCheckDialog').style.display='none'"></div>
    <div class="dialog-box">
        <h3 class="dialog-title" id="bulkCheckTitle">Alle Titel prüfen</h3>
        <p class="dialog-body">
            MusicBrainz wird für jeden Titel einzeln abgefragt – das dauert ca. 1–2 Sekunden pro Titel.
            Bei großen Playlists kann das mehrere Minuten in Anspruch nehmen.<br><br>
            Abweichende Jahre werden automatisch übernommen. Du kannst den Vorgang jederzeit abbrechen –
            bereits geprüfte Titel bleiben dann aktualisiert.
        </p>
        <div class="dialog-actions" style="gap:.75rem">
            <button class="btn-cancel" onclick="document.getElementById('bulkCheckDialog').style.display='none'">Abbrechen</button>
            <button class="btn btn-lg" onclick="startBulkCheck()">Fortfahren</button>
        </div>
    </div>
</div>

<!-- ── Deezer year-accuracy notice ── -->
<div id="deezerNotice" style="display:none" role="dialog" aria-modal="true" aria-labelledby="deezerNoticeTitle">
    <div class="dialog-backdrop"></div>
    <div class="dialog-box">
        <h3 class="dialog-title" id="deezerNoticeTitle">Hinweis zu Deezer-Jahreszahlen</h3>
        <p class="dialog-body">
            Die von Deezer gelieferten Erscheinungsjahre können gelegentlich ungenau sein –
            besonders bei remasterten oder neu veröffentlichten Versionen eines Titels.<br><br>
            Verwende den <strong>Prüfen</strong>-Button neben jedem Titel, um das Jahr
            über MusicBrainz zu verifizieren und bei Bedarf zu korrigieren.
        </p>
        <div class="dialog-actions">
            <button class="btn btn-lg" onclick="closeDeezerNotice()">Verstanden</button>
        </div>
    </div>
</div>

<!-- ── Loading overlay ── -->
<div id="loading-overlay" style="display:none" aria-live="polite">
    <div class="spinner"></div>
    <p id="overlayStatus">Tracks werden geladen…</p>
    <div id="progressWrap" style="display:none">
        <div class="progress-bar-bg">
            <div class="progress-bar-fill" id="progressFill"></div>
        </div>
        <p class="loading-sub" id="progressLabel"></p>
    </div>
    <button id="btnCancel" class="btn-cancel" onclick="cancelJob()">Abbrechen</button>
</div>

<script>
    // ── Unified service state ──
    const SERVICES  = ['Spotify', 'Tidal', 'Deezer', 'Apple Music', 'YouTube'];
    const GEN_IDS   = ['panelSpotify', 'panelTidal', 'panelDeezer', 'panelApple', 'panelYoutube'];
    const HOW_IDS   = ['howSpotify', 'howTidal', 'howDeezer', 'howApple', 'howYoutube'];
    const SVC_ICONS = ['https://open.spotify.com/favicon.ico', 'https://tidal.com/favicon.ico', 'https://www.deezer.com/favicon.ico', 'https://www.apple.com/favicon.ico', 'https://www.youtube.com/favicon.ico'];
    let currentService = 0;

    function updateTopbar() {
        document.getElementById('topbarServiceName').textContent = SERVICES[currentService];
        document.getElementById('topbarIcon').src = SVC_ICONS[currentService];
    }

    function animatePanels(outId, inId, dir) {
        const outEl = document.getElementById(outId);
        const inEl  = document.getElementById(inId);
        inEl.style.transition = 'none';
        inEl.style.transform  = `translateX(${dir > 0 ? '100%' : '-100%'})`;
        inEl.style.visibility = 'visible';
        requestAnimationFrame(() => requestAnimationFrame(() => {
            outEl.style.transform = `translateX(${dir > 0 ? '-100%' : '100%'})`;
            inEl.style.transition = '';
            inEl.style.transform  = 'translateX(0)';
        }));
        setTimeout(() => {
            outEl.style.visibility = 'hidden';
            outEl.style.transition = 'none';
        }, 360);
    }

    function switchService(dir) {
        const next = (currentService + dir + SERVICES.length) % SERVICES.length;
        animatePanels(HOW_IDS[currentService], HOW_IDS[next], dir);
        animatePanels(GEN_IDS[currentService], GEN_IDS[next], dir);
        currentService = next;
        updateTopbar();
        hideError();
    }

    function setService(idx) {
        if (idx === currentService) return;
        const dir = idx > currentService ? 1 : -1;
        animatePanels(HOW_IDS[currentService], HOW_IDS[idx], dir);
        animatePanels(GEN_IDS[currentService], GEN_IDS[idx], dir);
        currentService = idx;
        updateTopbar();
        hideError();
    }

    document.getElementById('btnTopbarPrev').addEventListener('click', () => switchService(-1));
    document.getElementById('btnTopbarNext').addEventListener('click', () => switchService(1));

    // ── Global touch swipe (service switch) ──
    let _tx = 0, _ty = 0, _tm = false, _tCarousel = false;
    document.addEventListener('touchstart', e => {
        _tx = e.touches[0].clientX;
        _ty = e.touches[0].clientY;
        _tm = false;
        _tCarousel = !!e.target.closest('.carousel-viewport');
    }, { passive: true });
    document.addEventListener('touchmove', e => {
        if (Math.abs(e.touches[0].clientX - _tx) > 8 ||
            Math.abs(e.touches[0].clientY - _ty) > 8) _tm = true;
    }, { passive: true });
    document.addEventListener('touchend', e => {
        if (!_tm || _tCarousel) return;
        _tm = false;
        const dx = e.changedTouches[0].clientX - _tx;
        const dy = e.changedTouches[0].clientY - _ty;
        if (Math.abs(dx) >= 60 && Math.abs(dx) > Math.abs(dy) * 1.5)
            switchService(dx < 0 ? 1 : -1);
    }, { passive: true });

    // ── Carousel engine ──────────────────────────────────────────────────────
    // Card widths are set purely via CSS container queries (cqw units),
    // so no JS layout calculation is needed here.

    const _carousels = {};

    function setupCarousel(id) {
        const viewport = document.querySelector('#' + id + 'Carousel .carousel-viewport');
        const track    = document.getElementById(id + 'Track');
        const prevBtn  = document.getElementById(id + 'Prev');
        const nextBtn  = document.getElementById(id + 'Next');
        if (!viewport || !track) return null;

        let offset = 0;

        function updateBtns() {
            const maxOff = Math.max(0, track.scrollWidth - viewport.offsetWidth);
            prevBtn.disabled = offset <= 0;
            nextBtn.disabled = offset >= maxOff - 1;
        }

        function shift(dir) {
            const vw     = viewport.offsetWidth;
            const maxOff = Math.max(0, track.scrollWidth - vw);
            offset = Math.max(0, Math.min(offset + dir * vw, maxOff));
            track.style.transform = `translateX(-${offset}px)`;
            updateBtns();
        }

        prevBtn.addEventListener('click', () => shift(-1));
        nextBtn.addEventListener('click', () => shift(1));

        // Carousel-local swipe (prevents global service-switch from firing)
        let cx = 0, cy = 0;
        viewport.addEventListener('touchstart', e => {
            cx = e.touches[0].clientX; cy = e.touches[0].clientY;
        }, { passive: true });
        viewport.addEventListener('touchend', e => {
            const dx = e.changedTouches[0].clientX - cx;
            const dy = e.changedTouches[0].clientY - cy;
            if (Math.abs(dx) >= 40 && Math.abs(dx) > Math.abs(dy) * 1.2) shift(dx < 0 ? 1 : -1);
        }, { passive: true });

        const api = {
            load(items, onSelect) {
                offset = 0;
                track.innerHTML = '';
                track.style.transform = '';
                if (!items.length) {
                    track.innerHTML = '<span style="color:var(--text-muted);font-size:.85rem">Keine Playlists gefunden.</span>';
                    prevBtn.disabled = nextBtn.disabled = true;
                    return;
                }
                items.forEach(pl => {
                    const card = document.createElement('div');
                    card.className = 'playlist-card';
                    card.innerHTML = (pl.coverUrl
                        ? '<img src="' + esc(pl.coverUrl) + '" alt="" loading="lazy">'
                        : '<div class="playlist-card-nocover">&#127925;</div>') +
                        '<span class="playlist-card-name">' + esc(pl.title) + '</span>';
                    card.addEventListener('click', () => onSelect(pl));
                    track.appendChild(card);
                });
                // Let the browser apply container-query widths, then check scroll range
                requestAnimationFrame(updateBtns);
            },
            error(msg) {
                track.innerHTML = '<span style="color:var(--text-muted);font-size:.85rem">' + esc(msg) + '</span>';
                prevBtn.disabled = nextBtn.disabled = true;
            }
        };
        _carousels[id] = api;
        return api;
    }

    // Init carousels (DOM is ready at script-parse time since script is at end of body)
    setupCarousel('spotify');
    setupCarousel('tidal');
    setupCarousel('apple');

    // ── Apple Music ──────────────────────────────────────────────────────────

    let appleDeveloperToken = null;

    async function initAppleMusic() {
        try {
            const cfgRes = await fetch('/apple/configured');
            const cfg    = await cfgRes.json();
            if (!cfg.configured) {
                document.getElementById('appleNotConfigured').style.display = '';
                return;
            }

            const tokenRes = await fetch('/apple/developer-token');
            if (!tokenRes.ok) { document.getElementById('appleNotConfigured').style.display = ''; return; }
            const tokenData = await tokenRes.json();
            appleDeveloperToken = tokenData.token;

            const connRes  = await fetch('/apple/connected');
            const connData = await connRes.json();
            if (connData.connected) {
                showApplePicker();
            } else {
                document.getElementById('appleConnectArea').style.display = '';
            }
        } catch (_) {
            document.getElementById('appleConnectArea').style.display = '';
        }
    }

    async function connectAppleMusic() {
        if (!appleDeveloperToken) return;

        // Ensure MusicKit JS is loaded
        if (typeof MusicKit === 'undefined') {
            const script = document.createElement('script');
            script.src = 'https://js-cdn.music.apple.com/musickit/v3/musickit.js';
            script.crossOrigin = 'anonymous';
            document.head.appendChild(script);
            await new Promise((resolve, reject) => {
                script.onload  = resolve;
                script.onerror = reject;
            });
        }

        try {
            const music = await MusicKit.configure({
                developerToken: appleDeveloperToken,
                app: { name: 'LibreDeck', build: '1.0' }
            });
            const userToken = await music.authorize();
            if (!userToken) { showError('Apple Music-Anmeldung abgebrochen.'); return; }

            await fetch('/apple/token', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ musicUserToken: userToken })
            });
            showApplePicker();
        } catch (e) {
            showError('Apple Music-Anmeldung fehlgeschlagen: ' + (e.message || e));
        }
    }

    async function disconnectAppleMusic() {
        await fetch('/apple/disconnect', { method: 'POST' });
        document.getElementById('applePickerSection').style.display = 'none';
        document.getElementById('appleConnectArea').style.display   = '';
    }

    async function showApplePicker() {
        document.getElementById('appleConnectArea').style.display = 'none';
        const ok = await loadApplePlaylists();
        if (!ok) document.getElementById('appleConnectArea').style.display = '';
    }

    async function loadApplePlaylists() {
        const section = document.getElementById('applePickerSection');
        const savedY  = window.scrollY;
        section.style.display = 'block';
        window.scrollTo({ top: savedY, behavior: 'instant' });
        _carousels.apple?.error('Lade Playlists\u2026');
        try {
            const res = await fetch('/apple/playlists');
            if (!res.ok) {
                _carousels.apple?.error('Fehler beim Laden (' + res.status + ').');
                return false;
            }
            const lists = await res.json();
            _carousels.apple?.load(lists, pl =>
                triggerGenerate('https://music.apple.com/library/playlists/' + pl.id));
            return true;
        } catch (e) {
            _carousels.apple?.error('Fehler beim Laden.');
            return false;
        }
    }

    function submitAppleUrl() {
        const url = document.getElementById('appleUrlInput').value.trim();
        if (!url) { document.getElementById('appleUrlInput').focus(); return; }
        triggerGenerate(url);
    }

    document.getElementById('appleUrlInput')?.addEventListener('keydown', e => {
        if (e.key === 'Enter') submitAppleUrl();
    });

    function submitYoutubeUrl() {
        const url = document.getElementById('youtubeUrlInput').value.trim();
        if (!url) { document.getElementById('youtubeUrlInput').focus(); return; }
        triggerGenerate(url);
    }

    document.getElementById('youtubeUrlInput')?.addEventListener('keydown', e => {
        if (e.key === 'Enter') submitYoutubeUrl();
    });

    // ── Connection checks on page load ──
    (async function () {
        const params    = new URLSearchParams(window.location.search);
        const spotifyUrl = params.get('spotifyUrl');
        const spotifyErr = params.get('spotifyError');
        const tidalUrl  = params.get('tidalUrl');
        const tidalErr  = params.get('tidalError');

        if (spotifyErr) {
            history.replaceState({}, '', window.location.pathname);
            showError('Spotify-Anmeldung fehlgeschlagen: ' + spotifyErr);
            return;
        }
        if (tidalErr) {
            history.replaceState({}, '', window.location.pathname);
            showError('Tidal-Anmeldung fehlgeschlagen: ' + tidalErr);
            return;
        }

        // Check Spotify
        try {
            const res  = await fetch('/spotify/connected');
            const data = await res.json();
            if (data.connected) showSpotifyPicker();
        } catch (_) { /* ignore */ }

        // Check Tidal
        try {
            const res  = await fetch('/tidal/connected');
            const data = await res.json();
            if (data.connected) showTidalPicker();
        } catch (_) { /* ignore */ }

        // Init Apple Music (checks configured + connected)
        initAppleMusic().catch(() => {});

        if (spotifyUrl) {
            history.replaceState({}, '', window.location.pathname);
            await triggerGenerate(spotifyUrl);
        } else if (tidalUrl) {
            history.replaceState({}, '', window.location.pathname);
            setService(1); // switch to Tidal
            await triggerGenerate(tidalUrl);
        }
    })();

    async function showSpotifyPicker() {
        document.getElementById('spotifyConnectArea').style.display = 'none';
        const ok = await loadSpotifyPlaylists();
        if (!ok) document.getElementById('spotifyConnectArea').style.display = '';
    }

    async function loadSpotifyPlaylists() {
        const section = document.getElementById('spotifyPickerSection');
        const savedY  = window.scrollY;
        section.style.display = 'block';
        window.scrollTo({ top: savedY, behavior: 'instant' });
        _carousels.spotify?.error('Lade Playlists\u2026');
        try {
            const res = await fetch('/spotify/playlists');
            if (res.status === 401) {
                section.style.display = 'none';
                document.getElementById('spotifyConnectArea').style.display = '';
                return false;
            }
            if (!res.ok) {
                _carousels.spotify?.error('Fehler beim Laden (' + res.status + ').');
                return false;
            }
            const lists = await res.json();
            _carousels.spotify?.load(lists, pl =>
                triggerGenerate('https://open.spotify.com/playlist/' + pl.id));
            return true;
        } catch (e) {
            _carousels.spotify?.error('Fehler beim Laden.');
            return false;
        }
    }

    function submitSpotifyUrl() {
        const url = document.getElementById('spotifyUrlInput').value.trim();
        if (!url) { document.getElementById('spotifyUrlInput').focus(); return; }
        triggerGenerate(url);
    }

    document.getElementById('spotifyUrlInput')?.addEventListener('keydown', e => {
        if (e.key === 'Enter') submitSpotifyUrl();
    });

    // ── Tidal picker ──

    async function showTidalPicker() {
        document.getElementById('tidalConnectArea').style.display = 'none';
        const ok = await loadTidalPlaylists();
        if (!ok) document.getElementById('tidalConnectArea').style.display = '';
    }

    async function loadTidalPlaylists() {
        const section = document.getElementById('tidalPickerSection');
        const savedY  = window.scrollY;
        section.style.display = 'block';
        window.scrollTo({ top: savedY, behavior: 'instant' });
        _carousels.tidal?.error('Lade Playlists\u2026');
        try {
            const res = await fetch('/tidal/playlists');
            if (!res.ok) {
                _carousels.tidal?.error('Fehler beim Laden (' + res.status + ').');
                return false;
            }
            const lists = await res.json();
            _carousels.tidal?.load(lists, pl =>
                triggerGenerate('https://tidal.com/browse/playlist/' + pl.id));
            return true;
        } catch (e) {
            _carousels.tidal?.error('Fehler beim Laden.');
            return false;
        }
    }

    function submitTidalUrl() {
        const url = document.getElementById('tidalUrlInput').value.trim();
        if (!url) { document.getElementById('tidalUrlInput').focus(); return; }
        triggerGenerate(url);
    }

    document.getElementById('tidalUrlInput')?.addEventListener('keydown', e => {
        if (e.key === 'Enter') submitTidalUrl();
    });

    // ── smooth scroll ──
    document.querySelectorAll('a[href="#generator"]').forEach(a => {
        a.addEventListener('click', e => {
            e.preventDefault();
            document.getElementById('generator').scrollIntoView({ behavior: 'smooth' });
        });
    });

    let activeJobId  = null;
    let jobCancelled = false;

    // ── step 1: lookup ──
    document.getElementById('btnGenerate').addEventListener('click', async () => {
        const url = document.getElementById('playlistUrl').value.trim();
        if (!url) { document.getElementById('playlistUrl').focus(); return; }
        await triggerGenerate(url);
    });

    async function triggerGenerate(url) {
        hideReview();
        hideError();
        jobCancelled = false;
        showOverlay();

        try {
            const res = await fetch('/generate/lookup?playlistUrl=' + encodeURIComponent(url));
            if (!res.ok) throw new Error('Server nicht erreichbar.');
            const data = await res.json();

            if (data.spotifyAuthRequired) {
                hideOverlay();
                setService(0);
                return;
            }
            if (data.tidalAuthRequired) {
                hideOverlay();
                setService(1);
                return;
            }
            if (data.appleMusicAuthRequired) {
                hideOverlay();
                setService(3); // switch to Apple Music panel
                return;
            }

            const { jobId } = data;
            activeJobId = jobId;

            const status = await pollUntilReviewOrDone(jobId);

            if (status.cancelled) return;
            if (status.phase === 'REVIEW_READY') {
                hideOverlay();
                if (currentService === 2) {
                    showDeezerNotice(() => showReview(status.tracks));
                } else {
                    showReview(status.tracks);
                }
            } else {
                triggerDownload(jobId);
            }

        } catch (err) {
            hideOverlay();
            showError(err.message || 'Ein unbekannter Fehler ist aufgetreten.');
        }
    }

    // ── step 2: confirm (after review) ──
    document.getElementById('btnConfirm').addEventListener('click', async () => {
        if (!activeJobId) return;

        const rows    = document.querySelectorAll('.review-row');
        const years   = {};
        const artists = {};
        const titles  = {};
        rows.forEach(row => {
            const id = row.dataset.id;
            years[id]   = (row.querySelector('.year-input')?.value ?? '').trim();
            artists[id] = (row.querySelector('.review-artist-input')?.value ?? '').trim();
            titles[id]  = (row.querySelector('.review-title-input')?.value ?? '').trim();
        });

        hideReview();
        hideError();
        jobCancelled = false;
        showOverlay();

        try {
            const res = await fetch('/generate/confirm', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ jobId: activeJobId, years, artists, titles })
            });
            if (!res.ok) throw new Error('Server nicht erreichbar.');

            const status = await pollUntilDone(activeJobId);
            if (status.cancelled) return;
            if (!status.ready) throw new Error(status.error || 'Unbekannter Fehler.');
            triggerDownload(activeJobId);

        } catch (err) {
            hideOverlay();
            showError(err.message || 'Ein unbekannter Fehler ist aufgetreten.');
        }
    });

    // ── polling helpers ──

    async function pollUntilReviewOrDone(jobId) {
        while (true) {
            await sleep(700);
            if (jobCancelled) return { cancelled: true };
            const res    = await fetch('/generate/status?jobId=' + encodeURIComponent(jobId));
            const status = await res.json();

            if (status.error) throw new Error(status.error);

            if (status.phase === 'FETCHING') {
                setStatus('Tracks werden geladen…'); hideProgress();
            } else if (status.phase === 'LOOKUP') {
                setStatus('Erscheinungsjahr wird ermittelt…');
                showProgress(status.done, status.total, 'Tracks geprüft');
            } else if (status.phase === 'GENERATING') {
                setStatus('Karten werden erstellt…');
                showProgress(status.done, status.total, 'Karten erstellt');
            }

            if (status.phase === 'REVIEW_READY' || status.ready) return status;
        }
    }

    async function pollUntilDone(jobId) {
        while (true) {
            await sleep(500);
            if (jobCancelled) return { cancelled: true };
            const res    = await fetch('/generate/status?jobId=' + encodeURIComponent(jobId));
            const status = await res.json();

            if (status.phase === 'GENERATING') {
                setStatus('Karten werden erstellt…');
                showProgress(status.done, status.total, 'Karten erstellt');
            }

            if (status.ready || status.error) return status;
        }
    }

    async function cancelJob() {
        jobCancelled = true;
        if (activeJobId) {
            fetch('/generate/cancel?jobId=' + encodeURIComponent(activeJobId), { method: 'POST' });
            activeJobId = null;
        }
        hideOverlay();
        hideReview();
    }

    function triggerDownload(jobId) {
        setStatus('Download startet…');
        window.location.href = '/generate/download?jobId=' + encodeURIComponent(jobId);
        setTimeout(hideOverlay, 1500);
    }

    // ── review section ──

    function showDeezerNotice(onClose) {
        const el = document.getElementById('deezerNotice');
        el.style.display = 'flex';
        el._onClose = onClose;
    }

    function closeDeezerNotice() {
        const el = document.getElementById('deezerNotice');
        el.style.display = 'none';
        if (typeof el._onClose === 'function') el._onClose();
    }

    function showReview(tracks) {
        const section = document.getElementById('reviewSection');
        const table   = document.getElementById('reviewTable');
        table.innerHTML = '';

        const isYouTube = currentService === 4;
        tracks.forEach(t => {
            const missing = !t.year;
            const row = document.createElement('div');
            row.className             = 'review-row' + (missing ? ' review-row--missing' : '');
            row.dataset.id            = t.id;
            row.dataset.streamingUrl  = t.streamingUrl ?? '';
            const swapBtn = isYouTube
                ? '<button class="btn-swap" title="Künstler ↔ Titel tauschen" onclick="swapArtistTitle(this)">⇄</button>'
                : '';
            row.innerHTML =
                '<input type="text" class="review-artist-input url-input" value="' + esc(t.artist) + '" placeholder="Künstler">' +
                swapBtn +
                '<input type="text" class="review-title-input url-input" value="' + esc(t.title) + '" placeholder="Titel">' +
                '<input type="text" class="year-input url-input' + (missing ? ' year-input--missing' : '') + '"' +
                '       maxlength="4" placeholder="Jahr" pattern="[0-9]{4}" inputmode="numeric"' +
                '       value="' + esc(t.year) + '">' +
                '<div class="check-cell">' +
                '  <button class="btn-check" onclick="checkYear(this)">Prüfen</button>' +
                '  <span class="year-suggestion" style="display:none">' +
                '    <span class="suggestion-year"></span>' +
                '    <button class="suggestion-accept" title="Übernehmen">✓</button>' +
                '    <button class="suggestion-reject" title="Behalten">✗</button>' +
                '  </span>' +
                '</div>';
            table.appendChild(row);
        });

        section.style.display = 'block';
        section.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }

    function swapArtistTitle(btn) {
        const row    = btn.closest('.review-row');
        const aInput = row.querySelector('.review-artist-input');
        const tInput = row.querySelector('.review-title-input');
        const tmp    = aInput.value;
        aInput.value = tInput.value;
        tInput.value = tmp;
    }

    async function checkYear(btn) {
        const row        = btn.closest('.review-row');
        const artist     = row.querySelector('.review-artist-input').value.trim();
        const title      = row.querySelector('.review-title-input').value.trim();
        const input      = row.querySelector('.year-input');
        const suggestion = row.querySelector('.year-suggestion');
        const sugYear    = row.querySelector('.suggestion-year');

        btn.disabled    = true;
        btn.textContent = '…';
        suggestion.style.display = 'none';

        try {
            const res  = await fetch('/generate/check-year?artist=' + encodeURIComponent(artist)
                                     + '&title=' + encodeURIComponent(title));
            const data = await res.json();
            const mbYear = data.year;

            if (!mbYear) {
                btn.textContent = 'Nicht gefunden';
                setTimeout(() => { btn.textContent = 'Prüfen'; btn.disabled = false; }, 2000);
                return;
            }

            if (mbYear === input.value.trim()) {
                row.classList.remove('review-row--missing');
                input.classList.remove('year-input--missing');
                btn.textContent = '✓ Bestätigt';
                setTimeout(() => { btn.textContent = 'Prüfen'; btn.disabled = false; }, 2000);
                return;
            }

            // different year – show inline suggestion
            sugYear.textContent = mbYear;
            suggestion.style.display = 'flex';

            const accept = row.querySelector('.suggestion-accept');
            const reject = row.querySelector('.suggestion-reject');

            // replace listeners to avoid duplicates
            const freshAccept = accept.cloneNode(true);
            const freshReject = reject.cloneNode(true);
            accept.replaceWith(freshAccept);
            reject.replaceWith(freshReject);

            freshAccept.addEventListener('click', () => {
                input.value = mbYear;
                input.classList.remove('year-input--missing');
                row.classList.remove('review-row--missing');
                suggestion.style.display = 'none';
                btn.textContent = 'Prüfen';
                btn.disabled = false;
            });
            freshReject.addEventListener('click', () => {
                suggestion.style.display = 'none';
                btn.textContent = 'Prüfen';
                btn.disabled = false;
            });

        } catch (e) {
            btn.textContent = 'Fehler';
            setTimeout(() => { btn.textContent = 'Prüfen'; btn.disabled = false; }, 2000);
        }
    }

    let bulkCheckCancelled = false;

    function startBulkCheck() {
        document.getElementById('bulkCheckDialog').style.display = 'none';
        bulkCheckCancelled = false;

        const rows = Array.from(document.querySelectorAll('.review-row'));
        document.getElementById('reviewActionButtons').style.display = 'none';
        const progress = document.getElementById('bulkProgress');
        progress.style.display = 'flex';
        updateBulkProgress(0, rows.length);

        (async () => {
            let done = 0;
            for (const row of rows) {
                if (bulkCheckCancelled) break;
                const artist = row.querySelector('.review-artist-input').value.trim();
                const title  = row.querySelector('.review-title-input').value.trim();
                const input  = row.querySelector('.year-input');
                try {
                    const res  = await fetch('/generate/check-year?artist=' + encodeURIComponent(artist)
                                             + '&title=' + encodeURIComponent(title));
                    const data = await res.json();
                    const current = input.value.trim();
                    const isOlder = data.year && (!current || data.year < current);
                    if (isOlder) {
                        input.value = data.year;
                        input.classList.remove('year-input--missing');
                        row.classList.add('review-row--updated');
                    }
                } catch (e) { /* skip */ }
                updateBulkProgress(++done, rows.length);
            }
            progress.style.display = 'none';
            document.getElementById('reviewActionButtons').style.display = 'flex';
        })();
    }

    function cancelBulkCheck() {
        bulkCheckCancelled = true;
    }

    function updateBulkProgress(done, total) {
        const fill  = document.getElementById('bulkProgressFill');
        const label = document.getElementById('bulkProgressLabel');
        const pct   = total > 0 ? Math.round(done / total * 100) : 0;
        fill.style.width    = pct + '%';
        label.textContent   = done + ' von ' + total + ' geprüft';
    }

    function hideReview() {
        document.getElementById('reviewSection').style.display = 'none';
    }

    // ── UI helpers ──

    function showProgress(done, total, label) {
        const wrap  = document.getElementById('progressWrap');
        const fill  = document.getElementById('progressFill');
        const lbl   = document.getElementById('progressLabel');
        wrap.style.display = 'block';
        const pct = total > 0 ? Math.round(done / total * 100) : 0;
        fill.style.width = pct + '%';
        lbl.textContent  = done + ' von ' + total + ' ' + label;
    }

    function hideProgress() {
        document.getElementById('progressWrap').style.display = 'none';
    }

    function setStatus(msg) {
        document.getElementById('overlayStatus').textContent = msg;
    }

    function showOverlay() {
        setStatus('Tracks werden geladen…');
        hideProgress();
        document.getElementById('loading-overlay').style.display = 'flex';
    }

    function hideOverlay() {
        document.getElementById('loading-overlay').style.display = 'none';
    }

    function showError(msg) {
        const el = document.getElementById('errorMsg');
        el.textContent = msg;
        el.style.display = 'block';
        document.getElementById('generator').scrollIntoView({ behavior: 'smooth' });
    }

    function hideError() {
        document.getElementById('errorMsg').style.display = 'none';
    }

    function esc(str) {
        if (!str) return '';
        return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }

    function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

    // ── Play dialog ──────────────────────────────────────────────────────────

    let _playDeck      = [];   // remaining cards this round
    let _playFlipped   = false;
    let _currentTrack  = null;

    document.getElementById('btnPlay').addEventListener('click', () => {
        const tracks = collectConfirmedTracks();
        if (!tracks.length) return;
        _playDeck    = shuffle([...tracks]);
        openPlayDialog();
    });

    function collectConfirmedTracks() {
        const rows = document.querySelectorAll('.review-row');
        const out  = [];
        rows.forEach(row => {
            const year = (row.querySelector('.year-input')?.value ?? '').trim();
            if (!year) return;
            out.push({
                artist:      (row.querySelector('.review-artist-input')?.value ?? '').trim(),
                title:       (row.querySelector('.review-title-input')?.value ?? '').trim(),
                year,
                streamingUrl: row.dataset.streamingUrl ?? ''
            });
        });
        return out;
    }

    function openPlayDialog() {
        document.getElementById('playDialog').style.display = 'flex';
        drawNextCard();
    }

    function closePlayDialog() {
        document.getElementById('playDialog').style.display = 'none';
        _playDeck    = [];
        _currentTrack = null;
    }

    function drawNextCard() {
        if (!_playDeck.length) {
            showPlayEmpty();
            return;
        }
        _currentTrack = _playDeck.pop();
        _playFlipped  = false;
        showPlayFront(_currentTrack);
    }

    function showPlayFront(track) {
        const card = document.getElementById('playCard');
        card.classList.remove('flipped');
        document.getElementById('btnFlip').style.display = '';
        document.getElementById('btnNext').style.display = 'none';

        // Generate QR code after flip resets
        setTimeout(() => {
            const qrEl  = document.getElementById('playQr');
            qrEl.innerHTML = '';
            if (track.streamingUrl) {
                const img = document.createElement('img');
                img.src = '/generate/qr?url=' + encodeURIComponent(track.streamingUrl);
                img.alt = 'QR';
                qrEl.appendChild(img);
            } else {
                qrEl.textContent = '(kein Link)';
            }
            document.getElementById('playRemaining').textContent =
                _playDeck.length + 1 + ' / ' + (document.querySelectorAll('.review-row').length) + ' Karten';
        }, 50);

        document.getElementById('playEmpty').style.display = 'none';
        document.getElementById('playCardWrap').style.display = 'block';
    }

    function flipCard() {
        if (_playFlipped) return;
        _playFlipped = true;
        document.getElementById('playCard').classList.add('flipped');
        document.getElementById('btnFlip').style.display = 'none';
        document.getElementById('btnNext').style.display = '';
        // Fill back face
        document.getElementById('playBackArtist').textContent = _currentTrack.artist;
        document.getElementById('playBackTitle').textContent  = _currentTrack.title;
        document.getElementById('playBackYear').textContent   = _currentTrack.year;
    }

    function showPlayEmpty() {
        document.getElementById('playCardWrap').style.display = 'none';
        document.getElementById('playEmpty').style.display    = 'block';
    }

    function restartRound() {
        const tracks = collectConfirmedTracks();
        _playDeck    = shuffle([...tracks]);
        drawNextCard();
    }

    function shuffle(arr) {
        for (let i = arr.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [arr[i], arr[j]] = [arr[j], arr[i]];
        }
        return arr;
    }
</script>

<!-- ── Play dialog ── -->
<div id="playDialog" style="display:none;position:fixed;inset:0;z-index:500;
     background:rgba(8,13,14,.92);backdrop-filter:blur(10px);
     align-items:center;justify-content:center;padding:1rem;">

    <div class="play-dialog-inner">
        <button class="play-close" onclick="closePlayDialog()">✕</button>
        <p class="play-remaining" id="playRemaining"></p>

        <!-- card flip wrapper -->
        <div id="playCardWrap">
            <div id="playCard" class="play-card">
                <!-- front: QR code -->
                <div class="play-face play-front">
                    <div id="playQr" class="play-qr"></div>
                </div>
                <!-- back: solution -->
                <div class="play-face play-back">
                    <p class="play-back-year"   id="playBackYear"></p>
                    <hr class="play-back-divider">
                    <p class="play-back-artist" id="playBackArtist"></p>
                    <p class="play-back-title"  id="playBackTitle"></p>
                    <span class="play-brand">LibreDeck</span>
                </div>
            </div>
            <div class="play-actions">
                <button class="btn btn-lg" id="btnFlip" onclick="flipCard()">Auflösen</button>
                <button class="btn btn-lg" id="btnNext" onclick="drawNextCard()" style="display:none">Nächste Karte</button>
            </div>
        </div>

        <!-- round finished -->
        <div id="playEmpty" style="display:none;text-align:center">
            <p style="color:var(--text-muted);font-size:1.05rem;margin-bottom:1.5rem">
                Alle Karten dieser Runde gespielt!
            </p>
            <button class="btn btn-lg" onclick="restartRound()">Neue Runde</button>
        </div>
    </div>
</div>
</body>
</html>
