@braccato/core 1.2.0 → 1.2.1

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.
@@ -19,6 +19,7 @@ export declare const BACKGROUND_LINE_CLASS: "blyrics-background-line";
19
19
  export declare const WORD_GROUP_CLASS: "blyrics-word-group";
20
20
  export declare const LONG_WORD_GROUP_CLASS: "blyrics-word-group-long";
21
21
  export declare const WORD_HIGHLIGHT_CLASS: "blyrics-word-highlight";
22
+ export declare const HIGHLIGHT_RUN_CLASS: "blyrics-highlight-run";
22
23
  export declare const LINE_SYNCED_WORD_CLASS: "blyrics-line-synced-word";
23
24
  export declare const BIDI_RUN_CLASS: "blyrics-bidi-run";
24
25
  export declare const BIDI_SENSITIVE_CLASS: "blyrics-bidi-sensitive";
package/dist/constants.js CHANGED
@@ -25,6 +25,7 @@ export const BACKGROUND_LINE_CLASS = "blyrics-background-line";
25
25
  export const WORD_GROUP_CLASS = "blyrics-word-group";
26
26
  export const LONG_WORD_GROUP_CLASS = "blyrics-word-group-long";
27
27
  export const WORD_HIGHLIGHT_CLASS = "blyrics-word-highlight";
28
+ export const HIGHLIGHT_RUN_CLASS = "blyrics-highlight-run";
28
29
  export const LINE_SYNCED_WORD_CLASS = "blyrics-line-synced-word";
29
30
  export const BIDI_RUN_CLASS = "blyrics-bidi-run";
30
31
  export const BIDI_SENSITIVE_CLASS = "blyrics-bidi-sensitive";
package/dist/engine.js CHANGED
@@ -302,10 +302,14 @@ function resetLineAnimationState(lineData) {
302
302
  resetLineAnimations(lineData);
303
303
  markLineAnimationsStopped(lineData);
304
304
  }
305
+ function togglePartClass(part, className, force) {
306
+ part.lyricElement.classList.toggle(className, force);
307
+ part.highlightElement?.classList.toggle(className, force);
308
+ }
305
309
  function setAnimationsPlayState(lineData, isPlaying) {
306
310
  const children = [lineData, ...lineData.parts];
307
311
  for (const part of children) {
308
- part.lyricElement.classList.toggle(PAUSED_CLASS, !isPlaying);
312
+ togglePartClass(part, PAUSED_CLASS, !isPlaying);
309
313
  for (const animation of part.animations) {
310
314
  if (isPlaying) {
311
315
  animation.play();
@@ -319,11 +323,10 @@ function setAnimationsPlayState(lineData, isPlaying) {
319
323
  function clearLineStateClasses(lineData) {
320
324
  lineData.lyricElement.classList.remove(ANIMATING_CLASS);
321
325
  for (const part of [lineData, ...lineData.parts]) {
322
- part.lyricElement.classList.remove(PAUSED_CLASS);
326
+ togglePartClass(part, PAUSED_CLASS, false);
323
327
  }
324
328
  }
325
329
  const LINE_SYNCED_WORD_CLASS = "blyrics-line-synced-word";
326
- const WORD_HIGHLIGHT_SELECTOR = ".blyrics-word-highlight";
327
330
  const INSTRUMENTAL_FILL_SELECTOR = ".blyrics--instrumental-fill";
328
331
  const INSTRUMENTAL_WAVE_CLIP_SELECTOR = ".blyrics--wave-clip";
329
332
  const INSTRUMENTAL_WAVE_PATH_SELECTOR = ".blyrics--wave-path";
@@ -610,13 +613,6 @@ function activeTextInstantKeyframes(config) {
610
613
  },
611
614
  ];
612
615
  }
613
- function highlightTarget(part) {
614
- const highlight = part.lyricElement.querySelector(WORD_HIGHLIGHT_SELECTOR);
615
- if (highlight) {
616
- return { element: highlight, options: {} };
617
- }
618
- return { element: part.lyricElement, options: { pseudoElement: "::after" } };
619
- }
620
616
  function lineSyncedTextKeyframes(config) {
621
617
  return [
622
618
  {
@@ -649,33 +645,30 @@ function fadeOutTextKeyframes(config) {
649
645
  }
650
646
  function startRichSyncedHighlightAnimations(engine, part, config, swipeTimeMs, wordTimeMs, swipeDurationMs, glowDurationMs, appliedTimingOffsetMs) {
651
647
  const animations = [];
652
- const target = highlightTarget(part);
648
+ const highlight = part.highlightElement;
653
649
  let swipeAnimation;
654
650
  if (config.enabled.highlightSwipe) {
655
- swipeAnimation = trackLyricAnimationTiming(engine, target.element.animate(activeTextGradientKeyframes(config), {
651
+ swipeAnimation = trackLyricAnimationTiming(engine, highlight.animate(activeTextGradientKeyframes(config), {
656
652
  duration: swipeDurationMs,
657
653
  easing: config.highlight.swipeEasing,
658
654
  fill: "forwards",
659
- ...target.options,
660
655
  }), { appliedTimingOffsetMs, offsetMs: swipeTimeMs - wordTimeMs });
661
656
  swipeAnimation.currentTime = correctedAnimationTimeMs(swipeTimeMs, appliedTimingOffsetMs, swipeDurationMs);
662
657
  animations.push(swipeAnimation);
663
658
  }
664
- const opacityAnimation = trackLyricAnimationTiming(engine, target.element.animate(config.enabled.highlightSwipe ? activeTextVisibleKeyframes() : activeTextInstantKeyframes(config), {
659
+ const opacityAnimation = trackLyricAnimationTiming(engine, highlight.animate(config.enabled.highlightSwipe ? activeTextVisibleKeyframes() : activeTextInstantKeyframes(config), {
665
660
  duration: 1,
666
661
  easing: "linear",
667
662
  fill: "forwards",
668
- ...target.options,
669
663
  }), { appliedTimingOffsetMs, offsetMs: 0 });
670
664
  opacityAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, 1);
671
665
  animations.push(opacityAnimation);
672
666
  let glowAnimation;
673
667
  if (config.enabled.highlightGlow) {
674
- glowAnimation = trackLyricAnimationTiming(engine, target.element.animate(activeTextGlowKeyframes(config), {
668
+ glowAnimation = trackLyricAnimationTiming(engine, highlight.animate(activeTextGlowKeyframes(config), {
675
669
  duration: glowDurationMs,
676
670
  easing: config.highlight.glowEasing,
677
671
  fill: "forwards",
678
- ...target.options,
679
672
  }), { appliedTimingOffsetMs, offsetMs: 0 });
680
673
  glowAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, glowDurationMs);
681
674
  animations.push(glowAnimation);
@@ -685,22 +678,20 @@ function startRichSyncedHighlightAnimations(engine, part, config, swipeTimeMs, w
685
678
  function startLineSyncedHighlightAnimations(engine, part, config, wordTimeMs, glowDurationMs, appliedTimingOffsetMs) {
686
679
  const animations = [];
687
680
  const fadeInDuration = config.enabled.highlightFade ? config.highlight.fadeInDurationMs : 1;
688
- const target = highlightTarget(part);
689
- const opacityAnimation = trackLyricAnimationTiming(engine, target.element.animate(lineSyncedTextKeyframes(config), {
681
+ const highlight = part.highlightElement;
682
+ const opacityAnimation = trackLyricAnimationTiming(engine, highlight.animate(lineSyncedTextKeyframes(config), {
690
683
  duration: fadeInDuration,
691
684
  easing: config.enabled.highlightFade ? config.highlight.fadeInEasing : "linear",
692
685
  fill: "forwards",
693
- ...target.options,
694
686
  }), { appliedTimingOffsetMs, offsetMs: 0 });
695
687
  opacityAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, fadeInDuration);
696
688
  animations.push(opacityAnimation);
697
689
  let glowAnimation;
698
690
  if (config.enabled.highlightGlow) {
699
- glowAnimation = trackLyricAnimationTiming(engine, target.element.animate(activeTextGlowKeyframes(config), {
691
+ glowAnimation = trackLyricAnimationTiming(engine, highlight.animate(activeTextGlowKeyframes(config), {
700
692
  duration: glowDurationMs,
701
693
  easing: config.highlight.glowEasing,
702
694
  fill: "forwards",
703
- ...target.options,
704
695
  }), { appliedTimingOffsetMs, offsetMs: 0 });
705
696
  glowAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, glowDurationMs);
706
697
  animations.push(glowAnimation);
@@ -753,8 +744,9 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
753
744
  const highlightAnimations = isLineSyncedWord
754
745
  ? startLineSyncedHighlightAnimations(engine, part, config, wordTimeMs, config.highlight.glowMinDurationMs, appliedTimingOffsetMs)
755
746
  : startRichSyncedHighlightAnimations(engine, part, config, swipeTimeMs, wordTimeMs, swipeDurationMs, glowDurationMs, appliedTimingOffsetMs);
756
- const wobbleAnimation = config.enabled.wordWobble
757
- ? trackLyricAnimationTiming(engine, part.lyricElement.animate([
747
+ const wobbleAnimations = [];
748
+ if (config.enabled.wordWobble) {
749
+ const wobbleKeyframes = [
758
750
  { transform: config.word.wobbleFrom },
759
751
  {
760
752
  transform: config.word.wobblePeak,
@@ -769,18 +761,25 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
769
761
  offset: Math.max(config.word.wobblePeakOffset, config.word.wobbleSettleOffset),
770
762
  },
771
763
  { transform: config.word.wobbleTo, easing: config.word.wobbleEndEasing },
772
- ], {
764
+ ];
765
+ const wobbleOptions = {
773
766
  duration: config.word.wobbleDurationMs,
774
767
  easing: config.word.wobbleEasing,
775
768
  fill: "forwards",
776
- }), { appliedTimingOffsetMs, offsetMs: 0 })
777
- : null;
778
- if (wobbleAnimation) {
779
- wobbleAnimation.currentTime = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, config.word.wobbleDurationMs);
769
+ };
770
+ const wobbleStartMs = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, config.word.wobbleDurationMs);
771
+ // The wobble is a paint transform, so the highlight copy must carry it too or the active
772
+ // sweep drifts off the word.
773
+ for (const wordElement of [part.lyricElement, part.highlightElement]) {
774
+ const animation = trackLyricAnimationTiming(engine, wordElement.animate(wobbleKeyframes, wobbleOptions), {
775
+ appliedTimingOffsetMs,
776
+ offsetMs: 0,
777
+ });
778
+ animation.currentTime = wobbleStartMs;
779
+ wobbleAnimations.push(animation);
780
+ }
780
781
  }
781
- part.animations = wobbleAnimation
782
- ? [...highlightAnimations.animations, wobbleAnimation]
783
- : highlightAnimations.animations;
782
+ part.animations = [...highlightAnimations.animations, ...wobbleAnimations];
784
783
  }
785
784
  function startLineAnimations(engine, lineData, config, currentTime) {
786
785
  const appliedTimingOffsetMs = engine.learnedAnimationTimingOffsetMs;
@@ -796,12 +795,10 @@ function startLineAnimations(engine, lineData, config, currentTime) {
796
795
  function startWordExitAnimation(part, config) {
797
796
  resetPartAnimations(part);
798
797
  const fadeDuration = config.enabled.highlightFade ? config.highlight.fadeOutDurationMs : 1;
799
- const target = highlightTarget(part);
800
- const animation = target.element.animate(fadeOutTextKeyframes(config), {
798
+ const animation = part.highlightElement.animate(fadeOutTextKeyframes(config), {
801
799
  duration: fadeDuration,
802
800
  easing: config.enabled.highlightFade ? config.highlight.fadeOutEasing : "linear",
803
801
  fill: "none",
804
- ...target.options,
805
802
  });
806
803
  part.animations = [animation];
807
804
  animation.addEventListener("finish", () => {
package/dist/inject.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface PartData {
14
14
  duration: number;
15
15
  lyricElement: HTMLElement;
16
16
  animations: Animation[];
17
+ highlightElement?: HTMLElement;
17
18
  }
18
19
  export type LineData = {
19
20
  parts: PartData[];
package/dist/inject.js CHANGED
@@ -11,7 +11,7 @@
11
11
  // animates is one word, so every part is split on whitespace with its timing pro-rated across the
12
12
  // split by character count. A line that arrives with no timed parts at all is rebuilt the same way
13
13
  // into zero duration words, so line synced lyrics reach the DOM the sweep already knows.
14
- import { BACKGROUND_LINE_CLASS, BACKGROUND_LYRIC_CLASS, BIDI_RUN_CLASS, BIDI_SENSITIVE_CLASS, CONTENT_LINE_CLASS, EXPLICIT_WORD_CLASS, LINE_MAIN_CLASS, LINE_SYNCED_WORD_CLASS, LONG_WORD_GROUP_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, WORD_CLASS, WORD_GROUP_CLASS, WORD_HIGHLIGHT_CLASS, ZERO_DURATION_ANIMATION_CLASS, } from "./constants.js";
14
+ import { BACKGROUND_LINE_CLASS, BACKGROUND_LYRIC_CLASS, BIDI_RUN_CLASS, BIDI_SENSITIVE_CLASS, CONTENT_LINE_CLASS, EXPLICIT_WORD_CLASS, HIGHLIGHT_RUN_CLASS, LINE_MAIN_CLASS, LINE_SYNCED_WORD_CLASS, LONG_WORD_GROUP_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, WORD_CLASS, WORD_GROUP_CLASS, WORD_HIGHLIGHT_CLASS, ZERO_DURATION_ANIMATION_CLASS, } from "./constants.js";
15
15
  import { getSeekTimeFromClick } from "./seek.js";
16
16
  import { testRtl } from "./text.js";
17
17
  import { registerThemeSetting } from "./themeSettings.js";
@@ -60,11 +60,12 @@ export function deriveSyncType(lyrics) {
60
60
  return "richsync";
61
61
  return lyrics.every(item => item.startTimeMs === 0) ? "none" : "synced";
62
62
  }
63
- function newPartData(part, span) {
63
+ function newPartData(part, span, highlight) {
64
64
  return {
65
65
  time: part.startTimeMs / 1000,
66
66
  duration: part.durationMs / 1000,
67
67
  lyricElement: span,
68
+ highlightElement: highlight,
68
69
  animations: [],
69
70
  };
70
71
  }
@@ -193,67 +194,57 @@ function appendLongWordBreaks(doc, span, text, threshold) {
193
194
  }
194
195
  return true;
195
196
  }
196
- function cloneTextWithBreaks(doc, source) {
197
- const fragment = doc.createDocumentFragment();
198
- for (const node of source.childNodes) {
199
- fragment.appendChild(node.cloneNode(true));
200
- }
201
- return fragment;
202
- }
203
197
  function createTimedWordSpan(doc, part, wrapThreshold) {
204
198
  const span = doc.createElement("span");
205
- span.classList.add(WORD_CLASS);
206
- span.dir = "auto";
207
- if (part.durationMs === 0) {
208
- span.classList.add(ZERO_DURATION_ANIMATION_CLASS);
209
- span.classList.add(LINE_SYNCED_WORD_CLASS);
210
- }
211
- if (testRtl(part.words)) {
212
- span.classList.add(RTL_CLASS);
213
- }
214
- if (part.durationMs > longWordThreshold.getNumberValue()) {
215
- span.dataset.longWord = "true";
216
- }
217
- if (part.isBackground) {
218
- span.classList.add(BACKGROUND_LYRIC_CLASS);
219
- }
220
- if (part.explicit) {
221
- span.classList.add(EXPLICIT_WORD_CLASS);
222
- }
223
- const hasBreaks = appendLongWordBreaks(doc, span, part.words, wrapThreshold);
224
- if (hasBreaks) {
225
- const highlight = doc.createElement("span");
226
- highlight.classList.add(WORD_HIGHLIGHT_CLASS);
227
- highlight.setAttribute("aria-hidden", "true");
228
- highlight.appendChild(cloneTextWithBreaks(doc, span));
229
- span.appendChild(highlight);
199
+ const highlight = doc.createElement("span");
200
+ highlight.classList.add(WORD_HIGHLIGHT_CLASS);
201
+ for (const wordElement of [span, highlight]) {
202
+ wordElement.classList.add(WORD_CLASS);
203
+ wordElement.dir = "auto";
204
+ if (part.durationMs === 0) {
205
+ wordElement.classList.add(ZERO_DURATION_ANIMATION_CLASS);
206
+ wordElement.classList.add(LINE_SYNCED_WORD_CLASS);
207
+ }
208
+ if (testRtl(part.words))
209
+ wordElement.classList.add(RTL_CLASS);
210
+ if (part.durationMs > longWordThreshold.getNumberValue())
211
+ wordElement.dataset.longWord = "true";
212
+ if (part.isBackground)
213
+ wordElement.classList.add(BACKGROUND_LYRIC_CLASS);
214
+ if (part.explicit)
215
+ wordElement.classList.add(EXPLICIT_WORD_CLASS);
216
+ appendLongWordBreaks(doc, wordElement, part.words, wrapThreshold);
217
+ wordElement.dataset.time = String(part.startTimeMs / 1000);
218
+ wordElement.dataset.duration = String(part.durationMs / 1000);
219
+ wordElement.dataset.content = part.words;
220
+ wordElement.style.setProperty("--blyrics-duration", part.durationMs + "ms");
230
221
  }
231
- span.dataset.time = String(part.startTimeMs / 1000);
232
- span.dataset.duration = String(part.durationMs / 1000);
233
- span.dataset.content = part.words;
234
- span.style.setProperty("--blyrics-duration", part.durationMs + "ms");
235
- return span;
222
+ return { span, highlight };
236
223
  }
237
224
  function createWordGroup(doc, group, lineData) {
238
225
  const wrapThreshold = Math.max(1, longWordWrapThreshold.getNumberValue());
239
- const groupElement = doc.createElement("span");
240
- groupElement.classList.add(WORD_GROUP_CLASS);
241
- groupElement.dir = "auto";
242
- groupElement.dataset.content = group.text;
243
- if (group.text.length > wrapThreshold * 2) {
244
- groupElement.classList.add(LONG_WORD_GROUP_CLASS);
245
- }
246
- if (group.isBackground) {
247
- groupElement.classList.add(BACKGROUND_LYRIC_CLASS);
226
+ const lyricGroup = doc.createElement("span");
227
+ const highlightGroup = doc.createElement("span");
228
+ for (const groupElement of [lyricGroup, highlightGroup]) {
229
+ groupElement.classList.add(WORD_GROUP_CLASS);
230
+ groupElement.dir = "auto";
231
+ groupElement.dataset.content = group.text;
232
+ if (group.text.length > wrapThreshold * 2) {
233
+ groupElement.classList.add(LONG_WORD_GROUP_CLASS);
234
+ }
235
+ if (group.isBackground) {
236
+ groupElement.classList.add(BACKGROUND_LYRIC_CLASS);
237
+ }
248
238
  }
249
239
  for (const token of group.tokens) {
250
240
  if (token.kind === "space")
251
241
  continue;
252
- const span = createTimedWordSpan(doc, token.part, wrapThreshold);
253
- lineData.parts.push(newPartData(token.part, span));
254
- groupElement.appendChild(span);
242
+ const { span, highlight } = createTimedWordSpan(doc, token.part, wrapThreshold);
243
+ lineData.parts.push(newPartData(token.part, span, highlight));
244
+ lyricGroup.appendChild(span);
245
+ highlightGroup.appendChild(highlight);
255
246
  }
256
- return groupElement;
247
+ return { lyricGroup, highlightGroup };
257
248
  }
258
249
  function createContentLine(doc, className, text) {
259
250
  const line = doc.createElement("div");
@@ -268,6 +259,12 @@ function createBidiRun(doc, text) {
268
259
  applyDirection(run, text);
269
260
  return run;
270
261
  }
262
+ function createHighlightRun(doc, text) {
263
+ const run = createBidiRun(doc, text);
264
+ run.classList.add(HIGHLIGHT_RUN_CLASS);
265
+ run.setAttribute("aria-hidden", "true");
266
+ return run;
267
+ }
271
268
  export function createLyricsLine(doc, parts, line, lyricElement, options = { splitBackgroundLine: true }) {
272
269
  const lineText = parts.map(part => part.words).join("");
273
270
  const mainText = options.splitBackgroundLine
@@ -282,13 +279,17 @@ export function createLyricsLine(doc, parts, line, lyricElement, options = { spl
282
279
  .join("");
283
280
  const main = createContentLine(doc, LINE_MAIN_CLASS, mainText);
284
281
  const mainRun = createBidiRun(doc, mainText);
282
+ const mainHighlightRun = createHighlightRun(doc, mainText);
285
283
  const groupedTokens = groupTokensByWord(normalizeParts(parts));
286
284
  const backgroundLine = createContentLine(doc, BACKGROUND_LINE_CLASS, backgroundText);
287
285
  const backgroundRun = createBidiRun(doc, backgroundText);
286
+ const backgroundHighlightRun = createHighlightRun(doc, backgroundText);
288
287
  let hasBackground = false;
289
288
  let pendingForegroundSpace = "";
290
289
  let pendingBackgroundSpace = "";
290
+ main.appendChild(mainHighlightRun);
291
291
  main.appendChild(mainRun);
292
+ backgroundLine.appendChild(backgroundHighlightRun);
292
293
  backgroundLine.appendChild(backgroundRun);
293
294
  for (const item of groupedTokens) {
294
295
  if ("kind" in item) {
@@ -299,11 +300,15 @@ export function createLyricsLine(doc, parts, line, lyricElement, options = { spl
299
300
  else {
300
301
  const shouldUseBackgroundLine = options.splitBackgroundLine && item.isBackground;
301
302
  const target = shouldUseBackgroundLine ? backgroundRun : mainRun;
303
+ const highlightRun = shouldUseBackgroundLine ? backgroundHighlightRun : mainHighlightRun;
302
304
  const pendingSpace = shouldUseBackgroundLine ? pendingBackgroundSpace : pendingForegroundSpace;
303
305
  if (target.childNodes.length > 0 && pendingSpace.length > 0) {
304
306
  target.appendChild(doc.createTextNode(pendingSpace));
307
+ highlightRun.appendChild(doc.createTextNode(pendingSpace));
305
308
  }
306
- target.appendChild(createWordGroup(doc, item, line));
309
+ const { lyricGroup, highlightGroup } = createWordGroup(doc, item, line);
310
+ target.appendChild(lyricGroup);
311
+ highlightRun.appendChild(highlightGroup);
307
312
  if (shouldUseBackgroundLine) {
308
313
  hasBackground = true;
309
314
  pendingBackgroundSpace = "";
@@ -38,6 +38,7 @@
38
38
  display: block;
39
39
  max-width: 100%;
40
40
  overflow-wrap: normal;
41
+ position: relative;
41
42
  text-align: inherit;
42
43
  unicode-bidi: plaintext;
43
44
  white-space: normal;
@@ -47,18 +48,28 @@
47
48
  display: block;
48
49
  margin-top: 0.1em;
49
50
  max-width: 100%;
51
+ position: relative;
50
52
  text-align: inherit;
51
53
  unicode-bidi: plaintext;
52
54
  white-space: normal;
53
55
  }
54
56
 
55
57
  .blyrics-bidi-run {
56
- display: inline;
58
+ display: block;
57
59
  max-width: 100%;
58
60
  unicode-bidi: normal;
59
61
  white-space: inherit;
60
62
  }
61
63
 
64
+ .blyrics-highlight-run {
65
+ inset: 0 0 auto;
66
+ pointer-events: none;
67
+ position: absolute;
68
+ user-select: none;
69
+ width: auto;
70
+ z-index: 1;
71
+ }
72
+
62
73
  .blyrics-word-group {
63
74
  display: inline-block;
64
75
  max-width: 100%;
@@ -116,68 +127,33 @@
116
127
  initial-value: -0.1;
117
128
  }
118
129
 
119
- .blyrics--word::after,
120
- .blyrics-word-highlight {
130
+ .blyrics--word.blyrics-word-highlight {
121
131
  color: transparent;
122
132
  background-image: linear-gradient(
123
133
  90deg,
124
134
  var(--blyrics-lyric-active-color)
125
- calc(
126
- 100% * var(--lyric-transition-amount-start) - 4rem *
127
- var(--lyric-transition-amount-start) + 2rem
128
- ),
135
+ calc(100% * var(--lyric-transition-amount-start)),
129
136
  #00000000
130
- calc(
131
- 100% * var(--lyric-transition-amount-end) - 4rem *
132
- var(--lyric-transition-amount-end) + 2rem + 1px
133
- )
137
+ calc(100% * var(--lyric-transition-amount-end) + 1px)
134
138
  );
135
139
  background-clip: text;
136
- box-sizing: content-box;
137
140
  filter: drop-shadow(0 0 0 var(--blyrics-glow-color));
138
- left: -2rem;
139
141
  opacity: 0;
140
- padding: 2rem;
141
142
  pointer-events: none;
142
- position: absolute;
143
- top: -2rem;
144
- white-space: inherit;
145
- width: 100%;
146
143
  --lyric-transition-amount-start: var(
147
144
  --blyrics-highlight-swipe-start-from,
148
145
  -0.2
149
146
  );
150
147
  --lyric-transition-amount-end: var(--blyrics-highlight-swipe-end-from, -0.1);
151
148
  }
152
-
153
- .blyrics--word::after {
154
- content: attr(data-content);
155
- }
156
-
157
- .blyrics-word-highlight {
158
- user-select: none;
159
- }
160
-
161
- .blyrics--word:has(.blyrics-word-highlight)::after {
162
- content: none;
163
- }
164
-
165
- .blyrics-rtl.blyrics--word::after,
166
- .blyrics--word.blyrics-rtl::after,
167
- .blyrics-rtl .blyrics-word-highlight,
168
- .blyrics--word.blyrics-rtl .blyrics-word-highlight {
149
+ .blyrics-word-highlight.blyrics-rtl,
150
+ .blyrics-rtl .blyrics-word-highlight {
169
151
  background-image: linear-gradient(
170
152
  270deg,
171
153
  var(--blyrics-lyric-active-color)
172
- calc(
173
- 100% * var(--lyric-transition-amount-start) - 4rem *
174
- var(--lyric-transition-amount-start) + 2rem
175
- ),
154
+ calc(100% * var(--lyric-transition-amount-start)),
176
155
  #00000000
177
- calc(
178
- 100% * var(--lyric-transition-amount-end) - 4rem *
179
- var(--lyric-transition-amount-end) + 2rem + 1px
180
- )
156
+ calc(100% * var(--lyric-transition-amount-end) + 1px)
181
157
  );
182
158
  }
183
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braccato/core",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Synchronized lyrics renderer with word-by-word animations",
5
5
  "type": "module",
6
6
  "license": "MIT",