@livepeer-frameworks/player-wc 0.1.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.
Files changed (141) hide show
  1. package/dist/cjs/components/fw-context-menu.js +17 -0
  2. package/dist/cjs/components/fw-context-menu.js.map +1 -0
  3. package/dist/cjs/components/fw-dev-mode-panel.js +273 -0
  4. package/dist/cjs/components/fw-dev-mode-panel.js.map +1 -0
  5. package/dist/cjs/components/fw-error-overlay.js +101 -0
  6. package/dist/cjs/components/fw-error-overlay.js.map +1 -0
  7. package/dist/cjs/components/fw-idle-screen.js +182 -0
  8. package/dist/cjs/components/fw-idle-screen.js.map +1 -0
  9. package/dist/cjs/components/fw-loading-spinner.js +62 -0
  10. package/dist/cjs/components/fw-loading-spinner.js.map +1 -0
  11. package/dist/cjs/components/fw-player-controls.js +258 -0
  12. package/dist/cjs/components/fw-player-controls.js.map +1 -0
  13. package/dist/cjs/components/fw-player.js +570 -0
  14. package/dist/cjs/components/fw-player.js.map +1 -0
  15. package/dist/cjs/components/fw-seek-bar.js +233 -0
  16. package/dist/cjs/components/fw-seek-bar.js.map +1 -0
  17. package/dist/cjs/components/fw-settings-menu.js +126 -0
  18. package/dist/cjs/components/fw-settings-menu.js.map +1 -0
  19. package/dist/cjs/components/fw-skip-indicator.js +143 -0
  20. package/dist/cjs/components/fw-skip-indicator.js.map +1 -0
  21. package/dist/cjs/components/fw-speed-indicator.js +61 -0
  22. package/dist/cjs/components/fw-speed-indicator.js.map +1 -0
  23. package/dist/cjs/components/fw-stats-panel.js +141 -0
  24. package/dist/cjs/components/fw-stats-panel.js.map +1 -0
  25. package/dist/cjs/components/fw-subtitle-renderer.js +70 -0
  26. package/dist/cjs/components/fw-subtitle-renderer.js.map +1 -0
  27. package/dist/cjs/components/fw-title-overlay.js +72 -0
  28. package/dist/cjs/components/fw-title-overlay.js.map +1 -0
  29. package/dist/cjs/components/fw-toast.js +74 -0
  30. package/dist/cjs/components/fw-toast.js.map +1 -0
  31. package/dist/cjs/components/fw-volume-control.js +140 -0
  32. package/dist/cjs/components/fw-volume-control.js.map +1 -0
  33. package/dist/cjs/controllers/player-controller-host.js +315 -0
  34. package/dist/cjs/controllers/player-controller-host.js.map +1 -0
  35. package/dist/cjs/define.js +45 -0
  36. package/dist/cjs/define.js.map +1 -0
  37. package/dist/cjs/icons/index.js +153 -0
  38. package/dist/cjs/icons/index.js.map +1 -0
  39. package/dist/cjs/index.js +88 -0
  40. package/dist/cjs/index.js.map +1 -0
  41. package/dist/cjs/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +33 -0
  42. package/dist/cjs/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js.map +1 -0
  43. package/dist/cjs/styles/shared-styles.js +1967 -0
  44. package/dist/cjs/styles/shared-styles.js.map +1 -0
  45. package/dist/cjs/styles/utility-styles.js +725 -0
  46. package/dist/cjs/styles/utility-styles.js.map +1 -0
  47. package/dist/esm/components/fw-context-menu.js +17 -0
  48. package/dist/esm/components/fw-context-menu.js.map +1 -0
  49. package/dist/esm/components/fw-dev-mode-panel.js +273 -0
  50. package/dist/esm/components/fw-dev-mode-panel.js.map +1 -0
  51. package/dist/esm/components/fw-error-overlay.js +101 -0
  52. package/dist/esm/components/fw-error-overlay.js.map +1 -0
  53. package/dist/esm/components/fw-idle-screen.js +182 -0
  54. package/dist/esm/components/fw-idle-screen.js.map +1 -0
  55. package/dist/esm/components/fw-loading-spinner.js +62 -0
  56. package/dist/esm/components/fw-loading-spinner.js.map +1 -0
  57. package/dist/esm/components/fw-player-controls.js +258 -0
  58. package/dist/esm/components/fw-player-controls.js.map +1 -0
  59. package/dist/esm/components/fw-player.js +570 -0
  60. package/dist/esm/components/fw-player.js.map +1 -0
  61. package/dist/esm/components/fw-seek-bar.js +233 -0
  62. package/dist/esm/components/fw-seek-bar.js.map +1 -0
  63. package/dist/esm/components/fw-settings-menu.js +126 -0
  64. package/dist/esm/components/fw-settings-menu.js.map +1 -0
  65. package/dist/esm/components/fw-skip-indicator.js +143 -0
  66. package/dist/esm/components/fw-skip-indicator.js.map +1 -0
  67. package/dist/esm/components/fw-speed-indicator.js +61 -0
  68. package/dist/esm/components/fw-speed-indicator.js.map +1 -0
  69. package/dist/esm/components/fw-stats-panel.js +141 -0
  70. package/dist/esm/components/fw-stats-panel.js.map +1 -0
  71. package/dist/esm/components/fw-subtitle-renderer.js +70 -0
  72. package/dist/esm/components/fw-subtitle-renderer.js.map +1 -0
  73. package/dist/esm/components/fw-title-overlay.js +72 -0
  74. package/dist/esm/components/fw-title-overlay.js.map +1 -0
  75. package/dist/esm/components/fw-toast.js +74 -0
  76. package/dist/esm/components/fw-toast.js.map +1 -0
  77. package/dist/esm/components/fw-volume-control.js +140 -0
  78. package/dist/esm/components/fw-volume-control.js.map +1 -0
  79. package/dist/esm/controllers/player-controller-host.js +313 -0
  80. package/dist/esm/controllers/player-controller-host.js.map +1 -0
  81. package/dist/esm/define.js +43 -0
  82. package/dist/esm/define.js.map +1 -0
  83. package/dist/esm/icons/index.js +141 -0
  84. package/dist/esm/icons/index.js.map +1 -0
  85. package/dist/esm/index.js +18 -0
  86. package/dist/esm/index.js.map +1 -0
  87. package/dist/esm/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +31 -0
  88. package/dist/esm/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js.map +1 -0
  89. package/dist/esm/styles/shared-styles.js +1965 -0
  90. package/dist/esm/styles/shared-styles.js.map +1 -0
  91. package/dist/esm/styles/utility-styles.js +723 -0
  92. package/dist/esm/styles/utility-styles.js.map +1 -0
  93. package/dist/fw-player.iife.js +4362 -0
  94. package/dist/fw-player.iife.js.map +1 -0
  95. package/dist/types/components/fw-context-menu.d.ts +15 -0
  96. package/dist/types/components/fw-dev-mode-panel.d.ts +24 -0
  97. package/dist/types/components/fw-error-overlay.d.ts +14 -0
  98. package/dist/types/components/fw-idle-screen.d.ts +13 -0
  99. package/dist/types/components/fw-loading-spinner.d.ts +10 -0
  100. package/dist/types/components/fw-player-controls.d.ts +23 -0
  101. package/dist/types/components/fw-player.d.ts +74 -0
  102. package/dist/types/components/fw-seek-bar.d.ts +33 -0
  103. package/dist/types/components/fw-settings-menu.d.ts +16 -0
  104. package/dist/types/components/fw-skip-indicator.d.ts +18 -0
  105. package/dist/types/components/fw-speed-indicator.d.ts +11 -0
  106. package/dist/types/components/fw-stats-panel.d.ts +18 -0
  107. package/dist/types/components/fw-subtitle-renderer.d.ts +21 -0
  108. package/dist/types/components/fw-title-overlay.d.ts +12 -0
  109. package/dist/types/components/fw-toast.d.ts +12 -0
  110. package/dist/types/components/fw-volume-control.d.ts +18 -0
  111. package/dist/types/controllers/player-controller-host.d.ts +119 -0
  112. package/dist/types/define.d.ts +1 -0
  113. package/dist/types/icons/index.d.ts +23 -0
  114. package/dist/types/iife-entry.d.ts +11 -0
  115. package/dist/types/index.d.ts +25 -0
  116. package/dist/types/styles/shared-styles.d.ts +1 -0
  117. package/dist/types/styles/utility-styles.d.ts +1 -0
  118. package/package.json +65 -0
  119. package/src/components/fw-context-menu.ts +23 -0
  120. package/src/components/fw-dev-mode-panel.ts +285 -0
  121. package/src/components/fw-error-overlay.ts +96 -0
  122. package/src/components/fw-idle-screen.ts +182 -0
  123. package/src/components/fw-loading-spinner.ts +63 -0
  124. package/src/components/fw-player-controls.ts +256 -0
  125. package/src/components/fw-player.ts +557 -0
  126. package/src/components/fw-seek-bar.ts +219 -0
  127. package/src/components/fw-settings-menu.ts +128 -0
  128. package/src/components/fw-skip-indicator.ts +139 -0
  129. package/src/components/fw-speed-indicator.ts +57 -0
  130. package/src/components/fw-stats-panel.ts +154 -0
  131. package/src/components/fw-subtitle-renderer.ts +65 -0
  132. package/src/components/fw-title-overlay.ts +64 -0
  133. package/src/components/fw-toast.ts +70 -0
  134. package/src/components/fw-volume-control.ts +140 -0
  135. package/src/controllers/player-controller-host.ts +457 -0
  136. package/src/define.ts +43 -0
  137. package/src/icons/index.ts +209 -0
  138. package/src/iife-entry.ts +11 -0
  139. package/src/index.ts +31 -0
  140. package/src/styles/shared-styles.ts +1962 -0
  141. package/src/styles/utility-styles.ts +720 -0
@@ -0,0 +1,141 @@
1
+ import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
+ import { css, LitElement, nothing, html } from 'lit';
3
+ import { property, customElement } from 'lit/decorators.js';
4
+ import { sharedStyles } from '../styles/shared-styles.js';
5
+ import { utilityStyles } from '../styles/utility-styles.js';
6
+ import { closeIcon } from '../icons/index.js';
7
+
8
+ let FwStatsPanel = class FwStatsPanel extends LitElement {
9
+ _resolution() {
10
+ const video = this.pc.s.videoElement;
11
+ if (!video || !video.videoWidth || !video.videoHeight)
12
+ return null;
13
+ return `${video.videoWidth}x${video.videoHeight}`;
14
+ }
15
+ _stat(label, value) {
16
+ if (value == null || value === "")
17
+ return nothing;
18
+ return html `<div class="row">
19
+ <span class="label">${label}</span><span class="value">${value}</span>
20
+ </div>`;
21
+ }
22
+ render() {
23
+ const s = this.pc.s;
24
+ const q = s.playbackQuality;
25
+ const meta = s.metadata;
26
+ const ss = s.streamState;
27
+ return html `
28
+ <div class="panel fw-stats-panel">
29
+ <div class="header">
30
+ <span class="title">Stats</span>
31
+ <button
32
+ class="close"
33
+ @click=${() => this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
34
+ aria-label="Close stats"
35
+ >
36
+ ${closeIcon()}
37
+ </button>
38
+ </div>
39
+
40
+ ${this._stat("State", s.state)} ${this._stat("Player", s.currentPlayerInfo?.name)}
41
+ ${this._stat("Source", s.currentSourceInfo?.type)}
42
+
43
+ <div class="sep"></div>
44
+
45
+ ${q
46
+ ? html `
47
+ ${this._stat("Resolution", this._resolution())}
48
+ ${this._stat("Bitrate", q.bitrate ? `${Math.round(q.bitrate / 1000)} kbps` : null)}
49
+ ${this._stat("Latency", q.latency != null ? `${q.latency.toFixed(1)}s` : null)}
50
+ ${this._stat("Buffer", q.bufferedAhead != null ? `${q.bufferedAhead.toFixed(1)}s` : null)}
51
+ ${this._stat("Quality", q.score != null ? `${q.score.toFixed(0)}` : null)}
52
+ ${this._stat("Frame drops", q.frameDropRate != null ? `${q.frameDropRate.toFixed(1)}%` : null)}
53
+ ${this._stat("Stalls", q.stallCount ?? null)}
54
+ `
55
+ : nothing}
56
+ ${meta || ss
57
+ ? html `
58
+ <div class="sep"></div>
59
+ ${this._stat("Viewers", meta?.viewers ?? null)}
60
+ ${this._stat("Stream status", ss?.status ?? null)}
61
+ `
62
+ : nothing}
63
+ </div>
64
+ `;
65
+ }
66
+ };
67
+ FwStatsPanel.styles = [
68
+ sharedStyles,
69
+ utilityStyles,
70
+ css `
71
+ :host {
72
+ display: contents;
73
+ }
74
+ .panel {
75
+ position: absolute;
76
+ top: 0.75rem;
77
+ left: 0.75rem;
78
+ z-index: 30;
79
+ min-width: 240px;
80
+ max-width: 320px;
81
+ max-height: 80%;
82
+ overflow: auto;
83
+ border-radius: 0.5rem;
84
+ border: 1px solid rgb(255 255 255 / 0.1);
85
+ background: rgb(0 0 0 / 0.85);
86
+ backdrop-filter: blur(8px);
87
+ padding: 0.5rem 0.75rem;
88
+ font-size: 0.6875rem;
89
+ color: rgb(255 255 255 / 0.7);
90
+ }
91
+ .header {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ margin-bottom: 0.5rem;
96
+ }
97
+ .title {
98
+ font-size: 0.75rem;
99
+ font-weight: 600;
100
+ color: white;
101
+ }
102
+ .close {
103
+ display: flex;
104
+ background: none;
105
+ border: none;
106
+ color: rgb(255 255 255 / 0.5);
107
+ cursor: pointer;
108
+ padding: 0;
109
+ }
110
+ .close:hover {
111
+ color: white;
112
+ }
113
+ .row {
114
+ display: flex;
115
+ justify-content: space-between;
116
+ padding: 0.125rem 0;
117
+ }
118
+ .label {
119
+ color: rgb(255 255 255 / 0.5);
120
+ }
121
+ .value {
122
+ color: rgb(255 255 255 / 0.9);
123
+ font-variant-numeric: tabular-nums;
124
+ font-family: ui-monospace, monospace;
125
+ }
126
+ .sep {
127
+ height: 1px;
128
+ background: rgb(255 255 255 / 0.08);
129
+ margin: 0.375rem 0;
130
+ }
131
+ `,
132
+ ];
133
+ __decorate([
134
+ property({ attribute: false })
135
+ ], FwStatsPanel.prototype, "pc", void 0);
136
+ FwStatsPanel = __decorate([
137
+ customElement("fw-stats-panel")
138
+ ], FwStatsPanel);
139
+
140
+ export { FwStatsPanel };
141
+ //# sourceMappingURL=fw-stats-panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-stats-panel.js","sources":["../../../../src/components/fw-stats-panel.ts"],"sourcesContent":["/**\n * <fw-stats-panel> — Stats for nerds overlay.\n * Port of StatsPanel.tsx from player-react.\n */\nimport { LitElement, html, css, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { sharedStyles } from \"../styles/shared-styles.js\";\nimport { utilityStyles } from \"../styles/utility-styles.js\";\nimport { closeIcon } from \"../icons/index.js\";\nimport type { PlayerControllerHost } from \"../controllers/player-controller-host.js\";\n\n@customElement(\"fw-stats-panel\")\nexport class FwStatsPanel extends LitElement {\n @property({ attribute: false }) pc!: PlayerControllerHost;\n\n static styles = [\n sharedStyles,\n utilityStyles,\n css`\n :host {\n display: contents;\n }\n .panel {\n position: absolute;\n top: 0.75rem;\n left: 0.75rem;\n z-index: 30;\n min-width: 240px;\n max-width: 320px;\n max-height: 80%;\n overflow: auto;\n border-radius: 0.5rem;\n border: 1px solid rgb(255 255 255 / 0.1);\n background: rgb(0 0 0 / 0.85);\n backdrop-filter: blur(8px);\n padding: 0.5rem 0.75rem;\n font-size: 0.6875rem;\n color: rgb(255 255 255 / 0.7);\n }\n .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.5rem;\n }\n .title {\n font-size: 0.75rem;\n font-weight: 600;\n color: white;\n }\n .close {\n display: flex;\n background: none;\n border: none;\n color: rgb(255 255 255 / 0.5);\n cursor: pointer;\n padding: 0;\n }\n .close:hover {\n color: white;\n }\n .row {\n display: flex;\n justify-content: space-between;\n padding: 0.125rem 0;\n }\n .label {\n color: rgb(255 255 255 / 0.5);\n }\n .value {\n color: rgb(255 255 255 / 0.9);\n font-variant-numeric: tabular-nums;\n font-family: ui-monospace, monospace;\n }\n .sep {\n height: 1px;\n background: rgb(255 255 255 / 0.08);\n margin: 0.375rem 0;\n }\n `,\n ];\n\n private _resolution(): string | null {\n const video = this.pc.s.videoElement;\n if (!video || !video.videoWidth || !video.videoHeight) return null;\n return `${video.videoWidth}x${video.videoHeight}`;\n }\n\n private _stat(label: string, value: string | number | null | undefined) {\n if (value == null || value === \"\") return nothing;\n return html`<div class=\"row\">\n <span class=\"label\">${label}</span><span class=\"value\">${value}</span>\n </div>`;\n }\n\n protected render() {\n const s = this.pc.s;\n const q = s.playbackQuality;\n const meta = s.metadata;\n const ss = s.streamState;\n\n return html`\n <div class=\"panel fw-stats-panel\">\n <div class=\"header\">\n <span class=\"title\">Stats</span>\n <button\n class=\"close\"\n @click=${() =>\n this.dispatchEvent(new CustomEvent(\"fw-close\", { bubbles: true, composed: true }))}\n aria-label=\"Close stats\"\n >\n ${closeIcon()}\n </button>\n </div>\n\n ${this._stat(\"State\", s.state)} ${this._stat(\"Player\", s.currentPlayerInfo?.name)}\n ${this._stat(\"Source\", s.currentSourceInfo?.type)}\n\n <div class=\"sep\"></div>\n\n ${q\n ? html`\n ${this._stat(\"Resolution\", this._resolution())}\n ${this._stat(\"Bitrate\", q.bitrate ? `${Math.round(q.bitrate / 1000)} kbps` : null)}\n ${this._stat(\"Latency\", q.latency != null ? `${q.latency.toFixed(1)}s` : null)}\n ${this._stat(\n \"Buffer\",\n q.bufferedAhead != null ? `${q.bufferedAhead.toFixed(1)}s` : null\n )}\n ${this._stat(\"Quality\", q.score != null ? `${q.score.toFixed(0)}` : null)}\n ${this._stat(\n \"Frame drops\",\n q.frameDropRate != null ? `${q.frameDropRate.toFixed(1)}%` : null\n )}\n ${this._stat(\"Stalls\", q.stallCount ?? null)}\n `\n : nothing}\n ${meta || ss\n ? html`\n <div class=\"sep\"></div>\n ${this._stat(\"Viewers\", meta?.viewers ?? null)}\n ${this._stat(\"Stream status\", ss?.status ?? null)}\n `\n : nothing}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-stats-panel\": FwStatsPanel;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
+ import { css, LitElement, nothing, html } from 'lit';
3
+ import { property, customElement } from 'lit/decorators.js';
4
+
5
+ let FwSubtitleRenderer = class FwSubtitleRenderer extends LitElement {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.currentTime = 0;
9
+ this.enabled = false;
10
+ this.cues = [];
11
+ }
12
+ get _activeCue() {
13
+ if (!this.enabled || !this.cues.length)
14
+ return null;
15
+ const t = this.currentTime * 1000; // Convert to ms
16
+ for (const cue of this.cues) {
17
+ if (t >= cue.startTime && t <= cue.endTime)
18
+ return cue.text;
19
+ }
20
+ return null;
21
+ }
22
+ render() {
23
+ const text = this._activeCue;
24
+ if (!text)
25
+ return nothing;
26
+ return html `<span class="subtitle" aria-live="polite">${text}</span>`;
27
+ }
28
+ };
29
+ FwSubtitleRenderer.styles = css `
30
+ :host {
31
+ display: contents;
32
+ }
33
+ .subtitle {
34
+ position: absolute;
35
+ bottom: 5%;
36
+ left: 50%;
37
+ transform: translateX(-50%);
38
+ display: inline-block;
39
+ max-width: 90%;
40
+ padding: 0.5em 1em;
41
+ border-radius: 4px;
42
+ background: rgb(0 0 0 / 0.75);
43
+ color: white;
44
+ font-size: 1.5rem;
45
+ font-family:
46
+ system-ui,
47
+ -apple-system,
48
+ sans-serif;
49
+ text-shadow: 2px 2px 4px rgb(0 0 0 / 0.5);
50
+ white-space: pre-wrap;
51
+ text-align: center;
52
+ z-index: 15;
53
+ pointer-events: none;
54
+ }
55
+ `;
56
+ __decorate([
57
+ property({ type: Number })
58
+ ], FwSubtitleRenderer.prototype, "currentTime", void 0);
59
+ __decorate([
60
+ property({ type: Boolean })
61
+ ], FwSubtitleRenderer.prototype, "enabled", void 0);
62
+ __decorate([
63
+ property({ attribute: false })
64
+ ], FwSubtitleRenderer.prototype, "cues", void 0);
65
+ FwSubtitleRenderer = __decorate([
66
+ customElement("fw-subtitle-renderer")
67
+ ], FwSubtitleRenderer);
68
+
69
+ export { FwSubtitleRenderer };
70
+ //# sourceMappingURL=fw-subtitle-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-subtitle-renderer.js","sources":["../../../../src/components/fw-subtitle-renderer.ts"],"sourcesContent":["import { LitElement, html, css, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\n\ninterface SubtitleCue {\n id?: string;\n startTime: number;\n endTime: number;\n text: string;\n}\n\n@customElement(\"fw-subtitle-renderer\")\nexport class FwSubtitleRenderer extends LitElement {\n @property({ type: Number }) currentTime = 0;\n @property({ type: Boolean }) enabled = false;\n @property({ attribute: false }) cues: SubtitleCue[] = [];\n\n static styles = css`\n :host {\n display: contents;\n }\n .subtitle {\n position: absolute;\n bottom: 5%;\n left: 50%;\n transform: translateX(-50%);\n display: inline-block;\n max-width: 90%;\n padding: 0.5em 1em;\n border-radius: 4px;\n background: rgb(0 0 0 / 0.75);\n color: white;\n font-size: 1.5rem;\n font-family:\n system-ui,\n -apple-system,\n sans-serif;\n text-shadow: 2px 2px 4px rgb(0 0 0 / 0.5);\n white-space: pre-wrap;\n text-align: center;\n z-index: 15;\n pointer-events: none;\n }\n `;\n\n private get _activeCue(): string | null {\n if (!this.enabled || !this.cues.length) return null;\n const t = this.currentTime * 1000; // Convert to ms\n for (const cue of this.cues) {\n if (t >= cue.startTime && t <= cue.endTime) return cue.text;\n }\n return null;\n }\n\n protected render() {\n const text = this._activeCue;\n if (!text) return nothing;\n return html`<span class=\"subtitle\" aria-live=\"polite\">${text}</span>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-subtitle-renderer\": FwSubtitleRenderer;\n }\n}\n"],"names":[],"mappings":";;;;AAWO,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,UAAU,CAAA;AAA3C,IAAA,WAAA,GAAA;;QACuB,IAAA,CAAA,WAAW,GAAG,CAAC;QACd,IAAA,CAAA,OAAO,GAAG,KAAK;QACZ,IAAA,CAAA,IAAI,GAAkB,EAAE;IA4C1D;AAdE,IAAA,IAAY,UAAU,GAAA;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,IAAI;QACnD,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;AAClC,QAAA,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE;YAC3B,IAAI,CAAC,IAAI,GAAG,CAAC,SAAS,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,IAAI;QAC7D;AACA,QAAA,OAAO,IAAI;IACb;IAEU,MAAM,GAAA;AACd,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU;AAC5B,QAAA,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,OAAO;AACzB,QAAA,OAAO,IAAI,CAAA,CAAA,0CAAA,EAA6C,IAAI,SAAS;IACvE;;AAzCO,kBAAA,CAAA,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BlB,EAAA,CA1BY;AAJe,UAAA,CAAA;AAA3B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;AAAkB,CAAA,EAAA,kBAAA,CAAA,SAAA,EAAA,aAAA,EAAA,MAAA,CAAA;AACf,UAAA,CAAA;AAA5B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAAkB,CAAA,EAAA,kBAAA,CAAA,SAAA,EAAA,SAAA,EAAA,MAAA,CAAA;AACb,UAAA,CAAA;AAA/B,IAAA,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE;AAA2B,CAAA,EAAA,kBAAA,CAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA;AAH9C,kBAAkB,GAAA,UAAA,CAAA;IAD9B,aAAa,CAAC,sBAAsB;AACxB,CAAA,EAAA,kBAAkB,CA+C9B;;;;"}
@@ -0,0 +1,72 @@
1
+ import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
+ import { css, LitElement, nothing, html } from 'lit';
3
+ import { property, customElement } from 'lit/decorators.js';
4
+ import { sharedStyles } from '../styles/shared-styles.js';
5
+ import { utilityStyles } from '../styles/utility-styles.js';
6
+
7
+ let FwTitleOverlay = class FwTitleOverlay extends LitElement {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.title = "";
11
+ this.description = null;
12
+ }
13
+ render() {
14
+ if (this.title === "" && !this.description)
15
+ return nothing;
16
+ return html `
17
+ <div class="overlay fw-title-overlay">
18
+ ${this.title ? html `<div class="title">${this.title}</div>` : nothing}
19
+ ${this.description ? html `<div class="desc">${this.description}</div>` : nothing}
20
+ </div>
21
+ `;
22
+ }
23
+ };
24
+ FwTitleOverlay.styles = [
25
+ sharedStyles,
26
+ utilityStyles,
27
+ css `
28
+ :host {
29
+ display: contents;
30
+ }
31
+ .overlay {
32
+ position: absolute;
33
+ inset: 0 0 auto 0;
34
+ padding: 1rem 1.25rem;
35
+ background: linear-gradient(to bottom, rgb(0 0 0 / 0.7), rgb(0 0 0 / 0.4), transparent);
36
+ pointer-events: none;
37
+ transition: opacity 300ms ease;
38
+ z-index: 10;
39
+ }
40
+ .title {
41
+ font-size: 0.875rem;
42
+ font-weight: 500;
43
+ color: white;
44
+ max-width: 80%;
45
+ overflow: hidden;
46
+ text-overflow: ellipsis;
47
+ white-space: nowrap;
48
+ }
49
+ .desc {
50
+ margin-top: 0.25rem;
51
+ font-size: 0.75rem;
52
+ color: rgb(255 255 255 / 0.7);
53
+ max-width: 70%;
54
+ display: -webkit-box;
55
+ -webkit-line-clamp: 2;
56
+ -webkit-box-orient: vertical;
57
+ overflow: hidden;
58
+ }
59
+ `,
60
+ ];
61
+ __decorate([
62
+ property({ type: String })
63
+ ], FwTitleOverlay.prototype, "title", void 0);
64
+ __decorate([
65
+ property({ type: String })
66
+ ], FwTitleOverlay.prototype, "description", void 0);
67
+ FwTitleOverlay = __decorate([
68
+ customElement("fw-title-overlay")
69
+ ], FwTitleOverlay);
70
+
71
+ export { FwTitleOverlay };
72
+ //# sourceMappingURL=fw-title-overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-title-overlay.js","sources":["../../../../src/components/fw-title-overlay.ts"],"sourcesContent":["import { LitElement, html, css, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { sharedStyles } from \"../styles/shared-styles.js\";\nimport { utilityStyles } from \"../styles/utility-styles.js\";\n\n@customElement(\"fw-title-overlay\")\nexport class FwTitleOverlay extends LitElement {\n @property({ type: String }) override title: string = \"\";\n @property({ type: String }) description: string | null = null;\n\n static styles = [\n sharedStyles,\n utilityStyles,\n css`\n :host {\n display: contents;\n }\n .overlay {\n position: absolute;\n inset: 0 0 auto 0;\n padding: 1rem 1.25rem;\n background: linear-gradient(to bottom, rgb(0 0 0 / 0.7), rgb(0 0 0 / 0.4), transparent);\n pointer-events: none;\n transition: opacity 300ms ease;\n z-index: 10;\n }\n .title {\n font-size: 0.875rem;\n font-weight: 500;\n color: white;\n max-width: 80%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .desc {\n margin-top: 0.25rem;\n font-size: 0.75rem;\n color: rgb(255 255 255 / 0.7);\n max-width: 70%;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n `,\n ];\n\n protected render() {\n if (this.title === \"\" && !this.description) return nothing;\n return html`\n <div class=\"overlay fw-title-overlay\">\n ${this.title ? html`<div class=\"title\">${this.title}</div>` : nothing}\n ${this.description ? html`<div class=\"desc\">${this.description}</div>` : nothing}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-title-overlay\": FwTitleOverlay;\n }\n}\n"],"names":[],"mappings":";;;;;;AAMO,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU,CAAA;AAAvC,IAAA,WAAA,GAAA;;QACgC,IAAA,CAAA,KAAK,GAAW,EAAE;QAC3B,IAAA,CAAA,WAAW,GAAkB,IAAI;IAiD/D;IATY,MAAM,GAAA;QACd,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW;AAAE,YAAA,OAAO,OAAO;AAC1D,QAAA,OAAO,IAAI,CAAA;;AAEL,QAAA,EAAA,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA,CAAA,mBAAA,EAAsB,IAAI,CAAC,KAAK,CAAA,MAAA,CAAQ,GAAG,OAAO;AACnE,QAAA,EAAA,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA,CAAA,kBAAA,EAAqB,IAAI,CAAC,WAAW,CAAA,MAAA,CAAQ,GAAG,OAAO;;KAEnF;IACH;;AA9CO,cAAA,CAAA,MAAM,GAAG;IACd,YAAY;IACZ,aAAa;AACb,IAAA,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCF,IAAA,CAAA;AACF,CApCY;AAHwB,UAAA,CAAA;AAApC,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;AAA8B,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,OAAA,EAAA,MAAA,CAAA;AAC5B,UAAA,CAAA;AAA3B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;AAAoC,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,aAAA,EAAA,MAAA,CAAA;AAFnD,cAAc,GAAA,UAAA,CAAA;IAD1B,aAAa,CAAC,kBAAkB;AACpB,CAAA,EAAA,cAAc,CAmD1B;;;;"}
@@ -0,0 +1,74 @@
1
+ import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
+ import { css, LitElement, nothing, html } from 'lit';
3
+ import { property, customElement } from 'lit/decorators.js';
4
+ import { closeIcon } from '../icons/index.js';
5
+
6
+ let FwToast = class FwToast extends LitElement {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.message = "";
10
+ }
11
+ render() {
12
+ if (!this.message)
13
+ return nothing;
14
+ return html `
15
+ <div class="toast">
16
+ <span>${this.message}</span>
17
+ <button type="button" @click=${this._dismiss} aria-label="Dismiss">${closeIcon()}</button>
18
+ </div>
19
+ `;
20
+ }
21
+ _dismiss() {
22
+ this.dispatchEvent(new CustomEvent("fw-dismiss", { bubbles: true, composed: true }));
23
+ }
24
+ };
25
+ FwToast.styles = css `
26
+ :host {
27
+ display: contents;
28
+ }
29
+ .toast {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 0.5rem;
33
+ border-radius: 0.5rem;
34
+ border: 1px solid rgb(255 255 255 / 0.1);
35
+ background: rgb(0 0 0 / 0.8);
36
+ padding: 0.5rem 1rem;
37
+ font-size: 0.875rem;
38
+ color: white;
39
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
40
+ backdrop-filter: blur(4px);
41
+ animation: _fw-slide-up 200ms ease-out;
42
+ }
43
+ @keyframes _fw-slide-up {
44
+ from {
45
+ opacity: 0;
46
+ transform: translateY(8px);
47
+ }
48
+ to {
49
+ opacity: 1;
50
+ transform: translateY(0);
51
+ }
52
+ }
53
+ button {
54
+ margin-left: 0.125rem;
55
+ color: rgb(255 255 255 / 0.6);
56
+ background: none;
57
+ border: none;
58
+ cursor: pointer;
59
+ padding: 0;
60
+ display: flex;
61
+ }
62
+ button:hover {
63
+ color: white;
64
+ }
65
+ `;
66
+ __decorate([
67
+ property({ type: String })
68
+ ], FwToast.prototype, "message", void 0);
69
+ FwToast = __decorate([
70
+ customElement("fw-toast")
71
+ ], FwToast);
72
+
73
+ export { FwToast };
74
+ //# sourceMappingURL=fw-toast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-toast.js","sources":["../../../../src/components/fw-toast.ts"],"sourcesContent":["import { LitElement, html, css, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { closeIcon } from \"../icons/index.js\";\n\n@customElement(\"fw-toast\")\nexport class FwToast extends LitElement {\n @property({ type: String }) message = \"\";\n\n static styles = css`\n :host {\n display: contents;\n }\n .toast {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n border-radius: 0.5rem;\n border: 1px solid rgb(255 255 255 / 0.1);\n background: rgb(0 0 0 / 0.8);\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n color: white;\n box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);\n backdrop-filter: blur(4px);\n animation: _fw-slide-up 200ms ease-out;\n }\n @keyframes _fw-slide-up {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n button {\n margin-left: 0.125rem;\n color: rgb(255 255 255 / 0.6);\n background: none;\n border: none;\n cursor: pointer;\n padding: 0;\n display: flex;\n }\n button:hover {\n color: white;\n }\n `;\n\n protected render() {\n if (!this.message) return nothing;\n return html`\n <div class=\"toast\">\n <span>${this.message}</span>\n <button type=\"button\" @click=${this._dismiss} aria-label=\"Dismiss\">${closeIcon()}</button>\n </div>\n `;\n }\n\n private _dismiss() {\n this.dispatchEvent(new CustomEvent(\"fw-dismiss\", { bubbles: true, composed: true }));\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-toast\": FwToast;\n }\n}\n"],"names":[],"mappings":";;;;;AAKO,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,UAAU,CAAA;AAAhC,IAAA,WAAA,GAAA;;QACuB,IAAA,CAAA,OAAO,GAAG,EAAE;IAyD1C;IAbY,MAAM,GAAA;QACd,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,OAAO;AACjC,QAAA,OAAO,IAAI,CAAA;;AAEC,cAAA,EAAA,IAAI,CAAC,OAAO,CAAA;AACW,qCAAA,EAAA,IAAI,CAAC,QAAQ,CAAA,sBAAA,EAAyB,SAAS,EAAE,CAAA;;KAEnF;IACH;IAEQ,QAAQ,GAAA;AACd,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACtF;;AAtDO,OAAA,CAAA,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwClB,EAAA,CAxCY;AAFe,UAAA,CAAA;AAA3B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;AAAe,CAAA,EAAA,OAAA,CAAA,SAAA,EAAA,SAAA,EAAA,MAAA,CAAA;AAD9B,OAAO,GAAA,UAAA,CAAA;IADnB,aAAa,CAAC,UAAU;AACZ,CAAA,EAAA,OAAO,CA0DnB;;;;"}
@@ -0,0 +1,140 @@
1
+ import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
+ import { css, LitElement, html } from 'lit';
3
+ import { property, state, customElement } from 'lit/decorators.js';
4
+ import { classMap } from 'lit/directives/class-map.js';
5
+ import { styleMap } from 'lit/directives/style-map.js';
6
+ import { sharedStyles } from '../styles/shared-styles.js';
7
+ import { volumeOffIcon, volumeUpIcon } from '../icons/index.js';
8
+
9
+ let FwVolumeControl = class FwVolumeControl extends LitElement {
10
+ constructor() {
11
+ super(...arguments);
12
+ this._expanded = false;
13
+ this._handleSliderClick = (e) => {
14
+ const target = e.currentTarget;
15
+ const rect = target.getBoundingClientRect();
16
+ const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
17
+ this.pc.setVolume(pct);
18
+ if (this.pc.s.isMuted && pct > 0)
19
+ this.pc.toggleMute();
20
+ };
21
+ this._handleSliderDrag = (e) => {
22
+ const target = e.currentTarget;
23
+ const rect = target.getBoundingClientRect();
24
+ const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
25
+ this.pc.setVolume(pct);
26
+ };
27
+ }
28
+ render() {
29
+ const { isMuted, volume } = this.pc.s;
30
+ const displayVol = isMuted ? 0 : volume;
31
+ return html `
32
+ <div
33
+ class="group fw-volume-group"
34
+ @mouseenter=${() => {
35
+ this._expanded = true;
36
+ }}
37
+ @mouseleave=${() => {
38
+ this._expanded = false;
39
+ }}
40
+ >
41
+ <button
42
+ class="btn fw-btn-flush fw-volume-btn"
43
+ type="button"
44
+ @click=${() => this.pc.toggleMute()}
45
+ aria-label="${isMuted ? "Unmute" : "Mute"}"
46
+ >
47
+ ${isMuted ? volumeOffIcon(16) : volumeUpIcon(16)}
48
+ </button>
49
+ <div class=${classMap({ "slider-wrap": true, "slider-wrap--expanded": this._expanded })}>
50
+ <div
51
+ class="slider"
52
+ @click=${this._handleSliderClick}
53
+ @pointermove=${this._handleSliderDrag}
54
+ >
55
+ <div class="slider-fill" style=${styleMap({ width: `${displayVol * 100}%` })}></div>
56
+ <div class="slider-thumb" style=${styleMap({ left: `${displayVol * 100}%` })}></div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ `;
61
+ }
62
+ };
63
+ FwVolumeControl.styles = [
64
+ sharedStyles,
65
+ css `
66
+ :host {
67
+ display: flex;
68
+ align-items: center;
69
+ }
70
+ .group {
71
+ display: flex;
72
+ align-items: center;
73
+ gap: 0;
74
+ }
75
+ .btn {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ width: 2rem;
80
+ height: 2rem;
81
+ background: none;
82
+ border: none;
83
+ color: rgb(255 255 255 / 0.8);
84
+ cursor: pointer;
85
+ padding: 0;
86
+ border-radius: 0.25rem;
87
+ transition: color 150ms;
88
+ }
89
+ .btn:hover {
90
+ color: white;
91
+ }
92
+ .slider-wrap {
93
+ width: 0;
94
+ overflow: hidden;
95
+ transition: width 200ms ease;
96
+ }
97
+ .slider-wrap--expanded {
98
+ width: 72px;
99
+ }
100
+ .slider {
101
+ position: relative;
102
+ width: 72px;
103
+ height: 4px;
104
+ background: rgb(255 255 255 / 0.15);
105
+ border-radius: 2px;
106
+ cursor: pointer;
107
+ }
108
+ .slider-fill {
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ height: 100%;
113
+ background: white;
114
+ border-radius: 2px;
115
+ pointer-events: none;
116
+ }
117
+ .slider-thumb {
118
+ position: absolute;
119
+ top: 50%;
120
+ width: 10px;
121
+ height: 10px;
122
+ border-radius: 50%;
123
+ background: white;
124
+ transform: translate(-50%, -50%);
125
+ pointer-events: none;
126
+ }
127
+ `,
128
+ ];
129
+ __decorate([
130
+ property({ attribute: false })
131
+ ], FwVolumeControl.prototype, "pc", void 0);
132
+ __decorate([
133
+ state()
134
+ ], FwVolumeControl.prototype, "_expanded", void 0);
135
+ FwVolumeControl = __decorate([
136
+ customElement("fw-volume-control")
137
+ ], FwVolumeControl);
138
+
139
+ export { FwVolumeControl };
140
+ //# sourceMappingURL=fw-volume-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fw-volume-control.js","sources":["../../../../src/components/fw-volume-control.ts"],"sourcesContent":["/**\n * <fw-volume-control> — Mute toggle + volume slider.\n */\nimport { LitElement, html, css } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { classMap } from \"lit/directives/class-map.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport { sharedStyles } from \"../styles/shared-styles.js\";\nimport { volumeUpIcon, volumeOffIcon } from \"../icons/index.js\";\nimport type { PlayerControllerHost } from \"../controllers/player-controller-host.js\";\n\n@customElement(\"fw-volume-control\")\nexport class FwVolumeControl extends LitElement {\n @property({ attribute: false }) pc!: PlayerControllerHost;\n @state() private _expanded = false;\n\n static styles = [\n sharedStyles,\n css`\n :host {\n display: flex;\n align-items: center;\n }\n .group {\n display: flex;\n align-items: center;\n gap: 0;\n }\n .btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n background: none;\n border: none;\n color: rgb(255 255 255 / 0.8);\n cursor: pointer;\n padding: 0;\n border-radius: 0.25rem;\n transition: color 150ms;\n }\n .btn:hover {\n color: white;\n }\n .slider-wrap {\n width: 0;\n overflow: hidden;\n transition: width 200ms ease;\n }\n .slider-wrap--expanded {\n width: 72px;\n }\n .slider {\n position: relative;\n width: 72px;\n height: 4px;\n background: rgb(255 255 255 / 0.15);\n border-radius: 2px;\n cursor: pointer;\n }\n .slider-fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: white;\n border-radius: 2px;\n pointer-events: none;\n }\n .slider-thumb {\n position: absolute;\n top: 50%;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: white;\n transform: translate(-50%, -50%);\n pointer-events: none;\n }\n `,\n ];\n\n private _handleSliderClick = (e: MouseEvent) => {\n const target = e.currentTarget as HTMLElement;\n const rect = target.getBoundingClientRect();\n const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n this.pc.setVolume(pct);\n if (this.pc.s.isMuted && pct > 0) this.pc.toggleMute();\n };\n\n private _handleSliderDrag = (e: PointerEvent) => {\n const target = e.currentTarget as HTMLElement;\n const rect = target.getBoundingClientRect();\n const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n this.pc.setVolume(pct);\n };\n\n protected render() {\n const { isMuted, volume } = this.pc.s;\n const displayVol = isMuted ? 0 : volume;\n\n return html`\n <div\n class=\"group fw-volume-group\"\n @mouseenter=${() => {\n this._expanded = true;\n }}\n @mouseleave=${() => {\n this._expanded = false;\n }}\n >\n <button\n class=\"btn fw-btn-flush fw-volume-btn\"\n type=\"button\"\n @click=${() => this.pc.toggleMute()}\n aria-label=\"${isMuted ? \"Unmute\" : \"Mute\"}\"\n >\n ${isMuted ? volumeOffIcon(16) : volumeUpIcon(16)}\n </button>\n <div class=${classMap({ \"slider-wrap\": true, \"slider-wrap--expanded\": this._expanded })}>\n <div\n class=\"slider\"\n @click=${this._handleSliderClick}\n @pointermove=${this._handleSliderDrag}\n >\n <div class=\"slider-fill\" style=${styleMap({ width: `${displayVol * 100}%` })}></div>\n <div class=\"slider-thumb\" style=${styleMap({ left: `${displayVol * 100}%` })}></div>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-volume-control\": FwVolumeControl;\n }\n}\n"],"names":[],"mappings":"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"}