1// ===== Header Scroll Effect ===== 2const header = document.getElementById('header'); 3let lastScroll = 0; 4 5window.addEventListener('scroll', () => { 6 const currentScroll = window.pageYOffset; 7 if (currentScroll > 50) { 8 header.classList.add('scrolled'); 9 } else { 10 header.classList.remove('scrolled'); 11 } 12 lastScroll = currentScroll; 13}); 14 15// ===== Mobile Nav Toggle ===== 16const navToggle = document.getElementById('navToggle'); 17const navLinks = document.querySelector('.nav-links'); 18 19navToggle.addEventListener('click', () => { 20 navLinks.classList.toggle('open'); 21 // Animate hamburger 22 const spans = navToggle.querySelectorAll('span'); 23 if (navLinks.classList.contains('open')) { 24 spans[0].style.transform = 'rotate(45deg) translate(5px, 5px)'; 25 spans[1].style.opacity = '0'; 26 spans[2].style.transform = 'rotate(-45deg) translate(5px, -5px)'; 27 } else { 28 spans[0].style.transform = ''; 29 spans[1].style.opacity = ''; 30 spans[2].style.transform = ''; 31 } 32}); 33 34// Close mobile nav on link click 35navLinks.querySelectorAll('a').forEach(link => { 36 link.addEventListener('click', () => { 37 navLinks.classList.remove('open'); 38 const spans = navToggle.querySelectorAll('span'); 39 spans[0].style.transform = ''; 40 spans[1].style.opacity = ''; 41 spans[2].style.transform = ''; 42 }); 43}); 44 45// ===== Hero Particles ===== 46function createParticles() { 47 const container = document.getElementById('particles'); 48 if (!container) return; 49 50 const particleCount = 20; 51 52 for (let i = 0; i < particleCount; i++) { 53 const particle = document.createElement('div'); 54 particle.classList.add('hero-particle'); 55 56 const size = Math.random() * 6 + 2; 57 const left = Math.random() * 100; 58 const delay = Math.random() * 15; 59 const duration = Math.random() * 10 + 15; 60 61 particle.style.width = size + 'px'; 62 particle.style.height = size + 'px'; 63 particle.style.left = left + '%'; 64 particle.style.animationDelay = delay + 's'; 65 particle.style.animationDuration = duration + 's'; 66 67 container.appendChild(particle); 68 } 69} 70 71createParticles(); 72 73// ===== Counter Animation ===== 74function animateCounters() { 75 const counters = document.querySelectorAll('.hero-stat-number'); 76 77 counters.forEach(counter => { 78 const target = parseInt(counter.getAttribute('data-target')); 79 if (!target) return; 80 81 const duration = 2000; 82 const startTime = performance.now(); 83 84 function update(currentTime) { 85 const elapsed = currentTime - startTime; 86 const progress = Math.min(elapsed / duration, 1); 87 88 // Ease out cubic 89 const eased = 1 - Math.pow(1 - progress, 3); 90 const current = Math.round(eased * target); 91 92 if (target >= 1000) { 93 counter.textContent = current.toLocaleString('ru-RU'); 94 } else { 95 counter.textContent = current + '+'; 96 } 97 98 if (progress < 1) { 99 requestAnimationFrame(update); 100 } else { 101 if (target >= 1000) { 102 counter.textContent = target.toLocaleString('ru-RU') + '+'; 103 } else { 104 counter.textContent = target + '+'; 105 } 106 } 107 } 108 109 requestAnimationFrame(update); 110 }); 111} 112 113// Start counters when hero is visible 114const heroObserver = new IntersectionObserver((entries) => { 115 entries.forEach(entry => { 116 if (entry.isIntersecting) { 117 animateCounters(); 118 heroObserver.disconnect(); 119 } 120 }); 121}, { threshold: 0.3 }); 122 123const heroStats = document.querySelector('.hero-stats'); 124if (heroStats) { 125 heroObserver.observe(heroStats); 126} 127 128// ===== Phone Input Formatting ===== 129const phoneInput = document.getElementById('phoneInput'); 130const searchBtn = document.getElementById('searchBtn'); 131 132phoneInput.addEventListener('input', (e) => { 133 let value = e.target.value.replace(/\D/g, ''); 134 135 if (value.startsWith('7') || value.startsWith('8')) { 136 // Format: 8 (XXX) XXX-XX-XX 137 let formatted = '8'; 138 if (value.length > 1) formatted += ' (' + value.substring(1, 4); 139 if (value.length >= 4) formatted += ') '; 140 if (value.length > 4) formatted += value.substring(4, 7); 141 if (value.length > 7) formatted += '-' + value.substring(7, 9); 142 if (value.length > 9) formatted += '-' + value.substring(9, 11); 143 e.target.value = formatted; 144 } 145}); 146 147phoneInput.addEventListener('keydown', (e) => { 148 if (e.key === 'Enter') { 149 handleSearch(); 150 } 151}); 152 153searchBtn.addEventListener('click', handleSearch); 154 155function handleSearch() { 156 const raw = phoneInput.value.replace(/\D/g, ''); 157 158 if (raw.length < 3) return; 159 160 // ÐÑопÑÑкаем пеÑвÑе 7 или 8, еÑли ÑÑо ÑкоÑее вÑего код ÑÑÑÐ°Ð½Ñ 161 let num = raw; 162 if (raw.startsWith('7') || raw.startsWith('8')) { 163 // ÐÑли длина 11 (полнÑй), 4 (8 + код), 7 (8 + код + ÑаÑÑÑ) 164 if (raw.length === 11 || raw.length === 4 || raw.length === 7) { 165 num = raw.substring(1); 166 } 167 } 168 169 if (num.length >= 6) { 170 // ÐеÑÐµÑ Ð¾Ð´ на ÑÑÑаниÑÑ 3+3 ÑиÑÑÑ (напÑимеÑ, /916/123/) даже Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð³Ð¾ номеÑа 171 window.location.href = '/' + num.substring(0, 3) + '/' + num.substring(3, 6) + '/'; 172 } else if (num.length >= 3) { 173 // ÐеÑÐµÑ Ð¾Ð´ на ÑÑÑаниÑÑ ÐºÐ¾Ð´Ð° (напÑимеÑ, /916/) 174 window.location.href = '/' + num.substring(0, 3) + '/'; 175 } 176} 177 178// ===== Result Card ===== 179const resultCard = document.getElementById('resultCard'); 180const resultClose = document.getElementById('resultClose'); 181 182if (resultClose) { 183 resultClose.addEventListener('click', () => { 184 resultCard.style.display = 'none'; 185 });
186} 187 188// ===== FAQ Accordion ===== 189const faqItems = document.querySelectorAll('.faq-item'); 190 191faqItems.forEach(item => { 192 const question = item.querySelector('.faq-question'); 193 194 question.addEventListener('click', () => { 195 const isActive = item.classList.contains('active'); 196 197 // Close all 198 faqItems.forEach(i => i.classList.remove('active')); 199 200 // Toggle current 201 if (!isActive) { 202 item.classList.add('active'); 203 } 204 }); 205}); 206 207// ===== Scroll Reveal Animation ===== 208function setupScrollReveal() { 209 const elements = document.querySelectorAll('.feature-card, .scenario-card, .step, .code-card'); 210 211 const observer = new IntersectionObserver((entries) => { 212 entries.forEach((entry, index) => { 213 if (entry.isIntersecting) { 214 // Stagger the animation (faster and caps out for large lists) 215 const domIndex = Array.from(entry.target.parentElement.children).indexOf(entry.target); 216 const delay = (domIndex % 20) * 10; 217 setTimeout(() => { 218 entry.target.classList.add('visible'); 219 }, delay); 220 observer.unobserve(entry.target); 221 } 222 }); 223 }, { 224 threshold: 0.1, 225 rootMargin: '0px 0px -50px 0px' 226 }); 227 228 elements.forEach(el => observer.observe(el)); 229} 230 231setupScrollReveal(); 232 233// ===== Smooth Scroll for anchor links ===== 234document.querySelectorAll('a[href^="#"]').forEach(anchor => { 235 anchor.addEventListener('click', function(e) { 236 e.preventDefault(); 237 const target = document.querySelector(this.getAttribute('href')); 238 if (target) { 239 const headerHeight = document.querySelector('.header').offsetHeight; 240 const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - headerHeight; 241 242 window.scrollTo({ 243 top: targetPosition, 244 behavior: 'smooth' 245 }); 246 } 247 }); 248}); 249 250// ===== Typing Effect for Search Placeholder ===== 251function typeEffect() { 252 const examples = [ 253 '8 (495) 123-45-67', 254 '8 (916) 234-56-78', 255 '8 (812) 345-67-89', 256 '8 (903) 456-78-90', 257 '8 (926) 567-89-01' 258 ]; 259 260 let exampleIndex = 0; 261 let charIndex = 0; 262 let isDeleting = false; 263 let isPaused = false; 264 265 const input = document.getElementById('phoneInput'); 266 if (!input || input === document.activeElement) return; 267 268 function tick() { 269 if (input === document.activeElement || input.value !== '' && !isDeleting) { 270 return; 271 } 272 273 const current = examples[exampleIndex]; 274 275 if (isPaused) { 276 isPaused = false; 277 isDeleting = true; 278 setTimeout(tick, 50); 279 return; 280 } 281 282 if (!isDeleting) { 283 input.placeholder = current.substring(0, charIndex + 1); 284 charIndex++; 285 286 if (charIndex === current.length) { 287 isPaused = true; 288 setTimeout(tick, 2000); 289 return; 290 } 291 } else { 292 input.placeholder = current.substring(0, charIndex - 1); 293 charIndex--; 294 295 if (charIndex === 0) { 296 isDeleting = false; 297 exampleIndex = (exampleIndex + 1) % examples.length; 298 } 299 } 300 301 setTimeout(tick, isDeleting ? 30 : 80); 302 } 303 304 setTimeout(tick, 2000); 305} 306 307typeEffect();
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.