nodebb-plugin-ezoic-infinite 1.7.93 → 1.7.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/client.js +75 -146
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,12 +77,11 @@
|
|
|
77
77
|
const A_CREATED = 'data-ezoic-created'; // timestamp création ms
|
|
78
78
|
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
79
79
|
|
|
80
|
+
const EMPTY_CHECK_MS = 20_000; // délai avant collapse d'un wrap vide post-show
|
|
80
81
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
81
82
|
const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
|
|
82
|
-
const MAX_INFLIGHT =
|
|
83
|
-
const SHOW_THROTTLE_MS =
|
|
84
|
-
const UNUSED_COOLDOWN_MS = 15_000; // cooldown après tentative sans fill (unused probable)
|
|
85
|
-
const PENDING_TIMEOUT_MS = 12_000; // au-delà, tentative considérée obsolète
|
|
83
|
+
const MAX_INFLIGHT = 4; // max showAds() simultanés
|
|
84
|
+
const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
|
|
86
85
|
const BURST_COOLDOWN_MS = 200; // délai min entre deux déclenchements de burst
|
|
87
86
|
|
|
88
87
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
@@ -125,11 +124,8 @@
|
|
|
125
124
|
domObs: null,
|
|
126
125
|
mutGuard: 0, // >0 : mutations DOM en cours (MutationObserver ignoré)
|
|
127
126
|
inflight: 0, // showAds() en cours
|
|
128
|
-
pending: [], // ids en attente de slot inflight
|
|
127
|
+
pending: [], // ids en attente de slot inflight
|
|
129
128
|
pendingSet: new Set(),
|
|
130
|
-
pendingShows: new Set(), // ids avec tentative show en cours
|
|
131
|
-
cooldownUntil:new Map(), // id -> ts de fin de cooldown (unused probable)
|
|
132
|
-
fillObsById: new Map(), // id -> MutationObserver placeholder fill
|
|
133
129
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
134
130
|
runQueued: false,
|
|
135
131
|
burstActive: false,
|
|
@@ -137,7 +133,7 @@
|
|
|
137
133
|
burstCount: 0,
|
|
138
134
|
lastBurstTs: 0,
|
|
139
135
|
lastScrollY: 0,
|
|
140
|
-
scrollDir: 1,
|
|
136
|
+
scrollDir: 1, // 1=down, -1=up
|
|
141
137
|
};
|
|
142
138
|
|
|
143
139
|
let blockedUntil = 0;
|
|
@@ -147,69 +143,6 @@
|
|
|
147
143
|
const isMobile = () => { try { return window.innerWidth < 768; } catch (_) { return false; } };
|
|
148
144
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
149
145
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
150
|
-
const isCooling = id => (S.cooldownUntil.get(id) || 0) > ts();
|
|
151
|
-
|
|
152
|
-
function viewportDistance(el) {
|
|
153
|
-
try {
|
|
154
|
-
const r = el?.getBoundingClientRect?.();
|
|
155
|
-
if (!r) return Infinity;
|
|
156
|
-
const vh = window.innerHeight || 800;
|
|
157
|
-
if (r.bottom >= 0 && r.top <= vh) return 0;
|
|
158
|
-
if (r.top > vh) return Math.max(0, r.top - vh);
|
|
159
|
-
return Math.max(0, -r.bottom);
|
|
160
|
-
} catch (_) { return Infinity; }
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function pendingEnqueue(id) {
|
|
164
|
-
if (S.pendingSet.has(id)) return;
|
|
165
|
-
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
166
|
-
const prio = viewportDistance(ph?.closest?.(`.${WRAP_CLASS}`) || ph);
|
|
167
|
-
const item = { id, prio, at: ts() };
|
|
168
|
-
let i = S.pending.length;
|
|
169
|
-
while (i > 0 && S.pending[i - 1].prio > prio) i--;
|
|
170
|
-
S.pending.splice(i, 0, item);
|
|
171
|
-
S.pendingSet.add(id);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function markUnusedProbable(id) {
|
|
175
|
-
if (!id) return;
|
|
176
|
-
S.pendingShows.delete(id);
|
|
177
|
-
S.cooldownUntil.set(id, ts() + UNUSED_COOLDOWN_MS);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function clearCooldownIfFilled(id) {
|
|
181
|
-
if (!id) return;
|
|
182
|
-
S.cooldownUntil.delete(id);
|
|
183
|
-
S.pendingShows.delete(id);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function unobserveFill(id) {
|
|
187
|
-
const obs = S.fillObsById.get(id);
|
|
188
|
-
if (obs) { try { obs.disconnect(); } catch (_) {} }
|
|
189
|
-
S.fillObsById.delete(id);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function watchPlaceholderFill(id) {
|
|
193
|
-
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
194
|
-
if (!ph?.isConnected) return;
|
|
195
|
-
unobserveFill(id);
|
|
196
|
-
const onMut = () => {
|
|
197
|
-
try {
|
|
198
|
-
if (!ph.isConnected) return unobserveFill(id);
|
|
199
|
-
if (!isFilled(ph)) return;
|
|
200
|
-
clearCooldownIfFilled(id);
|
|
201
|
-
const wrap = ph.closest?.(`.${WRAP_CLASS}`);
|
|
202
|
-
if (wrap) wrap.classList.remove('is-empty');
|
|
203
|
-
} catch (_) {}
|
|
204
|
-
};
|
|
205
|
-
try {
|
|
206
|
-
const obs = new MutationObserver(onMut);
|
|
207
|
-
obs.observe(ph, { childList: true, subtree: true, attributes: true });
|
|
208
|
-
S.fillObsById.set(id, obs);
|
|
209
|
-
} catch (_) {}
|
|
210
|
-
onMut();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
146
|
|
|
214
147
|
function mutate(fn) {
|
|
215
148
|
S.mutGuard++;
|
|
@@ -351,21 +284,6 @@
|
|
|
351
284
|
return (w?.isConnected) ? w : null;
|
|
352
285
|
}
|
|
353
286
|
|
|
354
|
-
function sweepDeadWraps() {
|
|
355
|
-
for (const [key, w] of Array.from(S.wrapByKey.entries())) {
|
|
356
|
-
if (w?.isConnected) continue;
|
|
357
|
-
S.wrapByKey.delete(key);
|
|
358
|
-
const id = parseInt(w?.getAttribute?.(A_WRAPID), 10);
|
|
359
|
-
if (Number.isFinite(id)) {
|
|
360
|
-
S.mountedIds.delete(id);
|
|
361
|
-
S.pendingSet.delete(id);
|
|
362
|
-
S.pending = S.pending.filter(it => (it?.id ?? it) !== id);
|
|
363
|
-
S.pendingShows.delete(id);
|
|
364
|
-
unobserveFill(id);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
287
|
// ── Pool ───────────────────────────────────────────────────────────────────
|
|
370
288
|
|
|
371
289
|
/**
|
|
@@ -398,39 +316,56 @@
|
|
|
398
316
|
typeof ez?.displayMore !== 'function') return null;
|
|
399
317
|
|
|
400
318
|
const vh = window.innerHeight || 800;
|
|
401
|
-
const targetRect = targetEl?.getBoundingClientRect?.();
|
|
402
|
-
|
|
319
|
+
const targetRect = targetEl?.getBoundingClientRect?.() || { top: vh, bottom: vh };
|
|
320
|
+
|
|
321
|
+
// Recyclage bidirectionnel :
|
|
322
|
+
// - scroll vers le bas -> recycle préférentiellement des wraps loin au-dessus
|
|
323
|
+
// - scroll vers le haut -> recycle préférentiellement des wraps loin en-dessous
|
|
324
|
+
// Fallback sur l'autre côté si aucun candidat.
|
|
325
|
+
const aboveThreshold = -vh; // wrap entièrement/suffisamment au-dessus
|
|
326
|
+
const belowThreshold = vh * 2; // wrap loin sous le viewport
|
|
403
327
|
|
|
404
|
-
let
|
|
405
|
-
let
|
|
328
|
+
let aboveEmpty = null, aboveEmptyBottom = Infinity;
|
|
329
|
+
let aboveFilled = null, aboveFilledBottom = Infinity;
|
|
330
|
+
let belowEmpty = null, belowEmptyTop = -Infinity;
|
|
331
|
+
let belowFilled = null, belowFilledTop = -Infinity;
|
|
406
332
|
|
|
407
|
-
|
|
333
|
+
document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(wrap => {
|
|
408
334
|
try {
|
|
409
|
-
if (!wrap?.isConnected
|
|
335
|
+
if (!wrap?.isConnected) return;
|
|
410
336
|
const rect = wrap.getBoundingClientRect();
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
337
|
+
|
|
338
|
+
if (rect.bottom < aboveThreshold) {
|
|
339
|
+
if (!isFilled(wrap)) {
|
|
340
|
+
if (rect.bottom < aboveEmptyBottom) { aboveEmptyBottom = rect.bottom; aboveEmpty = wrap; }
|
|
341
|
+
} else {
|
|
342
|
+
if (rect.bottom < aboveFilledBottom) { aboveFilledBottom = rect.bottom; aboveFilled = wrap; }
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (rect.top > belowThreshold) {
|
|
348
|
+
if (!isFilled(wrap)) {
|
|
349
|
+
if (rect.top > belowEmptyTop) { belowEmptyTop = rect.top; belowEmpty = wrap; }
|
|
350
|
+
} else {
|
|
351
|
+
if (rect.top > belowFilledTop) { belowFilledTop = rect.top; belowFilled = wrap; }
|
|
352
|
+
}
|
|
423
353
|
}
|
|
424
354
|
} catch (_) {}
|
|
425
|
-
}
|
|
355
|
+
});
|
|
426
356
|
|
|
427
|
-
|
|
357
|
+
const preferBelow = (S.scrollDir < 0) || (targetRect.top < vh * 0.5);
|
|
358
|
+
const pickAbove = () => aboveEmpty ?? aboveFilled;
|
|
359
|
+
const pickBelow = () => belowEmpty ?? belowFilled;
|
|
360
|
+
const best = preferBelow ? (pickBelow() ?? pickAbove()) : (pickAbove() ?? pickBelow());
|
|
428
361
|
if (!best) return null;
|
|
429
362
|
const id = parseInt(best.getAttribute(A_WRAPID), 10);
|
|
430
363
|
if (!Number.isFinite(id)) return null;
|
|
431
364
|
|
|
432
365
|
const oldKey = best.getAttribute(A_ANCHOR);
|
|
433
|
-
|
|
366
|
+
// Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
|
|
367
|
+
// parasite si le nœud était encore dans la zone IO_MARGIN.
|
|
368
|
+
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
434
369
|
mutate(() => {
|
|
435
370
|
best.setAttribute(A_ANCHOR, newKey);
|
|
436
371
|
best.setAttribute(A_CREATED, String(ts()));
|
|
@@ -443,9 +378,10 @@
|
|
|
443
378
|
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
444
379
|
S.wrapByKey.set(newKey, best);
|
|
445
380
|
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
const
|
|
381
|
+
// Délais requis : destroyPlaceholders est asynchrone en interne
|
|
382
|
+
const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
|
|
383
|
+
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
384
|
+
const doDisplay = () => { try { ez.displayMore([id]); } catch (_) {} };
|
|
449
385
|
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
450
386
|
|
|
451
387
|
return { id, wrap: best };
|
|
@@ -485,7 +421,6 @@
|
|
|
485
421
|
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
486
422
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
487
423
|
const id = parseInt(w.getAttribute(A_WRAPID), 10);
|
|
488
|
-
if (Number.isFinite(id)) unobserveFill(id);
|
|
489
424
|
if (Number.isFinite(id)) S.mountedIds.delete(id);
|
|
490
425
|
const key = w.getAttribute(A_ANCHOR);
|
|
491
426
|
if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
|
|
@@ -549,7 +484,6 @@
|
|
|
549
484
|
function injectBetween(klass, items, interval, showFirst, poolKey) {
|
|
550
485
|
if (!items.length) return 0;
|
|
551
486
|
let inserted = 0;
|
|
552
|
-
sweepDeadWraps();
|
|
553
487
|
|
|
554
488
|
for (const el of items) {
|
|
555
489
|
if (inserted >= MAX_INSERTS_RUN) break;
|
|
@@ -562,15 +496,13 @@
|
|
|
562
496
|
const key = anchorKey(klass, el);
|
|
563
497
|
if (findWrap(key)) continue;
|
|
564
498
|
|
|
565
|
-
|
|
566
|
-
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
499
|
+
const id = pickId(poolKey);
|
|
567
500
|
if (id) {
|
|
568
501
|
const w = insertAfter(el, id, klass, key);
|
|
569
502
|
if (w) { observePh(id); inserted++; }
|
|
570
503
|
} else {
|
|
571
504
|
const recycled = recycleAndMove(klass, el, key);
|
|
572
505
|
if (!recycled) break;
|
|
573
|
-
observePh(recycled.id);
|
|
574
506
|
inserted++;
|
|
575
507
|
}
|
|
576
508
|
}
|
|
@@ -596,54 +528,45 @@
|
|
|
596
528
|
|
|
597
529
|
function observePh(id) {
|
|
598
530
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
599
|
-
if (ph?.isConnected) {
|
|
600
|
-
try { getIO()?.observe(ph); } catch (_) {}
|
|
601
|
-
watchPlaceholderFill(id);
|
|
602
|
-
}
|
|
531
|
+
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
603
532
|
}
|
|
604
533
|
|
|
605
534
|
function enqueueShow(id) {
|
|
606
535
|
if (!id || isBlocked()) return;
|
|
607
|
-
if (isCooling(id) || S.pendingShows.has(id)) return;
|
|
608
536
|
if (ts() - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) return;
|
|
609
|
-
if (S.inflight >= MAX_INFLIGHT) {
|
|
537
|
+
if (S.inflight >= MAX_INFLIGHT) {
|
|
538
|
+
if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); }
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
610
541
|
startShow(id);
|
|
611
542
|
}
|
|
612
543
|
|
|
613
544
|
function drainQueue() {
|
|
614
545
|
if (isBlocked()) return;
|
|
615
546
|
while (S.inflight < MAX_INFLIGHT && S.pending.length) {
|
|
616
|
-
const
|
|
617
|
-
const id = item?.id ?? item;
|
|
547
|
+
const id = S.pending.shift();
|
|
618
548
|
S.pendingSet.delete(id);
|
|
619
|
-
if (!id || isCooling(id) || S.pendingShows.has(id)) continue;
|
|
620
|
-
if (!document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) continue;
|
|
621
|
-
if (ts() - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) continue;
|
|
622
549
|
startShow(id);
|
|
623
550
|
}
|
|
624
551
|
}
|
|
625
552
|
|
|
626
553
|
function startShow(id) {
|
|
627
554
|
if (!id || isBlocked()) return;
|
|
628
|
-
if (S.pendingShows.has(id) || isCooling(id)) return;
|
|
629
|
-
S.pendingShows.add(id);
|
|
630
555
|
S.inflight++;
|
|
631
556
|
let done = false;
|
|
632
557
|
const release = () => {
|
|
633
558
|
if (done) return;
|
|
634
559
|
done = true;
|
|
635
|
-
S.pendingShows.delete(id);
|
|
636
560
|
S.inflight = Math.max(0, S.inflight - 1);
|
|
637
561
|
drainQueue();
|
|
638
562
|
};
|
|
639
|
-
const timer = setTimeout(
|
|
563
|
+
const timer = setTimeout(release, 7000);
|
|
640
564
|
|
|
641
565
|
requestAnimationFrame(() => {
|
|
642
566
|
try {
|
|
643
567
|
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
644
568
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
645
|
-
if (!ph?.isConnected) { clearTimeout(timer);
|
|
646
|
-
if (isFilled(ph)) { clearTimeout(timer); clearCooldownIfFilled(id); return release(); }
|
|
569
|
+
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
647
570
|
|
|
648
571
|
const t = ts();
|
|
649
572
|
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
@@ -655,6 +578,7 @@
|
|
|
655
578
|
const ez = window.ezstandalone;
|
|
656
579
|
const doShow = () => {
|
|
657
580
|
try { ez.showAds(id); } catch (_) {}
|
|
581
|
+
scheduleEmptyCheck(id, t);
|
|
658
582
|
setTimeout(() => { clearTimeout(timer); release(); }, 700);
|
|
659
583
|
};
|
|
660
584
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
@@ -662,6 +586,17 @@
|
|
|
662
586
|
});
|
|
663
587
|
}
|
|
664
588
|
|
|
589
|
+
function scheduleEmptyCheck(id, showTs) {
|
|
590
|
+
setTimeout(() => {
|
|
591
|
+
try {
|
|
592
|
+
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
593
|
+
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
594
|
+
if (!wrap || !ph?.isConnected) return;
|
|
595
|
+
if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
|
|
596
|
+
wrap.classList.toggle('is-empty', !isFilled(ph));
|
|
597
|
+
} catch (_) {}
|
|
598
|
+
}, EMPTY_CHECK_MS);
|
|
599
|
+
}
|
|
665
600
|
|
|
666
601
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
667
602
|
//
|
|
@@ -703,7 +638,6 @@
|
|
|
703
638
|
async function runCore() {
|
|
704
639
|
if (isBlocked()) return 0;
|
|
705
640
|
patchShowAds();
|
|
706
|
-
sweepDeadWraps();
|
|
707
641
|
|
|
708
642
|
const cfg = await fetchConfig();
|
|
709
643
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -791,9 +725,6 @@
|
|
|
791
725
|
S.inflight = 0;
|
|
792
726
|
S.pending = [];
|
|
793
727
|
S.pendingSet.clear();
|
|
794
|
-
S.pendingShows.clear();
|
|
795
|
-
S.cooldownUntil.clear();
|
|
796
|
-
for (const id of Array.from(S.fillObsById.keys())) unobserveFill(id);
|
|
797
728
|
S.burstActive = false;
|
|
798
729
|
S.runQueued = false;
|
|
799
730
|
}
|
|
@@ -805,12 +736,7 @@
|
|
|
805
736
|
const allSel = [SEL.post, SEL.topic, SEL.category];
|
|
806
737
|
S.domObs = new MutationObserver(muts => {
|
|
807
738
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
808
|
-
let sawWrapRemoval = false;
|
|
809
739
|
for (const m of muts) {
|
|
810
|
-
for (const n of (m.removedNodes || [])) {
|
|
811
|
-
if (n?.nodeType !== 1) continue;
|
|
812
|
-
try { if ((n.classList && n.classList.contains(WRAP_CLASS)) || n.querySelector?.(`.${WRAP_CLASS}`)) sawWrapRemoval = true; } catch (_) {}
|
|
813
|
-
}
|
|
814
740
|
for (const n of m.addedNodes) {
|
|
815
741
|
if (n.nodeType !== 1) continue;
|
|
816
742
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
@@ -820,7 +746,6 @@
|
|
|
820
746
|
}
|
|
821
747
|
}
|
|
822
748
|
}
|
|
823
|
-
if (sawWrapRemoval) sweepDeadWraps();
|
|
824
749
|
});
|
|
825
750
|
try { S.domObs.observe(document.body, { childList: true, subtree: true }); } catch (_) {}
|
|
826
751
|
}
|
|
@@ -922,11 +847,14 @@
|
|
|
922
847
|
function bindScroll() {
|
|
923
848
|
let ticking = false;
|
|
924
849
|
window.addEventListener('scroll', () => {
|
|
850
|
+
try {
|
|
851
|
+
const y = window.scrollY || window.pageYOffset || 0;
|
|
852
|
+
const dy = y - (S.lastScrollY || 0);
|
|
853
|
+
if (Math.abs(dy) > 2) S.scrollDir = dy > 0 ? 1 : -1;
|
|
854
|
+
S.lastScrollY = y;
|
|
855
|
+
} catch (_) {}
|
|
925
856
|
if (ticking) return;
|
|
926
857
|
ticking = true;
|
|
927
|
-
const y = window.scrollY || window.pageYOffset || 0;
|
|
928
|
-
S.scrollDir = y >= (S.lastScrollY || 0) ? 1 : -1;
|
|
929
|
-
S.lastScrollY = y;
|
|
930
858
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
931
859
|
}, { passive: true });
|
|
932
860
|
}
|
|
@@ -934,6 +862,7 @@
|
|
|
934
862
|
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
935
863
|
|
|
936
864
|
S.pageKey = pageKey();
|
|
865
|
+
try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) { S.lastScrollY = 0; }
|
|
937
866
|
muteConsole();
|
|
938
867
|
ensureTcfLocator();
|
|
939
868
|
warmNetwork();
|