Improved Settings

This commit is contained in:
2025-08-13 20:25:20 +10:00
parent 1fda054d2a
commit c0255acb4c
3 changed files with 407 additions and 217 deletions
+330 -205
View File
@@ -2,16 +2,20 @@ import { ReactiveStore } from "@luna/core";
import { LunaSettings, LunaNumberSetting, LunaSwitchSetting, LunaTextSetting } from "@luna/ui";
import React from "react";
export type ColoramaMode = "single" | "gradient" | "auto-single" | "auto-gradient";
export type ColoramaMode = "single" | "gradient" | "auto-single" | "auto-gradient" | "rainbow";
export const settings = await ReactiveStore.getPluginStorage("ColoramaLyrics", {
enabled: true,
mode: "single" as ColoramaMode,
// Store colors as ARGB hex (#AARRGGBB)
singleColor: "#FFFFFFFF",
gradientStart: "#FFFFFFFF",
gradientEnd: "#88AAFFFF",
// Store colors as RGB hex (#RRGGBB) and opacity separately (0-100)
singleColor: "#FFFFFF",
singleAlpha: 100,
gradientStart: "#FFFFFF",
gradientStartAlpha: 100,
gradientEnd: "#AAFFFF",
gradientEndAlpha: 100,
gradientAngle: 0,
rainbowSpeed: 8,
customColors: [] as string[],
excludeInactive: false
});
@@ -20,59 +24,67 @@ export const Settings = () => {
const [enabled, setEnabled] = React.useState(settings.enabled);
const [mode, setMode] = React.useState<ColoramaMode>(settings.mode);
const [singleColor, setSingleColor] = React.useState(settings.singleColor);
const [singleAlpha, setSingleAlpha] = React.useState<number>(settings.singleAlpha ?? 100);
const [gradientStart, setGradientStart] = React.useState(settings.gradientStart);
const [gradientStartAlpha, setGradientStartAlpha] = React.useState<number>(settings.gradientStartAlpha ?? 100);
const [gradientEnd, setGradientEnd] = React.useState(settings.gradientEnd);
const [gradientEndAlpha, setGradientEndAlpha] = React.useState<number>(settings.gradientEndAlpha ?? 100);
const [gradientAngle, setGradientAngle] = React.useState(settings.gradientAngle);
const [rainbowSpeed, setRainbowSpeed] = React.useState<number>(settings.rainbowSpeed ?? 8);
const [customInput, setCustomInput] = React.useState(settings.singleColor);
const [customColors, setCustomColors] = React.useState(settings.customColors);
const [showPicker, setShowPicker] = React.useState(false);
const [isAnimatingIn, setIsAnimatingIn] = React.useState(false);
const [shouldRender, setShouldRender] = React.useState(false);
const [excludeInactive, setExcludeInactive] = React.useState(settings.excludeInactive);
const [activeEndpoint, setActiveEndpoint] = React.useState<'single' | 'start' | 'end'>('single');
const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType<any>;
// Helpers for ARGB <-> components
// Helpers for HEX parsing and alpha extraction
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
const normalizeToARGB = (hex: string, fallback: string = "#FFFFFFFF"): string => {
const normalizeToRGB = (hex: string, fallback: string = "#FFFFFF"): string => {
let v = hex.trim().toLowerCase();
if (!v.startsWith('#')) v = `#${v}`;
// #rgb or #rgba -> expand
if (/^#([0-9a-f]{3,4})$/.test(v)) {
const m = v.slice(1);
const a = m.length === 4 ? m[3] : 'f';
const r = m[0];
const g = m[1];
const b = m[2];
v = `#${a}${r}${g}${b}${r}${g}${b}${a}`; // temporary, will reformat below
// ignore alpha if provided (#rgba)
return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
}
// #aarrggbb -> strip alpha
if (/^#([0-9a-f]{8})$/.test(v)) {
const rrggbb = v.slice(3);
return `#${rrggbb}`.toUpperCase();
}
// #rrggbb
if (/^#([0-9a-f]{6})$/.test(v)) {
const m = v.slice(1);
const rrggbb = m;
const aa = 'ff';
return `#${aa}${rrggbb}`.toUpperCase();
}
// #aarrggbb
if (/^#([0-9a-f]{8})$/.test(v)) return v.toUpperCase();
if (/^#([0-9a-f]{6})$/.test(v)) return v.toUpperCase();
return fallback;
};
const setAlphaOnARGB = (argb: string, alpha01: number): string => {
const a = clamp(Math.round(alpha01 * 255), 0, 255).toString(16).padStart(2, '0');
const body = argb.replace('#', '').slice(2);
return (`#${a}${body}`).toUpperCase();
};
const getAlpha01 = (argb: string): number => {
const v = normalizeToARGB(argb);
const a = parseInt(v.slice(1, 3), 16);
return clamp(a / 255, 0, 1);
const extractAlphaPercent = (hex: string, fallbackPercent: number = 100): number => {
let v = hex.trim().toLowerCase();
if (!v.startsWith('#')) v = `#${v}`;
if (/^#([0-9a-f]{4})$/.test(v)) {
const a = v[4];
return Math.round((parseInt(a + a, 16) / 255) * 100);
}
if (/^#([0-9a-f]{8})$/.test(v)) {
const a = v.slice(1, 3);
return Math.round((parseInt(a, 16) / 255) * 100);
}
return fallbackPercent;
};
const colorPresets = [
"#FFFFFFFF", "#FF0000FF", "#00FF00FF", "#0000FFFF", "#FFFF00FF", "#FF00FFFF", "#00FFFFFF",
"#FF8800FF", "#8800FFFF", "#0088FFFF", "#88FF00FF", "#FF0088FF", "#00FF88FF",
"#444444FF", "#888888FF", "#CCCCCCFF", "#1DB954FF", "#E22134FF", "#1976D2FF"
"#FFFFFF", "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF",
"#FF8800", "#8800FF", "#0088FF", "#88FF00", "#FF0088", "#00FF88",
"#444444", "#888888", "#CCCCCC", "#1DB954", "#E22134", "#1976D2"
];
const openPicker = () => {
const openPicker = (endpoint: 'single' | 'start' | 'end' = 'single') => {
setActiveEndpoint(endpoint);
setShowPicker(true);
setShouldRender(true);
setTimeout(() => setIsAnimatingIn(true), 10);
@@ -85,16 +97,16 @@ export const Settings = () => {
}, 200);
};
const argbColorRegex = /^#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})$/i;
const hexColorRegex = /^#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})$/i;
const addCustomColor = () => {
const trimmed = customInput.trim();
if (
argbColorRegex.test(trimmed) &&
hexColorRegex.test(trimmed) &&
!colorPresets.includes(trimmed) &&
!customColors.includes(normalizeToARGB(trimmed))
!customColors.includes(normalizeToRGB(trimmed))
) {
const updated = [...customColors, normalizeToARGB(trimmed)];
const updated = [...customColors, normalizeToRGB(trimmed)];
setCustomColors(updated);
settings.customColors = updated;
}
@@ -115,12 +127,10 @@ export const Settings = () => {
return (
<LunaSettings>
{/* Mode selection via dropdown */}
<div style={{ padding: "8px 0", display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
<div style={{ minWidth: 160 }}>
<div style={{ fontWeight: "normal", fontSize: "1.075rem", marginBottom: 4 }}>Mode</div>
<div style={{ opacity: 0.7, fontSize: 14 }}>Choose how lyrics are colored</div>
</div>
{/* Mode selection via dropdown (aligned right) */}
<div style={{ padding: "8px 0", display: "flex", alignItems: "center", gap: 12 }}>
<div style={{ fontWeight: "normal", fontSize: "1.075rem" }}>Mode</div>
<div style={{ opacity: 0.7, fontSize: 14 }}>Choose how lyrics are colored</div>
<select
value={mode}
onChange={(e) => {
@@ -132,15 +142,18 @@ export const Settings = () => {
padding: "6px 10px",
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(255,255,255,0.05)",
background: "rgba(255,255,255,0.08)",
color: "#fff",
cursor: "pointer"
cursor: "pointer",
marginLeft: "auto",
minWidth: 180
}}
>
<option value="single">Single</option>
<option value="gradient">Gradient</option>
<option value="auto-single">Auto (Cover)</option>
<option value="auto-gradient">Auto Gradient</option>
<option value="single" style={{ color: '#000', background: '#fff' }}>Single</option>
<option value="gradient" style={{ color: '#000', background: '#fff' }}>Gradient</option>
<option value="auto-single" style={{ color: '#000', background: '#fff' }}>Auto (Cover)</option>
<option value="auto-gradient" style={{ color: '#000', background: '#fff' }}>Auto Gradient</option>
<option value="rainbow" style={{ color: '#000', background: '#fff' }}>Rainbow</option>
</select>
</div>
@@ -148,101 +161,71 @@ export const Settings = () => {
<div style={{ padding: "8px 0", display: mode === "single" ? "flex" : "none", justifyContent: "space-between", alignItems: "center" }}>
<div>
<div style={{ fontWeight: "normal", fontSize: "1.075rem", marginBottom: 4 }}>Lyrics Color</div>
<div style={{ opacity: 0.7, fontSize: 14 }}>Solid color (HEX/ARGB HEX)</div>
<div style={{ opacity: 0.7, fontSize: 14 }}>Solid color (configure inside picker)</div>
</div>
<div style={{ display: "flex", gap: 8, alignItems: "center", position: "relative" }}>
<button
onClick={() => (showPicker ? closePicker() : openPicker())}
onClick={() => (showPicker ? closePicker() : openPicker('single'))}
style={{
width: 32,
height: 32,
border: "1px solid rgba(255,255,255,0.15)",
borderRadius: 6,
cursor: "pointer",
background: normalizeToARGB(singleColor)
background: normalizeToRGB(singleColor)
}}
/>
</div>
</div>
<div style={{ display: mode === "single" ? 'block' : 'none' }}>
<LunaNumberSetting
title="Single Alpha"
desc="Opacity of the single color (0-100%)"
min={0}
max={100}
step={1}
value={Math.round(getAlpha01(singleColor) * 100)}
onNumber={(value: number) => {
const next = setAlphaOnARGB(normalizeToARGB(singleColor), value / 100);
setSingleColor((settings.singleColor = next));
if (customInput) setCustomInput(next);
requestApply();
}}
/>
</div>
{/* Gradient controls */}
{/* Gradient controls (triggers only) */}
<div style={{ padding: "8px 0", display: mode === "gradient" ? "block" : "none" }}>
<div style={{ fontWeight: "normal", fontSize: "1.075rem", marginBottom: 4 }}>Gradient</div>
<div style={{ opacity: 0.7, fontSize: 14, marginBottom: 8 }}>Pick start/end and angle</div>
<div style={{ opacity: 0.7, fontSize: 14, marginBottom: 8 }}>Pick start/end and angle (inside picker)</div>
<div style={{ display: "flex", gap: 12, alignItems: "center" }}>
<button
onClick={() => {
setCustomInput(gradientStart);
openPicker();
openPicker('start');
}}
title="Start Color"
style={{ width: 32, height: 32, border: "1px solid rgba(255,255,255,0.15)", borderRadius: 6, background: normalizeToARGB(gradientStart) }}
style={{ width: 32, height: 32, border: "1px solid rgba(255,255,255,0.15)", borderRadius: 6, background: normalizeToRGB(gradientStart) }}
/>
<button
onClick={() => {
setCustomInput(gradientEnd);
openPicker();
openPicker('end');
}}
title="End Color"
style={{ width: 32, height: 32, border: "1px solid rgba(255,255,255,0.15)", borderRadius: 6, background: normalizeToARGB(gradientEnd) }}
style={{ width: 32, height: 32, border: "1px solid rgba(255,255,255,0.15)", borderRadius: 6, background: normalizeToRGB(gradientEnd) }}
/>
</div>
<LunaNumberSetting
title="Start Alpha"
desc="Opacity of the gradient start (0-100%)"
min={0}
max={100}
step={1}
value={Math.round(getAlpha01(gradientStart) * 100)}
onNumber={(value: number) => {
const next = setAlphaOnARGB(normalizeToARGB(gradientStart), value / 100);
setGradientStart((settings.gradientStart = next));
requestApply();
}}
/>
<LunaNumberSetting
title="End Alpha"
desc="Opacity of the gradient end (0-100%)"
min={0}
max={100}
step={1}
value={Math.round(getAlpha01(gradientEnd) * 100)}
onNumber={(value: number) => {
const next = setAlphaOnARGB(normalizeToARGB(gradientEnd), value / 100);
setGradientEnd((settings.gradientEnd = next));
requestApply();
}}
/>
<LunaNumberSetting
title="Gradient Angle"
desc="Angle in degrees (0-360)"
min={0}
max={360}
step={1}
value={gradientAngle}
onNumber={(value: number) => {
setGradientAngle((settings.gradientAngle = value));
requestApply();
}}
/>
</div>
{/* Auto gradient controls (open picker for angle) */}
<div style={{ padding: "8px 0", display: mode === "auto-gradient" ? "flex" : "none", justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<div style={{ fontWeight: "normal", fontSize: "1.075rem", marginBottom: 4 }}>Auto Gradient</div>
<div style={{ opacity: 0.7, fontSize: 14 }}>Configure angle inside the picker</div>
</div>
<button
onClick={() => openPicker('start')}
style={{
padding: '8px 12px',
borderRadius: 8,
border: '1px solid rgba(255,255,255,0.2)',
background: 'rgba(255,255,255,0.08)',
color: '#fff',
cursor: 'pointer'
}}
>
Configure
</button>
</div>
{/* Rainbow controls removed: mode exists but has no UI */}
{/* Modal for picking and managing colors (reused) */}
{shouldRender && (
<>
@@ -281,112 +264,254 @@ export const Settings = () => {
transition: "all 0.2s ease"
}}
>
<div style={{ marginBottom: 12, color: "#fff", fontWeight: "bold", fontSize: 14 }}>Choose Color (ARGB HEX)</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 8, marginBottom: 16 }}>
{allColors.map((color, index) => (
<button
key={index}
onClick={() => {
if (mode === "single") {
const next = normalizeToARGB(color);
setSingleColor((settings.singleColor = next));
} else if (mode === "gradient") {
// Toggle which endpoint to update based on last edited input
if (customInput.toLowerCase() === gradientEnd.toLowerCase()) {
setGradientEnd((settings.gradientEnd = normalizeToARGB(color)));
} else {
setGradientStart((settings.gradientStart = normalizeToARGB(color)));
}
}
setCustomInput(normalizeToARGB(color));
requestApply();
closePicker();
}}
style={{
width: 32,
height: 32,
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.2)",
background: normalizeToARGB(color),
cursor: "pointer"
}}
/>
))}
<div style={{ marginBottom: 12, color: "#fff", fontWeight: "bold", fontSize: 14 }}>
{mode === 'single' ? 'Single Color' : 'Gradient Colors'}
</div>
<div style={{ marginBottom: 12 }}>
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: 12, marginBottom: 6 }}>Custom ARGB Hex (#AARRGGBB)</div>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<input
type="text"
value={customInput}
onChange={(e) => setCustomInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
{mode === 'gradient' && (
<div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12 }}>
<div style={{ color: 'rgba(255,255,255,0.7)', fontSize: 12 }}>Editing</div>
<button
onClick={() => { setActiveEndpoint('start'); setCustomInput(gradientStart); }}
style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '6px 10px', borderRadius: 8,
border: activeEndpoint === 'start' ? '1px solid rgba(255,255,255,0.5)' : '1px solid rgba(255,255,255,0.2)',
background: 'rgba(255,255,255,0.05)', color: '#fff', cursor: 'pointer'
}}
>
<span style={{ width: 14, height: 14, borderRadius: 3, background: normalizeToRGB(gradientStart), border: '1px solid rgba(255,255,255,0.3)' }} />
<span style={{ fontSize: 12 }}>Start</span>
</button>
<button
onClick={() => { setActiveEndpoint('end'); setCustomInput(gradientEnd); }}
style={{
display: 'flex', alignItems: 'center', gap: 8,
padding: '6px 10px', borderRadius: 8,
border: activeEndpoint === 'end' ? '1px solid rgba(255,255,255,0.5)' : '1px solid rgba(255,255,255,0.2)',
background: 'rgba(255,255,255,0.05)', color: '#fff', cursor: 'pointer'
}}
>
<span style={{ width: 14, height: 14, borderRadius: 3, background: normalizeToRGB(gradientEnd), border: '1px solid rgba(255,255,255,0.3)' }} />
<span style={{ fontSize: 12 }}>End</span>
</button>
</div>
)}
{mode !== 'auto-gradient' && (
<div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 8, marginBottom: 16 }}>
{allColors.map((color, index) => (
<button
key={index}
onClick={() => {
if (mode === "single") {
const next = normalizeToRGB(color);
setSingleColor((settings.singleColor = next));
} else if (mode === "gradient") {
if (activeEndpoint === 'end') {
setGradientEnd((settings.gradientEnd = normalizeToRGB(color)));
} else {
setGradientStart((settings.gradientStart = normalizeToRGB(color)));
}
}
setCustomInput(normalizeToRGB(color));
requestApply();
}}
style={{
width: 32,
height: 32,
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.2)",
background: normalizeToRGB(color),
cursor: "pointer"
}}
/>
))}
</div>
)}
{mode !== 'auto-gradient' && (
<div style={{ marginBottom: 12 }}>
<div style={{ color: "rgba(255,255,255,0.7)", fontSize: 12, marginBottom: 6 }}>Custom Hex (#RRGGBB)</div>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<input
type="text"
value={customInput}
onChange={(e) => setCustomInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
const trimmed = customInput.trim();
if (hexColorRegex.test(trimmed)) {
if (mode === "single") {
const next = normalizeToRGB(trimmed);
setSingleColor((settings.singleColor = next));
setCustomInput(next);
} else if (mode === "gradient") {
const norm = normalizeToRGB(trimmed);
if (activeEndpoint === 'end') {
setGradientEnd((settings.gradientEnd = norm));
} else {
setGradientStart((settings.gradientStart = norm));
}
setCustomInput(norm);
}
requestApply();
}
addCustomColor();
}
}}
placeholder="#RRGGBB"
style={{
flex: 1,
padding: "8px 12px",
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(255,255,255,0.1)",
color: "#fff",
fontSize: 14,
fontFamily: "monospace",
boxSizing: "border-box"
}}
/>
<button
onClick={() => {
const trimmed = customInput.trim();
if (argbColorRegex.test(trimmed)) {
if (hexColorRegex.test(trimmed)) {
if (mode === "single") {
setSingleColor((settings.singleColor = normalizeToARGB(trimmed)));
setSingleColor((settings.singleColor = normalizeToRGB(trimmed)));
} else if (mode === "gradient") {
if (customInput.toLowerCase() === gradientEnd.toLowerCase()) {
setGradientEnd((settings.gradientEnd = normalizeToARGB(trimmed)));
const norm = normalizeToRGB(trimmed);
if (activeEndpoint === 'end') {
setGradientEnd((settings.gradientEnd = norm));
} else {
setGradientStart((settings.gradientStart = normalizeToARGB(trimmed)));
setGradientStart((settings.gradientStart = norm));
}
}
requestApply();
}
addCustomColor();
}
}}
placeholder="#AARRGGBB"
style={{
flex: 1,
padding: "8px 12px",
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.2)",
background: "rgba(255,255,255,0.1)",
color: "#fff",
fontSize: 14,
fontFamily: "monospace",
boxSizing: "border-box"
}}
/>
<button
onClick={() => {
const trimmed = customInput.trim();
if (argbColorRegex.test(trimmed)) {
if (mode === "single") {
setSingleColor((settings.singleColor = normalizeToARGB(trimmed)));
} else if (mode === "gradient") {
if (customInput.toLowerCase() === gradientEnd.toLowerCase()) {
setGradientEnd((settings.gradientEnd = normalizeToARGB(trimmed)));
} else {
setGradientStart((settings.gradientStart = normalizeToARGB(trimmed)));
}
}
requestApply();
}
addCustomColor();
}}
style={{
width: 32,
height: 32,
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.3)",
background: "rgba(255,255,255,0.15)",
color: "#fff",
cursor: "pointer",
fontSize: 16,
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "all 0.2s ease"
}}
>
+
</button>
}}
style={{
width: 32,
height: 32,
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.3)",
background: "rgba(255,255,255,0.15)",
color: "#fff",
cursor: "pointer",
fontSize: 16,
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "all 0.2s ease"
}}
>
+
</button>
</div>
</div>
</div>
)}
{/* Sliders inside picker based on mode */}
{mode === 'single' && (
<div style={{ marginBottom: 16 }}>
<div style={{ color: "rgba(255,255,255,0.8)", fontSize: 12, marginBottom: 6 }}>Alpha</div>
<input
type="range"
min={0}
max={100}
step={1}
value={singleAlpha}
onChange={(e) => {
const value = Number(e.target.value);
setSingleAlpha((settings.singleAlpha = value));
requestApply();
}}
style={{ width: '100%' }}
/>
</div>
)}
{mode === 'gradient' && (
<div style={{ marginBottom: 16, display: 'grid', gap: 16 }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
<div style={{ width: 12, height: 12, borderRadius: 3, background: normalizeToRGB(gradientStart), border: '1px solid rgba(255,255,255,0.3)' }} />
<div style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12 }}>Start Alpha</div>
</div>
<input
type="range"
min={0}
max={100}
step={1}
value={gradientStartAlpha}
onChange={(e) => {
const value = Number(e.target.value);
setGradientStartAlpha((settings.gradientStartAlpha = value));
requestApply();
}}
style={{ width: '100%' }}
/>
</div>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
<div style={{ width: 12, height: 12, borderRadius: 3, background: normalizeToRGB(gradientEnd), border: '1px solid rgba(255,255,255,0.3)' }} />
<div style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12 }}>End Alpha</div>
</div>
<input
type="range"
min={0}
max={100}
step={1}
value={gradientEndAlpha}
onChange={(e) => {
const value = Number(e.target.value);
setGradientEndAlpha((settings.gradientEndAlpha = value));
requestApply();
}}
style={{ width: '100%' }}
/>
</div>
<div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
<div style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12 }}>Angle</div>
<div style={{ color: 'rgba(255,255,255,0.6)', fontSize: 12 }}>{gradientAngle}°</div>
</div>
<input
type="range"
min={0}
max={360}
step={1}
value={gradientAngle}
onChange={(e) => {
const value = Number(e.target.value);
setGradientAngle((settings.gradientAngle = value));
requestApply();
}}
style={{ width: '100%' }}
/>
</div>
</div>
)}
{mode === 'auto-gradient' && (
<div style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
<div style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12 }}>Angle</div>
<div style={{ color: 'rgba(255,255,255,0.6)', fontSize: 12 }}>{gradientAngle}°</div>
</div>
<input
type="range"
min={0}
max={360}
step={1}
value={gradientAngle}
onChange={(e) => {
const value = Number(e.target.value);
setGradientAngle((settings.gradientAngle = value));
requestApply();
}}
style={{ width: '100%' }}
/>
</div>
)}
<button
onClick={closePicker}
style={{
@@ -405,7 +530,7 @@ export const Settings = () => {
</div>
</>
)}
<LunaSwitchSetting
<AnySwitch
title="Exclude Inactive | Experimental"
desc="Apply color/gradient only to the currently active lyric line"
checked={excludeInactive}