@uimaxbai/am-lyrics 1.5.3 → 1.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/AmLyrics.d.ts +31 -1
- package/dist/src/AmLyrics.d.ts.map +1 -1
- package/dist/src/am-lyrics.js +903 -343
- package/dist/src/am-lyrics.js.map +1 -1
- package/dist/src/react.js +903 -343
- package/dist/src/react.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/AmLyrics.ts +1194 -380
package/dist/src/react.js
CHANGED
|
@@ -322,7 +322,7 @@ class GoogleService {
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
const VERSION = '1.5.
|
|
325
|
+
const VERSION = '1.5.5';
|
|
326
326
|
const INSTRUMENTAL_THRESHOLD_MS = 7000; // Show dots for gaps >= 7s
|
|
327
327
|
const FETCH_TIMEOUT_MS = 8000; // Timeout for all lyrics fetch requests
|
|
328
328
|
const SEEK_THRESHOLD_MS = 500;
|
|
@@ -331,6 +331,15 @@ const GAP_PULSE_DURATION_MS = 4000;
|
|
|
331
331
|
const GAP_PULSE_CYCLE_MS = GAP_PULSE_DURATION_MS * 2;
|
|
332
332
|
const GAP_EXIT_LEAD_MS = 600;
|
|
333
333
|
const GAP_MIN_SCALE = 0.85;
|
|
334
|
+
const NEXT_WORD_PRE_WIPE_MAX_GAP_MS = 180;
|
|
335
|
+
const NEXT_WORD_PRE_WIPE_MIN_DURATION_MS = 80;
|
|
336
|
+
const NEXT_WORD_PRE_WIPE_MAX_DURATION_MS = 240;
|
|
337
|
+
const BASE_WIPE_GRADIENT_EM = 0.75;
|
|
338
|
+
const LONG_WORD_WIPE_EXTRA_EM = 0.45;
|
|
339
|
+
const LONG_WORD_WIPE_EXTRA_RATIO = 0.35;
|
|
340
|
+
const SHORT_WORD_DRAG_MIN_DURATION_MS = 760;
|
|
341
|
+
const SHORT_WORD_GLOW_MIN_DURATION_MS = 1320;
|
|
342
|
+
const WORD_PRE_WIPE_HANDOFF_LEAD_MS = 120;
|
|
334
343
|
/**
|
|
335
344
|
* Fetch with an automatic timeout via AbortSignal.
|
|
336
345
|
* Rejects if the request takes longer than `timeoutMs`.
|
|
@@ -392,6 +401,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
392
401
|
this.activeLineIds = new Set();
|
|
393
402
|
this.currentPrimaryActiveLine = null;
|
|
394
403
|
this.lastPrimaryActiveLine = null;
|
|
404
|
+
this.backgroundExpandedLine = null;
|
|
395
405
|
// Scroll animation state
|
|
396
406
|
this.scrollAnimationState = null;
|
|
397
407
|
this.currentScrollOffset = 0;
|
|
@@ -489,6 +499,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
489
499
|
this.preActiveLineElements = [];
|
|
490
500
|
this.positionedLineElements = [];
|
|
491
501
|
this.activeGapLineElements = [];
|
|
502
|
+
this.clearBackgroundExpandedLine();
|
|
492
503
|
// Stop all running animations and clear highlights immediately
|
|
493
504
|
if (this.lyricsContainer) {
|
|
494
505
|
const activeLines = this.lyricsContainer.querySelectorAll('.lyrics-line.active, .lyrics-line.pre-active, .lyrics-line.bg-expanded');
|
|
@@ -521,7 +532,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
521
532
|
switchBtn.disabled = this.isFetchingAlternatives;
|
|
522
533
|
}
|
|
523
534
|
if (svgEl) {
|
|
524
|
-
svgEl.
|
|
535
|
+
svgEl.classList.toggle('is-loading', this.isFetchingAlternatives);
|
|
525
536
|
}
|
|
526
537
|
if (labelEl) {
|
|
527
538
|
labelEl.textContent = this.isFetchingAlternatives
|
|
@@ -694,6 +705,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
694
705
|
this.preActiveLineElements = [];
|
|
695
706
|
this.positionedLineElements = [];
|
|
696
707
|
this.activeGapLineElements = [];
|
|
708
|
+
this.clearBackgroundExpandedLine();
|
|
697
709
|
if (this.lyricsContainer) {
|
|
698
710
|
this.isProgrammaticScroll = true;
|
|
699
711
|
this.lyricsContainer.scrollTop = 0;
|
|
@@ -761,14 +773,23 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
761
773
|
return 21;
|
|
762
774
|
return 30;
|
|
763
775
|
}
|
|
776
|
+
static getDisplaySourceLabel(sourceLabel) {
|
|
777
|
+
return sourceLabel.toLowerCase().includes('lyricsplus')
|
|
778
|
+
? 'QQ'
|
|
779
|
+
: sourceLabel;
|
|
780
|
+
}
|
|
781
|
+
static getSourceKey(sourceLabel) {
|
|
782
|
+
const lower = (sourceLabel || '').trim().toLowerCase();
|
|
783
|
+
if (!lower)
|
|
784
|
+
return '';
|
|
785
|
+
if (lower.includes('lyricsplus') || lower === 'qq')
|
|
786
|
+
return 'qq';
|
|
787
|
+
return lower.replace(/\s+/g, ' ');
|
|
788
|
+
}
|
|
764
789
|
static mergeAndSortSources(collectedSources) {
|
|
765
790
|
const uniqueSourcesMap = new Map();
|
|
766
791
|
for (const source of collectedSources) {
|
|
767
|
-
const normalizedSource = source.source
|
|
768
|
-
.toLowerCase()
|
|
769
|
-
.includes('lyricsplus')
|
|
770
|
-
? 'QQ'
|
|
771
|
-
: source.source;
|
|
792
|
+
const normalizedSource = AmLyrics.getDisplaySourceLabel(source.source);
|
|
772
793
|
if (!uniqueSourcesMap.has(normalizedSource)) {
|
|
773
794
|
uniqueSourcesMap.set(normalizedSource, {
|
|
774
795
|
...source,
|
|
@@ -779,9 +800,43 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
779
800
|
return Array.from(uniqueSourcesMap.values()).sort((a, b) => AmLyrics.getRankForCollected(a.source, a.lines) -
|
|
780
801
|
AmLyrics.getRankForCollected(b.source, b.lines));
|
|
781
802
|
}
|
|
803
|
+
findCurrentSourceIndex(sources = this.availableSources, sourceLabel = this.lyricsSource, lines = this.lyrics) {
|
|
804
|
+
const identityIndex = sources.findIndex(source => source.lines === lines);
|
|
805
|
+
if (identityIndex !== -1)
|
|
806
|
+
return identityIndex;
|
|
807
|
+
const sourceKey = AmLyrics.getSourceKey(sourceLabel);
|
|
808
|
+
if (!sourceKey)
|
|
809
|
+
return -1;
|
|
810
|
+
return sources.findIndex(source => AmLyrics.getSourceKey(source.source) === sourceKey);
|
|
811
|
+
}
|
|
812
|
+
static getNextSourceIndex(sources, currentIndex, currentSourceLabel, currentLines) {
|
|
813
|
+
if (sources.length <= 1)
|
|
814
|
+
return -1;
|
|
815
|
+
if (currentIndex !== -1) {
|
|
816
|
+
return (currentIndex + 1) % sources.length;
|
|
817
|
+
}
|
|
818
|
+
const currentKey = AmLyrics.getSourceKey(currentSourceLabel);
|
|
819
|
+
const fallbackIndex = sources.findIndex(source => source.lines !== currentLines &&
|
|
820
|
+
AmLyrics.getSourceKey(source.source) !== currentKey);
|
|
821
|
+
return fallbackIndex === -1 ? 0 : fallbackIndex;
|
|
822
|
+
}
|
|
823
|
+
async applySourceAtIndex(index) {
|
|
824
|
+
const sourceResult = this.availableSources[index];
|
|
825
|
+
if (!sourceResult)
|
|
826
|
+
return;
|
|
827
|
+
this.currentSourceIndex = index;
|
|
828
|
+
this.lyrics = sourceResult.lines;
|
|
829
|
+
this.lyricsSource = sourceResult.source;
|
|
830
|
+
if (sourceResult.songwriters) {
|
|
831
|
+
this.songwriters = sourceResult.songwriters;
|
|
832
|
+
}
|
|
833
|
+
await this.onLyricsLoaded();
|
|
834
|
+
}
|
|
782
835
|
async switchSource() {
|
|
783
836
|
if (this.isFetchingAlternatives)
|
|
784
837
|
return;
|
|
838
|
+
const currentSourceLabel = this.lyricsSource;
|
|
839
|
+
const currentLines = this.lyrics;
|
|
785
840
|
if (!this.hasFetchedAllProviders) {
|
|
786
841
|
this.isFetchingAlternatives = true;
|
|
787
842
|
this._updateFooter();
|
|
@@ -824,10 +879,9 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
824
879
|
...this.availableSources,
|
|
825
880
|
...newSources,
|
|
826
881
|
]);
|
|
827
|
-
// Re-sync current index since sorting
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
this.currentSourceIndex = 0;
|
|
882
|
+
// Re-sync current index since sorting or label normalization can
|
|
883
|
+
// shift the currently displayed source underneath the old index.
|
|
884
|
+
this.currentSourceIndex = this.findCurrentSourceIndex(this.availableSources, currentSourceLabel, currentLines);
|
|
831
885
|
}
|
|
832
886
|
}
|
|
833
887
|
}
|
|
@@ -838,15 +892,11 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
838
892
|
}
|
|
839
893
|
}
|
|
840
894
|
if (this.availableSources.length > 1) {
|
|
841
|
-
this.
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
this.lyricsSource = sourceResult.source;
|
|
846
|
-
if (sourceResult.songwriters) {
|
|
847
|
-
this.songwriters = sourceResult.songwriters;
|
|
895
|
+
const currentIndex = this.findCurrentSourceIndex(this.availableSources, currentSourceLabel, currentLines);
|
|
896
|
+
const nextIndex = AmLyrics.getNextSourceIndex(this.availableSources, currentIndex, currentSourceLabel, currentLines);
|
|
897
|
+
if (nextIndex !== -1) {
|
|
898
|
+
await this.applySourceAtIndex(nextIndex);
|
|
848
899
|
}
|
|
849
|
-
await this.onLyricsLoaded();
|
|
850
900
|
}
|
|
851
901
|
}
|
|
852
902
|
async resolveSongMetadata() {
|
|
@@ -1898,7 +1948,9 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
1898
1948
|
if (!newActiveLines.includes(lineIndex)) {
|
|
1899
1949
|
const lineElement = this._getLineElement(lineIndex);
|
|
1900
1950
|
if (lineElement) {
|
|
1901
|
-
if (isSeek ||
|
|
1951
|
+
if (isSeek ||
|
|
1952
|
+
this.isUserScrolling ||
|
|
1953
|
+
AmLyrics.isLineSyncedLine(this.lyrics?.[lineIndex])) {
|
|
1902
1954
|
AmLyrics.unfinishSyllables(lineElement);
|
|
1903
1955
|
}
|
|
1904
1956
|
else {
|
|
@@ -1914,12 +1966,13 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
1914
1966
|
}
|
|
1915
1967
|
}
|
|
1916
1968
|
}
|
|
1917
|
-
// Add 'active'
|
|
1969
|
+
// Add 'active' to newly active lines. Background expansion is driven
|
|
1970
|
+
// separately by the current scroll target.
|
|
1918
1971
|
for (const lineIndex of newActiveLines) {
|
|
1919
1972
|
if (!oldActiveLines.includes(lineIndex)) {
|
|
1920
1973
|
const lineElement = this._getLineElement(lineIndex);
|
|
1921
1974
|
if (lineElement) {
|
|
1922
|
-
lineElement.classList.add('active'
|
|
1975
|
+
lineElement.classList.add('active');
|
|
1923
1976
|
lineElement.classList.remove('pre-active');
|
|
1924
1977
|
const preIdx = this.preActiveLineElements.indexOf(lineElement);
|
|
1925
1978
|
if (preIdx !== -1)
|
|
@@ -2139,8 +2192,12 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2139
2192
|
for (const lineIndex of activeLines) {
|
|
2140
2193
|
const lineEl = this._getLineElement(lineIndex);
|
|
2141
2194
|
if (lineEl)
|
|
2142
|
-
lineEl.classList.add('active'
|
|
2195
|
+
lineEl.classList.add('active');
|
|
2143
2196
|
}
|
|
2197
|
+
const primaryActiveLine = this.getPrimaryActiveLineIndex(activeLines);
|
|
2198
|
+
this.setBackgroundExpandedLine(primaryActiveLine !== null
|
|
2199
|
+
? this._getLineElement(primaryActiveLine)
|
|
2200
|
+
: null);
|
|
2144
2201
|
// Trigger a faux time-change so that updateSyllablesForLine fires
|
|
2145
2202
|
// to setup inline syllable CSS wipe animations for whatever the current time is
|
|
2146
2203
|
this._onTimeChanged(0, this.currentTime);
|
|
@@ -2179,6 +2236,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2179
2236
|
this.preActiveLineElements = [];
|
|
2180
2237
|
this.positionedLineElements = [];
|
|
2181
2238
|
this.activeGapLineElements = [];
|
|
2239
|
+
this.clearBackgroundExpandedLine();
|
|
2182
2240
|
this.setUserScrolling(false);
|
|
2183
2241
|
// Cancel any running animations
|
|
2184
2242
|
if (this.animationFrameId) {
|
|
@@ -2233,6 +2291,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2233
2291
|
// Don't override it with a scroll back to the last lyric.
|
|
2234
2292
|
const footer = this.lyricsContainer.querySelector('.lyrics-footer');
|
|
2235
2293
|
if (footer?.classList.contains('active')) {
|
|
2294
|
+
this.setBackgroundExpandedLine(null);
|
|
2236
2295
|
return;
|
|
2237
2296
|
}
|
|
2238
2297
|
// 1. Compute scroll lookahead based on gap to next line (YouLyPlus style)
|
|
@@ -2273,17 +2332,21 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2273
2332
|
targetElement = this._getLineElement(targetLineIdx);
|
|
2274
2333
|
}
|
|
2275
2334
|
}
|
|
2276
|
-
if (!targetElement)
|
|
2335
|
+
if (!targetElement) {
|
|
2336
|
+
this.setBackgroundExpandedLine(null);
|
|
2277
2337
|
return;
|
|
2278
|
-
|
|
2279
|
-
|
|
2338
|
+
}
|
|
2339
|
+
const scrollDuration = scrollLookAheadMs;
|
|
2340
|
+
targetElement.style.setProperty('--scroll-duration', `${scrollDuration}ms`);
|
|
2341
|
+
this.setBackgroundExpandedLine(targetElement);
|
|
2342
|
+
// Unblur the upcoming target line early while the separate bg-expanded
|
|
2343
|
+
// class starts background vocal height/opacity in sync with scroll.
|
|
2280
2344
|
if (!targetElement.classList.contains('active')) {
|
|
2281
2345
|
targetElement.classList.add('pre-active');
|
|
2282
2346
|
if (!this.preActiveLineElements.includes(targetElement)) {
|
|
2283
2347
|
this.preActiveLineElements.push(targetElement);
|
|
2284
2348
|
}
|
|
2285
2349
|
}
|
|
2286
|
-
const scrollDuration = scrollLookAheadMs;
|
|
2287
2350
|
this.focusLine(targetElement, forceScroll, scrollDuration);
|
|
2288
2351
|
}
|
|
2289
2352
|
_getTextWidth(text, font) {
|
|
@@ -2356,6 +2419,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2356
2419
|
this.preActiveLineElements = [];
|
|
2357
2420
|
this.positionedLineElements = [];
|
|
2358
2421
|
this.activeGapLineElements = [];
|
|
2422
|
+
this.clearBackgroundExpandedLine();
|
|
2359
2423
|
this.visibilityObserver?.disconnect();
|
|
2360
2424
|
this.visibilityObserver = undefined;
|
|
2361
2425
|
}
|
|
@@ -2391,6 +2455,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2391
2455
|
const groupGrowable = new Array(wordGroups.length).fill(false);
|
|
2392
2456
|
const groupGlowing = new Array(wordGroups.length).fill(false);
|
|
2393
2457
|
const groupCharRise = new Array(wordGroups.length).fill(false);
|
|
2458
|
+
const groupCharDrag = new Array(wordGroups.length).fill(false);
|
|
2394
2459
|
const vwFullText = new Array(wordGroups.length).fill('');
|
|
2395
2460
|
const vwFullDuration = new Array(wordGroups.length).fill(0);
|
|
2396
2461
|
const vwCharOffset = new Array(wordGroups.length).fill(0);
|
|
@@ -2422,25 +2487,46 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2422
2487
|
lineIsRTL = true;
|
|
2423
2488
|
const hasHyphen = combinedText.includes('-');
|
|
2424
2489
|
const wordLen = combinedText.length;
|
|
2425
|
-
|
|
2490
|
+
const canAnimateByChar = !isCJK && !isRTL && !hasHyphen && wordLen > 0;
|
|
2491
|
+
const isLineSynced = line.isWordSynced === false || line.text.some(s => s.lineSynced);
|
|
2492
|
+
let isGrowableVW = canAnimateByChar && wordLen > 0 && wordLen <= 7;
|
|
2426
2493
|
if (isGrowableVW) {
|
|
2427
|
-
if (wordLen
|
|
2494
|
+
if (wordLen <= 1) {
|
|
2428
2495
|
isGrowableVW =
|
|
2429
2496
|
combinedDuration >= 1050 && combinedDuration >= wordLen * 525;
|
|
2430
2497
|
}
|
|
2498
|
+
else if (wordLen <= 3) {
|
|
2499
|
+
isGrowableVW =
|
|
2500
|
+
combinedDuration >=
|
|
2501
|
+
SHORT_WORD_GLOW_MIN_DURATION_MS + (wordLen - 2) * 140;
|
|
2502
|
+
}
|
|
2431
2503
|
else {
|
|
2432
2504
|
isGrowableVW =
|
|
2433
2505
|
combinedDuration >= 850 && combinedDuration >= wordLen * 190;
|
|
2434
2506
|
}
|
|
2435
2507
|
}
|
|
2436
|
-
const
|
|
2508
|
+
const hasCharRiseDuration = combinedDuration >= Math.max(700, wordLen * 85);
|
|
2509
|
+
const hasTinyWordDragDuration = wordLen >= 2 &&
|
|
2510
|
+
wordLen <= 3 &&
|
|
2511
|
+
combinedDuration >=
|
|
2512
|
+
Math.max(SHORT_WORD_DRAG_MIN_DURATION_MS, wordLen * 150);
|
|
2513
|
+
const hasLongShortWordDuration = wordLen >= 4 && combinedDuration >= Math.max(1300, wordLen * 260);
|
|
2514
|
+
const isCharRiseVW = canAnimateByChar &&
|
|
2515
|
+
!isLineSynced &&
|
|
2516
|
+
!isGrowableVW &&
|
|
2517
|
+
((wordLen >= 8 && hasCharRiseDuration) ||
|
|
2518
|
+
(wordLen < 8 && hasLongShortWordDuration));
|
|
2519
|
+
const isCharDragVW = canAnimateByChar &&
|
|
2520
|
+
!isLineSynced &&
|
|
2521
|
+
!isGrowableVW &&
|
|
2522
|
+
hasTinyWordDragDuration;
|
|
2437
2523
|
const isGlowingVW = isGrowableVW && !isLineSynced;
|
|
2438
|
-
const isCharRiseVW = !isGrowableVW && !isLineSynced && !isCJK && !isRTL && wordLen >= 8;
|
|
2439
2524
|
let charOff = 0;
|
|
2440
2525
|
for (let gi = vwStart; gi <= vwEnd; gi += 1) {
|
|
2441
2526
|
groupGrowable[gi] = isGrowableVW;
|
|
2442
2527
|
groupGlowing[gi] = isGlowingVW;
|
|
2443
2528
|
groupCharRise[gi] = isCharRiseVW;
|
|
2529
|
+
groupCharDrag[gi] = isCharDragVW;
|
|
2444
2530
|
vwFullText[gi] = combinedText;
|
|
2445
2531
|
vwFullDuration[gi] = combinedDuration;
|
|
2446
2532
|
vwCharOffset[gi] = charOff;
|
|
@@ -2456,6 +2542,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2456
2542
|
groupGrowable,
|
|
2457
2543
|
groupGlowing,
|
|
2458
2544
|
groupCharRise,
|
|
2545
|
+
groupCharDrag,
|
|
2459
2546
|
vwFullText,
|
|
2460
2547
|
vwFullDuration,
|
|
2461
2548
|
vwCharOffset,
|
|
@@ -2475,48 +2562,97 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2475
2562
|
return;
|
|
2476
2563
|
const computedStyle = getComputedStyle(referenceSyllable);
|
|
2477
2564
|
const { font } = computedStyle; // Full font string
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
if (!charTimedWords)
|
|
2565
|
+
const charTimedWords = Array.from(this.shadowRoot.querySelectorAll('.lyrics-word.growable, .lyrics-word.char-rise, .lyrics-word.char-drag'));
|
|
2566
|
+
if (charTimedWords.length === 0)
|
|
2481
2567
|
return;
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2568
|
+
const wordsByVirtualId = new Map();
|
|
2569
|
+
charTimedWords.forEach((wordSpan, index) => {
|
|
2570
|
+
const virtualWordId = wordSpan.dataset.virtualWordId || `word-${index}`;
|
|
2571
|
+
const words = wordsByVirtualId.get(virtualWordId);
|
|
2572
|
+
if (words) {
|
|
2573
|
+
words.push(wordSpan);
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
wordsByVirtualId.set(virtualWordId, [wordSpan]);
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2579
|
+
wordsByVirtualId.forEach(wordSpans => {
|
|
2485
2580
|
const syllables = [];
|
|
2486
|
-
|
|
2487
|
-
const
|
|
2488
|
-
|
|
2489
|
-
|
|
2581
|
+
wordSpans.forEach(wordSpan => {
|
|
2582
|
+
const syllableWraps = wordSpan.querySelectorAll('.lyrics-syllable-wrap');
|
|
2583
|
+
syllableWraps.forEach(wrap => {
|
|
2584
|
+
const syl = wrap.querySelector('.lyrics-syllable');
|
|
2585
|
+
if (syl)
|
|
2586
|
+
syllables.push(syl);
|
|
2587
|
+
});
|
|
2490
2588
|
});
|
|
2491
|
-
syllables.
|
|
2492
|
-
const
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
const
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2589
|
+
const charSpans = syllables.flatMap(syl => {
|
|
2590
|
+
const spans = Array.from(syl.querySelectorAll('.char'));
|
|
2591
|
+
const target = syl;
|
|
2592
|
+
target._cachedCharSpans = spans;
|
|
2593
|
+
return spans;
|
|
2594
|
+
});
|
|
2595
|
+
if (charSpans.length === 0)
|
|
2596
|
+
return;
|
|
2597
|
+
wordSpans.forEach(wordSpan => {
|
|
2598
|
+
const target = wordSpan;
|
|
2599
|
+
target._cachedVirtualWordElements = wordSpans;
|
|
2600
|
+
target._cachedVirtualWordCharSpans = charSpans;
|
|
2601
|
+
});
|
|
2602
|
+
const syllableEntries = syllables.map(syl => {
|
|
2603
|
+
const spans = syl._cachedCharSpans;
|
|
2604
|
+
const charWidths = spans.map(span => this._getTextWidth(span.textContent || '', font));
|
|
2605
|
+
const totalWidth = charWidths.reduce((a, b) => a + b, 0);
|
|
2606
|
+
return {
|
|
2607
|
+
syl,
|
|
2608
|
+
spans,
|
|
2609
|
+
charWidths,
|
|
2610
|
+
totalWidth,
|
|
2611
|
+
start: parseFloat(syl.getAttribute('data-start-time') || ''),
|
|
2612
|
+
end: parseFloat(syl.getAttribute('data-end-time') || ''),
|
|
2613
|
+
};
|
|
2614
|
+
});
|
|
2615
|
+
const totalWordWidth = syllableEntries.reduce((total, entry) => total + entry.totalWidth, 0);
|
|
2616
|
+
if (totalWordWidth <= 0)
|
|
2617
|
+
return;
|
|
2618
|
+
const virtualWordStart = Math.min(...syllableEntries
|
|
2619
|
+
.map(entry => entry.start)
|
|
2620
|
+
.filter(start => Number.isFinite(start)));
|
|
2621
|
+
const virtualWordEnd = Math.max(...syllableEntries
|
|
2622
|
+
.map(entry => entry.end)
|
|
2623
|
+
.filter(end => Number.isFinite(end)));
|
|
2624
|
+
const virtualWordDuration = virtualWordEnd - virtualWordStart;
|
|
2625
|
+
const hasTimedSyllables = Number.isFinite(virtualWordStart) &&
|
|
2626
|
+
Number.isFinite(virtualWordEnd) &&
|
|
2627
|
+
virtualWordDuration > 0;
|
|
2628
|
+
let cumulativeCharWidth = 0;
|
|
2629
|
+
syllableEntries.forEach(entry => {
|
|
2630
|
+
let cumulativeSyllableWidth = 0;
|
|
2631
|
+
const syllableDuration = entry.end - entry.start;
|
|
2632
|
+
const useSyllableTiming = hasTimedSyllables &&
|
|
2633
|
+
Number.isFinite(entry.start) &&
|
|
2634
|
+
Number.isFinite(entry.end) &&
|
|
2635
|
+
syllableDuration > 0 &&
|
|
2636
|
+
entry.totalWidth > 0;
|
|
2637
|
+
entry.spans.forEach((span, index) => {
|
|
2638
|
+
const charWidth = entry.charWidths[index];
|
|
2639
|
+
let startPercent = cumulativeCharWidth / totalWordWidth;
|
|
2640
|
+
let durationPercent = charWidth / totalWordWidth;
|
|
2641
|
+
if (useSyllableTiming) {
|
|
2642
|
+
const charStartMs = entry.start -
|
|
2643
|
+
virtualWordStart +
|
|
2644
|
+
(cumulativeSyllableWidth / entry.totalWidth) * syllableDuration;
|
|
2645
|
+
const charDurationMs = (charWidth / entry.totalWidth) * syllableDuration;
|
|
2646
|
+
startPercent = AmLyrics.clamp(charStartMs / virtualWordDuration, 0, 1);
|
|
2647
|
+
durationPercent = AmLyrics.clamp(charDurationMs / virtualWordDuration, 0, 1);
|
|
2518
2648
|
}
|
|
2649
|
+
const target = span;
|
|
2650
|
+
target.dataset.wipeStart = startPercent.toFixed(4);
|
|
2651
|
+
target.dataset.wipeDuration = durationPercent.toFixed(4);
|
|
2652
|
+
target.style.setProperty('--word-wipe-width', `${totalWordWidth}px`);
|
|
2653
|
+
target.style.setProperty('--char-wipe-position', `${-cumulativeCharWidth}px`);
|
|
2519
2654
|
cumulativeCharWidth += charWidth;
|
|
2655
|
+
cumulativeSyllableWidth += charWidth;
|
|
2520
2656
|
});
|
|
2521
2657
|
});
|
|
2522
2658
|
});
|
|
@@ -2524,6 +2660,33 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2524
2660
|
static arraysEqual(a, b) {
|
|
2525
2661
|
return a.length === b.length && a.every((val, i) => val === b[i]);
|
|
2526
2662
|
}
|
|
2663
|
+
static isLineSyncedLine(line) {
|
|
2664
|
+
if (!line)
|
|
2665
|
+
return false;
|
|
2666
|
+
return line.isWordSynced === false || line.text.some(s => s.lineSynced);
|
|
2667
|
+
}
|
|
2668
|
+
getLineHighlightEndTime(index) {
|
|
2669
|
+
if (!this.lyrics)
|
|
2670
|
+
return 0;
|
|
2671
|
+
const line = this.lyrics[index];
|
|
2672
|
+
if (!line)
|
|
2673
|
+
return 0;
|
|
2674
|
+
const rawEnd = Math.max(line.endtime, line.timestamp);
|
|
2675
|
+
const nextLine = this.lyrics[index + 1];
|
|
2676
|
+
if (!nextLine || nextLine.timestamp <= line.timestamp) {
|
|
2677
|
+
return rawEnd > line.timestamp ? rawEnd + 200 : rawEnd;
|
|
2678
|
+
}
|
|
2679
|
+
if (rawEnd > line.timestamp) {
|
|
2680
|
+
if (nextLine.timestamp < rawEnd) {
|
|
2681
|
+
return rawEnd;
|
|
2682
|
+
}
|
|
2683
|
+
const gapToNext = nextLine.timestamp - rawEnd;
|
|
2684
|
+
if (gapToNext >= INSTRUMENTAL_THRESHOLD_MS) {
|
|
2685
|
+
return rawEnd;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
return nextLine.timestamp;
|
|
2689
|
+
}
|
|
2527
2690
|
static getLineIndexFromElement(lineElement) {
|
|
2528
2691
|
if (!lineElement)
|
|
2529
2692
|
return null;
|
|
@@ -2554,6 +2717,26 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2554
2717
|
}
|
|
2555
2718
|
this.preActiveLineElements = keptLines;
|
|
2556
2719
|
}
|
|
2720
|
+
setBackgroundExpandedLine(lineElement) {
|
|
2721
|
+
const target = lineElement &&
|
|
2722
|
+
!lineElement.classList.contains('lyrics-gap') &&
|
|
2723
|
+
lineElement.querySelector('.background-vocal-container')
|
|
2724
|
+
? lineElement
|
|
2725
|
+
: null;
|
|
2726
|
+
if (this.backgroundExpandedLine === target) {
|
|
2727
|
+
if (target && !target.classList.contains('bg-expanded')) {
|
|
2728
|
+
target.classList.add('bg-expanded');
|
|
2729
|
+
}
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
this.backgroundExpandedLine?.classList.remove('bg-expanded');
|
|
2733
|
+
this.backgroundExpandedLine = target;
|
|
2734
|
+
target?.classList.add('bg-expanded');
|
|
2735
|
+
}
|
|
2736
|
+
clearBackgroundExpandedLine() {
|
|
2737
|
+
this.backgroundExpandedLine?.classList.remove('bg-expanded');
|
|
2738
|
+
this.backgroundExpandedLine = null;
|
|
2739
|
+
}
|
|
2557
2740
|
getPrimaryActiveLineIndex(activeIndices) {
|
|
2558
2741
|
if (activeIndices.length === 0)
|
|
2559
2742
|
return null;
|
|
@@ -2743,9 +2926,10 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2743
2926
|
const activeLines = [];
|
|
2744
2927
|
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
2745
2928
|
const line = this.lyrics[i];
|
|
2929
|
+
const highlightEndTime = this.getLineHighlightEndTime(i);
|
|
2746
2930
|
if (line.timestamp > time)
|
|
2747
2931
|
break;
|
|
2748
|
-
if (time >= line.timestamp && time <
|
|
2932
|
+
if (time >= line.timestamp && time < highlightEndTime) {
|
|
2749
2933
|
activeLines.push(i);
|
|
2750
2934
|
}
|
|
2751
2935
|
}
|
|
@@ -2970,6 +3154,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2970
3154
|
this.lastPrimaryActiveLine = null;
|
|
2971
3155
|
this.activeLineIds.clear();
|
|
2972
3156
|
this.animatingLines = [];
|
|
3157
|
+
this.setBackgroundExpandedLine(null);
|
|
2973
3158
|
// Find the clicked line element and scroll to it with forceScroll (like YouLyPlus)
|
|
2974
3159
|
// Timestamps are already in milliseconds — match the data-start-time attribute directly
|
|
2975
3160
|
const clickedLineElement = this.lyricsContainer?.querySelector(`.lyrics-line[data-start-time="${line.text[0]?.timestamp || 0}"]`);
|
|
@@ -2986,6 +3171,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
2986
3171
|
this.isClickSeeking = false;
|
|
2987
3172
|
}, 800);
|
|
2988
3173
|
this.scrollToActiveLineYouLy(clickedLineElement, true);
|
|
3174
|
+
this.setBackgroundExpandedLine(clickedLineElement);
|
|
2989
3175
|
}
|
|
2990
3176
|
const event = new CustomEvent('line-click', {
|
|
2991
3177
|
detail: {
|
|
@@ -3331,21 +3517,259 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3331
3517
|
}
|
|
3332
3518
|
/**
|
|
3333
3519
|
* Update syllable highlight animation - apply CSS wipe animation
|
|
3334
|
-
* (Exact copy from YouLyPlus _updateSyllableAnimation)
|
|
3335
3520
|
*/
|
|
3521
|
+
static clamp(value, min, max) {
|
|
3522
|
+
return Math.min(max, Math.max(min, value));
|
|
3523
|
+
}
|
|
3524
|
+
static getVisibleCharacterCount(element) {
|
|
3525
|
+
const attrLength = parseFloat(element.getAttribute('data-word-length') || '');
|
|
3526
|
+
if (Number.isFinite(attrLength) && attrLength > 0)
|
|
3527
|
+
return attrLength;
|
|
3528
|
+
return (element.textContent || '').replace(/\s/g, '').length;
|
|
3529
|
+
}
|
|
3530
|
+
static getLongWordWipeScale(charCount) {
|
|
3531
|
+
if (charCount <= 6)
|
|
3532
|
+
return 1;
|
|
3533
|
+
return (1 +
|
|
3534
|
+
AmLyrics.clamp((charCount - 6) / 10, 0, 1) * LONG_WORD_WIPE_EXTRA_RATIO);
|
|
3535
|
+
}
|
|
3536
|
+
static applyWipeShape(element, charCount) {
|
|
3537
|
+
const extra = AmLyrics.clamp((charCount - 6) / 10, 0, 1) * LONG_WORD_WIPE_EXTRA_EM;
|
|
3538
|
+
const width = BASE_WIPE_GRADIENT_EM + extra;
|
|
3539
|
+
element.style.setProperty('--wipe-gradient-width', `${width.toFixed(3)}em`);
|
|
3540
|
+
element.style.setProperty('--wipe-gradient-half', `${(width / 2).toFixed(3)}em`);
|
|
3541
|
+
}
|
|
3542
|
+
static ensureWordWipeGeometry(charSpans, charCount) {
|
|
3543
|
+
if (charSpans.length === 0)
|
|
3544
|
+
return;
|
|
3545
|
+
const approxWidthCh = Math.max(1, charCount || charSpans.length);
|
|
3546
|
+
charSpans.forEach((span, index) => {
|
|
3547
|
+
if (!span.style.getPropertyValue('--word-wipe-width')) {
|
|
3548
|
+
span.style.setProperty('--word-wipe-width', `${approxWidthCh}ch`);
|
|
3549
|
+
}
|
|
3550
|
+
if (!span.style.getPropertyValue('--char-wipe-position')) {
|
|
3551
|
+
const startPct = Number.parseFloat(span.dataset.wipeStart || `${index / Math.max(1, charSpans.length)}`);
|
|
3552
|
+
span.style.setProperty('--char-wipe-position', `${-(AmLyrics.clamp(startPct, 0, 1) * approxWidthCh)}ch`);
|
|
3553
|
+
}
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
static clearPreHighlight(syllable) {
|
|
3557
|
+
const target = syllable;
|
|
3558
|
+
target.classList.remove('pre-highlight');
|
|
3559
|
+
target.style.removeProperty('--pre-wipe-duration');
|
|
3560
|
+
target.style.removeProperty('--pre-wipe-delay');
|
|
3561
|
+
target.style.animation = '';
|
|
3562
|
+
target
|
|
3563
|
+
.querySelectorAll('.pre-wipe-lead')
|
|
3564
|
+
.forEach(element => AmLyrics.clearPreWipeLead(element));
|
|
3565
|
+
}
|
|
3566
|
+
static clearPreWipeLead(element) {
|
|
3567
|
+
element.classList.remove('pre-wipe-lead');
|
|
3568
|
+
element.style.removeProperty('--pre-wipe-duration');
|
|
3569
|
+
element.style.removeProperty('--pre-wipe-delay');
|
|
3570
|
+
}
|
|
3571
|
+
static hasTextBoundaryAfter(syllable) {
|
|
3572
|
+
return /\s$/.test(syllable.textContent || '');
|
|
3573
|
+
}
|
|
3574
|
+
static getSyllableWordIndex(syllable) {
|
|
3575
|
+
const wordElement = AmLyrics.getWordElementForSyllable(syllable);
|
|
3576
|
+
const virtualWordId = wordElement?.dataset.virtualWordId;
|
|
3577
|
+
if (virtualWordId) {
|
|
3578
|
+
return `virtual:${virtualWordId}`;
|
|
3579
|
+
}
|
|
3580
|
+
const virtualWordStart = wordElement?.dataset.virtualWordStart;
|
|
3581
|
+
const virtualWordEnd = wordElement?.dataset.virtualWordEnd;
|
|
3582
|
+
if (virtualWordStart || virtualWordEnd) {
|
|
3583
|
+
return `virtual:${virtualWordStart || ''}:${virtualWordEnd || ''}`;
|
|
3584
|
+
}
|
|
3585
|
+
return (syllable.getAttribute('data-word-index') ||
|
|
3586
|
+
syllable.getAttribute('data-syllable-index') ||
|
|
3587
|
+
'');
|
|
3588
|
+
}
|
|
3589
|
+
static getNextWordSyllable(syllables, index) {
|
|
3590
|
+
const current = syllables[index];
|
|
3591
|
+
const currentWordIndex = AmLyrics.getSyllableWordIndex(current);
|
|
3592
|
+
const previousSyllable = current;
|
|
3593
|
+
for (let i = index + 1; i < syllables.length; i += 1) {
|
|
3594
|
+
const candidate = syllables[i];
|
|
3595
|
+
if (candidate.classList.contains('transliteration')) {
|
|
3596
|
+
// eslint-disable-next-line no-continue
|
|
3597
|
+
continue;
|
|
3598
|
+
}
|
|
3599
|
+
const candidateWordIndex = AmLyrics.getSyllableWordIndex(candidate);
|
|
3600
|
+
if (candidateWordIndex === currentWordIndex ||
|
|
3601
|
+
!AmLyrics.hasTextBoundaryAfter(previousSyllable)) {
|
|
3602
|
+
return null;
|
|
3603
|
+
}
|
|
3604
|
+
return candidate;
|
|
3605
|
+
}
|
|
3606
|
+
return null;
|
|
3607
|
+
}
|
|
3608
|
+
static getPreviousNonTransliterationSyllable(syllables, index) {
|
|
3609
|
+
for (let i = index - 1; i >= 0; i -= 1) {
|
|
3610
|
+
const candidate = syllables[i];
|
|
3611
|
+
if (!candidate.classList.contains('transliteration')) {
|
|
3612
|
+
return candidate;
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
return null;
|
|
3616
|
+
}
|
|
3617
|
+
static getRenderedWordSyllables(syllable) {
|
|
3618
|
+
const wordElement = AmLyrics.getWordElementForSyllable(syllable);
|
|
3619
|
+
const wordElements = AmLyrics.getCachedVirtualWordElements(wordElement);
|
|
3620
|
+
const wordSyllables = wordElements.flatMap(element => Array.from(element.querySelectorAll('.lyrics-syllable')));
|
|
3621
|
+
return wordSyllables.filter(wordSyllable => !wordSyllable.classList.contains('transliteration'));
|
|
3622
|
+
}
|
|
3623
|
+
static getWordElementForSyllable(syllable) {
|
|
3624
|
+
return syllable.parentElement?.parentElement;
|
|
3625
|
+
}
|
|
3626
|
+
static getWordPreWipeKey(syllable) {
|
|
3627
|
+
const wordElement = AmLyrics.getWordElementForSyllable(syllable);
|
|
3628
|
+
return (wordElement?.dataset.virtualWordId ||
|
|
3629
|
+
`${syllable.getAttribute('data-start-time') || ''}:${AmLyrics.getSyllableWordIndex(syllable)}`);
|
|
3630
|
+
}
|
|
3631
|
+
static isPreWipeArmed(syllable) {
|
|
3632
|
+
const wordElement = AmLyrics.getWordElementForSyllable(syllable);
|
|
3633
|
+
const target = wordElement;
|
|
3634
|
+
return Boolean(target?._wordPreWipeKey === AmLyrics.getWordPreWipeKey(syllable));
|
|
3635
|
+
}
|
|
3636
|
+
static applyWordPreWipe(nextSyllable, wordSyllables, currentTimeMs, preWipeStartMs, preWipeDurationMs) {
|
|
3637
|
+
if (AmLyrics.isPreWipeArmed(nextSyllable))
|
|
3638
|
+
return;
|
|
3639
|
+
const wordElement = AmLyrics.getWordElementForSyllable(nextSyllable);
|
|
3640
|
+
const wordElements = AmLyrics.getCachedVirtualWordElements(wordElement);
|
|
3641
|
+
const charSpans = AmLyrics.getCachedVirtualWordCharSpans(wordElement, []);
|
|
3642
|
+
const elapsedPreWipe = currentTimeMs - preWipeStartMs;
|
|
3643
|
+
const charCount = charSpans.length ||
|
|
3644
|
+
wordSyllables.reduce((total, wordSyllable) => total + AmLyrics.getVisibleCharacterCount(wordSyllable), 0) ||
|
|
3645
|
+
AmLyrics.getVisibleCharacterCount(nextSyllable);
|
|
3646
|
+
AmLyrics.ensureWordWipeGeometry(charSpans, charCount);
|
|
3647
|
+
const leadChar = charSpans[0];
|
|
3648
|
+
const leadCharSyllable = leadChar?.closest('.lyrics-syllable');
|
|
3649
|
+
const preWipeSyllable = leadCharSyllable || wordSyllables[0] || nextSyllable;
|
|
3650
|
+
AmLyrics.applyWipeShape(preWipeSyllable, charCount);
|
|
3651
|
+
preWipeSyllable.style.setProperty('--pre-wipe-duration', `${preWipeDurationMs}ms`);
|
|
3652
|
+
preWipeSyllable.style.setProperty('--pre-wipe-delay', `${-elapsedPreWipe}ms`);
|
|
3653
|
+
preWipeSyllable.classList.add('pre-highlight');
|
|
3654
|
+
let leadSpans = charSpans;
|
|
3655
|
+
if (leadSpans.length === 0 && leadChar) {
|
|
3656
|
+
leadSpans = [leadChar];
|
|
3657
|
+
}
|
|
3658
|
+
leadSpans.forEach(span => {
|
|
3659
|
+
AmLyrics.applyWipeShape(span, charCount);
|
|
3660
|
+
span.style.setProperty('--pre-wipe-duration', `${preWipeDurationMs}ms`);
|
|
3661
|
+
span.style.setProperty('--pre-wipe-delay', `${-elapsedPreWipe}ms`);
|
|
3662
|
+
span.classList.add('pre-wipe-lead');
|
|
3663
|
+
});
|
|
3664
|
+
wordElements.forEach(element => {
|
|
3665
|
+
const target = element;
|
|
3666
|
+
target._wordPreWipeKey = AmLyrics.getWordPreWipeKey(nextSyllable);
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
static maybePreWipeNextWord(syllables, index, currentTimeMs, currentEndTimeMs) {
|
|
3670
|
+
const syllable = syllables[index];
|
|
3671
|
+
if (syllable.classList.contains('line-synced') ||
|
|
3672
|
+
syllable.classList.contains('transliteration') ||
|
|
3673
|
+
syllable.closest('.lyrics-gap')) {
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
const currentWordReady = syllable.classList.contains('finished') ||
|
|
3677
|
+
currentTimeMs >= currentEndTimeMs - WORD_PRE_WIPE_HANDOFF_LEAD_MS;
|
|
3678
|
+
if (!currentWordReady) {
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
const nextSyllable = AmLyrics.getNextWordSyllable(syllables, index);
|
|
3682
|
+
if (!nextSyllable ||
|
|
3683
|
+
nextSyllable.classList.contains('line-synced') ||
|
|
3684
|
+
nextSyllable.classList.contains('transliteration') ||
|
|
3685
|
+
nextSyllable.closest('.lyrics-gap') ||
|
|
3686
|
+
nextSyllable.classList.contains('highlight') ||
|
|
3687
|
+
nextSyllable.classList.contains('finished')) {
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
const nextStartTimeMs = nextSyllable._cachedStartTime;
|
|
3691
|
+
if (!Number.isFinite(nextStartTimeMs))
|
|
3692
|
+
return;
|
|
3693
|
+
const gapMs = nextStartTimeMs - currentEndTimeMs;
|
|
3694
|
+
if (gapMs > NEXT_WORD_PRE_WIPE_MAX_GAP_MS || gapMs < -50) {
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3697
|
+
const nextWordSyllables = AmLyrics.getRenderedWordSyllables(nextSyllable);
|
|
3698
|
+
const preWipeSyllables = nextWordSyllables.length > 0 ? nextWordSyllables : [nextSyllable];
|
|
3699
|
+
const wordElement = AmLyrics.getWordElementForSyllable(nextSyllable);
|
|
3700
|
+
const wordCharSpans = AmLyrics.getCachedVirtualWordCharSpans(wordElement, []);
|
|
3701
|
+
const charCount = wordCharSpans.length ||
|
|
3702
|
+
preWipeSyllables.reduce((total, wordSyllable) => total + AmLyrics.getVisibleCharacterCount(wordSyllable), 0);
|
|
3703
|
+
if (charCount <= 0)
|
|
3704
|
+
return;
|
|
3705
|
+
const preWipeDuration = AmLyrics.clamp(64 + charCount * 9, NEXT_WORD_PRE_WIPE_MIN_DURATION_MS, NEXT_WORD_PRE_WIPE_MAX_DURATION_MS);
|
|
3706
|
+
const preWipeStart = Math.max(nextStartTimeMs - preWipeDuration, currentEndTimeMs - WORD_PRE_WIPE_HANDOFF_LEAD_MS);
|
|
3707
|
+
if (currentTimeMs < preWipeStart || currentTimeMs >= nextStartTimeMs) {
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
AmLyrics.applyWordPreWipe(nextSyllable, preWipeSyllables, currentTimeMs, preWipeStart, preWipeDuration);
|
|
3711
|
+
}
|
|
3712
|
+
static getCachedCharSpans(element) {
|
|
3713
|
+
const cacheTarget = element;
|
|
3714
|
+
if (!cacheTarget._cachedCharSpans) {
|
|
3715
|
+
cacheTarget._cachedCharSpans = Array.from(element.querySelectorAll('span.char'));
|
|
3716
|
+
}
|
|
3717
|
+
return cacheTarget._cachedCharSpans;
|
|
3718
|
+
}
|
|
3719
|
+
static getCachedVirtualWordElements(wordElement) {
|
|
3720
|
+
if (!wordElement)
|
|
3721
|
+
return [];
|
|
3722
|
+
const cacheTarget = wordElement;
|
|
3723
|
+
if (cacheTarget._cachedVirtualWordElements) {
|
|
3724
|
+
return cacheTarget._cachedVirtualWordElements;
|
|
3725
|
+
}
|
|
3726
|
+
const { virtualWordId } = wordElement.dataset;
|
|
3727
|
+
let wordElements = [wordElement];
|
|
3728
|
+
if (virtualWordId && wordElement.parentElement) {
|
|
3729
|
+
wordElements = Array.from(wordElement.parentElement.querySelectorAll('.lyrics-word')).filter(el => el.dataset.virtualWordId === virtualWordId);
|
|
3730
|
+
}
|
|
3731
|
+
wordElements.forEach(element => {
|
|
3732
|
+
const target = element;
|
|
3733
|
+
target._cachedVirtualWordElements = wordElements;
|
|
3734
|
+
});
|
|
3735
|
+
return wordElements;
|
|
3736
|
+
}
|
|
3737
|
+
static getCachedVirtualWordCharSpans(wordElement, fallbackCharSpans) {
|
|
3738
|
+
if (!wordElement)
|
|
3739
|
+
return fallbackCharSpans;
|
|
3740
|
+
const cacheTarget = wordElement;
|
|
3741
|
+
if (cacheTarget._cachedVirtualWordCharSpans) {
|
|
3742
|
+
return cacheTarget._cachedVirtualWordCharSpans;
|
|
3743
|
+
}
|
|
3744
|
+
const wordElements = AmLyrics.getCachedVirtualWordElements(wordElement);
|
|
3745
|
+
const charSpans = wordElements.flatMap(word => Array.from(word.querySelectorAll('span.char')));
|
|
3746
|
+
const result = charSpans.length > 0 ? charSpans : fallbackCharSpans;
|
|
3747
|
+
wordElements.forEach(element => {
|
|
3748
|
+
const target = element;
|
|
3749
|
+
target._cachedVirtualWordCharSpans = result;
|
|
3750
|
+
});
|
|
3751
|
+
return result;
|
|
3752
|
+
}
|
|
3336
3753
|
static updateSyllableAnimation(syllable, elapsedTimeMs = 0) {
|
|
3337
3754
|
if (syllable.classList.contains('highlight'))
|
|
3338
3755
|
return;
|
|
3339
3756
|
const { classList } = syllable;
|
|
3757
|
+
const hadPreHighlight = classList.contains('pre-highlight');
|
|
3340
3758
|
const isRTL = classList.contains('rtl-text');
|
|
3341
|
-
const charSpans =
|
|
3759
|
+
const charSpans = AmLyrics.getCachedCharSpans(syllable);
|
|
3342
3760
|
const wordElement = syllable.parentElement?.parentElement; // syllable-wrap -> word
|
|
3343
|
-
const
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
const isGrowable =
|
|
3347
|
-
const isCharRise =
|
|
3761
|
+
const typedWordElement = wordElement;
|
|
3762
|
+
const allWordElements = AmLyrics.getCachedVirtualWordElements(typedWordElement);
|
|
3763
|
+
const allWordCharSpans = AmLyrics.getCachedVirtualWordCharSpans(typedWordElement, charSpans);
|
|
3764
|
+
const isGrowable = typedWordElement?.classList.contains('growable');
|
|
3765
|
+
const isCharRise = typedWordElement?.classList.contains('char-rise');
|
|
3766
|
+
const isCharDrag = typedWordElement?.classList.contains('char-drag');
|
|
3348
3767
|
const isFirstSyllable = syllable.getAttribute('data-syllable-index') === '0';
|
|
3768
|
+
const syllableStartMs = parseFloat(syllable.getAttribute('data-start-time') || '0');
|
|
3769
|
+
const virtualWordStartMs = parseFloat(typedWordElement?.dataset.virtualWordStart || '');
|
|
3770
|
+
const isFirstInVirtualWord = isFirstSyllable &&
|
|
3771
|
+
(!Number.isFinite(virtualWordStartMs) ||
|
|
3772
|
+
Math.abs(syllableStartMs - virtualWordStartMs) < 0.5);
|
|
3349
3773
|
const isFirstInContainer = isFirstSyllable; // Simplified
|
|
3350
3774
|
const isGap = syllable.closest('.lyrics-gap') !== null;
|
|
3351
3775
|
// Get duration from data attribute
|
|
@@ -3353,11 +3777,14 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3353
3777
|
const wordDurationMs = parseFloat(syllable.getAttribute('data-word-duration') ||
|
|
3354
3778
|
syllable.getAttribute('data-duration') ||
|
|
3355
3779
|
'0') || syllableDurationMs;
|
|
3356
|
-
|
|
3780
|
+
const wordElapsedTimeMs = Number.isFinite(virtualWordStartMs)
|
|
3781
|
+
? elapsedTimeMs + (syllableStartMs - virtualWordStartMs)
|
|
3782
|
+
: elapsedTimeMs;
|
|
3783
|
+
const charWipeDurationMs = Math.max(wordDurationMs, syllableDurationMs);
|
|
3357
3784
|
const charAnimationsMap = new Map();
|
|
3358
3785
|
const styleUpdates = [];
|
|
3359
3786
|
// Step 1: Grow Pass
|
|
3360
|
-
if (isGrowable &&
|
|
3787
|
+
if (isGrowable && isFirstInVirtualWord && allWordCharSpans.length > 0) {
|
|
3361
3788
|
const finalDuration = wordDurationMs;
|
|
3362
3789
|
const baseDelayPerChar = finalDuration * 0.09;
|
|
3363
3790
|
const growDurationMs = finalDuration * 1.5;
|
|
@@ -3391,7 +3818,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3391
3818
|
});
|
|
3392
3819
|
});
|
|
3393
3820
|
}
|
|
3394
|
-
if (isCharRise &&
|
|
3821
|
+
if (isCharRise && isFirstInVirtualWord && allWordCharSpans.length > 0) {
|
|
3395
3822
|
const finalDuration = Math.max(wordDurationMs, syllableDurationMs);
|
|
3396
3823
|
const baseDelayPerChar = finalDuration * 0.09;
|
|
3397
3824
|
const riseDurationMs = finalDuration * 1.5;
|
|
@@ -3401,16 +3828,60 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3401
3828
|
charAnimationsMap.set(span, `rise-char ${riseDurationMs}ms ease-in-out ${riseDelay}ms forwards`);
|
|
3402
3829
|
});
|
|
3403
3830
|
}
|
|
3831
|
+
if (isCharDrag && isFirstInVirtualWord && allWordCharSpans.length > 0) {
|
|
3832
|
+
const finalDuration = Math.max(wordDurationMs, syllableDurationMs);
|
|
3833
|
+
const baseDelayPerChar = AmLyrics.clamp(finalDuration * 0.15, 64, 118);
|
|
3834
|
+
const dragDurationMs = AmLyrics.clamp(finalDuration * 0.82, 560, 900);
|
|
3835
|
+
allWordCharSpans.forEach(span => {
|
|
3836
|
+
const charIndex = parseFloat(span.dataset.syllableCharIndex || '0');
|
|
3837
|
+
const dragDelay = baseDelayPerChar * charIndex;
|
|
3838
|
+
charAnimationsMap.set(span, `drag-char ${dragDurationMs}ms ease ${dragDelay}ms forwards`);
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3404
3841
|
// Step 2: Wipe Pass
|
|
3405
3842
|
if (charSpans.length > 0) {
|
|
3406
|
-
|
|
3843
|
+
const wipeCharCount = allWordCharSpans.length ||
|
|
3844
|
+
charSpans.length ||
|
|
3845
|
+
AmLyrics.getVisibleCharacterCount(syllable);
|
|
3846
|
+
const wipeScale = AmLyrics.getLongWordWipeScale(wipeCharCount);
|
|
3847
|
+
AmLyrics.applyWipeShape(syllable, wipeCharCount);
|
|
3848
|
+
AmLyrics.ensureWordWipeGeometry(allWordCharSpans, wipeCharCount);
|
|
3849
|
+
allWordCharSpans.forEach(span => AmLyrics.applyWipeShape(span, wipeCharCount));
|
|
3850
|
+
const hasWordLevelWipe = !isFirstInVirtualWord &&
|
|
3851
|
+
(Boolean(typedWordElement?._wordWipeStarted) ||
|
|
3852
|
+
allWordCharSpans.some(span => span.style.animation.includes('wipe')));
|
|
3853
|
+
let charSpansToAnimate = charSpans;
|
|
3854
|
+
if (isFirstInVirtualWord) {
|
|
3855
|
+
charSpansToAnimate = allWordCharSpans;
|
|
3856
|
+
}
|
|
3857
|
+
else if (hasWordLevelWipe) {
|
|
3858
|
+
charSpansToAnimate = [];
|
|
3859
|
+
}
|
|
3860
|
+
if (charSpansToAnimate.length > 0 && allWordElements.length > 0) {
|
|
3861
|
+
allWordElements.forEach(element => {
|
|
3862
|
+
const target = element;
|
|
3863
|
+
target._wordWipeStarted = true;
|
|
3864
|
+
target._wordPreWipeKey = undefined;
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
charSpansToAnimate.forEach((span, charIndex) => {
|
|
3407
3868
|
const startPct = parseFloat(span.dataset.wipeStart || '0');
|
|
3408
3869
|
const durationPct = parseFloat(span.dataset.wipeDuration || '0');
|
|
3409
|
-
const
|
|
3410
|
-
const
|
|
3411
|
-
|
|
3870
|
+
const globalCharIndex = parseFloat(span.dataset.syllableCharIndex || `${charIndex}`);
|
|
3871
|
+
const hadCharPreWipe = span.classList.contains('pre-wipe-lead') ||
|
|
3872
|
+
(hadPreHighlight && globalCharIndex === 0);
|
|
3873
|
+
const charStartMs = charWipeDurationMs * startPct;
|
|
3874
|
+
const remainingWordWipeMs = Math.max(0, charWipeDurationMs - charStartMs);
|
|
3875
|
+
let wipeDelay = charStartMs - wordElapsedTimeMs;
|
|
3876
|
+
let wipeDuration = Math.min(charWipeDurationMs * durationPct * wipeScale, remainingWordWipeMs);
|
|
3877
|
+
const useStartAnimation = isFirstInContainer && globalCharIndex === 0 && !hadCharPreWipe;
|
|
3412
3878
|
let charWipeAnimation = 'wipe';
|
|
3413
|
-
if (
|
|
3879
|
+
if (hadCharPreWipe) {
|
|
3880
|
+
charWipeAnimation = 'wipe-word-from-pre';
|
|
3881
|
+
wipeDelay = -wordElapsedTimeMs;
|
|
3882
|
+
wipeDuration = charWipeDurationMs;
|
|
3883
|
+
}
|
|
3884
|
+
else if (useStartAnimation) {
|
|
3414
3885
|
charWipeAnimation = isRTL ? 'start-wipe-rtl' : 'start-wipe';
|
|
3415
3886
|
}
|
|
3416
3887
|
else {
|
|
@@ -3420,21 +3891,13 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3420
3891
|
const animationParts = [];
|
|
3421
3892
|
if (existingAnimation &&
|
|
3422
3893
|
(existingAnimation.includes('grow-dynamic') ||
|
|
3423
|
-
existingAnimation.includes('rise-char')
|
|
3894
|
+
existingAnimation.includes('rise-char') ||
|
|
3895
|
+
existingAnimation.includes('drag-char'))) {
|
|
3424
3896
|
animationParts.push(existingAnimation.split(',')[0].trim());
|
|
3425
3897
|
}
|
|
3426
|
-
if (charIndex > 0) {
|
|
3427
|
-
const arrivalTime = (span.dataset.preWipeArrival
|
|
3428
|
-
? parseFloat(span.dataset.preWipeArrival)
|
|
3429
|
-
: syllableDurationMs * startPct) - elapsedTimeMs;
|
|
3430
|
-
const constantDuration = parseFloat(span.dataset.preWipeDuration || '100');
|
|
3431
|
-
const animDelay = arrivalTime - constantDuration;
|
|
3432
|
-
if (constantDuration > 0) {
|
|
3433
|
-
animationParts.push(`pre-wipe-char ${constantDuration}ms linear ${animDelay}ms forwards`);
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
3898
|
if (wipeDuration > 0) {
|
|
3437
|
-
|
|
3899
|
+
const wipeFillMode = hadCharPreWipe ? 'both' : 'forwards';
|
|
3900
|
+
animationParts.push(`${charWipeAnimation} ${wipeDuration}ms linear ${wipeDelay}ms ${wipeFillMode}`);
|
|
3438
3901
|
}
|
|
3439
3902
|
if (animationParts.length > 0) {
|
|
3440
3903
|
charAnimationsMap.set(span, animationParts.join(', '));
|
|
@@ -3444,9 +3907,15 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3444
3907
|
else {
|
|
3445
3908
|
// Syllable-level wipe for regular (non-growable) words without chars
|
|
3446
3909
|
const wipeRatio = parseFloat(syllable.getAttribute('data-wipe-ratio') || '1');
|
|
3447
|
-
const
|
|
3910
|
+
const wipeCharCount = AmLyrics.getVisibleCharacterCount(syllable);
|
|
3911
|
+
const wipeScale = AmLyrics.getLongWordWipeScale(wipeCharCount);
|
|
3912
|
+
const visualDuration = syllableDurationMs * wipeRatio * wipeScale;
|
|
3913
|
+
AmLyrics.applyWipeShape(syllable, wipeCharCount);
|
|
3448
3914
|
let wipeAnimation = 'wipe';
|
|
3449
|
-
if (
|
|
3915
|
+
if (hadPreHighlight) {
|
|
3916
|
+
wipeAnimation = isRTL ? 'wipe-from-pre-rtl' : 'wipe-from-pre';
|
|
3917
|
+
}
|
|
3918
|
+
else if (isFirstInContainer) {
|
|
3450
3919
|
wipeAnimation = isRTL ? 'start-wipe-rtl' : 'start-wipe';
|
|
3451
3920
|
}
|
|
3452
3921
|
else {
|
|
@@ -3459,16 +3928,25 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3459
3928
|
syllable.style.animation = `${currentWipeAnimation} ${visualDuration}ms ${isGap ? 'ease-out' : 'linear'} ${-elapsedTimeMs}ms forwards`;
|
|
3460
3929
|
}
|
|
3461
3930
|
// --- WRITE PHASE ---
|
|
3931
|
+
if (allWordElements.length > 0) {
|
|
3932
|
+
allWordElements.forEach(element => {
|
|
3933
|
+
const target = element;
|
|
3934
|
+
target._wordPreWipeKey = undefined;
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3462
3937
|
classList.remove('pre-highlight');
|
|
3463
3938
|
classList.add('highlight');
|
|
3939
|
+
allWordCharSpans.forEach(span => AmLyrics.clearPreWipeLead(span));
|
|
3940
|
+
// Apply keyframe variables before assigning animation strings so the
|
|
3941
|
+
// first painted frame never uses fallback transform values.
|
|
3942
|
+
for (const update of styleUpdates) {
|
|
3943
|
+
update.element.style.setProperty(update.property, update.value);
|
|
3944
|
+
}
|
|
3464
3945
|
for (const [span, animationString] of charAnimationsMap.entries()) {
|
|
3465
3946
|
span.style.willChange = 'transform';
|
|
3947
|
+
span.style.removeProperty('background-color');
|
|
3466
3948
|
span.style.animation = animationString;
|
|
3467
3949
|
}
|
|
3468
|
-
// Apply style updates
|
|
3469
|
-
for (const update of styleUpdates) {
|
|
3470
|
-
update.element.style.setProperty(update.property, update.value);
|
|
3471
|
-
}
|
|
3472
3950
|
}
|
|
3473
3951
|
/**
|
|
3474
3952
|
* Reset syllable animation state
|
|
@@ -3492,10 +3970,19 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3492
3970
|
el.style.animation = '';
|
|
3493
3971
|
el.style.transition = 'none';
|
|
3494
3972
|
el.style.backgroundColor = 'var(--lyplus-text-secondary)';
|
|
3973
|
+
AmLyrics.clearPreWipeLead(el);
|
|
3495
3974
|
}
|
|
3496
3975
|
// Immediately remove all state classes
|
|
3497
3976
|
syllable.classList.remove('highlight', 'finished', 'pre-highlight', 'cleanup');
|
|
3498
3977
|
}
|
|
3978
|
+
static resetWordAnimationState(line) {
|
|
3979
|
+
const wordElements = line.querySelectorAll('.lyrics-word');
|
|
3980
|
+
wordElements.forEach(wordElement => {
|
|
3981
|
+
const target = wordElement;
|
|
3982
|
+
target._wordPreWipeKey = undefined;
|
|
3983
|
+
target._wordWipeStarted = false;
|
|
3984
|
+
});
|
|
3985
|
+
}
|
|
3499
3986
|
/**
|
|
3500
3987
|
* Reset all syllables in a line — batches deferred cleanup into a single rAF
|
|
3501
3988
|
*/
|
|
@@ -3503,6 +3990,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3503
3990
|
if (!line)
|
|
3504
3991
|
return;
|
|
3505
3992
|
line.classList.remove('persist-highlight');
|
|
3993
|
+
AmLyrics.resetWordAnimationState(line);
|
|
3506
3994
|
// eslint-disable-next-line no-param-reassign
|
|
3507
3995
|
line._cachedSyllableElements = null;
|
|
3508
3996
|
const syllables = line.getElementsByClassName('lyrics-syllable');
|
|
@@ -3534,6 +4022,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3534
4022
|
if (!line)
|
|
3535
4023
|
return;
|
|
3536
4024
|
line.classList.remove('persist-highlight');
|
|
4025
|
+
AmLyrics.resetWordAnimationState(line);
|
|
3537
4026
|
const syllables = line.getElementsByClassName('lyrics-syllable');
|
|
3538
4027
|
for (let i = 0; i < syllables.length; i += 1) {
|
|
3539
4028
|
const s = syllables[i];
|
|
@@ -3551,6 +4040,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3551
4040
|
el.style.removeProperty('background-color');
|
|
3552
4041
|
el.style.removeProperty('transition');
|
|
3553
4042
|
el.style.removeProperty('filter');
|
|
4043
|
+
AmLyrics.clearPreWipeLead(el);
|
|
3554
4044
|
}
|
|
3555
4045
|
}
|
|
3556
4046
|
}
|
|
@@ -3587,19 +4077,26 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3587
4077
|
syllable.style.animation = '';
|
|
3588
4078
|
syllable.style.removeProperty('--pre-wipe-duration');
|
|
3589
4079
|
syllable.style.removeProperty('--pre-wipe-delay');
|
|
4080
|
+
syllable.style.removeProperty('background-color');
|
|
4081
|
+
AmLyrics.applyWipeShape(syllable, AmLyrics.getVisibleCharacterCount(syllable));
|
|
3590
4082
|
const chars = syllable.querySelectorAll('span.char');
|
|
3591
4083
|
for (let ci = 0; ci < chars.length; ci += 1) {
|
|
3592
4084
|
const charEl = chars[ci];
|
|
3593
4085
|
const currentAnim = charEl.style.animation || '';
|
|
3594
4086
|
if (currentAnim.includes('grow-dynamic') ||
|
|
3595
|
-
currentAnim.includes('rise-char')
|
|
4087
|
+
currentAnim.includes('rise-char') ||
|
|
4088
|
+
currentAnim.includes('drag-char')) {
|
|
3596
4089
|
const parts = currentAnim.split(',').map(p => p.trim());
|
|
3597
|
-
const transformAnim = parts.find(p => p.includes('grow-dynamic') ||
|
|
4090
|
+
const transformAnim = parts.find(p => p.includes('grow-dynamic') ||
|
|
4091
|
+
p.includes('rise-char') ||
|
|
4092
|
+
p.includes('drag-char'));
|
|
3598
4093
|
charEl.style.animation = transformAnim || '';
|
|
3599
4094
|
}
|
|
3600
4095
|
else {
|
|
3601
4096
|
charEl.style.animation = '';
|
|
3602
4097
|
}
|
|
4098
|
+
charEl.style.backgroundColor = 'var(--lyplus-text-primary)';
|
|
4099
|
+
AmLyrics.clearPreWipeLead(charEl);
|
|
3603
4100
|
}
|
|
3604
4101
|
}
|
|
3605
4102
|
}
|
|
@@ -3640,14 +4137,16 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3640
4137
|
// Early exit check
|
|
3641
4138
|
if (!(currentTimeMs < startTime - 1000 && !hasActiveState)) {
|
|
3642
4139
|
let preHighlightReset = false;
|
|
3643
|
-
//
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
4140
|
+
// Before the syllable starts, pre-highlight only belongs beside a
|
|
4141
|
+
// previous active word. Once the syllable starts, updateSyllableAnimation
|
|
4142
|
+
// consumes the class so the actual wipe can continue from the pre-wipe
|
|
4143
|
+
// pose instead of restarting from the beginning.
|
|
4144
|
+
if (hasPreHighlight && currentTimeMs < startTime) {
|
|
4145
|
+
const prevSyllable = AmLyrics.getPreviousNonTransliterationSyllable(syllables, i);
|
|
4146
|
+
const previousCarriesHighlight = prevSyllable?.classList.contains('highlight') ||
|
|
4147
|
+
prevSyllable?.classList.contains('finished');
|
|
4148
|
+
if (!previousCarriesHighlight) {
|
|
4149
|
+
AmLyrics.clearPreHighlight(syllable);
|
|
3651
4150
|
preHighlightReset = true;
|
|
3652
4151
|
}
|
|
3653
4152
|
}
|
|
@@ -3675,6 +4174,7 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3675
4174
|
// Not yet started
|
|
3676
4175
|
AmLyrics.resetSyllable(syllable);
|
|
3677
4176
|
}
|
|
4177
|
+
AmLyrics.maybePreWipeNextWord(syllables, i, currentTimeMs, endTime);
|
|
3678
4178
|
}
|
|
3679
4179
|
}
|
|
3680
4180
|
}
|
|
@@ -3984,6 +4484,9 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
3984
4484
|
data-end-time="${endTimeMs}"
|
|
3985
4485
|
data-duration="${durationMs}"
|
|
3986
4486
|
data-syllable-index="${syllableIndex}"
|
|
4487
|
+
data-word-index="${syllableIndex}"
|
|
4488
|
+
data-word-length="${syllable.text.replace(/\s/g, '')
|
|
4489
|
+
.length}"
|
|
3987
4490
|
data-wipe-ratio="1"
|
|
3988
4491
|
>${syllable.text}</span
|
|
3989
4492
|
>${bgRomanizedText}</span
|
|
@@ -4005,11 +4508,13 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4005
4508
|
const groupGrowable = lineData?.groupGrowable ?? [];
|
|
4006
4509
|
const groupGlowing = lineData?.groupGlowing ?? [];
|
|
4007
4510
|
const groupCharRise = lineData?.groupCharRise ?? [];
|
|
4511
|
+
const groupCharDrag = lineData?.groupCharDrag ?? [];
|
|
4008
4512
|
const vwFullText = lineData?.vwFullText ?? [];
|
|
4009
4513
|
const vwFullDuration = lineData?.vwFullDuration ?? [];
|
|
4010
4514
|
const vwCharOffset = lineData?.vwCharOffset ?? [];
|
|
4515
|
+
const vwStartMs = lineData?.vwStartMs ?? [];
|
|
4516
|
+
const vwEndMs = lineData?.vwEndMs ?? [];
|
|
4011
4517
|
const lineIsRTL = lineData?.lineIsRTL ?? false;
|
|
4012
|
-
// Create main vocals using YouLyPlus syllable structure
|
|
4013
4518
|
const mainVocalElement = b `<p
|
|
4014
4519
|
class="main-vocal-container ${lineIsRTL ? 'rtl-text' : ''}"
|
|
4015
4520
|
>
|
|
@@ -4017,18 +4522,23 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4017
4522
|
const isGrowable = groupGrowable[groupIdx];
|
|
4018
4523
|
const isGlowing = groupGlowing[groupIdx];
|
|
4019
4524
|
const isCharRise = groupCharRise[groupIdx];
|
|
4020
|
-
const
|
|
4525
|
+
const isCharDrag = groupCharDrag[groupIdx];
|
|
4526
|
+
const isAnimatedByChar = isGrowable || isCharRise || isCharDrag;
|
|
4021
4527
|
const groupLineSynced = group.some(s => s.lineSynced);
|
|
4022
4528
|
const wordText = isAnimatedByChar ? vwFullText[groupIdx] : '';
|
|
4023
4529
|
const wordDuration = isAnimatedByChar
|
|
4024
4530
|
? vwFullDuration[groupIdx]
|
|
4025
4531
|
: 0;
|
|
4026
|
-
const wordNumChars = wordText.length;
|
|
4532
|
+
const wordNumChars = wordText.replace(/\s/g, '').length;
|
|
4027
4533
|
const groupCharOffset = isAnimatedByChar
|
|
4028
4534
|
? vwCharOffset[groupIdx]
|
|
4029
4535
|
: 0;
|
|
4536
|
+
const virtualWordId = `${lineIndex}:${vwStartMs[groupIdx]}:${vwEndMs[groupIdx]}`;
|
|
4537
|
+
const virtualWordStart = vwStartMs[groupIdx];
|
|
4538
|
+
const virtualWordEnd = vwEndMs[groupIdx];
|
|
4030
4539
|
let sylCharAccumulator = 0;
|
|
4031
4540
|
const groupText = group.map(s => s.text).join('');
|
|
4541
|
+
const visibleWordLength = groupText.replace(/\s/g, '').length;
|
|
4032
4542
|
const shouldAllowBreak = groupText.trim().length >= 16 ||
|
|
4033
4543
|
/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(groupText);
|
|
4034
4544
|
// Calculate dynamic rise duration based on the audio duration of the word
|
|
@@ -4040,9 +4550,12 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4040
4550
|
return b `<span
|
|
4041
4551
|
class="lyrics-word${isGrowable ? ' growable' : ''}${isCharRise
|
|
4042
4552
|
? ' char-rise'
|
|
4043
|
-
: ''}${
|
|
4044
|
-
? '
|
|
4045
|
-
: ''}"
|
|
4553
|
+
: ''}${isCharDrag ? ' char-drag' : ''}${isGlowing
|
|
4554
|
+
? ' glowing'
|
|
4555
|
+
: ''}${shouldAllowBreak ? ' allow-break' : ''}"
|
|
4556
|
+
data-virtual-word-id="${virtualWordId}"
|
|
4557
|
+
data-virtual-word-start="${virtualWordStart}"
|
|
4558
|
+
data-virtual-word-end="${virtualWordEnd}"
|
|
4046
4559
|
style="--rise-duration: ${riseDuration}s"
|
|
4047
4560
|
>${group.map((syllable, sylIdx) => {
|
|
4048
4561
|
const startTimeMs = syllable.timestamp;
|
|
@@ -4066,13 +4579,26 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4066
4579
|
: '';
|
|
4067
4580
|
let syllableContent = text;
|
|
4068
4581
|
if (isAnimatedByChar) {
|
|
4069
|
-
let charIndexInsideSyllable = 0;
|
|
4070
4582
|
const numCharsInSyllable = text.replace(/\s/g, '').length || 1;
|
|
4583
|
+
const hasVirtualTiming = wordDuration > 0 && Number.isFinite(virtualWordStart);
|
|
4584
|
+
const syllableStartRatio = hasVirtualTiming
|
|
4585
|
+
? AmLyrics.clamp((startTimeMs - virtualWordStart) / wordDuration, 0, 1)
|
|
4586
|
+
: 0;
|
|
4587
|
+
const syllableDurationRatio = hasVirtualTiming
|
|
4588
|
+
? AmLyrics.clamp(durationMs / wordDuration, 0, 1)
|
|
4589
|
+
: 1;
|
|
4590
|
+
let charIndexInsideSyllable = 0;
|
|
4071
4591
|
syllableContent = b `${text.split('').map(char => {
|
|
4072
4592
|
if (char === ' ')
|
|
4073
4593
|
return ' ';
|
|
4074
4594
|
const charIndexInsideWord = groupCharOffset + sylCharAccumulator;
|
|
4075
|
-
const
|
|
4595
|
+
const localCharIndex = charIndexInsideSyllable;
|
|
4596
|
+
const visibleWordChars = Math.max(1, wordNumChars);
|
|
4597
|
+
const charStartPercentVal = AmLyrics.clamp(syllableStartRatio +
|
|
4598
|
+
(localCharIndex / numCharsInSyllable) *
|
|
4599
|
+
syllableDurationRatio, 0, 1);
|
|
4600
|
+
const charDurationPercentVal = syllableDurationRatio / numCharsInSyllable ||
|
|
4601
|
+
1 / visibleWordChars;
|
|
4076
4602
|
sylCharAccumulator += 1;
|
|
4077
4603
|
charIndexInsideSyllable += 1;
|
|
4078
4604
|
const minDuration = 400;
|
|
@@ -4119,21 +4645,37 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4119
4645
|
const normalizedGrowth = (charMaxScale - 1.0) / 0.1;
|
|
4120
4646
|
const effectiveDuration = (wordDuration + durationMs * 2) / 3;
|
|
4121
4647
|
const peakMultiplier = Math.min(1, Math.max(0.3, effectiveDuration / 2000));
|
|
4122
|
-
const
|
|
4648
|
+
const baseTranslateYPeak = -normalizedGrowth * (2 * peakMultiplier); // Further dampened lift peak
|
|
4123
4649
|
const position = (charIndexInsideWord + 0.5) / wordNumChars;
|
|
4124
4650
|
const horizontalOffset = (position - 0.5) * 2 * ((charMaxScale - 1.0) * 25);
|
|
4651
|
+
const isDragMotion = isCharDrag;
|
|
4652
|
+
let charTranslateYPeak = baseTranslateYPeak;
|
|
4653
|
+
if (isCharRise) {
|
|
4654
|
+
charTranslateYPeak = 0;
|
|
4655
|
+
}
|
|
4656
|
+
else if (isDragMotion) {
|
|
4657
|
+
charTranslateYPeak = -0.78;
|
|
4658
|
+
}
|
|
4659
|
+
let motionHorizontalOffset = horizontalOffset;
|
|
4660
|
+
if (isCharRise) {
|
|
4661
|
+
motionHorizontalOffset = 0;
|
|
4662
|
+
}
|
|
4663
|
+
else if (isDragMotion) {
|
|
4664
|
+
motionHorizontalOffset = 0;
|
|
4665
|
+
}
|
|
4125
4666
|
return b `<span
|
|
4126
4667
|
class="char"
|
|
4127
4668
|
data-char-index="${charIndexInsideWord}"
|
|
4128
4669
|
data-syllable-char-index="${charIndexInsideWord}"
|
|
4129
4670
|
data-wipe-start="${charStartPercentVal.toFixed(4)}"
|
|
4130
|
-
data-wipe-duration="${
|
|
4671
|
+
data-wipe-duration="${charDurationPercentVal.toFixed(4)}"
|
|
4131
4672
|
data-horizontal-offset="${horizontalOffset.toFixed(2)}"
|
|
4132
4673
|
data-max-scale="${charMaxScale.toFixed(3)}"
|
|
4133
4674
|
data-matrix-scale="${(charMaxScale * 0.98).toFixed(3)}"
|
|
4134
|
-
data-char-offset-x="${(
|
|
4675
|
+
data-char-offset-x="${(motionHorizontalOffset * 0.98).toFixed(2)}"
|
|
4135
4676
|
data-shadow-intensity="${charShadowIntensity.toFixed(3)}"
|
|
4136
4677
|
data-translate-y-peak="${charTranslateYPeak.toFixed(3)}"
|
|
4678
|
+
style="--word-wipe-width: ${visibleWordChars}ch; --char-wipe-position: -${charIndexInsideWord}ch"
|
|
4137
4679
|
>${char}</span
|
|
4138
4680
|
>`;
|
|
4139
4681
|
})}`;
|
|
@@ -4151,6 +4693,8 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4151
4693
|
data-duration="${durationMs}"
|
|
4152
4694
|
data-word-duration="${wordDuration}"
|
|
4153
4695
|
data-syllable-index="${sylIdx}"
|
|
4696
|
+
data-word-index="${groupIdx}"
|
|
4697
|
+
data-word-length="${visibleWordLength}"
|
|
4154
4698
|
data-wipe-ratio="1"
|
|
4155
4699
|
>${syllableContent}</span
|
|
4156
4700
|
>${romanizedText}</span
|
|
@@ -4383,13 +4927,14 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4383
4927
|
<button
|
|
4384
4928
|
class="download-button source-switch-btn"
|
|
4385
4929
|
title="Switch Lyrics Source"
|
|
4386
|
-
style="font-family: inherit; font-size: 11px; padding: 2px 6px; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.2); background: transparent; cursor: pointer; color: #aaa; display: inline-flex; align-items: center;"
|
|
4387
4930
|
@click=${this.switchSource}
|
|
4388
4931
|
?disabled=${this.isFetchingAlternatives}
|
|
4389
4932
|
>
|
|
4390
4933
|
<svg
|
|
4391
|
-
class="source-switch-svg lucide lucide-arrow-down-up-icon lucide-arrow-down-up
|
|
4392
|
-
|
|
4934
|
+
class="source-switch-svg lucide lucide-arrow-down-up-icon lucide-arrow-down-up ${this
|
|
4935
|
+
.isFetchingAlternatives
|
|
4936
|
+
? 'is-loading'
|
|
4937
|
+
: ''}"
|
|
4393
4938
|
xmlns="http://www.w3.org/2000/svg"
|
|
4394
4939
|
width="12"
|
|
4395
4940
|
height="12"
|
|
@@ -4447,9 +4992,6 @@ let AmLyrics$1 = class AmLyrics extends i {
|
|
|
4447
4992
|
}
|
|
4448
4993
|
};
|
|
4449
4994
|
AmLyrics$1.styles = i$3 `
|
|
4450
|
-
/* ==========================================================================
|
|
4451
|
-
YOULYPLUS-INSPIRED STYLING - Design Tokens & Variables
|
|
4452
|
-
========================================================================== */
|
|
4453
4995
|
:host {
|
|
4454
4996
|
--lyplus-lyrics-palette: var(
|
|
4455
4997
|
--am-lyrics-highlight-color,
|
|
@@ -4478,6 +5020,8 @@ AmLyrics$1.styles = i$3 `
|
|
|
4478
5020
|
--lyplus-blur-amount: 0.07em;
|
|
4479
5021
|
--lyplus-blur-amount-near: 0.035em;
|
|
4480
5022
|
--lyplus-fade-gap-timing-function: ease-out;
|
|
5023
|
+
--wipe-gradient-width: 0.75em;
|
|
5024
|
+
--wipe-gradient-half: 0.375em;
|
|
4481
5025
|
|
|
4482
5026
|
--lyrics-scroll-padding-top: 25%;
|
|
4483
5027
|
|
|
@@ -4505,6 +5049,9 @@ AmLyrics$1.styles = i$3 `
|
|
|
4505
5049
|
max-height: 100vh;
|
|
4506
5050
|
overflow-y: auto;
|
|
4507
5051
|
-webkit-overflow-scrolling: touch;
|
|
5052
|
+
-webkit-touch-callout: none;
|
|
5053
|
+
-webkit-user-select: none;
|
|
5054
|
+
user-select: none;
|
|
4508
5055
|
box-sizing: border-box;
|
|
4509
5056
|
scrollbar-width: none;
|
|
4510
5057
|
overflow-anchor: none;
|
|
@@ -4601,17 +5148,15 @@ AmLyrics$1.styles = i$3 `
|
|
|
4601
5148
|
|
|
4602
5149
|
.background-vocal-container {
|
|
4603
5150
|
max-height: 0;
|
|
4604
|
-
overflow:
|
|
5151
|
+
overflow: hidden;
|
|
4605
5152
|
opacity: 0;
|
|
4606
5153
|
font-size: var(--lyplus-font-size-subtext);
|
|
4607
5154
|
line-height: 1.15;
|
|
4608
5155
|
color: color-mix(in srgb, var(--lyplus-text-secondary) 80%, transparent);
|
|
4609
|
-
/* Fast exit (0.25 s) so bg vocals collapse quickly and feel snappy.
|
|
4610
|
-
The scroll takes ~0.4 s; finishing the collapse first prevents
|
|
4611
|
-
the container from trailing behind the motion. */
|
|
4612
5156
|
transition:
|
|
4613
|
-
max-height
|
|
4614
|
-
|
|
5157
|
+
max-height var(--scroll-duration, 400ms)
|
|
5158
|
+
cubic-bezier(0.41, 0, 0.12, 0.99),
|
|
5159
|
+
opacity var(--scroll-duration, 400ms) cubic-bezier(0.41, 0, 0.12, 0.99);
|
|
4615
5160
|
margin: 0;
|
|
4616
5161
|
pointer-events: none;
|
|
4617
5162
|
}
|
|
@@ -4620,7 +5165,8 @@ AmLyrics$1.styles = i$3 `
|
|
|
4620
5165
|
display: block;
|
|
4621
5166
|
padding-top: 0;
|
|
4622
5167
|
padding-bottom: 0;
|
|
4623
|
-
transition: padding-top
|
|
5168
|
+
transition: padding-top var(--scroll-duration, 400ms)
|
|
5169
|
+
cubic-bezier(0.41, 0, 0.12, 0.99);
|
|
4624
5170
|
}
|
|
4625
5171
|
|
|
4626
5172
|
.lyrics-line.singer-right .background-vocal-container,
|
|
@@ -4635,16 +5181,11 @@ AmLyrics$1.styles = i$3 `
|
|
|
4635
5181
|
.lyrics-line.bg-expanded .background-vocal-container {
|
|
4636
5182
|
max-height: 4em;
|
|
4637
5183
|
opacity: 1;
|
|
4638
|
-
|
|
4639
|
-
transition:
|
|
4640
|
-
max-height 0.6s ease,
|
|
4641
|
-
opacity 0.6s ease;
|
|
4642
|
-
will-change: max-height, opacity;
|
|
5184
|
+
will-change: opacity;
|
|
4643
5185
|
}
|
|
4644
5186
|
|
|
4645
5187
|
.lyrics-line.bg-expanded .background-vocal-wrap {
|
|
4646
5188
|
padding-top: 0.26em;
|
|
4647
|
-
transition: padding-top 0.6s ease;
|
|
4648
5189
|
}
|
|
4649
5190
|
|
|
4650
5191
|
/* --- Line States & Modifiers --- */
|
|
@@ -4792,11 +5333,22 @@ AmLyrics$1.styles = i$3 `
|
|
|
4792
5333
|
white-space: nowrap;
|
|
4793
5334
|
}
|
|
4794
5335
|
|
|
5336
|
+
.lyrics-word.char-drag {
|
|
5337
|
+
display: inline-block;
|
|
5338
|
+
vertical-align: baseline;
|
|
5339
|
+
white-space: nowrap;
|
|
5340
|
+
}
|
|
5341
|
+
|
|
4795
5342
|
.lyrics-word.char-rise.allow-break {
|
|
4796
5343
|
display: inline;
|
|
4797
5344
|
white-space: normal;
|
|
4798
5345
|
}
|
|
4799
5346
|
|
|
5347
|
+
.lyrics-word.char-drag.allow-break {
|
|
5348
|
+
display: inline;
|
|
5349
|
+
white-space: normal;
|
|
5350
|
+
}
|
|
5351
|
+
|
|
4800
5352
|
.lyrics-syllable-wrap {
|
|
4801
5353
|
display: inline;
|
|
4802
5354
|
}
|
|
@@ -4851,44 +5403,30 @@ AmLyrics$1.styles = i$3 `
|
|
|
4851
5403
|
.lyrics-line.active:not(.lyrics-gap)
|
|
4852
5404
|
.lyrics-syllable.pre-highlight.no-chars {
|
|
4853
5405
|
background-repeat: no-repeat;
|
|
4854
|
-
background-image:
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
var(--
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
var(--lyplus-text-primary, #fff) 100%,
|
|
4864
|
-
#0000 100%
|
|
4865
|
-
);
|
|
4866
|
-
background-size:
|
|
4867
|
-
0.5em 100%,
|
|
4868
|
-
0% 100%;
|
|
4869
|
-
background-position:
|
|
4870
|
-
-0.5em 0%,
|
|
4871
|
-
-0.25em 0%;
|
|
5406
|
+
background-image: linear-gradient(
|
|
5407
|
+
90deg,
|
|
5408
|
+
var(--lyplus-text-primary, #fff) 0%,
|
|
5409
|
+
var(--lyplus-text-primary, #fff)
|
|
5410
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5411
|
+
#0000 100%
|
|
5412
|
+
);
|
|
5413
|
+
background-size: 0% 100%;
|
|
5414
|
+
background-position: left;
|
|
4872
5415
|
}
|
|
4873
5416
|
|
|
4874
5417
|
.lyrics-line.active:not(.lyrics-gap) .lyrics-syllable.highlight.rtl-text,
|
|
4875
5418
|
.lyrics-line.active:not(.lyrics-gap)
|
|
4876
5419
|
.lyrics-syllable.pre-highlight.rtl-text {
|
|
4877
5420
|
direction: rtl;
|
|
4878
|
-
background-image:
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
transparent 100%
|
|
4888
|
-
);
|
|
4889
|
-
background-position:
|
|
4890
|
-
calc(100% + 0.5em) 0%,
|
|
4891
|
-
right 0%;
|
|
5421
|
+
background-image: linear-gradient(
|
|
5422
|
+
-90deg,
|
|
5423
|
+
var(--lyplus-text-primary) 0%,
|
|
5424
|
+
var(--lyplus-text-primary)
|
|
5425
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5426
|
+
transparent 100%
|
|
5427
|
+
);
|
|
5428
|
+
background-size: 0% 100%;
|
|
5429
|
+
background-position: right 0%;
|
|
4892
5430
|
}
|
|
4893
5431
|
|
|
4894
5432
|
/* Background vocals: muted gray wipe instead of white.
|
|
@@ -4905,18 +5443,13 @@ AmLyrics$1.styles = i$3 `
|
|
|
4905
5443
|
.lyrics-line.pre-active
|
|
4906
5444
|
.background-vocal-container
|
|
4907
5445
|
.lyrics-syllable.pre-highlight.no-chars {
|
|
4908
|
-
background-image:
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
linear-gradient(
|
|
4916
|
-
90deg,
|
|
4917
|
-
color-mix(in srgb, var(--lyplus-text-primary, #fff) 50%, #888888) 100%,
|
|
4918
|
-
#0000 100%
|
|
4919
|
-
);
|
|
5446
|
+
background-image: linear-gradient(
|
|
5447
|
+
90deg,
|
|
5448
|
+
color-mix(in srgb, var(--lyplus-text-primary, #fff) 50%, #888888) 0%,
|
|
5449
|
+
color-mix(in srgb, var(--lyplus-text-primary, #fff) 50%, #888888)
|
|
5450
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5451
|
+
#0000 100%
|
|
5452
|
+
);
|
|
4920
5453
|
}
|
|
4921
5454
|
|
|
4922
5455
|
.lyrics-line.active
|
|
@@ -4931,17 +5464,13 @@ AmLyrics$1.styles = i$3 `
|
|
|
4931
5464
|
.lyrics-line.pre-active
|
|
4932
5465
|
.background-vocal-container
|
|
4933
5466
|
.lyrics-syllable.pre-highlight.rtl-text {
|
|
4934
|
-
background-image:
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
-90deg,
|
|
4942
|
-
color-mix(in srgb, var(--lyplus-text-primary) 50%, #888888) 100%,
|
|
4943
|
-
transparent 100%
|
|
4944
|
-
);
|
|
5467
|
+
background-image: linear-gradient(
|
|
5468
|
+
-90deg,
|
|
5469
|
+
color-mix(in srgb, var(--lyplus-text-primary) 50%, #888888) 0%,
|
|
5470
|
+
color-mix(in srgb, var(--lyplus-text-primary) 50%, #888888)
|
|
5471
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5472
|
+
transparent 100%
|
|
5473
|
+
);
|
|
4945
5474
|
}
|
|
4946
5475
|
|
|
4947
5476
|
/* Non-growable words float up with a gentle curve */
|
|
@@ -4965,6 +5494,10 @@ AmLyrics$1.styles = i$3 `
|
|
|
4965
5494
|
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
4966
5495
|
}
|
|
4967
5496
|
|
|
5497
|
+
.lyrics-word.char-drag .lyrics-syllable.cleanup .char {
|
|
5498
|
+
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
5499
|
+
}
|
|
5500
|
+
|
|
4968
5501
|
.lyrics-line.persist-highlight
|
|
4969
5502
|
.lyrics-word.growable
|
|
4970
5503
|
.lyrics-syllable.finished
|
|
@@ -4972,6 +5505,10 @@ AmLyrics$1.styles = i$3 `
|
|
|
4972
5505
|
.lyrics-line.persist-highlight
|
|
4973
5506
|
.lyrics-word.char-rise
|
|
4974
5507
|
.lyrics-syllable.finished
|
|
5508
|
+
.char,
|
|
5509
|
+
.lyrics-line.persist-highlight
|
|
5510
|
+
.lyrics-word.char-drag
|
|
5511
|
+
.lyrics-syllable.finished
|
|
4975
5512
|
.char {
|
|
4976
5513
|
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
4977
5514
|
}
|
|
@@ -5082,70 +5619,56 @@ AmLyrics$1.styles = i$3 `
|
|
|
5082
5619
|
transform 0.7s ease;
|
|
5083
5620
|
}
|
|
5084
5621
|
|
|
5622
|
+
.lyrics-word.char-drag span.char {
|
|
5623
|
+
transition: color 0.18s;
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5085
5626
|
/* Active char spans: structural only, wipe animation sets gradient */
|
|
5086
5627
|
.lyrics-line.active .lyrics-syllable span.char {
|
|
5087
5628
|
background-clip: text;
|
|
5088
5629
|
-webkit-background-clip: text;
|
|
5089
5630
|
background-repeat: no-repeat;
|
|
5090
|
-
background-image:
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
var(--
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
var(--lyplus-text-primary, #fff) 100%,
|
|
5100
|
-
#0000 100%
|
|
5101
|
-
);
|
|
5102
|
-
background-size:
|
|
5103
|
-
0.5em 100%,
|
|
5104
|
-
0% 100%;
|
|
5105
|
-
background-position:
|
|
5106
|
-
-0.5em 0%,
|
|
5107
|
-
-0.25em 0%;
|
|
5631
|
+
background-image: linear-gradient(
|
|
5632
|
+
90deg,
|
|
5633
|
+
var(--lyplus-text-primary, #fff) 0%,
|
|
5634
|
+
var(--lyplus-text-primary, #fff)
|
|
5635
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5636
|
+
#0000 100%
|
|
5637
|
+
);
|
|
5638
|
+
background-size: 0% 100%;
|
|
5639
|
+
background-position: left;
|
|
5108
5640
|
transition:
|
|
5109
5641
|
transform 0.7s ease,
|
|
5110
5642
|
color 0.18s;
|
|
5111
5643
|
}
|
|
5112
5644
|
|
|
5113
5645
|
.lyrics-line.active .lyrics-syllable span.char.highlight {
|
|
5114
|
-
background-image:
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
background-image:
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
var(--
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
var(--lyplus-text-primary, #fff) 100%,
|
|
5141
|
-
#0000 100%
|
|
5142
|
-
);
|
|
5143
|
-
background-size:
|
|
5144
|
-
0.75em 100%,
|
|
5145
|
-
0% 100%;
|
|
5146
|
-
background-position:
|
|
5147
|
-
-0.85em 0%,
|
|
5148
|
-
-0.25em 0%;
|
|
5646
|
+
background-image: linear-gradient(
|
|
5647
|
+
-90deg,
|
|
5648
|
+
var(--lyplus-text-primary, #fff) 0%,
|
|
5649
|
+
var(--lyplus-text-primary, #fff)
|
|
5650
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5651
|
+
#0000 100%
|
|
5652
|
+
);
|
|
5653
|
+
background-size: 0% 100%;
|
|
5654
|
+
background-position: right 0%;
|
|
5655
|
+
}
|
|
5656
|
+
|
|
5657
|
+
.lyrics-line.active .lyrics-syllable span.char.pre-wipe-lead {
|
|
5658
|
+
animation-name: pre-wipe-word-char;
|
|
5659
|
+
animation-duration: var(--pre-wipe-duration);
|
|
5660
|
+
animation-delay: var(--pre-wipe-delay);
|
|
5661
|
+
animation-timing-function: linear;
|
|
5662
|
+
animation-fill-mode: forwards;
|
|
5663
|
+
background-image: linear-gradient(
|
|
5664
|
+
90deg,
|
|
5665
|
+
var(--lyplus-text-primary, #fff) 0%,
|
|
5666
|
+
var(--lyplus-text-primary, #fff)
|
|
5667
|
+
calc(100% - var(--wipe-gradient-width, 0.75em)),
|
|
5668
|
+
#0000 100%
|
|
5669
|
+
);
|
|
5670
|
+
background-size: 0% 100%;
|
|
5671
|
+
background-position: var(--char-wipe-position, left) 0%;
|
|
5149
5672
|
}
|
|
5150
5673
|
|
|
5151
5674
|
/* ==========================================================================
|
|
@@ -5222,13 +5745,7 @@ AmLyrics$1.styles = i$3 `
|
|
|
5222
5745
|
color: var(--lyplus-text-primary) !important;
|
|
5223
5746
|
}
|
|
5224
5747
|
|
|
5225
|
-
.lyrics-line.
|
|
5226
|
-
animation: fade-in-line 0.14s ease-out forwards !important;
|
|
5227
|
-
color: var(--lyplus-text-primary) !important;
|
|
5228
|
-
}
|
|
5229
|
-
|
|
5230
|
-
.lyrics-line.active .lyrics-syllable.line-synced span.char,
|
|
5231
|
-
.lyrics-line.pre-active .lyrics-syllable.line-synced span.char {
|
|
5748
|
+
.lyrics-line.active .lyrics-syllable.line-synced span.char {
|
|
5232
5749
|
background-image: none !important;
|
|
5233
5750
|
background-color: var(--lyplus-text-primary) !important;
|
|
5234
5751
|
transition: background-color 120ms ease-out !important;
|
|
@@ -5401,6 +5918,49 @@ AmLyrics$1.styles = i$3 `
|
|
|
5401
5918
|
gap: 4px;
|
|
5402
5919
|
}
|
|
5403
5920
|
|
|
5921
|
+
.source-switch-btn {
|
|
5922
|
+
position: relative;
|
|
5923
|
+
display: inline-flex;
|
|
5924
|
+
align-items: center;
|
|
5925
|
+
padding: 2px 8px;
|
|
5926
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
5927
|
+
min-height: 28px;
|
|
5928
|
+
background: transparent;
|
|
5929
|
+
border-radius: 6px;
|
|
5930
|
+
color: #aaa;
|
|
5931
|
+
cursor: pointer;
|
|
5932
|
+
font-family: inherit;
|
|
5933
|
+
font-size: 11px;
|
|
5934
|
+
transition:
|
|
5935
|
+
color 0.2s ease,
|
|
5936
|
+
border-color 0.2s ease,
|
|
5937
|
+
background-color 0.2s ease,
|
|
5938
|
+
transform 0.12s ease;
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
.source-switch-btn::before {
|
|
5942
|
+
content: '';
|
|
5943
|
+
position: absolute;
|
|
5944
|
+
inset: -6px;
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
.source-switch-btn:active:not(:disabled) {
|
|
5948
|
+
transform: scale(0.96);
|
|
5949
|
+
}
|
|
5950
|
+
|
|
5951
|
+
.source-switch-btn:disabled {
|
|
5952
|
+
cursor: default;
|
|
5953
|
+
opacity: 0.7;
|
|
5954
|
+
}
|
|
5955
|
+
|
|
5956
|
+
.source-switch-svg {
|
|
5957
|
+
margin-right: 4px;
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5960
|
+
.source-switch-svg.is-loading {
|
|
5961
|
+
animation: source-switch-spin 1s linear infinite;
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5404
5964
|
.control-button {
|
|
5405
5965
|
background: transparent;
|
|
5406
5966
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
@@ -5409,7 +5969,10 @@ AmLyrics$1.styles = i$3 `
|
|
|
5409
5969
|
font-size: 0.8em;
|
|
5410
5970
|
color: rgba(255, 255, 255, 0.6);
|
|
5411
5971
|
cursor: pointer;
|
|
5412
|
-
transition:
|
|
5972
|
+
transition:
|
|
5973
|
+
color 0.2s,
|
|
5974
|
+
border-color 0.2s,
|
|
5975
|
+
background-color 0.2s;
|
|
5413
5976
|
font-weight: normal;
|
|
5414
5977
|
}
|
|
5415
5978
|
|
|
@@ -5547,137 +6110,125 @@ AmLyrics$1.styles = i$3 `
|
|
|
5547
6110
|
KEYFRAME ANIMATIONS
|
|
5548
6111
|
========================================================================== */
|
|
5549
6112
|
|
|
6113
|
+
@keyframes source-switch-spin {
|
|
6114
|
+
to {
|
|
6115
|
+
transform: rotate(360deg);
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
|
|
5550
6119
|
/* Wipe animation for syllables */
|
|
5551
6120
|
@keyframes wipe {
|
|
5552
6121
|
from {
|
|
5553
|
-
background-size:
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
6122
|
+
background-size: 0% 100%;
|
|
6123
|
+
background-position: left;
|
|
6124
|
+
}
|
|
6125
|
+
to {
|
|
6126
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6127
|
+
background-position: left;
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
|
|
6131
|
+
@keyframes wipe-from-pre {
|
|
6132
|
+
from {
|
|
6133
|
+
background-size: var(--wipe-gradient-width, 0.75em) 100%;
|
|
6134
|
+
background-position: left;
|
|
5559
6135
|
}
|
|
5560
6136
|
to {
|
|
5561
|
-
background-size:
|
|
5562
|
-
|
|
5563
|
-
100% 100%;
|
|
5564
|
-
background-position:
|
|
5565
|
-
calc(100% + 0.375em) 0%,
|
|
5566
|
-
left;
|
|
6137
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6138
|
+
background-position: left;
|
|
5567
6139
|
}
|
|
5568
6140
|
}
|
|
5569
6141
|
|
|
5570
6142
|
@keyframes start-wipe {
|
|
5571
6143
|
0% {
|
|
5572
|
-
background-size:
|
|
5573
|
-
|
|
5574
|
-
0% 100%;
|
|
5575
|
-
background-position:
|
|
5576
|
-
-0.75em 0%,
|
|
5577
|
-
-0.375em 0%;
|
|
6144
|
+
background-size: 0% 100%;
|
|
6145
|
+
background-position: left;
|
|
5578
6146
|
}
|
|
5579
6147
|
100% {
|
|
5580
|
-
background-size:
|
|
5581
|
-
|
|
5582
|
-
100% 100%;
|
|
5583
|
-
background-position:
|
|
5584
|
-
calc(100% + 0.375em) 0%,
|
|
5585
|
-
left;
|
|
6148
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6149
|
+
background-position: left;
|
|
5586
6150
|
}
|
|
5587
6151
|
}
|
|
5588
6152
|
|
|
5589
6153
|
@keyframes wipe-rtl {
|
|
5590
6154
|
from {
|
|
5591
|
-
background-size:
|
|
5592
|
-
|
|
5593
|
-
0% 100%;
|
|
5594
|
-
background-position:
|
|
5595
|
-
calc(100% + 0.375em) 0%,
|
|
5596
|
-
calc(100% + 0.36em) 0%;
|
|
6155
|
+
background-size: 0% 100%;
|
|
6156
|
+
background-position: right 0%;
|
|
5597
6157
|
}
|
|
5598
6158
|
to {
|
|
5599
|
-
background-size:
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
6159
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6160
|
+
background-position: right 0%;
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6164
|
+
@keyframes wipe-from-pre-rtl {
|
|
6165
|
+
from {
|
|
6166
|
+
background-size: var(--wipe-gradient-width, 0.75em) 100%;
|
|
6167
|
+
background-position: right 0%;
|
|
6168
|
+
}
|
|
6169
|
+
to {
|
|
6170
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6171
|
+
background-position: right 0%;
|
|
5605
6172
|
}
|
|
5606
6173
|
}
|
|
5607
6174
|
|
|
5608
6175
|
@keyframes start-wipe-rtl {
|
|
5609
6176
|
0% {
|
|
5610
|
-
background-size:
|
|
5611
|
-
|
|
5612
|
-
0% 100%;
|
|
5613
|
-
background-position:
|
|
5614
|
-
calc(100% + 0.75em) 0%,
|
|
5615
|
-
calc(100% + 0.5em) 0%;
|
|
6177
|
+
background-size: 0% 100%;
|
|
6178
|
+
background-position: right 0%;
|
|
5616
6179
|
}
|
|
5617
6180
|
100% {
|
|
5618
|
-
background-size:
|
|
5619
|
-
|
|
5620
|
-
100% 100%;
|
|
5621
|
-
background-position:
|
|
5622
|
-
-0.75em 0%,
|
|
5623
|
-
right 0%;
|
|
6181
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6182
|
+
background-position: right 0%;
|
|
5624
6183
|
}
|
|
5625
6184
|
}
|
|
5626
6185
|
|
|
5627
6186
|
@keyframes pre-wipe-universal {
|
|
5628
6187
|
from {
|
|
5629
|
-
background-size:
|
|
5630
|
-
|
|
5631
|
-
0% 100%;
|
|
5632
|
-
background-position:
|
|
5633
|
-
-0.75em 0%,
|
|
5634
|
-
left;
|
|
6188
|
+
background-size: 0% 100%;
|
|
6189
|
+
background-position: left;
|
|
5635
6190
|
}
|
|
5636
6191
|
to {
|
|
5637
|
-
background-size:
|
|
5638
|
-
|
|
5639
|
-
0% 100%;
|
|
5640
|
-
background-position:
|
|
5641
|
-
-0.375em 0%,
|
|
5642
|
-
left;
|
|
6192
|
+
background-size: var(--wipe-gradient-width, 0.75em) 100%;
|
|
6193
|
+
background-position: left;
|
|
5643
6194
|
}
|
|
5644
6195
|
}
|
|
5645
6196
|
|
|
5646
6197
|
@keyframes pre-wipe-universal-rtl {
|
|
5647
6198
|
from {
|
|
5648
|
-
background-size:
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
6199
|
+
background-size: 0% 100%;
|
|
6200
|
+
background-position: right 0%;
|
|
6201
|
+
}
|
|
6202
|
+
to {
|
|
6203
|
+
background-size: var(--wipe-gradient-width, 0.75em) 100%;
|
|
6204
|
+
background-position: right 0%;
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
|
|
6208
|
+
@keyframes pre-wipe-word-char {
|
|
6209
|
+
from {
|
|
6210
|
+
background-size: 0px 100%;
|
|
6211
|
+
background-position: var(--char-wipe-position, left) 0%;
|
|
5654
6212
|
}
|
|
5655
6213
|
to {
|
|
5656
|
-
background-size:
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
background-position:
|
|
5660
|
-
calc(100% + 0.375em) 0%,
|
|
5661
|
-
right 0%;
|
|
6214
|
+
background-size: var(--pre-wipe-word-size, var(--wipe-gradient-width))
|
|
6215
|
+
100%;
|
|
6216
|
+
background-position: var(--char-wipe-position, left) 0%;
|
|
5662
6217
|
}
|
|
5663
6218
|
}
|
|
5664
6219
|
|
|
5665
|
-
@keyframes
|
|
6220
|
+
@keyframes wipe-word-from-pre {
|
|
5666
6221
|
from {
|
|
5667
|
-
background-size:
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
background-position:
|
|
5671
|
-
-0.75em 0%,
|
|
5672
|
-
left;
|
|
6222
|
+
background-size: var(--pre-wipe-word-size, var(--wipe-gradient-width))
|
|
6223
|
+
100%;
|
|
6224
|
+
background-position: var(--char-wipe-position, left) 0%;
|
|
5673
6225
|
}
|
|
5674
6226
|
to {
|
|
5675
|
-
background-size:
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
left;
|
|
6227
|
+
background-size: calc(
|
|
6228
|
+
var(--word-wipe-width, 100%) + var(--wipe-gradient-width, 0.75em)
|
|
6229
|
+
)
|
|
6230
|
+
100%;
|
|
6231
|
+
background-position: var(--char-wipe-position, left) 0%;
|
|
5681
6232
|
}
|
|
5682
6233
|
}
|
|
5683
6234
|
|
|
@@ -5771,6 +6322,15 @@ AmLyrics$1.styles = i$3 `
|
|
|
5771
6322
|
}
|
|
5772
6323
|
}
|
|
5773
6324
|
|
|
6325
|
+
@keyframes drag-char {
|
|
6326
|
+
0% {
|
|
6327
|
+
transform: translate3d(0, 0, 0);
|
|
6328
|
+
}
|
|
6329
|
+
100% {
|
|
6330
|
+
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
6331
|
+
}
|
|
6332
|
+
}
|
|
6333
|
+
|
|
5774
6334
|
@keyframes grow-static {
|
|
5775
6335
|
0%,
|
|
5776
6336
|
100% {
|