Testing347 commited on
Commit
93a96fd
·
verified ·
1 Parent(s): 2c47e18

Update chat.html

Browse files
Files changed (1) hide show
  1. chat.html +465 -248
chat.html CHANGED
@@ -5,25 +5,79 @@
5
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
6
  <title>SILENTPATTERN — Console</title>
7
 
 
8
  <script src="https://cdn.tailwindcss.com"></script>
 
 
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
10
  <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
11
 
 
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap">
14
- <link rel="stylesheet" href="assets/site.css">
15
 
16
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  .chat-container {
18
  height: 520px;
19
  overflow-y: auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
 
 
21
  </style>
22
  </head>
23
 
24
  <body class="bg-black text-white overflow-x-hidden">
 
25
  <div id="vanta-bg" class="fixed top-0 left-0 w-full h-full z-0"></div>
26
 
 
27
  <nav class="relative z-10 py-6 px-8 flex justify-between items-center backdrop-blur-sm">
28
  <a href="index.html" class="flex items-center space-x-2">
29
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex items-center justify-center">
@@ -46,6 +100,7 @@
46
  </div>
47
  </nav>
48
 
 
49
  <section class="relative z-10 px-6 py-16">
50
  <div class="max-w-5xl mx-auto">
51
  <div class="text-center mb-10">
@@ -63,7 +118,9 @@
63
  </p>
64
  </div>
65
 
 
66
  <div class="relative rounded-2xl border border-gray-800 bg-gray-900/30 overflow-hidden aura">
 
67
  <div class="bg-gray-800/40 px-6 py-4 border-b border-gray-800 flex items-center justify-between">
68
  <div class="flex items-center">
69
  <div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div>
@@ -75,31 +132,34 @@
75
  <div class="w-1.5 h-1.5 rounded-full bg-white animate-pulse"></div>
76
  </div>
77
  <div>
78
- <div class="text-sm font-medium">SILENTPATTERN Console</div>
79
  <div class="text-xs text-gray-500">Session: local</div>
80
  </div>
81
  </div>
82
  </div>
83
 
 
84
  <div class="text-xs px-2.5 py-1 rounded-full border border-indigo-500/30 text-indigo-200 bg-indigo-900/15">
85
  DRAFT
86
  </div>
87
  </div>
88
 
89
- <div id="chat-messages" class="chat-container p-6 space-y-4 thin-scroll" aria-live="polite" aria-label="Chat messages">
90
- <div class="flex items-start" data-seed="assistant">
 
91
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3">
92
  <i class="fas fa-robot text-white text-sm"></i>
93
  </div>
94
  <div class="bg-gray-800/70 rounded-lg p-4 max-w-[85%]">
95
- <p class="text-gray-100" id="seed-assistant-text">
96
- Acknowledged. State your objective; I will respond with constraints, assumptions, and a minimal next experiment.
97
  </p>
98
  <p class="text-gray-400 text-xs mt-2">Note: do not paste secrets or API keys into chat.</p>
99
  </div>
100
  </div>
101
  </div>
102
 
 
103
  <div class="px-6 py-4 border-t border-gray-800 bg-black/10">
104
  <form id="chat-form" class="flex items-center" autocomplete="off" aria-label="Send message">
105
  <input id="chat-input" type="text"
@@ -122,28 +182,26 @@
122
  <i class="fas fa-file-arrow-down mr-1"></i> Export
123
  </button>
124
  </div>
125
-
126
- <div class="flex items-center gap-2">
127
- <span id="server-status" class="text-xs text-gray-600"></span>
128
- <span id="typing-indicator" class="hidden text-gray-400">
129
- <span class="mr-2">System is typing</span>
130
- <span class="typing-dots" aria-hidden="true"><span></span><span></span><span></span></span>
131
- </span>
132
  </div>
133
  </div>
134
 
 
135
  <div class="mt-3 text-xs text-gray-600">
136
- Integration note: do not call model APIs directly from the browser. Use a server endpoint
137
- (<span class="text-gray-400">/api/chat</span>) to keep keys private.
138
  </div>
139
  </div>
140
  </div>
141
  </div>
142
  </section>
143
 
 
144
  <footer class="relative z-10 px-6 pb-10">
145
  <div class="max-w-5xl mx-auto border-t border-gray-800/60 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
146
- <div class="text-sm text-gray-500">© 2025 SILENTPATTERN. All rights reserved.</div>
 
 
147
  <div class="text-sm text-gray-500 flex gap-6">
148
  <a href="research.html" class="hover:text-indigo-400 transition">Research</a>
149
  <a href="privacy.html" class="hover:text-indigo-400 transition">Privacy</a>
@@ -153,7 +211,7 @@
153
  </div>
154
  </footer>
155
 
156
- <!-- ACCESS MODAL (same structure) -->
157
  <div id="access-modal"
158
  class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm modal modal-hidden"
159
  role="dialog" aria-modal="true" aria-labelledby="access-modal-title" tabindex="-1">
@@ -163,7 +221,7 @@
163
  <div class="flex justify-between items-start mb-6">
164
  <div>
165
  <h3 class="text-xl font-bold" id="access-modal-title">Request Access</h3>
166
- <p class="text-gray-400 mt-1">Curated access for research and evaluation</p>
167
  </div>
168
  <button id="close-access-modal" class="text-gray-400 hover:text-white" aria-label="Close">
169
  <i class="fas fa-times"></i>
@@ -173,22 +231,26 @@
173
  <form id="access-form" class="space-y-4">
174
  <div>
175
  <label for="name" class="block text-sm font-medium mb-1">Full Name</label>
176
- <input type="text" id="name" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
 
177
  </div>
178
  <div>
179
  <label for="email" class="block text-sm font-medium mb-1">Email</label>
180
- <input type="email" id="email" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
 
181
  </div>
182
  <div>
183
  <label for="institution" class="block text-sm font-medium mb-1">Institution/Organization</label>
184
- <input type="text" id="institution" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
 
185
  </div>
186
  <div>
187
- <label for="purpose" class="block text-sm font-medium mb-1">Purpose</label>
188
  <select id="purpose" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
189
  <option value="">Select a purpose</option>
190
- <option value="research">Academic / Independent Research</option>
191
- <option value="development">Engineering / Product Evaluation</option>
 
192
  <option value="partnership">Partnership</option>
193
  <option value="other">Other</option>
194
  </select>
@@ -204,11 +266,13 @@
204
  </div>
205
  </div>
206
 
207
- <!-- LAB NAVIGATOR (minimal: same markup as index/capabilities is acceptable) -->
208
  <div id="lab-navigator"
209
  class="fixed inset-0 z-[60] bg-black/80 backdrop-blur-md modal modal-hidden"
210
  role="dialog" aria-modal="true" aria-label="Lab Navigator" tabindex="-1">
 
211
  <div class="absolute inset-0" data-lab-close="true"></div>
 
212
  <div class="relative w-full h-full flex items-center justify-center p-6">
213
  <div class="w-full max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6">
214
  <div class="relative rounded-2xl border border-gray-800 bg-gray-900/20 overflow-hidden">
@@ -222,6 +286,7 @@
222
  <div class="text-xs text-gray-500">Lab Navigator</div>
223
  </div>
224
  </div>
 
225
  <button id="lab-nav-close"
226
  class="w-9 h-9 rounded-full border border-gray-800 bg-gray-900/30 hover:bg-gray-900/50 transition flex items-center justify-center"
227
  aria-label="Close Lab Navigator">
@@ -230,43 +295,42 @@
230
  </div>
231
 
232
  <div class="relative p-6 min-h-[420px]">
 
 
 
 
233
  <div class="relative grid grid-cols-1 sm:grid-cols-2 gap-3">
234
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
235
  data-dossier="start">
236
  <div class="text-sm text-gray-200 font-medium">Start Here</div>
237
- <div class="text-xs text-gray-500 mt-1">Entry interface</div>
238
- </button>
239
-
240
- <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
241
- data-dossier="programs">
242
- <div class="text-sm text-gray-200 font-medium">Programs</div>
243
- <div class="text-xs text-gray-500 mt-1">Program Bay dossiers</div>
244
  </button>
245
 
246
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
247
  data-dossier="console">
248
  <div class="text-sm text-gray-200 font-medium">Console</div>
249
- <div class="text-xs text-gray-500 mt-1">This page</div>
250
  </button>
251
 
252
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
253
- data-dossier="research">
254
- <div class="text-sm text-gray-200 font-medium">Research</div>
255
- <div class="text-xs text-gray-500 mt-1">Notes and briefs</div>
256
  </button>
257
 
258
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
259
- data-dossier="contact">
260
- <div class="text-sm text-gray-200 font-medium">Contact</div>
261
- <div class="text-xs text-gray-500 mt-1">Direct channel</div>
262
  </button>
263
 
264
- <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
265
  data-dossier="access">
266
  <div class="text-sm text-gray-200 font-medium">Access</div>
267
- <div class="text-xs text-gray-500 mt-1">Curated entry</div>
268
  </button>
269
  </div>
 
270
  <div class="relative mt-6 text-xs text-gray-500">
271
  Tip: Press <span class="text-gray-300">Esc</span> to close.
272
  </div>
@@ -278,21 +342,28 @@
278
  <div class="flex items-start justify-between gap-4">
279
  <div>
280
  <div id="dossier-title" class="text-lg font-semibold text-gray-100">Lab Dossier</div>
281
- <div id="dossier-subtitle" class="text-xs text-gray-500 mt-1">Select a node to load details.</div>
282
  </div>
283
  <div id="dossier-status"
284
  class="text-xs px-2.5 py-1 rounded-full border border-indigo-500/30 text-indigo-200 bg-indigo-900/15">
285
- READY
286
  </div>
287
  </div>
288
  </div>
289
 
290
- <div class="p-6 space-y-5 max-h-[560px] overflow-auto thin-scroll">
291
- <div id="dossier-body" class="text-sm text-gray-300 leading-relaxed"></div>
 
 
 
 
292
  <div class="rounded-xl border border-gray-800 bg-black/20 p-4">
293
  <div class="text-xs text-gray-400 uppercase tracking-wider mb-2">Evidence Capsule</div>
294
- <ul id="dossier-evidence" class="text-sm text-gray-300 space-y-1"></ul>
 
 
295
  </div>
 
296
  <div class="flex flex-col sm:flex-row gap-3">
297
  <button id="dossier-primary"
298
  class="flex-1 px-5 py-3 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:opacity-90 transition">
@@ -303,236 +374,382 @@
303
  View Note
304
  </button>
305
  </div>
306
- <div id="dossier-meta" class="text-xs text-gray-500"></div>
 
 
 
307
  </div>
308
  </div>
 
309
  </div>
310
  </div>
311
  </div>
312
 
313
- <script src="assets/site.js"></script>
314
  <script>
315
- document.addEventListener('DOMContentLoaded', () => {
316
- SilentPattern.initVanta();
317
- SilentPattern.setupAccessModal();
318
-
319
- /* Chat logic */
320
- const API_ENDPOINT = "/api/chat";
321
- const chatForm = document.getElementById('chat-form');
322
- const chatInput = document.getElementById('chat-input');
323
- const chatMessages = document.getElementById('chat-messages');
324
- const typingIndicator = document.getElementById('typing-indicator');
325
- const sendBtn = document.getElementById('send-btn');
326
- const clearBtn = document.getElementById('clear-btn');
327
- const exportBtn = document.getElementById('export-btn');
328
- const serverStatus = document.getElementById('server-status');
329
-
330
- const transcript = []; // {role:'user'|'assistant', content:string, ts:number}
331
-
332
- function seedTranscript() {
333
- const seed = document.getElementById('seed-assistant-text');
334
- if (seed && seed.textContent.trim()) {
335
- transcript.push({ role: 'assistant', content: seed.textContent.trim(), ts: Date.now() });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  }
337
  }
338
- seedTranscript();
339
-
340
- function escapeHtml(str) {
341
- return str
342
- .replaceAll('&', '&amp;')
343
- .replaceAll('<', '&lt;')
344
- .replaceAll('>', '&gt;')
345
- .replaceAll('"', '&quot;')
346
- .replaceAll("'", '&#039;');
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
- function addMessage(text, role) {
350
- const safe = escapeHtml(text);
351
- const messageDiv = document.createElement('div');
352
- const isUser = role === 'user';
353
- messageDiv.className = `flex items-start ${isUser ? 'justify-end' : ''}`;
354
 
355
- if (!isUser) {
356
- messageDiv.innerHTML = `
357
- <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3" aria-hidden="true">
358
- <i class="fas fa-robot text-white text-sm"></i>
359
- </div>
360
- <div class="bg-gray-800/70 rounded-lg p-4 max-w-[85%]">
361
- <p class="text-gray-100">${safe}</p>
362
- </div>
363
- `;
364
- } else {
365
- messageDiv.innerHTML = `
366
- <div class="bg-indigo-900/50 rounded-lg p-4 max-w-[85%]">
367
- <p class="text-gray-100">${safe}</p>
368
- </div>
369
- `;
370
- }
371
 
372
- chatMessages.appendChild(messageDiv);
373
- chatMessages.scrollTop = chatMessages.scrollHeight;
374
- transcript.push({ role, content: text, ts: Date.now() });
 
 
 
 
 
 
 
375
  }
 
 
 
 
376
 
377
- function setBusy(isBusy) {
378
- if (isBusy) {
379
- typingIndicator.classList.remove('hidden');
380
- sendBtn.disabled = true;
381
- } else {
382
- typingIndicator.classList.add('hidden');
383
- sendBtn.disabled = false;
384
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
- async function callServerChat(userMessage) {
388
- const contextWindow = transcript.slice(-12);
389
- const res = await fetch(API_ENDPOINT, {
390
- method: 'POST',
391
- headers: { 'Content-Type': 'application/json' },
392
- body: JSON.stringify({
393
- message: userMessage,
394
- context: contextWindow,
395
- meta: { page: 'chat.html', product: 'silentpattern' }
396
- })
397
- });
398
-
399
- if (!res.ok) {
400
- const txt = await res.text().catch(() => '');
401
- throw new Error(`Server error (${res.status}). ${txt}`.trim());
402
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
- const data = await res.json();
405
- if (!data || typeof data.reply !== 'string') throw new Error('Invalid response format from /api/chat');
406
- return data.reply;
 
 
 
 
 
 
 
 
 
 
 
 
407
  }
408
 
409
- function localDemoResponse(userMessage) {
410
- const msg = userMessage.toLowerCase();
411
- if (msg.includes('mcap')) {
412
- return "MCAP acknowledged. Provide: (1) abstraction mapping, (2) causal fidelity test, (3) baselines. I will draft a research-note scaffold next.";
413
- }
414
- if (msg.includes('chai')) {
415
- return "CHAI acknowledged. Specify assets, horizon, labeling rules, and walk-forward protocol. Then we define evaluation harness + calibration metrics.";
416
- }
417
- if (msg.includes('quantum lambda')) {
418
- return "Quantum Lambda acknowledged. Gate 1: microstructure realism and execution assumptions. Share latency/slippage and risk limits before accuracy claims.";
419
- }
420
- return "Acknowledged. State (a) objective, (b) constraints, (c) evidence today. I will propose a minimal next experiment with audit hooks.";
 
421
  }
 
422
 
423
- chatForm.addEventListener('submit', async (e) => {
424
- e.preventDefault();
425
- const message = chatInput.value.trim();
426
- if (!message) return;
427
-
428
- addMessage(message, 'user');
429
- chatInput.value = '';
430
- setBusy(true);
431
- serverStatus.textContent = '';
432
-
433
- try {
434
- const reply = await callServerChat(message);
435
- serverStatus.textContent = 'Server: connected';
436
- addMessage(reply, 'assistant');
437
- } catch (err) {
438
- serverStatus.textContent = 'Server: unavailable (local fallback)';
439
- addMessage(localDemoResponse(message), 'assistant');
440
- } finally {
441
- setBusy(false);
442
- }
443
  });
444
 
445
- clearBtn.addEventListener('click', () => {
446
- const nodes = Array.from(chatMessages.children);
447
- for (let i = 1; i < nodes.length; i++) nodes[i].remove();
448
- transcript.length = 0;
449
- seedTranscript();
450
- addMessage('Session cleared.', 'assistant');
451
- });
452
 
453
- exportBtn.addEventListener('click', () => {
454
- const payload = {
455
- product: "SILENTPATTERN",
456
- page: "chat.html",
457
- exported_at: new Date().toISOString(),
458
- transcript
459
- };
460
- const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
461
- const url = URL.createObjectURL(blob);
462
- const a = document.createElement('a');
463
- a.href = url;
464
- a.download = `silentpattern_transcript_${Date.now()}.json`;
465
- document.body.appendChild(a);
466
- a.click();
467
- a.remove();
468
- URL.revokeObjectURL(url);
469
- });
470
 
471
- /* Lab Navigator dossiers */
472
- const DOSSIERS = {
473
- start: {
474
- title: "Start Here",
475
- subtitle: "Entry interface",
476
- status: "ACTIVE",
477
- body: "Return to the main interface. Programs and Console are accessible through dossiers.",
478
- evidence: ["Reduced surface area", "Audit-first posture", "Controlled interaction"],
479
- primary: { label: "Go to Index", action: () => { window.location.href = "index.html"; } },
480
- secondary: { label: "Programs", action: () => { window.location.href = "capabilities.html"; } },
481
- updated: "—"
482
- },
483
- programs: {
484
- title: "Programs",
485
- subtitle: "Program Bay dossiers",
486
- status: "ACTIVE",
487
- body: "Programs are exposed as dossiers: scope, assumptions, evaluation protocol, and access gates.",
488
- evidence: ["MCAP · CHAI · Quantum Lambda", "AI Scientist", "Agentic Workforce"],
489
- primary: { label: "Open Programs", action: () => { window.location.href = "capabilities.html"; } },
490
- secondary: { label: "Research", action: () => { window.location.href = "research.html"; } },
491
- updated: "—"
492
- },
493
- console: {
494
- title: "Console",
495
- subtitle: "Controlled interaction channel",
496
- status: "ACTIVE",
497
- body: "This page. For production, keep keys server-side and include logging and policy gating.",
498
- evidence: ["No client-side secrets", "Context window sent", "Exportable transcripts"],
499
- primary: { label: "Close Navigator", action: () => SilentPattern.toggleModal(document.getElementById('lab-navigator'), false) },
500
- secondary: { label: "Export Transcript", action: () => { SilentPattern.toggleModal(document.getElementById('lab-navigator'), false); exportBtn.click(); } },
501
- updated: "—"
502
- },
503
- research: {
504
- title: "Research",
505
- subtitle: "Notes and briefs",
506
- status: "DRAFT",
507
- body: "Research artifacts should keep protocols and uncertainty explicit. Store evaluation artifacts here.",
508
- evidence: ["Evaluation artifacts", "Reproducibility hooks", "Failure modes"],
509
- primary: { label: "Open Research", action: () => { window.location.href = "research.html"; } },
510
- secondary: { label: "Programs", action: () => { window.location.href = "capabilities.html"; } },
511
- updated: "—"
512
- },
513
- contact: {
514
- title: "Contact",
515
- subtitle: "Direct channel",
516
- status: "ACTIVE",
517
- body: "Use Contact for coordination, partnerships, and qualified inquiries.",
518
- evidence: ["Curated inbound", "Clear scope alignment", "Responsible scaling"],
519
- primary: { label: "Open Contact", action: () => { window.location.href = "contact.html"; } },
520
- secondary: { label: "Request Access", action: () => { SilentPattern.toggleModal(document.getElementById('lab-navigator'), false); document.getElementById('access-btn').click(); } },
521
- updated: "—"
522
- },
523
- access: {
524
- title: "Access",
525
- subtitle: "Curated entry",
526
- status: "ACTIVE",
527
- body: "Request access for controlled demos and evaluation workflows.",
528
- evidence: ["Application-based", "Segmented by intent", "Controlled demos"],
529
- primary: { label: "Request Access", action: () => { SilentPattern.toggleModal(document.getElementById('lab-navigator'), false); document.getElementById('access-btn').click(); } },
530
- secondary: { label: "Programs", action: () => { window.location.href = "capabilities.html"; } },
531
- updated: "—"
532
- }
533
  };
 
 
 
 
 
 
 
 
 
 
534
 
535
- SilentPattern.setupLabNavigator(DOSSIERS, 'console');
 
 
 
 
 
 
 
536
  });
537
  </script>
538
  </body>
 
5
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
6
  <title>SILENTPATTERN — Console</title>
7
 
8
+ <!-- Tailwind -->
9
  <script src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <!-- Three.js + Vanta -->
12
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
13
  <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
14
 
15
+ <!-- Icons + Font -->
16
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
17
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap">
 
18
 
19
  <style>
20
+ body { font-family: 'Inter', sans-serif; }
21
+
22
+ .gradient-text {
23
+ background: linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899);
24
+ -webkit-background-clip: text;
25
+ background-clip: text;
26
+ color: transparent;
27
+ }
28
+
29
+ .neural-bg {
30
+ background: radial-gradient(circle at center, #0f172a 0%, #020617 100%);
31
+ }
32
+
33
+ .conscious-element { transition: all 0.3s ease; }
34
+ .conscious-element:hover { transform: scale(1.02); }
35
+
36
  .chat-container {
37
  height: 520px;
38
  overflow-y: auto;
39
+ scrollbar-width: thin;
40
+ scrollbar-color: #4f46e5 #1e1b4b;
41
+ }
42
+ .chat-container::-webkit-scrollbar { width: 6px; }
43
+ .chat-container::-webkit-scrollbar-track { background: #1e1b4b; }
44
+ .chat-container::-webkit-scrollbar-thumb { background-color: #4f46e5; border-radius: 3px; }
45
+
46
+ .typing-indicator::after {
47
+ content: '...';
48
+ animation: typing 1.5s infinite;
49
+ display: inline-block;
50
+ width: 20px;
51
+ text-align: left;
52
+ }
53
+ @keyframes typing {
54
+ 0% { content: '.'; }
55
+ 33% { content: '..'; }
56
+ 66% { content: '...'; }
57
+ }
58
+
59
+ .modal { transition: opacity 0.3s ease, transform 0.3s ease; }
60
+ .modal-hidden { opacity: 0; transform: translateY(20px); pointer-events: none; }
61
+ .modal-visible { opacity: 1; transform: translateY(0); }
62
+
63
+ /* Subtle “console aura” layer */
64
+ .aura {
65
+ background:
66
+ radial-gradient(circle at 25% 15%, rgba(99,102,241,0.20), transparent 42%),
67
+ radial-gradient(circle at 70% 70%, rgba(236,72,153,0.12), transparent 46%),
68
+ radial-gradient(circle at 50% 45%, rgba(139,92,246,0.10), transparent 55%);
69
  }
70
+
71
+ /* Keep focus rings consistent */
72
+ .focus-ring:focus { outline: none; box-shadow: 0 0 0 2px rgba(99,102,241,0.65); }
73
  </style>
74
  </head>
75
 
76
  <body class="bg-black text-white overflow-x-hidden">
77
+ <!-- Animated background -->
78
  <div id="vanta-bg" class="fixed top-0 left-0 w-full h-full z-0"></div>
79
 
80
+ <!-- Top bar: minimal + glyph trigger -->
81
  <nav class="relative z-10 py-6 px-8 flex justify-between items-center backdrop-blur-sm">
82
  <a href="index.html" class="flex items-center space-x-2">
83
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex items-center justify-center">
 
100
  </div>
101
  </nav>
102
 
103
+ <!-- Console Body -->
104
  <section class="relative z-10 px-6 py-16">
105
  <div class="max-w-5xl mx-auto">
106
  <div class="text-center mb-10">
 
118
  </p>
119
  </div>
120
 
121
+ <!-- The Console Window -->
122
  <div class="relative rounded-2xl border border-gray-800 bg-gray-900/30 overflow-hidden aura">
123
+ <!-- “window controls” header -->
124
  <div class="bg-gray-800/40 px-6 py-4 border-b border-gray-800 flex items-center justify-between">
125
  <div class="flex items-center">
126
  <div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div>
 
132
  <div class="w-1.5 h-1.5 rounded-full bg-white animate-pulse"></div>
133
  </div>
134
  <div>
135
+ <div class="text-sm font-medium">SILENTPATTERN Interface</div>
136
  <div class="text-xs text-gray-500">Session: local</div>
137
  </div>
138
  </div>
139
  </div>
140
 
141
+ <!-- Small mode badge -->
142
  <div class="text-xs px-2.5 py-1 rounded-full border border-indigo-500/30 text-indigo-200 bg-indigo-900/15">
143
  DRAFT
144
  </div>
145
  </div>
146
 
147
+ <!-- Messages -->
148
+ <div id="chat-messages" class="chat-container p-6 space-y-4" aria-live="polite" aria-label="Chat messages">
149
+ <div class="flex items-start">
150
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3">
151
  <i class="fas fa-robot text-white text-sm"></i>
152
  </div>
153
  <div class="bg-gray-800/70 rounded-lg p-4 max-w-[85%]">
154
+ <p class="text-gray-100">
155
+ Acknowledged. This console is a controlled interface. State your objective; I will respond with constraints, assumptions, and next steps.
156
  </p>
157
  <p class="text-gray-400 text-xs mt-2">Note: do not paste secrets or API keys into chat.</p>
158
  </div>
159
  </div>
160
  </div>
161
 
162
+ <!-- Input -->
163
  <div class="px-6 py-4 border-t border-gray-800 bg-black/10">
164
  <form id="chat-form" class="flex items-center" autocomplete="off" aria-label="Send message">
165
  <input id="chat-input" type="text"
 
182
  <i class="fas fa-file-arrow-down mr-1"></i> Export
183
  </button>
184
  </div>
185
+ <div>
186
+ <span id="typing-indicator" class="typing-indicator hidden">System is typing</span>
 
 
 
 
 
187
  </div>
188
  </div>
189
 
190
+ <!-- Small implementation note (kept subtle) -->
191
  <div class="mt-3 text-xs text-gray-600">
192
+ Integration note: do not call OpenAI directly from the browser. Use a server endpoint (example: <span class="text-gray-400">/api/chat</span>) to keep keys private.
 
193
  </div>
194
  </div>
195
  </div>
196
  </div>
197
  </section>
198
 
199
+ <!-- Minimal Footer -->
200
  <footer class="relative z-10 px-6 pb-10">
201
  <div class="max-w-5xl mx-auto border-t border-gray-800/60 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
202
+ <div class="text-sm text-gray-500">
203
+ © 2025 SILENTPATTERN. All rights reserved.
204
+ </div>
205
  <div class="text-sm text-gray-500 flex gap-6">
206
  <a href="research.html" class="hover:text-indigo-400 transition">Research</a>
207
  <a href="privacy.html" class="hover:text-indigo-400 transition">Privacy</a>
 
211
  </div>
212
  </footer>
213
 
214
+ <!-- ACCESS MODAL (same behavior as index) -->
215
  <div id="access-modal"
216
  class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm modal modal-hidden"
217
  role="dialog" aria-modal="true" aria-labelledby="access-modal-title" tabindex="-1">
 
221
  <div class="flex justify-between items-start mb-6">
222
  <div>
223
  <h3 class="text-xl font-bold" id="access-modal-title">Request Access</h3>
224
+ <p class="text-gray-400 mt-1">Limited availability for qualified researchers</p>
225
  </div>
226
  <button id="close-access-modal" class="text-gray-400 hover:text-white" aria-label="Close">
227
  <i class="fas fa-times"></i>
 
231
  <form id="access-form" class="space-y-4">
232
  <div>
233
  <label for="name" class="block text-sm font-medium mb-1">Full Name</label>
234
+ <input type="text" id="name"
235
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
236
  </div>
237
  <div>
238
  <label for="email" class="block text-sm font-medium mb-1">Email</label>
239
+ <input type="email" id="email"
240
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
241
  </div>
242
  <div>
243
  <label for="institution" class="block text-sm font-medium mb-1">Institution/Organization</label>
244
+ <input type="text" id="institution"
245
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
246
  </div>
247
  <div>
248
+ <label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
249
  <select id="purpose" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
250
  <option value="">Select a purpose</option>
251
+ <option value="research">Academic Research</option>
252
+ <option value="development">AI Development</option>
253
+ <option value="policy">Policy Research</option>
254
  <option value="partnership">Partnership</option>
255
  <option value="other">Other</option>
256
  </select>
 
266
  </div>
267
  </div>
268
 
269
+ <!-- LAB NAVIGATOR (same as index, trimmed actions for this page) -->
270
  <div id="lab-navigator"
271
  class="fixed inset-0 z-[60] bg-black/80 backdrop-blur-md modal modal-hidden"
272
  role="dialog" aria-modal="true" aria-label="Lab Navigator" tabindex="-1">
273
+
274
  <div class="absolute inset-0" data-lab-close="true"></div>
275
+
276
  <div class="relative w-full h-full flex items-center justify-center p-6">
277
  <div class="w-full max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6">
278
  <div class="relative rounded-2xl border border-gray-800 bg-gray-900/20 overflow-hidden">
 
286
  <div class="text-xs text-gray-500">Lab Navigator</div>
287
  </div>
288
  </div>
289
+
290
  <button id="lab-nav-close"
291
  class="w-9 h-9 rounded-full border border-gray-800 bg-gray-900/30 hover:bg-gray-900/50 transition flex items-center justify-center"
292
  aria-label="Close Lab Navigator">
 
295
  </div>
296
 
297
  <div class="relative p-6 min-h-[420px]">
298
+ <div class="absolute inset-0 opacity-70 pointer-events-none"
299
+ style="background: radial-gradient(circle at 30% 20%, rgba(99,102,241,0.18), transparent 45%),
300
+ radial-gradient(circle at 70% 70%, rgba(236,72,153,0.10), transparent 50%);"></div>
301
+
302
  <div class="relative grid grid-cols-1 sm:grid-cols-2 gap-3">
303
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
304
  data-dossier="start">
305
  <div class="text-sm text-gray-200 font-medium">Start Here</div>
306
+ <div class="text-xs text-gray-500 mt-1">Return to the main interface</div>
 
 
 
 
 
 
307
  </button>
308
 
309
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
310
  data-dossier="console">
311
  <div class="text-sm text-gray-200 font-medium">Console</div>
312
+ <div class="text-xs text-gray-500 mt-1">This page: controlled chat</div>
313
  </button>
314
 
315
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
316
+ data-dossier="programs">
317
+ <div class="text-sm text-gray-200 font-medium">Programs</div>
318
+ <div class="text-xs text-gray-500 mt-1">MCAP · CHAI · Quantum Lambda</div>
319
  </button>
320
 
321
  <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4"
322
+ data-dossier="ai_scientist">
323
+ <div class="text-sm text-gray-200 font-medium">AI Scientist</div>
324
+ <div class="text-xs text-gray-500 mt-1">Hypothesis → experiment → report</div>
325
  </button>
326
 
327
+ <button class="lab-node text-left rounded-xl border border-gray-800 bg-gray-900/30 hover:border-indigo-500/50 hover:bg-gray-900/45 transition p-4 sm:col-span-2"
328
  data-dossier="access">
329
  <div class="text-sm text-gray-200 font-medium">Access</div>
330
+ <div class="text-xs text-gray-500 mt-1">Request access to demos and research</div>
331
  </button>
332
  </div>
333
+
334
  <div class="relative mt-6 text-xs text-gray-500">
335
  Tip: Press <span class="text-gray-300">Esc</span> to close.
336
  </div>
 
342
  <div class="flex items-start justify-between gap-4">
343
  <div>
344
  <div id="dossier-title" class="text-lg font-semibold text-gray-100">Lab Dossier</div>
345
+ <div id="dossier-subtitle" class="text-xs text-gray-500 mt-1">Select a node to open a file.</div>
346
  </div>
347
  <div id="dossier-status"
348
  class="text-xs px-2.5 py-1 rounded-full border border-indigo-500/30 text-indigo-200 bg-indigo-900/15">
349
+ DRAFT
350
  </div>
351
  </div>
352
  </div>
353
 
354
+ <div class="p-6 space-y-5 max-h-[560px] overflow-auto"
355
+ style="scrollbar-width: thin; scrollbar-color: #4f46e5 #1e1b4b;">
356
+ <div id="dossier-body" class="text-sm text-gray-300 leading-relaxed">
357
+ This console is designed for controlled interaction. Dossiers expose depth by intent.
358
+ </div>
359
+
360
  <div class="rounded-xl border border-gray-800 bg-black/20 p-4">
361
  <div class="text-xs text-gray-400 uppercase tracking-wider mb-2">Evidence Capsule</div>
362
+ <ul id="dossier-evidence" class="text-sm text-gray-300 space-y-1">
363
+ <li class="text-gray-500">No dossier selected.</li>
364
+ </ul>
365
  </div>
366
+
367
  <div class="flex flex-col sm:flex-row gap-3">
368
  <button id="dossier-primary"
369
  class="flex-1 px-5 py-3 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:opacity-90 transition">
 
374
  View Note
375
  </button>
376
  </div>
377
+
378
+ <div id="dossier-meta" class="text-xs text-gray-500">
379
+ Last updated: <span class="text-gray-300">—</span>
380
+ </div>
381
  </div>
382
  </div>
383
+
384
  </div>
385
  </div>
386
  </div>
387
 
 
388
  <script>
389
+ /* -------------------------------------------------------------
390
+ VANTA BACKGROUND
391
+ ------------------------------------------------------------- */
392
+ const vantaEffect = VANTA.NET({
393
+ el: "#vanta-bg",
394
+ mouseControls: true,
395
+ touchControls: true,
396
+ gyroControls: false,
397
+ minHeight: 200.00,
398
+ minWidth: 200.00,
399
+ scale: 1.00,
400
+ scaleMobile: 1.00,
401
+ color: 0x4f46e5,
402
+ backgroundColor: 0x020617,
403
+ points: 12.00,
404
+ maxDistance: 20.00,
405
+ spacing: 15.00
406
+ });
407
+ window.addEventListener('resize', () => vantaEffect.resize());
408
+
409
+ /* -------------------------------------------------------------
410
+ MODAL ACCESSIBILITY HELPERS
411
+ ------------------------------------------------------------- */
412
+ function trapFocus(modal) {
413
+ const focusable = modal.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
414
+ if (!focusable.length) return;
415
+ const first = focusable[0];
416
+ const last = focusable[focusable.length - 1];
417
+
418
+ function handler(e) {
419
+ if (e.key === 'Tab') {
420
+ if (e.shiftKey) {
421
+ if (document.activeElement === first) { e.preventDefault(); last.focus(); }
422
+ } else {
423
+ if (document.activeElement === last) { e.preventDefault(); first.focus(); }
424
+ }
425
+ } else if (e.key === 'Escape') {
426
+ toggleModal(modal, false);
427
  }
428
  }
429
+ modal.addEventListener('keydown', handler);
430
+ modal._focusHandler = handler;
431
+ }
432
+
433
+ function untrapFocus(modal) {
434
+ if (modal._focusHandler) {
435
+ modal.removeEventListener('keydown', modal._focusHandler);
436
+ delete modal._focusHandler;
437
+ }
438
+ }
439
+
440
+ const toggleModal = (modal, show) => {
441
+ if (show) {
442
+ modal.classList.remove('modal-hidden');
443
+ modal.classList.add('modal-visible');
444
+ document.body.style.overflow = 'hidden';
445
+ setTimeout(() => { modal.focus(); trapFocus(modal); }, 0);
446
+ } else {
447
+ modal.classList.remove('modal-visible');
448
+ modal.classList.add('modal-hidden');
449
+ document.body.style.overflow = '';
450
+ untrapFocus(modal);
451
  }
452
+ };
453
+
454
+ /* -------------------------------------------------------------
455
+ ACCESS MODAL
456
+ ------------------------------------------------------------- */
457
+ const accessModal = document.getElementById('access-modal');
458
+ const accessBtn = document.getElementById('access-btn');
459
+ const closeAccessModal = document.getElementById('close-access-modal');
460
+
461
+ accessBtn.addEventListener('click', () => {
462
+ toggleModal(accessModal, true);
463
+ setTimeout(() => document.getElementById('name').focus(), 50);
464
+ });
465
 
466
+ closeAccessModal.addEventListener('click', () => toggleModal(accessModal, false));
 
 
 
 
467
 
468
+ accessModal.addEventListener('click', (e) => {
469
+ if (e.target === accessModal) toggleModal(accessModal, false);
470
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
471
 
472
+ document.getElementById('access-form').addEventListener('submit', (e) => {
473
+ e.preventDefault();
474
+ const name = document.getElementById('name').value.trim();
475
+ const email = document.getElementById('email').value.trim();
476
+ const institution = document.getElementById('institution').value.trim();
477
+ const purpose = document.getElementById('purpose').value;
478
+
479
+ if (!name || !email || !institution || !purpose) {
480
+ alert('Please fill in all fields.');
481
+ return;
482
  }
483
+ alert('Request received. You will be contacted after review.');
484
+ e.target.reset();
485
+ toggleModal(accessModal, false);
486
+ });
487
 
488
+ /* -------------------------------------------------------------
489
+ LAB NAVIGATOR
490
+ ------------------------------------------------------------- */
491
+ const labNav = document.getElementById('lab-navigator');
492
+ const labNavBtn = document.getElementById('lab-nav-btn');
493
+ const labNavClose = document.getElementById('lab-nav-close');
494
+
495
+ function openLabNav() { toggleModal(labNav, true); setTimeout(() => labNav.focus(), 0); }
496
+ function closeLabNav() { toggleModal(labNav, false); }
497
+
498
+ labNavBtn.addEventListener('click', openLabNav);
499
+ labNavClose.addEventListener('click', closeLabNav);
500
+
501
+ labNav.addEventListener('click', (e) => {
502
+ const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
503
+ if (shouldClose) closeLabNav();
504
+ });
505
+
506
+ const DOSSIERS = {
507
+ start: {
508
+ title: "Start Here",
509
+ subtitle: "Return to the main interface",
510
+ status: "ACTIVE",
511
+ body: "SILENTPATTERN is presented as a lab interface: minimal surface, deep artifacts. The index is the entrypoint.",
512
+ evidence: ["Public entry layer", "Dossiers reveal depth", "Controlled demos by access"],
513
+ primary: { label: "Go to Index", action: () => { window.location.href = "index.html"; } },
514
+ secondary: { label: "Research", action: () => { window.location.href = "research.html"; } },
515
+ updated: "—"
516
+ },
517
+ console: {
518
+ title: "Console",
519
+ subtitle: "Controlled interaction channel",
520
+ status: "DRAFT",
521
+ body: "This console is a staging environment. It will connect to a server endpoint and produce auditable outputs (transcripts, notes, reports).",
522
+ evidence: ["Client: UI only", "Server: keys + policy + logging", "Exportable transcript"],
523
+ primary: { label: "Close Navigator", action: () => { closeLabNav(); } },
524
+ secondary: { label: "Export", action: () => { closeLabNav(); document.getElementById('export-btn').click(); } },
525
+ updated: "—"
526
+ },
527
+ programs: {
528
+ title: "Programs",
529
+ subtitle: "MCAP · CHAI · Quantum Lambda",
530
+ status: "DRAFT",
531
+ body: "Programs are introduced as research artifacts with maturity levels (Concept → Prototype → Validated) and evidence capsules.",
532
+ evidence: ["MCAP: causal abstraction principle", "CHAI: forecasting/regime modeling", "Quantum Lambda: high-frequency decision systems"],
533
+ primary: { label: "Open Programs", action: () => { window.location.href = "capabilities.html"; } },
534
+ secondary: { label: "Research Notes", action: () => { window.location.href = "research.html"; } },
535
+ updated: "—"
536
+ },
537
+ ai_scientist: {
538
+ title: "AI Scientist",
539
+ subtitle: "Hypothesis → experiment → report",
540
+ status: "DRAFT",
541
+ body: "The AI Scientist is positioned as the lab’s instrument: it standardizes experiments, enforces reproducibility, and produces reports with uncertainty.",
542
+ evidence: ["Experiment harnesses", "Evaluation baselines", "Report generation"],
543
+ primary: { label: "View Research", action: () => { window.location.href = "research.html"; } },
544
+ secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
545
+ updated: "—"
546
+ },
547
+ access: {
548
+ title: "Access",
549
+ subtitle: "Request access to demos and research",
550
+ status: "ACTIVE",
551
+ body: "Access is curated. The objective is qualified users, high-signal feedback, and responsible scaling.",
552
+ evidence: ["Application-based", "Segmented by intent", "Controlled demos"],
553
+ primary: { label: "Request Access", action: () => { closeLabNav(); accessBtn.click(); } },
554
+ secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
555
+ updated: "—"
556
  }
557
+ };
558
+
559
+ const dossierTitle = document.getElementById('dossier-title');
560
+ const dossierSubtitle = document.getElementById('dossier-subtitle');
561
+ const dossierStatus = document.getElementById('dossier-status');
562
+ const dossierBody = document.getElementById('dossier-body');
563
+ const dossierEvidence = document.getElementById('dossier-evidence');
564
+ const dossierPrimary = document.getElementById('dossier-primary');
565
+ const dossierSecondary = document.getElementById('dossier-secondary');
566
+ const dossierMeta = document.getElementById('dossier-meta');
567
+
568
+ function renderDossier(key) {
569
+ const d = DOSSIERS[key];
570
+ if (!d) return;
571
+
572
+ dossierTitle.textContent = d.title;
573
+ dossierSubtitle.textContent = d.subtitle;
574
+ dossierStatus.textContent = d.status;
575
+ dossierBody.textContent = d.body;
576
+
577
+ dossierEvidence.innerHTML = "";
578
+ d.evidence.forEach(item => {
579
+ const li = document.createElement('li');
580
+ li.textContent = item;
581
+ dossierEvidence.appendChild(li);
582
+ });
583
 
584
+ dossierPrimary.textContent = d.primary.label;
585
+ dossierPrimary.onclick = d.primary.action;
586
+
587
+ dossierSecondary.textContent = d.secondary.label;
588
+ dossierSecondary.onclick = d.secondary.action;
589
+
590
+ dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${d.updated}</span>`;
591
+ }
592
+
593
+ document.querySelectorAll('.lab-node').forEach(btn => {
594
+ btn.addEventListener('click', () => renderDossier(btn.getAttribute('data-dossier')));
595
+ });
596
+
597
+ /* -------------------------------------------------------------
598
+ CHAT (SECURE-BY-DESIGN CLIENT)
599
+ - This page does NOT embed API keys.
600
+ - It calls a server endpoint: POST /api/chat
601
+ - If not available, it returns a local “demo” response.
602
+ ------------------------------------------------------------- */
603
+ const chatForm = document.getElementById('chat-form');
604
+ const chatInput = document.getElementById('chat-input');
605
+ const chatMessages = document.getElementById('chat-messages');
606
+ const typingIndicator = document.getElementById('typing-indicator');
607
+ const sendBtn = document.getElementById('send-btn');
608
+ const clearBtn = document.getElementById('clear-btn');
609
+ const exportBtn = document.getElementById('export-btn');
610
+
611
+ const transcript = []; // {role:'user'|'system', content:string, ts:number}
612
+
613
+ function escapeHtml(str) {
614
+ return str
615
+ .replaceAll('&', '&amp;')
616
+ .replaceAll('<', '&lt;')
617
+ .replaceAll('>', '&gt;')
618
+ .replaceAll('"', '&quot;')
619
+ .replaceAll("'", '&#039;');
620
+ }
621
+
622
+ function addMessage(text, isUser = false) {
623
+ const safe = escapeHtml(text);
624
+ const messageDiv = document.createElement('div');
625
+ messageDiv.className = `flex items-start ${isUser ? 'justify-end' : ''}`;
626
 
627
+ if (!isUser) {
628
+ messageDiv.innerHTML = `
629
+ <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3" aria-hidden="true">
630
+ <i class="fas fa-robot text-white text-sm"></i>
631
+ </div>
632
+ <div class="bg-gray-800/70 rounded-lg p-4 max-w-[85%]">
633
+ <p class="text-gray-100">${safe}</p>
634
+ </div>
635
+ `;
636
+ } else {
637
+ messageDiv.innerHTML = `
638
+ <div class="bg-indigo-900/50 rounded-lg p-4 max-w-[85%]">
639
+ <p class="text-gray-100">${safe}</p>
640
+ </div>
641
+ `;
642
  }
643
 
644
+ chatMessages.appendChild(messageDiv);
645
+ chatMessages.scrollTop = chatMessages.scrollHeight;
646
+
647
+ transcript.push({ role: isUser ? 'user' : 'system', content: text, ts: Date.now() });
648
+ }
649
+
650
+ function setBusy(isBusy) {
651
+ if (isBusy) {
652
+ typingIndicator.classList.remove('hidden');
653
+ sendBtn.disabled = true;
654
+ } else {
655
+ typingIndicator.classList.add('hidden');
656
+ sendBtn.disabled = false;
657
  }
658
+ }
659
 
660
+ async function callServerChat(userMessage) {
661
+ const res = await fetch('/api/chat', {
662
+ method: 'POST',
663
+ headers: { 'Content-Type': 'application/json' },
664
+ body: JSON.stringify({
665
+ message: userMessage,
666
+ // Optional: pass context or selected program later
667
+ meta: { page: 'chat.html', product: 'silentpattern' }
668
+ })
 
 
 
 
 
 
 
 
 
 
 
669
  });
670
 
671
+ if (!res.ok) {
672
+ const txt = await res.text().catch(() => '');
673
+ throw new Error(`Server error (${res.status}). ${txt}`.trim());
674
+ }
 
 
 
675
 
676
+ const data = await res.json();
677
+ if (!data || typeof data.reply !== 'string') {
678
+ throw new Error('Invalid response format from /api/chat');
679
+ }
680
+ return data.reply;
681
+ }
 
 
 
 
 
 
 
 
 
 
 
682
 
683
+ function localDemoResponse(userMessage) {
684
+ const msg = userMessage.toLowerCase();
685
+ if (msg.includes('mcap')) {
686
+ return "MCAP acknowledged. Provide: (1) the abstraction mapping you propose, (2) how you test causal fidelity, (3) baseline comparisons. I will draft a research-note structure next.";
687
+ }
688
+ if (msg.includes('chai')) {
689
+ return "CHAI acknowledged. I will assume a regime-aware forecasting stack. Specify assets, horizon, labeling rules, and walk-forward protocol. Then we can define an evaluation harness.";
690
+ }
691
+ if (msg.includes('quantum lambda')) {
692
+ return "Quantum Lambda acknowledged. For HFT, the first gate is market microstructure constraints and realistic latency/slippage. Share the execution assumptions and risk limits before discussing accuracy.";
693
+ }
694
+ return "Acknowledged. State (a) objective, (b) constraints, (c) what evidence you have today. I will respond with a plan and a minimal next experiment.";
695
+ }
696
+
697
+ chatForm.addEventListener('submit', async (e) => {
698
+ e.preventDefault();
699
+ const message = chatInput.value.trim();
700
+ if (!message) return;
701
+
702
+ addMessage(message, true);
703
+ chatInput.value = '';
704
+ setBusy(true);
705
+
706
+ try {
707
+ // Preferred path: server endpoint
708
+ const reply = await callServerChat(message);
709
+ addMessage(reply, false);
710
+ } catch (err) {
711
+ // Fallback: demo response (keeps UI usable during setup)
712
+ addMessage(localDemoResponse(message), false);
713
+ } finally {
714
+ setBusy(false);
715
+ }
716
+ });
717
+
718
+ clearBtn.addEventListener('click', () => {
719
+ // Keep the first system greeting, clear the rest
720
+ const nodes = Array.from(chatMessages.children);
721
+ for (let i = 1; i < nodes.length; i++) nodes[i].remove();
722
+ transcript.length = 0;
723
+ transcript.push({ role: 'system', content: 'Session cleared.', ts: Date.now() });
724
+ addMessage('Session cleared.', false);
725
+ });
726
+
727
+ exportBtn.addEventListener('click', () => {
728
+ const payload = {
729
+ product: "SILENTPATTERN",
730
+ page: "chat.html",
731
+ exported_at: new Date().toISOString(),
732
+ transcript
 
 
 
 
 
 
 
 
 
 
 
 
733
  };
734
+ const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
735
+ const url = URL.createObjectURL(blob);
736
+ const a = document.createElement('a');
737
+ a.href = url;
738
+ a.download = `silentpattern_transcript_${Date.now()}.json`;
739
+ document.body.appendChild(a);
740
+ a.click();
741
+ a.remove();
742
+ URL.revokeObjectURL(url);
743
+ });
744
 
745
+ /* -------------------------------------------------------------
746
+ GLOBAL ESC: close whichever overlay is open
747
+ ------------------------------------------------------------- */
748
+ document.addEventListener('keydown', (e) => {
749
+ if (e.key === 'Escape') {
750
+ if (labNav && !labNav.classList.contains('modal-hidden')) closeLabNav();
751
+ if (accessModal && !accessModal.classList.contains('modal-hidden')) toggleModal(accessModal, false);
752
+ }
753
  });
754
  </script>
755
  </body>