diff --git a/plugins/radiant-lyrics-luna/src/index.ts b/plugins/radiant-lyrics-luna/src/index.ts index 8c6f7ed..a670d2d 100644 --- a/plugins/radiant-lyrics-luna/src/index.ts +++ b/plugins/radiant-lyrics-luna/src/index.ts @@ -1,10 +1,9 @@ -import { LunaUnload, Tracer, ftch } from "@luna/core"; +import { LunaUnload, Tracer } from "@luna/core"; import { StyleTag, PlayState } from "@luna/lib"; import { settings, Settings } from "./Settings"; // Import CSS files directly using Luna's file:// syntax - Took me a while to figure out <3 import baseStyles from "file://styles.css?minify"; -import separatedLyrics from "file://separated-lyrics.css?minify"; import playerBarHidden from "file://player-bar-hidden.css?minify"; import lyricsGlow from "file://lyrics-glow.css?minify"; import coverEverywhereCss from "file://cover-everywhere.css?minify"; @@ -25,368 +24,394 @@ let globalSpinningBgStyleTag: StyleTag | null = null; // Apply lyrics glow styles if enabled if (settings.lyricsGlowEnabled) { - lyricsGlowStyleTag.css = lyricsGlow; + lyricsGlowStyleTag.css = lyricsGlow; } var isHidden = false; let unhideButtonAutoFadeTimeout: number | null = null; -const updateButtonStates = function(): void { - const hideButton = document.querySelector('.hide-ui-button') as HTMLElement; - const unhideButton = document.querySelector('.unhide-ui-button') as HTMLElement; - - if (hideButton) { - if (settings.hideUIEnabled && !isHidden) { - hideButton.style.display = 'flex'; - // Small delay to ensure display is set first, then fade in - setTimeout(() => { - hideButton.style.opacity = '1'; - hideButton.style.visibility = 'visible'; - hideButton.style.pointerEvents = 'auto'; - }, 50); - } else { - // Hide UI button immediately when clicked - (couldn't get the fade to work) - hideButton.style.display = 'none'; - hideButton.style.opacity = '0'; - hideButton.style.visibility = 'hidden'; - hideButton.style.pointerEvents = 'none'; - } +const updateButtonStates = function (): void { + const hideButton = document.querySelector(".hide-ui-button") as HTMLElement; + const unhideButton = document.querySelector( + ".unhide-ui-button", + ) as HTMLElement; + + if (hideButton) { + if (settings.hideUIEnabled && !isHidden) { + hideButton.style.display = "flex"; + // Small delay to ensure display is set first, then fade in + setTimeout(() => { + hideButton.style.opacity = "1"; + hideButton.style.visibility = "visible"; + hideButton.style.pointerEvents = "auto"; + }, 50); + } else { + // Hide UI button immediately when clicked - (couldn't get the fade to work) + hideButton.style.display = "none"; + hideButton.style.opacity = "0"; + hideButton.style.visibility = "hidden"; + hideButton.style.pointerEvents = "none"; } - if (unhideButton) { - // Clear any existing auto-fade timeout - if (unhideButtonAutoFadeTimeout) { - window.clearTimeout(unhideButtonAutoFadeTimeout); - unhideButtonAutoFadeTimeout = null; - } - - if (settings.hideUIEnabled && isHidden) { - unhideButton.style.display = 'flex'; - // Remove the hide-immediately class and let it fade in smoothly - unhideButton.classList.remove('hide-immediately'); - unhideButton.classList.remove('auto-faded'); - // Small delay to ensure display is set first, then fade in - (Works for unhide button.. but not hide button.. because uhh idk) - setTimeout(() => { - unhideButton.style.opacity = '1'; - unhideButton.style.visibility = 'visible'; - unhideButton.style.pointerEvents = 'auto'; - - // Set up auto-fade after 2 seconds - unhideButtonAutoFadeTimeout = window.setTimeout(() => { - if (isHidden && unhideButton && !unhideButton.matches(':hover')) { - unhideButton.classList.add('auto-faded'); - } - }, 2000); - }, 50); - } else { - // Smooth fade out for Unhide UI button - unhideButton.style.opacity = '0'; - unhideButton.style.visibility = 'hidden'; - unhideButton.style.pointerEvents = 'none'; - unhideButton.classList.remove('auto-faded'); - // Keep display: flex to maintain transitions, then hide after fade - setTimeout(() => { - if (unhideButton.style.opacity === '0') { - unhideButton.style.display = 'none'; - } - }, 500); // Wait for transition to complete - } + } + if (unhideButton) { + // Clear any existing auto-fade timeout + if (unhideButtonAutoFadeTimeout) { + window.clearTimeout(unhideButtonAutoFadeTimeout); + unhideButtonAutoFadeTimeout = null; } + + if (settings.hideUIEnabled && isHidden) { + unhideButton.style.display = "flex"; + // Remove the hide-immediately class and let it fade in smoothly + unhideButton.classList.remove("hide-immediately"); + unhideButton.classList.remove("auto-faded"); + // Small delay to ensure display is set first, then fade in - (Works for unhide button.. but not hide button.. because uhh idk) + setTimeout(() => { + unhideButton.style.opacity = "1"; + unhideButton.style.visibility = "visible"; + unhideButton.style.pointerEvents = "auto"; + + // Set up auto-fade after 2 seconds + unhideButtonAutoFadeTimeout = window.setTimeout(() => { + if (isHidden && unhideButton && !unhideButton.matches(":hover")) { + unhideButton.classList.add("auto-faded"); + } + }, 2000); + }, 50); + } else { + // Smooth fade out for Unhide UI button + unhideButton.style.opacity = "0"; + unhideButton.style.visibility = "hidden"; + unhideButton.style.pointerEvents = "none"; + unhideButton.classList.remove("auto-faded"); + // Keep display: flex to maintain transitions, then hide after fade + setTimeout(() => { + if (unhideButton.style.opacity === "0") { + unhideButton.style.display = "none"; + } + }, 500); // Wait for transition to complete + } + } }; // Function to update styles when settings change -const updateRadiantLyricsStyles = function(): void { - if (isHidden) { - // Apply only base styles (button hiding), NOT separated lyrics styles - // to avoid affecting lyrics scrolling behavior - baseStyleTag.css = baseStyles; - - // Apply player bar styles based on setting - if (!settings.playerBarVisible) { - playerBarStyleTag.css = playerBarHidden; - } else { - playerBarStyleTag.remove(); - } - } +const updateRadiantLyricsStyles = function (): void { + if (isHidden) { + // Apply only base styles (button hiding), NOT separated lyrics styles + // to avoid affecting lyrics scrolling behavior + baseStyleTag.css = baseStyles; - // Update lyrics glow based on setting (only if UI is not hidden to avoid interference) - const lyricsContainer = document.querySelector('[class^="_lyricsContainer"]'); - if (lyricsContainer && !isHidden) { - if (settings.lyricsGlowEnabled) { - lyricsContainer.classList.remove('lyrics-glow-disabled'); - lyricsGlowStyleTag.css = lyricsGlow; - } else { - lyricsContainer.classList.add('lyrics-glow-disabled'); - lyricsGlowStyleTag.remove(); - } + // Apply player bar styles based on setting + if (!settings.playerBarVisible) { + playerBarStyleTag.css = playerBarHidden; + } else { + playerBarStyleTag.remove(); } + } + + // Update lyrics glow based on setting (only if UI is not hidden to avoid interference) + const lyricsContainer = document.querySelector('[class^="_lyricsContainer"]'); + if (lyricsContainer && !isHidden) { + if (settings.lyricsGlowEnabled) { + lyricsContainer.classList.remove("lyrics-glow-disabled"); + lyricsGlowStyleTag.css = lyricsGlow; + } else { + lyricsContainer.classList.add("lyrics-glow-disabled"); + lyricsGlowStyleTag.remove(); + } + } }; // Function to apply spinning background to the entire app (cover everywhere) const applyGlobalSpinningBackground = (coverArtImageSrc: string): void => { - const appContainer = document.querySelector('[data-test="main"]') as HTMLElement; - if (!settings.spinningCoverEverywhere) { - // Remove StyleTag and all background elements - if (globalSpinningBgStyleTag) { - globalSpinningBgStyleTag.remove(); - globalSpinningBgStyleTag = null; - } - if (appContainer) { - appContainer.querySelectorAll('.global-spinning-image, .global-spinning-black-bg').forEach(el => el.remove()); - } - return; + const appContainer = document.querySelector( + '[data-test="main"]', + ) as HTMLElement; + if (!settings.spinningCoverEverywhere) { + // Remove StyleTag and all background elements + if (globalSpinningBgStyleTag) { + globalSpinningBgStyleTag.remove(); + globalSpinningBgStyleTag = null; } - - // Add StyleTag if not present (Don't know if this is needed.. But it's here) - if (!globalSpinningBgStyleTag) { - globalSpinningBgStyleTag = new StyleTag("RadiantLyrics-global-spinning-bg", unloads, coverEverywhereCss); + if (appContainer) { + appContainer + .querySelectorAll(".global-spinning-image, .global-spinning-black-bg") + .forEach((el) => el.remove()); } + return; + } - if (!appContainer) return; + // Add StyleTag if not present (Don't know if this is needed.. But it's here) + if (!globalSpinningBgStyleTag) { + globalSpinningBgStyleTag = new StyleTag( + "RadiantLyrics-global-spinning-bg", + unloads, + coverEverywhereCss, + ); + } - // Remove any existing background elements - appContainer.querySelectorAll('.global-spinning-image, .global-spinning-black-bg').forEach(el => el.remove()); + if (!appContainer) return; - // Add black background (to obscure image edges) - const blackBg = document.createElement('div'); - blackBg.className = 'global-spinning-black-bg'; - appContainer.appendChild(blackBg); + // Remove any existing background elements + appContainer + .querySelectorAll(".global-spinning-image, .global-spinning-black-bg") + .forEach((el) => el.remove()); - // Add one image for background (spinning or static based on performance mode) - const img = document.createElement('img'); - img.src = coverArtImageSrc; - img.className = 'global-spinning-image'; - img.style.animationDelay = '0s'; - img.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; - - // Apply or remove animation based on performance mode - if (settings.performanceMode) { - img.style.animation = 'none'; - img.classList.add('performance-mode-static'); - } else { - img.style.animation = `spinGlobal ${settings.spinSpeed}s linear infinite`; - img.classList.remove('performance-mode-static'); - } - - appContainer.appendChild(img); + // Add black background (to obscure image edges) + const blackBg = document.createElement("div"); + blackBg.className = "global-spinning-black-bg"; + appContainer.appendChild(blackBg); + + // Add one image for background (spinning or static based on performance mode) + const img = document.createElement("img"); + img.src = coverArtImageSrc; + img.className = "global-spinning-image"; + img.style.animationDelay = "0s"; + img.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; + + // Apply or remove animation based on performance mode + if (settings.performanceMode) { + img.style.animation = "none"; + img.classList.add("performance-mode-static"); + } else { + img.style.animation = `spinGlobal ${settings.spinSpeed}s linear infinite`; + img.classList.remove("performance-mode-static"); + } + + appContainer.appendChild(img); }; // Function to clean up global spinning background -const cleanUpGlobalSpinningBackground = function(): void { - const globalImages = document.getElementsByClassName("global-spinning-image"); - Array.from(globalImages).forEach((element) => { - element.remove(); - }); - // Also remove the overlay - const overlay = document.querySelector('.global-spinning-overlay'); - if (overlay && overlay.parentNode) { - overlay.parentNode.removeChild(overlay); - } - // Also remove the black bg - const blackBg = document.querySelector('.global-spinning-black-bg'); - if (blackBg && blackBg.parentNode) { - blackBg.parentNode.removeChild(blackBg); - } +const cleanUpGlobalSpinningBackground = function (): void { + const globalImages = document.getElementsByClassName("global-spinning-image"); + Array.from(globalImages).forEach((element) => { + element.remove(); + }); + // Also remove the overlay + const overlay = document.querySelector(".global-spinning-overlay"); + if (overlay && overlay.parentNode) { + overlay.parentNode.removeChild(overlay); + } + // Also remove the black bg + const blackBg = document.querySelector(".global-spinning-black-bg"); + if (blackBg && blackBg.parentNode) { + blackBg.parentNode.removeChild(blackBg); + } }; // Function to update global background when settings change -const updateRadiantLyricsGlobalBackground = function(): void { - if (settings.spinningCoverEverywhere) { - // Get current cover art and apply global background - updateCoverArtBackground(); - } else { - cleanUpGlobalSpinningBackground(); - } +const updateRadiantLyricsGlobalBackground = function (): void { + if (settings.spinningCoverEverywhere) { + // Get current cover art and apply global background + updateCoverArtBackground(); + } else { + cleanUpGlobalSpinningBackground(); + } }; // Function to update Now Playing background when settings change -const updateRadiantLyricsNowPlayingBackground = function(): void { - const nowPlayingBackgroundImages = document.querySelectorAll('.now-playing-background-image'); - nowPlayingBackgroundImages.forEach((img: Element) => { - const imgElement = img as HTMLElement; - - // Default values when settings don't affect Now Playing - const defaultBlur = 80; - const defaultBrightness = 40; - const defaultContrast = 120; - const defaultSpinSpeed = 45; - - if (settings.settingsAffectNowPlaying) { - // Use settings values - if (settings.performanceMode) { - imgElement.style.animation = 'none'; - imgElement.classList.add('performance-mode-static'); - } else { - imgElement.style.animation = `spin ${settings.spinSpeed}s linear infinite`; - imgElement.classList.remove('performance-mode-static'); - } - imgElement.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; - } else { - // Reset to default values - if (settings.performanceMode) { - imgElement.style.animation = 'none'; - imgElement.classList.add('performance-mode-static'); - } else { - imgElement.style.animation = `spin ${defaultSpinSpeed}s linear infinite`; - imgElement.classList.remove('performance-mode-static'); - } - imgElement.style.filter = `blur(${defaultBlur}px) brightness(${defaultBrightness / 100}) contrast(${defaultContrast}%)`; - } - }); +const updateRadiantLyricsNowPlayingBackground = function (): void { + const nowPlayingBackgroundImages = document.querySelectorAll( + ".now-playing-background-image", + ); + nowPlayingBackgroundImages.forEach((img: Element) => { + const imgElement = img as HTMLElement; + + // Default values when settings don't affect Now Playing + const defaultBlur = 80; + const defaultBrightness = 40; + const defaultContrast = 120; + const defaultSpinSpeed = 45; + + if (settings.settingsAffectNowPlaying) { + // Use settings values + if (settings.performanceMode) { + imgElement.style.animation = "none"; + imgElement.classList.add("performance-mode-static"); + } else { + imgElement.style.animation = `spin ${settings.spinSpeed}s linear infinite`; + imgElement.classList.remove("performance-mode-static"); + } + imgElement.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; + } else { + // Reset to default values + if (settings.performanceMode) { + imgElement.style.animation = "none"; + imgElement.classList.add("performance-mode-static"); + } else { + imgElement.style.animation = `spin ${defaultSpinSpeed}s linear infinite`; + imgElement.classList.remove("performance-mode-static"); + } + imgElement.style.filter = `blur(${defaultBlur}px) brightness(${defaultBrightness / 100}) contrast(${defaultContrast}%)`; + } + }); }; // Make these functions available globally so Settings can call them (window as any).updateRadiantLyricsStyles = updateRadiantLyricsStyles; -(window as any).updateRadiantLyricsGlobalBackground = updateRadiantLyricsGlobalBackground; -(window as any).updateRadiantLyricsNowPlayingBackground = updateRadiantLyricsNowPlayingBackground; +(window as any).updateRadiantLyricsGlobalBackground = + updateRadiantLyricsGlobalBackground; +(window as any).updateRadiantLyricsNowPlayingBackground = + updateRadiantLyricsNowPlayingBackground; -const toggleRadiantLyrics = function(): void { - const nowPlayingContainer = document.querySelector('[class*="_nowPlayingContainer"]') as HTMLElement; - - if (isHidden) { - // currently hidden, so we're about to show UI - // Add a class to immediately hide the unhide button with CSS - const unhideButton = document.querySelector('.unhide-ui-button') as HTMLElement; - if (unhideButton) { - unhideButton.classList.add('hide-immediately'); // actually uses fade out but.. still - } - - // Toggle the state - isHidden = !isHidden; - - // Don't remove StyleTags completely, just remove the class to show elements again - if (nowPlayingContainer) { - nowPlayingContainer.classList.remove('radiant-lyrics-ui-hidden'); - } - document.body.classList.remove('radiant-lyrics-ui-hidden'); - // Remove styles after animation completes (I think this is needed.. but not sure) - setTimeout(() => { - if (!isHidden) { - lyricsStyleTag.remove(); - baseStyleTag.remove(); - playerBarStyleTag.remove(); - } - }, 500); // Wait for fade animation to complete - - // Update button states normally - updateButtonStates(); - } else { - // We're currently visible, so we're about to hide UI - // Toggle the state first - isHidden = !isHidden; - - // Update button states immediately to start Hide UI button fade-out - updateButtonStates(); - - // Delay adding the CSS class to allow Hide UI button to fade out first - (Had issues with the fade out.. so I removed it) - setTimeout(() => { - // Apply clean view styles - updateRadiantLyricsStyles(); - // Add a class to the container to trigger CSS animations - if (nowPlayingContainer) { - nowPlayingContainer.classList.add('radiant-lyrics-ui-hidden'); - } - document.body.classList.add('radiant-lyrics-ui-hidden'); - }, 50); // Small delay to let Hide UI button start its fade transition - (Had issues with the fade out.. so I removed it) +const toggleRadiantLyrics = function (): void { + const nowPlayingContainer = document.querySelector( + '[class*="_nowPlayingContainer"]', + ) as HTMLElement; + + if (isHidden) { + // currently hidden, so we're about to show UI + // Add a class to immediately hide the unhide button with CSS + const unhideButton = document.querySelector( + ".unhide-ui-button", + ) as HTMLElement; + if (unhideButton) { + unhideButton.classList.add("hide-immediately"); // actually uses fade out but.. still } + + // Toggle the state + isHidden = !isHidden; + + // Don't remove StyleTags completely, just remove the class to show elements again + if (nowPlayingContainer) { + nowPlayingContainer.classList.remove("radiant-lyrics-ui-hidden"); + } + document.body.classList.remove("radiant-lyrics-ui-hidden"); + // Remove styles after animation completes (I think this is needed.. but not sure) + setTimeout(() => { + if (!isHidden) { + lyricsStyleTag.remove(); + baseStyleTag.remove(); + playerBarStyleTag.remove(); + } + }, 500); // Wait for fade animation to complete + + // Update button states normally + updateButtonStates(); + } else { + // We're currently visible, so we're about to hide UI + // Toggle the state first + isHidden = !isHidden; + + // Update button states immediately to start Hide UI button fade-out + updateButtonStates(); + + // Delay adding the CSS class to allow Hide UI button to fade out first - (Had issues with the fade out.. so I removed it) + setTimeout(() => { + // Apply clean view styles + updateRadiantLyricsStyles(); + // Add a class to the container to trigger CSS animations + if (nowPlayingContainer) { + nowPlayingContainer.classList.add("radiant-lyrics-ui-hidden"); + } + document.body.classList.add("radiant-lyrics-ui-hidden"); + }, 50); // Small delay to let Hide UI button start its fade transition - (Had issues with the fade out.. so I removed it) + } }; -const createHideUIButton = function(): void { +const createHideUIButton = function (): void { + setTimeout(() => { + // Only create button if Hide UI is enabled in settings + if (!settings.hideUIEnabled) return; + + // Look for the fullscreen button's parent container + const fullscreenButton = document.querySelector( + '[data-test="request-fullscreen"]', + ); + if (!fullscreenButton || !fullscreenButton.parentElement) { + // Retry if fullscreen button not found yet + setTimeout(() => createHideUIButton(), 1000); + return; + } + + // Check if our button already exists + if (document.querySelector(".hide-ui-button")) return; + + const buttonContainer = fullscreenButton.parentElement; + + // Create our hide UI button + const hideUIButton = document.createElement("button"); + hideUIButton.className = "hide-ui-button"; + hideUIButton.setAttribute("aria-label", "Hide UI"); + hideUIButton.setAttribute("title", "Hide UI"); + hideUIButton.textContent = "Hide UI"; + + // Style to match Tidal's buttons + hideUIButton.style.backgroundColor = "var(--wave-color-solid-accent-fill)"; + hideUIButton.style.color = "black"; + hideUIButton.style.border = "none"; + hideUIButton.style.borderRadius = "12px"; + hideUIButton.style.height = "40px"; + hideUIButton.style.padding = "0 12px"; + hideUIButton.style.marginLeft = "8px"; + hideUIButton.style.cursor = "pointer"; + hideUIButton.style.display = "flex"; + hideUIButton.style.alignItems = "center"; + hideUIButton.style.justifyContent = "center"; + hideUIButton.style.fontSize = "12px"; + hideUIButton.style.fontWeight = "600"; + hideUIButton.style.whiteSpace = "nowrap"; + hideUIButton.style.transition = + "opacity 0.5s ease-in-out, visibility 0.5s ease-in-out, background-color 0.2s ease-in-out"; + hideUIButton.style.opacity = "0"; + hideUIButton.style.visibility = "hidden"; + hideUIButton.style.pointerEvents = "none"; + + // Add hover effect + hideUIButton.addEventListener("mouseenter", () => { + hideUIButton.style.backgroundColor = "lightgray"; + }); + + hideUIButton.addEventListener("mouseleave", () => { + hideUIButton.style.backgroundColor = + "var(--wave-color-solid-accent-fill)"; + }); + + hideUIButton.onclick = toggleRadiantLyrics; + + // Insert after the fullscreen button + buttonContainer.insertBefore(hideUIButton, fullscreenButton.nextSibling); + + // Fade in the button after a small delay setTimeout(() => { - // Only create button if Hide UI is enabled in settings - if (!settings.hideUIEnabled) return; - - // Look for the fullscreen button's parent container - const fullscreenButton = document.querySelector('[data-test="request-fullscreen"]'); - if (!fullscreenButton || !fullscreenButton.parentElement) { - // Retry if fullscreen button not found yet - setTimeout(() => createHideUIButton(), 1000); - return; - } + if (settings.hideUIEnabled && !isHidden) { + hideUIButton.style.opacity = "1"; + hideUIButton.style.visibility = "visible"; + hideUIButton.style.pointerEvents = "auto"; + } + }, 100); // Small delay to ensure DOM insertion is complete - // Check if our button already exists - if (document.querySelector('.hide-ui-button')) return; - - const buttonContainer = fullscreenButton.parentElement; - - // Create our hide UI button - const hideUIButton = document.createElement("button"); - hideUIButton.className = 'hide-ui-button'; - hideUIButton.setAttribute('aria-label', 'Hide UI'); - hideUIButton.setAttribute('title', 'Hide UI'); - hideUIButton.textContent = 'Hide UI'; - - // Style to match Tidal's buttons - hideUIButton.style.backgroundColor = 'var(--wave-color-solid-accent-fill)'; - hideUIButton.style.color = 'black'; - hideUIButton.style.border = 'none'; - hideUIButton.style.borderRadius = '12px'; - hideUIButton.style.height = '40px'; - hideUIButton.style.padding = '0 12px'; - hideUIButton.style.marginLeft = '8px'; - hideUIButton.style.cursor = 'pointer'; - hideUIButton.style.display = 'flex'; - hideUIButton.style.alignItems = 'center'; - hideUIButton.style.justifyContent = 'center'; - hideUIButton.style.fontSize = '12px'; - hideUIButton.style.fontWeight = '600'; - hideUIButton.style.whiteSpace = 'nowrap'; - hideUIButton.style.transition = 'opacity 0.5s ease-in-out, visibility 0.5s ease-in-out, background-color 0.2s ease-in-out'; - hideUIButton.style.opacity = '0'; - hideUIButton.style.visibility = 'hidden'; - hideUIButton.style.pointerEvents = 'none'; - - // Add hover effect - hideUIButton.addEventListener('mouseenter', () => { - hideUIButton.style.backgroundColor = 'lightgray'; - }); - - hideUIButton.addEventListener('mouseleave', () => { - hideUIButton.style.backgroundColor = 'var(--wave-color-solid-accent-fill)'; - }); - - hideUIButton.onclick = toggleRadiantLyrics; - - // Insert after the fullscreen button - buttonContainer.insertBefore(hideUIButton, fullscreenButton.nextSibling); - - // Fade in the button after a small delay - setTimeout(() => { - if (settings.hideUIEnabled && !isHidden) { - hideUIButton.style.opacity = '1'; - hideUIButton.style.visibility = 'visible'; - hideUIButton.style.pointerEvents = 'auto'; - } - }, 100); // Small delay to ensure DOM insertion is complete - - //trace.msg.log("Hide UI button added next to fullscreen button"); - }, 1000); + //trace.msg.log("Hide UI button added next to fullscreen button"); + }, 1000); }; -const createUnhideUIButton = function(): void { - setTimeout(() => { - // Only create button if Hide UI is enabled in settings - if (!settings.hideUIEnabled) return; - - // Check if our button already exists - if (document.querySelector('.unhide-ui-button')) return; +const createUnhideUIButton = function (): void { + setTimeout(() => { + // Only create button if Hide UI is enabled in settings + if (!settings.hideUIEnabled) return; - // Find the Now Playing container to place the button within it - const nowPlayingContainer = document.querySelector('[class*="_nowPlayingContainer"]') as HTMLElement; - if (!nowPlayingContainer) { - // Retry if container not found yet - setTimeout(() => createUnhideUIButton(), 1000); - return; - } + // Check if our button already exists + if (document.querySelector(".unhide-ui-button")) return; - // Create unhide UI button - const unhideUIButton = document.createElement("button"); - unhideUIButton.className = 'unhide-ui-button'; - unhideUIButton.setAttribute('aria-label', 'Unhide UI'); - unhideUIButton.setAttribute('title', 'Unhide UI'); - unhideUIButton.textContent = 'Unhide'; - - // Style for top-right positioning within the Now Playing container (is a pain) - unhideUIButton.style.cssText = ` + // Find the Now Playing container to place the button within it + const nowPlayingContainer = document.querySelector( + '[class*="_nowPlayingContainer"]', + ) as HTMLElement; + if (!nowPlayingContainer) { + // Retry if container not found yet + setTimeout(() => createUnhideUIButton(), 1000); + return; + } + + // Create unhide UI button + const unhideUIButton = document.createElement("button"); + unhideUIButton.className = "unhide-ui-button"; + unhideUIButton.setAttribute("aria-label", "Unhide UI"); + unhideUIButton.setAttribute("title", "Unhide UI"); + unhideUIButton.textContent = "Unhide"; + + // Style for top-right positioning within the Now Playing container (is a pain) + unhideUIButton.style.cssText = ` position: absolute; top: 10px; right: 10px; @@ -412,233 +437,255 @@ const createUnhideUIButton = function(): void { visibility: hidden; pointer-events: none; `; - - // Add hover effect with auto-fade handling - unhideUIButton.addEventListener('mouseenter', () => { - unhideUIButton.style.backgroundColor = 'rgba(255, 255, 255, 0.3)'; - unhideUIButton.style.transform = 'scale(1.05)'; - unhideUIButton.classList.remove('auto-faded'); - }); - - unhideUIButton.addEventListener('mouseleave', () => { - unhideUIButton.style.backgroundColor = 'rgba(255, 255, 255, 0.2)'; - unhideUIButton.style.transform = 'scale(1)'; - // Re-add auto-fade after a short delay if still in hidden mode - window.setTimeout(() => { - if (isHidden && !unhideUIButton.matches(':hover')) { - unhideUIButton.classList.add('auto-faded'); - } - }, 2000); - }); - - unhideUIButton.onclick = toggleRadiantLyrics; - // Append to the Now Playing container so it only shows there - nowPlayingContainer.appendChild(unhideUIButton); - - //trace.msg.log("Unhide UI button added to top-right of Now Playing container"); - updateButtonStates(); - }, 1500); // Slight delay after hide button + // Add hover effect with auto-fade handling + unhideUIButton.addEventListener("mouseenter", () => { + unhideUIButton.style.backgroundColor = "rgba(255, 255, 255, 0.3)"; + unhideUIButton.style.transform = "scale(1.05)"; + unhideUIButton.classList.remove("auto-faded"); + }); + + unhideUIButton.addEventListener("mouseleave", () => { + unhideUIButton.style.backgroundColor = "rgba(255, 255, 255, 0.2)"; + unhideUIButton.style.transform = "scale(1)"; + // Re-add auto-fade after a short delay if still in hidden mode + window.setTimeout(() => { + if (isHidden && !unhideUIButton.matches(":hover")) { + unhideUIButton.classList.add("auto-faded"); + } + }, 2000); + }); + + unhideUIButton.onclick = toggleRadiantLyrics; + + // Append to the Now Playing container so it only shows there + nowPlayingContainer.appendChild(unhideUIButton); + + //trace.msg.log("Unhide UI button added to top-right of Now Playing container"); + updateButtonStates(); + }, 1500); // Slight delay after hide button }; // Function to observe track changes using track ID const observeTrackChanges = (): void => { - let lastTrackId: string | null = null; - const interval = setInterval(() => { - const currentTrackId = PlayState.playbackContext?.actualProductId; - if (currentTrackId && currentTrackId !== lastTrackId) { - //trace.msg.log(`Track changed: ${lastTrackId} -> ${currentTrackId}`); - lastTrackId = currentTrackId; - // delay for cover art to load (to prevent flickering) - setTimeout(() => { - updateCoverArtBackground(); - }, 150); - } - }, 150); // Check every 150ms for better responsiveness - - unloads.add(() => clearInterval(interval)); - - // Initial background application (if a track is already loaded) + let lastTrackId: string | null = null; + const interval = setInterval(() => { const currentTrackId = PlayState.playbackContext?.actualProductId; - if (currentTrackId) { - lastTrackId = currentTrackId; - // Reduced delay for initial load - setTimeout(() => { - updateCoverArtBackground(); - }, 300); + if (currentTrackId && currentTrackId !== lastTrackId) { + //trace.msg.log(`Track changed: ${lastTrackId} -> ${currentTrackId}`); + lastTrackId = currentTrackId; + // delay for cover art to load (to prevent flickering) + setTimeout(() => { + updateCoverArtBackground(); + }, 150); } + }, 150); // Check every 150ms for better responsiveness + + unloads.add(() => clearInterval(interval)); + + // Initial background application (if a track is already loaded) + const currentTrackId = PlayState.playbackContext?.actualProductId; + if (currentTrackId) { + lastTrackId = currentTrackId; + // Reduced delay for initial load + setTimeout(() => { + updateCoverArtBackground(); + }, 300); + } }; // Button observer using polling (instead of Stupid Bloated MutationObserver) function observeForButtons(): void { - const buttonCheckInterval = setInterval(() => { - // Only observe for buttons if Hide UI is enabled - if (!settings.hideUIEnabled) return; - - const fullscreenButton = document.querySelector('[data-test="request-fullscreen"]'); - if (fullscreenButton && !document.querySelector('.hide-ui-button')) { - createHideUIButton(); - } - - // Create unhide button if it doesn't exist - if (!document.querySelector('.unhide-ui-button')) { - createUnhideUIButton(); - } - - // Fix unhide button visibility if UI is hidden but button isn't showing - if (isHidden) { - const unhideButton = document.querySelector('.unhide-ui-button') as HTMLElement; - if (unhideButton && (unhideButton.style.display === 'none' || unhideButton.style.opacity === '0')) { - // Force update button states to fix visibility - updateButtonStates(); - } - } - }, 500); // Check every 500ms (much more efficient than MutationObserver) - - unloads.add(() => clearInterval(buttonCheckInterval)); + const buttonCheckInterval = setInterval(() => { + // Only observe for buttons if Hide UI is enabled + if (!settings.hideUIEnabled) return; + + const fullscreenButton = document.querySelector( + '[data-test="request-fullscreen"]', + ); + if (fullscreenButton && !document.querySelector(".hide-ui-button")) { + createHideUIButton(); + } + + // Create unhide button if it doesn't exist + if (!document.querySelector(".unhide-ui-button")) { + createUnhideUIButton(); + } + + // Fix unhide button visibility if UI is hidden but button isn't showing + if (isHidden) { + const unhideButton = document.querySelector( + ".unhide-ui-button", + ) as HTMLElement; + if ( + unhideButton && + (unhideButton.style.display === "none" || + unhideButton.style.opacity === "0") + ) { + // Force update button states to fix visibility + updateButtonStates(); + } + } + }, 500); // Check every 500ms (much more efficient than MutationObserver) + + unloads.add(() => clearInterval(buttonCheckInterval)); } -// Also observe for lyrics container changes to apply the setting +// Also observe for lyrics container changes to apply the setting function observeLyricsContainer(): void { - const observer = new MutationObserver(() => { - const lyricsContainer = document.querySelector('[class^="_lyricsContainer"]'); - if (lyricsContainer) { - if (settings.lyricsGlowEnabled) { - lyricsContainer.classList.remove('lyrics-glow-disabled'); - } else { - lyricsContainer.classList.add('lyrics-glow-disabled'); - } - } - }); - - observer.observe(document.body, { - childList: true, - subtree: true - }); - - unloads.add(() => observer.disconnect()); + const observer = new MutationObserver(() => { + const lyricsContainer = document.querySelector( + '[class^="_lyricsContainer"]', + ); + if (lyricsContainer) { + if (settings.lyricsGlowEnabled) { + lyricsContainer.classList.remove("lyrics-glow-disabled"); + } else { + lyricsContainer.classList.add("lyrics-glow-disabled"); + } + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + unloads.add(() => observer.disconnect()); } const updateCoverArtBackground = function (method: number = 0): void { - if (method === 1) { - setTimeout(() => { - updateCoverArtBackground(); - return; - }, 2000); - } + if (method === 1) { + setTimeout(() => { + updateCoverArtBackground(); + return; + }, 2000); + } - let coverArtImageElement = document.querySelector('figure[class*="_albumImage"] > div > div > div > img') as HTMLImageElement; - let coverArtImageSrc: string | null = null; + let coverArtImageElement = document.querySelector( + 'figure[class*="_albumImage"] > div > div > div > img', + ) as HTMLImageElement; + let coverArtImageSrc: string | null = null; - if (coverArtImageElement) { - coverArtImageSrc = coverArtImageElement.src; + if (coverArtImageElement) { + coverArtImageSrc = coverArtImageElement.src; + // Set res to 1280x1280 + coverArtImageSrc = coverArtImageSrc.replace(/\d+x\d+/, "1280x1280"); + coverArtImageElement.src = coverArtImageSrc; + } else { + const videoElement = document.querySelector( + 'figure[class*="_albumImage"] > div > div > div > video', + ) as HTMLVideoElement; + if (videoElement) { + coverArtImageSrc = videoElement.getAttribute("poster"); + if (coverArtImageSrc) { // Set res to 1280x1280 - coverArtImageSrc = coverArtImageSrc.replace(/\d+x\d+/, '1280x1280'); - coverArtImageElement.src = coverArtImageSrc; + coverArtImageSrc = coverArtImageSrc.replace(/\d+x\d+/, "1280x1280"); + } } else { - const videoElement = document.querySelector('figure[class*="_albumImage"] > div > div > div > video') as HTMLVideoElement; - if (videoElement) { - coverArtImageSrc = videoElement.getAttribute("poster"); - if (coverArtImageSrc) { - // Set res to 1280x1280 - coverArtImageSrc = coverArtImageSrc.replace(/\d+x\d+/, '1280x1280'); - } - } else { - cleanUpDynamicArt(); - return; - } + cleanUpDynamicArt(); + return; + } + } + + // Update backgrounds when we have a valid cover art source + if (coverArtImageSrc) { + // Apply global spinning background if enabled + if (settings.spinningCoverEverywhere) { + applyGlobalSpinningBackground(coverArtImageSrc); } - // Update backgrounds when we have a valid cover art source - if (coverArtImageSrc) { - // Apply global spinning background if enabled - if (settings.spinningCoverEverywhere) { - applyGlobalSpinningBackground(coverArtImageSrc); - } - - // Apply spinning CoverArt background to the Now Playing container - const nowPlayingContainerElement = document.querySelector('[class*="_nowPlayingContainer"]') as HTMLElement; - if (nowPlayingContainerElement) { - // Remove existing background images if they exist - const existingBackgroundImages = nowPlayingContainerElement.querySelectorAll('.now-playing-background-image, .now-playing-black-bg'); - existingBackgroundImages.forEach(img => img.remove()); + // Apply spinning CoverArt background to the Now Playing container + const nowPlayingContainerElement = document.querySelector( + '[class*="_nowPlayingContainer"]', + ) as HTMLElement; + if (nowPlayingContainerElement) { + // Remove existing background images if they exist + const existingBackgroundImages = + nowPlayingContainerElement.querySelectorAll( + ".now-playing-background-image, .now-playing-black-bg", + ); + existingBackgroundImages.forEach((img) => img.remove()); - // Add black background layer (to obscure image edges) - const blackBg = document.createElement('div'); - blackBg.className = 'now-playing-black-bg'; - blackBg.style.position = 'absolute'; - blackBg.style.left = '0'; - blackBg.style.top = '0'; - blackBg.style.width = '100%'; - blackBg.style.height = '100%'; - blackBg.style.background = '#000'; - blackBg.style.zIndex = '-2'; - blackBg.style.pointerEvents = 'none'; - nowPlayingContainerElement.appendChild(blackBg); + // Add black background layer (to obscure image edges) + const blackBg = document.createElement("div"); + blackBg.className = "now-playing-black-bg"; + blackBg.style.position = "absolute"; + blackBg.style.left = "0"; + blackBg.style.top = "0"; + blackBg.style.width = "100%"; + blackBg.style.height = "100%"; + blackBg.style.background = "#000"; + blackBg.style.zIndex = "-2"; + blackBg.style.pointerEvents = "none"; + nowPlayingContainerElement.appendChild(blackBg); - // Create and append single background layer (the cover art) - const backgroundImage = document.createElement('img'); - backgroundImage.src = coverArtImageSrc; - backgroundImage.className = 'now-playing-background-image'; - backgroundImage.style.position = 'absolute'; - backgroundImage.style.left = '50%'; - backgroundImage.style.top = '50%'; - backgroundImage.style.transform = 'translate(-50%, -50%)'; - backgroundImage.style.width = '90vw'; - backgroundImage.style.height = '90vh'; - backgroundImage.style.objectFit = 'cover'; - backgroundImage.style.zIndex = '-1'; - backgroundImage.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; - backgroundImage.style.willChange = 'transform, filter'; - backgroundImage.style.transformOrigin = 'center center'; - - // Apply animation based on performance mode - if (settings.performanceMode) { - backgroundImage.style.animation = 'none'; - backgroundImage.classList.add('performance-mode-static'); - } else { - backgroundImage.style.animation = `spin ${settings.spinSpeed}s linear infinite`; - backgroundImage.classList.remove('performance-mode-static'); - } - nowPlayingContainerElement.appendChild(backgroundImage); + // Create and append single background layer (the cover art) + const backgroundImage = document.createElement("img"); + backgroundImage.src = coverArtImageSrc; + backgroundImage.className = "now-playing-background-image"; + backgroundImage.style.position = "absolute"; + backgroundImage.style.left = "50%"; + backgroundImage.style.top = "50%"; + backgroundImage.style.transform = "translate(-50%, -50%)"; + backgroundImage.style.width = "90vw"; + backgroundImage.style.height = "90vh"; + backgroundImage.style.objectFit = "cover"; + backgroundImage.style.zIndex = "-1"; + backgroundImage.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; + backgroundImage.style.willChange = "transform, filter"; + backgroundImage.style.transformOrigin = "center center"; - // Create subtle gradient overlay to hide edges (Hate this approach but it's the only way I could get it to work) - const gradientOverlay = document.createElement('div'); - gradientOverlay.className = 'now-playing-gradient-overlay'; - gradientOverlay.style.position = 'absolute'; - gradientOverlay.style.left = '0'; - gradientOverlay.style.top = '0'; - gradientOverlay.style.width = '100%'; - gradientOverlay.style.height = '100%'; - gradientOverlay.style.background = 'radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.3) 60%, rgba(0, 0, 0, 0.8) 90%)'; - gradientOverlay.style.zIndex = '-1'; - gradientOverlay.style.pointerEvents = 'none'; - nowPlayingContainerElement.appendChild(gradientOverlay); + // Apply animation based on performance mode + if (settings.performanceMode) { + backgroundImage.style.animation = "none"; + backgroundImage.classList.add("performance-mode-static"); + } else { + backgroundImage.style.animation = `spin ${settings.spinSpeed}s linear infinite`; + backgroundImage.classList.remove("performance-mode-static"); + } + nowPlayingContainerElement.appendChild(backgroundImage); - // Add keyframe animation if it doesn't exist - if (!document.querySelector('#spinAnimation')) { - const styleSheet = document.createElement('style'); - styleSheet.id = 'spinAnimation'; - styleSheet.textContent = ` + // Create subtle gradient overlay to hide edges (Hate this approach but it's the only way I could get it to work) + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "now-playing-gradient-overlay"; + gradientOverlay.style.position = "absolute"; + gradientOverlay.style.left = "0"; + gradientOverlay.style.top = "0"; + gradientOverlay.style.width = "100%"; + gradientOverlay.style.height = "100%"; + gradientOverlay.style.background = + "radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.3) 60%, rgba(0, 0, 0, 0.8) 90%)"; + gradientOverlay.style.zIndex = "-1"; + gradientOverlay.style.pointerEvents = "none"; + nowPlayingContainerElement.appendChild(gradientOverlay); + + // Add keyframe animation if it doesn't exist + if (!document.querySelector("#spinAnimation")) { + const styleSheet = document.createElement("style"); + styleSheet.id = "spinAnimation"; + styleSheet.textContent = ` @keyframes spin { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } } `; - document.head.appendChild(styleSheet); - } - } + document.head.appendChild(styleSheet); + } } + } }; const cleanUpDynamicArt = function (): void { - const nowPlayingBackgroundImages = document.getElementsByClassName("now-playing-background-image"); - Array.from(nowPlayingBackgroundImages).forEach((element) => { - element.remove(); - }); - - // Also clean up global spinning backgrounds - cleanUpGlobalSpinningBackground(); + const nowPlayingBackgroundImages = document.getElementsByClassName( + "now-playing-background-image", + ); + Array.from(nowPlayingBackgroundImages).forEach((element) => { + element.remove(); + }); + + // Also clean up global spinning backgrounds + cleanUpGlobalSpinningBackground(); }; // Initialize the button creation and observers @@ -649,31 +696,31 @@ updateCoverArtBackground(1); // Add cleanup to unloads unloads.add(() => { - cleanUpDynamicArt(); + cleanUpDynamicArt(); - // Clean up auto-fade timeout - if (unhideButtonAutoFadeTimeout) { - window.clearTimeout(unhideButtonAutoFadeTimeout); - unhideButtonAutoFadeTimeout = null; - } + // Clean up auto-fade timeout + if (unhideButtonAutoFadeTimeout) { + window.clearTimeout(unhideButtonAutoFadeTimeout); + unhideButtonAutoFadeTimeout = null; + } - // Clean up our custom buttons - const hideButton = document.querySelector('.hide-ui-button'); - if (hideButton && hideButton.parentNode) { - hideButton.parentNode.removeChild(hideButton); - } - - const unhideButton = document.querySelector('.unhide-ui-button'); - if (unhideButton && unhideButton.parentNode) { - unhideButton.parentNode.removeChild(unhideButton); - } + // Clean up our custom buttons + const hideButton = document.querySelector(".hide-ui-button"); + if (hideButton && hideButton.parentNode) { + hideButton.parentNode.removeChild(hideButton); + } - // Clean up spin animations - const spinAnimationStyle = document.querySelector('#spinAnimation'); - if (spinAnimationStyle && spinAnimationStyle.parentNode) { - spinAnimationStyle.parentNode.removeChild(spinAnimationStyle); - } - - // Clean up global spinning backgrounds - cleanUpGlobalSpinningBackground(); -}); \ No newline at end of file + const unhideButton = document.querySelector(".unhide-ui-button"); + if (unhideButton && unhideButton.parentNode) { + unhideButton.parentNode.removeChild(unhideButton); + } + + // Clean up spin animations + const spinAnimationStyle = document.querySelector("#spinAnimation"); + if (spinAnimationStyle && spinAnimationStyle.parentNode) { + spinAnimationStyle.parentNode.removeChild(spinAnimationStyle); + } + + // Clean up global spinning backgrounds + cleanUpGlobalSpinningBackground(); +}); diff --git a/plugins/radiant-lyrics-luna/src/separated-lyrics.css b/plugins/radiant-lyrics-luna/src/separated-lyrics.css deleted file mode 100644 index 159ca7e..0000000 --- a/plugins/radiant-lyrics-luna/src/separated-lyrics.css +++ /dev/null @@ -1,133 +0,0 @@ -/* Font imports for lyrics */ -@font-face { - font-family: "AbyssFont"; - font-weight: 400; - src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2") format("woff2"); -} - -@font-face { - font-family: "AbyssFont"; - font-weight: 500; - src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2"); -} - -@font-face { - font-family: "AbyssFont"; - font-weight: 600; - src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2"); -} - -@font-face { - font-family: "AbyssFont"; - font-weight: 700; - src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2"); -} - -/* Tab and container visibility - only when UI is hidden */ -.radiant-lyrics-ui-hidden [class*="tabItems"] { - opacity: 0 !important; - transition: opacity 0.4s ease-in-out; -} - -/* Remove image container positioning - let it stay where it is */ - -.radiant-lyrics-ui-hidden [class*="_tabItems"]:hover { - opacity: 1 !important; -} - -.radiant-lyrics-ui-hidden [data-test="header-container"] { - opacity: 0 !important; - transition: opacity 0.4s ease-in-out; -} - -/* Keep bottom gradient visible - may be needed for lyrics scrolling */ - -/* Remove credits button repositioning - let it stay where it is */ - -/* Keep lyrics provider visible - don't hide with UI */ - -/* Sync button margin */ -[class^="_syncButton"] { - margin-bottom: 10px; -} - -/* Smooth scrolling for lyrics */ -[class^="_lyricsContainer"] { - scroll-behavior: smooth; - padding-left: 20px !important; - padding-right: 20px !important; - width: 100% !important; - max-width: 100% !important; - box-sizing: border-box !important; -} - -/* Clean view specific styles */ -[class*="_lyricsText"] > div { - padding: 0 !important; - background-color: transparent !important; - transition: none !important; - margin-left: 0 !important; - margin-right: 0 !important; - width: 100% !important; -} - -[class*="_lyricsText"] > div:hover { - background-color: transparent !important; -} - -/* Ensure lyrics don't get cut off */ -[class^="_lyricsContainer"] { - padding-bottom: 200px !important; - margin-bottom: 0 !important; -} - -/* Remove any hover effects on lyrics container */ -[class^="_lyricsContainer"] > div > div { - background-color: transparent !important; - padding-left: 0 !important; - padding-right: 0 !important; - margin-left: 0 !important; - margin-right: 0 !important; - width: 100% !important; -} - -[class^="_lyricsContainer"] > div > div:hover { - background-color: transparent !important; -} - -/* Ensure lyrics text has proper spacing */ -[class*="_lyricsText"] { - padding-left: 0 !important; - padding-right: 0 !important; - margin-left: 0 !important; - margin-right: 0 !important; - width: 100% !important; - max-width: 100% !important; - box-sizing: border-box !important; -} - -/* Lyrics text styling */ -[class^="_lyricsText"] { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - line-height: 1.6; -} - -[class^="_lyricsText"] > div > span { - transition: all 0.3s ease; -} - -[class^="_lyricsText"] > div > span[data-current="true"] { - font-weight: 600; - transform: scale(1.05); -} - -/* Enhanced lyrics visibility */ -[class^="_lyricsText"] > div { - padding: 8px 0; - border-radius: 4px; - transition: all 0.2s ease; -} - -[class^="_lyricsText"] > div:hover { - background-color: rgba(255, 255, 255, 0.05); -} \ No newline at end of file