Spaces:
Running
Running
| // 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 = '<span class="spinner inline-block w-4 h-4 mr-2"></span>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); | |
| }); | |
| }); | |
| } | |