1/* ===================================================================== 2 CLASSIC â TEMA JAVASCRIPT'İ 3 4 Bilerek KÃÃÃK ve baÄımsız. Bootstrap'in kendi bileÅenleri (offcanvas) 5 data-bs-* öznitelikleriyle çalıÅıyor, burada kod gerektirmiyor. 6 7 `defer` ile yükleniyor: ilk boyamayı bloklamıyor ve DOM hazır 8 olduÄunda çalıÅıyor, bu yüzden DOMContentLoaded sarmalayıcısına 9 gerek yok. 10 11 İÃİNDEKİLER 12 1. Görsel standardı (emniyet aÄı + görünme geçiÅi) 13 2. Slider motoru <- yeniden kullanılabilir, SINIF tabanlı 14 3. Mobil yapıÅkan reklam 15 4. Ãerez onayı 16 5. Yukarı çık 17 6. Arama paneli 18 7. Anket 19 8. E-bülten 20 9. Spor manÅetinin yarım geniÅliÄi 21 ===================================================================== */ 22 23(function () { 24 'use strict'; 25 26 document.documentElement.classList.remove('no-js'); 27 28 /* =================================================================== 29 1. GÃRSEL STANDARDI 30 31 TEMANIN BİRİNCİ KURALI: logo ve manÅetin ilk slaytı dıÅında her 32 görsel tembel yüklenir. 33 34 Burada iki iÅ var: 35 36 a) EMNİYET AÄI. `loading` özniteliÄi yazılmamıŠher görsele `lazy` 37 veriliyor. Kural bir Åablonda unutulsa bile iÅler. 38 Zaten yazılmıŠolana DOKUNULMUYOR â `eager` verilmiÅ LCP 39 görselini tembelleÅtirmek felaket olurdu (vega'da ölçüldü: 40 mobil LCP 6,5 sn -> 17,5 sn). 41 42 b) GÃRÃNME GEÃİÅİ. `.hp-img` CSS'te saydam baÅlıyor, yüklenince 43 `is-loaded` ile beliriyor. Ãnbellekten anında gelen görseller 44 için `complete` kontrolü ÅART: o durumda `load` olayı hiç 45 tetiklenmez ve görsel saydam kalırdı. 46 =================================================================== */ 47 (function gorseller() { 48 var hepsi = document.images; 49 50 for (var i = 0; i < hepsi.length; i++) { 51 var img = hepsi[i]; 52 53 if (!img.hasAttribute('loading')) img.setAttribute('loading', 'lazy'); 54 if (!img.hasAttribute('decoding')) img.setAttribute('decoding', 'async'); 55 56 if (!img.classList.contains('hp-img')) continue; 57 58 if (img.complete && img.naturalWidth > 0) { 59 img.classList.add('is-loaded'); 60 } else { 61 img.addEventListener('load', function () { this.classList.add('is-loaded'); }, { once: true }); 62 /* Görsel gelmezse saydam kalmasın: kırık görselde de alt metin 63 görünsün. */ 64 img.addEventListener('error', function () { this.classList.add('is-loaded'); }, { once: true }); 65 } 66 } 67 })(); 68 69 70 /* =================================================================== 71 2. SLIDER MOTORU 72 73 TASARIM KARARI â ID YOK, SINIF VAR 74 Aynı sayfada istenildiÄi kadar slider olabilir. Her örnek yalnızca 75 KENDİ kökünün içine bakıyor (kok.querySelector), dolayısıyla iki 76 slider birbirinin düÄmesini ya da slaytını asla görmüyor. ID 77 kullanılsaydı her yeni slider için benzersiz ad uydurmak gerekir, 78 blok sistemine geçince (aynı blok sayfada iki kez) çakıÅırdı. 79 80 KULLANIM 81 <div class="hp-slider" data-hp-slider data-autoplay="6000"> 82 <div class="hp-slider-viewport"> 83 <div class="hp-slider-track"> 84 <div class="hp-slide">...</div> 85 </div> 86 </div> 87 <div data-hp-slider-nav><button>
871</button>...</div> 88 <button data-hp-slider-prev> / <button data-hp-slider-next> 89 <div data-hp-slider-dots></div> 90 </div> 91 92 SEÃENEKLER 93 data-autoplay="6000" ms; 0/yok ise otomatik geçiÅ yok 94 data-axis="x|y" varsayılan x 95 data-loop="false" varsayılan true 96 data-pause-hover="false" varsayılan true 97 98 DAVRANIÅ 99 · Fare üstündeyken ve odak içerideyken otomatik geçiÅ durur. 100 · Sekme arka plana alınınca durur (boÅuna iÅ yapmasın). 101 · Dokunmatik sürükleme (yalnızca yatay). 102 · Klavye: sol/saÄ ok â dikeyde yukarı/aÅaÄı. 103 · prefers-reduced-motion açıksa otomatik geçiÅ Hİà baÅlamaz. 104 · Tek slayt varsa hiçbir Åey kurulmaz. 105 =================================================================== */ 106 function HpSlider(kok) { 107 var track = kok.querySelector('.hp-slider-track'); 108 if (!track) return null; 109 110 var slaytlar = Array.prototype.filter.call(track.children, function (el) { 111 return el.classList.contains('hp-slide'); 112 }); 113 if (slaytlar.length < 2) return null; 114 115 var dikey = kok.getAttribute('data-axis') === 'y'; 116 var sure = parseInt(kok.getAttribute('data-autoplay') || '0', 10); 117 var dongu = kok.getAttribute('data-loop') !== 'false'; 118 var durHover = kok.getAttribute('data-pause-hover') !== 'false'; 119 120 /* --------------------------------------------------------------- 121 AYNI ANDA KAà SLAYT GÃRÃNSÃN 122 123 data-per-view="5" >=1200px 124 data-per-view-lg="4" <1200px 125 data-per-view-md="3" <992px 126 data-per-view-sm="2" <768px 127 data-per-view-xs="1" <576px 128 129 Yalnızca yazılanlar geçerli; yazılmayan bir üstteki deÄeri 130 sürdürür. Varsayılan 1 â böylece manÅet ve Åerit gibi mevcut 131 slider'ların davranıÅı hiç deÄiÅmiyor. 132 133 ADIM HER ZAMAN BİR SLAYT. BeÅli blokta ok tuÅuna basınca beÅ 134 birden deÄil, tek haber kayıyor. 135 --------------------------------------------------------------- */ 136 function perViewOku() { 137 var w = window.innerWidth; 138 var v = parseInt(kok.getAttribute('data-per-view') || '1', 10); 139 140 [[1200, 'data-per-view-lg'], [992, 'data-per-view-md'], 141 [768, 'data-per-view-sm'], [576, 'data-per-view-xs']].forEach(function (k) { 142 if (w < k[0] && kok.hasAttribute(k[1])) v = parseInt(kok.getAttribute(k[1]), 10); 143 }); 144 145 return Math.max(1, Math.min(v || 1, slaytlar.length)); 146 } 147 148 var perView = perViewOku(); 149 150 /* Birden fazla slayt görünüyorsa son durak sonuncu slayt DEÄİL, 151 son slaytın saÄa dayandıÄı konum. Aksi hâlde sonda boÅluk kalır. */ 152 function sonIndeks() { return Math.max(0, slaytlar.length - perView); } 153 154 var nav = kok.querySelector('[data-hp-slider-nav]'); 155 var dots = kok.querySelector('[data-hp-slider-dots]'); 156 var prev = kok.querySelector('[data-hp-slider-prev]'); 157 var next = kok.querySelector('[data-hp-slider-next]'); 158 159 var navDugmeleri = nav ? Array.prototype.slice.call(nav.querySelectorAll('button')) : []; 160 var dotDugmeleri = []; 161 162 var indeks = 0; 163 var zaman = null; 164 var azHareket = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 165 166 /* Slayt geniÅliÄini CSS'e bildir. `.hp-slide` bu deÄere göre
167 hesaplanıyor (theme.css -> SLIDER). */ 168 kok.style.setProperty('--hp-per-view', perView); 169 170 /* --- Noktalar: kap verilmiÅse kendisi üretir --- */ 171 if (dots) { 172 slaytlar.slice(0, sonIndeks() + 1).forEach(function (_, i) { 173 var b = document.createElement('button'); 174 b.type = 'button'; 175 b.setAttribute('aria-label', (i + 1) + '. içeriÄe git'); 176 b.addEventListener('click', function () { git(i, true); }); 177 dots.appendChild(b); 178 dotDugmeleri.push(b); 179 }); 180 } 181 182 /* --- EriÅilebilirlik --- 183 Görünmeyen slayttaki baÄlantılar sekme sırasına girmemeli, yoksa 184 kullanıcı Tab'la görünmeyen içeriÄe düÅüyor. */ 185 function erisim() { 186 slaytlar.forEach(function (s, i) { 187 /* Ãoklu görünümde GÃRÃNEN ARALIK açık sayılıyor, tek slayt 188 deÄil: beÅli blokta beÅi de okunabilir ve sekmeyle 189 gezilebilir olmalı. */ 190 var aktif = (i >= indeks && i < indeks + perView); 191 192 s.setAttribute('aria-hidden', aktif ? 'false' : 'true'); 193 194 var odaklanabilir = s.querySelectorAll('a, button, input, select, textarea'); 195 Array.prototype.forEach.call(odaklanabilir, function (el) { 196 if (aktif) el.removeAttribute('tabindex'); 197 else el.setAttribute('tabindex', '-1'); 198 }); 199 }); 200 } 201 202 function isaretle() { 203 navDugmeleri.forEach(function (b, i) { 204 b.classList.toggle('active', i === indeks); 205 b.setAttribute('aria-current', i === indeks ? 'true' : 'false'); 206 }); 207 dotDugmeleri.forEach(function (b, i) { 208 b.setAttribute('aria-current', i === indeks ? 'true' : 'false'); 209 }); 210 } 211 212 /* --------------------------------------------------------------- 213 KAYDIRMA â yatayda YÃZDE, dikeyde PİKSEL 214 215 Yüzde, transform'da öÄenin KENDİ ölçüsüne göre hesaplanır. 216 217 · Yatayda sorun yok: slaytlar `flex: 0 0 100%` olduÄu için 218 track'in geniÅliÄi viewport'a eÅit kalıyor, yani -%100 tam 219 bir slayt demek. Yüzde ayrıca yeniden boyutlandırmaya 220 kendiliÄinden uyum saÄlıyor. 221 222 · Dikeyde YANLIÅ. `flex-direction: column` olunca track'in 223 yüksekliÄi TÃM slaytların toplamı oluyor (5 x 40 = 200px); 224 -%100 bir slayt deÄil, BEÅ slayt birden kaydırıyordu. 225 Son dakika Åeridinde tam olarak bu yaÅandı: Åerit peÅ peÅe 226 atlıyor, boÅluÄa düÅüyor, sonra baÅa dönüyordu. 227 228 Dikeyde slaytın track içindeki gerçek konumu (offsetTop) 229 kullanılıyor. Bu ayrıca slaytların yükseklikleri eÅit olmasa 230 bile doÄru çalıÅıyor. 231 --------------------------------------------------------------- */ 232 /* KONUMLAR ÃNCEDEN ÃLÃÃLÃYOR â zorunlu yeniden düzenlemeyi önlemek 233 için. 234 235 Ãnce her geçiÅte `offsetTop` / `offsetLeft` okunuyordu. Bu okuma 236 bir önceki `style.transform` yazımından hemen sonra geldiÄi için 237 tarayıcıyı düzeni yeniden hesaplamaya zorluyordu ("forced 238 reflow"). Canlı PageSpeed ölçümünde bu satırlar 4 ms ve 13 ms 239 olarak raporlandı. 240 241 Åimdi ölçüm bir kez yapılıyor, geçiÅlerde yalnızca yazma var. 242 Yeniden hesap sadece pencere boyutu deÄiÅince. */ 243 var konumlar = []; 244 245 function konumlariOlc() { 246 konumlar = slaytlar.map(function (s) { 247 return dikey ? s.offsetTop : s.offsetLeft; 248 }); 249 } 250 251 function ciz() { 252 if (dikey || perView > 1) { 253 /* Yüzde bu iki durumda yanlıŠsonuç verir: dikeyde track'in 254 yüksekliÄi tüm slaytların toplamı, çoklu görünümde ise araya 255 `gap` giriyor. ÃlçülmüŠpiksel konumu ikisini de çözüyor. */ 256 var d = konumlar[indeks] || 0; 257 track.style.transform = dikey 258 ? 'translateY(-' + d + 'px)' 259 : 'translateX(-' + d + 'px)'; 260 } else { 261 track.style.transform = 'translateX(' + (-100 * indeks) + '%)'; 262 } 263 isaretle(); 264 erisim(); 265 } 266 267 function git(i, kullanici) { 268 var son = sonIndeks(); 269 270 if (dongu) { 271 /* Döngü SON DURAÄA göre: 10 slayt, 5 görünür ise duraklar 272 0..5. Sonuncudan sonra baÅa dönüyor. */ 273 if (i > son) i = 0; 274 if (i < 0) i = son; 275 indeks = i; 276 } else { 277 indeks = Math.max(0, Math.min(son, i)); 278 } 279 280 ciz(); 281 282 /* Kullanıcı elle geçtiyse sayaç baÅtan baÅlasın â seÃ
282§tiÄi slayt 283 yarım saniye sonra kayıp gitmesin. */ 284 if (kullanici) baslat(); 285 } 286 287 function ileri() { git(indeks + 1); } 288 289 function durdur() { if (zaman) { clearInterval(zaman); zaman = null; } } 290 function baslat() { 291 durdur(); 292 if (sure > 0 && !azHareket) zaman = setInterval(ileri, sure); 293 } 294 295 /* --- BaÄlantılar --- 296 Masaüstünde (hover) rakamın üstüne gelmek o slayta geçirir; 297 tıklamak o habere gider. Dokunmatikte tıklama slayta geçirir. */ 298 var hoverVar = window.matchMedia('(hover: hover)').matches; 299 navDugmeleri.forEach(function (b, i) { 300 if (hoverVar) { 301 b.addEventListener('mouseenter', function () { git(i, true); }); 302 b.addEventListener('click', function () { 303 var h = b.getAttribute('data-href'); 304 if (h) { window.location.href = h; } else { git(i, true); } 305 }); 306 } else { 307 b.addEventListener('click', function () { git(i, true); }); 308 } 309 }); 310 if (prev) prev.addEventListener('click', function () { git(indeks - 1, true); }); 311 if (next) next.addEventListener('click', function () { git(indeks + 1, true); }); 312 313 if (durHover) { 314 kok.addEventListener('mouseenter', durdur); 315 kok.addEventListener('mouseleave', baslat); 316 kok.addEventListener('focusin', durdur); 317 kok.addEventListener('focusout', baslat); 318 } 319 320 document.addEventListener('visibilitychange', function () { 321 if (document.hidden) durdur(); else baslat(); 322 }); 323 324 /* --- Pencere boyutu --- 325 İki durumda yeniden hesap gerekiyor: 326 · piksel cinsinden kaydırma (dikey ya da çoklu görünüm), 327 ölçüler deÄiÅince konum kayar; 328 · kırılıma baÄlı perView deÄiÅimi (5 -> 3 -> 1). 329 Yüzdeyle çalıÅan tek slaytlı yatay slider'a dokunulmuyor. */ 330 if (dikey || perView > 1 || kok.hasAttribute('data-per-view')) { 331 var zamanlayici = null; 332 333 window.addEventListener('resize', function () { 334 clearTimeout(zamanlayici); 335 zamanlayici = setTimeout(function () { 336 var yeni = perViewOku(); 337 338 if (yeni !== perView) { 339 perView = yeni; 340 kok.style.setProperty('--hp-per-view', perView); 341 } 342 343 /* Görünen sayı artınca mevcut durak geçersiz kalabiliyor 344 (10 slayt, 5 -> 8 görünür ise son durak 5'ten 2'ye iner). */ 345 if (indeks > sonIndeks()) indeks = sonIndeks(); 346 347 konumlariOlc(); 348 ciz(); 349 }, 150); 350 }); 351 } 352 353 /* --- Klavye --- */ 354 if (!kok.hasAttribute('tabindex')) kok.setAttribute('tabindex', '0'); 355 kok.addEventListener('keydown', function (e) { 356 var ileriTus = dikey ? 'ArrowDown' : 'ArrowRight'; 357 var geriTus = dikey ? 'ArrowUp' : 'ArrowLeft'; 358 if (e.key === ileriTus) { e.preventDefault(); git(indeks + 1, true); } 359 if (e.key === geriTus) { e.preventDefault(); git(indeks - 1, true); } 360 }); 361 362 /* --- Dokunmatik sürükleme (yalnızca yatay) --- */ 363 if (!dikey) { 364 var basX = 0, basY = 0, suruklu = false, yon = null; 365 366 kok.addEventListener('touchstart', function (e) { 367 basX = e.touches[0].clientX; 368 basY = e.touches[0].clientY; 369 suruklu = true; yon = null; 370 durdur(); 371 }, { passive: true }); 372 373 kok.addEventListener('touchmove', function (e) { 374 if (!suruklu) return; 375 var dx = e.touches[0].clientX - basX; 376 var dy = e.touches[0].clientY - basY; 377 378 /* Yön BİR KEZ kararlaÅtırılıyor: kullanıcı sayfayı dikey 379 kaydırmak istiyorsa slider araya girmemeli. */ 380 if (yon === null && (Math.abs(dx) > 8 || Math.abs(dy) > 8)) { 381 yon = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y'; 382 } 383 }, { passive: true }); 384 385 kok.addEventListener('touchend', function (e) { 386 if (!suruklu) return; 387 suruklu = false; 388 389 var dx = e.changedTouches[0].clientX - basX; 390 if (yon === 'x' && Math.abs(dx) > 45) git(indeks + (dx < 0 ? 1 : -1), true); 391 else baslat(); 392 }); 393 } 394 395 konumlariOlc(); 396 ciz(); 397 baslat(); 398 399 return { git: git, ileri: ileri, durdur: durdur, baslat: baslat }; 400 } 401 402 /* Sayfadaki tüm slider'ları kur. */ 403 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-slider]'), HpSl
403ider); 404 405 406 /* =================================================================== 407 ALT KENAR PAYI â ortak hesap 408 409 Ekranın altına üç Åey sabitlenebiliyor: çerez bandı, mobil 410 yapıÅkan reklam ve yukarı çık düÄmesi. Her biri kendi payını 411 ayrı ayrı hesaplasaydı üst üste binerlerdi. 412 413 Burada TEK yerde toplanıyor: `--hp-bottom-offset` = görünür olan 414 bantların toplam yüksekliÄi. Yukarı çık düÄmesi bu deÄerin 415 üstüne oturuyor (theme.css -> .hp-top), gövdeye de aynı kadar 416 dolgu ekleniyor ki alt bilginin son satırı örtülmesin. 417 418 Ãlçüler ELEMANLARDAN okunuyor, sabit sayı yazılmadı: CSS'te bir 419 bandın yüksekliÄi deÄiÅirse burası kendiliÄinden uyar. 420 =================================================================== */ 421 var altBantlar = []; 422 423 function altPayiHesapla() { 424 var toplam = 0; 425 426 altBantlar.forEach(function (el) { 427 if (!el) return; 428 var g = getComputedStyle(el); 429 if (g.display === 'none' || g.visibility === 'hidden') return; 430 /* Bandı aÅaÄıda gizli tutan dönüÅüm varsa henüz görünmüyor. */ 431 if (el.classList.contains('hp-consent') && !el.classList.contains('is-on')) return; 432 toplam += el.offsetHeight; 433 }); 434 435 if (toplam > 0) toplam += 8; 436 437 document.documentElement.style.setProperty('--hp-bottom-offset', toplam + 'px'); 438 document.body.style.paddingBottom = toplam ? toplam + 'px' : ''; 439 } 440 441 /* Yazma (paddingBottom) ile okuma (offsetHeight) arasinda tarayiciya 442 nefes aldiriliyor: art arda gelirlerse tarayici duzeni zorla 443 yeniden hesapliyor. Canli PageSpeed olcumunde bu fonksiyon 444 58 ms zorunlu yeniden duzenleme olarak raporlanmisti. 445 446 Ust uste gelen cagrilar tek kareye toplaniyor. */ 447 var altPayBekliyor = false; 448 449 function altPayiPlanla() { 450 if (altPayBekliyor) return; 451 altPayBekliyor = true; 452 requestAnimationFrame(function () { 453 altPayBekliyor = false; 454 altPayiHesapla(); 455 }); 456 } 457 458 window.addEventListener('resize', altPayiPlanla); 459 460 461 /* =================================================================== 462 3. MOBİL YAPIÅKAN REKLAM 463 464 İki iÅ: 465 466 a) GÃVDE DOLGUSU. Åerit `position:fixed` olduÄu için akıÅtan 467 çıkıyor ve alt bilginin son satırını örtüyordu. YüksekliÄi 468 kadar dolgu ekleniyor â sabit sayı YAZILMADI, ölçü elemandan 469 okunuyor ki CSS'te boyut deÄiÅirse burası da uysun. 470 471 b) KAPATMA. Kapatılamayan yapıÅkan reklam hem kullanıcıyı 472 kaçırıyor hem de Google'ın "araya giren geçiÅ reklamı" 473 ölçütüne takılıyor. Kapatınca dolgu da geri alınıyor. 474 475 Karar oturum boyunca hatırlanıyor: her sayfada yeniden kapatmak 476 zorunda kalmak, reklamın kendisinden daha rahatsız edici. 477 =================================================================== */ 478 var mobilReklam = document.querySelector('[data-hp-ad-mobile]'); 479 if (mobilReklam) { 480 /* Alt pay artık ortak hesapta (altPayiHesapla); bu bant da listeye 481 giriyor. Böylece çerez bandıyla üst üste binmiyorlar. */ 482 altBantlar.push(mobilReklam); 483 484 var kapat = function (hatirla) { 485 mobilReklam.classList.add('is-closed'); 486 altPayiPlanla(); 487 if (hatirla) { 488 try { sessionStorage.setItem('hpAdMobileKapali', '1'); } catch (e) {} 489 } 490 }; 491 492 /* Gizli pencerede sessionStorage eriÅimi istisna atabiliyor. */ 493 var kapaliMi = false; 494 try { kapaliMi = sessionStorage.getItem('hpAdMobileKapali') === '1'; } catch (e) {} 495 496 if (kapaliMi) { 497 kapat(false); 498 } else { 499 altPayiPlanla(); 500 501 var kapatDugmesi = mobilReklam.querySelector('.hp-ad-close'); 502 if (kapatDugmesi) kapatDugmesi.addEventListener('click', function () { kapat(true); }); 503 } 504 } 505 506 507 /* =================================================================== 508 4. ÃEREZ ONAYI 509 510 NEDEN ÃEREZ, localStorage DEÄİL 511 PHP aÅamasında tema, Google Analytics ve reklam etiketlerini 512 BASMADAN ÃNCE onayı kontrol edecek. `$_COOKIE['hp_consent']` 513 sunucuda okunabiliyor; localStorage okunamıyor. YanlıŠseçim 514 sonradan mimariyi bozardı. 515 516 SÃRELER BİLEREK FARKLI (data-* ile ayarlanıyor) 517 kabul -> 180 gün. Onay veren kullanıcıyı iki günde bir yeniden 518 rahatsız etmek hem kötü deneyim hem gereksiz; KVKK'da 519 onay geri çekilene kadar geçerli. 520 ret -> 2 gün. Kullanıcı fikrini deÄiÅtirmiÅ olabilir, ama 521 her sayfa yenilemesinde sormak taciz olur. 522 523 KAYIT BİÃİMİ 524 hp_consent = {"v":1,"a":0|1,"m":0|1,"t":<unix>} (URL kodlu) 525 v = sürüm. Politika deÄiÅirse artırılır ve eski onaylar geçersiz 526 sayılır â tekrar sorulur. 527 528 DIÅARI AÃILAN ARAYÃZ 529 window.hpConsent.get() -> {a,m} ya da null 530 window.hpConsent.ac() -> analitik izni var mı 531 window.hpConsent.reklam() -> reklam izni var mı 532 'hp:consent' olayı -> karar deÄiÅince tetiklenir 533 Analytics/reklam betikleri bu olayı dinleyip kendilerini o anda 534 yükleyebilir. 535 =================================================================== */ 536 var cerezBant = document.querySelector('[data-hp-consent]'); 537 538 var hpConsent = (function () { 539 var AD = 'hp_consent'; 540 var SURUM = 1; 541 542 function oku() { 543 var m = document.cookie.match(/(?:^|;\s*)hp_consent=([^;]*)/); 544 if (!m) return null; 545
546 try { 547 var v = JSON.parse(decodeURIComponent(m[1])); 548 /* Politika sürümü deÄiÅtiyse eski onay geçersiz. */ 549 if (!v || v.v !== SURUM) return null; 550 return v; 551 } catch (e) { return null; } 552 } 553 554 function yaz(analitik, reklam, gun) { 555 var veri = { v: SURUM, a: analitik ? 1 : 0, m: reklam ? 1 : 0, t: Math.floor(Date.now() / 1000) }; 556 557 var parca = AD + '=' + encodeURIComponent(JSON.stringify(veri)) 558 + ';path=/;max-age=' + (gun * 86400) + ';SameSite=Lax'; 559 560 /* HTTPS'te Secure ekleniyor; http'de eklenirse çerez hiç 561 yazılmaz ve onay her sayfada yeniden sorulurdu. */ 562 if (location.protocol === 'https:') parca += ';Secure'; 563 564 document.cookie = parca; 565 566 try { 567 document.dispatchEvent(new CustomEvent('hp:consent', { detail: veri })); 568 } catch (e) {} 569 570 return veri; 571 } 572 573 return { 574 get: oku, 575 set: yaz, 576 ac: function () { var v = oku(); return !!(v && v.a); }, 577 reklam: function () { var v = oku(); return !!(v && v.m); } 578 }; 579 })(); 580 581 window.hpConsent = hpConsent; 582 583 if (cerezBant) { 584 altBantlar.push(cerezBant); 585 586 var kabulGun = parseInt(cerezBant.getAttribute('data-accept-days') || '180', 10); 587 var retGun = parseInt(cerezBant.getAttribute('data-reject-days') || '2', 10); 588 589 var detay = cerezBant.querySelector('#hpConsentDetail'); 590 var ayarDugme = cerezBant.querySelector('[data-hp-consent-settings]'); 591 var anahtarlar = cerezBant.querySelectorAll('[data-hp-consent-cat]'); 592 593 function bandiGoster() { 594 /* Kök öÄeye sınıf: bant açıkken mobil yapıÅkan reklam 595 gizleniyor (onay alınmadan reklam çerezi çalıÅmamalı). */ 596 document.documentElement.classList.add('hp-consent-acik'); 597 598 /* Bir sonraki kareye bırakılıyor: aynı karede sınıf eklenirse 599 tarayıcı geçiÅi atlar ve bant zıplayarak belirir. */ 600 requestAnimationFrame(function () { 601 cerezBant.classList.add('is-on'); 602 altPayiPlanla(); 603 }); 604 } 605 606 function bandiGizle() { 607 cerezBant.classList.remove('is-on'); 608 document.documentElement.classList.remove('hp-consent-acik'); 609 if (detay) { detay.hidden = true; } 610 if (ayarDugme) ayarDugme.setAttribute('aria-expanded', 'false'); 611 612 /* GeçiÅ bitince payı geri al â hemen alınırsa bant kayarken 613 sayfa bir anda yukarı sıçrıyor. */ 614 setTimeout(altPayiHesapla, 380); 615 } 616 617 function karar(analitik, reklam, gun) { 618 hpConsent.set(analitik, reklam, gun); 619 bandiGizle(); 620 } 621 622 /* --- DüÄmeler --- */ 623 var kabulBtn = cerezBant.querySelector('[data-hp-consent-accept]'); 624 var retBtn = cerezBant.querySelector('[data-hp-consent-reject]'); 625 var kaydetBtn= cerezBant.querySelector('[data-hp-consent-save]'); 626 627 if (kabulBtn) kabulBtn.addEventListener('click', function () { karar(true, true, kabulGun); }); 628 if (retBtn) retBtn.addEventListener('click', function () { karar(false, false, retGun); }); 629 630 if (kaydetBtn) kaydetBtn.addEventListener('click', function () { 631 var a = false, m = false; 632 Array.prototype.forEach.call(anahtarlar, function (el) { 633 if (el.getAttribute('data-hp-consent-cat') === 'analitik') a = el.checked; 634 if (el.getAttribute('data-hp-consent-cat') === 'reklam') m = el.checked; 635 }); 636 /* Hiçbirini seçmediyse bu bir REDDİR: kısa süre uygulanıyor. */ 637 karar(a, m, (a || m) ? kabulGun : retGun); 638 }); 639 640 if (ayarDugme && detay) { 641 ayarDugme.addEventListener('click', function () { 642 var acik = !detay.hidden; 643 detay.hidden = acik; 644 ayarDugme.setAttribute('aria-expanded', String(!acik)); 645 altPayiPlanla(); 646 }); 647 } 648 649 /* --- Ayarları sonradan açan baÄlantılar (alt bilgi, bant metni) --- */ 650 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-consent-open]'), function (a) { 651 a.addEventListener('click', function (e) { 652 e.preventDefault(); 653 654 var v = hpConsent.get(); 655 Array.prototype.forEach.call(anahtarlar, function (el) { 656 var k = el.getAttribute('data-hp-consent-cat'); 657 el.checked = !!(v && ((k === 'analitik' && v.a) || (k === 'reklam' && v.m))); 658 }); 659 660 if (detay) detay.hidden = false;
661 if (ayarDugme) ayarDugme.setAttribute('aria-expanded', 'true'); 662 bandiGoster(); 663 }); 664 }); 665 666 /* --- İlk açılıÅ: karar yoksa sor --- */ 667 if (!hpConsent.get()) { 668 /* Küçük gecikme: sayfa oturmadan bant belirirse hem dikkat 669 daÄıtıyor hem de ilk boyamayla yarıÅıyor. */ 670 setTimeout(bandiGoster, 700); 671 } 672 } 673 674 675 /* =================================================================== 676 5. YUKARI ÃIK 677 678 Ãç iÅ tek yerde: 679 · 400px'ten sonra düÄmeyi göster 680 · okuma oranını `--hp-scroll` (0-100) olarak yaz â düÄmenin 681 çevresindeki halka bu deÄerle doluyor 682 · tıklanınca baÅa dön 683 684 KAYDIRMA OLAYI PAHALIDIR. Her `scroll` tetiklenmesinde okuma 685 yapmak (scrollTop, scrollHeight) zorunlu yeniden düzenlemeye yol 686 açıyor â canlı sitede jQuery kaynaklı 144 ms'lik reflow tam olarak 687 böyle oluÅmuÅtu. Burada iÅ `requestAnimationFrame` ile kareye 688 baÄlanıyor: saniyede yüzlerce kez deÄil, kare baÅına en fazla bir 689 kez hesaplanıyor. 690 =================================================================== */ 691 var ustDugme = document.querySelector('[data-hp-top]'); 692 if (ustDugme) { 693 /* Ad ÃNEKLİ: bu dosyanın tamamı tek bir IIFE ve `var` fonksiyon 694 kapsamlı â aynı adı iki blokta kullanmak AYNI deÄiÅkeni paylaÅmak 695 demek. Nitekim haber akıÅındaki throttle bayraÄı da `bekliyor` 696 adını taÅıyordu ve iki scroll iÅleyicisi birbirini kilitliyordu 697 (ölçüldü: ilerleme çubuÄu hiç güncellenmiyordu). */ 698 var ustBekliyor = false; 699 700 var guncelle = function () { 701 ustBekliyor = false; 702 703 var y = window.pageYOffset || document.documentElement.scrollTop; 704 var boy = document.documentElement.scrollHeight - window.innerHeight; 705 var oran = boy > 0 ? Math.min(100, Math.round((y / boy) * 100)) : 0; 706 707 ustDugme.style.setProperty('--hp-scroll', oran); 708 ustDugme.classList.toggle('is-on', y > 400); 709 }; 710 711 window.addEventListener('scroll', function () { 712 if (ustBekliyor) return; 713 ustBekliyor = true; 714 requestAnimationFrame(guncelle); 715 }, { passive: true }); 716 717 window.addEventListener('resize', guncelle); 718 guncelle(); 719 720 ustDugme.addEventListener('click', function () { 721 var azHareket = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 722 window.scrollTo({ top: 0, behavior: azHareket ? 'auto' : 'smooth' }); 723 }); 724 } 725 726 727 /* =================================================================== 728 6. ARAMA PANELİ 729 Panel açılınca imleç kutuya gitsin â düÄmeye basan kullanıcı 730 yazmak istiyordur. 731 =================================================================== */ 732 var aramaPanel = document.querySelector('[data-hp-search]'); 733 if (aramaPanel) { 734 aramaPanel.addEventListener('shown.bs.offcanvas', function () { 735 var kutu = aramaPanel.querySelector('input[type="search"]'); 736 if (kutu) kutu.focus(); 737 }); 738 } 739 740 741 /* =================================================================== 742 ORTAK â ajax.php'ye gönderim 743 744 Her iki form da aynı uca gidiyor, tek fark `do` alanı. Formun 745 kendi alanları olduÄu gibi taÅınıyor; JS'in hangi alanların 746 gerektiÄini bilmesine gerek yok (bal küpü de böyle geçiyor). 747 748 Uç bulunamazsa/aÄ koparsa söz REDDEDİLİYOR ve çaÄıran taraf 749 formu normal POST olarak gönderiyor â yani JS'in bozulduÄu 750 durumda bile oy ve kayıt kaybolmuyor. 751 =================================================================== */ 752 function hpGonder(form, is) { 753 var adres = form.getAttribute('data-url'); 754 if (!adres || !window.fetch) return Promise.reject(); 755 756 var veri = new FormData(form); 757 veri.append('do', is); 758 759 return fetch(adres, { 760 method: 'POST', 761 body: veri, 762 credentials: 'same-origin', 763 headers: { 'X-Requested-With': 'XMLHttpRequest' } 764 }).then(function (r) { return r.json(); }); 765 } 766 767 /* DüÄmeyi kilitle / çöz â çift tıklamada iki kayıt açılmasın. */ 768 function hpDugme(form, metin) { 769 var b = form.querySelector('button[type="submit"]'); 770 if (!b) return null;
771 if (!b.getAttribute('data-eski')) b.setAttribute('data-eski', b.textContent); 772 b.disabled = !!metin; 773 b.textContent = metin || b.getAttribute('data-eski'); 774 return b; 775 } 776 777 778 /* =================================================================== 779 7. ANKET 780 781 Form JS kapalıyken sıradan bir POST olarak aynı sayfaya gidiyor 782 (blocks/poll_newsletter.php onu iÅliyor). JS varsa burada 783 yakalanıp ajax.php'ye gönderiliyor ve SAYFA YENİLENMEDEN sonuç 784 görünümüne geçiliyor. 785 786 Sonuç çubukları sunucudan gelen yüzdelerle dolduruluyor â 787 istemcide hesaplamıyoruz; oyun gerçekten sayılıp sayılmadıÄını 788 yalnızca sunucu bilir (aynı IP ikinci kez oy veremiyor). 789 =================================================================== */ 790 /* SAYFADA BİRDEN ÃOK ANKET OLABİLİR: yan blok panelden yönetiliyor ve 791 anket kutusu hem ana sayfa bloÄunda hem yan blokta bulunabilir. 792 Tekil `querySelector` yalnızca ilkini baÄlıyor, ikincisinde oy 793 verme sessizce çalıÅmıyordu. Her form kendi kapsamında. */ 794 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-poll-form]'), function (pollForm) { 795 var pollKart = pollForm.closest('[data-hp-poll-card]') || document; 796 var pollSonuc = pollKart.querySelector('[data-hp-poll-results]'); 797 var pollMesaj = pollKart.querySelector('[data-hp-poll-msg]'); 798 var pollToplam = pollKart.querySelector('[data-hp-poll-total]'); 799 var pollAlt = pollKart.querySelector('[data-hp-poll-foot]'); 800 801 var pollMesajYaz = function (metin) { 802 if (!pollMesaj) return; 803 pollMesaj.textContent = metin || ''; 804 pollMesaj.classList.toggle('d-none', !metin); 805 }; 806 807 /* Sunucudan gelen anketi sonuç çubuklarına yaz. */ 808 var pollSonucYaz = function (anket) { 809 if (!pollSonuc || !anket || !anket.answers) return; 810 811 var toplam = 0; 812 anket.answers.forEach(function (s) { toplam += parseInt(s.hit, 10) || 0; }); 813 if (pollToplam) pollToplam.textContent = toplam.toLocaleString('tr-TR'); 814 815 anket.answers.forEach(function (s) { 816 var kutu = pollSonuc.querySelector('[data-hp-poll-result="' + s.id + '"]'); 817 if (!kutu) return; 818 var yuzde = parseFloat(s.percent) || 0; 819 var sayi = kutu.querySelector('.hp-h-sm'); 820 var cubuk = kutu.querySelector('.hp-poll-bar > span'); 821 if (sayi) sayi.textContent = String(yuzde).replace('.', ',') + '%'; 822 if (cubuk) cubuk.style.width = yuzde + '%'; 823 }); 824 825 pollSonuc.classList.remove('d-none'); 826 pollForm.classList.add('d-none'); 827 if (pollAlt) pollAlt.classList.remove('d-none'); 828 }; 829 830 pollForm.addEventListener('submit', function (e) { 831 if (!pollForm.querySelector('input[name="answer"]:checked')) return; // tarayıcı uyarsın 832 833 e.preventDefault(); 834 pollMesajYaz(''); 835 hpDugme(pollForm, 'Gönderiliyorâ¦'); 836 837 hpGonder(pollForm, 'poll_vote').then(function (c) { 838 hpDugme(pollForm, ''); 839 pollMesajYaz(c.mesaj); 840 if (c.ok) pollSonucYaz(c.anket); 841 }).catch(function () { 842 /* AÄ/uç sorunu: formu normal yoldan gönder, oy kaybolmasın. */ 843 pollForm.submit(); 844 }); 845 }); 846 }); 847 848 849 /* =================================================================== 850 8. E-BÃLTEN 851 852 Aynı desen: JS kapalıyken sıradan POST, açıkken ajax.php. 853 Adres doÄrulamasını tarayıcı yapıyor (required + type=email); 854 buradaki iÅ yalnızca sunucunun yanıtını göstermek. 855 856 BaÅarıda kutu temizleniyor ama düÄme "Kaydedildi" diye SABİT 857 kalmıyor â eski sürümde öyleydi ve ikinci bir adres eklemek 858 isteyen sayfayı yenilemek zorunda kalıyordu. 859 =================================================================== */ 860 /* Aynı gerekçe: bülten kutusu hem içerikte hem yan blokta olabilir. 861 Her form kendi kapsamında baÄlanıyor. */ 862 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-newsletter]'), function (nlForm) { 863 var nlMesaj = nlForm.querySelector('[data-hp-newsletter-msg]'); 864 865 var nlMesajYaz = function (metin, ok) { 866 if (!nlMesaj) return; 867 nlMesaj.textContent = metin || ''; 868 nlMesaj.style.color = ok ? 'var(--hp-primary-fixed-dim)' : '#ff9c9c'; 869 nlMesaj.classList.toggle('d-none', !metin); 870 }; 871 872 nlForm.addEventListener('submit', function (e) { 873 e.preventDefault(); 874 nlMesajYaz(''); 875 hpDugme(nlForm, 'Gönderiliyorâ¦'); 876 877 hpGonder(nlForm, 'newsletter').then(function (c) { 878 hpDugme(nlForm, ''); 879 nlMesajYaz(c.mesaj, c.ok); 880 if (c.ok) { 881 var kutu = nlForm.querySelector('input[type="email"]'); 882 if (kutu) kutu.value = ''; 883 } 884 }).catch(function () { 885 nlForm.submit(); 886 }); 887 }); 888 }); 889 890 891 /* =================================================================== 892 9. SPOR MANÅETİNİN YARIM GENİÅLİÄİ 893 max-width:50% yalnızca yan yana dizilimde doÄru; mobilde alt alta 894 inince görsel yarım kalıyordu. 895 =================================================================== */ 896 var yarim = document.querySelector('[data-hp-half]'); 897 if (yarim) { 898 var uygula = function () { 899 yarim.style.maxWidth = window.matchMedia('(min-width: 768px)').matches ? '50%' : '100%'; 900 }; 901 uygula(); 902 window.addEventListener('resize', uygula); 903 } 904 905 /* =================================================================== 906 10. GEÃİŠ/ ÃN REKLAM KAPLAMASI (yuva 5, 27, 31) 907 908 TEK BİLEÅEN, Ãà KULLANIM: 909 yuva 5 site önü tüm ekran, sayfa açılınca 910 yuva 27 galeri geçiÅi sahnenin üstünde, iÅaretli fotoÄrafta 911 yuva 31 video player önü oynatıcının üstünde 912 913 İÅaretlemeyi helpers.php -> hpGecisReklam() üretiyor; buradaki kod 914 yalnızca davranıÅı veriyor: 915 · geri sayım biter bitmez kendiliÄinden kapanır 916 · "Reklamı Geç" ve Esc her an kapatır 917 · `data-cerez` varsa SIKLIK SINIRI uygulanır 918 919 ÃEREZ AÃILIÅTA YAZILIYOR â DÃZELTİLEN HATA (24 Eyl 2026). 920 Ãnceki sürümde çerez yalnızca `siteOnuKapat(true)` yolundan, yani 921 "Reklamı Geç"e basılınca yazılıyordu. Geri sayım kendiliÄinden 922 bitince `siteOnuKapat(false)` çaÄrılıyor ve çerez YAZILMIYORDU; 923 okurun hiçbir Åeye dokunmadıÄı normal akıÅta site önü HER
923SAYFADA 924 yeniden açılıyordu. Artık çerez kaplama AÃILDIÄI anda yazılıyor: 925 gösterim gerçekleÅti, sayaç iÅlesin â nasıl kapandıÄı önemsiz. 926 927 `window.hpGecisReklam(el)` dıÅarıya açık: galeri geçiÅinde 928 kaplamayı gallery.js fotoÄraf deÄiÅince açıyor. 929 =================================================================== */ 930 window.hpGecisReklam = (function () { 931 var cerezOku = function (ad) { 932 var m = document.cookie.match('(^|; )' + ad + '=([^;]*)'); 933 return m ? decodeURIComponent(m[2]) : null; 934 }; 935 936 var cerezYaz = function (ad, deger, saat) { 937 var son = new Date(Date.now() + saat * 3600 * 1000); 938 document.cookie = ad + '=' + encodeURIComponent(deger) 939 + ';expires=' + son.toUTCString() + ';path=/;SameSite=Lax'; 940 }; 941 942 return function kur(el) { 943 if (!el) return null; 944 if (el.hpGecisKurulu) return el.hpGecisKurulu; 945 946 var sayacKutu = el.querySelector('[data-hp-interstitial-sec]'); 947 var gecDugme = el.querySelector('[data-hp-interstitial-skip]'); 948 var kapsam = el.getAttribute('data-kapsam') === 'kutu' ? 'kutu' : 'tam'; 949 var cerezAd = el.getAttribute('data-cerez') || ''; 950 var cerezSaat = parseInt(el.getAttribute('data-saat'), 10) || 0; 951 var sure = parseInt(el.getAttribute('data-sure'), 10); 952 if (isNaN(sure) || sure < 0) sure = 10; 953 954 var zamanlayici = null; 955 var kalan = sure; 956 957 /* Ãerezde HOST tutuluyor: aynı tarayıcıda birden çok site 958 açıksa biri diÄerinin kaplamasını susturmasın. */ 959 var susturuldu = function () { 960 return cerezAd !== '' && cerezOku(cerezAd) === window.location.host; 961 }; 962 963 var kapat = function () { 964 if (zamanlayici) { clearInterval(zamanlayici); zamanlayici = null; } 965 el.classList.remove('is-acik'); 966 if (kapsam === 'tam') document.body.style.overflow = ''; 967 document.removeEventListener('keydown', escDinle); 968 }; 969 970 var escDinle = function (e) { 971 if (e.key === 'Escape' || e.keyCode === 27) kapat(); 972 }; 973 974 var yaz = function () { 975 if (!sayacKutu) return; 976 sayacKutu.textContent = sure > 0 ? kalan + ' saniye kaldı' : ''; 977 }; 978 979 var ac = function () { 980 if (susturuldu()) { 981 if (el.parentNode) el.parentNode.removeChild(el); 982 return false; 983 } 984 985 /* Gösterim gerçekleÅti: sıklık sayacı Åimdi iÅlesin. */ 986 if (cerezAd !== '' && cerezSaat > 0) { 987 cerezYaz(cerezAd, window.location.host, cerezSaat); 988 } 989 990 kalan = sure; 991 el.classList.add('is-acik'); 992 if (kapsam === 'tam') document.body.style.overflow = 'hidden'; 993 document.addEventListener('keydown', escDinle); 994 yaz(); 995 996 if (sure > 0) { 997 zamanlayici = setInterval(function () { 998 kalan--; 999 if (kalan <= 0) { kapat(); return; } 1000 yaz(); 1001 }, 1000); 1002 } 1003 return true; 1004 }; 1005 1006 if (gecDugme) gecDugme.addEventListener('click', kapat); 1007 1008 el.hpGecisKurulu = { ac: ac, kapat: kapat, susturuldu: susturuldu }; 1009 return el.hpGecisKurulu; 1010 }; 1011 })(); 1012 1013 /* `data-hp-interstitial-oto` olanlar kendiliÄinden açılıyor. 1014 Galeri geçiÅinde bu öznitelik YOK: kaplamayı fotoÄraf deÄiÅince 1015 gallery.js açıyor. */ 1016 Array.prototype.forEach.call( 1017 document.querySelectorAll('[data-hp-interstitial]'), 1018 function (el) { 1019 var k = window.hpGecisReklam(el); 1020 if (k && el.hasAttribute('data-hp-interstitial-oto')) k.ac(); 1021 } 1022 ); 1023 1024 /* =================================================================== 1025 11. KATEGORİ AKIÅI â "Daha fazla yükle" + otomatik devam 1026 1027 Tasarımda hem düÄme hem klasik 1-2-3 sayfalama vardı; ikisi birden 1028 olmaz. Bırakılan düÄme, ama davranıÅı ikisinin ortası: 1029 1030 · DüÄme HTML'de gerçek bir BAÄLANTI (?sayfa=2). JS yoksa ya da 1031 bu betik patlarsa okur yine sonraki haberlere ulaÅıyor, arama 1032 motoru da akıÅın devamını görebiliyor. 1033 · JS varsa tıklama engellenip parça buraya ekleniyor. 1034 · DüÄme görünür alana girince İLK Ãà SEFER kendiliÄinden 1035 yükleniyor â sonsuz kaydırma hissi. 1036 · Ãçten sonra otomatik mod kapanıyor. Sebep: kesintisiz kaydırma
1037 alt bilgiye (künye, iletiÅim, KVKK) ulaÅmayı imkânsız kılıyor. 1038 1039 `history.replaceState` ile adres çubuÄuna ?sayfa=N yazılıyor: 1040 okur sayfayı yenilerse ya da baÄlantıyı paylaÅırsa kaldıÄı yeri 1041 kaybetmesin. 1042 =================================================================== */ 1043 var katMore = document.querySelector('[data-hp-cat-more]'); 1044 var katList = document.querySelector('[data-hp-cat-list]'); 1045 1046 if (katMore && katList) { 1047 var katBtn = katMore.querySelector('a, button'); 1048 var katSon = katMore.querySelector('[data-hp-cat-son]'); 1049 var katUrl = katMore.getAttribute('data-url'); 1050 var katKat = parseInt(katMore.getAttribute('data-kategori'), 10) || 0; 1051 var katStart = parseInt(katMore.getAttribute('data-start'), 10) || 0; 1052 var katParti = parseInt(katMore.getAttribute('data-parti'), 10) || 12; 1053 var katTavan = parseInt(katMore.getAttribute('data-tavan'), 10) || 200; 1054 1055 var katMesgul = false; 1056 var katBitti = false; 1057 var katOtoKalan = 3; // kaç kez kendiliÄinden yüklensin 1058 var katSayfa = 1; 1059 var katGozcu = null; 1060 1061 var katDur = function () { 1062 katBitti = true; 1063 if (katGozcu) katGozcu.disconnect(); 1064 if (katBtn) katBtn.remove(); 1065 if (katSon) katSon.classList.remove('d-none'); 1066 }; 1067 1068 var katYukle = function () { 1069 if (katMesgul || katBitti || !katUrl || !katKat) return; 1070 if (katStart >= katTavan) { katDur(); return; } 1071 1072 katMesgul = true; 1073 if (katBtn) { 1074 katBtn.setAttribute('aria-busy', 'true'); 1075 katBtn.dataset.eski = katBtn.dataset.eski || katBtn.textContent.trim(); 1076 katBtn.textContent = 'Yükleniyorâ¦'; 1077 } 1078 1079 var veri = new FormData(); 1080 veri.append('kategori', katKat); 1081 veri.append('start', katStart); 1082 veri.append('parti', katParti); 1083 1084 fetch(katUrl, { method: 'POST', body: veri, credentials: 'same-origin' }) 1085 .then(function (r) { return r.json(); }) 1086 .then(function (c) { 1087 katMesgul = false; 1088 if (katBtn) { 1089 katBtn.removeAttribute('aria-busy'); 1090 katBtn.textContent = katBtn.dataset.eski; 1091 } 1092 1093 if (!c || !c.adet) { katDur(); return; } 1094 1095 /* Parçayı DOM'a çevirip ekliyoruz. insertAdjacentHTML tek 1096 seferde ekliyor; her kart için ayrı ekleme yapmak sayfayı 1097 kart sayısı kadar yeniden yerleÅtirirdi. */ 1098 katList.insertAdjacentHTML('beforeend', c.html); 1099 1100 katStart += c.adet; 1101 katSayfa += 1; 1102 katMore.setAttribute('data-start', katStart); 1103 1104 try { 1105 var adres = new URL(window.location.href); 1106 adres.searchParams.set('sayfa', katSayfa); 1107 history.replaceState(null, '', adres.toString()); 1108 if (katBtn && katBtn.tagName === 'A') { 1109 var ileri = new URL(window.location.href); 1110 ileri.searchParams.set('sayfa', katSayfa + 1); 1111 katBtn.setAttribute('href', ileri.toString()); 1112 } 1113 } catch (e) {} 1114 1115 /* Gelen parti istenenden azsa kategori bitti demektir. */ 1116 if (c.adet < katParti || katStart >= katTavan) katDur(); 1117 }) 1118 .catch(function () { 1119 katMesgul = false; 1120 if (katBtn) { 1121 katBtn.removeAttribute('aria-busy'); 1122 katBtn.textContent = katBtn.dataset.eski; 1123 } 1124 /* AÄ hatası: otomatik modu kapat, düÄme elle denensin. 1125 Aksi hâlde gözcü her kaydırmada yeniden deneyip 1126 sunucuyu döverdi. */ 1127 katOtoKalan = 0; 1128 if (katGozcu) katGozcu.disconnect(); 1129 }); 1130 }; 1131 1132 if (katBtn) { 1133 katBtn.addEventListener('click', function (e) { 1134 e.preventDefault(); 1135 katOtoKalan = 0; // elle tıklandıysa okur kontrolü aldı 1136 if (katGozcu) katGozcu.disconnect(); 1137 katYukle(); 1138 }); 1139 } 1140 1141 if ('IntersectionObserver' in window) { 1142 katGozcu = new IntersectionObserver(function (girisler) { 1143 girisler.forEach(function (g) {
1144 if (!g.isIntersecting || katOtoKalan <= 0 || katBitti) return; 1145 katOtoKalan -= 1; 1146 if (katOtoKalan <= 0 && katGozcu) katGozcu.disconnect(); 1147 katYukle(); 1148 }); 1149 }, { rootMargin: '300px 0px' }); // ekrana girmeden biraz önce 1150 1151 katGozcu.observe(katMore); 1152 } 1153 } 1154 1155 /* =================================================================== 1156 12. HABER DETAY â araç çubuÄu 1157 1158 Panoya kopyala, yazdır ve yazı boyutu. Ãçü de sonsuz okumayla 1159 gelen makalelerde de çalıÅmalı, o yüzden tek tek dinleyici 1160 baÄlanmıyor: gövdeye bir dinleyici konup olay yükseltmesiyle 1161 (event delegation) yakalanıyor. Sonradan eklenen düÄmeler de 1162 kendiliÄinden çalıÅıyor. 1163 =================================================================== */ 1164 document.addEventListener('click', function (e) { 1165 var kop = e.target.closest ? e.target.closest('[data-hp-kopyala]') : null; 1166 if (kop) { 1167 var adres = kop.getAttribute('data-hp-kopyala'); 1168 var bitir = function (metin) { 1169 var eski = kop.getAttribute('title'); 1170 kop.setAttribute('title', metin); 1171 kop.classList.add('is-ok'); 1172 setTimeout(function () { kop.setAttribute('title', eski); kop.classList.remove('is-ok'); }, 1800); 1173 }; 1174 if (navigator.clipboard && window.isSecureContext) { 1175 navigator.clipboard.writeText(adres).then(function () { bitir('Kopyalandı'); }) 1176 .catch(function () { bitir('Kopyalanamadı'); }); 1177 } else { 1178 /* http:// üzerinden clipboard API yok; eski yöntem. */ 1179 try { 1180 var t = document.createElement('textarea'); 1181 t.value = adres; t.style.position = 'fixed'; t.style.opacity = '0'; 1182 document.body.appendChild(t); t.select(); document.execCommand('copy'); 1183 document.body.removeChild(t); bitir('Kopyalandı'); 1184 } catch (h) { bitir('Kopyalanamadı'); } 1185 } 1186 return; 1187 } 1188 1189 if (e.target.closest && e.target.closest('[data-hp-yazdir]')) { window.print(); return; } 1190 1191 var fbtn = e.target.closest 1192 ? e.target.closest('[data-hp-font-az],[data-hp-font-cok],[data-hp-font-sifir]') 1193 : null; 1194 if (fbtn) { 1195 var kok = document.documentElement; 1196 var simdi; 1197 /* Sıfırlama YENİ: iki kademe büyüten okur eskiden geri dönmek 1198 için Aâ'ye iki kez basmak zorundaydı. */ 1199 if (fbtn.hasAttribute('data-hp-font-sifir')) { 1200 simdi = 0; 1201 } else { 1202 simdi = parseInt(kok.getAttribute('data-hp-okuma') || '0', 10); 1203 simdi += fbtn.hasAttribute('data-hp-font-cok') ? 1 : -1; 1204 /* TABAN 0 (17px). Eskiden -1 kademesi vardı ve 15px'e 1205 düÅüyordu; gövde metni için fazla küçüktü. Artık 1206 yalnızca yukarı: 17 â 19 â 21 â 23. */ 1207 simdi = Math.max(0, Math.min(3, simdi)); // 0 .. +3 arası 1208 } 1209 kok.setAttribute('data-hp-okuma', simdi); 1210 try { localStorage.setItem('hpOkumaBoyut', simdi); } catch (h) {} 1211 } 1212 }); 1213 1214 /* Yazı boyutu tercihi ziyaretçide kalıcı. */ 1215 try { 1216 var kayitliBoyut = parseInt(localStorage.getItem('hpOkumaBoyut'), 10); 1217 /* Eski sürümde -1 kademesi vardı; o tercihi taÅıyan okur 1218 artık geçersiz bir deÄerle gelmesin diye kıstırılıyor. */ 1219 if (!isNaN(kayitliBoyut)) { 1220 kayitliBoyut = Math.max(0, Math.min(3, kayitliBoyut)); 1221 document.documentElement.setAttribute('data-hp-okuma', kayitliBoyut); 1222 } 1223 } catch (e) {} 1224 1225 1226 /* =================================================================== 1227 13. SONSUZ HABER OKUMA 1228 1229 Okur haberin sonuna gelince sıradaki haber altına ekleniyor. 1230 1231 VEGA'DAKİ Ãà SORUN VE ÃÃZÃMà 1232 1) Tüm sayfa indiriliyordu â uç yalnızca makale parçası veriyor. 1233 2) Adres/baÅlık sadece EKLEME anında güncelleniyordu; okur yukarı 1234 kaydırıp önceki habere dönünce adres yanlıŠkalıyordu 1235 â burada adres KAYDIRMAYLA birlikte, ekranda hangi makale 1236 varsa ona göre güncelleniyor (aÅaÄıdaki gozcuAktif). 1237 3) DıŠCDN kütüphanesi â kendi kodumuz. 1238 1239 DURMA KURALI: `data-oto` kadar otomatik yüklemeden sonra otomatik 1240 mod kapanıyor, devamı tıklamaya baÄlı. Kesintisiz akıŠokuru alt 1241 bilgiye (künye, KVKK, iletiÅim) hiç ulaÅtırmıyor. 1242 =================================================================== */ 1243 var akisKok = document.querySelector('[data-hp-article-stream]'); 1244 var nextZone = document.querySelector('[data-hp-next-zone]'); 1245 1246 if (akisKok && nextZone) { 1247 var nextLink = nextZone.querySelector('[data-hp-next-link]'); 1248 var nextSon = nextZone.querySelector('[data-hp-next-son]'); 1249 var nextUrl = nextZone.getAttribute('data-url'); 1250 /* SIFIR TUZAÄI: `parseInt('0',10) || 3` SIFIRI YUTUYOR â 0 falsy 1251 olduÄu için yedeÄe düÅüyor ve "otomatik akıÅı kapat" isteÄi
1252 sessizce "3 haber otomatik"e dönüyordu (fnex'te ölçüldü: 1253 data-oto="0" verilen sayfada yine bir sonraki haber yüklendi). 1254 Yedek yalnızca öznitelik YOKSA ya da sayı deÄilse devreye girer. */ 1255 var otoHam = parseInt(nextZone.getAttribute('data-oto'), 10); 1256 var otoKalan = isNaN(otoHam) ? 3 : Math.max(0, otoHam); 1257 1258 var mesgul = false, bitti = false, gozcuYukle = null; 1259 1260 /* Sayfa açılıÅındaki adres ve baÅlık: okur en baÅa dönerse 1261 bunlara geri dönülüyor. */ 1262 var ilkMakale = akisKok.querySelector('[data-hp-article]'); 1263 var aktifId = ilkMakale ? ilkMakale.getAttribute('data-id') : ''; 1264 1265 var dur = function () { 1266 bitti = true; 1267 if (gozcuYukle) gozcuYukle.disconnect(); 1268 if (nextLink) nextLink.remove(); 1269 if (nextSon) nextSon.classList.remove('d-none'); 1270 }; 1271 1272 /* --- Ekrandaki makaleye göre adres + baÅlık --- 1273 Ãlçüt: makalenin üst kenarı ekranın üst üçte birinin üzerinde 1274 kalan SON makale "okunan" sayılıyor. Basit "görünür mü" ölçütü 1275 iki makale aynı anda göründüÄünde titriyordu. */ 1276 var esikOran = 0.33; 1277 var adresGuncelle = function () { 1278 var makaleler = akisKok.querySelectorAll('[data-hp-article]'); 1279 var esik = window.innerHeight * esikOran; 1280 var secili = null; 1281 1282 for (var i = 0; i < makaleler.length; i++) { 1283 if (makaleler[i].getBoundingClientRect().top <= esik) secili = makaleler[i]; 1284 } 1285 if (!secili) secili = makaleler[0]; 1286 if (!secili) return; 1287 1288 var yid = secili.getAttribute('data-id'); 1289 if (yid === aktifId) return; 1290 aktifId = yid; 1291 1292 var yurl = secili.getAttribute('data-url'); 1293 var ybas = secili.getAttribute('data-baslik'); 1294 if (ybas) document.title = ybas; 1295 try { history.replaceState({ hpId: yid }, '', yurl); } catch (e) {} 1296 }; 1297 1298 /* --- Sıradaki haberi yükle --- */ 1299 var yukle = function () { 1300 if (mesgul || bitti) return; 1301 1302 var son = akisKok.querySelector('[data-hp-article]:last-of-type'); 1303 var hedef = son ? parseInt(son.getAttribute('data-sonraki'), 10) : 0; 1304 if (!hedef) { dur(); return; } 1305 1306 mesgul = true; 1307 nextZone.classList.add('is-yukleniyor'); 1308 1309 var veri = new FormData(); 1310 veri.append('id', hedef); 1311 1312 fetch(nextUrl, { method: 'POST', body: veri, credentials: 'same-origin' }) 1313 .then(function (r) { return r.json(); }) 1314 .then(function (c) { 1315 mesgul = false; 1316 nextZone.classList.remove('is-yukleniyor'); 1317 if (!c || !c.ok) { dur(); return; } 1318 1319 /* Makaleyi akıÅın sonuna ekle. */ 1320 var ayrac = document.createElement('div'); 1321 ayrac.className = 'hp-article-sep'; 1322 akisKok.appendChild(ayrac); 1323 akisKok.insertAdjacentHTML('beforeend', c.html); 1324 1325 /* "Sıradaki haber" kartını bir adım ilerlet. */ 1326 if (c.sonraki && c.sonraki.id) { 1327 if (nextLink) { 1328 nextLink.setAttribute('href', c.sonraki.url); 1329 var b = nextLink.querySelector('.hp-title'); 1330 if (b) b.textContent = c.sonraki.baslik; 1331 } 1332 } else { 1333 dur(); 1334 } 1335 1336 adresGuncelle(); 1337 }) 1338 .catch(function () { 1339 mesgul = false; 1340 nextZone.classList.remove('is-yukleniyor'); 1341 /* AÄ hatası: otomatik modu kapat, baÄlantı elle kullanılsın. 1342 Aksi hâlde gözcü her kaydırmada yeniden deneyip sunucuyu 1343 döverdi. */ 1344 otoKalan = 0; 1345 if (gozcuYukle) gozcuYukle.disconnect(); 1346 }); 1347 }; 1348 1349 /* BaÄlantıya tıklanınca: sayfa deÄiÅtirmek yerine akıÅa ekle. 1350 Yeni sekmede açmak isteyen (orta tık, Ctrl) engellenmiyor. */ 1351 if (nextLink) { 1352 nextLink.addEventListener('click', function (e) { 1353 if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return; 1354 e.preventDefault(); 1355 otoKalan = 0; // okur kontrolü aldı 1356 if (gozcuYukle) gozcuYukle.disconnect(); 1357 yukle(); 1358 }); 1359 } 1360 1361 if ('IntersectionObserver' in window) { 1362 /* TETİKLEME EÅİÄİ â NEGATİF rootMargin 1363 1364 Dört sürüm oldu: 1365 1) `600px` ön yükleme â okur sona varmadan sıradaki haber 1366 hazırdı, geçiÅ "pat" diye oluyordu. 1367 2) `200px` + 2 sn bekleme â yumuÅadı ama bekleme gereksizdi 1368 ve tetikleme hâlâ erkendi. 1369 3) `-55%` â doÄru yöne gitti ama YÃZDE KIRILGAN: eÅik ekran 1370 yüksekliÄiyle büyüyor, kartın çıkabileceÄi en yüksek nokta 1371 ise ALTINDAKİ İÃERİÄE baÄlı ve sabit. Ãlçüldü: kartın 1372 altında 624px içerik var (bölgenin kalanı + alt bilgi), 1373 yani kart en fazla `ekran - 624` yüksekliÄine çıkabiliyor.
1374 1200px'lik ekranda `-55%` eÅiÄi 540px, kartın ulaÅabildiÄi 1375 en yüksek nokta 576px â EÅİK Hİà GEÃİLMEZ, otomatik akıŠ1376 hiç çalıÅmazdı. 1377 4) ÅİMDİ: piksel. EÅik ekran yüksekliÄinden BAÄIMSIZ. 1378 1379 `-460px`: kartın üst kenarı ekranın alt kenarından 460px yukarı 1380 çıkmadan tetiklenmiyor. Okur o noktaya geldiÄinde yorumlar 1381 yukarıda kalmıŠoluyor (ölçüldü: son yorum kutusunun altı 1382 ~390px, yani ekranın üst yarısında). 1383 1384 624 - 460 = 164px pay: alt bilgisi bu sayfadakinden kısa olan 1385 bir kurulumda bile eÅik geçilebiliyor. Yine de altta bir de 1386 emniyet var (aÅaÄıya bak). */ 1387 gozcuYukle = new IntersectionObserver(function (girisler) { 1388 girisler.forEach(function (g) { 1389 if (!g.isIntersecting || bitti || otoKalan <= 0 || mesgul) return; 1390 otoKalan -= 1; 1391 if (otoKalan <= 0 && gozcuYukle) gozcuYukle.disconnect(); 1392 yukle(); 1393 }); 1394 }, { rootMargin: '0px 0px -460px 0px' }); 1395 gozcuYukle.observe(nextZone); 1396 } 1397 1398 /* --- Kaydırma: adres + okuma çubuÄu --- 1399 requestAnimationFrame ile tek karede bir çalıÅıyor; scroll 1400 olayında doÄrudan ölçüm almak düzen hesabını her karede 1401 tetikliyordu. */ 1402 var hbCubuk = document.querySelector('[data-hp-progress]'); 1403 var hbBekliyor = false; 1404 1405 var hbKare = function () { 1406 hbBekliyor = false; 1407 adresGuncelle(); 1408 1409 /* EMNİYET: eÅik piksel cinsinden ve kartın altındaki içerik 1410 (alt bilgi) beklenenden kısaysa gözcü hiç tetiklenmeyebilir. 1411 O durumda okur belgenin dibine dayanır ve akıŠsessizce 1412 durur. Dibe gelindiyse yükleme burada yapılıyor â 1413 gözcünün ıskaladıÄı tek durum bu. */ 1414 if (!bitti && otoKalan > 0 && !mesgul) { 1415 var kalan = document.documentElement.scrollHeight 1416 - window.scrollY - window.innerHeight; 1417 if (kalan < 60) { 1418 otoKalan -= 1; 1419 if (otoKalan <= 0 && gozcuYukle) gozcuYukle.disconnect(); 1420 yukle(); 1421 } 1422 } 1423 1424 if (hbCubuk) { 1425 var kok = akisKok.getBoundingClientRect(); 1426 var toplam = kok.height - window.innerHeight; 1427 var gecen = -kok.top; 1428 var oran = toplam > 0 ? (gecen / toplam) : 0; 1429 hbCubuk.style.width = Math.max(0, Math.min(1, oran)) * 100 + '%'; 1430 } 1431 }; 1432 1433 window.addEventListener('scroll', function () { 1434 if (hbBekliyor) return; 1435 hbBekliyor = true; 1436 requestAnimationFrame(hbKare); 1437 }, { passive: true }); 1438 1439 hbKare(); 1440 } 1441 /* =================================================================== 1442 14. GÃRSEL BÃYÃTECİ (LIGHTBOX) 1443 1444 Kütüphane KULLANILMIYOR. Tasarım `data-glightbox` öznitelikleriyle 1445 geldi ama GLightbox hiçbir yerde yüklenmiyordu; galeri görselleri 1446 tıklanınca ham dosyaya gidiyordu. DıŠbaÄımlılık eklemek yerine 1447 ihtiyaç kadarı burada. 1448 1449 TEK KAP, YENİDEN KULLANILIYOR. Her açılıÅta DOM kurmak yerine bir 1450 kez kuruluyor; 30 görsellik bir galeride fark ediliyor. 1451 1452 DELEGE DİNLEYİCİ: sonsuz okumayla sonradan gelen makalelerin 1453 görselleri de kendiliÄinden çalıÅıyor â tek tek baÄlanmıyor. 1454 1455 ODAK TUZAÄI: büyüteç açıkken Tab arkadaki sayfada gezinemiyor, 1456 kapanınca odak tıklanan görsele dönüyor. Klavyeyle gezen okur 1457 "kayboldu" hissi yaÅamıyor. 1458 =================================================================== */ 1459 var lbKap = null, lbGorsel, lbYazi, lbSayac, lbOnceki, lbSonraki, 1460 lbListe = [], lbSira = 0, lbSonOdak = null; 1461 1462 var lbKur = function () { 1463 if (lbKap) return; 1464 1465 lbKap = document.createElement('div'); 1466 lbKap.className = 'hp-lb'; 1467 lbKap.setAttribute('role', 'dialog'); 1468 lbKap.setAttribute('aria-modal', 'true'); 1469 lbKap.setAttribute('aria-label', 'Görsel görüntüleyici'); 1470 lbKap.innerHTML = 1471 '<button class="hp-lb-btn hp-lb-kapat" type="button" aria-label="Kapat">' + 1472 '<svg class="hp-ico" aria-hidden="true"><use href="#i-close"/></svg></button>' + 1473 '<button class="hp-lb-btn hp-lb-onceki" type="button" aria-label="Ãnceki görsel">' + 1474 '<svg class="hp-ico" aria-hidden="true"><use href="#i-chevron_left"/></svg></button>' + 1475 '<button class="hp-lb-btn hp-lb-sonraki" type="button" aria-label="Sonraki görsel">' + 1476 '<svg class="hp-ico" aria-hidden="true"><use href="#i-chevron_right"/></svg></button>' + 1477 '<div class="hp-lb-sahne">
1477' + 1478 '<img class="hp-lb-gorsel" alt="">' + 1479 '<p class="hp-lb-yazi"></p>' + 1480 '<span class="hp-lb-sayac"></span>' + 1481 '</div>'; 1482 1483 document.body.appendChild(lbKap); 1484 1485 lbGorsel = lbKap.querySelector('.hp-lb-gorsel'); 1486 lbYazi = lbKap.querySelector('.hp-lb-yazi'); 1487 lbSayac = lbKap.querySelector('.hp-lb-sayac'); 1488 lbOnceki = lbKap.querySelector('.hp-lb-onceki'); 1489 lbSonraki = lbKap.querySelector('.hp-lb-sonraki'); 1490 1491 lbOnceki.addEventListener('click', function () { lbGit(-1); }); 1492 lbSonraki.addEventListener('click', function () { lbGit(1); }); 1493 lbKap.querySelector('.hp-lb-kapat').addEventListener('click', lbKapat); 1494 1495 /* BoÅluÄa tıklayınca kapanıyor; görselin kendisine tıklayınca DEÄİL 1496 (okur büyütmek için tıkladı, kapatmak için deÄil). */ 1497 lbKap.addEventListener('click', function (e) { 1498 if (e.target === lbKap || e.target.classList.contains('hp-lb-sahne')) lbKapat(); 1499 }); 1500 }; 1501 1502 var lbYaz = function () { 1503 var g = lbListe[lbSira]; 1504 if (!g) return; 1505 1506 lbGorsel.src = g.src; 1507 lbGorsel.alt = g.yazi || ''; 1508 lbYazi.textContent = g.yazi || ''; 1509 lbYazi.hidden = !g.yazi; 1510 1511 lbSayac.textContent = lbListe.length > 1 ? (lbSira + 1) + ' / ' + lbListe.length : ''; 1512 lbOnceki.hidden = lbListe.length < 2; 1513 lbSonraki.hidden = lbListe.length < 2; 1514 }; 1515 1516 var lbGit = function (yon) { 1517 if (!lbListe.length) return; 1518 /* Döngüsel: son görselden sonra baÅa. Galeride "sona geldin" diye 1519 durdurmak okuru düÄmenin bozuk olduÄu Åüphesine düÅürüyor. */ 1520 lbSira = (lbSira + yon + lbListe.length) % lbListe.length; 1521 lbYaz(); 1522 }; 1523 1524 var lbKapat = function () { 1525 if (!lbKap) return; 1526 lbKap.classList.remove('is-acik'); 1527 document.documentElement.style.overflow = ''; 1528 if (lbSonOdak && lbSonOdak.focus) lbSonOdak.focus({ preventScroll: true }); 1529 lbSonOdak = null; 1530 }; 1531 1532 var lbAc = function (liste, sira, odak) { 1533 lbKur(); 1534 lbListe = liste; 1535 lbSira = sira; 1536 lbSonOdak = odak || null; 1537 lbYaz(); 1538 lbKap.classList.add('is-acik'); 1539 document.documentElement.style.overflow = 'hidden'; // arkada kaydırma olmasın 1540 lbKap.querySelector('.hp-lb-kapat').focus({ preventScroll: true }); 1541 }; 1542 1543 /* Görselin yanındaki açıklamayı bul: figcaption > alt > title. 1544 Editörün yazdıÄı açıklama çoÄu haberde figcaption'da. */ 1545 var lbAciklama = function (img) { 1546 var fig = img.closest ? img.closest('figure') : null; 1547 var alt = fig ? fig.querySelector('figcaption') : null; 1548 if (alt && alt.textContent.trim()) return alt.textContent.trim(); 1549 return (img.getAttribute('alt') || img.getAttribute('title') || '').trim(); 1550 }; 1551 1552 /* Bir kaptaki bütün görseller tek galeri sayılıyor: okur haberin 1553 içindeki 3. görsele tıklayıp okla diÄerlerine geçebiliyor. */ 1554 var lbSecici = '.hp-article-body img, [data-hp-lightbox] img'; 1555 1556 document.addEventListener('click', function (e) { 1557 var img = e.target.closest ? e.target.closest(lbSecici) : null; 1558 if (!img) return; 1559 1560 /* Görsel bir baÄlantının içindeyse ve baÄlantı BAÅKA bir sayfaya 1561 gidiyorsa okurun niyeti o sayfa; büyüteç devreye girmiyor. 1562 BaÄlantı görselin kendisine gidiyorsa (galeri deseni) büyüteç. */ 1563 var bag = img.closest('a'); 1564 if (bag) { 1565 var hedef = bag.getAttribute('href') || ''; 1566 var gorselMi = /\.(jpe?g|png|webp|gif|avif)(\?|#|$)/i.test(hedef); 1567 if (!gorselMi && hedef && hedef.charAt(0) !== '#') return; 1568 e.preventDefault(); 1569 } 1570 1571 var kap = img.closest('.hp-article-body, [data-hp-lightbox]') || document; 1572 var hepsi = Array.prototype.slice.call(kap.querySelectorAll(lbSecici)); 1573 1574 var liste = hepsi.map(function (g) { 1575 var b = g.closest('a'); 1576 var h = b ? (b.getAttribute('href') || '') : ''; 1577 /* Büyük sürüm baÄlantıda varsa onu kullan; yoksa görselin kendisi. */ 1578 return { 1579 src: /\.(jpe?g|png|webp|gif|avif)(\?|#|$)/i.test(h) ? h : g.currentSrc || g.src, 1580 yazi: lbAciklama(g) 1581 }; 1582 }); 1583 1584 lbAc(liste, hepsi.indexOf(img), img); 1585 }); 1586 1587 document.addEventListener('keydown', function (e) { 1588 if (!lbKap || !lbKap.classList.contains('is-acik')) return; 1589 if (e.key === 'Escape') { lbKapat(); return; } 1590 if (e.key === 'ArrowLeft') { lbGit(-1); return; } 1591 if (e.key === 'ArrowRight') { lbGit(1); return; } 1592 /* Odak tuzaÄı: büyüteç açıkken Tab dıÅarı çıkmıyor. */ 1593 if (e.key === 'Tab') { 1594 var odaklanir = lbKap.querySelectorAll('button:not([hidden])'); 1595 if (!odaklanir.length) return; 1596 var ilk = odaklanir[0], son = odaklanir[odaklanir.length - 1]; 1597 if (e.shiftKey && document.activeElement === ilk) { e.preventDefault(); son.focus(); } 1598 else if (!e.shiftKey && document.activeElement === son) { e.preventDefault(); ilk.focus(); } 1599 } 1600 }); 1601 1602 /* =================================================================== 1603 15. YAPIÅKAN YAN ÃUBUK â ÃLÃÃLEN `top` 1604 1605 Sorun: yan çubuk ekrandan uzunsa sabit bir `top: 168px` ile 1606 yapıÅtırıldıÄında ALT KUTULAR Hİà GÃRÃNMEZ; okur ne kadar 1607 kaydırırsa kaydırsın çubuÄun altı ekranın dıÅında kalır.
1608 İlk çözüm `max-height` + iç kaydırma idi ve daha kötüsünü yaptı: 1609 flex sütununda kutuları EZDİ (yazarlar 508 â 178px). 1610 1611 DoÄru desen: çubuk sayfayla birlikte serbest kaysın, ALTI ekranın 1612 altına dayandıÄında yapıÅsın. Bunun için `top` NEGATİF olmalı: 1613 1614 top = ekranYuksekligi - cubukYuksekligi - pay 1615 1616 Ãubuk 1118px, ekran 900px ise top = -242px. Ãubuk yukarı doÄru 1617 242px kayabiliyor, yani altı görünene kadar akıyor, sonra 1618 çakılıyor. Kısa çubukta ölçü pozitif çıkar; o zaman baÅlıÄın 1619 altına (168px) sabitleniyor. 1620 1621 Yükseklik CSS'te bilinemez, o yüzden JS ölçüyor. ResizeObserver 1622 ÅART: tembel görseller ve widget'lar yüklendikçe çubuk uzuyor, 1623 bir kez ölçmek yanlıŠdeÄer bırakırdı. 1624 =================================================================== */ 1625 var ypCubuk = document.querySelector('.hp-sticky'); 1626 1627 if (ypCubuk && window.matchMedia) { 1628 var ypGenis = window.matchMedia('(min-width: 992px)'); 1629 var ypUstPay = 168; // yapıÅkan baÅlıÄın kapladıÄı yer 1630 var ypAltPay = 24; // altta bıraktıÄımız nefes payı 1631 1632 var ypHesapla = function () { 1633 if (!ypGenis.matches) { ypCubuk.style.removeProperty('--hp-yapiskan-ust'); return; } 1634 1635 var yuk = ypCubuk.offsetHeight; 1636 var bos = window.innerHeight - ypUstPay - ypAltPay; 1637 1638 /* SıÄıyorsa sıradan yapıÅkanlık. SıÄmıyorsa negatif `top`. */ 1639 var ust = yuk <= bos ? ypUstPay : (window.innerHeight - yuk - ypAltPay); 1640 ypCubuk.style.setProperty('--hp-yapiskan-ust', Math.round(ust) + 'px'); 1641 }; 1642 1643 ypHesapla(); 1644 window.addEventListener('resize', ypHesapla); 1645 1646 if ('ResizeObserver' in window) { 1647 new ResizeObserver(ypHesapla).observe(ypCubuk); 1648 } else { 1649 /* Eski tarayıcı: görseller yüklendikçe bir kez daha ölç. */ 1650 window.addEventListener('load', ypHesapla); 1651 } 1652 1653 if (ypGenis.addEventListener) ypGenis.addEventListener('change', ypHesapla); 1654 } 1655 1656 /* =================================================================== 1657 16. YATAY ÅERİT â ortak kaydırma 1658 1659 Kategori kapsülleri gibi tek satırda kalan listeler için. 1660 Kullananlar: galeri ve video kategori sayfaları. 1661 1662 SORUN: `overflow-x: auto` masaüstünde YETMİYOR. Ãlçüldü (1440px): 1663 Åerit 1216px, içerik 1551px â 335px gizli. Dokunmatikte parmakla 1664 kayıyor ama masaüstünde fare tekerleÄi DİKEY kaydırıyor ve 1665 kaydırma çubuÄu gizli olduÄu için kategorilerin bir kısmına 1666 eriÅilemiyordu. 1667 1668 Ãç çare birden: 1669 · Ok düÄmeleri â yalnızca Åerit GERÃEKTEN taÅıyorsa açılıyor 1670 (`hidden` baÅlıyorlar). Az kategorili sitede boÅuna düÄme yok. 1671 · Tekerlek yataya çevriliyor â okurun refleksi bu. 1672 · Kenar soluklaÅması â "daha var" sinyali. 1673 1674 Delege deÄil doÄrudan baÄlanıyor: Åerit sayfa ömrü boyunca 1675 yerinde duruyor, sonradan eklenmiyor. 1676 =================================================================== */ 1677 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-serit]'), function (kap) { 1678 var yol = kap.querySelector('[data-hp-serit-yol]'); 1679 var sol = kap.querySelector('[data-hp-serit-sol]'); 1680 var sag = kap.querySelector('[data-hp-serit-sag]'); 1681 if (!yol) return; 1682 1683 var tasiyorMu = function () { return yol.scrollWidth - yol.clientWidth > 4; }; 1684 1685 var durumYaz = function () { 1686 var tasiyor = tasiyorMu(); 1687 1688 if (sol) sol.hidden = !tasiyor; 1689 if (sag) sag.hidden = !tasiyor; 1690 1691 if (!tasiyor) { 1692 kap.classList.remove('is-sol-var', 'is-sag-var'); 1693 return; 1694 } 1695 1696 var basta = yol.scrollLeft <= 2; 1697 var sonda = yol.scrollLeft + yol.clientWidth >= yol.scrollWidth - 2; 1698 1699 if (sol) sol.disabled = basta; 1700 if (sag) sag.disabled = sonda; 1701 1702 kap.classList.toggle('is-sol-var', !basta); 1703 kap.classList.toggle('is-sag-var', !sonda); 1704 }; 1705 1706 /* Bir adım = görünen geniÅliÄin %80'i. Tam geniÅlik kaydırmak
1707 okurun son gördüÄü kapsülü ekrandan tamamen çıkarıp yerini 1708 kaybettiriyor. */ 1709 var adim = function (yon) { 1710 yol.scrollBy({ left: yon * Math.round(yol.clientWidth * 0.8), behavior: 'smooth' }); 1711 }; 1712 1713 if (sol) sol.addEventListener('click', function () { adim(-1); }); 1714 if (sag) sag.addEventListener('click', function () { adim(1); }); 1715 1716 yol.addEventListener('scroll', durumYaz, { passive: true }); 1717 window.addEventListener('resize', durumYaz); 1718 1719 /* Tekerlek: DİKEY hareketi yatay kaydırmaya çeviriyor. Yalnızca 1720 Åerit taÅıyorsa ve hareket aÄırlıklı olarak dikeyse â yatay 1721 tekerleÄi (dokunmatik yüzey) olan okurun kendi hareketi zaten 1722 çalıÅıyor, ona karıÅmıyoruz. 1723 1724 `preventDefault` Åart: yoksa sayfa da birlikte kayıyor. Bu 1725 yüzden dinleyici `passive: false`. */ 1726 yol.addEventListener('wheel', function (e) { 1727 if (!tasiyorMu()) return; 1728 if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return; 1729 e.preventDefault(); 1730 yol.scrollLeft += e.deltaY; 1731 }, { passive: false }); 1732 1733 /* Görsel/yazı tipi yüklendikçe kapsüllerin geniÅliÄi deÄiÅiyor; 1734 tek seferlik ölçüm yanlıŠkalırdı. */ 1735 if ('ResizeObserver' in window) new ResizeObserver(durumYaz).observe(yol); 1736 1737 durumYaz(); 1738 }); 1739 1740 /* =================================================================== 1741 17. MODÃL KATEGORİ â ALT KATEGORİ PANELİ 1742 1743 Åerit `overflow-x: auto`. Panel Åeridin İÃİNDE açılsaydı taÅma 1744 yüzünden KIRPILIRDI â bu yüzden `position: fixed` ve konumu 1745 burada yazılıyor. 1746 1747 Kapsülün kendisi baÄlantı (üst kategoriye gider), yanındaki ok bu 1748 paneli açar. İki ayrı hedef bilerek: tek öÄeye iki iÅ yüklenirse 1749 okur hangisini yaptıÄını bilemez. 1750 1751 Aynı anda tek panel açık; dıÅarı tıklamak, Esc, sayfayı ya da 1752 Åeridi kaydırmak kapatıyor. 1753 =================================================================== */ 1754 var mkAcik = null; 1755 1756 var mkKapat = function () { 1757 if (!mkAcik) return; 1758 mkAcik.panel.hidden = true; 1759 mkAcik.dugme.setAttribute('aria-expanded', 'false'); 1760 mkAcik = null; 1761 }; 1762 1763 var mkKonumla = function (dugme, panel) { 1764 var k = dugme.closest('[data-hp-mkat]').getBoundingClientRect(); 1765 1766 /* Ãlçmek için önce görünür olmalı; yoksa geniÅliÄi 0 çıkar. */ 1767 panel.hidden = false; 1768 1769 var g = panel.offsetWidth; 1770 var sol = k.left; 1771 1772 /* SaÄ kenardan taÅarsa kapsülün saÄına hizala. */ 1773 if (sol + g > window.innerWidth - 8) sol = Math.max(8, k.right - g); 1774 1775 panel.style.left = Math.round(sol) + 'px'; 1776 1777 /* AÅaÄıda yer yoksa yukarı aç. */ 1778 var y = panel.offsetHeight; 1779 panel.style.top = (k.bottom + y + 8 > window.innerHeight && k.top - y - 8 > 0) 1780 ? Math.round(k.top - y - 6) + 'px' 1781 : Math.round(k.bottom + 6) + 'px'; 1782 }; 1783 1784 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-mkat]'), function (oge) { 1785 var dugme = oge.querySelector('[data-hp-mkat-ac]'); 1786 var panel = oge.querySelector('[data-hp-mkat-panel]'); 1787 if (!dugme || !panel) return; 1788 1789 dugme.addEventListener('click', function (e) { 1790 e.preventDefault(); 1791 e.stopPropagation(); 1792 1793 var buAcik = mkAcik && mkAcik.panel === panel; 1794 mkKapat(); 1795 if (buAcik) return; 1796 1797 mkKonumla(dugme, panel); 1798 dugme.setAttribute('aria-expanded', 'true'); 1799 mkAcik = { panel: panel, dugme: dugme }; 1800 }); 1801 1802 /* Panelin içine tıklamak (baÄlantılar hariç) kapatmasın. */ 1803 panel.addEventListener('click', function (e) { e.stopPropagation(); }); 1804 }); 1805 1806 if (document.querySelector('[data-hp-mkat]')) { 1807 document.addEventListener('click', mkKapat); 1808 document.addEventListener('keydown', function (e) { if (e.key === 'Escape') mkKapat(); }); 1809 /* Kaydırınca konum bozulur; yeniden hesaplamak yerine kapatmak 1810 hem daha ucuz hem okurun beklediÄi davranıÅ. */ 1811 window.addEventListener('scroll', mkKapat, { passive: true }); 1812 window.addEventListener('resize', mkKapat); 1813 Array.prototype.forEach.call(document.querySelectorAll('[data-hp-serit-yol]'), function (y) { 1814 y.addEventListener('scroll', mkKapat, { passive: true }); 1815 }); 1816 } 1817 1818 1819 /* =================================================================== 1820 §18 SERVİS ÃNBELLEÄİ TAZELEME 1821 (namaz · puan · yol · gazete · burç) 1822 1823 Bu sayfalar uzak bir servisten besleniyor ama ÃİZİM YOLUNDA aÄa
1824 çıkmıyorlar: sunucu yalnızca yerel kopyayı okuyor. Kopya bayatsa 1825 sayfa SON BİLİNEN veriyle çiziliyor ve tazeleme buraya kalıyor. 1826 1827 NEDEN SAYFAYI YENİLİYORUZ, DOM'U DEÄİL: 1828 Her servisin çizimi kendi Åablonunda (namaz kartları, puan 1829 cetveli, gazete ızgarasıâ¦). Aynı iÅaretlemeyi bir de JS'te 1830 yazmak, iki yerde iki ayrı gerçek demekti. Tazeleme baÅarılıysa 1831 tek satırla sayfayı yeniliyoruz; sunucu artık taze kopyayı 1832 görüyor. 1833 1834 DÃNGà KORUMASI: yenileme yalnızca uç `tazelendi:1` derse, üstelik 1835 sayfa baÅına EN FAZLA BİR KEZ (sessionStorage damgası). Servis 1836 bozuksa sayfa sonsuza kadar kendini yenilemiyor. 1837 1838 Sayfa boyandıktan sonra çalıÅıyor â ziyaretçi hiçbir zaman 1839 beklemiyor. 1840 =================================================================== */ 1841 /* SAYFADA BİRDEN ÃOK SERVİS KUTUSU OLABİLİR. 1842 Yan blok panelden yönetiliyor: namaz sayfasının kendi kutusunun 1843 yanında yan blokta puan ya da yol widget'ı da bulunabilir. Tekil 1844 `querySelector` yalnızca ilkini tazeliyordu. 1845 1846 TEK YENİLEME: her kutu ayrı ayrı `location.reload()` çaÄırsaydı 1847 ikinci isteÄi yarıda keserdi. Hepsi bitene kadar sayılıyor, en az 1848 biri gerçekten tazelendiyse sayfa BİR KEZ yenileniyor. */ 1849 var svcBayat = Array.prototype.filter.call( 1850 document.querySelectorAll('[data-hp-svc]'), 1851 function (k) { return k.getAttribute('data-hp-svc-bayat') === '1' && k.getAttribute('data-url'); } 1852 ); 1853 1854 if (svcBayat.length && window.fetch) { 1855 1856 /* Damga sayfa+servis baÅına: bozuk bir servis sayfayı sonsuz 1857 yenileme döngüsüne sokmasın. */ 1858 svcBayat = svcBayat.filter(function (k) { 1859 var d = 'hp-svc-' + (k.getAttribute('data-hp-svc') || '') + '-' + location.pathname + location.search; 1860 try { if (sessionStorage.getItem(d) === '1') return false; sessionStorage.setItem(d, '1'); } catch (e) {} 1861 return true; 1862 }); 1863 } 1864 1865 if (svcBayat.length && window.fetch) { 1866 var svcKalan = svcBayat.length; 1867 var svcDegisti = false; 1868 1869 var svcBitti = function () { 1870 if (--svcKalan > 0) return; 1871 if (svcDegisti) location.reload(); 1872 }; 1873 1874 var svcCalis = function () { 1875 svcBayat.forEach(function (kutu) { 1876 var v = new FormData(); 1877 v.append('do', 'servis'); 1878 v.append('servis', kutu.getAttribute('data-hp-svc') || ''); 1879 1880 var q = {}; 1881 try { q = JSON.parse(kutu.getAttribute('data-hp-svc-sorgu') || '{}'); } catch (e) {} 1882 Object.keys(q).forEach(function (k) { v.append(k, q[k]); }); 1883 1884 kutu.classList.add('is-tazeleniyor'); 1885 1886 fetch(kutu.getAttribute('data-url'), { 1887 method: 'POST', 1888 body: v, 1889 credentials: 'same-origin', 1890 headers: { 'X-Requested-With': 'XMLHttpRequest' } 1891 }) 1892 .then(function (r) { return r.json(); }) 1893 .then(function (d) { 1894 if (d && d.tazelendi === 1) svcDegisti = true; 1895 kutu.classList.remove('is-tazeleniyor'); 1896 svcBitti(); 1897 }) 1898 .catch(function () { kutu.classList.remove('is-tazeleniyor'); svcBitti(); }); 1899 }); 1900 }; 1901 1902 /* Boyama bitsin, sonra aÄ. */ 1903 if (window.requestIdleCallback) requestIdleCallback(svcCalis, { timeout: 2500 }); 1904 else setTimeout(svcCalis, 400); 1905 } 1906 1907 /* =================================================================== 1908 §19 NAMAZ GERİ SAYIMI 1909 1910 Sunucu yalnızca BAÅLANGIà dakikasını basıyor. Sayfa önbelleÄe 1911 giriyor (300 sn); sunucuda üretilmiÅ bir "44 dakika kaldı" 1912 ifadesi beÅ dakika boyunca yanlıŠgörünürdü. 1913 1914 Dakika baÅına bir kez güncelleniyor â saniye göstermiyoruz, 1915 çünkü vakit dakika hassasiyetinde ve saniye sayacı hem gereksiz 1916 bir kesinlik iddiası hem boÅuna iÅ olurdu. 1917 1918 Sayaç sıfıra inince sayfa BİR KEZ yenileniyor: sıradaki vakit 1919 artık bir sonraki. 1920 =================================================================== */ 1921 var nmzSayac = document.querySelector('[data-hp-namaz-sayac]'); 1922 1923 if (nmzSayac) { 1924 var nmzHedef = nmzSayac.querySelector('[data-hp-namaz-kalan]'); 1925 var nmzDk = parseInt(nmzSayac.getAttribute('data-hp-namaz-dk'), 10); 1926 1927 if (nmzHedef && nmzDk > 0) { 1928 /* Sayfa açıldıÄı andan itibaren geçen süreyi ölçüyoruz; sekme 1929 arka planda uyuyup uyanırsa da doÄru kalıyor. */ 1930 var nmzBas = Date.now(); 1931 1932 var nmzYaz = function () { 1933 var kalan = nmzDk - Math.floor((Date.now() - nmzBas) / 60000); 1934 1935 if (kalan <= 0) { location.reload(); return; } 1936 1937 var sa = Math.floor(kalan / 60), dk = kalan % 60; 1938 nmzHedef.textContent = sa > 0 ? (sa + ' saat ' + dk + ' dakika') : (dk + ' dakika'); 1939 }; 1940 1941 nmzYaz(); 1942 setInterval(nmzYaz, 30000); 1943 } 1944 } 1945 1946 /* =================================================================== 1947 §20 YOL BÃLTENİ SÃZME 1948 1949 Bülten uzun (kıÅın 50+ madde) ve okur genelde TEK bir yolu 1950 arıyor. Süzme tamamen tarayıcıda: liste zaten sayfada, yeni 1951 istek doÄurmuyor. 1952 1953 Kutu SUNUCUDA `hidden` baÅlıyor ve buradan açılıyor. JS 1954 çalıÅmazsa çalıÅmayan bir arama kutusu göstermiÅ olmuyoruz; 1955 liste yine tam hâliyle orada. 1956 1957 EÅleÅen kelime <mark> ile iÅaretleniyor â okur 40 maddelik bir 1958 listede aradıÄı ifadenin nerede geçtiÄini görebilsin. İÅaretleme 1959 yalnızca METİN düÄümlerine uygulanıyor; maddeler düz metin 1960 olduÄu için `textContent` üzerinden güvenle yeniden kuruluyor. 1961 =================================================================== */ 1962 var yolKok = document.querySelector('[data-hp-yol-kok]'); 1963 1964 if (yolKok) { 1965 var yolKutu = document.querySelector('[data-hp-yol-ara]'); 1966 var yolGiris = document.querySelector('[data-hp-yol-giris]'); 1967 var yolSayac = document.querySelector('[data-hp-yol-sayac]'); 1968 var yolBos = document.querySelector('[data-hp-yol-bos]'); 1969 var yolOge = Array.prototype.slice.call(yolKok.querySelectorAll('[data-hp-yol-oge]')); 1970 1971 if (yolKutu && yolGiris && yolOge.length) { 1972 yolKutu.hidden = false;
1973 1974 /* Ham metinler bir kez saklanıyor: her tuÅta DOM'dan okumak ve 1975 <mark>'lı metni yeniden aramak yanlıŠsonuç verirdi. */ 1976 var yolHam = yolOge.map(function (o) { 1977 var p = o.querySelector('.hp-yol-metin'); 1978 return { oge: o, p: p, metin: p ? p.textContent : '' }; 1979 }); 1980 1981 /* Türkçe karÅılaÅtırma: "İSTANBUL" ile "istanbul" eÅleÅmeli. 1982 toLowerCase() İ'yi doÄru çevirmiyor, önce elle eÅliyoruz. */ 1983 var yolKucult = function (t) { 1984 return t.replace(/İ/g, 'i').replace(/I/g, 'ı').toLowerCase(); 1985 }; 1986 1987 var yolSuz = function () { 1988 var q = yolKucult(yolGiris.value.trim()); 1989 var bulunan = 0; 1990 1991 yolHam.forEach(function (k) { 1992 var uyar = q === '' || yolKucult(k.metin).indexOf(q) !== -1; 1993 k.oge.hidden = !uyar; 1994 if (uyar) bulunan++; 1995 1996 if (!k.p) return; 1997 1998 if (q === '' || !uyar) { k.p.textContent = k.metin; return; } 1999 2000 /* İÅaretleme: metin parçalara bölünüp <mark> ile yeniden 2001 kuruluyor. innerHTML'e ham metin basılmıyor. */ 2002 k.p.textContent = ''; 2003 var kalan = k.metin, dusuk = yolKucult(k.metin), i; 2004 var kaydir = 0; 2005 2006 while ((i = dusuk.indexOf(q, kaydir)) !== -1) { 2007 k.p.appendChild(document.createTextNode(kalan.slice(kaydir, i))); 2008 var m = document.createElement('mark'); 2009 m.textContent = kalan.slice(i, i + q.length); 2010 k.p.appendChild(m); 2011 kaydir = i + q.length; 2012 } 2013 k.p.appendChild(document.createTextNode(kalan.slice(kaydir))); 2014 }); 2015 2016 if (yolSayac) { 2017 yolSayac.hidden = (q === ''); 2018 yolSayac.textContent = bulunan + ' madde'; 2019 } 2020 if (yolBos) yolBos.hidden = !(q !== '' && bulunan === 0); 2021 }; 2022 2023 yolGiris.addEventListener('input', yolSuz); 2024 } 2025 } 2026 2027 /* =================================================================== 2028 §21 GÃMME KODUNU KOPYALA (webmaster sayfası) 2029 2030 DüÄme sunucuda `hidden` baÅlıyor ve burada açılıyor: pano API'si 2031 yoksa ya da JS çalıÅmıyorsa çalıÅmayan bir düÄme göstermiyoruz â 2032 kod zaten <textarea> içinde seçilebilir hâlde. 2033 2034 navigator.clipboard yalnızca güvenli baÄlamda (https / localhost) 2035 var. Yoksa metni seçip execCommand'a düÅüyoruz; ikisi de 2036 olmuyorsa düÄmeyi hiç açmıyoruz. 2037 =================================================================== */ 2038 var kopKutu = document.querySelectorAll('[data-hp-kopyala-kutu]'); 2039 2040 if (kopKutu.length) { 2041 var kopVar = !!(navigator.clipboard && navigator.clipboard.writeText) || 2042 !!(document.queryCommandSupported && document.queryCommandSupported('copy')); 2043 2044 if (kopVar) { 2045 Array.prototype.forEach.call(kopKutu, function (kutu) { 2046 var alan = kutu.querySelector('[data-hp-kopyala-kaynak]'); 2047 var dug = kutu.querySelector('[data-hp-kopyala]'); 2048 if (!alan || !dug) return; 2049 2050 dug.hidden = false; 2051 2052 var etiket = dug.querySelector('[data-hp-kopyala-metin]') || dug; 2053 var eski = etiket.textContent; 2054 var zaman; 2055 2056 dug.addEventListener('click', function () { 2057 var bitir = function (ok) { 2058 etiket.textContent = ok ? 'Kopyalandı' : 'Kopyalanamadı'; 2059 dug.classList.toggle('is-oldu', ok); 2060 clearTimeout(zaman); 2061 zaman = setTimeout(function () { 2062 etiket.textContent = eski; 2063 dug.classList.remove('is-oldu'); 2064 }, 2000); 2065 }; 2066 2067 if (navigator.clipboard && navigator.clipboard.writeText) { 2068 navigator.clipboard.writeText(alan.value).then(function () { bitir(true); }, 2069 function () { bitir(false); }); 2070 } else { 2071 alan.select(); 2072 var ok = false; 2073 try { ok = document.execCommand('copy'); } catch (e) {} 2074 bitir(ok); 2075 } 2076 }); 2077 }); 2078 } 2079 } 2080 2081})();
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.