diff --git a/frontend/app.js b/frontend/app.js index 103d17a..d7c69f3 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -2353,8 +2353,9 @@ function updateNowPlayingActions() { // Used by service-mode lyrics and the presenter view. Measured with a hidden // real .sl-line (same font rules) in two passes: the display font has an // optical-size axis, so glyphs get relatively WIDER as the size drops and a -// single measurement at 100px over-fits on small screens. Sets --fit per row. -function fitLyricLines(list, { maxShare = 0.17, maxPx = 160 } = {}) { +// single measurement at 100px over-fits on small screens. Sets --fit per row; +// with `uniform` every row gets the smallest fit, so all lines share one size. +function fitLyricLines(list, { maxShare = 0.17, maxPx = 160, uniform = false } = {}) { if (!list) return; const rows = [...list.querySelectorAll('.sl-line')]; if (!rows.length || !list.clientWidth) return; @@ -2367,6 +2368,7 @@ function fitLyricLines(list, { maxShare = 0.17, maxPx = 160 } = {}) { probe.style.cssText = 'position:absolute;left:0;top:0;visibility:hidden;white-space:nowrap;width:auto;padding:0;font-size:100px;transition:none'; list.appendChild(probe); const widthAt = (px) => { probe.style.fontSize = px + 'px'; return probe.getBoundingClientRect().width; }; + const fits = []; for (const el of rows) { probe.textContent = el.textContent; const w100 = widthAt(100); @@ -2377,8 +2379,13 @@ function fitLyricLines(list, { maxShare = 0.17, maxPx = 160 } = {}) { px = Math.floor(clamp(wEst ? est * (width / wEst) : est)); } el.style.setProperty('--fit', px + 'px'); + fits.push(px); } probe.remove(); + if (uniform) { + const px = Math.min(...fits); + rows.forEach((el) => el.style.setProperty('--fit', px + 'px')); + } } // ============================================================================ @@ -3086,7 +3093,15 @@ const Notes = (() => { const serviceAuto = () => data.settings.serviceLyricsAutoscroll !== false; const sungLines = () => lines().map((l, i) => ({ ...l, i })).filter((l) => l.kind === 'line'); - function applyFits() { fitLyricLines($('serviceLyricsList')); } + // Plain (untimed) lyrics have no sung line to spotlight, so every line gets + // the same size — the largest at which the longest line still fits one row. + const plainLyrics = () => { const l = sungLines(); return l.length > 0 && l.every((x) => x.t === null || x.t === undefined); }; + function applyFits() { + const list = $('serviceLyricsList'); + const plain = plainLyrics(); + list.classList.toggle('plain', plain); + fitLyricLines(list, { uniform: plain }); + } function renderServiceButtons() { const on = !!data.settings.serviceLyrics; diff --git a/frontend/styles.css b/frontend/styles.css index b1036e9..38ccdcf 100755 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -3813,6 +3813,8 @@ body.remote-open { overflow: hidden; } text-shadow: 0 0 32px var(--accent-glow); } .sl-line.untimed { cursor: default; } +/* Plain lyrics (no timings): one shared --fit, every line full size. */ +.sl-list.plain .sl-line { font-size: var(--fit, 40px); opacity: 0.92; color: var(--text); } .sl-empty { display: flex; flex-direction: column; gap: 10px; align-items: center; font-size: clamp(22px, 4vw, 40px); font-weight: 700; color: var(--text-2);