mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
Added Sticky Lyrics
This commit is contained in:
@@ -18,6 +18,9 @@ export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
|
|||||||
backgroundBrightness: 40,
|
backgroundBrightness: 40,
|
||||||
spinSpeed: 45,
|
spinSpeed: 45,
|
||||||
settingsAffectNowPlaying: true,
|
settingsAffectNowPlaying: true,
|
||||||
|
stickyLyricsFeature: true,
|
||||||
|
stickyLyrics: false,
|
||||||
|
stickyLyricsIcon: "chevron" as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
@@ -61,6 +64,9 @@ export const Settings = () => {
|
|||||||
const [backgroundRadius, setBackgroundRadius] = React.useState(
|
const [backgroundRadius, setBackgroundRadius] = React.useState(
|
||||||
settings.backgroundRadius,
|
settings.backgroundRadius,
|
||||||
);
|
);
|
||||||
|
const [stickyLyricsFeature, setStickyLyricsFeature] = React.useState(
|
||||||
|
settings.stickyLyricsFeature,
|
||||||
|
);
|
||||||
|
|
||||||
// Derive props and override onChange to accept a broader first param type
|
// Derive props and override onChange to accept a broader first param type
|
||||||
type BaseSwitchProps = React.ComponentProps<typeof LunaSwitchSetting>;
|
type BaseSwitchProps = React.ComponentProps<typeof LunaSwitchSetting>;
|
||||||
@@ -97,6 +103,17 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<AnySwitch
|
||||||
|
title="Sticky Lyrics"
|
||||||
|
desc="Adds a dropdown to the Lyrics tab that auto-switches to Play Queue when lyrics aren't available"
|
||||||
|
checked={stickyLyricsFeature}
|
||||||
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
|
setStickyLyricsFeature((settings.stickyLyricsFeature = checked));
|
||||||
|
if ((window as any).updateStickyLyricsFeature) {
|
||||||
|
(window as any).updateStickyLyricsFeature();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<AnySwitch
|
<AnySwitch
|
||||||
title="Hide UI Feature"
|
title="Hide UI Feature"
|
||||||
desc="Enable hide/unhide UI functionality with toggle buttons"
|
desc="Enable hide/unhide UI functionality with toggle buttons"
|
||||||
|
|||||||
@@ -815,11 +815,11 @@ const cleanUpDynamicArt = function (): void {
|
|||||||
element.remove();
|
element.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also clean up global spinning backgrounds
|
// Clean up spinning background
|
||||||
cleanUpGlobalSpinningBackground();
|
cleanUpGlobalSpinningBackground();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reduce work when tab hidden: pause animations; restore on visible
|
// I may or may not have forgotten what this does..
|
||||||
document.addEventListener("visibilitychange", () => {
|
document.addEventListener("visibilitychange", () => {
|
||||||
const isHiddenDoc = document.hidden;
|
const isHiddenDoc = document.hidden;
|
||||||
const images = document.querySelectorAll(
|
const images = document.querySelectorAll(
|
||||||
@@ -846,27 +846,28 @@ document.addEventListener("visibilitychange", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply initial performance mode class
|
// Init performance mode
|
||||||
if (settings.performanceMode) {
|
if (settings.performanceMode) {
|
||||||
document.body.classList.add("performance-mode");
|
document.body.classList.add("performance-mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize text glow CSS variables on load
|
// Init text glow
|
||||||
updateRadiantLyricsTextGlow();
|
updateRadiantLyricsTextGlow();
|
||||||
|
|
||||||
|
// Init global background
|
||||||
updateCoverArtBackground(1);
|
updateCoverArtBackground(1);
|
||||||
|
|
||||||
// Add cleanup to unloads
|
// Cleanups
|
||||||
unloads.add(() => {
|
unloads.add(() => {
|
||||||
cleanUpDynamicArt();
|
cleanUpDynamicArt();
|
||||||
|
|
||||||
// Clean up auto-fade timeout
|
// Clean up HideUI button auto-fade timeout
|
||||||
if (unhideButtonAutoFadeTimeout != null) {
|
if (unhideButtonAutoFadeTimeout != null) {
|
||||||
window.clearTimeout(unhideButtonAutoFadeTimeout);
|
window.clearTimeout(unhideButtonAutoFadeTimeout);
|
||||||
unhideButtonAutoFadeTimeout = null;
|
unhideButtonAutoFadeTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up our custom buttons
|
// Clean up HideUI button
|
||||||
const hideButton = document.querySelector(".hide-ui-button");
|
const hideButton = document.querySelector(".hide-ui-button");
|
||||||
if (hideButton && hideButton.parentNode) {
|
if (hideButton && hideButton.parentNode) {
|
||||||
hideButton.parentNode.removeChild(hideButton);
|
hideButton.parentNode.removeChild(hideButton);
|
||||||
@@ -877,16 +878,260 @@ unloads.add(() => {
|
|||||||
unhideButton.parentNode.removeChild(unhideButton);
|
unhideButton.parentNode.removeChild(unhideButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up sticky lyrics elements
|
||||||
|
document.querySelectorAll(".sticky-lyrics-trigger, .sticky-lyrics-dropdown").forEach((el) => {
|
||||||
|
el.remove();
|
||||||
|
});
|
||||||
|
|
||||||
// Clean up spin animations
|
// Clean up spin animations
|
||||||
const spinAnimationStyle = document.querySelector("#spinAnimation");
|
const spinAnimationStyle = document.querySelector("#spinAnimation");
|
||||||
if (spinAnimationStyle && spinAnimationStyle.parentNode) {
|
if (spinAnimationStyle && spinAnimationStyle.parentNode) {
|
||||||
spinAnimationStyle.parentNode.removeChild(spinAnimationStyle);
|
spinAnimationStyle.parentNode.removeChild(spinAnimationStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up global spinning backgrounds
|
// Clean up spinning background
|
||||||
cleanUpGlobalSpinningBackground();
|
cleanUpGlobalSpinningBackground();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// MARKER: Sticky Lyrics Feature
|
||||||
|
|
||||||
|
const STICKY_ICONS: Record<string, string> = {
|
||||||
|
chevron: '<svg viewBox="0 0 24 24" width="10" height="10" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.29289 8.29289C4.68342 7.90237 5.31658 7.90237 5.70711 8.29289L12 14.5858L18.2929 8.29289C18.6834 7.90237 19.3166 7.90237 19.7071 8.29289C20.0976 8.68342 20.0976 9.31658 19.7071 9.70711L12.7071 16.7071C12.3166 17.0976 11.6834 17.0976 11.2929 16.7071L4.29289 9.70711C3.90237 9.31658 3.90237 8.68342 4.29289 8.29289Z" fill="currentColor"/></svg>',
|
||||||
|
sparkle: '<svg viewBox="0 0 512 512" width="12" height="12"><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 getStickyIcon = (): string => STICKY_ICONS[settings.stickyLyricsIcon] ?? STICKY_ICONS.chevron;
|
||||||
|
|
||||||
|
const applyStickyIcon = (): void => {
|
||||||
|
const trigger = document.querySelector(".sticky-lyrics-trigger") as HTMLElement;
|
||||||
|
if (!trigger) return;
|
||||||
|
trigger.innerHTML = getStickyIcon();
|
||||||
|
trigger.style.paddingLeft = settings.stickyLyricsIcon === "sparkle" ? "5px" : "5px";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Console: StickyLyrics.icon = "sparkle" or "chevron"
|
||||||
|
// I'm picky and prefer the Sparkle.. shhh
|
||||||
|
(window as any).StickyLyrics = {
|
||||||
|
get icon() { return settings.stickyLyricsIcon; },
|
||||||
|
set icon(value: string) {
|
||||||
|
const key = value.toLowerCase();
|
||||||
|
if (!STICKY_ICONS[key]) {
|
||||||
|
console.log(`[Radiant Lyrics] Unknown icon "${value}". Available: ${Object.keys(STICKY_ICONS).join(", ")}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings.stickyLyricsIcon = key;
|
||||||
|
applyStickyIcon();
|
||||||
|
console.log(`[Radiant Lyrics] Sticky Lyrics icon set to "${key}"`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tear down all sticky lyrics UI (trigger + dropdown + classes)
|
||||||
|
// For when the feature is disabled in plugin settings
|
||||||
|
const teardownStickyLyrics = (): void => {
|
||||||
|
document.querySelectorAll(".sticky-lyrics-trigger").forEach((el) => el.remove());
|
||||||
|
document.querySelectorAll(".sticky-lyrics-dropdown").forEach((el) => el.remove());
|
||||||
|
const lyricsTab = document.querySelector('[data-test="tabs-lyrics"]');
|
||||||
|
if (lyricsTab) lyricsTab.classList.remove("sticky-lyrics-open");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Called from Settings
|
||||||
|
const updateStickyLyricsFeature = (): void => {
|
||||||
|
if (settings.stickyLyricsFeature) {
|
||||||
|
// Feature enabled - inject the dropdown
|
||||||
|
const tab = document.querySelector('[data-test="tabs-lyrics"]');
|
||||||
|
if (tab && !tab.querySelector(".sticky-lyrics-trigger")) {
|
||||||
|
createStickyLyricsDropdown();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Feature disabled — remove everything & disable inner toggle
|
||||||
|
settings.stickyLyrics = false;
|
||||||
|
teardownStickyLyrics();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(window as any).updateStickyLyricsFeature = updateStickyLyricsFeature;
|
||||||
|
|
||||||
|
const createStickyLyricsDropdown = (): void => {
|
||||||
|
if (!settings.stickyLyricsFeature) return;
|
||||||
|
const lyricsTab = document.querySelector(
|
||||||
|
'[data-test="tabs-lyrics"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
if (!lyricsTab) return;
|
||||||
|
if (lyricsTab.querySelector(".sticky-lyrics-trigger")) return;
|
||||||
|
|
||||||
|
// Trigger
|
||||||
|
// lives inside the Lyrics <li>
|
||||||
|
const trigger = document.createElement("div");
|
||||||
|
trigger.className = "sticky-lyrics-trigger";
|
||||||
|
trigger.setAttribute("title", "Sticky Lyrics");
|
||||||
|
|
||||||
|
// Set the icon & it's styling
|
||||||
|
// is only needed because i'm picky and prefer the Sparkle.. shhh
|
||||||
|
trigger.innerHTML = getStickyIcon();
|
||||||
|
//trigger.style.paddingLeft = settings.stickyLyricsIcon === "sparkle" ? "5px" : "5px";
|
||||||
|
|
||||||
|
// Block non-click events on trigger from reaching the Lyrics tab (capture phase)
|
||||||
|
// (capture phase stops the tab from activating & runs the toggle before the event is consumed by the SVG child) - Thx React.. again..
|
||||||
|
for (const evtName of ["pointerdown", "pointerup", "mousedown", "mouseup"] as const) {
|
||||||
|
trigger.addEventListener(evtName, (e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdown
|
||||||
|
// lives in document.body so its events never touch the Lyrics tab - Thx React..
|
||||||
|
const dropdown = document.createElement("div");
|
||||||
|
dropdown.className = "sticky-lyrics-dropdown";
|
||||||
|
dropdown.style.display = "none";
|
||||||
|
|
||||||
|
dropdown.innerHTML = `
|
||||||
|
<div class="sticky-lyrics-dropdown-row">
|
||||||
|
<span class="sticky-lyrics-label">Sticky Lyrics</span>
|
||||||
|
<label class="sticky-lyrics-switch">
|
||||||
|
<input type="checkbox" ${settings.stickyLyrics ? "checked" : ""}>
|
||||||
|
<span class="sticky-lyrics-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Toggle dropdown on trigger click
|
||||||
|
const openDropdown = (): void => {
|
||||||
|
const buttonRect = lyricsTab.getBoundingClientRect();
|
||||||
|
dropdown.style.top = `${buttonRect.bottom}px`;
|
||||||
|
dropdown.style.left = `${buttonRect.left}px`;
|
||||||
|
dropdown.style.width = `${buttonRect.width}px`;
|
||||||
|
dropdown.style.display = "block";
|
||||||
|
lyricsTab.classList.add("sticky-lyrics-open");
|
||||||
|
};
|
||||||
|
const closeDropdown = (): void => {
|
||||||
|
dropdown.style.display = "none";
|
||||||
|
lyricsTab.classList.remove("sticky-lyrics-open");
|
||||||
|
};
|
||||||
|
|
||||||
|
trigger.addEventListener("click", (e: MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const isActive = lyricsTab.getAttribute("aria-selected") === "true";
|
||||||
|
if (!isActive) {
|
||||||
|
// Navigate to Lyrics & open dropdown
|
||||||
|
lyricsTab.click();
|
||||||
|
// Delay to let the tab activate
|
||||||
|
setTimeout(() => openDropdown(), 150);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Toggle dropdown
|
||||||
|
if (dropdown.style.display === "none") {
|
||||||
|
openDropdown();
|
||||||
|
} else {
|
||||||
|
closeDropdown();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
// Handle toggle switch change
|
||||||
|
const checkbox = dropdown.querySelector(
|
||||||
|
'input[type="checkbox"]',
|
||||||
|
) as HTMLInputElement;
|
||||||
|
checkbox.addEventListener("change", () => {
|
||||||
|
settings.stickyLyrics = checkbox.checked;
|
||||||
|
if (settings.stickyLyrics) {
|
||||||
|
handleStickyLyricsTrackChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close dropdown when clicking outside trigger & dropdown
|
||||||
|
const handleOutsideClick = (e: MouseEvent): void => {
|
||||||
|
if (!trigger.contains(e.target as Node) && !dropdown.contains(e.target as Node)) {
|
||||||
|
closeDropdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("click", handleOutsideClick);
|
||||||
|
|
||||||
|
// Trigger goes inside the Lyrics <li> & dropdown goes in <body>
|
||||||
|
lyricsTab.appendChild(trigger);
|
||||||
|
document.body.appendChild(dropdown);
|
||||||
|
|
||||||
|
// Register cleanup
|
||||||
|
unloads.add(() => {
|
||||||
|
document.removeEventListener("click", handleOutsideClick);
|
||||||
|
lyricsTab.classList.remove("sticky-lyrics-open");
|
||||||
|
trigger.remove();
|
||||||
|
dropdown.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle switching tabs on track change
|
||||||
|
const handleStickyLyricsTrackChange = (): void => {
|
||||||
|
if (!settings.stickyLyricsFeature || !settings.stickyLyrics) return;
|
||||||
|
|
||||||
|
// Process the track change and update tab state
|
||||||
|
// Tidal takes a while to process the track change sometimes :(
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!settings.stickyLyricsFeature || !settings.stickyLyrics) return;
|
||||||
|
|
||||||
|
const lyricsTab = document.querySelector(
|
||||||
|
'[data-test="tabs-lyrics"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
const playQueueTab = document.querySelector(
|
||||||
|
'[data-test="tabs-play-queue"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
|
if (!lyricsTab) {
|
||||||
|
// fall back to play queue
|
||||||
|
if (playQueueTab) playQueueTab.click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to switch to lyrics
|
||||||
|
lyricsTab.click();
|
||||||
|
|
||||||
|
// Verify we actually stayed on lyrics after a short delay
|
||||||
|
// TODO: Make not shitty (one day maybe)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!settings.stickyLyrics) return;
|
||||||
|
const onLyrics = document.querySelector(
|
||||||
|
'[data-test="tabs-lyrics"][aria-selected="true"]',
|
||||||
|
);
|
||||||
|
if (!onLyrics && playQueueTab) {
|
||||||
|
// Got redirected away from lyrics - fall back to play queue
|
||||||
|
playQueueTab.click();
|
||||||
|
}
|
||||||
|
}, 800);
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Observer: create dropdown when lyrics tab appears & detect track changes
|
||||||
|
function setupStickyLyricsObserver(): void {
|
||||||
|
// Create dropdown if lyrics tab already exists
|
||||||
|
if (settings.stickyLyricsFeature) {
|
||||||
|
const existing = document.querySelector('[data-test="tabs-lyrics"]');
|
||||||
|
if (existing && !existing.querySelector(".sticky-lyrics-trigger")) {
|
||||||
|
createStickyLyricsDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-create dropdown whenever lyrics tab is back from the ether
|
||||||
|
observe<HTMLElement>(unloads, '[data-test="tabs-lyrics"]', () => {
|
||||||
|
if (!settings.stickyLyricsFeature) return;
|
||||||
|
const tab = document.querySelector('[data-test="tabs-lyrics"]');
|
||||||
|
if (tab && !tab.querySelector(".sticky-lyrics-trigger")) {
|
||||||
|
createStickyLyricsDropdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Detect track changes & trigger sticky lyrics switching
|
||||||
|
let stickyLastTrackId: string | null =
|
||||||
|
PlayState.playbackContext?.actualProductId ?? null;
|
||||||
|
const checkStickyTrackChange = (): void => {
|
||||||
|
if (!settings.stickyLyricsFeature || !settings.stickyLyrics) return;
|
||||||
|
const currentTrackId = PlayState.playbackContext?.actualProductId;
|
||||||
|
if (currentTrackId && currentTrackId !== stickyLastTrackId) {
|
||||||
|
stickyLastTrackId = currentTrackId;
|
||||||
|
handleStickyLyricsTrackChange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stickyIntervalId = setInterval(checkStickyTrackChange, 500);
|
||||||
|
unloads.add(() => clearInterval(stickyIntervalId));
|
||||||
|
}
|
||||||
|
|
||||||
// Marker: Observers
|
// Marker: Observers
|
||||||
// Shared observer-based hooks and polling fallbacks
|
// Shared observer-based hooks and polling fallbacks
|
||||||
const observeTrackChanges = (): void => {
|
const observeTrackChanges = (): void => {
|
||||||
@@ -957,8 +1202,9 @@ function setupTrackTitleObserver(): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the button creation and observers (non-polling)
|
// Init observers
|
||||||
setupHeaderObserver();
|
setupHeaderObserver();
|
||||||
setupNowPlayingObserver();
|
setupNowPlayingObserver();
|
||||||
setupTrackTitleObserver();
|
setupTrackTitleObserver();
|
||||||
observeTrackChanges();
|
observeTrackChanges();
|
||||||
|
setupStickyLyricsObserver();
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/* Sidebar with dynamic hash */
|
/* Sidebar */
|
||||||
[class*="_sidebar_"] {
|
[class*="_sidebar_"] {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Section header with dynamic hash */
|
/* Section header */
|
||||||
[class*="_sectionHeader_"] {
|
[class*="_sectionHeader_"] {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rounded corners for various elements */
|
/* Rounded corners */
|
||||||
[class*="_thumbnail_"],
|
[class*="_thumbnail_"],
|
||||||
[class*="_imageWrapper_"],
|
[class*="_imageWrapper_"],
|
||||||
[class*="_coverImage_"],
|
[class*="_coverImage_"],
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
border-radius: 5px !important;
|
border-radius: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* MARKER: HideUI CSS*/
|
||||||
|
|
||||||
/* Only apply styles when UI is hidden */
|
/* Only apply styles when UI is hidden */
|
||||||
.radiant-lyrics-ui-hidden [class*="tabItems"] {
|
.radiant-lyrics-ui-hidden [class*="tabItems"] {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
@@ -74,6 +77,156 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* MARKER: Sticky Lyrics CSS */
|
||||||
|
|
||||||
|
/* Lyrics tab */
|
||||||
|
[data-test="tabs-lyrics"]:has(.sticky-lyrics-trigger) {
|
||||||
|
position: relative !important;
|
||||||
|
padding-right: 38px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trigger */
|
||||||
|
.sticky-lyrics-trigger {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 38px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 0px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: default;
|
||||||
|
color: #CCCCD1;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Divider line */
|
||||||
|
.sticky-lyrics-trigger::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 5px;
|
||||||
|
top: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 1px;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When Lyrics tab is active — show divider & make icon black*/
|
||||||
|
[data-test="tabs-lyrics"][aria-selected="true"] .sticky-lyrics-trigger {
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-test="tabs-lyrics"][aria-selected="true"] .sticky-lyrics-trigger::before {
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-test="tabs-lyrics"][aria-selected="true"] .sticky-lyrics-trigger:hover {
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Square the Lyrics button bottom corners when dropdown is open */
|
||||||
|
[data-test="tabs-lyrics"].sticky-lyrics-open {
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown */
|
||||||
|
.sticky-lyrics-dropdown {
|
||||||
|
position: fixed;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0 0 16px 16px;
|
||||||
|
padding: 8px 12px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
|
||||||
|
clip-path: inset(0 -20px -20px -20px);
|
||||||
|
animation: stickyLyricsDropdownIn 0.12s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stickyLyricsDropdownIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
clip-path: inset(0 0 100% 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
clip-path: inset(0 0 0 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row containing label + toggle */
|
||||||
|
.sticky-lyrics-dropdown-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-lyrics-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 1);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle switch */
|
||||||
|
.sticky-lyrics-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 34px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-lyrics-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-lyrics-slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-lyrics-slider::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
background-color: white;
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-lyrics-switch input:checked + .sticky-lyrics-slider {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-lyrics-switch input:checked + .sticky-lyrics-slider::before {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* MARKER: PATCHES (Random Fixes for Tidals Changes) */
|
||||||
|
/* These change allot so i gave them their own section */
|
||||||
|
|
||||||
/* Fixes the new Sticky Header Tidal added.. in the shittest jankiest way possible */
|
/* Fixes the new Sticky Header Tidal added.. in the shittest jankiest way possible */
|
||||||
[class*="_stickyHeader"] {
|
[class*="_stickyHeader"] {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user