(() => { const header = document.getElementById('siteHeader'); const revealItems = document.querySelectorAll('.reveal'); const year = document.getElementById('year'); year.textContent = new Date().getFullYear(); const onScroll = () => header.classList.toggle('scrolled', window.scrollY > 18); onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); const observer = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('is-visible'); obs.unobserve(entry.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -6% 0px' }); revealItems.forEach(item => observer.observe(item)); document.querySelectorAll('a[href^="#"]').forEach(link => { link.addEventListener('click', event => { const id = link.getAttribute('href'); const target = id && document.querySelector(id); if (!target) return; event.preventDefault(); const offset = header.offsetHeight + 8; const top = target.getBoundingClientRect().top + window.scrollY - offset; window.scrollTo({ top, behavior: 'smooth' }); }); }); })(); function collectAttribution() { const params = new URLSearchParams(location.search); const keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'yclid', 'gclid']; return keys.reduce((acc, key) => { if (params.get(key)) acc[key] = params.get(key); return acc; }, {}); } async function submitLead(event) { event.preventDefault(); const form = event.currentTarget; const status = document.getElementById('formStatus'); const button = document.getElementById('submitButton'); const originalLabel = button.querySelector('span').textContent; status.className = 'form-status'; status.textContent = ''; if (!form.checkValidity()) { form.reportValidity(); return false; } if (form.website.value) return false; const payload = Object.fromEntries(new FormData(form).entries()); delete payload.website; payload.page = '/hotels'; payload.source = 'exo-hotel-landing'; payload.attribution = collectAttribution(); payload.submitted_at = new Date().toISOString(); button.disabled = true; button.setAttribute('aria-busy', 'true'); button.querySelector('span').textContent = 'Отправляем…'; try { const endpoint = form.dataset.endpoint || '/api/leads/hotels'; const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload), credentials: 'same-origin' }); if (!response.ok) throw new Error('HTTP ' + response.status); form.reset(); status.className = 'form-status success show'; status.textContent = 'Спасибо. Заявка отправлена — специалист подготовит предложение и свяжется с вами.'; } catch (error) { console.error('Lead form error:', error); status.className = 'form-status error show'; status.textContent = 'Не удалось отправить форму. Проверьте подключение обработчика /api/leads/hotels и попробуйте ещё раз.'; } finally { button.disabled = false; button.removeAttribute('aria-busy'); button.querySelector('span').textContent = originalLabel; } return false; } /* ---- merged script block ---- */ (function(){ const money = new Intl.NumberFormat('ru-RU',{maximumFractionDigits:0}); const val = (root, selector) => { const el=root.querySelector(selector); if(!el) return 0; return Number(String(el.value||'').replace(/\s/g,'').replace(',','.'))||0; }; document.querySelectorAll('[data-econ-calc]').forEach(root=>{ const type=root.getAttribute('data-econ-calc'); const update=()=>{ let month=0, volume=0, ready=false; if(type==='hotel'){ const programs=val(root,'#hotelPrograms'), check=val(root,'#hotelCheck'), days=val(root,'#hotelDays'); ready=programs>0&&check>0&&days>0; volume=programs*days; month=volume*check; }else{ const members=val(root,'#fitMembers'), share=val(root,'#fitShare'), spend=val(root,'#fitSpend'); ready=members>0&&share>0&&spend>0; volume=Math.round(members*Math.min(share,100)/100); month=volume*spend; } const monthEl=root.querySelector('[data-econ-month]'), yearEl=root.querySelector('[data-econ-year]'), volumeEl=root.querySelector('[data-econ-volume]'); if(!ready){monthEl.textContent='—';yearEl.textContent='—';return;} monthEl.textContent=money.format(month)+' ₽';yearEl.textContent=money.format(month*12)+' ₽'; volumeEl.textContent=type==='hotel'?money.format(volume)+' сеансов в месяц':money.format(volume)+' покупателей recovery'; }; root.querySelectorAll('input').forEach(input=>input.addEventListener('input',update)); update(); }); })(); /* === EXO callback modal === */ (function(){ const modal=document.getElementById('exoCallbackModal'); const form=document.getElementById('exoCallbackForm'); if(!modal||!form) return; const triggers=[...document.querySelectorAll('.btn--call,.header-call')]; const closeEls=modal.querySelectorAll('[data-callback-close]'); const nameInput=document.getElementById('exoCallbackName'); const phoneInput=document.getElementById('exoCallbackPhone'); const submit=document.getElementById('exoCallbackSubmit'); const status=document.getElementById('exoCallbackStatus'); let lastFocused=null; function setStatus(message,type){ status.textContent=message; status.className='exo-callback__status is-show '+(type==='success'?'is-success':'is-error'); } function clearStatus(){ status.textContent=''; status.className='exo-callback__status'; } function openModal(event){ if(event) event.preventDefault(); lastFocused=document.activeElement; clearStatus(); modal.classList.add('is-open'); modal.setAttribute('aria-hidden','false'); document.body.classList.add('exo-callback-open'); setTimeout(()=>nameInput && nameInput.focus(),60); } function closeModal(){ modal.classList.remove('is-open'); modal.setAttribute('aria-hidden','true'); document.body.classList.remove('exo-callback-open'); if(lastFocused && typeof lastFocused.focus==='function') lastFocused.focus(); } triggers.forEach(el=>el.addEventListener('click',openModal)); closeEls.forEach(el=>el.addEventListener('click',closeModal)); document.addEventListener('keydown',e=>{ if(e.key==='Escape' && modal.classList.contains('is-open')) closeModal(); }); function formatPhone(){ let d=phoneInput.value.replace(/\D/g,'').slice(0,11); if(!d){phoneInput.value='';return} if(d[0]==='8') d='7'+d.slice(1); if(d[0]!=='7') d='7'+d; let value='+7'; if(d.length>1) value+=' '+d.slice(1,4); if(d.length>=5) value+=' '+d.slice(4,7); if(d.length>=8) value+='-'+d.slice(7,9); if(d.length>=10) value+='-'+d.slice(9,11); phoneInput.value=value; } phoneInput.addEventListener('input',formatPhone); form.addEventListener('submit',async function(event){ event.preventDefault(); clearStatus(); if(!form.checkValidity()){ form.reportValidity(); return; } const phoneDigits=phoneInput.value.replace(/\D/g,''); if(phoneDigits.length<11){ setStatus('Укажите полный номер телефона.','error'); phoneInput.focus(); return; } const payload=Object.fromEntries(new FormData(form).entries()); delete payload.consent; payload.lead_type='callback'; payload.source='header-call-popup'; payload.page_url=location.href; payload.page_title=document.title; payload.created_at=new Date().toISOString(); const original=submit.textContent; submit.disabled=true; submit.textContent='Отправляем…'; try{ if(location.protocol==='file:') throw new Error('preview'); const endpoint=form.dataset.endpoint; const response=await fetch(endpoint,{ method:'POST', headers:{'Content-Type':'application/json','Accept':'application/json'}, body:JSON.stringify(payload), credentials:'same-origin' }); if(!response.ok) throw new Error('HTTP '+response.status); setStatus('Спасибо. Заявка отправлена — специалист свяжется с вами.','success'); form.reset(); }catch(error){ console.warn('Callback form:',error); if(location.protocol==='file:' || location.hostname==='localhost'){ setStatus('Демонстрационный режим: форма работает. После загрузки на сервер заявка будет отправляться в подключённый обработчик.','success'); }else{ setStatus('Не удалось отправить заявку. Попробуйте ещё раз или позвоните нам по номеру +7 927 789-60-71.','error'); } }finally{ submit.disabled=false; submit.textContent=original; } }); })();