@bloopjs/web 0.0.91 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"BottomBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/BottomBar.tsx"],"names":[],"mappings":"AAiFA,wBAAgB,SAAS,4CA6FxB"}
1
+ {"version":3,"file":"BottomBar.d.ts","sourceRoot":"","sources":["../../../src/debugui/components/BottomBar.tsx"],"names":[],"mappings":"AAiFA,wBAAgB,SAAS,4CAuGxB"}
@@ -41,6 +41,7 @@ export type DebugState = {
41
41
  onSeek: Signal<((position: number) => void) | null>;
42
42
  onLoadTape: Signal<((bytes: Uint8Array, fileName: string) => void) | null>;
43
43
  onReplayLastTape: Signal<(() => void) | null>;
44
+ onSaveTape: Signal<(() => void) | null>;
44
45
  lastTapeName: Signal<string | null>;
45
46
  isLoadDialogOpen: Signal<boolean>;
46
47
  };
@@ -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/* Layout */\n.fullscreen {\n width: 100vw;\n height: 100vh;\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.layout {\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 width: 100%;\n height: 100%;\n padding: 1rem;\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: 2vw 1fr 2vw;\n grid-template-rows: 2vh 1fr 2vh;\n width: 100vw;\n height: 100vh;\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: 2vw;\n text-align: center;\n font-size: 9px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: #666;\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 padding: 0 8px;\n gap: 8px;\n}\n\n.playbar-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n}\n\n.playbar-btn {\n width: 1.5vh;\n height: 1.5vh;\n min-width: 18px;\n min-height: 18px;\n border: none;\n outline: none;\n background: transparent;\n color: #888;\n font-size: 10px;\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.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 height: 16px;\n background: #222;\n border-radius: 4px;\n position: relative;\n cursor: pointer;\n overflow: hidden;\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}\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/* 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: smaller buttons */\n@media (min-width: 769px) {\n .playbar-btn {\n width: 1.5vh;\n height: 1.5vh;\n min-width: 18px;\n min-height: 18px;\n font-size: 10px;\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";
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,oqRAkhBlB,CAAC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/debugui/styles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,itXA6qBlB,CAAC"}
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.91/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.91/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;
@@ -4394,6 +4394,7 @@ var onJumpForward = d3(null);
4394
4394
  var onSeek = d3(null);
4395
4395
  var onLoadTape = d3(null);
4396
4396
  var onReplayLastTape = d3(null);
4397
+ var onSaveTape = d3(null);
4397
4398
  var lastTapeName = d3(null);
4398
4399
  var isLoadDialogOpen = d3(false);
4399
4400
  var debugState = {
@@ -4424,6 +4425,7 @@ var debugState = {
4424
4425
  onSeek,
4425
4426
  onLoadTape,
4426
4427
  onReplayLastTape,
4428
+ onSaveTape,
4427
4429
  lastTapeName,
4428
4430
  isLoadDialogOpen
4429
4431
  };
@@ -4634,6 +4636,18 @@ function wireTapeLoadHandlers(app) {
4634
4636
  debugState.isLoadDialogOpen.value = false;
4635
4637
  }
4636
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
+ };
4637
4651
  checkForSavedTape();
4638
4652
  }
4639
4653
  // ../../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
@@ -5187,6 +5201,9 @@ function BottomBar() {
5187
5201
  const handleLoadTapeClick = q2(() => {
5188
5202
  debugState.isLoadDialogOpen.value = true;
5189
5203
  }, []);
5204
+ const handleSaveTapeClick = q2(() => {
5205
+ debugState.onSaveTape.value?.();
5206
+ }, []);
5190
5207
  return /* @__PURE__ */ u4("div", {
5191
5208
  className: "bottom-bar",
5192
5209
  children: [
@@ -5194,7 +5211,7 @@ function BottomBar() {
5194
5211
  className: "playbar-controls",
5195
5212
  children: [
5196
5213
  /* @__PURE__ */ u4("button", {
5197
- className: "playbar-btn",
5214
+ className: "playbar-btn jump-back",
5198
5215
  ...jumpBackRepeat,
5199
5216
  children: [
5200
5217
  "<<",
@@ -5210,7 +5227,7 @@ function BottomBar() {
5210
5227
  ]
5211
5228
  }, undefined, true, undefined, this),
5212
5229
  /* @__PURE__ */ u4("button", {
5213
- className: "playbar-btn",
5230
+ className: "playbar-btn step-back",
5214
5231
  ...stepBackRepeat,
5215
5232
  children: [
5216
5233
  "<",
@@ -5226,7 +5243,7 @@ function BottomBar() {
5226
5243
  ]
5227
5244
  }, undefined, true, undefined, this),
5228
5245
  /* @__PURE__ */ u4("button", {
5229
- className: "playbar-btn",
5246
+ className: "playbar-btn play-pause",
5230
5247
  onClick: handlePlayPause,
5231
5248
  children: [
5232
5249
  isPlaying2 ? "||" : ">",
@@ -5243,7 +5260,7 @@ function BottomBar() {
5243
5260
  ]
5244
5261
  }, undefined, true, undefined, this),
5245
5262
  /* @__PURE__ */ u4("button", {
5246
- className: "playbar-btn",
5263
+ className: "playbar-btn step-forward",
5247
5264
  ...stepForwardRepeat,
5248
5265
  children: [
5249
5266
  ">",
@@ -5259,7 +5276,7 @@ function BottomBar() {
5259
5276
  ]
5260
5277
  }, undefined, true, undefined, this),
5261
5278
  /* @__PURE__ */ u4("button", {
5262
- className: "playbar-btn",
5279
+ className: "playbar-btn jump-forward",
5263
5280
  ...jumpForwardRepeat,
5264
5281
  children: [
5265
5282
  ">>",
@@ -5274,6 +5291,22 @@ function BottomBar() {
5274
5291
  }, undefined, true, undefined, this)
5275
5292
  ]
5276
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),
5277
5310
  /* @__PURE__ */ u4("button", {
5278
5311
  className: "playbar-btn load-tape-btn",
5279
5312
  onClick: handleLoadTapeClick,
@@ -5427,10 +5460,26 @@ var styles = `
5427
5460
  box-sizing: border-box;
5428
5461
  }
5429
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
+
5430
5478
  /* Layout */
5431
5479
  .fullscreen {
5432
5480
  width: 100vw;
5433
5481
  height: 100vh;
5482
+ height: 100dvh;
5434
5483
  margin: 0;
5435
5484
  padding: 0;
5436
5485
  overflow: hidden;
@@ -5447,17 +5496,96 @@ var styles = `
5447
5496
  display: block;
5448
5497
  }
5449
5498
 
5499
+ /* Mobile-first: vertical scroll layout */
5450
5500
  .layout {
5451
- display: grid;
5452
- grid-template-areas:
5453
- "game stats"
5454
- "logs logs";
5455
- grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);
5456
- grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);
5457
- 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 {
5458
5530
  width: 100%;
5459
5531
  height: 100%;
5532
+ }
5533
+
5534
+ .layout .game .canvas-container canvas {
5535
+ width: 100%;
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;
5460
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
+ }
5461
5589
  }
5462
5590
 
5463
5591
  /* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */
@@ -5467,10 +5595,12 @@ var styles = `
5467
5595
  "top-bar top-bar top-bar"
5468
5596
  "left-bar game right-bar"
5469
5597
  "bottom-bar bottom-bar bottom-bar";
5470
- grid-template-columns: 2vw 1fr 2vw;
5471
- 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);
5472
5600
  width: 100vw;
5601
+ /* Use dvh with vh fallback for mobile Safari address bar */
5473
5602
  height: 100vh;
5603
+ height: 100dvh;
5474
5604
  background: #1a1a1a;
5475
5605
  overflow: hidden;
5476
5606
  overscroll-behavior: none;
@@ -5489,7 +5619,7 @@ var styles = `
5489
5619
  }
5490
5620
 
5491
5621
  .top-bar-side-label {
5492
- width: 2vw;
5622
+ width: var(--bar-size);
5493
5623
  text-align: center;
5494
5624
  font-size: 9px;
5495
5625
  text-transform: uppercase;
@@ -5497,6 +5627,17 @@ var styles = `
5497
5627
  color: #666;
5498
5628
  }
5499
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
+
5500
5641
  .top-bar-center {
5501
5642
  display: flex;
5502
5643
  align-items: center;
@@ -5565,8 +5706,17 @@ var styles = `
5565
5706
  display: flex;
5566
5707
  align-items: center;
5567
5708
  background: #111;
5568
- padding: 0 8px;
5569
- 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
+ }
5570
5720
  }
5571
5721
 
5572
5722
  .playbar-controls {
@@ -5576,16 +5726,35 @@ var styles = `
5576
5726
  flex-shrink: 0;
5577
5727
  }
5578
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
+
5579
5747
  .playbar-btn {
5580
- width: 1.5vh;
5581
- height: 1.5vh;
5582
- min-width: 18px;
5583
- min-height: 18px;
5748
+ /* Mobile-first: larger buttons */
5749
+ width: 4vh;
5750
+ height: 4vh;
5751
+ min-width: 32px;
5752
+ min-height: 32px;
5584
5753
  border: none;
5585
5754
  outline: none;
5586
5755
  background: transparent;
5587
5756
  color: #888;
5588
- font-size: 10px;
5757
+ font-size: 16px;
5589
5758
  cursor: pointer;
5590
5759
  border-radius: 2px;
5591
5760
  display: flex;
@@ -5595,6 +5764,17 @@ var styles = `
5595
5764
  position: relative;
5596
5765
  }
5597
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
+
5598
5778
  .playbar-btn:hover {
5599
5779
  background: #333;
5600
5780
  color: #fff;
@@ -5638,7 +5818,8 @@ var styles = `
5638
5818
 
5639
5819
  .seek-bar {
5640
5820
  flex: 1;
5641
- height: 16px;
5821
+ /* Mobile-first: larger seek bar */
5822
+ height: 32px;
5642
5823
  background: #222;
5643
5824
  border-radius: 4px;
5644
5825
  position: relative;
@@ -5646,6 +5827,13 @@ var styles = `
5646
5827
  overflow: hidden;
5647
5828
  }
5648
5829
 
5830
+ /* Desktop: smaller seek bar */
5831
+ @media (min-width: 769px) {
5832
+ .seek-bar {
5833
+ height: 16px;
5834
+ }
5835
+ }
5836
+
5649
5837
  .seek-bar-fill {
5650
5838
  position: absolute;
5651
5839
  top: 0;
@@ -7262,5 +7450,5 @@ export {
7262
7450
  App
7263
7451
  };
7264
7452
 
7265
- //# debugId=3429E07BC69AE66A64756E2164756E21
7453
+ //# debugId=B02D4DB9DDB2D5FC64756E2164756E21
7266
7454
  //# sourceMappingURL=mod.js.map