BIOME Refactor

This commit is contained in:
2025-09-09 18:31:35 +10:00
parent 99661096d5
commit 0d9b378e43
7 changed files with 176 additions and 89 deletions
+59 -30
View File
@@ -2,6 +2,12 @@ import { ReactiveStore } from "@luna/core";
import { LunaSettings, LunaSwitchSetting } from "@luna/ui"; import { LunaSettings, LunaSwitchSetting } from "@luna/ui";
import React from "react"; import React from "react";
declare global {
interface Window {
applyColoramaLyrics?: () => void;
}
}
export type ColoramaMode = export type ColoramaMode =
| "single" | "single"
| "gradient-experimental" | "gradient-experimental"
@@ -24,7 +30,7 @@ export const settings = await ReactiveStore.getPluginStorage("ColoramaLyrics", {
}); });
export const Settings = () => { export const Settings = () => {
const [enabled, setEnabled] = React.useState(settings.enabled); // const [enabled, setEnabled] = React.useState(settings.enabled);
const [mode, setMode] = React.useState<ColoramaMode>(settings.mode); const [mode, setMode] = React.useState<ColoramaMode>(settings.mode);
const [singleColor, setSingleColor] = React.useState(settings.singleColor); const [singleColor, setSingleColor] = React.useState(settings.singleColor);
const [singleAlpha, setSingleAlpha] = React.useState<number>( const [singleAlpha, setSingleAlpha] = React.useState<number>(
@@ -54,7 +60,9 @@ export const Settings = () => {
const [activeEndpoint, setActiveEndpoint] = React.useState< const [activeEndpoint, setActiveEndpoint] = React.useState<
"single" | "start" | "end" "single" | "start" | "end"
>("single"); >("single");
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<any>; const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<
Record<string, unknown>
>;
// Helper for HEX normalization // Helper for HEX normalization
const normalizeToRGB = ( const normalizeToRGB = (
@@ -125,14 +133,17 @@ export const Settings = () => {
if (!hexColorRegex.test(trimmed)) return; if (!hexColorRegex.test(trimmed)) return;
if (mode === "single") { if (mode === "single") {
const next = normalizeToRGB(trimmed); const next = normalizeToRGB(trimmed);
setSingleColor((settings.singleColor = next)); settings.singleColor = next;
setSingleColor(next);
if (updateInput) setCustomInput(next); if (updateInput) setCustomInput(next);
} else if (mode === "gradient-experimental") { } else if (mode === "gradient-experimental") {
const norm = normalizeToRGB(trimmed); const norm = normalizeToRGB(trimmed);
if (activeEndpoint === "end") { if (activeEndpoint === "end") {
setGradientEnd((settings.gradientEnd = norm)); settings.gradientEnd = norm;
setGradientEnd(norm);
} else { } else {
setGradientStart((settings.gradientStart = norm)); settings.gradientStart = norm;
setGradientStart(norm);
} }
if (updateInput) setCustomInput(norm); if (updateInput) setCustomInput(norm);
} }
@@ -152,16 +163,16 @@ export const Settings = () => {
} }
}; };
const removeCustomColor = (color: string) => { // const removeCustomColor = (color: string) => {
const updated = customColors.filter((c) => c !== color); // const updated = customColors.filter((c) => c !== color);
setCustomColors(updated); // setCustomColors(updated);
settings.customColors = updated; // settings.customColors = updated;
}; // };
const allColors = [...colorPresets, ...customColors]; const allColors = [...colorPresets, ...customColors];
const requestApply = () => { const requestApply = () => {
(window as any).applyColoramaLyrics?.(); window.applyColoramaLyrics?.();
}; };
return ( return (
@@ -185,7 +196,8 @@ export const Settings = () => {
value={mode} value={mode}
onChange={(e) => { onChange={(e) => {
const next = e.target.value as ColoramaMode; const next = e.target.value as ColoramaMode;
setMode((settings.mode = next)); settings.mode = next;
setMode(next);
requestApply(); requestApply();
}} }}
style={{ style={{
@@ -250,6 +262,7 @@ export const Settings = () => {
}} }}
> >
<button <button
type="button"
onClick={() => (showPicker ? closePicker() : openPicker("single"))} onClick={() => (showPicker ? closePicker() : openPicker("single"))}
style={{ style={{
width: 32, width: 32,
@@ -285,6 +298,7 @@ export const Settings = () => {
<div style={{ opacity: 0.7, fontSize: 14 }}>Set colors & angle</div> <div style={{ opacity: 0.7, fontSize: 14 }}>Set colors & angle</div>
</div> </div>
<button <button
type="button"
onClick={() => { onClick={() => {
setCustomInput(gradientStart); setCustomInput(gradientStart);
openPicker("start"); openPicker("start");
@@ -324,6 +338,7 @@ export const Settings = () => {
<div style={{ opacity: 0.7, fontSize: 14 }}>Set angle</div> <div style={{ opacity: 0.7, fontSize: 14 }}>Set angle</div>
</div> </div>
<button <button
type="button"
onClick={() => openPicker("start")} onClick={() => openPicker("start")}
style={{ style={{
padding: "8px 12px", padding: "8px 12px",
@@ -341,7 +356,7 @@ export const Settings = () => {
{/* Modal for picking and managing colors (reused) */} {/* Modal for picking and managing colors (reused) */}
{shouldRender && ( {shouldRender && (
<> <>
<div <button
style={{ style={{
position: "fixed", position: "fixed",
top: 0, top: 0,
@@ -353,7 +368,12 @@ export const Settings = () => {
opacity: isAnimatingIn ? 1 : 0, opacity: isAnimatingIn ? 1 : 0,
transition: "opacity 0.2s ease", transition: "opacity 0.2s ease",
}} }}
type="button"
aria-label="Close color picker"
onClick={closePicker} onClick={closePicker}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === "Escape") closePicker();
}}
/> />
<div <div
style={{ style={{
@@ -419,6 +439,7 @@ export const Settings = () => {
color: "#fff", color: "#fff",
cursor: "pointer", cursor: "pointer",
}} }}
type="button"
> >
<span <span
style={{ style={{
@@ -432,6 +453,7 @@ export const Settings = () => {
<span style={{ fontSize: 12 }}>Start</span> <span style={{ fontSize: 12 }}>Start</span>
</button> </button>
<button <button
type="button"
onClick={() => { onClick={() => {
setActiveEndpoint("end"); setActiveEndpoint("end");
setCustomInput(gradientEnd); setCustomInput(gradientEnd);
@@ -473,22 +495,23 @@ export const Settings = () => {
marginBottom: 16, marginBottom: 16,
}} }}
> >
{allColors.map((color, index) => ( {allColors.map((color) => (
<button <button
key={index} key={color}
type="button"
onClick={() => { onClick={() => {
if (mode === "single") { if (mode === "single") {
const next = normalizeToRGB(color); const next = normalizeToRGB(color);
setSingleColor((settings.singleColor = next)); settings.singleColor = next;
setSingleColor(next);
} else if (mode === "gradient-experimental") { } else if (mode === "gradient-experimental") {
const next = normalizeToRGB(color);
if (activeEndpoint === "end") { if (activeEndpoint === "end") {
setGradientEnd( settings.gradientEnd = next;
(settings.gradientEnd = normalizeToRGB(color)), setGradientEnd(next);
);
} else { } else {
setGradientStart( settings.gradientStart = next;
(settings.gradientStart = normalizeToRGB(color)), setGradientStart(next);
);
} }
} }
setCustomInput(normalizeToRGB(color)); setCustomInput(normalizeToRGB(color));
@@ -560,6 +583,7 @@ export const Settings = () => {
justifyContent: "center", justifyContent: "center",
transition: "all 0.2s ease", transition: "all 0.2s ease",
}} }}
type="button"
> >
+ +
</button> </button>
@@ -586,7 +610,8 @@ export const Settings = () => {
value={singleAlpha} value={singleAlpha}
onChange={(e) => { onChange={(e) => {
const value = Number(e.target.value); const value = Number(e.target.value);
setSingleAlpha((settings.singleAlpha = value)); settings.singleAlpha = value;
setSingleAlpha(value);
requestApply(); requestApply();
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
@@ -628,9 +653,8 @@ export const Settings = () => {
value={gradientStartAlpha} value={gradientStartAlpha}
onChange={(e) => { onChange={(e) => {
const value = Number(e.target.value); const value = Number(e.target.value);
setGradientStartAlpha( settings.gradientStartAlpha = value;
(settings.gradientStartAlpha = value), setGradientStartAlpha(value);
);
requestApply(); requestApply();
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
@@ -668,7 +692,8 @@ export const Settings = () => {
value={gradientEndAlpha} value={gradientEndAlpha}
onChange={(e) => { onChange={(e) => {
const value = Number(e.target.value); const value = Number(e.target.value);
setGradientEndAlpha((settings.gradientEndAlpha = value)); settings.gradientEndAlpha = value;
setGradientEndAlpha(value);
requestApply(); requestApply();
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
@@ -702,7 +727,8 @@ export const Settings = () => {
value={gradientAngle} value={gradientAngle}
onChange={(e) => { onChange={(e) => {
const value = Number(e.target.value); const value = Number(e.target.value);
setGradientAngle((settings.gradientAngle = value)); settings.gradientAngle = value;
setGradientAngle(value);
requestApply(); requestApply();
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
@@ -736,7 +762,8 @@ export const Settings = () => {
value={gradientAngle} value={gradientAngle}
onChange={(e) => { onChange={(e) => {
const value = Number(e.target.value); const value = Number(e.target.value);
setGradientAngle((settings.gradientAngle = value)); settings.gradientAngle = value;
setGradientAngle(value);
requestApply(); requestApply();
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
@@ -756,6 +783,7 @@ export const Settings = () => {
cursor: "pointer", cursor: "pointer",
fontSize: 12, fontSize: 12,
}} }}
type="button"
> >
Done Done
</button> </button>
@@ -767,7 +795,8 @@ export const Settings = () => {
desc="Apply color/gradient only to the currently active lyric line" desc="Apply color/gradient only to the currently active lyric line"
checked={excludeInactive} checked={excludeInactive}
onChange={(_: unknown, checked: boolean) => { onChange={(_: unknown, checked: boolean) => {
setExcludeInactive((settings.excludeInactive = checked)); settings.excludeInactive = checked;
setExcludeInactive(checked);
requestApply(); requestApply();
}} }}
/> />
+16 -12
View File
@@ -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,8 +9,8 @@ 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");
@@ -31,17 +31,17 @@ function SetClipboard(text: string): void {
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; const container = range.commonAncestorContainer;
// If the container is NOT an element and a document, adjust it. // If the container is NOT an element and a document, adjust it.
if ( if (
@@ -50,8 +50,8 @@ const onMouseUp = function (event: MouseEvent): void {
) { ) {
// Get the parent element if it's a text node // Get the parent element if it's a text node
const parentElement = container.parentElement; const parentElement = container.parentElement;
if (parentElement && parentElement.hasAttribute("data-current")) { if (parentElement?.hasAttribute("data-current")) {
let text_ = selection.toString().trim(); const text_ = selection.toString().trim();
SetClipboard(text_); SetClipboard(text_);
trace.msg.log("Copied to clipboard!"); trace.msg.log("Copied to clipboard!");
return; return;
@@ -60,7 +60,7 @@ const onMouseUp = function (event: MouseEvent): void {
// Get all the spans inside the container. // Get all the spans inside the container.
const spans = (container as Element).getElementsByTagName("span"); const spans = (container as Element).getElementsByTagName("span");
for (let span of spans) { for (const span of spans) {
if (selection.containsNode(span, true)) { if (selection.containsNode(span, true)) {
selectedSpans.push(span as HTMLSpanElement); selectedSpans.push(span as HTMLSpanElement);
} }
@@ -95,7 +95,7 @@ const onMouseUp = function (event: MouseEvent): void {
} }
}; };
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;
@@ -109,15 +109,19 @@ const onClickHooked = function (event: MouseEvent): boolean | void {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
return false; 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);
+27 -21
View File
@@ -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;
@@ -144,19 +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( // const index = settings.hiddenElements.findIndex(
(stored) => stored.selector === selector, // (stored) => stored.selector === selector,
); // );
// if (index !== -1) {
if (index !== -1) { // settings.hiddenElements.splice(index, 1);
settings.hiddenElements.splice(index, 1); // trace.log(`Permanently removed: ${selector}`);
trace.log(`Permanently removed: ${selector}`); // trace.log(`Remaining stored: ${settings.hiddenElements.length}`);
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 {
@@ -324,8 +323,15 @@ function setupElementObserver(): void {
} }
// 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}`);
@@ -662,8 +668,8 @@ unloads.add(() => {
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");
}); });
+10 -10
View File
@@ -1,11 +1,5 @@
import { LunaUnload, Tracer } from "@luna/core"; import { type LunaUnload, Tracer } from "@luna/core";
import { import { StyleTag, observePromise, PlayState } from "@luna/lib";
StyleTag,
observePromise,
PlayState,
Quality,
type MediaItem,
} from "@luna/lib";
import { settings, Settings } from "./Settings"; import { settings, Settings } from "./Settings";
// Import CSS files directly using Luna's file:// syntax - Took me a while to figure out <3 // Import CSS files directly using Luna's file:// syntax - Took me a while to figure out <3
@@ -109,7 +103,7 @@ const setupQualityMonitoring = (): void => {
}; };
// 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;
@@ -141,7 +135,13 @@ const applyThemeStyles = function (): void {
}; };
// 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();
+21 -16
View File
@@ -54,13 +54,18 @@ export const Settings = () => {
settings.trackTitleGlow, settings.trackTitleGlow,
); );
// Use a permissive wrapper to align with current usage props
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<
Record<string, unknown>
>;
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 Stytling Changes"
checked={lyricsGlowEnabled} checked={lyricsGlowEnabled}
onChange={(_, checked: boolean) => { onChange={(_event: 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) {
@@ -68,30 +73,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={(_event: 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={(_event: 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={(_event: 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
@@ -100,11 +105,11 @@ 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={spinningCoverEverywhere}
onChange={(_, checked: boolean) => { onChange={(_event: unknown, checked: boolean) => {
console.log( console.log(
"Spinning Cover Everywhere:", "Spinning Cover Everywhere:",
checked ? "enabled" : "disabled", checked ? "enabled" : "disabled",
@@ -118,11 +123,11 @@ export const Settings = () => {
} }
}} }}
/> />
<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={(_event: 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
@@ -134,11 +139,11 @@ 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={spinningArtEnabled}
onChange={(_, checked: boolean) => { onChange={(_event: unknown, checked: boolean) => {
console.log( console.log(
"Background Cover Spin:", "Background Cover Spin:",
checked ? "enabled" : "disabled", checked ? "enabled" : "disabled",
@@ -253,11 +258,11 @@ export const Settings = () => {
} }
}} }}
/> />
<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={(_event: unknown, checked: boolean) => {
console.log( console.log(
"Settings Affect Now Playing:", "Settings Affect Now Playing:",
checked ? "enabled" : "disabled", checked ? "enabled" : "disabled",
@@ -47,16 +47,21 @@
.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 */
/* biome-ignore lint: Required to override app styles in performance mode */
filter: blur(20px) brightness(0.4) contrast(1.2) saturate(1) !important; filter: blur(20px) brightness(0.4) contrast(1.2) saturate(1) !important;
/* Smaller size for performance */ /* Smaller size for performance */
/* biome-ignore lint: Required to override app layout sizes */
width: 120vw !important; width: 120vw !important;
/* biome-ignore lint: Required to override app layout sizes */
height: 120vh !important; height: 120vh !important;
} }
.now-playing-background-image.performance-mode-static { .now-playing-background-image.performance-mode-static {
/* Keep animation enabled in performance mode */ /* Keep animation enabled in performance mode */
/* Optimized size and effects for performance */ /* Optimized size and effects for performance */
/* biome-ignore lint: Required to override inline sizes in performance mode */
width: 80vw !important; width: 80vw !important;
/* biome-ignore lint: Required to override inline sizes in performance mode */
height: 80vh !important; height: 80vh !important;
} }
@@ -89,8 +94,11 @@
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.global-spinning-image, .global-spinning-image,
.now-playing-background-image { .now-playing-background-image {
/* biome-ignore lint: Accessibility override needs priority */
animation: none !important; animation: none !important;
/* biome-ignore lint: Accessibility override needs priority */
transform: translate(-50%, -50%) !important; transform: translate(-50%, -50%) !important;
/* biome-ignore lint: Accessibility override needs priority */
will-change: auto !important; will-change: auto !important;
} }
} }
@@ -99,6 +107,7 @@
.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 */
/* biome-ignore lint: Intentional override of runtime styles */
filter: blur(10px) brightness(0.4) contrast(1.1) !important; filter: blur(10px) brightness(0.4) contrast(1.1) !important;
} }
@@ -121,6 +130,7 @@ main,
[class^="_feedSidebarItemDiv"], [class^="_feedSidebarItemDiv"],
[class^="_cellContainer"], [class^="_cellContainer"],
[class^="_cellTextContainer"] { [class^="_cellTextContainer"] {
/* biome-ignore lint: Ensure background is fully cleared under theme CSS */
background: unset !important; background: unset !important;
} }
@@ -129,8 +139,11 @@ main,
[data-test="main-layout-sidebar-wrapper"], [data-test="main-layout-sidebar-wrapper"],
[class^="_bar"], [class^="_bar"],
[class^="_sidebarItem"]:hover { [class^="_sidebarItem"]:hover {
/* biome-ignore lint: Must beat app inline styles for translucency */
background-color: rgba(0, 0, 0, 0.3) !important; background-color: rgba(0, 0, 0, 0.3) !important;
/* biome-ignore lint: Must beat app inline styles for translucency */
backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important;
/* biome-ignore lint: Must beat app inline styles for translucency */
-webkit-backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;
} }
@@ -139,20 +152,27 @@ 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 {
/* biome-ignore lint: Performance mode style requires priority */
backdrop-filter: blur(5px) !important; backdrop-filter: blur(5px) !important;
/* biome-ignore lint: Performance mode style requires priority */
-webkit-backdrop-filter: blur(5px) !important; -webkit-backdrop-filter: blur(5px) !important;
} }
/* Feed sidebar panel - black tint background for readability */ /* Feed sidebar panel - black tint background for readability */
[data-test="feed-sidebar"] { [data-test="feed-sidebar"] {
/* biome-ignore lint: Ensure readability over media */
background-color: rgba(0, 0, 0, 0.5) !important; background-color: rgba(0, 0, 0, 0.5) !important;
/* biome-ignore lint: Ensure readability over media */
backdrop-filter: blur(10px) !important; backdrop-filter: blur(10px) !important;
/* biome-ignore lint: Ensure readability over media */
-webkit-backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;
} }
/* Performance mode: reduce sidebar backdrop blur */ /* Performance mode: reduce sidebar backdrop blur */
.performance-mode [data-test="feed-sidebar"] { .performance-mode [data-test="feed-sidebar"] {
/* biome-ignore lint: Performance mode style requires priority */
backdrop-filter: blur(5px) !important; backdrop-filter: blur(5px) !important;
/* biome-ignore lint: Performance mode style requires priority */
-webkit-backdrop-filter: blur(5px) !important; -webkit-backdrop-filter: blur(5px) !important;
} }
@@ -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"] {
/* biome-ignore lint: Match theme transparency */
background-color: transparent !important; background-color: transparent !important;
} }
/* Remove bottom gradient */ /* Remove bottom gradient */
[class^="_bottomGradient"] { [class^="_bottomGradient"] {
/* biome-ignore lint: Explicitly remove conflicting gradient */
display: none !important; display: none !important;
} }
@@ -31,10 +31,12 @@
[class*="_lyricsText"] > div > span[data-current="true"] { [class*="_lyricsText"] > div > span[data-current="true"] {
text-shadow: text-shadow:
0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #fff), 0 0 var(--rl-glow-inner, 2px) var(--cl-glow1, #fff),
/* biome-ignore lint: Required to override app glow strength */
0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #fff) !important; 0 0 var(--rl-glow-outer, 20px) var(--cl-glow2, #fff) !important;
padding-left: 20px; padding-left: 20px;
transition-duration: 0.7s; transition-duration: 0.7s;
font-size: 55px; font-size: 55px;
/* biome-ignore lint: Needs priority for active lyric color */
color: white !important; color: white !important;
font-family: font-family:
"AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
@@ -58,7 +60,9 @@
[class*="_lyricsText"] > div > span:hover { [class*="_lyricsText"] > div > span:hover {
text-shadow: text-shadow:
0 0 var(--rl-glow-inner, 2px) lightgray, 0 0 var(--rl-glow-inner, 2px) lightgray,
/* biome-ignore lint: Hover glow should override defaults */
0 0 var(--rl-glow-outer, 20px) lightgray !important; 0 0 var(--rl-glow-outer, 20px) lightgray !important;
/* biome-ignore lint: Hover color override */
color: lightgray !important; color: lightgray !important;
padding-left: 20px; padding-left: 20px;
transition-duration: 0.7s; transition-duration: 0.7s;
@@ -69,15 +73,21 @@
/* 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: text-shadow:
0 0 var(--rl-glow-inner, 1px) var(--cl-glow1, #fff), 0 0 var(--rl-glow-inner, 1px) var(--cl-glow1, #fff),
/* biome-ignore lint: Title glow needs priority */
0 0 var(--rl-glow-outer, 30px) #fff !important; 0 0 var(--rl-glow-outer, 30px) #fff !important;
/* biome-ignore lint: Reset vendor background clip */
-webkit-background-clip: initial !important; -webkit-background-clip: initial !important;
/* biome-ignore lint: Reset background clip */
background-clip: initial !important; background-clip: initial !important;
/* biome-ignore lint: Reset vendor text fill */
-webkit-text-fill-color: initial !important; -webkit-text-fill-color: initial !important;
/* biome-ignore lint: Ensure inherited color takes precedence */
color: inherit !important; color: inherit !important;
} }
/* When track title glow setting is disabled, remove glow regardless of Colorama */ /* When track title glow setting is disabled, remove glow regardless of Colorama */
.rl-title-glow-disabled[data-test="now-playing-track-title"] { .rl-title-glow-disabled[data-test="now-playing-track-title"] {
/* biome-ignore lint: Full reset required */
text-shadow: none !important; text-shadow: none !important;
} }
@@ -86,6 +96,7 @@
transition: transition:
text-shadow 0.7s ease-in-out, text-shadow 0.7s ease-in-out,
color 0.7s ease-in-out, color 0.7s ease-in-out,
/* biome-ignore lint: Transition priority needed */
padding 0.7s ease-in-out !important; padding 0.7s ease-in-out !important;
} }
@@ -97,6 +108,7 @@
"AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "AbyssFont", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-weight: 700; font-weight: 700;
/* biome-ignore lint: Typography override for readability */
font-size: 38px !important; font-size: 38px !important;
} }
@@ -106,13 +118,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 {
/* biome-ignore lint: Hard reset when disabled */
text-shadow: none !important; text-shadow: none !important;
/* biome-ignore lint: Hard reset when disabled */
padding-left: 0 !important; padding-left: 0 !important;
/* biome-ignore lint: Hard reset when disabled */
transition: none !important; transition: none !important;
/* biome-ignore lint: Hard reset when disabled */
font-size: inherit !important; font-size: inherit !important;
/* biome-ignore lint: Hard reset when disabled */
color: inherit !important; color: inherit !important;
/* biome-ignore lint: Hard reset when disabled */
font-family: inherit !important; font-family: inherit !important;
/* biome-ignore lint: Hard reset when disabled */
font-weight: inherit !important; font-weight: inherit !important;
/* biome-ignore lint: Hard reset when disabled */
margin-bottom: inherit !important; margin-bottom: inherit !important;
/* biome-ignore lint: Hard reset when disabled */
opacity: inherit !important; opacity: inherit !important;
} }