From b673ca2ff6c59d5ddace3cadcf957f307817e138 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Tue, 26 May 2026 04:15:43 +0200 Subject: [PATCH] fix(lyrics): restore spacing between word-synced lyric tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported: when an Enhanced LRC track reached an active line, the words collapsed into a single run — "Meet me in the crowd, people, people" rendered as "Meetmeinthecrowd,people,people". Other lines on the same track stayed correctly spaced. Two ingredients combined to produce the bug: 1. Each word in the active line is rendered as its own `display: inline-block` `` so the karaoke highlight can scale/opacity-animate per word. Inline-block sibling boxes do not inherit JSX-level whitespace — `ab` renders as `ab`, not `a b`. The non-active lines escape the bug because they fall through to `line.text` (already space-joined by the parser). 2. Many Enhanced LRC sources omit spaces between word stamps (`<00:01>Meet<00:02>me<00:03>in…`). The parser's `body.slice(start, end)` then yields word texts with no leading or trailing whitespace, so even falling back on intra-text space wouldn't help. Fix: wrap each word `` in a `Fragment` together with a literal `" "` text node when the word isn't the last in the line. The text node restores the inter-box gap; if the source did carry whitespace inside `word.text`, `white-space: normal` collapses the pair to a single space. Applied identically in `FullscreenLyrics` (Apple-Music- style fullscreen) and `LyricsPanel` (side panel). --- src/components/layout/LyricsPanel.tsx | 53 ++++++++++++--------- src/components/player/FullscreenLyrics.tsx | 55 +++++++++++++--------- 2 files changed, 66 insertions(+), 42 deletions(-) diff --git a/src/components/layout/LyricsPanel.tsx b/src/components/layout/LyricsPanel.tsx index ef30961..f694a55 100644 --- a/src/components/layout/LyricsPanel.tsx +++ b/src/components/layout/LyricsPanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "react-i18next"; import { motion } from "framer-motion"; @@ -274,27 +274,38 @@ export function LyricsPanel() { {hasWords ? ( {line.words!.map((word, wi) => ( - + - {word.text} - + ? "text-pink-500 dark:text-pink-400" + : wi < activeWordIndex + ? "" + : "opacity-60" + } + style={{ + display: "inline-block", + transform: + wi === activeWordIndex + ? "scale(1.04)" + : "scale(1)", + transition: + "color 150ms ease, opacity 150ms ease, transform 150ms ease", + }} + > + {word.text} + + {wi < line.words!.length - 1 && " "} + ))} ) : ( diff --git a/src/components/player/FullscreenLyrics.tsx b/src/components/player/FullscreenLyrics.tsx index 37afe7d..7e6c73a 100644 --- a/src/components/player/FullscreenLyrics.tsx +++ b/src/components/player/FullscreenLyrics.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef } from "react"; +import { Fragment, useEffect, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { X, Music2, Maximize2 } from "lucide-react"; import { Artwork } from "../common/Artwork"; @@ -217,27 +217,40 @@ export function FullscreenLyrics({ : wi < activeWordIndex ? "past" : "future"; + // Render a literal space between adjacent + // word boxes. `display: inline-block` strips + // the JSX whitespace that would normally + // separate inline siblings, and many + // Enhanced LRC sources omit spaces between + // word stamps (`Meetmein…`), + // so without this the active line collapses + // to "Meetmeinthecrowd". If the source did + // include trailing space inside `word.text`, + // `white-space: normal` collapses the pair + // back to one. return ( - - {word.text} - + + + {word.text} + + {wi < line.words!.length - 1 && " "} + ); })}