const FORM_ENDPOINT = '/api/leads/fitness-centers'; const devices = { therapy: {name:'ЭкзоТерапия', role:'флагманский full-body recovery и premium-продукт', benefits:['заметный якорь зоны','восстановительный этап после нагрузки','повышает технологичность сервиса']}, magnet: {name:'ЭкзоМагнит 3 в 1', role:'medical-core для нейромышечных и опорно-двигательных маршрутов', benefits:['магнит + УВТ + инфракрасный модуль','работа по медицинскому протоколу','ядро для спортивной медицины']}, tecar: {name:'ЭкзоТекар 3 в 1', role:'активная локальная работа с тканями, фасциями и подвижностью', benefits:['TECAR + УВТ + ультразвук','инструмент специалиста','сильная связь с PT и return-to-sport']}, press: {name:'ЭкзоПресс', role:'потоковый recovery для ног после бега, leg day и нагрузки', benefits:['компрессия + тепло / холод','понятный сервис для массового спроса','легко включается в пакеты']}, hydrogen: {name:'ЭкзоВодород', role:'автономная lounge-процедура для recovery и recharge', benefits:['спокойный формат в кресле','минимальное участие оператора','отдельная причина прийти в recovery-day']} }; const presets = { recovery: {ids:['therapy','press','hydrogen'], title:'Recovery Start', subtitle:'Компактный вход в recovery с флагманской, потоковой и автономной процедурой.'}, sport: {ids:['magnet','tecar','press'], title:'Sport Core', subtitle:'Активное ядро для клуба с сильным PT, беговым, силовым или спортивным направлением.'}, premium: {ids:['therapy','tecar','hydrogen'], title:'Premium Mix', subtitle:'Статусная конфигурация с индивидуальной процедурой, флагманским recovery и lounge-сценарием.'} }; let mode = 3; let selected = new Set(presets.recovery.ids); let currentPreset = 'recovery'; const threeImage = "assets/asset_09_cfa0f6440fb6.webp"; const fiveImage = "assets/asset_14_c3b984d644de.webp"; const cards = [...document.querySelectorAll('.device-card')]; const modeButtons = [...document.querySelectorAll('.mode-btn')]; const presetButtons = [...document.querySelectorAll('.preset-btn')]; function configString() { return [...selected].map(id => devices[id].name).join(', '); } function uniqueBenefits(ids) { const items = []; ids.forEach(id => devices[id].benefits.forEach(x => { if(!items.includes(x)) items.push(x); })); return items.slice(0,4); } function updateConstructor() { const ids = [...selected]; cards.forEach(card => { const active = selected.has(card.dataset.device); card.classList.toggle('selected',active); card.classList.toggle('locked',mode===5); card.setAttribute('aria-pressed',String(active)); }); modeButtons.forEach(btn => btn.classList.toggle('active',Number(btn.dataset.mode)===mode)); document.getElementById('selectedCount').textContent=ids.length; document.getElementById('modeCount').textContent=mode; document.getElementById('pickerHint').textContent = mode===5 ? 'Полная линейка выбрана' : (ids.length===3 ? 'Конфигурация собрана' : `Выберите ещё ${3-ids.length}`); document.getElementById('presetRow').style.display = mode===3 ? 'flex':'none'; presetButtons.forEach(btn=>btn.classList.toggle('active',btn.dataset.preset===currentPreset && mode===3)); const image = document.getElementById('configImage'); image.style.opacity='0'; setTimeout(()=>{ image.src=mode===5?fiveImage:threeImage; image.alt=mode===5?'Полная зона восстановления с пятью аппаратами':'Пример зоны восстановления с тремя аппаратами'; image.style.opacity='1'; },180); document.getElementById('configTag').textContent=mode===5?'Полная EXO-платформа':'Конструктор на 3 аппарата'; document.getElementById('configNumber').textContent=mode===5?'05':String(ids.length).padStart(2,'0'); const exactPreset = Object.entries(presets).find(([_,p])=>p.ids.length===ids.length && p.ids.every(id=>selected.has(id))); const incomplete = mode===3 && ids.length<3; const title = mode===5 ? 'Полная зона — 5 аппаратов' : (incomplete ? `Выбрано ${ids.length} из 3 аппаратов` : (exactPreset ? `${exactPreset[1].title} — 3 аппарата` : 'Персональный микс — 3 аппарата')); const subtitle = mode===5 ? 'Полный маршрут: active medical-core, premium recovery, потоковые и автономные процедуры.' : (incomplete ? 'Можно продолжить подбор или получить консультацию — специалист поможет собрать оптимальную конфигурацию.' : (exactPreset ? exactPreset[1].subtitle : 'Состав собран под выбранные вами приоритеты. Итоговая логика уточняется после аудита клуба.')); document.getElementById('configTitle').textContent=title; document.getElementById('configSubtitle').textContent=subtitle; document.getElementById('selectedList').innerHTML=ids.map(id=>`${devices[id].name}`).join(''); document.getElementById('configBenefits').innerHTML=uniqueBenefits(ids).map(x=>`
${x}
`).join(''); document.getElementById('configurationInput').value=ids.length ? `${ids.length} аппарата: ${configString()}` : 'Конфигурация не выбрана — нужна консультация'; const cta=document.getElementById('configCta'); cta.style.opacity='1';cta.style.pointerEvents='auto'; } cards.forEach(card=>card.addEventListener('click',()=>{ if(mode===5) return; const id=card.dataset.device; currentPreset=''; if(selected.has(id)) selected.delete(id); else { if(selected.size>=3) selected.delete([...selected][0]); selected.add(id); } updateConstructor(); })); modeButtons.forEach(btn=>btn.addEventListener('click',()=>{ mode=Number(btn.dataset.mode); if(mode===5){ selected=new Set(Object.keys(devices)); currentPreset=''; } else { selected=new Set(presets.recovery.ids); currentPreset='recovery'; } updateConstructor(); })); presetButtons.forEach(btn=>btn.addEventListener('click',()=>{ mode=3;currentPreset=btn.dataset.preset;selected=new Set(presets[currentPreset].ids);updateConstructor(); })); document.getElementById('resetConfig').addEventListener('click',()=>{mode=3;currentPreset='recovery';selected=new Set(presets.recovery.ids);updateConstructor();}); document.getElementById('configCta').addEventListener('click',()=>{const picked=configString();document.getElementById('comment').value=picked?`Интересует конфигурация: ${selected.size} аппарата — ${picked}`:'Нужна консультация по подбору конфигурации Recovery Zone';}); updateConstructor(); const header=document.getElementById('header'); const mobileCta=document.getElementById('mobileCta'); function onScroll(){header.classList.toggle('scrolled',scrollY>35);mobileCta.classList.toggle('show',scrollY>window.innerHeight*.75 && scrollYentries.forEach(entry=>{if(entry.isIntersecting){entry.target.classList.add('is-visible');observer.unobserve(entry.target)}}),{threshold:.12}); document.querySelectorAll('.reveal,.market-panel').forEach(el=>observer.observe(el)); const modal=document.getElementById('imageModal'), modalImg=document.getElementById('modalImage'); document.querySelectorAll('.gallery-card').forEach(card=>card.addEventListener('click',()=>{modalImg.src=card.dataset.full;modal.classList.add('open');document.body.classList.add('locked')})); function closeModal(){modal.classList.remove('open');document.body.classList.remove('locked')} modal.querySelector('.modal-close').addEventListener('click',closeModal);modal.addEventListener('click',e=>{if(e.target===modal)closeModal()});addEventListener('keydown',e=>{if(e.key==='Escape')closeModal()}); function collectUtm(){const p=new URLSearchParams(location.search),o={};['utm_source','utm_medium','utm_campaign','utm_content','utm_term'].forEach(k=>{if(p.get(k))o[k]=p.get(k)});return o} async function handleLead(event){ event.preventDefault(); const form=event.currentTarget,status=document.getElementById('formStatus'),btn=document.getElementById('submitBtn'); if(!form.checkValidity()){form.reportValidity();status.textContent='Проверьте обязательные поля и согласие.';return false} if(form.website.value) return false; const payload=Object.fromEntries(new FormData(form).entries());Object.assign(payload,collectUtm(),{page:'/fitness',created_at:new Date().toISOString()}); btn.disabled=true;btn.textContent='Отправляем…';status.textContent=''; try{ const res=await fetch(FORM_ENDPOINT,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); if(!res.ok)throw new Error('HTTP '+res.status); status.textContent='Спасибо. Заявка отправлена — специалист свяжется с вами.';status.style.color='#087F73';form.reset();updateConstructor(); window.dataLayer=window.dataLayer||[];window.dataLayer.push({event:'fitness_lead_sent',configuration:payload.configuration}); }catch(error){ localStorage.setItem('exo_fitness_lead_draft',JSON.stringify(payload)); status.textContent='Не удалось отправить форму автоматически. Данные сохранены в браузере — свяжитесь с нами по +7 939 717-80-80.';status.style.color='#A14D12'; console.warn('Lead endpoint error',error,payload); }finally{btn.disabled=false;btn.textContent='Получить консультацию →';} return false; } window.handleLead=handleLead; (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 programs=val(root,'#fitPrograms'), check=val(root,'#fitCheck'), days=val(root,'#fitDays'); ready=programs>0&&check>0&&days>0; volume=programs*days; month=volume*check; } const monthEl=root.querySelector('[data-econ-month]'), yearEl=root.querySelector('[data-econ-year]'), volumeEl=root.querySelector('[data-econ-volume]'); const paybackEl=root.querySelector('[data-econ-payback]'), roiEl=root.querySelector('[data-econ-roi]'); if(!ready){ if(monthEl) monthEl.textContent='—'; if(yearEl) yearEl.textContent='—'; if(volumeEl) volumeEl.textContent=type==='hotel'?'Введите программы и средний чек':'Введите программы в день и средний чек'; if(paybackEl) paybackEl.textContent='—'; if(roiEl) roiEl.textContent='—'; return; } if(monthEl) monthEl.textContent=money.format(month)+' ₽'; if(yearEl) yearEl.textContent=money.format(month*12)+' ₽'; if(volumeEl) volumeEl.textContent=type==='hotel'?money.format(volume)+' программ в месяц':money.format(val(root,'#fitPrograms'))+' × '+money.format(val(root,'#fitDays'))+' = '+money.format(volume)+' программ в месяц'; if(type==='fitness' && paybackEl && roiEl){ const investment=val(root,'#fitInvestment'); if(investment>0 && month>0){ const payback=investment/month; const roi=((month*12-investment)/investment)*100; paybackEl.textContent=(Math.round(payback*10)/10).toLocaleString('ru-RU')+' мес.'; roiEl.textContent=Math.round(roi).toLocaleString('ru-RU')+'%'; }else{ paybackEl.textContent='Укажите инвестиции'; roiEl.textContent='Укажите инвестиции'; } } }; root.querySelectorAll('input').forEach(input=>input.addEventListener('input',update)); update(); }); })();