@braccato/core 0.1.7 → 1.0.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/LICENSE +1 -1
- package/README.md +267 -32
- package/dist/LICENSE +21 -0
- package/dist/README.md +298 -0
- package/dist/constants.d.ts +25 -0
- package/dist/constants.js +42 -0
- package/dist/element.d.ts +155 -0
- package/dist/element.js +655 -0
- package/dist/engine.d.ts +309 -0
- package/dist/engine.js +1940 -0
- package/dist/index.d.ts +12 -128
- package/dist/index.js +11 -949
- package/dist/inject.d.ts +37 -0
- package/dist/inject.js +376 -0
- package/dist/instrumental.d.ts +10 -0
- package/dist/instrumental.js +78 -0
- package/dist/renderer.d.ts +29 -0
- package/dist/renderer.js +349 -0
- package/dist/seek.d.ts +1 -0
- package/dist/seek.js +27 -0
- package/dist/styles/instrumental.css +103 -0
- package/dist/styles/lyrics.css +274 -0
- package/dist/styles/variables.css +181 -0
- package/dist/text.d.ts +11 -0
- package/dist/text.js +54 -0
- package/dist/themeSettings.d.ts +23 -0
- package/dist/themeSettings.js +126 -0
- package/dist/types.d.ts +199 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +21 -0
- package/dist/util.js +64 -0
- package/dist/view.d.ts +22 -0
- package/dist/view.js +93 -0
- package/package.json +34 -21
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/* Main container styles */
|
|
2
|
+
.blyrics-container {
|
|
3
|
+
font-family: var(--blyrics-font-family);
|
|
4
|
+
font-size: var(--blyrics-font-size);
|
|
5
|
+
font-weight: var(--blyrics-font-weight);
|
|
6
|
+
isolation: isolate;
|
|
7
|
+
line-height: var(--blyrics-line-height);
|
|
8
|
+
padding-bottom: calc(var(--blyrics-padding-bottom));
|
|
9
|
+
padding-top: 2rem;
|
|
10
|
+
position: relative !important;
|
|
11
|
+
transition: transform var(--blyrics-lyric-scroll-duration)
|
|
12
|
+
var(--blyrics-lyric-scroll-timing-function) 0s;
|
|
13
|
+
will-change: transform;
|
|
14
|
+
z-index: 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.blyrics-container > div {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
padding: var(--blyrics-padding) 0.25em !important;
|
|
21
|
+
transform: scale(var(--blyrics-scale));
|
|
22
|
+
transform-origin: left center;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.blyrics--active {
|
|
26
|
+
cursor: default;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.blyrics--line {
|
|
30
|
+
color: var(--blyrics-lyric-inactive-color);
|
|
31
|
+
max-width: 100%;
|
|
32
|
+
unicode-bidi: plaintext;
|
|
33
|
+
white-space: normal;
|
|
34
|
+
word-break: normal;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.blyrics-line-main {
|
|
38
|
+
display: block;
|
|
39
|
+
max-width: 100%;
|
|
40
|
+
overflow-wrap: normal;
|
|
41
|
+
text-align: inherit;
|
|
42
|
+
unicode-bidi: plaintext;
|
|
43
|
+
white-space: normal;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.blyrics-background-line {
|
|
47
|
+
display: block;
|
|
48
|
+
margin-top: 0.1em;
|
|
49
|
+
max-width: 100%;
|
|
50
|
+
text-align: inherit;
|
|
51
|
+
unicode-bidi: plaintext;
|
|
52
|
+
white-space: normal;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.blyrics-bidi-run {
|
|
56
|
+
display: inline;
|
|
57
|
+
max-width: 100%;
|
|
58
|
+
unicode-bidi: normal;
|
|
59
|
+
white-space: inherit;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.blyrics-word-group {
|
|
63
|
+
display: inline-block;
|
|
64
|
+
max-width: 100%;
|
|
65
|
+
unicode-bidi: normal;
|
|
66
|
+
vertical-align: baseline;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.blyrics-word-group-long {
|
|
71
|
+
display: inline;
|
|
72
|
+
overflow-wrap: anywhere;
|
|
73
|
+
white-space: normal;
|
|
74
|
+
word-break: break-word;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.blyrics--word {
|
|
78
|
+
color: var(--blyrics-lyric-inactive-color);
|
|
79
|
+
display: inline;
|
|
80
|
+
position: relative;
|
|
81
|
+
transform: translateY(0);
|
|
82
|
+
unicode-bidi: normal;
|
|
83
|
+
white-space: inherit;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.blyrics-word-group:not(.blyrics-word-group-long) .blyrics--word {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.blyrics-bidi-sensitive .blyrics-word-group,
|
|
91
|
+
.blyrics-bidi-sensitive .blyrics-word-group-long {
|
|
92
|
+
display: contents;
|
|
93
|
+
max-width: none;
|
|
94
|
+
overflow-wrap: normal;
|
|
95
|
+
unicode-bidi: normal;
|
|
96
|
+
white-space: normal;
|
|
97
|
+
word-break: normal;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.blyrics-bidi-sensitive .blyrics--word,
|
|
101
|
+
.blyrics-bidi-sensitive .blyrics-word-group:not(.blyrics-word-group-long) .blyrics--word {
|
|
102
|
+
display: inline;
|
|
103
|
+
unicode-bidi: normal;
|
|
104
|
+
white-space: inherit;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@property --lyric-transition-amount-start {
|
|
108
|
+
syntax: "<number>";
|
|
109
|
+
inherits: false;
|
|
110
|
+
initial-value: -0.2;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@property --lyric-transition-amount-end {
|
|
114
|
+
syntax: "<number>";
|
|
115
|
+
inherits: false;
|
|
116
|
+
initial-value: -0.1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.blyrics--word::after,
|
|
120
|
+
.blyrics-word-highlight {
|
|
121
|
+
color: transparent;
|
|
122
|
+
background-image: linear-gradient(
|
|
123
|
+
90deg,
|
|
124
|
+
var(--blyrics-lyric-active-color)
|
|
125
|
+
calc(
|
|
126
|
+
100% * var(--lyric-transition-amount-start) - 4rem *
|
|
127
|
+
var(--lyric-transition-amount-start) + 2rem
|
|
128
|
+
),
|
|
129
|
+
#00000000
|
|
130
|
+
calc(
|
|
131
|
+
100% * var(--lyric-transition-amount-end) - 4rem *
|
|
132
|
+
var(--lyric-transition-amount-end) + 2rem + 1px
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
background-clip: text;
|
|
136
|
+
box-sizing: content-box;
|
|
137
|
+
filter: drop-shadow(0 0 0 var(--blyrics-glow-color));
|
|
138
|
+
left: -2rem;
|
|
139
|
+
opacity: 0;
|
|
140
|
+
padding: 2rem;
|
|
141
|
+
pointer-events: none;
|
|
142
|
+
position: absolute;
|
|
143
|
+
top: -2rem;
|
|
144
|
+
white-space: inherit;
|
|
145
|
+
width: 100%;
|
|
146
|
+
--lyric-transition-amount-start: var(
|
|
147
|
+
--blyrics-highlight-swipe-start-from,
|
|
148
|
+
-0.2
|
|
149
|
+
);
|
|
150
|
+
--lyric-transition-amount-end: var(--blyrics-highlight-swipe-end-from, -0.1);
|
|
151
|
+
}
|
|
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 {
|
|
169
|
+
background-image: linear-gradient(
|
|
170
|
+
270deg,
|
|
171
|
+
var(--blyrics-lyric-active-color)
|
|
172
|
+
calc(
|
|
173
|
+
100% * var(--lyric-transition-amount-start) - 4rem *
|
|
174
|
+
var(--lyric-transition-amount-start) + 2rem
|
|
175
|
+
),
|
|
176
|
+
#00000000
|
|
177
|
+
calc(
|
|
178
|
+
100% * var(--lyric-transition-amount-end) - 4rem *
|
|
179
|
+
var(--lyric-transition-amount-end) + 2rem + 1px
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
[blyrics-alt-hover] .blyrics--word:hover {
|
|
185
|
+
text-decoration: underline;
|
|
186
|
+
text-underline-offset: 0.15em;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.blyrics--line > .blyrics-line-main > .blyrics-background-lyric,
|
|
190
|
+
.blyrics--line > .blyrics-background-line > .blyrics-background-lyric,
|
|
191
|
+
.blyrics--line > .blyrics-line-main > .blyrics-bidi-run > .blyrics-background-lyric,
|
|
192
|
+
.blyrics--line > .blyrics-background-line > .blyrics-bidi-run > .blyrics-background-lyric {
|
|
193
|
+
font-size: 0.75em;
|
|
194
|
+
font-weight: 500;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.blyrics--line > .blyrics-line-main > .blyrics-word-group.blyrics-background-lyric,
|
|
198
|
+
.blyrics--line > .blyrics-background-line > .blyrics-word-group.blyrics-background-lyric,
|
|
199
|
+
.blyrics--line > .blyrics-line-main > .blyrics-bidi-run > .blyrics-word-group.blyrics-background-lyric,
|
|
200
|
+
.blyrics--line > .blyrics-background-line > .blyrics-bidi-run > .blyrics-word-group.blyrics-background-lyric {
|
|
201
|
+
color: color-mix(in srgb, var(--blyrics-lyric-inactive-color) 78%, transparent);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.blyrics-explicit {
|
|
205
|
+
text-decoration: line-through;
|
|
206
|
+
text-decoration-thickness: 0.08em;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.blyrics--translated,
|
|
210
|
+
.blyrics--romanized {
|
|
211
|
+
color: var(--blyrics-translated-color);
|
|
212
|
+
display: block;
|
|
213
|
+
font-family: var(--blyrics-translated-font-family);
|
|
214
|
+
font-size: var(--blyrics-translated-font-size);
|
|
215
|
+
font-weight: var(--blyrics-translated-font-weight);
|
|
216
|
+
line-height: 1.1;
|
|
217
|
+
margin-top: 8px;
|
|
218
|
+
max-width: 100%;
|
|
219
|
+
text-align: inherit;
|
|
220
|
+
unicode-bidi: plaintext;
|
|
221
|
+
white-space: normal;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.blyrics--romanized {
|
|
225
|
+
background: rgba(255, 255, 255, 0.05);
|
|
226
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
227
|
+
border-radius: 1rem;
|
|
228
|
+
box-sizing: border-box;
|
|
229
|
+
display: block;
|
|
230
|
+
font-size: calc(var(--blyrics-translated-font-size) / 1.25);
|
|
231
|
+
padding: 0.375rem 0.75rem;
|
|
232
|
+
width: fit-content;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.blyrics--romanized .blyrics-line-main {
|
|
236
|
+
display: inline;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.blyrics-rtl {
|
|
240
|
+
direction: rtl;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.blyrics--line[data-direction="rtl"] {
|
|
244
|
+
text-align: right;
|
|
245
|
+
transform-origin: right center;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Secondary and tertiary vocals */
|
|
249
|
+
.blyrics--line[data-agent="v2"],
|
|
250
|
+
.blyrics--line[data-agent="v3"] {
|
|
251
|
+
text-align: right;
|
|
252
|
+
transform-origin: right center !important;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* All vocals together */
|
|
256
|
+
.blyrics--line[data-agent="v1000"] {
|
|
257
|
+
text-align: center;
|
|
258
|
+
transform-origin: center center !important;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.blyrics--line[data-agent="v2"] .blyrics--romanized,
|
|
262
|
+
.blyrics--line[data-agent="v3"] .blyrics--romanized,
|
|
263
|
+
.blyrics--line[data-direction="rtl"] .blyrics--romanized {
|
|
264
|
+
margin-left: auto;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.blyrics--line[data-agent="v1000"] .blyrics--romanized {
|
|
268
|
+
margin-left: auto;
|
|
269
|
+
margin-right: auto;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#blyrics-wrapper {
|
|
273
|
+
margin-top: 0;
|
|
274
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Colors */
|
|
3
|
+
--blyrics-ui-text-color: var(
|
|
4
|
+
--blyrics-text-color,
|
|
5
|
+
color(display-p3 1 1 1 / 1)
|
|
6
|
+
);
|
|
7
|
+
--blyrics-glow-color: var(
|
|
8
|
+
--blyrics-highlight-color,
|
|
9
|
+
color(display-p3 1 1 1 / 0.5)
|
|
10
|
+
);
|
|
11
|
+
--blyrics-error-color: color(display-p3 0.992 0.882 0.882);
|
|
12
|
+
--blyrics-footer-bg-color: hsla(0, 0%, 100%, 0.1);
|
|
13
|
+
--blyrics-footer-border-color: hsla(0, 0%, 100%, 0.1);
|
|
14
|
+
--blyrics-footer-text-color: #aaa;
|
|
15
|
+
--blyrics-footer-link-color: #fff;
|
|
16
|
+
--blyrics-discord-hover-color: #5865f2;
|
|
17
|
+
--blyrics-vote-hover-color: hsla(0, 0%, 100%, 0.2);
|
|
18
|
+
--blyrics-lyric-active-color: var(
|
|
19
|
+
--blyrics-text-color,
|
|
20
|
+
color(display-p3 1 1 1 / var(--blyrics-active-opacity, 1))
|
|
21
|
+
);
|
|
22
|
+
--blyrics-lyric-inactive-color: color(
|
|
23
|
+
from var(--blyrics-text-color, color(display-p3 1 1 1)) display-p3 r g b /
|
|
24
|
+
var(--blyrics-inactive-opacity, 0.3)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
--blyrics-richsync-timing-offset: 0.150s;
|
|
28
|
+
--blyrics-timing-offset: 0.115s;
|
|
29
|
+
|
|
30
|
+
--blyrics-scroll-timing-offset: 0.5s;
|
|
31
|
+
|
|
32
|
+
/* Typography */
|
|
33
|
+
/* The stack behind --noto-sans-universal is 32 Noto families and the extension is what loads
|
|
34
|
+
them, so the declaration stays in misc.css, where two published themes select on it by name.
|
|
35
|
+
Inside the extension the fallback is unreachable. It is one real family rather than a generic
|
|
36
|
+
so that a standalone consumer without it still falls through to the rest of this stack; the
|
|
37
|
+
CJK, Devanagari, Arabic, Hebrew and Thai coverage is what such a consumer gives up either
|
|
38
|
+
way. */
|
|
39
|
+
--blyrics-font-family:
|
|
40
|
+
Satoshi, var(--noto-sans-universal, "Noto Sans"), Avenir, -apple-system,
|
|
41
|
+
BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans,
|
|
42
|
+
Helvetica Neue, sans-serif;
|
|
43
|
+
--blyrics-font-size: 3rem;
|
|
44
|
+
--blyrics-font-weight: 700;
|
|
45
|
+
--blyrics-line-height: 1.333;
|
|
46
|
+
|
|
47
|
+
--blyrics-translated-font-size: 2rem;
|
|
48
|
+
--blyrics-translated-font-weight: 600;
|
|
49
|
+
--blyrics-translated-font-family: var(--blyrics-font-family);
|
|
50
|
+
--blyrics-translated-color: color(
|
|
51
|
+
display-p3 1 1 1 / var(--blyrics-translated-opacity, 0.6)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
--blyrics-footer-font-family: Satoshi, Roboto, Noto Naskh Arabic UI, Arial, sans-serif;
|
|
55
|
+
--blyrics-footer-font-size: 14px;
|
|
56
|
+
--blyrics-footer-font-weight: 400;
|
|
57
|
+
|
|
58
|
+
/* Animations */
|
|
59
|
+
--blyrics-animate-line-scale: 1;
|
|
60
|
+
--blyrics-animate-word-wobble: 1;
|
|
61
|
+
--blyrics-animate-highlight-swipe: 1;
|
|
62
|
+
--blyrics-animate-highlight-glow: 1;
|
|
63
|
+
--blyrics-animate-highlight-fade: 1;
|
|
64
|
+
--blyrics-animate-scroll: 1;
|
|
65
|
+
--blyrics-animate-instrumental: 1;
|
|
66
|
+
|
|
67
|
+
--blyrics-loader-transition-duration: 0.6s;
|
|
68
|
+
--blyrics-loader-transition-easing: cubic-bezier(0.22, 1, 0.36, 1);
|
|
69
|
+
--blyrics-scale-transition-duration: var(
|
|
70
|
+
--blyrics-transition-duration,
|
|
71
|
+
0.166s
|
|
72
|
+
);
|
|
73
|
+
--blyrics-line-enter-transform-from: scale(var(--blyrics-scale));
|
|
74
|
+
--blyrics-line-enter-transform-to: scale(var(--blyrics-active-scale));
|
|
75
|
+
--blyrics-line-exit-transform-from: scale(var(--blyrics-active-scale));
|
|
76
|
+
--blyrics-line-exit-transform-to: scale(var(--blyrics-scale));
|
|
77
|
+
--blyrics-line-enter-easing: ease;
|
|
78
|
+
--blyrics-line-exit-easing: ease;
|
|
79
|
+
|
|
80
|
+
--blyrics-lyric-highlight-fade-in-duration: var(
|
|
81
|
+
--blyrics-opacity-transition,
|
|
82
|
+
0.33s
|
|
83
|
+
);
|
|
84
|
+
--blyrics-lyric-highlight-fade-out-duration: var(
|
|
85
|
+
--blyrics-opacity-transition,
|
|
86
|
+
0.5s
|
|
87
|
+
);
|
|
88
|
+
--blyrics-lyric-highlight-fade-in-easing: ease;
|
|
89
|
+
--blyrics-lyric-highlight-fade-out-easing: ease;
|
|
90
|
+
--blyrics-highlight-swipe-easing: linear;
|
|
91
|
+
--blyrics-highlight-swipe-start-from: -0.2;
|
|
92
|
+
--blyrics-highlight-swipe-end-from: -0.1;
|
|
93
|
+
--blyrics-highlight-swipe-start-to: 1.4;
|
|
94
|
+
--blyrics-highlight-swipe-end-to: 1.5;
|
|
95
|
+
--blyrics-highlight-glow-radius-from: 0.8rem;
|
|
96
|
+
--blyrics-highlight-glow-radius-to: 0;
|
|
97
|
+
--blyrics-highlight-glow-duration-ratio: 1.2;
|
|
98
|
+
--blyrics-highlight-glow-min-duration: 1.2s;
|
|
99
|
+
--blyrics-highlight-glow-easing: ease;
|
|
100
|
+
|
|
101
|
+
--blyrics-wobble-duration: 1s;
|
|
102
|
+
--blyrics-word-wobble-transform-from: scaleX(1);
|
|
103
|
+
--blyrics-word-wobble-transform-peak: translateX(0.05em) scaleX(1.025);
|
|
104
|
+
--blyrics-word-wobble-transform-settle: translateX(0) scaleX(1);
|
|
105
|
+
--blyrics-word-wobble-transform-to: scaleX(1);
|
|
106
|
+
--blyrics-word-wobble-peak-offset: 0.125;
|
|
107
|
+
--blyrics-word-wobble-settle-offset: 0.75;
|
|
108
|
+
--blyrics-word-wobble-easing: ease;
|
|
109
|
+
--blyrics-word-wobble-peak-easing: ease-in-out;
|
|
110
|
+
--blyrics-word-wobble-end-easing: ease-out;
|
|
111
|
+
|
|
112
|
+
--blyrics-instrumental-fill-fade-duration: 150ms;
|
|
113
|
+
--blyrics-instrumental-fill-fade-easing: ease;
|
|
114
|
+
--blyrics-instrumental-fill-transform-from: translateY(78%);
|
|
115
|
+
--blyrics-instrumental-fill-transform-to: translateY(-4%);
|
|
116
|
+
--blyrics-instrumental-fill-easing: linear;
|
|
117
|
+
--blyrics-instrumental-wave-transform-from: scaleY(1.2);
|
|
118
|
+
--blyrics-instrumental-wave-transform-to: scaleY(0.0001);
|
|
119
|
+
--blyrics-instrumental-wave-easing: ease-in;
|
|
120
|
+
--blyrics-instrumental-wave-oscillation-duration: 1.25s;
|
|
121
|
+
--blyrics-instrumental-wave-oscillation-easing: ease-in-out;
|
|
122
|
+
|
|
123
|
+
/* Layout */
|
|
124
|
+
--blyrics-padding: 2rem;
|
|
125
|
+
--blyrics-margin: 2rem;
|
|
126
|
+
--blyrics-small-border-radius: 1rem;
|
|
127
|
+
--blyrics-border-radius: 1000rem;
|
|
128
|
+
|
|
129
|
+
/* Effects */
|
|
130
|
+
--blyrics-blur-amount: 30px;
|
|
131
|
+
--blyrics-scale: 0.95;
|
|
132
|
+
--blyrics-active-scale: 1;
|
|
133
|
+
--blyrics-error-opacity: 0.33;
|
|
134
|
+
--blyrics-background-blur: 100px;
|
|
135
|
+
--blyrics-background-saturate: 2;
|
|
136
|
+
|
|
137
|
+
/* Lyric Transition Properties */
|
|
138
|
+
--blyrics-lyric-scroll-duration: var(
|
|
139
|
+
--blyrics-lyric-transition-duration,
|
|
140
|
+
650ms
|
|
141
|
+
);
|
|
142
|
+
--blyrics-lyric-scroll-timing-function: var(
|
|
143
|
+
--blyrics-lyric-transition-timing-function,
|
|
144
|
+
cubic-bezier(0.86, 0, 0.2, 1)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
/* Gradient stops */
|
|
148
|
+
--blyrics-gradient-stops:
|
|
149
|
+
transparent 0%, rgba(0, 0, 0, 0.013) 8.1%, rgba(0, 0, 0, 0.049) 15.5%,
|
|
150
|
+
rgba(0, 0, 0, 0.104) 22.5%, rgba(0, 0, 0, 0.175) 29%,
|
|
151
|
+
rgba(0, 0, 0, 0.259) 35.3%, rgba(0, 0, 0, 0.352) 41.2%,
|
|
152
|
+
rgba(0, 0, 0, 0.45) 47.1%, rgba(0, 0, 0, 0.55) 52.9%,
|
|
153
|
+
rgba(0, 0, 0, 0.648) 58.8%, rgba(0, 0, 0, 0.741) 64.7%,
|
|
154
|
+
rgba(0, 0, 0, 0.825) 71%, rgba(0, 0, 0, 0.896) 77.5%,
|
|
155
|
+
rgba(0, 0, 0, 0.951) 84.5%, rgba(0, 0, 0, 0.987) 91.9%, #000 100%;
|
|
156
|
+
|
|
157
|
+
--blyrics-shimmer-gradient: linear-gradient(
|
|
158
|
+
110deg,
|
|
159
|
+
color-mix(in srgb, var(--blyrics-ui-text-color) 50%, transparent) 30%,
|
|
160
|
+
var(--blyrics-ui-text-color) 50%,
|
|
161
|
+
color-mix(in srgb, var(--blyrics-ui-text-color) 50%, transparent) 70%
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Reduced-motion disables the vestibular triggers (zoom-on-active line scale,
|
|
166
|
+
translateX word wobble, and instrumental fill/wave motion) and flattens the
|
|
167
|
+
active/inactive scale delta. Smooth scroll remains enabled, but the animation
|
|
168
|
+
engine suppresses side-specific line-scroll differential effects.
|
|
169
|
+
Everything else is left on by design:
|
|
170
|
+
- karaoke swipe + highlight glow: gradient mask / drop-shadow fills, not movement.
|
|
171
|
+
- highlight fade-in / fade-out: opacity transitions are not motion. */
|
|
172
|
+
@media (prefers-reduced-motion: reduce) {
|
|
173
|
+
:root {
|
|
174
|
+
--blyrics-animate-line-scale: 0;
|
|
175
|
+
--blyrics-animate-word-wobble: 0;
|
|
176
|
+
--blyrics-animate-instrumental: 0;
|
|
177
|
+
|
|
178
|
+
--blyrics-scale: 1;
|
|
179
|
+
--blyrics-active-scale: 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const testRtl: (text: string) => boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a given string contains any non-Latin characters.
|
|
4
|
+
* @param text The string to check.
|
|
5
|
+
* @returns True if a non-Latin character is found, otherwise false.
|
|
6
|
+
*/
|
|
7
|
+
export declare function containsNonLatin(text: string): boolean;
|
|
8
|
+
declare const SCRIPT_TO_LANG: readonly [readonly [RegExp, "ja"], readonly [RegExp, "ko"], readonly [RegExp, "zh"], readonly [RegExp, "ru"], readonly [RegExp, "hi"], readonly [RegExp, "ar"], readonly [RegExp, "th"], readonly [RegExp, "el"], readonly [RegExp, "he"], readonly [RegExp, "bn"], readonly [RegExp, "ta"], readonly [RegExp, "te"], readonly [RegExp, "ml"], readonly [RegExp, "kn"], readonly [RegExp, "gu"], readonly [RegExp, "pa"], readonly [RegExp, "si"], readonly [RegExp, "my"], readonly [RegExp, "ka"], readonly [RegExp, "km"], readonly [RegExp, "lo"]];
|
|
9
|
+
type NonLatinLanguageCode = (typeof SCRIPT_TO_LANG)[number][1];
|
|
10
|
+
export declare function detectNonLatinLanguage(text: string): NonLatinLanguageCode | null;
|
|
11
|
+
export {};
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Script detection for the text the module renders. Pure string work, no DOM and no host state.
|
|
2
|
+
export const testRtl = (text) => /[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}]/u.test(text);
|
|
3
|
+
/**
|
|
4
|
+
* This regex is designed to detect any characters that are outside of the
|
|
5
|
+
* standard "Basic Latin" and "Latin-1 Supplement" Unicode blocks, as well
|
|
6
|
+
* as common "smart" punctuation like curved quotes.
|
|
7
|
+
*
|
|
8
|
+
* How it works:
|
|
9
|
+
* [^...] - This is a negated set, which matches any character NOT inside the brackets.
|
|
10
|
+
* \x00-\xFF - This range covers both the "Basic Latin" (ASCII) and "Latin-1 Supplement"
|
|
11
|
+
* blocks. This includes English letters, numbers, common punctuation, and
|
|
12
|
+
* most accented characters used in Western European languages (e.g., á, ö, ñ).
|
|
13
|
+
* ‘-” - This range covers common "smart" or curly punctuation, including single
|
|
14
|
+
* and double quotation marks/apostrophes (‘, ’, “, ”).
|
|
15
|
+
*/
|
|
16
|
+
const nonLatinRegex = /[^\p{Script_Extensions=Latin}\p{Script_Extensions=Common}]/u;
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a given string contains any non-Latin characters.
|
|
19
|
+
* @param text The string to check.
|
|
20
|
+
* @returns True if a non-Latin character is found, otherwise false.
|
|
21
|
+
*/
|
|
22
|
+
export function containsNonLatin(text) {
|
|
23
|
+
return nonLatinRegex.test(text);
|
|
24
|
+
}
|
|
25
|
+
const SCRIPT_TO_LANG = [
|
|
26
|
+
[/\p{Script=Hiragana}|\p{Script=Katakana}/u, "ja"],
|
|
27
|
+
[/\p{Script=Hangul}/u, "ko"],
|
|
28
|
+
[/\p{Script=Han}/u, "zh"],
|
|
29
|
+
[/\p{Script=Cyrillic}/u, "ru"],
|
|
30
|
+
[/\p{Script=Devanagari}/u, "hi"],
|
|
31
|
+
[/\p{Script=Arabic}/u, "ar"],
|
|
32
|
+
[/\p{Script=Thai}/u, "th"],
|
|
33
|
+
[/\p{Script=Greek}/u, "el"],
|
|
34
|
+
[/\p{Script=Hebrew}/u, "he"],
|
|
35
|
+
[/\p{Script=Bengali}/u, "bn"],
|
|
36
|
+
[/\p{Script=Tamil}/u, "ta"],
|
|
37
|
+
[/\p{Script=Telugu}/u, "te"],
|
|
38
|
+
[/\p{Script=Malayalam}/u, "ml"],
|
|
39
|
+
[/\p{Script=Kannada}/u, "kn"],
|
|
40
|
+
[/\p{Script=Gujarati}/u, "gu"],
|
|
41
|
+
[/\p{Script=Gurmukhi}/u, "pa"],
|
|
42
|
+
[/\p{Script=Sinhala}/u, "si"],
|
|
43
|
+
[/\p{Script=Myanmar}/u, "my"],
|
|
44
|
+
[/\p{Script=Georgian}/u, "ka"],
|
|
45
|
+
[/\p{Script=Khmer}/u, "km"],
|
|
46
|
+
[/\p{Script=Lao}/u, "lo"],
|
|
47
|
+
];
|
|
48
|
+
export function detectNonLatinLanguage(text) {
|
|
49
|
+
for (const [regex, lang] of SCRIPT_TO_LANG) {
|
|
50
|
+
if (regex.test(text))
|
|
51
|
+
return lang;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @public */
|
|
2
|
+
export declare class Setting {
|
|
3
|
+
readonly type: "number" | "boolean" | "string";
|
|
4
|
+
value: number | boolean | string;
|
|
5
|
+
readonly defaultValue: number | boolean | string;
|
|
6
|
+
readonly requiresLyricReload: boolean;
|
|
7
|
+
private manuallySet;
|
|
8
|
+
constructor(type: "number" | "boolean" | "string", value: number | boolean | string, defaultValue: number | boolean | string, requiresLyricReload: boolean);
|
|
9
|
+
getNumberValue(): number;
|
|
10
|
+
getBooleanValue(): boolean;
|
|
11
|
+
getStringValue(): string;
|
|
12
|
+
isManuallySet(): boolean;
|
|
13
|
+
setManuallySet(manuallySet: boolean): void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The `blyrics-*` configuration a compiled stylesheet declares, in declaration order, last one
|
|
17
|
+
* winning. Lives here rather than beside the renderer because it depends on nothing and its only
|
|
18
|
+
* consumer is `setThemeSettings`: a consumer that compiles a theme somewhere the renderer does not
|
|
19
|
+
* run can read the settings out of it without pulling the engine in.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseThemeConfig(css: string): Map<string, string>;
|
|
22
|
+
export declare function registerThemeSetting(key: string, defaultValue: number | boolean | string, requiresLyricReload?: boolean): Setting;
|
|
23
|
+
export declare function setThemeSettings(map: Map<string, string>): boolean;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// The renderer declares the theme settings its own code reads, so a setting and the code that
|
|
2
|
+
// consumes it stay together. Applying settings reports whether the lyrics need reloading; the host
|
|
3
|
+
// decides what to do about it.
|
|
4
|
+
//
|
|
5
|
+
// What arrives here is compiled CSS rather than theme source. Better Lyrics themes are written in
|
|
6
|
+
// RICS and compiled with the `rics` package first, which is the consumer's dependency and not this
|
|
7
|
+
// module's: nothing under this directory ships with any.
|
|
8
|
+
let keyToSettingMap = new Map();
|
|
9
|
+
// -- Setting --------------------------------------------
|
|
10
|
+
// `registerThemeSetting` returns one of these, so a consumer of this leaf has to be able to spell
|
|
11
|
+
// it. `@public` keeps knip off an export this repo only ever names through inference.
|
|
12
|
+
/** @public */
|
|
13
|
+
export class Setting {
|
|
14
|
+
type;
|
|
15
|
+
value;
|
|
16
|
+
defaultValue;
|
|
17
|
+
requiresLyricReload;
|
|
18
|
+
manuallySet = false;
|
|
19
|
+
constructor(type, value, defaultValue, requiresLyricReload) {
|
|
20
|
+
this.type = type;
|
|
21
|
+
this.value = value;
|
|
22
|
+
this.defaultValue = defaultValue;
|
|
23
|
+
this.requiresLyricReload = requiresLyricReload;
|
|
24
|
+
}
|
|
25
|
+
getNumberValue() {
|
|
26
|
+
return this.value;
|
|
27
|
+
}
|
|
28
|
+
getBooleanValue() {
|
|
29
|
+
return this.value;
|
|
30
|
+
}
|
|
31
|
+
getStringValue() {
|
|
32
|
+
return this.value;
|
|
33
|
+
}
|
|
34
|
+
isManuallySet() {
|
|
35
|
+
return this.manuallySet;
|
|
36
|
+
}
|
|
37
|
+
setManuallySet(manuallySet) {
|
|
38
|
+
this.manuallySet = manuallySet;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// -- Parsing a theme --------------------------------------------
|
|
42
|
+
// A theme is a stylesheet, and it configures the renderer through comments inside it, in the form
|
|
43
|
+
// `blyrics-some-key = value;`. Only inside comments: everything outside one is CSS the browser is
|
|
44
|
+
// going to read, and a stylesheet must not be able to configure the module by accident.
|
|
45
|
+
const THEME_COMMENT_PATTERN = /\/\*([\s\S]*?)\*\//g;
|
|
46
|
+
const THEME_SETTING_PATTERN = /(blyrics-[\w-]+)\s*=\s*([^;]+);/g;
|
|
47
|
+
/**
|
|
48
|
+
* The `blyrics-*` configuration a compiled stylesheet declares, in declaration order, last one
|
|
49
|
+
* winning. Lives here rather than beside the renderer because it depends on nothing and its only
|
|
50
|
+
* consumer is `setThemeSettings`: a consumer that compiles a theme somewhere the renderer does not
|
|
51
|
+
* run can read the settings out of it without pulling the engine in.
|
|
52
|
+
*/
|
|
53
|
+
export function parseThemeConfig(css) {
|
|
54
|
+
const config = new Map();
|
|
55
|
+
// `matchAll` works off a copy of the pattern, so neither of the two shared at module scope can
|
|
56
|
+
// carry a `lastIndex` out of one stylesheet and into the next however these loops are nested.
|
|
57
|
+
// Driving them with `exec` is correct too, as long as every loop is left running to exhaustion:
|
|
58
|
+
// this is written not to depend on that.
|
|
59
|
+
for (const [, comment] of css.matchAll(THEME_COMMENT_PATTERN)) {
|
|
60
|
+
for (const [, key, value] of comment.matchAll(THEME_SETTING_PATTERN)) {
|
|
61
|
+
config.set(key, value.trim());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
// -- Registry --------------------------------------------
|
|
67
|
+
// Registration runs at module scope, which evaluates once per bundle however many views that bundle
|
|
68
|
+
// is rendering, so a setting's value is shared by all of them rather than held per instance. The
|
|
69
|
+
// unit is a bundle rather than a document: this extension bundles the module into the isolated world
|
|
70
|
+
// and the page world separately, and each of those registries needs its own theme applied to it or
|
|
71
|
+
// one view renders against defaults while the other renders against the theme.
|
|
72
|
+
export function registerThemeSetting(key, defaultValue, requiresLyricReload = false) {
|
|
73
|
+
let type = typeof defaultValue;
|
|
74
|
+
if (type !== "number" && type !== "boolean" && type !== "string") {
|
|
75
|
+
throw new Error("Invalid type for theme setting");
|
|
76
|
+
}
|
|
77
|
+
let setting = new Setting(type, defaultValue, defaultValue, requiresLyricReload);
|
|
78
|
+
keyToSettingMap.set(key, setting);
|
|
79
|
+
return setting;
|
|
80
|
+
}
|
|
81
|
+
// Returns whether a setting flagged requiresLyricReload changed, so the caller can reload.
|
|
82
|
+
export function setThemeSettings(map) {
|
|
83
|
+
let needsLyricReload = false;
|
|
84
|
+
map.forEach((value, key) => {
|
|
85
|
+
let setting = keyToSettingMap.get(key);
|
|
86
|
+
if (setting) {
|
|
87
|
+
let lastValue = setting.value;
|
|
88
|
+
if (setting.type === "number") {
|
|
89
|
+
const parsed = parseFloat(value);
|
|
90
|
+
if (isNaN(parsed)) {
|
|
91
|
+
setting.value = setting.defaultValue;
|
|
92
|
+
setting.setManuallySet(false);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
setting.value = parsed;
|
|
96
|
+
setting.setManuallySet(true);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (setting.type === "boolean") {
|
|
100
|
+
setting.value = value.toLowerCase() === "true";
|
|
101
|
+
setting.setManuallySet(true);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
setting.value = value;
|
|
105
|
+
setting.setManuallySet(true);
|
|
106
|
+
}
|
|
107
|
+
if (setting.requiresLyricReload && lastValue !== setting.value) {
|
|
108
|
+
needsLyricReload = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// second pass reset undefined values to their default values
|
|
113
|
+
for (const [key, setting] of keyToSettingMap.entries()) {
|
|
114
|
+
if (!map.has(key) && setting.value !== setting.defaultValue) {
|
|
115
|
+
setting.value = setting.defaultValue;
|
|
116
|
+
setting.setManuallySet(false);
|
|
117
|
+
if (setting.requiresLyricReload) {
|
|
118
|
+
needsLyricReload = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (!map.has(key)) {
|
|
122
|
+
setting.setManuallySet(false);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return needsLyricReload;
|
|
126
|
+
}
|