@braccato/core 1.13.2 → 1.15.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 +66 -3
- package/dist/README.md +66 -3
- package/dist/engine.d.ts +17 -2
- package/dist/engine.js +321 -33
- package/dist/imageHighlights.d.ts +15 -0
- package/dist/imageHighlights.js +80 -0
- package/dist/index.d.ts +4 -0
- package/dist/inject.d.ts +2 -0
- package/dist/inject.js +16 -4
- package/dist/instrumental.d.ts +1 -2
- package/dist/instrumental.js +47 -5
- package/dist/renderer.js +23 -1
- package/dist/stage.d.ts +41 -0
- package/dist/stage.js +76 -0
- package/dist/styles/image-highlights.css +62 -0
- package/dist/styles/lyrics.css +8 -1
- package/dist/styles/stage.css +57 -0
- package/dist/styles/variables.css +5 -0
- package/dist/types.d.ts +10 -0
- package/dist/view.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -345,14 +345,15 @@ refactor. Import them from `@braccato/core/constants` instead of typing them out
|
|
|
345
345
|
|
|
346
346
|
## Stylesheets
|
|
347
347
|
|
|
348
|
-
|
|
348
|
+
Four sheets ship with the package, and loading them is yours, the way any package's CSS is. Leave
|
|
349
349
|
them out and you get lines that are in the document and unstyled, rather than lines that are missing.
|
|
350
350
|
|
|
351
351
|
| File | What it carries |
|
|
352
352
|
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
353
|
-
| `@braccato/core/styles/variables.css` | Every `--blyrics-*` default. It goes first, because the
|
|
353
|
+
| `@braccato/core/styles/variables.css` | Every `--blyrics-*` default. It goes first, because the others read from it. |
|
|
354
354
|
| `@braccato/core/styles/lyrics.css` | The container, the lines, the words and the sweep, plus two `@property` registrations the word animation interpolates through. |
|
|
355
355
|
| `@braccato/core/styles/instrumental.css` | The waveform that fills a bar nobody sings over, and the animation that walks it. |
|
|
356
|
+
| `@braccato/core/styles/stage.css` | Placement for the stage layout, and nothing else. Only a renderer created with `layout: "stage"` needs it. |
|
|
356
357
|
|
|
357
358
|
One thing they do not do for you. The module measures the room the first and last lines need to reach
|
|
358
359
|
the view's target scroll position and writes it on the root as `--blyrics-padding-top` and
|
|
@@ -382,6 +383,26 @@ catch the layout up: the hung line floats into place while the lines around it s
|
|
|
382
383
|
rather than the rest of the lyric jumping down. `--blyrics-animate-decoration-entry` set to `0` drops
|
|
383
384
|
both for an instant insert, and reduced-motion does the same.
|
|
384
385
|
|
|
386
|
+
`createLyricsRenderer({ layout: "stage" })` builds a view for subtitles over a video: one line at a
|
|
387
|
+
time, and only while it is being sung. It fills its nearest positioned ancestor. There is no scroll
|
|
388
|
+
element to hand it, no scroll padding written and no line culling. Unsynced lyrics show nothing, because
|
|
389
|
+
a stage places lines by their time and unsynced lines have none.
|
|
390
|
+
|
|
391
|
+
The engine owns where a line is and whether you can see it. It moves each line with a `translate` Web
|
|
392
|
+
Animation and fades it through `--blyrics-stage-opacity`, blurring it slightly on the way in and out so
|
|
393
|
+
one line reads as turning into the next. Each line carries `data-stage-role` (`current`, `previous`,
|
|
394
|
+
`queued` or `gone`) and the container carries `data-layout="stage"`. `stage.css` sets a stage line's
|
|
395
|
+
`opacity` from that property with `!important` inside a cascade layer, and keeps the line
|
|
396
|
+
`visibility: hidden` until it is marked `data-stage-visible`. Layered important declarations outrank
|
|
397
|
+
unlayered ones whatever their specificity, so a theme that forces `opacity: 1 !important` on active
|
|
398
|
+
lines, which some do, can neither reveal a queued line nor hold one that is leaving. In a duet (any line sung by `v2` or `v3`) the
|
|
399
|
+
container also gets `data-stage-duet` and each singer keeps to their side, mirrored for right-to-left
|
|
400
|
+
lines; lines for everyone stay centred.
|
|
401
|
+
|
|
402
|
+
Everything else is the theme's. Lines are styled exactly as they would be in a scrolling view.
|
|
403
|
+
`host.onStageLayout(box)` reports the box around the sung lines in the container's coordinates, or null
|
|
404
|
+
when nothing sung is on stage, so you can draw a backdrop outside the container the theme styles.
|
|
405
|
+
|
|
385
406
|
`@braccato/core/element` registers `<braccato-lyrics>`, and `<better-lyrics>` beside it, on import.
|
|
386
407
|
Registration is a side effect, which is why it is entered separately.
|
|
387
408
|
|
|
@@ -429,7 +450,10 @@ swapping `audio.src` between songs without playing goes on reporting the old pos
|
|
|
429
450
|
|
|
430
451
|
Two renderers in one document write over each other, so the module supports one. It is a constraint
|
|
431
452
|
rather than a setting, and it is stated rather than enforced: none of the points where two of them
|
|
432
|
-
collide is a crash.
|
|
453
|
+
collide is a crash. A stage renderer is the exception. It writes no scroll padding, so it can sit in
|
|
454
|
+
the same document as a scrolling one, with three rules: give both the same theme, tick both with the
|
|
455
|
+
same time and wall time in the same frame (whichever ticks first is the one that sees a seek), and if
|
|
456
|
+
the stage renderer applied the theme first, destroy it first, because its `<style>` element goes with it.
|
|
433
457
|
|
|
434
458
|
Two things are written per document and belong to whichever renderer wrote them last: the theme's
|
|
435
459
|
`<style>` element, and the scroll padding on the root. Two more are per bundle, because a settings
|
|
@@ -479,3 +503,42 @@ loading regional CJK web fonts should set `--noto-sans-universal` on these eleme
|
|
|
479
503
|
`--blyrics-font-family` and `--blyrics-translated-font-family` remain explicit theme overrides.
|
|
480
504
|
They are unset by default; custom CSS that reads them directly should use
|
|
481
505
|
`var(--blyrics-font-family, var(--blyrics-default-font-family))` for the default stack.
|
|
506
|
+
|
|
507
|
+
### Theme-provided image fills
|
|
508
|
+
|
|
509
|
+
Image highlights are optional. Enable them in a theme comment:
|
|
510
|
+
|
|
511
|
+
```css
|
|
512
|
+
/* blyrics-image-highlights = true; */
|
|
513
|
+
.blyrics-container {
|
|
514
|
+
--blyrics-highlight-image: url("my-image.jpg");
|
|
515
|
+
--blyrics-image-glow-opacity-from: 0.5;
|
|
516
|
+
--blyrics-image-glow-opacity-to: 0;
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Changing this setting requests a lyric rebuild through the existing `setTheme` return value.
|
|
521
|
+
Without it, the renderer adds no image or glow layers and keeps its shadow animation. With it,
|
|
522
|
+
the sharp fill and a separate blurred copy share the word reveal, letter motion, opacity, pause,
|
|
523
|
+
seek and playback-rate handling. Existing glow radius, duration and easing properties still apply;
|
|
524
|
+
the two image-glow opacity properties replace the solid shadow color's alpha.
|
|
525
|
+
|
|
526
|
+
The image may be an SDR texture or an HDR gain-map asset. The theme owns HDR media queries
|
|
527
|
+
and `dynamic-range-limit`; the core never opts into extra display brightness. Ordinary active
|
|
528
|
+
text color backs up the image fill, while the note keeps its original color path until its
|
|
529
|
+
SVG image loads. The note image reuses the original animated wave clip and fade group.
|
|
530
|
+
|
|
531
|
+
The glow layer is outside the paint's mask, avoiding clipped halos at word edges. Bidi-sensitive
|
|
532
|
+
runs and fragmented long words use a parallel inline text run for the glow, so the browser
|
|
533
|
+
reproduces the same ordering and wrapped fragments. Its reveal and visibility use the same
|
|
534
|
+
animation clock as the sharp text. No generated text or per-frame geometry reads are needed.
|
|
535
|
+
Joining-script word groups retain contextual joining by using whole-word sweeps instead of
|
|
536
|
+
inline-block letter waves, including when image highlights are disabled.
|
|
537
|
+
Forced colors suppress the image and halo in favor of system text colors.
|
|
538
|
+
|
|
539
|
+
For real-browser regression checks, build the package, serve the repository root and open
|
|
540
|
+
`tooling/browser/image-highlights.html`. Import `image-highlights-checks.js` from that page and
|
|
541
|
+
call `runImageHighlightChecks(testView)`. The default fixture uses an SDR image; a `theme` query
|
|
542
|
+
parameter can point at a consumer's HDR stylesheet for physical-display testing.
|
|
543
|
+
|
|
544
|
+
Words that mix RTL characters with LTR letters or numbers retain native text shaping instead of per-letter motion. Image glow, letter masks and letter motion retain their current paint during the line exit fade.
|
package/dist/README.md
CHANGED
|
@@ -345,14 +345,15 @@ refactor. Import them from `@braccato/core/constants` instead of typing them out
|
|
|
345
345
|
|
|
346
346
|
## Stylesheets
|
|
347
347
|
|
|
348
|
-
|
|
348
|
+
Four sheets ship with the package, and loading them is yours, the way any package's CSS is. Leave
|
|
349
349
|
them out and you get lines that are in the document and unstyled, rather than lines that are missing.
|
|
350
350
|
|
|
351
351
|
| File | What it carries |
|
|
352
352
|
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
353
|
-
| `@braccato/core/styles/variables.css` | Every `--blyrics-*` default. It goes first, because the
|
|
353
|
+
| `@braccato/core/styles/variables.css` | Every `--blyrics-*` default. It goes first, because the others read from it. |
|
|
354
354
|
| `@braccato/core/styles/lyrics.css` | The container, the lines, the words and the sweep, plus two `@property` registrations the word animation interpolates through. |
|
|
355
355
|
| `@braccato/core/styles/instrumental.css` | The waveform that fills a bar nobody sings over, and the animation that walks it. |
|
|
356
|
+
| `@braccato/core/styles/stage.css` | Placement for the stage layout, and nothing else. Only a renderer created with `layout: "stage"` needs it. |
|
|
356
357
|
|
|
357
358
|
One thing they do not do for you. The module measures the room the first and last lines need to reach
|
|
358
359
|
the view's target scroll position and writes it on the root as `--blyrics-padding-top` and
|
|
@@ -382,6 +383,26 @@ catch the layout up: the hung line floats into place while the lines around it s
|
|
|
382
383
|
rather than the rest of the lyric jumping down. `--blyrics-animate-decoration-entry` set to `0` drops
|
|
383
384
|
both for an instant insert, and reduced-motion does the same.
|
|
384
385
|
|
|
386
|
+
`createLyricsRenderer({ layout: "stage" })` builds a view for subtitles over a video: one line at a
|
|
387
|
+
time, and only while it is being sung. It fills its nearest positioned ancestor. There is no scroll
|
|
388
|
+
element to hand it, no scroll padding written and no line culling. Unsynced lyrics show nothing, because
|
|
389
|
+
a stage places lines by their time and unsynced lines have none.
|
|
390
|
+
|
|
391
|
+
The engine owns where a line is and whether you can see it. It moves each line with a `translate` Web
|
|
392
|
+
Animation and fades it through `--blyrics-stage-opacity`, blurring it slightly on the way in and out so
|
|
393
|
+
one line reads as turning into the next. Each line carries `data-stage-role` (`current`, `previous`,
|
|
394
|
+
`queued` or `gone`) and the container carries `data-layout="stage"`. `stage.css` sets a stage line's
|
|
395
|
+
`opacity` from that property with `!important` inside a cascade layer, and keeps the line
|
|
396
|
+
`visibility: hidden` until it is marked `data-stage-visible`. Layered important declarations outrank
|
|
397
|
+
unlayered ones whatever their specificity, so a theme that forces `opacity: 1 !important` on active
|
|
398
|
+
lines, which some do, can neither reveal a queued line nor hold one that is leaving. In a duet (any line sung by `v2` or `v3`) the
|
|
399
|
+
container also gets `data-stage-duet` and each singer keeps to their side, mirrored for right-to-left
|
|
400
|
+
lines; lines for everyone stay centred.
|
|
401
|
+
|
|
402
|
+
Everything else is the theme's. Lines are styled exactly as they would be in a scrolling view.
|
|
403
|
+
`host.onStageLayout(box)` reports the box around the sung lines in the container's coordinates, or null
|
|
404
|
+
when nothing sung is on stage, so you can draw a backdrop outside the container the theme styles.
|
|
405
|
+
|
|
385
406
|
`@braccato/core/element` registers `<braccato-lyrics>`, and `<better-lyrics>` beside it, on import.
|
|
386
407
|
Registration is a side effect, which is why it is entered separately.
|
|
387
408
|
|
|
@@ -429,7 +450,10 @@ swapping `audio.src` between songs without playing goes on reporting the old pos
|
|
|
429
450
|
|
|
430
451
|
Two renderers in one document write over each other, so the module supports one. It is a constraint
|
|
431
452
|
rather than a setting, and it is stated rather than enforced: none of the points where two of them
|
|
432
|
-
collide is a crash.
|
|
453
|
+
collide is a crash. A stage renderer is the exception. It writes no scroll padding, so it can sit in
|
|
454
|
+
the same document as a scrolling one, with three rules: give both the same theme, tick both with the
|
|
455
|
+
same time and wall time in the same frame (whichever ticks first is the one that sees a seek), and if
|
|
456
|
+
the stage renderer applied the theme first, destroy it first, because its `<style>` element goes with it.
|
|
433
457
|
|
|
434
458
|
Two things are written per document and belong to whichever renderer wrote them last: the theme's
|
|
435
459
|
`<style>` element, and the scroll padding on the root. Two more are per bundle, because a settings
|
|
@@ -479,3 +503,42 @@ loading regional CJK web fonts should set `--noto-sans-universal` on these eleme
|
|
|
479
503
|
`--blyrics-font-family` and `--blyrics-translated-font-family` remain explicit theme overrides.
|
|
480
504
|
They are unset by default; custom CSS that reads them directly should use
|
|
481
505
|
`var(--blyrics-font-family, var(--blyrics-default-font-family))` for the default stack.
|
|
506
|
+
|
|
507
|
+
### Theme-provided image fills
|
|
508
|
+
|
|
509
|
+
Image highlights are optional. Enable them in a theme comment:
|
|
510
|
+
|
|
511
|
+
```css
|
|
512
|
+
/* blyrics-image-highlights = true; */
|
|
513
|
+
.blyrics-container {
|
|
514
|
+
--blyrics-highlight-image: url("my-image.jpg");
|
|
515
|
+
--blyrics-image-glow-opacity-from: 0.5;
|
|
516
|
+
--blyrics-image-glow-opacity-to: 0;
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Changing this setting requests a lyric rebuild through the existing `setTheme` return value.
|
|
521
|
+
Without it, the renderer adds no image or glow layers and keeps its shadow animation. With it,
|
|
522
|
+
the sharp fill and a separate blurred copy share the word reveal, letter motion, opacity, pause,
|
|
523
|
+
seek and playback-rate handling. Existing glow radius, duration and easing properties still apply;
|
|
524
|
+
the two image-glow opacity properties replace the solid shadow color's alpha.
|
|
525
|
+
|
|
526
|
+
The image may be an SDR texture or an HDR gain-map asset. The theme owns HDR media queries
|
|
527
|
+
and `dynamic-range-limit`; the core never opts into extra display brightness. Ordinary active
|
|
528
|
+
text color backs up the image fill, while the note keeps its original color path until its
|
|
529
|
+
SVG image loads. The note image reuses the original animated wave clip and fade group.
|
|
530
|
+
|
|
531
|
+
The glow layer is outside the paint's mask, avoiding clipped halos at word edges. Bidi-sensitive
|
|
532
|
+
runs and fragmented long words use a parallel inline text run for the glow, so the browser
|
|
533
|
+
reproduces the same ordering and wrapped fragments. Its reveal and visibility use the same
|
|
534
|
+
animation clock as the sharp text. No generated text or per-frame geometry reads are needed.
|
|
535
|
+
Joining-script word groups retain contextual joining by using whole-word sweeps instead of
|
|
536
|
+
inline-block letter waves, including when image highlights are disabled.
|
|
537
|
+
Forced colors suppress the image and halo in favor of system text colors.
|
|
538
|
+
|
|
539
|
+
For real-browser regression checks, build the package, serve the repository root and open
|
|
540
|
+
`tooling/browser/image-highlights.html`. Import `image-highlights-checks.js` from that page and
|
|
541
|
+
call `runImageHighlightChecks(testView)`. The default fixture uses an SDR image; a `theme` query
|
|
542
|
+
parameter can point at a consumer's HDR stylesheet for physical-display testing.
|
|
543
|
+
|
|
544
|
+
Words that mix RTL characters with LTR letters or numbers retain native text shaping instead of per-letter motion. Image glow, letter masks and letter motion retain their current paint during the line exit fade.
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LineData } from "./inject.js";
|
|
2
|
-
import
|
|
2
|
+
import { type StageBox, type StageMetrics } from "./stage.js";
|
|
3
|
+
import type { LyricsLayout, LyricsRendererHost, LyricSyncType, ResolvedTickOptions, TickOptions } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* Everything that belongs to a single rendered lyrics view: one scroll container, one selection,
|
|
5
6
|
* one set of pending scroll work.
|
|
@@ -69,6 +70,15 @@ export interface AnimationEngineInstance extends AnimEngineViewState {
|
|
|
69
70
|
pendingLyricsUpdateFrame: number | null;
|
|
70
71
|
learnedAnimationTimingOffsetMs: number;
|
|
71
72
|
animationTimingVisibilityLogUntil: number;
|
|
73
|
+
layout: LyricsLayout;
|
|
74
|
+
stageKey: string;
|
|
75
|
+
stageBox: StageBox | null;
|
|
76
|
+
stageMetrics: Map<HTMLElement, StageMetrics>;
|
|
77
|
+
stageY: Map<HTMLElement, number>;
|
|
78
|
+
stageMoves: Map<HTMLElement, Animation>;
|
|
79
|
+
stageFades: Map<HTMLElement, Animation>;
|
|
80
|
+
stageBlurs: Map<HTMLElement, Animation>;
|
|
81
|
+
stageFontSize: number;
|
|
72
82
|
/**
|
|
73
83
|
* Releases everything the instance holds on its window: the reduced motion listener, the tab
|
|
74
84
|
* renderer observer and any frame it still has queued.
|
|
@@ -80,7 +90,7 @@ export interface AnimationEngineInstance extends AnimEngineViewState {
|
|
|
80
90
|
* deliberately not used inside the tick, which runs per frame and per line.
|
|
81
91
|
*/
|
|
82
92
|
export declare function forEveryLiveView(runOperation: (engine: AnimationEngineInstance) => void): void;
|
|
83
|
-
export declare function createAnimationEngineInstance(engineDocument: Document, engineWindow: EngineWindow, host: LyricsRendererHost): AnimationEngineInstance;
|
|
93
|
+
export declare function createAnimationEngineInstance(engineDocument: Document, engineWindow: EngineWindow, host: LyricsRendererHost, layout?: LyricsLayout): AnimationEngineInstance;
|
|
84
94
|
/**
|
|
85
95
|
* Forgets the last player snapshot, so the next tick is treated as the first one of a new song
|
|
86
96
|
* rather than as a jump away from the previous one.
|
|
@@ -171,6 +181,10 @@ interface AnimationConfig {
|
|
|
171
181
|
swipeEndFrom: string;
|
|
172
182
|
swipeStartTo: string;
|
|
173
183
|
swipeEndTo: string;
|
|
184
|
+
imageGlowFrom: string;
|
|
185
|
+
imageGlowTo: string;
|
|
186
|
+
imageGlowOpacityFrom: string;
|
|
187
|
+
imageGlowOpacityTo: string;
|
|
174
188
|
glowFrom: string;
|
|
175
189
|
glowTo: string;
|
|
176
190
|
glowDurationRatio: number;
|
|
@@ -334,6 +348,7 @@ export type AnimationTickStatus = "ok" | "lyrics-missing";
|
|
|
334
348
|
* matching no line at all.
|
|
335
349
|
*/
|
|
336
350
|
export declare function resolveTickOptions(options: TickOptions): ResolvedTickOptions;
|
|
351
|
+
export declare function measureStage(engine: AnimationEngineInstance): void;
|
|
337
352
|
/**
|
|
338
353
|
* Renders one view against a tick with nothing left out.
|
|
339
354
|
*/
|