freedify/static/index.html
2026-01-13 22:26:48 +00:00

778 lines
41 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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