synabun 1.3.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 (187) hide show
  1. package/CHANGELOG.md +665 -0
  2. package/LICENSE +191 -0
  3. package/README.md +853 -0
  4. package/hooks/claude-code/post-plan.mjs +391 -0
  5. package/hooks/claude-code/post-remember.mjs +239 -0
  6. package/hooks/claude-code/pre-compact.mjs +241 -0
  7. package/hooks/claude-code/pre-websearch.mjs +90 -0
  8. package/hooks/claude-code/prompt-submit.mjs +861 -0
  9. package/hooks/claude-code/session-start.mjs +494 -0
  10. package/hooks/claude-code/shared.mjs +368 -0
  11. package/hooks/claude-code/stop.mjs +646 -0
  12. package/hooks/claude-code/test-user-learning.mjs +531 -0
  13. package/mcp-server/package.json +38 -0
  14. package/mcp-server/run.mjs +10 -0
  15. package/mcp-server/src/config.ts +70 -0
  16. package/mcp-server/src/http.ts +93 -0
  17. package/mcp-server/src/index.ts +203 -0
  18. package/mcp-server/src/preload-http.ts +25 -0
  19. package/mcp-server/src/preload.ts +29 -0
  20. package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
  21. package/mcp-server/src/services/categories.ts +429 -0
  22. package/mcp-server/src/services/discord.ts +702 -0
  23. package/mcp-server/src/services/file-checksums.ts +33 -0
  24. package/mcp-server/src/services/local-embeddings.ts +77 -0
  25. package/mcp-server/src/services/neural-interface.ts +344 -0
  26. package/mcp-server/src/services/sqlite.ts +823 -0
  27. package/mcp-server/src/tools/browser-advanced.ts +123 -0
  28. package/mcp-server/src/tools/browser-interact.ts +255 -0
  29. package/mcp-server/src/tools/browser-navigate.ts +77 -0
  30. package/mcp-server/src/tools/browser-observe.ts +1330 -0
  31. package/mcp-server/src/tools/browser.ts +107 -0
  32. package/mcp-server/src/tools/card-tools.ts +193 -0
  33. package/mcp-server/src/tools/card.ts +22 -0
  34. package/mcp-server/src/tools/category.ts +379 -0
  35. package/mcp-server/src/tools/discord-channel.ts +274 -0
  36. package/mcp-server/src/tools/discord-guild.ts +188 -0
  37. package/mcp-server/src/tools/discord-member.ts +158 -0
  38. package/mcp-server/src/tools/discord-message.ts +231 -0
  39. package/mcp-server/src/tools/discord-onboarding.ts +228 -0
  40. package/mcp-server/src/tools/discord-role.ts +209 -0
  41. package/mcp-server/src/tools/discord-thread.ts +110 -0
  42. package/mcp-server/src/tools/discord-webhook.ts +176 -0
  43. package/mcp-server/src/tools/discord.ts +40 -0
  44. package/mcp-server/src/tools/forget.ts +38 -0
  45. package/mcp-server/src/tools/git-tools.ts +141 -0
  46. package/mcp-server/src/tools/git.ts +13 -0
  47. package/mcp-server/src/tools/image-tools.ts +98 -0
  48. package/mcp-server/src/tools/image.ts +17 -0
  49. package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
  50. package/mcp-server/src/tools/leonardo.ts +28 -0
  51. package/mcp-server/src/tools/loop.ts +348 -0
  52. package/mcp-server/src/tools/memories.ts +118 -0
  53. package/mcp-server/src/tools/recall.ts +323 -0
  54. package/mcp-server/src/tools/reflect.ts +145 -0
  55. package/mcp-server/src/tools/remember.ts +115 -0
  56. package/mcp-server/src/tools/response.ts +26 -0
  57. package/mcp-server/src/tools/restore.ts +34 -0
  58. package/mcp-server/src/tools/sync.ts +114 -0
  59. package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
  60. package/mcp-server/src/tools/tictactoe.ts +13 -0
  61. package/mcp-server/src/tools/utils.ts +25 -0
  62. package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
  63. package/mcp-server/src/tools/whiteboard.ts +22 -0
  64. package/mcp-server/src/tui.ts +940 -0
  65. package/mcp-server/src/types.ts +85 -0
  66. package/mcp-server/tsconfig.json +19 -0
  67. package/neural-interface/package.json +27 -0
  68. package/neural-interface/public/apple-touch-icon.png +0 -0
  69. package/neural-interface/public/category-logos/claude.svg +1 -0
  70. package/neural-interface/public/category-logos/gemini-color.svg +1 -0
  71. package/neural-interface/public/category-logos/openai.svg +1 -0
  72. package/neural-interface/public/category-logos/synabun.png +0 -0
  73. package/neural-interface/public/claude-chat.css +878 -0
  74. package/neural-interface/public/claude-chat.html +92 -0
  75. package/neural-interface/public/claude-chat.js +1168 -0
  76. package/neural-interface/public/favicon-16x16.png +0 -0
  77. package/neural-interface/public/favicon-32x32.png +0 -0
  78. package/neural-interface/public/gallery.svg +6 -0
  79. package/neural-interface/public/icon-192.png +0 -0
  80. package/neural-interface/public/icon-512.png +0 -0
  81. package/neural-interface/public/index-legacy.html +14677 -0
  82. package/neural-interface/public/index.html +212 -0
  83. package/neural-interface/public/index2d-legacy.html +8193 -0
  84. package/neural-interface/public/index2d.html +127 -0
  85. package/neural-interface/public/invite.html +351 -0
  86. package/neural-interface/public/leonardoai.svg +1 -0
  87. package/neural-interface/public/logo-synabun.png +0 -0
  88. package/neural-interface/public/logoHD.png +0 -0
  89. package/neural-interface/public/manifest.json +14 -0
  90. package/neural-interface/public/offline.html +299 -0
  91. package/neural-interface/public/onboarding.html +2335 -0
  92. package/neural-interface/public/openclaw-logo-text.png +0 -0
  93. package/neural-interface/public/pin-svgrepo-com.svg +25 -0
  94. package/neural-interface/public/sessionicon.svg +8 -0
  95. package/neural-interface/public/shared/ansi-parser.js +305 -0
  96. package/neural-interface/public/shared/api.js +1043 -0
  97. package/neural-interface/public/shared/colors.js +153 -0
  98. package/neural-interface/public/shared/constants.js +141 -0
  99. package/neural-interface/public/shared/html-shell.js +777 -0
  100. package/neural-interface/public/shared/html-term-renderer.js +1022 -0
  101. package/neural-interface/public/shared/i18n.js +147 -0
  102. package/neural-interface/public/shared/registry.js +82 -0
  103. package/neural-interface/public/shared/state.js +86 -0
  104. package/neural-interface/public/shared/storage.js +150 -0
  105. package/neural-interface/public/shared/styles.css +21212 -0
  106. package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
  107. package/neural-interface/public/shared/ui-bookmarks.js +231 -0
  108. package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
  109. package/neural-interface/public/shared/ui-command-runner.js +534 -0
  110. package/neural-interface/public/shared/ui-cost-widget.js +602 -0
  111. package/neural-interface/public/shared/ui-detail.js +1454 -0
  112. package/neural-interface/public/shared/ui-explorer.js +1356 -0
  113. package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
  114. package/neural-interface/public/shared/ui-games.js +411 -0
  115. package/neural-interface/public/shared/ui-help.js +107 -0
  116. package/neural-interface/public/shared/ui-image-gallery.js +461 -0
  117. package/neural-interface/public/shared/ui-invite.js +519 -0
  118. package/neural-interface/public/shared/ui-keybinds.js +452 -0
  119. package/neural-interface/public/shared/ui-layouts.js +297 -0
  120. package/neural-interface/public/shared/ui-link.js +731 -0
  121. package/neural-interface/public/shared/ui-loading.js +255 -0
  122. package/neural-interface/public/shared/ui-menubar.js +499 -0
  123. package/neural-interface/public/shared/ui-multiselect.js +93 -0
  124. package/neural-interface/public/shared/ui-navbar.js +222 -0
  125. package/neural-interface/public/shared/ui-notifications.js +287 -0
  126. package/neural-interface/public/shared/ui-panels.js +309 -0
  127. package/neural-interface/public/shared/ui-resume.js +653 -0
  128. package/neural-interface/public/shared/ui-search.js +145 -0
  129. package/neural-interface/public/shared/ui-sessions.js +531 -0
  130. package/neural-interface/public/shared/ui-settings.js +5987 -0
  131. package/neural-interface/public/shared/ui-sidebar.js +1905 -0
  132. package/neural-interface/public/shared/ui-skills.js +2805 -0
  133. package/neural-interface/public/shared/ui-stats.js +38 -0
  134. package/neural-interface/public/shared/ui-sync.js +201 -0
  135. package/neural-interface/public/shared/ui-terminal.js +6385 -0
  136. package/neural-interface/public/shared/ui-tooltip.js +123 -0
  137. package/neural-interface/public/shared/ui-trash.js +439 -0
  138. package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
  139. package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
  140. package/neural-interface/public/shared/ui-tutorial.js +1117 -0
  141. package/neural-interface/public/shared/ui-update.js +244 -0
  142. package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
  143. package/neural-interface/public/shared/ui-workspaces.js +722 -0
  144. package/neural-interface/public/shared/utils.js +360 -0
  145. package/neural-interface/public/shared/vterm-buffer.js +1102 -0
  146. package/neural-interface/public/sw.js +52 -0
  147. package/neural-interface/public/synabun.png +0 -0
  148. package/neural-interface/public/toolbar-album.svg +8 -0
  149. package/neural-interface/public/toolbar-question.svg +6 -0
  150. package/neural-interface/public/toolbar-sessions.svg +6 -0
  151. package/neural-interface/public/variant/2d/background.js +113 -0
  152. package/neural-interface/public/variant/2d/context-menu.js +133 -0
  153. package/neural-interface/public/variant/2d/gfx.js +45 -0
  154. package/neural-interface/public/variant/2d/graph.js +1635 -0
  155. package/neural-interface/public/variant/2d/hulls.js +207 -0
  156. package/neural-interface/public/variant/2d/lasso.js +135 -0
  157. package/neural-interface/public/variant/2d/layout.js +594 -0
  158. package/neural-interface/public/variant/2d/main.js +521 -0
  159. package/neural-interface/public/variant/2d/minimap.js +312 -0
  160. package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
  161. package/neural-interface/public/variant/3d/background.js +884 -0
  162. package/neural-interface/public/variant/3d/camera.js +661 -0
  163. package/neural-interface/public/variant/3d/gfx.js +116 -0
  164. package/neural-interface/public/variant/3d/graph.js +2399 -0
  165. package/neural-interface/public/variant/3d/main.js +500 -0
  166. package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
  167. package/neural-interface/scripts/rebuild-pty.js +100 -0
  168. package/neural-interface/server.js +15000 -0
  169. package/neural-interface/templates/CLAUDE-template.md +211 -0
  170. package/package.json +53 -0
  171. package/postinstall.js +135 -0
  172. package/setup.js +260 -0
  173. package/skills/leonardo/SKILL.md +159 -0
  174. package/skills/leonardo/icon.svg +1 -0
  175. package/skills/leonardo/modules/browser-guide.md +378 -0
  176. package/skills/leonardo/modules/image-prompter.md +277 -0
  177. package/skills/leonardo/modules/model-advisor.md +100 -0
  178. package/skills/leonardo/modules/prompt-library.md +110 -0
  179. package/skills/leonardo/modules/style-guide.md +186 -0
  180. package/skills/leonardo/modules/video-prompter.md +311 -0
  181. package/skills/synabun/SKILL.md +185 -0
  182. package/skills/synabun/icon.png +0 -0
  183. package/skills/synabun/modules/audit.md +374 -0
  184. package/skills/synabun/modules/changelog.md +194 -0
  185. package/skills/synabun/modules/idea.md +144 -0
  186. package/skills/synabun/modules/memorize.md +188 -0
  187. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,411 @@
1
+ // ═══════════════════════════════════════════
2
+ // UI-GAMES — Whiteboard Games (Tic Tac Toe)
3
+ // ═══════════════════════════════════════════
4
+
5
+ import { emit, on } from './state.js';
6
+ import { loadIfaceConfig, applyIfaceConfig, saveIfaceConfig } from './ui-settings.js';
7
+ import { storage } from './storage.js';
8
+ import { KEYS } from './constants.js';
9
+
10
+ const $ = (id) => document.getElementById(id);
11
+
12
+ // ── TicTacToe constants (only used for UI positioning) ──
13
+ const TTT_BOARD_SIZE = 500;
14
+
15
+ // ── Game prompts (dynamic based on dice roll) ──
16
+ function buildGamePrompt(humanFirst, opponentLabel) {
17
+ const youPiece = humanFirst ? 'O' : 'X';
18
+ const themPiece = humanFirst ? 'X' : 'O';
19
+
20
+ return `Tic Tac Toe. You are ${youPiece}. Use tictactoe tool (action "move", cell 1-9) for all moves — yours AND the human's. ${humanFirst ? 'Human goes first. Wait for their cell number.' : 'You go first. Pick a cell now.'}
21
+
22
+ Each turn: place the human's ${themPiece} at their cell, then place your ${youPiece}. Keep responses short.`;
23
+ }
24
+
25
+ // ── Dice face dot patterns (grid positions: TL=0, TC=1, TR=2, ML=3, MC=4, MR=5, BL=6, BC=7, BR=8) ──
26
+ const DICE_FACES = {
27
+ 1: [4], // center
28
+ 2: [2, 6], // TR, BL
29
+ 3: [2, 4, 6], // TR, MC, BL
30
+ 4: [0, 2, 6, 8], // four corners
31
+ 5: [0, 2, 4, 6, 8], // four corners + center
32
+ 6: [0, 3, 6, 2, 5, 8], // left col + right col
33
+ };
34
+
35
+ function createDieElement() {
36
+ const die = document.createElement('div');
37
+ die.className = 'ttt-die';
38
+ for (let i = 0; i < 9; i++) {
39
+ const dot = document.createElement('div');
40
+ dot.className = 'ttt-dot';
41
+ dot.dataset.pos = i;
42
+ die.appendChild(dot);
43
+ }
44
+ return die;
45
+ }
46
+
47
+ function setDieFace(die, face) {
48
+ const dots = die.querySelectorAll('.ttt-dot');
49
+ const active = DICE_FACES[face] || [];
50
+ dots.forEach((dot, i) => {
51
+ dot.classList.toggle('visible', active.includes(i));
52
+ });
53
+ die.dataset.face = face;
54
+ }
55
+
56
+ // ── Dice roll overlay ──
57
+ function showDiceRoll(opponentLabel) {
58
+ return new Promise((resolve) => {
59
+ const wbRoot = $('wb-root');
60
+ if (!wbRoot) return resolve({ humanFirst: true });
61
+
62
+ // Move logo up so dice don't overlap it
63
+ const rect = wbRoot.getBoundingClientRect();
64
+ const diceAreaTop = Math.round(rect.height / 2) - 80; // dice center minus half their height
65
+ positionLogoAboveBoard(diceAreaTop);
66
+
67
+ const overlay = document.createElement('div');
68
+ overlay.id = 'ttt-dice-overlay';
69
+
70
+ const row = document.createElement('div');
71
+ row.className = 'ttt-dice-row';
72
+
73
+ // Human die
74
+ const humanPlayer = document.createElement('div');
75
+ humanPlayer.className = 'ttt-dice-player';
76
+ const humanDie = createDieElement();
77
+ const humanLabel = document.createElement('div');
78
+ humanLabel.className = 'ttt-dice-label';
79
+ humanLabel.textContent = 'YOU';
80
+ humanPlayer.appendChild(humanDie);
81
+ humanPlayer.appendChild(humanLabel);
82
+
83
+ // VS text
84
+ const vs = document.createElement('div');
85
+ vs.className = 'ttt-dice-vs';
86
+ vs.textContent = 'VS';
87
+
88
+ // AI die
89
+ const aiPlayer = document.createElement('div');
90
+ aiPlayer.className = 'ttt-dice-player';
91
+ const aiDie = createDieElement();
92
+ const aiLabel = document.createElement('div');
93
+ aiLabel.className = 'ttt-dice-label';
94
+ aiLabel.textContent = opponentLabel.toUpperCase();
95
+ aiPlayer.appendChild(aiDie);
96
+ aiPlayer.appendChild(aiLabel);
97
+
98
+ row.appendChild(humanPlayer);
99
+ row.appendChild(vs);
100
+ row.appendChild(aiPlayer);
101
+
102
+ // Result text (hidden initially)
103
+ const resultEl = document.createElement('div');
104
+ resultEl.className = 'ttt-dice-result';
105
+
106
+ overlay.appendChild(row);
107
+ overlay.appendChild(resultEl);
108
+ wbRoot.appendChild(overlay);
109
+
110
+ // Generate final values (ensure no tie)
111
+ let humanRoll, aiRoll;
112
+ do {
113
+ humanRoll = Math.floor(Math.random() * 6) + 1;
114
+ aiRoll = Math.floor(Math.random() * 6) + 1;
115
+ } while (humanRoll === aiRoll);
116
+
117
+ const humanFirst = humanRoll > aiRoll;
118
+
119
+ // Start rolling animation
120
+ setDieFace(humanDie, 1);
121
+ setDieFace(aiDie, 1);
122
+ humanDie.classList.add('rolling');
123
+ aiDie.classList.add('rolling');
124
+
125
+ const rollInterval = setInterval(() => {
126
+ setDieFace(humanDie, Math.floor(Math.random() * 6) + 1);
127
+ setDieFace(aiDie, Math.floor(Math.random() * 6) + 1);
128
+ }, 80);
129
+
130
+ // Settle after 1.5s
131
+ setTimeout(() => {
132
+ clearInterval(rollInterval);
133
+ humanDie.classList.remove('rolling');
134
+ aiDie.classList.remove('rolling');
135
+
136
+ setDieFace(humanDie, humanRoll);
137
+ setDieFace(aiDie, aiRoll);
138
+
139
+ humanDie.classList.add('settled');
140
+ aiDie.classList.add('settled');
141
+
142
+ // Show result text
143
+ setTimeout(() => {
144
+ resultEl.textContent = humanFirst
145
+ ? 'You go first!'
146
+ : `${opponentLabel} goes first!`;
147
+ resultEl.classList.add('show');
148
+ }, 400);
149
+
150
+ // Fade out and resolve after 1.8s
151
+ setTimeout(() => {
152
+ overlay.classList.add('fade-out');
153
+ setTimeout(() => {
154
+ overlay.remove();
155
+ resolve({ humanFirst, humanRoll, aiRoll });
156
+ }, 500);
157
+ }, 1800);
158
+ }, 1500);
159
+ });
160
+ }
161
+
162
+ // ── Position logo above a board at given boardY ──
163
+ function positionLogoAboveBoard(boardY) {
164
+ const logo = document.querySelector('.static-bg-logo');
165
+ if (!logo) return;
166
+ const wbRoot = $('wb-root');
167
+ if (!wbRoot) return;
168
+ const rect = wbRoot.getBoundingClientRect();
169
+ const logoAboveBoardPx = rect.top + boardY - 20;
170
+ logo.classList.add('game-active');
171
+ logo.style.top = logoAboveBoardPx + 'px';
172
+ logo.style.transform = 'translate(-50%, -100%)';
173
+ }
174
+
175
+ // ── Reset logo to default center position ──
176
+ function resetLogoPosition() {
177
+ const logo = document.querySelector('.static-bg-logo');
178
+ if (!logo) return;
179
+ logo.classList.remove('game-active');
180
+ logo.style.top = '';
181
+ logo.style.transform = '';
182
+ }
183
+
184
+ // ── Check if a TicTacToe game is active in persisted whiteboard state ──
185
+ function detectActiveGame() {
186
+ try {
187
+ const raw = storage.getItem(KEYS.WHITEBOARD);
188
+ if (!raw) return null;
189
+ const data = JSON.parse(raw);
190
+ if (!Array.isArray(data.elements)) return null;
191
+ const board = data.elements.find(el => el.id === 'ttt-board');
192
+ if (!board) return null;
193
+ return { boardX: board.x, boardY: board.y };
194
+ } catch { return null; }
195
+ }
196
+
197
+ // ── SVG icons for opponent picker ──
198
+ const ICON_CLAUDE = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"/></svg>';
199
+ const ICON_OPENAI = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"/></svg>';
200
+ const ICON_GEMINI = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"/></svg>';
201
+
202
+ const OPPONENTS = [
203
+ { id: 'claude-code', icon: ICON_CLAUDE, label: 'Claude' },
204
+ { id: 'codex', icon: ICON_OPENAI, label: 'OpenAI' },
205
+ { id: 'gemini', icon: ICON_GEMINI, label: 'Gemini' },
206
+ ];
207
+
208
+ // ── Opponent picker overlay ──
209
+ let _pickerEl = null;
210
+
211
+ function showOpponentPicker() {
212
+ removeOpponentPicker();
213
+
214
+ const picker = document.createElement('div');
215
+ picker.id = 'ttt-opponent-picker';
216
+ picker.innerHTML = `
217
+ <div class="ttt-picker-title">CHOOSE YOUR OPPONENT</div>
218
+ <div class="ttt-picker-options">
219
+ ${OPPONENTS.map(o => `
220
+ <button class="ttt-picker-btn" data-profile="${o.id}" data-label="${o.label}" title="${o.label}">
221
+ <span class="ttt-picker-icon">${o.icon}</span>
222
+ <span class="ttt-picker-label">${o.label}</span>
223
+ </button>
224
+ `).join('')}
225
+ </div>
226
+ `;
227
+
228
+ const wbRoot = $('wb-root');
229
+ if (!wbRoot) return;
230
+ wbRoot.appendChild(picker);
231
+
232
+ // Move logo up so picker doesn't overlap it
233
+ const rect = wbRoot.getBoundingClientRect();
234
+ const pickerAreaTop = Math.round(rect.height / 2) - 40;
235
+ positionLogoAboveBoard(pickerAreaTop);
236
+
237
+ // Center in whiteboard, nudged slightly below center
238
+ picker.style.position = 'absolute';
239
+ picker.style.left = '50%';
240
+ picker.style.top = 'calc(50% + 20px)';
241
+ picker.style.transform = 'translate(-50%, -50%)';
242
+ picker.style.zIndex = '999';
243
+ picker.style.pointerEvents = 'auto';
244
+
245
+ // Wire click handlers — each button triggers dice roll → game start
246
+ picker.querySelectorAll('.ttt-picker-btn').forEach(btn => {
247
+ btn.addEventListener('click', async () => {
248
+ const profile = btn.dataset.profile;
249
+ const label = btn.dataset.label;
250
+ removeOpponentPicker();
251
+
252
+ // Dice roll to determine who goes first
253
+ const { humanFirst } = await showDiceRoll(label);
254
+
255
+ // Start the game — human is X if they go first, AI is X otherwise
256
+ await startGameAfterDice(profile, label, humanFirst);
257
+ });
258
+ });
259
+
260
+ _pickerEl = picker;
261
+ }
262
+
263
+ function removeOpponentPicker() {
264
+ if (_pickerEl) {
265
+ _pickerEl.remove();
266
+ _pickerEl = null;
267
+ }
268
+ }
269
+
270
+ // ── Start game after dice roll ──
271
+ async function startGameAfterDice(profile, opponentLabel, humanFirst) {
272
+ const wbRoot = $('wb-root');
273
+ if (!wbRoot) return;
274
+
275
+ // If human goes first, human is X. If AI goes first, AI is X.
276
+ // The `piece` param in /start represents which piece X is assigned to.
277
+ // X always goes first. We just control the prompt to assign roles.
278
+ const resp = await fetch('/api/games/tictactoe/start', {
279
+ method: 'POST',
280
+ headers: { 'Content-Type': 'application/json' },
281
+ body: JSON.stringify({ piece: 'X' }),
282
+ });
283
+
284
+ if (!resp.ok) {
285
+ console.error('[games] Failed to start TicTacToe:', await resp.text());
286
+ return;
287
+ }
288
+
289
+ const rect = wbRoot.getBoundingClientRect();
290
+ const boardX = Math.round((rect.width - TTT_BOARD_SIZE) / 2);
291
+ const boardY = Math.round((rect.height - TTT_BOARD_SIZE) / 2);
292
+
293
+ positionLogoAboveBoard(boardY);
294
+ showGameHud(boardX, boardY);
295
+
296
+ // Launch terminal with dynamic prompt based on dice result
297
+ const prompt = buildGamePrompt(humanFirst, opponentLabel);
298
+ emit('terminal:launch-floating', {
299
+ profile,
300
+ initialMessage: prompt,
301
+ });
302
+ }
303
+
304
+ // ── Game HUD overlay (close button + arrow annotation) ──
305
+ let _hudEl = null;
306
+
307
+ function showGameHud(boardX, boardY) {
308
+ removeGameHud();
309
+
310
+ const hud = document.createElement('div');
311
+ hud.id = 'ttt-hud';
312
+ hud.innerHTML = `
313
+ <button class="ttt-close-btn" title="Close game">&times;</button>
314
+ <svg class="ttt-arrow-annotation" viewBox="0 0 90 60" fill="none">
315
+ <path d="M82,5 C58,4 24,18 15,36"
316
+ stroke="rgba(255,255,255,0.18)" stroke-width="1.5" stroke-linecap="round" fill="none"/>
317
+ <polygon points="11,54 5,37 20,39" fill="rgba(255,255,255,0.18)"/>
318
+ </svg>
319
+ <span class="ttt-arrow-text">Do not Close.... EVER.</span>
320
+ `;
321
+
322
+ const wbRoot = $('wb-root');
323
+ if (!wbRoot) return;
324
+ wbRoot.appendChild(hud);
325
+
326
+ // Position at top-right corner of the board
327
+ hud.style.position = 'absolute';
328
+ hud.style.left = (boardX + TTT_BOARD_SIZE + 16) + 'px';
329
+ hud.style.top = (boardY - 8) + 'px';
330
+ hud.style.zIndex = '999';
331
+ hud.style.pointerEvents = 'auto';
332
+
333
+ hud.querySelector('.ttt-close-btn').addEventListener('click', teardownTicTacToe);
334
+
335
+ _hudEl = hud;
336
+ }
337
+
338
+ function removeGameHud() {
339
+ if (_hudEl) {
340
+ _hudEl.remove();
341
+ _hudEl = null;
342
+ }
343
+ }
344
+
345
+ // ── Teardown: end game via API + reset UI ──
346
+ async function teardownTicTacToe() {
347
+ removeGameHud();
348
+ removeOpponentPicker();
349
+ resetLogoPosition();
350
+ const wbRoot = $('wb-root');
351
+ if (wbRoot) wbRoot.classList.remove('ttt-active');
352
+ await fetch('/api/games/tictactoe/end', { method: 'POST' });
353
+ }
354
+
355
+ // ── Enter focus mode (whiteboard) if not already ──
356
+ function ensureFocusMode() {
357
+ const cfg = loadIfaceConfig();
358
+ if (cfg.visualizationEnabled !== false) {
359
+ cfg.visualizationEnabled = false;
360
+ applyIfaceConfig(cfg);
361
+ saveIfaceConfig(cfg);
362
+ emit('viz:toggle', false);
363
+ }
364
+ }
365
+
366
+ // ── Setup the TicTacToe game ──
367
+ async function setupTicTacToe() {
368
+ // Enter focus mode (whiteboard)
369
+ ensureFocusMode();
370
+
371
+ // Wait a tick for focus mode transition
372
+ await new Promise(r => setTimeout(r, 200));
373
+
374
+ const wbRoot = $('wb-root');
375
+ if (!wbRoot) {
376
+ console.error('[games] Whiteboard root not found');
377
+ return;
378
+ }
379
+ wbRoot.classList.add('ttt-active');
380
+
381
+ // Show opponent picker — it handles dice roll → game start → terminal launch
382
+ showOpponentPicker();
383
+ }
384
+
385
+ // ── Clear game elements from persisted storage (call BEFORE initWhiteboard) ──
386
+ export function clearGameOnLoad() {
387
+ try {
388
+ const raw = storage.getItem(KEYS.WHITEBOARD);
389
+ if (!raw) return;
390
+ const data = JSON.parse(raw);
391
+ if (!Array.isArray(data.elements)) return;
392
+ const hadGame = data.elements.some(el => el.id && el.id.startsWith('ttt-'));
393
+ if (!hadGame) return;
394
+ data.elements = data.elements.filter(el => !el.id || !el.id.startsWith('ttt-'));
395
+ storage.setItem(KEYS.WHITEBOARD, JSON.stringify(data));
396
+ // Also clear server state
397
+ fetch('/api/games/tictactoe/end', { method: 'POST' });
398
+ } catch { /* ignore */ }
399
+ }
400
+
401
+ // ── Init ──
402
+ export function initGames() {
403
+ // Wire menu click
404
+ const tttItem = $('menu-game-tictactoe');
405
+ if (tttItem) {
406
+ tttItem.addEventListener('click', () => {
407
+ emit('panel:close-all-dropdowns');
408
+ setupTicTacToe();
409
+ });
410
+ }
411
+ }
@@ -0,0 +1,107 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Help Modal
3
+ // Shared help sections + variant section injection via registry
4
+ // ═══════════════════════════════════════════
5
+
6
+ import { getHelpSections } from './registry.js';
7
+ import { registerAction, getDisplayKey } from './ui-keybinds.js';
8
+
9
+ // Shared help sections that appear in all variants
10
+ // Keyboard section uses dynamic keys from central keybinds system
11
+ function getSharedHelpSections() {
12
+ return [
13
+ {
14
+ order: 10,
15
+ html: `<div class="help-section">
16
+ <div class="help-section-title">Keyboard</div>
17
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('focus-search')}</span></div><span class="help-desc">Focus search</span></div>
18
+ <div class="help-row"><div class="help-keys"><span class="help-key">Esc</span></div><span class="help-desc">Close panel / clear search</span></div>
19
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-categories')}</span></div><span class="help-desc">Toggle categories</span></div>
20
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-terminal')}</span></div><span class="help-desc">Toggle terminal</span></div>
21
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('open-skills')}</span></div><span class="help-desc">Open Skills Studio</span></div>
22
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-explorer')}</span></div><span class="help-desc">Toggle explorer</span></div>
23
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-file-explorer')}</span></div><span class="help-desc">Toggle file explorer</span></div>
24
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-focus-mode')}</span></div><span class="help-desc">Toggle Focus Mode</span></div>
25
+ <div class="help-row"><div class="help-keys"><span class="help-key">${getDisplayKey('toggle-help')}</span></div><span class="help-desc">Toggle this help</span></div>
26
+ <div class="help-row"><div class="help-keys"><span class="help-key">Enter</span></div><span class="help-desc">Save layout (when naming)</span></div>
27
+ </div>`
28
+ },
29
+ {
30
+ order: 20,
31
+ html: `<div class="help-section">
32
+ <div class="help-section-title">Mouse &mdash; Graph</div>
33
+ <div class="help-row"><div class="help-keys"><span class="help-key">Click</span><span class="help-key">Node</span></div><span class="help-desc">Select &amp; inspect memory</span></div>
34
+ <div class="help-row"><div class="help-keys"><span class="help-key">Drag</span><span class="help-key">Node</span></div><span class="help-desc">Move &amp; pin node in place</span></div>
35
+ <div class="help-row"><div class="help-keys"><span class="help-key">Hover</span><span class="help-key">Node</span></div><span class="help-desc">Show tooltip preview</span></div>
36
+ <div class="help-row"><div class="help-keys"><span class="help-key">Ctrl</span><span class="help-key">Click</span></div><span class="help-desc">Multi-select nodes</span></div>
37
+ <div class="help-row"><div class="help-keys"><span class="help-key">Click</span><span class="help-key">Background</span></div><span class="help-desc">Deselect / close detail panel</span></div>
38
+ </div>`
39
+ },
40
+ {
41
+ order: 80,
42
+ html: `<div class="help-section">
43
+ <div class="help-section-title">Panels</div>
44
+ <div class="help-row"><div class="help-keys"><span class="help-key">Drag</span><span class="help-key">Grip</span></div><span class="help-desc">Move panel</span></div>
45
+ <div class="help-row"><div class="help-keys"><span class="help-key">Drag</span><span class="help-key">Edge</span></div><span class="help-desc">Resize panel</span></div>
46
+ <div class="help-row"><div class="help-keys"><span class="help-key">Pin</span><span class="help-key">&#x1F4CC;</span></div><span class="help-desc">Lock panel position &amp; size</span></div>
47
+ </div>`
48
+ },
49
+ {
50
+ order: 90,
51
+ html: `<div class="help-section">
52
+ <div class="help-section-title">Sidebar</div>
53
+ <div class="help-row"><div class="help-keys"><span class="help-key">Click</span><span class="help-key">Category</span></div><span class="help-desc">Toggle category visibility</span></div>
54
+ </div>`
55
+ }
56
+ ]; }
57
+
58
+ function buildHelpContent() {
59
+ const variantSections = getHelpSections();
60
+ const allSections = [...getSharedHelpSections(), ...variantSections];
61
+ allSections.sort((a, b) => a.order - b.order);
62
+ return allSections.map(s => s.html).join('');
63
+ }
64
+
65
+ export function openHelp() {
66
+ const overlay = document.getElementById('help-overlay');
67
+ const content = document.getElementById('help-content');
68
+ if (content) content.innerHTML = buildHelpContent();
69
+ if (overlay) overlay.classList.add('open');
70
+ }
71
+
72
+ export function closeHelp() {
73
+ const overlay = document.getElementById('help-overlay');
74
+ if (overlay) overlay.classList.remove('open');
75
+ }
76
+
77
+ export function initHelp() {
78
+ const helpBtn = document.getElementById('help-btn');
79
+ const helpClose = document.getElementById('help-close');
80
+ const helpOverlay = document.getElementById('help-overlay');
81
+
82
+ if (helpBtn) helpBtn.addEventListener('click', openHelp);
83
+ if (helpClose) helpClose.addEventListener('click', closeHelp);
84
+ // Overlay click disabled — close only via ESC or close button
85
+
86
+ const helpBdToggle = document.getElementById('help-backdrop-toggle');
87
+ if (helpBdToggle) {
88
+ helpBdToggle.addEventListener('click', () => {
89
+ if (helpOverlay) {
90
+ helpOverlay.classList.toggle('backdrop-hidden');
91
+ helpBdToggle.classList.toggle('active', helpOverlay.classList.contains('backdrop-hidden'));
92
+ }
93
+ });
94
+ }
95
+
96
+ // ESC key to close
97
+ document.addEventListener('keydown', (e) => {
98
+ if (e.key === 'Escape' && helpOverlay?.classList.contains('open')) closeHelp();
99
+ });
100
+
101
+ // Global toggle-help shortcut (via central keybinds)
102
+ registerAction('toggle-help', () => {
103
+ const overlay = document.getElementById('help-overlay');
104
+ if (overlay?.classList.contains('open')) closeHelp();
105
+ else openHelp();
106
+ });
107
+ }