@uimaxbai/am-lyrics 1.5.5 → 1.5.6

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/react.js CHANGED
@@ -322,7 +322,7 @@ class GoogleService {
322
322
  }
323
323
  }
324
324
 
325
- const VERSION = '1.5.5';
325
+ const VERSION = '1.5.6';
326
326
  const INSTRUMENTAL_THRESHOLD_MS = 7000; // Show dots for gaps >= 7s
327
327
  const FETCH_TIMEOUT_MS = 8000; // Timeout for all lyrics fetch requests
328
328
  const SEEK_THRESHOLD_MS = 500;
@@ -339,7 +339,7 @@ const LONG_WORD_WIPE_EXTRA_EM = 0.45;
339
339
  const LONG_WORD_WIPE_EXTRA_RATIO = 0.35;
340
340
  const SHORT_WORD_DRAG_MIN_DURATION_MS = 760;
341
341
  const SHORT_WORD_GLOW_MIN_DURATION_MS = 1320;
342
- const WORD_PRE_WIPE_HANDOFF_LEAD_MS = 120;
342
+ const WORD_PRE_WIPE_HANDOFF_LEAD_MS = 100;
343
343
  /**
344
344
  * Fetch with an automatic timeout via AbortSignal.
345
345
  * Rejects if the request takes longer than `timeoutMs`.
@@ -502,9 +502,9 @@ let AmLyrics$1 = class AmLyrics extends i {
502
502
  this.clearBackgroundExpandedLine();
503
503
  // Stop all running animations and clear highlights immediately
504
504
  if (this.lyricsContainer) {
505
- const activeLines = this.lyricsContainer.querySelectorAll('.lyrics-line.active, .lyrics-line.pre-active, .lyrics-line.bg-expanded');
505
+ const activeLines = this.lyricsContainer.querySelectorAll('.lyrics-line.active, .lyrics-line.pre-active, .lyrics-line.bg-expanded, .lyrics-line.scroll-exiting');
506
506
  activeLines.forEach(line => {
507
- line.classList.remove('active', 'pre-active', 'bg-expanded');
507
+ line.classList.remove('active', 'pre-active', 'bg-expanded', 'scroll-exiting');
508
508
  AmLyrics.resetSyllables(line);
509
509
  });
510
510
  const activeGaps = this.lyricsContainer.querySelectorAll('.lyrics-gap.active, .lyrics-gap.gap-exiting');
@@ -1956,7 +1956,7 @@ let AmLyrics$1 = class AmLyrics extends i {
1956
1956
  else {
1957
1957
  AmLyrics.finishSyllablesUpToTime(lineElement, newTime);
1958
1958
  }
1959
- lineElement.classList.remove('active', 'bg-expanded');
1959
+ lineElement.classList.remove('active', 'bg-expanded', 'scroll-exiting');
1960
1960
  if (lineElement.classList.contains('pre-active')) {
1961
1961
  lineElement.classList.remove('pre-active');
1962
1962
  }
@@ -1973,7 +1973,7 @@ let AmLyrics$1 = class AmLyrics extends i {
1973
1973
  const lineElement = this._getLineElement(lineIndex);
1974
1974
  if (lineElement) {
1975
1975
  lineElement.classList.add('active');
1976
- lineElement.classList.remove('pre-active');
1976
+ lineElement.classList.remove('pre-active', 'scroll-exiting');
1977
1977
  const preIdx = this.preActiveLineElements.indexOf(lineElement);
1978
1978
  if (preIdx !== -1)
1979
1979
  this.preActiveLineElements.splice(preIdx, 1);
@@ -2178,7 +2178,11 @@ let AmLyrics$1 = class AmLyrics extends i {
2178
2178
  }
2179
2179
  }
2180
2180
  updated(changedProperties) {
2181
- if (changedProperties.has('lyrics')) {
2181
+ const lyricsDomBecameRenderable = changedProperties.has('lyrics') ||
2182
+ (changedProperties.has('isLoading') &&
2183
+ !this.isLoading &&
2184
+ Boolean(this.lyrics));
2185
+ if (lyricsDomBecameRenderable) {
2182
2186
  this._invalidateCaches();
2183
2187
  this._ensureLineDataCache();
2184
2188
  this._updateCachedIsUnsynced();
@@ -2337,7 +2341,9 @@ let AmLyrics$1 = class AmLyrics extends i {
2337
2341
  return;
2338
2342
  }
2339
2343
  const scrollDuration = scrollLookAheadMs;
2340
- targetElement.style.setProperty('--scroll-duration', `${scrollDuration}ms`);
2344
+ if (targetElement !== this.currentPrimaryActiveLine || forceScroll) {
2345
+ targetElement.style.setProperty('--scroll-duration', `${scrollDuration}ms`);
2346
+ }
2341
2347
  this.setBackgroundExpandedLine(targetElement);
2342
2348
  // Unblur the upcoming target line early while the separate bg-expanded
2343
2349
  // class starts background vocal height/opacity in sync with scroll.
@@ -2562,6 +2568,7 @@ let AmLyrics$1 = class AmLyrics extends i {
2562
2568
  return;
2563
2569
  const computedStyle = getComputedStyle(referenceSyllable);
2564
2570
  const { font } = computedStyle; // Full font string
2571
+ const fontSize = Number.parseFloat(computedStyle.fontSize) || 16;
2565
2572
  const charTimedWords = Array.from(this.shadowRoot.querySelectorAll('.lyrics-word.growable, .lyrics-word.char-rise, .lyrics-word.char-drag'));
2566
2573
  if (charTimedWords.length === 0)
2567
2574
  return;
@@ -2625,6 +2632,13 @@ let AmLyrics$1 = class AmLyrics extends i {
2625
2632
  const hasTimedSyllables = Number.isFinite(virtualWordStart) &&
2626
2633
  Number.isFinite(virtualWordEnd) &&
2627
2634
  virtualWordDuration > 0;
2635
+ const wordVelocityPxPerMs = hasTimedSyllables
2636
+ ? totalWordWidth / virtualWordDuration
2637
+ : 0;
2638
+ const gradientLeadWidthPx = (BASE_WIPE_GRADIENT_EM * Math.max(1, fontSize)) / 2;
2639
+ const measuredPreWipeDuration = wordVelocityPxPerMs > 0
2640
+ ? gradientLeadWidthPx / wordVelocityPxPerMs
2641
+ : 100;
2628
2642
  let cumulativeCharWidth = 0;
2629
2643
  syllableEntries.forEach(entry => {
2630
2644
  let cumulativeSyllableWidth = 0;
@@ -2649,6 +2663,7 @@ let AmLyrics$1 = class AmLyrics extends i {
2649
2663
  const target = span;
2650
2664
  target.dataset.wipeStart = startPercent.toFixed(4);
2651
2665
  target.dataset.wipeDuration = durationPercent.toFixed(4);
2666
+ target.dataset.preWipeDuration = measuredPreWipeDuration.toFixed(2);
2652
2667
  target.style.setProperty('--word-wipe-width', `${totalWordWidth}px`);
2653
2668
  target.style.setProperty('--char-wipe-position', `${-cumulativeCharWidth}px`);
2654
2669
  cumulativeCharWidth += charWidth;
@@ -2813,7 +2828,12 @@ let AmLyrics$1 = class AmLyrics extends i {
2813
2828
  // effectiveEndTimes). Lines stay active until their extended end,
2814
2829
  // so we no longer need to remove .active here.
2815
2830
  this.lastPrimaryActiveLine = this.currentPrimaryActiveLine;
2831
+ if (this.lastPrimaryActiveLine) {
2832
+ this.lastPrimaryActiveLine.style.setProperty('--scroll-duration', `${scrollDuration ?? SCROLL_ANIMATION_DURATION_MS}ms`);
2833
+ this.lastPrimaryActiveLine.classList.add('scroll-exiting');
2834
+ }
2816
2835
  this.currentPrimaryActiveLine = lineElement;
2836
+ this.currentPrimaryActiveLine.classList.remove('scroll-exiting');
2817
2837
  const lineIndex = AmLyrics.getLineIndexFromElement(lineElement);
2818
2838
  if (lineIndex !== null) {
2819
2839
  this.lastActiveIndex = lineIndex;
@@ -3126,7 +3146,7 @@ let AmLyrics$1 = class AmLyrics extends i {
3126
3146
  allLines.forEach(lineEl => {
3127
3147
  AmLyrics.resetSyllables(lineEl);
3128
3148
  // Remove scroll-animate class and properties to stop any scroll animations
3129
- lineEl.classList.remove('scroll-animate');
3149
+ lineEl.classList.remove('scroll-animate', 'scroll-exiting');
3130
3150
  lineEl.style.removeProperty('--scroll-delta');
3131
3151
  lineEl.style.removeProperty('--lyrics-line-delay');
3132
3152
  });
@@ -3349,26 +3369,30 @@ let AmLyrics$1 = class AmLyrics extends i {
3349
3369
  return;
3350
3370
  const duration = Math.min(450, scrollDuration ?? SCROLL_ANIMATION_DURATION_MS);
3351
3371
  const delayIncrement = duration * 0.1;
3372
+ const maxStaggerSteps = 4;
3352
3373
  const lookAhead = 20;
3353
3374
  const len = lineArray.length;
3354
3375
  const start = Math.max(0, referenceIndex - lookAhead);
3355
3376
  const end = Math.min(len, referenceIndex + lookAhead);
3356
3377
  let maxAnimationDuration = 0;
3357
3378
  const newAnimatingLines = [];
3379
+ const lineDelays = new Map();
3358
3380
  const scrollingDown = delta >= 0;
3359
3381
  if (scrollingDown) {
3360
3382
  let delayCounter = 0;
3361
3383
  for (let i = start; i < end; i += 1) {
3362
3384
  const line = lineArray[i];
3363
- const delay = i >= referenceIndex ? delayCounter * delayIncrement : 0;
3385
+ const delay = i >= referenceIndex
3386
+ ? Math.min(delayCounter, maxStaggerSteps) * delayIncrement
3387
+ : 0;
3364
3388
  if (i >= referenceIndex && !line.classList.contains('lyrics-gap')) {
3365
3389
  delayCounter += 1;
3366
3390
  }
3367
3391
  line.style.setProperty('--scroll-delta', `${delta}px`);
3368
3392
  line.style.setProperty('--lyrics-line-delay', `${delay}ms`);
3369
- line.style.setProperty('--scroll-duration', `${duration + 100}ms`);
3393
+ lineDelays.set(line, delay);
3370
3394
  newAnimatingLines.push(line);
3371
- const lineDuration = duration + delay;
3395
+ const lineDuration = duration + 100 + delay;
3372
3396
  if (lineDuration > maxAnimationDuration) {
3373
3397
  maxAnimationDuration = lineDuration;
3374
3398
  }
@@ -3378,20 +3402,28 @@ let AmLyrics$1 = class AmLyrics extends i {
3378
3402
  let delayCounter = 0;
3379
3403
  for (let i = end - 1; i >= start; i -= 1) {
3380
3404
  const line = lineArray[i];
3381
- const delay = i <= referenceIndex ? delayCounter * delayIncrement : 0;
3405
+ const delay = i <= referenceIndex
3406
+ ? Math.min(delayCounter, maxStaggerSteps) * delayIncrement
3407
+ : 0;
3382
3408
  if (i <= referenceIndex && !line.classList.contains('lyrics-gap')) {
3383
3409
  delayCounter += 1;
3384
3410
  }
3385
3411
  line.style.setProperty('--scroll-delta', `${delta}px`);
3386
3412
  line.style.setProperty('--lyrics-line-delay', `${delay}ms`);
3387
- line.style.setProperty('--scroll-duration', `${duration + 100}ms`);
3413
+ lineDelays.set(line, delay);
3388
3414
  newAnimatingLines.push(line);
3389
- const lineDuration = duration + delay;
3415
+ const lineDuration = duration + 100 + delay;
3390
3416
  if (lineDuration > maxAnimationDuration) {
3391
3417
  maxAnimationDuration = lineDuration;
3392
3418
  }
3393
3419
  }
3394
3420
  }
3421
+ /* Preserve the staggered starts, but make every line settle together.
3422
+ This keeps the selected line from drifting after its neighbours. */
3423
+ for (const line of newAnimatingLines) {
3424
+ const delay = lineDelays.get(line) ?? 0;
3425
+ line.style.setProperty('--scroll-duration', `${Math.max(100, maxAnimationDuration - delay)}ms`);
3426
+ }
3395
3427
  // --- Step 3: Force reflow so the browser sees the class removal ---
3396
3428
  // Use offsetHeight which is cheaper than getBoundingClientRect
3397
3429
  // eslint-disable-next-line no-void
@@ -3651,16 +3683,15 @@ let AmLyrics$1 = class AmLyrics extends i {
3651
3683
  preWipeSyllable.style.setProperty('--pre-wipe-duration', `${preWipeDurationMs}ms`);
3652
3684
  preWipeSyllable.style.setProperty('--pre-wipe-delay', `${-elapsedPreWipe}ms`);
3653
3685
  preWipeSyllable.classList.add('pre-highlight');
3654
- let leadSpans = charSpans;
3655
- if (leadSpans.length === 0 && leadChar) {
3656
- leadSpans = [leadChar];
3657
- }
3658
- leadSpans.forEach(span => {
3659
- AmLyrics.applyWipeShape(span, charCount);
3660
- span.style.setProperty('--pre-wipe-duration', `${preWipeDurationMs}ms`);
3661
- span.style.setProperty('--pre-wipe-delay', `${-elapsedPreWipe}ms`);
3662
- span.classList.add('pre-wipe-lead');
3663
- });
3686
+ // Character-animated words still need a single word-leading gradient.
3687
+ // Giving every glyph this state creates one gradient per character and
3688
+ // makes the whole word enter the pre-wipe continuation simultaneously.
3689
+ if (leadChar) {
3690
+ AmLyrics.applyWipeShape(leadChar, charCount);
3691
+ leadChar.style.setProperty('--pre-wipe-duration', `${preWipeDurationMs}ms`);
3692
+ leadChar.style.setProperty('--pre-wipe-delay', `${-elapsedPreWipe}ms`);
3693
+ leadChar.classList.add('pre-wipe-lead');
3694
+ }
3664
3695
  wordElements.forEach(element => {
3665
3696
  const target = element;
3666
3697
  target._wordPreWipeKey = AmLyrics.getWordPreWipeKey(nextSyllable);
@@ -3872,20 +3903,15 @@ let AmLyrics$1 = class AmLyrics extends i {
3872
3903
  (hadPreHighlight && globalCharIndex === 0);
3873
3904
  const charStartMs = charWipeDurationMs * startPct;
3874
3905
  const remainingWordWipeMs = Math.max(0, charWipeDurationMs - charStartMs);
3875
- let wipeDelay = charStartMs - wordElapsedTimeMs;
3876
- let wipeDuration = Math.min(charWipeDurationMs * durationPct * wipeScale, remainingWordWipeMs);
3906
+ const wipeDelay = charStartMs - wordElapsedTimeMs;
3907
+ const wipeDuration = Math.min(charWipeDurationMs * durationPct * wipeScale, remainingWordWipeMs);
3877
3908
  const useStartAnimation = isFirstInContainer && globalCharIndex === 0 && !hadCharPreWipe;
3878
- let charWipeAnimation = 'wipe';
3909
+ let charWipeAnimation = 'char-wipe';
3879
3910
  if (hadCharPreWipe) {
3880
- charWipeAnimation = 'wipe-word-from-pre';
3881
- wipeDelay = -wordElapsedTimeMs;
3882
- wipeDuration = charWipeDurationMs;
3911
+ charWipeAnimation = 'char-wipe';
3883
3912
  }
3884
3913
  else if (useStartAnimation) {
3885
- charWipeAnimation = isRTL ? 'start-wipe-rtl' : 'start-wipe';
3886
- }
3887
- else {
3888
- charWipeAnimation = isRTL ? 'wipe-rtl' : 'wipe';
3914
+ charWipeAnimation = 'char-start-wipe';
3889
3915
  }
3890
3916
  const existingAnimation = charAnimationsMap.get(span) || span.style.animation || '';
3891
3917
  const animationParts = [];
@@ -3895,6 +3921,16 @@ let AmLyrics$1 = class AmLyrics extends i {
3895
3921
  existingAnimation.includes('drag-char'))) {
3896
3922
  animationParts.push(existingAnimation.split(',')[0].trim());
3897
3923
  }
3924
+ if (globalCharIndex > 0 &&
3925
+ !hadCharPreWipe &&
3926
+ wipeDelay > 0 &&
3927
+ wipeDuration > 0) {
3928
+ const measuredDuration = Number.parseFloat(span.dataset.preWipeDuration || '100');
3929
+ const preWipeDuration = Math.min(measuredDuration, wipeDuration * 0.9, charWipeDurationMs * 0.08, wipeDelay);
3930
+ if (preWipeDuration >= 16) {
3931
+ animationParts.push(`char-pre-wipe ${preWipeDuration}ms linear ${wipeDelay - preWipeDuration}ms none`);
3932
+ }
3933
+ }
3898
3934
  if (wipeDuration > 0) {
3899
3935
  const wipeFillMode = hadCharPreWipe ? 'both' : 'forwards';
3900
3936
  animationParts.push(`${charWipeAnimation} ${wipeDuration}ms linear ${wipeDelay}ms ${wipeFillMode}`);
@@ -5198,6 +5234,18 @@ AmLyrics$1.styles = i$3 `
5198
5234
  opacity: 1;
5199
5235
  }
5200
5236
 
5237
+ /* Predictive scrolling begins before the next timestamp. Start dimming
5238
+ the outgoing line at the same moment so it settles with the scroll. */
5239
+ .lyrics-line.scroll-exiting {
5240
+ opacity: 0.8;
5241
+ color: var(--lyplus-text-secondary);
5242
+ transition:
5243
+ opacity var(--scroll-duration, 400ms) cubic-bezier(0.41, 0, 0.12, 0.99),
5244
+ transform var(--scroll-duration, 400ms)
5245
+ cubic-bezier(0.41, 0, 0.12, 0.99) var(--lyrics-line-delay, 0ms),
5246
+ filter var(--scroll-duration, 400ms) ease;
5247
+ }
5248
+
5201
5249
  .lyrics-line.persist-highlight {
5202
5250
  filter: none !important;
5203
5251
  opacity: 1;
@@ -5628,15 +5676,24 @@ AmLyrics$1.styles = i$3 `
5628
5676
  background-clip: text;
5629
5677
  -webkit-background-clip: text;
5630
5678
  background-repeat: no-repeat;
5631
- background-image: linear-gradient(
5632
- 90deg,
5633
- var(--lyplus-text-primary, #fff) 0%,
5634
- var(--lyplus-text-primary, #fff)
5635
- calc(100% - var(--wipe-gradient-width, 0.75em)),
5636
- #0000 100%
5637
- );
5638
- background-size: 0% 100%;
5639
- background-position: left;
5679
+ background-image:
5680
+ linear-gradient(
5681
+ 90deg,
5682
+ #ffffff00 0%,
5683
+ var(--lyplus-text-primary, #fff) 50%,
5684
+ #0000 100%
5685
+ ),
5686
+ linear-gradient(
5687
+ 90deg,
5688
+ var(--lyplus-text-primary, #fff) 100%,
5689
+ #0000 100%
5690
+ );
5691
+ background-size:
5692
+ var(--wipe-gradient-width, 0.75em) 100%,
5693
+ 0% 100%;
5694
+ background-position:
5695
+ calc(-1 * var(--wipe-gradient-width, 0.75em)) 0%,
5696
+ left;
5640
5697
  transition:
5641
5698
  transform 0.7s ease,
5642
5699
  color 0.18s;
@@ -5655,20 +5712,11 @@ AmLyrics$1.styles = i$3 `
5655
5712
  }
5656
5713
 
5657
5714
  .lyrics-line.active .lyrics-syllable span.char.pre-wipe-lead {
5658
- animation-name: pre-wipe-word-char;
5715
+ animation-name: char-pre-wipe;
5659
5716
  animation-duration: var(--pre-wipe-duration);
5660
5717
  animation-delay: var(--pre-wipe-delay);
5661
5718
  animation-timing-function: linear;
5662
5719
  animation-fill-mode: forwards;
5663
- background-image: linear-gradient(
5664
- 90deg,
5665
- var(--lyplus-text-primary, #fff) 0%,
5666
- var(--lyplus-text-primary, #fff)
5667
- calc(100% - var(--wipe-gradient-width, 0.75em)),
5668
- #0000 100%
5669
- );
5670
- background-size: 0% 100%;
5671
- background-position: var(--char-wipe-position, left) 0%;
5672
5720
  }
5673
5721
 
5674
5722
  /* ==========================================================================
@@ -6205,30 +6253,63 @@ AmLyrics$1.styles = i$3 `
6205
6253
  }
6206
6254
  }
6207
6255
 
6208
- @keyframes pre-wipe-word-char {
6256
+ /* Character-rendered words use a separate moving gradient in front of
6257
+ their solid fill. This makes the individual glyph wipes read as one
6258
+ continuous word-level wipe. */
6259
+ @keyframes char-pre-wipe {
6209
6260
  from {
6210
- background-size: 0px 100%;
6211
- background-position: var(--char-wipe-position, left) 0%;
6261
+ background-size:
6262
+ var(--wipe-gradient-width, 0.75em) 100%,
6263
+ 0% 100%;
6264
+ background-position:
6265
+ calc(-1 * var(--wipe-gradient-width, 0.75em)) 0%,
6266
+ left;
6212
6267
  }
6213
6268
  to {
6214
- background-size: var(--pre-wipe-word-size, var(--wipe-gradient-width))
6215
- 100%;
6216
- background-position: var(--char-wipe-position, left) 0%;
6269
+ background-size:
6270
+ var(--wipe-gradient-width, 0.75em) 100%,
6271
+ 0% 100%;
6272
+ background-position:
6273
+ calc(-1 * var(--wipe-gradient-half, 0.375em)) 0%,
6274
+ left;
6217
6275
  }
6218
6276
  }
6219
6277
 
6220
- @keyframes wipe-word-from-pre {
6278
+ @keyframes char-start-wipe {
6221
6279
  from {
6222
- background-size: var(--pre-wipe-word-size, var(--wipe-gradient-width))
6223
- 100%;
6224
- background-position: var(--char-wipe-position, left) 0%;
6280
+ background-size:
6281
+ var(--wipe-gradient-width, 0.75em) 100%,
6282
+ 0% 100%;
6283
+ background-position:
6284
+ calc(-1 * var(--wipe-gradient-width, 0.75em)) 0%,
6285
+ left;
6225
6286
  }
6226
6287
  to {
6227
- background-size: calc(
6228
- var(--word-wipe-width, 100%) + var(--wipe-gradient-width, 0.75em)
6229
- )
6230
- 100%;
6231
- background-position: var(--char-wipe-position, left) 0%;
6288
+ background-size:
6289
+ var(--wipe-gradient-width, 0.75em) 100%,
6290
+ 100% 100%;
6291
+ background-position:
6292
+ calc(100% + var(--wipe-gradient-half, 0.375em)) 0%,
6293
+ left;
6294
+ }
6295
+ }
6296
+
6297
+ @keyframes char-wipe {
6298
+ from {
6299
+ background-size:
6300
+ var(--wipe-gradient-width, 0.75em) 100%,
6301
+ 0% 100%;
6302
+ background-position:
6303
+ calc(-1 * var(--wipe-gradient-half, 0.375em)) 0%,
6304
+ left;
6305
+ }
6306
+ to {
6307
+ background-size:
6308
+ var(--wipe-gradient-width, 0.75em) 100%,
6309
+ 100% 100%;
6310
+ background-position:
6311
+ calc(100% + var(--wipe-gradient-half, 0.375em)) 0%,
6312
+ left;
6232
6313
  }
6233
6314
  }
6234
6315