@bloopjs/web 0.0.102 → 0.0.104

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/mod.js CHANGED
@@ -82,6 +82,8 @@ __export2(exports_engine, {
82
82
  SNAPSHOT_HEADER_LEN: () => SNAPSHOT_HEADER_LEN,
83
83
  SNAPSHOT_HEADER_ENGINE_LEN_OFFSET: () => SNAPSHOT_HEADER_ENGINE_LEN_OFFSET,
84
84
  SCREEN_CTX_OFFSET: () => SCREEN_CTX_OFFSET,
85
+ RandContext: () => RandContext,
86
+ RAND_CTX_OFFSET: () => RAND_CTX_OFFSET,
85
87
  PlayerInputContext: () => PlayerInputContext,
86
88
  PLAYER_INPUTS_SIZE: () => PLAYER_INPUTS_SIZE,
87
89
  PLAYER_INPUTS_MOUSE_CTX_OFFSET: () => PLAYER_INPUTS_MOUSE_CTX_OFFSET,
@@ -421,6 +423,7 @@ var SCREEN_CTX_HEIGHT_OFFSET = 4;
421
423
  var SCREEN_CTX_PHYSICAL_WIDTH_OFFSET = 8;
422
424
  var SCREEN_CTX_PHYSICAL_HEIGHT_OFFSET = 12;
423
425
  var SCREEN_CTX_PIXEL_RATIO_OFFSET = 16;
426
+ var RAND_CTX_SEED_OFFSET = 0;
424
427
  var MOUSE_CTX_X_OFFSET = 0;
425
428
  var MOUSE_CTX_Y_OFFSET = 4;
426
429
  var MOUSE_CTX_WHEEL_X_OFFSET = 8;
@@ -1354,6 +1357,57 @@ class NetContext {
1354
1357
  }
1355
1358
  }
1356
1359
 
1360
+ class RandContext {
1361
+ dataView;
1362
+ constructor(dataView) {
1363
+ this.dataView = dataView;
1364
+ }
1365
+ seed(value) {
1366
+ if (!this.dataView) {
1367
+ throw new Error("RandContext not initialized");
1368
+ }
1369
+ this.dataView.setUint32(RAND_CTX_SEED_OFFSET, value >>> 0, true);
1370
+ }
1371
+ getSeed() {
1372
+ if (!this.dataView) {
1373
+ throw new Error("RandContext not initialized");
1374
+ }
1375
+ return this.dataView.getUint32(RAND_CTX_SEED_OFFSET, true);
1376
+ }
1377
+ next() {
1378
+ if (!this.dataView) {
1379
+ throw new Error("RandContext not initialized");
1380
+ }
1381
+ let seed = this.dataView.getUint32(RAND_CTX_SEED_OFFSET, true);
1382
+ seed = seed + 1831565813 | 0;
1383
+ let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
1384
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
1385
+ this.dataView.setUint32(RAND_CTX_SEED_OFFSET, seed >>> 0, true);
1386
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1387
+ }
1388
+ coinFlip() {
1389
+ return this.next() < 0.5;
1390
+ }
1391
+ rollDice(sides = 6) {
1392
+ return Math.floor(this.next() * sides) + 1;
1393
+ }
1394
+ int(min, max) {
1395
+ return Math.floor(this.next() * (max - min + 1)) + min;
1396
+ }
1397
+ float(min, max) {
1398
+ return this.next() * (max - min) + min;
1399
+ }
1400
+ shuffle(array) {
1401
+ for (let i = array.length - 1;i > 0; i--) {
1402
+ const j = Math.floor(this.next() * (i + 1));
1403
+ const temp = array[i];
1404
+ array[i] = array[j];
1405
+ array[j] = temp;
1406
+ }
1407
+ return array;
1408
+ }
1409
+ }
1410
+
1357
1411
  class ScreenContext {
1358
1412
  dataView;
1359
1413
  constructor(dataView) {
@@ -1450,13 +1504,14 @@ function readTapeHeader(tape) {
1450
1504
  eventCount: view.getUint16(14, true)
1451
1505
  };
1452
1506
  }
1453
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.102/wasm/bloop.wasm");
1507
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.104/wasm/bloop.wasm");
1454
1508
  var MAX_ROLLBACK_FRAMES = 500;
1455
1509
  var TIME_CTX_OFFSET = 0;
1456
1510
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
1457
1511
  var EVENTS_OFFSET = INPUT_CTX_OFFSET + 4;
1458
1512
  var NET_CTX_OFFSET = EVENTS_OFFSET + 4;
1459
1513
  var SCREEN_CTX_OFFSET = NET_CTX_OFFSET + 4;
1514
+ var RAND_CTX_OFFSET = SCREEN_CTX_OFFSET + 4;
1460
1515
  var SNAPSHOT_HEADER_LEN = 16;
1461
1516
  var SNAPSHOT_HEADER_USER_LEN_OFFSET = 4;
1462
1517
  var SNAPSHOT_HEADER_ENGINE_LEN_OFFSET = 8;
@@ -1556,6 +1611,9 @@ class Sim {
1556
1611
  throw new Error(`failed to start recording, error code=${result}`);
1557
1612
  }
1558
1613
  }
1614
+ stopRecording() {
1615
+ this.wasm.stop_recording();
1616
+ }
1559
1617
  saveTape() {
1560
1618
  const tapeLen = this.wasm.get_tape_len();
1561
1619
  const tapePtr = this.wasm.get_tape_ptr();
@@ -1855,6 +1913,7 @@ class Bloop {
1855
1913
  #systems = [];
1856
1914
  #context;
1857
1915
  #engineBuffer = new ArrayBuffer(0);
1916
+ #randSeeded = false;
1858
1917
  static create(opts = {}) {
1859
1918
  return new Bloop(opts, "dontCallMeDirectly");
1860
1919
  }
@@ -1865,6 +1924,7 @@ class Bloop {
1865
1924
  const inputs = new InputContext;
1866
1925
  const net = new NetContext;
1867
1926
  const screen = new ScreenContext;
1927
+ const rand = new RandContext;
1868
1928
  this.#context = {
1869
1929
  bag: opts.bag ?? {},
1870
1930
  time: new TimeContext,
@@ -1872,7 +1932,8 @@ class Bloop {
1872
1932
  players: new Players(inputs, net),
1873
1933
  rawPointer: -1,
1874
1934
  net,
1875
- screen
1935
+ screen,
1936
+ rand
1876
1937
  };
1877
1938
  }
1878
1939
  get bag() {
@@ -1934,6 +1995,14 @@ class Bloop {
1934
1995
  if (this.#context.screen.dataView?.buffer !== this.#engineBuffer || this.#context.screen.dataView?.byteOffset !== screenCtxPtr) {
1935
1996
  this.#context.screen.dataView = new DataView(this.#engineBuffer, screenCtxPtr);
1936
1997
  }
1998
+ const randCtxPtr = dv.getUint32(RAND_CTX_OFFSET, true);
1999
+ if (this.#context.rand.dataView?.buffer !== this.#engineBuffer || this.#context.rand.dataView?.byteOffset !== randCtxPtr) {
2000
+ this.#context.rand.dataView = new DataView(this.#engineBuffer, randCtxPtr);
2001
+ }
2002
+ if (!this.#randSeeded) {
2003
+ this.#context.rand.seed(Date.now() & 4294967295);
2004
+ this.#randSeeded = true;
2005
+ }
1937
2006
  },
1938
2007
  systemsCallback: (system_handle, ptr) => {
1939
2008
  this.hooks.setContext(ptr);
@@ -3321,12 +3390,13 @@ function readTapeHeader2(tape) {
3321
3390
  eventCount: view.getUint16(14, true)
3322
3391
  };
3323
3392
  }
3324
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.102/wasm/bloop.wasm");
3393
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.104/wasm/bloop.wasm");
3325
3394
  var TIME_CTX_OFFSET2 = 0;
3326
3395
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3327
3396
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
3328
3397
  var NET_CTX_OFFSET2 = EVENTS_OFFSET2 + 4;
3329
3398
  var SCREEN_CTX_OFFSET2 = NET_CTX_OFFSET2 + 4;
3399
+ var RAND_CTX_OFFSET2 = SCREEN_CTX_OFFSET2 + 4;
3330
3400
 
3331
3401
  // src/debugui/mod.ts
3332
3402
  var exports_mod = {};
@@ -4444,9 +4514,12 @@ var onJumpForward = d3(null);
4444
4514
  var onSeek = d3(null);
4445
4515
  var onLoadTape = d3(null);
4446
4516
  var onReplayLastTape = d3(null);
4517
+ var onReplayLastSaved = d3(null);
4447
4518
  var onSaveTape = d3(null);
4448
4519
  var lastTapeName = d3(null);
4520
+ var lastSavedTapeName = d3(null);
4449
4521
  var isLoadDialogOpen = d3(false);
4522
+ var onToggleRecording = d3(null);
4450
4523
  var debugState = {
4451
4524
  layoutMode,
4452
4525
  isVisible: w3(() => layoutMode.value !== "off"),
@@ -4477,9 +4550,12 @@ var debugState = {
4477
4550
  onSeek,
4478
4551
  onLoadTape,
4479
4552
  onReplayLastTape,
4553
+ onReplayLastSaved,
4480
4554
  onSaveTape,
4481
4555
  lastTapeName,
4482
- isLoadDialogOpen
4556
+ lastSavedTapeName,
4557
+ isLoadDialogOpen,
4558
+ onToggleRecording
4483
4559
  };
4484
4560
  function cycleLayout() {
4485
4561
  const current = layoutMode.value;
@@ -4615,12 +4691,20 @@ function wirePlaybarHandlers(app) {
4615
4691
  };
4616
4692
  debugState.onSeek.value = (ratio) => {
4617
4693
  if (app.sim.hasHistory) {
4694
+ app.sim.pause();
4618
4695
  const startFrame = debugState.tapeStartFrame.value;
4619
4696
  const frameCount = debugState.tapeFrameCount.value;
4620
4697
  const targetFrame = startFrame + Math.floor(ratio * frameCount);
4621
4698
  app.sim.seek(targetFrame);
4622
4699
  }
4623
4700
  };
4701
+ debugState.onToggleRecording.value = () => {
4702
+ if (app.sim.isRecording) {
4703
+ app.sim.stopRecording();
4704
+ } else {
4705
+ app.sim.record();
4706
+ }
4707
+ };
4624
4708
  }
4625
4709
  function wireTapeDragDrop(canvas, app) {
4626
4710
  canvas.addEventListener("dragover", (e4) => {
@@ -4639,7 +4723,8 @@ function wireTapeDragDrop(canvas, app) {
4639
4723
  }
4640
4724
  var TAPE_DB_NAME = "bloop-debug";
4641
4725
  var TAPE_STORE_NAME = "tapes";
4642
- var TAPE_KEY = "last";
4726
+ var TAPE_KEY_LOADED = "last-loaded";
4727
+ var TAPE_KEY_SAVED = "last-saved";
4643
4728
  function openTapeDB() {
4644
4729
  return new Promise((resolve, reject) => {
4645
4730
  const request = indexedDB.open(TAPE_DB_NAME, 1);
@@ -4650,21 +4735,21 @@ function openTapeDB() {
4650
4735
  };
4651
4736
  });
4652
4737
  }
4653
- async function saveTapeToStorage(bytes, fileName) {
4738
+ async function saveTapeToStorage(bytes, fileName, key = TAPE_KEY_LOADED) {
4654
4739
  const db = await openTapeDB();
4655
4740
  return new Promise((resolve, reject) => {
4656
4741
  const tx = db.transaction(TAPE_STORE_NAME, "readwrite");
4657
- tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, TAPE_KEY);
4742
+ tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, key);
4658
4743
  tx.oncomplete = () => resolve();
4659
4744
  tx.onerror = () => reject(tx.error);
4660
4745
  });
4661
4746
  }
4662
- async function loadTapeFromStorage() {
4747
+ async function loadTapeFromStorage(key = TAPE_KEY_LOADED) {
4663
4748
  try {
4664
4749
  const db = await openTapeDB();
4665
4750
  return new Promise((resolve, reject) => {
4666
4751
  const tx = db.transaction(TAPE_STORE_NAME, "readonly");
4667
- const request = tx.objectStore(TAPE_STORE_NAME).get(TAPE_KEY);
4752
+ const request = tx.objectStore(TAPE_STORE_NAME).get(key);
4668
4753
  request.onsuccess = () => resolve(request.result ?? null);
4669
4754
  request.onerror = () => reject(request.error);
4670
4755
  });
@@ -4673,32 +4758,46 @@ async function loadTapeFromStorage() {
4673
4758
  }
4674
4759
  }
4675
4760
  async function checkForSavedTape() {
4676
- const saved = await loadTapeFromStorage();
4677
- debugState.lastTapeName.value = saved?.fileName ?? null;
4761
+ const [loaded, saved] = await Promise.all([
4762
+ loadTapeFromStorage(TAPE_KEY_LOADED),
4763
+ loadTapeFromStorage(TAPE_KEY_SAVED)
4764
+ ]);
4765
+ debugState.lastTapeName.value = loaded?.fileName ?? null;
4766
+ debugState.lastSavedTapeName.value = saved?.fileName ?? null;
4678
4767
  }
4679
4768
  function wireTapeLoadHandlers(app) {
4680
4769
  debugState.onLoadTape.value = async (bytes, fileName) => {
4681
4770
  app.loadTape(bytes);
4682
- await saveTapeToStorage(bytes, fileName);
4771
+ await saveTapeToStorage(bytes, fileName, TAPE_KEY_LOADED);
4683
4772
  debugState.lastTapeName.value = fileName;
4684
4773
  debugState.isLoadDialogOpen.value = false;
4685
4774
  };
4686
4775
  debugState.onReplayLastTape.value = async () => {
4687
- const saved = await loadTapeFromStorage();
4776
+ const saved = await loadTapeFromStorage(TAPE_KEY_LOADED);
4777
+ if (saved) {
4778
+ app.loadTape(saved.bytes);
4779
+ debugState.isLoadDialogOpen.value = false;
4780
+ }
4781
+ };
4782
+ debugState.onReplayLastSaved.value = async () => {
4783
+ const saved = await loadTapeFromStorage(TAPE_KEY_SAVED);
4688
4784
  if (saved) {
4689
4785
  app.loadTape(saved.bytes);
4690
4786
  debugState.isLoadDialogOpen.value = false;
4691
4787
  }
4692
4788
  };
4693
- debugState.onSaveTape.value = () => {
4789
+ debugState.onSaveTape.value = async () => {
4694
4790
  if (!app.sim.hasHistory)
4695
4791
  return;
4696
4792
  const tape = app.sim.saveTape();
4793
+ const fileName = `tape-${Date.now()}.bloop`;
4794
+ await saveTapeToStorage(tape, fileName, TAPE_KEY_SAVED);
4795
+ debugState.lastSavedTapeName.value = fileName;
4697
4796
  const blob = new Blob([tape], { type: "application/octet-stream" });
4698
4797
  const url = URL.createObjectURL(blob);
4699
4798
  const a4 = document.createElement("a");
4700
4799
  a4.href = url;
4701
- a4.download = `tape-${Date.now()}.bloop`;
4800
+ a4.download = fileName;
4702
4801
  a4.click();
4703
4802
  URL.revokeObjectURL(url);
4704
4803
  };
@@ -4982,8 +5081,14 @@ function TopBar({ leftLabel, rightLabel }) {
4982
5081
  const frameNumber2 = debugState.frameNumber.value;
4983
5082
  const rtt = debugState.netStatus.value.rtt;
4984
5083
  const isOnline = debugState.netStatus.value.peers.length > 0;
5084
+ const stopPropagation = q2((e4) => {
5085
+ e4.stopPropagation();
5086
+ }, []);
4985
5087
  return /* @__PURE__ */ u4("div", {
4986
5088
  className: "top-bar",
5089
+ onMouseDown: stopPropagation,
5090
+ onMouseUp: stopPropagation,
5091
+ onClick: stopPropagation,
4987
5092
  children: [
4988
5093
  /* @__PURE__ */ u4("span", {
4989
5094
  className: "top-bar-side-label",
@@ -5049,18 +5154,31 @@ function VerticalBar({
5049
5154
  value,
5050
5155
  max,
5051
5156
  side,
5052
- color = "#4a9eff"
5157
+ color = "#4a9eff",
5158
+ displayValue
5053
5159
  }) {
5054
5160
  const percentage = max > 0 ? Math.min(100, value / max * 100) : 0;
5161
+ const stopPropagation = q2((e4) => {
5162
+ e4.stopPropagation();
5163
+ }, []);
5055
5164
  return /* @__PURE__ */ u4("div", {
5056
5165
  className: `${side}-bar`,
5166
+ onMouseDown: stopPropagation,
5167
+ onMouseUp: stopPropagation,
5168
+ onClick: stopPropagation,
5057
5169
  children: /* @__PURE__ */ u4("div", {
5058
5170
  className: "vertical-bar",
5059
- children: /* @__PURE__ */ u4("div", {
5060
- className: "vertical-bar-fill",
5061
- style: { height: `${percentage}%`, background: color }
5062
- }, undefined, false, undefined, this)
5063
- }, undefined, false, undefined, this)
5171
+ children: [
5172
+ /* @__PURE__ */ u4("div", {
5173
+ className: "vertical-bar-fill",
5174
+ style: { height: `${percentage}%`, background: color }
5175
+ }, undefined, false, undefined, this),
5176
+ displayValue && /* @__PURE__ */ u4("span", {
5177
+ className: `vertical-bar-popover ${side}`,
5178
+ children: displayValue
5179
+ }, undefined, false, undefined, this)
5180
+ ]
5181
+ }, undefined, true, undefined, this)
5064
5182
  }, undefined, false, undefined, this);
5065
5183
  }
5066
5184
 
@@ -5070,7 +5188,6 @@ function LoadTapeDialog() {
5070
5188
  const fileInputRef = A2(null);
5071
5189
  const [isDragOver, setIsDragOver] = d2(false);
5072
5190
  const isOpen = debugState.isLoadDialogOpen.value;
5073
- const lastTapeName2 = debugState.lastTapeName.value;
5074
5191
  y2(() => {
5075
5192
  const dialog = dialogRef.current;
5076
5193
  if (!dialog)
@@ -5121,11 +5238,20 @@ function LoadTapeDialog() {
5121
5238
  const handleReplayLast = q2(() => {
5122
5239
  debugState.onReplayLastTape.value?.();
5123
5240
  }, []);
5241
+ const handleReplayLastSaved = q2(() => {
5242
+ debugState.onReplayLastSaved.value?.();
5243
+ }, []);
5244
+ const handleDialogClick = q2((e4) => {
5245
+ if (e4.target === e4.currentTarget) {
5246
+ debugState.isLoadDialogOpen.value = false;
5247
+ }
5248
+ }, []);
5124
5249
  return /* @__PURE__ */ u4("dialog", {
5125
5250
  ref: dialogRef,
5126
5251
  className: "load-tape-dialog",
5127
5252
  onClose: handleClose,
5128
- children: /* @__PURE__ */ u4("div", {
5253
+ onClick: handleDialogClick,
5254
+ children: isOpen && /* @__PURE__ */ u4("div", {
5129
5255
  className: "load-tape-dialog-content",
5130
5256
  children: [
5131
5257
  /* @__PURE__ */ u4("h3", {
@@ -5153,12 +5279,17 @@ function LoadTapeDialog() {
5153
5279
  className: "hidden-file-input",
5154
5280
  onChange: handleFileInputChange
5155
5281
  }, undefined, false, undefined, this),
5156
- lastTapeName2 && /* @__PURE__ */ u4("button", {
5282
+ debugState.lastSavedTapeName.value && /* @__PURE__ */ u4("button", {
5283
+ className: "replay-last-btn",
5284
+ onClick: handleReplayLastSaved,
5285
+ children: "Replay last saved tape"
5286
+ }, undefined, false, undefined, this),
5287
+ debugState.lastTapeName.value && /* @__PURE__ */ u4("button", {
5157
5288
  className: "replay-last-btn",
5158
5289
  onClick: handleReplayLast,
5159
5290
  children: [
5160
- "Replay last: ",
5161
- lastTapeName2
5291
+ "Replay last loaded: ",
5292
+ debugState.lastTapeName.value
5162
5293
  ]
5163
5294
  }, undefined, true, undefined, this)
5164
5295
  ]
@@ -5169,6 +5300,14 @@ function LoadTapeDialog() {
5169
5300
  // src/debugui/components/BottomBar.tsx
5170
5301
  var iconProps = { width: 14, height: 14, viewBox: "0 0 24 24", fill: "currentColor" };
5171
5302
  var Icons = {
5303
+ record: /* @__PURE__ */ u4("svg", {
5304
+ ...iconProps,
5305
+ children: /* @__PURE__ */ u4("circle", {
5306
+ cx: "12",
5307
+ cy: "12",
5308
+ r: "8"
5309
+ }, undefined, false, undefined, this)
5310
+ }, undefined, false, undefined, this),
5172
5311
  jumpBack: /* @__PURE__ */ u4("svg", {
5173
5312
  ...iconProps,
5174
5313
  children: /* @__PURE__ */ u4("path", {
@@ -5311,22 +5450,33 @@ function BottomBar() {
5311
5450
  const handleSaveTapeClick = q2(() => {
5312
5451
  debugState.onSaveTape.value?.();
5313
5452
  }, []);
5453
+ const handleToggleRecording = q2(() => {
5454
+ debugState.onToggleRecording.value?.();
5455
+ }, []);
5456
+ const stopPropagation = q2((e4) => {
5457
+ e4.stopPropagation();
5458
+ }, []);
5314
5459
  return /* @__PURE__ */ u4("div", {
5315
5460
  className: "bottom-bar",
5461
+ onMouseDown: stopPropagation,
5462
+ onMouseUp: stopPropagation,
5463
+ onClick: stopPropagation,
5316
5464
  children: [
5317
5465
  /* @__PURE__ */ u4("div", {
5318
5466
  className: "playbar-controls",
5319
5467
  children: [
5320
- isRecording2 && /* @__PURE__ */ u4("span", {
5321
- className: "recording-indicator",
5322
- title: "Recording",
5468
+ /* @__PURE__ */ u4("button", {
5469
+ className: `playbar-btn record-btn ${isRecording2 ? "recording" : ""}`,
5470
+ onClick: handleToggleRecording,
5323
5471
  children: [
5324
- /* @__PURE__ */ u4("span", {
5325
- className: "recording-dot"
5472
+ Icons.record,
5473
+ isRecording2 && /* @__PURE__ */ u4("span", {
5474
+ className: "btn-label",
5475
+ children: "REC"
5326
5476
  }, undefined, false, undefined, this),
5327
5477
  /* @__PURE__ */ u4("span", {
5328
- className: "recording-label",
5329
- children: "REC"
5478
+ className: "tooltip tooltip-left",
5479
+ children: isRecording2 ? "Stop recording" : "Start recording"
5330
5480
  }, undefined, false, undefined, this)
5331
5481
  ]
5332
5482
  }, undefined, true, undefined, this),
@@ -5537,11 +5687,13 @@ function LetterboxedLayout({ canvas }) {
5537
5687
  const hmrFlash2 = debugState.hmrFlash.value;
5538
5688
  const leftValue = isOnline ? Math.abs(advantage) : frameTime2;
5539
5689
  const leftMax = isOnline ? 10 : 16.67;
5540
- const leftLabel = isOnline ? "ADV" : "MS";
5690
+ const leftLabel = isOnline ? "adv" : "time";
5541
5691
  const leftColor = isOnline ? advantage >= 0 ? "#4a9eff" : "#ff4a4a" : frameTime2 > 16.67 ? "#ff4a4a" : "#4aff4a";
5692
+ const leftDisplayValue = isOnline ? `${advantage >= 0 ? "+" : ""}${advantage} frames` : `${frameTime2.toFixed(1)}ms`;
5542
5693
  const rightValue = isOnline ? 0 : snapshotSize2;
5543
5694
  const rightMax = isOnline ? 10 : 1e4;
5544
- const rightLabel = isOnline ? "RB" : "KB";
5695
+ const rightLabel = isOnline ? "rb" : "size";
5696
+ const rightDisplayValue = isOnline ? "0 frames" : snapshotSize2 >= 1000 ? `${(snapshotSize2 / 1000).toFixed(1)}kb` : `${snapshotSize2}b`;
5545
5697
  const gameClassName = hmrFlash2 ? "letterboxed-game hmr-flash" : "letterboxed-game";
5546
5698
  return /* @__PURE__ */ u4("main", {
5547
5699
  className: "layout-letterboxed",
@@ -5554,7 +5706,8 @@ function LetterboxedLayout({ canvas }) {
5554
5706
  value: leftValue,
5555
5707
  max: leftMax,
5556
5708
  side: "left",
5557
- color: leftColor
5709
+ color: leftColor,
5710
+ displayValue: leftDisplayValue
5558
5711
  }, undefined, false, undefined, this),
5559
5712
  /* @__PURE__ */ u4("div", {
5560
5713
  className: gameClassName,
@@ -5565,7 +5718,8 @@ function LetterboxedLayout({ canvas }) {
5565
5718
  /* @__PURE__ */ u4(VerticalBar, {
5566
5719
  value: rightValue,
5567
5720
  max: rightMax,
5568
- side: "right"
5721
+ side: "right",
5722
+ displayValue: rightDisplayValue
5569
5723
  }, undefined, false, undefined, this),
5570
5724
  /* @__PURE__ */ u4(BottomBar, {}, undefined, false, undefined, this)
5571
5725
  ]
@@ -5749,6 +5903,7 @@ var styles = `
5749
5903
  font-family: monospace;
5750
5904
  font-size: 12px;
5751
5905
  padding: 0;
5906
+ user-select: none;
5752
5907
  }
5753
5908
 
5754
5909
  .top-bar-side-label {
@@ -5802,6 +5957,7 @@ var styles = `
5802
5957
  justify-content: flex-end;
5803
5958
  background: #111;
5804
5959
  padding: 4px 0;
5960
+ user-select: none;
5805
5961
  }
5806
5962
 
5807
5963
  .right-bar {
@@ -5812,6 +5968,7 @@ var styles = `
5812
5968
  justify-content: flex-end;
5813
5969
  background: #111;
5814
5970
  padding: 4px 0;
5971
+ user-select: none;
5815
5972
  }
5816
5973
 
5817
5974
  .vertical-bar {
@@ -5820,7 +5977,6 @@ var styles = `
5820
5977
  background: #333;
5821
5978
  border-radius: 2px;
5822
5979
  position: relative;
5823
- overflow: hidden;
5824
5980
  }
5825
5981
 
5826
5982
  .vertical-bar-fill {
@@ -5833,6 +5989,37 @@ var styles = `
5833
5989
  transition: height 0.1s ease-out;
5834
5990
  }
5835
5991
 
5992
+ .vertical-bar-popover {
5993
+ position: absolute;
5994
+ top: 50%;
5995
+ transform: translateY(-50%);
5996
+ background: #222;
5997
+ color: #ccc;
5998
+ padding: 4px 8px;
5999
+ border-radius: 4px;
6000
+ font-size: 10px;
6001
+ font-family: monospace;
6002
+ white-space: nowrap;
6003
+ opacity: 0;
6004
+ visibility: hidden;
6005
+ transition: opacity 0.15s;
6006
+ pointer-events: none;
6007
+ z-index: 10;
6008
+ }
6009
+
6010
+ .vertical-bar-popover.left {
6011
+ left: calc(100% + 8px);
6012
+ }
6013
+
6014
+ .vertical-bar-popover.right {
6015
+ right: calc(100% + 8px);
6016
+ }
6017
+
6018
+ .vertical-bar:hover .vertical-bar-popover {
6019
+ opacity: 1;
6020
+ visibility: visible;
6021
+ }
6022
+
5836
6023
 
5837
6024
  .bottom-bar {
5838
6025
  grid-area: bottom-bar;
@@ -5842,6 +6029,7 @@ var styles = `
5842
6029
  /* Mobile-first: more padding */
5843
6030
  padding: 0 16px;
5844
6031
  gap: 12px;
6032
+ user-select: none;
5845
6033
  }
5846
6034
 
5847
6035
  /* Desktop: tighter padding */
@@ -5859,22 +6047,16 @@ var styles = `
5859
6047
  flex-shrink: 0;
5860
6048
  }
5861
6049
 
5862
- /* Recording indicator - mobile: just the dot */
5863
- .recording-indicator {
5864
- display: flex;
5865
- align-items: center;
5866
- margin-right: 4px;
6050
+ /* Record button */
6051
+ .record-btn {
6052
+ color: #666;
5867
6053
  }
5868
6054
 
5869
- .recording-indicator .recording-label {
5870
- display: none;
6055
+ .record-btn.recording {
6056
+ color: #ff4444;
5871
6057
  }
5872
6058
 
5873
- .recording-dot {
5874
- width: 10px;
5875
- height: 10px;
5876
- background: #ff4444;
5877
- border-radius: 50%;
6059
+ .record-btn.recording svg {
5878
6060
  animation: recording-pulse 1s ease-in-out infinite;
5879
6061
  }
5880
6062
 
@@ -5883,6 +6065,17 @@ var styles = `
5883
6065
  50% { opacity: 0.4; }
5884
6066
  }
5885
6067
 
6068
+ /* Desktop: show REC label when recording */
6069
+ @media (min-width: 769px) {
6070
+ .record-btn.recording {
6071
+ width: auto;
6072
+ padding: 0 6px;
6073
+ gap: 4px;
6074
+ background: rgba(255, 68, 68, 0.15);
6075
+ border-radius: 3px;
6076
+ }
6077
+ }
6078
+
5886
6079
  /* Replay indicator - mobile: hidden */
5887
6080
  .replay-indicator {
5888
6081
  display: none;
@@ -6044,6 +6237,7 @@ var styles = `
6044
6237
  position: relative;
6045
6238
  cursor: pointer;
6046
6239
  overflow: hidden;
6240
+ user-select: none;
6047
6241
  }
6048
6242
 
6049
6243
  /* Desktop: smaller seek bar */
@@ -7698,5 +7892,5 @@ export {
7698
7892
  App
7699
7893
  };
7700
7894
 
7701
- //# debugId=BE2C82666F521FFC64756E2164756E21
7895
+ //# debugId=6B601178E41D054664756E2164756E21
7702
7896
  //# sourceMappingURL=mod.js.map