Merge pull request #110 from meowarex/dev

API Overhaul & Fixed Volume Slider <3
This commit is contained in:
Meow Meow
2026-04-06 23:33:32 +10:00
committed by GitHub
3 changed files with 437 additions and 361 deletions
+294
View File
@@ -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<string | undefined> {
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<Record<string, string>> {
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<string, { type: string; name: string; alias: string }>;
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<string, { type: string; name: string; alias: string }>;
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<LyricsApiResponse | null> {
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<FetchOutcome> => {
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<string[] | null> {
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<string, string> = {
"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;
}
+43 -268
View File
@@ -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<LunaUnload>();
let cachedPublicIP: string | undefined;
async function getPublicIPv4(): Promise<string | undefined> {
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<HTMLElement>(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<HTMLElement>(unloads, '[data-test="footer-player"]', () => {
applyPlayerBarTintToElement();
applyIntegratedSeekBar();
});
@@ -572,37 +569,26 @@ const flushLyrics = async (): Promise<void> => {
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<string, string> = {
"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<void> => {
}
};
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<string, { type: string; name: string; alias: string }>;
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<string, { type: string; name: string; alias: string }>;
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<string, string> = {
"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<FetchOutcome> => {
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<string[] | null> => {
const romanizeLyrics = async (lineTexts: string[]): Promise<string[] | null> => {
if (!settings.romanizeLyrics || lineTexts.length === 0) return null;
const cacheKey = `${lineTexts.join("\n")}\0r`;
@@ -2505,70 +2338,12 @@ const romanizeLines = async (lineTexts: string[]): Promise<string[] | null> => {
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<string, string> = { "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<void> => {
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<void> => {
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();
};
+100 -93
View File
@@ -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;
}