-
Beatport Top 100
-
The hottest electronic tracks right now
-
100 tracks
+
+
+
🎵 Genre Explorer
+
+
+
🎵
+
+
Browse All Genres
+
House, Techno, Trance, and 36 more genres
+
39 Genres
+
-
-
📀
-
-
Top 100 New Releases
-
Newest and most popular releases on Beatport
-
100 tracks
+
+
+
+
+
📊 Main Charts
+
+
+
🔥
+
+
Beatport Top 10
+
Current hottest tracks
+
10 tracks
+
+
+
+
💯
+
+
Beatport Top 100
+
Complete chart rankings
+
100 tracks
+
-
-
🚀
-
-
Hype Top 100
-
Rising stars and breakthrough tracks
-
100 tracks
+
+
+
+
+
🎵 Releases
+
+
+
🆕
+
+
Top 10 Releases
+
Newest releases trending
+
10 releases
+
+
+
+
📊
+
+
Top 100 Releases
+
All trending releases
+
100 releases
+
+
+
+
🕒
+
+
Latest Releases
+
Recently published
+
50 releases
+
+
+
+
+
+
+
+
🔥 Hype
+
+
+
🚀
+
+
Hype Top 10
+
Hottest trending tracks
+
10 tracks
+
+
+
+
🔥
+
+
Hype Top 100
+
Complete hype chart rankings
+
100 tracks
+
+
+
+
⚡
+
+
Hype Picks
+
Editor selected hype tracks
+
50 tracks
+
@@ -692,55 +735,6 @@
-
-
-
-
- Browse Charts > Staff Picks
-
-
-
-
⭐
-
-
Best New Tracks
-
Editor's picks of the week
-
20 tracks
-
-
-
-
🔫
-
-
Secret Weapons
-
Underground gems for DJs
-
15 tracks
-
-
-
-
🌅
-
-
Closing Essentials
-
Perfect tracks to end the night
-
12 tracks
-
-
-
-
🔥
-
-
Peak Time Driving
-
High-energy dancefloor destroyers
-
25 tracks
-
-
-
-
💎
-
-
Underground Gems
-
Hidden treasures from emerging artists
-
18 tracks
-
-
-
-
diff --git a/webui/static/script.js b/webui/static/script.js
index 13f935a9..7c290df4 100644
--- a/webui/static/script.js
+++ b/webui/static/script.js
@@ -9733,14 +9733,18 @@ function initializeSyncPage() {
});
});
- // Logic for Beatport category cards
- const beatportCategoryCards = document.querySelectorAll('.beatport-category-card');
- beatportCategoryCards.forEach(card => {
- card.addEventListener('click', () => {
- const category = card.dataset.category;
- handleBeatportCategoryClick(category);
+ // Logic for Homepage Genre Explorer card
+ const genreExplorerCard = document.querySelector('[data-action="show-genres"]');
+ if (genreExplorerCard) {
+ genreExplorerCard.addEventListener('click', () => {
+ console.log('🎵 Genre Explorer card clicked');
+ showBeatportSubView('genres');
+ loadBeatportGenres();
});
- });
+ }
+
+ // Setup homepage chart handlers (following genre page pattern to prevent duplicates)
+ setupHomepageChartTypeHandlers();
// Logic for Beatport breadcrumb back buttons
const beatportBackButtons = document.querySelectorAll('.breadcrumb-back');
@@ -10188,19 +10192,12 @@ async function clearBeatportPlaylists() {
function handleBeatportCategoryClick(category) {
console.log(`🎵 Beatport category clicked: ${category}`);
- // Show the appropriate sub-view based on category
+ // Only handle genres category now - homepage has direct chart buttons
switch(category) {
- case 'top-charts':
- showBeatportSubView('top-charts');
- loadBeatportTopCharts(); // Load top charts dynamically
- break;
case 'genres':
showBeatportSubView('genres');
loadBeatportGenres(); // Load genres dynamically
break;
- case 'staff-picks':
- showBeatportSubView('staff-picks');
- break;
default:
showToast(`Unknown category: ${category}`, 'error');
}
@@ -10369,159 +10366,148 @@ async function loadGenreImagesProgressively(genres) {
console.log(`✅ Progressive image loading complete: ${imagesLoaded}/${genres.length} images loaded`);
}
-async function loadBeatportTopCharts() {
- console.log('🔥 Loading Beatport top charts dynamically...');
+function setupHomepageChartTypeHandlers() {
+ console.log('🔧 Setting up homepage chart type handlers...');
- const chartList = document.querySelector('#beatport-top-charts-view .beatport-chart-list');
- if (!chartList) {
- console.error('❌ Could not find chart list element');
+ // Select all homepage chart type cards (following genre page pattern)
+ const chartTypeCards = document.querySelectorAll('.homepage-main-charts-section .genre-chart-type-card[data-chart-type], .homepage-releases-section .genre-chart-type-card[data-chart-type], .homepage-hype-section .genre-chart-type-card[data-chart-type]');
+
+ chartTypeCards.forEach(card => {
+ // Remove existing listeners by cloning (following genre page pattern)
+ card.replaceWith(card.cloneNode(true));
+ });
+
+ // Re-select after cloning to ensure clean event listeners (following genre page pattern)
+ const newChartTypeCards = document.querySelectorAll('.homepage-main-charts-section .genre-chart-type-card[data-chart-type], .homepage-releases-section .genre-chart-type-card[data-chart-type], .homepage-hype-section .genre-chart-type-card[data-chart-type]');
+
+ newChartTypeCards.forEach(card => {
+ card.addEventListener('click', () => {
+ const chartType = card.dataset.chartType;
+ const chartEndpoint = card.dataset.chartEndpoint;
+ const chartName = card.querySelector('.chart-type-info h3').textContent;
+ console.log(`🔥 Homepage chart clicked: ${chartName} (${chartType})`);
+ handleHomepageChartTypeClick(chartType, chartEndpoint, chartName);
+ });
+ });
+
+ console.log(`✅ Setup ${newChartTypeCards.length} homepage chart handlers`);
+}
+
+async function handleHomepageChartTypeClick(chartType, chartEndpoint, chartName) {
+ console.log(`🔥 Homepage chart type clicked: ${chartType} (${chartName})`);
+
+ // Map chart types to API endpoints and create descriptive names (following genre page pattern)
+ const chartTypeMap = {
+ 'top-10': {
+ endpoint: `/api/beatport/top-100`, // Use top-100 endpoint and limit to 10
+ name: `Beatport Top 10`,
+ limit: 10
+ },
+ 'top-100': {
+ endpoint: `/api/beatport/top-100`,
+ name: `Beatport Top 100`,
+ limit: 100
+ },
+ 'releases-top-10': {
+ endpoint: `/api/beatport/homepage/releases-top-10`, // Placeholder for future
+ name: `Top 10 Releases`,
+ limit: 10
+ },
+ 'releases-top-100': {
+ endpoint: `/api/beatport/top-100-releases`,
+ name: `Top 100 Releases`,
+ limit: 100
+ },
+ 'latest-releases': {
+ endpoint: `/api/beatport/homepage/latest-releases`, // Placeholder for future
+ name: `Latest Releases`,
+ limit: 50
+ },
+ 'hype-top-10': {
+ endpoint: `/api/beatport/hype-top-100`, // Use hype-100 endpoint and limit to 10
+ name: `Hype Top 10`,
+ limit: 10
+ },
+ 'hype-top-100': {
+ endpoint: `/api/beatport/hype-top-100`,
+ name: `Hype Top 100`,
+ limit: 100
+ },
+ 'hype-picks': {
+ endpoint: `/api/beatport/homepage/hype-picks`, // Placeholder for future
+ name: `Hype Picks`,
+ limit: 50
+ }
+ };
+
+ const chartConfig = chartTypeMap[chartType];
+ if (!chartConfig) {
+ console.error(`❌ Unknown homepage chart type: ${chartType}`);
+ showToast(`Unknown chart type: ${chartType}`, 'error');
return;
}
- // Show loading state
- chartList.innerHTML = `
-
-
-
🔥 Discovering current Beatport top charts...
-
- `;
-
try {
- // Define the top charts we want to show (based on our working endpoints)
- const topCharts = [
- {
- id: 'beatport-top-100',
- name: 'Beatport Top 100',
- description: 'The hottest electronic tracks right now',
- icon: '🏆',
- endpoint: '/api/beatport/top-100',
- trackCount: '100 tracks'
- },
- {
- id: 'top-100-releases',
- name: 'Top 100 New Releases',
- description: 'Newest and most popular releases on Beatport',
- icon: '📀',
- endpoint: '/api/beatport/top-100-releases',
- trackCount: '100 tracks'
- },
- {
- id: 'hype-top-100',
- name: 'Hype Top 100',
- description: 'Trending tracks gaining momentum',
- icon: '🚀',
- endpoint: '/api/beatport/hype-top-100',
- trackCount: '100 tracks'
- }
- ];
-
- // Generate chart cards dynamically
- const chartCardsHTML = topCharts.map(chart => `
-
-
${chart.icon}
-
-
${chart.name}
-
${chart.description}
-
${chart.trackCount}
-
-
- `).join('');
-
- chartList.innerHTML = chartCardsHTML;
-
- // Add click handlers to dynamically created chart items
- const chartItems = chartList.querySelectorAll('.beatport-chart-item');
- chartItems.forEach(item => {
- item.addEventListener('click', () => {
- const chartType = item.dataset.chartType;
- const chartId = item.dataset.chartId;
- const chartName = item.dataset.chartName;
- const chartEndpoint = item.dataset.chartEndpoint;
- handleBeatportTopChartClick(chartType, chartId, chartName, chartEndpoint);
- });
- });
-
- console.log(`✅ Loaded ${topCharts.length} Beatport top charts dynamically`);
- showToast(`Loaded ${topCharts.length} top charts`, 'success');
-
- } catch (error) {
- console.error('❌ Error loading Beatport top charts:', error);
- chartList.innerHTML = `
-
-
❌ Failed to load top charts: ${error.message}
-
-
- `;
- showToast(`Error loading Beatport top charts: ${error.message}`, 'error');
- }
-}
-
-async function handleBeatportTopChartClick(chartType, chartId, chartName, chartEndpoint) {
- console.log(`🔥 Beatport top chart clicked: ${chartName} - CREATING PLAYLIST CARD`);
-
- try {
- // Check if we already have a card for this chart
+ // Check if we already have a card for this specific chart type (following genre page pattern)
const existingState = Object.values(beatportChartStates).find(state =>
- state.chart && state.chart.name === chartName && state.chart.chart_type === chartType
+ state.chart && state.chart.name === chartConfig.name && state.chart.chart_type === `homepage_${chartType}`
);
if (existingState) {
- console.log(`🔄 Found existing Beatport card for ${chartName}, opening existing modal`);
+ console.log(`🔄 Found existing Beatport card for ${chartConfig.name}, opening existing modal`);
handleBeatportCardClick(existingState.chart.hash);
return;
}
- // First, create a chart hash for state management
- const chartHash = `${chartType}_${chartId}_${Date.now()}`;
+ // Create a chart hash for state management (following genre page pattern)
+ const chartHash = `homepage_${chartType}_${Date.now()}`;
- showToast(`Loading ${chartName}...`, 'info');
+ showToast(`Loading ${chartConfig.name}...`, 'info');
- // Fetch tracks from the chart endpoint
- const response = await fetch(`${chartEndpoint}?limit=100`);
+ // Fetch tracks from the specific endpoint (following genre page pattern)
+ const response = await fetch(`${chartConfig.endpoint}?limit=${chartConfig.limit}`);
if (!response.ok) {
- throw new Error(`Failed to fetch ${chartName}: ${response.status}`);
+ throw new Error(`Failed to fetch ${chartConfig.name}: ${response.status}`);
}
const data = await response.json();
if (!data.success || !data.tracks || data.tracks.length === 0) {
- throw new Error(`No tracks found in ${chartName}`);
+ throw new Error(`No tracks found in ${chartConfig.name}`);
}
- // Create chart data object for playlist card
+ // Create chart data object for playlist card (following genre page pattern)
const chartData = {
hash: chartHash,
- name: chartName,
- chart_type: chartType,
+ name: chartConfig.name,
+ chart_type: `homepage_${chartType}`,
track_count: data.tracks.length,
tracks: data.tracks.map(track => ({
name: track.title || 'Unknown Title',
artists: [track.artist || 'Unknown Artist'],
- album: chartName,
+ album: chartConfig.name,
duration_ms: 0,
external_urls: { beatport: track.url || '' },
source: 'beatport'
}))
};
- // Add card to container (in background, like YouTube does)
- console.log(`🃏 Creating Beatport playlist card for: ${chartName}`);
+ // Add card to container (in background, like YouTube does - following genre page pattern)
+ console.log(`🃏 Creating Beatport playlist card for: ${chartConfig.name}`);
addBeatportCardToContainer(chartData);
- // Automatically open discovery modal (like when you click a YouTube or Tidal card in fresh state)
+ // Automatically open discovery modal (like when you click a YouTube or Tidal card in fresh state - following genre page pattern)
handleBeatportCardClick(chartHash);
- console.log(`✅ Created Beatport card and opened discovery modal for ${chartName}`);
+ console.log(`✅ Created Beatport card and opened discovery modal for ${chartConfig.name}`);
} catch (error) {
- console.error(`❌ Error loading ${chartName}:`, error);
- showToast(`Error loading ${chartName}: ${error.message}`, 'error');
+ console.error(`❌ Error loading ${chartConfig.name}:`, error);
+ showToast(`Error loading ${chartConfig.name}: ${error.message}`, 'error');
}
}
+
+
async function openBeatportDiscoveryModal(chartHash, chartData) {
console.log(`🎵 Opening Beatport discovery modal (reusing YouTube modal): ${chartData.name}`);
diff --git a/webui/static/style.css b/webui/static/style.css
index 5d8c9a0f..5d6e95bb 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -4893,7 +4893,11 @@ body {
.genre-main-charts-section,
.genre-releases-section,
.genre-editorial-section,
-.genre-new-charts-section {
+.genre-new-charts-section,
+.homepage-genre-section,
+.homepage-main-charts-section,
+.homepage-releases-section,
+.homepage-hype-section {
margin-bottom: 40px;
padding: 20px;
background: linear-gradient(135deg,
@@ -5065,6 +5069,52 @@ body {
letter-spacing: 0.5px;
}
+/* Empty Section Placeholder */
+.empty-section-placeholder {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ padding: 40px 20px;
+ background: linear-gradient(135deg,
+ rgba(30, 30, 30, 0.5) 0%,
+ rgba(20, 20, 20, 0.7) 100%);
+ border-radius: 12px;
+ border: 2px dashed rgba(255, 255, 255, 0.15);
+ text-align: center;
+ opacity: 0.8;
+ transition: all 0.3s ease;
+ min-height: 120px;
+ justify-content: center;
+}
+
+.empty-section-placeholder:hover {
+ opacity: 1;
+ border-color: rgba(255, 255, 255, 0.25);
+ background: linear-gradient(135deg,
+ rgba(35, 35, 35, 0.6) 0%,
+ rgba(25, 25, 25, 0.8) 100%);
+}
+
+.empty-section-icon {
+ font-size: 48px;
+ opacity: 0.6;
+ filter: grayscale(1);
+}
+
+.empty-section-text h4 {
+ font-size: 18px;
+ font-weight: 600;
+ color: rgba(255, 255, 255, 0.8);
+ margin: 0 0 8px 0;
+}
+
+.empty-section-text p {
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.5);
+ margin: 0;
+ font-style: italic;
+}
+
.expand-indicator {
position: absolute;
top: 50%;
@@ -5520,7 +5570,11 @@ body {
.genre-main-charts-section,
.genre-releases-section,
.genre-editorial-section,
- .genre-new-charts-section {
+ .genre-new-charts-section,
+ .homepage-genre-section,
+ .homepage-main-charts-section,
+ .homepage-releases-section,
+ .homepage-hype-section {
margin-bottom: 24px;
padding: 16px;
}