[8c4ad8]: / web / index.js

Download this file

383 lines (315 with data), 12.7 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
/**
* DNAnalyzer Main JavaScript
* Handles animations, interactivity and UI functionality
*/
document.addEventListener('DOMContentLoaded', function() {
// Initialize DNA Helix animation
initDNAHelix();
// Initialize mobile menu toggle
initMobileMenu();
// Initialize navbar scroll effect
initNavbarScroll();
// Initialize stats counter animation
initStatsAnimation();
// Initialize notification banner dismiss functionality
initNotificationBanner();
// Initialize notification banner scroll behavior
initNotificationScroll();
// Initialize navbar pulse effect
initNavbarPulse();
});
// Shared state for notification banner
let notificationClosed = false;
/**
* Initialize the notification banner dismiss functionality and adjust navbar positioning
*/
function initNotificationBanner() {
const banner = document.querySelector('.notification-banner');
const navbar = document.getElementById('navbar');
if (!banner || !navbar) return;
const bannerHeight = banner.offsetHeight;
document.documentElement.style.setProperty('--notification-height', `${bannerHeight}px`);
const closeBtn = document.querySelector('.notification-banner .notification-close');
if (!closeBtn) return;
closeBtn.addEventListener('click', function() {
banner.classList.add('closed');
document.documentElement.style.setProperty('--notification-height', '0px');
navbar.style.top = '0';
notificationClosed = true;
});
// Initial positioning
navbar.style.top = `${bannerHeight}px`;
}
/**
* Initialize notification banner scroll behavior
*/
function initNotificationScroll() {
const banner = document.querySelector('.notification-banner');
const navbar = document.getElementById('navbar');
if (!banner || !navbar) return;
let lastScrollTop = 0;
const bannerHeight = banner.offsetHeight;
window.addEventListener('scroll', function() {
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (scrollTop > lastScrollTop && scrollTop > bannerHeight) {
// Scrolling down
banner.classList.add('hide-notification');
navbar.style.top = '0';
} else if (!notificationClosed) {
// Scrolling up and notification was not manually closed
banner.classList.remove('hide-notification');
navbar.style.top = `${bannerHeight}px`;
} else {
// Scrolling up but notification was manually closed
navbar.style.top = '0';
}
lastScrollTop = scrollTop;
});
}
/**
* Initialize the DNA Helix animation on the homepage
*/
function initDNAHelix() {
const dnaHelix = document.getElementById('dnaHelix');
if (!dnaHelix) return;
// Clear any existing content
dnaHelix.innerHTML = '';
const numBasesUp = 15; // Number of bases to extend upward
const numBasesDown = 25; // Number of bases to extend downward
const totalBases = numBasesUp + numBasesDown;
const basePairs = [
['A', 'T'],
['T', 'A'],
['G', 'C'],
['C', 'G']
];
// Create all base pair elements first but don't append to DOM yet
const fragments = document.createDocumentFragment();
const allBasePairs = [];
// Create base pairs with a proper twisted helix structure
// Start with negative indices for "upward" extension, then go to positive for "downward"
for (let i = -numBasesUp; i < numBasesDown; i++) {
const pairIndex = Math.floor(Math.random() * basePairs.length);
const [leftBase, rightBase] = basePairs[pairIndex];
const basePair = document.createElement('div');
basePair.className = 'base-pair';
// Position each base pair - adjust vertical position to account for bases extending upward
basePair.style.top = `${(i + numBasesUp) * 25}px`;
// Calculate initial rotation angle to create the helix twist
// Each pair is rotated an incremental amount for the helix effect
const angle = (i * 25) % 360;
// Calculate X offset based on the angle to create the curve effect
const xOffset = Math.sin(angle * Math.PI / 180) * 20;
// Start at the target position directly instead of animating to it
basePair.style.transform = `rotateY(${angle}deg) translateZ(40px) translateX(${xOffset}px)`;
// Set initial opacity to 0 to avoid flash of content
basePair.style.opacity = '0';
// Create elements
const leftBaseElem = document.createElement('div');
leftBaseElem.className = 'base left-base';
leftBaseElem.textContent = leftBase;
const rightBaseElem = document.createElement('div');
rightBaseElem.className = 'base right-base';
rightBaseElem.textContent = rightBase;
const connector = document.createElement('div');
connector.className = 'base-connector';
basePair.appendChild(leftBaseElem);
basePair.appendChild(connector);
basePair.appendChild(rightBaseElem);
fragments.appendChild(basePair);
allBasePairs.push(basePair);
}
// Add all elements to the DOM at once to minimize reflow
dnaHelix.appendChild(fragments);
// Adjust the container height to accommodate the extended helix
dnaHelix.style.height = `${totalBases * 25 + 50}px`;
// Add animations with staggered delays after elements are in the DOM
// This ensures smooth animation start
requestAnimationFrame(() => {
allBasePairs.forEach((basePair, index) => {
// Stagger animation delays for a smooth wave effect
const delay = (index * -0.1).toFixed(2);
// Apply animation now that the element is in the DOM
basePair.style.animation = `rotate 8s linear infinite ${delay}s`;
// Fade in elements smoothly over time
setTimeout(() => {
basePair.style.opacity = '1';
basePair.style.transition = 'opacity 0.5s ease-in-out';
}, 50 * index);
});
});
}
/**
* Initialize the mobile menu toggle functionality
*/
function initMobileMenu() {
const mobileToggle = document.getElementById('mobileToggle');
const navLinks = document.getElementById('navLinks');
if (!mobileToggle || !navLinks) return;
mobileToggle.addEventListener('click', function() {
navLinks.classList.toggle('active');
// Change the icon based on the state
const icon = mobileToggle.querySelector('i');
if (navLinks.classList.contains('active')) {
icon.classList.remove('fa-bars');
icon.classList.add('fa-times');
} else {
icon.classList.remove('fa-times');
icon.classList.add('fa-bars');
}
});
// Close the mobile menu when clicking a link
const links = navLinks.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('click', function() {
navLinks.classList.remove('active');
const icon = mobileToggle.querySelector('i');
icon.classList.remove('fa-times');
icon.classList.add('fa-bars');
});
});
}
/**
* Initialize the navbar scroll effect
*/
function initNavbarScroll() {
const navbar = document.getElementById('navbar');
if (!navbar) return;
window.addEventListener('scroll', function() {
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
});
}
/**
* Animate number counting from 0 to target
* @param {HTMLElement} element - The element containing the number
* @param {number} target - The target number to count to
* @param {string} suffix - Optional suffix like '%' or 'M'
* @param {number} duration - Animation duration in milliseconds
*/
function animateNumber(element, target, suffix = '', duration = 2000) {
if (!element) return;
let start = 0;
let startTime = null;
const targetNum = parseFloat(target);
function easeOutQuart(x) {
return 1 - Math.pow(1 - x, 4);
}
function animate(timestamp) {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
const easedProgress = easeOutQuart(progress);
let currentValue = Math.floor(easedProgress * targetNum);
// Handle special cases
if (suffix === '%') {
const decimal = (easedProgress * targetNum) % 1;
if (decimal > 0) {
currentValue = (easedProgress * targetNum).toFixed(1);
}
}
element.textContent = `${currentValue}${suffix}`;
if (progress < 1) {
requestAnimationFrame(animate);
} else {
element.textContent = `${target}${suffix}`;
}
}
requestAnimationFrame(animate);
}
/**
* Initialize the stats counter animation with Intersection Observer
*/
function initStatsAnimation() {
const statsSection = document.querySelector('.stats-section');
if (!statsSection) return;
const statElements = {
accuracy: { elem: document.getElementById('statAccuracy'), target: '141', suffix: '' },
sequences: { elem: document.getElementById('statSequences'), target: '7', suffix: 'M+' },
users: { elem: document.getElementById('statUsers'), target: '46', suffix: '+' }
};
let animated = false;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !animated) {
// Animate each stat with staggered delays
Object.values(statElements).forEach((stat, index) => {
setTimeout(() => {
if (stat.elem) {
animateNumber(stat.elem, stat.target, stat.suffix, 2000);
}
}, index * 200);
});
animated = true;
observer.unobserve(statsSection);
}
});
}, { threshold: 0.5 });
observer.observe(statsSection);
}
/**
* Add smooth scrolling for anchor links
*/
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
const targetId = this.getAttribute('href');
if (targetId === '#') return; // Skip if it's just "#"
const targetElement = document.querySelector(targetId);
if (targetElement) {
e.preventDefault();
window.scrollTo({
top: targetElement.offsetTop - 100,
behavior: 'smooth'
});
}
});
});
/**
* Initialize navbar pulse effect for futuristic look
*/
function initNavbarPulse() {
const navbar = document.getElementById('navbar');
if (!navbar) return;
setInterval(() => {
navbar.classList.toggle('navbar-pulse');
}, 2000);
}
/**
* Initialize futuristic notification effects on the notification banner
*/
function initFuturisticNotificationEffects() {
const banner = document.querySelector('.notification-banner');
if (!banner) return;
banner.addEventListener('mouseenter', () => {
banner.style.transition = 'filter 0.3s ease';
banner.style.filter = 'brightness(1.2) contrast(1.1)';
});
banner.addEventListener('mouseleave', () => {
banner.style.filter = 'none';
});
}
/**
* Add intersection observer for animating sections as they come into view
*/
const animateSections = document.querySelectorAll('.section, .feature-card, .card');
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -100px 0px'
};
const sectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
sectionObserver.unobserve(entry.target);
}
});
}, observerOptions);
animateSections.forEach(section => {
section.style.opacity = '0';
section.style.transform = 'translateY(20px)';
section.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
sectionObserver.observe(section);
});