diff --git a/Reference/BetterLyrics b/Reference/BetterLyrics new file mode 160000 index 0000000..d6393c6 --- /dev/null +++ b/Reference/BetterLyrics @@ -0,0 +1 @@ +Subproject commit d6393c6739087005374abf6ce5ddea023ff5b078 diff --git a/Reference/applemusic-like-lyrics b/Reference/applemusic-like-lyrics new file mode 160000 index 0000000..48fb050 --- /dev/null +++ b/Reference/applemusic-like-lyrics @@ -0,0 +1 @@ +Subproject commit 48fb050d2fac2bf4c8b67c973c5ee3f856c6a691 diff --git a/plugins/radiant-lyrics-luna/src/Settings.tsx b/plugins/radiant-lyrics-luna/src/Settings.tsx index 2037f89..6d46947 100644 --- a/plugins/radiant-lyrics-luna/src/Settings.tsx +++ b/plugins/radiant-lyrics-luna/src/Settings.tsx @@ -7,6 +7,12 @@ export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", { 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 +70,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, ); @@ -103,6 +130,23 @@ export const Settings = () => { } }} /> + {(lyricsGlowEnabled || trackTitleGlow) && ( + { + setTextGlow((settings.textGlow = value)); + // Update variables immediately when setting changes + if ((window as any).updateRadiantLyricsTextGlow) { + (window as any).updateRadiantLyricsTextGlow(); + } + }} + /> + )} { setHideUIEnabled((settings.hideUIEnabled = checked)); }} /> + {hideUIEnabled && ( { } }} /> + )} + { + setFloatingPlayerBar((settings.floatingPlayerBar = checked)); + if ((window as any).updateRadiantLyricsStyles) { + (window as any).updateRadiantLyricsStyles(); + } + }} + /> + {floatingPlayerBar && ( + <> + { + setPlayerBarRadius((settings.playerBarRadius = value)); + (window as any).updateRadiantLyricsPlayerBarTint?.(); + }} + /> + { + setPlayerBarSpacing((settings.playerBarSpacing = value)); + (window as any).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 as any).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 ( +
+ { + setPlayerBarTint((settings.playerBarTint = value)); + (window as any).updateRadiantLyricsPlayerBarTint?.(); + }} + /> + {/* Color swatch — positioned just left of the value box */} + + + {/* Color Picker Modal */} + {shouldRenderTintPicker && ( + <> +
+
+
+ Choose Tint Color +
+ +
+ {allTintColors.map((color, index) => { + const isCustomColor = tintCustomColors.includes(color); + const isHovered = tintHoveredColorIndex === index; + return ( +
setTintHoveredColorIndex(index)} + onMouseLeave={() => setTintHoveredColorIndex(null)} + > + + )} +
+ ); + })} +
+ +
+
+ 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", + }} + /> + +
+
+ + +
+ + )} +
+ ); + })()} { } }} /> + {CoverEverywhere && ( { } }} /> + )} + {CoverEverywhere && ( { } }} /> + )} + {CoverEverywhere && ( { - setTextGlow((settings.textGlow = value)); - // Update variables immediately when setting changes - if ((window as any).updateRadiantLyricsTextGlow) { - (window as any).updateRadiantLyricsTextGlow(); - } - }} - /> - { } }} /> + )} + {CoverEverywhere && ( { } }} /> + )} + {CoverEverywhere && ( { } }} /> + )} + {CoverEverywhere && ( { } }} /> + )} + {CoverEverywhere && ( { } }} /> + )} + {CoverEverywhere && spinningArt && ( { } }} /> + )} + {CoverEverywhere && ( { } }} /> + )} ); }; 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..fbf5720 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,72 @@ 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 } => { + const cleaned = (hex || "#000000").replace("#", ""); + 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 +117,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 +160,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 +367,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 +854,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 +926,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);