hotel: add the hotel v7 mockup as the design reference
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d13feb7df8
commit
7ce8bee320
@@ -0,0 +1,237 @@
|
||||
|
||||
(() => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user