@thestatic-tv/dcl-sdk 2.5.10 → 2.5.13

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/dist/index.mjs CHANGED
@@ -625,6 +625,17 @@ var UI_DIMENSIONS = {
625
625
  buttonHeightSmall: 30,
626
626
  inputHeight: 36
627
627
  },
628
+ // Notification banner - centered, aligned with toggle buttons
629
+ notification: {
630
+ width: 340,
631
+ height: 36,
632
+ // Same as toggle buttons
633
+ bottom: 10,
634
+ // Same as toggle buttons
635
+ borderWidth: 2,
636
+ fontSize: 14
637
+ // Same as toggle buttons (14)
638
+ },
628
639
  // Shared
629
640
  closeButton: {
630
641
  size: 40,
@@ -2252,12 +2263,144 @@ var ChatUIModule = class {
2252
2263
  };
2253
2264
 
2254
2265
  // src/ui/admin-panel-ui.tsx
2255
- import ReactEcs4, { UiEntity as UiEntity4, Button as Button3, Label as Label4, Input as Input4 } from "@dcl/sdk/react-ecs";
2266
+ import ReactEcs5, { UiEntity as UiEntity5, Button as Button3, Label as Label5, Input as Input4 } from "@dcl/sdk/react-ecs";
2256
2267
  import { Color4 as Color45, Vector3 } from "@dcl/sdk/math";
2257
2268
  import { getPlayer as getPlayer2 } from "@dcl/sdk/players";
2258
2269
  import { movePlayerTo, openExternalUrl as openExternalUrl3 } from "~system/RestrictedActions";
2270
+
2271
+ // src/ui/ui-renderer.tsx
2272
+ import ReactEcs4, { ReactEcsRenderer, UiEntity as UiEntity4, Label as Label4 } from "@dcl/sdk/react-ecs";
2273
+ import { engine as engine2 } from "@dcl/sdk/ecs";
2274
+ var notificationText = "";
2275
+ var notificationVisible = false;
2276
+ var notificationEndTime = 0;
2277
+ var notificationInitialized = false;
2278
+ var C = THEME.colors;
2279
+ var N = UI_DIMENSIONS.notification;
2280
+ function showNotification(message, durationMs = 5e3) {
2281
+ notificationText = message;
2282
+ notificationVisible = true;
2283
+ notificationEndTime = Date.now() + durationMs;
2284
+ }
2285
+ function hideNotification() {
2286
+ notificationVisible = false;
2287
+ }
2288
+ function initNotificationSystem() {
2289
+ if (notificationInitialized) return;
2290
+ notificationInitialized = true;
2291
+ engine2.addSystem(() => {
2292
+ if (notificationVisible && Date.now() > notificationEndTime) {
2293
+ notificationVisible = false;
2294
+ }
2295
+ });
2296
+ }
2297
+ function NotificationBanner() {
2298
+ initNotificationSystem();
2299
+ if (!notificationVisible) return null;
2300
+ return (
2301
+ // Full-width container to center the notification
2302
+ /* @__PURE__ */ ReactEcs4.createElement(
2303
+ UiEntity4,
2304
+ {
2305
+ uiTransform: {
2306
+ positionType: "absolute",
2307
+ position: { bottom: N.bottom, left: 0, right: 0 },
2308
+ height: N.height,
2309
+ justifyContent: "center",
2310
+ alignItems: "center"
2311
+ }
2312
+ },
2313
+ /* @__PURE__ */ ReactEcs4.createElement(
2314
+ UiEntity4,
2315
+ {
2316
+ uiTransform: {
2317
+ width: N.width,
2318
+ height: N.height,
2319
+ flexDirection: "row",
2320
+ alignItems: "center"
2321
+ },
2322
+ uiBackground: { color: C.panel }
2323
+ },
2324
+ /* @__PURE__ */ ReactEcs4.createElement(
2325
+ UiEntity4,
2326
+ {
2327
+ uiTransform: {
2328
+ width: N.borderWidth,
2329
+ height: "100%"
2330
+ },
2331
+ uiBackground: { color: C.cyan }
2332
+ }
2333
+ ),
2334
+ /* @__PURE__ */ ReactEcs4.createElement(
2335
+ UiEntity4,
2336
+ {
2337
+ uiTransform: {
2338
+ flexGrow: 1,
2339
+ height: "100%",
2340
+ padding: { left: 10, right: 10 },
2341
+ justifyContent: "center",
2342
+ alignItems: "flex-start"
2343
+ }
2344
+ },
2345
+ /* @__PURE__ */ ReactEcs4.createElement(
2346
+ Label4,
2347
+ {
2348
+ value: notificationText,
2349
+ fontSize: N.fontSize,
2350
+ color: C.white,
2351
+ textAlign: "middle-left"
2352
+ }
2353
+ )
2354
+ ),
2355
+ /* @__PURE__ */ ReactEcs4.createElement(
2356
+ UiEntity4,
2357
+ {
2358
+ uiTransform: {
2359
+ width: 5,
2360
+ height: 5,
2361
+ margin: { right: 10 }
2362
+ },
2363
+ uiBackground: { color: C.cyan }
2364
+ }
2365
+ )
2366
+ )
2367
+ )
2368
+ );
2369
+ }
2370
+ function createStaticUI(client) {
2371
+ initNotificationSystem();
2372
+ return function StaticUI() {
2373
+ const currentScale = client.uiScale;
2374
+ const guideComponent = client.guideUI?.getComponent() ?? null;
2375
+ const chatComponent = client.chatUI?.getComponent() ?? null;
2376
+ const adminComponent = client.adminPanel?.getComponent() ?? null;
2377
+ return /* @__PURE__ */ ReactEcs4.createElement(
2378
+ UiEntity4,
2379
+ {
2380
+ key: `static-ui-root-${currentScale}`,
2381
+ uiTransform: {
2382
+ width: "100%",
2383
+ height: "100%",
2384
+ positionType: "absolute",
2385
+ flexDirection: "column",
2386
+ alignItems: "center"
2387
+ }
2388
+ },
2389
+ /* @__PURE__ */ ReactEcs4.createElement(NotificationBanner, null),
2390
+ guideComponent,
2391
+ chatComponent,
2392
+ adminComponent
2393
+ );
2394
+ };
2395
+ }
2396
+ function setupStaticUI(client) {
2397
+ const StaticUI = createStaticUI(client);
2398
+ ReactEcsRenderer.setUiRenderer(StaticUI);
2399
+ }
2400
+
2401
+ // src/ui/admin-panel-ui.tsx
2259
2402
  var BAN_KICK_POSITION = Vector3.create(16, -50, 16);
2260
- var C = {
2403
+ var C2 = {
2261
2404
  bg: Color45.create(0.08, 0.08, 0.12, 0.98),
2262
2405
  header: Color45.create(0.9, 0.15, 0.15, 1),
2263
2406
  tabActive: Color45.create(0, 0.7, 0.7, 1),
@@ -2298,6 +2441,8 @@ var AdminPanelUIModule = class {
2298
2441
  this.streamControlling = false;
2299
2442
  this.streamControlStatus = "";
2300
2443
  this.pollIntervalId = null;
2444
+ this.commandPollIntervalId = null;
2445
+ this.lastCommandTimestamp = 0;
2301
2446
  this.trialClaiming = false;
2302
2447
  this.trialClaimError = "";
2303
2448
  // Mod tab state
@@ -2309,22 +2454,22 @@ var AdminPanelUIModule = class {
2309
2454
  this.modStatus = "";
2310
2455
  this.modsFetched = false;
2311
2456
  // --- UI Components ---
2312
- this.SectionHead = ({ label, color }) => /* @__PURE__ */ ReactEcs4.createElement(
2313
- UiEntity4,
2457
+ this.SectionHead = ({ label, color }) => /* @__PURE__ */ ReactEcs5.createElement(
2458
+ UiEntity5,
2314
2459
  {
2315
2460
  uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
2316
2461
  uiBackground: { color: Color45.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
2317
2462
  },
2318
- /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: label, fontSize: this.theme.sectionHead, color })
2463
+ /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: label, fontSize: this.theme.sectionHead, color })
2319
2464
  );
2320
- this.TabBtn = ({ label, tab }) => /* @__PURE__ */ ReactEcs4.createElement(
2465
+ this.TabBtn = ({ label, tab }) => /* @__PURE__ */ ReactEcs5.createElement(
2321
2466
  Button3,
2322
2467
  {
2323
2468
  uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
2324
- uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
2469
+ uiBackground: { color: this.activeTab === tab ? C2.tabActive : C2.tabInactive },
2325
2470
  value: label,
2326
2471
  fontSize: this.theme.tabButton,
2327
- color: this.activeTab === tab ? Color45.Black() : C.text,
2472
+ color: this.activeTab === tab ? Color45.Black() : C2.text,
2328
2473
  textAlign: "middle-center",
2329
2474
  onMouseDown: () => this.setActiveTab(tab)
2330
2475
  }
@@ -2334,365 +2479,368 @@ var AdminPanelUIModule = class {
2334
2479
  this.fetchStreamData();
2335
2480
  }
2336
2481
  const t = this.theme;
2337
- return /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, !this.streamData?.hasChannel && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "LIVE STREAM", color: C.btn }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No streaming channel linked", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { bottom: 8 } } }), this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2482
+ return /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, !this.streamData?.hasChannel && /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "LIVE STREAM", color: C2.btn }), /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "No streaming channel linked", fontSize: t.labelSmall, color: C2.textDim, uiTransform: { margin: { bottom: 8 } } }), this.isOwner && /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column" } }, this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2338
2483
  Button3,
2339
2484
  {
2340
2485
  uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2341
- uiBackground: { color: this.trialClaiming ? C.btn : C.green },
2486
+ uiBackground: { color: this.trialClaiming ? C2.btn : C2.green },
2342
2487
  value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
2343
2488
  fontSize: t.button,
2344
- color: C.text,
2489
+ color: C2.text,
2345
2490
  onMouseDown: () => this.claimTrial()
2346
2491
  }
2347
- ), this.trialClaimError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.trialClaimError, fontSize: t.status, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "One-time trial \u2022 4 hours of streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ ReactEcs4.createElement(
2492
+ ), this.trialClaimError && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: this.trialClaimError, fontSize: t.status, color: C2.red }), /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "One-time trial \u2022 4 hours of streaming", fontSize: t.labelSmall, color: C2.textDim, uiTransform: { margin: { top: 4 } } })), !this.streamData?.trialAvailable && /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column" } }, /* @__PURE__ */ ReactEcs5.createElement(
2348
2493
  Button3,
2349
2494
  {
2350
2495
  uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2351
- uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
2496
+ uiBackground: { color: this.channelCreating ? C2.btn : C2.cyan },
2352
2497
  value: this.channelCreating ? "Creating..." : "+ Create Channel",
2353
2498
  fontSize: t.button,
2354
- color: C.text,
2499
+ color: C2.text,
2355
2500
  onMouseDown: () => this.createChannel()
2356
2501
  }
2357
- ), this.channelCreateError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelCreateError, fontSize: t.status, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Relay tier \u2022 $25/mo \u2022 8 hours streaming", fontSize: t.labelSmall, color: C.textDim, uiTransform: { margin: { top: 4 } } })))), this.streamData?.hasChannel && this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: this.streamData.isLive ? "LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C.red : C.yellow }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2358
- Label4,
2502
+ ), this.channelCreateError && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: this.channelCreateError, fontSize: t.status, color: C2.red }), /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Relay tier \u2022 $25/mo \u2022 8 hours streaming", fontSize: t.labelSmall, color: C2.textDim, uiTransform: { margin: { top: 4 } } })))), this.streamData?.hasChannel && this.isOwner && /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: this.streamData.isLive ? "LIVE STREAM" : "STREAM SETTINGS", color: this.streamData.isLive ? C2.red : C2.yellow }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 8 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2503
+ Label5,
2359
2504
  {
2360
2505
  value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
2361
2506
  fontSize: t.label,
2362
- color: this.streamData.isLive ? C.red : C.textDim
2507
+ color: this.streamData.isLive ? C2.red : C2.textDim
2363
2508
  }
2364
- ), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: t.labelSmall, color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: t.labelSmall, color: C.textDim })), /* @__PURE__ */ ReactEcs4.createElement(
2365
- Label4,
2509
+ ), /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: ` \u2022 ${this.streamData.tier?.toUpperCase() || "RELAY"}`, fontSize: t.labelSmall, color: C2.cyan }), /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: ` \u2022 ${this.streamData.sparksBalance || 0} Sparks`, fontSize: t.labelSmall, color: C2.textDim })), /* @__PURE__ */ ReactEcs5.createElement(
2510
+ Label5,
2366
2511
  {
2367
2512
  value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
2368
2513
  fontSize: t.labelSmall,
2369
- color: C.textDim,
2514
+ color: C2.textDim,
2370
2515
  uiTransform: { margin: { bottom: 10 } }
2371
2516
  }
2372
- ), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "RTMP Server:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
2373
- Label4,
2517
+ ), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "RTMP Server:", fontSize: t.labelSmall, color: C2.textDim }), /* @__PURE__ */ ReactEcs5.createElement(
2518
+ Label5,
2374
2519
  {
2375
2520
  value: this.streamData.rtmpUrl || "Loading...",
2376
2521
  fontSize: t.label,
2377
- color: this.streamData.rtmpUrl ? C.text : C.textDim,
2522
+ color: this.streamData.rtmpUrl ? C2.text : C2.textDim,
2378
2523
  uiTransform: { margin: { left: 6 } }
2379
2524
  }
2380
- )), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream Key:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
2381
- Label4,
2525
+ )), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Stream Key:", fontSize: t.labelSmall, color: C2.textDim }), /* @__PURE__ */ ReactEcs5.createElement(
2526
+ Label5,
2382
2527
  {
2383
2528
  value: this.streamData.streamKey || "Loading...",
2384
2529
  fontSize: t.label,
2385
- color: this.streamData.streamKey ? C.cyan : C.textDim,
2530
+ color: this.streamData.streamKey ? C2.cyan : C2.textDim,
2386
2531
  uiTransform: { margin: { left: 6 } }
2387
2532
  }
2388
- )), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "HLS Playback:", fontSize: t.labelSmall, color: C.textDim }), /* @__PURE__ */ ReactEcs4.createElement(
2389
- Label4,
2533
+ )), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "HLS Playback:", fontSize: t.labelSmall, color: C2.textDim }), /* @__PURE__ */ ReactEcs5.createElement(
2534
+ Label5,
2390
2535
  {
2391
2536
  value: this.streamData.hlsUrl || "Not available",
2392
2537
  fontSize: t.label,
2393
- color: this.streamData.hlsUrl ? C.green : C.textDim,
2538
+ color: this.streamData.hlsUrl ? C2.green : C2.textDim,
2394
2539
  uiTransform: { margin: { left: 6 } }
2395
2540
  }
2396
- )), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
2541
+ )), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ ReactEcs5.createElement(
2397
2542
  Button3,
2398
2543
  {
2399
2544
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2400
- uiBackground: { color: this.streamControlling ? C.btn : C.green },
2545
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.green },
2401
2546
  value: this.streamControlling ? "Starting..." : "Start Stream",
2402
2547
  fontSize: t.buttonSmall,
2403
- color: C.text,
2548
+ color: C2.text,
2404
2549
  onMouseDown: () => this.startStream()
2405
2550
  }
2406
- ), this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
2551
+ ), this.streamData.isLive && /* @__PURE__ */ ReactEcs5.createElement(
2407
2552
  Button3,
2408
2553
  {
2409
2554
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2410
- uiBackground: { color: this.streamControlling ? C.btn : C.red },
2555
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.red },
2411
2556
  value: this.streamControlling ? "Stopping..." : "Stop Stream",
2412
2557
  fontSize: t.buttonSmall,
2413
- color: C.text,
2558
+ color: C2.text,
2414
2559
  onMouseDown: () => this.stopStream()
2415
2560
  }
2416
- ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ ReactEcs4.createElement(
2561
+ ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ ReactEcs5.createElement(
2417
2562
  Button3,
2418
2563
  {
2419
2564
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2420
- uiBackground: { color: C.cyan },
2565
+ uiBackground: { color: C2.cyan },
2421
2566
  value: "Play on Screen",
2422
2567
  fontSize: t.buttonSmall,
2423
- color: C.text,
2568
+ color: C2.text,
2424
2569
  onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
2425
2570
  }
2426
- )), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2571
+ )), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2427
2572
  Button3,
2428
2573
  {
2429
2574
  uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2430
- uiBackground: { color: C.btn },
2575
+ uiBackground: { color: C2.btn },
2431
2576
  value: "Refresh",
2432
2577
  fontSize: t.buttonSmall,
2433
- color: C.text,
2578
+ color: C2.text,
2434
2579
  onMouseDown: () => this.refreshStreamStatus()
2435
2580
  }
2436
- ), /* @__PURE__ */ ReactEcs4.createElement(
2581
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2437
2582
  Button3,
2438
2583
  {
2439
2584
  uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2440
- uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
2585
+ uiBackground: { color: this.keyRotating ? C2.btn : C2.yellow },
2441
2586
  value: this.keyRotating ? "..." : "Rotate Key",
2442
2587
  fontSize: t.buttonSmall,
2443
- color: C.text,
2588
+ color: C2.text,
2444
2589
  onMouseDown: () => this.rotateStreamKey()
2445
2590
  }
2446
- ), !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
2591
+ ), !this.streamData.isLive && /* @__PURE__ */ ReactEcs5.createElement(
2447
2592
  Button3,
2448
2593
  {
2449
2594
  uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2450
- uiBackground: { color: this.channelDeleting ? C.btn : C.red },
2595
+ uiBackground: { color: this.channelDeleting ? C2.btn : C2.red },
2451
2596
  value: this.channelDeleting ? "..." : "Delete",
2452
2597
  fontSize: t.buttonSmall,
2453
- color: C.text,
2598
+ color: C2.text,
2454
2599
  onMouseDown: () => this.deleteChannel()
2455
2600
  }
2456
- )), this.streamControlStatus === "started" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream started - begin broadcasting in OBS", fontSize: t.status, color: C.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream stopped", fontSize: t.status, color: C.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Key rotated! Update OBS", fontSize: t.status, color: C.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Failed to rotate key", fontSize: t.status, color: C.red }), this.channelDeleteError && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.channelDeleteError, fontSize: t.status, color: C.red }), this.streamControlStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Stream control failed", fontSize: t.status, color: C.red })), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAY NOW", color: C.orange }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2601
+ )), this.streamControlStatus === "started" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Stream started - begin broadcasting in OBS", fontSize: t.status, color: C2.green }), this.streamControlStatus === "stopped" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Stream stopped", fontSize: t.status, color: C2.textDim }), this.keyRotateStatus === "success" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Key rotated! Update OBS", fontSize: t.status, color: C2.green }), this.keyRotateStatus === "error" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Failed to rotate key", fontSize: t.status, color: C2.red }), this.channelDeleteError && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: this.channelDeleteError, fontSize: t.status, color: C2.red }), this.streamControlStatus === "error" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Stream control failed", fontSize: t.status, color: C2.red })), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "PLAY NOW", color: C2.orange }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2457
2602
  Input4,
2458
2603
  {
2459
2604
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2460
2605
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2461
2606
  placeholder: "Video URL...",
2462
- placeholderColor: C.textDim,
2463
- color: C.text,
2607
+ placeholderColor: C2.textDim,
2608
+ color: C2.text,
2464
2609
  fontSize: t.input,
2465
2610
  value: this.customVideoUrl,
2466
2611
  onChange: (val) => {
2467
2612
  this.customVideoUrl = val;
2468
2613
  }
2469
2614
  }
2470
- ), /* @__PURE__ */ ReactEcs4.createElement(
2615
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2471
2616
  Button3,
2472
2617
  {
2473
2618
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2474
- uiBackground: { color: C.green },
2619
+ uiBackground: { color: C2.green },
2475
2620
  value: "Play",
2476
2621
  fontSize: t.button,
2477
- color: C.text,
2622
+ color: C2.text,
2478
2623
  onMouseDown: () => {
2479
2624
  if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
2480
2625
  }
2481
2626
  }
2482
- ), /* @__PURE__ */ ReactEcs4.createElement(
2627
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2483
2628
  Button3,
2484
2629
  {
2485
2630
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
2486
- uiBackground: { color: C.btn },
2631
+ uiBackground: { color: C2.btn },
2487
2632
  value: "Clear",
2488
2633
  fontSize: t.button,
2489
- color: C.text,
2634
+ color: C2.text,
2490
2635
  onMouseDown: () => {
2491
2636
  this.customVideoUrl = "";
2492
2637
  }
2493
2638
  }
2494
- )), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "PLAYBACK", color: C.green }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2639
+ )), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "PLAYBACK", color: C2.green }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2495
2640
  Button3,
2496
2641
  {
2497
2642
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2498
- uiBackground: { color: C.green },
2643
+ uiBackground: { color: C2.green },
2499
2644
  value: "Play",
2500
2645
  fontSize: t.button,
2501
- color: C.text,
2646
+ color: C2.text,
2502
2647
  onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
2503
2648
  }
2504
- ), /* @__PURE__ */ ReactEcs4.createElement(
2649
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2505
2650
  Button3,
2506
2651
  {
2507
2652
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2508
- uiBackground: { color: C.red },
2653
+ uiBackground: { color: C2.red },
2509
2654
  value: "Stop",
2510
2655
  fontSize: t.button,
2511
- color: C.text,
2656
+ color: C2.text,
2512
2657
  onMouseDown: () => this.config.onVideoStop?.()
2513
2658
  }
2514
- ), /* @__PURE__ */ ReactEcs4.createElement(
2659
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2515
2660
  Button3,
2516
2661
  {
2517
2662
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2518
- uiBackground: { color: C.btn },
2663
+ uiBackground: { color: C2.btn },
2519
2664
  value: "Reset Default",
2520
2665
  fontSize: t.buttonSmall,
2521
- color: C.text,
2666
+ color: C2.text,
2522
2667
  onMouseDown: () => this.config.onCommand?.("videoClear", {})
2523
2668
  }
2524
- )), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C.cyan }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 1", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot1") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 2", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot2") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 3", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot3") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 4", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot4") }), /* @__PURE__ */ ReactEcs4.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C.cyan }, value: "Play 5", fontSize: t.button, color: C.text, onMouseDown: () => this.playSlot("slot5") })), /* @__PURE__ */ ReactEcs4.createElement(
2669
+ )), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "VIDEO SLOTS", color: C2.cyan }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs5.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 1", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot1") }), /* @__PURE__ */ ReactEcs5.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 2", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot2") }), /* @__PURE__ */ ReactEcs5.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 3", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot3") }), /* @__PURE__ */ ReactEcs5.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 4", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot4") }), /* @__PURE__ */ ReactEcs5.createElement(Button3, { uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 }, uiBackground: { color: C2.cyan }, value: "Play 5", fontSize: t.button, color: C2.text, onMouseDown: () => this.playSlot("slot5") })), /* @__PURE__ */ ReactEcs5.createElement(
2525
2670
  Button3,
2526
2671
  {
2527
2672
  uiTransform: { height: this.s(24) },
2528
2673
  uiBackground: { color: Color45.create(0, 0, 0, 0) },
2529
2674
  value: "Edit slots at thestatic.tv",
2530
2675
  fontSize: t.labelSmall,
2531
- color: C.cyan,
2676
+ color: C2.cyan,
2532
2677
  onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2533
2678
  }
2534
2679
  ));
2535
2680
  };
2536
2681
  this.ModTab = () => {
2537
2682
  const t = this.theme;
2538
- return /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, this.modStatus === "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Loading...", fontSize: t.status, color: C.yellow }), this.modStatus === "saved" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Saved!", fontSize: t.status, color: C.green }), this.modStatus === "error" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "Error - check input", fontSize: t.status, color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "BROADCAST", color: C.orange }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2683
+ return /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", width: "100%", padding: 10 } }, this.modStatus === "loading" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Loading...", fontSize: t.status, color: C2.yellow }), this.modStatus === "saved" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Saved!", fontSize: t.status, color: C2.green }), this.modStatus === "error" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "Error - check input", fontSize: t.status, color: C2.red }), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "BROADCAST", color: C2.orange }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2539
2684
  Input4,
2540
2685
  {
2541
2686
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2542
2687
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2543
2688
  placeholder: "Message to all players...",
2544
- placeholderColor: C.textDim,
2545
- color: C.text,
2689
+ placeholderColor: C2.textDim,
2690
+ color: C2.text,
2546
2691
  fontSize: t.input,
2547
2692
  value: this.broadcastText,
2548
2693
  onChange: (val) => {
2549
2694
  this.broadcastText = val;
2550
2695
  }
2551
2696
  }
2552
- ), /* @__PURE__ */ ReactEcs4.createElement(
2697
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2553
2698
  Button3,
2554
2699
  {
2555
2700
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2556
- uiBackground: { color: C.orange },
2701
+ uiBackground: { color: C2.orange },
2557
2702
  value: "Send",
2558
2703
  fontSize: t.button,
2559
- color: C.text,
2704
+ color: C2.text,
2560
2705
  onMouseDown: () => this.sendBroadcast()
2561
2706
  }
2562
- )), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "CHAOS MODE", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2707
+ )), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "CHAOS MODE", color: C2.red }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2563
2708
  Button3,
2564
2709
  {
2565
2710
  uiTransform: { width: this.s(130), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: 4 },
2566
- uiBackground: { color: C.red },
2711
+ uiBackground: { color: C2.red },
2567
2712
  value: "KICK ALL",
2568
2713
  fontSize: t.button,
2569
- color: C.text,
2714
+ color: C2.text,
2570
2715
  onMouseDown: () => {
2571
2716
  this.log("KICK ALL clicked");
2572
2717
  this.config.onCommand?.("kickAll", {});
2573
2718
  }
2574
2719
  }
2575
- )), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C.purple }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No scene admins", fontSize: t.labelSmall, color: C.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
2576
- UiEntity4,
2720
+ )), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "SCENE ADMINS", color: C2.purple }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.sceneAdmins.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "No scene admins", fontSize: t.labelSmall, color: C2.textDim }), this.sceneAdmins.map((wallet, i) => /* @__PURE__ */ ReactEcs5.createElement(
2721
+ UiEntity5,
2577
2722
  {
2578
2723
  key: `admin-${i}`,
2579
2724
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2580
2725
  },
2581
- /* @__PURE__ */ ReactEcs4.createElement(
2582
- Label4,
2726
+ /* @__PURE__ */ ReactEcs5.createElement(
2727
+ Label5,
2583
2728
  {
2584
2729
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2585
2730
  fontSize: t.label,
2586
- color: C.text,
2731
+ color: C2.text,
2587
2732
  uiTransform: { width: this.s(130) }
2588
2733
  }
2589
2734
  ),
2590
- /* @__PURE__ */ ReactEcs4.createElement(
2735
+ /* @__PURE__ */ ReactEcs5.createElement(
2591
2736
  Button3,
2592
2737
  {
2593
2738
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2594
- uiBackground: { color: C.btn },
2739
+ uiBackground: { color: C2.btn },
2595
2740
  value: "Remove",
2596
2741
  fontSize: t.buttonSmall,
2597
- color: C.text,
2742
+ color: C2.text,
2598
2743
  onMouseDown: () => this.removeSceneAdmin(wallet)
2599
2744
  }
2600
2745
  )
2601
- ))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2746
+ ))), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2602
2747
  Input4,
2603
2748
  {
2604
2749
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2605
2750
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2606
2751
  placeholder: "0x... add admin",
2607
- placeholderColor: C.textDim,
2608
- color: C.text,
2752
+ placeholderColor: C2.textDim,
2753
+ color: C2.text,
2609
2754
  fontSize: t.input,
2610
2755
  value: this.newAdminWallet,
2611
2756
  onChange: (val) => {
2612
2757
  this.newAdminWallet = val;
2613
2758
  }
2614
2759
  }
2615
- ), /* @__PURE__ */ ReactEcs4.createElement(
2760
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2616
2761
  Button3,
2617
2762
  {
2618
2763
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2619
- uiBackground: { color: C.purple },
2764
+ uiBackground: { color: C2.purple },
2620
2765
  value: "Add",
2621
2766
  fontSize: t.button,
2622
- color: C.text,
2767
+ color: C2.text,
2623
2768
  onMouseDown: () => {
2624
2769
  if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
2625
2770
  }
2626
2771
  }
2627
- )), /* @__PURE__ */ ReactEcs4.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C.red }), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: "No banned wallets", fontSize: t.labelSmall, color: C.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ ReactEcs4.createElement(
2628
- UiEntity4,
2772
+ )), /* @__PURE__ */ ReactEcs5.createElement(this.SectionHead, { label: "BANNED WALLETS", color: C2.red }), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "column", margin: { bottom: 6 } } }, this.bannedWallets.length === 0 && this.modStatus !== "loading" && /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: "No banned wallets", fontSize: t.labelSmall, color: C2.textDim }), this.bannedWallets.map((wallet, i) => /* @__PURE__ */ ReactEcs5.createElement(
2773
+ UiEntity5,
2629
2774
  {
2630
2775
  key: `ban-${i}`,
2631
2776
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2632
2777
  },
2633
- /* @__PURE__ */ ReactEcs4.createElement(
2634
- Label4,
2778
+ /* @__PURE__ */ ReactEcs5.createElement(
2779
+ Label5,
2635
2780
  {
2636
2781
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2637
2782
  fontSize: t.label,
2638
- color: C.red,
2783
+ color: C2.red,
2639
2784
  uiTransform: { width: this.s(130) }
2640
2785
  }
2641
2786
  ),
2642
- /* @__PURE__ */ ReactEcs4.createElement(
2787
+ /* @__PURE__ */ ReactEcs5.createElement(
2643
2788
  Button3,
2644
2789
  {
2645
2790
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2646
- uiBackground: { color: C.green },
2791
+ uiBackground: { color: C2.green },
2647
2792
  value: "Unban",
2648
2793
  fontSize: t.buttonSmall,
2649
- color: C.text,
2794
+ color: C2.text,
2650
2795
  onMouseDown: () => this.unbanWallet(wallet)
2651
2796
  }
2652
2797
  )
2653
- ))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2798
+ ))), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2654
2799
  Input4,
2655
2800
  {
2656
2801
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2657
2802
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2658
2803
  placeholder: "0x... ban wallet",
2659
- placeholderColor: C.textDim,
2660
- color: C.text,
2804
+ placeholderColor: C2.textDim,
2805
+ color: C2.text,
2661
2806
  fontSize: t.input,
2662
2807
  value: this.newBanWallet,
2663
2808
  onChange: (val) => {
2664
2809
  this.newBanWallet = val;
2665
2810
  }
2666
2811
  }
2667
- ), /* @__PURE__ */ ReactEcs4.createElement(
2812
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2668
2813
  Button3,
2669
2814
  {
2670
2815
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2671
- uiBackground: { color: C.red },
2816
+ uiBackground: { color: C2.red },
2672
2817
  value: "Ban",
2673
2818
  fontSize: t.button,
2674
- color: C.text,
2819
+ color: C2.text,
2675
2820
  onMouseDown: () => {
2676
2821
  if (this.newBanWallet) this.banWallet(this.newBanWallet);
2677
2822
  }
2678
2823
  }
2679
- )), /* @__PURE__ */ ReactEcs4.createElement(
2824
+ )), /* @__PURE__ */ ReactEcs5.createElement(
2680
2825
  Button3,
2681
2826
  {
2682
2827
  uiTransform: { height: this.s(24) },
2683
2828
  uiBackground: { color: Color45.create(0, 0, 0, 0) },
2684
2829
  value: "Manage at thestatic.tv",
2685
2830
  fontSize: t.labelSmall,
2686
- color: C.cyan,
2831
+ color: C2.cyan,
2687
2832
  onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2688
2833
  }
2689
2834
  ));
2690
2835
  };
2691
2836
  /**
2692
2837
  * Get the React-ECS component for the admin panel
2838
+ * Always renders NotificationBanner (for broadcasts), admin panel only for admins
2693
2839
  */
2694
2840
  this.getComponent = () => {
2695
- if (!this.isAdmin) return null;
2841
+ if (!this.isAdmin) {
2842
+ return /* @__PURE__ */ ReactEcs5.createElement(NotificationBanner, null);
2843
+ }
2696
2844
  const t = this.theme;
2697
2845
  const tabs = [];
2698
2846
  if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
@@ -2707,8 +2855,8 @@ var AdminPanelUIModule = class {
2707
2855
  if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
2708
2856
  this.activeTab = tabs[0].id;
2709
2857
  }
2710
- return /* @__PURE__ */ ReactEcs4.createElement(
2711
- UiEntity4,
2858
+ return /* @__PURE__ */ ReactEcs5.createElement(
2859
+ UiEntity5,
2712
2860
  {
2713
2861
  uiTransform: {
2714
2862
  width: "100%",
@@ -2716,28 +2864,29 @@ var AdminPanelUIModule = class {
2716
2864
  positionType: "absolute"
2717
2865
  }
2718
2866
  },
2719
- /* @__PURE__ */ ReactEcs4.createElement(
2720
- UiEntity4,
2867
+ /* @__PURE__ */ ReactEcs5.createElement(NotificationBanner, null),
2868
+ /* @__PURE__ */ ReactEcs5.createElement(
2869
+ UiEntity5,
2721
2870
  {
2722
2871
  uiTransform: {
2723
2872
  position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
2724
2873
  positionType: "absolute"
2725
2874
  }
2726
2875
  },
2727
- /* @__PURE__ */ ReactEcs4.createElement(
2876
+ /* @__PURE__ */ ReactEcs5.createElement(
2728
2877
  Button3,
2729
2878
  {
2730
2879
  uiTransform: { width: this.s(100), height: this.s(45) },
2731
- uiBackground: { color: this.panelOpen ? C.btn : C.header },
2880
+ uiBackground: { color: this.panelOpen ? C2.btn : C2.header },
2732
2881
  value: this.panelOpen ? "CLOSE" : "ADMIN",
2733
2882
  fontSize: this.s(14),
2734
- color: C.text,
2883
+ color: C2.text,
2735
2884
  onMouseDown: () => this.toggle()
2736
2885
  }
2737
2886
  )
2738
2887
  ),
2739
- this.panelOpen && /* @__PURE__ */ ReactEcs4.createElement(
2740
- UiEntity4,
2888
+ this.panelOpen && /* @__PURE__ */ ReactEcs5.createElement(
2889
+ UiEntity5,
2741
2890
  {
2742
2891
  key: `admin-panel-${this.client.uiScale}`,
2743
2892
  uiTransform: {
@@ -2748,10 +2897,10 @@ var AdminPanelUIModule = class {
2748
2897
  positionType: "absolute",
2749
2898
  flexDirection: "column"
2750
2899
  },
2751
- uiBackground: { color: C.bg }
2900
+ uiBackground: { color: C2.bg }
2752
2901
  },
2753
- /* @__PURE__ */ ReactEcs4.createElement(
2754
- UiEntity4,
2902
+ /* @__PURE__ */ ReactEcs5.createElement(
2903
+ UiEntity5,
2755
2904
  {
2756
2905
  uiTransform: {
2757
2906
  width: "100%",
@@ -2761,10 +2910,10 @@ var AdminPanelUIModule = class {
2761
2910
  flexDirection: "row",
2762
2911
  padding: { left: 12, right: 8 }
2763
2912
  },
2764
- uiBackground: { color: C.header }
2913
+ uiBackground: { color: C2.header }
2765
2914
  },
2766
- /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C.text }),
2767
- /* @__PURE__ */ ReactEcs4.createElement(
2915
+ /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C2.text }),
2916
+ /* @__PURE__ */ ReactEcs5.createElement(
2768
2917
  Button3,
2769
2918
  {
2770
2919
  uiTransform: { width: this.s(28), height: this.s(28) },
@@ -2776,9 +2925,9 @@ var AdminPanelUIModule = class {
2776
2925
  }
2777
2926
  )
2778
2927
  ),
2779
- /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.tabHeight), flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ ReactEcs4.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
2780
- /* @__PURE__ */ ReactEcs4.createElement(
2781
- UiEntity4,
2928
+ /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.tabHeight), flexDirection: "row" } }, tabs.map((tab) => /* @__PURE__ */ ReactEcs5.createElement(this.TabBtn, { label: tab.label, tab: tab.id }))),
2929
+ /* @__PURE__ */ ReactEcs5.createElement(
2930
+ UiEntity5,
2782
2931
  {
2783
2932
  uiTransform: {
2784
2933
  width: "100%",
@@ -2787,12 +2936,12 @@ var AdminPanelUIModule = class {
2787
2936
  flexDirection: "column"
2788
2937
  }
2789
2938
  },
2790
- this.activeTab === "video" && /* @__PURE__ */ ReactEcs4.createElement(this.VideoTab, null),
2791
- this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(this.ModTab, null),
2939
+ this.activeTab === "video" && /* @__PURE__ */ ReactEcs5.createElement(this.VideoTab, null),
2940
+ this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ ReactEcs5.createElement(this.ModTab, null),
2792
2941
  this.config.sceneTabs?.map((tab) => this.activeTab === tab.id && tab.render())
2793
2942
  ),
2794
- /* @__PURE__ */ ReactEcs4.createElement(
2795
- UiEntity4,
2943
+ /* @__PURE__ */ ReactEcs5.createElement(
2944
+ UiEntity5,
2796
2945
  {
2797
2946
  uiTransform: {
2798
2947
  width: "100%",
@@ -2800,16 +2949,16 @@ var AdminPanelUIModule = class {
2800
2949
  justifyContent: "center",
2801
2950
  alignItems: "center"
2802
2951
  },
2803
- uiBackground: { color: C.section }
2952
+ uiBackground: { color: C2.section }
2804
2953
  },
2805
- /* @__PURE__ */ ReactEcs4.createElement(
2954
+ /* @__PURE__ */ ReactEcs5.createElement(
2806
2955
  Button3,
2807
2956
  {
2808
2957
  uiTransform: { height: this.s(24) },
2809
2958
  uiBackground: { color: Color45.create(0, 0, 0, 0) },
2810
2959
  value: `thestatic.tv/scene/${this.config.sceneId}`,
2811
2960
  fontSize: t.labelSmall,
2812
- color: C.cyan,
2961
+ color: C2.cyan,
2813
2962
  onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2814
2963
  }
2815
2964
  )
@@ -2832,7 +2981,7 @@ var AdminPanelUIModule = class {
2832
2981
  };
2833
2982
  this.baseUrl = client.getBaseUrl();
2834
2983
  if (config.headerColor) {
2835
- C.header = Color45.create(
2984
+ C2.header = Color45.create(
2836
2985
  config.headerColor.r,
2837
2986
  config.headerColor.g,
2838
2987
  config.headerColor.b,
@@ -2860,6 +3009,7 @@ var AdminPanelUIModule = class {
2860
3009
  await this.checkAdminStatus();
2861
3010
  await this.fetchVideoState();
2862
3011
  this.autoPlayDefault();
3012
+ this.startCommandPolling();
2863
3013
  this.log("Initialized");
2864
3014
  }
2865
3015
  /**
@@ -2965,6 +3115,61 @@ var AdminPanelUIModule = class {
2965
3115
  this.log("Stream polling stopped");
2966
3116
  }
2967
3117
  }
3118
+ // --- Command Polling (receives broadcasts from website) ---
3119
+ startCommandPolling() {
3120
+ if (this.commandPollIntervalId !== null) return;
3121
+ this.commandPollIntervalId = dclSetInterval(() => {
3122
+ this.pollCommands();
3123
+ }, 5e3);
3124
+ this.pollCommands();
3125
+ this.log("Command polling started");
3126
+ }
3127
+ stopCommandPolling() {
3128
+ if (this.commandPollIntervalId !== null) {
3129
+ dclClearInterval(this.commandPollIntervalId);
3130
+ this.commandPollIntervalId = null;
3131
+ this.log("Command polling stopped");
3132
+ }
3133
+ }
3134
+ async pollCommands() {
3135
+ try {
3136
+ const res = await fetch(`${this.baseUrl}/scene/${this.config.sceneId}/commands`);
3137
+ if (!res.ok) return;
3138
+ const data = await res.json();
3139
+ const commands = data.commands || [];
3140
+ for (const cmd of commands) {
3141
+ if (cmd.timestamp <= this.lastCommandTimestamp) continue;
3142
+ this.lastCommandTimestamp = cmd.timestamp;
3143
+ if (cmd.type === "broadcast" && cmd.payload?.text) {
3144
+ const msg = cmd.payload.from ? `${cmd.payload.from}: ${cmd.payload.text}` : cmd.payload.text;
3145
+ this.log("Received broadcast:", msg);
3146
+ this.config.onBroadcast?.(msg);
3147
+ } else if (cmd.type === "clearBroadcast") {
3148
+ this.log("Received clearBroadcast");
3149
+ hideNotification();
3150
+ } else if ((cmd.type === "videoPlay" || cmd.type === "videoSetUrl") && cmd.payload?.url) {
3151
+ this.log("Received video play:", cmd.type, cmd.payload.url);
3152
+ this.config.onVideoPlay?.(cmd.payload.url);
3153
+ } else if (cmd.type === "videoStop" || cmd.type === "videoClear") {
3154
+ this.log("Received video stop/clear:", cmd.type);
3155
+ this.config.onVideoStop?.();
3156
+ } else if (cmd.type === "videoPlaySlot" && cmd.payload?.slot) {
3157
+ const slotId = cmd.payload.slot;
3158
+ if (cmd.payload.url) {
3159
+ this.log("Received videoPlaySlot with URL:", slotId, cmd.payload.url);
3160
+ this.config.onVideoPlay?.(cmd.payload.url);
3161
+ } else {
3162
+ this.log("Received videoPlaySlot:", slotId);
3163
+ this.playSlot(slotId);
3164
+ }
3165
+ } else if (this.config.onCommand) {
3166
+ this.log("Received command:", cmd.type, cmd.payload);
3167
+ this.config.onCommand(cmd.type, cmd.payload);
3168
+ }
3169
+ }
3170
+ } catch (err) {
3171
+ }
3172
+ }
2968
3173
  // --- Stream API Calls ---
2969
3174
  async fetchStreamData() {
2970
3175
  if (this.streamFetched || !this.playerWallet) return;
@@ -3396,87 +3601,6 @@ var AdminPanelUIModule = class {
3396
3601
  }
3397
3602
  };
3398
3603
 
3399
- // src/ui/ui-renderer.tsx
3400
- import ReactEcs5, { ReactEcsRenderer, UiEntity as UiEntity5, Label as Label5 } from "@dcl/sdk/react-ecs";
3401
- import { Color4 as Color46 } from "@dcl/sdk/math";
3402
- import { engine as engine2 } from "@dcl/sdk/ecs";
3403
- var notificationText = "";
3404
- var notificationVisible = false;
3405
- var notificationEndTime = 0;
3406
- var notificationInitialized = false;
3407
- function showNotification(message, durationMs = 5e3) {
3408
- notificationText = message;
3409
- notificationVisible = true;
3410
- notificationEndTime = Date.now() + durationMs;
3411
- }
3412
- function initNotificationSystem() {
3413
- if (notificationInitialized) return;
3414
- notificationInitialized = true;
3415
- engine2.addSystem(() => {
3416
- if (notificationVisible && Date.now() > notificationEndTime) {
3417
- notificationVisible = false;
3418
- }
3419
- });
3420
- }
3421
- function NotificationBanner() {
3422
- if (!notificationVisible) return null;
3423
- return /* @__PURE__ */ ReactEcs5.createElement(
3424
- UiEntity5,
3425
- {
3426
- uiTransform: {
3427
- positionType: "absolute",
3428
- position: { top: 80 },
3429
- width: 500,
3430
- height: 60,
3431
- padding: 16,
3432
- alignSelf: "center",
3433
- justifyContent: "center",
3434
- alignItems: "center"
3435
- },
3436
- uiBackground: { color: Color46.create(0.1, 0.1, 0.15, 0.95) }
3437
- },
3438
- /* @__PURE__ */ ReactEcs5.createElement(
3439
- Label5,
3440
- {
3441
- value: notificationText,
3442
- fontSize: 18,
3443
- color: Color46.create(0, 1, 1, 1),
3444
- textAlign: "middle-center"
3445
- }
3446
- )
3447
- );
3448
- }
3449
- function createStaticUI(client) {
3450
- initNotificationSystem();
3451
- return function StaticUI() {
3452
- const currentScale = client.uiScale;
3453
- const guideComponent = client.guideUI?.getComponent() ?? null;
3454
- const chatComponent = client.chatUI?.getComponent() ?? null;
3455
- const adminComponent = client.adminPanel?.getComponent() ?? null;
3456
- return /* @__PURE__ */ ReactEcs5.createElement(
3457
- UiEntity5,
3458
- {
3459
- key: `static-ui-root-${currentScale}`,
3460
- uiTransform: {
3461
- width: "100%",
3462
- height: "100%",
3463
- positionType: "absolute",
3464
- flexDirection: "column",
3465
- alignItems: "center"
3466
- }
3467
- },
3468
- /* @__PURE__ */ ReactEcs5.createElement(NotificationBanner, null),
3469
- guideComponent,
3470
- chatComponent,
3471
- adminComponent
3472
- );
3473
- };
3474
- }
3475
- function setupStaticUI(client) {
3476
- const StaticUI = createStaticUI(client);
3477
- ReactEcsRenderer.setUiRenderer(StaticUI);
3478
- }
3479
-
3480
3604
  // src/StaticTVClient.ts
3481
3605
  import { VideoPlayer, videoEventsSystem, VideoState } from "@dcl/sdk/ecs";
3482
3606
  import * as utils from "@dcl-sdk/utils";
@@ -3757,10 +3881,13 @@ var StaticTVClient = class {
3757
3881
  */
3758
3882
  stopVideo() {
3759
3883
  const screen = this.config.videoScreen;
3884
+ this._pendingVideoData = null;
3885
+ this._streamVerified = false;
3886
+ this._clearVerificationTimeout();
3887
+ if (this.guideUI) {
3888
+ this.guideUI.currentVideoId = null;
3889
+ }
3760
3890
  if (screen !== void 0) {
3761
- if (this.guideUI) {
3762
- this.guideUI.currentVideoId = null;
3763
- }
3764
3891
  const fallbackUrl = this.config.fallbackVideoUrl;
3765
3892
  const fallbackDisabled = fallbackUrl === "";
3766
3893
  if (fallbackDisabled) {
@@ -3916,20 +4043,60 @@ var StaticTVClient = class {
3916
4043
  }
3917
4044
  /**
3918
4045
  * Play video with stream verification for live content
4046
+ * Works with both videoScreen (SDK-managed) and onVideoPlay (user-managed) modes.
3919
4047
  * @internal
3920
4048
  */
3921
4049
  _playVideoWithVerification(video, isLiveStream) {
3922
4050
  const screen = this.config.videoScreen;
4051
+ const hasCallback = this.config.onVideoPlay !== void 0;
4052
+ this.log(`Playing video: ${video.src} (live: ${isLiveStream}, screen: ${screen !== void 0}, callback: ${hasCallback})`);
4053
+ this._currentVideoUrl = video.src;
4054
+ this._clearVerificationTimeout();
4055
+ this._streamVerified = false;
3923
4056
  if (screen !== void 0) {
3924
- this.log(`Playing video: ${video.src} (live: ${isLiveStream})`);
3925
- this._currentVideoUrl = video.src;
3926
4057
  this._playVideoInternal(video.src, false, isLiveStream);
3927
- if (this.guideUI) {
3928
- this.guideUI.currentVideoId = video.id;
3929
- }
3930
4058
  }
3931
- if (this.config.onVideoPlay) {
4059
+ if (hasCallback) {
3932
4060
  this.config.onVideoPlay(video.src);
4061
+ if (isLiveStream) {
4062
+ this._startCallbackVerification(video);
4063
+ }
4064
+ }
4065
+ if (this.guideUI) {
4066
+ this.guideUI.currentVideoId = video.id;
4067
+ }
4068
+ }
4069
+ /**
4070
+ * Start stream verification for callback-based video playback
4071
+ * Shows CONNECTING state and triggers fallback if verification times out
4072
+ * @internal
4073
+ */
4074
+ _startCallbackVerification(video) {
4075
+ this.showNotification(`Connecting to ${video.name}...`, 6e3);
4076
+ this._verificationTimeoutId = utils.timers.setTimeout(() => {
4077
+ if (!this._streamVerified && this._pendingVideoData) {
4078
+ this.log(`Stream verification timeout (callback mode): ${video.name}`);
4079
+ this._handleStreamOffline();
4080
+ }
4081
+ }, 5e3);
4082
+ }
4083
+ /**
4084
+ * Call this to confirm that a video stream is playing successfully.
4085
+ * Use this when you're managing video playback yourself (onVideoPlay callback).
4086
+ *
4087
+ * @example
4088
+ * ```typescript
4089
+ * // In your video player's onReady or similar event:
4090
+ * videoPlayer.events.on('playing', () => {
4091
+ * staticTV.confirmVideoPlaying()
4092
+ * })
4093
+ * ```
4094
+ */
4095
+ confirmVideoPlaying() {
4096
+ if (this._pendingVideoData && !this._streamVerified) {
4097
+ this._streamVerified = true;
4098
+ this._clearVerificationTimeout();
4099
+ this.log(`Stream confirmed playing: ${this._pendingVideoData.name}`);
3933
4100
  }
3934
4101
  }
3935
4102
  /**
@@ -4203,11 +4370,13 @@ export {
4203
4370
  InteractionsModule,
4204
4371
  KEY_TYPE_CHANNEL,
4205
4372
  KEY_TYPE_SCENE,
4373
+ NotificationBanner,
4206
4374
  SessionModule,
4207
4375
  StaticTVClient,
4208
4376
  fetchUserData,
4209
4377
  getPlayerDisplayName,
4210
4378
  getPlayerWallet,
4379
+ hideNotification,
4211
4380
  setupStaticUI,
4212
4381
  showNotification
4213
4382
  };