PageSourceSearch

https://www.muglaturk.com/theme/news/assets/js/theme.js?v=1790626126

js muglaturk.com collected 2026-10-07 01:58:52 UTC 83,008 bytes, 2,081 lines download raw bytes

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.