import { ReactiveStore } from "@luna/core"; import { LunaSettings, LunaSwitchSetting, LunaNumberSetting } from "@luna/ui"; import React from "react"; declare global { interface Window { updateRadiantLyricsStyles?: () => void; updateRadiantLyricsTextGlow?: () => void; updateRadiantLyricsPlayerBarTint?: () => void; updateRadiantLyricsBackdrop?: () => void; updateQualityProgressColor?: () => void; updateIntegratedSeekBar?: () => void; updateLyricsStyle?: () => void; updateLyricsStyleSetting?: (value: number) => void; updateRomanizeLyrics?: () => void; updateRomanizeLyricsSetting?: (checked: boolean) => void; updateAiSyllables?: () => void; updateAiSyllablesSetting?: (checked: boolean) => void; } } export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", { lyricsGlowEnabled: true, textGlow: 20, lyricsStyle: 2, lyricsFontSize: 100, blurInactive: true, contextAwareLyrics: true, bubbledLyrics: true, romanizeLyrics: false, aiSyllables: false, syllableStyle: 0, // MARKER: Syllable animations SETTINGS (WIP coming soon) syllableLogging: false, // Playback clock calibration in ms (i assume Lunar will break playback one day since MPV is wierd so i added this for that) lyricsOffsetMs: 0, hideUIEnabled: true, playerBarVisible: false, qualityProgressColor: true, integratedSeekBar: false, floatingPlayerBar: true, playerBarRadius: 5, playerBarSpacing: 10, playerBarBlur: true, playerBarBlurAmount: 15, playerBarTintEnabled: true, playerBarTint: 5, playerBarTintColor: "#000000" as string, playerBarTintCustomColors: [] as string[], // Master switch backdropEnabled: true, backdropStyle: 0, backdropPlaybackReactive: true, CoverEverywhere: true, performanceMode: false, // Backdrop is rendered entirely by kawarp (yay!) backdropOpacity: 75, backdropWarp: 100, backdropBlurPasses: 6, backdropSpeed: 175, backdropContrast: 125, backdropSaturation: 125, backdropDithering: 15, backdropScale: 100, // Readability on bright covers <3 backdropDarken: 80, }); export const Settings = () => { const [hideUIEnabled, setHideUIEnabled] = React.useState( settings.hideUIEnabled, ); const [playerBarVisible, setPlayerBarVisible] = React.useState( settings.playerBarVisible, ); const [lyricsGlowEnabled, setLyricsGlowEnabled] = React.useState( settings.lyricsGlowEnabled, ); const [textGlow, setTextGlow] = React.useState(settings.textGlow); const [CoverEverywhere, setCoverEverywhere] = React.useState( settings.CoverEverywhere, ); const [backdropEnabled, setBackdropEnabled] = React.useState( settings.backdropEnabled, ); const [backdropStyle, setBackdropStyle] = React.useState( settings.backdropStyle, ); const [backdropPlaybackReactive, setBackdropPlaybackReactive] = React.useState(settings.backdropPlaybackReactive); const [performanceMode, setPerformanceMode] = React.useState( settings.performanceMode, ); const [backdropOpacity, setBackdropOpacity] = React.useState( settings.backdropOpacity, ); const [backdropWarp, setBackdropWarp] = React.useState(settings.backdropWarp); const [backdropBlurPasses, setBackdropBlurPasses] = React.useState( settings.backdropBlurPasses, ); const [backdropSpeed, setBackdropSpeed] = React.useState( settings.backdropSpeed, ); const [backdropContrast, setBackdropContrast] = React.useState( settings.backdropContrast, ); const [backdropSaturation, setBackdropSaturation] = React.useState( settings.backdropSaturation, ); const [backdropDithering, setBackdropDithering] = React.useState( settings.backdropDithering, ); const [backdropScale, setBackdropScale] = React.useState( settings.backdropScale, ); const [backdropDarken, setBackdropDarken] = React.useState( settings.backdropDarken, ); const [floatingPlayerBar, setFloatingPlayerBar] = React.useState( settings.floatingPlayerBar, ); const [playerBarTintEnabled, setPlayerBarTintEnabled] = React.useState( settings.playerBarTintEnabled, ); const [playerBarTint, setPlayerBarTint] = React.useState( settings.playerBarTint, ); const [playerBarTintColor, setPlayerBarTintColor] = React.useState( settings.playerBarTintColor, ); const [playerBarBlur, setPlayerBarBlur] = React.useState( settings.playerBarBlur, ); const [playerBarBlurAmount, setPlayerBarBlurAmount] = React.useState( settings.playerBarBlurAmount, ); const [playerBarRadius, setPlayerBarRadius] = React.useState( settings.playerBarRadius, ); const [playerBarSpacing, setPlayerBarSpacing] = React.useState( settings.playerBarSpacing, ); const [showTintColorPicker, setShowTintColorPicker] = React.useState(false); const [isTintAnimatingIn, setIsTintAnimatingIn] = React.useState(false); const [shouldRenderTintPicker, setShouldRenderTintPicker] = React.useState(false); const [tintCustomInput, setTintCustomInput] = React.useState( settings.playerBarTintColor, ); const [tintCustomColors, setTintCustomColors] = React.useState( settings.playerBarTintCustomColors, ); const [tintHoveredColorIndex, setTintHoveredColorIndex] = React.useState< number | null >(null); const [lyricsStyle, setLyricsStyle] = React.useState(settings.lyricsStyle); React.useEffect(() => { window.updateLyricsStyleSetting = (value: number) => setLyricsStyle(value); return () => { window.updateLyricsStyleSetting = undefined; }; }, []); const [lyricsFontSize, setLyricsFontSize] = React.useState( settings.lyricsFontSize, ); const [contextAwareLyrics, setContextAwareLyrics] = React.useState( settings.contextAwareLyrics, ); const [blurInactive, setBlurInactive] = React.useState(settings.blurInactive); const [bubbledLyrics, setBubbledLyrics] = React.useState( settings.bubbledLyrics, ); const [qualityProgressColor, setQualityProgressColor] = React.useState( settings.qualityProgressColor, ); const [integratedSeekBar, setIntegratedSeekBar] = React.useState( settings.integratedSeekBar, ); const [romanizeLyrics, setRomanizeLyrics] = React.useState( settings.romanizeLyrics, ); React.useEffect(() => { window.updateRomanizeLyricsSetting = (checked: boolean) => setRomanizeLyrics(checked); return () => { window.updateRomanizeLyricsSetting = undefined; }; }, []); const [aiSyllables, setAiSyllables] = React.useState( settings.aiSyllables, ); React.useEffect(() => { window.updateAiSyllablesSetting = (checked: boolean) => setAiSyllables(checked); return () => { window.updateAiSyllablesSetting = undefined; }; }, []); const refreshBackdrop = () => { window.updateRadiantLyricsBackdrop?.(); }; // Derive props and override onChange to accept a broader first param type type BaseSwitchProps = React.ComponentProps; type AnySwitchProps = Omit & { onChange: (_: unknown, checked: boolean) => void; checked: boolean; }; const AnySwitch = LunaSwitchSetting as unknown as React.ComponentType; return ( { settings.lyricsGlowEnabled = checked; setLyricsGlowEnabled(checked); // Update styles immediately when setting changes if (window.updateRadiantLyricsStyles) { window.updateRadiantLyricsStyles(); } }} /> {lyricsGlowEnabled && ( { settings.textGlow = value; setTextGlow(value); // Update variables immediately when setting changes if (window.updateRadiantLyricsTextGlow) { window.updateRadiantLyricsTextGlow(); } }} /> )} { settings.lyricsStyle = value; setLyricsStyle(value); if (window.updateLyricsStyle) { window.updateLyricsStyle(); } }} /> { settings.lyricsFontSize = value; setLyricsFontSize(value); if (window.updateRadiantLyricsTextGlow) { window.updateRadiantLyricsTextGlow(); } }} /> { settings.blurInactive = checked; setBlurInactive(checked); if (window.updateLyricsStyle) { window.updateLyricsStyle(); } }} /> { settings.contextAwareLyrics = checked; setContextAwareLyrics(checked); if (window.updateLyricsStyle) { window.updateLyricsStyle(); } }} /> { settings.bubbledLyrics = checked; setBubbledLyrics(checked); if (window.updateLyricsStyle) { window.updateLyricsStyle(); } }} /> { settings.romanizeLyrics = checked; setRomanizeLyrics(checked); if (window.updateRomanizeLyrics) { window.updateRomanizeLyrics(); } }} /> { settings.aiSyllables = checked; setAiSyllables(checked); if (window.updateAiSyllables) { window.updateAiSyllables(); } }} /> { settings.hideUIEnabled = checked; setHideUIEnabled(checked); }} /> {hideUIEnabled && ( { console.log( "Player Bar Visibility:", checked ? "visible" : "hidden", ); settings.playerBarVisible = checked; setPlayerBarVisible(checked); // Update styles immediately when setting changes if (window.updateRadiantLyricsStyles) { window.updateRadiantLyricsStyles(); } }} /> )} { settings.qualityProgressColor = checked; setQualityProgressColor(checked); if (window.updateQualityProgressColor) { window.updateQualityProgressColor(); } }} /> { settings.integratedSeekBar = checked; setIntegratedSeekBar(checked); if (window.updateIntegratedSeekBar) { window.updateIntegratedSeekBar(); } }} /> { settings.floatingPlayerBar = checked; setFloatingPlayerBar(checked); if (window.updateRadiantLyricsStyles) { window.updateRadiantLyricsStyles(); } }} /> {floatingPlayerBar && ( <> { settings.playerBarRadius = value; setPlayerBarRadius(value); window.updateRadiantLyricsPlayerBarTint?.(); }} /> { settings.playerBarSpacing = value; setPlayerBarSpacing(value); window.updateRadiantLyricsPlayerBarTint?.(); }} /> )} { settings.playerBarBlur = checked; setPlayerBarBlur(checked); window.updateRadiantLyricsPlayerBarTint?.(); }} /> {playerBarBlur && ( { settings.playerBarBlurAmount = value; setPlayerBarBlurAmount(value); window.updateRadiantLyricsPlayerBarTint?.(); }} /> )} {(() => { const closeTintColorPicker = () => { setIsTintAnimatingIn(false); setTimeout(() => { setShowTintColorPicker(false); setShouldRenderTintPicker(false); }, 200); }; const openTintColorPicker = () => { setShowTintColorPicker(true); setShouldRenderTintPicker(true); setTimeout(() => setIsTintAnimatingIn(true), 10); }; const updateTintColor = (color: string) => { setPlayerBarTintColor(color); setTintCustomInput(color); settings.playerBarTintColor = color; window.updateRadiantLyricsPlayerBarTint?.(); }; const addTintCustomColor = () => { if (tintCustomInput) { const trimmedInput = tintCustomInput.trim().toLowerCase(); const hexColorRegex = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i; if ( hexColorRegex.test(trimmedInput) && !tintColorPresets.includes(trimmedInput) && !tintCustomColors.includes(trimmedInput) ) { const newCustomColors = [...tintCustomColors, trimmedInput]; setTintCustomColors(newCustomColors); settings.playerBarTintCustomColors = newCustomColors; } } }; const removeTintCustomColor = (colorToRemove: string) => { const newCustomColors = tintCustomColors.filter( (color) => color !== colorToRemove, ); setTintCustomColors(newCustomColors); settings.playerBarTintCustomColors = newCustomColors; if (playerBarTintColor === colorToRemove) { updateTintColor("#000000"); } }; const tintColorPresets = [ "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#888888", "#aaaaaa", "#cccccc", "#ffffff", "#0d1117", "#1a1a2e", "#16213e", "#0f3460", "#1b1b2f", "#162447", "#1f4068", "#e94560", ]; const allTintColors = [...tintColorPresets, ...tintCustomColors]; return (
{ settings.playerBarTint = value; setPlayerBarTint(value); window.updateRadiantLyricsPlayerBarTint?.(); }} /> {/* Color swatch — positioned just left of the value box */} {/* Color Picker Modal */} {shouldRenderTintPicker && ( <> )}
); })}
Add Custom Color
setTintCustomInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { updateTintColor(tintCustomInput); addTintCustomColor(); } }} placeholder="#000000" 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", }} />
)} ); })()} { settings.backdropEnabled = checked; setBackdropEnabled(checked); refreshBackdrop(); }} /> { settings.CoverEverywhere = checked; setCoverEverywhere(checked); refreshBackdrop(); }} /> {backdropEnabled && ( <> { settings.backdropStyle = value; setBackdropStyle(value); refreshBackdrop(); }} /> { settings.backdropPlaybackReactive = checked; setBackdropPlaybackReactive(checked); refreshBackdrop(); }} /> { settings.performanceMode = checked; setPerformanceMode(checked); refreshBackdrop(); }} /> { settings.backdropOpacity = value; setBackdropOpacity(value); refreshBackdrop(); }} /> { settings.backdropWarp = value; setBackdropWarp(value); refreshBackdrop(); }} /> { settings.backdropBlurPasses = value; setBackdropBlurPasses(value); refreshBackdrop(); }} /> { settings.backdropSpeed = value; setBackdropSpeed(value); refreshBackdrop(); }} /> { settings.backdropContrast = value; setBackdropContrast(value); refreshBackdrop(); }} /> { settings.backdropDarken = value; setBackdropDarken(value); refreshBackdrop(); }} /> { settings.backdropSaturation = value; setBackdropSaturation(value); refreshBackdrop(); }} /> { settings.backdropScale = value; setBackdropScale(value); refreshBackdrop(); }} /> { settings.backdropDithering = value; setBackdropDithering(value); refreshBackdrop(); }} /> )}
); };