| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| (function() { |
| 'use strict'; |
|
|
| |
| const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || |
| (window.innerWidth < 768); |
| |
| |
| const mobileFeatures = { |
| gesturesEnabled: true, |
| hapticFeedback: true, |
| adaptiveLayout: true, |
| touchOptimized: true, |
| swipeNavigation: true, |
| pullToRefresh: true |
| }; |
|
|
| |
| function initEnhancedSpotify() { |
| |
| const style = document.createElement('style'); |
| style.textContent = getCSSStyles(); |
| document.head.appendChild(style); |
|
|
| |
| createVisualizerContainer(); |
|
|
| |
| if (isMobile && mobileFeatures.gesturesEnabled) { |
| setupMobileGestures(); |
| } |
|
|
| |
| setupAudioAnalysis(); |
|
|
| |
| applyUIEnhancements(); |
|
|
| |
| setupResponsiveAdjustments(); |
|
|
| console.log('✨ Spotify Ultimate Mobile UI/UX Enhancer loaded successfully!'); |
| console.log(`📱 Mobile Mode: ${isMobile ? 'Enabled' : 'Disabled'}`); |
| } |
|
|
| |
| function getCSSStyles() { |
| return ` |
| /* Mobile-first base styles */ |
| :root { |
| --primary-gradient: linear-gradient(45deg, #1DB954, #1ED760, #00ffcc); |
| --secondary-gradient: linear-gradient(45deg, #8A2BE2, #9370DB, #BA55D3); |
| --accent-gradient: linear-gradient(45deg, #ff00cc, #3333ff, #00ffcc); |
| --glow-color: rgba(0, 255, 204, 0.7); |
| --mobile-radius: 24px; |
| --desktop-radius: 12px; |
| --transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
| --transition-normal: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); |
| --transition-slow: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); |
| --ease-out-back: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.15); |
| --shadow-md: 0 4px 15px rgba(0, 0, 0, 0.2); |
| --shadow-lg: 0 8px 30px rgba(0, 255, 204, 0.4); |
| --shadow-glow: 0 0 20px rgba(0, 255, 204, 0.5); |
| } |
| |
| /* Mobile optimization */ |
| @media (max-width: 768px) { |
| :root { |
| --mobile-radius: 28px; |
| --desktop-radius: 16px; |
| } |
| } |
| |
| /* Global enhancements */ |
| * { |
| transition: var(--transition-fast); |
| } |
| |
| /* Enhanced buttons - mobile optimized */ |
| button, .Button-sc-1dqy6sn-0, .ButtonInner-sc-1dqy6sn-1 { |
| transition: var(--transition-normal) !important; |
| transform: scale(1) !important; |
| background: var(--primary-gradient) !important; |
| background-size: 200% 200% !important; |
| animation: gradientBG 3s ease infinite !important; |
| box-shadow: var(--shadow-sm) !important; |
| border: none !important; |
| border-radius: 16px !important; |
| padding: 8px 16px !important; |
| font-weight: 600 !important; |
| will-change: transform, box-shadow !important; |
| } |
| |
| button:active, .Button-sc-1dqy6sn-0:active, .ButtonInner-sc-1dqy6sn-1:active { |
| transform: scale(0.95) !important; |
| box-shadow: 0 2px 6px rgba(0, 255, 204, 0.3) !important; |
| } |
| |
| button:hover, .Button-sc-1dqy6sn-0:hover, .ButtonInner-sc-1dqy6sn-1:hover { |
| transform: scale(1.05) !important; |
| box-shadow: var(--shadow-md) !important; |
| filter: brightness(1.1) !important; |
| } |
| |
| /* Play button pulse */ |
| .Button-sc-1dqy6sn-0.Lsg1a0PdZMXQ3vQZlE0_M { |
| animation: pulse 2s infinite !important; |
| position: relative !important; |
| z-index: 10 !important; |
| box-shadow: 0 0 0 rgba(0, 255, 204, 0.4) !important; |
| animation-name: pulse, gradientBG !important; |
| animation-duration: 2s, 3s !important; |
| animation-timing-function: ease, ease !important; |
| animation-iteration-count: infinite, infinite !important; |
| } |
| |
| /* Card enhancements with mobile radius */ |
| .Card-sc-1uyc430-0, .main-card-card, .main-shelf-shelf { |
| transition: var(--ease-out-back) !important; |
| transform: translateY(0) !important; |
| box-shadow: var(--shadow-sm) !important; |
| border-radius: var(--mobile-radius) !important; |
| overflow: hidden !important; |
| margin: 8px 0 !important; |
| will-change: transform, box-shadow !important; |
| background: rgba(255, 255, 255, 0.05) !important; |
| backdrop-filter: blur(10px) !important; |
| border: 1px solid rgba(255, 255, 255, 0.1) !important; |
| } |
| |
| .Card-sc-1uyc430-0:hover, .main-card-card:hover, .main-shelf-shelf:hover { |
| transform: translateY(-8px) !important; |
| box-shadow: var(--shadow-lg) !important; |
| z-index: 100 !important; |
| background: rgba(255, 255, 255, 0.08) !important; |
| } |
| |
| /* Album art with mobile optimization */ |
| .cover-art-image, .main-image-image { |
| transition: var(--ease-out-back) !important; |
| transform-origin: center !important; |
| border-radius: var(--mobile-radius) !important; |
| overflow: hidden !important; |
| will-change: transform, box-shadow !important; |
| } |
| |
| .cover-art-image:hover, .main-image-image:hover { |
| transform: scale(1.12) rotate(2deg) !important; |
| box-shadow: var(--shadow-glow) !important; |
| z-index: 1000 !important; |
| } |
| |
| /* Mobile touch feedback */ |
| [role="button"], [role="link"], .main-trackList-row { |
| touch-action: manipulation !important; |
| -webkit-tap-highlight-color: transparent !important; |
| } |
| |
| [role="button"]:active, [role="link"]:active, .main-trackList-row:active { |
| transform: scale(0.98) !important; |
| opacity: 0.9 !important; |
| } |
| |
| /* Now playing bar - mobile enhanced */ |
| .now-playing-bar, .playback-bar { |
| transition: var(--transition-slow) !important; |
| background: rgba(0, 0, 0, 0.9) !important; |
| backdrop-filter: blur(15px) !important; |
| border-top: 1px solid rgba(0, 255, 204, 0.3) !important; |
| padding: 8px !important; |
| border-radius: var(--mobile-radius) var(--mobile-radius) 0 0 !important; |
| margin-bottom: env(safe-area-inset-bottom) !important; |
| } |
| |
| .now-playing-bar:hover { |
| transform: translateY(-5px) !important; |
| box-shadow: 0 -10px 30px rgba(0, 255, 204, 0.3) !important; |
| } |
| |
| /* Visualizer container - mobile responsive */ |
| #visualizer-container { |
| position: fixed; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| height: 120px; |
| background: rgba(0, 0, 0, 0.85); |
| backdrop-filter: blur(15px); |
| z-index: 9999; |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| padding: 0 16px; |
| border-top: 1px solid rgba(0, 255, 204, 0.3); |
| opacity: 0.95; |
| transition: var(--transition-normal); |
| transform: translateY(0); |
| padding-bottom: calc(env(safe-area-inset-bottom) + 16px); |
| } |
| |
| #visualizer-container.minimized { |
| height: 60px; |
| } |
| |
| #visualizer-container.collapsed { |
| transform: translateY(100%); |
| opacity: 0; |
| pointer-events: none; |
| } |
| |
| /* Visualizer canvas */ |
| #audio-visualizer { |
| width: 100%; |
| height: 60px; |
| display: block; |
| border-radius: 12px; |
| overflow: hidden; |
| } |
| |
| /* Mobile controls */ |
| .visualizer-controls { |
| position: absolute; |
| bottom: calc(env(safe-area-inset-bottom) + 16px); |
| right: 16px; |
| display: flex; |
| gap: 12px; |
| z-index: 10000; |
| flex-direction: row; |
| } |
| |
| .visualizer-btn { |
| width: 48px; |
| height: 48px; |
| border-radius: 24px; |
| background: rgba(255, 255, 255, 0.1); |
| border: 2px solid rgba(0, 255, 204, 0.5); |
| color: white; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| cursor: pointer; |
| font-size: 18px; |
| transition: all 0.2s ease; |
| touch-action: manipulation; |
| -webkit-tap-highlight-color: transparent; |
| } |
| |
| .visualizer-btn:active { |
| transform: scale(0.9); |
| background: rgba(0, 255, 204, 0.2); |
| } |
| |
| .visualizer-btn:hover { |
| background: rgba(0, 255, 204, 0.25); |
| transform: scale(1.05); |
| } |
| |
| /* Progress bar mobile optimization */ |
| .progress-bar, .volume-bar { |
| height: 6px !important; |
| background: rgba(255, 255, 255, 0.15) !important; |
| border-radius: 3px !important; |
| overflow: hidden !important; |
| position: relative !important; |
| touch-action: manipulation !important; |
| } |
| |
| .progress-bar__slider, .volume-bar__slider { |
| width: 24px !important; |
| height: 24px !important; |
| background: var(--accent-gradient) !important; |
| border: 3px solid white !important; |
| box-shadow: 0 0 15px var(--glow-color) !important; |
| transition: all 0.2s ease !important; |
| touch-action: manipulation !important; |
| } |
| |
| .progress-bar__slider:active, .volume-bar__slider:active { |
| transform: scale(1.3) !important; |
| box-shadow: 0 0 25px var(--glow-color) !important; |
| } |
| |
| /* Mobile navigation enhancements */ |
| .main-navBar-navBar, .main-buddyFeed-buddyFeed { |
| background: rgba(0, 0, 0, 0.7) !important; |
| backdrop-filter: blur(10px) !important; |
| border-radius: var(--desktop-radius) !important; |
| margin: 8px !important; |
| border: 1px solid rgba(255, 255, 255, 0.1) !important; |
| } |
| |
| /* Text animations */ |
| .Type__TypeElement-sc-goli3j-0 { |
| transition: opacity 0.3s ease, transform 0.3s ease !important; |
| } |
| |
| .Type__TypeElement-sc-goli3j-0:hover { |
| opacity: 1 !important; |
| transform: translateY(-2px) !important; |
| } |
| |
| /* Gradient text */ |
| .glow-text { |
| background: var(--accent-gradient); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| text-shadow: 0 0 10px rgba(0, 255, 204, 0.3); |
| background-size: 200% 200%; |
| animation: gradientText 3s ease infinite; |
| } |
| |
| /* Mobile gesture indicators */ |
| .gesture-indicator { |
| position: fixed; |
| bottom: 20%; |
| left: 50%; |
| transform: translateX(-50%); |
| color: rgba(0, 255, 204, 0.8); |
| font-size: 14px; |
| padding: 8px 16px; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 20px; |
| backdrop-filter: blur(5px); |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| pointer-events: none; |
| z-index: 9999; |
| } |
| |
| /* Mobile swipe areas */ |
| .swipe-area { |
| position: fixed; |
| height: 100%; |
| width: 60px; |
| z-index: 9998; |
| touch-action: none; |
| } |
| |
| .swipe-area.left { |
| left: 0; |
| background: linear-gradient(to right, rgba(0, 255, 204, 0.1) 0%, transparent 100%); |
| } |
| |
| .swipe-area.right { |
| right: 0; |
| background: linear-gradient(to left, rgba(0, 255, 204, 0.1) 0%, transparent 100%); |
| } |
| |
| /* Pulsing indicator for interactive elements */ |
| .pulse-indicator { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| border-radius: inherit; |
| background: radial-gradient(circle, rgba(0, 255, 204, 0.4) 0%, transparent 70%); |
| opacity: 0; |
| pointer-events: none; |
| animation: pulseIndicator 0.6s ease-out; |
| } |
| |
| /* Fullscreen visualizer overlay */ |
| #fullscreen-visualizer-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: rgba(0, 0, 0, 0.95); |
| backdrop-filter: blur(5px); |
| z-index: 99999; |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| opacity: 0; |
| transform: scale(0.9); |
| transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); |
| pointer-events: none; |
| padding: 20px; |
| } |
| |
| #fullscreen-visualizer-overlay.active { |
| opacity: 1; |
| transform: scale(1); |
| pointer-events: all; |
| } |
| |
| #fullscreen-visualizer { |
| width: 100%; |
| max-width: 800px; |
| height: 60vh; |
| border-radius: 24px; |
| overflow: hidden; |
| box-shadow: 0 0 50px rgba(0, 255, 204, 0.5); |
| } |
| |
| .fullscreen-controls { |
| margin-top: 20px; |
| display: flex; |
| gap: 15px; |
| } |
| |
| /* Keyframes */ |
| @keyframes gradientBG { |
| 0% { background-position: 0% 50% } |
| 50% { background-position: 100% 50% } |
| 100% { background-position: 0% 50% } |
| } |
| |
| @keyframes gradientText { |
| 0% { background-position: 0% 50% } |
| 50% { background-position: 100% 50% } |
| 100% { background-position: 0% 50% } |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 255, 204, 0.4); } |
| 70% { transform: scale(1.05); box-shadow: 0 0 0 15px rgba(0, 255, 204, 0); } |
| 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 255, 204, 0); } |
| } |
| |
| @keyframes pulseIndicator { |
| 0% { transform: scale(0.8); opacity: 0.7; } |
| 100% { transform: scale(1.2); opacity: 0; } |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| @keyframes slideUp { |
| from { transform: translateY(100%); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| |
| /* Mobile-specific animations */ |
| .mobile-enter { |
| animation: slideUp 0.4s cubic-bezier(0.23, 1, 0.32, 1) forwards; |
| } |
| |
| /* Dark mode optimization */ |
| @media (prefers-color-scheme: dark) { |
| .Card-sc-1uyc430-0, .main-card-card { |
| background: rgba(30, 30, 30, 0.8) !important; |
| } |
| } |
| |
| /* Reduce motion for accessibility */ |
| @media (prefers-reduced-motion: reduce) { |
| * { |
| transition: none !important; |
| animation: none !important; |
| } |
| .Card-sc-1uyc430-0:hover, .main-card-card:hover { |
| transform: none !important; |
| box-shadow: var(--shadow-sm) !important; |
| } |
| .cover-art-image:hover, .main-image-image:hover { |
| transform: none !important; |
| box-shadow: none !important; |
| } |
| } |
| |
| /* Mobile viewport optimization */ |
| @media (max-width: 480px) { |
| #visualizer-container { |
| height: 100px; |
| padding: 0 12px; |
| padding-bottom: calc(env(safe-area-inset-bottom) + 12px); |
| } |
| |
| .visualizer-btn { |
| width: 42px; |
| height: 42px; |
| font-size: 16px; |
| } |
| |
| .progress-bar__slider, .volume-bar__slider { |
| width: 22px !important; |
| height: 22px !important; |
| } |
| |
| .main-type-element { |
| font-size: 14px !important; |
| } |
| } |
| `; |
| } |
|
|
| |
| function createVisualizerContainer() { |
| const container = document.createElement('div'); |
| container.id = 'visualizer-container'; |
| container.className = isMobile ? 'mobile-enter' : ''; |
|
|
| container.innerHTML = ` |
| <canvas id="audio-visualizer"></canvas> |
| <div class="visualizer-controls"> |
| <div class="visualizer-btn" id="visualizer-toggle">❚❚</div> |
| <div class="visualizer-btn" id="visualizer-fullscreen">⛶</div> |
| <div class="visualizer-btn" id="visualizer-preset">🎨</div> |
| </div> |
| <div class="gesture-indicator" id="gesture-indicator">Swipe up for more</div> |
| `; |
| |
| |
| if (isMobile && mobileFeatures.swipeNavigation) { |
| const leftSwipe = document.createElement('div'); |
| leftSwipe.className = 'swipe-area left'; |
| leftSwipe.id = 'left-swipe-area'; |
| |
| const rightSwipe = document.createElement('div'); |
| rightSwipe.className = 'swipe-area right'; |
| rightSwipe.id = 'right-swipe-area'; |
| |
| document.body.appendChild(leftSwipe); |
| document.body.appendChild(rightSwipe); |
| } |
|
|
| |
| const fullscreenOverlay = document.createElement('div'); |
| fullscreenOverlay.id = 'fullscreen-visualizer-overlay'; |
| fullscreenOverlay.innerHTML = ` |
| <canvas id="fullscreen-visualizer"></canvas> |
| <div class="fullscreen-controls"> |
| <div class="visualizer-btn" id="fullscreen-close">✕</div> |
| <div class="visualizer-btn" id="fullscreen-preset">🎨</div> |
| </div> |
| `; |
| |
| document.body.appendChild(container); |
| document.body.appendChild(fullscreenOverlay); |
|
|
| |
| setupVisualizerEvents(); |
| } |
|
|
| |
| function setupVisualizerEvents() { |
| |
| document.getElementById('visualizer-container').addEventListener('click', (e) => { |
| if (isMobile && !e.target.closest('.visualizer-btn')) { |
| const container = document.getElementById('visualizer-container'); |
| container.classList.toggle('minimized'); |
| |
| |
| const indicator = document.getElementById('gesture-indicator'); |
| if (!container.classList.contains('minimized')) { |
| indicator.style.opacity = '1'; |
| setTimeout(() => { |
| indicator.style.opacity = '0'; |
| }, 2000); |
| } |
| } |
| }, { passive: true }); |
|
|
| |
| document.getElementById('visualizer-toggle').addEventListener('click', function() { |
| const container = document.getElementById('visualizer-container'); |
| container.classList.toggle('collapsed'); |
| this.textContent = container.classList.contains('collapsed') ? '▶️' : '❚❚'; |
| }); |
|
|
| |
| document.getElementById('visualizer-fullscreen').addEventListener('click', function() { |
| const overlay = document.getElementById('fullscreen-visualizer-overlay'); |
| overlay.classList.add('active'); |
| setupFullscreenVisualizer(); |
| }); |
|
|
| |
| document.getElementById('fullscreen-close').addEventListener('click', function() { |
| document.getElementById('fullscreen-visualizer-overlay').classList.remove('active'); |
| }); |
|
|
| |
| let currentPreset = 0; |
| const presets = ['bars', 'wave', 'particles', 'circular']; |
| |
| document.getElementById('visualizer-preset').addEventListener('click', function() { |
| currentPreset = (currentPreset + 1) % presets.length; |
| setCurrentVisualizerPreset(presets[currentPreset]); |
| showMobileToast(`Visualizer: ${presets[currentPreset].charAt(0).toUpperCase() + presets[currentPreset].slice(1)}`); |
| }); |
|
|
| document.getElementById('fullscreen-preset').addEventListener('click', function() { |
| currentPreset = (currentPreset + 1) % presets.length; |
| setCurrentVisualizerPreset(presets[currentPreset]); |
| showMobileToast(`Fullscreen: ${presets[currentPreset].charAt(0).toUpperCase() + presets[currentPreset].slice(1)}`); |
| }); |
| } |
|
|
| |
| function setupMobileGestures() { |
| let startY = 0; |
| let currentY = 0; |
| let isSwiping = false; |
| |
| const container = document.getElementById('visualizer-container'); |
| const indicator = document.getElementById('gesture-indicator'); |
|
|
| |
| container.addEventListener('touchstart', (e) => { |
| startY = e.touches[0].clientY; |
| currentY = startY; |
| isSwiping = true; |
| indicator.style.opacity = '0'; |
| }, { passive: true }); |
|
|
| |
| container.addEventListener('touchmove', (e) => { |
| if (!isSwiping) return; |
| |
| currentY = e.touches[0].clientY; |
| const deltaY = currentY - startY; |
| |
| |
| if (deltaY < -20 && !container.classList.contains('minimized')) { |
| indicator.style.opacity = '1'; |
| } else if (deltaY > 20) { |
| indicator.style.opacity = '0'; |
| } |
| |
| |
| if (mobileFeatures.pullToRefresh && deltaY > 0 && container.classList.contains('minimized')) { |
| container.style.transform = `translateY(${deltaY/3}px)`; |
| } |
| }, { passive: true }); |
|
|
| |
| container.addEventListener('touchend', (e) => { |
| if (!isSwiping) return; |
| |
| const deltaY = currentY - startY; |
| |
| |
| if (deltaY < -50 && container.classList.contains('minimized')) { |
| container.classList.remove('minimized'); |
| if (mobileFeatures.hapticFeedback) triggerHapticFeedback('light'); |
| } |
| |
| |
| else if (deltaY > 50 && !container.classList.contains('minimized')) { |
| container.classList.add('minimized'); |
| if (mobileFeatures.hapticFeedback) triggerHapticFeedback('light'); |
| } |
| |
| |
| else if (mobileFeatures.pullToRefresh && deltaY > 100 && container.classList.contains('minimized')) { |
| showMobileToast('Refreshing...'); |
| if (mobileFeatures.hapticFeedback) triggerHapticFeedback('medium'); |
| setTimeout(() => { |
| location.reload(); |
| }, 1000); |
| } |
| |
| container.style.transform = ''; |
| isSwiping = false; |
| setTimeout(() => { |
| indicator.style.opacity = '0'; |
| }, 1000); |
| }, { passive: true }); |
|
|
| |
| if (mobileFeatures.swipeNavigation) { |
| setupSwipeNavigation(); |
| } |
| } |
|
|
| |
| function setupSwipeNavigation() { |
| let touchStartX = 0; |
| let touchEndX = 0; |
| |
| document.getElementById('left-swipe-area').addEventListener('touchstart', (e) => { |
| touchStartX = e.touches[0].clientX; |
| }, { passive: true }); |
|
|
| document.getElementById('left-swipe-area').addEventListener('touchend', (e) => { |
| touchEndX = e.changedTouches[0].clientX; |
| if (touchStartX - touchEndX > 50) { |
| navigatePreviousTrack(); |
| showMobileToast('⏮ Previous Track'); |
| if (mobileFeatures.hapticFeedback) triggerHapticFeedback('light'); |
| } |
| }, { passive: true }); |
|
|
| document.getElementById('right-swipe-area').addEventListener('touchstart', (e) => { |
| touchStartX = e.touches[0].clientX; |
| }, { passive: true }); |
|
|
| document.getElementById('right-swipe-area').addEventListener('touchend', (e) => { |
| touchEndX = e.changedTouches[0].clientX; |
| if (touchEndX - touchStartX > 50) { |
| navigateNextTrack(); |
| showMobileToast('⏭ Next Track'); |
| if (mobileFeatures.hapticFeedback) triggerHapticFeedback('light'); |
| } |
| }, { passive: true }); |
| } |
|
|
| |
| function setupAudioAnalysis() { |
| const observer = new MutationObserver(mutations => { |
| mutations.forEach(mutation => { |
| if (mutation.addedNodes.length) { |
| const audioElements = document.querySelectorAll('audio'); |
| if (audioElements.length > 0) { |
| initializeVisualizer(audioElements[0]); |
| observer.disconnect(); |
| } |
| } |
| }); |
| }); |
|
|
| observer.observe(document.body, { |
| childList: true, |
| subtree: true |
| }); |
|
|
| |
| let audioCheckInterval = setInterval(() => { |
| const audioElements = document.querySelectorAll('audio'); |
| if (audioElements.length > 0) { |
| initializeVisualizer(audioElements[0]); |
| clearInterval(audioCheckInterval); |
| observer.disconnect(); |
| } |
| }, 1000); |
| } |
|
|
| |
| function initializeVisualizer(audioElement) { |
| const canvas = document.getElementById('audio-visualizer'); |
| const ctx = canvas.getContext('2d'); |
| const container = document.getElementById('visualizer-container'); |
| |
| |
| function resizeCanvas() { |
| const dpr = window.devicePixelRatio || 1; |
| canvas.width = (container.clientWidth - 32) * dpr; |
| canvas.height = (container.clientHeight - 40) * dpr; |
| canvas.style.width = `${container.clientWidth - 32}px`; |
| canvas.style.height = `${container.clientHeight - 40}px`; |
| ctx.scale(dpr, dpr); |
| } |
| |
| resizeCanvas(); |
| window.addEventListener('resize', resizeCanvas); |
|
|
| |
| try { |
| const AudioContext = window.AudioContext || window.webkitAudioContext; |
| const audioContext = new AudioContext(); |
| const analyzer = audioContext.createAnalyser(); |
| analyzer.fftSize = 256; |
| analyzer.smoothingTimeConstant = 0.8; |
| |
| const source = audioContext.createMediaElementSource(audioElement); |
| source.connect(analyzer); |
| analyzer.connect(audioContext.destination); |
| |
| const bufferLength = analyzer.frequencyBinCount; |
| const dataArray = new Uint8Array(bufferLength); |
| |
| let currentPreset = 'bars'; |
| let isPlaying = true; |
| let animationFrame; |
| |
| function setCurrentVisualizerPreset(preset) { |
| currentPreset = preset; |
| } |
|
|
| function drawVisualizer() { |
| if (!isPlaying) { |
| animationFrame = requestAnimationFrame(drawVisualizer); |
| return; |
| } |
|
|
| analyzer.getByteFrequencyData(dataArray); |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| switch(currentPreset) { |
| case 'bars': |
| drawBars(ctx, dataArray, canvas.width, canvas.height); |
| break; |
| case 'wave': |
| drawWave(ctx, dataArray, canvas.width, canvas.height); |
| break; |
| case 'particles': |
| drawParticles(ctx, dataArray, canvas.width, canvas.height); |
| break; |
| case 'circular': |
| drawCircular(ctx, dataArray, canvas.width, canvas.height); |
| break; |
| } |
| |
| animationFrame = requestAnimationFrame(drawVisualizer); |
| } |
| |
| drawVisualizer(); |
| |
| |
| window.addEventListener('beforeunload', () => { |
| cancelAnimationFrame(animationFrame); |
| if (audioContext.state !== 'closed') { |
| audioContext.close(); |
| } |
| }); |
|
|
| } catch (error) { |
| console.error('Error initializing audio visualizer:', error); |
| createFallbackVisualizer(ctx, canvas.width, canvas.height); |
| } |
| } |
|
|
| |
| function drawBars(ctx, dataArray, width, height) { |
| const barWidth = (width / dataArray.length) * 2.5; |
| let x = 0; |
| const centerY = height / 2; |
| |
| for (let i = 0; i < dataArray.length; i++) { |
| const barHeight = (dataArray[i] / 255) * height * 0.8; |
| const hue = (i / dataArray.length) * 360; |
| |
| |
| const gradient = ctx.createLinearGradient(0, centerY + barHeight/2, 0, centerY - barHeight/2); |
| gradient.addColorStop(0, `hsla(${hue}, 80%, 40%, 0.3)`); |
| gradient.addColorStop(0.5, `hsla(${hue}, 100%, 60%, 0.8)`); |
| gradient.addColorStop(1, `hsla(${hue}, 80%, 40%, 0.3)`); |
| |
| ctx.fillStyle = gradient; |
| ctx.fillRect(x, centerY - barHeight/2, barWidth, barHeight); |
| |
| |
| ctx.shadowColor = `hsla(${hue}, 100%, 60%, 0.7)`; |
| ctx.shadowBlur = 8; |
| |
| x += barWidth + 1; |
| } |
| |
| ctx.shadowBlur = 0; |
| } |
|
|
| function drawWave(ctx, dataArray, width, height) { |
| const centerY = height / 2; |
| const amplitude = height * 0.4; |
| |
| ctx.beginPath(); |
| ctx.moveTo(0, centerY); |
| |
| for (let i = 0; i < dataArray.length; i++) { |
| const x = (i / dataArray.length) * width; |
| const y = centerY + ((dataArray[i] - 128) / 128) * amplitude; |
| |
| if (i === 0) { |
| ctx.moveTo(x, y); |
| } else { |
| ctx.lineTo(x, y); |
| } |
| } |
| |
| |
| const gradient = ctx.createLinearGradient(0, 0, width, 0); |
| gradient.addColorStop(0, '#ff00cc'); |
| gradient.addColorStop(0.5, '#00ffcc'); |
| gradient.addColorStop(1, '#3333ff'); |
| |
| ctx.strokeStyle = gradient; |
| ctx.lineWidth = 3; |
| ctx.lineCap = 'round'; |
| ctx.lineJoin = 'round'; |
| ctx.stroke(); |
| |
| |
| ctx.lineTo(width, height); |
| ctx.lineTo(0, height); |
| ctx.closePath(); |
| |
| const fillGradient = ctx.createLinearGradient(0, 0, 0, height); |
| fillGradient.addColorStop(0, 'rgba(0, 255, 204, 0.3)'); |
| fillGradient.addColorStop(1, 'rgba(0, 255, 204, 0.1)'); |
| |
| ctx.fillStyle = fillGradient; |
| ctx.fill(); |
| |
| |
| for (let i = 0; i < dataArray.length; i += 5) { |
| if (dataArray[i] > 200) { |
| const x = (i / dataArray.length) * width; |
| const y = centerY + ((dataArray[i] - 128) / 128) * amplitude; |
| const size = (dataArray[i] / 255) * 6 + 2; |
| |
| ctx.beginPath(); |
| ctx.arc(x, y, size, 0, Math.PI * 2); |
| ctx.fillStyle = `rgba(255, 255, 255, ${dataArray[i] / 255})`; |
| ctx.fill(); |
| } |
| } |
| } |
|
|
| function drawParticles(ctx, dataArray, width, height) { |
| const centerX = width / 2; |
| const centerY = height / 2; |
| const maxRadius = Math.min(width, height) * 0.35; |
| |
| |
| for (let i = 0; i < 50; i++) { |
| const angle = Math.random() * Math.PI * 2; |
| const distance = Math.random() * maxRadius * 1.2; |
| const size = Math.random() * 3 + 1; |
| const hue = Math.random() * 360; |
| |
| ctx.beginPath(); |
| ctx.arc( |
| centerX + Math.cos(angle) * distance, |
| centerY + Math.sin(angle) * distance, |
| size, |
| 0, |
| Math.PI * 2 |
| ); |
| ctx.fillStyle = `hsla(${hue}, 80%, 60%, ${Math.random() * 0.3})`; |
| ctx.fill(); |
| } |
| |
| |
| for (let i = 0; i < dataArray.length; i += 3) { |
| const angle = (i / dataArray.length) * Math.PI * 2; |
| const distance = (dataArray[i] / 255) * maxRadius; |
| const x = centerX + Math.cos(angle) * distance; |
| const y = centerY + Math.sin(angle) * distance; |
| const size = (dataArray[i] / 255) * 8 + 2; |
| const hue = (i / dataArray.length) * 360; |
| |
| ctx.beginPath(); |
| ctx.arc(x, y, size, 0, Math.PI * 2); |
| ctx.fillStyle = `hsla(${hue}, 80%, 70%, ${dataArray[i] / 255})`; |
| ctx.fill(); |
| |
| |
| for (let j = 1; j < 5; j++) { |
| const trailSize = size * (1 - j/10); |
| const trailX = centerX + Math.cos(angle) * (distance - j * 5); |
| const trailY = centerY + Math.sin(angle) * (distance - j * 5); |
| |
| ctx.beginPath(); |
| ctx.arc(trailX, trailY, trailSize, 0, Math.PI * 2); |
| ctx.fillStyle = `hsla(${hue}, 80%, 70%, ${dataArray[i] / 255 * 0.3})`; |
| ctx.fill(); |
| } |
| } |
| |
| |
| const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxRadius * 0.4); |
| gradient.addColorStop(0, 'rgba(0, 255, 204, 0.8)'); |
| gradient.addColorStop(1, 'rgba(0, 255, 204, 0)'); |
| |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.arc(centerX, centerY, maxRadius * 0.4, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
|
|
| function drawCircular(ctx, dataArray, width, height) { |
| const centerX = width / 2; |
| const centerY = height / 2; |
| const maxRadius = Math.min(width, height) * 0.4; |
| |
| ctx.save(); |
| ctx.translate(centerX, centerY); |
| |
| |
| const rotation = Date.now() / 2000; |
| ctx.rotate(rotation); |
| |
| |
| for (let ring = 0; ring < 3; ring++) { |
| const ringRadius = maxRadius * (1 - ring * 0.25); |
| |
| ctx.beginPath(); |
| ctx.arc(0, 0, ringRadius, 0, Math.PI * 2); |
| |
| const gradient = ctx.createLinearGradient(-ringRadius, 0, ringRadius, 0); |
| gradient.addColorStop(0, '#ff00cc'); |
| gradient.addColorStop(0.5, '#00ffcc'); |
| gradient.addColorStop(1, '#3333ff'); |
| |
| ctx.strokeStyle = gradient; |
| ctx.lineWidth = 2 + ring; |
| ctx.stroke(); |
| } |
| |
| |
| const barCount = 64; |
| |
| for (let i = 0; i < barCount; i++) { |
| const angle = (i / barCount) * Math.PI * 2; |
| const barHeight = (dataArray[i % dataArray.length] / 255) * maxRadius * 0.3; |
| const hue = (i / barCount) * 360; |
| |
| ctx.save(); |
| ctx.rotate(angle); |
| |
| const gradient = ctx.createLinearGradient(0, 0, 0, -barHeight); |
| gradient.addColorStop(0, `hsla(${hue}, 80%, 40%, 0.3)`); |
| gradient.addColorStop(1, `hsla(${hue}, 100%, 70%, 0.9)`); |
| |
| ctx.fillStyle = gradient; |
| ctx.fillRect(0, -barHeight, 4, barHeight); |
| |
| ctx.restore(); |
| } |
| |
| |
| const pulseSize = (Math.sin(Date.now() / 300) * 0.3 + 0.7) * 20; |
| const pulseGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, pulseSize); |
| pulseGradient.addColorStop(0, 'rgba(0, 255, 204, 0.8)'); |
| pulseGradient.addColorStop(1, 'rgba(0, 255, 204, 0)'); |
| |
| ctx.fillStyle = pulseGradient; |
| ctx.beginPath(); |
| ctx.arc(0, 0, pulseSize, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| ctx.restore(); |
| } |
|
|
| |
| function applyUIEnhancements() { |
| |
| document.querySelectorAll('.Type__TypeElement-sc-goli3j-0').forEach(text => { |
| if (text.textContent.length > 3 && text.textContent.length < 30) { |
| text.classList.add('glow-text'); |
| } |
| }); |
|
|
| |
| document.querySelectorAll('[role="button"], [role="link"], .main-trackList-row').forEach(element => { |
| element.addEventListener('touchstart', function(e) { |
| if (!this.classList.contains('pulse-active')) { |
| this.classList.add('pulse-active'); |
| const pulse = document.createElement('div'); |
| pulse.className = 'pulse-indicator'; |
| this.appendChild(pulse); |
| |
| setTimeout(() => { |
| pulse.remove(); |
| this.classList.remove('pulse-active'); |
| }, 600); |
| } |
| }, { passive: true }); |
| }); |
|
|
| |
| const uiObserver = new MutationObserver(mutations => { |
| mutations.forEach(mutation => { |
| if (mutation.addedNodes.length) { |
| |
| document.querySelectorAll('.Type__TypeElement-sc-goli3j-0').forEach(text => { |
| if (text.textContent.length > 3 && text.textContent.length < 30 && !text.classList.contains('glow-text')) { |
| text.classList.add('glow-text'); |
| } |
| }); |
| |
| if (isMobile) { |
| document.querySelectorAll('[role="button"], [role="link"], .main-trackList-row').forEach(element => { |
| if (!element.hasAttribute('data-touch-listener')) { |
| element.setAttribute('data-touch-listener', 'true'); |
| element.addEventListener('touchstart', function(e) { |
| if (!this.classList.contains('pulse-active')) { |
| this.classList.add('pulse-active'); |
| const pulse = document.createElement('div'); |
| pulse.className = 'pulse-indicator'; |
| this.appendChild(pulse); |
| |
| setTimeout(() => { |
| pulse.remove(); |
| this.classList.remove('pulse-active'); |
| }, 600); |
| } |
| }, { passive: true }); |
| } |
| }); |
| } |
| } |
| }); |
| }); |
|
|
| uiObserver.observe(document.body, { |
| childList: true, |
| subtree: true |
| }); |
|
|
| |
| window.addEventListener('beforeunload', () => { |
| uiObserver.disconnect(); |
| }); |
| } |
|
|
| |
| function setupResponsiveAdjustments() { |
| function adjustLayout() { |
| const isCurrentlyMobile = window.innerWidth < 768; |
| |
| if (isCurrentlyMobile && !isMobile) { |
| |
| document.documentElement.style.setProperty('--mobile-radius', '28px'); |
| document.documentElement.style.setProperty('--desktop-radius', '16px'); |
| showMobileToast('📱 Mobile Mode Activated'); |
| } else if (!isCurrentlyMobile && isMobile) { |
| |
| document.documentElement.style.setProperty('--mobile-radius', '24px'); |
| document.documentElement.style.setProperty('--desktop-radius', '12px'); |
| showMobileToast('🖥 Desktop Mode Activated'); |
| } |
| } |
|
|
| window.addEventListener('resize', adjustLayout, { passive: true }); |
| adjustLayout(); |
| } |
|
|
| |
| function triggerHapticFeedback(type = 'light') { |
| if (navigator.vibrate) { |
| switch(type) { |
| case 'light': |
| navigator.vibrate(10); |
| break; |
| case 'medium': |
| navigator.vibrate(30); |
| break; |
| case 'heavy': |
| navigator.vibrate(50); |
| break; |
| case 'success': |
| navigator.vibrate([20, 10, 20]); |
| break; |
| case 'error': |
| navigator.vibrate([50, 30, 50]); |
| break; |
| } |
| } |
| } |
|
|
| function showMobileToast(message, duration = 2000) { |
| if (!isMobile) return; |
| |
| let toast = document.getElementById('mobile-toast'); |
| if (!toast) { |
| toast = document.createElement('div'); |
| toast.id = 'mobile-toast'; |
| toast.style.cssText = ` |
| position: fixed; |
| bottom: 20%; |
| left: 50%; |
| transform: translateX(-50%); |
| background: rgba(0, 0, 0, 0.85); |
| color: white; |
| padding: 12px 24px; |
| border-radius: 24px; |
| backdrop-filter: blur(10px); |
| font-size: 16px; |
| z-index: 99999; |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| border: 1px solid rgba(0, 255, 204, 0.3); |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); |
| `; |
| document.body.appendChild(toast); |
| } |
| |
| toast.textContent = message; |
| toast.style.opacity = '1'; |
| |
| setTimeout(() => { |
| toast.style.opacity = '0'; |
| }, duration); |
| } |
|
|
| function navigateNextTrack() { |
| const nextBtn = document.querySelector('.main-skipForwardButton-button') || |
| document.querySelector('[aria-label="Next"]'); |
| if (nextBtn) nextBtn.click(); |
| } |
|
|
| function navigatePreviousTrack() { |
| const prevBtn = document.querySelector('.main-skipBackButton-button') || |
| document.querySelector('[aria-label="Previous"]'); |
| if (prevBtn) prevBtn.click(); |
| } |
|
|
| function setupFullscreenVisualizer() { |
| const canvas = document.getElementById('fullscreen-visualizer'); |
| const ctx = canvas.getContext('2d'); |
| |
| function resizeCanvas() { |
| const dpr = window.devicePixelRatio || 1; |
| canvas.width = (canvas.clientWidth) * dpr; |
| canvas.height = (canvas.clientHeight) * dpr; |
| ctx.scale(dpr, dpr); |
| } |
| |
| resizeCanvas(); |
| window.addEventListener('resize', resizeCanvas); |
| |
| |
| let animationFrame; |
| const colors = ['#ff00cc', '#3333ff', '#00ffcc']; |
| let colorIndex = 0; |
| |
| function animate() { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| |
| |
| const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); |
| gradient.addColorStop(0, colors[colorIndex]); |
| gradient.addColorStop(1, colors[(colorIndex + 1) % colors.length]); |
| ctx.fillStyle = gradient; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| for (let i = 0; i < 20; i++) { |
| const x = Math.sin(Date.now() / 1000 + i) * (canvas.width / 4) + canvas.width / 2; |
| const y = Math.cos(Date.now() / 1200 + i) * (canvas.height / 4) + canvas.height / 2; |
| const size = Math.abs(Math.sin(Date.now() / 2000 + i)) * 30 + 10; |
| |
| ctx.beginPath(); |
| ctx.arc(x, y, size, 0, Math.PI * 2); |
| ctx.fillStyle = `rgba(255, 255, 255, ${0.3 + Math.abs(Math.sin(Date.now() / 1000 + i)) * 0.7})`; |
| ctx.fill(); |
| } |
| |
| colorIndex = (colorIndex + 0.01) % colors.length; |
| animationFrame = requestAnimationFrame(animate); |
| } |
| |
| animate(); |
| |
| |
| document.getElementById('fullscreen-close').addEventListener('click', () => { |
| cancelAnimationFrame(animationFrame); |
| }, { once: true }); |
| } |
|
|
| |
| if (document.readyState !== 'loading') { |
| initEnhancedSpotify(); |
| } else { |
| document.addEventListener('DOMContentLoaded', initEnhancedSpotify); |
| } |
| })(); |