Merge pull request #67 from meowarex/dev

Massive Cleanup + Logging Easter Egg
This commit is contained in:
Meow Meow
2026-02-20 23:54:38 +11:00
committed by GitHub
4 changed files with 551 additions and 521 deletions
+445 -344
View File
@@ -7,12 +7,13 @@ declare global {
updateRadiantLyricsStyles?: () => void; updateRadiantLyricsStyles?: () => void;
updateRadiantLyricsTextGlow?: () => void; updateRadiantLyricsTextGlow?: () => void;
updateStickyLyricsFeature?: () => void; updateStickyLyricsFeature?: () => void;
updateStickyLyricsSetting?: (checked: boolean) => void;
updateRadiantLyricsPlayerBarTint?: () => void; updateRadiantLyricsPlayerBarTint?: () => void;
updateRadiantLyricsGlobalBackground?: () => void; updateRadiantLyricsGlobalBackground?: () => void;
updateRadiantLyricsNowPlayingBackground?: () => void; updateRadiantLyricsNowPlayingBackground?: () => void;
updateStickyLyricsIcon?: () => void;
updateQualityProgressColor?: () => void; updateQualityProgressColor?: () => void;
updateLyricsStyle?: () => void; updateLyricsStyle?: () => void;
updateLyricsStyleSetting?: (value: number) => void;
} }
} }
@@ -39,10 +40,10 @@ export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
backgroundBrightness: 40, backgroundBrightness: 40,
spinSpeed: 45, spinSpeed: 45,
settingsAffectNowPlaying: true, settingsAffectNowPlaying: true,
stickyLyricsFeature: true, stickyLyrics: false,
stickyLyrics: true,
stickyLyricsIcon: "sparkle" as string, stickyLyricsIcon: "sparkle" as string,
lyricsStyle: 0, lyricsStyle: 0,
syllableLogging: false,
}); });
export const Settings = () => { export const Settings = () => {
@@ -62,9 +63,7 @@ export const Settings = () => {
const [performanceMode, setPerformanceMode] = React.useState( const [performanceMode, setPerformanceMode] = React.useState(
settings.performanceMode, settings.performanceMode,
); );
const [spinningArt, setspinningArt] = React.useState( const [spinningArt, setspinningArt] = React.useState(settings.spinningArt);
settings.spinningArt,
);
const [backgroundContrast, setBackgroundContrast] = React.useState( const [backgroundContrast, setBackgroundContrast] = React.useState(
settings.backgroundContrast, settings.backgroundContrast,
); );
@@ -103,13 +102,33 @@ export const Settings = () => {
); );
const [showTintColorPicker, setShowTintColorPicker] = React.useState(false); const [showTintColorPicker, setShowTintColorPicker] = React.useState(false);
const [isTintAnimatingIn, setIsTintAnimatingIn] = React.useState(false); const [isTintAnimatingIn, setIsTintAnimatingIn] = React.useState(false);
const [shouldRenderTintPicker, setShouldRenderTintPicker] = React.useState(false); const [shouldRenderTintPicker, setShouldRenderTintPicker] =
const [tintCustomInput, setTintCustomInput] = React.useState(settings.playerBarTintColor); React.useState(false);
const [tintCustomColors, setTintCustomColors] = React.useState(settings.playerBarTintCustomColors); const [tintCustomInput, setTintCustomInput] = React.useState(
const [tintHoveredColorIndex, setTintHoveredColorIndex] = React.useState<number | null>(null); settings.playerBarTintColor,
const [stickyLyricsFeature, setStickyLyricsFeature] = React.useState(
settings.stickyLyricsFeature,
); );
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( const [qualityProgressColor, setQualityProgressColor] = React.useState(
settings.qualityProgressColor, settings.qualityProgressColor,
); );
@@ -120,9 +139,8 @@ export const Settings = () => {
onChange: (_: unknown, checked: boolean) => void; onChange: (_: unknown, checked: boolean) => void;
checked: boolean; checked: boolean;
}; };
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType< const AnySwitch =
AnySwitchProps LunaSwitchSetting as unknown as React.ComponentType<AnySwitchProps>;
>;
return ( return (
<LunaSettings> <LunaSettings>
@@ -132,7 +150,7 @@ export const Settings = () => {
checked={lyricsGlowEnabled} checked={lyricsGlowEnabled}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
settings.lyricsGlowEnabled = checked; settings.lyricsGlowEnabled = checked;
setLyricsGlowEnabled(checked); setLyricsGlowEnabled(checked);
// Update styles immediately when setting changes // Update styles immediately when setting changes
if (window.updateRadiantLyricsStyles) { if (window.updateRadiantLyricsStyles) {
window.updateRadiantLyricsStyles(); window.updateRadiantLyricsStyles();
@@ -145,88 +163,92 @@ export const Settings = () => {
checked={trackTitleGlow} checked={trackTitleGlow}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
settings.trackTitleGlow = checked; settings.trackTitleGlow = checked;
setTrackTitleGlow(checked); setTrackTitleGlow(checked);
if (window.updateRadiantLyricsStyles) { if (window.updateRadiantLyricsStyles) {
window.updateRadiantLyricsStyles(); window.updateRadiantLyricsStyles();
} }
}} }}
/> />
{(lyricsGlowEnabled || trackTitleGlow) && ( {(lyricsGlowEnabled || trackTitleGlow) && (
<LunaNumberSetting <LunaNumberSetting
title="Text Glow" title="Text Glow"
desc="Adjust the glow size of lyrics (0-100, default: 20)" desc="Adjust the glow size of lyrics (0-100, default: 20)"
min={0} min={0}
max={100} max={100}
step={1} step={1}
value={textGlow} value={textGlow}
onNumber={(value: number) => { onNumber={(value: number) => {
settings.textGlow = value; settings.textGlow = value;
setTextGlow(value); setTextGlow(value);
// Update variables immediately when setting changes // Update variables immediately when setting changes
if (window.updateRadiantLyricsTextGlow) { if (window.updateRadiantLyricsTextGlow) {
window.updateRadiantLyricsTextGlow(); window.updateRadiantLyricsTextGlow();
} }
}} }}
/> />
)} )}
<LunaNumberSetting
title="Lyrics Style"
desc="0 = Line (default), 1 = Word, 2 = Syllable (mirrored in lyrics dropdown)"
min={0}
max={1}
step={1}
value={lyricsStyle}
onNumber={(value: number) => {
settings.lyricsStyle = value;
setLyricsStyle(value);
if (window.updateLyricsStyle) {
window.updateLyricsStyle();
}
}}
/>
<AnySwitch <AnySwitch
title="Sticky Lyrics" title="Sticky Lyrics"
desc="auto-switches to Play Queue when lyrics aren't available (mirrored in lyrics dropdown)" desc="auto-switches to Play Queue when lyrics aren't available (mirrored in lyrics dropdown)"
checked={stickyLyricsFeature} checked={stickyLyrics}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
settings.stickyLyricsFeature = checked; settings.stickyLyrics = checked;
setStickyLyricsFeature(checked); setStickyLyrics(checked);
if (window.updateStickyLyricsFeature) { if (window.updateStickyLyricsFeature) {
window.updateStickyLyricsFeature(); window.updateStickyLyricsFeature();
} }
}} }}
/> />
<LunaNumberSetting <AnySwitch
title="Lyrics Style"
desc="0 = Line (default), 1 = Word, 2 = Syllable (coming soon) (mirrored in lyrics dropdown)"
min={0}
max={1}
step={1}
value={settings.lyricsStyle}
onNumber={(value: number) => {
settings.lyricsStyle = value;
if (window.updateLyricsStyle) {
window.updateLyricsStyle();
}
}}
/>
<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"
checked={hideUIEnabled} checked={hideUIEnabled}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
settings.hideUIEnabled = checked; settings.hideUIEnabled = checked;
setHideUIEnabled(checked); setHideUIEnabled(checked);
}} }}
/> />
{hideUIEnabled && ( {hideUIEnabled && (
<AnySwitch <AnySwitch
title="Player Bar Visibility in Hide UI Mode" title="Player Bar Visibility in Hide UI Mode"
desc="Keep player bar visible when UI is hidden" desc="Keep player bar visible when UI is hidden"
checked={playerBarVisible} checked={playerBarVisible}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
console.log("Player Bar Visibility:", checked ? "visible" : "hidden"); console.log(
settings.playerBarVisible = checked; "Player Bar Visibility:",
setPlayerBarVisible(checked); checked ? "visible" : "hidden",
// Update styles immediately when setting changes );
if (window.updateRadiantLyricsStyles) { settings.playerBarVisible = checked;
window.updateRadiantLyricsStyles(); setPlayerBarVisible(checked);
} // Update styles immediately when setting changes
}} if (window.updateRadiantLyricsStyles) {
/> window.updateRadiantLyricsStyles();
)} }
}}
/>
)}
<AnySwitch <AnySwitch
title="Quality Matched Seeker Color" title="Quality Matched Seeker Color"
desc="Color the progress/seeker bar based on streaming quality" desc="Color the progress/seeker bar based on streaming quality"
checked={qualityProgressColor} checked={qualityProgressColor}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
settings.qualityProgressColor = checked; settings.qualityProgressColor = checked;
setQualityProgressColor(checked); setQualityProgressColor(checked);
if (window.updateQualityProgressColor) { if (window.updateQualityProgressColor) {
window.updateQualityProgressColor(); window.updateQualityProgressColor();
} }
@@ -238,7 +260,7 @@ export const Settings = () => {
checked={floatingPlayerBar} checked={floatingPlayerBar}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
settings.floatingPlayerBar = checked; settings.floatingPlayerBar = checked;
setFloatingPlayerBar(checked); setFloatingPlayerBar(checked);
if (window.updateRadiantLyricsStyles) { if (window.updateRadiantLyricsStyles) {
window.updateRadiantLyricsStyles(); window.updateRadiantLyricsStyles();
} }
@@ -255,7 +277,7 @@ export const Settings = () => {
value={playerBarRadius} value={playerBarRadius}
onNumber={(value: number) => { onNumber={(value: number) => {
settings.playerBarRadius = value; settings.playerBarRadius = value;
setPlayerBarRadius(value); setPlayerBarRadius(value);
window.updateRadiantLyricsPlayerBarTint?.(); window.updateRadiantLyricsPlayerBarTint?.();
}} }}
/> />
@@ -268,7 +290,7 @@ export const Settings = () => {
value={playerBarSpacing} value={playerBarSpacing}
onNumber={(value: number) => { onNumber={(value: number) => {
settings.playerBarSpacing = value; settings.playerBarSpacing = value;
setPlayerBarSpacing(value); setPlayerBarSpacing(value);
window.updateRadiantLyricsPlayerBarTint?.(); window.updateRadiantLyricsPlayerBarTint?.();
}} }}
/> />
@@ -324,10 +346,25 @@ export const Settings = () => {
}; };
const tintColorPresets = [ const tintColorPresets = [
"#000000", "#111111", "#222222", "#333333", "#444444", "#000000",
"#555555", "#666666", "#888888", "#aaaaaa", "#cccccc", "#111111",
"#ffffff", "#0d1117", "#1a1a2e", "#16213e", "#0f3460", "#222222",
"#1b1b2f", "#162447", "#1f4068", "#e94560", "#333333",
"#444444",
"#555555",
"#666666",
"#888888",
"#aaaaaa",
"#cccccc",
"#ffffff",
"#0d1117",
"#1a1a2e",
"#16213e",
"#0f3460",
"#1b1b2f",
"#162447",
"#1f4068",
"#e94560",
]; ];
const allTintColors = [...tintColorPresets, ...tintCustomColors]; const allTintColors = [...tintColorPresets, ...tintCustomColors];
@@ -343,17 +380,21 @@ export const Settings = () => {
value={playerBarTint} value={playerBarTint}
onNumber={(value: number) => { onNumber={(value: number) => {
settings.playerBarTint = value; settings.playerBarTint = value;
setPlayerBarTint(value); setPlayerBarTint(value);
window.updateRadiantLyricsPlayerBarTint?.(); window.updateRadiantLyricsPlayerBarTint?.();
}} }}
/> />
{/* Color swatch — positioned just left of the value box */} {/* Color swatch — positioned just left of the value box */}
<button <button
type="button" type="button"
onClick={() => showTintColorPicker ? closeTintColorPicker() : openTintColorPicker()} onClick={() =>
style={{ showTintColorPicker
width: "28px", ? closeTintColorPicker()
height: "28px", : openTintColorPicker()
}
style={{
width: "28px",
height: "28px",
border: "1px solid rgba(255,255,255,0.15)", border: "1px solid rgba(255,255,255,0.15)",
borderRadius: "6px", borderRadius: "6px",
cursor: "pointer", cursor: "pointer",
@@ -366,29 +407,39 @@ export const Settings = () => {
zIndex: 1, 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> </button>
{/* Color Picker Modal */} {/* Color Picker Modal */}
{shouldRenderTintPicker && ( {shouldRenderTintPicker && (
<> <>
<button <button
type="button" type="button"
aria-label="Close color picker" aria-label="Close color picker"
onClick={closeTintColorPicker} onClick={closeTintColorPicker}
style={{ style={{
position: "fixed", position: "fixed",
top: 0, left: 0, right: 0, bottom: 0, top: 0,
background: "rgba(0,0,0,0.6)", left: 0,
zIndex: 1000, right: 0,
opacity: isTintAnimatingIn ? 1 : 0, bottom: 0,
transition: "opacity 0.2s ease", background: "rgba(0,0,0,0.6)",
border: "none", zIndex: 1000,
padding: 0, opacity: isTintAnimatingIn ? 1 : 0,
cursor: "default", transition: "opacity 0.2s ease",
width: "100%", border: "none",
}} padding: 0,
/> cursor: "default",
width: "100%",
}}
/>
<div <div
style={{ style={{
position: "fixed", position: "fixed",
@@ -412,45 +463,73 @@ export const Settings = () => {
transition: "all 0.2s ease", 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 Choose Tint Color
</div> </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) => { {allTintColors.map((color, index) => {
const isCustomColor = tintCustomColors.includes(color); const isCustomColor = tintCustomColors.includes(color);
const isHovered = tintHoveredColorIndex === index; const isHovered = tintHoveredColorIndex === index;
return ( return (
// biome-ignore lint/a11y/noStaticElementInteractions: cosmetic hover tracking on wrapper containing interactive buttons // biome-ignore lint/a11y/noStaticElementInteractions: cosmetic hover tracking on wrapper containing interactive buttons
<div <div
key={color} key={color}
style={{ position: "relative", width: "32px", height: "32px", cursor: "pointer" }}
onMouseEnter={() => setTintHoveredColorIndex(index)}
onMouseLeave={() => setTintHoveredColorIndex(null)}
>
<button
type="button"
onClick={() => { updateTintColor(color); closeTintColorPicker(); }}
style={{ style={{
position: "relative",
width: "32px",
height: "32px",
cursor: "pointer",
}}
onMouseEnter={() => setTintHoveredColorIndex(index)}
onMouseLeave={() => setTintHoveredColorIndex(null)}
>
<button
type="button"
onClick={() => {
updateTintColor(color);
closeTintColorPicker();
}}
style={{
width: "100%", width: "100%",
height: "100%", height: "100%",
borderRadius: "6px", borderRadius: "6px",
border: playerBarTintColor === color border:
? "2px solid #fff" playerBarTintColor === color
: "1px solid rgba(255,255,255,0.2)", ? "2px solid #fff"
: "1px solid rgba(255,255,255,0.2)",
background: color, background: color,
cursor: "pointer", cursor: "pointer",
transition: "all 0.2s ease", transition: "all 0.2s ease",
}} }}
/> />
{isCustomColor && ( {isCustomColor && (
<button <button
type="button" type="button"
onClick={(e) => { e.stopPropagation(); removeTintCustomColor(color); }} onClick={(e) => {
style={{ e.stopPropagation();
removeTintCustomColor(color);
}}
style={{
position: "absolute", position: "absolute",
top: "-4px", right: "-4px", top: "-4px",
width: "16px", height: "16px", right: "-4px",
width: "16px",
height: "16px",
borderRadius: "50%", borderRadius: "50%",
border: "1px solid rgba(255,255,255,0.8)", border: "1px solid rgba(255,255,255,0.8)",
background: "rgba(0,0,0,0.8)", background: "rgba(0,0,0,0.8)",
@@ -474,10 +553,22 @@ export const Settings = () => {
</div> </div>
<div style={{ marginBottom: "12px" }}> <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 Add Custom Color
</div> </div>
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}> <div
style={{
display: "flex",
gap: "8px",
alignItems: "center",
}}
>
<input <input
type="text" type="text"
value={tintCustomInput} value={tintCustomInput}
@@ -501,11 +592,15 @@ export const Settings = () => {
boxSizing: "border-box", boxSizing: "border-box",
}} }}
/> />
<button <button
type="button" type="button"
onClick={() => { updateTintColor(tintCustomInput); addTintCustomColor(); }} onClick={() => {
style={{ updateTintColor(tintCustomInput);
width: "32px", height: "32px", addTintCustomColor();
}}
style={{
width: "32px",
height: "32px",
borderRadius: "6px", borderRadius: "6px",
border: "1px solid rgba(255,255,255,0.3)", border: "1px solid rgba(255,255,255,0.3)",
background: "rgba(255,255,255,0.15)", background: "rgba(255,255,255,0.15)",
@@ -517,20 +612,26 @@ export const Settings = () => {
justifyContent: "center", justifyContent: "center",
transition: "all 0.2s ease", transition: "all 0.2s ease",
}} }}
onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.25)"; }} onMouseEnter={(e) => {
onMouseLeave={(e) => { e.currentTarget.style.background = "rgba(255,255,255,0.15)"; }} e.currentTarget.style.background =
"rgba(255,255,255,0.25)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background =
"rgba(255,255,255,0.15)";
}}
> >
+ +
</button> </button>
</div> </div>
</div> </div>
<button <button
type="button" type="button"
onClick={closeTintColorPicker} onClick={closeTintColorPicker}
style={{ style={{
width: "100%", width: "100%",
padding: "8px", padding: "8px",
borderRadius: "6px", borderRadius: "6px",
border: "1px solid rgba(255,255,255,0.2)", border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(255,255,255,0.1)", background: "rgba(255,255,255,0.1)",
@@ -557,7 +658,7 @@ export const Settings = () => {
checked ? "enabled" : "disabled", checked ? "enabled" : "disabled",
); );
settings.CoverEverywhere = checked; settings.CoverEverywhere = checked;
setCoverEverywhere(checked); setCoverEverywhere(checked);
// Update styles immediately when setting changes // Update styles immediately when setting changes
if (window.updateRadiantLyricsGlobalBackground) { if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground(); window.updateRadiantLyricsGlobalBackground();
@@ -565,208 +666,208 @@ export const Settings = () => {
}} }}
/> />
{CoverEverywhere && ( {CoverEverywhere && (
<AnySwitch <AnySwitch
title="Performance Mode | Experimental" title="Performance Mode | Experimental"
desc="Performance mode: Reduces blur effects & uses smaller image sizes, to optimize GPU usage" desc="Performance mode: Reduces blur effects & uses smaller image sizes, to optimize GPU usage"
checked={performanceMode} checked={performanceMode}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
console.log("Performance Mode:", checked ? "enabled" : "disabled"); console.log("Performance Mode:", checked ? "enabled" : "disabled");
settings.performanceMode = checked; settings.performanceMode = checked;
setPerformanceMode(checked); setPerformanceMode(checked);
// Update background animations immediately when setting changes // Update background animations immediately when setting changes
if (window.updateRadiantLyricsGlobalBackground) { if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground(); window.updateRadiantLyricsGlobalBackground();
} }
if (window.updateRadiantLyricsNowPlayingBackground) { if (window.updateRadiantLyricsNowPlayingBackground) {
window.updateRadiantLyricsNowPlayingBackground(); window.updateRadiantLyricsNowPlayingBackground();
} }
}} }}
/> />
)} )}
{CoverEverywhere && (
<AnySwitch
title="Background Cover Spin" // Cheers @Max/n0201 for the idea <3
desc="Enable the spinning cover art background animation"
checked={spinningArt}
onChange={(_: unknown, checked: boolean) => {
console.log(
"Background Cover Spin:",
checked ? "enabled" : "disabled",
);
settings.spinningArt = checked;
setspinningArt(checked);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Scale"
desc="Adjust the scale of the background cover (1=10% - 50=500%, default: 15)"
min={1}
max={50}
step={1}
value={backgroundScale}
onNumber={(value: number) => {
settings.backgroundScale = value;
setBackgroundScale(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Radius"
desc="Adjust the cover art corner radius (0-100%, default: 25)"
min={0}
max={100}
step={1}
value={backgroundRadius}
onNumber={(value: number) => {
settings.backgroundRadius = value;
setBackgroundRadius(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Contrast"
desc="Adjust the contrast of the spinning background (0-200, default: 120)"
min={0}
max={200}
step={1}
value={backgroundContrast}
onNumber={(value: number) => {
settings.backgroundContrast = value;
setBackgroundContrast(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Blur"
desc="Adjust the blur amount of the spinning background (0-200, default: 80)"
min={0}
max={200}
step={1}
value={backgroundBlur}
onNumber={(value: number) => {
console.log("Background Blur:", value);
settings.backgroundBlur = value;
setBackgroundBlur(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Brightness"
desc="Adjust the brightness of the spinning background (0-100, default: 40)"
min={0}
max={100}
step={1}
value={backgroundBrightness}
onNumber={(value: number) => {
console.log("Background Brightness:", value);
settings.backgroundBrightness = value;
setBackgroundBrightness(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && spinningArt && (
<LunaNumberSetting
title="Spin Speed"
desc="Adjust the rotation speed in seconds (10-120, default: 45) - Lower values = Faster rotation"
min={10}
max={120}
step={1}
value={spinSpeed}
onNumber={(value: number) => {
console.log("Spin Speed:", value);
settings.spinSpeed = value;
setSpinSpeed(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && ( {CoverEverywhere && (
<AnySwitch <AnySwitch
title="Settings Affect Now Playing" title="Background Cover Spin" // Cheers @Max/n0201 for the idea <3
desc="Apply background settings to Now Playing view" desc="Enable the spinning cover art background animation"
checked={settingsAffectNowPlaying} checked={spinningArt}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
console.log( console.log(
"Settings Affect Now Playing:", "Background Cover Spin:",
checked ? "enabled" : "disabled", checked ? "enabled" : "disabled",
); );
settings.settingsAffectNowPlaying = checked; settings.spinningArt = checked;
setSettingsAffectNowPlaying(checked); setspinningArt(checked);
// Update Now Playing background immediately when setting changes if (window.updateRadiantLyricsGlobalBackground) {
if (window.updateRadiantLyricsNowPlayingBackground) { window.updateRadiantLyricsGlobalBackground();
window.updateRadiantLyricsNowPlayingBackground(); }
} if (
}} settings.settingsAffectNowPlaying &&
/> window.updateRadiantLyricsNowPlayingBackground
)} ) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Scale"
desc="Adjust the scale of the background cover (1=10% - 50=500%, default: 15)"
min={1}
max={50}
step={1}
value={backgroundScale}
onNumber={(value: number) => {
settings.backgroundScale = value;
setBackgroundScale(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Radius"
desc="Adjust the cover art corner radius (0-100%, default: 25)"
min={0}
max={100}
step={1}
value={backgroundRadius}
onNumber={(value: number) => {
settings.backgroundRadius = value;
setBackgroundRadius(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Contrast"
desc="Adjust the contrast of the spinning background (0-200, default: 120)"
min={0}
max={200}
step={1}
value={backgroundContrast}
onNumber={(value: number) => {
settings.backgroundContrast = value;
setBackgroundContrast(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Blur"
desc="Adjust the blur amount of the spinning background (0-200, default: 80)"
min={0}
max={200}
step={1}
value={backgroundBlur}
onNumber={(value: number) => {
console.log("Background Blur:", value);
settings.backgroundBlur = value;
setBackgroundBlur(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<LunaNumberSetting
title="Background Brightness"
desc="Adjust the brightness of the spinning background (0-100, default: 40)"
min={0}
max={100}
step={1}
value={backgroundBrightness}
onNumber={(value: number) => {
console.log("Background Brightness:", value);
settings.backgroundBrightness = value;
setBackgroundBrightness(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && spinningArt && (
<LunaNumberSetting
title="Spin Speed"
desc="Adjust the rotation speed in seconds (10-120, default: 45) - Lower values = Faster rotation"
min={10}
max={120}
step={1}
value={spinSpeed}
onNumber={(value: number) => {
console.log("Spin Speed:", value);
settings.spinSpeed = value;
setSpinSpeed(value);
if (window.updateRadiantLyricsGlobalBackground) {
window.updateRadiantLyricsGlobalBackground();
}
if (
settings.settingsAffectNowPlaying &&
window.updateRadiantLyricsNowPlayingBackground
) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
{CoverEverywhere && (
<AnySwitch
title="Settings Affect Now Playing"
desc="Apply background settings to Now Playing view"
checked={settingsAffectNowPlaying}
onChange={(_: unknown, checked: boolean) => {
console.log(
"Settings Affect Now Playing:",
checked ? "enabled" : "disabled",
);
settings.settingsAffectNowPlaying = checked;
setSettingsAffectNowPlaying(checked);
// Update Now Playing background immediately when setting changes
if (window.updateRadiantLyricsNowPlayingBackground) {
window.updateRadiantLyricsNowPlayingBackground();
}
}}
/>
)}
</LunaSettings> </LunaSettings>
); );
}; };
@@ -43,28 +43,6 @@
backface-visibility: hidden; 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 Optimization */
.now-playing-background-container { .now-playing-background-container {
position: absolute; position: absolute;
+103 -153
View File
@@ -1,5 +1,5 @@
// MARKER: Core Setup // 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 { StyleTag, PlayState, MediaItem, observePromise, observe, safeInterval, safeTimeout } from "@luna/lib";
import { settings, Settings } from "./Settings"; import { settings, Settings } from "./Settings";
// Interpret integer backgroundScale (e.g., 10=1.0x, 20=2.0x) // Interpret integer backgroundScale (e.g., 10=1.0x, 20=2.0x)
@@ -87,11 +87,6 @@ const applyFloatingPlayerBar = (): void => {
// Alias for settings callback // Alias for settings callback
const updateRadiantLyricsPlayerBarTint = applyFloatingPlayerBar; 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!) // 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(); applyPlayerBarTintToElement();
observe<HTMLElement>(unloads, '[data-test="footer-player"]', () => { observe<HTMLElement>(unloads, '[data-test="footer-player"]', () => {
@@ -132,16 +127,9 @@ const applyQualityProgressColor = (): void => {
progressIndicator.style.setProperty("background-color", color, "important"); progressIndicator.style.setProperty("background-color", color, "important");
}; };
// Called Settings // Apply on load
const updateQualityProgressColor = (): void => { if (settings.qualityProgressColor) {
applyQualityProgressColor(); 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) // 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 // MARKER: UI Visibility Control
// UI state shared across features // UI state shared across features
var isHidden = false; let isHidden = false;
let unhideButtonAutoFadeTimeout: number | null = null; let unhideButtonAutoFadeTimeout: number | null = null;
// Helper to safely create a one-off timeout that clears previous if any // 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 nowPlayingBackgroundImage: HTMLImageElement | null = null;
let nowPlayingBlackBg: HTMLElement | null = null; let nowPlayingBlackBg: HTMLElement | null = null;
let nowPlayingGradientOverlay: HTMLElement | null = null; let nowPlayingGradientOverlay: HTMLElement | null = null;
let currentNowPlayingCoverSrc: string | null = null;
let spinAnimationAdded = false; let spinAnimationAdded = false;
// apply scaled pixel sizes to cover art // apply scaled pixel sizes to cover art
@@ -468,7 +455,7 @@ function updateCoverArtBackground(method: number = 0): void {
return; return;
} }
let coverArtImageElement = document.querySelector( const coverArtImageElement = document.querySelector(
'figure[class*="_albumImage"] > div > div > div > img', 'figure[class*="_albumImage"] > div > div > div > img',
) as HTMLImageElement; ) as HTMLImageElement;
let coverArtImageSrc: string | null = null; let coverArtImageSrc: string | null = null;
@@ -590,51 +577,28 @@ function updateCoverArtBackground(method: number = 0): void {
nowPlayingBackgroundImage.src !== coverArtImageSrc nowPlayingBackgroundImage.src !== coverArtImageSrc
) { ) {
nowPlayingBackgroundImage.src = coverArtImageSrc; nowPlayingBackgroundImage.src = coverArtImageSrc;
currentNowPlayingCoverSrc = coverArtImageSrc;
} }
// Apply pixel-based size using intrinsic dimensions // Apply pixel-based size using intrinsic dimensions
applyScaledPixelSize(nowPlayingBackgroundImage); applyScaledPixelSize(nowPlayingBackgroundImage);
// Apply performance-optimized settings (filter/animation); size handled above
if (nowPlayingBackgroundImage) { if (nowPlayingBackgroundImage) {
if (settings.performanceMode) { const blur = settings.performanceMode ? Math.min(settings.backgroundBlur, 20) : settings.backgroundBlur;
// Performance mode with spinning enabled const contrast = settings.performanceMode ? Math.min(settings.backgroundContrast, 150) : settings.backgroundContrast;
const blur = Math.min(settings.backgroundBlur, 20); const radius = `${settings.backgroundRadius}%`;
const contrast = Math.min(settings.backgroundContrast, 150); if (nowPlayingBackgroundImage.style.borderRadius !== radius)
const radiusPm = `${settings.backgroundRadius}%`; nowPlayingBackgroundImage.style.borderRadius = radius;
if (nowPlayingBackgroundImage.style.borderRadius !== radiusPm) const filt = `blur(${blur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${contrast}%)`;
nowPlayingBackgroundImage.style.borderRadius = radiusPm; if (nowPlayingBackgroundImage.style.filter !== filt)
const filt = `blur(${blur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${contrast}%)`; nowPlayingBackgroundImage.style.filter = filt;
if (nowPlayingBackgroundImage.style.filter !== filt) const anim = settings.spinningArt
nowPlayingBackgroundImage.style.filter = filt; ? `spin ${settings.spinSpeed}s linear infinite`
const anim = settings.spinningArt : "none";
? `spin ${settings.spinSpeed}s linear infinite` const wc = settings.spinningArt ? "transform" : "auto";
: "none"; if (nowPlayingBackgroundImage.style.animation !== anim)
const wc = settings.spinningArt ? "transform" : "auto"; nowPlayingBackgroundImage.style.animation = anim;
if (nowPlayingBackgroundImage.style.animation !== anim) if (nowPlayingBackgroundImage.style.willChange !== wc)
nowPlayingBackgroundImage.style.animation = anim; nowPlayingBackgroundImage.style.willChange = wc;
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 // Add keyframe animation only once
@@ -764,40 +728,20 @@ const applyGlobalSpinningBackground = (coverArtImageSrc: string): void => {
globalBackgroundImage.src = coverArtImageSrc; globalBackgroundImage.src = coverArtImageSrc;
} }
// Apply performance-optimized settings
if (globalBackgroundImage) { if (globalBackgroundImage) {
// Pixel-based sizing based on intrinsic dimensions
applyScaledPixelSize(globalBackgroundImage); 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}%`; const radius = `${settings.backgroundRadius}%`;
// Performance mode optimizations globalBackgroundImage.style.filter = `blur(${blur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${contrast}%)`;
if (settings.performanceMode) { if (globalBackgroundImage.style.borderRadius !== radius)
// Performance mode with spinning enabled globalBackgroundImage.style.borderRadius = radius;
globalBackgroundImage.style.filter = `blur(${Math.min(settings.backgroundBlur, 20)}px) brightness(${settings.backgroundBrightness / 100}) contrast(${Math.min(settings.backgroundContrast, 150)}%)`; if (settings.spinningArt) {
if (globalBackgroundImage.style.borderRadius !== radius) globalBackgroundImage.style.animation = `spinGlobal ${settings.spinSpeed}s linear infinite`;
globalBackgroundImage.style.borderRadius = radius; globalBackgroundImage.style.willChange = "transform";
// 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");
} else { } else {
// Normal mode globalBackgroundImage.style.animation = "none";
globalBackgroundImage.style.filter = `blur(${settings.backgroundBlur}px) brightness(${settings.backgroundBrightness / 100}) contrast(${settings.backgroundContrast}%)`; globalBackgroundImage.style.willChange = "auto";
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,30 +813,17 @@ const updateRadiantLyricsNowPlayingBackground = function (): void {
const radius = `${settings.backgroundRadius}%`; const radius = `${settings.backgroundRadius}%`;
if (imgElement.style.borderRadius !== radius) imgElement.style.borderRadius = radius; if (imgElement.style.borderRadius !== radius) imgElement.style.borderRadius = radius;
// Performance mode optimizations
if (settings.performanceMode) { if (settings.performanceMode) {
// Reduce blur and effects for better performance, but keep spinning
blur = Math.min(blur, 20); blur = Math.min(blur, 20);
contrast = Math.min(contrast, 150); contrast = Math.min(contrast, 150);
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");
} 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");
} }
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.style.filter = `blur(${blur}px) brightness(${brightness / 100}) contrast(${contrast}%)`; imgElement.style.filter = `blur(${blur}px) brightness(${brightness / 100}) contrast(${contrast}%)`;
}); });
}; };
@@ -905,7 +836,7 @@ const updateRadiantLyricsNowPlayingBackground = function (): void {
updateRadiantLyricsNowPlayingBackground; updateRadiantLyricsNowPlayingBackground;
(window as any).updateRadiantLyricsTextGlow = updateRadiantLyricsTextGlow; (window as any).updateRadiantLyricsTextGlow = updateRadiantLyricsTextGlow;
(window as any).updateRadiantLyricsPlayerBarTint = updateRadiantLyricsPlayerBarTint; (window as any).updateRadiantLyricsPlayerBarTint = updateRadiantLyricsPlayerBarTint;
(window as any).updateQualityProgressColor = updateQualityProgressColor; (window as any).updateQualityProgressColor = applyQualityProgressColor;
const cleanUpDynamicArt = function (): void { const cleanUpDynamicArt = function (): void {
// Clean up cached Now Playing elements // Clean up cached Now Playing elements
@@ -921,7 +852,6 @@ const cleanUpDynamicArt = function (): void {
nowPlayingBackgroundImage = null; nowPlayingBackgroundImage = null;
nowPlayingBlackBg = null; nowPlayingBlackBg = null;
nowPlayingGradientOverlay = null; nowPlayingGradientOverlay = null;
currentNowPlayingCoverSrc = null;
// Clean up any remaining elements (fallback) // Clean up any remaining elements (fallback)
const nowPlayingBackgroundImages = document.getElementsByClassName( const nowPlayingBackgroundImages = document.getElementsByClassName(
@@ -1033,7 +963,7 @@ const applyStickyIcon = (): void => {
const trigger = document.querySelector(".sticky-lyrics-trigger") as HTMLElement; const trigger = document.querySelector(".sticky-lyrics-trigger") as HTMLElement;
if (!trigger) return; if (!trigger) return;
trigger.innerHTML = getStickyIcon(); trigger.innerHTML = getStickyIcon();
trigger.style.paddingLeft = settings.stickyLyricsIcon === "sparkle" ? "5px" : "5px"; trigger.style.paddingLeft = "5px";
}; };
// Console: StickyLyrics.icon = "sparkle" or "chevron" // 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 => { const updateStickyLyricsFeature = (): void => {
settings.stickyLyrics = settings.stickyLyricsFeature;
const checkbox = document.querySelector('input[data-setting="stickyLyrics"]') as HTMLInputElement; const checkbox = document.querySelector('input[data-setting="stickyLyrics"]') as HTMLInputElement;
if (checkbox) checkbox.checked = settings.stickyLyrics; if (checkbox) checkbox.checked = settings.stickyLyrics;
}; };
@@ -1076,7 +1017,6 @@ const createStickyLyricsDropdown = (): void => {
// Set the icon & it's styling // Set the icon & it's styling
// is only needed because i'm picky and prefer the Sparkle.. shhh // is only needed because i'm picky and prefer the Sparkle.. shhh
trigger.innerHTML = getStickyIcon(); trigger.innerHTML = getStickyIcon();
//trigger.style.paddingLeft = settings.stickyLyricsIcon === "sparkle" ? "5px" : "5px";
// Block non-click events on trigger from reaching the Lyrics tab (capture phase) // 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.. // (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; ) as HTMLInputElement;
stickyCheckbox.addEventListener("change", () => { stickyCheckbox.addEventListener("change", () => {
settings.stickyLyrics = stickyCheckbox.checked; settings.stickyLyrics = stickyCheckbox.checked;
(window as any).updateStickyLyricsSetting?.(stickyCheckbox.checked);
if (settings.stickyLyrics) { if (settings.stickyLyrics) {
handleStickyLyricsTrackChange(); handleStickyLyricsTrackChange();
} }
@@ -1165,7 +1106,8 @@ const createStickyLyricsDropdown = (): void => {
settings.lyricsStyle = style; settings.lyricsStyle = style;
for (const b of segButtons) b.classList.remove("rl-seg-active"); for (const b of segButtons) b.classList.remove("rl-seg-active");
btn.classList.add("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(); toggle();
}); });
} }
@@ -1321,7 +1263,6 @@ interface LineEntry {
} }
let lines: LineEntry[] = []; let lines: LineEntry[] = [];
let allWords: WordEntry[] = [];
let rerenderObserver: MutationObserver | null = null; let rerenderObserver: MutationObserver | null = null;
let rerenderDebounce: number | null = null; let rerenderDebounce: number | null = null;
let activeWordEl: HTMLSpanElement | null = null; let activeWordEl: HTMLSpanElement | null = null;
@@ -1397,7 +1338,7 @@ const fetchWordLyrics = async (
for (const url of urls) { for (const url of urls) {
try { try {
trace.log(`Fetching word lyrics: ${url}`); sylTrace(`Fetching word lyrics: ${url}`);
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) { if (!res.ok) {
trace.log(`Word lyrics fetch failed: ${res.status} from ${url}`); trace.log(`Word lyrics fetch failed: ${res.status} from ${url}`);
@@ -1435,7 +1376,7 @@ const hideTidalLyrics = (): boolean => {
// Save classes on first call (for teardown) // Save classes on first call (for teardown)
if (!savedTidalClasses) { if (!savedTidalClasses) {
savedTidalClasses = tidalClasses; savedTidalClasses = tidalClasses;
trace.log(`Saved Tidal classes: ${savedTidalClasses.join(", ")}`); sylTrace(`Saved Tidal classes: ${savedTidalClasses.join(", ")}`);
} }
for (const c of tidalClasses) lyricsContainer.classList.remove(c); for (const c of tidalClasses) lyricsContainer.classList.remove(c);
@@ -1455,7 +1396,7 @@ const restoreTidalLyrics = (): void => {
lyricsContainer.classList.add(c); lyricsContainer.classList.add(c);
} }
} }
trace.log(`Restored Tidal classes: ${savedTidalClasses.join(", ")}`); sylTrace(`Restored Tidal classes: ${savedTidalClasses.join(", ")}`);
} }
lyricsContainer.classList.remove("rl-wbw-active"); lyricsContainer.classList.remove("rl-wbw-active");
@@ -1478,20 +1419,18 @@ const restoreTidalLyrics = (): void => {
// build word/syllable container over tidal spans // build word/syllable container over tidal spans
const buildWordSpans = (): { const buildWordSpans = (): {
words: WordEntry[];
lines: LineEntry[]; lines: LineEntry[];
} => { } => {
const words: WordEntry[] = [];
const lines: LineEntry[] = []; const lines: LineEntry[] = [];
if (!lyricsData) return { words, lines }; if (!lyricsData) return { lines };
const lyricsContainer = document.querySelector( const lyricsContainer = document.querySelector(
'[data-test="lyrics-lines"]', '[data-test="lyrics-lines"]',
) as HTMLElement; ) as HTMLElement;
if (!lyricsContainer) return { words, lines }; if (!lyricsContainer) return { lines };
const innerDiv = lyricsContainer.querySelector(":scope > div") as HTMLElement; const innerDiv = lyricsContainer.querySelector(":scope > div") as HTMLElement;
if (!innerDiv) return { words, lines }; if (!innerDiv) return { lines };
// remove existing container // remove existing container
innerDiv.querySelector(".rl-wbw-container")?.remove(); innerDiv.querySelector(".rl-wbw-container")?.remove();
@@ -1608,14 +1547,22 @@ const buildWordSpans = (): {
for (const group of wordGroups) { for (const group of wordGroups) {
if (isSylMode) { 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) { for (const si of group) {
const syl = syllabus[si]; 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); lineDiv.appendChild(span);
const entry: WordEntry = { el: span, start: syl.time, end: syl.time + syl.duration, duration: syl.duration }; const entry: WordEntry = { el: span, start: syl.time, end: syl.time + syl.duration, duration: syl.duration };
lineWords.push(entry); lineWords.push(entry);
words.push(entry);
} }
} else { } else {
// Word mode: merge syllables into one span // Word mode: merge syllables into one span
@@ -1629,7 +1576,6 @@ const buildWordSpans = (): {
lineDiv.appendChild(span); lineDiv.appendChild(span);
const entry: WordEntry = { el: span, start, end, duration: end - start }; const entry: WordEntry = { el: span, start, end, duration: end - start };
lineWords.push(entry); lineWords.push(entry);
words.push(entry);
} }
// Space between words (not between syllables of the same word) // Space between words (not between syllables of the same word)
lineDiv.appendChild(document.createTextNode(" ")); lineDiv.appendChild(document.createTextNode(" "));
@@ -1671,17 +1617,17 @@ const buildWordSpans = (): {
for (let i = 0; i < lines.length && i < tidalSpans.length; i++) { for (let i = 0; i < lines.length && i < tidalSpans.length; i++) {
lines[i].tidalSpan = tidalSpans[i]; lines[i].tidalSpan = tidalSpans[i];
} }
trace.log( sylTrace(
`Matched ${Math.min(lines.length, tidalSpans.length)} word-by-word lines to Tidal spans (${lines.length} lines, ${tidalSpans.length} spans)`, `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) // append lyrics container (yea ik i was gonan edit tidals but uhh shhhh)
innerDiv.appendChild(wbwContainer); innerDiv.appendChild(wbwContainer);
trace.log( sylTrace(
`Word-by-word DOM: ${words.length} word spans across ${lines.length} lines`, `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 // 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..) // check if our container has been nuked by a react re-render (thx react again again..)
const existing = lyricsContainer.querySelector(".rl-wbw-container"); const existing = lyricsContainer.querySelector(".rl-wbw-container");
if (!existing) { if (!existing) {
trace.log( sylTrace(
"Word-by-word: re-applying after Tidal re-render", "Word-by-word: re-applying after Tidal re-render",
); );
hideTidalLyrics(); hideTidalLyrics();
const result = buildWordSpans(); const result = buildWordSpans();
allWords = result.words;
lines = result.lines; lines = result.lines;
} }
}, 100); }, 100);
@@ -1751,7 +1696,6 @@ const teardown = (): void => {
scrollSynced = true; scrollSynced = true;
isActive = false; isActive = false;
lyricsData = null; lyricsData = null;
allWords = [];
lines = []; lines = [];
activeWordEl = null; activeWordEl = null;
activeLineIdx = -1; activeLineIdx = -1;
@@ -1858,7 +1802,7 @@ const resync = (): void => {
const tidalSyncBtn = document.querySelector('div[class*="_syncButton"] button') as HTMLElement; const tidalSyncBtn = document.querySelector('div[class*="_syncButton"] button') as HTMLElement;
if (tidalSyncBtn) tidalSyncBtn.click(); if (tidalSyncBtn) tidalSyncBtn.click();
unhookSyncButton(); unhookSyncButton();
console.log("[RL-Syllable] Scroll resynced"); sylLog("[RL-Syllable] Scroll resynced");
}; };
// Hook user scroll // Hook user scroll
@@ -1867,7 +1811,7 @@ const hookUserScroll = (parent: HTMLElement): void => {
const onUserScroll = () => { const onUserScroll = () => {
if (!scrollSynced) return; if (!scrollSynced) return;
scrollSynced = false; 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("wheel", onUserScroll, { passive: true });
parent.addEventListener("touchmove", onUserScroll, { passive: true }); parent.addEventListener("touchmove", onUserScroll, { passive: true });
@@ -1907,10 +1851,10 @@ const unhookSyncButton = (): void => {
const startTickLoop = (): void => { const startTickLoop = (): void => {
clearTickLoop(); clearTickLoop();
console.log("[RL-Syllable] Tick loop started"); sylLog("[RL-Syllable] Tick loop started");
let lastLogTime = 0; let lastLogTime = 0;
let lastTickMs = -1; let lastTickMs = 0;
tickLoopUnload = safeInterval(unloads, () => { tickLoopUnload = safeInterval(unloads, () => {
if (!isActive || lines.length === 0) return; if (!isActive || lines.length === 0) return;
@@ -1935,7 +1879,7 @@ const startTickLoop = (): void => {
if (nowMs - lastLogTime >= 1000) { if (nowMs - lastLogTime >= 1000) {
lastLogTime = nowMs; 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) // find active line (-1 if before all lyrics or in instrumental)
@@ -1969,7 +1913,7 @@ const startTickLoop = (): void => {
lines[activeLineIdx].el.removeAttribute("data-current"); lines[activeLineIdx].el.removeAttribute("data-current");
} }
activeLineIdx = -1; 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 // Deactivate line when entering instrumental
@@ -2004,7 +1948,7 @@ const startTickLoop = (): void => {
scrollTo(scrollParent, { top: Math.max(0, scrollTarget), behavior: "smooth" }); 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]`, `[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`; word.el.style.animation = `rl-wipe ${word.duration}ms linear forwards`;
} }
activeWordEl = word.el; activeWordEl = word.el;
console.log( sylLog(
`[RL-Syllable] Word "${word.el.textContent}" | ${word.start} ms - ${word.end} ms [${nowMs.toFixed(0)} ms]`, `[RL-Syllable] Word/Syllable "${word.el.textContent}" | ${word.start} ms - ${word.end} ms [${nowMs.toFixed(0)} ms]`,
); );
} }
} else { } else {
@@ -2085,7 +2029,7 @@ const onTrackChange = async (): Promise<void> => {
return; return;
} }
trace.log( sylTrace(
`Word lyrics: looking up "${trackInfo.title}" by "${trackInfo.artist}"`, `Word lyrics: looking up "${trackInfo.title}" by "${trackInfo.artist}"`,
); );
@@ -2095,14 +2039,14 @@ const onTrackChange = async (): Promise<void> => {
); );
if (token !== trackChangeToken) return; if (token !== trackChangeToken) return;
if (!response) { 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; return;
} }
trace.log( sylTrace(
`Word lyrics: loaded ${response.data.length} lines (source: ${response.metadata.source})`, `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`, `[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 // Build word spans and line entries
const result = buildWordSpans(); const result = buildWordSpans();
allWords = result.words;
lines = result.lines; lines = result.lines;
// Watch React re-renders // Watch React re-renders
@@ -2140,11 +2083,10 @@ const reapplyWordLyrics = (): void => {
isActive = true; isActive = true;
hideTidalLyrics(); hideTidalLyrics();
const result = buildWordSpans(); const result = buildWordSpans();
allWords = result.words;
lines = result.lines; lines = result.lines;
watchForRerender(); watchForRerender();
startTickLoop(); startTickLoop();
console.log("[RL-Syllable] Reapplied word lyrics (cached)"); sylLog("[RL-Syllable] Reapplied word/syllable lyrics (cached)");
}; };
// Called by Settings or dropdown // Called by Settings or dropdown
@@ -2154,7 +2096,16 @@ const toggle = (): void => {
onTrackChange(); 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 // Update lyrics on track change
onGlobalTrackChange(() => { onGlobalTrackChange(() => {
@@ -2240,5 +2191,4 @@ setupHeaderObserver();
setupNowPlayingObserver(); setupNowPlayingObserver();
setupTrackTitleObserver(); setupTrackTitleObserver();
setupStickyLyricsObserver(); setupStickyLyricsObserver();
setupQualityProgressObserver();
setupTrackChangeListener(); setupTrackChangeListener();
@@ -139,8 +139,9 @@
color 0.15s ease-out; color 0.15s ease-out;
} }
/* Hover word */ /* Hover word (Grouped Syllables) */
.rl-wbw-word:hover { .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: text-shadow:
0 0 var(--rl-glow-inner, 2px) lightgray, 0 0 var(--rl-glow-inner, 2px) lightgray,
/* biome-ignore lint: Hover glow should override defaults */ /* biome-ignore lint: Hover glow should override defaults */