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