@oasiz/sdk 1.8.1 → 1.8.3

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