@bloopjs/web 0.0.90 → 0.0.92

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
@@ -1432,7 +1432,7 @@ function readTapeHeader(tape) {
1432
1432
  eventCount: view.getUint16(14, true)
1433
1433
  };
1434
1434
  }
1435
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.90/wasm/bloop.wasm");
1435
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.92/wasm/bloop.wasm");
1436
1436
  var MAX_ROLLBACK_FRAMES = 500;
1437
1437
  var TIME_CTX_OFFSET = 0;
1438
1438
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
@@ -3273,7 +3273,7 @@ function readTapeHeader2(tape) {
3273
3273
  eventCount: view.getUint16(14, true)
3274
3274
  };
3275
3275
  }
3276
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.90/wasm/bloop.wasm");
3276
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.92/wasm/bloop.wasm");
3277
3277
  var TIME_CTX_OFFSET2 = 0;
3278
3278
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3279
3279
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
@@ -3283,6 +3283,7 @@ var SCREEN_CTX_OFFSET2 = NET_CTX_OFFSET2 + 4;
3283
3283
  // src/debugui/mod.ts
3284
3284
  var exports_mod = {};
3285
3285
  __export(exports_mod, {
3286
+ wireTapeLoadHandlers: () => wireTapeLoadHandlers,
3286
3287
  wireTapeDragDrop: () => wireTapeDragDrop,
3287
3288
  wirePlaybarHandlers: () => wirePlaybarHandlers,
3288
3289
  updatePeer: () => updatePeer,
@@ -3294,6 +3295,7 @@ __export(exports_mod, {
3294
3295
  debugState: () => debugState,
3295
3296
  cycleLayout: () => cycleLayout,
3296
3297
  clearLogs: () => clearLogs,
3298
+ checkForSavedTape: () => checkForSavedTape,
3297
3299
  addPeer: () => addPeer,
3298
3300
  addLog: () => addLog,
3299
3301
  DebugUi: () => DebugUi
@@ -3637,6 +3639,45 @@ function p2(n2, t3) {
3637
3639
  var u3 = r2.__H || (r2.__H = { __: [], __h: [] });
3638
3640
  return n2 >= u3.__.length && u3.__.push({}), u3.__[n2];
3639
3641
  }
3642
+ function d2(n2) {
3643
+ return o2 = 1, h2(D2, n2);
3644
+ }
3645
+ function h2(n2, u3, i3) {
3646
+ var o3 = p2(t2++, 2);
3647
+ if (o3.t = n2, !o3.__c && (o3.__ = [i3 ? i3(u3) : D2(undefined, u3), function(n3) {
3648
+ var t3 = o3.__N ? o3.__N[0] : o3.__[0], r3 = o3.t(t3, n3);
3649
+ t3 !== r3 && (o3.__N = [r3, o3.__[1]], o3.__c.setState({}));
3650
+ }], o3.__c = r2, !r2.__f)) {
3651
+ var f3 = function(n3, t3, r3) {
3652
+ if (!o3.__c.__H)
3653
+ return true;
3654
+ var u4 = o3.__c.__H.__.filter(function(n4) {
3655
+ return !!n4.__c;
3656
+ });
3657
+ if (u4.every(function(n4) {
3658
+ return !n4.__N;
3659
+ }))
3660
+ return !c3 || c3.call(this, n3, t3, r3);
3661
+ var i4 = o3.__c.props !== n3;
3662
+ return u4.forEach(function(n4) {
3663
+ if (n4.__N) {
3664
+ var t4 = n4.__[0];
3665
+ n4.__ = n4.__N, n4.__N = undefined, t4 !== n4.__[0] && (i4 = true);
3666
+ }
3667
+ }), c3 && c3.call(this, n3, t3, r3) || i4;
3668
+ };
3669
+ r2.__f = true;
3670
+ var { shouldComponentUpdate: c3, componentWillUpdate: e3 } = r2;
3671
+ r2.componentWillUpdate = function(n3, t3, r3) {
3672
+ if (this.__e) {
3673
+ var u4 = c3;
3674
+ c3 = undefined, f3(n3, t3, r3), c3 = u4;
3675
+ }
3676
+ e3 && e3.call(this, n3, t3, r3);
3677
+ }, r2.shouldComponentUpdate = f3;
3678
+ }
3679
+ return o3.__N || o3.__;
3680
+ }
3640
3681
  function y2(n2, u3) {
3641
3682
  var i3 = p2(t2++, 3);
3642
3683
  !c2.__s && C2(i3.__H, u3) && (i3.__ = n2, i3.u = u3, r2.__H.__h.push(i3));
@@ -3723,15 +3764,18 @@ function C2(n2, t3) {
3723
3764
  return t4 !== n2[r3];
3724
3765
  });
3725
3766
  }
3767
+ function D2(n2, t3) {
3768
+ return typeof t3 == "function" ? t3(n2) : t3;
3769
+ }
3726
3770
 
3727
3771
  // ../../node_modules/@preact/signals-core/dist/signals-core.module.js
3728
3772
  var i3 = Symbol.for("preact-signals");
3729
3773
  function t3() {
3730
3774
  if (!(s3 > 1)) {
3731
3775
  var i4, t4 = false;
3732
- while (h2 !== undefined) {
3733
- var r3 = h2;
3734
- h2 = undefined;
3776
+ while (h3 !== undefined) {
3777
+ var r3 = h3;
3778
+ h3 = undefined;
3735
3779
  f3++;
3736
3780
  while (r3 !== undefined) {
3737
3781
  var o3 = r3.o;
@@ -3766,7 +3810,7 @@ function n2(i4) {
3766
3810
  o3 = t4;
3767
3811
  }
3768
3812
  }
3769
- var h2 = undefined;
3813
+ var h3 = undefined;
3770
3814
  var s3 = 0;
3771
3815
  var f3 = 0;
3772
3816
  var v3 = 0;
@@ -3897,7 +3941,7 @@ Object.defineProperty(u3.prototype, "value", { get: function() {
3897
3941
  }
3898
3942
  }
3899
3943
  } });
3900
- function d2(i4, t4) {
3944
+ function d3(i4, t4) {
3901
3945
  return new u3(i4, t4);
3902
3946
  }
3903
3947
  function c3(i4) {
@@ -4097,8 +4141,8 @@ p3.prototype.S = function() {
4097
4141
  p3.prototype.N = function() {
4098
4142
  if (!(2 & this.f)) {
4099
4143
  this.f |= 2;
4100
- this.o = h2;
4101
- h2 = this;
4144
+ this.o = h3;
4145
+ h3 = this;
4102
4146
  }
4103
4147
  };
4104
4148
  p3.prototype.d = function() {
@@ -4128,12 +4172,12 @@ var s4;
4128
4172
  function l4(i4, n3) {
4129
4173
  l[i4] = n3.bind(null, l[i4] || function() {});
4130
4174
  }
4131
- function d3(i4) {
4175
+ function d4(i4) {
4132
4176
  if (s4)
4133
4177
  s4();
4134
4178
  s4 = i4 && i4.S();
4135
4179
  }
4136
- function h3(i4) {
4180
+ function h4(i4) {
4137
4181
  var r4 = this, f4 = i4.data, o4 = useSignal(f4);
4138
4182
  o4.value = f4;
4139
4183
  var e4 = T2(function() {
@@ -4159,8 +4203,8 @@ function h3(i4) {
4159
4203
  }, []);
4160
4204
  return e4.value;
4161
4205
  }
4162
- h3.displayName = "_st";
4163
- Object.defineProperties(u3.prototype, { constructor: { configurable: true, value: undefined }, type: { configurable: true, value: h3 }, props: { configurable: true, get: function() {
4206
+ h4.displayName = "_st";
4207
+ Object.defineProperties(u3.prototype, { constructor: { configurable: true, value: undefined }, type: { configurable: true, value: h4 }, props: { configurable: true, get: function() {
4164
4208
  return { data: this };
4165
4209
  } }, __b: { configurable: true, value: 1 } });
4166
4210
  l4("__b", function(i4, r4) {
@@ -4180,7 +4224,7 @@ l4("__b", function(i4, r4) {
4180
4224
  i4(r4);
4181
4225
  });
4182
4226
  l4("__r", function(i4, r4) {
4183
- d3();
4227
+ d4();
4184
4228
  var n3, t4 = r4.__c;
4185
4229
  if (t4) {
4186
4230
  t4.__$f &= -2;
@@ -4198,16 +4242,16 @@ l4("__r", function(i4, r4) {
4198
4242
  }();
4199
4243
  }
4200
4244
  v4 = t4;
4201
- d3(n3);
4245
+ d4(n3);
4202
4246
  i4(r4);
4203
4247
  });
4204
4248
  l4("__e", function(i4, r4, n3, t4) {
4205
- d3();
4249
+ d4();
4206
4250
  v4 = undefined;
4207
4251
  i4(r4, n3, t4);
4208
4252
  });
4209
4253
  l4("diffed", function(i4, r4) {
4210
- d3();
4254
+ d4();
4211
4255
  v4 = undefined;
4212
4256
  var n3;
4213
4257
  if (typeof r4.type == "string" && (n3 = r4.__e)) {
@@ -4237,7 +4281,7 @@ l4("diffed", function(i4, r4) {
4237
4281
  i4(r4);
4238
4282
  });
4239
4283
  function p4(i4, r4, n3, t4) {
4240
- var f4 = r4 in i4 && i4.ownerSVGElement === undefined, o4 = d2(n3);
4284
+ var f4 = r4 in i4 && i4.ownerSVGElement === undefined, o4 = d3(n3);
4241
4285
  return { o: function(i5, r5) {
4242
4286
  o4.value = i5;
4243
4287
  t4 = r5;
@@ -4310,7 +4354,7 @@ x.prototype.shouldComponentUpdate = function(i4, r4) {
4310
4354
  };
4311
4355
  function useSignal(i4) {
4312
4356
  return T2(function() {
4313
- return d2(i4);
4357
+ return d3(i4);
4314
4358
  }, []);
4315
4359
  }
4316
4360
  function useSignalEffect(i4) {
@@ -4324,30 +4368,35 @@ function useSignalEffect(i4) {
4324
4368
  }
4325
4369
 
4326
4370
  // src/debugui/state.ts
4327
- var layoutMode = d2("off");
4328
- var netStatus = d2({
4371
+ var layoutMode = d3("off");
4372
+ var netStatus = d3({
4329
4373
  ourId: null,
4330
4374
  remoteId: null,
4331
4375
  rtt: null,
4332
4376
  peers: []
4333
4377
  });
4334
- var logs = d2([]);
4335
- var fps = d2(0);
4336
- var frameTime = d2(0);
4337
- var snapshotSize = d2(0);
4338
- var frameNumber = d2(0);
4339
- var hmrFlash = d2(false);
4340
- var isPlaying = d2(true);
4341
- var tapeUtilization = d2(0);
4342
- var playheadPosition = d2(0);
4343
- var tapeStartFrame = d2(0);
4344
- var tapeFrameCount = d2(0);
4345
- var onJumpBack = d2(null);
4346
- var onStepBack = d2(null);
4347
- var onPlayPause = d2(null);
4348
- var onStepForward = d2(null);
4349
- var onJumpForward = d2(null);
4350
- var onSeek = d2(null);
4378
+ var logs = d3([]);
4379
+ var fps = d3(0);
4380
+ var frameTime = d3(0);
4381
+ var snapshotSize = d3(0);
4382
+ var frameNumber = d3(0);
4383
+ var hmrFlash = d3(false);
4384
+ var isPlaying = d3(true);
4385
+ var tapeUtilization = d3(0);
4386
+ var playheadPosition = d3(0);
4387
+ var tapeStartFrame = d3(0);
4388
+ var tapeFrameCount = d3(0);
4389
+ var onJumpBack = d3(null);
4390
+ var onStepBack = d3(null);
4391
+ var onPlayPause = d3(null);
4392
+ var onStepForward = d3(null);
4393
+ var onJumpForward = d3(null);
4394
+ var onSeek = d3(null);
4395
+ var onLoadTape = d3(null);
4396
+ var onReplayLastTape = d3(null);
4397
+ var onSaveTape = d3(null);
4398
+ var lastTapeName = d3(null);
4399
+ var isLoadDialogOpen = d3(false);
4351
4400
  var debugState = {
4352
4401
  layoutMode,
4353
4402
  isVisible: w3(() => layoutMode.value !== "off"),
@@ -4373,7 +4422,12 @@ var debugState = {
4373
4422
  onPlayPause,
4374
4423
  onStepForward,
4375
4424
  onJumpForward,
4376
- onSeek
4425
+ onSeek,
4426
+ onLoadTape,
4427
+ onReplayLastTape,
4428
+ onSaveTape,
4429
+ lastTapeName,
4430
+ isLoadDialogOpen
4377
4431
  };
4378
4432
  function cycleLayout() {
4379
4433
  const current = layoutMode.value;
@@ -4529,6 +4583,73 @@ function wireTapeDragDrop(canvas, app) {
4529
4583
  app.loadTape(bytes);
4530
4584
  });
4531
4585
  }
4586
+ var TAPE_DB_NAME = "bloop-debug";
4587
+ var TAPE_STORE_NAME = "tapes";
4588
+ var TAPE_KEY = "last";
4589
+ function openTapeDB() {
4590
+ return new Promise((resolve, reject) => {
4591
+ const request = indexedDB.open(TAPE_DB_NAME, 1);
4592
+ request.onerror = () => reject(request.error);
4593
+ request.onsuccess = () => resolve(request.result);
4594
+ request.onupgradeneeded = () => {
4595
+ request.result.createObjectStore(TAPE_STORE_NAME);
4596
+ };
4597
+ });
4598
+ }
4599
+ async function saveTapeToStorage(bytes, fileName) {
4600
+ const db = await openTapeDB();
4601
+ return new Promise((resolve, reject) => {
4602
+ const tx = db.transaction(TAPE_STORE_NAME, "readwrite");
4603
+ tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, TAPE_KEY);
4604
+ tx.oncomplete = () => resolve();
4605
+ tx.onerror = () => reject(tx.error);
4606
+ });
4607
+ }
4608
+ async function loadTapeFromStorage() {
4609
+ try {
4610
+ const db = await openTapeDB();
4611
+ return new Promise((resolve, reject) => {
4612
+ const tx = db.transaction(TAPE_STORE_NAME, "readonly");
4613
+ const request = tx.objectStore(TAPE_STORE_NAME).get(TAPE_KEY);
4614
+ request.onsuccess = () => resolve(request.result ?? null);
4615
+ request.onerror = () => reject(request.error);
4616
+ });
4617
+ } catch {
4618
+ return null;
4619
+ }
4620
+ }
4621
+ async function checkForSavedTape() {
4622
+ const saved = await loadTapeFromStorage();
4623
+ debugState.lastTapeName.value = saved?.fileName ?? null;
4624
+ }
4625
+ function wireTapeLoadHandlers(app) {
4626
+ debugState.onLoadTape.value = async (bytes, fileName) => {
4627
+ app.loadTape(bytes);
4628
+ await saveTapeToStorage(bytes, fileName);
4629
+ debugState.lastTapeName.value = fileName;
4630
+ debugState.isLoadDialogOpen.value = false;
4631
+ };
4632
+ debugState.onReplayLastTape.value = async () => {
4633
+ const saved = await loadTapeFromStorage();
4634
+ if (saved) {
4635
+ app.loadTape(saved.bytes);
4636
+ debugState.isLoadDialogOpen.value = false;
4637
+ }
4638
+ };
4639
+ debugState.onSaveTape.value = () => {
4640
+ if (!app.sim.hasHistory)
4641
+ return;
4642
+ const tape = app.sim.saveTape();
4643
+ const blob = new Blob([tape], { type: "application/octet-stream" });
4644
+ const url = URL.createObjectURL(blob);
4645
+ const a4 = document.createElement("a");
4646
+ a4.href = url;
4647
+ a4.download = `tape-${Date.now()}.bloop`;
4648
+ a4.click();
4649
+ URL.revokeObjectURL(url);
4650
+ };
4651
+ checkForSavedTape();
4652
+ }
4532
4653
  // ../../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
4533
4654
  var f4 = 0;
4534
4655
  function u4(e4, t4, n3, o4, i4, u5) {
@@ -4889,6 +5010,108 @@ function VerticalBar({
4889
5010
  }, undefined, false, undefined, this);
4890
5011
  }
4891
5012
 
5013
+ // src/debugui/components/LoadTapeDialog.tsx
5014
+ function LoadTapeDialog() {
5015
+ const dialogRef = A2(null);
5016
+ const fileInputRef = A2(null);
5017
+ const [isDragOver, setIsDragOver] = d2(false);
5018
+ const isOpen = debugState.isLoadDialogOpen.value;
5019
+ const lastTapeName2 = debugState.lastTapeName.value;
5020
+ y2(() => {
5021
+ const dialog = dialogRef.current;
5022
+ if (!dialog)
5023
+ return;
5024
+ if (isOpen && !dialog.open) {
5025
+ dialog.showModal();
5026
+ } else if (!isOpen && dialog.open) {
5027
+ dialog.close();
5028
+ }
5029
+ }, [isOpen]);
5030
+ const handleClose = q2(() => {
5031
+ debugState.isLoadDialogOpen.value = false;
5032
+ }, []);
5033
+ const handleFileSelect = q2(async (file) => {
5034
+ if (!file.name.endsWith(".bloop"))
5035
+ return;
5036
+ const bytes = new Uint8Array(await file.arrayBuffer());
5037
+ debugState.onLoadTape.value?.(bytes, file.name);
5038
+ }, []);
5039
+ const handleDropZoneClick = q2(() => {
5040
+ fileInputRef.current?.click();
5041
+ }, []);
5042
+ const handleFileInputChange = q2((e4) => {
5043
+ const input = e4.currentTarget;
5044
+ const file = input.files?.[0];
5045
+ if (file) {
5046
+ handleFileSelect(file);
5047
+ input.value = "";
5048
+ }
5049
+ }, [handleFileSelect]);
5050
+ const handleDragOver = q2((e4) => {
5051
+ e4.preventDefault();
5052
+ if (e4.dataTransfer)
5053
+ e4.dataTransfer.dropEffect = "copy";
5054
+ setIsDragOver(true);
5055
+ }, []);
5056
+ const handleDragLeave = q2(() => {
5057
+ setIsDragOver(false);
5058
+ }, []);
5059
+ const handleDrop = q2((e4) => {
5060
+ e4.preventDefault();
5061
+ setIsDragOver(false);
5062
+ const file = e4.dataTransfer?.files[0];
5063
+ if (file) {
5064
+ handleFileSelect(file);
5065
+ }
5066
+ }, [handleFileSelect]);
5067
+ const handleReplayLast = q2(() => {
5068
+ debugState.onReplayLastTape.value?.();
5069
+ }, []);
5070
+ return /* @__PURE__ */ u4("dialog", {
5071
+ ref: dialogRef,
5072
+ className: "load-tape-dialog",
5073
+ onClose: handleClose,
5074
+ children: /* @__PURE__ */ u4("div", {
5075
+ className: "load-tape-dialog-content",
5076
+ children: [
5077
+ /* @__PURE__ */ u4("h3", {
5078
+ children: "Load Tape"
5079
+ }, undefined, false, undefined, this),
5080
+ /* @__PURE__ */ u4("div", {
5081
+ className: `drop-zone ${isDragOver ? "drag-over" : ""}`,
5082
+ onClick: handleDropZoneClick,
5083
+ onDragOver: handleDragOver,
5084
+ onDragLeave: handleDragLeave,
5085
+ onDrop: handleDrop,
5086
+ children: /* @__PURE__ */ u4("span", {
5087
+ className: "drop-zone-text",
5088
+ children: [
5089
+ "Drop .bloop file here",
5090
+ /* @__PURE__ */ u4("br", {}, undefined, false, undefined, this),
5091
+ "or click to browse"
5092
+ ]
5093
+ }, undefined, true, undefined, this)
5094
+ }, undefined, false, undefined, this),
5095
+ /* @__PURE__ */ u4("input", {
5096
+ ref: fileInputRef,
5097
+ type: "file",
5098
+ accept: ".bloop",
5099
+ className: "hidden-file-input",
5100
+ onChange: handleFileInputChange
5101
+ }, undefined, false, undefined, this),
5102
+ lastTapeName2 && /* @__PURE__ */ u4("button", {
5103
+ className: "replay-last-btn",
5104
+ onClick: handleReplayLast,
5105
+ children: [
5106
+ "Replay last: ",
5107
+ lastTapeName2
5108
+ ]
5109
+ }, undefined, true, undefined, this)
5110
+ ]
5111
+ }, undefined, true, undefined, this)
5112
+ }, undefined, false, undefined, this);
5113
+ }
5114
+
4892
5115
  // src/debugui/components/BottomBar.tsx
4893
5116
  function useRepeatOnHold(action) {
4894
5117
  const rafId = A2(null);
@@ -4975,6 +5198,12 @@ function BottomBar() {
4975
5198
  debugState.onSeek.value?.(ratio);
4976
5199
  }, []);
4977
5200
  const seekDrag = useSeekDrag(handleSeek);
5201
+ const handleLoadTapeClick = q2(() => {
5202
+ debugState.isLoadDialogOpen.value = true;
5203
+ }, []);
5204
+ const handleSaveTapeClick = q2(() => {
5205
+ debugState.onSaveTape.value?.();
5206
+ }, []);
4978
5207
  return /* @__PURE__ */ u4("div", {
4979
5208
  className: "bottom-bar",
4980
5209
  children: [
@@ -4982,7 +5211,7 @@ function BottomBar() {
4982
5211
  className: "playbar-controls",
4983
5212
  children: [
4984
5213
  /* @__PURE__ */ u4("button", {
4985
- className: "playbar-btn",
5214
+ className: "playbar-btn jump-back",
4986
5215
  ...jumpBackRepeat,
4987
5216
  children: [
4988
5217
  "<<",
@@ -4998,7 +5227,7 @@ function BottomBar() {
4998
5227
  ]
4999
5228
  }, undefined, true, undefined, this),
5000
5229
  /* @__PURE__ */ u4("button", {
5001
- className: "playbar-btn",
5230
+ className: "playbar-btn step-back",
5002
5231
  ...stepBackRepeat,
5003
5232
  children: [
5004
5233
  "<",
@@ -5014,7 +5243,7 @@ function BottomBar() {
5014
5243
  ]
5015
5244
  }, undefined, true, undefined, this),
5016
5245
  /* @__PURE__ */ u4("button", {
5017
- className: "playbar-btn",
5246
+ className: "playbar-btn play-pause",
5018
5247
  onClick: handlePlayPause,
5019
5248
  children: [
5020
5249
  isPlaying2 ? "||" : ">",
@@ -5031,7 +5260,7 @@ function BottomBar() {
5031
5260
  ]
5032
5261
  }, undefined, true, undefined, this),
5033
5262
  /* @__PURE__ */ u4("button", {
5034
- className: "playbar-btn",
5263
+ className: "playbar-btn step-forward",
5035
5264
  ...stepForwardRepeat,
5036
5265
  children: [
5037
5266
  ">",
@@ -5047,7 +5276,7 @@ function BottomBar() {
5047
5276
  ]
5048
5277
  }, undefined, true, undefined, this),
5049
5278
  /* @__PURE__ */ u4("button", {
5050
- className: "playbar-btn",
5279
+ className: "playbar-btn jump-forward",
5051
5280
  ...jumpForwardRepeat,
5052
5281
  children: [
5053
5282
  ">>",
@@ -5061,6 +5290,33 @@ function BottomBar() {
5061
5290
  ]
5062
5291
  }, undefined, true, undefined, this)
5063
5292
  ]
5293
+ }, undefined, true, undefined, this),
5294
+ /* @__PURE__ */ u4("button", {
5295
+ className: "playbar-btn save-tape-btn",
5296
+ onClick: handleSaveTapeClick,
5297
+ children: [
5298
+ "Save",
5299
+ /* @__PURE__ */ u4("span", {
5300
+ className: "tooltip",
5301
+ children: [
5302
+ "Save tape ",
5303
+ /* @__PURE__ */ u4("kbd", {
5304
+ children: "Cmd+S"
5305
+ }, undefined, false, undefined, this)
5306
+ ]
5307
+ }, undefined, true, undefined, this)
5308
+ ]
5309
+ }, undefined, true, undefined, this),
5310
+ /* @__PURE__ */ u4("button", {
5311
+ className: "playbar-btn load-tape-btn",
5312
+ onClick: handleLoadTapeClick,
5313
+ children: [
5314
+ "Load",
5315
+ /* @__PURE__ */ u4("span", {
5316
+ className: "tooltip",
5317
+ children: "Load tape"
5318
+ }, undefined, false, undefined, this)
5319
+ ]
5064
5320
  }, undefined, true, undefined, this)
5065
5321
  ]
5066
5322
  }, undefined, true, undefined, this),
@@ -5077,7 +5333,8 @@ function BottomBar() {
5077
5333
  style: { left: `${playheadPosition2 * tapeUtilization2 * 100}%` }
5078
5334
  }, undefined, false, undefined, this)
5079
5335
  ]
5080
- }, undefined, true, undefined, this)
5336
+ }, undefined, true, undefined, this),
5337
+ /* @__PURE__ */ u4(LoadTapeDialog, {}, undefined, false, undefined, this)
5081
5338
  ]
5082
5339
  }, undefined, true, undefined, this);
5083
5340
  }
@@ -5203,10 +5460,26 @@ var styles = `
5203
5460
  box-sizing: border-box;
5204
5461
  }
5205
5462
 
5463
+ /* Mobile-first CSS variables */
5464
+ :host {
5465
+ --bar-size: 10vw;
5466
+ --bar-size-h: 10vh;
5467
+ --bar-size-h: 10dvh;
5468
+ }
5469
+
5470
+ /* Desktop overrides */
5471
+ @media (min-width: 769px) {
5472
+ :host {
5473
+ --bar-size: 2vw;
5474
+ --bar-size-h: 2vh;
5475
+ }
5476
+ }
5477
+
5206
5478
  /* Layout */
5207
5479
  .fullscreen {
5208
5480
  width: 100vw;
5209
5481
  height: 100vh;
5482
+ height: 100dvh;
5210
5483
  margin: 0;
5211
5484
  padding: 0;
5212
5485
  overflow: hidden;
@@ -5223,17 +5496,96 @@ var styles = `
5223
5496
  display: block;
5224
5497
  }
5225
5498
 
5499
+ /* Mobile-first: vertical scroll layout */
5226
5500
  .layout {
5227
- display: grid;
5228
- grid-template-areas:
5229
- "game stats"
5230
- "logs logs";
5231
- grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);
5232
- grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);
5233
- gap: 1rem;
5501
+ /* Use fixed position on mobile to escape parent overflow:hidden */
5502
+ position: fixed;
5503
+ top: 0;
5504
+ left: 0;
5505
+ right: 0;
5506
+ bottom: 0;
5507
+ display: flex;
5508
+ flex-direction: column;
5509
+ overflow-y: auto;
5510
+ overflow-x: hidden;
5511
+ -webkit-overflow-scrolling: touch;
5512
+ overscroll-behavior-y: contain;
5513
+ padding: 0;
5514
+ gap: 0;
5515
+ background: #1a1a1a;
5516
+ }
5517
+
5518
+ .layout .game {
5519
+ /* Use dvh with vh fallback for mobile Safari address bar */
5520
+ height: 100vh;
5521
+ height: 100dvh;
5522
+ width: 100%;
5523
+ flex-shrink: 0;
5524
+ /* Mobile: no border radius, fullscreen game */
5525
+ border-radius: 0;
5526
+ }
5527
+
5528
+ /* Mobile: stretch canvas to fill game area */
5529
+ .layout .game .canvas-container {
5530
+ width: 100%;
5531
+ height: 100%;
5532
+ }
5533
+
5534
+ .layout .game .canvas-container canvas {
5234
5535
  width: 100%;
5235
5536
  height: 100%;
5537
+ max-width: none;
5538
+ max-height: none;
5539
+ display: block;
5540
+ }
5541
+
5542
+ .layout .stats,
5543
+ .layout .logs {
5544
+ width: 100%;
5545
+ min-height: 50vh;
5546
+ min-height: 50dvh;
5236
5547
  padding: 1rem;
5548
+ flex-shrink: 0;
5549
+ }
5550
+
5551
+ /* Desktop: 2x2 grid layout */
5552
+ @media (min-width: 769px) {
5553
+ .layout {
5554
+ position: static;
5555
+ display: grid;
5556
+ grid-template-areas:
5557
+ "game stats"
5558
+ "logs logs";
5559
+ grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);
5560
+ grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);
5561
+ gap: 1rem;
5562
+ padding: 1rem;
5563
+ height: 100%;
5564
+ overflow: hidden;
5565
+ -webkit-overflow-scrolling: auto;
5566
+ overscroll-behavior-y: auto;
5567
+ }
5568
+
5569
+ .layout .game {
5570
+ height: auto;
5571
+ flex-shrink: initial;
5572
+ border-radius: 8px;
5573
+ }
5574
+
5575
+ /* Desktop: restore centered canvas with constraints */
5576
+ .layout .game .canvas-container canvas {
5577
+ width: auto;
5578
+ height: auto;
5579
+ max-width: 100%;
5580
+ max-height: 100%;
5581
+ }
5582
+
5583
+ .layout .stats,
5584
+ .layout .logs {
5585
+ min-height: auto;
5586
+ padding: 1rem;
5587
+ flex-shrink: initial;
5588
+ }
5237
5589
  }
5238
5590
 
5239
5591
  /* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */
@@ -5243,10 +5595,12 @@ var styles = `
5243
5595
  "top-bar top-bar top-bar"
5244
5596
  "left-bar game right-bar"
5245
5597
  "bottom-bar bottom-bar bottom-bar";
5246
- grid-template-columns: 2vw 1fr 2vw;
5247
- grid-template-rows: 2vh 1fr 2vh;
5598
+ grid-template-columns: var(--bar-size) 1fr var(--bar-size);
5599
+ grid-template-rows: var(--bar-size-h) 1fr var(--bar-size-h);
5248
5600
  width: 100vw;
5601
+ /* Use dvh with vh fallback for mobile Safari address bar */
5249
5602
  height: 100vh;
5603
+ height: 100dvh;
5250
5604
  background: #1a1a1a;
5251
5605
  overflow: hidden;
5252
5606
  overscroll-behavior: none;
@@ -5265,7 +5619,7 @@ var styles = `
5265
5619
  }
5266
5620
 
5267
5621
  .top-bar-side-label {
5268
- width: 2vw;
5622
+ width: var(--bar-size);
5269
5623
  text-align: center;
5270
5624
  font-size: 9px;
5271
5625
  text-transform: uppercase;
@@ -5273,6 +5627,17 @@ var styles = `
5273
5627
  color: #666;
5274
5628
  }
5275
5629
 
5630
+ /* Mobile: larger top bar text */
5631
+ @media (max-width: 768px) {
5632
+ .top-bar-side-label {
5633
+ font-size: 12px;
5634
+ }
5635
+
5636
+ .top-bar {
5637
+ font-size: 14px;
5638
+ }
5639
+ }
5640
+
5276
5641
  .top-bar-center {
5277
5642
  display: flex;
5278
5643
  align-items: center;
@@ -5341,8 +5706,17 @@ var styles = `
5341
5706
  display: flex;
5342
5707
  align-items: center;
5343
5708
  background: #111;
5344
- padding: 0 8px;
5345
- gap: 8px;
5709
+ /* Mobile-first: more padding */
5710
+ padding: 0 16px;
5711
+ gap: 12px;
5712
+ }
5713
+
5714
+ /* Desktop: tighter padding */
5715
+ @media (min-width: 769px) {
5716
+ .bottom-bar {
5717
+ padding: 0 8px;
5718
+ gap: 8px;
5719
+ }
5346
5720
  }
5347
5721
 
5348
5722
  .playbar-controls {
@@ -5352,16 +5726,35 @@ var styles = `
5352
5726
  flex-shrink: 0;
5353
5727
  }
5354
5728
 
5729
+ /* Mobile-first: hide step/jump buttons */
5730
+ .playbar-btn.jump-back,
5731
+ .playbar-btn.step-back,
5732
+ .playbar-btn.step-forward,
5733
+ .playbar-btn.jump-forward {
5734
+ display: none;
5735
+ }
5736
+
5737
+ /* Desktop: show all controls */
5738
+ @media (min-width: 769px) {
5739
+ .playbar-btn.jump-back,
5740
+ .playbar-btn.step-back,
5741
+ .playbar-btn.step-forward,
5742
+ .playbar-btn.jump-forward {
5743
+ display: flex;
5744
+ }
5745
+ }
5746
+
5355
5747
  .playbar-btn {
5356
- width: 1.5vh;
5357
- height: 1.5vh;
5358
- min-width: 18px;
5359
- min-height: 18px;
5748
+ /* Mobile-first: larger buttons */
5749
+ width: 4vh;
5750
+ height: 4vh;
5751
+ min-width: 32px;
5752
+ min-height: 32px;
5360
5753
  border: none;
5361
5754
  outline: none;
5362
5755
  background: transparent;
5363
5756
  color: #888;
5364
- font-size: 10px;
5757
+ font-size: 16px;
5365
5758
  cursor: pointer;
5366
5759
  border-radius: 2px;
5367
5760
  display: flex;
@@ -5371,6 +5764,17 @@ var styles = `
5371
5764
  position: relative;
5372
5765
  }
5373
5766
 
5767
+ /* Desktop: smaller buttons */
5768
+ @media (min-width: 769px) {
5769
+ .playbar-btn {
5770
+ width: 1.5vh;
5771
+ height: 1.5vh;
5772
+ min-width: 18px;
5773
+ min-height: 18px;
5774
+ font-size: 10px;
5775
+ }
5776
+ }
5777
+
5374
5778
  .playbar-btn:hover {
5375
5779
  background: #333;
5376
5780
  color: #fff;
@@ -5414,7 +5818,8 @@ var styles = `
5414
5818
 
5415
5819
  .seek-bar {
5416
5820
  flex: 1;
5417
- height: 16px;
5821
+ /* Mobile-first: larger seek bar */
5822
+ height: 32px;
5418
5823
  background: #222;
5419
5824
  border-radius: 4px;
5420
5825
  position: relative;
@@ -5422,6 +5827,13 @@ var styles = `
5422
5827
  overflow: hidden;
5423
5828
  }
5424
5829
 
5830
+ /* Desktop: smaller seek bar */
5831
+ @media (min-width: 769px) {
5832
+ .seek-bar {
5833
+ height: 16px;
5834
+ }
5835
+ }
5836
+
5425
5837
  .seek-bar-fill {
5426
5838
  position: absolute;
5427
5839
  top: 0;
@@ -5644,6 +6056,89 @@ var styles = `
5644
6056
  border-radius: 4px;
5645
6057
  border: 1px inset lavender;
5646
6058
  }
6059
+
6060
+ /* Load Tape Dialog */
6061
+ .load-tape-dialog {
6062
+ background: #1a1a1a;
6063
+ border: 1px solid #333;
6064
+ border-radius: 8px;
6065
+ padding: 0;
6066
+ color: #ccc;
6067
+ font-family: monospace;
6068
+ max-width: 320px;
6069
+ width: 90vw;
6070
+ }
6071
+
6072
+ .load-tape-dialog::backdrop {
6073
+ background: rgba(0, 0, 0, 0.7);
6074
+ }
6075
+
6076
+ .load-tape-dialog-content {
6077
+ padding: 16px;
6078
+ }
6079
+
6080
+ .load-tape-dialog h3 {
6081
+ margin: 0 0 16px 0;
6082
+ font-size: 14px;
6083
+ font-weight: 600;
6084
+ color: #fff;
6085
+ }
6086
+
6087
+ .drop-zone {
6088
+ border: 2px dashed #444;
6089
+ border-radius: 8px;
6090
+ padding: 32px 16px;
6091
+ text-align: center;
6092
+ cursor: pointer;
6093
+ transition: border-color 0.15s, background 0.15s;
6094
+ }
6095
+
6096
+ .drop-zone:hover {
6097
+ border-color: #666;
6098
+ background: #222;
6099
+ }
6100
+
6101
+ .drop-zone.drag-over {
6102
+ border-color: #7b3fa0;
6103
+ background: rgba(123, 63, 160, 0.1);
6104
+ }
6105
+
6106
+ .drop-zone-text {
6107
+ color: #888;
6108
+ font-size: 12px;
6109
+ line-height: 1.5;
6110
+ }
6111
+
6112
+ .hidden-file-input {
6113
+ display: none;
6114
+ }
6115
+
6116
+ .replay-last-btn {
6117
+ width: 100%;
6118
+ margin-top: 12px;
6119
+ padding: 8px 12px;
6120
+ background: #333;
6121
+ border: none;
6122
+ border-radius: 4px;
6123
+ color: #ccc;
6124
+ font-family: monospace;
6125
+ font-size: 12px;
6126
+ cursor: pointer;
6127
+ transition: background 0.15s;
6128
+ text-align: left;
6129
+ overflow: hidden;
6130
+ text-overflow: ellipsis;
6131
+ white-space: nowrap;
6132
+ }
6133
+
6134
+ .replay-last-btn:hover {
6135
+ background: #444;
6136
+ color: #fff;
6137
+ }
6138
+
6139
+ .load-tape-btn {
6140
+ margin-left: 4px;
6141
+ }
5647
6142
  `;
5648
6143
 
5649
6144
  // src/debugui/DebugUi.ts
@@ -6671,6 +7166,7 @@ class App {
6671
7166
  this.#debugUi = new DebugUi(opts);
6672
7167
  wirePlaybarHandlers(this);
6673
7168
  wireTapeDragDrop(this.#debugUi.canvas, this);
7169
+ wireTapeLoadHandlers(this);
6674
7170
  return this.#debugUi;
6675
7171
  }
6676
7172
  get debugUi() {
@@ -6954,5 +7450,5 @@ export {
6954
7450
  App
6955
7451
  };
6956
7452
 
6957
- //# debugId=669ECA7E8ACBF62064756E2164756E21
7453
+ //# debugId=B02D4DB9DDB2D5FC64756E2164756E21
6958
7454
  //# sourceMappingURL=mod.js.map