Testing347 commited on
Commit
f424be9
·
verified ·
1 Parent(s): 9c2b8d6

Create assets/site.js

Browse files
Files changed (1) hide show
  1. assets/site.js +213 -0
assets/site.js ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- assets/site.js -->
2
+ <script>
3
+ (function () {
4
+ function q(id) { return document.getElementById(id); }
5
+
6
+ function trapFocus(modal) {
7
+ const focusable = modal.querySelectorAll(
8
+ 'a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])'
9
+ );
10
+ if (!focusable.length) return;
11
+ const first = focusable[0];
12
+ const last = focusable[focusable.length - 1];
13
+
14
+ function handler(e) {
15
+ if (e.key === 'Tab') {
16
+ if (e.shiftKey) {
17
+ if (document.activeElement === first) { e.preventDefault(); last.focus(); }
18
+ } else {
19
+ if (document.activeElement === last) { e.preventDefault(); first.focus(); }
20
+ }
21
+ } else if (e.key === 'Escape') {
22
+ window.SilentPattern.toggleModal(modal, false);
23
+ }
24
+ }
25
+ modal.addEventListener('keydown', handler);
26
+ modal._focusHandler = handler;
27
+ }
28
+
29
+ function untrapFocus(modal) {
30
+ if (modal._focusHandler) {
31
+ modal.removeEventListener('keydown', modal._focusHandler);
32
+ delete modal._focusHandler;
33
+ }
34
+ }
35
+
36
+ function toggleModal(modal, show) {
37
+ if (!modal) return;
38
+ if (show) {
39
+ modal.classList.remove('modal-hidden');
40
+ modal.classList.add('modal-visible');
41
+ document.body.style.overflow = 'hidden';
42
+ setTimeout(() => { modal.focus(); trapFocus(modal); }, 0);
43
+ } else {
44
+ modal.classList.remove('modal-visible');
45
+ modal.classList.add('modal-hidden');
46
+ document.body.style.overflow = '';
47
+ untrapFocus(modal);
48
+ }
49
+ }
50
+
51
+ function initVanta() {
52
+ const el = q('vanta-bg');
53
+ if (!el || !window.VANTA || !window.VANTA.NET) return null;
54
+
55
+ const fx = window.VANTA.NET({
56
+ el: "#vanta-bg",
57
+ mouseControls: true,
58
+ touchControls: true,
59
+ gyroControls: false,
60
+ minHeight: 200.0,
61
+ minWidth: 200.0,
62
+ scale: 1.0,
63
+ scaleMobile: 1.0,
64
+ color: 0x4f46e5,
65
+ backgroundColor: 0x020617,
66
+ points: 12.0,
67
+ maxDistance: 20.0,
68
+ spacing: 15.0
69
+ });
70
+
71
+ window.addEventListener('resize', () => fx.resize());
72
+ return fx;
73
+ }
74
+
75
+ function setupAccessModal() {
76
+ const accessModal = q('access-modal');
77
+ const accessBtn = q('access-btn');
78
+ const accessCta = q('access-cta');
79
+ const closeAccessModal = q('close-access-modal');
80
+
81
+ function openAccess() {
82
+ toggleModal(accessModal, true);
83
+ setTimeout(() => {
84
+ const name = q('name');
85
+ if (name) name.focus();
86
+ }, 50);
87
+ }
88
+
89
+ if (accessBtn) accessBtn.addEventListener('click', openAccess);
90
+ if (accessCta) accessCta.addEventListener('click', openAccess);
91
+ if (closeAccessModal) closeAccessModal.addEventListener('click', () => toggleModal(accessModal, false));
92
+ if (accessModal) accessModal.addEventListener('click', (e) => { if (e.target === accessModal) toggleModal(accessModal, false); });
93
+
94
+ const form = q('access-form');
95
+ if (form) {
96
+ form.addEventListener('submit', async (e) => {
97
+ e.preventDefault();
98
+ const name = (q('name')?.value || '').trim();
99
+ const email = (q('email')?.value || '').trim();
100
+ const institution = (q('institution')?.value || '').trim();
101
+ const purpose = (q('purpose')?.value || '').trim();
102
+
103
+ if (!name || !email || !institution || !purpose) {
104
+ alert('Please fill in all fields.');
105
+ return;
106
+ }
107
+
108
+ // Optional: post to server if present; otherwise just acknowledge.
109
+ try {
110
+ const res = await fetch('/api/access', {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({ name, email, institution, purpose, page: location.pathname })
114
+ });
115
+ if (!res.ok) throw new Error('Request not accepted.');
116
+ alert('Request received. You will be contacted after review.');
117
+ } catch {
118
+ alert('Request received. You will be contacted after review.');
119
+ }
120
+
121
+ form.reset();
122
+ toggleModal(accessModal, false);
123
+ });
124
+ }
125
+
126
+ return { accessModal };
127
+ }
128
+
129
+ function setupLabNavigator(dossiers, defaultKey) {
130
+ const labNav = q('lab-navigator');
131
+ const labNavBtn = q('lab-nav-btn');
132
+ const labNavClose = q('lab-nav-close');
133
+
134
+ function openLabNav() { toggleModal(labNav, true); setTimeout(() => labNav?.focus(), 0); }
135
+ function closeLabNav() { toggleModal(labNav, false); }
136
+
137
+ if (labNavBtn) labNavBtn.addEventListener('click', openLabNav);
138
+ if (labNavClose) labNavClose.addEventListener('click', closeLabNav);
139
+ if (labNav) labNav.addEventListener('click', (e) => {
140
+ const shouldClose = e.target && e.target.getAttribute('data-lab-close') === 'true';
141
+ if (shouldClose) closeLabNav();
142
+ });
143
+
144
+ const dossierTitle = q('dossier-title');
145
+ const dossierSubtitle = q('dossier-subtitle');
146
+ const dossierStatus = q('dossier-status');
147
+ const dossierBody = q('dossier-body');
148
+ const dossierEvidence = q('dossier-evidence');
149
+ const dossierPrimary = q('dossier-primary');
150
+ const dossierSecondary = q('dossier-secondary');
151
+ const dossierMeta = q('dossier-meta');
152
+
153
+ function renderDossier(key) {
154
+ const d = dossiers?.[key];
155
+ if (!d) return;
156
+
157
+ if (dossierTitle) dossierTitle.textContent = d.title || 'Lab Dossier';
158
+ if (dossierSubtitle) dossierSubtitle.textContent = d.subtitle || '';
159
+ if (dossierStatus) dossierStatus.textContent = d.status || 'READY';
160
+ if (dossierBody) dossierBody.textContent = d.body || '';
161
+
162
+ if (dossierEvidence) {
163
+ dossierEvidence.innerHTML = '';
164
+ (d.evidence || []).forEach(item => {
165
+ const li = document.createElement('li');
166
+ li.textContent = item;
167
+ dossierEvidence.appendChild(li);
168
+ });
169
+ if (!(d.evidence || []).length) {
170
+ const li = document.createElement('li');
171
+ li.className = 'text-gray-500';
172
+ li.textContent = 'No evidence items provided.';
173
+ dossierEvidence.appendChild(li);
174
+ }
175
+ }
176
+
177
+ if (dossierPrimary) {
178
+ dossierPrimary.textContent = d.primary?.label || 'Open';
179
+ dossierPrimary.onclick = d.primary?.action || null;
180
+ }
181
+ if (dossierSecondary) {
182
+ dossierSecondary.textContent = d.secondary?.label || 'View Note';
183
+ dossierSecondary.onclick = d.secondary?.action || null;
184
+ }
185
+ if (dossierMeta) {
186
+ const u = d.updated || '—';
187
+ dossierMeta.innerHTML = `Last updated: <span class="text-gray-300">${u}</span>`;
188
+ }
189
+ }
190
+
191
+ document.querySelectorAll('.lab-node').forEach(btn => {
192
+ btn.addEventListener('click', () => renderDossier(btn.getAttribute('data-dossier')));
193
+ });
194
+
195
+ document.addEventListener('keydown', (e) => {
196
+ if (e.key !== 'Escape') return;
197
+ const accessModal = q('access-modal');
198
+ if (labNav && !labNav.classList.contains('modal-hidden')) closeLabNav();
199
+ if (accessModal && !accessModal.classList.contains('modal-hidden')) toggleModal(accessModal, false);
200
+ });
201
+
202
+ renderDossier(defaultKey || 'start');
203
+ return { openLabNav, closeLabNav, renderDossier, labNav };
204
+ }
205
+
206
+ window.SilentPattern = {
207
+ toggleModal,
208
+ initVanta,
209
+ setupAccessModal,
210
+ setupLabNavigator
211
+ };
212
+ })();
213
+ </script>