@bloopjs/web 0.0.102 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"BottomBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/BottomBar.tsx"],"names":[],"mappings":"AA+HA,wBAAgB,SAAS,4CAsHxB"}
1
+ {"version":3,"file":"BottomBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/BottomBar.tsx"],"names":[],"mappings":"AAoIA,wBAAgB,SAAS,4CAuIxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"LoadTapeDialog.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/LoadTapeDialog.tsx"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,4CA4G7B"}
1
+ {"version":3,"file":"LoadTapeDialog.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/LoadTapeDialog.tsx"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,4CAsI7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"TopBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/TopBar.tsx"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,WAAW,2CA4B5D"}
1
+ {"version":3,"file":"TopBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/TopBar.tsx"],"names":[],"mappings":"AAGA,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,WAAW,2CAqC5D"}
@@ -3,7 +3,8 @@ type VerticalBarProps = {
3
3
  max: number;
4
4
  side: "left" | "right";
5
5
  color?: string;
6
+ displayValue?: string;
6
7
  };
7
- export declare function VerticalBar({ value, max, side, color, }: VerticalBarProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function VerticalBar({ value, max, side, color, displayValue, }: VerticalBarProps): import("react/jsx-runtime").JSX.Element;
8
9
  export {};
9
10
  //# sourceMappingURL=VerticalBar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VerticalBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/VerticalBar.tsx"],"names":[],"mappings":"AAAA,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,GAAG,EACH,IAAI,EACJ,KAAiB,GAClB,EAAE,gBAAgB,2CAalB"}
1
+ {"version":3,"file":"VerticalBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/VerticalBar.tsx"],"names":[],"mappings":"AAEA,KAAK,gBAAgB,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,GAAG,EACH,IAAI,EACJ,KAAiB,EACjB,YAAY,GACb,EAAE,gBAAgB,2CAyBlB"}
@@ -43,9 +43,12 @@ export type DebugState = {
43
43
  onSeek: Signal<((position: number) => void) | null>;
44
44
  onLoadTape: Signal<((bytes: Uint8Array, fileName: string) => void) | null>;
45
45
  onReplayLastTape: Signal<(() => void) | null>;
46
+ onReplayLastSaved: Signal<(() => void) | null>;
46
47
  onSaveTape: Signal<(() => void) | null>;
47
48
  lastTapeName: Signal<string | null>;
49
+ lastSavedTapeName: Signal<string | null>;
48
50
  isLoadDialogOpen: Signal<boolean>;
51
+ onToggleRecording: Signal<(() => void) | null>;
49
52
  };
50
53
  export declare const debugState: DebugState;
51
54
  /** Cycle through layout modes: off -> letterboxed -> full -> off */
@@ -64,7 +67,7 @@ export declare function resetState(): void;
64
67
  export declare function wirePlaybarHandlers(app: App): void;
65
68
  /** Set up drag-and-drop tape loading on a canvas element */
66
69
  export declare function wireTapeDragDrop(canvas: HTMLCanvasElement, app: App): void;
67
- /** Check for saved tape and update lastTapeName signal */
70
+ /** Check for saved tapes and update signals */
68
71
  export declare function checkForSavedTape(): Promise<void>;
69
72
  /** Wire up tape loading handlers */
70
73
  export declare function wireTapeLoadHandlers(app: App): void;
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/debugui/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/debugui/state.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const styles = "\n/* Reset for shadow DOM */\n* {\n box-sizing: border-box;\n}\n\n/* Mobile-first CSS variables */\n:host {\n --bar-size: 10vw;\n --bar-size-h: 10vh;\n --bar-size-h: 10dvh;\n}\n\n/* Desktop overrides */\n@media (min-width: 769px) {\n :host {\n --bar-size: 2vw;\n --bar-size-h: 2vh;\n }\n}\n\n/* Layout */\n.fullscreen {\n width: 100vw;\n height: 100vh;\n height: 100dvh;\n margin: 0;\n padding: 0;\n overflow: hidden;\n}\n\n.fullscreen .canvas-container {\n width: 100%;\n height: 100%;\n}\n\n.fullscreen canvas {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* Mobile-first: vertical scroll layout */\n.layout {\n /* Use fixed position on mobile to escape parent overflow:hidden */\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior-y: contain;\n padding: 0;\n gap: 0;\n background: #1a1a1a;\n}\n\n.layout .game {\n /* Use dvh with vh fallback for mobile Safari address bar */\n height: 100vh;\n height: 100dvh;\n width: 100%;\n flex-shrink: 0;\n /* Mobile: no border radius, fullscreen game */\n border-radius: 0;\n}\n\n/* Mobile: stretch canvas to fill game area */\n.layout .game .canvas-container {\n width: 100%;\n height: 100%;\n}\n\n.layout .game .canvas-container canvas {\n width: 100%;\n height: 100%;\n max-width: none;\n max-height: none;\n display: block;\n}\n\n.layout .stats,\n.layout .logs {\n width: 100%;\n min-height: 50vh;\n min-height: 50dvh;\n padding: 1rem;\n flex-shrink: 0;\n}\n\n/* Desktop: 2x2 grid layout */\n@media (min-width: 769px) {\n .layout {\n position: static;\n display: grid;\n grid-template-areas:\n \"game stats\"\n \"logs logs\";\n grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);\n grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);\n gap: 1rem;\n padding: 1rem;\n height: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: auto;\n overscroll-behavior-y: auto;\n }\n\n .layout .game {\n height: auto;\n flex-shrink: initial;\n border-radius: 8px;\n }\n\n /* Desktop: restore centered canvas with constraints */\n .layout .game .canvas-container canvas {\n width: auto;\n height: auto;\n max-width: 100%;\n max-height: 100%;\n }\n\n .layout .stats,\n .layout .logs {\n min-height: auto;\n padding: 1rem;\n flex-shrink: initial;\n }\n}\n\n/* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */\n.layout-letterboxed {\n display: grid;\n grid-template-areas:\n \"top-bar top-bar top-bar\"\n \"left-bar game right-bar\"\n \"bottom-bar bottom-bar bottom-bar\";\n grid-template-columns: var(--bar-size) 1fr var(--bar-size);\n grid-template-rows: var(--bar-size-h) 1fr var(--bar-size-h);\n width: 100vw;\n /* Use dvh with vh fallback for mobile Safari address bar */\n height: 100vh;\n height: 100dvh;\n background: #1a1a1a;\n overflow: hidden;\n overscroll-behavior: none;\n}\n\n.top-bar {\n grid-area: top-bar;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: #111;\n color: #aaa;\n font-family: monospace;\n font-size: 12px;\n padding: 0;\n}\n\n.top-bar-side-label {\n width: var(--bar-size);\n text-align: center;\n font-size: 9px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: #666;\n}\n\n/* Mobile: larger top bar text */\n@media (max-width: 768px) {\n .top-bar-side-label {\n font-size: 12px;\n }\n\n .top-bar {\n font-size: 14px;\n }\n}\n\n.top-bar-center {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 24px;\n flex: 1;\n}\n\n.top-bar-item {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.top-bar-label {\n opacity: 0.6;\n}\n\n.top-bar-value {\n color: #fff;\n font-weight: 500;\n}\n\n.left-bar {\n grid-area: left-bar;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n background: #111;\n padding: 4px 0;\n}\n\n.right-bar {\n grid-area: right-bar;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n background: #111;\n padding: 4px 0;\n}\n\n.vertical-bar {\n width: 12px;\n flex: 1;\n background: #333;\n border-radius: 2px;\n position: relative;\n overflow: hidden;\n}\n\n.vertical-bar-fill {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n background: #4a9eff;\n border-radius: 2px;\n transition: height 0.1s ease-out;\n}\n\n\n.bottom-bar {\n grid-area: bottom-bar;\n display: flex;\n align-items: center;\n background: #111;\n /* Mobile-first: more padding */\n padding: 0 16px;\n gap: 12px;\n}\n\n/* Desktop: tighter padding */\n@media (min-width: 769px) {\n .bottom-bar {\n padding: 0 8px;\n gap: 8px;\n }\n}\n\n.playbar-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n}\n\n/* Recording indicator - mobile: just the dot */\n.recording-indicator {\n display: flex;\n align-items: center;\n margin-right: 4px;\n}\n\n.recording-indicator .recording-label {\n display: none;\n}\n\n.recording-dot {\n width: 10px;\n height: 10px;\n background: #ff4444;\n border-radius: 50%;\n animation: recording-pulse 1s ease-in-out infinite;\n}\n\n@keyframes recording-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n/* Replay indicator - mobile: hidden */\n.replay-indicator {\n display: none;\n}\n\n/* Desktop: full indicators with text */\n@media (min-width: 769px) {\n .recording-indicator {\n gap: 4px;\n padding: 2px 6px;\n background: rgba(255, 0, 0, 0.2);\n border: 1px solid #ff4444;\n border-radius: 3px;\n }\n\n .recording-indicator .recording-label {\n display: inline;\n color: #ff4444;\n font-size: 10px;\n font-weight: bold;\n font-family: monospace;\n }\n\n .recording-dot {\n width: 8px;\n height: 8px;\n }\n\n .replay-indicator {\n display: flex;\n align-items: center;\n padding: 2px 6px;\n background: rgba(100, 100, 255, 0.2);\n border: 1px solid #6666ff;\n border-radius: 3px;\n color: #6666ff;\n font-size: 10px;\n font-weight: bold;\n font-family: monospace;\n margin-right: 4px;\n }\n}\n\n/* Mobile-first: hide step/jump buttons */\n.playbar-btn.jump-back,\n.playbar-btn.step-back,\n.playbar-btn.step-forward,\n.playbar-btn.jump-forward {\n display: none;\n}\n\n/* Desktop: show all controls */\n@media (min-width: 769px) {\n .playbar-btn.jump-back,\n .playbar-btn.step-back,\n .playbar-btn.step-forward,\n .playbar-btn.jump-forward {\n display: flex;\n }\n}\n\n.playbar-btn {\n /* Mobile-first: larger buttons */\n width: 4vh;\n height: 4vh;\n min-width: 32px;\n min-height: 32px;\n border: none;\n outline: none;\n background: transparent;\n color: #888;\n font-size: 16px;\n cursor: pointer;\n border-radius: 2px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, color 0.15s;\n position: relative;\n}\n\n/* Desktop: sized to match indicators */\n@media (min-width: 769px) {\n .playbar-btn {\n width: 24px;\n height: 24px;\n min-width: 24px;\n min-height: 24px;\n }\n\n /* Save/Load buttons need room for icon + text */\n .playbar-btn.save-tape-btn,\n .playbar-btn.load-tape-btn {\n width: auto;\n padding: 0 6px;\n gap: 4px;\n }\n}\n\n/* Mobile-first: hide button text labels, show only icons */\n.btn-label {\n display: none;\n}\n\n/* Desktop: show button text labels */\n@media (min-width: 769px) {\n .btn-label {\n display: inline;\n }\n}\n\n.playbar-btn:hover {\n background: #333;\n color: #fff;\n}\n\n.playbar-btn:hover .tooltip {\n opacity: 1;\n visibility: visible;\n}\n\n.tooltip {\n position: absolute;\n bottom: calc(100% + 4px);\n left: 50%;\n transform: translateX(-50%);\n background: #222;\n color: #ccc;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 10px;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.15s;\n pointer-events: none;\n z-index: 10;\n}\n\n.tooltip-left {\n left: 0;\n transform: none;\n}\n\n.tooltip kbd {\n background: #444;\n padding: 1px 4px;\n border-radius: 2px;\n margin-left: 4px;\n font-family: monospace;\n}\n\n.seek-bar {\n flex: 1;\n /* Mobile-first: larger seek bar */\n height: 32px;\n background: #222;\n border-radius: 4px;\n position: relative;\n cursor: pointer;\n overflow: hidden;\n}\n\n/* Desktop: smaller seek bar */\n@media (min-width: 769px) {\n .seek-bar {\n height: 16px;\n }\n}\n\n.seek-bar-fill {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n background: linear-gradient(to right, #4a2070, #7b3fa0);\n border-radius: 4px;\n transition: width 0.1s ease-out;\n}\n\n.seek-bar-position {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: #fff;\n}\n\n.letterboxed-game {\n grid-area: game;\n overflow: hidden;\n}\n\n.letterboxed-game .canvas-container {\n width: 100%;\n height: 100%;\n}\n\n.letterboxed-game canvas {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.letterboxed-game {\n position: relative;\n}\n\n.letterboxed-game.hmr-flash::after {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n animation: hmr-pulse 0.3s ease-out forwards;\n}\n\n@keyframes hmr-pulse {\n 0% { box-shadow: inset 0 0 0 36px #7b3fa0; }\n 100% { box-shadow: inset 0 0 0 0 #7b3fa0; }\n}\n\n.game {\n grid-area: game;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.stats {\n grid-area: stats;\n background-color: #f0f0f0;\n padding: 1rem;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.logs {\n grid-area: logs;\n background-color: #f0f0f0;\n padding: 1rem;\n border-radius: 8px;\n overflow: hidden;\n}\n\n/* Canvas container */\n.canvas-container {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.canvas-container canvas {\n max-width: 100%;\n max-height: 100%;\n}\n\n/* Debug toggle button */\n.debug-toggle {\n position: fixed;\n bottom: 16px;\n right: 16px;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background-color: rgba(0, 0, 0, 0.5);\n color: white;\n font-size: 18px;\n cursor: pointer;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.2s;\n}\n\n.debug-toggle:hover {\n background-color: rgba(0, 0, 0, 0.7);\n}\n\n/* Stats panel */\n.stats-panel {\n background: rgba(0, 0, 0, 0.7);\n color: white;\n padding: 12px;\n border-radius: 8px;\n font-family: monospace;\n font-size: 14px;\n max-width: 100%;\n overflow: hidden;\n}\n\n.stats-panel h3 {\n margin: 0 0 8px 0;\n font-size: 14px;\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.stats-panel table {\n width: 100%;\n border-collapse: collapse;\n table-layout: fixed;\n}\n\n.stats-panel tr {\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n.stats-panel tr:last-child {\n border-bottom: none;\n}\n\n.stats-panel td {\n padding: 4px 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.stats-panel td:first-child {\n opacity: 0.7;\n width: 60%;\n}\n\n.stats-panel td:last-child {\n text-align: right;\n font-weight: 600;\n width: 40%;\n}\n\n.stats-panel p {\n margin: 0;\n opacity: 0.7;\n}\n\n/* Logs panel */\n.logs-list {\n width: 100%;\n height: 100%;\n overflow: auto;\n margin: 0;\n padding: 0;\n}\n\n.logs-list li {\n margin: 0 0 24px 0;\n list-style: none;\n}\n\n.logs-list h3 {\n font-size: 16px;\n font-weight: 500;\n margin: 0;\n}\n\n.logs-list .ws {\n color: darkolivegreen;\n}\n\n.logs-list .webrtc {\n color: darkmagenta;\n}\n\n.logs-list .rollback {\n color: darkblue;\n}\n\n.logs-list .local {\n color: #333;\n}\n\n.logs-list .content {\n font-size: 16px;\n}\n\n.logs-list p {\n margin: 4px 0;\n}\n\n.logs-list pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n background-color: oldlace;\n padding: 8px;\n border-radius: 4px;\n border: 1px inset lavender;\n}\n\n/* Load Tape Dialog */\n.load-tape-dialog {\n background: #1a1a1a;\n border: 1px solid #333;\n border-radius: 8px;\n padding: 0;\n color: #ccc;\n font-family: monospace;\n max-width: 320px;\n width: 90vw;\n}\n\n.load-tape-dialog::backdrop {\n background: rgba(0, 0, 0, 0.7);\n}\n\n.load-tape-dialog-content {\n padding: 16px;\n}\n\n.load-tape-dialog h3 {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: #fff;\n}\n\n.drop-zone {\n border: 2px dashed #444;\n border-radius: 8px;\n padding: 32px 16px;\n text-align: center;\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n}\n\n.drop-zone:hover {\n border-color: #666;\n background: #222;\n}\n\n.drop-zone.drag-over {\n border-color: #7b3fa0;\n background: rgba(123, 63, 160, 0.1);\n}\n\n.drop-zone-text {\n color: #888;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hidden-file-input {\n display: none;\n}\n\n.replay-last-btn {\n width: 100%;\n margin-top: 12px;\n padding: 8px 12px;\n background: #333;\n border: none;\n border-radius: 4px;\n color: #ccc;\n font-family: monospace;\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.replay-last-btn:hover {\n background: #444;\n color: #fff;\n}\n\n.load-tape-btn {\n margin-left: 4px;\n}\n";
1
+ export declare const styles = "\n/* Reset for shadow DOM */\n* {\n box-sizing: border-box;\n}\n\n/* Mobile-first CSS variables */\n:host {\n --bar-size: 10vw;\n --bar-size-h: 10vh;\n --bar-size-h: 10dvh;\n}\n\n/* Desktop overrides */\n@media (min-width: 769px) {\n :host {\n --bar-size: 2vw;\n --bar-size-h: 2vh;\n }\n}\n\n/* Layout */\n.fullscreen {\n width: 100vw;\n height: 100vh;\n height: 100dvh;\n margin: 0;\n padding: 0;\n overflow: hidden;\n}\n\n.fullscreen .canvas-container {\n width: 100%;\n height: 100%;\n}\n\n.fullscreen canvas {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* Mobile-first: vertical scroll layout */\n.layout {\n /* Use fixed position on mobile to escape parent overflow:hidden */\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior-y: contain;\n padding: 0;\n gap: 0;\n background: #1a1a1a;\n}\n\n.layout .game {\n /* Use dvh with vh fallback for mobile Safari address bar */\n height: 100vh;\n height: 100dvh;\n width: 100%;\n flex-shrink: 0;\n /* Mobile: no border radius, fullscreen game */\n border-radius: 0;\n}\n\n/* Mobile: stretch canvas to fill game area */\n.layout .game .canvas-container {\n width: 100%;\n height: 100%;\n}\n\n.layout .game .canvas-container canvas {\n width: 100%;\n height: 100%;\n max-width: none;\n max-height: none;\n display: block;\n}\n\n.layout .stats,\n.layout .logs {\n width: 100%;\n min-height: 50vh;\n min-height: 50dvh;\n padding: 1rem;\n flex-shrink: 0;\n}\n\n/* Desktop: 2x2 grid layout */\n@media (min-width: 769px) {\n .layout {\n position: static;\n display: grid;\n grid-template-areas:\n \"game stats\"\n \"logs logs\";\n grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);\n grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);\n gap: 1rem;\n padding: 1rem;\n height: 100%;\n overflow: hidden;\n -webkit-overflow-scrolling: auto;\n overscroll-behavior-y: auto;\n }\n\n .layout .game {\n height: auto;\n flex-shrink: initial;\n border-radius: 8px;\n }\n\n /* Desktop: restore centered canvas with constraints */\n .layout .game .canvas-container canvas {\n width: auto;\n height: auto;\n max-width: 100%;\n max-height: 100%;\n }\n\n .layout .stats,\n .layout .logs {\n min-height: auto;\n padding: 1rem;\n flex-shrink: initial;\n }\n}\n\n/* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */\n.layout-letterboxed {\n display: grid;\n grid-template-areas:\n \"top-bar top-bar top-bar\"\n \"left-bar game right-bar\"\n \"bottom-bar bottom-bar bottom-bar\";\n grid-template-columns: var(--bar-size) 1fr var(--bar-size);\n grid-template-rows: var(--bar-size-h) 1fr var(--bar-size-h);\n width: 100vw;\n /* Use dvh with vh fallback for mobile Safari address bar */\n height: 100vh;\n height: 100dvh;\n background: #1a1a1a;\n overflow: hidden;\n overscroll-behavior: none;\n}\n\n.top-bar {\n grid-area: top-bar;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: #111;\n color: #aaa;\n font-family: monospace;\n font-size: 12px;\n padding: 0;\n user-select: none;\n}\n\n.top-bar-side-label {\n width: var(--bar-size);\n text-align: center;\n font-size: 9px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: #666;\n}\n\n/* Mobile: larger top bar text */\n@media (max-width: 768px) {\n .top-bar-side-label {\n font-size: 12px;\n }\n\n .top-bar {\n font-size: 14px;\n }\n}\n\n.top-bar-center {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 24px;\n flex: 1;\n}\n\n.top-bar-item {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.top-bar-label {\n opacity: 0.6;\n}\n\n.top-bar-value {\n color: #fff;\n font-weight: 500;\n}\n\n.left-bar {\n grid-area: left-bar;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n background: #111;\n padding: 4px 0;\n user-select: none;\n}\n\n.right-bar {\n grid-area: right-bar;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n background: #111;\n padding: 4px 0;\n user-select: none;\n}\n\n.vertical-bar {\n width: 12px;\n flex: 1;\n background: #333;\n border-radius: 2px;\n position: relative;\n}\n\n.vertical-bar-fill {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n background: #4a9eff;\n border-radius: 2px;\n transition: height 0.1s ease-out;\n}\n\n.vertical-bar-popover {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n background: #222;\n color: #ccc;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 10px;\n font-family: monospace;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.15s;\n pointer-events: none;\n z-index: 10;\n}\n\n.vertical-bar-popover.left {\n left: calc(100% + 8px);\n}\n\n.vertical-bar-popover.right {\n right: calc(100% + 8px);\n}\n\n.vertical-bar:hover .vertical-bar-popover {\n opacity: 1;\n visibility: visible;\n}\n\n\n.bottom-bar {\n grid-area: bottom-bar;\n display: flex;\n align-items: center;\n background: #111;\n /* Mobile-first: more padding */\n padding: 0 16px;\n gap: 12px;\n user-select: none;\n}\n\n/* Desktop: tighter padding */\n@media (min-width: 769px) {\n .bottom-bar {\n padding: 0 8px;\n gap: 8px;\n }\n}\n\n.playbar-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n}\n\n/* Record button */\n.record-btn {\n color: #666;\n}\n\n.record-btn.recording {\n color: #ff4444;\n}\n\n.record-btn.recording svg {\n animation: recording-pulse 1s ease-in-out infinite;\n}\n\n@keyframes recording-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n/* Desktop: show REC label when recording */\n@media (min-width: 769px) {\n .record-btn.recording {\n width: auto;\n padding: 0 6px;\n gap: 4px;\n background: rgba(255, 68, 68, 0.15);\n border-radius: 3px;\n }\n}\n\n/* Replay indicator - mobile: hidden */\n.replay-indicator {\n display: none;\n}\n\n/* Desktop: full indicators with text */\n@media (min-width: 769px) {\n .recording-indicator {\n gap: 4px;\n padding: 2px 6px;\n background: rgba(255, 0, 0, 0.2);\n border: 1px solid #ff4444;\n border-radius: 3px;\n }\n\n .recording-indicator .recording-label {\n display: inline;\n color: #ff4444;\n font-size: 10px;\n font-weight: bold;\n font-family: monospace;\n }\n\n .recording-dot {\n width: 8px;\n height: 8px;\n }\n\n .replay-indicator {\n display: flex;\n align-items: center;\n padding: 2px 6px;\n background: rgba(100, 100, 255, 0.2);\n border: 1px solid #6666ff;\n border-radius: 3px;\n color: #6666ff;\n font-size: 10px;\n font-weight: bold;\n font-family: monospace;\n margin-right: 4px;\n }\n}\n\n/* Mobile-first: hide step/jump buttons */\n.playbar-btn.jump-back,\n.playbar-btn.step-back,\n.playbar-btn.step-forward,\n.playbar-btn.jump-forward {\n display: none;\n}\n\n/* Desktop: show all controls */\n@media (min-width: 769px) {\n .playbar-btn.jump-back,\n .playbar-btn.step-back,\n .playbar-btn.step-forward,\n .playbar-btn.jump-forward {\n display: flex;\n }\n}\n\n.playbar-btn {\n /* Mobile-first: larger buttons */\n width: 4vh;\n height: 4vh;\n min-width: 32px;\n min-height: 32px;\n border: none;\n outline: none;\n background: transparent;\n color: #888;\n font-size: 16px;\n cursor: pointer;\n border-radius: 2px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, color 0.15s;\n position: relative;\n}\n\n/* Desktop: sized to match indicators */\n@media (min-width: 769px) {\n .playbar-btn {\n width: 24px;\n height: 24px;\n min-width: 24px;\n min-height: 24px;\n }\n\n /* Save/Load buttons need room for icon + text */\n .playbar-btn.save-tape-btn,\n .playbar-btn.load-tape-btn {\n width: auto;\n padding: 0 6px;\n gap: 4px;\n }\n}\n\n/* Mobile-first: hide button text labels, show only icons */\n.btn-label {\n display: none;\n}\n\n/* Desktop: show button text labels */\n@media (min-width: 769px) {\n .btn-label {\n display: inline;\n }\n}\n\n.playbar-btn:hover {\n background: #333;\n color: #fff;\n}\n\n.playbar-btn:hover .tooltip {\n opacity: 1;\n visibility: visible;\n}\n\n.tooltip {\n position: absolute;\n bottom: calc(100% + 4px);\n left: 50%;\n transform: translateX(-50%);\n background: #222;\n color: #ccc;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 10px;\n white-space: nowrap;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.15s;\n pointer-events: none;\n z-index: 10;\n}\n\n.tooltip-left {\n left: 0;\n transform: none;\n}\n\n.tooltip kbd {\n background: #444;\n padding: 1px 4px;\n border-radius: 2px;\n margin-left: 4px;\n font-family: monospace;\n}\n\n.seek-bar {\n flex: 1;\n /* Mobile-first: larger seek bar */\n height: 32px;\n background: #222;\n border-radius: 4px;\n position: relative;\n cursor: pointer;\n overflow: hidden;\n user-select: none;\n}\n\n/* Desktop: smaller seek bar */\n@media (min-width: 769px) {\n .seek-bar {\n height: 16px;\n }\n}\n\n.seek-bar-fill {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n background: linear-gradient(to right, #4a2070, #7b3fa0);\n border-radius: 4px;\n transition: width 0.1s ease-out;\n}\n\n.seek-bar-position {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: #fff;\n}\n\n.letterboxed-game {\n grid-area: game;\n overflow: hidden;\n}\n\n.letterboxed-game .canvas-container {\n width: 100%;\n height: 100%;\n}\n\n.letterboxed-game canvas {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n.letterboxed-game {\n position: relative;\n}\n\n.letterboxed-game.hmr-flash::after {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n animation: hmr-pulse 0.3s ease-out forwards;\n}\n\n@keyframes hmr-pulse {\n 0% { box-shadow: inset 0 0 0 36px #7b3fa0; }\n 100% { box-shadow: inset 0 0 0 0 #7b3fa0; }\n}\n\n.game {\n grid-area: game;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.stats {\n grid-area: stats;\n background-color: #f0f0f0;\n padding: 1rem;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.logs {\n grid-area: logs;\n background-color: #f0f0f0;\n padding: 1rem;\n border-radius: 8px;\n overflow: hidden;\n}\n\n/* Canvas container */\n.canvas-container {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.canvas-container canvas {\n max-width: 100%;\n max-height: 100%;\n}\n\n/* Debug toggle button */\n.debug-toggle {\n position: fixed;\n bottom: 16px;\n right: 16px;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n border: none;\n background-color: rgba(0, 0, 0, 0.5);\n color: white;\n font-size: 18px;\n cursor: pointer;\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.2s;\n}\n\n.debug-toggle:hover {\n background-color: rgba(0, 0, 0, 0.7);\n}\n\n/* Stats panel */\n.stats-panel {\n background: rgba(0, 0, 0, 0.7);\n color: white;\n padding: 12px;\n border-radius: 8px;\n font-family: monospace;\n font-size: 14px;\n max-width: 100%;\n overflow: hidden;\n}\n\n.stats-panel h3 {\n margin: 0 0 8px 0;\n font-size: 14px;\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.stats-panel table {\n width: 100%;\n border-collapse: collapse;\n table-layout: fixed;\n}\n\n.stats-panel tr {\n border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n.stats-panel tr:last-child {\n border-bottom: none;\n}\n\n.stats-panel td {\n padding: 4px 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.stats-panel td:first-child {\n opacity: 0.7;\n width: 60%;\n}\n\n.stats-panel td:last-child {\n text-align: right;\n font-weight: 600;\n width: 40%;\n}\n\n.stats-panel p {\n margin: 0;\n opacity: 0.7;\n}\n\n/* Logs panel */\n.logs-list {\n width: 100%;\n height: 100%;\n overflow: auto;\n margin: 0;\n padding: 0;\n}\n\n.logs-list li {\n margin: 0 0 24px 0;\n list-style: none;\n}\n\n.logs-list h3 {\n font-size: 16px;\n font-weight: 500;\n margin: 0;\n}\n\n.logs-list .ws {\n color: darkolivegreen;\n}\n\n.logs-list .webrtc {\n color: darkmagenta;\n}\n\n.logs-list .rollback {\n color: darkblue;\n}\n\n.logs-list .local {\n color: #333;\n}\n\n.logs-list .content {\n font-size: 16px;\n}\n\n.logs-list p {\n margin: 4px 0;\n}\n\n.logs-list pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n background-color: oldlace;\n padding: 8px;\n border-radius: 4px;\n border: 1px inset lavender;\n}\n\n/* Load Tape Dialog */\n.load-tape-dialog {\n background: #1a1a1a;\n border: 1px solid #333;\n border-radius: 8px;\n padding: 0;\n color: #ccc;\n font-family: monospace;\n max-width: 320px;\n width: 90vw;\n}\n\n.load-tape-dialog::backdrop {\n background: rgba(0, 0, 0, 0.7);\n}\n\n.load-tape-dialog-content {\n padding: 16px;\n}\n\n.load-tape-dialog h3 {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: #fff;\n}\n\n.drop-zone {\n border: 2px dashed #444;\n border-radius: 8px;\n padding: 32px 16px;\n text-align: center;\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n}\n\n.drop-zone:hover {\n border-color: #666;\n background: #222;\n}\n\n.drop-zone.drag-over {\n border-color: #7b3fa0;\n background: rgba(123, 63, 160, 0.1);\n}\n\n.drop-zone-text {\n color: #888;\n font-size: 12px;\n line-height: 1.5;\n}\n\n.hidden-file-input {\n display: none;\n}\n\n.replay-last-btn {\n width: 100%;\n margin-top: 12px;\n padding: 8px 12px;\n background: #333;\n border: none;\n border-radius: 4px;\n color: #ccc;\n font-family: monospace;\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.replay-last-btn:hover {\n background: #444;\n color: #fff;\n}\n\n.load-tape-btn {\n margin-left: 4px;\n}\n";
2
2
  //# sourceMappingURL=styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/debugui/styles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,43aAmwBlB,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/debugui/styles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,klcA2yBlB,CAAC"}
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.102/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();
@@ -3321,7 +3324,7 @@ function readTapeHeader2(tape) {
3321
3324
  eventCount: view.getUint16(14, true)
3322
3325
  };
3323
3326
  }
3324
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.102/wasm/bloop.wasm");
3327
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.103/wasm/bloop.wasm");
3325
3328
  var TIME_CTX_OFFSET2 = 0;
3326
3329
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3327
3330
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
@@ -4444,9 +4447,12 @@ var onJumpForward = d3(null);
4444
4447
  var onSeek = d3(null);
4445
4448
  var onLoadTape = d3(null);
4446
4449
  var onReplayLastTape = d3(null);
4450
+ var onReplayLastSaved = d3(null);
4447
4451
  var onSaveTape = d3(null);
4448
4452
  var lastTapeName = d3(null);
4453
+ var lastSavedTapeName = d3(null);
4449
4454
  var isLoadDialogOpen = d3(false);
4455
+ var onToggleRecording = d3(null);
4450
4456
  var debugState = {
4451
4457
  layoutMode,
4452
4458
  isVisible: w3(() => layoutMode.value !== "off"),
@@ -4477,9 +4483,12 @@ var debugState = {
4477
4483
  onSeek,
4478
4484
  onLoadTape,
4479
4485
  onReplayLastTape,
4486
+ onReplayLastSaved,
4480
4487
  onSaveTape,
4481
4488
  lastTapeName,
4482
- isLoadDialogOpen
4489
+ lastSavedTapeName,
4490
+ isLoadDialogOpen,
4491
+ onToggleRecording
4483
4492
  };
4484
4493
  function cycleLayout() {
4485
4494
  const current = layoutMode.value;
@@ -4615,12 +4624,20 @@ function wirePlaybarHandlers(app) {
4615
4624
  };
4616
4625
  debugState.onSeek.value = (ratio) => {
4617
4626
  if (app.sim.hasHistory) {
4627
+ app.sim.pause();
4618
4628
  const startFrame = debugState.tapeStartFrame.value;
4619
4629
  const frameCount = debugState.tapeFrameCount.value;
4620
4630
  const targetFrame = startFrame + Math.floor(ratio * frameCount);
4621
4631
  app.sim.seek(targetFrame);
4622
4632
  }
4623
4633
  };
4634
+ debugState.onToggleRecording.value = () => {
4635
+ if (app.sim.isRecording) {
4636
+ app.sim.stopRecording();
4637
+ } else {
4638
+ app.sim.record();
4639
+ }
4640
+ };
4624
4641
  }
4625
4642
  function wireTapeDragDrop(canvas, app) {
4626
4643
  canvas.addEventListener("dragover", (e4) => {
@@ -4639,7 +4656,8 @@ function wireTapeDragDrop(canvas, app) {
4639
4656
  }
4640
4657
  var TAPE_DB_NAME = "bloop-debug";
4641
4658
  var TAPE_STORE_NAME = "tapes";
4642
- var TAPE_KEY = "last";
4659
+ var TAPE_KEY_LOADED = "last-loaded";
4660
+ var TAPE_KEY_SAVED = "last-saved";
4643
4661
  function openTapeDB() {
4644
4662
  return new Promise((resolve, reject) => {
4645
4663
  const request = indexedDB.open(TAPE_DB_NAME, 1);
@@ -4650,21 +4668,21 @@ function openTapeDB() {
4650
4668
  };
4651
4669
  });
4652
4670
  }
4653
- async function saveTapeToStorage(bytes, fileName) {
4671
+ async function saveTapeToStorage(bytes, fileName, key = TAPE_KEY_LOADED) {
4654
4672
  const db = await openTapeDB();
4655
4673
  return new Promise((resolve, reject) => {
4656
4674
  const tx = db.transaction(TAPE_STORE_NAME, "readwrite");
4657
- tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, TAPE_KEY);
4675
+ tx.objectStore(TAPE_STORE_NAME).put({ bytes, fileName }, key);
4658
4676
  tx.oncomplete = () => resolve();
4659
4677
  tx.onerror = () => reject(tx.error);
4660
4678
  });
4661
4679
  }
4662
- async function loadTapeFromStorage() {
4680
+ async function loadTapeFromStorage(key = TAPE_KEY_LOADED) {
4663
4681
  try {
4664
4682
  const db = await openTapeDB();
4665
4683
  return new Promise((resolve, reject) => {
4666
4684
  const tx = db.transaction(TAPE_STORE_NAME, "readonly");
4667
- const request = tx.objectStore(TAPE_STORE_NAME).get(TAPE_KEY);
4685
+ const request = tx.objectStore(TAPE_STORE_NAME).get(key);
4668
4686
  request.onsuccess = () => resolve(request.result ?? null);
4669
4687
  request.onerror = () => reject(request.error);
4670
4688
  });
@@ -4673,32 +4691,46 @@ async function loadTapeFromStorage() {
4673
4691
  }
4674
4692
  }
4675
4693
  async function checkForSavedTape() {
4676
- const saved = await loadTapeFromStorage();
4677
- 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;
4678
4700
  }
4679
4701
  function wireTapeLoadHandlers(app) {
4680
4702
  debugState.onLoadTape.value = async (bytes, fileName) => {
4681
4703
  app.loadTape(bytes);
4682
- await saveTapeToStorage(bytes, fileName);
4704
+ await saveTapeToStorage(bytes, fileName, TAPE_KEY_LOADED);
4683
4705
  debugState.lastTapeName.value = fileName;
4684
4706
  debugState.isLoadDialogOpen.value = false;
4685
4707
  };
4686
4708
  debugState.onReplayLastTape.value = async () => {
4687
- const saved = await loadTapeFromStorage();
4709
+ const saved = await loadTapeFromStorage(TAPE_KEY_LOADED);
4710
+ if (saved) {
4711
+ app.loadTape(saved.bytes);
4712
+ debugState.isLoadDialogOpen.value = false;
4713
+ }
4714
+ };
4715
+ debugState.onReplayLastSaved.value = async () => {
4716
+ const saved = await loadTapeFromStorage(TAPE_KEY_SAVED);
4688
4717
  if (saved) {
4689
4718
  app.loadTape(saved.bytes);
4690
4719
  debugState.isLoadDialogOpen.value = false;
4691
4720
  }
4692
4721
  };
4693
- debugState.onSaveTape.value = () => {
4722
+ debugState.onSaveTape.value = async () => {
4694
4723
  if (!app.sim.hasHistory)
4695
4724
  return;
4696
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;
4697
4729
  const blob = new Blob([tape], { type: "application/octet-stream" });
4698
4730
  const url = URL.createObjectURL(blob);
4699
4731
  const a4 = document.createElement("a");
4700
4732
  a4.href = url;
4701
- a4.download = `tape-${Date.now()}.bloop`;
4733
+ a4.download = fileName;
4702
4734
  a4.click();
4703
4735
  URL.revokeObjectURL(url);
4704
4736
  };
@@ -4982,8 +5014,14 @@ function TopBar({ leftLabel, rightLabel }) {
4982
5014
  const frameNumber2 = debugState.frameNumber.value;
4983
5015
  const rtt = debugState.netStatus.value.rtt;
4984
5016
  const isOnline = debugState.netStatus.value.peers.length > 0;
5017
+ const stopPropagation = q2((e4) => {
5018
+ e4.stopPropagation();
5019
+ }, []);
4985
5020
  return /* @__PURE__ */ u4("div", {
4986
5021
  className: "top-bar",
5022
+ onMouseDown: stopPropagation,
5023
+ onMouseUp: stopPropagation,
5024
+ onClick: stopPropagation,
4987
5025
  children: [
4988
5026
  /* @__PURE__ */ u4("span", {
4989
5027
  className: "top-bar-side-label",
@@ -5049,18 +5087,31 @@ function VerticalBar({
5049
5087
  value,
5050
5088
  max,
5051
5089
  side,
5052
- color = "#4a9eff"
5090
+ color = "#4a9eff",
5091
+ displayValue
5053
5092
  }) {
5054
5093
  const percentage = max > 0 ? Math.min(100, value / max * 100) : 0;
5094
+ const stopPropagation = q2((e4) => {
5095
+ e4.stopPropagation();
5096
+ }, []);
5055
5097
  return /* @__PURE__ */ u4("div", {
5056
5098
  className: `${side}-bar`,
5099
+ onMouseDown: stopPropagation,
5100
+ onMouseUp: stopPropagation,
5101
+ onClick: stopPropagation,
5057
5102
  children: /* @__PURE__ */ u4("div", {
5058
5103
  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)
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)
5064
5115
  }, undefined, false, undefined, this);
5065
5116
  }
5066
5117
 
@@ -5070,7 +5121,6 @@ function LoadTapeDialog() {
5070
5121
  const fileInputRef = A2(null);
5071
5122
  const [isDragOver, setIsDragOver] = d2(false);
5072
5123
  const isOpen = debugState.isLoadDialogOpen.value;
5073
- const lastTapeName2 = debugState.lastTapeName.value;
5074
5124
  y2(() => {
5075
5125
  const dialog = dialogRef.current;
5076
5126
  if (!dialog)
@@ -5121,11 +5171,20 @@ function LoadTapeDialog() {
5121
5171
  const handleReplayLast = q2(() => {
5122
5172
  debugState.onReplayLastTape.value?.();
5123
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
+ }, []);
5124
5182
  return /* @__PURE__ */ u4("dialog", {
5125
5183
  ref: dialogRef,
5126
5184
  className: "load-tape-dialog",
5127
5185
  onClose: handleClose,
5128
- children: /* @__PURE__ */ u4("div", {
5186
+ onClick: handleDialogClick,
5187
+ children: isOpen && /* @__PURE__ */ u4("div", {
5129
5188
  className: "load-tape-dialog-content",
5130
5189
  children: [
5131
5190
  /* @__PURE__ */ u4("h3", {
@@ -5153,12 +5212,17 @@ function LoadTapeDialog() {
5153
5212
  className: "hidden-file-input",
5154
5213
  onChange: handleFileInputChange
5155
5214
  }, undefined, false, undefined, this),
5156
- 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", {
5157
5221
  className: "replay-last-btn",
5158
5222
  onClick: handleReplayLast,
5159
5223
  children: [
5160
- "Replay last: ",
5161
- lastTapeName2
5224
+ "Replay last loaded: ",
5225
+ debugState.lastTapeName.value
5162
5226
  ]
5163
5227
  }, undefined, true, undefined, this)
5164
5228
  ]
@@ -5169,6 +5233,14 @@ function LoadTapeDialog() {
5169
5233
  // src/debugui/components/BottomBar.tsx
5170
5234
  var iconProps = { width: 14, height: 14, viewBox: "0 0 24 24", fill: "currentColor" };
5171
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),
5172
5244
  jumpBack: /* @__PURE__ */ u4("svg", {
5173
5245
  ...iconProps,
5174
5246
  children: /* @__PURE__ */ u4("path", {
@@ -5311,22 +5383,33 @@ function BottomBar() {
5311
5383
  const handleSaveTapeClick = q2(() => {
5312
5384
  debugState.onSaveTape.value?.();
5313
5385
  }, []);
5386
+ const handleToggleRecording = q2(() => {
5387
+ debugState.onToggleRecording.value?.();
5388
+ }, []);
5389
+ const stopPropagation = q2((e4) => {
5390
+ e4.stopPropagation();
5391
+ }, []);
5314
5392
  return /* @__PURE__ */ u4("div", {
5315
5393
  className: "bottom-bar",
5394
+ onMouseDown: stopPropagation,
5395
+ onMouseUp: stopPropagation,
5396
+ onClick: stopPropagation,
5316
5397
  children: [
5317
5398
  /* @__PURE__ */ u4("div", {
5318
5399
  className: "playbar-controls",
5319
5400
  children: [
5320
- isRecording2 && /* @__PURE__ */ u4("span", {
5321
- className: "recording-indicator",
5322
- title: "Recording",
5401
+ /* @__PURE__ */ u4("button", {
5402
+ className: `playbar-btn record-btn ${isRecording2 ? "recording" : ""}`,
5403
+ onClick: handleToggleRecording,
5323
5404
  children: [
5324
- /* @__PURE__ */ u4("span", {
5325
- className: "recording-dot"
5405
+ Icons.record,
5406
+ isRecording2 && /* @__PURE__ */ u4("span", {
5407
+ className: "btn-label",
5408
+ children: "REC"
5326
5409
  }, undefined, false, undefined, this),
5327
5410
  /* @__PURE__ */ u4("span", {
5328
- className: "recording-label",
5329
- children: "REC"
5411
+ className: "tooltip tooltip-left",
5412
+ children: isRecording2 ? "Stop recording" : "Start recording"
5330
5413
  }, undefined, false, undefined, this)
5331
5414
  ]
5332
5415
  }, undefined, true, undefined, this),
@@ -5537,11 +5620,13 @@ function LetterboxedLayout({ canvas }) {
5537
5620
  const hmrFlash2 = debugState.hmrFlash.value;
5538
5621
  const leftValue = isOnline ? Math.abs(advantage) : frameTime2;
5539
5622
  const leftMax = isOnline ? 10 : 16.67;
5540
- const leftLabel = isOnline ? "ADV" : "MS";
5623
+ const leftLabel = isOnline ? "adv" : "time";
5541
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`;
5542
5626
  const rightValue = isOnline ? 0 : snapshotSize2;
5543
5627
  const rightMax = isOnline ? 10 : 1e4;
5544
- 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`;
5545
5630
  const gameClassName = hmrFlash2 ? "letterboxed-game hmr-flash" : "letterboxed-game";
5546
5631
  return /* @__PURE__ */ u4("main", {
5547
5632
  className: "layout-letterboxed",
@@ -5554,7 +5639,8 @@ function LetterboxedLayout({ canvas }) {
5554
5639
  value: leftValue,
5555
5640
  max: leftMax,
5556
5641
  side: "left",
5557
- color: leftColor
5642
+ color: leftColor,
5643
+ displayValue: leftDisplayValue
5558
5644
  }, undefined, false, undefined, this),
5559
5645
  /* @__PURE__ */ u4("div", {
5560
5646
  className: gameClassName,
@@ -5565,7 +5651,8 @@ function LetterboxedLayout({ canvas }) {
5565
5651
  /* @__PURE__ */ u4(VerticalBar, {
5566
5652
  value: rightValue,
5567
5653
  max: rightMax,
5568
- side: "right"
5654
+ side: "right",
5655
+ displayValue: rightDisplayValue
5569
5656
  }, undefined, false, undefined, this),
5570
5657
  /* @__PURE__ */ u4(BottomBar, {}, undefined, false, undefined, this)
5571
5658
  ]
@@ -5749,6 +5836,7 @@ var styles = `
5749
5836
  font-family: monospace;
5750
5837
  font-size: 12px;
5751
5838
  padding: 0;
5839
+ user-select: none;
5752
5840
  }
5753
5841
 
5754
5842
  .top-bar-side-label {
@@ -5802,6 +5890,7 @@ var styles = `
5802
5890
  justify-content: flex-end;
5803
5891
  background: #111;
5804
5892
  padding: 4px 0;
5893
+ user-select: none;
5805
5894
  }
5806
5895
 
5807
5896
  .right-bar {
@@ -5812,6 +5901,7 @@ var styles = `
5812
5901
  justify-content: flex-end;
5813
5902
  background: #111;
5814
5903
  padding: 4px 0;
5904
+ user-select: none;
5815
5905
  }
5816
5906
 
5817
5907
  .vertical-bar {
@@ -5820,7 +5910,6 @@ var styles = `
5820
5910
  background: #333;
5821
5911
  border-radius: 2px;
5822
5912
  position: relative;
5823
- overflow: hidden;
5824
5913
  }
5825
5914
 
5826
5915
  .vertical-bar-fill {
@@ -5833,6 +5922,37 @@ var styles = `
5833
5922
  transition: height 0.1s ease-out;
5834
5923
  }
5835
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
+
5836
5956
 
5837
5957
  .bottom-bar {
5838
5958
  grid-area: bottom-bar;
@@ -5842,6 +5962,7 @@ var styles = `
5842
5962
  /* Mobile-first: more padding */
5843
5963
  padding: 0 16px;
5844
5964
  gap: 12px;
5965
+ user-select: none;
5845
5966
  }
5846
5967
 
5847
5968
  /* Desktop: tighter padding */
@@ -5859,22 +5980,16 @@ var styles = `
5859
5980
  flex-shrink: 0;
5860
5981
  }
5861
5982
 
5862
- /* Recording indicator - mobile: just the dot */
5863
- .recording-indicator {
5864
- display: flex;
5865
- align-items: center;
5866
- margin-right: 4px;
5983
+ /* Record button */
5984
+ .record-btn {
5985
+ color: #666;
5867
5986
  }
5868
5987
 
5869
- .recording-indicator .recording-label {
5870
- display: none;
5988
+ .record-btn.recording {
5989
+ color: #ff4444;
5871
5990
  }
5872
5991
 
5873
- .recording-dot {
5874
- width: 10px;
5875
- height: 10px;
5876
- background: #ff4444;
5877
- border-radius: 50%;
5992
+ .record-btn.recording svg {
5878
5993
  animation: recording-pulse 1s ease-in-out infinite;
5879
5994
  }
5880
5995
 
@@ -5883,6 +5998,17 @@ var styles = `
5883
5998
  50% { opacity: 0.4; }
5884
5999
  }
5885
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
+
5886
6012
  /* Replay indicator - mobile: hidden */
5887
6013
  .replay-indicator {
5888
6014
  display: none;
@@ -6044,6 +6170,7 @@ var styles = `
6044
6170
  position: relative;
6045
6171
  cursor: pointer;
6046
6172
  overflow: hidden;
6173
+ user-select: none;
6047
6174
  }
6048
6175
 
6049
6176
  /* Desktop: smaller seek bar */
@@ -7698,5 +7825,5 @@ export {
7698
7825
  App
7699
7826
  };
7700
7827
 
7701
- //# debugId=BE2C82666F521FFC64756E2164756E21
7828
+ //# debugId=A1466E71356AC5F764756E2164756E21
7702
7829
  //# sourceMappingURL=mod.js.map