diff --git a/Reference/luna-plugins b/Reference/luna-plugins deleted file mode 160000 index 29204be..0000000 --- a/Reference/luna-plugins +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 29204beb9362803ee82fe5d4fcc9d80c4fa1fb4a diff --git a/plugins/radiant-lyrics-luna/src/Settings.tsx b/plugins/radiant-lyrics-luna/src/Settings.tsx index 2037f89..d8547ad 100644 --- a/plugins/radiant-lyrics-luna/src/Settings.tsx +++ b/plugins/radiant-lyrics-luna/src/Settings.tsx @@ -2,11 +2,29 @@ import { ReactiveStore } from "@luna/core"; import { LunaSettings, LunaSwitchSetting, LunaNumberSetting } from "@luna/ui"; import React from "react"; +declare global { + interface Window { + updateRadiantLyricsStyles?: () => void; + updateRadiantLyricsTextGlow?: () => void; + updateStickyLyricsFeature?: () => void; + updateRadiantLyricsPlayerBarTint?: () => void; + updateRadiantLyricsGlobalBackground?: () => void; + updateRadiantLyricsNowPlayingBackground?: () => void; + updateStickyLyricsIcon?: () => void; + } +} + export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", { lyricsGlowEnabled: true, trackTitleGlow: false, hideUIEnabled: true, playerBarVisible: false, + floatingPlayerBar: true, + playerBarTint: 5, + playerBarTintColor: "#000000" as string, + playerBarTintCustomColors: [] as string[], + playerBarRadius: 5, + playerBarSpacing: 10, CoverEverywhere: true, performanceMode: false, spinningArt: true, @@ -64,6 +82,27 @@ export const Settings = () => { const [backgroundRadius, setBackgroundRadius] = React.useState( settings.backgroundRadius, ); + const [floatingPlayerBar, setFloatingPlayerBar] = React.useState( + settings.floatingPlayerBar, + ); + const [playerBarTint, setPlayerBarTint] = React.useState( + settings.playerBarTint, + ); + const [playerBarTintColor, setPlayerBarTintColor] = React.useState( + settings.playerBarTintColor, + ); + 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(null); const [stickyLyricsFeature, setStickyLyricsFeature] = React.useState( settings.stickyLyricsFeature, ); @@ -85,10 +124,11 @@ export const Settings = () => { desc="Enable glowing effect for lyrics & Font Styling Changes" checked={lyricsGlowEnabled} onChange={(_: unknown, checked: boolean) => { - setLyricsGlowEnabled((settings.lyricsGlowEnabled = checked)); + settings.lyricsGlowEnabled = checked; + setLyricsGlowEnabled(checked); // Update styles immediately when setting changes - if ((window as any).updateRadiantLyricsStyles) { - (window as any).updateRadiantLyricsStyles(); + if (window.updateRadiantLyricsStyles) { + window.updateRadiantLyricsStyles(); } }} /> @@ -97,20 +137,40 @@ export const Settings = () => { desc="Apply glow to the track title" checked={trackTitleGlow} onChange={(_: unknown, checked: boolean) => { - setTrackTitleGlow((settings.trackTitleGlow = checked)); - if ((window as any).updateRadiantLyricsStyles) { - (window as any).updateRadiantLyricsStyles(); + settings.trackTitleGlow = checked; + setTrackTitleGlow(checked); + if (window.updateRadiantLyricsStyles) { + window.updateRadiantLyricsStyles(); } }} /> + {(lyricsGlowEnabled || trackTitleGlow) && ( + { + settings.textGlow = value; + setTextGlow(value); + // Update variables immediately when setting changes + if (window.updateRadiantLyricsTextGlow) { + window.updateRadiantLyricsTextGlow(); + } + }} + /> + )} { - setStickyLyricsFeature((settings.stickyLyricsFeature = checked)); - if ((window as any).updateStickyLyricsFeature) { - (window as any).updateStickyLyricsFeature(); + settings.stickyLyricsFeature = checked; + setStickyLyricsFeature(checked); + if (window.updateStickyLyricsFeature) { + window.updateStickyLyricsFeature(); } }} /> @@ -119,22 +179,341 @@ export const Settings = () => { desc="Enable hide/unhide UI functionality with toggle buttons" checked={hideUIEnabled} onChange={(_: unknown, checked: boolean) => { - setHideUIEnabled((settings.hideUIEnabled = checked)); + settings.hideUIEnabled = checked; + setHideUIEnabled(checked); }} /> + {hideUIEnabled && ( { console.log("Player Bar Visibility:", checked ? "visible" : "hidden"); - setPlayerBarVisible((settings.playerBarVisible = checked)); + settings.playerBarVisible = checked; + setPlayerBarVisible(checked); // Update styles immediately when setting changes - if ((window as any).updateRadiantLyricsStyles) { - (window as any).updateRadiantLyricsStyles(); + if (window.updateRadiantLyricsStyles) { + window.updateRadiantLyricsStyles(); } }} /> + )} + { + 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?.(); + }} + /> + + )} + {(() => { + 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", + }} + /> + +
+
+ + + + + )} + + ); + })()} { "Spinning Cover Everywhere:", checked ? "enabled" : "disabled", ); - setCoverEverywhere( - (settings.CoverEverywhere = checked), - ); + settings.CoverEverywhere = checked; + setCoverEverywhere(checked); // Update styles immediately when setting changes - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } }} /> + {CoverEverywhere && ( { console.log("Performance Mode:", checked ? "enabled" : "disabled"); - setPerformanceMode((settings.performanceMode = checked)); + settings.performanceMode = checked; + setPerformanceMode(checked); // Update background animations immediately when setting changes - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } - if ((window as any).updateRadiantLyricsNowPlayingBackground) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + if (window.updateRadiantLyricsNowPlayingBackground) { + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { "Background Cover Spin:", checked ? "enabled" : "disabled", ); - setspinningArt((settings.spinningArt = checked)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.spinningArt = checked; + setspinningArt(checked); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { - setTextGlow((settings.textGlow = value)); - // Update variables immediately when setting changes - if ((window as any).updateRadiantLyricsTextGlow) { - (window as any).updateRadiantLyricsTextGlow(); - } - }} - /> - { - setBackgroundScale((settings.backgroundScale = value)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.backgroundScale = value; + setBackgroundScale(value); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { - setBackgroundRadius((settings.backgroundRadius = value)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.backgroundRadius = value; + setBackgroundRadius(value); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { step={1} value={backgroundContrast} onNumber={(value: number) => { - setBackgroundContrast((settings.backgroundContrast = value)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.backgroundContrast = value; + setBackgroundContrast(value); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { value={backgroundBlur} onNumber={(value: number) => { console.log("Background Blur:", value); - setBackgroundBlur((settings.backgroundBlur = value)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.backgroundBlur = value; + setBackgroundBlur(value); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { value={backgroundBrightness} onNumber={(value: number) => { console.log("Background Brightness:", value); - setBackgroundBrightness((settings.backgroundBrightness = value)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.backgroundBrightness = value; + setBackgroundBrightness(value); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && spinningArt && ( { value={spinSpeed} onNumber={(value: number) => { console.log("Spin Speed:", value); - setSpinSpeed((settings.spinSpeed = value)); - if ((window as any).updateRadiantLyricsGlobalBackground) { - (window as any).updateRadiantLyricsGlobalBackground(); + settings.spinSpeed = value; + setSpinSpeed(value); + if (window.updateRadiantLyricsGlobalBackground) { + window.updateRadiantLyricsGlobalBackground(); } if ( settings.settingsAffectNowPlaying && - (window as any).updateRadiantLyricsNowPlayingBackground + window.updateRadiantLyricsNowPlayingBackground ) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} + {CoverEverywhere && ( { "Settings Affect Now Playing:", checked ? "enabled" : "disabled", ); - setSettingsAffectNowPlaying( - (settings.settingsAffectNowPlaying = checked), - ); + settings.settingsAffectNowPlaying = checked; + setSettingsAffectNowPlaying(checked); // Update Now Playing background immediately when setting changes - if ((window as any).updateRadiantLyricsNowPlayingBackground) { - (window as any).updateRadiantLyricsNowPlayingBackground(); + if (window.updateRadiantLyricsNowPlayingBackground) { + window.updateRadiantLyricsNowPlayingBackground(); } }} /> + )} ); }; diff --git a/plugins/radiant-lyrics-luna/src/floating-player-bar.css b/plugins/radiant-lyrics-luna/src/floating-player-bar.css new file mode 100644 index 0000000..b64aad8 --- /dev/null +++ b/plugins/radiant-lyrics-luna/src/floating-player-bar.css @@ -0,0 +1,9 @@ +/* Floating Rounded Player Bar from Obsidian <3 */ + +/* MARKER: Floating Player Bar CSS*/ + +[data-test="footer-player"] { + position: absolute !important; + backdrop-filter: blur(10px); + border: 1px solid var(--wave-color-opacity-contrast-fill-ultra-thin) !important; +} diff --git a/plugins/radiant-lyrics-luna/src/index.ts b/plugins/radiant-lyrics-luna/src/index.ts index 495df1d..32afd19 100644 --- a/plugins/radiant-lyrics-luna/src/index.ts +++ b/plugins/radiant-lyrics-luna/src/index.ts @@ -1,4 +1,4 @@ -// Marker: Core Setup +// MARKER: Core Setup import { LunaUnload, Tracer, ftch } from "@luna/core"; import { StyleTag, PlayState, observePromise, observe } from "@luna/lib"; import { settings, Settings } from "./Settings"; @@ -13,6 +13,7 @@ import baseStyles from "file://styles.css?minify"; import playerBarHidden from "file://player-bar-hidden.css?minify"; import lyricsGlow from "file://lyrics-glow.css?minify"; import coverEverywhereCss from "file://cover-everywhere.css?minify"; +import floatingPlayerBarCss from "file://floating-player-bar.css?minify"; // Core tracer and exports export const { trace } = Tracer("[Radiant Lyrics]"); @@ -27,12 +28,78 @@ const lyricsStyleTag = new StyleTag("RadiantLyrics-lyrics", unloads); const baseStyleTag = new StyleTag("RadiantLyrics-base", unloads); const playerBarStyleTag = new StyleTag("RadiantLyrics-player-bar", unloads); const lyricsGlowStyleTag = new StyleTag("RadiantLyrics-lyrics-glow", unloads); +const floatingPlayerBarStyleTag = new StyleTag("RadiantLyrics-floating-player-bar", unloads); // Apply lyrics glow styles if enabled if (settings.lyricsGlowEnabled) { lyricsGlowStyleTag.css = lyricsGlow; } +// MARKER: Floating Player Bar + +// Hex color to RGB +// (i'm deranged and love Hexadecimal) +const hexToRgb = (hex: string): { r: number; g: number; b: number } => { + let cleaned = (hex || "#000000").replace("#", ""); + if (cleaned.length === 3) { + cleaned = cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2]; + } + if (cleaned.length !== 6) { + return { r: 0, g: 0, b: 0 }; + } + return { + r: parseInt(cleaned.substring(0, 2), 16) || 0, + g: parseInt(cleaned.substring(2, 4), 16) || 0, + b: parseInt(cleaned.substring(4, 6), 16) || 0, + }; +}; + +// Apply Settings to Floating Player Bar using inline styles because idk.. CSS is hard (Change my mind!) +const applyPlayerBarTintToElement = (): void => { + const footerPlayer = document.querySelector('[data-test="footer-player"]') as HTMLElement; + if (!footerPlayer) return; + // Always apply tint regardless of floating state + const alpha = settings.playerBarTint / 10; + const { r, g, b } = hexToRgb(settings.playerBarTintColor); + footerPlayer.style.setProperty("background-color", `rgba(${r}, ${g}, ${b}, ${alpha})`, "important"); + if (settings.floatingPlayerBar) { + footerPlayer.style.setProperty("border-radius", `${settings.playerBarRadius}px`, "important"); + const spacing = settings.playerBarSpacing; + footerPlayer.style.setProperty("bottom", `${spacing}px`, "important"); + footerPlayer.style.setProperty("left", `${spacing}px`, "important"); + footerPlayer.style.setProperty("width", `calc(100% - ${spacing * 2}px)`, "important"); + } else { + footerPlayer.style.removeProperty("border-radius"); + footerPlayer.style.removeProperty("bottom"); + footerPlayer.style.removeProperty("left"); + footerPlayer.style.removeProperty("width"); + } +}; + +// Apply/update the floating player bar stylesheet + tint +const applyFloatingPlayerBar = (): void => { + if (settings.floatingPlayerBar) { + floatingPlayerBarStyleTag.css = floatingPlayerBarCss; + } else { + floatingPlayerBarStyleTag.remove(); + } + applyPlayerBarTintToElement(); +}; + +// Alias for settings callback +const updateRadiantLyricsPlayerBarTint = applyFloatingPlayerBar; + +// Apply floating player bar styles if enabled +if (settings.floatingPlayerBar) { + floatingPlayerBarStyleTag.css = floatingPlayerBarCss; +} + +// Apply Tint and Observe in case doesn't exist yet (ik this isnt the best way to do it but.. make a PR i dare ya!) +applyPlayerBarTintToElement(); +observe(unloads, '[data-test="footer-player"]', () => { + applyPlayerBarTintToElement(); +}); + // Apply base styles always (contains global fixes and conditional UI hiding styles) baseStyleTag.css = baseStyles; @@ -56,6 +123,9 @@ const updateRadiantLyricsStyles = function (): void { playerBarStyleTag.remove(); } + // Handle Floating Player Bar + applyFloatingPlayerBar(); + // Update lyrics glow based on setting (Always apply if enabled, even when UI is hidden) const lyricsContainer = document.querySelector('[class^="_lyricsContainer"]'); if (lyricsContainer) { @@ -96,7 +166,7 @@ const updateRadiantLyricsStyles = function (): void { } }; -// Marker: UI Visibility Control +// MARKER: UI Visibility Control // UI state shared across features var isHidden = false; let unhideButtonAutoFadeTimeout: number | null = null; @@ -303,7 +373,7 @@ const createUnhideUIButton = function (): void { }, 1500); }; -// Marker: Background Rendering +// MARKER: Background Rendering // Variable setup let globalSpinningBgStyleTag: StyleTag | null = null; let globalBackgroundContainer: HTMLElement | null = null; @@ -790,6 +860,7 @@ const updateRadiantLyricsNowPlayingBackground = function (): void { (window as any).updateRadiantLyricsNowPlayingBackground = updateRadiantLyricsNowPlayingBackground; (window as any).updateRadiantLyricsTextGlow = updateRadiantLyricsTextGlow; +(window as any).updateRadiantLyricsPlayerBarTint = updateRadiantLyricsPlayerBarTint; const cleanUpDynamicArt = function (): void { // Clean up cached Now Playing elements @@ -861,6 +932,16 @@ updateCoverArtBackground(1); unloads.add(() => { cleanUpDynamicArt(); + // Clean up floating player bar inline styles + const footerPlayer = document.querySelector('[data-test="footer-player"]') as HTMLElement; + if (footerPlayer) { + footerPlayer.style.removeProperty("background-color"); + footerPlayer.style.removeProperty("border-radius"); + footerPlayer.style.removeProperty("bottom"); + footerPlayer.style.removeProperty("left"); + footerPlayer.style.removeProperty("width"); + } + // Clean up HideUI button auto-fade timeout if (unhideButtonAutoFadeTimeout != null) { window.clearTimeout(unhideButtonAutoFadeTimeout);