@playruo/embed 1.3.0 → 1.3.2

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.js CHANGED
@@ -34,15 +34,18 @@ var PlayruoEmbed = ({
34
34
  onReady,
35
35
  onStateChange,
36
36
  onDemoCompleted,
37
+ onEvent,
37
38
  onPurchaseRequest,
38
39
  onError,
40
+ forwardEventsToParent = true,
39
41
  renderState,
40
42
  iframeRef: externalIframeRef
41
43
  }) => {
42
44
  const internalIframeRef = useRef(null);
43
45
  const iframeRef = externalIframeRef || internalIframeRef;
44
46
  const wasEmbedFullscreenRef = useRef(false);
45
- const wasEmbedPointerLockedRef = useRef(false);
47
+ const hasNotifiedCompletionRef = useRef(false);
48
+ const suppressNextFullscreenExitCleanupRef = useRef(false);
46
49
  const [isIframeLoaded, setIsIframeLoaded] = useState(false);
47
50
  const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
48
51
  const [showGameIframe, setShowGameIframe] = useState(false);
@@ -112,6 +115,149 @@ var PlayruoEmbed = ({
112
115
  console.warn("[PlayruoEmbed] Failed to release Escape key lock:", error);
113
116
  }
114
117
  }, [getKeyboardApi]);
118
+ const getFullscreenElement = useCallback(() => {
119
+ const doc = document;
120
+ return doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
121
+ }, []);
122
+ const notifyDemoCompletedOnce = useCallback(
123
+ (payload) => {
124
+ if (hasNotifiedCompletionRef.current) {
125
+ return;
126
+ }
127
+ hasNotifiedCompletionRef.current = true;
128
+ onDemoCompleted?.(
129
+ payload ?? {
130
+ reason: "Demo session ended",
131
+ storeUrl
132
+ }
133
+ );
134
+ },
135
+ [onDemoCompleted, storeUrl]
136
+ );
137
+ const postExitFullscreenToIframe = useCallback(() => {
138
+ const iframe = iframeRef.current;
139
+ if (!iframe) {
140
+ return;
141
+ }
142
+ try {
143
+ iframe.contentWindow?.postMessage({
144
+ type: "playruo",
145
+ action: "exitFullscreen",
146
+ fromEmbed: true,
147
+ isFullscreen: false,
148
+ parentWillHandleFullscreen: true
149
+ }, "*");
150
+ } catch {
151
+ console.error("[PlayruoEmbed] Failed to exit fullscreen");
152
+ }
153
+ }, [iframeRef]);
154
+ const postExitFullscreenAckToIframe = useCallback(() => {
155
+ const iframe = iframeRef.current;
156
+ if (!iframe) {
157
+ return;
158
+ }
159
+ try {
160
+ iframe.contentWindow?.postMessage({ type: "ACK_EXIT_FULLSCREEN" }, "*");
161
+ } catch {
162
+ console.error("[PlayruoEmbed] Failed to acknowledge fullscreen exit");
163
+ }
164
+ }, [iframeRef]);
165
+ const exitEmbedFullscreen = useCallback(
166
+ async ({
167
+ reason = "cleanup",
168
+ suppressFullscreenExitCleanup = true
169
+ } = {}) => {
170
+ const fullscreenElement = getFullscreenElement();
171
+ const doc = document;
172
+ const exitFullscreen = doc.exitFullscreen?.bind(doc) ?? doc.webkitExitFullscreen?.bind(doc) ?? doc.mozCancelFullScreen?.bind(doc) ?? doc.msExitFullscreen?.bind(doc);
173
+ const shouldAttemptExit = Boolean(fullscreenElement || wasEmbedFullscreenRef.current);
174
+ if (!shouldAttemptExit || !exitFullscreen) {
175
+ console.log("[PlayruoEmbed] Parent fullscreen exit skipped", {
176
+ hasFullscreenElement: Boolean(fullscreenElement),
177
+ hasExitFullscreen: Boolean(exitFullscreen),
178
+ reason,
179
+ wasEmbedFullscreen: wasEmbedFullscreenRef.current
180
+ });
181
+ return false;
182
+ }
183
+ console.log("[PlayruoEmbed] Parent fullscreen exit requested", {
184
+ fullscreenElement: fullscreenElement?.tagName,
185
+ reason,
186
+ wasEmbedFullscreen: wasEmbedFullscreenRef.current
187
+ });
188
+ const shouldSuppressFullscreenExitCleanup = Boolean(
189
+ suppressFullscreenExitCleanup && fullscreenElement
190
+ );
191
+ if (shouldSuppressFullscreenExitCleanup) {
192
+ suppressNextFullscreenExitCleanupRef.current = true;
193
+ wasEmbedFullscreenRef.current = false;
194
+ }
195
+ try {
196
+ await exitFullscreen();
197
+ if (shouldSuppressFullscreenExitCleanup) {
198
+ window.setTimeout(() => {
199
+ suppressNextFullscreenExitCleanupRef.current = false;
200
+ }, 1e3);
201
+ }
202
+ return true;
203
+ } catch (error) {
204
+ if (shouldSuppressFullscreenExitCleanup) {
205
+ suppressNextFullscreenExitCleanupRef.current = false;
206
+ wasEmbedFullscreenRef.current = Boolean(getFullscreenElement());
207
+ }
208
+ console.warn("[PlayruoEmbed] Failed to leave fullscreen:", error);
209
+ return false;
210
+ }
211
+ },
212
+ [getFullscreenElement]
213
+ );
214
+ const cleanupEmbedSession = useCallback(
215
+ ({
216
+ payload,
217
+ notifyCompletion = true,
218
+ requestFullscreenExit = true,
219
+ notifyIframe = false,
220
+ markPlayed = true
221
+ } = {}) => {
222
+ setShowGameIframe(false);
223
+ if (markPlayed) {
224
+ setHasPlayedOnce(true);
225
+ }
226
+ releaseEscapeKeyLock();
227
+ if (notifyIframe) {
228
+ postExitFullscreenToIframe();
229
+ }
230
+ if (requestFullscreenExit) {
231
+ void exitEmbedFullscreen();
232
+ }
233
+ if (notifyCompletion) {
234
+ notifyDemoCompletedOnce(payload);
235
+ }
236
+ },
237
+ [
238
+ exitEmbedFullscreen,
239
+ notifyDemoCompletedOnce,
240
+ postExitFullscreenToIframe,
241
+ releaseEscapeKeyLock
242
+ ]
243
+ );
244
+ const markStreamActive = useCallback(() => {
245
+ hasNotifiedCompletionRef.current = false;
246
+ setShowGameIframe(true);
247
+ }, []);
248
+ const forwardEventToParent = useCallback(
249
+ (message) => {
250
+ if (!forwardEventsToParent || typeof window === "undefined" || window.parent === window) {
251
+ return;
252
+ }
253
+ try {
254
+ window.parent.postMessage(message, "*");
255
+ } catch (error) {
256
+ console.warn("[PlayruoEmbed] Failed to forward event to parent:", error);
257
+ }
258
+ },
259
+ [forwardEventsToParent]
260
+ );
115
261
  const lockEscapeKey = useCallback(async () => {
116
262
  const keyboard = getKeyboardApi();
117
263
  if (!keyboard?.lock) {
@@ -124,55 +270,8 @@ var PlayruoEmbed = ({
124
270
  console.warn("[PlayruoEmbed] Failed to lock Escape key:", error);
125
271
  }
126
272
  }, [getKeyboardApi]);
127
- const getPointerLockElement = useCallback(() => {
128
- const doc = document;
129
- return doc.pointerLockElement || doc.mozPointerLockElement || doc.webkitPointerLockElement || null;
130
- }, []);
131
- const releasePointerLock = useCallback(() => {
132
- const doc = document;
133
- const exitPointerLock = doc.exitPointerLock?.bind(doc) || doc.mozExitPointerLock?.bind(doc) || doc.webkitExitPointerLock?.bind(doc);
134
- try {
135
- exitPointerLock?.();
136
- wasEmbedPointerLockedRef.current = false;
137
- } catch (error) {
138
- console.warn("[PlayruoEmbed] Failed to release pointer lock:", error);
139
- }
140
- }, []);
141
273
  const requestImmersiveMode = useCallback(
142
274
  async (iframe) => {
143
- const pointerTarget = iframe;
144
- if (pointerTarget.requestPointerLock) {
145
- try {
146
- const pointerLockResult = pointerTarget.requestPointerLock({ unadjustedMovement: true });
147
- if (pointerLockResult && typeof pointerLockResult.then === "function") {
148
- void pointerLockResult.then(() => {
149
- wasEmbedPointerLockedRef.current = true;
150
- console.log("[PlayruoEmbed] Pointer locked on iframe");
151
- }).catch(async (error) => {
152
- if (error instanceof DOMException && error.name === "NotSupportedError") {
153
- try {
154
- const fallbackResult = pointerTarget.requestPointerLock?.();
155
- if (fallbackResult && typeof fallbackResult.then === "function") {
156
- await fallbackResult;
157
- }
158
- wasEmbedPointerLockedRef.current = true;
159
- console.log("[PlayruoEmbed] Pointer locked on iframe without raw input");
160
- return;
161
- } catch (fallbackError) {
162
- console.warn("[PlayruoEmbed] Failed to lock pointer without raw input:", fallbackError);
163
- return;
164
- }
165
- }
166
- console.warn("[PlayruoEmbed] Failed to lock pointer:", error);
167
- });
168
- } else {
169
- wasEmbedPointerLockedRef.current = true;
170
- console.log("[PlayruoEmbed] Pointer lock requested on iframe");
171
- }
172
- } catch (error) {
173
- console.warn("[PlayruoEmbed] Failed to request pointer lock:", error);
174
- }
175
- }
176
275
  const fullscreenPromise = iframe.requestFullscreen();
177
276
  try {
178
277
  await fullscreenPromise;
@@ -180,11 +279,10 @@ var PlayruoEmbed = ({
180
279
  console.log("[PlayruoEmbed] Fullscreen granted");
181
280
  } catch (error) {
182
281
  releaseEscapeKeyLock();
183
- releasePointerLock();
184
282
  throw error;
185
283
  }
186
284
  },
187
- [releaseEscapeKeyLock, releasePointerLock]
285
+ [releaseEscapeKeyLock]
188
286
  );
189
287
  const handleTryNow = useCallback(async () => {
190
288
  if (!isCompatible) {
@@ -196,6 +294,7 @@ var PlayruoEmbed = ({
196
294
  console.warn("[PlayruoEmbed] Iframe not ready");
197
295
  return;
198
296
  }
297
+ hasNotifiedCompletionRef.current = false;
199
298
  setShowGameIframe(true);
200
299
  try {
201
300
  try {
@@ -219,6 +318,7 @@ var PlayruoEmbed = ({
219
318
  setHasPlayedOnce(true);
220
319
  } catch (error) {
221
320
  console.error("[PlayruoEmbed] Failed to start game:", error);
321
+ setShowGameIframe(false);
222
322
  setErrorMessage("Failed to start game");
223
323
  onError?.("Failed to start game");
224
324
  }
@@ -242,7 +342,8 @@ var PlayruoEmbed = ({
242
342
  }, [isReady, onReady]);
243
343
  useEffect(() => {
244
344
  const handleGameMessage = (event) => {
245
- const { type, data } = event.data || {};
345
+ const message = event.data || {};
346
+ const { type, data } = message;
246
347
  const iframe = iframeRef.current;
247
348
  if (!iframe || event.source !== iframe.contentWindow) {
248
349
  return;
@@ -263,8 +364,7 @@ var PlayruoEmbed = ({
263
364
  onPurchaseRequest?.(data);
264
365
  } else if (type === "playruo_demo_completed" && data) {
265
366
  console.log("\u{1F537} [Embed] playruo_demo_completed received:", data);
266
- setHasPlayedOnce(true);
267
- onDemoCompleted?.(data);
367
+ cleanupEmbedSession({ payload: data });
268
368
  } else if (type === "playruo_error") {
269
369
  const errorMessage2 = typeof data === "string" ? data : data?.message || "Unknown error";
270
370
  console.log("\u{1F537} [Embed] playruo_error received:", errorMessage2);
@@ -285,25 +385,65 @@ var PlayruoEmbed = ({
285
385
  onError?.(errorMessage2);
286
386
  } else if (type === "playruo_session_over" && data) {
287
387
  console.log("\u{1F537} [Embed] playruo_session_over received:", data);
288
- setHasPlayedOnce(true);
388
+ void exitEmbedFullscreen({ reason: "playruo_session_over" });
289
389
  setIframeState(data);
290
390
  onStateChange?.(data);
391
+ cleanupEmbedSession({ requestFullscreenExit: false });
291
392
  } else if (type === "playruo_stream_start") {
292
393
  console.log("\u{1F537} [Embed] playruo_stream_start received");
394
+ markStreamActive();
395
+ } else if (type === "playruo_stream_stop") {
396
+ console.log("\u{1F537} [Embed] playruo_stream_stop received");
397
+ void exitEmbedFullscreen({ reason: "playruo_stream_stop" });
398
+ cleanupEmbedSession({
399
+ markPlayed: false,
400
+ notifyCompletion: false,
401
+ requestFullscreenExit: false
402
+ });
403
+ } else if (type === "REQUEST_EXIT_FULLSCREEN") {
404
+ console.log("\u{1F537} [Embed] REQUEST_EXIT_FULLSCREEN received");
405
+ void exitEmbedFullscreen({
406
+ reason: "REQUEST_EXIT_FULLSCREEN",
407
+ suppressFullscreenExitCleanup: false
408
+ }).finally(postExitFullscreenAckToIframe);
409
+ } else if (type === "playruo_event" && data && typeof data === "object") {
410
+ console.log("\u{1F537} [Embed] playruo_event received:", data);
411
+ const eventPayload = data;
412
+ onEvent?.(eventPayload);
413
+ forwardEventToParent({ type: "playruo_event", data: eventPayload });
414
+ } else if (type === "playruo" && message.action === "event" && message.payload && typeof message.payload === "object") {
415
+ console.log("\u{1F537} [Embed] legacy playruo event received:", message.payload);
416
+ const eventPayload = message.payload;
417
+ onEvent?.(eventPayload);
418
+ forwardEventToParent({ type: "playruo_event", data: eventPayload });
293
419
  }
294
420
  if (type === "error" || type === "closeForBuy" || type === "goToStore") {
295
421
  console.log("\u{1F537} [Embed] Legacy message received:", type);
296
- setHasPlayedOnce(true);
297
- const payload = {
298
- reason: "Demo session ended",
299
- storeUrl
300
- };
301
- onDemoCompleted?.(payload);
422
+ cleanupEmbedSession({
423
+ payload: {
424
+ reason: "Demo session ended",
425
+ storeUrl
426
+ }
427
+ });
302
428
  }
303
429
  };
304
430
  window.addEventListener("message", handleGameMessage);
305
431
  return () => window.removeEventListener("message", handleGameMessage);
306
- }, [storeUrl, onDemoCompleted, onReady, onStateChange, onPurchaseRequest, onError, iframeRef]);
432
+ }, [
433
+ cleanupEmbedSession,
434
+ exitEmbedFullscreen,
435
+ forwardEventToParent,
436
+ hasPlayedOnce,
437
+ iframeRef,
438
+ markStreamActive,
439
+ onEvent,
440
+ onError,
441
+ onPurchaseRequest,
442
+ onReady,
443
+ onStateChange,
444
+ postExitFullscreenAckToIframe,
445
+ storeUrl
446
+ ]);
307
447
  const generateUUID = () => {
308
448
  if (self.crypto.randomUUID) return self.crypto.randomUUID();
309
449
  const bytes = new Uint8Array(16);
@@ -350,9 +490,8 @@ var PlayruoEmbed = ({
350
490
  }, [iframeRef]);
351
491
  useEffect(() => {
352
492
  const handleFullscreenChange = () => {
353
- const doc = document;
354
493
  const iframe = iframeRef.current;
355
- const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement || null;
494
+ const fullscreenElement = getFullscreenElement();
356
495
  const isEmbedFullscreen = Boolean(iframe && fullscreenElement === iframe);
357
496
  if (isEmbedFullscreen) {
358
497
  wasEmbedFullscreenRef.current = true;
@@ -369,30 +508,15 @@ var PlayruoEmbed = ({
369
508
  return;
370
509
  }
371
510
  releaseEscapeKeyLock();
511
+ if (suppressNextFullscreenExitCleanupRef.current) {
512
+ suppressNextFullscreenExitCleanupRef.current = false;
513
+ return;
514
+ }
372
515
  if (wasEmbedFullscreenRef.current) {
373
516
  wasEmbedFullscreenRef.current = false;
374
- const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
375
- if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
376
- releasePointerLock();
377
- }
378
- if (iframe) {
379
- try {
380
- iframe.contentWindow?.postMessage({
381
- type: "playruo",
382
- action: "exitFullscreen",
383
- fromEmbed: true,
384
- isFullscreen: false,
385
- parentWillHandleFullscreen: true
386
- }, "*");
387
- } catch {
388
- console.error("[PlayruoEmbed] Failed to exit fullscreen");
389
- }
390
- }
391
- setShowGameIframe(false);
392
- setHasPlayedOnce(true);
393
- onDemoCompleted?.({
394
- reason: "Demo session ended",
395
- storeUrl
517
+ cleanupEmbedSession({
518
+ notifyIframe: true,
519
+ requestFullscreenExit: false
396
520
  });
397
521
  }
398
522
  };
@@ -406,33 +530,18 @@ var PlayruoEmbed = ({
406
530
  document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
407
531
  document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
408
532
  };
409
- }, [storeUrl, onDemoCompleted, iframeRef, getPointerLockElement, lockEscapeKey, releaseEscapeKeyLock, releasePointerLock]);
410
- useEffect(() => {
411
- const handlePointerLockChange = () => {
412
- const iframe = iframeRef.current;
413
- wasEmbedPointerLockedRef.current = Boolean(iframe && getPointerLockElement() === iframe);
414
- };
415
- const handlePointerLockError = () => {
416
- wasEmbedPointerLockedRef.current = false;
417
- console.warn("[PlayruoEmbed] Pointer lock request was rejected");
418
- };
419
- document.addEventListener("pointerlockchange", handlePointerLockChange);
420
- document.addEventListener("pointerlockerror", handlePointerLockError);
421
- return () => {
422
- document.removeEventListener("pointerlockchange", handlePointerLockChange);
423
- document.removeEventListener("pointerlockerror", handlePointerLockError);
424
- };
425
- }, [getPointerLockElement, iframeRef]);
533
+ }, [
534
+ cleanupEmbedSession,
535
+ getFullscreenElement,
536
+ iframeRef,
537
+ lockEscapeKey,
538
+ releaseEscapeKeyLock
539
+ ]);
426
540
  useEffect(() => {
427
541
  return () => {
428
542
  releaseEscapeKeyLock();
429
- const iframe = iframeRef.current;
430
- const isEmbedPointerLocked = Boolean(iframe && getPointerLockElement() === iframe);
431
- if (wasEmbedPointerLockedRef.current || isEmbedPointerLocked) {
432
- releasePointerLock();
433
- }
434
543
  };
435
- }, [getPointerLockElement, iframeRef, releaseEscapeKeyLock, releasePointerLock]);
544
+ }, [releaseEscapeKeyLock]);
436
545
  useEffect(() => {
437
546
  const logCrossOriginStatus = () => {
438
547
  const isIsolated = self.crossOriginIsolated;
@@ -512,6 +621,8 @@ var PlayruoEmbed = ({
512
621
  }
513
622
  }, [isReady]);
514
623
  const isAvailable = Boolean(isReady && isCompatible && hasIframeConfirmed);
624
+ const shouldDisplayGameIframe = showGameIframe;
625
+ const shouldShowIframeMisuseMessage = Boolean(showIframe && !showGameIframe);
515
626
  const renderContent = () => {
516
627
  if (!renderState) return null;
517
628
  const canTryNow = Boolean(
@@ -541,6 +652,26 @@ var PlayruoEmbed = ({
541
652
  };
542
653
  return /* @__PURE__ */ jsxs("div", { className, children: [
543
654
  renderContent(),
655
+ isCompatible && shouldShowIframeMisuseMessage && /* @__PURE__ */ jsx(
656
+ "div",
657
+ {
658
+ role: "status",
659
+ style: {
660
+ alignItems: "center",
661
+ background: "#000",
662
+ color: "#fff",
663
+ display: "flex",
664
+ fontFamily: "system-ui, sans-serif",
665
+ fontSize: 14,
666
+ height: "100%",
667
+ justifyContent: "center",
668
+ padding: 24,
669
+ textAlign: "center",
670
+ width: "100%"
671
+ },
672
+ children: "This iframe is only meant to be displayed through PlayruoEmbed during an active stream."
673
+ }
674
+ ),
544
675
  isCompatible && /* @__PURE__ */ jsx(
545
676
  "iframe",
546
677
  {
@@ -553,9 +684,9 @@ var PlayruoEmbed = ({
553
684
  title: "Playruo",
554
685
  referrerPolicy: "strict-origin-when-cross-origin",
555
686
  style: {
556
- // Only mounted when compatible; visible after click (internal) or if parent explicitly asks
557
- opacity: showGameIframe || showIframe ? 1 : 0,
558
- pointerEvents: showGameIframe || showIframe ? "auto" : "none",
687
+ // The iframe stays mounted for readiness checks, visible only during launch/play, and hidden after terminal events.
688
+ opacity: shouldDisplayGameIframe ? 1 : 0,
689
+ pointerEvents: shouldDisplayGameIframe ? "auto" : "none",
559
690
  width: "100%",
560
691
  height: "100%",
561
692
  border: 0
@@ -582,36 +713,61 @@ var PlayruoEmbed = ({
582
713
  // src/react/embed-tester.tsx
583
714
  import React, { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
584
715
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
716
+ var createInitialEmbedState = () => ({
717
+ buyNow: { available: false, status: "Not ready" },
718
+ hasPlayedOnce: false,
719
+ isReady: false,
720
+ tryNow: { available: false, status: "Not ready" }
721
+ });
722
+ var createInitialDiagnostics = () => ({
723
+ chromiumBrowser: false,
724
+ crossOriginIsolation: false,
725
+ desktopBrowser: false,
726
+ fullscreenSupported: false,
727
+ gamepadSupported: false,
728
+ iframeAllowsFullscreen: false,
729
+ iframeAllowsGamepad: false,
730
+ iframeAllowsPointerLock: false,
731
+ keyboardLockSupported: false,
732
+ localStorageSupported: false,
733
+ pointerLockSupported: false,
734
+ secureContext: false,
735
+ sharedArrayBufferSupported: false,
736
+ topLevelContext: false,
737
+ webAssemblySupported: false,
738
+ webgl2Supported: false
739
+ });
585
740
  var EmbedTester = (props = {}) => {
586
- const [operation, setOperation] = useState2(props.operation ?? "iframe");
587
- const [storeUrl, setStoreUrl] = useState2(
741
+ const [operationDraft, setOperationDraft] = useState2(props.operation ?? "iframe");
742
+ const [storeUrlDraft, setStoreUrlDraft] = useState2(
743
+ props.storeUrl ?? "https://store.steampowered.com/app/123456"
744
+ );
745
+ const [appliedOperation, setAppliedOperation] = useState2(props.operation ?? "iframe");
746
+ const [appliedStoreUrl, setAppliedStoreUrl] = useState2(
588
747
  props.storeUrl ?? "https://store.steampowered.com/app/123456"
589
748
  );
590
749
  const [key, setKey] = useState2(0);
591
750
  const [hasUserClicked, setHasUserClicked] = useState2(false);
592
751
  const [showIframe, setShowIframe] = useState2(false);
593
- const [embedState, setEmbedState] = useState2({
594
- buyNow: { available: false, status: "Not ready" },
595
- hasPlayedOnce: false,
596
- isReady: false,
597
- tryNow: { available: false, status: "Not ready" }
598
- });
752
+ const [embedState, setEmbedState] = useState2(createInitialEmbedState);
599
753
  const [eventLog, setEventLog] = useState2([]);
600
- const [diagnostics, setDiagnostics] = useState2({
601
- crossOriginIsolation: false,
602
- gamepadSupported: false,
603
- fullscreenSupported: false,
604
- keyboardLockSupported: false
605
- });
754
+ const [diagnostics, setDiagnostics] = useState2(createInitialDiagnostics);
606
755
  const displayHost = props.baseUrl ? new URL(props.baseUrl).host : "playruo.live";
607
756
  const enableConsoleLog = props.enableConsoleLog ?? false;
608
757
  const hasReadyBeenCalled = useRef2(false);
758
+ const nextEventIdRef = useRef2(0);
609
759
  const previousStateRef = useRef2("");
610
760
  const iframeRef = useRef2(null);
761
+ const isOperationPending = operationDraft !== appliedOperation;
762
+ const isStoreUrlPending = storeUrlDraft !== appliedStoreUrl;
763
+ const hasPendingConfigChanges = isOperationPending || isStoreUrlPending;
764
+ const pendingChangeCount = Number(isOperationPending) + Number(isStoreUrlPending);
611
765
  const addEvent = useCallback2(
612
766
  (message) => {
613
767
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
614
- setEventLog((prev) => [...prev.slice(-10), { message, timestamp }]);
768
+ const id = nextEventIdRef.current;
769
+ nextEventIdRef.current += 1;
770
+ setEventLog((prev) => [...prev.slice(-10), { id, message, timestamp }]);
615
771
  if (enableConsoleLog) {
616
772
  console.log(`[EmbedTester] ${timestamp}: ${message}`);
617
773
  }
@@ -619,18 +775,48 @@ var EmbedTester = (props = {}) => {
619
775
  [enableConsoleLog]
620
776
  );
621
777
  const runDiagnostics = useCallback2(() => {
778
+ const userAgent = navigator.userAgent || navigator.vendor || window.opera || "";
779
+ const isMobileDevice = /android|iPad|iPhone|iPod/i.test(userAgent);
780
+ const hasChrome = !!window.chrome;
781
+ const isChromium = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
782
+ const iframeAllow = iframeRef.current?.getAttribute("allow") ?? "";
783
+ const iframeSandbox = iframeRef.current?.getAttribute("sandbox") ?? "";
784
+ const canvas = document.createElement("canvas");
785
+ let localStorageSupported = false;
786
+ try {
787
+ const storageKey = "__playruo_embed_tester__";
788
+ window.localStorage.setItem(storageKey, "1");
789
+ window.localStorage.removeItem(storageKey);
790
+ localStorageSupported = true;
791
+ } catch {
792
+ localStorageSupported = false;
793
+ }
622
794
  const newDiagnostics = {
795
+ chromiumBrowser: hasChrome || isChromium,
623
796
  crossOriginIsolation: !!(window.crossOriginIsolated || window.crossOriginIsolated),
624
- gamepadSupported: "getGamepads" in navigator,
797
+ desktopBrowser: !isMobileDevice,
625
798
  fullscreenSupported: !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled),
626
- keyboardLockSupported: "keyboard" in navigator && "lock" in navigator.keyboard
799
+ gamepadSupported: "getGamepads" in navigator,
800
+ iframeAllowsFullscreen: iframeAllow.includes("fullscreen"),
801
+ iframeAllowsGamepad: iframeAllow.includes("gamepad"),
802
+ iframeAllowsPointerLock: iframeSandbox.includes("allow-pointer-lock"),
803
+ keyboardLockSupported: "keyboard" in navigator && "lock" in navigator.keyboard,
804
+ localStorageSupported,
805
+ pointerLockSupported: "requestPointerLock" in HTMLIFrameElement.prototype || "requestPointerLock" in HTMLElement.prototype || "pointerLockElement" in document,
806
+ secureContext: window.isSecureContext,
807
+ sharedArrayBufferSupported: typeof SharedArrayBuffer !== "undefined",
808
+ topLevelContext: window.self === window.top,
809
+ webAssemblySupported: typeof WebAssembly !== "undefined",
810
+ webgl2Supported: !!canvas.getContext("webgl2")
627
811
  };
628
812
  setDiagnostics(newDiagnostics);
629
- addEvent(`\u{1F50D} Diagnostics updated - COI: ${newDiagnostics.crossOriginIsolation}, Gamepad: ${newDiagnostics.gamepadSupported}`);
630
- }, [addEvent]);
813
+ addEvent(
814
+ `\u{1F50D} Diagnostics updated - Browser: ${newDiagnostics.desktopBrowser && newDiagnostics.chromiumBrowser}, COI: ${newDiagnostics.crossOriginIsolation}, Fullscreen: ${newDiagnostics.fullscreenSupported}`
815
+ );
816
+ }, [addEvent, iframeRef]);
631
817
  React.useEffect(() => {
632
818
  runDiagnostics();
633
- }, [runDiagnostics]);
819
+ }, [key, runDiagnostics]);
634
820
  const handleReady = useCallback2(() => {
635
821
  if (!hasReadyBeenCalled.current) {
636
822
  hasReadyBeenCalled.current = true;
@@ -659,284 +845,1081 @@ var EmbedTester = (props = {}) => {
659
845
  console.error("Embed error:", error);
660
846
  }
661
847
  }, [addEvent, enableConsoleLog]);
848
+ const handleEvent = useCallback2((data) => {
849
+ const eventName = typeof data.event === "string" ? data.event : typeof data.name === "string" ? data.name : "unknown";
850
+ addEvent(`Event received: ${eventName}`);
851
+ if (enableConsoleLog) {
852
+ console.log("Embed event:", data);
853
+ }
854
+ }, [addEvent, enableConsoleLog]);
662
855
  const handlePurchaseRequest = useCallback2((data) => {
663
856
  addEvent(`\u{1F4B3} Explicit purchase request from Buy Now button`);
664
857
  addEvent(`Opening store: ${data.storeUrl}`);
665
858
  window.open(data.storeUrl, "_blank");
666
859
  }, [addEvent]);
667
- const handleRestart = useCallback2(() => {
860
+ const resetEmbedSession = useCallback2((message) => {
861
+ setEmbedState(createInitialEmbedState());
668
862
  setKey((prev) => prev + 1);
669
863
  setHasUserClicked(false);
670
864
  setShowIframe(false);
671
865
  hasReadyBeenCalled.current = false;
672
866
  previousStateRef.current = "";
673
- addEvent("Embed restarted");
867
+ addEvent(message);
674
868
  }, [addEvent]);
675
- const buttonStyle = {
676
- padding: "8px 16px",
677
- margin: "4px",
678
- border: "1px solid #404040",
679
- borderRadius: "4px",
680
- backgroundColor: "#2a2a2a",
681
- color: "#e5e5e5",
682
- cursor: "pointer",
683
- fontSize: "14px",
684
- transition: "all 0.2s ease"
869
+ const handleApply = useCallback2(() => {
870
+ if (!hasPendingConfigChanges) {
871
+ return;
872
+ }
873
+ setAppliedOperation(operationDraft);
874
+ setAppliedStoreUrl(storeUrlDraft);
875
+ resetEmbedSession("Applied updated embed settings");
876
+ }, [hasPendingConfigChanges, operationDraft, resetEmbedSession, storeUrlDraft]);
877
+ const handleRestartSession = useCallback2(() => {
878
+ resetEmbedSession("Embed restarted with applied settings");
879
+ }, [resetEmbedSession]);
880
+ const handleResetDrafts = useCallback2(() => {
881
+ setOperationDraft(appliedOperation);
882
+ setStoreUrlDraft(appliedStoreUrl);
883
+ addEvent("Reverted draft changes to the applied configuration");
884
+ }, [addEvent, appliedOperation, appliedStoreUrl]);
885
+ const panelStyle = {
886
+ position: "relative",
887
+ border: "1px solid rgba(148, 163, 184, 0.18)",
888
+ borderRadius: "24px",
889
+ padding: "20px",
890
+ background: "linear-gradient(180deg, rgba(15, 23, 42, 0.88) 0%, rgba(7, 12, 24, 0.96) 100%)",
891
+ boxShadow: "0 24px 80px rgba(0, 0, 0, 0.35)",
892
+ backdropFilter: "blur(18px)"
893
+ };
894
+ const sectionTitleStyle = {
895
+ margin: 0,
896
+ fontSize: "18px",
897
+ fontWeight: 600,
898
+ color: "#f8fafc"
899
+ };
900
+ const labelStyle = {
901
+ display: "flex",
902
+ alignItems: "center",
903
+ justifyContent: "space-between",
904
+ gap: "12px",
905
+ marginBottom: "8px",
906
+ fontSize: "13px",
907
+ fontWeight: 600,
908
+ color: "#dbe7ff"
685
909
  };
910
+ const buildIndicatorStyle = (pending) => ({
911
+ display: "inline-flex",
912
+ alignItems: "center",
913
+ gap: "8px",
914
+ padding: "4px 10px",
915
+ borderRadius: "999px",
916
+ border: pending ? "1px solid rgba(251, 191, 36, 0.28)" : "1px solid rgba(74, 222, 128, 0.22)",
917
+ backgroundColor: pending ? "rgba(245, 158, 11, 0.12)" : "rgba(34, 197, 94, 0.12)",
918
+ color: pending ? "#fbbf24" : "#86efac",
919
+ fontSize: "11px",
920
+ fontWeight: 700,
921
+ letterSpacing: "0.08em",
922
+ textTransform: "uppercase"
923
+ });
924
+ const buildIndicatorDotStyle = (pending) => ({
925
+ width: "8px",
926
+ height: "8px",
927
+ borderRadius: "999px",
928
+ backgroundColor: pending ? "#f59e0b" : "#22c55e",
929
+ boxShadow: pending ? "0 0 0 4px rgba(245, 158, 11, 0.12)" : "0 0 0 4px rgba(34, 197, 94, 0.12)"
930
+ });
931
+ const buildInputShellStyle = (pending) => ({
932
+ display: "flex",
933
+ alignItems: "center",
934
+ gap: "12px",
935
+ border: pending ? "1px solid rgba(245, 158, 11, 0.35)" : "1px solid rgba(148, 163, 184, 0.18)",
936
+ borderRadius: "16px",
937
+ padding: "0 14px",
938
+ background: pending ? "rgba(35, 23, 8, 0.72)" : "rgba(15, 23, 42, 0.7)",
939
+ boxShadow: pending ? "0 0 0 1px rgba(245, 158, 11, 0.08)" : "none"
940
+ });
686
941
  const inputStyle = {
687
942
  width: "100%",
688
- padding: "8px",
689
- margin: "4px 0",
690
- border: "1px solid #404040",
691
- borderRadius: "4px",
692
- backgroundColor: "#1a1a1a",
693
- color: "#e5e5e5",
694
- fontSize: "14px"
943
+ padding: "14px 0",
944
+ border: "none",
945
+ outline: "none",
946
+ backgroundColor: "transparent",
947
+ color: "#f8fafc",
948
+ fontSize: "15px"
695
949
  };
696
- const cardStyle = {
697
- border: "1px solid #333333",
698
- borderRadius: "8px",
699
- padding: "16px",
700
- margin: "8px 0",
701
- backgroundColor: "#1a1a1a"
950
+ const helperTextStyle = {
951
+ margin: "8px 0 0 0",
952
+ color: "#94a3b8",
953
+ fontSize: "12px",
954
+ lineHeight: 1.5
702
955
  };
703
- const statusStyle = {
956
+ const codeStyle = {
704
957
  display: "inline-block",
705
- padding: "4px 8px",
706
- margin: "2px",
707
- borderRadius: "12px",
958
+ padding: "2px 8px",
959
+ borderRadius: "999px",
960
+ backgroundColor: "rgba(148, 163, 184, 0.12)",
961
+ color: "#e2e8f0",
708
962
  fontSize: "12px",
709
- fontWeight: "500"
963
+ fontFamily: '"SF Mono", "Roboto Mono", monospace',
964
+ wordBreak: "break-all"
710
965
  };
711
- const successStatus = { ...statusStyle, backgroundColor: "#0d5d1f", color: "#4ade80" };
712
- const errorStatus = { ...statusStyle, backgroundColor: "#5d1a1a", color: "#f87171" };
713
- return /* @__PURE__ */ jsx2("div", { className: props.className, style: { minHeight: "100vh", padding: "20px", backgroundColor: "#000000", color: "#e5e5e5" }, children: /* @__PURE__ */ jsxs2("div", { style: { maxWidth: "1200px", margin: "0 auto" }, children: [
714
- /* @__PURE__ */ jsx2("h1", { style: { marginBottom: "20px", fontSize: "24px", fontWeight: "bold", color: "#ffffff" }, children: "\u{1F9EA} Playruo Embed Tester" }),
715
- /* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "20px" }, children: [
716
- /* @__PURE__ */ jsxs2("div", { children: [
717
- /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
718
- /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Test Controls" }),
719
- /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "12px" }, children: [
720
- /* @__PURE__ */ jsx2("label", { htmlFor: "operation", style: { display: "block", marginBottom: "4px", fontWeight: "500", color: "#d4d4d4" }, children: "Operation" }),
721
- /* @__PURE__ */ jsx2(
722
- "input",
723
- {
724
- style: inputStyle,
725
- onChange: (e) => setOperation(e.target.value),
726
- placeholder: "e.g., iframe",
727
- value: operation
728
- }
729
- )
730
- ] }),
731
- /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "12px" }, children: [
732
- /* @__PURE__ */ jsx2("label", { htmlFor: "storeUrl", style: { display: "block", marginBottom: "4px", fontWeight: "500", color: "#d4d4d4" }, children: "Store URL" }),
733
- /* @__PURE__ */ jsx2(
734
- "input",
735
- {
736
- style: inputStyle,
737
- onChange: (e) => setStoreUrl(e.target.value),
738
- placeholder: "https://store.example.com",
739
- value: storeUrl
740
- }
741
- )
742
- ] }),
966
+ const buildButtonStyle = (variant, disabled = false) => {
967
+ const palette = {
968
+ ghost: {
969
+ background: "rgba(15, 23, 42, 0.5)",
970
+ border: "1px solid rgba(148, 163, 184, 0.18)",
971
+ color: "#dbe7ff"
972
+ },
973
+ primary: {
974
+ background: "linear-gradient(135deg, #38bdf8 0%, #2563eb 100%)",
975
+ border: "1px solid rgba(125, 211, 252, 0.32)",
976
+ color: "#eff6ff"
977
+ },
978
+ secondary: {
979
+ background: "linear-gradient(135deg, rgba(30, 41, 59, 0.92) 0%, rgba(15, 23, 42, 0.98) 100%)",
980
+ border: "1px solid rgba(148, 163, 184, 0.18)",
981
+ color: "#e2e8f0"
982
+ }
983
+ }[variant];
984
+ return {
985
+ display: "inline-flex",
986
+ alignItems: "center",
987
+ justifyContent: "center",
988
+ gap: "10px",
989
+ minHeight: "44px",
990
+ padding: "0 18px",
991
+ borderRadius: "14px",
992
+ background: palette.background,
993
+ border: palette.border,
994
+ color: palette.color,
995
+ cursor: disabled ? "not-allowed" : "pointer",
996
+ opacity: disabled ? 0.45 : 1,
997
+ fontSize: "14px",
998
+ fontWeight: 700,
999
+ letterSpacing: "0.01em",
1000
+ transition: "all 0.2s ease",
1001
+ boxShadow: variant === "primary" && !disabled ? "0 16px 32px rgba(37, 99, 235, 0.28)" : "none"
1002
+ };
1003
+ };
1004
+ const buildStatusChipStyle = (active) => ({
1005
+ display: "inline-flex",
1006
+ alignItems: "center",
1007
+ gap: "8px",
1008
+ padding: "8px 12px",
1009
+ borderRadius: "999px",
1010
+ backgroundColor: active ? "rgba(34, 197, 94, 0.12)" : "rgba(248, 113, 113, 0.12)",
1011
+ border: active ? "1px solid rgba(34, 197, 94, 0.22)" : "1px solid rgba(248, 113, 113, 0.22)",
1012
+ color: active ? "#86efac" : "#fca5a5",
1013
+ fontSize: "12px",
1014
+ fontWeight: 700
1015
+ });
1016
+ const statusItems = [
1017
+ { label: "Ready", value: Boolean(embedState.isReady) },
1018
+ { label: "Played", value: Boolean(embedState.hasPlayedOnce) },
1019
+ { label: "Try Now", value: Boolean(embedState.tryNow?.available) },
1020
+ { label: "Buy Now", value: Boolean(embedState.buyNow?.available) }
1021
+ ];
1022
+ const diagnosticGroups = [
1023
+ {
1024
+ title: "Compatibility",
1025
+ items: [
1026
+ {
1027
+ detail: "Playruo currently expects a non-mobile browser.",
1028
+ label: "Desktop browser",
1029
+ value: diagnostics.desktopBrowser
1030
+ },
1031
+ {
1032
+ detail: "The embed flow is tuned for Chromium-based browsers.",
1033
+ label: "Chromium browser",
1034
+ value: diagnostics.chromiumBrowser
1035
+ },
1036
+ {
1037
+ detail: "Keyboard lock works best when the page is not itself embedded.",
1038
+ label: "Top-level tab",
1039
+ value: diagnostics.topLevelContext
1040
+ },
1041
+ {
1042
+ detail: "HTTPS or localhost unlocks the best browser APIs.",
1043
+ label: "Secure context",
1044
+ value: diagnostics.secureContext
1045
+ }
1046
+ ]
1047
+ },
1048
+ {
1049
+ title: "Gameplay APIs",
1050
+ items: [
1051
+ {
1052
+ detail: "Needed for the immersive play flow.",
1053
+ label: "Fullscreen API",
1054
+ value: diagnostics.fullscreenSupported
1055
+ },
1056
+ {
1057
+ detail: "Needed for raw mouse capture on the iframe.",
1058
+ label: "Pointer lock",
1059
+ value: diagnostics.pointerLockSupported
1060
+ },
1061
+ {
1062
+ detail: "Used to keep Escape reserved while fullscreen is active.",
1063
+ label: "Keyboard lock",
1064
+ value: diagnostics.keyboardLockSupported
1065
+ },
1066
+ {
1067
+ detail: "Lets the game read connected controllers.",
1068
+ label: "Gamepad API",
1069
+ value: diagnostics.gamepadSupported
1070
+ }
1071
+ ]
1072
+ },
1073
+ {
1074
+ title: "Performance",
1075
+ items: [
1076
+ {
1077
+ detail: "Improves the overall runtime budget for the experience.",
1078
+ label: "Cross-origin isolated",
1079
+ value: diagnostics.crossOriginIsolation
1080
+ },
1081
+ {
1082
+ detail: "Important for heavier threaded workloads.",
1083
+ label: "SharedArrayBuffer",
1084
+ value: diagnostics.sharedArrayBufferSupported
1085
+ },
1086
+ {
1087
+ detail: "Confirms modern rendering features are reachable.",
1088
+ label: "WebGL2",
1089
+ value: diagnostics.webgl2Supported
1090
+ },
1091
+ {
1092
+ detail: "Confirms the runtime can execute compiled game code paths.",
1093
+ label: "WebAssembly",
1094
+ value: diagnostics.webAssemblySupported
1095
+ }
1096
+ ]
1097
+ },
1098
+ {
1099
+ title: "Embed wiring",
1100
+ items: [
1101
+ {
1102
+ detail: "The iframe allow list includes fullscreen access.",
1103
+ label: "Iframe fullscreen allow",
1104
+ value: diagnostics.iframeAllowsFullscreen
1105
+ },
1106
+ {
1107
+ detail: "The iframe allow list includes gamepad access.",
1108
+ label: "Iframe gamepad allow",
1109
+ value: diagnostics.iframeAllowsGamepad
1110
+ },
1111
+ {
1112
+ detail: "The iframe sandbox includes pointer-lock permission.",
1113
+ label: "Iframe pointer-lock allow",
1114
+ value: diagnostics.iframeAllowsPointerLock
1115
+ },
1116
+ {
1117
+ detail: "Needed for UUID persistence and session continuity.",
1118
+ label: "Local storage",
1119
+ value: diagnostics.localStorageSupported
1120
+ }
1121
+ ]
1122
+ }
1123
+ ];
1124
+ const criticalDiagnosticChecks = [
1125
+ { label: "Desktop browser", value: diagnostics.desktopBrowser },
1126
+ { label: "Chromium browser", value: diagnostics.chromiumBrowser },
1127
+ { label: "Fullscreen API", value: diagnostics.fullscreenSupported },
1128
+ { label: "Pointer lock", value: diagnostics.pointerLockSupported }
1129
+ ];
1130
+ const warningDiagnosticChecks = [
1131
+ { label: "Top-level tab", value: diagnostics.topLevelContext },
1132
+ { label: "Secure context", value: diagnostics.secureContext },
1133
+ { label: "Cross-origin isolation", value: diagnostics.crossOriginIsolation },
1134
+ { label: "SharedArrayBuffer", value: diagnostics.sharedArrayBufferSupported },
1135
+ { label: "Keyboard lock", value: diagnostics.keyboardLockSupported },
1136
+ { label: "Gamepad API", value: diagnostics.gamepadSupported },
1137
+ { label: "WebGL2", value: diagnostics.webgl2Supported }
1138
+ ];
1139
+ const blockingDiagnostics = criticalDiagnosticChecks.filter((item) => !item.value);
1140
+ const advisoryDiagnostics = warningDiagnosticChecks.filter((item) => !item.value);
1141
+ const diagnosticsVerdict = blockingDiagnostics.length > 0 ? {
1142
+ label: "Blocked",
1143
+ tone: "#fca5a5",
1144
+ background: "rgba(127, 29, 29, 0.28)",
1145
+ border: "1px solid rgba(248, 113, 113, 0.24)",
1146
+ text: "A core requirement is missing for the intended embed flow."
1147
+ } : advisoryDiagnostics.length > 0 ? {
1148
+ label: "Limited",
1149
+ tone: "#fde68a",
1150
+ background: "rgba(120, 53, 15, 0.24)",
1151
+ border: "1px solid rgba(245, 158, 11, 0.22)",
1152
+ text: "The embed should run, but some features or performance may be reduced."
1153
+ } : {
1154
+ label: "Ready",
1155
+ tone: "#86efac",
1156
+ background: "rgba(20, 83, 45, 0.24)",
1157
+ border: "1px solid rgba(34, 197, 94, 0.22)",
1158
+ text: "All key browser, performance, and iframe capabilities are present."
1159
+ };
1160
+ return /* @__PURE__ */ jsxs2(
1161
+ "div",
1162
+ {
1163
+ className: props.className,
1164
+ style: {
1165
+ position: "relative",
1166
+ minHeight: "100vh",
1167
+ overflow: "hidden",
1168
+ padding: "32px 20px 48px",
1169
+ background: "radial-gradient(circle at top left, rgba(56, 189, 248, 0.18) 0%, rgba(2, 6, 23, 0) 34%), radial-gradient(circle at 85% 10%, rgba(37, 99, 235, 0.18) 0%, rgba(2, 6, 23, 0) 26%), linear-gradient(180deg, #020617 0%, #050816 48%, #020617 100%)",
1170
+ color: "#e5e7eb"
1171
+ },
1172
+ children: [
1173
+ /* @__PURE__ */ jsxs2(
1174
+ "div",
1175
+ {
1176
+ style: {
1177
+ position: "absolute",
1178
+ inset: 0,
1179
+ overflow: "hidden",
1180
+ pointerEvents: "none"
1181
+ },
1182
+ children: [
1183
+ /* @__PURE__ */ jsx2(
1184
+ "div",
1185
+ {
1186
+ style: {
1187
+ position: "absolute",
1188
+ top: "-120px",
1189
+ left: "-120px",
1190
+ width: "320px",
1191
+ height: "320px",
1192
+ borderRadius: "999px",
1193
+ background: "rgba(56, 189, 248, 0.12)",
1194
+ filter: "blur(60px)"
1195
+ }
1196
+ }
1197
+ ),
1198
+ /* @__PURE__ */ jsx2(
1199
+ "div",
1200
+ {
1201
+ style: {
1202
+ position: "absolute",
1203
+ right: "-80px",
1204
+ top: "120px",
1205
+ width: "280px",
1206
+ height: "280px",
1207
+ borderRadius: "999px",
1208
+ background: "rgba(37, 99, 235, 0.12)",
1209
+ filter: "blur(60px)"
1210
+ }
1211
+ }
1212
+ )
1213
+ ]
1214
+ }
1215
+ ),
1216
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative", maxWidth: "1280px", margin: "0 auto" }, children: [
743
1217
  /* @__PURE__ */ jsx2(
744
- "button",
1218
+ "div",
745
1219
  {
746
- type: "button",
747
1220
  style: {
748
- ...buttonStyle,
749
- backgroundColor: "#0066cc",
750
- color: "#ffffff",
751
- border: "none",
752
- fontWeight: "600"
1221
+ ...panelStyle,
1222
+ marginBottom: "24px",
1223
+ padding: "28px",
1224
+ background: "linear-gradient(135deg, rgba(15, 23, 42, 0.92) 0%, rgba(8, 15, 30, 0.98) 55%, rgba(15, 23, 42, 0.92) 100%)"
753
1225
  },
754
- onClick: handleRestart,
755
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "#0052a3",
756
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "#0066cc",
757
- children: "\u{1F504} Restart Embed"
1226
+ children: /* @__PURE__ */ jsxs2(
1227
+ "div",
1228
+ {
1229
+ style: {
1230
+ display: "flex",
1231
+ flexWrap: "wrap",
1232
+ gap: "20px",
1233
+ justifyContent: "space-between",
1234
+ alignItems: "flex-start"
1235
+ },
1236
+ children: [
1237
+ /* @__PURE__ */ jsxs2("div", { style: { maxWidth: "720px" }, children: [
1238
+ /* @__PURE__ */ jsx2(
1239
+ "div",
1240
+ {
1241
+ style: {
1242
+ display: "inline-flex",
1243
+ alignItems: "center",
1244
+ gap: "10px",
1245
+ padding: "6px 12px",
1246
+ borderRadius: "999px",
1247
+ backgroundColor: "rgba(56, 189, 248, 0.12)",
1248
+ border: "1px solid rgba(125, 211, 252, 0.18)",
1249
+ color: "#7dd3fc",
1250
+ fontSize: "11px",
1251
+ fontWeight: 700,
1252
+ letterSpacing: "0.12em",
1253
+ textTransform: "uppercase"
1254
+ },
1255
+ children: "\u{1F9EA} Playruo QA Surface"
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsx2(
1259
+ "h1",
1260
+ {
1261
+ style: {
1262
+ margin: "16px 0 12px 0",
1263
+ fontSize: "clamp(32px, 4vw, 46px)",
1264
+ lineHeight: 1.05,
1265
+ letterSpacing: "-0.04em",
1266
+ color: "#f8fafc"
1267
+ },
1268
+ children: "Embed Tester"
1269
+ }
1270
+ ),
1271
+ /* @__PURE__ */ jsx2(
1272
+ "p",
1273
+ {
1274
+ style: {
1275
+ margin: 0,
1276
+ maxWidth: "640px",
1277
+ color: "#94a3b8",
1278
+ fontSize: "16px",
1279
+ lineHeight: 1.7
1280
+ },
1281
+ children: "Edit configuration safely, apply changes only when you decide to, and keep runtime feedback visible without the UI fighting your typing."
1282
+ }
1283
+ )
1284
+ ] }),
1285
+ /* @__PURE__ */ jsxs2(
1286
+ "div",
1287
+ {
1288
+ style: {
1289
+ display: "grid",
1290
+ gap: "12px",
1291
+ gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
1292
+ flex: "1 1 320px"
1293
+ },
1294
+ children: [
1295
+ /* @__PURE__ */ jsxs2(
1296
+ "div",
1297
+ {
1298
+ style: {
1299
+ padding: "16px",
1300
+ borderRadius: "18px",
1301
+ backgroundColor: "rgba(15, 23, 42, 0.72)",
1302
+ border: "1px solid rgba(148, 163, 184, 0.14)"
1303
+ },
1304
+ children: [
1305
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "12px", color: "#94a3b8", marginBottom: "8px" }, children: "Active host" }),
1306
+ /* @__PURE__ */ jsx2("div", { style: { color: "#f8fafc", fontSize: "16px", fontWeight: 700 }, children: displayHost })
1307
+ ]
1308
+ }
1309
+ ),
1310
+ /* @__PURE__ */ jsxs2(
1311
+ "div",
1312
+ {
1313
+ style: {
1314
+ padding: "16px",
1315
+ borderRadius: "18px",
1316
+ backgroundColor: hasPendingConfigChanges ? "rgba(35, 23, 8, 0.72)" : "rgba(15, 23, 42, 0.72)",
1317
+ border: hasPendingConfigChanges ? "1px solid rgba(245, 158, 11, 0.24)" : "1px solid rgba(148, 163, 184, 0.14)"
1318
+ },
1319
+ children: [
1320
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: "12px", color: "#94a3b8", marginBottom: "8px" }, children: "Config state" }),
1321
+ /* @__PURE__ */ jsxs2(
1322
+ "div",
1323
+ {
1324
+ style: {
1325
+ display: "flex",
1326
+ alignItems: "center",
1327
+ gap: "10px",
1328
+ color: hasPendingConfigChanges ? "#fbbf24" : "#86efac",
1329
+ fontSize: "16px",
1330
+ fontWeight: 700
1331
+ },
1332
+ children: [
1333
+ /* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(hasPendingConfigChanges) }),
1334
+ hasPendingConfigChanges ? `${pendingChangeCount} pending change(s)` : "Everything applied"
1335
+ ]
1336
+ }
1337
+ )
1338
+ ]
1339
+ }
1340
+ )
1341
+ ]
1342
+ }
1343
+ )
1344
+ ]
1345
+ }
1346
+ )
758
1347
  }
759
- )
760
- ] }),
761
- /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
762
- /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Status" }),
763
- /* @__PURE__ */ jsxs2("div", { children: [
764
- /* @__PURE__ */ jsx2("span", { style: embedState.isReady ? successStatus : errorStatus, children: embedState.isReady ? "\u2705 Ready" : "\u274C Not Ready" }),
765
- /* @__PURE__ */ jsx2("span", { style: embedState.hasPlayedOnce ? successStatus : errorStatus, children: embedState.hasPlayedOnce ? "\u2705 Played" : "\u274C Not Played" }),
766
- /* @__PURE__ */ jsx2("span", { style: embedState.tryNow?.available ? successStatus : errorStatus, children: embedState.tryNow?.available ? "\u2705 Try Now" : "\u274C Try Now" }),
767
- /* @__PURE__ */ jsx2("span", { style: embedState.buyNow?.available ? successStatus : errorStatus, children: embedState.buyNow?.available ? "\u2705 Buy Now" : "\u274C Buy Now" })
768
- ] }),
769
- !embedState.tryNow?.available && embedState.tryNow?.status && /* @__PURE__ */ jsxs2("p", { style: { margin: "8px 0 0 0", fontSize: "12px", color: "#a3a3a3" }, children: [
770
- "Try Now reason: ",
771
- embedState.tryNow.status
772
- ] })
773
- ] }),
774
- /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
775
- /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "\u{1F50D} Browser Diagnostics" }),
776
- /* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px" }, children: [
777
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
778
- /* @__PURE__ */ jsx2("span", { style: diagnostics.crossOriginIsolation ? successStatus : errorStatus, children: diagnostics.crossOriginIsolation ? "\u2705" : "\u274C" }),
779
- /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Cross-Origin Isolation" })
780
- ] }),
781
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
782
- /* @__PURE__ */ jsx2("span", { style: diagnostics.gamepadSupported ? successStatus : errorStatus, children: diagnostics.gamepadSupported ? "\u2705" : "\u274C" }),
783
- /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Gamepad API" })
784
- ] }),
785
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
786
- /* @__PURE__ */ jsx2("span", { style: diagnostics.fullscreenSupported ? successStatus : errorStatus, children: diagnostics.fullscreenSupported ? "\u2705" : "\u274C" }),
787
- /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Fullscreen API" })
788
- ] }),
789
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
790
- /* @__PURE__ */ jsx2("span", { style: diagnostics.keyboardLockSupported ? successStatus : errorStatus, children: diagnostics.keyboardLockSupported ? "\u2705" : "\u274C" }),
791
- /* @__PURE__ */ jsx2("span", { style: { fontSize: "12px", color: "#d4d4d4" }, children: "Keyboard Lock" })
792
- ] })
793
- ] })
794
- ] }),
795
- /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
796
- /* @__PURE__ */ jsx2("h3", { style: { margin: "0 0 16px 0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Event Log" }),
797
- /* @__PURE__ */ jsx2("div", { style: {
798
- backgroundColor: "#0a0a0a",
799
- padding: "12px",
800
- borderRadius: "4px",
801
- minHeight: "200px",
802
- height: "min(50vh, 480px)",
803
- overflowY: "auto",
804
- fontFamily: "monospace",
805
- fontSize: "12px",
806
- border: "1px solid #333333"
807
- }, children: eventLog.length ? /* @__PURE__ */ jsx2("div", { children: eventLog.map((event) => /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "4px" }, children: [
808
- /* @__PURE__ */ jsxs2("span", { style: { color: "#737373" }, children: [
809
- "[",
810
- event.timestamp,
811
- "]"
812
- ] }),
813
- " ",
814
- /* @__PURE__ */ jsx2("span", { style: { color: "#e5e5e5" }, children: event.message })
815
- ] }, event.timestamp)) }) : /* @__PURE__ */ jsx2("div", { style: { color: "#737373" }, children: "No events yet..." }) })
816
- ] })
817
- ] }),
818
- /* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsxs2("div", { style: cardStyle, children: [
819
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" }, children: [
820
- /* @__PURE__ */ jsx2("h3", { style: { margin: "0", fontSize: "18px", fontWeight: "600", color: "#ffffff" }, children: "Embed Container" }),
821
- /* @__PURE__ */ jsxs2("span", { style: {
822
- backgroundColor: "#2a2a2a",
823
- color: "#e5e5e5",
824
- padding: "4px 8px",
825
- borderRadius: "12px",
826
- fontSize: "12px",
827
- fontWeight: "500",
828
- border: "1px solid #404040"
829
- }, children: [
830
- "\u{1F9EA} ",
831
- displayHost
832
- ] })
833
- ] }),
834
- /* @__PURE__ */ jsx2("div", { style: {
835
- position: "relative",
836
- minHeight: "500px",
837
- overflow: "hidden",
838
- borderRadius: "8px",
839
- border: "1px solid #333333",
840
- backgroundColor: "#0a0a0a"
841
- }, children: /* @__PURE__ */ jsx2(
842
- PlayruoEmbed,
843
- {
844
- baseUrl: props.baseUrl,
845
- className: "w-full h-full absolute inset-0",
846
- iframeRef,
847
- onDemoCompleted: handleDemoCompleted,
848
- onError: handleError,
849
- onPurchaseRequest: handlePurchaseRequest,
850
- onReady: handleReady,
851
- onStateChange: handleStateChange,
852
- operation,
853
- renderState: ({ actions, message, status }) => /* @__PURE__ */ jsx2(Fragment, { children: !hasUserClicked && /* @__PURE__ */ jsxs2("div", { style: {
854
- position: "absolute",
855
- inset: "0",
856
- display: "flex",
857
- flexDirection: "column",
858
- alignItems: "center",
859
- justifyContent: "center",
860
- gap: "16px",
861
- backgroundColor: "rgba(0, 0, 0, 0.85)",
862
- backdropFilter: "blur(8px)",
863
- zIndex: 50,
864
- padding: "20px"
865
- }, children: [
866
- /* @__PURE__ */ jsxs2("p", { style: { margin: "0", fontSize: "14px", color: "#d4d4d4" }, children: [
867
- "Status: ",
868
- /* @__PURE__ */ jsx2("span", { style: { color: "#ffffff", fontWeight: "600" }, children: status })
1348
+ ),
1349
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexWrap: "wrap", gap: "24px", alignItems: "flex-start" }, children: [
1350
+ /* @__PURE__ */ jsxs2("div", { style: { flex: "1 1 380px", minWidth: "320px" }, children: [
1351
+ /* @__PURE__ */ jsxs2("div", { style: { ...panelStyle, marginBottom: "18px" }, children: [
1352
+ /* @__PURE__ */ jsxs2(
1353
+ "div",
1354
+ {
1355
+ style: {
1356
+ display: "flex",
1357
+ flexWrap: "wrap",
1358
+ justifyContent: "space-between",
1359
+ gap: "12px",
1360
+ marginBottom: "18px"
1361
+ },
1362
+ children: [
1363
+ /* @__PURE__ */ jsxs2("div", { children: [
1364
+ /* @__PURE__ */ jsx2("h3", { style: sectionTitleStyle, children: "Configuration" }),
1365
+ /* @__PURE__ */ jsxs2("p", { style: { ...helperTextStyle, marginTop: "6px" }, children: [
1366
+ "Draft values stay local until you press ",
1367
+ /* @__PURE__ */ jsx2("strong", { style: { color: "#e2e8f0" }, children: "Apply" }),
1368
+ "."
1369
+ ] })
1370
+ ] }),
1371
+ /* @__PURE__ */ jsxs2("div", { style: buildIndicatorStyle(hasPendingConfigChanges), children: [
1372
+ /* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(hasPendingConfigChanges) }),
1373
+ hasPendingConfigChanges ? "Not applied" : "Applied"
1374
+ ] })
1375
+ ]
1376
+ }
1377
+ ),
1378
+ /* @__PURE__ */ jsxs2("div", { style: { display: "grid", gap: "16px" }, children: [
1379
+ /* @__PURE__ */ jsxs2("div", { children: [
1380
+ /* @__PURE__ */ jsxs2("label", { htmlFor: "operation", style: labelStyle, children: [
1381
+ /* @__PURE__ */ jsx2("span", { children: "Operation" }),
1382
+ /* @__PURE__ */ jsxs2("span", { style: buildIndicatorStyle(isOperationPending), children: [
1383
+ /* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(isOperationPending) }),
1384
+ isOperationPending ? "Pending" : "Live"
1385
+ ] })
1386
+ ] }),
1387
+ /* @__PURE__ */ jsxs2("div", { style: buildInputShellStyle(isOperationPending), children: [
1388
+ /* @__PURE__ */ jsx2(
1389
+ "input",
1390
+ {
1391
+ id: "operation",
1392
+ style: inputStyle,
1393
+ onChange: (e) => setOperationDraft(e.target.value),
1394
+ placeholder: "e.g. iframe",
1395
+ value: operationDraft
1396
+ }
1397
+ ),
1398
+ isOperationPending && /* @__PURE__ */ jsx2(
1399
+ "span",
1400
+ {
1401
+ "aria-hidden": "true",
1402
+ style: {
1403
+ fontSize: "16px",
1404
+ color: "#fbbf24",
1405
+ fontWeight: 700
1406
+ },
1407
+ children: "!"
1408
+ }
1409
+ )
1410
+ ] }),
1411
+ /* @__PURE__ */ jsxs2("p", { style: helperTextStyle, children: [
1412
+ "Applied value: ",
1413
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: appliedOperation })
1414
+ ] })
1415
+ ] }),
1416
+ /* @__PURE__ */ jsxs2("div", { children: [
1417
+ /* @__PURE__ */ jsxs2("label", { htmlFor: "storeUrl", style: labelStyle, children: [
1418
+ /* @__PURE__ */ jsx2("span", { children: "Store URL" }),
1419
+ /* @__PURE__ */ jsxs2("span", { style: buildIndicatorStyle(isStoreUrlPending), children: [
1420
+ /* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(isStoreUrlPending) }),
1421
+ isStoreUrlPending ? "Pending" : "Live"
1422
+ ] })
1423
+ ] }),
1424
+ /* @__PURE__ */ jsxs2("div", { style: buildInputShellStyle(isStoreUrlPending), children: [
1425
+ /* @__PURE__ */ jsx2(
1426
+ "input",
1427
+ {
1428
+ id: "storeUrl",
1429
+ style: inputStyle,
1430
+ onChange: (e) => setStoreUrlDraft(e.target.value),
1431
+ placeholder: "https://store.example.com",
1432
+ value: storeUrlDraft
1433
+ }
1434
+ ),
1435
+ isStoreUrlPending && /* @__PURE__ */ jsx2(
1436
+ "span",
1437
+ {
1438
+ "aria-hidden": "true",
1439
+ style: {
1440
+ fontSize: "16px",
1441
+ color: "#fbbf24",
1442
+ fontWeight: 700
1443
+ },
1444
+ children: "!"
1445
+ }
1446
+ )
1447
+ ] }),
1448
+ /* @__PURE__ */ jsxs2("p", { style: helperTextStyle, children: [
1449
+ "Applied value: ",
1450
+ /* @__PURE__ */ jsx2("code", { style: codeStyle, children: appliedStoreUrl })
1451
+ ] })
1452
+ ] })
1453
+ ] }),
1454
+ /* @__PURE__ */ jsxs2(
1455
+ "div",
1456
+ {
1457
+ style: {
1458
+ display: "flex",
1459
+ flexWrap: "wrap",
1460
+ gap: "12px",
1461
+ marginTop: "20px"
1462
+ },
1463
+ children: [
1464
+ /* @__PURE__ */ jsx2(
1465
+ "button",
1466
+ {
1467
+ type: "button",
1468
+ style: buildButtonStyle("primary", !hasPendingConfigChanges),
1469
+ disabled: !hasPendingConfigChanges,
1470
+ onClick: handleApply,
1471
+ children: "Apply"
1472
+ }
1473
+ ),
1474
+ /* @__PURE__ */ jsx2(
1475
+ "button",
1476
+ {
1477
+ type: "button",
1478
+ style: buildButtonStyle("secondary"),
1479
+ onClick: handleRestartSession,
1480
+ children: "Restart Session"
1481
+ }
1482
+ ),
1483
+ /* @__PURE__ */ jsx2(
1484
+ "button",
1485
+ {
1486
+ type: "button",
1487
+ style: buildButtonStyle("ghost", !hasPendingConfigChanges),
1488
+ disabled: !hasPendingConfigChanges,
1489
+ onClick: handleResetDrafts,
1490
+ children: "Reset Drafts"
1491
+ }
1492
+ )
1493
+ ]
1494
+ }
1495
+ )
1496
+ ] }),
1497
+ /* @__PURE__ */ jsxs2("div", { style: { ...panelStyle, marginBottom: "18px" }, children: [
1498
+ /* @__PURE__ */ jsx2("h3", { style: sectionTitleStyle, children: "Runtime status" }),
1499
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: "10px", marginTop: "16px" }, children: statusItems.map((item) => /* @__PURE__ */ jsxs2("span", { style: buildStatusChipStyle(item.value), children: [
1500
+ /* @__PURE__ */ jsx2("span", { children: item.value ? "\u25CF" : "\u25CB" }),
1501
+ item.label
1502
+ ] }, item.label)) }),
1503
+ !embedState.tryNow?.available && embedState.tryNow?.status && /* @__PURE__ */ jsxs2("p", { style: { ...helperTextStyle, marginTop: "14px" }, children: [
1504
+ "Try Now reason: ",
1505
+ /* @__PURE__ */ jsx2("span", { style: { color: "#cbd5e1" }, children: embedState.tryNow.status })
1506
+ ] })
869
1507
  ] }),
870
- message && /* @__PURE__ */ jsx2("p", { style: { margin: "0", fontSize: "14px", color: "#d4d4d4" }, children: message }),
1508
+ /* @__PURE__ */ jsxs2("div", { style: { ...panelStyle, marginBottom: "18px" }, children: [
1509
+ /* @__PURE__ */ jsx2("h3", { style: sectionTitleStyle, children: "Browser diagnostics" }),
1510
+ /* @__PURE__ */ jsxs2(
1511
+ "div",
1512
+ {
1513
+ style: {
1514
+ marginTop: "16px",
1515
+ padding: "16px 18px",
1516
+ borderRadius: "20px",
1517
+ background: diagnosticsVerdict.background,
1518
+ border: diagnosticsVerdict.border
1519
+ },
1520
+ children: [
1521
+ /* @__PURE__ */ jsxs2(
1522
+ "div",
1523
+ {
1524
+ style: {
1525
+ display: "flex",
1526
+ flexWrap: "wrap",
1527
+ justifyContent: "space-between",
1528
+ gap: "12px",
1529
+ alignItems: "center"
1530
+ },
1531
+ children: [
1532
+ /* @__PURE__ */ jsxs2("div", { children: [
1533
+ /* @__PURE__ */ jsx2(
1534
+ "div",
1535
+ {
1536
+ style: {
1537
+ color: diagnosticsVerdict.tone,
1538
+ fontSize: "12px",
1539
+ fontWeight: 700,
1540
+ letterSpacing: "0.1em",
1541
+ textTransform: "uppercase"
1542
+ },
1543
+ children: "Diagnostics verdict"
1544
+ }
1545
+ ),
1546
+ /* @__PURE__ */ jsx2("div", { style: { color: "#f8fafc", fontSize: "20px", fontWeight: 700, marginTop: "6px" }, children: diagnosticsVerdict.label })
1547
+ ] }),
1548
+ /* @__PURE__ */ jsx2("div", { style: { color: "#cbd5e1", fontSize: "13px", maxWidth: "420px", lineHeight: 1.6 }, children: diagnosticsVerdict.text })
1549
+ ]
1550
+ }
1551
+ ),
1552
+ (blockingDiagnostics.length > 0 || advisoryDiagnostics.length > 0) && /* @__PURE__ */ jsxs2(
1553
+ "div",
1554
+ {
1555
+ style: {
1556
+ display: "grid",
1557
+ gap: "10px",
1558
+ marginTop: "16px"
1559
+ },
1560
+ children: [
1561
+ blockingDiagnostics.length > 0 && /* @__PURE__ */ jsxs2("div", { style: { color: "#fca5a5", fontSize: "13px", lineHeight: 1.6 }, children: [
1562
+ "Blocking: ",
1563
+ blockingDiagnostics.map((item) => item.label).join(", ")
1564
+ ] }),
1565
+ advisoryDiagnostics.length > 0 && /* @__PURE__ */ jsxs2("div", { style: { color: "#fde68a", fontSize: "13px", lineHeight: 1.6 }, children: [
1566
+ "Worth checking: ",
1567
+ advisoryDiagnostics.map((item) => item.label).join(", ")
1568
+ ] })
1569
+ ]
1570
+ }
1571
+ )
1572
+ ]
1573
+ }
1574
+ ),
1575
+ /* @__PURE__ */ jsx2(
1576
+ "div",
1577
+ {
1578
+ style: {
1579
+ display: "grid",
1580
+ gap: "14px",
1581
+ marginTop: "18px"
1582
+ },
1583
+ children: diagnosticGroups.map((group) => /* @__PURE__ */ jsxs2(
1584
+ "div",
1585
+ {
1586
+ style: {
1587
+ padding: "16px",
1588
+ borderRadius: "20px",
1589
+ backgroundColor: "rgba(15, 23, 42, 0.42)",
1590
+ border: "1px solid rgba(148, 163, 184, 0.12)"
1591
+ },
1592
+ children: [
1593
+ /* @__PURE__ */ jsx2(
1594
+ "div",
1595
+ {
1596
+ style: {
1597
+ color: "#e2e8f0",
1598
+ fontSize: "14px",
1599
+ fontWeight: 700,
1600
+ marginBottom: "14px"
1601
+ },
1602
+ children: group.title
1603
+ }
1604
+ ),
1605
+ /* @__PURE__ */ jsx2(
1606
+ "div",
1607
+ {
1608
+ style: {
1609
+ display: "grid",
1610
+ gridTemplateColumns: "repeat(auto-fit, minmax(190px, 1fr))",
1611
+ gap: "12px"
1612
+ },
1613
+ children: group.items.map((item) => /* @__PURE__ */ jsxs2(
1614
+ "div",
1615
+ {
1616
+ style: {
1617
+ padding: "14px 16px",
1618
+ borderRadius: "18px",
1619
+ backgroundColor: item.value ? "rgba(8, 31, 17, 0.72)" : "rgba(42, 12, 12, 0.52)",
1620
+ border: item.value ? "1px solid rgba(34, 197, 94, 0.18)" : "1px solid rgba(248, 113, 113, 0.18)"
1621
+ },
1622
+ children: [
1623
+ /* @__PURE__ */ jsx2("div", { style: { color: "#94a3b8", fontSize: "12px", marginBottom: "8px" }, children: item.label }),
1624
+ /* @__PURE__ */ jsxs2(
1625
+ "div",
1626
+ {
1627
+ style: {
1628
+ display: "flex",
1629
+ alignItems: "center",
1630
+ gap: "8px",
1631
+ color: item.value ? "#86efac" : "#fca5a5",
1632
+ fontWeight: 700
1633
+ },
1634
+ children: [
1635
+ /* @__PURE__ */ jsx2("span", { children: item.value ? "\u2713" : "\u2715" }),
1636
+ item.value ? "Available" : "Missing"
1637
+ ]
1638
+ }
1639
+ ),
1640
+ /* @__PURE__ */ jsx2("div", { style: { color: "#94a3b8", fontSize: "12px", lineHeight: 1.5, marginTop: "10px" }, children: item.detail })
1641
+ ]
1642
+ },
1643
+ item.label
1644
+ ))
1645
+ }
1646
+ )
1647
+ ]
1648
+ },
1649
+ group.title
1650
+ ))
1651
+ }
1652
+ )
1653
+ ] }),
1654
+ /* @__PURE__ */ jsxs2("div", { style: panelStyle, children: [
1655
+ /* @__PURE__ */ jsxs2(
1656
+ "div",
1657
+ {
1658
+ style: {
1659
+ display: "flex",
1660
+ justifyContent: "space-between",
1661
+ alignItems: "center",
1662
+ gap: "12px",
1663
+ marginBottom: "16px"
1664
+ },
1665
+ children: [
1666
+ /* @__PURE__ */ jsx2("h3", { style: sectionTitleStyle, children: "Event log" }),
1667
+ /* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "4px 10px" }, children: [
1668
+ eventLog.length,
1669
+ " recent events"
1670
+ ] })
1671
+ ]
1672
+ }
1673
+ ),
1674
+ /* @__PURE__ */ jsx2(
1675
+ "div",
1676
+ {
1677
+ style: {
1678
+ background: "linear-gradient(180deg, rgba(2, 6, 23, 0.88) 0%, rgba(2, 6, 23, 0.64) 100%)",
1679
+ padding: "14px",
1680
+ borderRadius: "18px",
1681
+ minHeight: "220px",
1682
+ height: "min(46vh, 420px)",
1683
+ overflowY: "auto",
1684
+ fontFamily: '"SF Mono", "Roboto Mono", monospace',
1685
+ fontSize: "12px",
1686
+ border: "1px solid rgba(148, 163, 184, 0.12)"
1687
+ },
1688
+ children: eventLog.length ? /* @__PURE__ */ jsx2("div", { style: { display: "grid", gap: "10px" }, children: eventLog.map((event) => /* @__PURE__ */ jsxs2(
1689
+ "div",
1690
+ {
1691
+ style: {
1692
+ paddingBottom: "10px",
1693
+ borderBottom: "1px solid rgba(148, 163, 184, 0.08)"
1694
+ },
1695
+ children: [
1696
+ /* @__PURE__ */ jsx2("div", { style: { color: "#64748b", marginBottom: "4px" }, children: event.timestamp }),
1697
+ /* @__PURE__ */ jsx2("div", { style: { color: "#e2e8f0", lineHeight: 1.6 }, children: event.message })
1698
+ ]
1699
+ },
1700
+ event.id
1701
+ )) }) : /* @__PURE__ */ jsx2("div", { style: { color: "#64748b" }, children: "No events yet. Start a session or apply a config change." })
1702
+ }
1703
+ )
1704
+ ] })
1705
+ ] }),
1706
+ /* @__PURE__ */ jsx2("div", { style: { flex: "1 1 560px", minWidth: "320px" }, children: /* @__PURE__ */ jsxs2("div", { style: { ...panelStyle, padding: "24px" }, children: [
871
1707
  /* @__PURE__ */ jsxs2(
872
- "button",
1708
+ "div",
873
1709
  {
874
- type: "button",
875
1710
  style: {
876
- ...buttonStyle,
877
- backgroundColor: "#16a34a",
878
- color: "#ffffff",
879
- border: "none",
880
- padding: "12px 24px",
881
- fontSize: "16px",
882
- fontWeight: "600",
883
- cursor: actions.canTryNow ? "pointer" : "not-allowed",
884
- opacity: actions.canTryNow ? 1 : 0.5
885
- },
886
- disabled: !actions.canTryNow,
887
- onMouseEnter: (e) => {
888
- if (actions.canTryNow) {
889
- e.currentTarget.style.backgroundColor = "#15803d";
890
- }
891
- },
892
- onMouseLeave: (e) => {
893
- if (actions.canTryNow) {
894
- e.currentTarget.style.backgroundColor = "#16a34a";
895
- }
896
- },
897
- onClick: () => {
898
- setHasUserClicked(true);
899
- setShowIframe(true);
900
- addEvent(embedState.hasPlayedOnce ? "User clicked - Resuming game" : "User clicked - Starting game with tryNow");
901
- actions.tryNow();
1711
+ display: "flex",
1712
+ flexWrap: "wrap",
1713
+ justifyContent: "space-between",
1714
+ alignItems: "center",
1715
+ gap: "12px",
1716
+ marginBottom: "16px"
902
1717
  },
903
1718
  children: [
904
- "\u{1F3AE} ",
905
- embedState.hasPlayedOnce ? "Resume Game" : "Try Demo Now"
1719
+ /* @__PURE__ */ jsxs2("div", { children: [
1720
+ /* @__PURE__ */ jsx2("h3", { style: sectionTitleStyle, children: "Embed preview" }),
1721
+ /* @__PURE__ */ jsx2("p", { style: { ...helperTextStyle, marginTop: "6px" }, children: "The live iframe always reflects the applied configuration." })
1722
+ ] }),
1723
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexWrap: "wrap", gap: "10px" }, children: [
1724
+ /* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
1725
+ "Host: ",
1726
+ displayHost
1727
+ ] }),
1728
+ /* @__PURE__ */ jsxs2("span", { style: { ...codeStyle, padding: "6px 10px" }, children: [
1729
+ "Operation: ",
1730
+ appliedOperation
1731
+ ] })
1732
+ ] })
906
1733
  ]
907
1734
  }
908
1735
  ),
909
- actions.canBuyNow && /* @__PURE__ */ jsx2(
910
- "button",
1736
+ hasPendingConfigChanges && /* @__PURE__ */ jsxs2(
1737
+ "div",
911
1738
  {
912
- type: "button",
913
1739
  style: {
914
- ...buttonStyle,
915
- backgroundColor: "#525252",
916
- color: "#ffffff",
917
- border: "none",
918
- padding: "8px 16px",
919
- fontSize: "14px",
920
- fontWeight: "500"
1740
+ display: "flex",
1741
+ alignItems: "center",
1742
+ gap: "12px",
1743
+ padding: "14px 16px",
1744
+ marginBottom: "16px",
1745
+ borderRadius: "18px",
1746
+ backgroundColor: "rgba(35, 23, 8, 0.72)",
1747
+ border: "1px solid rgba(245, 158, 11, 0.22)",
1748
+ color: "#fde68a"
921
1749
  },
922
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "#404040",
923
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "#525252",
924
- onClick: () => {
925
- addEvent("Buy Now clicked");
926
- actions.buyNow();
1750
+ children: [
1751
+ /* @__PURE__ */ jsx2("span", { style: buildIndicatorDotStyle(true) }),
1752
+ "Draft values are ready but not applied to the iframe yet."
1753
+ ]
1754
+ }
1755
+ ),
1756
+ /* @__PURE__ */ jsxs2(
1757
+ "div",
1758
+ {
1759
+ style: {
1760
+ position: "relative",
1761
+ minHeight: "640px",
1762
+ overflow: "hidden",
1763
+ borderRadius: "24px",
1764
+ border: "1px solid rgba(148, 163, 184, 0.14)",
1765
+ background: "linear-gradient(180deg, rgba(3, 7, 18, 0.86) 0%, rgba(2, 6, 23, 1) 100%)",
1766
+ boxShadow: "inset 0 1px 0 rgba(255, 255, 255, 0.04)"
927
1767
  },
928
- children: "Buy Now"
1768
+ children: [
1769
+ /* @__PURE__ */ jsxs2(
1770
+ "div",
1771
+ {
1772
+ style: {
1773
+ display: "flex",
1774
+ alignItems: "center",
1775
+ justifyContent: "space-between",
1776
+ gap: "12px",
1777
+ padding: "14px 18px",
1778
+ borderBottom: "1px solid rgba(148, 163, 184, 0.08)",
1779
+ backgroundColor: "rgba(15, 23, 42, 0.52)"
1780
+ },
1781
+ children: [
1782
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1783
+ /* @__PURE__ */ jsx2("span", { style: { width: "10px", height: "10px", borderRadius: "999px", backgroundColor: "#f97316" } }),
1784
+ /* @__PURE__ */ jsx2("span", { style: { width: "10px", height: "10px", borderRadius: "999px", backgroundColor: "#facc15" } }),
1785
+ /* @__PURE__ */ jsx2("span", { style: { width: "10px", height: "10px", borderRadius: "999px", backgroundColor: "#4ade80" } })
1786
+ ] }),
1787
+ /* @__PURE__ */ jsxs2("span", { style: { color: "#94a3b8", fontSize: "12px", fontFamily: '"SF Mono", "Roboto Mono", monospace' }, children: [
1788
+ "/embed?operation=",
1789
+ appliedOperation
1790
+ ] })
1791
+ ]
1792
+ }
1793
+ ),
1794
+ /* @__PURE__ */ jsx2(
1795
+ PlayruoEmbed,
1796
+ {
1797
+ baseUrl: props.baseUrl,
1798
+ className: "w-full h-full absolute inset-0",
1799
+ iframeRef,
1800
+ onDemoCompleted: handleDemoCompleted,
1801
+ onError: handleError,
1802
+ onEvent: handleEvent,
1803
+ onPurchaseRequest: handlePurchaseRequest,
1804
+ onReady: handleReady,
1805
+ onStateChange: handleStateChange,
1806
+ operation: appliedOperation,
1807
+ renderState: ({ actions, message, status }) => /* @__PURE__ */ jsx2(Fragment, { children: !hasUserClicked && /* @__PURE__ */ jsx2(
1808
+ "div",
1809
+ {
1810
+ style: {
1811
+ position: "absolute",
1812
+ inset: "53px 0 0 0",
1813
+ display: "flex",
1814
+ alignItems: "center",
1815
+ justifyContent: "center",
1816
+ padding: "24px",
1817
+ background: "linear-gradient(180deg, rgba(2, 6, 23, 0.4) 0%, rgba(2, 6, 23, 0.84) 70%, rgba(2, 6, 23, 0.9) 100%)",
1818
+ backdropFilter: "blur(12px)",
1819
+ zIndex: 50
1820
+ },
1821
+ children: /* @__PURE__ */ jsxs2(
1822
+ "div",
1823
+ {
1824
+ style: {
1825
+ width: "min(100%, 420px)",
1826
+ padding: "28px",
1827
+ borderRadius: "28px",
1828
+ border: "1px solid rgba(148, 163, 184, 0.18)",
1829
+ background: "linear-gradient(180deg, rgba(15, 23, 42, 0.94) 0%, rgba(7, 12, 24, 0.98) 100%)",
1830
+ boxShadow: "0 32px 72px rgba(0, 0, 0, 0.35)"
1831
+ },
1832
+ children: [
1833
+ /* @__PURE__ */ jsx2(
1834
+ "div",
1835
+ {
1836
+ style: {
1837
+ display: "inline-flex",
1838
+ alignItems: "center",
1839
+ gap: "10px",
1840
+ padding: "6px 12px",
1841
+ borderRadius: "999px",
1842
+ backgroundColor: "rgba(56, 189, 248, 0.12)",
1843
+ border: "1px solid rgba(125, 211, 252, 0.18)",
1844
+ color: "#7dd3fc",
1845
+ fontSize: "11px",
1846
+ fontWeight: 700,
1847
+ letterSpacing: "0.08em",
1848
+ textTransform: "uppercase"
1849
+ },
1850
+ children: "Live overlay"
1851
+ }
1852
+ ),
1853
+ /* @__PURE__ */ jsx2(
1854
+ "h4",
1855
+ {
1856
+ style: {
1857
+ margin: "18px 0 10px 0",
1858
+ color: "#f8fafc",
1859
+ fontSize: "28px",
1860
+ letterSpacing: "-0.03em"
1861
+ },
1862
+ children: embedState.hasPlayedOnce ? "Resume the session" : "Start the demo"
1863
+ }
1864
+ ),
1865
+ /* @__PURE__ */ jsxs2("p", { style: { margin: 0, color: "#94a3b8", lineHeight: 1.7 }, children: [
1866
+ "Status: ",
1867
+ /* @__PURE__ */ jsx2("span", { style: { color: "#f8fafc", fontWeight: 700 }, children: status })
1868
+ ] }),
1869
+ message && /* @__PURE__ */ jsx2("p", { style: { margin: "10px 0 0 0", color: "#cbd5e1", lineHeight: 1.7 }, children: message }),
1870
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexWrap: "wrap", gap: "12px", marginTop: "24px" }, children: [
1871
+ /* @__PURE__ */ jsxs2(
1872
+ "button",
1873
+ {
1874
+ type: "button",
1875
+ style: buildButtonStyle("primary", !actions.canTryNow),
1876
+ disabled: !actions.canTryNow,
1877
+ onClick: () => {
1878
+ setHasUserClicked(true);
1879
+ setShowIframe(true);
1880
+ addEvent(
1881
+ embedState.hasPlayedOnce ? "User clicked - Resuming game" : "User clicked - Starting game with tryNow"
1882
+ );
1883
+ actions.tryNow();
1884
+ },
1885
+ children: [
1886
+ "\u{1F3AE} ",
1887
+ embedState.hasPlayedOnce ? "Resume game" : "Try demo now"
1888
+ ]
1889
+ }
1890
+ ),
1891
+ actions.canBuyNow && /* @__PURE__ */ jsx2(
1892
+ "button",
1893
+ {
1894
+ type: "button",
1895
+ style: buildButtonStyle("secondary"),
1896
+ onClick: () => {
1897
+ addEvent("Buy Now clicked");
1898
+ actions.buyNow();
1899
+ },
1900
+ children: "Buy Now"
1901
+ }
1902
+ )
1903
+ ] })
1904
+ ]
1905
+ }
1906
+ )
1907
+ }
1908
+ ) }),
1909
+ showIframe,
1910
+ storeUrl: appliedStoreUrl
1911
+ },
1912
+ key
1913
+ )
1914
+ ]
929
1915
  }
930
1916
  )
931
- ] }) }),
932
- showIframe,
933
- storeUrl
934
- },
935
- key
936
- ) })
937
- ] }) })
938
- ] })
939
- ] }) });
1917
+ ] }) })
1918
+ ] })
1919
+ ] })
1920
+ ]
1921
+ }
1922
+ );
940
1923
  };
941
1924
 
942
1925
  // src/post-message-types.ts
@@ -944,7 +1927,7 @@ var isPlayruoEmbedMessage = (data) => {
944
1927
  if (!data || typeof data !== "object") return false;
945
1928
  const msg = data;
946
1929
  if (typeof msg.type !== "string") return false;
947
- return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error" || msg.type === "playruo_session_over" || msg.type === "playruo_stream_start" || msg.type === "playruo_stream_stop";
1930
+ return msg.type === "playruo_ready" || msg.type === "playruo_state_update" || msg.type === "playruo_purchase_request" || msg.type === "playruo_demo_completed" || msg.type === "playruo_error" || msg.type === "playruo_session_over" || msg.type === "playruo_stream_start" || msg.type === "playruo_stream_stop" || msg.type === "REQUEST_EXIT_FULLSCREEN" || msg.type === "playruo_event";
948
1931
  };
949
1932
  var isPlayruoInternalMessage = (data) => {
950
1933
  if (!data || typeof data !== "object") return false;