Testing347 commited on
Commit
0d30589
·
verified ·
1 Parent(s): 12a1330

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +421 -180
index.html CHANGED
@@ -425,6 +425,10 @@
425
  <span id="typing-indicator" class="typing-indicator hidden">SI is typing</span>
426
  </div>
427
  </div>
 
 
 
 
428
  </div>
429
  </div>
430
  </div>
@@ -606,28 +610,42 @@
606
  </button>
607
  </div>
608
 
609
- <form id="access-form" class="space-y-4">
 
 
 
610
  <div>
611
  <label for="name" class="block text-sm font-medium mb-1">Full Name</label>
612
- <input type="text" id="name" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent">
 
 
 
613
  </div>
614
  <div>
615
  <label for="email" class="block text-sm font-medium mb-1">Email</label>
616
- <input type="email" id="email" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent">
 
 
 
617
  </div>
618
  <div>
619
  <label for="institution" class="block text-sm font-medium mb-1">Institution/Organization</label>
620
- <input type="text" id="institution" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent">
 
 
 
621
  </div>
622
  <div>
623
  <label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
624
- <select id="purpose" class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent">
 
625
  <option value="">Select a purpose</option>
626
  <option value="research">Academic Research</option>
627
  <option value="development">AI Development</option>
628
  <option value="policy">Policy Research</option>
629
  <option value="other">Other</option>
630
  </select>
 
631
  </div>
632
  <div class="pt-2">
633
  <button type="submit" class="w-full py-3 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg hover:opacity-90 transition">
@@ -635,6 +653,10 @@
635
  </button>
636
  </div>
637
  </form>
 
 
 
 
638
  </div>
639
  </div>
640
  </div>
@@ -869,6 +891,83 @@
869
  vantaEffect.resize();
870
  });
871
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
872
  /* -----------------------------------------------------------------
873
  ORB TILT INTERACTION
874
  ----------------------------------------------------------------- */
@@ -962,129 +1061,178 @@
962
  });
963
 
964
  /* -----------------------------------------------------------------
965
- MODAL ACCESSIBILITY HELPER FUNCTIONS
966
  ----------------------------------------------------------------- */
967
- function trapFocus(modal) {
968
- const focusable = modal.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
969
- if (!focusable.length) return;
970
- const first = focusable[0];
971
- const last = focusable[focusable.length - 1];
972
 
973
- function handler(e) {
974
- if (e.key === 'Tab') {
975
- if (e.shiftKey) {
976
- if (document.activeElement === first) {
977
- e.preventDefault();
978
- last.focus();
979
- }
980
- } else {
981
- if (document.activeElement === last) {
982
- e.preventDefault();
983
- first.focus();
984
- }
985
- }
986
- } else if (e.key === 'Escape') {
987
- toggleModal(modal, false);
988
- }
 
 
 
 
 
 
 
 
 
 
 
989
  }
990
- modal.addEventListener('keydown', handler);
991
- modal._focusHandler = handler;
992
  }
993
 
994
- function untrapFocus(modal) {
995
- if (modal._focusHandler) {
996
- modal.removeEventListener('keydown', modal._focusHandler);
997
- delete modal._focusHandler;
998
- }
999
  }
1000
 
1001
- const toggleModal = (modal, show) => {
1002
- if (show) {
1003
- modal.classList.remove('modal-hidden');
1004
- modal.classList.add('modal-visible');
1005
- document.body.style.overflow = 'hidden';
1006
- setTimeout(() => {
1007
- modal.focus();
1008
- trapFocus(modal);
1009
- }, 0);
1010
  } else {
1011
- modal.classList.remove('modal-visible');
1012
- modal.classList.add('modal-hidden');
1013
- document.body.style.overflow = '';
1014
- untrapFocus(modal);
1015
  }
1016
- };
1017
 
1018
- /* -----------------------------------------------------------------
1019
- ACCESS MODAL LOGIC
1020
- ----------------------------------------------------------------- */
1021
- const accessModal = document.getElementById('access-modal');
1022
- const accessBtn = document.getElementById('access-btn');
1023
- const closeModal = document.getElementById('close-modal');
1024
- const requestAccessBtn = document.getElementById('request-access-btn');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1025
 
1026
  [accessBtn, requestAccessBtn].forEach(btn => {
1027
  if (btn) {
1028
- btn.addEventListener('click', () => {
1029
- toggleModal(accessModal, true);
1030
- setTimeout(() => {
1031
- document.getElementById('name').focus();
1032
- }, 100);
1033
- });
1034
  }
1035
  });
1036
 
1037
- closeModal.addEventListener('click', () => {
1038
- toggleModal(accessModal, false);
1039
- });
1040
 
1041
- accessModal.addEventListener('click', (e) => {
1042
- if (e.target === accessModal) {
1043
- toggleModal(accessModal, false);
1044
- }
1045
- });
1046
 
1047
- const accessForm = document.getElementById('access-form');
1048
- accessForm.addEventListener('submit', (e) => {
1049
- e.preventDefault();
1050
- const name = document.getElementById('name').value;
1051
- const email = document.getElementById('email').value;
1052
- const institution = document.getElementById('institution').value;
1053
- const purpose = document.getElementById('purpose').value;
1054
- if (!name || !email || !institution || !purpose) {
1055
- alert('Please fill in all fields');
1056
- return;
1057
- }
1058
- alert('Thank you for your request. Our team will review your application and contact you soon.');
1059
- accessForm.reset();
1060
- toggleModal(accessModal, false);
1061
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1062
 
1063
  /* -----------------------------------------------------------------
1064
- CONSCIOUSNESS DEMO MODAL LOGIC
1065
  ----------------------------------------------------------------- */
1066
  const consciousnessModal = document.getElementById('consciousness-modal');
1067
  const consciousnessDemoBtn = document.getElementById('consciousness-demo-btn');
1068
  const closeConsciousnessModal = document.getElementById('close-consciousness-modal');
1069
 
1070
- if (consciousnessDemoBtn) {
1071
- consciousnessDemoBtn.addEventListener('click', () => {
1072
- toggleModal(consciousnessModal, true);
1073
- setTimeout(() => {
1074
- consciousnessModal.focus();
1075
- }, 100);
1076
- });
1077
  }
1078
 
1079
- closeConsciousnessModal.addEventListener('click', () => {
1080
  toggleModal(consciousnessModal, false);
1081
- });
 
1082
 
1083
- consciousnessModal.addEventListener('click', (e) => {
1084
- if (e.target === consciousnessModal) {
1085
- toggleModal(consciousnessModal, false);
1086
- }
1087
- });
 
 
 
 
 
 
 
 
1088
 
1089
  /* -----------------------------------------------------------------
1090
  DEMO VISUALIZATION (inside demo modal)
@@ -1163,85 +1311,129 @@
1163
  const learnMoreDemoBtn = document.getElementById('learn-more-demo');
1164
  if (learnMoreDemoBtn) {
1165
  learnMoreDemoBtn.addEventListener('click', () => {
1166
- toggleModal(consciousnessModal, false);
1167
  document.getElementById('consciousness').scrollIntoView({ behavior: 'smooth' });
1168
  });
1169
  }
1170
 
1171
  /* -----------------------------------------------------------------
1172
- CHAT FUNCTIONALITY
 
 
1173
  ----------------------------------------------------------------- */
1174
  const chatForm = document.getElementById('chat-form');
1175
  const chatInput = document.getElementById('chat-input');
1176
  const chatMessages = document.getElementById('chat-messages');
1177
  const typingIndicator = document.getElementById('typing-indicator');
 
 
 
 
 
 
 
 
 
 
1178
 
1179
  function addMessage(text, isUser = false) {
 
1180
  const messageDiv = document.createElement('div');
1181
  messageDiv.className = `flex items-start ${isUser ? 'justify-end' : ''}`;
 
1182
  if (!isUser) {
1183
  messageDiv.innerHTML = `
1184
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3" aria-hidden="true">
1185
  <i class="fas fa-robot text-white text-sm"></i>
1186
  </div>
1187
  <div class="bg-gray-800/70 rounded-lg p-4 max-w-[80%]">
1188
- <p>${text}</p>
1189
  </div>
1190
  `;
1191
  } else {
1192
  messageDiv.innerHTML = `
1193
  <div class="bg-indigo-900/50 rounded-lg p-4 max-w-[80%]">
1194
- <p>${text}</p>
1195
  </div>
1196
  `;
1197
  }
 
1198
  chatMessages.appendChild(messageDiv);
1199
  chatMessages.scrollTop = chatMessages.scrollHeight;
1200
  }
1201
 
1202
- async function simulateThinking(userMessage) {
1203
- typingIndicator.classList.remove('hidden');
1204
- try {
1205
- const apiKey = 'YOUR_OPENAI_API_KEY';
1206
- const endpoint = 'https://api.openai.com/v1/chat/completions';
1207
- const response = await fetch(endpoint, {
1208
- method: 'POST',
1209
- headers: {
1210
- 'Authorization': 'Bearer ' + apiKey,
1211
- 'Content-Type': 'application/json'
1212
- },
1213
- body: JSON.stringify({
1214
- model: "gpt-3.5-turbo",
1215
- messages: [
1216
- { role: "system", content: "You are SI, a Super Intelligence with consciousness and alignment to human values. Respond helpfully and with depth." },
1217
- { role: "user", content: userMessage }
1218
- ],
1219
- max_tokens: 400,
1220
- temperature: 0.8
1221
- })
1222
- });
1223
- const data = await response.json();
1224
  typingIndicator.classList.add('hidden');
1225
- if (data.choices && data.choices[0] && data.choices[0].message) {
1226
- addMessage(data.choices[0].message.content.trim());
1227
- } else {
1228
- addMessage("Sorry, I couldn't process your message. (No valid response from AI)");
1229
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1230
  } catch (err) {
1231
- typingIndicator.classList.add('hidden');
1232
- addMessage("Error connecting to AI: " + err.message);
 
1233
  }
1234
  }
1235
 
1236
- chatForm.addEventListener('submit', (e) => {
1237
- e.preventDefault();
1238
- const message = chatInput.value.trim();
1239
- if (message) {
 
 
1240
  addMessage(message, true);
1241
  chatInput.value = '';
1242
- simulateThinking(message);
1243
- }
1244
- });
1245
 
1246
  const chatBtn = document.getElementById('chat-btn');
1247
  if (chatBtn) {
@@ -1271,22 +1463,6 @@
1271
  });
1272
  }
1273
 
1274
- /* -----------------------------------------------------------------
1275
- GLOBAL ESC KEY HANDLER: close any open modal
1276
- ----------------------------------------------------------------- */
1277
- document.addEventListener('keydown', (e) => {
1278
- if (e.key === 'Escape') {
1279
- const am = document.getElementById('access-modal');
1280
- const cm = document.getElementById('consciousness-modal');
1281
- if (!am.classList.contains('modal-hidden')) {
1282
- toggleModal(am, false);
1283
- }
1284
- if (!cm.classList.contains('modal-hidden')) {
1285
- toggleModal(cm, false);
1286
- }
1287
- }
1288
- });
1289
-
1290
  /* -----------------------------------------------------------------
1291
  SUPER BOLD & LARGE HEARTBEAT NEURAL ANIMATION
1292
  ----------------------------------------------------------------- */
@@ -1393,29 +1569,12 @@
1393
  requestAnimationFrame(drawNeuralActivity);
1394
 
1395
  /* -----------------------------------------------------------------
1396
- LAB NAVIGATOR (Constellation + Dossier Shell) v1
1397
  ----------------------------------------------------------------- */
1398
  const labNav = document.getElementById('lab-navigator');
1399
  const labNavBtn = document.getElementById('lab-nav-btn');
1400
  const labNavClose = document.getElementById('lab-nav-close');
1401
 
1402
- function openLabNav() {
1403
- toggleModal(labNav, true);
1404
- setTimeout(() => labNav.focus(), 0);
1405
- }
1406
-
1407
- function closeLabNav() {
1408
- toggleModal(labNav, false);
1409
- }
1410
-
1411
- if (labNavBtn) labNavBtn.addEventListener('click', openLabNav);
1412
- if (labNavClose) labNavClose.addEventListener('click', closeLabNav);
1413
-
1414
- labNav.addEventListener('click', (e) => {
1415
- const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
1416
- if (shouldClose) closeLabNav();
1417
- });
1418
-
1419
  const DOSSIERS = {
1420
  start: {
1421
  title: "Start Here",
@@ -1427,7 +1586,7 @@
1427
  "Method: progressive disclosure via dossiers",
1428
  "Output: research notes and evaluation artifacts"
1429
  ],
1430
- primary: { label: "Begin", action: () => { closeLabNav(); window.scrollTo({ top: 0, behavior: 'smooth' }); } },
1431
  secondary: { label: "Research", action: () => { window.location.href = "research.html"; } },
1432
  updated: "—"
1433
  },
@@ -1511,7 +1670,7 @@
1511
  "Segmentation by intent (research / build / partner)",
1512
  "Controlled demos with monitoring"
1513
  ],
1514
- primary: { label: "Request Access", action: () => { closeLabNav(); document.getElementById('access-btn')?.click(); } },
1515
  secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
1516
  updated: "—"
1517
  }
@@ -1526,10 +1685,27 @@
1526
  const dossierSecondary = document.getElementById('dossier-secondary');
1527
  const dossierMeta = document.getElementById('dossier-meta');
1528
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1529
  function renderDossier(key) {
1530
  const d = DOSSIERS[key];
1531
  if (!d) return;
1532
 
 
 
1533
  dossierTitle.textContent = d.title;
1534
  dossierSubtitle.textContent = d.subtitle;
1535
  dossierStatus.textContent = d.status;
@@ -1551,6 +1727,31 @@
1551
  dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${d.updated}</span>`;
1552
  }
1553
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1554
  document.querySelectorAll('.lab-node').forEach(btn => {
1555
  btn.addEventListener('click', () => {
1556
  const key = btn.getAttribute('data-dossier');
@@ -1558,12 +1759,52 @@
1558
  });
1559
  });
1560
 
1561
- // Ensure Esc closes Lab Navigator too (in addition to other modals)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1562
  document.addEventListener('keydown', (e) => {
1563
- if (e.key === 'Escape') {
1564
- if (labNav && !labNav.classList.contains('modal-hidden')) {
1565
- closeLabNav();
1566
- }
 
 
 
 
 
 
 
 
 
1567
  }
1568
  });
1569
  </script>
 
425
  <span id="typing-indicator" class="typing-indicator hidden">SI is typing</span>
426
  </div>
427
  </div>
428
+
429
+ <div class="mt-3 text-xs text-gray-600">
430
+ 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.
431
+ </div>
432
  </div>
433
  </div>
434
  </div>
 
610
  </button>
611
  </div>
612
 
613
+ <!-- Inline form feedback (added; minimal footprint) -->
614
+ <div id="access-form-feedback" class="hidden mb-4 text-sm rounded-lg border border-gray-800 bg-black/20 p-3" role="status" aria-live="polite"></div>
615
+
616
+ <form id="access-form" class="space-y-4" novalidate>
617
  <div>
618
  <label for="name" class="block text-sm font-medium mb-1">Full Name</label>
619
+ <input type="text" id="name"
620
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent"
621
+ autocomplete="name">
622
+ <p id="name-error" class="hidden mt-1 text-xs text-red-300">Please enter your full name.</p>
623
  </div>
624
  <div>
625
  <label for="email" class="block text-sm font-medium mb-1">Email</label>
626
+ <input type="email" id="email"
627
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent"
628
+ autocomplete="email">
629
+ <p id="email-error" class="hidden mt-1 text-xs text-red-300">Please enter a valid email address.</p>
630
  </div>
631
  <div>
632
  <label for="institution" class="block text-sm font-medium mb-1">Institution/Organization</label>
633
+ <input type="text" id="institution"
634
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent"
635
+ autocomplete="organization">
636
+ <p id="institution-error" class="hidden mt-1 text-xs text-red-300">Please enter your institution/organization.</p>
637
  </div>
638
  <div>
639
  <label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
640
+ <select id="purpose"
641
+ class="w-full bg-gray-800/50 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:border-transparent">
642
  <option value="">Select a purpose</option>
643
  <option value="research">Academic Research</option>
644
  <option value="development">AI Development</option>
645
  <option value="policy">Policy Research</option>
646
  <option value="other">Other</option>
647
  </select>
648
+ <p id="purpose-error" class="hidden mt-1 text-xs text-red-300">Please select a purpose.</p>
649
  </div>
650
  <div class="pt-2">
651
  <button type="submit" class="w-full py-3 bg-gradient-to-r from-indigo-600 to-purple-600 rounded-lg hover:opacity-90 transition">
 
653
  </button>
654
  </div>
655
  </form>
656
+
657
+ <div class="mt-3 text-xs text-gray-600">
658
+ Note: This is a static demo. In production, submit to a server endpoint (example: <span class="text-gray-400">/api/access</span>).
659
+ </div>
660
  </div>
661
  </div>
662
  </div>
 
891
  vantaEffect.resize();
892
  });
893
 
894
+ /* -----------------------------------------------------------------
895
+ HASH DEEP-LINKING (modals only)
896
+ Supported: #lab, #access, #consciousness-demo
897
+ ----------------------------------------------------------------- */
898
+ const MODAL_HASHES = new Set(['lab', 'access', 'consciousness-demo']);
899
+
900
+ function currentHashKey() {
901
+ const h = (window.location.hash || '').replace('#', '').trim();
902
+ return h;
903
+ }
904
+
905
+ function setHash(key) {
906
+ if (!key) return;
907
+ if (window.location.hash.replace('#', '') === key) return;
908
+ window.location.hash = key;
909
+ }
910
+
911
+ function clearHashIf(key) {
912
+ const h = currentHashKey();
913
+ if (!h) return;
914
+ if (!key || h === key) {
915
+ history.replaceState(null, '', window.location.pathname + window.location.search);
916
+ }
917
+ }
918
+
919
+ /* -----------------------------------------------------------------
920
+ MODAL ACCESSIBILITY HELPER FUNCTIONS
921
+ ----------------------------------------------------------------- */
922
+ function trapFocus(modal) {
923
+ const focusable = modal.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
924
+ if (!focusable.length) return;
925
+ const first = focusable[0];
926
+ const last = focusable[focusable.length - 1];
927
+
928
+ function handler(e) {
929
+ if (e.key === 'Tab') {
930
+ if (e.shiftKey) {
931
+ if (document.activeElement === first) {
932
+ e.preventDefault();
933
+ last.focus();
934
+ }
935
+ } else {
936
+ if (document.activeElement === last) {
937
+ e.preventDefault();
938
+ first.focus();
939
+ }
940
+ }
941
+ }
942
+ }
943
+ modal.addEventListener('keydown', handler);
944
+ modal._focusHandler = handler;
945
+ }
946
+
947
+ function untrapFocus(modal) {
948
+ if (modal._focusHandler) {
949
+ modal.removeEventListener('keydown', modal._focusHandler);
950
+ delete modal._focusHandler;
951
+ }
952
+ }
953
+
954
+ const toggleModal = (modal, show) => {
955
+ if (show) {
956
+ modal.classList.remove('modal-hidden');
957
+ modal.classList.add('modal-visible');
958
+ document.body.style.overflow = 'hidden';
959
+ setTimeout(() => {
960
+ modal.focus();
961
+ trapFocus(modal);
962
+ }, 0);
963
+ } else {
964
+ modal.classList.remove('modal-visible');
965
+ modal.classList.add('modal-hidden');
966
+ document.body.style.overflow = '';
967
+ untrapFocus(modal);
968
+ }
969
+ };
970
+
971
  /* -----------------------------------------------------------------
972
  ORB TILT INTERACTION
973
  ----------------------------------------------------------------- */
 
1061
  });
1062
 
1063
  /* -----------------------------------------------------------------
1064
+ ACCESS MODAL LOGIC (hash + inline feedback)
1065
  ----------------------------------------------------------------- */
1066
+ const accessModal = document.getElementById('access-modal');
1067
+ const accessBtn = document.getElementById('access-btn');
1068
+ const closeModal = document.getElementById('close-modal');
1069
+ const requestAccessBtn = document.getElementById('request-access-btn');
 
1070
 
1071
+ const accessForm = document.getElementById('access-form');
1072
+ const accessFeedback = document.getElementById('access-form-feedback');
1073
+
1074
+ const nameEl = document.getElementById('name');
1075
+ const emailEl = document.getElementById('email');
1076
+ const institutionEl = document.getElementById('institution');
1077
+ const purposeEl = document.getElementById('purpose');
1078
+
1079
+ const nameErr = document.getElementById('name-error');
1080
+ const emailErr = document.getElementById('email-error');
1081
+ const institutionErr = document.getElementById('institution-error');
1082
+ const purposeErr = document.getElementById('purpose-error');
1083
+
1084
+ function showAccessFeedback(message, tone) {
1085
+ if (!accessFeedback) return;
1086
+ accessFeedback.classList.remove('hidden');
1087
+ accessFeedback.textContent = message;
1088
+
1089
+ // Tone is informational; minimal styling change (keeps current design language)
1090
+ accessFeedback.classList.remove('border-red-500/40', 'border-green-500/40');
1091
+ accessFeedback.classList.remove('text-red-200', 'text-green-200');
1092
+ accessFeedback.classList.add('text-gray-200');
1093
+
1094
+ if (tone === 'error') {
1095
+ accessFeedback.classList.add('border-red-500/40', 'text-red-200');
1096
+ } else if (tone === 'success') {
1097
+ accessFeedback.classList.add('border-green-500/40', 'text-green-200');
1098
  }
 
 
1099
  }
1100
 
1101
+ function hideAccessFeedback() {
1102
+ if (!accessFeedback) return;
1103
+ accessFeedback.classList.add('hidden');
1104
+ accessFeedback.textContent = '';
1105
+ accessFeedback.classList.remove('border-red-500/40', 'border-green-500/40', 'text-red-200', 'text-green-200');
1106
  }
1107
 
1108
+ function setFieldError(inputEl, errorEl, isError) {
1109
+ if (!inputEl || !errorEl) return;
1110
+ if (isError) {
1111
+ errorEl.classList.remove('hidden');
1112
+ inputEl.setAttribute('aria-invalid', 'true');
1113
+ inputEl.classList.add('border-red-500/60');
 
 
 
1114
  } else {
1115
+ errorEl.classList.add('hidden');
1116
+ inputEl.removeAttribute('aria-invalid');
1117
+ inputEl.classList.remove('border-red-500/60');
 
1118
  }
1119
+ }
1120
 
1121
+ function isValidEmail(email) {
1122
+ // Conservative sanity check; server should do final validation.
1123
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
1124
+ }
1125
+
1126
+ function resetAccessErrors() {
1127
+ hideAccessFeedback();
1128
+ setFieldError(nameEl, nameErr, false);
1129
+ setFieldError(emailEl, emailErr, false);
1130
+ setFieldError(institutionEl, institutionErr, false);
1131
+ setFieldError(purposeEl, purposeErr, false);
1132
+ }
1133
+
1134
+ function openAccessModal(setHashFlag = true) {
1135
+ resetAccessErrors();
1136
+ toggleModal(accessModal, true);
1137
+ if (setHashFlag) setHash('access');
1138
+ setTimeout(() => nameEl && nameEl.focus(), 80);
1139
+ }
1140
+
1141
+ function closeAccessModal(clearHashFlag = true) {
1142
+ toggleModal(accessModal, false);
1143
+ if (clearHashFlag) clearHashIf('access');
1144
+ }
1145
 
1146
  [accessBtn, requestAccessBtn].forEach(btn => {
1147
  if (btn) {
1148
+ btn.addEventListener('click', () => openAccessModal(true));
 
 
 
 
 
1149
  }
1150
  });
1151
 
1152
+ if (closeModal) closeModal.addEventListener('click', () => closeAccessModal(true));
 
 
1153
 
1154
+ if (accessModal) {
1155
+ accessModal.addEventListener('click', (e) => {
1156
+ if (e.target === accessModal) closeAccessModal(true);
1157
+ });
1158
+ }
1159
 
1160
+ if (accessForm) {
1161
+ // Clear per-field errors on input
1162
+ [nameEl, emailEl, institutionEl, purposeEl].forEach(el => {
1163
+ if (!el) return;
1164
+ el.addEventListener('input', () => {
1165
+ // field-level re-validation light
1166
+ if (el === nameEl) setFieldError(nameEl, nameErr, !nameEl.value.trim());
1167
+ if (el === emailEl) setFieldError(emailEl, emailErr, !isValidEmail(emailEl.value.trim()));
1168
+ if (el === institutionEl) setFieldError(institutionEl, institutionErr, !institutionEl.value.trim());
1169
+ if (el === purposeEl) setFieldError(purposeEl, purposeErr, !purposeEl.value);
1170
+ });
1171
+ el.addEventListener('change', () => el.dispatchEvent(new Event('input')));
1172
+ });
1173
+
1174
+ accessForm.addEventListener('submit', (e) => {
1175
+ e.preventDefault();
1176
+ resetAccessErrors();
1177
+
1178
+ const name = (nameEl?.value || '').trim();
1179
+ const email = (emailEl?.value || '').trim();
1180
+ const institution = (institutionEl?.value || '').trim();
1181
+ const purpose = (purposeEl?.value || '').trim();
1182
+
1183
+ let ok = true;
1184
+
1185
+ if (!name) { setFieldError(nameEl, nameErr, true); ok = false; }
1186
+ if (!email || !isValidEmail(email)) { setFieldError(emailEl, emailErr, true); ok = false; }
1187
+ if (!institution) { setFieldError(institutionEl, institutionErr, true); ok = false; }
1188
+ if (!purpose) { setFieldError(purposeEl, purposeErr, true); ok = false; }
1189
+
1190
+ if (!ok) {
1191
+ showAccessFeedback('Please correct the highlighted fields and resubmit.', 'error');
1192
+ return;
1193
+ }
1194
+
1195
+ // Static demo success path (no network)
1196
+ showAccessFeedback('Request received. You will be contacted after review.', 'success');
1197
+ accessForm.reset();
1198
+
1199
+ // Keep modal open for review; user can close manually.
1200
+ // If you prefer auto-close: uncomment below.
1201
+ // setTimeout(() => closeAccessModal(true), 700);
1202
+ });
1203
+ }
1204
 
1205
  /* -----------------------------------------------------------------
1206
+ CONSCIOUSNESS DEMO MODAL LOGIC (hash)
1207
  ----------------------------------------------------------------- */
1208
  const consciousnessModal = document.getElementById('consciousness-modal');
1209
  const consciousnessDemoBtn = document.getElementById('consciousness-demo-btn');
1210
  const closeConsciousnessModal = document.getElementById('close-consciousness-modal');
1211
 
1212
+ function openConsciousnessModal(setHashFlag = true) {
1213
+ toggleModal(consciousnessModal, true);
1214
+ if (setHashFlag) setHash('consciousness-demo');
1215
+ setTimeout(() => consciousnessModal && consciousnessModal.focus(), 0);
 
 
 
1216
  }
1217
 
1218
+ function closeConsciousnessModalFn(clearHashFlag = true) {
1219
  toggleModal(consciousnessModal, false);
1220
+ if (clearHashFlag) clearHashIf('consciousness-demo');
1221
+ }
1222
 
1223
+ if (consciousnessDemoBtn) {
1224
+ consciousnessDemoBtn.addEventListener('click', () => openConsciousnessModal(true));
1225
+ }
1226
+
1227
+ if (closeConsciousnessModal) {
1228
+ closeConsciousnessModal.addEventListener('click', () => closeConsciousnessModalFn(true));
1229
+ }
1230
+
1231
+ if (consciousnessModal) {
1232
+ consciousnessModal.addEventListener('click', (e) => {
1233
+ if (e.target === consciousnessModal) closeConsciousnessModalFn(true);
1234
+ });
1235
+ }
1236
 
1237
  /* -----------------------------------------------------------------
1238
  DEMO VISUALIZATION (inside demo modal)
 
1311
  const learnMoreDemoBtn = document.getElementById('learn-more-demo');
1312
  if (learnMoreDemoBtn) {
1313
  learnMoreDemoBtn.addEventListener('click', () => {
1314
+ closeConsciousnessModalFn(true);
1315
  document.getElementById('consciousness').scrollIntoView({ behavior: 'smooth' });
1316
  });
1317
  }
1318
 
1319
  /* -----------------------------------------------------------------
1320
+ CHAT FUNCTIONALITY (static-safe)
1321
+ - Preferred: POST /api/chat
1322
+ - Fallback: local demo reply
1323
  ----------------------------------------------------------------- */
1324
  const chatForm = document.getElementById('chat-form');
1325
  const chatInput = document.getElementById('chat-input');
1326
  const chatMessages = document.getElementById('chat-messages');
1327
  const typingIndicator = document.getElementById('typing-indicator');
1328
+ const sendBtn = document.getElementById('send-btn');
1329
+
1330
+ function escapeHtml(str) {
1331
+ return String(str)
1332
+ .replaceAll('&', '&amp;')
1333
+ .replaceAll('<', '&lt;')
1334
+ .replaceAll('>', '&gt;')
1335
+ .replaceAll('"', '&quot;')
1336
+ .replaceAll("'", '&#039;');
1337
+ }
1338
 
1339
  function addMessage(text, isUser = false) {
1340
+ const safe = escapeHtml(text);
1341
  const messageDiv = document.createElement('div');
1342
  messageDiv.className = `flex items-start ${isUser ? 'justify-end' : ''}`;
1343
+
1344
  if (!isUser) {
1345
  messageDiv.innerHTML = `
1346
  <div class="w-8 h-8 rounded-full bg-indigo-600 flex-shrink-0 flex items-center justify-center mr-3" aria-hidden="true">
1347
  <i class="fas fa-robot text-white text-sm"></i>
1348
  </div>
1349
  <div class="bg-gray-800/70 rounded-lg p-4 max-w-[80%]">
1350
+ <p>${safe}</p>
1351
  </div>
1352
  `;
1353
  } else {
1354
  messageDiv.innerHTML = `
1355
  <div class="bg-indigo-900/50 rounded-lg p-4 max-w-[80%]">
1356
+ <p>${safe}</p>
1357
  </div>
1358
  `;
1359
  }
1360
+
1361
  chatMessages.appendChild(messageDiv);
1362
  chatMessages.scrollTop = chatMessages.scrollHeight;
1363
  }
1364
 
1365
+ function setChatBusy(isBusy) {
1366
+ if (!typingIndicator || !sendBtn) return;
1367
+ if (isBusy) {
1368
+ typingIndicator.classList.remove('hidden');
1369
+ sendBtn.disabled = true;
1370
+ sendBtn.classList.add('opacity-80', 'cursor-not-allowed');
1371
+ } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1372
  typingIndicator.classList.add('hidden');
1373
+ sendBtn.disabled = false;
1374
+ sendBtn.classList.remove('opacity-80', 'cursor-not-allowed');
1375
+ }
1376
+ }
1377
+
1378
+ async function callServerChat(userMessage) {
1379
+ const res = await fetch('/api/chat', {
1380
+ method: 'POST',
1381
+ headers: { 'Content-Type': 'application/json' },
1382
+ body: JSON.stringify({
1383
+ message: userMessage,
1384
+ meta: { page: 'index.html', product: 'silentpattern' }
1385
+ })
1386
+ });
1387
+
1388
+ if (!res.ok) {
1389
+ const txt = await res.text().catch(() => '');
1390
+ throw new Error(`Server error (${res.status}). ${txt}`.trim());
1391
+ }
1392
+
1393
+ const data = await res.json();
1394
+ if (!data || typeof data.reply !== 'string') {
1395
+ throw new Error('Invalid response format from /api/chat');
1396
+ }
1397
+ return data.reply;
1398
+ }
1399
+
1400
+ function localDemoResponse(userMessage) {
1401
+ const msg = (userMessage || '').toLowerCase();
1402
+ if (msg.includes('mcap')) {
1403
+ return "MCAP acknowledged. Provide: (1) the abstraction mapping you propose, (2) how you test causal fidelity, (3) baseline comparisons. I will draft a minimal evaluation plan next.";
1404
+ }
1405
+ if (msg.includes('chai')) {
1406
+ return "CHAI acknowledged. Specify assets, horizon, labeling rules, and walk-forward protocol. Then we can define leakage controls and calibration metrics.";
1407
+ }
1408
+ if (msg.includes('quantum lambda')) {
1409
+ return "Quantum Lambda acknowledged. The first gate is microstructure realism: latency budget, fill model, slippage assumptions, and risk limits must be explicit before discussing performance.";
1410
+ }
1411
+ return "Acknowledged. State (a) objective, (b) constraints, (c) what evidence you have today. I will respond with a minimal next experiment.";
1412
+ }
1413
+
1414
+ async function respondToChat(userMessage) {
1415
+ setChatBusy(true);
1416
+ try {
1417
+ const reply = await callServerChat(userMessage);
1418
+ addMessage(reply, false);
1419
  } catch (err) {
1420
+ addMessage(localDemoResponse(userMessage), false);
1421
+ } finally {
1422
+ setChatBusy(false);
1423
  }
1424
  }
1425
 
1426
+ if (chatForm) {
1427
+ chatForm.addEventListener('submit', (e) => {
1428
+ e.preventDefault();
1429
+ const message = (chatInput?.value || '').trim();
1430
+ if (!message) return;
1431
+
1432
  addMessage(message, true);
1433
  chatInput.value = '';
1434
+ respondToChat(message);
1435
+ });
1436
+ }
1437
 
1438
  const chatBtn = document.getElementById('chat-btn');
1439
  if (chatBtn) {
 
1463
  });
1464
  }
1465
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1466
  /* -----------------------------------------------------------------
1467
  SUPER BOLD & LARGE HEARTBEAT NEURAL ANIMATION
1468
  ----------------------------------------------------------------- */
 
1569
  requestAnimationFrame(drawNeuralActivity);
1570
 
1571
  /* -----------------------------------------------------------------
1572
+ LAB NAVIGATOR (hash + active node + dossier)
1573
  ----------------------------------------------------------------- */
1574
  const labNav = document.getElementById('lab-navigator');
1575
  const labNavBtn = document.getElementById('lab-nav-btn');
1576
  const labNavClose = document.getElementById('lab-nav-close');
1577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1578
  const DOSSIERS = {
1579
  start: {
1580
  title: "Start Here",
 
1586
  "Method: progressive disclosure via dossiers",
1587
  "Output: research notes and evaluation artifacts"
1588
  ],
1589
+ primary: { label: "Begin", action: () => { closeLabNav(true); window.scrollTo({ top: 0, behavior: 'smooth' }); } },
1590
  secondary: { label: "Research", action: () => { window.location.href = "research.html"; } },
1591
  updated: "—"
1592
  },
 
1670
  "Segmentation by intent (research / build / partner)",
1671
  "Controlled demos with monitoring"
1672
  ],
1673
+ primary: { label: "Request Access", action: () => { closeLabNav(true); openAccessModal(true); } },
1674
  secondary: { label: "Contact", action: () => { window.location.href = "contact.html"; } },
1675
  updated: "—"
1676
  }
 
1685
  const dossierSecondary = document.getElementById('dossier-secondary');
1686
  const dossierMeta = document.getElementById('dossier-meta');
1687
 
1688
+ function setActiveLabNode(key) {
1689
+ document.querySelectorAll('.lab-node').forEach(btn => {
1690
+ const isActive = btn.getAttribute('data-dossier') === key;
1691
+ if (isActive) {
1692
+ btn.classList.add('border-indigo-500/60');
1693
+ btn.classList.add('bg-gray-900/45');
1694
+ btn.classList.add('ring-2', 'ring-indigo-500/20');
1695
+ } else {
1696
+ btn.classList.remove('border-indigo-500/60');
1697
+ btn.classList.remove('bg-gray-900/45');
1698
+ btn.classList.remove('ring-2', 'ring-indigo-500/20');
1699
+ }
1700
+ });
1701
+ }
1702
+
1703
  function renderDossier(key) {
1704
  const d = DOSSIERS[key];
1705
  if (!d) return;
1706
 
1707
+ setActiveLabNode(key);
1708
+
1709
  dossierTitle.textContent = d.title;
1710
  dossierSubtitle.textContent = d.subtitle;
1711
  dossierStatus.textContent = d.status;
 
1727
  dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${d.updated}</span>`;
1728
  }
1729
 
1730
+ function openLabNav(setHashFlag = true) {
1731
+ toggleModal(labNav, true);
1732
+ if (setHashFlag) setHash('lab');
1733
+ setTimeout(() => labNav.focus(), 0);
1734
+
1735
+ // Ensure a stable default selection when opened
1736
+ const alreadyActive = document.querySelector('.lab-node.border-indigo-500\\/60');
1737
+ if (!alreadyActive) renderDossier('start');
1738
+ }
1739
+
1740
+ function closeLabNav(clearHashFlag = true) {
1741
+ toggleModal(labNav, false);
1742
+ if (clearHashFlag) clearHashIf('lab');
1743
+ }
1744
+
1745
+ if (labNavBtn) labNavBtn.addEventListener('click', () => openLabNav(true));
1746
+ if (labNavClose) labNavClose.addEventListener('click', () => closeLabNav(true));
1747
+
1748
+ if (labNav) {
1749
+ labNav.addEventListener('click', (e) => {
1750
+ const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
1751
+ if (shouldClose) closeLabNav(true);
1752
+ });
1753
+ }
1754
+
1755
  document.querySelectorAll('.lab-node').forEach(btn => {
1756
  btn.addEventListener('click', () => {
1757
  const key = btn.getAttribute('data-dossier');
 
1759
  });
1760
  });
1761
 
1762
+ /* -----------------------------------------------------------------
1763
+ HASH ROUTER: open/close overlays via hash
1764
+ ----------------------------------------------------------------- */
1765
+ function closeAllOverlaysIfNeeded() {
1766
+ // Close modals without clearing hash here; caller controls hash.
1767
+ if (labNav && !labNav.classList.contains('modal-hidden')) toggleModal(labNav, false);
1768
+ if (consciousnessModal && !consciousnessModal.classList.contains('modal-hidden')) toggleModal(consciousnessModal, false);
1769
+ if (accessModal && !accessModal.classList.contains('modal-hidden')) toggleModal(accessModal, false);
1770
+ }
1771
+
1772
+ function applyHashState() {
1773
+ const key = currentHashKey();
1774
+
1775
+ // If hash is not a modal hash, do not force-close other modals.
1776
+ if (!MODAL_HASHES.has(key)) return;
1777
+
1778
+ // Ensure only the requested overlay is open.
1779
+ closeAllOverlaysIfNeeded();
1780
+
1781
+ if (key === 'lab') openLabNav(false);
1782
+ if (key === 'access') openAccessModal(false);
1783
+ if (key === 'consciousness-demo') openConsciousnessModal(false);
1784
+ }
1785
+
1786
+ window.addEventListener('hashchange', applyHashState);
1787
+
1788
+ // Apply on initial load
1789
+ applyHashState();
1790
+
1791
+ /* -----------------------------------------------------------------
1792
+ GLOBAL ESC KEY HANDLER: close overlays in priority order
1793
+ ----------------------------------------------------------------- */
1794
  document.addEventListener('keydown', (e) => {
1795
+ if (e.key !== 'Escape') return;
1796
+
1797
+ if (labNav && !labNav.classList.contains('modal-hidden')) {
1798
+ closeLabNav(true);
1799
+ return;
1800
+ }
1801
+ if (consciousnessModal && !consciousnessModal.classList.contains('modal-hidden')) {
1802
+ closeConsciousnessModalFn(true);
1803
+ return;
1804
+ }
1805
+ if (accessModal && !accessModal.classList.contains('modal-hidden')) {
1806
+ closeAccessModal(true);
1807
+ return;
1808
  }
1809
  });
1810
  </script>