1/** 2 * ============================================================ 3 * GORZÃW KONTA - front-end paska logowania/rejestracji + modala. 4 * Plik identyczny w obu wtyczkach (assets/js/konta.js). DziaÅa 5 * niezależnie od script.js każdej wtyczki - po zalogowaniu blokuje 6 * (na zielono, jak dotychczasowe "rozpoznanie po IP") wszystkie pola 7 * "nick zgÅaszajÄ cego" znalezione na stronie, wiÄc nie trzeba nic 8 * zmieniaÄ w istniejÄ cej logice formularzy. 9 * ============================================================ 10 */ 11(function () { 12 'use strict'; 13 if (typeof gorzowKontaConfig === 'undefined') return; 14 if (window.__gorzowKontaZaladowany) return; // dwie wtyczki na stronie = dwa <div id="gk-pasek-konta"> mogÄ istnieÄ, ale skrypt inicjujemy raz na kontener 15 window.__gorzowKontaZaladowany = true; 16 17 const cfg = gorzowKontaConfig; 18 let stanKonta = null; // { zalogowany, login, nick, email } | null (jeszcze nie sprawdzono) 19 let captchaDane = null; 20 21 function escHtml(t) { 22 const d = document.createElement('div'); 23 d.textContent = String(t == null ? '' : t); 24 return d.innerHTML; 25 } 26 27 // Strony z pamiÄci podrÄcznej majÄ stary nonce - pobieramy Åwieży i 28 // przekazujemy go też wtyczkom (Mapa Miejsc, Ceny Paliw). 29 async function odswiezNonce() { 30 if (!cfg.restUrlNonce) return false; 31 try { 32 const r = await fetch(cfg.restUrlNonce, { credentials: 'same-origin', cache: 'no-store' }); 33 if (!r.ok) return false; 34 const j = await r.json(); 35 if (!j || !j.nonce) return false; 36 cfg.nonce = j.nonce; 37 ['gorzowMiejscaConfig', 'gorzowPaliwaConfig'].forEach((n) => { if (window[n]) window[n].nonce = j.nonce; }); 38 return true; 39 } catch (e) { 40 return false; 41 } 42 } 43 44 async function postJson(url, dane, ponowienie) { 45 const body = new URLSearchParams(Object.assign({ tf: cfg.czasFormularza, www: '' }, dane)); 46 let resp; 47 try { 48 resp = await fetch(url, { 49 method: 'POST', 50 credentials: 'same-origin', 51 headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-WP-Nonce': cfg.nonce }, 52 body: body.toString(), 53 }); 54 } catch (e) { 55 return { ok: false, dane: { message: 'Brak poÅÄ czenia z internetem - spróbuj ponownie.' } }; 56 } 57 let j = {}; 58 try { j = await resp.json(); } catch (e) { /* noop */ } 59 const zlyNonce = resp.status === 403 && j && /zla_autoryzacja|invalid_nonce/.test(String(j.code || '')); 60 if (zlyNonce && !ponowienie && await odswiezNonce()) { 61 return postJson(url, dane, true); 62 } 63 if (resp.status === 429 && !j.message) j.message = 'Zbyt wiele prób - odczekaj chwilÄ.'; 64 return { ok: resp.ok, dane: j }; 65 } 66 67 async function pobierzStanKonta() { 68 try { 69 const resp = await fetch(cfg.restUrlJa, { headers: { 'X-WP-Nonce': cfg.nonce } }); 70 stanKonta = await resp.json(); 71 } catch (e) { 72 stanKonta = { zalogowany: false }; 73 } 74 window.gorzowKontaStan = stanKonta; 75 try { document.dispatchEvent(new CustomEvent('gorzow-konta:stan', { detail: stanKonta })); } catch (e) { /* noop */ } 76 return stanKonta; 77 } 78 79 function zamknijMenuKonta() { 80 document.querySelectorAll('.gk-konto-menu').forEach((m) => { m.hidden = true; }); 81 document.querySelectorAll('.gk-konto-przycisk').forEach((b) => b.setAttribute('aria-expanded', 'false')); 82 } 83 84 /** 85 * Wspólne okno "Czy na pewno?" (używajÄ go też wtyczki, np. przy 86 * usuwaniu danych powiÄ zanych z urzÄ dzeniem). Zwraca Promise<boolean>. 87 * Przycisk potwierdzenia jest aktywny dopiero po zaznaczeniu 88 * "Rozumiem, że tego nie da siÄ cofnÄ Ä". 89 */ 90 function potwierdz(opcje) { 91 const o = opcje || {}; 92 return new Promise((resolve) => { 93 otworzModal(` 94 <button type="button" class="gk-modal-zamknij" data-gk-potwierdz="nie" aria-label="Zamknij"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></button> 95 <div class="gk-potwierdz-ikona" aria-hidden="true">â ï¸</div> 96 <h2>${escHtml(o.tytul || 'Na pewno?')}</h2> 97 <p class="gk-modal-info">${escHtml(o.tresc || '')}</p> 98 <label class="gk-potwierdz-check"><input type="checkbox" id="gk-potwierdz-check"> Rozumiem, że tej operacji nie da siÄ cofnÄ Ä</label> 99 <div class="gk-potwierdz-przyciski"> 100 <button type="button" class="gk-pasek-btn" data-gk-potwierdz="nie">Anuluj</button> 101 <button type="button" class="gk-modal-submit gk-modal-submit-usun" data-gk-potwierdz="tak" disabled>${escHtml(o.przycisk || 'Tak, usuÅ')}</button> 102 </div>`); 103 const modal = document.getElementById('gk-modal-konto'); 104 const check = document.getElementById('gk-potwierdz-check'); 105 const tak = modal.querySelector('[data-gk-potwierdz="tak"]'); 106 check.addEventListener('change', () => { tak.disabled = !check.checked; });
107 const koniec = (wynik) => { 108 modal.removeEventListener('click', klik); 109 zamknijModal(); 110 resolve(wynik); 111 }; 112 const klik = (e) => { 113 const b = e.target.closest('[data-gk-potwierdz]'); 114 if (!b || b.disabled) return; 115 koniec(b.dataset.gkPotwierdz === 'tak'); 116 }; 117 modal.addEventListener('click', klik); 118 }); 119 } 120 window.gorzowKonta = { potwierdz: potwierdz }; 121 122 function renderUsuniecie() { 123 otworzModal(` 124 <button type="button" class="gk-modal-zamknij" data-gk="zamknij" aria-label="Zamknij"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></button> 125 <div class="gk-potwierdz-ikona" aria-hidden="true">â ï¸</div> 126 <h2>UsunÄ Ä konto?</h2> 127 <p class="gk-modal-info">Konto <strong>${escHtml(stanKonta && stanKonta.nick || '')}</strong> zostanie usuniÄte w Mapie Miejsc i w Cenach Paliw. Dla bezpieczeÅstwa wyÅlemy Ci e-mail z linkiem - konto zniknie dopiero po jego klikniÄciu.</p> 128 <div class="gk-modal-status" id="gk-modal-status"></div> 129 <form id="gk-form-usun"> 130 <label class="gk-modal-pole">Twoje aktualne hasÅo<input type="password" name="haslo" required autocomplete="current-password"></label> 131 <label class="gk-potwierdz-check"><input type="checkbox" name="rozumiem" id="gk-usun-check"> Na pewno chcÄ usunÄ Ä konto - rozumiem, że tego nie da siÄ cofnÄ Ä</label> 132 <div class="gk-honeypot" aria-hidden="true"><label>Zostaw puste<input type="text" name="www" tabindex="-1" autocomplete="off"></label></div> 133 <div class="gk-potwierdz-przyciski"> 134 <button type="button" class="gk-pasek-btn" data-gk="zamknij">Anuluj</button> 135 <button type="submit" class="gk-modal-submit gk-modal-submit-usun" disabled>UsuÅ konto</button> 136 </div> 137 </form>`); 138 const form = document.getElementById('gk-form-usun'); 139 const przycisk = form.querySelector('button[type="submit"]'); 140 const odswiez = () => { przycisk.disabled = !(form.rozumiem.checked && form.haslo.value.length > 0); }; 141 form.addEventListener('input', odswiez); 142 form.addEventListener('change', odswiez); 143 form.addEventListener('submit', async (e) => { 144 e.preventDefault(); 145 if (przycisk.disabled) return; 146 const status = document.getElementById('gk-modal-status'); 147 status.className = 'gk-modal-status'; 148 status.textContent = 'WysyÅanieâ¦'; 149 przycisk.disabled = true; 150 const { ok, dane } = await postJson(cfg.restUrlUsunZadaj, { haslo: form.haslo.value }); 151 status.className = ok ? 'gk-modal-status gk-modal-status-ok' : 'gk-modal-status gk-modal-status-blad'; 152 status.textContent = dane.wiadomosc || dane.message || (ok ? 'Sprawdź e-mail.' : 'Nie udaÅo siÄ.'); 153 if (ok) form.querySelectorAll('input').forEach((i) => { i.disabled = true; }); 154 else odswiez(); 155 }); 156 } 157 158 /** Blokuje (nick + odznaka konta) wszystkie znane pola nicku na stronie. */ 159 function zablokujPolaNickiem(nick) { 160 document.querySelectorAll('input[name="zglaszajacy"], input[name="autor"]').forEach((pole) => { 161 pole.value = nick; 162 pole.readOnly = true; 163 pole.classList.add('gm-pole-zablokowane', 'gp-pole-zablokowane'); 164 }); 165 document.querySelectorAll('[id$="-moj-nick-formularz"], [id*="-moj-nick-opinia-"]').forEach((box) => { 166 if (box.dataset.gkWypelnione) return; // nie nadpisuj, jeÅli wÅasna wtyczka już coÅ tam wstawiÅa (odznaki itp.) 167 }); 168 } 169 170 // Po zalogowaniu piguÅka konta trafia do prawego rogu nagÅówka wtyczki 171 // (Mapa miejsc / Ceny paliw), zamiast zajmowaÄ osobny, pusty wiersz. 172 function przeniesDoNaglowka(pasek) { 173 let naglowek = pasek.previousElementSibling; 174 if (pasek.parentElement && pasek.parentElement.matches('.gm-header, .gp-header')) naglowek = pasek.parentElement; 175 if (!naglowek || !naglowek.matches('.gm-header, .gp-header')) return; 176 naglowek.classList.add('gk-naglowek-z-kontem'); 177 pasek.classList.add('gk-pasek-w-naglowku'); 178 if (pasek.parentElement !== naglowek) naglowek.appendChild(pasek); 179 } 180 181 function renderPasek() { 182 const pasek = document.getElementById('gk-pasek-konta'); 183 if (!pasek) return; 184 if (!stanKonta) { 185 pasek.innerHTML = '<span class="gk-pasek-ladowanie">â¦</span>'; 186 return; 187 } 188 if (stanKonta.zalogowany) { 189 // Po zalogowaniu: zamiast paska z logowaniem - maÅe menu konta 190 // (Wyloguj / UsuÅ konto). "UsuÅ konto" jest schowane w menu 191 // i wymaga potwierdzenia. 192 const nick = String(stanKonta.nick || ''); 193 pasek.classList.add('gk-pasek-zalogowany'); 194 pasek.innerHTML = `<div class="gk-konto"> 195 <button type="button" class="gk-konto-przycisk" data-gk="menu" aria-haspopup="true" aria-expanded="false"> 196 ${htmlAwatara(nick)}
197 <span class="gk-konto-nick">${escHtml(nick)}</span> 198 <svg class="gk-konto-strzalka" viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg> 199 </button> 200 <div class="gk-konto-menu" role="menu" hidden> 201 <div class="gk-konto-menu-glowa"> 202 ${htmlAwatara(nick)} 203 <span><small>Zalogowano jako</small><strong>${escHtml(nick)}</strong>${stanKonta.email ? `<em>${escHtml(stanKonta.email)}</em>` : ''}</span> 204 </div> 205 <button type="button" class="gk-konto-pozycja" role="menuitem" data-gk="awatar"><span class="gk-konto-pozycja-ikona" aria-hidden="true">ð·</span>${stanKonta.awatar ? 'ZmieÅ zdjÄcie profilowe' : 'Dodaj zdjÄcie profilowe'}</button> 206 ${(window.gorzowKontaMenu || []).map((d) => `<button type="button" class="gk-konto-pozycja" role="menuitem" data-gk-dodatek="${escHtml(d.id)}"><span class="gk-konto-pozycja-ikona" aria-hidden="true">${escHtml(d.ikona || 'â¢')}</span>${escHtml(d.etykieta)}</button>`).join('')} 207 ${(window.gorzowKontaMenu || []).length ? '<div class="gk-konto-menu-kreska"></div>' : ''} 208 <button type="button" class="gk-konto-pozycja" role="menuitem" data-gk="wyloguj"> 209 <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden="true"><path d="M8 4H5a1 1 0 00-1 1v10a1 1 0 001 1h3M13 13.5L16.5 10 13 6.5M16.5 10H8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg> 210 Wyloguj siÄ 211 </button> 212 <div class="gk-konto-menu-kreska"></div> 213 <button type="button" class="gk-konto-pozycja gk-konto-pozycja-usun" role="menuitem" data-gk="usun"> 214 <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden="true"><path d="M4.5 6h11M8 6V4.5h4V6M6 6l.7 9.5a1 1 0 001 .9h4.6a1 1 0 001-.9L14 6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg> 215 UsuÅ konto⦠216 </button> 217 </div> 218 </div>`; 219 zablokujPolaNickiem(stanKonta.nick); 220 przeniesDoNaglowka(pasek); 221 } else { 222 // Bez logowania: zamiast dużej belki - maÅa, dyskretna piguÅka 223 // "Zaloguj siÄ" w rogu nagÅówka z krótkim menu (Zaloguj / ZaÅóż 224 // konto). Konto jest opcjonalne - nie zabiera miejsca. 225 pasek.classList.remove('gk-pasek-zalogowany'); 226 pasek.classList.add('gk-pasek-gosc'); 227 pasek.innerHTML = `<div class="gk-konto"> 228 <button type="button" class="gk-konto-przycisk gk-konto-gosc" data-gk="menu" aria-haspopup="true" aria-expanded="false" title="Konto jest opcjonalne - pozwala używaÄ tego samego nicku na każdym urzÄ dzeniu"> 229 <span class="gk-pasek-awatar gk-pasek-awatar-gosc" aria-hidden="true"><svg viewBox="0 0 24 24" width="15" height="15" fill="none"><circle cx="12" cy="8.5" r="3.6" stroke="currentColor" stroke-width="2"/><path d="M5 19.5c1.2-3.4 3.9-5.1 7-5.1s5.8 1.7 7 5.1" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span> 230 <span class="gk-konto-nick">Zaloguj siÄ</span> 231 <svg class="gk-konto-strzalka" viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg> 232 </button> 233 <div class="gk-konto-menu gk-konto-menu-gosc" role="menu" hidden> 234 <div class="gk-konto-menu-info"><strong>Konto jest opcjonalne</strong><small>Możesz korzystaÄ ze wszystkiego bez logowania. Konto zachowuje Twój nick, punkty i paszport na każdym urzÄ dzeniu.</small></div> 235 <button type="button" class="gk-konto-pozycja gk-konto-pozycja-glowna" role="menuitem" data-gk="logowanie"> 236 <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden="true"><path d="M11 4h4a1 1 0 011 1v10a1 1 0 01-1 1h-4M8 13.5L11.5 10 8 6.5M11.5 10H3.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg> 237 Zaloguj siÄ 238 </button> 239 <button type="button" class="gk-konto-pozycja" role="menuitem" data-gk="rejestracja"> 240 <svg viewBox="0 0 20 20" width="16" height="16" fill="none" aria-hidden="true"><circle cx="10" cy="10" r="7" stroke="currentColor" stroke-width="1.6"/><path d="M10 6.8v6.4M6.8 10h6.4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg> 241 ZaÅóż darmowe konto 242 </button> 243 </div> 244 </div>`; 245 przeniesDoNaglowka(pasek); 246 } 247 } 248 249 // ZdjÄcie profilowe albo pierwsza litera nicku. 250 function htmlAwatara(nick, klasa) { 251 const url = stanKonta && stanKonta.awatar ? String(stanKonta.awatar) : ''; 252 return url 253 ? `<span class="gk-pasek-awatar gk-ma-zdjecie${klasa ? ' ' + klasa : ''}" aria-hidden="true"><img src="${escHtml(url)}" alt="" loading="lazy" decoding="async"></span>` 254 : `<span class="gk-pasek-awatar${klasa ? ' ' + klasa : ''}" aria-hidden="true">${escHtml(String(nick || '').charAt(0).toUpperCase() || '?')}</span>`;
255 } 256 window.gorzowKontaAwatar = htmlAwatara; 257 258 // Zmniejszenie i przyciÄcie zdjÄcia do kwadratu w przeglÄ darce 259 // (mniejszy plik = szybsze wysyÅanie; serwer i tak przycina do 256 px). 260 function przygotujAwatar(plik) { 261 return new Promise((resolve, reject) => { 262 if (!/^image\/(jpeg|png|webp)$/.test(plik.type)) { reject(new Error('Wybierz zdjÄcie JPG, PNG lub WEBP.')); return; } 263 if (plik.size > 15 * 1024 * 1024) { reject(new Error('Plik jest za duży (maks. 15 MB).')); return; } 264 const url = URL.createObjectURL(plik); 265 const img = new Image(); 266 img.onload = () => { 267 const bok = Math.min(img.naturalWidth, img.naturalHeight); 268 if (bok < 64) { URL.revokeObjectURL(url); reject(new Error('ZdjÄcie jest za maÅe (min. 64 x 64 px).')); return; } 269 const c = document.createElement('canvas'); 270 c.width = c.height = 512; 271 const ctx = c.getContext('2d'); 272 ctx.fillStyle = '#ffffff'; 273 ctx.fillRect(0, 0, 512, 512); 274 ctx.drawImage(img, (img.naturalWidth - bok) / 2, (img.naturalHeight - bok) / 2, bok, bok, 0, 0, 512, 512); 275 URL.revokeObjectURL(url); 276 c.toBlob((b) => (b ? resolve({ blob: b, podglad: c.toDataURL('image/jpeg', 0.85) }) : reject(new Error('Nie udaÅo siÄ przygotowaÄ zdjÄcia.'))), 'image/jpeg', 0.88); 277 }; 278 img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Nie udaÅo siÄ odczytaÄ zdjÄcia.')); }; 279 img.src = url; 280 }); 281 } 282 283 function renderAwatar() { 284 if (!stanKonta || !stanKonta.zalogowany) { renderLogowanie(); return; } 285 const nick = String(stanKonta.nick || ''); 286 const ma = !!stanKonta.awatar; 287 otworzModal(`${naglowekModala('uzytkownik', 'ZdjÄcie profilowe', 'Widoczne przy Twoim nicku w obu serwisach.', 'fiolet')} 288 <div class="gk-aw"> 289 <label class="gk-aw-podglad" for="gk-aw-plik" title="Wybierz zdjÄcie"> 290 ${ma ? `<img src="${escHtml(stanKonta.awatar)}" alt="">` : `<span>${escHtml(nick.charAt(0).toUpperCase() || '?')}</span>`} 291 <i aria-hidden="true"><svg viewBox="0 0 20 20" width="16" height="16" fill="none"><path d="M4 7h2.5l1.2-2h4.6l1.2 2H16v8.5H4z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><circle cx="10" cy="11" r="2.6" stroke="currentColor" stroke-width="1.6"/></svg></i> 292 </label> 293 <input type="file" id="gk-aw-plik" accept="image/jpeg,image/png,image/webp" hidden> 294 <div class="gk-aw-strefa" data-gk-aw="strefa">PrzeciÄ gnij zdjÄcie tutaj albo <button type="button" class="gk-link" data-gk-aw="wybierz">wybierz z urzÄ dzenia</button><small>JPG, PNG lub WEBP ⢠zdjÄcie zostanie przyciÄte do kwadratu</small></div> 295 <div class="gk-m-status" data-gk-aw="status" aria-live="polite"></div> 296 <div class="gk-aw-przyciski"> 297 ${ma ? '<button type="button" class="gk-btn gk-btn-drugi" data-gk-aw="usun">UsuÅ zdjÄcie</button>' : ''} 298 <button type="button" class="gk-btn gk-btn-glowny" data-gk-aw="zapisz" disabled>Zapisz zdjÄcie</button> 299 </div> 300 </div>`); 301 const modal = document.getElementById('gk-modal-konto'); 302 const plik = modal.querySelector('#gk-aw-plik'); 303 const podglad = modal.querySelector('.gk-aw-podglad'); 304 const status = modal.querySelector('[data-gk-aw="status"]'); 305 const zapisz = modal.querySelector('[data-gk-aw="zapisz"]'); 306 const strefa = modal.querySelector('[data-gk-aw="strefa"]'); 307 let gotowy = null; 308 const pokazStatus = (t, blad) => { status.textContent = t; status.classList.toggle('is-blad', !!blad); }; 309 const przyjmij = async (f) => { 310 if (!f) return; 311 try { 312 pokazStatus('PrzygotowujÄ zdjÄcieâ¦'); 313 gotowy = await przygotujAwatar(f); 314 podglad.querySelector('img, span') && podglad.querySelector('img, span').remove(); 315 podglad.insertAdjacentHTML('afterbegin', `<img src="${gotowy.podglad}" alt="">`); 316 pokazStatus('WyglÄ da dobrze? Kliknij âZapisz zdjÄcieâ.'); 317 zapisz.disabled = false;
318 } catch (err) { 319 gotowy = null; 320 zapisz.disabled = true; 321 pokazStatus(err.message, true); 322 } 323 }; 324 plik.addEventListener('change', () => przyjmij(plik.files[0])); 325 modal.querySelector('[data-gk-aw="wybierz"]').addEventListener('click', () => plik.click()); 326 ['dragover', 'dragenter'].forEach((t) => strefa.addEventListener(t, (e) => { e.preventDefault(); strefa.classList.add('is-nad'); })); 327 ['dragleave', 'drop'].forEach((t) => strefa.addEventListener(t, (e) => { e.preventDefault(); strefa.classList.remove('is-nad'); })); 328 strefa.addEventListener('drop', (e) => przyjmij(e.dataTransfer && e.dataTransfer.files[0])); 329 const wyslij = async (url, dane) => { 330 const zrob = () => fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.nonce }, body: dane }); 331 let r = await zrob(); 332 if (r.status === 403 && await odswiezNonce()) r = await zrob(); 333 let j = {}; 334 try { j = await r.json(); } catch (e) { /* nic */ } 335 return { ok: r.ok, j }; 336 }; 337 const poZmianie = (url) => { 338 stanKonta = Object.assign({}, stanKonta, { awatar: url || '' }); 339 window.gorzowKontaStan = stanKonta; 340 renderPasek(); 341 try { document.dispatchEvent(new CustomEvent('gorzow-konta:stan', { detail: stanKonta })); } catch (e) { /* nic */ } 342 }; 343 zapisz.addEventListener('click', async () => { 344 if (!gotowy || !cfg.restUrlAwatar) return; 345 zapisz.disabled = true; 346 pokazStatus('WysyÅanieâ¦'); 347 const fd = new FormData(); 348 fd.append('plik', gotowy.blob, 'awatar.jpg'); 349 const { ok, j } = await wyslij(cfg.restUrlAwatar, fd); 350 if (ok && j.awatar) { 351 poZmianie(j.awatar); 352 pokazStatus('Zapisano! ð'); 353 setTimeout(zamknijModal, 700); 354 } else { 355 pokazStatus(j.message || 'Nie udaÅo siÄ zapisaÄ zdjÄcia.', true); 356 zapisz.disabled = false; 357 } 358 }); 359 const usun = modal.querySelector('[data-gk-aw="usun"]'); 360 if (usun) usun.addEventListener('click', async () => { 361 usun.disabled = true; 362 const { ok, j } = await wyslij(cfg.restUrlAwatarUsun, new FormData()); 363 if (ok) { poZmianie(''); zamknijModal(); } else { pokazStatus(j.message || 'Nie udaÅo siÄ usunÄ Ä zdjÄcia.', true); usun.disabled = false; } 364 }); 365 } 366 367 function otworzModal(tresc) { 368 const tlo = document.getElementById('gk-modal-tlo'); 369 const modal = document.getElementById('gk-modal-konto'); 370 if (!tlo || !modal) return; 371 modal.innerHTML = tresc; 372 modal.classList.remove('gk-modal-szeroki'); 373 modal.classList.add('gk-modal-v2'); 374 modal.setAttribute('aria-labelledby', 'gk-m-tytul'); 375 tlo.hidden = false; 376 requestAnimationFrame(() => tlo.classList.add('gk-widoczny')); 377 document.body.style.overflow = 'hidden'; 378 const pierwszePole = modal.querySelector('input:not([type="hidden"]):not([tabindex="-1"])'); 379 if (pierwszePole && window.innerWidth > 640) pierwszePole.focus(); 380 } 381 function zamknijModal() { 382 const tlo = document.getElementById('gk-modal-tlo'); 383 if (tlo) { tlo.hidden = true; tlo.classList.remove('gk-widoczny'); } 384 document.body.style.overflow = ''; 385 } 386 387 async function pobierzCaptche() { 388 try { 389 const resp = await fetch(cfg.restUrlCaptcha, { headers: { 'X-WP-Nonce': cfg.nonce } }); 390 captchaDane = await resp.json(); 391 } catch (e) { 392 captchaDane = { token: '', pytanie: '?' }; 393 } 394 return captchaDane; 395 } 396 397 function polePytanieCaptcha() { 398 if (captchaDane && captchaDane.recaptchaSiteKey) { 399 return `<div class="g-recaptcha" data-sitekey="${escHtml(captchaDane.recaptchaSiteKey)}" data-callback="gorzowKontaRecaptchaOk"></div><input type="hidden" name="recaptcha_token" id="gk-recaptcha-token">`; 400 } 401 return `<label class="gk-modal-pole gk-m-captcha-pole"><span>Ile to jest <strong>${escHtml(captchaDane ? captchaDane.pytanie : 'â¦')}</strong>?</span> 402 <input type="text" inputmode="numeric" name="captcha_odpowiedz" maxlength="3" required autocomplete="off"></label> 403 <input type="hidden" name="captcha_token" value="${esc
403Html(captchaDane ? captchaDane.token : '')}">`; 404 } 405 406 window.gorzowKontaRecaptchaOk = function (token) { 407 const pole = document.getElementById('gk-recaptcha-token'); 408 if (pole) pole.value = token; 409 }; 410 411 // ------------------------------------------------------------ 412 // Åadniejsze okna logowania / rejestracji / resetu (v2) 413 // ------------------------------------------------------------ 414 const IKONY = { 415 uzytkownik: '<circle cx="10" cy="7" r="3.2"/><path d="M4 16.5c1-3 3.3-4.5 6-4.5s5 1.5 6 4.5"/>', 416 klodka: '<rect x="4.5" y="9" width="11" height="8" rx="2"/><path d="M7 9V6.8a3 3 0 0 1 6 0V9"/>', 417 mail: '<rect x="3" y="5" width="14" height="10" rx="2"/><path d="M3.5 6l6.5 5 6.5-5"/>', 418 znaczek: '<path d="M10 3l2 4.2 4.5.6-3.3 3.1.8 4.6L10 13.3l-4 2.2.8-4.6L3.5 7.8 8 7.2z"/>', 419 tarcza: '<path d="M10 2.8l6 2.4v4.3c0 3.8-2.6 6.4-6 7.7-3.4-1.3-6-3.9-6-7.7V5.2z"/><path d="M7.5 10l1.8 1.8 3.4-3.6"/>', 420 oko: '<path d="M2.5 10s2.8-5 7.5-5 7.5 5 7.5 5-2.8 5-7.5 5-7.5-5-7.5-5z"/><circle cx="10" cy="10" r="2.3"/>', 421 okoZamkniete: '<path d="M3 3l14 14M8.3 5.3A7.7 7.7 0 0 1 10 5c4.7 0 7.5 5 7.5 5a13 13 0 0 1-2.3 2.9M12.2 12.4A2.3 2.3 0 0 1 8 10c0-.6.2-1.2.6-1.6M5.5 7.2A12.6 12.6 0 0 0 2.5 10s2.8 5 7.5 5c1 0 1.9-.2 2.7-.5"/>', 422 klucz: '<circle cx="7" cy="12.5" r="3"/><path d="M9.2 10.3L16 3.5M13.5 6l2 2M11.8 7.7l1.5 1.5"/>', 423 wejscie: '<path d="M11 4h4a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-4M8 13.5L11.5 10 8 6.5M11.5 10H3.5"/>', 424 plus: '<circle cx="10" cy="10" r="7"/><path d="M10 6.8v6.4M6.8 10h6.4"/>', 425 }; 426 const ikona = (nazwa, r) => `<svg viewBox="0 0 20 20" width="${r || 18}" height="${r || 18}" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${IKONY[nazwa]}</svg>`; 427 428 const pamiecFormularza = { login: '' }; 429 430 function bokModala() { 431 return `<aside class="gk-m-bok" aria-hidden="true"> 432 <span class="gk-m-bok-logo">G</span> 433 <strong>Jedno konto,<br>dwa serwisy</strong> 434 <p>Mapa Miejsc i Ceny Paliw Gorzowa - z tym samym nickiem na każdym urzÄ dzeniu.</p> 435 <ul> 436 <li><span>ð</span>Dodawaj miejsca i opinie pod swoim nickiem</li> 437 <li><span>ð </span>Zbieraj punkty, odznaki i miejsca w rankingu</li> 438 <li><span>â½</span>ZgÅaszaj ceny paliw i ustaw alerty</li> 439 <li><span>ð</span>Bez reklam i spamu - tylko to, co potrzebne</li> 440 </ul> 441 </aside>`; 442 } 443 444 function otworzModalKonta(tresc) { 445 otworzModal(`<div class="gk-m-uklad">${bokModala()}<div class="gk-m-glowny">${tresc}</div></div>`); 446 const modal = document.getElementById('gk-modal-konto'); 447 if (modal) modal.classList.add('gk-modal-szeroki'); 448 } 449 450 function naglowekModala(ikonaNazwa, tytul, podtytul, kolor) { 451 return `<button type="button" class="gk-modal-zamknij" data-gk="zamknij" aria-label="Zamknij"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></button> 452 <div class="gk-m-glowa"> 453 <span class="gk-m-ikona gk-m-ikona-${kolor || 'fiolet'}">${ikona(ikonaNazwa, 22)}</span> 454 <h2 id="gk-m-tytul">${escHtml(tytul)}</h2> 455 ${podtytul ? `<p>${escHtml(podtytul)}</p>` : ''} 456 </div>`; 457 } 458 459 function zakladki(aktywna) { 460 return `<div class="gk-m-zakladki" role="tablist"> 461 <button type="button" role="tab" aria-selected="${aktywna === 'logowanie'}" class="${aktywna === 'logowanie' ? 'is-on' : ''}" data-gk="logowanie">Logowanie</button> 462 <button type="button" role="tab" aria-selected="${aktywna === 'rejestracja'}" class="${aktywna === 'rejestracja' ? 'is-on' : ''}" data-gk="rejestracja">ZaÅóż konto</button> 463 </div>`; 464 } 465 466 function pole(o) { 467 const haslo = o.typ === 'password'; 468 const wartosc = o.nazwa === 'login' && pamiecFormularza.login ? ` value="${esc
468Html(pamiecFormularza.login)}"` : ''; 469 return `<label class="gk-m-pole gk-m-pole-plywajace"> 470 <span class="gk-m-wejscie"> 471 ${ikona(o.ikona)} 472 <input type="${o.typ || 'text'}" name="${o.nazwa}" ${o.atrybuty || ''}${wartosc} placeholder=" "> 473 <span class="gk-m-etykieta">${escHtml(o.etykieta)}${o.placeholder ? `<em> - ${escHtml(o.placeholder)}</em>` : ''}</span> 474 ${haslo ? `<button type="button" class="gk-m-oko" data-gk-oko aria-label="Pokaż hasÅo" title="Pokaż hasÅo">${ikona('oko')}</button>` : ''} 475 </span> 476 ${haslo ? '<small class="gk-m-capslock" hidden>⪠Caps Lock jest wÅÄ czony</small>' : ''} 477 ${o.podpowiedz ? `<small class="gk-m-podpowiedz" data-gk-podpowiedz="${o.nazwa}">${o.podpowiedz}</small>` : ''} 478 </label>`; 479 } 480 481 function przyciskWyslij(tekst, ik) { 482 return `<button type="submit" class="gk-modal-submit gk-m-wyslij"><span class="gk-m-spinner" aria-hidden="true"></span>${ik ? ikona(ik) : ''}<span>${escHtml(tekst)}</span></button>`; 483 } 484 485 function ustawLadowanie(form, tak) { 486 const b = form.querySelector('.gk-m-wyslij'); 487 if (!b) return; 488 b.disabled = tak; 489 b.classList.toggle('is-laduje', tak); 490 } 491 492 function ustawStatus(typ, tekst) { 493 const status = document.getElementById('gk-modal-status'); 494 if (!status) return; 495 status.className = 'gk-modal-status' + (typ ? ' gk-modal-status-' + typ : ''); 496 status.textContent = tekst || ''; 497 } 498 499 // SiÅa hasÅa: 0-4. 500 function silaHasla(h) { 501 if (!h) return 0; 502 let p = 0; 503 if (h.length >= 8) p++; 504 if (/[a-zÄ ÄÄÅÅóÅźż]/i.test(h) && /\d/.test(h)) p++; 505 if (/[a-zÄ ÄÄÅÅóÅźż]/.test(h) && /[A-ZÄÄÄÅÅÃÅŹŻ]/.test(h)) p++; 506 if (/[^A-Za-z0-9Ä ÄÄÅÅóÅźżÄÄÄÅÅÃÅŹŻ]/.test(h) || h.length >= 12) p++; 507 if (h.length < 8) p = Math.min(p, 1); 508 return p; 509 } 510 511 function captchaHtml() { 512 return `<div class="gk-m-captcha"> 513 <span class="gk-m-captcha-ikona">${ikona('tarcza')}</span> 514 ${polePytanieCaptcha()} 515 </div>`; 516 } 517 518 async function renderLogowanie() { 519 otworzModalKonta(` 520 ${naglowekModala('wejscie', 'Witaj ponownie', 'Jedno konto dziaÅa w Mapie Miejsc i w Cenach Paliw.')} 521 ${zakladki('logowanie')} 522 <div class="gk-modal-status" id="gk-modal-status" role="status"></div> 523 <form id="gk-form-logowanie" novalidate> 524 ${pole({ etykieta: 'Login lub e-mail', nazwa: 'login', ikona: 'uzytkownik', atrybuty: 'required autocomplete="username" maxlength="100"', placeholder: 'np. jan.kowalski' })} 525 ${pole({ etykieta: 'HasÅo', nazwa: 'haslo', typ: 'password', ikona: 'klodka', atrybuty: 'required autocomplete="current-password" maxlength="200"', placeholder: 'â¢â¢â¢â¢â¢â¢â¢â¢' })} 526 <div id="gk-login-captcha"></div> 527 <div class="gk-honeypot" aria-hidden="true"><label>Zostaw puste<input type="text" name="www" tabindex="-1" autocomplete="off"></label></div> 528 <div class="gk-m-wiersz"><a href="#" data-gk="reset">Nie pamiÄtasz hasÅa?</a></div> 529 ${przyciskWyslij('Zaloguj siÄ', 'wejscie')} 530 </form> 531 <p class="gk-m-stopka">${ikona('tarcza', 14)} PoÅÄ czenie chronione ⢠limit prób logowania</p>`); 532 533 const form = document.getElementById('gk-form-logowanie'); 534 form.addEventListener('submit', async (e) => { 535 e.preventDefault(); 536 if (!form.login.value.trim() || !form.haslo.value) { 537 ustawStatus('blad', 'Podaj login i hasÅo.'); 538 return; 539 } 540 ustawLadowanie(form, true); 541 ustawStatus('', 'Logowanieâ¦'); 542 const { ok, dane } = await postJson(cfg.restUrlLogowanie, Object.fromEntries(new FormData(form))); 543 ustawLadowanie(form, false); 544 if (ok) { 545 const glowny = form.closest('.gk-m-glowny') || form.parentElement; 546 glowny.innerHTML = `<div class="gk-m-zalogowano"><span class="gk-m-ptaszek" aria-hidden="true"><svg viewBox="0 0 52 52"><circle cx="26" cy="26" r="24"/><path d="M15 27l7 7 15-16"/></svg></span> 547 <strong>CzeÅÄ, ${escHtml(dane.nick || '')}!</strong><small>Zalogowano - odÅwieżam stronÄâ¦</small></div>`; 548 stanKonta = { zalogowany: true, nick: dane.nick }; 549 window.gorzowKontaStan = stanKonta; 550 // PrzeÅadowanie odÅwieża nick w formularzach, rankingach i 551 // profilach obu wtyczek (bez tego czÄÅÄ stanu byÅa nieaktualna). 552 setTimeout(() => window.location.reload(), 1100); 553 return; 554 } 555 const wymagaCaptchy = dane && (dane.code === 'gorzow_konta_wymagana_captcha' || (dane.data && dane.data.wymagaCaptchy)); 556 if (wymagaCaptchy) { 557 await pobierzCaptche();
558 const k = document.getElementById('gk-login-captcha'); 559 if (k) { 560 k.innerHTML = captchaHtml(); 561 const pc = k.querySelector('input[name="captcha_odpowiedz"]'); 562 if (pc) pc.focus(); 563 } 564 } 565 ustawStatus('blad', (dane && dane.message) || 'Nie udaÅo siÄ zalogowaÄ.'); 566 form.haslo.value = wymagaCaptchy ? form.haslo.value : ''; 567 }); 568 } 569 570 async function renderRejestracja() { 571 otworzModalKonta(` 572 ${naglowekModala('plus', 'ZaÅóż darmowe konto', 'Twój nick w rankingach Mapy Miejsc i Cen Paliw - na każdym urzÄ dzeniu.', 'zielony')} 573 ${zakladki('rejestracja')} 574 <div class="gk-modal-status" id="gk-modal-status" role="status"></div> 575 <form id="gk-form-rejestracja" autocomplete="off" novalidate> 576 <div class="gk-m-dwa"> 577 ${pole({ etykieta: 'Login', nazwa: 'login', ikona: 'uzytkownik', atrybuty: 'minlength="3" maxlength="25" required autocomplete="username"', placeholder: 'do logowania', podpowiedz: '3-25 znaków: litery, cyfry, . - _' })} 578 ${pole({ etykieta: 'Nick publiczny', nazwa: 'nick', ikona: 'znaczek', atrybuty: 'minlength="2" maxlength="30" required', placeholder: 'widoczny w rankingu' })} 579 </div> 580 ${pole({ etykieta: 'E-mail', nazwa: 'email', typ: 'email', ikona: 'mail', atrybuty: 'required autocomplete="email" maxlength="100"', placeholder: 'na link aktywacyjny' })} 581 ${pole({ etykieta: 'HasÅo', nazwa: 'haslo', typ: 'password', ikona: 'klodka', atrybuty: 'minlength="8" maxlength="200" required autocomplete="new-password"', placeholder: 'min. 8 znaków, litera i cyfra' })} 582 <div class="gk-m-sila" aria-live="polite"><span class="gk-m-sila-paski"><i></i><i></i><i></i><i></i></span><small class="gk-m-sila-opis">Wpisz hasÅo</small></div> 583 ${pole({ etykieta: 'Powtórz hasÅo', nazwa: 'haslo2', typ: 'password', ikona: 'klodka', atrybuty: 'minlength="8" maxlength="200" required autocomplete="new-password"', placeholder: 'wpisz hasÅo jeszcze raz', podpowiedz: ' ' })} 584 <div id="gk-captcha-kontener">${captchaHtml()}</div> 585 <div class="gk-honeypot" aria-hidden="true"><label>Zostaw puste<input type="text" name="www" tabindex="-1" autocomplete="off"></label></div> 586 ${przyciskWyslij('ZaÅóż konto', 'plus')} 587 </form> 588 <p class="gk-m-stopka">${ikona('tarcza', 14)} HasÅo przechowujemy wyÅÄ cznie w formie zaszyfrowanej</p>`); 589 pobierzCaptche().then(() => { 590 const k = document.getElementById('gk-captcha-kontener'); 591 if (k) k.innerHTML = captchaHtml(); 592 if (captchaDane && captchaDane.recaptchaSiteKey && !window.__gkRecaptchaScript) { 593 window.__gkRecaptchaScript = true; 594 const sc = document.createElement('script'); 595 sc.src = 'https://www.google.com/recaptcha/api.js'; 596 sc.async = true; sc.defer = true; 597 document.head.appendChild(sc); 598 } 599 }); 600 601 const form = document.getElementById('gk-form-rejestracja'); 602 const sila = form.querySelector('.gk-m-sila'); 603 const opisSily = form.querySelector('.gk-m-sila-opis'); 604 const zgodnosc = form.querySelector('[data-gk-podpowiedz="haslo2"]'); 605 const OPISY = ['Za krótkie', 'SÅabe', 'Årednie', 'Dobre', 'Silne']; 606 const odswiez = () => { 607 const s = silaHasla(form.haslo.value); 608 sila.dataset.sila = form.haslo.value ? String(s) : ''; 609 opisSily.textContent = form.haslo.value ? OPISY[s] + (s < 2 ? ' - dodaj cyfrÄ i wiÄcej znaków' : '') : 'Wpisz hasÅo'; 610 if (!form.haslo2.value) { 611 zgodnosc.innerHTML = ' '; 612 zgodnosc.className = 'gk-m-podpowiedz'; 613 } else if (form.haslo2.value === form.haslo.value) { 614 zgodnosc.textContent = 'â HasÅa sÄ zgodne'; 615 zgodnosc.className = 'gk-m-podpowiedz is-ok'; 616 } else { 617 zgodnosc.textContent = 'HasÅa siÄ różniÄ '; 618 zgodnosc.className = 'gk-m-podpowiedz is-blad'; 619 } 620 }; 621 form.addEventListener('input', odswiez); 622 623 form.addEventListener('submit', async (e) => { 624 e.preventDefault(); 625 const d = Object.fromEntries(new FormData(form)); 626 if (!/^[A-Za-z0-9._-]{3,25}$/.test(d.login || '')) { ustawStatus('blad', 'Login: 3-25 znaków (litery, cyfry, kropka, myÅlnik, podkreÅlenie).'); form.login.focus(); return; } 627 if ((d.nick || '').trim().length < 2) { ustawStatus('blad', 'Nick musi mieÄ co najmniej 2 znaki.'); form.nick.focus(); return; } 628 if (!form.email.checkValidity() || !d.email) { ustawStatus('blad', 'Podaj prawidÅowy adres e-mail.'); form.email.focus(); return; } 629 if (silaHasla(d.haslo) < 2 || !/\d/.test(d.haslo) || !/[A-Za-zÄ ÄÄÅÅóÅźżÄÄÄÅÅÃÅŹŻ]/.test(d.haslo)) { ustawStatus('blad', 'HasÅo: min. 8 znaków, co najmniej jedna litera i jedna cyfra.'); form.haslo.focus(); return; } 630 if (d.haslo !== d.haslo2) { ustawStatus('blad', 'HasÅa nie sÄ takie same.'); form.haslo2.focus(); return; } 631 632 ustawLadowanie(form, true); 633 ustawStatus('', 'ZakÅadanie kontaâ¦'); 634 const { ok, dane } = await postJson(cfg.restUrlRejestracja, d); 635 ustawLadowanie(form, false); 636 if (ok) { 637 const modal = document.getElementById('gk-modal-konto'); 638 modal.innerHTML = `${naglowekModala('mail', 'Prawie gotowe!', '', 'z
638ielony')} 639 <div class="gk-m-sukces"><p>${escHtml(dane.wiadomosc || 'Konto zaÅożone.')}</p> 640 <button type="button" class="gk-modal-submit gk-m-wyslij" data-gk="logowanie"><span>Przejdź do logowania</span></button></div>`; 641 return; 642 } 643 ustawStatus('blad', (dane && (dane.message || dane.wiadomosc)) || 'Nie udaÅo siÄ zaÅożyÄ konta.'); 644 await pobierzCaptche(); 645 const k = document.getElementById('gk-captcha-kontener'); 646 if (k) k.innerHTML = captchaHtml(); 647 }); 648 } 649 650 function renderReset() { 651 otworzModalKonta(` 652 ${naglowekModala('klucz', 'Reset hasÅa', 'Podaj login lub e-mail - wyÅlemy link do ustawienia nowego hasÅa.', 'niebieski')} 653 <div class="gk-modal-status" id="gk-modal-status" role="status"></div> 654 <form id="gk-form-reset" novalidate> 655 ${pole({ etykieta: 'Login lub e-mail', nazwa: 'login', ikona: 'mail', atrybuty: 'required maxlength="100" autocomplete="username"' })} 656 <div class="gk-honeypot" aria-hidden="true"><label>Zostaw puste<input type="text" name="www" tabindex="-1" autocomplete="off"></label></div> 657 ${przyciskWyslij('WyÅlij link resetujÄ cy', 'mail')} 658 </form> 659 <p class="gk-modal-linki"><a href="#" data-gk="logowanie">â WrÃ³Ä do logowania</a></p>`); 660 const form = document.getElementById('gk-form-reset'); 661 form.addEventListener('submit', async (e) => { 662 e.preventDefault(); 663 if (!form.login.value.trim()) { ustawStatus('blad', 'Podaj login lub e-mail.'); return; } 664 ustawLadowanie(form, true); 665 ustawStatus('', 'WysyÅanieâ¦'); 666 const { ok, dane } = await postJson(cfg.restUrlResetZadaj, Object.fromEntries(new FormData(form))); 667 ustawLadowanie(form, false); 668 ustawStatus(ok ? 'ok' : 'blad', dane.wiadomosc || dane.message || 'Gotowe.'); 669 if (ok) form.querySelectorAll('input,button').forEach((i) => { i.disabled = true; }); 670 }); 671 } 672 673 document.addEventListener('click', async (e) => { 674 const oko = e.target.closest('[data-gk-oko]'); 675 if (oko) { 676 const pole = oko.parentElement.querySelector('input'); 677 const pokaz = pole.type === 'password'; 678 pole.type = pokaz ? 'text' : 'password'; 679 oko.innerHTML = ikona(pokaz ? 'okoZamkniete' : 'oko'); 680 oko.setAttribute('aria-label', pokaz ? 'Ukryj hasÅo' : 'Pokaż hasÅo'); 681 return; 682 } 683 const dodatek = e.target.closest('[data-gk-dodatek]'); 684 if (dodatek) { 685 zamknijMenuKonta(); 686 document.dispatchEvent(new CustomEvent('gorzow-konta:menu', { detail: { id: dodatek.dataset.gkDodatek } })); 687 return; 688 } 689 const cel = e.target.closest('[data-gk]'); 690 if (!cel) return; 691 const akcja = cel.dataset.gk; 692 if (akcja === 'zamknij') { zamknijModal(); return; } 693 if (akcja === 'menu') { 694 const menu = cel.parentElement.querySelector('.gk-konto-menu'); 695 const otworz = menu && menu.hidden; 696 zamknijMenuKonta(); 697 if (menu && otworz) { 698 menu.hidden = false; 699 cel.setAttribute('aria-expanded', 'true'); 700 } 701 return; 702 } 703 zamknijMenuKonta(); 704 if (['logowanie', 'rejestracja', 'reset'].includes(akcja)) { 705 e.preventDefault(); 706 if (akcja === 'logowanie') renderLogowanie(); 707 if (akcja === 'rejestracja') renderRejestracja(); 708 if (akcja === 'reset') renderReset(); 709 } 710 if (akcja === 'wyloguj') { 711 cel.disabled = true; 712 await postJson(cfg.restUrlWylogowanie, {}); 713 window.location.reload(); 714 } 715 if (akcja === 'usun') { 716 renderUsuniecie(); 717 } 718 if (akcja === 'awatar') { 719 zamknijMenuKonta(); 720 renderAwatar(); 721 } 722 }); 723 // TÅo zamyka okno tylko przy peÅnym klikniÄciu w tÅo (nie przy 724 // zaznaczaniu tekstu, które koÅczy siÄ poza oknem) i nie wtedy, gdy 725 // w formularzu sÄ już wpisane dane - wtedy okno tylko "drgnie". 726 let wcisnietoNaTle = false;
727 const tloModala = document.getElementById('gk-modal-tlo'); 728 if (tloModala) { 729 tloModala.addEventListener('pointerdown', (e) => { wcisnietoNaTle = e.target === tloModala; }); 730 tloModala.addEventListener('click', (e) => { 731 if (e.target !== tloModala || !wcisnietoNaTle) return; 732 const modal = document.getElementById('gk-modal-konto'); 733 const wypelnione = modal && Array.from(modal.querySelectorAll('input:not([type="hidden"]):not([tabindex="-1"]):not([type="checkbox"])')).some((i) => i.value && !i.disabled); 734 if (wypelnione) { 735 modal.classList.remove('gk-drgnij'); 736 void modal.offsetWidth; 737 modal.classList.add('gk-drgnij'); 738 return; 739 } 740 zamknijModal(); 741 }); 742 } 743 // ZapamiÄtanie loginu przy przeÅÄ czaniu zakÅadek + ostrzeżenie Caps Lock. 744 document.addEventListener('input', (e) => { 745 if (e.target && e.target.name === 'login' && e.target.closest('#gk-modal-konto')) pamiecFormularza.login = e.target.value; 746 }); 747 const sprawdzCapsLock = (e) => { 748 if (!e.target || e.target.type !== 'password' && !(e.target.name === 'haslo' || e.target.name === 'haslo2')) return; 749 if (typeof e.getModifierState !== 'function') return; 750 const pole = e.target.closest('.gk-m-pole'); 751 const info = pole && pole.querySelector('.gk-m-capslock'); 752 if (info) info.hidden = !e.getModifierState('CapsLock'); 753 }; 754 document.addEventListener('keyup', sprawdzCapsLock); 755 document.addEventListener('keydown', sprawdzCapsLock); 756 document.addEventListener('keydown', (e) => { 757 if (e.key === 'Escape') { zamknijModal(); zamknijMenuKonta(); } 758 }); 759 document.addEventListener('click', (e) => { 760 if (!e.target.closest('.gk-konto')) zamknijMenuKonta(); 761 }); 762 763 // Strona starsza niż 6 h (pamiÄÄ podrÄczna) - najpierw Åwieży nonce. 764 const start = async () => { 765 if (cfg.czasFormularza && (Date.now() / 1000 - Number(cfg.czasFormularza)) > 6 * 3600) { 766 await odswiezNonce(); 767 } 768 await pobierzStanKonta(); 769 renderPasek(); 770 }; 771 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); 772 else start(); 773})(); 774 775/** 776 * Ochrona przed "czarnymi" hoverami z motywu strony (np. Elementor/Astra 777 * ustawiajÄ button:hover / button:focus na czarne tÅo i biaÅy tekst). 778 * Dla każdego przycisku w module zapamiÄtujemy jego zwykÅe tÅo i kolor, 779 * a przy najechaniu / klikniÄciu (focus) przywracamy je. Nasze wÅasne, 780 * zaprojektowane efekty hover (z !important) dziaÅajÄ dalej. 781 */ 782(function antyCzarnyHover(korzenie, idStylu) { 783 if (document.getElementById(idStylu)) return; 784 // :not(#gz-x) podnosi ważnoÅÄ reguÅy ponad typowe reguÅy motywu. 785 const sel = korzenie.map((k) => `${k} button:not(#gz-x):is(:hover,:focus,:active), ${k} [type="button"]:not(#gz-x):is(:hover,:focus,:active), ${k} [type="submit"]:not(#gz-x):is(:hover,:focus,:active)`).join(','); 786 const st = document.createElement('style'); 787 st.id = idStylu; 788 st.textContent = `${sel}{background-color:var(--gz-bg,transparent);color:var(--gz-c,inherit);text-decoration:none}`; 789 (document.head || document.documentElement).appendChild(st); 790 791 const zapamietaj = (b, proba) => { 792 if (!(b instanceof HTMLElement) || b.dataset.gzHv) return; 793 if (b.matches(':hover, :focus')) { 794 // Przycisk jest wÅaÅnie wskazany/klikniÄty - spróbuj ponownie za chwilÄ. 795 if ((proba || 0) < 20) setTimeout(() => zapamietaj(b, (proba || 0) + 1), 500); 796 return; 797 } 798 const cs = getComputedStyle(b); 799 b.style.setProperty('--gz-bg', cs.backgroundColor); 800 b.style.setProperty('--gz-c', cs.color); 801 b.dataset.gzHv = '1'; 802 }; 803 const skanuj = (el) => { 804 if (!el || !el.querySelectorAll) return; 805 if (el.matches && el.matches('button, [type="button"], [type="submit"]')) zapamietaj(el); 806 el.querySelectorAll('button, [type="button"], [type="submit"]').forEach(zapamietaj); 807 }; 808 // Zmiana klasy (np. aktywny/nieaktywny) = nowe tÅo - odÅwieżamy zapamiÄtane. 809 const odswiez = (b) => { delete b.dataset.gzHv; b.style.removeProperty('--gz-bg'); b.style.removeProperty('--gz-c'); requestAnimationFrame(() => zapamietaj(b)); };
810 let kolejka = new Set(); 811 let zaplanowane = false; 812 const obs = new MutationObserver((lista) => { 813 lista.forEach((m) => { 814 if (m.type === 'attributes') { 815 const t = m.target; 816 if (t.matches && t.matches('button, [type="button"], [type="submit"]') && (m.attributeName === 'class' || m.attributeName === 'aria-pressed' || m.attributeName === 'aria-selected')) odswiez(t); 817 } else m.addedNodes.forEach((n) => kolejka.add(n)); 818 }); 819 if (!zaplanowane && kolejka.size) { 820 zaplanowane = true; 821 requestAnimationFrame(() => { kolejka.forEach(skanuj); kolejka = new Set(); zaplanowane = false; }); 822 } 823 }); 824 const start = () => { 825 korzenie.forEach((k) => document.querySelectorAll(k).forEach((r) => { 826 skanuj(r); 827 obs.observe(r, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'aria-pressed', 'aria-selected'] }); 828 })); 829 // ModuÅy dokÅadajÄ ce okna do <body> (galeria, okna konta). 830 obs.observe(document.body, { childList: true }); 831 }; 832 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start(); 833})(['#gk-modal-tlo', '.gk-pasek'], 'gk-antyhover');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.