@bloopjs/web 0.0.101 → 0.0.103

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
@@ -1450,7 +1450,7 @@ function readTapeHeader(tape) {
1450
1450
  eventCount: view.getUint16(14, true)
1451
1451
  };
1452
1452
  }
1453
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.101/wasm/bloop.wasm");
1453
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.103/wasm/bloop.wasm");
1454
1454
  var MAX_ROLLBACK_FRAMES = 500;
1455
1455
  var TIME_CTX_OFFSET = 0;
1456
1456
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
@@ -1556,6 +1556,9 @@ class Sim {
1556
1556
  throw new Error(`failed to start recording, error code=${result}`);
1557
1557
  }
1558
1558
  }
1559
+ stopRecording() {
1560
+ this.wasm.stop_recording();
1561
+ }
1559
1562
  saveTape() {
1560
1563
  const tapeLen = this.wasm.get_tape_len();
1561
1564
  const tapePtr = this.wasm.get_tape_ptr();
@@ -1621,21 +1624,33 @@ class Sim {
1621
1624
  }
1622
1625
  emit = {
1623
1626
  keydown: (key, peerId = 0) => {
1627
+ if (this.#isPaused)
1628
+ return;
1624
1629
  this.wasm.emit_keydown(keyToKeyCode(key), peerId);
1625
1630
  },
1626
1631
  keyup: (key, peerId = 0) => {
1632
+ if (this.#isPaused)
1633
+ return;
1627
1634
  this.wasm.emit_keyup(keyToKeyCode(key), peerId);
1628
1635
  },
1629
1636
  mousemove: (x, y, peerId = 0) => {
1637
+ if (this.#isPaused)
1638
+ return;
1630
1639
  this.wasm.emit_mousemove(x, y, peerId);
1631
1640
  },
1632
1641
  mousedown: (button, peerId = 0) => {
1642
+ if (this.#isPaused)
1643
+ return;
1633
1644
  this.wasm.emit_mousedown(mouseButtonToMouseButtonCode(button), peerId);
1634
1645
  },
1635
1646
  mouseup: (button, peerId = 0) => {
1647
+ if (this.#isPaused)
1648
+ return;
1636
1649
  this.wasm.emit_mouseup(mouseButtonToMouseButtonCode(button), peerId);
1637
1650
  },
1638
1651
  mousewheel: (x, y, peerId = 0) => {
1652
+ if (this.#isPaused)
1653
+ return;
1639
1654
  this.wasm.emit_mousewheel(x, y, peerId);
1640
1655
  },
1641
1656
  resize: (width, height, physicalWidth, physicalHeight, pixelRatio) => {
@@ -3309,7 +3324,7 @@ function readTapeHeader2(tape) {
3309
3324
  eventCount: view.getUint16(14, true)
3310
3325
  };
3311
3326
  }
3312
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.101/wasm/bloop.wasm");
3327
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.103/wasm/bloop.wasm");
3313
3328
  var TIME_CTX_OFFSET2 = 0;
3314
3329
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3315
3330
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
@@ -4432,9 +4447,12 @@ var onJumpForward = d3(null);
4432
4447
  var onSeek = d3(null);
4433
4448
  var onLoadTape = d3(null);
4434
4449
  var onReplayLastTape = d3(null);
4450
+ var onReplayLastSaved = d3(null);
4435
4451
  var onSaveTape = d3(null);
4436
4452
  var lastTapeName = d3(null);
4453
+ var lastSavedTapeName = d3(null);
4437
4454
  var isLoadDialogOpen = d3(false);
4455
+ var onToggleRecording = d3(null);
4438
4456
  var debugState = {
4439
4457
  layoutMode,
4440
4458
  isVisible: w3(() => layoutMode.value !== "off"),
@@ -4465,9 +4483,12 @@ var debugState = {
4465
4483
  onSeek,
4466
4484
  onLoadTape,
4467
4485
  onReplayLastTape,
4486
+ onReplayLastSaved,
4468
4487
  onSaveTape,
4469
4488
  lastTapeName,
4470
- isLoadDialogOpen
4489
+ lastSavedTapeName,
4490
+ isLoadDialogOpen,
4491
+ onToggleRecording
4471
4492
  };
4472
4493
  function cycleLayout() {
4473
4494
  const current = layoutMode.value;
@@ -4603,12 +4624,20 @@ function wirePlaybarHandlers(app) {
4603
4624
  };
4604
4625
  debugState.onSeek.value = (ratio) => {
4605
4626
  if (app.sim.hasHistory) {
4627
+ app.sim.pause();
4606
4628
  const startFrame = debugState.tapeStartFrame.value;
4607
4629
  const frameCount = debugState.tapeFrameCount.value;
4608
4630
  const targetFrame = startFrame + Math.floor(ratio * frameCount);
4609
4631
  app.sim.seek(targetFrame);
4610
4632
  }
4611
4633
  };
4634
+ debugState.onToggleRecording.value = () => {
4635
+ if (app.sim.isRecording) {
4636
+ app.sim.stopRecording();
4637
+ } else {
4638
+ app.sim.record();
4639
+ }
4640
+ };
4612
4641
  }
4613
4642
  function wireTapeDragDrop(canvas, app) {
4614
4643
  canvas.addEventListener("dragover", (e4) => {
@@ -4627,7 +4656,8 @@ function wireTapeDragDrop(canvas, app) {
4627
4656
  }
4628
4657
  var TAPE_DB_NAME = "bloop-debug";
4629
4658
  var TAPE_STORE_NAME = "tapes";
4630
- var TAPE_KEY = "last";
4659
+ var TAPE_KEY_LOADED = "last-loaded";
4660
+ var TAPE_KEY_SAVED = "last-saved";
4631
4661
  function openTapeDB() {
4632
4662
  return new Promise((resolve, reject) => {
4633
4663
  const request = indexedDB.open(TAPE_DB_NAME, 1);
@@ -4638,21 +4668,21 @@ function openTapeDB() {
4638
4668
  };
4639
4669
  });
4640
4670
  }
4641
- async function saveTapeToStorage(bytes, fileName) {
4671
+ async function saveTapeToStorage(bytes, fileName, key = TAPE_KEY_LOADED) {
4642
4672
  const db = await openTapeDB();
4643
4673
  return new Promise((resolve, reject) => {
4644
4674
  const tx = db.transaction(TAPE_STORE_NAME, "readwrite");
4645
- tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, TAPE_KEY);
4675
+ tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, key);
4646
4676
  tx.oncomplete = () => resolve();
4647
4677
  tx.onerror = () => reject(tx.error);
4648
4678
  });
4649
4679
  }
4650
- async function loadTapeFromStorage() {
4680
+ async function loadTapeFromStorage(key = TAPE_KEY_LOADED) {
4651
4681
  try {
4652
4682
  const db = await openTapeDB();
4653
4683
  return new Promise((resolve, reject) => {
4654
4684
  const tx = db.transaction(TAPE_STORE_NAME, "readonly");
4655
- const request = tx.objectStore(TAPE_STORE_NAME).get(TAPE_KEY);
4685
+ const request = tx.objectStore(TAPE_STORE_NAME).get(key);
4656
4686
  request.onsuccess = () => resolve(request.result ?? null);
4657
4687
  request.onerror = () => reject(request.error);
4658
4688
  });
@@ -4661,32 +4691,46 @@ async function loadTapeFromStorage() {
4661
4691
  }
4662
4692
  }
4663
4693
  async function checkForSavedTape() {
4664
- const saved = await loadTapeFromStorage();
4665
- debugState.lastTapeName.value = saved?.fileName ?? null;
4694
+ const [loaded, saved] = await Promise.all([
4695
+ loadTapeFromStorage(TAPE_KEY_LOADED),
4696
+ loadTapeFromStorage(TAPE_KEY_SAVED)
4697
+ ]);
4698
+ debugState.lastTapeName.value = loaded?.fileName ?? null;
4699
+ debugState.lastSavedTapeName.value = saved?.fileName ?? null;
4666
4700
  }
4667
4701
  function wireTapeLoadHandlers(app) {
4668
4702
  debugState.onLoadTape.value = async (bytes, fileName) => {
4669
4703
  app.loadTape(bytes);
4670
- await saveTapeToStorage(bytes, fileName);
4704
+ await saveTapeToStorage(bytes, fileName, TAPE_KEY_LOADED);
4671
4705
  debugState.lastTapeName.value = fileName;
4672
4706
  debugState.isLoadDialogOpen.value = false;
4673
4707
  };
4674
4708
  debugState.onReplayLastTape.value = async () => {
4675
- const saved = await loadTapeFromStorage();
4709
+ const saved = await loadTapeFromStorage(TAPE_KEY_LOADED);
4676
4710
  if (saved) {
4677
4711
  app.loadTape(saved.bytes);
4678
4712
  debugState.isLoadDialogOpen.value = false;
4679
4713
  }
4680
4714
  };
4681
- debugState.onSaveTape.value = () => {
4715
+ debugState.onReplayLastSaved.value = async () => {
4716
+ const saved = await loadTapeFromStorage(TAPE_KEY_SAVED);
4717
+ if (saved) {
4718
+ app.loadTape(saved.bytes);
4719
+ debugState.isLoadDialogOpen.value = false;
4720
+ }
4721
+ };
4722
+ debugState.onSaveTape.value = async () => {
4682
4723
  if (!app.sim.hasHistory)
4683
4724
  return;
4684
4725
  const tape = app.sim.saveTape();
4726
+ const fileName = `tape-${Date.now()}.bloop`;
4727
+ await saveTapeToStorage(tape, fileName, TAPE_KEY_SAVED);
4728
+ debugState.lastSavedTapeName.value = fileName;
4685
4729
  const blob = new Blob([tape], { type: "application/octet-stream" });
4686
4730
  const url = URL.createObjectURL(blob);
4687
4731
  const a4 = document.createElement("a");
4688
4732
  a4.href = url;
4689
- a4.download = `tape-${Date.now()}.bloop`;
4733
+ a4.download = fileName;
4690
4734
  a4.click();
4691
4735
  URL.revokeObjectURL(url);
4692
4736
  };
@@ -4970,8 +5014,14 @@ function TopBar({ leftLabel, rightLabel }) {
4970
5014
  const frameNumber2 = debugState.frameNumber.value;
4971
5015
  const rtt = debugState.netStatus.value.rtt;
4972
5016
  const isOnline = debugState.netStatus.value.peers.length > 0;
5017
+ const stopPropagation = q2((e4) => {
5018
+ e4.stopPropagation();
5019
+ }, []);
4973
5020
  return /* @__PURE__ */ u4("div", {
4974
5021
  className: "top-bar",
5022
+ onMouseDown: stopPropagation,
5023
+ onMouseUp: stopPropagation,
5024
+ onClick: stopPropagation,
4975
5025
  children: [
4976
5026
  /* @__PURE__ */ u4("span", {
4977
5027
  className: "top-bar-side-label",
@@ -5037,18 +5087,31 @@ function VerticalBar({
5037
5087
  value,
5038
5088
  max,
5039
5089
  side,
5040
- color = "#4a9eff"
5090
+ color = "#4a9eff",
5091
+ displayValue
5041
5092
  }) {
5042
5093
  const percentage = max > 0 ? Math.min(100, value / max * 100) : 0;
5094
+ const stopPropagation = q2((e4) => {
5095
+ e4.stopPropagation();
5096
+ }, []);
5043
5097
  return /* @__PURE__ */ u4("div", {
5044
5098
  className: `${side}-bar`,
5099
+ onMouseDown: stopPropagation,
5100
+ onMouseUp: stopPropagation,
5101
+ onClick: stopPropagation,
5045
5102
  children: /* @__PURE__ */ u4("div", {
5046
5103
  className: "vertical-bar",
5047
- children: /* @__PURE__ */ u4("div", {
5048
- className: "vertical-bar-fill",
5049
- style: { height: `${percentage}%`, background: color }
5050
- }, undefined, false, undefined, this)
5051
- }, undefined, false, undefined, this)
5104
+ children: [
5105
+ /* @__PURE__ */ u4("div", {
5106
+ className: "vertical-bar-fill",
5107
+ style: { height: `${percentage}%`, background: color }
5108
+ }, undefined, false, undefined, this),
5109
+ displayValue && /* @__PURE__ */ u4("span", {
5110
+ className: `vertical-bar-popover ${side}`,
5111
+ children: displayValue
5112
+ }, undefined, false, undefined, this)
5113
+ ]
5114
+ }, undefined, true, undefined, this)
5052
5115
  }, undefined, false, undefined, this);
5053
5116
  }
5054
5117
 
@@ -5058,7 +5121,6 @@ function LoadTapeDialog() {
5058
5121
  const fileInputRef = A2(null);
5059
5122
  const [isDragOver, setIsDragOver] = d2(false);
5060
5123
  const isOpen = debugState.isLoadDialogOpen.value;
5061
- const lastTapeName2 = debugState.lastTapeName.value;
5062
5124
  y2(() => {
5063
5125
  const dialog = dialogRef.current;
5064
5126
  if (!dialog)
@@ -5109,11 +5171,20 @@ function LoadTapeDialog() {
5109
5171
  const handleReplayLast = q2(() => {
5110
5172
  debugState.onReplayLastTape.value?.();
5111
5173
  }, []);
5174
+ const handleReplayLastSaved = q2(() => {
5175
+ debugState.onReplayLastSaved.value?.();
5176
+ }, []);
5177
+ const handleDialogClick = q2((e4) => {
5178
+ if (e4.target === e4.currentTarget) {
5179
+ debugState.isLoadDialogOpen.value = false;
5180
+ }
5181
+ }, []);
5112
5182
  return /* @__PURE__ */ u4("dialog", {
5113
5183
  ref: dialogRef,
5114
5184
  className: "load-tape-dialog",
5115
5185
  onClose: handleClose,
5116
- children: /* @__PURE__ */ u4("div", {
5186
+ onClick: handleDialogClick,
5187
+ children: isOpen && /* @__PURE__ */ u4("div", {
5117
5188
  className: "load-tape-dialog-content",
5118
5189
  children: [
5119
5190
  /* @__PURE__ */ u4("h3", {
@@ -5141,12 +5212,17 @@ function LoadTapeDialog() {
5141
5212
  className: "hidden-file-input",
5142
5213
  onChange: handleFileInputChange
5143
5214
  }, undefined, false, undefined, this),
5144
- lastTapeName2 && /* @__PURE__ */ u4("button", {
5215
+ debugState.lastSavedTapeName.value && /* @__PURE__ */ u4("button", {
5216
+ className: "replay-last-btn",
5217
+ onClick: handleReplayLastSaved,
5218
+ children: "Replay last saved tape"
5219
+ }, undefined, false, undefined, this),
5220
+ debugState.lastTapeName.value && /* @__PURE__ */ u4("button", {
5145
5221
  className: "replay-last-btn",
5146
5222
  onClick: handleReplayLast,
5147
5223
  children: [
5148
- "Replay last: ",
5149
- lastTapeName2
5224
+ "Replay last loaded: ",
5225
+ debugState.lastTapeName.value
5150
5226
  ]
5151
5227
  }, undefined, true, undefined, this)
5152
5228
  ]
@@ -5157,6 +5233,14 @@ function LoadTapeDialog() {
5157
5233
  // src/debugui/components/BottomBar.tsx
5158
5234
  var iconProps = { width: 14, height: 14, viewBox: "0 0 24 24", fill: "currentColor" };
5159
5235
  var Icons = {
5236
+ record: /* @__PURE__ */ u4("svg", {
5237
+ ...iconProps,
5238
+ children: /* @__PURE__ */ u4("circle", {
5239
+ cx: "12",
5240
+ cy: "12",
5241
+ r: "8"
5242
+ }, undefined, false, undefined, this)
5243
+ }, undefined, false, undefined, this),
5160
5244
  jumpBack: /* @__PURE__ */ u4("svg", {
5161
5245
  ...iconProps,
5162
5246
  children: /* @__PURE__ */ u4("path", {
@@ -5299,22 +5383,33 @@ function BottomBar() {
5299
5383
  const handleSaveTapeClick = q2(() => {
5300
5384
  debugState.onSaveTape.value?.();
5301
5385
  }, []);
5386
+ const handleToggleRecording = q2(() => {
5387
+ debugState.onToggleRecording.value?.();
5388
+ }, []);
5389
+ const stopPropagation = q2((e4) => {
5390
+ e4.stopPropagation();
5391
+ }, []);
5302
5392
  return /* @__PURE__ */ u4("div", {
5303
5393
  className: "bottom-bar",
5394
+ onMouseDown: stopPropagation,
5395
+ onMouseUp: stopPropagation,
5396
+ onClick: stopPropagation,
5304
5397
  children: [
5305
5398
  /* @__PURE__ */ u4("div", {
5306
5399
  className: "playbar-controls",
5307
5400
  children: [
5308
- isRecording2 && /* @__PURE__ */ u4("span", {
5309
- className: "recording-indicator",
5310
- title: "Recording",
5401
+ /* @__PURE__ */ u4("button", {
5402
+ className: `playbar-btn record-btn ${isRecording2 ? "recording" : ""}`,
5403
+ onClick: handleToggleRecording,
5311
5404
  children: [
5312
- /* @__PURE__ */ u4("span", {
5313
- className: "recording-dot"
5405
+ Icons.record,
5406
+ isRecording2 && /* @__PURE__ */ u4("span", {
5407
+ className: "btn-label",
5408
+ children: "REC"
5314
5409
  }, undefined, false, undefined, this),
5315
5410
  /* @__PURE__ */ u4("span", {
5316
- className: "recording-label",
5317
- children: "REC"
5411
+ className: "tooltip tooltip-left",
5412
+ children: isRecording2 ? "Stop recording" : "Start recording"
5318
5413
  }, undefined, false, undefined, this)
5319
5414
  ]
5320
5415
  }, undefined, true, undefined, this),
@@ -5525,11 +5620,13 @@ function LetterboxedLayout({ canvas }) {
5525
5620
  const hmrFlash2 = debugState.hmrFlash.value;
5526
5621
  const leftValue = isOnline ? Math.abs(advantage) : frameTime2;
5527
5622
  const leftMax = isOnline ? 10 : 16.67;
5528
- const leftLabel = isOnline ? "ADV" : "MS";
5623
+ const leftLabel = isOnline ? "adv" : "time";
5529
5624
  const leftColor = isOnline ? advantage >= 0 ? "#4a9eff" : "#ff4a4a" : frameTime2 > 16.67 ? "#ff4a4a" : "#4aff4a";
5625
+ const leftDisplayValue = isOnline ? `${advantage >= 0 ? "+" : ""}${advantage} frames` : `${frameTime2.toFixed(1)}ms`;
5530
5626
  const rightValue = isOnline ? 0 : snapshotSize2;
5531
5627
  const rightMax = isOnline ? 10 : 1e4;
5532
- const rightLabel = isOnline ? "RB" : "KB";
5628
+ const rightLabel = isOnline ? "rb" : "size";
5629
+ const rightDisplayValue = isOnline ? "0 frames" : snapshotSize2 >= 1000 ? `${(snapshotSize2 / 1000).toFixed(1)}kb` : `${snapshotSize2}b`;
5533
5630
  const gameClassName = hmrFlash2 ? "letterboxed-game hmr-flash" : "letterboxed-game";
5534
5631
  return /* @__PURE__ */ u4("main", {
5535
5632
  className: "layout-letterboxed",
@@ -5542,7 +5639,8 @@ function LetterboxedLayout({ canvas }) {
5542
5639
  value: leftValue,
5543
5640
  max: leftMax,
5544
5641
  side: "left",
5545
- color: leftColor
5642
+ color: leftColor,
5643
+ displayValue: leftDisplayValue
5546
5644
  }, undefined, false, undefined, this),
5547
5645
  /* @__PURE__ */ u4("div", {
5548
5646
  className: gameClassName,
@@ -5553,7 +5651,8 @@ function LetterboxedLayout({ canvas }) {
5553
5651
  /* @__PURE__ */ u4(VerticalBar, {
5554
5652
  value: rightValue,
5555
5653
  max: rightMax,
5556
- side: "right"
5654
+ side: "right",
5655
+ displayValue: rightDisplayValue
5557
5656
  }, undefined, false, undefined, this),
5558
5657
  /* @__PURE__ */ u4(BottomBar, {}, undefined, false, undefined, this)
5559
5658
  ]
@@ -5737,6 +5836,7 @@ var styles = `
5737
5836
  font-family: monospace;
5738
5837
  font-size: 12px;
5739
5838
  padding: 0;
5839
+ user-select: none;
5740
5840
  }
5741
5841
 
5742
5842
  .top-bar-side-label {
@@ -5790,6 +5890,7 @@ var styles = `
5790
5890
  justify-content: flex-end;
5791
5891
  background: #111;
5792
5892
  padding: 4px 0;
5893
+ user-select: none;
5793
5894
  }
5794
5895
 
5795
5896
  .right-bar {
@@ -5800,6 +5901,7 @@ var styles = `
5800
5901
  justify-content: flex-end;
5801
5902
  background: #111;
5802
5903
  padding: 4px 0;
5904
+ user-select: none;
5803
5905
  }
5804
5906
 
5805
5907
  .vertical-bar {
@@ -5808,7 +5910,6 @@ var styles = `
5808
5910
  background: #333;
5809
5911
  border-radius: 2px;
5810
5912
  position: relative;
5811
- overflow: hidden;
5812
5913
  }
5813
5914
 
5814
5915
  .vertical-bar-fill {
@@ -5821,6 +5922,37 @@ var styles = `
5821
5922
  transition: height 0.1s ease-out;
5822
5923
  }
5823
5924
 
5925
+ .vertical-bar-popover {
5926
+ position: absolute;
5927
+ top: 50%;
5928
+ transform: translateY(-50%);
5929
+ background: #222;
5930
+ color: #ccc;
5931
+ padding: 4px 8px;
5932
+ border-radius: 4px;
5933
+ font-size: 10px;
5934
+ font-family: monospace;
5935
+ white-space: nowrap;
5936
+ opacity: 0;
5937
+ visibility: hidden;
5938
+ transition: opacity 0.15s;
5939
+ pointer-events: none;
5940
+ z-index: 10;
5941
+ }
5942
+
5943
+ .vertical-bar-popover.left {
5944
+ left: calc(100% + 8px);
5945
+ }
5946
+
5947
+ .vertical-bar-popover.right {
5948
+ right: calc(100% + 8px);
5949
+ }
5950
+
5951
+ .vertical-bar:hover .vertical-bar-popover {
5952
+ opacity: 1;
5953
+ visibility: visible;
5954
+ }
5955
+
5824
5956
 
5825
5957
  .bottom-bar {
5826
5958
  grid-area: bottom-bar;
@@ -5830,6 +5962,7 @@ var styles = `
5830
5962
  /* Mobile-first: more padding */
5831
5963
  padding: 0 16px;
5832
5964
  gap: 12px;
5965
+ user-select: none;
5833
5966
  }
5834
5967
 
5835
5968
  /* Desktop: tighter padding */
@@ -5847,22 +5980,16 @@ var styles = `
5847
5980
  flex-shrink: 0;
5848
5981
  }
5849
5982
 
5850
- /* Recording indicator - mobile: just the dot */
5851
- .recording-indicator {
5852
- display: flex;
5853
- align-items: center;
5854
- margin-right: 4px;
5983
+ /* Record button */
5984
+ .record-btn {
5985
+ color: #666;
5855
5986
  }
5856
5987
 
5857
- .recording-indicator .recording-label {
5858
- display: none;
5988
+ .record-btn.recording {
5989
+ color: #ff4444;
5859
5990
  }
5860
5991
 
5861
- .recording-dot {
5862
- width: 10px;
5863
- height: 10px;
5864
- background: #ff4444;
5865
- border-radius: 50%;
5992
+ .record-btn.recording svg {
5866
5993
  animation: recording-pulse 1s ease-in-out infinite;
5867
5994
  }
5868
5995
 
@@ -5871,6 +5998,17 @@ var styles = `
5871
5998
  50% { opacity: 0.4; }
5872
5999
  }
5873
6000
 
6001
+ /* Desktop: show REC label when recording */
6002
+ @media (min-width: 769px) {
6003
+ .record-btn.recording {
6004
+ width: auto;
6005
+ padding: 0 6px;
6006
+ gap: 4px;
6007
+ background: rgba(255, 68, 68, 0.15);
6008
+ border-radius: 3px;
6009
+ }
6010
+ }
6011
+
5874
6012
  /* Replay indicator - mobile: hidden */
5875
6013
  .replay-indicator {
5876
6014
  display: none;
@@ -6032,6 +6170,7 @@ var styles = `
6032
6170
  position: relative;
6033
6171
  cursor: pointer;
6034
6172
  overflow: hidden;
6173
+ user-select: none;
6035
6174
  }
6036
6175
 
6037
6176
  /* Desktop: smaller seek bar */
@@ -7686,5 +7825,5 @@ export {
7686
7825
  App
7687
7826
  };
7688
7827
 
7689
- //# debugId=5705F0D8DF18CA1364756E2164756E21
7828
+ //# debugId=A1466E71356AC5F764756E2164756E21
7690
7829
  //# sourceMappingURL=mod.js.map