').join('')}
function chips(){$('#chips').innerHTML=(mode==='cat'?Object.keys(C).map(k=>''+C[k][0]+'').join(''):'')+''}
function card(p){return '
'+E(p.n)+'
'+C[p.c][0]+'
'+R(p.p)+''+(p.s?'':'')+'
'}
function grid(){
const a=P.filter(p=>(mode==='fav'?fav.has(p.id):p.c===cat)&&p.n.toLowerCase().includes(qr.toLowerCase()));
let empty='Ничего не найдено.';
if(!ready)empty=failed?'Не удалось загрузить каталог. Обновите страницу.':'Загрузка…';
else if(mode==='fav'&&!qr)empty='Пока пусто. Нажимайте на сердечко у товаров.';
else if(mode==='cat'&&!qr)empty='Скоро здесь появятся товары.';
$('#grid').innerHTML=a.map(card).join('')||'
'+empty+'
';
}
function route(){
const h=location.hash,m=h.match(/^#\/c\/(\w+)/);
if(m&&C[m[1]]){cat=m[1];mode='cat'}else if(h==='#/fav')mode='fav';else mode='home';
$('#home').hidden=mode!=='home';$('#pg').hidden=mode==='home';
if(mode!=='home'){qr='';$('#pt').textContent=mode==='fav'?'Избранное':C[cat][0];$('#pd').textContent=mode==='fav'?'Товары, которые вы отметили сердечком.':C[cat][1];chips();grid();scrollTo(0,0)}
else{const el=h.length>2&&h[1]!=='/'?document.getElementById(h.slice(1)):null;if(el)setTimeout(()=>el.scrollIntoView(),30);else if(h===''||h==='#/')scrollTo(0,0)}
}
function counts(){const n=Object.keys(cart).reduce((s,i)=>s+(G(i)?cart[i]:0),0);$('#cc').textContent=n;$('#cc').hidden=!n;$('#fc').textContent=fav.size;$('#fc').hidden=!fav.size}
function openM(h){const m=$('#md');m.className='ov';m.innerHTML='
'+h+'
';m.hidden=false;document.body.style.overflow='hidden'}
function closeM(){$('#md').hidden=true;document.body.style.overflow=''}
function prod(id){
const p=G(id);if(!p)return;
openM('
'+pic(p)+'
'+C[p.c][0]+'
'+E(p.n)+'
'+R(p.p)+'
'+(p.d?E(p.d):'Описание скоро появится.')+'
'+(p.s&&p.st!=null?'
В наличии: '+p.st+' шт.
':'')+'
'+(p.s?'':'')+'
');
}
function cartUI(done){
if(done){return openM('
✓
Заявка принята
Заказ № '+E(done.id)+' на сумму '+R(done.total)+'. Менеджер напишет вам в Telegram и подтвердит заказ.
';
const hint=promo?'Промокод '+E(promo.code)+': скидка '+promo.percent+'%':(BOTU?'Промокоды можно получить в нашем Telegram-боте':'Введите промокод, если он у вас есть');
openM('
Ваш заказ
'+list+'
Оформление
'+hint+'
Товары'+R(s)+'
'+(d?'
'+(byLoy?'Скидка по карте '+E(loy.tier):'Промокод')+'−'+R(d)+'
':'')+'
Итого'+R(s-d)+'
');
}
function bump(){counts();const b=$('#cb');b.classList.remove('bp');void b.offsetWidth;b.classList.add('bp')}
function fly(from,k){
if(matchMedia('(prefers-reduced-motion:reduce)').matches||!document.body.animate)return bump();
const s=from.getBoundingClientRect(),d=$('#cb').getBoundingClientRect(),sx=s.left+s.width/2,sy=s.top+s.height/2,dx=d.left+d.width/2-sx,dy=d.top+d.height/2-sy,el=document.createElement('div');
el.className='fly';el.style.cssText='--c:'+C[k][2]+';left:'+sx+'px;top:'+sy+'px';el.innerHTML=svg(k);document.body.appendChild(el);
el.animate([{transform:'translate(0,0) scale(1)',opacity:1},{transform:'translate('+dx*.4+'px,'+(dy*.4-90)+'px) scale(.95)',opacity:1,offset:.45},{transform:'translate('+dx+'px,'+dy+'px) scale(.2)',opacity:.4}],{duration:850,easing:'cubic-bezier(.5,0,.3,1)'}).onfinish=()=>{el.remove();bump()};
}
function load(){
return fetch('/api/products').then(r=>r.json()).then(list=>{
P=list.filter(p=>C[p.cat]).map(p=>({id:String(p.id),c:p.cat,n:p.name,p:p.price,b:p.badge||'',img:p.image||'',d:p.desc||'',s:p.inStock!==false,st:typeof p.stock==='number'?p.stock:null}));
ready=true;failed=false;
for(const i of Object.keys(cart)){const p=G(i);if(!p||!p.s)delete cart[i];else if(p.st!=null&&cart[i]>p.st)cart[i]=p.st}
sv('aer_cart',cart);
}).catch(()=>{if(!ready)failed=true}).then(()=>{counts();if(mode!=='home')grid()});
}
document.addEventListener('click',e=>{
const t=e.target.closest('[data-v],[data-f],[data-a],[data-x],[data-q],#pa,#cb');if(!t||t.disabled)return;
if(t.dataset.v)prod(t.dataset.v);
else if(t.dataset.f){const i=t.dataset.f;fav.has(i)?fav.delete(i):fav.add(i);sv('aer_fav',[...fav]);t.classList.toggle('on');counts();if(mode!=='home')grid()}
else if(t.dataset.a){
const id=t.dataset.a,p=G(id);if(!p||!p.s)return;const cur=cart[id]||0,pr=t.closest('.pr');
if(p.st!=null&&cur+1>p.st){t.textContent='Это всё, что есть';setTimeout(()=>t.textContent='В корзину',1200);return}
cart[id]=cur+1;sv('aer_cart',cart);t.textContent='✓ Добавлено';setTimeout(()=>t.textContent='В корзину',1000);fly(pr?pr.querySelector('.pic'):t,p.c);
}
else if(t.dataset.x!==undefined)closeM();
else if(t.dataset.q){
const k=t.dataset.q.lastIndexOf(':'),i=t.dataset.q.slice(0,k),d=+t.dataset.q.slice(k+1),p=G(i);if(!p)return;
if(d>0&&p.st!=null&&cart[i]+1>p.st)return;
cart[i]+=d;if(cart[i]<1)delete cart[i];sv('aer_cart',cart);counts();cartUI();
}
else if(t.id==='pa'){
if(promo){promo=null;return cartUI()}
const code=$('#pc').value.trim();if(!code)return;t.disabled=true;
fetch('/api/promo',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code})}).then(r=>r.ok?r.json():Promise.reject()).then(j=>{promo={code:j.code,percent:+j.percent||0};cartUI()}).catch(()=>{t.disabled=false;$('#ph').textContent='Такого промокода нет'});
}
else if(t.id==='cb')cartUI();
});
document.addEventListener('input',e=>{if(e.target.id==='sr'){qr=e.target.value;grid()}});
document.addEventListener('submit',e=>{
if(e.target.id!=='fm')return;e.preventDefault();
const f=e.target,b=f.querySelector('button'),er=$('#er'),items=Object.keys(cart).filter(i=>G(i)).map(i=>({id:i,q:cart[i]}));
if(!items.length)return;b.disabled=true;b.textContent='Отправляем…';er.textContent='';
fetch('/api/order',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:f.elements.n.value,tg:f.elements.t.value,comment:f.elements.c.value,promo:promo?promo.code:'',items,initData:INIT})})
.then(r=>r.json().catch(()=>({})).then(j=>{if(!r.ok)throw{st:r.status,j};return j}))
.then(j=>{cart={};promo=null;sv('aer_cart',cart);counts();load();cartUI(j)})
.catch(x=>{
b.disabled=false;b.textContent='Оформить заказ';
const j=(x&&x.j)||{},st=x&&x.st;
if(st===409){er.textContent='Товара «'+(j.name||'')+'» осталось только '+j.left+' шт. Измените количество.';load()}
else if(st===400&&j.error==='item'){er.textContent='Один из товаров закончился. Каталог обновлён, проверьте корзину.';load().then(()=>cartUI())}
else if(st===400)er.textContent='Проверьте имя и Telegram (@username).';
else if(st===429)er.textContent='Слишком часто. Подождите минуту и повторите.';
else er.textContent='Не получилось отправить. Попробуйте ещё раз.';
});
});
addEventListener('keydown',e=>{if(e.key==='Escape')closeM()});
addEventListener('hashchange',route);
$('#md').addEventListener('click',e=>{if(e.target.id==='md')closeM()});
fetch('/api/config').then(r=>r.json()).then(j=>{BOTU=j.bot||''}).catch(()=>{});
if(INIT)fetch('/api/me',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({initData:INIT})}).then(r=>r.json()).then(j=>{if(j&&j.ok&&j.discount>0)loy={discount:+j.discount,tier:j.tier||''}}).catch(()=>{});
load();
const $=s=>document.querySelector(s),R=v=>Number(v).toLocaleString('ru-RU')+' ₽',E=s=>String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const TG=window.Telegram&&Telegram.WebApp;if(TG){try{TG.ready();TG.expand()}catch(e){}}
const INIT=(TG&&TG.initData)||'',TGU=(TG&&TG.initDataUnsafe&&TG.initDataUnsafe.user)||null;
const ART={liquids:'',disp:'',pods:'',parts:''};
const svg=k=>'';
const C={liquids:['Жидкости','Вкус на любой настрой','#8d9bff'],disp:['Одноразки','Достал и пользуйся','#6fc3d9'],pods:['Поды','Компактные устройства','#c98bd0'],parts:['Расходники','Испарители и картриджи','#7fd1a8']};
const ld=k=>{try{return JSON.parse(localStorage.getItem(k)||'null')}catch(e){return null}},sv=(k,v)=>{try{localStorage.setItem(k,JSON.stringify(v))}catch(e){}};
let P=[],ready=false,failed=false,cat='liquids',mode='home',qr='',BOTU='',loy=null,promo=null;
let fav=new Set(ld('aer_fav')||[]),cart=ld('aer_cart')||{};
const G=id=>P.find(x=>x.id===id),col=c=>'style="--c:'+C[c][2]+'"',pic=p=>p.img?'':svg(p.c);
const pct=()=>Math.max(promo?promo.percent:0,loy?loy.discount:0);
function bento(){$('#bento').innerHTML=Object.keys(C).map((k,i)=>'0'+(i+1)+''+svg(k)+'
').join('')}
function chips(){$('#chips').innerHTML=(mode==='cat'?Object.keys(C).map(k=>''+C[k][0]+'').join(''):'')+''}
function card(p){return '
'+E(p.n)+'
'+C[p.c][0]+'
'+R(p.p)+''+(p.s?'':'')+'
'}
function grid(){
const a=P.filter(p=>(mode==='fav'?fav.has(p.id):p.c===cat)&&p.n.toLowerCase().includes(qr.toLowerCase()));
let empty='Ничего не найдено.';
if(!ready)empty=failed?'Не удалось загрузить каталог. Обновите страницу.':'Загрузка…';
else if(mode==='fav'&&!qr)empty='Пока пусто. Нажимайте на сердечко у товаров.';
else if(mode==='cat'&&!qr)empty='Скоро здесь появятся товары.';
$('#grid').innerHTML=a.map(card).join('')||'
'+empty+'
';
}
function route(){
const h=location.hash,m=h.match(/^#\/c\/(\w+)/);
if(m&&C[m[1]]){cat=m[1];mode='cat'}else if(h==='#/fav')mode='fav';else mode='home';
$('#home').hidden=mode!=='home';$('#pg').hidden=mode==='home';
if(mode!=='home'){qr='';$('#pt').textContent=mode==='fav'?'Избранное':C[cat][0];$('#pd').textContent=mode==='fav'?'Товары, которые вы отметили сердечком.':C[cat][1];chips();grid();scrollTo(0,0)}
else{const el=h.length>2&&h[1]!=='/'?document.getElementById(h.slice(1)):null;if(el)setTimeout(()=>el.scrollIntoView(),30);else if(h===''||h==='#/')scrollTo(0,0)}
}
function counts(){const n=Object.keys(cart).reduce((s,i)=>s+(G(i)?cart[i]:0),0);$('#cc').textContent=n;$('#cc').hidden=!n;$('#fc').textContent=fav.size;$('#fc').hidden=!fav.size}
function openM(h){const m=$('#md');m.className='ov';m.innerHTML='
'+h+'
';m.hidden=false;document.body.style.overflow='hidden'}
function closeM(){$('#md').hidden=true;document.body.style.overflow=''}
function prod(id){
const p=G(id);if(!p)return;
openM('
'+pic(p)+'
'+C[p.c][0]+'
'+E(p.n)+'
'+R(p.p)+'
'+(p.d?E(p.d):'Описание скоро появится.')+'
'+(p.s&&p.st!=null?'
В наличии: '+p.st+' шт.
':'')+'
'+(p.s?'':'')+'
');
}
function cartUI(done){
if(done){return openM('
✓
Заявка принята
Заказ № '+E(done.id)+' на сумму '+R(done.total)+'. Менеджер напишет вам в Telegram и подтвердит заказ.
';
const hint=promo?'Промокод '+E(promo.code)+': скидка '+promo.percent+'%':(BOTU?'Промокоды можно получить в нашем Telegram-боте':'Введите промокод, если он у вас есть');
openM('
Ваш заказ
'+list+'
Оформление
'+hint+'
Товары'+R(s)+'
'+(d?'
'+(byLoy?'Скидка по карте '+E(loy.tier):'Промокод')+'−'+R(d)+'
':'')+'
Итого'+R(s-d)+'
');
}
function bump(){counts();const b=$('#cb');b.classList.remove('bp');void b.offsetWidth;b.classList.add('bp')}
function fly(from,k){
if(matchMedia('(prefers-reduced-motion:reduce)').matches||!document.body.animate)return bump();
const s=from.getBoundingClientRect(),d=$('#cb').getBoundingClientRect(),sx=s.left+s.width/2,sy=s.top+s.height/2,dx=d.left+d.width/2-sx,dy=d.top+d.height/2-sy,el=document.createElement('div');
el.className='fly';el.style.cssText='--c:'+C[k][2]+';left:'+sx+'px;top:'+sy+'px';el.innerHTML=svg(k);document.body.appendChild(el);
el.animate([{transform:'translate(0,0) scale(1)',opacity:1},{transform:'translate('+dx*.4+'px,'+(dy*.4-90)+'px) scale(.95)',opacity:1,offset:.45},{transform:'translate('+dx+'px,'+dy+'px) scale(.2)',opacity:.4}],{duration:850,easing:'cubic-bezier(.5,0,.3,1)'}).onfinish=()=>{el.remove();bump()};
}
function load(){
return fetch('/api/products').then(r=>r.json()).then(list=>{
P=list.filter(p=>C[p.cat]).map(p=>({id:String(p.id),c:p.cat,n:p.name,p:p.price,b:p.badge||'',img:p.image||'',d:p.desc||'',s:p.inStock!==false,st:typeof p.stock==='number'?p.stock:null}));
ready=true;failed=false;
for(const i of Object.keys(cart)){const p=G(i);if(!p||!p.s)delete cart[i];else if(p.st!=null&&cart[i]>p.st)cart[i]=p.st}
sv('aer_cart',cart);
}).catch(()=>{if(!ready)failed=true}).then(()=>{counts();if(mode!=='home')grid()});
}
document.addEventListener('click',e=>{
const t=e.target.closest('[data-v],[data-f],[data-a],[data-x],[data-q],#pa,#cb');if(!t||t.disabled)return;
if(t.dataset.v)prod(t.dataset.v);
else if(t.dataset.f){const i=t.dataset.f;fav.has(i)?fav.delete(i):fav.add(i);sv('aer_fav',[...fav]);t.classList.toggle('on');counts();if(mode!=='home')grid()}
else if(t.dataset.a){
const id=t.dataset.a,p=G(id);if(!p||!p.s)return;const cur=cart[id]||0,pr=t.closest('.pr');
if(p.st!=null&&cur+1>p.st){t.textContent='Это всё, что есть';setTimeout(()=>t.textContent='В корзину',1200);return}
cart[id]=cur+1;sv('aer_cart',cart);t.textContent='✓ Добавлено';setTimeout(()=>t.textContent='В корзину',1000);fly(pr?pr.querySelector('.pic'):t,p.c);
}
else if(t.dataset.x!==undefined)closeM();
else if(t.dataset.q){
const k=t.dataset.q.lastIndexOf(':'),i=t.dataset.q.slice(0,k),d=+t.dataset.q.slice(k+1),p=G(i);if(!p)return;
if(d>0&&p.st!=null&&cart[i]+1>p.st)return;
cart[i]+=d;if(cart[i]<1)delete cart[i];sv('aer_cart',cart);counts();cartUI();
}
else if(t.id==='pa'){
if(promo){promo=null;return cartUI()}
const code=$('#pc').value.trim();if(!code)return;t.disabled=true;
fetch('/api/promo',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code})}).then(r=>r.ok?r.json():Promise.reject()).then(j=>{promo={code:j.code,percent:+j.percent||0};cartUI()}).catch(()=>{t.disabled=false;$('#ph').textContent='Такого промокода нет'});
}
else if(t.id==='cb')cartUI();
});
document.addEventListener('input',e=>{if(e.target.id==='sr'){qr=e.target.value;grid()}});
document.addEventListener('submit',e=>{
if(e.target.id!=='fm')return;e.preventDefault();
const f=e.target,b=f.querySelector('button'),er=$('#er'),items=Object.keys(cart).filter(i=>G(i)).map(i=>({id:i,q:cart[i]}));
if(!items.length)return;b.disabled=true;b.textContent='Отправляем…';er.textContent='';
fetch('/api/order',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:f.elements.n.value,tg:f.elements.t.value,comment:f.elements.c.value,promo:promo?promo.code:'',items,initData:INIT})})
.then(r=>r.json().catch(()=>({})).then(j=>{if(!r.ok)throw{st:r.status,j};return j}))
.then(j=>{cart={};promo=null;sv('aer_cart',cart);counts();load();cartUI(j)})
.catch(x=>{
b.disabled=false;b.textContent='Оформить заказ';
const j=(x&&x.j)||{},st=x&&x.st;
if(st===409){er.textContent='Товара «'+(j.name||'')+'» осталось только '+j.left+' шт. Измените количество.';load()}
else if(st===400&&j.error==='item'){er.textContent='Один из товаров закончился. Каталог обновлён, проверьте корзину.';load().then(()=>cartUI())}
else if(st===400)er.textContent='Проверьте имя и Telegram (@username).';
else if(st===429)er.textContent='Слишком часто. Подождите минуту и повторите.';
else er.textContent='Не получилось отправить. Попробуйте ещё раз.';
});
});
addEventListener('keydown',e=>{if(e.key==='Escape')closeM()});
addEventListener('hashchange',route);
$('#md').addEventListener('click',e=>{if(e.target.id==='md')closeM()});
fetch('/api/config').then(r=>r.json()).then(j=>{BOTU=j.bot||''}).catch(()=>{});
if(INIT)fetch('/api/me',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({initData:INIT})}).then(r=>r.json()).then(j=>{if(j&&j.ok&&j.discount>0)loy={discount:+j.discount,tier:j.tier||''}}).catch(()=>{});
load();