/* ============================================================ АННА СОКОЛОВА — Premium Wellness Landing · main.js ============================================================ */ (() => { 'use strict'; // ---------- 1. Sticky nav: тень при скролле ---------- const nav = document.getElementById('nav'); const onScroll = () => { if (window.scrollY > 12) { nav.classList.add('scrolled'); } else { nav.classList.remove('scrolled'); } }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); // ---------- 2. Hamburger menu toggle ---------- const navToggle = document.getElementById('nav-toggle'); const navMenu = document.getElementById('nav-menu'); navToggle.addEventListener('click', () => { const isOpen = navMenu.classList.toggle('active'); navToggle.classList.toggle('active', isOpen); navToggle.setAttribute('aria-expanded', String(isOpen)); navToggle.setAttribute('aria-label', isOpen ? 'Закрыть меню' : 'Открыть меню'); document.body.style.overflow = isOpen ? 'hidden' : ''; }); // Закрывать меню при клике по ссылке navMenu.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { navMenu.classList.remove('active'); navToggle.classList.remove('active'); navToggle.setAttribute('aria-expanded', 'false'); document.body.style.overflow = ''; }); }); // ---------- 3. Scroll-triggered fade-in (IntersectionObserver) ---------- const reveals = document.querySelectorAll('.reveal'); if ('IntersectionObserver' in window) { const revealObserver = new IntersectionObserver((entries) => { entries.forEach((entry, i) => { if (entry.isIntersecting) { // мягкий стаггер — соседи появляются с лёгкой задержкой setTimeout(() => entry.target.classList.add('visible'), i * 60); revealObserver.unobserve(entry.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' }); reveals.forEach(el => revealObserver.observe(el)); } else { // Fallback — сразу показываем всё reveals.forEach(el => el.classList.add('visible')); } // ---------- 4. Animated counters (статистика в hero) ---------- const counters = document.querySelectorAll('[data-count]'); const animateCounter = (el) => { const target = parseInt(el.dataset.count, 10); const duration = 1600; const start = performance.now(); const tick = (now) => { const progress = Math.min((now - start) / duration, 1); // ease-out кривая const eased = 1 - Math.pow(1 - progress, 3); el.textContent = Math.floor(target * eased); if (progress < 1) requestAnimationFrame(tick); else el.textContent = target; }; requestAnimationFrame(tick); }; if ('IntersectionObserver' in window) { const counterObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { animateCounter(entry.target); counterObserver.unobserve(entry.target); } }); }, { threshold: 0.5 }); counters.forEach(el => counterObserver.observe(el)); } else { counters.forEach(animateCounter); } // ---------- 5. Popup лид-магнита: триггеры scroll 40% + exit-intent ---------- const popupOverlay = document.getElementById('popup-overlay'); const popupClose = document.getElementById('popup-close'); const STORAGE_KEY = 'lead_popup_shown_v1'; const wasShown = () => { try { return sessionStorage.getItem(STORAGE_KEY) === '1'; } catch (_) { return false; } }; const markShown = () => { try { sessionStorage.setItem(STORAGE_KEY, '1'); } catch (_) {} }; const openPopup = () => { if (wasShown()) return; popupOverlay.hidden = false; // двойной requestAnimationFrame для надёжного перехода opacity requestAnimationFrame(() => { requestAnimationFrame(() => popupOverlay.classList.add('active')); }); document.body.style.overflow = 'hidden'; markShown(); }; const closePopup = () => { popupOverlay.classList.remove('active'); document.body.style.overflow = ''; setTimeout(() => { popupOverlay.hidden = true; }, 350); }; // Триггер 1: scroll 40% страницы let scrollTriggerFired = false; const checkScrollTrigger = () => { if (scrollTriggerFired || wasShown()) return; const scrolled = window.scrollY + window.innerHeight; const total = document.documentElement.scrollHeight; if (scrolled / total >= 0.4) { scrollTriggerFired = true; openPopup(); } }; window.addEventListener('scroll', checkScrollTrigger, { passive: true }); // Триггер 2: exit-intent (мышь к верху страницы) — только desktop if (window.matchMedia('(min-width: 768px)').matches) { document.addEventListener('mouseleave', (e) => { if (e.clientY <= 0 && !wasShown()) { openPopup(); } }); } // Закрытие popupClose.addEventListener('click', closePopup); popupOverlay.addEventListener('click', (e) => { if (e.target === popupOverlay) closePopup(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !popupOverlay.hidden) closePopup(); }); // ---------- 6. Smooth scroll для anchor-ссылок ---------- // (CSS scroll-behavior: smooth уже есть, но компенсируем sticky-nav) const navHeight = () => nav.getBoundingClientRect().height; document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const href = anchor.getAttribute('href'); if (href === '#' || href.length < 2) return; const target = document.querySelector(href); if (!target) return; e.preventDefault(); const top = target.getBoundingClientRect().top + window.scrollY - navHeight() - 12; window.scrollTo({ top, behavior: 'smooth' }); }); }); // ---------- 7. Валидация форм лид-магнита ---------- const handleFormSubmit = (form) => { form.addEventListener('submit', (e) => { e.preventDefault(); const emailInput = form.querySelector('input[type="email"]'); const email = emailInput.value.trim(); // простая HTML5-валидация + ручная проверка regex const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); if (!valid) { emailInput.focus(); emailInput.style.borderColor = '#d97757'; setTimeout(() => { emailInput.style.borderColor = ''; }, 2000); return; } // Имитация отправки (на проде — fetch к бекенду) const successMsg = form.querySelector('.form-success'); const submitBtn = form.querySelector('button[type="submit"]'); submitBtn.disabled = true; submitBtn.textContent = 'Отправляем...'; setTimeout(() => { if (successMsg) { successMsg.hidden = false; } form.reset(); submitBtn.disabled = false; submitBtn.textContent = form.id === 'lead-form' ? 'Получить гайд' : 'Получить гайд бесплатно'; // если popup — закрыть через 1.5 сек if (form.id === 'popup-form') { setTimeout(closePopup, 1500); } }, 800); }); }; document.querySelectorAll('.lead-form').forEach(handleFormSubmit); // ---------- 8. Scroll-to-top ---------- const scrollTopBtn = document.getElementById('scroll-top'); const toggleScrollTop = () => { if (window.scrollY > 600) { scrollTopBtn.hidden = false; requestAnimationFrame(() => scrollTopBtn.classList.add('visible')); } else { scrollTopBtn.classList.remove('visible'); setTimeout(() => { if (window.scrollY <= 600) scrollTopBtn.hidden = true; }, 300); } }; window.addEventListener('scroll', toggleScrollTop, { passive: true }); scrollTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); })();