// ==UserScript== // @name Spotify Ultimate Mobile UI/UX Enhancer // @namespace https://violentmonkey.github.io/ // @version 3.0 // @description Transforms Spotify web player with stunning animations, advanced audio visualizer, and mobile-optimized UX that surpasses the native app // @author UX Designer // @match https://open.spotify.com/* // @icon https://www.spotify.com/favicon.ico // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Feature detection and initialization const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || (window.innerWidth < 768); // Mobile-specific features const mobileFeatures = { gesturesEnabled: true, hapticFeedback: true, adaptiveLayout: true, touchOptimized: true, swipeNavigation: true, pullToRefresh: true }; // Initialize the enhanced UI function initEnhancedSpotify() { // Create style element const style = document.createElement('style'); style.textContent = getCSSStyles(); document.head.appendChild(style); // Create visualizer container createVisualizerContainer(); // Setup mobile gestures if (isMobile && mobileFeatures.gesturesEnabled) { setupMobileGestures(); } // Setup audio analysis setupAudioAnalysis(); // Apply UI enhancements applyUIEnhancements(); // Setup responsive adjustments setupResponsiveAdjustments(); console.log('✨ Spotify Ultimate Mobile UI/UX Enhancer loaded successfully!'); console.log(`📱 Mobile Mode: ${isMobile ? 'Enabled' : 'Disabled'}`); } // Get CSS styles with mobile optimization 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; } } `; } // Create visualizer container function createVisualizerContainer() { const container = document.createElement('div'); container.id = 'visualizer-container'; container.className = isMobile ? 'mobile-enter' : ''; container.innerHTML = `
❚❚
🎨
Swipe up for more
`; // Add swipe areas for mobile 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); } // Create fullscreen overlay const fullscreenOverlay = document.createElement('div'); fullscreenOverlay.id = 'fullscreen-visualizer-overlay'; fullscreenOverlay.innerHTML = `
🎨
`; document.body.appendChild(container); document.body.appendChild(fullscreenOverlay); // Setup event listeners setupVisualizerEvents(); } // Setup visualizer events function setupVisualizerEvents() { // Minimize/maximize on tap (mobile) document.getElementById('visualizer-container').addEventListener('click', (e) => { if (isMobile && !e.target.closest('.visualizer-btn')) { const container = document.getElementById('visualizer-container'); container.classList.toggle('minimized'); // Show gesture indicator const indicator = document.getElementById('gesture-indicator'); if (!container.classList.contains('minimized')) { indicator.style.opacity = '1'; setTimeout(() => { indicator.style.opacity = '0'; }, 2000); } } }, { passive: true }); // Toggle visualizer document.getElementById('visualizer-toggle').addEventListener('click', function() { const container = document.getElementById('visualizer-container'); container.classList.toggle('collapsed'); this.textContent = container.classList.contains('collapsed') ? '▶️' : '❚❚'; }); // Fullscreen visualizer document.getElementById('visualizer-fullscreen').addEventListener('click', function() { const overlay = document.getElementById('fullscreen-visualizer-overlay'); overlay.classList.add('active'); setupFullscreenVisualizer(); }); // Close fullscreen document.getElementById('fullscreen-close').addEventListener('click', function() { document.getElementById('fullscreen-visualizer-overlay').classList.remove('active'); }); // Preset cycling 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)}`); }); } // Setup mobile gestures function setupMobileGestures() { let startY = 0; let currentY = 0; let isSwiping = false; const container = document.getElementById('visualizer-container'); const indicator = document.getElementById('gesture-indicator'); // Touch start container.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; currentY = startY; isSwiping = true; indicator.style.opacity = '0'; }, { passive: true }); // Touch move container.addEventListener('touchmove', (e) => { if (!isSwiping) return; currentY = e.touches[0].clientY; const deltaY = currentY - startY; // Show indicator when swiping up if (deltaY < -20 && !container.classList.contains('minimized')) { indicator.style.opacity = '1'; } else if (deltaY > 20) { indicator.style.opacity = '0'; } // Pull to refresh effect if (mobileFeatures.pullToRefresh && deltaY > 0 && container.classList.contains('minimized')) { container.style.transform = `translateY(${deltaY/3}px)`; } }, { passive: true }); // Touch end container.addEventListener('touchend', (e) => { if (!isSwiping) return; const deltaY = currentY - startY; // Swipe up to expand if (deltaY < -50 && container.classList.contains('minimized')) { container.classList.remove('minimized'); if (mobileFeatures.hapticFeedback) triggerHapticFeedback('light'); } // Swipe down to minimize else if (deltaY > 50 && !container.classList.contains('minimized')) { container.classList.add('minimized'); if (mobileFeatures.hapticFeedback) triggerHapticFeedback('light'); } // Pull to refresh 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 }); // Swipe navigation if (mobileFeatures.swipeNavigation) { setupSwipeNavigation(); } } // Setup swipe navigation 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 }); } // Setup audio analysis 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 }); // Fallback check let audioCheckInterval = setInterval(() => { const audioElements = document.querySelectorAll('audio'); if (audioElements.length > 0) { initializeVisualizer(audioElements[0]); clearInterval(audioCheckInterval); observer.disconnect(); } }, 1000); } // Initialize visualizer function initializeVisualizer(audioElement) { const canvas = document.getElementById('audio-visualizer'); const ctx = canvas.getContext('2d'); const container = document.getElementById('visualizer-container'); // Set canvas dimensions 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); // Create audio context and analyzer 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); // Draw based on current preset 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(); // Cleanup on page unload 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); } } // Draw different visualizer types 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; // Create gradient for each bar 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); // Glow effect 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); } } // Gradient stroke 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(); // Fill under wave 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(); // Particles on peaks 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; // Background particles 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(); } // Audio-reactive particles 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(); // Trail effect 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(); } } // Center glow 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); // Rotate over time const rotation = Date.now() / 2000; ctx.rotate(rotation); // Draw multiple rings 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(); } // Draw frequency bars around circle 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(); } // Center pulse 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(); } // Apply UI enhancements function applyUIEnhancements() { // Add gradient text to important elements document.querySelectorAll('.Type__TypeElement-sc-goli3j-0').forEach(text => { if (text.textContent.length > 3 && text.textContent.length < 30) { text.classList.add('glow-text'); } }); // Add touch feedback to interactive elements 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 }); }); // MutationObserver for dynamic content const uiObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.addedNodes.length) { // Re-apply enhancements to new elements 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 }); // Cleanup on page unload window.addEventListener('beforeunload', () => { uiObserver.disconnect(); }); } // Setup responsive adjustments function setupResponsiveAdjustments() { function adjustLayout() { const isCurrentlyMobile = window.innerWidth < 768; if (isCurrentlyMobile && !isMobile) { // Switch to mobile mode document.documentElement.style.setProperty('--mobile-radius', '28px'); document.documentElement.style.setProperty('--desktop-radius', '16px'); showMobileToast('📱 Mobile Mode Activated'); } else if (!isCurrentlyMobile && isMobile) { // Switch to desktop mode 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(); } // Mobile utility functions 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); // Simple animation for fullscreen let animationFrame; const colors = ['#ff00cc', '#3333ff', '#00ffcc']; let colorIndex = 0; function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw gradient background 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); // Draw some shapes 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(); // Cleanup on close document.getElementById('fullscreen-close').addEventListener('click', () => { cancelAnimationFrame(animationFrame); }, { once: true }); } // Initialize the script if (document.readyState !== 'loading') { initEnhancedSpotify(); } else { document.addEventListener('DOMContentLoaded', initEnhancedSpotify); } })();