legacy/v2 is legacy/new plus the «Перезвоним вам» callback dialog and the new phone number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
238 lines
11 KiB
JavaScript
238 lines
11 KiB
JavaScript
const FORM_ENDPOINT = '/api/leads/medical-centers-no-physio';
|
|
|
|
const header = document.getElementById('header');
|
|
const updateHeader = () => header.classList.toggle('scrolled', window.scrollY > 18);
|
|
updateHeader();
|
|
window.addEventListener('scroll', updateHeader, {passive:true});
|
|
|
|
const revealObserver = 'IntersectionObserver' in window ? new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) { entry.target.classList.add('is-visible'); observer.unobserve(entry.target); }
|
|
});
|
|
}, {threshold:.12, rootMargin:'0px 0px -42px'}) : null;
|
|
document.querySelectorAll('[data-reveal]').forEach((el, i) => {
|
|
el.style.transitionDelay = `${Math.min((i % 5) * 55, 220)}ms`;
|
|
if (revealObserver) revealObserver.observe(el); else el.classList.add('is-visible');
|
|
});
|
|
|
|
const counterObserver = 'IntersectionObserver' in window ? new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (!entry.isIntersecting) return;
|
|
const el = entry.target;
|
|
const target = parseFloat(el.dataset.counter || '0');
|
|
const suffix = el.dataset.suffix || '';
|
|
const prefix = el.dataset.prefix || '';
|
|
const decimals = String(target).includes('.') ? 1 : 0;
|
|
const start = performance.now();
|
|
const duration = 950;
|
|
const tick = now => {
|
|
const p = Math.min(1, (now - start) / duration);
|
|
const eased = 1 - Math.pow(1 - p, 3);
|
|
const v = target * eased;
|
|
el.textContent = prefix + (target === 2.41 ? v.toFixed(2) : v.toFixed(decimals)).replace('.', ',') + suffix;
|
|
if (p < 1) requestAnimationFrame(tick);
|
|
};
|
|
requestAnimationFrame(tick);
|
|
observer.unobserve(el);
|
|
});
|
|
}, {threshold:.6}) : null;
|
|
document.querySelectorAll('[data-counter]').forEach(el => counterObserver ? counterObserver.observe(el) : null);
|
|
|
|
// Save UTM parameters into hidden fields.
|
|
const params = new URLSearchParams(location.search);
|
|
['utm_source','utm_medium','utm_campaign','utm_content','utm_term'].forEach(key => {
|
|
const input = document.querySelector(`[name="${key}"]`);
|
|
let stored = '';
|
|
try { stored = sessionStorage.getItem(key) || ''; } catch (e) { stored = ''; }
|
|
if (input) input.value = params.get(key) || stored;
|
|
if (params.get(key)) { try { sessionStorage.setItem(key, params.get(key)); } catch (e) {} }
|
|
});
|
|
|
|
const phone = document.getElementById('phone');
|
|
phone.addEventListener('input', () => {
|
|
let d = phone.value.replace(/\D/g,'').slice(0,11);
|
|
if (!d) 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);
|
|
phone.value = value;
|
|
});
|
|
|
|
function setStatus(message, type) {
|
|
const status = document.getElementById('formStatus');
|
|
status.textContent = message;
|
|
status.className = `form-status show ${type}`;
|
|
}
|
|
|
|
async function submitLead(event) {
|
|
event.preventDefault();
|
|
const form = event.currentTarget;
|
|
const button = document.getElementById('submitBtn');
|
|
if (form.website.value) return false;
|
|
if (!form.checkValidity()) { form.reportValidity(); setStatus('Проверьте обязательные поля формы.', 'error'); return false; }
|
|
const phoneDigits = form.phone.value.replace(/\D/g,'');
|
|
if (phoneDigits.length < 11) { setStatus('Укажите полный номер телефона.', 'error'); form.phone.focus(); return false; }
|
|
|
|
const payload = Object.fromEntries(new FormData(form).entries());
|
|
payload.created_at = new Date().toISOString();
|
|
payload.page_url = location.href;
|
|
payload.page_title = document.title;
|
|
|
|
button.disabled = true;
|
|
button.textContent = 'Отправляем…';
|
|
try {
|
|
if (location.protocol === 'file:') throw new Error('preview');
|
|
const response = await fetch(FORM_ENDPOINT, {
|
|
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)
|
|
});
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
setStatus('Спасибо. Заявка отправлена — специалист свяжется с вами для предварительного аудита.', 'success');
|
|
form.reset();
|
|
} catch (error) {
|
|
console.warn('Lead endpoint is not connected in preview:', error);
|
|
if (location.protocol === 'file:' || location.hostname === 'localhost') {
|
|
setStatus('Демонстрационный режим: форма заполнена корректно. Для публикации подключите FORM_ENDPOINT к CRM или webhook.', 'success');
|
|
} else {
|
|
setStatus('Не удалось отправить заявку. Попробуйте ещё раз или свяжитесь с компанией по телефону.', 'error');
|
|
}
|
|
} finally {
|
|
button.disabled = false;
|
|
button.innerHTML = `Получить предварительный аудит <svg viewBox='0 0 24 24' aria-hidden='true'><path d='M5 12h14M13 6l6 6-6 6' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>`;
|
|
}
|
|
return false;
|
|
}
|
|
window.submitLead = submitLead;
|
|
|
|
/* ---- next bundled script ---- */
|
|
|
|
(function(){
|
|
const root=document.querySelector('[data-course-calculator]'); if(!root)return;
|
|
const countInput=root.querySelector('#courseCalcCount'), checkInput=root.querySelector('#courseCalcCheck'), investmentInput=root.querySelector('#courseCalcInvestment');
|
|
const monthOutput=root.querySelector('[data-course-month]'), yearOutput=root.querySelector('[data-course-year]'), paybackOutput=root.querySelector('[data-course-payback]'), roiOutput=root.querySelector('[data-course-roi]');
|
|
const money=new Intl.NumberFormat('ru-RU',{maximumFractionDigits:0}); const decimal=new Intl.NumberFormat('ru-RU',{minimumFractionDigits:1,maximumFractionDigits:1});
|
|
function safeNumber(input){const value=Number(String(input.value||'').replace(/\s/g,'').replace(',','.'));return Number.isFinite(value)&&value>0?value:0}
|
|
function updateCourseRevenue(){
|
|
const courses=safeNumber(countInput), averageCheck=safeNumber(checkInput), investment=safeNumber(investmentInput); const month=courses&&averageCheck?Math.round(courses*averageCheck):0; const year=month*12;
|
|
monthOutput.textContent=month?money.format(month)+' ₽':'—'; yearOutput.textContent=year?money.format(year)+' ₽':'—';
|
|
if(month&&investment){const payback=investment/month; paybackOutput.textContent=decimal.format(payback)+' мес.'; const roi=((year-investment)/investment)*100; roiOutput.textContent=Number.isFinite(roi)?money.format(Math.round(roi))+'%':'—'} else {paybackOutput.textContent='—';roiOutput.textContent='—'}
|
|
}
|
|
[countInput,checkInput,investmentInput].forEach(el=>el&&el.addEventListener('input',updateCourseRevenue)); updateCourseRevenue();
|
|
})();
|
|
|
|
|
|
/* === 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;
|
|
}
|
|
});
|
|
})();
|
|
|