@oasiz/sdk 1.6.0 → 1.6.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
@@ -1,80 +1,2409 @@
1
- // src/haptics.ts
1
+ // src/navigation.ts
2
+ var BACK_BUTTON_TEST_STATE_KEY = "__oasizBackButtonTest";
3
+ var activeBackListeners = 0;
4
+ var activeBackButtonTestingHandle;
2
5
  function isDevelopment() {
3
6
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
4
7
  return nodeEnv !== "production";
5
8
  }
6
- function getBridgeWindow() {
9
+ function getBridgeWindow() {
10
+ if (typeof window === "undefined") {
11
+ return void 0;
12
+ }
13
+ return window;
14
+ }
15
+ function warnMissingBridge(methodName) {
16
+ if (isDevelopment()) {
17
+ console.warn(
18
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
19
+ );
20
+ }
21
+ }
22
+ function normalizeNavigationError(error) {
23
+ if (error instanceof Error) {
24
+ return error;
25
+ }
26
+ return new Error(
27
+ typeof error === "string" ? error : "Back button callback failed."
28
+ );
29
+ }
30
+ function isRecord(value) {
31
+ return typeof value === "object" && value !== null;
32
+ }
33
+ function dispatchNavigationEvent(eventName) {
34
+ const bridge = getBridgeWindow();
35
+ if (!bridge || typeof bridge.dispatchEvent !== "function") {
36
+ return;
37
+ }
38
+ bridge.dispatchEvent(new Event(eventName));
39
+ }
40
+ function addNavigationListener(eventName, callback) {
41
+ if (typeof window === "undefined") {
42
+ if (isDevelopment()) {
43
+ console.warn(
44
+ "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
45
+ );
46
+ }
47
+ return () => {
48
+ };
49
+ }
50
+ const handler = () => callback();
51
+ window.addEventListener(eventName, handler);
52
+ return () => window.removeEventListener(eventName, handler);
53
+ }
54
+ function enableBackButtonTesting(options = {}) {
55
+ activeBackButtonTestingHandle?.destroy();
56
+ const bridge = getBridgeWindow();
57
+ if (!bridge) {
58
+ if (isDevelopment()) {
59
+ console.warn(
60
+ "[oasiz/sdk] enableBackButtonTesting requires a browser window."
61
+ );
62
+ }
63
+ return {
64
+ destroy: () => {
65
+ },
66
+ isBackOverrideActive: () => false,
67
+ triggerBack: () => {
68
+ },
69
+ triggerLeave: () => {
70
+ }
71
+ };
72
+ }
73
+ const bridgeWindow = bridge;
74
+ const keyboard = options.keyboard ?? true;
75
+ const browserHistory = options.browserHistory ?? true;
76
+ const log = options.log === true;
77
+ const previousSetBackOverride = bridgeWindow.__oasizSetBackOverride;
78
+ const previousLeaveGame = bridgeWindow.__oasizLeaveGame;
79
+ let destroyed = false;
80
+ let backOverrideActive = false;
81
+ let historyTrapArmed = false;
82
+ function maybeLog(message) {
83
+ if (log) {
84
+ console.info("[oasiz/sdk] " + message);
85
+ }
86
+ }
87
+ function canUseHistoryTrap() {
88
+ return browserHistory && typeof bridgeWindow.history?.pushState === "function" && typeof bridgeWindow.history?.replaceState === "function" && typeof bridgeWindow.location?.href === "string";
89
+ }
90
+ function ensureHistoryTrap() {
91
+ if (!backOverrideActive || historyTrapArmed || !canUseHistoryTrap()) {
92
+ return;
93
+ }
94
+ try {
95
+ const currentState = isRecord(bridgeWindow.history.state) ? bridgeWindow.history.state : {};
96
+ bridgeWindow.history.replaceState(
97
+ { ...currentState, [BACK_BUTTON_TEST_STATE_KEY]: "base" },
98
+ "",
99
+ bridgeWindow.location.href
100
+ );
101
+ bridgeWindow.history.pushState(
102
+ { [BACK_BUTTON_TEST_STATE_KEY]: "trap" },
103
+ "",
104
+ bridgeWindow.location.href
105
+ );
106
+ historyTrapArmed = true;
107
+ maybeLog("Local browser Back testing is armed.");
108
+ } catch (error) {
109
+ historyTrapArmed = false;
110
+ if (log) {
111
+ console.warn("[oasiz/sdk] Failed to arm browser Back testing:", error);
112
+ }
113
+ }
114
+ }
115
+ function triggerBack() {
116
+ dispatchNavigationEvent("oasiz:back");
117
+ }
118
+ function triggerLeave() {
119
+ dispatchNavigationEvent("oasiz:leave");
120
+ }
121
+ function setBackOverride(active) {
122
+ backOverrideActive = active;
123
+ if (active) {
124
+ ensureHistoryTrap();
125
+ }
126
+ if (typeof previousSetBackOverride === "function") {
127
+ previousSetBackOverride(active);
128
+ }
129
+ maybeLog("Back override " + (active ? "enabled" : "disabled") + ".");
130
+ }
131
+ function stopBackEvent(event) {
132
+ event.preventDefault();
133
+ event.stopPropagation();
134
+ event.stopImmediatePropagation?.();
135
+ }
136
+ const handleKeyDown = (event) => {
137
+ if (!backOverrideActive || event.key !== "Escape") {
138
+ return;
139
+ }
140
+ stopBackEvent(event);
141
+ triggerBack();
142
+ };
143
+ const handlePopState = (event) => {
144
+ if (!backOverrideActive) {
145
+ historyTrapArmed = false;
146
+ return;
147
+ }
148
+ stopBackEvent(event);
149
+ triggerBack();
150
+ historyTrapArmed = false;
151
+ ensureHistoryTrap();
152
+ };
153
+ const testLeaveGame = () => {
154
+ triggerLeave();
155
+ if (typeof previousLeaveGame === "function") {
156
+ previousLeaveGame();
157
+ }
158
+ };
159
+ bridgeWindow.__oasizSetBackOverride = setBackOverride;
160
+ bridgeWindow.__oasizLeaveGame = testLeaveGame;
161
+ if (keyboard) {
162
+ bridgeWindow.addEventListener("keydown", handleKeyDown);
163
+ }
164
+ if (browserHistory) {
165
+ bridgeWindow.addEventListener("popstate", handlePopState);
166
+ }
167
+ if (activeBackListeners > 0) {
168
+ setBackOverride(true);
169
+ }
170
+ maybeLog("Back button testing bridge installed.");
171
+ const handle = {
172
+ destroy: () => {
173
+ if (destroyed) return;
174
+ destroyed = true;
175
+ if (keyboard) {
176
+ bridgeWindow.removeEventListener("keydown", handleKeyDown);
177
+ }
178
+ if (browserHistory) {
179
+ bridgeWindow.removeEventListener("popstate", handlePopState);
180
+ }
181
+ if (bridgeWindow.__oasizSetBackOverride === setBackOverride) {
182
+ bridgeWindow.__oasizSetBackOverride = previousSetBackOverride;
183
+ }
184
+ if (bridgeWindow.__oasizLeaveGame === testLeaveGame) {
185
+ bridgeWindow.__oasizLeaveGame = previousLeaveGame;
186
+ }
187
+ if (activeBackButtonTestingHandle === handle) {
188
+ activeBackButtonTestingHandle = void 0;
189
+ }
190
+ maybeLog("Back button testing bridge removed.");
191
+ },
192
+ isBackOverrideActive: () => backOverrideActive,
193
+ triggerBack,
194
+ triggerLeave
195
+ };
196
+ activeBackButtonTestingHandle = handle;
197
+ return handle;
198
+ }
199
+ function onBackButton(callback) {
200
+ const off = addNavigationListener("oasiz:back", () => {
201
+ try {
202
+ callback();
203
+ } catch (error) {
204
+ leaveGame();
205
+ throw normalizeNavigationError(error);
206
+ }
207
+ });
208
+ const bridge = getBridgeWindow();
209
+ activeBackListeners += 1;
210
+ if (activeBackListeners === 1) {
211
+ if (typeof bridge?.__oasizSetBackOverride === "function") {
212
+ bridge.__oasizSetBackOverride(true);
213
+ } else {
214
+ warnMissingBridge("__oasizSetBackOverride");
215
+ }
216
+ }
217
+ return () => {
218
+ off();
219
+ activeBackListeners = Math.max(0, activeBackListeners - 1);
220
+ if (activeBackListeners === 0) {
221
+ const currentBridge = getBridgeWindow();
222
+ if (typeof currentBridge?.__oasizSetBackOverride === "function") {
223
+ currentBridge.__oasizSetBackOverride(false);
224
+ } else {
225
+ warnMissingBridge("__oasizSetBackOverride");
226
+ }
227
+ }
228
+ };
229
+ }
230
+ function onLeaveGame(callback) {
231
+ return addNavigationListener("oasiz:leave", callback);
232
+ }
233
+ function leaveGame() {
234
+ const bridge = getBridgeWindow();
235
+ if (typeof bridge?.__oasizLeaveGame === "function") {
236
+ bridge.__oasizLeaveGame();
237
+ return;
238
+ }
239
+ warnMissingBridge("__oasizLeaveGame");
240
+ }
241
+
242
+ // src/app-simulator.ts
243
+ var DEVICE_PRESETS = {
244
+ "iphone-11": {
245
+ name: "iPhone 11",
246
+ width: 414,
247
+ height: 896,
248
+ safeArea: { top: 48, right: 0, bottom: 34, left: 0 }
249
+ },
250
+ "iphone-11-pro": {
251
+ name: "iPhone 11 Pro",
252
+ width: 375,
253
+ height: 812,
254
+ safeArea: { top: 44, right: 0, bottom: 34, left: 0 }
255
+ },
256
+ "iphone-11-pro-max": {
257
+ name: "iPhone 11 Pro Max",
258
+ width: 414,
259
+ height: 896,
260
+ safeArea: { top: 44, right: 0, bottom: 34, left: 0 }
261
+ },
262
+ "iphone-12-mini": {
263
+ name: "iPhone 12 mini",
264
+ width: 375,
265
+ height: 812,
266
+ safeArea: { top: 50, right: 0, bottom: 34, left: 0 }
267
+ },
268
+ "iphone-12": {
269
+ name: "iPhone 12",
270
+ width: 390,
271
+ height: 844,
272
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
273
+ },
274
+ "iphone-12-pro": {
275
+ name: "iPhone 12 Pro",
276
+ width: 390,
277
+ height: 844,
278
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
279
+ },
280
+ "iphone-12-pro-max": {
281
+ name: "iPhone 12 Pro Max",
282
+ width: 428,
283
+ height: 926,
284
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
285
+ },
286
+ "iphone-13-mini": {
287
+ name: "iPhone 13 mini",
288
+ width: 375,
289
+ height: 812,
290
+ safeArea: { top: 50, right: 0, bottom: 34, left: 0 }
291
+ },
292
+ "iphone-13": {
293
+ name: "iPhone 13",
294
+ width: 390,
295
+ height: 844,
296
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
297
+ },
298
+ "iphone-13-pro": {
299
+ name: "iPhone 13 Pro",
300
+ width: 390,
301
+ height: 844,
302
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
303
+ },
304
+ "iphone-13-pro-max": {
305
+ name: "iPhone 13 Pro Max",
306
+ width: 428,
307
+ height: 926,
308
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
309
+ },
310
+ "iphone-14": {
311
+ name: "iPhone 14",
312
+ width: 390,
313
+ height: 844,
314
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
315
+ },
316
+ "iphone-14-plus": {
317
+ name: "iPhone 14 Plus",
318
+ width: 428,
319
+ height: 926,
320
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
321
+ },
322
+ "iphone-14-pro": {
323
+ name: "iPhone 14 Pro",
324
+ width: 393,
325
+ height: 852,
326
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
327
+ },
328
+ "iphone-14-pro-max": {
329
+ name: "iPhone 14 Pro Max",
330
+ width: 430,
331
+ height: 932,
332
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
333
+ },
334
+ "iphone-15": {
335
+ name: "iPhone 15",
336
+ width: 393,
337
+ height: 852,
338
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
339
+ },
340
+ "iphone-15-plus": {
341
+ name: "iPhone 15 Plus",
342
+ width: 430,
343
+ height: 932,
344
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
345
+ },
346
+ "iphone-15-pro": {
347
+ name: "iPhone 15 Pro",
348
+ width: 393,
349
+ height: 852,
350
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
351
+ },
352
+ "iphone-15-pro-max": {
353
+ name: "iPhone 15 Pro Max",
354
+ width: 430,
355
+ height: 932,
356
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
357
+ },
358
+ "iphone-16": {
359
+ name: "iPhone 16",
360
+ width: 393,
361
+ height: 852,
362
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
363
+ },
364
+ "iphone-16-plus": {
365
+ name: "iPhone 16 Plus",
366
+ width: 430,
367
+ height: 932,
368
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
369
+ },
370
+ "iphone-16-pro": {
371
+ name: "iPhone 16 Pro",
372
+ width: 402,
373
+ height: 874,
374
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
375
+ },
376
+ "iphone-16-pro-max": {
377
+ name: "iPhone 16 Pro Max",
378
+ width: 440,
379
+ height: 956,
380
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
381
+ },
382
+ "iphone-16e": {
383
+ name: "iPhone 16e",
384
+ width: 390,
385
+ height: 844,
386
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
387
+ },
388
+ "iphone-17": {
389
+ name: "iPhone 17",
390
+ width: 402,
391
+ height: 874,
392
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
393
+ },
394
+ "iphone-17-pro": {
395
+ name: "iPhone 17 Pro",
396
+ width: 402,
397
+ height: 874,
398
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
399
+ },
400
+ "iphone-17-pro-max": {
401
+ name: "iPhone 17 Pro Max",
402
+ width: 440,
403
+ height: 956,
404
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
405
+ },
406
+ "iphone-17e": {
407
+ name: "iPhone 17e",
408
+ width: 390,
409
+ height: 844,
410
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
411
+ },
412
+ "iphone-air": {
413
+ name: "iPhone Air",
414
+ width: 420,
415
+ height: 912,
416
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
417
+ },
418
+ "iphone-se": {
419
+ name: "iPhone SE",
420
+ width: 375,
421
+ height: 667,
422
+ safeArea: { top: 20, right: 0, bottom: 0, left: 0 }
423
+ },
424
+ "pixel-8": {
425
+ name: "Pixel 8",
426
+ width: 412,
427
+ height: 915,
428
+ safeArea: { top: 32, right: 0, bottom: 24, left: 0 }
429
+ }
430
+ };
431
+ var DEFAULT_COUNTS = {
432
+ comments: 18,
433
+ likes: 128
434
+ };
435
+ var DEFAULT_SCORE = 12400;
436
+ var TOP_BAR_OFFSET = 12;
437
+ var TOP_CHROME_HEIGHT = 44;
438
+ var BODY_BACKGROUND = "#08090d";
439
+ var TEXT_PRIMARY = "#F6F9FB";
440
+ var TEXT_SECONDARY = "rgba(246,249,251,0.72)";
441
+ var TEXT_MUTED = "rgba(246,249,251,0.52)";
442
+ var BORDER = "rgba(255,255,255,0.14)";
443
+ var ACCENT = "#00A1E4";
444
+ var LIKE = "#ef4444";
445
+ var TROPHY = "#FBBF24";
446
+ var MAX_Z_INDEX = 2147483638;
447
+ var EMPTY_INSETS = {
448
+ top: 0,
449
+ right: 0,
450
+ bottom: 0,
451
+ left: 0
452
+ };
453
+ var BRIDGE_KEYS = [
454
+ "__OASIZ_SAFE_AREA_BOTTOM__",
455
+ "__OASIZ_SAFE_AREA_BOTTOM_PERCENT__",
456
+ "__OASIZ_SAFE_AREA_LEFT__",
457
+ "__OASIZ_SAFE_AREA_LEFT_PERCENT__",
458
+ "__OASIZ_SAFE_AREA_RIGHT__",
459
+ "__OASIZ_SAFE_AREA_RIGHT_PERCENT__",
460
+ "__OASIZ_SAFE_AREA_TOP__",
461
+ "__OASIZ_SAFE_AREA_TOP_PERCENT__",
462
+ "__OASIZ_VIEWPORT_INSETS__",
463
+ "__OASIZ_VIEWPORT_INSETS_PERCENT__",
464
+ "__oasizSetLeaderboardVisible",
465
+ "getSafeAreaBottom",
466
+ "getSafeAreaBottomPercent",
467
+ "getSafeAreaLeft",
468
+ "getSafeAreaLeftPercent",
469
+ "getSafeAreaRight",
470
+ "getSafeAreaRightPercent",
471
+ "getSafeAreaTop",
472
+ "getSafeAreaTopPercent",
473
+ "getViewportInsets",
474
+ "getViewportInsetsPercent"
475
+ ];
476
+ var NOOP_HANDLE = {
477
+ closeSheet() {
478
+ },
479
+ destroy() {
480
+ },
481
+ getViewportInsets() {
482
+ return { pixels: { ...EMPTY_INSETS }, percent: { ...EMPTY_INSETS } };
483
+ },
484
+ hide() {
485
+ },
486
+ isVisible() {
487
+ return false;
488
+ },
489
+ openComments() {
490
+ },
491
+ openLeaderboard() {
492
+ },
493
+ setCounts() {
494
+ },
495
+ setLeaderboardVisible() {
496
+ },
497
+ setLiked() {
498
+ },
499
+ show() {
500
+ },
501
+ triggerBack() {
502
+ },
503
+ triggerLeave() {
504
+ }
505
+ };
506
+ function getBrowserWindow() {
507
+ if (typeof window === "undefined") {
508
+ return void 0;
509
+ }
510
+ return window;
511
+ }
512
+ function getDocument() {
513
+ if (typeof document === "undefined") {
514
+ return void 0;
515
+ }
516
+ return document;
517
+ }
518
+ function warn(message) {
519
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
520
+ if (nodeEnv !== "production") {
521
+ console.warn("[oasiz/sdk] " + message);
522
+ }
523
+ }
524
+ function normalizeCount(value, fallback) {
525
+ if (typeof value !== "number" || !Number.isFinite(value)) {
526
+ return fallback;
527
+ }
528
+ return Math.max(0, Math.floor(value));
529
+ }
530
+ function formatCompactCount(value) {
531
+ if (value >= 1e6) {
532
+ return (value / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
533
+ }
534
+ if (value >= 1e3) {
535
+ return (value / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
536
+ }
537
+ return String(value);
538
+ }
539
+ function resolveDevice(device, orientation) {
540
+ const preset = typeof device === "string" ? DEVICE_PRESETS[device] : device ? {
541
+ name: device.name ?? "Custom phone",
542
+ width: device.width,
543
+ height: device.height,
544
+ safeArea: {
545
+ ...EMPTY_INSETS,
546
+ ...device.safeArea
547
+ }
548
+ } : DEVICE_PRESETS["iphone-17-pro-max"];
549
+ const normalized = {
550
+ name: preset.name,
551
+ width: Math.max(320, Math.floor(preset.width)),
552
+ height: Math.max(480, Math.floor(preset.height)),
553
+ safeArea: {
554
+ ...EMPTY_INSETS,
555
+ ...preset.safeArea
556
+ }
557
+ };
558
+ if (orientation === "landscape") {
559
+ return {
560
+ ...normalized,
561
+ width: Math.max(normalized.width, normalized.height),
562
+ height: Math.min(normalized.width, normalized.height),
563
+ safeArea: {
564
+ top: 0,
565
+ right: normalized.safeArea.top,
566
+ bottom: 21,
567
+ left: normalized.safeArea.top
568
+ }
569
+ };
570
+ }
571
+ return normalized;
572
+ }
573
+ function shouldFrame(frame, browserWindow, device) {
574
+ if (frame === true) {
575
+ return true;
576
+ }
577
+ if (frame === false) {
578
+ return false;
579
+ }
580
+ return browserWindow.innerWidth > device.width + 80 || browserWindow.innerHeight > device.height + 80;
581
+ }
582
+ function computeRect(browserWindow, device, frameEnabled) {
583
+ const viewportWidth = Math.max(320, browserWindow.innerWidth || device.width);
584
+ const viewportHeight = Math.max(480, browserWindow.innerHeight || device.height);
585
+ if (!frameEnabled) {
586
+ return {
587
+ left: 0,
588
+ top: 0,
589
+ width: viewportWidth,
590
+ height: viewportHeight,
591
+ scale: Math.min(viewportWidth / device.width, viewportHeight / device.height)
592
+ };
593
+ }
594
+ const margin = 24;
595
+ const scale = Math.min(
596
+ (viewportWidth - margin) / device.width,
597
+ (viewportHeight - margin) / device.height,
598
+ 1
599
+ );
600
+ const width = Math.round(device.width * scale);
601
+ const height = Math.round(device.height * scale);
602
+ return {
603
+ left: Math.round((viewportWidth - width) / 2),
604
+ top: Math.round((viewportHeight - height) / 2),
605
+ width,
606
+ height,
607
+ scale
608
+ };
609
+ }
610
+ function scaledInset(value, scale) {
611
+ if (typeof value !== "number" || !Number.isFinite(value)) {
612
+ return 0;
613
+ }
614
+ return Math.max(0, Math.round(value * scale));
615
+ }
616
+ function getSimulatorInsets(state) {
617
+ const safe = state.device.safeArea;
618
+ const top = scaledInset(safe.top, state.rect.scale) + Math.round((TOP_BAR_OFFSET + TOP_CHROME_HEIGHT) * state.rect.scale);
619
+ const pixels = {
620
+ top,
621
+ right: scaledInset(safe.right, state.rect.scale),
622
+ bottom: scaledInset(safe.bottom, state.rect.scale),
623
+ left: scaledInset(safe.left, state.rect.scale)
624
+ };
625
+ const percent = {
626
+ top: state.rect.height > 0 ? pixels.top / state.rect.height * 100 : 0,
627
+ right: state.rect.width > 0 ? pixels.right / state.rect.width * 100 : 0,
628
+ bottom: state.rect.height > 0 ? pixels.bottom / state.rect.height * 100 : 0,
629
+ left: state.rect.width > 0 ? pixels.left / state.rect.width * 100 : 0
630
+ };
631
+ return { pixels, percent };
632
+ }
633
+ function snapshotBridge(browserWindow) {
634
+ const globals = {};
635
+ for (const key of BRIDGE_KEYS) {
636
+ globals[key] = browserWindow[key];
637
+ }
638
+ return { globals };
639
+ }
640
+ function restoreBridge(browserWindow, snapshot) {
641
+ for (const key of BRIDGE_KEYS) {
642
+ const value = snapshot.globals[key];
643
+ if (typeof value === "undefined") {
644
+ delete browserWindow[key];
645
+ } else {
646
+ browserWindow[key] = value;
647
+ }
648
+ }
649
+ }
650
+ function installBridge(state) {
651
+ const browserWindow = getBrowserWindow();
652
+ if (!browserWindow) return;
653
+ const bridge = browserWindow;
654
+ function refreshInsets() {
655
+ const insets = getSimulatorInsets(state);
656
+ bridge.__OASIZ_VIEWPORT_INSETS__ = insets;
657
+ bridge.__OASIZ_VIEWPORT_INSETS_PERCENT__ = insets.percent;
658
+ bridge.__OASIZ_SAFE_AREA_TOP__ = insets.pixels.top;
659
+ bridge.__OASIZ_SAFE_AREA_RIGHT__ = insets.pixels.right;
660
+ bridge.__OASIZ_SAFE_AREA_BOTTOM__ = insets.pixels.bottom;
661
+ bridge.__OASIZ_SAFE_AREA_LEFT__ = insets.pixels.left;
662
+ bridge.__OASIZ_SAFE_AREA_TOP_PERCENT__ = insets.percent.top;
663
+ bridge.__OASIZ_SAFE_AREA_RIGHT_PERCENT__ = insets.percent.right;
664
+ bridge.__OASIZ_SAFE_AREA_BOTTOM_PERCENT__ = insets.percent.bottom;
665
+ bridge.__OASIZ_SAFE_AREA_LEFT_PERCENT__ = insets.percent.left;
666
+ return insets;
667
+ }
668
+ bridge.getViewportInsets = () => refreshInsets();
669
+ bridge.getViewportInsetsPercent = () => refreshInsets().percent;
670
+ bridge.getSafeAreaTop = () => refreshInsets().pixels.top;
671
+ bridge.getSafeAreaRight = () => refreshInsets().pixels.right;
672
+ bridge.getSafeAreaBottom = () => refreshInsets().pixels.bottom;
673
+ bridge.getSafeAreaLeft = () => refreshInsets().pixels.left;
674
+ bridge.getSafeAreaTopPercent = () => refreshInsets().percent.top;
675
+ bridge.getSafeAreaRightPercent = () => refreshInsets().percent.right;
676
+ bridge.getSafeAreaBottomPercent = () => refreshInsets().percent.bottom;
677
+ bridge.getSafeAreaLeftPercent = () => refreshInsets().percent.left;
678
+ bridge.__oasizSetLeaderboardVisible = (visible) => {
679
+ state.leaderboard.visible = visible;
680
+ renderSimulator(state);
681
+ };
682
+ refreshInsets();
683
+ }
684
+ function createStyleElement() {
685
+ const style = document.createElement("style");
686
+ style.setAttribute("data-oasiz-app-simulator", "styles");
687
+ style.textContent = [
688
+ ".oasiz-app-sim-button{appearance:none;border:0;margin:0;padding:0;font:inherit;color:inherit;background:transparent;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:manipulation}",
689
+ ".oasiz-app-sim-button:active{transform:scale(0.97)}",
690
+ ".oasiz-app-sim-glass{background:rgba(18,20,24,0.42);border:1px solid rgba(255,255,255,0.22);box-shadow:0 12px 30px rgba(0,0,0,0.28),inset 0 1px 0 rgba(255,255,255,0.18);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px)}",
691
+ ".oasiz-app-sim-scroll::-webkit-scrollbar{width:0;height:0}"
692
+ ].join("\n");
693
+ return style;
694
+ }
695
+ function applyTextStyle(element, size = 12, weight = 600) {
696
+ element.style.font = String(weight) + " " + String(size) + "px/1.2 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif";
697
+ }
698
+ function createDiv(className) {
699
+ const element = document.createElement("div");
700
+ if (className) {
701
+ element.className = className;
702
+ }
703
+ return element;
704
+ }
705
+ function createButton(label, title) {
706
+ const button = document.createElement("button");
707
+ button.type = "button";
708
+ button.className = "oasiz-app-sim-button";
709
+ button.setAttribute("aria-label", title);
710
+ button.title = title;
711
+ button.innerHTML = label;
712
+ return button;
713
+ }
714
+ function svgIcon(name, filled = false) {
715
+ if (name === "back") {
716
+ return '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"/><path d="m12 19-7-7 7-7"/></svg>';
717
+ }
718
+ if (name === "heart") {
719
+ return filled ? '<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor"><path d="M12 21s-7.4-4.4-9.7-9.1C.7 8.5 2.7 4.8 6.4 4.3c2-.3 3.8.7 5.6 2.8 1.8-2.1 3.6-3.1 5.6-2.8 3.7.5 5.7 4.2 4.1 7.6C19.4 16.6 12 21 12 21Z"/></svg>' : '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.8 4.6c-2-1.8-5.1-1.5-6.9.6L12 7.4l-1.9-2.2C8.3 3.1 5.2 2.8 3.2 4.6.8 6.8.7 10.5 3 12.9L12 21l9-8.1c2.3-2.4 2.2-6.1-.2-8.3Z"/></svg>';
720
+ }
721
+ if (name === "chat") {
722
+ return filled ? '<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor"><path d="M4 5.5A3.5 3.5 0 0 1 7.5 2h9A3.5 3.5 0 0 1 20 5.5v7A3.5 3.5 0 0 1 16.5 16H9l-4.2 3.1A1.1 1.1 0 0 1 3 18.2V5.5Z"/></svg>' : '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5.5A3.5 3.5 0 0 1 7.5 2h9A3.5 3.5 0 0 1 20 5.5v7A3.5 3.5 0 0 1 16.5 16H9l-4.2 3.1A1.1 1.1 0 0 1 3 18.2V5.5Z"/></svg>';
723
+ }
724
+ if (name === "trophy") {
725
+ return '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7 3h10v3h3a1 1 0 0 1 1 1c0 3.2-1.8 5.5-4.6 6.1A5 5 0 0 1 13 15.9V19h3v2H8v-2h3v-3.1a5 5 0 0 1-3.4-2.8C4.8 12.5 3 10.2 3 7a1 1 0 0 1 1-1h3V3Zm10 5v2.8c1.1-.5 1.8-1.5 2-2.8h-2ZM5 8c.2 1.3.9 2.3 2 2.8V8H5Z"/></svg>';
726
+ }
727
+ if (name === "share") {
728
+ return '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M12 16V4"/><path d="m7 9 5-5 5 5"/><path d="M5 14v5h14v-5"/></svg>';
729
+ }
730
+ return filled ? '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1Z"/></svg>' : '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1Z"/></svg>';
731
+ }
732
+ function setBoxStyle(element, state) {
733
+ const { rect } = state;
734
+ element.style.left = rect.left + "px";
735
+ element.style.top = rect.top + "px";
736
+ element.style.width = rect.width + "px";
737
+ element.style.height = rect.height + "px";
738
+ }
739
+ function createCircleButton(icon, title, onClick) {
740
+ const button = createButton(icon, title);
741
+ button.classList.add("oasiz-app-sim-glass");
742
+ button.style.cssText += [
743
+ "position:absolute",
744
+ "width:44px",
745
+ "height:44px",
746
+ "border-radius:999px",
747
+ "display:flex",
748
+ "align-items:center",
749
+ "justify-content:center",
750
+ "color:" + TEXT_PRIMARY,
751
+ "pointer-events:auto"
752
+ ].join(";");
753
+ button.addEventListener("click", (event) => {
754
+ event.preventDefault();
755
+ event.stopPropagation();
756
+ onClick();
757
+ });
758
+ return button;
759
+ }
760
+ function createHubPill(state) {
761
+ const button = createButton("", "Open comments");
762
+ button.classList.add("oasiz-app-sim-glass");
763
+ button.style.cssText += [
764
+ "position:absolute",
765
+ "right:16px",
766
+ "height:44px",
767
+ "min-width:94px",
768
+ "border-radius:999px",
769
+ "display:flex",
770
+ "align-items:center",
771
+ "justify-content:center",
772
+ "gap:10px",
773
+ "padding:0 14px",
774
+ "color:" + TEXT_PRIMARY,
775
+ "pointer-events:auto"
776
+ ].join(";");
777
+ const likeColor = state.wasLiked ? LIKE : TEXT_PRIMARY;
778
+ button.innerHTML = '<span style="display:inline-flex;align-items:center;gap:4px;color:' + likeColor + '">' + svgIcon("heart", state.wasLiked) + "<span>" + formatCompactCount(state.counts.likes) + '</span></span><span style="display:inline-flex;align-items:center;gap:4px;color:' + ACCENT + '">' + svgIcon("chat", true) + "<span>" + formatCompactCount(state.counts.comments) + "</span></span>";
779
+ applyTextStyle(button, 12, 700);
780
+ button.addEventListener("click", (event) => {
781
+ event.preventDefault();
782
+ event.stopPropagation();
783
+ state.sheet = "comments";
784
+ renderSimulator(state);
785
+ });
786
+ return button;
787
+ }
788
+ function createLeaderboardPill(state) {
789
+ const button = createButton("", "Open leaderboard");
790
+ button.classList.add("oasiz-app-sim-glass");
791
+ button.style.cssText += [
792
+ "position:absolute",
793
+ "left:50%",
794
+ "height:44px",
795
+ "min-width:118px",
796
+ "border-radius:999px",
797
+ "display:" + (state.leaderboard.visible ? "flex" : "none"),
798
+ "align-items:center",
799
+ "justify-content:center",
800
+ "gap:8px",
801
+ "padding:0 14px",
802
+ "color:" + TEXT_PRIMARY,
803
+ "pointer-events:auto",
804
+ "transform:translateX(-50%)"
805
+ ].join(";");
806
+ button.innerHTML = '<span style="color:' + TROPHY + ';display:inline-flex">' + svgIcon("trophy", true) + '</span><span style="min-width:0">' + formatCompactCount(state.leaderboard.score) + "</span>";
807
+ applyTextStyle(button, 14, 800);
808
+ button.addEventListener("click", (event) => {
809
+ event.preventDefault();
810
+ event.stopPropagation();
811
+ state.sheet = "leaderboard";
812
+ renderSimulator(state);
813
+ });
814
+ return button;
815
+ }
816
+ function createToolbarButton(icon, count, color, title, onClick) {
817
+ const button = createButton("", title);
818
+ button.classList.add("oasiz-app-sim-glass");
819
+ button.style.cssText += [
820
+ "height:42px",
821
+ "min-width:42px",
822
+ "border-radius:999px",
823
+ "display:flex",
824
+ "align-items:center",
825
+ "justify-content:center",
826
+ "gap:6px",
827
+ "padding:0 12px",
828
+ "color:" + color,
829
+ "pointer-events:auto"
830
+ ].join(";");
831
+ button.innerHTML = icon + (count === null ? "" : '<span style="color:' + color + ';min-width:0">' + formatCompactCount(count) + "</span>");
832
+ applyTextStyle(button, 13, 700);
833
+ button.addEventListener("click", (event) => {
834
+ event.preventDefault();
835
+ event.stopPropagation();
836
+ onClick();
837
+ });
838
+ return button;
839
+ }
840
+ function createSheet(state) {
841
+ if (!state.sheet) {
842
+ return null;
843
+ }
844
+ const overlay = createDiv();
845
+ overlay.style.cssText = [
846
+ "position:absolute",
847
+ "left:0",
848
+ "top:0",
849
+ "right:0",
850
+ "bottom:0",
851
+ "display:flex",
852
+ "align-items:center",
853
+ "justify-content:center",
854
+ "padding:" + String(scaledInset(state.device.safeArea.top, state.rect.scale) + 72) + "px 12px " + String(Math.max(18, scaledInset(state.device.safeArea.bottom, state.rect.scale) + 18)) + "px",
855
+ "background:rgba(0,0,0,0.34)",
856
+ "pointer-events:auto"
857
+ ].join(";");
858
+ overlay.addEventListener("click", () => {
859
+ state.sheet = null;
860
+ renderSimulator(state);
861
+ });
862
+ const sheet = createDiv();
863
+ sheet.style.cssText = [
864
+ "position:relative",
865
+ "width:min(100%, 408px)",
866
+ "max-height:100%",
867
+ "min-height:min(390px, 64%)",
868
+ "display:flex",
869
+ "flex-direction:column",
870
+ "border-radius:24px",
871
+ "border:1px solid " + BORDER,
872
+ "background:linear-gradient(180deg, rgba(18,20,24,0.97), rgba(9,11,15,0.98))",
873
+ "box-shadow:0 30px 80px rgba(0,0,0,0.52), inset 0 1px 0 rgba(255,255,255,0.10)",
874
+ "overflow:hidden",
875
+ "pointer-events:auto"
876
+ ].join(";");
877
+ sheet.addEventListener("click", (event) => {
878
+ event.stopPropagation();
879
+ });
880
+ const handle = createDiv();
881
+ handle.style.cssText = [
882
+ "width:42px",
883
+ "height:5px",
884
+ "border-radius:999px",
885
+ "background:rgba(255,255,255,0.28)",
886
+ "align-self:center",
887
+ "margin:10px 0 2px"
888
+ ].join(";");
889
+ const header = createDiv();
890
+ header.style.cssText = [
891
+ "display:flex",
892
+ "align-items:center",
893
+ "justify-content:space-between",
894
+ "gap:8px",
895
+ "min-height:62px",
896
+ "padding:6px 14px 10px",
897
+ "border-bottom:1px solid " + BORDER
898
+ ].join(";");
899
+ const left = createDiv();
900
+ left.style.cssText = "display:flex;align-items:center;gap:8px;min-width:112px";
901
+ const title = createDiv();
902
+ title.textContent = state.sheet === "comments" ? String(state.counts.comments) + " comments" : "Leaderboard";
903
+ title.style.cssText = [
904
+ "flex:1",
905
+ "min-width:0",
906
+ "text-align:center",
907
+ "color:" + TEXT_PRIMARY
908
+ ].join(";");
909
+ applyTextStyle(title, 14, 700);
910
+ const right = createDiv();
911
+ right.style.cssText = "display:flex;align-items:center;justify-content:flex-end;gap:8px;min-width:112px";
912
+ const back = createToolbarButton(svgIcon("back"), null, TEXT_PRIMARY, "Close", () => {
913
+ state.sheet = null;
914
+ renderSimulator(state);
915
+ });
916
+ back.style.width = "36px";
917
+ back.style.height = "36px";
918
+ back.style.padding = "0";
919
+ left.appendChild(back);
920
+ if (state.sheet === "comments") {
921
+ const like = createToolbarButton(
922
+ svgIcon("heart", state.wasLiked),
923
+ state.counts.likes,
924
+ state.wasLiked ? LIKE : TEXT_PRIMARY,
925
+ "Like game",
926
+ () => {
927
+ state.wasLiked = !state.wasLiked;
928
+ state.counts.likes = Math.max(0, state.counts.likes + (state.wasLiked ? 1 : -1));
929
+ renderSimulator(state);
930
+ }
931
+ );
932
+ left.appendChild(like);
933
+ } else {
934
+ const trophy = createToolbarButton(svgIcon("trophy", true), null, TROPHY, "Leaderboard", () => {
935
+ });
936
+ trophy.style.width = "36px";
937
+ trophy.style.height = "36px";
938
+ trophy.style.padding = "0";
939
+ left.appendChild(trophy);
940
+ }
941
+ const share2 = createToolbarButton(svgIcon("share"), null, TEXT_PRIMARY, "Share", () => {
942
+ });
943
+ const save = createToolbarButton(svgIcon("bookmark"), null, TEXT_PRIMARY, "Save", () => {
944
+ });
945
+ right.appendChild(share2);
946
+ right.appendChild(save);
947
+ header.appendChild(left);
948
+ header.appendChild(title);
949
+ header.appendChild(right);
950
+ const body = createDiv("oasiz-app-sim-scroll");
951
+ body.style.cssText = [
952
+ "display:flex",
953
+ "flex-direction:column",
954
+ "gap:10px",
955
+ "overflow:auto",
956
+ "padding:14px 16px calc(" + String(Math.max(16, scaledInset(state.device.safeArea.bottom, state.rect.scale))) + "px + 14px)",
957
+ "min-height:0"
958
+ ].join(";");
959
+ if (state.sheet === "leaderboard") {
960
+ appendLeaderboardBody(body);
961
+ } else {
962
+ appendCommentsBody(body, state);
963
+ }
964
+ sheet.appendChild(handle);
965
+ sheet.appendChild(header);
966
+ sheet.appendChild(body);
967
+ overlay.appendChild(sheet);
968
+ return overlay;
969
+ }
970
+ function appendLeaderboardBody(body) {
971
+ const tabs = createDiv();
972
+ tabs.style.cssText = [
973
+ "display:grid",
974
+ "grid-template-columns:repeat(3,1fr)",
975
+ "gap:6px",
976
+ "padding:4px",
977
+ "border-radius:14px",
978
+ "background:rgba(255,255,255,0.07)"
979
+ ].join(";");
980
+ for (const label of ["Weekly", "Global", "Friends"]) {
981
+ const tab = createDiv();
982
+ tab.textContent = label;
983
+ tab.style.cssText = [
984
+ "border-radius:10px",
985
+ "padding:8px 6px",
986
+ "text-align:center",
987
+ "color:" + (label === "Weekly" ? TEXT_PRIMARY : TEXT_MUTED),
988
+ "background:" + (label === "Weekly" ? "rgba(0,161,228,0.28)" : "transparent")
989
+ ].join(";");
990
+ applyTextStyle(tab, 12, 700);
991
+ tabs.appendChild(tab);
992
+ }
993
+ body.appendChild(tabs);
994
+ const entries = [
995
+ ["1", "Nova", "24.8k"],
996
+ ["2", "You", "12.4k"],
997
+ ["3", "Mika", "9.7k"],
998
+ ["4", "Ari", "8.1k"]
999
+ ];
1000
+ for (const [rank, name, score] of entries) {
1001
+ const row = createDiv();
1002
+ row.style.cssText = [
1003
+ "display:grid",
1004
+ "grid-template-columns:34px 1fr auto",
1005
+ "align-items:center",
1006
+ "gap:10px",
1007
+ "min-height:58px",
1008
+ "padding:10px 12px",
1009
+ "border-radius:14px",
1010
+ "background:" + (name === "You" ? "rgba(0,161,228,0.13)" : "rgba(255,255,255,0.06)"),
1011
+ "border:1px solid " + (name === "You" ? "rgba(0,161,228,0.28)" : "rgba(255,255,255,0.08)")
1012
+ ].join(";");
1013
+ const rankEl = createDiv();
1014
+ rankEl.textContent = rank;
1015
+ rankEl.style.cssText = "color:" + (rank === "1" ? TROPHY : TEXT_MUTED) + ";text-align:center";
1016
+ applyTextStyle(rankEl, 14, 800);
1017
+ const nameEl = createDiv();
1018
+ nameEl.textContent = name;
1019
+ nameEl.style.cssText = "color:" + TEXT_PRIMARY + ";min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
1020
+ applyTextStyle(nameEl, 14, 700);
1021
+ const scoreEl = createDiv();
1022
+ scoreEl.textContent = score;
1023
+ scoreEl.style.cssText = "color:" + TEXT_SECONDARY;
1024
+ applyTextStyle(scoreEl, 13, 700);
1025
+ row.appendChild(rankEl);
1026
+ row.appendChild(nameEl);
1027
+ row.appendChild(scoreEl);
1028
+ body.appendChild(row);
1029
+ }
1030
+ }
1031
+ function appendCommentsBody(body, state) {
1032
+ const toolbar = createDiv();
1033
+ toolbar.style.cssText = [
1034
+ "display:flex",
1035
+ "align-items:center",
1036
+ "gap:8px",
1037
+ "height:48px"
1038
+ ].join(";");
1039
+ toolbar.appendChild(
1040
+ createToolbarButton(
1041
+ svgIcon("heart", state.wasLiked),
1042
+ state.counts.likes,
1043
+ state.wasLiked ? LIKE : TEXT_PRIMARY,
1044
+ "Like game",
1045
+ () => {
1046
+ state.wasLiked = !state.wasLiked;
1047
+ state.counts.likes = Math.max(0, state.counts.likes + (state.wasLiked ? 1 : -1));
1048
+ renderSimulator(state);
1049
+ }
1050
+ )
1051
+ );
1052
+ toolbar.appendChild(
1053
+ createToolbarButton(svgIcon("chat", true), state.counts.comments, ACCENT, "Comments", () => {
1054
+ })
1055
+ );
1056
+ toolbar.appendChild(createToolbarButton(svgIcon("share"), null, TEXT_PRIMARY, "Share", () => {
1057
+ }));
1058
+ toolbar.appendChild(createToolbarButton(svgIcon("bookmark"), null, TEXT_PRIMARY, "Save", () => {
1059
+ }));
1060
+ body.appendChild(toolbar);
1061
+ const comments = [
1062
+ ["You", "Can my score panel clear the top buttons?"],
1063
+ ["Nova", "This is a good spot to check pause and menu spacing."],
1064
+ ["Mika", "The app chrome stays above the game just like mobile."]
1065
+ ];
1066
+ for (const [author, text] of comments) {
1067
+ const row = createDiv();
1068
+ row.style.cssText = [
1069
+ "display:grid",
1070
+ "grid-template-columns:34px 1fr",
1071
+ "gap:10px",
1072
+ "padding:10px 0"
1073
+ ].join(";");
1074
+ const avatar = createDiv();
1075
+ avatar.textContent = author.charAt(0);
1076
+ avatar.style.cssText = [
1077
+ "width:34px",
1078
+ "height:34px",
1079
+ "border-radius:999px",
1080
+ "display:flex",
1081
+ "align-items:center",
1082
+ "justify-content:center",
1083
+ "background:rgba(0,161,228,0.28)",
1084
+ "color:" + TEXT_PRIMARY
1085
+ ].join(";");
1086
+ applyTextStyle(avatar, 13, 800);
1087
+ const message = createDiv();
1088
+ message.innerHTML = '<div style="color:' + TEXT_PRIMARY + ';font-weight:700;margin-bottom:3px">' + author + '</div><div style="color:' + TEXT_SECONDARY + '">' + text + "</div>";
1089
+ applyTextStyle(message, 13, 500);
1090
+ row.appendChild(avatar);
1091
+ row.appendChild(message);
1092
+ body.appendChild(row);
1093
+ }
1094
+ const composer = createDiv();
1095
+ composer.textContent = "Add comment...";
1096
+ composer.style.cssText = [
1097
+ "height:44px",
1098
+ "border-radius:18px",
1099
+ "display:flex",
1100
+ "align-items:center",
1101
+ "padding:0 14px",
1102
+ "background:rgba(255,255,255,0.08)",
1103
+ "border:1px solid rgba(255,255,255,0.10)",
1104
+ "color:" + TEXT_MUTED
1105
+ ].join(";");
1106
+ applyTextStyle(composer, 13, 600);
1107
+ body.appendChild(composer);
1108
+ }
1109
+ function renderSimulator(state) {
1110
+ if (state.wasDestroyed) {
1111
+ return;
1112
+ }
1113
+ const { root, stage, gameViewport } = state.elements;
1114
+ root.style.display = state.visible ? "block" : "none";
1115
+ setBoxStyle(stage, state);
1116
+ if (gameViewport) {
1117
+ setBoxStyle(gameViewport, state);
1118
+ }
1119
+ stage.replaceChildren();
1120
+ const top = scaledInset(state.device.safeArea.top, state.rect.scale) + Math.round(TOP_BAR_OFFSET * state.rect.scale);
1121
+ const back = createCircleButton(svgIcon("back"), "Back", () => {
1122
+ if (state.backHandle.isBackOverrideActive()) {
1123
+ state.backHandle.triggerBack();
1124
+ } else {
1125
+ state.backHandle.triggerLeave();
1126
+ }
1127
+ });
1128
+ back.style.left = Math.round(16 * state.rect.scale) + "px";
1129
+ back.style.top = top + "px";
1130
+ const leaderboard = createLeaderboardPill(state);
1131
+ leaderboard.style.top = top + "px";
1132
+ const hub = createHubPill(state);
1133
+ hub.style.top = top + "px";
1134
+ hub.style.right = Math.round(16 * state.rect.scale) + "px";
1135
+ stage.appendChild(back);
1136
+ stage.appendChild(leaderboard);
1137
+ stage.appendChild(hub);
1138
+ const sheet = createSheet(state);
1139
+ if (sheet) {
1140
+ stage.appendChild(sheet);
1141
+ }
1142
+ if (state.options.log) {
1143
+ console.info("[oasiz/sdk] App simulator rendered.", {
1144
+ device: state.device.name,
1145
+ frame: state.frameEnabled,
1146
+ insets: getSimulatorInsets(state)
1147
+ });
1148
+ }
1149
+ }
1150
+ function installFrame(doc, state) {
1151
+ if (!state.frameEnabled || !doc.body) {
1152
+ return;
1153
+ }
1154
+ const body = doc.body;
1155
+ const html = doc.documentElement;
1156
+ const viewport = state.elements.gameViewport;
1157
+ if (!viewport) {
1158
+ return;
1159
+ }
1160
+ html.style.cssText = [
1161
+ state.previousDocumentElementStyle ?? "",
1162
+ "width:100%",
1163
+ "height:100%",
1164
+ "background:" + BODY_BACKGROUND,
1165
+ "overflow:hidden"
1166
+ ].join(";");
1167
+ body.style.cssText = [
1168
+ state.previousBodyStyle ?? "",
1169
+ "width:100%",
1170
+ "height:100%",
1171
+ "margin:0",
1172
+ "background:" + BODY_BACKGROUND,
1173
+ "overflow:hidden"
1174
+ ].join(";");
1175
+ const candidates = Array.from(body.children).filter(
1176
+ (node) => shouldMoveIntoGameViewport(node, state.elements)
1177
+ );
1178
+ for (const node of candidates) {
1179
+ state.movedNodes.push(node);
1180
+ viewport.appendChild(node);
1181
+ }
1182
+ body.appendChild(viewport);
1183
+ }
1184
+ function shouldMoveIntoGameViewport(node, elements) {
1185
+ if (node === elements.root || node === elements.style || node === elements.gameViewport) {
1186
+ return false;
1187
+ }
1188
+ const tagName = node.tagName.toUpperCase();
1189
+ if (tagName === "SCRIPT" || tagName === "STYLE" || tagName === "LINK" || tagName === "META" || tagName === "NOSCRIPT") {
1190
+ return false;
1191
+ }
1192
+ return node.getAttribute("data-oasiz-app-simulator") !== "true";
1193
+ }
1194
+ function restoreFrame(doc, state) {
1195
+ const body = doc.body;
1196
+ if (!body) {
1197
+ return;
1198
+ }
1199
+ for (const node of state.movedNodes) {
1200
+ body.appendChild(node);
1201
+ }
1202
+ state.movedNodes = [];
1203
+ state.elements.gameViewport?.remove();
1204
+ if (state.previousBodyStyle === null) {
1205
+ body.removeAttribute("style");
1206
+ } else {
1207
+ body.setAttribute("style", state.previousBodyStyle);
1208
+ }
1209
+ if (state.previousDocumentElementStyle === null) {
1210
+ doc.documentElement.removeAttribute("style");
1211
+ } else {
1212
+ doc.documentElement.setAttribute("style", state.previousDocumentElementStyle);
1213
+ }
1214
+ }
1215
+ function createElements(frameEnabled) {
1216
+ const root = document.createElement("div");
1217
+ root.setAttribute("data-oasiz-app-simulator", "true");
1218
+ root.style.cssText = [
1219
+ "position:fixed",
1220
+ "inset:0",
1221
+ "z-index:" + String(MAX_Z_INDEX),
1222
+ "pointer-events:none",
1223
+ "display:block",
1224
+ "font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif"
1225
+ ].join(";");
1226
+ const stage = document.createElement("div");
1227
+ stage.setAttribute("data-oasiz-app-simulator", "stage");
1228
+ stage.style.cssText = [
1229
+ "position:absolute",
1230
+ "overflow:hidden",
1231
+ "pointer-events:none"
1232
+ ].join(";");
1233
+ root.appendChild(stage);
1234
+ const gameViewport = frameEnabled ? document.createElement("div") : null;
1235
+ if (gameViewport) {
1236
+ gameViewport.setAttribute("data-oasiz-app-simulator", "game-viewport");
1237
+ gameViewport.style.cssText = [
1238
+ "position:fixed",
1239
+ "z-index:0",
1240
+ "overflow:hidden",
1241
+ "background:#000",
1242
+ "border-radius:28px",
1243
+ "outline:1px solid rgba(255,255,255,0.28)",
1244
+ "box-shadow:0 28px 90px rgba(0,0,0,0.48),0 0 0 8px rgba(255,255,255,0.06),0 0 0 10px rgba(0,0,0,0.58)"
1245
+ ].join(";");
1246
+ }
1247
+ return {
1248
+ gameViewport,
1249
+ root,
1250
+ stage,
1251
+ style: createStyleElement()
1252
+ };
1253
+ }
1254
+ function installResizeHandler(browserWindow, state) {
1255
+ const resize = () => {
1256
+ state.rect = computeRect(browserWindow, state.device, state.frameEnabled);
1257
+ installBridge(state);
1258
+ renderSimulator(state);
1259
+ };
1260
+ browserWindow.addEventListener("resize", resize);
1261
+ browserWindow.addEventListener("orientationchange", resize);
1262
+ return () => {
1263
+ browserWindow.removeEventListener("resize", resize);
1264
+ browserWindow.removeEventListener("orientationchange", resize);
1265
+ };
1266
+ }
1267
+ function enableAppSimulator(options = {}) {
1268
+ if (options.enabled === false) {
1269
+ return NOOP_HANDLE;
1270
+ }
1271
+ const browserWindow = getBrowserWindow();
1272
+ const doc = getDocument();
1273
+ if (!browserWindow || !doc?.body || !doc.documentElement) {
1274
+ warn("enableAppSimulator requires a browser document.");
1275
+ return NOOP_HANDLE;
1276
+ }
1277
+ browserWindow.__oasizAppSimulatorHandle__?.destroy();
1278
+ const orientation = options.orientation ?? "portrait";
1279
+ const device = resolveDevice(options.device, orientation);
1280
+ const frameEnabled = shouldFrame(options.frame ?? true, browserWindow, device);
1281
+ const elements = createElements(frameEnabled);
1282
+ const rect = computeRect(browserWindow, device, frameEnabled);
1283
+ const backHandle = enableBackButtonTesting({
1284
+ browserHistory: options.browserHistoryBack ?? true,
1285
+ keyboard: options.keyboardBack ?? true,
1286
+ log: options.log === true
1287
+ });
1288
+ const state = {
1289
+ backHandle,
1290
+ bridgeSnapshot: snapshotBridge(browserWindow),
1291
+ cleanupResize: () => {
1292
+ },
1293
+ counts: {
1294
+ comments: normalizeCount(options.comments, DEFAULT_COUNTS.comments),
1295
+ likes: normalizeCount(options.likes, DEFAULT_COUNTS.likes)
1296
+ },
1297
+ device,
1298
+ elements,
1299
+ frameEnabled,
1300
+ leaderboard: {
1301
+ score: normalizeCount(options.score, DEFAULT_SCORE),
1302
+ visible: options.leaderboardVisible ?? true
1303
+ },
1304
+ movedNodes: [],
1305
+ options: {
1306
+ browserHistoryBack: options.browserHistoryBack ?? true,
1307
+ keyboardBack: options.keyboardBack ?? true,
1308
+ log: options.log === true,
1309
+ orientation,
1310
+ title: options.title ?? "Oasiz App Preview"
1311
+ },
1312
+ previousBodyStyle: doc.body.getAttribute("style"),
1313
+ previousDocumentElementStyle: doc.documentElement.getAttribute("style"),
1314
+ rect,
1315
+ sheet: null,
1316
+ visible: true,
1317
+ wasDestroyed: false,
1318
+ wasLiked: false
1319
+ };
1320
+ if (doc.head) {
1321
+ doc.head.appendChild(elements.style);
1322
+ } else {
1323
+ doc.body.appendChild(elements.style);
1324
+ }
1325
+ installFrame(doc, state);
1326
+ doc.body.appendChild(elements.root);
1327
+ installBridge(state);
1328
+ state.cleanupResize = installResizeHandler(browserWindow, state);
1329
+ const handle = {
1330
+ closeSheet: () => {
1331
+ state.sheet = null;
1332
+ renderSimulator(state);
1333
+ },
1334
+ destroy: () => {
1335
+ if (state.wasDestroyed) return;
1336
+ state.wasDestroyed = true;
1337
+ state.cleanupResize();
1338
+ state.backHandle.destroy();
1339
+ restoreBridge(browserWindow, state.bridgeSnapshot);
1340
+ delete browserWindow.__oasizAppSimulatorHandle__;
1341
+ elements.root.remove();
1342
+ elements.style.remove();
1343
+ restoreFrame(doc, state);
1344
+ },
1345
+ getViewportInsets: () => getSimulatorInsets(state),
1346
+ hide: () => {
1347
+ state.visible = false;
1348
+ renderSimulator(state);
1349
+ },
1350
+ isVisible: () => state.visible,
1351
+ openComments: () => {
1352
+ state.sheet = "comments";
1353
+ renderSimulator(state);
1354
+ },
1355
+ openLeaderboard: () => {
1356
+ state.sheet = "leaderboard";
1357
+ renderSimulator(state);
1358
+ },
1359
+ setCounts: (counts) => {
1360
+ state.counts.comments = normalizeCount(counts.comments, state.counts.comments);
1361
+ state.counts.likes = normalizeCount(counts.likes, state.counts.likes);
1362
+ renderSimulator(state);
1363
+ },
1364
+ setLeaderboardVisible: (visible) => {
1365
+ state.leaderboard.visible = visible;
1366
+ renderSimulator(state);
1367
+ },
1368
+ setLiked: (liked) => {
1369
+ if (state.wasLiked === liked) {
1370
+ return;
1371
+ }
1372
+ state.wasLiked = liked;
1373
+ state.counts.likes = Math.max(0, state.counts.likes + (liked ? 1 : -1));
1374
+ renderSimulator(state);
1375
+ },
1376
+ show: () => {
1377
+ state.visible = true;
1378
+ renderSimulator(state);
1379
+ },
1380
+ triggerBack: () => state.backHandle.triggerBack(),
1381
+ triggerLeave: () => state.backHandle.triggerLeave()
1382
+ };
1383
+ browserWindow.__oasizAppSimulatorHandle__ = handle;
1384
+ renderSimulator(state);
1385
+ return handle;
1386
+ }
1387
+
1388
+ // src/character.ts
1389
+ function isDevelopment2() {
1390
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1391
+ return nodeEnv !== "production";
1392
+ }
1393
+ function getBridgeWindow2() {
1394
+ if (typeof window === "undefined") {
1395
+ return void 0;
1396
+ }
1397
+ return window;
1398
+ }
1399
+ function warnMissingBridge2(methodName) {
1400
+ if (isDevelopment2()) {
1401
+ console.warn(
1402
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1403
+ );
1404
+ }
1405
+ }
1406
+ async function getPlayerCharacter() {
1407
+ const bridge = getBridgeWindow2();
1408
+ if (typeof bridge?.__oasizGetPlayerCharacter !== "function") {
1409
+ warnMissingBridge2("getPlayerCharacter");
1410
+ return null;
1411
+ }
1412
+ try {
1413
+ const result = await bridge.__oasizGetPlayerCharacter();
1414
+ return result ?? null;
1415
+ } catch (error) {
1416
+ if (isDevelopment2()) {
1417
+ console.error("[oasiz/sdk] getPlayerCharacter failed:", error);
1418
+ }
1419
+ return null;
1420
+ }
1421
+ }
1422
+
1423
+ // src/haptics.ts
1424
+ function isDevelopment3() {
1425
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1426
+ return nodeEnv !== "production";
1427
+ }
1428
+ function getBridgeWindow3() {
1429
+ if (typeof window === "undefined") {
1430
+ return void 0;
1431
+ }
1432
+ return window;
1433
+ }
1434
+ function triggerHaptic(type) {
1435
+ const bridge = getBridgeWindow3();
1436
+ if (typeof bridge?.triggerHaptic === "function") {
1437
+ bridge.triggerHaptic(type);
1438
+ return;
1439
+ }
1440
+ if (isDevelopment3()) {
1441
+ console.warn(
1442
+ "[oasiz/sdk] triggerHaptic bridge is unavailable. This is expected in local development."
1443
+ );
1444
+ }
1445
+ }
1446
+
1447
+ // src/log-overlay.ts
1448
+ var CONSOLE_METHODS = [
1449
+ "debug",
1450
+ "log",
1451
+ "info",
1452
+ "warn",
1453
+ "error"
1454
+ ];
1455
+ var DEFAULT_MAX_ENTRIES = 200;
1456
+ var DEFAULT_TITLE = "SDK Logs";
1457
+ var OVERLAY_MARGIN = 12;
1458
+ var DEFAULT_COLLAPSED_WIDTH = 156;
1459
+ var DEFAULT_COLLAPSED_HEIGHT = 52;
1460
+ var DEFAULT_EXPANDED_WIDTH = 565;
1461
+ var DEFAULT_EXPANDED_HEIGHT = 372;
1462
+ var DRAG_THRESHOLD_PX = 6;
1463
+ var MIN_EXPANDED_WIDTH = 160;
1464
+ var MIN_EXPANDED_HEIGHT = 110;
1465
+ var RESIZE_HOTSPOT_PX = 28;
1466
+ var TOP_DRAG_ZONE_PX = 44;
1467
+ var NOOP_HANDLE2 = {
1468
+ clear() {
1469
+ },
1470
+ destroy() {
1471
+ },
1472
+ hide() {
1473
+ },
1474
+ isVisible() {
1475
+ return false;
1476
+ },
1477
+ show() {
1478
+ }
1479
+ };
1480
+ function getBrowserWindow2() {
7
1481
  if (typeof window === "undefined") {
8
1482
  return void 0;
9
1483
  }
10
1484
  return window;
11
1485
  }
12
- function triggerHaptic(type) {
13
- const bridge = getBridgeWindow();
14
- if (typeof bridge?.triggerHaptic === "function") {
15
- bridge.triggerHaptic(type);
1486
+ function getDocument2() {
1487
+ if (typeof document === "undefined") {
1488
+ return void 0;
1489
+ }
1490
+ return document;
1491
+ }
1492
+ function clampMaxEntries(value) {
1493
+ if (!Number.isFinite(value)) {
1494
+ return DEFAULT_MAX_ENTRIES;
1495
+ }
1496
+ return Math.max(10, Math.floor(value));
1497
+ }
1498
+ function createConsoleSnapshot() {
1499
+ const fallback = console.log.bind(console);
1500
+ return {
1501
+ debug: typeof console.debug === "function" ? console.debug.bind(console) : fallback,
1502
+ log: fallback,
1503
+ info: typeof console.info === "function" ? console.info.bind(console) : fallback,
1504
+ warn: typeof console.warn === "function" ? console.warn.bind(console) : fallback,
1505
+ error: typeof console.error === "function" ? console.error.bind(console) : fallback
1506
+ };
1507
+ }
1508
+ function formatTimestamp(timestamp) {
1509
+ const date = new Date(timestamp);
1510
+ const hours = String(date.getHours()).padStart(2, "0");
1511
+ const minutes = String(date.getMinutes()).padStart(2, "0");
1512
+ const seconds = String(date.getSeconds()).padStart(2, "0");
1513
+ const milliseconds = String(date.getMilliseconds()).padStart(3, "0");
1514
+ return "[" + hours + ":" + minutes + ":" + seconds + "." + milliseconds + "]";
1515
+ }
1516
+ function safeStringify(value) {
1517
+ const seen = /* @__PURE__ */ new WeakSet();
1518
+ try {
1519
+ return JSON.stringify(
1520
+ value,
1521
+ (_key, candidate) => {
1522
+ if (typeof candidate === "bigint") {
1523
+ return candidate.toString() + "n";
1524
+ }
1525
+ if (typeof candidate === "object" && candidate !== null) {
1526
+ if (seen.has(candidate)) {
1527
+ return "[Circular]";
1528
+ }
1529
+ seen.add(candidate);
1530
+ }
1531
+ return candidate;
1532
+ },
1533
+ 2
1534
+ ) ?? String(value);
1535
+ } catch {
1536
+ return String(value);
1537
+ }
1538
+ }
1539
+ function formatArg(value) {
1540
+ if (typeof value === "string") {
1541
+ return value;
1542
+ }
1543
+ if (value instanceof Error) {
1544
+ if (value.stack) {
1545
+ return value.stack;
1546
+ }
1547
+ return value.name + ": " + value.message;
1548
+ }
1549
+ if (typeof value === "undefined") {
1550
+ return "undefined";
1551
+ }
1552
+ if (typeof value === "function") {
1553
+ return "[Function " + (value.name || "anonymous") + "]";
1554
+ }
1555
+ return safeStringify(value);
1556
+ }
1557
+ function formatEntryMessage(args) {
1558
+ const message = args.map(formatArg).join(" ");
1559
+ if (message.length <= 4e3) {
1560
+ return message;
1561
+ }
1562
+ return message.slice(0, 3997) + "...";
1563
+ }
1564
+ function createEntry(level, args, id) {
1565
+ return {
1566
+ id,
1567
+ level,
1568
+ message: formatEntryMessage(args),
1569
+ timestamp: Date.now()
1570
+ };
1571
+ }
1572
+ function createButton2(label) {
1573
+ const button = document.createElement("button");
1574
+ button.type = "button";
1575
+ button.textContent = label;
1576
+ button.style.cssText = [
1577
+ "appearance:none",
1578
+ "border:1px solid rgba(255,255,255,0.18)",
1579
+ "background:rgba(255,255,255,0.06)",
1580
+ "color:#f8fafc",
1581
+ "border-radius:999px",
1582
+ "padding:6px 10px",
1583
+ "font:600 12px/1.1 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
1584
+ "cursor:pointer"
1585
+ ].join(";");
1586
+ return button;
1587
+ }
1588
+ function getLevelAccent(level) {
1589
+ if (level === "error") {
1590
+ return {
1591
+ lineBackground: "rgba(255, 109, 122, 0.08)"
1592
+ };
1593
+ }
1594
+ if (level === "warn") {
1595
+ return {
1596
+ lineBackground: "rgba(255, 196, 94, 0.07)"
1597
+ };
1598
+ }
1599
+ if (level === "info") {
1600
+ return {
1601
+ lineBackground: "rgba(82, 187, 255, 0.07)"
1602
+ };
1603
+ }
1604
+ if (level === "debug") {
1605
+ return {
1606
+ lineBackground: "rgba(166, 137, 255, 0.07)"
1607
+ };
1608
+ }
1609
+ return {
1610
+ lineBackground: "rgba(117, 235, 191, 0.06)"
1611
+ };
1612
+ }
1613
+ function getViewportSize() {
1614
+ const browserWindow = getBrowserWindow2();
1615
+ return {
1616
+ width: Math.max(320, browserWindow?.innerWidth ?? 1280),
1617
+ height: Math.max(240, browserWindow?.innerHeight ?? 720)
1618
+ };
1619
+ }
1620
+ function clampPanelSize(size) {
1621
+ const viewport = getViewportSize();
1622
+ const maxWidth = Math.max(MIN_EXPANDED_WIDTH, viewport.width - OVERLAY_MARGIN * 2);
1623
+ const maxHeight = Math.max(
1624
+ MIN_EXPANDED_HEIGHT,
1625
+ viewport.height - OVERLAY_MARGIN * 2
1626
+ );
1627
+ return {
1628
+ width: Math.min(maxWidth, Math.max(MIN_EXPANDED_WIDTH, size.width)),
1629
+ height: Math.min(maxHeight, Math.max(MIN_EXPANDED_HEIGHT, size.height))
1630
+ };
1631
+ }
1632
+ function getOverlaySize(state) {
1633
+ if (state.expanded && state.panelSize) {
1634
+ return state.panelSize;
1635
+ }
1636
+ const rect = state.ui?.root && typeof state.ui.root.getBoundingClientRect === "function" ? state.ui.root.getBoundingClientRect() : null;
1637
+ if (rect && Number.isFinite(rect.width) && Number.isFinite(rect.height)) {
1638
+ return {
1639
+ width: Math.max(1, rect.width),
1640
+ height: Math.max(1, rect.height)
1641
+ };
1642
+ }
1643
+ return state.expanded ? clampPanelSize({
1644
+ width: DEFAULT_EXPANDED_WIDTH,
1645
+ height: DEFAULT_EXPANDED_HEIGHT
1646
+ }) : { width: DEFAULT_COLLAPSED_WIDTH, height: DEFAULT_COLLAPSED_HEIGHT };
1647
+ }
1648
+ function applyPanelSize(state) {
1649
+ if (!state.ui) {
16
1650
  return;
17
1651
  }
18
- if (isDevelopment()) {
19
- console.warn(
20
- "[oasiz/sdk] triggerHaptic bridge is unavailable. This is expected in local development."
1652
+ if (!state.expanded) {
1653
+ state.ui.root.style.width = "auto";
1654
+ state.ui.panel.style.width = "min(565px, calc(100vw - 24px))";
1655
+ state.ui.panel.style.height = "auto";
1656
+ state.ui.entries.style.maxHeight = "min(36vh, 280px)";
1657
+ return;
1658
+ }
1659
+ if (!state.panelSize) {
1660
+ state.ui.root.style.width = "min(565px, calc(100vw - 24px))";
1661
+ state.ui.panel.style.width = "min(565px, calc(100vw - 24px))";
1662
+ state.ui.panel.style.height = "auto";
1663
+ state.ui.entries.style.maxHeight = "min(36vh, 280px)";
1664
+ return;
1665
+ }
1666
+ const nextSize = clampPanelSize(
1667
+ state.panelSize
1668
+ );
1669
+ state.panelSize = nextSize;
1670
+ state.ui.root.style.width = nextSize.width + "px";
1671
+ state.ui.panel.style.width = "100%";
1672
+ state.ui.panel.style.height = nextSize.height + "px";
1673
+ state.ui.entries.style.maxHeight = Math.max(72, nextSize.height - 88) + "px";
1674
+ }
1675
+ function clampPosition(point, state) {
1676
+ const viewport = getViewportSize();
1677
+ const size = getOverlaySize(state);
1678
+ const maxX = Math.max(OVERLAY_MARGIN, viewport.width - size.width - OVERLAY_MARGIN);
1679
+ const maxY = Math.max(
1680
+ OVERLAY_MARGIN,
1681
+ viewport.height - size.height - OVERLAY_MARGIN
1682
+ );
1683
+ return {
1684
+ x: Math.min(maxX, Math.max(OVERLAY_MARGIN, point.x)),
1685
+ y: Math.min(maxY, Math.max(OVERLAY_MARGIN, point.y))
1686
+ };
1687
+ }
1688
+ function applyOverlayPosition(state) {
1689
+ if (!state.ui) {
1690
+ return;
1691
+ }
1692
+ if (!state.position) {
1693
+ const viewport = getViewportSize();
1694
+ const size = getOverlaySize(state);
1695
+ state.position = clampPosition(
1696
+ {
1697
+ x: viewport.width - size.width - OVERLAY_MARGIN,
1698
+ y: viewport.height - size.height - OVERLAY_MARGIN
1699
+ },
1700
+ state
1701
+ );
1702
+ } else {
1703
+ state.position = clampPosition(state.position, state);
1704
+ }
1705
+ state.ui.root.style.left = state.position.x + "px";
1706
+ state.ui.root.style.top = state.position.y + "px";
1707
+ }
1708
+ function getPointFromMouseEvent(event) {
1709
+ return { x: event.clientX, y: event.clientY };
1710
+ }
1711
+ function getPointFromTouchEvent(event) {
1712
+ const touch = event.touches[0] ?? event.changedTouches[0];
1713
+ if (!touch) {
1714
+ return null;
1715
+ }
1716
+ return { x: touch.clientX, y: touch.clientY };
1717
+ }
1718
+ function stopDragging(state) {
1719
+ if (state.isDragging || state.isResizing) {
1720
+ state.suppressToggleClickUntil = Date.now() + 180;
1721
+ }
1722
+ state.isDragging = false;
1723
+ state.dragStartPoint = null;
1724
+ state.lastDragPoint = null;
1725
+ state.removeDragListeners?.();
1726
+ state.removeDragListeners = null;
1727
+ if (state.ui) {
1728
+ state.ui.panel.style.cursor = "default";
1729
+ state.ui.dragZone.style.cursor = "grab";
1730
+ state.ui.toggleButton.style.cursor = "grab";
1731
+ }
1732
+ }
1733
+ function stopResizing(state) {
1734
+ if (state.isResizing) {
1735
+ state.suppressToggleClickUntil = Date.now() + 180;
1736
+ }
1737
+ state.isResizing = false;
1738
+ state.resizeStartPoint = null;
1739
+ state.resizeStartSize = null;
1740
+ state.removeResizeListeners?.();
1741
+ state.removeResizeListeners = null;
1742
+ }
1743
+ function beginDragTracking(state, startPoint) {
1744
+ const doc = getDocument2();
1745
+ if (!doc) {
1746
+ return;
1747
+ }
1748
+ stopResizing(state);
1749
+ stopDragging(state);
1750
+ state.dragMoved = false;
1751
+ state.dragStartPoint = startPoint;
1752
+ state.lastDragPoint = startPoint;
1753
+ const handlePointerMove = (nextPoint) => {
1754
+ if (!state.dragStartPoint || !state.lastDragPoint || !nextPoint) {
1755
+ return;
1756
+ }
1757
+ if (!state.isDragging) {
1758
+ const deltaFromStartX = nextPoint.x - state.dragStartPoint.x;
1759
+ const deltaFromStartY = nextPoint.y - state.dragStartPoint.y;
1760
+ const distance = Math.sqrt(
1761
+ deltaFromStartX * deltaFromStartX + deltaFromStartY * deltaFromStartY
1762
+ );
1763
+ if (distance < DRAG_THRESHOLD_PX) {
1764
+ return;
1765
+ }
1766
+ state.isDragging = true;
1767
+ state.dragMoved = true;
1768
+ if (state.ui) {
1769
+ state.ui.panel.style.cursor = "grabbing";
1770
+ state.ui.dragZone.style.cursor = "grabbing";
1771
+ state.ui.toggleButton.style.cursor = "grabbing";
1772
+ }
1773
+ }
1774
+ const currentPosition = state.position ?? { x: OVERLAY_MARGIN, y: OVERLAY_MARGIN };
1775
+ state.position = clampPosition(
1776
+ {
1777
+ x: currentPosition.x + (nextPoint.x - state.lastDragPoint.x),
1778
+ y: currentPosition.y + (nextPoint.y - state.lastDragPoint.y)
1779
+ },
1780
+ state
21
1781
  );
1782
+ state.lastDragPoint = nextPoint;
1783
+ applyOverlayPosition(state);
1784
+ };
1785
+ const handleMouseMove = (event) => {
1786
+ handlePointerMove(getPointFromMouseEvent(event));
1787
+ };
1788
+ const handleTouchMove = (event) => {
1789
+ handlePointerMove(getPointFromTouchEvent(event));
1790
+ };
1791
+ const handleMouseUp = () => {
1792
+ stopDragging(state);
1793
+ };
1794
+ const handleTouchEnd = () => {
1795
+ stopDragging(state);
1796
+ };
1797
+ doc.addEventListener("mousemove", handleMouseMove);
1798
+ doc.addEventListener("mouseup", handleMouseUp);
1799
+ doc.addEventListener("touchmove", handleTouchMove, { passive: true });
1800
+ doc.addEventListener("touchend", handleTouchEnd);
1801
+ doc.addEventListener("touchcancel", handleTouchEnd);
1802
+ state.removeDragListeners = () => {
1803
+ doc.removeEventListener("mousemove", handleMouseMove);
1804
+ doc.removeEventListener("mouseup", handleMouseUp);
1805
+ doc.removeEventListener("touchmove", handleTouchMove);
1806
+ doc.removeEventListener("touchend", handleTouchEnd);
1807
+ doc.removeEventListener("touchcancel", handleTouchEnd);
1808
+ };
1809
+ }
1810
+ function startResizing(state, startPoint) {
1811
+ const doc = getDocument2();
1812
+ if (!doc) {
1813
+ return;
1814
+ }
1815
+ stopDragging(state);
1816
+ stopResizing(state);
1817
+ state.isResizing = true;
1818
+ state.resizeStartPoint = startPoint;
1819
+ state.resizeStartSize = state.panelSize ?? clampPanelSize({ width: DEFAULT_EXPANDED_WIDTH, height: DEFAULT_EXPANDED_HEIGHT });
1820
+ const handleResizeMove = (nextPoint) => {
1821
+ if (!state.isResizing || !state.resizeStartPoint || !state.resizeStartSize || !nextPoint) {
1822
+ return;
1823
+ }
1824
+ state.panelSize = clampPanelSize({
1825
+ width: state.resizeStartSize.width + (nextPoint.x - state.resizeStartPoint.x),
1826
+ height: state.resizeStartSize.height + (nextPoint.y - state.resizeStartPoint.y)
1827
+ });
1828
+ applyPanelSize(state);
1829
+ applyOverlayPosition(state);
1830
+ };
1831
+ const handleMouseMove = (event) => {
1832
+ handleResizeMove(getPointFromMouseEvent(event));
1833
+ };
1834
+ const handleTouchMove = (event) => {
1835
+ handleResizeMove(getPointFromTouchEvent(event));
1836
+ };
1837
+ const handleFinish = () => {
1838
+ stopResizing(state);
1839
+ };
1840
+ doc.addEventListener("mousemove", handleMouseMove);
1841
+ doc.addEventListener("mouseup", handleFinish);
1842
+ doc.addEventListener("touchmove", handleTouchMove, { passive: true });
1843
+ doc.addEventListener("touchend", handleFinish);
1844
+ doc.addEventListener("touchcancel", handleFinish);
1845
+ state.removeResizeListeners = () => {
1846
+ doc.removeEventListener("mousemove", handleMouseMove);
1847
+ doc.removeEventListener("mouseup", handleFinish);
1848
+ doc.removeEventListener("touchmove", handleTouchMove);
1849
+ doc.removeEventListener("touchend", handleFinish);
1850
+ doc.removeEventListener("touchcancel", handleFinish);
1851
+ };
1852
+ }
1853
+ function isInBottomRightResizeZone(element, point) {
1854
+ const rect = element.getBoundingClientRect();
1855
+ return point.x >= rect.right - RESIZE_HOTSPOT_PX && point.x <= rect.right && point.y >= rect.bottom - RESIZE_HOTSPOT_PX && point.y <= rect.bottom;
1856
+ }
1857
+ function canStartDragFromTarget(target) {
1858
+ if (!(target instanceof Element)) {
1859
+ return true;
1860
+ }
1861
+ if (target.closest("button") || target.closest("a") || target.closest("input") || target.closest("textarea") || target.closest("select")) {
1862
+ return false;
1863
+ }
1864
+ return true;
1865
+ }
1866
+ function isInTopDragZone(element, point, zoneHeight) {
1867
+ const rect = element.getBoundingClientRect();
1868
+ return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.top + zoneHeight;
1869
+ }
1870
+ function attachDragStartListeners(element, state, options = {}) {
1871
+ element.addEventListener("mousedown", (event) => {
1872
+ if (!options.allowInteractiveTarget && !canStartDragFromTarget(event.target)) {
1873
+ return;
1874
+ }
1875
+ const point = getPointFromMouseEvent(event);
1876
+ if (typeof options.topZoneHeight === "number" && !isInTopDragZone(element, point, options.topZoneHeight)) {
1877
+ return;
1878
+ }
1879
+ beginDragTracking(state, point);
1880
+ });
1881
+ element.addEventListener("touchstart", (event) => {
1882
+ if (!options.allowInteractiveTarget && !canStartDragFromTarget(event.target)) {
1883
+ return;
1884
+ }
1885
+ const point = getPointFromTouchEvent(event);
1886
+ if (!point) {
1887
+ return;
1888
+ }
1889
+ if (typeof options.topZoneHeight === "number" && !isInTopDragZone(element, point, options.topZoneHeight)) {
1890
+ return;
1891
+ }
1892
+ beginDragTracking(state, point);
1893
+ });
1894
+ }
1895
+ function attachCollapsedToggleListeners(element, state) {
1896
+ const startToggleInteraction = (startPoint) => {
1897
+ const doc = getDocument2();
1898
+ if (!doc) {
1899
+ return;
1900
+ }
1901
+ beginDragTracking(state, startPoint);
1902
+ const finishInteraction = () => {
1903
+ releaseListeners();
1904
+ if (state.dragMoved || Date.now() < state.suppressToggleClickUntil) {
1905
+ return;
1906
+ }
1907
+ state.expanded = true;
1908
+ state.unreadCount = 0;
1909
+ renderOverlay(state);
1910
+ };
1911
+ const releaseListeners = () => {
1912
+ doc.removeEventListener("mouseup", finishInteraction);
1913
+ doc.removeEventListener("touchend", finishInteraction);
1914
+ doc.removeEventListener("touchcancel", finishInteraction);
1915
+ };
1916
+ doc.addEventListener("mouseup", finishInteraction);
1917
+ doc.addEventListener("touchend", finishInteraction);
1918
+ doc.addEventListener("touchcancel", finishInteraction);
1919
+ };
1920
+ element.addEventListener("mousedown", (event) => {
1921
+ event.preventDefault();
1922
+ startToggleInteraction(getPointFromMouseEvent(event));
1923
+ });
1924
+ element.addEventListener("touchstart", (event) => {
1925
+ const point = getPointFromTouchEvent(event);
1926
+ if (!point) {
1927
+ return;
1928
+ }
1929
+ event.preventDefault();
1930
+ startToggleInteraction(point);
1931
+ });
1932
+ }
1933
+ function attachPanelResizeListeners(element, state) {
1934
+ element.addEventListener("mousedown", (event) => {
1935
+ if (!state.expanded || !canStartDragFromTarget(event.target)) {
1936
+ return;
1937
+ }
1938
+ const point = getPointFromMouseEvent(event);
1939
+ if (!isInBottomRightResizeZone(element, point)) {
1940
+ return;
1941
+ }
1942
+ event.preventDefault();
1943
+ event.stopPropagation();
1944
+ startResizing(state, point);
1945
+ });
1946
+ element.addEventListener("touchstart", (event) => {
1947
+ if (!state.expanded || !canStartDragFromTarget(event.target)) {
1948
+ return;
1949
+ }
1950
+ const point = getPointFromTouchEvent(event);
1951
+ if (!point || !isInBottomRightResizeZone(element, point)) {
1952
+ return;
1953
+ }
1954
+ event.preventDefault();
1955
+ event.stopPropagation();
1956
+ startResizing(state, point);
1957
+ });
1958
+ }
1959
+ function createOverlayUi(state) {
1960
+ const root = document.createElement("div");
1961
+ root.style.cssText = [
1962
+ "position:fixed",
1963
+ "left:12px",
1964
+ "top:12px",
1965
+ "z-index:2147483647",
1966
+ "display:flex",
1967
+ "flex-direction:column",
1968
+ "align-items:stretch",
1969
+ "gap:8px",
1970
+ "width:min(565px, calc(100vw - 24px))",
1971
+ "pointer-events:none"
1972
+ ].join(";");
1973
+ const toggleButton = createButton2("Logs");
1974
+ toggleButton.style.pointerEvents = "auto";
1975
+ toggleButton.style.alignSelf = "flex-end";
1976
+ toggleButton.style.display = "inline-flex";
1977
+ toggleButton.style.alignItems = "center";
1978
+ toggleButton.style.justifyContent = "center";
1979
+ toggleButton.style.minHeight = "40px";
1980
+ toggleButton.style.minWidth = "76px";
1981
+ toggleButton.style.padding = "8px 14px";
1982
+ toggleButton.style.textAlign = "center";
1983
+ toggleButton.style.border = "1px solid rgba(122, 212, 255, 0.22)";
1984
+ toggleButton.style.background = "linear-gradient(180deg, rgba(13,31,54,0.98), rgba(8,19,37,0.98))";
1985
+ toggleButton.style.boxShadow = "0 18px 40px rgba(4,12,24,0.34)";
1986
+ toggleButton.style.cursor = "grab";
1987
+ toggleButton.style.touchAction = "none";
1988
+ const panel = document.createElement("div");
1989
+ panel.style.cssText = [
1990
+ "position:relative",
1991
+ "display:flex",
1992
+ "flex-direction:column",
1993
+ "width:min(565px, calc(100vw - 24px))",
1994
+ "max-height:min(48vh, 372px)",
1995
+ "border-radius:18px",
1996
+ "border:1px solid rgba(116,167,255,0.16)",
1997
+ "background:linear-gradient(180deg, rgba(9,19,37,0.98), rgba(5,12,24,0.98))",
1998
+ "box-shadow:0 28px 64px rgba(2,8,18,0.46)",
1999
+ "backdrop-filter:blur(16px)",
2000
+ "overflow:hidden",
2001
+ "cursor:default",
2002
+ "pointer-events:auto"
2003
+ ].join(";");
2004
+ const dragZone = document.createElement("div");
2005
+ dragZone.style.cssText = [
2006
+ "position:absolute",
2007
+ "top:0",
2008
+ "left:0",
2009
+ "right:0",
2010
+ "height:" + String(TOP_DRAG_ZONE_PX) + "px",
2011
+ "z-index:1",
2012
+ "cursor:grab",
2013
+ "background:transparent",
2014
+ "pointer-events:auto"
2015
+ ].join(";");
2016
+ const controls = document.createElement("div");
2017
+ controls.style.cssText = [
2018
+ "position:absolute",
2019
+ "top:22px",
2020
+ "right:22px",
2021
+ "z-index:2",
2022
+ "display:flex",
2023
+ "align-items:center",
2024
+ "gap:8px",
2025
+ "pointer-events:auto"
2026
+ ].join(";");
2027
+ const clearButton = createButton2("Clear");
2028
+ clearButton.style.background = "rgba(255,255,255,0.1)";
2029
+ clearButton.style.border = "1px solid rgba(255,255,255,0.16)";
2030
+ clearButton.style.color = "#eef6ff";
2031
+ clearButton.style.minHeight = "30px";
2032
+ clearButton.style.padding = "4px 9px";
2033
+ clearButton.style.fontSize = "11px";
2034
+ clearButton.style.backdropFilter = "blur(8px)";
2035
+ const collapseButton = createButton2("Hide");
2036
+ collapseButton.style.background = "rgba(113, 171, 255, 0.12)";
2037
+ collapseButton.style.border = "1px solid rgba(113, 171, 255, 0.2)";
2038
+ collapseButton.style.color = "#d9ebff";
2039
+ collapseButton.style.minHeight = "30px";
2040
+ collapseButton.style.padding = "4px 9px";
2041
+ collapseButton.style.fontSize = "11px";
2042
+ collapseButton.style.backdropFilter = "blur(8px)";
2043
+ const body = document.createElement("div");
2044
+ body.style.cssText = [
2045
+ "position:relative",
2046
+ "display:flex",
2047
+ "flex-direction:column",
2048
+ "padding:12px",
2049
+ "background:linear-gradient(180deg, rgba(4,10,20,0.88), rgba(3,8,18,0.98))",
2050
+ "flex:1 1 auto",
2051
+ "min-height:0"
2052
+ ].join(";");
2053
+ const entries = document.createElement("div");
2054
+ entries.style.cssText = [
2055
+ "display:flex",
2056
+ "flex-direction:column",
2057
+ "gap:0",
2058
+ "overflow:auto",
2059
+ "flex:1 1 auto",
2060
+ "min-height:96px",
2061
+ "max-height:min(36vh, 280px)",
2062
+ "padding:0",
2063
+ "border:1px solid rgba(115,153,212,0.14)",
2064
+ "border-radius:12px",
2065
+ "background:rgba(4,10,20,0.82)"
2066
+ ].join(";");
2067
+ const emptyState = document.createElement("div");
2068
+ emptyState.style.cssText = [
2069
+ "display:flex",
2070
+ "align-items:center",
2071
+ "justify-content:center",
2072
+ "flex:1 1 auto",
2073
+ "min-height:96px",
2074
+ "color:rgba(204,222,250,0.6)",
2075
+ "font:500 12px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
2076
+ "text-align:center",
2077
+ "padding:18px"
2078
+ ].join(";");
2079
+ emptyState.textContent = "Console output will appear here.";
2080
+ collapseButton.addEventListener("click", (event) => {
2081
+ event.stopPropagation();
2082
+ state.expanded = false;
2083
+ renderOverlay(state);
2084
+ });
2085
+ clearButton.addEventListener("click", (event) => {
2086
+ event.stopPropagation();
2087
+ state.entries = [];
2088
+ state.unreadCount = 0;
2089
+ renderOverlay(state);
2090
+ });
2091
+ controls.appendChild(clearButton);
2092
+ controls.appendChild(collapseButton);
2093
+ entries.appendChild(emptyState);
2094
+ body.appendChild(entries);
2095
+ body.appendChild(controls);
2096
+ panel.appendChild(dragZone);
2097
+ panel.appendChild(body);
2098
+ attachDragStartListeners(dragZone, state);
2099
+ attachPanelResizeListeners(panel, state);
2100
+ attachCollapsedToggleListeners(toggleButton, state);
2101
+ root.appendChild(panel);
2102
+ root.appendChild(toggleButton);
2103
+ return {
2104
+ body,
2105
+ clearButton,
2106
+ collapseButton,
2107
+ controls,
2108
+ dragZone,
2109
+ emptyState,
2110
+ entries,
2111
+ panel,
2112
+ root,
2113
+ toggleButton
2114
+ };
2115
+ }
2116
+ function renderOverlay(state) {
2117
+ if (!state.ui) {
2118
+ return;
2119
+ }
2120
+ state.ui.panel.style.display = state.expanded ? "flex" : "none";
2121
+ state.ui.toggleButton.style.display = state.expanded ? "none" : "inline-flex";
2122
+ state.ui.toggleButton.textContent = "Logs";
2123
+ if (state.entries.length === 0) {
2124
+ state.ui.entries.style.display = "flex";
2125
+ state.ui.emptyState.style.display = "flex";
2126
+ state.ui.entries.replaceChildren(state.ui.emptyState);
2127
+ applyPanelSize(state);
2128
+ applyOverlayPosition(state);
2129
+ return;
2130
+ }
2131
+ state.ui.entries.style.display = "flex";
2132
+ state.ui.emptyState.style.display = "none";
2133
+ const nextChildren = state.entries.map((entry) => {
2134
+ const accent = getLevelAccent(entry.level);
2135
+ const row = document.createElement("div");
2136
+ row.style.cssText = [
2137
+ "display:flex",
2138
+ "align-items:flex-start",
2139
+ "gap:0",
2140
+ "padding:4px 12px",
2141
+ "background:" + accent.lineBackground
2142
+ ].join(";");
2143
+ const line = document.createElement("div");
2144
+ line.textContent = formatTimestamp(entry.timestamp) + " " + entry.level.toUpperCase() + " " + entry.message;
2145
+ line.style.cssText = [
2146
+ "color:#ecf4ff",
2147
+ "font:500 12px/1.5 ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
2148
+ "white-space:pre-wrap",
2149
+ "word-break:break-word",
2150
+ "flex:1 1 auto"
2151
+ ].join(";");
2152
+ row.appendChild(line);
2153
+ return row;
2154
+ });
2155
+ state.ui.entries.replaceChildren(...nextChildren);
2156
+ state.ui.entries.scrollTop = state.ui.entries.scrollHeight;
2157
+ applyPanelSize(state);
2158
+ applyOverlayPosition(state);
2159
+ }
2160
+ function mountOverlay(state) {
2161
+ const doc = getDocument2();
2162
+ if (!doc?.body || state.ui) {
2163
+ return;
2164
+ }
2165
+ state.ui = createOverlayUi(state);
2166
+ doc.body.appendChild(state.ui.root);
2167
+ applyPanelSize(state);
2168
+ applyOverlayPosition(state);
2169
+ renderOverlay(state);
2170
+ }
2171
+ function enqueueEntry(state, level, args) {
2172
+ state.entries.push(createEntry(level, args, state.nextEntryId));
2173
+ state.nextEntryId += 1;
2174
+ if (state.entries.length > state.maxEntries) {
2175
+ state.entries.splice(0, state.entries.length - state.maxEntries);
2176
+ }
2177
+ if (!state.expanded) {
2178
+ state.unreadCount += 1;
2179
+ }
2180
+ renderOverlay(state);
2181
+ }
2182
+ function restoreConsole(snapshot) {
2183
+ for (const method of CONSOLE_METHODS) {
2184
+ console[method] = snapshot[method];
22
2185
  }
23
2186
  }
2187
+ function patchConsole(state) {
2188
+ for (const method of CONSOLE_METHODS) {
2189
+ const original = state.originalConsole[method];
2190
+ console[method] = (...args) => {
2191
+ enqueueEntry(state, method, args);
2192
+ original(...args);
2193
+ };
2194
+ }
2195
+ }
2196
+ function cleanupOverlay(browserWindow, state) {
2197
+ restoreConsole(state.originalConsole);
2198
+ stopResizing(state);
2199
+ stopDragging(state);
2200
+ if (state.domReadyHandler) {
2201
+ getDocument2()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
2202
+ state.domReadyHandler = void 0;
2203
+ }
2204
+ if (state.resizeHandler && typeof browserWindow.removeEventListener === "function") {
2205
+ browserWindow.removeEventListener("resize", state.resizeHandler);
2206
+ state.resizeHandler = void 0;
2207
+ }
2208
+ state.ui?.root.remove();
2209
+ state.ui = null;
2210
+ delete browserWindow.__oasizLogOverlayController__;
2211
+ delete browserWindow.__oasizLogOverlayState__;
2212
+ }
2213
+ function createController(browserWindow, state) {
2214
+ return {
2215
+ retain() {
2216
+ state.refCount += 1;
2217
+ this.ensureMounted();
2218
+ },
2219
+ ensureMounted() {
2220
+ const doc = getDocument2();
2221
+ if (!doc) {
2222
+ return;
2223
+ }
2224
+ if (doc.body) {
2225
+ mountOverlay(state);
2226
+ applyOverlayPosition(state);
2227
+ return;
2228
+ }
2229
+ if (!state.domReadyHandler) {
2230
+ state.domReadyHandler = () => {
2231
+ mountOverlay(state);
2232
+ state.domReadyHandler = void 0;
2233
+ };
2234
+ doc.addEventListener("DOMContentLoaded", state.domReadyHandler, {
2235
+ once: true
2236
+ });
2237
+ }
2238
+ },
2239
+ clear() {
2240
+ state.entries = [];
2241
+ state.unreadCount = 0;
2242
+ renderOverlay(state);
2243
+ },
2244
+ show() {
2245
+ state.expanded = true;
2246
+ state.unreadCount = 0;
2247
+ renderOverlay(state);
2248
+ },
2249
+ hide() {
2250
+ state.expanded = false;
2251
+ renderOverlay(state);
2252
+ },
2253
+ isVisible() {
2254
+ return state.expanded;
2255
+ },
2256
+ destroy() {
2257
+ state.refCount = Math.max(0, state.refCount - 1);
2258
+ if (state.refCount === 0) {
2259
+ cleanupOverlay(browserWindow, state);
2260
+ }
2261
+ }
2262
+ };
2263
+ }
2264
+ function enableLogOverlay(options = {}) {
2265
+ if (options.enabled === false) {
2266
+ return NOOP_HANDLE2;
2267
+ }
2268
+ const browserWindow = getBrowserWindow2();
2269
+ const doc = getDocument2();
2270
+ if (!browserWindow || !doc) {
2271
+ return NOOP_HANDLE2;
2272
+ }
2273
+ const existingController = browserWindow.__oasizLogOverlayController__;
2274
+ const existingState = browserWindow.__oasizLogOverlayState__;
2275
+ if (existingController && existingState) {
2276
+ existingController.retain();
2277
+ if (typeof options.maxEntries === "number") {
2278
+ existingState.maxEntries = clampMaxEntries(options.maxEntries);
2279
+ if (existingState.entries.length > existingState.maxEntries) {
2280
+ existingState.entries.splice(
2281
+ 0,
2282
+ existingState.entries.length - existingState.maxEntries
2283
+ );
2284
+ }
2285
+ }
2286
+ if (typeof options.collapsed === "boolean") {
2287
+ existingState.expanded = !options.collapsed;
2288
+ applyPanelSize(existingState);
2289
+ if (existingState.expanded) {
2290
+ existingState.unreadCount = 0;
2291
+ }
2292
+ }
2293
+ if (typeof options.title === "string" && options.title.trim().length > 0) {
2294
+ existingState.title = options.title.trim();
2295
+ }
2296
+ existingController.ensureMounted();
2297
+ renderOverlay(existingState);
2298
+ return existingController;
2299
+ }
2300
+ const state = {
2301
+ dragMoved: false,
2302
+ dragStartPoint: null,
2303
+ entries: [],
2304
+ expanded: options.collapsed !== true,
2305
+ isDragging: false,
2306
+ isResizing: false,
2307
+ lastDragPoint: null,
2308
+ maxEntries: clampMaxEntries(options.maxEntries),
2309
+ nextEntryId: 1,
2310
+ originalConsole: createConsoleSnapshot(),
2311
+ panelSize: null,
2312
+ position: null,
2313
+ refCount: 1,
2314
+ removeDragListeners: null,
2315
+ removeResizeListeners: null,
2316
+ resizeStartPoint: null,
2317
+ resizeStartSize: null,
2318
+ suppressToggleClickUntil: 0,
2319
+ title: typeof options.title === "string" && options.title.trim().length > 0 ? options.title.trim() : DEFAULT_TITLE,
2320
+ resizeHandler: void 0,
2321
+ ui: null,
2322
+ unreadCount: 0
2323
+ };
2324
+ const controller = createController(browserWindow, state);
2325
+ browserWindow.__oasizLogOverlayState__ = state;
2326
+ browserWindow.__oasizLogOverlayController__ = controller;
2327
+ patchConsole(state);
2328
+ if (typeof browserWindow.addEventListener === "function") {
2329
+ state.resizeHandler = () => {
2330
+ applyOverlayPosition(state);
2331
+ };
2332
+ browserWindow.addEventListener("resize", state.resizeHandler);
2333
+ }
2334
+ controller.ensureMounted();
2335
+ return controller;
2336
+ }
24
2337
 
25
2338
  // src/multiplayer.ts
26
- function isDevelopment2() {
2339
+ function isDevelopment4() {
27
2340
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
28
2341
  return nodeEnv !== "production";
29
2342
  }
30
- function getBridgeWindow2() {
2343
+ function getBridgeWindow4() {
31
2344
  if (typeof window === "undefined") {
32
2345
  return void 0;
33
2346
  }
34
2347
  return window;
35
2348
  }
36
- function shareRoomCode(roomCode) {
37
- const bridge = getBridgeWindow2();
2349
+ function shareRoomCode(roomCode, options) {
2350
+ const bridge = getBridgeWindow4();
38
2351
  if (typeof bridge?.shareRoomCode === "function") {
39
- bridge.shareRoomCode(roomCode);
2352
+ bridge.shareRoomCode(roomCode, options);
40
2353
  return;
41
2354
  }
42
- if (isDevelopment2()) {
2355
+ if (isDevelopment4()) {
43
2356
  console.warn(
44
2357
  "[oasiz/sdk] shareRoomCode bridge is unavailable. This is expected in local development."
45
2358
  );
46
2359
  }
47
2360
  }
2361
+ function openInviteModal() {
2362
+ const bridge = getBridgeWindow4();
2363
+ if (typeof bridge?.openInviteModal === "function") {
2364
+ bridge.openInviteModal();
2365
+ return;
2366
+ }
2367
+ if (isDevelopment4()) {
2368
+ console.warn(
2369
+ "[oasiz/sdk] openInviteModal bridge is unavailable. This is expected in local development."
2370
+ );
2371
+ }
2372
+ }
48
2373
  function getGameId() {
49
- const bridge = getBridgeWindow2();
2374
+ const bridge = getBridgeWindow4();
50
2375
  return bridge?.__GAME_ID__;
51
2376
  }
52
2377
  function getRoomCode() {
53
- const bridge = getBridgeWindow2();
2378
+ const bridge = getBridgeWindow4();
54
2379
  return bridge?.__ROOM_CODE__;
55
2380
  }
2381
+ function getPlayerId() {
2382
+ const bridge = getBridgeWindow4();
2383
+ return bridge?.__PLAYER_ID__;
2384
+ }
56
2385
  function getPlayerName() {
57
- const bridge = getBridgeWindow2();
2386
+ const bridge = getBridgeWindow4();
58
2387
  return bridge?.__PLAYER_NAME__;
59
2388
  }
60
2389
  function getPlayerAvatar() {
61
- const bridge = getBridgeWindow2();
2390
+ const bridge = getBridgeWindow4();
62
2391
  return bridge?.__PLAYER_AVATAR__;
63
2392
  }
64
2393
 
65
2394
  // src/score.ts
66
- function isDevelopment3() {
2395
+ function isDevelopment5() {
67
2396
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
68
2397
  return nodeEnv !== "production";
69
2398
  }
70
- function warnMissingBridge(methodName) {
71
- if (isDevelopment3()) {
2399
+ function warnMissingBridge3(methodName) {
2400
+ if (isDevelopment5()) {
72
2401
  console.warn(
73
2402
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
74
2403
  );
75
2404
  }
76
2405
  }
77
- function getBridgeWindow3() {
2406
+ function getBridgeWindow5() {
78
2407
  if (typeof window === "undefined") {
79
2408
  return void 0;
80
2409
  }
@@ -82,41 +2411,92 @@ function getBridgeWindow3() {
82
2411
  }
83
2412
  function submitScore(score) {
84
2413
  if (!Number.isFinite(score)) {
85
- if (isDevelopment3()) {
2414
+ if (isDevelopment5()) {
86
2415
  console.warn("[oasiz/sdk] submitScore expected a finite number:", score);
87
2416
  }
88
2417
  return;
89
2418
  }
90
- const bridge = getBridgeWindow3();
2419
+ const bridge = getBridgeWindow5();
91
2420
  const normalizedScore = Math.max(0, Math.floor(score));
92
2421
  if (typeof bridge?.submitScore === "function") {
93
2422
  bridge.submitScore(normalizedScore);
94
2423
  return;
95
2424
  }
96
- warnMissingBridge("submitScore");
2425
+ warnMissingBridge3("submitScore");
97
2426
  }
98
- function emitScoreConfig(config) {
99
- const bridge = getBridgeWindow3();
100
- if (typeof bridge?.emitScoreConfig === "function") {
101
- bridge.emitScoreConfig(config);
102
- return;
2427
+
2428
+ // src/score-edit.ts
2429
+ function isDevelopment6() {
2430
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
2431
+ return nodeEnv !== "production";
2432
+ }
2433
+ function getBridgeWindow6() {
2434
+ if (typeof window === "undefined") {
2435
+ return void 0;
2436
+ }
2437
+ return window;
2438
+ }
2439
+ function warnMissingBridge4(methodName) {
2440
+ if (isDevelopment6()) {
2441
+ console.warn(
2442
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
2443
+ );
2444
+ }
2445
+ }
2446
+ async function editScore(payload, methodName) {
2447
+ const bridge = getBridgeWindow6();
2448
+ if (typeof bridge?.__oasizEditScore !== "function") {
2449
+ warnMissingBridge4(methodName);
2450
+ return null;
2451
+ }
2452
+ try {
2453
+ const result = await bridge.__oasizEditScore(payload);
2454
+ return result ?? null;
2455
+ } catch (error) {
2456
+ if (isDevelopment6()) {
2457
+ console.error("[oasiz/sdk] " + methodName + " failed:", error);
2458
+ }
2459
+ return null;
2460
+ }
2461
+ }
2462
+ async function addScore(delta) {
2463
+ if (!Number.isInteger(delta)) {
2464
+ if (isDevelopment6()) {
2465
+ console.warn("[oasiz/sdk] addScore expected an integer:", delta);
2466
+ }
2467
+ return null;
2468
+ }
2469
+ if (delta === 0) {
2470
+ return null;
2471
+ }
2472
+ return editScore({ delta }, "addScore");
2473
+ }
2474
+ async function setScore(score) {
2475
+ if (!Number.isInteger(score) || score < 0) {
2476
+ if (isDevelopment6()) {
2477
+ console.warn(
2478
+ "[oasiz/sdk] setScore expected a non-negative integer:",
2479
+ score
2480
+ );
2481
+ }
2482
+ return null;
103
2483
  }
104
- warnMissingBridge("emitScoreConfig");
2484
+ return editScore({ score }, "setScore");
105
2485
  }
106
2486
 
107
2487
  // src/share.ts
108
- function isDevelopment4() {
2488
+ function isDevelopment7() {
109
2489
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
110
2490
  return nodeEnv !== "production";
111
2491
  }
112
- function getBridgeWindow4() {
2492
+ function getBridgeWindow7() {
113
2493
  if (typeof window === "undefined") {
114
2494
  return void 0;
115
2495
  }
116
2496
  return window;
117
2497
  }
118
- function warnMissingBridge2(methodName) {
119
- if (isDevelopment4()) {
2498
+ function warnMissingBridge5(methodName) {
2499
+ if (isDevelopment7()) {
120
2500
  console.warn(
121
2501
  "[oasiz/sdk] " + methodName + " share bridge is unavailable. This is expected in local development."
122
2502
  );
@@ -159,20 +2539,20 @@ function validateRequest(options) {
159
2539
  }
160
2540
  async function share(options) {
161
2541
  const request = validateRequest(options);
162
- const bridge = getBridgeWindow4();
2542
+ const bridge = getBridgeWindow7();
163
2543
  if (typeof bridge?.__oasizShareRequest !== "function") {
164
- warnMissingBridge2("__oasizShareRequest");
2544
+ warnMissingBridge5("__oasizShareRequest");
165
2545
  throw new Error("Share bridge unavailable");
166
2546
  }
167
2547
  await bridge.__oasizShareRequest(request);
168
2548
  }
169
2549
 
170
2550
  // src/state.ts
171
- function isDevelopment5() {
2551
+ function isDevelopment8() {
172
2552
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
173
2553
  return nodeEnv !== "production";
174
2554
  }
175
- function getBridgeWindow5() {
2555
+ function getBridgeWindow8() {
176
2556
  if (typeof window === "undefined") {
177
2557
  return void 0;
178
2558
  }
@@ -185,22 +2565,22 @@ function isPlainObject(value) {
185
2565
  const proto = Object.getPrototypeOf(value);
186
2566
  return proto === Object.prototype || proto === null;
187
2567
  }
188
- function warnMissingBridge3(methodName) {
189
- if (isDevelopment5()) {
2568
+ function warnMissingBridge6(methodName) {
2569
+ if (isDevelopment8()) {
190
2570
  console.warn(
191
2571
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
192
2572
  );
193
2573
  }
194
2574
  }
195
2575
  function loadGameState() {
196
- const bridge = getBridgeWindow5();
2576
+ const bridge = getBridgeWindow8();
197
2577
  if (typeof bridge?.loadGameState !== "function") {
198
- warnMissingBridge3("loadGameState");
2578
+ warnMissingBridge6("loadGameState");
199
2579
  return {};
200
2580
  }
201
2581
  const state = bridge.loadGameState();
202
2582
  if (!isPlainObject(state)) {
203
- if (isDevelopment5()) {
2583
+ if (isDevelopment8()) {
204
2584
  console.warn(
205
2585
  "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
206
2586
  );
@@ -211,35 +2591,35 @@ function loadGameState() {
211
2591
  }
212
2592
  function saveGameState(state) {
213
2593
  if (!isPlainObject(state)) {
214
- if (isDevelopment5()) {
2594
+ if (isDevelopment8()) {
215
2595
  console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
216
2596
  }
217
2597
  return;
218
2598
  }
219
- const bridge = getBridgeWindow5();
2599
+ const bridge = getBridgeWindow8();
220
2600
  if (typeof bridge?.saveGameState === "function") {
221
2601
  bridge.saveGameState(state);
222
2602
  return;
223
2603
  }
224
- warnMissingBridge3("saveGameState");
2604
+ warnMissingBridge6("saveGameState");
225
2605
  }
226
2606
  function flushGameState() {
227
- const bridge = getBridgeWindow5();
2607
+ const bridge = getBridgeWindow8();
228
2608
  if (typeof bridge?.flushGameState === "function") {
229
2609
  bridge.flushGameState();
230
2610
  return;
231
2611
  }
232
- warnMissingBridge3("flushGameState");
2612
+ warnMissingBridge6("flushGameState");
233
2613
  }
234
2614
 
235
2615
  // src/lifecycle.ts
236
- function isDevelopment6() {
2616
+ function isDevelopment9() {
237
2617
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
238
2618
  return nodeEnv !== "production";
239
2619
  }
240
2620
  function addLifecycleListener(eventName, callback) {
241
2621
  if (typeof window === "undefined") {
242
- if (isDevelopment6()) {
2622
+ if (isDevelopment9()) {
243
2623
  console.warn(
244
2624
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
245
2625
  );
@@ -258,252 +2638,732 @@ function onResume(callback) {
258
2638
  return addLifecycleListener("oasiz:resume", callback);
259
2639
  }
260
2640
 
261
- // src/navigation.ts
262
- var activeBackListeners = 0;
263
- function isDevelopment7() {
2641
+ // src/layout.ts
2642
+ var INSET_SIDES = ["top", "right", "bottom", "left"];
2643
+ var SIDE_TO_AXIS = {
2644
+ top: "vertical",
2645
+ right: "horizontal",
2646
+ bottom: "vertical",
2647
+ left: "horizontal"
2648
+ };
2649
+ function createInsetEdges(value) {
2650
+ return {
2651
+ top: value,
2652
+ right: value,
2653
+ bottom: value,
2654
+ left: value
2655
+ };
2656
+ }
2657
+ function isDevelopment10() {
264
2658
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
265
2659
  return nodeEnv !== "production";
266
2660
  }
267
- function getBridgeWindow6() {
2661
+ function getBridgeWindow9() {
268
2662
  if (typeof window === "undefined") {
269
2663
  return void 0;
270
2664
  }
271
2665
  return window;
272
2666
  }
273
- function warnMissingBridge4(methodName) {
274
- if (isDevelopment7()) {
2667
+ function warnMissingBridge7(methodName) {
2668
+ if (isDevelopment10()) {
275
2669
  console.warn(
276
2670
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
277
2671
  );
278
2672
  }
279
2673
  }
280
- function normalizeNavigationError(error) {
281
- if (error instanceof Error) {
282
- return error;
283
- }
284
- return new Error(
285
- typeof error === "string" ? error : "Back button callback failed."
286
- );
2674
+ function isRecord2(value) {
2675
+ return typeof value === "object" && value !== null;
287
2676
  }
288
- function addNavigationListener(eventName, callback) {
289
- if (typeof window === "undefined") {
290
- if (isDevelopment7()) {
291
- console.warn(
292
- "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
293
- );
2677
+ function toFiniteNumber(value) {
2678
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2679
+ if (typeof value !== "string") {
2680
+ return void 0;
294
2681
  }
295
- return () => {
296
- };
2682
+ const parsed = Number.parseFloat(value.trim());
2683
+ if (!Number.isFinite(parsed)) {
2684
+ return void 0;
2685
+ }
2686
+ return parsed;
297
2687
  }
298
- const handler = () => callback();
299
- window.addEventListener(eventName, handler);
300
- return () => window.removeEventListener(eventName, handler);
2688
+ return value;
301
2689
  }
302
- function onBackButton(callback) {
303
- const off = addNavigationListener("oasiz:back", () => {
304
- try {
305
- callback();
306
- } catch (error) {
307
- leaveGame();
308
- throw normalizeNavigationError(error);
309
- }
310
- });
311
- const bridge = getBridgeWindow6();
312
- activeBackListeners += 1;
313
- if (activeBackListeners === 1) {
314
- if (typeof bridge?.__oasizSetBackOverride === "function") {
315
- bridge.__oasizSetBackOverride(true);
2690
+ function clampInsetPixels(value) {
2691
+ const numeric = toFiniteNumber(value);
2692
+ if (typeof numeric === "undefined") {
2693
+ return void 0;
2694
+ }
2695
+ return Math.max(0, numeric);
2696
+ }
2697
+ function normalizeInsetPercent(value) {
2698
+ const numeric = toFiniteNumber(value);
2699
+ if (typeof numeric === "undefined") {
2700
+ return void 0;
2701
+ }
2702
+ return Math.min(100, Math.max(0, numeric));
2703
+ }
2704
+ function getViewportSize2(bridge, axis) {
2705
+ const visualViewportSize = axis === "vertical" ? bridge.visualViewport?.height : bridge.visualViewport?.width;
2706
+ if (typeof visualViewportSize === "number" && Number.isFinite(visualViewportSize) && visualViewportSize > 0) {
2707
+ return visualViewportSize;
2708
+ }
2709
+ const innerSize = axis === "vertical" ? bridge.innerHeight : bridge.innerWidth;
2710
+ if (typeof innerSize === "number" && Number.isFinite(innerSize) && innerSize > 0) {
2711
+ return innerSize;
2712
+ }
2713
+ const documentSize = axis === "vertical" ? bridge.document?.documentElement?.clientHeight : bridge.document?.documentElement?.clientWidth;
2714
+ if (typeof documentSize === "number" && Number.isFinite(documentSize) && documentSize > 0) {
2715
+ return documentSize;
2716
+ }
2717
+ const bodySize = axis === "vertical" ? bridge.document?.body?.clientHeight : bridge.document?.body?.clientWidth;
2718
+ if (typeof bodySize === "number" && Number.isFinite(bodySize) && bodySize > 0) {
2719
+ return bodySize;
2720
+ }
2721
+ return 0;
2722
+ }
2723
+ function readCssSafeAreaValue(bridge, cssValue) {
2724
+ const doc = bridge.document;
2725
+ const root = doc?.body ?? doc?.documentElement;
2726
+ if (!doc || !root || typeof doc.createElement !== "function" || typeof root.appendChild !== "function" || typeof bridge.getComputedStyle !== "function") {
2727
+ return 0;
2728
+ }
2729
+ const probe = doc.createElement("div");
2730
+ probe.style.position = "fixed";
2731
+ probe.style.top = "0";
2732
+ probe.style.left = "0";
2733
+ probe.style.width = "0";
2734
+ probe.style.height = "0";
2735
+ probe.style.visibility = "hidden";
2736
+ probe.style.pointerEvents = "none";
2737
+ probe.style.paddingTop = cssValue;
2738
+ root.appendChild(probe);
2739
+ try {
2740
+ return clampInsetPixels(bridge.getComputedStyle(probe).paddingTop) ?? 0;
2741
+ } finally {
2742
+ if (typeof probe.remove === "function") {
2743
+ probe.remove();
316
2744
  } else {
317
- warnMissingBridge4("__oasizSetBackOverride");
2745
+ probe.parentNode?.removeChild(probe);
318
2746
  }
319
2747
  }
320
- return () => {
321
- off();
322
- activeBackListeners = Math.max(0, activeBackListeners - 1);
323
- if (activeBackListeners === 0) {
324
- const currentBridge = getBridgeWindow6();
325
- if (typeof currentBridge?.__oasizSetBackOverride === "function") {
326
- currentBridge.__oasizSetBackOverride(false);
327
- } else {
328
- warnMissingBridge4("__oasizSetBackOverride");
329
- }
330
- }
2748
+ }
2749
+ function readCssSafeAreaPixels(bridge, side) {
2750
+ const envPixels = readCssSafeAreaValue(
2751
+ bridge,
2752
+ "env(safe-area-inset-" + side + ")"
2753
+ );
2754
+ if (envPixels > 0) {
2755
+ return envPixels;
2756
+ }
2757
+ return readCssSafeAreaValue(
2758
+ bridge,
2759
+ "constant(safe-area-inset-" + side + ")"
2760
+ );
2761
+ }
2762
+ function getDevicePixelRatio(bridge) {
2763
+ const dpr = bridge.devicePixelRatio;
2764
+ if (typeof dpr !== "number" || !Number.isFinite(dpr) || dpr <= 0) {
2765
+ return 1;
2766
+ }
2767
+ return dpr;
2768
+ }
2769
+ function roughlyEqualPixels(a, b) {
2770
+ return Math.abs(a - b) <= 2;
2771
+ }
2772
+ function normalizeInsetPixels(value, bridge, side) {
2773
+ const pixels = clampInsetPixels(value);
2774
+ if (typeof pixels === "undefined") {
2775
+ return void 0;
2776
+ }
2777
+ const cssEnvPixels = readCssSafeAreaPixels(bridge, side);
2778
+ if (pixels <= 0) {
2779
+ return cssEnvPixels;
2780
+ }
2781
+ const dpr = getDevicePixelRatio(bridge);
2782
+ if (cssEnvPixels > 0 && dpr > 1 && roughlyEqualPixels(pixels / dpr, cssEnvPixels)) {
2783
+ return cssEnvPixels;
2784
+ }
2785
+ return pixels;
2786
+ }
2787
+ function pixelsToPercentOfViewport(pixels, bridge, side) {
2788
+ const size = getViewportSize2(bridge, SIDE_TO_AXIS[side]);
2789
+ if (size <= 0) {
2790
+ return 0;
2791
+ }
2792
+ return normalizeInsetPercent(pixels / size * 100) ?? 0;
2793
+ }
2794
+ function percentToPixelsOfViewport(percent, bridge, side) {
2795
+ const size = getViewportSize2(bridge, SIDE_TO_AXIS[side]);
2796
+ if (size <= 0) {
2797
+ return 0;
2798
+ }
2799
+ return percent / 100 * size;
2800
+ }
2801
+ function cssSafeAreaPercent(bridge, side) {
2802
+ return pixelsToPercentOfViewport(readCssSafeAreaPixels(bridge, side), bridge, side);
2803
+ }
2804
+ function resolvePercentValue(value, bridge, side) {
2805
+ const percent = normalizeInsetPercent(value);
2806
+ if (typeof percent === "undefined") {
2807
+ return void 0;
2808
+ }
2809
+ return percent > 0 ? percent : cssSafeAreaPercent(bridge, side);
2810
+ }
2811
+ function resolvePixelValue(value, bridge, side) {
2812
+ const numeric = toFiniteNumber(value);
2813
+ if (typeof numeric === "undefined") {
2814
+ return void 0;
2815
+ }
2816
+ return normalizeInsetPixels(numeric, bridge, side);
2817
+ }
2818
+ function sideSuffix(side) {
2819
+ switch (side) {
2820
+ case "top":
2821
+ return "Top";
2822
+ case "right":
2823
+ return "Right";
2824
+ case "bottom":
2825
+ return "Bottom";
2826
+ case "left":
2827
+ return "Left";
2828
+ }
2829
+ }
2830
+ function callBridgeFunction(bridge, name) {
2831
+ const fn = bridge[name];
2832
+ if (typeof fn !== "function") {
2833
+ return void 0;
2834
+ }
2835
+ try {
2836
+ return fn.call(bridge);
2837
+ } catch (error) {
2838
+ console.error("[oasiz/sdk] " + String(name) + " failed:", error);
2839
+ return void 0;
2840
+ }
2841
+ }
2842
+ function readInsetObjectValue(value, side, group) {
2843
+ if (!isRecord2(value)) {
2844
+ return void 0;
2845
+ }
2846
+ if (group) {
2847
+ return isRecord2(value[group]) ? value[group][side] : void 0;
2848
+ }
2849
+ return value[side];
2850
+ }
2851
+ function firstDefined(...values) {
2852
+ return values.find((value) => typeof value !== "undefined");
2853
+ }
2854
+ function readHostInsetSources(bridge) {
2855
+ return {
2856
+ globalPixels: bridge.__OASIZ_VIEWPORT_INSETS__,
2857
+ globalPercent: bridge.__OASIZ_VIEWPORT_INSETS_PERCENT__,
2858
+ methodPixels: callBridgeFunction(bridge, "getViewportInsets"),
2859
+ methodPercent: callBridgeFunction(bridge, "getViewportInsetsPercent")
331
2860
  };
332
2861
  }
333
- function onLeaveGame(callback) {
334
- return addNavigationListener("oasiz:leave", callback);
2862
+ function readIndividualPercentValue(bridge, side) {
2863
+ const suffix = sideSuffix(side);
2864
+ return firstDefined(
2865
+ callBridgeFunction(
2866
+ bridge,
2867
+ "getSafeArea" + suffix + "Percent"
2868
+ ),
2869
+ bridge["__OASIZ_SAFE_AREA_" + side.toUpperCase() + "_PERCENT__"]
2870
+ );
335
2871
  }
336
- function leaveGame() {
337
- const bridge = getBridgeWindow6();
338
- if (typeof bridge?.__oasizLeaveGame === "function") {
339
- bridge.__oasizLeaveGame();
2872
+ function readIndividualPixelValue(bridge, side) {
2873
+ const suffix = sideSuffix(side);
2874
+ return firstDefined(
2875
+ callBridgeFunction(
2876
+ bridge,
2877
+ "getSafeArea" + suffix
2878
+ ),
2879
+ bridge["__OASIZ_SAFE_AREA_" + side.toUpperCase() + "__"]
2880
+ );
2881
+ }
2882
+ function resolveInsetSide(bridge, sources, side) {
2883
+ const percentCandidate = firstDefined(
2884
+ readInsetObjectValue(sources.methodPercent, side, "percent"),
2885
+ readInsetObjectValue(sources.methodPercent, side),
2886
+ readInsetObjectValue(sources.globalPercent, side, "percent"),
2887
+ readInsetObjectValue(sources.globalPercent, side),
2888
+ readInsetObjectValue(sources.methodPixels, side, "percent"),
2889
+ readInsetObjectValue(sources.globalPixels, side, "percent"),
2890
+ readIndividualPercentValue(bridge, side)
2891
+ );
2892
+ const percent = resolvePercentValue(percentCandidate, bridge, side);
2893
+ if (typeof percent !== "undefined") {
2894
+ return {
2895
+ pixels: percentToPixelsOfViewport(percent, bridge, side),
2896
+ percent
2897
+ };
2898
+ }
2899
+ const pixelCandidate = firstDefined(
2900
+ readInsetObjectValue(sources.methodPixels, side, "pixels"),
2901
+ readInsetObjectValue(sources.methodPixels, side),
2902
+ readInsetObjectValue(sources.globalPixels, side, "pixels"),
2903
+ readInsetObjectValue(sources.globalPixels, side),
2904
+ readIndividualPixelValue(bridge, side)
2905
+ );
2906
+ const pixels = resolvePixelValue(pixelCandidate, bridge, side);
2907
+ if (typeof pixels !== "undefined") {
2908
+ return {
2909
+ pixels,
2910
+ percent: pixelsToPercentOfViewport(pixels, bridge, side)
2911
+ };
2912
+ }
2913
+ const cssPixels = readCssSafeAreaPixels(bridge, side);
2914
+ return {
2915
+ pixels: cssPixels,
2916
+ percent: pixelsToPercentOfViewport(cssPixels, bridge, side)
2917
+ };
2918
+ }
2919
+ function getViewportInsets() {
2920
+ const bridge = getBridgeWindow9();
2921
+ if (!bridge) {
2922
+ return {
2923
+ pixels: createInsetEdges(0),
2924
+ percent: createInsetEdges(0)
2925
+ };
2926
+ }
2927
+ const sources = readHostInsetSources(bridge);
2928
+ const pixels = createInsetEdges(0);
2929
+ const percent = createInsetEdges(0);
2930
+ for (const side of INSET_SIDES) {
2931
+ const resolved = resolveInsetSide(bridge, sources, side);
2932
+ pixels[side] = resolved.pixels;
2933
+ percent[side] = resolved.percent;
2934
+ }
2935
+ return { pixels, percent };
2936
+ }
2937
+ function getSafeAreaTop() {
2938
+ const bridge = getBridgeWindow9();
2939
+ if (!bridge) {
2940
+ return 0;
2941
+ }
2942
+ const top = getViewportInsets().percent.top;
2943
+ if (top <= 0) {
2944
+ warnMissingBridge7("getSafeAreaTop");
2945
+ }
2946
+ return top;
2947
+ }
2948
+ function setLeaderboardVisible(visible) {
2949
+ if (typeof visible !== "boolean") {
2950
+ if (isDevelopment10()) {
2951
+ console.warn(
2952
+ "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
2953
+ visible
2954
+ );
2955
+ }
2956
+ return;
2957
+ }
2958
+ const bridge = getBridgeWindow9();
2959
+ if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
2960
+ bridge.__oasizSetLeaderboardVisible(visible);
340
2961
  return;
341
2962
  }
342
- warnMissingBridge4("__oasizLeaveGame");
2963
+ warnMissingBridge7("__oasizSetLeaderboardVisible");
343
2964
  }
344
2965
 
345
- // src/store.ts
346
- function isDevelopment8() {
347
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
348
- return nodeEnv !== "production";
349
- }
350
- function getBridgeWindow7() {
2966
+ // src/performance.ts
2967
+ var DEFAULT_GRAPHICS_PERFORMANCE = {
2968
+ fps: 45,
2969
+ tier: "medium"
2970
+ };
2971
+ var TIER_DEFAULT_FPS = {
2972
+ minimal: 24,
2973
+ low: 30,
2974
+ medium: 45,
2975
+ high: 60
2976
+ };
2977
+ var cachedEstimatedGraphicsPerformance;
2978
+ function getBridgeWindow10() {
351
2979
  if (typeof window === "undefined") {
352
2980
  return void 0;
353
2981
  }
354
2982
  return window;
355
2983
  }
356
- function warnMissingBridge5(methodName) {
357
- if (isDevelopment8()) {
358
- console.warn(
359
- "[oasiz/sdk] " + methodName + " store bridge is unavailable. This is expected in local development."
360
- );
2984
+ function isRecord3(value) {
2985
+ return typeof value === "object" && value !== null;
2986
+ }
2987
+ function toFiniteNumber2(value) {
2988
+ if (typeof value === "number" && Number.isFinite(value)) {
2989
+ return value;
2990
+ }
2991
+ if (typeof value === "string") {
2992
+ const parsed = Number.parseFloat(value.trim());
2993
+ if (Number.isFinite(parsed)) {
2994
+ return parsed;
2995
+ }
361
2996
  }
2997
+ return void 0;
362
2998
  }
363
- async function requestStoreBridge(action, payload) {
364
- const bridge = getBridgeWindow7();
365
- if (typeof bridge?.__oasizStoreBridgeRequest !== "function") {
366
- warnMissingBridge5(action);
367
- throw new Error("Store bridge unavailable");
2999
+ function clampScore(value) {
3000
+ return Math.min(100, Math.max(0, Math.round(value)));
3001
+ }
3002
+ function clampFps(value) {
3003
+ return Math.min(240, Math.max(1, Math.round(value)));
3004
+ }
3005
+ function tierFromScore(score) {
3006
+ if (score < 25) return "minimal";
3007
+ if (score < 40) return "low";
3008
+ if (score < 70) return "medium";
3009
+ return "high";
3010
+ }
3011
+ function tierFromFps(fps) {
3012
+ if (fps < 30) return "minimal";
3013
+ if (fps < 45) return "low";
3014
+ if (fps < 58) return "medium";
3015
+ return "high";
3016
+ }
3017
+ function fpsFromScore(score) {
3018
+ return TIER_DEFAULT_FPS[tierFromScore(score)];
3019
+ }
3020
+ function normalizeTier(value) {
3021
+ if (typeof value !== "string") {
3022
+ return void 0;
368
3023
  }
369
- return await bridge.__oasizStoreBridgeRequest({
370
- action,
371
- payload
372
- });
3024
+ const normalized = value.trim().toLowerCase();
3025
+ if (normalized === "minimal" || normalized === "safe") return "minimal";
3026
+ if (normalized === "high") return "high";
3027
+ if (normalized === "medium") return "medium";
3028
+ if (normalized === "low") return "low";
3029
+ return void 0;
373
3030
  }
374
- async function getFreshState() {
375
- return await requestStoreBridge("getStoreState");
3031
+ function firstDefined2(...values) {
3032
+ return values.find((value) => typeof value !== "undefined");
376
3033
  }
377
- function subscribeToStoreEvent(eventName, callback) {
378
- if (typeof window === "undefined") {
379
- return () => {
380
- };
3034
+ function normalizeMetric(value) {
3035
+ if (typeof value === "undefined" || value === null) {
3036
+ return void 0;
381
3037
  }
382
- const handler = (event) => {
383
- const customEvent = event;
384
- callback(customEvent.detail);
385
- };
386
- window.addEventListener(eventName, handler);
387
- return () => {
388
- window.removeEventListener(eventName, handler);
3038
+ if (typeof value === "number" || typeof value === "string") {
3039
+ const numeric = toFiniteNumber2(value);
3040
+ if (typeof numeric !== "undefined") {
3041
+ const fps2 = clampFps(numeric);
3042
+ return { fps: fps2, tier: tierFromFps(fps2) };
3043
+ }
3044
+ const tier2 = normalizeTier(value);
3045
+ if (tier2) {
3046
+ return { fps: TIER_DEFAULT_FPS[tier2], tier: tier2 };
3047
+ }
3048
+ return void 0;
3049
+ }
3050
+ if (!isRecord3(value)) {
3051
+ return void 0;
3052
+ }
3053
+ const fpsCandidate = firstDefined2(
3054
+ value.fps,
3055
+ value.targetFps,
3056
+ value.frameRate,
3057
+ value.framesPerSecond
3058
+ );
3059
+ const legacyScoreCandidate = firstDefined2(
3060
+ value.score,
3061
+ value.metric,
3062
+ value.value,
3063
+ value.performance,
3064
+ value.performanceScore,
3065
+ value.graphicsScore
3066
+ );
3067
+ const tierCandidate = firstDefined2(
3068
+ value.tier,
3069
+ value.graphicsTier,
3070
+ value.performanceTier,
3071
+ value.qualityTier,
3072
+ value.recommendedTier
3073
+ );
3074
+ const tier = normalizeTier(tierCandidate);
3075
+ const fpsNumeric = toFiniteNumber2(fpsCandidate);
3076
+ const scoreNumeric = toFiniteNumber2(legacyScoreCandidate);
3077
+ if (typeof fpsNumeric === "undefined" && typeof scoreNumeric === "undefined" && !tier) {
3078
+ return void 0;
3079
+ }
3080
+ const fps = clampFps(
3081
+ typeof fpsNumeric !== "undefined" ? fpsNumeric : typeof scoreNumeric !== "undefined" ? fpsFromScore(clampScore(scoreNumeric)) : TIER_DEFAULT_FPS[tier]
3082
+ );
3083
+ return {
3084
+ fps,
3085
+ tier: tier ?? tierFromFps(fps)
389
3086
  };
390
3087
  }
391
- async function syncProducts(products, expectedVersion) {
392
- return await requestStoreBridge("syncProducts", {
393
- expectedVersion: expectedVersion ?? null,
394
- products
395
- });
3088
+ function callBridgeMetric(bridge, name) {
3089
+ const fn = bridge[name];
3090
+ if (typeof fn !== "function") {
3091
+ return void 0;
3092
+ }
3093
+ try {
3094
+ return fn.call(bridge);
3095
+ } catch (error) {
3096
+ console.error("[oasiz/sdk] " + String(name) + " failed:", error);
3097
+ return void 0;
3098
+ }
396
3099
  }
397
- async function getProducts() {
398
- const state = await getFreshState();
399
- return state.products;
3100
+ function readHostGraphicsPerformance(bridge) {
3101
+ const candidates = [
3102
+ callBridgeMetric(bridge, "getGraphicsPerformance"),
3103
+ callBridgeMetric(bridge, "getGraphicsPerformanceMetric"),
3104
+ callBridgeMetric(bridge, "getPerformanceMetric"),
3105
+ bridge.__OASIZ_GRAPHICS_PERFORMANCE__,
3106
+ bridge.__OASIZ_PERFORMANCE_METRIC__
3107
+ ];
3108
+ for (const candidate of candidates) {
3109
+ const metric = normalizeMetric(candidate);
3110
+ if (metric) {
3111
+ return metric;
3112
+ }
3113
+ }
3114
+ return void 0;
400
3115
  }
401
- async function getEntitlements() {
402
- const state = await getFreshState();
403
- return state.entitlements;
3116
+ function getDevicePixelRatio2(bridge) {
3117
+ const dpr = bridge.devicePixelRatio;
3118
+ return typeof dpr === "number" && Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
404
3119
  }
405
- async function hasEntitlement(productId) {
406
- const state = await getFreshState();
407
- return state.entitlements.find((item) => item.productId === productId)?.owned === true;
3120
+ function getNavigatorValue(bridge) {
3121
+ return bridge.navigator;
408
3122
  }
409
- async function getQuantity(productId) {
410
- const state = await getFreshState();
411
- return state.entitlements.find((item) => item.productId === productId)?.quantity ?? 0;
3123
+ function parseIosMajorVersion(userAgent) {
3124
+ const match = /\bOS (\d+)_/i.exec(userAgent);
3125
+ if (!match) {
3126
+ return void 0;
3127
+ }
3128
+ const parsed = Number.parseInt(match[1] ?? "", 10);
3129
+ return Number.isFinite(parsed) ? parsed : void 0;
412
3130
  }
413
- async function purchase(productId, quantity = 1) {
414
- return await requestStoreBridge("purchaseProduct", {
415
- productId,
416
- quantity
417
- });
3131
+ function isAppleMobileDevice(bridge) {
3132
+ const userAgent = bridge.navigator?.userAgent ?? "";
3133
+ return /iP(hone|ad|od)/i.test(userAgent) || /Macintosh/i.test(userAgent) && (bridge.navigator?.maxTouchPoints ?? 0) > 1;
418
3134
  }
419
- async function consume(productId, quantity = 1) {
420
- return await requestStoreBridge("consumeProduct", {
421
- productId,
422
- quantity
423
- });
3135
+ function getLongestScreenEdge(bridge) {
3136
+ const screenWidth = bridge.screen?.width;
3137
+ const screenHeight = bridge.screen?.height;
3138
+ const innerWidth = bridge.innerWidth;
3139
+ const innerHeight = bridge.innerHeight;
3140
+ return Math.max(
3141
+ typeof screenWidth === "number" ? screenWidth : 0,
3142
+ typeof screenHeight === "number" ? screenHeight : 0,
3143
+ typeof innerWidth === "number" ? innerWidth : 0,
3144
+ typeof innerHeight === "number" ? innerHeight : 0
3145
+ );
424
3146
  }
425
- async function getJemBalance() {
426
- const state = await getFreshState();
427
- return state.jemBalance;
3147
+ function isCoarsePointer(bridge) {
3148
+ try {
3149
+ return bridge.matchMedia?.("(pointer: coarse)").matches === true;
3150
+ } catch {
3151
+ return false;
3152
+ }
428
3153
  }
429
- function onEntitlementsChanged(callback) {
430
- return subscribeToStoreEvent("oasiz:entitlements-changed", (state) => {
431
- callback(state.entitlements);
432
- });
3154
+ function getWebGlInfo(bridge) {
3155
+ const canvas = bridge.document?.createElement?.("canvas");
3156
+ if (!canvas || typeof canvas.getContext !== "function") {
3157
+ return { webglVersion: 0 };
3158
+ }
3159
+ const gl2 = canvas.getContext("webgl2");
3160
+ const gl = gl2 ?? canvas.getContext("webgl") ?? canvas.getContext("experimental-webgl");
3161
+ if (!gl) {
3162
+ return { webglVersion: 0 };
3163
+ }
3164
+ const context = gl;
3165
+ let maxTextureSize;
3166
+ let renderer;
3167
+ try {
3168
+ const value = context.getParameter(context.MAX_TEXTURE_SIZE);
3169
+ if (typeof value === "number" && Number.isFinite(value)) {
3170
+ maxTextureSize = value;
3171
+ }
3172
+ } catch {
3173
+ }
3174
+ try {
3175
+ const debugInfo = context.getExtension("WEBGL_debug_renderer_info");
3176
+ const rendererParam = debugInfo?.UNMASKED_RENDERER_WEBGL ?? context.RENDERER;
3177
+ const value = context.getParameter(rendererParam);
3178
+ if (typeof value === "string") {
3179
+ renderer = value;
3180
+ }
3181
+ } catch {
3182
+ }
3183
+ return {
3184
+ maxTextureSize,
3185
+ renderer,
3186
+ webglVersion: gl2 ? 2 : 1
3187
+ };
433
3188
  }
434
- function onJemBalanceChanged(callback) {
435
- return subscribeToStoreEvent("oasiz:jem-balance-changed", (state) => {
436
- callback(state.jemBalance);
437
- });
3189
+ function applyMemoryScore(score, memory) {
3190
+ if (typeof memory !== "number" || !Number.isFinite(memory) || memory <= 0) {
3191
+ return score;
3192
+ }
3193
+ if (memory <= 1) return score - 18;
3194
+ if (memory <= 2) return score - 10;
3195
+ if (memory >= 8) return score + 16;
3196
+ if (memory >= 6) return score + 10;
3197
+ return score;
3198
+ }
3199
+ function applyCoreScore(score, cores) {
3200
+ if (typeof cores !== "number" || !Number.isFinite(cores) || cores <= 0) {
3201
+ return score;
3202
+ }
3203
+ if (cores <= 2) return score - 12;
3204
+ if (cores <= 4) return score;
3205
+ if (cores >= 8) return score + 12;
3206
+ return score + 8;
3207
+ }
3208
+ function applyWebGlScore(score, info) {
3209
+ let next = score;
3210
+ if (info.webglVersion === 2) {
3211
+ next += 16;
3212
+ } else if (info.webglVersion === 1) {
3213
+ next += 6;
3214
+ } else {
3215
+ next -= 25;
3216
+ }
3217
+ if (typeof info.maxTextureSize === "number") {
3218
+ if (info.maxTextureSize >= 8192) next += 10;
3219
+ else if (info.maxTextureSize >= 4096) next += 3;
3220
+ else next -= 8;
3221
+ }
3222
+ const renderer = info.renderer?.toLowerCase() ?? "";
3223
+ if (/swiftshader|llvmpipe|software/.test(renderer)) {
3224
+ next = Math.min(next, 35);
3225
+ } else if (/\bm[1-9]\b|apple gpu|a1[6-9]|rtx|radeon|adreno 7|adreno 8/.test(renderer)) {
3226
+ next += 8;
3227
+ }
3228
+ return next;
3229
+ }
3230
+ function applyMobileCostScore(score, bridge) {
3231
+ if (!isCoarsePointer(bridge)) {
3232
+ return score;
3233
+ }
3234
+ const dpr = getDevicePixelRatio2(bridge);
3235
+ const width = bridge.screen?.width ?? bridge.innerWidth ?? 0;
3236
+ const height = bridge.screen?.height ?? bridge.innerHeight ?? 0;
3237
+ const physicalPixels = width * height * dpr * dpr;
3238
+ if (physicalPixels > 4e6) {
3239
+ return score - 5;
3240
+ }
3241
+ return score;
3242
+ }
3243
+ function applyAppleMobileRules(score, bridge) {
3244
+ if (!isAppleMobileDevice(bridge)) {
3245
+ return score;
3246
+ }
3247
+ const userAgent = bridge.navigator?.userAgent ?? "";
3248
+ const iosMajorVersion = parseIosMajorVersion(userAgent);
3249
+ const highDensityDisplay = getDevicePixelRatio2(bridge) >= 3;
3250
+ const longestScreenEdge = getLongestScreenEdge(bridge);
3251
+ if (typeof iosMajorVersion === "number" && iosMajorVersion < 15) {
3252
+ return Math.min(score, 35);
3253
+ }
3254
+ if (highDensityDisplay && longestScreenEdge >= 430 && (typeof iosMajorVersion === "undefined" || iosMajorVersion >= 17)) {
3255
+ return Math.max(score, 78);
3256
+ }
3257
+ if (highDensityDisplay && longestScreenEdge >= 414 && (typeof iosMajorVersion === "undefined" || iosMajorVersion >= 16)) {
3258
+ return Math.max(score, 62);
3259
+ }
3260
+ return Math.min(score, 48);
3261
+ }
3262
+ function estimateGraphicsPerformance(bridge) {
3263
+ const navigatorValue = getNavigatorValue(bridge);
3264
+ let score = 45;
3265
+ score = applyMemoryScore(score, navigatorValue?.deviceMemory);
3266
+ score = applyCoreScore(score, navigatorValue?.hardwareConcurrency);
3267
+ score = applyWebGlScore(score, getWebGlInfo(bridge));
3268
+ score = applyMobileCostScore(score, bridge);
3269
+ score = applyAppleMobileRules(score, bridge);
3270
+ const normalizedScore = clampScore(score);
3271
+ return {
3272
+ fps: fpsFromScore(normalizedScore),
3273
+ tier: tierFromScore(normalizedScore)
3274
+ };
3275
+ }
3276
+ function getGraphicsPerformance() {
3277
+ const bridge = getBridgeWindow10();
3278
+ if (!bridge) {
3279
+ return { ...DEFAULT_GRAPHICS_PERFORMANCE };
3280
+ }
3281
+ const hostMetric = readHostGraphicsPerformance(bridge);
3282
+ if (hostMetric) {
3283
+ return hostMetric;
3284
+ }
3285
+ cachedEstimatedGraphicsPerformance ??= estimateGraphicsPerformance(bridge);
3286
+ return { ...cachedEstimatedGraphicsPerformance };
438
3287
  }
439
3288
 
440
3289
  // src/index.ts
441
3290
  var oasiz = {
442
3291
  submitScore,
443
- emitScoreConfig,
3292
+ enableAppSimulator,
3293
+ addScore,
3294
+ setScore,
3295
+ getPlayerCharacter,
444
3296
  share,
445
3297
  triggerHaptic,
3298
+ enableLogOverlay,
446
3299
  loadGameState,
447
3300
  saveGameState,
448
3301
  flushGameState,
449
3302
  shareRoomCode,
3303
+ openInviteModal,
450
3304
  onPause,
451
3305
  onResume,
3306
+ getSafeAreaTop,
3307
+ getViewportInsets,
3308
+ setLeaderboardVisible,
3309
+ getGraphicsPerformance,
3310
+ enableBackButtonTesting,
452
3311
  onBackButton,
453
3312
  onLeaveGame,
454
3313
  leaveGame,
455
- store: {
456
- syncProducts,
457
- getProducts,
458
- getEntitlements,
459
- hasEntitlement,
460
- getQuantity,
461
- purchase,
462
- consume,
463
- getJemBalance,
464
- onEntitlementsChanged,
465
- onJemBalanceChanged
466
- },
467
3314
  get gameId() {
468
3315
  return getGameId();
469
3316
  },
470
3317
  get roomCode() {
471
3318
  return getRoomCode();
472
3319
  },
3320
+ get playerId() {
3321
+ return getPlayerId();
3322
+ },
473
3323
  get playerName() {
474
3324
  return getPlayerName();
475
3325
  },
476
3326
  get playerAvatar() {
477
3327
  return getPlayerAvatar();
3328
+ },
3329
+ get safeAreaTop() {
3330
+ return getSafeAreaTop();
3331
+ },
3332
+ get viewportInsets() {
3333
+ return getViewportInsets();
3334
+ },
3335
+ get graphicsPerformance() {
3336
+ return getGraphicsPerformance();
478
3337
  }
479
3338
  };
480
3339
  export {
481
- consume,
482
- emitScoreConfig,
3340
+ addScore,
3341
+ enableAppSimulator,
3342
+ enableBackButtonTesting,
3343
+ enableLogOverlay,
483
3344
  flushGameState,
484
- getEntitlements,
485
3345
  getGameId,
486
- getJemBalance,
3346
+ getGraphicsPerformance,
487
3347
  getPlayerAvatar,
3348
+ getPlayerCharacter,
3349
+ getPlayerId,
488
3350
  getPlayerName,
489
- getProducts,
490
- getQuantity,
491
3351
  getRoomCode,
492
- hasEntitlement,
3352
+ getSafeAreaTop,
3353
+ getViewportInsets,
493
3354
  leaveGame,
494
3355
  loadGameState,
495
3356
  oasiz,
496
3357
  onBackButton,
497
- onEntitlementsChanged,
498
- onJemBalanceChanged,
499
3358
  onLeaveGame,
500
3359
  onPause,
501
3360
  onResume,
502
- purchase,
3361
+ openInviteModal,
503
3362
  saveGameState,
3363
+ setLeaderboardVisible,
3364
+ setScore,
504
3365
  share,
505
3366
  shareRoomCode,
506
3367
  submitScore,
507
- syncProducts,
508
3368
  triggerHaptic
509
3369
  };