// Intersection Observer for Scroll Animations const observerOptions = { root: null, rootMargin: '0px', threshold: 0.1 }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('active'); // Animate metric numbers if (entry.target.classList.contains('metric-value')) { animateValue(entry.target); } } }); }, observerOptions); // Initialize Observers document.addEventListener('DOMContentLoaded', () => { // Observe all reveal elements document.querySelectorAll('.reveal').forEach(el => observer.observe(el)); // Initialize feather icons if (typeof feather !== 'undefined') { feather.replace(); } // Smooth scroll for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // Initialize code copy buttons initCodeCopy(); // Animate metrics on load setTimeout(() => { document.querySelectorAll('.metric-value').forEach(el => { el.classList.add('active'); animateValue(el); }); }, 500); }); // Number Animation Function function animateValue(element) { const finalValue = element.getAttribute('data-value'); if (!finalValue) return; const isPercentage = finalValue.includes('%'); const isDecimal = finalValue.includes('.'); const numericValue = parseFloat(finalValue.replace(/[^0-9.]/g, '')); let current = 0; const increment = numericValue / 50; const duration = 1500; const stepTime = duration / 50; const timer = setInterval(() => { current += increment; if (current >= numericValue) { current = numericValue; clearInterval(timer); } let display = isDecimal ? current.toFixed(1) : Math.floor(current); element.textContent = display + (isPercentage ? '%' : ''); }, stepTime); } // Code Copy Functionality function initCodeCopy() { document.querySelectorAll('.copy-code-btn').forEach(btn => { btn.addEventListener('click', function() { const code = this.getAttribute('data-code'); navigator.clipboard.writeText(code).then(() => { const originalHTML = this.innerHTML; this.innerHTML = ''; feather.replace(); setTimeout(() => { this.innerHTML = originalHTML; feather.replace(); }, 2000); }); }); }); } // Phase Navigation Active State const sections = document.querySelectorAll('phase-section'); const navLinks = document.querySelectorAll('.phase-nav-link'); window.addEventListener('scroll', () => { let current = ''; sections.forEach(section => { const sectionTop = section.offsetTop; const sectionHeight = section.clientHeight; if (pageYOffset >= sectionTop - 200) { current = section.getAttribute('id'); } }); navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href').includes(current)) { link.classList.add('active'); } }); }); // Dynamic Background Gradient Follow Mouse document.addEventListener('mousemove', (e) => { const x = e.clientX / window.innerWidth; const y = e.clientY / window.innerHeight; document.documentElement.style.setProperty('--mouse-x', x); document.documentElement.style.setProperty('--mouse-y', y); }); // Performance Monitor if ('PerformanceObserver' in window) { const perfObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'largest-contentful-paint') { console.log(`LCP: ${entry.startTime}ms`); } } }); perfObserver.observe({ entryTypes: ['largest-contentful-paint'] }); } // Service Worker Registration (for PWA capabilities) if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').catch(err => { console.log('Service Worker registration failed:', err); }); } // Utility: Debounce function function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Handle window resize const handleResize = debounce(() => { // Recalculate layout if needed document.dispatchEvent(new CustomEvent('layout:resize')); }, 250); window.addEventListener('resize', handleResize); // Export functions for global access window.GeometricFlow = { animateValue, debounce, refreshIcons: () => feather.replace() };