@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.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  addScore: () => addScore,
24
+ enableAppSimulator: () => enableAppSimulator,
24
25
  enableBackButtonTesting: () => enableBackButtonTesting,
25
26
  enableLogOverlay: () => enableLogOverlay,
26
27
  flushGameState: () => flushGameState,
@@ -51,35 +52,1422 @@ __export(index_exports, {
51
52
  });
52
53
  module.exports = __toCommonJS(index_exports);
53
54
 
55
+ // src/navigation.ts
56
+ var BACK_BUTTON_TEST_STATE_KEY = "__oasizBackButtonTest";
57
+ var activeBackListeners = 0;
58
+ var activeBackButtonTestingHandle;
59
+ function isDevelopment() {
60
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
61
+ return nodeEnv !== "production";
62
+ }
63
+ function getBridgeWindow() {
64
+ if (typeof window === "undefined") {
65
+ return void 0;
66
+ }
67
+ return window;
68
+ }
69
+ function warnMissingBridge(methodName) {
70
+ if (isDevelopment()) {
71
+ console.warn(
72
+ "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
73
+ );
74
+ }
75
+ }
76
+ function normalizeNavigationError(error) {
77
+ if (error instanceof Error) {
78
+ return error;
79
+ }
80
+ return new Error(
81
+ typeof error === "string" ? error : "Back button callback failed."
82
+ );
83
+ }
84
+ function isRecord(value) {
85
+ return typeof value === "object" && value !== null;
86
+ }
87
+ function dispatchNavigationEvent(eventName) {
88
+ const bridge = getBridgeWindow();
89
+ if (!bridge || typeof bridge.dispatchEvent !== "function") {
90
+ return;
91
+ }
92
+ bridge.dispatchEvent(new Event(eventName));
93
+ }
94
+ function addNavigationListener(eventName, callback) {
95
+ if (typeof window === "undefined") {
96
+ if (isDevelopment()) {
97
+ console.warn(
98
+ "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
99
+ );
100
+ }
101
+ return () => {
102
+ };
103
+ }
104
+ const handler = () => callback();
105
+ window.addEventListener(eventName, handler);
106
+ return () => window.removeEventListener(eventName, handler);
107
+ }
108
+ function enableBackButtonTesting(options = {}) {
109
+ activeBackButtonTestingHandle?.destroy();
110
+ const bridge = getBridgeWindow();
111
+ if (!bridge) {
112
+ if (isDevelopment()) {
113
+ console.warn(
114
+ "[oasiz/sdk] enableBackButtonTesting requires a browser window."
115
+ );
116
+ }
117
+ return {
118
+ destroy: () => {
119
+ },
120
+ isBackOverrideActive: () => false,
121
+ triggerBack: () => {
122
+ },
123
+ triggerLeave: () => {
124
+ }
125
+ };
126
+ }
127
+ const bridgeWindow = bridge;
128
+ const keyboard = options.keyboard ?? true;
129
+ const browserHistory = options.browserHistory ?? true;
130
+ const log = options.log === true;
131
+ const previousSetBackOverride = bridgeWindow.__oasizSetBackOverride;
132
+ const previousLeaveGame = bridgeWindow.__oasizLeaveGame;
133
+ let destroyed = false;
134
+ let backOverrideActive = false;
135
+ let historyTrapArmed = false;
136
+ function maybeLog(message) {
137
+ if (log) {
138
+ console.info("[oasiz/sdk] " + message);
139
+ }
140
+ }
141
+ function canUseHistoryTrap() {
142
+ return browserHistory && typeof bridgeWindow.history?.pushState === "function" && typeof bridgeWindow.history?.replaceState === "function" && typeof bridgeWindow.location?.href === "string";
143
+ }
144
+ function ensureHistoryTrap() {
145
+ if (!backOverrideActive || historyTrapArmed || !canUseHistoryTrap()) {
146
+ return;
147
+ }
148
+ try {
149
+ const currentState = isRecord(bridgeWindow.history.state) ? bridgeWindow.history.state : {};
150
+ bridgeWindow.history.replaceState(
151
+ { ...currentState, [BACK_BUTTON_TEST_STATE_KEY]: "base" },
152
+ "",
153
+ bridgeWindow.location.href
154
+ );
155
+ bridgeWindow.history.pushState(
156
+ { [BACK_BUTTON_TEST_STATE_KEY]: "trap" },
157
+ "",
158
+ bridgeWindow.location.href
159
+ );
160
+ historyTrapArmed = true;
161
+ maybeLog("Local browser Back testing is armed.");
162
+ } catch (error) {
163
+ historyTrapArmed = false;
164
+ if (log) {
165
+ console.warn("[oasiz/sdk] Failed to arm browser Back testing:", error);
166
+ }
167
+ }
168
+ }
169
+ function triggerBack() {
170
+ dispatchNavigationEvent("oasiz:back");
171
+ }
172
+ function triggerLeave() {
173
+ dispatchNavigationEvent("oasiz:leave");
174
+ }
175
+ function setBackOverride(active) {
176
+ backOverrideActive = active;
177
+ if (active) {
178
+ ensureHistoryTrap();
179
+ }
180
+ if (typeof previousSetBackOverride === "function") {
181
+ previousSetBackOverride(active);
182
+ }
183
+ maybeLog("Back override " + (active ? "enabled" : "disabled") + ".");
184
+ }
185
+ function stopBackEvent(event) {
186
+ event.preventDefault();
187
+ event.stopPropagation();
188
+ event.stopImmediatePropagation?.();
189
+ }
190
+ const handleKeyDown = (event) => {
191
+ if (!backOverrideActive || event.key !== "Escape") {
192
+ return;
193
+ }
194
+ stopBackEvent(event);
195
+ triggerBack();
196
+ };
197
+ const handlePopState = (event) => {
198
+ if (!backOverrideActive) {
199
+ historyTrapArmed = false;
200
+ return;
201
+ }
202
+ stopBackEvent(event);
203
+ triggerBack();
204
+ historyTrapArmed = false;
205
+ ensureHistoryTrap();
206
+ };
207
+ const testLeaveGame = () => {
208
+ triggerLeave();
209
+ if (typeof previousLeaveGame === "function") {
210
+ previousLeaveGame();
211
+ }
212
+ };
213
+ bridgeWindow.__oasizSetBackOverride = setBackOverride;
214
+ bridgeWindow.__oasizLeaveGame = testLeaveGame;
215
+ if (keyboard) {
216
+ bridgeWindow.addEventListener("keydown", handleKeyDown);
217
+ }
218
+ if (browserHistory) {
219
+ bridgeWindow.addEventListener("popstate", handlePopState);
220
+ }
221
+ if (activeBackListeners > 0) {
222
+ setBackOverride(true);
223
+ }
224
+ maybeLog("Back button testing bridge installed.");
225
+ const handle = {
226
+ destroy: () => {
227
+ if (destroyed) return;
228
+ destroyed = true;
229
+ if (keyboard) {
230
+ bridgeWindow.removeEventListener("keydown", handleKeyDown);
231
+ }
232
+ if (browserHistory) {
233
+ bridgeWindow.removeEventListener("popstate", handlePopState);
234
+ }
235
+ if (bridgeWindow.__oasizSetBackOverride === setBackOverride) {
236
+ bridgeWindow.__oasizSetBackOverride = previousSetBackOverride;
237
+ }
238
+ if (bridgeWindow.__oasizLeaveGame === testLeaveGame) {
239
+ bridgeWindow.__oasizLeaveGame = previousLeaveGame;
240
+ }
241
+ if (activeBackButtonTestingHandle === handle) {
242
+ activeBackButtonTestingHandle = void 0;
243
+ }
244
+ maybeLog("Back button testing bridge removed.");
245
+ },
246
+ isBackOverrideActive: () => backOverrideActive,
247
+ triggerBack,
248
+ triggerLeave
249
+ };
250
+ activeBackButtonTestingHandle = handle;
251
+ return handle;
252
+ }
253
+ function onBackButton(callback) {
254
+ const off = addNavigationListener("oasiz:back", () => {
255
+ try {
256
+ callback();
257
+ } catch (error) {
258
+ leaveGame();
259
+ throw normalizeNavigationError(error);
260
+ }
261
+ });
262
+ const bridge = getBridgeWindow();
263
+ activeBackListeners += 1;
264
+ if (activeBackListeners === 1) {
265
+ if (typeof bridge?.__oasizSetBackOverride === "function") {
266
+ bridge.__oasizSetBackOverride(true);
267
+ } else {
268
+ warnMissingBridge("__oasizSetBackOverride");
269
+ }
270
+ }
271
+ return () => {
272
+ off();
273
+ activeBackListeners = Math.max(0, activeBackListeners - 1);
274
+ if (activeBackListeners === 0) {
275
+ const currentBridge = getBridgeWindow();
276
+ if (typeof currentBridge?.__oasizSetBackOverride === "function") {
277
+ currentBridge.__oasizSetBackOverride(false);
278
+ } else {
279
+ warnMissingBridge("__oasizSetBackOverride");
280
+ }
281
+ }
282
+ };
283
+ }
284
+ function onLeaveGame(callback) {
285
+ return addNavigationListener("oasiz:leave", callback);
286
+ }
287
+ function leaveGame() {
288
+ const bridge = getBridgeWindow();
289
+ if (typeof bridge?.__oasizLeaveGame === "function") {
290
+ bridge.__oasizLeaveGame();
291
+ return;
292
+ }
293
+ warnMissingBridge("__oasizLeaveGame");
294
+ }
295
+
296
+ // src/app-simulator.ts
297
+ var DEVICE_PRESETS = {
298
+ "iphone-11": {
299
+ name: "iPhone 11",
300
+ width: 414,
301
+ height: 896,
302
+ safeArea: { top: 48, right: 0, bottom: 34, left: 0 }
303
+ },
304
+ "iphone-11-pro": {
305
+ name: "iPhone 11 Pro",
306
+ width: 375,
307
+ height: 812,
308
+ safeArea: { top: 44, right: 0, bottom: 34, left: 0 }
309
+ },
310
+ "iphone-11-pro-max": {
311
+ name: "iPhone 11 Pro Max",
312
+ width: 414,
313
+ height: 896,
314
+ safeArea: { top: 44, right: 0, bottom: 34, left: 0 }
315
+ },
316
+ "iphone-12-mini": {
317
+ name: "iPhone 12 mini",
318
+ width: 375,
319
+ height: 812,
320
+ safeArea: { top: 50, right: 0, bottom: 34, left: 0 }
321
+ },
322
+ "iphone-12": {
323
+ name: "iPhone 12",
324
+ width: 390,
325
+ height: 844,
326
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
327
+ },
328
+ "iphone-12-pro": {
329
+ name: "iPhone 12 Pro",
330
+ width: 390,
331
+ height: 844,
332
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
333
+ },
334
+ "iphone-12-pro-max": {
335
+ name: "iPhone 12 Pro Max",
336
+ width: 428,
337
+ height: 926,
338
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
339
+ },
340
+ "iphone-13-mini": {
341
+ name: "iPhone 13 mini",
342
+ width: 375,
343
+ height: 812,
344
+ safeArea: { top: 50, right: 0, bottom: 34, left: 0 }
345
+ },
346
+ "iphone-13": {
347
+ name: "iPhone 13",
348
+ width: 390,
349
+ height: 844,
350
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
351
+ },
352
+ "iphone-13-pro": {
353
+ name: "iPhone 13 Pro",
354
+ width: 390,
355
+ height: 844,
356
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
357
+ },
358
+ "iphone-13-pro-max": {
359
+ name: "iPhone 13 Pro Max",
360
+ width: 428,
361
+ height: 926,
362
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
363
+ },
364
+ "iphone-14": {
365
+ name: "iPhone 14",
366
+ width: 390,
367
+ height: 844,
368
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
369
+ },
370
+ "iphone-14-plus": {
371
+ name: "iPhone 14 Plus",
372
+ width: 428,
373
+ height: 926,
374
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
375
+ },
376
+ "iphone-14-pro": {
377
+ name: "iPhone 14 Pro",
378
+ width: 393,
379
+ height: 852,
380
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
381
+ },
382
+ "iphone-14-pro-max": {
383
+ name: "iPhone 14 Pro Max",
384
+ width: 430,
385
+ height: 932,
386
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
387
+ },
388
+ "iphone-15": {
389
+ name: "iPhone 15",
390
+ width: 393,
391
+ height: 852,
392
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
393
+ },
394
+ "iphone-15-plus": {
395
+ name: "iPhone 15 Plus",
396
+ width: 430,
397
+ height: 932,
398
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
399
+ },
400
+ "iphone-15-pro": {
401
+ name: "iPhone 15 Pro",
402
+ width: 393,
403
+ height: 852,
404
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
405
+ },
406
+ "iphone-15-pro-max": {
407
+ name: "iPhone 15 Pro Max",
408
+ width: 430,
409
+ height: 932,
410
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
411
+ },
412
+ "iphone-16": {
413
+ name: "iPhone 16",
414
+ width: 393,
415
+ height: 852,
416
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
417
+ },
418
+ "iphone-16-plus": {
419
+ name: "iPhone 16 Plus",
420
+ width: 430,
421
+ height: 932,
422
+ safeArea: { top: 59, right: 0, bottom: 34, left: 0 }
423
+ },
424
+ "iphone-16-pro": {
425
+ name: "iPhone 16 Pro",
426
+ width: 402,
427
+ height: 874,
428
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
429
+ },
430
+ "iphone-16-pro-max": {
431
+ name: "iPhone 16 Pro Max",
432
+ width: 440,
433
+ height: 956,
434
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
435
+ },
436
+ "iphone-16e": {
437
+ name: "iPhone 16e",
438
+ width: 390,
439
+ height: 844,
440
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
441
+ },
442
+ "iphone-17": {
443
+ name: "iPhone 17",
444
+ width: 402,
445
+ height: 874,
446
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
447
+ },
448
+ "iphone-17-pro": {
449
+ name: "iPhone 17 Pro",
450
+ width: 402,
451
+ height: 874,
452
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
453
+ },
454
+ "iphone-17-pro-max": {
455
+ name: "iPhone 17 Pro Max",
456
+ width: 440,
457
+ height: 956,
458
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
459
+ },
460
+ "iphone-17e": {
461
+ name: "iPhone 17e",
462
+ width: 390,
463
+ height: 844,
464
+ safeArea: { top: 47, right: 0, bottom: 34, left: 0 }
465
+ },
466
+ "iphone-air": {
467
+ name: "iPhone Air",
468
+ width: 420,
469
+ height: 912,
470
+ safeArea: { top: 62, right: 0, bottom: 34, left: 0 }
471
+ },
472
+ "iphone-se": {
473
+ name: "iPhone SE",
474
+ width: 375,
475
+ height: 667,
476
+ safeArea: { top: 20, right: 0, bottom: 0, left: 0 }
477
+ },
478
+ "pixel-8": {
479
+ name: "Pixel 8",
480
+ width: 412,
481
+ height: 915,
482
+ safeArea: { top: 32, right: 0, bottom: 24, left: 0 }
483
+ }
484
+ };
485
+ var DEFAULT_COUNTS = {
486
+ comments: 18,
487
+ likes: 128
488
+ };
489
+ var DEFAULT_SCORE = 12400;
490
+ var TOP_BAR_OFFSET = 12;
491
+ var TOP_CHROME_HEIGHT = 44;
492
+ var BODY_BACKGROUND = "#08090d";
493
+ var TEXT_PRIMARY = "#F6F9FB";
494
+ var TEXT_SECONDARY = "rgba(246,249,251,0.72)";
495
+ var TEXT_MUTED = "rgba(246,249,251,0.52)";
496
+ var BORDER = "rgba(255,255,255,0.14)";
497
+ var ACCENT = "#00A1E4";
498
+ var LIKE = "#ef4444";
499
+ var TROPHY = "#FBBF24";
500
+ var MAX_Z_INDEX = 2147483638;
501
+ var EMPTY_INSETS = {
502
+ top: 0,
503
+ right: 0,
504
+ bottom: 0,
505
+ left: 0
506
+ };
507
+ var BRIDGE_KEYS = [
508
+ "__OASIZ_SAFE_AREA_BOTTOM__",
509
+ "__OASIZ_SAFE_AREA_BOTTOM_PERCENT__",
510
+ "__OASIZ_SAFE_AREA_LEFT__",
511
+ "__OASIZ_SAFE_AREA_LEFT_PERCENT__",
512
+ "__OASIZ_SAFE_AREA_RIGHT__",
513
+ "__OASIZ_SAFE_AREA_RIGHT_PERCENT__",
514
+ "__OASIZ_SAFE_AREA_TOP__",
515
+ "__OASIZ_SAFE_AREA_TOP_PERCENT__",
516
+ "__OASIZ_VIEWPORT_INSETS__",
517
+ "__OASIZ_VIEWPORT_INSETS_PERCENT__",
518
+ "__oasizSetLeaderboardVisible",
519
+ "getSafeAreaBottom",
520
+ "getSafeAreaBottomPercent",
521
+ "getSafeAreaLeft",
522
+ "getSafeAreaLeftPercent",
523
+ "getSafeAreaRight",
524
+ "getSafeAreaRightPercent",
525
+ "getSafeAreaTop",
526
+ "getSafeAreaTopPercent",
527
+ "getViewportInsets",
528
+ "getViewportInsetsPercent"
529
+ ];
530
+ var NOOP_HANDLE = {
531
+ closeSheet() {
532
+ },
533
+ destroy() {
534
+ },
535
+ getViewportInsets() {
536
+ return { pixels: { ...EMPTY_INSETS }, percent: { ...EMPTY_INSETS } };
537
+ },
538
+ hide() {
539
+ },
540
+ isVisible() {
541
+ return false;
542
+ },
543
+ openComments() {
544
+ },
545
+ openLeaderboard() {
546
+ },
547
+ setCounts() {
548
+ },
549
+ setLeaderboardVisible() {
550
+ },
551
+ setLiked() {
552
+ },
553
+ show() {
554
+ },
555
+ triggerBack() {
556
+ },
557
+ triggerLeave() {
558
+ }
559
+ };
560
+ function getBrowserWindow() {
561
+ if (typeof window === "undefined") {
562
+ return void 0;
563
+ }
564
+ return window;
565
+ }
566
+ function getDocument() {
567
+ if (typeof document === "undefined") {
568
+ return void 0;
569
+ }
570
+ return document;
571
+ }
572
+ function warn(message) {
573
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
574
+ if (nodeEnv !== "production") {
575
+ console.warn("[oasiz/sdk] " + message);
576
+ }
577
+ }
578
+ function normalizeCount(value, fallback) {
579
+ if (typeof value !== "number" || !Number.isFinite(value)) {
580
+ return fallback;
581
+ }
582
+ return Math.max(0, Math.floor(value));
583
+ }
584
+ function formatCompactCount(value) {
585
+ if (value >= 1e6) {
586
+ return (value / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
587
+ }
588
+ if (value >= 1e3) {
589
+ return (value / 1e3).toFixed(1).replace(/\.0$/, "") + "k";
590
+ }
591
+ return String(value);
592
+ }
593
+ function resolveDevice(device, orientation) {
594
+ const preset = typeof device === "string" ? DEVICE_PRESETS[device] : device ? {
595
+ name: device.name ?? "Custom phone",
596
+ width: device.width,
597
+ height: device.height,
598
+ safeArea: {
599
+ ...EMPTY_INSETS,
600
+ ...device.safeArea
601
+ }
602
+ } : DEVICE_PRESETS["iphone-17-pro-max"];
603
+ const normalized = {
604
+ name: preset.name,
605
+ width: Math.max(320, Math.floor(preset.width)),
606
+ height: Math.max(480, Math.floor(preset.height)),
607
+ safeArea: {
608
+ ...EMPTY_INSETS,
609
+ ...preset.safeArea
610
+ }
611
+ };
612
+ if (orientation === "landscape") {
613
+ return {
614
+ ...normalized,
615
+ width: Math.max(normalized.width, normalized.height),
616
+ height: Math.min(normalized.width, normalized.height),
617
+ safeArea: {
618
+ top: 0,
619
+ right: normalized.safeArea.top,
620
+ bottom: 21,
621
+ left: normalized.safeArea.top
622
+ }
623
+ };
624
+ }
625
+ return normalized;
626
+ }
627
+ function shouldFrame(frame, browserWindow, device) {
628
+ if (frame === true) {
629
+ return true;
630
+ }
631
+ if (frame === false) {
632
+ return false;
633
+ }
634
+ return browserWindow.innerWidth > device.width + 80 || browserWindow.innerHeight > device.height + 80;
635
+ }
636
+ function computeRect(browserWindow, device, frameEnabled) {
637
+ const viewportWidth = Math.max(320, browserWindow.innerWidth || device.width);
638
+ const viewportHeight = Math.max(480, browserWindow.innerHeight || device.height);
639
+ if (!frameEnabled) {
640
+ return {
641
+ left: 0,
642
+ top: 0,
643
+ width: viewportWidth,
644
+ height: viewportHeight,
645
+ scale: Math.min(viewportWidth / device.width, viewportHeight / device.height)
646
+ };
647
+ }
648
+ const margin = 24;
649
+ const scale = Math.min(
650
+ (viewportWidth - margin) / device.width,
651
+ (viewportHeight - margin) / device.height,
652
+ 1
653
+ );
654
+ const width = Math.round(device.width * scale);
655
+ const height = Math.round(device.height * scale);
656
+ return {
657
+ left: Math.round((viewportWidth - width) / 2),
658
+ top: Math.round((viewportHeight - height) / 2),
659
+ width,
660
+ height,
661
+ scale
662
+ };
663
+ }
664
+ function scaledInset(value, scale) {
665
+ if (typeof value !== "number" || !Number.isFinite(value)) {
666
+ return 0;
667
+ }
668
+ return Math.max(0, Math.round(value * scale));
669
+ }
670
+ function getSimulatorInsets(state) {
671
+ const safe = state.device.safeArea;
672
+ const top = scaledInset(safe.top, state.rect.scale) + Math.round((TOP_BAR_OFFSET + TOP_CHROME_HEIGHT) * state.rect.scale);
673
+ const pixels = {
674
+ top,
675
+ right: scaledInset(safe.right, state.rect.scale),
676
+ bottom: scaledInset(safe.bottom, state.rect.scale),
677
+ left: scaledInset(safe.left, state.rect.scale)
678
+ };
679
+ const percent = {
680
+ top: state.rect.height > 0 ? pixels.top / state.rect.height * 100 : 0,
681
+ right: state.rect.width > 0 ? pixels.right / state.rect.width * 100 : 0,
682
+ bottom: state.rect.height > 0 ? pixels.bottom / state.rect.height * 100 : 0,
683
+ left: state.rect.width > 0 ? pixels.left / state.rect.width * 100 : 0
684
+ };
685
+ return { pixels, percent };
686
+ }
687
+ function snapshotBridge(browserWindow) {
688
+ const globals = {};
689
+ for (const key of BRIDGE_KEYS) {
690
+ globals[key] = browserWindow[key];
691
+ }
692
+ return { globals };
693
+ }
694
+ function restoreBridge(browserWindow, snapshot) {
695
+ for (const key of BRIDGE_KEYS) {
696
+ const value = snapshot.globals[key];
697
+ if (typeof value === "undefined") {
698
+ delete browserWindow[key];
699
+ } else {
700
+ browserWindow[key] = value;
701
+ }
702
+ }
703
+ }
704
+ function installBridge(state) {
705
+ const browserWindow = getBrowserWindow();
706
+ if (!browserWindow) return;
707
+ const bridge = browserWindow;
708
+ function refreshInsets() {
709
+ const insets = getSimulatorInsets(state);
710
+ bridge.__OASIZ_VIEWPORT_INSETS__ = insets;
711
+ bridge.__OASIZ_VIEWPORT_INSETS_PERCENT__ = insets.percent;
712
+ bridge.__OASIZ_SAFE_AREA_TOP__ = insets.pixels.top;
713
+ bridge.__OASIZ_SAFE_AREA_RIGHT__ = insets.pixels.right;
714
+ bridge.__OASIZ_SAFE_AREA_BOTTOM__ = insets.pixels.bottom;
715
+ bridge.__OASIZ_SAFE_AREA_LEFT__ = insets.pixels.left;
716
+ bridge.__OASIZ_SAFE_AREA_TOP_PERCENT__ = insets.percent.top;
717
+ bridge.__OASIZ_SAFE_AREA_RIGHT_PERCENT__ = insets.percent.right;
718
+ bridge.__OASIZ_SAFE_AREA_BOTTOM_PERCENT__ = insets.percent.bottom;
719
+ bridge.__OASIZ_SAFE_AREA_LEFT_PERCENT__ = insets.percent.left;
720
+ return insets;
721
+ }
722
+ bridge.getViewportInsets = () => refreshInsets();
723
+ bridge.getViewportInsetsPercent = () => refreshInsets().percent;
724
+ bridge.getSafeAreaTop = () => refreshInsets().pixels.top;
725
+ bridge.getSafeAreaRight = () => refreshInsets().pixels.right;
726
+ bridge.getSafeAreaBottom = () => refreshInsets().pixels.bottom;
727
+ bridge.getSafeAreaLeft = () => refreshInsets().pixels.left;
728
+ bridge.getSafeAreaTopPercent = () => refreshInsets().percent.top;
729
+ bridge.getSafeAreaRightPercent = () => refreshInsets().percent.right;
730
+ bridge.getSafeAreaBottomPercent = () => refreshInsets().percent.bottom;
731
+ bridge.getSafeAreaLeftPercent = () => refreshInsets().percent.left;
732
+ bridge.__oasizSetLeaderboardVisible = (visible) => {
733
+ state.leaderboard.visible = visible;
734
+ renderSimulator(state);
735
+ };
736
+ refreshInsets();
737
+ }
738
+ function createStyleElement() {
739
+ const style = document.createElement("style");
740
+ style.setAttribute("data-oasiz-app-simulator", "styles");
741
+ style.textContent = [
742
+ ".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}",
743
+ ".oasiz-app-sim-button:active{transform:scale(0.97)}",
744
+ ".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)}",
745
+ ".oasiz-app-sim-scroll::-webkit-scrollbar{width:0;height:0}"
746
+ ].join("\n");
747
+ return style;
748
+ }
749
+ function applyTextStyle(element, size = 12, weight = 600) {
750
+ element.style.font = String(weight) + " " + String(size) + "px/1.2 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif";
751
+ }
752
+ function createDiv(className) {
753
+ const element = document.createElement("div");
754
+ if (className) {
755
+ element.className = className;
756
+ }
757
+ return element;
758
+ }
759
+ function createButton(label, title) {
760
+ const button = document.createElement("button");
761
+ button.type = "button";
762
+ button.className = "oasiz-app-sim-button";
763
+ button.setAttribute("aria-label", title);
764
+ button.title = title;
765
+ button.innerHTML = label;
766
+ return button;
767
+ }
768
+ function svgIcon(name, filled = false) {
769
+ if (name === "back") {
770
+ 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>';
771
+ }
772
+ if (name === "heart") {
773
+ 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>';
774
+ }
775
+ if (name === "chat") {
776
+ 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>';
777
+ }
778
+ if (name === "trophy") {
779
+ 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>';
780
+ }
781
+ if (name === "share") {
782
+ 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>';
783
+ }
784
+ 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>';
785
+ }
786
+ function setBoxStyle(element, state) {
787
+ const { rect } = state;
788
+ element.style.left = rect.left + "px";
789
+ element.style.top = rect.top + "px";
790
+ element.style.width = rect.width + "px";
791
+ element.style.height = rect.height + "px";
792
+ }
793
+ function createCircleButton(icon, title, onClick) {
794
+ const button = createButton(icon, title);
795
+ button.classList.add("oasiz-app-sim-glass");
796
+ button.style.cssText += [
797
+ "position:absolute",
798
+ "width:44px",
799
+ "height:44px",
800
+ "border-radius:999px",
801
+ "display:flex",
802
+ "align-items:center",
803
+ "justify-content:center",
804
+ "color:" + TEXT_PRIMARY,
805
+ "pointer-events:auto"
806
+ ].join(";");
807
+ button.addEventListener("click", (event) => {
808
+ event.preventDefault();
809
+ event.stopPropagation();
810
+ onClick();
811
+ });
812
+ return button;
813
+ }
814
+ function createHubPill(state) {
815
+ const button = createButton("", "Open comments");
816
+ button.classList.add("oasiz-app-sim-glass");
817
+ button.style.cssText += [
818
+ "position:absolute",
819
+ "right:16px",
820
+ "height:44px",
821
+ "min-width:94px",
822
+ "border-radius:999px",
823
+ "display:flex",
824
+ "align-items:center",
825
+ "justify-content:center",
826
+ "gap:10px",
827
+ "padding:0 14px",
828
+ "color:" + TEXT_PRIMARY,
829
+ "pointer-events:auto"
830
+ ].join(";");
831
+ const likeColor = state.wasLiked ? LIKE : TEXT_PRIMARY;
832
+ 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>";
833
+ applyTextStyle(button, 12, 700);
834
+ button.addEventListener("click", (event) => {
835
+ event.preventDefault();
836
+ event.stopPropagation();
837
+ state.sheet = "comments";
838
+ renderSimulator(state);
839
+ });
840
+ return button;
841
+ }
842
+ function createLeaderboardPill(state) {
843
+ const button = createButton("", "Open leaderboard");
844
+ button.classList.add("oasiz-app-sim-glass");
845
+ button.style.cssText += [
846
+ "position:absolute",
847
+ "left:50%",
848
+ "height:44px",
849
+ "min-width:118px",
850
+ "border-radius:999px",
851
+ "display:" + (state.leaderboard.visible ? "flex" : "none"),
852
+ "align-items:center",
853
+ "justify-content:center",
854
+ "gap:8px",
855
+ "padding:0 14px",
856
+ "color:" + TEXT_PRIMARY,
857
+ "pointer-events:auto",
858
+ "transform:translateX(-50%)"
859
+ ].join(";");
860
+ button.innerHTML = '<span style="color:' + TROPHY + ';display:inline-flex">' + svgIcon("trophy", true) + '</span><span style="min-width:0">' + formatCompactCount(state.leaderboard.score) + "</span>";
861
+ applyTextStyle(button, 14, 800);
862
+ button.addEventListener("click", (event) => {
863
+ event.preventDefault();
864
+ event.stopPropagation();
865
+ state.sheet = "leaderboard";
866
+ renderSimulator(state);
867
+ });
868
+ return button;
869
+ }
870
+ function createToolbarButton(icon, count, color, title, onClick) {
871
+ const button = createButton("", title);
872
+ button.classList.add("oasiz-app-sim-glass");
873
+ button.style.cssText += [
874
+ "height:42px",
875
+ "min-width:42px",
876
+ "border-radius:999px",
877
+ "display:flex",
878
+ "align-items:center",
879
+ "justify-content:center",
880
+ "gap:6px",
881
+ "padding:0 12px",
882
+ "color:" + color,
883
+ "pointer-events:auto"
884
+ ].join(";");
885
+ button.innerHTML = icon + (count === null ? "" : '<span style="color:' + color + ';min-width:0">' + formatCompactCount(count) + "</span>");
886
+ applyTextStyle(button, 13, 700);
887
+ button.addEventListener("click", (event) => {
888
+ event.preventDefault();
889
+ event.stopPropagation();
890
+ onClick();
891
+ });
892
+ return button;
893
+ }
894
+ function createSheet(state) {
895
+ if (!state.sheet) {
896
+ return null;
897
+ }
898
+ const overlay = createDiv();
899
+ overlay.style.cssText = [
900
+ "position:absolute",
901
+ "left:0",
902
+ "top:0",
903
+ "right:0",
904
+ "bottom:0",
905
+ "display:flex",
906
+ "align-items:center",
907
+ "justify-content:center",
908
+ "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",
909
+ "background:rgba(0,0,0,0.34)",
910
+ "pointer-events:auto"
911
+ ].join(";");
912
+ overlay.addEventListener("click", () => {
913
+ state.sheet = null;
914
+ renderSimulator(state);
915
+ });
916
+ const sheet = createDiv();
917
+ sheet.style.cssText = [
918
+ "position:relative",
919
+ "width:min(100%, 408px)",
920
+ "max-height:100%",
921
+ "min-height:min(390px, 64%)",
922
+ "display:flex",
923
+ "flex-direction:column",
924
+ "border-radius:24px",
925
+ "border:1px solid " + BORDER,
926
+ "background:linear-gradient(180deg, rgba(18,20,24,0.97), rgba(9,11,15,0.98))",
927
+ "box-shadow:0 30px 80px rgba(0,0,0,0.52), inset 0 1px 0 rgba(255,255,255,0.10)",
928
+ "overflow:hidden",
929
+ "pointer-events:auto"
930
+ ].join(";");
931
+ sheet.addEventListener("click", (event) => {
932
+ event.stopPropagation();
933
+ });
934
+ const handle = createDiv();
935
+ handle.style.cssText = [
936
+ "width:42px",
937
+ "height:5px",
938
+ "border-radius:999px",
939
+ "background:rgba(255,255,255,0.28)",
940
+ "align-self:center",
941
+ "margin:10px 0 2px"
942
+ ].join(";");
943
+ const header = createDiv();
944
+ header.style.cssText = [
945
+ "display:flex",
946
+ "align-items:center",
947
+ "justify-content:space-between",
948
+ "gap:8px",
949
+ "min-height:62px",
950
+ "padding:6px 14px 10px",
951
+ "border-bottom:1px solid " + BORDER
952
+ ].join(";");
953
+ const left = createDiv();
954
+ left.style.cssText = "display:flex;align-items:center;gap:8px;min-width:112px";
955
+ const title = createDiv();
956
+ title.textContent = state.sheet === "comments" ? String(state.counts.comments) + " comments" : "Leaderboard";
957
+ title.style.cssText = [
958
+ "flex:1",
959
+ "min-width:0",
960
+ "text-align:center",
961
+ "color:" + TEXT_PRIMARY
962
+ ].join(";");
963
+ applyTextStyle(title, 14, 700);
964
+ const right = createDiv();
965
+ right.style.cssText = "display:flex;align-items:center;justify-content:flex-end;gap:8px;min-width:112px";
966
+ const back = createToolbarButton(svgIcon("back"), null, TEXT_PRIMARY, "Close", () => {
967
+ state.sheet = null;
968
+ renderSimulator(state);
969
+ });
970
+ back.style.width = "36px";
971
+ back.style.height = "36px";
972
+ back.style.padding = "0";
973
+ left.appendChild(back);
974
+ if (state.sheet === "comments") {
975
+ const like = createToolbarButton(
976
+ svgIcon("heart", state.wasLiked),
977
+ state.counts.likes,
978
+ state.wasLiked ? LIKE : TEXT_PRIMARY,
979
+ "Like game",
980
+ () => {
981
+ state.wasLiked = !state.wasLiked;
982
+ state.counts.likes = Math.max(0, state.counts.likes + (state.wasLiked ? 1 : -1));
983
+ renderSimulator(state);
984
+ }
985
+ );
986
+ left.appendChild(like);
987
+ } else {
988
+ const trophy = createToolbarButton(svgIcon("trophy", true), null, TROPHY, "Leaderboard", () => {
989
+ });
990
+ trophy.style.width = "36px";
991
+ trophy.style.height = "36px";
992
+ trophy.style.padding = "0";
993
+ left.appendChild(trophy);
994
+ }
995
+ const share2 = createToolbarButton(svgIcon("share"), null, TEXT_PRIMARY, "Share", () => {
996
+ });
997
+ const save = createToolbarButton(svgIcon("bookmark"), null, TEXT_PRIMARY, "Save", () => {
998
+ });
999
+ right.appendChild(share2);
1000
+ right.appendChild(save);
1001
+ header.appendChild(left);
1002
+ header.appendChild(title);
1003
+ header.appendChild(right);
1004
+ const body = createDiv("oasiz-app-sim-scroll");
1005
+ body.style.cssText = [
1006
+ "display:flex",
1007
+ "flex-direction:column",
1008
+ "gap:10px",
1009
+ "overflow:auto",
1010
+ "padding:14px 16px calc(" + String(Math.max(16, scaledInset(state.device.safeArea.bottom, state.rect.scale))) + "px + 14px)",
1011
+ "min-height:0"
1012
+ ].join(";");
1013
+ if (state.sheet === "leaderboard") {
1014
+ appendLeaderboardBody(body);
1015
+ } else {
1016
+ appendCommentsBody(body, state);
1017
+ }
1018
+ sheet.appendChild(handle);
1019
+ sheet.appendChild(header);
1020
+ sheet.appendChild(body);
1021
+ overlay.appendChild(sheet);
1022
+ return overlay;
1023
+ }
1024
+ function appendLeaderboardBody(body) {
1025
+ const tabs = createDiv();
1026
+ tabs.style.cssText = [
1027
+ "display:grid",
1028
+ "grid-template-columns:repeat(3,1fr)",
1029
+ "gap:6px",
1030
+ "padding:4px",
1031
+ "border-radius:14px",
1032
+ "background:rgba(255,255,255,0.07)"
1033
+ ].join(";");
1034
+ for (const label of ["Weekly", "Global", "Friends"]) {
1035
+ const tab = createDiv();
1036
+ tab.textContent = label;
1037
+ tab.style.cssText = [
1038
+ "border-radius:10px",
1039
+ "padding:8px 6px",
1040
+ "text-align:center",
1041
+ "color:" + (label === "Weekly" ? TEXT_PRIMARY : TEXT_MUTED),
1042
+ "background:" + (label === "Weekly" ? "rgba(0,161,228,0.28)" : "transparent")
1043
+ ].join(";");
1044
+ applyTextStyle(tab, 12, 700);
1045
+ tabs.appendChild(tab);
1046
+ }
1047
+ body.appendChild(tabs);
1048
+ const entries = [
1049
+ ["1", "Nova", "24.8k"],
1050
+ ["2", "You", "12.4k"],
1051
+ ["3", "Mika", "9.7k"],
1052
+ ["4", "Ari", "8.1k"]
1053
+ ];
1054
+ for (const [rank, name, score] of entries) {
1055
+ const row = createDiv();
1056
+ row.style.cssText = [
1057
+ "display:grid",
1058
+ "grid-template-columns:34px 1fr auto",
1059
+ "align-items:center",
1060
+ "gap:10px",
1061
+ "min-height:58px",
1062
+ "padding:10px 12px",
1063
+ "border-radius:14px",
1064
+ "background:" + (name === "You" ? "rgba(0,161,228,0.13)" : "rgba(255,255,255,0.06)"),
1065
+ "border:1px solid " + (name === "You" ? "rgba(0,161,228,0.28)" : "rgba(255,255,255,0.08)")
1066
+ ].join(";");
1067
+ const rankEl = createDiv();
1068
+ rankEl.textContent = rank;
1069
+ rankEl.style.cssText = "color:" + (rank === "1" ? TROPHY : TEXT_MUTED) + ";text-align:center";
1070
+ applyTextStyle(rankEl, 14, 800);
1071
+ const nameEl = createDiv();
1072
+ nameEl.textContent = name;
1073
+ nameEl.style.cssText = "color:" + TEXT_PRIMARY + ";min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap";
1074
+ applyTextStyle(nameEl, 14, 700);
1075
+ const scoreEl = createDiv();
1076
+ scoreEl.textContent = score;
1077
+ scoreEl.style.cssText = "color:" + TEXT_SECONDARY;
1078
+ applyTextStyle(scoreEl, 13, 700);
1079
+ row.appendChild(rankEl);
1080
+ row.appendChild(nameEl);
1081
+ row.appendChild(scoreEl);
1082
+ body.appendChild(row);
1083
+ }
1084
+ }
1085
+ function appendCommentsBody(body, state) {
1086
+ const toolbar = createDiv();
1087
+ toolbar.style.cssText = [
1088
+ "display:flex",
1089
+ "align-items:center",
1090
+ "gap:8px",
1091
+ "height:48px"
1092
+ ].join(";");
1093
+ toolbar.appendChild(
1094
+ createToolbarButton(
1095
+ svgIcon("heart", state.wasLiked),
1096
+ state.counts.likes,
1097
+ state.wasLiked ? LIKE : TEXT_PRIMARY,
1098
+ "Like game",
1099
+ () => {
1100
+ state.wasLiked = !state.wasLiked;
1101
+ state.counts.likes = Math.max(0, state.counts.likes + (state.wasLiked ? 1 : -1));
1102
+ renderSimulator(state);
1103
+ }
1104
+ )
1105
+ );
1106
+ toolbar.appendChild(
1107
+ createToolbarButton(svgIcon("chat", true), state.counts.comments, ACCENT, "Comments", () => {
1108
+ })
1109
+ );
1110
+ toolbar.appendChild(createToolbarButton(svgIcon("share"), null, TEXT_PRIMARY, "Share", () => {
1111
+ }));
1112
+ toolbar.appendChild(createToolbarButton(svgIcon("bookmark"), null, TEXT_PRIMARY, "Save", () => {
1113
+ }));
1114
+ body.appendChild(toolbar);
1115
+ const comments = [
1116
+ ["You", "Can my score panel clear the top buttons?"],
1117
+ ["Nova", "This is a good spot to check pause and menu spacing."],
1118
+ ["Mika", "The app chrome stays above the game just like mobile."]
1119
+ ];
1120
+ for (const [author, text] of comments) {
1121
+ const row = createDiv();
1122
+ row.style.cssText = [
1123
+ "display:grid",
1124
+ "grid-template-columns:34px 1fr",
1125
+ "gap:10px",
1126
+ "padding:10px 0"
1127
+ ].join(";");
1128
+ const avatar = createDiv();
1129
+ avatar.textContent = author.charAt(0);
1130
+ avatar.style.cssText = [
1131
+ "width:34px",
1132
+ "height:34px",
1133
+ "border-radius:999px",
1134
+ "display:flex",
1135
+ "align-items:center",
1136
+ "justify-content:center",
1137
+ "background:rgba(0,161,228,0.28)",
1138
+ "color:" + TEXT_PRIMARY
1139
+ ].join(";");
1140
+ applyTextStyle(avatar, 13, 800);
1141
+ const message = createDiv();
1142
+ message.innerHTML = '<div style="color:' + TEXT_PRIMARY + ';font-weight:700;margin-bottom:3px">' + author + '</div><div style="color:' + TEXT_SECONDARY + '">' + text + "</div>";
1143
+ applyTextStyle(message, 13, 500);
1144
+ row.appendChild(avatar);
1145
+ row.appendChild(message);
1146
+ body.appendChild(row);
1147
+ }
1148
+ const composer = createDiv();
1149
+ composer.textContent = "Add comment...";
1150
+ composer.style.cssText = [
1151
+ "height:44px",
1152
+ "border-radius:18px",
1153
+ "display:flex",
1154
+ "align-items:center",
1155
+ "padding:0 14px",
1156
+ "background:rgba(255,255,255,0.08)",
1157
+ "border:1px solid rgba(255,255,255,0.10)",
1158
+ "color:" + TEXT_MUTED
1159
+ ].join(";");
1160
+ applyTextStyle(composer, 13, 600);
1161
+ body.appendChild(composer);
1162
+ }
1163
+ function renderSimulator(state) {
1164
+ if (state.wasDestroyed) {
1165
+ return;
1166
+ }
1167
+ const { root, stage, gameViewport } = state.elements;
1168
+ root.style.display = state.visible ? "block" : "none";
1169
+ setBoxStyle(stage, state);
1170
+ if (gameViewport) {
1171
+ setBoxStyle(gameViewport, state);
1172
+ }
1173
+ stage.replaceChildren();
1174
+ const top = scaledInset(state.device.safeArea.top, state.rect.scale) + Math.round(TOP_BAR_OFFSET * state.rect.scale);
1175
+ const back = createCircleButton(svgIcon("back"), "Back", () => {
1176
+ if (state.backHandle.isBackOverrideActive()) {
1177
+ state.backHandle.triggerBack();
1178
+ } else {
1179
+ state.backHandle.triggerLeave();
1180
+ }
1181
+ });
1182
+ back.style.left = Math.round(16 * state.rect.scale) + "px";
1183
+ back.style.top = top + "px";
1184
+ const leaderboard = createLeaderboardPill(state);
1185
+ leaderboard.style.top = top + "px";
1186
+ const hub = createHubPill(state);
1187
+ hub.style.top = top + "px";
1188
+ hub.style.right = Math.round(16 * state.rect.scale) + "px";
1189
+ stage.appendChild(back);
1190
+ stage.appendChild(leaderboard);
1191
+ stage.appendChild(hub);
1192
+ const sheet = createSheet(state);
1193
+ if (sheet) {
1194
+ stage.appendChild(sheet);
1195
+ }
1196
+ if (state.options.log) {
1197
+ console.info("[oasiz/sdk] App simulator rendered.", {
1198
+ device: state.device.name,
1199
+ frame: state.frameEnabled,
1200
+ insets: getSimulatorInsets(state)
1201
+ });
1202
+ }
1203
+ }
1204
+ function installFrame(doc, state) {
1205
+ if (!state.frameEnabled || !doc.body) {
1206
+ return;
1207
+ }
1208
+ const body = doc.body;
1209
+ const html = doc.documentElement;
1210
+ const viewport = state.elements.gameViewport;
1211
+ if (!viewport) {
1212
+ return;
1213
+ }
1214
+ html.style.cssText = [
1215
+ state.previousDocumentElementStyle ?? "",
1216
+ "width:100%",
1217
+ "height:100%",
1218
+ "background:" + BODY_BACKGROUND,
1219
+ "overflow:hidden"
1220
+ ].join(";");
1221
+ body.style.cssText = [
1222
+ state.previousBodyStyle ?? "",
1223
+ "width:100%",
1224
+ "height:100%",
1225
+ "margin:0",
1226
+ "background:" + BODY_BACKGROUND,
1227
+ "overflow:hidden"
1228
+ ].join(";");
1229
+ const candidates = Array.from(body.children).filter(
1230
+ (node) => shouldMoveIntoGameViewport(node, state.elements)
1231
+ );
1232
+ for (const node of candidates) {
1233
+ state.movedNodes.push(node);
1234
+ viewport.appendChild(node);
1235
+ }
1236
+ body.appendChild(viewport);
1237
+ }
1238
+ function shouldMoveIntoGameViewport(node, elements) {
1239
+ if (node === elements.root || node === elements.style || node === elements.gameViewport) {
1240
+ return false;
1241
+ }
1242
+ const tagName = node.tagName.toUpperCase();
1243
+ if (tagName === "SCRIPT" || tagName === "STYLE" || tagName === "LINK" || tagName === "META" || tagName === "NOSCRIPT") {
1244
+ return false;
1245
+ }
1246
+ return node.getAttribute("data-oasiz-app-simulator") !== "true";
1247
+ }
1248
+ function restoreFrame(doc, state) {
1249
+ const body = doc.body;
1250
+ if (!body) {
1251
+ return;
1252
+ }
1253
+ for (const node of state.movedNodes) {
1254
+ body.appendChild(node);
1255
+ }
1256
+ state.movedNodes = [];
1257
+ state.elements.gameViewport?.remove();
1258
+ if (state.previousBodyStyle === null) {
1259
+ body.removeAttribute("style");
1260
+ } else {
1261
+ body.setAttribute("style", state.previousBodyStyle);
1262
+ }
1263
+ if (state.previousDocumentElementStyle === null) {
1264
+ doc.documentElement.removeAttribute("style");
1265
+ } else {
1266
+ doc.documentElement.setAttribute("style", state.previousDocumentElementStyle);
1267
+ }
1268
+ }
1269
+ function createElements(frameEnabled) {
1270
+ const root = document.createElement("div");
1271
+ root.setAttribute("data-oasiz-app-simulator", "true");
1272
+ root.style.cssText = [
1273
+ "position:fixed",
1274
+ "inset:0",
1275
+ "z-index:" + String(MAX_Z_INDEX),
1276
+ "pointer-events:none",
1277
+ "display:block",
1278
+ "font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif"
1279
+ ].join(";");
1280
+ const stage = document.createElement("div");
1281
+ stage.setAttribute("data-oasiz-app-simulator", "stage");
1282
+ stage.style.cssText = [
1283
+ "position:absolute",
1284
+ "overflow:hidden",
1285
+ "pointer-events:none"
1286
+ ].join(";");
1287
+ root.appendChild(stage);
1288
+ const gameViewport = frameEnabled ? document.createElement("div") : null;
1289
+ if (gameViewport) {
1290
+ gameViewport.setAttribute("data-oasiz-app-simulator", "game-viewport");
1291
+ gameViewport.style.cssText = [
1292
+ "position:fixed",
1293
+ "z-index:0",
1294
+ "overflow:hidden",
1295
+ "background:#000",
1296
+ "border-radius:28px",
1297
+ "outline:1px solid rgba(255,255,255,0.28)",
1298
+ "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)"
1299
+ ].join(";");
1300
+ }
1301
+ return {
1302
+ gameViewport,
1303
+ root,
1304
+ stage,
1305
+ style: createStyleElement()
1306
+ };
1307
+ }
1308
+ function installResizeHandler(browserWindow, state) {
1309
+ const resize = () => {
1310
+ state.rect = computeRect(browserWindow, state.device, state.frameEnabled);
1311
+ installBridge(state);
1312
+ renderSimulator(state);
1313
+ };
1314
+ browserWindow.addEventListener("resize", resize);
1315
+ browserWindow.addEventListener("orientationchange", resize);
1316
+ return () => {
1317
+ browserWindow.removeEventListener("resize", resize);
1318
+ browserWindow.removeEventListener("orientationchange", resize);
1319
+ };
1320
+ }
1321
+ function enableAppSimulator(options = {}) {
1322
+ if (options.enabled === false) {
1323
+ return NOOP_HANDLE;
1324
+ }
1325
+ const browserWindow = getBrowserWindow();
1326
+ const doc = getDocument();
1327
+ if (!browserWindow || !doc?.body || !doc.documentElement) {
1328
+ warn("enableAppSimulator requires a browser document.");
1329
+ return NOOP_HANDLE;
1330
+ }
1331
+ browserWindow.__oasizAppSimulatorHandle__?.destroy();
1332
+ const orientation = options.orientation ?? "portrait";
1333
+ const device = resolveDevice(options.device, orientation);
1334
+ const frameEnabled = shouldFrame(options.frame ?? true, browserWindow, device);
1335
+ const elements = createElements(frameEnabled);
1336
+ const rect = computeRect(browserWindow, device, frameEnabled);
1337
+ const backHandle = enableBackButtonTesting({
1338
+ browserHistory: options.browserHistoryBack ?? true,
1339
+ keyboard: options.keyboardBack ?? true,
1340
+ log: options.log === true
1341
+ });
1342
+ const state = {
1343
+ backHandle,
1344
+ bridgeSnapshot: snapshotBridge(browserWindow),
1345
+ cleanupResize: () => {
1346
+ },
1347
+ counts: {
1348
+ comments: normalizeCount(options.comments, DEFAULT_COUNTS.comments),
1349
+ likes: normalizeCount(options.likes, DEFAULT_COUNTS.likes)
1350
+ },
1351
+ device,
1352
+ elements,
1353
+ frameEnabled,
1354
+ leaderboard: {
1355
+ score: normalizeCount(options.score, DEFAULT_SCORE),
1356
+ visible: options.leaderboardVisible ?? true
1357
+ },
1358
+ movedNodes: [],
1359
+ options: {
1360
+ browserHistoryBack: options.browserHistoryBack ?? true,
1361
+ keyboardBack: options.keyboardBack ?? true,
1362
+ log: options.log === true,
1363
+ orientation,
1364
+ title: options.title ?? "Oasiz App Preview"
1365
+ },
1366
+ previousBodyStyle: doc.body.getAttribute("style"),
1367
+ previousDocumentElementStyle: doc.documentElement.getAttribute("style"),
1368
+ rect,
1369
+ sheet: null,
1370
+ visible: true,
1371
+ wasDestroyed: false,
1372
+ wasLiked: false
1373
+ };
1374
+ if (doc.head) {
1375
+ doc.head.appendChild(elements.style);
1376
+ } else {
1377
+ doc.body.appendChild(elements.style);
1378
+ }
1379
+ installFrame(doc, state);
1380
+ doc.body.appendChild(elements.root);
1381
+ installBridge(state);
1382
+ state.cleanupResize = installResizeHandler(browserWindow, state);
1383
+ const handle = {
1384
+ closeSheet: () => {
1385
+ state.sheet = null;
1386
+ renderSimulator(state);
1387
+ },
1388
+ destroy: () => {
1389
+ if (state.wasDestroyed) return;
1390
+ state.wasDestroyed = true;
1391
+ state.cleanupResize();
1392
+ state.backHandle.destroy();
1393
+ restoreBridge(browserWindow, state.bridgeSnapshot);
1394
+ delete browserWindow.__oasizAppSimulatorHandle__;
1395
+ elements.root.remove();
1396
+ elements.style.remove();
1397
+ restoreFrame(doc, state);
1398
+ },
1399
+ getViewportInsets: () => getSimulatorInsets(state),
1400
+ hide: () => {
1401
+ state.visible = false;
1402
+ renderSimulator(state);
1403
+ },
1404
+ isVisible: () => state.visible,
1405
+ openComments: () => {
1406
+ state.sheet = "comments";
1407
+ renderSimulator(state);
1408
+ },
1409
+ openLeaderboard: () => {
1410
+ state.sheet = "leaderboard";
1411
+ renderSimulator(state);
1412
+ },
1413
+ setCounts: (counts) => {
1414
+ state.counts.comments = normalizeCount(counts.comments, state.counts.comments);
1415
+ state.counts.likes = normalizeCount(counts.likes, state.counts.likes);
1416
+ renderSimulator(state);
1417
+ },
1418
+ setLeaderboardVisible: (visible) => {
1419
+ state.leaderboard.visible = visible;
1420
+ renderSimulator(state);
1421
+ },
1422
+ setLiked: (liked) => {
1423
+ if (state.wasLiked === liked) {
1424
+ return;
1425
+ }
1426
+ state.wasLiked = liked;
1427
+ state.counts.likes = Math.max(0, state.counts.likes + (liked ? 1 : -1));
1428
+ renderSimulator(state);
1429
+ },
1430
+ show: () => {
1431
+ state.visible = true;
1432
+ renderSimulator(state);
1433
+ },
1434
+ triggerBack: () => state.backHandle.triggerBack(),
1435
+ triggerLeave: () => state.backHandle.triggerLeave()
1436
+ };
1437
+ browserWindow.__oasizAppSimulatorHandle__ = handle;
1438
+ renderSimulator(state);
1439
+ return handle;
1440
+ }
1441
+
54
1442
  // src/character.ts
55
- function isDevelopment() {
1443
+ function isDevelopment2() {
56
1444
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
57
1445
  return nodeEnv !== "production";
58
1446
  }
59
- function getBridgeWindow() {
1447
+ function getBridgeWindow2() {
60
1448
  if (typeof window === "undefined") {
61
1449
  return void 0;
62
1450
  }
63
1451
  return window;
64
1452
  }
65
- function warnMissingBridge(methodName) {
66
- if (isDevelopment()) {
1453
+ function warnMissingBridge2(methodName) {
1454
+ if (isDevelopment2()) {
67
1455
  console.warn(
68
1456
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
69
1457
  );
70
1458
  }
71
1459
  }
72
1460
  async function getPlayerCharacter() {
73
- const bridge = getBridgeWindow();
1461
+ const bridge = getBridgeWindow2();
74
1462
  if (typeof bridge?.__oasizGetPlayerCharacter !== "function") {
75
- warnMissingBridge("getPlayerCharacter");
1463
+ warnMissingBridge2("getPlayerCharacter");
76
1464
  return null;
77
1465
  }
78
1466
  try {
79
1467
  const result = await bridge.__oasizGetPlayerCharacter();
80
1468
  return result ?? null;
81
1469
  } catch (error) {
82
- if (isDevelopment()) {
1470
+ if (isDevelopment2()) {
83
1471
  console.error("[oasiz/sdk] getPlayerCharacter failed:", error);
84
1472
  }
85
1473
  return null;
@@ -87,23 +1475,23 @@ async function getPlayerCharacter() {
87
1475
  }
88
1476
 
89
1477
  // src/haptics.ts
90
- function isDevelopment2() {
1478
+ function isDevelopment3() {
91
1479
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
92
1480
  return nodeEnv !== "production";
93
1481
  }
94
- function getBridgeWindow2() {
1482
+ function getBridgeWindow3() {
95
1483
  if (typeof window === "undefined") {
96
1484
  return void 0;
97
1485
  }
98
1486
  return window;
99
1487
  }
100
1488
  function triggerHaptic(type) {
101
- const bridge = getBridgeWindow2();
1489
+ const bridge = getBridgeWindow3();
102
1490
  if (typeof bridge?.triggerHaptic === "function") {
103
1491
  bridge.triggerHaptic(type);
104
1492
  return;
105
1493
  }
106
- if (isDevelopment2()) {
1494
+ if (isDevelopment3()) {
107
1495
  console.warn(
108
1496
  "[oasiz/sdk] triggerHaptic bridge is unavailable. This is expected in local development."
109
1497
  );
@@ -130,7 +1518,7 @@ var MIN_EXPANDED_WIDTH = 160;
130
1518
  var MIN_EXPANDED_HEIGHT = 110;
131
1519
  var RESIZE_HOTSPOT_PX = 28;
132
1520
  var TOP_DRAG_ZONE_PX = 44;
133
- var NOOP_HANDLE = {
1521
+ var NOOP_HANDLE2 = {
134
1522
  clear() {
135
1523
  },
136
1524
  destroy() {
@@ -143,13 +1531,13 @@ var NOOP_HANDLE = {
143
1531
  show() {
144
1532
  }
145
1533
  };
146
- function getBrowserWindow() {
1534
+ function getBrowserWindow2() {
147
1535
  if (typeof window === "undefined") {
148
1536
  return void 0;
149
1537
  }
150
1538
  return window;
151
1539
  }
152
- function getDocument() {
1540
+ function getDocument2() {
153
1541
  if (typeof document === "undefined") {
154
1542
  return void 0;
155
1543
  }
@@ -235,7 +1623,7 @@ function createEntry(level, args, id) {
235
1623
  timestamp: Date.now()
236
1624
  };
237
1625
  }
238
- function createButton(label) {
1626
+ function createButton2(label) {
239
1627
  const button = document.createElement("button");
240
1628
  button.type = "button";
241
1629
  button.textContent = label;
@@ -277,7 +1665,7 @@ function getLevelAccent(level) {
277
1665
  };
278
1666
  }
279
1667
  function getViewportSize() {
280
- const browserWindow = getBrowserWindow();
1668
+ const browserWindow = getBrowserWindow2();
281
1669
  return {
282
1670
  width: Math.max(320, browserWindow?.innerWidth ?? 1280),
283
1671
  height: Math.max(240, browserWindow?.innerHeight ?? 720)
@@ -407,7 +1795,7 @@ function stopResizing(state) {
407
1795
  state.removeResizeListeners = null;
408
1796
  }
409
1797
  function beginDragTracking(state, startPoint) {
410
- const doc = getDocument();
1798
+ const doc = getDocument2();
411
1799
  if (!doc) {
412
1800
  return;
413
1801
  }
@@ -474,7 +1862,7 @@ function beginDragTracking(state, startPoint) {
474
1862
  };
475
1863
  }
476
1864
  function startResizing(state, startPoint) {
477
- const doc = getDocument();
1865
+ const doc = getDocument2();
478
1866
  if (!doc) {
479
1867
  return;
480
1868
  }
@@ -560,7 +1948,7 @@ function attachDragStartListeners(element, state, options = {}) {
560
1948
  }
561
1949
  function attachCollapsedToggleListeners(element, state) {
562
1950
  const startToggleInteraction = (startPoint) => {
563
- const doc = getDocument();
1951
+ const doc = getDocument2();
564
1952
  if (!doc) {
565
1953
  return;
566
1954
  }
@@ -636,7 +2024,7 @@ function createOverlayUi(state) {
636
2024
  "width:min(565px, calc(100vw - 24px))",
637
2025
  "pointer-events:none"
638
2026
  ].join(";");
639
- const toggleButton = createButton("Logs");
2027
+ const toggleButton = createButton2("Logs");
640
2028
  toggleButton.style.pointerEvents = "auto";
641
2029
  toggleButton.style.alignSelf = "flex-end";
642
2030
  toggleButton.style.display = "inline-flex";
@@ -690,7 +2078,7 @@ function createOverlayUi(state) {
690
2078
  "gap:8px",
691
2079
  "pointer-events:auto"
692
2080
  ].join(";");
693
- const clearButton = createButton("Clear");
2081
+ const clearButton = createButton2("Clear");
694
2082
  clearButton.style.background = "rgba(255,255,255,0.1)";
695
2083
  clearButton.style.border = "1px solid rgba(255,255,255,0.16)";
696
2084
  clearButton.style.color = "#eef6ff";
@@ -698,7 +2086,7 @@ function createOverlayUi(state) {
698
2086
  clearButton.style.padding = "4px 9px";
699
2087
  clearButton.style.fontSize = "11px";
700
2088
  clearButton.style.backdropFilter = "blur(8px)";
701
- const collapseButton = createButton("Hide");
2089
+ const collapseButton = createButton2("Hide");
702
2090
  collapseButton.style.background = "rgba(113, 171, 255, 0.12)";
703
2091
  collapseButton.style.border = "1px solid rgba(113, 171, 255, 0.2)";
704
2092
  collapseButton.style.color = "#d9ebff";
@@ -824,7 +2212,7 @@ function renderOverlay(state) {
824
2212
  applyOverlayPosition(state);
825
2213
  }
826
2214
  function mountOverlay(state) {
827
- const doc = getDocument();
2215
+ const doc = getDocument2();
828
2216
  if (!doc?.body || state.ui) {
829
2217
  return;
830
2218
  }
@@ -864,7 +2252,7 @@ function cleanupOverlay(browserWindow, state) {
864
2252
  stopResizing(state);
865
2253
  stopDragging(state);
866
2254
  if (state.domReadyHandler) {
867
- getDocument()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
2255
+ getDocument2()?.removeEventListener("DOMContentLoaded", state.domReadyHandler);
868
2256
  state.domReadyHandler = void 0;
869
2257
  }
870
2258
  if (state.resizeHandler && typeof browserWindow.removeEventListener === "function") {
@@ -883,7 +2271,7 @@ function createController(browserWindow, state) {
883
2271
  this.ensureMounted();
884
2272
  },
885
2273
  ensureMounted() {
886
- const doc = getDocument();
2274
+ const doc = getDocument2();
887
2275
  if (!doc) {
888
2276
  return;
889
2277
  }
@@ -929,12 +2317,12 @@ function createController(browserWindow, state) {
929
2317
  }
930
2318
  function enableLogOverlay(options = {}) {
931
2319
  if (options.enabled === false) {
932
- return NOOP_HANDLE;
2320
+ return NOOP_HANDLE2;
933
2321
  }
934
- const browserWindow = getBrowserWindow();
935
- const doc = getDocument();
2322
+ const browserWindow = getBrowserWindow2();
2323
+ const doc = getDocument2();
936
2324
  if (!browserWindow || !doc) {
937
- return NOOP_HANDLE;
2325
+ return NOOP_HANDLE2;
938
2326
  }
939
2327
  const existingController = browserWindow.__oasizLogOverlayController__;
940
2328
  const existingState = browserWindow.__oasizLogOverlayState__;
@@ -1002,74 +2390,74 @@ function enableLogOverlay(options = {}) {
1002
2390
  }
1003
2391
 
1004
2392
  // src/multiplayer.ts
1005
- function isDevelopment3() {
2393
+ function isDevelopment4() {
1006
2394
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1007
2395
  return nodeEnv !== "production";
1008
2396
  }
1009
- function getBridgeWindow3() {
2397
+ function getBridgeWindow4() {
1010
2398
  if (typeof window === "undefined") {
1011
2399
  return void 0;
1012
2400
  }
1013
2401
  return window;
1014
2402
  }
1015
2403
  function shareRoomCode(roomCode, options) {
1016
- const bridge = getBridgeWindow3();
2404
+ const bridge = getBridgeWindow4();
1017
2405
  if (typeof bridge?.shareRoomCode === "function") {
1018
2406
  bridge.shareRoomCode(roomCode, options);
1019
2407
  return;
1020
2408
  }
1021
- if (isDevelopment3()) {
2409
+ if (isDevelopment4()) {
1022
2410
  console.warn(
1023
2411
  "[oasiz/sdk] shareRoomCode bridge is unavailable. This is expected in local development."
1024
2412
  );
1025
2413
  }
1026
2414
  }
1027
2415
  function openInviteModal() {
1028
- const bridge = getBridgeWindow3();
2416
+ const bridge = getBridgeWindow4();
1029
2417
  if (typeof bridge?.openInviteModal === "function") {
1030
2418
  bridge.openInviteModal();
1031
2419
  return;
1032
2420
  }
1033
- if (isDevelopment3()) {
2421
+ if (isDevelopment4()) {
1034
2422
  console.warn(
1035
2423
  "[oasiz/sdk] openInviteModal bridge is unavailable. This is expected in local development."
1036
2424
  );
1037
2425
  }
1038
2426
  }
1039
2427
  function getGameId() {
1040
- const bridge = getBridgeWindow3();
2428
+ const bridge = getBridgeWindow4();
1041
2429
  return bridge?.__GAME_ID__;
1042
2430
  }
1043
2431
  function getRoomCode() {
1044
- const bridge = getBridgeWindow3();
2432
+ const bridge = getBridgeWindow4();
1045
2433
  return bridge?.__ROOM_CODE__;
1046
2434
  }
1047
2435
  function getPlayerId() {
1048
- const bridge = getBridgeWindow3();
2436
+ const bridge = getBridgeWindow4();
1049
2437
  return bridge?.__PLAYER_ID__;
1050
2438
  }
1051
2439
  function getPlayerName() {
1052
- const bridge = getBridgeWindow3();
2440
+ const bridge = getBridgeWindow4();
1053
2441
  return bridge?.__PLAYER_NAME__;
1054
2442
  }
1055
2443
  function getPlayerAvatar() {
1056
- const bridge = getBridgeWindow3();
2444
+ const bridge = getBridgeWindow4();
1057
2445
  return bridge?.__PLAYER_AVATAR__;
1058
2446
  }
1059
2447
 
1060
2448
  // src/score.ts
1061
- function isDevelopment4() {
2449
+ function isDevelopment5() {
1062
2450
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1063
2451
  return nodeEnv !== "production";
1064
2452
  }
1065
- function warnMissingBridge2(methodName) {
1066
- if (isDevelopment4()) {
2453
+ function warnMissingBridge3(methodName) {
2454
+ if (isDevelopment5()) {
1067
2455
  console.warn(
1068
2456
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1069
2457
  );
1070
2458
  }
1071
2459
  }
1072
- function getBridgeWindow4() {
2460
+ function getBridgeWindow5() {
1073
2461
  if (typeof window === "undefined") {
1074
2462
  return void 0;
1075
2463
  }
@@ -1077,49 +2465,49 @@ function getBridgeWindow4() {
1077
2465
  }
1078
2466
  function submitScore(score) {
1079
2467
  if (!Number.isFinite(score)) {
1080
- if (isDevelopment4()) {
2468
+ if (isDevelopment5()) {
1081
2469
  console.warn("[oasiz/sdk] submitScore expected a finite number:", score);
1082
2470
  }
1083
2471
  return;
1084
2472
  }
1085
- const bridge = getBridgeWindow4();
2473
+ const bridge = getBridgeWindow5();
1086
2474
  const normalizedScore = Math.max(0, Math.floor(score));
1087
2475
  if (typeof bridge?.submitScore === "function") {
1088
2476
  bridge.submitScore(normalizedScore);
1089
2477
  return;
1090
2478
  }
1091
- warnMissingBridge2("submitScore");
2479
+ warnMissingBridge3("submitScore");
1092
2480
  }
1093
2481
 
1094
2482
  // src/score-edit.ts
1095
- function isDevelopment5() {
2483
+ function isDevelopment6() {
1096
2484
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1097
2485
  return nodeEnv !== "production";
1098
2486
  }
1099
- function getBridgeWindow5() {
2487
+ function getBridgeWindow6() {
1100
2488
  if (typeof window === "undefined") {
1101
2489
  return void 0;
1102
2490
  }
1103
2491
  return window;
1104
2492
  }
1105
- function warnMissingBridge3(methodName) {
1106
- if (isDevelopment5()) {
2493
+ function warnMissingBridge4(methodName) {
2494
+ if (isDevelopment6()) {
1107
2495
  console.warn(
1108
2496
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1109
2497
  );
1110
2498
  }
1111
2499
  }
1112
2500
  async function editScore(payload, methodName) {
1113
- const bridge = getBridgeWindow5();
2501
+ const bridge = getBridgeWindow6();
1114
2502
  if (typeof bridge?.__oasizEditScore !== "function") {
1115
- warnMissingBridge3(methodName);
2503
+ warnMissingBridge4(methodName);
1116
2504
  return null;
1117
2505
  }
1118
2506
  try {
1119
2507
  const result = await bridge.__oasizEditScore(payload);
1120
2508
  return result ?? null;
1121
2509
  } catch (error) {
1122
- if (isDevelopment5()) {
2510
+ if (isDevelopment6()) {
1123
2511
  console.error("[oasiz/sdk] " + methodName + " failed:", error);
1124
2512
  }
1125
2513
  return null;
@@ -1127,7 +2515,7 @@ async function editScore(payload, methodName) {
1127
2515
  }
1128
2516
  async function addScore(delta) {
1129
2517
  if (!Number.isInteger(delta)) {
1130
- if (isDevelopment5()) {
2518
+ if (isDevelopment6()) {
1131
2519
  console.warn("[oasiz/sdk] addScore expected an integer:", delta);
1132
2520
  }
1133
2521
  return null;
@@ -1139,7 +2527,7 @@ async function addScore(delta) {
1139
2527
  }
1140
2528
  async function setScore(score) {
1141
2529
  if (!Number.isInteger(score) || score < 0) {
1142
- if (isDevelopment5()) {
2530
+ if (isDevelopment6()) {
1143
2531
  console.warn(
1144
2532
  "[oasiz/sdk] setScore expected a non-negative integer:",
1145
2533
  score
@@ -1151,18 +2539,18 @@ async function setScore(score) {
1151
2539
  }
1152
2540
 
1153
2541
  // src/share.ts
1154
- function isDevelopment6() {
2542
+ function isDevelopment7() {
1155
2543
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1156
2544
  return nodeEnv !== "production";
1157
2545
  }
1158
- function getBridgeWindow6() {
2546
+ function getBridgeWindow7() {
1159
2547
  if (typeof window === "undefined") {
1160
2548
  return void 0;
1161
2549
  }
1162
2550
  return window;
1163
2551
  }
1164
- function warnMissingBridge4(methodName) {
1165
- if (isDevelopment6()) {
2552
+ function warnMissingBridge5(methodName) {
2553
+ if (isDevelopment7()) {
1166
2554
  console.warn(
1167
2555
  "[oasiz/sdk] " + methodName + " share bridge is unavailable. This is expected in local development."
1168
2556
  );
@@ -1205,20 +2593,20 @@ function validateRequest(options) {
1205
2593
  }
1206
2594
  async function share(options) {
1207
2595
  const request = validateRequest(options);
1208
- const bridge = getBridgeWindow6();
2596
+ const bridge = getBridgeWindow7();
1209
2597
  if (typeof bridge?.__oasizShareRequest !== "function") {
1210
- warnMissingBridge4("__oasizShareRequest");
2598
+ warnMissingBridge5("__oasizShareRequest");
1211
2599
  throw new Error("Share bridge unavailable");
1212
2600
  }
1213
2601
  await bridge.__oasizShareRequest(request);
1214
2602
  }
1215
2603
 
1216
2604
  // src/state.ts
1217
- function isDevelopment7() {
2605
+ function isDevelopment8() {
1218
2606
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1219
2607
  return nodeEnv !== "production";
1220
2608
  }
1221
- function getBridgeWindow7() {
2609
+ function getBridgeWindow8() {
1222
2610
  if (typeof window === "undefined") {
1223
2611
  return void 0;
1224
2612
  }
@@ -1231,22 +2619,22 @@ function isPlainObject(value) {
1231
2619
  const proto = Object.getPrototypeOf(value);
1232
2620
  return proto === Object.prototype || proto === null;
1233
2621
  }
1234
- function warnMissingBridge5(methodName) {
1235
- if (isDevelopment7()) {
2622
+ function warnMissingBridge6(methodName) {
2623
+ if (isDevelopment8()) {
1236
2624
  console.warn(
1237
2625
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1238
2626
  );
1239
2627
  }
1240
2628
  }
1241
2629
  function loadGameState() {
1242
- const bridge = getBridgeWindow7();
2630
+ const bridge = getBridgeWindow8();
1243
2631
  if (typeof bridge?.loadGameState !== "function") {
1244
- warnMissingBridge5("loadGameState");
2632
+ warnMissingBridge6("loadGameState");
1245
2633
  return {};
1246
2634
  }
1247
2635
  const state = bridge.loadGameState();
1248
2636
  if (!isPlainObject(state)) {
1249
- if (isDevelopment7()) {
2637
+ if (isDevelopment8()) {
1250
2638
  console.warn(
1251
2639
  "[oasiz/sdk] loadGameState returned invalid data. Falling back to empty object."
1252
2640
  );
@@ -1257,35 +2645,35 @@ function loadGameState() {
1257
2645
  }
1258
2646
  function saveGameState(state) {
1259
2647
  if (!isPlainObject(state)) {
1260
- if (isDevelopment7()) {
2648
+ if (isDevelopment8()) {
1261
2649
  console.warn("[oasiz/sdk] saveGameState expected a plain object:", state);
1262
2650
  }
1263
2651
  return;
1264
2652
  }
1265
- const bridge = getBridgeWindow7();
2653
+ const bridge = getBridgeWindow8();
1266
2654
  if (typeof bridge?.saveGameState === "function") {
1267
2655
  bridge.saveGameState(state);
1268
2656
  return;
1269
2657
  }
1270
- warnMissingBridge5("saveGameState");
2658
+ warnMissingBridge6("saveGameState");
1271
2659
  }
1272
2660
  function flushGameState() {
1273
- const bridge = getBridgeWindow7();
2661
+ const bridge = getBridgeWindow8();
1274
2662
  if (typeof bridge?.flushGameState === "function") {
1275
2663
  bridge.flushGameState();
1276
2664
  return;
1277
2665
  }
1278
- warnMissingBridge5("flushGameState");
2666
+ warnMissingBridge6("flushGameState");
1279
2667
  }
1280
2668
 
1281
2669
  // src/lifecycle.ts
1282
- function isDevelopment8() {
2670
+ function isDevelopment9() {
1283
2671
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1284
2672
  return nodeEnv !== "production";
1285
2673
  }
1286
2674
  function addLifecycleListener(eventName, callback) {
1287
2675
  if (typeof window === "undefined") {
1288
- if (isDevelopment8()) {
2676
+ if (isDevelopment9()) {
1289
2677
  console.warn(
1290
2678
  "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1291
2679
  );
@@ -1320,24 +2708,24 @@ function createInsetEdges(value) {
1320
2708
  left: value
1321
2709
  };
1322
2710
  }
1323
- function isDevelopment9() {
2711
+ function isDevelopment10() {
1324
2712
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1325
2713
  return nodeEnv !== "production";
1326
2714
  }
1327
- function getBridgeWindow8() {
2715
+ function getBridgeWindow9() {
1328
2716
  if (typeof window === "undefined") {
1329
2717
  return void 0;
1330
2718
  }
1331
2719
  return window;
1332
2720
  }
1333
- function warnMissingBridge6(methodName) {
1334
- if (isDevelopment9()) {
2721
+ function warnMissingBridge7(methodName) {
2722
+ if (isDevelopment10()) {
1335
2723
  console.warn(
1336
2724
  "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1337
2725
  );
1338
2726
  }
1339
2727
  }
1340
- function isRecord(value) {
2728
+ function isRecord2(value) {
1341
2729
  return typeof value === "object" && value !== null;
1342
2730
  }
1343
2731
  function toFiniteNumber(value) {
@@ -1506,11 +2894,11 @@ function callBridgeFunction(bridge, name) {
1506
2894
  }
1507
2895
  }
1508
2896
  function readInsetObjectValue(value, side, group) {
1509
- if (!isRecord(value)) {
2897
+ if (!isRecord2(value)) {
1510
2898
  return void 0;
1511
2899
  }
1512
2900
  if (group) {
1513
- return isRecord(value[group]) ? value[group][side] : void 0;
2901
+ return isRecord2(value[group]) ? value[group][side] : void 0;
1514
2902
  }
1515
2903
  return value[side];
1516
2904
  }
@@ -1583,7 +2971,7 @@ function resolveInsetSide(bridge, sources, side) {
1583
2971
  };
1584
2972
  }
1585
2973
  function getViewportInsets() {
1586
- const bridge = getBridgeWindow8();
2974
+ const bridge = getBridgeWindow9();
1587
2975
  if (!bridge) {
1588
2976
  return {
1589
2977
  pixels: createInsetEdges(0),
@@ -1601,19 +2989,19 @@ function getViewportInsets() {
1601
2989
  return { pixels, percent };
1602
2990
  }
1603
2991
  function getSafeAreaTop() {
1604
- const bridge = getBridgeWindow8();
2992
+ const bridge = getBridgeWindow9();
1605
2993
  if (!bridge) {
1606
2994
  return 0;
1607
2995
  }
1608
2996
  const top = getViewportInsets().percent.top;
1609
2997
  if (top <= 0) {
1610
- warnMissingBridge6("getSafeAreaTop");
2998
+ warnMissingBridge7("getSafeAreaTop");
1611
2999
  }
1612
3000
  return top;
1613
3001
  }
1614
3002
  function setLeaderboardVisible(visible) {
1615
3003
  if (typeof visible !== "boolean") {
1616
- if (isDevelopment9()) {
3004
+ if (isDevelopment10()) {
1617
3005
  console.warn(
1618
3006
  "[oasiz/sdk] setLeaderboardVisible expected a boolean:",
1619
3007
  visible
@@ -1621,253 +3009,12 @@ function setLeaderboardVisible(visible) {
1621
3009
  }
1622
3010
  return;
1623
3011
  }
1624
- const bridge = getBridgeWindow8();
3012
+ const bridge = getBridgeWindow9();
1625
3013
  if (typeof bridge?.__oasizSetLeaderboardVisible === "function") {
1626
3014
  bridge.__oasizSetLeaderboardVisible(visible);
1627
3015
  return;
1628
3016
  }
1629
- warnMissingBridge6("__oasizSetLeaderboardVisible");
1630
- }
1631
-
1632
- // src/navigation.ts
1633
- var BACK_BUTTON_TEST_STATE_KEY = "__oasizBackButtonTest";
1634
- var activeBackListeners = 0;
1635
- var activeBackButtonTestingHandle;
1636
- function isDevelopment10() {
1637
- const nodeEnv = globalThis.process?.env?.NODE_ENV;
1638
- return nodeEnv !== "production";
1639
- }
1640
- function getBridgeWindow9() {
1641
- if (typeof window === "undefined") {
1642
- return void 0;
1643
- }
1644
- return window;
1645
- }
1646
- function warnMissingBridge7(methodName) {
1647
- if (isDevelopment10()) {
1648
- console.warn(
1649
- "[oasiz/sdk] " + methodName + " bridge is unavailable. This is expected in local development."
1650
- );
1651
- }
1652
- }
1653
- function normalizeNavigationError(error) {
1654
- if (error instanceof Error) {
1655
- return error;
1656
- }
1657
- return new Error(
1658
- typeof error === "string" ? error : "Back button callback failed."
1659
- );
1660
- }
1661
- function isRecord2(value) {
1662
- return typeof value === "object" && value !== null;
1663
- }
1664
- function dispatchNavigationEvent(eventName) {
1665
- const bridge = getBridgeWindow9();
1666
- if (!bridge || typeof bridge.dispatchEvent !== "function") {
1667
- return;
1668
- }
1669
- bridge.dispatchEvent(new Event(eventName));
1670
- }
1671
- function addNavigationListener(eventName, callback) {
1672
- if (typeof window === "undefined") {
1673
- if (isDevelopment10()) {
1674
- console.warn(
1675
- "[oasiz/sdk] " + eventName + " listener registered without a browser window. This is expected in local development."
1676
- );
1677
- }
1678
- return () => {
1679
- };
1680
- }
1681
- const handler = () => callback();
1682
- window.addEventListener(eventName, handler);
1683
- return () => window.removeEventListener(eventName, handler);
1684
- }
1685
- function enableBackButtonTesting(options = {}) {
1686
- activeBackButtonTestingHandle?.destroy();
1687
- const bridge = getBridgeWindow9();
1688
- if (!bridge) {
1689
- if (isDevelopment10()) {
1690
- console.warn(
1691
- "[oasiz/sdk] enableBackButtonTesting requires a browser window."
1692
- );
1693
- }
1694
- return {
1695
- destroy: () => {
1696
- },
1697
- isBackOverrideActive: () => false,
1698
- triggerBack: () => {
1699
- },
1700
- triggerLeave: () => {
1701
- }
1702
- };
1703
- }
1704
- const bridgeWindow = bridge;
1705
- const keyboard = options.keyboard ?? true;
1706
- const browserHistory = options.browserHistory ?? true;
1707
- const log = options.log === true;
1708
- const previousSetBackOverride = bridgeWindow.__oasizSetBackOverride;
1709
- const previousLeaveGame = bridgeWindow.__oasizLeaveGame;
1710
- let destroyed = false;
1711
- let backOverrideActive = false;
1712
- let historyTrapArmed = false;
1713
- function maybeLog(message) {
1714
- if (log) {
1715
- console.info("[oasiz/sdk] " + message);
1716
- }
1717
- }
1718
- function canUseHistoryTrap() {
1719
- return browserHistory && typeof bridgeWindow.history?.pushState === "function" && typeof bridgeWindow.history?.replaceState === "function" && typeof bridgeWindow.location?.href === "string";
1720
- }
1721
- function ensureHistoryTrap() {
1722
- if (!backOverrideActive || historyTrapArmed || !canUseHistoryTrap()) {
1723
- return;
1724
- }
1725
- try {
1726
- const currentState = isRecord2(bridgeWindow.history.state) ? bridgeWindow.history.state : {};
1727
- bridgeWindow.history.replaceState(
1728
- { ...currentState, [BACK_BUTTON_TEST_STATE_KEY]: "base" },
1729
- "",
1730
- bridgeWindow.location.href
1731
- );
1732
- bridgeWindow.history.pushState(
1733
- { [BACK_BUTTON_TEST_STATE_KEY]: "trap" },
1734
- "",
1735
- bridgeWindow.location.href
1736
- );
1737
- historyTrapArmed = true;
1738
- maybeLog("Local browser Back testing is armed.");
1739
- } catch (error) {
1740
- historyTrapArmed = false;
1741
- if (log) {
1742
- console.warn("[oasiz/sdk] Failed to arm browser Back testing:", error);
1743
- }
1744
- }
1745
- }
1746
- function triggerBack() {
1747
- dispatchNavigationEvent("oasiz:back");
1748
- }
1749
- function triggerLeave() {
1750
- dispatchNavigationEvent("oasiz:leave");
1751
- }
1752
- function setBackOverride(active) {
1753
- backOverrideActive = active;
1754
- if (active) {
1755
- ensureHistoryTrap();
1756
- }
1757
- if (typeof previousSetBackOverride === "function") {
1758
- previousSetBackOverride(active);
1759
- }
1760
- maybeLog("Back override " + (active ? "enabled" : "disabled") + ".");
1761
- }
1762
- function stopBackEvent(event) {
1763
- event.preventDefault();
1764
- event.stopPropagation();
1765
- event.stopImmediatePropagation?.();
1766
- }
1767
- const handleKeyDown = (event) => {
1768
- if (!backOverrideActive || event.key !== "Escape") {
1769
- return;
1770
- }
1771
- stopBackEvent(event);
1772
- triggerBack();
1773
- };
1774
- const handlePopState = (event) => {
1775
- if (!backOverrideActive) {
1776
- historyTrapArmed = false;
1777
- return;
1778
- }
1779
- stopBackEvent(event);
1780
- triggerBack();
1781
- historyTrapArmed = false;
1782
- ensureHistoryTrap();
1783
- };
1784
- const testLeaveGame = () => {
1785
- triggerLeave();
1786
- if (typeof previousLeaveGame === "function") {
1787
- previousLeaveGame();
1788
- }
1789
- };
1790
- bridgeWindow.__oasizSetBackOverride = setBackOverride;
1791
- bridgeWindow.__oasizLeaveGame = testLeaveGame;
1792
- if (keyboard) {
1793
- bridgeWindow.addEventListener("keydown", handleKeyDown);
1794
- }
1795
- if (browserHistory) {
1796
- bridgeWindow.addEventListener("popstate", handlePopState);
1797
- }
1798
- if (activeBackListeners > 0) {
1799
- setBackOverride(true);
1800
- }
1801
- maybeLog("Back button testing bridge installed.");
1802
- const handle = {
1803
- destroy: () => {
1804
- if (destroyed) return;
1805
- destroyed = true;
1806
- if (keyboard) {
1807
- bridgeWindow.removeEventListener("keydown", handleKeyDown);
1808
- }
1809
- if (browserHistory) {
1810
- bridgeWindow.removeEventListener("popstate", handlePopState);
1811
- }
1812
- if (bridgeWindow.__oasizSetBackOverride === setBackOverride) {
1813
- bridgeWindow.__oasizSetBackOverride = previousSetBackOverride;
1814
- }
1815
- if (bridgeWindow.__oasizLeaveGame === testLeaveGame) {
1816
- bridgeWindow.__oasizLeaveGame = previousLeaveGame;
1817
- }
1818
- if (activeBackButtonTestingHandle === handle) {
1819
- activeBackButtonTestingHandle = void 0;
1820
- }
1821
- maybeLog("Back button testing bridge removed.");
1822
- },
1823
- isBackOverrideActive: () => backOverrideActive,
1824
- triggerBack,
1825
- triggerLeave
1826
- };
1827
- activeBackButtonTestingHandle = handle;
1828
- return handle;
1829
- }
1830
- function onBackButton(callback) {
1831
- const off = addNavigationListener("oasiz:back", () => {
1832
- try {
1833
- callback();
1834
- } catch (error) {
1835
- leaveGame();
1836
- throw normalizeNavigationError(error);
1837
- }
1838
- });
1839
- const bridge = getBridgeWindow9();
1840
- activeBackListeners += 1;
1841
- if (activeBackListeners === 1) {
1842
- if (typeof bridge?.__oasizSetBackOverride === "function") {
1843
- bridge.__oasizSetBackOverride(true);
1844
- } else {
1845
- warnMissingBridge7("__oasizSetBackOverride");
1846
- }
1847
- }
1848
- return () => {
1849
- off();
1850
- activeBackListeners = Math.max(0, activeBackListeners - 1);
1851
- if (activeBackListeners === 0) {
1852
- const currentBridge = getBridgeWindow9();
1853
- if (typeof currentBridge?.__oasizSetBackOverride === "function") {
1854
- currentBridge.__oasizSetBackOverride(false);
1855
- } else {
1856
- warnMissingBridge7("__oasizSetBackOverride");
1857
- }
1858
- }
1859
- };
1860
- }
1861
- function onLeaveGame(callback) {
1862
- return addNavigationListener("oasiz:leave", callback);
1863
- }
1864
- function leaveGame() {
1865
- const bridge = getBridgeWindow9();
1866
- if (typeof bridge?.__oasizLeaveGame === "function") {
1867
- bridge.__oasizLeaveGame();
1868
- return;
1869
- }
1870
- warnMissingBridge7("__oasizLeaveGame");
3017
+ warnMissingBridge7("__oasizSetLeaderboardVisible");
1871
3018
  }
1872
3019
 
1873
3020
  // src/performance.ts
@@ -2196,6 +3343,7 @@ function getGraphicsPerformance() {
2196
3343
  // src/index.ts
2197
3344
  var oasiz = {
2198
3345
  submitScore,
3346
+ enableAppSimulator,
2199
3347
  addScore,
2200
3348
  setScore,
2201
3349
  getPlayerCharacter,
@@ -2245,6 +3393,7 @@ var oasiz = {
2245
3393
  // Annotate the CommonJS export names for ESM import in node:
2246
3394
  0 && (module.exports = {
2247
3395
  addScore,
3396
+ enableAppSimulator,
2248
3397
  enableBackButtonTesting,
2249
3398
  enableLogOverlay,
2250
3399
  flushGameState,