freedify/static/index.html

779 lines
41 KiB
HTML
Raw Permalink Normal View History

2026-01-13 22:26:48 +00:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
<meta name="theme-color" content="#1a1a2e">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Freedify</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/static/icon.png" type="image/png">
<link rel="apple-touch-icon" href="/static/icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@400;700;900&family=Permanent+Marker&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/styles.css?t=1736053100">
<!-- Google API for Drive sync -->
<script src="https://accounts.google.com/gsi/client" async defer></script>
<script src="https://apis.google.com/js/api.js" async defer></script>
<!-- Butterchurn (MilkDrop WebGL visualizer) -->
<script src="https://cdn.jsdelivr.net/npm/butterchurn@2.6.7/lib/butterchurn.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/butterchurn-presets@2.4.7/lib/butterchurnPresets.min.js"></script>
</head>
<body>
<!-- Toast Container -->
<div id="toast-container" class="toast-container"></div>
<!-- Keyboard Shortcuts Help -->
<div id="shortcuts-help" class="shortcuts-help hidden">
<div class="shortcuts-content">
<div class="shortcuts-header">
<h3>⌨️ Keyboard Shortcuts</h3>
<button id="shortcuts-close" class="shortcuts-close">×</button>
</div>
<div class="shortcuts-grid">
<div class="shortcut"><kbd>Space</kbd><span>Play/Pause</span></div>
<div class="shortcut"><kbd></kbd><span>Next Track</span></div>
<div class="shortcut"><kbd></kbd><span>Previous Track</span></div>
<div class="shortcut"><kbd>Shift + →</kbd><span>Seek +10s</span></div>
<div class="shortcut"><kbd>Shift + ←</kbd><span>Seek -10s</span></div>
<div class="shortcut"><kbd></kbd><span>Volume Up</span></div>
<div class="shortcut"><kbd></kbd><span>Volume Down</span></div>
<div class="shortcut"><kbd>M</kbd><span>Mute/Unmute</span></div>
<div class="shortcut"><kbd>S</kbd><span>Shuffle Queue</span></div>
<div class="shortcut"><kbd>R</kbd><span>Toggle Repeat</span></div>
<div class="shortcut"><kbd>F</kbd><span>Fullscreen</span></div>
<div class="shortcut"><kbd>Q</kbd><span>Toggle Queue</span></div>
<div class="shortcut"><kbd>E</kbd><span>Toggle EQ</span></div>
<div class="shortcut"><kbd>P</kbd><span>Add to Playlist</span></div>
<div class="shortcut"><kbd>H</kbd><span>HiFi/Hi-Res</span></div>
<div class="shortcut"><kbd>D</kbd><span>Download Track</span></div>
<div class="shortcut"><kbd>A</kbd><span>AI Radio</span></div>
<div class="shortcut"><kbd>L</kbd><span>Lyrics</span></div>
<div class="shortcut"><kbd>V</kbd><span>Music Video</span></div>
<div class="shortcut"><kbd>Shift + S</kbd><span>Sync to Drive</span></div>
<div class="shortcut"><kbd>?</kbd><span>This Help</span></div>
</div>
</div>
</div>
<div class="app-container">
<!-- Header -->
<header class="header">
<h1 class="logo"><span class="brand-text">Freedify</span></h1>
<div class="header-actions">
<button id="hifi-btn" class="header-btn" title="HiFi Mode - Stream lossless FLAC (faster startup, more data)">HiFi</button>
<label for="file-input" class="header-btn" title="Add Local Songs (MP3/FLAC)" style="cursor: pointer; display: inline-flex; align-items: center; justify-content: center;">📂</label>
<button id="dj-mode-btn" class="header-btn" title="DJ Mode">🎧</button>
<button id="sync-btn" class="header-btn" title="Sync with Google Drive">☁️</button>
<button id="theme-btn" class="theme-btn" title="Change Theme">🎨</button>
</div>
</header>
<!-- Theme Picker Dropdown -->
<div id="theme-picker" class="theme-picker hidden">
<div class="theme-option" data-theme="">🌙 Default</div>
<div class="theme-option" data-theme="theme-purple">💜 Purple</div>
<div class="theme-option" data-theme="theme-blue">💙 Blue</div>
<div class="theme-option" data-theme="theme-green">💚 Green</div>
<div class="theme-option" data-theme="theme-pink">💕 Pink</div>
<div class="theme-option" data-theme="theme-orange">🧡 Orange</div>
</div>
<!-- Search Section -->
<section class="search-section">
<div class="search-container">
<input
type="text"
id="search-input"
class="search-input"
placeholder="Search music or paste ANY link (Spotify, Bandcamp, SoundCloud...)"
autocomplete="off"
title="Search or Import"
>
<button id="search-clear" class="search-clear" title="Clear search">×</button>
</div>
<!-- Search Type Selector -->
<div class="search-type-selector">
<button class="type-btn active" data-type="track">Songs</button>
<button class="type-btn" data-type="album">Albums</button>
<button class="type-btn" data-type="artist">Artists</button>
<button class="type-btn" data-type="podcast">Podcasts</button>
<button id="search-more-btn" class="type-btn more-btn">⋮ More</button>
</div>
<!-- Search More Menu -->
<div id="search-more-menu" class="search-more-menu hidden">
<button class="menu-item" id="ai-menu-btn">Smart Playlist</button>
<button class="menu-item" id="concert-search-menu-btn">Concert Search</button>
<div class="menu-divider"></div>
<button class="menu-item type-btn-menu" data-type="ytmusic">YT Music</button>
<button class="menu-item type-btn-menu" data-type="setlist">Setlists</button>
<button class="menu-item type-btn-menu" data-type="rec">For You</button>
<button class="menu-item type-btn-menu" data-type="favorites">Playlists</button>
</div>
</section>
<!-- Loading Overlay -->
<div id="loading-overlay" class="loading-overlay hidden">
<div class="loading-content">
<div class="spinner"></div>
<p id="loading-text">Loading...</p>
</div>
</div>
<!-- Error Message -->
<div id="error-message" class="error-message hidden">
<span class="error-icon">😕</span>
<p id="error-text">Something went wrong</p>
<button id="error-retry" class="btn-retry">Try Again</button>
</div>
<!-- Download Modal -->
<div id="download-modal" class="modal hidden">
<div class="modal-content">
<h3>Download Track</h3>
<p id="download-track-name">Track Name</p>
<p id="download-source-hint" class="download-hint hidden"></p>
<div class="format-selector">
<label>Select Format:</label>
<select id="download-format">
<optgroup label="Lossy">
<option value="mp3" data-min-quality="lossy">MP3 (320kbps)</option>
</optgroup>
<optgroup label="Lossless (16-bit)">
<option value="flac" data-min-quality="lossless">FLAC (16-bit)</option>
<option value="aiff" data-min-quality="lossless">AIFF (16-bit)</option>
<option value="wav" data-min-quality="lossless">WAV (16-bit)</option>
<option value="alac" data-min-quality="lossless">ALAC (Apple Lossless)</option>
</optgroup>
<optgroup id="hires-formats" label="Hi-Res (24-bit)">
<option value="flac_24" data-min-quality="hires">FLAC (24-bit Hi-Res)</option>
<option value="aiff_24" data-min-quality="hires">AIFF (24-bit)</option>
<option value="wav_24" data-min-quality="hires">WAV (24-bit)</option>
</optgroup>
</select>
</div>
<div class="modal-actions">
<button id="download-cancel-btn" class="btn-secondary">Cancel</button>
<button id="download-drive-btn" class="btn-secondary" title="Save to Google Drive">☁️ Save to Drive</button>
<button id="download-confirm-btn" class="btn-primary">Download</button>
</div>
</div>
</div>
<!-- Results Section -->
<section id="results-section" class="results-section">
<div id="results-container" class="results-container">
<!-- Search results will be inserted here -->
<div class="empty-state">
<span class="empty-icon">🔍</span>
<p>Search for your favorite music</p>
<p class="hint">Or paste a Spotify link to an album or playlist</p>
</div>
</div>
<button id="load-more-btn" class="load-more-btn hidden">Load More Results</button>
</section>
<!-- Album/Playlist Detail View -->
<section id="detail-view" class="detail-view hidden">
<div class="detail-header">
<button id="back-btn" class="back-btn">← Back</button>
<div class="detail-actions">
<button id="shuffle-btn" class="shuffle-btn" title="Shuffle & Play">Shuffle</button>
<button id="queue-all-btn" class="queue-all-btn">Add All to Queue</button>
<button id="download-all-btn" class="download-all-btn">Download ZIP</button>
</div>
</div>
<div id="detail-info" class="detail-info">
<!-- Album/playlist info will be inserted here -->
</div>
<div id="detail-tracks" class="detail-tracks">
<!-- Tracks will be inserted here -->
</div>
</section>
<!-- Queue Section -->
<section id="queue-section" class="queue-section hidden">
<div class="queue-header">
<h3>Queue <span id="queue-count">(0)</span></h3>
<div class="queue-controls">
<button id="queue-clear" class="queue-clear">Clear</button>
<button id="queue-close" class="queue-close-btn" title="Close Queue">×</button>
</div>
</div>
<!-- Crossfade Toggle -->
<div class="crossfade-toggle">
<span class="crossfade-icon"></span>
<div class="crossfade-info">
<span class="crossfade-title">1s Crossfade</span>
<span class="crossfade-desc">Smooth transition between tracks</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="crossfade-checkbox">
<span class="toggle-slider"></span>
</label>
</div>
<div id="queue-container" class="queue-container">
<!-- Queue items will be inserted here -->
</div>
</section>
<!-- Album Details Modal -->
<div id="album-modal" class="album-modal hidden">
<div class="album-modal-overlay"></div>
<div class="album-modal-content">
<div class="album-modal-header">
<button id="album-modal-close" class="album-modal-close" title="Close">×</button>
<h2>Album Details</h2>
</div>
<div class="album-modal-body">
<div class="album-modal-info">
<img id="album-modal-art" class="album-modal-art" src="" alt="Album Art">
<div class="album-modal-meta">
<h3 id="album-modal-title" class="album-modal-title">Album Title</h3>
<p id="album-modal-artist" class="album-modal-artist">Artist Name</p>
<!-- Metadata Pills -->
<div class="album-meta-pills">
<span id="album-modal-date" class="meta-pill">📅 2024-01-01</span>
<span id="album-modal-trackcount" class="meta-pill">🎵 12 tracks</span>
<span id="album-modal-duration" class="meta-pill">⏱️ 45 min</span>
</div>
<!-- Action Buttons -->
<div class="album-action-buttons">
<button id="album-play-btn" class="album-action-btn primary">▶ Play Album</button>
<button id="album-queue-btn" class="album-action-btn">+ Add to Queue</button>
<button id="album-download-btn" class="album-action-btn">⬇ Download Album</button>
<button id="album-playlist-btn" class="album-action-btn">♡ Add to Playlist</button>
</div>
<!-- Quality Badge -->
<div id="album-modal-quality" class="album-quality-badge">
🎵 FLAC • 16bit / 44.1kHz
</div>
</div>
</div>
<!-- Tabs -->
<div class="album-tabs">
<button class="album-tab active" data-tab="tracks">Tracks</button>
<button class="album-tab" data-tab="info">Album Info</button>
</div>
<!-- Track List -->
<div id="album-modal-tracks" class="album-modal-tracks">
<!-- Tracks will be inserted here -->
</div>
<!-- Album Info (hidden by default) -->
<div id="album-modal-info-tab" class="album-modal-info-tab hidden">
<p id="album-modal-description">Album description and additional info...</p>
</div>
</div>
</div>
</div>
<!-- AI Assistant Modal -->
<div id="ai-modal" class="ai-modal hidden">
<div class="ai-modal-overlay"></div>
<div class="ai-modal-content">
<div class="ai-modal-header">
<h2>🧠 Smart Playlist</h2>
<button id="ai-modal-close" class="ai-modal-close" title="Close">×</button>
</div>
<!-- Playlist Generator Content -->
<div class="ai-tab-content active">
<p class="ai-tab-desc">Describe your perfect playlist and I'll create it...</p>
<textarea id="ai-playlist-input" class="ai-input" placeholder="e.g., 'A 30-minute morning coffee playlist with jazz and bossa nova'" rows="3"></textarea>
<div class="ai-duration-row">
<label>Duration:</label>
<input type="range" id="ai-duration-slider" min="15" max="120" value="60" step="15">
<span id="ai-duration-label">60 min</span>
</div>
<button id="ai-playlist-gen-btn" class="ai-action-btn">🎵 Generate Playlist</button>
<div id="ai-playlist-results" class="ai-results"></div>
</div>
</div>
</div>
<!-- Bottom Player -->
<div id="player-bar" class="player-bar hidden">
<!-- Main visible row (Art + Info + Primary Controls) -->
<div class="player-main-row">
<div class="player-info">
<img id="player-art" class="player-art" src="" alt="Album art">
<div class="player-details">
<p id="player-title" class="player-title">No track playing</p>
<div class="player-meta-row">
<span id="player-artist" class="player-artist clickable" title="Search artist">-</span>
<span class="player-separator"></span>
<span id="player-album" class="player-album clickable" title="View album">-</span>
<span id="player-year" class="player-year"></span>
<span id="audio-format-badge" class="audio-format-badge hidden">MP3</span>
</div>
</div>
</div>
<div class="player-controls-primary">
<button id="prev-btn" class="control-btn" title="Previous"></button>
<button id="play-btn" class="control-btn play-btn" title="Play/Pause"></button>
<button id="next-btn" class="control-btn" title="Next"></button>
<button id="fs-toggle-btn" class="control-btn" title="Full Screen"></button>
<button id="more-controls-btn" class="control-btn" title="More Options"></button>
</div>
</div>
<!-- Progress Bar -->
<div class="player-progress">
<span id="current-time" class="time">0:00</span>
<input type="range" id="progress-bar" class="progress-bar" min="0" max="100" value="0" title="Seek">
<span id="duration" class="time">0:00</span>
</div>
<!-- More Menu (Popup) - 2x5 Grid -->
<div id="player-more-menu" class="player-more-menu hidden">
<div class="more-menu-grid four-col">
<!-- Row 1 -->
<button id="shuffle-queue-btn" class="control-btn" title="Shuffle Queue"></button>
<button id="repeat-btn" class="control-btn" title="Repeat: Off"></button>
<button id="download-current-btn" class="control-btn" title="Download"></button>
<button id="queue-btn" class="control-btn queue-toggle" title="Queue"></button>
<!-- Row 2 -->
<button id="mute-btn" class="control-btn volume-btn" title="Mute">🔊</button>
<button id="eq-toggle-btn" class="control-btn eq-btn" title="Equalizer">🎛️</button>
<button id="ai-radio-btn" class="control-btn ai-radio-btn" title="AI Radio">📻</button>
<button id="mini-player-btn" class="control-btn" title="Popout Winamp Player"></button>
<!-- Row 3 -->
<button id="add-to-playlist-btn" class="control-btn" title="Add to Playlist">🩷</button>
<button id="lyrics-btn" class="control-btn lyrics-btn" title="Lyrics">📝</button>
<button id="video-btn" class="control-btn" title="Music Video">🎬</button>
<button id="menu-visualizer-btn" class="control-btn" title="Visualizer">🌈</button>
</div>
<!-- Volume slider inside menu for mobile compactness -->
<div class="more-menu-volume">
<input type="range" id="volume-slider" class="volume-slider" min="0" max="100" value="100" title="Volume">
</div>
</div>
</div>
<!-- Equalizer Panel -->
<div id="eq-panel" class="eq-panel hidden">
<div class="eq-header">
<h3>🎛️ Equalizer</h3>
<button id="eq-close-btn" class="eq-close-btn">×</button>
</div>
<div class="eq-presets">
<button class="eq-preset active" data-preset="flat">Flat</button>
<button class="eq-preset" data-preset="bass">Bass Boost</button>
<button class="eq-preset" data-preset="treble">Treble</button>
<button class="eq-preset" data-preset="vocal">Vocal</button>
</div>
<div class="eq-sliders">
<div class="eq-band">
<input type="range" id="eq-60" class="eq-slider" min="-12" max="12" value="0" orient="vertical">
<span class="eq-label">60Hz</span>
</div>
<div class="eq-band">
<input type="range" id="eq-230" class="eq-slider" min="-12" max="12" value="0">
<span class="eq-label">230Hz</span>
</div>
<div class="eq-band">
<input type="range" id="eq-910" class="eq-slider" min="-12" max="12" value="0">
<span class="eq-label">910Hz</span>
</div>
<div class="eq-band">
<input type="range" id="eq-3600" class="eq-slider" min="-12" max="12" value="0">
<span class="eq-label">3.6kHz</span>
</div>
<div class="eq-band">
<input type="range" id="eq-7500" class="eq-slider" min="-12" max="12" value="0">
<span class="eq-label">7.5kHz</span>
</div>
</div>
<div class="eq-extras">
<div class="eq-extra">
<label for="bass-boost">Bass Boost</label>
<input type="range" id="bass-boost" class="eq-boost-slider" min="0" max="12" value="0">
<span id="bass-boost-val">0dB</span>
</div>
<div class="eq-extra">
<label for="volume-boost">Volume Boost</label>
<input type="range" id="volume-boost" class="eq-boost-slider" min="0" max="6" value="0">
<span id="volume-boost-val">0dB</span>
</div>
</div>
</div>
<!-- Audio Elements (dual for gapless/crossfade) -->
<audio id="audio-player" preload="auto"></audio>
<audio id="audio-player-2" preload="auto"></audio>
</div>
<!-- Fullscreen Player Overlay (outside .app for proper fixed positioning) -->
<div id="fullscreen-player" class="fullscreen-player hidden">
<div class="fs-backdrop"></div>
<div class="fs-content">
<div class="fs-header">
<button id="fs-close-btn" class="fs-close-btn">×</button>
</div>
<div class="fs-art-container">
<img id="fs-art" src="/static/icon.svg" alt="Album Art">
<button id="fs-lyrics-btn" class="fs-art-lyrics-btn" title="Lyrics">📝</button>
</div>
<div class="fs-info">
<h2 id="fs-title">No Track Playing</h2>
<p id="fs-artist">Select music to play</p>
<div id="fs-dj-info" class="fs-dj-info hidden"></div>
</div>
<div class="fs-progress-container">
<span id="fs-current-time">0:00</span>
<input type="range" id="fs-progress-bar" class="progress-bar" min="0" max="100" value="0" title="Seek">
<span id="fs-duration">0:00</span>
</div>
<div class="fs-controls">
<button id="fs-heart-btn" class="fs-control-btn" title="Add to Playlist">🩷</button>
<button id="fs-prev-btn" class="fs-control-btn"></button>
<button id="fs-play-btn" class="fs-control-btn play-btn"></button>
<button id="fs-next-btn" class="fs-control-btn"></button>
<button id="fs-download-btn" class="fs-control-btn" title="Download"></button>
<button id="fs-visualizer-btn" class="fs-control-btn" title="Visualizer">🌈</button>
</div>
</div>
</div>
<!-- Visualizer Overlay -->
<div id="visualizer-overlay" class="visualizer-overlay hidden">
<canvas id="visualizer-canvas"></canvas>
<canvas id="visualizer-canvas-webgl" class="hidden"></canvas>
<div class="visualizer-controls">
<div class="visualizer-track-info">
<span id="viz-track-name">No Track</span>
<span id="viz-track-artist"></span>
</div>
<div class="visualizer-mode-selector">
<button id="viz-prev-preset" class="viz-action-btn" title="Previous Preset (P)" style="display: none;">⏮ Prev</button>
<button class="viz-mode-btn" data-mode="milkdrop">MilkDrop</button>
<button id="viz-next-preset" class="viz-action-btn" title="Next Preset (N)" style="display: none;">Next ⏭</button>
<button class="viz-mode-btn active" data-mode="bars">Bars</button>
<button class="viz-mode-btn" data-mode="wave">Wave</button>
<button class="viz-mode-btn" data-mode="particles">Particles</button>
</div>
<button id="visualizer-close" class="visualizer-close-btn">✕ Exit</button>
</div>
<div class="visualizer-hint">Press ESC or click to exit</div>
</div>
<!-- Podcast Episode Details Modal -->
<div id="podcast-modal" class="podcast-modal hidden">
<div class="podcast-modal-content">
<button id="podcast-modal-close" class="podcast-modal-close">×</button>
<img id="podcast-modal-art" class="podcast-modal-art" src="" alt="Episode Art">
<h2 id="podcast-modal-title" class="podcast-modal-title"></h2>
<p id="podcast-modal-date" class="podcast-modal-date"></p>
<p id="podcast-modal-duration" class="podcast-modal-duration"></p>
<div id="podcast-modal-description" class="podcast-modal-description"></div>
<div class="podcast-modal-actions">
<button id="podcast-modal-play" class="podcast-modal-play">▶ Play Episode</button>
</div>
</div>
</div>
<!-- Lyrics Modal -->
<div id="lyrics-modal" class="lyrics-modal hidden">
<div class="lyrics-modal-content">
<button id="lyrics-modal-close" class="lyrics-modal-close">×</button>
<div class="lyrics-modal-header">
<img id="lyrics-modal-art" class="lyrics-modal-art" src="" alt="Album Art">
<div class="lyrics-modal-info">
<h2 id="lyrics-modal-title">Song Title</h2>
<p id="lyrics-modal-artist">Artist</p>
<p id="lyrics-modal-album" class="lyrics-modal-album"></p>
</div>
</div>
<div class="lyrics-tabs">
<button class="lyrics-tab active" data-tab="lyrics">Lyrics</button>
<button class="lyrics-tab" data-tab="about">About</button>
<button class="lyrics-tab" data-tab="annotations">Annotations</button>
</div>
<div class="lyrics-tab-content">
<div id="lyrics-panel" class="lyrics-panel active">
<div id="lyrics-loading" class="lyrics-loading hidden">
<div class="lyrics-spinner"></div>
<p>Fetching lyrics...</p>
</div>
<div id="lyrics-text" class="lyrics-text"></div>
<div id="lyrics-not-found" class="lyrics-not-found hidden">
<p>😢 Lyrics not found for this track</p>
<a id="lyrics-search-link" href="#" target="_blank" class="lyrics-search-link">Search on Genius →</a>
</div>
</div>
<div id="about-panel" class="lyrics-panel">
<div id="about-content" class="about-content">
<div id="about-description" class="about-description"></div>
<div id="about-credits" class="about-credits">
<p id="about-release"></p>
<p id="about-writers"></p>
<p id="about-producers"></p>
</div>
<a id="genius-link" href="#" target="_blank" class="genius-link">View on Genius →</a>
</div>
</div>
<div id="annotations-panel" class="lyrics-panel">
<div id="annotations-loading" class="lyrics-loading hidden">
<div class="lyrics-spinner"></div>
<p>Loading annotations...</p>
</div>
<div id="annotations-list" class="annotations-list"></div>
<div id="annotations-empty" class="lyrics-not-found hidden">
<p>No annotations available for this track</p>
</div>
</div>
</div>
</div>
</div>
<!-- Concerts Modal -->
<div id="concerts-modal" class="concerts-modal hidden">
<div class="concerts-modal-content">
<button id="concerts-modal-close" class="concerts-modal-close">×</button>
<div class="concerts-modal-header">
<h2>🎤 Upcoming Concerts</h2>
</div>
<div class="concerts-settings">
<label>My Cities (comma-separated):</label>
<input type="text" id="concerts-cities" class="concerts-cities-input" placeholder="San Francisco, Los Angeles, Seattle...">
<button id="concerts-save-cities" class="concerts-save-btn">Save</button>
</div>
<div class="concerts-tabs">
<button class="concerts-tab active" data-source="queue">From Queue</button>
<button class="concerts-tab" data-source="search">Search Artist</button>
</div>
<div id="concerts-search-section" class="concerts-search-section hidden">
<input type="text" id="concerts-artist-search" class="concerts-artist-input" placeholder="Search artist...">
<button id="concerts-search-btn" class="concerts-search-btn">🔍</button>
</div>
<div id="concerts-loading" class="lyrics-loading hidden">
<div class="lyrics-spinner"></div>
<p>Finding concerts...</p>
</div>
<div id="concerts-list" class="concerts-list"></div>
<div id="concerts-empty" class="concerts-empty hidden">
<p>No upcoming concerts found</p>
<p class="concerts-empty-hint">Try adding more artists to your queue or adjusting your cities</p>
</div>
</div>
</div>
<!-- DJ Setlist Modal -->
<div id="dj-setlist-modal" class="dj-modal hidden">
<div class="dj-modal-content">
<div class="dj-modal-header">
<h2>🎧 AI DJ Setlist</h2>
<button id="dj-modal-close" class="dj-modal-close">×</button>
</div>
<div class="dj-modal-body">
<div class="dj-style-selector">
<label>Set Style:</label>
<select id="dj-style-select">
<option value="progressive">Progressive (Build Energy)</option>
<option value="peak-time">Peak Time (High Energy)</option>
<option value="chill">Chill (Low-Med Energy)</option>
<option value="journey">Journey (Wave Pattern)</option>
</select>
</div>
<div id="dj-setlist-loading" class="dj-loading hidden">
<div class="spinner"></div>
<p>Analyzing tracks and generating setlist...</p>
</div>
<div id="dj-setlist-results" class="dj-results hidden">
<div id="dj-ordered-tracks" class="dj-ordered-tracks"></div>
</div>
</div>
<div class="dj-modal-actions">
<button id="dj-generate-btn" class="btn-primary">✨ Generate Setlist</button>
<button id="dj-apply-btn" class="btn-secondary hidden">Apply to Queue</button>
</div>
</div>
</div>
<!-- Hidden File Input (Moved to body end for reliability) -->
<input
type="file"
id="file-input"
multiple
accept="audio/*,.flac,.mp3,.wav,.aiff,.aac,.ogg,.m4a"
style="position: fixed; top: -100px; left: -100px; opacity: 0; pointer-events: none;"
onclick="this.value=null"
onchange="if(window.handleFiles) { window.handleFiles(this.files); } else { alert('Error: handleFiles not loaded'); }"
>
<!-- Playlist Selection Modal -->
<div id="playlist-modal" class="modal hidden">
<div class="modal-content playlist-modal-content">
<h3>Add to Playlist</h3>
<div id="playlist-list" class="playlist-list"></div>
<div class="create-playlist-row">
<input type="text" id="new-playlist-input" placeholder="New playlist name..." class="new-playlist-input">
<button id="create-playlist-btn" class="btn-primary">+</button>
</div>
<button id="playlist-modal-close" class="btn-secondary" style="width:100%; margin-top:12px;">Cancel</button>
</div>
</div>
<!-- Setlist Detail Modal -->
<div id="setlist-modal" class="modal hidden">
<div class="modal-content setlist-modal-content" style="max-height: 80vh; overflow-y: auto;">
<div class="modal-header">
<h3>Setlist</h3>
<button id="setlist-close-btn" class="modal-close-btn">×</button>
</div>
<div id="setlist-info" class="setlist-header-info">
<!-- Artist at Venue - Date inserted here -->
</div>
<div id="setlist-tracks" class="setlist-tracks-list">
<!-- Tracks inserted here -->
</div>
<div class="modal-actions" style="margin-top: 16px;">
<button id="setlist-play-btn" class="btn-primary" style="width: 100%;">
🎧 Listen to Show
</button>
</div>
</div>
</div>
<!-- Drive Sync Modal -->
<div id="drive-sync-modal" class="modal hidden">
<div class="modal-content drive-modal-content">
<div class="modal-header">
<h2>Google Drive Sync</h2>
<button id="drive-modal-close-top" class="modal-close-btn">×</button>
</div>
<!-- Auth Section -->
<div id="drive-auth-section">
<p style="margin-bottom: 20px; color: var(--text-secondary);">Sign in to sync your library across devices.</p>
<div class="drive-signin-container">
<button id="drive-signin-btn" class="drive-auth-btn">
<svg width="20" height="20" viewBox="0 0 24 24"><path fill="currentColor" d="M21.35 11.1h-9.17v2.73h6.51c-.33 3.81-3.5 5.44-6.5 5.44C8.36 19.27 5 16.25 5 12c0-4.1 3.2-7.27 7.2-7.27 3.09 0 4.9 1.97 4.9 1.97L19 4.72S16.56 2 12.1 2C6.42 2 2.03 6.8 2.03 12c0 5.05 4.13 10 10.22 10 5.35 0 9.25-3.67 9.25-9.09 0-1.15-.15-1.81-.15-1.81z"/></svg>
Sign in with Google
</button>
<div id="drive-loading" class="hidden">Connecting...</div>
</div>
</div>
<!-- Options Section -->
<div id="drive-options-section" class="hidden">
<div class="drive-user-info">
<span id="drive-user-email">Connected</span>
<button id="drive-signout-btn" class="text-link">Sign Out</button>
</div>
<div class="drive-actions-grid">
<!-- Upload Group -->
<div class="sync-group upload-group">
<h3>☁️ Upload (Save)</h3>
<p class="sync-desc">Save your current library to the cloud.</p>
<div class="action-buttons">
<button id="drive-up-all" class="action-btn upload">
<span class="btn-icon">⬆️</span>
<span class="btn-text">Everything</span>
</button>
<button id="drive-up-playlists" class="action-btn upload secondary">
<span class="btn-text">Playlists Only</span>
</button>
<button id="drive-up-queue" class="action-btn upload secondary">
<span class="btn-text">Queue Only</span>
</button>
</div>
</div>
<!-- Download Group -->
<div class="sync-group download-group">
<h3>⬇️ Download (Load)</h3>
<p class="sync-desc">Restore your library from the cloud.</p>
<div class="action-buttons">
<button id="drive-down-all" class="action-btn download">
<span class="btn-icon">⬇️</span>
<span class="btn-text">Everything</span>
</button>
<button id="drive-down-playlists" class="action-btn download secondary">
<span class="btn-text">Playlists Only</span>
</button>
<button id="drive-down-queue" class="action-btn download secondary">
<span class="btn-text">Queue Only</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Concert Alerts Modal -->
<div id="concert-modal" class="modal hidden">
<div class="modal-content concert-modal-content">
<div class="modal-header">
<h3>🎫 Concert Alerts</h3>
<button class="modal-close" id="concert-modal-close">×</button>
</div>
<!-- Tabs: Recent Artists | Search -->
<div class="concert-tabs">
<button class="concert-tab active" data-tab="recent">Recent Artists</button>
<button class="concert-tab" data-tab="search">Search Artist</button>
</div>
<!-- Recent Artists Tab (default) -->
<div id="concert-recent-section" class="concert-tab-content">
<p class="concert-hint">Showing concerts for artists you've listened to recently</p>
</div>
<!-- Search Tab -->
<div id="concert-search-section" class="concert-tab-content hidden">
<div class="concert-search-wrapper">
<input type="text" id="concert-artist-search" placeholder="Search for an artist...">
<button id="concert-search-btn" class="btn-primary">Search</button>
</div>
</div>
<!-- Results -->
<div id="concert-results" class="concert-results"></div>
<div id="concert-loading" class="concert-loading hidden">
<span class="loading-spinner"></span> Finding concerts...
</div>
<div id="concert-empty" class="concert-empty hidden">
<span>🎵</span>
<p>No upcoming concerts found</p>
</div>
</div>
</div>
<script src="static/jsmediatags.min.js"></script>
<script src="https://apis.google.com/js/api.js"></script>
<script src="https://accounts.google.com/gsi/client"></script>
<script src="/static/app.js?t=1736053100"></script></script>
</body>
</html>