diff --git a/plugins/radiant-lyrics-luna/src/api.ts b/plugins/radiant-lyrics-luna/src/api.ts new file mode 100644 index 0000000..db64109 --- /dev/null +++ b/plugins/radiant-lyrics-luna/src/api.ts @@ -0,0 +1,294 @@ +import { Tracer } from "@luna/core"; + +import { settings } from "./Settings"; + +const { trace } = Tracer("[Radiant Lyrics]"); + +const sylTrace = (...args: unknown[]) => { + if (settings.syllableLogging) trace.log(...args); +}; + +export const RL_PLATFORM = "Radiant Lyrics"; + +const RL_ACCESS_TOKEN_ID = "58hy4s86"; +const RL_ACCESS_TOKEN = "xjehy2lfg5h5mjwotoxrcqugam"; +// Yup that's right, plaintext token in a public Repo!! +// The API does not return sensitive data & won't be plain text like this in the future <3 + +let cachedPublicIP: string | undefined; + +export async function ip(): Promise { + if (cachedPublicIP) return cachedPublicIP; + try { + const res = await fetch("https://api.ipify.org?format=text"); + if (res.ok) cachedPublicIP = (await res.text()).trim(); + } catch {} + return cachedPublicIP; +} + +export async function auth(): Promise> { + const clientIP = await ip(); + return { + "P-Access-Token-Id": RL_ACCESS_TOKEN_ID, + "P-Access-Token": RL_ACCESS_TOKEN, + "x-client-ip": clientIP ?? "null", + }; +} + +// Platform param (just for DX logging) +const platformQs = `platform=${encodeURIComponent(RL_PLATFORM)}`; + +// Query string & params +function query( + title: string, + artist: string, + isrc: string | undefined, + options?: { romanize?: boolean; flush?: boolean }, +): string { + let q = `?title=${encodeURIComponent(title)}&artist=${encodeURIComponent(artist)}`; + if (isrc) q += `&isrc=${encodeURIComponent(isrc)}`; + if (options?.romanize) q += "&romanize=true"; + if (options?.flush) q += "&flush=true"; + q += `&${platformQs}`; + return q; +} + +// Response types + +export interface WordTiming { + text: string; + time: number; + duration: number; + isBackground: boolean; + romanized?: string; +} + +export interface WordLine { + text: string; + startTime: number; + duration: number; + endTime: number; + syllabus: WordTiming[]; + element: { + key: string; + songPart?: string; + songPartIndex?: number; + singer: string; + }; + translation: string | null; + romanized?: string; +} + +export interface ApiLine { + text: string; + startTime: number; + duration: number; + endTime: number; + syllabus?: WordTiming[]; + element?: { + key: string; + songPart?: string; + songPartIndex?: number; + singer?: string; + }; + translation?: string | null; + romanized?: string; +} + +export interface WordLyricsResponse { + type: "Word"; + data: WordLine[]; + metadata: { + source: string; + title: string; + language: string; + totalDuration: string; + agents?: Record; + songParts?: Array<{ name: string; time: number; duration: number }>; + }; + _cached?: boolean; +} + +export interface LineLyricsResponse { + type: "Line"; + data: ApiLine[]; + metadata: { + source: string; + title: string; + language: string; + totalDuration: string; + agents?: Record; + songParts?: Array<{ name: string; time: number; duration: number }>; + }; + _cached?: boolean; +} + +export type LyricsApiResponse = WordLyricsResponse | LineLyricsResponse; + +type FetchOutcome = + | { status: "ok"; data: LyricsApiResponse | null } + | { status: "404" } + | { status: "500" } + | { status: "err" }; + +// Lyrics lookup (network) +export async function fetchLyrics( + title: string, + artist: string, + isrc: string | undefined, + romanize: boolean, +): Promise { + const params = query(title, artist, isrc, { romanize }); + const atomixUrl = `https://api.atomix.one/rl-api${params}`; + const fallbackUrl = `https://rl-api.kineticsand.net/lyrics${params}`; + + const rlApiHeaders = await auth(); + + const tryFetch = async (url: string, useAtomixAuth: boolean): Promise => { + try { + sylTrace(`RL API: Fetching lyrics: ${url}`); + const res = await fetch(url, { + headers: useAtomixAuth ? rlApiHeaders : undefined, + }); + if (!res.ok) { + trace.log(`RL API: fetch failed: ${res.status} from ${url}`); + if (res.status === 404) return { status: "404" }; + return res.status === 500 ? { status: "500" } : { status: "err" }; + } + const data = (await res.json()) as LyricsApiResponse; + if (!data?.data || !Array.isArray(data.data)) { + trace.log("Lyrics API returned invalid payload"); + return { status: "ok", data: null }; + } + if (data.type !== "Word" && data.type !== "Line") { + trace.log("Lyrics not available in supported format"); + return { status: "ok", data: null }; + } + return { status: "ok", data }; + } catch (err) { + trace.log(`RL API: fetch error from ${url}: ${err}`); + return { status: "err" }; + } + }; + + const primary = await tryFetch(atomixUrl, true); + if (primary.status === "ok") return primary.data; + if (primary.status === "404") { + trace.log("RL API: 404 — no API lyrics exist for this track"); + return null; + } + if (primary.status === "500") { + trace.log("RL API: 500 (Execution Timeout) — fallback"); + } + + const fallback = await tryFetch(fallbackUrl, false); + if (fallback.status === "ok") return fallback.data; + if (fallback.status === "404") { + trace.log("RL API: 404 from fallback — no API lyrics exist for this track"); + return null; + } + if (fallback.status === "500") { + trace.log("RL API: 500 from fallback — API IS ACTUALLY BORKED!"); + return null; + } + + trace.log("RL API: All Endpoints Failed"); + return null; +} + +export async function flushLyrics(track: { + title: string; + artist: string; + isrc?: string; +}): Promise< + | { ok: true; data: LyricsApiResponse & { _flush?: string } } + | { ok: false; status: number; notFound: boolean } +> { + const q = query(track.title, track.artist, track.isrc, { + flush: true, + }); + const url = `https://api.atomix.one/rl-api${q}`; + const headers = await auth(); + const res = await fetch(url, { headers }); + if (res.status === 404) { + return { ok: false, status: 404, notFound: true }; + } + if (!res.ok) { + return { ok: false, status: res.status, notFound: false }; + } + const data = (await res.json()) as LyricsApiResponse & { _flush?: string }; + return { ok: true, data }; +} + +// Romanize +export async function romanizeLyrics( + lineTexts: string[], +): Promise { + if (lineTexts.length === 0) return null; + + const payload = { + type: "Line" as const, + data: lineTexts.map((text, idx) => ({ + text, + startTime: idx, + duration: 0, + endTime: idx, + })), + }; + + const romanizeQuery = `?${platformQs}`; + const urls: { url: string; useAtomixAuth: boolean }[] = [ + { + url: `https://api.atomix.one/rl-api/romanize${romanizeQuery}`, + useAtomixAuth: true, + }, + { + url: `https://rl-api.kineticsand.net/romanize${romanizeQuery}`, + useAtomixAuth: false, + }, + ]; + + for (const { url, useAtomixAuth } of urls) { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + try { + const romanizeHeaders: Record = { + "content-type": "application/json", + }; + if (useAtomixAuth) { + Object.assign(romanizeHeaders, await auth()); + } + const res = await fetch(url, { + method: "POST", + headers: romanizeHeaders, + body: JSON.stringify(payload), + signal: controller.signal, + }); + clearTimeout(timeout); + if (!res.ok) { + trace.log(`Romanize: request failed ${res.status} | ${url}`); + continue; + } + + const data = (await res.json()) as { + type?: string; + data?: Array<{ text?: string; romanized?: string }>; + }; + if (!Array.isArray(data?.data)) continue; + + return lineTexts.map((original, idx) => { + const item = data.data?.[idx]; + return item?.romanized ?? item?.text ?? original; + }); + } catch (err) { + clearTimeout(timeout); + if (err instanceof DOMException && err.name === "AbortError") { + trace.log(`Romanize: request timed out | ${url}`); + } else { + trace.log(`Romanize: request error | ${url} | ${err}`); + } + } + } + + return null; +} diff --git a/plugins/radiant-lyrics-luna/src/index.ts b/plugins/radiant-lyrics-luna/src/index.ts index 79d2e0a..0dd4765 100644 --- a/plugins/radiant-lyrics-luna/src/index.ts +++ b/plugins/radiant-lyrics-luna/src/index.ts @@ -9,6 +9,15 @@ import { safeTimeout, redux, } from "@luna/lib"; +import { + type ApiLine, + type LyricsApiResponse, + type WordLine, + type WordTiming, + fetchLyrics as fetchLyricsApi, + flushLyrics as flushLyricsApi, + romanizeLyrics as romanizeLyricsApi, +} from "./api"; import { Settings, settings } from "./Settings"; // Interpret integer backgroundScale (e.g., 10=1.0x, 20=2.0x) @@ -40,16 +49,6 @@ const toastErr = (msg: string) => // clean up resources export const unloads = new Set(); -let cachedPublicIP: string | undefined; -async function getPublicIPv4(): Promise { - if (cachedPublicIP) return cachedPublicIP; - try { - const res = await fetch("https://api.ipify.org?format=text"); - if (res.ok) cachedPublicIP = (await res.text()).trim(); - } catch {} - return cachedPublicIP; -} - // MARKER: Player Market UI (Ensure new UI is enabled) function enablePlayerMarketUI() { @@ -199,11 +198,8 @@ const applyFloatingPlayerBar = (): void => { // Alias for settings callback const updateRadiantLyricsPlayerBarTint = applyFloatingPlayerBar; -// Apply floating player bar + tint on load +// Apply floating player bar on load (Fixes race condition with flush button) applyFloatingPlayerBar(); -observe(unloads, '[data-test="footer-player"]', () => { - applyPlayerBarTintToElement(); -}); // MARKER: Quality-Based Seeker Color // Maps data-test-quality-badge-streaming-quality values to colors @@ -417,9 +413,10 @@ const applyIntegratedSeekBar = (): void => { }, 250); }; -// Apply on load +// Apply integrated seek bar on load (fixes race condition with flush button) applyIntegratedSeekBar(); observe(unloads, '[data-test="footer-player"]', () => { + applyPlayerBarTintToElement(); applyIntegratedSeekBar(); }); @@ -572,37 +569,26 @@ const flushLyrics = async (): Promise => { lockFlush(); - let params = `?title=${encodeURIComponent(trackInfo.title)}&artist=${encodeURIComponent(trackInfo.artist)}`; - if (trackInfo.isrc) params += `&isrc=${encodeURIComponent(trackInfo.isrc)}`; - params += "&flush=true&platform=" + encodeURIComponent("Radiant Lyrics"); - - const url = `https://api.atomix.one/rl-api${params}`; try { - const clientIP = await getPublicIPv4(); - const flushHeaders: Record = { - "P-Access-Token-Id": "58hy4s86", - "P-Access-Token": "xjehy2lfg5h5mjwotoxrcqugam", - }; - flushHeaders["x-client-ip"] = clientIP ?? "null"; - const res = await fetch(url, { headers: flushHeaders }); - if (res.status === 404) { - toast("No lyrics found for this track"); + const result = await flushLyricsApi(trackInfo); + if (!result.ok) { + if (result.notFound) { + toast("No lyrics found for this track"); + return; + } + toastErr(`Flush failed (${result.status})`); return; } - if (!res.ok) { - toastErr(`Flush failed (${res.status})`); - return; - } - const data = (await res.json()) as LyricsApiResponse & { _flush?: string }; - const flush = data?._flush ?? ""; + const data = result.data; + const note = data?._flush ?? ""; - const needsReload = flush.startsWith("Created") || flush.startsWith("Updated"); - if (flush) { - toast(flush); + const needsReload = note.startsWith("Created") || note.startsWith("Updated"); + if (note) { + toast(note); } else { toast("Lyrics flushed"); } - if (needsReload || !flush) { + if (needsReload || !note) { cachedLyricsKey = null; cachedLyricsData = null; onTrackChange(); @@ -612,7 +598,7 @@ const flushLyrics = async (): Promise => { } }; -const createFlushButton = function (): void { +const flushBtn = function (): void { const closeButton = document.querySelector( '[data-test="new-now-playing-close"]', ) as HTMLButtonElement; @@ -1605,75 +1591,6 @@ const onGlobalTrackChange = (listener: () => void): void => { // MARKER: Syllable Lyrics -interface WordTiming { - text: string; - time: number; // ms - duration: number; // ms - isBackground: boolean; - romanized?: string; -} - -interface WordLine { - text: string; - startTime: number; // s - duration: number; // s - endTime: number; // s - syllabus: WordTiming[]; - element: { - key: string; - songPart?: string; - songPartIndex?: number; - singer: string; - }; - translation: string | null; - romanized?: string; -} - -interface ApiLine { - text: string; - startTime: number; - duration: number; - endTime: number; - syllabus?: WordTiming[]; - element?: { - key: string; - songPart?: string; - songPartIndex?: number; - singer?: string; - }; - translation?: string | null; - romanized?: string; -} - -interface WordLyricsResponse { - type: "Word"; - data: WordLine[]; - metadata: { - source: string; - title: string; - language: string; - totalDuration: string; - agents?: Record; - songParts?: Array<{ name: string; time: number; duration: number }>; - }; - _cached?: boolean; -} - -interface LineLyricsResponse { - type: "Line"; - data: ApiLine[]; - metadata: { - source: string; - title: string; - language: string; - totalDuration: string; - agents?: Record; - songParts?: Array<{ name: string; time: number; duration: number }>; - }; - _cached?: boolean; -} - -type LyricsApiResponse = WordLyricsResponse | LineLyricsResponse; type LyricsOverlayMode = "none" | "word" | "line-api" | "line-tidal"; interface TrackInfo { @@ -2345,99 +2262,15 @@ const fetchLyrics = async ( sylLog(`[RL-Syllable] Cache hit for "${title}" by "${artist}"`); return cachedLyricsData; } - - let params = `?title=${encodeURIComponent(title)}&artist=${encodeURIComponent(artist)}`; - if (isrc) params += `&isrc=${encodeURIComponent(isrc)}`; - if (settings.romanizeLyrics) params += "&romanize=true"; - - const platformParam = "&platform=" + encodeURIComponent("Radiant Lyrics"); - const primaryUrls = [ - `https://api.atomix.one/rl-api${params}${platformParam}`, - `https://lyricsplus-api.atomix.one/lyrics${params}${platformParam}`, - ]; - const fallbackUrl = `https://rl-api.kineticsand.net/lyrics${params}`; - - // "ok" = got a response (data may still be null if type is unsupported) - // "404" = lyrics not found, stop all attempts immediately - // "500" = serverless timeout, skip remaining primaries and go to fallback - // "err" = network/other error, try next host - type FetchOutcome = - | { status: "ok"; data: LyricsApiResponse | null } - | { status: "404" } - | { status: "500" } - | { status: "err" }; - - const rlApiHeaders: Record = { - "P-Access-Token-Id": "58hy4s86", - "P-Access-Token": "xjehy2lfg5h5mjwotoxrcqugam", - }; - const clientIP = await getPublicIPv4(); - rlApiHeaders["x-client-ip"] = clientIP ?? "null"; - - const tryFetch = async (url: string): Promise => { - try { - sylTrace(`RL API: Fetching lyrics: ${url}`); - const res = await fetch(url, { - headers: url.includes("api.atomix.one") ? rlApiHeaders : undefined, - }); - if (!res.ok) { - trace.log(`RL API: fetch failed: ${res.status} from ${url}`); - if (res.status === 404) return { status: "404" }; - return res.status === 500 ? { status: "500" } : { status: "err" }; - } - const data = (await res.json()) as LyricsApiResponse; - if (!data?.data || !Array.isArray(data.data)) { - trace.log("Lyrics API returned invalid payload"); - return { status: "ok", data: null }; - } - if (data.type !== "Word" && data.type !== "Line") { - trace.log("Lyrics not available in supported format"); - return { status: "ok", data: null }; - } - return { status: "ok", data }; - } catch (err) { - trace.log(`RL API: fetch error from ${url}: ${err}`); - return { status: "err" }; - } - }; - - const finish = (data: LyricsApiResponse | null): LyricsApiResponse | null => { - cachedLyricsKey = cacheKey; - cachedLyricsData = data; - return data; - }; - - // Try primary hosts; bail to fallback immediately on 500, stop entirely on 404 - for (const url of primaryUrls) { - const outcome = await tryFetch(url); - if (outcome.status === "ok") return finish(outcome.data); - if (outcome.status === "404") { - trace.log("RL API: 404 — no API lyrics exist for this track"); - return finish(null); - } - if (outcome.status === "500") { - trace.log("RL API: 500 (Execution Timeout) — fallback"); - break; - } - // "err" → try next primary - } - - // Fallback: kineticsand (no serverless timeout) - const fallback = await tryFetch(fallbackUrl); - if (fallback.status === "ok") return finish(fallback.data); - if (fallback.status === "404") { - trace.log("RL API: 404 from fallback — no API lyrics exist for this track"); - return finish(null); - } - if (fallback.status === "500") { - trace.log("RL API: 500 from fallback — API IS ACTUALLY BORKED!"); - return finish(null); - } - - trace.log("RL API: All Endpoints Failed"); + const data = await fetchLyricsApi( + title, + artist, + isrc, + settings.romanizeLyrics, + ); cachedLyricsKey = cacheKey; - cachedLyricsData = null; - return null; + cachedLyricsData = data; + return data; }; const normalizeLineData = (data: ApiLine[]): WordLine[] => { @@ -2497,7 +2330,7 @@ const getTidalLines = (): string[] => { .filter((text) => text.trim().length > 0); }; -const romanizeLines = async (lineTexts: string[]): Promise => { +const romanizeLyrics = async (lineTexts: string[]): Promise => { if (!settings.romanizeLyrics || lineTexts.length === 0) return null; const cacheKey = `${lineTexts.join("\n")}\0r`; @@ -2505,70 +2338,12 @@ const romanizeLines = async (lineTexts: string[]): Promise => { return cachedTidalRomanizedLines; } - const payload = { - type: "Line" as const, - data: lineTexts.map((text, idx) => ({ - text, - startTime: idx, - duration: 0, - endTime: idx, - })), - }; - - const romanizePlatform = "?platform=" + encodeURIComponent("Radiant Lyrics"); - const urls = [ - `https://api.atomix.one/rl-api/romanize${romanizePlatform}`, - `https://lyricsplus-api.atomix.one/romanize${romanizePlatform}`, - "https://rl-api.kineticsand.net/romanize", - ]; - - for (const url of urls) { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5000); - try { - const romanizeHeaders: Record = { "content-type": "application/json" }; - if (url.includes("api.atomix.one")) { - romanizeHeaders["P-Access-Token-Id"] = "58hy4s86"; - romanizeHeaders["P-Access-Token"] = "xjehy2lfg5h5mjwotoxrcqugam"; - const ip = await getPublicIPv4(); - romanizeHeaders["x-client-ip"] = ip ?? "null"; - } - const res = await fetch(url, { - method: "POST", - headers: romanizeHeaders, - body: JSON.stringify(payload), - signal: controller.signal, - }); - clearTimeout(timeout); - if (!res.ok) { - trace.log(`Romanize: request failed ${res.status} from ${url}`); - continue; - } - - const data = (await res.json()) as { - type?: string; - data?: Array<{ text?: string; romanized?: string }>; - }; - if (!Array.isArray(data?.data)) continue; - - const romanized = lineTexts.map((original, idx) => { - const item = data.data?.[idx]; - return item?.romanized ?? item?.text ?? original; - }); - cachedTidalRomanizeKey = cacheKey; - cachedTidalRomanizedLines = romanized; - return romanized; - } catch (err) { - clearTimeout(timeout); - if (err instanceof DOMException && err.name === "AbortError") { - trace.log(`Romanize: request timed out from ${url}`); - } else { - trace.log(`Romanize: request error from ${url}: ${err}`); - } - } + const romanized = await romanizeLyricsApi(lineTexts); + if (romanized) { + cachedTidalRomanizeKey = cacheKey; + cachedTidalRomanizedLines = romanized; } - - return null; + return romanized; }; // strip tidal css classes (prevent conflict) @@ -4142,7 +3917,7 @@ const onTrackChange = async (): Promise => { disableFlushNoLyrics(); const tidalTexts = getTidalLines(); const romanized = settings.romanizeLyrics - ? await romanizeLines(tidalTexts) + ? await romanizeLyrics(tidalTexts) : null; if (token !== trackChangeToken) return; cachedTidalRomanizedLines = romanized; @@ -4303,7 +4078,7 @@ const reapplyTidalLines = async (): Promise => { lyricsMode = "line-tidal"; const tidalTexts = getTidalLines(); const romanized = settings.romanizeLyrics - ? await romanizeLines(tidalTexts) + ? await romanizeLyrics(tidalTexts) : null; hideTidalLyrics(); const result = buildTidalLines(romanized); @@ -4380,7 +4155,7 @@ const setupTrackChangeListener = (): void => { function setupHeaderObserver(): void { const injectButtons = () => { - if (!document.querySelector(".flush-lyrics-button")) createFlushButton(); + if (!document.querySelector(".flush-lyrics-button")) flushBtn(); if (!document.querySelector(".hide-ui-button")) createHideUIButton(); }; diff --git a/plugins/radiant-lyrics-luna/src/styles.css b/plugins/radiant-lyrics-luna/src/styles.css index 2741c5e..c5ca94e 100644 --- a/plugins/radiant-lyrics-luna/src/styles.css +++ b/plugins/radiant-lyrics-luna/src/styles.css @@ -322,101 +322,12 @@ body.rl-integrated-seekbar .rl-seekbar-bar:hover [data-test="progress-bar"] [dat height: 5px !important; } - -/* MARKER: PATCHES (Random Fixes for Tidals Changes) */ -/* These change a lot so I gave them their own section */ - -/* Remove max-width cap on now-playing content */ -[class*="_contentInner"] { - max-width: none !important; +/* Z-Index Fix for Integrated Seekbar (Volume slider) */ +body.rl-integrated-seekbar [data-test="footer-player"] [class*="playerContent"] > [class*="utilityContainer"] { + position: relative; + z-index: 110; } -/* Round now-playing artwork corners */ -[data-test="now-playing-artwork"] { - /* biome-ignore lint: Override flat corners */ - border-radius: 10px !important; -} - -/* Hide the Overlay Scrollbar (people just use mouse scroll) */ -.os-scrollbar { - display: none !important; - pointer-events: none !important; -} - -._artworkTilt_1c6d5cc { - border: none !important; -} - -/* Hide fullscreen button — breaks Radiant Lyrics */ -[data-test="new-now-playing-expand"] { - display: none !important; -} - - -/* Restore the Old Quality Tag style | thx Aya <3 */ - -._gradientMax_9111fba { - background-color: #ffd4321a !important; - box-shadow: none; - border-style: none; - border-radius: 0.75em; -} - -._max_894bc7c ._badgeText_1c9dd30 { - color: #ffd432 !important; - text-shadow: 0 0 10px #0000 !important; - font-weight: 600 !important; - font-size: 90% !important; -} - -._gradientHigh_87f2c3b { - background-color: #073430 !important; - box-shadow: none; - border-style: none; - border-radius: 0.75em; -} - -._high_4b5525b ._badgeText_1c9dd30 { - color: #33ffee !important; - text-shadow: none !important; - font-weight: 600 !important; - font-size: 90% !important; -} - -._gradientLow_3f9bc0d { - background-color: #ffffff1a !important; - box-shadow: none; - border-style: none; - border-radius: 0.75em; -} - -._badgeText_1c9dd30 { - color: #e4e4e7 !important; - text-shadow: none; - font-weight: 600 !important; - font-size: 90% !important; -} - -._badge_7b2911e { - border: none; - box-shadow: none; - background: none; - height: 33px; - padding: 0 !important; - width: 111px; - min-width: 111px; - border-radius: 0.75em; - transform: scale(1); -} - -._badge_7b2911e:hover { - transition: 100ms; - filter: saturate(1.25) brightness(1.1); -} - -._glowEffect_74c5e85 { - display: none !important; -} /* MARKER: Lyrics core CSS (always loaded) */ @@ -879,4 +790,100 @@ body.rl-integrated-seekbar .rl-seekbar-bar:hover [data-test="progress-bar"] [dat padding-left: calc(var(--rl-glow-outer, 20px) + 0px) !important; /* 4px cushion (not needed atm) */ padding-right: calc(var(--rl-glow-outer, 20px) + 0px) !important; /* 4px cushion (not needed atm) */ box-sizing: border-box !important; +} + + +/* MARKER: PATCHES (Random Fixes for Tidals Changes) */ +/* These change a lot so I gave them their own section */ + +/* Remove max-width cap on now-playing content */ +[class*="_contentInner"] { + max-width: none !important; +} + +/* Round now-playing artwork corners */ +[data-test="now-playing-artwork"] { + /* biome-ignore lint: Override flat corners */ + border-radius: 10px !important; +} + +/* Hide the Overlay Scrollbar (people just use mouse scroll) */ +.os-scrollbar { + display: none !important; + pointer-events: none !important; +} + +._artworkTilt_1c6d5cc { + border: none !important; +} + +/* Hide fullscreen button — breaks Radiant Lyrics */ +[data-test="new-now-playing-expand"] { + display: none !important; +} + + +/* Restore the Old Quality Tag style | thx Aya <3 */ + +._gradientMax_9111fba { + background-color: #ffd4321a !important; + box-shadow: none; + border-style: none; + border-radius: 0.75em; +} + +._max_894bc7c ._badgeText_1c9dd30 { + color: #ffd432 !important; + text-shadow: 0 0 10px #0000 !important; + font-weight: 600 !important; + font-size: 90% !important; +} + +._gradientHigh_87f2c3b { + background-color: #073430 !important; + box-shadow: none; + border-style: none; + border-radius: 0.75em; +} + +._high_4b5525b ._badgeText_1c9dd30 { + color: #33ffee !important; + text-shadow: none !important; + font-weight: 600 !important; + font-size: 90% !important; +} + +._gradientLow_3f9bc0d { + background-color: #ffffff1a !important; + box-shadow: none; + border-style: none; + border-radius: 0.75em; +} + +._badgeText_1c9dd30 { + color: #e4e4e7 !important; + text-shadow: none; + font-weight: 600 !important; + font-size: 90% !important; +} + +._badge_7b2911e { + border: none; + box-shadow: none; + background: none; + height: 33px; + padding: 0 !important; + width: 111px; + min-width: 111px; + border-radius: 0.75em; + transform: scale(1); +} + +._badge_7b2911e:hover { + transition: 100ms; + filter: saturate(1.25) brightness(1.1); +} + +._glowEffect_74c5e85 { + display: none !important; } \ No newline at end of file