Testing347 commited on
Commit
25e2014
·
verified ·
1 Parent(s): cc952b5

Update chat.html

Browse files
Files changed (1) hide show
  1. chat.html +243 -433
chat.html CHANGED
@@ -5,73 +5,25 @@
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
- .conscious-element { transition: all 0.3s ease; }
30
- .conscious-element:hover { transform: scale(1.02); }
31
-
32
  .chat-container {
33
  height: 520px;
34
  overflow-y: auto;
35
- scrollbar-width: thin;
36
- scrollbar-color: #4f46e5 #1e1b4b;
37
- }
38
- .chat-container::-webkit-scrollbar { width: 6px; }
39
- .chat-container::-webkit-scrollbar-track { background: #1e1b4b; }
40
- .chat-container::-webkit-scrollbar-thumb { background-color: #4f46e5; border-radius: 3px; }
41
-
42
- .typing-indicator::after {
43
- content: '...';
44
- animation: typing 1.5s infinite;
45
- display: inline-block;
46
- width: 20px;
47
- text-align: left;
48
  }
49
- @keyframes typing {
50
- 0% { content: '.'; }
51
- 33% { content: '..'; }
52
- 66% { content: '...'; }
53
- }
54
-
55
- .modal { transition: opacity 0.3s ease, transform 0.3s ease; }
56
- .modal-hidden { opacity: 0; transform: translateY(20px); pointer-events: none; }
57
- .modal-visible { opacity: 1; transform: translateY(0); }
58
-
59
- .aura {
60
- background:
61
- radial-gradient(circle at 25% 15%, rgba(99,102,241,0.20), transparent 42%),
62
- radial-gradient(circle at 70% 70%, rgba(236,72,153,0.12), transparent 46%),
63
- radial-gradient(circle at 50% 45%, rgba(139,92,246,0.10), transparent 55%);
64
- }
65
-
66
- .focus-ring:focus { outline: none; box-shadow: 0 0 0 2px rgba(99,102,241,0.65); }
67
  </style>
68
  </head>
69
 
70
  <body class="bg-black text-white overflow-x-hidden">
71
- <!-- Animated background -->
72
  <div id="vanta-bg" class="fixed top-0 left-0 w-full h-full z-0"></div>
73
 
74
- <!-- Top bar -->
75
  <nav class="relative z-10 py-6 px-8 flex justify-between items-center backdrop-blur-sm">
76
  <a href="index.html" class="flex items-center space-x-2">
77
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex items-center justify-center">
@@ -94,7 +46,6 @@
94
  </div>
95
  </nav>
96
 
97
- <!-- Console -->
98
  <section class="relative z-10 px-6 py-16">
99
  <div class="max-w-5xl mx-auto">
100
  <div class="text-center mb-10">
@@ -124,7 +75,7 @@
124
  <div class="w-1.5 h-1.5 rounded-full bg-white animate-pulse"></div>
125
  </div>
126
  <div>
127
- <div class="text-sm font-medium">SILENTPATTERN Interface</div>
128
  <div class="text-xs text-gray-500">Session: local</div>
129
  </div>
130
  </div>
@@ -135,14 +86,14 @@
135
  </div>
136
  </div>
137
 
138
- <div id="chat-messages" class="chat-container p-6 space-y-4" aria-live="polite" aria-label="Chat messages">
139
- <div class="flex items-start" data-seed="system">
140
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3">
141
  <i class="fas fa-robot text-white text-sm"></i>
142
  </div>
143
  <div class="bg-gray-800/70 rounded-lg p-4 max-w-[85%]">
144
- <p class="text-gray-100" id="seed-system-text">
145
- Acknowledged. This console is a controlled interface. State your objective; I will respond with constraints, assumptions, and next steps.
146
  </p>
147
  <p class="text-gray-400 text-xs mt-2">Note: do not paste secrets or API keys into chat.</p>
148
  </div>
@@ -171,14 +122,19 @@
171
  <i class="fas fa-file-arrow-down mr-1"></i> Export
172
  </button>
173
  </div>
174
- <div>
175
- <span id="typing-indicator" class="typing-indicator hidden">System is typing</span>
 
 
 
 
 
176
  </div>
177
  </div>
178
 
179
  <div class="mt-3 text-xs text-gray-600">
180
- Integration note: do not call OpenAI directly from the browser. Use a server endpoint (example:
181
- <span class="text-gray-400">/api/chat</span>) to keep keys private.
182
  </div>
183
  </div>
184
  </div>
@@ -197,7 +153,7 @@
197
  </div>
198
  </footer>
199
 
200
- <!-- ACCESS MODAL -->
201
  <div id="access-modal"
202
  class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm modal modal-hidden"
203
  role="dialog" aria-modal="true" aria-labelledby="access-modal-title" tabindex="-1">
@@ -207,7 +163,7 @@
207
  <div class="flex justify-between items-start mb-6">
208
  <div>
209
  <h3 class="text-xl font-bold" id="access-modal-title">Request Access</h3>
210
- <p class="text-gray-400 mt-1">Limited availability for qualified researchers</p>
211
  </div>
212
  <button id="close-access-modal" class="text-gray-400 hover:text-white" aria-label="Close">
213
  <i class="fas fa-times"></i>
@@ -228,12 +184,11 @@
228
  <input type="text" id="institution" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
229
  </div>
230
  <div>
231
- <label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
232
  <select id="purpose" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring">
233
  <option value="">Select a purpose</option>
234
- <option value="research">Academic Research</option>
235
- <option value="development">AI Development</option>
236
- <option value="policy">Policy Research</option>
237
  <option value="partnership">Partnership</option>
238
  <option value="other">Other</option>
239
  </select>
@@ -249,13 +204,11 @@
249
  </div>
250
  </div>
251
 
252
- <!-- LAB NAVIGATOR -->
253
  <div id="lab-navigator"
254
  class="fixed inset-0 z-[60] bg-black/80 backdrop-blur-md modal modal-hidden"
255
  role="dialog" aria-modal="true" aria-label="Lab Navigator" tabindex="-1">
256
-
257
  <div class="absolute inset-0" data-lab-close="true"></div>
258
-
259
  <div class="relative w-full h-full flex items-center justify-center p-6">
260
  <div class="w-full max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6">
261
  <div class="relative rounded-2xl border border-gray-800 bg-gray-900/20 overflow-hidden">
@@ -269,7 +222,6 @@
269
  <div class="text-xs text-gray-500">Lab Navigator</div>
270
  </div>
271
  </div>
272
-
273
  <button id="lab-nav-close"
274
  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"
275
  aria-label="Close Lab Navigator">
@@ -278,42 +230,43 @@
278
  </div>
279
 
280
  <div class="relative p-6 min-h-[420px]">
281
- <div class="absolute inset-0 opacity-70 pointer-events-none"
282
- style="background: radial-gradient(circle at 30% 20%, rgba(99,102,241,0.18), transparent 45%),
283
- radial-gradient(circle at 70% 70%, rgba(236,72,153,0.10), transparent 50%);"></div>
284
-
285
  <div class="relative grid grid-cols-1 sm:grid-cols-2 gap-3">
286
  <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"
287
  data-dossier="start">
288
  <div class="text-sm text-gray-200 font-medium">Start Here</div>
289
- <div class="text-xs text-gray-500 mt-1">Return to the main interface</div>
 
 
 
 
 
 
290
  </button>
291
 
292
  <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"
293
  data-dossier="console">
294
  <div class="text-sm text-gray-200 font-medium">Console</div>
295
- <div class="text-xs text-gray-500 mt-1">This page: controlled chat</div>
296
  </button>
297
 
298
  <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"
299
- data-dossier="programs">
300
- <div class="text-sm text-gray-200 font-medium">Programs</div>
301
- <div class="text-xs text-gray-500 mt-1">MCAP · CHAI · Quantum Lambda</div>
302
  </button>
303
 
304
  <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"
305
- data-dossier="ai_scientist">
306
- <div class="text-sm text-gray-200 font-medium">AI Scientist</div>
307
- <div class="text-xs text-gray-500 mt-1">Hypothesis → experiment → report</div>
308
  </button>
309
 
310
- <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"
311
  data-dossier="access">
312
  <div class="text-sm text-gray-200 font-medium">Access</div>
313
- <div class="text-xs text-gray-500 mt-1">Request access to demos and research</div>
314
  </button>
315
  </div>
316
-
317
  <div class="relative mt-6 text-xs text-gray-500">
318
  Tip: Press <span class="text-gray-300">Esc</span> to close.
319
  </div>
@@ -325,28 +278,21 @@
325
  <div class="flex items-start justify-between gap-4">
326
  <div>
327
  <div id="dossier-title" class="text-lg font-semibold text-gray-100">Lab Dossier</div>
328
- <div id="dossier-subtitle" class="text-xs text-gray-500 mt-1">Select a node to open a file.</div>
329
  </div>
330
  <div id="dossier-status"
331
  class="text-xs px-2.5 py-1 rounded-full border border-indigo-500/30 text-indigo-200 bg-indigo-900/15">
332
- DRAFT
333
  </div>
334
  </div>
335
  </div>
336
 
337
- <div class="p-6 space-y-5 max-h-[560px] overflow-auto"
338
- style="scrollbar-width: thin; scrollbar-color: #4f46e5 #1e1b4b;">
339
- <div id="dossier-body" class="text-sm text-gray-300 leading-relaxed">
340
- This console is designed for controlled interaction. Dossiers expose depth by intent.
341
- </div>
342
-
343
  <div class="rounded-xl border border-gray-800 bg-black/20 p-4">
344
  <div class="text-xs text-gray-400 uppercase tracking-wider mb-2">Evidence Capsule</div>
345
- <ul id="dossier-evidence" class="text-sm text-gray-300 space-y-1">
346
- <li class="text-gray-500">No dossier selected.</li>
347
- </ul>
348
  </div>
349
-
350
  <div class="flex flex-col sm:flex-row gap-3">
351
  <button id="dossier-primary"
352
  class="flex-1 px-5 py-3 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:opacity-90 transition">
@@ -357,373 +303,237 @@
357
  View Note
358
  </button>
359
  </div>
360
-
361
- <div id="dossier-meta" class="text-xs text-gray-500">
362
- Last updated: <span class="text-gray-300">—</span>
363
- </div>
364
  </div>
365
  </div>
366
-
367
  </div>
368
  </div>
369
  </div>
370
 
 
371
  <script>
372
- /* VANTA */
373
- const vantaEffect = VANTA.NET({
374
- el: "#vanta-bg",
375
- mouseControls: true,
376
- touchControls: true,
377
- gyroControls: false,
378
- minHeight: 200.00,
379
- minWidth: 200.00,
380
- scale: 1.00,
381
- scaleMobile: 1.00,
382
- color: 0x4f46e5,
383
- backgroundColor: 0x020617,
384
- points: 12.00,
385
- maxDistance: 20.00,
386
- spacing: 15.00
387
- });
388
- window.addEventListener('resize', () => vantaEffect.resize());
389
-
390
- /* MODAL ACCESSIBILITY */
391
- function trapFocus(modal) {
392
- const focusable = modal.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
393
- if (!focusable.length) return;
394
- const first = focusable[0];
395
- const last = focusable[focusable.length - 1];
396
-
397
- function handler(e) {
398
- if (e.key === 'Tab') {
399
- if (e.shiftKey) {
400
- if (document.activeElement === first) { e.preventDefault(); last.focus(); }
401
- } else {
402
- if (document.activeElement === last) { e.preventDefault(); first.focus(); }
403
- }
404
- } else if (e.key === 'Escape') {
405
- toggleModal(modal, false);
406
  }
407
  }
408
- modal.addEventListener('keydown', handler);
409
- modal._focusHandler = handler;
410
- }
411
- function untrapFocus(modal) {
412
- if (modal._focusHandler) {
413
- modal.removeEventListener('keydown', modal._focusHandler);
414
- delete modal._focusHandler;
415
- }
416
- }
417
- const toggleModal = (modal, show) => {
418
- if (show) {
419
- modal.classList.remove('modal-hidden');
420
- modal.classList.add('modal-visible');
421
- document.body.style.overflow = 'hidden';
422
- setTimeout(() => { modal.focus(); trapFocus(modal); }, 0);
423
- } else {
424
- modal.classList.remove('modal-visible');
425
- modal.classList.add('modal-hidden');
426
- document.body.style.overflow = '';
427
- untrapFocus(modal);
428
- }
429
- };
430
-
431
- /* ACCESS MODAL */
432
- const accessModal = document.getElementById('access-modal');
433
- const accessBtn = document.getElementById('access-btn');
434
- const closeAccessModal = document.getElementById('close-access-modal');
435
 
436
- accessBtn.addEventListener('click', () => {
437
- toggleModal(accessModal, true);
438
- setTimeout(() => document.getElementById('name').focus(), 50);
439
- });
440
- closeAccessModal.addEventListener('click', () => toggleModal(accessModal, false));
441
- accessModal.addEventListener('click', (e) => { if (e.target === accessModal) toggleModal(accessModal, false); });
442
-
443
- document.getElementById('access-form').addEventListener('submit', (e) => {
444
- e.preventDefault();
445
- const name = document.getElementById('name').value.trim();
446
- const email = document.getElementById('email').value.trim();
447
- const institution = document.getElementById('institution').value.trim();
448
- const purpose = document.getElementById('purpose').value;
449
- if (!name || !email || !institution || !purpose) {
450
- alert('Please fill in all fields.');
451
- return;
452
  }
453
- alert('Request received. You will be contacted after review.');
454
- e.target.reset();
455
- toggleModal(accessModal, false);
456
- });
457
-
458
- /* LAB NAVIGATOR */
459
- const labNav = document.getElementById('lab-navigator');
460
- const labNavBtn = document.getElementById('lab-nav-btn');
461
- const labNavClose = document.getElementById('lab-nav-close');
462
 
463
- function openLabNav() { toggleModal(labNav, true); setTimeout(() => labNav.focus(), 0); }
464
- function closeLabNav() { toggleModal(labNav, false); }
 
 
 
465
 
466
- labNavBtn.addEventListener('click', openLabNav);
467
- labNavClose.addEventListener('click', closeLabNav);
468
- labNav.addEventListener('click', (e) => {
469
- const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
470
- if (shouldClose) closeLabNav();
471
- });
 
 
 
 
 
 
 
 
 
 
472
 
473
- const DOSSIERS = {
474
- start: {
475
- title: "Start Here",
476
- subtitle: "Return to the main interface",
477
- status: "ACTIVE",
478
- body: "SILENTPATTERN is presented as a lab interface: minimal surface, deep artifacts. The index is the entrypoint.",
479
- evidence: ["Public entry layer", "Dossiers reveal depth", "Controlled demos by access"],
480
- primary: { label: "Go to Index", action: () => { window.location.href = "index.html"; } },
481
- secondary: { label: "Research", action: () => { window.location.href = "research.html"; } },
482
- updated: "—"
483
- },
484
- console: {
485
- title: "Console",
486
- subtitle: "Controlled interaction channel",
487
- status: "DRAFT",
488
- body: "This console is a staging environment. It will connect to a server endpoint and produce auditable outputs (transcripts, notes, reports).",
489
- evidence: ["Client: UI only", "Server: keys + policy + logging", "Exportable transcript"],
490
- primary: { label: "Close Navigator", action: () => { closeLabNav(); } },
491
- secondary: { label: "Export", action: () => { closeLabNav(); document.getElementById('export-btn').click(); } },
492
- updated: "—"
493
- },
494
- programs: {
495
- title: "Programs",
496
- subtitle: "MCAP · CHAI · Quantum Lambda",
497
- status: "DRAFT",
498
- body: "Programs are introduced as research artifacts with maturity levels (Concept → Prototype → Validated) and evidence capsules.",
499
- evidence: ["MCAP: causal abstraction principle", "CHAI: forecasting/regime modeling", "Quantum Lambda: high-frequency decision systems"],
500
- primary: { label: "Open Programs", action: () => { window.location.href = "capabilities.html"; } },
501
- secondary: { label: "Research Notes", action: () => { window.location.href = "research.html"; } },
502
- updated: "—"
503
- },
504
- ai_scientist: {
505
- title: "AI Scientist",
506
- subtitle: "Hypothesis → experiment → report",
507
- status: "DRAFT",
508
- body: "The AI Scientist is positioned as the lab’s instrument: it standardizes experiments, enforces reproducibility, and produces reports with uncertainty.",
509
- evidence: ["Experiment harnesses", "Evaluation baselines", "Report generation"],
510
- primary: { label: "View Research", action: () => { window.location.href = "research.html"; } },
511
- secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
512
- updated: "—"
513
- },
514
- access: {
515
- title: "Access",
516
- subtitle: "Request access to demos and research",
517
- status: "ACTIVE",
518
- body: "Access is curated. The objective is qualified users, high-signal feedback, and responsible scaling.",
519
- evidence: ["Application-based", "Segmented by intent", "Controlled demos"],
520
- primary: { label: "Request Access", action: () => { closeLabNav(); accessBtn.click(); } },
521
- secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
522
- updated: "—"
523
  }
524
- };
525
-
526
- const dossierTitle = document.getElementById('dossier-title');
527
- const dossierSubtitle = document.getElementById('dossier-subtitle');
528
- const dossierStatus = document.getElementById('dossier-status');
529
- const dossierBody = document.getElementById('dossier-body');
530
- const dossierEvidence = document.getElementById('dossier-evidence');
531
- const dossierPrimary = document.getElementById('dossier-primary');
532
- const dossierSecondary = document.getElementById('dossier-secondary');
533
- const dossierMeta = document.getElementById('dossier-meta');
534
-
535
- function renderDossier(key) {
536
- const d = DOSSIERS[key];
537
- if (!d) return;
538
- dossierTitle.textContent = d.title;
539
- dossierSubtitle.textContent = d.subtitle;
540
- dossierStatus.textContent = d.status;
541
- dossierBody.textContent = d.body;
542
-
543
- dossierEvidence.innerHTML = "";
544
- d.evidence.forEach(item => {
545
- const li = document.createElement('li');
546
- li.textContent = item;
547
- dossierEvidence.appendChild(li);
548
- });
549
-
550
- dossierPrimary.textContent = d.primary.label;
551
- dossierPrimary.onclick = d.primary.action;
552
-
553
- dossierSecondary.textContent = d.secondary.label;
554
- dossierSecondary.onclick = d.secondary.action;
555
-
556
- dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${d.updated}</span>`;
557
- }
558
-
559
- document.querySelectorAll('.lab-node').forEach(btn => {
560
- btn.addEventListener('click', () => renderDossier(btn.getAttribute('data-dossier')));
561
- });
562
-
563
- /* CHAT: secure-by-design client */
564
- const API_ENDPOINT = "/api/chat"; // change if your deployment requires a prefix
565
-
566
- const chatForm = document.getElementById('chat-form');
567
- const chatInput = document.getElementById('chat-input');
568
- const chatMessages = document.getElementById('chat-messages');
569
- const typingIndicator = document.getElementById('typing-indicator');
570
- const sendBtn = document.getElementById('send-btn');
571
- const clearBtn = document.getElementById('clear-btn');
572
- const exportBtn = document.getElementById('export-btn');
573
 
574
- const transcript = []; // {role:'user'|'system', content:string, ts:number}
575
-
576
- function seedTranscript() {
577
- const seed = document.getElementById('seed-system-text');
578
- if (seed && seed.textContent.trim()) {
579
- transcript.push({ role: 'system', content: seed.textContent.trim(), ts: Date.now() });
 
 
580
  }
581
- }
582
- seedTranscript();
583
-
584
- function escapeHtml(str) {
585
- return str
586
- .replaceAll('&', '&amp;')
587
- .replaceAll('<', '&lt;')
588
- .replaceAll('>', '&gt;')
589
- .replaceAll('"', '&quot;')
590
- .replaceAll("'", '&#039;');
591
- }
592
 
593
- function addMessage(text, isUser = false) {
594
- const safe = escapeHtml(text);
595
- const messageDiv = document.createElement('div');
596
- messageDiv.className = `flex items-start ${isUser ? 'justify-end' : ''}`;
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
- if (!isUser) {
599
- messageDiv.innerHTML = `
600
- <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3" aria-hidden="true">
601
- <i class="fas fa-robot text-white text-sm"></i>
602
- </div>
603
- <div class="bg-gray-800/70 rounded-lg p-4 max-w-[85%]">
604
- <p class="text-gray-100">${safe}</p>
605
- </div>
606
- `;
607
- } else {
608
- messageDiv.innerHTML = `
609
- <div class="bg-indigo-900/50 rounded-lg p-4 max-w-[85%]">
610
- <p class="text-gray-100">${safe}</p>
611
- </div>
612
- `;
613
  }
614
 
615
- chatMessages.appendChild(messageDiv);
616
- chatMessages.scrollTop = chatMessages.scrollHeight;
617
-
618
- transcript.push({ role: isUser ? 'user' : 'system', content: text, ts: Date.now() });
619
- }
620
-
621
- function setBusy(isBusy) {
622
- if (isBusy) {
623
- typingIndicator.classList.remove('hidden');
624
- sendBtn.disabled = true;
625
- } else {
626
- typingIndicator.classList.add('hidden');
627
- sendBtn.disabled = false;
628
  }
629
- }
630
 
631
- async function callServerChat(userMessage) {
632
- const res = await fetch(API_ENDPOINT, {
633
- method: 'POST',
634
- headers: { 'Content-Type': 'application/json' },
635
- body: JSON.stringify({
636
- message: userMessage,
637
- meta: { page: 'chat.html', product: 'silentpattern' }
638
- })
 
 
 
 
 
 
 
 
 
 
 
 
639
  });
640
 
641
- if (!res.ok) {
642
- const txt = await res.text().catch(() => '');
643
- throw new Error(`Server error (${res.status}). ${txt}`.trim());
644
- }
645
-
646
- const data = await res.json();
647
- if (!data || typeof data.reply !== 'string') {
648
- throw new Error('Invalid response format from /api/chat');
649
- }
650
- return data.reply;
651
- }
652
-
653
- function localDemoResponse(userMessage) {
654
- const msg = userMessage.toLowerCase();
655
- if (msg.includes('mcap')) {
656
- 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.";
657
- }
658
- if (msg.includes('chai')) {
659
- 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.";
660
- }
661
- if (msg.includes('quantum lambda')) {
662
- 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.";
663
- }
664
- return "Acknowledged. State (a) objective, (b) constraints, (c) what evidence you have today. I will respond with a plan and a minimal next experiment.";
665
- }
666
-
667
- chatForm.addEventListener('submit', async (e) => {
668
- e.preventDefault();
669
- const message = chatInput.value.trim();
670
- if (!message) return;
671
-
672
- addMessage(message, true);
673
- chatInput.value = '';
674
- setBusy(true);
675
-
676
- try {
677
- const reply = await callServerChat(message);
678
- addMessage(reply, false);
679
- } catch (err) {
680
- addMessage(localDemoResponse(message), false);
681
- } finally {
682
- setBusy(false);
683
- }
684
- });
685
-
686
- clearBtn.addEventListener('click', () => {
687
- // Keep the seed greeting, remove everything after it
688
- const nodes = Array.from(chatMessages.children);
689
- for (let i = 1; i < nodes.length; i++) nodes[i].remove();
690
-
691
- // Reset transcript to the seed system message only
692
- transcript.length = 0;
693
- seedTranscript();
694
 
695
- // Add a visible confirmation message (this will also be recorded)
696
- addMessage('Session cleared.', false);
697
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
 
699
- exportBtn.addEventListener('click', () => {
700
- const payload = {
701
- product: "SILENTPATTERN",
702
- page: "chat.html",
703
- exported_at: new Date().toISOString(),
704
- transcript
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  };
706
- const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
707
- const url = URL.createObjectURL(blob);
708
- const a = document.createElement('a');
709
- a.href = url;
710
- a.download = `silentpattern_transcript_${Date.now()}.json`;
711
- document.body.appendChild(a);
712
- a.click();
713
- a.remove();
714
- URL.revokeObjectURL(url);
715
- });
716
 
717
- /* GLOBAL ESC */
718
- document.addEventListener('keydown', (e) => {
719
- if (e.key === 'Escape') {
720
- if (labNav && !labNav.classList.contains('modal-hidden')) closeLabNav();
721
- if (accessModal && !accessModal.classList.contains('modal-hidden')) toggleModal(accessModal, false);
722
- }
723
  });
724
-
725
- // Optional: preselect a dossier for the right pane
726
- renderDossier('console');
727
  </script>
728
  </body>
729
  </html>
 
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
  </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">
 
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>
 
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>
 
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>
 
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
  <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>
 
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
  </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
  <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
  </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
  <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
  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>
539
  </html>