|
|
|
|
|
function debounce(func, wait) { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function initNavigation() { |
|
|
const sections = document.querySelectorAll('section[id]'); |
|
|
const navLinks = document.querySelectorAll('.nav-link'); |
|
|
|
|
|
if (!sections.length || !navLinks.length) return; |
|
|
|
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
const id = entry.target.id; |
|
|
|
|
|
navLinks.forEach(link => { |
|
|
link.classList.remove('active'); |
|
|
if (link.getAttribute('href') === `#${id}`) { |
|
|
link.classList.add('active'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
}); |
|
|
}, { |
|
|
rootMargin: '-30% 0px -70% 0px', |
|
|
threshold: 0 |
|
|
}); |
|
|
|
|
|
sections.forEach(section => observer.observe(section)); |
|
|
} |
|
|
|
|
|
|
|
|
function initMobileMenu() { |
|
|
const mobileMenuBtn = document.getElementById('mobile-menu-btn'); |
|
|
const mobileMenu = document.getElementById('mobile-menu'); |
|
|
const mobileMenuLinks = mobileMenu?.querySelectorAll('a'); |
|
|
|
|
|
if (!mobileMenuBtn || !mobileMenu) return; |
|
|
|
|
|
mobileMenuBtn.addEventListener('click', () => { |
|
|
mobileMenu.classList.toggle('open'); |
|
|
}); |
|
|
|
|
|
|
|
|
mobileMenuLinks?.forEach(link => { |
|
|
link.addEventListener('click', () => { |
|
|
mobileMenu.classList.remove('open'); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
if (e.key === 'Escape') { |
|
|
mobileMenu.classList.remove('open'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function animateStats() { |
|
|
const stats = document.querySelectorAll('[data-stat]'); |
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
const target = entry.target; |
|
|
const finalValue = parseInt(target.getAttribute('data-stat')); |
|
|
const duration = 1500; |
|
|
const startTime = performance.now(); |
|
|
|
|
|
function updateCounter(currentTime) { |
|
|
const elapsed = currentTime - startTime; |
|
|
const progress = Math.min(elapsed / duration, 1); |
|
|
const currentValue = Math.floor(progress * finalValue); |
|
|
|
|
|
target.textContent = currentValue.toLocaleString(); |
|
|
|
|
|
if (progress < 1) { |
|
|
requestAnimationFrame(updateCounter); |
|
|
} else { |
|
|
target.textContent = finalValue.toLocaleString(); |
|
|
} |
|
|
} |
|
|
|
|
|
requestAnimationFrame(updateCounter); |
|
|
observer.unobserve(target); |
|
|
} |
|
|
}); |
|
|
}, { threshold: 0.5 }); |
|
|
|
|
|
stats.forEach(stat => observer.observe(stat)); |
|
|
} |
|
|
|
|
|
|
|
|
function initScrollAnimations() { |
|
|
const cards = document.querySelectorAll('.glass-card, .spec-block, .node-card, .benefit-card, .use-case-card, .component-card, .network-card, .learning-card, .path-card, .final-card'); |
|
|
|
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
entry.target.classList.add('fade-in-up'); |
|
|
observer.unobserve(entry.target); |
|
|
} |
|
|
}); |
|
|
}, { |
|
|
threshold: 0.1, |
|
|
rootMargin: '0px 0px -50px 0px' |
|
|
}); |
|
|
|
|
|
cards.forEach(card => observer.observe(card)); |
|
|
} |
|
|
|
|
|
|
|
|
function initSmoothScroll() { |
|
|
const links = document.querySelectorAll('a[href^="#"]'); |
|
|
|
|
|
links.forEach(link => { |
|
|
link.addEventListener('click', (e) => { |
|
|
e.preventDefault(); |
|
|
const targetId = link.getAttribute('href'); |
|
|
const targetElement = document.querySelector(targetId); |
|
|
|
|
|
if (targetElement) { |
|
|
const headerOffset = 80; |
|
|
const elementPosition = targetElement.offsetTop; |
|
|
const offsetPosition = elementPosition - headerOffset; |
|
|
|
|
|
window.scrollTo({ |
|
|
top: offsetPosition, |
|
|
behavior: 'smooth' |
|
|
}); |
|
|
|
|
|
|
|
|
history.pushState(null, null, targetId); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function initParallax() { |
|
|
const hero = document.getElementById('hero'); |
|
|
if (!hero) return; |
|
|
|
|
|
window.addEventListener('scroll', debounce(() => { |
|
|
const scrolled = window.pageYOffset; |
|
|
const rate = scrolled * -0.5; |
|
|
|
|
|
hero.style.transform = `translateY(${rate}px)`; |
|
|
}, 10)); |
|
|
} |
|
|
|
|
|
|
|
|
function handleAnchorLinks() { |
|
|
if (window.location.hash) { |
|
|
const element = document.querySelector(window.location.hash); |
|
|
if (element) { |
|
|
setTimeout(() => { |
|
|
const headerOffset = 80; |
|
|
const elementPosition = element.offsetTop; |
|
|
const offsetPosition = elementPosition - headerOffset; |
|
|
|
|
|
window.scrollTo({ |
|
|
top: offsetPosition, |
|
|
behavior: 'smooth' |
|
|
}); |
|
|
}, 100); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
initNavigation(); |
|
|
initMobileMenu(); |
|
|
initSmoothScroll(); |