BIOME Formating

This commit is contained in:
2025-09-09 17:59:47 +10:00
parent 8178699d81
commit 99661096d5
28 changed files with 3781 additions and 3054 deletions
+410 -329
View File
@@ -1,355 +1,436 @@
import { ReactiveStore } from "@luna/core"; import { ReactiveStore } from "@luna/core";
import { LunaSettings, LunaNumberSetting, LunaSwitchSetting, LunaTextSetting } from "@luna/ui"; import {
LunaSettings,
LunaNumberSetting,
LunaSwitchSetting,
LunaTextSetting,
} from "@luna/ui";
import React from "react"; import React from "react";
export const settings = await ReactiveStore.getPluginStorage("AudioVisualizer", { export const settings = await ReactiveStore.getPluginStorage(
barCount: 32, "AudioVisualizer",
barColor: "#ffffff", {
barRounding: true, barCount: 32,
customColors: [] as string[] barColor: "#ffffff",
}); barRounding: true,
customColors: [] as string[],
},
);
export const Settings = () => { export const Settings = () => {
const [barCount, setBarCount] = React.useState(settings.barCount); const [barCount, setBarCount] = React.useState(settings.barCount);
const [barColor, setBarColor] = React.useState(settings.barColor); const [barColor, setBarColor] = React.useState(settings.barColor);
const [barRounding, setBarRounding] = React.useState(settings.barRounding); const [barRounding, setBarRounding] = React.useState(settings.barRounding);
const [showColorPicker, setShowColorPicker] = React.useState(false); const [showColorPicker, setShowColorPicker] = React.useState(false);
const [isAnimatingIn, setIsAnimatingIn] = React.useState(false); const [isAnimatingIn, setIsAnimatingIn] = React.useState(false);
const [shouldRender, setShouldRender] = React.useState(false); const [shouldRender, setShouldRender] = React.useState(false);
const [customInput, setCustomInput] = React.useState(settings.barColor); const [customInput, setCustomInput] = React.useState(settings.barColor);
const [customColors, setCustomColors] = React.useState(settings.customColors); const [customColors, setCustomColors] = React.useState(settings.customColors);
const [hoveredColorIndex, setHoveredColorIndex] = React.useState<number | null>(null); const [hoveredColorIndex, setHoveredColorIndex] = React.useState<
number | null
>(null);
const closeColorPicker = () => { const closeColorPicker = () => {
setIsAnimatingIn(false); setIsAnimatingIn(false);
setTimeout(() => { setTimeout(() => {
setShowColorPicker(false); setShowColorPicker(false);
setShouldRender(false); setShouldRender(false);
}, 200); // Wait for animation to complete because i need to }, 200); // Wait for animation to complete because i need to
}; };
const openColorPicker = () => { const openColorPicker = () => {
setShowColorPicker(true); setShowColorPicker(true);
setShouldRender(true); setShouldRender(true);
setTimeout(() => setIsAnimatingIn(true), 10); setTimeout(() => setIsAnimatingIn(true), 10);
}; };
React.useEffect(() => { React.useEffect(() => {
if (showColorPicker) { if (showColorPicker) {
setShouldRender(true); setShouldRender(true);
setTimeout(() => setIsAnimatingIn(true), 10); setTimeout(() => setIsAnimatingIn(true), 10);
} }
}, [showColorPicker]); }, [showColorPicker]);
// Common color presets for cool points :D // Common color presets for cool points :D
const colorPresets = [ const colorPresets = [
"#ffffff", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#ffffff",
"#ff8800", "#8800ff", "#0088ff", "#88ff00", "#ff0088", "#00ff88", "#ff0000",
"#444444", "#888888", "#cccccc", "#1db954", "#e22134", "#1976d2" "#00ff00",
]; "#0000ff",
"#ffff00",
"#ff00ff",
"#00ffff",
"#ff8800",
"#8800ff",
"#0088ff",
"#88ff00",
"#ff0088",
"#00ff88",
"#444444",
"#888888",
"#cccccc",
"#1db954",
"#e22134",
"#1976d2",
];
const updateColor = (color: string) => { const updateColor = (color: string) => {
setBarColor(color); setBarColor(color);
setCustomInput(color); setCustomInput(color);
settings.barColor = color; settings.barColor = color;
(window as any).updateAudioVisualizer?.(); (window as any).updateAudioVisualizer?.();
}; };
const addCustomColor = () => { const addCustomColor = () => {
if (customInput) { if (customInput) {
// Trim whitespace and convert to lowercase // Trim whitespace and convert to lowercase
const trimmedInput = customInput.trim().toLowerCase(); const trimmedInput = customInput.trim().toLowerCase();
// Validate hex color format // Validate hex color format
const hexColorRegex = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i; const hexColorRegex = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
if (hexColorRegex.test(trimmedInput) && if (
!colorPresets.includes(trimmedInput) && hexColorRegex.test(trimmedInput) &&
!customColors.includes(trimmedInput)) { !colorPresets.includes(trimmedInput) &&
const newCustomColors = [...customColors, trimmedInput]; !customColors.includes(trimmedInput)
setCustomColors(newCustomColors); ) {
settings.customColors = newCustomColors; const newCustomColors = [...customColors, trimmedInput];
} setCustomColors(newCustomColors);
} settings.customColors = newCustomColors;
}; }
}
};
const removeCustomColor = (colorToRemove: string) => { const removeCustomColor = (colorToRemove: string) => {
const newCustomColors = customColors.filter(color => color !== colorToRemove); const newCustomColors = customColors.filter(
setCustomColors(newCustomColors); (color) => color !== colorToRemove,
settings.customColors = newCustomColors; );
setCustomColors(newCustomColors);
settings.customColors = newCustomColors;
// If the removed color was the selected color (reset to white) // If the removed color was the selected color (reset to white)
if (barColor === colorToRemove) { if (barColor === colorToRemove) {
updateColor("#ffffff"); updateColor("#ffffff");
} }
}; };
const allColors = [...colorPresets, ...customColors]; const allColors = [...colorPresets, ...customColors];
return ( return (
<LunaSettings> <LunaSettings>
<LunaSwitchSetting <LunaSwitchSetting
title="Bar Roundness" title="Bar Roundness"
desc="Enable rounded corners on visualizer bars" desc="Enable rounded corners on visualizer bars"
checked={barRounding} checked={barRounding}
onChange={(_, checked) => { onChange={(_, checked) => {
setBarRounding(checked); setBarRounding(checked);
settings.barRounding = checked; settings.barRounding = checked;
(window as any).updateAudioVisualizer?.(); (window as any).updateAudioVisualizer?.();
}} }}
/> />
<LunaNumberSetting <LunaNumberSetting
title="Bar Count" title="Bar Count"
desc="Number of frequency bars to display" desc="Number of frequency bars to display"
min={8} min={8}
max={64} max={64}
step={1} step={1}
value={barCount} value={barCount}
onNumber={(value: number) => { onNumber={(value: number) => {
setBarCount(value); setBarCount(value);
settings.barCount = value; settings.barCount = value;
(window as any).updateAudioVisualizer?.(); (window as any).updateAudioVisualizer?.();
}} }}
/> />
{/* YUP YOUR EYES WORK... we do be using React code in the settings..*/} {/* YUP YOUR EYES WORK... we do be using React code in the settings..*/}
{/* I'm not sure if this is a good idea, but it works & looks amazing */} {/* I'm not sure if this is a good idea, but it works & looks amazing */}
{/* Sorry @Inrixia <3 */} {/* Sorry @Inrixia <3 */}
<div style={{ <div
padding: "16px 0", style={{
display: "flex", padding: "16px 0",
justifyContent: "space-between", display: "flex",
alignItems: "center" justifyContent: "space-between",
}}> alignItems: "center",
<div> }}
<div style={{ fontWeight: "normal", fontSize: "1.075rem", marginBottom: "4px" }}>Bar Color</div> >
<div style={{ opacity: 0.7, fontSize: "14px" }}>Color of the visualizer bars</div> <div>
</div> <div
<div style={{ display: "flex", gap: "8px", alignItems: "center", position: "relative" }}> style={{
<button fontWeight: "normal",
onClick={() => showColorPicker ? closeColorPicker() : openColorPicker()} fontSize: "1.075rem",
style={{ marginBottom: "4px",
width: "32px", }}
height: "32px", >
border: "1px solid rgba(255,255,255,0.15)", Bar Color
borderRadius: "6px", </div>
cursor: "pointer", <div style={{ opacity: 0.7, fontSize: "14px" }}>
background: barColor, Color of the visualizer bars
backdropFilter: "blur(10px)", </div>
WebkitBackdropFilter: "blur(10px)", </div>
position: "relative", <div
overflow: "hidden" style={{
}} display: "flex",
> gap: "8px",
<div style={{ alignItems: "center",
position: "absolute", position: "relative",
inset: 0, }}
background: "rgba(0,0,0,0.1)", >
backdropFilter: "blur(2px)" <button
}} /> onClick={() =>
</button> showColorPicker ? closeColorPicker() : openColorPicker()
}
style={{
width: "32px",
height: "32px",
border: "1px solid rgba(255,255,255,0.15)",
borderRadius: "6px",
cursor: "pointer",
background: barColor,
backdropFilter: "blur(10px)",
WebkitBackdropFilter: "blur(10px)",
position: "relative",
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
inset: 0,
background: "rgba(0,0,0,0.1)",
backdropFilter: "blur(2px)",
}}
/>
</button>
{/* Custom Color Picker Modal */} {/* Custom Color Picker Modal */}
{shouldRender && ( {shouldRender && (
<> <>
{/* Backdrop */} {/* Backdrop */}
<div <div
style={{ style={{
position: "fixed", position: "fixed",
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
background: "rgba(0,0,0,0.6)", background: "rgba(0,0,0,0.6)",
zIndex: 1000, zIndex: 1000,
opacity: isAnimatingIn ? 1 : 0, opacity: isAnimatingIn ? 1 : 0,
transition: "opacity 0.2s ease" transition: "opacity 0.2s ease",
}} }}
onClick={closeColorPicker} onClick={closeColorPicker}
/> />
{/* Color Picker Panel */} {/* Color Picker Panel */}
<div style={{ <div
position: "fixed", style={{
top: "50%", position: "fixed",
left: "50%", top: "50%",
background: "rgba(20,20,20,0.98)", left: "50%",
backdropFilter: "blur(20px)", background: "rgba(20,20,20,0.98)",
WebkitBackdropFilter: "blur(20px)", backdropFilter: "blur(20px)",
border: "1px solid rgba(255,255,255,0.15)", WebkitBackdropFilter: "blur(20px)",
borderRadius: "16px", border: "1px solid rgba(255,255,255,0.15)",
padding: "20px", borderRadius: "16px",
minWidth: "320px", padding: "20px",
maxWidth: "90vw", minWidth: "320px",
maxHeight: "90vh", maxWidth: "90vw",
zIndex: 1001, maxHeight: "90vh",
boxShadow: "0 20px 40px rgba(0,0,0,0.7)", zIndex: 1001,
opacity: isAnimatingIn ? 1 : 0, boxShadow: "0 20px 40px rgba(0,0,0,0.7)",
transform: isAnimatingIn ? "translate(-50%, -50%) scale(1)" : "translate(-50%, -50%) scale(0.9)", opacity: isAnimatingIn ? 1 : 0,
transition: "all 0.2s ease" transform: isAnimatingIn
}}> ? "translate(-50%, -50%) scale(1)"
<div style={{ marginBottom: "12px", color: "#fff", fontWeight: "bold", fontSize: "14px" }}> : "translate(-50%, -50%) scale(0.9)",
Choose Color transition: "all 0.2s ease",
</div> }}
>
<div
style={{
marginBottom: "12px",
color: "#fff",
fontWeight: "bold",
fontSize: "14px",
}}
>
Choose Color
</div>
{/* Color Grid */} {/* Color Grid */}
<div style={{ <div
display: "grid", style={{
gridTemplateColumns: "repeat(7, 1fr)", display: "grid",
gap: "8px", gridTemplateColumns: "repeat(7, 1fr)",
marginBottom: "16px" gap: "8px",
}}> marginBottom: "16px",
{allColors.map((color, index) => { }}
const isCustomColor = customColors.includes(color); >
const isHovered = hoveredColorIndex === index; {allColors.map((color, index) => {
return ( const isCustomColor = customColors.includes(color);
<div const isHovered = hoveredColorIndex === index;
key={index} return (
style={{ <div
position: "relative", key={index}
width: "32px", style={{
height: "32px", position: "relative",
cursor: "pointer" width: "32px",
}} height: "32px",
className="color-item" cursor: "pointer",
onMouseEnter={() => setHoveredColorIndex(index)} }}
onMouseLeave={() => setHoveredColorIndex(null)} className="color-item"
> onMouseEnter={() => setHoveredColorIndex(index)}
<button onMouseLeave={() => setHoveredColorIndex(null)}
onClick={() => { >
updateColor(color); <button
closeColorPicker(); onClick={() => {
}} updateColor(color);
style={{ closeColorPicker();
width: "100%", }}
height: "100%", style={{
borderRadius: "6px", width: "100%",
border: barColor === color ? "2px solid #fff" : "1px solid rgba(255,255,255,0.2)", height: "100%",
background: color, borderRadius: "6px",
cursor: "pointer", border:
transition: "all 0.2s ease" barColor === color
}} ? "2px solid #fff"
/> : "1px solid rgba(255,255,255,0.2)",
{isCustomColor && ( background: color,
<button cursor: "pointer",
onClick={(e) => { transition: "all 0.2s ease",
e.stopPropagation(); }}
removeCustomColor(color); />
}} {isCustomColor && (
style={{ <button
position: "absolute", onClick={(e) => {
top: "-4px", e.stopPropagation();
right: "-4px", removeCustomColor(color);
width: "16px", }}
height: "16px", style={{
borderRadius: "50%", position: "absolute",
border: "1px solid rgba(255,255,255,0.8)", top: "-4px",
background: "rgba(0,0,0,0.8)", right: "-4px",
color: "#fff", width: "16px",
cursor: "pointer", height: "16px",
fontSize: "10px", borderRadius: "50%",
display: "flex", border: "1px solid rgba(255,255,255,0.8)",
alignItems: "center", background: "rgba(0,0,0,0.8)",
justifyContent: "center", color: "#fff",
opacity: isHovered ? 1 : 0, cursor: "pointer",
transition: "opacity 0.2s ease", fontSize: "10px",
zIndex: 10 display: "flex",
}} alignItems: "center",
className="remove-button" justifyContent: "center",
> opacity: isHovered ? 1 : 0,
× transition: "opacity 0.2s ease",
</button> zIndex: 10,
)} }}
</div> className="remove-button"
); >
})} ×
</div> </button>
)}
</div>
);
})}
</div>
{/* Custom Hex Input */} {/* Custom Hex Input */}
<div style={{ marginBottom: "12px" }}> <div style={{ marginBottom: "12px" }}>
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: "12px", marginBottom: "6px" }}> <div
Add Custom Color style={{
</div> color: "rgba(255,255,255,0.7)",
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}> fontSize: "12px",
<input marginBottom: "6px",
type="text" }}
value={customInput} >
onChange={(e) => setCustomInput(e.target.value)} Add Custom Color
onKeyDown={(e) => { </div>
if (e.key === 'Enter') { <div
updateColor(customInput); style={{
addCustomColor(); display: "flex",
} gap: "8px",
}} alignItems: "center",
placeholder="#ffffff" }}
style={{ >
flex: 1, <input
padding: "8px 12px", type="text"
borderRadius: "6px", value={customInput}
border: "1px solid rgba(255,255,255,0.2)", onChange={(e) => setCustomInput(e.target.value)}
background: "rgba(255,255,255,0.1)", onKeyDown={(e) => {
color: "#fff", if (e.key === "Enter") {
fontSize: "14px", updateColor(customInput);
fontFamily: "monospace", addCustomColor();
boxSizing: "border-box" }
}} }}
/> placeholder="#ffffff"
<button style={{
onClick={() => { flex: 1,
updateColor(customInput); padding: "8px 12px",
addCustomColor(); borderRadius: "6px",
}} border: "1px solid rgba(255,255,255,0.2)",
style={{ background: "rgba(255,255,255,0.1)",
width: "32px", color: "#fff",
height: "32px", fontSize: "14px",
borderRadius: "6px", fontFamily: "monospace",
border: "1px solid rgba(255,255,255,0.3)", boxSizing: "border-box",
background: "rgba(255,255,255,0.15)", }}
color: "#fff", />
cursor: "pointer", <button
fontSize: "16px", onClick={() => {
display: "flex", updateColor(customInput);
alignItems: "center", addCustomColor();
justifyContent: "center", }}
transition: "all 0.2s ease" style={{
}} width: "32px",
onMouseEnter={(e) => { height: "32px",
e.currentTarget.style.background = "rgba(255,255,255,0.25)"; borderRadius: "6px",
}} border: "1px solid rgba(255,255,255,0.3)",
onMouseLeave={(e) => { background: "rgba(255,255,255,0.15)",
e.currentTarget.style.background = "rgba(255,255,255,0.15)"; color: "#fff",
}} cursor: "pointer",
> fontSize: "16px",
+ display: "flex",
</button> alignItems: "center",
</div> justifyContent: "center",
</div> 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)";
}}
>
+
</button>
</div>
</div>
{/* Close Button (Done) - Also runs when color chosen*/} {/* Close Button (Done) - Also runs when color chosen*/}
<button <button
onClick={closeColorPicker} onClick={closeColorPicker}
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)",
color: "#fff", color: "#fff",
cursor: "pointer", cursor: "pointer",
fontSize: "12px" fontSize: "12px",
}} }}
> >
Done Done
</button> </button>
</div> </div>
</> </>
)} )}
</div> </div>
</div> </div>
</LunaSettings>
);
</LunaSettings>
);
}; };
+335 -306
View File
@@ -10,21 +10,28 @@ export const { trace } = Tracer("[Audio Visualizer]");
// Helper function for consistent logging // Helper function for consistent logging
const log = (message: string) => console.log(`[Audio Visualizer] ${message}`); const log = (message: string) => console.log(`[Audio Visualizer] ${message}`);
const warn = (message: string) => console.warn(`[Audio Visualizer] ${message}`); const warn = (message: string) => console.warn(`[Audio Visualizer] ${message}`);
const error = (message: string) => console.error(`[Audio Visualizer] ${message}`); const error = (message: string) =>
console.error(`[Audio Visualizer] ${message}`);
export { Settings }; export { Settings };
// Basic config with settings // Basic config with settings
const config = { const config = {
enabled: true, enabled: true,
position: 'left' as 'left' | 'right', position: "left" as "left" | "right",
width: 200, width: 200,
height: 40, height: 40,
get barCount() { return settings.barCount; }, get barCount() {
get color() { return settings.barColor; }, return settings.barCount;
get barRounding() { return settings.barRounding; }, },
sensitivity: 1.5, get color() {
smoothing: 0.8, return settings.barColor;
visualizerType: 'bars' as 'bars' | 'waveform' | 'circular' },
get barRounding() {
return settings.barRounding;
},
sensitivity: 1.5,
smoothing: 0.8,
visualizerType: "bars" as "bars" | "waveform" | "circular",
}; };
// Clean up resources // Clean up resources
@@ -49,128 +56,135 @@ let canvasContext: CanvasRenderingContext2D | null = null;
// Find the audio element - this is a bit of a hack but it works // Find the audio element - this is a bit of a hack but it works
const findAudioElement = (): HTMLAudioElement | null => { const findAudioElement = (): HTMLAudioElement | null => {
// Try main selectors first // Try main selectors first
const selectors = [ const selectors = [
'audio', "audio",
'video', "video",
'audio[data-test]', "audio[data-test]",
'[data-test="audio-player"] audio' '[data-test="audio-player"] audio',
]; ];
for (const selector of selectors) { for (const selector of selectors) {
const element = document.querySelector(selector) as HTMLAudioElement; const element = document.querySelector(selector) as HTMLAudioElement;
if (element && (element.tagName === 'AUDIO' || element.tagName === 'VIDEO')) { if (
return element; element &&
} (element.tagName === "AUDIO" || element.tagName === "VIDEO")
} ) {
return element;
}
}
// Quick scan for any audio elements // Quick scan for any audio elements
const audioElements = document.querySelectorAll('audio, video'); const audioElements = document.querySelectorAll("audio, video");
for (const element of audioElements) { for (const element of audioElements) {
const audioEl = element as HTMLAudioElement; const audioEl = element as HTMLAudioElement;
if (audioEl.src || audioEl.currentSrc) { if (audioEl.src || audioEl.currentSrc) {
return audioEl; return audioEl;
} }
} }
return null; return null;
}; };
// Initialize audio visualization // Initialize audio visualization
const initializeAudioVisualizer = async (): Promise<void> => { const initializeAudioVisualizer = async (): Promise<void> => {
try { try {
// Find the audio element // Find the audio element
const audioElement = findAudioElement(); const audioElement = findAudioElement();
if (!audioElement) { if (!audioElement) {
return; return;
} }
// create audio context // create audio context
if (!audioContext) { if (!audioContext) {
audioContext = new AudioContext(); audioContext = new AudioContext();
log("Created AudioContext"); log("Created AudioContext");
} }
// create analyser // create analyser
if (!analyser) { if (!analyser) {
analyser = audioContext.createAnalyser(); analyser = audioContext.createAnalyser();
analyser.fftSize = 512; // Fixed power of 2 that provides enough frequency bins analyser.fftSize = 512; // Fixed power of 2 that provides enough frequency bins
analyser.smoothingTimeConstant = config.smoothing; analyser.smoothingTimeConstant = config.smoothing;
dataArray = new Uint8Array(analyser.frequencyBinCount); dataArray = new Uint8Array(analyser.frequencyBinCount);
log("Created AnalyserNode"); log("Created AnalyserNode");
} }
// attempt audio connection if not already connected // attempt audio connection if not already connected
if (!isSourceConnected && audioElement !== currentAudioElement) { if (!isSourceConnected && audioElement !== currentAudioElement) {
try { try {
// Create audio source - this might fail if already connected elsewhere // Create audio source - this might fail if already connected elsewhere
audioSource = audioContext.createMediaElementSource(audioElement); audioSource = audioContext.createMediaElementSource(audioElement);
audioSource.connect(analyser); audioSource.connect(analyser);
// CRITICAL: connect back to destination for audio output (otherwise no sound) // CRITICAL: connect back to destination for audio output (otherwise no sound)
analyser.connect(audioContext.destination); analyser.connect(audioContext.destination);
currentAudioElement = audioElement; currentAudioElement = audioElement;
isSourceConnected = true; isSourceConnected = true;
log("Connected to audio stream with output"); log("Connected to audio stream with output");
} catch (error) { } catch (error) {
// Audio is connected elsewhere - that's fine, we just can't visualize // Audio is connected elsewhere - that's fine, we just can't visualize
if (error instanceof Error && error.message.includes('already connected')) { if (
log("Audio already connected elsewhere - skipping visualization"); error instanceof Error &&
} error.message.includes("already connected")
return; ) {
} log("Audio already connected elsewhere - skipping visualization");
} }
return;
}
}
// Resume context only if needed and don't wait for it // Resume context only if needed and don't wait for it
// (otherwise it will wait for the audio to start playing) // (otherwise it will wait for the audio to start playing)
if (audioContext.state === 'suspended') { if (audioContext.state === "suspended") {
audioContext.resume().catch(() => {}); // Fire and forget audioContext.resume().catch(() => {}); // Fire and forget
} }
// Create UI only if it doesn't exist // Create UI only if it doesn't exist
if (!visualizerContainer) { if (!visualizerContainer) {
createVisualizerUI(); createVisualizerUI();
} }
// Start animation only if not already running // Start animation only if not already running
if (!animationId) { if (!animationId) {
animate(); animate();
} }
} catch (err) {
} catch (err) { // log errors
// log errors console.error(err);
console.error(err); }
}
}; };
// Create the visualizer UI container and canvas // Create the visualizer UI container and canvas
const createVisualizerUI = (): void => { const createVisualizerUI = (): void => {
// Remove existing visualizer if it exists // Remove existing visualizer if it exists
removeVisualizerUI(); removeVisualizerUI();
if (!config.enabled) return; if (!config.enabled) return;
// Find the search bar // Find the search bar
const searchField = document.querySelector('input[class*="_searchField"]') as HTMLInputElement; const searchField = document.querySelector(
if (!searchField) { 'input[class*="_searchField"]',
warn("Search field not found"); ) as HTMLInputElement;
return; if (!searchField) {
} warn("Search field not found");
return;
}
const searchContainer = searchField.parentElement; const searchContainer = searchField.parentElement;
if (!searchContainer) { if (!searchContainer) {
warn("Search container not found"); warn("Search container not found");
return; return;
} }
// Create visualizer container // Create visualizer container
visualizerContainer = document.createElement('div'); visualizerContainer = document.createElement("div");
visualizerContainer.id = 'audio-visualizer-container'; visualizerContainer.id = "audio-visualizer-container";
visualizerContainer.style.cssText = ` visualizerContainer.style.cssText = `
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-${config.position === 'left' ? 'right' : 'left'}: 12px; margin-${config.position === "left" ? "right" : "left"}: 12px;
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
border-radius: 8px; border-radius: 8px;
padding: 4px; padding: 4px;
@@ -178,154 +192,168 @@ const createVisualizerUI = (): void => {
-webkit-backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
`; `;
// Create canvas // Create canvas
canvas = document.createElement('canvas'); canvas = document.createElement("canvas");
canvas.width = config.width; canvas.width = config.width;
canvas.height = config.height; canvas.height = config.height;
canvas.style.cssText = ` canvas.style.cssText = `
width: ${config.width}px; width: ${config.width}px;
height: ${config.height}px; height: ${config.height}px;
border-radius: 4px; border-radius: 4px;
`; `;
visualizerContainer.appendChild(canvas); visualizerContainer.appendChild(canvas);
canvasContext = canvas.getContext('2d'); canvasContext = canvas.getContext("2d");
// Insert visualizer next to search bar // Insert visualizer next to search bar
if (config.position === 'left') { if (config.position === "left") {
searchContainer.parentElement?.insertBefore(visualizerContainer, searchContainer); searchContainer.parentElement?.insertBefore(
} else { visualizerContainer,
searchContainer.parentElement?.insertBefore(visualizerContainer, searchContainer.nextSibling); searchContainer,
} );
} else {
searchContainer.parentElement?.insertBefore(
visualizerContainer,
searchContainer.nextSibling,
);
}
}; };
// Remove visualizer UI // Remove visualizer UI
const removeVisualizerUI = (): void => { const removeVisualizerUI = (): void => {
if (visualizerContainer) { if (visualizerContainer) {
visualizerContainer.remove(); visualizerContainer.remove();
visualizerContainer = null; visualizerContainer = null;
canvas = null; canvas = null;
canvasContext = null; canvasContext = null;
} }
}; };
// Animation loop for rendering visualizer // Animation loop for rendering visualizer
const animate = (): void => { const animate = (): void => {
if (!canvasContext || !canvas) { if (!canvasContext || !canvas) {
animationId = null; animationId = null;
return; return;
} }
// Update canvas color in case it changed // Update canvas color in case it changed
canvasContext.fillStyle = config.color; canvasContext.fillStyle = config.color;
canvasContext.strokeStyle = config.color; canvasContext.strokeStyle = config.color;
// Check if we have real audio data - this might not be needed but its a good idea // Check if we have real audio data - this might not be needed but its a good idea
let hasRealAudio = false; let hasRealAudio = false;
if (analyser && dataArray) { if (analyser && dataArray) {
analyser.getByteFrequencyData(dataArray); analyser.getByteFrequencyData(dataArray);
// Check if there's actual audio signal (not just silence) // Check if there's actual audio signal (not just silence)
const avgVolume = dataArray.reduce((sum, val) => sum + val, 0) / dataArray.length; const avgVolume =
hasRealAudio = avgVolume > 5; // Threshold for detecting actual audio dataArray.reduce((sum, val) => sum + val, 0) / dataArray.length;
} hasRealAudio = avgVolume > 5; // Threshold for detecting actual audio
}
// Clear canvas // Clear canvas
canvasContext.clearRect(0, 0, canvas.width, canvas.height); canvasContext.clearRect(0, 0, canvas.width, canvas.height);
if (hasRealAudio && analyser && dataArray) { if (hasRealAudio && analyser && dataArray) {
// Draw real audio visualization // Draw real audio visualization
switch (config.visualizerType) { switch (config.visualizerType) {
case 'bars': // Is implemented YAYYY (default) case "bars": // Is implemented YAYYY (default)
drawBars(); drawBars();
break; break;
case 'waveform': // Not implemented yet case "waveform": // Not implemented yet
drawWaveform(); drawWaveform();
break; break;
case 'circular': // Not implemented yet case "circular": // Not implemented yet
drawCircular(); drawCircular();
break; break;
} }
} else { } else {
// Draw cool scrolling wave effect when no audio // Draw cool scrolling wave effect when no audio
drawScrollingWave(); drawScrollingWave();
} }
animationId = requestAnimationFrame(animate); animationId = requestAnimationFrame(animate);
}; };
// Global wave animation state // Global wave animation state
let waveTime = 0; let waveTime = 0;
// Helper function to draw rounded rectangles // Helper function to draw rounded rectangles
const drawRoundedRect = (ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number): void => { const drawRoundedRect = (
ctx.beginPath(); ctx: CanvasRenderingContext2D,
ctx.roundRect(x, y, width, height, radius); x: number,
ctx.fill(); y: number,
width: number,
height: number,
radius: number,
): void => {
ctx.beginPath();
ctx.roundRect(x, y, width, height, radius);
ctx.fill();
}; };
// Draw scrolling wave effect when no audio is detected // Draw scrolling wave effect when no audio is detected
const drawScrollingWave = (): void => { const drawScrollingWave = (): void => {
if (!canvasContext || !canvas) return; if (!canvasContext || !canvas) return;
waveTime += 0.05; // Speed of wave animation waveTime += 0.05; // Speed of wave animation
const barCount = config.barCount; const barCount = config.barCount;
const barWidth = canvas.width / barCount; const barWidth = canvas.width / barCount;
const maxHeight = canvas.height * 0.6; const maxHeight = canvas.height * 0.6;
canvasContext.fillStyle = config.color; canvasContext.fillStyle = config.color;
for (let i = 0; i < barCount; i++) { for (let i = 0; i < barCount; i++) {
// Create a sine wave that scrolls back and forth // Create a sine wave that scrolls back and forth
const x = i / barCount; const x = i / barCount;
const wave1 = Math.sin(x * Math.PI * 2 + waveTime) * 0.3; const wave1 = Math.sin(x * Math.PI * 2 + waveTime) * 0.3;
const wave2 = Math.sin(x * Math.PI * 4 + waveTime * 1.3) * 0.2; const wave2 = Math.sin(x * Math.PI * 4 + waveTime * 1.3) * 0.2;
const wave3 = Math.sin(x * Math.PI * 6 + waveTime * 0.7) * 0.1; const wave3 = Math.sin(x * Math.PI * 6 + waveTime * 0.7) * 0.1;
// Combine waves for complex pattern // Combine waves for complex pattern
const combinedWave = (wave1 + wave2 + wave3 + 1) / 2; // Normalize to 0-1 const combinedWave = (wave1 + wave2 + wave3 + 1) / 2; // Normalize to 0-1
// Add a traveling wave effect // Add a traveling wave effect
const travelWave = Math.sin(x * Math.PI * 3 - waveTime * 2) * 0.5 + 0.5; const travelWave = Math.sin(x * Math.PI * 3 - waveTime * 2) * 0.5 + 0.5;
// Final height calculation // Final height calculation
const barHeight = maxHeight * combinedWave * travelWave * 0.8 + 2; // Minimum height of 2px const barHeight = maxHeight * combinedWave * travelWave * 0.8 + 2; // Minimum height of 2px
const xPos = i * barWidth; const xPos = i * barWidth;
const yPos = (canvas.height - barHeight) / 2; const yPos = (canvas.height - barHeight) / 2;
// Draw rounded or square bars based on setting // Draw rounded or square bars based on setting
if (config.barRounding) { if (config.barRounding) {
drawRoundedRect(canvasContext, xPos, yPos, barWidth - 1, barHeight, 2); drawRoundedRect(canvasContext, xPos, yPos, barWidth - 1, barHeight, 2);
} else { } else {
canvasContext.fillRect(xPos, yPos, barWidth - 1, barHeight); canvasContext.fillRect(xPos, yPos, barWidth - 1, barHeight);
} }
} }
}; };
// Draw frequency bars - default // Draw frequency bars - default
const drawBars = (): void => { const drawBars = (): void => {
if (!canvasContext || !dataArray || !canvas) return; if (!canvasContext || !dataArray || !canvas) return;
const barWidth = canvas.width / config.barCount; const barWidth = canvas.width / config.barCount;
const heightScale = canvas.height / 255; const heightScale = canvas.height / 255;
canvasContext.fillStyle = config.color; canvasContext.fillStyle = config.color;
for (let i = 0; i < config.barCount; i++) { for (let i = 0; i < config.barCount; i++) {
const dataIndex = Math.floor(i * (dataArray.length / config.barCount)); const dataIndex = Math.floor(i * (dataArray.length / config.barCount));
const barHeight = (dataArray[dataIndex] * config.sensitivity * heightScale); const barHeight = dataArray[dataIndex] * config.sensitivity * heightScale;
const x = i * barWidth; const x = i * barWidth;
const y = canvas.height - barHeight; const y = canvas.height - barHeight;
// Draw rounded or square bars based on setting // Draw rounded or square bars based on setting
if (config.barRounding) { if (config.barRounding) {
drawRoundedRect(canvasContext, x, y, barWidth - 1, barHeight, 2); drawRoundedRect(canvasContext, x, y, barWidth - 1, barHeight, 2);
} else { } else {
canvasContext.fillRect(x, y, barWidth - 1, barHeight); canvasContext.fillRect(x, y, barWidth - 1, barHeight);
} }
} }
}; };
// Draw waveform visualization - NOT IMPLEMENTED YET // Draw waveform visualization - NOT IMPLEMENTED YET
@@ -386,22 +414,22 @@ const drawBars = (): void => {
// Update visualizer settings // Update visualizer settings
const updateAudioVisualizer = (): void => { const updateAudioVisualizer = (): void => {
if (analyser) { if (analyser) {
// use a fixed size that provides enough frequency bins // use a fixed size that provides enough frequency bins
analyser.fftSize = 512; // Fixed power of 2 - important analyser.fftSize = 512; // Fixed power of 2 - important
analyser.smoothingTimeConstant = config.smoothing; analyser.smoothingTimeConstant = config.smoothing;
dataArray = new Uint8Array(analyser.frequencyBinCount); dataArray = new Uint8Array(analyser.frequencyBinCount);
} }
if (canvas) { if (canvas) {
canvas.width = config.width; canvas.width = config.width;
canvas.height = config.height; canvas.height = config.height;
canvas.style.width = `${config.width}px`; canvas.style.width = `${config.width}px`;
canvas.style.height = `${config.height}px`; canvas.style.height = `${config.height}px`;
} }
// Recreate UI if position changed // Recreate UI if position changed
createVisualizerUI(); createVisualizerUI();
}; };
// Make updateAudioVisualizer available globally for settings // Make updateAudioVisualizer available globally for settings
@@ -409,116 +437,117 @@ const updateAudioVisualizer = (): void => {
// Clean up function // Clean up function
const cleanupAudioVisualizer = (): void => { const cleanupAudioVisualizer = (): void => {
// stop animation and hide UI - don't touch audio connections (otherwise it will reconnect) // stop animation and hide UI - don't touch audio connections (otherwise it will reconnect)
if (animationId) { if (animationId) {
cancelAnimationFrame(animationId); cancelAnimationFrame(animationId);
animationId = null; animationId = null;
} }
removeVisualizerUI(); removeVisualizerUI();
// i was killing audio connections - But it was reconnecting and being a pain // i was killing audio connections - But it was reconnecting and being a pain
// so i just left it alone - it works fine // so i just left it alone - it works fine
}; };
// Initialize when DOM is ready and track is playing // Initialize when DOM is ready and track is playing
const observePlayState = (): void => { const observePlayState = (): void => {
let hasTriedInitialization = false; let hasTriedInitialization = false;
let checkCount = 0; let checkCount = 0;
const checkAndInitialize = () => { const checkAndInitialize = () => {
checkCount++; checkCount++;
// Only try to initialize once when music starts playing // Only try to initialize once when music starts playing
if (PlayState.playing && !hasTriedInitialization) { if (PlayState.playing && !hasTriedInitialization) {
hasTriedInitialization = true; hasTriedInitialization = true;
log("Initializing audio visualizer..."); log("Initializing audio visualizer...");
// Initialize immediately - no delay (after audio starts playing ofc) // Initialize immediately - no delay (after audio starts playing ofc)
initializeAudioVisualizer().then(() => { initializeAudioVisualizer().then(() => {
if (audioContext && analyser) { if (audioContext && analyser) {
log("Audio visualizer ready!"); log("Audio visualizer ready!");
} else { } else {
hasTriedInitialization = false; // Allow retry if failed hasTriedInitialization = false; // Allow retry if failed
} }
}); });
} else if (!PlayState.playing && hasTriedInitialization) { } else if (!PlayState.playing && hasTriedInitialization) {
// Reset try flag when music stops so it can try again next time (otherwise it explode) // Reset try flag when music stops so it can try again next time (otherwise it explode)
hasTriedInitialization = false; hasTriedInitialization = false;
} }
// Keep animation running regardless of play state // Keep animation running regardless of play state
if (!animationId) { if (!animationId) {
animate(); animate();
} }
}; };
// Start with fast checking, then slow down // Start with fast checking, then slow down
const fastInterval = setInterval(() => { const fastInterval = setInterval(() => {
checkAndInitialize(); checkAndInitialize();
if (checkCount > 10) { // After 10 quick checks, switch to slower if (checkCount > 10) {
clearInterval(fastInterval); // After 10 quick checks, switch to slower
const slowInterval = setInterval(checkAndInitialize, 2000); clearInterval(fastInterval);
unloads.add(() => clearInterval(slowInterval)); const slowInterval = setInterval(checkAndInitialize, 2000);
} unloads.add(() => clearInterval(slowInterval));
}, 200); // Check every 200ms initially }
}, 200); // Check every 200ms initially
unloads.add(() => clearInterval(fastInterval)); unloads.add(() => clearInterval(fastInterval));
// Immediate first check // Immediate first check
checkAndInitialize(); checkAndInitialize();
}; };
// Initialize the plugin // Initialize the plugin
const initialize = (): void => { const initialize = (): void => {
log("Audio Visualizer plugin initializing..."); log("Audio Visualizer plugin initializing...");
// Start immediately - DOM should be ready by plugin load // Start immediately - DOM should be ready by plugin load
setTimeout(() => { setTimeout(() => {
log("Starting visualizer..."); log("Starting visualizer...");
// Create UI immediately so wave effect shows // Create UI immediately so wave effect shows
createVisualizerUI(); createVisualizerUI();
// Start animation loop immediately // Start animation loop immediately
animate(); animate();
// Also observe play state for audio detection // Also observe play state for audio detection
observePlayState(); observePlayState();
}, 100); // Minimal delay to ensure DOM is ready }, 100); // Minimal delay to ensure DOM is ready
}; };
// Complete cleanup function for plugin unload // Complete cleanup function for plugin unload
const completeCleanup = (): void => { const completeCleanup = (): void => {
log("Complete cleanup - plugin unloading"); log("Complete cleanup - plugin unloading");
if (animationId) { if (animationId) {
cancelAnimationFrame(animationId); cancelAnimationFrame(animationId);
animationId = null; animationId = null;
} }
removeVisualizerUI(); removeVisualizerUI();
// Fully disconnect and reset everything // Fully disconnect and reset everything
if (audioSource) { if (audioSource) {
try { try {
audioSource.disconnect(); audioSource.disconnect();
log("Disconnected audio source completely"); log("Disconnected audio source completely");
} catch (e) { } catch (e) {
log("Audio source already disconnected"); log("Audio source already disconnected");
} }
} }
// Close audio context completely on plugin unload // Close audio context completely on plugin unload
if (audioContext && audioContext.state !== 'closed') { if (audioContext && audioContext.state !== "closed") {
audioContext.close(); audioContext.close();
log("Closed AudioContext"); log("Closed AudioContext");
} }
// Reset all references // Reset all references
audioContext = null; audioContext = null;
analyser = null; analyser = null;
audioSource = null; audioSource = null;
dataArray = null; dataArray = null;
currentAudioElement = null; currentAudioElement = null;
isSourceConnected = false; isSourceConnected = false;
}; };
// Register cleanup // Register cleanup
+26 -26
View File
@@ -1,56 +1,56 @@
/* Audio Visualizer CSS - Only applies to the Visualizer */ /* Audio Visualizer CSS - Only applies to the Visualizer */
#audio-visualizer-container { #audio-visualizer-container {
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
} }
#audio-visualizer-container:hover { #audio-visualizer-container:hover {
transform: scale(1.02); transform: scale(1.02);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
} }
#audio-visualizer-container canvas { #audio-visualizer-container canvas {
display: block; display: block;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
/* Responsive adjustments */ /* Responsive adjustments */
@media (max-width: 768px) { @media (max-width: 768px) {
#audio-visualizer-container { #audio-visualizer-container {
margin: 4px; margin: 4px;
padding: 2px; padding: 2px;
} }
#audio-visualizer-container canvas { #audio-visualizer-container canvas {
max-width: 150px; max-width: 150px;
max-height: 30px; max-height: 30px;
} }
} }
/* Where to put the thingy */ /* Where to put the thingy */
[class*="_searchField"] { [class*="_searchField"] {
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
/* Shadow when active - doesnt seem to only apply when active but thats better */ /* Shadow when active - doesnt seem to only apply when active but thats better */
#audio-visualizer-container.active { #audio-visualizer-container.active {
box-shadow: 0 0 20px rgba(255, 255, 255, 0.3); box-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
} }
/* Fade in animation */ /* Fade in animation */
@keyframes fadeIn { @keyframes fadeIn {
from { from {
opacity: 0; opacity: 0;
transform: scale(0.8); transform: scale(0.8);
} }
to { to {
opacity: 1; opacity: 1;
transform: scale(1); transform: scale(1);
} }
} }
#audio-visualizer-container { #audio-visualizer-container {
animation: fadeIn 0.5s ease-out; animation: fadeIn 0.5s ease-out;
} }
+9 -10
View File
@@ -1,12 +1,11 @@
{ {
"name": "@meowarex/colorama-lyrics", "name": "@meowarex/colorama-lyrics",
"description": "Customize lyrics colors: single, gradient & auto from cover art", "description": "Customize lyrics colors: single, gradient & auto from cover art",
"author": { "author": {
"name": "meowarex", "name": "meowarex",
"url": "https://github.com/meowarex", "url": "https://github.com/meowarex",
"avatarUrl": "https://avatars.githubusercontent.com/u/90243579" "avatarUrl": "https://avatars.githubusercontent.com/u/90243579"
}, },
"main": "./src/index.ts", "main": "./src/index.ts",
"type": "module" "type": "module"
} }
File diff suppressed because it is too large Load Diff
+178 -164
View File
@@ -13,181 +13,195 @@ new StyleTag("ColoramaLyrics", unloads, styles);
// Simple dominant color extraction from current cover art // Simple dominant color extraction from current cover art
async function getCoverArtElement(): Promise<HTMLImageElement | null> { async function getCoverArtElement(): Promise<HTMLImageElement | null> {
const img = document.querySelector('figure[class*="_albumImage"] > div > div > div > img') as HTMLImageElement | null; const img = document.querySelector(
if (img) return img; 'figure[class*="_albumImage"] > div > div > div > img',
const video = document.querySelector('figure[class*="_albumImage"] > div > div > div > video') as HTMLVideoElement | null; ) as HTMLImageElement | null;
if (video) { if (img) return img;
const poster = video.getAttribute("poster"); const video = document.querySelector(
if (!poster) return null; 'figure[class*="_albumImage"] > div > div > div > video',
const tempImg = new Image(); ) as HTMLVideoElement | null;
tempImg.crossOrigin = "anonymous"; if (video) {
tempImg.src = poster; const poster = video.getAttribute("poster");
await new Promise<void>((resolve) => { if (!poster) return null;
tempImg.onload = () => resolve(); const tempImg = new Image();
tempImg.onerror = () => resolve(); tempImg.crossOrigin = "anonymous";
}); tempImg.src = poster;
return tempImg as unknown as HTMLImageElement; await new Promise<void>((resolve) => {
} tempImg.onload = () => resolve();
return null; tempImg.onerror = () => resolve();
});
return tempImg as unknown as HTMLImageElement;
}
return null;
} }
function getDominantColorsFromImage(img: HTMLImageElement, count: number = 2): string[] { function getDominantColorsFromImage(
try { img: HTMLImageElement,
const canvas = document.createElement('canvas'); count: number = 2,
const ctx = canvas.getContext('2d'); ): string[] {
if (!ctx) return ["#ffffff", "#88aaff"]; // fallback try {
const w = 64; const canvas = document.createElement("canvas");
const h = 64; const ctx = canvas.getContext("2d");
canvas.width = w; if (!ctx) return ["#ffffff", "#88aaff"]; // fallback
canvas.height = h; const w = 64;
ctx.drawImage(img, 0, 0, w, h); const h = 64;
const data = ctx.getImageData(0, 0, w, h).data; canvas.width = w;
canvas.height = h;
ctx.drawImage(img, 0, 0, w, h);
const data = ctx.getImageData(0, 0, w, h).data;
// Simple k-means-ish binning into 16 buckets per channel // Simple k-means-ish binning into 16 buckets per channel
const buckets = new Map<string, number>(); const buckets = new Map<string, number>();
for (let i = 0; i < data.length; i += 4) { for (let i = 0; i < data.length; i += 4) {
const r = data[i]; const r = data[i];
const g = data[i + 1]; const g = data[i + 1];
const b = data[i + 2]; const b = data[i + 2];
const key = `${Math.round(r/16)},${Math.round(g/16)},${Math.round(b/16)}`; const key = `${Math.round(r / 16)},${Math.round(g / 16)},${Math.round(b / 16)}`;
buckets.set(key, (buckets.get(key) ?? 0) + 1); buckets.set(key, (buckets.get(key) ?? 0) + 1);
} }
const sorted = [...buckets.entries()].sort((a, b) => b[1] - a[1]); const sorted = [...buckets.entries()].sort((a, b) => b[1] - a[1]);
const picked = sorted.slice(0, Math.max(1, count)).map(([key]) => { const picked = sorted.slice(0, Math.max(1, count)).map(([key]) => {
const [r, g, b] = key.split(',').map(v => parseInt(v, 10) * 16); const [r, g, b] = key.split(",").map((v) => parseInt(v, 10) * 16);
return `#${[r, g, b].map(v => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0')).join('')}`; return `#${[r, g, b].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")}`;
}); });
return picked; return picked;
} catch { } catch {
return ["#ffffff", "#88aaff"]; // fallback return ["#ffffff", "#88aaff"]; // fallback
} }
} }
// build rgba() from hex + alpha percentage // build rgba() from hex + alpha percentage
function hexToRgb(hex: string): { r: number; g: number; b: number } | null { function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
let v = hex.trim(); let v = hex.trim();
if (!v.startsWith('#')) v = `#${v}`; if (!v.startsWith("#")) v = `#${v}`;
if (/^#([0-9a-fA-F]{3})$/.test(v)) { if (/^#([0-9a-fA-F]{3})$/.test(v)) {
const r = parseInt(v[1] + v[1], 16); const r = parseInt(v[1] + v[1], 16);
const g = parseInt(v[2] + v[2], 16); const g = parseInt(v[2] + v[2], 16);
const b = parseInt(v[3] + v[3], 16); const b = parseInt(v[3] + v[3], 16);
return { r, g, b }; return { r, g, b };
} }
if (/^#([0-9a-fA-F]{6})$/.test(v)) { if (/^#([0-9a-fA-F]{6})$/.test(v)) {
const r = parseInt(v.slice(1, 3), 16); const r = parseInt(v.slice(1, 3), 16);
const g = parseInt(v.slice(3, 5), 16); const g = parseInt(v.slice(3, 5), 16);
const b = parseInt(v.slice(5, 7), 16); const b = parseInt(v.slice(5, 7), 16);
return { r, g, b }; return { r, g, b };
} }
// 8-digit hex expects #AARRGGBB. Indices 1-3 are the alpha byte (ignored here), // 8-digit hex expects #AARRGGBB. Indices 1-3 are the alpha byte (ignored here),
// so r/g/b are extracted from v.slice(3,5), v.slice(5,7), v.slice(7,9) respectively. // so r/g/b are extracted from v.slice(3,5), v.slice(5,7), v.slice(7,9) respectively.
if (/^#([0-9a-fA-F]{8})$/.test(v)) { if (/^#([0-9a-fA-F]{8})$/.test(v)) {
const r = parseInt(v.slice(3, 5), 16); const r = parseInt(v.slice(3, 5), 16);
const g = parseInt(v.slice(5, 7), 16); const g = parseInt(v.slice(5, 7), 16);
const b = parseInt(v.slice(7, 9), 16); const b = parseInt(v.slice(7, 9), 16);
return { r, g, b }; return { r, g, b };
} }
return null; return null;
} }
function rgbaFromHexAndAlpha(hex: string, alphaPercent: number | undefined): string { function rgbaFromHexAndAlpha(
const rgb = hexToRgb(hex); hex: string,
const a = Math.max(0.05, Math.min(100, alphaPercent ?? 100)) / 100; alphaPercent: number | undefined,
if (!rgb) return `rgba(255,255,255,${a})`; ): string {
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`; const rgb = hexToRgb(hex);
const a = Math.max(0.05, Math.min(100, alphaPercent ?? 100)) / 100;
if (!rgb) return `rgba(255,255,255,${a})`;
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`;
} }
function applySingleColor(color: string) { function applySingleColor(color: string) {
const alpha = (settings as any).singleAlpha ?? 100; const alpha = (settings as any).singleAlpha ?? 100;
const rgba = rgbaFromHexAndAlpha(color, alpha); const rgba = rgbaFromHexAndAlpha(color, alpha);
document.documentElement.style.setProperty('--cl-lyrics-color', rgba); document.documentElement.style.setProperty("--cl-lyrics-color", rgba);
document.documentElement.style.setProperty('--cl-glow1', rgba); document.documentElement.style.setProperty("--cl-glow1", rgba);
document.documentElement.style.setProperty('--cl-glow2', rgba); document.documentElement.style.setProperty("--cl-glow2", rgba);
document.documentElement.style.removeProperty('--cl-grad-start'); document.documentElement.style.removeProperty("--cl-grad-start");
document.documentElement.style.removeProperty('--cl-grad-end'); document.documentElement.style.removeProperty("--cl-grad-end");
document.documentElement.style.removeProperty('--cl-grad-angle'); document.documentElement.style.removeProperty("--cl-grad-angle");
document.body.classList.remove('colorama-gradient'); document.body.classList.remove("colorama-gradient");
document.body.classList.add('colorama-single'); document.body.classList.add("colorama-single");
} }
function applyGradient(start: string, end: string, angle: number) { function applyGradient(start: string, end: string, angle: number) {
const startAlpha = (settings as any).gradientStartAlpha ?? 100; const startAlpha = (settings as any).gradientStartAlpha ?? 100;
const endAlpha = (settings as any).gradientEndAlpha ?? 100; const endAlpha = (settings as any).gradientEndAlpha ?? 100;
const startRgba = rgbaFromHexAndAlpha(start, startAlpha); const startRgba = rgbaFromHexAndAlpha(start, startAlpha);
const endRgba = rgbaFromHexAndAlpha(end, endAlpha); const endRgba = rgbaFromHexAndAlpha(end, endAlpha);
document.documentElement.style.setProperty('--cl-grad-start', startRgba); document.documentElement.style.setProperty("--cl-grad-start", startRgba);
document.documentElement.style.setProperty('--cl-grad-end', endRgba); document.documentElement.style.setProperty("--cl-grad-end", endRgba);
document.documentElement.style.setProperty('--cl-grad-angle', `${angle}deg`); document.documentElement.style.setProperty("--cl-grad-angle", `${angle}deg`);
document.documentElement.style.setProperty('--cl-glow1', startRgba); document.documentElement.style.setProperty("--cl-glow1", startRgba);
document.documentElement.style.setProperty('--cl-glow2', endRgba); document.documentElement.style.setProperty("--cl-glow2", endRgba);
document.body.classList.remove('colorama-single'); document.body.classList.remove("colorama-single");
document.body.classList.add('colorama-gradient'); document.body.classList.add("colorama-gradient");
} }
function resetModeClasses(): void { function resetModeClasses(): void {
document.body.classList.remove('colorama-single', 'colorama-gradient'); document.body.classList.remove("colorama-single", "colorama-gradient");
} }
async function applyCoverColors(gradient: boolean) { async function applyCoverColors(gradient: boolean) {
const img = await getCoverArtElement(); const img = await getCoverArtElement();
if (!img) return; if (!img) return;
const colors = getDominantColorsFromImage(img, gradient ? 2 : 1); const colors = getDominantColorsFromImage(img, gradient ? 2 : 1);
if (gradient) { if (gradient) {
const start = colors[0] ?? settings.gradientStart; const start = colors[0] ?? settings.gradientStart;
const end = colors[1] ?? settings.gradientEnd; const end = colors[1] ?? settings.gradientEnd;
applyGradient(start, end, settings.gradientAngle); applyGradient(start, end, settings.gradientAngle);
} else { } else {
const color = colors[0] ?? settings.singleColor; const color = colors[0] ?? settings.singleColor;
applySingleColor(color); applySingleColor(color);
} }
} }
function applyColoramaLyrics(): void { function applyColoramaLyrics(): void {
if (!settings.enabled) { if (!settings.enabled) {
document.body.classList.remove('colorama-single', 'colorama-gradient'); document.body.classList.remove("colorama-single", "colorama-gradient");
return; return;
} }
// Toggle only-active-line mode class // Toggle only-active-line mode class
if (settings.excludeInactive) { if (settings.excludeInactive) {
document.body.classList.add('colorama-only-active'); document.body.classList.add("colorama-only-active");
} else { } else {
document.body.classList.remove('colorama-only-active'); document.body.classList.remove("colorama-only-active");
} }
resetModeClasses(); resetModeClasses();
switch (settings.mode) { switch (settings.mode) {
case "single": case "single":
applySingleColor(settings.singleColor); applySingleColor(settings.singleColor);
break; break;
case "gradient-experimental": case "gradient-experimental":
applyGradient(settings.gradientStart, settings.gradientEnd, settings.gradientAngle); applyGradient(
break; settings.gradientStart,
case "cover": settings.gradientEnd,
applyCoverColors(false); settings.gradientAngle,
break; );
case "cover-gradient": break;
applyCoverColors(true); case "cover":
break; applyCoverColors(false);
} break;
case "cover-gradient":
applyCoverColors(true);
break;
}
} }
(window as any).applyColoramaLyrics = applyColoramaLyrics; (window as any).applyColoramaLyrics = applyColoramaLyrics;
// Re-apply on track changes (for auto modes) // Re-apply on track changes (for auto modes)
function observeTrackChanges(): void { function observeTrackChanges(): void {
let lastTrackId: string | null = null; let lastTrackId: string | null = null;
const check = () => { const check = () => {
const currentTrackId = PlayState.playbackContext?.actualProductId; const currentTrackId = PlayState.playbackContext?.actualProductId;
if (currentTrackId && currentTrackId !== lastTrackId) { if (currentTrackId && currentTrackId !== lastTrackId) {
lastTrackId = currentTrackId; lastTrackId = currentTrackId;
if (settings.mode === 'cover' || settings.mode === 'cover-gradient') { if (settings.mode === "cover" || settings.mode === "cover-gradient") {
setTimeout(() => applyColoramaLyrics(), 200); setTimeout(() => applyColoramaLyrics(), 200);
} }
} }
}; };
const interval = setInterval(check, 500); const interval = setInterval(check, 500);
unloads.add(() => clearInterval(interval)); unloads.add(() => clearInterval(interval));
check(); check();
} }
// Initial apply and observers // Initial apply and observers
@@ -196,26 +210,26 @@ observeTrackChanges();
// for some reason, re-apply after Radiant updates its styles/backgrounds // for some reason, re-apply after Radiant updates its styles/backgrounds
function hookRadiantUpdates(): void { function hookRadiantUpdates(): void {
const w = window as any; const w = window as any;
const wrap = (name: string) => { const wrap = (name: string) => {
const fn = w[name]; const fn = w[name];
if (typeof fn === 'function' && !fn.__coloramaPatched) { if (typeof fn === "function" && !fn.__coloramaPatched) {
const orig = fn.bind(w); const orig = fn.bind(w);
const patched = (...args: unknown[]) => { const patched = (...args: unknown[]) => {
const result = orig(...args); const result = orig(...args);
try { applyColoramaLyrics(); } catch {} try {
return result; applyColoramaLyrics();
}; } catch {}
(patched as any).__coloramaPatched = true; return result;
w[name] = patched; };
} (patched as any).__coloramaPatched = true;
}; w[name] = patched;
wrap('updateRadiantLyricsStyles'); }
wrap('updateRadiantLyricsNowPlayingBackground'); };
wrap('updateRadiantLyricsGlobalBackground'); wrap("updateRadiantLyricsStyles");
wrap('updateRadiantLyricsTextGlow'); wrap("updateRadiantLyricsNowPlayingBackground");
wrap("updateRadiantLyricsGlobalBackground");
wrap("updateRadiantLyricsTextGlow");
} }
setTimeout(() => hookRadiantUpdates(), 0); setTimeout(() => hookRadiantUpdates(), 0);
+95 -51
View File
@@ -1,94 +1,138 @@
/* Variables used by Colorama Lyrics */ /* Variables used by Colorama Lyrics */
:root { :root {
--cl-lyrics-color: #ffffff; --cl-lyrics-color: #ffffff;
--cl-grad-start: #ffffff; --cl-grad-start: #ffffff;
--cl-grad-end: #88aaff; --cl-grad-end: #88aaff;
--cl-grad-angle: 0deg; --cl-grad-angle: 0deg;
--cl-glow1: #ffffff; --cl-glow1: #ffffff;
--cl-glow2: #ffffff; --cl-glow2: #ffffff;
} }
/* Apply solid color to lyrics text */ /* Apply solid color to lyrics text */
.colorama-single [class*="_lyricsText"] > div > span, .colorama-single [class*="_lyricsText"] > div > span,
.colorama-single [class*="_lyricsText"] > div > span[data-current="true"], .colorama-single [class*="_lyricsText"] > div > span[data-current="true"],
.colorama-single [class^="_lyricsContainer"] > div > div > span, .colorama-single [class^="_lyricsContainer"] > div > div > span,
.colorama-single [class^="_lyricsContainer"] > div > div > span[data-current="true"] { .colorama-single
color: var(--cl-lyrics-color) !important; [class^="_lyricsContainer"]
background: none !important; > div
-webkit-background-clip: initial !important; > div
background-clip: initial !important; > span[data-current="true"] {
-webkit-text-fill-color: initial !important; color: var(--cl-lyrics-color) !important;
background: none !important;
-webkit-background-clip: initial !important;
background-clip: initial !important;
-webkit-text-fill-color: initial !important;
} }
/* Apply gradient to lyrics text */ /* Apply gradient to lyrics text */
.colorama-gradient [class*="_lyricsText"] > div > span, .colorama-gradient [class*="_lyricsText"] > div > span,
.colorama-gradient [class*="_lyricsText"] > div > span[data-current="true"], .colorama-gradient [class*="_lyricsText"] > div > span[data-current="true"],
.colorama-gradient [class^="_lyricsContainer"] > div > div > span, .colorama-gradient [class^="_lyricsContainer"] > div > div > span,
.colorama-gradient [class^="_lyricsContainer"] > div > div > span[data-current="true"] { .colorama-gradient
background: linear-gradient(var(--cl-grad-angle), var(--cl-grad-start), var(--cl-grad-end)) !important; [class^="_lyricsContainer"]
-webkit-background-clip: text !important; > div
background-clip: text !important; > div
color: transparent !important; > span[data-current="true"] {
-webkit-text-fill-color: transparent !important; background: linear-gradient(
var(--cl-grad-angle),
var(--cl-grad-start),
var(--cl-grad-end)
) !important;
-webkit-background-clip: text !important;
background-clip: text !important;
color: transparent !important;
-webkit-text-fill-color: transparent !important;
} }
/* Only-active: apply container class only on the active line via JS */ /* Only-active: apply container class only on the active line via JS */
/* Slight emphasis on current line (uniform to single mode) */ /* Slight emphasis on current line (uniform to single mode) */
.colorama-gradient [class*="_lyricsText"] > div > span[data-current="true"], .colorama-gradient [class*="_lyricsText"] > div > span[data-current="true"],
.colorama-gradient [class^="_lyricsContainer"] > div > div > span[data-current="true"] { .colorama-gradient
filter: brightness(1.1) !important; [class^="_lyricsContainer"]
> div
> div
> span[data-current="true"] {
filter: brightness(1.1) !important;
} }
/* Keep song title color unchanged; its glow is controlled in Radiant CSS */ /* Keep song title color unchanged; its glow is controlled in Radiant CSS */
/* Color Radiant glow shadows using Colorama colors (respect RL sizes) */ /* Color Radiant glow shadows using Colorama colors (respect RL sizes) */
.colorama-single [class*="_lyricsText"] > div > span[data-current="true"], .colorama-single [class*="_lyricsText"] > div > span[data-current="true"],
.colorama-single [class^="_lyricsContainer"] > div > div > span[data-current="true"], .colorama-single
[class^="_lyricsContainer"]
> div
> div
> span[data-current="true"],
.colorama-gradient [class*="_lyricsText"] > div > span[data-current="true"], .colorama-gradient [class*="_lyricsText"] > div > span[data-current="true"],
.colorama-gradient [class^="_lyricsContainer"] > div > div > span[data-current="true"], .colorama-gradient
.colorama-gradient [class^="_lyricsContainer"] > div > div > span[data-current="true"] { [class^="_lyricsContainer"]
text-shadow: 0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #ffffff), 0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #ffffff) !important; > div
> div
> span[data-current="true"],
.colorama-gradient
[class^="_lyricsContainer"]
> div
> div
> span[data-current="true"] {
text-shadow:
0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #ffffff),
0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #ffffff) !important;
} }
/* Hover: force glow color to match Colorama settings for inactive lines */ /* Hover: force glow color to match Colorama settings for inactive lines */
.colorama-single [class*="_lyricsText"] > div > span:hover, .colorama-single [class*="_lyricsText"] > div > span:hover,
.colorama-single [class^="_lyricsContainer"] > div > div > span:hover { .colorama-single [class^="_lyricsContainer"] > div > div > span:hover {
color: var(--cl-lyrics-color) !important; color: var(--cl-lyrics-color) !important;
text-shadow: 0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #ffffff), 0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #ffffff) !important; text-shadow:
0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #ffffff),
0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #ffffff) !important;
} }
.colorama-gradient [class*="_lyricsText"] > div > span:hover, .colorama-gradient [class*="_lyricsText"] > div > span:hover,
.colorama-gradient [class^="_lyricsContainer"] > div > div > span:hover { .colorama-gradient [class^="_lyricsContainer"] > div > div > span:hover {
background: linear-gradient(var(--cl-grad-angle), var(--cl-grad-start), var(--cl-grad-end)) !important; background: linear-gradient(
-webkit-background-clip: text !important; var(--cl-grad-angle),
background-clip: text !important; var(--cl-grad-start),
color: transparent !important; var(--cl-grad-end)
-webkit-text-fill-color: transparent !important; ) !important;
/* Do not increase glow strength on hover for gradients */ -webkit-background-clip: text !important;
background-clip: text !important;
color: transparent !important;
-webkit-text-fill-color: transparent !important;
/* Do not increase glow strength on hover for gradients */
} }
/* Only color active line mode */ /* Only color active line mode */
body.colorama-only-active.colorama-single [class*="_lyricsText"] > div > span:not([data-current="true"]), body.colorama-only-active.colorama-single [class*="_lyricsText"]
body.colorama-only-active.colorama-gradient [class*="_lyricsText"] > div > span:not([data-current="true"]) { > div
/* Match Radiant inactive styling */ > span:not([data-current="true"]),
color: rgba(128, 128, 128, 0.4) !important; body.colorama-only-active.colorama-gradient
background: none !important; [class*="_lyricsText"]
-webkit-background-clip: initial !important; > div
background-clip: initial !important; > span:not([data-current="true"]) {
-webkit-text-fill-color: initial !important; /* Match Radiant inactive styling */
text-shadow: initial !important; color: rgba(128, 128, 128, 0.4) !important;
background: none !important;
-webkit-background-clip: initial !important;
background-clip: initial !important;
-webkit-text-fill-color: initial !important;
text-shadow: initial !important;
} }
/* In only-active mode, keep TIDAL defaults even on hover for inactive lines */ /* In only-active mode, keep TIDAL defaults even on hover for inactive lines */
body.colorama-only-active.colorama-single [class*="_lyricsText"] > div > span:not([data-current="true"]):hover, body.colorama-only-active.colorama-single [class*="_lyricsText"]
body.colorama-only-active.colorama-gradient [class*="_lyricsText"] > div > span:not([data-current="true"]):hover { > div
color: lightgray !important; > span:not([data-current="true"]):hover,
background: none !important; body.colorama-only-active.colorama-gradient
-webkit-background-clip: initial !important; [class*="_lyricsText"]
background-clip: initial !important; > div
-webkit-text-fill-color: initial !important; > span:not([data-current="true"]):hover {
text-shadow: initial !important; color: lightgray !important;
background: none !important;
-webkit-background-clip: initial !important;
background-clip: initial !important;
-webkit-text-fill-color: initial !important;
text-shadow: initial !important;
} }
+82 -75
View File
@@ -13,95 +13,102 @@ export const unloads = new Set<LunaUnload>();
const lyricsStyleTag = new StyleTag("Copy-Lyrics", unloads, unlockSelection); const lyricsStyleTag = new StyleTag("Copy-Lyrics", unloads, unlockSelection);
function SetClipboard(text: string): void { function SetClipboard(text: string): void {
const textarea = document.createElement("textarea"); const textarea = document.createElement("textarea");
textarea.value = text; textarea.value = text;
textarea.style.position = "fixed"; // Avoid scrolling to bottom textarea.style.position = "fixed"; // Avoid scrolling to bottom
document.body.appendChild(textarea); document.body.appendChild(textarea);
textarea.select(); textarea.select();
try { try {
const success = document.execCommand("copy"); const success = document.execCommand("copy");
if (!success) throw new Error("Failed to copy text."); if (!success) throw new Error("Failed to copy text.");
} catch (err) { } catch (err) {
trace.msg.err(err instanceof Error ? err.message : String(err)); trace.msg.err(err instanceof Error ? err.message : String(err));
} finally { } finally {
document.body.removeChild(textarea); document.body.removeChild(textarea);
} }
} }
let isSelecting = false; let isSelecting = false;
const onMouseDown = function (): void { const onMouseDown = function (): void {
isSelecting = true; isSelecting = true;
}; };
const onMouseUp = function (event: MouseEvent): void { const onMouseUp = function (event: MouseEvent): void {
if (isSelecting) { if (isSelecting) {
const selection = window.getSelection(); const selection = window.getSelection();
if (selection && selection.toString().length > 0) { if (selection && selection.toString().length > 0) {
const selectedSpans: HTMLSpanElement[] = []; const selectedSpans: HTMLSpanElement[] = [];
const range = selection.getRangeAt(0); const range = selection.getRangeAt(0);
let container = range.commonAncestorContainer; let container = range.commonAncestorContainer;
// If the container is NOT an element and a document, adjust it. // If the container is NOT an element and a document, adjust it.
if ( if (
container.nodeType !== Node.ELEMENT_NODE && container.nodeType !== Node.ELEMENT_NODE &&
container.nodeType !== Node.DOCUMENT_NODE container.nodeType !== Node.DOCUMENT_NODE
) { ) {
// Get the parent element if it's a text node // Get the parent element if it's a text node
const parentElement = container.parentElement; const parentElement = container.parentElement;
if (parentElement && parentElement.hasAttribute("data-current")) { if (parentElement && parentElement.hasAttribute("data-current")) {
let text_ = selection.toString().trim(); let text_ = selection.toString().trim();
SetClipboard(text_); SetClipboard(text_);
trace.msg.log("Copied to clipboard!"); trace.msg.log("Copied to clipboard!");
return; return;
} }
} }
// Get all the spans inside the container. // Get all the spans inside the container.
const spans = (container as Element).getElementsByTagName("span"); const spans = (container as Element).getElementsByTagName("span");
for (let span of spans) { for (let span of spans) {
if (selection.containsNode(span, true)) { if (selection.containsNode(span, true)) {
selectedSpans.push(span as HTMLSpanElement); selectedSpans.push(span as HTMLSpanElement);
} }
} }
// Concat the text of the selected spans. // Concat the text of the selected spans.
let hasCorrectAttribute = false; let hasCorrectAttribute = false;
let text = ""; let text = "";
selectedSpans.forEach((span) => { selectedSpans.forEach((span) => {
if (span.hasAttribute("data-current")) { if (span.hasAttribute("data-current")) {
hasCorrectAttribute = true; hasCorrectAttribute = true;
text += span.textContent + "\n"; text += span.textContent + "\n";
if ([...span.classList].some((className) => className.startsWith("endOfStanza--"))) { if (
text += "\n"; [...span.classList].some((className) =>
} className.startsWith("endOfStanza--"),
} )
}); ) {
text += "\n";
}
}
});
text = text.trim(); text = text.trim();
if (hasCorrectAttribute) { if (hasCorrectAttribute) {
SetClipboard(text); SetClipboard(text);
trace.msg.log("Copied to clipboard!"); trace.msg.log("Copied to clipboard!");
selection.removeAllRanges(); selection.removeAllRanges();
} }
} }
isSelecting = false; isSelecting = false;
} }
}; };
const onClickHooked = function (event: MouseEvent): boolean | void { const onClickHooked = function (event: MouseEvent): boolean | void {
if (!isSelecting) return; if (!isSelecting) return;
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
if (target.tagName.toLowerCase() === "span" && target.hasAttribute("data-current")) { if (
// Prevent default behavior and stop event propagation target.tagName.toLowerCase() === "span" &&
event.preventDefault(); target.hasAttribute("data-current")
event.stopPropagation(); ) {
event.stopImmediatePropagation(); // Prevent default behavior and stop event propagation
return false; event.preventDefault();
} event.stopPropagation();
event.stopImmediatePropagation();
return false;
}
}; };
// Add event listener with capture phase to intercept events before they reach other handlers // Add event listener with capture phase to intercept events before they reach other handlers
@@ -111,8 +118,8 @@ document.addEventListener("mouseup", onMouseUp);
// Add cleanup to unloads // Add cleanup to unloads
unloads.add(() => { unloads.add(() => {
// Remove event listeners // Remove event listeners
document.removeEventListener("click", onClickHooked, true); document.removeEventListener("click", onClickHooked, true);
document.removeEventListener("mousedown", onMouseDown); document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("mouseup", onMouseUp); document.removeEventListener("mouseup", onMouseUp);
}); });
+5 -5
View File
@@ -1,9 +1,9 @@
[class^="_lyricsText"]>div>span { [class^="_lyricsText"] > div > span {
user-select: text; user-select: text;
cursor: text; cursor: text;
} }
::selection { ::selection {
background: rgb(72, 0, 60); background: rgb(72, 0, 60);
color: rgb(255, 255, 255); color: rgb(255, 255, 255);
} }
+1 -1
View File
@@ -9,7 +9,7 @@ export const settings = await ReactiveStore.getPluginStorage("ElementHider", {
className: string; className: string;
textContent: string; textContent: string;
timestamp: number; timestamp: number;
}> }>,
}); });
export const Settings = () => { export const Settings = () => {
+170 -108
View File
@@ -32,7 +32,7 @@ function generateElementSelector(element: HTMLElement): string {
} }
// Priority 2: data-test attribute (very specific for Tidal <3) // Priority 2: data-test attribute (very specific for Tidal <3)
const dataTest = element.getAttribute('data-test'); const dataTest = element.getAttribute("data-test");
if (dataTest) { if (dataTest) {
return `[data-test="${dataTest}"]`; return `[data-test="${dataTest}"]`;
} }
@@ -41,28 +41,43 @@ function generateElementSelector(element: HTMLElement): string {
let selector = element.tagName.toLowerCase(); let selector = element.tagName.toLowerCase();
// Get filtered classes (exclude our temporary classes) // Get filtered classes (exclude our temporary classes)
const classes = element.className ? element.className.trim().split(/\s+/).filter(cls => { const classes = element.className
return cls.length > 0 && ? element.className
!cls.startsWith('element-hider-') && .trim()
cls !== 'element-hider-target' && .split(/\s+/)
cls !== 'element-hider-hiding' && .filter((cls) => {
cls !== 'element-hider-hidden'; return (
}) : []; cls.length > 0 &&
!cls.startsWith("element-hider-") &&
cls !== "element-hider-target" &&
cls !== "element-hider-hiding" &&
cls !== "element-hider-hidden"
);
})
: [];
// Only use classes if we have them and they're not generic and dumb // Only use classes if we have them and they're not generic and dumb
if (classes.length > 0) { if (classes.length > 0) {
// Use ALL classes to be very specific // Use ALL classes to be very specific
selector += '.' + classes.join('.'); selector += "." + classes.join(".");
// Add parent context for extra specificity (for when the element is inside another element) // Add parent context for extra specificity (for when the element is inside another element)
const parent = element.parentElement; const parent = element.parentElement;
if (parent && parent.tagName !== 'BODY' && parent.tagName !== 'HTML') { if (parent && parent.tagName !== "BODY" && parent.tagName !== "HTML") {
const parentClasses = parent.className ? parent.className.trim().split(/\s+/).filter(cls => { const parentClasses = parent.className
return cls.length > 0 && !cls.startsWith('element-hider-'); ? parent.className
}) : []; .trim()
.split(/\s+/)
.filter((cls) => {
return cls.length > 0 && !cls.startsWith("element-hider-");
})
: [];
if (parentClasses.length > 0) { if (parentClasses.length > 0) {
const parentSelector = parent.tagName.toLowerCase() + '.' + parentClasses.slice(0, 2).join('.'); const parentSelector =
parent.tagName.toLowerCase() +
"." +
parentClasses.slice(0, 2).join(".");
selector = `${parentSelector} > ${selector}`; selector = `${parentSelector} > ${selector}`;
} }
} }
@@ -70,19 +85,29 @@ function generateElementSelector(element: HTMLElement): string {
// If no useful classes, use position-based selector with parent context // If no useful classes, use position-based selector with parent context
const parent = element.parentElement; const parent = element.parentElement;
if (parent) { if (parent) {
const siblings = Array.from(parent.children).filter(child => child.tagName === element.tagName); const siblings = Array.from(parent.children).filter(
(child) => child.tagName === element.tagName,
);
const index = siblings.indexOf(element); const index = siblings.indexOf(element);
if (index >= 0) { if (index >= 0) {
selector += `:nth-of-type(${index + 1})`; selector += `:nth-of-type(${index + 1})`;
// Add parent context // Add parent context
if (parent.tagName !== 'BODY' && parent.tagName !== 'HTML') { if (parent.tagName !== "BODY" && parent.tagName !== "HTML") {
const parentClasses = parent.className ? parent.className.trim().split(/\s+/).filter(cls => { const parentClasses = parent.className
return cls.length > 0 && !cls.startsWith('element-hider-'); ? parent.className
}) : []; .trim()
.split(/\s+/)
.filter((cls) => {
return cls.length > 0 && !cls.startsWith("element-hider-");
})
: [];
if (parentClasses.length > 0) { if (parentClasses.length > 0) {
const parentSelector = parent.tagName.toLowerCase() + '.' + parentClasses.slice(0, 2).join('.'); const parentSelector =
parent.tagName.toLowerCase() +
"." +
parentClasses.slice(0, 2).join(".");
selector = `${parentSelector} > ${selector}`; selector = `${parentSelector} > ${selector}`;
} }
} }
@@ -100,14 +125,14 @@ function saveHiddenElement(element: HTMLElement): void {
const elementInfo = { const elementInfo = {
selector: selector, selector: selector,
tagName: element.tagName, tagName: element.tagName,
className: element.className || '', className: element.className || "",
textContent: element.textContent?.substring(0, 100) || '', textContent: element.textContent?.substring(0, 100) || "",
timestamp: Date.now() timestamp: Date.now(),
}; };
// Check if element is already saved // Check if element is already saved
const existingIndex = settings.hiddenElements.findIndex( const existingIndex = settings.hiddenElements.findIndex(
stored => stored.selector === elementInfo.selector (stored) => stored.selector === elementInfo.selector,
); );
if (existingIndex === -1) { if (existingIndex === -1) {
@@ -122,7 +147,9 @@ function saveHiddenElement(element: HTMLElement): void {
// Remove hidden element from persistent storage (for unhiding) // Remove hidden element from persistent storage (for unhiding)
function removeSavedElement(element: HTMLElement): void { function removeSavedElement(element: HTMLElement): void {
const selector = generateElementSelector(element); const selector = generateElementSelector(element);
const index = settings.hiddenElements.findIndex(stored => stored.selector === selector); const index = settings.hiddenElements.findIndex(
(stored) => stored.selector === selector,
);
if (index !== -1) { if (index !== -1) {
settings.hiddenElements.splice(index, 1); settings.hiddenElements.splice(index, 1);
@@ -154,14 +181,18 @@ function hideElementDirectly(element: HTMLElement): void {
element.classList.add("element-hider-hidden"); element.classList.add("element-hider-hidden");
hiddenElements.add(element); hiddenElements.add(element);
hiddenElementsArray.push(element); hiddenElementsArray.push(element);
trace.log(`Hidden element: ${element.tagName}${element.className ? '.' + element.className.split(' ')[0] : ''}`); trace.log(
`Hidden element: ${element.tagName}${element.className ? "." + element.className.split(" ")[0] : ""}`,
);
} }
// Hide the target element with animation // Hide the target element with animation
function hideTargetElement(): void { function hideTargetElement(): void {
if (!targetElement) return; if (!targetElement) return;
trace.log(`Hiding with animation: ${targetElement.tagName}${targetElement.className ? '.' + targetElement.className.split(' ')[0] : ''}`); trace.log(
`Hiding with animation: ${targetElement.tagName}${targetElement.className ? "." + targetElement.className.split(" ")[0] : ""}`,
);
// Add hiding animation class // Add hiding animation class
targetElement.classList.add("element-hider-hiding"); targetElement.classList.add("element-hider-hiding");
@@ -175,7 +206,10 @@ function hideTargetElement(): void {
// Wait for animation to complete, then hide // Wait for animation to complete, then hide
setTimeout(() => { setTimeout(() => {
elementToHide.classList.add("element-hider-hidden"); elementToHide.classList.add("element-hider-hidden");
elementToHide.classList.remove("element-hider-hiding", "element-hider-target"); elementToHide.classList.remove(
"element-hider-hiding",
"element-hider-target",
);
hiddenElements.add(elementToHide); hiddenElements.add(elementToHide);
hiddenElementsArray.push(elementToHide); hiddenElementsArray.push(elementToHide);
}, 300); }, 300);
@@ -186,10 +220,12 @@ function hideTargetElement(): void {
// Unhide all elements permanently (remove from storage) // Unhide all elements permanently (remove from storage)
function unhideAllElements(): void { function unhideAllElements(): void {
trace.log(`Permanently unhiding ${settings.hiddenElements.length} saved elements`); trace.log(
`Permanently unhiding ${settings.hiddenElements.length} saved elements`,
);
// Show all currently hidden elements // Show all currently hidden elements
hiddenElementsArray.forEach(element => { hiddenElementsArray.forEach((element) => {
if (document.body.contains(element)) { if (document.body.contains(element)) {
element.classList.remove("element-hider-hidden", "element-hider-hiding"); element.classList.remove("element-hider-hidden", "element-hider-hiding");
} }
@@ -205,7 +241,9 @@ function unhideAllElements(): void {
function processAllElements(): void { function processAllElements(): void {
if (settings.hiddenElements.length === 0) return; if (settings.hiddenElements.length === 0) return;
trace.log(`Scanning document for ${settings.hiddenElements.length} stored selectors`); trace.log(
`Scanning document for ${settings.hiddenElements.length} stored selectors`,
);
let hiddenCount = 0; let hiddenCount = 0;
// Use querySelectorAll for each stored selector with validation // Use querySelectorAll for each stored selector with validation
@@ -217,7 +255,9 @@ function processAllElements(): void {
// Limit to prevent over-hiding (safety check) // Limit to prevent over-hiding (safety check)
if (elements.length > 10) { if (elements.length > 10) {
trace.warn(`Selector too broad (${elements.length} matches), skipping: ${storedElement.selector}`); trace.warn(
`Selector too broad (${elements.length} matches), skipping: ${storedElement.selector}`,
);
return; return;
} }
@@ -226,7 +266,9 @@ function processAllElements(): void {
if (!hiddenElements.has(htmlElement)) { if (!hiddenElements.has(htmlElement)) {
hideElementDirectly(htmlElement); hideElementDirectly(htmlElement);
hiddenCount++; hiddenCount++;
trace.log(`Hid element ${elemIndex + 1}/${elements.length} for selector ${index + 1}`); trace.log(
`Hid element ${elemIndex + 1}/${elements.length} for selector ${index + 1}`,
);
} }
}); });
} catch (error) { } catch (error) {
@@ -241,7 +283,7 @@ function processAllElements(): void {
// Process new elements that are added to the DOM // Process new elements that are added to the DOM
function processNewElements(addedNodes: NodeList): void { function processNewElements(addedNodes: NodeList): void {
addedNodes.forEach(node => { addedNodes.forEach((node) => {
if (node.nodeType !== Node.ELEMENT_NODE) return; if (node.nodeType !== Node.ELEMENT_NODE) return;
const element = node as HTMLElement; const element = node as HTMLElement;
@@ -252,8 +294,8 @@ function processNewElements(addedNodes: NodeList): void {
} }
// Check all descendant elements // Check all descendant elements
const descendants = element.querySelectorAll('*'); const descendants = element.querySelectorAll("*");
descendants.forEach(descendant => { descendants.forEach((descendant) => {
if (matchesStoredSelector(descendant as HTMLElement)) { if (matchesStoredSelector(descendant as HTMLElement)) {
hideElementDirectly(descendant as HTMLElement); hideElementDirectly(descendant as HTMLElement);
} }
@@ -267,7 +309,7 @@ function setupElementObserver(): void {
elementObserver = new MutationObserver((mutations) => { elementObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => { mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
processNewElements(mutation.addedNodes); processNewElements(mutation.addedNodes);
} }
}); });
@@ -275,7 +317,7 @@ function setupElementObserver(): void {
elementObserver.observe(document.body, { elementObserver.observe(document.body, {
childList: true, childList: true,
subtree: true subtree: true,
}); });
trace.log(`Set up reactive element observer`); trace.log(`Set up reactive element observer`);
@@ -297,19 +339,19 @@ function setupElementObserver(): void {
// Handle highlighting target element // Handle highlighting target element
function highlightElement(element: HTMLElement): void { function highlightElement(element: HTMLElement): void {
// Remove previous highlights // Remove previous highlights
document.querySelectorAll('.element-hider-target').forEach(el => { document.querySelectorAll(".element-hider-target").forEach((el) => {
el.classList.remove('element-hider-target'); el.classList.remove("element-hider-target");
}); });
// Highlight current element // Highlight current element
element.classList.add('element-hider-target'); element.classList.add("element-hider-target");
targetElement = element; targetElement = element;
} }
// Remove highlight // Remove highlight
function removeHighlight(): void { function removeHighlight(): void {
if (targetElement) { if (targetElement) {
targetElement.classList.remove('element-hider-target'); targetElement.classList.remove("element-hider-target");
targetElement = null; targetElement = null;
} }
} }
@@ -321,59 +363,71 @@ let contextMenuTimeout: number | null = null;
let waitingForBuiltInMenu = false; let waitingForBuiltInMenu = false;
// Listen for right-click events to capture the target for context menu // Listen for right-click events to capture the target for context menu
document.addEventListener('contextmenu', (event: MouseEvent) => { document.addEventListener(
const target = event.target as HTMLElement; "contextmenu",
(event: MouseEvent) => {
const target = event.target as HTMLElement;
// Don't interfere with native context menus on inputs, textareas, etc. // Don't interfere with native context menus on inputs, textareas, etc.
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) { if (
currentContextElement = null; target.tagName === "INPUT" ||
return; target.tagName === "TEXTAREA" ||
} target.isContentEditable
) {
// Don't show menu on our own custom menu currentContextElement = null;
if (target.closest(".element-hider-custom-menu")) { return;
return;
}
// Close any existing custom menu
closeCustomMenu();
// Store the right-clicked element for context menu
currentContextElement = target;
waitingForBuiltInMenu = true;
// Store event coordinates for potential custom menu
const eventX = event.clientX;
const eventY = event.clientY;
// Prevent default immediately if we plan to handle it
event.preventDefault();
// Wait to see if the built-in context menu appears
contextMenuTimeout = window.setTimeout(() => {
// If we're still waiting and no built-in menu appeared, show our custom menu
if (waitingForBuiltInMenu && currentContextElement) {
showCustomMenu(eventX, eventY);
} }
waitingForBuiltInMenu = false;
}, 150); // Wait 150ms for built-in menu
// Don't prevent default initially - let Luna try to handle the context menu // Don't show menu on our own custom menu
}, true); if (target.closest(".element-hider-custom-menu")) {
return;
}
// Close any existing custom menu
closeCustomMenu();
// Store the right-clicked element for context menu
currentContextElement = target;
waitingForBuiltInMenu = true;
// Store event coordinates for potential custom menu
const eventX = event.clientX;
const eventY = event.clientY;
// Prevent default immediately if we plan to handle it
event.preventDefault();
// Wait to see if the built-in context menu appears
contextMenuTimeout = window.setTimeout(() => {
// If we're still waiting and no built-in menu appeared, show our custom menu
if (waitingForBuiltInMenu && currentContextElement) {
showCustomMenu(eventX, eventY);
}
waitingForBuiltInMenu = false;
}, 150); // Wait 150ms for built-in menu
// Don't prevent default initially - let Luna try to handle the context menu
},
true,
);
// Listen for clicks to close custom menu // Listen for clicks to close custom menu
document.addEventListener('click', (event: MouseEvent) => { document.addEventListener(
const target = event.target as HTMLElement; "click",
(event: MouseEvent) => {
const target = event.target as HTMLElement;
// If clicking outside our custom menu, close it // If clicking outside our custom menu, close it
if (customMenu && !target.closest(".element-hider-custom-menu")) { if (customMenu && !target.closest(".element-hider-custom-menu")) {
closeCustomMenu(); closeCustomMenu();
removeHighlight(); removeHighlight();
} }
}, true); },
true,
);
// Handle escape key to close custom menu and remove highlights // Handle escape key to close custom menu and remove highlights
document.addEventListener('keydown', (event: KeyboardEvent) => { document.addEventListener("keydown", (event: KeyboardEvent) => {
if (event.key === "Escape") { if (event.key === "Escape") {
if (customMenu) { if (customMenu) {
closeCustomMenu(); closeCustomMenu();
@@ -464,8 +518,15 @@ const contextMenuObserver = new MutationObserver((mutations) => {
const element = node as HTMLElement; const element = node as HTMLElement;
// Look for Tidal's context menu // Look for Tidal's context menu
if (element.matches('[data-test="contextmenu"]') || element.querySelector('[data-test="contextmenu"]')) { if (
const contextMenu = element.matches('[data-test="contextmenu"]') ? element : element.querySelector('[data-test="contextmenu"]') as HTMLElement; element.matches('[data-test="contextmenu"]') ||
element.querySelector('[data-test="contextmenu"]')
) {
const contextMenu = element.matches('[data-test="contextmenu"]')
? element
: (element.querySelector(
'[data-test="contextmenu"]',
) as HTMLElement);
if (contextMenu && currentContextElement && waitingForBuiltInMenu) { if (contextMenu && currentContextElement && waitingForBuiltInMenu) {
// Built-in menu appeared, cancel custom menu timeout // Built-in menu appeared, cancel custom menu timeout
@@ -485,8 +546,8 @@ const contextMenuObserver = new MutationObserver((mutations) => {
// Add our options to the existing context menu // Add our options to the existing context menu
function addElementHiderOptions(contextMenu: HTMLElement): void { function addElementHiderOptions(contextMenu: HTMLElement): void {
// Create hide element button // Create hide element button
const hideButton = document.createElement('button'); const hideButton = document.createElement("button");
hideButton.className = 'element-hider-menu-item'; hideButton.className = "element-hider-menu-item";
hideButton.style.cssText = ` hideButton.style.cssText = `
display: flex; display: flex;
align-items: center; align-items: center;
@@ -503,7 +564,7 @@ function addElementHiderOptions(contextMenu: HTMLElement): void {
`; `;
hideButton.innerHTML = `Hide This Element`; hideButton.innerHTML = `Hide This Element`;
hideButton.addEventListener('click', () => { hideButton.addEventListener("click", () => {
if (currentContextElement) { if (currentContextElement) {
targetElement = currentContextElement; targetElement = currentContextElement;
hideTargetElement(); hideTargetElement();
@@ -511,37 +572,38 @@ function addElementHiderOptions(contextMenu: HTMLElement): void {
}); });
// Add hover effects for highlighting // Add hover effects for highlighting
hideButton.addEventListener('mouseenter', () => { hideButton.addEventListener("mouseenter", () => {
hideButton.style.background = 'var(--wave-color-background-hover, #3a3a3a)'; hideButton.style.background = "var(--wave-color-background-hover, #3a3a3a)";
if (currentContextElement) { if (currentContextElement) {
highlightElement(currentContextElement); highlightElement(currentContextElement);
} }
}); });
hideButton.addEventListener('mouseleave', () => { hideButton.addEventListener("mouseleave", () => {
hideButton.style.background = 'transparent'; hideButton.style.background = "transparent";
removeHighlight(); removeHighlight();
}); });
// Create unhide all button // Create unhide all button
const unhideAllButton = document.createElement('button'); const unhideAllButton = document.createElement("button");
unhideAllButton.className = 'element-hider-menu-item'; unhideAllButton.className = "element-hider-menu-item";
unhideAllButton.style.cssText = hideButton.style.cssText; unhideAllButton.style.cssText = hideButton.style.cssText;
unhideAllButton.innerHTML = `Unhide All Elements (${hiddenElementsArray.length})`; unhideAllButton.innerHTML = `Unhide All Elements (${hiddenElementsArray.length})`;
unhideAllButton.addEventListener('click', unhideAllElements); unhideAllButton.addEventListener("click", unhideAllElements);
// Add hover effects for unhide all button // Add hover effects for unhide all button
unhideAllButton.addEventListener('mouseenter', () => { unhideAllButton.addEventListener("mouseenter", () => {
unhideAllButton.style.background = 'var(--wave-color-background-hover, #3a3a3a)'; unhideAllButton.style.background =
"var(--wave-color-background-hover, #3a3a3a)";
}); });
unhideAllButton.addEventListener('mouseleave', () => { unhideAllButton.addEventListener("mouseleave", () => {
unhideAllButton.style.background = 'transparent'; unhideAllButton.style.background = "transparent";
}); });
// Add a separator if the menu has other items // Add a separator if the menu has other items
if (contextMenu.children.length > 0) { if (contextMenu.children.length > 0) {
const separator = document.createElement('div'); const separator = document.createElement("div");
separator.style.cssText = ` separator.style.cssText = `
height: 1px; height: 1px;
background: var(--wave-color-border, #444); background: var(--wave-color-border, #444);
@@ -558,10 +620,10 @@ function addElementHiderOptions(contextMenu: HTMLElement): void {
// Start observing for context menus // Start observing for context menus
contextMenuObserver.observe(document.body, { contextMenuObserver.observe(document.body, {
childList: true, childList: true,
subtree: true subtree: true,
}); });
// Initialize plugin // Initialize plugin
function initializePlugin() { function initializePlugin() {
trace.log("Initializing plugin..."); trace.log("Initializing plugin...");
@@ -578,8 +640,8 @@ function initializePlugin() {
} }
// Run initialization when DOM is ready // Run initialization when DOM is ready
if (document.readyState === 'loading') { if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', initializePlugin); document.addEventListener("DOMContentLoaded", initializePlugin);
} else { } else {
initializePlugin(); initializePlugin();
} }
+36 -34
View File
@@ -2,62 +2,64 @@
/* Custom context menu for elements without built-in menu */ /* Custom context menu for elements without built-in menu */
.element-hider-custom-menu { .element-hider-custom-menu {
position: fixed; position: fixed;
background: var(--wave-color-background-elevated, #2a2a2a); background: var(--wave-color-background-elevated, #2a2a2a);
border: 1px solid var(--wave-color-border, #444); border: 1px solid var(--wave-color-border, #444);
border-radius: 8px; border-radius: 8px;
padding: 8px 0; padding: 8px 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 999999; z-index: 999999;
min-width: 180px; min-width: 180px;
font-family: inherit; font-family: inherit;
font-size: 14px; font-size: 14px;
} }
.element-hider-menu-item { .element-hider-menu-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 8px 16px; padding: 8px 16px;
cursor: pointer; cursor: pointer;
color: var(--wave-color-text, #ffffff); color: var(--wave-color-text, #ffffff);
background: transparent; background: transparent;
border: none; border: none;
width: 100%; width: 100%;
text-align: left; text-align: left;
transition: background-color 0.15s ease; transition: background-color 0.15s ease;
font-family: inherit; font-family: inherit;
font-size: 14px; font-size: 14px;
} }
.element-hider-menu-item:hover { .element-hider-menu-item:hover {
background: var(--wave-color-background-hover, #3a3a3a); background: var(--wave-color-background-hover, #3a3a3a);
} }
.element-hider-menu-item:active { .element-hider-menu-item:active {
background: var(--wave-color-background-active, #4a4a4a); background: var(--wave-color-background-active, #4a4a4a);
} }
.element-hider-menu-icon { .element-hider-menu-icon {
margin-right: 8px; margin-right: 8px;
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
/* Highlight the target element */ /* Highlight the target element */
.element-hider-target { .element-hider-target {
outline: 2px solid #ff6b6b !important; outline: 2px solid #ff6b6b !important;
outline-offset: 2px !important; outline-offset: 2px !important;
box-shadow: 0 0 10px rgba(255, 107, 107, 0.6) !important; box-shadow: 0 0 10px rgba(255, 107, 107, 0.6) !important;
} }
/* Hidden elements */ /* Hidden elements */
.element-hider-hidden { .element-hider-hidden {
display: none !important; display: none !important;
} }
/* Animation for hiding */ /* Animation for hiding */
.element-hider-hiding { .element-hider-hiding {
transition: opacity 0.3s ease, transform 0.3s ease; transition:
opacity: 0; opacity 0.3s ease,
transform: scale(0.95); transform 0.3s ease;
opacity: 0;
transform: scale(0.95);
} }
+16 -5
View File
@@ -9,8 +9,11 @@ export const settings = await ReactiveStore.getPluginStorage("OLEDTheme", {
}); });
export const Settings = () => { export const Settings = () => {
const [qualityColorMatchedSeekBar, setQualityColorMatchedSeekBar] = React.useState(settings.qualityColorMatchedSeekBar); const [qualityColorMatchedSeekBar, setQualityColorMatchedSeekBar] =
const [oledFriendlyButtons, setOledFriendlyButtons] = React.useState(settings.oledFriendlyButtons); React.useState(settings.qualityColorMatchedSeekBar);
const [oledFriendlyButtons, setOledFriendlyButtons] = React.useState(
settings.oledFriendlyButtons,
);
const [lightMode, setLightMode] = React.useState(settings.lightMode); const [lightMode, setLightMode] = React.useState(settings.lightMode);
return ( return (
@@ -20,8 +23,13 @@ export const Settings = () => {
desc="Color the Seek/Progress Bar based on audio quality" desc="Color the Seek/Progress Bar based on audio quality"
checked={qualityColorMatchedSeekBar} checked={qualityColorMatchedSeekBar}
onChange={(_, checked) => { onChange={(_, checked) => {
console.log("Quality Color Matched Seek Bar:", checked ? "enabled" : "disabled"); console.log(
setQualityColorMatchedSeekBar((settings.qualityColorMatchedSeekBar = checked)); "Quality Color Matched Seek Bar:",
checked ? "enabled" : "disabled",
);
setQualityColorMatchedSeekBar(
(settings.qualityColorMatchedSeekBar = checked),
);
// Update styles immediately when setting changes // Update styles immediately when setting changes
if ((window as any).updateOLEDThemeStyles) { if ((window as any).updateOLEDThemeStyles) {
(window as any).updateOLEDThemeStyles(); (window as any).updateOLEDThemeStyles();
@@ -33,7 +41,10 @@ export const Settings = () => {
desc="Remove button styling from OLED theme to keep buttons with original Tidal appearance" desc="Remove button styling from OLED theme to keep buttons with original Tidal appearance"
checked={oledFriendlyButtons} checked={oledFriendlyButtons}
onChange={(_, checked) => { onChange={(_, checked) => {
console.log("OLED Friendly Buttons:", checked ? "enabled" : "disabled"); console.log(
"OLED Friendly Buttons:",
checked ? "enabled" : "disabled",
);
setOledFriendlyButtons((settings.oledFriendlyButtons = checked)); setOledFriendlyButtons((settings.oledFriendlyButtons = checked));
// Update styles immediately when setting changes // Update styles immediately when setting changes
if ((window as any).updateOLEDThemeStyles) { if ((window as any).updateOLEDThemeStyles) {
+135 -132
View File
@@ -7,295 +7,298 @@
*/ */
::-webkit-scrollbar { ::-webkit-scrollbar {
display: none; display: none;
} }
:root { :root {
--wave-color-solid-accent-fill: white; --wave-color-solid-accent-fill: white;
--wave-color-solid-rainbow-yellow-fill: white; --wave-color-solid-rainbow-yellow-fill: white;
--wave-color-solid-contrast-fill: white; --wave-color-solid-contrast-fill: white;
--wave-color-solid-base-brighter: black; --wave-color-solid-base-brighter: black;
--wave-text-body-medium: white !important; --wave-text-body-medium: white !important;
--track-vibrant-color: white !important; --track-vibrant-color: white !important;
--wave-color-opacity-contrast-fill-ultra-thin: #fffafa1a !important; --wave-color-opacity-contrast-fill-ultra-thin: #fffafa1a !important;
--wave-color-solid-rainbow-yellow-darkest: #fffafa1a !important; --wave-color-solid-rainbow-yellow-darkest: #fffafa1a !important;
--wave-color-solid-accent-dark: rgb(128, 128, 128); --wave-color-solid-accent-dark: rgb(128, 128, 128);
} }
/* Credits to https://github.com/surfbryce for the fonts */ /* Credits to https://github.com/surfbryce for the fonts */
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 400; font-weight: 400;
src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 500; font-weight: 500;
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 600; font-weight: 600;
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 700; font-weight: 700;
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2")
format("woff2");
} }
[class^="followingButton"], [class^="followingButton"],
[title="Unfollow"], [title="Unfollow"],
[title="Follow"], [title="Follow"],
[title="Unfollow"]>span, [title="Unfollow"] > span,
[title="Follow"]>span { [title="Follow"] > span {
background-color: var(--wave-color-solid-rainbow-yellow-fill) !important; background-color: var(--wave-color-solid-rainbow-yellow-fill) !important;
color: var(--wave-color-solid-base-brighter); color: var(--wave-color-solid-base-brighter);
} }
[class^="_wave-badge-color-max"] { [class^="_wave-badge-color-max"] {
color: black !important; color: black !important;
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 3px; border-radius: 3px;
} }
[data-test="main-layout-sidebar-wrapper"] { [data-test="main-layout-sidebar-wrapper"] {
border-right: rgb(25, 25, 25) 1px solid; border-right: rgb(25, 25, 25) 1px solid;
} }
[class^="_wave-badge"] { [class^="_wave-badge"] {
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 4px; border-radius: 4px;
color: black; color: black;
} }
[class^="_progressBarWrapper"] { [class^="_progressBarWrapper"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="_sidebarItem"]>span { [class^="_sidebarItem"] > span {
color: var(--wave-color-solid-accent-dark); color: var(--wave-color-solid-accent-dark);
} }
[data-test="main-layout-header"] { [data-test="main-layout-header"] {
border-left: 0 !important; border-left: 0 !important;
} }
[class^="_sidebarItem"]:hover span { [class^="_sidebarItem"]:hover span {
color: var(--wave-color-solid-contrast-fill); color: var(--wave-color-solid-contrast-fill);
} }
[class^="_sidebarItem"] [class^="active"]>span { [class^="_sidebarItem"] [class^="active"] > span {
color: var(--wave-color-solid-accent-dark) !important; color: var(--wave-color-solid-accent-dark) !important;
} }
[class^="_active"] { [class^="_active"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="ReactVirtualized__Grid"] { [class^="ReactVirtualized__Grid"] {
border-radius: 10px; border-radius: 10px;
margin: 5px; margin: 5px;
} }
[data-test="media-table"]>div>div>div { [data-test="media-table"] > div > div > div {
border: 1px solid rgb(25, 25, 25) !important; border: 1px solid rgb(25, 25, 25) !important;
} }
[class^="ReactVirtualized__Grid__innerScrollContainer"] { [class^="ReactVirtualized__Grid__innerScrollContainer"] {
border: none; border: none;
margin: 5px; margin: 5px;
} }
[class^="button"]>span { [class^="button"] > span {
color: black; color: black;
} }
[class^="_explicitBadge"] { [class^="_explicitBadge"] {
color: var(--wave-color-solid-accent-fill); color: var(--wave-color-solid-accent-fill);
} }
[class^="viewAllButton"] { [class^="viewAllButton"] {
border-radius: 4px; border-radius: 4px;
display: grid; display: grid;
place-items: center; place-items: center;
} }
[data-test="current-media-imagery"] { [data-test="current-media-imagery"] {
border: 0 !important; border: 0 !important;
margin: none; margin: none;
} }
[class^="_imageBorder"] { [class^="_imageBorder"] {
display: none; display: none;
} }
[class^="_headerButtons"]>button, [class^="_headerButtons"] > button,
[class^="_headerButtons"]>button>span, [class^="_headerButtons"] > button > span,
[data-test="toggle-picture-in-picture"] { [data-test="toggle-picture-in-picture"] {
background-color: var(--wave-color-solid-accent-fill) !important; background-color: var(--wave-color-solid-accent-fill) !important;
color: black; color: black;
} }
[class^="_container"]>[class^="_navigationArrows"] { [class^="_container"] > [class^="_navigationArrows"] {
color: black; color: black;
background-color: var(--wave-color-solid-accent-fill) !important; background-color: var(--wave-color-solid-accent-fill) !important;
border-radius: 4px; border-radius: 4px;
} }
[class^="_buttons"]>button>span { [class^="_buttons"] > button > span {
color: black !important; color: black !important;
} }
[class^="_container"]>button { [class^="_container"] > button {
border: 0px none; border: 0px none;
} }
[data-test="feed-sidebar"] { [data-test="feed-sidebar"] {
margin-top: 10px; margin-top: 10px;
} }
[data-test="footer-player"] { [data-test="footer-player"] {
width: calc(100% - 20px); width: calc(100% - 20px);
bottom: 10px; bottom: 10px;
left: 10px; left: 10px;
border: 1px solid rgb(25, 25, 25); border: 1px solid rgb(25, 25, 25);
border-radius: 4px !important; border-radius: 4px !important;
position: absolute !important; position: absolute !important;
} }
[class^="_tooltipContainer"]>button { [class^="_tooltipContainer"] > button {
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
color: black; color: black;
} }
[class^="_tooltipContainer"]>button:hover { [class^="_tooltipContainer"] > button:hover {
background-color: lightgray !important; background-color: lightgray !important;
} }
[class^="_tableRow"]:hover>*, [class^="_tableRow"]:hover > *,
[data-test-is-playing="true"]>* { [data-test-is-playing="true"] > * {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="_tableRow"]>*, [class^="_tableRow"] > *,
[data-test-is-playing="false"]>* { [data-test-is-playing="false"] > * {
color: lightgray !important; color: lightgray !important;
} }
[class*="coverColumn"] { [class*="coverColumn"] {
padding-left: 5px !important; padding-left: 5px !important;
} }
[class^="actionList"] { [class^="actionList"] {
background-color: transparent; background-color: transparent;
margin: 0px; margin: 0px;
border-radius: 5px; border-radius: 5px;
} }
button[data-test="request-fullscreen"], button[data-test="request-fullscreen"],
button[data-test="close-now-playing"], button[data-test="close-now-playing"],
button[data-test="play-all"], button[data-test="play-all"],
button[data-test="shuffle-all"] { button[data-test="shuffle-all"] {
color: black; color: black;
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 12px; border-radius: 12px;
} }
button[data-test="request-fullscreen"]:hover, button[data-test="request-fullscreen"]:hover,
button[data-test="close-now-playing"]:hover { button[data-test="close-now-playing"]:hover {
color: black; color: black;
background-color: lightgray !important; background-color: lightgray !important;
} }
.neptune-switch-checkbox:checked+.neptune-switch { .neptune-switch-checkbox:checked + .neptune-switch {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
[data-test="navigation-arrows"]>button { [data-test="navigation-arrows"] > button {
background-color: var(--wave-color-solid-accent-fill) !important; background-color: var(--wave-color-solid-accent-fill) !important;
color: black !important; color: black !important;
border-radius: 5px; border-radius: 5px;
} }
[data-test="navigation-arrows"]>button:disabled { [data-test="navigation-arrows"] > button:disabled {
background-color: lightgray !important; background-color: lightgray !important;
opacity: 1; opacity: 1;
} }
[data-test="main-layout-header"], [data-test="main-layout-header"],
[data-test="feed-sidebar"], [data-test="feed-sidebar"],
[data-test="stream-metadata"], [data-test="stream-metadata"],
[data-test="footer-player"] { [data-test="footer-player"] {
background-color: rgba(0, 0, 0, 0.8) !important; background-color: rgba(0, 0, 0, 0.8) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[data-wave-color=textUrl] { [data-wave-color="textUrl"] {
color: var(--wave-color-solid-accent-fill); color: var(--wave-color-solid-accent-fill);
} }
[class^="_smallHeader"] { [class^="_smallHeader"] {
margin-top: 7.5px; margin-top: 7.5px;
} }
[data-test="play-all"]>div>*, [data-test="play-all"] > div > *,
[data-test="shuffle-all"]>div>*, [data-test="shuffle-all"] > div > *,
[data-test="play-all"], [data-test="play-all"],
[data-test="shuffle-all"] { [data-test="shuffle-all"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
background-color: transparent !important; background-color: transparent !important;
} }
[class^="__NEPTUNE_PAGE"], [class^="__NEPTUNE_PAGE"],
[data-test="main"] { [data-test="main"] {
margin-top: 35px; margin-top: 35px;
} }
[data-test="button-desktop-release-notes"], [data-test="button-desktop-release-notes"],
[data-test="button-release-notes"] { [data-test="button-release-notes"] {
background-color: white; background-color: white;
} }
[data-test="button-desktop-release-notes"]:hover, [data-test="button-desktop-release-notes"]:hover,
[data-test="button-release-notes"]:hover { [data-test="button-release-notes"]:hover {
background-color: lightgray !important; background-color: lightgray !important;
transition: none !important; transition: none !important;
} }
#playQueueSidebar { #playQueueSidebar {
top: 50px !important; top: 50px !important;
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin); border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin);
margin: 2px; margin: 2px;
margin-right: -14px !important; margin-right: -14px !important;
background-color: rgba(0, 0, 0, 0.8) !important; background-color: rgba(0, 0, 0, 0.8) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
[class^="_bottomGradient"] { [class^="_bottomGradient"] {
visibility: hidden; visibility: hidden;
} }
[data-test="settings-page"] { [data-test="settings-page"] {
padding-bottom: 60px !important; padding-bottom: 60px !important;
} }
[data-test="query-suggestions"], [data-test="query-suggestions"],
[data-test="recent-searches-container"] { [data-test="recent-searches-container"] {
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
[data-test="contextmenu"] { [data-test="contextmenu"] {
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[class^="_dataContainer_"]::before { [class^="_dataContainer_"]::before {
background-image: var(--img); background-image: var(--img);
filter: blur(10px) brightness(0.4); filter: blur(10px) brightness(0.4);
} }
+97 -78
View File
@@ -1,5 +1,11 @@
import { LunaUnload, Tracer } from "@luna/core"; import { LunaUnload, Tracer } from "@luna/core";
import { StyleTag, observePromise, PlayState, Quality, type MediaItem } from "@luna/lib"; import {
StyleTag,
observePromise,
PlayState,
Quality,
type MediaItem,
} from "@luna/lib";
import { settings, Settings } from "./Settings"; import { settings, Settings } from "./Settings";
// Import CSS files directly using Luna's file:// syntax - Took me a while to figure out <3 // Import CSS files directly using Luna's file:// syntax - Took me a while to figure out <3
@@ -19,105 +25,119 @@ const themeStyleTag = new StyleTag("OLED-Theme", unloads);
// Quality color mapping // Quality color mapping
const QUALITY_COLORS = { const QUALITY_COLORS = {
MAX: "#FED330", // Max/HiFi MAX: "#FED330", // Max/HiFi
HIGH: "#31FFEE", // High HIGH: "#31FFEE", // High
LOW: "#FFFFFE" // Low LOW: "#FFFFFE", // Low
}; };
// Function to get quality color based on audio quality // Function to get quality color based on audio quality
const getQualityColor = (audioQuality: string): string => { const getQualityColor = (audioQuality: string): string => {
const quality = audioQuality?.toUpperCase(); const quality = audioQuality?.toUpperCase();
if (quality?.includes("HI_RES_LOSSLESS")) { if (quality?.includes("HI_RES_LOSSLESS")) {
return QUALITY_COLORS.MAX; return QUALITY_COLORS.MAX;
} else if (quality?.includes("LOSSLESS")) { } else if (quality?.includes("LOSSLESS")) {
return QUALITY_COLORS.HIGH; return QUALITY_COLORS.HIGH;
} else { } else {
return QUALITY_COLORS.LOW; return QUALITY_COLORS.LOW;
} }
}; };
// Function to Reset Seek Bar Color (if setting gets disabled while playing) // Function to Reset Seek Bar Color (if setting gets disabled while playing)
const resetSeekBarColor = async (): Promise<void> => { const resetSeekBarColor = async (): Promise<void> => {
try { try {
const progressBarWrapper = await observePromise<HTMLElement>(unloads, `[class^="_progressBarWrapper"]`); const progressBarWrapper = await observePromise<HTMLElement>(
if (!progressBarWrapper) return; unloads,
progressBarWrapper.style.removeProperty('color'); `[class^="_progressBarWrapper"]`,
progressBarWrapper.querySelectorAll('[class*="progress"], [class*="bar"]').forEach(el => { );
if (el instanceof HTMLElement) el.style.removeProperty('color'); if (!progressBarWrapper) return;
}); progressBarWrapper.style.removeProperty("color");
} catch (error) { progressBarWrapper
trace.msg.err(`Failed to reset seek bar color: ${error}`); .querySelectorAll('[class*="progress"], [class*="bar"]')
} .forEach((el) => {
if (el instanceof HTMLElement) el.style.removeProperty("color");
});
} catch (error) {
trace.msg.err(`Failed to reset seek bar color: ${error}`);
}
}; };
// Function to apply quality-based seek bar coloring (if enabled) // Function to apply quality-based seek bar coloring (if enabled)
const applyQualityColors = async (): Promise<void> => { const applyQualityColors = async (): Promise<void> => {
if (!settings.qualityColorMatchedSeekBar) return; if (!settings.qualityColorMatchedSeekBar) return;
try { try {
const progressBarWrapper = await observePromise<HTMLElement>(unloads, `[class^="_progressBarWrapper"]`); const progressBarWrapper = await observePromise<HTMLElement>(
if (!progressBarWrapper) return; unloads,
const audioQuality = PlayState.playbackContext?.actualAudioQuality; `[class^="_progressBarWrapper"]`,
if (!audioQuality) return; );
const qualityColor = getQualityColor(audioQuality); if (!progressBarWrapper) return;
progressBarWrapper.style.setProperty('color', qualityColor, 'important'); const audioQuality = PlayState.playbackContext?.actualAudioQuality;
progressBarWrapper.querySelectorAll('[class*="progress"], [class*="bar"]').forEach(el => { if (!audioQuality) return;
if (el instanceof HTMLElement) el.style.setProperty('color', qualityColor, 'important'); const qualityColor = getQualityColor(audioQuality);
}); progressBarWrapper.style.setProperty("color", qualityColor, "important");
//trace.msg.log(`Applied quality color ${qualityColor}`); progressBarWrapper
} catch (error) { .querySelectorAll('[class*="progress"], [class*="bar"]')
trace.msg.err(`Failed to apply quality colors: ${error}`); .forEach((el) => {
} if (el instanceof HTMLElement)
el.style.setProperty("color", qualityColor, "important");
});
//trace.msg.log(`Applied quality color ${qualityColor}`);
} catch (error) {
trace.msg.err(`Failed to apply quality colors: ${error}`);
}
}; };
// Function to monitor track changes using track ID // Function to monitor track changes using track ID
const setupQualityMonitoring = (): void => { const setupQualityMonitoring = (): void => {
let lastTrackId: string | null = null; let lastTrackId: string | null = null;
const interval = setInterval(() => { const interval = setInterval(() => {
if (!settings.qualityColorMatchedSeekBar) return; if (!settings.qualityColorMatchedSeekBar) return;
const currentTrackId = PlayState.playbackContext?.actualProductId; const currentTrackId = PlayState.playbackContext?.actualProductId;
if (currentTrackId && currentTrackId !== lastTrackId) { if (currentTrackId && currentTrackId !== lastTrackId) {
//trace.msg.log(`[OLED Theme] Track ID changed: ${lastTrackId} -> ${currentTrackId}`); //trace.msg.log(`[OLED Theme] Track ID changed: ${lastTrackId} -> ${currentTrackId}`);
lastTrackId = currentTrackId; lastTrackId = currentTrackId;
applyQualityColors(); applyQualityColors();
} }
}, 250); }, 250);
unloads.add(() => clearInterval(interval)); unloads.add(() => clearInterval(interval));
// Initial color application (if a track is already loaded) // Initial color application (if a track is already loaded)
const currentTrackId = PlayState.playbackContext?.actualProductId; const currentTrackId = PlayState.playbackContext?.actualProductId;
if (settings.qualityColorMatchedSeekBar && currentTrackId) { if (settings.qualityColorMatchedSeekBar && currentTrackId) {
lastTrackId = currentTrackId; lastTrackId = currentTrackId;
applyQualityColors(); applyQualityColors();
} }
}; };
// Function to apply theme styles based on current settings // Function to apply theme styles based on current settings
const applyThemeStyles = function(): void { const applyThemeStyles = function (): void {
// Choose the appropriate CSS file based on settings // Choose the appropriate CSS file based on settings
let selectedStyle: string; let selectedStyle: string;
if (settings.lightMode) { if (settings.lightMode) {
// Light mode - (OLED friendly doesn't apply to light theme) // Light mode - (OLED friendly doesn't apply to light theme)
selectedStyle = lightTheme; selectedStyle = lightTheme;
} else { } else {
// Dark mode // Dark mode
selectedStyle = settings.oledFriendlyButtons ? oledFriendlyTheme : darkTheme; selectedStyle = settings.oledFriendlyButtons
} ? oledFriendlyTheme
: darkTheme;
// Remove SeekBar coloring if Quality Color Matched Seek Bar is enabled }
// This allows our manual coloring to take precedence
if (settings.qualityColorMatchedSeekBar) {
selectedStyle = selectedStyle.replace(/\[class\^="_progressBarWrapper"\]\s*\{[^}]*\}/g, '');
setupQualityMonitoring();
} else {
// If disabling, reset the seek bar color
resetSeekBarColor();
}
// Apply the selected theme using StyleTag
themeStyleTag.css = selectedStyle;
// Remove SeekBar coloring if Quality Color Matched Seek Bar is enabled
// This allows our manual coloring to take precedence
if (settings.qualityColorMatchedSeekBar) {
selectedStyle = selectedStyle.replace(
/\[class\^="_progressBarWrapper"\]\s*\{[^}]*\}/g,
"",
);
setupQualityMonitoring();
} else {
// If disabling, reset the seek bar color
resetSeekBarColor();
}
// Apply the selected theme using StyleTag
themeStyleTag.css = selectedStyle;
}; };
// Make this function available globally so Settings can call it // Make this function available globally so Settings can call it
@@ -125,4 +145,3 @@ const applyThemeStyles = function(): void {
// Apply the OLED theme initially // Apply the OLED theme initially
applyThemeStyles(); applyThemeStyles();
+164 -163
View File
@@ -7,90 +7,94 @@
*/ */
::-webkit-scrollbar { ::-webkit-scrollbar {
display: none; display: none;
} }
:root { :root {
--wave-color-solid-accent-fill: #666666; --wave-color-solid-accent-fill: #666666;
--wave-color-solid-rainbow-yellow-fill: #666666; --wave-color-solid-rainbow-yellow-fill: #666666;
--wave-color-solid-contrast-fill: #666666; --wave-color-solid-contrast-fill: #666666;
--wave-color-solid-base-brighter: #666666; --wave-color-solid-base-brighter: #666666;
--wave-text-body-medium: #333333 !important; --wave-text-body-medium: #333333 !important;
--track-vibrant-color: #666666 !important; --track-vibrant-color: #666666 !important;
--wave-color-opacity-contrast-fill-ultra-thin: #c0c0c0 !important; --wave-color-opacity-contrast-fill-ultra-thin: #c0c0c0 !important;
--wave-color-solid-rainbow-yellow-darkest: #c0c0c0 !important; --wave-color-solid-rainbow-yellow-darkest: #c0c0c0 !important;
--wave-color-solid-accent-dark: #555555; --wave-color-solid-accent-dark: #555555;
} }
/* Credits to https://github.com/surfbryce for the fonts */ /* Credits to https://github.com/surfbryce for the fonts */
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 400; font-weight: 400;
src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 500; font-weight: 500;
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 600; font-weight: 600;
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 700; font-weight: 700;
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2")
format("woff2");
} }
[class^="followingButton"], [class^="followingButton"],
[title="Unfollow"], [title="Unfollow"],
[title="Follow"], [title="Follow"],
[title="Unfollow"]>span, [title="Unfollow"] > span,
[title="Follow"]>span { [title="Follow"] > span {
background-color: var(--wave-color-solid-rainbow-yellow-fill) !important; background-color: var(--wave-color-solid-rainbow-yellow-fill) !important;
color: var(--wave-color-solid-base-brighter); color: var(--wave-color-solid-base-brighter);
} }
[class^="_wave-badge-color-max"] { [class^="_wave-badge-color-max"] {
color: #333333 !important; color: #333333 !important;
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 3px; border-radius: 3px;
} }
[data-test="main-layout-sidebar-wrapper"] { [data-test="main-layout-sidebar-wrapper"] {
border-right: rgb(230, 230, 230) 1px solid; border-right: rgb(230, 230, 230) 1px solid;
background-color: rgba(250, 250, 250, 0.95) !important; background-color: rgba(250, 250, 250, 0.95) !important;
} }
[class^="_wave-badge"] { [class^="_wave-badge"] {
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 4px; border-radius: 4px;
color: #333333; color: #333333;
} }
[class^="_progressBarWrapper"] { [class^="_progressBarWrapper"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="_sidebarItem"]>span { [class^="_sidebarItem"] > span {
color: #666666; color: #666666;
} }
[data-test="main-layout-header"] { [data-test="main-layout-header"] {
border-left: 0 !important; border-left: 0 !important;
} }
[class^="_sidebarItem"]:hover span { [class^="_sidebarItem"]:hover span {
color: #333333; color: #333333;
} }
[class^="_sidebarItem"] [class^="active"]>span { [class^="_sidebarItem"] [class^="active"] > span {
color: #333333 !important; color: #333333 !important;
} }
/* Sidebar icons and text - ensure grey colors */ /* Sidebar icons and text - ensure grey colors */
@@ -98,110 +102,107 @@
[data-test="main-layout-sidebar-wrapper"] path, [data-test="main-layout-sidebar-wrapper"] path,
[class^="_sidebarItem"] svg, [class^="_sidebarItem"] svg,
[class^="_sidebarItem"] path { [class^="_sidebarItem"] path {
fill: #666666 !important; fill: #666666 !important;
color: #666666 !important; color: #666666 !important;
} }
[data-test="main-layout-sidebar-wrapper"] span, [data-test="main-layout-sidebar-wrapper"] span,
[class^="_sidebarItem"] span { [class^="_sidebarItem"] span {
color: #666666 !important; color: #666666 !important;
} }
[class^="_active"] { [class^="_active"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="ReactVirtualized__Grid"] { [class^="ReactVirtualized__Grid"] {
border-radius: 10px; border-radius: 10px;
margin: 5px; margin: 5px;
} }
[data-test="media-table"]>div>div>div { [data-test="media-table"] > div > div > div {
border: 1px solid rgb(230, 230, 230) !important; border: 1px solid rgb(230, 230, 230) !important;
} }
[class^="ReactVirtualized__Grid__innerScrollContainer"] { [class^="ReactVirtualized__Grid__innerScrollContainer"] {
border: none; border: none;
margin: 5px; margin: 5px;
} }
[class^="button"]>span { [class^="button"] > span {
color: #333333; color: #333333;
} }
[class^="_explicitBadge"] { [class^="_explicitBadge"] {
color: var(--wave-color-solid-accent-fill); color: var(--wave-color-solid-accent-fill);
} }
[class^="viewAllButton"] { [class^="viewAllButton"] {
border-radius: 4px; border-radius: 4px;
display: grid; display: grid;
place-items: center; place-items: center;
} }
[data-test="current-media-imagery"] { [data-test="current-media-imagery"] {
border: 0 !important; border: 0 !important;
margin: none; margin: none;
} }
[class^="_imageBorder"] { [class^="_imageBorder"] {
display: none; display: none;
} }
[class^="_headerButtons"]>button, [class^="_headerButtons"] > button,
[class^="_headerButtons"]>button>span, [class^="_headerButtons"] > button > span,
[data-test="toggle-picture-in-picture"] { [data-test="toggle-picture-in-picture"] {
background-color: var(--wave-color-solid-accent-fill) !important; background-color: var(--wave-color-solid-accent-fill) !important;
color: #333333; color: #333333;
} }
[class^="_container"]>[class^="_navigationArrows"] { [class^="_container"] > [class^="_navigationArrows"] {
color: #333333; color: #333333;
background-color: var(--wave-color-solid-accent-fill) !important; background-color: var(--wave-color-solid-accent-fill) !important;
border-radius: 4px; border-radius: 4px;
} }
[class^="_buttons"]>button>span { [class^="_buttons"] > button > span {
color: #333333 !important; color: #333333 !important;
} }
[class^="_container"]>button { [class^="_container"] > button {
border: 0px none; border: 0px none;
} }
[data-test="feed-sidebar"] { [data-test="feed-sidebar"] {
margin-top: 10px; margin-top: 10px;
} }
[data-test="footer-player"] { [data-test="footer-player"] {
width: calc(100% - 20px); width: calc(100% - 20px);
bottom: 10px; bottom: 10px;
left: 10px; left: 10px;
border: 1px solid rgba(200, 200, 200, 0.7); border: 1px solid rgba(200, 200, 200, 0.7);
border-radius: 4px !important; border-radius: 4px !important;
position: absolute !important; position: absolute !important;
} }
[class^="_tooltipContainer"]>button { [class^="_tooltipContainer"] > button {
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
color: #333333; color: #333333;
} }
[class^="_tooltipContainer"]>button:hover { [class^="_tooltipContainer"] > button:hover {
background-color: #555555 !important; background-color: #555555 !important;
} }
[class^="_tableRow"]:hover>*, [class^="_tableRow"]:hover > *,
[data-test-is-playing="true"]>* { [data-test-is-playing="true"] > * {
color: #333333 !important; color: #333333 !important;
} }
[class^="_tableRow"]>*, [class^="_tableRow"] > *,
[data-test-is-playing="false"]>* { [data-test-is-playing="false"] > * {
color: #333333 !important; color: #333333 !important;
} }
/* Track list text - ensure all text is dark */ /* Track list text - ensure all text is dark */
@@ -211,200 +212,200 @@
[class^="_albumTitle"], [class^="_albumTitle"],
[class^="_tableCell"] *, [class^="_tableCell"] *,
[class^="_tableCellContent"] * { [class^="_tableCellContent"] * {
color: #333333 !important; color: #333333 !important;
} }
[class*="coverColumn"] { [class*="coverColumn"] {
padding-left: 5px !important; padding-left: 5px !important;
} }
[class^="actionList"] { [class^="actionList"] {
background-color: transparent; background-color: transparent;
margin: 0px; margin: 0px;
border-radius: 5px; border-radius: 5px;
} }
button[data-test="request-fullscreen"], button[data-test="request-fullscreen"],
button[data-test="close-now-playing"], button[data-test="close-now-playing"],
button[data-test="play-all"], button[data-test="play-all"],
button[data-test="shuffle-all"] { button[data-test="shuffle-all"] {
color: #333333; color: #333333;
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 12px; border-radius: 12px;
} }
button[data-test="request-fullscreen"]:hover, button[data-test="request-fullscreen"]:hover,
button[data-test="close-now-playing"]:hover { button[data-test="close-now-playing"]:hover {
color: #333333; color: #333333;
background-color: #aaaaaa !important; background-color: #aaaaaa !important;
} }
.neptune-switch-checkbox:checked+.neptune-switch { .neptune-switch-checkbox:checked + .neptune-switch {
background-color: rgba(0, 0, 0, 0.1); background-color: rgba(0, 0, 0, 0.1);
} }
[data-test="navigation-arrows"]>button { [data-test="navigation-arrows"] > button {
background-color: var(--wave-color-solid-accent-fill) !important; background-color: var(--wave-color-solid-accent-fill) !important;
color: #333333 !important; color: #333333 !important;
border-radius: 5px; border-radius: 5px;
} }
[data-test="navigation-arrows"]>button:disabled { [data-test="navigation-arrows"] > button:disabled {
background-color: #cccccc !important; background-color: #cccccc !important;
opacity: 1; opacity: 1;
} }
[data-test="main-layout-header"] { [data-test="main-layout-header"] {
background-color: rgba(235, 235, 235, 0.95) !important; background-color: rgba(235, 235, 235, 0.95) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[data-test="feed-sidebar"] { [data-test="feed-sidebar"] {
background-color: rgba(225, 225, 225, 0.9) !important; background-color: rgba(225, 225, 225, 0.9) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[data-test="stream-metadata"] { [data-test="stream-metadata"] {
background-color: rgba(230, 230, 230, 0.92) !important; background-color: rgba(230, 230, 230, 0.92) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[data-test="footer-player"] { [data-test="footer-player"] {
background-color: rgba(255, 255, 255, 0.6) !important; background-color: rgba(255, 255, 255, 0.6) !important;
backdrop-filter: blur(15px); backdrop-filter: blur(15px);
border: 1px solid rgba(200, 200, 200, 0.7) !important; border: 1px solid rgba(200, 200, 200, 0.7) !important;
} }
[data-wave-color=textUrl] { [data-wave-color="textUrl"] {
color: var(--wave-color-solid-accent-fill); color: var(--wave-color-solid-accent-fill);
} }
[class^="_smallHeader"] { [class^="_smallHeader"] {
margin-top: 7.5px; margin-top: 7.5px;
} }
/* Button styling using proper light theme approach */ /* Button styling using proper light theme approach */
:root { :root {
--button-light: #d9d9d9 !important; --button-light: #d9d9d9 !important;
--button-medium: #cbcbcb !important; --button-medium: #cbcbcb !important;
} }
/*buttons*/ /*buttons*/
._activeTab_f47dafa { ._activeTab_f47dafa {
background: #0000001c; background: #0000001c;
} }
/*canvas nav buttons*/ /*canvas nav buttons*/
.viewAllButton--Nb87U, .viewAllButton--Nb87U,
.css-7l8ggf { .css-7l8ggf {
background: #e0e0e0; background: #e0e0e0;
} }
.viewAllButton--Nb87U:hover, .viewAllButton--Nb87U:hover,
.css-7l8ggf:hover { .css-7l8ggf:hover {
background: #cbcbcb; background: #cbcbcb;
} }
/*tracks page*/ /*tracks page*/
.variantPrimary--pjymy, .variantPrimary--pjymy,
._button_3357ce6 { ._button_3357ce6 {
background-color: var(--button-light); background-color: var(--button-light);
} }
._button_f1c7fcb { ._button_f1c7fcb {
background: var(--wave-color-solid-base-brighter); background: var(--wave-color-solid-base-brighter);
} }
._button_84b8ffe { ._button_84b8ffe {
background-color: var(--wave-color-solid-base-brighter); background-color: var(--wave-color-solid-base-brighter);
} }
._button_84b8ffe:hover { ._button_84b8ffe:hover {
background-color: var(--wave-color-solid-base-brightest); background-color: var(--wave-color-solid-base-brightest);
} }
.button--_0I_t { .button--_0I_t {
background-color: var(--button-light); background-color: var(--button-light);
} }
.button--_0I_t:hover { .button--_0I_t:hover {
background-color: var(--wave-color-opacity-contrast-fill-regular); background-color: var(--wave-color-opacity-contrast-fill-regular);
} }
._button_94c5125 { ._button_94c5125 {
background: var(--wave-color-solid-base-brighter); background: var(--wave-color-solid-base-brighter);
} }
.primary--NLSX4 { .primary--NLSX4 {
background-color: #d5d5d5; background-color: #d5d5d5;
} }
.primary--NLSX4:hover { .primary--NLSX4:hover {
background-color: var(--wave-color-opacity-contrast-fill-regular) !important; background-color: var(--wave-color-opacity-contrast-fill-regular) !important;
} }
.primary--NLSX4:disabled { .primary--NLSX4:disabled {
background-color: #e7e7e8; background-color: #e7e7e8;
} }
.primary--NLSX4:disabled:hover { .primary--NLSX4:disabled:hover {
background-color: #e7e7e8; background-color: #e7e7e8;
} }
[class^="__NEPTUNE_PAGE"], [class^="__NEPTUNE_PAGE"],
[data-test="main"] { [data-test="main"] {
margin-top: 35px; margin-top: 35px;
} }
[data-test="button-desktop-release-notes"], [data-test="button-desktop-release-notes"],
[data-test="button-release-notes"] { [data-test="button-release-notes"] {
background-color: #333333; background-color: #333333;
} }
[data-test="button-desktop-release-notes"]:hover, [data-test="button-desktop-release-notes"]:hover,
[data-test="button-release-notes"]:hover { [data-test="button-release-notes"]:hover {
background-color: #555555 !important; background-color: #555555 !important;
transition: none !important; transition: none !important;
} }
#playQueueSidebar { #playQueueSidebar {
top: 50px !important; top: 50px !important;
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin); border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin);
margin: 2px; margin: 2px;
margin-right: -14px !important; margin-right: -14px !important;
background-color: rgba(220, 220, 220, 0.9) !important; background-color: rgba(220, 220, 220, 0.9) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
[class^="_bottomGradient"] { [class^="_bottomGradient"] {
visibility: hidden; visibility: hidden;
} }
[data-test="settings-page"] { [data-test="settings-page"] {
padding-bottom: 60px !important; padding-bottom: 60px !important;
} }
[data-test="query-suggestions"], [data-test="query-suggestions"],
[data-test="recent-searches-container"] { [data-test="recent-searches-container"] {
background-color: rgba(227, 227, 227, 0.85); background-color: rgba(227, 227, 227, 0.85);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
[data-test="contextmenu"] { [data-test="contextmenu"] {
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[class^="_dataContainer_"]::before { [class^="_dataContainer_"]::before {
background-image: var(--img); background-image: var(--img);
filter: blur(10px) brightness(1.2); filter: blur(10px) brightness(1.2);
} }
/* Player bar text colors - fix white text issues */ /* Player bar text colors - fix white text issues */
[data-test="footer-player"] * { [data-test="footer-player"] * {
color: #333333 !important; color: #333333 !important;
} }
[data-test="footer-player"] [class*="trackTitle"], [data-test="footer-player"] [class*="trackTitle"],
@@ -413,12 +414,12 @@ button[data-test="close-now-playing"]:hover {
[data-test="footer-player"] [class*="duration"], [data-test="footer-player"] [class*="duration"],
[data-test="footer-player"] [class*="time"], [data-test="footer-player"] [class*="time"],
[data-test="footer-player"] [class*="timestamp"] { [data-test="footer-player"] [class*="timestamp"] {
color: #333333 !important; color: #333333 !important;
} }
/* Main page background */ /* Main page background */
body, body,
[data-test="main"], [data-test="main"],
[class^="__NEPTUNE_PAGE"] { [class^="__NEPTUNE_PAGE"] {
background-color: #f5f5f5 !important; background-color: #f5f5f5 !important;
} }
+94 -90
View File
@@ -7,209 +7,213 @@
*/ */
::-webkit-scrollbar { ::-webkit-scrollbar {
display: none; display: none;
} }
:root { :root {
--wave-color-solid-accent-fill: white; --wave-color-solid-accent-fill: white;
--wave-color-solid-rainbow-yellow-fill: white; --wave-color-solid-rainbow-yellow-fill: white;
--wave-color-solid-contrast-fill: white; --wave-color-solid-contrast-fill: white;
--wave-color-solid-base-brighter: black; --wave-color-solid-base-brighter: black;
--wave-text-body-medium: white !important; --wave-text-body-medium: white !important;
--track-vibrant-color: white !important; --track-vibrant-color: white !important;
--wave-color-opacity-contrast-fill-ultra-thin: #fffafa1a !important; --wave-color-opacity-contrast-fill-ultra-thin: #fffafa1a !important;
--wave-color-solid-rainbow-yellow-darkest: #fffafa1a !important; --wave-color-solid-rainbow-yellow-darkest: #fffafa1a !important;
--wave-color-solid-accent-dark: rgb(128, 128, 128); --wave-color-solid-accent-dark: rgb(128, 128, 128);
} }
/* Credits to https://github.com/surfbryce for the fonts */ /* Credits to https://github.com/surfbryce for the fonts */
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 400; font-weight: 400;
src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 500; font-weight: 500;
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 600; font-weight: 600;
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 700; font-weight: 700;
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2")
format("woff2");
} }
[class^="followingButton"], [class^="followingButton"],
[title="Unfollow"], [title="Unfollow"],
[title="Follow"], [title="Follow"],
[title="Unfollow"]>span, [title="Unfollow"] > span,
[title="Follow"]>span { [title="Follow"] > span {
background-color: var(--wave-color-solid-rainbow-yellow-fill) !important; background-color: var(--wave-color-solid-rainbow-yellow-fill) !important;
color: var(--wave-color-solid-base-brighter); color: var(--wave-color-solid-base-brighter);
} }
[class^="_wave-badge-color-max"] { [class^="_wave-badge-color-max"] {
color: black !important; color: black !important;
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 3px; border-radius: 3px;
} }
[data-test="main-layout-sidebar-wrapper"] { [data-test="main-layout-sidebar-wrapper"] {
border-right: rgb(25, 25, 25) 1px solid; border-right: rgb(25, 25, 25) 1px solid;
} }
[class^="_wave-badge"] { [class^="_wave-badge"] {
background-color: var(--wave-color-solid-accent-fill); background-color: var(--wave-color-solid-accent-fill);
border-radius: 4px; border-radius: 4px;
color: black; color: black;
} }
[class^="_progressBarWrapper"] { [class^="_progressBarWrapper"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="_sidebarItem"]>span { [class^="_sidebarItem"] > span {
color: var(--wave-color-solid-accent-dark); color: var(--wave-color-solid-accent-dark);
} }
[data-test="main-layout-header"] { [data-test="main-layout-header"] {
border-left: 0 !important; border-left: 0 !important;
} }
[class^="_sidebarItem"]:hover span { [class^="_sidebarItem"]:hover span {
color: var(--wave-color-solid-contrast-fill); color: var(--wave-color-solid-contrast-fill);
} }
[class^="_sidebarItem"] [class^="active"]>span { [class^="_sidebarItem"] [class^="active"] > span {
color: var(--wave-color-solid-accent-dark) !important; color: var(--wave-color-solid-accent-dark) !important;
} }
[class^="_active"] { [class^="_active"] {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="ReactVirtualized__Grid"] { [class^="ReactVirtualized__Grid"] {
border-radius: 10px; border-radius: 10px;
margin: 5px; margin: 5px;
} }
[data-test="media-table"]>div>div>div { [data-test="media-table"] > div > div > div {
border: 1px solid rgb(25, 25, 25) !important; border: 1px solid rgb(25, 25, 25) !important;
} }
[class^="ReactVirtualized__Grid__innerScrollContainer"] { [class^="ReactVirtualized__Grid__innerScrollContainer"] {
border: none; border: none;
margin: 5px; margin: 5px;
} }
[class^="_explicitBadge"] { [class^="_explicitBadge"] {
color: var(--wave-color-solid-accent-fill); color: var(--wave-color-solid-accent-fill);
} }
[data-test="current-media-imagery"] { [data-test="current-media-imagery"] {
border: 0 !important; border: 0 !important;
margin: none; margin: none;
} }
[class^="_imageBorder"] { [class^="_imageBorder"] {
display: none; display: none;
} }
[data-test="feed-sidebar"] { [data-test="feed-sidebar"] {
margin-top: 10px; margin-top: 10px;
} }
[data-test="footer-player"] { [data-test="footer-player"] {
width: calc(100% - 20px); width: calc(100% - 20px);
bottom: 10px; bottom: 10px;
left: 10px; left: 10px;
border: 1px solid rgb(25, 25, 25); border: 1px solid rgb(25, 25, 25);
border-radius: 4px !important; border-radius: 4px !important;
position: absolute !important; position: absolute !important;
} }
[class^="_tableRow"]:hover>*, [class^="_tableRow"]:hover > *,
[data-test-is-playing="true"]>* { [data-test-is-playing="true"] > * {
color: var(--wave-color-solid-accent-fill) !important; color: var(--wave-color-solid-accent-fill) !important;
} }
[class^="_tableRow"]>*, [class^="_tableRow"] > *,
[data-test-is-playing="false"]>* { [data-test-is-playing="false"] > * {
color: lightgray !important; color: lightgray !important;
} }
[class*="coverColumn"] { [class*="coverColumn"] {
padding-left: 5px !important; padding-left: 5px !important;
} }
[class^="actionList"] { [class^="actionList"] {
background-color: transparent; background-color: transparent;
margin: 0px; margin: 0px;
border-radius: 5px; border-radius: 5px;
} }
.neptune-switch-checkbox:checked+.neptune-switch { .neptune-switch-checkbox:checked + .neptune-switch {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
[data-test="main-layout-header"], [data-test="main-layout-header"],
[data-test="feed-sidebar"], [data-test="feed-sidebar"],
[data-test="stream-metadata"], [data-test="stream-metadata"],
[data-test="footer-player"] { [data-test="footer-player"] {
background-color: rgba(0, 0, 0, 0.8) !important; background-color: rgba(0, 0, 0, 0.8) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[data-wave-color=textUrl] { [data-wave-color="textUrl"] {
color: var(--wave-color-solid-accent-fill); color: var(--wave-color-solid-accent-fill);
} }
[class^="_smallHeader"] { [class^="_smallHeader"] {
margin-top: 7.5px; margin-top: 7.5px;
} }
[class^="__NEPTUNE_PAGE"], [class^="__NEPTUNE_PAGE"],
[data-test="main"] { [data-test="main"] {
margin-top: 35px; margin-top: 35px;
} }
#playQueueSidebar { #playQueueSidebar {
top: 50px !important; top: 50px !important;
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin); border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin);
margin: 2px; margin: 2px;
margin-right: -14px !important; margin-right: -14px !important;
background-color: rgba(0, 0, 0, 0.8) !important; background-color: rgba(0, 0, 0, 0.8) !important;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
[class^="_bottomGradient"] { [class^="_bottomGradient"] {
visibility: hidden; visibility: hidden;
} }
[data-test="settings-page"] { [data-test="settings-page"] {
padding-bottom: 60px !important; padding-bottom: 60px !important;
} }
[data-test="query-suggestions"], [data-test="query-suggestions"],
[data-test="recent-searches-container"] { [data-test="recent-searches-container"] {
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
[data-test="contextmenu"] { [data-test="contextmenu"] {
border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important;
} }
[class^="_dataContainer_"]::before { [class^="_dataContainer_"]::before {
background-image: var(--img); background-image: var(--img);
filter: blur(10px) brightness(0.4); filter: blur(10px) brightness(0.4);
} }
+98 -49
View File
@@ -5,33 +5,54 @@ import React from "react";
export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", { export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
hideUIEnabled: true, hideUIEnabled: true,
trackTitleGlow: false, trackTitleGlow: false,
playerBarVisible: false, playerBarVisible: false,
lyricsGlowEnabled: true, lyricsGlowEnabled: true,
textGlow: 20, textGlow: 20,
spinningCoverEverywhere: true, spinningCoverEverywhere: true,
performanceMode: false, performanceMode: false,
spinningArtEnabled: true, spinningArtEnabled: true,
backgroundContrast: 120, backgroundContrast: 120,
backgroundBlur: 80, backgroundBlur: 80,
backgroundBrightness: 40, backgroundBrightness: 40,
spinSpeed: 45, spinSpeed: 45,
settingsAffectNowPlaying: true settingsAffectNowPlaying: true,
}); });
export const Settings = () => { export const Settings = () => {
const [hideUIEnabled, setHideUIEnabled] = React.useState(settings.hideUIEnabled); const [hideUIEnabled, setHideUIEnabled] = React.useState(
const [playerBarVisible, setPlayerBarVisible] = React.useState(settings.playerBarVisible); settings.hideUIEnabled,
const [lyricsGlowEnabled, setLyricsGlowEnabled] = React.useState(settings.lyricsGlowEnabled); );
const [playerBarVisible, setPlayerBarVisible] = React.useState(
settings.playerBarVisible,
);
const [lyricsGlowEnabled, setLyricsGlowEnabled] = React.useState(
settings.lyricsGlowEnabled,
);
const [textGlow, setTextGlow] = React.useState(settings.textGlow); const [textGlow, setTextGlow] = React.useState(settings.textGlow);
const [spinningCoverEverywhere, setSpinningCoverEverywhere] = React.useState(settings.spinningCoverEverywhere); const [spinningCoverEverywhere, setSpinningCoverEverywhere] = React.useState(
const [performanceMode, setPerformanceMode] = React.useState(settings.performanceMode); settings.spinningCoverEverywhere,
const [spinningArtEnabled, setSpinningArtEnabled] = React.useState(settings.spinningArtEnabled); );
const [backgroundContrast, setBackgroundContrast] = React.useState(settings.backgroundContrast); const [performanceMode, setPerformanceMode] = React.useState(
const [backgroundBlur, setBackgroundBlur] = React.useState(settings.backgroundBlur); settings.performanceMode,
const [backgroundBrightness, setBackgroundBrightness] = React.useState(settings.backgroundBrightness); );
const [spinningArtEnabled, setSpinningArtEnabled] = React.useState(
settings.spinningArtEnabled,
);
const [backgroundContrast, setBackgroundContrast] = React.useState(
settings.backgroundContrast,
);
const [backgroundBlur, setBackgroundBlur] = React.useState(
settings.backgroundBlur,
);
const [backgroundBrightness, setBackgroundBrightness] = React.useState(
settings.backgroundBrightness,
);
const [spinSpeed, setSpinSpeed] = React.useState(settings.spinSpeed); const [spinSpeed, setSpinSpeed] = React.useState(settings.spinSpeed);
const [settingsAffectNowPlaying, setSettingsAffectNowPlaying] = React.useState(settings.settingsAffectNowPlaying); const [settingsAffectNowPlaying, setSettingsAffectNowPlaying] =
const [trackTitleGlow, setTrackTitleGlow] = React.useState(settings.trackTitleGlow); React.useState(settings.settingsAffectNowPlaying);
const [trackTitleGlow, setTrackTitleGlow] = React.useState(
settings.trackTitleGlow,
);
return ( return (
<LunaSettings> <LunaSettings>
@@ -48,16 +69,16 @@ export const Settings = () => {
}} }}
/> />
<LunaSwitchSetting <LunaSwitchSetting
title="Track Title Glow" title="Track Title Glow"
desc="Apply glow to the track title" desc="Apply glow to the track title"
checked={trackTitleGlow} checked={trackTitleGlow}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
setTrackTitleGlow((settings.trackTitleGlow = checked)); setTrackTitleGlow((settings.trackTitleGlow = checked));
if ((window as any).updateRadiantLyricsStyles) { if ((window as any).updateRadiantLyricsStyles) {
(window as any).updateRadiantLyricsStyles(); (window as any).updateRadiantLyricsStyles();
} }
}} }}
/> />
<LunaSwitchSetting <LunaSwitchSetting
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"
@@ -84,8 +105,13 @@ export const Settings = () => {
desc="Apply the spinning Cover Art background to the entire app, not just the Now Playing view, Heavily Inspired by Cover-Theme by @Inrixia" desc="Apply the spinning Cover Art background to the entire app, not just the Now Playing view, Heavily Inspired by Cover-Theme by @Inrixia"
checked={spinningCoverEverywhere} checked={spinningCoverEverywhere}
onChange={(_, checked: boolean) => { onChange={(_, checked: boolean) => {
console.log("Spinning Cover Everywhere:", checked ? "enabled" : "disabled"); console.log(
setSpinningCoverEverywhere((settings.spinningCoverEverywhere = checked)); "Spinning Cover Everywhere:",
checked ? "enabled" : "disabled",
);
setSpinningCoverEverywhere(
(settings.spinningCoverEverywhere = checked),
);
// Update styles immediately when setting changes // Update styles immediately when setting changes
if ((window as any).updateRadiantLyricsGlobalBackground) { if ((window as any).updateRadiantLyricsGlobalBackground) {
(window as any).updateRadiantLyricsGlobalBackground(); (window as any).updateRadiantLyricsGlobalBackground();
@@ -113,31 +139,37 @@ export const Settings = () => {
desc="Enable the spinning cover art background animation" desc="Enable the spinning cover art background animation"
checked={spinningArtEnabled} checked={spinningArtEnabled}
onChange={(_, checked: boolean) => { onChange={(_, checked: boolean) => {
console.log("Background Cover Spin:", checked ? "enabled" : "disabled"); console.log(
"Background Cover Spin:",
checked ? "enabled" : "disabled",
);
setSpinningArtEnabled((settings.spinningArtEnabled = checked)); setSpinningArtEnabled((settings.spinningArtEnabled = checked));
if ((window as any).updateRadiantLyricsGlobalBackground) { if ((window as any).updateRadiantLyricsGlobalBackground) {
(window as any).updateRadiantLyricsGlobalBackground(); (window as any).updateRadiantLyricsGlobalBackground();
} }
if (settings.settingsAffectNowPlaying && (window as any).updateRadiantLyricsNowPlayingBackground) { if (
settings.settingsAffectNowPlaying &&
(window as any).updateRadiantLyricsNowPlayingBackground
) {
(window as any).updateRadiantLyricsNowPlayingBackground(); (window as any).updateRadiantLyricsNowPlayingBackground();
} }
}} }}
/> />
<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) => {
setTextGlow((settings.textGlow = value)); setTextGlow((settings.textGlow = value));
// Update variables immediately when setting changes // Update variables immediately when setting changes
if ((window as any).updateRadiantLyricsTextGlow) { if ((window as any).updateRadiantLyricsTextGlow) {
(window as any).updateRadiantLyricsTextGlow(); (window as any).updateRadiantLyricsTextGlow();
} }
}} }}
/> />
<LunaNumberSetting <LunaNumberSetting
title="Background Contrast" title="Background Contrast"
desc="Adjust the contrast of the spinning background (0-200, default: 120)" desc="Adjust the contrast of the spinning background (0-200, default: 120)"
@@ -150,7 +182,10 @@ export const Settings = () => {
if ((window as any).updateRadiantLyricsGlobalBackground) { if ((window as any).updateRadiantLyricsGlobalBackground) {
(window as any).updateRadiantLyricsGlobalBackground(); (window as any).updateRadiantLyricsGlobalBackground();
} }
if (settings.settingsAffectNowPlaying && (window as any).updateRadiantLyricsNowPlayingBackground) { if (
settings.settingsAffectNowPlaying &&
(window as any).updateRadiantLyricsNowPlayingBackground
) {
(window as any).updateRadiantLyricsNowPlayingBackground(); (window as any).updateRadiantLyricsNowPlayingBackground();
} }
}} }}
@@ -168,7 +203,10 @@ export const Settings = () => {
if ((window as any).updateRadiantLyricsGlobalBackground) { if ((window as any).updateRadiantLyricsGlobalBackground) {
(window as any).updateRadiantLyricsGlobalBackground(); (window as any).updateRadiantLyricsGlobalBackground();
} }
if (settings.settingsAffectNowPlaying && (window as any).updateRadiantLyricsNowPlayingBackground) { if (
settings.settingsAffectNowPlaying &&
(window as any).updateRadiantLyricsNowPlayingBackground
) {
(window as any).updateRadiantLyricsNowPlayingBackground(); (window as any).updateRadiantLyricsNowPlayingBackground();
} }
}} }}
@@ -186,7 +224,10 @@ export const Settings = () => {
if ((window as any).updateRadiantLyricsGlobalBackground) { if ((window as any).updateRadiantLyricsGlobalBackground) {
(window as any).updateRadiantLyricsGlobalBackground(); (window as any).updateRadiantLyricsGlobalBackground();
} }
if (settings.settingsAffectNowPlaying && (window as any).updateRadiantLyricsNowPlayingBackground) { if (
settings.settingsAffectNowPlaying &&
(window as any).updateRadiantLyricsNowPlayingBackground
) {
(window as any).updateRadiantLyricsNowPlayingBackground(); (window as any).updateRadiantLyricsNowPlayingBackground();
} }
}} }}
@@ -204,7 +245,10 @@ export const Settings = () => {
if ((window as any).updateRadiantLyricsGlobalBackground) { if ((window as any).updateRadiantLyricsGlobalBackground) {
(window as any).updateRadiantLyricsGlobalBackground(); (window as any).updateRadiantLyricsGlobalBackground();
} }
if (settings.settingsAffectNowPlaying && (window as any).updateRadiantLyricsNowPlayingBackground) { if (
settings.settingsAffectNowPlaying &&
(window as any).updateRadiantLyricsNowPlayingBackground
) {
(window as any).updateRadiantLyricsNowPlayingBackground(); (window as any).updateRadiantLyricsNowPlayingBackground();
} }
}} }}
@@ -214,8 +258,13 @@ export const Settings = () => {
desc="Apply background settings to Now Playing view" desc="Apply background settings to Now Playing view"
checked={settingsAffectNowPlaying} checked={settingsAffectNowPlaying}
onChange={(_, checked: boolean) => { onChange={(_, checked: boolean) => {
console.log("Settings Affect Now Playing:", checked ? "enabled" : "disabled"); console.log(
setSettingsAffectNowPlaying((settings.settingsAffectNowPlaying = checked)); "Settings Affect Now Playing:",
checked ? "enabled" : "disabled",
);
setSettingsAffectNowPlaying(
(settings.settingsAffectNowPlaying = checked),
);
// Update Now Playing background immediately when setting changes // Update Now Playing background immediately when setting changes
if ((window as any).updateRadiantLyricsNowPlayingBackground) { if ((window as any).updateRadiantLyricsNowPlayingBackground) {
(window as any).updateRadiantLyricsNowPlayingBackground(); (window as any).updateRadiantLyricsNowPlayingBackground();
@@ -1,105 +1,105 @@
/* Global Spinning Background Styles - PERFORMANCE OPTIMIZED */ /* Global Spinning Background Styles - PERFORMANCE OPTIMIZED */
.global-background-container { .global-background-container {
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
z-index: -3; z-index: -3;
pointer-events: none; pointer-events: none;
overflow: hidden; overflow: hidden;
/* Hardware acceleration */ /* Hardware acceleration */
transform: translateZ(0); transform: translateZ(0);
backface-visibility: hidden; backface-visibility: hidden;
} }
.global-spinning-black-bg { .global-spinning-black-bg {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #000; background: #000;
z-index: -2; z-index: -2;
pointer-events: none; pointer-events: none;
} }
.global-spinning-image { .global-spinning-image {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 150vw; width: 150vw;
height: 150vh; height: 150vh;
object-fit: cover; object-fit: cover;
z-index: -1; z-index: -1;
filter: blur(80px) brightness(0.4) contrast(1.2) saturate(1); filter: blur(80px) brightness(0.4) contrast(1.2) saturate(1);
opacity: 1; opacity: 1;
animation: spinGlobal 45s linear infinite; animation: spinGlobal 45s linear infinite;
will-change: transform; will-change: transform;
/* Hardware acceleration */ /* Hardware acceleration */
transform-origin: center center; transform-origin: center center;
backface-visibility: hidden; backface-visibility: hidden;
} }
/* Performance mode optimizations - keep spinning but optimize other aspects */ /* Performance mode optimizations - keep spinning but optimize other aspects */
.global-spinning-image.performance-mode-static { .global-spinning-image.performance-mode-static {
/* Keep animation enabled in performance mode */ /* Keep animation enabled in performance mode */
/* Lighter blur for performance */ /* Lighter blur for performance */
filter: blur(20px) brightness(0.4) contrast(1.2) saturate(1) !important; filter: blur(20px) brightness(0.4) contrast(1.2) saturate(1) !important;
/* Smaller size for performance */ /* Smaller size for performance */
width: 120vw !important; width: 120vw !important;
height: 120vh !important; height: 120vh !important;
} }
.now-playing-background-image.performance-mode-static { .now-playing-background-image.performance-mode-static {
/* Keep animation enabled in performance mode */ /* Keep animation enabled in performance mode */
/* Optimized size and effects for performance */ /* Optimized size and effects for performance */
width: 80vw !important; width: 80vw !important;
height: 80vh !important; 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;
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: -3; z-index: -3;
pointer-events: none; pointer-events: none;
overflow: hidden; overflow: hidden;
/* Hardware acceleration */ /* Hardware acceleration */
transform: translateZ(0); transform: translateZ(0);
backface-visibility: hidden; backface-visibility: hidden;
} }
/* Optimized keyframe animations with GPU acceleration */ /* Optimized keyframe animations with GPU acceleration */
@keyframes spinGlobal { @keyframes spinGlobal {
from { from {
transform: translate(-50%, -50%) rotate(0deg); transform: translate(-50%, -50%) rotate(0deg);
} }
to { to {
transform: translate(-50%, -50%) rotate(360deg); transform: translate(-50%, -50%) rotate(360deg);
} }
} }
/* Reduced motion for users who prefer it */ /* Reduced motion for users who prefer it */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.global-spinning-image, .global-spinning-image,
.now-playing-background-image { .now-playing-background-image {
animation: none !important; animation: none !important;
transform: translate(-50%, -50%) !important; transform: translate(-50%, -50%) !important;
will-change: auto !important; will-change: auto !important;
} }
} }
/* Performance mode: optimize effects but keep spinning */ /* Performance mode: optimize effects but keep spinning */
.performance-mode .global-spinning-image, .performance-mode .global-spinning-image,
.performance-mode .now-playing-background-image { .performance-mode .now-playing-background-image {
/* Keep animations but optimize filter effects */ /* Keep animations but optimize filter effects */
filter: blur(10px) brightness(0.4) contrast(1.1) !important; filter: blur(10px) brightness(0.4) contrast(1.1) !important;
} }
/* Make Notification Feed sidebar transparent */ /* Make Notification Feed sidebar transparent */
@@ -115,13 +115,13 @@ main,
[data-test="stream-metadata"], [data-test="stream-metadata"],
[data-test="footer-player"], [data-test="footer-player"],
/* Notification Feed sidebar specific container */ /* Notification Feed sidebar specific container */
[class^="_feedSidebarVStack"], [class^="_feedSidebarVStack"],
[class^="_feedSidebarSpacer"], [class^="_feedSidebarSpacer"],
[class^="_feedSidebarItem"], [class^="_feedSidebarItem"],
[class^="_feedSidebarItemDiv"], [class^="_feedSidebarItemDiv"],
[class^="_cellContainer"], [class^="_cellContainer"],
[class^="_cellTextContainer"] { [class^="_cellTextContainer"] {
background: unset !important; background: unset !important;
} }
/* Make sidebar and player bar semi-transparent with optimized backdrop-filter */ /* Make sidebar and player bar semi-transparent with optimized backdrop-filter */
@@ -129,9 +129,9 @@ main,
[data-test="main-layout-sidebar-wrapper"], [data-test="main-layout-sidebar-wrapper"],
[class^="_bar"], [class^="_bar"],
[class^="_sidebarItem"]:hover { [class^="_sidebarItem"]:hover {
background-color: rgba(0, 0, 0, 0.3) !important; background-color: rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important;
-webkit-backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;
} }
/* Performance mode: reduce backdrop blur */ /* Performance mode: reduce backdrop blur */
@@ -139,21 +139,21 @@ main,
.performance-mode [data-test="main-layout-sidebar-wrapper"], .performance-mode [data-test="main-layout-sidebar-wrapper"],
.performance-mode [class^="_bar"], .performance-mode [class^="_bar"],
.performance-mode [class^="_sidebarItem"]:hover { .performance-mode [class^="_sidebarItem"]:hover {
backdrop-filter: blur(5px) !important; backdrop-filter: blur(5px) !important;
-webkit-backdrop-filter: blur(5px) !important; -webkit-backdrop-filter: blur(5px) !important;
} }
/* Feed sidebar panel - black tint background for readability */ /* Feed sidebar panel - black tint background for readability */
[data-test="feed-sidebar"] { [data-test="feed-sidebar"] {
background-color: rgba(0, 0, 0, 0.5) !important; background-color: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important;
-webkit-backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;
} }
/* Performance mode: reduce sidebar backdrop blur */ /* Performance mode: reduce sidebar backdrop blur */
.performance-mode [data-test="feed-sidebar"] { .performance-mode [data-test="feed-sidebar"] {
backdrop-filter: blur(5px) !important; backdrop-filter: blur(5px) !important;
-webkit-backdrop-filter: blur(5px) !important; -webkit-backdrop-filter: blur(5px) !important;
} }
/* Feed sidebar items - transparent */ /* Feed sidebar items - transparent */
@@ -162,10 +162,10 @@ main,
[class*="_cellContainer"], [class*="_cellContainer"],
[data-test="feed-interval"], [data-test="feed-interval"],
[data-test="feed-item"] { [data-test="feed-item"] {
background-color: transparent !important; background-color: transparent !important;
} }
/* Remove bottom gradient */ /* Remove bottom gradient */
[class^="_bottomGradient"] { [class^="_bottomGradient"] {
display: none !important; display: none !important;
} }
File diff suppressed because it is too large Load Diff
+72 -51
View File
@@ -1,82 +1,103 @@
/* Font imports for lyrics */ /* Font imports for lyrics */
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 400; font-weight: 400;
src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsRegular.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 500; font-weight: 500;
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 600; font-weight: 600;
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2")
format("woff2");
} }
@font-face { @font-face {
font-family: "AbyssFont"; font-family: "AbyssFont";
font-weight: 700; font-weight: 700;
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2"); src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2")
format("woff2");
} }
/* Enhanced lyrics styling with glow effects */ /* Enhanced lyrics styling with glow effects */
[class*="_lyricsText"] > div > span[data-current="true"] { [class*="_lyricsText"] > div > span[data-current="true"] {
text-shadow: 0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #fff), 0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #fff) !important; text-shadow:
padding-left: 20px; 0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #fff),
transition-duration: 0.7s; 0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #fff) !important;
font-size: 55px; padding-left: 20px;
color: white !important; transition-duration: 0.7s;
font-family: "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-size: 55px;
font-weight: 700; color: white !important;
font-family:
"AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-weight: 700;
} }
[class*="_lyricsText"] > div > span { [class*="_lyricsText"] > div > span {
text-shadow: 0 0 0px transparent, 0 0 0px transparent; text-shadow:
transition-duration: 0.25s; 0 0 0px transparent,
color: rgba(128, 128, 128, 0.4); 0 0 0px transparent;
font-size: 40px; transition-duration: 0.25s;
font-family: "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; color: rgba(128, 128, 128, 0.4);
font-weight: 700; font-size: 40px;
font-family:
"AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-weight: 700;
} }
[class*="_lyricsText"] > div > span:hover { [class*="_lyricsText"] > div > span:hover {
text-shadow: 0 0 var(--rl-glow-inner, 2px) lightgray, 0 0 var(--rl-glow-outer, 20px) lightgray !important; text-shadow:
color: lightgray !important; 0 0 var(--rl-glow-inner, 2px) lightgray,
padding-left: 20px; 0 0 var(--rl-glow-outer, 20px) lightgray !important;
transition-duration: 0.7s; color: lightgray !important;
padding-left: 20px;
transition-duration: 0.7s;
} }
/* Track title glow */ /* Track title glow */
[data-test="now-playing-track-title"] { [data-test="now-playing-track-title"] {
/* Title text color/gradient is left to default app styling; only glow is customized. */ /* Title text color/gradient is left to default app styling; only glow is customized. */
text-shadow: 0 0 var(--rl-glow-inner, 1px) var(--cl-glow1, #fff), 0 0 var(--rl-glow-outer, 30px) #fff !important; text-shadow:
-webkit-background-clip: initial !important; 0 0 var(--rl-glow-inner, 1px) var(--cl-glow1, #fff),
background-clip: initial !important; 0 0 var(--rl-glow-outer, 30px) #fff !important;
-webkit-text-fill-color: initial !important; -webkit-background-clip: initial !important;
color: inherit !important; background-clip: initial !important;
-webkit-text-fill-color: initial !important;
color: inherit !important;
} }
/* When track title glow setting is disabled, remove glow regardless of Colorama */ /* When track title glow setting is disabled, remove glow regardless of Colorama */
.rl-title-glow-disabled[data-test="now-playing-track-title"] { .rl-title-glow-disabled[data-test="now-playing-track-title"] {
text-shadow: none !important; text-shadow: none !important;
} }
/* Current line transitions */ /* Current line transitions */
[class*="_lyricsText"] > div > span { [class*="_lyricsText"] > div > span {
transition: text-shadow 0.7s ease-in-out, color 0.7s ease-in-out, padding 0.7s ease-in-out !important; transition:
text-shadow 0.7s ease-in-out,
color 0.7s ease-in-out,
padding 0.7s ease-in-out !important;
} }
/* Lyrics container styling */ /* Lyrics container styling */
[class^="_lyricsContainer"] > div > div > span { [class^="_lyricsContainer"] > div > div > span {
margin-bottom: 2rem; margin-bottom: 2rem;
opacity: 1; opacity: 1;
font-family: "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-family:
font-weight: 700; "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
font-size: 38px !important; Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-weight: 700;
font-size: 38px !important;
} }
/* Reset all lyrics styling when disabled */ /* Reset all lyrics styling when disabled */
@@ -85,13 +106,13 @@
.lyrics-glow-disabled [class*="_lyricsText"] > div > span:hover, .lyrics-glow-disabled [class*="_lyricsText"] > div > span:hover,
.lyrics-glow-disabled [data-test="now-playing-track-title"], .lyrics-glow-disabled [data-test="now-playing-track-title"],
.lyrics-glow-disabled [class^="_lyricsContainer"] > div > div > span { .lyrics-glow-disabled [class^="_lyricsContainer"] > div > div > span {
text-shadow: none !important; text-shadow: none !important;
padding-left: 0 !important; padding-left: 0 !important;
transition: none !important; transition: none !important;
font-size: inherit !important; font-size: inherit !important;
color: inherit !important; color: inherit !important;
font-family: inherit !important; font-family: inherit !important;
font-weight: inherit !important; font-weight: inherit !important;
margin-bottom: inherit !important; margin-bottom: inherit !important;
opacity: inherit !important; opacity: inherit !important;
} }
@@ -1,15 +1,15 @@
/* Hide player bar when setting is disabled, but show on hover - only when UI is hidden */ /* Hide player bar when setting is disabled, but show on hover - only when UI is hidden */
.radiant-lyrics-ui-hidden [data-test="footer-player"] { .radiant-lyrics-ui-hidden [data-test="footer-player"] {
opacity: 0 !important; opacity: 0 !important;
transition: opacity 0.5s ease-in-out !important; transition: opacity 0.5s ease-in-out !important;
} }
.radiant-lyrics-ui-hidden [data-test="footer-player"]:hover { .radiant-lyrics-ui-hidden [data-test="footer-player"]:hover {
opacity: 1 !important; opacity: 1 !important;
} }
/* Also show player bar when hovering over the bottom area - only when UI is hidden */ /* Also show player bar when hovering over the bottom area - only when UI is hidden */
.radiant-lyrics-ui-hidden body.rl-footer-hover [data-test="footer-player"], .radiant-lyrics-ui-hidden body.rl-footer-hover [data-test="footer-player"],
.radiant-lyrics-ui-hidden [data-test="footer-player"]:hover { .radiant-lyrics-ui-hidden [data-test="footer-player"]:hover {
opacity: 1 !important; opacity: 1 !important;
} }
+122 -74
View File
@@ -1,30 +1,32 @@
/* Only apply styles when UI is hidden */ /* Only apply styles when UI is hidden */
.radiant-lyrics-ui-hidden [class*="tabItems"] { .radiant-lyrics-ui-hidden [class*="tabItems"] {
opacity: 0 !important; opacity: 0 !important;
transition: opacity 0.4s ease-in-out; transition: opacity 0.4s ease-in-out;
} }
/* Default state - visible */ /* Default state - visible */
[class*="tabItems"] { [class*="tabItems"] {
transition: opacity 0.4s ease-in-out; transition: opacity 0.4s ease-in-out;
} }
/* Tab items stay hidden - no hover functionality (if the song changes and it doesnt have lyrics.. and ya want them back.. you can unhide the UI <3) */ /* Tab items stay hidden - no hover functionality (if the song changes and it doesnt have lyrics.. and ya want them back.. you can unhide the UI <3) */
.radiant-lyrics-ui-hidden [data-test="header-container"]:not(.rl-header-has-hide-btn) { .radiant-lyrics-ui-hidden
opacity: 0 !important; [data-test="header-container"]:not(.rl-header-has-hide-btn) {
transition: opacity 0.4s ease-in-out; opacity: 0 !important;
transition: opacity 0.4s ease-in-out;
} }
/* Keep header visible if it contains the Hide UI button, but hide its other children */ /* Keep header visible if it contains the Hide UI button, but hide its other children */
.radiant-lyrics-ui-hidden [data-test="header-container"].rl-header-has-hide-btn > *:not(.hide-ui-button) { .radiant-lyrics-ui-hidden [data-test="header-container"].rl-header-has-hide-btn
opacity: 0 !important; > *:not(.hide-ui-button) {
transition: opacity 0.4s ease-in-out; opacity: 0 !important;
transition: opacity 0.4s ease-in-out;
} }
/* Default state for header */ /* Default state for header */
[data-test="header-container"] { [data-test="header-container"] {
transition: opacity 0.4s ease-in-out; transition: opacity 0.4s ease-in-out;
} }
/* Only prevent specific text elements in player bar from being affected by margin adjustments */ /* Only prevent specific text elements in player bar from being affected by margin adjustments */
@@ -32,30 +34,30 @@
[data-test="footer-player"] [class*="_artistName"], [data-test="footer-player"] [class*="_artistName"],
[data-test="footer-player"] [class*="_trackInfo"], [data-test="footer-player"] [class*="_trackInfo"],
[data-test="footer-player"] [class*="_trackContainer"] { [data-test="footer-player"] [class*="_trackContainer"] {
margin-top: 0 !important; margin-top: 0 !important;
transform: none !important; transform: none !important;
} }
/* Immediate hide class for unhide button with smooth transition (had issues with the fade out.. so I removed it) */ /* Immediate hide class for unhide button with smooth transition (had issues with the fade out.. so I removed it) */
.hide-immediately { .hide-immediately {
opacity: 0 !important; opacity: 0 !important;
visibility: hidden !important; visibility: hidden !important;
pointer-events: none !important; pointer-events: none !important;
} }
[class^="_bar"] { [class^="_bar"] {
background-color: transparent; background-color: transparent;
} }
.radiant-lyrics-ui-hidden [class^="_bar"]>*:not(.hide-ui-button) { .radiant-lyrics-ui-hidden [class^="_bar"] > *:not(.hide-ui-button) {
opacity: 0 !important; opacity: 0 !important;
pointer-events: none !important; pointer-events: none !important;
transition: opacity 0.4s ease-in-out; transition: opacity 0.4s ease-in-out;
} }
/* Default state for bar elements */ /* Default state for bar elements */
[class^="_bar"]>* { [class^="_bar"] > * {
transition: opacity 0.4s ease-in-out; transition: opacity 0.4s ease-in-out;
} }
/* Hide search box and make it non-interactive */ /* Hide search box and make it non-interactive */
@@ -73,47 +75,74 @@
.radiant-lyrics-ui-hidden [data-test="main-layout-header"] [class*="input"], .radiant-lyrics-ui-hidden [data-test="main-layout-header"] [class*="input"],
.radiant-lyrics-ui-hidden header input, .radiant-lyrics-ui-hidden header input,
.radiant-lyrics-ui-hidden nav input { .radiant-lyrics-ui-hidden nav input {
pointer-events: none !important; pointer-events: none !important;
cursor: default !important; cursor: default !important;
user-select: none !important; user-select: none !important;
} }
/* Hide bottom left controls completely - no hover functionality */ /* Hide bottom left controls completely - no hover functionality */
/* Exclude heart button in player bar and make sure hidden buttons can't be clicked */ /* Exclude heart button in player bar and make sure hidden buttons can't be clicked */
.radiant-lyrics-ui-hidden [data-test="add-to-playlist"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden [data-test="remove-from-playlist"]:not([data-test="footer-player"] *), [data-test="add-to-playlist"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden [data-test="like-toggle"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden [data-test="dislike-toggle"]:not([data-test="footer-player"] *), [data-test="remove-from-playlist"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden [data-test="favorite-toggle"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden [data-test="heart-button"]:not([data-test="footer-player"] *), [data-test="like-toggle"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden [data-test="playlist-add"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
[data-test="dislike-toggle"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
[data-test="favorite-toggle"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
[data-test="heart-button"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
[data-test="playlist-add"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden [class*="_trackActions"], .radiant-lyrics-ui-hidden [class*="_trackActions"],
.radiant-lyrics-ui-hidden [class*="_bottomLeftControls"], .radiant-lyrics-ui-hidden [class*="_bottomLeftControls"],
.radiant-lyrics-ui-hidden [class*="_actionButtons"], .radiant-lyrics-ui-hidden [class*="_actionButtons"],
.radiant-lyrics-ui-hidden [class*="_favoriteButton"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
[class*="_favoriteButton"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden [class*="_addToPlaylist"], .radiant-lyrics-ui-hidden [class*="_addToPlaylist"],
.radiant-lyrics-ui-hidden [class*="_lowerLeft"], .radiant-lyrics-ui-hidden [class*="_lowerLeft"],
.radiant-lyrics-ui-hidden [class*="_bottomActions"], .radiant-lyrics-ui-hidden [class*="_bottomActions"],
.radiant-lyrics-ui-hidden [class*="_mediaControls"] > div:first-child, .radiant-lyrics-ui-hidden [class*="_mediaControls"] > div:first-child,
.radiant-lyrics-ui-hidden button[title*="Add to"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden button[title*="Remove from"]:not([data-test="footer-player"] *), button[title*="Add to"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden button[title*="Like"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden button[title*="Favorite"]:not([data-test="footer-player"] *), button[title*="Remove from"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden button[title*="Heart"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden button[aria-label*="Add to"]:not([data-test="footer-player"] *), button[title*="Like"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden button[aria-label*="Remove from"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden button[aria-label*="Like"]:not([data-test="footer-player"] *), button[title*="Favorite"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden button[aria-label*="Favorite"]:not([data-test="footer-player"] *), .radiant-lyrics-ui-hidden
.radiant-lyrics-ui-hidden button[aria-label*="Heart"]:not([data-test="footer-player"] *), button[title*="Heart"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
button[aria-label*="Add to"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
button[aria-label*="Remove from"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
button[aria-label*="Like"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
button[aria-label*="Favorite"]:not([data-test="footer-player"] *),
.radiant-lyrics-ui-hidden
button[aria-label*="Heart"]:not([data-test="footer-player"] *),
/* Target buttons in bottom left area specifically - (idk if this is needed.. but it's here) */ /* Target buttons in bottom left area specifically - (idk if this is needed.. but it's here) */
.radiant-lyrics-ui-hidden [class*="_nowPlayingContainer"] button[class*="_button"]:not(.unhide-ui-button), .radiant-lyrics-ui-hidden [class*="_nowPlayingContainer"]
.radiant-lyrics-ui-hidden [class*="_nowPlayingContainer"] [class*="_iconButton"]:not(.unhide-ui-button), button[class*="_button"]:not(.unhide-ui-button),
.radiant-lyrics-ui-hidden
[class*="_nowPlayingContainer"]
[class*="_iconButton"]:not(.unhide-ui-button),
/* Additional catch-all for bottom left area buttons - (idk if this is needed.. but it's here) */ /* Additional catch-all for bottom left area buttons - (idk if this is needed.. but it's here) */
.radiant-lyrics-ui-hidden [class*="_nowPlayingContainer"] > div > div:first-child button:not(.unhide-ui-button), .radiant-lyrics-ui-hidden [class*="_nowPlayingContainer"]
.radiant-lyrics-ui-hidden [class*="_nowPlayingContainer"] > div:first-child button:not(.unhide-ui-button) { > div
opacity: 0 !important; > div:first-child
pointer-events: none !important; button:not(.unhide-ui-button),
transition: opacity 0.5s ease-in-out !important; .radiant-lyrics-ui-hidden
[class*="_nowPlayingContainer"]
> div:first-child
button:not(.unhide-ui-button) {
opacity: 0 !important;
pointer-events: none !important;
transition: opacity 0.5s ease-in-out !important;
} }
/* No hover functionality in Hide UI Mode - buttons stay hidden.. yea thats right, you heard me */ /* No hover functionality in Hide UI Mode - buttons stay hidden.. yea thats right, you heard me */
@@ -146,33 +175,38 @@ button[aria-label*="Favorite"],
button[aria-label*="Heart"], button[aria-label*="Heart"],
[class*="_nowPlayingContainer"] button[class*="_button"]:not(.unhide-ui-button), [class*="_nowPlayingContainer"] button[class*="_button"]:not(.unhide-ui-button),
[class*="_nowPlayingContainer"] [class*="_iconButton"]:not(.unhide-ui-button), [class*="_nowPlayingContainer"] [class*="_iconButton"]:not(.unhide-ui-button),
[class*="_nowPlayingContainer"] > div > div:first-child button:not(.unhide-ui-button), [class*="_nowPlayingContainer"]
[class*="_nowPlayingContainer"] > div:first-child button:not(.unhide-ui-button) { > div
transition: opacity 0.5s ease-in-out; > div:first-child
button:not(.unhide-ui-button),
[class*="_nowPlayingContainer"]
> div:first-child
button:not(.unhide-ui-button) {
transition: opacity 0.5s ease-in-out;
} }
/* Smooth cover art movement when UI is hidden */ /* Smooth cover art movement when UI is hidden */
[class*="_albumImage"], [class*="_albumImage"],
[class*="_coverArt"], [class*="_coverArt"],
figure[class*="_albumImage"] { figure[class*="_albumImage"] {
transition: transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out;
} }
.radiant-lyrics-ui-hidden [class*="_albumImage"], .radiant-lyrics-ui-hidden [class*="_albumImage"],
.radiant-lyrics-ui-hidden [class*="_coverArt"], .radiant-lyrics-ui-hidden [class*="_coverArt"],
.radiant-lyrics-ui-hidden figure[class*="_albumImage"] { .radiant-lyrics-ui-hidden figure[class*="_albumImage"] {
transform: translateX(80px) !important; transform: translateX(80px) !important;
} }
/* Smooth track info wrapper movement when UI is hidden (Arists & Track Title) */ /* Smooth track info wrapper movement when UI is hidden (Arists & Track Title) */
[class*="_infoWrapper"], [class*="_infoWrapper"],
[class*="_textContainer"] { [class*="_textContainer"] {
transition: transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out;
} }
.radiant-lyrics-ui-hidden [class*="_infoWrapper"], .radiant-lyrics-ui-hidden [class*="_infoWrapper"],
.radiant-lyrics-ui-hidden [class*="_textContainer"] { .radiant-lyrics-ui-hidden [class*="_textContainer"] {
transform: translateX(40px) !important; transform: translateX(40px) !important;
} }
/* Move parent containers instead of lyrics container directly to preserve gradient fade */ /* Move parent containers instead of lyrics container directly to preserve gradient fade */
@@ -183,7 +217,7 @@ figure[class*="_albumImage"] {
[class*="_sidePanel"], [class*="_sidePanel"],
[class*="_lyricsSection"], [class*="_lyricsSection"],
[class*="_lyricsWrapper"] { [class*="_lyricsWrapper"] {
transition: transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out;
} }
.radiant-lyrics-ui-hidden [data-test="stream-metadata"], .radiant-lyrics-ui-hidden [data-test="stream-metadata"],
@@ -193,32 +227,46 @@ figure[class*="_albumImage"] {
.radiant-lyrics-ui-hidden [class*="_sidePanel"], .radiant-lyrics-ui-hidden [class*="_sidePanel"],
.radiant-lyrics-ui-hidden [class*="_lyricsSection"], .radiant-lyrics-ui-hidden [class*="_lyricsSection"],
.radiant-lyrics-ui-hidden [class*="_lyricsWrapper"] { .radiant-lyrics-ui-hidden [class*="_lyricsWrapper"] {
transform: translateX(60px) translateY(-70px) !important; transform: translateX(60px) translateY(-70px) !important;
} }
/* Hide UI button base styling - just the transition */ /* Hide UI button base styling - just the transition */
.hide-ui-button { .hide-ui-button {
transition: opacity 0.5s ease-in-out, visibility 0.5s ease-in-out, background-color 0.2s ease-in-out, transform 0.2s ease-in-out !important; transition:
opacity 0.5s ease-in-out,
visibility 0.5s ease-in-out,
background-color 0.2s ease-in-out,
transform 0.2s ease-in-out !important;
} }
/* Auto-fade styling for unhide button - (Keeps Text Visible, just not full opacity) | Cheers @Zyhn for the idea*/ /* Auto-fade styling for unhide button - (Keeps Text Visible, just not full opacity) | Cheers @Zyhn for the idea*/
.unhide-ui-button.auto-faded { .unhide-ui-button.auto-faded {
background-color: transparent !important; background-color: transparent !important;
border-color: transparent !important; border-color: transparent !important;
box-shadow: none !important; box-shadow: none !important;
backdrop-filter: none !important; backdrop-filter: none !important;
-webkit-backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
color: rgba(255, 255, 255, 0.8) !important; color: rgba(255, 255, 255, 0.8) !important;
transition: background-color 0.8s ease-in-out, border-color 0.8s ease-in-out, box-shadow 0.8s ease-in-out, backdrop-filter 0.8s ease-in-out, color 0.8s ease-in-out; transition:
background-color 0.8s ease-in-out,
border-color 0.8s ease-in-out,
box-shadow 0.8s ease-in-out,
backdrop-filter 0.8s ease-in-out,
color 0.8s ease-in-out;
} }
/* Restore button styling on hover */ /* Restore button styling on hover */
.unhide-ui-button.auto-faded:hover { .unhide-ui-button.auto-faded:hover {
background-color: rgba(255, 255, 255, 0.2) !important; background-color: rgba(255, 255, 255, 0.2) !important;
border-color: rgba(255, 255, 255, 0.3) !important; border-color: rgba(255, 255, 255, 0.3) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important;
-webkit-backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;
color: white !important; color: white !important;
transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out, box-shadow 0.3s ease-in-out, backdrop-filter 0.3s ease-in-out, color 0.3s ease-in-out; transition:
background-color 0.3s ease-in-out,
border-color 0.3s ease-in-out,
box-shadow 0.3s ease-in-out,
backdrop-filter 0.3s ease-in-out,
color 0.3s ease-in-out;
} }