@livepeer-frameworks/player-wc 0.1.2 → 0.1.3

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.
Files changed (116) hide show
  1. package/dist/cjs/components/fw-dev-mode-panel.js +845 -212
  2. package/dist/cjs/components/fw-dev-mode-panel.js.map +1 -1
  3. package/dist/cjs/components/fw-dvd-logo.js +211 -0
  4. package/dist/cjs/components/fw-dvd-logo.js.map +1 -0
  5. package/dist/cjs/components/fw-idle-screen.js +641 -97
  6. package/dist/cjs/components/fw-idle-screen.js.map +1 -1
  7. package/dist/cjs/components/fw-loading-screen.js +513 -0
  8. package/dist/cjs/components/fw-loading-screen.js.map +1 -0
  9. package/dist/cjs/components/fw-player-controls.js +347 -173
  10. package/dist/cjs/components/fw-player-controls.js.map +1 -1
  11. package/dist/cjs/components/fw-player.js +460 -60
  12. package/dist/cjs/components/fw-player.js.map +1 -1
  13. package/dist/cjs/components/fw-seek-bar.js +292 -142
  14. package/dist/cjs/components/fw-seek-bar.js.map +1 -1
  15. package/dist/cjs/components/fw-settings-menu.js +191 -81
  16. package/dist/cjs/components/fw-settings-menu.js.map +1 -1
  17. package/dist/cjs/components/fw-stats-panel.js +134 -70
  18. package/dist/cjs/components/fw-stats-panel.js.map +1 -1
  19. package/dist/cjs/components/fw-stream-state-overlay.js +338 -0
  20. package/dist/cjs/components/fw-stream-state-overlay.js.map +1 -0
  21. package/dist/cjs/components/fw-subtitle-renderer.js +174 -27
  22. package/dist/cjs/components/fw-subtitle-renderer.js.map +1 -1
  23. package/dist/cjs/components/fw-thumbnail-overlay.js +161 -0
  24. package/dist/cjs/components/fw-thumbnail-overlay.js.map +1 -0
  25. package/dist/cjs/components/fw-volume-control.js +150 -69
  26. package/dist/cjs/components/fw-volume-control.js.map +1 -1
  27. package/dist/cjs/components/shared/hitmarker-audio.js +76 -0
  28. package/dist/cjs/components/shared/hitmarker-audio.js.map +1 -0
  29. package/dist/cjs/constants/media-assets.js +11 -0
  30. package/dist/cjs/constants/media-assets.js.map +1 -0
  31. package/dist/cjs/controllers/player-controller-host.js +28 -1
  32. package/dist/cjs/controllers/player-controller-host.js.map +1 -1
  33. package/dist/cjs/define.js +8 -0
  34. package/dist/cjs/define.js.map +1 -1
  35. package/dist/cjs/icons/index.js +27 -0
  36. package/dist/cjs/icons/index.js.map +1 -1
  37. package/dist/cjs/index.js +20 -0
  38. package/dist/cjs/index.js.map +1 -1
  39. package/dist/esm/components/fw-dev-mode-panel.js +846 -213
  40. package/dist/esm/components/fw-dev-mode-panel.js.map +1 -1
  41. package/dist/esm/components/fw-dvd-logo.js +211 -0
  42. package/dist/esm/components/fw-dvd-logo.js.map +1 -0
  43. package/dist/esm/components/fw-idle-screen.js +643 -99
  44. package/dist/esm/components/fw-idle-screen.js.map +1 -1
  45. package/dist/esm/components/fw-loading-screen.js +513 -0
  46. package/dist/esm/components/fw-loading-screen.js.map +1 -0
  47. package/dist/esm/components/fw-player-controls.js +348 -174
  48. package/dist/esm/components/fw-player-controls.js.map +1 -1
  49. package/dist/esm/components/fw-player.js +460 -60
  50. package/dist/esm/components/fw-player.js.map +1 -1
  51. package/dist/esm/components/fw-seek-bar.js +293 -143
  52. package/dist/esm/components/fw-seek-bar.js.map +1 -1
  53. package/dist/esm/components/fw-settings-menu.js +192 -82
  54. package/dist/esm/components/fw-settings-menu.js.map +1 -1
  55. package/dist/esm/components/fw-stats-panel.js +135 -71
  56. package/dist/esm/components/fw-stats-panel.js.map +1 -1
  57. package/dist/esm/components/fw-stream-state-overlay.js +338 -0
  58. package/dist/esm/components/fw-stream-state-overlay.js.map +1 -0
  59. package/dist/esm/components/fw-subtitle-renderer.js +175 -28
  60. package/dist/esm/components/fw-subtitle-renderer.js.map +1 -1
  61. package/dist/esm/components/fw-thumbnail-overlay.js +161 -0
  62. package/dist/esm/components/fw-thumbnail-overlay.js.map +1 -0
  63. package/dist/esm/components/fw-volume-control.js +150 -69
  64. package/dist/esm/components/fw-volume-control.js.map +1 -1
  65. package/dist/esm/components/shared/hitmarker-audio.js +74 -0
  66. package/dist/esm/components/shared/hitmarker-audio.js.map +1 -0
  67. package/dist/esm/constants/media-assets.js +8 -0
  68. package/dist/esm/constants/media-assets.js.map +1 -0
  69. package/dist/esm/controllers/player-controller-host.js +28 -1
  70. package/dist/esm/controllers/player-controller-host.js.map +1 -1
  71. package/dist/esm/define.js +8 -0
  72. package/dist/esm/define.js.map +1 -1
  73. package/dist/esm/icons/index.js +26 -2
  74. package/dist/esm/icons/index.js.map +1 -1
  75. package/dist/esm/index.js +4 -0
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/fw-player.iife.js +2072 -880
  78. package/dist/types/components/fw-dev-mode-panel.d.ts +36 -9
  79. package/dist/types/components/fw-dvd-logo.d.ts +29 -0
  80. package/dist/types/components/fw-idle-screen.d.ts +36 -0
  81. package/dist/types/components/fw-loading-screen.d.ts +36 -0
  82. package/dist/types/components/fw-player-controls.d.ts +21 -6
  83. package/dist/types/components/fw-player.d.ts +28 -1
  84. package/dist/types/components/fw-seek-bar.d.ts +31 -14
  85. package/dist/types/components/fw-settings-menu.d.ts +15 -1
  86. package/dist/types/components/fw-stats-panel.d.ts +4 -4
  87. package/dist/types/components/fw-stream-state-overlay.d.ts +20 -0
  88. package/dist/types/components/fw-subtitle-renderer.d.ts +33 -2
  89. package/dist/types/components/fw-thumbnail-overlay.d.ts +17 -0
  90. package/dist/types/components/fw-volume-control.d.ts +11 -4
  91. package/dist/types/components/shared/hitmarker-audio.d.ts +1 -0
  92. package/dist/types/constants/media-assets.d.ts +5 -0
  93. package/dist/types/controllers/player-controller-host.d.ts +14 -1
  94. package/dist/types/iife-entry.d.ts +4 -0
  95. package/dist/types/index.d.ts +4 -0
  96. package/package.json +2 -2
  97. package/src/components/fw-dev-mode-panel.ts +929 -228
  98. package/src/components/fw-dvd-logo.ts +233 -0
  99. package/src/components/fw-idle-screen.ts +680 -100
  100. package/src/components/fw-loading-screen.ts +540 -0
  101. package/src/components/fw-player-controls.ts +435 -176
  102. package/src/components/fw-player.ts +505 -57
  103. package/src/components/fw-seek-bar.ts +336 -143
  104. package/src/components/fw-settings-menu.ts +208 -85
  105. package/src/components/fw-stats-panel.ts +150 -77
  106. package/src/components/fw-stream-state-overlay.ts +331 -0
  107. package/src/components/fw-subtitle-renderer.ts +216 -28
  108. package/src/components/fw-thumbnail-overlay.ts +148 -0
  109. package/src/components/fw-volume-control.ts +166 -66
  110. package/src/components/shared/hitmarker-audio.ts +92 -0
  111. package/src/constants/media-assets.ts +7 -0
  112. package/src/controllers/player-controller-host.ts +29 -2
  113. package/src/define.ts +8 -0
  114. package/src/iife-entry.ts +4 -0
  115. package/src/index.ts +4 -0
  116. package/dist/fw-player.iife.js.map +0 -1
@@ -0,0 +1,540 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { customElement, property, query, state } from "lit/decorators.js";
3
+ import { sharedStyles } from "../styles/shared-styles.js";
4
+ import { utilityStyles } from "../styles/utility-styles.js";
5
+ import { LOGOMARK_DATA_URL } from "../constants/media-assets.js";
6
+ import { playHitmarkerSound } from "./shared/hitmarker-audio.js";
7
+ import "./fw-dvd-logo.js";
8
+
9
+ interface ParticleState {
10
+ left: number;
11
+ size: number;
12
+ color: string;
13
+ duration: number;
14
+ delay: number;
15
+ }
16
+
17
+ interface BubbleState {
18
+ top: number;
19
+ left: number;
20
+ size: number;
21
+ opacity: number;
22
+ color: string;
23
+ }
24
+
25
+ interface Hitmarker {
26
+ id: number;
27
+ x: number;
28
+ y: number;
29
+ }
30
+
31
+ const BUBBLE_COLORS = [
32
+ "rgba(122, 162, 247, 0.2)",
33
+ "rgba(187, 154, 247, 0.2)",
34
+ "rgba(158, 206, 106, 0.2)",
35
+ "rgba(115, 218, 202, 0.2)",
36
+ "rgba(125, 207, 255, 0.2)",
37
+ "rgba(247, 118, 142, 0.2)",
38
+ "rgba(224, 175, 104, 0.2)",
39
+ "rgba(42, 195, 222, 0.2)",
40
+ ];
41
+
42
+ const PARTICLE_COLORS = [
43
+ "#7aa2f7",
44
+ "#bb9af7",
45
+ "#9ece6a",
46
+ "#73daca",
47
+ "#7dcfff",
48
+ "#f7768e",
49
+ "#e0af68",
50
+ "#2ac3de",
51
+ ];
52
+
53
+ @customElement("fw-loading-screen")
54
+ export class FwLoadingScreen extends LitElement {
55
+ @property({ type: String }) message = "Waiting for source...";
56
+ @property({ type: String, attribute: "logo-src" }) logoSrc?: string;
57
+ @query(".loading-container") private _containerEl?: HTMLDivElement;
58
+
59
+ @state() private _logoSize = 100;
60
+ @state() private _logoOffset = { x: 0, y: 0 };
61
+ @state() private _isLogoHovered = false;
62
+ @state() private _bubbles: BubbleState[] = this._createBubbles();
63
+ @state() private _hitmarkers: Hitmarker[] = [];
64
+
65
+ private _bubbleTimers = new Set<ReturnType<typeof setTimeout>>();
66
+ private _resizeObserver: ResizeObserver | null = null;
67
+ private readonly _particles: ParticleState[] = this._createParticles();
68
+
69
+ static styles = [
70
+ sharedStyles,
71
+ utilityStyles,
72
+ css`
73
+ :host {
74
+ display: block;
75
+ width: 100%;
76
+ height: 100%;
77
+ }
78
+
79
+ .loading-container {
80
+ position: relative;
81
+ width: 100%;
82
+ height: 100%;
83
+ min-height: 300px;
84
+ overflow: hidden;
85
+ user-select: none;
86
+ background: linear-gradient(
87
+ 135deg,
88
+ hsl(var(--tn-bg-dark, 235 21% 11%)) 0%,
89
+ hsl(var(--tn-bg, 233 23% 17%)) 25%,
90
+ hsl(var(--tn-bg-dark, 235 21% 11%)) 50%,
91
+ hsl(var(--tn-bg, 233 23% 17%)) 75%,
92
+ hsl(var(--tn-bg-dark, 235 21% 11%)) 100%
93
+ );
94
+ background-size: 400% 400%;
95
+ animation: _fw-gradient-shift 16s ease-in-out infinite;
96
+ }
97
+
98
+ .particles,
99
+ .bubbles {
100
+ position: absolute;
101
+ inset: 0;
102
+ pointer-events: none;
103
+ }
104
+
105
+ .particle {
106
+ position: absolute;
107
+ border-radius: 50%;
108
+ opacity: 0;
109
+ animation: _fw-float-up linear infinite;
110
+ }
111
+
112
+ .bubble {
113
+ position: absolute;
114
+ border-radius: 50%;
115
+ transition: opacity 1s ease-in-out;
116
+ }
117
+
118
+ .center-logo {
119
+ position: absolute;
120
+ top: 50%;
121
+ left: 50%;
122
+ transform: translate(-50%, -50%);
123
+ z-index: 10;
124
+ transition: transform 0.3s ease-out;
125
+ }
126
+
127
+ .logo-pulse {
128
+ position: absolute;
129
+ border-radius: 50%;
130
+ background: rgba(122, 162, 247, 0.15);
131
+ animation: _fw-logo-pulse 3s ease-in-out infinite;
132
+ pointer-events: none;
133
+ }
134
+
135
+ .logo-pulse.hovered {
136
+ animation: _fw-logo-pulse 1s ease-in-out infinite;
137
+ transform: scale(1.2);
138
+ }
139
+
140
+ .logo-mark {
141
+ position: relative;
142
+ z-index: 1;
143
+ transition: all 0.3s ease-out;
144
+ border: none;
145
+ background: transparent;
146
+ padding: 0;
147
+ margin: 0;
148
+ cursor: pointer;
149
+ }
150
+
151
+ .logo-mark img {
152
+ width: 100%;
153
+ height: 100%;
154
+ display: block;
155
+ }
156
+
157
+ .logo-mark.hovered {
158
+ transform: scale(1.1);
159
+ filter: drop-shadow(0 6px 12px rgba(36, 40, 59, 0.4)) brightness(1.1);
160
+ }
161
+
162
+ .message {
163
+ position: absolute;
164
+ bottom: 20%;
165
+ left: 50%;
166
+ transform: translateX(-50%);
167
+ z-index: 8;
168
+ color: #a9b1d6;
169
+ font-size: 16px;
170
+ font-weight: 500;
171
+ text-align: center;
172
+ text-shadow: 0 2px 4px rgba(36, 40, 59, 0.5);
173
+ animation: _fw-fade-in-out 2s ease-in-out infinite;
174
+ pointer-events: none;
175
+ }
176
+
177
+ .overlay-texture {
178
+ position: absolute;
179
+ inset: 0;
180
+ pointer-events: none;
181
+ background:
182
+ radial-gradient(circle at 20% 80%, rgba(122, 162, 247, 0.03) 0%, transparent 50%),
183
+ radial-gradient(circle at 80% 20%, rgba(187, 154, 247, 0.03) 0%, transparent 50%),
184
+ radial-gradient(circle at 40% 40%, rgba(158, 206, 106, 0.02) 0%, transparent 50%);
185
+ }
186
+
187
+ .hitmarker {
188
+ position: absolute;
189
+ transform: translate(-50%, -50%);
190
+ pointer-events: none;
191
+ z-index: 100;
192
+ width: 40px;
193
+ height: 40px;
194
+ }
195
+
196
+ .hitmarker-line {
197
+ position: absolute;
198
+ width: 12px;
199
+ height: 3px;
200
+ background-color: #fff;
201
+ box-shadow: 0 0 8px rgba(255, 255, 255, 0.8);
202
+ border-radius: 1px;
203
+ }
204
+
205
+ .hitmarker-line.tl {
206
+ top: 25%;
207
+ left: 25%;
208
+ animation: _fw-hitmarker-fade-45 0.6s ease-out forwards;
209
+ }
210
+
211
+ .hitmarker-line.tr {
212
+ top: 25%;
213
+ left: 75%;
214
+ animation: _fw-hitmarker-fade-neg-45 0.6s ease-out forwards;
215
+ }
216
+
217
+ .hitmarker-line.bl {
218
+ top: 75%;
219
+ left: 25%;
220
+ animation: _fw-hitmarker-fade-neg-45 0.6s ease-out forwards;
221
+ }
222
+
223
+ .hitmarker-line.br {
224
+ top: 75%;
225
+ left: 75%;
226
+ animation: _fw-hitmarker-fade-45 0.6s ease-out forwards;
227
+ }
228
+
229
+ @keyframes _fw-fade-in-out {
230
+ 0%,
231
+ 100% {
232
+ opacity: 0.6;
233
+ }
234
+ 50% {
235
+ opacity: 0.9;
236
+ }
237
+ }
238
+
239
+ @keyframes _fw-logo-pulse {
240
+ 0%,
241
+ 100% {
242
+ opacity: 0.15;
243
+ transform: scale(1);
244
+ }
245
+ 50% {
246
+ opacity: 0.25;
247
+ transform: scale(1.05);
248
+ }
249
+ }
250
+
251
+ @keyframes _fw-float-up {
252
+ 0% {
253
+ transform: translateY(100vh) rotate(0deg);
254
+ opacity: 0;
255
+ }
256
+ 10% {
257
+ opacity: 0.6;
258
+ }
259
+ 90% {
260
+ opacity: 0.6;
261
+ }
262
+ 100% {
263
+ transform: translateY(-100px) rotate(360deg);
264
+ opacity: 0;
265
+ }
266
+ }
267
+
268
+ @keyframes _fw-gradient-shift {
269
+ 0%,
270
+ 100% {
271
+ background-position: 0% 50%;
272
+ }
273
+ 50% {
274
+ background-position: 100% 50%;
275
+ }
276
+ }
277
+
278
+ @keyframes _fw-hitmarker-fade-45 {
279
+ 0% {
280
+ opacity: 1;
281
+ transform: translate(-50%, -50%) rotate(45deg) scale(0.5);
282
+ }
283
+ 20% {
284
+ opacity: 1;
285
+ transform: translate(-50%, -50%) rotate(45deg) scale(1.2);
286
+ }
287
+ 100% {
288
+ opacity: 0;
289
+ transform: translate(-50%, -50%) rotate(45deg) scale(1);
290
+ }
291
+ }
292
+
293
+ @keyframes _fw-hitmarker-fade-neg-45 {
294
+ 0% {
295
+ opacity: 1;
296
+ transform: translate(-50%, -50%) rotate(-45deg) scale(0.5);
297
+ }
298
+ 20% {
299
+ opacity: 1;
300
+ transform: translate(-50%, -50%) rotate(-45deg) scale(1.2);
301
+ }
302
+ 100% {
303
+ opacity: 0;
304
+ transform: translate(-50%, -50%) rotate(-45deg) scale(1);
305
+ }
306
+ }
307
+ `,
308
+ ];
309
+
310
+ connectedCallback(): void {
311
+ super.connectedCallback();
312
+ this._clearBubbleTimers();
313
+ this._startBubbleAnimations();
314
+ }
315
+
316
+ disconnectedCallback(): void {
317
+ super.disconnectedCallback();
318
+ this._clearBubbleTimers();
319
+ this._resizeObserver?.disconnect();
320
+ this._resizeObserver = null;
321
+ }
322
+
323
+ protected firstUpdated(): void {
324
+ this._updateLogoSize();
325
+ if (typeof ResizeObserver !== "undefined") {
326
+ this._resizeObserver = new ResizeObserver(() => this._updateLogoSize());
327
+ if (this._containerEl) {
328
+ this._resizeObserver.observe(this._containerEl);
329
+ }
330
+ }
331
+ }
332
+
333
+ private _createParticles(): ParticleState[] {
334
+ return Array.from({ length: 12 }, (_, index) => ({
335
+ left: Math.random() * 100,
336
+ size: Math.random() * 4 + 2,
337
+ color: PARTICLE_COLORS[index % PARTICLE_COLORS.length],
338
+ duration: 8 + Math.random() * 4,
339
+ delay: Math.random() * 8,
340
+ }));
341
+ }
342
+
343
+ private _createBubbles(): BubbleState[] {
344
+ return Array.from({ length: 8 }, (_, index) => ({
345
+ top: Math.random() * 80 + 10,
346
+ left: Math.random() * 80 + 10,
347
+ size: Math.random() * 60 + 30,
348
+ opacity: 0,
349
+ color: BUBBLE_COLORS[index % BUBBLE_COLORS.length],
350
+ }));
351
+ }
352
+
353
+ private _setManagedTimer(callback: () => void, delayMs: number): void {
354
+ const timer = setTimeout(() => {
355
+ this._bubbleTimers.delete(timer);
356
+ callback();
357
+ }, delayMs);
358
+ this._bubbleTimers.add(timer);
359
+ }
360
+
361
+ private _clearBubbleTimers(): void {
362
+ this._bubbleTimers.forEach((timer) => clearTimeout(timer));
363
+ this._bubbleTimers.clear();
364
+ }
365
+
366
+ private _updateBubble(index: number, nextState: Partial<BubbleState>): void {
367
+ const next = [...this._bubbles];
368
+ next[index] = { ...next[index], ...nextState };
369
+ this._bubbles = next;
370
+ }
371
+
372
+ private _animateBubble(index: number): void {
373
+ this._updateBubble(index, { opacity: 0.15 });
374
+ const visibleDuration = 4000 + Math.random() * 3000;
375
+ this._setManagedTimer(() => {
376
+ this._updateBubble(index, { opacity: 0 });
377
+ this._setManagedTimer(() => {
378
+ this._updateBubble(index, {
379
+ top: Math.random() * 80 + 10,
380
+ left: Math.random() * 80 + 10,
381
+ size: Math.random() * 60 + 30,
382
+ });
383
+ this._setManagedTimer(() => this._animateBubble(index), 200);
384
+ }, 1500);
385
+ }, visibleDuration);
386
+ }
387
+
388
+ private _startBubbleAnimations(): void {
389
+ this._bubbles.forEach((_, index) => {
390
+ this._setManagedTimer(() => this._animateBubble(index), index * 500);
391
+ });
392
+ }
393
+
394
+ private _updateLogoSize(): void {
395
+ const rect = this._containerEl?.getBoundingClientRect() ?? this.getBoundingClientRect();
396
+ const minDimension = Math.min(rect.width, rect.height);
397
+ if (!Number.isFinite(minDimension) || minDimension <= 0) {
398
+ return;
399
+ }
400
+ this._logoSize = minDimension * 0.2;
401
+ }
402
+
403
+ private _handleMouseMove = (event: MouseEvent): void => {
404
+ const rect = this._containerEl?.getBoundingClientRect() ?? this.getBoundingClientRect();
405
+ if (rect.width <= 0 || rect.height <= 0) {
406
+ return;
407
+ }
408
+
409
+ const centerX = rect.left + rect.width / 2;
410
+ const centerY = rect.top + rect.height / 2;
411
+ const deltaX = event.clientX - centerX;
412
+ const deltaY = event.clientY - centerY;
413
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
414
+ const maxDistance = this._logoSize * 1.5;
415
+
416
+ if (distance < maxDistance && distance > 0) {
417
+ const pushStrength = (maxDistance - distance) / maxDistance;
418
+ const pushDistance = 50 * pushStrength;
419
+ this._logoOffset = {
420
+ x: -(deltaX / distance) * pushDistance,
421
+ y: -(deltaY / distance) * pushDistance,
422
+ };
423
+ this._isLogoHovered = true;
424
+ return;
425
+ }
426
+
427
+ this._logoOffset = { x: 0, y: 0 };
428
+ this._isLogoHovered = false;
429
+ };
430
+
431
+ private _handleMouseLeave = (): void => {
432
+ this._logoOffset = { x: 0, y: 0 };
433
+ this._isLogoHovered = false;
434
+ };
435
+
436
+ private _handleLogoClick = (event: MouseEvent): void => {
437
+ event.stopPropagation();
438
+ const rect = this._containerEl?.getBoundingClientRect() ?? this.getBoundingClientRect();
439
+ const hitmarker: Hitmarker = {
440
+ id: Date.now() + Math.random(),
441
+ x: event.clientX - rect.left,
442
+ y: event.clientY - rect.top,
443
+ };
444
+ this._hitmarkers = [...this._hitmarkers, hitmarker];
445
+ playHitmarkerSound();
446
+
447
+ this._setManagedTimer(() => {
448
+ this._hitmarkers = this._hitmarkers.filter((value) => value.id !== hitmarker.id);
449
+ }, 600);
450
+ };
451
+
452
+ protected render() {
453
+ const logoSrc = this.logoSrc || LOGOMARK_DATA_URL;
454
+ return html`
455
+ <div
456
+ class="loading-container fw-player-root"
457
+ @mousemove=${this._handleMouseMove}
458
+ @mouseleave=${this._handleMouseLeave}
459
+ >
460
+ ${this._hitmarkers.map(
461
+ (hitmarker) => html`
462
+ <div class="hitmarker" style="left:${hitmarker.x}px;top:${hitmarker.y}px;">
463
+ <div class="hitmarker-line tl"></div>
464
+ <div class="hitmarker-line tr"></div>
465
+ <div class="hitmarker-line bl"></div>
466
+ <div class="hitmarker-line br"></div>
467
+ </div>
468
+ `
469
+ )}
470
+
471
+ <div class="particles">
472
+ ${this._particles.map(
473
+ (particle) => html`
474
+ <div
475
+ class="particle"
476
+ style="
477
+ left:${particle.left}%;
478
+ width:${particle.size}px;
479
+ height:${particle.size}px;
480
+ background:${particle.color};
481
+ animation-duration:${particle.duration}s;
482
+ animation-delay:${particle.delay}s;
483
+ "
484
+ ></div>
485
+ `
486
+ )}
487
+ </div>
488
+
489
+ <div class="bubbles">
490
+ ${this._bubbles.map(
491
+ (bubble) => html`
492
+ <div
493
+ class="bubble"
494
+ style="
495
+ top:${bubble.top}%;
496
+ left:${bubble.left}%;
497
+ width:${bubble.size}px;
498
+ height:${bubble.size}px;
499
+ background:${bubble.color};
500
+ opacity:${bubble.opacity};
501
+ "
502
+ ></div>
503
+ `
504
+ )}
505
+ </div>
506
+
507
+ <div
508
+ class="center-logo"
509
+ style="transform:translate(-50%, -50%) translate(${this._logoOffset.x}px, ${this
510
+ ._logoOffset.y}px);"
511
+ >
512
+ <div
513
+ class="logo-pulse ${this._isLogoHovered ? "hovered" : ""}"
514
+ style="width:${this._logoSize * 1.4}px;height:${this._logoSize * 1.4}px;"
515
+ ></div>
516
+ <button
517
+ type="button"
518
+ class="logo-mark ${this._isLogoHovered ? "hovered" : ""}"
519
+ style="width:${this._logoSize}px;height:${this._logoSize}px;"
520
+ @click=${this._handleLogoClick}
521
+ aria-label="FrameWorks logo"
522
+ >
523
+ <img src=${logoSrc} alt="FrameWorks Logo" draggable="false" />
524
+ </button>
525
+ </div>
526
+
527
+ <fw-dvd-logo .parentRef=${this._containerEl ?? null} .scale=${0.08}></fw-dvd-logo>
528
+
529
+ <div class="message">${this.message}</div>
530
+ <div class="overlay-texture"></div>
531
+ </div>
532
+ `;
533
+ }
534
+ }
535
+
536
+ declare global {
537
+ interface HTMLElementTagNameMap {
538
+ "fw-loading-screen": FwLoadingScreen;
539
+ }
540
+ }