Files
exodevices/hotel/legacy/hotel v7/script.js
T

238 lines
9.4 KiB
JavaScript

(() => {
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;
}
});
})();