1// Initialize all components when the DOM is loaded 2document.addEventListener('DOMContentLoaded', function() { 3 initializeWebsite(); 4}); 5 6function initializeWebsite() { 7 // Initialize all components 8 initScrollEffects(); 9 initMobileMenu(); 10 initParticles(); 11 initCursorEffect(); 12 initScrollProgress(); 13 initSmoothScrolling(); 14 initLocalizedPricing(); 15} 16 17// Scroll Effects 18function initScrollEffects() { 19 const header = document.querySelector('.main-header'); 20 const fadeElements = document.querySelectorAll('.fade-in'); 21 22 // Header scroll effect 23 window.addEventListener('scroll', () => { 24 if (window.scrollY > 100) { 25 header.classList.add('scrolled'); 26 } else { 27 header.classList.remove('scrolled'); 28 } 29 }); 30 31 // Intersection Observer for fade-in animations 32 if ('IntersectionObserver' in window) { 33 const observerOptions = { 34 threshold: 0.1, 35 rootMargin: '0px 0px -50px 0px' 36 }; 37 38 const observer = new IntersectionObserver((entries) => { 39 entries.forEach(entry => { 40 if (entry.isIntersecting) { 41 entry.target.classList.add('visible'); 42 } 43 }); 44 }, observerOptions); 45 46 fadeElements.forEach(el => observer.observe(el)); 47 } else { 48 // Fallback for browsers that don't support IntersectionObserver 49 fadeElements.forEach(el => el.classList.add('visible')); 50 } 51} 52 53// Mobile Menu 54function initMobileMenu() { 55 const toggle = document.querySelector('.mobile-menu-toggle'); 56 const menu = document.querySelector('.mobile-menu'); 57 58 if (toggle && menu) { 59 toggle.addEventListener('click', () => { 60 toggle.classList.toggle('active'); 61 menu.classList.toggle('active'); 62 }); 63 64 // Close menu when clicking links 65 menu.querySelectorAll('a').forEach(link => { 66 link.addEventListener('click', () => { 67 toggle.classList.remove('active'); 68 menu.classList.remove('active'); 69 }); 70 }); 71 } 72} 73 74// Floating Particles 75function initParticles() { 76 const particlesContainer = document.getElementById('particles'); 77 if (!particlesContainer) return; 78 79 const particleCount = 15; 80 81 for (let i = 0; i < particleCount; i++) { 82 createParticle(); 83 } 84 85 function createParticle() { 86 const particle = document.createElement('div'); 87 particle.className = 'particle'; 88 89 // Random size and position 90 const size = Math.random() * 6 + 2; 91 particle.style.width = size + 'px'; 92 particle.style.height = size + 'px'; 93 particle.style.left = Math.random() * 100 + '%'; 94 particle.style.animationDuration = (Math.random() * 10 + 15) + 's'; 95 particle.style.animationDelay = Math.random() * 5 + 's'; 96 97 particlesContainer.appendChild(particle); 98 99 // Remove and recreate particle after animation 100 setTimeout(() => { 101 particle.remove(); 102 createParticle(); 103 }, (15 + Math.random() * 10) * 1000); 104 } 105} 106 107// Cursor Glow Effect 108function initCursorEffect() { 109 const cursor = document.querySelector('.cursor-glow'); 110 if (!cursor) return; 111 112 document.addEventListener('mousemove', (e) => { 113 cursor.style.left = e.clientX + 'px'; 114 cursor.style.top = e.clientY + 'px'; 115 }); 116} 117 118// Scroll Progress Bar 119function initScrollProgress() { 120 const progressBar = document.querySelector('.scroll-progress'); 121 if (!progressBar) return; 122 123 window.addEventListener('scroll', () => { 124 const scrollTop = window.pageYOffset || document.documentElement.scrollTop; 125 const docHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; 126 const scrollPercent = (scrollTop / docHeight) * 100; 127 128 progressBar.style.width = scrollPercent + '%'; 129 }); 130} 131 132// Smooth Scrolling Function 133function initSmoothScrolling() { 134 document.querySelectorAll('a[href^="#"]').forEach(anchor => { 135 anchor.addEventListener('click', function (e) { 136 // Skip if it's an empty hash or just "#" 137 if (this.getAttribute('href') === '#' || this.getAttribute('href') === '') return; 138 139 e.preventDefault(); 140 const targetId = this.getAttribute('href'); 141 const target = document.querySelector(targetId); 142 143 if (target) { 144 const headerHeight = document.querySelector('.main-header').offsetHeight; 145 const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerHeight; 146 147 window.scrollTo({ 148 top: targetPosition, 149 behavior: 'smooth' 150 }); 151 152 // Update URL hash without jumping 153 history.pushState(null, null, targetId); 154 } 155 }); 156 }); 157} 158 159// Localized Pricing (free: locale + exchangerate.host) 160function initLocalizedPricing() { 161 const priceNodes = document.querySelectorAll('[data-inr]'); 162 if (!priceNodes.length) return; 163 164 const forced = getCurrencyOverride(); 165 const targetCurrency = forced || detectCurrencyFromLocale(); 166 if (targetCurrency === 'INR') return; 167 168 const rateUrl = `https://api.exchangerate.host/latest?base=INR&symbols=${encodeURIComponent(targetCurrency)}`; 169 170 fetch(rateUrl) 171 .then(response => response.ok ? response.json() : null) 172 .then(data => { 173 const rate = data?.rates?.[targetCurrency]; 174 if (!rate) return; 175
176 priceNodes.forEach(node => { 177 const baseInr = parseFloat(node.dataset.inr); 178 if (Number.isNaN(baseInr)) return; 179 180 const prefix = node.dataset.prefix || ''; 181 const suffix = node.dataset.suffix || ''; 182 const fractionDigits = getCurrencyFractionDigits(targetCurrency); 183 const formatted = new Intl.NumberFormat(undefined, { 184 style: 'currency', 185 currency: targetCurrency, 186 minimumFractionDigits: fractionDigits, 187 maximumFractionDigits: fractionDigits 188 }).format(baseInr * rate); 189 190 node.textContent = `${prefix}${formatted}${suffix}`; 191 }); 192 }) 193 .catch(() => { 194 // Silently fallback to INR on any error 195 }); 196} 197 198function getCurrencyOverride() { 199 try { 200 const params = new URLSearchParams(window.location.search); 201 const urlParam = params.get('currency') || params.get('forceCurrency'); 202 const stored = localStorage.getItem('currencyOverride'); 203 const candidate = (urlParam || stored || '').toUpperCase().trim(); 204 if (/^[A-Z]{3}$/.test(candidate)) { 205 if (urlParam) { 206 localStorage.setItem('currencyOverride', candidate); 207 } 208 return candidate; 209 } 210 } catch (e) { 211 // Ignore storage issues 212 } 213 return ''; 214} 215 216function detectCurrencyFromLocale() { 217 const locale = navigator.language || navigator.userLanguage || ''; 218 const region = (locale.split('-')[1] || locale.split('_')[1] || '').toUpperCase(); 219 const regionToCurrency = { 220 US: 'USD', CA: 'CAD', MX: 'MXN', BR: 'BRL', AR: 'ARS', 221 GB: 'GBP', IE: 'EUR', FR: 'EUR', DE: 'EUR', ES: 'EUR', IT: 'EUR', NL: 'EUR', BE: 'EUR', AT: 'EUR', PT: 'EUR', FI: 'EUR', GR: 'EUR', 222 AU: 'AUD', NZ: 'NZD', SG: 'SGD', HK: 'HKD', JP: 'JPY', KR: 'KRW', 223 AE: 'AED', SA: 'SAR', QA: 'QAR', KW: 'KWD', BH: 'BHD', OM: 'OMR', 224 ZA: 'ZAR', NG: 'NGN', KE: 'KES', EG: 'EGP', 225 CH: 'CHF', NO: 'NOK', SE: 'SEK', DK: 'DKK', 226 RU: 'RUB', TR: 'TRY', IL: 'ILS', ID: 'IDR', PH: 'PHP', MY: 'MYR', TH: 'THB' 227 }; 228 229 return regionToCurrency[region] || 'INR'; 230} 231 232function getCurrencyFractionDigits(code) { 233 try { 234 const nf = new Intl.NumberFormat(undefined, { style: 'currency', currency: code }); 235 const opts = nf.resolvedOptions(); 236 if (typeof opts.minimumFractionDigits === 'number') { 237 const min = opts.minimumFractionDigits; 238 const max = typeof opts.maximumFractionDigits === 'number' ? opts.maximumFractionDigits : min; 239 return Math.max(0, Math.min(Math.max(min, max), 4)); 240 } 241 } catch (e) { 242 // Fallback to defaults below 243 } 244 245 const zeroDecimalCurrencies = ['JPY', 'KRW']; 246 return zeroDecimalCurrencies.includes(code) ? 0 : 2; 247} 248 249// Performance Optimization 250function debounce(func, wait) { 251 let timeout; 252 return function executedFunction(...args) { 253 const later = () => { 254 clearTimeout(timeout); 255 func(...args); 256 }; 257 clearTimeout(timeout); 258 timeout = setTimeout(later, wait); 259 }; 260} 261 262// Optimize scroll events 263window.addEventListener('scroll', debounce(() => { 264 // Scroll-dependent functions here 265}, 10));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.