// Shared JavaScript across all pages document.addEventListener('DOMContentLoaded', function() { // Initialize lazy loading initLazyLoading(); // Counter animation for stats const counters = document.querySelectorAll('[data-count]'); const speed = 200; const animateCounter = (counter) => { const target = +counter.getAttribute('data-count'); const increment = target / speed; const updateCount = () => { const count = +counter.innerText.replace(/,/g, ''); if (count < target) { counter.innerText = Math.ceil(count + increment).toLocaleString(); setTimeout(updateCount, 1); } else { counter.innerText = target.toLocaleString(); } }; updateCount(); }; // Intersection Observer for animations and lazy loading const observerOptions = { threshold: 0.1, rootMargin: '50px 0px -50px 0px' }; const observerCallback = (entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { // Handle lazy loaded images if (entry.target.dataset.src) { entry.target.src = entry.target.dataset.src; entry.target.classList.remove('skeleton'); entry.target.onload = () => { entry.target.classList.add('loaded'); }; } // Handle lazy loaded elements if (entry.target.classList.contains('lazy-load')) { entry.target.classList.add('loaded'); } // Handle counter animations if (entry.target.hasAttribute('data-count')) { animateCounter(entry.target); observer.unobserve(entry.target); } } }); }; const observer = new IntersectionObserver(observerCallback, observerOptions); // Observe all elements with data-count counters.forEach(counter => observer.observe(counter)); // Observe all lazy-load elements document.querySelectorAll('.lazy-load').forEach(el => observer.observe(el)); // Observe all images with data-src document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img)); }); // Lazy Loading Implementation function initLazyLoading() { // Add skeleton loading to images while they load document.querySelectorAll('img[data-src]').forEach(img => { img.classList.add('skeleton'); // Set a low-quality placeholder img.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="40" height="40"%3E%3Crect width="40" height="40" fill="%23f0f0f0"/%3E%3C/svg%3E'; }); // Preload critical images const criticalImages = document.querySelectorAll('img[data-critical="true"]'); criticalImages.forEach(img => { if (img.dataset.src) { img.src = img.dataset.src; img.classList.remove('skeleton'); } }); // Smooth scroll for anchor links with improved performance const scrollLinks = document.querySelectorAll('a[href^="#"]'); scrollLinks.forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const targetId = this.getAttribute('href'); const target = document.querySelector(targetId); if (target) { const headerOffset = 80; const elementPosition = target.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }); }); // Add active state to navigation based on scroll position const sections = document.querySelectorAll('section[id]'); const navLinks = document.querySelectorAll('nav a[href^="#"]'); const highlightNav = () => { const scrollY = window.pageYOffset; sections.forEach(section => { const sectionHeight = section.offsetHeight; const sectionTop = section.offsetTop - 100; const sectionId = section.getAttribute('id'); if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { navLinks.forEach(link => { link.classList.remove('text-indigo-600', 'font-semibold'); if (link.getAttribute('href') === `#${sectionId}`) { link.classList.add('text-indigo-600', 'font-semibold'); } }); } }); }; window.addEventListener('scroll', highlightNav); highlightNav(); // Call once on load }); // Enhanced utility functions const utils = { debounce: (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, throttle: (func, limit) => { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }, // Performance monitoring measurePerformance: (name, fn) => { const start = performance.now(); const result = fn(); const end = performance.now(); console.log(`${name} took ${end - start} milliseconds`); return result; }, // Loading state management setLoading: (element, loading = true) => { if (loading) { element.disabled = true; element.dataset.originalText = element.textContent; element.innerHTML = 'Loading...'; } else { element.disabled = false; element.textContent = element.dataset.originalText || 'Submit'; } } }; // Enhanced local storage helper with compression const storage = { set: (key, value) => { try { const serialized = JSON.stringify(value); localStorage.setItem(key, serialized); return true; } catch (e) { console.error('Error saving to localStorage', e); return false; } }, get: (key) => { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : null; } catch (e) { console.error('Error reading from localStorage', e); return null; } }, remove: (key) => { try { localStorage.removeItem(key); return true; } catch (e) { console.error('Error removing from localStorage', e); return false; } }, // Clear all storage clear: () => { try { localStorage.clear(); return true; } catch (e) { console.error('Error clearing localStorage', e); return false; } }, // Get storage usage getUsage: () => { let total = 0; for (let key in localStorage) { if (localStorage.hasOwnProperty(key)) { total += localStorage[key].length + key.length; } } return (total / 1024).toFixed(2) + ' KB'; } }; // Service Worker registration for PWA capabilities if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('SW registered: ', registration); }) .catch(registrationError => { console.log('SW registration failed: ', registrationError); }); }); }