mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
Cleanup <3
This commit is contained in:
@@ -7,12 +7,13 @@ declare global {
|
||||
updateRadiantLyricsStyles?: () => void;
|
||||
updateRadiantLyricsTextGlow?: () => void;
|
||||
updateStickyLyricsFeature?: () => void;
|
||||
updateStickyLyricsSetting?: (checked: boolean) => void;
|
||||
updateRadiantLyricsPlayerBarTint?: () => void;
|
||||
updateRadiantLyricsGlobalBackground?: () => void;
|
||||
updateRadiantLyricsNowPlayingBackground?: () => void;
|
||||
updateStickyLyricsIcon?: () => void;
|
||||
updateQualityProgressColor?: () => void;
|
||||
updateLyricsStyle?: () => void;
|
||||
updateLyricsStyleSetting?: (value: number) => void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +40,10 @@ export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
|
||||
backgroundBrightness: 40,
|
||||
spinSpeed: 45,
|
||||
settingsAffectNowPlaying: true,
|
||||
stickyLyricsFeature: true,
|
||||
stickyLyrics: true,
|
||||
stickyLyrics: false,
|
||||
stickyLyricsIcon: "sparkle" as string,
|
||||
lyricsStyle: 0,
|
||||
syllableLogging: false,
|
||||
});
|
||||
|
||||
export const Settings = () => {
|
||||
@@ -62,9 +63,7 @@ export const Settings = () => {
|
||||
const [performanceMode, setPerformanceMode] = React.useState(
|
||||
settings.performanceMode,
|
||||
);
|
||||
const [spinningArt, setspinningArt] = React.useState(
|
||||
settings.spinningArt,
|
||||
);
|
||||
const [spinningArt, setspinningArt] = React.useState(settings.spinningArt);
|
||||
const [backgroundContrast, setBackgroundContrast] = React.useState(
|
||||
settings.backgroundContrast,
|
||||
);
|
||||
@@ -103,13 +102,33 @@ export const Settings = () => {
|
||||
);
|
||||
const [showTintColorPicker, setShowTintColorPicker] = React.useState(false);
|
||||
const [isTintAnimatingIn, setIsTintAnimatingIn] = React.useState(false);
|
||||
const [shouldRenderTintPicker, setShouldRenderTintPicker] = React.useState(false);
|
||||
const [tintCustomInput, setTintCustomInput] = React.useState(settings.playerBarTintColor);
|
||||
const [tintCustomColors, setTintCustomColors] = React.useState(settings.playerBarTintCustomColors);
|
||||
const [tintHoveredColorIndex, setTintHoveredColorIndex] = React.useState<number | null>(null);
|
||||
const [stickyLyricsFeature, setStickyLyricsFeature] = React.useState(
|
||||
settings.stickyLyricsFeature,
|
||||
const [shouldRenderTintPicker, setShouldRenderTintPicker] =
|
||||
React.useState(false);
|
||||
const [tintCustomInput, setTintCustomInput] = React.useState(
|
||||
settings.playerBarTintColor,
|
||||
);
|
||||
const [tintCustomColors, setTintCustomColors] = React.useState(
|
||||
settings.playerBarTintCustomColors,
|
||||
);
|
||||
const [tintHoveredColorIndex, setTintHoveredColorIndex] = React.useState<
|
||||
number | 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);
|
||||
React.useEffect(() => {
|
||||
window.updateLyricsStyleSetting = (value: number) =>
|
||||
setLyricsStyle(value);
|
||||
return () => {
|
||||
window.updateLyricsStyleSetting = undefined;
|
||||
};
|
||||
}, []);
|
||||
const [qualityProgressColor, setQualityProgressColor] = React.useState(
|
||||
settings.qualityProgressColor,
|
||||
);
|
||||
@@ -120,9 +139,8 @@ export const Settings = () => {
|
||||
onChange: (_: unknown, checked: boolean) => void;
|
||||
checked: boolean;
|
||||
};
|
||||
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<
|
||||
AnySwitchProps
|
||||
>;
|
||||
const AnySwitch =
|
||||
LunaSwitchSetting as unknown as React.ComponentType<AnySwitchProps>;
|
||||
|
||||
return (
|
||||
<LunaSettings>
|
||||
@@ -169,31 +187,32 @@ export const Settings = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<AnySwitch
|
||||
title="Sticky Lyrics"
|
||||
desc="auto-switches to Play Queue when lyrics aren't available (mirrored in lyrics dropdown)"
|
||||
checked={stickyLyricsFeature}
|
||||
onChange={(_: unknown, checked: boolean) => {
|
||||
settings.stickyLyricsFeature = checked;
|
||||
setStickyLyricsFeature(checked);
|
||||
if (window.updateStickyLyricsFeature) {
|
||||
window.updateStickyLyricsFeature();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<LunaNumberSetting
|
||||
title="Lyrics Style"
|
||||
desc="0 = Line (default), 1 = Word, 2 = Syllable (coming soon) (mirrored in lyrics dropdown)"
|
||||
desc="0 = Line (default), 1 = Word, 2 = Syllable (mirrored in lyrics dropdown)"
|
||||
min={0}
|
||||
max={1}
|
||||
step={1}
|
||||
value={settings.lyricsStyle}
|
||||
value={lyricsStyle}
|
||||
onNumber={(value: number) => {
|
||||
settings.lyricsStyle = value;
|
||||
setLyricsStyle(value);
|
||||
if (window.updateLyricsStyle) {
|
||||
window.updateLyricsStyle();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<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="Hide UI Feature"
|
||||
@@ -210,7 +229,10 @@ export const Settings = () => {
|
||||
desc="Keep player bar visible when UI is hidden"
|
||||
checked={playerBarVisible}
|
||||
onChange={(_: unknown, checked: boolean) => {
|
||||
console.log("Player Bar Visibility:", checked ? "visible" : "hidden");
|
||||
console.log(
|
||||
"Player Bar Visibility:",
|
||||
checked ? "visible" : "hidden",
|
||||
);
|
||||
settings.playerBarVisible = checked;
|
||||
setPlayerBarVisible(checked);
|
||||
// Update styles immediately when setting changes
|
||||
@@ -324,10 +346,25 @@ export const Settings = () => {
|
||||
};
|
||||
|
||||
const tintColorPresets = [
|
||||
"#000000", "#111111", "#222222", "#333333", "#444444",
|
||||
"#555555", "#666666", "#888888", "#aaaaaa", "#cccccc",
|
||||
"#ffffff", "#0d1117", "#1a1a2e", "#16213e", "#0f3460",
|
||||
"#1b1b2f", "#162447", "#1f4068", "#e94560",
|
||||
"#000000",
|
||||
"#111111",
|
||||
"#222222",
|
||||
"#333333",
|
||||
"#444444",
|
||||
"#555555",
|
||||
"#666666",
|
||||
"#888888",
|
||||
"#aaaaaa",
|
||||
"#cccccc",
|
||||
"#ffffff",
|
||||
"#0d1117",
|
||||
"#1a1a2e",
|
||||
"#16213e",
|
||||
"#0f3460",
|
||||
"#1b1b2f",
|
||||
"#162447",
|
||||
"#1f4068",
|
||||
"#e94560",
|
||||
];
|
||||
|
||||
const allTintColors = [...tintColorPresets, ...tintCustomColors];
|
||||
@@ -350,7 +387,11 @@ export const Settings = () => {
|
||||
{/* Color swatch — positioned just left of the value box */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => showTintColorPicker ? closeTintColorPicker() : openTintColorPicker()}
|
||||
onClick={() =>
|
||||
showTintColorPicker
|
||||
? closeTintColorPicker()
|
||||
: openTintColorPicker()
|
||||
}
|
||||
style={{
|
||||
width: "28px",
|
||||
height: "28px",
|
||||
@@ -366,7 +407,14 @@ export const Settings = () => {
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
<div style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.1)", backdropFilter: "blur(2px)" }} />
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
background: "rgba(0,0,0,0.1)",
|
||||
backdropFilter: "blur(2px)",
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Color Picker Modal */}
|
||||
@@ -378,7 +426,10 @@ export const Settings = () => {
|
||||
onClick={closeTintColorPicker}
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0, left: 0, right: 0, bottom: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: "rgba(0,0,0,0.6)",
|
||||
zIndex: 1000,
|
||||
opacity: isTintAnimatingIn ? 1 : 0,
|
||||
@@ -412,11 +463,25 @@ export const Settings = () => {
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "12px", color: "#fff", fontWeight: "bold", fontSize: "14px" }}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: "12px",
|
||||
color: "#fff",
|
||||
fontWeight: "bold",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
Choose Tint Color
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: "8px", marginBottom: "16px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(7, 1fr)",
|
||||
gap: "8px",
|
||||
marginBottom: "16px",
|
||||
}}
|
||||
>
|
||||
{allTintColors.map((color, index) => {
|
||||
const isCustomColor = tintCustomColors.includes(color);
|
||||
const isHovered = tintHoveredColorIndex === index;
|
||||
@@ -424,18 +489,27 @@ export const Settings = () => {
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: cosmetic hover tracking on wrapper containing interactive buttons
|
||||
<div
|
||||
key={color}
|
||||
style={{ position: "relative", width: "32px", height: "32px", cursor: "pointer" }}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "32px",
|
||||
height: "32px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onMouseEnter={() => setTintHoveredColorIndex(index)}
|
||||
onMouseLeave={() => setTintHoveredColorIndex(null)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { updateTintColor(color); closeTintColorPicker(); }}
|
||||
onClick={() => {
|
||||
updateTintColor(color);
|
||||
closeTintColorPicker();
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: "6px",
|
||||
border: playerBarTintColor === color
|
||||
border:
|
||||
playerBarTintColor === color
|
||||
? "2px solid #fff"
|
||||
: "1px solid rgba(255,255,255,0.2)",
|
||||
background: color,
|
||||
@@ -446,11 +520,16 @@ export const Settings = () => {
|
||||
{isCustomColor && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); removeTintCustomColor(color); }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeTintCustomColor(color);
|
||||
}}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "-4px", right: "-4px",
|
||||
width: "16px", height: "16px",
|
||||
top: "-4px",
|
||||
right: "-4px",
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
borderRadius: "50%",
|
||||
border: "1px solid rgba(255,255,255,0.8)",
|
||||
background: "rgba(0,0,0,0.8)",
|
||||
@@ -474,10 +553,22 @@ export const Settings = () => {
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "12px" }}>
|
||||
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: "12px", marginBottom: "6px" }}>
|
||||
<div
|
||||
style={{
|
||||
color: "rgba(255,255,255,0.7)",
|
||||
fontSize: "12px",
|
||||
marginBottom: "6px",
|
||||
}}
|
||||
>
|
||||
Add Custom Color
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={tintCustomInput}
|
||||
@@ -503,9 +594,13 @@ export const Settings = () => {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { updateTintColor(tintCustomInput); addTintCustomColor(); }}
|
||||
onClick={() => {
|
||||
updateTintColor(tintCustomInput);
|
||||
addTintCustomColor();
|
||||
}}
|
||||
style={{
|
||||
width: "32px", height: "32px",
|
||||
width: "32px",
|
||||
height: "32px",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
background: "rgba(255,255,255,0.15)",
|
||||
@@ -517,8 +612,14 @@ export const Settings = () => {
|
||||
justifyContent: "center",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.25)"; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.15)"; }}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background =
|
||||
"rgba(255,255,255,0.25)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background =
|
||||
"rgba(255,255,255,0.15)";
|
||||
}}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
@@ -43,28 +43,6 @@
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
/* Performance mode optimizations - keep spinning but optimize other aspects */
|
||||
.global-spinning-image.performance-mode-static {
|
||||
/* Keep animation enabled in performance mode */
|
||||
/* Lighter blur for performance */
|
||||
/* biome-ignore lint: Required to override app styles in performance mode */
|
||||
filter: blur(20px) brightness(0.4) contrast(1.2) saturate(1) !important;
|
||||
/* Smaller size for performance */
|
||||
/* biome-ignore lint: Required to override app layout sizes */
|
||||
width: 120vw !important;
|
||||
/* biome-ignore lint: Required to override app layout sizes */
|
||||
height: 120vh !important;
|
||||
}
|
||||
|
||||
.now-playing-background-image.performance-mode-static {
|
||||
/* Keep animation enabled in performance mode */
|
||||
/* Optimized size and effects for performance */
|
||||
/* biome-ignore lint: Required to override inline sizes in performance mode */
|
||||
width: 80vw !important;
|
||||
/* biome-ignore lint: Required to override inline sizes in performance mode */
|
||||
height: 80vh !important;
|
||||
}
|
||||
|
||||
/* Now Playing Background Container Optimization */
|
||||
.now-playing-background-container {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// MARKER: Core Setup
|
||||
import { LunaUnload, Tracer, ftch } from "@luna/core";
|
||||
import { type LunaUnload, Tracer } from "@luna/core";
|
||||
import { StyleTag, PlayState, MediaItem, observePromise, observe, safeInterval, safeTimeout } from "@luna/lib";
|
||||
import { settings, Settings } from "./Settings";
|
||||
// Interpret integer backgroundScale (e.g., 10=1.0x, 20=2.0x)
|
||||
@@ -87,11 +87,6 @@ const applyFloatingPlayerBar = (): void => {
|
||||
// Alias for settings callback
|
||||
const updateRadiantLyricsPlayerBarTint = applyFloatingPlayerBar;
|
||||
|
||||
// Apply floating player bar styles if enabled
|
||||
if (settings.floatingPlayerBar) {
|
||||
floatingPlayerBarStyleTag.css = floatingPlayerBarCss;
|
||||
}
|
||||
|
||||
// Apply Tint and Observe in case doesn't exist yet (ik this isnt the best way to do it but.. make a PR i dare ya!)
|
||||
applyPlayerBarTintToElement();
|
||||
observe<HTMLElement>(unloads, '[data-test="footer-player"]', () => {
|
||||
@@ -132,16 +127,9 @@ const applyQualityProgressColor = (): void => {
|
||||
progressIndicator.style.setProperty("background-color", color, "important");
|
||||
};
|
||||
|
||||
// Called Settings
|
||||
const updateQualityProgressColor = (): void => {
|
||||
// Apply on load
|
||||
if (settings.qualityProgressColor) {
|
||||
applyQualityProgressColor();
|
||||
};
|
||||
|
||||
function setupQualityProgressObserver(): void {
|
||||
// Apply on load (uses observeTrackChanges instead of polling yay me <3)
|
||||
if (settings.qualityProgressColor) {
|
||||
applyQualityProgressColor();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply base styles always (I kinda dont really remember what this does but it's important i guess)
|
||||
@@ -212,7 +200,7 @@ const updateRadiantLyricsStyles = function (): void {
|
||||
|
||||
// MARKER: UI Visibility Control
|
||||
// UI state shared across features
|
||||
var isHidden = false;
|
||||
let isHidden = false;
|
||||
let unhideButtonAutoFadeTimeout: number | null = null;
|
||||
|
||||
// Helper to safely create a one-off timeout that clears previous if any
|
||||
@@ -433,7 +421,6 @@ let nowPlayingBackgroundContainer: HTMLElement | null = null;
|
||||
let nowPlayingBackgroundImage: HTMLImageElement | null = null;
|
||||
let nowPlayingBlackBg: HTMLElement | null = null;
|
||||
let nowPlayingGradientOverlay: HTMLElement | null = null;
|
||||
let currentNowPlayingCoverSrc: string | null = null;
|
||||
let spinAnimationAdded = false;
|
||||
|
||||
// apply scaled pixel sizes to cover art
|
||||
@@ -468,7 +455,7 @@ function updateCoverArtBackground(method: number = 0): void {
|
||||
return;
|
||||
}
|
||||
|
||||
let coverArtImageElement = document.querySelector(
|
||||
const coverArtImageElement = document.querySelector(
|
||||
'figure[class*="_albumImage"] > div > div > div > img',
|
||||
) as HTMLImageElement;
|
||||
let coverArtImageSrc: string | null = null;
|
||||
@@ -590,21 +577,17 @@ function updateCoverArtBackground(method: number = 0): void {
|
||||
nowPlayingBackgroundImage.src !== coverArtImageSrc
|
||||
) {
|
||||
nowPlayingBackgroundImage.src = coverArtImageSrc;
|
||||
currentNowPlayingCoverSrc = coverArtImageSrc;
|
||||
}
|
||||
|
||||
// Apply pixel-based size using intrinsic dimensions
|
||||
applyScaledPixelSize(nowPlayingBackgroundImage);
|
||||
|
||||
// Apply performance-optimized settings (filter/animation); size handled above
|
||||
if (nowPlayingBackgroundImage) {
|
||||
if (settings.performanceMode) {
|
||||
// Performance mode with spinning enabled
|
||||
const blur = Math.min(settings.backgroundBlur, 20);
|
||||
const contrast = Math.min(settings.backgroundContrast, 150);
|
||||
const radiusPm = `${settings.backgroundRadius}%`;
|
||||
if (nowPlayingBackgroundImage.style.borderRadius !== radiusPm)
|
||||
nowPlayingBackgroundImage.style.borderRadius = radiusPm;
|
||||
const blur = settings.performanceMode ? Math.min(settings.backgroundBlur, 20) : settings.backgroundBlur;
|
||||
const contrast = settings.performanceMode ? Math.min(settings.backgroundContrast, 150) : settings.backgroundContrast;
|
||||
const radius = `${settings.backgroundRadius}%`;
|
||||
if (nowPlayingBackgroundImage.style.borderRadius !== radius)
|
||||
nowPlayingBackgroundImage.style.borderRadius = radius;
|
||||
const filt = `blur(${blur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${contrast}%)`;
|
||||
if (nowPlayingBackgroundImage.style.filter !== filt)
|
||||
nowPlayingBackgroundImage.style.filter = filt;
|
||||
@@ -616,25 +599,6 @@ function updateCoverArtBackground(method: number = 0): void {
|
||||
nowPlayingBackgroundImage.style.animation = anim;
|
||||
if (nowPlayingBackgroundImage.style.willChange !== wc)
|
||||
nowPlayingBackgroundImage.style.willChange = wc;
|
||||
nowPlayingBackgroundImage.classList.remove("performance-mode-static");
|
||||
} else {
|
||||
// Normal mode
|
||||
const radiusNm = `${settings.backgroundRadius}%`;
|
||||
if (nowPlayingBackgroundImage.style.borderRadius !== radiusNm)
|
||||
nowPlayingBackgroundImage.style.borderRadius = radiusNm;
|
||||
const filt = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`;
|
||||
if (nowPlayingBackgroundImage.style.filter !== filt)
|
||||
nowPlayingBackgroundImage.style.filter = filt;
|
||||
const anim = settings.spinningArt
|
||||
? `spin ${settings.spinSpeed}s linear infinite`
|
||||
: "none";
|
||||
const wc = settings.spinningArt ? "transform" : "auto";
|
||||
if (nowPlayingBackgroundImage.style.animation !== anim)
|
||||
nowPlayingBackgroundImage.style.animation = anim;
|
||||
if (nowPlayingBackgroundImage.style.willChange !== wc)
|
||||
nowPlayingBackgroundImage.style.willChange = wc;
|
||||
nowPlayingBackgroundImage.classList.remove("performance-mode-static");
|
||||
}
|
||||
}
|
||||
|
||||
// Add keyframe animation only once
|
||||
@@ -764,18 +728,14 @@ const applyGlobalSpinningBackground = (coverArtImageSrc: string): void => {
|
||||
globalBackgroundImage.src = coverArtImageSrc;
|
||||
}
|
||||
|
||||
// Apply performance-optimized settings
|
||||
if (globalBackgroundImage) {
|
||||
// Pixel-based sizing based on intrinsic dimensions
|
||||
applyScaledPixelSize(globalBackgroundImage);
|
||||
const blur = settings.performanceMode ? Math.min(settings.backgroundBlur, 20) : settings.backgroundBlur;
|
||||
const contrast = settings.performanceMode ? Math.min(settings.backgroundContrast, 150) : settings.backgroundContrast;
|
||||
const radius = `${settings.backgroundRadius}%`;
|
||||
// Performance mode optimizations
|
||||
if (settings.performanceMode) {
|
||||
// Performance mode with spinning enabled
|
||||
globalBackgroundImage.style.filter = `blur(${Math.min(settings.backgroundBlur, 20)}px) brightness(${settings.backgroundBrightness / 100}) contrast(${Math.min(settings.backgroundContrast, 150)}%)`;
|
||||
globalBackgroundImage.style.filter = `blur(${blur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${contrast}%)`;
|
||||
if (globalBackgroundImage.style.borderRadius !== radius)
|
||||
globalBackgroundImage.style.borderRadius = radius;
|
||||
// Do not apply radius to vignette overlay; matches Now Playing behavior
|
||||
if (settings.spinningArt) {
|
||||
globalBackgroundImage.style.animation = `spinGlobal ${settings.spinSpeed}s linear infinite`;
|
||||
globalBackgroundImage.style.willChange = "transform";
|
||||
@@ -783,22 +743,6 @@ const applyGlobalSpinningBackground = (coverArtImageSrc: string): void => {
|
||||
globalBackgroundImage.style.animation = "none";
|
||||
globalBackgroundImage.style.willChange = "auto";
|
||||
}
|
||||
globalBackgroundImage.classList.remove("performance-mode-static");
|
||||
} else {
|
||||
// Normal mode
|
||||
globalBackgroundImage.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`;
|
||||
if (globalBackgroundImage.style.borderRadius !== radius)
|
||||
globalBackgroundImage.style.borderRadius = radius;
|
||||
// Do not apply radius to vignette overlay; matches Now Playing behavior
|
||||
if (settings.spinningArt) {
|
||||
globalBackgroundImage.style.animation = `spinGlobal ${settings.spinSpeed}s linear infinite`;
|
||||
globalBackgroundImage.style.willChange = "transform";
|
||||
} else {
|
||||
globalBackgroundImage.style.animation = "none";
|
||||
globalBackgroundImage.style.willChange = "auto";
|
||||
}
|
||||
globalBackgroundImage.classList.remove("performance-mode-static");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -869,11 +813,10 @@ const updateRadiantLyricsNowPlayingBackground = function (): void {
|
||||
const radius = `${settings.backgroundRadius}%`;
|
||||
if (imgElement.style.borderRadius !== radius) imgElement.style.borderRadius = radius;
|
||||
|
||||
// Performance mode optimizations
|
||||
if (settings.performanceMode) {
|
||||
// Reduce blur and effects for better performance, but keep spinning
|
||||
blur = Math.min(blur, 20);
|
||||
contrast = Math.min(contrast, 150);
|
||||
}
|
||||
if (settings.spinningArt) {
|
||||
imgElement.style.animation = `spin ${spinSpeed}s linear infinite`;
|
||||
imgElement.style.willChange = "transform";
|
||||
@@ -881,18 +824,6 @@ const updateRadiantLyricsNowPlayingBackground = function (): void {
|
||||
imgElement.style.animation = "none";
|
||||
imgElement.style.willChange = "auto";
|
||||
}
|
||||
imgElement.classList.remove("performance-mode-static");
|
||||
} else {
|
||||
if (settings.spinningArt) {
|
||||
imgElement.style.animation = `spin ${spinSpeed}s linear infinite`;
|
||||
imgElement.style.willChange = "transform";
|
||||
} else {
|
||||
imgElement.style.animation = "none";
|
||||
imgElement.style.willChange = "auto";
|
||||
}
|
||||
imgElement.classList.remove("performance-mode-static");
|
||||
}
|
||||
|
||||
imgElement.style.filter = `blur(${blur}px) brightness(${brightness / 100}) contrast(${contrast}%)`;
|
||||
});
|
||||
};
|
||||
@@ -905,7 +836,7 @@ const updateRadiantLyricsNowPlayingBackground = function (): void {
|
||||
updateRadiantLyricsNowPlayingBackground;
|
||||
(window as any).updateRadiantLyricsTextGlow = updateRadiantLyricsTextGlow;
|
||||
(window as any).updateRadiantLyricsPlayerBarTint = updateRadiantLyricsPlayerBarTint;
|
||||
(window as any).updateQualityProgressColor = updateQualityProgressColor;
|
||||
(window as any).updateQualityProgressColor = applyQualityProgressColor;
|
||||
|
||||
const cleanUpDynamicArt = function (): void {
|
||||
// Clean up cached Now Playing elements
|
||||
@@ -921,7 +852,6 @@ const cleanUpDynamicArt = function (): void {
|
||||
nowPlayingBackgroundImage = null;
|
||||
nowPlayingBlackBg = null;
|
||||
nowPlayingGradientOverlay = null;
|
||||
currentNowPlayingCoverSrc = null;
|
||||
|
||||
// Clean up any remaining elements (fallback)
|
||||
const nowPlayingBackgroundImages = document.getElementsByClassName(
|
||||
@@ -1033,7 +963,7 @@ 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";
|
||||
trigger.style.paddingLeft = "5px";
|
||||
};
|
||||
|
||||
// Console: StickyLyrics.icon = "sparkle" or "chevron"
|
||||
@@ -1052,9 +982,20 @@ const applyStickyIcon = (): void => {
|
||||
},
|
||||
};
|
||||
|
||||
// Called from Settings — sync the dropdown toggle with the setting
|
||||
// Console: Syllables.log = true/false
|
||||
// Verbose logging for word/syllable lyrics (hidden setting)
|
||||
const sylLog = (...args: unknown[]) => { if (settings.syllableLogging) console.log(...args); };
|
||||
const sylTrace = (...args: unknown[]) => { if (settings.syllableLogging) trace.log(...args); };
|
||||
(window as any).Syllables = {
|
||||
get log() { return settings.syllableLogging; },
|
||||
set log(value: boolean) {
|
||||
settings.syllableLogging = value;
|
||||
console.log(`[Radiant Lyrics] Syllable logging ${value ? "enabled" : "disabled"}`);
|
||||
},
|
||||
};
|
||||
|
||||
// Called from Settings (mirrors dropdown checkbox)
|
||||
const updateStickyLyricsFeature = (): void => {
|
||||
settings.stickyLyrics = settings.stickyLyricsFeature;
|
||||
const checkbox = document.querySelector('input[data-setting="stickyLyrics"]') as HTMLInputElement;
|
||||
if (checkbox) checkbox.checked = settings.stickyLyrics;
|
||||
};
|
||||
@@ -1076,7 +1017,6 @@ const createStickyLyricsDropdown = (): void => {
|
||||
// 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..
|
||||
@@ -1147,6 +1087,7 @@ const createStickyLyricsDropdown = (): void => {
|
||||
) as HTMLInputElement;
|
||||
stickyCheckbox.addEventListener("change", () => {
|
||||
settings.stickyLyrics = stickyCheckbox.checked;
|
||||
(window as any).updateStickyLyricsSetting?.(stickyCheckbox.checked);
|
||||
if (settings.stickyLyrics) {
|
||||
handleStickyLyricsTrackChange();
|
||||
}
|
||||
@@ -1165,7 +1106,8 @@ const createStickyLyricsDropdown = (): void => {
|
||||
settings.lyricsStyle = style;
|
||||
for (const b of segButtons) b.classList.remove("rl-seg-active");
|
||||
btn.classList.add("rl-seg-active");
|
||||
console.log(`[RL-Syllable] Lyrics style changed to "${styleNames[style]}"`);
|
||||
(window as any).updateLyricsStyleSetting?.(style);
|
||||
sylLog(`[RL-Syllable] Lyrics style changed to "${styleNames[style]}"`);
|
||||
toggle();
|
||||
});
|
||||
}
|
||||
@@ -1321,7 +1263,6 @@ interface LineEntry {
|
||||
}
|
||||
|
||||
let lines: LineEntry[] = [];
|
||||
let allWords: WordEntry[] = [];
|
||||
let rerenderObserver: MutationObserver | null = null;
|
||||
let rerenderDebounce: number | null = null;
|
||||
let activeWordEl: HTMLSpanElement | null = null;
|
||||
@@ -1397,7 +1338,7 @@ const fetchWordLyrics = async (
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
trace.log(`Fetching word lyrics: ${url}`);
|
||||
sylTrace(`Fetching word lyrics: ${url}`);
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
trace.log(`Word lyrics fetch failed: ${res.status} from ${url}`);
|
||||
@@ -1435,7 +1376,7 @@ const hideTidalLyrics = (): boolean => {
|
||||
// Save classes on first call (for teardown)
|
||||
if (!savedTidalClasses) {
|
||||
savedTidalClasses = tidalClasses;
|
||||
trace.log(`Saved Tidal classes: ${savedTidalClasses.join(", ")}`);
|
||||
sylTrace(`Saved Tidal classes: ${savedTidalClasses.join(", ")}`);
|
||||
}
|
||||
|
||||
for (const c of tidalClasses) lyricsContainer.classList.remove(c);
|
||||
@@ -1455,7 +1396,7 @@ const restoreTidalLyrics = (): void => {
|
||||
lyricsContainer.classList.add(c);
|
||||
}
|
||||
}
|
||||
trace.log(`Restored Tidal classes: ${savedTidalClasses.join(", ")}`);
|
||||
sylTrace(`Restored Tidal classes: ${savedTidalClasses.join(", ")}`);
|
||||
}
|
||||
|
||||
lyricsContainer.classList.remove("rl-wbw-active");
|
||||
@@ -1478,20 +1419,18 @@ const restoreTidalLyrics = (): void => {
|
||||
|
||||
// build word/syllable container over tidal spans
|
||||
const buildWordSpans = (): {
|
||||
words: WordEntry[];
|
||||
lines: LineEntry[];
|
||||
} => {
|
||||
const words: WordEntry[] = [];
|
||||
const lines: LineEntry[] = [];
|
||||
if (!lyricsData) return { words, lines };
|
||||
if (!lyricsData) return { lines };
|
||||
|
||||
const lyricsContainer = document.querySelector(
|
||||
'[data-test="lyrics-lines"]',
|
||||
) as HTMLElement;
|
||||
if (!lyricsContainer) return { words, lines };
|
||||
if (!lyricsContainer) return { lines };
|
||||
|
||||
const innerDiv = lyricsContainer.querySelector(":scope > div") as HTMLElement;
|
||||
if (!innerDiv) return { words, lines };
|
||||
if (!innerDiv) return { lines };
|
||||
|
||||
// remove existing container
|
||||
innerDiv.querySelector(".rl-wbw-container")?.remove();
|
||||
@@ -1608,14 +1547,22 @@ const buildWordSpans = (): {
|
||||
|
||||
for (const group of wordGroups) {
|
||||
if (isSylMode) {
|
||||
// Syllable mode: separate span per syllable, no space within same word
|
||||
// Syllable mode: separate span per syllable, seek/hover grouped by word
|
||||
const wordStartMs = syllabus[group[0]].time;
|
||||
const groupSpans: HTMLSpanElement[] = [];
|
||||
for (const si of group) {
|
||||
const syl = syllabus[si];
|
||||
const span = makeSpan(syl.text.trimEnd(), syl.time, syl.isBackground);
|
||||
const span = makeSpan(syl.text.trimEnd(), wordStartMs, syl.isBackground);
|
||||
span.addEventListener("mouseenter", () => {
|
||||
for (const s of groupSpans) s.classList.add("rl-wbw-word-hover");
|
||||
});
|
||||
span.addEventListener("mouseleave", () => {
|
||||
for (const s of groupSpans) s.classList.remove("rl-wbw-word-hover");
|
||||
});
|
||||
groupSpans.push(span);
|
||||
lineDiv.appendChild(span);
|
||||
const entry: WordEntry = { el: span, start: syl.time, end: syl.time + syl.duration, duration: syl.duration };
|
||||
lineWords.push(entry);
|
||||
words.push(entry);
|
||||
}
|
||||
} else {
|
||||
// Word mode: merge syllables into one span
|
||||
@@ -1629,7 +1576,6 @@ const buildWordSpans = (): {
|
||||
lineDiv.appendChild(span);
|
||||
const entry: WordEntry = { el: span, start, end, duration: end - start };
|
||||
lineWords.push(entry);
|
||||
words.push(entry);
|
||||
}
|
||||
// Space between words (not between syllables of the same word)
|
||||
lineDiv.appendChild(document.createTextNode(" "));
|
||||
@@ -1671,17 +1617,17 @@ const buildWordSpans = (): {
|
||||
for (let i = 0; i < lines.length && i < tidalSpans.length; i++) {
|
||||
lines[i].tidalSpan = tidalSpans[i];
|
||||
}
|
||||
trace.log(
|
||||
`Matched ${Math.min(lines.length, tidalSpans.length)} word-by-word lines to Tidal spans (${lines.length} lines, ${tidalSpans.length} spans)`,
|
||||
sylTrace(
|
||||
`Matched ${Math.min(lines.length, tidalSpans.length)} word/syllable lines to Tidal spans (${lines.length} lines, ${tidalSpans.length} spans)`,
|
||||
);
|
||||
|
||||
// append lyrics container (yea ik i was gonan edit tidals but uhh shhhh)
|
||||
innerDiv.appendChild(wbwContainer);
|
||||
|
||||
trace.log(
|
||||
`Word-by-word DOM: ${words.length} word spans across ${lines.length} lines`,
|
||||
sylTrace(
|
||||
`Word-by-word DOM: ${lines.reduce((n, l) => n + l.words.length, 0)} word spans across ${lines.length} lines`,
|
||||
);
|
||||
return { words, lines };
|
||||
return { lines };
|
||||
};
|
||||
|
||||
// watch for re-renders
|
||||
@@ -1705,12 +1651,11 @@ const watchForRerender = (): void => {
|
||||
// check if our container has been nuked by a react re-render (thx react again again..)
|
||||
const existing = lyricsContainer.querySelector(".rl-wbw-container");
|
||||
if (!existing) {
|
||||
trace.log(
|
||||
sylTrace(
|
||||
"Word-by-word: re-applying after Tidal re-render",
|
||||
);
|
||||
hideTidalLyrics();
|
||||
const result = buildWordSpans();
|
||||
allWords = result.words;
|
||||
lines = result.lines;
|
||||
}
|
||||
}, 100);
|
||||
@@ -1751,7 +1696,6 @@ const teardown = (): void => {
|
||||
scrollSynced = true;
|
||||
isActive = false;
|
||||
lyricsData = null;
|
||||
allWords = [];
|
||||
lines = [];
|
||||
activeWordEl = null;
|
||||
activeLineIdx = -1;
|
||||
@@ -1858,7 +1802,7 @@ const resync = (): void => {
|
||||
const tidalSyncBtn = document.querySelector('div[class*="_syncButton"] button') as HTMLElement;
|
||||
if (tidalSyncBtn) tidalSyncBtn.click();
|
||||
unhookSyncButton();
|
||||
console.log("[RL-Syllable] Scroll resynced");
|
||||
sylLog("[RL-Syllable] Scroll resynced");
|
||||
};
|
||||
|
||||
// Hook user scroll
|
||||
@@ -1867,7 +1811,7 @@ const hookUserScroll = (parent: HTMLElement): void => {
|
||||
const onUserScroll = () => {
|
||||
if (!scrollSynced) return;
|
||||
scrollSynced = false;
|
||||
console.log("[RL-Syllable] User scrolled — auto-scroll unhooked");
|
||||
sylLog("[RL-Syllable] User scrolled — auto-scroll unhooked");
|
||||
};
|
||||
parent.addEventListener("wheel", onUserScroll, { passive: true });
|
||||
parent.addEventListener("touchmove", onUserScroll, { passive: true });
|
||||
@@ -1907,10 +1851,10 @@ const unhookSyncButton = (): void => {
|
||||
const startTickLoop = (): void => {
|
||||
clearTickLoop();
|
||||
|
||||
console.log("[RL-Syllable] Tick loop started");
|
||||
sylLog("[RL-Syllable] Tick loop started");
|
||||
|
||||
let lastLogTime = 0;
|
||||
let lastTickMs = -1;
|
||||
let lastTickMs = 0;
|
||||
|
||||
tickLoopUnload = safeInterval(unloads, () => {
|
||||
if (!isActive || lines.length === 0) return;
|
||||
@@ -1935,7 +1879,7 @@ const startTickLoop = (): void => {
|
||||
|
||||
if (nowMs - lastLogTime >= 1000) {
|
||||
lastLogTime = nowMs;
|
||||
console.log(`[RL-Syllable] Playback | ${nowMs.toFixed(0)} ms`);
|
||||
sylLog(`[RL-Syllable] Playback | ${nowMs.toFixed(0)} ms`);
|
||||
}
|
||||
|
||||
// find active line (-1 if before all lyrics or in instrumental)
|
||||
@@ -1969,7 +1913,7 @@ const startTickLoop = (): void => {
|
||||
lines[activeLineIdx].el.removeAttribute("data-current");
|
||||
}
|
||||
activeLineIdx = -1;
|
||||
console.log(`[RL-Syllable] Scrub detected (${timeDelta > 0 ? "+" : ""}${timeDelta.toFixed(0)} ms) → resync`);
|
||||
sylLog(`[RL-Syllable] Scrub detected (${timeDelta > 0 ? "+" : ""}${timeDelta.toFixed(0)} ms) → resync`);
|
||||
}
|
||||
|
||||
// Deactivate line when entering instrumental
|
||||
@@ -2004,7 +1948,7 @@ const startTickLoop = (): void => {
|
||||
scrollTo(scrollParent, { top: Math.max(0, scrollTarget), behavior: "smooth" });
|
||||
}
|
||||
|
||||
console.log(
|
||||
sylLog(
|
||||
`[RL-Syllable] Line ${activeLineIdx} Active "${newLine.el.textContent?.slice(0, 40)}" | ${newLine.startMs} ms - ${newLine.endMs} ms [${nowMs.toFixed(0)} ms]`,
|
||||
);
|
||||
}
|
||||
@@ -2053,8 +1997,8 @@ const startTickLoop = (): void => {
|
||||
word.el.style.animation = `rl-wipe ${word.duration}ms linear forwards`;
|
||||
}
|
||||
activeWordEl = word.el;
|
||||
console.log(
|
||||
`[RL-Syllable] Word "${word.el.textContent}" | ${word.start} ms - ${word.end} ms [${nowMs.toFixed(0)} ms]`,
|
||||
sylLog(
|
||||
`[RL-Syllable] Word/Syllable "${word.el.textContent}" | ${word.start} ms - ${word.end} ms [${nowMs.toFixed(0)} ms]`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -2085,7 +2029,7 @@ const onTrackChange = async (): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
trace.log(
|
||||
sylTrace(
|
||||
`Word lyrics: looking up "${trackInfo.title}" by "${trackInfo.artist}"`,
|
||||
);
|
||||
|
||||
@@ -2095,14 +2039,14 @@ const onTrackChange = async (): Promise<void> => {
|
||||
);
|
||||
if (token !== trackChangeToken) return;
|
||||
if (!response) {
|
||||
trace.log("Word lyrics: no word-level lyrics for this track");
|
||||
trace.log("Word lyrics: no word/syllable lyrics for this track");
|
||||
return;
|
||||
}
|
||||
|
||||
trace.log(
|
||||
sylTrace(
|
||||
`Word lyrics: loaded ${response.data.length} lines (source: ${response.metadata.source})`,
|
||||
);
|
||||
console.log(
|
||||
sylLog(
|
||||
`[RL-Syllable] Loaded "${trackInfo.title}" by "${trackInfo.artist}" — ${response.data.length} lines`,
|
||||
);
|
||||
|
||||
@@ -2115,7 +2059,6 @@ const onTrackChange = async (): Promise<void> => {
|
||||
|
||||
// Build word spans and line entries
|
||||
const result = buildWordSpans();
|
||||
allWords = result.words;
|
||||
lines = result.lines;
|
||||
|
||||
// Watch React re-renders
|
||||
@@ -2140,11 +2083,10 @@ const reapplyWordLyrics = (): void => {
|
||||
isActive = true;
|
||||
hideTidalLyrics();
|
||||
const result = buildWordSpans();
|
||||
allWords = result.words;
|
||||
lines = result.lines;
|
||||
watchForRerender();
|
||||
startTickLoop();
|
||||
console.log("[RL-Syllable] Reapplied word lyrics (cached)");
|
||||
sylLog("[RL-Syllable] Reapplied word/syllable lyrics (cached)");
|
||||
};
|
||||
|
||||
// Called by Settings or dropdown
|
||||
@@ -2154,7 +2096,16 @@ const toggle = (): void => {
|
||||
onTrackChange();
|
||||
}
|
||||
};
|
||||
(window as any).updateLyricsStyle = toggle;
|
||||
const updateLyricsStyleFromSettings = (): void => {
|
||||
const segButtons = document.querySelectorAll(".rl-seg-btn");
|
||||
for (const btn of segButtons) {
|
||||
const raw = (btn as HTMLElement).dataset.style;
|
||||
if (raw === undefined) continue;
|
||||
btn.classList.toggle("rl-seg-active", Number(raw) === settings.lyricsStyle);
|
||||
}
|
||||
toggle();
|
||||
};
|
||||
(window as any).updateLyricsStyle = updateLyricsStyleFromSettings;
|
||||
|
||||
// Update lyrics on track change
|
||||
onGlobalTrackChange(() => {
|
||||
@@ -2240,5 +2191,4 @@ setupHeaderObserver();
|
||||
setupNowPlayingObserver();
|
||||
setupTrackTitleObserver();
|
||||
setupStickyLyricsObserver();
|
||||
setupQualityProgressObserver();
|
||||
setupTrackChangeListener();
|
||||
@@ -139,8 +139,9 @@
|
||||
color 0.15s ease-out;
|
||||
}
|
||||
|
||||
/* Hover word */
|
||||
.rl-wbw-word:hover {
|
||||
/* Hover word (Grouped Syllables) */
|
||||
.rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word:hover,
|
||||
.rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word.rl-wbw-word-hover {
|
||||
text-shadow:
|
||||
0 0 var(--rl-glow-inner, 2px) lightgray,
|
||||
/* biome-ignore lint: Hover glow should override defaults */
|
||||
|
||||
Reference in New Issue
Block a user