@puzzmo/sdk 0.0.7 → 1.0.0

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.
Files changed (75) hide show
  1. package/README.md +80 -260
  2. package/dist/createSimulator-9IxV0l3_.js +2749 -0
  3. package/dist/createSimulator-9IxV0l3_.js.map +1 -0
  4. package/dist/createSimulator-DxhvbnJB.cjs +1428 -0
  5. package/dist/createSimulator-DxhvbnJB.cjs.map +1 -0
  6. package/dist/index.cjs +2 -2
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +3 -13
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +267 -14
  11. package/dist/index.js.map +1 -1
  12. package/dist/objectSpread2-C4RR_HMd.cjs +1 -0
  13. package/dist/objectSpread2-CJo2CZQ6.js +76 -0
  14. package/dist/sdk.d.ts +61 -0
  15. package/dist/sdk.d.ts.map +1 -0
  16. package/dist/simulator/createSimulator.d.ts +41 -0
  17. package/dist/simulator/createSimulator.d.ts.map +1 -0
  18. package/dist/simulator/fixtures.d.ts +18 -0
  19. package/dist/simulator/fixtures.d.ts.map +1 -0
  20. package/dist/simulator/index.cjs +1 -0
  21. package/dist/simulator/index.d.ts +3 -0
  22. package/dist/simulator/index.d.ts.map +1 -0
  23. package/dist/simulator/index.js +2 -0
  24. package/dist/simulator/messaging.d.ts +12 -0
  25. package/dist/simulator/messaging.d.ts.map +1 -0
  26. package/dist/simulator/standalone.cjs +2 -0
  27. package/dist/simulator/standalone.cjs.map +1 -0
  28. package/dist/simulator/standalone.d.ts +7 -0
  29. package/dist/simulator/standalone.d.ts.map +1 -0
  30. package/dist/simulator/standalone.js +19 -0
  31. package/dist/simulator/standalone.js.map +1 -0
  32. package/dist/simulator/state.d.ts +12 -0
  33. package/dist/simulator/state.d.ts.map +1 -0
  34. package/dist/simulator/styles.d.ts +2 -0
  35. package/dist/simulator/styles.d.ts.map +1 -0
  36. package/dist/simulator/types.d.ts +72 -0
  37. package/dist/simulator/types.d.ts.map +1 -0
  38. package/dist/simulator/views/AuthView.d.ts +3 -0
  39. package/dist/simulator/views/AuthView.d.ts.map +1 -0
  40. package/dist/simulator/views/CheckpointsView.d.ts +3 -0
  41. package/dist/simulator/views/CheckpointsView.d.ts.map +1 -0
  42. package/dist/simulator/views/CtrlView.d.ts +3 -0
  43. package/dist/simulator/views/CtrlView.d.ts.map +1 -0
  44. package/dist/simulator/views/DataView.d.ts +3 -0
  45. package/dist/simulator/views/DataView.d.ts.map +1 -0
  46. package/dist/simulator/views/DoneView.d.ts +3 -0
  47. package/dist/simulator/views/DoneView.d.ts.map +1 -0
  48. package/dist/simulator/views/FeaturesView.d.ts +4 -0
  49. package/dist/simulator/views/FeaturesView.d.ts.map +1 -0
  50. package/dist/simulator/views/MsgsView.d.ts +6 -0
  51. package/dist/simulator/views/MsgsView.d.ts.map +1 -0
  52. package/dist/simulator/views/ThemeView.d.ts +3 -0
  53. package/dist/simulator/views/ThemeView.d.ts.map +1 -0
  54. package/dist/simulator/views/ThumbView.d.ts +6 -0
  55. package/dist/simulator/views/ThumbView.d.ts.map +1 -0
  56. package/dist/simulator/views/index.d.ts +10 -0
  57. package/dist/simulator/views/index.d.ts.map +1 -0
  58. package/dist/themes.d.ts +46 -0
  59. package/dist/themes.d.ts.map +1 -0
  60. package/dist/types.d.ts +232 -0
  61. package/dist/types.d.ts.map +1 -0
  62. package/dist/vite.cjs +11 -0
  63. package/dist/vite.cjs.map +1 -0
  64. package/dist/vite.d.ts +16 -0
  65. package/dist/vite.d.ts.map +1 -0
  66. package/dist/vite.js +45 -0
  67. package/dist/vite.js.map +1 -0
  68. package/dist/workshop.d.ts +7 -175
  69. package/dist/workshop.d.ts.map +1 -1
  70. package/package.json +22 -16
  71. package/dist/index.iife.js +0 -2
  72. package/dist/index.iife.js.map +0 -1
  73. package/dist/index.umd.js +0 -2
  74. package/dist/index.umd.js.map +0 -1
  75. package/dist/puzzmoSDK.d.ts +0 -237
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSimulator-DxhvbnJB.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 { ThumbnailConfig, ThumbnailFunction } 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: ThumbnailFunction } | 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 ThumbnailFunction }\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 { ThumbnailConfig, ThumbnailFunction } 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: ThumbnailFunction } | 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 ThumbnailFunction }\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 in index.html:\n * ```html\n * <script type=\"module\">\n * import { createSimulator } from \"@puzzmo/sdk/simulator\"\n * createSimulator({ puzzlePath: \"./sample-puzzle.json\", autoStart: true })\n * </script>\n * ```\n *\n * With fixtures (recommended for multiple puzzles):\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// Default puzzle path\nconst DEFAULT_PUZZLE_PATH = \"./sample-puzzle.json\"\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 puzzlePath = config.puzzlePath ?? DEFAULT_PUZZLE_PATH\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((v) => `<button class=\"simulator-tab\" data-tab=\"${v.id}\">${v.label}<span class=\"simulator-tab-badge\" data-badge=\"${v.id}\"></span></button>`)\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\n const loadPuzzle = async (): Promise<any> => {\n if (state.puzzleData) return state.puzzleData\n\n try {\n const response = await fetch(puzzlePath)\n if (!response.ok) {\n throw new Error(`Failed to load puzzle from ${puzzlePath}`)\n }\n state.puzzleData = await response.json()\n console.log(\"Simulator: Puzzle loaded\", state.puzzleData)\n state.originalPuzzle = JSON.stringify(state.puzzleData, null, 2)\n\n // Update puzzle textarea if exists\n const puzzleTextarea = getElement<HTMLTextAreaElement>(\"#simulator-puzzle\")\n if (puzzleTextarea) puzzleTextarea.value = state.originalPuzzle\n\n // Refresh thumbnail if thumb tab is active\n if (state.activeTab === \"thumb\") {\n updateThumbnail()\n }\n return state.puzzleData\n } catch (error) {\n console.error(\"Simulator: Failed to load puzzle\", error)\n throw error\n }\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 metric1: 0,\n metric2: 0,\n metric3: 0,\n metric4: 0,\n metricStrings: [],\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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"}
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class s extends Error{constructor(o,e,t){super(e),this.type=o,this.originalError=t,this.name="WorkshopImportError"}}function l(r){return{hello:()=>{console.log("Hello from Puzzmo SDK!",r)}}}exports.WorkshopImportError=s;exports.createPuzzmoSDK=l;
2
- //# sourceMappingURL=index.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./objectSpread2-C4RR_HMd.cjs`);function t(e){let t=e>=3600*1e3;return new Date(e).toISOString().slice(t?11:14,-1).split(`.`)[0]}function n(e=0,n=0){let r=e,i=n,a=0,o,s,c,l=()=>{var e;if(s===void 0)return r+i;if(o!==void 0)return o;let t=((e=c)==null?performance.now():e)-s-a;return r+i+t};return{_init:()=>{s===void 0&&(s=performance.now(),c=void 0)},_pause:()=>{c!==void 0||s===void 0||(c=performance.now())},_resume:()=>{c!==void 0&&(a+=performance.now()-c,c=void 0)},_reset:(e=0,t=0)=>{r=e,i=t,a=0,o=void 0,s=void 0,c=void 0},_conclude:()=>{if(c!==void 0){o=l();return}if(s===void 0){o=r+i;return}o=l()},timeMs:()=>l(),timeSecs:()=>l()/1e3,addedTimeMs:()=>i,addedTimeSecs:()=>i/1e3,timeWithoutPenaltySecs:()=>(l()-i)/1e3,addPenalty:e=>{i+=e},isPaused:()=>c!==void 0||s===void 0,isRunning:()=>s!==void 0&&c===void 0,display:()=>{let e=l()-i;return[t(Math.max(0,e)),i===0?``:t(i)]}}}function r(){let e=new Map;return typeof window<`u`&&window.addEventListener(`message`,t=>{var n;if(!(!(t==null||(n=t.data)==null)&&n.type))return;let r=t.data.type,i=e.get(r);if(i){var a,o;let e=(a=(o=t.data.data)==null?t.data.json:o)==null?{}:a;r!==`TIMER_TICK`&&r!==`TIMER_SYNC`&&console.log(`[Puzzmo SDK] received:`,r,e),i.forEach(t=>t(e))}}),{sendMessage:(e,t)=>{var n,r;let i={type:e,json:t,_:`p`,__:`mp`,private:!0};`parent`in window&&window.parent!==window&&window.parent.postMessage(i,`*`),window.postMessage(i,`*`),`webkit`in window&&!((n=window.webkit)==null||(n=n.messageHandlers)==null)&&n.app&&window.webkit.messageHandlers.app.postMessage(i),`puzzmoMessageString`in window&&window.puzzmoMessageString(JSON.stringify(i)),`ReactNativeWebView`in window&&(r=window.ReactNativeWebView)!=null&&r.postMessage&&window.ReactNativeWebView.postMessage(JSON.stringify(i)),e!==`TIMER_TICK`&&e!==`TIMER_SYNC`&&console.log(`[Puzzmo SDK] sent:`,e,t)},onMessage:(t,n)=>(e.has(t)||e.set(t,new Set),e.get(t).add(n),()=>{var r;(r=e.get(t))==null||r.delete(n)})}}var i=r();const a=(t={})=>{let r=null,a=null,o=()=>{var e;return r==null||(e=r.startOrFindGameplay)==null?void 0:e.gamePlayed},s=()=>{var e,t;return(e=(t=o())==null?void 0:t.id)==null?null:e},c=()=>{var e,t;return(e=(t=o())==null?void 0:t.puzzle.puzzle)==null?null:e},l=()=>{var e,t;return(e=(t=o())==null?void 0:t.boardState)==null?null:e},u=()=>{var e;return(e=r==null?void 0:r.theme)==null?null:e},d=()=>{var e,t;return(e=(t=o())==null?void 0:t.completed)==null?!1:e},f=new Map,p=n(),m=null,h=null,g=()=>{m||(m=setInterval(()=>{if(p.isPaused())return;let[e,t]=p.display();i.sendMessage(`TIMER_TICK`,{display:[e,t]})},500),h=setInterval(()=>{p.isPaused()||i.sendMessage(`TIMER_SYNC`,Math.floor(p.timeWithoutPenaltySecs()))},1e4))},_=()=>{m&&(clearInterval(m),m=null),h&&(clearInterval(h),h=null)},v=(e,t)=>{let n=f.get(e);n&&n.forEach(e=>e(t))};return i.onMessage(`START_GAME`,()=>{p._init(),g(),v(`start`)}),i.onMessage(`PAUSE_GAME`,()=>{p._pause(),v(`pause`)}),i.onMessage(`RESUME_GAME`,()=>{p._resume(),v(`resume`)}),i.onMessage(`SETTINGS_UPDATE`,e=>v(`settingsUpdate`,e)),i.onMessage(`RETRY_PUZZLE`,()=>{p._reset(),_(),v(`retry`)}),i.onMessage(`READY_DATA`,e=>{var t;let n=e;r=n;let i=(t=n.startOrFindGameplay)==null?void 0:t.gamePlayed;if(i){var o,s;let e=((o=i.elapsedTimeSecs)==null?0:o)*1e3,t=((s=i.additionalTimeAddedSecs)==null?0:s)*1e3;p._reset(e,t)}a&&(a(n),a=null)}),{timer:{timeMs:()=>p.timeMs(),timeSecs:()=>p.timeSecs(),addedTimeMs:()=>p.addedTimeMs(),addedTimeSecs:()=>p.addedTimeSecs(),timeWithoutPenaltySecs:()=>p.timeWithoutPenaltySecs(),display:()=>p.display(),addPenalty:e=>p.addPenalty(e),isPaused:()=>p.isPaused(),isRunning:()=>p.isRunning()},gameReady:function(){var n=e.n(function*(){var e;if(i.sendMessage(`READY`,{}),c())return{puzzleString:c(),boardState:l(),theme:u(),completed:d(),readyData:r};let n=(e=t.timeout)==null?5e3:e;yield new Promise((e,t)=>{a=e,setTimeout(()=>{a&&(a=null,t(Error(`Timeout waiting for READY_DATA after ${n}ms`)))},n)});let o=c();if(!o)throw Error(`READY_DATA received but no puzzle data found`);return{puzzleString:o,boardState:l(),theme:u(),completed:d(),readyData:r}});return function(){return n.apply(this,arguments)}}(),gameLoaded:(e={})=>{i.sendMessage(`READY_GAME_LOADED`,{state:e,gameRuntimeContract:`1.0`,embedRuntimeContract:`1.0`})},on:(e,t)=>(f.has(e)||f.set(e,new Set),f.get(e).add(t),()=>{var n;(n=f.get(e))==null||n.delete(t)}),off:(e,t)=>{var n;(n=f.get(e))==null||n.delete(t)},updateGameState:(e,t)=>{var n,r;let a=s();a&&i.sendMessage(`UPLOAD_NEW_GAME_STATE`,{id:a,input:{boardState:e,elapsedTimeSecs:(n=t==null?void 0:t.elapsedTimeSecs)==null?p.timeWithoutPenaltySecs():n,additionalTimeAddedSecs:(r=t==null?void 0:t.additionalTimeAddedSecs)==null?p.addedTimeSecs():r,hintsUsed:t==null?void 0:t.hintsUsed,resetsUsed:t==null?void 0:t.resetsUsed,metric1:0,metric2:0,metric3:0,metric4:0,metricStrings:[],collabUserReferences:[]}})},gameCompleted:(t,n)=>{var r,a,o,c,l;p._conclude(),_();let u=e.t(e.t({},t),{},{elapsedTimeSecs:(r=t.elapsedTimeSecs)==null?p.timeWithoutPenaltySecs():r,additionalTimeAddedSecs:(a=t.additionalTimeAddedSecs)==null?p.addedTimeSecs():a}),d=(o=n==null?void 0:n.deeds)==null?[]:o;d.push({id:`points`,value:t.pointsAwarded}),d.push({id:`time`,value:Math.round((c=u.elapsedTimeSecs)==null?0:c)+Math.round((l=u.additionalTimeAddedSecs)==null?0:l)});let f=s();f&&i.sendMessage(`GAME_COMPLETED`,{id:f,input:u,config:n})},showCompletionScreen:(e,t,n=!0)=>{i.sendMessage(`SHOW_GAME_COMPLETE_SCREEN`,{results:e,showRetry:n,gameplay:t})},hitCheckpoint:(e,t,n)=>{var r;if(!s())return;let a=(r=l())==null?``:r,o={elapsedTimeSecs:p.timeWithoutPenaltySecs(),additionalTimeAddedSecs:p.addedTimeSecs()};i.sendMessage(`HIT_CHECKPOINT`,{checkpointName:e,gameplay:{inputStr:a,play:o},checkpointConfig:t,augConfig:n==null?{}:n})},_hostAPI:i}};var o=class extends Error{constructor(e,t,n){super(t),this.type=e,this.originalError=n,this.name=`WorkshopImportError`}};exports.WorkshopImportError=o,exports.createPuzzmoSDK=a;
2
+ //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/workshop.ts","../src/index.ts"],"sourcesContent":["import type { GameSettingsUIComponents } from \"@puzzmo-com/shared/hostAPI\";\n\n/**\n * Severity level for validation issues.\n *\n * - `error`: Critical issue that prevents puzzle from being valid\n * - `warning`: Issue that should be addressed but doesn't prevent usage\n * - `info`: Informational message about the puzzle\n */\nexport type ValidationLevel = \"error\" | \"warning\" | \"info\";\n\n/** Represents a single validation issue found during puzzle validation. */\nexport interface ValidationIssue {\n /** Severity level of this validation issue */\n level: ValidationLevel;\n /** Human-readable description of the issue */\n message: string;\n /** Line number in the puzzle data where the issue occurs (1-indexed, optional) */\n line?: number;\n /**\n * Column position in the puzzle data where the issue occurs (1-indexed,\n * optional)\n */\n col?: number;\n /** Length of the problematic text segment for highlighting (optional) */\n length?: number;\n}\n\n/**\n * Complete validation report for a puzzle. Contains the overall validation\n * status and any issues found.\n */\nexport interface ValidationReport {\n /** Whether the puzzle passed validation without errors */\n success: boolean;\n /** Array of validation issues found (errors, warnings, and info messages) */\n issues: ValidationIssue[];\n}\n\n/**\n * Types of errors that can occur during puzzle import.\n *\n * - `invalid_format`: The file format is not supported or recognized\n * - `parsing_error`: The file format is valid but parsing failed\n * - `unknown`: An unexpected error occurred during import\n */\nexport type ImportErrorType = \"invalid_format\" | \"parsing_error\" | \"unknown\";\n\n/**\n * Custom error class for workshop import failures. Thrown by the `onImport`\n * function when a puzzle cannot be imported.\n *\n * @example\n *\n * ```typescript\n * throw new WorkshopImportError(\n * \"invalid_format\",\n * \"Unsupported file extension: .xyz\",\n * );\n * ```\n */\nexport class WorkshopImportError extends Error {\n constructor(\n /** The category of import error */\n public type: ImportErrorType,\n /** Human-readable error message */\n message: string,\n /** Optional underlying error that caused the import to fail */\n public originalError?: unknown,\n ) {\n super(message);\n this.name = \"WorkshopImportError\";\n }\n}\n\n/**\n * Result of a successful puzzle import operation. Contains the converted puzzle\n * data and optional metadata.\n */\nexport interface ImportResult {\n /**\n * The converted puzzle data in the target format (e.g., XD format for\n * crosswords)\n */\n data: string;\n /** Optional validation warnings about the imported puzzle */\n warnings?: ValidationIssue[];\n /** Optional puzzle title extracted from the import file */\n title?: string;\n /** Optional list of puzzle authors */\n authors?: string[];\n /** Optional list of puzzle editors */\n editors?: string[];\n}\n\n/**\n * Main interface for a Workshop bundle. Workshop bundles are dynamically loaded\n * modules that provide game-specific functionality for puzzle validation and\n * import.\n *\n * @example\n *\n * ```typescript\n * // In your workshop bundle (e.g., games/crossword/src/workshop/index.ts)\n * export const validator = {\n * validate(data: string): ValidationReport {\n * // Custom validation logic\n * return { success: true, issues: [] };\n * },\n * };\n *\n * export const importer = {\n * async onImport(\n * filename: string,\n * contents: string | ArrayBuffer,\n * ): Promise<ImportResult> {\n * // Custom import logic\n * return { data: convertedData };\n * },\n * };\n * ```\n */\nexport interface WorkshopBundle {\n /** Required validator for puzzle data validation */\n validator: {\n /**\n * Validates puzzle data and returns a report of issues.\n *\n * @param data - The raw puzzle data string to validate\n *\n * @returns Validation report with success status and any issues found\n */\n validate(data: string): Promise<ValidationReport> | ValidationReport;\n };\n /** Optional importer for converting external puzzle file formats */\n importer?: {\n /**\n * Imports a puzzle file and converts it to the game's native format. Should\n * throw `WorkshopImportError` for known failure cases.\n *\n * @param filename - Name of the file being imported\n * @param contents - Raw file contents (string for\n * text files, ArrayBuffer for binary)\n *\n * @returns Import result with converted data and optional metadata\n *\n * @throws {WorkshopImportError} For invalid or unsupported files\n */\n onImport(\n filename: string,\n contents: string | ArrayBuffer,\n ): Promise<ImportResult> | ImportResult;\n };\n /**\n * Optional settings configuration for embed customization in Workshop. Allows\n * Workshop to render a settings form for configuring embed overrides.\n */\n settings?: {\n /** UI component definitions for rendering the settings form */\n components: GameSettingsUIComponents[];\n /** Default values for all settings */\n defaults: Record<string, unknown>;\n };\n /**\n * Optional per-queue editor settings declaration. Shown on the queue page and\n * passed to editor.mount() as config.settings.\n */\n editorSettings?: {\n /** UI component definitions for rendering the editor settings form */\n components: GameSettingsUIComponents[];\n /** Default values for all settings */\n defaults: Record<string, unknown>;\n };\n\n editor?: {\n /** Called when first visiting a puzzle page. */\n mount: (\n element: HTMLElement,\n config: {\n puzzleString: string;\n onChange: (puzzleString: string) => void;\n theme: \"light\" | \"dark\";\n width: number;\n height: number;\n /**\n * Optional function for making chat completions from the editor (e.g.,\n * for AI-assisted puzzle editing). This needs to be enabled for a team\n * explicitly.\n */\n chatCompletion?: (prompt: string) => Promise<string>;\n /**\n * Optional function for fetching URLs from the editor (e.g., for\n * fetching article content). This needs to be enabled for a team\n * explicitly.\n */\n fetchURL?: (url: string) => Promise<{ status: number; body: string }>;\n /**\n * Pre-configured editor settings values from the queue's\n * editorSettings.\n */\n settings?: Record<string, unknown>;\n },\n ) => Promise<{\n unmount: () => void;\n /**\n * Called whenever the puzzle string is updated in the editor from the\n * outside, or when the theme or dimensions update. Workshop will rely on\n * validator to reject / accept updates.\n */\n update: (config: {\n puzzleString?: string;\n theme?: \"light\" | \"dark\";\n width?: number;\n height?: number;\n }) => void;\n }>;\n };\n}\n","import { GameConfig } from \"./puzzmoSDK\";\n\n// Export the auto-generated public SDK types\nexport type * from \"./puzzmoSDK\";\n\n// Export workshop types\nexport * from \"./workshop\";\n\n/**\n * Creates a Puzzmo SDK instance\n *\n * @param config - Configuration options\n *\n * @returns A Puzzmo SDK instance\n */\nexport function createPuzzmoSDK(config: GameConfig) {\n return {\n hello: () => {\n console.log(\"Hello from Puzzmo SDK!\", config);\n },\n };\n}\n\nexport type PuzzmoSDK = ReturnType<typeof createPuzzmoSDK>;\n"],"names":["WorkshopImportError","type","message","originalError","createPuzzmoSDK","config"],"mappings":"gFA6DO,MAAMA,UAA4B,KAAM,CAC7C,YAESC,EAEPC,EAEOC,EACP,CACA,MAAMD,CAAO,EANN,KAAA,KAAAD,EAIA,KAAA,cAAAE,EAGP,KAAK,KAAO,qBACd,CACF,CC1DO,SAASC,EAAgBC,EAAoB,CAClD,MAAO,CACL,MAAO,IAAM,CACX,QAAQ,IAAI,yBAA0BA,CAAM,CAC9C,CAAA,CAEJ"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/sdk.ts","../src/workshop.ts"],"sourcesContent":["import type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n} from \"./types\"\n\nexport type SDK = ReturnType<typeof createPuzzmoSDK>\n\nexport interface PuzzmoSDKOptions {\n /** Optional timeout in ms to wait for READY_DATA (default: 5000) */\n timeout?: number\n}\n\ntype SupportedOutgoingMessages = Pick<\n MessagesSentFromEmbed,\n | \"READY\"\n | \"READY_GAME_LOADED\"\n | \"GAME_COMPLETED\"\n | \"SHOW_GAME_COMPLETE_SCREEN\"\n | \"TIMER_TICK\"\n | \"TIMER_SYNC\"\n | \"UPLOAD_NEW_GAME_STATE\"\n | \"HIT_CHECKPOINT\"\n>\n\ntype SupportedIncomingMessages = Pick<\n MessagesReceived,\n \"READY_DATA\" | \"START_GAME\" | \"PAUSE_GAME\" | \"RESUME_GAME\" | \"SETTINGS_UPDATE\" | \"RETRY_PUZZLE\"\n>\n\ntype MessageHandler<T extends keyof SupportedIncomingMessages> = (data: SupportedIncomingMessages[T]) => void\n\nexport type SDKEventMap = {\n start: void\n pause: void\n resume: void\n retry: void\n settingsUpdate: any\n}\n\nexport type SDKEventType = keyof SDKEventMap\n\nexport interface SDKTimer {\n /** Get elapsed time in milliseconds */\n timeMs: () => number\n /** Get elapsed time in seconds */\n timeSecs: () => number\n /** Get added/penalty time in milliseconds */\n addedTimeMs: () => number\n /** Get added/penalty time in seconds */\n addedTimeSecs: () => number\n /** Get elapsed time without penalties in seconds */\n timeWithoutPenaltySecs: () => number\n /** Get formatted display strings [elapsed, added] */\n display: () => [string, string]\n /** Add penalty time in milliseconds */\n addPenalty: (ms: number) => void\n /** Check if timer is paused */\n isPaused: () => boolean\n /** Check if timer has been started */\n isRunning: () => boolean\n}\n\nfunction formatTime(timeMs: number): string {\n const isAnHourOrMore = timeMs >= 60 * 60 * 1000\n return new Date(timeMs)\n .toISOString()\n .slice(isAnHourOrMore ? 11 : 14, -1)\n .split(\".\")[0]\n}\n\nfunction createTimer(\n initialTimeMs = 0,\n initialAddedTimeMs = 0,\n): SDKTimer & {\n _init: () => void\n _pause: () => void\n _resume: () => void\n _reset: (initialTimeMs?: number, initialAddedTimeMs?: number) => void\n _conclude: () => void\n} {\n let baseTime = initialTimeMs\n let addedTime = initialAddedTimeMs\n let pausedTime = 0\n let concludeTime: number | undefined\n\n let startDate: number | undefined = undefined\n let pausedDate: number | undefined = undefined\n\n const getTime = (): number => {\n if (startDate === undefined) return baseTime + addedTime\n if (concludeTime !== undefined) return concludeTime\n const now = pausedDate ?? performance.now()\n const elapsed = now - startDate - pausedTime\n return baseTime + addedTime + elapsed\n }\n\n return {\n _init: () => {\n if (startDate === undefined) {\n startDate = performance.now()\n pausedDate = undefined\n }\n },\n _pause: () => {\n if (pausedDate !== undefined || startDate === undefined) return\n pausedDate = performance.now()\n },\n _resume: () => {\n if (pausedDate === undefined) return\n pausedTime += performance.now() - pausedDate\n pausedDate = undefined\n },\n _reset: (newInitialTimeMs = 0, newInitialAddedTimeMs = 0) => {\n baseTime = newInitialTimeMs\n addedTime = newInitialAddedTimeMs\n pausedTime = 0\n concludeTime = undefined\n startDate = undefined\n pausedDate = undefined\n },\n _conclude: () => {\n if (pausedDate !== undefined) {\n concludeTime = getTime()\n return\n }\n if (startDate === undefined) {\n concludeTime = baseTime + addedTime\n return\n }\n concludeTime = getTime()\n },\n timeMs: () => getTime(),\n timeSecs: () => getTime() / 1000,\n addedTimeMs: () => addedTime,\n addedTimeSecs: () => addedTime / 1000,\n timeWithoutPenaltySecs: () => (getTime() - addedTime) / 1000,\n addPenalty: (ms: number) => {\n addedTime += ms\n },\n isPaused: () => pausedDate !== undefined || startDate === undefined,\n isRunning: () => startDate !== undefined && pausedDate === undefined,\n display: () => {\n const elapsed = getTime() - addedTime\n const elapsedStr = formatTime(Math.max(0, elapsed))\n const addedStr = addedTime === 0 ? \"\" : formatTime(addedTime)\n return [elapsedStr, addedStr]\n },\n }\n}\n\nfunction createHostAPI() {\n const messageHandlers = new Map<string, Set<(data: any) => void>>()\n\n const sendMessage = <T extends keyof SupportedOutgoingMessages>(type: T, json: SupportedOutgoingMessages[T]) => {\n const message = { type, json, _: \"p\", __: \"mp\", private: true }\n\n if (\"parent\" in window && window.parent !== window)\n window.parent.postMessage(message, \"*\")\n\n window.postMessage(message, \"*\")\n\n if (\"webkit\" in window && (window as any).webkit?.messageHandlers?.app)\n (window as any).webkit.messageHandlers.app.postMessage(message)\n\n if (\"puzzmoMessageString\" in window)\n (window as any).puzzmoMessageString(JSON.stringify(message))\n\n if (\"ReactNativeWebView\" in window && (window as any).ReactNativeWebView?.postMessage)\n (window as any).ReactNativeWebView.postMessage(JSON.stringify(message))\n\n if (type !== \"TIMER_TICK\" && type !== \"TIMER_SYNC\")\n console.log(\"[Puzzmo SDK] sent:\", type, json)\n }\n\n const onMessage = <T extends keyof SupportedIncomingMessages>(type: T, handler: MessageHandler<T>) => {\n if (!messageHandlers.has(type))\n messageHandlers.set(type, new Set())\n messageHandlers.get(type)!.add(handler)\n\n return () => {\n messageHandlers.get(type)?.delete(handler)\n }\n }\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"message\", (event) => {\n if (!event?.data?.type) return\n const msgType = event.data.type as string\n const handlers = messageHandlers.get(msgType)\n if (handlers) {\n const msgData = event.data.data ?? event.data.json ?? {}\n if (msgType !== \"TIMER_TICK\" && msgType !== \"TIMER_SYNC\")\n console.log(\"[Puzzmo SDK] received:\", msgType, msgData)\n handlers.forEach((handler) => handler(msgData))\n }\n })\n }\n\n return { sendMessage, onMessage }\n}\n\nconst hostAPI = createHostAPI()\n\n/** Creates a Puzzmo SDK instance for communicating with the Puzzmo host */\nexport const createPuzzmoSDK = (options: PuzzmoSDKOptions = {}) => {\n let readyData: MessagesReceived[\"READY_DATA\"] | null = null\n let readyDataResolve: ((data: MessagesReceived[\"READY_DATA\"]) => void) | null = null\n\n const getGameplay = () => readyData?.startOrFindGameplay?.gamePlayed\n const getGameplayID = () => getGameplay()?.id ?? null\n const getPuzzleString = () => getGameplay()?.puzzle.puzzle ?? null\n const getBoardState = () => getGameplay()?.boardState ?? null\n const getTheme = () => readyData?.theme ?? null\n const getCompleted = () => getGameplay()?.completed ?? false\n\n const eventListeners = new Map<SDKEventType, Set<(data?: any) => void>>()\n\n const internalTimer = createTimer()\n let timerTickInterval: ReturnType<typeof setInterval> | null = null\n let timerSyncInterval: ReturnType<typeof setInterval> | null = null\n\n const startTimerIntervals = () => {\n if (timerTickInterval) return\n\n timerTickInterval = setInterval(() => {\n if (internalTimer.isPaused()) return\n const [elapsed, added] = internalTimer.display()\n hostAPI.sendMessage(\"TIMER_TICK\", { display: [elapsed, added] })\n }, 500)\n\n timerSyncInterval = setInterval(() => {\n if (internalTimer.isPaused()) return\n hostAPI.sendMessage(\"TIMER_SYNC\", Math.floor(internalTimer.timeWithoutPenaltySecs()))\n }, 10000)\n }\n\n const stopTimerIntervals = () => {\n if (timerTickInterval) {\n clearInterval(timerTickInterval)\n timerTickInterval = null\n }\n if (timerSyncInterval) {\n clearInterval(timerSyncInterval)\n timerSyncInterval = null\n }\n }\n\n const emit = <T extends SDKEventType>(event: T, data?: SDKEventMap[T]) => {\n const listeners = eventListeners.get(event)\n if (listeners) listeners.forEach((listener) => listener(data))\n }\n\n hostAPI.onMessage(\"START_GAME\", () => {\n internalTimer._init()\n startTimerIntervals()\n emit(\"start\")\n })\n\n hostAPI.onMessage(\"PAUSE_GAME\", () => {\n internalTimer._pause()\n emit(\"pause\")\n })\n\n hostAPI.onMessage(\"RESUME_GAME\", () => {\n internalTimer._resume()\n emit(\"resume\")\n })\n\n hostAPI.onMessage(\"SETTINGS_UPDATE\", (data) => emit(\"settingsUpdate\", data))\n\n hostAPI.onMessage(\"RETRY_PUZZLE\", () => {\n internalTimer._reset()\n stopTimerIntervals()\n emit(\"retry\")\n })\n\n hostAPI.onMessage(\"READY_DATA\", (data) => {\n const bootstrapData = data as MessagesReceived[\"READY_DATA\"]\n readyData = bootstrapData\n\n const gamePlayed = bootstrapData.startOrFindGameplay?.gamePlayed\n if (gamePlayed) {\n const existingTime = (gamePlayed.elapsedTimeSecs ?? 0) * 1000\n const existingAddedTime = (gamePlayed.additionalTimeAddedSecs ?? 0) * 1000\n internalTimer._reset(existingTime, existingAddedTime)\n }\n\n if (readyDataResolve) {\n readyDataResolve(bootstrapData)\n readyDataResolve = null\n }\n })\n\n const timer: SDKTimer = {\n timeMs: () => internalTimer.timeMs(),\n timeSecs: () => internalTimer.timeSecs(),\n addedTimeMs: () => internalTimer.addedTimeMs(),\n addedTimeSecs: () => internalTimer.addedTimeSecs(),\n timeWithoutPenaltySecs: () => internalTimer.timeWithoutPenaltySecs(),\n display: () => internalTimer.display(),\n addPenalty: (ms: number) => internalTimer.addPenalty(ms),\n isPaused: () => internalTimer.isPaused(),\n isRunning: () => internalTimer.isRunning(),\n }\n\n return {\n timer,\n\n gameReady: async (): Promise<{\n puzzleString: string\n boardState: string | null\n theme: Theme | null\n completed: boolean\n readyData: MessagesReceived[\"READY_DATA\"] | null\n }> => {\n hostAPI.sendMessage(\"READY\", {})\n\n if (getPuzzleString()) {\n return {\n puzzleString: getPuzzleString()!,\n boardState: getBoardState(),\n theme: getTheme(),\n completed: getCompleted(),\n readyData,\n }\n }\n\n const timeout = options.timeout ?? 5000\n const readyDataPromise = new Promise<MessagesReceived[\"READY_DATA\"]>((resolve, reject) => {\n readyDataResolve = resolve\n setTimeout(() => {\n if (readyDataResolve) {\n readyDataResolve = null\n reject(new Error(`Timeout waiting for READY_DATA after ${timeout}ms`))\n }\n }, timeout)\n })\n\n await readyDataPromise\n\n const puzzleString = getPuzzleString()\n if (!puzzleString) throw new Error(\"READY_DATA received but no puzzle data found\")\n\n return {\n puzzleString,\n boardState: getBoardState(),\n theme: getTheme(),\n completed: getCompleted(),\n readyData,\n }\n },\n\n gameLoaded: (state: any = {}) => {\n hostAPI.sendMessage(\"READY_GAME_LOADED\", {\n state,\n gameRuntimeContract: \"1.0\",\n embedRuntimeContract: \"1.0\",\n })\n },\n\n on: <T extends SDKEventType>(event: T, listener: (data?: SDKEventMap[T]) => void): (() => void) => {\n if (!eventListeners.has(event))\n eventListeners.set(event, new Set())\n eventListeners.get(event)!.add(listener)\n return () => {\n eventListeners.get(event)?.delete(listener)\n }\n },\n\n off: <T extends SDKEventType>(event: T, listener: (data?: SDKEventMap[T]) => void) => {\n eventListeners.get(event)?.delete(listener)\n },\n\n updateGameState: (inputString: string, play?: Partial<GamePlay>) => {\n const gameplayID = getGameplayID()\n if (!gameplayID) return\n\n hostAPI.sendMessage(\"UPLOAD_NEW_GAME_STATE\", {\n id: gameplayID,\n input: {\n boardState: inputString,\n elapsedTimeSecs: play?.elapsedTimeSecs ?? internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: play?.additionalTimeAddedSecs ?? internalTimer.addedTimeSecs(),\n hintsUsed: play?.hintsUsed,\n resetsUsed: play?.resetsUsed,\n metric1: 0,\n metric2: 0,\n metric3: 0,\n metric4: 0,\n metricStrings: [],\n collabUserReferences: [],\n },\n })\n },\n\n gameCompleted: (play: Partial<GamePlay>, config?: AugmentationConfig) => {\n internalTimer._conclude()\n stopTimerIntervals()\n\n const finalPlay: Partial<GamePlay> = {\n ...play,\n elapsedTimeSecs: play.elapsedTimeSecs ?? internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: play.additionalTimeAddedSecs ?? internalTimer.addedTimeSecs(),\n }\n\n const deeds: Deed[] = (config?.deeds as any) ?? []\n deeds.push({ id: \"points\", value: play.pointsAwarded })\n deeds.push({\n id: \"time\",\n value: Math.round(finalPlay.elapsedTimeSecs ?? 0) + Math.round(finalPlay.additionalTimeAddedSecs ?? 0),\n })\n\n const gameplayID = getGameplayID()\n if (gameplayID) {\n hostAPI.sendMessage(\"GAME_COMPLETED\", {\n id: gameplayID,\n input: finalPlay,\n config,\n })\n }\n },\n\n showCompletionScreen: (results: any[], gameplay: GamePlay, showRetry = true) => {\n hostAPI.sendMessage(\"SHOW_GAME_COMPLETE_SCREEN\", {\n results,\n showRetry,\n gameplay,\n })\n },\n\n hitCheckpoint: (checkpointName: string, checkpointConfig: CheckpointConfig, config?: AugmentationConfig) => {\n const gameplayID = getGameplayID()\n if (!gameplayID) return\n\n const inputStr = getBoardState() ?? \"\"\n const play: Partial<GamePlay> = {\n elapsedTimeSecs: internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: internalTimer.addedTimeSecs(),\n }\n\n hostAPI.sendMessage(\"HIT_CHECKPOINT\", {\n checkpointName,\n gameplay: { inputStr, play },\n checkpointConfig,\n augConfig: config ?? {},\n })\n },\n\n _hostAPI: hostAPI,\n }\n}\n","/** Severity level for validation issues */\nexport type ValidationLevel = \"error\" | \"warning\" | \"info\"\n\n/** Represents a single validation issue found during puzzle validation */\nexport interface ValidationIssue {\n level: ValidationLevel\n message: string\n line?: number\n col?: number\n length?: number\n}\n\n/** Complete validation report for a puzzle */\nexport interface ValidationReport {\n success: boolean\n issues: ValidationIssue[]\n}\n\nexport type ImportErrorType = \"invalid_format\" | \"parsing_error\" | \"unknown\"\n\n/** Custom error class for workshop import failures */\nexport class WorkshopImportError extends Error {\n constructor(\n public type: ImportErrorType,\n message: string,\n public originalError?: unknown,\n ) {\n super(message)\n this.name = \"WorkshopImportError\"\n }\n}\n\n/** Result of a successful puzzle import operation */\nexport interface ImportResult {\n data: string\n warnings?: ValidationIssue[]\n title?: string\n authors?: string[]\n editors?: string[]\n}\n\n/** Main interface for a Workshop bundle */\nexport interface WorkshopBundle {\n validator: {\n validate(data: string): Promise<ValidationReport> | ValidationReport\n }\n importer?: {\n onImport(filename: string, contents: string | ArrayBuffer): Promise<ImportResult> | ImportResult\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,15 +1,5 @@
1
- import { GameConfig } from "./puzzmoSDK";
2
- export type * from "./puzzmoSDK";
1
+ export { createPuzzmoSDK } from "./sdk";
2
+ export type { SDK as PuzzmoSDK, PuzzmoSDKOptions, SDKEventMap, SDKEventType, SDKTimer } from "./sdk";
3
+ export type { Theme, GamePlay, AugmentationConfig, CheckpointConfig, Deed, GameOverMessageUIComponent, BootstrapGameData, MessagesReceived, MessagesSentFromEmbed, ThumbnailConfig, ThumbnailFunction, } from "./types";
3
4
  export * from "./workshop";
4
- /**
5
- * Creates a Puzzmo SDK instance
6
- *
7
- * @param config - Configuration options
8
- *
9
- * @returns A Puzzmo SDK instance
10
- */
11
- export declare function createPuzzmoSDK(config: GameConfig): {
12
- hello: () => void;
13
- };
14
- export type PuzzmoSDK = ReturnType<typeof createPuzzmoSDK>;
15
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,mBAAmB,aAAa,CAAC;AAGjC,cAAc,YAAY,CAAC;AAE3B;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU;;EAMjD;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AACvC,YAAY,EAAE,GAAG,IAAI,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACpG,YAAY,EACV,KAAK,EACL,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,0BAA0B,EAC1B,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,GAClB,MAAM,SAAS,CAAA;AAChB,cAAc,YAAY,CAAA"}