@livepeer-frameworks/streamcrafter-wc 0.1.1 → 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.
@@ -8,98 +8,479 @@ var sharedStyles = require('../styles/shared-styles.js');
8
8
  var utilityStyles = require('../styles/utility-styles.js');
9
9
  var index = require('../icons/index.js');
10
10
 
11
+ function formatBitrate(bps) {
12
+ if (bps >= 1_000_000)
13
+ return `${(bps / 1_000_000).toFixed(1)} Mbps`;
14
+ return `${(bps / 1000).toFixed(0)} kbps`;
15
+ }
11
16
  exports.FwScAdvanced = class FwScAdvanced extends lit.LitElement {
12
17
  constructor() {
13
18
  super(...arguments);
14
- this._activeTab = "stats";
19
+ this.compositorEnabled = false;
20
+ this.compositorRendererType = null;
21
+ this.compositorStats = null;
22
+ this.sceneCount = 0;
23
+ this.layerCount = 0;
24
+ this._activeTab = "audio";
15
25
  }
16
26
  render() {
17
27
  return lit.html `
18
28
  <div class="panel">
19
29
  <div class="header">
20
- <div class="tabs">
21
- <button
22
- class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "stats" })}
23
- @click=${() => {
30
+ <button
31
+ class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "audio" })}
32
+ @click=${() => {
33
+ this._activeTab = "audio";
34
+ }}
35
+ >
36
+ Audio
37
+ </button>
38
+ <button
39
+ class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "stats" })}
40
+ @click=${() => {
24
41
  this._activeTab = "stats";
25
42
  }}
26
- >
27
- Stats
28
- </button>
29
- <button
30
- class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "info" })}
31
- @click=${() => {
43
+ >
44
+ Stats
45
+ </button>
46
+ <button
47
+ class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "info" })}
48
+ @click=${() => {
32
49
  this._activeTab = "info";
33
50
  }}
34
- >
35
- Info
36
- </button>
37
- </div>
51
+ >
52
+ Info
53
+ </button>
54
+ ${this.compositorEnabled
55
+ ? lit.html `
56
+ <button
57
+ class=${classMap_js.classMap({
58
+ tab: true,
59
+ "tab--active": this._activeTab === "compositor",
60
+ })}
61
+ @click=${() => {
62
+ this._activeTab = "compositor";
63
+ }}
64
+ >
65
+ Comp
66
+ </button>
67
+ `
68
+ : lit.nothing}
69
+ <div style="flex:1"></div>
38
70
  <button
39
71
  class="close"
40
72
  @click=${() => this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
41
- aria-label="Close panel"
73
+ aria-label="Close advanced panel"
42
74
  >
43
- ${index.xIcon(14)}
75
+ ${index.xIcon(12)}
44
76
  </button>
45
77
  </div>
46
78
  <div class="body">
47
- ${this._activeTab === "stats" ? this._renderStats() : this._renderInfo()}
79
+ ${this._activeTab === "audio"
80
+ ? this._renderAudio()
81
+ : this._activeTab === "stats"
82
+ ? this._renderStats()
83
+ : this._activeTab === "info"
84
+ ? this._renderInfo()
85
+ : this._renderCompositor()}
48
86
  </div>
49
87
  </div>
50
88
  `;
51
89
  }
90
+ // ---- Audio Tab ----
91
+ _renderAudio() {
92
+ const s = this.ic.s;
93
+ const masterVolume = this.ic.getMasterVolume();
94
+ const audioLevel = s.audioLevel;
95
+ const levelColor = audioLevel > 0.9 ? "#f7768e" : audioLevel > 0.7 ? "#e0af68" : "#9ece6a";
96
+ const volColor = masterVolume > 1 ? "#e0af68" : masterVolume === 1 ? "#9ece6a" : "#c0caf5";
97
+ return lit.html `
98
+ <!-- Master Volume -->
99
+ <div class="section">
100
+ <div class="section-header">Master Volume</div>
101
+ <div style="display:flex;align-items:center;gap:12px">
102
+ <fw-sc-volume
103
+ .value=${masterVolume}
104
+ .min=${0}
105
+ .max=${2}
106
+ @fw-sc-volume-change=${(e) => this.ic.setMasterVolume(e.detail.value)}
107
+ ></fw-sc-volume>
108
+ <span
109
+ style="font-size:14px;min-width:48px;text-align:right;color:${volColor}"
110
+ >
111
+ ${Math.round(masterVolume * 100)}%
112
+ </span>
113
+ </div>
114
+ ${masterVolume > 1
115
+ ? lit.html `<div style="font-size:10px;color:#e0af68;margin-top:4px">
116
+ +${((masterVolume - 1) * 100).toFixed(0)}% boost
117
+ </div>`
118
+ : lit.nothing}
119
+ </div>
120
+
121
+ <!-- Audio Level -->
122
+ <div class="section">
123
+ <div class="section-header">Output Level</div>
124
+ <div class="level-bar">
125
+ <div
126
+ class="level-fill"
127
+ style="width:${audioLevel * 100}%;background:${levelColor}"
128
+ ></div>
129
+ </div>
130
+ <div class="level-labels">
131
+ <span>-60dB</span><span>0dB</span>
132
+ </div>
133
+ </div>
134
+
135
+ <!-- Audio Mixing Status -->
136
+ <div class="section">
137
+ <div style="display:flex;justify-content:space-between;align-items:center">
138
+ <span class="section-header" style="margin-bottom:0">Audio Mixing</span>
139
+ <span
140
+ class="badge"
141
+ style="background:rgba(158,206,106,0.2);color:#9ece6a"
142
+ >
143
+ ON
144
+ </span>
145
+ </div>
146
+ <div style="font-size:10px;color:#565f89;margin-top:4px">
147
+ Compressor + Limiter active
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Audio Processing -->
152
+ <div style="border-bottom:1px solid rgba(65,72,104,0.3)">
153
+ <div class="section-dark">
154
+ <span class="section-header" style="margin-bottom:0">Processing</span>
155
+ <span style="font-size:9px;color:#565f89">
156
+ profile: ${s.qualityProfile}
157
+ </span>
158
+ </div>
159
+ ${this._renderToggle("Echo Cancellation", true)}
160
+ ${this._renderToggle("Noise Suppression", true)}
161
+ ${this._renderToggle("Auto Gain Control", true)}
162
+ </div>
163
+ `;
164
+ }
165
+ _renderToggle(label, checked) {
166
+ return lit.html `
167
+ <div class="processing-row">
168
+ <span class="processing-label">${label}</span>
169
+ <button
170
+ type="button"
171
+ role="switch"
172
+ aria-checked=${checked}
173
+ class="toggle ${checked ? "toggle--on" : "toggle--off"}"
174
+ >
175
+ <div class="toggle-knob"></div>
176
+ </button>
177
+ </div>
178
+ `;
179
+ }
180
+ // ---- Stats Tab ----
52
181
  _renderStats() {
53
182
  const s = this.ic.s;
54
183
  const stats = s.stats;
184
+ const stateColor = s.state === "streaming"
185
+ ? "#9ece6a"
186
+ : s.state === "connecting"
187
+ ? "#7aa2f7"
188
+ : s.state === "error"
189
+ ? "#f7768e"
190
+ : "#c0caf5";
55
191
  return lit.html `
56
192
  <div class="section">
57
- <div class="label">Connection</div>
58
- ${this._row("State", s.state)}
59
- ${this._row("WebCodecs", s.isWebCodecsActive ? "Active" : s.useWebCodecs ? "Pending" : "Off")}
193
+ <div class="section-header" style="margin-bottom:4px">Connection</div>
194
+ <div style="font-size:14px;font-weight:600;color:${stateColor}">
195
+ ${s.state.charAt(0).toUpperCase() + s.state.slice(1)}
196
+ </div>
60
197
  </div>
61
198
  ${stats
62
199
  ? lit.html `
63
- <div class="section">
64
- <div class="label">WebRTC Stats</div>
65
- ${this._row("Video bitrate", stats.video.bitrate ? `${Math.round(stats.video.bitrate / 1000)} kbps` : "—")}
66
- ${this._row("Audio bitrate", stats.audio.bitrate ? `${Math.round(stats.audio.bitrate / 1000)} kbps` : "—")}
67
- ${this._row("RTT", stats.connection.rtt ? `${(stats.connection.rtt * 1000).toFixed(0)} ms` : "—")}
68
- ${this._row("FPS", stats.video.framesPerSecond ? String(stats.video.framesPerSecond) : "—")}
69
- ${this._row("Packets sent", String(stats.video.packetsSent))}
70
- ${this._row("Packets lost", String(stats.video.packetsLost))}
200
+ <div class="row">
201
+ <span class="row-label">Bitrate</span>
202
+ <span class="row-value">
203
+ ${formatBitrate(stats.video.bitrate + stats.audio.bitrate)}
204
+ </span>
205
+ </div>
206
+ <div class="row">
207
+ <span class="row-label">Video</span>
208
+ <span class="row-value" style="color:#7aa2f7">
209
+ ${formatBitrate(stats.video.bitrate)}
210
+ </span>
211
+ </div>
212
+ <div class="row">
213
+ <span class="row-label">Audio</span>
214
+ <span class="row-value" style="color:#7aa2f7">
215
+ ${formatBitrate(stats.audio.bitrate)}
216
+ </span>
217
+ </div>
218
+ <div class="row">
219
+ <span class="row-label">Frame Rate</span>
220
+ <span class="row-value">
221
+ ${stats.video.framesPerSecond.toFixed(0)} fps
222
+ </span>
223
+ </div>
224
+ <div class="row">
225
+ <span class="row-label">Frames Encoded</span>
226
+ <span class="row-value">${stats.video.framesEncoded}</span>
227
+ </div>
228
+ ${stats.video.packetsLost > 0 || stats.audio.packetsLost > 0
229
+ ? lit.html `
230
+ <div class="row">
231
+ <span class="row-label">Packets Lost</span>
232
+ <span class="row-value" style="color:#f7768e">
233
+ ${stats.video.packetsLost + stats.audio.packetsLost}
234
+ </span>
235
+ </div>
236
+ `
237
+ : lit.nothing}
238
+ <div class="row">
239
+ <span class="row-label">RTT</span>
240
+ <span
241
+ class="row-value"
242
+ style="color:${stats.connection.rtt > 200 ? "#e0af68" : "#c0caf5"}"
243
+ >
244
+ ${stats.connection.rtt.toFixed(0)} ms
245
+ </span>
246
+ </div>
247
+ <div class="row">
248
+ <span class="row-label">ICE State</span>
249
+ <span class="row-value" style="text-transform:capitalize">
250
+ ${stats.connection.iceState}
251
+ </span>
252
+ </div>
253
+ `
254
+ : lit.html `
255
+ <div style="color:#565f89;text-align:center;padding:24px">
256
+ ${s.state === "streaming"
257
+ ? "Waiting for stats..."
258
+ : "Start streaming to see stats"}
259
+ </div>
260
+ `}
261
+ ${s.error
262
+ ? lit.html `
263
+ <div
264
+ style="padding:12px;border-top:1px solid rgba(247,118,142,0.3);background:rgba(247,118,142,0.1)"
265
+ >
266
+ <div class="section-header" style="color:#f7768e;margin-bottom:4px">
267
+ Error
268
+ </div>
269
+ <div style="font-size:12px;color:#f7768e">${s.error}</div>
71
270
  </div>
72
271
  `
73
272
  : lit.nothing}
74
273
  ${s.encoderStats
75
274
  ? lit.html `
76
- <div class="section">
77
- <div class="label">Encoder</div>
78
- ${this._row("Video frames", String(s.encoderStats.video.framesEncoded))}
79
- ${this._row("Video pending", String(s.encoderStats.video.framesPending))}
80
- ${this._row("Audio samples", String(s.encoderStats.audio.samplesEncoded))}
275
+ <div style="border-top:1px solid rgba(65,72,104,0.3)">
276
+ <div class="section-dark">
277
+ <span class="section-header" style="margin-bottom:0">Encoder</span>
278
+ </div>
279
+ <div class="row">
280
+ <span class="row-label">Video frames</span>
281
+ <span class="row-value">${s.encoderStats.video.framesEncoded}</span>
282
+ </div>
283
+ <div class="row">
284
+ <span class="row-label">Video pending</span>
285
+ <span
286
+ class="row-value"
287
+ style="color:${s.encoderStats.video.framesPending > 5
288
+ ? "#e0af68"
289
+ : "#c0caf5"}"
290
+ >
291
+ ${s.encoderStats.video.framesPending}
292
+ </span>
293
+ </div>
294
+ <div class="row">
295
+ <span class="row-label">Audio samples</span>
296
+ <span class="row-value">${s.encoderStats.audio.samplesEncoded}</span>
297
+ </div>
81
298
  </div>
82
299
  `
83
300
  : lit.nothing}
84
301
  `;
85
302
  }
303
+ // ---- Info Tab ----
86
304
  _renderInfo() {
87
305
  const s = this.ic.s;
88
306
  return lit.html `
89
307
  <div class="section">
90
- <div class="label">Configuration</div>
91
- ${this._row("Profile", s.qualityProfile)} ${this._row("Sources", String(s.sources.length))}
92
- ${this._row("WebCodecs Available", s.isWebCodecsAvailable ? "Yes" : "No")}
308
+ <div class="section-header" style="margin-bottom:4px">Quality Profile</div>
309
+ <div style="font-size:14px;color:#c0caf5;text-transform:capitalize">
310
+ ${s.qualityProfile}
311
+ </div>
93
312
  </div>
313
+
94
314
  <div class="section">
95
- <div class="label">Sources</div>
96
- ${s.sources.map((source) => lit.html `
97
- ${this._row(source.label, `${source.type} ${source.muted ? "(muted)" : ""}`)}
98
- `)}
315
+ <div class="section-header" style="margin-bottom:4px">Configuration</div>
316
+ ${this._simpleRow("WebCodecs", s.isWebCodecsAvailable ? "Available" : "Unavailable")}
317
+ ${this._simpleRow("WebCodecs Active", s.isWebCodecsActive ? "Yes" : s.useWebCodecs ? "Pending" : "No")}
318
+ ${this._simpleRow("Sources", String(s.sources.length))}
319
+ </div>
320
+
321
+ ${s.sources.length > 0
322
+ ? lit.html `
323
+ <div style="border-bottom:1px solid rgba(65,72,104,0.3)">
324
+ <div class="section-dark">
325
+ <span class="section-header" style="margin-bottom:0">
326
+ Sources (${s.sources.length})
327
+ </span>
328
+ </div>
329
+ ${s.sources.map((source, idx) => lit.html `
330
+ <div
331
+ style="padding:8px 12px;${idx > 0
332
+ ? "border-top:1px solid rgba(65,72,104,0.2)"
333
+ : ""}"
334
+ >
335
+ <div style="display:flex;align-items:center;gap:8px">
336
+ <span
337
+ class="source-type"
338
+ style="background:${source.type === "camera"
339
+ ? "rgba(122,162,247,0.2)"
340
+ : source.type === "screen"
341
+ ? "rgba(158,206,106,0.2)"
342
+ : "rgba(224,175,104,0.2)"};color:${source.type === "camera"
343
+ ? "#7aa2f7"
344
+ : source.type === "screen"
345
+ ? "#9ece6a"
346
+ : "#e0af68"}"
347
+ >
348
+ ${source.type}
349
+ </span>
350
+ <span
351
+ style="color:#c0caf5;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
352
+ >
353
+ ${source.label}
354
+ </span>
355
+ </div>
356
+ <div
357
+ style="display:flex;gap:12px;margin-top:4px;font-size:10px;color:#565f89"
358
+ >
359
+ <span>Vol: ${Math.round(source.volume * 100)}%</span>
360
+ ${source.muted
361
+ ? lit.html `<span style="color:#f7768e">Muted</span>`
362
+ : lit.nothing}
363
+ </div>
364
+ </div>
365
+ `)}
366
+ </div>
367
+ `
368
+ : lit.nothing}
369
+ `;
370
+ }
371
+ // ---- Compositor Tab ----
372
+ _renderCompositor() {
373
+ const s = this.ic.s;
374
+ const rt = this.compositorRendererType;
375
+ const stats = this.compositorStats;
376
+ const rendererColor = rt === "webgpu" ? "#bb9af7" : rt === "webgl" ? "#7aa2f7" : "#9ece6a";
377
+ const rendererLabel = rt === "webgpu"
378
+ ? "WebGPU"
379
+ : rt === "webgl"
380
+ ? "WebGL"
381
+ : rt === "canvas2d"
382
+ ? "Canvas2D"
383
+ : "Not initialized";
384
+ return lit.html `
385
+ <div class="section">
386
+ <div class="section-header">Renderer</div>
387
+ <div style="font-size:14px;font-weight:600;color:${rendererColor}">
388
+ ${rendererLabel}
389
+ </div>
390
+ </div>
391
+
392
+ ${stats
393
+ ? lit.html `
394
+ <div style="border-bottom:1px solid rgba(65,72,104,0.3)">
395
+ <div class="section-dark">
396
+ <span class="section-header" style="margin-bottom:0">Performance</span>
397
+ </div>
398
+ <div class="row">
399
+ <span class="row-label">Frame Rate</span>
400
+ <span class="row-value">${stats.fps} fps</span>
401
+ </div>
402
+ <div class="row">
403
+ <span class="row-label">Frame Time</span>
404
+ <span
405
+ class="row-value"
406
+ style="color:${stats.frameTimeMs > 16 ? "#e0af68" : "#c0caf5"}"
407
+ >
408
+ ${stats.frameTimeMs.toFixed(2)} ms
409
+ </span>
410
+ </div>
411
+ ${stats.gpuMemoryMB !== undefined
412
+ ? lit.html `
413
+ <div class="row">
414
+ <span class="row-label">GPU Memory</span>
415
+ <span class="row-value">
416
+ ${stats.gpuMemoryMB.toFixed(1)} MB
417
+ </span>
418
+ </div>
419
+ `
420
+ : lit.nothing}
421
+ </div>
422
+ `
423
+ : lit.nothing}
424
+
425
+ <div style="border-bottom:1px solid rgba(65,72,104,0.3)">
426
+ <div class="section-dark">
427
+ <span class="section-header" style="margin-bottom:0">Composition</span>
428
+ </div>
429
+ <div class="row">
430
+ <span class="row-label">Scenes</span>
431
+ <span class="row-value">${this.sceneCount}</span>
432
+ </div>
433
+ <div class="row">
434
+ <span class="row-label">Layers</span>
435
+ <span class="row-value">${this.layerCount}</span>
436
+ </div>
437
+ </div>
438
+
439
+ <!-- Encoder -->
440
+ <div style="border-bottom:1px solid rgba(65,72,104,0.3)">
441
+ <div class="section-dark">
442
+ <span class="section-header" style="margin-bottom:0">Encoder</span>
443
+ </div>
444
+ <div class="row">
445
+ <span class="row-label">Type</span>
446
+ <span
447
+ class="badge"
448
+ style="background:${s.useWebCodecs && s.isWebCodecsAvailable
449
+ ? "rgba(187,154,247,0.2)"
450
+ : "rgba(122,162,247,0.2)"};color:${s.useWebCodecs && s.isWebCodecsAvailable
451
+ ? "#bb9af7"
452
+ : "#7aa2f7"}"
453
+ >
454
+ ${s.useWebCodecs && s.isWebCodecsAvailable ? "WebCodecs" : "Browser"}
455
+ ${s.state === "streaming"
456
+ ? lit.html `<span style="opacity:0.7;margin-left:4px">
457
+ ${s.isWebCodecsActive ? "(active)" : "(pending)"}
458
+ </span>`
459
+ : lit.nothing}
460
+ </span>
461
+ </div>
462
+ <div class="processing-row">
463
+ <span class="processing-label">Use WebCodecs</span>
464
+ <button
465
+ type="button"
466
+ role="switch"
467
+ aria-checked=${s.useWebCodecs}
468
+ class="toggle ${s.useWebCodecs ? "toggle--on" : "toggle--off"}"
469
+ ?disabled=${s.state === "streaming" || !s.isWebCodecsAvailable}
470
+ @click=${() => this.ic.setUseWebCodecs(!s.useWebCodecs)}
471
+ >
472
+ <div class="toggle-knob"></div>
473
+ </button>
474
+ </div>
475
+ ${!s.isWebCodecsAvailable
476
+ ? lit.html `<div style="padding:8px 12px;font-size:10px;color:#f7768e">
477
+ Not available - RTCRtpScriptTransform unsupported
478
+ </div>`
479
+ : lit.nothing}
99
480
  </div>
100
481
  `;
101
482
  }
102
- _row(label, value) {
483
+ _simpleRow(label, value) {
103
484
  return lit.html `<div class="row">
104
485
  <span class="row-label">${label}</span><span class="row-value">${value}</span>
105
486
  </div>`;
@@ -113,68 +494,80 @@ exports.FwScAdvanced.styles = [
113
494
  display: block;
114
495
  }
115
496
  .panel {
116
- width: 320px;
497
+ width: 280px;
117
498
  height: 100%;
118
- border-left: 1px solid rgba(90, 96, 127, 0.3);
499
+ border-left: 1px solid rgba(65, 72, 104, 0.5);
119
500
  background: #1a1b26;
120
- overflow: auto;
121
- font-size: 0.75rem;
501
+ display: flex;
502
+ flex-direction: column;
503
+ font-size: 12px;
504
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;
122
505
  color: #a9b1d6;
506
+ flex-shrink: 0;
507
+ z-index: 40;
123
508
  }
124
509
  .header {
125
510
  display: flex;
126
511
  align-items: center;
127
- justify-content: space-between;
128
- padding: 0.5rem 0.75rem;
129
- border-bottom: 1px solid rgba(90, 96, 127, 0.3);
130
- }
131
- .tabs {
132
- display: flex;
133
- gap: 0.5rem;
512
+ border-bottom: 1px solid rgba(65, 72, 104, 0.3);
513
+ background: #16161e;
134
514
  }
135
515
  .tab {
136
- padding: 0.25rem 0.5rem;
516
+ padding: 8px 12px;
517
+ font-size: 10px;
518
+ text-transform: uppercase;
519
+ letter-spacing: 0.05em;
520
+ font-weight: 600;
137
521
  border: none;
138
- background: none;
522
+ background: transparent;
139
523
  color: #565f89;
140
- font-size: 0.6875rem;
141
- font-weight: 600;
142
524
  cursor: pointer;
143
- border-radius: 0.25rem;
525
+ transition: all 0.15s;
144
526
  }
145
527
  .tab--active {
528
+ background: #1a1b26;
146
529
  color: #c0caf5;
147
- background: rgba(90, 96, 127, 0.2);
148
530
  }
149
531
  .close {
150
532
  display: flex;
151
- background: none;
533
+ background: transparent;
152
534
  border: none;
153
535
  color: #565f89;
154
536
  cursor: pointer;
155
- padding: 0;
537
+ padding: 8px;
538
+ transition: color 0.15s;
156
539
  }
157
540
  .close:hover {
158
541
  color: #c0caf5;
159
542
  }
160
543
  .body {
161
- padding: 0.75rem;
162
- }
163
- .section {
164
- margin-bottom: 0.75rem;
544
+ flex: 1;
545
+ overflow-y: auto;
165
546
  }
166
- .label {
167
- font-size: 0.625rem;
168
- font-weight: 600;
547
+ .section-header {
548
+ font-size: 10px;
549
+ color: #565f89;
169
550
  text-transform: uppercase;
170
551
  letter-spacing: 0.05em;
171
- color: #565f89;
172
- margin-bottom: 0.375rem;
552
+ font-weight: 600;
553
+ margin-bottom: 8px;
554
+ }
555
+ .section {
556
+ padding: 12px;
557
+ border-bottom: 1px solid rgba(65, 72, 104, 0.3);
558
+ }
559
+ .section-dark {
560
+ padding: 8px 12px;
561
+ background: #16161e;
562
+ display: flex;
563
+ justify-content: space-between;
564
+ align-items: center;
173
565
  }
174
566
  .row {
175
567
  display: flex;
176
568
  justify-content: space-between;
177
- padding: 0.125rem 0;
569
+ padding: 8px 12px;
570
+ border-top: 1px solid rgba(65, 72, 104, 0.2);
178
571
  }
179
572
  .row-label {
180
573
  color: #565f89;
@@ -184,11 +577,102 @@ exports.FwScAdvanced.styles = [
184
577
  font-family: ui-monospace, monospace;
185
578
  font-variant-numeric: tabular-nums;
186
579
  }
580
+ .level-bar {
581
+ height: 8px;
582
+ background: rgba(65, 72, 104, 0.3);
583
+ border-radius: 4px;
584
+ overflow: hidden;
585
+ }
586
+ .level-fill {
587
+ height: 100%;
588
+ transition: all 75ms;
589
+ }
590
+ .level-labels {
591
+ display: flex;
592
+ justify-content: space-between;
593
+ font-size: 10px;
594
+ color: #565f89;
595
+ margin-top: 4px;
596
+ }
597
+ .badge {
598
+ font-size: 12px;
599
+ font-family: monospace;
600
+ padding: 2px 6px;
601
+ }
602
+ .toggle {
603
+ position: relative;
604
+ display: inline-flex;
605
+ height: 20px;
606
+ width: 36px;
607
+ flex-shrink: 0;
608
+ cursor: pointer;
609
+ border-radius: 10px;
610
+ border: 2px solid transparent;
611
+ transition: background 0.2s;
612
+ padding: 0;
613
+ }
614
+ .toggle:disabled {
615
+ opacity: 0.5;
616
+ cursor: not-allowed;
617
+ }
618
+ .toggle-knob {
619
+ position: absolute;
620
+ top: 2px;
621
+ width: 12px;
622
+ height: 12px;
623
+ border-radius: 50%;
624
+ background: white;
625
+ transition: left 0.2s;
626
+ }
627
+ .toggle--on {
628
+ background: #7aa2f7;
629
+ }
630
+ .toggle--off {
631
+ background: rgba(65, 72, 104, 0.5);
632
+ }
633
+ .toggle--on .toggle-knob {
634
+ left: 18px;
635
+ }
636
+ .toggle--off .toggle-knob {
637
+ left: 4px;
638
+ }
639
+ .processing-row {
640
+ display: flex;
641
+ justify-content: space-between;
642
+ align-items: center;
643
+ padding: 8px 12px;
644
+ border-top: 1px solid rgba(65, 72, 104, 0.2);
645
+ }
646
+ .processing-label {
647
+ font-size: 12px;
648
+ color: #a9b1d6;
649
+ }
650
+ .source-type {
651
+ font-size: 10px;
652
+ font-family: monospace;
653
+ padding: 2px 6px;
654
+ text-transform: uppercase;
655
+ }
187
656
  `,
188
657
  ];
189
658
  tslib_es6.__decorate([
190
659
  decorators_js.property({ attribute: false })
191
660
  ], exports.FwScAdvanced.prototype, "ic", void 0);
661
+ tslib_es6.__decorate([
662
+ decorators_js.property({ type: Boolean, attribute: "compositor-enabled" })
663
+ ], exports.FwScAdvanced.prototype, "compositorEnabled", void 0);
664
+ tslib_es6.__decorate([
665
+ decorators_js.property({ type: String, attribute: "compositor-renderer" })
666
+ ], exports.FwScAdvanced.prototype, "compositorRendererType", void 0);
667
+ tslib_es6.__decorate([
668
+ decorators_js.property({ attribute: false })
669
+ ], exports.FwScAdvanced.prototype, "compositorStats", void 0);
670
+ tslib_es6.__decorate([
671
+ decorators_js.property({ type: Number, attribute: "scene-count" })
672
+ ], exports.FwScAdvanced.prototype, "sceneCount", void 0);
673
+ tslib_es6.__decorate([
674
+ decorators_js.property({ type: Number, attribute: "layer-count" })
675
+ ], exports.FwScAdvanced.prototype, "layerCount", void 0);
192
676
  tslib_es6.__decorate([
193
677
  decorators_js.state()
194
678
  ], exports.FwScAdvanced.prototype, "_activeTab", void 0);