mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
Inject Lyrics to Tracks without them in tidal <3
This commit is contained in:
@@ -1092,7 +1092,7 @@ const sylTrace = (...args: unknown[]) => {
|
|||||||
const container = document.querySelector(".rl-wbw-container");
|
const container = document.querySelector(".rl-wbw-container");
|
||||||
if (container) {
|
if (container) {
|
||||||
container.classList.remove("rl-syl-pop", "rl-syl-jump");
|
container.classList.remove("rl-syl-pop", "rl-syl-jump");
|
||||||
if (isWordTimingMode()) {
|
if (isWordMode()) {
|
||||||
if (clamped === 1) container.classList.add("rl-syl-pop");
|
if (clamped === 1) container.classList.add("rl-syl-pop");
|
||||||
else if (clamped === 2) container.classList.add("rl-syl-jump");
|
else if (clamped === 2) container.classList.add("rl-syl-jump");
|
||||||
}
|
}
|
||||||
@@ -1260,16 +1260,9 @@ const createStickyLyricsDropdown = (): void => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle switching tabs on track change
|
// Sticky Lyrics nav for injected lyrics tab
|
||||||
const handleStickyLyricsTrackChange = (): void => {
|
const tryActivateStickyLyricsTab = (): boolean => {
|
||||||
if (!settings.stickyLyrics) return;
|
if (!settings.stickyLyrics) return false;
|
||||||
|
|
||||||
// Process the track change and update tab state
|
|
||||||
// Tidal takes a while to process the track change sometimes :(
|
|
||||||
safeTimeout(
|
|
||||||
unloads,
|
|
||||||
() => {
|
|
||||||
if (!settings.stickyLyrics) return;
|
|
||||||
|
|
||||||
const lyricsTab = document.querySelector(
|
const lyricsTab = document.querySelector(
|
||||||
'[data-test="tabs-lyrics"]',
|
'[data-test="tabs-lyrics"]',
|
||||||
@@ -1278,15 +1271,15 @@ const handleStickyLyricsTrackChange = (): void => {
|
|||||||
'[data-test="tabs-play-queue"]',
|
'[data-test="tabs-play-queue"]',
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
|
|
||||||
if (!lyricsTab) {
|
if (!lyricsTab) return false;
|
||||||
if (playQueueTab) playQueueTab.click();
|
|
||||||
return;
|
if (lyricsTab.getAttribute("data-rl-injected") === "true") {
|
||||||
|
showInjectedLyricsTab();
|
||||||
|
} else {
|
||||||
|
lyricsTab.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
lyricsTab.click();
|
|
||||||
|
|
||||||
// Verify we actually stayed on lyrics after a short delay
|
// Verify we actually stayed on lyrics after a short delay
|
||||||
// TODO: Make not shitty (one day maybe)
|
|
||||||
safeTimeout(
|
safeTimeout(
|
||||||
unloads,
|
unloads,
|
||||||
() => {
|
() => {
|
||||||
@@ -1300,11 +1293,318 @@ const handleStickyLyricsTrackChange = (): void => {
|
|||||||
},
|
},
|
||||||
800,
|
800,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle switching tabs on track change
|
||||||
|
const handleStickyLyricsTrackChange = (): void => {
|
||||||
|
if (!settings.stickyLyrics) return;
|
||||||
|
|
||||||
|
// Process the track change and update tab state
|
||||||
|
// Tidal takes a while to process the track change sometimes :(
|
||||||
|
safeTimeout(
|
||||||
|
unloads,
|
||||||
|
() => {
|
||||||
|
if (!settings.stickyLyrics) return;
|
||||||
|
|
||||||
|
if (!tryActivateStickyLyricsTab()) {
|
||||||
|
const playQueueTab = document.querySelector(
|
||||||
|
'[data-test="tabs-play-queue"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
if (playQueueTab) playQueueTab.click();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
1200,
|
1200,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// MARKER: Injected API Lyrics (for non tidal lyric tracks)
|
||||||
|
|
||||||
|
let injectedTablistClickCleanup: (() => void) | null = null;
|
||||||
|
|
||||||
|
const getTabsRoot = (): HTMLElement | null => {
|
||||||
|
const roots = Array.from(
|
||||||
|
document.querySelectorAll('.react-tabs[data-rttabs="true"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
for (const root of roots) {
|
||||||
|
if (root.querySelector('[data-test="tabs-play-queue"]')) return root;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideInjectedLyricsTab = (): void => {
|
||||||
|
if (!injectedTabEl || !injectedPanelEl) return;
|
||||||
|
const root = getTabsRoot();
|
||||||
|
if (root) {
|
||||||
|
const nativePanels = Array.from(
|
||||||
|
root.querySelectorAll('div[role="tabpanel"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
for (const panel of nativePanels) {
|
||||||
|
if (panel === injectedPanelEl) continue;
|
||||||
|
panel.removeAttribute("aria-hidden");
|
||||||
|
panel.style.removeProperty("display");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
injectedTabEl.setAttribute("aria-selected", "false");
|
||||||
|
injectedTabEl.setAttribute("tabindex", "-1");
|
||||||
|
injectedTabEl.setAttribute("aria-expanded", "false");
|
||||||
|
injectedTabEl.classList.remove("react-tabs__tab--selected");
|
||||||
|
if (activeTabClass) {
|
||||||
|
injectedTabEl.classList.remove(activeTabClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
injectedPanelEl.classList.remove("react-tabs__tab-panel--selected");
|
||||||
|
if (activePanelClass) {
|
||||||
|
injectedPanelEl.classList.remove(activePanelClass);
|
||||||
|
}
|
||||||
|
injectedPanelEl.setAttribute("aria-hidden", "true");
|
||||||
|
injectedPanelEl.style.display = "none";
|
||||||
|
};
|
||||||
|
|
||||||
|
const showInjectedLyricsTab = (): void => {
|
||||||
|
if (!injectedTabEl || !injectedPanelEl) return;
|
||||||
|
const root = getTabsRoot();
|
||||||
|
if (!root) return;
|
||||||
|
|
||||||
|
const tabs = Array.from(
|
||||||
|
root.querySelectorAll('ul[role="tablist"] > li[role="tab"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
const panels = Array.from(
|
||||||
|
root.querySelectorAll('div[role="tabpanel"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
|
||||||
|
if (!activeTabClass) {
|
||||||
|
for (const tab of tabs) {
|
||||||
|
const cls = Array.from(tab.classList).find((c) =>
|
||||||
|
c.includes("_activeTab_"),
|
||||||
|
);
|
||||||
|
if (cls) {
|
||||||
|
activeTabClass = cls;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!activePanelClass) {
|
||||||
|
for (const panel of panels) {
|
||||||
|
const cls = Array.from(panel.classList).find((c) =>
|
||||||
|
c.includes("_isActive_"),
|
||||||
|
);
|
||||||
|
if (cls) {
|
||||||
|
activePanelClass = cls;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nativePanels = Array.from(
|
||||||
|
root.querySelectorAll('div[role="tabpanel"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
for (const tab of tabs) {
|
||||||
|
if (tab === injectedTabEl) continue;
|
||||||
|
if (activeTabClass) tab.classList.remove(activeTabClass);
|
||||||
|
}
|
||||||
|
for (const panel of nativePanels) {
|
||||||
|
if (panel === injectedPanelEl) continue;
|
||||||
|
panel.setAttribute("aria-hidden", "true");
|
||||||
|
panel.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
injectedTabEl.setAttribute("aria-selected", "true");
|
||||||
|
injectedTabEl.setAttribute("tabindex", "0");
|
||||||
|
injectedTabEl.setAttribute("aria-expanded", "true");
|
||||||
|
injectedTabEl.classList.add("react-tabs__tab--selected");
|
||||||
|
if (activeTabClass) injectedTabEl.classList.add(activeTabClass);
|
||||||
|
|
||||||
|
injectedPanelEl.classList.add("react-tabs__tab-panel--selected");
|
||||||
|
if (activePanelClass) {
|
||||||
|
injectedPanelEl.classList.add(activePanelClass);
|
||||||
|
}
|
||||||
|
injectedPanelEl.removeAttribute("aria-hidden");
|
||||||
|
injectedPanelEl.style.removeProperty("display");
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearInjectedLyricsTab = (): void => {
|
||||||
|
hideInjectedLyricsTab();
|
||||||
|
if (creditsTabEl) {
|
||||||
|
if (creditsPrevOrder) {
|
||||||
|
creditsTabEl.style.setProperty("order", creditsPrevOrder);
|
||||||
|
} else {
|
||||||
|
creditsTabEl.style.removeProperty("order");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (injectedTablistClickCleanup) {
|
||||||
|
injectedTablistClickCleanup();
|
||||||
|
injectedTablistClickCleanup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (injectedTabEl) injectedTabEl.remove();
|
||||||
|
if (injectedPanelEl) injectedPanelEl.remove();
|
||||||
|
|
||||||
|
injectedTabEl = null;
|
||||||
|
injectedPanelEl = null;
|
||||||
|
activeTabClass = "";
|
||||||
|
activePanelClass = "";
|
||||||
|
creditsTabEl = null;
|
||||||
|
creditsPrevOrder = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildInjectedLyricsShell = (panel: HTMLElement): void => {
|
||||||
|
if (panel.querySelector('[data-test="lyrics-lines"]')) return;
|
||||||
|
|
||||||
|
const trackLyrics = document.createElement("div");
|
||||||
|
trackLyrics.setAttribute("data-test", "track-lyrics");
|
||||||
|
|
||||||
|
const lyricsContainer = document.createElement("div");
|
||||||
|
lyricsContainer.className = "_lyricsContainer_fa37c08 _smoothScroll_05ef096";
|
||||||
|
lyricsContainer.setAttribute("data-test", "lyrics");
|
||||||
|
|
||||||
|
const lyricsLines = document.createElement("div");
|
||||||
|
lyricsLines.className =
|
||||||
|
"_lyricsText_bf0080e _lyrics_0537465 _hasCues_76b4841";
|
||||||
|
lyricsLines.setAttribute("data-test", "lyrics-lines");
|
||||||
|
|
||||||
|
const placeholder = document.createElement("span");
|
||||||
|
placeholder.textContent = "...";
|
||||||
|
const linesInner = document.createElement("div");
|
||||||
|
|
||||||
|
lyricsLines.appendChild(placeholder);
|
||||||
|
lyricsLines.appendChild(linesInner);
|
||||||
|
lyricsContainer.appendChild(lyricsLines);
|
||||||
|
trackLyrics.appendChild(lyricsContainer);
|
||||||
|
|
||||||
|
panel.replaceChildren(trackLyrics);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureLyricsTab = (): boolean => {
|
||||||
|
const existingLyricsTab = document.querySelector(
|
||||||
|
'[data-test="tabs-lyrics"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
if (existingLyricsTab && existingLyricsTab !== injectedTabEl) {
|
||||||
|
clearInjectedLyricsTab();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (injectedTabEl && injectedPanelEl) {
|
||||||
|
buildInjectedLyricsShell(injectedPanelEl);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = getTabsRoot();
|
||||||
|
if (!root) return false;
|
||||||
|
const tabList = root.querySelector('ul[role="tablist"]') as HTMLElement;
|
||||||
|
if (!tabList) return false;
|
||||||
|
|
||||||
|
const sampleTab = tabList.querySelector('li[role="tab"]') as HTMLElement;
|
||||||
|
const tabItemClass =
|
||||||
|
Array.from(sampleTab?.classList ?? []).find((c) =>
|
||||||
|
c.includes("_tabItem_"),
|
||||||
|
) ?? "";
|
||||||
|
|
||||||
|
const samplePanel = root.querySelector('div[role="tabpanel"]') as HTMLElement;
|
||||||
|
const panelBaseClass =
|
||||||
|
Array.from(samplePanel?.classList ?? []).find((c) =>
|
||||||
|
c.includes("_tabPanelStyles_"),
|
||||||
|
) ?? "";
|
||||||
|
|
||||||
|
const panelId = `panel:rl:${Date.now().toString(36)}`;
|
||||||
|
const tabId = `tab:rl:${Date.now().toString(36)}`;
|
||||||
|
|
||||||
|
const tabEl = document.createElement("li");
|
||||||
|
tabEl.setAttribute("data-test", "tabs-lyrics");
|
||||||
|
tabEl.setAttribute("data-rttab", "true");
|
||||||
|
tabEl.setAttribute("data-rl-injected", "true");
|
||||||
|
tabEl.setAttribute("role", "tab");
|
||||||
|
tabEl.setAttribute("id", tabId);
|
||||||
|
tabEl.setAttribute("aria-selected", "false");
|
||||||
|
tabEl.setAttribute("aria-disabled", "false");
|
||||||
|
tabEl.setAttribute("aria-controls", panelId);
|
||||||
|
tabEl.setAttribute("tabindex", "-1");
|
||||||
|
if (tabItemClass) tabEl.classList.add(tabItemClass);
|
||||||
|
|
||||||
|
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||||
|
icon.setAttribute("class", "_icon_77f3f89");
|
||||||
|
icon.setAttribute("viewBox", "0 0 20 20");
|
||||||
|
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
|
||||||
|
use.setAttribute("href", "#general__lyrics");
|
||||||
|
icon.appendChild(use);
|
||||||
|
|
||||||
|
const label = document.createElement("span");
|
||||||
|
label.className = "wave-text-description-demi";
|
||||||
|
label.setAttribute("data-wave-color", "textDefault");
|
||||||
|
label.textContent = "Lyrics";
|
||||||
|
|
||||||
|
tabEl.appendChild(icon);
|
||||||
|
tabEl.appendChild(label);
|
||||||
|
|
||||||
|
const panelEl = document.createElement("div");
|
||||||
|
panelEl.setAttribute("data-rl-injected", "true");
|
||||||
|
panelEl.setAttribute("role", "tabpanel");
|
||||||
|
panelEl.setAttribute("id", panelId);
|
||||||
|
panelEl.setAttribute("aria-labelledby", tabId);
|
||||||
|
if (panelBaseClass) panelEl.classList.add(panelBaseClass);
|
||||||
|
|
||||||
|
buildInjectedLyricsShell(panelEl);
|
||||||
|
|
||||||
|
const creditsTab = tabList.querySelector(
|
||||||
|
'[data-test="tabs-credits"]',
|
||||||
|
) as HTMLElement | null;
|
||||||
|
if (creditsTab) {
|
||||||
|
creditsTabEl = creditsTab;
|
||||||
|
creditsPrevOrder = creditsTab.style.getPropertyValue("order") || "";
|
||||||
|
creditsTab.style.setProperty("order", "1000");
|
||||||
|
tabEl.style.setProperty("order", "999");
|
||||||
|
}
|
||||||
|
|
||||||
|
tabList.appendChild(tabEl);
|
||||||
|
|
||||||
|
root.appendChild(panelEl);
|
||||||
|
|
||||||
|
tabEl.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
(
|
||||||
|
e as unknown as { stopImmediatePropagation?: () => void }
|
||||||
|
).stopImmediatePropagation?.();
|
||||||
|
showInjectedLyricsTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTabListClick = (e: Event): void => {
|
||||||
|
const target = e.target as Node;
|
||||||
|
const clickedTab = (target as HTMLElement)?.closest?.(
|
||||||
|
'li[role="tab"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
if (!clickedTab || clickedTab === tabEl) return;
|
||||||
|
|
||||||
|
const allTabs = Array.from(
|
||||||
|
tabList.querySelectorAll('li[role="tab"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
for (const tab of allTabs) {
|
||||||
|
if (tab === tabEl) continue;
|
||||||
|
if (activeTabClass) tab.classList.remove(activeTabClass);
|
||||||
|
tab.classList.remove("react-tabs__tab--selected");
|
||||||
|
}
|
||||||
|
if (activeTabClass) clickedTab.classList.add(activeTabClass);
|
||||||
|
clickedTab.classList.add("react-tabs__tab--selected");
|
||||||
|
clickedTab.setAttribute("aria-selected", "true");
|
||||||
|
clickedTab.setAttribute("tabindex", "0");
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
hideInjectedLyricsTab();
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
tabList.addEventListener("click", handleTabListClick);
|
||||||
|
injectedTablistClickCleanup = () => {
|
||||||
|
tabList.removeEventListener("click", handleTabListClick);
|
||||||
|
};
|
||||||
|
|
||||||
|
injectedTabEl = tabEl;
|
||||||
|
injectedPanelEl = panelEl;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// Observer: create dropdown when lyrics tab appears & detect track changes
|
// Observer: create dropdown when lyrics tab appears & detect track changes
|
||||||
function setupStickyLyricsObserver(): void {
|
function setupStickyLyricsObserver(): void {
|
||||||
// Create dropdown if lyrics tab already exists
|
// Create dropdown if lyrics tab already exists
|
||||||
@@ -1319,12 +1619,15 @@ function setupStickyLyricsObserver(): void {
|
|||||||
if (tab && !tab.querySelector(".sticky-lyrics-trigger")) {
|
if (tab && !tab.querySelector(".sticky-lyrics-trigger")) {
|
||||||
createStickyLyricsDropdown();
|
createStickyLyricsDropdown();
|
||||||
}
|
}
|
||||||
|
if (settings.stickyLyrics) {
|
||||||
|
tryActivateStickyLyricsTab();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply word lyrics when lyrics container appears or reappears
|
// Apply word lyrics when lyrics container appears or reappears
|
||||||
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
|
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
|
||||||
if (lyricsMode === "line-tidal") {
|
if (lyricsMode === "line-tidal") {
|
||||||
void reapplyTidalLineLyrics();
|
void reapplyTidalLines();
|
||||||
} else if (lyricsData) {
|
} else if (lyricsData) {
|
||||||
reapplyWordLyrics();
|
reapplyWordLyrics();
|
||||||
} else {
|
} else {
|
||||||
@@ -1428,10 +1731,15 @@ let tickLoopUnload: LunaUnload | null = null;
|
|||||||
let isActive = false;
|
let isActive = false;
|
||||||
let savedTidalClasses: string[] | null = null;
|
let savedTidalClasses: string[] | null = null;
|
||||||
let tidalFollowObserver: MutationObserver | null = null;
|
let tidalFollowObserver: MutationObserver | null = null;
|
||||||
|
let injectedTabEl: HTMLElement | null = null;
|
||||||
|
let injectedPanelEl: HTMLElement | null = null;
|
||||||
|
let activeTabClass = "";
|
||||||
|
let activePanelClass = "";
|
||||||
|
let creditsTabEl: HTMLElement | null = null;
|
||||||
|
let creditsPrevOrder = "";
|
||||||
|
|
||||||
const isWordTimingMode = (): boolean => lyricsMode === "word";
|
const isWordMode = (): boolean => lyricsMode === "word";
|
||||||
const getEffectiveLyricsStyle = (): number =>
|
const getLyricsStyle = (): number => (isWordMode() ? settings.lyricsStyle : 0);
|
||||||
isWordTimingMode() ? settings.lyricsStyle : 0;
|
|
||||||
|
|
||||||
interface WordEntry {
|
interface WordEntry {
|
||||||
el: HTMLSpanElement;
|
el: HTMLSpanElement;
|
||||||
@@ -1457,6 +1765,22 @@ const activeWordEls = new Map<number, HTMLSpanElement | null>();
|
|||||||
const activeBgWordEls = new Map<number, HTMLSpanElement | null>();
|
const activeBgWordEls = new Map<number, HTMLSpanElement | null>();
|
||||||
let activeLineIdxs = new Set<number>();
|
let activeLineIdxs = new Set<number>();
|
||||||
let primaryLineIdx = -1;
|
let primaryLineIdx = -1;
|
||||||
|
const lineSlideTimers = new Map<number, number>();
|
||||||
|
|
||||||
|
const clearLineSlideTimer = (idx: number): void => {
|
||||||
|
const timer = lineSlideTimers.get(idx);
|
||||||
|
if (timer !== undefined) {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
lineSlideTimers.delete(idx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearLineSlideTimers = (): void => {
|
||||||
|
for (const timer of lineSlideTimers.values()) {
|
||||||
|
window.clearTimeout(timer);
|
||||||
|
}
|
||||||
|
lineSlideTimers.clear();
|
||||||
|
};
|
||||||
|
|
||||||
// Scroll sync (unhook on user scroll)
|
// Scroll sync (unhook on user scroll)
|
||||||
let scrollSynced = true;
|
let scrollSynced = true;
|
||||||
@@ -1768,7 +2092,7 @@ const fetchLyrics = async (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeLineLyricsData = (data: ApiLine[]): WordLine[] => {
|
const normalizeLineData = (data: ApiLine[]): WordLine[] => {
|
||||||
return data
|
return data
|
||||||
.filter((line) => typeof line.text === "string")
|
.filter((line) => typeof line.text === "string")
|
||||||
.map((line, idx) => {
|
.map((line, idx) => {
|
||||||
@@ -1811,7 +2135,7 @@ const normalizeLineLyricsData = (data: ApiLine[]): WordLine[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Scrapes Tidal Line Texts (For Romanization)
|
// Scrapes Tidal Line Texts (For Romanization)
|
||||||
const getTidalLineTexts = (): string[] => {
|
const getTidalLines = (): string[] => {
|
||||||
const lyricsContainer = document.querySelector(
|
const lyricsContainer = document.querySelector(
|
||||||
'[data-test="lyrics-lines"]',
|
'[data-test="lyrics-lines"]',
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
@@ -1827,9 +2151,7 @@ const getTidalLineTexts = (): string[] => {
|
|||||||
.filter((text) => text.trim().length > 0);
|
.filter((text) => text.trim().length > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const romanizeLinePayload = async (
|
const romanizeLines = async (lineTexts: string[]): Promise<string[] | null> => {
|
||||||
lineTexts: string[],
|
|
||||||
): Promise<string[] | null> => {
|
|
||||||
if (!settings.romanizeLyrics || lineTexts.length === 0) return null;
|
if (!settings.romanizeLyrics || lineTexts.length === 0) return null;
|
||||||
|
|
||||||
const cacheKey = `${lineTexts.join("\n")}\0r`;
|
const cacheKey = `${lineTexts.join("\n")}\0r`;
|
||||||
@@ -2056,8 +2378,8 @@ const buildWordSpans = (): {
|
|||||||
wbwContainer.className = "rl-wbw-container";
|
wbwContainer.className = "rl-wbw-container";
|
||||||
if (settings.blurInactive) wbwContainer.classList.add("rl-blur-active");
|
if (settings.blurInactive) wbwContainer.classList.add("rl-blur-active");
|
||||||
if (settings.bubbledLyrics) wbwContainer.classList.add("rl-bubbled");
|
if (settings.bubbledLyrics) wbwContainer.classList.add("rl-bubbled");
|
||||||
const effectiveStyle = getEffectiveLyricsStyle();
|
const effectiveStyle = getLyricsStyle();
|
||||||
const allowWordSylStyles = isWordTimingMode();
|
const allowWordSylStyles = isWordMode();
|
||||||
// MARKER: Syllable animations (WIP coming soon)
|
// MARKER: Syllable animations (WIP coming soon)
|
||||||
if (allowWordSylStyles && settings.syllableStyle === 1)
|
if (allowWordSylStyles && settings.syllableStyle === 1)
|
||||||
wbwContainer.classList.add("rl-syl-pop");
|
wbwContainer.classList.add("rl-syl-pop");
|
||||||
@@ -2361,7 +2683,7 @@ const buildWordSpans = (): {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Scrapes & Builds Tidal Line Spans (no lines found in API)
|
// Scrapes & Builds Tidal Line Spans (no lines found in API)
|
||||||
const buildTidalLineSpans = (
|
const buildTidalLines = (
|
||||||
romanizedLines: string[] | null = null,
|
romanizedLines: string[] | null = null,
|
||||||
): { lines: LineEntry[] } => {
|
): { lines: LineEntry[] } => {
|
||||||
const lines: LineEntry[] = [];
|
const lines: LineEntry[] = [];
|
||||||
@@ -2608,7 +2930,7 @@ const watchForRerender = (): void => {
|
|||||||
sylTrace("Lyrics overlay: re-applying after Tidal re-render");
|
sylTrace("Lyrics overlay: re-applying after Tidal re-render");
|
||||||
hideTidalLyrics();
|
hideTidalLyrics();
|
||||||
if (lyricsMode === "line-tidal") {
|
if (lyricsMode === "line-tidal") {
|
||||||
const result = buildTidalLineSpans(cachedTidalRomanizedLines);
|
const result = buildTidalLines(cachedTidalRomanizedLines);
|
||||||
lines = result.lines;
|
lines = result.lines;
|
||||||
startTidalFollowLoop();
|
startTidalFollowLoop();
|
||||||
} else if (lyricsData) {
|
} else if (lyricsData) {
|
||||||
@@ -2648,6 +2970,7 @@ const teardown = (): void => {
|
|||||||
trackChangeToken++;
|
trackChangeToken++;
|
||||||
clearTickLoop();
|
clearTickLoop();
|
||||||
stopTidalFollowLoop();
|
stopTidalFollowLoop();
|
||||||
|
clearInjectedLyricsTab();
|
||||||
clearScrollAnim();
|
clearScrollAnim();
|
||||||
unwatchRerender();
|
unwatchRerender();
|
||||||
unhookUserScroll();
|
unhookUserScroll();
|
||||||
@@ -2663,6 +2986,8 @@ const teardown = (): void => {
|
|||||||
activeBgWordEls.clear();
|
activeBgWordEls.clear();
|
||||||
activeLineIdxs.clear();
|
activeLineIdxs.clear();
|
||||||
primaryLineIdx = -1;
|
primaryLineIdx = -1;
|
||||||
|
clearLineSlideTimers();
|
||||||
|
clearLineSlideTimers();
|
||||||
restoreTidalLyrics();
|
restoreTidalLyrics();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2847,7 +3172,7 @@ const startTickLoop = (): void => {
|
|||||||
if (!isActive || lines.length === 0) return;
|
if (!isActive || lines.length === 0) return;
|
||||||
|
|
||||||
const nowMs = getPlaybackMs();
|
const nowMs = getPlaybackMs();
|
||||||
const effectiveStyle = getEffectiveLyricsStyle();
|
const effectiveStyle = getLyricsStyle();
|
||||||
const isSyl = effectiveStyle === 2;
|
const isSyl = effectiveStyle === 2;
|
||||||
const isLineStyle = effectiveStyle === 0;
|
const isLineStyle = effectiveStyle === 0;
|
||||||
const CLS_ACTIVE = isSyl ? "rl-syl-active" : "rl-wbw-active";
|
const CLS_ACTIVE = isSyl ? "rl-syl-active" : "rl-wbw-active";
|
||||||
@@ -2896,6 +3221,7 @@ const startTickLoop = (): void => {
|
|||||||
// single pass to set correct state for all words (scrub or seek)
|
// single pass to set correct state for all words (scrub or seek)
|
||||||
if (didScrub) {
|
if (didScrub) {
|
||||||
for (let li = 0; li < lines.length; li++) {
|
for (let li = 0; li < lines.length; li++) {
|
||||||
|
lines[li].el.classList.remove("rl-line-slide");
|
||||||
const allEntries =
|
const allEntries =
|
||||||
lines[li].bgWords.length > 0
|
lines[li].bgWords.length > 0
|
||||||
? [...lines[li].words, ...lines[li].bgWords]
|
? [...lines[li].words, ...lines[li].bgWords]
|
||||||
@@ -2922,6 +3248,7 @@ const startTickLoop = (): void => {
|
|||||||
}
|
}
|
||||||
activeLineIdxs.clear();
|
activeLineIdxs.clear();
|
||||||
primaryLineIdx = -1;
|
primaryLineIdx = -1;
|
||||||
|
clearLineSlideTimers();
|
||||||
const held = document.querySelector(".rl-gap-hold");
|
const held = document.querySelector(".rl-gap-hold");
|
||||||
if (held) held.classList.remove("rl-gap-hold");
|
if (held) held.classList.remove("rl-gap-hold");
|
||||||
sylLog(
|
sylLog(
|
||||||
@@ -2933,6 +3260,8 @@ const startTickLoop = (): void => {
|
|||||||
for (const idx of activeLineIdxs) {
|
for (const idx of activeLineIdxs) {
|
||||||
if (!newActiveSet.has(idx) && idx < lines.length) {
|
if (!newActiveSet.has(idx) && idx < lines.length) {
|
||||||
lines[idx].el.classList.remove("rl-wbw-line-active");
|
lines[idx].el.classList.remove("rl-wbw-line-active");
|
||||||
|
lines[idx].el.classList.remove("rl-line-slide");
|
||||||
|
clearLineSlideTimer(idx);
|
||||||
lines[idx].el.removeAttribute("data-current");
|
lines[idx].el.removeAttribute("data-current");
|
||||||
const lastWord = activeWordEls.get(idx);
|
const lastWord = activeWordEls.get(idx);
|
||||||
if (lastWord) {
|
if (lastWord) {
|
||||||
@@ -2957,6 +3286,16 @@ const startTickLoop = (): void => {
|
|||||||
lines[idx].el.classList.add("rl-wbw-line-active");
|
lines[idx].el.classList.add("rl-wbw-line-active");
|
||||||
lines[idx].el.classList.remove("rl-pos-1", "rl-pos-2", "rl-pos-3");
|
lines[idx].el.classList.remove("rl-pos-1", "rl-pos-2", "rl-pos-3");
|
||||||
lines[idx].el.setAttribute("data-current", "true");
|
lines[idx].el.setAttribute("data-current", "true");
|
||||||
|
if (isLineStyle) {
|
||||||
|
lines[idx].el.classList.add("rl-line-slide");
|
||||||
|
clearLineSlideTimer(idx);
|
||||||
|
const t = window.setTimeout(() => {
|
||||||
|
if (idx < lines.length)
|
||||||
|
lines[idx].el.classList.remove("rl-line-slide");
|
||||||
|
lineSlideTimers.delete(idx);
|
||||||
|
}, 360);
|
||||||
|
lineSlideTimers.set(idx, t);
|
||||||
|
}
|
||||||
sylLog(
|
sylLog(
|
||||||
`[RL-Syllable] Line ${idx} Active "${lines[idx].el.textContent?.slice(0, 40)}" | ${lines[idx].startMs} ms - ${lines[idx].endMs} ms [${nowMs.toFixed(0)} ms]`,
|
`[RL-Syllable] Line ${idx} Active "${lines[idx].el.textContent?.slice(0, 40)}" | ${lines[idx].startMs} ms - ${lines[idx].endMs} ms [${nowMs.toFixed(0)} ms]`,
|
||||||
);
|
);
|
||||||
@@ -3192,9 +3531,9 @@ const onTrackChange = async (): Promise<void> => {
|
|||||||
if (token !== trackChangeToken) return;
|
if (token !== trackChangeToken) return;
|
||||||
if (!response) {
|
if (!response) {
|
||||||
trace.log("RL API: no API lyrics available, falling back to TIDAL lines");
|
trace.log("RL API: no API lyrics available, falling back to TIDAL lines");
|
||||||
const tidalTexts = getTidalLineTexts();
|
const tidalTexts = getTidalLines();
|
||||||
const romanized = settings.romanizeLyrics
|
const romanized = settings.romanizeLyrics
|
||||||
? await romanizeLinePayload(tidalTexts)
|
? await romanizeLines(tidalTexts)
|
||||||
: null;
|
: null;
|
||||||
if (token !== trackChangeToken) return;
|
if (token !== trackChangeToken) return;
|
||||||
cachedTidalRomanizedLines = romanized;
|
cachedTidalRomanizedLines = romanized;
|
||||||
@@ -3204,7 +3543,7 @@ const onTrackChange = async (): Promise<void> => {
|
|||||||
isActive = true;
|
isActive = true;
|
||||||
lyricsMode = "line-tidal";
|
lyricsMode = "line-tidal";
|
||||||
hideTidalLyrics();
|
hideTidalLyrics();
|
||||||
const tidalResult = buildTidalLineSpans(romanized);
|
const tidalResult = buildTidalLines(romanized);
|
||||||
lines = tidalResult.lines;
|
lines = tidalResult.lines;
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
trace.log("No TIDAL lines available yet");
|
trace.log("No TIDAL lines available yet");
|
||||||
@@ -3224,10 +3563,24 @@ const onTrackChange = async (): Promise<void> => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
lyricsMode = response.type === "Word" ? "word" : "line-api";
|
lyricsMode = response.type === "Word" ? "word" : "line-api";
|
||||||
|
if (!ensureLyricsTab()) {
|
||||||
|
trace.log("Could not create/find lyrics tab container");
|
||||||
|
teardown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (injectedTabEl && settings.stickyLyrics) {
|
||||||
|
showInjectedLyricsTab();
|
||||||
|
safeTimeout(
|
||||||
|
unloads,
|
||||||
|
() => {
|
||||||
|
if (!settings.stickyLyrics || token !== trackChangeToken) return;
|
||||||
|
showInjectedLyricsTab();
|
||||||
|
},
|
||||||
|
180,
|
||||||
|
);
|
||||||
|
}
|
||||||
lyricsData =
|
lyricsData =
|
||||||
response.type === "Word"
|
response.type === "Word" ? response.data : normalizeLineData(response.data);
|
||||||
? response.data
|
|
||||||
: normalizeLineLyricsData(response.data);
|
|
||||||
lyricsResponse = response;
|
lyricsResponse = response;
|
||||||
isActive = true;
|
isActive = true;
|
||||||
if (!lyricsData || lyricsData.length === 0) {
|
if (!lyricsData || lyricsData.length === 0) {
|
||||||
@@ -3264,6 +3617,7 @@ const reapplyWordLyrics = (): void => {
|
|||||||
activeBgWordEls.clear();
|
activeBgWordEls.clear();
|
||||||
activeLineIdxs.clear();
|
activeLineIdxs.clear();
|
||||||
primaryLineIdx = -1;
|
primaryLineIdx = -1;
|
||||||
|
clearLineSlideTimers();
|
||||||
|
|
||||||
isActive = true;
|
isActive = true;
|
||||||
lyricsMode = lyricsMode === "line-api" ? "line-api" : "word";
|
lyricsMode = lyricsMode === "line-api" ? "line-api" : "word";
|
||||||
@@ -3275,7 +3629,7 @@ const reapplyWordLyrics = (): void => {
|
|||||||
sylLog("[RL-Syllable] Reapplied word/syllable lyrics (cached)");
|
sylLog("[RL-Syllable] Reapplied word/syllable lyrics (cached)");
|
||||||
};
|
};
|
||||||
|
|
||||||
const reapplyTidalLineLyrics = async (): Promise<void> => {
|
const reapplyTidalLines = async (): Promise<void> => {
|
||||||
clearTickLoop();
|
clearTickLoop();
|
||||||
stopTidalFollowLoop();
|
stopTidalFollowLoop();
|
||||||
clearScrollAnim();
|
clearScrollAnim();
|
||||||
@@ -3290,12 +3644,12 @@ const reapplyTidalLineLyrics = async (): Promise<void> => {
|
|||||||
|
|
||||||
isActive = true;
|
isActive = true;
|
||||||
lyricsMode = "line-tidal";
|
lyricsMode = "line-tidal";
|
||||||
const tidalTexts = getTidalLineTexts();
|
const tidalTexts = getTidalLines();
|
||||||
const romanized = settings.romanizeLyrics
|
const romanized = settings.romanizeLyrics
|
||||||
? await romanizeLinePayload(tidalTexts)
|
? await romanizeLines(tidalTexts)
|
||||||
: null;
|
: null;
|
||||||
hideTidalLyrics();
|
hideTidalLyrics();
|
||||||
const result = buildTidalLineSpans(romanized);
|
const result = buildTidalLines(romanized);
|
||||||
lines = result.lines;
|
lines = result.lines;
|
||||||
if (lines.length === 0) return;
|
if (lines.length === 0) return;
|
||||||
watchForRerender();
|
watchForRerender();
|
||||||
|
|||||||
@@ -9,19 +9,22 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "AbyssFont";
|
font-family: "AbyssFont";
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2") format("woff2");
|
src: url("https://excel.lexploits.top/extra/tidal/LyricsMedium.woff2")
|
||||||
|
format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "AbyssFont";
|
font-family: "AbyssFont";
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2") format("woff2");
|
src: url("https://excel.lexploits.top/extra/tidal/LyricsSemibold.woff2")
|
||||||
|
format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "AbyssFont";
|
font-family: "AbyssFont";
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2") format("woff2");
|
src: url("https://excel.lexploits.top/extra/tidal/LyricsBold.woff2")
|
||||||
|
format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enhanced lyrics styling with glow effects */
|
/* Enhanced lyrics styling with glow effects */
|
||||||
@@ -223,7 +226,9 @@
|
|||||||
.rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word:hover,
|
.rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word:hover,
|
||||||
.rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word.rl-wbw-word-hover,
|
.rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word.rl-wbw-word-hover,
|
||||||
.rl-wbw-line:not(.rl-wbw-line-active) .rl-wbw-main .rl-wbw-word:hover,
|
.rl-wbw-line:not(.rl-wbw-line-active) .rl-wbw-main .rl-wbw-word:hover,
|
||||||
.rl-wbw-line:not(.rl-wbw-line-active) .rl-wbw-main .rl-wbw-word.rl-wbw-word-hover {
|
.rl-wbw-line:not(.rl-wbw-line-active)
|
||||||
|
.rl-wbw-main
|
||||||
|
.rl-wbw-word.rl-wbw-word-hover {
|
||||||
text-shadow:
|
text-shadow:
|
||||||
0 0 var(--rl-glow-inner, 2px) lightgray,
|
0 0 var(--rl-glow-inner, 2px) lightgray,
|
||||||
/* biome-ignore lint: Hover glow should override defaults */
|
/* biome-ignore lint: Hover glow should override defaults */
|
||||||
@@ -243,16 +248,33 @@
|
|||||||
color: var(--cl-glow1, #fff) !important;
|
color: var(--cl-glow1, #fff) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Line mode: suppress glow during slide-in only */
|
||||||
|
.rl-wbw-line.rl-line-slide .rl-wbw-word.rl-wbw-active {
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* MARKER: Syllable sweep animation CSS */
|
/* MARKER: Syllable sweep animation CSS */
|
||||||
|
|
||||||
@keyframes rl-wipe {
|
@keyframes rl-wipe {
|
||||||
from {
|
from {
|
||||||
background-size: 0.75em 100%, 0% 100%, 100% 100%;
|
background-size:
|
||||||
background-position: -0.375em 0%, left, left;
|
0.75em 100%,
|
||||||
|
0% 100%,
|
||||||
|
100% 100%;
|
||||||
|
background-position:
|
||||||
|
-0.375em 0%,
|
||||||
|
left,
|
||||||
|
left;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
background-size: 0.75em 100%, 100% 100%, 100% 100%;
|
background-size:
|
||||||
background-position: calc(100% + 0.375em) 0%, left, left;
|
0.75em 100%,
|
||||||
|
100% 100%,
|
||||||
|
100% 100%;
|
||||||
|
background-position:
|
||||||
|
calc(100% + 0.375em) 0%,
|
||||||
|
left,
|
||||||
|
left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,11 +290,22 @@
|
|||||||
background-clip: text !important;
|
background-clip: text !important;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(90deg, transparent 0%, var(--cl-glow1, #fff) 50%, transparent 100%),
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
var(--cl-glow1, #fff) 50%,
|
||||||
|
transparent 100%
|
||||||
|
),
|
||||||
linear-gradient(90deg, var(--cl-glow1, #fff) 100%, transparent 100%),
|
linear-gradient(90deg, var(--cl-glow1, #fff) 100%, transparent 100%),
|
||||||
linear-gradient(90deg, rgba(128, 128, 128, 0.4), rgba(128, 128, 128, 0.4));
|
linear-gradient(90deg, rgba(128, 128, 128, 0.4), rgba(128, 128, 128, 0.4));
|
||||||
background-size: 0.75em 100%, 0% 100%, 100% 100%;
|
background-size:
|
||||||
background-position: -0.375em 0%, left, left;
|
0.75em 100%,
|
||||||
|
0% 100%,
|
||||||
|
100% 100%;
|
||||||
|
background-position:
|
||||||
|
-0.375em 0%,
|
||||||
|
left,
|
||||||
|
left;
|
||||||
/* biome-ignore lint: No glow for syllable mode */
|
/* biome-ignore lint: No glow for syllable mode */
|
||||||
text-shadow: none !important;
|
text-shadow: none !important;
|
||||||
/* biome-ignore lint: No glow for syllable mode */
|
/* biome-ignore lint: No glow for syllable mode */
|
||||||
@@ -295,18 +328,26 @@
|
|||||||
/* syllableStyle: 0 = none, 1 = Pop!, 2 = Jump */
|
/* syllableStyle: 0 = none, 1 = Pop!, 2 = Jump */
|
||||||
|
|
||||||
@keyframes rl-pop {
|
@keyframes rl-pop {
|
||||||
0%, 100% {
|
0%,
|
||||||
|
100% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
25%, 35% {
|
25%,
|
||||||
|
35% {
|
||||||
transform: scale(1.03) translateY(-0.5%);
|
transform: scale(1.03) translateY(-0.5%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rl-jump {
|
@keyframes rl-jump {
|
||||||
0% { transform: translateY(8px); }
|
0% {
|
||||||
50% { transform: translateY(-3px); }
|
transform: translateY(8px);
|
||||||
100% { transform: translateY(0); }
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pop! for word mode */
|
/* Pop! for word mode */
|
||||||
@@ -347,14 +388,18 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
font-size: 0.55em;
|
font-size: 0.55em;
|
||||||
padding-top: 0.15em;
|
padding-top: 0.15em;
|
||||||
transition: max-height 0.3s ease, opacity 0.5s ease;
|
transition:
|
||||||
|
max-height 0.3s ease,
|
||||||
|
opacity 0.5s ease;
|
||||||
color: rgba(128, 128, 128, 0.4);
|
color: rgba(128, 128, 128, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rl-wbw-line.rl-wbw-line-active .rl-wbw-bg-container {
|
.rl-wbw-line.rl-wbw-line-active .rl-wbw-bg-container {
|
||||||
max-height: 3em;
|
max-height: 3em;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: max-height 0.5s ease, opacity 0.5s ease;
|
transition:
|
||||||
|
max-height 0.5s ease,
|
||||||
|
opacity 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Singer duet positioning */
|
/* Singer duet positioning */
|
||||||
@@ -397,10 +442,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* kill glow on hovered word */
|
/* kill glow on hovered word */
|
||||||
.lyrics-glow-disabled .rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word:hover,
|
.lyrics-glow-disabled .rl-wbw-line:not(.rl-wbw-line-active)
|
||||||
.lyrics-glow-disabled .rl-wbw-line:not(.rl-wbw-line-active) > .rl-wbw-word.rl-wbw-word-hover,
|
> .rl-wbw-word:hover,
|
||||||
.lyrics-glow-disabled .rl-wbw-line:not(.rl-wbw-line-active) .rl-wbw-main .rl-wbw-word:hover,
|
.lyrics-glow-disabled
|
||||||
.lyrics-glow-disabled .rl-wbw-line:not(.rl-wbw-line-active) .rl-wbw-main .rl-wbw-word.rl-wbw-word-hover {
|
.rl-wbw-line:not(.rl-wbw-line-active)
|
||||||
|
> .rl-wbw-word.rl-wbw-word-hover,
|
||||||
|
.lyrics-glow-disabled
|
||||||
|
.rl-wbw-line:not(.rl-wbw-line-active)
|
||||||
|
.rl-wbw-main
|
||||||
|
.rl-wbw-word:hover,
|
||||||
|
.lyrics-glow-disabled
|
||||||
|
.rl-wbw-line:not(.rl-wbw-line-active)
|
||||||
|
.rl-wbw-main
|
||||||
|
.rl-wbw-word.rl-wbw-word-hover {
|
||||||
/* biome-ignore lint: Kill glow on hovered word */
|
/* biome-ignore lint: Kill glow on hovered word */
|
||||||
text-shadow: none !important;
|
text-shadow: none !important;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user