// ==UserScript==
// @name YouTube Music Spinning Album Art
// @namespace http://tampermonkey.net/
// @version 2.2
// @description Spins album artwork with grooves, shine, and spindle. Syncs with play/pause state on YouTube Music.
// @author Chesley
// @match https://music.youtube.com/*
// @license MIT
// @grant none
// ==/UserScript==
(function () {
'use strict';
const styleId = 'spinning-cropped-art-style';
let initialized = false;
let lastSrc = null;
const addStyles = () => {
if (document.getElementById(styleId)) return;
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
@keyframes spinAlbum {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes shineMove {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
ytmusic-player {
background-color: transparent;
}
ytmusic-player #thumbnail {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 50%;
width: 90%;
height: 90%;
margin: auto;
position: relative;
border: 2px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
}
ytmusic-player #thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
animation: spinAlbum 8s linear infinite;
animation-play-state: running;
border-radius: 50%;
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
z-index: 2;
}
ytmusic-player #thumbnail.paused img,
ytmusic-player #thumbnail.paused .shine {
animation-play-state: paused;
}
ytmusic-player #thumbnail .grooves {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: repeating-radial-gradient(
circle,
rgba(255, 255, 255, 0.1),
rgba(255, 255, 255, 0.1) 15px,
rgba(0, 0, 0, 0.3) 15.5px,
rgba(0, 0, 0, 0) 18px
);
pointer-events: none;
z-index: 3;
}
/* 🔥 Updated Shine Style */
ytmusic-player #thumbnail .shine {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: linear-gradient(
120deg,
rgba(255, 255, 255, 0.50) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 80%
);
animation: shineMove 6s linear infinite;
pointer-events: none;
z-index: 100;
}
ytmusic-player #thumbnail::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 28px;
height: 28px;
background: radial-gradient(circle, #444 30%, #000 100%);
border-radius: 50%;
z-index: 5;
pointer-events: none;
}
`;
document.head.appendChild(style);
};
const applyDecorations = () => {
const thumbnail = document.querySelector('ytmusic-player #thumbnail');
const img = thumbnail?.querySelector('img');
if (!img) return;
const currentSrc = img.src;
if (currentSrc === lastSrc) return;
lastSrc = currentSrc;
if (!thumbnail.querySelector('.grooves')) {
const grooves = document.createElement('div');
grooves.className = 'grooves';
thumbnail.appendChild(grooves);
}
if (!thumbnail.querySelector('.shine')) {
const shine = document.createElement('div');
shine.className = 'shine';
thumbnail.appendChild(shine);
}
};
const syncWithPlayback = () => {
const video = document.querySelector('video');
const thumbnail = document.querySelector('ytmusic-player #thumbnail');
if (!video || !thumbnail) return;
const updateState = () => {
if (video.paused) {
thumbnail.classList.add('paused');
} else {
thumbnail.classList.remove('paused');
}
};
video.addEventListener('play', updateState);
video.addEventListener('pause', updateState);
updateState();
};
const watchSongChanges = () => {
const player = document.querySelector('ytmusic-player');
if (!player) return;
const songObserver = new MutationObserver(() => {
applyDecorations();
syncWithPlayback();
});
songObserver.observe(player, {
childList: true,
subtree: true,
});
};
const init = () => {
const img = document.querySelector('ytmusic-player #thumbnail img');
if (!img) return;
if (!initialized) {
addStyles();
initialized = true;
}
applyDecorations();
syncWithPlayback();
watchSongChanges();
};
const bootstrap = new MutationObserver(() => {
const ready = document.querySelector('ytmusic-player #thumbnail img');
if (ready) {
init();
bootstrap.disconnect();
}
});
bootstrap.observe(document.body, {
childList: true,
subtree: true
});
})();