mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
@@ -8,4 +8,4 @@
|
|||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
const hexColorRegex = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
|
|
||||||
|
|
||||||
if (hexColorRegex.test(trimmedInput) &&
|
|
||||||
!colorPresets.includes(trimmedInput) &&
|
|
||||||
!customColors.includes(trimmedInput)) {
|
|
||||||
const newCustomColors = [...customColors, trimmedInput];
|
|
||||||
setCustomColors(newCustomColors);
|
|
||||||
settings.customColors = newCustomColors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeCustomColor = (colorToRemove: string) => {
|
// Validate hex color format
|
||||||
const newCustomColors = customColors.filter(color => color !== colorToRemove);
|
const hexColorRegex = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
|
||||||
setCustomColors(newCustomColors);
|
|
||||||
settings.customColors = newCustomColors;
|
|
||||||
|
|
||||||
// If the removed color was the selected color (reset to white)
|
|
||||||
if (barColor === colorToRemove) {
|
|
||||||
updateColor("#ffffff");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const allColors = [...colorPresets, ...customColors];
|
if (
|
||||||
|
hexColorRegex.test(trimmedInput) &&
|
||||||
|
!colorPresets.includes(trimmedInput) &&
|
||||||
|
!customColors.includes(trimmedInput)
|
||||||
|
) {
|
||||||
|
const newCustomColors = [...customColors, trimmedInput];
|
||||||
|
setCustomColors(newCustomColors);
|
||||||
|
settings.customColors = newCustomColors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const removeCustomColor = (colorToRemove: string) => {
|
||||||
<LunaSettings>
|
const newCustomColors = customColors.filter(
|
||||||
<LunaSwitchSetting
|
(color) => color !== colorToRemove,
|
||||||
title="Bar Roundness"
|
);
|
||||||
desc="Enable rounded corners on visualizer bars"
|
setCustomColors(newCustomColors);
|
||||||
checked={barRounding}
|
settings.customColors = newCustomColors;
|
||||||
onChange={(_, checked) => {
|
|
||||||
setBarRounding(checked);
|
|
||||||
settings.barRounding = checked;
|
|
||||||
(window as any).updateAudioVisualizer?.();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LunaNumberSetting
|
|
||||||
title="Bar Count"
|
|
||||||
desc="Number of frequency bars to display"
|
|
||||||
min={8}
|
|
||||||
max={64}
|
|
||||||
step={1}
|
|
||||||
value={barCount}
|
|
||||||
onNumber={(value: number) => {
|
|
||||||
setBarCount(value);
|
|
||||||
settings.barCount = value;
|
|
||||||
(window as any).updateAudioVisualizer?.();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 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 */}
|
|
||||||
{/* Sorry @Inrixia <3 */}
|
|
||||||
|
|
||||||
<div style={{
|
|
||||||
padding: "16px 0",
|
|
||||||
display: "flex",
|
|
||||||
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 style={{ display: "flex", gap: "8px", alignItems: "center", position: "relative" }}>
|
|
||||||
<button
|
|
||||||
onClick={() => 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 */}
|
|
||||||
{shouldRender && (
|
|
||||||
<>
|
|
||||||
{/* Backdrop */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
background: "rgba(0,0,0,0.6)",
|
|
||||||
zIndex: 1000,
|
|
||||||
opacity: isAnimatingIn ? 1 : 0,
|
|
||||||
transition: "opacity 0.2s ease"
|
|
||||||
}}
|
|
||||||
onClick={closeColorPicker}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Color Picker Panel */}
|
|
||||||
<div style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
background: "rgba(20,20,20,0.98)",
|
|
||||||
backdropFilter: "blur(20px)",
|
|
||||||
WebkitBackdropFilter: "blur(20px)",
|
|
||||||
border: "1px solid rgba(255,255,255,0.15)",
|
|
||||||
borderRadius: "16px",
|
|
||||||
padding: "20px",
|
|
||||||
minWidth: "320px",
|
|
||||||
maxWidth: "90vw",
|
|
||||||
maxHeight: "90vh",
|
|
||||||
zIndex: 1001,
|
|
||||||
boxShadow: "0 20px 40px rgba(0,0,0,0.7)",
|
|
||||||
opacity: isAnimatingIn ? 1 : 0,
|
|
||||||
transform: isAnimatingIn ? "translate(-50%, -50%) scale(1)" : "translate(-50%, -50%) scale(0.9)",
|
|
||||||
transition: "all 0.2s ease"
|
|
||||||
}}>
|
|
||||||
<div style={{ marginBottom: "12px", color: "#fff", fontWeight: "bold", fontSize: "14px" }}>
|
|
||||||
Choose Color
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Color Grid */}
|
|
||||||
<div style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(7, 1fr)",
|
|
||||||
gap: "8px",
|
|
||||||
marginBottom: "16px"
|
|
||||||
}}>
|
|
||||||
{allColors.map((color, index) => {
|
|
||||||
const isCustomColor = customColors.includes(color);
|
|
||||||
const isHovered = hoveredColorIndex === index;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
style={{
|
|
||||||
position: "relative",
|
|
||||||
width: "32px",
|
|
||||||
height: "32px",
|
|
||||||
cursor: "pointer"
|
|
||||||
}}
|
|
||||||
className="color-item"
|
|
||||||
onMouseEnter={() => setHoveredColorIndex(index)}
|
|
||||||
onMouseLeave={() => setHoveredColorIndex(null)}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
updateColor(color);
|
|
||||||
closeColorPicker();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: barColor === color ? "2px solid #fff" : "1px solid rgba(255,255,255,0.2)",
|
|
||||||
background: color,
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "all 0.2s ease"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{isCustomColor && (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
removeCustomColor(color);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "-4px",
|
|
||||||
right: "-4px",
|
|
||||||
width: "16px",
|
|
||||||
height: "16px",
|
|
||||||
borderRadius: "50%",
|
|
||||||
border: "1px solid rgba(255,255,255,0.8)",
|
|
||||||
background: "rgba(0,0,0,0.8)",
|
|
||||||
color: "#fff",
|
|
||||||
cursor: "pointer",
|
|
||||||
fontSize: "10px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
opacity: isHovered ? 1 : 0,
|
|
||||||
transition: "opacity 0.2s ease",
|
|
||||||
zIndex: 10
|
|
||||||
}}
|
|
||||||
className="remove-button"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Custom Hex Input */}
|
|
||||||
<div style={{ marginBottom: "12px" }}>
|
|
||||||
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: "12px", marginBottom: "6px" }}>
|
|
||||||
Add Custom Color
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={customInput}
|
|
||||||
onChange={(e) => setCustomInput(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
updateColor(customInput);
|
|
||||||
addCustomColor();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder="#ffffff"
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
padding: "8px 12px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid rgba(255,255,255,0.2)",
|
|
||||||
background: "rgba(255,255,255,0.1)",
|
|
||||||
color: "#fff",
|
|
||||||
fontSize: "14px",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
boxSizing: "border-box"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
updateColor(customInput);
|
|
||||||
addCustomColor();
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
width: "32px",
|
|
||||||
height: "32px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid rgba(255,255,255,0.3)",
|
|
||||||
background: "rgba(255,255,255,0.15)",
|
|
||||||
color: "#fff",
|
|
||||||
cursor: "pointer",
|
|
||||||
fontSize: "16px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
transition: "all 0.2s ease"
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.background = "rgba(255,255,255,0.25)";
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.background = "rgba(255,255,255,0.15)";
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Close Button (Done) - Also runs when color chosen*/}
|
|
||||||
<button
|
|
||||||
onClick={closeColorPicker}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
padding: "8px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid rgba(255,255,255,0.2)",
|
|
||||||
background: "rgba(255,255,255,0.1)",
|
|
||||||
color: "#fff",
|
|
||||||
cursor: "pointer",
|
|
||||||
fontSize: "12px"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</LunaSettings>
|
// If the removed color was the selected color (reset to white)
|
||||||
);
|
if (barColor === colorToRemove) {
|
||||||
};
|
updateColor("#ffffff");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const allColors = [...colorPresets, ...customColors];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LunaSettings>
|
||||||
|
<LunaSwitchSetting
|
||||||
|
title="Bar Roundness"
|
||||||
|
desc="Enable rounded corners on visualizer bars"
|
||||||
|
checked={barRounding}
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
setBarRounding(checked);
|
||||||
|
settings.barRounding = checked;
|
||||||
|
(window as any).updateAudioVisualizer?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LunaNumberSetting
|
||||||
|
title="Bar Count"
|
||||||
|
desc="Number of frequency bars to display"
|
||||||
|
min={8}
|
||||||
|
max={64}
|
||||||
|
step={1}
|
||||||
|
value={barCount}
|
||||||
|
onNumber={(value: number) => {
|
||||||
|
setBarCount(value);
|
||||||
|
settings.barCount = value;
|
||||||
|
(window as any).updateAudioVisualizer?.();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 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 */}
|
||||||
|
{/* Sorry @Inrixia <3 */}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "16px 0",
|
||||||
|
display: "flex",
|
||||||
|
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
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "8px",
|
||||||
|
alignItems: "center",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
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 */}
|
||||||
|
{shouldRender && (
|
||||||
|
<>
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: "rgba(0,0,0,0.6)",
|
||||||
|
zIndex: 1000,
|
||||||
|
opacity: isAnimatingIn ? 1 : 0,
|
||||||
|
transition: "opacity 0.2s ease",
|
||||||
|
}}
|
||||||
|
onClick={closeColorPicker}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Color Picker Panel */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
background: "rgba(20,20,20,0.98)",
|
||||||
|
backdropFilter: "blur(20px)",
|
||||||
|
WebkitBackdropFilter: "blur(20px)",
|
||||||
|
border: "1px solid rgba(255,255,255,0.15)",
|
||||||
|
borderRadius: "16px",
|
||||||
|
padding: "20px",
|
||||||
|
minWidth: "320px",
|
||||||
|
maxWidth: "90vw",
|
||||||
|
maxHeight: "90vh",
|
||||||
|
zIndex: 1001,
|
||||||
|
boxShadow: "0 20px 40px rgba(0,0,0,0.7)",
|
||||||
|
opacity: isAnimatingIn ? 1 : 0,
|
||||||
|
transform: isAnimatingIn
|
||||||
|
? "translate(-50%, -50%) scale(1)"
|
||||||
|
: "translate(-50%, -50%) scale(0.9)",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: "12px",
|
||||||
|
color: "#fff",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Choose Color
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Color Grid */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(7, 1fr)",
|
||||||
|
gap: "8px",
|
||||||
|
marginBottom: "16px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{allColors.map((color, index) => {
|
||||||
|
const isCustomColor = customColors.includes(color);
|
||||||
|
const isHovered = hoveredColorIndex === index;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
className="color-item"
|
||||||
|
onMouseEnter={() => setHoveredColorIndex(index)}
|
||||||
|
onMouseLeave={() => setHoveredColorIndex(null)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
updateColor(color);
|
||||||
|
closeColorPicker();
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border:
|
||||||
|
barColor === color
|
||||||
|
? "2px solid #fff"
|
||||||
|
: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
background: color,
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{isCustomColor && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
removeCustomColor(color);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-4px",
|
||||||
|
right: "-4px",
|
||||||
|
width: "16px",
|
||||||
|
height: "16px",
|
||||||
|
borderRadius: "50%",
|
||||||
|
border: "1px solid rgba(255,255,255,0.8)",
|
||||||
|
background: "rgba(0,0,0,0.8)",
|
||||||
|
color: "#fff",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "10px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: isHovered ? 1 : 0,
|
||||||
|
transition: "opacity 0.2s ease",
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
className="remove-button"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Custom Hex Input */}
|
||||||
|
<div style={{ marginBottom: "12px" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: "rgba(255,255,255,0.7)",
|
||||||
|
fontSize: "12px",
|
||||||
|
marginBottom: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Custom Color
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "8px",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={customInput}
|
||||||
|
onChange={(e) => setCustomInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
updateColor(customInput);
|
||||||
|
addCustomColor();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="#ffffff"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: "8px 12px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
background: "rgba(255,255,255,0.1)",
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
updateColor(customInput);
|
||||||
|
addCustomColor();
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.3)",
|
||||||
|
background: "rgba(255,255,255,0.15)",
|
||||||
|
color: "#fff",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "16px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background =
|
||||||
|
"rgba(255,255,255,0.25)";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background =
|
||||||
|
"rgba(255,255,255,0.15)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Close Button (Done) - Also runs when color chosen*/}
|
||||||
|
<button
|
||||||
|
onClick={closeColorPicker}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "8px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.2)",
|
||||||
|
background: "rgba(255,255,255,0.1)",
|
||||||
|
color: "#fff",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LunaSettings>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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
|
|
||||||
if (!analyser) {
|
|
||||||
analyser = audioContext.createAnalyser();
|
|
||||||
analyser.fftSize = 512; // Fixed power of 2 that provides enough frequency bins
|
|
||||||
analyser.smoothingTimeConstant = config.smoothing;
|
|
||||||
dataArray = new Uint8Array(analyser.frequencyBinCount);
|
|
||||||
log("Created AnalyserNode");
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt audio connection if not already connected
|
|
||||||
if (!isSourceConnected && audioElement !== currentAudioElement) {
|
|
||||||
try {
|
|
||||||
// Create audio source - this might fail if already connected elsewhere
|
|
||||||
audioSource = audioContext.createMediaElementSource(audioElement);
|
|
||||||
audioSource.connect(analyser);
|
|
||||||
// CRITICAL: connect back to destination for audio output (otherwise no sound)
|
|
||||||
analyser.connect(audioContext.destination);
|
|
||||||
|
|
||||||
currentAudioElement = audioElement;
|
|
||||||
isSourceConnected = true;
|
|
||||||
log("Connected to audio stream with output");
|
|
||||||
} catch (error) {
|
|
||||||
// Audio is connected elsewhere - that's fine, we just can't visualize
|
|
||||||
if (error instanceof Error && error.message.includes('already connected')) {
|
|
||||||
log("Audio already connected elsewhere - skipping visualization");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume context only if needed and don't wait for it
|
// create analyser
|
||||||
// (otherwise it will wait for the audio to start playing)
|
if (!analyser) {
|
||||||
if (audioContext.state === 'suspended') {
|
analyser = audioContext.createAnalyser();
|
||||||
audioContext.resume().catch(() => {}); // Fire and forget
|
analyser.fftSize = 512; // Fixed power of 2 that provides enough frequency bins
|
||||||
}
|
analyser.smoothingTimeConstant = config.smoothing;
|
||||||
|
dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
log("Created AnalyserNode");
|
||||||
|
}
|
||||||
|
|
||||||
// Create UI only if it doesn't exist
|
// attempt audio connection if not already connected
|
||||||
if (!visualizerContainer) {
|
if (!isSourceConnected && audioElement !== currentAudioElement) {
|
||||||
createVisualizerUI();
|
try {
|
||||||
}
|
// Create audio source - this might fail if already connected elsewhere
|
||||||
|
audioSource = audioContext.createMediaElementSource(audioElement);
|
||||||
// Start animation only if not already running
|
audioSource.connect(analyser);
|
||||||
if (!animationId) {
|
// CRITICAL: connect back to destination for audio output (otherwise no sound)
|
||||||
animate();
|
analyser.connect(audioContext.destination);
|
||||||
}
|
|
||||||
|
currentAudioElement = audioElement;
|
||||||
} catch (err) {
|
isSourceConnected = true;
|
||||||
// log errors
|
log("Connected to audio stream with output");
|
||||||
console.error(err);
|
} catch (error) {
|
||||||
}
|
// Audio is connected elsewhere - that's fine, we just can't visualize
|
||||||
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
error.message.includes("already connected")
|
||||||
|
) {
|
||||||
|
log("Audio already connected elsewhere - skipping visualization");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume context only if needed and don't wait for it
|
||||||
|
// (otherwise it will wait for the audio to start playing)
|
||||||
|
if (audioContext.state === "suspended") {
|
||||||
|
audioContext.resume().catch(() => {}); // Fire and forget
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create UI only if it doesn't exist
|
||||||
|
if (!visualizerContainer) {
|
||||||
|
createVisualizerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start animation only if not already running
|
||||||
|
if (!animationId) {
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// log errors
|
||||||
|
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;
|
|
||||||
|
|
||||||
// Find the search bar
|
if (!config.enabled) return;
|
||||||
const searchField = document.querySelector('input[class*="_searchField"]') as HTMLInputElement;
|
|
||||||
if (!searchField) {
|
|
||||||
warn("Search field not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchContainer = searchField.parentElement;
|
// Find the search bar
|
||||||
if (!searchContainer) {
|
const searchField = document.querySelector(
|
||||||
warn("Search container not found");
|
'input[class*="_searchField"]',
|
||||||
return;
|
) as HTMLInputElement;
|
||||||
}
|
if (!searchField) {
|
||||||
|
warn("Search field not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create visualizer container
|
const searchContainer = searchField.parentElement;
|
||||||
visualizerContainer = document.createElement('div');
|
if (!searchContainer) {
|
||||||
visualizerContainer.id = 'audio-visualizer-container';
|
warn("Search container not found");
|
||||||
visualizerContainer.style.cssText = `
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create visualizer container
|
||||||
|
visualizerContainer = document.createElement("div");
|
||||||
|
visualizerContainer.id = "audio-visualizer-container";
|
||||||
|
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
|
||||||
@@ -342,10 +370,10 @@ const drawBars = (): void => {
|
|||||||
// 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 amplitude = (dataArray[dataIndex] - 128) * config.sensitivity * amplitudeScale;
|
// const amplitude = (dataArray[dataIndex] - 128) * config.sensitivity * amplitudeScale;
|
||||||
|
|
||||||
// const x = (i / config.barCount) * canvas.width;
|
// const x = (i / config.barCount) * canvas.width;
|
||||||
// const y = centerY + amplitude;
|
// const y = centerY + amplitude;
|
||||||
|
|
||||||
// if (i === 0) {
|
// if (i === 0) {
|
||||||
// canvasContext.moveTo(x, y);
|
// canvasContext.moveTo(x, y);
|
||||||
// } else {
|
// } else {
|
||||||
@@ -370,13 +398,13 @@ const drawBars = (): void => {
|
|||||||
// 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 amplitude = (dataArray[dataIndex] * config.sensitivity) / 255;
|
// const amplitude = (dataArray[dataIndex] * config.sensitivity) / 255;
|
||||||
|
|
||||||
// const angle = (i / config.barCount) * Math.PI * 2;
|
// const angle = (i / config.barCount) * Math.PI * 2;
|
||||||
// const startX = centerX + Math.cos(angle) * radius * 0.7;
|
// const startX = centerX + Math.cos(angle) * radius * 0.7;
|
||||||
// const startY = centerY + Math.sin(angle) * radius * 0.7;
|
// const startY = centerY + Math.sin(angle) * radius * 0.7;
|
||||||
// const endX = centerX + Math.cos(angle) * radius * (0.7 + amplitude * 0.3);
|
// const endX = centerX + Math.cos(angle) * radius * (0.7 + amplitude * 0.3);
|
||||||
// const endY = centerY + Math.sin(angle) * radius * (0.7 + amplitude * 0.3);
|
// const endY = centerY + Math.sin(angle) * radius * (0.7 + amplitude * 0.3);
|
||||||
|
|
||||||
// canvasContext.beginPath();
|
// canvasContext.beginPath();
|
||||||
// canvasContext.moveTo(startX, startY);
|
// canvasContext.moveTo(startX, startY);
|
||||||
// canvasContext.lineTo(endX, endY);
|
// canvasContext.lineTo(endX, endY);
|
||||||
@@ -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,120 +437,121 @@ 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 = () => {
|
|
||||||
checkCount++;
|
|
||||||
|
|
||||||
// Only try to initialize once when music starts playing
|
|
||||||
if (PlayState.playing && !hasTriedInitialization) {
|
|
||||||
hasTriedInitialization = true;
|
|
||||||
log("Initializing audio visualizer...");
|
|
||||||
|
|
||||||
// Initialize immediately - no delay (after audio starts playing ofc)
|
|
||||||
initializeAudioVisualizer().then(() => {
|
|
||||||
if (audioContext && analyser) {
|
|
||||||
log("Audio visualizer ready!");
|
|
||||||
} else {
|
|
||||||
hasTriedInitialization = false; // Allow retry if failed
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!PlayState.playing && hasTriedInitialization) {
|
|
||||||
// Reset try flag when music stops so it can try again next time (otherwise it explode)
|
|
||||||
hasTriedInitialization = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep animation running regardless of play state
|
|
||||||
if (!animationId) {
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start with fast checking, then slow down
|
const checkAndInitialize = () => {
|
||||||
const fastInterval = setInterval(() => {
|
checkCount++;
|
||||||
checkAndInitialize();
|
|
||||||
if (checkCount > 10) { // After 10 quick checks, switch to slower
|
// Only try to initialize once when music starts playing
|
||||||
clearInterval(fastInterval);
|
if (PlayState.playing && !hasTriedInitialization) {
|
||||||
const slowInterval = setInterval(checkAndInitialize, 2000);
|
hasTriedInitialization = true;
|
||||||
unloads.add(() => clearInterval(slowInterval));
|
log("Initializing audio visualizer...");
|
||||||
}
|
|
||||||
}, 200); // Check every 200ms initially
|
// Initialize immediately - no delay (after audio starts playing ofc)
|
||||||
|
initializeAudioVisualizer().then(() => {
|
||||||
unloads.add(() => clearInterval(fastInterval));
|
if (audioContext && analyser) {
|
||||||
|
log("Audio visualizer ready!");
|
||||||
// Immediate first check
|
} else {
|
||||||
checkAndInitialize();
|
hasTriedInitialization = false; // Allow retry if failed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (!PlayState.playing && hasTriedInitialization) {
|
||||||
|
// Reset try flag when music stops so it can try again next time (otherwise it explode)
|
||||||
|
hasTriedInitialization = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep animation running regardless of play state
|
||||||
|
if (!animationId) {
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start with fast checking, then slow down
|
||||||
|
const fastInterval = setInterval(() => {
|
||||||
|
checkAndInitialize();
|
||||||
|
if (checkCount > 10) {
|
||||||
|
// After 10 quick checks, switch to slower
|
||||||
|
clearInterval(fastInterval);
|
||||||
|
const slowInterval = setInterval(checkAndInitialize, 2000);
|
||||||
|
unloads.add(() => clearInterval(slowInterval));
|
||||||
|
}
|
||||||
|
}, 200); // Check every 200ms initially
|
||||||
|
|
||||||
|
unloads.add(() => clearInterval(fastInterval));
|
||||||
|
|
||||||
|
// Immediate first check
|
||||||
|
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
|
||||||
unloads.add(completeCleanup);
|
unloads.add(completeCleanup);
|
||||||
|
|
||||||
// Start initialization
|
// Start initialization
|
||||||
initialize();
|
initialize();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LunaUnload, Tracer } from "@luna/core";
|
import { type LunaUnload, Tracer } from "@luna/core";
|
||||||
import { StyleTag } from "@luna/lib";
|
import { StyleTag } from "@luna/lib";
|
||||||
|
|
||||||
// Import CSS directly using Luna's file:// syntax - Took me a while to figure out <3
|
// Import CSS directly using Luna's file:// syntax - Took me a while to figure out <3
|
||||||
@@ -9,110 +9,135 @@ export const { trace } = Tracer("[Copy Lyrics]");
|
|||||||
// clean up resources
|
// clean up resources
|
||||||
export const unloads = new Set<LunaUnload>();
|
export const unloads = new Set<LunaUnload>();
|
||||||
|
|
||||||
// StyleTag for lyrics selection styling
|
// Style injection via side effect
|
||||||
const lyricsStyleTag = new StyleTag("Copy-Lyrics", unloads, unlockSelection);
|
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 = (): void => {
|
||||||
isSelecting = true;
|
isSelecting = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseUp = function (event: MouseEvent): void {
|
const onMouseUp = (): void => {
|
||||||
if (isSelecting) {
|
if (isSelecting) {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (selection && selection.toString().length > 0) {
|
if (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: Node | null = range.commonAncestorContainer;
|
||||||
|
|
||||||
// If the container is NOT an element and a document, adjust it.
|
|
||||||
if (
|
|
||||||
container.nodeType !== Node.ELEMENT_NODE &&
|
|
||||||
container.nodeType !== Node.DOCUMENT_NODE
|
|
||||||
) {
|
|
||||||
// Get the parent element if it's a text node
|
|
||||||
const parentElement = container.parentElement;
|
|
||||||
if (parentElement && parentElement.hasAttribute("data-current")) {
|
|
||||||
let text_ = selection.toString().trim();
|
|
||||||
SetClipboard(text_);
|
|
||||||
trace.msg.log("Copied to clipboard!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all the spans inside the container.
|
// Normalize container: if it's a text node, use its parent element/node
|
||||||
const spans = (container as Element).getElementsByTagName("span");
|
if (container && container.nodeType === Node.TEXT_NODE) {
|
||||||
for (let span of spans) {
|
container = (container.parentElement ?? container.parentNode) as Node | null;
|
||||||
if (selection.containsNode(span, true)) {
|
}
|
||||||
selectedSpans.push(span as HTMLSpanElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concat the text of the selected spans.
|
// If parent has data-current, treat as single-line copy case
|
||||||
let hasCorrectAttribute = false;
|
if (
|
||||||
let text = "";
|
container &&
|
||||||
selectedSpans.forEach((span) => {
|
container.nodeType === Node.ELEMENT_NODE &&
|
||||||
if (span.hasAttribute("data-current")) {
|
(container as Element).hasAttribute("data-current")
|
||||||
hasCorrectAttribute = true;
|
) {
|
||||||
text += span.textContent + "\n";
|
const text_ = selection.toString().trim();
|
||||||
if ([...span.classList].some((className) => className.startsWith("endOfStanza--"))) {
|
SetClipboard(text_);
|
||||||
text += "\n";
|
trace.msg.log("Copied to clipboard!");
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
text = text.trim();
|
// Ensure we have an Element or Document before querying
|
||||||
|
if (
|
||||||
|
!container ||
|
||||||
|
(container.nodeType !== Node.ELEMENT_NODE &&
|
||||||
|
container.nodeType !== Node.DOCUMENT_NODE)
|
||||||
|
) {
|
||||||
|
isSelecting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasCorrectAttribute) {
|
// Get all the spans inside the container.
|
||||||
SetClipboard(text);
|
const spans = (container as Element | Document).getElementsByTagName(
|
||||||
trace.msg.log("Copied to clipboard!");
|
"span",
|
||||||
selection.removeAllRanges();
|
);
|
||||||
}
|
for (const span of spans) {
|
||||||
}
|
if (selection.containsNode(span, true)) {
|
||||||
isSelecting = false;
|
selectedSpans.push(span as HTMLSpanElement);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concat the text of the selected spans.
|
||||||
|
let hasCorrectAttribute = false;
|
||||||
|
let text = "";
|
||||||
|
selectedSpans.forEach((span) => {
|
||||||
|
if (span.hasAttribute("data-current")) {
|
||||||
|
hasCorrectAttribute = true;
|
||||||
|
text += span.textContent + "\n";
|
||||||
|
if (
|
||||||
|
[...span.classList].some((className) =>
|
||||||
|
className.startsWith("endOfStanza--"),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
text += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
text = text.trim();
|
||||||
|
|
||||||
|
if (hasCorrectAttribute) {
|
||||||
|
SetClipboard(text);
|
||||||
|
trace.msg.log("Copied to clipboard!");
|
||||||
|
selection.removeAllRanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isSelecting = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickHooked = function (event: MouseEvent): boolean | void {
|
const onClickHooked = (event: MouseEvent): boolean | undefined => {
|
||||||
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;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
document.addEventListener("click", onClickHooked, true);
|
document.addEventListener("click", onClickHooked, true);
|
||||||
|
|
||||||
document.addEventListener("mousedown", onMouseDown);
|
document.addEventListener("mousedown", onMouseDown);
|
||||||
|
|
||||||
document.addEventListener("mouseup", onMouseUp);
|
document.addEventListener("mouseup", onMouseUp);
|
||||||
|
|
||||||
// Add cleanup to unloads
|
// Add cleanup to unloads
|
||||||
unloads.add(() => {
|
unloads.add((): void => {
|
||||||
// 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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ 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 = () => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LunaUnload, Tracer } from "@luna/core";
|
import { type LunaUnload, Tracer } from "@luna/core";
|
||||||
import { StyleTag, ContextMenu } from "@luna/lib";
|
import { StyleTag } from "@luna/lib";
|
||||||
import { settings, Settings } from "./Settings";
|
import { settings, Settings } from "./Settings";
|
||||||
|
|
||||||
// Import CSS directly using Luna's file:// syntax
|
// Import CSS directly using Luna's file:// syntax
|
||||||
@@ -13,8 +13,8 @@ export { Settings };
|
|||||||
// Clean up resources
|
// Clean up resources
|
||||||
export const unloads = new Set<LunaUnload>();
|
export const unloads = new Set<LunaUnload>();
|
||||||
|
|
||||||
// StyleTag for element hider
|
// StyleTag for element hider (side-effect)
|
||||||
const styleTag = new StyleTag("Element-Hider", unloads, styles);
|
new StyleTag("Element-Hider", unloads, styles);
|
||||||
|
|
||||||
// State management
|
// State management
|
||||||
let targetElement: HTMLElement | null = null;
|
let targetElement: HTMLElement | null = null;
|
||||||
@@ -30,39 +30,54 @@ function generateElementSelector(element: HTMLElement): string {
|
|||||||
if (element.id) {
|
if (element.id) {
|
||||||
return `#${element.id}`;
|
return `#${element.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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}"]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 3: Combination of tag + specific classes + position
|
// Priority 3: Combination of tag + specific classes + position
|
||||||
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,26 +85,36 @@ 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}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trace.log(`Generated specific selector: ${selector}`);
|
trace.log(`Generated specific selector: ${selector}`);
|
||||||
return selector;
|
return selector;
|
||||||
}
|
}
|
||||||
@@ -100,16 +125,16 @@ 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) {
|
||||||
settings.hiddenElements.push(elementInfo);
|
settings.hiddenElements.push(elementInfo);
|
||||||
trace.log(`Saved element: ${elementInfo.selector}`);
|
trace.log(`Saved element: ${elementInfo.selector}`);
|
||||||
@@ -119,17 +144,18 @@ function saveHiddenElement(element: HTMLElement): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove hidden element from persistent storage (for unhiding)
|
// Remove hidden element from persistent storage (for unhiding) - currently unused
|
||||||
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) {
|
// );
|
||||||
settings.hiddenElements.splice(index, 1);
|
// if (index !== -1) {
|
||||||
trace.log(`Permanently removed: ${selector}`);
|
// settings.hiddenElements.splice(index, 1);
|
||||||
trace.log(`Remaining stored: ${settings.hiddenElements.length}`);
|
// trace.log(`Permanently removed: ${selector}`);
|
||||||
}
|
// trace.log(`Remaining stored: ${settings.hiddenElements.length}`);
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Check if an element matches any stored selector (EXACT match only)
|
// Check if an element matches any stored selector (EXACT match only)
|
||||||
function matchesStoredSelector(element: HTMLElement): boolean {
|
function matchesStoredSelector(element: HTMLElement): boolean {
|
||||||
@@ -143,58 +169,67 @@ function matchesStoredSelector(element: HTMLElement): boolean {
|
|||||||
trace.warn(`Invalid selector: ${storedElement.selector}`, error);
|
trace.warn(`Invalid selector: ${storedElement.selector}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide element directly without animation
|
// Hide element directly without animation
|
||||||
function hideElementDirectly(element: HTMLElement): void {
|
function hideElementDirectly(element: HTMLElement): void {
|
||||||
if (hiddenElements.has(element)) return;
|
if (hiddenElements.has(element)) return;
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
// Store reference to the element
|
// Store reference to the element
|
||||||
const elementToHide = targetElement;
|
const elementToHide = targetElement;
|
||||||
|
|
||||||
// Save to persistent storage
|
// Save to persistent storage
|
||||||
saveHiddenElement(elementToHide);
|
saveHiddenElement(elementToHide);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
// Clear target reference
|
// Clear target reference
|
||||||
targetElement = null;
|
targetElement = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear both storage and runtime collections
|
// Clear both storage and runtime collections
|
||||||
settings.hiddenElements = [];
|
settings.hiddenElements = [];
|
||||||
hiddenElements = new WeakSet<HTMLElement>();
|
hiddenElements = new WeakSet<HTMLElement>();
|
||||||
@@ -204,36 +239,42 @@ function unhideAllElements(): void {
|
|||||||
// Process all elements in the document to hide matching ones (with strict matching)
|
// Process all elements in the document to hide matching ones (with strict matching)
|
||||||
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
|
||||||
settings.hiddenElements.forEach((storedElement, index) => {
|
settings.hiddenElements.forEach((storedElement, index) => {
|
||||||
try {
|
try {
|
||||||
trace.log(`Searching for: ${storedElement.selector}`);
|
trace.log(`Searching for: ${storedElement.selector}`);
|
||||||
const elements = document.querySelectorAll(storedElement.selector);
|
const elements = document.querySelectorAll(storedElement.selector);
|
||||||
trace.log(`Found ${elements.length} matches for selector ${index + 1}`);
|
trace.log(`Found ${elements.length} matches for selector ${index + 1}`);
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
elements.forEach((element, elemIndex) => {
|
elements.forEach((element, elemIndex) => {
|
||||||
const htmlElement = element as HTMLElement;
|
const htmlElement = element as HTMLElement;
|
||||||
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) {
|
||||||
trace.warn(`Invalid selector: ${storedElement.selector}`, error);
|
trace.warn(`Invalid selector: ${storedElement.selector}`, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hiddenCount > 0) {
|
if (hiddenCount > 0) {
|
||||||
trace.log(`Total elements hidden: ${hiddenCount}`);
|
trace.log(`Total elements hidden: ${hiddenCount}`);
|
||||||
}
|
}
|
||||||
@@ -241,19 +282,19 @@ 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;
|
||||||
|
|
||||||
// Check the element itself
|
// Check the element itself
|
||||||
if (matchesStoredSelector(element)) {
|
if (matchesStoredSelector(element)) {
|
||||||
hideElementDirectly(element);
|
hideElementDirectly(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
@@ -264,26 +305,33 @@ function processNewElements(addedNodes: NodeList): void {
|
|||||||
// Set up reactive element observer
|
// Set up reactive element observer
|
||||||
function setupElementObserver(): void {
|
function setupElementObserver(): void {
|
||||||
if (elementObserver) return;
|
if (elementObserver) return;
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global functions
|
// Global functions
|
||||||
(window as any).showAllElementsFromSettings = unhideAllElements;
|
declare global {
|
||||||
(window as any).debugElementHider = () => {
|
interface Window {
|
||||||
|
showAllElementsFromSettings?: () => void;
|
||||||
|
debugElementHider?: () => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showAllElementsFromSettings = unhideAllElements;
|
||||||
|
window.debugElementHider = () => {
|
||||||
trace.log(`=== Element Hider Debug Info ===`);
|
trace.log(`=== Element Hider Debug Info ===`);
|
||||||
trace.log(`Stored elements: ${settings.hiddenElements.length}`);
|
trace.log(`Stored elements: ${settings.hiddenElements.length}`);
|
||||||
trace.log(`Currently hidden elements: ${hiddenElementsArray.length}`);
|
trace.log(`Currently hidden elements: ${hiddenElementsArray.length}`);
|
||||||
@@ -297,19 +345,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 +369,70 @@ 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) => {
|
||||||
// Don't interfere with native context menus on inputs, textareas, etc.
|
const target = event.target as HTMLElement;
|
||||||
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
|
|
||||||
currentContextElement = null;
|
// Don't interfere with native context menus on inputs, textareas, etc.
|
||||||
return;
|
if (
|
||||||
}
|
target.tagName === "INPUT" ||
|
||||||
|
target.tagName === "TEXTAREA" ||
|
||||||
// Don't show menu on our own custom menu
|
target.isContentEditable
|
||||||
if (target.closest(".element-hider-custom-menu")) {
|
) {
|
||||||
return;
|
currentContextElement = null;
|
||||||
}
|
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 show menu on our own custom menu
|
||||||
|
if (target.closest(".element-hider-custom-menu")) {
|
||||||
// Don't prevent default initially - let Luna try to handle the context menu
|
return;
|
||||||
}, true);
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Allow native context menu by default; we'll show our custom menu only if needed
|
||||||
|
|
||||||
|
// 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) => {
|
||||||
// If clicking outside our custom menu, close it
|
const target = event.target as HTMLElement;
|
||||||
if (customMenu && !target.closest(".element-hider-custom-menu")) {
|
|
||||||
closeCustomMenu();
|
// If clicking outside our custom menu, close it
|
||||||
removeHighlight();
|
if (customMenu && !target.closest(".element-hider-custom-menu")) {
|
||||||
}
|
closeCustomMenu();
|
||||||
}, true);
|
removeHighlight();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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();
|
||||||
@@ -386,7 +445,7 @@ document.addEventListener('keydown', (event: KeyboardEvent) => {
|
|||||||
function createCustomMenu(): HTMLElement {
|
function createCustomMenu(): HTMLElement {
|
||||||
const menu = document.createElement("div");
|
const menu = document.createElement("div");
|
||||||
menu.className = "element-hider-custom-menu";
|
menu.className = "element-hider-custom-menu";
|
||||||
|
|
||||||
// Hide Element option
|
// Hide Element option
|
||||||
const hideItem = document.createElement("button");
|
const hideItem = document.createElement("button");
|
||||||
hideItem.className = "element-hider-menu-item";
|
hideItem.className = "element-hider-menu-item";
|
||||||
@@ -398,18 +457,18 @@ function createCustomMenu(): HTMLElement {
|
|||||||
closeCustomMenu();
|
closeCustomMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add hover effects for highlighting
|
// Add hover effects for highlighting
|
||||||
hideItem.addEventListener("mouseenter", () => {
|
hideItem.addEventListener("mouseenter", () => {
|
||||||
if (currentContextElement) {
|
if (currentContextElement) {
|
||||||
highlightElement(currentContextElement);
|
highlightElement(currentContextElement);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
hideItem.addEventListener("mouseleave", () => {
|
hideItem.addEventListener("mouseleave", () => {
|
||||||
removeHighlight();
|
removeHighlight();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unhide All Elements option
|
// Unhide All Elements option
|
||||||
const unhideAllItem = document.createElement("button");
|
const unhideAllItem = document.createElement("button");
|
||||||
unhideAllItem.className = "element-hider-menu-item";
|
unhideAllItem.className = "element-hider-menu-item";
|
||||||
@@ -418,28 +477,28 @@ function createCustomMenu(): HTMLElement {
|
|||||||
unhideAllElements();
|
unhideAllElements();
|
||||||
closeCustomMenu();
|
closeCustomMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.appendChild(hideItem);
|
menu.appendChild(hideItem);
|
||||||
menu.appendChild(unhideAllItem);
|
menu.appendChild(unhideAllItem);
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show custom context menu
|
// Show custom context menu
|
||||||
function showCustomMenu(x: number, y: number): void {
|
function showCustomMenu(x: number, y: number): void {
|
||||||
closeCustomMenu();
|
closeCustomMenu();
|
||||||
|
|
||||||
customMenu = createCustomMenu();
|
customMenu = createCustomMenu();
|
||||||
document.body.appendChild(customMenu);
|
document.body.appendChild(customMenu);
|
||||||
|
|
||||||
// Position the menu
|
// Position the menu
|
||||||
const rect = customMenu.getBoundingClientRect();
|
const rect = customMenu.getBoundingClientRect();
|
||||||
const finalX = Math.min(x, window.innerWidth - rect.width - 10);
|
const finalX = Math.min(x, window.innerWidth - rect.width - 10);
|
||||||
const finalY = Math.min(y, window.innerHeight - rect.height - 10);
|
const finalY = Math.min(y, window.innerHeight - rect.height - 10);
|
||||||
|
|
||||||
customMenu.style.left = `${finalX}px`;
|
customMenu.style.left = `${finalX}px`;
|
||||||
customMenu.style.top = `${finalY}px`;
|
customMenu.style.top = `${finalY}px`;
|
||||||
|
|
||||||
trace.log(`Context menu opened for: ${currentContextElement?.tagName}`);
|
trace.log(`Context menu opened for: ${currentContextElement?.tagName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,7 +508,7 @@ function closeCustomMenu(): void {
|
|||||||
customMenu.remove();
|
customMenu.remove();
|
||||||
customMenu = null;
|
customMenu = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contextMenuTimeout) {
|
if (contextMenuTimeout) {
|
||||||
clearTimeout(contextMenuTimeout);
|
clearTimeout(contextMenuTimeout);
|
||||||
contextMenuTimeout = null;
|
contextMenuTimeout = null;
|
||||||
@@ -462,11 +521,18 @@ const contextMenuObserver = new MutationObserver((mutations) => {
|
|||||||
mutation.addedNodes.forEach((node) => {
|
mutation.addedNodes.forEach((node) => {
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
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
|
||||||
waitingForBuiltInMenu = false;
|
waitingForBuiltInMenu = false;
|
||||||
@@ -485,8 +551,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;
|
||||||
@@ -502,46 +568,47 @@ function addElementHiderOptions(contextMenu: HTMLElement): void {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
`;
|
`;
|
||||||
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -549,7 +616,7 @@ function addElementHiderOptions(contextMenu: HTMLElement): void {
|
|||||||
`;
|
`;
|
||||||
contextMenu.appendChild(separator);
|
contextMenu.appendChild(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add our buttons
|
// Add our buttons
|
||||||
contextMenu.appendChild(hideButton);
|
contextMenu.appendChild(hideButton);
|
||||||
contextMenu.appendChild(unhideAllButton);
|
contextMenu.appendChild(unhideAllButton);
|
||||||
@@ -558,28 +625,28 @@ 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...");
|
||||||
|
|
||||||
// Process immediately when DOM is ready
|
// Process immediately when DOM is ready
|
||||||
trace.log("Starting element processing...");
|
trace.log("Starting element processing...");
|
||||||
|
|
||||||
// Process existing elements
|
// Process existing elements
|
||||||
processAllElements();
|
processAllElements();
|
||||||
|
|
||||||
// Set up reactive observer for new elements
|
// Set up reactive observer for new elements
|
||||||
setupElementObserver();
|
setupElementObserver();
|
||||||
|
|
||||||
trace.log("Plugin fully initialized");
|
trace.log("Plugin fully initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
||||||
}
|
}
|
||||||
@@ -592,18 +659,18 @@ unloads.add(() => {
|
|||||||
elementObserver = null;
|
elementObserver = null;
|
||||||
}
|
}
|
||||||
contextMenuObserver.disconnect();
|
contextMenuObserver.disconnect();
|
||||||
|
|
||||||
// Close any open custom menu
|
// Close any open custom menu
|
||||||
closeCustomMenu();
|
closeCustomMenu();
|
||||||
|
|
||||||
// Remove highlights
|
// Remove highlights
|
||||||
removeHighlight();
|
removeHighlight();
|
||||||
|
|
||||||
// Clean up global functions
|
// Clean up global functions
|
||||||
(window as any).showAllElementsFromSettings = undefined;
|
window.showAllElementsFromSettings = undefined;
|
||||||
(window as any).debugElementHider = undefined;
|
window.debugElementHider = undefined;
|
||||||
|
|
||||||
trace.log("Plugin unloaded");
|
trace.log("Plugin unloaded");
|
||||||
});
|
});
|
||||||
|
|
||||||
trace.log("Plugin loaded - Right-click any element to hide it!");
|
trace.log("Plugin loaded - Right-click any element to hide it!");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ 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 (
|
||||||
<LunaSettings>
|
<LunaSettings>
|
||||||
<LunaSwitchSetting
|
<LunaSwitchSetting
|
||||||
@@ -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) {
|
||||||
@@ -56,4 +67,4 @@ export const Settings = () => {
|
|||||||
/>
|
/>
|
||||||
</LunaSettings>
|
</LunaSettings>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LunaUnload, Tracer } from "@luna/core";
|
import { type LunaUnload, Tracer } from "@luna/core";
|
||||||
import { StyleTag, observePromise, PlayState, Quality, type MediaItem } from "@luna/lib";
|
import { StyleTag, observePromise, PlayState } 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,110 +19,147 @@ 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 if (quality?.includes("HIGH")) {
|
||||||
return QUALITY_COLORS.LOW;
|
return QUALITY_COLORS.HIGH;
|
||||||
}
|
} else if (quality?.includes("LOW")) {
|
||||||
|
return QUALITY_COLORS.LOW;
|
||||||
|
}
|
||||||
|
return QUALITY_COLORS.LOW;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Interval tracking for quality monitoring
|
||||||
|
let qualityMonitoringIntervalId: number | null = null;
|
||||||
|
|
||||||
// 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;
|
if (qualityMonitoringIntervalId != null) return;
|
||||||
const interval = setInterval(() => {
|
let lastTrackId: string | null = null;
|
||||||
if (!settings.qualityColorMatchedSeekBar) return;
|
qualityMonitoringIntervalId = window.setInterval(() => {
|
||||||
const currentTrackId = PlayState.playbackContext?.actualProductId;
|
if (!settings.qualityColorMatchedSeekBar) return;
|
||||||
if (currentTrackId && currentTrackId !== lastTrackId) {
|
const currentTrackId = PlayState.playbackContext?.actualProductId;
|
||||||
//trace.msg.log(`[OLED Theme] Track ID changed: ${lastTrackId} -> ${currentTrackId}`);
|
if (currentTrackId && currentTrackId !== lastTrackId) {
|
||||||
lastTrackId = currentTrackId;
|
lastTrackId = currentTrackId;
|
||||||
applyQualityColors();
|
applyQualityColors();
|
||||||
}
|
}
|
||||||
}, 250);
|
}, 250);
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopQualityMonitoring = (): void => {
|
||||||
|
if (qualityMonitoringIntervalId != null) {
|
||||||
|
window.clearInterval(qualityMonitoringIntervalId);
|
||||||
|
qualityMonitoringIntervalId = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to apply theme styles based on current settings
|
// Function to apply theme styles based on current settings
|
||||||
const applyThemeStyles = function(): void {
|
const applyThemeStyles = (): 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) {
|
|
||||||
// Light mode - (OLED friendly doesn't apply to light theme)
|
|
||||||
selectedStyle = lightTheme;
|
|
||||||
} else {
|
|
||||||
// Dark mode
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
if (settings.lightMode) {
|
||||||
|
// Light mode - (OLED friendly doesn't apply to light theme)
|
||||||
|
selectedStyle = lightTheme;
|
||||||
|
} else {
|
||||||
|
// Dark mode
|
||||||
|
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 and stop monitoring
|
||||||
|
resetSeekBarColor();
|
||||||
|
stopQualityMonitoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
(window as any).updateOLEDThemeStyles = applyThemeStyles;
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
updateOLEDThemeStyles?: () => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.updateOLEDThemeStyles = applyThemeStyles;
|
||||||
|
|
||||||
// Apply the OLED theme initially
|
// Apply the OLED theme initially
|
||||||
applyThemeStyles();
|
applyThemeStyles();
|
||||||
|
|
||||||
|
// Ensure interval is cleared on unload
|
||||||
|
unloads.add(() => {
|
||||||
|
stopQualityMonitoring();
|
||||||
|
});
|
||||||
|
|||||||
@@ -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: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
},
|
},
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,43 +3,82 @@ import { LunaSettings, LunaSwitchSetting, LunaNumberSetting } from "@luna/ui";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
|
export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
|
||||||
hideUIEnabled: true,
|
|
||||||
trackTitleGlow: false,
|
|
||||||
playerBarVisible: false,
|
|
||||||
lyricsGlowEnabled: true,
|
lyricsGlowEnabled: true,
|
||||||
textGlow: 20,
|
trackTitleGlow: false,
|
||||||
spinningCoverEverywhere: true,
|
hideUIEnabled: true,
|
||||||
|
playerBarVisible: false,
|
||||||
|
CoverEverywhere: true,
|
||||||
performanceMode: false,
|
performanceMode: false,
|
||||||
spinningArtEnabled: true,
|
spinningArt: true,
|
||||||
|
textGlow: 20,
|
||||||
|
backgroundScale: 15,
|
||||||
|
backgroundRadius: 25,
|
||||||
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 [CoverEverywhere, setCoverEverywhere] = React.useState(
|
||||||
const [performanceMode, setPerformanceMode] = React.useState(settings.performanceMode);
|
settings.CoverEverywhere,
|
||||||
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 [spinningArt, setspinningArt] = React.useState(
|
||||||
|
settings.spinningArt,
|
||||||
|
);
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
const [backgroundScale, setBackgroundScale] = React.useState(
|
||||||
|
settings.backgroundScale,
|
||||||
|
);
|
||||||
|
const [backgroundRadius, setBackgroundRadius] = React.useState(
|
||||||
|
settings.backgroundRadius,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Derive props and override onChange to accept a broader first param type
|
||||||
|
type BaseSwitchProps = React.ComponentProps<typeof LunaSwitchSetting>;
|
||||||
|
type AnySwitchProps = Omit<BaseSwitchProps, "onChange"> & {
|
||||||
|
onChange: (_: unknown, checked: boolean) => void;
|
||||||
|
checked: boolean;
|
||||||
|
};
|
||||||
|
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<
|
||||||
|
AnySwitchProps
|
||||||
|
>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LunaSettings>
|
<LunaSettings>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
title="Lyrics Glow Effect"
|
title="Lyrics Glow Effect"
|
||||||
desc="Enable glowing effect for lyrics & Font Stytling Changes"
|
desc="Enable glowing effect for lyrics & Font Styling Changes"
|
||||||
checked={lyricsGlowEnabled}
|
checked={lyricsGlowEnabled}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
setLyricsGlowEnabled((settings.lyricsGlowEnabled = checked));
|
setLyricsGlowEnabled((settings.lyricsGlowEnabled = checked));
|
||||||
// Update styles immediately when setting changes
|
// Update styles immediately when setting changes
|
||||||
if ((window as any).updateRadiantLyricsStyles) {
|
if ((window as any).updateRadiantLyricsStyles) {
|
||||||
@@ -47,30 +86,30 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
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
|
<AnySwitch
|
||||||
title="Hide UI Feature"
|
title="Hide UI Feature"
|
||||||
desc="Enable hide/unhide UI functionality with toggle buttons"
|
desc="Enable hide/unhide UI functionality with toggle buttons"
|
||||||
checked={hideUIEnabled}
|
checked={hideUIEnabled}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
setHideUIEnabled((settings.hideUIEnabled = checked));
|
setHideUIEnabled((settings.hideUIEnabled = checked));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
title="Player Bar Visibility in Hide UI Mode"
|
title="Player Bar Visibility in Hide UI Mode"
|
||||||
desc="Keep player bar visible when UI is hidden"
|
desc="Keep player bar visible when UI is hidden"
|
||||||
checked={playerBarVisible}
|
checked={playerBarVisible}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
console.log("Player Bar Visibility:", checked ? "visible" : "hidden");
|
console.log("Player Bar Visibility:", checked ? "visible" : "hidden");
|
||||||
setPlayerBarVisible((settings.playerBarVisible = checked));
|
setPlayerBarVisible((settings.playerBarVisible = checked));
|
||||||
// Update styles immediately when setting changes
|
// Update styles immediately when setting changes
|
||||||
@@ -79,24 +118,29 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
title="Cover Everywhere"
|
title="Cover Everywhere"
|
||||||
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={CoverEverywhere}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
console.log("Spinning Cover Everywhere:", checked ? "enabled" : "disabled");
|
console.log(
|
||||||
setSpinningCoverEverywhere((settings.spinningCoverEverywhere = checked));
|
"Spinning Cover Everywhere:",
|
||||||
|
checked ? "enabled" : "disabled",
|
||||||
|
);
|
||||||
|
setCoverEverywhere(
|
||||||
|
(settings.CoverEverywhere = 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();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
title="Performance Mode | Experimental"
|
title="Performance Mode | Experimental"
|
||||||
desc="Performance mode: Reduces blur effects & uses smaller image sizes, to optimize GPU usage"
|
desc="Performance mode: Reduces blur effects & uses smaller image sizes, to optimize GPU usage"
|
||||||
checked={performanceMode}
|
checked={performanceMode}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
console.log("Performance Mode:", checked ? "enabled" : "disabled");
|
console.log("Performance Mode:", checked ? "enabled" : "disabled");
|
||||||
setPerformanceMode((settings.performanceMode = checked));
|
setPerformanceMode((settings.performanceMode = checked));
|
||||||
// Update background animations immediately when setting changes
|
// Update background animations immediately when setting changes
|
||||||
@@ -108,36 +152,82 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
title="Background Cover Spin" // Cheers @Max/n0201 for the idea <3
|
title="Background Cover Spin" // Cheers @Max/n0201 for the idea <3
|
||||||
desc="Enable the spinning cover art background animation"
|
desc="Enable the spinning cover art background animation"
|
||||||
checked={spinningArtEnabled}
|
checked={spinningArt}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, checked: boolean) => {
|
||||||
console.log("Background Cover Spin:", checked ? "enabled" : "disabled");
|
console.log(
|
||||||
setSpinningArtEnabled((settings.spinningArtEnabled = checked));
|
"Background Cover Spin:",
|
||||||
|
checked ? "enabled" : "disabled",
|
||||||
|
);
|
||||||
|
setspinningArt((settings.spinningArt = 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
|
||||||
|
title="Background Scale"
|
||||||
|
desc="Adjust the scale of the background cover (1=10% - 50=500%)"
|
||||||
|
min={1}
|
||||||
|
max={50}
|
||||||
|
step={1}
|
||||||
|
value={backgroundScale}
|
||||||
|
onNumber={(value: number) => {
|
||||||
|
setBackgroundScale((settings.backgroundScale = value));
|
||||||
|
if ((window as any).updateRadiantLyricsGlobalBackground) {
|
||||||
|
(window as any).updateRadiantLyricsGlobalBackground();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
settings.settingsAffectNowPlaying &&
|
||||||
|
(window as any).updateRadiantLyricsNowPlayingBackground
|
||||||
|
) {
|
||||||
|
(window as any).updateRadiantLyricsNowPlayingBackground();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<LunaNumberSetting
|
||||||
|
title="Background Radius"
|
||||||
|
desc="Adjust the cover art corner radius (0-100%, 100% = circle)"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={backgroundRadius}
|
||||||
|
onNumber={(value: number) => {
|
||||||
|
setBackgroundRadius((settings.backgroundRadius = value));
|
||||||
|
if ((window as any).updateRadiantLyricsGlobalBackground) {
|
||||||
|
(window as any).updateRadiantLyricsGlobalBackground();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
settings.settingsAffectNowPlaying &&
|
||||||
|
(window as any).updateRadiantLyricsNowPlayingBackground
|
||||||
|
) {
|
||||||
|
(window as any).updateRadiantLyricsNowPlayingBackground();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<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 +240,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 +261,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 +282,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,18 +303,26 @@ 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();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LunaSwitchSetting
|
<AnySwitch
|
||||||
title="Settings Affect Now Playing"
|
title="Settings Affect Now Playing"
|
||||||
desc="Apply background settings to Now Playing view"
|
desc="Apply background settings to Now Playing view"
|
||||||
checked={settingsAffectNowPlaying}
|
checked={settingsAffectNowPlaying}
|
||||||
onChange={(_, checked: boolean) => {
|
onChange={(_: unknown, 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();
|
||||||
@@ -224,4 +331,4 @@ export const Settings = () => {
|
|||||||
/>
|
/>
|
||||||
</LunaSettings>
|
</LunaSettings>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,105 +1,114 @@
|
|||||||
/* 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;
|
/* biome-ignore lint: Required to override app styles in performance mode */
|
||||||
/* Smaller size for performance */
|
filter: blur(20px) brightness(0.4) contrast(1.2) saturate(1) !important;
|
||||||
width: 120vw !important;
|
/* Smaller size for performance */
|
||||||
height: 120vh !important;
|
/* biome-ignore lint: Required to override app layout sizes */
|
||||||
|
width: 120vw !important;
|
||||||
|
/* biome-ignore lint: Required to override app layout sizes */
|
||||||
|
height: 120vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.now-playing-background-image.performance-mode-static {
|
.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;
|
/* biome-ignore lint: Required to override inline sizes in performance mode */
|
||||||
height: 80vh !important;
|
width: 80vw !important;
|
||||||
|
/* biome-ignore lint: Required to override inline sizes in performance mode */
|
||||||
|
height: 80vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now Playing Background Container Optimization */
|
/* Now Playing Background Container Optimization */
|
||||||
.now-playing-background-container {
|
.now-playing-background-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
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;
|
/* biome-ignore lint: Accessibility override needs priority */
|
||||||
transform: translate(-50%, -50%) !important;
|
animation: none !important;
|
||||||
will-change: auto !important;
|
/* biome-ignore lint: Accessibility override needs priority */
|
||||||
}
|
transform: translate(-50%, -50%) !important;
|
||||||
|
/* biome-ignore lint: Accessibility override needs priority */
|
||||||
|
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;
|
/* biome-ignore lint: Intentional override of runtime styles */
|
||||||
|
filter: blur(10px) brightness(0.4) contrast(1.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make Notification Feed sidebar transparent */
|
/* Make Notification Feed sidebar transparent */
|
||||||
@@ -115,13 +124,14 @@ 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;
|
/* biome-ignore lint: Ensure background is fully cleared under theme CSS */
|
||||||
|
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 +139,12 @@ 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;
|
/* biome-ignore lint: Must beat app inline styles for translucency */
|
||||||
backdrop-filter: blur(10px) !important;
|
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||||
-webkit-backdrop-filter: blur(10px) !important;
|
/* biome-ignore lint: Must beat app inline styles for translucency */
|
||||||
|
backdrop-filter: blur(10px) !important;
|
||||||
|
/* biome-ignore lint: Must beat app inline styles for translucency */
|
||||||
|
-webkit-backdrop-filter: blur(10px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Performance mode: reduce backdrop blur */
|
/* Performance mode: reduce backdrop blur */
|
||||||
@@ -139,21 +152,28 @@ 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;
|
/* biome-ignore lint: Performance mode style requires priority */
|
||||||
-webkit-backdrop-filter: blur(5px) !important;
|
backdrop-filter: blur(5px) !important;
|
||||||
|
/* biome-ignore lint: Performance mode style requires priority */
|
||||||
|
-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;
|
/* biome-ignore lint: Ensure readability over media */
|
||||||
backdrop-filter: blur(10px) !important;
|
background-color: rgba(0, 0, 0, 0.5) !important;
|
||||||
-webkit-backdrop-filter: blur(10px) !important;
|
/* biome-ignore lint: Ensure readability over media */
|
||||||
|
backdrop-filter: blur(10px) !important;
|
||||||
|
/* biome-ignore lint: Ensure readability over media */
|
||||||
|
-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;
|
/* biome-ignore lint: Performance mode style requires priority */
|
||||||
-webkit-backdrop-filter: blur(5px) !important;
|
backdrop-filter: blur(5px) !important;
|
||||||
|
/* biome-ignore lint: Performance mode style requires priority */
|
||||||
|
-webkit-backdrop-filter: blur(5px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Feed sidebar items - transparent */
|
/* Feed sidebar items - transparent */
|
||||||
@@ -162,10 +182,12 @@ 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;
|
/* biome-ignore lint: Match theme transparency */
|
||||||
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove bottom gradient */
|
/* Remove bottom gradient */
|
||||||
[class^="_bottomGradient"] {
|
[class^="_bottomGradient"] {
|
||||||
display: none !important;
|
/* biome-ignore lint: Explicitly remove conflicting gradient */
|
||||||
}
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,82 +1,112 @@
|
|||||||
/* 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;
|
/* biome-ignore lint: Required to override app glow strength */
|
||||||
font-size: 55px;
|
0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #fff) !important;
|
||||||
color: white !important;
|
padding-left: 20px;
|
||||||
font-family: "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
transition-duration: 0.7s;
|
||||||
font-weight: 700;
|
font-size: 55px;
|
||||||
|
/* biome-ignore lint: Needs priority for active lyric color */
|
||||||
|
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;
|
/* biome-ignore lint: Hover glow should override defaults */
|
||||||
transition-duration: 0.7s;
|
0 0 var(--rl-glow-outer, 20px) lightgray !important;
|
||||||
|
/* biome-ignore lint: Hover color override */
|
||||||
|
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;
|
/* biome-ignore lint: Title glow needs priority */
|
||||||
-webkit-text-fill-color: initial !important;
|
0 0 var(--rl-glow-outer, 30px) #fff !important;
|
||||||
color: inherit !important;
|
/* biome-ignore lint: Reset vendor background clip */
|
||||||
|
-webkit-background-clip: initial !important;
|
||||||
|
/* biome-ignore lint: Reset background clip */
|
||||||
|
background-clip: initial !important;
|
||||||
|
/* biome-ignore lint: Reset vendor text fill */
|
||||||
|
-webkit-text-fill-color: initial !important;
|
||||||
|
/* biome-ignore lint: Ensure inherited color takes precedence */
|
||||||
|
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;
|
/* biome-ignore lint: Full reset required */
|
||||||
|
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,
|
||||||
|
/* biome-ignore lint: Transition priority needed */
|
||||||
|
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;
|
||||||
|
/* biome-ignore lint: Typography override for readability */
|
||||||
|
font-size: 38px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset all lyrics styling when disabled */
|
/* Reset all lyrics styling when disabled */
|
||||||
@@ -85,13 +115,22 @@
|
|||||||
.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;
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
padding-left: 0 !important;
|
text-shadow: none !important;
|
||||||
transition: none !important;
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
font-size: inherit !important;
|
padding-left: 0 !important;
|
||||||
color: inherit !important;
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
font-family: inherit !important;
|
transition: none !important;
|
||||||
font-weight: inherit !important;
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
margin-bottom: inherit !important;
|
font-size: inherit !important;
|
||||||
opacity: inherit !important;
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
}
|
color: inherit !important;
|
||||||
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
|
font-family: inherit !important;
|
||||||
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
|
font-weight: inherit !important;
|
||||||
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
|
margin-bottom: inherit !important;
|
||||||
|
/* biome-ignore lint: Hard reset when disabled */
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,54 +75,81 @@
|
|||||||
.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 */
|
||||||
|
|
||||||
/* Default state for control buttons */
|
/* Default state for control buttons */
|
||||||
[data-test="add-to-playlist"],
|
[data-test="add-to-playlist"],
|
||||||
[data-test="remove-from-playlist"],
|
[data-test="remove-from-playlist"],
|
||||||
[data-test="like-toggle"],
|
[data-test="like-toggle"],
|
||||||
[data-test="dislike-toggle"],
|
[data-test="dislike-toggle"],
|
||||||
[data-test="favorite-toggle"],
|
[data-test="favorite-toggle"],
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user