PageSourceSearch

https://otkudazvon.ru/static/landing_script.js

js otkudazvon.ru collected 2026-10-08 15:23:55 UTC 9,996 bytes, 307 lines download raw bytes

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.