mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-08-26 22:37:45 +10:00
Merge pull request #141 from meowarex/dev
Cover Everywhere (Stock Support)
This commit is contained in:
@@ -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,18 +306,6 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<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
|
<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)"
|
||||||
@@ -926,6 +903,18 @@ export const Settings = () => {
|
|||||||
refreshBackdrop();
|
refreshBackdrop();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<AnySwitch
|
||||||
|
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"
|
||||||
|
checked={CoverEverywhere}
|
||||||
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
|
settings.CoverEverywhere = checked;
|
||||||
|
setCoverEverywhere(checked);
|
||||||
|
refreshBackdrop();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{backdropEnabled && (
|
||||||
|
<>
|
||||||
<LunaNumberSetting
|
<LunaNumberSetting
|
||||||
title="Backdrop Style" // This feature is the result of a bug caused by my ADHD <3
|
title="Backdrop Style" // This feature is the result of a bug caused by my ADHD <3
|
||||||
desc="0 = Fluid, 1 = Aurora"
|
desc="0 = Fluid, 1 = Aurora"
|
||||||
@@ -949,16 +938,6 @@ export const Settings = () => {
|
|||||||
refreshBackdrop();
|
refreshBackdrop();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AnySwitch
|
|
||||||
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"
|
|
||||||
checked={CoverEverywhere}
|
|
||||||
onChange={(_: unknown, checked: boolean) => {
|
|
||||||
settings.CoverEverywhere = checked;
|
|
||||||
setCoverEverywhere(checked);
|
|
||||||
refreshBackdrop();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AnySwitch
|
<AnySwitch
|
||||||
title="Performance Mode | Experimental"
|
title="Performance Mode | Experimental"
|
||||||
desc="Caps the shader at 4 blur passes, disables dithering and renders at 1x pixel ratio to cut GPU load"
|
desc="Caps the shader at 4 blur passes, disables dithering and renders at 1x pixel ratio to cut GPU load"
|
||||||
@@ -1087,6 +1066,8 @@ export const Settings = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</LunaSettings>
|
</LunaSettings>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Hide Tidal now-playing background color */
|
/* Hide Tidal now-playing background color */
|
||||||
[data-test="new-now-playing"] > [class*="_background_"] {
|
body.rl-backdrop-active [data-test="new-now-playing"] > [class*="_background_"] {
|
||||||
/* biome-ignore lint: Must override native album-art-derived background */
|
/* biome-ignore lint: Must override native album-art-derived background */
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure now-playing container is transparent */
|
/* Ensure now-playing container is transparent */
|
||||||
[class*="_nowPlayingContainer"] {
|
body.rl-backdrop-active [class*="_nowPlayingContainer"] {
|
||||||
/* biome-ignore lint: Must override any inline background styles */
|
/* biome-ignore lint: Must override any inline background styles */
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,105 @@
|
|||||||
/* Cover-art backdrop container styles
|
/* Cover-art backdrop container styles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Stock backdrop projected app wide */
|
||||||
|
.rl-stock-background-container {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: -3;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rl-stock-rotator {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 100vmax;
|
||||||
|
height: 100vmax;
|
||||||
|
margin-left: -50vmax;
|
||||||
|
margin-top: -50vmax;
|
||||||
|
will-change: transform;
|
||||||
|
animation: rl-stock-spin 33s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tidal lays 40% of the container & then scales it 4x which is actually kinda smart (i should have thought of this..)*/
|
||||||
|
.rl-stock-overscan {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 40vmax;
|
||||||
|
height: 40vmax;
|
||||||
|
margin-left: -20vmax;
|
||||||
|
margin-top: -20vmax;
|
||||||
|
transform: scale(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rl-stock-art {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
filter: blur(28px) saturate(1.3);
|
||||||
|
transform: scale(1.26);
|
||||||
|
opacity: 0;
|
||||||
|
/* Crossfade between the two layers on track change (again.. NOT AUDIO CROSSFADE) */
|
||||||
|
transition: opacity 600ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rl-stock-art.rl-stock-art-active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rl-stock-scrim {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* Both Tidal gradients */
|
||||||
|
background:
|
||||||
|
radial-gradient(
|
||||||
|
120% 100% at 50% 38%,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(0, 0, 0, 0.12) 62%,
|
||||||
|
rgba(0, 0, 0, 0.28) 100%
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
rgba(0, 0, 0, 0.34) 0%,
|
||||||
|
rgba(0, 0, 0, 0.42) 24%,
|
||||||
|
rgba(0, 0, 0, 0.5) 45%,
|
||||||
|
rgba(0, 0, 0, 0.62) 72%,
|
||||||
|
rgba(0, 0, 0, 0.76) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tidal stops its spin while paused */
|
||||||
|
.rl-stock-paused .rl-stock-rotator {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rl-stock-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.rl-stock-rotator {
|
||||||
|
/* biome-ignore lint: Accessibility override needs priority */
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.global-background-container {
|
.global-background-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -727,6 +732,9 @@ const setLayerRunning = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const syncBackdropActivity = (): void => {
|
const syncBackdropActivity = (): void => {
|
||||||
|
// Stock backdrop keeps current (no shader loop)
|
||||||
|
if (stockContainer) applyStockCoverEverywhere();
|
||||||
|
|
||||||
const nowPlaying = kawarpLayers.nowPlaying;
|
const nowPlaying = kawarpLayers.nowPlaying;
|
||||||
const global = kawarpLayers.global;
|
const global = kawarpLayers.global;
|
||||||
const nowPlayingVisible = nowPlaying?.isVisible() ?? false;
|
const nowPlayingVisible = nowPlaying?.isVisible() ?? false;
|
||||||
@@ -858,10 +866,15 @@ function updateCoverArtBackground(method: number = 0): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Teardown <3
|
// Teardown <3
|
||||||
|
document.body.classList.toggle("rl-backdrop-active", settings.backdropEnabled);
|
||||||
if (!settings.backdropEnabled) {
|
if (!settings.backdropEnabled) {
|
||||||
cleanUpDynamicArt();
|
cleanUpDynamicArt();
|
||||||
|
// Cover Everywhere still applies (Stock Tidal Backdrop)
|
||||||
|
if (settings.CoverEverywhere) applyStockCoverEverywhere();
|
||||||
|
else cleanUpStockCoverEverywhere();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
cleanUpStockCoverEverywhere();
|
||||||
|
|
||||||
const coverArtImageSrc = getCoverArtSrc();
|
const coverArtImageSrc = getCoverArtSrc();
|
||||||
if (!coverArtImageSrc) {
|
if (!coverArtImageSrc) {
|
||||||
@@ -875,6 +888,77 @@ function updateCoverArtBackground(method: number = 0): void {
|
|||||||
syncBackdropActivity();
|
syncBackdropActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARKER: Stock Backdrop (Cover Everywhere without the shader)
|
||||||
|
|
||||||
|
const STOCK_MARKUP =
|
||||||
|
'<div class="rl-stock-rotator"><div class="rl-stock-overscan">' +
|
||||||
|
'<img class="rl-stock-art" alt=""><img class="rl-stock-art" alt="">' +
|
||||||
|
'</div></div><div class="rl-stock-scrim"></div>';
|
||||||
|
const STOCK_ART_RES = "320x320";
|
||||||
|
|
||||||
|
let stockBgStyleTag: StyleTag | null = null;
|
||||||
|
let stockContainer: HTMLElement | null = null;
|
||||||
|
|
||||||
|
const applyStockCoverEverywhere = (): void => {
|
||||||
|
// Same sheet the shader uses
|
||||||
|
stockBgStyleTag ??= new StyleTag(
|
||||||
|
"RadiantLyrics-stock-backdrop",
|
||||||
|
unloads,
|
||||||
|
coverEverywhereCss,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!stockContainer?.isConnected) {
|
||||||
|
// Sweep first
|
||||||
|
cleanUpStockContainers();
|
||||||
|
stockContainer = document.createElement("div");
|
||||||
|
stockContainer.className = "rl-stock-background-container";
|
||||||
|
stockContainer.innerHTML = STOCK_MARKUP;
|
||||||
|
document.body.appendChild(stockContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = document.querySelector<HTMLImageElement>(
|
||||||
|
'[data-test="footer-player"] [data-test="current-media-imagery"] img',
|
||||||
|
);
|
||||||
|
const layers = [
|
||||||
|
...stockContainer.querySelectorAll<HTMLImageElement>(".rl-stock-art"),
|
||||||
|
];
|
||||||
|
// Which layer is showing lives in DOM (no state to track)
|
||||||
|
const shown =
|
||||||
|
layers.find((l) => l.classList.contains("rl-stock-art-active")) ?? layers[1];
|
||||||
|
const next = layers.find((l) => l !== shown);
|
||||||
|
|
||||||
|
const cover = source?.src.replace(/\d+x\d+/, STOCK_ART_RES);
|
||||||
|
if (next && cover && cover !== shown.src) {
|
||||||
|
next.src = cover;
|
||||||
|
// Swap only once decoded (otherwise the crossfade reveals a blank layer)
|
||||||
|
const reveal = () => {
|
||||||
|
next.classList.add("rl-stock-art-active");
|
||||||
|
shown.classList.remove("rl-stock-art-active");
|
||||||
|
};
|
||||||
|
if (next.complete) reveal();
|
||||||
|
else next.addEventListener("load", reveal, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tidal halts its own spin while paused
|
||||||
|
stockContainer.classList.toggle(
|
||||||
|
"rl-stock-paused",
|
||||||
|
!reduxPlaybackIsPlaying(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanUpStockContainers = (): void => {
|
||||||
|
document
|
||||||
|
.querySelectorAll(".rl-stock-background-container")
|
||||||
|
.forEach((el) => el.remove());
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanUpStockCoverEverywhere = (): void => {
|
||||||
|
stockContainer = null;
|
||||||
|
cleanUpStockContainers();
|
||||||
|
stockBgStyleTag?.remove();
|
||||||
|
stockBgStyleTag = null;
|
||||||
|
};
|
||||||
|
|
||||||
const cleanUpGlobalBackground = function (): void {
|
const cleanUpGlobalBackground = function (): void {
|
||||||
// Release WebGL context before canvas is orphaned (otherwise death occurs)
|
// Release WebGL context before canvas is orphaned (otherwise death occurs)
|
||||||
disposeKawarpLayer("global");
|
disposeKawarpLayer("global");
|
||||||
@@ -912,10 +996,17 @@ const updateRadiantLyricsBackdrop = function (): void {
|
|||||||
document.body.classList.remove("performance-mode");
|
document.body.classList.remove("performance-mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gates the rules that hide Tidal now-playing background
|
||||||
|
document.body.classList.toggle("rl-backdrop-active", settings.backdropEnabled);
|
||||||
|
|
||||||
if (!settings.backdropEnabled) {
|
if (!settings.backdropEnabled) {
|
||||||
cleanUpDynamicArt();
|
cleanUpDynamicArt();
|
||||||
|
// Cover Everywhere still applies (Stock Tidal Backdrop)
|
||||||
|
if (settings.CoverEverywhere) applyStockCoverEverywhere();
|
||||||
|
else cleanUpStockCoverEverywhere();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
cleanUpStockCoverEverywhere();
|
||||||
|
|
||||||
if (!settings.CoverEverywhere) cleanUpGlobalBackground();
|
if (!settings.CoverEverywhere) cleanUpGlobalBackground();
|
||||||
|
|
||||||
@@ -965,6 +1056,8 @@ updateCoverArtBackground(1);
|
|||||||
// Cleanups
|
// Cleanups
|
||||||
unloads.add(() => {
|
unloads.add(() => {
|
||||||
cleanUpDynamicArt();
|
cleanUpDynamicArt();
|
||||||
|
cleanUpStockCoverEverywhere();
|
||||||
|
document.body.classList.remove("rl-backdrop-active");
|
||||||
|
|
||||||
// Clean up floating player bar inline styles
|
// Clean up floating player bar inline styles
|
||||||
const footerPlayer = document.querySelector(
|
const footerPlayer = document.querySelector(
|
||||||
@@ -1001,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();
|
||||||
});
|
});
|
||||||
@@ -1012,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";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1066,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) => {
|
||||||
@@ -1111,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>
|
||||||
@@ -1154,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"]',
|
||||||
@@ -1205,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);
|
||||||
@@ -1222,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",
|
||||||
@@ -1265,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,
|
||||||
@@ -1279,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();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1379,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)
|
||||||
@@ -3994,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
|
||||||
@@ -4196,5 +4203,5 @@ onGlobalTrackChange(() => {
|
|||||||
|
|
||||||
// Init observers
|
// Init observers
|
||||||
setupHeaderObserver();
|
setupHeaderObserver();
|
||||||
setupStickyLyricsObserver();
|
setupLyricsMenuObserver();
|
||||||
setupTrackChangeListener();
|
setupTrackChangeListener();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user