@bloopjs/web 0.0.91 → 0.0.93

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
@@ -415,6 +415,7 @@ var NET_CTX_PEERS_OFFSET = 32;
415
415
  var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET = 128;
416
416
  var NET_CTX_TOTAL_ROLLBACKS_OFFSET = 132;
417
417
  var NET_CTX_FRAMES_RESIMULATED_OFFSET = 136;
418
+ var NET_CTX_CONFIRMED_MATCH_FRAME_OFFSET = 144;
418
419
  var SCREEN_CTX_WIDTH_OFFSET = 0;
419
420
  var SCREEN_CTX_HEIGHT_OFFSET = 4;
420
421
  var SCREEN_CTX_PHYSICAL_WIDTH_OFFSET = 8;
@@ -1334,6 +1335,23 @@ class NetContext {
1334
1335
  }
1335
1336
  return Number(this.dataView.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET, true));
1336
1337
  }
1338
+ get confirmedMatchFrame() {
1339
+ if (!this.#hasValidBuffer()) {
1340
+ throw new Error("NetContext dataView is not valid");
1341
+ }
1342
+ return this.dataView.getInt32(NET_CTX_CONFIRMED_MATCH_FRAME_OFFSET, true);
1343
+ }
1344
+ get isConfirmFrame() {
1345
+ if (this.peerCount <= 1)
1346
+ return true;
1347
+ const confirmed = this.confirmedMatchFrame;
1348
+ if (confirmed < 0)
1349
+ return false;
1350
+ return this.matchFrame <= confirmed;
1351
+ }
1352
+ get isPredictionFrame() {
1353
+ return !this.isConfirmFrame;
1354
+ }
1337
1355
  }
1338
1356
 
1339
1357
  class ScreenContext {
@@ -1432,7 +1450,7 @@ function readTapeHeader(tape) {
1432
1450
  eventCount: view.getUint16(14, true)
1433
1451
  };
1434
1452
  }
1435
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.91/wasm/bloop.wasm");
1453
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.93/wasm/bloop.wasm");
1436
1454
  var MAX_ROLLBACK_FRAMES = 500;
1437
1455
  var TIME_CTX_OFFSET = 0;
1438
1456
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
@@ -2343,6 +2361,7 @@ var NET_CTX_PEERS_OFFSET2 = 32;
2343
2361
  var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET2 = 128;
2344
2362
  var NET_CTX_TOTAL_ROLLBACKS_OFFSET2 = 132;
2345
2363
  var NET_CTX_FRAMES_RESIMULATED_OFFSET2 = 136;
2364
+ var NET_CTX_CONFIRMED_MATCH_FRAME_OFFSET2 = 144;
2346
2365
  var MOUSE_CTX_X_OFFSET2 = 0;
2347
2366
  var MOUSE_CTX_Y_OFFSET2 = 4;
2348
2367
  var MOUSE_CTX_WHEEL_X_OFFSET2 = 8;
@@ -3254,6 +3273,23 @@ class NetContext2 {
3254
3273
  }
3255
3274
  return Number(this.dataView.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET2, true));
3256
3275
  }
3276
+ get confirmedMatchFrame() {
3277
+ if (!this.#hasValidBuffer()) {
3278
+ throw new Error("NetContext dataView is not valid");
3279
+ }
3280
+ return this.dataView.getInt32(NET_CTX_CONFIRMED_MATCH_FRAME_OFFSET2, true);
3281
+ }
3282
+ get isConfirmFrame() {
3283
+ if (this.peerCount <= 1)
3284
+ return true;
3285
+ const confirmed = this.confirmedMatchFrame;
3286
+ if (confirmed < 0)
3287
+ return false;
3288
+ return this.matchFrame <= confirmed;
3289
+ }
3290
+ get isPredictionFrame() {
3291
+ return !this.isConfirmFrame;
3292
+ }
3257
3293
  }
3258
3294
  function mouseButtonCodeToMouseButton2(code) {
3259
3295
  return MouseButton2[code];
@@ -3273,7 +3309,7 @@ function readTapeHeader2(tape) {
3273
3309
  eventCount: view.getUint16(14, true)
3274
3310
  };
3275
3311
  }
3276
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.91/wasm/bloop.wasm");
3312
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.93/wasm/bloop.wasm");
3277
3313
  var TIME_CTX_OFFSET2 = 0;
3278
3314
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3279
3315
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
@@ -4394,6 +4430,7 @@ var onJumpForward = d3(null);
4394
4430
  var onSeek = d3(null);
4395
4431
  var onLoadTape = d3(null);
4396
4432
  var onReplayLastTape = d3(null);
4433
+ var onSaveTape = d3(null);
4397
4434
  var lastTapeName = d3(null);
4398
4435
  var isLoadDialogOpen = d3(false);
4399
4436
  var debugState = {
@@ -4424,6 +4461,7 @@ var debugState = {
4424
4461
  onSeek,
4425
4462
  onLoadTape,
4426
4463
  onReplayLastTape,
4464
+ onSaveTape,
4427
4465
  lastTapeName,
4428
4466
  isLoadDialogOpen
4429
4467
  };
@@ -4634,6 +4672,18 @@ function wireTapeLoadHandlers(app) {
4634
4672
  debugState.isLoadDialogOpen.value = false;
4635
4673
  }
4636
4674
  };
4675
+ debugState.onSaveTape.value = () => {
4676
+ if (!app.sim.hasHistory)
4677
+ return;
4678
+ const tape = app.sim.saveTape();
4679
+ const blob = new Blob([tape], { type: "application/octet-stream" });
4680
+ const url = URL.createObjectURL(blob);
4681
+ const a4 = document.createElement("a");
4682
+ a4.href = url;
4683
+ a4.download = `tape-${Date.now()}.bloop`;
4684
+ a4.click();
4685
+ URL.revokeObjectURL(url);
4686
+ };
4637
4687
  checkForSavedTape();
4638
4688
  }
4639
4689
  // ../../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js
@@ -5187,6 +5237,9 @@ function BottomBar() {
5187
5237
  const handleLoadTapeClick = q2(() => {
5188
5238
  debugState.isLoadDialogOpen.value = true;
5189
5239
  }, []);
5240
+ const handleSaveTapeClick = q2(() => {
5241
+ debugState.onSaveTape.value?.();
5242
+ }, []);
5190
5243
  return /* @__PURE__ */ u4("div", {
5191
5244
  className: "bottom-bar",
5192
5245
  children: [
@@ -5194,7 +5247,7 @@ function BottomBar() {
5194
5247
  className: "playbar-controls",
5195
5248
  children: [
5196
5249
  /* @__PURE__ */ u4("button", {
5197
- className: "playbar-btn",
5250
+ className: "playbar-btn jump-back",
5198
5251
  ...jumpBackRepeat,
5199
5252
  children: [
5200
5253
  "<<",
@@ -5210,7 +5263,7 @@ function BottomBar() {
5210
5263
  ]
5211
5264
  }, undefined, true, undefined, this),
5212
5265
  /* @__PURE__ */ u4("button", {
5213
- className: "playbar-btn",
5266
+ className: "playbar-btn step-back",
5214
5267
  ...stepBackRepeat,
5215
5268
  children: [
5216
5269
  "<",
@@ -5226,7 +5279,7 @@ function BottomBar() {
5226
5279
  ]
5227
5280
  }, undefined, true, undefined, this),
5228
5281
  /* @__PURE__ */ u4("button", {
5229
- className: "playbar-btn",
5282
+ className: "playbar-btn play-pause",
5230
5283
  onClick: handlePlayPause,
5231
5284
  children: [
5232
5285
  isPlaying2 ? "||" : ">",
@@ -5243,7 +5296,7 @@ function BottomBar() {
5243
5296
  ]
5244
5297
  }, undefined, true, undefined, this),
5245
5298
  /* @__PURE__ */ u4("button", {
5246
- className: "playbar-btn",
5299
+ className: "playbar-btn step-forward",
5247
5300
  ...stepForwardRepeat,
5248
5301
  children: [
5249
5302
  ">",
@@ -5259,7 +5312,7 @@ function BottomBar() {
5259
5312
  ]
5260
5313
  }, undefined, true, undefined, this),
5261
5314
  /* @__PURE__ */ u4("button", {
5262
- className: "playbar-btn",
5315
+ className: "playbar-btn jump-forward",
5263
5316
  ...jumpForwardRepeat,
5264
5317
  children: [
5265
5318
  ">>",
@@ -5274,6 +5327,22 @@ function BottomBar() {
5274
5327
  }, undefined, true, undefined, this)
5275
5328
  ]
5276
5329
  }, undefined, true, undefined, this),
5330
+ /* @__PURE__ */ u4("button", {
5331
+ className: "playbar-btn save-tape-btn",
5332
+ onClick: handleSaveTapeClick,
5333
+ children: [
5334
+ "Save",
5335
+ /* @__PURE__ */ u4("span", {
5336
+ className: "tooltip",
5337
+ children: [
5338
+ "Save tape ",
5339
+ /* @__PURE__ */ u4("kbd", {
5340
+ children: "Cmd+S"
5341
+ }, undefined, false, undefined, this)
5342
+ ]
5343
+ }, undefined, true, undefined, this)
5344
+ ]
5345
+ }, undefined, true, undefined, this),
5277
5346
  /* @__PURE__ */ u4("button", {
5278
5347
  className: "playbar-btn load-tape-btn",
5279
5348
  onClick: handleLoadTapeClick,
@@ -5427,10 +5496,26 @@ var styles = `
5427
5496
  box-sizing: border-box;
5428
5497
  }
5429
5498
 
5499
+ /* Mobile-first CSS variables */
5500
+ :host {
5501
+ --bar-size: 10vw;
5502
+ --bar-size-h: 10vh;
5503
+ --bar-size-h: 10dvh;
5504
+ }
5505
+
5506
+ /* Desktop overrides */
5507
+ @media (min-width: 769px) {
5508
+ :host {
5509
+ --bar-size: 2vw;
5510
+ --bar-size-h: 2vh;
5511
+ }
5512
+ }
5513
+
5430
5514
  /* Layout */
5431
5515
  .fullscreen {
5432
5516
  width: 100vw;
5433
5517
  height: 100vh;
5518
+ height: 100dvh;
5434
5519
  margin: 0;
5435
5520
  padding: 0;
5436
5521
  overflow: hidden;
@@ -5447,17 +5532,96 @@ var styles = `
5447
5532
  display: block;
5448
5533
  }
5449
5534
 
5535
+ /* Mobile-first: vertical scroll layout */
5450
5536
  .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;
5537
+ /* Use fixed position on mobile to escape parent overflow:hidden */
5538
+ position: fixed;
5539
+ top: 0;
5540
+ left: 0;
5541
+ right: 0;
5542
+ bottom: 0;
5543
+ display: flex;
5544
+ flex-direction: column;
5545
+ overflow-y: auto;
5546
+ overflow-x: hidden;
5547
+ -webkit-overflow-scrolling: touch;
5548
+ overscroll-behavior-y: contain;
5549
+ padding: 0;
5550
+ gap: 0;
5551
+ background: #1a1a1a;
5552
+ }
5553
+
5554
+ .layout .game {
5555
+ /* Use dvh with vh fallback for mobile Safari address bar */
5556
+ height: 100vh;
5557
+ height: 100dvh;
5558
+ width: 100%;
5559
+ flex-shrink: 0;
5560
+ /* Mobile: no border radius, fullscreen game */
5561
+ border-radius: 0;
5562
+ }
5563
+
5564
+ /* Mobile: stretch canvas to fill game area */
5565
+ .layout .game .canvas-container {
5458
5566
  width: 100%;
5459
5567
  height: 100%;
5568
+ }
5569
+
5570
+ .layout .game .canvas-container canvas {
5571
+ width: 100%;
5572
+ height: 100%;
5573
+ max-width: none;
5574
+ max-height: none;
5575
+ display: block;
5576
+ }
5577
+
5578
+ .layout .stats,
5579
+ .layout .logs {
5580
+ width: 100%;
5581
+ min-height: 50vh;
5582
+ min-height: 50dvh;
5460
5583
  padding: 1rem;
5584
+ flex-shrink: 0;
5585
+ }
5586
+
5587
+ /* Desktop: 2x2 grid layout */
5588
+ @media (min-width: 769px) {
5589
+ .layout {
5590
+ position: static;
5591
+ display: grid;
5592
+ grid-template-areas:
5593
+ "game stats"
5594
+ "logs logs";
5595
+ grid-template-columns: calc(50% - 0.5rem) calc(50% - 0.5rem);
5596
+ grid-template-rows: calc(50% - 0.5rem) calc(50% - 0.5rem);
5597
+ gap: 1rem;
5598
+ padding: 1rem;
5599
+ height: 100%;
5600
+ overflow: hidden;
5601
+ -webkit-overflow-scrolling: auto;
5602
+ overscroll-behavior-y: auto;
5603
+ }
5604
+
5605
+ .layout .game {
5606
+ height: auto;
5607
+ flex-shrink: initial;
5608
+ border-radius: 8px;
5609
+ }
5610
+
5611
+ /* Desktop: restore centered canvas with constraints */
5612
+ .layout .game .canvas-container canvas {
5613
+ width: auto;
5614
+ height: auto;
5615
+ max-width: 100%;
5616
+ max-height: 100%;
5617
+ }
5618
+
5619
+ .layout .stats,
5620
+ .layout .logs {
5621
+ min-height: auto;
5622
+ padding: 1rem;
5623
+ flex-shrink: initial;
5624
+ }
5461
5625
  }
5462
5626
 
5463
5627
  /* Letterboxed layout - using equal vw/vh percentages keeps game at viewport aspect ratio */
@@ -5467,10 +5631,12 @@ var styles = `
5467
5631
  "top-bar top-bar top-bar"
5468
5632
  "left-bar game right-bar"
5469
5633
  "bottom-bar bottom-bar bottom-bar";
5470
- grid-template-columns: 2vw 1fr 2vw;
5471
- grid-template-rows: 2vh 1fr 2vh;
5634
+ grid-template-columns: var(--bar-size) 1fr var(--bar-size);
5635
+ grid-template-rows: var(--bar-size-h) 1fr var(--bar-size-h);
5472
5636
  width: 100vw;
5637
+ /* Use dvh with vh fallback for mobile Safari address bar */
5473
5638
  height: 100vh;
5639
+ height: 100dvh;
5474
5640
  background: #1a1a1a;
5475
5641
  overflow: hidden;
5476
5642
  overscroll-behavior: none;
@@ -5489,7 +5655,7 @@ var styles = `
5489
5655
  }
5490
5656
 
5491
5657
  .top-bar-side-label {
5492
- width: 2vw;
5658
+ width: var(--bar-size);
5493
5659
  text-align: center;
5494
5660
  font-size: 9px;
5495
5661
  text-transform: uppercase;
@@ -5497,6 +5663,17 @@ var styles = `
5497
5663
  color: #666;
5498
5664
  }
5499
5665
 
5666
+ /* Mobile: larger top bar text */
5667
+ @media (max-width: 768px) {
5668
+ .top-bar-side-label {
5669
+ font-size: 12px;
5670
+ }
5671
+
5672
+ .top-bar {
5673
+ font-size: 14px;
5674
+ }
5675
+ }
5676
+
5500
5677
  .top-bar-center {
5501
5678
  display: flex;
5502
5679
  align-items: center;
@@ -5565,8 +5742,17 @@ var styles = `
5565
5742
  display: flex;
5566
5743
  align-items: center;
5567
5744
  background: #111;
5568
- padding: 0 8px;
5569
- gap: 8px;
5745
+ /* Mobile-first: more padding */
5746
+ padding: 0 16px;
5747
+ gap: 12px;
5748
+ }
5749
+
5750
+ /* Desktop: tighter padding */
5751
+ @media (min-width: 769px) {
5752
+ .bottom-bar {
5753
+ padding: 0 8px;
5754
+ gap: 8px;
5755
+ }
5570
5756
  }
5571
5757
 
5572
5758
  .playbar-controls {
@@ -5576,16 +5762,35 @@ var styles = `
5576
5762
  flex-shrink: 0;
5577
5763
  }
5578
5764
 
5765
+ /* Mobile-first: hide step/jump buttons */
5766
+ .playbar-btn.jump-back,
5767
+ .playbar-btn.step-back,
5768
+ .playbar-btn.step-forward,
5769
+ .playbar-btn.jump-forward {
5770
+ display: none;
5771
+ }
5772
+
5773
+ /* Desktop: show all controls */
5774
+ @media (min-width: 769px) {
5775
+ .playbar-btn.jump-back,
5776
+ .playbar-btn.step-back,
5777
+ .playbar-btn.step-forward,
5778
+ .playbar-btn.jump-forward {
5779
+ display: flex;
5780
+ }
5781
+ }
5782
+
5579
5783
  .playbar-btn {
5580
- width: 1.5vh;
5581
- height: 1.5vh;
5582
- min-width: 18px;
5583
- min-height: 18px;
5784
+ /* Mobile-first: larger buttons */
5785
+ width: 4vh;
5786
+ height: 4vh;
5787
+ min-width: 32px;
5788
+ min-height: 32px;
5584
5789
  border: none;
5585
5790
  outline: none;
5586
5791
  background: transparent;
5587
5792
  color: #888;
5588
- font-size: 10px;
5793
+ font-size: 16px;
5589
5794
  cursor: pointer;
5590
5795
  border-radius: 2px;
5591
5796
  display: flex;
@@ -5595,6 +5800,17 @@ var styles = `
5595
5800
  position: relative;
5596
5801
  }
5597
5802
 
5803
+ /* Desktop: smaller buttons */
5804
+ @media (min-width: 769px) {
5805
+ .playbar-btn {
5806
+ width: 1.5vh;
5807
+ height: 1.5vh;
5808
+ min-width: 18px;
5809
+ min-height: 18px;
5810
+ font-size: 10px;
5811
+ }
5812
+ }
5813
+
5598
5814
  .playbar-btn:hover {
5599
5815
  background: #333;
5600
5816
  color: #fff;
@@ -5638,7 +5854,8 @@ var styles = `
5638
5854
 
5639
5855
  .seek-bar {
5640
5856
  flex: 1;
5641
- height: 16px;
5857
+ /* Mobile-first: larger seek bar */
5858
+ height: 32px;
5642
5859
  background: #222;
5643
5860
  border-radius: 4px;
5644
5861
  position: relative;
@@ -5646,6 +5863,13 @@ var styles = `
5646
5863
  overflow: hidden;
5647
5864
  }
5648
5865
 
5866
+ /* Desktop: smaller seek bar */
5867
+ @media (min-width: 769px) {
5868
+ .seek-bar {
5869
+ height: 16px;
5870
+ }
5871
+ }
5872
+
5649
5873
  .seek-bar-fill {
5650
5874
  position: absolute;
5651
5875
  top: 0;
@@ -7262,5 +7486,5 @@ export {
7262
7486
  App
7263
7487
  };
7264
7488
 
7265
- //# debugId=3429E07BC69AE66A64756E2164756E21
7489
+ //# debugId=18A1E6B20945E8E464756E2164756E21
7266
7490
  //# sourceMappingURL=mod.js.map