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.
- package/CHANGELOG.md +665 -0
- package/LICENSE +191 -0
- package/README.md +853 -0
- package/hooks/claude-code/post-plan.mjs +391 -0
- package/hooks/claude-code/post-remember.mjs +239 -0
- package/hooks/claude-code/pre-compact.mjs +241 -0
- package/hooks/claude-code/pre-websearch.mjs +90 -0
- package/hooks/claude-code/prompt-submit.mjs +861 -0
- package/hooks/claude-code/session-start.mjs +494 -0
- package/hooks/claude-code/shared.mjs +368 -0
- package/hooks/claude-code/stop.mjs +646 -0
- package/hooks/claude-code/test-user-learning.mjs +531 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/run.mjs +10 -0
- package/mcp-server/src/config.ts +70 -0
- package/mcp-server/src/http.ts +93 -0
- package/mcp-server/src/index.ts +203 -0
- package/mcp-server/src/preload-http.ts +25 -0
- package/mcp-server/src/preload.ts +29 -0
- package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
- package/mcp-server/src/services/categories.ts +429 -0
- package/mcp-server/src/services/discord.ts +702 -0
- package/mcp-server/src/services/file-checksums.ts +33 -0
- package/mcp-server/src/services/local-embeddings.ts +77 -0
- package/mcp-server/src/services/neural-interface.ts +344 -0
- package/mcp-server/src/services/sqlite.ts +823 -0
- package/mcp-server/src/tools/browser-advanced.ts +123 -0
- package/mcp-server/src/tools/browser-interact.ts +255 -0
- package/mcp-server/src/tools/browser-navigate.ts +77 -0
- package/mcp-server/src/tools/browser-observe.ts +1330 -0
- package/mcp-server/src/tools/browser.ts +107 -0
- package/mcp-server/src/tools/card-tools.ts +193 -0
- package/mcp-server/src/tools/card.ts +22 -0
- package/mcp-server/src/tools/category.ts +379 -0
- package/mcp-server/src/tools/discord-channel.ts +274 -0
- package/mcp-server/src/tools/discord-guild.ts +188 -0
- package/mcp-server/src/tools/discord-member.ts +158 -0
- package/mcp-server/src/tools/discord-message.ts +231 -0
- package/mcp-server/src/tools/discord-onboarding.ts +228 -0
- package/mcp-server/src/tools/discord-role.ts +209 -0
- package/mcp-server/src/tools/discord-thread.ts +110 -0
- package/mcp-server/src/tools/discord-webhook.ts +176 -0
- package/mcp-server/src/tools/discord.ts +40 -0
- package/mcp-server/src/tools/forget.ts +38 -0
- package/mcp-server/src/tools/git-tools.ts +141 -0
- package/mcp-server/src/tools/git.ts +13 -0
- package/mcp-server/src/tools/image-tools.ts +98 -0
- package/mcp-server/src/tools/image.ts +17 -0
- package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
- package/mcp-server/src/tools/leonardo.ts +28 -0
- package/mcp-server/src/tools/loop.ts +348 -0
- package/mcp-server/src/tools/memories.ts +118 -0
- package/mcp-server/src/tools/recall.ts +323 -0
- package/mcp-server/src/tools/reflect.ts +145 -0
- package/mcp-server/src/tools/remember.ts +115 -0
- package/mcp-server/src/tools/response.ts +26 -0
- package/mcp-server/src/tools/restore.ts +34 -0
- package/mcp-server/src/tools/sync.ts +114 -0
- package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
- package/mcp-server/src/tools/tictactoe.ts +13 -0
- package/mcp-server/src/tools/utils.ts +25 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
- package/mcp-server/src/tools/whiteboard.ts +22 -0
- package/mcp-server/src/tui.ts +940 -0
- package/mcp-server/src/types.ts +85 -0
- package/mcp-server/tsconfig.json +19 -0
- package/neural-interface/package.json +27 -0
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/claude.svg +1 -0
- package/neural-interface/public/category-logos/gemini-color.svg +1 -0
- package/neural-interface/public/category-logos/openai.svg +1 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude-chat.css +878 -0
- package/neural-interface/public/claude-chat.html +92 -0
- package/neural-interface/public/claude-chat.js +1168 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/gallery.svg +6 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index-legacy.html +14677 -0
- package/neural-interface/public/index.html +212 -0
- package/neural-interface/public/index2d-legacy.html +8193 -0
- package/neural-interface/public/index2d.html +127 -0
- package/neural-interface/public/invite.html +351 -0
- package/neural-interface/public/leonardoai.svg +1 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +14 -0
- package/neural-interface/public/offline.html +299 -0
- package/neural-interface/public/onboarding.html +2335 -0
- package/neural-interface/public/openclaw-logo-text.png +0 -0
- package/neural-interface/public/pin-svgrepo-com.svg +25 -0
- package/neural-interface/public/sessionicon.svg +8 -0
- package/neural-interface/public/shared/ansi-parser.js +305 -0
- package/neural-interface/public/shared/api.js +1043 -0
- package/neural-interface/public/shared/colors.js +153 -0
- package/neural-interface/public/shared/constants.js +141 -0
- package/neural-interface/public/shared/html-shell.js +777 -0
- package/neural-interface/public/shared/html-term-renderer.js +1022 -0
- package/neural-interface/public/shared/i18n.js +147 -0
- package/neural-interface/public/shared/registry.js +82 -0
- package/neural-interface/public/shared/state.js +86 -0
- package/neural-interface/public/shared/storage.js +150 -0
- package/neural-interface/public/shared/styles.css +21212 -0
- package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
- package/neural-interface/public/shared/ui-bookmarks.js +231 -0
- package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
- package/neural-interface/public/shared/ui-command-runner.js +534 -0
- package/neural-interface/public/shared/ui-cost-widget.js +602 -0
- package/neural-interface/public/shared/ui-detail.js +1454 -0
- package/neural-interface/public/shared/ui-explorer.js +1356 -0
- package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
- package/neural-interface/public/shared/ui-games.js +411 -0
- package/neural-interface/public/shared/ui-help.js +107 -0
- package/neural-interface/public/shared/ui-image-gallery.js +461 -0
- package/neural-interface/public/shared/ui-invite.js +519 -0
- package/neural-interface/public/shared/ui-keybinds.js +452 -0
- package/neural-interface/public/shared/ui-layouts.js +297 -0
- package/neural-interface/public/shared/ui-link.js +731 -0
- package/neural-interface/public/shared/ui-loading.js +255 -0
- package/neural-interface/public/shared/ui-menubar.js +499 -0
- package/neural-interface/public/shared/ui-multiselect.js +93 -0
- package/neural-interface/public/shared/ui-navbar.js +222 -0
- package/neural-interface/public/shared/ui-notifications.js +287 -0
- package/neural-interface/public/shared/ui-panels.js +309 -0
- package/neural-interface/public/shared/ui-resume.js +653 -0
- package/neural-interface/public/shared/ui-search.js +145 -0
- package/neural-interface/public/shared/ui-sessions.js +531 -0
- package/neural-interface/public/shared/ui-settings.js +5987 -0
- package/neural-interface/public/shared/ui-sidebar.js +1905 -0
- package/neural-interface/public/shared/ui-skills.js +2805 -0
- package/neural-interface/public/shared/ui-stats.js +38 -0
- package/neural-interface/public/shared/ui-sync.js +201 -0
- package/neural-interface/public/shared/ui-terminal.js +6385 -0
- package/neural-interface/public/shared/ui-tooltip.js +123 -0
- package/neural-interface/public/shared/ui-trash.js +439 -0
- package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
- package/neural-interface/public/shared/ui-tutorial.js +1117 -0
- package/neural-interface/public/shared/ui-update.js +244 -0
- package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
- package/neural-interface/public/shared/ui-workspaces.js +722 -0
- package/neural-interface/public/shared/utils.js +360 -0
- package/neural-interface/public/shared/vterm-buffer.js +1102 -0
- package/neural-interface/public/sw.js +52 -0
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/toolbar-album.svg +8 -0
- package/neural-interface/public/toolbar-question.svg +6 -0
- package/neural-interface/public/toolbar-sessions.svg +6 -0
- package/neural-interface/public/variant/2d/background.js +113 -0
- package/neural-interface/public/variant/2d/context-menu.js +133 -0
- package/neural-interface/public/variant/2d/gfx.js +45 -0
- package/neural-interface/public/variant/2d/graph.js +1635 -0
- package/neural-interface/public/variant/2d/hulls.js +207 -0
- package/neural-interface/public/variant/2d/lasso.js +135 -0
- package/neural-interface/public/variant/2d/layout.js +594 -0
- package/neural-interface/public/variant/2d/main.js +521 -0
- package/neural-interface/public/variant/2d/minimap.js +312 -0
- package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
- package/neural-interface/public/variant/3d/background.js +884 -0
- package/neural-interface/public/variant/3d/camera.js +661 -0
- package/neural-interface/public/variant/3d/gfx.js +116 -0
- package/neural-interface/public/variant/3d/graph.js +2399 -0
- package/neural-interface/public/variant/3d/main.js +500 -0
- package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
- package/neural-interface/scripts/rebuild-pty.js +100 -0
- package/neural-interface/server.js +15000 -0
- package/neural-interface/templates/CLAUDE-template.md +211 -0
- package/package.json +53 -0
- package/postinstall.js +135 -0
- package/setup.js +260 -0
- package/skills/leonardo/SKILL.md +159 -0
- package/skills/leonardo/icon.svg +1 -0
- package/skills/leonardo/modules/browser-guide.md +378 -0
- package/skills/leonardo/modules/image-prompter.md +277 -0
- package/skills/leonardo/modules/model-advisor.md +100 -0
- package/skills/leonardo/modules/prompt-library.md +110 -0
- package/skills/leonardo/modules/style-guide.md +186 -0
- package/skills/leonardo/modules/video-prompter.md +311 -0
- package/skills/synabun/SKILL.md +185 -0
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +374 -0
- package/skills/synabun/modules/changelog.md +194 -0
- package/skills/synabun/modules/idea.md +144 -0
- package/skills/synabun/modules/memorize.md +188 -0
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,2335 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Setup - SynaBun</title>
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
9
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
10
|
+
<style>
|
|
11
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
12
|
+
|
|
13
|
+
:root {
|
|
14
|
+
--t-faint: rgba(255,255,255,0.15);
|
|
15
|
+
--t-muted: rgba(255,255,255,0.32);
|
|
16
|
+
--t-secondary: rgba(255,255,255,0.52);
|
|
17
|
+
--t-primary: rgba(255,255,255,0.72);
|
|
18
|
+
--t-bright: rgba(255,255,255,0.92);
|
|
19
|
+
--s-subtle: rgba(255,255,255,0.03);
|
|
20
|
+
--s-light: rgba(255,255,255,0.05);
|
|
21
|
+
--s-medium: rgba(255,255,255,0.08);
|
|
22
|
+
--s-hover: rgba(255,255,255,0.06);
|
|
23
|
+
--s-active: rgba(255,255,255,0.12);
|
|
24
|
+
--b-subtle: rgba(255,255,255,0.05);
|
|
25
|
+
--b-light: rgba(255,255,255,0.08);
|
|
26
|
+
--b-hover: rgba(255,255,255,0.14);
|
|
27
|
+
--b-visible: rgba(255,255,255,0.20);
|
|
28
|
+
--accent: #ffffff;
|
|
29
|
+
--accent-bg: rgba(255,255,255,0.06);
|
|
30
|
+
--accent-border: rgba(255,255,255,0.18);
|
|
31
|
+
--accent-dim: rgba(255,255,255,0.45);
|
|
32
|
+
--green: #6dd58c;
|
|
33
|
+
--green-bg: rgba(109,213,140,0.10);
|
|
34
|
+
--red: #ff6b6b;
|
|
35
|
+
--red-bg: rgba(255,107,107,0.10);
|
|
36
|
+
--r-card: 12px;
|
|
37
|
+
--r-button: 12px;
|
|
38
|
+
--ease: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
body {
|
|
42
|
+
background: #050505;
|
|
43
|
+
color: #e0e0e0;
|
|
44
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', 'Fira Code', monospace;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
height: 100vh;
|
|
47
|
+
width: 100vw;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Background canvas */
|
|
55
|
+
#bgCanvas {
|
|
56
|
+
position: fixed;
|
|
57
|
+
inset: 0;
|
|
58
|
+
width: 100%;
|
|
59
|
+
height: 100%;
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
z-index: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
::-webkit-scrollbar { width: 4px; }
|
|
65
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
66
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }
|
|
67
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.18); }
|
|
68
|
+
|
|
69
|
+
/* ── Wizard Container ── */
|
|
70
|
+
.wizard {
|
|
71
|
+
width: 620px;
|
|
72
|
+
max-height: 90vh;
|
|
73
|
+
background: rgba(10, 10, 12, 0.85);
|
|
74
|
+
backdrop-filter: blur(60px) saturate(1.3);
|
|
75
|
+
-webkit-backdrop-filter: blur(60px) saturate(1.3);
|
|
76
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
77
|
+
border-radius: 20px;
|
|
78
|
+
box-shadow:
|
|
79
|
+
0 0 0 1px rgba(0,0,0,0.3),
|
|
80
|
+
0 2px 8px rgba(0,0,0,0.3),
|
|
81
|
+
0 12px 48px rgba(0,0,0,0.5),
|
|
82
|
+
inset 0 1px 0 rgba(255,255,255,0.04);
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
position: relative;
|
|
87
|
+
z-index: 2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ── Steps Viewport ── */
|
|
91
|
+
.steps-viewport {
|
|
92
|
+
position: relative;
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
transition: height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.step {
|
|
98
|
+
position: absolute;
|
|
99
|
+
top: 0; left: 0; right: 0;
|
|
100
|
+
padding: 36px 40px;
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-direction: column;
|
|
103
|
+
transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s ease;
|
|
104
|
+
overflow-y: auto;
|
|
105
|
+
}
|
|
106
|
+
.step.active { transform: translateX(0); opacity: 1; z-index: 2; }
|
|
107
|
+
.step.left { transform: translateX(-100%); opacity: 0; z-index: 1; }
|
|
108
|
+
.step.right { transform: translateX(100%); opacity: 0; z-index: 1; }
|
|
109
|
+
|
|
110
|
+
.step-eyes {
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: center;
|
|
113
|
+
pointer-events: none;
|
|
114
|
+
user-select: none;
|
|
115
|
+
margin-bottom: 8px;
|
|
116
|
+
min-height: 0;
|
|
117
|
+
transition: opacity 0.4s ease;
|
|
118
|
+
}
|
|
119
|
+
.step-eyes:empty { display: none; }
|
|
120
|
+
|
|
121
|
+
.step h2 {
|
|
122
|
+
font-size: 20px;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
color: var(--t-bright);
|
|
125
|
+
margin-bottom: 6px;
|
|
126
|
+
letter-spacing: -0.3px;
|
|
127
|
+
}
|
|
128
|
+
.step .subtitle {
|
|
129
|
+
font-size: 13px;
|
|
130
|
+
color: var(--t-secondary);
|
|
131
|
+
margin-bottom: 24px;
|
|
132
|
+
line-height: 1.5;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ── Navigation Bar ── */
|
|
136
|
+
.nav-bar {
|
|
137
|
+
padding: 12px 24px;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
border-top: 1px solid rgba(255,255,255,0.06);
|
|
142
|
+
background: rgba(0,0,0,0.25);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.dots {
|
|
146
|
+
display: flex;
|
|
147
|
+
gap: 3px;
|
|
148
|
+
align-items: center;
|
|
149
|
+
}
|
|
150
|
+
.dot {
|
|
151
|
+
width: 16px; height: 3px;
|
|
152
|
+
border-radius: 1px;
|
|
153
|
+
background: rgba(255,255,255,0.08);
|
|
154
|
+
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
155
|
+
}
|
|
156
|
+
.dot.active {
|
|
157
|
+
background: var(--accent);
|
|
158
|
+
}
|
|
159
|
+
.dot.done { background: rgba(255,255,255,0.30); }
|
|
160
|
+
|
|
161
|
+
.nav-btn {
|
|
162
|
+
padding: 7px 16px;
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
165
|
+
background: rgba(255,255,255,0.03);
|
|
166
|
+
color: var(--t-muted);
|
|
167
|
+
font-size: 11px;
|
|
168
|
+
font-family: inherit;
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
transition: all 0.2s ease;
|
|
171
|
+
letter-spacing: 0.04em;
|
|
172
|
+
text-transform: uppercase;
|
|
173
|
+
}
|
|
174
|
+
.nav-btn:hover { color: var(--t-bright); background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.12); }
|
|
175
|
+
.nav-btn:disabled { opacity: 0; cursor: default; pointer-events: none; }
|
|
176
|
+
.nav-btn.primary {
|
|
177
|
+
background: rgba(255,255,255,0.06);
|
|
178
|
+
border-color: rgba(255,255,255,0.18);
|
|
179
|
+
color: var(--accent);
|
|
180
|
+
}
|
|
181
|
+
.nav-btn.primary:hover {
|
|
182
|
+
background: rgba(255,255,255,0.12);
|
|
183
|
+
border-color: rgba(255,255,255,0.30);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* ── Form Elements ── */
|
|
187
|
+
.field { margin-bottom: 18px; }
|
|
188
|
+
.field label:not(.key-backup-check) {
|
|
189
|
+
display: block;
|
|
190
|
+
font-size: 11px;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
color: var(--t-secondary);
|
|
193
|
+
margin-bottom: 8px;
|
|
194
|
+
text-transform: uppercase;
|
|
195
|
+
letter-spacing: 0.6px;
|
|
196
|
+
}
|
|
197
|
+
.field input:not([type="checkbox"]), .field select {
|
|
198
|
+
width: 100%;
|
|
199
|
+
padding: 11px 14px;
|
|
200
|
+
background: rgba(255,255,255,0.03);
|
|
201
|
+
border: 1px solid var(--b-light);
|
|
202
|
+
border-radius: 10px;
|
|
203
|
+
color: var(--t-bright);
|
|
204
|
+
font-size: 13px;
|
|
205
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', 'Fira Code', monospace;
|
|
206
|
+
outline: none;
|
|
207
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
208
|
+
}
|
|
209
|
+
.field input:not([type="checkbox"]):focus, .field select:focus {
|
|
210
|
+
border-color: var(--accent-border);
|
|
211
|
+
background: rgba(255,255,255,0.04);
|
|
212
|
+
box-shadow: 0 0 0 3px rgba(255,255,255,0.04);
|
|
213
|
+
}
|
|
214
|
+
.field input:not([type="checkbox"])::placeholder { color: var(--t-muted); font-family: inherit; }
|
|
215
|
+
.field .hint {
|
|
216
|
+
font-size: 11px;
|
|
217
|
+
color: var(--t-muted);
|
|
218
|
+
margin-top: 6px;
|
|
219
|
+
line-height: 1.4;
|
|
220
|
+
}
|
|
221
|
+
.field .error-text {
|
|
222
|
+
font-size: 11px;
|
|
223
|
+
color: var(--red);
|
|
224
|
+
margin-top: 6px;
|
|
225
|
+
}
|
|
226
|
+
.field .error-text:empty {
|
|
227
|
+
display: none;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.input-row {
|
|
231
|
+
display: flex;
|
|
232
|
+
gap: 8px;
|
|
233
|
+
}
|
|
234
|
+
.input-row input { flex: 1; }
|
|
235
|
+
.input-row .btn-sm {
|
|
236
|
+
padding: 9px 14px;
|
|
237
|
+
border-radius: 10px;
|
|
238
|
+
border: 1px solid var(--b-light);
|
|
239
|
+
background: var(--s-light);
|
|
240
|
+
color: var(--t-primary);
|
|
241
|
+
font-size: 12px;
|
|
242
|
+
font-family: inherit;
|
|
243
|
+
cursor: pointer;
|
|
244
|
+
white-space: nowrap;
|
|
245
|
+
transition: all var(--ease);
|
|
246
|
+
}
|
|
247
|
+
.input-row .btn-sm:hover { background: var(--s-medium); color: var(--t-bright); border-color: var(--b-hover); }
|
|
248
|
+
|
|
249
|
+
/* ── Key Backup Notice ── */
|
|
250
|
+
.key-backup-notice {
|
|
251
|
+
margin-top: 16px;
|
|
252
|
+
padding: 12px 14px;
|
|
253
|
+
background: rgba(255,255,255,0.025);
|
|
254
|
+
border: 1px solid var(--b-light);
|
|
255
|
+
border-radius: 10px;
|
|
256
|
+
display: flex;
|
|
257
|
+
gap: 10px;
|
|
258
|
+
align-items: flex-start;
|
|
259
|
+
}
|
|
260
|
+
.key-backup-icon {
|
|
261
|
+
flex-shrink: 0;
|
|
262
|
+
color: var(--t-muted);
|
|
263
|
+
margin-top: 1px;
|
|
264
|
+
}
|
|
265
|
+
.key-backup-text {
|
|
266
|
+
font-size: 12px;
|
|
267
|
+
color: var(--t-secondary);
|
|
268
|
+
line-height: 1.5;
|
|
269
|
+
margin: 0;
|
|
270
|
+
}
|
|
271
|
+
.key-backup-check {
|
|
272
|
+
display: flex;
|
|
273
|
+
align-items: center;
|
|
274
|
+
gap: 10px;
|
|
275
|
+
margin-top: 12px;
|
|
276
|
+
cursor: pointer;
|
|
277
|
+
user-select: none;
|
|
278
|
+
font-size: 13px;
|
|
279
|
+
color: var(--t-secondary);
|
|
280
|
+
padding: 10px 14px;
|
|
281
|
+
border: 1px solid var(--b-light);
|
|
282
|
+
border-radius: 10px;
|
|
283
|
+
background: var(--s-subtle);
|
|
284
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
285
|
+
}
|
|
286
|
+
.key-backup-check:hover {
|
|
287
|
+
background: var(--s-hover);
|
|
288
|
+
border-color: var(--b-hover);
|
|
289
|
+
color: var(--t-primary);
|
|
290
|
+
}
|
|
291
|
+
.key-backup-check:has(input:checked) {
|
|
292
|
+
border-color: rgba(109,213,140,0.25);
|
|
293
|
+
background: rgba(109,213,140,0.05);
|
|
294
|
+
color: var(--t-bright);
|
|
295
|
+
}
|
|
296
|
+
.key-backup-checkbox-wrap {
|
|
297
|
+
position: relative;
|
|
298
|
+
width: 18px;
|
|
299
|
+
height: 18px;
|
|
300
|
+
flex-shrink: 0;
|
|
301
|
+
}
|
|
302
|
+
.key-backup-checkbox-wrap input {
|
|
303
|
+
position: absolute;
|
|
304
|
+
opacity: 0;
|
|
305
|
+
width: 18px;
|
|
306
|
+
height: 18px;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
margin: 0;
|
|
309
|
+
z-index: 1;
|
|
310
|
+
}
|
|
311
|
+
.key-backup-checkmark {
|
|
312
|
+
position: absolute;
|
|
313
|
+
top: 0;
|
|
314
|
+
left: 0;
|
|
315
|
+
width: 18px;
|
|
316
|
+
height: 18px;
|
|
317
|
+
border: 1px solid var(--b-hover);
|
|
318
|
+
border-radius: 5px;
|
|
319
|
+
background: rgba(255,255,255,0.03);
|
|
320
|
+
display: flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
justify-content: center;
|
|
323
|
+
transition: all 0.2s ease;
|
|
324
|
+
color: transparent;
|
|
325
|
+
}
|
|
326
|
+
.key-backup-checkbox-wrap input:checked ~ .key-backup-checkmark {
|
|
327
|
+
background: rgba(109,213,140,0.15);
|
|
328
|
+
border-color: rgba(109,213,140,0.35);
|
|
329
|
+
color: var(--green);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* ── Provider Cards Grid ── */
|
|
333
|
+
.provider-grid {
|
|
334
|
+
display: grid;
|
|
335
|
+
grid-template-columns: 1fr 1fr;
|
|
336
|
+
gap: 10px;
|
|
337
|
+
margin-bottom: 20px;
|
|
338
|
+
}
|
|
339
|
+
.provider-card {
|
|
340
|
+
padding: 12px 14px;
|
|
341
|
+
border: 1px solid var(--b-light);
|
|
342
|
+
border-radius: var(--r-card);
|
|
343
|
+
background: var(--s-subtle);
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
346
|
+
position: relative;
|
|
347
|
+
}
|
|
348
|
+
.provider-card:hover { background: var(--s-hover); border-color: var(--b-hover); transform: translateY(-1px); }
|
|
349
|
+
.provider-card.selected {
|
|
350
|
+
border-color: var(--accent-border);
|
|
351
|
+
background: var(--accent-bg);
|
|
352
|
+
box-shadow: 0 0 0 1px var(--accent-border);
|
|
353
|
+
}
|
|
354
|
+
.provider-card .pname {
|
|
355
|
+
font-size: 13px;
|
|
356
|
+
font-weight: 600;
|
|
357
|
+
color: var(--t-bright);
|
|
358
|
+
}
|
|
359
|
+
.provider-card .ptag {
|
|
360
|
+
position: absolute;
|
|
361
|
+
top: 8px; right: 10px;
|
|
362
|
+
font-size: 9px;
|
|
363
|
+
padding: 2px 7px;
|
|
364
|
+
border-radius: 6px;
|
|
365
|
+
background: rgba(255,255,255,0.08);
|
|
366
|
+
color: var(--t-bright);
|
|
367
|
+
text-transform: uppercase;
|
|
368
|
+
letter-spacing: 0.5px;
|
|
369
|
+
font-weight: 600;
|
|
370
|
+
}
|
|
371
|
+
.provider-card .pmodel {
|
|
372
|
+
font-size: 11px;
|
|
373
|
+
color: var(--t-muted);
|
|
374
|
+
margin-top: 3px;
|
|
375
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
|
|
376
|
+
font-size: 10px;
|
|
377
|
+
}
|
|
378
|
+
.provider-card .plink {
|
|
379
|
+
display: inline-block;
|
|
380
|
+
font-size: 10px;
|
|
381
|
+
color: var(--accent-dim);
|
|
382
|
+
margin-top: 6px;
|
|
383
|
+
text-decoration: none;
|
|
384
|
+
transition: color 0.2s ease;
|
|
385
|
+
}
|
|
386
|
+
.provider-card .plink:hover { color: var(--accent); text-decoration: underline; }
|
|
387
|
+
|
|
388
|
+
/* ── Mode Toggle ── */
|
|
389
|
+
.mode-toggle {
|
|
390
|
+
display: flex;
|
|
391
|
+
gap: 0;
|
|
392
|
+
margin-bottom: 20px;
|
|
393
|
+
border: 1px solid var(--b-light);
|
|
394
|
+
border-radius: 10px;
|
|
395
|
+
overflow: hidden;
|
|
396
|
+
width: fit-content;
|
|
397
|
+
background: var(--s-subtle);
|
|
398
|
+
}
|
|
399
|
+
.mode-toggle button {
|
|
400
|
+
padding: 8px 20px;
|
|
401
|
+
border: none;
|
|
402
|
+
background: transparent;
|
|
403
|
+
color: var(--t-muted);
|
|
404
|
+
font-size: 12px;
|
|
405
|
+
font-family: inherit;
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
transition: all 0.25s ease;
|
|
408
|
+
letter-spacing: 0.01em;
|
|
409
|
+
font-weight: 500;
|
|
410
|
+
}
|
|
411
|
+
.mode-toggle button.active {
|
|
412
|
+
background: rgba(255,255,255,0.10);
|
|
413
|
+
color: var(--t-bright);
|
|
414
|
+
}
|
|
415
|
+
.mode-toggle button:hover:not(.active) { color: var(--t-primary); background: var(--s-hover); }
|
|
416
|
+
|
|
417
|
+
/* ── Tool Cards ── */
|
|
418
|
+
.tool-grid {
|
|
419
|
+
display: flex;
|
|
420
|
+
gap: 8px;
|
|
421
|
+
margin-bottom: 16px;
|
|
422
|
+
}
|
|
423
|
+
.tool-card {
|
|
424
|
+
flex: 1;
|
|
425
|
+
padding: 10px 6px;
|
|
426
|
+
border: 1px solid var(--b-light);
|
|
427
|
+
border-radius: var(--r-card);
|
|
428
|
+
background: var(--s-subtle);
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
text-align: center;
|
|
431
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
432
|
+
}
|
|
433
|
+
.tool-card:hover { background: var(--s-hover); border-color: var(--b-hover); transform: translateY(-1px); }
|
|
434
|
+
.tool-card.selected {
|
|
435
|
+
border-color: var(--accent-border);
|
|
436
|
+
background: var(--accent-bg);
|
|
437
|
+
box-shadow: 0 0 0 1px var(--accent-border);
|
|
438
|
+
}
|
|
439
|
+
.tool-card .tname {
|
|
440
|
+
font-size: 12px;
|
|
441
|
+
font-weight: 600;
|
|
442
|
+
color: var(--t-bright);
|
|
443
|
+
white-space: nowrap;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* ── Dependency List ── */
|
|
447
|
+
.dep-list {
|
|
448
|
+
list-style: none;
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
gap: 10px;
|
|
452
|
+
}
|
|
453
|
+
.dep-item {
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
gap: 14px;
|
|
457
|
+
padding: 12px 16px;
|
|
458
|
+
border: 1px solid var(--b-light);
|
|
459
|
+
border-radius: var(--r-card);
|
|
460
|
+
background: var(--s-subtle);
|
|
461
|
+
opacity: 0;
|
|
462
|
+
transform: translateY(8px);
|
|
463
|
+
transition: opacity 0.4s ease, transform 0.4s ease, border-color 0.3s ease, background 0.3s ease, box-shadow 0.3s ease;
|
|
464
|
+
}
|
|
465
|
+
.dep-item.visible { opacity: 1; transform: translateY(0); }
|
|
466
|
+
.dep-item.ok { border-color: rgba(255,255,255,0.12); background: rgba(255,255,255,0.03); }
|
|
467
|
+
.dep-item.warn { border-color: rgba(255,193,7,0.15); background: rgba(255,193,7,0.03); }
|
|
468
|
+
.dep-item.fail { border-color: rgba(255,107,107,0.15); background: rgba(255,107,107,0.03); }
|
|
469
|
+
|
|
470
|
+
.dep-icon {
|
|
471
|
+
width: 32px; height: 32px;
|
|
472
|
+
border-radius: 10px;
|
|
473
|
+
display: flex;
|
|
474
|
+
align-items: center;
|
|
475
|
+
justify-content: center;
|
|
476
|
+
font-size: 14px;
|
|
477
|
+
flex-shrink: 0;
|
|
478
|
+
transition: all 0.3s ease;
|
|
479
|
+
}
|
|
480
|
+
.dep-item.checking .dep-icon { background: var(--s-medium); color: var(--t-muted); }
|
|
481
|
+
.dep-item.ok .dep-icon { background: rgba(255,255,255,0.08); color: var(--t-bright); }
|
|
482
|
+
.dep-item.warn .dep-icon { background: rgba(255,193,7,0.12); color: #FFC107; }
|
|
483
|
+
.dep-item.fail .dep-icon { background: rgba(255,107,107,0.10); color: var(--red); }
|
|
484
|
+
|
|
485
|
+
.dep-info { flex: 1; min-width: 0; }
|
|
486
|
+
.dep-name {
|
|
487
|
+
font-size: 13px;
|
|
488
|
+
font-weight: 600;
|
|
489
|
+
color: var(--t-bright);
|
|
490
|
+
}
|
|
491
|
+
.dep-detail {
|
|
492
|
+
font-size: 11px;
|
|
493
|
+
color: var(--t-muted);
|
|
494
|
+
margin-top: 3px;
|
|
495
|
+
}
|
|
496
|
+
.dep-detail a {
|
|
497
|
+
color: var(--accent-dim);
|
|
498
|
+
text-decoration: none;
|
|
499
|
+
transition: color 0.2s ease;
|
|
500
|
+
}
|
|
501
|
+
.dep-detail a:hover { color: var(--accent); text-decoration: underline; }
|
|
502
|
+
|
|
503
|
+
@keyframes dep-spin {
|
|
504
|
+
to { transform: rotate(360deg); }
|
|
505
|
+
}
|
|
506
|
+
.dep-item.checking .dep-icon::after {
|
|
507
|
+
content: '';
|
|
508
|
+
width: 14px; height: 14px;
|
|
509
|
+
border: 2px solid var(--b-light);
|
|
510
|
+
border-top-color: var(--t-bright);
|
|
511
|
+
border-radius: 50%;
|
|
512
|
+
animation: dep-spin 0.7s linear infinite;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* ── Terminal Output ── */
|
|
516
|
+
.terminal {
|
|
517
|
+
background: rgba(0,0,0,0.35);
|
|
518
|
+
border: 1px solid var(--b-subtle);
|
|
519
|
+
border-radius: 10px;
|
|
520
|
+
padding: 14px 16px;
|
|
521
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', 'Fira Code', monospace;
|
|
522
|
+
font-size: 11px;
|
|
523
|
+
color: var(--t-secondary);
|
|
524
|
+
max-height: 180px;
|
|
525
|
+
overflow-y: auto;
|
|
526
|
+
white-space: pre-wrap;
|
|
527
|
+
word-break: break-all;
|
|
528
|
+
line-height: 1.6;
|
|
529
|
+
margin-bottom: 14px;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* ── Status Indicators ── */
|
|
533
|
+
.status-row {
|
|
534
|
+
display: flex;
|
|
535
|
+
align-items: center;
|
|
536
|
+
gap: 10px;
|
|
537
|
+
margin-bottom: 12px;
|
|
538
|
+
font-size: 13px;
|
|
539
|
+
color: var(--t-primary);
|
|
540
|
+
}
|
|
541
|
+
.status-dot {
|
|
542
|
+
width: 8px; height: 8px;
|
|
543
|
+
border-radius: 50%;
|
|
544
|
+
flex-shrink: 0;
|
|
545
|
+
}
|
|
546
|
+
.status-dot.green { background: var(--t-bright); }
|
|
547
|
+
.status-dot.red { background: var(--red); }
|
|
548
|
+
.status-dot.yellow { background: #FFC107; }
|
|
549
|
+
.status-dot.gray { background: rgba(255,255,255,0.15); }
|
|
550
|
+
|
|
551
|
+
/* ── Action Button ── */
|
|
552
|
+
.action-btn {
|
|
553
|
+
padding: 11px 26px;
|
|
554
|
+
border-radius: var(--r-button);
|
|
555
|
+
border: 1px solid rgba(255,255,255,0.18);
|
|
556
|
+
background: rgba(255,255,255,0.06);
|
|
557
|
+
color: var(--t-bright);
|
|
558
|
+
font-size: 13px;
|
|
559
|
+
font-weight: 600;
|
|
560
|
+
font-family: inherit;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
563
|
+
display: inline-flex;
|
|
564
|
+
align-items: center;
|
|
565
|
+
gap: 8px;
|
|
566
|
+
}
|
|
567
|
+
.action-btn:hover {
|
|
568
|
+
background: rgba(255,255,255,0.12);
|
|
569
|
+
border-color: rgba(255,255,255,0.30);
|
|
570
|
+
transform: translateY(-1px);
|
|
571
|
+
}
|
|
572
|
+
.action-btn:active { transform: translateY(0); }
|
|
573
|
+
.action-btn:disabled { opacity: 0.4; cursor: default; transform: none; }
|
|
574
|
+
.action-btn.success {
|
|
575
|
+
border-color: rgba(109,213,140,0.30);
|
|
576
|
+
background: var(--green-bg);
|
|
577
|
+
color: var(--green);
|
|
578
|
+
}
|
|
579
|
+
.action-btn.success:hover {
|
|
580
|
+
background: rgba(109,213,140,0.12);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* ── SynaBun Eyes ── */
|
|
584
|
+
#synabun-bg {
|
|
585
|
+
display: flex;
|
|
586
|
+
justify-content: center;
|
|
587
|
+
pointer-events: none;
|
|
588
|
+
user-select: none;
|
|
589
|
+
margin-bottom: 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/* ── Typewriter ── */
|
|
593
|
+
.typewriter-line {
|
|
594
|
+
font-size: 28px;
|
|
595
|
+
font-weight: 700;
|
|
596
|
+
color: var(--t-bright);
|
|
597
|
+
text-align: center;
|
|
598
|
+
min-height: 36px;
|
|
599
|
+
letter-spacing: -0.5px;
|
|
600
|
+
}
|
|
601
|
+
.welcome-desc {
|
|
602
|
+
font-size: 15px;
|
|
603
|
+
color: var(--t-secondary);
|
|
604
|
+
text-align: center;
|
|
605
|
+
margin-top: 16px;
|
|
606
|
+
opacity: 0;
|
|
607
|
+
transition: opacity 0.6s ease;
|
|
608
|
+
line-height: 1.8;
|
|
609
|
+
}
|
|
610
|
+
.welcome-desc.visible { opacity: 1; }
|
|
611
|
+
|
|
612
|
+
/* ── Welcome Choice Cards ── */
|
|
613
|
+
.welcome-choices {
|
|
614
|
+
display: flex;
|
|
615
|
+
gap: 14px;
|
|
616
|
+
margin-top: 28px;
|
|
617
|
+
opacity: 0;
|
|
618
|
+
transition: opacity 0.5s ease;
|
|
619
|
+
width: 100%;
|
|
620
|
+
max-width: 440px;
|
|
621
|
+
}
|
|
622
|
+
.welcome-choices.visible { opacity: 1; }
|
|
623
|
+
.welcome-choice {
|
|
624
|
+
flex: 1;
|
|
625
|
+
padding: 20px 16px;
|
|
626
|
+
border: 1px solid var(--b-light);
|
|
627
|
+
border-radius: var(--r-card);
|
|
628
|
+
background: var(--s-subtle);
|
|
629
|
+
cursor: pointer;
|
|
630
|
+
text-align: center;
|
|
631
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
632
|
+
display: flex;
|
|
633
|
+
flex-direction: column;
|
|
634
|
+
align-items: center;
|
|
635
|
+
gap: 8px;
|
|
636
|
+
}
|
|
637
|
+
.welcome-choice:hover {
|
|
638
|
+
background: var(--s-hover);
|
|
639
|
+
border-color: var(--b-hover);
|
|
640
|
+
transform: translateY(-2px);
|
|
641
|
+
}
|
|
642
|
+
.welcome-choice-icon {
|
|
643
|
+
width: 32px;
|
|
644
|
+
height: 32px;
|
|
645
|
+
color: var(--t-muted);
|
|
646
|
+
transition: color 0.2s ease;
|
|
647
|
+
}
|
|
648
|
+
.welcome-choice:hover .welcome-choice-icon { color: var(--t-bright); }
|
|
649
|
+
.welcome-choice-title {
|
|
650
|
+
font-size: 14px;
|
|
651
|
+
font-weight: 600;
|
|
652
|
+
color: var(--t-bright);
|
|
653
|
+
}
|
|
654
|
+
.welcome-choice-desc {
|
|
655
|
+
font-size: 11px;
|
|
656
|
+
color: var(--t-muted);
|
|
657
|
+
line-height: 1.4;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/* Restore sub-panel inside step 0 */
|
|
661
|
+
.welcome-restore-panel {
|
|
662
|
+
width: 100%;
|
|
663
|
+
max-width: 440px;
|
|
664
|
+
margin-top: 20px;
|
|
665
|
+
opacity: 0;
|
|
666
|
+
transition: opacity 0.4s ease;
|
|
667
|
+
display: none;
|
|
668
|
+
}
|
|
669
|
+
.welcome-restore-panel.visible {
|
|
670
|
+
display: block;
|
|
671
|
+
opacity: 1;
|
|
672
|
+
}
|
|
673
|
+
.restore-dropzone {
|
|
674
|
+
padding: 28px 20px;
|
|
675
|
+
border: 2px dashed var(--b-light);
|
|
676
|
+
border-radius: var(--r-card);
|
|
677
|
+
background: var(--s-subtle);
|
|
678
|
+
text-align: center;
|
|
679
|
+
cursor: pointer;
|
|
680
|
+
transition: all 0.25s ease;
|
|
681
|
+
}
|
|
682
|
+
.restore-dropzone:hover,
|
|
683
|
+
.restore-dropzone.dragover {
|
|
684
|
+
border-color: var(--b-hover);
|
|
685
|
+
background: var(--s-hover);
|
|
686
|
+
}
|
|
687
|
+
.restore-dropzone-icon {
|
|
688
|
+
color: var(--t-muted);
|
|
689
|
+
margin-bottom: 8px;
|
|
690
|
+
}
|
|
691
|
+
.restore-dropzone-text {
|
|
692
|
+
font-size: 13px;
|
|
693
|
+
color: var(--t-secondary);
|
|
694
|
+
}
|
|
695
|
+
.restore-dropzone-hint {
|
|
696
|
+
font-size: 11px;
|
|
697
|
+
color: var(--t-muted);
|
|
698
|
+
margin-top: 4px;
|
|
699
|
+
}
|
|
700
|
+
.restore-back-link {
|
|
701
|
+
display: inline-block;
|
|
702
|
+
margin-top: 14px;
|
|
703
|
+
font-size: 11px;
|
|
704
|
+
color: var(--t-muted);
|
|
705
|
+
cursor: pointer;
|
|
706
|
+
transition: color 0.2s ease;
|
|
707
|
+
text-decoration: none;
|
|
708
|
+
}
|
|
709
|
+
.restore-back-link:hover { color: var(--t-bright); }
|
|
710
|
+
|
|
711
|
+
/* ── Instructions Preview ── */
|
|
712
|
+
.instructions-preview {
|
|
713
|
+
background: rgba(0,0,0,0.30);
|
|
714
|
+
border: 1px solid var(--b-subtle);
|
|
715
|
+
border-radius: 10px;
|
|
716
|
+
padding: 16px;
|
|
717
|
+
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', 'Fira Code', monospace;
|
|
718
|
+
font-size: 11px;
|
|
719
|
+
color: var(--t-secondary);
|
|
720
|
+
max-height: 240px;
|
|
721
|
+
overflow-y: auto;
|
|
722
|
+
white-space: pre-wrap;
|
|
723
|
+
line-height: 1.6;
|
|
724
|
+
margin-bottom: 14px;
|
|
725
|
+
}
|
|
726
|
+
.code-copy-wrap {
|
|
727
|
+
position: relative;
|
|
728
|
+
cursor: pointer;
|
|
729
|
+
transition: opacity 0.15s ease;
|
|
730
|
+
}
|
|
731
|
+
.code-copy-wrap:hover { opacity: 0.85; }
|
|
732
|
+
.code-copy-wrap:hover .code-copy-label { opacity: 1; }
|
|
733
|
+
.code-copy-wrap:active .instructions-preview {
|
|
734
|
+
border-color: rgba(255,255,255,0.22);
|
|
735
|
+
}
|
|
736
|
+
.code-copy-wrap .instructions-preview { margin-bottom: 0; }
|
|
737
|
+
#mcpSetupCode { max-height: 140px; }
|
|
738
|
+
.code-copy-label {
|
|
739
|
+
position: absolute;
|
|
740
|
+
bottom: 8px;
|
|
741
|
+
right: 12px;
|
|
742
|
+
font-size: 10px;
|
|
743
|
+
color: var(--t-muted);
|
|
744
|
+
opacity: 0.5;
|
|
745
|
+
transition: opacity 0.15s ease;
|
|
746
|
+
text-transform: uppercase;
|
|
747
|
+
letter-spacing: 0.5px;
|
|
748
|
+
pointer-events: none;
|
|
749
|
+
}
|
|
750
|
+
.mascot-toast {
|
|
751
|
+
position: absolute;
|
|
752
|
+
top: 50%;
|
|
753
|
+
left: 50%;
|
|
754
|
+
transform: translate(40px, -50%);
|
|
755
|
+
font-size: 11px;
|
|
756
|
+
color: var(--t-bright);
|
|
757
|
+
white-space: nowrap;
|
|
758
|
+
opacity: 0;
|
|
759
|
+
transition: opacity 0.25s ease, transform 0.3s ease;
|
|
760
|
+
pointer-events: none;
|
|
761
|
+
}
|
|
762
|
+
.mascot-toast.show {
|
|
763
|
+
opacity: 1;
|
|
764
|
+
transform: translate(50px, -50%);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/* ── Checklist ── */
|
|
768
|
+
.checklist {
|
|
769
|
+
list-style: none;
|
|
770
|
+
margin: 16px 0;
|
|
771
|
+
}
|
|
772
|
+
.checklist li {
|
|
773
|
+
display: flex;
|
|
774
|
+
align-items: center;
|
|
775
|
+
gap: 12px;
|
|
776
|
+
padding: 10px 0;
|
|
777
|
+
font-size: 13px;
|
|
778
|
+
color: var(--t-primary);
|
|
779
|
+
border-bottom: 1px solid rgba(255,255,255,0.03);
|
|
780
|
+
}
|
|
781
|
+
.checklist li:last-child { border-bottom: none; }
|
|
782
|
+
.checklist .check {
|
|
783
|
+
color: var(--t-bright);
|
|
784
|
+
font-size: 16px;
|
|
785
|
+
flex-shrink: 0;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/* ── Spinner ── */
|
|
789
|
+
.spinner {
|
|
790
|
+
display: inline-block;
|
|
791
|
+
width: 14px; height: 14px;
|
|
792
|
+
border: 2px solid rgba(255,255,255,0.12);
|
|
793
|
+
border-top-color: var(--t-bright);
|
|
794
|
+
border-radius: 50%;
|
|
795
|
+
animation: spin 0.7s linear infinite;
|
|
796
|
+
}
|
|
797
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
798
|
+
|
|
799
|
+
.btn-row {
|
|
800
|
+
display: flex;
|
|
801
|
+
gap: 8px;
|
|
802
|
+
margin-top: 10px;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* ── Social Icons ── */
|
|
806
|
+
.social-bar {
|
|
807
|
+
display: flex;
|
|
808
|
+
align-items: center;
|
|
809
|
+
gap: 2px;
|
|
810
|
+
padding: 3px;
|
|
811
|
+
border-radius: 6px;
|
|
812
|
+
border: 1px solid rgba(255,255,255,0.06);
|
|
813
|
+
background: rgba(255,255,255,0.02);
|
|
814
|
+
}
|
|
815
|
+
.social-icon {
|
|
816
|
+
display: flex;
|
|
817
|
+
align-items: center;
|
|
818
|
+
justify-content: center;
|
|
819
|
+
width: 28px;
|
|
820
|
+
height: 24px;
|
|
821
|
+
border-radius: 4px;
|
|
822
|
+
color: rgba(255,255,255,0.20);
|
|
823
|
+
transition: all 0.2s ease;
|
|
824
|
+
}
|
|
825
|
+
.social-icon:hover {
|
|
826
|
+
color: rgba(255,255,255,0.60);
|
|
827
|
+
background: rgba(255,255,255,0.06);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/* ── Floating Mascot ── */
|
|
831
|
+
.wizard-wrap {
|
|
832
|
+
position: relative;
|
|
833
|
+
perspective: 600px;
|
|
834
|
+
}
|
|
835
|
+
#synabun-float {
|
|
836
|
+
position: absolute;
|
|
837
|
+
left: 50%;
|
|
838
|
+
top: -60px;
|
|
839
|
+
pointer-events: none;
|
|
840
|
+
user-select: none;
|
|
841
|
+
z-index: 3;
|
|
842
|
+
opacity: 0;
|
|
843
|
+
transform: translate(-50%, 20px);
|
|
844
|
+
transform-style: preserve-3d;
|
|
845
|
+
transition:
|
|
846
|
+
transform 1s cubic-bezier(0.34, 1.4, 0.64, 1),
|
|
847
|
+
opacity 0.5s ease;
|
|
848
|
+
will-change: transform, opacity;
|
|
849
|
+
}
|
|
850
|
+
#synabun-float.visible { opacity: 1; }
|
|
851
|
+
|
|
852
|
+
.float-inner {
|
|
853
|
+
will-change: transform;
|
|
854
|
+
transform-style: preserve-3d;
|
|
855
|
+
transform: rotateX(18deg);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/* ── Idle animations (all preserve rotateX tilt) ── */
|
|
859
|
+
@keyframes mascot-float {
|
|
860
|
+
0%, 100% { transform: rotateX(18deg) translateY(0) rotateZ(0deg); }
|
|
861
|
+
25% { transform: rotateX(20deg) translateY(-5px) rotateZ(0.5deg); }
|
|
862
|
+
75% { transform: rotateX(16deg) translateY(-2px) rotateZ(-0.5deg); }
|
|
863
|
+
}
|
|
864
|
+
@keyframes mascot-drift-r {
|
|
865
|
+
0%, 100% { transform: rotateX(18deg) translateX(0) translateY(0) rotateZ(0deg); }
|
|
866
|
+
30% { transform: rotateX(22deg) translateX(4px) translateY(-4px) rotateZ(2deg); }
|
|
867
|
+
70% { transform: rotateX(15deg) translateX(-2px) translateY(-2px) rotateZ(-1deg); }
|
|
868
|
+
}
|
|
869
|
+
@keyframes mascot-drift-l {
|
|
870
|
+
0%, 100% { transform: rotateX(18deg) translateX(0) translateY(0) rotateZ(0deg); }
|
|
871
|
+
30% { transform: rotateX(22deg) translateX(-4px) translateY(-4px) rotateZ(-2deg); }
|
|
872
|
+
70% { transform: rotateX(15deg) translateX(2px) translateY(-2px) rotateZ(1deg); }
|
|
873
|
+
}
|
|
874
|
+
@keyframes mascot-nod {
|
|
875
|
+
0%, 100% { transform: rotateX(18deg) translateY(0) scale(1); }
|
|
876
|
+
25% { transform: rotateX(28deg) translateY(4px) scale(1.02, 0.96); }
|
|
877
|
+
50% { transform: rotateX(12deg) translateY(-3px) scale(0.98, 1.04); }
|
|
878
|
+
75% { transform: rotateX(20deg) translateY(1px) scale(1); }
|
|
879
|
+
}
|
|
880
|
+
@keyframes mascot-celebrate {
|
|
881
|
+
0%, 100% { transform: rotateX(18deg) translateY(0) rotateZ(0deg) scale(1); }
|
|
882
|
+
20% { transform: rotateX(10deg) translateY(-8px) rotateZ(-3deg) scale(1.05); }
|
|
883
|
+
40% { transform: rotateX(22deg) translateY(-2px) rotateZ(3deg) scale(1); }
|
|
884
|
+
60% { transform: rotateX(12deg) translateY(-6px) rotateZ(-2deg) scale(1.03); }
|
|
885
|
+
80% { transform: rotateX(20deg) translateY(-1px) rotateZ(1deg) scale(1); }
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
#synabun-float svg {
|
|
889
|
+
filter: drop-shadow(0 6px 24px rgba(255, 255, 255, 0.08));
|
|
890
|
+
}
|
|
891
|
+
</style>
|
|
892
|
+
<script>if('serviceWorker' in navigator)navigator.serviceWorker.register('/sw.js').catch(()=>{})</script>
|
|
893
|
+
</head>
|
|
894
|
+
<body>
|
|
895
|
+
|
|
896
|
+
<canvas id="bgCanvas"></canvas>
|
|
897
|
+
|
|
898
|
+
<a href="https://synabun.ai" target="_blank" rel="noopener" style="position:fixed;top:24px;left:28px;z-index:10;opacity:0.4;transition:opacity 0.2s;" onmouseenter="this.style.opacity='0.7'" onmouseleave="this.style.opacity='0.4'">
|
|
899
|
+
<img src="synabun.png?v=2" alt="SynaBun" style="height:34px;width:auto;">
|
|
900
|
+
</a>
|
|
901
|
+
|
|
902
|
+
<div class="wizard-wrap">
|
|
903
|
+
<div id="synabun-float"><div class="float-inner" id="synabunFloatInner"></div></div>
|
|
904
|
+
<div class="wizard">
|
|
905
|
+
<div class="steps-viewport" id="stepsViewport">
|
|
906
|
+
|
|
907
|
+
<!-- Step 0: Welcome -->
|
|
908
|
+
<div class="step active" data-step="0" style="align-items:center;">
|
|
909
|
+
<div id="synabun-bg"></div>
|
|
910
|
+
<div style="display:flex;flex-direction:column;align-items:center;">
|
|
911
|
+
<div class="typewriter-line" id="typewriter"></div>
|
|
912
|
+
<div class="welcome-desc" id="welcomeDesc">
|
|
913
|
+
Persistent vector memory for your AI coding tools.<br>
|
|
914
|
+
How would you like to get started?
|
|
915
|
+
</div>
|
|
916
|
+
<div class="welcome-choices" id="welcomeChoices">
|
|
917
|
+
<div class="welcome-choice" id="choiceFreshInstall">
|
|
918
|
+
<svg class="welcome-choice-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
919
|
+
<path d="M12 5v14M5 12h14"/>
|
|
920
|
+
</svg>
|
|
921
|
+
<div class="welcome-choice-title">Fresh Install</div>
|
|
922
|
+
<div class="welcome-choice-desc">Set up SynaBun from scratch with the guided wizard</div>
|
|
923
|
+
</div>
|
|
924
|
+
<div class="welcome-choice" id="choiceRestore">
|
|
925
|
+
<svg class="welcome-choice-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
926
|
+
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
|
|
927
|
+
</svg>
|
|
928
|
+
<div class="welcome-choice-title">Restore Backup</div>
|
|
929
|
+
<div class="welcome-choice-desc">Restore from a previous SynaBun backup file</div>
|
|
930
|
+
</div>
|
|
931
|
+
</div>
|
|
932
|
+
<div class="welcome-restore-panel" id="welcomeRestorePanel">
|
|
933
|
+
<div class="restore-dropzone" id="restoreDropzone">
|
|
934
|
+
<div class="restore-dropzone-icon">
|
|
935
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
936
|
+
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
|
|
937
|
+
</svg>
|
|
938
|
+
</div>
|
|
939
|
+
<div class="restore-dropzone-text">Drop your backup .zip here</div>
|
|
940
|
+
<div class="restore-dropzone-hint">or click to browse</div>
|
|
941
|
+
</div>
|
|
942
|
+
<input type="file" id="onboardRestoreFile" accept=".zip" style="display:none">
|
|
943
|
+
<div id="onboardRestoreStatus" style="display:none;margin-top:12px;font-size:12px;align-items:center;gap:8px">
|
|
944
|
+
<div class="wiz-status-dot spin" id="onboardRestoreDot"></div>
|
|
945
|
+
<span id="onboardRestoreText"></span>
|
|
946
|
+
</div>
|
|
947
|
+
<div style="text-align:center">
|
|
948
|
+
<span class="restore-back-link" id="restoreBackLink">← Back to options</span>
|
|
949
|
+
</div>
|
|
950
|
+
</div>
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
<!-- Step 1: Dependencies -->
|
|
955
|
+
<div class="step right" data-step="1">
|
|
956
|
+
<div class="step-eyes"></div>
|
|
957
|
+
<h2>Environment Check</h2>
|
|
958
|
+
<p class="subtitle">Verifying your system has everything SynaBun needs.</p>
|
|
959
|
+
<ul class="dep-list" id="depList"></ul>
|
|
960
|
+
</div>
|
|
961
|
+
|
|
962
|
+
<!-- Step 2: Memory Name & Location -->
|
|
963
|
+
<div class="step right" data-step="2">
|
|
964
|
+
<div class="step-eyes"></div>
|
|
965
|
+
<h2>Memory Name</h2>
|
|
966
|
+
<p class="subtitle">A display name for your memory database.</p>
|
|
967
|
+
<div class="field">
|
|
968
|
+
<label>Name</label>
|
|
969
|
+
<input type="text" id="collectionName" value="Claude Memory" placeholder="Claude Memory">
|
|
970
|
+
<div class="hint">Type any name you like.</div>
|
|
971
|
+
<div class="error-text" id="collectionError"></div>
|
|
972
|
+
</div>
|
|
973
|
+
<div class="field" style="margin-top:18px">
|
|
974
|
+
<label>Database Location</label>
|
|
975
|
+
<div style="display:flex;gap:6px">
|
|
976
|
+
<input type="text" id="dbLocationPath" autocomplete="off" spellcheck="false"
|
|
977
|
+
placeholder="Loading default..." style="flex:1">
|
|
978
|
+
<button class="action-btn" id="dbBrowseBtn" style="padding:8px 14px;font-size:12px;flex:0 0 auto">Browse</button>
|
|
979
|
+
</div>
|
|
980
|
+
<div class="hint">Where to store memory.db. Leave blank to use the default location.</div>
|
|
981
|
+
<div class="error-text" id="dbLocationError"></div>
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
|
|
985
|
+
<!-- Step 3: Install & Build -->
|
|
986
|
+
<div class="step right" data-step="3">
|
|
987
|
+
<div class="step-eyes"></div>
|
|
988
|
+
<h2>Install & Build</h2>
|
|
989
|
+
<p class="subtitle">Install npm dependencies and compile the MCP server.</p>
|
|
990
|
+
<div id="installBuildSteps" style="display:flex;flex-direction:column;gap:8px;margin-bottom:14px">
|
|
991
|
+
<div class="dep-item" id="ibStepNI" style="opacity:1;transform:none">
|
|
992
|
+
<div class="dep-icon" id="ibIconNI" style="background:var(--s-medium);color:var(--t-muted);font-size:12px">1</div>
|
|
993
|
+
<div class="dep-info">
|
|
994
|
+
<div class="dep-name">Neural Interface</div>
|
|
995
|
+
<div class="dep-detail" id="ibDetailNI">npm install</div>
|
|
996
|
+
</div>
|
|
997
|
+
</div>
|
|
998
|
+
<div class="dep-item" id="ibStepMCP" style="opacity:1;transform:none">
|
|
999
|
+
<div class="dep-icon" id="ibIconMCP" style="background:var(--s-medium);color:var(--t-muted);font-size:12px">2</div>
|
|
1000
|
+
<div class="dep-info">
|
|
1001
|
+
<div class="dep-name">MCP Server</div>
|
|
1002
|
+
<div class="dep-detail" id="ibDetailMCP">npm install</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
<div class="dep-item" id="ibStepBuild" style="opacity:1;transform:none">
|
|
1006
|
+
<div class="dep-icon" id="ibIconBuild" style="background:var(--s-medium);color:var(--t-muted);font-size:12px">3</div>
|
|
1007
|
+
<div class="dep-info">
|
|
1008
|
+
<div class="dep-name">Build MCP Server</div>
|
|
1009
|
+
<div class="dep-detail" id="ibDetailBuild">TypeScript → JavaScript</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
</div>
|
|
1013
|
+
<div class="terminal" id="buildOutput" style="display:none"></div>
|
|
1014
|
+
<button class="action-btn" id="buildBtn">Install & Build</button>
|
|
1015
|
+
</div>
|
|
1016
|
+
|
|
1017
|
+
<!-- Step 4: Add Projects -->
|
|
1018
|
+
<div class="step right" data-step="4">
|
|
1019
|
+
<div class="step-eyes"></div>
|
|
1020
|
+
<h2>Add Projects</h2>
|
|
1021
|
+
<p class="subtitle">Register your coding projects so SynaBun can organize memories per-project.</p>
|
|
1022
|
+
<div id="onboardProjectList" style="display:flex;flex-direction:column;gap:6px;margin-bottom:10px">
|
|
1023
|
+
<div id="onboardProjectEmpty" style="text-align:center;padding:16px 10px;color:var(--t-secondary);font-size:13px;background:rgba(255,255,255,0.02);border:1px dashed rgba(255,255,255,0.06);border-radius:8px;line-height:1.6">
|
|
1024
|
+
Each project gets its own memory categories<br>
|
|
1025
|
+
<span style="font-size:11px;opacity:0.6">(architecture, bugs, config, and more)</span>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="error-text" id="onboardProjectError" style="margin-bottom:6px"></div>
|
|
1029
|
+
<button class="action-btn" id="onboardProjectAdd" style="width:100%;padding:12px 14px;font-size:13px;font-weight:500;display:flex;align-items:center;justify-content:center;gap:8px">
|
|
1030
|
+
<svg viewBox="0 0 24 24" style="width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;flex-shrink:0"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
1031
|
+
Browse for Project Folder
|
|
1032
|
+
</button>
|
|
1033
|
+
<div class="hint" style="margin-top:10px;text-align:center;opacity:0.5">You can skip this and add projects later in Settings.</div>
|
|
1034
|
+
</div>
|
|
1035
|
+
|
|
1036
|
+
<!-- Step 5: AI Tool Integration -->
|
|
1037
|
+
<div class="step right" data-step="5">
|
|
1038
|
+
<div style="position:relative;display:flex;justify-content:center;align-items:center;">
|
|
1039
|
+
<div class="step-eyes"></div>
|
|
1040
|
+
<span class="mascot-toast" id="mascotToast"></span>
|
|
1041
|
+
</div>
|
|
1042
|
+
<h2>AI Tool Integration</h2>
|
|
1043
|
+
<p class="subtitle">Select your AI coding tool to get setup instructions.</p>
|
|
1044
|
+
<div class="tool-grid" id="toolGrid"></div>
|
|
1045
|
+
<div id="mcpSetupPanel" style="display:none;">
|
|
1046
|
+
<div id="mcpSetupLabel" style="font-size:12px;color:var(--t-secondary);margin-bottom:6px;"></div>
|
|
1047
|
+
<div class="code-copy-wrap" id="mcpCodeWrap">
|
|
1048
|
+
<div class="instructions-preview" id="mcpSetupCode"></div>
|
|
1049
|
+
<span class="code-copy-label">copy</span>
|
|
1050
|
+
</div>
|
|
1051
|
+
<div id="mcpSetupHint" style="margin-top:6px;font-size:11px;color:var(--t-muted);"></div>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
|
|
1055
|
+
<!-- Step 6: Ruleset -->
|
|
1056
|
+
<div class="step right" data-step="6">
|
|
1057
|
+
<div style="position:relative;display:flex;justify-content:center;align-items:center;">
|
|
1058
|
+
<div class="step-eyes"></div>
|
|
1059
|
+
</div>
|
|
1060
|
+
<h2>Memory Ruleset</h2>
|
|
1061
|
+
<p class="subtitle">Copy this into your project's <code id="rulesetFileName" style="color:var(--t-bright);">CLAUDE.md</code> so your AI tool knows how to use SynaBun.</p>
|
|
1062
|
+
<div id="rulesetPanel">
|
|
1063
|
+
<div class="code-copy-wrap" id="rulesetCodeWrap">
|
|
1064
|
+
<div class="instructions-preview" id="rulesetCode" style="max-height:220px;">Loading...</div>
|
|
1065
|
+
<span class="code-copy-label">copy</span>
|
|
1066
|
+
</div>
|
|
1067
|
+
</div>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<!-- Step 7: Complete -->
|
|
1071
|
+
<div class="step right" data-step="7">
|
|
1072
|
+
<div class="step-eyes"></div>
|
|
1073
|
+
<h2 style="text-align:center;margin-bottom:12px;">Setup Complete!</h2>
|
|
1074
|
+
<ul class="checklist" id="checklist"></ul>
|
|
1075
|
+
<div id="restartNotice" style="margin:16px 0 0;padding:12px 14px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.10);border-radius:var(--r-card);font-size:12px;color:var(--t-primary);line-height:1.5;">
|
|
1076
|
+
<span style="color:var(--t-bright);font-weight:600;">Restart required</span> — Relaunch <span id="restartToolName" style="color:var(--t-bright);font-weight:500;"></span> for the MCP changes to be picked up.
|
|
1077
|
+
</div>
|
|
1078
|
+
<div style="text-align:center;margin-top:16px;">
|
|
1079
|
+
<button class="action-btn" id="launchBtn" style="font-size:15px;padding:12px 32px;">Launch Neural Interface</button>
|
|
1080
|
+
</div>
|
|
1081
|
+
</div>
|
|
1082
|
+
|
|
1083
|
+
</div>
|
|
1084
|
+
|
|
1085
|
+
<div class="nav-bar">
|
|
1086
|
+
<button class="nav-btn" id="prevBtn" disabled>← Back</button>
|
|
1087
|
+
<div class="dots" id="dots"></div>
|
|
1088
|
+
<button class="nav-btn primary" id="nextBtn">Begin →</button>
|
|
1089
|
+
</div>
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<div style="text-align:center;margin-top:16px;display:flex;flex-direction:column;align-items:center;gap:12px;">
|
|
1094
|
+
<div class="social-bar">
|
|
1095
|
+
<a href="https://discord.gg/x6yWqE9GZP" target="_blank" rel="noopener" class="social-icon" title="Discord">
|
|
1096
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
|
1097
|
+
</a>
|
|
1098
|
+
<a href="https://x.com/SynabunAI" target="_blank" rel="noopener" class="social-icon" title="X / Twitter">
|
|
1099
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
|
1100
|
+
</a>
|
|
1101
|
+
<a href="https://github.com/danilokhury/Synabun" target="_blank" rel="noopener" class="social-icon" title="GitHub">
|
|
1102
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
|
1103
|
+
</a>
|
|
1104
|
+
<a href="https://synabun.ai" target="_blank" rel="noopener" class="social-icon" title="Website">
|
|
1105
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
|
1106
|
+
</a>
|
|
1107
|
+
</div>
|
|
1108
|
+
</div>
|
|
1109
|
+
|
|
1110
|
+
<script>
|
|
1111
|
+
// ── Animated mesh background ──
|
|
1112
|
+
(function() {
|
|
1113
|
+
const canvas = document.getElementById('bgCanvas');
|
|
1114
|
+
const ctx = canvas.getContext('2d');
|
|
1115
|
+
let w, h, nodes = [], edges = [];
|
|
1116
|
+
const NODE_COUNT = 140;
|
|
1117
|
+
const EDGE_DIST = 200;
|
|
1118
|
+
|
|
1119
|
+
function resize() {
|
|
1120
|
+
w = canvas.width = window.innerWidth;
|
|
1121
|
+
h = canvas.height = window.innerHeight;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function init() {
|
|
1125
|
+
resize();
|
|
1126
|
+
nodes = [];
|
|
1127
|
+
for (let i = 0; i < NODE_COUNT; i++) {
|
|
1128
|
+
nodes.push({
|
|
1129
|
+
x: Math.random() * w,
|
|
1130
|
+
y: Math.random() * h,
|
|
1131
|
+
vx: (Math.random() - 0.5) * 0.3,
|
|
1132
|
+
vy: (Math.random() - 0.5) * 0.3,
|
|
1133
|
+
r: 1 + Math.random() * 1.5,
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function draw() {
|
|
1139
|
+
ctx.clearRect(0, 0, w, h);
|
|
1140
|
+
|
|
1141
|
+
// Update positions
|
|
1142
|
+
for (const n of nodes) {
|
|
1143
|
+
n.x += n.vx;
|
|
1144
|
+
n.y += n.vy;
|
|
1145
|
+
if (n.x < 0 || n.x > w) n.vx *= -1;
|
|
1146
|
+
if (n.y < 0 || n.y > h) n.vy *= -1;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Draw edges
|
|
1150
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1151
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
1152
|
+
const dx = nodes[i].x - nodes[j].x;
|
|
1153
|
+
const dy = nodes[i].y - nodes[j].y;
|
|
1154
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1155
|
+
if (dist < EDGE_DIST) {
|
|
1156
|
+
const alpha = (1 - dist / EDGE_DIST) * 0.15;
|
|
1157
|
+
ctx.beginPath();
|
|
1158
|
+
ctx.moveTo(nodes[i].x, nodes[i].y);
|
|
1159
|
+
ctx.lineTo(nodes[j].x, nodes[j].y);
|
|
1160
|
+
ctx.strokeStyle = `rgba(255,255,255,${alpha})`;
|
|
1161
|
+
ctx.lineWidth = 0.5;
|
|
1162
|
+
ctx.stroke();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Draw nodes
|
|
1168
|
+
for (const n of nodes) {
|
|
1169
|
+
ctx.beginPath();
|
|
1170
|
+
ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
|
|
1171
|
+
ctx.fillStyle = 'rgba(255,255,255,0.22)';
|
|
1172
|
+
ctx.fill();
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
requestAnimationFrame(draw);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
window.addEventListener('resize', () => { resize(); });
|
|
1179
|
+
init();
|
|
1180
|
+
draw();
|
|
1181
|
+
})();
|
|
1182
|
+
</script>
|
|
1183
|
+
|
|
1184
|
+
<script type="module">
|
|
1185
|
+
|
|
1186
|
+
// ═══════════════════════════════════════════
|
|
1187
|
+
// STATE
|
|
1188
|
+
// ═══════════════════════════════════════════
|
|
1189
|
+
|
|
1190
|
+
const TOTAL_STEPS = 8;
|
|
1191
|
+
let currentStep = 0;
|
|
1192
|
+
|
|
1193
|
+
const wizardState = {
|
|
1194
|
+
collectionName: 'claude_memory',
|
|
1195
|
+
collectionDisplayName: 'Claude Memory',
|
|
1196
|
+
dbLocationPath: null,
|
|
1197
|
+
selectedTool: null,
|
|
1198
|
+
projectRoot: '',
|
|
1199
|
+
buildReady: false,
|
|
1200
|
+
mcpWritten: false,
|
|
1201
|
+
projects: [],
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
const TOOLS = [
|
|
1205
|
+
{ id: 'claude', name: 'Claude Code', instrFile: 'CLAUDE.md' },
|
|
1206
|
+
{ id: 'cursor', name: 'Cursor', instrFile: '.cursorrules' },
|
|
1207
|
+
{ id: 'windsurf', name: 'Windsurf', instrFile: '.windsurfrules' },
|
|
1208
|
+
{ id: 'codex', name: 'Codex', instrFile: 'AGENTS.md' },
|
|
1209
|
+
{ id: 'other', name: 'Other', instrFile: 'AGENTS.md' },
|
|
1210
|
+
];
|
|
1211
|
+
|
|
1212
|
+
// ═══════════════════════════════════════════
|
|
1213
|
+
// DOM REFS
|
|
1214
|
+
// ═══════════════════════════════════════════
|
|
1215
|
+
|
|
1216
|
+
const $steps = document.querySelectorAll('.step');
|
|
1217
|
+
const $viewport = document.getElementById('stepsViewport');
|
|
1218
|
+
const $dots = document.getElementById('dots');
|
|
1219
|
+
const $prevBtn = document.getElementById('prevBtn');
|
|
1220
|
+
const $nextBtn = document.getElementById('nextBtn');
|
|
1221
|
+
|
|
1222
|
+
// ═══════════════════════════════════════════
|
|
1223
|
+
// DOTS
|
|
1224
|
+
// ═══════════════════════════════════════════
|
|
1225
|
+
|
|
1226
|
+
for (let i = 0; i < TOTAL_STEPS; i++) {
|
|
1227
|
+
const d = document.createElement('div');
|
|
1228
|
+
d.className = 'dot' + (i === 0 ? ' active' : '');
|
|
1229
|
+
$dots.appendChild(d);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function updateDots() {
|
|
1233
|
+
$dots.querySelectorAll('.dot').forEach((d, i) => {
|
|
1234
|
+
d.className = 'dot' + (i === currentStep ? ' active' : i < currentStep ? ' done' : '');
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// ═══════════════════════════════════════════
|
|
1239
|
+
// STEP NAVIGATION
|
|
1240
|
+
// ═══════════════════════════════════════════
|
|
1241
|
+
|
|
1242
|
+
function resizeViewport(stepIndex) {
|
|
1243
|
+
// Only resize for the currently active step — ignore stale async callbacks
|
|
1244
|
+
if (stepIndex !== currentStep) return;
|
|
1245
|
+
const step = $steps[stepIndex];
|
|
1246
|
+
if (!step) return;
|
|
1247
|
+
const pb = parseFloat(getComputedStyle(step).paddingBottom) || 36;
|
|
1248
|
+
// Find the last visible child (skip hidden inputs, zero-height elements)
|
|
1249
|
+
let last = null;
|
|
1250
|
+
for (let i = step.children.length - 1; i >= 0; i--) {
|
|
1251
|
+
if (step.children[i].offsetHeight > 0) { last = step.children[i]; break; }
|
|
1252
|
+
}
|
|
1253
|
+
if (!last) { $viewport.style.height = step.scrollHeight + 'px'; return; }
|
|
1254
|
+
// Include last child's margin-bottom so content doesn't touch the edge
|
|
1255
|
+
const lastMb = parseFloat(getComputedStyle(last).marginBottom) || 0;
|
|
1256
|
+
$viewport.style.height = (last.offsetTop + last.offsetHeight + lastMb + pb) + 'px';
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
function goToStep(n) {
|
|
1260
|
+
if (n < 0 || n >= TOTAL_STEPS) return;
|
|
1261
|
+
|
|
1262
|
+
// Mascot: embed eyes in step, or return to welcome
|
|
1263
|
+
if (currentStep > 0 && n === 0) returnMascotToStep0();
|
|
1264
|
+
if (n > 0) positionMascotInStep(n);
|
|
1265
|
+
|
|
1266
|
+
$steps.forEach((s, i) => {
|
|
1267
|
+
if (i === n) {
|
|
1268
|
+
s.className = 'step active';
|
|
1269
|
+
} else if (i < n) {
|
|
1270
|
+
s.className = 'step left';
|
|
1271
|
+
} else {
|
|
1272
|
+
s.className = 'step right';
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
currentStep = n;
|
|
1277
|
+
updateDots();
|
|
1278
|
+
updateNav();
|
|
1279
|
+
// Defer viewport resize to after browser reflow so offsetTop/offsetHeight are accurate
|
|
1280
|
+
requestAnimationFrame(() => {
|
|
1281
|
+
resizeViewport(n);
|
|
1282
|
+
onStepEnter(n);
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function updateNav() {
|
|
1287
|
+
$prevBtn.disabled = currentStep === 0;
|
|
1288
|
+
|
|
1289
|
+
const labels = ['', 'Next', 'Next', 'Next', 'Next', 'Next', 'Next', 'Finish'];
|
|
1290
|
+
$nextBtn.textContent = (labels[currentStep] || 'Next') + ' \u2192';
|
|
1291
|
+
|
|
1292
|
+
if (currentStep === 0 || currentStep === TOTAL_STEPS - 1) {
|
|
1293
|
+
$nextBtn.style.display = 'none';
|
|
1294
|
+
} else {
|
|
1295
|
+
$nextBtn.style.display = '';
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
$prevBtn.addEventListener('click', () => goToStep(currentStep - 1));
|
|
1300
|
+
$nextBtn.addEventListener('click', () => {
|
|
1301
|
+
if (validateStep(currentStep)) {
|
|
1302
|
+
goToStep(currentStep + 1);
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// ═══════════════════════════════════════════
|
|
1307
|
+
// VALIDATION
|
|
1308
|
+
// ═══════════════════════════════════════════
|
|
1309
|
+
|
|
1310
|
+
function validateStep(n) {
|
|
1311
|
+
switch(n) {
|
|
1312
|
+
case 2: {
|
|
1313
|
+
const displayName = document.getElementById('collectionName').value.trim();
|
|
1314
|
+
const err = document.getElementById('collectionError');
|
|
1315
|
+
if (displayName.length < 2) {
|
|
1316
|
+
err.textContent = 'Name must be at least 2 characters.';
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
if (displayName.length > 50) {
|
|
1320
|
+
err.textContent = 'Name must be 50 characters or less.';
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
err.textContent = '';
|
|
1324
|
+
wizardState.collectionDisplayName = displayName;
|
|
1325
|
+
wizardState.collectionName = displayName.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 50);
|
|
1326
|
+
// Capture DB location (empty = use default)
|
|
1327
|
+
const dbPath = document.getElementById('dbLocationPath').value.trim();
|
|
1328
|
+
wizardState.dbLocationPath = dbPath || null;
|
|
1329
|
+
return true;
|
|
1330
|
+
}
|
|
1331
|
+
case 5: {
|
|
1332
|
+
if (!wizardState.selectedTool) return false;
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
default:
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// ═══════════════════════════════════════════
|
|
1341
|
+
// ON STEP ENTER (hooks)
|
|
1342
|
+
// ═══════════════════════════════════════════
|
|
1343
|
+
|
|
1344
|
+
async function onStepEnter(n) {
|
|
1345
|
+
if (n === 0) startMascot();
|
|
1346
|
+
if (n === 1) await onDepsStep();
|
|
1347
|
+
if (n === 2) {
|
|
1348
|
+
// Prefill default DB path
|
|
1349
|
+
const input = document.getElementById('dbLocationPath');
|
|
1350
|
+
if (input && !input.dataset.loaded) {
|
|
1351
|
+
try {
|
|
1352
|
+
const res = await fetch('/api/setup/onboarding');
|
|
1353
|
+
const data = await res.json();
|
|
1354
|
+
if (data.defaultDbDir) input.placeholder = data.defaultDbDir;
|
|
1355
|
+
} catch {}
|
|
1356
|
+
input.dataset.loaded = '1';
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if (n === 3) await onBuildStep();
|
|
1360
|
+
if (n === 6) await onRulesetStep();
|
|
1361
|
+
if (n === 7) await onCompleteStep();
|
|
1362
|
+
// Re-measure after async content loads — but only if user is still on this step
|
|
1363
|
+
if (currentStep === n) resizeViewport(n);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// ═══════════════════════════════════════════
|
|
1367
|
+
// DB LOCATION — Browse button
|
|
1368
|
+
// ═══════════════════════════════════════════
|
|
1369
|
+
|
|
1370
|
+
document.getElementById('dbBrowseBtn').addEventListener('click', async () => {
|
|
1371
|
+
const btn = document.getElementById('dbBrowseBtn');
|
|
1372
|
+
btn.disabled = true;
|
|
1373
|
+
btn.textContent = '...';
|
|
1374
|
+
try {
|
|
1375
|
+
const res = await fetch('/api/browse-folder', {
|
|
1376
|
+
method: 'POST',
|
|
1377
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1378
|
+
body: JSON.stringify({ description: 'Select folder for SynaBun memory database' }),
|
|
1379
|
+
});
|
|
1380
|
+
const data = await res.json();
|
|
1381
|
+
if (data.path) {
|
|
1382
|
+
document.getElementById('dbLocationPath').value = data.path;
|
|
1383
|
+
}
|
|
1384
|
+
} catch {}
|
|
1385
|
+
btn.disabled = false;
|
|
1386
|
+
btn.textContent = 'Browse';
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
// ═══════════════════════════════════════════
|
|
1390
|
+
// SYNABUN — Glowing Pill Eyes (background)
|
|
1391
|
+
// ═══════════════════════════════════════════
|
|
1392
|
+
|
|
1393
|
+
let mascotRunning = false;
|
|
1394
|
+
let mouseX = 0, mouseY = 0;
|
|
1395
|
+
let lookX = 0, lookY = 0;
|
|
1396
|
+
|
|
1397
|
+
document.addEventListener('mousemove', e => { mouseX = e.clientX; mouseY = e.clientY; });
|
|
1398
|
+
|
|
1399
|
+
// ── SynaBun Pill Eyes ──
|
|
1400
|
+
|
|
1401
|
+
function initEyesSVG(containerOrId, w, h) {
|
|
1402
|
+
const $el = typeof containerOrId === 'string'
|
|
1403
|
+
? document.getElementById(containerOrId || 'synabun-bg')
|
|
1404
|
+
: containerOrId;
|
|
1405
|
+
$el.innerHTML = `
|
|
1406
|
+
<svg viewBox="0 0 280 140" width="${w || 280}" height="${h || 140}" style="overflow:visible">
|
|
1407
|
+
<!-- Left eye -->
|
|
1408
|
+
<g class="syna-eye">
|
|
1409
|
+
<g class="syna-lid">
|
|
1410
|
+
<rect x="80" y="24" width="38" height="72" rx="19" fill="#ffffff"/>
|
|
1411
|
+
</g>
|
|
1412
|
+
</g>
|
|
1413
|
+
|
|
1414
|
+
<!-- Right eye -->
|
|
1415
|
+
<g class="syna-eye">
|
|
1416
|
+
<g class="syna-lid">
|
|
1417
|
+
<rect x="162" y="24" width="38" height="72" rx="19" fill="#ffffff"/>
|
|
1418
|
+
</g>
|
|
1419
|
+
</g>
|
|
1420
|
+
|
|
1421
|
+
<!-- Smile -->
|
|
1422
|
+
<path class="syna-mouth" d="M118,116 Q140,130 162,116" fill="none" stroke="rgba(255,255,255,0.25)" stroke-width="2.5" stroke-linecap="round"/>
|
|
1423
|
+
</svg>
|
|
1424
|
+
`;
|
|
1425
|
+
|
|
1426
|
+
return {
|
|
1427
|
+
eyes: $el.querySelectorAll('.syna-eye'),
|
|
1428
|
+
lids: $el.querySelectorAll('.syna-lid'),
|
|
1429
|
+
mouth: $el.querySelector('.syna-mouth')
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function updateEyesSVG(refs, lx, ly, blinkAmt, now) {
|
|
1434
|
+
// Mouse tracking — slight shift on eyes
|
|
1435
|
+
const moveX = lx * 4;
|
|
1436
|
+
const moveY = ly * 3;
|
|
1437
|
+
|
|
1438
|
+
// Blink — scaleY squishes pill
|
|
1439
|
+
const cy = 60;
|
|
1440
|
+
const vScale = Math.max(0.06, 1 - blinkAmt * 0.94);
|
|
1441
|
+
refs.lids.forEach(lid => {
|
|
1442
|
+
lid.setAttribute('transform',
|
|
1443
|
+
`translate(${moveX},${moveY}) translate(0,${cy}) scale(1,${vScale}) translate(0,${-cy})`);
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
// Mouth — follows eyes + subtle wobble
|
|
1447
|
+
const t = (now || 0) * 0.001;
|
|
1448
|
+
const wobbleX = Math.sin(t * 0.7) * 1.2;
|
|
1449
|
+
const wobbleY = Math.cos(t * 1.1) * 0.8;
|
|
1450
|
+
const mouthX = moveX * 0.5 + wobbleX;
|
|
1451
|
+
const mouthY = moveY * 0.5 + wobbleY;
|
|
1452
|
+
refs.mouth.setAttribute('transform', `translate(${mouthX},${mouthY})`);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// --- Animation loop ---
|
|
1456
|
+
|
|
1457
|
+
let activeRefs = null;
|
|
1458
|
+
|
|
1459
|
+
// Mascot is now embedded inside each step's .step-eyes container
|
|
1460
|
+
|
|
1461
|
+
// Place eyes inside the active step's .step-eyes container
|
|
1462
|
+
function positionMascotInStep(step) {
|
|
1463
|
+
const stepEl = $steps[step];
|
|
1464
|
+
if (!stepEl) return;
|
|
1465
|
+
const target = stepEl.querySelector('.step-eyes');
|
|
1466
|
+
if (!target) return;
|
|
1467
|
+
|
|
1468
|
+
// Clear eyes from all other steps
|
|
1469
|
+
document.querySelectorAll('.step-eyes').forEach(el => { el.innerHTML = ''; });
|
|
1470
|
+
|
|
1471
|
+
// Hide the floating mascot
|
|
1472
|
+
const $float = document.getElementById('synabun-float');
|
|
1473
|
+
$float.classList.remove('visible');
|
|
1474
|
+
document.getElementById('synabunFloatInner').innerHTML = '';
|
|
1475
|
+
|
|
1476
|
+
// Inject eyes into the step
|
|
1477
|
+
activeRefs = initEyesSVG(target, 160, 80);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function transitionMascotToFloat() {
|
|
1481
|
+
positionMascotInStep(1);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function returnMascotToStep0() {
|
|
1485
|
+
// Clear step eyes
|
|
1486
|
+
document.querySelectorAll('.step-eyes').forEach(el => { el.innerHTML = ''; });
|
|
1487
|
+
const $float = document.getElementById('synabun-float');
|
|
1488
|
+
$float.classList.remove('visible');
|
|
1489
|
+
document.getElementById('synabunFloatInner').innerHTML = '';
|
|
1490
|
+
activeRefs = initEyesSVG('synabun-bg', 380, 190);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function startMascot() {
|
|
1494
|
+
if (mascotRunning) return;
|
|
1495
|
+
mascotRunning = true;
|
|
1496
|
+
|
|
1497
|
+
activeRefs = initEyesSVG('synabun-bg', 380, 190);
|
|
1498
|
+
|
|
1499
|
+
let blinkState = 'idle';
|
|
1500
|
+
let blinkTimer = 0;
|
|
1501
|
+
let blinkLid = 0;
|
|
1502
|
+
let nextBlink = performance.now() + 3000 + Math.random() * 3000;
|
|
1503
|
+
|
|
1504
|
+
let twDone = false;
|
|
1505
|
+
const twText = "Hi! I'm SynaBun!";
|
|
1506
|
+
let twIndex = 0;
|
|
1507
|
+
let twLast = 0;
|
|
1508
|
+
const $tw = document.getElementById('typewriter');
|
|
1509
|
+
const $desc = document.getElementById('welcomeDesc');
|
|
1510
|
+
|
|
1511
|
+
function render(now) {
|
|
1512
|
+
// Smooth mouse tracking
|
|
1513
|
+
const tX = Math.max(-1, Math.min(1, (mouseX - window.innerWidth / 2) / (window.innerWidth * 0.5)));
|
|
1514
|
+
const tY = Math.max(-1, Math.min(1, (mouseY - window.innerHeight / 2) / (window.innerHeight * 0.5)));
|
|
1515
|
+
lookX += (tX - lookX) * 0.06;
|
|
1516
|
+
lookY += (tY - lookY) * 0.06;
|
|
1517
|
+
|
|
1518
|
+
// Blink state machine
|
|
1519
|
+
if (blinkState === 'idle' && now > nextBlink) { blinkState = 'closing'; blinkTimer = now; }
|
|
1520
|
+
if (blinkState === 'closing') {
|
|
1521
|
+
blinkLid = Math.min(1, (now - blinkTimer) / 90);
|
|
1522
|
+
if (blinkLid >= 1) { blinkState = 'hold'; blinkTimer = now; }
|
|
1523
|
+
}
|
|
1524
|
+
if (blinkState === 'hold' && now - blinkTimer > 60) { blinkState = 'opening'; blinkTimer = now; }
|
|
1525
|
+
if (blinkState === 'opening') {
|
|
1526
|
+
blinkLid = Math.max(0, 1 - (now - blinkTimer) / 130);
|
|
1527
|
+
if (blinkLid <= 0) { blinkState = 'idle'; nextBlink = now + 3000 + Math.random() * 4000; }
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
if (activeRefs) updateEyesSVG(activeRefs, lookX, lookY, blinkLid, now);
|
|
1531
|
+
|
|
1532
|
+
// Typewriter (only on welcome step)
|
|
1533
|
+
if (currentStep === 0 && !twDone) {
|
|
1534
|
+
if (now - twLast > 40 && twIndex <= twText.length) {
|
|
1535
|
+
$tw.textContent = twText.slice(0, twIndex) + (twIndex < twText.length ? '\u2588' : '');
|
|
1536
|
+
twIndex++;
|
|
1537
|
+
twLast = now;
|
|
1538
|
+
if (twIndex > twText.length) {
|
|
1539
|
+
twDone = true;
|
|
1540
|
+
$tw.textContent = twText;
|
|
1541
|
+
setTimeout(() => {
|
|
1542
|
+
$desc.classList.add('visible');
|
|
1543
|
+
setTimeout(() => {
|
|
1544
|
+
const $choices = document.getElementById('welcomeChoices');
|
|
1545
|
+
if ($choices) $choices.classList.add('visible');
|
|
1546
|
+
resizeViewport(0);
|
|
1547
|
+
}, 400);
|
|
1548
|
+
}, 300);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
requestAnimationFrame(render);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
requestAnimationFrame(render);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Happy variant — no-op, completion screen uses the logo image
|
|
1560
|
+
function renderHappyBun() {}
|
|
1561
|
+
|
|
1562
|
+
// ═══════════════════════════════════════════
|
|
1563
|
+
// STEP 0: Welcome Choices (Fresh Install / Restore)
|
|
1564
|
+
// ═══════════════════════════════════════════
|
|
1565
|
+
|
|
1566
|
+
(function initWelcomeChoices() {
|
|
1567
|
+
const $choices = document.getElementById('welcomeChoices');
|
|
1568
|
+
const $restorePanel = document.getElementById('welcomeRestorePanel');
|
|
1569
|
+
const $dropzone = document.getElementById('restoreDropzone');
|
|
1570
|
+
const $fileInput = document.getElementById('onboardRestoreFile');
|
|
1571
|
+
const $statusEl = document.getElementById('onboardRestoreStatus');
|
|
1572
|
+
const $statusDot = document.getElementById('onboardRestoreDot');
|
|
1573
|
+
const $statusText = document.getElementById('onboardRestoreText');
|
|
1574
|
+
const $backLink = document.getElementById('restoreBackLink');
|
|
1575
|
+
|
|
1576
|
+
// Fresh Install → proceed to step 1
|
|
1577
|
+
document.getElementById('choiceFreshInstall').addEventListener('click', () => {
|
|
1578
|
+
goToStep(1);
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
// Restore → show restore panel
|
|
1582
|
+
document.getElementById('choiceRestore').addEventListener('click', () => {
|
|
1583
|
+
$choices.style.display = 'none';
|
|
1584
|
+
$restorePanel.style.display = 'block';
|
|
1585
|
+
requestAnimationFrame(() => {
|
|
1586
|
+
$restorePanel.classList.add('visible');
|
|
1587
|
+
resizeViewport(0);
|
|
1588
|
+
});
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
// Back to options
|
|
1592
|
+
$backLink.addEventListener('click', () => {
|
|
1593
|
+
$restorePanel.classList.remove('visible');
|
|
1594
|
+
$restorePanel.style.display = 'none';
|
|
1595
|
+
$statusEl.style.display = 'none';
|
|
1596
|
+
$choices.style.display = '';
|
|
1597
|
+
resizeViewport(0);
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
// Dropzone click → trigger file input
|
|
1601
|
+
$dropzone.addEventListener('click', () => $fileInput.click());
|
|
1602
|
+
|
|
1603
|
+
// Drag and drop
|
|
1604
|
+
$dropzone.addEventListener('dragover', (e) => { e.preventDefault(); $dropzone.classList.add('dragover'); });
|
|
1605
|
+
$dropzone.addEventListener('dragleave', () => $dropzone.classList.remove('dragover'));
|
|
1606
|
+
$dropzone.addEventListener('drop', (e) => {
|
|
1607
|
+
e.preventDefault();
|
|
1608
|
+
$dropzone.classList.remove('dragover');
|
|
1609
|
+
const file = e.dataTransfer.files[0];
|
|
1610
|
+
if (file && file.name.endsWith('.zip')) handleRestoreFile(file);
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
// File input change
|
|
1614
|
+
$fileInput.addEventListener('change', (e) => {
|
|
1615
|
+
const file = e.target.files[0];
|
|
1616
|
+
if (file) handleRestoreFile(file);
|
|
1617
|
+
e.target.value = '';
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
async function handleRestoreFile(file) {
|
|
1621
|
+
$dropzone.style.display = 'none';
|
|
1622
|
+
$backLink.style.display = 'none';
|
|
1623
|
+
$statusEl.style.display = 'flex';
|
|
1624
|
+
$statusDot.className = 'wiz-status-dot spin';
|
|
1625
|
+
$statusText.textContent = 'Reading backup file...';
|
|
1626
|
+
resizeViewport(0);
|
|
1627
|
+
|
|
1628
|
+
try {
|
|
1629
|
+
const buffer = await file.arrayBuffer();
|
|
1630
|
+
|
|
1631
|
+
$statusText.textContent = 'Validating backup...';
|
|
1632
|
+
const previewRes = await fetch('/api/system/restore/preview', {
|
|
1633
|
+
method: 'POST',
|
|
1634
|
+
headers: { 'Content-Type': 'application/zip' },
|
|
1635
|
+
body: buffer,
|
|
1636
|
+
});
|
|
1637
|
+
if (!previewRes.ok) {
|
|
1638
|
+
let errMsg = 'Invalid backup file';
|
|
1639
|
+
try { const body = await previewRes.json(); errMsg = body.error || errMsg; } catch {}
|
|
1640
|
+
throw new Error(errMsg);
|
|
1641
|
+
}
|
|
1642
|
+
const { manifest: m } = await previewRes.json();
|
|
1643
|
+
|
|
1644
|
+
// Show confirmation
|
|
1645
|
+
$statusEl.style.display = 'none';
|
|
1646
|
+
showRestoreConfirm(m, buffer);
|
|
1647
|
+
|
|
1648
|
+
} catch (err) {
|
|
1649
|
+
$statusDot.className = 'wiz-status-dot red';
|
|
1650
|
+
$statusText.textContent = err.message;
|
|
1651
|
+
$backLink.style.display = '';
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
function showRestoreConfirm(m, buffer) {
|
|
1656
|
+
const fileCount = (m.files || []).length;
|
|
1657
|
+
const hasDb = !!(m.database);
|
|
1658
|
+
|
|
1659
|
+
$restorePanel.innerHTML = `
|
|
1660
|
+
<div style="padding:20px 0;">
|
|
1661
|
+
<h3 style="font-size:16px;font-weight:600;color:var(--t-bright);margin-bottom:4px;">Restore Backup</h3>
|
|
1662
|
+
<p style="font-size:11px;color:var(--t-muted);margin-bottom:14px;">
|
|
1663
|
+
Created: ${new Date(m.created).toLocaleString()} on ${esc(m.hostname || 'unknown')}
|
|
1664
|
+
</p>
|
|
1665
|
+
<div style="font-size:12px;color:var(--t-primary);margin-bottom:6px;">
|
|
1666
|
+
<strong>${fileCount}</strong> config files
|
|
1667
|
+
</div>
|
|
1668
|
+
${hasDb ? `<div style="font-size:12px;color:var(--t-primary);margin-bottom:10px;">
|
|
1669
|
+
<strong>Memory database included</strong>
|
|
1670
|
+
</div>` : ''}
|
|
1671
|
+
<p style="font-size:11px;color:var(--accent-dim);margin-bottom:16px;">
|
|
1672
|
+
This will restore your .env, data files, and memory database.
|
|
1673
|
+
</p>
|
|
1674
|
+
<div style="display:flex;gap:10px;justify-content:center;">
|
|
1675
|
+
<button class="nav-btn" id="onboardRestoreCancel">Cancel</button>
|
|
1676
|
+
<button class="nav-btn primary" id="onboardRestoreConfirm">Restore</button>
|
|
1677
|
+
</div>
|
|
1678
|
+
<div id="onboardRestoreProgress" style="display:none;margin-top:12px;font-size:12px;align-items:center;gap:8px;justify-content:center;">
|
|
1679
|
+
<div class="wiz-status-dot spin" id="onboardRestoreProgressDot"></div>
|
|
1680
|
+
<span id="onboardRestoreProgressText"></span>
|
|
1681
|
+
</div>
|
|
1682
|
+
</div>
|
|
1683
|
+
`;
|
|
1684
|
+
resizeViewport(0);
|
|
1685
|
+
|
|
1686
|
+
document.getElementById('onboardRestoreCancel').addEventListener('click', () => {
|
|
1687
|
+
// Reset to dropzone view
|
|
1688
|
+
resetRestorePanel();
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
document.getElementById('onboardRestoreConfirm').addEventListener('click', async () => {
|
|
1692
|
+
const confirmBtn = document.getElementById('onboardRestoreConfirm');
|
|
1693
|
+
const cancelBtn = document.getElementById('onboardRestoreCancel');
|
|
1694
|
+
const progressEl = document.getElementById('onboardRestoreProgress');
|
|
1695
|
+
const progressDot = document.getElementById('onboardRestoreProgressDot');
|
|
1696
|
+
const progressText = document.getElementById('onboardRestoreProgressText');
|
|
1697
|
+
|
|
1698
|
+
confirmBtn.textContent = 'Restoring...';
|
|
1699
|
+
confirmBtn.disabled = true;
|
|
1700
|
+
cancelBtn.style.display = 'none';
|
|
1701
|
+
progressEl.style.display = 'flex';
|
|
1702
|
+
progressDot.className = 'wiz-status-dot spin';
|
|
1703
|
+
|
|
1704
|
+
try {
|
|
1705
|
+
progressText.textContent = 'Restoring backup...';
|
|
1706
|
+
const restoreRes = await fetch('/api/system/restore', {
|
|
1707
|
+
method: 'POST',
|
|
1708
|
+
headers: { 'Content-Type': 'application/zip' },
|
|
1709
|
+
body: buffer,
|
|
1710
|
+
});
|
|
1711
|
+
if (!restoreRes.ok) {
|
|
1712
|
+
let errMsg = 'Restore failed';
|
|
1713
|
+
try { const body = await restoreRes.json(); errMsg = body.error || errMsg; } catch {}
|
|
1714
|
+
throw new Error(errMsg);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
progressDot.className = 'wiz-status-dot green';
|
|
1718
|
+
progressText.textContent = 'Restore complete! Launching...';
|
|
1719
|
+
confirmBtn.style.display = 'none';
|
|
1720
|
+
|
|
1721
|
+
// Mark setup as complete and launch
|
|
1722
|
+
try { await fetch('/api/setup/complete', { method: 'POST' }); } catch {}
|
|
1723
|
+
setTimeout(() => { window.location.href = '/'; }, 1500);
|
|
1724
|
+
|
|
1725
|
+
} catch (err) {
|
|
1726
|
+
progressDot.className = 'wiz-status-dot red';
|
|
1727
|
+
progressText.textContent = err.message;
|
|
1728
|
+
confirmBtn.textContent = 'Restore';
|
|
1729
|
+
confirmBtn.disabled = false;
|
|
1730
|
+
cancelBtn.style.display = '';
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function resetRestorePanel() {
|
|
1736
|
+
$restorePanel.classList.remove('visible');
|
|
1737
|
+
$restorePanel.style.display = 'none';
|
|
1738
|
+
// Rebuild the original restore panel content
|
|
1739
|
+
$restorePanel.innerHTML = `
|
|
1740
|
+
<div class="restore-dropzone" id="restoreDropzone">
|
|
1741
|
+
<div class="restore-dropzone-icon">
|
|
1742
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1743
|
+
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
|
|
1744
|
+
</svg>
|
|
1745
|
+
</div>
|
|
1746
|
+
<div class="restore-dropzone-text">Drop your backup .zip here</div>
|
|
1747
|
+
<div class="restore-dropzone-hint">or click to browse</div>
|
|
1748
|
+
</div>
|
|
1749
|
+
<input type="file" id="onboardRestoreFile" accept=".zip" style="display:none">
|
|
1750
|
+
<div id="onboardRestoreStatus" style="display:none;margin-top:12px;font-size:12px;align-items:center;gap:8px">
|
|
1751
|
+
<div class="wiz-status-dot spin" id="onboardRestoreDot"></div>
|
|
1752
|
+
<span id="onboardRestoreText"></span>
|
|
1753
|
+
</div>
|
|
1754
|
+
<div style="text-align:center">
|
|
1755
|
+
<span class="restore-back-link" id="restoreBackLink">← Back to options</span>
|
|
1756
|
+
</div>
|
|
1757
|
+
`;
|
|
1758
|
+
// Show the choices again
|
|
1759
|
+
$choices.style.display = '';
|
|
1760
|
+
resizeViewport(0);
|
|
1761
|
+
|
|
1762
|
+
// Re-wire dropzone events on the new DOM
|
|
1763
|
+
rewireDropzone();
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function rewireDropzone() {
|
|
1767
|
+
const dz = document.getElementById('restoreDropzone');
|
|
1768
|
+
const fi = document.getElementById('onboardRestoreFile');
|
|
1769
|
+
const bl = document.getElementById('restoreBackLink');
|
|
1770
|
+
|
|
1771
|
+
dz.addEventListener('click', () => fi.click());
|
|
1772
|
+
dz.addEventListener('dragover', (e) => { e.preventDefault(); dz.classList.add('dragover'); });
|
|
1773
|
+
dz.addEventListener('dragleave', () => dz.classList.remove('dragover'));
|
|
1774
|
+
dz.addEventListener('drop', (e) => {
|
|
1775
|
+
e.preventDefault();
|
|
1776
|
+
dz.classList.remove('dragover');
|
|
1777
|
+
const file = e.dataTransfer.files[0];
|
|
1778
|
+
if (file && file.name.endsWith('.zip')) handleRestoreFile(file);
|
|
1779
|
+
});
|
|
1780
|
+
fi.addEventListener('change', (e) => {
|
|
1781
|
+
const file = e.target.files[0];
|
|
1782
|
+
if (file) handleRestoreFile(file);
|
|
1783
|
+
e.target.value = '';
|
|
1784
|
+
});
|
|
1785
|
+
bl.addEventListener('click', () => {
|
|
1786
|
+
const rp = document.getElementById('welcomeRestorePanel');
|
|
1787
|
+
rp.classList.remove('visible');
|
|
1788
|
+
rp.style.display = 'none';
|
|
1789
|
+
document.getElementById('onboardRestoreStatus').style.display = 'none';
|
|
1790
|
+
$choices.style.display = '';
|
|
1791
|
+
resizeViewport(0);
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
1796
|
+
})();
|
|
1797
|
+
|
|
1798
|
+
// ═══════════════════════════════════════════
|
|
1799
|
+
// STEP 1: Dependencies Check
|
|
1800
|
+
// ═══════════════════════════════════════════
|
|
1801
|
+
|
|
1802
|
+
async function onDepsStep() {
|
|
1803
|
+
const $list = document.getElementById('depList');
|
|
1804
|
+
$list.innerHTML = '';
|
|
1805
|
+
|
|
1806
|
+
let deps;
|
|
1807
|
+
try {
|
|
1808
|
+
const res = await fetch('/api/setup/check-deps');
|
|
1809
|
+
const data = await res.json();
|
|
1810
|
+
deps = data.deps;
|
|
1811
|
+
} catch {
|
|
1812
|
+
$list.innerHTML = '<li style="color:var(--t-muted);font-size:13px;">Could not reach server.</li>';
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// Build all items hidden first
|
|
1817
|
+
const items = deps.map(dep => {
|
|
1818
|
+
const li = document.createElement('li');
|
|
1819
|
+
li.className = 'dep-item checking';
|
|
1820
|
+
li.innerHTML = `
|
|
1821
|
+
<div class="dep-icon"></div>
|
|
1822
|
+
<div class="dep-info">
|
|
1823
|
+
<div class="dep-name">${dep.name}</div>
|
|
1824
|
+
<div class="dep-detail">Checking\u2026</div>
|
|
1825
|
+
</div>`;
|
|
1826
|
+
$list.appendChild(li);
|
|
1827
|
+
return { li, dep };
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
// Pre-size viewport to account for all items before animation
|
|
1831
|
+
resizeViewport(1);
|
|
1832
|
+
|
|
1833
|
+
// Staggered reveal + resolve
|
|
1834
|
+
for (let i = 0; i < items.length; i++) {
|
|
1835
|
+
const { li, dep } = items[i];
|
|
1836
|
+
|
|
1837
|
+
// Reveal with stagger
|
|
1838
|
+
await wait(250);
|
|
1839
|
+
li.classList.add('visible');
|
|
1840
|
+
|
|
1841
|
+
// Simulate a brief check delay, then resolve
|
|
1842
|
+
await wait(400 + Math.random() * 200);
|
|
1843
|
+
|
|
1844
|
+
li.classList.remove('checking');
|
|
1845
|
+
const icon = li.querySelector('.dep-icon');
|
|
1846
|
+
const detail = li.querySelector('.dep-detail');
|
|
1847
|
+
|
|
1848
|
+
if (dep.ok) {
|
|
1849
|
+
li.classList.add('ok');
|
|
1850
|
+
icon.textContent = '\u2713';
|
|
1851
|
+
detail.textContent = dep.detail;
|
|
1852
|
+
} else if (dep.warn) {
|
|
1853
|
+
li.classList.add('warn');
|
|
1854
|
+
icon.textContent = '!';
|
|
1855
|
+
detail.innerHTML = `${dep.detail} — <a href="${dep.url}" target="_blank" rel="noopener">Install \u2197</a>`;
|
|
1856
|
+
} else {
|
|
1857
|
+
li.classList.add('fail');
|
|
1858
|
+
icon.textContent = '\u2717';
|
|
1859
|
+
detail.innerHTML = `${dep.detail} — <a href="${dep.url}" target="_blank" rel="noopener">Download \u2197</a>`;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function wait(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
1865
|
+
|
|
1866
|
+
// ═══════════════════════════════════════════
|
|
1867
|
+
// STEP 3: Install & Build
|
|
1868
|
+
// ═══════════════════════════════════════════
|
|
1869
|
+
|
|
1870
|
+
const IB_STEPS = {
|
|
1871
|
+
ni: { el: 'ibStepNI', icon: 'ibIconNI', detail: 'ibDetailNI', dirName: 'Neural Interface' },
|
|
1872
|
+
mcp: { el: 'ibStepMCP', icon: 'ibIconMCP', detail: 'ibDetailMCP', dirName: 'MCP Server' },
|
|
1873
|
+
build: { el: 'ibStepBuild', icon: 'ibIconBuild', detail: 'ibDetailBuild', dirName: 'Build MCP Server' },
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
function ibSetStatus(key, status, detail) {
|
|
1877
|
+
const s = IB_STEPS[key];
|
|
1878
|
+
const $el = document.getElementById(s.el);
|
|
1879
|
+
const $icon = document.getElementById(s.icon);
|
|
1880
|
+
const $detail = document.getElementById(s.detail);
|
|
1881
|
+
|
|
1882
|
+
$el.classList.remove('checking', 'ok', 'warn', 'fail');
|
|
1883
|
+
|
|
1884
|
+
if (status === 'checking') {
|
|
1885
|
+
$el.classList.add('checking');
|
|
1886
|
+
$icon.textContent = '';
|
|
1887
|
+
$detail.textContent = detail || 'Installing...';
|
|
1888
|
+
} else if (status === 'ok') {
|
|
1889
|
+
$el.classList.add('ok');
|
|
1890
|
+
$icon.textContent = '\u2713';
|
|
1891
|
+
$detail.textContent = detail || 'Done';
|
|
1892
|
+
} else if (status === 'skipped') {
|
|
1893
|
+
$el.classList.add('ok');
|
|
1894
|
+
$icon.textContent = '\u2713';
|
|
1895
|
+
$detail.textContent = detail || 'Already installed';
|
|
1896
|
+
} else if (status === 'fail') {
|
|
1897
|
+
$el.classList.add('fail');
|
|
1898
|
+
$icon.textContent = '\u2717';
|
|
1899
|
+
$detail.textContent = detail || 'Failed';
|
|
1900
|
+
} else {
|
|
1901
|
+
// pending / default
|
|
1902
|
+
$icon.style.background = 'var(--s-medium)';
|
|
1903
|
+
$icon.style.color = 'var(--t-muted)';
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
async function onBuildStep() {
|
|
1908
|
+
// Save collection name config
|
|
1909
|
+
try {
|
|
1910
|
+
await fetch('/api/setup/save-config', {
|
|
1911
|
+
method: 'POST',
|
|
1912
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1913
|
+
body: JSON.stringify({
|
|
1914
|
+
collectionName: wizardState.collectionName,
|
|
1915
|
+
collectionDisplayName: wizardState.collectionDisplayName,
|
|
1916
|
+
dbLocationPath: wizardState.dbLocationPath || null,
|
|
1917
|
+
}),
|
|
1918
|
+
});
|
|
1919
|
+
} catch {}
|
|
1920
|
+
|
|
1921
|
+
const $btn = document.getElementById('buildBtn');
|
|
1922
|
+
|
|
1923
|
+
// Check if already built — if so, mark all steps done
|
|
1924
|
+
try {
|
|
1925
|
+
const res = await fetch('/api/setup/onboarding');
|
|
1926
|
+
const data = await res.json();
|
|
1927
|
+
if (data.mcpBuilt) {
|
|
1928
|
+
wizardState.buildReady = true;
|
|
1929
|
+
ibSetStatus('ni', 'skipped', 'Dependencies ready');
|
|
1930
|
+
ibSetStatus('mcp', 'skipped', 'Dependencies ready');
|
|
1931
|
+
ibSetStatus('build', 'skipped', 'dist/index.js found — already compiled');
|
|
1932
|
+
$btn.textContent = 'Already Done';
|
|
1933
|
+
$btn.classList.add('success');
|
|
1934
|
+
$btn.disabled = true;
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
} catch {}
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
document.getElementById('buildBtn').addEventListener('click', async () => {
|
|
1941
|
+
const $btn = document.getElementById('buildBtn');
|
|
1942
|
+
const $out = document.getElementById('buildOutput');
|
|
1943
|
+
|
|
1944
|
+
$btn.disabled = true;
|
|
1945
|
+
$btn.innerHTML = '<div class="spinner"></div> Installing...';
|
|
1946
|
+
$out.style.display = 'none';
|
|
1947
|
+
$out.textContent = '';
|
|
1948
|
+
|
|
1949
|
+
// Map server dir names to our step keys
|
|
1950
|
+
const dirKeyMap = { 'Neural Interface': 'ni', 'MCP Server': 'mcp' };
|
|
1951
|
+
|
|
1952
|
+
try {
|
|
1953
|
+
const res = await fetch('/api/setup/build', { method: 'POST' });
|
|
1954
|
+
const reader = res.body.getReader();
|
|
1955
|
+
const decoder = new TextDecoder();
|
|
1956
|
+
let buffer = '';
|
|
1957
|
+
|
|
1958
|
+
while (true) {
|
|
1959
|
+
const { done, value } = await reader.read();
|
|
1960
|
+
if (done) break;
|
|
1961
|
+
|
|
1962
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1963
|
+
|
|
1964
|
+
// Parse SSE events from the buffer
|
|
1965
|
+
let idx;
|
|
1966
|
+
while ((idx = buffer.indexOf('\n\n')) !== -1) {
|
|
1967
|
+
const block = buffer.slice(0, idx);
|
|
1968
|
+
buffer = buffer.slice(idx + 2);
|
|
1969
|
+
|
|
1970
|
+
let event = '', data = '';
|
|
1971
|
+
for (const line of block.split('\n')) {
|
|
1972
|
+
if (line.startsWith('event: ')) event = line.slice(7);
|
|
1973
|
+
else if (line.startsWith('data: ')) data = line.slice(6);
|
|
1974
|
+
}
|
|
1975
|
+
if (!event || !data) continue;
|
|
1976
|
+
|
|
1977
|
+
let payload;
|
|
1978
|
+
try { payload = JSON.parse(data); } catch { continue; }
|
|
1979
|
+
|
|
1980
|
+
if (event === 'install') {
|
|
1981
|
+
const key = dirKeyMap[payload.dir];
|
|
1982
|
+
if (!key) continue;
|
|
1983
|
+
if (payload.status === 'start') {
|
|
1984
|
+
ibSetStatus(key, 'checking', 'Installing dependencies...');
|
|
1985
|
+
} else if (payload.status === 'done') {
|
|
1986
|
+
ibSetStatus(key, 'ok', 'Installed');
|
|
1987
|
+
} else if (payload.status === 'skipped') {
|
|
1988
|
+
ibSetStatus(key, 'skipped', 'Already installed');
|
|
1989
|
+
} else if (payload.status === 'error') {
|
|
1990
|
+
ibSetStatus(key, 'fail', 'Install failed');
|
|
1991
|
+
$out.style.display = '';
|
|
1992
|
+
$out.textContent += payload.output || '';
|
|
1993
|
+
}
|
|
1994
|
+
} else if (event === 'build') {
|
|
1995
|
+
if (payload.status === 'start') {
|
|
1996
|
+
$btn.innerHTML = '<div class="spinner"></div> Building...';
|
|
1997
|
+
ibSetStatus('build', 'checking', 'Compiling TypeScript...');
|
|
1998
|
+
} else if (payload.status === 'done') {
|
|
1999
|
+
ibSetStatus('build', 'ok', 'Build successful');
|
|
2000
|
+
} else if (payload.status === 'skipped') {
|
|
2001
|
+
ibSetStatus('build', 'skipped', 'Already up to date');
|
|
2002
|
+
} else if (payload.status === 'error') {
|
|
2003
|
+
ibSetStatus('build', 'fail', 'Build failed');
|
|
2004
|
+
$out.style.display = '';
|
|
2005
|
+
$out.textContent += payload.output || '';
|
|
2006
|
+
}
|
|
2007
|
+
} else if (event === 'complete') {
|
|
2008
|
+
if (payload.ok) {
|
|
2009
|
+
wizardState.buildReady = true;
|
|
2010
|
+
$btn.textContent = 'Done';
|
|
2011
|
+
$btn.classList.add('success');
|
|
2012
|
+
} else {
|
|
2013
|
+
$btn.textContent = 'Retry';
|
|
2014
|
+
$btn.disabled = false;
|
|
2015
|
+
$btn.classList.remove('success');
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
resizeViewport(currentStep);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
} catch (err) {
|
|
2023
|
+
$out.style.display = '';
|
|
2024
|
+
$out.textContent += '\nError: ' + err.message;
|
|
2025
|
+
$btn.textContent = 'Retry';
|
|
2026
|
+
$btn.disabled = false;
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
// ═══════════════════════════════════════════
|
|
2031
|
+
// STEP 4: Add Projects
|
|
2032
|
+
// ═══════════════════════════════════════════
|
|
2033
|
+
|
|
2034
|
+
{
|
|
2035
|
+
const $list = document.getElementById('onboardProjectList');
|
|
2036
|
+
const $empty = document.getElementById('onboardProjectEmpty');
|
|
2037
|
+
const $addBtn = document.getElementById('onboardProjectAdd');
|
|
2038
|
+
const $error = document.getElementById('onboardProjectError');
|
|
2039
|
+
|
|
2040
|
+
function renderProjectList() {
|
|
2041
|
+
$list.querySelectorAll('.onboard-project-card').forEach(c => c.remove());
|
|
2042
|
+
if (wizardState.projects.length === 0) {
|
|
2043
|
+
$empty.style.display = '';
|
|
2044
|
+
} else {
|
|
2045
|
+
$empty.style.display = 'none';
|
|
2046
|
+
wizardState.projects.forEach((p, i) => {
|
|
2047
|
+
const card = document.createElement('div');
|
|
2048
|
+
card.className = 'onboard-project-card';
|
|
2049
|
+
card.style.cssText = 'display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(109,213,140,0.06);border:1px solid rgba(109,213,140,0.2);border-radius:8px;font-size:12px';
|
|
2050
|
+
card.innerHTML = `
|
|
2051
|
+
<svg viewBox="0 0 24 24" style="width:18px;height:18px;fill:none;stroke:rgba(109,213,140,0.8);stroke-width:2;flex-shrink:0"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
2052
|
+
<div style="flex:1;min-width:0">
|
|
2053
|
+
<div style="color:var(--t-bright);font-weight:500">${p.label}</div>
|
|
2054
|
+
<div style="color:var(--t-muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${p.path.replace(/\\/g, '/')}">${p.path.replace(/\\/g, '/')}</div>
|
|
2055
|
+
</div>
|
|
2056
|
+
<button class="onboard-project-remove" style="background:none;border:none;color:var(--t-muted);cursor:pointer;font-size:16px;padding:2px 6px;line-height:1;opacity:0.5" title="Remove">×</button>
|
|
2057
|
+
`;
|
|
2058
|
+
card.querySelector('.onboard-project-remove').addEventListener('click', () => {
|
|
2059
|
+
wizardState.projects.splice(i, 1);
|
|
2060
|
+
renderProjectList();
|
|
2061
|
+
resizeViewport(currentStep);
|
|
2062
|
+
});
|
|
2063
|
+
$list.appendChild(card);
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
resizeViewport(currentStep);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// Single-click: browse → auto-label → register → done
|
|
2070
|
+
$addBtn.addEventListener('click', async () => {
|
|
2071
|
+
$error.textContent = '';
|
|
2072
|
+
$addBtn.disabled = true;
|
|
2073
|
+
const origHTML = $addBtn.innerHTML;
|
|
2074
|
+
$addBtn.innerHTML = '<div class="spinner" style="width:14px;height:14px"></div> Waiting for folder picker...';
|
|
2075
|
+
|
|
2076
|
+
let folderPath;
|
|
2077
|
+
try {
|
|
2078
|
+
const res = await fetch('/api/browse-folder', {
|
|
2079
|
+
method: 'POST',
|
|
2080
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2081
|
+
body: JSON.stringify({ description: 'Select project folder' }),
|
|
2082
|
+
});
|
|
2083
|
+
const data = await res.json();
|
|
2084
|
+
folderPath = data.path;
|
|
2085
|
+
} catch {}
|
|
2086
|
+
|
|
2087
|
+
if (!folderPath) {
|
|
2088
|
+
$addBtn.disabled = false;
|
|
2089
|
+
$addBtn.innerHTML = origHTML;
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// Deduplicate
|
|
2094
|
+
const normalized = folderPath.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
2095
|
+
if (wizardState.projects.some(p => p.path.replace(/\\/g, '/').replace(/\/+$/, '') === normalized)) {
|
|
2096
|
+
$error.textContent = 'This project is already added.';
|
|
2097
|
+
$addBtn.disabled = false;
|
|
2098
|
+
$addBtn.innerHTML = origHTML;
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// Auto-label from folder name
|
|
2103
|
+
const label = folderPath.replace(/\\/g, '/').split('/').filter(Boolean).pop() || 'Project';
|
|
2104
|
+
|
|
2105
|
+
// Register via API
|
|
2106
|
+
$addBtn.innerHTML = '<div class="spinner" style="width:14px;height:14px"></div> Registering...';
|
|
2107
|
+
try {
|
|
2108
|
+
const res = await fetch('/api/claude-code/integrations', {
|
|
2109
|
+
method: 'POST',
|
|
2110
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2111
|
+
body: JSON.stringify({ target: 'project', projectPath: folderPath, label }),
|
|
2112
|
+
});
|
|
2113
|
+
const data = await res.json();
|
|
2114
|
+
if (!data.ok) {
|
|
2115
|
+
$error.textContent = data.error || 'Failed to register project.';
|
|
2116
|
+
$addBtn.disabled = false;
|
|
2117
|
+
$addBtn.innerHTML = origHTML;
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
} catch (err) {
|
|
2121
|
+
$error.textContent = 'Failed: ' + err.message;
|
|
2122
|
+
$addBtn.disabled = false;
|
|
2123
|
+
$addBtn.innerHTML = origHTML;
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
wizardState.projects.push({ path: folderPath, label });
|
|
2128
|
+
$addBtn.disabled = false;
|
|
2129
|
+
$addBtn.innerHTML = origHTML;
|
|
2130
|
+
renderProjectList();
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// ═══════════════════════════════════════════
|
|
2135
|
+
// STEP 5: Tool Selection
|
|
2136
|
+
// ═══════════════════════════════════════════
|
|
2137
|
+
|
|
2138
|
+
const $toolGrid = document.getElementById('toolGrid');
|
|
2139
|
+
TOOLS.forEach(t => {
|
|
2140
|
+
const card = document.createElement('div');
|
|
2141
|
+
card.className = 'tool-card';
|
|
2142
|
+
card.dataset.id = t.id;
|
|
2143
|
+
card.innerHTML = `<div class="tname">${t.name}</div>`;
|
|
2144
|
+
card.addEventListener('click', () => selectTool(t.id));
|
|
2145
|
+
$toolGrid.appendChild(card);
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
function getMcpSetup(id) {
|
|
2149
|
+
const root = (wizardState.projectRoot || '').replace(/\\/g, '/');
|
|
2150
|
+
const mcpPath = root + '/mcp-server/run.mjs';
|
|
2151
|
+
const envPath = root + '/.env';
|
|
2152
|
+
|
|
2153
|
+
if (id === 'claude') {
|
|
2154
|
+
return {
|
|
2155
|
+
label: 'Run this in your terminal to register SynaBun globally:',
|
|
2156
|
+
code: `claude mcp add SynaBun -s user -e DOTENV_PATH="${envPath}" -- node "${mcpPath}"`,
|
|
2157
|
+
hint: 'Registers SynaBun globally. Restart Claude Code after running.',
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
if (id === 'cursor') {
|
|
2161
|
+
const json = JSON.stringify({ mcpServers: { SynaBun: { command: 'node', args: [mcpPath], env: { DOTENV_PATH: envPath } } } }, null, 2);
|
|
2162
|
+
return {
|
|
2163
|
+
label: 'Add to Cursor Settings > MCP Servers, or paste into .cursor/mcp.json:',
|
|
2164
|
+
code: json,
|
|
2165
|
+
hint: 'Settings > MCP > Add Server, or save as .cursor/mcp.json',
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
if (id === 'windsurf') {
|
|
2169
|
+
const json = JSON.stringify({ mcpServers: { SynaBun: { command: 'node', args: [mcpPath], env: { DOTENV_PATH: envPath } } } }, null, 2);
|
|
2170
|
+
return {
|
|
2171
|
+
label: 'Add to ~/.codeium/windsurf/mcp_config.json:',
|
|
2172
|
+
code: json,
|
|
2173
|
+
hint: 'Cascade sidebar > hammer icon > Configure',
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
// codex, other
|
|
2177
|
+
const json = JSON.stringify({ mcpServers: { SynaBun: { command: 'node', args: [mcpPath], env: { DOTENV_PATH: envPath } } } }, null, 2);
|
|
2178
|
+
return {
|
|
2179
|
+
label: 'Add to your project\'s .mcp.json:',
|
|
2180
|
+
code: json,
|
|
2181
|
+
hint: 'Save as .mcp.json in your project root.',
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function selectTool(id) {
|
|
2186
|
+
wizardState.selectedTool = id;
|
|
2187
|
+
$toolGrid.querySelectorAll('.tool-card').forEach(c => {
|
|
2188
|
+
c.classList.toggle('selected', c.dataset.id === id);
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
const setup = getMcpSetup(id);
|
|
2192
|
+
document.getElementById('mcpSetupLabel').textContent = setup.label;
|
|
2193
|
+
document.getElementById('mcpSetupCode').textContent = setup.code;
|
|
2194
|
+
document.getElementById('mcpSetupHint').textContent = setup.hint;
|
|
2195
|
+
document.getElementById('mcpSetupPanel').style.display = '';
|
|
2196
|
+
|
|
2197
|
+
requestAnimationFrame(() => resizeViewport(currentStep));
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
function showMascotToast(msg, duration = 2000) {
|
|
2201
|
+
const toast = document.getElementById('mascotToast');
|
|
2202
|
+
if (!toast) return;
|
|
2203
|
+
toast.textContent = msg;
|
|
2204
|
+
toast.classList.add('show');
|
|
2205
|
+
setTimeout(() => toast.classList.remove('show'), duration);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
document.getElementById('mcpCodeWrap').addEventListener('click', () => {
|
|
2209
|
+
const code = document.getElementById('mcpSetupCode').textContent;
|
|
2210
|
+
navigator.clipboard.writeText(code);
|
|
2211
|
+
wizardState.mcpWritten = true;
|
|
2212
|
+
const label = document.querySelector('#mcpCodeWrap .code-copy-label');
|
|
2213
|
+
label.textContent = 'copied!';
|
|
2214
|
+
label.style.opacity = '1';
|
|
2215
|
+
showMascotToast('Copied to clipboard!');
|
|
2216
|
+
setTimeout(() => { label.textContent = 'copy'; label.style.opacity = ''; }, 2000);
|
|
2217
|
+
});
|
|
2218
|
+
|
|
2219
|
+
// ═══════════════════════════════════════════
|
|
2220
|
+
// STEP 6: Ruleset
|
|
2221
|
+
// ═══════════════════════════════════════════
|
|
2222
|
+
|
|
2223
|
+
async function onRulesetStep() {
|
|
2224
|
+
const tool = TOOLS.find(t => t.id === wizardState.selectedTool) || TOOLS[0];
|
|
2225
|
+
document.getElementById('rulesetFileName').textContent = tool.instrFile;
|
|
2226
|
+
|
|
2227
|
+
// Map tool to API format
|
|
2228
|
+
const formatMap = { claude: 'claude', cursor: 'cursor', windsurf: 'generic', codex: 'codex', other: 'generic' };
|
|
2229
|
+
const format = formatMap[tool.id] || 'generic';
|
|
2230
|
+
|
|
2231
|
+
const $code = document.getElementById('rulesetCode');
|
|
2232
|
+
if ($code.dataset.loadedFormat === format) return; // already loaded
|
|
2233
|
+
|
|
2234
|
+
$code.textContent = 'Loading...';
|
|
2235
|
+
try {
|
|
2236
|
+
const res = await fetch(`/api/claude-code/ruleset?format=${format}`);
|
|
2237
|
+
const data = await res.json();
|
|
2238
|
+
if (data.ok && data.ruleset) {
|
|
2239
|
+
$code.textContent = data.ruleset;
|
|
2240
|
+
$code.dataset.loadedFormat = format;
|
|
2241
|
+
wizardState.rulesetCopied = false;
|
|
2242
|
+
} else {
|
|
2243
|
+
$code.textContent = 'Could not load ruleset.';
|
|
2244
|
+
}
|
|
2245
|
+
} catch {
|
|
2246
|
+
$code.textContent = 'Failed to load ruleset.';
|
|
2247
|
+
}
|
|
2248
|
+
if (currentStep === 6) resizeViewport(6);
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
document.getElementById('rulesetCodeWrap').addEventListener('click', () => {
|
|
2252
|
+
const code = document.getElementById('rulesetCode').textContent;
|
|
2253
|
+
if (code === 'Loading...' || code.startsWith('Could not') || code.startsWith('Failed')) return;
|
|
2254
|
+
navigator.clipboard.writeText(code);
|
|
2255
|
+
wizardState.rulesetCopied = true;
|
|
2256
|
+
const label = document.querySelector('#rulesetCodeWrap .code-copy-label');
|
|
2257
|
+
label.textContent = 'copied!';
|
|
2258
|
+
label.style.opacity = '1';
|
|
2259
|
+
setTimeout(() => { label.textContent = 'copy'; label.style.opacity = ''; }, 2000);
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
// ═══════════════════════════════════════════
|
|
2263
|
+
// STEP 7: Complete
|
|
2264
|
+
// ═══════════════════════════════════════════
|
|
2265
|
+
|
|
2266
|
+
async function onCompleteStep() {
|
|
2267
|
+
renderHappyBun();
|
|
2268
|
+
|
|
2269
|
+
const tool = TOOLS.find(t => t.id === wizardState.selectedTool) || TOOLS[0];
|
|
2270
|
+
document.getElementById('restartToolName').textContent = tool.name;
|
|
2271
|
+
|
|
2272
|
+
// Auto-register hooks globally for Claude Code users
|
|
2273
|
+
if (tool.id === 'claude') {
|
|
2274
|
+
try {
|
|
2275
|
+
await fetch('/api/claude-code/integrations', {
|
|
2276
|
+
method: 'POST',
|
|
2277
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2278
|
+
body: JSON.stringify({ target: 'global' }),
|
|
2279
|
+
});
|
|
2280
|
+
wizardState.hooksRegistered = true;
|
|
2281
|
+
} catch { /* non-critical — user can enable via Settings later */ }
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
const items = [
|
|
2285
|
+
{ label: 'Memory name set', done: !!wizardState.collectionName },
|
|
2286
|
+
{ label: 'Dependencies installed & MCP built', done: wizardState.buildReady },
|
|
2287
|
+
{ label: 'Projects registered', done: (wizardState.projects || []).length > 0 },
|
|
2288
|
+
{ label: 'MCP config copied', done: wizardState.mcpWritten },
|
|
2289
|
+
{ label: 'Ruleset copied', done: !!wizardState.rulesetCopied },
|
|
2290
|
+
];
|
|
2291
|
+
|
|
2292
|
+
const $list = document.getElementById('checklist');
|
|
2293
|
+
$list.innerHTML = items.map(i => `
|
|
2294
|
+
<li>
|
|
2295
|
+
<span class="check">${i.done ? '\u2713' : '\u2013'}</span>
|
|
2296
|
+
<span style="${i.done ? '' : 'color:var(--t-muted)'}">${i.label}</span>
|
|
2297
|
+
</li>
|
|
2298
|
+
`).join('');
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
document.getElementById('launchBtn').addEventListener('click', async () => {
|
|
2302
|
+
try {
|
|
2303
|
+
await fetch('/api/setup/complete', { method: 'POST' });
|
|
2304
|
+
} catch {}
|
|
2305
|
+
window.location.href = '/';
|
|
2306
|
+
});
|
|
2307
|
+
|
|
2308
|
+
// ═══════════════════════════════════════════
|
|
2309
|
+
// INIT
|
|
2310
|
+
// ═══════════════════════════════════════════
|
|
2311
|
+
|
|
2312
|
+
async function init() {
|
|
2313
|
+
try {
|
|
2314
|
+
const res = await fetch('/api/setup/onboarding');
|
|
2315
|
+
const data = await res.json();
|
|
2316
|
+
if (data.setupComplete) {
|
|
2317
|
+
window.location.href = '/';
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
if (data.projectDir) {
|
|
2321
|
+
wizardState.projectRoot = data.projectDir;
|
|
2322
|
+
}
|
|
2323
|
+
} catch {}
|
|
2324
|
+
|
|
2325
|
+
updateNav();
|
|
2326
|
+
startMascot();
|
|
2327
|
+
// Resize after mascot SVG is injected so scrollHeight includes it
|
|
2328
|
+
requestAnimationFrame(() => resizeViewport(0));
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
init();
|
|
2332
|
+
|
|
2333
|
+
</script>
|
|
2334
|
+
</body>
|
|
2335
|
+
</html>
|