@oasiz/sdk 1.6.1 → 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,32 +1,1419 @@
1
+ // src/navigation.ts
2
+ var BACK_BUTTON_TEST_STATE_KEY = "__oasizBackButtonTest";
3
+ var activeBackListeners = 0;
4
+ var activeBackButtonTestingHandle;
5
+ function isDevelopment() {
6
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
7
+ return nodeEnv !== "production";
8
+ }
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
+
1
1388
  // src/character.ts
2
- function isDevelopment() {
1389
+ function isDevelopment2() {
3
1390
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
4
1391
  return nodeEnv !== "production";
5
1392
  }
6
- function getBridgeWindow() {
1393
+ function getBridgeWindow2() {
7
1394
  if (typeof window === "undefined") {
8
1395
  return void 0;
9
1396
  }
10
1397
  return window;
11
1398
  }
12
- function warnMissingBridge(methodName) {
13
- if (isDevelopment()) {
1399
+ function warnMissingBridge2(methodName) {
1400
+ if (isDevelopment2()) {
14
1401
  console.warn(
15
1402
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
16
1403
  );
17
1404
  }
18
1405
  }
19
1406
  async function getPlayerCharacter() {
20
- const bridge = getBridgeWindow();
1407
+ const bridge = getBridgeWindow2();
21
1408
  if (typeof bridge?.__oasizGetPlayerCharacter !== "function") {
22
- warnMissingBridge("getPlayerCharacter");
1409
+ warnMissingBridge2("getPlayerCharacter");
23
1410
  return null;
24
1411
  }
25
1412
  try {
26
1413
  const result = await bridge.__oasizGetPlayerCharacter();
27
1414
  return result ?? null;
28
1415
  } catch (error) {
29
- if (isDevelopment()) {
1416
+ if (isDevelopment2()) {
30
1417
  console.error("[oasiz/sdk] getPlayerCharacter failed:", error);
31
1418
  }
32
1419
  return null;
@@ -34,23 +1421,23 @@ async function getPlayerCharacter() {
34
1421
  }
35
1422
 
36
1423
  // src/haptics.ts
37
- function isDevelopment2() {
1424
+ function isDevelopment3() {
38
1425
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
39
1426
  return nodeEnv !== "production";
40
1427
  }
41
- function getBridgeWindow2() {
1428
+ function getBridgeWindow3() {
42
1429
  if (typeof window === "undefined") {
43
1430
  return void 0;
44
1431
  }
45
1432
  return window;
46
1433
  }
47
1434
  function triggerHaptic(type) {
48
- const bridge = getBridgeWindow2();
1435
+ const bridge = getBridgeWindow3();
49
1436
  if (typeof bridge?.triggerHaptic === "function") {
50
1437
  bridge.triggerHaptic(type);
51
1438
  return;
52
1439
  }
53
- if (isDevelopment2()) {
1440
+ if (isDevelopment3()) {
54
1441
  console.warn(
55
1442
  "[oasiz/sdk] triggerHaptic bridge is unavailable. This is expected in local development."
56
1443
  );
@@ -77,7 +1464,7 @@ var MIN_EXPANDED_WIDTH = 160;
77
1464
  var MIN_EXPANDED_HEIGHT = 110;
78
1465
  var RESIZE_HOTSPOT_PX = 28;
79
1466
  var TOP_DRAG_ZONE_PX = 44;
80
- var NOOP_HANDLE = {
1467
+ var NOOP_HANDLE2 = {
81
1468
  clear() {
82
1469
  },
83
1470
  destroy() {
@@ -90,13 +1477,13 @@ var NOOP_HANDLE = {
90
1477
  show() {
91
1478
  }
92
1479
  };
93
- function getBrowserWindow() {
1480
+ function getBrowserWindow2() {
94
1481
  if (typeof window === "undefined") {
95
1482
  return void 0;
96
1483
  }
97
1484
  return window;
98
1485
  }
99
- function getDocument() {
1486
+ function getDocument2() {
100
1487
  if (typeof document === "undefined") {
101
1488
  return void 0;
102
1489
  }
@@ -182,7 +1569,7 @@ function createEntry(level, args, id) {
182
1569
  timestamp: Date.now()
183
1570
  };
184
1571
  }
185
- function createButton(label) {
1572
+ function createButton2(label) {
186
1573
  const button = document.createElement("button");
187
1574
  button.type = "button";
188
1575
  button.textContent = label;
@@ -224,7 +1611,7 @@ function getLevelAccent(level) {
224
1611
  };
225
1612
  }
226
1613
  function getViewportSize() {
227
- const browserWindow = getBrowserWindow();
1614
+ const browserWindow = getBrowserWindow2();
228
1615
  return {
229
1616
  width: Math.max(320, browserWindow?.innerWidth ?? 1280),
230
1617
  height: Math.max(240, browserWindow?.innerHeight ?? 720)
@@ -354,7 +1741,7 @@ function stopResizing(state) {
354
1741
  state.removeResizeListeners = null;
355
1742
  }
356
1743
  function beginDragTracking(state, startPoint) {
357
- const doc = getDocument();
1744
+ const doc = getDocument2();
358
1745
  if (!doc) {
359
1746
  return;
360
1747
  }
@@ -421,7 +1808,7 @@ function beginDragTracking(state, startPoint) {
421
1808
  };
422
1809
  }
423
1810
  function startResizing(state, startPoint) {
424
- const doc = getDocument();
1811
+ const doc = getDocument2();
425
1812
  if (!doc) {
426
1813
  return;
427
1814
  }
@@ -507,7 +1894,7 @@ function attachDragStartListeners(element, state, options = {}) {
507
1894
  }
508
1895
  function attachCollapsedToggleListeners(element, state) {
509
1896
  const startToggleInteraction = (startPoint) => {
510
- const doc = getDocument();
1897
+ const doc = getDocument2();
511
1898
  if (!doc) {
512
1899
  return;
513
1900
  }
@@ -583,7 +1970,7 @@ function createOverlayUi(state) {
583
1970
  "width:min(565px, calc(100vw - 24px))",
584
1971
  "pointer-events:none"
585
1972
  ].join(";");
586
- const toggleButton = createButton("Logs");
1973
+ const toggleButton = createButton2("Logs");
587
1974
  toggleButton.style.pointerEvents = "auto";
588
1975
  toggleButton.style.alignSelf = "flex-end";
589
1976
  toggleButton.style.display = "inline-flex";
@@ -637,7 +2024,7 @@ function createOverlayUi(state) {
637
2024
  "gap:8px",
638
2025
  "pointer-events:auto"
639
2026
  ].join(";");
640
- const clearButton = createButton("Clear");
2027
+ const clearButton = createButton2("Clear");
641
2028
  clearButton.style.background = "rgba(255,255,255,0.1)";
642
2029
  clearButton.style.border = "1px solid rgba(255,255,255,0.16)";
643
2030
  clearButton.style.color = "#eef6ff";
@@ -645,7 +2032,7 @@ function createOverlayUi(state) {
645
2032
  clearButton.style.padding = "4px 9px";
646
2033
  clearButton.style.fontSize = "11px";
647
2034
  clearButton.style.backdropFilter = "blur(8px)";
648
- const collapseButton = createButton("Hide");
2035
+ const collapseButton = createButton2("Hide");
649
2036
  collapseButton.style.background = "rgba(113, 171, 255, 0.12)";
650
2037
  collapseButton.style.border = "1px solid rgba(113, 171, 255, 0.2)";
651
2038
  collapseButton.style.color = "#d9ebff";
@@ -771,7 +2158,7 @@ function renderOverlay(state) {
771
2158
  applyOverlayPosition(state);
772
2159
  }
773
2160
  function mountOverlay(state) {
774
- const doc = getDocument();
2161
+ const doc = getDocument2();
775
2162
  if (!doc?.body || state.ui) {
776
2163
  return;
777
2164
  }
@@ -811,7 +2198,7 @@ function cleanupOverlay(browserWindow, state) {
811
2198
  stopResizing(state);
812
2199
  stopDragging(state);
813
2200
  if (state.domReadyHandler) {
814
- getDocument()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
2201
+ getDocument2()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
815
2202
  state.domReadyHandler = void 0;
816
2203
  }
817
2204
  if (state.resizeHandler && typeof browserWindow.removeEventListener === "function") {
@@ -830,7 +2217,7 @@ function createController(browserWindow, state) {
830
2217
  this.ensureMounted();
831
2218
  },
832
2219
  ensureMounted() {
833
- const doc = getDocument();
2220
+ const doc = getDocument2();
834
2221
  if (!doc) {
835
2222
  return;
836
2223
  }
@@ -876,12 +2263,12 @@ function createController(browserWindow, state) {
876
2263
  }
877
2264
  function enableLogOverlay(options = {}) {
878
2265
  if (options.enabled === false) {
879
- return NOOP_HANDLE;
2266
+ return NOOP_HANDLE2;
880
2267
  }
881
- const browserWindow = getBrowserWindow();
882
- const doc = getDocument();
2268
+ const browserWindow = getBrowserWindow2();
2269
+ const doc = getDocument2();
883
2270
  if (!browserWindow || !doc) {
884
- return NOOP_HANDLE;
2271
+ return NOOP_HANDLE2;
885
2272
  }
886
2273
  const existingController = browserWindow.__oasizLogOverlayController__;
887
2274
  const existingState = browserWindow.__oasizLogOverlayState__;
@@ -949,74 +2336,74 @@ function enableLogOverlay(options = {}) {
949
2336
  }
950
2337
 
951
2338
  // src/multiplayer.ts
952
- function isDevelopment3() {
2339
+ function isDevelopment4() {
953
2340
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
954
2341
  return nodeEnv !== "production";
955
2342
  }
956
- function getBridgeWindow3() {
2343
+ function getBridgeWindow4() {
957
2344
  if (typeof window === "undefined") {
958
2345
  return void 0;
959
2346
  }
960
2347
  return window;
961
2348
  }
962
2349
  function shareRoomCode(roomCode, options) {
963
- const bridge = getBridgeWindow3();
2350
+ const bridge = getBridgeWindow4();
964
2351
  if (typeof bridge?.shareRoomCode === "function") {
965
2352
  bridge.shareRoomCode(roomCode, options);
966
2353
  return;
967
2354
  }
968
- if (isDevelopment3()) {
2355
+ if (isDevelopment4()) {
969
2356
  console.warn(
970
2357
  "[oasiz/sdk] shareRoomCode bridge is unavailable. This is expected in local development."
971
2358
  );
972
2359
  }
973
2360
  }
974
2361
  function openInviteModal() {
975
- const bridge = getBridgeWindow3();
2362
+ const bridge = getBridgeWindow4();
976
2363
  if (typeof bridge?.openInviteModal === "function") {
977
2364
  bridge.openInviteModal();
978
2365
  return;
979
2366
  }
980
- if (isDevelopment3()) {
2367
+ if (isDevelopment4()) {
981
2368
  console.warn(
982
2369
  "[oasiz/sdk] openInviteModal bridge is unavailable. This is expected in local development."
983
2370
  );
984
2371
  }
985
2372
  }
986
2373
  function getGameId() {
987
- const bridge = getBridgeWindow3();
2374
+ const bridge = getBridgeWindow4();
988
2375
  return bridge?.__GAME_ID__;
989
2376
  }
990
2377
  function getRoomCode() {
991
- const bridge = getBridgeWindow3();
2378
+ const bridge = getBridgeWindow4();
992
2379
  return bridge?.__ROOM_CODE__;
993
2380
  }
994
2381
  function getPlayerId() {
995
- const bridge = getBridgeWindow3();
2382
+ const bridge = getBridgeWindow4();
996
2383
  return bridge?.__PLAYER_ID__;
997
2384
  }
998
2385
  function getPlayerName() {
999
- const bridge = getBridgeWindow3();
2386
+ const bridge = getBridgeWindow4();
1000
2387
  return bridge?.__PLAYER_NAME__;
1001
2388
  }
1002
2389
  function getPlayerAvatar() {
1003
- const bridge = getBridgeWindow3();
2390
+ const bridge = getBridgeWindow4();
1004
2391
  return bridge?.__PLAYER_AVATAR__;
1005
2392
  }
1006
2393
 
1007
2394
  // src/score.ts
1008
- function isDevelopment4() {
2395
+ function isDevelopment5() {
1009
2396
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1010
2397
  return nodeEnv !== "production";
1011
2398
  }
1012
- function warnMissingBridge2(methodName) {
1013
- if (isDevelopment4()) {
2399
+ function warnMissingBridge3(methodName) {
2400
+ if (isDevelopment5()) {
1014
2401
  console.warn(
1015
2402
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1016
2403
  );
1017
2404
  }
1018
2405
  }
1019
- function getBridgeWindow4() {
2406
+ function getBridgeWindow5() {
1020
2407
  if (typeof window === "undefined") {
1021
2408
  return void 0;
1022
2409
  }
@@ -1024,49 +2411,49 @@ function getBridgeWindow4() {
1024
2411
  }
1025
2412
  function submitScore(score) {
1026
2413
  if (!Number.isFinite(score)) {
1027
- if (isDevelopment4()) {
2414
+ if (isDevelopment5()) {
1028
2415
  console.warn("[oasiz/sdk] submitScore expected a finite number:", score);
1029
2416
  }
1030
2417
  return;
1031
2418
  }
1032
- const bridge = getBridgeWindow4();
2419
+ const bridge = getBridgeWindow5();
1033
2420
  const normalizedScore = Math.max(0, Math.floor(score));
1034
2421
  if (typeof bridge?.submitScore === "function") {
1035
2422
  bridge.submitScore(normalizedScore);
1036
2423
  return;
1037
2424
  }
1038
- warnMissingBridge2("submitScore");
2425
+ warnMissingBridge3("submitScore");
1039
2426
  }
1040
2427
 
1041
2428
  // src/score-edit.ts
1042
- function isDevelopment5() {
2429
+ function isDevelopment6() {
1043
2430
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1044
2431
  return nodeEnv !== "production";
1045
2432
  }
1046
- function getBridgeWindow5() {
2433
+ function getBridgeWindow6() {
1047
2434
  if (typeof window === "undefined") {
1048
2435
  return void 0;
1049
2436
  }
1050
2437
  return window;
1051
2438
  }
1052
- function warnMissingBridge3(methodName) {
1053
- if (isDevelopment5()) {
2439
+ function warnMissingBridge4(methodName) {
2440
+ if (isDevelopment6()) {
1054
2441
  console.warn(
1055
2442
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1056
2443
  );
1057
2444
  }
1058
2445
  }
1059
2446
  async function editScore(payload, methodName) {
1060
- const bridge = getBridgeWindow5();
2447
+ const bridge = getBridgeWindow6();
1061
2448
  if (typeof bridge?.__oasizEditScore !== "function") {
1062
- warnMissingBridge3(methodName);
2449
+ warnMissingBridge4(methodName);
1063
2450
  return null;
1064
2451
  }
1065
2452
  try {
1066
2453
  const result = await bridge.__oasizEditScore(payload);
1067
2454
  return result ?? null;
1068
2455
  } catch (error) {
1069
- if (isDevelopment5()) {
2456
+ if (isDevelopment6()) {
1070
2457
  console.error("[oasiz/sdk] " + methodName + " failed:", error);
1071
2458
  }
1072
2459
  return null;
@@ -1074,7 +2461,7 @@ async function editScore(payload, methodName) {
1074
2461
  }
1075
2462
  async function addScore(delta) {
1076
2463
  if (!Number.isInteger(delta)) {
1077
- if (isDevelopment5()) {
2464
+ if (isDevelopment6()) {
1078
2465
  console.warn("[oasiz/sdk] addScore expected an integer:", delta);
1079
2466
  }
1080
2467
  return null;
@@ -1086,7 +2473,7 @@ async function addScore(delta) {
1086
2473
  }
1087
2474
  async function setScore(score) {
1088
2475
  if (!Number.isInteger(score) || score < 0) {
1089
- if (isDevelopment5()) {
2476
+ if (isDevelopment6()) {
1090
2477
  console.warn(
1091
2478
  "[oasiz/sdk] setScore expected a non-negative integer:",
1092
2479
  score
@@ -1098,18 +2485,18 @@ async function setScore(score) {
1098
2485
  }
1099
2486
 
1100
2487
  // src/share.ts
1101
- function isDevelopment6() {
2488
+ function isDevelopment7() {
1102
2489
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1103
2490
  return nodeEnv !== "production";
1104
2491
  }
1105
- function getBridgeWindow6() {
2492
+ function getBridgeWindow7() {
1106
2493
  if (typeof window === "undefined") {
1107
2494
  return void 0;
1108
2495
  }
1109
2496
  return window;
1110
2497
  }
1111
- function warnMissingBridge4(methodName) {
1112
- if (isDevelopment6()) {
2498
+ function warnMissingBridge5(methodName) {
2499
+ if (isDevelopment7()) {
1113
2500
  console.warn(
1114
2501
  "[oasiz/sdk] " + methodName + " share bridge is unavailable. This is expected in local development."
1115
2502
  );
@@ -1152,20 +2539,20 @@ function validateRequest(options) {
1152
2539
  }
1153
2540
  async function share(options) {
1154
2541
  const request = validateRequest(options);
1155
- const bridge = getBridgeWindow6();
2542
+ const bridge = getBridgeWindow7();
1156
2543
  if (typeof bridge?.__oasizShareRequest !== "function") {
1157
- warnMissingBridge4("__oasizShareRequest");
2544
+ warnMissingBridge5("__oasizShareRequest");
1158
2545
  throw new Error("Share bridge unavailable");
1159
2546
  }
1160
2547
  await bridge.__oasizShareRequest(request);
1161
2548
  }
1162
2549
 
1163
2550
  // src/state.ts
1164
- function isDevelopment7() {
2551
+ function isDevelopment8() {
1165
2552
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1166
2553
  return nodeEnv !== "production";
1167
2554
  }
1168
- function getBridgeWindow7() {
2555
+ function getBridgeWindow8() {
1169
2556
  if (typeof window === "undefined") {
1170
2557
  return void 0;
1171
2558
  }
@@ -1178,22 +2565,22 @@ function isPlainObject(value) {
1178
2565
  const proto = Object.getPrototypeOf(value);
1179
2566
  return proto === Object.prototype || proto === null;
1180
2567
  }
1181
- function warnMissingBridge5(methodName) {
1182
- if (isDevelopment7()) {
2568
+ function warnMissingBridge6(methodName) {
2569
+ if (isDevelopment8()) {
1183
2570
  console.warn(
1184
2571
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1185
2572
  );
1186
2573
  }
1187
2574
  }
1188
2575
  function loadGameState() {
1189
- const bridge = getBridgeWindow7();
2576
+ const bridge = getBridgeWindow8();
1190
2577
  if (typeof bridge?.loadGameState !== "function") {
1191
- warnMissingBridge5("loadGameState");
2578
+ warnMissingBridge6("loadGameState");
1192
2579
  return {};
1193
2580
  }
1194
2581
  const state = bridge.loadGameState();
1195
2582
  if (!isPlainObject(state)) {
1196
- if (isDevelopment7()) {
2583
+ if (isDevelopment8()) {
1197
2584
  console.warn(
1198
2585
  "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
1199
2586
  );
@@ -1204,35 +2591,35 @@ function loadGameState() {
1204
2591
  }
1205
2592
  function saveGameState(state) {
1206
2593
  if (!isPlainObject(state)) {
1207
- if (isDevelopment7()) {
2594
+ if (isDevelopment8()) {
1208
2595
  console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
1209
2596
  }
1210
2597
  return;
1211
2598
  }
1212
- const bridge = getBridgeWindow7();
2599
+ const bridge = getBridgeWindow8();
1213
2600
  if (typeof bridge?.saveGameState === "function") {
1214
2601
  bridge.saveGameState(state);
1215
2602
  return;
1216
2603
  }
1217
- warnMissingBridge5("saveGameState");
2604
+ warnMissingBridge6("saveGameState");
1218
2605
  }
1219
2606
  function flushGameState() {
1220
- const bridge = getBridgeWindow7();
2607
+ const bridge = getBridgeWindow8();
1221
2608
  if (typeof bridge?.flushGameState === "function") {
1222
2609
  bridge.flushGameState();
1223
2610
  return;
1224
2611
  }
1225
- warnMissingBridge5("flushGameState");
2612
+ warnMissingBridge6("flushGameState");
1226
2613
  }
1227
2614
 
1228
2615
  // src/lifecycle.ts
1229
- function isDevelopment8() {
2616
+ function isDevelopment9() {
1230
2617
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1231
2618
  return nodeEnv !== "production";
1232
2619
  }
1233
2620
  function addLifecycleListener(eventName, callback) {
1234
2621
  if (typeof window === "undefined") {
1235
- if (isDevelopment8()) {
2622
+ if (isDevelopment9()) {
1236
2623
  console.warn(
1237
2624
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1238
2625
  );
@@ -1267,24 +2654,24 @@ function createInsetEdges(value) {
1267
2654
  left: value
1268
2655
  };
1269
2656
  }
1270
- function isDevelopment9() {
2657
+ function isDevelopment10() {
1271
2658
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1272
2659
  return nodeEnv !== "production";
1273
2660
  }
1274
- function getBridgeWindow8() {
2661
+ function getBridgeWindow9() {
1275
2662
  if (typeof window === "undefined") {
1276
2663
  return void 0;
1277
2664
  }
1278
2665
  return window;
1279
2666
  }
1280
- function warnMissingBridge6(methodName) {
1281
- if (isDevelopment9()) {
2667
+ function warnMissingBridge7(methodName) {
2668
+ if (isDevelopment10()) {
1282
2669
  console.warn(
1283
2670
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1284
2671
  );
1285
2672
  }
1286
2673
  }
1287
- function isRecord(value) {
2674
+ function isRecord2(value) {
1288
2675
  return typeof value === "object" && value !== null;
1289
2676
  }
1290
2677
  function toFiniteNumber(value) {
@@ -1453,11 +2840,11 @@ function callBridgeFunction(bridge, name) {
1453
2840
  }
1454
2841
  }
1455
2842
  function readInsetObjectValue(value, side, group) {
1456
- if (!isRecord(value)) {
2843
+ if (!isRecord2(value)) {
1457
2844
  return void 0;
1458
2845
  }
1459
2846
  if (group) {
1460
- return isRecord(value[group]) ? value[group][side] : void 0;
2847
+ return isRecord2(value[group]) ? value[group][side] : void 0;
1461
2848
  }
1462
2849
  return value[side];
1463
2850
  }
@@ -1530,7 +2917,7 @@ function resolveInsetSide(bridge, sources, side) {
1530
2917
  };
1531
2918
  }
1532
2919
  function getViewportInsets() {
1533
- const bridge = getBridgeWindow8();
2920
+ const bridge = getBridgeWindow9();
1534
2921
  if (!bridge) {
1535
2922
  return {
1536
2923
  pixels: createInsetEdges(0),
@@ -1548,19 +2935,19 @@ function getViewportInsets() {
1548
2935
  return { pixels, percent };
1549
2936
  }
1550
2937
  function getSafeAreaTop() {
1551
- const bridge = getBridgeWindow8();
2938
+ const bridge = getBridgeWindow9();
1552
2939
  if (!bridge) {
1553
2940
  return 0;
1554
2941
  }
1555
2942
  const top = getViewportInsets().percent.top;
1556
2943
  if (top <= 0) {
1557
- warnMissingBridge6("getSafeAreaTop");
2944
+ warnMissingBridge7("getSafeAreaTop");
1558
2945
  }
1559
2946
  return top;
1560
2947
  }
1561
2948
  function setLeaderboardVisible(visible) {
1562
2949
  if (typeof visible !== "boolean") {
1563
- if (isDevelopment9()) {
2950
+ if (isDevelopment10()) {
1564
2951
  console.warn(
1565
2952
  "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
1566
2953
  visible
@@ -1568,253 +2955,12 @@ function setLeaderboardVisible(visible) {
1568
2955
  }
1569
2956
  return;
1570
2957
  }
1571
- const bridge = getBridgeWindow8();
2958
+ const bridge = getBridgeWindow9();
1572
2959
  if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
1573
2960
  bridge.__oasizSetLeaderboardVisible(visible);
1574
2961
  return;
1575
2962
  }
1576
- warnMissingBridge6("__oasizSetLeaderboardVisible");
1577
- }
1578
-
1579
- // src/navigation.ts
1580
- var BACK_BUTTON_TEST_STATE_KEY = "__oasizBackButtonTest";
1581
- var activeBackListeners = 0;
1582
- var activeBackButtonTestingHandle;
1583
- function isDevelopment10() {
1584
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
1585
- return nodeEnv !== "production";
1586
- }
1587
- function getBridgeWindow9() {
1588
- if (typeof window === "undefined") {
1589
- return void 0;
1590
- }
1591
- return window;
1592
- }
1593
- function warnMissingBridge7(methodName) {
1594
- if (isDevelopment10()) {
1595
- console.warn(
1596
- "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1597
- );
1598
- }
1599
- }
1600
- function normalizeNavigationError(error) {
1601
- if (error instanceof Error) {
1602
- return error;
1603
- }
1604
- return new Error(
1605
- typeof error === "string" ? error : "Back button callback failed."
1606
- );
1607
- }
1608
- function isRecord2(value) {
1609
- return typeof value === "object" && value !== null;
1610
- }
1611
- function dispatchNavigationEvent(eventName) {
1612
- const bridge = getBridgeWindow9();
1613
- if (!bridge || typeof bridge.dispatchEvent !== "function") {
1614
- return;
1615
- }
1616
- bridge.dispatchEvent(new Event(eventName));
1617
- }
1618
- function addNavigationListener(eventName, callback) {
1619
- if (typeof window === "undefined") {
1620
- if (isDevelopment10()) {
1621
- console.warn(
1622
- "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1623
- );
1624
- }
1625
- return () => {
1626
- };
1627
- }
1628
- const handler = () => callback();
1629
- window.addEventListener(eventName, handler);
1630
- return () => window.removeEventListener(eventName, handler);
1631
- }
1632
- function enableBackButtonTesting(options = {}) {
1633
- activeBackButtonTestingHandle?.destroy();
1634
- const bridge = getBridgeWindow9();
1635
- if (!bridge) {
1636
- if (isDevelopment10()) {
1637
- console.warn(
1638
- "[oasiz/sdk] enableBackButtonTesting requires a browser window."
1639
- );
1640
- }
1641
- return {
1642
- destroy: () => {
1643
- },
1644
- isBackOverrideActive: () => false,
1645
- triggerBack: () => {
1646
- },
1647
- triggerLeave: () => {
1648
- }
1649
- };
1650
- }
1651
- const bridgeWindow = bridge;
1652
- const keyboard = options.keyboard ?? true;
1653
- const browserHistory = options.browserHistory ?? true;
1654
- const log = options.log === true;
1655
- const previousSetBackOverride = bridgeWindow.__oasizSetBackOverride;
1656
- const previousLeaveGame = bridgeWindow.__oasizLeaveGame;
1657
- let destroyed = false;
1658
- let backOverrideActive = false;
1659
- let historyTrapArmed = false;
1660
- function maybeLog(message) {
1661
- if (log) {
1662
- console.info("[oasiz/sdk] " + message);
1663
- }
1664
- }
1665
- function canUseHistoryTrap() {
1666
- return browserHistory && typeof bridgeWindow.history?.pushState === "function" && typeof bridgeWindow.history?.replaceState === "function" && typeof bridgeWindow.location?.href === "string";
1667
- }
1668
- function ensureHistoryTrap() {
1669
- if (!backOverrideActive || historyTrapArmed || !canUseHistoryTrap()) {
1670
- return;
1671
- }
1672
- try {
1673
- const currentState = isRecord2(bridgeWindow.history.state) ? bridgeWindow.history.state : {};
1674
- bridgeWindow.history.replaceState(
1675
- { ...currentState, [BACK_BUTTON_TEST_STATE_KEY]: "base" },
1676
- "",
1677
- bridgeWindow.location.href
1678
- );
1679
- bridgeWindow.history.pushState(
1680
- { [BACK_BUTTON_TEST_STATE_KEY]: "trap" },
1681
- "",
1682
- bridgeWindow.location.href
1683
- );
1684
- historyTrapArmed = true;
1685
- maybeLog("Local browser Back testing is armed.");
1686
- } catch (error) {
1687
- historyTrapArmed = false;
1688
- if (log) {
1689
- console.warn("[oasiz/sdk] Failed to arm browser Back testing:", error);
1690
- }
1691
- }
1692
- }
1693
- function triggerBack() {
1694
- dispatchNavigationEvent("oasiz:back");
1695
- }
1696
- function triggerLeave() {
1697
- dispatchNavigationEvent("oasiz:leave");
1698
- }
1699
- function setBackOverride(active) {
1700
- backOverrideActive = active;
1701
- if (active) {
1702
- ensureHistoryTrap();
1703
- }
1704
- if (typeof previousSetBackOverride === "function") {
1705
- previousSetBackOverride(active);
1706
- }
1707
- maybeLog("Back override " + (active ? "enabled" : "disabled") + ".");
1708
- }
1709
- function stopBackEvent(event) {
1710
- event.preventDefault();
1711
- event.stopPropagation();
1712
- event.stopImmediatePropagation?.();
1713
- }
1714
- const handleKeyDown = (event) => {
1715
- if (!backOverrideActive || event.key !== "Escape") {
1716
- return;
1717
- }
1718
- stopBackEvent(event);
1719
- triggerBack();
1720
- };
1721
- const handlePopState = (event) => {
1722
- if (!backOverrideActive) {
1723
- historyTrapArmed = false;
1724
- return;
1725
- }
1726
- stopBackEvent(event);
1727
- triggerBack();
1728
- historyTrapArmed = false;
1729
- ensureHistoryTrap();
1730
- };
1731
- const testLeaveGame = () => {
1732
- triggerLeave();
1733
- if (typeof previousLeaveGame === "function") {
1734
- previousLeaveGame();
1735
- }
1736
- };
1737
- bridgeWindow.__oasizSetBackOverride = setBackOverride;
1738
- bridgeWindow.__oasizLeaveGame = testLeaveGame;
1739
- if (keyboard) {
1740
- bridgeWindow.addEventListener("keydown", handleKeyDown);
1741
- }
1742
- if (browserHistory) {
1743
- bridgeWindow.addEventListener("popstate", handlePopState);
1744
- }
1745
- if (activeBackListeners > 0) {
1746
- setBackOverride(true);
1747
- }
1748
- maybeLog("Back button testing bridge installed.");
1749
- const handle = {
1750
- destroy: () => {
1751
- if (destroyed) return;
1752
- destroyed = true;
1753
- if (keyboard) {
1754
- bridgeWindow.removeEventListener("keydown", handleKeyDown);
1755
- }
1756
- if (browserHistory) {
1757
- bridgeWindow.removeEventListener("popstate", handlePopState);
1758
- }
1759
- if (bridgeWindow.__oasizSetBackOverride === setBackOverride) {
1760
- bridgeWindow.__oasizSetBackOverride = previousSetBackOverride;
1761
- }
1762
- if (bridgeWindow.__oasizLeaveGame === testLeaveGame) {
1763
- bridgeWindow.__oasizLeaveGame = previousLeaveGame;
1764
- }
1765
- if (activeBackButtonTestingHandle === handle) {
1766
- activeBackButtonTestingHandle = void 0;
1767
- }
1768
- maybeLog("Back button testing bridge removed.");
1769
- },
1770
- isBackOverrideActive: () => backOverrideActive,
1771
- triggerBack,
1772
- triggerLeave
1773
- };
1774
- activeBackButtonTestingHandle = handle;
1775
- return handle;
1776
- }
1777
- function onBackButton(callback) {
1778
- const off = addNavigationListener("oasiz:back", () => {
1779
- try {
1780
- callback();
1781
- } catch (error) {
1782
- leaveGame();
1783
- throw normalizeNavigationError(error);
1784
- }
1785
- });
1786
- const bridge = getBridgeWindow9();
1787
- activeBackListeners += 1;
1788
- if (activeBackListeners === 1) {
1789
- if (typeof bridge?.__oasizSetBackOverride === "function") {
1790
- bridge.__oasizSetBackOverride(true);
1791
- } else {
1792
- warnMissingBridge7("__oasizSetBackOverride");
1793
- }
1794
- }
1795
- return () => {
1796
- off();
1797
- activeBackListeners = Math.max(0, activeBackListeners - 1);
1798
- if (activeBackListeners === 0) {
1799
- const currentBridge = getBridgeWindow9();
1800
- if (typeof currentBridge?.__oasizSetBackOverride === "function") {
1801
- currentBridge.__oasizSetBackOverride(false);
1802
- } else {
1803
- warnMissingBridge7("__oasizSetBackOverride");
1804
- }
1805
- }
1806
- };
1807
- }
1808
- function onLeaveGame(callback) {
1809
- return addNavigationListener("oasiz:leave", callback);
1810
- }
1811
- function leaveGame() {
1812
- const bridge = getBridgeWindow9();
1813
- if (typeof bridge?.__oasizLeaveGame === "function") {
1814
- bridge.__oasizLeaveGame();
1815
- return;
1816
- }
1817
- warnMissingBridge7("__oasizLeaveGame");
2963
+ warnMissingBridge7("__oasizSetLeaderboardVisible");
1818
2964
  }
1819
2965
 
1820
2966
  // src/performance.ts
@@ -2143,6 +3289,7 @@ function getGraphicsPerformance() {
2143
3289
  // src/index.ts
2144
3290
  var oasiz = {
2145
3291
  submitScore,
3292
+ enableAppSimulator,
2146
3293
  addScore,
2147
3294
  setScore,
2148
3295
  getPlayerCharacter,
@@ -2191,6 +3338,7 @@ var oasiz = {
2191
3338
  };
2192
3339
  export {
2193
3340
  addScore,
3341
+ enableAppSimulator,
2194
3342
  enableBackButtonTesting,
2195
3343
  enableLogOverlay,
2196
3344
  flushGameState,