@braccato/core 1.0.0 → 1.2.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 +14 -1
- package/dist/README.md +14 -1
- package/dist/element.js +8 -1
- package/dist/engine.d.ts +3 -0
- package/dist/engine.js +47 -7
- package/dist/instrumental.d.ts +2 -0
- package/dist/instrumental.js +3 -2
- package/dist/renderer.js +4 -0
- package/dist/styles/instrumental.css +2 -11
- package/dist/styles/variables.css +2 -0
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ connects, and everything it was handed by then is applied at once.
|
|
|
81
81
|
| `mediaElement` | | `HTMLMediaElement \| null` (get) | `null` | What `source` resolved to. Null while disconnected, and null for a selector that missed. |
|
|
82
82
|
| `currentTime` | `current-time` | `number` | `0` | Playback position in **seconds**. Writing it renders the view again, so whoever holds the clock drives the lyrics by writing this. |
|
|
83
83
|
| `playing` | `playing` | `boolean` | `false` | A paused view animates differently from a playing one. |
|
|
84
|
-
| `tickOptions` | | `ElementTickOptions` | `{}` | The rest of a tick: four offsets taken off the clock before it is matched, whether passive scrolling is on,
|
|
84
|
+
| `tickOptions` | | `ElementTickOptions` | `{}` | The rest of a tick: four offsets taken off the clock before it is matched, whether passive scrolling is on, when the clock was sampled, and the rate the song is playing at. |
|
|
85
85
|
| `theme` | `theme` | `string` | `""` | A compiled stylesheet. See Theming. |
|
|
86
86
|
| `host` | | `Partial<LyricsRendererHost>` | `{}` | Overrides for what the renderer asks of its surroundings. Every member has a default. Writing it while connected rebuilds the view. |
|
|
87
87
|
| `renderer` | | `LyricsRenderer \| null` (get) | `null` | The renderer underneath, for the day the tag runs out. A different one after every reconnection. |
|
|
@@ -179,6 +179,13 @@ The ones a theme reaches for first. `variables.css` declares the rest.
|
|
|
179
179
|
`line-height`. Every word is given the glow, so a theme that wants it to mean something selects on
|
|
180
180
|
`data-long-word`, which the module sets on any part held past `blyrics-long-word-threshold`.
|
|
181
181
|
|
|
182
|
+
The instrumental ripple is the one place a property carries geometry rather than a value.
|
|
183
|
+
`--blyrics-instrumental-wave-path-high` and `--blyrics-instrumental-wave-path-low` are the two shapes
|
|
184
|
+
it morphs between, each a `path()`, and a theme redrawing them is how the wave changes amplitude or
|
|
185
|
+
frequency. Both must use the same commands in the same order with the same number of arguments: a
|
|
186
|
+
browser only interpolates two paths smoothly when their command sequences match, and a mismatched
|
|
187
|
+
pair snaps at the halfway point instead of flowing.
|
|
188
|
+
|
|
182
189
|
### Class names
|
|
183
190
|
|
|
184
191
|
These are published API rather than implementation. Renaming one costs a migration rather than a
|
|
@@ -256,6 +263,12 @@ either is dropped and the getter keeps reporting what the binding last read. Dro
|
|
|
256
263
|
reported, because a consumer who bound a source and left their own frame loop running would otherwise
|
|
257
264
|
be told about it sixty times a second. Unbind and the clock goes back to whoever asked for it.
|
|
258
265
|
|
|
266
|
+
The rate is read off the media element too, and passed on as `tickOptions.playbackRate`, so a song at
|
|
267
|
+
half or double speed animates at half or double speed rather than sweeping at 1x and being corrected
|
|
268
|
+
on the next tick. A consumer driving the clock itself sets that option instead. Only the animations
|
|
269
|
+
that follow the song are scaled: a line's exit, a word's fade and the scroll between lines keep the
|
|
270
|
+
timing the theme asked for at every rate.
|
|
271
|
+
|
|
259
272
|
A reading the media element has not refreshed yet is carried forward at the playback rate it was
|
|
260
273
|
taken at, capped at 100ms of frame time. That cap is what covers a stall: the view runs at most 100ms
|
|
261
274
|
past the last real reading and then waits with it. What it costs is a step backwards when the clock
|
package/dist/README.md
CHANGED
|
@@ -81,7 +81,7 @@ connects, and everything it was handed by then is applied at once.
|
|
|
81
81
|
| `mediaElement` | | `HTMLMediaElement \| null` (get) | `null` | What `source` resolved to. Null while disconnected, and null for a selector that missed. |
|
|
82
82
|
| `currentTime` | `current-time` | `number` | `0` | Playback position in **seconds**. Writing it renders the view again, so whoever holds the clock drives the lyrics by writing this. |
|
|
83
83
|
| `playing` | `playing` | `boolean` | `false` | A paused view animates differently from a playing one. |
|
|
84
|
-
| `tickOptions` | | `ElementTickOptions` | `{}` | The rest of a tick: four offsets taken off the clock before it is matched, whether passive scrolling is on,
|
|
84
|
+
| `tickOptions` | | `ElementTickOptions` | `{}` | The rest of a tick: four offsets taken off the clock before it is matched, whether passive scrolling is on, when the clock was sampled, and the rate the song is playing at. |
|
|
85
85
|
| `theme` | `theme` | `string` | `""` | A compiled stylesheet. See Theming. |
|
|
86
86
|
| `host` | | `Partial<LyricsRendererHost>` | `{}` | Overrides for what the renderer asks of its surroundings. Every member has a default. Writing it while connected rebuilds the view. |
|
|
87
87
|
| `renderer` | | `LyricsRenderer \| null` (get) | `null` | The renderer underneath, for the day the tag runs out. A different one after every reconnection. |
|
|
@@ -179,6 +179,13 @@ The ones a theme reaches for first. `variables.css` declares the rest.
|
|
|
179
179
|
`line-height`. Every word is given the glow, so a theme that wants it to mean something selects on
|
|
180
180
|
`data-long-word`, which the module sets on any part held past `blyrics-long-word-threshold`.
|
|
181
181
|
|
|
182
|
+
The instrumental ripple is the one place a property carries geometry rather than a value.
|
|
183
|
+
`--blyrics-instrumental-wave-path-high` and `--blyrics-instrumental-wave-path-low` are the two shapes
|
|
184
|
+
it morphs between, each a `path()`, and a theme redrawing them is how the wave changes amplitude or
|
|
185
|
+
frequency. Both must use the same commands in the same order with the same number of arguments: a
|
|
186
|
+
browser only interpolates two paths smoothly when their command sequences match, and a mismatched
|
|
187
|
+
pair snaps at the halfway point instead of flowing.
|
|
188
|
+
|
|
182
189
|
### Class names
|
|
183
190
|
|
|
184
191
|
These are published API rather than implementation. Renaming one costs a migration rather than a
|
|
@@ -256,6 +263,12 @@ either is dropped and the getter keeps reporting what the binding last read. Dro
|
|
|
256
263
|
reported, because a consumer who bound a source and left their own frame loop running would otherwise
|
|
257
264
|
be told about it sixty times a second. Unbind and the clock goes back to whoever asked for it.
|
|
258
265
|
|
|
266
|
+
The rate is read off the media element too, and passed on as `tickOptions.playbackRate`, so a song at
|
|
267
|
+
half or double speed animates at half or double speed rather than sweeping at 1x and being corrected
|
|
268
|
+
on the next tick. A consumer driving the clock itself sets that option instead. Only the animations
|
|
269
|
+
that follow the song are scaled: a line's exit, a word's fade and the scroll between lines keep the
|
|
270
|
+
timing the theme asked for at every rate.
|
|
271
|
+
|
|
259
272
|
A reading the media element has not refreshed yet is carried forward at the playback rate it was
|
|
260
273
|
taken at, capped at 100ms of frame time. That cap is what covers a stall: the view runs at most 100ms
|
|
261
274
|
past the last real reading and then waits with it. What it costs is a step backwards when the clock
|
package/dist/element.js
CHANGED
|
@@ -422,8 +422,15 @@ export class BraccatoLyricsElement extends HTMLElement {
|
|
|
422
422
|
// consumer whose clock runs before the lyrics arrive is the ordinary case rather than a fault.
|
|
423
423
|
if (renderer === null || renderer.container === null)
|
|
424
424
|
return;
|
|
425
|
+
// The rate a bound media element is playing at is the element's to report, the same way the
|
|
426
|
+
// clock is, so it overrides what the consumer wrote for exactly as long as the binding lasts.
|
|
427
|
+
const boundRate = this.#media === null ? undefined : this.#media.playbackRate;
|
|
425
428
|
// The play state last, so a consumer writing plain JavaScript cannot answer that question twice.
|
|
426
|
-
renderer.tick(this.#currentTimeS, {
|
|
429
|
+
renderer.tick(this.#currentTimeS, {
|
|
430
|
+
...this.#tickOptions,
|
|
431
|
+
...(boundRate === undefined ? {} : { playbackRate: boundRate }),
|
|
432
|
+
isPlaying: this.#playing,
|
|
433
|
+
});
|
|
427
434
|
}
|
|
428
435
|
#upgradeProperty(name) {
|
|
429
436
|
// Written through the class rather than `this`: TypeScript refuses an indexed write to a
|
package/dist/engine.d.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface AnimationEngineInstance extends AnimEngineViewState {
|
|
|
56
56
|
* from a tick, so it has no options object to read.
|
|
57
57
|
*/
|
|
58
58
|
passiveScrollEnabled: boolean;
|
|
59
|
+
playbackRate: number;
|
|
59
60
|
passiveRAFId: number | null;
|
|
60
61
|
pendingLyricsUpdateFrame: number | null;
|
|
61
62
|
learnedAnimationTimingOffsetMs: number;
|
|
@@ -183,6 +184,8 @@ interface AnimationConfig {
|
|
|
183
184
|
waveFrom: string;
|
|
184
185
|
waveTo: string;
|
|
185
186
|
waveEasing: string;
|
|
187
|
+
wavePathHigh: string;
|
|
188
|
+
wavePathLow: string;
|
|
186
189
|
waveOscillationDurationMs: number;
|
|
187
190
|
waveOscillationEasing: string;
|
|
188
191
|
};
|
package/dist/engine.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// module is bundled into the isolated world and the page world separately, so those are two clocks
|
|
18
18
|
// that never meet. `themeSettings.ts` holds the third thing under that rule.
|
|
19
19
|
import { ANIMATING_CLASS, CURRENT_LYRICS_CLASS, FOOTER_CLASS, LINE_CLASS, PAUSED_CLASS, USER_SCROLLING_CLASS, } from "./constants.js";
|
|
20
|
+
import { INSTRUMENTAL_WAVE_PATH_HIGH, INSTRUMENTAL_WAVE_PATH_LOW } from "./instrumental.js";
|
|
20
21
|
import { registerThemeSetting } from "./themeSettings.js";
|
|
21
22
|
import { clamp, getRelativeLayoutBounds, positiveModulo, roundedMs, toMs } from "./util.js";
|
|
22
23
|
const NO_LYRICS_ELEMENT_LOG = "No lyrics element found on the page, skipping lyrics injection";
|
|
@@ -145,6 +146,7 @@ export function createAnimationEngineInstance(engineDocument, engineWindow, host
|
|
|
145
146
|
cachedCSSValues: new Map(),
|
|
146
147
|
cachedAnimationSettings: null,
|
|
147
148
|
passiveScrollEnabled: false,
|
|
149
|
+
playbackRate: 1,
|
|
148
150
|
passiveRAFId: null,
|
|
149
151
|
pendingLyricsUpdateFrame: null,
|
|
150
152
|
learnedAnimationTimingOffsetMs: 0,
|
|
@@ -325,8 +327,8 @@ const WORD_HIGHLIGHT_SELECTOR = ".blyrics-word-highlight";
|
|
|
325
327
|
const INSTRUMENTAL_FILL_SELECTOR = ".blyrics--instrumental-fill";
|
|
326
328
|
const INSTRUMENTAL_WAVE_CLIP_SELECTOR = ".blyrics--wave-clip";
|
|
327
329
|
const INSTRUMENTAL_WAVE_PATH_SELECTOR = ".blyrics--wave-path";
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
+
const INSTRUMENTAL_WAVE_PATH_HIGH_FALLBACK = `path("${INSTRUMENTAL_WAVE_PATH_HIGH}")`;
|
|
331
|
+
const INSTRUMENTAL_WAVE_PATH_LOW_FALLBACK = `path("${INSTRUMENTAL_WAVE_PATH_LOW}")`;
|
|
330
332
|
const LINE_SCROLL_INDEX_PROPERTY = "--blyrics-line-scroll-index";
|
|
331
333
|
const LINE_SCROLL_ACTIVE_INDEX_PROPERTY = "--blyrics-line-scroll-active-index";
|
|
332
334
|
const LINE_SCROLL_RELATIVE_INDEX_PROPERTY = "--blyrics-line-scroll-relative-index";
|
|
@@ -351,8 +353,29 @@ function trackLyricAnimationTiming(engine, animation, timing) {
|
|
|
351
353
|
...timing,
|
|
352
354
|
appliedTimingOffsetMs: timing.appliedTimingOffsetMs ?? engine.learnedAnimationTimingOffsetMs,
|
|
353
355
|
});
|
|
356
|
+
// Being tracked is what makes an animation the song's rather than the interface's, so it is also
|
|
357
|
+
// what decides which ones follow the song's rate.
|
|
358
|
+
animation.playbackRate = engine.playbackRate;
|
|
354
359
|
return animation;
|
|
355
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Puts the animations already running onto a new rate. Setting `playbackRate` keeps `currentTime`,
|
|
363
|
+
* so each one carries on from where the song left it rather than restarting.
|
|
364
|
+
*/
|
|
365
|
+
function applyPlaybackRateToRunningAnimations(engine) {
|
|
366
|
+
for (const line of engine.lines) {
|
|
367
|
+
for (const animation of line.animations) {
|
|
368
|
+
if (animationTimingTracks.has(animation))
|
|
369
|
+
animation.playbackRate = engine.playbackRate;
|
|
370
|
+
}
|
|
371
|
+
for (const part of line.parts) {
|
|
372
|
+
for (const animation of part.animations) {
|
|
373
|
+
if (animationTimingTracks.has(animation))
|
|
374
|
+
animation.playbackRate = engine.playbackRate;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
356
379
|
function correctedAnimationTimeMs(targetTimeMs, appliedTimingOffsetMs, maxTimeMs) {
|
|
357
380
|
const scheduledTimeMs = targetTimeMs - appliedTimingOffsetMs;
|
|
358
381
|
return maxTimeMs === undefined ? scheduledTimeMs : Math.min(scheduledTimeMs, maxTimeMs);
|
|
@@ -834,9 +857,9 @@ function startInstrumentalAnimations(engine, lineData, config, currentTime, appl
|
|
|
834
857
|
fill: "both",
|
|
835
858
|
}, { appliedTimingOffsetMs, offsetMs: 0 });
|
|
836
859
|
waveOscillationAnimation = animateInstrumentalChild(engine, lineData, INSTRUMENTAL_WAVE_PATH_SELECTOR, [
|
|
837
|
-
{ d:
|
|
838
|
-
{ d:
|
|
839
|
-
{ d:
|
|
860
|
+
{ d: config.instrumental.wavePathHigh },
|
|
861
|
+
{ d: config.instrumental.wavePathLow, offset: 0.5 },
|
|
862
|
+
{ d: config.instrumental.wavePathHigh },
|
|
840
863
|
], {
|
|
841
864
|
duration: config.instrumental.waveOscillationDurationMs,
|
|
842
865
|
easing: config.instrumental.waveOscillationEasing,
|
|
@@ -994,6 +1017,8 @@ function readAnimationConfig(engine, lyricsElement) {
|
|
|
994
1017
|
waveFrom: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-wave-transform-from", "scaleY(1.2)"),
|
|
995
1018
|
waveTo: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-wave-transform-to", "scaleY(0.0001)"),
|
|
996
1019
|
waveEasing: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-wave-easing", "ease-in"),
|
|
1020
|
+
wavePathHigh: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-wave-path-high", INSTRUMENTAL_WAVE_PATH_HIGH_FALLBACK),
|
|
1021
|
+
wavePathLow: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-wave-path-low", INSTRUMENTAL_WAVE_PATH_LOW_FALLBACK),
|
|
997
1022
|
waveOscillationDurationMs: getCSSDurationWithFallback(engine, lyricsElement, "--blyrics-instrumental-wave-oscillation-duration", "1.25s"),
|
|
998
1023
|
waveOscillationEasing: getCSSValue(engine, lyricsElement, "--blyrics-instrumental-wave-oscillation-easing", "ease-in-out"),
|
|
999
1024
|
},
|
|
@@ -1452,14 +1477,24 @@ export function resolveTickOptions(options) {
|
|
|
1452
1477
|
richsyncOffsetTrim: options.richsyncOffsetTrim ?? 0,
|
|
1453
1478
|
lineOffsetTrim: options.lineOffsetTrim ?? 0,
|
|
1454
1479
|
passiveScrollEnabled: options.passiveScrollEnabled ?? false,
|
|
1480
|
+
playbackRate: resolvePlaybackRate(options.playbackRate),
|
|
1455
1481
|
};
|
|
1456
1482
|
}
|
|
1483
|
+
// A rate of zero or less would freeze every animation that follows the song, which is a second
|
|
1484
|
+
// answer to the question `isPlaying` already answers.
|
|
1485
|
+
function resolvePlaybackRate(rate) {
|
|
1486
|
+
return rate !== undefined && Number.isFinite(rate) && rate > 0 ? rate : 1;
|
|
1487
|
+
}
|
|
1457
1488
|
/**
|
|
1458
1489
|
* Renders one view against a tick with nothing left out.
|
|
1459
1490
|
*/
|
|
1460
1491
|
export function tickView(engine, currentTime, options) {
|
|
1461
1492
|
const { eventCreationTime, isPlaying, smoothScroll } = options;
|
|
1462
1493
|
engine.passiveScrollEnabled = options.passiveScrollEnabled;
|
|
1494
|
+
if (engine.playbackRate !== options.playbackRate) {
|
|
1495
|
+
engine.playbackRate = options.playbackRate;
|
|
1496
|
+
applyPlaybackRateToRunningAnimations(engine);
|
|
1497
|
+
}
|
|
1463
1498
|
const now = Date.now();
|
|
1464
1499
|
if (currentTime === 0 && !isPlaying) {
|
|
1465
1500
|
return "ok";
|
|
@@ -1528,6 +1563,7 @@ export function tickView(engine, currentTime, options) {
|
|
|
1528
1563
|
}
|
|
1529
1564
|
const tabRendererHeight = engine.cachedTabRendererHeight ?? tabRenderer.getBoundingClientRect().height;
|
|
1530
1565
|
let scrollTop = tabRenderer.scrollTop;
|
|
1566
|
+
const maxScrollTop = Math.max(0, tabRenderer.scrollHeight - tabRenderer.clientHeight);
|
|
1531
1567
|
if (animationConfig.enabled.scroll) {
|
|
1532
1568
|
updateVisibleLyricWillChange(engine, lines, scrollTop, engine.pendingLineScroll?.toScrollTop ?? scrollTop, tabRendererHeight);
|
|
1533
1569
|
}
|
|
@@ -1681,8 +1717,9 @@ export function tickView(engine, currentTime, options) {
|
|
|
1681
1717
|
scrollPos = Math.max(scrollPos, lastActiveLyric.position - tabRendererHeight + lastActiveLyric.height);
|
|
1682
1718
|
// Make sure top of last active lyric is visible.
|
|
1683
1719
|
scrollPos = Math.min(scrollPos, lastActiveLyric.position);
|
|
1684
|
-
//
|
|
1685
|
-
|
|
1720
|
+
// Past either end the browser clamps the write and reports nothing, leaving the view aiming
|
|
1721
|
+
// at a position it never reached and re-aiming once per remaining line.
|
|
1722
|
+
scrollPos = clamp(scrollPos, 0, maxScrollTop);
|
|
1686
1723
|
if (ENABLE_DEBUG_RENDER.getBooleanValue()) {
|
|
1687
1724
|
let transform = engine.window.getComputedStyle(lyricsElement).transform;
|
|
1688
1725
|
const matrix = new engine.window.DOMMatrix(transform);
|
|
@@ -1867,6 +1904,9 @@ function applyScrollPadding(engine) {
|
|
|
1867
1904
|
});
|
|
1868
1905
|
engine.document.documentElement.style.setProperty("--blyrics-padding-top", top + "px");
|
|
1869
1906
|
engine.document.documentElement.style.setProperty("--blyrics-padding-bottom", bottom + "px");
|
|
1907
|
+
// Inline, because a theme's own `.blyrics-container { padding }` is appended after the package's
|
|
1908
|
+
// stylesheet at the same weight and would otherwise take this room away.
|
|
1909
|
+
lyricsElement.style.setProperty("padding-bottom", bottom + "px");
|
|
1870
1910
|
}
|
|
1871
1911
|
/**
|
|
1872
1912
|
* Re-reads the view's layout: the scroll padding first, then the line positions the padding moved.
|
package/dist/instrumental.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const INSTRUMENTAL_WAVE_PATH_HIGH = "M -4 3 Q 1 2 5 3 Q 10 4 14 3 Q 18 2 22 3 Q 26 4 30 3 L 30 4 L -4 4 Z";
|
|
2
|
+
export declare const INSTRUMENTAL_WAVE_PATH_LOW = "M -4 3 Q 1 4 5 3 Q 10 2 14 3 Q 18 4 22 3 Q 26 2 30 3 L 30 4 L -4 4 Z";
|
|
1
3
|
/**
|
|
2
4
|
* Creates an HTML element representing an instrumental break in the lyrics.
|
|
3
5
|
*
|
package/dist/instrumental.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export const INSTRUMENTAL_WAVE_PATH_HIGH = "M -4 3 Q 1 2 5 3 Q 10 4 14 3 Q 18 2 22 3 Q 26 4 30 3 L 30 4 L -4 4 Z";
|
|
2
|
+
export const INSTRUMENTAL_WAVE_PATH_LOW = "M -4 3 Q 1 4 5 3 Q 10 2 14 3 Q 18 4 22 3 Q 26 2 30 3 L 30 4 L -4 4 Z";
|
|
1
3
|
/**
|
|
2
4
|
* Creates an HTML element representing an instrumental break in the lyrics.
|
|
3
5
|
*
|
|
@@ -56,8 +58,7 @@ export function createInstrumentalElement(doc, container, durationMs, lineIndex)
|
|
|
56
58
|
// This only contains the surface water. It closes at y=4.
|
|
57
59
|
const wavePath = doc.createElementNS(svgNS, "path");
|
|
58
60
|
wavePath.classList.add("blyrics--wave-path");
|
|
59
|
-
|
|
60
|
-
wavePath.setAttribute("d", "M -4 3 Q 1 2 5 3 Q 10 4 14 3 Q 18 2 22 3 Q 26 4 30 3 L 30 4 L -4 4 Z");
|
|
61
|
+
wavePath.setAttribute("d", INSTRUMENTAL_WAVE_PATH_HIGH);
|
|
61
62
|
clipPath.appendChild(wavePath);
|
|
62
63
|
defs.appendChild(clipPath);
|
|
63
64
|
svg.appendChild(defs);
|
package/dist/renderer.js
CHANGED
|
@@ -269,6 +269,10 @@ export function createLyricsRenderer(rendererOptions) {
|
|
|
269
269
|
// Everything the engine resolved off the document was resolved against the theme that just
|
|
270
270
|
// went away.
|
|
271
271
|
clearEngineStyleCaches(engine);
|
|
272
|
+
// The target scroll position is one of the settings a theme carries, and the scroll padding is
|
|
273
|
+
// sized against it, so a theme that moves the target moves the layout even when no rule in it
|
|
274
|
+
// does.
|
|
275
|
+
measure();
|
|
272
276
|
return needsLyricRebuild;
|
|
273
277
|
},
|
|
274
278
|
tick(currentTimeS, options) {
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
|
|
70
70
|
/* Prepare the WAAPI flatten transition. */
|
|
71
71
|
transform: scaleY(1.2);
|
|
72
|
+
|
|
73
|
+
d: var(--blyrics-instrumental-wave-path-high);
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/* Trigger the Flattening */
|
|
@@ -90,14 +92,3 @@
|
|
|
90
92
|
transition-timing-function: ease-out;
|
|
91
93
|
transition-delay: 0s;
|
|
92
94
|
}
|
|
93
|
-
|
|
94
|
-
/* Update Keyframes for the "Split" shape */
|
|
95
|
-
/* These paths must close at 'L 30 4 L -4 4' to match the static rect overlap */
|
|
96
|
-
@keyframes blyrics-wave {
|
|
97
|
-
0%, 100% {
|
|
98
|
-
d: path("M -4 3 Q 1 2 5 3 Q 10 4 14 3 Q 18 2 22 3 Q 26 4 30 3 L 30 4 L -4 4 Z");
|
|
99
|
-
}
|
|
100
|
-
50% {
|
|
101
|
-
d: path("M -4 3 Q 1 4 5 3 Q 10 2 14 3 Q 18 4 22 3 Q 26 2 30 3 L 30 4 L -4 4 Z");
|
|
102
|
-
}
|
|
103
|
-
}
|
|
@@ -117,6 +117,8 @@
|
|
|
117
117
|
--blyrics-instrumental-wave-transform-from: scaleY(1.2);
|
|
118
118
|
--blyrics-instrumental-wave-transform-to: scaleY(0.0001);
|
|
119
119
|
--blyrics-instrumental-wave-easing: ease-in;
|
|
120
|
+
--blyrics-instrumental-wave-path-high: path("M -4 3 Q 1 2 5 3 Q 10 4 14 3 Q 18 2 22 3 Q 26 4 30 3 L 30 4 L -4 4 Z");
|
|
121
|
+
--blyrics-instrumental-wave-path-low: path("M -4 3 Q 1 4 5 3 Q 10 2 14 3 Q 18 4 22 3 Q 26 2 30 3 L 30 4 L -4 4 Z");
|
|
120
122
|
--blyrics-instrumental-wave-oscillation-duration: 1.25s;
|
|
121
123
|
--blyrics-instrumental-wave-oscillation-easing: ease-in-out;
|
|
122
124
|
|
package/dist/types.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface TickOptions {
|
|
|
28
28
|
* false.
|
|
29
29
|
*/
|
|
30
30
|
passiveScrollEnabled?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* How fast the song moves through its own timeline, as a multiple of real time. Defaults to 1, and
|
|
33
|
+
* zero or less reads as 1 because `isPlaying` is what says a song is stopped. Scales the animations
|
|
34
|
+
* that follow the song; a line's exit, a word's fade and the scroll between lines are the
|
|
35
|
+
* interface's own gestures and keep the timing the theme asked for at every rate.
|
|
36
|
+
*/
|
|
37
|
+
playbackRate?: number;
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
33
40
|
* A tick with nothing left out. The engine reads every field unconditionally, so the defaults are
|