import {typeNames,categories,amount,offerTitle,wager,filterOffers,groupOffers,turnover,plNoun} from './core.mjs'; const $=(s,r=document)=>r.querySelector(s),$$=(s,r=document)=>[...r.querySelectorAll(s)]; const esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const smallIcon=k=>k==='heart'?'':'⇄'; let toastTimer;function toast(s){const el=$('.toast');el.textContent=s;el.hidden=false;clearTimeout(toastTimer);toastTimer=setTimeout(()=>el.hidden=true,3500);} $('.menu-toggle')?.addEventListener('click',e=>{const b=e.currentTarget;const open=b.getAttribute('aria-expanded')!=='true';b.setAttribute('aria-expanded',String(open));$('#main-nav').classList.toggle('open',open);}); document.addEventListener('keydown',e=>{if(e.key==='Escape'){$('#main-nav')?.classList.remove('open');$('.menu-toggle')?.setAttribute('aria-expanded','false');}}); $('.mobile-filter-toggle')?.addEventListener('click',e=>{const open=$('#filters').classList.toggle('open');e.currentTarget.setAttribute('aria-expanded',String(open));}); $('[data-close-filters]')?.addEventListener('click',()=>{$('#filters').classList.remove('open');$('.mobile-filter-toggle').setAttribute('aria-expanded','false');$('.mobile-filter-toggle').focus({preventScroll:true});$('#offer-list').scrollIntoView({block:'start'});}); const categoryNav=$('.category-nav'),currentCategory=categoryNav?.querySelector('[aria-current]');if(currentCategory)categoryNav.scrollLeft=Math.max(0,currentCategory.offsetLeft-16); let catalog; try{const res=await fetch('/assets/js/catalog.json');if(!res.ok)throw Error('load');catalog=await res.json();}catch(error){toast('Narzędzia chwilowo niedostępne. Możesz nadal przeglądać strony katalogu.');throw error;} const {offers,brands,config}=catalog,byId=new Map(offers.map(o=>[o.id,o])),brandById=new Map(brands.map(b=>[b.id,b])); const mode=document.body.dataset.mode,cat=document.body.dataset.category,brandId=document.body.dataset.brand; const roots={all:'/',nodep:'/bonusy/bez-depozytu/',spins:'/bonusy/bez-depozytu/darmowe-spiny/',cash:'/bonusy/bez-depozytu/pieniadze/',welcome:'/bonusy/powitalne/',deposit:'/bonusy/depozytowe/',app:'/bonusy/w-aplikacji/'}; function readStore(key){try{let x=JSON.parse(localStorage.getItem(key)||'[]');return Array.isArray(x)?x.filter(id=>byId.has(id)):[];}catch{return [];}} let selected=readStore('bonusatlas.compare').slice(0,4),favorites=readStore('bonusatlas.favorites'),compareMode='bonus'; function save(){try{localStorage.setItem('bonusatlas.compare',JSON.stringify(selected));localStorage.setItem('bonusatlas.favorites',JSON.stringify(favorites));}catch{toast('Przeglądarka blokuje zapis. Wybory działają w tej karcie.');}updateControls();} function updateControls(){ $$('[data-compare-count]').forEach(el=>{el.textContent=selected.length;el.hidden=!selected.length;}); $$('[data-fav-count]').forEach(el=>{el.textContent=favorites.length;el.hidden=!favorites.length;}); const dock=$('.compare-dock');dock.hidden=!selected.length||mode==='compare';$('[data-dock-count]').textContent=selected.length; $$('[data-compare]').forEach(b=>{const checked=selected.includes(b.dataset.compare);b.setAttribute('aria-pressed',String(checked));b.textContent=checked?'✓ Dodano':'⇄ Porównaj';b.classList.toggle('btn-lime',checked);}); $$('[data-favorite]').forEach(b=>{const checked=favorites.includes(b.dataset.favorite);b.setAttribute('aria-pressed',String(checked));b.classList.toggle('active',checked);b.setAttribute('aria-label',(checked?'Usuń z zapisanych: ':'Zapisz bonus: ')+(byId.get(b.dataset.favorite)?.brand||''));}); } const w=n=>n===0?'Bez obrotu':n==null?'Nie podano':'×'+n; const days=n=>n==null?'Nie podano':n+' dni'; const external=o=>o.outboundUrl||''; const detail=o=>catalog.offerDetails[o.id]||''; function card(group,id){const o=group.find(x=>x.id===id)||group[0],b=brandById.get(o.brandId),href=external(o); const variants=group.length>1?``:''; return `
${esc(b.name)}

${esc(b.name)}

Bonusy tej marki
${typeNames[o.type]}
${esc(offerTitle(o))}
Min. depozyt${o.type.startsWith('nd-')?'Bez wpłaty':esc(amount(o.deposit,o.currency))}
${o.wagerCash==null?'Obrót FS':'Obrót bonusu'}${w(o.wagerCash??o.wagerFs)}
${variants}
${href?`Sprawdź ofertę →`:'Brak odnośnika do oferty'}
${detail(o)}
`; } function empty(message='Żaden bonus nie spełnia wszystkich kryteriów.'){return `

Brak wyników

${esc(message)}

`;} async function copy(text){try{await navigator.clipboard.writeText(text);toast('Skopiowano do schowka.');}catch{const ta=document.createElement('textarea');ta.value=text;ta.style.position='fixed';ta.style.opacity='0';document.body.append(ta);ta.select();const success=document.execCommand('copy');ta.remove();toast(success?'Skopiowano do schowka.':'Nie udało się skopiować. Zaznacz tekst ręcznie.');}} document.addEventListener('click',e=>{ const cb=e.target.closest('[data-compare]');if(cb){const id=cb.dataset.compare;if(selected.includes(id)){selected=selected.filter(x=>x!==id);toast('Usunięto z porównania.');}else if(selected.length<4){selected.push(id);toast('Dodano do porównania.');}else{toast('Możesz porównać maksymalnie 4 oferty.');}save();if(mode==='compare')renderComparison();} const fb=e.target.closest('[data-favorite]');if(fb){const id=fb.dataset.favorite;favorites=favorites.includes(id)?favorites.filter(x=>x!==id):[...favorites,id];save();if(mode==='favorites')renderFavorites();} const copyb=e.target.closest('[data-copy]');if(copyb)copy(copyb.dataset.copy); if(e.target.closest('[data-clear-compare]')){selected=[];save();if(mode==='compare')renderComparison();} if(e.target.closest('[data-reset-filters]')){if($('#filters')){$('#filters').reset();history.replaceState(null,'',location.pathname);renderCatalog(true);}else if(mode==='finder'){$('#finder-form').reset();runFinder();}} }); document.addEventListener('change',e=>{if(e.target.matches('[data-variants]')){const s=e.target,id=s.value,g=s.dataset.variants.split(',').map(x=>byId.get(x)).filter(Boolean);s.closest('.offer-card').outerHTML=card(g,id);updateControls();}}); let visibleCount=20,initialPage=Number(document.body.dataset.page)||1; function formFilters(){const f=Object.fromEntries(new FormData($('#filters')));f.category=cat;return f;} function hasFilters(f){return Object.entries(f).some(([k,v])=>k!=='category'&&v);} function renderCatalog(reset=false){ const f=formFilters(),sort=$('#sort').value;if(reset)visibleCount=20; const result=filterOffers(offers,f),gs=groupOffers(result,sort),filtered=hasFilters(f)||sort!=='name'; const offset=filtered?0:(initialPage-1)*20,shown=gs.slice(offset,filtered?visibleCount:offset+20); $('#offer-list').innerHTML=shown.length?shown.map(g=>card(g)).join(''):empty();$('.results-count').innerHTML=`${gs.length} ${plNoun(gs.length,'kasyno','kasyna','kasyn')} · ${result.length} ${plNoun(result.length,'oferta','oferty','ofert')}`; const chips=Object.entries(f).filter(([k,v])=>k!=='category'&&v).map(([k,v])=>{const input=$(`[name="${k}"]`,$('#filters')),label=input?.selectedOptions?.[0]?.textContent||v;return ``;});$('.active-filters').innerHTML=chips.join(''); const pager=$('#pagination');if(filtered){pager.innerHTML=gs.length>visibleCount?'':'';}else{const total=Math.ceil(gs.length/20);pager.innerHTML=total>1?'':'';}updateControls(); } function hashFilters(){const p=new URLSearchParams(location.hash.slice(1));for(const input of $('#filters').elements){if(input.name)input.value=p.get(input.name)||'';}$('#sort').value=p.get('sort')||'name';} function writeFilters(){let f=formFilters();delete f.category;if($('#sort').value!=='name')f.sort=$('#sort').value;const p=new URLSearchParams(Object.entries(f).filter(([,v])=>v));history.replaceState(null,'',location.pathname+(p.size?'#'+p.toString():''));} if(mode==='catalog'){ hashFilters();if(location.hash)renderCatalog(true); $('#filters').addEventListener('submit',e=>e.preventDefault()); let timeout;document.addEventListener('input',e=>{if(e.target.form!==$('#filters'))return;clearTimeout(timeout);timeout=setTimeout(()=>{writeFilters();renderCatalog(true);},120);}); $('#filters').addEventListener('reset',()=>setTimeout(()=>{$('#sort').value='name';writeFilters();renderCatalog(true);},0)); $('#sort').addEventListener('change',()=>{writeFilters();renderCatalog(true);}); window.addEventListener('hashchange',()=>{hashFilters();renderCatalog(true);}); document.addEventListener('click',e=>{if(e.target.closest('[data-more-results]')){visibleCount+=20;renderCatalog();}const x=e.target.closest('[data-remove-filter]');if(x){$('#filters').elements.namedItem(x.dataset.removeFilter).value='';writeFilters();renderCatalog(true);}}); } if(mode==='brand'){$$('[data-brand-type]').forEach(b=>b.addEventListener('click',()=>{$$('[data-brand-type]').forEach(x=>x.classList.toggle('active',x===b));const os=offers.filter(o=>o.brandId===brandId&&(!b.dataset.brandType||o.type===b.dataset.brandType));$('#offer-list').innerHTML=os.map(o=>card([o])).join('');updateControls();}));} if(mode==='brands'){$('#brand-search').addEventListener('input',e=>{const q=e.target.value.trim().toLowerCase(),bs=brands.filter(b=>b.name.toLowerCase().includes(q));$('#brand-grid').innerHTML=bs.map(b=>`${esc(b.name)}

${esc(b.name)}

${b.offerIds.length} ${plNoun(b.offerIds.length,'oferta','oferty','ofert')} · warunki i warianty →

`).join('')||'

Brak marek o takiej nazwie.

';$('#brand-count').textContent=bs.length+' '+plNoun(bs.length,'marka','marki','marek');$('#brand-pagination').hidden=true;});} function renderFavorites(){const os=favorites.map(id=>byId.get(id)).filter(Boolean);$('#favorites-list').innerHTML=os.length?os.map(o=>card([o])).join(''):'

Nie masz zapisanych bonusów

Zaznacz serduszko przy wybranym wariancie. Później możesz go usunąć albo dodać do porównania.

Przeglądaj bonusy
';updateControls();} if(mode==='favorites')renderFavorites(); function updateCompareOfferSelect(){const bid=$('#compare-brand').value,sel=$('#compare-offer');const os=offers.filter(o=>o.brandId===bid);sel.innerHTML=os.length?os.map(o=>``).join(''):'';sel.disabled=!os.length;} function renderComparison(){ if(compareMode==='brand'){const seen=new Set();selected=selected.filter(id=>{const b=byId.get(id)?.brandId;if(!b||seen.has(b))return false;seen.add(b);return true;});} const os=selected.map(id=>byId.get(id)).filter(Boolean),root=$('#comparison'); if(!os.length){root.innerHTML='

Co chcesz porównać?

Wybierz kasyno i ofertę powyżej albo dodaj bonusy z listingu.

Przejdź do bonusów
';history.replaceState(null,'',location.pathname);save();return;} const rows=[['Rodzaj',o=>typeNames[o.type]],['Kwota bonusu',o=>amount(o.cash,o.currency)],['Darmowe spiny',o=>o.fs==null?'Nie podano':o.fs+' FS'],['Procent bonusu',o=>o.percent==null?'Nie podano':o.percent+'%'],['Minimalny depozyt',o=>o.type.startsWith('nd-')?'Bez wpłaty':amount(o.deposit,o.currency)],['Obrót części pieniężnej',o=>w(o.wagerCash)],['Obrót wygranych z FS',o=>w(o.wagerFs)],['Podstawa obrotu',()=> 'Sprawdź regulamin'],['Termin: pieniądze',o=>days(o.daysCash)],['Termin: spiny',o=>days(o.daysFs)],['Maksymalna wypłata',o=>catalog.offerLimits[o.id]||'Nie podano'],['Gra',o=>o.slot||'Nie podano'],['Kod',o=>o.code||'Nie podano'],['Etapy welcome',o=>o.depositStages?.length?o.depositStages.join(' '):o.type==='welcome'?'Sprawdź podział w regulaminie':'Nie dotyczy']]; if(compareMode==='brand')rows.unshift(['Bonusy tej marki marki',o=>String(brandById.get(o.brandId).offerIds.length)],['Dostępne rodzaje',o=>[...new Set(offers.filter(x=>x.brandId===o.brandId).map(x=>typeNames[x.type]))].join(', ')]); const only=$('#differences').checked; const rowhtml=rows.map(([label,fn])=>{const vs=os.map(fn),diff=new Set(vs).size>1;if(only&&!diff)return '';return `${label}${vs.map(v=>`${esc(v)}`).join('')}`;}).join(''); root.innerHTML=`
${os.map(o=>``).join('')}${rowhtml||``}${os.map(o=>``).join('')}
Porównanie ${compareMode==='brand'?'kasyn według ofert':'bonusów'}
Warunek${esc(o.brand)}${esc(offerTitle(o))}${compareMode==='brand'?``:''}
Brak różnic w pokazanych parametrach. Nie oznacza to identycznych pełnych regulaminów.
Pełne warunki${external(o)?`Sprawdź ofertę ↗`:'Nie podano'}

Zielone wiersze wskazują różnice, nie lepszą ofertę. Brak danych nie oznacza braku ograniczenia. Sprawdź pełne warunki wybranego wariantu.

`; history.replaceState(null,'',location.pathname+'#'+new URLSearchParams({bonus:selected.join(','),mode:compareMode}).toString());save(); } if(mode==='compare'){ const hash=new URLSearchParams(location.hash.slice(1));if(hash.get('bonus'))selected=[...new Set(hash.get('bonus').split(',').filter(id=>byId.has(id)))].slice(0,4); if(hash.get('mode')==='brand')compareMode='brand'; if(hash.get('brand')&&brandById.has(hash.get('brand'))){$('#compare-brand').value=hash.get('brand');compareMode='brand';const os=offers.filter(o=>o.brandId===hash.get('brand'));if(!selected.some(id=>byId.get(id).brandId===hash.get('brand'))&&selected.length<4)selected.push((os.find(o=>o.type==='welcome')||os[0]).id);} updateCompareOfferSelect();$('#compare-brand').addEventListener('change',updateCompareOfferSelect); $('#compare-add').addEventListener('submit',e=>{e.preventDefault();const id=$('#compare-offer').value;if(!byId.has(id))return;if(compareMode==='brand'){selected=selected.filter(x=>byId.get(x).brandId!==byId.get(id).brandId);}if(selected.includes(id)){toast('Ta oferta jest już w porównaniu.');return;}if(selected.length===4){toast('Usuń jedną ofertę, aby dodać kolejną.');return;}selected.push(id);renderComparison();}); $('#differences').addEventListener('change',renderComparison); $$('[data-compare-mode]').forEach(b=>{b.classList.toggle('active',b.dataset.compareMode===compareMode);b.addEventListener('click',()=>{compareMode=b.dataset.compareMode;$$('[data-compare-mode]').forEach(x=>x.classList.toggle('active',x===b));renderComparison();});}); document.addEventListener('click',e=>{const rem=e.target.closest('[data-remove-comparison]');if(rem){selected=selected.filter(x=>x!==rem.dataset.removeComparison);renderComparison();}if(e.target.closest('[data-share-compare]'))copy(location.href);}); document.addEventListener('change',e=>{if(e.target.matches('[data-swap-brand]')){selected=selected.map(x=>x===e.target.dataset.swapBrand?e.target.value:x);renderComparison();}}); renderComparison(); } if(mode==='calculator'){ const form=$('#calc-form');function compute(){if(!form.checkValidity()){$('#calc-result').textContent='—';$('#calc-formula').textContent='';$('#calc-target').textContent='';$('#calc-error').textContent='Sprawdź wartości. Udział stawek musi być większy niż 0 i nie większy niż 100%.';return;}try{const f=Object.fromEntries(new FormData(form)),r=turnover(f);$('#calc-error').textContent='';$('#calc-result').textContent=amount(r.stakes);$('#calc-target').textContent='Wymagany obrót kwalifikowany: '+amount(r.target);$('#calc-formula').textContent=(f.base==='both'?'('+amount(Number(f.deposit))+' + '+amount(Number(f.bonus))+')':amount(Number(f.bonus)))+' × '+f.multiplier+' ÷ '+f.contribution+'% = '+amount(r.stakes);}catch(e){$('#calc-error').textContent=e.message;}} form.addEventListener('submit',e=>{e.preventDefault();compute();});form.addEventListener('input',compute); } let finderCount=20; function runFinder(reset=true){if(reset)finderCount=20;const f=Object.fromEntries(new FormData($('#finder-form'))),os=filterOffers(offers,f),gs=groupOffers(os);$('#finder-count').textContent=gs.length+' '+plNoun(gs.length,'kasyno','kasyna','kasyn')+' · '+os.length+' '+plNoun(os.length,'oferta','oferty','ofert');$('#finder-results').innerHTML=gs.length?gs.slice(0,finderCount).map(g=>card(g)).join('')+(gs.length>finderCount?'':''):empty();updateControls();} if(mode==='finder'){$('#finder-form').addEventListener('submit',e=>{e.preventDefault();runFinder();});document.addEventListener('click',e=>{if(e.target.closest('[data-finder-more]')){finderCount+=20;runFinder(false);}});} updateControls();