@uimaxbai/am-lyrics 1.5.4 → 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 +876 -342
- package/dist/src/am-lyrics.js.map +1 -1
- package/dist/src/react.js +876 -342
- package/dist/src/react.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/AmLyrics.ts +1155 -384
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);
|
|
@@ -2423,28 +2488,42 @@ class AmLyrics extends i {
|
|
|
2423
2488
|
const isLineSynced = line.isWordSynced === false || line.text.some(s => s.lineSynced);
|
|
2424
2489
|
let isGrowableVW = canAnimateByChar && wordLen > 0 && wordLen <= 7;
|
|
2425
2490
|
if (isGrowableVW) {
|
|
2426
|
-
if (wordLen
|
|
2491
|
+
if (wordLen <= 1) {
|
|
2427
2492
|
isGrowableVW =
|
|
2428
2493
|
combinedDuration >= 1050 && combinedDuration >= wordLen * 525;
|
|
2429
2494
|
}
|
|
2495
|
+
else if (wordLen <= 3) {
|
|
2496
|
+
isGrowableVW =
|
|
2497
|
+
combinedDuration >=
|
|
2498
|
+
SHORT_WORD_GLOW_MIN_DURATION_MS + (wordLen - 2) * 140;
|
|
2499
|
+
}
|
|
2430
2500
|
else {
|
|
2431
2501
|
isGrowableVW =
|
|
2432
2502
|
combinedDuration >= 850 && combinedDuration >= wordLen * 190;
|
|
2433
2503
|
}
|
|
2434
2504
|
}
|
|
2435
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);
|
|
2436
2510
|
const hasLongShortWordDuration = wordLen >= 4 && combinedDuration >= Math.max(1300, wordLen * 260);
|
|
2437
2511
|
const isCharRiseVW = canAnimateByChar &&
|
|
2438
2512
|
!isLineSynced &&
|
|
2439
2513
|
!isGrowableVW &&
|
|
2440
2514
|
((wordLen >= 8 && hasCharRiseDuration) ||
|
|
2441
2515
|
(wordLen < 8 && hasLongShortWordDuration));
|
|
2516
|
+
const isCharDragVW = canAnimateByChar &&
|
|
2517
|
+
!isLineSynced &&
|
|
2518
|
+
!isGrowableVW &&
|
|
2519
|
+
hasTinyWordDragDuration;
|
|
2442
2520
|
const isGlowingVW = isGrowableVW && !isLineSynced;
|
|
2443
2521
|
let charOff = 0;
|
|
2444
2522
|
for (let gi = vwStart; gi <= vwEnd; gi += 1) {
|
|
2445
2523
|
groupGrowable[gi] = isGrowableVW;
|
|
2446
2524
|
groupGlowing[gi] = isGlowingVW;
|
|
2447
2525
|
groupCharRise[gi] = isCharRiseVW;
|
|
2526
|
+
groupCharDrag[gi] = isCharDragVW;
|
|
2448
2527
|
vwFullText[gi] = combinedText;
|
|
2449
2528
|
vwFullDuration[gi] = combinedDuration;
|
|
2450
2529
|
vwCharOffset[gi] = charOff;
|
|
@@ -2460,6 +2539,7 @@ class AmLyrics extends i {
|
|
|
2460
2539
|
groupGrowable,
|
|
2461
2540
|
groupGlowing,
|
|
2462
2541
|
groupCharRise,
|
|
2542
|
+
groupCharDrag,
|
|
2463
2543
|
vwFullText,
|
|
2464
2544
|
vwFullDuration,
|
|
2465
2545
|
vwCharOffset,
|
|
@@ -2479,48 +2559,97 @@ class AmLyrics extends i {
|
|
|
2479
2559
|
return;
|
|
2480
2560
|
const computedStyle = getComputedStyle(referenceSyllable);
|
|
2481
2561
|
const { font } = computedStyle; // Full font string
|
|
2482
|
-
const
|
|
2483
|
-
|
|
2484
|
-
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)
|
|
2485
2564
|
return;
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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 => {
|
|
2489
2577
|
const syllables = [];
|
|
2490
|
-
|
|
2491
|
-
const
|
|
2492
|
-
|
|
2493
|
-
|
|
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
|
+
});
|
|
2494
2585
|
});
|
|
2495
|
-
syllables.
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
const
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
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);
|
|
2522
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`);
|
|
2523
2651
|
cumulativeCharWidth += charWidth;
|
|
2652
|
+
cumulativeSyllableWidth += charWidth;
|
|
2524
2653
|
});
|
|
2525
2654
|
});
|
|
2526
2655
|
});
|
|
@@ -2528,6 +2657,33 @@ class AmLyrics extends i {
|
|
|
2528
2657
|
static arraysEqual(a, b) {
|
|
2529
2658
|
return a.length === b.length && a.every((val, i) => val === b[i]);
|
|
2530
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
|
+
}
|
|
2531
2687
|
static getLineIndexFromElement(lineElement) {
|
|
2532
2688
|
if (!lineElement)
|
|
2533
2689
|
return null;
|
|
@@ -2558,6 +2714,26 @@ class AmLyrics extends i {
|
|
|
2558
2714
|
}
|
|
2559
2715
|
this.preActiveLineElements = keptLines;
|
|
2560
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
|
+
}
|
|
2561
2737
|
getPrimaryActiveLineIndex(activeIndices) {
|
|
2562
2738
|
if (activeIndices.length === 0)
|
|
2563
2739
|
return null;
|
|
@@ -2747,9 +2923,10 @@ class AmLyrics extends i {
|
|
|
2747
2923
|
const activeLines = [];
|
|
2748
2924
|
for (let i = 0; i < this.lyrics.length; i += 1) {
|
|
2749
2925
|
const line = this.lyrics[i];
|
|
2926
|
+
const highlightEndTime = this.getLineHighlightEndTime(i);
|
|
2750
2927
|
if (line.timestamp > time)
|
|
2751
2928
|
break;
|
|
2752
|
-
if (time >= line.timestamp && time <
|
|
2929
|
+
if (time >= line.timestamp && time < highlightEndTime) {
|
|
2753
2930
|
activeLines.push(i);
|
|
2754
2931
|
}
|
|
2755
2932
|
}
|
|
@@ -2974,6 +3151,7 @@ class AmLyrics extends i {
|
|
|
2974
3151
|
this.lastPrimaryActiveLine = null;
|
|
2975
3152
|
this.activeLineIds.clear();
|
|
2976
3153
|
this.animatingLines = [];
|
|
3154
|
+
this.setBackgroundExpandedLine(null);
|
|
2977
3155
|
// Find the clicked line element and scroll to it with forceScroll (like YouLyPlus)
|
|
2978
3156
|
// Timestamps are already in milliseconds — match the data-start-time attribute directly
|
|
2979
3157
|
const clickedLineElement = this.lyricsContainer?.querySelector(`.lyrics-line[data-start-time="${line.text[0]?.timestamp || 0}"]`);
|
|
@@ -2990,6 +3168,7 @@ class AmLyrics extends i {
|
|
|
2990
3168
|
this.isClickSeeking = false;
|
|
2991
3169
|
}, 800);
|
|
2992
3170
|
this.scrollToActiveLineYouLy(clickedLineElement, true);
|
|
3171
|
+
this.setBackgroundExpandedLine(clickedLineElement);
|
|
2993
3172
|
}
|
|
2994
3173
|
const event = new CustomEvent('line-click', {
|
|
2995
3174
|
detail: {
|
|
@@ -3335,30 +3514,256 @@ class AmLyrics extends i {
|
|
|
3335
3514
|
}
|
|
3336
3515
|
/**
|
|
3337
3516
|
* Update syllable highlight animation - apply CSS wipe animation
|
|
3338
|
-
* (Exact copy from YouLyPlus _updateSyllableAnimation)
|
|
3339
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
|
+
}
|
|
3340
3750
|
static updateSyllableAnimation(syllable, elapsedTimeMs = 0) {
|
|
3341
3751
|
if (syllable.classList.contains('highlight'))
|
|
3342
3752
|
return;
|
|
3343
3753
|
const { classList } = syllable;
|
|
3754
|
+
const hadPreHighlight = classList.contains('pre-highlight');
|
|
3344
3755
|
const isRTL = classList.contains('rtl-text');
|
|
3345
|
-
const charSpans =
|
|
3756
|
+
const charSpans = AmLyrics.getCachedCharSpans(syllable);
|
|
3346
3757
|
const wordElement = syllable.parentElement?.parentElement; // syllable-wrap -> word
|
|
3347
|
-
const
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
else if (wordElement) {
|
|
3354
|
-
wordElements = [wordElement];
|
|
3355
|
-
}
|
|
3356
|
-
const allWordCharSpans = wordElements.flatMap(word => Array.from(word.querySelectorAll('span.char')));
|
|
3357
|
-
const isGrowable = wordElement?.classList.contains('growable');
|
|
3358
|
-
const isCharRise = wordElement?.classList.contains('char-rise');
|
|
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');
|
|
3359
3764
|
const isFirstSyllable = syllable.getAttribute('data-syllable-index') === '0';
|
|
3360
3765
|
const syllableStartMs = parseFloat(syllable.getAttribute('data-start-time') || '0');
|
|
3361
|
-
const virtualWordStartMs = parseFloat(
|
|
3766
|
+
const virtualWordStartMs = parseFloat(typedWordElement?.dataset.virtualWordStart || '');
|
|
3362
3767
|
const isFirstInVirtualWord = isFirstSyllable &&
|
|
3363
3768
|
(!Number.isFinite(virtualWordStartMs) ||
|
|
3364
3769
|
Math.abs(syllableStartMs - virtualWordStartMs) < 0.5);
|
|
@@ -3369,7 +3774,10 @@ class AmLyrics extends i {
|
|
|
3369
3774
|
const wordDurationMs = parseFloat(syllable.getAttribute('data-word-duration') ||
|
|
3370
3775
|
syllable.getAttribute('data-duration') ||
|
|
3371
3776
|
'0') || syllableDurationMs;
|
|
3372
|
-
|
|
3777
|
+
const wordElapsedTimeMs = Number.isFinite(virtualWordStartMs)
|
|
3778
|
+
? elapsedTimeMs + (syllableStartMs - virtualWordStartMs)
|
|
3779
|
+
: elapsedTimeMs;
|
|
3780
|
+
const charWipeDurationMs = Math.max(wordDurationMs, syllableDurationMs);
|
|
3373
3781
|
const charAnimationsMap = new Map();
|
|
3374
3782
|
const styleUpdates = [];
|
|
3375
3783
|
// Step 1: Grow Pass
|
|
@@ -3417,16 +3825,60 @@ class AmLyrics extends i {
|
|
|
3417
3825
|
charAnimationsMap.set(span, `rise-char ${riseDurationMs}ms ease-in-out ${riseDelay}ms forwards`);
|
|
3418
3826
|
});
|
|
3419
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
|
+
}
|
|
3420
3838
|
// Step 2: Wipe Pass
|
|
3421
3839
|
if (charSpans.length > 0) {
|
|
3422
|
-
|
|
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) => {
|
|
3423
3865
|
const startPct = parseFloat(span.dataset.wipeStart || '0');
|
|
3424
3866
|
const durationPct = parseFloat(span.dataset.wipeDuration || '0');
|
|
3425
|
-
const
|
|
3426
|
-
const
|
|
3427
|
-
|
|
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;
|
|
3428
3875
|
let charWipeAnimation = 'wipe';
|
|
3429
|
-
if (
|
|
3876
|
+
if (hadCharPreWipe) {
|
|
3877
|
+
charWipeAnimation = 'wipe-word-from-pre';
|
|
3878
|
+
wipeDelay = -wordElapsedTimeMs;
|
|
3879
|
+
wipeDuration = charWipeDurationMs;
|
|
3880
|
+
}
|
|
3881
|
+
else if (useStartAnimation) {
|
|
3430
3882
|
charWipeAnimation = isRTL ? 'start-wipe-rtl' : 'start-wipe';
|
|
3431
3883
|
}
|
|
3432
3884
|
else {
|
|
@@ -3436,22 +3888,13 @@ class AmLyrics extends i {
|
|
|
3436
3888
|
const animationParts = [];
|
|
3437
3889
|
if (existingAnimation &&
|
|
3438
3890
|
(existingAnimation.includes('grow-dynamic') ||
|
|
3439
|
-
existingAnimation.includes('rise-char')
|
|
3891
|
+
existingAnimation.includes('rise-char') ||
|
|
3892
|
+
existingAnimation.includes('drag-char'))) {
|
|
3440
3893
|
animationParts.push(existingAnimation.split(',')[0].trim());
|
|
3441
3894
|
}
|
|
3442
|
-
if (charIndex > 0 && wipeDelay > 0 && wipeDuration > 0) {
|
|
3443
|
-
const arrivalTime = (span.dataset.preWipeArrival
|
|
3444
|
-
? parseFloat(span.dataset.preWipeArrival)
|
|
3445
|
-
: syllableDurationMs * startPct) - elapsedTimeMs;
|
|
3446
|
-
const measuredPreWipeDuration = parseFloat(span.dataset.preWipeDuration || '100');
|
|
3447
|
-
const preWipeDuration = Math.min(measuredPreWipeDuration, wipeDuration * 0.9, syllableDurationMs * 0.08, arrivalTime);
|
|
3448
|
-
const animDelay = arrivalTime - preWipeDuration;
|
|
3449
|
-
if (preWipeDuration >= 16) {
|
|
3450
|
-
animationParts.push(`pre-wipe-char ${preWipeDuration}ms linear ${animDelay}ms none`);
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
3895
|
if (wipeDuration > 0) {
|
|
3454
|
-
|
|
3896
|
+
const wipeFillMode = hadCharPreWipe ? 'both' : 'forwards';
|
|
3897
|
+
animationParts.push(`${charWipeAnimation} ${wipeDuration}ms linear ${wipeDelay}ms ${wipeFillMode}`);
|
|
3455
3898
|
}
|
|
3456
3899
|
if (animationParts.length > 0) {
|
|
3457
3900
|
charAnimationsMap.set(span, animationParts.join(', '));
|
|
@@ -3461,9 +3904,15 @@ class AmLyrics extends i {
|
|
|
3461
3904
|
else {
|
|
3462
3905
|
// Syllable-level wipe for regular (non-growable) words without chars
|
|
3463
3906
|
const wipeRatio = parseFloat(syllable.getAttribute('data-wipe-ratio') || '1');
|
|
3464
|
-
const
|
|
3907
|
+
const wipeCharCount = AmLyrics.getVisibleCharacterCount(syllable);
|
|
3908
|
+
const wipeScale = AmLyrics.getLongWordWipeScale(wipeCharCount);
|
|
3909
|
+
const visualDuration = syllableDurationMs * wipeRatio * wipeScale;
|
|
3910
|
+
AmLyrics.applyWipeShape(syllable, wipeCharCount);
|
|
3465
3911
|
let wipeAnimation = 'wipe';
|
|
3466
|
-
if (
|
|
3912
|
+
if (hadPreHighlight) {
|
|
3913
|
+
wipeAnimation = isRTL ? 'wipe-from-pre-rtl' : 'wipe-from-pre';
|
|
3914
|
+
}
|
|
3915
|
+
else if (isFirstInContainer) {
|
|
3467
3916
|
wipeAnimation = isRTL ? 'start-wipe-rtl' : 'start-wipe';
|
|
3468
3917
|
}
|
|
3469
3918
|
else {
|
|
@@ -3476,16 +3925,25 @@ class AmLyrics extends i {
|
|
|
3476
3925
|
syllable.style.animation = `${currentWipeAnimation} ${visualDuration}ms ${isGap ? 'ease-out' : 'linear'} ${-elapsedTimeMs}ms forwards`;
|
|
3477
3926
|
}
|
|
3478
3927
|
// --- WRITE PHASE ---
|
|
3928
|
+
if (allWordElements.length > 0) {
|
|
3929
|
+
allWordElements.forEach(element => {
|
|
3930
|
+
const target = element;
|
|
3931
|
+
target._wordPreWipeKey = undefined;
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3479
3934
|
classList.remove('pre-highlight');
|
|
3480
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
|
+
}
|
|
3481
3942
|
for (const [span, animationString] of charAnimationsMap.entries()) {
|
|
3482
3943
|
span.style.willChange = 'transform';
|
|
3944
|
+
span.style.removeProperty('background-color');
|
|
3483
3945
|
span.style.animation = animationString;
|
|
3484
3946
|
}
|
|
3485
|
-
// Apply style updates
|
|
3486
|
-
for (const update of styleUpdates) {
|
|
3487
|
-
update.element.style.setProperty(update.property, update.value);
|
|
3488
|
-
}
|
|
3489
3947
|
}
|
|
3490
3948
|
/**
|
|
3491
3949
|
* Reset syllable animation state
|
|
@@ -3509,10 +3967,19 @@ class AmLyrics extends i {
|
|
|
3509
3967
|
el.style.animation = '';
|
|
3510
3968
|
el.style.transition = 'none';
|
|
3511
3969
|
el.style.backgroundColor = 'var(--lyplus-text-secondary)';
|
|
3970
|
+
AmLyrics.clearPreWipeLead(el);
|
|
3512
3971
|
}
|
|
3513
3972
|
// Immediately remove all state classes
|
|
3514
3973
|
syllable.classList.remove('highlight', 'finished', 'pre-highlight', 'cleanup');
|
|
3515
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
|
+
}
|
|
3516
3983
|
/**
|
|
3517
3984
|
* Reset all syllables in a line — batches deferred cleanup into a single rAF
|
|
3518
3985
|
*/
|
|
@@ -3520,6 +3987,7 @@ class AmLyrics extends i {
|
|
|
3520
3987
|
if (!line)
|
|
3521
3988
|
return;
|
|
3522
3989
|
line.classList.remove('persist-highlight');
|
|
3990
|
+
AmLyrics.resetWordAnimationState(line);
|
|
3523
3991
|
// eslint-disable-next-line no-param-reassign
|
|
3524
3992
|
line._cachedSyllableElements = null;
|
|
3525
3993
|
const syllables = line.getElementsByClassName('lyrics-syllable');
|
|
@@ -3551,6 +4019,7 @@ class AmLyrics extends i {
|
|
|
3551
4019
|
if (!line)
|
|
3552
4020
|
return;
|
|
3553
4021
|
line.classList.remove('persist-highlight');
|
|
4022
|
+
AmLyrics.resetWordAnimationState(line);
|
|
3554
4023
|
const syllables = line.getElementsByClassName('lyrics-syllable');
|
|
3555
4024
|
for (let i = 0; i < syllables.length; i += 1) {
|
|
3556
4025
|
const s = syllables[i];
|
|
@@ -3568,6 +4037,7 @@ class AmLyrics extends i {
|
|
|
3568
4037
|
el.style.removeProperty('background-color');
|
|
3569
4038
|
el.style.removeProperty('transition');
|
|
3570
4039
|
el.style.removeProperty('filter');
|
|
4040
|
+
AmLyrics.clearPreWipeLead(el);
|
|
3571
4041
|
}
|
|
3572
4042
|
}
|
|
3573
4043
|
}
|
|
@@ -3604,19 +4074,26 @@ class AmLyrics extends i {
|
|
|
3604
4074
|
syllable.style.animation = '';
|
|
3605
4075
|
syllable.style.removeProperty('--pre-wipe-duration');
|
|
3606
4076
|
syllable.style.removeProperty('--pre-wipe-delay');
|
|
4077
|
+
syllable.style.removeProperty('background-color');
|
|
4078
|
+
AmLyrics.applyWipeShape(syllable, AmLyrics.getVisibleCharacterCount(syllable));
|
|
3607
4079
|
const chars = syllable.querySelectorAll('span.char');
|
|
3608
4080
|
for (let ci = 0; ci < chars.length; ci += 1) {
|
|
3609
4081
|
const charEl = chars[ci];
|
|
3610
4082
|
const currentAnim = charEl.style.animation || '';
|
|
3611
4083
|
if (currentAnim.includes('grow-dynamic') ||
|
|
3612
|
-
currentAnim.includes('rise-char')
|
|
4084
|
+
currentAnim.includes('rise-char') ||
|
|
4085
|
+
currentAnim.includes('drag-char')) {
|
|
3613
4086
|
const parts = currentAnim.split(',').map(p => p.trim());
|
|
3614
|
-
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'));
|
|
3615
4090
|
charEl.style.animation = transformAnim || '';
|
|
3616
4091
|
}
|
|
3617
4092
|
else {
|
|
3618
4093
|
charEl.style.animation = '';
|
|
3619
4094
|
}
|
|
4095
|
+
charEl.style.backgroundColor = 'var(--lyplus-text-primary)';
|
|
4096
|
+
AmLyrics.clearPreWipeLead(charEl);
|
|
3620
4097
|
}
|
|
3621
4098
|
}
|
|
3622
4099
|
}
|
|
@@ -3657,14 +4134,16 @@ class AmLyrics extends i {
|
|
|
3657
4134
|
// Early exit check
|
|
3658
4135
|
if (!(currentTimeMs < startTime - 1000 && !hasActiveState)) {
|
|
3659
4136
|
let preHighlightReset = false;
|
|
3660
|
-
//
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
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);
|
|
3668
4147
|
preHighlightReset = true;
|
|
3669
4148
|
}
|
|
3670
4149
|
}
|
|
@@ -3692,6 +4171,7 @@ class AmLyrics extends i {
|
|
|
3692
4171
|
// Not yet started
|
|
3693
4172
|
AmLyrics.resetSyllable(syllable);
|
|
3694
4173
|
}
|
|
4174
|
+
AmLyrics.maybePreWipeNextWord(syllables, i, currentTimeMs, endTime);
|
|
3695
4175
|
}
|
|
3696
4176
|
}
|
|
3697
4177
|
}
|
|
@@ -4001,6 +4481,9 @@ class AmLyrics extends i {
|
|
|
4001
4481
|
data-end-time="${endTimeMs}"
|
|
4002
4482
|
data-duration="${durationMs}"
|
|
4003
4483
|
data-syllable-index="${syllableIndex}"
|
|
4484
|
+
data-word-index="${syllableIndex}"
|
|
4485
|
+
data-word-length="${syllable.text.replace(/\s/g, '')
|
|
4486
|
+
.length}"
|
|
4004
4487
|
data-wipe-ratio="1"
|
|
4005
4488
|
>${syllable.text}</span
|
|
4006
4489
|
>${bgRomanizedText}</span
|
|
@@ -4022,13 +4505,13 @@ class AmLyrics extends i {
|
|
|
4022
4505
|
const groupGrowable = lineData?.groupGrowable ?? [];
|
|
4023
4506
|
const groupGlowing = lineData?.groupGlowing ?? [];
|
|
4024
4507
|
const groupCharRise = lineData?.groupCharRise ?? [];
|
|
4508
|
+
const groupCharDrag = lineData?.groupCharDrag ?? [];
|
|
4025
4509
|
const vwFullText = lineData?.vwFullText ?? [];
|
|
4026
4510
|
const vwFullDuration = lineData?.vwFullDuration ?? [];
|
|
4027
4511
|
const vwCharOffset = lineData?.vwCharOffset ?? [];
|
|
4028
4512
|
const vwStartMs = lineData?.vwStartMs ?? [];
|
|
4029
4513
|
const vwEndMs = lineData?.vwEndMs ?? [];
|
|
4030
4514
|
const lineIsRTL = lineData?.lineIsRTL ?? false;
|
|
4031
|
-
// Create main vocals using YouLyPlus syllable structure
|
|
4032
4515
|
const mainVocalElement = b `<p
|
|
4033
4516
|
class="main-vocal-container ${lineIsRTL ? 'rtl-text' : ''}"
|
|
4034
4517
|
>
|
|
@@ -4036,25 +4519,23 @@ class AmLyrics extends i {
|
|
|
4036
4519
|
const isGrowable = groupGrowable[groupIdx];
|
|
4037
4520
|
const isGlowing = groupGlowing[groupIdx];
|
|
4038
4521
|
const isCharRise = groupCharRise[groupIdx];
|
|
4039
|
-
const
|
|
4522
|
+
const isCharDrag = groupCharDrag[groupIdx];
|
|
4523
|
+
const isAnimatedByChar = isGrowable || isCharRise || isCharDrag;
|
|
4040
4524
|
const groupLineSynced = group.some(s => s.lineSynced);
|
|
4041
4525
|
const wordText = isAnimatedByChar ? vwFullText[groupIdx] : '';
|
|
4042
4526
|
const wordDuration = isAnimatedByChar
|
|
4043
4527
|
? vwFullDuration[groupIdx]
|
|
4044
4528
|
: 0;
|
|
4045
|
-
const wordNumChars = wordText.length;
|
|
4529
|
+
const wordNumChars = wordText.replace(/\s/g, '').length;
|
|
4046
4530
|
const groupCharOffset = isAnimatedByChar
|
|
4047
4531
|
? vwCharOffset[groupIdx]
|
|
4048
4532
|
: 0;
|
|
4049
|
-
const virtualWordId =
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
const virtualWordStart = isAnimatedByChar
|
|
4053
|
-
? vwStartMs[groupIdx]
|
|
4054
|
-
: '';
|
|
4055
|
-
const virtualWordEnd = isAnimatedByChar ? vwEndMs[groupIdx] : '';
|
|
4533
|
+
const virtualWordId = `${lineIndex}:${vwStartMs[groupIdx]}:${vwEndMs[groupIdx]}`;
|
|
4534
|
+
const virtualWordStart = vwStartMs[groupIdx];
|
|
4535
|
+
const virtualWordEnd = vwEndMs[groupIdx];
|
|
4056
4536
|
let sylCharAccumulator = 0;
|
|
4057
4537
|
const groupText = group.map(s => s.text).join('');
|
|
4538
|
+
const visibleWordLength = groupText.replace(/\s/g, '').length;
|
|
4058
4539
|
const shouldAllowBreak = groupText.trim().length >= 16 ||
|
|
4059
4540
|
/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(groupText);
|
|
4060
4541
|
// Calculate dynamic rise duration based on the audio duration of the word
|
|
@@ -4066,9 +4547,9 @@ class AmLyrics extends i {
|
|
|
4066
4547
|
return b `<span
|
|
4067
4548
|
class="lyrics-word${isGrowable ? ' growable' : ''}${isCharRise
|
|
4068
4549
|
? ' char-rise'
|
|
4069
|
-
: ''}${
|
|
4070
|
-
? '
|
|
4071
|
-
: ''}"
|
|
4550
|
+
: ''}${isCharDrag ? ' char-drag' : ''}${isGlowing
|
|
4551
|
+
? ' glowing'
|
|
4552
|
+
: ''}${shouldAllowBreak ? ' allow-break' : ''}"
|
|
4072
4553
|
data-virtual-word-id="${virtualWordId}"
|
|
4073
4554
|
data-virtual-word-start="${virtualWordStart}"
|
|
4074
4555
|
data-virtual-word-end="${virtualWordEnd}"
|
|
@@ -4095,13 +4576,26 @@ class AmLyrics extends i {
|
|
|
4095
4576
|
: '';
|
|
4096
4577
|
let syllableContent = text;
|
|
4097
4578
|
if (isAnimatedByChar) {
|
|
4098
|
-
let charIndexInsideSyllable = 0;
|
|
4099
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;
|
|
4100
4588
|
syllableContent = b `${text.split('').map(char => {
|
|
4101
4589
|
if (char === ' ')
|
|
4102
4590
|
return ' ';
|
|
4103
4591
|
const charIndexInsideWord = groupCharOffset + sylCharAccumulator;
|
|
4104
|
-
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;
|
|
4105
4599
|
sylCharAccumulator += 1;
|
|
4106
4600
|
charIndexInsideSyllable += 1;
|
|
4107
4601
|
const minDuration = 400;
|
|
@@ -4148,21 +4642,37 @@ class AmLyrics extends i {
|
|
|
4148
4642
|
const normalizedGrowth = (charMaxScale - 1.0) / 0.1;
|
|
4149
4643
|
const effectiveDuration = (wordDuration + durationMs * 2) / 3;
|
|
4150
4644
|
const peakMultiplier = Math.min(1, Math.max(0.3, effectiveDuration / 2000));
|
|
4151
|
-
const
|
|
4645
|
+
const baseTranslateYPeak = -normalizedGrowth * (2 * peakMultiplier); // Further dampened lift peak
|
|
4152
4646
|
const position = (charIndexInsideWord + 0.5) / wordNumChars;
|
|
4153
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
|
+
}
|
|
4154
4663
|
return b `<span
|
|
4155
4664
|
class="char"
|
|
4156
4665
|
data-char-index="${charIndexInsideWord}"
|
|
4157
4666
|
data-syllable-char-index="${charIndexInsideWord}"
|
|
4158
4667
|
data-wipe-start="${charStartPercentVal.toFixed(4)}"
|
|
4159
|
-
data-wipe-duration="${
|
|
4668
|
+
data-wipe-duration="${charDurationPercentVal.toFixed(4)}"
|
|
4160
4669
|
data-horizontal-offset="${horizontalOffset.toFixed(2)}"
|
|
4161
4670
|
data-max-scale="${charMaxScale.toFixed(3)}"
|
|
4162
4671
|
data-matrix-scale="${(charMaxScale * 0.98).toFixed(3)}"
|
|
4163
|
-
data-char-offset-x="${(
|
|
4672
|
+
data-char-offset-x="${(motionHorizontalOffset * 0.98).toFixed(2)}"
|
|
4164
4673
|
data-shadow-intensity="${charShadowIntensity.toFixed(3)}"
|
|
4165
4674
|
data-translate-y-peak="${charTranslateYPeak.toFixed(3)}"
|
|
4675
|
+
style="--word-wipe-width: ${visibleWordChars}ch; --char-wipe-position: -${charIndexInsideWord}ch"
|
|
4166
4676
|
>${char}</span
|
|
4167
4677
|
>`;
|
|
4168
4678
|
})}`;
|
|
@@ -4180,6 +4690,8 @@ class AmLyrics extends i {
|
|
|
4180
4690
|
data-duration="${durationMs}"
|
|
4181
4691
|
data-word-duration="${wordDuration}"
|
|
4182
4692
|
data-syllable-index="${sylIdx}"
|
|
4693
|
+
data-word-index="${groupIdx}"
|
|
4694
|
+
data-word-length="${visibleWordLength}"
|
|
4183
4695
|
data-wipe-ratio="1"
|
|
4184
4696
|
>${syllableContent}</span
|
|
4185
4697
|
>${romanizedText}</span
|
|
@@ -4412,13 +4924,14 @@ class AmLyrics extends i {
|
|
|
4412
4924
|
<button
|
|
4413
4925
|
class="download-button source-switch-btn"
|
|
4414
4926
|
title="Switch Lyrics Source"
|
|
4415
|
-
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;"
|
|
4416
4927
|
@click=${this.switchSource}
|
|
4417
4928
|
?disabled=${this.isFetchingAlternatives}
|
|
4418
4929
|
>
|
|
4419
4930
|
<svg
|
|
4420
|
-
class="source-switch-svg lucide lucide-arrow-down-up-icon lucide-arrow-down-up
|
|
4421
|
-
|
|
4931
|
+
class="source-switch-svg lucide lucide-arrow-down-up-icon lucide-arrow-down-up ${this
|
|
4932
|
+
.isFetchingAlternatives
|
|
4933
|
+
? 'is-loading'
|
|
4934
|
+
: ''}"
|
|
4422
4935
|
xmlns="http://www.w3.org/2000/svg"
|
|
4423
4936
|
width="12"
|
|
4424
4937
|
height="12"
|
|
@@ -4476,9 +4989,6 @@ class AmLyrics extends i {
|
|
|
4476
4989
|
}
|
|
4477
4990
|
}
|
|
4478
4991
|
AmLyrics.styles = i$3 `
|
|
4479
|
-
/* ==========================================================================
|
|
4480
|
-
YOULYPLUS-INSPIRED STYLING - Design Tokens & Variables
|
|
4481
|
-
========================================================================== */
|
|
4482
4992
|
:host {
|
|
4483
4993
|
--lyplus-lyrics-palette: var(
|
|
4484
4994
|
--am-lyrics-highlight-color,
|
|
@@ -4507,6 +5017,8 @@ AmLyrics.styles = i$3 `
|
|
|
4507
5017
|
--lyplus-blur-amount: 0.07em;
|
|
4508
5018
|
--lyplus-blur-amount-near: 0.035em;
|
|
4509
5019
|
--lyplus-fade-gap-timing-function: ease-out;
|
|
5020
|
+
--wipe-gradient-width: 0.75em;
|
|
5021
|
+
--wipe-gradient-half: 0.375em;
|
|
4510
5022
|
|
|
4511
5023
|
--lyrics-scroll-padding-top: 25%;
|
|
4512
5024
|
|
|
@@ -4534,6 +5046,9 @@ AmLyrics.styles = i$3 `
|
|
|
4534
5046
|
max-height: 100vh;
|
|
4535
5047
|
overflow-y: auto;
|
|
4536
5048
|
-webkit-overflow-scrolling: touch;
|
|
5049
|
+
-webkit-touch-callout: none;
|
|
5050
|
+
-webkit-user-select: none;
|
|
5051
|
+
user-select: none;
|
|
4537
5052
|
box-sizing: border-box;
|
|
4538
5053
|
scrollbar-width: none;
|
|
4539
5054
|
overflow-anchor: none;
|
|
@@ -4663,7 +5178,7 @@ AmLyrics.styles = i$3 `
|
|
|
4663
5178
|
.lyrics-line.bg-expanded .background-vocal-container {
|
|
4664
5179
|
max-height: 4em;
|
|
4665
5180
|
opacity: 1;
|
|
4666
|
-
will-change:
|
|
5181
|
+
will-change: opacity;
|
|
4667
5182
|
}
|
|
4668
5183
|
|
|
4669
5184
|
.lyrics-line.bg-expanded .background-vocal-wrap {
|
|
@@ -4815,11 +5330,22 @@ AmLyrics.styles = i$3 `
|
|
|
4815
5330
|
white-space: nowrap;
|
|
4816
5331
|
}
|
|
4817
5332
|
|
|
5333
|
+
.lyrics-word.char-drag {
|
|
5334
|
+
display: inline-block;
|
|
5335
|
+
vertical-align: baseline;
|
|
5336
|
+
white-space: nowrap;
|
|
5337
|
+
}
|
|
5338
|
+
|
|
4818
5339
|
.lyrics-word.char-rise.allow-break {
|
|
4819
5340
|
display: inline;
|
|
4820
5341
|
white-space: normal;
|
|
4821
5342
|
}
|
|
4822
5343
|
|
|
5344
|
+
.lyrics-word.char-drag.allow-break {
|
|
5345
|
+
display: inline;
|
|
5346
|
+
white-space: normal;
|
|
5347
|
+
}
|
|
5348
|
+
|
|
4823
5349
|
.lyrics-syllable-wrap {
|
|
4824
5350
|
display: inline;
|
|
4825
5351
|
}
|
|
@@ -4874,44 +5400,30 @@ AmLyrics.styles = i$3 `
|
|
|
4874
5400
|
.lyrics-line.active:not(.lyrics-gap)
|
|
4875
5401
|
.lyrics-syllable.pre-highlight.no-chars {
|
|
4876
5402
|
background-repeat: no-repeat;
|
|
4877
|
-
background-image:
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
var(--
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
var(--lyplus-text-primary, #fff) 100%,
|
|
4887
|
-
#0000 100%
|
|
4888
|
-
);
|
|
4889
|
-
background-size:
|
|
4890
|
-
0.5em 100%,
|
|
4891
|
-
0% 100%;
|
|
4892
|
-
background-position:
|
|
4893
|
-
-0.5em 0%,
|
|
4894
|
-
-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;
|
|
4895
5412
|
}
|
|
4896
5413
|
|
|
4897
5414
|
.lyrics-line.active:not(.lyrics-gap) .lyrics-syllable.highlight.rtl-text,
|
|
4898
5415
|
.lyrics-line.active:not(.lyrics-gap)
|
|
4899
5416
|
.lyrics-syllable.pre-highlight.rtl-text {
|
|
4900
5417
|
direction: rtl;
|
|
4901
|
-
background-image:
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
transparent 100%
|
|
4911
|
-
);
|
|
4912
|
-
background-position:
|
|
4913
|
-
calc(100% + 0.5em) 0%,
|
|
4914
|
-
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%;
|
|
4915
5427
|
}
|
|
4916
5428
|
|
|
4917
5429
|
/* Background vocals: muted gray wipe instead of white.
|
|
@@ -4928,18 +5440,13 @@ AmLyrics.styles = i$3 `
|
|
|
4928
5440
|
.lyrics-line.pre-active
|
|
4929
5441
|
.background-vocal-container
|
|
4930
5442
|
.lyrics-syllable.pre-highlight.no-chars {
|
|
4931
|
-
background-image:
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
linear-gradient(
|
|
4939
|
-
90deg,
|
|
4940
|
-
color-mix(in srgb, var(--lyplus-text-primary, #fff) 50%, #888888) 100%,
|
|
4941
|
-
#0000 100%
|
|
4942
|
-
);
|
|
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
|
+
);
|
|
4943
5450
|
}
|
|
4944
5451
|
|
|
4945
5452
|
.lyrics-line.active
|
|
@@ -4954,17 +5461,13 @@ AmLyrics.styles = i$3 `
|
|
|
4954
5461
|
.lyrics-line.pre-active
|
|
4955
5462
|
.background-vocal-container
|
|
4956
5463
|
.lyrics-syllable.pre-highlight.rtl-text {
|
|
4957
|
-
background-image:
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
-90deg,
|
|
4965
|
-
color-mix(in srgb, var(--lyplus-text-primary) 50%, #888888) 100%,
|
|
4966
|
-
transparent 100%
|
|
4967
|
-
);
|
|
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
|
+
);
|
|
4968
5471
|
}
|
|
4969
5472
|
|
|
4970
5473
|
/* Non-growable words float up with a gentle curve */
|
|
@@ -4988,6 +5491,10 @@ AmLyrics.styles = i$3 `
|
|
|
4988
5491
|
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
4989
5492
|
}
|
|
4990
5493
|
|
|
5494
|
+
.lyrics-word.char-drag .lyrics-syllable.cleanup .char {
|
|
5495
|
+
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
5496
|
+
}
|
|
5497
|
+
|
|
4991
5498
|
.lyrics-line.persist-highlight
|
|
4992
5499
|
.lyrics-word.growable
|
|
4993
5500
|
.lyrics-syllable.finished
|
|
@@ -4995,6 +5502,10 @@ AmLyrics.styles = i$3 `
|
|
|
4995
5502
|
.lyrics-line.persist-highlight
|
|
4996
5503
|
.lyrics-word.char-rise
|
|
4997
5504
|
.lyrics-syllable.finished
|
|
5505
|
+
.char,
|
|
5506
|
+
.lyrics-line.persist-highlight
|
|
5507
|
+
.lyrics-word.char-drag
|
|
5508
|
+
.lyrics-syllable.finished
|
|
4998
5509
|
.char {
|
|
4999
5510
|
transform: translate3d(0, var(--char-rise-y, -1.12px), 0);
|
|
5000
5511
|
}
|
|
@@ -5105,70 +5616,56 @@ AmLyrics.styles = i$3 `
|
|
|
5105
5616
|
transform 0.7s ease;
|
|
5106
5617
|
}
|
|
5107
5618
|
|
|
5619
|
+
.lyrics-word.char-drag span.char {
|
|
5620
|
+
transition: color 0.18s;
|
|
5621
|
+
}
|
|
5622
|
+
|
|
5108
5623
|
/* Active char spans: structural only, wipe animation sets gradient */
|
|
5109
5624
|
.lyrics-line.active .lyrics-syllable span.char {
|
|
5110
5625
|
background-clip: text;
|
|
5111
5626
|
-webkit-background-clip: text;
|
|
5112
5627
|
background-repeat: no-repeat;
|
|
5113
|
-
background-image:
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
var(--
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
var(--lyplus-text-primary, #fff) 100%,
|
|
5123
|
-
#0000 100%
|
|
5124
|
-
);
|
|
5125
|
-
background-size:
|
|
5126
|
-
0.5em 100%,
|
|
5127
|
-
0% 100%;
|
|
5128
|
-
background-position:
|
|
5129
|
-
-0.5em 0%,
|
|
5130
|
-
-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;
|
|
5131
5637
|
transition:
|
|
5132
5638
|
transform 0.7s ease,
|
|
5133
5639
|
color 0.18s;
|
|
5134
5640
|
}
|
|
5135
5641
|
|
|
5136
5642
|
.lyrics-line.active .lyrics-syllable span.char.highlight {
|
|
5137
|
-
background-image:
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
background-image:
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
var(--
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
var(--lyplus-text-primary, #fff) 100%,
|
|
5164
|
-
#0000 100%
|
|
5165
|
-
);
|
|
5166
|
-
background-size:
|
|
5167
|
-
0.75em 100%,
|
|
5168
|
-
0% 100%;
|
|
5169
|
-
background-position:
|
|
5170
|
-
-0.85em 0%,
|
|
5171
|
-
-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%;
|
|
5172
5669
|
}
|
|
5173
5670
|
|
|
5174
5671
|
/* ==========================================================================
|
|
@@ -5245,13 +5742,7 @@ AmLyrics.styles = i$3 `
|
|
|
5245
5742
|
color: var(--lyplus-text-primary) !important;
|
|
5246
5743
|
}
|
|
5247
5744
|
|
|
5248
|
-
.lyrics-line.
|
|
5249
|
-
animation: fade-in-line 0.14s ease-out forwards !important;
|
|
5250
|
-
color: var(--lyplus-text-primary) !important;
|
|
5251
|
-
}
|
|
5252
|
-
|
|
5253
|
-
.lyrics-line.active .lyrics-syllable.line-synced span.char,
|
|
5254
|
-
.lyrics-line.pre-active .lyrics-syllable.line-synced span.char {
|
|
5745
|
+
.lyrics-line.active .lyrics-syllable.line-synced span.char {
|
|
5255
5746
|
background-image: none !important;
|
|
5256
5747
|
background-color: var(--lyplus-text-primary) !important;
|
|
5257
5748
|
transition: background-color 120ms ease-out !important;
|
|
@@ -5424,6 +5915,49 @@ AmLyrics.styles = i$3 `
|
|
|
5424
5915
|
gap: 4px;
|
|
5425
5916
|
}
|
|
5426
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
|
+
|
|
5427
5961
|
.control-button {
|
|
5428
5962
|
background: transparent;
|
|
5429
5963
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
@@ -5432,7 +5966,10 @@ AmLyrics.styles = i$3 `
|
|
|
5432
5966
|
font-size: 0.8em;
|
|
5433
5967
|
color: rgba(255, 255, 255, 0.6);
|
|
5434
5968
|
cursor: pointer;
|
|
5435
|
-
transition:
|
|
5969
|
+
transition:
|
|
5970
|
+
color 0.2s,
|
|
5971
|
+
border-color 0.2s,
|
|
5972
|
+
background-color 0.2s;
|
|
5436
5973
|
font-weight: normal;
|
|
5437
5974
|
}
|
|
5438
5975
|
|
|
@@ -5570,137 +6107,125 @@ AmLyrics.styles = i$3 `
|
|
|
5570
6107
|
KEYFRAME ANIMATIONS
|
|
5571
6108
|
========================================================================== */
|
|
5572
6109
|
|
|
6110
|
+
@keyframes source-switch-spin {
|
|
6111
|
+
to {
|
|
6112
|
+
transform: rotate(360deg);
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
|
|
5573
6116
|
/* Wipe animation for syllables */
|
|
5574
6117
|
@keyframes wipe {
|
|
5575
6118
|
from {
|
|
5576
|
-
background-size:
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
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;
|
|
5582
6132
|
}
|
|
5583
6133
|
to {
|
|
5584
|
-
background-size:
|
|
5585
|
-
|
|
5586
|
-
100% 100%;
|
|
5587
|
-
background-position:
|
|
5588
|
-
calc(100% + 0.375em) 0%,
|
|
5589
|
-
left;
|
|
6134
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6135
|
+
background-position: left;
|
|
5590
6136
|
}
|
|
5591
6137
|
}
|
|
5592
6138
|
|
|
5593
6139
|
@keyframes start-wipe {
|
|
5594
6140
|
0% {
|
|
5595
|
-
background-size:
|
|
5596
|
-
|
|
5597
|
-
0% 100%;
|
|
5598
|
-
background-position:
|
|
5599
|
-
-0.75em 0%,
|
|
5600
|
-
-0.375em 0%;
|
|
6141
|
+
background-size: 0% 100%;
|
|
6142
|
+
background-position: left;
|
|
5601
6143
|
}
|
|
5602
6144
|
100% {
|
|
5603
|
-
background-size:
|
|
5604
|
-
|
|
5605
|
-
100% 100%;
|
|
5606
|
-
background-position:
|
|
5607
|
-
calc(100% + 0.375em) 0%,
|
|
5608
|
-
left;
|
|
6145
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6146
|
+
background-position: left;
|
|
5609
6147
|
}
|
|
5610
6148
|
}
|
|
5611
6149
|
|
|
5612
6150
|
@keyframes wipe-rtl {
|
|
5613
6151
|
from {
|
|
5614
|
-
background-size:
|
|
5615
|
-
|
|
5616
|
-
0% 100%;
|
|
5617
|
-
background-position:
|
|
5618
|
-
calc(100% + 0.375em) 0%,
|
|
5619
|
-
calc(100% + 0.36em) 0%;
|
|
6152
|
+
background-size: 0% 100%;
|
|
6153
|
+
background-position: right 0%;
|
|
5620
6154
|
}
|
|
5621
6155
|
to {
|
|
5622
|
-
background-size:
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
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%;
|
|
5628
6169
|
}
|
|
5629
6170
|
}
|
|
5630
6171
|
|
|
5631
6172
|
@keyframes start-wipe-rtl {
|
|
5632
6173
|
0% {
|
|
5633
|
-
background-size:
|
|
5634
|
-
|
|
5635
|
-
0% 100%;
|
|
5636
|
-
background-position:
|
|
5637
|
-
calc(100% + 0.75em) 0%,
|
|
5638
|
-
calc(100% + 0.5em) 0%;
|
|
6174
|
+
background-size: 0% 100%;
|
|
6175
|
+
background-position: right 0%;
|
|
5639
6176
|
}
|
|
5640
6177
|
100% {
|
|
5641
|
-
background-size:
|
|
5642
|
-
|
|
5643
|
-
100% 100%;
|
|
5644
|
-
background-position:
|
|
5645
|
-
-0.75em 0%,
|
|
5646
|
-
right 0%;
|
|
6178
|
+
background-size: calc(100% + var(--wipe-gradient-width, 0.75em)) 100%;
|
|
6179
|
+
background-position: right 0%;
|
|
5647
6180
|
}
|
|
5648
6181
|
}
|
|
5649
6182
|
|
|
5650
6183
|
@keyframes pre-wipe-universal {
|
|
5651
6184
|
from {
|
|
5652
|
-
background-size:
|
|
5653
|
-
|
|
5654
|
-
0% 100%;
|
|
5655
|
-
background-position:
|
|
5656
|
-
-0.75em 0%,
|
|
5657
|
-
left;
|
|
6185
|
+
background-size: 0% 100%;
|
|
6186
|
+
background-position: left;
|
|
5658
6187
|
}
|
|
5659
6188
|
to {
|
|
5660
|
-
background-size:
|
|
5661
|
-
|
|
5662
|
-
0% 100%;
|
|
5663
|
-
background-position:
|
|
5664
|
-
-0.375em 0%,
|
|
5665
|
-
left;
|
|
6189
|
+
background-size: var(--wipe-gradient-width, 0.75em) 100%;
|
|
6190
|
+
background-position: left;
|
|
5666
6191
|
}
|
|
5667
6192
|
}
|
|
5668
6193
|
|
|
5669
6194
|
@keyframes pre-wipe-universal-rtl {
|
|
5670
6195
|
from {
|
|
5671
|
-
background-size:
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
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%;
|
|
5677
6209
|
}
|
|
5678
6210
|
to {
|
|
5679
|
-
background-size:
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
background-position:
|
|
5683
|
-
calc(100% + 0.375em) 0%,
|
|
5684
|
-
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%;
|
|
5685
6214
|
}
|
|
5686
6215
|
}
|
|
5687
6216
|
|
|
5688
|
-
@keyframes
|
|
6217
|
+
@keyframes wipe-word-from-pre {
|
|
5689
6218
|
from {
|
|
5690
|
-
background-size:
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
background-position:
|
|
5694
|
-
-0.75em 0%,
|
|
5695
|
-
left;
|
|
6219
|
+
background-size: var(--pre-wipe-word-size, var(--wipe-gradient-width))
|
|
6220
|
+
100%;
|
|
6221
|
+
background-position: var(--char-wipe-position, left) 0%;
|
|
5696
6222
|
}
|
|
5697
6223
|
to {
|
|
5698
|
-
background-size:
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
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%;
|
|
5704
6229
|
}
|
|
5705
6230
|
}
|
|
5706
6231
|
|
|
@@ -5794,6 +6319,15 @@ AmLyrics.styles = i$3 `
|
|
|
5794
6319
|
}
|
|
5795
6320
|
}
|
|
5796
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
|
+
|
|
5797
6331
|
@keyframes grow-static {
|
|
5798
6332
|
0%,
|
|
5799
6333
|
100% {
|