Сайты и реклама для строительных компаний
`}}catch(e){}})();
async function collectClientGeo(){
try{
const controller=new AbortController();
const timer=setTimeout(()=>controller.abort(),1800);
const response=await fetch('https://ipwho.is/',{signal:controller.signal,headers:{'Accept':'application/json'}});
clearTimeout(timer);
if(!response.ok)return null;
const data=await response.json();
if(data?.success===false)return null;
return {
country:String(data?.country||'').slice(0,120),
region:String(data?.region||'').slice(0,160),
city:String(data?.city||'').slice(0,160),
timezone:String(data?.timezone?.id||'').slice(0,120)
};
}catch(_){return null}
}
function requireHumanSlider(){
if(!(s?.quiz?.antiFraudEnabled===true)) return Promise.resolve(true);
return new Promise(resolve=>{
const overlay=document.createElement('div');
overlay.className='antifraud-overlay';
overlay.innerHTML=`
× Подтвердите, что вы человек Потяните ползунок вправо до конца — после этого заявка отправится.
Потяните вправо →
Проверка пройдена ✓
`;
document.body.appendChild(overlay);
const card=overlay.querySelector('.antifraud-card'),track=overlay.querySelector('.antifraud-track'),handle=overlay.querySelector('.antifraud-handle'),close=overlay.querySelector('.antifraud-close');
let dragging=false,startX=0,startLeft=0,moves=0,startedAt=0,done=false;
const maxLeft=()=>Math.max(0,track.clientWidth-handle.offsetWidth-8);
const setPos=(left)=>{const m=maxLeft(),x=Math.max(0,Math.min(m,left));handle.style.left=(x+4)+'px';const pct=m?Math.round(x/m*100):0;track.style.setProperty('--af-progress',pct+'%');track.setAttribute('aria-valuenow',String(pct));return {x,pct,m}};
const finish=()=>{if(done)return;done=true;const m=maxLeft();setPos(m);card.classList.add('is-success');handle.textContent='✓';setTimeout(()=>{overlay.remove();resolve(true)},380)};
const cancel=()=>{if(done)return;done=true;overlay.remove();resolve(false)};
const begin=(clientX)=>{dragging=true;startedAt=performance.now();moves=0;startX=clientX;startLeft=Math.max(0,(parseFloat(handle.style.left)||4)-4)};
const move=(clientX)=>{if(!dragging||done)return; moves++; const pos=setPos(startLeft+(clientX-startX)); if(pos.pct>=96 && moves>=4 && performance.now()-startedAt>=220){dragging=false;finish()}};
handle.addEventListener('pointerdown',e=>{e.preventDefault();handle.setPointerCapture?.(e.pointerId);begin(e.clientX)});
handle.addEventListener('pointermove',e=>move(e.clientX));
handle.addEventListener('pointerup',()=>{if(done)return;dragging=false;const pct=Number(track.getAttribute('aria-valuenow')||0);if(pct<96)setPos(0)});
handle.addEventListener('pointercancel',()=>{if(!done){dragging=false;setPos(0)}});
track.addEventListener('keydown',e=>{if(done)return;const cur=Number(track.getAttribute('aria-valuenow')||0);if(e.key==='ArrowRight'){e.preventDefault();const next=Math.min(100,cur+10);setPos(maxLeft()*next/100);if(next>=100)finish()}else if(e.key==='Home'||e.key==='ArrowLeft'){e.preventDefault();setPos(0)}});
close.onclick=cancel;
overlay.addEventListener('click',e=>{if(e.target===overlay)cancel()});
setTimeout(()=>track.focus(),30);
});
}
async function submitPublicLead(lead){
try{
const clientGeo=await collectClientGeo();
if(clientGeo)lead.clientGeo=clientGeo;
}catch(_){}
const response=await fetch('/api/public/project/'+encodeURIComponent(resolveProjectSlug())+'/leads',{
method:'POST',
headers:{'Content-Type':'application/json'},
credentials:'same-origin',
body:JSON.stringify(lead)
});
let data={};
try{data=await response.json()}catch(_){}
if(!response.ok||data?.ok===false)throw new Error(data?.error||('Ошибка отправки заявки ('+response.status+')'));
return data;
}
function trackEvent(evt){
try {
fetch('/api/public/project/' + encodeURIComponent(pageSlug) + '/events', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ event: evt })
}).catch(()=>{});
}catch(e){}
}
trackEvent('visit');
function reachMetrikaGoal(goal){
try{
const metrikaId=String(s?.quiz?.metrika||'').replace(/\D/g,'').slice(0,12);
if(!metrikaId || typeof window.ym!=='function') return false;
window.ym(Number(metrikaId),'reachGoal',String(goal||''));
return true;
}catch(e){
return false;
}
}
// Universal conversion tracking for phone and messenger clicks.
document.addEventListener('click',e=>{
const a=e.target.closest?.('a,button');
if(!a) return;
const explicit=String(a.dataset?.metrikaGoal||'').trim();
if(explicit){
reachMetrikaGoal(explicit);
return;
}
const href=String(a.getAttribute?.('href')||'').trim();
if(/^tel:/i.test(href)){
reachMetrikaGoal('call');
return;
}
if(/(?:^|\/\/)(?:t\.me|telegram\.me|wa\.me|api\.whatsapp\.com|web\.whatsapp\.com|max\.ru)\//i.test(href)){
reachMetrikaGoal('messenger');
}
},true);
function buildAutoPrivacyPolicy(){
const l=s.legal||{};
const owner=String(l.ownerName||'').trim();
if(!owner) return '';
const ownerType=l.ownerType==='ooo'?'ООО':l.ownerType==='self'?'Самозанятый':'ИП';
const inn=String(l.inn||'').trim();
const ogrn=String(l.ogrn||'').trim();
const email=String(l.email||'').trim();
const address=String(l.address||'').trim();
const collected=[];
if(l.collectName!==false) collected.push('имя');
if(l.collectPhone!==false) collected.push('номер телефона');
if(l.collectEmail===true) collected.push('адрес электронной почты');
if(l.collectAnswers!==false) collected.push('ответы, указанные в формах и квизах');
if(l.collectAnalytics!==false) collected.push('технические данные об использовании сайта, включая cookies и данные веб-аналитики');
const details=[
`${ownerType} ${owner}`,
inn?`ИНН ${inn}`:'',
ogrn?`ОГРН/ОГРНИП ${ogrn}`:'',
email?`электронная почта: ${email}`:'',
address?`адрес: ${address}`:''
].filter(Boolean).join(', ');
const dataList=collected.length?collected.join(', '):'данные, которые пользователь самостоятельно передаёт через формы сайта';
return [
'1. Общие положения',
`Настоящая Политика конфиденциальности определяет порядок обработки персональных данных пользователей сайта. Оператором персональных данных является ${details}.`,
'',
'2. Какие данные могут обрабатываться',
`В зависимости от используемых форм и функций сайта могут обрабатываться: ${dataList}.`,
'',
'3. Цели обработки',
'Персональные данные используются для обработки заявок и обращений, подготовки предложений и расчётов, обратной связи с пользователем, обеспечения работы сайта, анализа его эффективности и улучшения качества сервиса.',
'',
'4. Основания обработки',
'Обработка осуществляется на основании согласия пользователя, которое выражается при отправке формы, а также в иных случаях, предусмотренных применимым законодательством.',
'',
'5. Передача и хранение данных',
'Персональные данные не передаются третьим лицам, за исключением случаев, необходимых для работы сайта и используемых сервисов, исполнения требований законодательства или когда пользователь дал на это отдельное согласие.',
'',
'6. Cookies и аналитика',
l.collectAnalytics!==false
? 'Сайт может использовать cookies и сервисы веб-аналитики для корректной работы, оценки посещаемости и улучшения пользовательского опыта.'
: 'Сайт может использовать только технические cookies, необходимые для его корректной работы.',
'',
'7. Права пользователя',
`Пользователь вправе запросить уточнение, ограничение или прекращение обработки своих персональных данных, направив обращение${email?` на ${email}`:' оператору сайта'}.`,
'',
'8. Изменения политики',
'Оператор вправе обновлять настоящую Политику. Актуальная версия размещается на сайте и применяется с момента публикации.'
].join('\n');
}
function getPrivacyPolicyText(){
const l=s.legal||{};
const own=String(l.ownPolicy||'').trim();
if(own) return own;
if((l.mode||'auto')==='auto') return buildAutoPrivacyPolicy();
return '';
}
function getConsentText(){
const l=s.legal||{};
return String(l.ownConsent||'').trim();
}
function renderFooterConsent(){
const l=s.legal||{};
const label='Согласие на обработку данных';
if(String(l.consentUrl||'').trim()){
return ``;
}
if(getConsentText()){
return ``;
}
return '';
}
function renderFooterOperator(){
const l=s.legal||{};
const owner=String(l.ownerName||'').trim();
const inn=String(l.inn||'').trim();
if(!owner && !inn) return '';
const type=l.ownerType==='ooo'?'ООО':l.ownerType==='self'?'Самозанятый':'ИП';
const parts=[];
if(owner) parts.push(`${type} ${owner}`);
if(inn) parts.push(`ИНН ${inn}`);
return ``;
}
function ensureConsentDialog(){
let dlg=document.getElementById('publicConsentDialog');
if(dlg) return dlg;
dlg=document.createElement('dialog');
dlg.id='publicConsentDialog';
dlg.className='public-privacy-dialog';
dlg.innerHTML=`
Согласие на обработку персональных данных
×
`;
document.body.appendChild(dlg);
dlg.querySelector('.public-consent-close').onclick=()=>dlg.close();
dlg.addEventListener('click',e=>{if(e.target===dlg)dlg.close()});
return dlg;
}
function renderFooterPrivacy(){
const l=s.legal||{};
if(l.showFooterLinks===false) return '';
const label='Политика конфиденциальности';
if(l.mode==='external' && String(l.policyUrl||'').trim()){
return ``;
}
const policy=getPrivacyPolicyText();
if(policy){
return ``;
}
const footerBlock=(s.page?.blocks||[]).find(b=>b.type==='footer');
const legacy=String(footerBlock?.data?.privacy||'').trim();
return legacy ? `` : '';
}
function ensurePrivacyDialog(){
let dlg=document.getElementById('publicPrivacyDialog');
if(dlg) return dlg;
dlg=document.createElement('dialog');
dlg.id='publicPrivacyDialog';
dlg.className='public-privacy-dialog';
dlg.innerHTML=`
Политика конфиденциальности
×
`;
document.body.appendChild(dlg);
dlg.querySelector('.public-privacy-close').onclick=()=>dlg.close();
dlg.addEventListener('click',e=>{if(e.target===dlg)dlg.close()});
return dlg;
}
function cookiePolicyTarget(){
const l=s.legal||{};
if(l.mode==='external' && String(l.policyUrl||'').trim()){
return {type:'url', value:String(l.policyUrl).trim()};
}
if(getPrivacyPolicyText()){
return {type:'dialog', value:''};
}
return null;
}
function renderCookieBanner(){
const l=s.legal||{};
if(l.showCookieBanner===false) return '';
const target=cookiePolicyTarget();
const policyLink=target
? (target.type==='url'
? `Подробнее `
: `Подробнее `)
: '';
return `
Мы используем cookies
Они помогают сайту работать корректно и улучшать взаимодействие с посетителями.
${policyLink}
Хорошо
`;
}
function initCookieBanner(){
const banner=document.getElementById('cookieBanner');
if(!banner) return;
const key='unicorn_cookie_notice_v1';
try{
if(localStorage.getItem(key)==='accepted'){
banner.remove();
return;
}
}catch(_){}
requestAnimationFrame(()=>banner.classList.add('is-visible'));
banner.querySelector('.cookie-accept')?.addEventListener('click',()=>{
try{localStorage.setItem(key,'accepted')}catch(_){}
banner.classList.remove('is-visible');
setTimeout(()=>banner.remove(),220);
});
banner.querySelector('.cookie-policy-open')?.addEventListener('click',()=>{
const policy=getPrivacyPolicyText();
if(!policy)return;
const dlg=ensurePrivacyDialog();
const body=dlg.querySelector('.public-privacy-text');
body.innerHTML='';
policy.split(/\n{2,}/).forEach(chunk=>{
const p=document.createElement('p');
p.textContent=chunk.trim();
body.appendChild(p);
});
if(!dlg.open)dlg.showModal();
});
}
const R={
header:d=>{
const brand=(d.brand||'').trim()||(s.quiz?.title||'').trim()||'Компания';
const tagline=(d.tagline||'').trim(), phone=(d.phone||'').trim(), msg=(d.messenger||'').trim()||'Связаться', loc=(d.location||'').trim();
const mobileLogo=d.mobileLogo||d.logo||'', mlz=Math.max(1,Number(d.mobileLogoZoom)||1.35), mlx=Math.min(100,Math.max(0,Number(d.mobileLogoX)||50)), mly=Math.min(100,Math.max(0,Number(d.mobileLogoY)||50));
const lz=Math.min(2,Math.max(.5,Number(d.logoCropZoom)||1)), lx=Math.min(100,Math.max(0,Number(d.logoCropX)||50)), ly=Math.min(100,Math.max(0,Number(d.logoCropY)||50));
const logoFormat=d.logoDesktopFormat||'wide', logoCropped=d.logoDesktopCropped===true;
const squareTaglineMode=d.squareTaglineMode||'right';
const desktopManual=d.logoManualCropEnabled===true;
const mcx=Number(d.logoManualCropX)||0,mcy=Number(d.logoManualCropY)||0,mcw=Math.max(1,Number(d.logoManualCropW)||100),mch=Math.max(1,Number(d.logoManualCropH)||100),mcr=Math.max(.2,Number(d.logoManualCropRatio)||1);
const mobileManual=d.mobileLogoManualCropEnabled===true;
const mobileManualFormat=mobileManual&&d.mobileLogoManualCropFormat==='wide'?'wide':'square';
const mmx=Number(d.mobileLogoManualCropX)||0,mmy=Number(d.mobileLogoManualCropY)||0,mmw=Math.max(1,Number(d.mobileLogoManualCropW)||100),mmh=Math.max(1,Number(d.mobileLogoManualCropH)||100);
const squareLogo=desktopManual?mcr<=1.35:logoFormat==='square';
const action=d.messengerAction||'anchor'; let attrs='';
if(action==='anchor') attrs=`data-anchor="${E(d.messengerAnchor||'quiz')}"`;
else if(action==='telegram'){
const rawTelegram=String(d.messengerUrl||'').trim();
const telegramUrl=/^https?:\/\//i.test(rawTelegram)?rawTelegram:(rawTelegram?`https://t.me/${rawTelegram.replace(/^@/,'').replace(/^t\.me\//i,'')}`:'');
attrs=`data-url="${E(telegramUrl)}" data-external="1" data-metrika-goal="messenger"`;
}
else if(action==='url') attrs=`data-url="${E(d.messengerUrl||'')}" data-external="1"`;
else if(action==='phone') attrs=`data-phone="${E(phone)}" data-metrika-goal="call"`;
return ``;
},
hero:d=>{
const title=(d.title||'').trim()||(s.quiz?.title||'').trim()||'Качественные решения для вашего бизнеса', subtitle=(d.subtitle||'').trim(), kicker=(d.kicker||'').trim(), btn=(d.button||'').trim();
const trust=(d.trust||'').trim(), heroImg=(d.image||'').trim()||'https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1600&q=85';
const heroVideo=(d.video||'').trim(), heroIsVideo=d.mediaType==='video'&&heroVideo;
const promoMode=d.imagePromoMode==='image'?'image':'text';
const promoKicker=(d.imagePromoKicker||'').trim(), promoText=(d.imagePromoText||'').trim();
const showPromo=d.imagePromoEnabled!==false&&(promoMode==='text'||String(d.imagePromoImage||'').trim());
const hx=Math.min(100,Math.max(0,Number(d.imageCropX)||50)),hy=Math.min(100,Math.max(0,Number(d.imageCropY)||50)),hz=Math.min(2,Math.max(.5,Number(d.imageCropZoom)||1));
const custom=d.customTypography===true, tDesk=Math.min(96,Math.max(32,Number(d.titleSizeDesktop)||62)), tMob=Math.min(60,Math.max(26,Number(d.titleSizeMobile)||38)), sDesk=Math.min(30,Math.max(13,Number(d.subtitleSizeDesktop)||16)), sMob=Math.min(24,Math.max(13,Number(d.subtitleSizeMobile)||15));
const promoSize=['small','medium','large'].includes(d.imagePromoSize)?d.imagePromoSize:'medium', promoBg=d.imagePromoBackground!==false, promoFit=d.imagePromoFit==='cover'?'cover':'contain', promoScale=Math.min(160,Math.max(60,Number(d.imagePromoScale)||100))/100, pix=Math.min(100,Math.max(0,Number(d.imagePromoImageCropX??50))), piy=Math.min(100,Math.max(0,Number(d.imagePromoImageCropY??50))), piz=Math.min(2,Math.max(.5,Number(d.imagePromoImageCropZoom??1)));
return `${kicker?`
✦ ${E(kicker)} `:''}
${E(title)} ${subtitle?`
${E(subtitle)}
`:''}
${E(btn||'Рассчитать стоимость')}${d.buttonNote?`${E(d.buttonNote)} `:''} ${trust?`
${iconOf(d.trustIcon||'check')?`${E(iconOf(d.trustIcon||'check'))} `:''}${E(trust)}
`:''}
`;
},
promobar:d=>{if(!d)return '';const promoParts=i=>{const raw=String(d['item'+i]||'').trim(),parts=raw.split(/\n+|(?<=\.)\s*/).map(x=>x.trim()).filter(Boolean);return {title:String(d['itemTitle'+i]||'').trim()||(parts[0]||raw),text:String(d['itemText'+i]||'').trim()||(parts.length>1?parts.slice(1).join(' '):'')}};const hasAny=String(d.offer||'').trim()||[1,2,3].some(i=>{const p=promoParts(i);return p.title||p.text});if(!hasAny)return '';const iconStyle=d.iconStyle==='color'?'color':'simple';const card=i=>{const p=promoParts(i);if(!p.title&&!p.text)return '';if(d.variant!=='iconCards')return `${E(iconOf(d['icon'+i]||'check'))} ${E(p.title||d['item'+i]||'')} `;const extended=!!p.text;return `${promoIconHtml(d['icon'+i]||'check',iconStyle)} ${E(p.title)} ${extended?`${E(p.text)} `:''} `};return `${['benefitsOnly','iconCards'].includes(d.variant)?'':`
🎁 ${E(d.offer||'')}
`}
${card(1)}${card(2)}${card(3)}
`},
about:d=>{const paras=String(d.text||'').split(/\n\s*\n/).map(x=>x.trim()).filter(Boolean),ax=Math.min(100,Math.max(0,Number(d.imageCropX)||50)),ay=Math.min(100,Math.max(0,Number(d.imageCropY)||50)),az=Math.min(2,Math.max(.5,Number(d.imageCropZoom)||1)),max=Math.min(100,Math.max(0,Number(d.mobileImageCropX??50))),may=Math.min(100,Math.max(0,Number(d.mobileImageCropY??35))),maz=Math.min(2,Math.max(.5,Number(d.mobileImageCropZoom??1))),fit=d.imageFit==='contain'?'contain':'cover';return `${d.mediaType==='video'?`
${videoCard({video:d.video||'',image:d.image||''})}
`:`
`}
${E(d.eyebrow)}
${E(d.title)} ${paras.map(p=>`
${E(p).replace(/\n/g,' ')}
`).join('')}
${(d.stats||[]).filter(x=>String(x.value||'').trim()&&String(x.label||'').trim()).length?`
${(d.stats||[]).filter(x=>String(x.value||'').trim()&&String(x.label||'').trim()).map(x=>`
${E(x.value)} ${E(x.label)}
`).join('')}
`:''}
`},
quiz:d=>`${E(d.eyebrow)}
${E(d.title)} ${E(d.subtitle)}
`,
benefits:d=>`${(d.items||[]).map((x,i)=>`
${String(i+1).padStart(2,'0')} ${E(x.title)} ${E(x.text)}
`).join('')}
`,
works:d=>{const items=d.items||[],layout=d.layout||'three',legacySlider=['slider','slider3','slider4'].includes(layout),navSlider=['two','three','vertical','wide'].includes(layout)&&d.navigation==='slider',isSlider=legacySlider||navSlider,moreLayout=layout==='more',showTitles=d.showTitles!==false,bg=d.cardBackground===true?'work-bg':'',fit=['contain','width','height'].includes(d.imageFit)?d.imageFit:'cover',sliderCount=layout==='slider4'?4:layout==='two'||layout==='wide'?2:layout==='vertical'?4:3,rowSettings=d.rowSettings||{};const gridCols=moreLayout?2:(layout==='two'?2:layout==='vertical'?4:layout==='wide'?2:3),maxCollapsedRows=2,collapsedVisible=items.length<=gridCols?items.length:Math.max(gridCols,Math.floor(Math.min(items.length,gridCols*maxCollapsedRows)/gridCols)*gridCols);const imgHtml=x=>{const cx=Math.min(100,Math.max(0,Number(x.imageCropX??x.cropX??50))),cy=Math.min(100,Math.max(0,Number(x.imageCropY??x.cropY??50))),cz=Math.min(2,Math.max(.5,Number(x.imageCropZoom??x.cropZoom??1)));return ` `};const compareHtml=x=>{const before=x.beforeImage||'',after=x.afterImage||'';if(!before||!after)return `
Добавьте фото «До» и «После»
`;return `
До После `};const card=(x,extra='',active='')=>``;const usedRows=[...new Set(items.map(x=>Math.max(1,Number(x.row)||1)))].sort((a,b)=>a-b),multiRows=usedRows.length>1||Object.keys(rowSettings).length>0;const ratioClass=r=>{const q=(rowSettings[String(r)]||{}).ratio||'default';return q==='16x9'?'works-row-ratio-16x9':q==='4x3'?'works-row-ratio-4x3':q==='3x4'?'works-row-ratio-3x4':q==='1x1'?'works-row-ratio-1x1':q==='2x1'?'works-row-ratio-2x1':'works-row-ratio-default'};const renderRows=()=>usedRows.map((row,ri)=>{const rowItems=items.filter(x=>(Math.max(1,Number(x.row)||1))===row),cfg=rowSettings[String(row)]||{},mode=cfg.mode||(row===1&&isSlider?'slider':'grid'),cols=[1,2,3,4].includes(Number(cfg.columns))?Number(cfg.columns):(row===1?sliderCount:2),rc=ratioClass(row),mobileClass=cfg.mobileMode==='stack'?'works-row-mobile-stack':'works-row-mobile-swipe';if(mode==='slider')return `
${rowItems.map((x,i)=>card(x,'works-slide',i
`;return `
${rowItems.map(x=>card(x)).join('')}
`}).join('');return `
${d.eyebrow?`
${E(d.eyebrow)}
`:''}
${E(d.title)} ${d.subtitle?`
${E(d.subtitle)}
`:''}
${multiRows?renderRows():(isSlider?`
${items.map((x,i)=>card(x,'works-slide',i
`:`
${items.map((x,i)=>card(x,i>=collapsedVisible?'work-extra':'')).join('')}
${items.length>collapsedVisible?`
Ещё работы ↓
`:''}`)}
`},
textImage:d=>`
${E(d.kicker)}
${E(d.title)} ${E(d.text)}
`,
steps:d=>`
Как работаем
${E(d.title)} ${(d.items||[]).map((x,i)=>`
${String(i+1).padStart(2,'0')} ${E(x.title)} ${E(x.text)}
`).join('')}
`,
reviews:d=>{const shots=(d.items||[]).filter(x=>(x.type==='video'?x.video:x.image)),cols=[3,4,5].includes(Number(d.cardsPerRow))?Number(d.cardsPerRow):3;const cards=shots.map(x=>x.type==='video'?`
${videoCard(x)}${x.title?`${E(x.title)} `:''} `:`
${x.title?`${E(x.title)} `:''}`).join('');return `
Отзывы
${E(d.title||'Отзывы клиентов')} ${E(d.subtitle||'')}
${d.mode==='embed'?`
${d.embed||''}
`:(d.layout==='slider'?`
`:`
${cards}
`)}
`},
customCode:d=>`
${d.title?`
${E(d.title)} `:''}
${d.code||''}
`,
final:d=>`
${E(d.title)} ${E(d.subtitle)}
${E(d.button)} ${E(d.phone||'')}
`,
footer:d=>``};
let bs=[...(s.page.blocks||[])];
// Defensive normalization for public page: Footer is always last,
// and Final CTA is always immediately before Footer/Promo-footer.
const take=t=>bs.find(b=>b.type===t);
const header=take('header'), hero=take('hero'), quiz=take('quiz'),
promoBlock=take('promo'), finalBlock=take('final'), footer=take('footer');
const content=bs.filter(b=>!['header','hero','quiz','promo','final','footer'].includes(b.type));
let ordered=[];
if(header) ordered.push(header);
if(hero) ordered.push(hero);
if(promoBlock && s.page.promoPlacement!=='beforeFooter') ordered.push(promoBlock);
if(quiz) ordered.push(quiz);
ordered.push(...content);
if(finalBlock) ordered.push(finalBlock);
if(promoBlock && s.page.promoPlacement==='beforeFooter') ordered.push(promoBlock);
if(footer) ordered.push(footer);
const renderBlock=b=>{
if(b.type==='promo') return R.promobar?R.promobar(b.data||{}):'';
return R[b.type]?R[b.type](b.data||{}):'';
};
app.innerHTML=ordered.filter(b=>b.enabled!==false).map(renderBlock).join('') + renderCookieBanner();
initCookieBanner();
/* v159 review slider arrows */
document.querySelectorAll('.reviews-layout-slider .reviews-slider-wrap').forEach(wrap=>{
const rail=wrap.querySelector('.reviews-grid');
const prev=wrap.querySelector('.reviews-prev');
const next=wrap.querySelector('.reviews-next');
if(!rail)return;
const step=()=>{
const card=rail.querySelector('.review-shot');
if(!card)return Math.max(280,rail.clientWidth);
const gap=parseFloat(getComputedStyle(rail).gap||'14')||14;
return card.getBoundingClientRect().width+gap;
};
prev?.addEventListener('click',()=>rail.scrollBy({left:-step(),behavior:'smooth'}));
next?.addEventListener('click',()=>rail.scrollBy({left:step(),behavior:'smooth'}));
});
document.querySelectorAll('.footer-privacy-open').forEach(btn=>{
btn.addEventListener('click',()=>{
const policy=getPrivacyPolicyText();
if(!policy)return;
const dlg=ensurePrivacyDialog();
const body=dlg.querySelector('.public-privacy-text');
body.innerHTML='';
policy.split(/\n{2,}/).forEach(chunk=>{
const p=document.createElement('p');
p.textContent=chunk.trim();
body.appendChild(p);
});
if(!dlg.open)dlg.showModal();
});
});
document.querySelectorAll('.footer-consent-open').forEach(btn=>{
btn.addEventListener('click',()=>{
const text=getConsentText();
if(!text)return;
const dlg=ensureConsentDialog();
const body=dlg.querySelector('.public-privacy-text');
body.innerHTML='';
text.split(/\n{2,}/).forEach(chunk=>{
const p=document.createElement('p');
p.textContent=chunk.trim();
body.appendChild(p);
});
if(!dlg.open)dlg.showModal();
});
});
document.querySelectorAll('.brand:not(.brand-square-logo) .brand-logo-desktop').forEach(img=>{const apply=()=>{if(img.naturalWidth&&img.naturalHeight){const r=img.naturalWidth/img.naturalHeight;if(r>.82&&r<1.22)img.closest('.brand')?.classList.add('brand-square-auto')}};if(img.complete)apply();else img.addEventListener('load',apply,{once:true})});
// Execute trusted widget scripts only on the published page. In admin preview they stay inert.
if(!new URLSearchParams(location.search).has('preview')){
document.querySelectorAll('.custom-code-content script, .reviews-embed script').forEach(oldScript=>{
const fresh=document.createElement('script');
[...oldScript.attributes].forEach(a=>fresh.setAttribute(a.name,a.value));
fresh.textContent=oldScript.textContent||'';
oldScript.replaceWith(fresh);
});
}
function quizUploadPreviewHtml(items){
return (Array.isArray(items)?items:[]).map((url,i)=>`
Заменить ⌫
`).join('');
}
async function uploadEmbeddedQuizImages(files,maxCount){
const selected=[...files].slice(0,Math.max(1,maxCount||5));
if(!selected.length)return [];
for(const file of selected){
const mime=String(file.type||'').toLowerCase();
const name=String(file.name||'').toLowerCase();
const supported=/^image\/(jpeg|jpg|png|webp|gif)$/i.test(mime)||/\.(jpe?g|png|webp|gif)$/i.test(name);
if(!supported)throw new Error('Поддерживаются JPG, PNG, WebP и GIF');
if(file.size>5*1024*1024)throw new Error('Одна фотография должна быть не больше 5 МБ');
}
const fd=new FormData();
selected.forEach(file=>fd.append('images',file,file.name||'photo'));
let r;
try{
r=await fetch('/api/public/project/'+encodeURIComponent(pageSlug)+'/quiz-images',{method:'POST',body:fd,credentials:'same-origin'});
}catch(_){
throw new Error('Не удалось связаться с сервером. Проверьте интернет и попробуйте ещё раз.');
}
const raw=await r.text();
let d={};try{d=raw?JSON.parse(raw):{}}catch(_){}
if(!r.ok)throw new Error(d.error||('Не удалось загрузить фотографию ('+r.status+')'));
const urls=Array.isArray(d.urls)?d.urls:[];
if(!urls.length)throw new Error('Сервер не вернул загруженную фотографию');
return urls.map(url=>new URL(url,location.origin).href);
}
function rq(){let h=document.getElementById('qbody');if(!h)return;let qs=s.quiz.questions||[];if(qi
${E(a||'')}`;
}else if(q.type==='number'){
f=` `;
}else if(isUpload){
const uploaded=Array.isArray(a)?a:[];
const maxUpload=[1,2,3,5,10].includes(Number(q.uploadMax))?Number(q.uploadMax):5;
f=``;
}else{
f=`${isMulti?`✓ Можно выбрать несколько вариантов ${Array.isArray(a)&&a.length?`Выбрано: ${a.length}`:'Выберите подходящие ответы'}
`:''}${(q.options||[]).map((o,oi)=>{const img=(q.optionImages||[])[oi]||'';const selected=isMulti?Array.isArray(a)&&a.includes(o):a===o;return `${img?` `:''}${isMulti?'✓ ':''}${E(o)} `}).join('')}
`;
}
h.innerHTML=`Шаг ${qi+1} из ${qs.length}
${E(q.title)} ${String(q.description||'').trim()?`${E(q.description)}
`:''}${q.image?``:''}${f}← Назад ${isMulti?'Продолжить →':(isGift?'Выбрать подарок →':'Далее →')}
`;
const nextBtn=h.querySelector('.next');
const hasRequiredAnswer=()=>{if(!q.required)return true;const v=qa[qi];if(['multi','upload'].includes(q.type))return Array.isArray(v)&&v.length>0;if(q.type==='text')return String(h.querySelector('#txt')?.value??v??'').trim().length>0;if(q.type==='number')return String(h.querySelector('#num')?.value??v??'').trim().length>0;return String(v??'').trim().length>0};
const syncNext=()=>{if(nextBtn)nextBtn.disabled=!hasRequiredAnswer()};
if(!isUpload&&q.type!=='text'&&q.type!=='number')h.querySelectorAll('.answer').forEach(b=>b.onclick=()=>{
const value=b.dataset.a;
if(isMulti){
const current=Array.isArray(qa[qi])?[...qa[qi]]:[];
const pos=current.indexOf(value);
if(pos>=0)current.splice(pos,1);else current.push(value);
qa[qi]=current;
b.classList.toggle('selected',current.includes(value));
const count=h.querySelector('#multiChoiceCount');
if(count)count.textContent=current.length?`Выбрано: ${current.length}`:'Выберите подходящие ответы';
syncNext();
return;
}
qa[qi]=value;
h.querySelectorAll('.answer').forEach(x=>x.classList.toggle('selected',x===b));
syncNext();
if(isGift)return;
const isSingle=q.type==='single'||!q.type||q.type==='radio';
if(isSingle)setTimeout(()=>{qi++;rq()},220);
});
if(isUpload){
const input=h.querySelector('#quizUploadFiles'),replaceInput=h.querySelector('#quizReplaceFile'),drop=h.querySelector('#quizUploadDrop'),previews=h.querySelector('#quizUploadPreviews'),status=h.querySelector('#quizUploadStatus');
const maxUpload=[1,2,3,5,10].includes(Number(q.uploadMax))?Number(q.uploadMax):5;
let replaceIndex=-1;
const redraw=()=>{const arr=Array.isArray(qa[qi])?qa[qi]:[];previews.innerHTML=quizUploadPreviewHtml(arr);drop.classList.toggle('has-files',arr.length>0);status.textContent=arr.length?`Загружено: ${arr.length} из ${maxUpload}`:'';status.classList.remove('error');previews.querySelectorAll('[data-upload-remove]').forEach(btn=>btn.onclick=()=>{const current=[...(qa[qi]||[])];current.splice(Number(btn.dataset.uploadRemove),1);qa[qi]=current;redraw();syncNext()});previews.querySelectorAll('[data-upload-replace]').forEach(btn=>btn.onclick=()=>{replaceIndex=Number(btn.dataset.uploadReplace);replaceInput.click()});syncNext()};
const process=async files=>{const current=Array.isArray(qa[qi])?[...qa[qi]]:[];const room=maxUpload-current.length;if(room<=0){status.textContent=`Можно загрузить не больше ${maxUpload}`;status.classList.add('error');return}status.textContent='Загружаем…';status.classList.remove('error');try{const urls=await uploadEmbeddedQuizImages([...files].slice(0,room),room);qa[qi]=current.concat(urls);redraw()}catch(err){status.textContent=err.message||'Ошибка загрузки';status.classList.add('error')}};
input.onchange=e=>{process(e.target.files);e.target.value=''};
replaceInput.onchange=async e=>{const file=e.target.files?.[0];if(!file||replaceIndex<0)return;e.target.value='';status.textContent='Заменяем…';status.classList.remove('error');try{const urls=await uploadEmbeddedQuizImages([file],1);const current=[...(qa[qi]||[])];if(urls[0]&¤t[replaceIndex])current[replaceIndex]=urls[0];qa[qi]=current;redraw()}catch(err){status.textContent=err.message||'Ошибка загрузки';status.classList.add('error')}finally{replaceIndex=-1}};
drop.ondragover=e=>{e.preventDefault();drop.classList.add('dragover')};drop.ondragleave=()=>drop.classList.remove('dragover');drop.ondrop=e=>{e.preventDefault();drop.classList.remove('dragover');process(e.dataTransfer.files)};
redraw();
}
if(q.type==='text')h.querySelector('#txt')?.addEventListener('input',syncNext);
if(q.type==='number')h.querySelector('#num')?.addEventListener('input',syncNext);
syncNext();
h.querySelector('.prev').onclick=()=>{if(qi){qi--;rq()}};
h.querySelector('.next').onclick=()=>{
if(q.type==='text')qa[qi]=h.querySelector('#txt')?.value||'';
if(q.type==='number')qa[qi]=h.querySelector('#num')?.value||'';
const emptyArray=['multi','upload'].includes(q.type)&&(!Array.isArray(qa[qi])||qa[qi].length===0);
if(q.required&&(!qa[qi]||emptyArray)){
const el=h.querySelector('.answers')||h.querySelector('.quiz-upload-drop')||h.querySelector('#txt')||h.querySelector('#num');
if(el){el.classList.add('validation-error');setTimeout(()=>el.classList.remove('validation-error'),1400)}
return;
}
qi++;rq();
}
}else{document.getElementById('prog').style.width='100%';{
if(!window.__contactsTracked){window.__contactsTracked=true;trackEvent('contacts');}
const methods=[];
if(s.quiz.contactMethodPhone!==false)methods.push(['phone','Позвонить']);
if(s.quiz.contactMethodTelegram!==false)methods.push(['telegram','Telegram']);
if(s.quiz.contactMethodWhatsapp!==false)methods.push(['whatsapp','WhatsApp']);
if(s.quiz.contactMethodMax!==false)methods.push(['max','MAX']);
if(s.quiz.contactMethodVk===true)methods.push(['vk','ВКонтакте']);
if(!methods.length)methods.push(['phone','Позвонить']);
const consentOn=s.legal?.showConsent!==false;
const legalText=s.legal?.formText||'Нажимая кнопку, вы соглашаетесь на обработку персональных данных.';
const contactTitle=String(s.quiz?.contactTitle||'Как с вами связаться?').trim()||'Как с вами связаться?';
const contactHint=String(s.quiz?.contactHint||'').trim();
const summaryItems=[];
qs.forEach((question,index)=>{const value=qa[index];if(question.type==='upload'&&Array.isArray(value)&&value.length)summaryItems.push(`📷 ${value.length} фото загружено `);if(question.type==='gift'&&value)summaryItems.push(`🎁 ${E(value)} `)});
h.innerHTML=`Последний шаг
${E(contactTitle)} ${contactHint?`${E(contactHint)}
`:''}${summaryItems.length?`${summaryItems.join('')}
`:''}
${methods.map((m,i)=>`${m[1]} `).join('')}
${consentOn?`${E(legalText)} `:''}
← Назад ${E(s.quiz.contactButton)}
`;
const consentEl=document.getElementById('consent');if(consentEl)consentEl.checked=false;
let selectedMethod=methods[0][0];
const nameEl=document.getElementById('nm'),phEl=document.getElementById('ph'),hint=document.getElementById('methodHint');
nameEl.addEventListener('input',()=>{const v=sanitizePersonName(nameEl.value);if(nameEl.value!==v)nameEl.value=v;});
phEl.addEventListener('input',()=>{if(!['telegram','vk'].includes(selectedMethod))phEl.value=formatRuPhone(phEl.value);});
const syncMethod=()=>{
const isTelegram=selectedMethod==='telegram', isVk=selectedMethod==='vk', isTextContact=isTelegram||isVk;
phEl.inputMode=isTextContact?'text':'tel';
phEl.placeholder=isTelegram?'@username или телефон':isVk?'vk.com/username или username':'+7 (999) 123-45-67';
if(!isTextContact&&phEl.value)phEl.value=formatRuPhone(phEl.value);
hint.textContent=isTelegram?'Укажите Telegram username или номер телефона.':isVk?'Укажите ник или полную ссылку на профиль ВКонтакте.':selectedMethod==='whatsapp'?'Укажите российский номер, к которому привязан WhatsApp.':selectedMethod==='max'?'Укажите российский номер телефона для MAX.':'Укажите российский номер телефона.';
};
h.querySelectorAll('.contact-method').forEach(btn=>btn.onclick=()=>{
selectedMethod=btn.dataset.method;
h.querySelectorAll('.contact-method').forEach(x=>x.classList.toggle('selected',x===btn));
syncMethod();
});
syncMethod();
h.querySelector('.prev').onclick=()=>{qi=Math.max(0,qs.length-1);rq()};
document.getElementById('send').onclick=async ()=>{
const contact=phEl.value.trim();
const sendBtn=document.getElementById('send');
const isTextContact=['telegram','vk'].includes(selectedMethod);
const vkOk=selectedMethod!=='vk'||/^(?:https?:\/\/)?(?:www\.)?vk\.(?:com|ru)\/[A-Za-z0-9_.-]+\/?(?:[?#].*)?$|^@?[A-Za-z0-9_.-]{2,64}$/.test(contact);
if(!contact || (!isTextContact&&!validRuPhone(contact)) || !vkOk){
phEl.style.borderColor='#ef4444';
hint.textContent=selectedMethod==='telegram'?'Укажите Telegram username или номер телефона.':selectedMethod==='vk'?'Укажите ник ВКонтакте или ссылку вида vk.com/username.':'Введите номер полностью: +7 (999) 123-45-67';
phEl.focus();
return;
}
phEl.style.borderColor='';
if(consentOn&&!document.getElementById('consent').checked){
const consentLabel=document.querySelector('.public-consent');
if(consentLabel){
consentLabel.classList.add('validation-error');
setTimeout(()=>consentLabel.classList.remove('validation-error'),1600);
}
return;
}
if(!(await requireHumanSlider())) return;
sendBtn.disabled=true;
sendBtn.textContent='Отправка...';
const slug=pageSlug;
markQuizStarted();const lead={date:new Date().toISOString(),name:document.getElementById('nm').value.trim(),phone:['telegram','vk'].includes(selectedMethod)?'':contact,contact,contactMethod:selectedMethod,answers:qa,visitorId:__visitorId,company_website:document.getElementById('company_website')?.value||'',antiFraud:collectAntiFraudMeta(),...collectLeadAttribution()};
try {
await submitPublicLead(lead);
} catch(e) {
console.error('Server lead error:',e);
sendBtn.disabled=false;
sendBtn.textContent=s.quiz.contactButton||'Отправить';
hint.textContent='Не удалось отправить заявку. Попробуйте ещё раз.';
hint.style.color='#ef4444';
return;
}
try{await notifyLead(lead)}catch(e){console.warn('Webhook notification error:',e)}
s.leads=s.leads||[];
s.leads.unshift(lead);
try { localStorage.setItem(KEY,JSON.stringify(s)); } catch(e){}
window.location.href='/thanks';
}
}}}rq();document.querySelectorAll('.jump').forEach(b=>b.onclick=()=>document.querySelector('.quiz-section')?.scrollIntoView({behavior:'smooth'}));
document.querySelectorAll('.header-cta').forEach(b=>b.onclick=()=>{const a=b.dataset.anchor,u=b.dataset.url,p=b.dataset.phone,external=b.dataset.external==='1';if(u){window.open(u,external?'_blank':'_self','noopener')}else if(p){location.href='tel:'+p.replace(/[^+\d]/g,'')}else if(a){document.getElementById(a)?.scrollIntoView({behavior:'smooth'})}});
function initWorkComparisons(){
const clampCompare=(n,min,max)=>Math.min(max,Math.max(min,n));
document.querySelectorAll('[data-compare]').forEach(compare=>{
if(compare.dataset.compareReady==='1')return;
compare.dataset.compareReady='1';
const handle=compare.querySelector('.compare-handle');
if(!handle)return;
const setFromClientX=x=>{const r=compare.getBoundingClientRect();if(!r.width)return;const pct=clampCompare((x-r.left)/r.width*100,0,100);compare.style.setProperty('--compare',pct+'%');handle.setAttribute('aria-valuenow',String(Math.round(pct)))};
let dragging=false;
compare.addEventListener('pointerdown',e=>{dragging=true;try{compare.setPointerCapture(e.pointerId)}catch(_){}setFromClientX(e.clientX);e.preventDefault()});
compare.addEventListener('pointermove',e=>{if(!dragging)return;setFromClientX(e.clientX);e.preventDefault()});
const stop=e=>{if(!dragging)return;dragging=false;try{compare.releasePointerCapture(e.pointerId)}catch(_){}};
compare.addEventListener('pointerup',stop);compare.addEventListener('pointercancel',stop);
handle.addEventListener('keydown',e=>{let v=Number(handle.getAttribute('aria-valuenow')||50);if(e.key==='ArrowLeft')v-=2;else if(e.key==='ArrowRight')v+=2;else return;e.preventDefault();v=clampCompare(v,0,100);compare.style.setProperty('--compare',v+'%');handle.setAttribute('aria-valuenow',String(v))});
});
}
initWorkComparisons();
document.querySelectorAll('.works-more').forEach(btn=>{
const section=btn.closest('.works');
const extras=[...section.querySelectorAll('.work-extra')];
btn.onclick=()=>{
const hasHidden=extras.some(el=>!el.classList.contains('is-revealed'));
if(hasHidden){
extras.forEach(el=>el.classList.add('is-revealed'));
section.classList.add('works-expanded');
btn.innerHTML='Скрыть работы ↑ ';
}else{
extras.forEach(el=>el.classList.remove('is-revealed'));
section.classList.remove('works-expanded');
btn.innerHTML='Ещё работы ↓ ';
section.scrollIntoView({behavior:'smooth',block:'start'});
}
};
});
// Click-to-open gallery images without loading a second heavy original.
const workLightbox=document.createElement('dialog');
workLightbox.className='work-lightbox';
workLightbox.innerHTML='× ';
document.body.appendChild(workLightbox);
document.querySelectorAll('.work-image-open').forEach(btn=>btn.addEventListener('click',()=>{
const src=btn.dataset.workImage;if(!src)return;
workLightbox.querySelector('img').src=src;workLightbox.showModal();
}));
workLightbox.querySelector('.work-lightbox-close').onclick=()=>workLightbox.close();
workLightbox.addEventListener('click',e=>{if(e.target===workLightbox)workLightbox.close()});
workLightbox.addEventListener('close',()=>{const img=workLightbox.querySelector('img');if(img)img.removeAttribute('src')});
document.querySelectorAll('.review-image-open').forEach(btn=>btn.addEventListener('click',()=>{const src=btn.dataset.reviewImage;if(!src)return;workLightbox.querySelector('img').src=src;workLightbox.showModal()}));
document.querySelectorAll('.works-slider-wrap').forEach(wrap=>{
const slider=wrap.querySelector('.works-slider');
const slides=[...wrap.querySelectorAll('.works-slide')];
if(!slider||!slides.length)return;
let index=0;
let suppressClick=false;
const desktopCount=Number(wrap.dataset.count||3);
const visibleCount=()=>{
if(window.innerWidth<=560) return 1;
if(window.innerWidth<=900) return Math.min(2,slides.length);
return Math.min(desktopCount,slides.length);
};
const show=i=>{
const total=slides.length;
const count=visibleCount();
if(!total)return;
index=((i%total)+total)%total;
// Carousel window wraps around, so arrows/drag still work even when
// total === visibleCount (all cards stay visible but their order cycles).
const visible=new Set();
for(let k=0;k{
slide.classList.toggle('is-active',visible.has(n));
const rel=(n-index+total)%total;
slide.style.order=visible.has(n)?String(rel):String(total+rel);
});
const hide=total<=1;
wrap.querySelectorAll('.works-slider-arrow').forEach(a=>a.style.visibility=hide?'hidden':'visible');
};
const goPrev=()=>show(index-1);
const goNext=()=>show(index+1);
wrap.querySelector('.works-prev')?.addEventListener('click',e=>{
e.preventDefault(); e.stopPropagation(); goPrev();
});
wrap.querySelector('.works-next')?.addEventListener('click',e=>{
e.preventDefault(); e.stopPropagation(); goNext();
});
// Touch swipe: change only the carousel index. Do not translate/scale the page.
let touchStartX=0,touchStartY=0,touchLastX=0,touchHorizontal=false;
slider.addEventListener('touchstart',e=>{
if(!e.touches||e.touches.length!==1)return;
touchStartX=touchLastX=e.touches[0].clientX;
touchStartY=e.touches[0].clientY;
touchHorizontal=false;
},{passive:true});
slider.addEventListener('touchmove',e=>{
if(!e.touches||e.touches.length!==1)return;
touchLastX=e.touches[0].clientX;
const dx=touchLastX-touchStartX;
const dy=e.touches[0].clientY-touchStartY;
if(!touchHorizontal && Math.abs(dx)>8 && Math.abs(dx)>Math.abs(dy)*1.15){
touchHorizontal=true;
}
if(touchHorizontal)e.preventDefault();
},{passive:false});
slider.addEventListener('touchend',()=>{
if(!touchHorizontal)return;
const dx=touchLastX-touchStartX;
if(Math.abs(dx)>=34){
suppressClick=true;
dx<0?goNext():goPrev();
setTimeout(()=>{suppressClick=false},180);
}
touchHorizontal=false;
},{passive:true});
// Desktop mouse drag.
let drag=false,dragStartX=0,dragStartY=0,dragLastX=0,dragHorizontal=false;
slider.addEventListener('pointerdown',e=>{
if(e.pointerType==='touch')return; // touch is handled above
if(e.button!==0)return;
drag=true;
dragStartX=dragLastX=e.clientX;
dragStartY=e.clientY;
dragHorizontal=false;
// Do not preventDefault here: a normal click on a work must still open the lightbox.
});
slider.addEventListener('pointermove',e=>{
if(!drag||e.pointerType==='touch')return;
dragLastX=e.clientX;
const dx=dragLastX-dragStartX;
const dy=e.clientY-dragStartY;
if(!dragHorizontal && Math.abs(dx)>7 && Math.abs(dx)>Math.abs(dy)*1.1){
dragHorizontal=true;
slider.classList.add('is-dragging');
try{slider.setPointerCapture(e.pointerId)}catch(_){}
}
if(dragHorizontal)e.preventDefault();
});
const finishPointer=e=>{
if(!drag)return;
const dx=dragLastX-dragStartX;
if(dragHorizontal&&Math.abs(dx)>=34){
suppressClick=true;
dx<0?goNext():goPrev();
setTimeout(()=>{suppressClick=false},180);
}
drag=false; dragHorizontal=false;
slider.classList.remove('is-dragging');
try{slider.releasePointerCapture(e.pointerId)}catch(_){}
};
slider.addEventListener('pointerup',finishPointer);
slider.addEventListener('pointercancel',finishPointer);
slider.addEventListener('click',e=>{
if(!suppressClick)return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation?.();
},true);
window.addEventListener('resize',()=>show(index));
show(0);
});
function ensureVideoModal(){
let modal=document.getElementById('siteVideoModal');
if(modal)return modal;
modal=document.createElement('div');modal.id='siteVideoModal';modal.className='site-video-modal';modal.innerHTML='×
';
document.body.appendChild(modal);
modal.addEventListener('click',e=>{if(e.target===modal||e.target.closest('.site-video-modal-close')){modal.classList.remove('is-open');modal.querySelector('iframe').src='';document.body.style.overflow=''}});
return modal;
}
document.addEventListener('click',e=>{
const btn=e.target.closest('.video-modal-open');if(!btn)return;
const modal=ensureVideoModal();modal.querySelector('iframe').src=btn.dataset.videoUrl||'';modal.classList.add('is-open');document.body.style.overflow='hidden';
});