|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(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); |
|
|
} |
|
|
})(); |