Testing347 commited on
Commit
cb13f5f
Β·
verified Β·
1 Parent(s): cdca016

Update chat.html

Browse files
Files changed (1) hide show
  1. chat.html +249 -130
chat.html CHANGED
@@ -1,3 +1,4 @@
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
@@ -68,6 +69,13 @@
68
  }
69
 
70
  .focus-ring:focus { outline: none; box-shadow: 0 0 0 2px rgba(99,102,241,0.65); }
 
 
 
 
 
 
 
71
  </style>
72
  </head>
73
 
@@ -86,14 +94,14 @@
86
 
87
  <div class="flex items-center space-x-3">
88
  <button id="lab-nav-btn"
89
- class="w-10 h-10 rounded-full border border-indigo-500/40 bg-gray-900/20 hover:bg-gray-900/40 backdrop-blur-sm transition flex items-center justify-center"
90
  aria-label="Open Lab Navigator" title="Lab Navigator"
91
  aria-controls="lab-navigator" aria-haspopup="dialog">
92
  <i class="fas fa-asterisk text-indigo-300 text-sm"></i>
93
  </button>
94
 
95
  <button id="access-btn"
96
- class="px-6 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-full hover:opacity-90 transition"
97
  aria-controls="access-modal" aria-haspopup="dialog">
98
  Access
99
  </button>
@@ -114,7 +122,7 @@
114
  <span class="gradient-text">Conversation</span> Interface
115
  </h1>
116
  <p class="mt-3 text-gray-300 max-w-2xl mx-auto">
117
- A controlled channel into SILENTPATTERN’s systems. Minimal surface; auditable outputs.
118
  </p>
119
  </div>
120
 
@@ -158,11 +166,11 @@
158
  <div class="px-6 py-4 border-t border-gray-800 bg-black/10">
159
  <form id="chat-form" class="flex items-center" autocomplete="off" aria-label="Send message">
160
  <input id="chat-input" type="text"
161
- placeholder="Describe a task (e.g., β€˜Draft a research note for MCAP’)..."
162
  class="flex-1 bg-gray-800/50 border border-gray-700 rounded-l-xl px-4 py-3 focus-ring"
163
  aria-label="Type your message" />
164
  <button id="send-btn" type="submit"
165
- class="bg-indigo-600 hover:bg-indigo-700 px-6 py-3 rounded-r-xl transition disabled:opacity-60 disabled:cursor-not-allowed"
166
  aria-label="Send">
167
  <i class="fas fa-paper-plane"></i>
168
  </button>
@@ -170,10 +178,10 @@
170
 
171
  <div class="mt-2 flex justify-between items-center text-sm text-gray-500">
172
  <div class="flex items-center gap-3">
173
- <button type="button" id="clear-btn" class="hover:text-indigo-400 transition" aria-label="Clear session">
174
  <i class="fas fa-broom mr-1"></i> Clear
175
  </button>
176
- <button type="button" id="export-btn" class="hover:text-indigo-400 transition" aria-label="Export transcript">
177
  <i class="fas fa-file-arrow-down mr-1"></i> Export
178
  </button>
179
  </div>
@@ -220,7 +228,7 @@
220
  <h3 class="text-xl font-bold" id="access-modal-title">Request Access</h3>
221
  <p class="text-gray-400 mt-1">Limited availability for qualified researchers</p>
222
  </div>
223
- <button id="close-access-modal" class="text-gray-400 hover:text-white" aria-label="Close">
224
  <i class="fas fa-times"></i>
225
  </button>
226
  </div>
@@ -234,14 +242,17 @@
234
  <div>
235
  <label for="name" class="block text-sm font-medium mb-1">Full Name</label>
236
  <input type="text" id="name" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring" autocomplete="name">
 
237
  </div>
238
  <div>
239
  <label for="email" class="block text-sm font-medium mb-1">Email</label>
240
  <input type="email" id="email" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring" autocomplete="email">
 
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" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring" autocomplete="organization">
 
245
  </div>
246
  <div>
247
  <label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
@@ -253,10 +264,11 @@
253
  <option value="partnership">Partnership</option>
254
  <option value="other">Other</option>
255
  </select>
 
256
  </div>
257
  <div class="pt-2">
258
  <button type="submit"
259
- class="w-full py-3 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg hover:opacity-90 transition">
260
  Submit Request
261
  </button>
262
  </div>
@@ -287,7 +299,7 @@
287
  </div>
288
 
289
  <button id="lab-nav-close"
290
- 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"
291
  aria-label="Close Lab Navigator">
292
  <i class="fas fa-times text-gray-300 text-sm"></i>
293
  </button>
@@ -299,32 +311,32 @@
299
  radial-gradient(circle at 70% 70%, rgba(236,72,153,0.10), transparent 50%);"></div>
300
 
301
  <div class="relative grid grid-cols-1 sm:grid-cols-2 gap-3">
302
- <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"
303
- data-dossier="start">
304
  <div class="text-sm text-gray-200 font-medium">Start Here</div>
305
  <div class="text-xs text-gray-500 mt-1">Return to the main interface</div>
306
  </button>
307
 
308
- <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"
309
- data-dossier="console">
310
  <div class="text-sm text-gray-200 font-medium">Console</div>
311
  <div class="text-xs text-gray-500 mt-1">This page: controlled chat</div>
312
  </button>
313
 
314
- <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"
315
- data-dossier="programs">
316
  <div class="text-sm text-gray-200 font-medium">Programs</div>
317
  <div class="text-xs text-gray-500 mt-1">MCAP Β· CHAI Β· Quantum Lambda</div>
318
  </button>
319
 
320
- <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"
321
- data-dossier="ai_scientist">
322
  <div class="text-sm text-gray-200 font-medium">AI Scientist</div>
323
  <div class="text-xs text-gray-500 mt-1">Hypothesis β†’ experiment β†’ report</div>
324
  </button>
325
 
326
- <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"
327
- data-dossier="access">
328
  <div class="text-sm text-gray-200 font-medium">Access</div>
329
  <div class="text-xs text-gray-500 mt-1">Request access to demos and research</div>
330
  </button>
@@ -350,8 +362,7 @@
350
  </div>
351
  </div>
352
 
353
- <div class="p-6 space-y-5 max-h-[560px] overflow-auto"
354
- style="scrollbar-width: thin; scrollbar-color: #4f46e5 #1e1b4b;">
355
  <div id="dossier-body" class="text-sm text-gray-300 leading-relaxed">
356
  This console is designed for controlled interaction. Dossiers expose depth by intent.
357
  </div>
@@ -365,11 +376,11 @@
365
 
366
  <div class="flex flex-col sm:flex-row gap-3">
367
  <button id="dossier-primary"
368
- class="flex-1 px-5 py-3 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:opacity-90 transition">
369
  Open
370
  </button>
371
  <button id="dossier-secondary"
372
- class="flex-1 px-5 py-3 rounded-xl border border-gray-700 bg-gray-900/20 hover:bg-gray-900/35 transition">
373
  View Note
374
  </button>
375
  </div>
@@ -385,34 +396,49 @@
385
  </div>
386
 
387
  <script>
 
 
 
 
 
388
  /* -------------------------------------------------------------
389
- VANTA (guarded)
390
  ------------------------------------------------------------- */
391
- let vantaEffect = null;
392
- try {
393
- if (window.VANTA && typeof VANTA.NET === 'function') {
394
- vantaEffect = VANTA.NET({
395
- el: "#vanta-bg",
396
- mouseControls: true,
397
- touchControls: true,
398
- gyroControls: false,
399
- minHeight: 200.00,
400
- minWidth: 200.00,
401
- scale: 1.00,
402
- scaleMobile: 1.00,
403
- color: 0x4f46e5,
404
- backgroundColor: 0x020617,
405
- points: 12.00,
406
- maxDistance: 20.00,
407
- spacing: 15.00
408
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  }
410
- } catch (_) {
411
- vantaEffect = null;
412
  }
413
- window.addEventListener('resize', () => {
414
- if (vantaEffect && typeof vantaEffect.resize === 'function') vantaEffect.resize();
415
- });
416
 
417
  /* -------------------------------------------------------------
418
  MODAL ACCESSIBILITY (focus trap + restore opener focus)
@@ -445,7 +471,6 @@
445
 
446
  const toggleModal = (modal, show) => {
447
  if (show) {
448
- modal._opener = document.activeElement;
449
  modal.classList.remove('modal-hidden');
450
  modal.classList.add('modal-visible');
451
  document.body.style.overflow = 'hidden';
@@ -455,13 +480,38 @@
455
  modal.classList.add('modal-hidden');
456
  document.body.style.overflow = '';
457
  untrapFocus(modal);
458
- if (modal._opener && typeof modal._opener.focus === 'function') {
459
- modal._opener.focus();
460
- }
461
- modal._opener = null;
462
  }
463
  };
464
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  /* -------------------------------------------------------------
466
  ACCESS MODAL (inline feedback)
467
  ------------------------------------------------------------- */
@@ -471,7 +521,18 @@
471
  const accessForm = document.getElementById('access-form');
472
  const accessFeedback = document.getElementById('access-feedback');
473
 
 
 
 
 
 
 
 
 
 
 
474
  function setAccessFeedback(kind, text) {
 
475
  accessFeedback.classList.remove('hidden');
476
  accessFeedback.classList.remove('border-red-500/30', 'bg-red-900/10', 'text-red-200');
477
  accessFeedback.classList.remove('border-emerald-500/30', 'bg-emerald-900/10', 'text-emerald-200');
@@ -487,7 +548,8 @@
487
  accessFeedback.textContent = text;
488
  }
489
 
490
- function clearAccessFeedback() {
 
491
  accessFeedback.textContent = '';
492
  accessFeedback.classList.add('hidden');
493
  accessFeedback.classList.remove(
@@ -497,39 +559,85 @@
497
  );
498
  }
499
 
500
- function isValidEmail(email) {
501
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  }
503
 
504
- accessBtn.addEventListener('click', () => {
505
- clearAccessFeedback();
506
  toggleModal(accessModal, true);
507
- setTimeout(() => document.getElementById('name').focus(), 50);
508
- });
509
- closeAccessModal.addEventListener('click', () => toggleModal(accessModal, false));
510
- accessModal.addEventListener('click', (e) => { if (e.target === accessModal) toggleModal(accessModal, false); });
511
 
512
- accessForm.addEventListener('submit', (e) => {
513
- e.preventDefault();
514
- const name = document.getElementById('name').value.trim();
515
- const email = document.getElementById('email').value.trim();
516
- const institution = document.getElementById('institution').value.trim();
517
- const purpose = document.getElementById('purpose').value;
518
-
519
- if (!name || !email || !institution || !purpose) {
520
- setAccessFeedback('error', 'Please fill in all fields.');
521
- return;
522
- }
523
- if (!isValidEmail(email)) {
524
- setAccessFeedback('error', 'Please enter a valid email address.');
525
- return;
526
- }
527
 
528
- setAccessFeedback('success', 'Request received. You will be contacted after review.');
529
- accessForm.reset();
530
- setTimeout(() => toggleModal(accessModal, false), 900);
 
 
531
  });
532
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  /* -------------------------------------------------------------
534
  LAB NAVIGATOR
535
  ------------------------------------------------------------- */
@@ -537,16 +645,6 @@
537
  const labNavBtn = document.getElementById('lab-nav-btn');
538
  const labNavClose = document.getElementById('lab-nav-close');
539
 
540
- function openLabNav() { toggleModal(labNav, true); setTimeout(() => labNav.focus(), 0); }
541
- function closeLabNav() { toggleModal(labNav, false); }
542
-
543
- labNavBtn.addEventListener('click', openLabNav);
544
- labNavClose.addEventListener('click', closeLabNav);
545
- labNav.addEventListener('click', (e) => {
546
- const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
547
- if (shouldClose) closeLabNav();
548
- });
549
-
550
  const DOSSIERS = {
551
  start: {
552
  title: "Start Here",
@@ -564,8 +662,8 @@
564
  status: "DRAFT",
565
  body: "This console is a staging environment. It will connect to a server endpoint and produce auditable outputs (transcripts, notes, reports).",
566
  evidence: ["Client: UI only", "Server: keys + policy + logging", "Exportable transcript"],
567
- primary: { label: "Close Navigator", action: () => { closeLabNav(); } },
568
- secondary: { label: "Export", action: () => { closeLabNav(); document.getElementById('export-btn').click(); } },
569
  updated: "β€”"
570
  },
571
  programs: {
@@ -582,7 +680,7 @@
582
  title: "AI Scientist",
583
  subtitle: "Hypothesis β†’ experiment β†’ report",
584
  status: "DRAFT",
585
- body: "The AI Scientist is positioned as the lab’s instrument: it standardizes experiments, enforces reproducibility, and produces reports with uncertainty.",
586
  evidence: ["Experiment harnesses", "Evaluation baselines", "Report generation"],
587
  primary: { label: "View Research", action: () => { window.location.href = "research.html"; } },
588
  secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
@@ -594,7 +692,7 @@
594
  status: "ACTIVE",
595
  body: "Access is curated. The objective is qualified users, high-signal feedback, and responsible scaling.",
596
  evidence: ["Application-based", "Segmented by intent", "Controlled demos"],
597
- primary: { label: "Request Access", action: () => { closeLabNav(); accessBtn.click(); } },
598
  secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
599
  updated: "β€”"
600
  }
@@ -609,10 +707,20 @@
609
  const dossierSecondary = document.getElementById('dossier-secondary');
610
  const dossierMeta = document.getElementById('dossier-meta');
611
 
 
 
 
 
 
 
 
 
612
  function renderDossier(key) {
613
  const d = DOSSIERS[key];
614
  if (!d) return;
615
 
 
 
616
  dossierTitle.textContent = d.title;
617
  dossierSubtitle.textContent = d.subtitle;
618
  dossierStatus.textContent = d.status;
@@ -634,16 +742,39 @@
634
  dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${d.updated}</span>`;
635
  }
636
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  document.querySelectorAll('.lab-node').forEach(btn => {
638
  btn.addEventListener('click', () => {
639
  const key = btn.getAttribute('data-dossier');
640
  renderDossier(key);
641
 
642
- // Apply the implied navigation behavior (previously missing)
643
  if (key === 'start') window.location.href = "index.html";
644
  if (key === 'programs') window.location.href = "capabilities.html";
645
  if (key === 'ai_scientist') window.location.href = "research.html";
646
- if (key === 'access') { closeLabNav(); accessBtn.click(); }
647
  // console -> stays here
648
  });
649
  });
@@ -651,8 +782,6 @@
651
  /* -------------------------------------------------------------
652
  CHAT: secure-by-design client
653
  ------------------------------------------------------------- */
654
- const API_ENDPOINT = "/api/chat"; // change if your deployment requires a prefix
655
-
656
  const chatForm = document.getElementById('chat-form');
657
  const chatInput = document.getElementById('chat-input');
658
  const chatMessages = document.getElementById('chat-messages');
@@ -671,15 +800,6 @@
671
  }
672
  seedTranscript();
673
 
674
- function escapeHtml(str) {
675
- return str
676
- .replaceAll('&', '&amp;')
677
- .replaceAll('<', '&lt;')
678
- .replaceAll('>', '&gt;')
679
- .replaceAll('"', '&quot;')
680
- .replaceAll("'", '&#039;');
681
- }
682
-
683
  function addMessage(text, isUser = false) {
684
  const safe = escapeHtml(text);
685
  const messageDiv = document.createElement('div');
@@ -722,25 +842,9 @@
722
  }
723
 
724
  async function callServerChat(userMessage) {
725
- const res = await fetch(API_ENDPOINT, {
726
- method: 'POST',
727
- headers: { 'Content-Type': 'application/json' },
728
- body: JSON.stringify({
729
- message: userMessage,
730
- meta: { page: 'chat.html', product: 'silentpattern' }
731
- })
732
- });
733
-
734
- if (!res.ok) {
735
- const txt = await res.text().catch(() => '');
736
- throw new Error(`Server error (${res.status}). ${txt}`.trim());
737
- }
738
-
739
- const data = await res.json();
740
- if (!data || typeof data.reply !== 'string') {
741
- throw new Error('Invalid response format from /api/chat');
742
- }
743
- return data.reply;
744
  }
745
 
746
  function localDemoResponse(userMessage) {
@@ -804,18 +908,33 @@
804
  URL.revokeObjectURL(url);
805
  });
806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
  /* -------------------------------------------------------------
808
  GLOBAL ESC
809
  ------------------------------------------------------------- */
810
  document.addEventListener('keydown', (e) => {
811
  if (e.key === 'Escape') {
812
- if (labNav && !labNav.classList.contains('modal-hidden')) closeLabNav();
813
- if (accessModal && !accessModal.classList.contains('modal-hidden')) toggleModal(accessModal, false);
814
  }
815
  });
816
 
817
- // Preselect a dossier for the right pane
818
  renderDossier('console');
819
  </script>
820
  </body>
821
- </html>
 
1
+ <!-- SILENTPATTERN FINAL BUILD: 2025-12-15 | pages: 10 | features: hash-deep-linking, lab-navigator, access-modal, form-validation, chat-console, export-transcript -->
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
 
69
  }
70
 
71
  .focus-ring:focus { outline: none; box-shadow: 0 0 0 2px rgba(99,102,241,0.65); }
72
+
73
+ /* Active states */
74
+ .lab-node.active {
75
+ border-color: rgba(99,102,241,0.55) !important;
76
+ box-shadow: 0 0 0 1px rgba(99,102,241,0.22), 0 0 28px rgba(99,102,241,0.08);
77
+ transform: translateY(-1px);
78
+ }
79
  </style>
80
  </head>
81
 
 
94
 
95
  <div class="flex items-center space-x-3">
96
  <button id="lab-nav-btn"
97
+ class="w-10 h-10 rounded-full border border-indigo-500/40 bg-gray-900/20 hover:bg-gray-900/40 backdrop-blur-sm transition flex items-center justify-center focus-ring"
98
  aria-label="Open Lab Navigator" title="Lab Navigator"
99
  aria-controls="lab-navigator" aria-haspopup="dialog">
100
  <i class="fas fa-asterisk text-indigo-300 text-sm"></i>
101
  </button>
102
 
103
  <button id="access-btn"
104
+ class="px-6 py-2 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-full hover:opacity-90 transition focus-ring"
105
  aria-controls="access-modal" aria-haspopup="dialog">
106
  Access
107
  </button>
 
122
  <span class="gradient-text">Conversation</span> Interface
123
  </h1>
124
  <p class="mt-3 text-gray-300 max-w-2xl mx-auto">
125
+ A controlled channel into SILENTPATTERN's systems. Minimal surface; auditable outputs.
126
  </p>
127
  </div>
128
 
 
166
  <div class="px-6 py-4 border-t border-gray-800 bg-black/10">
167
  <form id="chat-form" class="flex items-center" autocomplete="off" aria-label="Send message">
168
  <input id="chat-input" type="text"
169
+ placeholder="Describe a task (e.g., 'Draft a research note for MCAP')..."
170
  class="flex-1 bg-gray-800/50 border border-gray-700 rounded-l-xl px-4 py-3 focus-ring"
171
  aria-label="Type your message" />
172
  <button id="send-btn" type="submit"
173
+ class="bg-indigo-600 hover:bg-indigo-700 px-6 py-3 rounded-r-xl transition disabled:opacity-60 disabled:cursor-not-allowed focus-ring"
174
  aria-label="Send">
175
  <i class="fas fa-paper-plane"></i>
176
  </button>
 
178
 
179
  <div class="mt-2 flex justify-between items-center text-sm text-gray-500">
180
  <div class="flex items-center gap-3">
181
+ <button type="button" id="clear-btn" class="hover:text-indigo-400 transition focus-ring" aria-label="Clear session">
182
  <i class="fas fa-broom mr-1"></i> Clear
183
  </button>
184
+ <button type="button" id="export-btn" class="hover:text-indigo-400 transition focus-ring" aria-label="Export transcript">
185
  <i class="fas fa-file-arrow-down mr-1"></i> Export
186
  </button>
187
  </div>
 
228
  <h3 class="text-xl font-bold" id="access-modal-title">Request Access</h3>
229
  <p class="text-gray-400 mt-1">Limited availability for qualified researchers</p>
230
  </div>
231
+ <button id="close-access-modal" class="text-gray-400 hover:text-white focus-ring" aria-label="Close">
232
  <i class="fas fa-times"></i>
233
  </button>
234
  </div>
 
242
  <div>
243
  <label for="name" class="block text-sm font-medium mb-1">Full Name</label>
244
  <input type="text" id="name" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring" autocomplete="name">
245
+ <p id="name-error" class="hidden mt-1 text-xs text-red-300">Please enter your full name.</p>
246
  </div>
247
  <div>
248
  <label for="email" class="block text-sm font-medium mb-1">Email</label>
249
  <input type="email" id="email" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring" autocomplete="email">
250
+ <p id="email-error" class="hidden mt-1 text-xs text-red-300">Please enter a valid email address.</p>
251
  </div>
252
  <div>
253
  <label for="institution" class="block text-sm font-medium mb-1">Institution/Organization</label>
254
  <input type="text" id="institution" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus-ring" autocomplete="organization">
255
+ <p id="institution-error" class="hidden mt-1 text-xs text-red-300">Please enter your institution/organization.</p>
256
  </div>
257
  <div>
258
  <label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
 
264
  <option value="partnership">Partnership</option>
265
  <option value="other">Other</option>
266
  </select>
267
+ <p id="purpose-error" class="hidden mt-1 text-xs text-red-300">Please select a purpose.</p>
268
  </div>
269
  <div class="pt-2">
270
  <button type="submit"
271
+ class="w-full py-3 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg hover:opacity-90 transition focus-ring">
272
  Submit Request
273
  </button>
274
  </div>
 
299
  </div>
300
 
301
  <button id="lab-nav-close"
302
+ 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 focus-ring"
303
  aria-label="Close Lab Navigator">
304
  <i class="fas fa-times text-gray-300 text-sm"></i>
305
  </button>
 
311
  radial-gradient(circle at 70% 70%, rgba(236,72,153,0.10), transparent 50%);"></div>
312
 
313
  <div class="relative grid grid-cols-1 sm:grid-cols-2 gap-3">
314
+ <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 focus-ring"
315
+ data-dossier="start" aria-current="false">
316
  <div class="text-sm text-gray-200 font-medium">Start Here</div>
317
  <div class="text-xs text-gray-500 mt-1">Return to the main interface</div>
318
  </button>
319
 
320
+ <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 focus-ring"
321
+ data-dossier="console" aria-current="false">
322
  <div class="text-sm text-gray-200 font-medium">Console</div>
323
  <div class="text-xs text-gray-500 mt-1">This page: controlled chat</div>
324
  </button>
325
 
326
+ <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 focus-ring"
327
+ data-dossier="programs" aria-current="false">
328
  <div class="text-sm text-gray-200 font-medium">Programs</div>
329
  <div class="text-xs text-gray-500 mt-1">MCAP Β· CHAI Β· Quantum Lambda</div>
330
  </button>
331
 
332
+ <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 focus-ring"
333
+ data-dossier="ai_scientist" aria-current="false">
334
  <div class="text-sm text-gray-200 font-medium">AI Scientist</div>
335
  <div class="text-xs text-gray-500 mt-1">Hypothesis β†’ experiment β†’ report</div>
336
  </button>
337
 
338
+ <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 focus-ring sm:col-span-2"
339
+ data-dossier="access" aria-current="false">
340
  <div class="text-sm text-gray-200 font-medium">Access</div>
341
  <div class="text-xs text-gray-500 mt-1">Request access to demos and research</div>
342
  </button>
 
362
  </div>
363
  </div>
364
 
365
+ <div class="p-6 space-y-5 max-h-[560px] overflow-auto thin-scroll">
 
366
  <div id="dossier-body" class="text-sm text-gray-300 leading-relaxed">
367
  This console is designed for controlled interaction. Dossiers expose depth by intent.
368
  </div>
 
376
 
377
  <div class="flex flex-col sm:flex-row gap-3">
378
  <button id="dossier-primary"
379
+ class="flex-1 px-5 py-3 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:opacity-90 transition focus-ring">
380
  Open
381
  </button>
382
  <button id="dossier-secondary"
383
+ class="flex-1 px-5 py-3 rounded-xl border border-gray-700 bg-gray-900/20 hover:bg-gray-900/35 transition focus-ring">
384
  View Note
385
  </button>
386
  </div>
 
396
  </div>
397
 
398
  <script>
399
+ // Site-wide configuration
400
+ const CONFIG = {
401
+ MODAL_HASHES: new Set(['lab', 'access'])
402
+ };
403
+
404
  /* -------------------------------------------------------------
405
+ UTILITY FUNCTIONS (consistent with other pages)
406
  ------------------------------------------------------------- */
407
+ function escapeHtml(str) {
408
+ return String(str)
409
+ .replaceAll('&', '&amp;')
410
+ .replaceAll('<', '&lt;')
411
+ .replaceAll('>', '&gt;')
412
+ .replaceAll('"', '&quot;')
413
+ .replaceAll("'", '&#039;');
414
+ }
415
+
416
+ function isValidEmail(email) {
417
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
418
+ }
419
+
420
+ function currentHashKey() {
421
+ const h = (window.location.hash || '').replace('#', '').trim();
422
+ return h;
423
+ }
424
+
425
+ function setHash(key, replace = false) {
426
+ if (!key) return;
427
+ if (window.location.hash.replace('#', '') === key) return;
428
+ if (replace) {
429
+ history.replaceState(null, '', '#' + key);
430
+ } else {
431
+ history.pushState(null, '', '#' + key);
432
+ }
433
+ }
434
+
435
+ function clearHashIf(key) {
436
+ const h = currentHashKey();
437
+ if (!h) return;
438
+ if (!key || h === key) {
439
+ history.replaceState(null, '', window.location.pathname + window.location.search);
440
  }
 
 
441
  }
 
 
 
442
 
443
  /* -------------------------------------------------------------
444
  MODAL ACCESSIBILITY (focus trap + restore opener focus)
 
471
 
472
  const toggleModal = (modal, show) => {
473
  if (show) {
 
474
  modal.classList.remove('modal-hidden');
475
  modal.classList.add('modal-visible');
476
  document.body.style.overflow = 'hidden';
 
480
  modal.classList.add('modal-hidden');
481
  document.body.style.overflow = '';
482
  untrapFocus(modal);
 
 
 
 
483
  }
484
  };
485
 
486
+ /* -------------------------------------------------------------
487
+ VANTA (guarded)
488
+ ------------------------------------------------------------- */
489
+ let vantaEffect = null;
490
+ try {
491
+ if (window.VANTA && typeof VANTA.NET === 'function') {
492
+ vantaEffect = VANTA.NET({
493
+ el: "#vanta-bg",
494
+ mouseControls: true,
495
+ touchControls: true,
496
+ gyroControls: false,
497
+ minHeight: 200.00,
498
+ minWidth: 200.00,
499
+ scale: 1.00,
500
+ scaleMobile: 1.00,
501
+ color: 0x4f46e5,
502
+ backgroundColor: 0x020617,
503
+ points: 12.00,
504
+ maxDistance: 20.00,
505
+ spacing: 15.00
506
+ });
507
+ }
508
+ } catch (_) {
509
+ vantaEffect = null;
510
+ }
511
+ window.addEventListener('resize', () => {
512
+ if (vantaEffect && typeof vantaEffect.resize === 'function') vantaEffect.resize();
513
+ });
514
+
515
  /* -------------------------------------------------------------
516
  ACCESS MODAL (inline feedback)
517
  ------------------------------------------------------------- */
 
521
  const accessForm = document.getElementById('access-form');
522
  const accessFeedback = document.getElementById('access-feedback');
523
 
524
+ const nameEl = document.getElementById('name');
525
+ const emailEl = document.getElementById('email');
526
+ const institutionEl = document.getElementById('institution');
527
+ const purposeEl = document.getElementById('purpose');
528
+
529
+ const nameErr = document.getElementById('name-error');
530
+ const emailErr = document.getElementById('email-error');
531
+ const institutionErr = document.getElementById('institution-error');
532
+ const purposeErr = document.getElementById('purpose-error');
533
+
534
  function setAccessFeedback(kind, text) {
535
+ if (!accessFeedback) return;
536
  accessFeedback.classList.remove('hidden');
537
  accessFeedback.classList.remove('border-red-500/30', 'bg-red-900/10', 'text-red-200');
538
  accessFeedback.classList.remove('border-emerald-500/30', 'bg-emerald-900/10', 'text-emerald-200');
 
548
  accessFeedback.textContent = text;
549
  }
550
 
551
+ function hideAccessFeedback() {
552
+ if (!accessFeedback) return;
553
  accessFeedback.textContent = '';
554
  accessFeedback.classList.add('hidden');
555
  accessFeedback.classList.remove(
 
559
  );
560
  }
561
 
562
+ function setFieldError(inputEl, errorEl, isError) {
563
+ if (!inputEl || !errorEl) return;
564
+ if (isError) {
565
+ errorEl.classList.remove('hidden');
566
+ inputEl.setAttribute('aria-invalid', 'true');
567
+ inputEl.classList.add('border-red-500/60');
568
+ } else {
569
+ errorEl.classList.add('hidden');
570
+ inputEl.removeAttribute('aria-invalid');
571
+ inputEl.classList.remove('border-red-500/60');
572
+ }
573
+ }
574
+
575
+ function resetAccessErrors() {
576
+ hideAccessFeedback();
577
+ setFieldError(nameEl, nameErr, false);
578
+ setFieldError(emailEl, emailErr, false);
579
+ setFieldError(institutionEl, institutionErr, false);
580
+ setFieldError(purposeEl, purposeErr, false);
581
  }
582
 
583
+ function openAccessModal(setHashFlag = true) {
584
+ resetAccessErrors();
585
  toggleModal(accessModal, true);
586
+ if (setHashFlag) setHash('access');
587
+ setTimeout(() => nameEl && nameEl.focus(), 80);
588
+ }
 
589
 
590
+ function closeAccessModal(clearHashFlag = true) {
591
+ toggleModal(accessModal, false);
592
+ if (clearHashFlag) clearHashIf('access');
593
+ }
 
 
 
 
 
 
 
 
 
 
 
594
 
595
+ accessBtn.addEventListener('click', () => openAccessModal(true));
596
+ closeAccessModal.addEventListener('click', () => closeAccessModal(true));
597
+
598
+ accessModal.addEventListener('click', (e) => {
599
+ if (e.target === accessModal) closeAccessModal(true);
600
  });
601
 
602
+ if (accessForm) {
603
+ // Clear per-field errors on input
604
+ [nameEl, emailEl, institutionEl, purposeEl].forEach(el => {
605
+ if (!el) return;
606
+ el.addEventListener('input', () => {
607
+ if (el === nameEl) setFieldError(nameEl, nameErr, !nameEl.value.trim());
608
+ if (el === emailEl) setFieldError(emailEl, emailErr, !isValidEmail(emailEl.value.trim()));
609
+ if (el === institutionEl) setFieldError(institutionEl, institutionErr, !institutionEl.value.trim());
610
+ if (el === purposeEl) setFieldError(purposeEl, purposeErr, !purposeEl.value);
611
+ });
612
+ el.addEventListener('change', () => el.dispatchEvent(new Event('input')));
613
+ });
614
+
615
+ accessForm.addEventListener('submit', (e) => {
616
+ e.preventDefault();
617
+ resetAccessErrors();
618
+
619
+ const name = (nameEl?.value || '').trim();
620
+ const email = (emailEl?.value || '').trim();
621
+ const institution = (institutionEl?.value || '').trim();
622
+ const purpose = (purposeEl?.value || '').trim();
623
+
624
+ let ok = true;
625
+
626
+ if (!name) { setFieldError(nameEl, nameErr, true); ok = false; }
627
+ if (!email || !isValidEmail(email)) { setFieldError(emailEl, emailErr, true); ok = false; }
628
+ if (!institution) { setFieldError(institutionEl, institutionErr, true); ok = false; }
629
+ if (!purpose) { setFieldError(purposeEl, purposeErr, true); ok = false; }
630
+
631
+ if (!ok) {
632
+ setAccessFeedback('error', 'Please correct the highlighted fields and resubmit.');
633
+ return;
634
+ }
635
+
636
+ setAccessFeedback('success', 'Request received. You will be contacted after review.');
637
+ accessForm.reset();
638
+ });
639
+ }
640
+
641
  /* -------------------------------------------------------------
642
  LAB NAVIGATOR
643
  ------------------------------------------------------------- */
 
645
  const labNavBtn = document.getElementById('lab-nav-btn');
646
  const labNavClose = document.getElementById('lab-nav-close');
647
 
 
 
 
 
 
 
 
 
 
 
648
  const DOSSIERS = {
649
  start: {
650
  title: "Start Here",
 
662
  status: "DRAFT",
663
  body: "This console is a staging environment. It will connect to a server endpoint and produce auditable outputs (transcripts, notes, reports).",
664
  evidence: ["Client: UI only", "Server: keys + policy + logging", "Exportable transcript"],
665
+ primary: { label: "Close Navigator", action: () => closeLabNav(true) },
666
+ secondary: { label: "Export", action: () => { closeLabNav(true); document.getElementById('export-btn').click(); } },
667
  updated: "β€”"
668
  },
669
  programs: {
 
680
  title: "AI Scientist",
681
  subtitle: "Hypothesis β†’ experiment β†’ report",
682
  status: "DRAFT",
683
+ body: "The AI Scientist is positioned as the lab's instrument: it standardizes experiments, enforces reproducibility, and produces reports with uncertainty.",
684
  evidence: ["Experiment harnesses", "Evaluation baselines", "Report generation"],
685
  primary: { label: "View Research", action: () => { window.location.href = "research.html"; } },
686
  secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
 
692
  status: "ACTIVE",
693
  body: "Access is curated. The objective is qualified users, high-signal feedback, and responsible scaling.",
694
  evidence: ["Application-based", "Segmented by intent", "Controlled demos"],
695
+ primary: { label: "Request Access", action: () => { closeLabNav(true); openAccessModal(true); } },
696
  secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
697
  updated: "β€”"
698
  }
 
707
  const dossierSecondary = document.getElementById('dossier-secondary');
708
  const dossierMeta = document.getElementById('dossier-meta');
709
 
710
+ function setActiveLabNode(key) {
711
+ document.querySelectorAll('.lab-node').forEach(btn => {
712
+ const isActive = btn.getAttribute('data-dossier') === key;
713
+ btn.classList.toggle('active', isActive);
714
+ btn.setAttribute('aria-current', isActive ? 'true' : 'false');
715
+ });
716
+ }
717
+
718
  function renderDossier(key) {
719
  const d = DOSSIERS[key];
720
  if (!d) return;
721
 
722
+ setActiveLabNode(key);
723
+
724
  dossierTitle.textContent = d.title;
725
  dossierSubtitle.textContent = d.subtitle;
726
  dossierStatus.textContent = d.status;
 
742
  dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${d.updated}</span>`;
743
  }
744
 
745
+ function openLabNav(setHashFlag = true) {
746
+ toggleModal(labNav, true);
747
+ if (setHashFlag) setHash('lab');
748
+ setTimeout(() => labNav.focus(), 0);
749
+
750
+ // Ensure a stable default selection when opened
751
+ const alreadyActive = document.querySelector('.lab-node.active');
752
+ if (!alreadyActive) renderDossier('console');
753
+ }
754
+
755
+ function closeLabNav(clearHashFlag = true) {
756
+ toggleModal(labNav, false);
757
+ if (clearHashFlag) clearHashIf('lab');
758
+ }
759
+
760
+ labNavBtn.addEventListener('click', () => openLabNav(true));
761
+ labNavClose.addEventListener('click', () => closeLabNav(true));
762
+
763
+ labNav.addEventListener('click', (e) => {
764
+ const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
765
+ if (shouldClose) closeLabNav(true);
766
+ });
767
+
768
  document.querySelectorAll('.lab-node').forEach(btn => {
769
  btn.addEventListener('click', () => {
770
  const key = btn.getAttribute('data-dossier');
771
  renderDossier(key);
772
 
773
+ // Apply the implied navigation behavior
774
  if (key === 'start') window.location.href = "index.html";
775
  if (key === 'programs') window.location.href = "capabilities.html";
776
  if (key === 'ai_scientist') window.location.href = "research.html";
777
+ if (key === 'access') { closeLabNav(true); openAccessModal(true); }
778
  // console -> stays here
779
  });
780
  });
 
782
  /* -------------------------------------------------------------
783
  CHAT: secure-by-design client
784
  ------------------------------------------------------------- */
 
 
785
  const chatForm = document.getElementById('chat-form');
786
  const chatInput = document.getElementById('chat-input');
787
  const chatMessages = document.getElementById('chat-messages');
 
800
  }
801
  seedTranscript();
802
 
 
 
 
 
 
 
 
 
 
803
  function addMessage(text, isUser = false) {
804
  const safe = escapeHtml(text);
805
  const messageDiv = document.createElement('div');
 
842
  }
843
 
844
  async function callServerChat(userMessage) {
845
+ // This would be a server endpoint in production
846
+ // For static demo, always fall back to local response
847
+ throw new Error('Server endpoint not available in static demo');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  }
849
 
850
  function localDemoResponse(userMessage) {
 
908
  URL.revokeObjectURL(url);
909
  });
910
 
911
+ /* -------------------------------------------------------------
912
+ HASH ROUTER: handle modal hashes
913
+ ------------------------------------------------------------- */
914
+ function applyHashState() {
915
+ const key = currentHashKey();
916
+
917
+ if (CONFIG.MODAL_HASHES.has(key)) {
918
+ if (key === 'lab') openLabNav(false);
919
+ if (key === 'access') openAccessModal(false);
920
+ }
921
+ }
922
+
923
+ window.addEventListener('hashchange', applyHashState);
924
+ applyHashState(); // Initial load
925
+
926
  /* -------------------------------------------------------------
927
  GLOBAL ESC
928
  ------------------------------------------------------------- */
929
  document.addEventListener('keydown', (e) => {
930
  if (e.key === 'Escape') {
931
+ if (labNav && !labNav.classList.contains('modal-hidden')) closeLabNav(true);
932
+ if (accessModal && !accessModal.classList.contains('modal-hidden')) closeAccessModal(true);
933
  }
934
  });
935
 
936
+ // Initial dossier render
937
  renderDossier('console');
938
  </script>
939
  </body>
940
+ </html>