@puzzmo/sdk 1.0.9 → 1.0.10

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":"createSimulator-BmeD2jIP.cjs","names":[],"sources":["../src/simulator/styles.ts","../src/themes.ts","../src/simulator/state.ts","../src/simulator/messaging.ts","../src/simulator/fixtures.ts","../src/simulator/views/CtrlView.ts","../src/simulator/views/DataView.ts","../src/simulator/views/MsgsView.ts","../src/simulator/views/DoneView.ts","../src/simulator/views/CheckpointsView.ts","../src/simulator/views/ThumbView.ts","../src/simulator/views/ThemeView.ts","../src/simulator/views/AuthView.ts","../src/simulator/views/FeaturesView.ts","../src/simulator/createSimulator.ts"],"sourcesContent":["export const simulatorStyles = `\n :root {\n --sim-bg: #1a1a2e;\n --sim-bg-alt: #16213e;\n --sim-panel: #0f0f1a;\n --sim-border: #3a3a5c;\n --sim-border-light: #5a5a8c;\n --sim-accent: #ffd700;\n --sim-accent-hover: #ffed4a;\n --sim-text: #e8e8e8;\n --sim-text-dim: #888899;\n --sim-success: #4ade80;\n --sim-error: #f87171;\n --sim-warning: #fbbf24;\n --sim-blue: #60a5fa;\n }\n #simulator {\n position: fixed;\n bottom: 4px;\n left: 4px;\n z-index: 999999;\n font: 11px/1.4 \"Menlo\", \"Monaco\", \"Consolas\", monospace;\n user-select: none;\n }\n #simulator-panel {\n background: var(--sim-panel);\n border: 2px solid var(--sim-border);\n border-radius: 4px;\n color: var(--sim-text);\n width: 420px;\n box-shadow: 4px 4px 0 rgba(0,0,0,0.5);\n }\n #simulator-panel.collapsed {\n width: auto;\n }\n #simulator-header {\n display: flex;\n align-items: center;\n padding: 4px 8px;\n background: var(--sim-bg);\n border-bottom: 2px solid var(--sim-border);\n gap: 0;\n }\n #simulator-panel.collapsed #simulator-header {\n border-bottom: none;\n cursor: pointer;\n }\n #simulator-panel.collapsed #simulator-header:hover {\n background: var(--sim-bg-alt);\n }\n .header-sep {\n color: var(--sim-border-light);\n margin: 0 8px;\n opacity: 0.6;\n }\n .header-spacer {\n flex: 1;\n }\n #simulator-title {\n color: var(--sim-accent);\n text-transform: uppercase;\n letter-spacing: 1px;\n font-size: 10px;\n font-weight: bold;\n }\n #simulator-header-controls {\n display: flex;\n gap: 2px;\n }\n .header-icon-btn {\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: 2px;\n color: var(--sim-text);\n cursor: pointer;\n padding: 0;\n }\n .header-icon-btn:hover:not(:disabled) {\n background: var(--sim-border);\n color: var(--sim-accent);\n }\n .header-icon-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n }\n .header-icon-btn.active {\n background: var(--sim-accent);\n color: var(--sim-panel);\n }\n #simulator-header-status {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 9px;\n color: var(--sim-text-dim);\n }\n #simulator-header-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sim-text-dim);\n flex-shrink: 0;\n }\n #simulator-header-indicator.ready {\n background: var(--sim-success);\n box-shadow: 0 0 4px var(--sim-success);\n }\n #simulator-header-indicator.waiting {\n background: var(--sim-warning);\n box-shadow: 0 0 4px var(--sim-warning);\n }\n #simulator-header-indicator.paused {\n background: var(--sim-error);\n box-shadow: 0 0 4px var(--sim-error);\n }\n #simulator-timer {\n font-size: 11px;\n color: var(--sim-text);\n font-family: \"Menlo\", monospace;\n font-weight: bold;\n font-variant-numeric: tabular-nums;\n min-width: 38px;\n }\n #simulator-timer .penalty {\n color: var(--sim-error);\n margin-left: 2px;\n }\n /* Collapsed state - show minimal bar */\n #simulator-panel.collapsed .header-sep,\n #simulator-panel.collapsed #simulator-header-controls,\n #simulator-panel.collapsed #simulator-header-status,\n #simulator-panel.collapsed #simulator-header-settings,\n #simulator-panel.collapsed #simulator-toggle {\n display: none;\n }\n #simulator-panel.collapsed #simulator-title {\n cursor: pointer;\n }\n #simulator-panel.collapsed #simulator-timer {\n margin-left: 8px;\n }\n #simulator-body {\n display: flex;\n flex-direction: row;\n }\n #simulator-panel.collapsed #simulator-body {\n display: none;\n }\n #simulator-tabs {\n display: flex;\n flex-direction: column;\n background: var(--sim-bg);\n padding: 4px;\n gap: 2px;\n border-right: 2px solid var(--sim-border);\n }\n .simulator-tab {\n padding: 2px 3px;\n background: var(--sim-bg-alt);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text-dim);\n cursor: pointer;\n font: inherit;\n text-transform: uppercase;\n font-size: 9px;\n font-weight: bold;\n letter-spacing: 0.5px;\n min-height: 0;\n }\n .simulator-tab:hover {\n color: var(--sim-text);\n background: var(--sim-border);\n }\n .simulator-tab.active {\n color: var(--sim-panel);\n background: var(--sim-accent);\n border-color: var(--sim-accent);\n }\n .simulator-tab {\n position: relative;\n }\n .simulator-tab-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n background: var(--sim-blue);\n color: #fff;\n font-size: 8px;\n font-weight: bold;\n border-radius: 7px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 1px 2px rgba(0,0,0,0.3);\n line-height: 1;\n text-align: center;\n box-sizing: border-box;\n }\n .simulator-tab-badge:empty {\n display: none;\n }\n #simulator-content {\n padding: 6px;\n background: var(--sim-panel);\n height: 500px;\n flex: 1;\n overflow-y: auto;\n }\n #simulator-content.hidden {\n display: none;\n }\n #simulator-content::-webkit-scrollbar {\n width: 8px;\n }\n #simulator-content::-webkit-scrollbar-track {\n background: var(--sim-bg);\n }\n #simulator-content::-webkit-scrollbar-thumb {\n background: var(--sim-border);\n border-radius: 2px;\n }\n .simulator-btn {\n padding: 4px 8px;\n background: var(--sim-bg-alt);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: inherit;\n font-size: 10px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 1px;\n cursor: pointer;\n }\n .simulator-btn:hover {\n background: var(--sim-border);\n border-color: var(--sim-border-light);\n }\n .simulator-btn:active {\n transform: translate(1px, 1px);\n }\n .simulator-btn.primary {\n background: var(--sim-accent);\n border-color: var(--sim-accent);\n color: var(--sim-panel);\n }\n .simulator-btn.primary:hover {\n background: var(--sim-accent-hover);\n border-color: var(--sim-accent-hover);\n }\n .simulator-btn.danger {\n background: var(--sim-error);\n border-color: var(--sim-error);\n color: #fff;\n }\n .simulator-btn.danger:hover {\n background: #ef4444;\n border-color: #ef4444;\n }\n .simulator-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n transform: none;\n }\n .simulator-btn.subtle {\n background: transparent;\n border-color: transparent;\n color: var(--sim-text-dim);\n }\n .simulator-btn.subtle:hover:not(:disabled) {\n background: var(--sim-bg-alt);\n border-color: var(--sim-border);\n color: var(--sim-text);\n }\n #simulator-status {\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 4px 0 6px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n #simulator-status .indicator {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n background: var(--sim-text-dim);\n }\n #simulator-status .indicator.ready {\n background: var(--sim-success);\n box-shadow: 0 0 6px var(--sim-success);\n }\n #simulator-status .indicator.waiting {\n background: var(--sim-warning);\n box-shadow: 0 0 6px var(--sim-warning);\n }\n #simulator-status .indicator.paused {\n background: var(--sim-error);\n box-shadow: 0 0 6px var(--sim-error);\n }\n .simulator-row {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n .simulator-row .simulator-btn {\n flex: 1;\n }\n .simulator-divider {\n height: 2px;\n background: var(--sim-border);\n margin: 8px 0;\n }\n .simulator-field {\n margin-bottom: 6px;\n }\n .simulator-select {\n width: 100%;\n padding: 4px 6px;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: inherit;\n font-size: 10px;\n cursor: pointer;\n }\n .simulator-select:focus {\n outline: none;\n border-color: var(--sim-accent);\n }\n .simulator-select option {\n background: var(--sim-bg);\n color: var(--sim-text);\n }\n .simulator-fixtures {\n margin-bottom: 8px;\n padding-bottom: 8px;\n border-bottom: 2px solid var(--sim-border);\n }\n .simulator-label {\n display: block;\n color: var(--sim-accent);\n text-transform: uppercase;\n font-size: 9px;\n font-weight: bold;\n letter-spacing: 1px;\n margin-bottom: 4px;\n }\n .simulator-textarea {\n width: 100%;\n min-height: 40px;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: 10px/1.4 \"Menlo\", monospace;\n padding: 4px;\n resize: none;\n box-sizing: border-box;\n overflow-y: auto;\n }\n .simulator-textarea.auto-resize {\n resize: none;\n overflow-y: auto;\n }\n .simulator-textarea:focus {\n outline: none;\n border-color: var(--sim-accent);\n }\n .simulator-input {\n width: 100%;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: 10px/1.4 \"Menlo\", monospace;\n padding: 4px 6px;\n box-sizing: border-box;\n }\n .simulator-input:focus {\n outline: none;\n border-color: var(--sim-accent);\n }\n .simulator-input::placeholder {\n color: var(--sim-text-dim);\n }\n .simulator-tab-content {\n display: none;\n }\n .simulator-tab-content.active {\n display: block;\n }\n .msgs-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .msgs-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n #simulator-msgs-log {\n display: flex;\n flex-direction: column;\n gap: 2px;\n flex: 1;\n overflow-y: auto;\n }\n .simulator-msg {\n padding: 3px 4px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n font-size: 10px;\n }\n .simulator-msg.out {\n border-left: 2px solid var(--sim-accent);\n }\n .simulator-msg.in {\n border-left: 2px solid var(--sim-blue);\n }\n .simulator-msg-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 2px;\n }\n .simulator-msg-type {\n font-weight: bold;\n color: var(--sim-text);\n }\n .simulator-msg.out .simulator-msg-type {\n color: var(--sim-accent);\n }\n .simulator-msg.in .simulator-msg-type {\n color: var(--sim-blue);\n }\n .simulator-msg-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n }\n .simulator-msg-data {\n color: var(--sim-text-dim);\n font-size: 9px;\n white-space: pre-wrap;\n word-break: break-all;\n max-height: 60px;\n overflow: hidden;\n cursor: pointer;\n border-radius: 2px;\n padding: 2px 4px;\n background: var(--sim-bg-alt);\n }\n .simulator-msg-data:hover {\n background: var(--sim-border);\n }\n .simulator-msg.expanded {\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n .simulator-msg.expanded .simulator-msg-data {\n max-height: none;\n flex: 1;\n overflow-y: auto;\n }\n .simulator-empty {\n color: var(--sim-text-dim);\n font-size: 10px;\n text-align: center;\n padding: 20px;\n }\n .simulator-value {\n color: var(--sim-text);\n font-size: 10px;\n padding: 4px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n }\n .simulator-deeds {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .simulator-deed {\n display: flex;\n justify-content: space-between;\n padding: 3px 4px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n font-size: 10px;\n }\n .simulator-deed-name {\n color: var(--sim-text);\n }\n .simulator-deed-value {\n color: var(--sim-accent);\n font-weight: bold;\n }\n .thumb-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .thumb-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n #simulator-thumb-preview {\n background: var(--sim-thumb-bg, transparent);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n aspect-ratio: 1;\n width: 100%;\n box-sizing: border-box;\n }\n #simulator-thumb-preview svg {\n width: 100%;\n height: 100%;\n max-width: 100%;\n max-height: 100%;\n }\n #simulator-thumb-fn {\n font-size: 10px;\n color: var(--sim-text-dim);\n margin-top: 4px;\n }\n .theme-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .theme-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n .simulator-themes {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .simulator-theme-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n cursor: pointer;\n }\n .simulator-theme-item:hover {\n border-color: var(--sim-border-light);\n }\n .simulator-theme-item.selected {\n border-color: var(--sim-accent);\n background: var(--sim-bg-alt);\n }\n .simulator-theme-preview {\n width: 48px;\n height: 32px;\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n grid-template-rows: repeat(2, 1fr);\n gap: 1px;\n border-radius: 2px;\n overflow: hidden;\n flex-shrink: 0;\n }\n .simulator-theme-preview-cell {\n width: 100%;\n height: 100%;\n }\n .simulator-theme-name {\n font-size: 10px;\n color: var(--sim-text);\n flex: 1;\n }\n .simulator-theme-type {\n font-size: 9px;\n color: var(--sim-text-dim);\n text-transform: uppercase;\n }\n /* Auth tab styles */\n .simulator-section {\n margin-bottom: 12px;\n }\n .simulator-section-title {\n color: var(--sim-accent);\n text-transform: uppercase;\n font-size: 10px;\n font-weight: bold;\n letter-spacing: 1px;\n margin-bottom: 6px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--sim-border);\n }\n .auth-header-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .auth-status {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 4px 0;\n }\n .auth-status.authenticated {\n color: var(--sim-success);\n }\n .simulator-btn.small {\n padding: 2px 6px;\n font-size: 9px;\n }\n .simulator-btn.tiny {\n padding: 3px 2px;\n font-size: 8px;\n border-width: 1px;\n line-height: 1;\n min-height: 0;\n height: auto;\n }\n .auth-title-row {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .auth-title-row > span:first-child {\n flex: 1;\n }\n .auth-title-row .simulator-btn.active {\n background: var(--sim-accent);\n color: black;\n }\n .auth-description {\n font-size: 10px;\n color: var(--sim-text-dim);\n margin: 6px 0;\n line-height: 1.4;\n }\n .auth-user-info {\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n margin-bottom: 8px;\n }\n .auth-user-info div {\n margin-bottom: 4px;\n }\n .auth-user-info div:last-child {\n margin-bottom: 0;\n }\n .auth-user-info code {\n color: var(--sim-text);\n background: var(--sim-bg-alt);\n padding: 1px 4px;\n border-radius: 2px;\n }\n .auth-warning {\n color: var(--sim-warning);\n font-style: italic;\n }\n .auth-error {\n margin-top: 8px;\n }\n .auth-error .error,\n .auth-api-result .error {\n color: var(--sim-error);\n font-size: 10px;\n padding: 6px;\n background: rgba(248, 113, 113, 0.1);\n border: 1px solid var(--sim-error);\n border-radius: 2px;\n }\n .auth-api-result {\n margin-top: 8px;\n }\n .auth-api-result pre {\n font-size: 9px;\n color: var(--sim-text);\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n padding: 6px;\n margin: 0;\n white-space: pre-wrap;\n word-break: break-all;\n max-height: 150px;\n overflow-y: auto;\n }\n .auth-api-result .loading {\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n }\n /* Data view styles */\n .data-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .data-subtabs {\n display: flex;\n gap: 2px;\n margin-bottom: 8px;\n flex-shrink: 0;\n }\n .data-subtab {\n padding: 4px 10px;\n background: var(--sim-bg-alt);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text-dim);\n cursor: pointer;\n font: inherit;\n font-size: 9px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .data-subtab:hover {\n color: var(--sim-text);\n background: var(--sim-border);\n }\n .data-subtab.active {\n color: var(--sim-panel);\n background: var(--sim-accent);\n border-color: var(--sim-accent);\n }\n .data-subtab-content {\n display: none;\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n }\n .data-subtab-content.active {\n display: flex;\n flex-direction: column;\n }\n /* History tab styles */\n .history-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n .history-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .history-item {\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n display: flex;\n flex-direction: row;\n gap: 8px;\n }\n .history-item:hover {\n border-color: var(--sim-border-light);\n }\n .history-item-thumb {\n width: 80px;\n height: 80px;\n flex-shrink: 0;\n background: var(--history-thumb-bg, var(--sim-bg-alt));\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n .history-item-thumb svg {\n width: 100%;\n height: 100%;\n }\n .history-item-thumb:empty {\n display: none;\n }\n .history-item-content {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .history-item-header {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .history-item-num {\n color: var(--sim-accent);\n font-weight: bold;\n font-size: 9px;\n }\n .history-item-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n flex: 1;\n }\n .history-item-preview {\n font-size: 9px;\n color: var(--sim-text);\n word-break: break-all;\n line-height: 1.3;\n background: var(--sim-bg-alt);\n padding: 3px 4px;\n border-radius: 2px;\n flex: 1;\n overflow: hidden;\n }\n /* Saves tab styles */\n .save-new {\n display: flex;\n gap: 4px;\n margin-bottom: 8px;\n flex-shrink: 0;\n }\n .save-new .simulator-input {\n flex: 1;\n }\n .saves-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .save-item {\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n }\n .save-item:hover {\n border-color: var(--sim-border-light);\n }\n .save-item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 4px;\n }\n .save-item-name {\n color: var(--sim-text);\n font-weight: bold;\n font-size: 10px;\n }\n .save-item-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n }\n .save-item-actions {\n display: flex;\n gap: 4px;\n }\n /* Features view styles */\n .features-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .features-slug-input {\n display: flex;\n gap: 4px;\n margin-bottom: 8px;\n }\n .features-slug-input .simulator-input {\n flex: 1;\n }\n .features-content {\n flex: 1;\n overflow-y: auto;\n }\n .features-loading {\n color: var(--sim-text-dim);\n font-size: 10px;\n text-align: center;\n padding: 20px;\n }\n .features-error {\n color: var(--sim-error);\n font-size: 10px;\n padding: 8px;\n background: rgba(248, 113, 113, 0.1);\n border: 1px solid var(--sim-error);\n border-radius: 2px;\n }\n .features-empty {\n color: var(--sim-text-dim);\n font-size: 10px;\n text-align: center;\n padding: 20px;\n }\n .features-auth-required {\n color: var(--sim-text-dim);\n font-size: 10px;\n padding: 12px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n line-height: 1.5;\n }\n .features-auth-required p {\n margin: 0 0 8px 0;\n }\n .features-auth-required p:last-child {\n margin-bottom: 0;\n }\n .features-game-name {\n color: var(--sim-accent);\n font-size: 11px;\n font-weight: bold;\n margin-bottom: 8px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--sim-border);\n }\n .feature-group {\n margin-bottom: 12px;\n }\n .feature-group-title {\n color: var(--sim-text);\n font-size: 10px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n padding-bottom: 2px;\n border-bottom: 1px solid var(--sim-border);\n }\n .feature-group-items {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .feature-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .feature-item:hover {\n border-color: var(--sim-border-light);\n background: var(--sim-bg-alt);\n }\n .feature-item.updating {\n opacity: 0.5;\n pointer-events: none;\n }\n .feature-status {\n font-size: 14px;\n width: 18px;\n text-align: center;\n flex-shrink: 0;\n }\n .feature-item.enabled .feature-status {\n color: var(--sim-success);\n }\n .feature-item.disabled .feature-status {\n color: var(--sim-error);\n opacity: 0.5;\n }\n .feature-title {\n font-size: 10px;\n color: var(--sim-text);\n flex: 1;\n }\n .feature-item.disabled .feature-title {\n color: var(--sim-text-dim);\n }\n /* Checkpoints view styles */\n .checkpoints-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .checkpoints-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n .checkpoints-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .checkpoint-item {\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-left: 3px solid var(--sim-accent);\n border-radius: 2px;\n }\n .checkpoint-item:hover {\n border-color: var(--sim-border-light);\n border-left-color: var(--sim-accent);\n }\n .checkpoint-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n .checkpoint-item .simulator-deeds {\n margin-top: 4px;\n }\n .checkpoint-name {\n color: var(--sim-accent);\n font-weight: bold;\n font-size: 10px;\n }\n .checkpoint-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n }\n`\n","import type { Theme } from \"./types\"\n\nexport const lightTheme: Theme = {\n name: \"Puzzmo (light)\",\n type: \"light\" as \"light\" | \"dark\",\n\n /** The bright Pink */\n key: \"#FFAAAC\",\n /** The color to draw on a key */\n keyFG: \"#000000\",\n /** A bolder version of the bright pink */\n keyStrong: \"#F7868B\",\n /** A lighter version of the bright pink */\n keyLight: \"#FFD2D3\",\n /** The bright Pink */\n g_key: \"#FFAAAC\",\n\n /** A secondary color for the brand, in Puzzmo's case this is the puzzmo yellow */\n subBrand: \"#FFC000\",\n /** Foreground color for the secondary */\n subBrandFG: \"#000000\",\n\n /** The blue you traditionally see around a user avatar and selection */\n player: \"#5DBAFC\",\n /** The color render text above a players */\n playerFG: \"#000000\",\n /** A lighter variant on the user's blue */\n playerLight: \"#9EDDFF\",\n /** Alt color, this one is an earthy green in the main theme */\n alt1: \"#98B389\",\n /** Alt color 2, this one is a faded yellow which looks quite like the puzmo yellow */\n alt2: \"#FAC16C\",\n /** Alt color 3, this one is a cute purple */\n alt3: \"#D298FF\",\n\n /** The foreground body text color */\n fg: \"#000000\",\n /** The red for showing errors */\n error: \"#FF3C3C\",\n\n /** Stays dark regardless of being in light/dark mode */\n alwaysDark: \"#1B1D29\",\n /** Stays light regardless of being in light/dark mode */\n alwaysLight: \"#FFFFFF\",\n\n /** The 'tile' usually */\n g_bg: \"#FFFFFF\",\n /** The 'tile' alternative in checkered patterns */\n g_bgAlt: \"#EBEBEB\",\n /** Sorta useful for content layers on the existing bg, a darker version */\n g_bgDark: \"#D6D6D6\",\n /** When _the text_ is on a light background, this is basically black */\n g_textDark: \"#1B1B28\",\n /** When _the text_ is dark background, this is white */\n g_textLight: \"#FFFFFF\",\n /** For voids in a game, this is black */\n g_blank: \"#000000\",\n /** For when a tile is not yet solved, this is a lighter grey */\n g_unsolved: \"#C2C2C2\",\n /** Border for a game frame, for example the crossword edge */\n g_outline: \"#1B1D29\",\n\n /** The light grey you see as the background for most pages */\n a_bg: \"#F2F2F2\",\n /** The darker grey which usually is seen as borders in the background */\n a_bgAlt: \"#ECECEC\",\n /** The yellow shade of the puzmonaut */\n a_puzmo: \"#FFC000\",\n /** Header colors */\n a_headerText: \"#000000\",\n /** When tabulating results, the background color for an even table row */\n a_table: \"#F9F9F9\",\n /** When tabulating results, the background color for an odd table row */\n a_tableAlt: \"#F6F4F4\",\n /** Inline tag bg (??) */\n a_inlineTag: \"#D9D9D9\",\n /** Used to indicate a link */\n a_anchor: \"#1F97EE\",\n /** The background color of the info bar, couldn't think of a semantic name :D */\n a_infoBG: \"#FFFFFF\",\n}\n\nconst darkTheme: Theme = {\n name: \"Puzzmo (dark)\",\n type: \"dark\" as \"light\" | \"dark\",\n key: \"#FFAAAC\",\n keyFG: \"#000000\",\n keyStrong: \"#F7868B\",\n keyLight: \"#FFD2D3\",\n g_key: \"#FFAAAC\",\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n alt1: \"#98B389\",\n alt2: \"#FAC16C\",\n alt3: \"#D298FF\",\n error: \"#FF3C3C\",\n\n fg: \"#ECECEC\",\n alwaysDark: \"#1B1D29\",\n alwaysLight: \"#FFFFFF\",\n\n g_bg: \"#374351\",\n g_bgAlt: \"#333D49\",\n g_bgDark: \"#2C2F33\",\n g_textDark: \"#F2F2F2\",\n g_textLight: \"#20180E\",\n g_blank: \"#000000\",\n g_unsolved: \"#747474\",\n g_outline: \"#646464\",\n\n a_bg: \"#141620\",\n a_bgAlt: \"#202433\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#A1BAD4\",\n a_table: \"#303246\",\n a_tableAlt: \"#393b52\",\n a_inlineTag: \"#ECECEC\",\n a_anchor: \"#1F97EE\",\n a_infoBG: \"#282C3A\",\n}\n\nconst brightWhiteTheme: Theme = {\n name: \"Bright white\",\n type: \"light\" as \"light\" | \"dark\",\n key: \"#67ced2\",\n keyFG: \"#000000\",\n keyStrong: \"#26a0a5\",\n keyLight: \"#ade6e9\",\n g_key: \"#67ced2\",\n\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n alt1: \"#d26767\",\n alt2: \"#fac16c\",\n alt3: \"#7580bd\",\n\n fg: \"#000000\",\n error: \"#FF3C3C\",\n\n alwaysDark: \"#111111\",\n alwaysLight: \"#FFFFFF\",\n\n g_bg: \"#f6f6f6\",\n g_bgAlt: \"#F4F4F4\",\n g_bgDark: \"#D6D6D6\",\n g_textDark: \"#1B1B28\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#000000\",\n g_unsolved: \"#C2C2C2\",\n g_outline: \"#1B1D29\",\n\n a_bg: \"#FFFFFF\",\n a_bgAlt: \"#f8fbfc\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#000000\",\n a_table: \"#EDEDED\",\n a_tableAlt: \"#DBDBDB\",\n a_inlineTag: \"#D9D9D9\",\n a_anchor: \"#1F97EE\",\n a_infoBG: \"#f6f6f6\",\n}\n\nconst newsPaperDarkTheme: Theme = {\n name: \"News paper dark\",\n type: \"dark\" as \"light\" | \"dark\",\n key: \"#FFAAAC\",\n keyFG: \"#000000\",\n keyStrong: \"#F7868B\",\n keyLight: \"#FFD2D3\",\n g_key: \"#FFAAAC\",\n\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n alt1: \"#98B389\",\n alt2: \"#FAC16C\",\n alt3: \"#D298FF\",\n error: \"#FF3C3C\",\n\n fg: \"#ECECEC\",\n alwaysDark: \"#000000\",\n alwaysLight: \"#FFFFFF\",\n\n g_bg: \"#374351\",\n g_bgAlt: \"#333D49\",\n g_bgDark: \"#2C2F33\",\n g_textDark: \"#F2F2F2\",\n g_textLight: \"#20180E\",\n g_blank: \"#000000\",\n g_unsolved: \"#747474\",\n g_outline: \"#646464\",\n\n a_bg: \"#000000\",\n a_bgAlt: \"#202433\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#A1BAD4\",\n a_table: \"#303246\",\n a_tableAlt: \"#393b52\",\n a_inlineTag: \"#ECECEC\",\n a_anchor: \"#1F97EE\",\n a_infoBG: \"#000000\",\n}\n\nexport const themes: Theme[] = [\n lightTheme,\n darkTheme,\n {\n name: \"Foshay\",\n type: \"light\" as \"light\" | \"dark\",\n key: \"#98B389\",\n keyFG: \"#313540\",\n keyStrong: \"#87BD69\",\n keyLight: \"#809B77\",\n g_key: \"#98B389\",\n\n player: \"#3EC0E5\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n playerFG: \"#292B35\",\n playerLight: \"#A8B5D6\",\n alt1: \"#D87DA4\",\n alt2: \"#E3A54F\",\n alt3: \"#D298FF\",\n error: \"#FF3C3C\",\n\n fg: \"#292B35\",\n alwaysDark: \"#292B35\",\n alwaysLight: \"#D1DBF2\",\n\n g_bg: \"#949EBA\",\n g_bgAlt: \"#8891AB\",\n g_bgDark: \"#7B839B\",\n g_textDark: \"#292B35\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#292B35\",\n g_unsolved: \"#B6B7BA\",\n g_outline: \"#676F83\",\n\n a_bg: \"#828CA3\",\n a_bgAlt: \"#7F889F\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#292B35\",\n a_table: \"#7A8399\",\n a_tableAlt: \"#767E94\",\n a_inlineTag: \"#ECECEC\",\n a_anchor: \"#56C9E9\",\n a_infoBG: \"#767F95\",\n },\n brightWhiteTheme,\n {\n name: \"Submersible\",\n type: \"dark\" as \"light\" | \"dark\",\n key: \"#CD6DC6\",\n keyFG: \"#031698\",\n keyStrong: \"#FF7ABC\",\n keyLight: \"#B467CB\",\n g_key: \"#CD6DC6\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n player: \"#4AB1D1\",\n playerFG: \"#071D47\",\n playerLight: \"#3C99D7\",\n alt1: \"#8BA964\",\n alt2: \"#C69A58\",\n alt3: \"#7644B5\",\n fg: \"#FFEBFF\",\n error: \"#FF3C3C\",\n alwaysDark: \"#031698\",\n alwaysLight: \"#FFEBFF\",\n g_bg: \"#043BED\",\n g_bgAlt: \"#0A31CC\",\n g_bgDark: \"#0E2DA8\",\n g_textDark: \"#031698\",\n g_textLight: \"#FFEBFF\",\n g_blank: \"#031698\",\n g_unsolved: \"#3662F1\",\n g_outline: \"#031698\",\n a_bg: \"#043BED\",\n a_bgAlt: \"#0A31CC\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#FFEBFF\",\n a_table: \"#043BED\",\n a_tableAlt: \"#0A31CC\",\n a_inlineTag: \"#FF7ABC\",\n a_anchor: \"#FF7ABC\",\n a_infoBG: \"#0A31CC\",\n },\n {\n name: \"Hot Dog (beta)\",\n type: \"light\" as \"light\" | \"dark\",\n\n key: \"#FFFF00\",\n keyFG: \"#000000\",\n keyStrong: \"#FFE600\",\n keyLight: \"#FFEC44\",\n g_key: \"#FFFF00\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n player: \"#FF7A00\",\n playerFG: \"#000000\",\n playerLight: \"#FFA450\",\n alt1: \"#3ABC5E\",\n alt2: \"#5C3ABC\",\n alt3: \"#BC3AAF\",\n fg: \"#000000\",\n error: \"#FF3C3C\",\n alwaysDark: \"#1B1D29\",\n alwaysLight: \"#FFFFFF\",\n g_bg: \"#EBFF00\",\n g_bgAlt: \"#EBEBEB\",\n g_bgDark: \"#D6D6D6\",\n g_textDark: \"#1B1B28\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#000000\",\n g_unsolved: \"#C2C2C2\",\n g_outline: \"#1B1D29\",\n a_bg: \"#FF0000\",\n a_bgAlt: \"#E50000\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#FFFFFF\",\n a_table: \"#E50000\",\n a_tableAlt: \"#FF2525\",\n a_inlineTag: \"#000000\",\n a_anchor: \"#000000\",\n a_infoBG: \"#C6C6C6\",\n },\n {\n name: \"Outlook Hayesy (beta)\",\n type: \"light\" as \"light\" | \"dark\",\n key: \"#DAB98C\",\n keyFG: \"#000000\",\n keyStrong: \"#B99368\",\n keyLight: \"#DAB98C\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n g_key: \"#DAB98C\",\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n alt1: \"#5EC386\",\n alt2: \"#5E93C3\",\n alt3: \"#C35E5E\",\n fg: \"#000000\",\n error: \"#FF3C3C\",\n alwaysDark: \"#5E390F\",\n alwaysLight: \"#FFF3E4\",\n g_bg: \"#FFFFFF\",\n g_bgAlt: \"#EBEBEB\",\n g_bgDark: \"#D6D6D6\",\n g_textDark: \"#1B1B28\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#000000\",\n g_unsolved: \"#C2C2C2\",\n g_outline: \"#1B1D29\",\n\n a_bg: \"#FEF5E8\",\n a_bgAlt: \"#ffffff\",\n a_puzmo: \"#FAC7CE\",\n a_headerText: \"#000000\",\n a_table: \"#FAE8D3\",\n a_tableAlt: \"#EED7BC\",\n a_inlineTag: \"#D9D9D9\",\n a_anchor: \"#B99368\",\n a_infoBG: \"#FFFFFF\",\n },\n {\n name: \"Console (beta)\",\n type: \"dark\" as \"light\" | \"dark\",\n\n key: \"#957df9\",\n keyFG: \"#000000\",\n\n keyStrong: \"#590FF5\",\n keyLight: \"#590FF5\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n g_key: \"#FFFF00\",\n player: \"#ffffff\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n alt1: \"#590ff5\",\n alt2: \"#00e200\",\n alt3: \"#ff8a02\",\n\n fg: \"#00e200\",\n error: \"#FF3C3C\",\n\n alwaysDark: \"#1B1D29\",\n alwaysLight: \"#00e200\",\n\n g_bg: \"#101428\",\n g_bgAlt: \"#101428\",\n g_bgDark: \"#00e200\",\n g_textDark: \"#00e200\",\n g_textLight: \"#590ff5\",\n g_blank: \"#00e200\",\n g_unsolved: \"#0000ff\",\n g_outline: \"#00e200\",\n\n a_bg: \"#000000\",\n a_bgAlt: \"#112211\",\n a_puzmo: \"#ffffff\",\n a_headerText: \"#00e200\",\n a_table: \"#000000\",\n a_tableAlt: \"#002200\",\n a_inlineTag: \"#D9D9D9\",\n a_anchor: \"#957df9\",\n a_infoBG: \"#001900\",\n },\n]\n\nexport const defaultLightTheme = lightTheme\nexport const defaultDarkTheme = darkTheme\nexport const newsPaperLightTheme = brightWhiteTheme\n\nexport const modifiedNewsPaperTheme = (type: \"light\" | \"dark\", hexes: string[] | readonly string[], partnerSlug: string) => {\n const baseTheme = (type === \"light\" ? newsPaperLightTheme : newsPaperDarkTheme) as Theme\n\n const theme = {\n ...baseTheme,\n name: baseTheme.name + partnerSlug ? `-(${partnerSlug})` : \"\",\n a_puzmo: hexes[0] || baseTheme.a_puzmo,\n key: hexes[1] || baseTheme.key,\n keyLight: hexes[2] || baseTheme.keyLight,\n keyStrong: hexes[3] || baseTheme.keyStrong,\n alt1: hexes[4] || baseTheme.alt1,\n alt2: hexes[5] || baseTheme.alt2,\n alt3: hexes[6] || baseTheme.alt3,\n keyFG: hexes[7] || baseTheme.player,\n player: hexes[8] || baseTheme.player,\n playerLight: hexes[9] || baseTheme.playerLight,\n playerFG: hexes[10] || baseTheme.playerFG,\n subBrand: hexes[11] || baseTheme.subBrand,\n subBrandFG: hexes[12] || baseTheme.subBrandFG,\n g_key: hexes[13] || baseTheme.subBrandFG,\n a_infoBG: type === \"light\" ? baseTheme.alwaysLight : baseTheme.alwaysDark,\n }\n return theme\n}\n\nexport type PuzmoTheme = typeof lightTheme\n\n// Useless JS until we get 'satisfies' in TS 4.9\nconst validThemes = (_theme: readonly Theme[]): _theme is PuzmoTheme[] => true\nvalidThemes(themes)\n","import type { Theme, ThumbnailConfig } from \"../types\"\nimport { themes } from \"../themes\"\nimport type { SimulatorConfig, SimulatorState, TabName } from \"./types\"\n\nconst STORAGE_KEYS = {\n collapsed: \"simulator-collapsed\",\n tab: \"simulator-tab\",\n theme: \"simulator-theme\",\n fixtureCategory: \"simulator-fixture-category\",\n fixturePuzzle: \"simulator-fixture-puzzle\",\n renderHost: \"simulator-render-host\",\n renderContext: \"simulator-render-context\",\n} as const\n\nfunction getStoredTheme(): Theme {\n const storedThemeName = localStorage.getItem(STORAGE_KEYS.theme)\n if (storedThemeName) {\n const found = themes.find((t) => t.name === storedThemeName)\n if (found) return found\n }\n return themes[0] // Default to first theme (light)\n}\n\nfunction getStoredTab(validTabIds: string[]): TabName {\n const storedTab = localStorage.getItem(STORAGE_KEYS.tab)\n if (storedTab && validTabIds.includes(storedTab)) {\n return storedTab\n }\n return validTabIds[0] ?? \"ctrl\"\n}\n\nfunction getStoredCollapsed(configDefault: boolean): boolean {\n const storedCollapsed = localStorage.getItem(STORAGE_KEYS.collapsed)\n if (storedCollapsed !== null) {\n return storedCollapsed === \"true\"\n }\n return configDefault\n}\n\nfunction getStoredRenderHost(): ThumbnailConfig[\"renderHost\"] {\n const stored = localStorage.getItem(STORAGE_KEYS.renderHost)\n if (stored && [\"game\", \"app\", \"opengraph\"].includes(stored)) {\n return stored as ThumbnailConfig[\"renderHost\"]\n }\n return \"game\"\n}\n\nfunction getStoredRenderContext(): ThumbnailConfig[\"renderContext\"] {\n const stored = localStorage.getItem(STORAGE_KEYS.renderContext)\n if (stored && [\"preview\", \"share\", \"completed\", \"timeline\"].includes(stored)) {\n return stored as ThumbnailConfig[\"renderContext\"]\n }\n return undefined\n}\n\nexport function createInitialState(config: SimulatorConfig, fixtureCategories: string[], validTabIds: string[]): SimulatorState {\n const storedFixtureCategory = localStorage.getItem(STORAGE_KEYS.fixtureCategory)\n const storedFixturePuzzle = localStorage.getItem(STORAGE_KEYS.fixturePuzzle)\n\n return {\n isCollapsed: getStoredCollapsed(config.collapsed ?? true),\n isPaused: false,\n hasStarted: false,\n activeTab: getStoredTab(validTabIds),\n puzzleData: null,\n originalPuzzle: \"\",\n currentInputStr: \"\",\n completionData: null,\n selectedTheme: getStoredTheme(),\n selectedCategory:\n storedFixtureCategory && fixtureCategories.includes(storedFixtureCategory) ? storedFixtureCategory : (fixtureCategories[0] ?? null),\n selectedPuzzle: storedFixturePuzzle ?? null,\n renderHost: getStoredRenderHost(),\n renderContext: getStoredRenderContext(),\n }\n}\n\nexport function persistCollapsed(collapsed: boolean): void {\n localStorage.setItem(STORAGE_KEYS.collapsed, String(collapsed))\n}\n\nexport function persistTab(tab: TabName): void {\n localStorage.setItem(STORAGE_KEYS.tab, tab)\n}\n\nexport function persistTheme(themeName: string): void {\n localStorage.setItem(STORAGE_KEYS.theme, themeName)\n}\n\nexport function persistFixtureCategory(category: string): void {\n localStorage.setItem(STORAGE_KEYS.fixtureCategory, category)\n}\n\nexport function persistFixturePuzzle(puzzle: string): void {\n localStorage.setItem(STORAGE_KEYS.fixturePuzzle, puzzle)\n}\n\nexport function clearFixturePuzzle(): void {\n localStorage.removeItem(STORAGE_KEYS.fixturePuzzle)\n}\n\nexport function persistRenderHost(host: ThumbnailConfig[\"renderHost\"]): void {\n if (host) {\n localStorage.setItem(STORAGE_KEYS.renderHost, host)\n } else {\n localStorage.removeItem(STORAGE_KEYS.renderHost)\n }\n}\n\nexport function persistRenderContext(context: ThumbnailConfig[\"renderContext\"]): void {\n if (context) {\n localStorage.setItem(STORAGE_KEYS.renderContext, context)\n } else {\n localStorage.removeItem(STORAGE_KEYS.renderContext)\n }\n}\n","import type { MessagesReceived } from \"../types\"\nimport type { MessageLogEntry } from \"./types\"\n\nexport interface MessageLogger {\n log: (type: string, data: any, direction: \"in\" | \"out\") => void\n clear: () => void\n getLog: () => MessageLogEntry[]\n}\n\nexport function createMessageLogger(onLog?: (entry: MessageLogEntry) => void): MessageLogger {\n const messageLog: MessageLogEntry[] = []\n\n return {\n log(type: string, data: any, direction: \"in\" | \"out\") {\n const time = new Date().toLocaleTimeString(\"en-US\", {\n hour12: false,\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n })\n const entry: MessageLogEntry = { type, data, time, direction }\n messageLog.push(entry)\n\n // Limit log size\n if (messageLog.length > 100) messageLog.shift()\n\n onLog?.(entry)\n },\n clear() {\n messageLog.length = 0\n },\n getLog() {\n return messageLog\n },\n }\n}\n\nexport function sendToGame(type: keyof MessagesReceived, data: any, logger?: MessageLogger): void {\n console.log(\"Simulator sending:\", type, data)\n logger?.log(type, data, \"out\")\n window.postMessage({ type, data }, \"*\")\n}\n\nexport type MessageHandler = (type: string, data: any) => void\n\nexport function createMessageListener(handler: MessageHandler, logger?: MessageLogger): () => void {\n const listener = (event: MessageEvent) => {\n if (!event?.data?.type) return\n\n const type = event.data.type\n // Handle both { data } and { json } formats from host\n const data = event.data.data ?? event.data.json ?? {}\n\n // Skip logging for high-frequency timer messages\n const skipLogTypes = [\"TIMER_TICK\", \"TIMER_SYNC\"]\n if (!skipLogTypes.includes(type)) {\n logger?.log(type, data, \"in\")\n }\n\n handler(type, data)\n }\n\n window.addEventListener(\"message\", listener)\n\n // Return cleanup function\n return () => window.removeEventListener(\"message\", listener)\n}\n","import type { FixtureImports } from \"./types\"\n\n/**\n * Parse fixture imports into a structured format: { category: { filename: data } }\n */\nexport function parseFixtures(fixtures: FixtureImports): Map<string, Map<string, any>> {\n const result = new Map<string, Map<string, any>>()\n\n console.log(\"Simulator: Parsing fixtures\", Object.keys(fixtures))\n\n for (const [path, module] of Object.entries(fixtures)) {\n // Extract category and filename from path like \"./fixtures/puzzles/3x3/puzzle_1.json\"\n const parts = path.split(\"/\")\n const filename = parts.pop()?.replace(\".json\", \"\") ?? \"\"\n const category = parts.pop() ?? \"default\"\n\n if (!result.has(category)) {\n result.set(category, new Map())\n }\n\n const data = (module as any).default ?? module\n result.get(category)!.set(filename, data)\n }\n\n // Sort puzzles naturally (puzzle_1, puzzle_2, ... puzzle_10, puzzle_11)\n result.forEach((puzzles, category) => {\n const sorted = new Map<string, any>(\n Array.from(puzzles.entries()).sort((a, b) => {\n const numA = parseInt(a[0].match(/\\d+/)?.[0] ?? \"0\")\n const numB = parseInt(b[0].match(/\\d+/)?.[0] ?? \"0\")\n return numA - numB\n }),\n )\n result.set(category, sorted)\n })\n\n return result\n}\n\n/**\n * Render fixture selector HTML\n */\nexport function renderFixtureSelector(categories: string[], selectedCategory: string | null): string {\n if (categories.length === 0) return \"\"\n\n return `\n <div class=\"simulator-fixtures\">\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Category</label>\n <select class=\"simulator-select\" id=\"simulator-fixture-category\">\n ${categories.map((cat) => `<option value=\"${cat}\" ${cat === selectedCategory ? \"selected\" : \"\"}>${cat}</option>`).join(\"\")}\n </select>\n </div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Puzzle</label>\n <select class=\"simulator-select\" id=\"simulator-fixture-puzzle\"></select>\n </div>\n </div>\n `\n}\n\n/**\n * Update puzzle select options based on selected category\n */\nexport function updatePuzzleOptions(\n puzzleSelect: HTMLSelectElement,\n fixtures: Map<string, Map<string, any>>,\n category: string,\n selectedPuzzle: string | null,\n): string | null {\n const puzzles = fixtures.get(category)\n if (!puzzles) return null\n\n const puzzleNames = Array.from(puzzles.keys())\n\n // If stored puzzle exists in this category, use it; otherwise use first\n let actualSelected = selectedPuzzle\n if (!selectedPuzzle || !puzzles.has(selectedPuzzle)) {\n actualSelected = puzzleNames[0] ?? null\n }\n\n puzzleSelect.innerHTML = puzzleNames\n .map((name) => `<option value=\"${name}\" ${name === actualSelected ? \"selected\" : \"\"}>${name}</option>`)\n .join(\"\")\n\n return actualSelected\n}\n\n/**\n * Get puzzle data from fixtures\n */\nexport function getFixturePuzzle(fixtures: Map<string, Map<string, any>>, category: string | null, puzzle: string | null): any | null {\n if (!category || !puzzle) return null\n return fixtures.get(category)?.get(puzzle) ?? null\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\nimport { renderFixtureSelector, updatePuzzleOptions, getFixturePuzzle } from \"../fixtures\"\nimport { persistFixtureCategory, persistFixturePuzzle, clearFixturePuzzle } from \"../state\"\n\nexport function createCtrlView(): SimulatorView {\n return {\n id: \"ctrl\",\n label: \"Ctrl\",\n\n render() {\n return `\n <div id=\"simulator-fixtures-container\"></div>\n <div id=\"simulator-status\">\n <span class=\"indicator waiting\"></span>\n <span class=\"text\">Waiting for READY...</span>\n </div>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn primary\" id=\"simulator-start\" disabled>Start</button>\n <button class=\"simulator-btn\" id=\"simulator-pause\" disabled>Pause</button>\n </div>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn danger\" id=\"simulator-retry\" disabled>Retry</button>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const startBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-start\")\n const pauseBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-pause\")\n const retryBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-retry\")\n const fixturesContainer = ctx.getElement<HTMLElement>(\"#simulator-fixtures-container\")\n\n console.log(\"Simulator: CtrlView.bind called\", {\n hasFixtures: !!ctx.fixtures,\n categories: ctx.fixtureCategories,\n selectedCategory: ctx.state.selectedCategory,\n selectedPuzzle: ctx.state.selectedPuzzle,\n })\n\n // Set up fixtures UI if fixtures are provided\n if (ctx.fixtures && ctx.fixtureCategories.length > 0 && fixturesContainer) {\n // Clear any existing content (in case bind is called multiple times)\n fixturesContainer.innerHTML = renderFixtureSelector(ctx.fixtureCategories, ctx.state.selectedCategory)\n\n const categorySelect = ctx.getElement<HTMLSelectElement>(\"#simulator-fixture-category\")\n const puzzleSelect = ctx.getElement<HTMLSelectElement>(\"#simulator-fixture-puzzle\")\n\n if (categorySelect && puzzleSelect && ctx.state.selectedCategory) {\n // Update puzzle options\n ctx.state.selectedPuzzle = updatePuzzleOptions(\n puzzleSelect,\n ctx.fixtures,\n ctx.state.selectedCategory,\n ctx.state.selectedPuzzle,\n )\n\n // Load initial puzzle data\n const puzzleData = getFixturePuzzle(ctx.fixtures, ctx.state.selectedCategory, ctx.state.selectedPuzzle)\n console.log(\"Simulator: Loading fixture puzzle\", {\n category: ctx.state.selectedCategory,\n puzzle: ctx.state.selectedPuzzle,\n hasPuzzleData: !!puzzleData,\n })\n if (puzzleData) {\n ctx.state.puzzleData = puzzleData\n ctx.state.originalPuzzle = JSON.stringify(puzzleData, null, 2)\n if (ctx.state.selectedCategory) persistFixtureCategory(ctx.state.selectedCategory)\n if (ctx.state.selectedPuzzle) persistFixturePuzzle(ctx.state.selectedPuzzle)\n }\n\n // Category change handler\n categorySelect.addEventListener(\"change\", () => {\n console.log(\"Simulator: Category changed, reloading...\", categorySelect.value)\n persistFixtureCategory(categorySelect.value)\n clearFixturePuzzle()\n window.location.reload()\n })\n\n // Puzzle change handler\n puzzleSelect.addEventListener(\"change\", () => {\n console.log(\"Simulator: Puzzle changed, reloading...\", puzzleSelect.value)\n persistFixturePuzzle(puzzleSelect.value)\n window.location.reload()\n })\n }\n }\n\n // Button handlers\n startBtn?.addEventListener(\"click\", () => {\n if (ctx.state.isPaused) {\n ctx.sendToGame(\"RESUME_GAME\", {})\n ctx.state.isPaused = false\n if (pauseBtn) pauseBtn.textContent = \"Pause\"\n ctx.updateStatus(\"Running\", \"ready\")\n } else {\n ctx.sendToGame(\"START_GAME\", undefined)\n ctx.state.hasStarted = true\n if (pauseBtn) pauseBtn.disabled = false\n if (startBtn) startBtn.textContent = \"Resume\"\n ctx.updateStatus(\"Running\", \"ready\")\n }\n })\n\n pauseBtn?.addEventListener(\"click\", () => {\n if (ctx.state.isPaused) {\n ctx.sendToGame(\"RESUME_GAME\", {})\n ctx.state.isPaused = false\n pauseBtn.textContent = \"Pause\"\n ctx.updateStatus(\"Running\", \"ready\")\n } else {\n ctx.sendToGame(\"PAUSE_GAME\", {})\n ctx.state.isPaused = true\n pauseBtn.textContent = \"Resume\"\n ctx.updateStatus(\"Paused\", \"paused\")\n }\n })\n\n retryBtn?.addEventListener(\"click\", () => {\n ctx.sendToGame(\"RETRY_PUZZLE\", {})\n ctx.state.hasStarted = false\n ctx.state.isPaused = false\n if (pauseBtn) {\n pauseBtn.disabled = true\n pauseBtn.textContent = \"Pause\"\n }\n if (startBtn) startBtn.textContent = \"Start\"\n ctx.updateStatus(\"Ready to retry\", \"ready\")\n })\n },\n\n onMessage(type: string, _data: any, ctx: SimulatorContext) {\n const startBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-start\")\n const pauseBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-pause\")\n const retryBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-retry\")\n\n if (type === \"READY_GAME_LOADED\") {\n if (startBtn) startBtn.disabled = false\n if (retryBtn) retryBtn.disabled = false\n ctx.updateStatus(\"Ready\", \"ready\")\n }\n\n if (type === \"GAME_COMPLETED\") {\n ctx.state.hasStarted = false\n if (pauseBtn) pauseBtn.disabled = true\n if (startBtn) startBtn.disabled = true\n ctx.updateStatus(\"Completed!\", \"ready\")\n }\n },\n }\n}\n","import type { AppBundle, ThumbnailConfig } from \"../../types\"\nimport type { SimulatorContext, SimulatorView } from \"../types\"\n\n// Storage key for saved states (global across all puzzles)\nconst SAVED_STATES_KEY = \"simulator-saved-states\"\n\n/**\n * Find thumbnail function on globalThis (looks for functions ending in \"Thumbnail\")\n */\nfunction findThumbnailFn(): { name: string; fn: AppBundle[\"renderThumbnail\"] } | null {\n const globalObj = globalThis as Record<string, unknown>\n for (const key of Object.keys(globalObj)) {\n if (key.endsWith(\"Thumbnail\") && typeof globalObj[key] === \"function\") {\n return { name: key, fn: globalObj[key] as AppBundle[\"renderThumbnail\"] }\n }\n }\n return null\n}\n\n/**\n * Generate a small thumbnail SVG for a given input string\n */\nfunction generateMiniThumbnail(ctx: SimulatorContext, inputStr: string): string {\n const thumbFn = findThumbnailFn()\n if (!thumbFn) return \"\"\n\n try {\n const puzzleStr = ctx.state.puzzleData ? JSON.stringify(ctx.state.puzzleData) : \"\"\n const thumbnailConfig: ThumbnailConfig = {\n viewerIsOwner: true,\n theme: ctx.state.selectedTheme,\n strict: true,\n renderHost: \"game\",\n }\n return thumbFn.fn(puzzleStr, inputStr, thumbnailConfig)\n } catch {\n return \"\"\n }\n}\n\n// Input string history entry\ninterface HistoryEntry {\n value: string\n timestamp: number\n}\n\n// Saved state entry (includes puzzle since you can save any puzzle + input combination)\ninterface SavedState {\n name: string\n puzzleStr: string\n inputStr: string\n timestamp: number\n}\n\n// Module-level state for history (persists across tab switches)\nlet inputHistory: HistoryEntry[] = []\n\n// Helper to auto-resize a textarea based on content\nfunction autoResizeTextarea(textarea: HTMLTextAreaElement, maxRows: number = 8) {\n const lineHeight = 14 // Based on font size 10px with line-height 1.4\n const padding = 8 // 4px padding top + bottom\n const border = 4 // 2px border top + bottom\n const minHeight = lineHeight * 2 + padding + border // Minimum 2 rows\n const maxHeight = lineHeight * maxRows + padding + border\n\n textarea.style.height = \"auto\"\n const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight)\n textarea.style.height = `${newHeight}px`\n}\n\nexport function createDataView(): SimulatorView {\n // Track original values for change detection\n let originalPuzzleValue = \"\"\n let originalInputValue = \"\"\n\n return {\n id: \"data\",\n label: \"Data\",\n\n render() {\n return `\n <div class=\"data-view-container\">\n <div class=\"data-subtabs\">\n <button class=\"data-subtab active\" data-subtab=\"edit\">Edit</button>\n <button class=\"data-subtab\" data-subtab=\"history\">History</button>\n <button class=\"data-subtab\" data-subtab=\"saves\">Saves</button>\n </div>\n\n <div class=\"data-subtab-content active\" id=\"data-subtab-edit\">\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Puzzle String</label>\n <textarea class=\"simulator-textarea auto-resize\" id=\"simulator-puzzle\" placeholder=\"Loading...\"></textarea>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn subtle\" id=\"simulator-puzzle-reset\">Reset</button>\n <button class=\"simulator-btn primary\" id=\"simulator-puzzle-apply\" disabled>Apply</button>\n </div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Input String</label>\n <textarea class=\"simulator-textarea auto-resize\" id=\"simulator-input\" placeholder=\"No input yet...\"></textarea>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn subtle\" id=\"simulator-input-reset\">Reset</button>\n <button class=\"simulator-btn primary\" id=\"simulator-input-apply\" disabled>Apply</button>\n </div>\n </div>\n </div>\n\n <div class=\"data-subtab-content\" id=\"data-subtab-history\">\n <div class=\"history-header\">\n <span class=\"simulator-label\">Input String Timeline</span>\n <button class=\"simulator-btn tiny\" id=\"simulator-history-clear\">Clear</button>\n </div>\n <div class=\"history-list\" id=\"simulator-history-list\">\n <div class=\"simulator-empty\">No history yet</div>\n </div>\n </div>\n\n <div class=\"data-subtab-content\" id=\"data-subtab-saves\">\n <div class=\"save-new\">\n <input type=\"text\" class=\"simulator-input\" id=\"simulator-save-name\" placeholder=\"Save name...\" />\n <button class=\"simulator-btn primary\" id=\"simulator-save-btn\">Save</button>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"saves-list\" id=\"simulator-saves-list\">\n <div class=\"simulator-empty\">No saved states</div>\n </div>\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n // Sub-tab switching\n const subtabs = ctx.getElement<HTMLElement>(\".data-subtabs\")\n subtabs?.querySelectorAll(\".data-subtab\").forEach((tab) => {\n tab.addEventListener(\"click\", () => {\n const subtabName = tab.getAttribute(\"data-subtab\")\n if (!subtabName) return\n\n // Update active tab button\n subtabs.querySelectorAll(\".data-subtab\").forEach((t) => t.classList.remove(\"active\"))\n tab.classList.add(\"active\")\n\n // Update active content\n const container = ctx.getElement<HTMLElement>(\".data-view-container\")\n container?.querySelectorAll(\".data-subtab-content\").forEach((content) => {\n content.classList.toggle(\"active\", content.id === `data-subtab-${subtabName}`)\n })\n\n // Refresh content when switching\n if (subtabName === \"edit\") {\n refreshEditTab(ctx)\n } else if (subtabName === \"history\") {\n renderHistory(ctx)\n } else if (subtabName === \"saves\") {\n renderSaves(ctx)\n }\n })\n })\n\n // Edit tab elements\n const puzzleTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n const puzzleResetBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-puzzle-reset\")\n const puzzleApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-puzzle-apply\")\n const inputResetBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-reset\")\n const inputApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-apply\")\n const startBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-start\")\n const pauseBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-pause\")\n\n // Initialize puzzle textarea - defer to allow CtrlView to set state first\n setTimeout(() => {\n if (puzzleTextarea && ctx.state.originalPuzzle) {\n puzzleTextarea.value = ctx.state.originalPuzzle\n originalPuzzleValue = ctx.state.originalPuzzle\n autoResizeTextarea(puzzleTextarea)\n }\n\n // Initialize input textarea\n if (inputTextarea && ctx.state.currentInputStr) {\n inputTextarea.value = ctx.state.currentInputStr\n originalInputValue = ctx.state.currentInputStr\n autoResizeTextarea(inputTextarea)\n }\n }, 0)\n\n // Auto-resize and change detection for puzzle\n puzzleTextarea?.addEventListener(\"input\", () => {\n autoResizeTextarea(puzzleTextarea)\n const hasChanges = puzzleTextarea.value !== originalPuzzleValue\n if (puzzleApplyBtn) puzzleApplyBtn.disabled = !hasChanges\n })\n\n // Auto-resize and change detection for input\n inputTextarea?.addEventListener(\"input\", () => {\n autoResizeTextarea(inputTextarea)\n const hasChanges = inputTextarea.value !== originalInputValue\n if (inputApplyBtn) inputApplyBtn.disabled = !hasChanges\n })\n\n // Puzzle reset\n puzzleResetBtn?.addEventListener(\"click\", () => {\n if (puzzleTextarea) {\n puzzleTextarea.value = ctx.state.originalPuzzle\n originalPuzzleValue = ctx.state.originalPuzzle\n autoResizeTextarea(puzzleTextarea)\n if (puzzleApplyBtn) puzzleApplyBtn.disabled = true\n }\n })\n\n // Puzzle apply\n puzzleApplyBtn?.addEventListener(\"click\", () => {\n if (!puzzleTextarea) return\n try {\n const newPuzzle = JSON.parse(puzzleTextarea.value)\n ctx.state.puzzleData = newPuzzle\n ctx.state.originalPuzzle = puzzleTextarea.value\n originalPuzzleValue = puzzleTextarea.value\n // Trigger a retry with the new puzzle\n ctx.sendToGame(\"RETRY_PUZZLE\", {})\n ctx.state.hasStarted = false\n ctx.state.isPaused = false\n if (pauseBtn) {\n pauseBtn.disabled = true\n pauseBtn.textContent = \"Pause\"\n }\n if (startBtn) startBtn.textContent = \"Start\"\n ctx.updateStatus(\"Puzzle updated\", \"ready\")\n puzzleApplyBtn.disabled = true\n } catch (e) {\n console.error(\"Simulator: Invalid puzzle JSON\", e)\n ctx.updateStatus(\"Invalid JSON\", \"paused\")\n }\n })\n\n // Input reset\n inputResetBtn?.addEventListener(\"click\", () => {\n if (inputTextarea) {\n inputTextarea.value = originalInputValue\n autoResizeTextarea(inputTextarea)\n if (inputApplyBtn) inputApplyBtn.disabled = true\n }\n })\n\n // Input apply\n inputApplyBtn?.addEventListener(\"click\", () => {\n if (inputTextarea) {\n ctx.state.currentInputStr = inputTextarea.value\n originalInputValue = inputTextarea.value\n console.log(\"Simulator: Input string updated (game restart required to apply)\")\n ctx.updateStatus(\"Input stored\", \"ready\")\n inputApplyBtn.disabled = true\n }\n })\n\n // History tab\n const historyClearBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-history-clear\")\n historyClearBtn?.addEventListener(\"click\", () => {\n inputHistory = []\n renderHistory(ctx)\n })\n\n // Saves tab\n const saveNameInput = ctx.getElement<HTMLInputElement>(\"#simulator-save-name\")\n const saveBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-save-btn\")\n\n saveBtn?.addEventListener(\"click\", () => {\n if (!saveNameInput || !saveNameInput.value.trim()) return\n\n const savedStates = getSavedStates()\n const newState: SavedState = {\n name: saveNameInput.value.trim(),\n puzzleStr: ctx.state.originalPuzzle,\n inputStr: ctx.state.currentInputStr,\n timestamp: Date.now(),\n }\n savedStates.push(newState)\n localStorage.setItem(SAVED_STATES_KEY, JSON.stringify(savedStates))\n saveNameInput.value = \"\"\n renderSaves(ctx)\n })\n\n // Initial render of saves\n renderSaves(ctx)\n },\n\n onActivate(ctx: SimulatorContext) {\n // Update original values for change detection\n originalPuzzleValue = ctx.state.originalPuzzle\n originalInputValue = ctx.state.currentInputStr\n\n // Refresh edit tab content\n refreshEditTab(ctx)\n\n // Refresh history\n renderHistory(ctx)\n },\n\n onMessage(type: string, data: any, ctx: SimulatorContext) {\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n const inputApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-apply\")\n\n // boardState can be at data.boardState (legacy) or data.input.boardState (current SDK format)\n const boardState = data?.input?.boardState ?? data?.boardState\n if (type === \"UPLOAD_NEW_GAME_STATE\" && boardState) {\n ctx.state.currentInputStr = boardState\n\n // Add to history if different from last entry\n if (inputHistory.length === 0 || inputHistory[inputHistory.length - 1].value !== boardState) {\n inputHistory.push({ value: boardState, timestamp: Date.now() })\n // Keep history reasonable (last 100 entries)\n if (inputHistory.length > 100) {\n inputHistory = inputHistory.slice(-100)\n }\n\n // Update history view live if it's currently visible\n const historyTab = ctx.getElement<HTMLElement>(\"#data-subtab-history\")\n if (historyTab?.classList.contains(\"active\")) {\n renderHistory(ctx)\n }\n }\n\n if (inputTextarea) {\n inputTextarea.value = ctx.state.currentInputStr\n originalInputValue = ctx.state.currentInputStr\n autoResizeTextarea(inputTextarea)\n if (inputApplyBtn) inputApplyBtn.disabled = true\n }\n }\n },\n }\n}\n\n// Helper to get saved states from localStorage\nfunction getSavedStates(): SavedState[] {\n try {\n const stored = localStorage.getItem(SAVED_STATES_KEY)\n return stored ? JSON.parse(stored) : []\n } catch {\n return []\n }\n}\n\n// Render the history list\nfunction renderHistory(ctx: SimulatorContext) {\n const historyList = ctx.getElement<HTMLElement>(\"#simulator-history-list\")\n if (!historyList) return\n\n // Set theme background for thumbnails\n historyList.style.setProperty(\"--history-thumb-bg\", ctx.state.selectedTheme.a_bg)\n\n if (inputHistory.length === 0) {\n historyList.innerHTML = '<div class=\"simulator-empty\">No history yet</div>'\n return\n }\n\n // Show most recent first\n const reversedHistory = [...inputHistory].reverse()\n historyList.innerHTML = reversedHistory\n .map((entry, idx) => {\n const realIdx = inputHistory.length - 1 - idx\n const time = new Date(entry.timestamp).toLocaleTimeString()\n const preview = entry.value.length > 60 ? entry.value.slice(0, 60) + \"...\" : entry.value\n const thumbnail = generateMiniThumbnail(ctx, entry.value)\n return `\n <div class=\"history-item\" data-history-idx=\"${realIdx}\">\n <div class=\"history-item-thumb\">${thumbnail}</div>\n <div class=\"history-item-content\">\n <div class=\"history-item-header\">\n <span class=\"history-item-num\">#${realIdx + 1}</span>\n <span class=\"history-item-time\">${time}</span>\n <button class=\"simulator-btn tiny history-restore-btn\" data-history-idx=\"${realIdx}\">Restore</button>\n </div>\n <div class=\"history-item-preview\">${escapeHtml(preview)}</div>\n </div>\n </div>\n `\n })\n .join(\"\")\n\n // Bind restore buttons\n historyList.querySelectorAll(\".history-restore-btn\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const idx = parseInt((e.target as HTMLElement).getAttribute(\"data-history-idx\") || \"0\")\n const entry = inputHistory[idx]\n if (entry) {\n ctx.state.currentInputStr = entry.value\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n if (inputTextarea) {\n inputTextarea.value = entry.value\n autoResizeTextarea(inputTextarea)\n }\n ctx.updateStatus(\"Input restored from history\", \"ready\")\n\n // Switch to edit tab\n const editTab = ctx.getElement<HTMLElement>('.data-subtab[data-subtab=\"edit\"]')\n editTab?.click()\n }\n })\n })\n}\n\n// Render the saves list\nfunction renderSaves(ctx: SimulatorContext) {\n const savesList = ctx.getElement<HTMLElement>(\"#simulator-saves-list\")\n if (!savesList) return\n\n const savedStates = getSavedStates()\n\n if (savedStates.length === 0) {\n savesList.innerHTML = '<div class=\"simulator-empty\">No saved states</div>'\n return\n }\n\n // Show most recent first\n const reversedSaves = [...savedStates].reverse()\n savesList.innerHTML = reversedSaves\n .map((state, idx) => {\n const realIdx = savedStates.length - 1 - idx\n const date = new Date(state.timestamp).toLocaleDateString()\n const time = new Date(state.timestamp).toLocaleTimeString()\n return `\n <div class=\"save-item\">\n <div class=\"save-item-header\">\n <span class=\"save-item-name\">${escapeHtml(state.name)}</span>\n <span class=\"save-item-time\">${date} ${time}</span>\n </div>\n <div class=\"save-item-actions\">\n <button class=\"simulator-btn tiny save-load-btn\" data-save-idx=\"${realIdx}\">Load</button>\n <button class=\"simulator-btn tiny danger save-delete-btn\" data-save-idx=\"${realIdx}\">Delete</button>\n </div>\n </div>\n `\n })\n .join(\"\")\n\n // Bind load buttons\n savesList.querySelectorAll(\".save-load-btn\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const idx = parseInt((e.target as HTMLElement).getAttribute(\"data-save-idx\") || \"0\")\n const state = savedStates[idx]\n if (state) {\n // Update puzzle\n try {\n ctx.state.puzzleData = JSON.parse(state.puzzleStr)\n ctx.state.originalPuzzle = state.puzzleStr\n } catch {\n // If invalid JSON, just set the string\n ctx.state.originalPuzzle = state.puzzleStr\n }\n\n // Update input\n ctx.state.currentInputStr = state.inputStr\n\n // Refresh UI\n const puzzleTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n if (puzzleTextarea) {\n puzzleTextarea.value = state.puzzleStr\n autoResizeTextarea(puzzleTextarea)\n }\n if (inputTextarea) {\n inputTextarea.value = state.inputStr\n autoResizeTextarea(inputTextarea)\n }\n\n ctx.updateStatus(`Loaded: ${state.name}`, \"ready\")\n\n // Switch to edit tab\n const editTab = ctx.getElement<HTMLElement>('.data-subtab[data-subtab=\"edit\"]')\n editTab?.click()\n }\n })\n })\n\n // Bind delete buttons\n savesList.querySelectorAll(\".save-delete-btn\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const idx = parseInt((e.target as HTMLElement).getAttribute(\"data-save-idx\") || \"0\")\n const updatedStates = savedStates.filter((_, i) => i !== idx)\n localStorage.setItem(SAVED_STATES_KEY, JSON.stringify(updatedStates))\n renderSaves(ctx)\n })\n })\n}\n\n// Helper to escape HTML\nfunction escapeHtml(str: string): string {\n return str.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n}\n\n// Helper to refresh the edit tab content\nfunction refreshEditTab(ctx: SimulatorContext) {\n const puzzleTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n const puzzleApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-puzzle-apply\")\n const inputApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-apply\")\n\n if (puzzleTextarea && ctx.state.originalPuzzle) {\n puzzleTextarea.value = ctx.state.originalPuzzle\n autoResizeTextarea(puzzleTextarea)\n if (puzzleApplyBtn) puzzleApplyBtn.disabled = true\n }\n\n if (inputTextarea) {\n inputTextarea.value = ctx.state.currentInputStr\n autoResizeTextarea(inputTextarea)\n if (inputApplyBtn) inputApplyBtn.disabled = true\n }\n}\n","import type { SimulatorContext, SimulatorView, MessageLogEntry } from \"../types\"\n\nexport interface MsgsViewExtended extends SimulatorView {\n addLogEntry: (entry: MessageLogEntry, ctx: SimulatorContext) => void\n}\n\n// Helper to escape HTML\nfunction escapeHtml(str: string): string {\n return str.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n}\n\nexport function createMsgsView(): MsgsViewExtended {\n let messageCount = 0\n\n return {\n id: \"msgs\",\n label: \"Msgs\",\n\n render() {\n return `\n <div class=\"msgs-view-container\">\n <div class=\"msgs-header\">\n <span class=\"simulator-label\">Message Log</span>\n <button class=\"simulator-btn tiny\" id=\"simulator-msgs-clear\">Clear</button>\n </div>\n <div id=\"simulator-msgs-log\"></div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const clearBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-msgs-clear\")\n\n clearBtn?.addEventListener(\"click\", () => {\n const logEl = ctx.getElement<HTMLElement>(\"#simulator-msgs-log\")\n if (logEl) {\n logEl.innerHTML = \"\"\n }\n messageCount = 0\n ctx.updateBadge(\"msgs\", 0)\n })\n },\n\n addLogEntry(entry: MessageLogEntry, ctx: SimulatorContext) {\n const logEl = ctx.getElement<HTMLElement>(\"#simulator-msgs-log\")\n if (!logEl) return\n\n messageCount++\n ctx.updateBadge(\"msgs\", messageCount)\n\n const msgEl = document.createElement(\"div\")\n msgEl.className = `simulator-msg ${entry.direction}`\n const dataStr = entry.data !== undefined ? JSON.stringify(entry.data, null, 2) : \"\"\n msgEl.innerHTML = `\n <div class=\"simulator-msg-header\">\n <span class=\"simulator-msg-type\">${entry.direction === \"out\" ? \"\\u2192\" : \"\\u2190\"} ${entry.type}</span>\n <span class=\"simulator-msg-time\">${entry.time}</span>\n </div>\n ${dataStr ? `<div class=\"simulator-msg-data\">${escapeHtml(dataStr)}</div>` : \"\"}\n `\n\n // Add click handler to expand/collapse message data\n const dataEl = msgEl.querySelector(\".simulator-msg-data\")\n if (dataEl) {\n dataEl.addEventListener(\"click\", () => {\n // Collapse any other expanded messages first\n logEl.querySelectorAll(\".simulator-msg.expanded\").forEach((el) => {\n if (el !== msgEl) el.classList.remove(\"expanded\")\n })\n // Toggle this message\n msgEl.classList.toggle(\"expanded\")\n })\n }\n\n logEl.insertBefore(msgEl, logEl.firstChild)\n\n // Limit displayed messages\n while (logEl.children.length > 50) {\n logEl.removeChild(logEl.lastChild!)\n }\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\n\nexport function createDoneView(): SimulatorView {\n return {\n id: \"done\",\n label: \"Done\",\n\n render() {\n return `\n <div id=\"simulator-done-empty\" class=\"simulator-empty\">Game not completed yet</div>\n <div id=\"simulator-done-content\" style=\"display:none;\">\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Completion Text</label>\n <div id=\"simulator-done-text\" class=\"simulator-value\"></div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Board State</label>\n <textarea class=\"simulator-textarea\" id=\"simulator-done-input\" rows=\"2\" readonly></textarea>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Deeds</label>\n <div id=\"simulator-done-deeds\" class=\"simulator-deeds\"></div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Raw Data</label>\n <textarea class=\"simulator-textarea\" id=\"simulator-done-raw\" rows=\"4\" readonly></textarea>\n </div>\n </div>\n `\n },\n\n bind(_ctx: SimulatorContext) {\n // No event bindings needed for done view (read-only)\n },\n\n onMessage(type: string, data: any, ctx: SimulatorContext) {\n if (type !== \"GAME_COMPLETED\") return\n\n ctx.state.completionData = data\n\n const emptyEl = ctx.getElement<HTMLElement>(\"#simulator-done-empty\")\n const contentEl = ctx.getElement<HTMLElement>(\"#simulator-done-content\")\n const textEl = ctx.getElement<HTMLElement>(\"#simulator-done-text\")\n const inputEl = ctx.getElement<HTMLTextAreaElement>(\"#simulator-done-input\")\n const deedsEl = ctx.getElement<HTMLElement>(\"#simulator-done-deeds\")\n const rawEl = ctx.getElement<HTMLTextAreaElement>(\"#simulator-done-raw\")\n\n if (emptyEl) emptyEl.style.display = \"none\"\n if (contentEl) contentEl.style.display = \"block\"\n\n // Completion text (from input if available)\n const completionText = data.input?.completionText || data.completionText || \"(no completion text)\"\n if (textEl) textEl.textContent = completionText\n\n // Board state (from input.boardState)\n const boardState = data.input?.boardState || data.boardState || ctx.state.currentInputStr || \"\"\n if (inputEl) inputEl.value = boardState\n\n // Deeds (from config.deeds, each deed has id/value)\n if (deedsEl) {\n deedsEl.innerHTML = \"\"\n const deeds = data.config?.deeds || data.deeds\n if (deeds && Array.isArray(deeds)) {\n deeds.forEach((deed: { id: string; value: any }) => {\n const deedEl = document.createElement(\"div\")\n deedEl.className = \"simulator-deed\"\n deedEl.innerHTML = `\n <span class=\"simulator-deed-name\">${deed.id}</span>\n <span class=\"simulator-deed-value\">${deed.value}</span>\n `\n deedsEl.appendChild(deedEl)\n })\n } else {\n deedsEl.innerHTML = '<div class=\"simulator-empty\">No deeds</div>'\n }\n }\n\n // Raw data\n if (rawEl) rawEl.value = JSON.stringify(data, null, 2)\n\n // Switch to done tab\n ctx.setCollapsed(false)\n ctx.switchTab(\"done\")\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\nimport type { AugmentationConfig } from \"../../types\"\n\ninterface CheckpointEntry {\n checkpointName: string\n augConfig: AugmentationConfig\n time: string\n}\n\nexport function createCheckpointsView(): SimulatorView {\n let checkpoints: CheckpointEntry[] = []\n\n const formatTime = () => {\n const now = new Date()\n return now.toLocaleTimeString(\"en-US\", { hour12: false })\n }\n\n const renderCheckpoint = (checkpoint: CheckpointEntry): string => {\n const { checkpointName, augConfig, time } = checkpoint\n const deeds = augConfig?.deeds as Array<{ id: string; value: any }> | undefined\n\n return `\n <div class=\"checkpoint-item\">\n <div class=\"checkpoint-header\">\n <span class=\"checkpoint-name\">${checkpointName}</span>\n <span class=\"checkpoint-time\">${time}</span>\n </div>\n ${\n deeds && deeds.length > 0\n ? `<div class=\"simulator-deeds\">\n ${deeds.map((deed) => `<div class=\"simulator-deed\"><span class=\"simulator-deed-name\">${deed.id}</span><span class=\"simulator-deed-value\">${deed.value}</span></div>`).join(\"\")}\n </div>`\n : \"\"\n }\n </div>\n `\n }\n\n const renderList = (): string => {\n if (checkpoints.length === 0) {\n return '<div class=\"simulator-empty\">No checkpoints received yet</div>'\n }\n return checkpoints.map((cp) => renderCheckpoint(cp)).join(\"\")\n }\n\n return {\n id: \"checkpoints\",\n label: \"Chkpt\",\n\n render() {\n return `\n <div class=\"checkpoints-view-container\">\n <div class=\"checkpoints-header\">\n <span class=\"simulator-label\">Checkpoints</span>\n <button class=\"simulator-btn subtle small\" id=\"simulator-checkpoints-clear\">Clear</button>\n </div>\n <div id=\"simulator-checkpoints-list\" class=\"checkpoints-list\">\n ${renderList()}\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const clearBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-checkpoints-clear\")\n const listEl = ctx.getElement<HTMLElement>(\"#simulator-checkpoints-list\")\n\n clearBtn?.addEventListener(\"click\", () => {\n checkpoints = []\n if (listEl) listEl.innerHTML = renderList()\n ctx.updateBadge(\"checkpoints\", 0)\n })\n },\n\n onMessage(type: string, data: any, ctx: SimulatorContext) {\n if (type !== \"HIT_CHECKPOINT\") return\n\n const entry: CheckpointEntry = {\n checkpointName: data.checkpointName,\n augConfig: data.augConfig,\n time: formatTime(),\n }\n\n checkpoints.push(entry)\n ctx.updateBadge(\"checkpoints\", checkpoints.length)\n\n const listEl = ctx.getElement<HTMLElement>(\"#simulator-checkpoints-list\")\n if (listEl) {\n listEl.innerHTML = renderList()\n // Scroll to bottom to show newest\n listEl.scrollTop = listEl.scrollHeight\n }\n },\n }\n}\n","import type { AppBundle, ThumbnailConfig } from \"../../types\"\nimport type { SimulatorContext, SimulatorView } from \"../types\"\nimport { persistRenderContext, persistRenderHost } from \"../state\"\n\n/**\n * Find thumbnail function on globalThis (looks for functions ending in \"Thumbnail\")\n */\nfunction findThumbnailFn(): { name: string; fn: AppBundle[\"renderThumbnail\"] } | null {\n const globalObj = globalThis as Record<string, unknown>\n for (const key of Object.keys(globalObj)) {\n if (key.endsWith(\"Thumbnail\") && typeof globalObj[key] === \"function\") {\n return { name: key, fn: globalObj[key] as AppBundle[\"renderThumbnail\"] }\n }\n }\n return null\n}\n\nexport interface ThumbViewExtended extends SimulatorView {\n updatePreview: (ctx: SimulatorContext) => void\n}\n\nexport function createThumbView(): ThumbViewExtended {\n const updatePreview = (ctx: SimulatorContext) => {\n const previewEl = ctx.getElement<HTMLElement>(\"#simulator-thumb-preview\")\n const fnEl = ctx.getElement<HTMLElement>(\"#simulator-thumb-fn\")\n\n // Match the page background (theme) behind the thumbnail SVG.\n previewEl?.style.setProperty(\"--sim-thumb-bg\", ctx.state.selectedTheme.g_bg)\n\n const thumbFn = findThumbnailFn()\n if (!thumbFn) {\n if (previewEl) previewEl.innerHTML = '<span class=\"simulator-empty\">No thumbnail function found</span>'\n if (fnEl) fnEl.textContent = \"\"\n return\n }\n\n if (fnEl) fnEl.textContent = `Using: ${thumbFn.name}()`\n\n try {\n const puzzleStr = ctx.state.puzzleData ? JSON.stringify(ctx.state.puzzleData) : \"\"\n\n const thumbnailConfig: ThumbnailConfig = {\n viewerIsOwner: true,\n theme: ctx.state.selectedTheme,\n strict: true,\n renderHost: ctx.state.renderHost,\n renderContext: ctx.state.renderContext,\n }\n\n const svgString = thumbFn.fn(puzzleStr, ctx.state.currentInputStr, thumbnailConfig)\n if (previewEl) previewEl.innerHTML = svgString\n } catch (e) {\n console.error(\"Simulator: Thumbnail error\", e)\n if (previewEl) previewEl.innerHTML = `<span class=\"simulator-empty\">Error: ${e}</span>`\n }\n }\n\n return {\n id: \"thumb\",\n label: \"Thumb\",\n\n render() {\n return `\n <div class=\"thumb-view-container\">\n <div class=\"thumb-header\">\n <span class=\"simulator-label\">Thumbnail Preview</span>\n <button class=\"simulator-btn tiny\" id=\"simulator-thumb-refresh\">Refresh</button>\n </div>\n <div id=\"simulator-thumb-preview\">\n <span class=\"simulator-empty\">No thumbnail function found</span>\n </div>\n <div id=\"simulator-thumb-fn\"></div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Render Host</label>\n <select class=\"simulator-select\" id=\"simulator-render-host-select\">\n <option value=\"game\">game</option>\n <option value=\"app\">app</option>\n <option value=\"opengraph\">opengraph</option>\n </select>\n </div>\n <div class=\"simulator-field\" id=\"simulator-render-context-field\" style=\"display: none;\">\n <label class=\"simulator-label\">Render Context</label>\n <select class=\"simulator-select\" id=\"simulator-render-context-select\">\n <option value=\"preview\">preview</option>\n <option value=\"share\">share</option>\n <option value=\"completed\">completed</option>\n <option value=\"timeline\">timeline</option>\n </select>\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const refreshBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-thumb-refresh\")\n refreshBtn?.addEventListener(\"click\", () => ctx.updateThumbnail())\n\n const renderHostSelect = ctx.getElement<HTMLSelectElement>(\"#simulator-render-host-select\")\n const renderContextSelect = ctx.getElement<HTMLSelectElement>(\"#simulator-render-context-select\")\n const renderContextField = ctx.getElement<HTMLElement>(\"#simulator-render-context-field\")\n\n const updateContextVisibility = () => {\n if (renderContextField) {\n renderContextField.style.display = ctx.state.renderHost === \"opengraph\" ? \"block\" : \"none\"\n }\n }\n\n // Initialize render host select\n if (renderHostSelect) {\n // Default to \"game\" if not set\n if (!ctx.state.renderHost) {\n ctx.state.renderHost = \"game\"\n persistRenderHost(ctx.state.renderHost)\n }\n renderHostSelect.value = ctx.state.renderHost || \"game\"\n\n renderHostSelect.addEventListener(\"change\", () => {\n ctx.state.renderHost = renderHostSelect.value as ThumbnailConfig[\"renderHost\"]\n persistRenderHost(ctx.state.renderHost)\n updateContextVisibility()\n ctx.updateThumbnail()\n })\n }\n\n // Initialize render context select\n if (renderContextSelect) {\n // Default to \"preview\" if not set\n if (!ctx.state.renderContext) {\n ctx.state.renderContext = \"preview\"\n persistRenderContext(ctx.state.renderContext)\n }\n renderContextSelect.value = ctx.state.renderContext || \"preview\"\n\n renderContextSelect.addEventListener(\"change\", () => {\n ctx.state.renderContext = renderContextSelect.value as ThumbnailConfig[\"renderContext\"]\n persistRenderContext(ctx.state.renderContext)\n ctx.updateThumbnail()\n })\n }\n\n updateContextVisibility()\n },\n\n onActivate(ctx: SimulatorContext) {\n // Defer to allow thumbnail function to be registered\n setTimeout(() => ctx.updateThumbnail(), 0)\n },\n\n updatePreview,\n }\n}\n","import type { Theme } from \"../../types\"\nimport { themes } from \"../../themes\"\nimport type { SimulatorContext, SimulatorView } from \"../types\"\nimport { persistTheme } from \"../state\"\n\n/**\n * Generate theme preview HTML showing key colors\n */\nfunction generateThemePreview(theme: Theme): string {\n // Show key, subBrand, player, alt1, alt2, alt3 with g_bg as background\n const colors = [theme.key, theme.subBrand, theme.player, theme.alt1, theme.alt2, theme.alt3]\n const cells = colors.map((c) => `<div class=\"simulator-theme-preview-cell\" style=\"background: ${c}\"></div>`).join(\"\")\n return `<div class=\"simulator-theme-preview\" style=\"background: ${theme.g_bg}\">${cells}</div>`\n}\n\nexport function createThemeView(): SimulatorView {\n return {\n id: \"theme\",\n label: \"Theme\",\n\n render() {\n return `\n <div class=\"theme-view-container\">\n <div class=\"theme-header\">\n <span class=\"simulator-label\">Select Theme</span>\n </div>\n <div id=\"simulator-themes\" class=\"simulator-themes\"></div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const themesEl = ctx.getElement<HTMLElement>(\"#simulator-themes\")\n if (!themesEl) return\n\n themes.forEach((theme) => {\n const themeItem = document.createElement(\"div\")\n themeItem.className = `simulator-theme-item${theme.name === ctx.state.selectedTheme.name ? \" selected\" : \"\"}`\n themeItem.innerHTML = `\n ${generateThemePreview(theme)}\n <div class=\"simulator-theme-name\">${theme.name}</div>\n <div class=\"simulator-theme-type\">${theme.type}</div>\n `\n themeItem.addEventListener(\"click\", () => {\n console.log(\"Simulator: Theme changed, reloading...\", theme.name)\n persistTheme(theme.name)\n window.location.reload()\n })\n themesEl.appendChild(themeItem)\n })\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\n\n// API mode storage\nconst API_MODE_KEY = \"puzzmo_sim_api_mode\"\nconst LOCAL_DEV_URL = \"http://localhost:8911\"\nconst PROD_URL = \"https://api.puzzmo.com\"\n\ntype ApiMode = \"prod\" | \"dev\"\n\nconst getApiMode = (): ApiMode => {\n const stored = localStorage.getItem(API_MODE_KEY)\n return stored === \"dev\" ? \"dev\" : \"prod\"\n}\n\nconst setApiMode = (mode: ApiMode) => {\n localStorage.setItem(API_MODE_KEY, mode)\n}\n\n// Check if local dev server is available\nlet localDevAvailable: boolean | null = null\nconst checkLocalDevAvailable = async (): Promise<boolean> => {\n if (localDevAvailable !== null) return localDevAvailable\n\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 1000)\n\n const response = await fetch(`${LOCAL_DEV_URL}/healthz`, {\n signal: controller.signal,\n })\n clearTimeout(timeoutId)\n\n localDevAvailable = response.ok\n return localDevAvailable\n } catch {\n localDevAvailable = false\n return false\n }\n}\n\n// OAuth configuration\nconst getOAuthConfig = () => {\n const mode = getApiMode()\n const apiURL = mode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n const clientID = \"protosdk:oauthclient\"\n // Use a fixed callback path that's registered with the OAuth server\n const redirectUri = `${window.location.origin}/oauth/callback`\n\n return { apiURL, clientID, redirectUri }\n}\n\n// Generate CSRF state token\nconst generateState = (): string => {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n return Array.from(array, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\")\n}\n\n// Token storage\nconst TOKEN_KEY = \"puzzmo_sim_oauth_token\"\nconst REFRESH_TOKEN_KEY = \"puzzmo_sim_oauth_refresh_token\"\n\nconst storeAccessToken = (token: string) => localStorage.setItem(TOKEN_KEY, token)\nconst storeRefreshToken = (token: string) => localStorage.setItem(REFRESH_TOKEN_KEY, token)\nconst getAccessToken = (): string | null => {\n const token = localStorage.getItem(TOKEN_KEY)\n console.log(\"[AuthView] getAccessToken:\", { TOKEN_KEY, token: token ? `${token.substring(0, 20)}...` : null })\n return token\n}\nconst getRefreshToken = (): string | null => localStorage.getItem(REFRESH_TOKEN_KEY)\nconst clearTokens = () => {\n localStorage.removeItem(TOKEN_KEY)\n localStorage.removeItem(REFRESH_TOKEN_KEY)\n}\n\n// Initiate OAuth login flow\nconst initiateOAuthLogin = () => {\n const config = getOAuthConfig()\n const state = generateState()\n\n sessionStorage.setItem(\"oauth_state\", state)\n // Store the current URL to return to after OAuth callback\n sessionStorage.setItem(\"oauth_return_url\", window.location.href)\n\n const authURL = new URL(`${config.apiURL}/oauth/auth`)\n authURL.searchParams.set(\"client_id\", config.clientID)\n authURL.searchParams.set(\"response_type\", \"code\")\n authURL.searchParams.set(\"redirect_uri\", config.redirectUri)\n authURL.searchParams.set(\"state\", state)\n\n window.location.href = authURL.toString()\n}\n\n// Exchange authorization code for access token\ntype TokenResponse = {\n // Standard OAuth snake_case fields\n access_token?: string\n token_type?: string\n expires_in?: number\n scope?: string\n refresh_token?: string\n // Server returns camelCase\n accessToken?: string\n refreshToken?: string\n}\n\n// Check if token is expired or about to expire (within 5 minutes)\nconst isTokenExpired = (token: string): boolean => {\n try {\n const parts = token.split(\".\")\n if (parts.length !== 3) return true\n const payload = JSON.parse(atob(parts[1]))\n const exp = payload.exp\n if (!exp) return true\n // Consider expired if less than 5 minutes remaining\n return Date.now() >= (exp * 1000) - 5 * 60 * 1000\n } catch {\n return true\n }\n}\n\n// Refresh the access token using the refresh token\nconst refreshAccessToken = async (): Promise<boolean> => {\n const refreshToken = getRefreshToken()\n if (!refreshToken) {\n console.log(\"[AuthView] No refresh token available\")\n return false\n }\n\n const config = getOAuthConfig()\n\n try {\n const params = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientID,\n })\n\n const response = await fetch(`${config.apiURL}/oauth/token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n console.error(\"[AuthView] Failed to refresh token:\", response.statusText)\n return false\n }\n\n const tokenResponse: TokenResponse = await response.json()\n const accessToken = tokenResponse.access_token || tokenResponse.accessToken\n if (!accessToken) {\n console.error(\"[AuthView] No access token in refresh response\")\n return false\n }\n\n storeAccessToken(accessToken)\n // Update refresh token if a new one was provided\n const newRefreshToken = tokenResponse.refresh_token || tokenResponse.refreshToken\n if (newRefreshToken) {\n storeRefreshToken(newRefreshToken)\n }\n\n console.log(\"[AuthView] Successfully refreshed access token\")\n return true\n } catch (error) {\n console.error(\"[AuthView] Error refreshing token:\", error)\n return false\n }\n}\n\nconst exchangeCodeForToken = async (code: string, state: string): Promise<TokenResponse | null> => {\n const config = getOAuthConfig()\n const storedState = sessionStorage.getItem(\"oauth_state\")\n\n if (!storedState || storedState !== state) {\n console.error(\"OAuth state mismatch - possible CSRF attack\")\n return null\n }\n\n sessionStorage.removeItem(\"oauth_state\")\n\n try {\n const params = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: config.clientID,\n redirect_uri: config.redirectUri,\n })\n\n const response = await fetch(`${config.apiURL}/oauth/token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n console.error(\"Failed to exchange code for token:\", response.statusText)\n return null\n }\n\n return await response.json()\n } catch (error) {\n console.error(\"Error exchanging code for token:\", error)\n return null\n }\n}\n\n// Make authenticated API request with automatic token refresh\nconst makeAuthenticatedRequest = async (query: string, variables: Record<string, unknown> = {}) => {\n const config = getOAuthConfig()\n let token = getAccessToken()\n\n if (!token) {\n throw new Error(\"Not authenticated\")\n }\n\n // Check if token is expired and try to refresh\n if (isTokenExpired(token)) {\n console.log(\"[AuthView] Access token expired, attempting refresh...\")\n const refreshed = await refreshAccessToken()\n if (refreshed) {\n token = getAccessToken()\n if (!token) {\n throw new Error(\"Token refresh succeeded but no token available\")\n }\n } else {\n // Refresh failed, clear tokens and require re-login\n clearTokens()\n throw new Error(\"Session expired. Please log in again.\")\n }\n }\n\n const response = await fetch(`${config.apiURL}/graphql`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n \"auth-provider\": \"custom\",\n },\n body: JSON.stringify({ query, variables }),\n })\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.statusText}`)\n }\n\n return response.json()\n}\n\n// Decode JWT to get user info (basic decode, no verification)\nconst decodeJWT = (token: string): { sub?: string; exp?: number; userID?: string } | null => {\n try {\n console.log(\"[AuthView] decodeJWT input:\", token)\n const parts = token.split(\".\")\n console.log(\"[AuthView] JWT parts:\", parts.length)\n if (parts.length !== 3) return null\n const payload = JSON.parse(atob(parts[1]))\n console.log(\"[AuthView] JWT payload:\", payload)\n return payload\n } catch (e) {\n console.error(\"[AuthView] decodeJWT error:\", e)\n return null\n }\n}\n\nexport function createAuthView(): SimulatorView {\n // Track if we've checked for local dev availability\n let devCheckDone = false\n\n return {\n id: \"auth\",\n label: \"Auth\",\n\n render() {\n const token = getAccessToken()\n const isAuthenticated = !!token\n const currentMode = getApiMode()\n const apiURL = currentMode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n\n // Dev toggle button - hidden by default, shown via JS if local dev is available\n const devToggle = `<button class=\"simulator-btn tiny\" id=\"auth-dev-toggle\" style=\"display: none;\">${currentMode === \"dev\" ? \"Using Dev\" : \"Dev\"}</button>`\n\n if (isAuthenticated) {\n const decoded = decodeJWT(token)\n const expiresAt = decoded?.exp ? new Date(decoded.exp * 1000).toLocaleString() : \"Unknown\"\n const hasRefreshToken = !!getRefreshToken()\n const refreshDecoded = hasRefreshToken ? decodeJWT(getRefreshToken()!) : null\n const refreshExpiresAt = refreshDecoded?.exp ? new Date(refreshDecoded.exp * 1000).toLocaleString() : null\n console.log({ decoded })\n\n return `\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title auth-title-row\">\n <span>Puzzmo Authentication</span>\n ${devToggle}\n <button class=\"simulator-btn danger tiny\" id=\"auth-logout\">Logout</button>\n </div>\n <div id=\"auth-status\" class=\"auth-status authenticated\">\n <span class=\"indicator ready\"></span>\n <span>Authenticated</span>\n </div>\n <div id=\"auth-user-info\" class=\"auth-user-info\">\n <div>User ID: <code>${decoded?.userID || decoded?.sub || \"Unknown\"}</code></div>\n <div>API: <code>${apiURL}</code></div>\n <div>Access expires: <code>${expiresAt}</code></div>\n ${hasRefreshToken ? `<div>Refresh expires: <code>${refreshExpiresAt || \"Unknown\"}</code></div>` : '<div class=\"auth-warning\">No refresh token</div>'}\n </div>\n ${hasRefreshToken ? '<button class=\"simulator-btn secondary tiny\" id=\"auth-refresh\">Refresh Now</button>' : \"\"}\n </div>\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title\">Test API Request</div>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn primary\" id=\"auth-test-api\">Fetch Current User</button>\n </div>\n <div id=\"auth-api-result\" class=\"auth-api-result\"></div>\n </div>\n `\n }\n\n return `\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title auth-title-row\">\n <span>Puzzmo Authentication</span>\n ${devToggle}\n </div>\n <div id=\"auth-status\" class=\"auth-status\">\n <span class=\"indicator waiting\"></span>\n <span>Not authenticated</span>\n </div>\n <p class=\"auth-description\">\n Login with your Puzzmo account to make authenticated API requests.\n ${currentMode === \"dev\" ? `<br><strong>Using local dev server:</strong> ${LOCAL_DEV_URL}` : \"\"}\n </p>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn primary\" id=\"auth-login\">Login with Puzzmo</button>\n </div>\n <div id=\"auth-error\" class=\"auth-error\"></div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const loginBtn = ctx.getElement<HTMLButtonElement>(\"#auth-login\")\n const logoutBtn = ctx.getElement<HTMLButtonElement>(\"#auth-logout\")\n const refreshBtn = ctx.getElement<HTMLButtonElement>(\"#auth-refresh\")\n const testApiBtn = ctx.getElement<HTMLButtonElement>(\"#auth-test-api\")\n const apiResult = ctx.getElement<HTMLElement>(\"#auth-api-result\")\n const devToggleBtn = ctx.getElement<HTMLButtonElement>(\"#auth-dev-toggle\")\n\n // Check for local dev availability and show toggle if available\n if (!devCheckDone) {\n devCheckDone = true\n checkLocalDevAvailable().then((available) => {\n if (available && devToggleBtn) {\n devToggleBtn.style.display = \"\"\n // Style based on current mode\n if (getApiMode() === \"dev\") {\n devToggleBtn.classList.add(\"active\")\n }\n }\n })\n } else if (localDevAvailable && devToggleBtn) {\n // Already checked, just show the button\n devToggleBtn.style.display = \"\"\n if (getApiMode() === \"dev\") {\n devToggleBtn.classList.add(\"active\")\n }\n }\n\n // Dev toggle click handler\n devToggleBtn?.addEventListener(\"click\", () => {\n const currentMode = getApiMode()\n const newMode = currentMode === \"dev\" ? \"prod\" : \"dev\"\n setApiMode(newMode)\n // Clear tokens when switching modes since they're for different servers\n clearTokens()\n window.location.reload()\n })\n\n loginBtn?.addEventListener(\"click\", () => {\n initiateOAuthLogin()\n })\n\n logoutBtn?.addEventListener(\"click\", () => {\n clearTokens()\n window.location.reload()\n })\n\n refreshBtn?.addEventListener(\"click\", async () => {\n refreshBtn.disabled = true\n refreshBtn.textContent = \"Refreshing...\"\n const success = await refreshAccessToken()\n if (success) {\n window.location.reload()\n } else {\n refreshBtn.textContent = \"Refresh Failed\"\n refreshBtn.disabled = false\n }\n })\n\n testApiBtn?.addEventListener(\"click\", async () => {\n if (!apiResult) return\n\n apiResult.innerHTML = '<div class=\"loading\">Loading...</div>'\n\n try {\n const result = await makeAuthenticatedRequest(`\n query {\n currentUser {\n id\n username\n usernameID\n name\n }\n }\n `)\n\n if (result.errors) {\n apiResult.innerHTML = `<div class=\"error\">Error: ${result.errors[0]?.message || \"Unknown error\"}</div>`\n } else {\n apiResult.innerHTML = `<pre>${JSON.stringify(result.data, null, 2)}</pre>`\n }\n } catch (error) {\n apiResult.innerHTML = `<div class=\"error\">Error: ${error instanceof Error ? error.message : \"Unknown error\"}</div>`\n }\n })\n },\n\n async onActivate(ctx: SimulatorContext) {\n // Check for OAuth callback parameters\n const urlParams = new URLSearchParams(window.location.search)\n const code = urlParams.get(\"code\")\n const state = urlParams.get(\"state\")\n const error = urlParams.get(\"error\")\n\n if (error) {\n const errorEl = ctx.getElement<HTMLElement>(\"#auth-error\")\n if (errorEl) {\n errorEl.innerHTML = `<div class=\"error\">OAuth error: ${error}</div>`\n }\n // Clean up URL\n window.history.replaceState({}, \"\", window.location.pathname)\n return\n }\n\n if (code && state) {\n const statusEl = ctx.getElement<HTMLElement>(\"#auth-status\")\n if (statusEl) {\n statusEl.innerHTML = '<span class=\"indicator waiting\"></span><span>Exchanging code...</span>'\n }\n\n const tokenResponse = await exchangeCodeForToken(code, state)\n\n // Clean up URL\n window.history.replaceState({}, \"\", window.location.pathname)\n\n if (tokenResponse) {\n const accessToken = tokenResponse.access_token || tokenResponse.accessToken\n if (accessToken) {\n storeAccessToken(accessToken)\n const refreshToken = tokenResponse.refresh_token || tokenResponse.refreshToken\n if (refreshToken) {\n storeRefreshToken(refreshToken)\n }\n }\n window.location.reload()\n } else {\n const errorEl = ctx.getElement<HTMLElement>(\"#auth-error\")\n if (errorEl) {\n errorEl.innerHTML = '<div class=\"error\">Failed to exchange code for token</div>'\n }\n }\n }\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\n\n// API mode storage (shared with AuthView)\nconst API_MODE_KEY = \"puzzmo_sim_api_mode\"\nconst LOCAL_DEV_URL = \"http://localhost:8911\"\nconst PROD_URL = \"https://api.puzzmo.com\"\nconst TOKEN_KEY = \"puzzmo_sim_oauth_token\"\nconst REFRESH_TOKEN_KEY = \"puzzmo_sim_oauth_refresh_token\"\n\ntype ApiMode = \"prod\" | \"dev\"\n\nconst getApiMode = (): ApiMode => {\n const stored = localStorage.getItem(API_MODE_KEY)\n return stored === \"dev\" ? \"dev\" : \"prod\"\n}\n\nconst getAccessToken = (): string | null => localStorage.getItem(TOKEN_KEY)\nconst getRefreshToken = (): string | null => localStorage.getItem(REFRESH_TOKEN_KEY)\n\nconst storeAccessToken = (token: string) => localStorage.setItem(TOKEN_KEY, token)\nconst storeRefreshToken = (token: string) => localStorage.setItem(REFRESH_TOKEN_KEY, token)\n\nconst clearTokens = () => {\n localStorage.removeItem(TOKEN_KEY)\n localStorage.removeItem(REFRESH_TOKEN_KEY)\n}\n\n// Check if token is expired or about to expire (within 5 minutes)\nconst isTokenExpired = (token: string): boolean => {\n try {\n const parts = token.split(\".\")\n if (parts.length !== 3) return true\n const payload = JSON.parse(atob(parts[1]))\n const exp = payload.exp\n if (!exp) return true\n return Date.now() >= exp * 1000 - 5 * 60 * 1000\n } catch {\n return true\n }\n}\n\n// Refresh the access token using the refresh token\nconst refreshAccessToken = async (): Promise<boolean> => {\n const refreshToken = getRefreshToken()\n if (!refreshToken) return false\n\n const mode = getApiMode()\n const apiURL = mode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n\n try {\n const params = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: \"protosdk:oauthclient\",\n })\n\n const response = await fetch(`${apiURL}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: params.toString(),\n })\n\n if (!response.ok) return false\n\n const tokenResponse = await response.json()\n const accessToken = tokenResponse.access_token || tokenResponse.accessToken\n if (!accessToken) return false\n\n storeAccessToken(accessToken)\n const newRefreshToken = tokenResponse.refresh_token || tokenResponse.refreshToken\n if (newRefreshToken) storeRefreshToken(newRefreshToken)\n\n return true\n } catch {\n return false\n }\n}\n\n// Make authenticated API request with automatic token refresh\nconst makeAuthenticatedRequest = async (query: string, variables: Record<string, unknown> = {}) => {\n const mode = getApiMode()\n const apiURL = mode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n let token = getAccessToken()\n\n if (!token) throw new Error(\"Not authenticated\")\n\n if (isTokenExpired(token)) {\n const refreshed = await refreshAccessToken()\n if (refreshed) {\n token = getAccessToken()\n if (!token) throw new Error(\"Token refresh succeeded but no token available\")\n } else {\n clearTokens()\n throw new Error(\"Session expired. Please log in again.\")\n }\n }\n\n const response = await fetch(`${apiURL}/graphql`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n \"auth-provider\": \"custom\",\n },\n body: JSON.stringify({ query, variables }),\n })\n\n if (!response.ok) throw new Error(`API request failed: ${response.statusText}`)\n return response.json()\n}\n\n// Types for game features\ntype GameFeature = {\n featureID: number\n title: string\n isEnabled: boolean\n}\n\ntype GameFeatureGroup = {\n slug: string\n title: string\n features: GameFeature[]\n}\n\ntype GameData = {\n id: string\n slug: string\n displayName: string\n featuresArr: number[]\n gameFeatures: GameFeatureGroup[]\n}\n\n// GraphQL queries\nconst GAME_FEATURES_QUERY = `\n query GameFeaturesQuery($slug: ID!) {\n game(id: $slug) {\n id\n slug\n displayName\n featuresArr\n gameFeatures {\n slug\n title\n features {\n featureID\n title\n isEnabled\n }\n }\n }\n }\n`\n\nconst TOGGLE_FEATURE_MUTATION = `\n mutation ToggleFeatureMutation($gameSlug: ID!, $input: UpdateGameInput!) {\n updateGame(id: $gameSlug, input: $input) {\n id\n featuresArr\n gameFeatures {\n slug\n title\n features {\n featureID\n title\n isEnabled\n }\n }\n }\n }\n`\n\n// Helper to toggle a feature in the array\nconst toggleFeatureInArr = (featuresArr: number[], featureID: number): number[] => {\n const bitIndex = featureID - 1\n const arrayIndex = Math.floor(bitIndex / 31)\n const bitPosition = bitIndex % 31\n\n const result = [...featuresArr]\n while (result.length <= arrayIndex) result.push(0)\n result[arrayIndex] = result[arrayIndex] ^ (1 << bitPosition)\n return result\n}\n\n// Check if user is authenticated\nexport const isAuthenticated = (): boolean => {\n const token = getAccessToken()\n return !!token\n}\n\nexport function createFeaturesView(): SimulatorView {\n let gameData: GameData | null = null\n let loading = false\n let error: string | null = null\n\n const fetchGameFeatures = async (gameSlug: string): Promise<void> => {\n loading = true\n error = null\n\n try {\n const result = await makeAuthenticatedRequest(GAME_FEATURES_QUERY, { slug: gameSlug })\n\n if (result.errors) {\n error = result.errors[0]?.message || \"Unknown error\"\n gameData = null\n } else if (result.data?.game) {\n gameData = result.data.game\n } else {\n error = \"Game not found\"\n gameData = null\n }\n } catch (e) {\n error = e instanceof Error ? e.message : \"Unknown error\"\n gameData = null\n } finally {\n loading = false\n }\n }\n\n const toggleFeature = async (featureID: number): Promise<void> => {\n if (!gameData) return\n\n const newFeaturesArr = toggleFeatureInArr(gameData.featuresArr, featureID)\n\n try {\n const result = await makeAuthenticatedRequest(TOGGLE_FEATURE_MUTATION, {\n gameSlug: gameData.slug,\n input: { featuresArr: newFeaturesArr },\n })\n\n if (result.errors) {\n console.error(\"Failed to toggle feature:\", result.errors[0]?.message)\n } else if (result.data?.updateGame) {\n // Update local state with new data\n gameData = {\n ...gameData,\n featuresArr: result.data.updateGame.featuresArr,\n gameFeatures: result.data.updateGame.gameFeatures,\n }\n }\n } catch (e) {\n console.error(\"Failed to toggle feature:\", e)\n }\n }\n\n const renderFeatures = (): string => {\n if (loading) {\n return '<div class=\"features-loading\">Loading features...</div>'\n }\n\n if (error) {\n return `<div class=\"features-error\">${error}</div>`\n }\n\n if (!gameData) {\n return '<div class=\"features-empty\">Enter your game slug above to load features</div>'\n }\n\n const groupsHtml = gameData.gameFeatures\n .map((group) => {\n const featuresHtml = group.features\n .map((f) => {\n const statusClass = f.isEnabled ? \"enabled\" : \"disabled\"\n const statusIcon = f.isEnabled ? \"✓\" : \"✗\"\n return `\n <div class=\"feature-item ${statusClass}\" data-feature-id=\"${f.featureID}\">\n <span class=\"feature-status\">${statusIcon}</span>\n <span class=\"feature-title\">${f.title}</span>\n </div>\n `\n })\n .join(\"\")\n\n return `\n <div class=\"feature-group\">\n <div class=\"feature-group-title\">${group.title}</div>\n <div class=\"feature-group-items\">${featuresHtml}</div>\n </div>\n `\n })\n .join(\"\")\n\n return `\n <div class=\"features-game-name\">${gameData.displayName}</div>\n ${groupsHtml}\n `\n }\n\n return {\n id: \"features\",\n label: \"Features\",\n\n render() {\n if (!isAuthenticated()) {\n return `\n <div class=\"features-view-container\">\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title\">Game Features</div>\n <div class=\"features-auth-required\">\n <p>You must be logged in to view and edit game features.</p>\n <p>Go to the <strong>Auth</strong> tab to log in with your Puzzmo account.</p>\n </div>\n </div>\n </div>\n `\n }\n\n return `\n <div class=\"features-view-container\">\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title\">Game Features</div>\n <div class=\"features-slug-input\">\n <input type=\"text\" class=\"simulator-input\" id=\"features-game-slug\" placeholder=\"Game slug (e.g. crossword)\" />\n <button class=\"simulator-btn primary\" id=\"features-load-btn\">Load</button>\n </div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div id=\"features-content\" class=\"features-content\">\n ${renderFeatures()}\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const loadBtn = ctx.getElement<HTMLButtonElement>(\"#features-load-btn\")\n const slugInput = ctx.getElement<HTMLInputElement>(\"#features-game-slug\")\n const contentEl = ctx.getElement<HTMLElement>(\"#features-content\")\n\n const updateContent = () => {\n if (contentEl) {\n contentEl.innerHTML = renderFeatures()\n bindFeatureItems()\n }\n }\n\n const bindFeatureItems = () => {\n const featureItems = ctx.getElement<HTMLElement>(\"#features-content\")?.querySelectorAll(\".feature-item\")\n featureItems?.forEach((item) => {\n item.addEventListener(\"click\", async () => {\n const featureId = parseInt(item.getAttribute(\"data-feature-id\") || \"0\")\n if (featureId > 0) {\n item.classList.add(\"updating\")\n await toggleFeature(featureId)\n updateContent()\n }\n })\n })\n }\n\n const loadFeatures = async (slug: string) => {\n if (!slug) return\n\n if (loadBtn) {\n loadBtn.disabled = true\n loadBtn.textContent = \"Loading...\"\n }\n\n await fetchGameFeatures(slug)\n updateContent()\n\n if (loadBtn) {\n loadBtn.disabled = false\n loadBtn.textContent = \"Load\"\n }\n }\n\n loadBtn?.addEventListener(\"click\", async () => {\n const slug = slugInput?.value.trim()\n if (slug) await loadFeatures(slug)\n })\n\n slugInput?.addEventListener(\"keypress\", (e) => {\n if (e.key === \"Enter\") {\n loadBtn?.click()\n }\n })\n\n // Pre-fill slug from context and auto-load if authenticated\n console.log(\"[FeaturesView] bind called, ctx.gameSlug:\", ctx.gameSlug, \"slugInput:\", slugInput)\n if (ctx.gameSlug && slugInput) {\n slugInput.value = ctx.gameSlug\n // Auto-load features if authenticated and we have a slug\n if (isAuthenticated() && !gameData) {\n loadFeatures(ctx.gameSlug)\n }\n }\n\n // Bind initial feature items if any\n bindFeatureItems()\n },\n\n onActivate(ctx: SimulatorContext) {\n // Re-render entire view when tab becomes active to handle auth status changes\n const tabContainer = ctx.getElement<HTMLElement>(\"#simulator-tab-features\")\n if (tabContainer) {\n tabContainer.innerHTML = this.render()\n this.bind(ctx)\n }\n },\n }\n}\n","/**\n * Simulator - A development UI for testing games with the Puzzmo Proto SDK.\n *\n * This script simulates the Puzzmo host environment by:\n * - Listening for READY messages from the game\n * - Sending READY_DATA with puzzle data\n * - Providing UI controls for START_GAME, PAUSE_GAME, RESUME_GAME, RETRY_PUZZLE\n *\n * Usage with Vite plugin (recommended):\n *\n * ```ts\n * // vite.config.ts\n * import { puzzmoSimulator } from \"@puzzmo/sdk/vite\"\n * export default defineConfig({\n * plugins: [puzzmoSimulator({ slug: \"my-game\", fixturesGlob: \"./fixtures/puzzles/**\\/*.json\" })]\n * })\n * ```\n *\n * The plugin handles making sure it is removed on vite builds.\n *\n * Usage with manual imports:\n * ```html\n * <script type=\"module\">\n * import { createSimulator } from \"@puzzmo/sdk/simulator\"\n * const fixtures = import.meta.glob(\"./fixtures/puzzles/**\\/*.json\", { eager: true })\n * createSimulator({ fixtures })\n * </script>\n * ```\n * The fixtures folder structure should be: fixtures/puzzles/{category}/{puzzle}.json\n * This will show dropdowns in the Ctrl tab to select category and puzzle.\n */\n\nimport type { BootstrapGameData, MessagesReceived } from \"../types\"\nimport type { SimulatorConfig, SimulatorContext, SimulatorState, SimulatorView, TabName, FixtureImports } from \"./types\"\nimport { simulatorStyles } from \"./styles\"\nimport { createInitialState, persistCollapsed, persistTab } from \"./state\"\nimport { sendToGame, createMessageListener, createMessageLogger } from \"./messaging\"\nimport { parseFixtures } from \"./fixtures\"\nimport {\n createCtrlView,\n createDataView,\n createMsgsView,\n createDoneView,\n createCheckpointsView,\n createThumbView,\n createThemeView,\n createAuthView,\n createFeaturesView,\n} from \"./views\"\n\n// Re-export types for consumers\nexport type { SimulatorConfig, FixtureImports }\n\n// Singleton instance state\ninterface SimulatorInstance {\n updateFixtures: (fixtures: FixtureImports) => void\n sendToGame: (type: any, data: any) => void\n loadPuzzle: () => Promise<any>\n}\nlet simulatorInstance: SimulatorInstance | null = null\n\n/**\n * Creates the Simulator UI and message handling.\n * If called multiple times, updates the existing instance with new settings (e.g., fixtures).\n */\nfunction createSimulator(config: SimulatorConfig = {}): SimulatorInstance {\n console.log(\"[Simulator] createSimulator called with config:\", { slug: config.slug, hasFixtures: !!config.fixtures })\n\n // If instance already exists, update it with new config\n if (simulatorInstance) {\n if (config.fixtures) {\n console.log(\"[Simulator] Instance already exists, updating fixtures\")\n simulatorInstance.updateFixtures(config.fixtures)\n }\n return simulatorInstance\n }\n\n const autoStart = config.autoStart ?? true\n\n // Parse fixtures if provided\n let fixtures = config.fixtures ? parseFixtures(config.fixtures) : null\n let fixtureCategories = fixtures ? Array.from(fixtures.keys()).sort() : []\n\n // Create views first so we can extract valid tab IDs\n const msgsView = createMsgsView()\n const thumbView = createThumbView()\n const views = [\n createCtrlView(),\n createDataView(),\n msgsView,\n createDoneView(),\n createCheckpointsView(),\n thumbView,\n createThemeView(),\n createAuthView(),\n createFeaturesView(),\n ] satisfies SimulatorView[]\n\n const validTabIds = views.map((v) => v.id)\n\n // Initialize state\n const state: SimulatorState = createInitialState(config, fixtureCategories, validTabIds)\n\n // SVG Icons for the header\n const icons = {\n pause: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><rect x=\"1\" y=\"1\" width=\"3\" height=\"8\"/><rect x=\"6\" y=\"1\" width=\"3\" height=\"8\"/></svg>`,\n play: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><polygon points=\"2,1 9,5 2,9\"/></svg>`,\n retry: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><path d=\"M5 1C2.8 1 1 2.8 1 5s1.8 4 4 4c1.8 0 3.3-1.2 3.8-2.8H7.5c-.4.9-1.3 1.5-2.5 1.5-1.5 0-2.7-1.2-2.7-2.7S3.5 2.3 5 2.3c.7 0 1.4.3 1.9.8L5.5 4.5H9V1L7.6 2.4C6.9 1.5 5.9 1 5 1z\"/></svg>`,\n cog: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><path d=\"M9.5 5.8l-.8-.5c0-.2.1-.5.1-.8s0-.5-.1-.8l.8-.5c.1-.1.2-.2.1-.4l-.8-1.4c-.1-.1-.2-.2-.4-.1l-1 .3c-.3-.3-.7-.5-1.1-.6L6.1.2C6.1.1 6 0 5.8 0H4.2c-.2 0-.3.1-.3.2l-.2 1c-.4.1-.7.3-1.1.6l-1-.3c-.2 0-.3 0-.4.1l-.8 1.4c-.1.2 0 .3.1.4l.8.5c0 .2-.1.5-.1.8s0 .5.1.8l-.8.5c-.1.1-.2.2-.1.4l.8 1.4c.1.1.2.2.4.1l1-.3c.3.3.7.5 1.1.6l.2 1c0 .1.1.2.3.2h1.6c.2 0 .3-.1.3-.2l.2-1c.4-.1.7-.3 1.1-.6l1 .3c.2 0 .3 0 .4-.1l.8-1.4c.1-.2 0-.3-.1-.4zM5 6.5c-.8 0-1.5-.7-1.5-1.5S4.2 3.5 5 3.5 6.5 4.2 6.5 5 5.8 6.5 5 6.5z\"/></svg>`,\n minimize: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><rect x=\"1\" y=\"4\" width=\"8\" height=\"2\"/></svg>`,\n expand: `<svg width=\"8\" height=\"8\" viewBox=\"0 0 8 8\" fill=\"currentColor\"><polygon points=\"1,1 7,4 1,7\"/></svg>`,\n }\n\n // Create the Simulator UI container\n const container = document.createElement(\"div\")\n container.id = \"simulator\"\n container.innerHTML = `\n <style>${simulatorStyles}</style>\n <div id=\"simulator-panel\" class=\"${state.isCollapsed ? \"collapsed\" : \"\"}\">\n <div id=\"simulator-header\">\n <span id=\"simulator-title\">PUZZMO SIMULATOR</span>\n <span class=\"header-sep\">|</span>\n <div id=\"simulator-timer\">--:--</div>\n <span class=\"header-sep\">|</span>\n <div id=\"simulator-header-controls\">\n <button id=\"simulator-header-pause\" class=\"header-icon-btn\" title=\"Pause\" disabled>${icons.pause}</button>\n <button id=\"simulator-header-retry\" class=\"header-icon-btn\" title=\"Retry\" disabled>${icons.retry}</button>\n </div>\n <span class=\"header-sep\">|</span>\n <div id=\"simulator-header-status\">\n <span id=\"simulator-header-indicator\" class=\"waiting\"></span>\n <span id=\"simulator-header-status-text\">Waiting...</span>\n </div>\n <span class=\"header-spacer\"></span>\n <button id=\"simulator-header-settings\" class=\"header-icon-btn\" title=\"Settings\">${icons.cog}</button>\n <button id=\"simulator-toggle\" class=\"header-icon-btn\" title=\"Minimize\">${icons.minimize}</button>\n </div>\n <div id=\"simulator-body\">\n <div id=\"simulator-tabs\">\n ${views\n .filter((v) => v.id !== \"auth\")\n .map(\n (v) =>\n `<button class=\"simulator-tab\" data-tab=\"${v.id}\">${v.label}<span class=\"simulator-tab-badge\" data-badge=\"${v.id}\"></span></button>`,\n )\n .join(\"\")}\n </div>\n <div id=\"simulator-content\" class=\"hidden\">\n ${views.map((v) => `<div id=\"simulator-tab-${v.id}\" class=\"simulator-tab-content\">${v.render()}</div>`).join(\"\")}\n </div>\n </div>\n </div>\n `\n\n // Add to DOM\n document.body.appendChild(container)\n\n // Get UI elements\n const panel = container.querySelector(\"#simulator-panel\") as HTMLElement\n const header = container.querySelector(\"#simulator-header\") as HTMLElement\n const headerIndicator = container.querySelector(\"#simulator-header-indicator\") as HTMLElement\n const headerStatusText = container.querySelector(\"#simulator-header-status-text\") as HTMLElement\n const headerPauseBtn = container.querySelector(\"#simulator-header-pause\") as HTMLButtonElement\n const headerRetryBtn = container.querySelector(\"#simulator-header-retry\") as HTMLButtonElement\n const headerSettingsBtn = container.querySelector(\"#simulator-header-settings\") as HTMLButtonElement\n const toggleBtn = container.querySelector(\"#simulator-toggle\") as HTMLButtonElement\n const timerEl = container.querySelector(\"#simulator-timer\") as HTMLElement\n const tabsContainer = container.querySelector(\"#simulator-tabs\") as HTMLElement\n const contentEl = container.querySelector(\"#simulator-content\") as HTMLElement\n\n // Helper to get elements within container\n const getElement = <T extends HTMLElement>(selector: string): T | null => {\n return container.querySelector(selector) as T | null\n }\n\n // Update pause button icon\n const updatePauseIcon = (isPaused: boolean) => {\n headerPauseBtn.innerHTML = isPaused ? icons.play : icons.pause\n headerPauseBtn.title = isPaused ? \"Resume\" : \"Pause\"\n }\n\n // Update status display (both in ctrl tab and header)\n const updateStatus = (text: string, className: string) => {\n const statusText = getElement<HTMLElement>(\"#simulator-status .text\")\n const statusIndicator = getElement<HTMLElement>(\"#simulator-status .indicator\")\n if (statusText) statusText.textContent = text\n if (statusIndicator) statusIndicator.className = `indicator ${className}`\n headerIndicator.className = className\n headerStatusText.textContent = text\n }\n\n // Update timer display\n const updateTimer = (display: string, penalty?: string) => {\n if (penalty && penalty !== \"0\") {\n timerEl.innerHTML = `${display}<span class=\"penalty\">+${penalty}</span>`\n } else {\n timerEl.textContent = display\n }\n }\n\n // Tab switching logic\n const switchTab = (tabName: TabName) => {\n state.activeTab = tabName\n contentEl.classList.remove(\"hidden\")\n persistTab(tabName)\n\n // Update tab buttons\n tabsContainer.querySelectorAll(\".simulator-tab\").forEach((t) => {\n t.classList.toggle(\"active\", t.getAttribute(\"data-tab\") === tabName)\n })\n\n // Update tab content\n container.querySelectorAll(\".simulator-tab-content\").forEach((c) => {\n c.classList.toggle(\"active\", c.id === `simulator-tab-${tabName}`)\n })\n\n // Update header button to show active when auth tab is selected\n headerSettingsBtn.classList.toggle(\"active\", tabName === \"auth\")\n\n // Call onActivate for the view\n const view = views.find((v) => v.id === tabName)\n view?.onActivate?.(context)\n }\n\n // Expand/collapse helper\n const setCollapsed = (collapsed: boolean) => {\n state.isCollapsed = collapsed\n panel.classList.toggle(\"collapsed\", collapsed)\n persistCollapsed(collapsed)\n\n if (collapsed) {\n contentEl.classList.add(\"hidden\")\n tabsContainer.querySelectorAll(\".simulator-tab\").forEach((t) => t.classList.remove(\"active\"))\n } else {\n switchTab(state.activeTab)\n }\n }\n\n // Update thumbnail (delegated to thumbView)\n const updateThumbnail = () => {\n thumbView.updatePreview(context)\n }\n\n // Load puzzle data from fixtures\n const loadPuzzle = async (): Promise<any> => {\n if (state.puzzleData) return state.puzzleData\n\n if (!fixtures || fixtures.size === 0) {\n throw new Error(\"No fixtures configured. Add puzzle JSON files to a fixtures directory and pass fixturesGlob to the simulator.\")\n }\n\n // Pick the selected fixture, or the first one available\n const category = state.selectedCategory ?? fixtureCategories[0]\n const categoryMap = category ? fixtures.get(category) : undefined\n if (!categoryMap || categoryMap.size === 0) {\n throw new Error(`No puzzles found in fixture category \"${category}\"`)\n }\n\n const puzzleName = state.selectedPuzzle ?? categoryMap.keys().next().value\n const puzzleData = puzzleName ? categoryMap.get(puzzleName) : undefined\n if (!puzzleData) {\n throw new Error(`Puzzle \"${puzzleName}\" not found in category \"${category}\"`)\n }\n\n state.puzzleData = puzzleData\n console.log(\"Simulator: Puzzle loaded from fixtures\", { category, puzzle: puzzleName })\n state.originalPuzzle = JSON.stringify(state.puzzleData, null, 2)\n\n const puzzleTextarea = getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n if (puzzleTextarea) puzzleTextarea.value = state.originalPuzzle\n\n if (state.activeTab === \"thumb\") updateThumbnail()\n\n return state.puzzleData\n }\n\n // Wrapped sendToGame with logger\n const messageLogger = createMessageLogger((entry) => {\n msgsView.addLogEntry(entry, context)\n })\n\n const wrappedSendToGame = (type: keyof MessagesReceived, data: any) => {\n sendToGame(type, data, messageLogger)\n }\n\n // Update badge count for a tab\n const updateBadge = (tabId: string, count: number | undefined) => {\n const badge = container.querySelector(`[data-badge=\"${tabId}\"]`) as HTMLElement\n if (badge) {\n badge.textContent = count && count > 0 ? String(count) : \"\"\n }\n }\n\n // Create context object for views\n const context: SimulatorContext = {\n state,\n getElement,\n sendToGame: wrappedSendToGame,\n logMessage: messageLogger.log,\n loadPuzzle,\n updateStatus,\n updateTimer,\n setCollapsed,\n switchTab,\n updateThumbnail,\n updateBadge,\n fixtures,\n fixtureCategories,\n gameSlug: config.slug ?? null,\n }\n\n console.log(\"[Simulator] Context created with gameSlug:\", context.gameSlug)\n\n // Bind all views\n views.forEach((view) => view.bind(context))\n\n // Tab click handlers\n tabsContainer.querySelectorAll(\".simulator-tab\").forEach((tab) => {\n tab.addEventListener(\"click\", () => {\n const tabName = tab.getAttribute(\"data-tab\") as TabName\n switchTab(tabName)\n updateBadge(tabName, 0)\n })\n })\n\n // Toggle button (only visible when expanded) - minimizes the panel\n toggleBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n setCollapsed(true)\n })\n\n // Header pause button\n headerPauseBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n if (state.isPaused) {\n wrappedSendToGame(\"RESUME_GAME\", {})\n state.isPaused = false\n updatePauseIcon(false)\n updateStatus(\"Running\", \"ready\")\n } else {\n wrappedSendToGame(\"PAUSE_GAME\", {})\n state.isPaused = true\n updatePauseIcon(true)\n updateStatus(\"Paused\", \"paused\")\n }\n // Sync with ctrl tab buttons\n const pauseBtn = getElement<HTMLButtonElement>(\"#simulator-pause\")\n if (pauseBtn) pauseBtn.textContent = state.isPaused ? \"Resume\" : \"Pause\"\n })\n\n // Header retry button\n headerRetryBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n wrappedSendToGame(\"RETRY_PUZZLE\", {})\n state.hasStarted = false\n state.isPaused = false\n updatePauseIcon(false)\n headerPauseBtn.disabled = true\n updateStatus(\"Ready to retry\", \"ready\")\n // Sync with ctrl tab buttons\n const pauseBtn = getElement<HTMLButtonElement>(\"#simulator-pause\")\n const startBtn = getElement<HTMLButtonElement>(\"#simulator-start\")\n if (pauseBtn) {\n pauseBtn.disabled = true\n pauseBtn.textContent = \"Pause\"\n }\n if (startBtn) startBtn.textContent = \"Start\"\n })\n\n // Header settings button - opens auth tab\n headerSettingsBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n if (state.isCollapsed) {\n setCollapsed(false)\n }\n switchTab(\"auth\")\n })\n\n // Header click (when collapsed) - expands the panel\n header.addEventListener(\"click\", (e) => {\n if (state.isCollapsed && e.target !== toggleBtn) {\n setCollapsed(false)\n }\n })\n\n // Restore tab state on init (always select a tab when not collapsed)\n if (!state.isCollapsed) {\n switchTab(state.activeTab)\n }\n\n // Check for OAuth callback parameters on page load - handle regardless of which tab is active\n const urlParams = new URLSearchParams(window.location.search)\n if (urlParams.has(\"code\") || urlParams.has(\"error\")) {\n // Switch to auth tab to handle the OAuth callback\n setCollapsed(false)\n switchTab(\"auth\")\n }\n\n // Create READY_DATA payload\n const createReadyData = (puzzle: any): BootstrapGameData => {\n return {\n userState: {\n gameSettings: {},\n id: \"simulator-user\",\n nakamaLogin: \"simulator\",\n ownerID: \"simulator-owner\",\n },\n currentUser: null,\n startOrFindGameplay: {\n gamePlayed: {\n additionalTimeAddedSecs: 0,\n boardState: state.currentInputStr,\n cheatsUsed: 0,\n combinedTimeSecs: 0,\n completed: false,\n createdAt: new Date().toISOString(),\n elapsedTimeSecs: 0,\n hintsUsed: 0,\n id: `simulator-gameplay-${Date.now()}`,\n ownerID: \"simulator-owner\",\n pointsAwarded: 0,\n resetsUsed: 0,\n slug: \"simulator-game\",\n viewerOwnsPuzzle: true,\n puzzle: {\n id: \"simulator-puzzle\",\n name: \"Proto Jig Puzzle\",\n puzzle: JSON.stringify(puzzle),\n seriesNumber: 1,\n game: {\n assetsPath: \"./assets/\",\n assetsSha: \"\",\n displayName: \"Proto Game\",\n exposedGlobalFunction: \"startGame\",\n jsPath: \"\",\n slug: \"proto-game\",\n },\n },\n },\n },\n theme: state.selectedTheme,\n hostFlags: [],\n hostContext: [],\n appRuntimeContract: \"1.0\",\n }\n }\n\n // Handle READY message from game - send READY_DATA with puzzle\n const handleReady = async () => {\n updateStatus(\"Loading puzzle...\", \"waiting\")\n\n try {\n const puzzle = await loadPuzzle()\n const readyData = createReadyData(puzzle)\n\n updateStatus(\"Sending READY_DATA...\", \"waiting\")\n wrappedSendToGame(\"READY_DATA\", readyData)\n\n updateStatus(\"Waiting for game to load...\", \"waiting\")\n } catch (error) {\n updateStatus(`Error: ${error}`, \"paused\")\n }\n }\n\n // Handle READY_GAME_LOADED message from game\n const handleGameLoaded = () => {\n console.log(\"Simulator: Game loaded, ready to start\")\n\n // Enable header buttons\n headerRetryBtn.disabled = false\n\n // Notify views\n views.forEach((v) => v.onMessage?.(\"READY_GAME_LOADED\", undefined, context))\n\n // Auto-start if configured\n if (autoStart && !state.hasStarted) {\n setTimeout(() => {\n const startBtn = getElement<HTMLButtonElement>(\"#simulator-start\")\n startBtn?.click()\n // Enable pause button after start\n headerPauseBtn.disabled = false\n state.hasStarted = true\n updateStatus(\"Running\", \"ready\")\n }, 100)\n }\n }\n\n // Set up message listener\n createMessageListener((type, data) => {\n // Handle core messages\n if (type === \"READY\") {\n console.log(\"Simulator: Received READY from game\")\n handleReady()\n return\n }\n\n if (type === \"INITIALIZE_SETTINGS\") {\n console.log(\"Simulator: Game initialized settings\", data)\n return\n }\n\n if (type === \"READY_GAME_LOADED\") {\n handleGameLoaded()\n return\n }\n\n if (type === \"TIMER_TICK\") {\n if (data?.display) {\n const [display, penalty] = data.display\n updateTimer(display, penalty)\n }\n return\n }\n\n if (type === \"TIMER_SYNC\") {\n return\n }\n\n if (type === \"SHOW_GAME_COMPLETE_SCREEN\") {\n console.log(\"Simulator: Show completion screen\", data)\n return\n }\n\n if (type === \"SIDEBAR_UPDATE\") {\n console.log(\"Simulator: Sidebar update\", data)\n return\n }\n\n // Delegate to views for other messages\n views.forEach((v) => v.onMessage?.(type, data, context))\n\n // Handle state updates\n // boardState can be at data.boardState (legacy) or data.input.boardState (current SDK format)\n const boardState = data?.input?.boardState ?? data?.boardState\n if (type === \"UPLOAD_NEW_GAME_STATE\" && boardState) {\n state.currentInputStr = boardState\n if (state.activeTab === \"thumb\") {\n updateThumbnail()\n }\n console.log(\"Simulator: Game state uploaded\", data)\n }\n\n if (type === \"GAME_COMPLETED\") {\n console.log(\"Simulator: Game completed!\", data)\n headerPauseBtn.disabled = true\n state.hasStarted = false\n updateStatus(\"Completed!\", \"ready\")\n }\n }, messageLogger)\n\n console.log(\"Simulator initialized\")\n\n // Function to update fixtures after initial creation\n // Note: This is called when createSimulator is called again with fixtures\n // after the singleton was already created. We DON'T reload here - just update state.\n const updateFixtures = (newFixtureImports: FixtureImports) => {\n fixtures = parseFixtures(newFixtureImports)\n fixtureCategories = Array.from(fixtures.keys()).sort()\n context.fixtures = fixtures\n context.fixtureCategories = fixtureCategories\n\n // Update state's selected category if it's not set or invalid\n const storedCategory = localStorage.getItem(\"simulator-fixture-category\")\n if (!state.selectedCategory || !fixtureCategories.includes(state.selectedCategory)) {\n state.selectedCategory =\n storedCategory && fixtureCategories.includes(storedCategory) ? storedCategory : (fixtureCategories[0] ?? null)\n }\n\n // Re-bind ctrl view to pick up new fixtures\n const ctrlView = views.find((v) => v.id === \"ctrl\")\n ctrlView?.bind(context)\n\n console.log(\"Simulator: Fixtures updated\", { categories: fixtureCategories, selectedCategory: state.selectedCategory })\n }\n\n // Store instance for subsequent calls\n simulatorInstance = {\n updateFixtures,\n sendToGame: wrappedSendToGame,\n loadPuzzle,\n }\n\n return simulatorInstance\n}\n\nexport { createSimulator }\n"],"mappings":"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"}
1
+ {"version":3,"file":"createSimulator-BmeD2jIP.cjs","names":[],"sources":["../src/simulator/styles.ts","../src/themes.ts","../src/simulator/state.ts","../src/simulator/messaging.ts","../src/simulator/fixtures.ts","../src/simulator/views/CtrlView.ts","../src/simulator/views/DataView.ts","../src/simulator/views/MsgsView.ts","../src/simulator/views/DoneView.ts","../src/simulator/views/CheckpointsView.ts","../src/simulator/views/ThumbView.ts","../src/simulator/views/ThemeView.ts","../src/simulator/views/AuthView.ts","../src/simulator/views/FeaturesView.ts","../src/simulator/createSimulator.ts"],"sourcesContent":["export const simulatorStyles = `\n :root {\n --sim-bg: #1a1a2e;\n --sim-bg-alt: #16213e;\n --sim-panel: #0f0f1a;\n --sim-border: #3a3a5c;\n --sim-border-light: #5a5a8c;\n --sim-accent: #ffd700;\n --sim-accent-hover: #ffed4a;\n --sim-text: #e8e8e8;\n --sim-text-dim: #888899;\n --sim-success: #4ade80;\n --sim-error: #f87171;\n --sim-warning: #fbbf24;\n --sim-blue: #60a5fa;\n }\n #simulator {\n position: fixed;\n bottom: 4px;\n left: 4px;\n z-index: 999999;\n font: 11px/1.4 \"Menlo\", \"Monaco\", \"Consolas\", monospace;\n user-select: none;\n }\n #simulator-panel {\n background: var(--sim-panel);\n border: 2px solid var(--sim-border);\n border-radius: 4px;\n color: var(--sim-text);\n width: 420px;\n box-shadow: 4px 4px 0 rgba(0,0,0,0.5);\n }\n #simulator-panel.collapsed {\n width: auto;\n }\n #simulator-header {\n display: flex;\n align-items: center;\n padding: 4px 8px;\n background: var(--sim-bg);\n border-bottom: 2px solid var(--sim-border);\n gap: 0;\n }\n #simulator-panel.collapsed #simulator-header {\n border-bottom: none;\n cursor: pointer;\n }\n #simulator-panel.collapsed #simulator-header:hover {\n background: var(--sim-bg-alt);\n }\n .header-sep {\n color: var(--sim-border-light);\n margin: 0 8px;\n opacity: 0.6;\n }\n .header-spacer {\n flex: 1;\n }\n #simulator-title {\n color: var(--sim-accent);\n text-transform: uppercase;\n letter-spacing: 1px;\n font-size: 10px;\n font-weight: bold;\n }\n #simulator-header-controls {\n display: flex;\n gap: 2px;\n }\n .header-icon-btn {\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: 2px;\n color: var(--sim-text);\n cursor: pointer;\n padding: 0;\n }\n .header-icon-btn:hover:not(:disabled) {\n background: var(--sim-border);\n color: var(--sim-accent);\n }\n .header-icon-btn:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n }\n .header-icon-btn.active {\n background: var(--sim-accent);\n color: var(--sim-panel);\n }\n #simulator-header-status {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 9px;\n color: var(--sim-text-dim);\n }\n #simulator-header-indicator {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--sim-text-dim);\n flex-shrink: 0;\n }\n #simulator-header-indicator.ready {\n background: var(--sim-success);\n box-shadow: 0 0 4px var(--sim-success);\n }\n #simulator-header-indicator.waiting {\n background: var(--sim-warning);\n box-shadow: 0 0 4px var(--sim-warning);\n }\n #simulator-header-indicator.paused {\n background: var(--sim-error);\n box-shadow: 0 0 4px var(--sim-error);\n }\n #simulator-timer {\n font-size: 11px;\n color: var(--sim-text);\n font-family: \"Menlo\", monospace;\n font-weight: bold;\n font-variant-numeric: tabular-nums;\n min-width: 38px;\n }\n #simulator-timer .penalty {\n color: var(--sim-error);\n margin-left: 2px;\n }\n /* Collapsed state - show minimal bar */\n #simulator-panel.collapsed .header-sep,\n #simulator-panel.collapsed #simulator-header-controls,\n #simulator-panel.collapsed #simulator-header-status,\n #simulator-panel.collapsed #simulator-header-settings,\n #simulator-panel.collapsed #simulator-toggle {\n display: none;\n }\n #simulator-panel.collapsed #simulator-title {\n cursor: pointer;\n }\n #simulator-panel.collapsed #simulator-timer {\n margin-left: 8px;\n }\n #simulator-body {\n display: flex;\n flex-direction: row;\n }\n #simulator-panel.collapsed #simulator-body {\n display: none;\n }\n #simulator-tabs {\n display: flex;\n flex-direction: column;\n background: var(--sim-bg);\n padding: 4px;\n gap: 2px;\n border-right: 2px solid var(--sim-border);\n }\n .simulator-tab {\n padding: 2px 3px;\n background: var(--sim-bg-alt);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text-dim);\n cursor: pointer;\n font: inherit;\n text-transform: uppercase;\n font-size: 9px;\n font-weight: bold;\n letter-spacing: 0.5px;\n min-height: 0;\n }\n .simulator-tab:hover {\n color: var(--sim-text);\n background: var(--sim-border);\n }\n .simulator-tab.active {\n color: var(--sim-panel);\n background: var(--sim-accent);\n border-color: var(--sim-accent);\n }\n .simulator-tab {\n position: relative;\n }\n .simulator-tab-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 14px;\n height: 14px;\n padding: 0 3px;\n background: var(--sim-blue);\n color: #fff;\n font-size: 8px;\n font-weight: bold;\n border-radius: 7px;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 1px 2px rgba(0,0,0,0.3);\n line-height: 1;\n text-align: center;\n box-sizing: border-box;\n }\n .simulator-tab-badge:empty {\n display: none;\n }\n #simulator-content {\n padding: 6px;\n background: var(--sim-panel);\n height: 500px;\n flex: 1;\n overflow-y: auto;\n }\n #simulator-content.hidden {\n display: none;\n }\n #simulator-content::-webkit-scrollbar {\n width: 8px;\n }\n #simulator-content::-webkit-scrollbar-track {\n background: var(--sim-bg);\n }\n #simulator-content::-webkit-scrollbar-thumb {\n background: var(--sim-border);\n border-radius: 2px;\n }\n .simulator-btn {\n padding: 4px 8px;\n background: var(--sim-bg-alt);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: inherit;\n font-size: 10px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 1px;\n cursor: pointer;\n }\n .simulator-btn:hover {\n background: var(--sim-border);\n border-color: var(--sim-border-light);\n }\n .simulator-btn:active {\n transform: translate(1px, 1px);\n }\n .simulator-btn.primary {\n background: var(--sim-accent);\n border-color: var(--sim-accent);\n color: var(--sim-panel);\n }\n .simulator-btn.primary:hover {\n background: var(--sim-accent-hover);\n border-color: var(--sim-accent-hover);\n }\n .simulator-btn.danger {\n background: var(--sim-error);\n border-color: var(--sim-error);\n color: #fff;\n }\n .simulator-btn.danger:hover {\n background: #ef4444;\n border-color: #ef4444;\n }\n .simulator-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n transform: none;\n }\n .simulator-btn.subtle {\n background: transparent;\n border-color: transparent;\n color: var(--sim-text-dim);\n }\n .simulator-btn.subtle:hover:not(:disabled) {\n background: var(--sim-bg-alt);\n border-color: var(--sim-border);\n color: var(--sim-text);\n }\n #simulator-status {\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 4px 0 6px;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n #simulator-status .indicator {\n width: 8px;\n height: 8px;\n border-radius: 2px;\n background: var(--sim-text-dim);\n }\n #simulator-status .indicator.ready {\n background: var(--sim-success);\n box-shadow: 0 0 6px var(--sim-success);\n }\n #simulator-status .indicator.waiting {\n background: var(--sim-warning);\n box-shadow: 0 0 6px var(--sim-warning);\n }\n #simulator-status .indicator.paused {\n background: var(--sim-error);\n box-shadow: 0 0 6px var(--sim-error);\n }\n .simulator-row {\n display: flex;\n gap: 4px;\n margin-top: 4px;\n }\n .simulator-row .simulator-btn {\n flex: 1;\n }\n .simulator-divider {\n height: 2px;\n background: var(--sim-border);\n margin: 8px 0;\n }\n .simulator-field {\n margin-bottom: 6px;\n }\n .simulator-select {\n width: 100%;\n padding: 4px 6px;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: inherit;\n font-size: 10px;\n cursor: pointer;\n }\n .simulator-select:focus {\n outline: none;\n border-color: var(--sim-accent);\n }\n .simulator-select option {\n background: var(--sim-bg);\n color: var(--sim-text);\n }\n .simulator-fixtures {\n margin-bottom: 8px;\n padding-bottom: 8px;\n border-bottom: 2px solid var(--sim-border);\n }\n .simulator-label {\n display: block;\n color: var(--sim-accent);\n text-transform: uppercase;\n font-size: 9px;\n font-weight: bold;\n letter-spacing: 1px;\n margin-bottom: 4px;\n }\n .simulator-textarea {\n width: 100%;\n min-height: 40px;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: 10px/1.4 \"Menlo\", monospace;\n padding: 4px;\n resize: none;\n box-sizing: border-box;\n overflow-y: auto;\n }\n .simulator-textarea.auto-resize {\n resize: none;\n overflow-y: auto;\n }\n .simulator-textarea:focus {\n outline: none;\n border-color: var(--sim-accent);\n }\n .simulator-input {\n width: 100%;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text);\n font: 10px/1.4 \"Menlo\", monospace;\n padding: 4px 6px;\n box-sizing: border-box;\n }\n .simulator-input:focus {\n outline: none;\n border-color: var(--sim-accent);\n }\n .simulator-input::placeholder {\n color: var(--sim-text-dim);\n }\n .simulator-tab-content {\n display: none;\n }\n .simulator-tab-content.active {\n display: block;\n }\n .msgs-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .msgs-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n #simulator-msgs-log {\n display: flex;\n flex-direction: column;\n gap: 2px;\n flex: 1;\n overflow-y: auto;\n }\n .simulator-msg {\n padding: 3px 4px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n font-size: 10px;\n }\n .simulator-msg.out {\n border-left: 2px solid var(--sim-accent);\n }\n .simulator-msg.in {\n border-left: 2px solid var(--sim-blue);\n }\n .simulator-msg-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 2px;\n }\n .simulator-msg-type {\n font-weight: bold;\n color: var(--sim-text);\n }\n .simulator-msg.out .simulator-msg-type {\n color: var(--sim-accent);\n }\n .simulator-msg.in .simulator-msg-type {\n color: var(--sim-blue);\n }\n .simulator-msg-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n }\n .simulator-msg-data {\n color: var(--sim-text-dim);\n font-size: 9px;\n white-space: pre-wrap;\n word-break: break-all;\n max-height: 60px;\n overflow: hidden;\n cursor: pointer;\n border-radius: 2px;\n padding: 2px 4px;\n background: var(--sim-bg-alt);\n }\n .simulator-msg-data:hover {\n background: var(--sim-border);\n }\n .simulator-msg.expanded {\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n .simulator-msg.expanded .simulator-msg-data {\n max-height: none;\n flex: 1;\n overflow-y: auto;\n }\n .simulator-empty {\n color: var(--sim-text-dim);\n font-size: 10px;\n text-align: center;\n padding: 20px;\n }\n .simulator-value {\n color: var(--sim-text);\n font-size: 10px;\n padding: 4px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n }\n .simulator-deeds {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .simulator-deed {\n display: flex;\n justify-content: space-between;\n padding: 3px 4px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n font-size: 10px;\n }\n .simulator-deed-name {\n color: var(--sim-text);\n }\n .simulator-deed-value {\n color: var(--sim-accent);\n font-weight: bold;\n }\n .thumb-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .thumb-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n #simulator-thumb-preview {\n background: var(--sim-thumb-bg, transparent);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n aspect-ratio: 1;\n width: 100%;\n box-sizing: border-box;\n }\n #simulator-thumb-preview svg {\n width: 100%;\n height: 100%;\n max-width: 100%;\n max-height: 100%;\n }\n #simulator-thumb-fn {\n font-size: 10px;\n color: var(--sim-text-dim);\n margin-top: 4px;\n }\n .theme-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .theme-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n .simulator-themes {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .simulator-theme-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px;\n background: var(--sim-bg);\n border: 2px solid var(--sim-border);\n border-radius: 2px;\n cursor: pointer;\n }\n .simulator-theme-item:hover {\n border-color: var(--sim-border-light);\n }\n .simulator-theme-item.selected {\n border-color: var(--sim-accent);\n background: var(--sim-bg-alt);\n }\n .simulator-theme-preview {\n width: 48px;\n height: 32px;\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n grid-template-rows: repeat(2, 1fr);\n gap: 1px;\n border-radius: 2px;\n overflow: hidden;\n flex-shrink: 0;\n }\n .simulator-theme-preview-cell {\n width: 100%;\n height: 100%;\n }\n .simulator-theme-name {\n font-size: 10px;\n color: var(--sim-text);\n flex: 1;\n }\n .simulator-theme-type {\n font-size: 9px;\n color: var(--sim-text-dim);\n text-transform: uppercase;\n }\n /* Auth tab styles */\n .simulator-section {\n margin-bottom: 12px;\n }\n .simulator-section-title {\n color: var(--sim-accent);\n text-transform: uppercase;\n font-size: 10px;\n font-weight: bold;\n letter-spacing: 1px;\n margin-bottom: 6px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--sim-border);\n }\n .auth-header-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 6px;\n }\n .auth-status {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 4px 0;\n }\n .auth-status.authenticated {\n color: var(--sim-success);\n }\n .simulator-btn.small {\n padding: 2px 6px;\n font-size: 9px;\n }\n .simulator-btn.tiny {\n padding: 3px 2px;\n font-size: 8px;\n border-width: 1px;\n line-height: 1;\n min-height: 0;\n height: auto;\n }\n .auth-title-row {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .auth-title-row > span:first-child {\n flex: 1;\n }\n .auth-title-row .simulator-btn.active {\n background: var(--sim-accent);\n color: black;\n }\n .auth-description {\n font-size: 10px;\n color: var(--sim-text-dim);\n margin: 6px 0;\n line-height: 1.4;\n }\n .auth-user-info {\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n margin-bottom: 8px;\n }\n .auth-user-info div {\n margin-bottom: 4px;\n }\n .auth-user-info div:last-child {\n margin-bottom: 0;\n }\n .auth-user-info code {\n color: var(--sim-text);\n background: var(--sim-bg-alt);\n padding: 1px 4px;\n border-radius: 2px;\n }\n .auth-warning {\n color: var(--sim-warning);\n font-style: italic;\n }\n .auth-error {\n margin-top: 8px;\n }\n .auth-error .error,\n .auth-api-result .error {\n color: var(--sim-error);\n font-size: 10px;\n padding: 6px;\n background: rgba(248, 113, 113, 0.1);\n border: 1px solid var(--sim-error);\n border-radius: 2px;\n }\n .auth-api-result {\n margin-top: 8px;\n }\n .auth-api-result pre {\n font-size: 9px;\n color: var(--sim-text);\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n padding: 6px;\n margin: 0;\n white-space: pre-wrap;\n word-break: break-all;\n max-height: 150px;\n overflow-y: auto;\n }\n .auth-api-result .loading {\n font-size: 10px;\n color: var(--sim-text-dim);\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n }\n /* Data view styles */\n .data-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .data-subtabs {\n display: flex;\n gap: 2px;\n margin-bottom: 8px;\n flex-shrink: 0;\n }\n .data-subtab {\n padding: 4px 10px;\n background: var(--sim-bg-alt);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n color: var(--sim-text-dim);\n cursor: pointer;\n font: inherit;\n font-size: 9px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .data-subtab:hover {\n color: var(--sim-text);\n background: var(--sim-border);\n }\n .data-subtab.active {\n color: var(--sim-panel);\n background: var(--sim-accent);\n border-color: var(--sim-accent);\n }\n .data-subtab-content {\n display: none;\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n }\n .data-subtab-content.active {\n display: flex;\n flex-direction: column;\n }\n /* History tab styles */\n .history-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n .history-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .history-item {\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n display: flex;\n flex-direction: row;\n gap: 8px;\n }\n .history-item:hover {\n border-color: var(--sim-border-light);\n }\n .history-item-thumb {\n width: 80px;\n height: 80px;\n flex-shrink: 0;\n background: var(--history-thumb-bg, var(--sim-bg-alt));\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n .history-item-thumb svg {\n width: 100%;\n height: 100%;\n }\n .history-item-thumb:empty {\n display: none;\n }\n .history-item-content {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .history-item-header {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n .history-item-num {\n color: var(--sim-accent);\n font-weight: bold;\n font-size: 9px;\n }\n .history-item-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n flex: 1;\n }\n .history-item-preview {\n font-size: 9px;\n color: var(--sim-text);\n word-break: break-all;\n line-height: 1.3;\n background: var(--sim-bg-alt);\n padding: 3px 4px;\n border-radius: 2px;\n flex: 1;\n overflow: hidden;\n }\n /* Saves tab styles */\n .save-new {\n display: flex;\n gap: 4px;\n margin-bottom: 8px;\n flex-shrink: 0;\n }\n .save-new .simulator-input {\n flex: 1;\n }\n .saves-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .save-item {\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n }\n .save-item:hover {\n border-color: var(--sim-border-light);\n }\n .save-item-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 4px;\n }\n .save-item-name {\n color: var(--sim-text);\n font-weight: bold;\n font-size: 10px;\n }\n .save-item-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n }\n .save-item-actions {\n display: flex;\n gap: 4px;\n }\n /* Features view styles */\n .features-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .features-slug-input {\n display: flex;\n gap: 4px;\n margin-bottom: 8px;\n }\n .features-slug-input .simulator-input {\n flex: 1;\n }\n .features-content {\n flex: 1;\n overflow-y: auto;\n }\n .features-loading {\n color: var(--sim-text-dim);\n font-size: 10px;\n text-align: center;\n padding: 20px;\n }\n .features-error {\n color: var(--sim-error);\n font-size: 10px;\n padding: 8px;\n background: rgba(248, 113, 113, 0.1);\n border: 1px solid var(--sim-error);\n border-radius: 2px;\n }\n .features-empty {\n color: var(--sim-text-dim);\n font-size: 10px;\n text-align: center;\n padding: 20px;\n }\n .features-auth-required {\n color: var(--sim-text-dim);\n font-size: 10px;\n padding: 12px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n line-height: 1.5;\n }\n .features-auth-required p {\n margin: 0 0 8px 0;\n }\n .features-auth-required p:last-child {\n margin-bottom: 0;\n }\n .features-game-name {\n color: var(--sim-accent);\n font-size: 11px;\n font-weight: bold;\n margin-bottom: 8px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--sim-border);\n }\n .feature-group {\n margin-bottom: 12px;\n }\n .feature-group-title {\n color: var(--sim-text);\n font-size: 10px;\n font-weight: bold;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n padding-bottom: 2px;\n border-bottom: 1px solid var(--sim-border);\n }\n .feature-group-items {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .feature-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-radius: 2px;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n .feature-item:hover {\n border-color: var(--sim-border-light);\n background: var(--sim-bg-alt);\n }\n .feature-item.updating {\n opacity: 0.5;\n pointer-events: none;\n }\n .feature-status {\n font-size: 14px;\n width: 18px;\n text-align: center;\n flex-shrink: 0;\n }\n .feature-item.enabled .feature-status {\n color: var(--sim-success);\n }\n .feature-item.disabled .feature-status {\n color: var(--sim-error);\n opacity: 0.5;\n }\n .feature-title {\n font-size: 10px;\n color: var(--sim-text);\n flex: 1;\n }\n .feature-item.disabled .feature-title {\n color: var(--sim-text-dim);\n }\n /* Checkpoints view styles */\n .checkpoints-view-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n .checkpoints-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 6px;\n flex-shrink: 0;\n }\n .checkpoints-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n overflow-y: auto;\n }\n .checkpoint-item {\n padding: 6px;\n background: var(--sim-bg);\n border: 1px solid var(--sim-border);\n border-left: 3px solid var(--sim-accent);\n border-radius: 2px;\n }\n .checkpoint-item:hover {\n border-color: var(--sim-border-light);\n border-left-color: var(--sim-accent);\n }\n .checkpoint-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n .checkpoint-item .simulator-deeds {\n margin-top: 4px;\n }\n .checkpoint-name {\n color: var(--sim-accent);\n font-weight: bold;\n font-size: 10px;\n }\n .checkpoint-time {\n color: var(--sim-text-dim);\n font-size: 9px;\n }\n`\n","import type { Theme } from \"./types\"\n\nexport const lightTheme: Theme = {\n name: \"Puzzmo (light)\",\n type: \"light\" as \"light\" | \"dark\",\n\n /** The bright Pink */\n key: \"#FFAAAC\",\n /** The color to draw on a key */\n keyFG: \"#000000\",\n /** A bolder version of the bright pink */\n keyStrong: \"#F7868B\",\n /** A lighter version of the bright pink */\n keyLight: \"#FFD2D3\",\n /** The bright Pink */\n g_key: \"#FFAAAC\",\n\n /** A secondary color for the brand, in Puzzmo's case this is the puzzmo yellow */\n subBrand: \"#FFC000\",\n /** Foreground color for the secondary */\n subBrandFG: \"#000000\",\n\n /** The blue you traditionally see around a user avatar and selection */\n player: \"#5DBAFC\",\n /** The color render text above a players */\n playerFG: \"#000000\",\n /** A lighter variant on the user's blue */\n playerLight: \"#9EDDFF\",\n /** Alt color, this one is an earthy green in the main theme */\n alt1: \"#98B389\",\n /** Alt color 2, this one is a faded yellow which looks quite like the puzmo yellow */\n alt2: \"#FAC16C\",\n /** Alt color 3, this one is a cute purple */\n alt3: \"#D298FF\",\n\n /** The foreground body text color */\n fg: \"#000000\",\n /** The red for showing errors */\n error: \"#FF3C3C\",\n\n /** Stays dark regardless of being in light/dark mode */\n alwaysDark: \"#1B1D29\",\n /** Stays light regardless of being in light/dark mode */\n alwaysLight: \"#FFFFFF\",\n\n /** The 'tile' usually */\n g_bg: \"#FFFFFF\",\n /** The 'tile' alternative in checkered patterns */\n g_bgAlt: \"#EBEBEB\",\n /** Sorta useful for content layers on the existing bg, a darker version */\n g_bgDark: \"#D6D6D6\",\n /** When _the text_ is on a light background, this is basically black */\n g_textDark: \"#1B1B28\",\n /** When _the text_ is dark background, this is white */\n g_textLight: \"#FFFFFF\",\n /** For voids in a game, this is black */\n g_blank: \"#000000\",\n /** For when a tile is not yet solved, this is a lighter grey */\n g_unsolved: \"#C2C2C2\",\n /** Border for a game frame, for example the crossword edge */\n g_outline: \"#1B1D29\",\n\n /** The light grey you see as the background for most pages */\n a_bg: \"#F2F2F2\",\n /** The darker grey which usually is seen as borders in the background */\n a_bgAlt: \"#ECECEC\",\n /** The yellow shade of the puzmonaut */\n a_puzmo: \"#FFC000\",\n /** Header colors */\n a_headerText: \"#000000\",\n /** When tabulating results, the background color for an even table row */\n a_table: \"#F9F9F9\",\n /** When tabulating results, the background color for an odd table row */\n a_tableAlt: \"#F6F4F4\",\n /** Inline tag bg (??) */\n a_inlineTag: \"#D9D9D9\",\n /** Used to indicate a link */\n a_anchor: \"#1F97EE\",\n /** The background color of the info bar, couldn't think of a semantic name :D */\n a_infoBG: \"#FFFFFF\",\n}\n\nconst darkTheme: Theme = {\n name: \"Puzzmo (dark)\",\n type: \"dark\" as \"light\" | \"dark\",\n key: \"#FFAAAC\",\n keyFG: \"#000000\",\n keyStrong: \"#F7868B\",\n keyLight: \"#FFD2D3\",\n g_key: \"#FFAAAC\",\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n alt1: \"#98B389\",\n alt2: \"#FAC16C\",\n alt3: \"#D298FF\",\n error: \"#FF3C3C\",\n\n fg: \"#ECECEC\",\n alwaysDark: \"#1B1D29\",\n alwaysLight: \"#FFFFFF\",\n\n g_bg: \"#374351\",\n g_bgAlt: \"#333D49\",\n g_bgDark: \"#2C2F33\",\n g_textDark: \"#F2F2F2\",\n g_textLight: \"#20180E\",\n g_blank: \"#000000\",\n g_unsolved: \"#747474\",\n g_outline: \"#646464\",\n\n a_bg: \"#141620\",\n a_bgAlt: \"#202433\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#A1BAD4\",\n a_table: \"#303246\",\n a_tableAlt: \"#393b52\",\n a_inlineTag: \"#ECECEC\",\n a_anchor: \"#1F97EE\",\n a_infoBG: \"#282C3A\",\n}\n\nconst brightWhiteTheme: Theme = {\n name: \"Bright white\",\n type: \"light\" as \"light\" | \"dark\",\n key: \"#67ced2\",\n keyFG: \"#000000\",\n keyStrong: \"#26a0a5\",\n keyLight: \"#ade6e9\",\n g_key: \"#67ced2\",\n\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n alt1: \"#d26767\",\n alt2: \"#fac16c\",\n alt3: \"#7580bd\",\n\n fg: \"#000000\",\n error: \"#FF3C3C\",\n\n alwaysDark: \"#111111\",\n alwaysLight: \"#FFFFFF\",\n\n g_bg: \"#f6f6f6\",\n g_bgAlt: \"#F4F4F4\",\n g_bgDark: \"#D6D6D6\",\n g_textDark: \"#1B1B28\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#000000\",\n g_unsolved: \"#C2C2C2\",\n g_outline: \"#1B1D29\",\n\n a_bg: \"#FFFFFF\",\n a_bgAlt: \"#f8fbfc\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#000000\",\n a_table: \"#EDEDED\",\n a_tableAlt: \"#DBDBDB\",\n a_inlineTag: \"#D9D9D9\",\n a_anchor: \"#1F97EE\",\n a_infoBG: \"#f6f6f6\",\n}\n\nconst newsPaperDarkTheme: Theme = {\n name: \"News paper dark\",\n type: \"dark\" as \"light\" | \"dark\",\n key: \"#FFAAAC\",\n keyFG: \"#000000\",\n keyStrong: \"#F7868B\",\n keyLight: \"#FFD2D3\",\n g_key: \"#FFAAAC\",\n\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n alt1: \"#98B389\",\n alt2: \"#FAC16C\",\n alt3: \"#D298FF\",\n error: \"#FF3C3C\",\n\n fg: \"#ECECEC\",\n alwaysDark: \"#000000\",\n alwaysLight: \"#FFFFFF\",\n\n g_bg: \"#374351\",\n g_bgAlt: \"#333D49\",\n g_bgDark: \"#2C2F33\",\n g_textDark: \"#F2F2F2\",\n g_textLight: \"#20180E\",\n g_blank: \"#000000\",\n g_unsolved: \"#747474\",\n g_outline: \"#646464\",\n\n a_bg: \"#000000\",\n a_bgAlt: \"#202433\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#A1BAD4\",\n a_table: \"#303246\",\n a_tableAlt: \"#393b52\",\n a_inlineTag: \"#ECECEC\",\n a_anchor: \"#1F97EE\",\n a_infoBG: \"#000000\",\n}\n\nexport const themes: Theme[] = [\n lightTheme,\n darkTheme,\n {\n name: \"Foshay\",\n type: \"light\" as \"light\" | \"dark\",\n key: \"#98B389\",\n keyFG: \"#313540\",\n keyStrong: \"#87BD69\",\n keyLight: \"#809B77\",\n g_key: \"#98B389\",\n\n player: \"#3EC0E5\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n playerFG: \"#292B35\",\n playerLight: \"#A8B5D6\",\n alt1: \"#D87DA4\",\n alt2: \"#E3A54F\",\n alt3: \"#D298FF\",\n error: \"#FF3C3C\",\n\n fg: \"#292B35\",\n alwaysDark: \"#292B35\",\n alwaysLight: \"#D1DBF2\",\n\n g_bg: \"#949EBA\",\n g_bgAlt: \"#8891AB\",\n g_bgDark: \"#7B839B\",\n g_textDark: \"#292B35\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#292B35\",\n g_unsolved: \"#B6B7BA\",\n g_outline: \"#676F83\",\n\n a_bg: \"#828CA3\",\n a_bgAlt: \"#7F889F\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#292B35\",\n a_table: \"#7A8399\",\n a_tableAlt: \"#767E94\",\n a_inlineTag: \"#ECECEC\",\n a_anchor: \"#56C9E9\",\n a_infoBG: \"#767F95\",\n },\n brightWhiteTheme,\n {\n name: \"Submersible\",\n type: \"dark\" as \"light\" | \"dark\",\n key: \"#CD6DC6\",\n keyFG: \"#031698\",\n keyStrong: \"#FF7ABC\",\n keyLight: \"#B467CB\",\n g_key: \"#CD6DC6\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n player: \"#4AB1D1\",\n playerFG: \"#071D47\",\n playerLight: \"#3C99D7\",\n alt1: \"#8BA964\",\n alt2: \"#C69A58\",\n alt3: \"#7644B5\",\n fg: \"#FFEBFF\",\n error: \"#FF3C3C\",\n alwaysDark: \"#031698\",\n alwaysLight: \"#FFEBFF\",\n g_bg: \"#043BED\",\n g_bgAlt: \"#0A31CC\",\n g_bgDark: \"#0E2DA8\",\n g_textDark: \"#031698\",\n g_textLight: \"#FFEBFF\",\n g_blank: \"#031698\",\n g_unsolved: \"#3662F1\",\n g_outline: \"#031698\",\n a_bg: \"#043BED\",\n a_bgAlt: \"#0A31CC\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#FFEBFF\",\n a_table: \"#043BED\",\n a_tableAlt: \"#0A31CC\",\n a_inlineTag: \"#FF7ABC\",\n a_anchor: \"#FF7ABC\",\n a_infoBG: \"#0A31CC\",\n },\n {\n name: \"Hot Dog (beta)\",\n type: \"light\" as \"light\" | \"dark\",\n\n key: \"#FFFF00\",\n keyFG: \"#000000\",\n keyStrong: \"#FFE600\",\n keyLight: \"#FFEC44\",\n g_key: \"#FFFF00\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n player: \"#FF7A00\",\n playerFG: \"#000000\",\n playerLight: \"#FFA450\",\n alt1: \"#3ABC5E\",\n alt2: \"#5C3ABC\",\n alt3: \"#BC3AAF\",\n fg: \"#000000\",\n error: \"#FF3C3C\",\n alwaysDark: \"#1B1D29\",\n alwaysLight: \"#FFFFFF\",\n g_bg: \"#EBFF00\",\n g_bgAlt: \"#EBEBEB\",\n g_bgDark: \"#D6D6D6\",\n g_textDark: \"#1B1B28\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#000000\",\n g_unsolved: \"#C2C2C2\",\n g_outline: \"#1B1D29\",\n a_bg: \"#FF0000\",\n a_bgAlt: \"#E50000\",\n a_puzmo: \"#FFC000\",\n a_headerText: \"#FFFFFF\",\n a_table: \"#E50000\",\n a_tableAlt: \"#FF2525\",\n a_inlineTag: \"#000000\",\n a_anchor: \"#000000\",\n a_infoBG: \"#C6C6C6\",\n },\n {\n name: \"Outlook Hayesy (beta)\",\n type: \"light\" as \"light\" | \"dark\",\n key: \"#DAB98C\",\n keyFG: \"#000000\",\n keyStrong: \"#B99368\",\n keyLight: \"#DAB98C\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n g_key: \"#DAB98C\",\n player: \"#5DBAFC\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n alt1: \"#5EC386\",\n alt2: \"#5E93C3\",\n alt3: \"#C35E5E\",\n fg: \"#000000\",\n error: \"#FF3C3C\",\n alwaysDark: \"#5E390F\",\n alwaysLight: \"#FFF3E4\",\n g_bg: \"#FFFFFF\",\n g_bgAlt: \"#EBEBEB\",\n g_bgDark: \"#D6D6D6\",\n g_textDark: \"#1B1B28\",\n g_textLight: \"#FFFFFF\",\n g_blank: \"#000000\",\n g_unsolved: \"#C2C2C2\",\n g_outline: \"#1B1D29\",\n\n a_bg: \"#FEF5E8\",\n a_bgAlt: \"#ffffff\",\n a_puzmo: \"#FAC7CE\",\n a_headerText: \"#000000\",\n a_table: \"#FAE8D3\",\n a_tableAlt: \"#EED7BC\",\n a_inlineTag: \"#D9D9D9\",\n a_anchor: \"#B99368\",\n a_infoBG: \"#FFFFFF\",\n },\n {\n name: \"Console (beta)\",\n type: \"dark\" as \"light\" | \"dark\",\n\n key: \"#957df9\",\n keyFG: \"#000000\",\n\n keyStrong: \"#590FF5\",\n keyLight: \"#590FF5\",\n subBrand: \"#FFC000\",\n subBrandFG: \"#000000\",\n g_key: \"#FFFF00\",\n player: \"#ffffff\",\n playerFG: \"#000000\",\n playerLight: \"#9EDDFF\",\n alt1: \"#590ff5\",\n alt2: \"#00e200\",\n alt3: \"#ff8a02\",\n\n fg: \"#00e200\",\n error: \"#FF3C3C\",\n\n alwaysDark: \"#1B1D29\",\n alwaysLight: \"#00e200\",\n\n g_bg: \"#101428\",\n g_bgAlt: \"#101428\",\n g_bgDark: \"#00e200\",\n g_textDark: \"#00e200\",\n g_textLight: \"#590ff5\",\n g_blank: \"#00e200\",\n g_unsolved: \"#0000ff\",\n g_outline: \"#00e200\",\n\n a_bg: \"#000000\",\n a_bgAlt: \"#112211\",\n a_puzmo: \"#ffffff\",\n a_headerText: \"#00e200\",\n a_table: \"#000000\",\n a_tableAlt: \"#002200\",\n a_inlineTag: \"#D9D9D9\",\n a_anchor: \"#957df9\",\n a_infoBG: \"#001900\",\n },\n]\n\nexport const defaultLightTheme = lightTheme\nexport const defaultDarkTheme = darkTheme\nexport const newsPaperLightTheme = brightWhiteTheme\n\nexport const modifiedNewsPaperTheme = (type: \"light\" | \"dark\", hexes: string[] | readonly string[], partnerSlug: string) => {\n const baseTheme = (type === \"light\" ? newsPaperLightTheme : newsPaperDarkTheme) as Theme\n\n const theme = {\n ...baseTheme,\n name: baseTheme.name + partnerSlug ? `-(${partnerSlug})` : \"\",\n a_puzmo: hexes[0] || baseTheme.a_puzmo,\n key: hexes[1] || baseTheme.key,\n keyLight: hexes[2] || baseTheme.keyLight,\n keyStrong: hexes[3] || baseTheme.keyStrong,\n alt1: hexes[4] || baseTheme.alt1,\n alt2: hexes[5] || baseTheme.alt2,\n alt3: hexes[6] || baseTheme.alt3,\n keyFG: hexes[7] || baseTheme.player,\n player: hexes[8] || baseTheme.player,\n playerLight: hexes[9] || baseTheme.playerLight,\n playerFG: hexes[10] || baseTheme.playerFG,\n subBrand: hexes[11] || baseTheme.subBrand,\n subBrandFG: hexes[12] || baseTheme.subBrandFG,\n g_key: hexes[13] || baseTheme.subBrandFG,\n a_infoBG: type === \"light\" ? baseTheme.alwaysLight : baseTheme.alwaysDark,\n }\n return theme\n}\n\nexport type PuzmoTheme = typeof lightTheme\n\n// Useless JS until we get 'satisfies' in TS 4.9\nconst validThemes = (_theme: readonly Theme[]): _theme is PuzmoTheme[] => true\nvalidThemes(themes)\n","import type { Theme, ThumbnailConfig } from \"../types\"\nimport { themes } from \"../themes\"\nimport type { SimulatorConfig, SimulatorState, TabName } from \"./types\"\n\nconst STORAGE_KEYS = {\n collapsed: \"simulator-collapsed\",\n tab: \"simulator-tab\",\n theme: \"simulator-theme\",\n fixtureCategory: \"simulator-fixture-category\",\n fixturePuzzle: \"simulator-fixture-puzzle\",\n renderHost: \"simulator-render-host\",\n renderContext: \"simulator-render-context\",\n} as const\n\nfunction getStoredTheme(): Theme {\n const storedThemeName = localStorage.getItem(STORAGE_KEYS.theme)\n if (storedThemeName) {\n const found = themes.find((t) => t.name === storedThemeName)\n if (found) return found\n }\n return themes[0] // Default to first theme (light)\n}\n\nfunction getStoredTab(validTabIds: string[]): TabName {\n const storedTab = localStorage.getItem(STORAGE_KEYS.tab)\n if (storedTab && validTabIds.includes(storedTab)) {\n return storedTab\n }\n return validTabIds[0] ?? \"ctrl\"\n}\n\nfunction getStoredCollapsed(configDefault: boolean): boolean {\n const storedCollapsed = localStorage.getItem(STORAGE_KEYS.collapsed)\n if (storedCollapsed !== null) {\n return storedCollapsed === \"true\"\n }\n return configDefault\n}\n\nfunction getStoredRenderHost(): ThumbnailConfig[\"renderHost\"] {\n const stored = localStorage.getItem(STORAGE_KEYS.renderHost)\n if (stored && [\"game\", \"app\", \"opengraph\"].includes(stored)) {\n return stored as ThumbnailConfig[\"renderHost\"]\n }\n return \"game\"\n}\n\nfunction getStoredRenderContext(): ThumbnailConfig[\"renderContext\"] {\n const stored = localStorage.getItem(STORAGE_KEYS.renderContext)\n if (stored && [\"preview\", \"share\", \"completed\", \"timeline\"].includes(stored)) {\n return stored as ThumbnailConfig[\"renderContext\"]\n }\n return undefined\n}\n\nexport function createInitialState(config: SimulatorConfig, fixtureCategories: string[], validTabIds: string[]): SimulatorState {\n const storedFixtureCategory = localStorage.getItem(STORAGE_KEYS.fixtureCategory)\n const storedFixturePuzzle = localStorage.getItem(STORAGE_KEYS.fixturePuzzle)\n\n return {\n isCollapsed: getStoredCollapsed(config.collapsed ?? true),\n isPaused: false,\n hasStarted: false,\n activeTab: getStoredTab(validTabIds),\n puzzleData: null,\n originalPuzzle: \"\",\n currentInputStr: \"\",\n completionData: null,\n selectedTheme: getStoredTheme(),\n selectedCategory:\n storedFixtureCategory && fixtureCategories.includes(storedFixtureCategory) ? storedFixtureCategory : (fixtureCategories[0] ?? null),\n selectedPuzzle: storedFixturePuzzle ?? null,\n renderHost: getStoredRenderHost(),\n renderContext: getStoredRenderContext(),\n }\n}\n\nexport function persistCollapsed(collapsed: boolean): void {\n localStorage.setItem(STORAGE_KEYS.collapsed, String(collapsed))\n}\n\nexport function persistTab(tab: TabName): void {\n localStorage.setItem(STORAGE_KEYS.tab, tab)\n}\n\nexport function persistTheme(themeName: string): void {\n localStorage.setItem(STORAGE_KEYS.theme, themeName)\n}\n\nexport function persistFixtureCategory(category: string): void {\n localStorage.setItem(STORAGE_KEYS.fixtureCategory, category)\n}\n\nexport function persistFixturePuzzle(puzzle: string): void {\n localStorage.setItem(STORAGE_KEYS.fixturePuzzle, puzzle)\n}\n\nexport function clearFixturePuzzle(): void {\n localStorage.removeItem(STORAGE_KEYS.fixturePuzzle)\n}\n\nexport function persistRenderHost(host: ThumbnailConfig[\"renderHost\"]): void {\n if (host) {\n localStorage.setItem(STORAGE_KEYS.renderHost, host)\n } else {\n localStorage.removeItem(STORAGE_KEYS.renderHost)\n }\n}\n\nexport function persistRenderContext(context: ThumbnailConfig[\"renderContext\"]): void {\n if (context) {\n localStorage.setItem(STORAGE_KEYS.renderContext, context)\n } else {\n localStorage.removeItem(STORAGE_KEYS.renderContext)\n }\n}\n","import type { MessagesReceived } from \"../types\"\nimport type { MessageLogEntry } from \"./types\"\n\nexport interface MessageLogger {\n log: (type: string, data: any, direction: \"in\" | \"out\") => void\n clear: () => void\n getLog: () => MessageLogEntry[]\n}\n\nexport function createMessageLogger(onLog?: (entry: MessageLogEntry) => void): MessageLogger {\n const messageLog: MessageLogEntry[] = []\n\n return {\n log(type: string, data: any, direction: \"in\" | \"out\") {\n const time = new Date().toLocaleTimeString(\"en-US\", {\n hour12: false,\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n })\n const entry: MessageLogEntry = { type, data, time, direction }\n messageLog.push(entry)\n\n // Limit log size\n if (messageLog.length > 100) messageLog.shift()\n\n onLog?.(entry)\n },\n clear() {\n messageLog.length = 0\n },\n getLog() {\n return messageLog\n },\n }\n}\n\nexport function sendToGame(type: keyof MessagesReceived, data: any, logger?: MessageLogger): void {\n console.log(\"Simulator sending:\", type, data)\n logger?.log(type, data, \"out\")\n window.postMessage({ type, data }, \"*\")\n}\n\nexport type MessageHandler = (type: string, data: any) => void\n\nexport function createMessageListener(handler: MessageHandler, logger?: MessageLogger): () => void {\n const listener = (event: MessageEvent) => {\n if (!event?.data?.type) return\n\n const type = event.data.type\n // Handle both { data } and { json } formats from host\n const data = event.data.data ?? event.data.json ?? {}\n\n // Skip logging for high-frequency timer messages\n const skipLogTypes = [\"TIMER_TICK\", \"TIMER_SYNC\"]\n if (!skipLogTypes.includes(type)) {\n logger?.log(type, data, \"in\")\n }\n\n handler(type, data)\n }\n\n window.addEventListener(\"message\", listener)\n\n // Return cleanup function\n return () => window.removeEventListener(\"message\", listener)\n}\n","import type { FixtureImports } from \"./types\"\n\n/**\n * Parse fixture imports into a structured format: { category: { filename: data } }\n */\nexport function parseFixtures(fixtures: FixtureImports): Map<string, Map<string, any>> {\n const result = new Map<string, Map<string, any>>()\n\n console.log(\"Simulator: Parsing fixtures\", Object.keys(fixtures))\n\n for (const [path, module] of Object.entries(fixtures)) {\n // Extract category and filename from path like \"./fixtures/puzzles/3x3/puzzle_1.json\"\n const parts = path.split(\"/\")\n const filename = parts.pop()?.replace(\".json\", \"\") ?? \"\"\n const category = parts.pop() ?? \"default\"\n\n if (!result.has(category)) {\n result.set(category, new Map())\n }\n\n const data = (module as any).default ?? module\n result.get(category)!.set(filename, data)\n }\n\n // Sort puzzles naturally (puzzle_1, puzzle_2, ... puzzle_10, puzzle_11)\n result.forEach((puzzles, category) => {\n const sorted = new Map<string, any>(\n Array.from(puzzles.entries()).sort((a, b) => {\n const numA = parseInt(a[0].match(/\\d+/)?.[0] ?? \"0\")\n const numB = parseInt(b[0].match(/\\d+/)?.[0] ?? \"0\")\n return numA - numB\n }),\n )\n result.set(category, sorted)\n })\n\n return result\n}\n\n/**\n * Render fixture selector HTML\n */\nexport function renderFixtureSelector(categories: string[], selectedCategory: string | null): string {\n if (categories.length === 0) return \"\"\n\n return `\n <div class=\"simulator-fixtures\">\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Category</label>\n <select class=\"simulator-select\" id=\"simulator-fixture-category\">\n ${categories.map((cat) => `<option value=\"${cat}\" ${cat === selectedCategory ? \"selected\" : \"\"}>${cat}</option>`).join(\"\")}\n </select>\n </div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Puzzle</label>\n <select class=\"simulator-select\" id=\"simulator-fixture-puzzle\"></select>\n </div>\n </div>\n `\n}\n\n/**\n * Update puzzle select options based on selected category\n */\nexport function updatePuzzleOptions(\n puzzleSelect: HTMLSelectElement,\n fixtures: Map<string, Map<string, any>>,\n category: string,\n selectedPuzzle: string | null,\n): string | null {\n const puzzles = fixtures.get(category)\n if (!puzzles) return null\n\n const puzzleNames = Array.from(puzzles.keys())\n\n // If stored puzzle exists in this category, use it; otherwise use first\n let actualSelected = selectedPuzzle\n if (!selectedPuzzle || !puzzles.has(selectedPuzzle)) {\n actualSelected = puzzleNames[0] ?? null\n }\n\n puzzleSelect.innerHTML = puzzleNames\n .map((name) => `<option value=\"${name}\" ${name === actualSelected ? \"selected\" : \"\"}>${name}</option>`)\n .join(\"\")\n\n return actualSelected\n}\n\n/**\n * Get puzzle data from fixtures\n */\nexport function getFixturePuzzle(fixtures: Map<string, Map<string, any>>, category: string | null, puzzle: string | null): any | null {\n if (!category || !puzzle) return null\n return fixtures.get(category)?.get(puzzle) ?? null\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\nimport { renderFixtureSelector, updatePuzzleOptions, getFixturePuzzle } from \"../fixtures\"\nimport { persistFixtureCategory, persistFixturePuzzle, clearFixturePuzzle } from \"../state\"\n\nexport function createCtrlView(): SimulatorView {\n return {\n id: \"ctrl\",\n label: \"Ctrl\",\n\n render() {\n return `\n <div id=\"simulator-fixtures-container\"></div>\n <div id=\"simulator-status\">\n <span class=\"indicator waiting\"></span>\n <span class=\"text\">Waiting for READY...</span>\n </div>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn primary\" id=\"simulator-start\" disabled>Start</button>\n <button class=\"simulator-btn\" id=\"simulator-pause\" disabled>Pause</button>\n </div>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn danger\" id=\"simulator-retry\" disabled>Retry</button>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const startBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-start\")\n const pauseBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-pause\")\n const retryBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-retry\")\n const fixturesContainer = ctx.getElement<HTMLElement>(\"#simulator-fixtures-container\")\n\n console.log(\"Simulator: CtrlView.bind called\", {\n hasFixtures: !!ctx.fixtures,\n categories: ctx.fixtureCategories,\n selectedCategory: ctx.state.selectedCategory,\n selectedPuzzle: ctx.state.selectedPuzzle,\n })\n\n // Set up fixtures UI if fixtures are provided\n if (ctx.fixtures && ctx.fixtureCategories.length > 0 && fixturesContainer) {\n // Clear any existing content (in case bind is called multiple times)\n fixturesContainer.innerHTML = renderFixtureSelector(ctx.fixtureCategories, ctx.state.selectedCategory)\n\n const categorySelect = ctx.getElement<HTMLSelectElement>(\"#simulator-fixture-category\")\n const puzzleSelect = ctx.getElement<HTMLSelectElement>(\"#simulator-fixture-puzzle\")\n\n if (categorySelect && puzzleSelect && ctx.state.selectedCategory) {\n // Update puzzle options\n ctx.state.selectedPuzzle = updatePuzzleOptions(puzzleSelect, ctx.fixtures, ctx.state.selectedCategory, ctx.state.selectedPuzzle)\n\n // Load initial puzzle data\n const puzzleData = getFixturePuzzle(ctx.fixtures, ctx.state.selectedCategory, ctx.state.selectedPuzzle)\n console.log(\"Simulator: Loading fixture puzzle\", {\n category: ctx.state.selectedCategory,\n puzzle: ctx.state.selectedPuzzle,\n hasPuzzleData: !!puzzleData,\n })\n if (puzzleData) {\n ctx.state.puzzleData = puzzleData\n ctx.state.originalPuzzle = JSON.stringify(puzzleData, null, 2)\n if (ctx.state.selectedCategory) persistFixtureCategory(ctx.state.selectedCategory)\n if (ctx.state.selectedPuzzle) persistFixturePuzzle(ctx.state.selectedPuzzle)\n }\n\n // Category change handler\n categorySelect.addEventListener(\"change\", () => {\n console.log(\"Simulator: Category changed, reloading...\", categorySelect.value)\n persistFixtureCategory(categorySelect.value)\n clearFixturePuzzle()\n window.location.reload()\n })\n\n // Puzzle change handler\n puzzleSelect.addEventListener(\"change\", () => {\n console.log(\"Simulator: Puzzle changed, reloading...\", puzzleSelect.value)\n persistFixturePuzzle(puzzleSelect.value)\n window.location.reload()\n })\n }\n }\n\n // Button handlers\n startBtn?.addEventListener(\"click\", () => {\n if (ctx.state.isPaused) {\n ctx.sendToGame(\"RESUME_GAME\", {})\n ctx.state.isPaused = false\n if (pauseBtn) pauseBtn.textContent = \"Pause\"\n ctx.updateStatus(\"Running\", \"ready\")\n } else {\n ctx.sendToGame(\"START_GAME\", undefined)\n ctx.state.hasStarted = true\n if (pauseBtn) pauseBtn.disabled = false\n if (startBtn) startBtn.textContent = \"Resume\"\n ctx.updateStatus(\"Running\", \"ready\")\n }\n })\n\n pauseBtn?.addEventListener(\"click\", () => {\n if (ctx.state.isPaused) {\n ctx.sendToGame(\"RESUME_GAME\", {})\n ctx.state.isPaused = false\n pauseBtn.textContent = \"Pause\"\n ctx.updateStatus(\"Running\", \"ready\")\n } else {\n ctx.sendToGame(\"PAUSE_GAME\", {})\n ctx.state.isPaused = true\n pauseBtn.textContent = \"Resume\"\n ctx.updateStatus(\"Paused\", \"paused\")\n }\n })\n\n retryBtn?.addEventListener(\"click\", () => {\n ctx.sendToGame(\"RETRY_PUZZLE\", {})\n ctx.state.hasStarted = false\n ctx.state.isPaused = false\n if (pauseBtn) {\n pauseBtn.disabled = true\n pauseBtn.textContent = \"Pause\"\n }\n if (startBtn) startBtn.textContent = \"Start\"\n ctx.updateStatus(\"Ready to retry\", \"ready\")\n })\n },\n\n onMessage(type: string, _data: any, ctx: SimulatorContext) {\n const startBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-start\")\n const pauseBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-pause\")\n const retryBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-retry\")\n\n if (type === \"READY_GAME_LOADED\") {\n if (startBtn) startBtn.disabled = false\n if (retryBtn) retryBtn.disabled = false\n ctx.updateStatus(\"Ready\", \"ready\")\n }\n\n if (type === \"GAME_COMPLETED\") {\n ctx.state.hasStarted = false\n if (pauseBtn) pauseBtn.disabled = true\n if (startBtn) startBtn.disabled = true\n ctx.updateStatus(\"Completed!\", \"ready\")\n }\n },\n }\n}\n","import type { AppBundle, ThumbnailConfig } from \"../../types\"\nimport type { SimulatorContext, SimulatorView } from \"../types\"\n\n// Storage key for saved states (global across all puzzles)\nconst SAVED_STATES_KEY = \"simulator-saved-states\"\n\n/**\n * Find thumbnail function on globalThis (looks for functions ending in \"Thumbnail\")\n */\nfunction findThumbnailFn(): { name: string; fn: AppBundle[\"renderThumbnail\"] } | null {\n const globalObj = globalThis as Record<string, unknown>\n for (const key of Object.keys(globalObj)) {\n if (key.endsWith(\"Thumbnail\") && typeof globalObj[key] === \"function\") {\n return { name: key, fn: globalObj[key] as AppBundle[\"renderThumbnail\"] }\n }\n }\n return null\n}\n\n/**\n * Generate a small thumbnail SVG for a given input string\n */\nfunction generateMiniThumbnail(ctx: SimulatorContext, inputStr: string): string {\n const thumbFn = findThumbnailFn()\n if (!thumbFn) return \"\"\n\n try {\n const puzzleStr = ctx.state.puzzleData ? JSON.stringify(ctx.state.puzzleData) : \"\"\n const thumbnailConfig: ThumbnailConfig = {\n viewerIsOwner: true,\n theme: ctx.state.selectedTheme,\n strict: true,\n renderHost: \"game\",\n }\n return thumbFn.fn(puzzleStr, inputStr, thumbnailConfig)\n } catch {\n return \"\"\n }\n}\n\n// Input string history entry\ninterface HistoryEntry {\n value: string\n timestamp: number\n}\n\n// Saved state entry (includes puzzle since you can save any puzzle + input combination)\ninterface SavedState {\n name: string\n puzzleStr: string\n inputStr: string\n timestamp: number\n}\n\n// Module-level state for history (persists across tab switches)\nlet inputHistory: HistoryEntry[] = []\n\n// Helper to auto-resize a textarea based on content\nfunction autoResizeTextarea(textarea: HTMLTextAreaElement, maxRows: number = 8) {\n const lineHeight = 14 // Based on font size 10px with line-height 1.4\n const padding = 8 // 4px padding top + bottom\n const border = 4 // 2px border top + bottom\n const minHeight = lineHeight * 2 + padding + border // Minimum 2 rows\n const maxHeight = lineHeight * maxRows + padding + border\n\n textarea.style.height = \"auto\"\n const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight)\n textarea.style.height = `${newHeight}px`\n}\n\nexport function createDataView(): SimulatorView {\n // Track original values for change detection\n let originalPuzzleValue = \"\"\n let originalInputValue = \"\"\n\n return {\n id: \"data\",\n label: \"Data\",\n\n render() {\n return `\n <div class=\"data-view-container\">\n <div class=\"data-subtabs\">\n <button class=\"data-subtab active\" data-subtab=\"edit\">Edit</button>\n <button class=\"data-subtab\" data-subtab=\"history\">History</button>\n <button class=\"data-subtab\" data-subtab=\"saves\">Saves</button>\n </div>\n\n <div class=\"data-subtab-content active\" id=\"data-subtab-edit\">\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Puzzle String</label>\n <textarea class=\"simulator-textarea auto-resize\" id=\"simulator-puzzle\" placeholder=\"Loading...\"></textarea>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn subtle\" id=\"simulator-puzzle-reset\">Reset</button>\n <button class=\"simulator-btn primary\" id=\"simulator-puzzle-apply\" disabled>Apply</button>\n </div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Input String</label>\n <textarea class=\"simulator-textarea auto-resize\" id=\"simulator-input\" placeholder=\"No input yet...\"></textarea>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn subtle\" id=\"simulator-input-reset\">Reset</button>\n <button class=\"simulator-btn primary\" id=\"simulator-input-apply\" disabled>Apply</button>\n </div>\n </div>\n </div>\n\n <div class=\"data-subtab-content\" id=\"data-subtab-history\">\n <div class=\"history-header\">\n <span class=\"simulator-label\">Input String Timeline</span>\n <button class=\"simulator-btn tiny\" id=\"simulator-history-clear\">Clear</button>\n </div>\n <div class=\"history-list\" id=\"simulator-history-list\">\n <div class=\"simulator-empty\">No history yet</div>\n </div>\n </div>\n\n <div class=\"data-subtab-content\" id=\"data-subtab-saves\">\n <div class=\"save-new\">\n <input type=\"text\" class=\"simulator-input\" id=\"simulator-save-name\" placeholder=\"Save name...\" />\n <button class=\"simulator-btn primary\" id=\"simulator-save-btn\">Save</button>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"saves-list\" id=\"simulator-saves-list\">\n <div class=\"simulator-empty\">No saved states</div>\n </div>\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n // Sub-tab switching\n const subtabs = ctx.getElement<HTMLElement>(\".data-subtabs\")\n subtabs?.querySelectorAll(\".data-subtab\").forEach((tab) => {\n tab.addEventListener(\"click\", () => {\n const subtabName = tab.getAttribute(\"data-subtab\")\n if (!subtabName) return\n\n // Update active tab button\n subtabs.querySelectorAll(\".data-subtab\").forEach((t) => t.classList.remove(\"active\"))\n tab.classList.add(\"active\")\n\n // Update active content\n const container = ctx.getElement<HTMLElement>(\".data-view-container\")\n container?.querySelectorAll(\".data-subtab-content\").forEach((content) => {\n content.classList.toggle(\"active\", content.id === `data-subtab-${subtabName}`)\n })\n\n // Refresh content when switching\n if (subtabName === \"edit\") {\n refreshEditTab(ctx)\n } else if (subtabName === \"history\") {\n renderHistory(ctx)\n } else if (subtabName === \"saves\") {\n renderSaves(ctx)\n }\n })\n })\n\n // Edit tab elements\n const puzzleTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n const puzzleResetBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-puzzle-reset\")\n const puzzleApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-puzzle-apply\")\n const inputResetBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-reset\")\n const inputApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-apply\")\n const startBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-start\")\n const pauseBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-pause\")\n\n // Initialize puzzle textarea - defer to allow CtrlView to set state first\n setTimeout(() => {\n if (puzzleTextarea && ctx.state.originalPuzzle) {\n puzzleTextarea.value = ctx.state.originalPuzzle\n originalPuzzleValue = ctx.state.originalPuzzle\n autoResizeTextarea(puzzleTextarea)\n }\n\n // Initialize input textarea\n if (inputTextarea && ctx.state.currentInputStr) {\n inputTextarea.value = ctx.state.currentInputStr\n originalInputValue = ctx.state.currentInputStr\n autoResizeTextarea(inputTextarea)\n }\n }, 0)\n\n // Auto-resize and change detection for puzzle\n puzzleTextarea?.addEventListener(\"input\", () => {\n autoResizeTextarea(puzzleTextarea)\n const hasChanges = puzzleTextarea.value !== originalPuzzleValue\n if (puzzleApplyBtn) puzzleApplyBtn.disabled = !hasChanges\n })\n\n // Auto-resize and change detection for input\n inputTextarea?.addEventListener(\"input\", () => {\n autoResizeTextarea(inputTextarea)\n const hasChanges = inputTextarea.value !== originalInputValue\n if (inputApplyBtn) inputApplyBtn.disabled = !hasChanges\n })\n\n // Puzzle reset\n puzzleResetBtn?.addEventListener(\"click\", () => {\n if (puzzleTextarea) {\n puzzleTextarea.value = ctx.state.originalPuzzle\n originalPuzzleValue = ctx.state.originalPuzzle\n autoResizeTextarea(puzzleTextarea)\n if (puzzleApplyBtn) puzzleApplyBtn.disabled = true\n }\n })\n\n // Puzzle apply\n puzzleApplyBtn?.addEventListener(\"click\", () => {\n if (!puzzleTextarea) return\n try {\n const newPuzzle = JSON.parse(puzzleTextarea.value)\n ctx.state.puzzleData = newPuzzle\n ctx.state.originalPuzzle = puzzleTextarea.value\n originalPuzzleValue = puzzleTextarea.value\n // Trigger a retry with the new puzzle\n ctx.sendToGame(\"RETRY_PUZZLE\", {})\n ctx.state.hasStarted = false\n ctx.state.isPaused = false\n if (pauseBtn) {\n pauseBtn.disabled = true\n pauseBtn.textContent = \"Pause\"\n }\n if (startBtn) startBtn.textContent = \"Start\"\n ctx.updateStatus(\"Puzzle updated\", \"ready\")\n puzzleApplyBtn.disabled = true\n } catch (e) {\n console.error(\"Simulator: Invalid puzzle JSON\", e)\n ctx.updateStatus(\"Invalid JSON\", \"paused\")\n }\n })\n\n // Input reset\n inputResetBtn?.addEventListener(\"click\", () => {\n if (inputTextarea) {\n inputTextarea.value = originalInputValue\n autoResizeTextarea(inputTextarea)\n if (inputApplyBtn) inputApplyBtn.disabled = true\n }\n })\n\n // Input apply\n inputApplyBtn?.addEventListener(\"click\", () => {\n if (inputTextarea) {\n ctx.state.currentInputStr = inputTextarea.value\n originalInputValue = inputTextarea.value\n console.log(\"Simulator: Input string updated (game restart required to apply)\")\n ctx.updateStatus(\"Input stored\", \"ready\")\n inputApplyBtn.disabled = true\n }\n })\n\n // History tab\n const historyClearBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-history-clear\")\n historyClearBtn?.addEventListener(\"click\", () => {\n inputHistory = []\n renderHistory(ctx)\n })\n\n // Saves tab\n const saveNameInput = ctx.getElement<HTMLInputElement>(\"#simulator-save-name\")\n const saveBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-save-btn\")\n\n saveBtn?.addEventListener(\"click\", () => {\n if (!saveNameInput || !saveNameInput.value.trim()) return\n\n const savedStates = getSavedStates()\n const newState: SavedState = {\n name: saveNameInput.value.trim(),\n puzzleStr: ctx.state.originalPuzzle,\n inputStr: ctx.state.currentInputStr,\n timestamp: Date.now(),\n }\n savedStates.push(newState)\n localStorage.setItem(SAVED_STATES_KEY, JSON.stringify(savedStates))\n saveNameInput.value = \"\"\n renderSaves(ctx)\n })\n\n // Initial render of saves\n renderSaves(ctx)\n },\n\n onActivate(ctx: SimulatorContext) {\n // Update original values for change detection\n originalPuzzleValue = ctx.state.originalPuzzle\n originalInputValue = ctx.state.currentInputStr\n\n // Refresh edit tab content\n refreshEditTab(ctx)\n\n // Refresh history\n renderHistory(ctx)\n },\n\n onMessage(type: string, data: any, ctx: SimulatorContext) {\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n const inputApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-apply\")\n\n // boardState can be at data.boardState (legacy) or data.input.boardState (current SDK format)\n const boardState = data?.input?.boardState ?? data?.boardState\n if (type === \"UPLOAD_NEW_GAME_STATE\" && boardState) {\n ctx.state.currentInputStr = boardState\n\n // Add to history if different from last entry\n if (inputHistory.length === 0 || inputHistory[inputHistory.length - 1].value !== boardState) {\n inputHistory.push({ value: boardState, timestamp: Date.now() })\n // Keep history reasonable (last 100 entries)\n if (inputHistory.length > 100) {\n inputHistory = inputHistory.slice(-100)\n }\n\n // Update history view live if it's currently visible\n const historyTab = ctx.getElement<HTMLElement>(\"#data-subtab-history\")\n if (historyTab?.classList.contains(\"active\")) {\n renderHistory(ctx)\n }\n }\n\n if (inputTextarea) {\n inputTextarea.value = ctx.state.currentInputStr\n originalInputValue = ctx.state.currentInputStr\n autoResizeTextarea(inputTextarea)\n if (inputApplyBtn) inputApplyBtn.disabled = true\n }\n }\n },\n }\n}\n\n// Helper to get saved states from localStorage\nfunction getSavedStates(): SavedState[] {\n try {\n const stored = localStorage.getItem(SAVED_STATES_KEY)\n return stored ? JSON.parse(stored) : []\n } catch {\n return []\n }\n}\n\n// Render the history list\nfunction renderHistory(ctx: SimulatorContext) {\n const historyList = ctx.getElement<HTMLElement>(\"#simulator-history-list\")\n if (!historyList) return\n\n // Set theme background for thumbnails\n historyList.style.setProperty(\"--history-thumb-bg\", ctx.state.selectedTheme.a_bg)\n\n if (inputHistory.length === 0) {\n historyList.innerHTML = '<div class=\"simulator-empty\">No history yet</div>'\n return\n }\n\n // Show most recent first\n const reversedHistory = [...inputHistory].reverse()\n historyList.innerHTML = reversedHistory\n .map((entry, idx) => {\n const realIdx = inputHistory.length - 1 - idx\n const time = new Date(entry.timestamp).toLocaleTimeString()\n const preview = entry.value.length > 60 ? entry.value.slice(0, 60) + \"...\" : entry.value\n const thumbnail = generateMiniThumbnail(ctx, entry.value)\n return `\n <div class=\"history-item\" data-history-idx=\"${realIdx}\">\n <div class=\"history-item-thumb\">${thumbnail}</div>\n <div class=\"history-item-content\">\n <div class=\"history-item-header\">\n <span class=\"history-item-num\">#${realIdx + 1}</span>\n <span class=\"history-item-time\">${time}</span>\n <button class=\"simulator-btn tiny history-restore-btn\" data-history-idx=\"${realIdx}\">Restore</button>\n </div>\n <div class=\"history-item-preview\">${escapeHtml(preview)}</div>\n </div>\n </div>\n `\n })\n .join(\"\")\n\n // Bind restore buttons\n historyList.querySelectorAll(\".history-restore-btn\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const idx = parseInt((e.target as HTMLElement).getAttribute(\"data-history-idx\") || \"0\")\n const entry = inputHistory[idx]\n if (entry) {\n ctx.state.currentInputStr = entry.value\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n if (inputTextarea) {\n inputTextarea.value = entry.value\n autoResizeTextarea(inputTextarea)\n }\n ctx.updateStatus(\"Input restored from history\", \"ready\")\n\n // Switch to edit tab\n const editTab = ctx.getElement<HTMLElement>('.data-subtab[data-subtab=\"edit\"]')\n editTab?.click()\n }\n })\n })\n}\n\n// Render the saves list\nfunction renderSaves(ctx: SimulatorContext) {\n const savesList = ctx.getElement<HTMLElement>(\"#simulator-saves-list\")\n if (!savesList) return\n\n const savedStates = getSavedStates()\n\n if (savedStates.length === 0) {\n savesList.innerHTML = '<div class=\"simulator-empty\">No saved states</div>'\n return\n }\n\n // Show most recent first\n const reversedSaves = [...savedStates].reverse()\n savesList.innerHTML = reversedSaves\n .map((state, idx) => {\n const realIdx = savedStates.length - 1 - idx\n const date = new Date(state.timestamp).toLocaleDateString()\n const time = new Date(state.timestamp).toLocaleTimeString()\n return `\n <div class=\"save-item\">\n <div class=\"save-item-header\">\n <span class=\"save-item-name\">${escapeHtml(state.name)}</span>\n <span class=\"save-item-time\">${date} ${time}</span>\n </div>\n <div class=\"save-item-actions\">\n <button class=\"simulator-btn tiny save-load-btn\" data-save-idx=\"${realIdx}\">Load</button>\n <button class=\"simulator-btn tiny danger save-delete-btn\" data-save-idx=\"${realIdx}\">Delete</button>\n </div>\n </div>\n `\n })\n .join(\"\")\n\n // Bind load buttons\n savesList.querySelectorAll(\".save-load-btn\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const idx = parseInt((e.target as HTMLElement).getAttribute(\"data-save-idx\") || \"0\")\n const state = savedStates[idx]\n if (state) {\n // Update puzzle\n try {\n ctx.state.puzzleData = JSON.parse(state.puzzleStr)\n ctx.state.originalPuzzle = state.puzzleStr\n } catch {\n // If invalid JSON, just set the string\n ctx.state.originalPuzzle = state.puzzleStr\n }\n\n // Update input\n ctx.state.currentInputStr = state.inputStr\n\n // Refresh UI\n const puzzleTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n if (puzzleTextarea) {\n puzzleTextarea.value = state.puzzleStr\n autoResizeTextarea(puzzleTextarea)\n }\n if (inputTextarea) {\n inputTextarea.value = state.inputStr\n autoResizeTextarea(inputTextarea)\n }\n\n ctx.updateStatus(`Loaded: ${state.name}`, \"ready\")\n\n // Switch to edit tab\n const editTab = ctx.getElement<HTMLElement>('.data-subtab[data-subtab=\"edit\"]')\n editTab?.click()\n }\n })\n })\n\n // Bind delete buttons\n savesList.querySelectorAll(\".save-delete-btn\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const idx = parseInt((e.target as HTMLElement).getAttribute(\"data-save-idx\") || \"0\")\n const updatedStates = savedStates.filter((_, i) => i !== idx)\n localStorage.setItem(SAVED_STATES_KEY, JSON.stringify(updatedStates))\n renderSaves(ctx)\n })\n })\n}\n\n// Helper to escape HTML\nfunction escapeHtml(str: string): string {\n return str.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n}\n\n// Helper to refresh the edit tab content\nfunction refreshEditTab(ctx: SimulatorContext) {\n const puzzleTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n const inputTextarea = ctx.getElement<HTMLTextAreaElement>(\"#simulator-input\")\n const puzzleApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-puzzle-apply\")\n const inputApplyBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-input-apply\")\n\n if (puzzleTextarea && ctx.state.originalPuzzle) {\n puzzleTextarea.value = ctx.state.originalPuzzle\n autoResizeTextarea(puzzleTextarea)\n if (puzzleApplyBtn) puzzleApplyBtn.disabled = true\n }\n\n if (inputTextarea) {\n inputTextarea.value = ctx.state.currentInputStr\n autoResizeTextarea(inputTextarea)\n if (inputApplyBtn) inputApplyBtn.disabled = true\n }\n}\n","import type { SimulatorContext, SimulatorView, MessageLogEntry } from \"../types\"\n\nexport interface MsgsViewExtended extends SimulatorView {\n addLogEntry: (entry: MessageLogEntry, ctx: SimulatorContext) => void\n}\n\n// Helper to escape HTML\nfunction escapeHtml(str: string): string {\n return str.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n}\n\nexport function createMsgsView(): MsgsViewExtended {\n let messageCount = 0\n\n return {\n id: \"msgs\",\n label: \"Msgs\",\n\n render() {\n return `\n <div class=\"msgs-view-container\">\n <div class=\"msgs-header\">\n <span class=\"simulator-label\">Message Log</span>\n <button class=\"simulator-btn tiny\" id=\"simulator-msgs-clear\">Clear</button>\n </div>\n <div id=\"simulator-msgs-log\"></div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const clearBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-msgs-clear\")\n\n clearBtn?.addEventListener(\"click\", () => {\n const logEl = ctx.getElement<HTMLElement>(\"#simulator-msgs-log\")\n if (logEl) {\n logEl.innerHTML = \"\"\n }\n messageCount = 0\n ctx.updateBadge(\"msgs\", 0)\n })\n },\n\n addLogEntry(entry: MessageLogEntry, ctx: SimulatorContext) {\n const logEl = ctx.getElement<HTMLElement>(\"#simulator-msgs-log\")\n if (!logEl) return\n\n messageCount++\n ctx.updateBadge(\"msgs\", messageCount)\n\n const msgEl = document.createElement(\"div\")\n msgEl.className = `simulator-msg ${entry.direction}`\n const dataStr = entry.data !== undefined ? JSON.stringify(entry.data, null, 2) : \"\"\n msgEl.innerHTML = `\n <div class=\"simulator-msg-header\">\n <span class=\"simulator-msg-type\">${entry.direction === \"out\" ? \"\\u2192\" : \"\\u2190\"} ${entry.type}</span>\n <span class=\"simulator-msg-time\">${entry.time}</span>\n </div>\n ${dataStr ? `<div class=\"simulator-msg-data\">${escapeHtml(dataStr)}</div>` : \"\"}\n `\n\n // Add click handler to expand/collapse message data\n const dataEl = msgEl.querySelector(\".simulator-msg-data\")\n if (dataEl) {\n dataEl.addEventListener(\"click\", () => {\n // Collapse any other expanded messages first\n logEl.querySelectorAll(\".simulator-msg.expanded\").forEach((el) => {\n if (el !== msgEl) el.classList.remove(\"expanded\")\n })\n // Toggle this message\n msgEl.classList.toggle(\"expanded\")\n })\n }\n\n logEl.insertBefore(msgEl, logEl.firstChild)\n\n // Limit displayed messages\n while (logEl.children.length > 50) {\n logEl.removeChild(logEl.lastChild!)\n }\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\n\nexport function createDoneView(): SimulatorView {\n return {\n id: \"done\",\n label: \"Done\",\n\n render() {\n return `\n <div id=\"simulator-done-empty\" class=\"simulator-empty\">Game not completed yet</div>\n <div id=\"simulator-done-content\" style=\"display:none;\">\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Completion Text</label>\n <div id=\"simulator-done-text\" class=\"simulator-value\"></div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Board State</label>\n <textarea class=\"simulator-textarea\" id=\"simulator-done-input\" rows=\"2\" readonly></textarea>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Deeds</label>\n <div id=\"simulator-done-deeds\" class=\"simulator-deeds\"></div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Raw Data</label>\n <textarea class=\"simulator-textarea\" id=\"simulator-done-raw\" rows=\"4\" readonly></textarea>\n </div>\n </div>\n `\n },\n\n bind(_ctx: SimulatorContext) {\n // No event bindings needed for done view (read-only)\n },\n\n onMessage(type: string, data: any, ctx: SimulatorContext) {\n if (type !== \"GAME_COMPLETED\") return\n\n ctx.state.completionData = data\n\n const emptyEl = ctx.getElement<HTMLElement>(\"#simulator-done-empty\")\n const contentEl = ctx.getElement<HTMLElement>(\"#simulator-done-content\")\n const textEl = ctx.getElement<HTMLElement>(\"#simulator-done-text\")\n const inputEl = ctx.getElement<HTMLTextAreaElement>(\"#simulator-done-input\")\n const deedsEl = ctx.getElement<HTMLElement>(\"#simulator-done-deeds\")\n const rawEl = ctx.getElement<HTMLTextAreaElement>(\"#simulator-done-raw\")\n\n if (emptyEl) emptyEl.style.display = \"none\"\n if (contentEl) contentEl.style.display = \"block\"\n\n // Completion text (from input if available)\n const completionText = data.input?.completionText || data.completionText || \"(no completion text)\"\n if (textEl) textEl.textContent = completionText\n\n // Board state (from input.boardState)\n const boardState = data.input?.boardState || data.boardState || ctx.state.currentInputStr || \"\"\n if (inputEl) inputEl.value = boardState\n\n // Deeds (from config.deeds, each deed has id/value)\n if (deedsEl) {\n deedsEl.innerHTML = \"\"\n const deeds = data.config?.deeds || data.deeds\n if (deeds && Array.isArray(deeds)) {\n deeds.forEach((deed: { id: string; value: any }) => {\n const deedEl = document.createElement(\"div\")\n deedEl.className = \"simulator-deed\"\n deedEl.innerHTML = `\n <span class=\"simulator-deed-name\">${deed.id}</span>\n <span class=\"simulator-deed-value\">${deed.value}</span>\n `\n deedsEl.appendChild(deedEl)\n })\n } else {\n deedsEl.innerHTML = '<div class=\"simulator-empty\">No deeds</div>'\n }\n }\n\n // Raw data\n if (rawEl) rawEl.value = JSON.stringify(data, null, 2)\n\n // Switch to done tab\n ctx.setCollapsed(false)\n ctx.switchTab(\"done\")\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\nimport type { AugmentationConfig } from \"../../types\"\n\ninterface CheckpointEntry {\n checkpointName: string\n augConfig: AugmentationConfig\n time: string\n}\n\nexport function createCheckpointsView(): SimulatorView {\n let checkpoints: CheckpointEntry[] = []\n\n const formatTime = () => {\n const now = new Date()\n return now.toLocaleTimeString(\"en-US\", { hour12: false })\n }\n\n const renderCheckpoint = (checkpoint: CheckpointEntry): string => {\n const { checkpointName, augConfig, time } = checkpoint\n const deeds = augConfig?.deeds as Array<{ id: string; value: any }> | undefined\n\n return `\n <div class=\"checkpoint-item\">\n <div class=\"checkpoint-header\">\n <span class=\"checkpoint-name\">${checkpointName}</span>\n <span class=\"checkpoint-time\">${time}</span>\n </div>\n ${\n deeds && deeds.length > 0\n ? `<div class=\"simulator-deeds\">\n ${deeds.map((deed) => `<div class=\"simulator-deed\"><span class=\"simulator-deed-name\">${deed.id}</span><span class=\"simulator-deed-value\">${deed.value}</span></div>`).join(\"\")}\n </div>`\n : \"\"\n }\n </div>\n `\n }\n\n const renderList = (): string => {\n if (checkpoints.length === 0) {\n return '<div class=\"simulator-empty\">No checkpoints received yet</div>'\n }\n return checkpoints.map((cp) => renderCheckpoint(cp)).join(\"\")\n }\n\n return {\n id: \"checkpoints\",\n label: \"Chkpt\",\n\n render() {\n return `\n <div class=\"checkpoints-view-container\">\n <div class=\"checkpoints-header\">\n <span class=\"simulator-label\">Checkpoints</span>\n <button class=\"simulator-btn subtle small\" id=\"simulator-checkpoints-clear\">Clear</button>\n </div>\n <div id=\"simulator-checkpoints-list\" class=\"checkpoints-list\">\n ${renderList()}\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const clearBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-checkpoints-clear\")\n const listEl = ctx.getElement<HTMLElement>(\"#simulator-checkpoints-list\")\n\n clearBtn?.addEventListener(\"click\", () => {\n checkpoints = []\n if (listEl) listEl.innerHTML = renderList()\n ctx.updateBadge(\"checkpoints\", 0)\n })\n },\n\n onMessage(type: string, data: any, ctx: SimulatorContext) {\n if (type !== \"HIT_CHECKPOINT\") return\n\n const entry: CheckpointEntry = {\n checkpointName: data.checkpointName,\n augConfig: data.augConfig,\n time: formatTime(),\n }\n\n checkpoints.push(entry)\n ctx.updateBadge(\"checkpoints\", checkpoints.length)\n\n const listEl = ctx.getElement<HTMLElement>(\"#simulator-checkpoints-list\")\n if (listEl) {\n listEl.innerHTML = renderList()\n // Scroll to bottom to show newest\n listEl.scrollTop = listEl.scrollHeight\n }\n },\n }\n}\n","import type { AppBundle, ThumbnailConfig } from \"../../types\"\nimport type { SimulatorContext, SimulatorView } from \"../types\"\nimport { persistRenderContext, persistRenderHost } from \"../state\"\n\n/**\n * Find thumbnail function on globalThis (looks for functions ending in \"Thumbnail\")\n */\nfunction findThumbnailFn(): { name: string; fn: AppBundle[\"renderThumbnail\"] } | null {\n const globalObj = globalThis as Record<string, unknown>\n for (const key of Object.keys(globalObj)) {\n if (key.endsWith(\"Thumbnail\") && typeof globalObj[key] === \"function\") {\n return { name: key, fn: globalObj[key] as AppBundle[\"renderThumbnail\"] }\n }\n }\n return null\n}\n\nexport interface ThumbViewExtended extends SimulatorView {\n updatePreview: (ctx: SimulatorContext) => void\n}\n\nexport function createThumbView(): ThumbViewExtended {\n const updatePreview = (ctx: SimulatorContext) => {\n const previewEl = ctx.getElement<HTMLElement>(\"#simulator-thumb-preview\")\n const fnEl = ctx.getElement<HTMLElement>(\"#simulator-thumb-fn\")\n\n // Match the page background (theme) behind the thumbnail SVG.\n previewEl?.style.setProperty(\"--sim-thumb-bg\", ctx.state.selectedTheme.g_bg)\n\n const thumbFn = findThumbnailFn()\n if (!thumbFn) {\n if (previewEl) previewEl.innerHTML = '<span class=\"simulator-empty\">No thumbnail function found</span>'\n if (fnEl) fnEl.textContent = \"\"\n return\n }\n\n if (fnEl) fnEl.textContent = `Using: ${thumbFn.name}()`\n\n try {\n const puzzleStr = ctx.state.puzzleData ? JSON.stringify(ctx.state.puzzleData) : \"\"\n\n const thumbnailConfig: ThumbnailConfig = {\n viewerIsOwner: true,\n theme: ctx.state.selectedTheme,\n strict: true,\n renderHost: ctx.state.renderHost,\n renderContext: ctx.state.renderContext,\n }\n\n const svgString = thumbFn.fn(puzzleStr, ctx.state.currentInputStr, thumbnailConfig)\n if (previewEl) previewEl.innerHTML = svgString\n } catch (e) {\n console.error(\"Simulator: Thumbnail error\", e)\n if (previewEl) previewEl.innerHTML = `<span class=\"simulator-empty\">Error: ${e}</span>`\n }\n }\n\n return {\n id: \"thumb\",\n label: \"Thumb\",\n\n render() {\n return `\n <div class=\"thumb-view-container\">\n <div class=\"thumb-header\">\n <span class=\"simulator-label\">Thumbnail Preview</span>\n <button class=\"simulator-btn tiny\" id=\"simulator-thumb-refresh\">Refresh</button>\n </div>\n <div id=\"simulator-thumb-preview\">\n <span class=\"simulator-empty\">No thumbnail function found</span>\n </div>\n <div id=\"simulator-thumb-fn\"></div>\n <div class=\"simulator-divider\"></div>\n <div class=\"simulator-field\">\n <label class=\"simulator-label\">Render Host</label>\n <select class=\"simulator-select\" id=\"simulator-render-host-select\">\n <option value=\"game\">game</option>\n <option value=\"app\">app</option>\n <option value=\"opengraph\">opengraph</option>\n </select>\n </div>\n <div class=\"simulator-field\" id=\"simulator-render-context-field\" style=\"display: none;\">\n <label class=\"simulator-label\">Render Context</label>\n <select class=\"simulator-select\" id=\"simulator-render-context-select\">\n <option value=\"preview\">preview</option>\n <option value=\"share\">share</option>\n <option value=\"completed\">completed</option>\n <option value=\"timeline\">timeline</option>\n </select>\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const refreshBtn = ctx.getElement<HTMLButtonElement>(\"#simulator-thumb-refresh\")\n refreshBtn?.addEventListener(\"click\", () => ctx.updateThumbnail())\n\n const renderHostSelect = ctx.getElement<HTMLSelectElement>(\"#simulator-render-host-select\")\n const renderContextSelect = ctx.getElement<HTMLSelectElement>(\"#simulator-render-context-select\")\n const renderContextField = ctx.getElement<HTMLElement>(\"#simulator-render-context-field\")\n\n const updateContextVisibility = () => {\n if (renderContextField) {\n renderContextField.style.display = ctx.state.renderHost === \"opengraph\" ? \"block\" : \"none\"\n }\n }\n\n // Initialize render host select\n if (renderHostSelect) {\n // Default to \"game\" if not set\n if (!ctx.state.renderHost) {\n ctx.state.renderHost = \"game\"\n persistRenderHost(ctx.state.renderHost)\n }\n renderHostSelect.value = ctx.state.renderHost || \"game\"\n\n renderHostSelect.addEventListener(\"change\", () => {\n ctx.state.renderHost = renderHostSelect.value as ThumbnailConfig[\"renderHost\"]\n persistRenderHost(ctx.state.renderHost)\n updateContextVisibility()\n ctx.updateThumbnail()\n })\n }\n\n // Initialize render context select\n if (renderContextSelect) {\n // Default to \"preview\" if not set\n if (!ctx.state.renderContext) {\n ctx.state.renderContext = \"preview\"\n persistRenderContext(ctx.state.renderContext)\n }\n renderContextSelect.value = ctx.state.renderContext || \"preview\"\n\n renderContextSelect.addEventListener(\"change\", () => {\n ctx.state.renderContext = renderContextSelect.value as ThumbnailConfig[\"renderContext\"]\n persistRenderContext(ctx.state.renderContext)\n ctx.updateThumbnail()\n })\n }\n\n updateContextVisibility()\n },\n\n onActivate(ctx: SimulatorContext) {\n // Defer to allow thumbnail function to be registered\n setTimeout(() => ctx.updateThumbnail(), 0)\n },\n\n updatePreview,\n }\n}\n","import type { Theme } from \"../../types\"\nimport { themes } from \"../../themes\"\nimport type { SimulatorContext, SimulatorView } from \"../types\"\nimport { persistTheme } from \"../state\"\n\n/**\n * Generate theme preview HTML showing key colors\n */\nfunction generateThemePreview(theme: Theme): string {\n // Show key, subBrand, player, alt1, alt2, alt3 with g_bg as background\n const colors = [theme.key, theme.subBrand, theme.player, theme.alt1, theme.alt2, theme.alt3]\n const cells = colors.map((c) => `<div class=\"simulator-theme-preview-cell\" style=\"background: ${c}\"></div>`).join(\"\")\n return `<div class=\"simulator-theme-preview\" style=\"background: ${theme.g_bg}\">${cells}</div>`\n}\n\nexport function createThemeView(): SimulatorView {\n return {\n id: \"theme\",\n label: \"Theme\",\n\n render() {\n return `\n <div class=\"theme-view-container\">\n <div class=\"theme-header\">\n <span class=\"simulator-label\">Select Theme</span>\n </div>\n <div id=\"simulator-themes\" class=\"simulator-themes\"></div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const themesEl = ctx.getElement<HTMLElement>(\"#simulator-themes\")\n if (!themesEl) return\n\n themes.forEach((theme) => {\n const themeItem = document.createElement(\"div\")\n themeItem.className = `simulator-theme-item${theme.name === ctx.state.selectedTheme.name ? \" selected\" : \"\"}`\n themeItem.innerHTML = `\n ${generateThemePreview(theme)}\n <div class=\"simulator-theme-name\">${theme.name}</div>\n <div class=\"simulator-theme-type\">${theme.type}</div>\n `\n themeItem.addEventListener(\"click\", () => {\n console.log(\"Simulator: Theme changed, reloading...\", theme.name)\n persistTheme(theme.name)\n window.location.reload()\n })\n themesEl.appendChild(themeItem)\n })\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\n\n// API mode storage\nconst API_MODE_KEY = \"puzzmo_sim_api_mode\"\nconst LOCAL_DEV_URL = \"http://localhost:8911\"\nconst PROD_URL = \"https://api.puzzmo.com\"\n\ntype ApiMode = \"prod\" | \"dev\"\n\nconst getApiMode = (): ApiMode => {\n const stored = localStorage.getItem(API_MODE_KEY)\n return stored === \"dev\" ? \"dev\" : \"prod\"\n}\n\nconst setApiMode = (mode: ApiMode) => {\n localStorage.setItem(API_MODE_KEY, mode)\n}\n\n// Check if local dev server is available\nlet localDevAvailable: boolean | null = null\nconst checkLocalDevAvailable = async (): Promise<boolean> => {\n if (localDevAvailable !== null) return localDevAvailable\n\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 1000)\n\n const response = await fetch(`${LOCAL_DEV_URL}/healthz`, {\n signal: controller.signal,\n })\n clearTimeout(timeoutId)\n\n localDevAvailable = response.ok\n return localDevAvailable\n } catch {\n localDevAvailable = false\n return false\n }\n}\n\n// OAuth configuration\nconst getOAuthConfig = () => {\n const mode = getApiMode()\n const apiURL = mode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n const clientID = \"protosdk:oauthclient\"\n // Use a fixed callback path that's registered with the OAuth server\n const redirectUri = `${window.location.origin}/oauth/callback`\n\n return { apiURL, clientID, redirectUri }\n}\n\n// Generate CSRF state token\nconst generateState = (): string => {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n return Array.from(array, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\")\n}\n\n// Token storage\nconst TOKEN_KEY = \"puzzmo_sim_oauth_token\"\nconst REFRESH_TOKEN_KEY = \"puzzmo_sim_oauth_refresh_token\"\n\nconst storeAccessToken = (token: string) => localStorage.setItem(TOKEN_KEY, token)\nconst storeRefreshToken = (token: string) => localStorage.setItem(REFRESH_TOKEN_KEY, token)\nconst getAccessToken = (): string | null => {\n const token = localStorage.getItem(TOKEN_KEY)\n console.log(\"[AuthView] getAccessToken:\", { TOKEN_KEY, token: token ? `${token.substring(0, 20)}...` : null })\n return token\n}\nconst getRefreshToken = (): string | null => localStorage.getItem(REFRESH_TOKEN_KEY)\nconst clearTokens = () => {\n localStorage.removeItem(TOKEN_KEY)\n localStorage.removeItem(REFRESH_TOKEN_KEY)\n}\n\n// Initiate OAuth login flow\nconst initiateOAuthLogin = () => {\n const config = getOAuthConfig()\n const state = generateState()\n\n sessionStorage.setItem(\"oauth_state\", state)\n // Store the current URL to return to after OAuth callback\n sessionStorage.setItem(\"oauth_return_url\", window.location.href)\n\n const authURL = new URL(`${config.apiURL}/oauth/auth`)\n authURL.searchParams.set(\"client_id\", config.clientID)\n authURL.searchParams.set(\"response_type\", \"code\")\n authURL.searchParams.set(\"redirect_uri\", config.redirectUri)\n authURL.searchParams.set(\"state\", state)\n\n window.location.href = authURL.toString()\n}\n\n// Exchange authorization code for access token\ntype TokenResponse = {\n // Standard OAuth snake_case fields\n access_token?: string\n token_type?: string\n expires_in?: number\n scope?: string\n refresh_token?: string\n // Server returns camelCase\n accessToken?: string\n refreshToken?: string\n}\n\n// Check if token is expired or about to expire (within 5 minutes)\nconst isTokenExpired = (token: string): boolean => {\n try {\n const parts = token.split(\".\")\n if (parts.length !== 3) return true\n const payload = JSON.parse(atob(parts[1]))\n const exp = payload.exp\n if (!exp) return true\n // Consider expired if less than 5 minutes remaining\n return Date.now() >= exp * 1000 - 5 * 60 * 1000\n } catch {\n return true\n }\n}\n\n// Refresh the access token using the refresh token\nconst refreshAccessToken = async (): Promise<boolean> => {\n const refreshToken = getRefreshToken()\n if (!refreshToken) {\n console.log(\"[AuthView] No refresh token available\")\n return false\n }\n\n const config = getOAuthConfig()\n\n try {\n const params = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientID,\n })\n\n const response = await fetch(`${config.apiURL}/oauth/token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n console.error(\"[AuthView] Failed to refresh token:\", response.statusText)\n return false\n }\n\n const tokenResponse: TokenResponse = await response.json()\n const accessToken = tokenResponse.access_token || tokenResponse.accessToken\n if (!accessToken) {\n console.error(\"[AuthView] No access token in refresh response\")\n return false\n }\n\n storeAccessToken(accessToken)\n // Update refresh token if a new one was provided\n const newRefreshToken = tokenResponse.refresh_token || tokenResponse.refreshToken\n if (newRefreshToken) {\n storeRefreshToken(newRefreshToken)\n }\n\n console.log(\"[AuthView] Successfully refreshed access token\")\n return true\n } catch (error) {\n console.error(\"[AuthView] Error refreshing token:\", error)\n return false\n }\n}\n\nconst exchangeCodeForToken = async (code: string, state: string): Promise<TokenResponse | null> => {\n const config = getOAuthConfig()\n const storedState = sessionStorage.getItem(\"oauth_state\")\n\n if (!storedState || storedState !== state) {\n console.error(\"OAuth state mismatch - possible CSRF attack\")\n return null\n }\n\n sessionStorage.removeItem(\"oauth_state\")\n\n try {\n const params = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: config.clientID,\n redirect_uri: config.redirectUri,\n })\n\n const response = await fetch(`${config.apiURL}/oauth/token`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n console.error(\"Failed to exchange code for token:\", response.statusText)\n return null\n }\n\n return await response.json()\n } catch (error) {\n console.error(\"Error exchanging code for token:\", error)\n return null\n }\n}\n\n// Make authenticated API request with automatic token refresh\nconst makeAuthenticatedRequest = async (query: string, variables: Record<string, unknown> = {}) => {\n const config = getOAuthConfig()\n let token = getAccessToken()\n\n if (!token) {\n throw new Error(\"Not authenticated\")\n }\n\n // Check if token is expired and try to refresh\n if (isTokenExpired(token)) {\n console.log(\"[AuthView] Access token expired, attempting refresh...\")\n const refreshed = await refreshAccessToken()\n if (refreshed) {\n token = getAccessToken()\n if (!token) {\n throw new Error(\"Token refresh succeeded but no token available\")\n }\n } else {\n // Refresh failed, clear tokens and require re-login\n clearTokens()\n throw new Error(\"Session expired. Please log in again.\")\n }\n }\n\n const response = await fetch(`${config.apiURL}/graphql`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n \"auth-provider\": \"custom\",\n },\n body: JSON.stringify({ query, variables }),\n })\n\n if (!response.ok) {\n throw new Error(`API request failed: ${response.statusText}`)\n }\n\n return response.json()\n}\n\n// Decode JWT to get user info (basic decode, no verification)\nconst decodeJWT = (token: string): { sub?: string; exp?: number; userID?: string } | null => {\n try {\n console.log(\"[AuthView] decodeJWT input:\", token)\n const parts = token.split(\".\")\n console.log(\"[AuthView] JWT parts:\", parts.length)\n if (parts.length !== 3) return null\n const payload = JSON.parse(atob(parts[1]))\n console.log(\"[AuthView] JWT payload:\", payload)\n return payload\n } catch (e) {\n console.error(\"[AuthView] decodeJWT error:\", e)\n return null\n }\n}\n\nexport function createAuthView(): SimulatorView {\n // Track if we've checked for local dev availability\n let devCheckDone = false\n\n return {\n id: \"auth\",\n label: \"Auth\",\n\n render() {\n const token = getAccessToken()\n const isAuthenticated = !!token\n const currentMode = getApiMode()\n const apiURL = currentMode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n\n // Dev toggle button - hidden by default, shown via JS if local dev is available\n const devToggle = `<button class=\"simulator-btn tiny\" id=\"auth-dev-toggle\" style=\"display: none;\">${currentMode === \"dev\" ? \"Using Dev\" : \"Dev\"}</button>`\n\n if (isAuthenticated) {\n const decoded = decodeJWT(token)\n const expiresAt = decoded?.exp ? new Date(decoded.exp * 1000).toLocaleString() : \"Unknown\"\n const hasRefreshToken = !!getRefreshToken()\n const refreshDecoded = hasRefreshToken ? decodeJWT(getRefreshToken()!) : null\n const refreshExpiresAt = refreshDecoded?.exp ? new Date(refreshDecoded.exp * 1000).toLocaleString() : null\n console.log({ decoded })\n\n return `\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title auth-title-row\">\n <span>Puzzmo Authentication</span>\n ${devToggle}\n <button class=\"simulator-btn danger tiny\" id=\"auth-logout\">Logout</button>\n </div>\n <div id=\"auth-status\" class=\"auth-status authenticated\">\n <span class=\"indicator ready\"></span>\n <span>Authenticated</span>\n </div>\n <div id=\"auth-user-info\" class=\"auth-user-info\">\n <div>User ID: <code>${decoded?.userID || decoded?.sub || \"Unknown\"}</code></div>\n <div>API: <code>${apiURL}</code></div>\n <div>Access expires: <code>${expiresAt}</code></div>\n ${hasRefreshToken ? `<div>Refresh expires: <code>${refreshExpiresAt || \"Unknown\"}</code></div>` : '<div class=\"auth-warning\">No refresh token</div>'}\n </div>\n ${hasRefreshToken ? '<button class=\"simulator-btn secondary tiny\" id=\"auth-refresh\">Refresh Now</button>' : \"\"}\n </div>\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title\">Test API Request</div>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn primary\" id=\"auth-test-api\">Fetch Current User</button>\n </div>\n <div id=\"auth-api-result\" class=\"auth-api-result\"></div>\n </div>\n `\n }\n\n return `\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title auth-title-row\">\n <span>Puzzmo Authentication</span>\n ${devToggle}\n </div>\n <div id=\"auth-status\" class=\"auth-status\">\n <span class=\"indicator waiting\"></span>\n <span>Not authenticated</span>\n </div>\n <p class=\"auth-description\">\n Login with your Puzzmo account to make authenticated API requests.\n ${currentMode === \"dev\" ? `<br><strong>Using local dev server:</strong> ${LOCAL_DEV_URL}` : \"\"}\n </p>\n <div class=\"simulator-row\">\n <button class=\"simulator-btn primary\" id=\"auth-login\">Login with Puzzmo</button>\n </div>\n <div id=\"auth-error\" class=\"auth-error\"></div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const loginBtn = ctx.getElement<HTMLButtonElement>(\"#auth-login\")\n const logoutBtn = ctx.getElement<HTMLButtonElement>(\"#auth-logout\")\n const refreshBtn = ctx.getElement<HTMLButtonElement>(\"#auth-refresh\")\n const testApiBtn = ctx.getElement<HTMLButtonElement>(\"#auth-test-api\")\n const apiResult = ctx.getElement<HTMLElement>(\"#auth-api-result\")\n const devToggleBtn = ctx.getElement<HTMLButtonElement>(\"#auth-dev-toggle\")\n\n // Check for local dev availability and show toggle if available\n if (!devCheckDone) {\n devCheckDone = true\n checkLocalDevAvailable().then((available) => {\n if (available && devToggleBtn) {\n devToggleBtn.style.display = \"\"\n // Style based on current mode\n if (getApiMode() === \"dev\") {\n devToggleBtn.classList.add(\"active\")\n }\n }\n })\n } else if (localDevAvailable && devToggleBtn) {\n // Already checked, just show the button\n devToggleBtn.style.display = \"\"\n if (getApiMode() === \"dev\") {\n devToggleBtn.classList.add(\"active\")\n }\n }\n\n // Dev toggle click handler\n devToggleBtn?.addEventListener(\"click\", () => {\n const currentMode = getApiMode()\n const newMode = currentMode === \"dev\" ? \"prod\" : \"dev\"\n setApiMode(newMode)\n // Clear tokens when switching modes since they're for different servers\n clearTokens()\n window.location.reload()\n })\n\n loginBtn?.addEventListener(\"click\", () => {\n initiateOAuthLogin()\n })\n\n logoutBtn?.addEventListener(\"click\", () => {\n clearTokens()\n window.location.reload()\n })\n\n refreshBtn?.addEventListener(\"click\", async () => {\n refreshBtn.disabled = true\n refreshBtn.textContent = \"Refreshing...\"\n const success = await refreshAccessToken()\n if (success) {\n window.location.reload()\n } else {\n refreshBtn.textContent = \"Refresh Failed\"\n refreshBtn.disabled = false\n }\n })\n\n testApiBtn?.addEventListener(\"click\", async () => {\n if (!apiResult) return\n\n apiResult.innerHTML = '<div class=\"loading\">Loading...</div>'\n\n try {\n const result = await makeAuthenticatedRequest(`\n query {\n currentUser {\n id\n username\n usernameID\n name\n }\n }\n `)\n\n if (result.errors) {\n apiResult.innerHTML = `<div class=\"error\">Error: ${result.errors[0]?.message || \"Unknown error\"}</div>`\n } else {\n apiResult.innerHTML = `<pre>${JSON.stringify(result.data, null, 2)}</pre>`\n }\n } catch (error) {\n apiResult.innerHTML = `<div class=\"error\">Error: ${error instanceof Error ? error.message : \"Unknown error\"}</div>`\n }\n })\n },\n\n async onActivate(ctx: SimulatorContext) {\n // Check for OAuth callback parameters\n const urlParams = new URLSearchParams(window.location.search)\n const code = urlParams.get(\"code\")\n const state = urlParams.get(\"state\")\n const error = urlParams.get(\"error\")\n\n if (error) {\n const errorEl = ctx.getElement<HTMLElement>(\"#auth-error\")\n if (errorEl) {\n errorEl.innerHTML = `<div class=\"error\">OAuth error: ${error}</div>`\n }\n // Clean up URL\n window.history.replaceState({}, \"\", window.location.pathname)\n return\n }\n\n if (code && state) {\n const statusEl = ctx.getElement<HTMLElement>(\"#auth-status\")\n if (statusEl) {\n statusEl.innerHTML = '<span class=\"indicator waiting\"></span><span>Exchanging code...</span>'\n }\n\n const tokenResponse = await exchangeCodeForToken(code, state)\n\n // Clean up URL\n window.history.replaceState({}, \"\", window.location.pathname)\n\n if (tokenResponse) {\n const accessToken = tokenResponse.access_token || tokenResponse.accessToken\n if (accessToken) {\n storeAccessToken(accessToken)\n const refreshToken = tokenResponse.refresh_token || tokenResponse.refreshToken\n if (refreshToken) {\n storeRefreshToken(refreshToken)\n }\n }\n window.location.reload()\n } else {\n const errorEl = ctx.getElement<HTMLElement>(\"#auth-error\")\n if (errorEl) {\n errorEl.innerHTML = '<div class=\"error\">Failed to exchange code for token</div>'\n }\n }\n }\n },\n }\n}\n","import type { SimulatorContext, SimulatorView } from \"../types\"\n\n// API mode storage (shared with AuthView)\nconst API_MODE_KEY = \"puzzmo_sim_api_mode\"\nconst LOCAL_DEV_URL = \"http://localhost:8911\"\nconst PROD_URL = \"https://api.puzzmo.com\"\nconst TOKEN_KEY = \"puzzmo_sim_oauth_token\"\nconst REFRESH_TOKEN_KEY = \"puzzmo_sim_oauth_refresh_token\"\n\ntype ApiMode = \"prod\" | \"dev\"\n\nconst getApiMode = (): ApiMode => {\n const stored = localStorage.getItem(API_MODE_KEY)\n return stored === \"dev\" ? \"dev\" : \"prod\"\n}\n\nconst getAccessToken = (): string | null => localStorage.getItem(TOKEN_KEY)\nconst getRefreshToken = (): string | null => localStorage.getItem(REFRESH_TOKEN_KEY)\n\nconst storeAccessToken = (token: string) => localStorage.setItem(TOKEN_KEY, token)\nconst storeRefreshToken = (token: string) => localStorage.setItem(REFRESH_TOKEN_KEY, token)\n\nconst clearTokens = () => {\n localStorage.removeItem(TOKEN_KEY)\n localStorage.removeItem(REFRESH_TOKEN_KEY)\n}\n\n// Check if token is expired or about to expire (within 5 minutes)\nconst isTokenExpired = (token: string): boolean => {\n try {\n const parts = token.split(\".\")\n if (parts.length !== 3) return true\n const payload = JSON.parse(atob(parts[1]))\n const exp = payload.exp\n if (!exp) return true\n return Date.now() >= exp * 1000 - 5 * 60 * 1000\n } catch {\n return true\n }\n}\n\n// Refresh the access token using the refresh token\nconst refreshAccessToken = async (): Promise<boolean> => {\n const refreshToken = getRefreshToken()\n if (!refreshToken) return false\n\n const mode = getApiMode()\n const apiURL = mode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n\n try {\n const params = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: \"protosdk:oauthclient\",\n })\n\n const response = await fetch(`${apiURL}/oauth/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: params.toString(),\n })\n\n if (!response.ok) return false\n\n const tokenResponse = await response.json()\n const accessToken = tokenResponse.access_token || tokenResponse.accessToken\n if (!accessToken) return false\n\n storeAccessToken(accessToken)\n const newRefreshToken = tokenResponse.refresh_token || tokenResponse.refreshToken\n if (newRefreshToken) storeRefreshToken(newRefreshToken)\n\n return true\n } catch {\n return false\n }\n}\n\n// Make authenticated API request with automatic token refresh\nconst makeAuthenticatedRequest = async (query: string, variables: Record<string, unknown> = {}) => {\n const mode = getApiMode()\n const apiURL = mode === \"dev\" ? LOCAL_DEV_URL : PROD_URL\n let token = getAccessToken()\n\n if (!token) throw new Error(\"Not authenticated\")\n\n if (isTokenExpired(token)) {\n const refreshed = await refreshAccessToken()\n if (refreshed) {\n token = getAccessToken()\n if (!token) throw new Error(\"Token refresh succeeded but no token available\")\n } else {\n clearTokens()\n throw new Error(\"Session expired. Please log in again.\")\n }\n }\n\n const response = await fetch(`${apiURL}/graphql`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n \"auth-provider\": \"custom\",\n },\n body: JSON.stringify({ query, variables }),\n })\n\n if (!response.ok) throw new Error(`API request failed: ${response.statusText}`)\n return response.json()\n}\n\n// Types for game features\ntype GameFeature = {\n featureID: number\n title: string\n isEnabled: boolean\n}\n\ntype GameFeatureGroup = {\n slug: string\n title: string\n features: GameFeature[]\n}\n\ntype GameData = {\n id: string\n slug: string\n displayName: string\n featuresArr: number[]\n gameFeatures: GameFeatureGroup[]\n}\n\n// GraphQL queries\nconst GAME_FEATURES_QUERY = `\n query GameFeaturesQuery($slug: ID!) {\n game(id: $slug) {\n id\n slug\n displayName\n featuresArr\n gameFeatures {\n slug\n title\n features {\n featureID\n title\n isEnabled\n }\n }\n }\n }\n`\n\nconst TOGGLE_FEATURE_MUTATION = `\n mutation ToggleFeatureMutation($gameSlug: ID!, $input: UpdateGameInput!) {\n updateGame(id: $gameSlug, input: $input) {\n id\n featuresArr\n gameFeatures {\n slug\n title\n features {\n featureID\n title\n isEnabled\n }\n }\n }\n }\n`\n\n// Helper to toggle a feature in the array\nconst toggleFeatureInArr = (featuresArr: number[], featureID: number): number[] => {\n const bitIndex = featureID - 1\n const arrayIndex = Math.floor(bitIndex / 31)\n const bitPosition = bitIndex % 31\n\n const result = [...featuresArr]\n while (result.length <= arrayIndex) result.push(0)\n result[arrayIndex] = result[arrayIndex] ^ (1 << bitPosition)\n return result\n}\n\n// Check if user is authenticated\nexport const isAuthenticated = (): boolean => {\n const token = getAccessToken()\n return !!token\n}\n\nexport function createFeaturesView(): SimulatorView {\n let gameData: GameData | null = null\n let loading = false\n let error: string | null = null\n\n const fetchGameFeatures = async (gameSlug: string): Promise<void> => {\n loading = true\n error = null\n\n try {\n const result = await makeAuthenticatedRequest(GAME_FEATURES_QUERY, { slug: gameSlug })\n\n if (result.errors) {\n error = result.errors[0]?.message || \"Unknown error\"\n gameData = null\n } else if (result.data?.game) {\n gameData = result.data.game\n } else {\n error = \"Game not found\"\n gameData = null\n }\n } catch (e) {\n error = e instanceof Error ? e.message : \"Unknown error\"\n gameData = null\n } finally {\n loading = false\n }\n }\n\n const toggleFeature = async (featureID: number): Promise<void> => {\n if (!gameData) return\n\n const newFeaturesArr = toggleFeatureInArr(gameData.featuresArr, featureID)\n\n try {\n const result = await makeAuthenticatedRequest(TOGGLE_FEATURE_MUTATION, {\n gameSlug: gameData.slug,\n input: { featuresArr: newFeaturesArr },\n })\n\n if (result.errors) {\n console.error(\"Failed to toggle feature:\", result.errors[0]?.message)\n } else if (result.data?.updateGame) {\n // Update local state with new data\n gameData = {\n ...gameData,\n featuresArr: result.data.updateGame.featuresArr,\n gameFeatures: result.data.updateGame.gameFeatures,\n }\n }\n } catch (e) {\n console.error(\"Failed to toggle feature:\", e)\n }\n }\n\n const renderFeatures = (): string => {\n if (loading) {\n return '<div class=\"features-loading\">Loading features...</div>'\n }\n\n if (error) {\n return `<div class=\"features-error\">${error}</div>`\n }\n\n if (!gameData) {\n return '<div class=\"features-empty\">Enter your game slug above to load features</div>'\n }\n\n const groupsHtml = gameData.gameFeatures\n .map((group) => {\n const featuresHtml = group.features\n .map((f) => {\n const statusClass = f.isEnabled ? \"enabled\" : \"disabled\"\n const statusIcon = f.isEnabled ? \"✓\" : \"✗\"\n return `\n <div class=\"feature-item ${statusClass}\" data-feature-id=\"${f.featureID}\">\n <span class=\"feature-status\">${statusIcon}</span>\n <span class=\"feature-title\">${f.title}</span>\n </div>\n `\n })\n .join(\"\")\n\n return `\n <div class=\"feature-group\">\n <div class=\"feature-group-title\">${group.title}</div>\n <div class=\"feature-group-items\">${featuresHtml}</div>\n </div>\n `\n })\n .join(\"\")\n\n return `\n <div class=\"features-game-name\">${gameData.displayName}</div>\n ${groupsHtml}\n `\n }\n\n return {\n id: \"features\",\n label: \"Features\",\n\n render() {\n if (!isAuthenticated()) {\n return `\n <div class=\"features-view-container\">\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title\">Game Features</div>\n <div class=\"features-auth-required\">\n <p>You must be logged in to view and edit game features.</p>\n <p>Go to the <strong>Auth</strong> tab to log in with your Puzzmo account.</p>\n </div>\n </div>\n </div>\n `\n }\n\n return `\n <div class=\"features-view-container\">\n <div class=\"simulator-section\">\n <div class=\"simulator-section-title\">Game Features</div>\n <div class=\"features-slug-input\">\n <input type=\"text\" class=\"simulator-input\" id=\"features-game-slug\" placeholder=\"Game slug (e.g. crossword)\" />\n <button class=\"simulator-btn primary\" id=\"features-load-btn\">Load</button>\n </div>\n </div>\n <div class=\"simulator-divider\"></div>\n <div id=\"features-content\" class=\"features-content\">\n ${renderFeatures()}\n </div>\n </div>\n `\n },\n\n bind(ctx: SimulatorContext) {\n const loadBtn = ctx.getElement<HTMLButtonElement>(\"#features-load-btn\")\n const slugInput = ctx.getElement<HTMLInputElement>(\"#features-game-slug\")\n const contentEl = ctx.getElement<HTMLElement>(\"#features-content\")\n\n const updateContent = () => {\n if (contentEl) {\n contentEl.innerHTML = renderFeatures()\n bindFeatureItems()\n }\n }\n\n const bindFeatureItems = () => {\n const featureItems = ctx.getElement<HTMLElement>(\"#features-content\")?.querySelectorAll(\".feature-item\")\n featureItems?.forEach((item) => {\n item.addEventListener(\"click\", async () => {\n const featureId = parseInt(item.getAttribute(\"data-feature-id\") || \"0\")\n if (featureId > 0) {\n item.classList.add(\"updating\")\n await toggleFeature(featureId)\n updateContent()\n }\n })\n })\n }\n\n const loadFeatures = async (slug: string) => {\n if (!slug) return\n\n if (loadBtn) {\n loadBtn.disabled = true\n loadBtn.textContent = \"Loading...\"\n }\n\n await fetchGameFeatures(slug)\n updateContent()\n\n if (loadBtn) {\n loadBtn.disabled = false\n loadBtn.textContent = \"Load\"\n }\n }\n\n loadBtn?.addEventListener(\"click\", async () => {\n const slug = slugInput?.value.trim()\n if (slug) await loadFeatures(slug)\n })\n\n slugInput?.addEventListener(\"keypress\", (e) => {\n if (e.key === \"Enter\") {\n loadBtn?.click()\n }\n })\n\n // Pre-fill slug from context and auto-load if authenticated\n console.log(\"[FeaturesView] bind called, ctx.gameSlug:\", ctx.gameSlug, \"slugInput:\", slugInput)\n if (ctx.gameSlug && slugInput) {\n slugInput.value = ctx.gameSlug\n // Auto-load features if authenticated and we have a slug\n if (isAuthenticated() && !gameData) {\n loadFeatures(ctx.gameSlug)\n }\n }\n\n // Bind initial feature items if any\n bindFeatureItems()\n },\n\n onActivate(ctx: SimulatorContext) {\n // Re-render entire view when tab becomes active to handle auth status changes\n const tabContainer = ctx.getElement<HTMLElement>(\"#simulator-tab-features\")\n if (tabContainer) {\n tabContainer.innerHTML = this.render()\n this.bind(ctx)\n }\n },\n }\n}\n","/**\n * Simulator - A development UI for testing games with the Puzzmo Proto SDK.\n *\n * This script simulates the Puzzmo host environment by:\n * - Listening for READY messages from the game\n * - Sending READY_DATA with puzzle data\n * - Providing UI controls for START_GAME, PAUSE_GAME, RESUME_GAME, RETRY_PUZZLE\n *\n * Usage with Vite plugin (recommended):\n *\n * ```ts\n * // vite.config.ts\n * import { puzzmoSimulator } from \"@puzzmo/sdk/vite\"\n * export default defineConfig({\n * plugins: [puzzmoSimulator({ slug: \"my-game\", fixturesGlob: \"./fixtures/puzzles/**\\/*.json\" })]\n * })\n * ```\n *\n * The plugin handles making sure it is removed on vite builds.\n *\n * Usage with manual imports:\n * ```html\n * <script type=\"module\">\n * import { createSimulator } from \"@puzzmo/sdk/simulator\"\n * const fixtures = import.meta.glob(\"./fixtures/puzzles/**\\/*.json\", { eager: true })\n * createSimulator({ fixtures })\n * </script>\n * ```\n * The fixtures folder structure should be: fixtures/puzzles/{category}/{puzzle}.json\n * This will show dropdowns in the Ctrl tab to select category and puzzle.\n */\n\nimport type { BootstrapGameData, MessagesReceived } from \"../types\"\nimport type { SimulatorConfig, SimulatorContext, SimulatorState, SimulatorView, TabName, FixtureImports } from \"./types\"\nimport { simulatorStyles } from \"./styles\"\nimport { createInitialState, persistCollapsed, persistTab } from \"./state\"\nimport { sendToGame, createMessageListener, createMessageLogger } from \"./messaging\"\nimport { parseFixtures } from \"./fixtures\"\nimport {\n createCtrlView,\n createDataView,\n createMsgsView,\n createDoneView,\n createCheckpointsView,\n createThumbView,\n createThemeView,\n createAuthView,\n createFeaturesView,\n} from \"./views\"\n\n// Re-export types for consumers\nexport type { SimulatorConfig, FixtureImports }\n\n// Singleton instance state\ninterface SimulatorInstance {\n updateFixtures: (fixtures: FixtureImports) => void\n sendToGame: (type: any, data: any) => void\n loadPuzzle: () => Promise<any>\n}\nlet simulatorInstance: SimulatorInstance | null = null\n\n/**\n * Creates the Simulator UI and message handling.\n * If called multiple times, updates the existing instance with new settings (e.g., fixtures).\n */\nfunction createSimulator(config: SimulatorConfig = {}): SimulatorInstance {\n console.log(\"[Simulator] createSimulator called with config:\", { slug: config.slug, hasFixtures: !!config.fixtures })\n\n // If instance already exists, update it with new config\n if (simulatorInstance) {\n if (config.fixtures) {\n console.log(\"[Simulator] Instance already exists, updating fixtures\")\n simulatorInstance.updateFixtures(config.fixtures)\n }\n return simulatorInstance\n }\n\n const autoStart = config.autoStart ?? true\n\n // Parse fixtures if provided\n let fixtures = config.fixtures ? parseFixtures(config.fixtures) : null\n let fixtureCategories = fixtures ? Array.from(fixtures.keys()).sort() : []\n\n // Create views first so we can extract valid tab IDs\n const msgsView = createMsgsView()\n const thumbView = createThumbView()\n const views = [\n createCtrlView(),\n createDataView(),\n msgsView,\n createDoneView(),\n createCheckpointsView(),\n thumbView,\n createThemeView(),\n createAuthView(),\n createFeaturesView(),\n ] satisfies SimulatorView[]\n\n const validTabIds = views.map((v) => v.id)\n\n // Initialize state\n const state: SimulatorState = createInitialState(config, fixtureCategories, validTabIds)\n\n // SVG Icons for the header\n const icons = {\n pause: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><rect x=\"1\" y=\"1\" width=\"3\" height=\"8\"/><rect x=\"6\" y=\"1\" width=\"3\" height=\"8\"/></svg>`,\n play: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><polygon points=\"2,1 9,5 2,9\"/></svg>`,\n retry: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><path d=\"M5 1C2.8 1 1 2.8 1 5s1.8 4 4 4c1.8 0 3.3-1.2 3.8-2.8H7.5c-.4.9-1.3 1.5-2.5 1.5-1.5 0-2.7-1.2-2.7-2.7S3.5 2.3 5 2.3c.7 0 1.4.3 1.9.8L5.5 4.5H9V1L7.6 2.4C6.9 1.5 5.9 1 5 1z\"/></svg>`,\n cog: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><path d=\"M9.5 5.8l-.8-.5c0-.2.1-.5.1-.8s0-.5-.1-.8l.8-.5c.1-.1.2-.2.1-.4l-.8-1.4c-.1-.1-.2-.2-.4-.1l-1 .3c-.3-.3-.7-.5-1.1-.6L6.1.2C6.1.1 6 0 5.8 0H4.2c-.2 0-.3.1-.3.2l-.2 1c-.4.1-.7.3-1.1.6l-1-.3c-.2 0-.3 0-.4.1l-.8 1.4c-.1.2 0 .3.1.4l.8.5c0 .2-.1.5-.1.8s0 .5.1.8l-.8.5c-.1.1-.2.2-.1.4l.8 1.4c.1.1.2.2.4.1l1-.3c.3.3.7.5 1.1.6l.2 1c0 .1.1.2.3.2h1.6c.2 0 .3-.1.3-.2l.2-1c.4-.1.7-.3 1.1-.6l1 .3c.2 0 .3 0 .4-.1l.8-1.4c.1-.2 0-.3-.1-.4zM5 6.5c-.8 0-1.5-.7-1.5-1.5S4.2 3.5 5 3.5 6.5 4.2 6.5 5 5.8 6.5 5 6.5z\"/></svg>`,\n minimize: `<svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\"><rect x=\"1\" y=\"4\" width=\"8\" height=\"2\"/></svg>`,\n expand: `<svg width=\"8\" height=\"8\" viewBox=\"0 0 8 8\" fill=\"currentColor\"><polygon points=\"1,1 7,4 1,7\"/></svg>`,\n }\n\n // Create the Simulator UI container\n const container = document.createElement(\"div\")\n container.id = \"simulator\"\n container.innerHTML = `\n <style>${simulatorStyles}</style>\n <div id=\"simulator-panel\" class=\"${state.isCollapsed ? \"collapsed\" : \"\"}\">\n <div id=\"simulator-header\">\n <span id=\"simulator-title\">PUZZMO SIMULATOR</span>\n <span class=\"header-sep\">|</span>\n <div id=\"simulator-timer\">--:--</div>\n <span class=\"header-sep\">|</span>\n <div id=\"simulator-header-controls\">\n <button id=\"simulator-header-pause\" class=\"header-icon-btn\" title=\"Pause\" disabled>${icons.pause}</button>\n <button id=\"simulator-header-retry\" class=\"header-icon-btn\" title=\"Retry\" disabled>${icons.retry}</button>\n </div>\n <span class=\"header-sep\">|</span>\n <div id=\"simulator-header-status\">\n <span id=\"simulator-header-indicator\" class=\"waiting\"></span>\n <span id=\"simulator-header-status-text\">Waiting...</span>\n </div>\n <span class=\"header-spacer\"></span>\n <button id=\"simulator-header-settings\" class=\"header-icon-btn\" title=\"Settings\">${icons.cog}</button>\n <button id=\"simulator-toggle\" class=\"header-icon-btn\" title=\"Minimize\">${icons.minimize}</button>\n </div>\n <div id=\"simulator-body\">\n <div id=\"simulator-tabs\">\n ${views\n .filter((v) => v.id !== \"auth\")\n .map(\n (v) =>\n `<button class=\"simulator-tab\" data-tab=\"${v.id}\">${v.label}<span class=\"simulator-tab-badge\" data-badge=\"${v.id}\"></span></button>`,\n )\n .join(\"\")}\n </div>\n <div id=\"simulator-content\" class=\"hidden\">\n ${views.map((v) => `<div id=\"simulator-tab-${v.id}\" class=\"simulator-tab-content\">${v.render()}</div>`).join(\"\")}\n </div>\n </div>\n </div>\n `\n\n // Add to DOM\n document.body.appendChild(container)\n\n // Get UI elements\n const panel = container.querySelector(\"#simulator-panel\") as HTMLElement\n const header = container.querySelector(\"#simulator-header\") as HTMLElement\n const headerIndicator = container.querySelector(\"#simulator-header-indicator\") as HTMLElement\n const headerStatusText = container.querySelector(\"#simulator-header-status-text\") as HTMLElement\n const headerPauseBtn = container.querySelector(\"#simulator-header-pause\") as HTMLButtonElement\n const headerRetryBtn = container.querySelector(\"#simulator-header-retry\") as HTMLButtonElement\n const headerSettingsBtn = container.querySelector(\"#simulator-header-settings\") as HTMLButtonElement\n const toggleBtn = container.querySelector(\"#simulator-toggle\") as HTMLButtonElement\n const timerEl = container.querySelector(\"#simulator-timer\") as HTMLElement\n const tabsContainer = container.querySelector(\"#simulator-tabs\") as HTMLElement\n const contentEl = container.querySelector(\"#simulator-content\") as HTMLElement\n\n // Helper to get elements within container\n const getElement = <T extends HTMLElement>(selector: string): T | null => {\n return container.querySelector(selector) as T | null\n }\n\n // Update pause button icon\n const updatePauseIcon = (isPaused: boolean) => {\n headerPauseBtn.innerHTML = isPaused ? icons.play : icons.pause\n headerPauseBtn.title = isPaused ? \"Resume\" : \"Pause\"\n }\n\n // Update status display (both in ctrl tab and header)\n const updateStatus = (text: string, className: string) => {\n const statusText = getElement<HTMLElement>(\"#simulator-status .text\")\n const statusIndicator = getElement<HTMLElement>(\"#simulator-status .indicator\")\n if (statusText) statusText.textContent = text\n if (statusIndicator) statusIndicator.className = `indicator ${className}`\n headerIndicator.className = className\n headerStatusText.textContent = text\n }\n\n // Update timer display\n const updateTimer = (display: string, penalty?: string) => {\n if (penalty && penalty !== \"0\") {\n timerEl.innerHTML = `${display}<span class=\"penalty\">+${penalty}</span>`\n } else {\n timerEl.textContent = display\n }\n }\n\n // Tab switching logic\n const switchTab = (tabName: TabName) => {\n state.activeTab = tabName\n contentEl.classList.remove(\"hidden\")\n persistTab(tabName)\n\n // Update tab buttons\n tabsContainer.querySelectorAll(\".simulator-tab\").forEach((t) => {\n t.classList.toggle(\"active\", t.getAttribute(\"data-tab\") === tabName)\n })\n\n // Update tab content\n container.querySelectorAll(\".simulator-tab-content\").forEach((c) => {\n c.classList.toggle(\"active\", c.id === `simulator-tab-${tabName}`)\n })\n\n // Update header button to show active when auth tab is selected\n headerSettingsBtn.classList.toggle(\"active\", tabName === \"auth\")\n\n // Call onActivate for the view\n const view = views.find((v) => v.id === tabName)\n view?.onActivate?.(context)\n }\n\n // Expand/collapse helper\n const setCollapsed = (collapsed: boolean) => {\n state.isCollapsed = collapsed\n panel.classList.toggle(\"collapsed\", collapsed)\n persistCollapsed(collapsed)\n\n if (collapsed) {\n contentEl.classList.add(\"hidden\")\n tabsContainer.querySelectorAll(\".simulator-tab\").forEach((t) => t.classList.remove(\"active\"))\n } else {\n switchTab(state.activeTab)\n }\n }\n\n // Update thumbnail (delegated to thumbView)\n const updateThumbnail = () => {\n thumbView.updatePreview(context)\n }\n\n // Load puzzle data from fixtures\n const loadPuzzle = async (): Promise<any> => {\n if (state.puzzleData) return state.puzzleData\n\n if (!fixtures || fixtures.size === 0) {\n throw new Error(\"No fixtures configured. Add puzzle JSON files to a fixtures directory and pass fixturesGlob to the simulator.\")\n }\n\n // Pick the selected fixture, or the first one available\n const category = state.selectedCategory ?? fixtureCategories[0]\n const categoryMap = category ? fixtures.get(category) : undefined\n if (!categoryMap || categoryMap.size === 0) {\n throw new Error(`No puzzles found in fixture category \"${category}\"`)\n }\n\n const puzzleName = state.selectedPuzzle ?? categoryMap.keys().next().value\n const puzzleData = puzzleName ? categoryMap.get(puzzleName) : undefined\n if (!puzzleData) {\n throw new Error(`Puzzle \"${puzzleName}\" not found in category \"${category}\"`)\n }\n\n state.puzzleData = puzzleData\n console.log(\"Simulator: Puzzle loaded from fixtures\", { category, puzzle: puzzleName })\n state.originalPuzzle = JSON.stringify(state.puzzleData, null, 2)\n\n const puzzleTextarea = getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n if (puzzleTextarea) puzzleTextarea.value = state.originalPuzzle\n\n if (state.activeTab === \"thumb\") updateThumbnail()\n\n return state.puzzleData\n }\n\n // Wrapped sendToGame with logger\n const messageLogger = createMessageLogger((entry) => {\n msgsView.addLogEntry(entry, context)\n })\n\n const wrappedSendToGame = (type: keyof MessagesReceived, data: any) => {\n sendToGame(type, data, messageLogger)\n }\n\n // Update badge count for a tab\n const updateBadge = (tabId: string, count: number | undefined) => {\n const badge = container.querySelector(`[data-badge=\"${tabId}\"]`) as HTMLElement\n if (badge) {\n badge.textContent = count && count > 0 ? String(count) : \"\"\n }\n }\n\n // Create context object for views\n const context: SimulatorContext = {\n state,\n getElement,\n sendToGame: wrappedSendToGame,\n logMessage: messageLogger.log,\n loadPuzzle,\n updateStatus,\n updateTimer,\n setCollapsed,\n switchTab,\n updateThumbnail,\n updateBadge,\n fixtures,\n fixtureCategories,\n gameSlug: config.slug ?? null,\n }\n\n console.log(\"[Simulator] Context created with gameSlug:\", context.gameSlug)\n\n // Bind all views\n views.forEach((view) => view.bind(context))\n\n // Tab click handlers\n tabsContainer.querySelectorAll(\".simulator-tab\").forEach((tab) => {\n tab.addEventListener(\"click\", () => {\n const tabName = tab.getAttribute(\"data-tab\") as TabName\n switchTab(tabName)\n updateBadge(tabName, 0)\n })\n })\n\n // Toggle button (only visible when expanded) - minimizes the panel\n toggleBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n setCollapsed(true)\n })\n\n // Header pause button\n headerPauseBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n if (state.isPaused) {\n wrappedSendToGame(\"RESUME_GAME\", {})\n state.isPaused = false\n updatePauseIcon(false)\n updateStatus(\"Running\", \"ready\")\n } else {\n wrappedSendToGame(\"PAUSE_GAME\", {})\n state.isPaused = true\n updatePauseIcon(true)\n updateStatus(\"Paused\", \"paused\")\n }\n // Sync with ctrl tab buttons\n const pauseBtn = getElement<HTMLButtonElement>(\"#simulator-pause\")\n if (pauseBtn) pauseBtn.textContent = state.isPaused ? \"Resume\" : \"Pause\"\n })\n\n // Header retry button\n headerRetryBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n wrappedSendToGame(\"RETRY_PUZZLE\", {})\n state.hasStarted = false\n state.isPaused = false\n updatePauseIcon(false)\n headerPauseBtn.disabled = true\n updateStatus(\"Ready to retry\", \"ready\")\n // Sync with ctrl tab buttons\n const pauseBtn = getElement<HTMLButtonElement>(\"#simulator-pause\")\n const startBtn = getElement<HTMLButtonElement>(\"#simulator-start\")\n if (pauseBtn) {\n pauseBtn.disabled = true\n pauseBtn.textContent = \"Pause\"\n }\n if (startBtn) startBtn.textContent = \"Start\"\n })\n\n // Header settings button - opens auth tab\n headerSettingsBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation()\n if (state.isCollapsed) {\n setCollapsed(false)\n }\n switchTab(\"auth\")\n })\n\n // Header click (when collapsed) - expands the panel\n header.addEventListener(\"click\", (e) => {\n if (state.isCollapsed && e.target !== toggleBtn) {\n setCollapsed(false)\n }\n })\n\n // Restore tab state on init (always select a tab when not collapsed)\n if (!state.isCollapsed) {\n switchTab(state.activeTab)\n }\n\n // Check for OAuth callback parameters on page load - handle regardless of which tab is active\n const urlParams = new URLSearchParams(window.location.search)\n if (urlParams.has(\"code\") || urlParams.has(\"error\")) {\n // Switch to auth tab to handle the OAuth callback\n setCollapsed(false)\n switchTab(\"auth\")\n }\n\n // Create READY_DATA payload\n const createReadyData = (puzzle: any): BootstrapGameData => {\n return {\n userState: {\n gameSettings: {},\n id: \"simulator-user\",\n nakamaLogin: \"simulator\",\n ownerID: \"simulator-owner\",\n },\n currentUser: null,\n startOrFindGameplay: {\n gamePlayed: {\n additionalTimeAddedSecs: 0,\n boardState: state.currentInputStr,\n cheatsUsed: 0,\n combinedTimeSecs: 0,\n completed: false,\n createdAt: new Date().toISOString(),\n elapsedTimeSecs: 0,\n hintsUsed: 0,\n id: `simulator-gameplay-${Date.now()}`,\n ownerID: \"simulator-owner\",\n pointsAwarded: 0,\n resetsUsed: 0,\n slug: \"simulator-game\",\n viewerOwnsPuzzle: true,\n puzzle: {\n id: \"simulator-puzzle\",\n name: \"Proto Jig Puzzle\",\n puzzle: JSON.stringify(puzzle),\n seriesNumber: 1,\n game: {\n assetsPath: \"./assets/\",\n assetsSha: \"\",\n displayName: \"Proto Game\",\n exposedGlobalFunction: \"startGame\",\n jsPath: \"\",\n slug: \"proto-game\",\n },\n },\n },\n },\n theme: state.selectedTheme,\n hostFlags: [],\n hostContext: [],\n appRuntimeContract: \"1.0\",\n }\n }\n\n // Handle READY message from game - send READY_DATA with puzzle\n const handleReady = async () => {\n updateStatus(\"Loading puzzle...\", \"waiting\")\n\n try {\n const puzzle = await loadPuzzle()\n const readyData = createReadyData(puzzle)\n\n updateStatus(\"Sending READY_DATA...\", \"waiting\")\n wrappedSendToGame(\"READY_DATA\", readyData)\n\n updateStatus(\"Waiting for game to load...\", \"waiting\")\n } catch (error) {\n updateStatus(`Error: ${error}`, \"paused\")\n }\n }\n\n // Handle READY_GAME_LOADED message from game\n const handleGameLoaded = () => {\n console.log(\"Simulator: Game loaded, ready to start\")\n\n // Enable header buttons\n headerRetryBtn.disabled = false\n\n // Notify views\n views.forEach((v) => v.onMessage?.(\"READY_GAME_LOADED\", undefined, context))\n\n // Auto-start if configured\n if (autoStart && !state.hasStarted) {\n setTimeout(() => {\n const startBtn = getElement<HTMLButtonElement>(\"#simulator-start\")\n startBtn?.click()\n // Enable pause button after start\n headerPauseBtn.disabled = false\n state.hasStarted = true\n updateStatus(\"Running\", \"ready\")\n }, 100)\n }\n }\n\n // Set up message listener\n createMessageListener((type, data) => {\n // Handle core messages\n if (type === \"READY\") {\n console.log(\"Simulator: Received READY from game\")\n handleReady()\n return\n }\n\n if (type === \"INITIALIZE_SETTINGS\") {\n console.log(\"Simulator: Game initialized settings\", data)\n return\n }\n\n if (type === \"READY_GAME_LOADED\") {\n handleGameLoaded()\n return\n }\n\n if (type === \"TIMER_TICK\") {\n if (data?.display) {\n const [display, penalty] = data.display\n updateTimer(display, penalty)\n }\n return\n }\n\n if (type === \"TIMER_SYNC\") {\n return\n }\n\n if (type === \"SHOW_GAME_COMPLETE_SCREEN\") {\n console.log(\"Simulator: Show completion screen\", data)\n return\n }\n\n if (type === \"SIDEBAR_UPDATE\") {\n console.log(\"Simulator: Sidebar update\", data)\n return\n }\n\n // Delegate to views for other messages\n views.forEach((v) => v.onMessage?.(type, data, context))\n\n // Handle state updates\n // boardState can be at data.boardState (legacy) or data.input.boardState (current SDK format)\n const boardState = data?.input?.boardState ?? data?.boardState\n if (type === \"UPLOAD_NEW_GAME_STATE\" && boardState) {\n state.currentInputStr = boardState\n if (state.activeTab === \"thumb\") {\n updateThumbnail()\n }\n console.log(\"Simulator: Game state uploaded\", data)\n }\n\n if (type === \"GAME_COMPLETED\") {\n console.log(\"Simulator: Game completed!\", data)\n headerPauseBtn.disabled = true\n state.hasStarted = false\n updateStatus(\"Completed!\", \"ready\")\n }\n }, messageLogger)\n\n console.log(\"Simulator initialized\")\n\n // Function to update fixtures after initial creation\n // Note: This is called when createSimulator is called again with fixtures\n // after the singleton was already created. We DON'T reload here - just update state.\n const updateFixtures = (newFixtureImports: FixtureImports) => {\n fixtures = parseFixtures(newFixtureImports)\n fixtureCategories = Array.from(fixtures.keys()).sort()\n context.fixtures = fixtures\n context.fixtureCategories = fixtureCategories\n\n // Update state's selected category if it's not set or invalid\n const storedCategory = localStorage.getItem(\"simulator-fixture-category\")\n if (!state.selectedCategory || !fixtureCategories.includes(state.selectedCategory)) {\n state.selectedCategory =\n storedCategory && fixtureCategories.includes(storedCategory) ? storedCategory : (fixtureCategories[0] ?? null)\n }\n\n // Re-bind ctrl view to pick up new fixtures\n const ctrlView = views.find((v) => v.id === \"ctrl\")\n ctrlView?.bind(context)\n\n console.log(\"Simulator: Fixtures updated\", { categories: fixtureCategories, selectedCategory: state.selectedCategory })\n }\n\n // Store instance for subsequent calls\n simulatorInstance = {\n updateFixtures,\n sendToGame: wrappedSendToGame,\n loadPuzzle,\n }\n\n return simulatorInstance\n}\n\nexport { createSimulator }\n"],"mappings":"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"}