@thestatic-tv/dcl-sdk 2.5.10 → 2.5.11

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 - next to DCL native chat panel
629
+ notification: {
630
+ width: 340,
631
+ height: 44,
632
+ bottom: 55,
633
+ // Above the chat input area
634
+ left: 400,
635
+ // Right of DCL native chat (chat is ~380px wide)
636
+ borderWidth: 2,
637
+ fontSize: 14
638
+ },
628
639
  // Shared
629
640
  closeButton: {
630
641
  size: 40,
@@ -2252,12 +2263,131 @@ 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 /* @__PURE__ */ ReactEcs4.createElement(
2301
+ UiEntity4,
2302
+ {
2303
+ uiTransform: {
2304
+ positionType: "absolute",
2305
+ position: { bottom: N.bottom, left: N.left },
2306
+ width: N.width,
2307
+ height: N.height,
2308
+ flexDirection: "row",
2309
+ alignItems: "center"
2310
+ },
2311
+ uiBackground: { color: C.panel }
2312
+ },
2313
+ /* @__PURE__ */ ReactEcs4.createElement(
2314
+ UiEntity4,
2315
+ {
2316
+ uiTransform: {
2317
+ width: N.borderWidth,
2318
+ height: "100%"
2319
+ },
2320
+ uiBackground: { color: C.cyan }
2321
+ }
2322
+ ),
2323
+ /* @__PURE__ */ ReactEcs4.createElement(
2324
+ UiEntity4,
2325
+ {
2326
+ uiTransform: {
2327
+ flexGrow: 1,
2328
+ height: "100%",
2329
+ padding: { left: 12, right: 12 },
2330
+ justifyContent: "center",
2331
+ alignItems: "flex-start"
2332
+ }
2333
+ },
2334
+ /* @__PURE__ */ ReactEcs4.createElement(
2335
+ Label4,
2336
+ {
2337
+ value: notificationText,
2338
+ fontSize: N.fontSize,
2339
+ color: C.white,
2340
+ textAlign: "middle-left"
2341
+ }
2342
+ )
2343
+ ),
2344
+ /* @__PURE__ */ ReactEcs4.createElement(
2345
+ UiEntity4,
2346
+ {
2347
+ uiTransform: {
2348
+ width: 6,
2349
+ height: 6,
2350
+ margin: { right: 12 }
2351
+ },
2352
+ uiBackground: { color: C.cyan }
2353
+ }
2354
+ )
2355
+ );
2356
+ }
2357
+ function createStaticUI(client) {
2358
+ initNotificationSystem();
2359
+ return function StaticUI() {
2360
+ const currentScale = client.uiScale;
2361
+ const guideComponent = client.guideUI?.getComponent() ?? null;
2362
+ const chatComponent = client.chatUI?.getComponent() ?? null;
2363
+ const adminComponent = client.adminPanel?.getComponent() ?? null;
2364
+ return /* @__PURE__ */ ReactEcs4.createElement(
2365
+ UiEntity4,
2366
+ {
2367
+ key: `static-ui-root-${currentScale}`,
2368
+ uiTransform: {
2369
+ width: "100%",
2370
+ height: "100%",
2371
+ positionType: "absolute",
2372
+ flexDirection: "column",
2373
+ alignItems: "center"
2374
+ }
2375
+ },
2376
+ /* @__PURE__ */ ReactEcs4.createElement(NotificationBanner, null),
2377
+ guideComponent,
2378
+ chatComponent,
2379
+ adminComponent
2380
+ );
2381
+ };
2382
+ }
2383
+ function setupStaticUI(client) {
2384
+ const StaticUI = createStaticUI(client);
2385
+ ReactEcsRenderer.setUiRenderer(StaticUI);
2386
+ }
2387
+
2388
+ // src/ui/admin-panel-ui.tsx
2259
2389
  var BAN_KICK_POSITION = Vector3.create(16, -50, 16);
2260
- var C = {
2390
+ var C2 = {
2261
2391
  bg: Color45.create(0.08, 0.08, 0.12, 0.98),
2262
2392
  header: Color45.create(0.9, 0.15, 0.15, 1),
2263
2393
  tabActive: Color45.create(0, 0.7, 0.7, 1),
@@ -2298,6 +2428,8 @@ var AdminPanelUIModule = class {
2298
2428
  this.streamControlling = false;
2299
2429
  this.streamControlStatus = "";
2300
2430
  this.pollIntervalId = null;
2431
+ this.commandPollIntervalId = null;
2432
+ this.lastCommandTimestamp = 0;
2301
2433
  this.trialClaiming = false;
2302
2434
  this.trialClaimError = "";
2303
2435
  // Mod tab state
@@ -2309,22 +2441,22 @@ var AdminPanelUIModule = class {
2309
2441
  this.modStatus = "";
2310
2442
  this.modsFetched = false;
2311
2443
  // --- UI Components ---
2312
- this.SectionHead = ({ label, color }) => /* @__PURE__ */ ReactEcs4.createElement(
2313
- UiEntity4,
2444
+ this.SectionHead = ({ label, color }) => /* @__PURE__ */ ReactEcs5.createElement(
2445
+ UiEntity5,
2314
2446
  {
2315
2447
  uiTransform: { width: "100%", height: this.s(UI_DIMENSIONS.admin.sectionHeadHeight), margin: { bottom: 8 }, padding: { left: 10 }, alignItems: "center" },
2316
2448
  uiBackground: { color: Color45.create(color.r * 0.3, color.g * 0.3, color.b * 0.3, 0.5) }
2317
2449
  },
2318
- /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: label, fontSize: this.theme.sectionHead, color })
2450
+ /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: label, fontSize: this.theme.sectionHead, color })
2319
2451
  );
2320
- this.TabBtn = ({ label, tab }) => /* @__PURE__ */ ReactEcs4.createElement(
2452
+ this.TabBtn = ({ label, tab }) => /* @__PURE__ */ ReactEcs5.createElement(
2321
2453
  Button3,
2322
2454
  {
2323
2455
  uiTransform: { flexGrow: 1, height: this.s(UI_DIMENSIONS.admin.tabHeight), justifyContent: "center", alignItems: "center" },
2324
- uiBackground: { color: this.activeTab === tab ? C.tabActive : C.tabInactive },
2456
+ uiBackground: { color: this.activeTab === tab ? C2.tabActive : C2.tabInactive },
2325
2457
  value: label,
2326
2458
  fontSize: this.theme.tabButton,
2327
- color: this.activeTab === tab ? Color45.Black() : C.text,
2459
+ color: this.activeTab === tab ? Color45.Black() : C2.text,
2328
2460
  textAlign: "middle-center",
2329
2461
  onMouseDown: () => this.setActiveTab(tab)
2330
2462
  }
@@ -2334,365 +2466,368 @@ var AdminPanelUIModule = class {
2334
2466
  this.fetchStreamData();
2335
2467
  }
2336
2468
  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(
2469
+ 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
2470
  Button3,
2339
2471
  {
2340
2472
  uiTransform: { width: this.s(200), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2341
- uiBackground: { color: this.trialClaiming ? C.btn : C.green },
2473
+ uiBackground: { color: this.trialClaiming ? C2.btn : C2.green },
2342
2474
  value: this.trialClaiming ? "Claiming..." : "Start Free 4-Hour Trial",
2343
2475
  fontSize: t.button,
2344
- color: C.text,
2476
+ color: C2.text,
2345
2477
  onMouseDown: () => this.claimTrial()
2346
2478
  }
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(
2479
+ ), 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
2480
  Button3,
2349
2481
  {
2350
2482
  uiTransform: { width: this.s(170), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: { bottom: 6 } },
2351
- uiBackground: { color: this.channelCreating ? C.btn : C.cyan },
2483
+ uiBackground: { color: this.channelCreating ? C2.btn : C2.cyan },
2352
2484
  value: this.channelCreating ? "Creating..." : "+ Create Channel",
2353
2485
  fontSize: t.button,
2354
- color: C.text,
2486
+ color: C2.text,
2355
2487
  onMouseDown: () => this.createChannel()
2356
2488
  }
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,
2489
+ ), 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(
2490
+ Label5,
2359
2491
  {
2360
2492
  value: this.streamData.isLive ? `LIVE \u2022 ${this.streamData.currentViewers || 0} viewers` : "OFFLINE",
2361
2493
  fontSize: t.label,
2362
- color: this.streamData.isLive ? C.red : C.textDim
2494
+ color: this.streamData.isLive ? C2.red : C2.textDim
2363
2495
  }
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,
2496
+ ), /* @__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(
2497
+ Label5,
2366
2498
  {
2367
2499
  value: `Channel: ${this.streamData.channelName || this.streamData.channelId}`,
2368
2500
  fontSize: t.labelSmall,
2369
- color: C.textDim,
2501
+ color: C2.textDim,
2370
2502
  uiTransform: { margin: { bottom: 10 } }
2371
2503
  }
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,
2504
+ ), /* @__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(
2505
+ Label5,
2374
2506
  {
2375
2507
  value: this.streamData.rtmpUrl || "Loading...",
2376
2508
  fontSize: t.label,
2377
- color: this.streamData.rtmpUrl ? C.text : C.textDim,
2509
+ color: this.streamData.rtmpUrl ? C2.text : C2.textDim,
2378
2510
  uiTransform: { margin: { left: 6 } }
2379
2511
  }
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,
2512
+ )), /* @__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(
2513
+ Label5,
2382
2514
  {
2383
2515
  value: this.streamData.streamKey || "Loading...",
2384
2516
  fontSize: t.label,
2385
- color: this.streamData.streamKey ? C.cyan : C.textDim,
2517
+ color: this.streamData.streamKey ? C2.cyan : C2.textDim,
2386
2518
  uiTransform: { margin: { left: 6 } }
2387
2519
  }
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,
2520
+ )), /* @__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(
2521
+ Label5,
2390
2522
  {
2391
2523
  value: this.streamData.hlsUrl || "Not available",
2392
2524
  fontSize: t.label,
2393
- color: this.streamData.hlsUrl ? C.green : C.textDim,
2525
+ color: this.streamData.hlsUrl ? C2.green : C2.textDim,
2394
2526
  uiTransform: { margin: { left: 6 } }
2395
2527
  }
2396
- )), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
2528
+ )), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, !this.streamData.isLive && /* @__PURE__ */ ReactEcs5.createElement(
2397
2529
  Button3,
2398
2530
  {
2399
2531
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2400
- uiBackground: { color: this.streamControlling ? C.btn : C.green },
2532
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.green },
2401
2533
  value: this.streamControlling ? "Starting..." : "Start Stream",
2402
2534
  fontSize: t.buttonSmall,
2403
- color: C.text,
2535
+ color: C2.text,
2404
2536
  onMouseDown: () => this.startStream()
2405
2537
  }
2406
- ), this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
2538
+ ), this.streamData.isLive && /* @__PURE__ */ ReactEcs5.createElement(
2407
2539
  Button3,
2408
2540
  {
2409
2541
  uiTransform: { width: this.s(110), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2410
- uiBackground: { color: this.streamControlling ? C.btn : C.red },
2542
+ uiBackground: { color: this.streamControlling ? C2.btn : C2.red },
2411
2543
  value: this.streamControlling ? "Stopping..." : "Stop Stream",
2412
2544
  fontSize: t.buttonSmall,
2413
- color: C.text,
2545
+ color: C2.text,
2414
2546
  onMouseDown: () => this.stopStream()
2415
2547
  }
2416
- ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ ReactEcs4.createElement(
2548
+ ), this.streamData.isLive && this.streamData.hlsUrl && /* @__PURE__ */ ReactEcs5.createElement(
2417
2549
  Button3,
2418
2550
  {
2419
2551
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2420
- uiBackground: { color: C.cyan },
2552
+ uiBackground: { color: C2.cyan },
2421
2553
  value: "Play on Screen",
2422
2554
  fontSize: t.buttonSmall,
2423
- color: C.text,
2555
+ color: C2.text,
2424
2556
  onMouseDown: () => this.config.onVideoPlay?.(this.streamData.hlsUrl)
2425
2557
  }
2426
- )), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2558
+ )), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", flexWrap: "wrap", margin: { bottom: 6 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2427
2559
  Button3,
2428
2560
  {
2429
2561
  uiTransform: { width: this.s(80), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2430
- uiBackground: { color: C.btn },
2562
+ uiBackground: { color: C2.btn },
2431
2563
  value: "Refresh",
2432
2564
  fontSize: t.buttonSmall,
2433
- color: C.text,
2565
+ color: C2.text,
2434
2566
  onMouseDown: () => this.refreshStreamStatus()
2435
2567
  }
2436
- ), /* @__PURE__ */ ReactEcs4.createElement(
2568
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2437
2569
  Button3,
2438
2570
  {
2439
2571
  uiTransform: { width: this.s(95), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2440
- uiBackground: { color: this.keyRotating ? C.btn : C.yellow },
2572
+ uiBackground: { color: this.keyRotating ? C2.btn : C2.yellow },
2441
2573
  value: this.keyRotating ? "..." : "Rotate Key",
2442
2574
  fontSize: t.buttonSmall,
2443
- color: C.text,
2575
+ color: C2.text,
2444
2576
  onMouseDown: () => this.rotateStreamKey()
2445
2577
  }
2446
- ), !this.streamData.isLive && /* @__PURE__ */ ReactEcs4.createElement(
2578
+ ), !this.streamData.isLive && /* @__PURE__ */ ReactEcs5.createElement(
2447
2579
  Button3,
2448
2580
  {
2449
2581
  uiTransform: { width: this.s(70), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2450
- uiBackground: { color: this.channelDeleting ? C.btn : C.red },
2582
+ uiBackground: { color: this.channelDeleting ? C2.btn : C2.red },
2451
2583
  value: this.channelDeleting ? "..." : "Delete",
2452
2584
  fontSize: t.buttonSmall,
2453
- color: C.text,
2585
+ color: C2.text,
2454
2586
  onMouseDown: () => this.deleteChannel()
2455
2587
  }
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(
2588
+ )), 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
2589
  Input4,
2458
2590
  {
2459
2591
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2460
2592
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2461
2593
  placeholder: "Video URL...",
2462
- placeholderColor: C.textDim,
2463
- color: C.text,
2594
+ placeholderColor: C2.textDim,
2595
+ color: C2.text,
2464
2596
  fontSize: t.input,
2465
2597
  value: this.customVideoUrl,
2466
2598
  onChange: (val) => {
2467
2599
  this.customVideoUrl = val;
2468
2600
  }
2469
2601
  }
2470
- ), /* @__PURE__ */ ReactEcs4.createElement(
2602
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2471
2603
  Button3,
2472
2604
  {
2473
2605
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2474
- uiBackground: { color: C.green },
2606
+ uiBackground: { color: C2.green },
2475
2607
  value: "Play",
2476
2608
  fontSize: t.button,
2477
- color: C.text,
2609
+ color: C2.text,
2478
2610
  onMouseDown: () => {
2479
2611
  if (this.customVideoUrl) this.config.onVideoPlay?.(this.customVideoUrl);
2480
2612
  }
2481
2613
  }
2482
- ), /* @__PURE__ */ ReactEcs4.createElement(
2614
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2483
2615
  Button3,
2484
2616
  {
2485
2617
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 6 } },
2486
- uiBackground: { color: C.btn },
2618
+ uiBackground: { color: C2.btn },
2487
2619
  value: "Clear",
2488
2620
  fontSize: t.button,
2489
- color: C.text,
2621
+ color: C2.text,
2490
2622
  onMouseDown: () => {
2491
2623
  this.customVideoUrl = "";
2492
2624
  }
2493
2625
  }
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(
2626
+ )), /* @__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
2627
  Button3,
2496
2628
  {
2497
2629
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2498
- uiBackground: { color: C.green },
2630
+ uiBackground: { color: C2.green },
2499
2631
  value: "Play",
2500
2632
  fontSize: t.button,
2501
- color: C.text,
2633
+ color: C2.text,
2502
2634
  onMouseDown: () => this.config.onCommand?.("videoPlay", { playing: true })
2503
2635
  }
2504
- ), /* @__PURE__ */ ReactEcs4.createElement(
2636
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2505
2637
  Button3,
2506
2638
  {
2507
2639
  uiTransform: { width: this.s(75), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2508
- uiBackground: { color: C.red },
2640
+ uiBackground: { color: C2.red },
2509
2641
  value: "Stop",
2510
2642
  fontSize: t.button,
2511
- color: C.text,
2643
+ color: C2.text,
2512
2644
  onMouseDown: () => this.config.onVideoStop?.()
2513
2645
  }
2514
- ), /* @__PURE__ */ ReactEcs4.createElement(
2646
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2515
2647
  Button3,
2516
2648
  {
2517
2649
  uiTransform: { width: this.s(100), height: this.s(UI_DIMENSIONS.admin.buttonHeightSmall), margin: 4 },
2518
- uiBackground: { color: C.btn },
2650
+ uiBackground: { color: C2.btn },
2519
2651
  value: "Reset Default",
2520
2652
  fontSize: t.buttonSmall,
2521
- color: C.text,
2653
+ color: C2.text,
2522
2654
  onMouseDown: () => this.config.onCommand?.("videoClear", {})
2523
2655
  }
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(
2656
+ )), /* @__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
2657
  Button3,
2526
2658
  {
2527
2659
  uiTransform: { height: this.s(24) },
2528
2660
  uiBackground: { color: Color45.create(0, 0, 0, 0) },
2529
2661
  value: "Edit slots at thestatic.tv",
2530
2662
  fontSize: t.labelSmall,
2531
- color: C.cyan,
2663
+ color: C2.cyan,
2532
2664
  onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2533
2665
  }
2534
2666
  ));
2535
2667
  };
2536
2668
  this.ModTab = () => {
2537
2669
  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(
2670
+ 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
2671
  Input4,
2540
2672
  {
2541
2673
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2542
2674
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2543
2675
  placeholder: "Message to all players...",
2544
- placeholderColor: C.textDim,
2545
- color: C.text,
2676
+ placeholderColor: C2.textDim,
2677
+ color: C2.text,
2546
2678
  fontSize: t.input,
2547
2679
  value: this.broadcastText,
2548
2680
  onChange: (val) => {
2549
2681
  this.broadcastText = val;
2550
2682
  }
2551
2683
  }
2552
- ), /* @__PURE__ */ ReactEcs4.createElement(
2684
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2553
2685
  Button3,
2554
2686
  {
2555
2687
  uiTransform: { width: this.s(65), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2556
- uiBackground: { color: C.orange },
2688
+ uiBackground: { color: C2.orange },
2557
2689
  value: "Send",
2558
2690
  fontSize: t.button,
2559
- color: C.text,
2691
+ color: C2.text,
2560
2692
  onMouseDown: () => this.sendBroadcast()
2561
2693
  }
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(
2694
+ )), /* @__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
2695
  Button3,
2564
2696
  {
2565
2697
  uiTransform: { width: this.s(130), height: this.s(UI_DIMENSIONS.admin.buttonHeight), margin: 4 },
2566
- uiBackground: { color: C.red },
2698
+ uiBackground: { color: C2.red },
2567
2699
  value: "KICK ALL",
2568
2700
  fontSize: t.button,
2569
- color: C.text,
2701
+ color: C2.text,
2570
2702
  onMouseDown: () => {
2571
2703
  this.log("KICK ALL clicked");
2572
2704
  this.config.onCommand?.("kickAll", {});
2573
2705
  }
2574
2706
  }
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,
2707
+ )), /* @__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(
2708
+ UiEntity5,
2577
2709
  {
2578
2710
  key: `admin-${i}`,
2579
2711
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2580
2712
  },
2581
- /* @__PURE__ */ ReactEcs4.createElement(
2582
- Label4,
2713
+ /* @__PURE__ */ ReactEcs5.createElement(
2714
+ Label5,
2583
2715
  {
2584
2716
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2585
2717
  fontSize: t.label,
2586
- color: C.text,
2718
+ color: C2.text,
2587
2719
  uiTransform: { width: this.s(130) }
2588
2720
  }
2589
2721
  ),
2590
- /* @__PURE__ */ ReactEcs4.createElement(
2722
+ /* @__PURE__ */ ReactEcs5.createElement(
2591
2723
  Button3,
2592
2724
  {
2593
2725
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2594
- uiBackground: { color: C.btn },
2726
+ uiBackground: { color: C2.btn },
2595
2727
  value: "Remove",
2596
2728
  fontSize: t.buttonSmall,
2597
- color: C.text,
2729
+ color: C2.text,
2598
2730
  onMouseDown: () => this.removeSceneAdmin(wallet)
2599
2731
  }
2600
2732
  )
2601
- ))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2733
+ ))), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 14 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2602
2734
  Input4,
2603
2735
  {
2604
2736
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2605
2737
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2606
2738
  placeholder: "0x... add admin",
2607
- placeholderColor: C.textDim,
2608
- color: C.text,
2739
+ placeholderColor: C2.textDim,
2740
+ color: C2.text,
2609
2741
  fontSize: t.input,
2610
2742
  value: this.newAdminWallet,
2611
2743
  onChange: (val) => {
2612
2744
  this.newAdminWallet = val;
2613
2745
  }
2614
2746
  }
2615
- ), /* @__PURE__ */ ReactEcs4.createElement(
2747
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2616
2748
  Button3,
2617
2749
  {
2618
2750
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2619
- uiBackground: { color: C.purple },
2751
+ uiBackground: { color: C2.purple },
2620
2752
  value: "Add",
2621
2753
  fontSize: t.button,
2622
- color: C.text,
2754
+ color: C2.text,
2623
2755
  onMouseDown: () => {
2624
2756
  if (this.newAdminWallet) this.addSceneAdmin(this.newAdminWallet);
2625
2757
  }
2626
2758
  }
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,
2759
+ )), /* @__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(
2760
+ UiEntity5,
2629
2761
  {
2630
2762
  key: `ban-${i}`,
2631
2763
  uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 5 }, width: "100%" }
2632
2764
  },
2633
- /* @__PURE__ */ ReactEcs4.createElement(
2634
- Label4,
2765
+ /* @__PURE__ */ ReactEcs5.createElement(
2766
+ Label5,
2635
2767
  {
2636
2768
  value: `${wallet.slice(0, 6)}...${wallet.slice(-4)}`,
2637
2769
  fontSize: t.label,
2638
- color: C.red,
2770
+ color: C2.red,
2639
2771
  uiTransform: { width: this.s(130) }
2640
2772
  }
2641
2773
  ),
2642
- /* @__PURE__ */ ReactEcs4.createElement(
2774
+ /* @__PURE__ */ ReactEcs5.createElement(
2643
2775
  Button3,
2644
2776
  {
2645
2777
  uiTransform: { width: this.s(70), height: this.s(28), margin: { left: 8 } },
2646
- uiBackground: { color: C.green },
2778
+ uiBackground: { color: C2.green },
2647
2779
  value: "Unban",
2648
2780
  fontSize: t.buttonSmall,
2649
- color: C.text,
2781
+ color: C2.text,
2650
2782
  onMouseDown: () => this.unbanWallet(wallet)
2651
2783
  }
2652
2784
  )
2653
- ))), /* @__PURE__ */ ReactEcs4.createElement(UiEntity4, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs4.createElement(
2785
+ ))), /* @__PURE__ */ ReactEcs5.createElement(UiEntity5, { uiTransform: { flexDirection: "row", alignItems: "center", margin: { bottom: 10 } } }, /* @__PURE__ */ ReactEcs5.createElement(
2654
2786
  Input4,
2655
2787
  {
2656
2788
  uiTransform: { width: this.s(230), height: this.s(UI_DIMENSIONS.admin.inputHeight) },
2657
2789
  uiBackground: { color: Color45.create(0.15, 0.15, 0.2, 1) },
2658
2790
  placeholder: "0x... ban wallet",
2659
- placeholderColor: C.textDim,
2660
- color: C.text,
2791
+ placeholderColor: C2.textDim,
2792
+ color: C2.text,
2661
2793
  fontSize: t.input,
2662
2794
  value: this.newBanWallet,
2663
2795
  onChange: (val) => {
2664
2796
  this.newBanWallet = val;
2665
2797
  }
2666
2798
  }
2667
- ), /* @__PURE__ */ ReactEcs4.createElement(
2799
+ ), /* @__PURE__ */ ReactEcs5.createElement(
2668
2800
  Button3,
2669
2801
  {
2670
2802
  uiTransform: { width: this.s(60), height: this.s(UI_DIMENSIONS.admin.inputHeight), margin: { left: 8 } },
2671
- uiBackground: { color: C.red },
2803
+ uiBackground: { color: C2.red },
2672
2804
  value: "Ban",
2673
2805
  fontSize: t.button,
2674
- color: C.text,
2806
+ color: C2.text,
2675
2807
  onMouseDown: () => {
2676
2808
  if (this.newBanWallet) this.banWallet(this.newBanWallet);
2677
2809
  }
2678
2810
  }
2679
- )), /* @__PURE__ */ ReactEcs4.createElement(
2811
+ )), /* @__PURE__ */ ReactEcs5.createElement(
2680
2812
  Button3,
2681
2813
  {
2682
2814
  uiTransform: { height: this.s(24) },
2683
2815
  uiBackground: { color: Color45.create(0, 0, 0, 0) },
2684
2816
  value: "Manage at thestatic.tv",
2685
2817
  fontSize: t.labelSmall,
2686
- color: C.cyan,
2818
+ color: C2.cyan,
2687
2819
  onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2688
2820
  }
2689
2821
  ));
2690
2822
  };
2691
2823
  /**
2692
2824
  * Get the React-ECS component for the admin panel
2825
+ * Always renders NotificationBanner (for broadcasts), admin panel only for admins
2693
2826
  */
2694
2827
  this.getComponent = () => {
2695
- if (!this.isAdmin) return null;
2828
+ if (!this.isAdmin) {
2829
+ return /* @__PURE__ */ ReactEcs5.createElement(NotificationBanner, null);
2830
+ }
2696
2831
  const t = this.theme;
2697
2832
  const tabs = [];
2698
2833
  if (this.config.sceneTabs && this.config.sceneTabs.length > 0) {
@@ -2707,8 +2842,8 @@ var AdminPanelUIModule = class {
2707
2842
  if (!tabs.find((tab) => tab.id === this.activeTab) && tabs.length > 0) {
2708
2843
  this.activeTab = tabs[0].id;
2709
2844
  }
2710
- return /* @__PURE__ */ ReactEcs4.createElement(
2711
- UiEntity4,
2845
+ return /* @__PURE__ */ ReactEcs5.createElement(
2846
+ UiEntity5,
2712
2847
  {
2713
2848
  uiTransform: {
2714
2849
  width: "100%",
@@ -2716,28 +2851,29 @@ var AdminPanelUIModule = class {
2716
2851
  positionType: "absolute"
2717
2852
  }
2718
2853
  },
2719
- /* @__PURE__ */ ReactEcs4.createElement(
2720
- UiEntity4,
2854
+ /* @__PURE__ */ ReactEcs5.createElement(NotificationBanner, null),
2855
+ /* @__PURE__ */ ReactEcs5.createElement(
2856
+ UiEntity5,
2721
2857
  {
2722
2858
  uiTransform: {
2723
2859
  position: { right: 20 + this.s(100) + 10 + this.s(100) + 10, bottom: 10 },
2724
2860
  positionType: "absolute"
2725
2861
  }
2726
2862
  },
2727
- /* @__PURE__ */ ReactEcs4.createElement(
2863
+ /* @__PURE__ */ ReactEcs5.createElement(
2728
2864
  Button3,
2729
2865
  {
2730
2866
  uiTransform: { width: this.s(100), height: this.s(45) },
2731
- uiBackground: { color: this.panelOpen ? C.btn : C.header },
2867
+ uiBackground: { color: this.panelOpen ? C2.btn : C2.header },
2732
2868
  value: this.panelOpen ? "CLOSE" : "ADMIN",
2733
2869
  fontSize: this.s(14),
2734
- color: C.text,
2870
+ color: C2.text,
2735
2871
  onMouseDown: () => this.toggle()
2736
2872
  }
2737
2873
  )
2738
2874
  ),
2739
- this.panelOpen && /* @__PURE__ */ ReactEcs4.createElement(
2740
- UiEntity4,
2875
+ this.panelOpen && /* @__PURE__ */ ReactEcs5.createElement(
2876
+ UiEntity5,
2741
2877
  {
2742
2878
  key: `admin-panel-${this.client.uiScale}`,
2743
2879
  uiTransform: {
@@ -2748,10 +2884,10 @@ var AdminPanelUIModule = class {
2748
2884
  positionType: "absolute",
2749
2885
  flexDirection: "column"
2750
2886
  },
2751
- uiBackground: { color: C.bg }
2887
+ uiBackground: { color: C2.bg }
2752
2888
  },
2753
- /* @__PURE__ */ ReactEcs4.createElement(
2754
- UiEntity4,
2889
+ /* @__PURE__ */ ReactEcs5.createElement(
2890
+ UiEntity5,
2755
2891
  {
2756
2892
  uiTransform: {
2757
2893
  width: "100%",
@@ -2761,10 +2897,10 @@ var AdminPanelUIModule = class {
2761
2897
  flexDirection: "row",
2762
2898
  padding: { left: 12, right: 8 }
2763
2899
  },
2764
- uiBackground: { color: C.header }
2900
+ uiBackground: { color: C2.header }
2765
2901
  },
2766
- /* @__PURE__ */ ReactEcs4.createElement(Label4, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C.text }),
2767
- /* @__PURE__ */ ReactEcs4.createElement(
2902
+ /* @__PURE__ */ ReactEcs5.createElement(Label5, { value: this.config.title || "ADMIN PANEL", fontSize: t.header, color: C2.text }),
2903
+ /* @__PURE__ */ ReactEcs5.createElement(
2768
2904
  Button3,
2769
2905
  {
2770
2906
  uiTransform: { width: this.s(28), height: this.s(28) },
@@ -2776,9 +2912,9 @@ var AdminPanelUIModule = class {
2776
2912
  }
2777
2913
  )
2778
2914
  ),
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,
2915
+ /* @__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 }))),
2916
+ /* @__PURE__ */ ReactEcs5.createElement(
2917
+ UiEntity5,
2782
2918
  {
2783
2919
  uiTransform: {
2784
2920
  width: "100%",
@@ -2787,12 +2923,12 @@ var AdminPanelUIModule = class {
2787
2923
  flexDirection: "column"
2788
2924
  }
2789
2925
  },
2790
- this.activeTab === "video" && /* @__PURE__ */ ReactEcs4.createElement(this.VideoTab, null),
2791
- this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ ReactEcs4.createElement(this.ModTab, null),
2926
+ this.activeTab === "video" && /* @__PURE__ */ ReactEcs5.createElement(this.VideoTab, null),
2927
+ this.activeTab === "mod" && this.isOwner && /* @__PURE__ */ ReactEcs5.createElement(this.ModTab, null),
2792
2928
  this.config.sceneTabs?.map((tab) => this.activeTab === tab.id && tab.render())
2793
2929
  ),
2794
- /* @__PURE__ */ ReactEcs4.createElement(
2795
- UiEntity4,
2930
+ /* @__PURE__ */ ReactEcs5.createElement(
2931
+ UiEntity5,
2796
2932
  {
2797
2933
  uiTransform: {
2798
2934
  width: "100%",
@@ -2800,16 +2936,16 @@ var AdminPanelUIModule = class {
2800
2936
  justifyContent: "center",
2801
2937
  alignItems: "center"
2802
2938
  },
2803
- uiBackground: { color: C.section }
2939
+ uiBackground: { color: C2.section }
2804
2940
  },
2805
- /* @__PURE__ */ ReactEcs4.createElement(
2941
+ /* @__PURE__ */ ReactEcs5.createElement(
2806
2942
  Button3,
2807
2943
  {
2808
2944
  uiTransform: { height: this.s(24) },
2809
2945
  uiBackground: { color: Color45.create(0, 0, 0, 0) },
2810
2946
  value: `thestatic.tv/scene/${this.config.sceneId}`,
2811
2947
  fontSize: t.labelSmall,
2812
- color: C.cyan,
2948
+ color: C2.cyan,
2813
2949
  onMouseDown: () => openExternalUrl3({ url: this.config.footerLink || `https://thestatic.tv/scene/${this.config.sceneId}` })
2814
2950
  }
2815
2951
  )
@@ -2832,7 +2968,7 @@ var AdminPanelUIModule = class {
2832
2968
  };
2833
2969
  this.baseUrl = client.getBaseUrl();
2834
2970
  if (config.headerColor) {
2835
- C.header = Color45.create(
2971
+ C2.header = Color45.create(
2836
2972
  config.headerColor.r,
2837
2973
  config.headerColor.g,
2838
2974
  config.headerColor.b,
@@ -2860,6 +2996,7 @@ var AdminPanelUIModule = class {
2860
2996
  await this.checkAdminStatus();
2861
2997
  await this.fetchVideoState();
2862
2998
  this.autoPlayDefault();
2999
+ this.startCommandPolling();
2863
3000
  this.log("Initialized");
2864
3001
  }
2865
3002
  /**
@@ -2965,6 +3102,61 @@ var AdminPanelUIModule = class {
2965
3102
  this.log("Stream polling stopped");
2966
3103
  }
2967
3104
  }
3105
+ // --- Command Polling (receives broadcasts from website) ---
3106
+ startCommandPolling() {
3107
+ if (this.commandPollIntervalId !== null) return;
3108
+ this.commandPollIntervalId = dclSetInterval(() => {
3109
+ this.pollCommands();
3110
+ }, 5e3);
3111
+ this.pollCommands();
3112
+ this.log("Command polling started");
3113
+ }
3114
+ stopCommandPolling() {
3115
+ if (this.commandPollIntervalId !== null) {
3116
+ dclClearInterval(this.commandPollIntervalId);
3117
+ this.commandPollIntervalId = null;
3118
+ this.log("Command polling stopped");
3119
+ }
3120
+ }
3121
+ async pollCommands() {
3122
+ try {
3123
+ const res = await fetch(`${this.baseUrl}/scene/${this.config.sceneId}/commands`);
3124
+ if (!res.ok) return;
3125
+ const data = await res.json();
3126
+ const commands = data.commands || [];
3127
+ for (const cmd of commands) {
3128
+ if (cmd.timestamp <= this.lastCommandTimestamp) continue;
3129
+ this.lastCommandTimestamp = cmd.timestamp;
3130
+ if (cmd.type === "broadcast" && cmd.payload?.text) {
3131
+ const msg = cmd.payload.from ? `${cmd.payload.from}: ${cmd.payload.text}` : cmd.payload.text;
3132
+ this.log("Received broadcast:", msg);
3133
+ this.config.onBroadcast?.(msg);
3134
+ } else if (cmd.type === "clearBroadcast") {
3135
+ this.log("Received clearBroadcast");
3136
+ hideNotification();
3137
+ } else if ((cmd.type === "videoPlay" || cmd.type === "videoSetUrl") && cmd.payload?.url) {
3138
+ this.log("Received video play:", cmd.type, cmd.payload.url);
3139
+ this.config.onVideoPlay?.(cmd.payload.url);
3140
+ } else if (cmd.type === "videoStop" || cmd.type === "videoClear") {
3141
+ this.log("Received video stop/clear:", cmd.type);
3142
+ this.config.onVideoStop?.();
3143
+ } else if (cmd.type === "videoPlaySlot" && cmd.payload?.slot) {
3144
+ const slotId = cmd.payload.slot;
3145
+ if (cmd.payload.url) {
3146
+ this.log("Received videoPlaySlot with URL:", slotId, cmd.payload.url);
3147
+ this.config.onVideoPlay?.(cmd.payload.url);
3148
+ } else {
3149
+ this.log("Received videoPlaySlot:", slotId);
3150
+ this.playSlot(slotId);
3151
+ }
3152
+ } else if (this.config.onCommand) {
3153
+ this.log("Received command:", cmd.type, cmd.payload);
3154
+ this.config.onCommand(cmd.type, cmd.payload);
3155
+ }
3156
+ }
3157
+ } catch (err) {
3158
+ }
3159
+ }
2968
3160
  // --- Stream API Calls ---
2969
3161
  async fetchStreamData() {
2970
3162
  if (this.streamFetched || !this.playerWallet) return;
@@ -3396,87 +3588,6 @@ var AdminPanelUIModule = class {
3396
3588
  }
3397
3589
  };
3398
3590
 
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
3591
  // src/StaticTVClient.ts
3481
3592
  import { VideoPlayer, videoEventsSystem, VideoState } from "@dcl/sdk/ecs";
3482
3593
  import * as utils from "@dcl-sdk/utils";
@@ -3757,10 +3868,13 @@ var StaticTVClient = class {
3757
3868
  */
3758
3869
  stopVideo() {
3759
3870
  const screen = this.config.videoScreen;
3871
+ this._pendingVideoData = null;
3872
+ this._streamVerified = false;
3873
+ this._clearVerificationTimeout();
3874
+ if (this.guideUI) {
3875
+ this.guideUI.currentVideoId = null;
3876
+ }
3760
3877
  if (screen !== void 0) {
3761
- if (this.guideUI) {
3762
- this.guideUI.currentVideoId = null;
3763
- }
3764
3878
  const fallbackUrl = this.config.fallbackVideoUrl;
3765
3879
  const fallbackDisabled = fallbackUrl === "";
3766
3880
  if (fallbackDisabled) {
@@ -3916,20 +4030,60 @@ var StaticTVClient = class {
3916
4030
  }
3917
4031
  /**
3918
4032
  * Play video with stream verification for live content
4033
+ * Works with both videoScreen (SDK-managed) and onVideoPlay (user-managed) modes.
3919
4034
  * @internal
3920
4035
  */
3921
4036
  _playVideoWithVerification(video, isLiveStream) {
3922
4037
  const screen = this.config.videoScreen;
4038
+ const hasCallback = this.config.onVideoPlay !== void 0;
4039
+ this.log(`Playing video: ${video.src} (live: ${isLiveStream}, screen: ${screen !== void 0}, callback: ${hasCallback})`);
4040
+ this._currentVideoUrl = video.src;
4041
+ this._clearVerificationTimeout();
4042
+ this._streamVerified = false;
3923
4043
  if (screen !== void 0) {
3924
- this.log(`Playing video: ${video.src} (live: ${isLiveStream})`);
3925
- this._currentVideoUrl = video.src;
3926
4044
  this._playVideoInternal(video.src, false, isLiveStream);
3927
- if (this.guideUI) {
3928
- this.guideUI.currentVideoId = video.id;
3929
- }
3930
4045
  }
3931
- if (this.config.onVideoPlay) {
4046
+ if (hasCallback) {
3932
4047
  this.config.onVideoPlay(video.src);
4048
+ if (isLiveStream) {
4049
+ this._startCallbackVerification(video);
4050
+ }
4051
+ }
4052
+ if (this.guideUI) {
4053
+ this.guideUI.currentVideoId = video.id;
4054
+ }
4055
+ }
4056
+ /**
4057
+ * Start stream verification for callback-based video playback
4058
+ * Shows CONNECTING state and triggers fallback if verification times out
4059
+ * @internal
4060
+ */
4061
+ _startCallbackVerification(video) {
4062
+ this.showNotification(`Connecting to ${video.name}...`, 6e3);
4063
+ this._verificationTimeoutId = utils.timers.setTimeout(() => {
4064
+ if (!this._streamVerified && this._pendingVideoData) {
4065
+ this.log(`Stream verification timeout (callback mode): ${video.name}`);
4066
+ this._handleStreamOffline();
4067
+ }
4068
+ }, 5e3);
4069
+ }
4070
+ /**
4071
+ * Call this to confirm that a video stream is playing successfully.
4072
+ * Use this when you're managing video playback yourself (onVideoPlay callback).
4073
+ *
4074
+ * @example
4075
+ * ```typescript
4076
+ * // In your video player's onReady or similar event:
4077
+ * videoPlayer.events.on('playing', () => {
4078
+ * staticTV.confirmVideoPlaying()
4079
+ * })
4080
+ * ```
4081
+ */
4082
+ confirmVideoPlaying() {
4083
+ if (this._pendingVideoData && !this._streamVerified) {
4084
+ this._streamVerified = true;
4085
+ this._clearVerificationTimeout();
4086
+ this.log(`Stream confirmed playing: ${this._pendingVideoData.name}`);
3933
4087
  }
3934
4088
  }
3935
4089
  /**
@@ -4203,11 +4357,13 @@ export {
4203
4357
  InteractionsModule,
4204
4358
  KEY_TYPE_CHANNEL,
4205
4359
  KEY_TYPE_SCENE,
4360
+ NotificationBanner,
4206
4361
  SessionModule,
4207
4362
  StaticTVClient,
4208
4363
  fetchUserData,
4209
4364
  getPlayerDisplayName,
4210
4365
  getPlayerWallet,
4366
+ hideNotification,
4211
4367
  setupStaticUI,
4212
4368
  showNotification
4213
4369
  };