|
|
<!DOCTYPE html>
|
|
|
<html lang="en">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<title>SoulSync - Music Sync & Manager</title>
|
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="main-container">
|
|
|
<!-- Sidebar - Always Visible -->
|
|
|
<div class="sidebar">
|
|
|
<!-- Header Section -->
|
|
|
<div class="sidebar-header">
|
|
|
<h1 class="app-name">SoulSync</h1>
|
|
|
<p class="app-subtitle">Music Sync & Manager</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Navigation Section -->
|
|
|
<nav class="sidebar-nav">
|
|
|
<button class="nav-button active" data-page="dashboard">
|
|
|
<span class="nav-icon">📊</span>
|
|
|
<span class="nav-text">Dashboard</span>
|
|
|
</button>
|
|
|
<button class="nav-button" data-page="sync">
|
|
|
<span class="nav-icon">🔄</span>
|
|
|
<span class="nav-text">Sync</span>
|
|
|
</button>
|
|
|
<button class="nav-button" data-page="downloads">
|
|
|
<span class="nav-icon">📥</span>
|
|
|
<span class="nav-text">Search</span>
|
|
|
</button>
|
|
|
<button class="nav-button" data-page="discover">
|
|
|
<span class="nav-icon">🎧</span>
|
|
|
<span class="nav-text">Discover</span>
|
|
|
</button>
|
|
|
<button class="nav-button" data-page="artists">
|
|
|
<span class="nav-icon">🎵</span>
|
|
|
<span class="nav-text">Artists</span>
|
|
|
</button>
|
|
|
<button class="nav-button" data-page="library">
|
|
|
<span class="nav-icon">📚</span>
|
|
|
<span class="nav-text">Library</span>
|
|
|
</button>
|
|
|
<button class="nav-button" data-page="settings">
|
|
|
<span class="nav-icon">⚙️</span>
|
|
|
<span class="nav-text">Settings</span>
|
|
|
</button>
|
|
|
</nav>
|
|
|
|
|
|
<!-- Spacer -->
|
|
|
<div class="sidebar-spacer"></div>
|
|
|
|
|
|
<!-- Media Player Section -->
|
|
|
<div class="media-player" id="media-player">
|
|
|
<!-- Loading Animation -->
|
|
|
<div class="loading-animation hidden" id="loading-animation">
|
|
|
<div class="loading-bar">
|
|
|
<div class="loading-progress"></div>
|
|
|
</div>
|
|
|
<div class="loading-text">0%</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Header (always visible) -->
|
|
|
<div class="media-header">
|
|
|
<div class="media-info">
|
|
|
<div class="track-title" id="track-title">No track</div>
|
|
|
<div class="artist-name" id="artist-name">Unknown Artist</div>
|
|
|
</div>
|
|
|
<button class="play-button" id="play-button" disabled>▷</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Expanded Content (hidden initially) -->
|
|
|
<div class="media-expanded hidden" id="media-expanded">
|
|
|
<div class="album-name" id="album-name">Unknown Album</div>
|
|
|
|
|
|
<!-- Progress Bar and Time Display -->
|
|
|
<div class="progress-section">
|
|
|
<div class="time-display">
|
|
|
<span class="current-time" id="current-time">0:00</span>
|
|
|
<span class="time-separator">/</span>
|
|
|
<span class="total-time" id="total-time">0:00</span>
|
|
|
</div>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-track">
|
|
|
<div class="progress-fill" id="progress-fill"></div>
|
|
|
</div>
|
|
|
<input type="range" class="progress-bar" id="progress-bar" min="0" max="100" value="0" step="0.1">
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="media-controls">
|
|
|
<div class="volume-control">
|
|
|
<span class="volume-icon">🔊</span>
|
|
|
<input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="70">
|
|
|
</div>
|
|
|
<button class="stop-button" id="stop-button" disabled>⏹</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- No Track Message -->
|
|
|
<div class="no-track-message" id="no-track-message">
|
|
|
Start playing music to see controls
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Crypto Donation Section -->
|
|
|
<div class="crypto-donation">
|
|
|
<div class="donation-header">
|
|
|
<span class="donation-title">Support Development</span>
|
|
|
<button class="toggle-button" id="donation-toggle">Show</button>
|
|
|
</div>
|
|
|
<div class="donation-addresses hidden" id="donation-addresses">
|
|
|
<div class="donation-item" onclick="openKofi()">
|
|
|
<span class="donation-name">Ko-fi</span>
|
|
|
<span class="donation-link">Click to open</span>
|
|
|
</div>
|
|
|
<div class="donation-item" onclick="copyAddress('3JVWrRSkozAQSmw5DXYVxYKsM9bndPTqdS', 'Bitcoin')">
|
|
|
<span class="donation-name">Bitcoin</span>
|
|
|
<span class="donation-address">3JVWrR...dPTqdS</span>
|
|
|
</div>
|
|
|
<div class="donation-item" onclick="copyAddress('0x343fC48c2cd1C6332b0df9a58F86e6520a026AC5', 'Ethereum')">
|
|
|
<span class="donation-name">Ethereum</span>
|
|
|
<span class="donation-address">0x343f...026AC5</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Version Section -->
|
|
|
<div class="version-section">
|
|
|
<button class="version-button" onclick="showVersionInfo()">v1.0</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Status Section -->
|
|
|
<div class="status-section">
|
|
|
<h4 class="status-title">Service Status</h4>
|
|
|
<div class="status-indicator" id="spotify-indicator">
|
|
|
<span class="status-dot disconnected"></span>
|
|
|
<span class="status-name">Spotify</span>
|
|
|
</div>
|
|
|
<div class="status-indicator" id="media-server-indicator">
|
|
|
<span class="status-dot disconnected"></span>
|
|
|
<span class="status-name" id="media-server-name">Plex</span>
|
|
|
</div>
|
|
|
<div class="status-indicator" id="soulseek-indicator">
|
|
|
<span class="status-dot disconnected"></span>
|
|
|
<span class="status-name">Soulseek</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Main Content Area -->
|
|
|
<div class="main-content">
|
|
|
<!-- Dashboard Page -->
|
|
|
<div class="page active" id="dashboard-page">
|
|
|
<div class="dashboard-container">
|
|
|
<div class="dashboard-header">
|
|
|
<div class="header-text">
|
|
|
<h2 class="header-title">System Dashboard</h2>
|
|
|
<p class="header-subtitle">Monitor your music system health and manage operations</p>
|
|
|
</div>
|
|
|
<div class="header-spacer"></div>
|
|
|
<div class="header-actions">
|
|
|
<button class="header-button watchlist-button" id="watchlist-button">👁️ Watchlist (0)</button>
|
|
|
<button class="header-button wishlist-button" id="wishlist-button">🎵 Wishlist (0)</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="dashboard-section">
|
|
|
<h3 class="section-title">Service Status</h3>
|
|
|
<div class="service-status-grid">
|
|
|
<div class="service-card" id="spotify-service-card">
|
|
|
<div class="service-card-header">
|
|
|
<span class="service-card-title">Spotify</span>
|
|
|
<span class="service-card-indicator disconnected" id="spotify-status-indicator">●</span>
|
|
|
</div>
|
|
|
<p class="service-card-status-text" id="spotify-status-text">Disconnected</p>
|
|
|
<p class="service-card-response-time" id="spotify-response-time">Response: --</p>
|
|
|
<div class="service-card-footer">
|
|
|
<button class="service-card-button" onclick="testDashboardConnection('spotify')">Test Connection</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="service-card" id="media-server-service-card">
|
|
|
<div class="service-card-header">
|
|
|
<span class="service-card-title" id="media-server-service-name">Server</span>
|
|
|
<span class="service-card-indicator disconnected" id="media-server-status-indicator">●</span>
|
|
|
</div>
|
|
|
<p class="service-card-status-text" id="media-server-status-text">Disconnected</p>
|
|
|
<p class="service-card-response-time" id="media-server-response-time">Response: --</p>
|
|
|
<div class="service-card-footer">
|
|
|
<button class="service-card-button" onclick="testDashboardConnection('server')">Test Connection</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="service-card" id="soulseek-service-card">
|
|
|
<div class="service-card-header">
|
|
|
<span class="service-card-title">Soulseek</span>
|
|
|
<span class="service-card-indicator disconnected" id="soulseek-status-indicator">●</span>
|
|
|
</div>
|
|
|
<p class="service-card-status-text" id="soulseek-status-text">Disconnected</p>
|
|
|
<p class="service-card-response-time" id="soulseek-response-time">Response: --</p>
|
|
|
<div class="service-card-footer">
|
|
|
<button class="service-card-button" onclick="testDashboardConnection('soulseek')">Test Connection</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="dashboard-section">
|
|
|
<h3 class="section-title">System Statistics</h3>
|
|
|
<div class="stats-grid-dashboard">
|
|
|
<div class="stat-card-dashboard" id="active-downloads-card">
|
|
|
<p class="stat-card-title">Active Downloads</p>
|
|
|
<p class="stat-card-value">0</p>
|
|
|
<p class="stat-card-subtitle">Currently downloading</p>
|
|
|
</div>
|
|
|
<div class="stat-card-dashboard" id="finished-downloads-card">
|
|
|
<p class="stat-card-title">Finished Downloads</p>
|
|
|
<p class="stat-card-value">0</p>
|
|
|
<p class="stat-card-subtitle">Completed this session</p>
|
|
|
</div>
|
|
|
<div class="stat-card-dashboard" id="download-speed-card">
|
|
|
<p class="stat-card-title">Download Speed</p>
|
|
|
<p class="stat-card-value">0 KB/s</p>
|
|
|
<p class="stat-card-subtitle">Combined speed</p>
|
|
|
</div>
|
|
|
<div class="stat-card-dashboard" id="active-syncs-card">
|
|
|
<p class="stat-card-title">Active Syncs</p>
|
|
|
<p class="stat-card-value">0</p>
|
|
|
<p class="stat-card-subtitle">Playlists syncing</p>
|
|
|
</div>
|
|
|
<div class="stat-card-dashboard" id="uptime-card">
|
|
|
<p class="stat-card-title">System Uptime</p>
|
|
|
<p class="stat-card-value">0m</p>
|
|
|
<p class="stat-card-subtitle">Application runtime</p>
|
|
|
</div>
|
|
|
<div class="stat-card-dashboard" id="memory-card">
|
|
|
<p class="stat-card-title">Memory Usage</p>
|
|
|
<p class="stat-card-value">--</p>
|
|
|
<p class="stat-card-subtitle">Current usage</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="dashboard-section">
|
|
|
<h3 class="section-title">Tools & Operations</h3>
|
|
|
<div class="tools-grid">
|
|
|
<div class="tool-card" id="db-updater-card">
|
|
|
<div class="tool-card-header">
|
|
|
<h4 class="tool-card-title">Database Updater</h4>
|
|
|
<button class="tool-help-button" data-tool="db-updater" title="Learn more about this tool">?</button>
|
|
|
</div>
|
|
|
<p class="tool-card-info">Last Full Refresh: <span id="db-last-refresh">Never</span></p>
|
|
|
<div class="tool-card-stats">
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Artists:</span>
|
|
|
<span class="stat-item-value" id="db-stat-artists">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Albums:</span>
|
|
|
<span class="stat-item-value" id="db-stat-albums">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Tracks:</span>
|
|
|
<span class="stat-item-value" id="db-stat-tracks">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Size:</span>
|
|
|
<span class="stat-item-value" id="db-stat-size">0.0 MB</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="tool-card-controls">
|
|
|
<select id="db-refresh-type">
|
|
|
<option value="incremental">Incremental Update</option>
|
|
|
<option value="full">Full Refresh</option>
|
|
|
</select>
|
|
|
<button id="db-update-button">Update Database</button>
|
|
|
</div>
|
|
|
<div class="tool-card-progress-section">
|
|
|
<p class="progress-phase-label" id="db-phase-label">Idle</p>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-bar-fill" id="db-progress-bar" style="width: 0%;"></div>
|
|
|
</div>
|
|
|
<p class="progress-details-label" id="db-progress-label">0 / 0 artists (0.0%)</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="tool-card" id="metadata-updater-card">
|
|
|
<div class="tool-card-header">
|
|
|
<h4 class="tool-card-title">Metadata Updater</h4>
|
|
|
<button class="tool-help-button" data-tool="metadata-updater" title="Learn more about this tool">?</button>
|
|
|
</div>
|
|
|
<p class="metadata-updater-description tool-card-info">Updates artist photos, genres, and album art from Spotify.</p>
|
|
|
<div class="tool-card-controls">
|
|
|
<select id="metadata-refresh-interval">
|
|
|
<option value="180">6 months</option>
|
|
|
<option value="90">3 months</option>
|
|
|
<option value="30" selected>1 month</option>
|
|
|
<option value="14">2 weeks</option>
|
|
|
<option value="7">1 week</option>
|
|
|
<option value="0">Full refresh</option>
|
|
|
</select>
|
|
|
<button id="metadata-update-button">Begin Update</button>
|
|
|
</div>
|
|
|
<div class="tool-card-progress-section">
|
|
|
<p class="progress-phase-label" id="metadata-phase-label">Current Artist: Not running</p>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-bar-fill" id="metadata-progress-bar" style="width: 0%;"></div>
|
|
|
</div>
|
|
|
<p class="progress-details-label" id="metadata-progress-label">0 / 0 artists (0.0%)</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="tool-card" id="quality-scanner-card">
|
|
|
<div class="tool-card-header">
|
|
|
<h4 class="tool-card-title">Quality Scanner</h4>
|
|
|
<button class="tool-help-button" data-tool="quality-scanner" title="Learn more about this tool">?</button>
|
|
|
</div>
|
|
|
<p class="tool-card-info">Scan library for tracks below quality preferences</p>
|
|
|
<div class="tool-card-stats">
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Processed:</span>
|
|
|
<span class="stat-item-value" id="quality-stat-processed">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Quality Met:</span>
|
|
|
<span class="stat-item-value" id="quality-stat-met">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Low Quality:</span>
|
|
|
<span class="stat-item-value" id="quality-stat-low">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Matched:</span>
|
|
|
<span class="stat-item-value" id="quality-stat-matched">0</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="tool-card-controls">
|
|
|
<select id="quality-scan-scope">
|
|
|
<option value="watchlist">Watchlist Artists Only</option>
|
|
|
<option value="all">All Library Tracks</option>
|
|
|
</select>
|
|
|
<button id="quality-scan-button">Scan Library</button>
|
|
|
</div>
|
|
|
<div class="tool-card-progress-section">
|
|
|
<p class="progress-phase-label" id="quality-phase-label">Ready to scan</p>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-bar-fill" id="quality-progress-bar" style="width: 0%;"></div>
|
|
|
</div>
|
|
|
<p class="progress-details-label" id="quality-progress-label">0 / 0 tracks scanned (0.0%)</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="tool-card" id="duplicate-cleaner-card">
|
|
|
<div class="tool-card-header">
|
|
|
<h4 class="tool-card-title">Duplicate Cleaner</h4>
|
|
|
<button class="tool-help-button" data-tool="duplicate-cleaner" title="Learn more about this tool">?</button>
|
|
|
</div>
|
|
|
<p class="tool-card-info">Detect and remove duplicate tracks in Transfer folder</p>
|
|
|
<div class="tool-card-stats">
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Files Scanned:</span>
|
|
|
<span class="stat-item-value" id="duplicate-stat-scanned">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Duplicates Found:</span>
|
|
|
<span class="stat-item-value" id="duplicate-stat-found">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Deleted:</span>
|
|
|
<span class="stat-item-value" id="duplicate-stat-deleted">0</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Space Freed:</span>
|
|
|
<span class="stat-item-value" id="duplicate-stat-space">0 MB</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="tool-card-controls">
|
|
|
<button id="duplicate-clean-button">Clean Duplicates</button>
|
|
|
</div>
|
|
|
<div class="tool-card-progress-section">
|
|
|
<p class="progress-phase-label" id="duplicate-phase-label">Ready to scan</p>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-bar-fill" id="duplicate-progress-bar" style="width: 0%;"></div>
|
|
|
</div>
|
|
|
<p class="progress-details-label" id="duplicate-progress-label">0 files scanned (0.0%)</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="tool-card" id="media-scan-card" style="display: none;">
|
|
|
<div class="tool-card-header">
|
|
|
<h4 class="tool-card-title">Media Server Scan</h4>
|
|
|
<button class="tool-help-button" data-tool="media-scan" title="Learn more about this tool">?</button>
|
|
|
</div>
|
|
|
<p class="tool-card-info">Manually trigger Plex media library scan for music</p>
|
|
|
<div class="tool-card-stats">
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Last Scan:</span>
|
|
|
<span class="stat-item-value" id="media-scan-last-time">Never</span>
|
|
|
</div>
|
|
|
<div class="stat-item">
|
|
|
<span class="stat-item-label">Status:</span>
|
|
|
<span class="stat-item-value" id="media-scan-status">Idle</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="tool-card-controls">
|
|
|
<button id="media-scan-button" class="media-scan-btn">
|
|
|
<span class="scan-icon">📡</span>
|
|
|
<span class="scan-text">Scan Library</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
<div class="tool-card-progress-section">
|
|
|
<p class="progress-phase-label" id="media-scan-phase-label">Ready to scan</p>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-bar-fill" id="media-scan-progress-bar" style="width: 0%;"></div>
|
|
|
</div>
|
|
|
<p class="progress-details-label" id="media-scan-progress-label">Waiting for scan request</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="dashboard-section">
|
|
|
<h3 class="section-title">Recent Activity</h3>
|
|
|
<div class="activity-feed-container" id="dashboard-activity-feed">
|
|
|
<div class="activity-item">
|
|
|
<span class="activity-icon">📊</span>
|
|
|
<div class="activity-text-content">
|
|
|
<p class="activity-title">System Started</p>
|
|
|
<p class="activity-subtitle">Dashboard initialized successfully</p>
|
|
|
</div>
|
|
|
<p class="activity-time">Now</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Main container for the Sync page -->
|
|
|
<div class="page" id="sync-page">
|
|
|
<!-- Header -->
|
|
|
<div class="sync-header">
|
|
|
<h2 class="sync-title">Playlist Sync</h2>
|
|
|
<p class="sync-subtitle">Synchronize your Spotify, Tidal, and YouTube playlists with your media server</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Main two-column content area -->
|
|
|
<div class="sync-content-area">
|
|
|
<!-- Left Panel: Tabbed Playlist Section -->
|
|
|
<div class="sync-main-panel">
|
|
|
<div class="sync-tabs">
|
|
|
<button class="sync-tab-button active" data-tab="spotify">
|
|
|
<span class="tab-icon spotify-icon"></span> Spotify
|
|
|
</button>
|
|
|
<button class="sync-tab-button" data-tab="tidal">
|
|
|
<span class="tab-icon tidal-icon"></span> Tidal
|
|
|
</button>
|
|
|
<button class="sync-tab-button" data-tab="youtube">
|
|
|
<span class="tab-icon youtube-icon"></span> YouTube
|
|
|
</button>
|
|
|
<button class="sync-tab-button" data-tab="beatport">
|
|
|
<span class="tab-icon beatport-icon"></span> Beatport
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Spotify Tab Content -->
|
|
|
<div class="sync-tab-content active" id="spotify-tab-content">
|
|
|
<div class="playlist-header">
|
|
|
<h3>Your Spotify Playlists</h3>
|
|
|
<button class="refresh-button" id="spotify-refresh-btn">🔄 Refresh</button>
|
|
|
</div>
|
|
|
<div class="playlist-scroll-container" id="spotify-playlist-container">
|
|
|
<div class="playlist-placeholder">Click 'Refresh' to load your Spotify playlists.</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Tidal Tab Content -->
|
|
|
<div class="sync-tab-content" id="tidal-tab-content">
|
|
|
<div class="playlist-header">
|
|
|
<h3>Your Tidal Playlists</h3>
|
|
|
<button class="refresh-button tidal" id="tidal-refresh-btn">🔄 Refresh</button>
|
|
|
</div>
|
|
|
<div class="playlist-scroll-container" id="tidal-playlist-container">
|
|
|
<div class="playlist-placeholder">Click 'Refresh' to load your Tidal playlists.</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- YouTube Tab Content -->
|
|
|
<div class="sync-tab-content" id="youtube-tab-content">
|
|
|
<div class="youtube-input-section">
|
|
|
<input type="text" id="youtube-url-input" placeholder="Paste YouTube Music Playlist URL...">
|
|
|
<button id="youtube-parse-btn">Parse Playlist</button>
|
|
|
</div>
|
|
|
<div class="playlist-scroll-container" id="youtube-playlist-container">
|
|
|
<div class="playlist-placeholder">Parsed YouTube playlists will appear here.</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Beatport Tab Content -->
|
|
|
<div class="sync-tab-content" id="beatport-tab-content">
|
|
|
<!-- Beatport Nested Tabs -->
|
|
|
<div class="beatport-tabs">
|
|
|
<button class="beatport-tab-button active" data-beatport-tab="rebuild">
|
|
|
<span class="tab-icon rebuild-icon"></span> Browse
|
|
|
</button>
|
|
|
<button class="beatport-tab-button" data-beatport-tab="browse">
|
|
|
<span class="tab-icon browse-icon"></span> Browse Charts
|
|
|
</button>
|
|
|
<button class="beatport-tab-button" data-beatport-tab="playlists">
|
|
|
<span class="tab-icon playlist-icon"></span> My Playlists
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Browse Charts Tab Content -->
|
|
|
<div class="beatport-tab-content" id="beatport-browse-content">
|
|
|
|
|
|
|
|
|
<div class="beatport-navigation">
|
|
|
<!-- New Homepage Main View -->
|
|
|
<div class="beatport-main-view active" id="beatport-main-view">
|
|
|
<div class="beatport-hero">
|
|
|
<div class="beatport-hero-bg"></div>
|
|
|
<div class="beatport-hero-content">
|
|
|
<h2>Browse Beatport Charts</h2>
|
|
|
<p>Explore top electronic music charts and discover new tracks</p>
|
|
|
<div class="beatport-stats">
|
|
|
<span class="stat-item">39 Genres</span>
|
|
|
<span class="stat-divider">•</span>
|
|
|
<span class="stat-item">Top 100</span>
|
|
|
<span class="stat-divider">•</span>
|
|
|
<span class="stat-item">Daily Updates</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Genre Explorer Section -->
|
|
|
<div class="homepage-genre-section">
|
|
|
<h3 class="section-title">🎵 Genre Explorer</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-action="show-genres">
|
|
|
<div class="chart-type-icon">🎵</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Browse All Genres</h3>
|
|
|
<p>House, Techno, Trance, and 36 more genres</p>
|
|
|
<span class="track-count">39 Genres</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Main Charts Section -->
|
|
|
<div class="homepage-main-charts-section">
|
|
|
<h3 class="section-title">📊 Main Charts</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="top-10" data-chart-endpoint="/api/beatport/top-100">
|
|
|
<div class="chart-type-icon">🔥</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Beatport Top 10</h3>
|
|
|
<p>Current hottest tracks</p>
|
|
|
<span class="track-count">10 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="top-100" data-chart-endpoint="/api/beatport/top-100">
|
|
|
<div class="chart-type-icon">💯</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Beatport Top 100</h3>
|
|
|
<p>Complete chart rankings</p>
|
|
|
<span class="track-count">100 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Releases Section -->
|
|
|
<div class="homepage-releases-section">
|
|
|
<h3 class="section-title">🎵 Releases</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="releases-top-10" data-chart-endpoint="/api/beatport/homepage/top-10-releases">
|
|
|
<div class="chart-type-icon">🆕</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Top 10 Releases</h3>
|
|
|
<p>Newest releases trending</p>
|
|
|
<span class="track-count">10 releases</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="releases-top-100" data-chart-endpoint="/api/beatport/top-100-releases">
|
|
|
<div class="chart-type-icon">📊</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Top 100 Releases</h3>
|
|
|
<p>All trending releases</p>
|
|
|
<span class="track-count">100 releases</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="latest-releases" data-chart-endpoint="/api/beatport/homepage/new-releases">
|
|
|
<div class="chart-type-icon">🕒</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Latest Releases</h3>
|
|
|
<p>Recently published</p>
|
|
|
<span class="track-count">50 releases</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Hype Section -->
|
|
|
<div class="homepage-hype-section">
|
|
|
<h3 class="section-title">🔥 Hype</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="hype-top-10" data-chart-endpoint="/api/beatport/hype-top-100">
|
|
|
<div class="chart-type-icon">🚀</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Hype Top 10</h3>
|
|
|
<p>Hottest trending tracks</p>
|
|
|
<span class="track-count">10 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="hype-top-100" data-chart-endpoint="/api/beatport/hype-top-100">
|
|
|
<div class="chart-type-icon">🔥</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Hype Top 100</h3>
|
|
|
<p>Complete hype chart rankings</p>
|
|
|
<span class="track-count">100 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="hype-picks" data-chart-endpoint="/api/beatport/homepage/hype-picks">
|
|
|
<div class="chart-type-icon">⚡</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3>Hype Picks</h3>
|
|
|
<p>Editor selected hype tracks</p>
|
|
|
<span class="track-count">50 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- DJ Charts Section -->
|
|
|
<div class="homepage-dj-charts-section">
|
|
|
<h3 class="section-title">🎧 DJ Charts Collection</h3>
|
|
|
<p class="section-description">DJ curated chart collections</p>
|
|
|
<div class="charts-loading-inline" id="dj-charts-loading-inline">
|
|
|
<div class="loading-spinner-small"></div>
|
|
|
<p>Loading DJ chart collections...</p>
|
|
|
</div>
|
|
|
<div class="dj-charts-grid" id="dj-charts-grid">
|
|
|
<!-- Charts will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Featured Charts Section -->
|
|
|
<div class="homepage-featured-charts-section">
|
|
|
<h3 class="section-title">⭐ Featured Charts Collection</h3>
|
|
|
<p class="section-description">Editor curated chart collections</p>
|
|
|
<div class="charts-loading-inline" id="featured-charts-loading-inline">
|
|
|
<div class="loading-spinner-small"></div>
|
|
|
<p>Loading featured chart collections...</p>
|
|
|
</div>
|
|
|
<div class="featured-charts-grid" id="featured-charts-grid">
|
|
|
<!-- Charts will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Genre Explorer Sub-View -->
|
|
|
<div class="beatport-sub-view" id="beatport-genres-view">
|
|
|
<div class="beatport-breadcrumb">
|
|
|
<button class="breadcrumb-back">← Back to Categories</button>
|
|
|
<span class="breadcrumb-path">Browse Charts > Genre Explorer</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-grid">
|
|
|
<div class="beatport-genre-item" data-genre-slug="house" data-genre-id="5">
|
|
|
<div class="genre-icon">🏠</div>
|
|
|
<h3>House</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="tech-house" data-genre-id="11">
|
|
|
<div class="genre-icon">🔧</div>
|
|
|
<h3>Tech House</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="techno" data-genre-id="6">
|
|
|
<div class="genre-icon">⚡</div>
|
|
|
<h3>Techno</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="deep-house" data-genre-id="12">
|
|
|
<div class="genre-icon">🌊</div>
|
|
|
<h3>Deep House</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="trance" data-genre-id="7">
|
|
|
<div class="genre-icon">🌀</div>
|
|
|
<h3>Trance</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="drum-and-bass" data-genre-id="1">
|
|
|
<div class="genre-icon">🥁</div>
|
|
|
<h3>Drum & Bass</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="dubstep" data-genre-id="18">
|
|
|
<div class="genre-icon">🎵</div>
|
|
|
<h3>Dubstep</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="progressive-house" data-genre-id="15">
|
|
|
<div class="genre-icon">📈</div>
|
|
|
<h3>Progressive House</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="melodic-house-and-techno" data-genre-id="90">
|
|
|
<div class="genre-icon">🎼</div>
|
|
|
<h3>Melodic House & Techno</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="afro-house" data-genre-id="89">
|
|
|
<div class="genre-icon">🌍</div>
|
|
|
<h3>Afro House</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="minimal" data-genre-id="14">
|
|
|
<div class="genre-icon">⚫</div>
|
|
|
<h3>Minimal</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
<div class="beatport-genre-item" data-genre-slug="nu-disco" data-genre-id="50">
|
|
|
<div class="genre-icon">✨</div>
|
|
|
<h3>Nu Disco</h3>
|
|
|
<span class="genre-track-count">Top 100</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Genre Detail Sub-View -->
|
|
|
<div class="beatport-sub-view" id="beatport-genre-detail-view">
|
|
|
<div class="beatport-breadcrumb">
|
|
|
<button class="breadcrumb-back" id="genre-detail-back">← Back to Genre Explorer</button>
|
|
|
<span class="breadcrumb-path" id="genre-detail-breadcrumb">Browse Charts > Genre Explorer > Loading...</span>
|
|
|
</div>
|
|
|
<div class="genre-detail-header">
|
|
|
<div class="genre-detail-info">
|
|
|
<h2 id="genre-detail-title">Loading Genre...</h2>
|
|
|
<p id="genre-detail-description">Explore all chart types for this genre</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Main Chart Types Section -->
|
|
|
<div class="genre-main-charts-section">
|
|
|
<h3 class="section-title">📊 Main Charts</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="top-10">
|
|
|
<div class="chart-type-icon">🔥</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-top-10-title">Top 10</h3>
|
|
|
<p>Current hottest tracks</p>
|
|
|
<span class="track-count">10 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="top-100">
|
|
|
<div class="chart-type-icon">💯</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-top-100-title">Top 100</h3>
|
|
|
<p>Complete chart rankings</p>
|
|
|
<span class="track-count">100 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Releases Section -->
|
|
|
<div class="genre-releases-section">
|
|
|
<h3 class="section-title">🎵 Releases</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="releases-top-10">
|
|
|
<div class="chart-type-icon">🆕</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-releases-top-10-title">Top 10 Releases</h3>
|
|
|
<p>Newest releases trending</p>
|
|
|
<span class="track-count">10 releases</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="releases-top-100">
|
|
|
<div class="chart-type-icon">📊</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-releases-top-100-title">Top 100 Releases</h3>
|
|
|
<p>All trending releases</p>
|
|
|
<span class="track-count">100 releases</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="latest-releases">
|
|
|
<div class="chart-type-icon">🕒</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-latest-releases-title">Latest Releases</h3>
|
|
|
<p>Recently published</p>
|
|
|
<span class="track-count">50 releases</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Editorial Section -->
|
|
|
<div class="genre-editorial-section">
|
|
|
<h3 class="section-title">⭐ Editorial</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="staff-picks">
|
|
|
<div class="chart-type-icon">⭐</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-staff-picks-title">Staff Picks</h3>
|
|
|
<p>Editor curated selection</p>
|
|
|
<span class="track-count">50 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Hype Section -->
|
|
|
<div class="genre-hype-section">
|
|
|
<h3 class="section-title">🔥 Hype</h3>
|
|
|
<div class="genre-chart-types-grid">
|
|
|
<div class="genre-chart-type-card" data-chart-type="hype-top-10">
|
|
|
<div class="chart-type-icon">🚀</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-hype-top-10-title">Hype Top 10</h3>
|
|
|
<p>Hottest trending tracks</p>
|
|
|
<span class="track-count">10 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="hype-top-100">
|
|
|
<div class="chart-type-icon">🔥</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-hype-top-100-title">Hype Top 100</h3>
|
|
|
<p>Complete hype chart rankings</p>
|
|
|
<span class="track-count">100 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-chart-type-card" data-chart-type="hype-picks">
|
|
|
<div class="chart-type-icon">⚡</div>
|
|
|
<div class="chart-type-info">
|
|
|
<h3 id="genre-hype-picks-title">Hype Picks</h3>
|
|
|
<p>Editor selected hype tracks</p>
|
|
|
<span class="track-count">50 tracks</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- New Charts Section (Always Visible) -->
|
|
|
<div class="genre-new-charts-section">
|
|
|
<h3 class="section-title">📈 New Charts Collection</h3>
|
|
|
<p class="section-description">Artist and DJ curated chart collections</p>
|
|
|
|
|
|
<!-- Always Visible Charts List -->
|
|
|
<div class="new-charts-content" id="new-charts-content">
|
|
|
<div class="charts-loading-inline" id="charts-loading-inline">
|
|
|
<div class="loading-spinner-small"></div>
|
|
|
<p>Loading chart collections...</p>
|
|
|
</div>
|
|
|
<div class="new-charts-grid" id="new-charts-grid">
|
|
|
<!-- Charts will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Genre Charts List Sub-View -->
|
|
|
<div class="beatport-sub-view" id="beatport-genre-charts-list-view">
|
|
|
<div class="beatport-breadcrumb">
|
|
|
<button class="breadcrumb-back" id="genre-charts-list-back">← Back to Genre Charts</button>
|
|
|
<span class="breadcrumb-path" id="genre-charts-list-breadcrumb">Browse Charts > Genre Explorer > Genre Charts > New Charts</span>
|
|
|
</div>
|
|
|
<div class="genre-charts-list-header">
|
|
|
<div class="genre-charts-list-info">
|
|
|
<h2 id="genre-charts-list-title">Loading Charts...</h2>
|
|
|
<p id="genre-charts-list-description">Browse all available chart collections for this genre</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="genre-charts-list-container">
|
|
|
<div class="charts-loading-placeholder" id="charts-loading-placeholder">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<p>🔍 Loading chart collections...</p>
|
|
|
</div>
|
|
|
<div class="genre-charts-grid" id="genre-charts-grid">
|
|
|
<!-- Charts will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- My Playlists Tab Content -->
|
|
|
<div class="beatport-tab-content" id="beatport-playlists-content">
|
|
|
<div class="playlist-header">
|
|
|
<h3>My Beatport Playlists</h3>
|
|
|
<button class="refresh-button beatport" id="beatport-clear-btn">🗑️ Clear</button>
|
|
|
</div>
|
|
|
<div class="playlist-scroll-container" id="beatport-playlist-container">
|
|
|
<div class="playlist-placeholder">Your created Beatport playlists will appear here.</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Rebuild Tab Content -->
|
|
|
<div class="beatport-tab-content active" id="beatport-rebuild-content">
|
|
|
<div class="beatport-rebuild-slider-container">
|
|
|
<div class="beatport-rebuild-slider" id="beatport-rebuild-slider">
|
|
|
<div class="beatport-rebuild-slider-track" id="beatport-rebuild-slider-track">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-rebuild-loading">
|
|
|
<div class="beatport-rebuild-loading-content">
|
|
|
<h2>🎯 Loading Fresh Beatport Tracks...</h2>
|
|
|
<p>Fetching the latest music from Beatport</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Navigation -->
|
|
|
<div class="beatport-rebuild-slider-nav">
|
|
|
<button class="beatport-rebuild-nav-btn beatport-rebuild-prev-btn" id="beatport-rebuild-prev-btn">‹</button>
|
|
|
<button class="beatport-rebuild-nav-btn beatport-rebuild-next-btn" id="beatport-rebuild-next-btn">›</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Indicators -->
|
|
|
<div class="beatport-rebuild-slider-indicators">
|
|
|
<!-- Indicators will be dynamically generated -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Navigation Buttons Section -->
|
|
|
<div class="beatport-nav-buttons-section">
|
|
|
<div class="beatport-nav-buttons-container">
|
|
|
<button class="beatport-nav-button" id="browse-by-genre-btn">
|
|
|
<span class="beatport-nav-icon genre-icon"></span>
|
|
|
<span class="beatport-nav-text">Browse by Genre</span>
|
|
|
</button>
|
|
|
<button class="beatport-nav-button" id="beatport-top100-btn">
|
|
|
<span class="beatport-nav-icon top100-icon"></span>
|
|
|
<span class="beatport-nav-text">Beatport Top 100</span>
|
|
|
</button>
|
|
|
<button class="beatport-nav-button" id="hype-top100-btn">
|
|
|
<span class="beatport-nav-icon hype-icon"></span>
|
|
|
<span class="beatport-nav-text">Hype Top 100</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Top 10 Lists Section -->
|
|
|
<div class="beatport-top10-section">
|
|
|
<div class="beatport-top10-header">
|
|
|
<h2 class="beatport-top10-title">🏆 Top 10 Lists</h2>
|
|
|
<p class="beatport-top10-subtitle">Current trending tracks from Beatport charts</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="beatport-top10-container">
|
|
|
<!-- Beatport Top 10 List -->
|
|
|
<div class="beatport-top10-list" id="beatport-top10-list">
|
|
|
<div class="beatport-top10-list-header">
|
|
|
<h3 class="beatport-top10-list-title">🎵 Beatport Top 10</h3>
|
|
|
<p class="beatport-top10-list-subtitle">Most popular tracks on Beatport</p>
|
|
|
</div>
|
|
|
<div class="beatport-top10-tracks" id="beatport-top10-tracks">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-top10-loading">
|
|
|
<div class="beatport-top10-loading-content">
|
|
|
<h4>🎵 Loading Beatport Top 10...</h4>
|
|
|
<p>Fetching trending tracks</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Hype Top 10 List -->
|
|
|
<div class="beatport-hype10-list" id="beatport-hype10-list">
|
|
|
<div class="beatport-hype10-list-header">
|
|
|
<h3 class="beatport-hype10-list-title">🔥 Hype Top 10</h3>
|
|
|
<p class="beatport-hype10-list-subtitle">Editor's hottest trending picks</p>
|
|
|
</div>
|
|
|
<div class="beatport-hype10-tracks" id="beatport-hype10-tracks">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-hype10-loading">
|
|
|
<div class="beatport-hype10-loading-content">
|
|
|
<h4>🔥 Loading Hype Top 10...</h4>
|
|
|
<p>Fetching editor's picks</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Top 10 Releases Section -->
|
|
|
<div class="beatport-releases-top10-section">
|
|
|
<div class="beatport-releases-top10-header">
|
|
|
<h2 class="beatport-releases-top10-title">💿 Top 10 Releases</h2>
|
|
|
<p class="beatport-releases-top10-subtitle">Most popular albums and EPs on Beatport</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="beatport-releases-top10-container">
|
|
|
<div class="beatport-releases-top10-list" id="beatport-releases-top10-list">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-releases-top10-loading">
|
|
|
<div class="beatport-releases-top10-loading-content">
|
|
|
<h4>💿 Loading Top 10 Releases...</h4>
|
|
|
<p>Fetching trending albums and EPs</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- New Releases Grid Slideshow Section -->
|
|
|
<div class="beatport-releases-section">
|
|
|
<div class="beatport-releases-header">
|
|
|
<h2 class="beatport-releases-title">🆕 New Releases</h2>
|
|
|
<p class="beatport-releases-subtitle">Latest albums and EPs from Beatport</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="beatport-releases-slider-container">
|
|
|
<div class="beatport-releases-slider" id="beatport-releases-slider">
|
|
|
<div class="beatport-releases-slider-track" id="beatport-releases-slider-track">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-releases-loading">
|
|
|
<div class="beatport-releases-loading-content">
|
|
|
<h3>📀 Loading New Releases...</h3>
|
|
|
<p>Fetching the latest albums and EPs</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Navigation -->
|
|
|
<div class="beatport-releases-slider-nav">
|
|
|
<button class="beatport-releases-nav-btn beatport-releases-prev-btn" id="beatport-releases-prev-btn">‹</button>
|
|
|
<button class="beatport-releases-nav-btn beatport-releases-next-btn" id="beatport-releases-next-btn">›</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Indicators -->
|
|
|
<div class="beatport-releases-slider-indicators" id="beatport-releases-slider-indicators">
|
|
|
<!-- Indicators will be dynamically generated -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Hype Picks Grid Slideshow Section -->
|
|
|
<div class="beatport-hype-picks-section">
|
|
|
<div class="beatport-hype-picks-header">
|
|
|
<h2 class="beatport-hype-picks-title">🔥 Hype Picks</h2>
|
|
|
<p class="beatport-hype-picks-subtitle">Editor selected trending tracks from Beatport</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="beatport-hype-picks-slider-container">
|
|
|
<div class="beatport-hype-picks-slider" id="beatport-hype-picks-slider">
|
|
|
<div class="beatport-hype-picks-slider-track" id="beatport-hype-picks-slider-track">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-hype-picks-loading">
|
|
|
<div class="beatport-hype-picks-loading-content">
|
|
|
<h3>🔥 Loading Hype Picks...</h3>
|
|
|
<p>Fetching the hottest trending tracks</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Navigation -->
|
|
|
<div class="beatport-hype-picks-slider-nav">
|
|
|
<button class="beatport-hype-picks-nav-btn beatport-hype-picks-prev-btn" id="beatport-hype-picks-prev-btn">‹</button>
|
|
|
<button class="beatport-hype-picks-nav-btn beatport-hype-picks-next-btn" id="beatport-hype-picks-next-btn">›</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Indicators -->
|
|
|
<div class="beatport-hype-picks-slider-indicators" id="beatport-hype-picks-slider-indicators">
|
|
|
<!-- Indicators will be dynamically generated -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Featured Charts Grid Slideshow Section -->
|
|
|
<div class="beatport-charts-section">
|
|
|
<div class="beatport-charts-header">
|
|
|
<h2 class="beatport-charts-title">🔥 Featured Charts</h2>
|
|
|
<p class="beatport-charts-subtitle">Top chart collections from Beatport creators</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="beatport-charts-slider-container">
|
|
|
<div class="beatport-charts-slider" id="beatport-charts-slider">
|
|
|
<div class="beatport-charts-slider-track" id="beatport-charts-slider-track">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-charts-loading">
|
|
|
<div class="beatport-charts-loading-content">
|
|
|
<h3>📊 Loading Featured Charts...</h3>
|
|
|
<p>Fetching top chart collections</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Navigation -->
|
|
|
<div class="beatport-charts-slider-nav">
|
|
|
<button class="beatport-charts-nav-btn beatport-charts-prev-btn" id="beatport-charts-prev-btn">‹</button>
|
|
|
<button class="beatport-charts-nav-btn beatport-charts-next-btn" id="beatport-charts-next-btn">›</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Indicators -->
|
|
|
<div class="beatport-charts-slider-indicators" id="beatport-charts-slider-indicators">
|
|
|
<!-- Indicators will be dynamically generated -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- DJ Charts Carousel Section -->
|
|
|
<div class="beatport-dj-section">
|
|
|
<div class="beatport-dj-header">
|
|
|
<h2 class="beatport-dj-title">🎧 DJ Charts</h2>
|
|
|
<p class="beatport-dj-subtitle">Curated charts from top DJs and artists</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="beatport-dj-slider-container">
|
|
|
<div class="beatport-dj-slider" id="beatport-dj-slider">
|
|
|
<div class="beatport-dj-slider-track" id="beatport-dj-slider-track">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="beatport-dj-loading">
|
|
|
<div class="beatport-dj-loading-content">
|
|
|
<h3>🎧 Loading DJ Charts...</h3>
|
|
|
<p>Fetching curated DJ selections</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Navigation -->
|
|
|
<div class="beatport-dj-slider-nav">
|
|
|
<button class="beatport-dj-nav-btn beatport-dj-prev-btn" id="beatport-dj-prev-btn">‹</button>
|
|
|
<button class="beatport-dj-nav-btn beatport-dj-next-btn" id="beatport-dj-next-btn">›</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slider Indicators -->
|
|
|
<div class="beatport-dj-slider-indicators" id="beatport-dj-slider-indicators">
|
|
|
<!-- Indicators will be dynamically generated -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Right Panel: Sidebar with Options & Logging -->
|
|
|
<div class="sync-sidebar">
|
|
|
<div class="sidebar-section">
|
|
|
<h4>Sync Actions</h4>
|
|
|
<div id="selection-info">Select playlists to sync</div>
|
|
|
<button id="start-sync-btn" class="neo-button" disabled>Start Sync</button>
|
|
|
</div>
|
|
|
<div class="sidebar-section progress-section">
|
|
|
<h4>Sync Progress</h4>
|
|
|
<div class="progress-bar-container">
|
|
|
<div class="progress-bar-fill" id="sync-progress-bar" style="width: 0%;"></div>
|
|
|
</div>
|
|
|
<div id="sync-progress-text">Ready to sync...</div>
|
|
|
<textarea id="sync-log-area" readonly>Waiting for sync to start...</textarea>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Downloads Page -->
|
|
|
<div class="page" id="downloads-page">
|
|
|
<!--
|
|
|
This top-level container replicates the QSplitter from downloads.py,
|
|
|
creating the two-panel layout for the page.
|
|
|
-->
|
|
|
<div class="downloads-content">
|
|
|
|
|
|
<!-- ======================================================= -->
|
|
|
<!-- == LEFT PANEL: Search, Filters, and Results == -->
|
|
|
<!-- ======================================================= -->
|
|
|
<div class="downloads-main-panel">
|
|
|
|
|
|
<!-- Header: Replicates create_elegant_header() -->
|
|
|
<div class="downloads-header">
|
|
|
<h2 class="downloads-title">🎵 Music Downloads</h2>
|
|
|
<p class="downloads-subtitle">Search, discover, and download high-quality music</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Search Bar: Replicates create_elegant_search_bar() -->
|
|
|
<div class="search-bar-container">
|
|
|
<input type="text" id="downloads-search-input" placeholder="Search for music... (e.g., 'Virtual Mage', 'Queen Bohemian Rhapsody')">
|
|
|
<button id="downloads-cancel-btn" class="hidden">✕ Cancel</button>
|
|
|
<button id="downloads-search-btn">🔍 Search</button>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<div id="filters-container" class="filters-container hidden">
|
|
|
<div class="filter-toggle-header">
|
|
|
<button id="filter-toggle-btn" class="filter-toggle-btn">⏷ Filters</button>
|
|
|
</div>
|
|
|
|
|
|
<div id="filter-content" class="filter-content hidden">
|
|
|
<!-- Filter by Type -->
|
|
|
<div class="filter-group">
|
|
|
<label class="filter-label">Type:</label>
|
|
|
<button class="filter-btn active" data-filter-type="type" data-value="all">All</button>
|
|
|
<button class="filter-btn" data-filter-type="type" data-value="album">Albums</button>
|
|
|
<button class="filter-btn" data-filter-type="type" data-value="track">Singles</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Filter by Format -->
|
|
|
<div class="filter-group">
|
|
|
<label class="filter-label">Format:</label>
|
|
|
<button class="filter-btn active" data-filter-type="format" data-value="all">All</button>
|
|
|
<button class="filter-btn" data-filter-type="format" data-value="flac">FLAC</button>
|
|
|
<button class="filter-btn" data-filter-type="format" data-value="mp3">MP3</button>
|
|
|
<!-- Added missing format buttons -->
|
|
|
<button class="filter-btn" data-filter-type="format" data-value="ogg">OGG</button>
|
|
|
<button class="filter-btn" data-filter-type="format" data-value="aac">AAC</button>
|
|
|
<button class="filter-btn" data-filter-type="format" data-value="wma">WMA</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Sort Controls -->
|
|
|
<div class="filter-group">
|
|
|
<label class="filter-label">Sort by:</label>
|
|
|
<button id="sort-order-btn" class="filter-btn sort-order-btn" data-order="desc">↓</button>
|
|
|
<!-- Added all sort options from the GUI -->
|
|
|
<button class="filter-btn active" data-filter-type="sort" data-value="relevance">Relevance</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="quality_score">Quality</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="size">Size</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="title">Name</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="username">Uploader</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="bitrate">Bitrate</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="duration">Duration</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="availability">Available</button>
|
|
|
<button class="filter-btn" data-filter-type="sort" data-value="upload_speed">Speed</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<!-- Search Status Bar -->
|
|
|
<div class="search-status-container">
|
|
|
<div class="spinner-animation hidden"></div>
|
|
|
<p id="search-status-text">Ready to search • Enter artist, song, or album name</p>
|
|
|
<div class="dots-animation hidden"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Search Results Area: Replicates the QScrollArea -->
|
|
|
<div class="search-results-container">
|
|
|
<div class="search-results-header">
|
|
|
<h3>Search Results</h3>
|
|
|
</div>
|
|
|
<div class="search-results-scroll-area" id="search-results-area">
|
|
|
<!--
|
|
|
The placeholder search results have been removed.
|
|
|
This area will now be populated by JavaScript based on API responses.
|
|
|
-->
|
|
|
<div class="search-results-placeholder">
|
|
|
<p>Your search results will appear here.</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- ======================================================= -->
|
|
|
<!-- == RIGHT PANEL: Controls and Download Queue == -->
|
|
|
<!-- ======================================================= -->
|
|
|
<div class="downloads-side-panel">
|
|
|
|
|
|
<!-- Controls Panel: Replicates create_collapsible_controls_panel() -->
|
|
|
<div class="controls-panel">
|
|
|
<h3 class="controls-panel__header">Download Manager</h3>
|
|
|
<div class="controls-panel__stats">
|
|
|
<p id="active-downloads-label">• Active Downloads: 0</p>
|
|
|
<p id="finished-downloads-label">• Finished Downloads: 0</p>
|
|
|
</div>
|
|
|
<div class="controls-panel__actions">
|
|
|
<button class="controls-panel__clear-btn">🗑️ Clear Completed</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Download Queue: Replicates TabbedDownloadManager -->
|
|
|
<div class="download-manager">
|
|
|
<div class="download-manager__tabs">
|
|
|
<button class="tab-btn active" data-tab="active-queue">Download Queue (0)</button>
|
|
|
<button class="tab-btn" data-tab="finished-queue">Finished (0)</button>
|
|
|
</div>
|
|
|
<div class="download-manager__content">
|
|
|
|
|
|
<!-- Active Queue -->
|
|
|
<div class="download-queue active" id="active-queue">
|
|
|
<div class="download-queue__empty-message">No active downloads.</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Finished Queue -->
|
|
|
<div class="download-queue" id="finished-queue">
|
|
|
<div class="download-queue__empty-message">No finished downloads.</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Artists Page -->
|
|
|
<div class="page" id="artists-page">
|
|
|
<!-- Initial Search State -->
|
|
|
<div class="artists-search-state" id="artists-search-state">
|
|
|
<div class="artists-search-container">
|
|
|
<div class="artists-welcome-section">
|
|
|
<h2 class="artists-welcome-title">🎵 Discover Artists</h2>
|
|
|
<p class="artists-welcome-subtitle">Search for your favorite artists and explore their complete discography</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="artists-search-input-container">
|
|
|
<input type="text"
|
|
|
id="artists-search-input"
|
|
|
class="artists-search-input"
|
|
|
placeholder="Search for an artist...">
|
|
|
<div class="artists-search-icon">🔍</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="artists-search-status" id="artists-search-status">
|
|
|
Start typing to search for artists
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Search Results State -->
|
|
|
<div class="artists-results-state hidden" id="artists-results-state">
|
|
|
<div class="artists-results-header">
|
|
|
<button class="artists-back-button" id="artists-back-button">
|
|
|
<span class="back-icon">←</span>
|
|
|
<span>Back to Search</span>
|
|
|
</button>
|
|
|
|
|
|
<div class="artists-search-header">
|
|
|
<input type="text"
|
|
|
id="artists-header-search-input"
|
|
|
class="artists-header-search-input"
|
|
|
placeholder="Search for an artist...">
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="artists-results-content">
|
|
|
<div class="artists-results-title">Search Results</div>
|
|
|
<div class="artists-cards-container" id="artists-cards-container">
|
|
|
<!-- Artist cards will be dynamically populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Artist Detail State -->
|
|
|
<div class="artist-detail-state hidden" id="artist-detail-state">
|
|
|
<div class="artist-detail-header">
|
|
|
<div class="artist-detail-header-left">
|
|
|
<button class="artist-detail-back-button" id="artist-detail-back-button">
|
|
|
<span class="back-icon">←</span>
|
|
|
<span>Back to Results</span>
|
|
|
</button>
|
|
|
|
|
|
<div class="artist-detail-info">
|
|
|
<div class="artist-detail-image" id="search-artist-detail-image"></div>
|
|
|
<div class="artist-detail-text">
|
|
|
<h2 class="artist-detail-name" id="search-artist-detail-name">Artist Name</h2>
|
|
|
<p class="artist-detail-genres" id="search-artist-detail-genres">Genres</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<button class="artist-detail-watchlist-btn" id="artist-detail-watchlist-btn">
|
|
|
<span class="watchlist-icon">👁️</span>
|
|
|
<span class="watchlist-text">Add to Watchlist</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="artist-detail-content">
|
|
|
<div class="artist-detail-tabs">
|
|
|
<button class="artist-tab active" data-tab="albums" id="albums-tab">
|
|
|
<span class="tab-icon">💿</span>
|
|
|
<span>Albums</span>
|
|
|
</button>
|
|
|
<button class="artist-tab" data-tab="singles" id="singles-tab">
|
|
|
<span class="tab-icon">🎵</span>
|
|
|
<span>Singles & EPs</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="artist-detail-discography">
|
|
|
<div class="tab-content active" id="albums-content">
|
|
|
<div class="album-cards-container" id="album-cards-container">
|
|
|
<!-- Album cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="tab-content" id="singles-content">
|
|
|
<div class="singles-cards-container" id="singles-cards-container">
|
|
|
<!-- Singles cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Similar Artists Section -->
|
|
|
<div class="similar-artists-section" id="similar-artists-section">
|
|
|
<div class="similar-artists-header">
|
|
|
<h3 class="similar-artists-title">Similar Artists</h3>
|
|
|
<p class="similar-artists-subtitle">Discover artists with a similar sound</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Loading State -->
|
|
|
<div class="similar-artists-loading hidden" id="similar-artists-loading">
|
|
|
<div class="loading-spinner-small"></div>
|
|
|
<span>Finding similar artists...</span>
|
|
|
</div>
|
|
|
|
|
|
<!-- Error State -->
|
|
|
<div class="similar-artists-error hidden" id="similar-artists-error">
|
|
|
<span class="error-icon">⚠️</span>
|
|
|
<span class="error-text">Unable to load similar artists</span>
|
|
|
</div>
|
|
|
|
|
|
<!-- Similar Artists Bubbles Container -->
|
|
|
<div class="similar-artists-bubbles-container" id="similar-artists-bubbles-container">
|
|
|
<!-- Artist bubble cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Library Page -->
|
|
|
<div class="page" id="library-page">
|
|
|
<div class="library-container">
|
|
|
<!-- Header -->
|
|
|
<div class="library-header">
|
|
|
<div class="library-header-content">
|
|
|
<h2 class="library-title">📚 Music Library</h2>
|
|
|
<p class="library-subtitle">Browse your complete music collection</p>
|
|
|
</div>
|
|
|
<div class="library-stats" id="library-stats">
|
|
|
<span class="library-stat">
|
|
|
<span class="stat-number" id="library-artist-count">0</span>
|
|
|
<span class="stat-label">Artists</span>
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Search and Filters -->
|
|
|
<div class="library-controls">
|
|
|
<div class="library-search-container">
|
|
|
<input type="text"
|
|
|
id="library-search-input"
|
|
|
class="library-search-input"
|
|
|
placeholder="Search artists...">
|
|
|
<div class="library-search-icon">🔍</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Alphabet Selector -->
|
|
|
<div class="alphabet-selector" id="alphabet-selector">
|
|
|
<div class="alphabet-selector-inner">
|
|
|
<button class="alphabet-btn active" data-letter="all">All</button>
|
|
|
<button class="alphabet-btn" data-letter="a">A</button>
|
|
|
<button class="alphabet-btn" data-letter="b">B</button>
|
|
|
<button class="alphabet-btn" data-letter="c">C</button>
|
|
|
<button class="alphabet-btn" data-letter="d">D</button>
|
|
|
<button class="alphabet-btn" data-letter="e">E</button>
|
|
|
<button class="alphabet-btn" data-letter="f">F</button>
|
|
|
<button class="alphabet-btn" data-letter="g">G</button>
|
|
|
<button class="alphabet-btn" data-letter="h">H</button>
|
|
|
<button class="alphabet-btn" data-letter="i">I</button>
|
|
|
<button class="alphabet-btn" data-letter="j">J</button>
|
|
|
<button class="alphabet-btn" data-letter="k">K</button>
|
|
|
<button class="alphabet-btn" data-letter="l">L</button>
|
|
|
<button class="alphabet-btn" data-letter="m">M</button>
|
|
|
<button class="alphabet-btn" data-letter="n">N</button>
|
|
|
<button class="alphabet-btn" data-letter="o">O</button>
|
|
|
<button class="alphabet-btn" data-letter="p">P</button>
|
|
|
<button class="alphabet-btn" data-letter="q">Q</button>
|
|
|
<button class="alphabet-btn" data-letter="r">R</button>
|
|
|
<button class="alphabet-btn" data-letter="s">S</button>
|
|
|
<button class="alphabet-btn" data-letter="t">T</button>
|
|
|
<button class="alphabet-btn" data-letter="u">U</button>
|
|
|
<button class="alphabet-btn" data-letter="v">V</button>
|
|
|
<button class="alphabet-btn" data-letter="w">W</button>
|
|
|
<button class="alphabet-btn" data-letter="x">X</button>
|
|
|
<button class="alphabet-btn" data-letter="y">Y</button>
|
|
|
<button class="alphabet-btn" data-letter="z">Z</button>
|
|
|
<button class="alphabet-btn" data-letter="#">#</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Content Area -->
|
|
|
<div class="library-content">
|
|
|
<!-- Loading State -->
|
|
|
<div class="library-loading hidden" id="library-loading">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<div class="loading-text">Loading artists...</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Artist Grid -->
|
|
|
<div class="library-artists-grid" id="library-artists-grid">
|
|
|
<!-- Artist cards will be populated here -->
|
|
|
</div>
|
|
|
|
|
|
<!-- Empty State -->
|
|
|
<div class="library-empty hidden" id="library-empty">
|
|
|
<div class="empty-icon">🎵</div>
|
|
|
<div class="empty-title">No artists found</div>
|
|
|
<div class="empty-subtitle">Try adjusting your search or filters</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Pagination -->
|
|
|
<div class="library-pagination hidden" id="library-pagination">
|
|
|
<button class="pagination-btn" id="prev-page-btn" disabled>
|
|
|
<span>← Previous</span>
|
|
|
</button>
|
|
|
<div class="pagination-info">
|
|
|
<span id="page-info">Page 1 of 1</span>
|
|
|
</div>
|
|
|
<button class="pagination-btn" id="next-page-btn" disabled>
|
|
|
<span>Next →</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Artist Detail Page -->
|
|
|
<div class="page" id="artist-detail-page">
|
|
|
<div class="page-header">
|
|
|
<button class="back-btn" id="artist-detail-back-btn">
|
|
|
<span>← Back to Library</span>
|
|
|
</button>
|
|
|
|
|
|
<button class="library-artist-watchlist-btn" id="library-artist-watchlist-btn">
|
|
|
<span class="watchlist-icon">👁️</span>
|
|
|
<span class="watchlist-text">Add to Watchlist</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Artist Hero Section -->
|
|
|
<div class="artist-hero-section" id="artist-hero-section">
|
|
|
<div class="artist-hero-content">
|
|
|
<div class="artist-image-container">
|
|
|
<img class="artist-image" id="artist-detail-image" src="" alt="Artist Image" />
|
|
|
<div class="artist-image-fallback" id="artist-detail-image-fallback">🎵</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="artist-info">
|
|
|
<h1 class="artist-name" id="artist-detail-name">Artist Name</h1>
|
|
|
<div class="artist-genres-container" id="artist-genres"></div>
|
|
|
|
|
|
<div class="collection-overview">
|
|
|
<div class="collection-category">
|
|
|
<div class="category-header">
|
|
|
<span class="category-label">Albums</span>
|
|
|
<span class="category-stats" id="albums-stats">0 owned, 0 missing</span>
|
|
|
</div>
|
|
|
<div class="completion-section">
|
|
|
<div class="completion-bar">
|
|
|
<div class="completion-fill" id="albums-completion-fill" style="width: 0%"></div>
|
|
|
</div>
|
|
|
<span class="completion-text" id="albums-completion-text">0%</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="collection-category">
|
|
|
<div class="category-header">
|
|
|
<span class="category-label">EPs</span>
|
|
|
<span class="category-stats" id="eps-stats">0 owned, 0 missing</span>
|
|
|
</div>
|
|
|
<div class="completion-section">
|
|
|
<div class="completion-bar">
|
|
|
<div class="completion-fill" id="eps-completion-fill" style="width: 0%"></div>
|
|
|
</div>
|
|
|
<span class="completion-text" id="eps-completion-text">0%</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="collection-category">
|
|
|
<div class="category-header">
|
|
|
<span class="category-label">Singles</span>
|
|
|
<span class="category-stats" id="singles-stats">0 owned, 0 missing</span>
|
|
|
</div>
|
|
|
<div class="completion-section">
|
|
|
<div class="completion-bar">
|
|
|
<div class="completion-fill" id="singles-completion-fill" style="width: 0%"></div>
|
|
|
</div>
|
|
|
<span class="completion-text" id="singles-completion-text">0%</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="artist-detail-content">
|
|
|
<!-- Loading State -->
|
|
|
<div class="artist-detail-loading hidden" id="artist-detail-loading">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<p>Loading artist discography...</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Error State -->
|
|
|
<div class="artist-detail-error hidden" id="artist-detail-error">
|
|
|
<div class="error-icon">⚠️</div>
|
|
|
<h3>Failed to load artist details</h3>
|
|
|
<p id="artist-detail-error-message">An error occurred while loading the artist's discography.</p>
|
|
|
<button class="retry-btn" id="artist-detail-retry-btn">Retry</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Main Content -->
|
|
|
<div class="artist-detail-main" id="artist-detail-main">
|
|
|
|
|
|
<!-- Discography Sections -->
|
|
|
<div class="discography-sections">
|
|
|
<!-- Albums Section -->
|
|
|
<div class="discography-section" id="albums-section">
|
|
|
<div class="section-header">
|
|
|
<h3>Albums</h3>
|
|
|
<div class="section-stats">
|
|
|
<span id="albums-owned-count">0 owned</span>
|
|
|
<span id="albums-missing-count">0 missing</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="releases-grid" id="albums-grid">
|
|
|
<!-- Album cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- EPs Section -->
|
|
|
<div class="discography-section" id="eps-section">
|
|
|
<div class="section-header">
|
|
|
<h3>EPs</h3>
|
|
|
<div class="section-stats">
|
|
|
<span id="eps-owned-count">0 owned</span>
|
|
|
<span id="eps-missing-count">0 missing</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="releases-grid" id="eps-grid">
|
|
|
<!-- EP cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Singles Section -->
|
|
|
<div class="discography-section" id="singles-section">
|
|
|
<div class="section-header">
|
|
|
<h3>Singles</h3>
|
|
|
<div class="section-stats">
|
|
|
<span id="singles-owned-count">0 owned</span>
|
|
|
<span id="singles-missing-count">0 missing</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="releases-grid" id="singles-grid">
|
|
|
<!-- Single cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Discover Page -->
|
|
|
<div class="page" id="discover-page">
|
|
|
<div class="discover-container">
|
|
|
<!-- Hero Section -->
|
|
|
<div class="discover-hero">
|
|
|
<div class="discover-hero-background" id="discover-hero-bg"></div>
|
|
|
<div class="discover-hero-overlay"></div>
|
|
|
|
|
|
<!-- Navigation Arrows -->
|
|
|
<button class="discover-hero-nav discover-hero-nav-prev" onclick="navigateDiscoverHero(-1)" aria-label="Previous artist">
|
|
|
<span>‹</span>
|
|
|
</button>
|
|
|
<button class="discover-hero-nav discover-hero-nav-next" onclick="navigateDiscoverHero(1)" aria-label="Next artist">
|
|
|
<span>›</span>
|
|
|
</button>
|
|
|
|
|
|
<div class="discover-hero-content">
|
|
|
<div class="discover-hero-info">
|
|
|
<div class="discover-hero-label">FEATURED ARTIST</div>
|
|
|
<h1 class="discover-hero-title" id="discover-hero-title">Loading...</h1>
|
|
|
<p class="discover-hero-subtitle" id="discover-hero-subtitle">Discover new music tailored to your taste</p>
|
|
|
<div class="discover-hero-meta" id="discover-hero-meta">
|
|
|
<!-- Popularity and genres will be populated here -->
|
|
|
</div>
|
|
|
<div class="discover-hero-actions">
|
|
|
<button class="discover-hero-button secondary" id="discover-hero-discography" onclick="viewDiscoverHeroDiscography()">
|
|
|
<span class="button-icon">📀</span>
|
|
|
<span class="button-text">View Discography</span>
|
|
|
</button>
|
|
|
<button class="discover-hero-button primary watchlist-toggle-btn" id="discover-hero-add" onclick="toggleDiscoverHeroWatchlist(event)">
|
|
|
<span class="watchlist-icon">👁️</span>
|
|
|
<span class="watchlist-text">Add to Watchlist</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-hero-image" id="discover-hero-image">
|
|
|
<div class="hero-image-placeholder">🎧</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Slideshow Indicators -->
|
|
|
<div class="discover-hero-indicators" id="discover-hero-indicators"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Recent Releases Section -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">Recent Releases</h2>
|
|
|
<p class="discover-section-subtitle">New music from artists you follow</p>
|
|
|
</div>
|
|
|
<div class="discover-carousel" id="recent-releases-carousel">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
<div class="discover-loading">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<p>Loading recent releases...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Seasonal Albums Section (Auto-shows based on current season) -->
|
|
|
<div class="discover-section" id="seasonal-albums-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title" id="seasonal-albums-title">Seasonal</h2>
|
|
|
<p class="discover-section-subtitle" id="seasonal-albums-subtitle">Seasonal music</p>
|
|
|
</div>
|
|
|
<div class="discover-carousel" id="seasonal-albums-carousel">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Seasonal Playlist Section (Auto-shows based on current season) -->
|
|
|
<div class="discover-section" id="seasonal-playlist-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title" id="seasonal-playlist-title">Seasonal Mix</h2>
|
|
|
<p class="discover-section-subtitle" id="seasonal-playlist-subtitle">Curated seasonal playlist</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('seasonal_playlist', 'Seasonal Mix')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="seasonal-playlist-sync-btn" onclick="startDiscoverPlaylistSync('seasonal_playlist', 'Seasonal Mix')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="seasonal-playlist-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">✓ <span id="seasonal-playlist-sync-completed">0</span></span>
|
|
|
<span class="sync-stat">⏳ <span id="seasonal-playlist-sync-pending">0</span></span>
|
|
|
<span class="sync-stat">✗ <span id="seasonal-playlist-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="seasonal-playlist-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="seasonal-playlist">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Recently Added Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">🆕 Recently Added</h2>
|
|
|
<p class="discover-section-subtitle">Latest additions to your library</p>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-recently-added">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Daily Mixes Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">🎵 Daily Mixes</h2>
|
|
|
<p class="discover-section-subtitle">Personalized mixes based on your taste</p>
|
|
|
</div>
|
|
|
<div class="discover-more-playlists-grid" id="daily-mixes-grid">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Fresh Tape Section -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">Fresh Tape</h2>
|
|
|
<p class="discover-section-subtitle">New drops from recent releases</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('release_radar', 'Fresh Tape')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="release-radar-sync-btn" onclick="startDiscoverPlaylistSync('release_radar', 'Fresh Tape')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="release-radar-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">♪ <span id="release-radar-sync-total">0</span></span>
|
|
|
<span class="sync-separator">/</span>
|
|
|
<span class="sync-stat">✓ <span id="release-radar-sync-matched">0</span></span>
|
|
|
<span class="sync-separator">/</span>
|
|
|
<span class="sync-stat">✗ <span id="release-radar-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="release-radar-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="release-radar-playlist">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
<div class="discover-loading">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<p>Loading fresh tape...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- The Archives Section -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">The Archives</h2>
|
|
|
<p class="discover-section-subtitle">Curated from your collection</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('discovery_weekly', 'The Archives')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="discovery-weekly-sync-btn" onclick="startDiscoverPlaylistSync('discovery_weekly', 'The Archives')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="discovery-weekly-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">♪ <span id="discovery-weekly-sync-total">0</span></span>
|
|
|
<span class="sync-separator">/</span>
|
|
|
<span class="sync-stat">✓ <span id="discovery-weekly-sync-matched">0</span></span>
|
|
|
<span class="sync-separator">/</span>
|
|
|
<span class="sync-stat">✗ <span id="discovery-weekly-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="discovery-weekly-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="discovery-weekly-playlist">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
<div class="discover-loading">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<p>Loading the archives...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Popular Picks Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">🔥 Popular Picks</h2>
|
|
|
<p class="discover-section-subtitle">Trending tracks from new discoveries</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('popular_picks', 'Popular Picks')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="popular-picks-sync-btn" onclick="startDiscoverPlaylistSync('popular_picks', 'Popular Picks')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="popular-picks-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">✓ <span id="popular-picks-sync-completed">0</span></span>
|
|
|
<span class="sync-stat">⏳ <span id="popular-picks-sync-pending">0</span></span>
|
|
|
<span class="sync-stat">✗ <span id="popular-picks-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="popular-picks-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-popular-picks">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Hidden Gems Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">🌟 Hidden Gems</h2>
|
|
|
<p class="discover-section-subtitle">Underground discoveries waiting for you</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('hidden_gems', 'Hidden Gems')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="hidden-gems-sync-btn" onclick="startDiscoverPlaylistSync('hidden_gems', 'Hidden Gems')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="hidden-gems-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">✓ <span id="hidden-gems-sync-completed">0</span></span>
|
|
|
<span class="sync-stat">⏳ <span id="hidden-gems-sync-pending">0</span></span>
|
|
|
<span class="sync-stat">✗ <span id="hidden-gems-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="hidden-gems-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-hidden-gems">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Your Top 50 Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">🏆 Your Top 50</h2>
|
|
|
<p class="discover-section-subtitle">All-time favorites from your library</p>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-top-tracks">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Forgotten Favorites Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">💎 Forgotten Favorites</h2>
|
|
|
<p class="discover-section-subtitle">Rediscover tracks you used to love</p>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-forgotten-favorites">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Discovery Shuffle Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">🔀 Discovery Shuffle</h2>
|
|
|
<p class="discover-section-subtitle">Random tracks from your discovery pool - different every time</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('discovery_shuffle', 'Discovery Shuffle')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="discovery-shuffle-sync-btn" onclick="startDiscoverPlaylistSync('discovery_shuffle', 'Discovery Shuffle')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="discovery-shuffle-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">✓ <span id="discovery-shuffle-sync-completed">0</span></span>
|
|
|
<span class="sync-stat">⏳ <span id="discovery-shuffle-sync-pending">0</span></span>
|
|
|
<span class="sync-stat">✗ <span id="discovery-shuffle-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="discovery-shuffle-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-discovery-shuffle">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Familiar Favorites Section -->
|
|
|
<div class="discover-section" style="display: none;">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">❤️ Familiar Favorites</h2>
|
|
|
<p class="discover-section-subtitle">Your reliable go-to tracks</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForDiscoverPlaylist('familiar_favorites', 'Familiar Favorites')" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="familiar-favorites-sync-btn" onclick="startDiscoverPlaylistSync('familiar_favorites', 'Familiar Favorites')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="familiar-favorites-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">✓ <span id="familiar-favorites-sync-completed">0</span></span>
|
|
|
<span class="sync-stat">⏳ <span id="familiar-favorites-sync-pending">0</span></span>
|
|
|
<span class="sync-stat">✗ <span id="familiar-favorites-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="familiar-favorites-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="discover-playlist-container compact" id="personalized-familiar-favorites">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Build a Playlist Section -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">🎨 Build a Playlist</h2>
|
|
|
<p class="discover-section-subtitle">Create a custom playlist from your favorite artists</p>
|
|
|
</div>
|
|
|
<div class="build-playlist-container">
|
|
|
<!-- Artist Search -->
|
|
|
<div class="build-playlist-search-section">
|
|
|
<input
|
|
|
type="text"
|
|
|
id="build-playlist-search"
|
|
|
placeholder="Search for artists (1-5 artists)..."
|
|
|
oninput="searchBuildPlaylistArtists()"
|
|
|
/>
|
|
|
<div id="build-playlist-search-results" class="build-playlist-search-results"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Selected Artists -->
|
|
|
<div class="build-playlist-selected-section">
|
|
|
<h3>Selected Artists:</h3>
|
|
|
<div id="build-playlist-selected-artists" class="build-playlist-selected-artists">
|
|
|
<div class="build-playlist-no-selection">No artists selected. Search and select 1-5 artists.</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Generate Button -->
|
|
|
<div class="build-playlist-actions">
|
|
|
<button
|
|
|
id="build-playlist-generate-btn"
|
|
|
class="build-playlist-generate-btn"
|
|
|
onclick="generateBuildPlaylist()"
|
|
|
disabled
|
|
|
style="opacity: 0.5;"
|
|
|
>
|
|
|
Generate Playlist (50 tracks)
|
|
|
</button>
|
|
|
<div id="build-playlist-loading" class="build-playlist-loading" style="display: none;">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<p>Building your playlist...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Generated Playlist Results -->
|
|
|
<div id="build-playlist-results-wrapper" style="display: none;">
|
|
|
<!-- Playlist Header with Actions -->
|
|
|
<div class="discover-section-header" style="margin-top: 20px;">
|
|
|
<div>
|
|
|
<h3 id="build-playlist-results-title" style="margin: 0; color: #fff; font-size: 18px;">Generated Playlist</h3>
|
|
|
<p id="build-playlist-results-subtitle" style="margin: 4px 0 0 0; color: #999; font-size: 13px;"></p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button secondary" onclick="openDownloadModalForBuildPlaylist()" title="Download missing tracks">
|
|
|
<span class="button-icon">↓</span>
|
|
|
<span class="button-text">Download</span>
|
|
|
</button>
|
|
|
<button class="action-button primary" id="build-playlist-sync-btn" onclick="startDiscoverPlaylistSync('build_playlist', 'Custom Playlist')" title="Sync to media server">
|
|
|
<span class="button-icon">⟳</span>
|
|
|
<span class="button-text">Sync</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Sync Status Display -->
|
|
|
<div class="discover-sync-status" id="build-playlist-sync-status" style="display: none;">
|
|
|
<div class="sync-status-content">
|
|
|
<div class="sync-status-label">
|
|
|
<span class="sync-icon">⟳</span>
|
|
|
<span>Syncing to media server...</span>
|
|
|
</div>
|
|
|
<div class="sync-status-stats">
|
|
|
<span class="sync-stat">✓ <span id="build-playlist-sync-completed">0</span></span>
|
|
|
<span class="sync-stat">⏳ <span id="build-playlist-sync-pending">0</span></span>
|
|
|
<span class="sync-stat">✗ <span id="build-playlist-sync-failed">0</span></span>
|
|
|
<span class="sync-stat">(<span id="build-playlist-sync-percentage">0</span>%)</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Metadata Display -->
|
|
|
<div id="build-playlist-metadata-display"></div>
|
|
|
|
|
|
<!-- Track List -->
|
|
|
<div id="build-playlist-results" class="discover-playlist-container compact">
|
|
|
<!-- Generated playlist will appear here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- ListenBrainz Playlists (Tabbed) -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<div>
|
|
|
<h2 class="discover-section-title">🧠 ListenBrainz Playlists</h2>
|
|
|
<p class="discover-section-subtitle">Playlists from ListenBrainz</p>
|
|
|
</div>
|
|
|
<div class="discover-section-actions">
|
|
|
<button class="action-button primary" id="listenbrainz-refresh-btn" onclick="refreshListenBrainzPlaylists()" title="Refresh playlists from ListenBrainz">
|
|
|
<span class="button-icon">🔄</span>
|
|
|
<span class="button-text">Refresh</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- ListenBrainz Tabs -->
|
|
|
<div class="listenbrainz-tabs" id="listenbrainz-tabs">
|
|
|
<div class="discover-loading"><div class="loading-spinner"></div><p>Loading playlists...</p></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- ListenBrainz Tab Content -->
|
|
|
<div class="listenbrainz-tab-content" id="listenbrainz-tab-content">
|
|
|
<!-- Content will be populated dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Time Machine (Tabbed by Decade) -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">⏰ Time Machine</h2>
|
|
|
<p class="discover-section-subtitle">Explore music from different decades</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Decade Tabs (will be populated dynamically) -->
|
|
|
<div class="decade-tabs" id="decade-tabs">
|
|
|
<div class="discover-loading"><div class="loading-spinner"></div><p>Loading decades...</p></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Decade Tab Contents (will be populated dynamically) -->
|
|
|
<div id="decade-tab-contents"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Browse by Genre (Tabbed by Genre) -->
|
|
|
<div class="discover-section">
|
|
|
<div class="discover-section-header">
|
|
|
<h2 class="discover-section-title">🎵 Browse by Genre</h2>
|
|
|
<p class="discover-section-subtitle">Discover music by your favorite genres</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Genre Tabs (will be populated dynamically) -->
|
|
|
<div class="genre-tabs" id="genre-tabs">
|
|
|
<div class="discover-loading"><div class="loading-spinner"></div><p>Loading genres...</p></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Genre Tab Contents (will be populated dynamically) -->
|
|
|
<div id="genre-tab-contents"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Settings Page -->
|
|
|
<div class="page" id="settings-page">
|
|
|
<div class="page-header">
|
|
|
<h2>Settings</h2>
|
|
|
<button class="save-button" onclick="document.getElementById('save-settings').click()">💾 Save Settings</button>
|
|
|
</div>
|
|
|
<div class="settings-content">
|
|
|
<!-- Two Column Layout -->
|
|
|
<div class="settings-columns">
|
|
|
<!-- Left Column - API Configuration -->
|
|
|
<div class="settings-left-column">
|
|
|
<!-- API Configuration -->
|
|
|
<div class="settings-group">
|
|
|
<h3>API Configuration</h3>
|
|
|
|
|
|
<!-- Spotify Settings -->
|
|
|
<div class="api-service-frame">
|
|
|
<h4 class="service-title spotify-title">Spotify</h4>
|
|
|
<div class="form-group">
|
|
|
<label>Client ID:</label>
|
|
|
<input type="text" id="spotify-client-id" placeholder="Spotify Client ID">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Client Secret:</label>
|
|
|
<input type="password" id="spotify-client-secret" placeholder="Spotify Client Secret">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Redirect URI:</label>
|
|
|
<input type="text" id="spotify-redirect-uri" placeholder="http://127.0.0.1:8888/callback">
|
|
|
</div>
|
|
|
<div class="callback-info">
|
|
|
<div class="callback-label">Current Redirect URI:</div>
|
|
|
<div class="callback-url" id="spotify-callback-display">http://127.0.0.1:8888/callback</div>
|
|
|
<div class="callback-help">Add this URL to your Spotify app's 'Redirect URIs' in the Spotify Developer Dashboard</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button class="auth-button" onclick="authenticateSpotify()">🔐 Authenticate</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Tidal Settings -->
|
|
|
<div class="api-service-frame">
|
|
|
<h4 class="service-title tidal-title">Tidal</h4>
|
|
|
<div class="form-group">
|
|
|
<label>Client ID:</label>
|
|
|
<input type="text" id="tidal-client-id" placeholder="Tidal Client ID">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Client Secret:</label>
|
|
|
<input type="password" id="tidal-client-secret" placeholder="Tidal Client Secret">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Redirect URI:</label>
|
|
|
<input type="text" id="tidal-redirect-uri" placeholder="http://127.0.0.1:8889/tidal/callback">
|
|
|
</div>
|
|
|
<div class="callback-info">
|
|
|
<div class="callback-label">Current Redirect URI:</div>
|
|
|
<div class="callback-url" id="tidal-callback-display">http://127.0.0.1:8889/tidal/callback</div>
|
|
|
<div class="callback-help">Add this URL to your Tidal app configuration</div>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button class="auth-button" onclick="authenticateTidal()">🔐 Authenticate</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Soulseek Settings -->
|
|
|
<div class="api-service-frame">
|
|
|
<h4 class="service-title soulseek-title">Soulseek</h4>
|
|
|
<div class="form-group">
|
|
|
<label>slskd URL:</label>
|
|
|
<div class="path-input-group">
|
|
|
<input type="url" id="soulseek-url" placeholder="http://localhost:5030">
|
|
|
<button class="detect-button" onclick="autoDetectSlskd()">Auto-detect</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>API Key:</label>
|
|
|
<input type="password" id="soulseek-api-key" placeholder="Slskd API Key">
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- ListenBrainz Settings -->
|
|
|
<div class="api-service-frame">
|
|
|
<h4 class="service-title listenbrainz-title">ListenBrainz</h4>
|
|
|
<div class="form-group">
|
|
|
<label>User Token:</label>
|
|
|
<input type="password" id="listenbrainz-token" placeholder="ListenBrainz User Token">
|
|
|
</div>
|
|
|
<div class="callback-info">
|
|
|
<div class="callback-help">Get your token from <a href="https://listenbrainz.org/profile/" target="_blank" style="color: #eb743b;">ListenBrainz Settings</a></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Test Connection Buttons -->
|
|
|
<div class="api-test-buttons">
|
|
|
<button class="test-button" onclick="testConnection('spotify')">Test Spotify</button>
|
|
|
<button class="test-button" onclick="testConnection('tidal')">Test Tidal</button>
|
|
|
<button class="test-button" onclick="testConnection('soulseek')">Test Soulseek</button>
|
|
|
<button class="test-button" onclick="testConnection('listenbrainz')">Test ListenBrainz</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Server Connections -->
|
|
|
<div class="settings-group">
|
|
|
<h3>Server Connections</h3>
|
|
|
|
|
|
<!-- Server Toggle Buttons -->
|
|
|
<div class="server-toggle-container">
|
|
|
<button class="server-toggle-btn active" id="plex-toggle" onclick="toggleServer('plex')">
|
|
|
<img src="https://www.plex.tv/wp-content/themes/plex/assets/img/plex-logo.svg" alt="Plex" class="server-logo">
|
|
|
</button>
|
|
|
<button class="server-toggle-btn" id="jellyfin-toggle" onclick="toggleServer('jellyfin')">
|
|
|
<img src="https://jellyfin.org/images/logo.svg" alt="Jellyfin" class="server-logo">
|
|
|
</button>
|
|
|
<button class="server-toggle-btn" id="navidrome-toggle" onclick="toggleServer('navidrome')">
|
|
|
<img src="https://tweakers.net/ext/i/2007323764.png" alt="Navidrome" class="server-logo">
|
|
|
Navidrome
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<!-- Plex Settings -->
|
|
|
<div class="server-config-container" id="plex-container">
|
|
|
<div class="form-group">
|
|
|
<label>Plex Server URL:</label>
|
|
|
<input type="url" id="plex-url" placeholder="http://localhost:32400">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Plex Token:</label>
|
|
|
<input type="password" id="plex-token" placeholder="X-Plex-Token">
|
|
|
</div>
|
|
|
<div class="form-group" id="plex-library-selector-container" style="display: none;">
|
|
|
<label>Music Library:</label>
|
|
|
<select id="plex-music-library" onchange="selectPlexLibrary()">
|
|
|
<option value="">Loading...</option>
|
|
|
</select>
|
|
|
<small style="color: #999; font-size: 0.9em; display: block; margin-top: 5px;">
|
|
|
Select which music library to use (doesn't affect config file)
|
|
|
</small>
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button class="detect-button" onclick="autoDetectPlex()">Auto-detect</button>
|
|
|
<button class="test-button" onclick="testConnection('plex')">Test</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Jellyfin Settings -->
|
|
|
<div class="server-config-container hidden" id="jellyfin-container">
|
|
|
<div class="form-group">
|
|
|
<label>Jellyfin Server URL:</label>
|
|
|
<input type="url" id="jellyfin-url" placeholder="http://localhost:8096">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>API Key:</label>
|
|
|
<input type="password" id="jellyfin-api-key" placeholder="Jellyfin API Key">
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button class="detect-button" onclick="autoDetectJellyfin()">Auto-detect</button>
|
|
|
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Navidrome Settings -->
|
|
|
<div class="server-config-container hidden" id="navidrome-container">
|
|
|
<div class="form-group">
|
|
|
<label>Navidrome Server URL:</label>
|
|
|
<input type="url" id="navidrome-url" placeholder="http://localhost:4533">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Username:</label>
|
|
|
<input type="text" id="navidrome-username" placeholder="Username">
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label>Password:</label>
|
|
|
<input type="password" id="navidrome-password" placeholder="Password">
|
|
|
</div>
|
|
|
<div class="form-actions">
|
|
|
<button class="detect-button" onclick="autoDetectNavidrome()">Auto-detect</button>
|
|
|
<button class="test-button" onclick="testConnection('navidrome')">Test</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Server Test Button -->
|
|
|
<div class="server-test-section">
|
|
|
<button class="test-button server-test-btn" onclick="testConnection('server')">Test Server</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Right Column - Download Settings, Database, Metadata, Logging -->
|
|
|
<div class="settings-right-column">
|
|
|
<!-- Download Settings -->
|
|
|
<div class="settings-group">
|
|
|
<h3>Download Settings</h3>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label>Slskd Download Dir:</label>
|
|
|
<div class="path-input-group">
|
|
|
<input type="text" id="download-path" placeholder="./downloads">
|
|
|
<button class="browse-button" onclick="browsePath('download')">Browse</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label>Matched Transfer Dir (Plex Music Dir?):</label>
|
|
|
<div class="path-input-group">
|
|
|
<input type="text" id="transfer-path" placeholder="./Transfer">
|
|
|
<button class="browse-button" onclick="browsePath('transfer')">Browse</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Quality Profile Settings -->
|
|
|
<div class="settings-group">
|
|
|
<h3>🎵 Quality Profile</h3>
|
|
|
|
|
|
<!-- Presets -->
|
|
|
<div class="quality-presets">
|
|
|
<label>Quick Presets:</label>
|
|
|
<div class="preset-buttons">
|
|
|
<button class="preset-button" onclick="applyQualityPreset('audiophile')" title="FLAC only, strict size constraints">
|
|
|
🎧 Audiophile
|
|
|
</button>
|
|
|
<button class="preset-button active" onclick="applyQualityPreset('balanced')" title="FLAC preferred, MP3 fallback">
|
|
|
⚖️ Balanced
|
|
|
</button>
|
|
|
<button class="preset-button" onclick="applyQualityPreset('space_saver')" title="MP3 preferred, smaller sizes">
|
|
|
💾 Space Saver
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- FLAC Quality -->
|
|
|
<div class="quality-tier">
|
|
|
<div class="quality-tier-header">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="quality-flac-enabled" checked onchange="toggleQuality('flac')">
|
|
|
<span class="quality-tier-name">FLAC (Lossless)</span>
|
|
|
</label>
|
|
|
<span class="quality-tier-priority" id="priority-flac">Priority: 1</span>
|
|
|
</div>
|
|
|
<div class="quality-tier-sliders" id="sliders-flac">
|
|
|
<div class="slider-group">
|
|
|
<label>File Size Range:</label>
|
|
|
<div class="dual-slider-container">
|
|
|
<input type="range" class="range-slider range-slider-min" id="flac-min" min="0" max="200" value="0" step="5" oninput="updateQualityRange('flac')">
|
|
|
<input type="range" class="range-slider range-slider-max" id="flac-max" min="0" max="200" value="150" step="5" oninput="updateQualityRange('flac')">
|
|
|
<div class="range-slider-track"></div>
|
|
|
</div>
|
|
|
<div class="slider-values">
|
|
|
<span id="flac-min-value">0 MB</span>
|
|
|
<span>-</span>
|
|
|
<span id="flac-max-value">150 MB</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- MP3 320 Quality -->
|
|
|
<div class="quality-tier">
|
|
|
<div class="quality-tier-header">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="quality-mp3_320-enabled" checked onchange="toggleQuality('mp3_320')">
|
|
|
<span class="quality-tier-name">MP3 320 kbps</span>
|
|
|
</label>
|
|
|
<span class="quality-tier-priority" id="priority-mp3_320">Priority: 2</span>
|
|
|
</div>
|
|
|
<div class="quality-tier-sliders" id="sliders-mp3_320">
|
|
|
<div class="slider-group">
|
|
|
<label>File Size Range:</label>
|
|
|
<div class="dual-slider-container">
|
|
|
<input type="range" class="range-slider range-slider-min" id="mp3_320-min" min="0" max="50" value="0" step="1" oninput="updateQualityRange('mp3_320')">
|
|
|
<input type="range" class="range-slider range-slider-max" id="mp3_320-max" min="0" max="50" value="20" step="1" oninput="updateQualityRange('mp3_320')">
|
|
|
<div class="range-slider-track"></div>
|
|
|
</div>
|
|
|
<div class="slider-values">
|
|
|
<span id="mp3_320-min-value">0 MB</span>
|
|
|
<span>-</span>
|
|
|
<span id="mp3_320-max-value">20 MB</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- MP3 256 Quality -->
|
|
|
<div class="quality-tier">
|
|
|
<div class="quality-tier-header">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="quality-mp3_256-enabled" checked onchange="toggleQuality('mp3_256')">
|
|
|
<span class="quality-tier-name">MP3 256 kbps</span>
|
|
|
</label>
|
|
|
<span class="quality-tier-priority" id="priority-mp3_256">Priority: 3</span>
|
|
|
</div>
|
|
|
<div class="quality-tier-sliders" id="sliders-mp3_256">
|
|
|
<div class="slider-group">
|
|
|
<label>File Size Range:</label>
|
|
|
<div class="dual-slider-container">
|
|
|
<input type="range" class="range-slider range-slider-min" id="mp3_256-min" min="0" max="40" value="0" step="1" oninput="updateQualityRange('mp3_256')">
|
|
|
<input type="range" class="range-slider range-slider-max" id="mp3_256-max" min="0" max="40" value="15" step="1" oninput="updateQualityRange('mp3_256')">
|
|
|
<div class="range-slider-track"></div>
|
|
|
</div>
|
|
|
<div class="slider-values">
|
|
|
<span id="mp3_256-min-value">0 MB</span>
|
|
|
<span>-</span>
|
|
|
<span id="mp3_256-max-value">15 MB</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- MP3 192 Quality -->
|
|
|
<div class="quality-tier">
|
|
|
<div class="quality-tier-header">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="quality-mp3_192-enabled" onchange="toggleQuality('mp3_192')">
|
|
|
<span class="quality-tier-name">MP3 192 kbps</span>
|
|
|
</label>
|
|
|
<span class="quality-tier-priority" id="priority-mp3_192">Priority: 4</span>
|
|
|
</div>
|
|
|
<div class="quality-tier-sliders disabled" id="sliders-mp3_192">
|
|
|
<div class="slider-group">
|
|
|
<label>File Size Range:</label>
|
|
|
<div class="dual-slider-container">
|
|
|
<input type="range" class="range-slider range-slider-min" id="mp3_192-min" min="0" max="30" value="0" step="1" oninput="updateQualityRange('mp3_192')">
|
|
|
<input type="range" class="range-slider range-slider-max" id="mp3_192-max" min="0" max="30" value="12" step="1" oninput="updateQualityRange('mp3_192')">
|
|
|
<div class="range-slider-track"></div>
|
|
|
</div>
|
|
|
<div class="slider-values">
|
|
|
<span id="mp3_192-min-value">0 MB</span>
|
|
|
<span>-</span>
|
|
|
<span id="mp3_192-max-value">12 MB</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Fallback Option -->
|
|
|
<div class="form-group">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="quality-fallback-enabled" checked>
|
|
|
Allow fallback to any quality if preferred qualities unavailable
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
<div class="help-text">
|
|
|
<strong>How it works:</strong> Downloads try each enabled quality in priority order (1 = highest).
|
|
|
File size constraints filter out outliers - MAX limits catch fake files (500MB "FLACs"), MIN limits (optional) can enforce minimum quality.
|
|
|
Set MIN to 0 to accept all file sizes (recommended for most users).
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Third Column - Database, Metadata, Playlist Sync, Logging -->
|
|
|
<div class="settings-third-column">
|
|
|
<!-- Database Settings -->
|
|
|
<div class="settings-group">
|
|
|
<h3>Database Settings</h3>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label>Concurrent Workers:</label>
|
|
|
<select id="max-workers">
|
|
|
<option value="3">3</option>
|
|
|
<option value="4">4</option>
|
|
|
<option value="5" selected>5</option>
|
|
|
<option value="6">6</option>
|
|
|
<option value="7">7</option>
|
|
|
<option value="8">8</option>
|
|
|
<option value="9">9</option>
|
|
|
<option value="10">10</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
|
|
|
<div class="help-text">Number of parallel threads for database updates. Higher values = faster updates but more server load.</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Metadata Enhancement Settings -->
|
|
|
<div class="settings-group">
|
|
|
<h3>🎵 Metadata Enhancement</h3>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="metadata-enabled" checked>
|
|
|
Enable metadata enhancement with Spotify data
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="embed-album-art" checked>
|
|
|
Embed high-quality album art from Spotify
|
|
|
</label>
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label>Supported Formats:</label>
|
|
|
<div class="supported-formats">MP3, FLAC, MP4/M4A, OGG</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Playlist Sync Settings -->
|
|
|
<div class="settings-group">
|
|
|
<h3>Playlist Sync Settings</h3>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label class="checkbox-label">
|
|
|
<input type="checkbox" id="create-backup" checked>
|
|
|
Create playlist backups before sync
|
|
|
</label>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Logging Information (Read-only) -->
|
|
|
<div class="settings-group">
|
|
|
<h3>Logging Information</h3>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label>Log Level:</label>
|
|
|
<div class="readonly-field" id="log-level-display">DEBUG</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="form-group">
|
|
|
<label>Log Path:</label>
|
|
|
<div class="readonly-field" id="log-path-display">logs/app.log</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Save Button -->
|
|
|
<div class="settings-actions">
|
|
|
<button class="save-button" id="save-settings">💾 Save Settings</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Loading Overlay -->
|
|
|
<div class="loading-overlay hidden" id="loading-overlay">
|
|
|
<div class="loading-spinner"></div>
|
|
|
<div class="loading-message">Processing...</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Toast Notifications -->
|
|
|
<div class="toast-container" id="toast-container"></div>
|
|
|
|
|
|
<!-- Hidden HTML5 Audio Player for Streaming -->
|
|
|
<audio id="audio-player" style="display: none;"></audio>
|
|
|
|
|
|
<!-- Matched Download Modal -->
|
|
|
<div class="modal-overlay hidden" id="matching-modal-overlay">
|
|
|
<div class="matching-modal" id="matching-modal">
|
|
|
<div class="matching-modal-header">
|
|
|
<h2 id="matching-modal-title">Match Download to Spotify</h2>
|
|
|
<button class="matching-modal-close" onclick="closeMatchingModal()">✕</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="matching-modal-content">
|
|
|
<!-- Artist Selection Stage -->
|
|
|
<div id="artist-selection-stage" class="selection-stage">
|
|
|
<div class="stage-header">
|
|
|
<h3 id="artist-stage-title">Step 1: Select the correct Artist</h3>
|
|
|
<p class="stage-subtitle">Choose the artist that best matches your download</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="suggestions-section">
|
|
|
<h4 class="suggestions-title">Top Suggestions</h4>
|
|
|
<div class="suggestions-container" id="artist-suggestions">
|
|
|
<!-- Artist suggestion cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="manual-search-section">
|
|
|
<h4 class="suggestions-title">Or, Search Manually</h4>
|
|
|
<input type="text" id="artist-search-input" class="search-input" placeholder="Search for an artist...">
|
|
|
<div class="suggestions-container" id="artist-manual-results">
|
|
|
<!-- Manual search results will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Album Selection Stage (for album downloads) -->
|
|
|
<div id="album-selection-stage" class="selection-stage hidden">
|
|
|
<div class="stage-header">
|
|
|
<h3 id="album-stage-title">Step 2: Select the correct Album</h3>
|
|
|
<p class="stage-subtitle">Choose the album that best matches your download for <span id="selected-artist-name"></span></p>
|
|
|
</div>
|
|
|
|
|
|
<div class="suggestions-section">
|
|
|
<h4 class="suggestions-title">Top Suggestions</h4>
|
|
|
<div class="suggestions-container" id="album-suggestions">
|
|
|
<!-- Album suggestion cards will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="manual-search-section">
|
|
|
<h4 class="suggestions-title">Or, Search Manually</h4>
|
|
|
<input type="text" id="album-search-input" class="search-input" placeholder="Search for an album...">
|
|
|
<div class="suggestions-container" id="album-manual-results">
|
|
|
<!-- Manual search results will be populated here -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="matching-modal-actions">
|
|
|
<button id="skip-matching-btn" class="modal-button modal-button--secondary">Skip Matching</button>
|
|
|
<button id="cancel-match-btn" class="modal-button modal-button--cancel">Cancel</button>
|
|
|
<button id="confirm-match-btn" class="modal-button modal-button--primary" disabled>Confirm Selection</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<!-- Version Info Modal -->
|
|
|
<div class="version-modal-overlay hidden" id="version-modal-overlay" onclick="closeVersionModal()">
|
|
|
<div class="version-modal" onclick="event.stopPropagation()">
|
|
|
<!-- Header -->
|
|
|
<div class="version-modal-header">
|
|
|
<h2 class="version-modal-title">What's New in SoulSync</h2>
|
|
|
<div class="version-modal-subtitle">Version 1.0 - Complete WebUI Rebuild</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Content Area with Scroll -->
|
|
|
<div class="version-modal-content">
|
|
|
<div class="version-content-container" id="version-content-container">
|
|
|
<!-- Content will be populated by JavaScript -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Footer -->
|
|
|
<div class="version-modal-footer">
|
|
|
<button class="version-modal-close" onclick="closeVersionModal()">Close</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Add to Wishlist Modal -->
|
|
|
<div class="modal-overlay hidden" id="add-to-wishlist-modal-overlay">
|
|
|
<div class="add-to-wishlist-modal" id="add-to-wishlist-modal">
|
|
|
<div class="add-to-wishlist-modal-header">
|
|
|
<div class="add-to-wishlist-modal-hero" id="add-to-wishlist-modal-hero">
|
|
|
<!-- Hero content will be dynamically populated -->
|
|
|
</div>
|
|
|
<div class="add-to-wishlist-modal-header-actions">
|
|
|
<span class="add-to-wishlist-modal-close" onclick="closeAddToWishlistModal()">×</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="add-to-wishlist-modal-content">
|
|
|
<div class="add-to-wishlist-modal-body">
|
|
|
<div class="wishlist-track-list-container">
|
|
|
<div class="wishlist-track-list-header">
|
|
|
<h3>Tracks to Add to Wishlist</h3>
|
|
|
<p class="wishlist-track-list-subtitle">All tracks from this release will be added to your wishlist</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="wishlist-track-list" id="wishlist-track-list">
|
|
|
<!-- Track list will be dynamically populated -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="add-to-wishlist-modal-footer">
|
|
|
<div class="wishlist-modal-actions">
|
|
|
<button class="wishlist-modal-btn wishlist-modal-btn-secondary" onclick="closeAddToWishlistModal()">
|
|
|
Close
|
|
|
</button>
|
|
|
<button class="wishlist-modal-btn wishlist-modal-btn-primary" id="confirm-add-to-wishlist-btn">
|
|
|
Add to Wishlist
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Genre Browser Modal -->
|
|
|
<div class="genre-browser-modal-overlay" id="genre-browser-modal">
|
|
|
<div class="genre-browser-modal-container">
|
|
|
<div class="genre-browser-modal-header">
|
|
|
<h2 class="genre-browser-modal-title">🎵 Browse by Genre</h2>
|
|
|
<button class="genre-browser-modal-close" id="genre-browser-modal-close">
|
|
|
<span class="genre-browser-close-icon">×</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="genre-browser-modal-content">
|
|
|
<div class="genre-browser-search-section">
|
|
|
<div class="genre-browser-search-container">
|
|
|
<input type="text" class="genre-browser-search-input" placeholder="Search genres..." id="genre-browser-search">
|
|
|
<span class="genre-browser-search-icon">🔍</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="genre-browser-genres-section">
|
|
|
<div class="genre-browser-genres-grid" id="genre-browser-genres-grid">
|
|
|
<!-- Loading placeholder -->
|
|
|
<div class="genre-browser-loading-container">
|
|
|
<div class="genre-browser-loading-spinner"></div>
|
|
|
<p class="genre-browser-loading-text">🔍 Discovering current Beatport genres...</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Tool Help Modal -->
|
|
|
<div class="tool-help-modal" id="tool-help-modal">
|
|
|
<div class="tool-help-modal-content">
|
|
|
<div class="tool-help-modal-header">
|
|
|
<h3 id="tool-help-modal-title">Tool Information</h3>
|
|
|
<button class="tool-help-modal-close">×</button>
|
|
|
</div>
|
|
|
<div class="tool-help-modal-body" id="tool-help-modal-body">
|
|
|
<!-- Content will be dynamically inserted -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Right Sidebar Download Indicator (Global - outside all containers) -->
|
|
|
<div class="discover-download-sidebar" id="discover-download-sidebar">
|
|
|
<div class="discover-download-sidebar-header">
|
|
|
<span class="discover-download-sidebar-icon">🎵</span>
|
|
|
<span class="discover-download-sidebar-title">Downloads</span>
|
|
|
<span class="discover-download-sidebar-count" id="discover-download-count">0</span>
|
|
|
</div>
|
|
|
<div class="discover-download-bubbles" id="discover-download-bubbles">
|
|
|
<!-- Download bubbles will be added here dynamically -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
|
|
</body>
|
|
|
</html> |