From bc2be55455b695199d474ba2edb4798f2dfa8917 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Mon, 29 Sep 2025 23:35:55 -0700 Subject: [PATCH] beatport progress --- web_server.py | 132 +++++++++++++++ webui/index.html | 33 ++++ webui/static/script.js | 322 ++++++++++++++++++++++++++++++----- webui/static/style.css | 376 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 822 insertions(+), 41 deletions(-) diff --git a/web_server.py b/web_server.py index 7d3bba85..1e85c203 100644 --- a/web_server.py +++ b/web_server.py @@ -2210,6 +2210,138 @@ def get_beatport_new_releases(): 'releases': [] }), 500 + +@app.route('/api/beatport/featured-charts') +def get_beatport_featured_charts(): + """Get featured charts from Beatport for the charts slider grid using GridSlider approach""" + try: + logger.info("đŸ”Ĩ Fetching Beatport featured charts...") + + # Initialize scraper + scraper = BeatportUnifiedScraper() + + # Get page and extract charts + soup = scraper.get_page(scraper.base_url) + if not soup: + raise Exception("Could not fetch Beatport homepage") + + # Find Featured Charts GridSlider container (like New Releases) + gridsliders = soup.select('[class*="GridSlider-style__Wrapper"]') + featured_container = None + + logger.info(f"🔍 Checking {len(gridsliders)} GridSlider containers for featured charts...") + + for container in gridsliders: + h2 = container.select_one('h2') + if h2: + title = h2.get_text(strip=True).lower() + logger.info(f"📋 Found section: '{h2.get_text(strip=True)}'") + + if 'featured' in title and 'chart' in title: + featured_container = container + logger.info(f"đŸ”Ĩ FOUND FEATURED CHARTS: '{h2.get_text(strip=True)}'") + break + + if not featured_container: + logger.warning("❌ No Featured Charts GridSlider container found") + return jsonify({ + 'success': False, + 'error': 'Featured Charts section not found', + 'charts': [] + }) + + # Extract charts from the container using chart links + charts = [] + chart_links = featured_container.select('a[href*="/chart/"]') + + logger.info(f"📊 Found {len(chart_links)} chart links in Featured Charts section") + + for i, link in enumerate(chart_links[:100]): # Limit to 100 for 10 slides + chart_data = {} + + # Extract chart name from link text or nearby elements + name_elem = link.select_one('h3, h4, h5, h6, [class*="title"], [class*="Title"], [class*="name"], [class*="Name"]') + if name_elem: + name_text = name_elem.get_text(strip=True) + else: + name_text = link.get_text(strip=True) + + if name_text and len(name_text) > 2 and name_text.lower() not in ['featured charts', 'buy', 'play']: + chart_data['name'] = name_text + + # Extract creator using the specific CSS class pattern from chart cards + creator = 'Beatport' # Default + + # Look for the ChartCard Name class that contains the creator + creator_elem = link.select_one('[class*="ChartCard-style__Name"]') + if creator_elem: + creator_text = creator_elem.get_text(strip=True) + if creator_text and len(creator_text) > 1 and creator_text.lower() not in ['by', 'chart', 'featured', 'beatport']: + creator = creator_text + elif creator_text.lower() == 'beatport': + creator = 'Beatport' + else: + # Fallback: look for other creator indicators + parent = link.parent + if parent: + fallback_selectors = [ + '[class*="artist"]', '[class*="Artist"]', + '[class*="creator"]', '[class*="Creator"]', + '[class*="author"]', '[class*="Author"]' + ] + + for selector in fallback_selectors: + fallback_elem = parent.select_one(selector) + if fallback_elem: + fallback_text = fallback_elem.get_text(strip=True) + if fallback_text and len(fallback_text) > 1 and fallback_text.lower() not in ['by', 'chart', 'featured']: + creator = fallback_text + break + + chart_data['creator'] = creator + + # Extract URL + href = link.get('href', '') + if href: + if href.startswith('/'): + chart_data['url'] = f"https://www.beatport.com{href}" + else: + chart_data['url'] = href + + # Extract image + img_elem = link.select_one('img') or (link.parent.select_one('img') if link.parent else None) + if img_elem: + src = img_elem.get('src', '') or img_elem.get('data-src', '') + if src: + if src.startswith('//'): + src = f"https:{src}" + elif src.startswith('/'): + src = f"https://www.beatport.com{src}" + chart_data['image'] = src + + # Only add if we have meaningful data + if 'name' in chart_data and 'url' in chart_data: + charts.append(chart_data) + logger.info(f"✅ Chart {len(charts)}: {chart_data['name']} by {chart_data['creator']}") + + logger.info(f"📊 Successfully extracted {len(charts)} featured charts") + + return jsonify({ + 'success': True, + 'charts': charts, + 'count': len(charts), + 'slides': (len(charts) + 9) // 10, # Calculate number of slides needed + 'timestamp': datetime.now().isoformat() + }) + + except Exception as e: + logger.error(f"❌ Error fetching featured charts: {str(e)}") + return jsonify({ + 'success': False, + 'error': str(e), + 'charts': [] + }), 500 + # --- Placeholder API Endpoints for Other Pages --- @app.route('/api/activity') diff --git a/webui/index.html b/webui/index.html index 86b1b827..8079084c 100644 --- a/webui/index.html +++ b/webui/index.html @@ -838,6 +838,39 @@ + + +
+
+

đŸ”Ĩ Featured Charts

+

Top chart collections from Beatport creators

+
+ +
+
+
+ +
+
+

📊 Loading Featured Charts...

+

Fetching top chart collections

+
+
+
+ + +
+ + +
+ + +
+ +
+
+
+
diff --git a/webui/static/script.js b/webui/static/script.js index 71428b59..c39d53e5 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -9735,6 +9735,7 @@ function initializeSyncPage() { if (tabId === 'rebuild') { initializeBeatportRebuildSlider(); initializeBeatportReleasesSlider(); + initializeBeatportChartsSlider(); } }); }); @@ -18364,8 +18365,6 @@ function setupBeatportSliderFunctionality() { // Set up indicators setupBeatportRebuildSliderIndicators(); - // Set up slide click handlers - setupBeatportRebuildSlideClickHandlers(); // Start auto-play startBeatportRebuildSliderAutoPlay(); @@ -18496,43 +18495,6 @@ function setupBeatportRebuildSliderHoverPause() { } } -/** - * Set up click handlers for slides to open Beatport URLs - */ -function setupBeatportRebuildSlideClickHandlers() { - const slides = document.querySelectorAll('.beatport-rebuild-slide'); - - slides.forEach((slide, index) => { - slide.addEventListener('click', (e) => { - e.preventDefault(); - - const url = slide.dataset.url; - if (url && url !== '#') { - console.log(`đŸŽĩ Opening Beatport track: ${url}`); - window.open(url, '_blank'); - } else { - console.log(`â„šī¸ No URL available for slide ${index + 1}`); - } - }); - - // Add cursor pointer style for clickable slides - if (slide.dataset.url && slide.dataset.url !== '#') { - slide.style.cursor = 'pointer'; - - // Add hover effect - slide.addEventListener('mouseenter', () => { - slide.style.transform = 'scale(1.02)'; - slide.style.transition = 'transform 0.3s ease'; - }); - - slide.addEventListener('mouseleave', () => { - slide.style.transform = 'scale(1)'; - }); - } - }); - - console.log(`🔗 Set up click handlers for ${slides.length} slides`); -} /** * Clean up beatport rebuild slider when switching away @@ -18658,7 +18620,7 @@ function populateBeatportReleasesSlider(releases) { if (i < slideReleases.length) { const release = slideReleases[i]; gridHtml += ` -
+
${release.image_url ? `${release.title}` : ''} @@ -18866,3 +18828,283 @@ function cleanupBeatportReleasesSlider() { beatportReleasesSliderState.autoPlayInterval = null; } } + +// =================================== +// BEATPORT FEATURED CHARTS SLIDER +// =================================== + +// State management for featured charts slider (copied from releases slider) +let beatportChartsSliderState = { + currentSlide: 0, + totalSlides: 0, + autoPlayInterval: null, + autoPlayDelay: 10000, // Slightly longer auto-play for charts + isInitialized: false +}; + +/** + * Initialize the beatport featured charts slider functionality (based on releases slider) + */ +function initializeBeatportChartsSlider() { + console.log('đŸ”Ĩ Initializing beatport featured charts slider...'); + + const slider = document.getElementById('beatport-charts-slider'); + if (!slider) { + console.warn('Beatport charts slider not found'); + return; + } + + // Prevent double initialization + if (slider.dataset.initialized === 'true') { + console.log('Charts slider already initialized'); + return; + } + + const sliderTrack = document.getElementById('beatport-charts-slider-track'); + const indicatorsContainer = document.getElementById('beatport-charts-slider-indicators'); + + if (!sliderTrack || !indicatorsContainer) { + console.warn('Charts slider elements not found'); + return; + } + + // Load data and initialize + loadBeatportFeaturedCharts().then(success => { + if (success) { + setupBeatportChartsSliderNavigation(); + setupBeatportChartsSliderIndicators(); + setupBeatportChartsSliderHoverPause(); + startBeatportChartsSliderAutoPlay(); + slider.dataset.initialized = 'true'; + beatportChartsSliderState.isInitialized = true; + console.log('✅ Featured charts slider initialized successfully'); + } + }); +} + +/** + * Load featured charts data from API + */ +async function loadBeatportFeaturedCharts() { + try { + console.log('📊 Loading featured charts data...'); + const response = await fetch('/api/beatport/featured-charts'); + const data = await response.json(); + + if (data.success && data.charts && data.charts.length > 0) { + console.log(`📈 Loaded ${data.charts.length} featured charts`); + createBeatportChartsSlides(data.charts); + return true; + } else { + console.warn('No featured charts data available'); + return false; + } + } catch (error) { + console.error('❌ Error loading featured charts:', error); + return false; + } +} + +/** + * Create chart slides with grid layout (copied from releases slider) + */ +function createBeatportChartsSlides(charts) { + const sliderTrack = document.getElementById('beatport-charts-slider-track'); + const indicatorsContainer = document.getElementById('beatport-charts-slider-indicators'); + + if (!sliderTrack || !indicatorsContainer) { + console.error('Charts slider elements not found'); + return; + } + + const cardsPerSlide = 10; // 5x2 grid + const totalSlides = Math.ceil(charts.length / cardsPerSlide); + + // Clear existing content + sliderTrack.innerHTML = ''; + indicatorsContainer.innerHTML = ''; + + // Update state + beatportChartsSliderState.totalSlides = totalSlides; + beatportChartsSliderState.currentSlide = 0; + + console.log(`đŸŽ¯ Creating ${totalSlides} chart slides with ${cardsPerSlide} cards each`); + + // Generate slides HTML + for (let slideIndex = 0; slideIndex < totalSlides; slideIndex++) { + const startIndex = slideIndex * cardsPerSlide; + const endIndex = Math.min(startIndex + cardsPerSlide, charts.length); + const slideCharts = charts.slice(startIndex, endIndex); + + // Create grid HTML for this slide + const gridHtml = slideCharts.map(chart => { + const bgImageStyle = chart.image ? `--chart-bg-image: url('${chart.image}')` : ''; + return ` +
+
+
${chart.name || 'Unknown Chart'}
+
${chart.creator || 'Unknown Creator'}
+
+
+ `; + }).join(''); + + // Create slide HTML + const slideHtml = ` +
+
+ ${gridHtml} +
+
+ `; + + sliderTrack.innerHTML += slideHtml; + + // Create indicator + const indicatorHtml = ``; + indicatorsContainer.innerHTML += indicatorHtml; + } + + console.log(`✅ Created ${totalSlides} chart slides`); +} + +/** + * Set up navigation functionality (copied from releases slider with button cloning) + */ +function setupBeatportChartsSliderNavigation() { + const prevBtn = document.getElementById('beatport-charts-prev-btn'); + const nextBtn = document.getElementById('beatport-charts-next-btn'); + + if (prevBtn) { + // Clone button to remove all existing event listeners + const newPrevBtn = prevBtn.cloneNode(true); + prevBtn.parentNode.replaceChild(newPrevBtn, prevBtn); + + newPrevBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + console.log('Previous charts button clicked, current slide:', beatportChartsSliderState.currentSlide); + goToBeatportChartsSlide(beatportChartsSliderState.currentSlide - 1); + resetBeatportChartsSliderAutoPlay(); + }); + } + + if (nextBtn) { + // Clone button to remove all existing event listeners + const newNextBtn = nextBtn.cloneNode(true); + nextBtn.parentNode.replaceChild(newNextBtn, nextBtn); + + newNextBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + console.log('Next charts button clicked, current slide:', beatportChartsSliderState.currentSlide); + goToBeatportChartsSlide(beatportChartsSliderState.currentSlide + 1); + resetBeatportChartsSliderAutoPlay(); + }); + } +} + +/** + * Set up indicator functionality (copied from releases slider) + */ +function setupBeatportChartsSliderIndicators() { + const indicators = document.querySelectorAll('.beatport-charts-indicator'); + + indicators.forEach((indicator, index) => { + indicator.addEventListener('click', () => { + goToBeatportChartsSlide(index); + resetBeatportChartsSliderAutoPlay(); + }); + }); +} + +/** + * Navigate to a specific slide (copied from releases slider) + */ +function goToBeatportChartsSlide(slideIndex) { + console.log('goToBeatportChartsSlide called with:', slideIndex, 'current:', beatportChartsSliderState.currentSlide); + + // Wrap around if out of bounds + if (slideIndex < 0) { + slideIndex = beatportChartsSliderState.totalSlides - 1; + } else if (slideIndex >= beatportChartsSliderState.totalSlides) { + slideIndex = 0; + } + + console.log('After wrapping, slideIndex:', slideIndex); + + // Update current slide + beatportChartsSliderState.currentSlide = slideIndex; + + // Update slide visibility + const slides = document.querySelectorAll('.beatport-charts-slide'); + slides.forEach((slide, index) => { + slide.classList.remove('active', 'prev', 'next'); + + if (index === slideIndex) { + slide.classList.add('active'); + } else if (index < slideIndex) { + slide.classList.add('prev'); + } else { + slide.classList.add('next'); + } + }); + + // Update indicators + const indicators = document.querySelectorAll('.beatport-charts-indicator'); + indicators.forEach((indicator, index) => { + indicator.classList.toggle('active', index === slideIndex); + }); + + console.log('Charts slide updated to:', beatportChartsSliderState.currentSlide); +} + +/** + * Start auto-play functionality (copied from releases slider) + */ +function startBeatportChartsSliderAutoPlay() { + if (beatportChartsSliderState.autoPlayInterval) { + clearInterval(beatportChartsSliderState.autoPlayInterval); + } + + beatportChartsSliderState.autoPlayInterval = setInterval(() => { + goToBeatportChartsSlide(beatportChartsSliderState.currentSlide + 1); + }, beatportChartsSliderState.autoPlayDelay); +} + +/** + * Reset auto-play timer (copied from releases slider) + */ +function resetBeatportChartsSliderAutoPlay() { + startBeatportChartsSliderAutoPlay(); +} + +/** + * Set up hover pause functionality (copied from releases slider) + */ +function setupBeatportChartsSliderHoverPause() { + const sliderContainer = document.querySelector('.beatport-charts-slider-container'); + + if (sliderContainer) { + sliderContainer.addEventListener('mouseenter', () => { + if (beatportChartsSliderState.autoPlayInterval) { + clearInterval(beatportChartsSliderState.autoPlayInterval); + beatportChartsSliderState.autoPlayInterval = null; + } + }); + + sliderContainer.addEventListener('mouseleave', () => { + startBeatportChartsSliderAutoPlay(); + }); + } +} + +/** + * Clean up charts slider when switching away (copied from releases slider) + */ +function cleanupBeatportChartsSlider() { + if (beatportChartsSliderState.autoPlayInterval) { + clearInterval(beatportChartsSliderState.autoPlayInterval); + beatportChartsSliderState.autoPlayInterval = null; + } +} diff --git a/webui/static/style.css b/webui/static/style.css index 60841f63..7584b851 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -4158,7 +4158,8 @@ body { display: grid; grid-template-columns: 2.5fr 0.75fr; /* More space for main panel, smaller sidebar */ gap: 25px; - min-height: calc(100vh - 200px); /* Minimum height, but allow expansion */ + /* min-height: calc(90vh - 200px); Minimum height, but allow expansion */ + height: 95%; } .sync-main-panel, .sync-sidebar { @@ -12419,3 +12420,376 @@ body { font-size: 24px; } } + +/* ==================== FEATURED CHARTS SLIDER STYLES ==================== */ + +/* Charts Section */ +.beatport-charts-section { + margin-top: 40px; + padding: 0 30px; +} + +.beatport-charts-header { + text-align: center; + margin-bottom: 30px; +} + +.beatport-charts-title { + font-size: 32px; + font-weight: 700; + color: #ffffff; + margin-bottom: 8px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); +} + +.beatport-charts-subtitle { + font-size: 16px; + color: rgba(255, 255, 255, 0.8); + margin: 0; +} + +/* Charts Slider Container */ +.beatport-charts-slider-container { + position: relative; + background: linear-gradient(135deg, + rgba(16, 16, 16, 0.4), + rgba(24, 24, 24, 0.2)); + border-radius: 20px; + border: 1px solid rgba(255, 255, 255, 0.06); + backdrop-filter: blur(15px); + min-height: 600px; + padding: 30px; +} + +.beatport-charts-slider { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + border-radius: 16px; +} + +.beatport-charts-slider-track { + position: relative; + height: 550px; +} + +.beatport-charts-slide { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + transition: all 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94); + transform: translateX(100px); +} + +.beatport-charts-slide.active { + opacity: 1; + transform: translateX(0); + z-index: 10; +} + +.beatport-charts-slide.prev { + opacity: 0; + transform: translateX(-100px); +} + +.beatport-charts-slide.next { + opacity: 0; + transform: translateX(100px); +} + +/* Charts Grid */ +.beatport-charts-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-template-rows: repeat(2, 1fr); + gap: 20px; + padding: 20px; + background: linear-gradient(135deg, + rgba(16, 16, 16, 0.6), + rgba(24, 24, 24, 0.4)); + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + justify-items: center; + align-items: center; + margin: 0 auto; +} + +/* Chart Card Styling - Large Background Photo Design */ +.beatport-chart-card { + position: relative; + width: 100%; + height: 200px; + min-height: 200px; + max-height: 200px; + border-radius: 16px; + cursor: pointer; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + overflow: hidden; + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.4), + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.beatport-chart-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--chart-bg-image, linear-gradient(135deg, #1db954, #191414)); + background-size: cover; + background-position: center; + border-radius: 16px; + z-index: 1; +} + +.beatport-chart-card::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 135deg, + rgba(0, 0, 0, 0.2) 0%, + rgba(0, 0, 0, 0.4) 50%, + rgba(0, 0, 0, 0.7) 100% + ); + border-radius: 16px; + z-index: 2; +} + +.beatport-chart-card:hover { + transform: translateY(-12px) scale(1.08); + box-shadow: + 0 25px 50px rgba(0, 0, 0, 0.7), + 0 0 40px rgba(29, 185, 84, 0.5), + inset 0 1px 0 rgba(255, 255, 255, 0.3); + z-index: 20; +} + +.beatport-chart-card:hover::after { + background: linear-gradient( + 135deg, + rgba(29, 185, 84, 0.2) 0%, + rgba(0, 0, 0, 0.2) 50%, + rgba(0, 0, 0, 0.5) 100% + ); +} + +.beatport-chart-card:hover .beatport-chart-name { + color: rgba(29, 185, 84, 0.95); + text-shadow: 0 2px 12px rgba(0, 0, 0, 0.9); + transform: translateY(-2px); +} + +.beatport-chart-card:hover .beatport-chart-creator { + color: #ffffff; + text-shadow: 0 1px 8px rgba(0, 0, 0, 0.9); + transform: translateY(-1px); +} + +.beatport-chart-card-content { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 20px; + z-index: 3; + text-align: left; +} + +.beatport-chart-name { + font-size: 18px; + font-weight: 700; + color: #ffffff; + margin-bottom: 8px; + line-height: 1.2; + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.beatport-chart-creator { + font-size: 14px; + color: rgba(255, 255, 255, 0.9); + font-weight: 500; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +/* Charts Loading State */ +.beatport-charts-loading { + display: flex; + align-items: center; + justify-content: center; + height: 500px; + background: linear-gradient(135deg, + rgba(29, 185, 84, 0.1), + rgba(16, 16, 16, 0.8)); + border-radius: 16px; + border: 1px solid rgba(29, 185, 84, 0.2); +} + +.beatport-charts-loading-content { + text-align: center; + color: #ffffff; +} + +.beatport-charts-loading-content h3 { + font-size: 24px; + margin-bottom: 8px; + color: rgba(29, 185, 84, 0.9); +} + +.beatport-charts-loading-content p { + font-size: 16px; + color: rgba(255, 255, 255, 0.7); + margin: 0; +} + +/* Charts Navigation */ +.beatport-charts-slider-nav { + position: absolute; + top: 50%; + left: 0; + right: 0; + transform: translateY(-50%); + display: flex; + justify-content: space-between; + padding: 0 20px; + pointer-events: none; + z-index: 100; +} + +.beatport-charts-nav-btn { + background: linear-gradient(135deg, + rgba(29, 185, 84, 0.9), + rgba(29, 185, 84, 0.7)); + border: none; + color: #ffffff; + font-size: 24px; + font-weight: 600; + width: 50px; + height: 50px; + border-radius: 50%; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); + backdrop-filter: blur(10px); + border: 2px solid rgba(255, 255, 255, 0.2); + box-shadow: + 0 4px 16px rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.2); + pointer-events: auto; + display: flex; + align-items: center; + justify-content: center; +} + +.beatport-charts-nav-btn:hover { + background: linear-gradient(135deg, + rgba(29, 185, 84, 1), + rgba(24, 160, 72, 0.9)); + transform: scale(1.1); + box-shadow: + 0 8px 24px rgba(0, 0, 0, 0.4), + 0 0 20px rgba(29, 185, 84, 0.5), + inset 0 1px 0 rgba(255, 255, 255, 0.3); +} + +.beatport-charts-nav-btn:active { + transform: scale(0.95); +} + +/* Charts Indicators */ +.beatport-charts-slider-indicators { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 12px; + z-index: 10; +} + +.beatport-charts-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.4); + border: none; + cursor: pointer; + transition: all 0.3s ease; +} + +.beatport-charts-indicator.active { + background: rgba(29, 185, 84, 0.9); + transform: scale(1.2); + box-shadow: 0 0 10px rgba(29, 185, 84, 0.5); +} + +.beatport-charts-indicator:hover { + background: rgba(255, 255, 255, 0.6); + transform: scale(1.1); +} + +/* Charts Responsive Design */ +@media (max-width: 1400px) { + .beatport-charts-grid { + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(2, 1fr); + } +} + +@media (max-width: 1200px) { + .beatport-charts-grid { + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 1fr); + gap: 16px; + } + + .beatport-chart-card { + height: 180px; + min-height: 180px; + max-height: 180px; + } +} + +@media (max-width: 900px) { + .beatport-charts-grid { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(3, 1fr); + gap: 14px; + } + + .beatport-charts-slider-container { + padding: 20px; + } +} + +@media (max-width: 600px) { + .beatport-charts-grid { + grid-template-columns: 1fr; + grid-template-rows: repeat(5, 1fr); + gap: 12px; + } + + .beatport-charts-section { + padding: 0 15px; + } + + .beatport-charts-title { + font-size: 24px; + } +}