Center Cover Art <3

This commit is contained in:
2026-08-13 22:07:09 +10:00
parent b20e942b4d
commit c369de4f1b
3 changed files with 239 additions and 334 deletions
+155 -174
View File
@@ -6,8 +6,6 @@ declare global {
interface Window { interface Window {
updateRadiantLyricsStyles?: () => void; updateRadiantLyricsStyles?: () => void;
updateRadiantLyricsTextGlow?: () => void; updateRadiantLyricsTextGlow?: () => void;
updateStickyLyricsFeature?: () => void;
updateStickyLyricsSetting?: (checked: boolean) => void;
updateRadiantLyricsPlayerBarTint?: () => void; updateRadiantLyricsPlayerBarTint?: () => void;
updateRadiantLyricsBackdrop?: () => void; updateRadiantLyricsBackdrop?: () => void;
updateQualityProgressColor?: () => void; updateQualityProgressColor?: () => void;
@@ -31,7 +29,6 @@ export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
bubbledLyrics: true, bubbledLyrics: true,
romanizeLyrics: false, romanizeLyrics: false,
aiSyllables: false, aiSyllables: false,
stickyLyrics: false,
syllableStyle: 0, // MARKER: Syllable animations SETTINGS (WIP coming soon) syllableStyle: 0, // MARKER: Syllable animations SETTINGS (WIP coming soon)
syllableLogging: false, syllableLogging: false,
hideUIEnabled: true, hideUIEnabled: true,
@@ -153,14 +150,6 @@ export const Settings = () => {
const [tintHoveredColorIndex, setTintHoveredColorIndex] = React.useState< const [tintHoveredColorIndex, setTintHoveredColorIndex] = React.useState<
number | null number | null
>(null); >(null);
const [stickyLyrics, setStickyLyrics] = React.useState(settings.stickyLyrics);
React.useEffect(() => {
window.updateStickyLyricsSetting = (checked: boolean) =>
setStickyLyrics(checked);
return () => {
window.updateStickyLyricsSetting = undefined;
};
}, []);
const [lyricsStyle, setLyricsStyle] = React.useState(settings.lyricsStyle); const [lyricsStyle, setLyricsStyle] = React.useState(settings.lyricsStyle);
React.useEffect(() => { React.useEffect(() => {
window.updateLyricsStyleSetting = (value: number) => setLyricsStyle(value); window.updateLyricsStyleSetting = (value: number) => setLyricsStyle(value);
@@ -317,19 +306,7 @@ export const Settings = () => {
} }
}} }}
/> />
<AnySwitch <AnySwitch
title="Sticky Lyrics"
desc="auto-switches to Play Queue when lyrics aren't available (mirrored in lyrics dropdown)"
checked={stickyLyrics}
onChange={(_: unknown, checked: boolean) => {
settings.stickyLyrics = checked;
setStickyLyrics(checked);
if (window.updateStickyLyricsFeature) {
window.updateStickyLyricsFeature();
}
}}
/>
<AnySwitch
title="Romanize Lyrics" title="Romanize Lyrics"
desc="Display romanized (latin) text for non-latin lyrics (e.g. Korean, Japanese, Chinese)" desc="Display romanized (latin) text for non-latin lyrics (e.g. Korean, Japanese, Chinese)"
checked={romanizeLyrics} checked={romanizeLyrics}
@@ -926,29 +903,6 @@ export const Settings = () => {
refreshBackdrop(); refreshBackdrop();
}} }}
/> />
<LunaNumberSetting
title="Backdrop Style" // This feature is the result of a bug caused by my ADHD <3
desc="0 = Fluid, 1 = Aurora"
min={0}
max={1}
step={1}
value={backdropStyle}
onNumber={(value: number) => {
settings.backdropStyle = value;
setBackdropStyle(value);
refreshBackdrop();
}}
/>
<AnySwitch
title="Playback Reactive"
desc="Cover shader reacts to playback state by freezing/resuming"
checked={backdropPlaybackReactive}
onChange={(_: unknown, checked: boolean) => {
settings.backdropPlaybackReactive = checked;
setBackdropPlaybackReactive(checked);
refreshBackdrop();
}}
/>
<AnySwitch <AnySwitch
title="Cover Everywhere" title="Cover Everywhere"
desc="Apply the cover art backdrop to the entire app, not just the Now Playing view, Heavily Inspired by Cover-Theme by @Inrixia" desc="Apply the cover art backdrop to the entire app, not just the Now Playing view, Heavily Inspired by Cover-Theme by @Inrixia"
@@ -959,134 +913,161 @@ export const Settings = () => {
refreshBackdrop(); refreshBackdrop();
}} }}
/> />
<AnySwitch {backdropEnabled && (
title="Performance Mode | Experimental" <>
desc="Caps the shader at 4 blur passes, disables dithering and renders at 1x pixel ratio to cut GPU load" <LunaNumberSetting
checked={performanceMode} title="Backdrop Style" // This feature is the result of a bug caused by my ADHD <3
onChange={(_: unknown, checked: boolean) => { desc="0 = Fluid, 1 = Aurora"
settings.performanceMode = checked; min={0}
setPerformanceMode(checked); max={1}
refreshBackdrop(); step={1}
}} value={backdropStyle}
/> onNumber={(value: number) => {
<LunaNumberSetting settings.backdropStyle = value;
title="Backdrop Opacity" setBackdropStyle(value);
desc="How strongly the backdrop shows through (0-100% default = 75)" refreshBackdrop();
min={0} }}
max={100} />
step={1} <AnySwitch
value={backdropOpacity} title="Playback Reactive"
onNumber={(value: number) => { desc="Cover shader reacts to playback state by freezing/resuming"
settings.backdropOpacity = value; checked={backdropPlaybackReactive}
setBackdropOpacity(value); onChange={(_: unknown, checked: boolean) => {
refreshBackdrop(); settings.backdropPlaybackReactive = checked;
}} setBackdropPlaybackReactive(checked);
/> refreshBackdrop();
<LunaNumberSetting }}
title="Warp Intensity" />
desc="Strength of the fluidity effect (0-100% default = 100)" <AnySwitch
min={0} title="Performance Mode | Experimental"
max={100} desc="Caps the shader at 4 blur passes, disables dithering and renders at 1x pixel ratio to cut GPU load"
step={1} checked={performanceMode}
value={backdropWarp} onChange={(_: unknown, checked: boolean) => {
onNumber={(value: number) => { settings.performanceMode = checked;
settings.backdropWarp = value; setPerformanceMode(checked);
setBackdropWarp(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Backdrop Opacity"
title="Blur Passes" desc="How strongly the backdrop shows through (0-100% default = 75)"
desc="Kawase blur passes, higher is softer but costs more GPU (1-40 default = 5)" min={0}
min={1} max={100}
max={40} step={1}
step={1} value={backdropOpacity}
value={backdropBlurPasses} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropOpacity = value;
settings.backdropBlurPasses = value; setBackdropOpacity(value);
setBackdropBlurPasses(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Warp Intensity"
title="Animation Speed" desc="Strength of the fluidity effect (0-100% default = 100)"
desc="How fast the backdrop flows (0-500% default = 175)" min={0}
min={0} max={100}
max={500} step={1}
step={5} value={backdropWarp}
value={backdropSpeed} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropWarp = value;
settings.backdropSpeed = value; setBackdropWarp(value);
setBackdropSpeed(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Blur Passes"
title="Contrast" desc="Kawase blur passes, higher is softer but costs more GPU (1-40 default = 5)"
desc="Contrast of the backdrop (0-300%, 100 = stock default = 125)" min={1}
min={0} max={40}
max={300} step={1}
step={5} value={backdropBlurPasses}
value={backdropContrast} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropBlurPasses = value;
settings.backdropContrast = value; setBackdropBlurPasses(value);
setBackdropContrast(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Animation Speed"
title="Auto Darken Bright Covers" desc="How fast the backdrop flows (0-500% default = 175)"
desc="Prevents bright covers from making text unreadable (0-100% 0 = Off default = 80)" min={0}
min={0} max={500}
max={100} step={5}
step={1} value={backdropSpeed}
value={backdropDarken} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropSpeed = value;
settings.backdropDarken = value; setBackdropSpeed(value);
setBackdropDarken(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Contrast"
title="Saturation" desc="Contrast of the backdrop (0-300%, 100 = stock default = 125)"
desc="Colour intensity of the backdrop (0-400% default = 125)" min={0}
min={0} max={300}
max={400} step={5}
step={5} value={backdropContrast}
value={backdropSaturation} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropContrast = value;
settings.backdropSaturation = value; setBackdropContrast(value);
setBackdropSaturation(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Auto Darken Bright Covers"
title="Backdrop Scale" desc="Prevents bright covers from making text unreadable (0-100% 0 = Off default = 80)"
desc="Zoom level of the effect (10-400% default = 100)" min={0}
min={10} max={100}
max={400} step={1}
step={5} value={backdropDarken}
value={backdropScale} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropDarken = value;
settings.backdropScale = value; setBackdropDarken(value);
setBackdropScale(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
<LunaNumberSetting title="Saturation"
title="Dithering" desc="Colour intensity of the backdrop (0-400% default = 125)"
desc="Breaks up color banding in smooth gradients (0-100 default = 15)" min={0}
min={0} max={400}
max={100} step={5}
step={1} value={backdropSaturation}
value={backdropDithering} onNumber={(value: number) => {
onNumber={(value: number) => { settings.backdropSaturation = value;
settings.backdropDithering = value; setBackdropSaturation(value);
setBackdropDithering(value); refreshBackdrop();
refreshBackdrop(); }}
}} />
/> <LunaNumberSetting
title="Backdrop Scale"
desc="Zoom level of the effect (10-400% default = 100)"
min={10}
max={400}
step={5}
value={backdropScale}
onNumber={(value: number) => {
settings.backdropScale = value;
setBackdropScale(value);
refreshBackdrop();
}}
/>
<LunaNumberSetting
title="Dithering"
desc="Breaks up color banding in smooth gradients (0-100 default = 15)"
min={0}
max={100}
step={1}
value={backdropDithering}
onNumber={(value: number) => {
settings.backdropDithering = value;
setBackdropDithering(value);
refreshBackdrop();
}}
/>
</>
)}
</LunaSettings> </LunaSettings>
); );
}; };
+59 -140
View File
@@ -187,6 +187,11 @@ const applyPlayerBarTintToElement = (): void => {
footerPlayer.style.removeProperty("left"); footerPlayer.style.removeProperty("left");
footerPlayer.style.removeProperty("width"); footerPlayer.style.removeProperty("width");
} }
// Now Playing centres its artwork in a row running to the viewport bottom but the player bar overlays it so i do some complicaated thing to reserve the space <3
document.documentElement.style.setProperty(
"--rl-player-bar-inset",
`${Math.round(footerPlayer.getBoundingClientRect().height) + (settings.floatingPlayerBar ? settings.playerBarSpacing : 0)}px`,
);
applyIntegratedSeekbarChrome(); applyIntegratedSeekbarChrome();
}; };
@@ -1089,9 +1094,9 @@ unloads.add(() => {
if (btn?.parentNode) btn.parentNode.removeChild(btn); if (btn?.parentNode) btn.parentNode.removeChild(btn);
} }
// Clean up sticky lyrics elements // Clean up lyrics menu elements
document document
.querySelectorAll(".sticky-lyrics-trigger, .sticky-lyrics-dropdown") .querySelectorAll(".rl-lyrics-menu-trigger, .rl-lyrics-menu")
.forEach((el) => { .forEach((el) => {
el.remove(); el.remove();
}); });
@@ -1100,17 +1105,17 @@ unloads.add(() => {
cleanUpDynamicArt(); cleanUpDynamicArt();
}); });
// MARKER: Sticky Lyrics Feature // MARKER: Lyrics Menu (dropdown on the lyrics toggle)
const STICKY_ICON = const LYRICS_MENU_ICON =
'<svg viewBox="0 0 512 512" width="16" height="16"><path fill="currentColor" d="M208,512a24.84,24.84,0,0,1-23.34-16l-39.84-103.6a16.06,16.06,0,0,0-9.19-9.19L32,343.34a25,25,0,0,1,0-46.68l103.6-39.84a16.06,16.06,0,0,0,9.19-9.19L184.66,144a25,25,0,0,1,46.68,0l39.84,103.6a16.06,16.06,0,0,0,9.19,9.19l103,39.63A25.49,25.49,0,0,1,400,320.52a24.82,24.82,0,0,1-16,22.82l-103.6,39.84a16.06,16.06,0,0,0-9.19,9.19L231.34,496A24.84,24.84,0,0,1,208,512Zm66.85-254.84h0Z"/><path fill="currentColor" d="M88,176a14.67,14.67,0,0,1-13.69-9.4L57.45,122.76a7.28,7.28,0,0,0-4.21-4.21L9.4,101.69a14.67,14.67,0,0,1,0-27.38L53.24,57.45a7.31,7.31,0,0,0,4.21-4.21L74.16,9.79A15,15,0,0,1,86.23.11,14.67,14.67,0,0,1,101.69,9.4l16.86,43.84a7.31,7.31,0,0,0,4.21,4.21L166.6,74.31a14.67,14.67,0,0,1,0,27.38l-43.84,16.86a7.28,7.28,0,0,0-4.21,4.21L101.69,166.6A14.67,14.67,0,0,1,88,176Z"/><path fill="currentColor" d="M400,256a16,16,0,0,1-14.93-10.26l-22.84-59.37a8,8,0,0,0-4.6-4.6l-59.37-22.84a16,16,0,0,1,0-29.86l59.37-22.84a8,8,0,0,0,4.6-4.6L384.9,42.68a16.45,16.45,0,0,1,13.17-10.57,16,16,0,0,1,16.86,10.15l22.84,59.37a8,8,0,0,0,4.6,4.6l59.37,22.84a16,16,0,0,1,0,29.86l-59.37,22.84a8,8,0,0,0-4.6,4.6l-22.84,59.37A16,16,0,0,1,400,256Z"/></svg>'; '<svg viewBox="0 0 512 512" width="16" height="16"><path fill="currentColor" d="M208,512a24.84,24.84,0,0,1-23.34-16l-39.84-103.6a16.06,16.06,0,0,0-9.19-9.19L32,343.34a25,25,0,0,1,0-46.68l103.6-39.84a16.06,16.06,0,0,0,9.19-9.19L184.66,144a25,25,0,0,1,46.68,0l39.84,103.6a16.06,16.06,0,0,0,9.19,9.19l103,39.63A25.49,25.49,0,0,1,400,320.52a24.82,24.82,0,0,1-16,22.82l-103.6,39.84a16.06,16.06,0,0,0-9.19,9.19L231.34,496A24.84,24.84,0,0,1,208,512Zm66.85-254.84h0Z"/><path fill="currentColor" d="M88,176a14.67,14.67,0,0,1-13.69-9.4L57.45,122.76a7.28,7.28,0,0,0-4.21-4.21L9.4,101.69a14.67,14.67,0,0,1,0-27.38L53.24,57.45a7.31,7.31,0,0,0,4.21-4.21L74.16,9.79A15,15,0,0,1,86.23.11,14.67,14.67,0,0,1,101.69,9.4l16.86,43.84a7.31,7.31,0,0,0,4.21,4.21L166.6,74.31a14.67,14.67,0,0,1,0,27.38l-43.84,16.86a7.28,7.28,0,0,0-4.21,4.21L101.69,166.6A14.67,14.67,0,0,1,88,176Z"/><path fill="currentColor" d="M400,256a16,16,0,0,1-14.93-10.26l-22.84-59.37a8,8,0,0,0-4.6-4.6l-59.37-22.84a16,16,0,0,1,0-29.86l59.37-22.84a8,8,0,0,0,4.6-4.6L384.9,42.68a16.45,16.45,0,0,1,13.17-10.57,16,16,0,0,1,16.86,10.15l22.84,59.37a8,8,0,0,0,4.6,4.6l59.37,22.84a16,16,0,0,1,0,29.86l-59.37,22.84a8,8,0,0,0-4.6,4.6l-22.84,59.37A16,16,0,0,1,400,256Z"/></svg>';
const applyStickyIcon = (): void => { const applyLyricsMenuIcon = (): void => {
const trigger = document.querySelector( const trigger = document.querySelector(
".sticky-lyrics-trigger", ".rl-lyrics-menu-trigger",
) as HTMLElement; ) as HTMLElement;
if (!trigger) return; if (!trigger) return;
trigger.innerHTML = STICKY_ICON; trigger.innerHTML = LYRICS_MENU_ICON;
trigger.style.paddingLeft = "5px"; trigger.style.paddingLeft = "5px";
}; };
@@ -1154,31 +1159,22 @@ const sylTrace = (...args: unknown[]) => {
}, },
}; };
// Called from Settings (mirrors dropdown checkbox) let lyricsMenuEl: HTMLElement | null = null;
const updateStickyLyricsFeature = (): void => { let lyricsMenuOpen = false;
const checkbox = document.querySelector(
'input[data-setting="stickyLyrics"]',
) as HTMLInputElement;
if (checkbox) checkbox.checked = settings.stickyLyrics;
};
(window as any).updateStickyLyricsFeature = updateStickyLyricsFeature;
let stickyDropdownEl: HTMLElement | null = null;
let stickyDropdownOpen = false;
const positionDropdown = (): void => { const positionDropdown = (): void => {
if (!stickyDropdownEl) return; if (!lyricsMenuEl) return;
const toggle = document.querySelector('[data-test="toggle-lyrics"]') as HTMLElement; const toggle = document.querySelector('[data-test="toggle-lyrics"]') as HTMLElement;
if (!toggle) return; if (!toggle) return;
const rect = toggle.getBoundingClientRect(); const rect = toggle.getBoundingClientRect();
stickyDropdownEl.style.top = `${rect.bottom}px`; lyricsMenuEl.style.top = `${rect.bottom}px`;
stickyDropdownEl.style.left = `${rect.left}px`; lyricsMenuEl.style.left = `${rect.left}px`;
stickyDropdownEl.style.width = `${rect.width}px`; lyricsMenuEl.style.width = `${rect.width}px`;
stickyDropdownEl.style.display = "block"; lyricsMenuEl.style.display = "block";
}; };
const openStickyDropdown = (toggle: HTMLElement): void => { const openLyricsMenu = (toggle: HTMLElement): void => {
stickyDropdownOpen = true; lyricsMenuOpen = true;
document.body.classList.add("rl-dropdown-open"); document.body.classList.add("rl-dropdown-open");
let positioned = false; let positioned = false;
const onWidened = (e: TransitionEvent) => { const onWidened = (e: TransitionEvent) => {
@@ -1199,41 +1195,35 @@ const openStickyDropdown = (toggle: HTMLElement): void => {
}, 200); }, 200);
}; };
const closeStickyDropdown = (): void => { const closeLyricsMenu = (): void => {
stickyDropdownOpen = false; lyricsMenuOpen = false;
document.body.classList.remove("rl-dropdown-open"); document.body.classList.remove("rl-dropdown-open");
if (stickyDropdownEl) stickyDropdownEl.style.display = "none"; if (lyricsMenuEl) lyricsMenuEl.style.display = "none";
}; };
const ensureStickyDropdown = (): HTMLElement => { const ensureLyricsMenu = (): HTMLElement => {
if (stickyDropdownEl) return stickyDropdownEl; if (lyricsMenuEl) return lyricsMenuEl;
const dropdown = document.createElement("div"); const dropdown = document.createElement("div");
dropdown.className = "sticky-lyrics-dropdown"; dropdown.className = "rl-lyrics-menu";
dropdown.style.display = "none"; dropdown.style.display = "none";
dropdown.innerHTML = ` dropdown.innerHTML = `
<div class="sticky-lyrics-dropdown-row"> <div class="rl-lyrics-menu-row">
<span class="sticky-lyrics-label">Sticky Lyrics</span> <span class="rl-lyrics-menu-label">Romanization</span>
<label class="sticky-lyrics-switch"> <label class="rl-lyrics-menu-switch">
<input type="checkbox" data-setting="stickyLyrics" ${settings.stickyLyrics ? "checked" : ""}>
<span class="sticky-lyrics-slider"></span>
</label>
</div> <div class="sticky-lyrics-dropdown-row">
<span class="sticky-lyrics-label">Romanization</span>
<label class="sticky-lyrics-switch">
<input type="checkbox" data-setting="romanizeLyrics" ${settings.romanizeLyrics ? "checked" : ""}> <input type="checkbox" data-setting="romanizeLyrics" ${settings.romanizeLyrics ? "checked" : ""}>
<span class="sticky-lyrics-slider"></span> <span class="rl-lyrics-menu-slider"></span>
</label> </label>
</div> </div>
<div class="sticky-lyrics-dropdown-row"> <div class="rl-lyrics-menu-row">
<span class="sticky-lyrics-label">AI Syllables | WIP</span> <span class="rl-lyrics-menu-label">AI Syllables | WIP</span>
<label class="sticky-lyrics-switch"> <label class="rl-lyrics-menu-switch">
<input type="checkbox" data-setting="aiSyllables" ${settings.aiSyllables ? "checked" : ""}> <input type="checkbox" data-setting="aiSyllables" ${settings.aiSyllables ? "checked" : ""}>
<span class="sticky-lyrics-slider"></span> <span class="rl-lyrics-menu-slider"></span>
</label> </label>
</div> </div>
<div class="sticky-lyrics-dropdown-row rl-style-row"> <div class="rl-lyrics-menu-row rl-style-row">
<div class="rl-seg-control"> <div class="rl-seg-control">
<button type="button" class="rl-seg-btn${settings.lyricsStyle === 0 ? " rl-seg-active" : ""}" data-style="0">Line</button> <button type="button" class="rl-seg-btn${settings.lyricsStyle === 0 ? " rl-seg-active" : ""}" data-style="0">Line</button>
<button type="button" class="rl-seg-btn${settings.lyricsStyle === 1 ? " rl-seg-active" : ""}" data-style="1">Word</button> <button type="button" class="rl-seg-btn${settings.lyricsStyle === 1 ? " rl-seg-active" : ""}" data-style="1">Word</button>
@@ -1242,17 +1232,6 @@ const ensureStickyDropdown = (): HTMLElement => {
</div> </div>
`; `;
const stickyCheckbox = dropdown.querySelector(
'input[data-setting="stickyLyrics"]',
) as HTMLInputElement;
stickyCheckbox.addEventListener("change", () => {
settings.stickyLyrics = stickyCheckbox.checked;
(window as any).updateStickyLyricsSetting?.(stickyCheckbox.checked);
if (settings.stickyLyrics) {
handleStickyLyricsTrackChange();
}
});
// AI Generated Syllables (Radiant AI [RL API]) // AI Generated Syllables (Radiant AI [RL API])
const synthCheckbox = dropdown.querySelector( const synthCheckbox = dropdown.querySelector(
'input[data-setting="aiSyllables"]', 'input[data-setting="aiSyllables"]',
@@ -1293,15 +1272,15 @@ const ensureStickyDropdown = (): HTMLElement => {
} }
document.body.appendChild(dropdown); document.body.appendChild(dropdown);
stickyDropdownEl = dropdown; lyricsMenuEl = dropdown;
const outsideHandler = (e: MouseEvent): void => { const outsideHandler = (e: MouseEvent): void => {
const trigger = document.querySelector(".sticky-lyrics-trigger"); const trigger = document.querySelector(".rl-lyrics-menu-trigger");
if ( if (
(!trigger || !trigger.contains(e.target as Node)) && (!trigger || !trigger.contains(e.target as Node)) &&
!dropdown.contains(e.target as Node) !dropdown.contains(e.target as Node)
) { ) {
closeStickyDropdown(); closeLyricsMenu();
} }
}; };
document.addEventListener("click", outsideHandler); document.addEventListener("click", outsideHandler);
@@ -1310,26 +1289,26 @@ const ensureStickyDropdown = (): HTMLElement => {
document.removeEventListener("click", outsideHandler); document.removeEventListener("click", outsideHandler);
document.body.classList.remove("rl-dropdown-open"); document.body.classList.remove("rl-dropdown-open");
dropdown.remove(); dropdown.remove();
stickyDropdownEl = null; lyricsMenuEl = null;
stickyDropdownOpen = false; lyricsMenuOpen = false;
}); });
return dropdown; return dropdown;
}; };
const createStickyLyricsDropdown = (): void => { const createLyricsMenu = (): void => {
const lyricsToggle = document.querySelector( const lyricsToggle = document.querySelector(
'[data-test="toggle-lyrics"]', '[data-test="toggle-lyrics"]',
) as HTMLElement; ) as HTMLElement;
if (!lyricsToggle) return; if (!lyricsToggle) return;
if (lyricsToggle.querySelector(".sticky-lyrics-trigger")) return; if (lyricsToggle.querySelector(".rl-lyrics-menu-trigger")) return;
ensureStickyDropdown(); ensureLyricsMenu();
const trigger = document.createElement("div"); const trigger = document.createElement("div");
trigger.className = "sticky-lyrics-trigger"; trigger.className = "rl-lyrics-menu-trigger";
trigger.setAttribute("title", "Sticky Lyrics"); trigger.setAttribute("title", "Lyrics Options");
trigger.innerHTML = STICKY_ICON; trigger.innerHTML = LYRICS_MENU_ICON;
for (const evtName of [ for (const evtName of [
"pointerdown", "pointerdown",
@@ -1353,13 +1332,13 @@ const createStickyLyricsDropdown = (): void => {
const isActive = lyricsToggle.getAttribute("aria-pressed") === "true"; const isActive = lyricsToggle.getAttribute("aria-pressed") === "true";
if (!isActive) { if (!isActive) {
lyricsToggle.click(); lyricsToggle.click();
safeTimeout(unloads, () => openStickyDropdown(lyricsToggle), 150); safeTimeout(unloads, () => openLyricsMenu(lyricsToggle), 150);
return; return;
} }
if (stickyDropdownOpen) { if (lyricsMenuOpen) {
closeStickyDropdown(); closeLyricsMenu();
} else { } else {
openStickyDropdown(lyricsToggle); openLyricsMenu(lyricsToggle);
} }
}, },
true, true,
@@ -1367,86 +1346,35 @@ const createStickyLyricsDropdown = (): void => {
lyricsToggle.appendChild(trigger); lyricsToggle.appendChild(trigger);
if (stickyDropdownOpen) { if (lyricsMenuOpen) {
positionDropdown(); positionDropdown();
} }
}; };
// Sticky Lyrics nav for injected lyrics tab // Nav for injected lyrics tab
const tryActivateStickyLyricsTab = (): boolean => {
if (!settings.stickyLyrics) return false;
const lyricsToggle = document.querySelector(
'[data-test="toggle-lyrics"]',
) as HTMLElement;
if (!lyricsToggle || lyricsToggle.getAttribute("aria-disabled") === "true") {
tryActivateSimilarTracksTab();
return false;
}
if (syntheticNativeLyrics) {
notifyNativeLyricsStateChanged();
}
if (lyricsToggle.getAttribute("aria-pressed") === "true") return true;
lyricsToggle.click();
return true;
};
const tryActivateSimilarTracksTab = (): void => {
const btn = document.querySelector(
'[data-test="toggle-similar-tracks"]',
) as HTMLElement;
if (!btn) return;
if (btn.getAttribute("aria-pressed") === "true") return;
btn.click();
};
const syncNativeLyricsAvailability = (): void => { const syncNativeLyricsAvailability = (): void => {
if (!syntheticNativeLyrics) return; if (!syntheticNativeLyrics) return;
notifyNativeLyricsStateChanged(); notifyNativeLyricsStateChanged();
}; };
const handleStickyLyricsTrackChange = (): void => {
if (!settings.stickyLyrics) return;
tryActivateStickyLyricsTab();
};
// Track change sequencing (used by onTrackChange) // Track change sequencing (used by onTrackChange)
let isTrackChangeRunning = false; let isTrackChangeRunning = false;
let trackChangeRunSeq = 0; let trackChangeRunSeq = 0;
// Observer: create dropdown when lyrics toggle appears & detect track changes // Observer: create dropdown when lyrics toggle appears & detect track changes
function setupStickyLyricsObserver(): void { function setupLyricsMenuObserver(): void {
// Create dropdown if lyrics toggle already exists // Create dropdown if lyrics toggle already exists
const existing = document.querySelector('[data-test="toggle-lyrics"]'); const existing = document.querySelector('[data-test="toggle-lyrics"]');
if (existing && !existing.querySelector(".sticky-lyrics-trigger")) { if (existing && !existing.querySelector(".rl-lyrics-menu-trigger")) {
createStickyLyricsDropdown(); createLyricsMenu();
} }
// Re-create dropdown whenever lyrics toggle reappears // Re-create dropdown whenever lyrics toggle reappears
observe<HTMLElement>(unloads, '[data-test="toggle-lyrics"]', () => { observe<HTMLElement>(unloads, '[data-test="toggle-lyrics"]', () => {
const toggle = document.querySelector('[data-test="toggle-lyrics"]'); const toggle = document.querySelector('[data-test="toggle-lyrics"]');
syncNativeLyricsAvailability(); syncNativeLyricsAvailability();
if (toggle && !toggle.querySelector(".sticky-lyrics-trigger")) { if (toggle && !toggle.querySelector(".rl-lyrics-menu-trigger")) {
createStickyLyricsDropdown(); createLyricsMenu();
if (settings.stickyLyrics) {
tryActivateStickyLyricsTab();
}
}
});
// When lyrics toggle becomes disabled → similar tracks; enabled → lyrics
observe<HTMLElement>(unloads, '[data-test="toggle-lyrics"][aria-disabled="true"]', () => {
if (settings.stickyLyrics) {
tryActivateSimilarTracksTab();
}
});
observe<HTMLElement>(unloads, '[data-test="toggle-lyrics"]:not([aria-disabled])', () => {
if (settings.stickyLyrics) {
tryActivateStickyLyricsTab();
} }
}); });
@@ -1467,12 +1395,6 @@ function setupStickyLyricsObserver(): void {
} }
}); });
// sticky lyrics track changes
onGlobalTrackChange(() => {
if (settings.stickyLyrics) {
handleStickyLyricsTrackChange();
}
});
} }
// track change system (used everywhere) // track change system (used everywhere)
@@ -4082,9 +4004,6 @@ const onTrackChange = async (): Promise<void> => {
safeTimeout(unloads, () => { safeTimeout(unloads, () => {
if (token !== trackChangeToken) return; if (token !== trackChangeToken) return;
syncNativeLyricsAvailability(); syncNativeLyricsAvailability();
if (settings.stickyLyrics) {
tryActivateStickyLyricsTab();
}
if (!nativeHasLyrics) { if (!nativeHasLyrics) {
// Track had no native lyrics but API found them — // Track had no native lyrics but API found them —
// force navigate to lyrics view so the panel mounts // force navigate to lyrics view so the panel mounts
@@ -4284,5 +4203,5 @@ onGlobalTrackChange(() => {
// Init observers // Init observers
setupHeaderObserver(); setupHeaderObserver();
setupStickyLyricsObserver(); setupLyricsMenuObserver();
setupTrackChangeListener(); setupTrackChangeListener();
+25 -20
View File
@@ -46,16 +46,16 @@
/* MARKER: Sticky Lyrics CSS */ /* MARKER: Lyrics Menu CSS */
/* Lyrics toggle button */ /* Lyrics toggle button */
[data-test="toggle-lyrics"]:has(.sticky-lyrics-trigger) { [data-test="toggle-lyrics"]:has(.rl-lyrics-menu-trigger) {
position: relative !important; position: relative !important;
padding-right: 38px !important; padding-right: 38px !important;
} }
/* Trigger */ /* Trigger */
.sticky-lyrics-trigger { .rl-lyrics-menu-trigger {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
@@ -73,7 +73,7 @@
} }
/* Divider line */ /* Divider line */
.sticky-lyrics-trigger::before { .rl-lyrics-menu-trigger::before {
content: ""; content: "";
position: absolute; position: absolute;
left: 5px; left: 5px;
@@ -85,21 +85,21 @@
} }
/* When Lyrics toggle is pressed — show divider & adjust icon */ /* When Lyrics toggle is pressed — show divider & adjust icon */
[data-test="toggle-lyrics"][aria-pressed="true"] .sticky-lyrics-trigger { [data-test="toggle-lyrics"][aria-pressed="true"] .rl-lyrics-menu-trigger {
color: rgb(30, 30, 30); color: rgb(30, 30, 30);
cursor: pointer; cursor: pointer;
} }
[data-test="toggle-lyrics"][aria-pressed="true"] .sticky-lyrics-trigger::before { [data-test="toggle-lyrics"][aria-pressed="true"] .rl-lyrics-menu-trigger::before {
background: rgba(0, 0, 0, 0.15); background: rgba(0, 0, 0, 0.15);
} }
[data-test="toggle-lyrics"][aria-pressed="true"] .sticky-lyrics-trigger:hover { [data-test="toggle-lyrics"][aria-pressed="true"] .rl-lyrics-menu-trigger:hover {
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.5);
} }
/* Animate widening when dropdown opens */ /* Animate widening when dropdown opens */
[data-test="toggle-lyrics"]:has(.sticky-lyrics-trigger) { [data-test="toggle-lyrics"]:has(.rl-lyrics-menu-trigger) {
transition: min-width 0.12s ease-out; transition: min-width 0.12s ease-out;
} }
@@ -111,7 +111,7 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
} }
/* Dropdown — right-aligned under the Lyrics button */ /* Dropdown — right-aligned under the Lyrics button */
.sticky-lyrics-dropdown { .rl-lyrics-menu {
position: fixed; position: fixed;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
border-radius: 0 0 12px 12px; border-radius: 0 0 12px 12px;
@@ -120,10 +120,10 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
z-index: 10000; z-index: 10000;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
clip-path: inset(0 -20px -20px -20px); clip-path: inset(0 -20px -20px -20px);
animation: stickyLyricsDropdownIn 0.12s ease-out; animation: rlLyricsMenuIn 0.12s ease-out;
} }
@keyframes stickyLyricsDropdownIn { @keyframes rlLyricsMenuIn {
from { from {
opacity: 0; opacity: 0;
transform: translateY(-4px); transform: translateY(-4px);
@@ -135,7 +135,7 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
} }
/* Row containing label + toggle */ /* Row containing label + toggle */
.sticky-lyrics-dropdown-row { .rl-lyrics-menu-row {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@@ -143,11 +143,11 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
} }
/* Breathing room between stacked rows */ /* Breathing room between stacked rows */
.sticky-lyrics-dropdown-row + .sticky-lyrics-dropdown-row { .rl-lyrics-menu-row + .rl-lyrics-menu-row {
margin-top: 8px; margin-top: 8px;
} }
.sticky-lyrics-label { .rl-lyrics-menu-label {
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
color: rgba(0, 0, 0, 0.8); color: rgba(0, 0, 0, 0.8);
@@ -155,7 +155,7 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
} }
/* Toggle switch */ /* Toggle switch */
.sticky-lyrics-switch { .rl-lyrics-menu-switch {
position: relative; position: relative;
display: inline-block; display: inline-block;
width: 34px; width: 34px;
@@ -163,13 +163,13 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
flex-shrink: 0; flex-shrink: 0;
} }
.sticky-lyrics-switch input { .rl-lyrics-menu-switch input {
opacity: 0; opacity: 0;
width: 0; width: 0;
height: 0; height: 0;
} }
.sticky-lyrics-slider { .rl-lyrics-menu-slider {
position: absolute; position: absolute;
cursor: pointer; cursor: pointer;
top: 0; top: 0;
@@ -181,7 +181,7 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
border-radius: 18px; border-radius: 18px;
} }
.sticky-lyrics-slider::before { .rl-lyrics-menu-slider::before {
position: absolute; position: absolute;
content: ""; content: "";
height: 14px; height: 14px;
@@ -194,11 +194,11 @@ body.rl-dropdown-open [data-test="toggle-lyrics"] {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
} }
.sticky-lyrics-switch input:checked + .sticky-lyrics-slider { .rl-lyrics-menu-switch input:checked + .rl-lyrics-menu-slider {
background-color: rgb(30, 30, 30); background-color: rgb(30, 30, 30);
} }
.sticky-lyrics-switch input:checked + .sticky-lyrics-slider::before { .rl-lyrics-menu-switch input:checked + .rl-lyrics-menu-slider::before {
transform: translateX(16px); transform: translateX(16px);
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
} }
@@ -951,3 +951,8 @@ body.rl-owns-lyric-scroll [class*="_syncButtonContainer_"] {
/* Removes the veil for the play queue */ /* Removes the veil for the play queue */
._container_b3e8f28,._container_b3e8f28._nowPlayingVisible_6285d60 { ._container_b3e8f28,._container_b3e8f28._nowPlayingVisible_6285d60 {
background: rgba(0, 0, 0, 0.402);} background: rgba(0, 0, 0, 0.402);}
/* MARKER: Now Playing artwork centering */
[class*="_artworkWrapper_"] {
padding-bottom: var(--rl-player-bar-inset, 98px);
}