@braccato/core 1.4.0 → 1.5.0
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/README.md +27 -0
- package/dist/README.md +27 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/engine.d.ts +8 -0
- package/dist/engine.js +42 -5
- package/dist/inject.d.ts +2 -0
- package/dist/inject.js +34 -7
- package/dist/styles/lyrics.css +21 -2
- package/dist/styles/variables.css +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -186,6 +186,33 @@ frequency. Both must use the same commands in the same order with the same numbe
|
|
|
186
186
|
browser only interpolates two paths smoothly when their command sequences match, and a mismatched
|
|
187
187
|
pair snaps at the halfway point instead of flowing.
|
|
188
188
|
|
|
189
|
+
### Letter wave (experimental)
|
|
190
|
+
|
|
191
|
+
On by default; a theme opts out with `/* blyrics-letter-wave = false; */`. It splits every word into
|
|
192
|
+
per-letter spans and, as the word is sung, floats each letter up and eases it most of the way back on
|
|
193
|
+
a small stagger, so a wave travels through the word. It layers on top of the word wobble rather than
|
|
194
|
+
replacing it: the word keeps whatever `--blyrics-word-wobble-*` does and the letters ride on top, so
|
|
195
|
+
it composes with the default `scaleX` pop and reduces to just the letters when a theme sets its wobble
|
|
196
|
+
to identity. It follows `--blyrics-animate-word-wobble`, so reduced motion turns it off with the rest.
|
|
197
|
+
|
|
198
|
+
A word held past `blyrics-long-word-threshold` (the same `data-long-word` the glow keys off) also
|
|
199
|
+
swells each letter with a transient scale at the crest.
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
.blyrics-container {
|
|
203
|
+
--blyrics-letter-wave-transform: translateY(-0.05em); /* crest lift */
|
|
204
|
+
--blyrics-letter-wave-settle: translateY(-0.02em); /* rest the crest eases back to */
|
|
205
|
+
--blyrics-letter-wave-emphasis-scale: 1.11; /* long-word letter swell, 1 turns it off */
|
|
206
|
+
--blyrics-letter-wave-duration: 0.9s;
|
|
207
|
+
--blyrics-letter-wave-rise-easing: ease-in-out;
|
|
208
|
+
--blyrics-letter-wave-fall-easing: ease-out;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The split multiplies the DOM per character and reruns the karaoke sweep per letter, so a theme that
|
|
213
|
+
does not want the cost turns it off with `/* blyrics-letter-wave = false; */`. `blyrics-letter-wave`
|
|
214
|
+
reloads the lines when it changes, the way every build-time setting does.
|
|
215
|
+
|
|
189
216
|
### Class names
|
|
190
217
|
|
|
191
218
|
These are published API rather than implementation. Renaming one costs a migration rather than a
|
package/dist/README.md
CHANGED
|
@@ -186,6 +186,33 @@ frequency. Both must use the same commands in the same order with the same numbe
|
|
|
186
186
|
browser only interpolates two paths smoothly when their command sequences match, and a mismatched
|
|
187
187
|
pair snaps at the halfway point instead of flowing.
|
|
188
188
|
|
|
189
|
+
### Letter wave (experimental)
|
|
190
|
+
|
|
191
|
+
On by default; a theme opts out with `/* blyrics-letter-wave = false; */`. It splits every word into
|
|
192
|
+
per-letter spans and, as the word is sung, floats each letter up and eases it most of the way back on
|
|
193
|
+
a small stagger, so a wave travels through the word. It layers on top of the word wobble rather than
|
|
194
|
+
replacing it: the word keeps whatever `--blyrics-word-wobble-*` does and the letters ride on top, so
|
|
195
|
+
it composes with the default `scaleX` pop and reduces to just the letters when a theme sets its wobble
|
|
196
|
+
to identity. It follows `--blyrics-animate-word-wobble`, so reduced motion turns it off with the rest.
|
|
197
|
+
|
|
198
|
+
A word held past `blyrics-long-word-threshold` (the same `data-long-word` the glow keys off) also
|
|
199
|
+
swells each letter with a transient scale at the crest.
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
.blyrics-container {
|
|
203
|
+
--blyrics-letter-wave-transform: translateY(-0.05em); /* crest lift */
|
|
204
|
+
--blyrics-letter-wave-settle: translateY(-0.02em); /* rest the crest eases back to */
|
|
205
|
+
--blyrics-letter-wave-emphasis-scale: 1.11; /* long-word letter swell, 1 turns it off */
|
|
206
|
+
--blyrics-letter-wave-duration: 0.9s;
|
|
207
|
+
--blyrics-letter-wave-rise-easing: ease-in-out;
|
|
208
|
+
--blyrics-letter-wave-fall-easing: ease-out;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The split multiplies the DOM per character and reruns the karaoke sweep per letter, so a theme that
|
|
213
|
+
does not want the cost turns it off with `/* blyrics-letter-wave = false; */`. `blyrics-letter-wave`
|
|
214
|
+
reloads the lines when it changes, the way every build-time setting does.
|
|
215
|
+
|
|
189
216
|
### Class names
|
|
190
217
|
|
|
191
218
|
These are published API rather than implementation. Renaming one costs a migration rather than a
|
package/dist/constants.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export declare const LYRICS_WRAPPER_ID: "blyrics-wrapper";
|
|
|
2
2
|
export declare const LYRICS_CLASS: "blyrics-container";
|
|
3
3
|
export declare const LINE_CLASS: "blyrics--line";
|
|
4
4
|
export declare const WORD_CLASS: "blyrics--word";
|
|
5
|
+
export declare const LETTER_CLASS: "blyrics--letter";
|
|
5
6
|
export declare const FOOTER_CLASS: "blyrics-footer";
|
|
6
7
|
export declare const CURRENT_LYRICS_CLASS: "blyrics--active";
|
|
7
8
|
export declare const ANIMATING_CLASS: "blyrics--animating";
|
package/dist/constants.js
CHANGED
|
@@ -5,6 +5,7 @@ export const LYRICS_WRAPPER_ID = "blyrics-wrapper";
|
|
|
5
5
|
export const LYRICS_CLASS = "blyrics-container";
|
|
6
6
|
export const LINE_CLASS = "blyrics--line";
|
|
7
7
|
export const WORD_CLASS = "blyrics--word";
|
|
8
|
+
export const LETTER_CLASS = "blyrics--letter";
|
|
8
9
|
export const FOOTER_CLASS = "blyrics-footer";
|
|
9
10
|
// -- Playback state --------------------------------------------
|
|
10
11
|
export const CURRENT_LYRICS_CLASS = "blyrics--active";
|
package/dist/engine.d.ts
CHANGED
|
@@ -175,6 +175,14 @@ interface AnimationConfig {
|
|
|
175
175
|
wobblePeakOffset: number;
|
|
176
176
|
wobbleSettleOffset: number;
|
|
177
177
|
};
|
|
178
|
+
letterWave: {
|
|
179
|
+
transform: string;
|
|
180
|
+
settle: string;
|
|
181
|
+
emphasisScale: string;
|
|
182
|
+
durationMs: number;
|
|
183
|
+
riseEasing: string;
|
|
184
|
+
fallEasing: string;
|
|
185
|
+
};
|
|
178
186
|
instrumental: {
|
|
179
187
|
fillFadeDurationMs: number;
|
|
180
188
|
fillFadeEasing: string;
|
package/dist/engine.js
CHANGED
|
@@ -755,9 +755,6 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
|
|
|
755
755
|
offset: config.word.wobblePeakOffset,
|
|
756
756
|
easing: config.word.wobblePeakEasing,
|
|
757
757
|
},
|
|
758
|
-
// The two offsets are read and clamped independently, so a theme is free to settle
|
|
759
|
-
// before it peaks. animate() rejects offsets that go backwards, and that throw would
|
|
760
|
-
// orphan the highlight animations above, which are not tracked until the end.
|
|
761
758
|
{
|
|
762
759
|
transform: config.word.wobbleSettle,
|
|
763
760
|
offset: Math.max(config.word.wobblePeakOffset, config.word.wobbleSettleOffset),
|
|
@@ -770,8 +767,6 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
|
|
|
770
767
|
fill: "forwards",
|
|
771
768
|
};
|
|
772
769
|
const wobbleStartMs = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, config.word.wobbleDurationMs);
|
|
773
|
-
// The wobble is a paint transform, so the highlight copy must carry it too or the active
|
|
774
|
-
// sweep drifts off the word.
|
|
775
770
|
for (const wordElement of [part.lyricElement, part.highlightElement]) {
|
|
776
771
|
const animation = trackLyricAnimationTiming(engine, wordElement.animate(wobbleKeyframes, wobbleOptions), {
|
|
777
772
|
appliedTimingOffsetMs,
|
|
@@ -780,6 +775,40 @@ function startWordAnimations(engine, part, config, currentTime, appliedTimingOff
|
|
|
780
775
|
animation.currentTime = wobbleStartMs;
|
|
781
776
|
wobbleAnimations.push(animation);
|
|
782
777
|
}
|
|
778
|
+
const letters = part.letterElements;
|
|
779
|
+
if (letters && letters.length > 0) {
|
|
780
|
+
const emphasise = part.lyricElement?.dataset.longWord === "true";
|
|
781
|
+
const emphasisPeak = emphasise ? ` scale(${config.letterWave.emphasisScale})` : "";
|
|
782
|
+
const emphasisRest = emphasise ? " scale(1)" : "";
|
|
783
|
+
const floatKeyframes = [
|
|
784
|
+
{ transform: `translateY(0)${emphasisRest}`, easing: config.letterWave.riseEasing },
|
|
785
|
+
{
|
|
786
|
+
transform: `${config.letterWave.transform}${emphasisPeak}`,
|
|
787
|
+
offset: 0.4,
|
|
788
|
+
easing: config.letterWave.fallEasing,
|
|
789
|
+
},
|
|
790
|
+
{ transform: `${config.letterWave.settle}${emphasisRest}` },
|
|
791
|
+
];
|
|
792
|
+
const letterCount = letters.length;
|
|
793
|
+
const staggerMs = timedDurationMs > 0 ? timedDurationMs / 2.5 / letterCount : 0;
|
|
794
|
+
const cascadeDurationMs = config.letterWave.durationMs + (letterCount - 1) * staggerMs;
|
|
795
|
+
const floatStartMs = correctedAnimationTimeMs(wordTimeMs, appliedTimingOffsetMs, cascadeDurationMs);
|
|
796
|
+
for (const set of [part.letterElements, part.highlightLetterElements]) {
|
|
797
|
+
set?.forEach((letterElement, index) => {
|
|
798
|
+
const options = {
|
|
799
|
+
duration: config.letterWave.durationMs,
|
|
800
|
+
delay: index * staggerMs,
|
|
801
|
+
fill: "forwards",
|
|
802
|
+
};
|
|
803
|
+
const animation = trackLyricAnimationTiming(engine, letterElement.animate(floatKeyframes, options), {
|
|
804
|
+
appliedTimingOffsetMs,
|
|
805
|
+
offsetMs: 0,
|
|
806
|
+
});
|
|
807
|
+
animation.currentTime = floatStartMs;
|
|
808
|
+
wobbleAnimations.push(animation);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
783
812
|
}
|
|
784
813
|
part.animations = [...highlightAnimations.animations, ...wobbleAnimations];
|
|
785
814
|
}
|
|
@@ -1007,6 +1036,14 @@ function readAnimationConfig(engine, lyricsElement) {
|
|
|
1007
1036
|
wobblePeakOffset: getCSSOffset(engine, lyricsElement, "--blyrics-word-wobble-peak-offset", 0.125),
|
|
1008
1037
|
wobbleSettleOffset: getCSSOffset(engine, lyricsElement, "--blyrics-word-wobble-settle-offset", 0.75),
|
|
1009
1038
|
},
|
|
1039
|
+
letterWave: {
|
|
1040
|
+
transform: getCSSValue(engine, lyricsElement, "--blyrics-letter-wave-transform", "translateY(-0.06em)"),
|
|
1041
|
+
settle: getCSSValue(engine, lyricsElement, "--blyrics-letter-wave-settle", "translateY(-0.05em)"),
|
|
1042
|
+
emphasisScale: getCSSValue(engine, lyricsElement, "--blyrics-letter-wave-emphasis-scale", "1.08"),
|
|
1043
|
+
durationMs: getCSSDurationWithFallback(engine, lyricsElement, "--blyrics-letter-wave-duration", "0.9s"),
|
|
1044
|
+
riseEasing: getCSSValue(engine, lyricsElement, "--blyrics-letter-wave-rise-easing", "ease-in-out"),
|
|
1045
|
+
fallEasing: getCSSValue(engine, lyricsElement, "--blyrics-letter-wave-fall-easing", "ease-in-out"),
|
|
1046
|
+
},
|
|
1010
1047
|
instrumental: {
|
|
1011
1048
|
fillFadeDurationMs: getCSSDurationWithFallback(engine, lyricsElement, "--blyrics-instrumental-fill-fade-duration", "150ms"),
|
|
1012
1049
|
fillFadeEasing: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-fill-fade-easing", "ease"),
|
package/dist/inject.d.ts
CHANGED
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, 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";
|
|
14
|
+
import { BACKGROUND_LINE_CLASS, BACKGROUND_LYRIC_CLASS, BIDI_RUN_CLASS, BIDI_SENSITIVE_CLASS, CONTENT_LINE_CLASS, EXPLICIT_WORD_CLASS, HIGHLIGHT_RUN_CLASS, LETTER_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";
|
|
@@ -19,6 +19,7 @@ export let disableRichsync = registerThemeSetting("blyrics-disable-richsync", fa
|
|
|
19
19
|
let lineSyncedAnimationDelay = registerThemeSetting("blyrics-line-synced-animation-delay", 50, true);
|
|
20
20
|
let longWordThreshold = registerThemeSetting("blyrics-long-word-threshold", 1500, true);
|
|
21
21
|
let longWordWrapThreshold = registerThemeSetting("blyrics-long-word-wrap-threshold", 10, true);
|
|
22
|
+
let letterWave = registerThemeSetting("blyrics-letter-wave", true, true);
|
|
22
23
|
const RTL_SCRIPT_REGEX = /[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}]/u;
|
|
23
24
|
const LTR_SCRIPT_REGEX = /[\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}\p{Script=Han}\p{Script=Hangul}\p{Script=Hiragana}\p{Script=Katakana}]/u;
|
|
24
25
|
const SPACE_REGEX = /^\s+$/u;
|
|
@@ -60,12 +61,14 @@ export function deriveSyncType(lyrics) {
|
|
|
60
61
|
return "richsync";
|
|
61
62
|
return lyrics.every(item => item.startTimeMs === 0) ? "none" : "synced";
|
|
62
63
|
}
|
|
63
|
-
function newPartData(part, span, highlight) {
|
|
64
|
+
function newPartData(part, span, highlight, letterElements, highlightLetterElements) {
|
|
64
65
|
return {
|
|
65
66
|
time: part.startTimeMs / 1000,
|
|
66
67
|
duration: part.durationMs / 1000,
|
|
67
68
|
lyricElement: span,
|
|
68
69
|
highlightElement: highlight,
|
|
70
|
+
letterElements,
|
|
71
|
+
highlightLetterElements,
|
|
69
72
|
animations: [],
|
|
70
73
|
};
|
|
71
74
|
}
|
|
@@ -195,10 +198,24 @@ function appendLongWordBreaks(doc, span, text, threshold) {
|
|
|
195
198
|
}
|
|
196
199
|
return true;
|
|
197
200
|
}
|
|
198
|
-
function
|
|
201
|
+
function appendLetters(doc, wordElement, text) {
|
|
202
|
+
const chars = [...text];
|
|
203
|
+
wordElement.style.setProperty("--letters", String(chars.length));
|
|
204
|
+
return chars.map((char, index) => {
|
|
205
|
+
const letter = doc.createElement("span");
|
|
206
|
+
letter.classList.add(LETTER_CLASS);
|
|
207
|
+
letter.style.setProperty("--letter-index", String(index));
|
|
208
|
+
letter.textContent = char;
|
|
209
|
+
wordElement.appendChild(letter);
|
|
210
|
+
return letter;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function createTimedWordSpan(doc, part, wrapThreshold, perLetter) {
|
|
199
214
|
const span = doc.createElement("span");
|
|
200
215
|
const highlight = doc.createElement("span");
|
|
201
216
|
highlight.classList.add(WORD_HIGHLIGHT_CLASS);
|
|
217
|
+
let letters;
|
|
218
|
+
let highlightLetters;
|
|
202
219
|
for (const wordElement of [span, highlight]) {
|
|
203
220
|
wordElement.classList.add(WORD_CLASS);
|
|
204
221
|
wordElement.dir = "auto";
|
|
@@ -214,16 +231,26 @@ function createTimedWordSpan(doc, part, wrapThreshold) {
|
|
|
214
231
|
wordElement.classList.add(BACKGROUND_LYRIC_CLASS);
|
|
215
232
|
if (part.explicit)
|
|
216
233
|
wordElement.classList.add(EXPLICIT_WORD_CLASS);
|
|
217
|
-
|
|
234
|
+
if (perLetter) {
|
|
235
|
+
const collected = appendLetters(doc, wordElement, part.words);
|
|
236
|
+
if (wordElement === span)
|
|
237
|
+
letters = collected;
|
|
238
|
+
else
|
|
239
|
+
highlightLetters = collected;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
appendLongWordBreaks(doc, wordElement, part.words, wrapThreshold);
|
|
243
|
+
}
|
|
218
244
|
wordElement.dataset.time = String(part.startTimeMs / 1000);
|
|
219
245
|
wordElement.dataset.duration = String(part.durationMs / 1000);
|
|
220
246
|
wordElement.dataset.content = part.words;
|
|
221
247
|
wordElement.style.setProperty("--blyrics-duration", part.durationMs + "ms");
|
|
222
248
|
}
|
|
223
|
-
return { span, highlight };
|
|
249
|
+
return { span, highlight, letters, highlightLetters };
|
|
224
250
|
}
|
|
225
251
|
function createWordGroup(doc, group, lineData) {
|
|
226
252
|
const wrapThreshold = Math.max(1, longWordWrapThreshold.getNumberValue());
|
|
253
|
+
const perLetter = letterWave.getBooleanValue();
|
|
227
254
|
const lyricGroup = doc.createElement("span");
|
|
228
255
|
const highlightGroup = doc.createElement("span");
|
|
229
256
|
for (const groupElement of [lyricGroup, highlightGroup]) {
|
|
@@ -240,8 +267,8 @@ function createWordGroup(doc, group, lineData) {
|
|
|
240
267
|
for (const token of group.tokens) {
|
|
241
268
|
if (token.kind === "space")
|
|
242
269
|
continue;
|
|
243
|
-
const { span, highlight } = createTimedWordSpan(doc, token.part, wrapThreshold);
|
|
244
|
-
lineData.parts.push(newPartData(token.part, span, highlight));
|
|
270
|
+
const { span, highlight, letters, highlightLetters } = createTimedWordSpan(doc, token.part, wrapThreshold, perLetter);
|
|
271
|
+
lineData.parts.push(newPartData(token.part, span, highlight, letters, highlightLetters));
|
|
245
272
|
lyricGroup.appendChild(span);
|
|
246
273
|
highlightGroup.appendChild(highlight);
|
|
247
274
|
}
|
package/dist/styles/lyrics.css
CHANGED
|
@@ -117,13 +117,13 @@
|
|
|
117
117
|
|
|
118
118
|
@property --lyric-transition-amount-start {
|
|
119
119
|
syntax: "<number>";
|
|
120
|
-
inherits:
|
|
120
|
+
inherits: true;
|
|
121
121
|
initial-value: -0.2;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
@property --lyric-transition-amount-end {
|
|
125
125
|
syntax: "<number>";
|
|
126
|
-
inherits:
|
|
126
|
+
inherits: true;
|
|
127
127
|
initial-value: -0.1;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -157,6 +157,25 @@
|
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
.blyrics--letter {
|
|
161
|
+
display: inline-block;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.blyrics-word-highlight:has(.blyrics--letter) {
|
|
165
|
+
background-image: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.blyrics-word-highlight .blyrics--letter {
|
|
169
|
+
background-image: linear-gradient(
|
|
170
|
+
90deg,
|
|
171
|
+
var(--blyrics-lyric-active-color)
|
|
172
|
+
calc(100% * (var(--lyric-transition-amount-start) * var(--letters, 1) - var(--letter-index, 0))),
|
|
173
|
+
#00000000
|
|
174
|
+
calc(100% * (var(--lyric-transition-amount-end) * var(--letters, 1) - var(--letter-index, 0)) + 1px)
|
|
175
|
+
);
|
|
176
|
+
background-clip: text;
|
|
177
|
+
}
|
|
178
|
+
|
|
160
179
|
[blyrics-alt-hover] .blyrics--word:hover {
|
|
161
180
|
text-decoration: underline;
|
|
162
181
|
text-underline-offset: 0.15em;
|
|
@@ -110,6 +110,13 @@
|
|
|
110
110
|
--blyrics-word-wobble-peak-easing: ease-in-out;
|
|
111
111
|
--blyrics-word-wobble-end-easing: ease-out;
|
|
112
112
|
|
|
113
|
+
--blyrics-letter-wave-transform: translateY(-0.05em);
|
|
114
|
+
--blyrics-letter-wave-settle: translateY(-0.02em);
|
|
115
|
+
--blyrics-letter-wave-emphasis-scale: 1.11;
|
|
116
|
+
--blyrics-letter-wave-duration: 0.9s;
|
|
117
|
+
--blyrics-letter-wave-rise-easing: ease-in-out;
|
|
118
|
+
--blyrics-letter-wave-fall-easing: ease-out;
|
|
119
|
+
|
|
113
120
|
--blyrics-instrumental-fill-fade-duration: 150ms;
|
|
114
121
|
--blyrics-instrumental-fill-fade-easing: ease;
|
|
115
122
|
--blyrics-instrumental-fill-transform-from: translateY(78%);
|