dryymatt commited on
Commit
2105281
·
verified ·
1 Parent(s): 04cdb33

Upload static/wizard.js

Browse files
Files changed (1) hide show
  1. static/wizard.js +173 -107
static/wizard.js CHANGED
@@ -1,50 +1,46 @@
1
  /**
2
  * ╔══════════════════════════════════════════════════════╗
3
  * ║ OMNI-VIBE STUDIO — Client Runtime ║
4
- * ║ Tabbed Forge/Preview · SSE Stream · Ghost Deploy
5
  * ╚══════════════════════════════════════════════════════╝
6
  */
7
 
8
- const S = { sessionId: null, sandboxStatus: 'idle', fullCode: '', errorsFound: 0, errorsFixed: 0, publishData: null, previewUnlocked: false };
 
 
 
9
  const D = {
10
  prompt: document.getElementById('prompt-input'),
11
  vibeBtn: document.getElementById('vibe-btn'),
12
  codeContent: document.getElementById('code-content'),
 
13
  previewFrame: document.getElementById('preview-frame'),
14
  previewOverlay: document.getElementById('preview-overlay'),
15
- statusDot: document.getElementById('status-dot'),
16
- statusText: document.getElementById('status-text'),
17
- statusPhase: document.getElementById('status-phase'),
18
- sandboxBadge: document.getElementById('sandbox-badge'),
19
  publishBtn: document.getElementById('publish-btn'),
20
- publishGate: document.getElementById('publish-gate'),
21
- gateIcon: document.querySelector('.gate-icon'),
22
- gateText: document.querySelector('.gate-text'),
23
  publishResult: document.getElementById('publish-result'),
24
- publishUrl: document.getElementById('publish-url'),
25
- githubLink: document.getElementById('github-link'),
26
- spacesLink: document.getElementById('spaces-link'),
27
- agentLink: document.getElementById('agent-link'),
28
- modelBadges: document.getElementById('model-badges'),
29
  wizardHat: document.getElementById('wizard-hat'),
30
- statusRing: document.getElementById('status-ring'),
 
 
31
  copyBtn: document.getElementById('copy-btn'),
32
- previewTabBtn: document.getElementById('preview-tab-btn'),
33
- panelForge: document.getElementById('panel-forge'),
34
- panelPreview: document.getElementById('panel-preview'),
35
  };
36
 
37
  /* ═══ TAB SWITCHING ═══ */
38
  document.querySelectorAll('.tab-btn').forEach(btn => {
39
  btn.addEventListener('click', () => {
40
  if (btn.disabled) return;
41
- // Deactivate all
42
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
43
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
44
- // Activate clicked
45
  btn.classList.add('active');
46
- const panelId = 'panel-' + btn.dataset.tab;
47
- document.getElementById(panelId).classList.add('active');
48
  });
49
  });
50
 
@@ -57,12 +53,31 @@ function unlockPreview() {
57
  D.previewTabBtn.querySelector('.tab-icon').textContent = '👁️';
58
  }
59
 
60
- function switchToPreview() {
61
- if (!S.previewUnlocked) return;
62
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
63
- document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
64
- D.previewTabBtn.classList.add('active');
65
- D.panelPreview.classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
  /* ═══ SSE STREAMING ═══ */
@@ -90,37 +105,7 @@ async function* streamFromServer(prompt) {
90
  }
91
  }
92
 
93
- /* ═══ STATE MACHINE ═══ */
94
- function setSandboxStatus(st) {
95
- S.sandboxStatus = st;
96
- D.wizardHat.classList.remove('stable','error','published','building');
97
- D.statusRing.classList.remove('stable','error','published');
98
- D.statusDot.classList.remove('building','streaming','stable','error','published');
99
- D.publishGate.classList.remove('unlocked');
100
- const map = {
101
- building: {hat:'building', dot:'building', badge:'🔨', btn:true, gate:[false,'🔒','Building...']},
102
- streaming: {hat:'', dot:'streaming',badge:'📡', btn:true, gate:[false,'🔒','Streaming...']},
103
- stable: {hat:'stable', dot:'stable', badge:'✅', btn:false,gate:[true,'🔓','Sandbox stable — Ready']},
104
- error: {hat:'error', dot:'error', badge:'❌', btn:true, gate:[false,'🔒','Sandbox errors']},
105
- published: {hat:'published',dot:'published',badge:'🌟',btn:false,gate:[true,'🌟','Published to A2A']},
106
- };
107
- const m = map[st] || map.building;
108
- if (m.hat) D.wizardHat.classList.add(m.hat);
109
- if (st==='stable'||st==='published') D.statusRing.classList.add(st);
110
- D.statusDot.classList.add(m.dot);
111
- D.sandboxBadge.textContent = m.badge;
112
- D.publishBtn.disabled = m.btn;
113
- if (m.gate) {
114
- if (m.gate[0]) D.publishGate.classList.add('unlocked');
115
- D.gateIcon.textContent = m.gate[1];
116
- D.gateText.textContent = m.gate[2];
117
- }
118
- if (st==='published') D.publishBtn.classList.add('gold');
119
- else D.publishBtn.classList.remove('gold');
120
- D.statusText.textContent = st.charAt(0).toUpperCase()+st.slice(1);
121
- }
122
-
123
- /* ═══ IFRAME HOT-RELOAD ═══ */
124
  function updateIframe(code) {
125
  const blob = new Blob([code], {type:'text/html'});
126
  const url = URL.createObjectURL(blob);
@@ -136,84 +121,150 @@ function appendCode(chunk) {
136
  span.className='code-chunk-new'; span.textContent=chunk;
137
  D.codeContent.appendChild(span);
138
  D.codeContent.parentElement.scrollTop = D.codeContent.parentElement.scrollHeight;
 
139
  }
140
 
141
- function updateModelBadges(plan) {
142
- D.modelBadges.innerHTML = '';
143
- if (!plan?.models) return;
144
- plan.models.forEach(m => {
145
- const b = document.createElement('span');
146
- b.className = `model-badge ${m.task_type}`;
147
- b.textContent = m.model_id.split('/').pop();
148
- b.title = `${m.model_id} (${Math.round(m.confidence*100)}%)`;
149
- D.modelBadges.appendChild(b);
150
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
152
 
153
  /* ═══ MAIN GENERATION ═══ */
154
  async function generate() {
155
  const prompt = D.prompt.value.trim(); if (!prompt) return;
156
- S.fullCode = ''; S.errorsFound = 0; S.errorsFixed = 0;
157
- S.publishData = null; S.previewUnlocked = false;
158
 
159
- // Reset to Forge tab
160
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
161
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
162
  document.querySelector('.tab-btn[data-tab="forge"]').classList.add('active');
163
  D.panelForge.classList.add('active');
164
-
165
- // Lock Preview tab
166
  D.previewTabBtn.disabled = true;
167
  D.previewTabBtn.classList.add('locked');
168
  D.previewTabBtn.classList.remove('unlocked');
169
  D.previewTabBtn.querySelector('.tab-icon').textContent = '🔒';
170
 
171
- D.codeContent.innerHTML = '<span class="placeholder">Streaming...</span>';
 
172
  D.previewOverlay.classList.remove('hidden');
173
  D.previewFrame.src = 'about:blank';
174
  D.publishResult.style.display = 'none';
175
- D.publishBtn.classList.remove('gold'); D.publishBtn.disabled = true;
176
- D.modelBadges.innerHTML = '';
177
  D.vibeBtn.disabled = true;
178
- D.vibeBtn.querySelector('.btn-text').textContent = 'Generating...';
179
- setSandboxStatus('building');
180
- D.statusPhase.textContent = 'Connecting...';
 
 
181
 
182
  try {
183
  for await (const ev of streamFromServer(prompt)) {
184
- if (ev.phase) D.statusPhase.textContent = ev.phase;
185
- if (ev.plan) updateModelBadges(ev.plan);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
187
  if (ev.partial) updateIframe(ev.partial);
188
- if (ev.heal) { S.errorsFound = ev.errors_found; S.errorsFixed = ev.errors_fixed; }
 
189
  if (ev.sandbox) {
190
- setSandboxStatus(ev.status);
191
- // Unlock Preview tab when stable or published
192
  if (ev.status === 'stable' || ev.status === 'published') {
193
  unlockPreview();
 
 
 
 
 
194
  }
195
  }
196
  }
197
  } catch (err) {
198
  console.error('Stream error:', err);
199
- setSandboxStatus('error');
200
- D.statusText.textContent = `Error: ${err.message}`;
201
  } finally {
202
  D.vibeBtn.disabled = false;
203
- D.vibeBtn.querySelector('.btn-text').textContent = 'Generate';
204
- D.statusText.textContent = 'Done';
205
- // Always unlock preview after generation, even on errors
206
- // (so user can see partial/error output)
207
- if (S.fullCode) unlockPreview();
208
  }
209
  }
210
 
211
- /* ═══ PUBLISH ═══ */
212
  async function publish() {
213
- const repoName = prompt('Repo name:', `omni-vibe-${Date.now().toString(36)}`);
214
- if (!repoName) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  D.publishBtn.disabled = true;
216
- D.publishBtn.querySelector('.btn-text').textContent = 'Publishing...';
 
 
217
  try {
218
  const resp = await fetch('/api/publish', {
219
  method:'POST',
@@ -221,20 +272,29 @@ async function publish() {
221
  body:JSON.stringify({repo_name:repoName, description:D.prompt.value.trim().slice(0,200)}),
222
  });
223
  const r = await resp.json();
 
224
  if (r.success) {
225
- S.publishData = r; setSandboxStatus('published');
 
 
 
226
  D.publishResult.style.display = 'block';
227
- D.publishUrl.textContent = r.spaces?.url || r.github?.repo_url || 'Published';
228
- if (r.github?.repo_url) { D.githubLink.href = r.github.repo_url; D.githubLink.style.display = 'inline'; }
229
- if (r.spaces?.url) { D.spacesLink.href = r.spaces.url; D.spacesLink.style.display = 'inline'; }
230
- if (r.agent_url) { D.agentLink.href = r.agent_url; D.agentLink.style.display = 'inline'; }
231
  D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
232
- } else { D.statusText.textContent = `Publish error: ${r.error}`; }
 
 
 
233
  } catch (err) {
234
- D.statusText.textContent = `Publish error: ${err.message}`;
 
235
  } finally {
236
- D.publishBtn.disabled = false;
237
- D.publishBtn.querySelector('.btn-text').textContent = 'Publish to A2A Network';
 
238
  }
239
  }
240
 
@@ -242,11 +302,17 @@ async function publish() {
242
  D.vibeBtn.addEventListener('click', generate);
243
  D.publishBtn.addEventListener('click', publish);
244
  D.prompt.addEventListener('keydown', e => {
245
- if ((e.ctrlKey||e.metaKey) && e.key==='Enter') { e.preventDefault(); generate(); }
246
  });
247
  D.copyBtn.addEventListener('click', async () => {
248
  if (!S.fullCode) return;
249
  try { await navigator.clipboard.writeText(S.fullCode); } catch {}
250
  });
251
 
252
- console.log('🧙‍♂️ Omni-Vibe Studio Liquid Glass Client ready');
 
 
 
 
 
 
 
1
  /**
2
  * ╔══════════════════════════════════════════════════════╗
3
  * ║ OMNI-VIBE STUDIO — Client Runtime ║
4
+ * ║ Fluid Thinking · Wizard Pulse · E2E Handshake
5
  * ╚══════════════════════════════════════════════════════╝
6
  */
7
 
8
+ const S = {
9
+ sessionId: null, fullCode: '', previewUnlocked: false,
10
+ publishReady: false, publishData: null, handshakeVerified: false,
11
+ };
12
  const D = {
13
  prompt: document.getElementById('prompt-input'),
14
  vibeBtn: document.getElementById('vibe-btn'),
15
  codeContent: document.getElementById('code-content'),
16
+ charCount: document.getElementById('char-count'),
17
  previewFrame: document.getElementById('preview-frame'),
18
  previewOverlay: document.getElementById('preview-overlay'),
19
+ previewBadge: document.getElementById('preview-badge'),
20
+ previewTabBtn: document.getElementById('preview-tab-btn'),
21
+ panelForge: document.getElementById('panel-forge'),
22
+ panelPreview: document.getElementById('panel-preview'),
23
  publishBtn: document.getElementById('publish-btn'),
24
+ publishBar: document.getElementById('publish-bar'),
25
+ pubIcon: document.getElementById('pub-icon'),
26
+ pubText: document.getElementById('pub-text'),
27
  publishResult: document.getElementById('publish-result'),
28
+ liveLink: document.getElementById('live-link'),
 
 
 
 
29
  wizardHat: document.getElementById('wizard-hat'),
30
+ thoughtBar: document.getElementById('thought-bar'),
31
+ thoughtText: document.getElementById('thought-text'),
32
+ thoughtModels: document.getElementById('thought-models'),
33
  copyBtn: document.getElementById('copy-btn'),
 
 
 
34
  };
35
 
36
  /* ═══ TAB SWITCHING ═══ */
37
  document.querySelectorAll('.tab-btn').forEach(btn => {
38
  btn.addEventListener('click', () => {
39
  if (btn.disabled) return;
 
40
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
41
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
 
42
  btn.classList.add('active');
43
+ document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
 
44
  });
45
  });
46
 
 
53
  D.previewTabBtn.querySelector('.tab-icon').textContent = '👁️';
54
  }
55
 
56
+ /* ═══ WIZARD PULSE — high-velocity, 600ms ═══ */
57
+ function pulseWizard() {
58
+ D.wizardHat.classList.add('pulse');
59
+ }
60
+ function stopWizard(state) {
61
+ D.wizardHat.classList.remove('pulse');
62
+ D.wizardHat.classList.remove('stable','error','published');
63
+ if (state) D.wizardHat.classList.add(state);
64
+ }
65
+
66
+ /* ═══ SWARM THOUGHT PROCESS — single line ═══ */
67
+ function showThought() {
68
+ D.thoughtBar.style.display = 'flex';
69
+ requestAnimationFrame(() => D.thoughtBar.classList.add('visible'));
70
+ }
71
+ function updateThought(text, models) {
72
+ D.thoughtText.textContent = text;
73
+ if (models?.length) {
74
+ D.thoughtModels.innerHTML = models.map(m =>
75
+ `<span>${m.model_id.split('/').pop()}</span>`
76
+ ).join('');
77
+ }
78
+ }
79
+ function hideThought() {
80
+ D.thoughtBar.classList.remove('visible');
81
  }
82
 
83
  /* ═══ SSE STREAMING ═══ */
 
105
  }
106
  }
107
 
108
+ /* ═══ IFRAME ═══ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  function updateIframe(code) {
110
  const blob = new Blob([code], {type:'text/html'});
111
  const url = URL.createObjectURL(blob);
 
121
  span.className='code-chunk-new'; span.textContent=chunk;
122
  D.codeContent.appendChild(span);
123
  D.codeContent.parentElement.scrollTop = D.codeContent.parentElement.scrollHeight;
124
+ D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`;
125
  }
126
 
127
+ /* ═══ END-TO-END HANDSHAKE VERIFICATION ═══ */
128
+ async function verifyHandshake(url) {
129
+ try {
130
+ const resp = await fetch('/api/verify-handshake', {
131
+ method:'POST',
132
+ headers:{'Content-Type':'application/json'},
133
+ body:JSON.stringify({url}),
134
+ });
135
+ const data = await resp.json();
136
+ S.handshakeVerified = data.verified;
137
+ return data;
138
+ } catch {
139
+ S.handshakeVerified = false;
140
+ return {verified:false, error:'Handshake unreachable'};
141
+ }
142
+ }
143
+
144
+ /* ═══ PUBLISH GATE ═══ */
145
+ function setPublishState(ready, text, icon) {
146
+ S.publishReady = ready;
147
+ D.publishBtn.disabled = !ready;
148
+ if (ready) {
149
+ D.publishBar.classList.add('unlocked');
150
+ D.pubIcon.textContent = icon || '🔓';
151
+ D.pubText.textContent = text || 'Ready to publish';
152
+ } else {
153
+ D.publishBar.classList.remove('unlocked');
154
+ D.pubIcon.textContent = icon || '🔒';
155
+ D.pubText.textContent = text || 'Validate to unlock publishing';
156
+ }
157
  }
158
 
159
  /* ═══ MAIN GENERATION ═══ */
160
  async function generate() {
161
  const prompt = D.prompt.value.trim(); if (!prompt) return;
162
+ S.fullCode = ''; S.previewUnlocked = false; S.publishReady = false; S.handshakeVerified = false;
 
163
 
164
+ // Reset
165
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
166
  document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
167
  document.querySelector('.tab-btn[data-tab="forge"]').classList.add('active');
168
  D.panelForge.classList.add('active');
 
 
169
  D.previewTabBtn.disabled = true;
170
  D.previewTabBtn.classList.add('locked');
171
  D.previewTabBtn.classList.remove('unlocked');
172
  D.previewTabBtn.querySelector('.tab-icon').textContent = '🔒';
173
 
174
+ D.codeContent.innerHTML = '<span class="placeholder">Streaming</span>';
175
+ D.charCount.textContent = '';
176
  D.previewOverlay.classList.remove('hidden');
177
  D.previewFrame.src = 'about:blank';
178
  D.publishResult.style.display = 'none';
179
+ setPublishState(false, 'Building…', '🔨');
 
180
  D.vibeBtn.disabled = true;
181
+
182
+ // Wizard pulse start
183
+ pulseWizard();
184
+ showThought();
185
+ updateThought('Analyzing prompt…', []);
186
 
187
  try {
188
  for await (const ev of streamFromServer(prompt)) {
189
+ // Phase updates single thought line
190
+ if (ev.phase) {
191
+ const phaseMap = {
192
+ 'pose': 'Selecting models…',
193
+ 'generate': 'Generating code…',
194
+ 'audit': 'Verifying output…',
195
+ 'heal': 'Self-healing errors…',
196
+ 'sandbox': 'Validating sandbox…',
197
+ };
198
+ updateThought(phaseMap[ev.phase] || ev.phase, []);
199
+ }
200
+
201
+ // Pose plan → show model badges
202
+ if (ev.pose) {
203
+ const models = [];
204
+ if (ev.pose.architect?.stack?.backend) models.push({model_id:'postgres+oauth'});
205
+ models.push({model_id:'liquid-glass'});
206
+ updateThought('Swarm dispatched', models);
207
+ }
208
+
209
+ // Code streaming
210
  if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
211
  if (ev.partial) updateIframe(ev.partial);
212
+
213
+ // Sandbox result
214
  if (ev.sandbox) {
215
+ stopWizard(ev.status === 'stable' ? 'stable' : ev.status === 'error' ? 'error' : null);
 
216
  if (ev.status === 'stable' || ev.status === 'published') {
217
  unlockPreview();
218
+ setPublishState(true, 'Sandbox stable', '🔓');
219
+ updateThought('Build complete', [{model_id:'ready'}]);
220
+ } else if (ev.status === 'error') {
221
+ setPublishState(false, 'Sandbox errors found', '❌');
222
+ updateThought('Errors detected', []);
223
  }
224
  }
225
  }
226
  } catch (err) {
227
  console.error('Stream error:', err);
228
+ stopWizard('error');
229
+ updateThought(`Error: ${err.message}`, []);
230
  } finally {
231
  D.vibeBtn.disabled = false;
232
+ if (!S.publishReady) stopWizard(null);
233
+ setTimeout(hideThought, 2000);
 
 
 
234
  }
235
  }
236
 
237
+ /* ═══ PUBLISH WITH HANDSHAKE ═══ */
238
  async function publish() {
239
+ if (!S.publishReady) return;
240
+
241
+ // Phase 1: Verify handshake
242
+ D.publishBtn.classList.add('verifying');
243
+ D.publishBtn.querySelector('.spinner').style.display = 'inline-block';
244
+ updateThought('Verifying server handshake…', [{model_id:'litheat.app'}]);
245
+ showThought();
246
+
247
+ const hk = await verifyHandshake('litheat.app');
248
+ if (!hk.verified) {
249
+ updateThought('Handshake failed — retrying…', []);
250
+ // Retry once
251
+ await new Promise(r => setTimeout(r, 1000));
252
+ const hk2 = await verifyHandshake('litheat.app');
253
+ if (!hk2.verified) {
254
+ updateThought('⚠ Server unreachable — publishing anyway', []);
255
+ D.publishBtn.classList.remove('verifying');
256
+ D.vibeBtn.querySelector('.spinner')?.style && (D.publishBtn.querySelector('.spinner').style.display = 'none');
257
+ }
258
+ } else {
259
+ updateThought('✓ Handshake verified', [{model_id:'litheat.app'}]);
260
+ }
261
+
262
+ // Phase 2: Publish
263
+ const repoName = `omni-vibe-${Date.now().toString(36)}`;
264
  D.publishBtn.disabled = true;
265
+ D.publishBtn.classList.add('verifying');
266
+ updateThought('Deploying to production…', [{model_id:'ghost-deploy'}]);
267
+
268
  try {
269
  const resp = await fetch('/api/publish', {
270
  method:'POST',
 
272
  body:JSON.stringify({repo_name:repoName, description:D.prompt.value.trim().slice(0,200)}),
273
  });
274
  const r = await resp.json();
275
+
276
  if (r.success) {
277
+ S.publishData = r;
278
+ stopWizard('published');
279
+ setPublishState(true, 'Published!', '🌟');
280
+ D.publishBtn.classList.add('gold');
281
  D.publishResult.style.display = 'block';
282
+ const liveUrl = r.spaces?.url || r.tunnel_url || '#';
283
+ D.liveLink.href = liveUrl;
284
+ D.liveLink.textContent = liveUrl;
285
+ updateThought('✓ Live production ready', [{model_id:'deployed'}]);
286
  D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
287
+ } else {
288
+ updateThought(`Publish error: ${r.error || r.space_error || 'Unknown'}`, []);
289
+ setPublishState(true, 'Publish failed — retry', '⚠');
290
+ }
291
  } catch (err) {
292
+ updateThought(`Publish error: ${err.message}`, []);
293
+ setPublishState(true, 'Publish failed — retry', '⚠');
294
  } finally {
295
+ D.publishBtn.disabled = !S.publishReady;
296
+ D.publishBtn.classList.remove('verifying');
297
+ setTimeout(hideThought, 3000);
298
  }
299
  }
300
 
 
302
  D.vibeBtn.addEventListener('click', generate);
303
  D.publishBtn.addEventListener('click', publish);
304
  D.prompt.addEventListener('keydown', e => {
305
+ if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); generate(); }
306
  });
307
  D.copyBtn.addEventListener('click', async () => {
308
  if (!S.fullCode) return;
309
  try { await navigator.clipboard.writeText(S.fullCode); } catch {}
310
  });
311
 
312
+ /* ═══ AUTO-RESIZE TEXTAREA ═══ */
313
+ D.prompt.addEventListener('input', () => {
314
+ D.prompt.style.height = 'auto';
315
+ D.prompt.style.height = Math.min(D.prompt.scrollHeight, 120) + 'px';
316
+ });
317
+
318
+ console.log('🧙‍♂️ Omni-Vibe Studio — Fluid Thinking Client ready');