Spaces:
Running
Running
Update index.html
Browse files- 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 |
-
|
|
|
|
|
|
|
|
|
|
| 610 |
<div>
|
| 611 |
<label for="name" class="block text-sm font-medium mb-1">Full Name</label>
|
| 612 |
-
<input type="text" id="name"
|
|
|
|
|
|
|
|
|
|
| 613 |
</div>
|
| 614 |
<div>
|
| 615 |
<label for="email" class="block text-sm font-medium mb-1">Email</label>
|
| 616 |
-
<input type="email" id="email"
|
|
|
|
|
|
|
|
|
|
| 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"
|
|
|
|
|
|
|
|
|
|
| 621 |
</div>
|
| 622 |
<div>
|
| 623 |
<label for="purpose" class="block text-sm font-medium mb-1">Purpose of Access</label>
|
| 624 |
-
<select id="purpose"
|
|
|
|
| 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
|
| 966 |
----------------------------------------------------------------- */
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
const last = focusable[focusable.length - 1];
|
| 972 |
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
|
| 988 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
}
|
| 990 |
-
modal.addEventListener('keydown', handler);
|
| 991 |
-
modal._focusHandler = handler;
|
| 992 |
}
|
| 993 |
|
| 994 |
-
function
|
| 995 |
-
if (
|
| 996 |
-
|
| 997 |
-
|
| 998 |
-
|
| 999 |
}
|
| 1000 |
|
| 1001 |
-
|
| 1002 |
-
if (
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
|
| 1007 |
-
modal.focus();
|
| 1008 |
-
trapFocus(modal);
|
| 1009 |
-
}, 0);
|
| 1010 |
} else {
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
untrapFocus(modal);
|
| 1015 |
}
|
| 1016 |
-
}
|
| 1017 |
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
}
|
| 1045 |
-
}
|
| 1046 |
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 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 |
-
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
|
| 1074 |
-
consciousnessModal.focus();
|
| 1075 |
-
}, 100);
|
| 1076 |
-
});
|
| 1077 |
}
|
| 1078 |
|
| 1079 |
-
|
| 1080 |
toggleModal(consciousnessModal, false);
|
| 1081 |
-
|
|
|
|
| 1082 |
|
| 1083 |
-
|
| 1084 |
-
|
| 1085 |
-
|
| 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 |
-
|
| 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>${
|
| 1189 |
</div>
|
| 1190 |
`;
|
| 1191 |
} else {
|
| 1192 |
messageDiv.innerHTML = `
|
| 1193 |
<div class="bg-indigo-900/50 rounded-lg p-4 max-w-[80%]">
|
| 1194 |
-
<p>${
|
| 1195 |
</div>
|
| 1196 |
`;
|
| 1197 |
}
|
|
|
|
| 1198 |
chatMessages.appendChild(messageDiv);
|
| 1199 |
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 1200 |
}
|
| 1201 |
|
| 1202 |
-
|
| 1203 |
-
typingIndicator
|
| 1204 |
-
|
| 1205 |
-
|
| 1206 |
-
|
| 1207 |
-
|
| 1208 |
-
|
| 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 |
-
|
| 1226 |
-
|
| 1227 |
-
|
| 1228 |
-
|
| 1229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1230 |
} catch (err) {
|
| 1231 |
-
|
| 1232 |
-
|
|
|
|
| 1233 |
}
|
| 1234 |
}
|
| 1235 |
|
| 1236 |
-
|
| 1237 |
-
|
| 1238 |
-
|
| 1239 |
-
|
|
|
|
|
|
|
| 1240 |
addMessage(message, true);
|
| 1241 |
chatInput.value = '';
|
| 1242 |
-
|
| 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 (
|
| 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();
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1562 |
document.addEventListener('keydown', (e) => {
|
| 1563 |
-
if (e.key
|
| 1564 |
-
|
| 1565 |
-
|
| 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('&', '&')
|
| 1333 |
+
.replaceAll('<', '<')
|
| 1334 |
+
.replaceAll('>', '>')
|
| 1335 |
+
.replaceAll('"', '"')
|
| 1336 |
+
.replaceAll("'", ''');
|
| 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>
|