aceipts / app.js
NeoPy's picture
Update app.js
db69e64 verified
// ==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 = `
<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>
`;
// 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 = `
<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);
// 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);
}
})();