nodebb-plugin-ezoic-infinite 1.7.85 → 1.7.86

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.7.85",
3
+ "version": "1.7.86",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -76,13 +76,10 @@
76
76
  const A_WRAPID = 'data-ezoic-wrapid'; // id Ezoic
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
- const A_FILLED = 'data-ezoic-filled'; // timestamp premier fill réel ms
80
- const A_LAST_W = 'data-ezoic-lastw'; // dernière largeur pub connue
81
- const A_LAST_H = 'data-ezoic-lasth'; // dernière hauteur pub connue
79
+ const A_FILLED = 'data-ezoic-filled'; // timestamp premier fill réel
80
+ const A_LAST_H = 'data-ezoic-lasth'; // dernière hauteur pub vue
82
81
 
83
82
  const EMPTY_CHECK_MS = 20_000; // délai avant collapse d'un wrap vide post-show
84
- const EMPTY_RECHECK_2_MS = EMPTY_CHECK_MS + 5_000;
85
- const EMPTY_RECHECK_3_MS = EMPTY_CHECK_MS + 15_000;
86
83
  const MIN_LIVE_AFTER_SHOW_MS = 15_000;
87
84
  const MIN_LIVE_AFTER_FILL_MS = 25_000;
88
85
  const KEEP_SHELL_AFTER_UNUSED_MS = 90_000;
@@ -136,7 +133,8 @@
136
133
  pending: [], // ids en attente de slot inflight
137
134
  pendingSet: new Set(),
138
135
  wrapByKey: new Map(), // anchorKey → wrap DOM node
139
- emptyChecks: new Map(), // id -> timeout ids
136
+ emptyChecks: new Map(), // id [timerIds]
137
+ fillObsById: new Map(), // id → MutationObserver
140
138
  runQueued: false,
141
139
  burstActive: false,
142
140
  burstDeadline: 0,
@@ -152,185 +150,89 @@
152
150
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
153
151
  const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
154
152
 
155
- function mutate(fn) {
156
- S.mutGuard++;
157
- try { fn(); } finally { S.mutGuard--; }
158
- }
159
153
 
160
- function clearEmptyChecks(id) {
161
- const timers = S.emptyChecks.get(id);
162
- if (!timers) return;
163
- for (const t of timers) clearTimeout(t);
164
- S.emptyChecks.delete(id);
165
- }
166
-
167
- function queueEmptyCheck(id, timerId) {
168
- if (!S.emptyChecks.has(id)) S.emptyChecks.set(id, []);
169
- S.emptyChecks.get(id).push(timerId);
170
- }
171
-
172
- function hasEverFilled(wrap) { return !!wrap?.getAttribute?.(A_FILLED); }
173
- function getFilledTs(wrap) { return parseInt(wrap?.getAttribute?.(A_FILLED) || '0', 10) || 0; }
174
- function shouldKeepShellAfterUnused(wrap) {
175
- const t = getFilledTs(wrap);
176
- return !!t && (Date.now() - t) < KEEP_SHELL_AFTER_UNUSED_MS;
177
- }
178
- function markFilledOnce(ph) {
179
- try {
180
- const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
181
- if (!wrap) return;
182
- if (!wrap.getAttribute(A_FILLED) && isFilled(ph)) wrap.setAttribute(A_FILLED, String(Date.now()));
183
- } catch (_) {}
184
- }
185
- function rememberAdSize(ph, w, h) {
186
- try {
187
- const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
188
- if (!wrap) return;
189
- const ww = Math.round(w || 0), hh = Math.round(h || 0);
190
- if (ww > 0) wrap.setAttribute(A_LAST_W, String(ww));
191
- if (hh > 0) wrap.setAttribute(A_LAST_H, String(hh));
192
- } catch (_) {}
193
- }
194
- function getAdIntrinsicSize(ph) {
195
- if (!ph) return null;
196
- const iframe = ph.querySelector('iframe');
197
- if (iframe) {
198
- const wAttr = parseInt(iframe.getAttribute('width') || iframe.getAttribute('ezaw') || '0', 10);
199
- const hAttr = parseInt(iframe.getAttribute('height') || iframe.getAttribute('ezah') || '0', 10);
200
- const cs = window.getComputedStyle(iframe);
201
- const w = wAttr || Math.round(parseFloat(cs.width) || iframe.offsetWidth || 0);
202
- const h = hAttr || Math.round(parseFloat(cs.height) || iframe.offsetHeight || 0);
203
- if (w > 0 && h > 0) return { w, h };
204
- }
205
- const c = ph.querySelector('[id$="__container__"]') || ph.querySelector('.ezoic-ad');
206
- if (c) {
207
- const cs = window.getComputedStyle(c);
208
- const w = Math.round(parseFloat(cs.width) || c.offsetWidth || 0);
209
- const h = Math.round(parseFloat(cs.height) || c.offsetHeight || 0);
210
- if (w > 0 && h > 0) return { w, h };
211
- }
212
- return null;
213
- }
214
- function rememberCurrentAdSize(ph) {
215
- try {
216
- const size = getAdIntrinsicSize(ph);
217
- if (size?.w > 0 && size?.h > 0) return rememberAdSize(ph, size.w, size.h);
218
- const r = ph.getBoundingClientRect?.();
219
- if (r && (r.width > 0 || r.height > 0)) rememberAdSize(ph, r.width, r.height);
220
- } catch (_) {}
221
- }
222
- function ensureScaleBox(ph) {
223
- let box = ph.querySelector(':scope > .nbb-ez-scale-box');
224
- if (box) return box;
225
- let hasChild = false;
226
- for (const n of Array.from(ph.childNodes)) {
227
- if (n.nodeType === 1) { hasChild = true; break; }
228
- if (n.nodeType === 3 && (n.textContent || '').trim()) { hasChild = true; break; }
229
- }
230
- if (!hasChild) return null;
231
- box = document.createElement('div');
232
- box.className = 'nbb-ez-scale-box';
233
- while (ph.firstChild) box.appendChild(ph.firstChild);
234
- ph.appendChild(box);
235
- return box;
236
- }
237
- function clearAdScale(ph) {
154
+ function clearEmptyChecks(id) {
155
+ const timers = S.emptyChecks.get(id);
156
+ if (!timers) return;
157
+ for (const t of timers) { try { clearTimeout(t); } catch (_) {} }
158
+ S.emptyChecks.delete(id);
159
+ }
160
+ function queueEmptyCheck(id, timerId) {
161
+ const arr = S.emptyChecks.get(id) || [];
162
+ arr.push(timerId);
163
+ S.emptyChecks.set(id, arr);
164
+ }
165
+ function markFilledOnce(ph) {
166
+ try {
167
+ const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
168
+ if (!wrap || !isFilled(ph)) return;
169
+ if (!wrap.getAttribute(A_FILLED)) wrap.setAttribute(A_FILLED, String(ts()));
170
+ const h = Math.round(ph.getBoundingClientRect?.().height || ph.offsetHeight || 0);
171
+ if (h > 0) wrap.setAttribute(A_LAST_H, String(h));
172
+ } catch (_) {}
173
+ }
174
+ function shouldKeepShellAfterUnused(wrap) {
175
+ try {
176
+ const filledTs = parseInt(wrap?.getAttribute?.(A_FILLED) || '0', 10) || 0;
177
+ return !!filledTs && (ts() - filledTs) < KEEP_SHELL_AFTER_UNUSED_MS;
178
+ } catch (_) { return false; }
179
+ }
180
+ function applyUnusedShell(wrap, ph) {
181
+ try {
182
+ const lastH = parseInt(wrap.getAttribute(A_LAST_H) || '0', 10) || 0;
183
+ const h = Math.max(MIN_SHELL_HEIGHT, lastH || 0);
184
+ wrap.classList.remove('is-empty');
185
+ wrap.classList.add('is-unused-shell');
186
+ wrap.style.minHeight = `${h}px`;
187
+ if (ph) ph.style.minHeight = `${h}px`;
188
+ } catch (_) {}
189
+ }
190
+ function clearUnusedShell(wrap, ph) {
191
+ try {
192
+ wrap?.classList?.remove('is-unused-shell');
193
+ wrap?.style?.removeProperty('min-height');
194
+ ph?.style?.removeProperty('min-height');
195
+ } catch (_) {}
196
+ }
197
+ function isProtectedFromDrop(wrap) {
198
+ try {
199
+ const now = ts();
200
+ const shownTs = parseInt(wrap?.getAttribute?.(A_SHOWN) || '0', 10) || 0;
201
+ const filledTs = parseInt(wrap?.getAttribute?.(A_FILLED) || '0', 10) || 0;
202
+ if (shownTs && (now - shownTs) < MIN_LIVE_AFTER_SHOW_MS) return true;
203
+ if (filledTs && (now - filledTs) < MIN_LIVE_AFTER_FILL_MS) return true;
204
+ return false;
205
+ } catch (_) { return false; }
206
+ }
207
+ function uncollapseIfFilled(ph) {
208
+ try {
238
209
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
239
210
  if (!wrap) return;
240
- wrap.classList.remove('is-scaled');
241
- wrap.style.removeProperty('height');
242
- const box = ph.querySelector(':scope > .nbb-ez-scale-box');
243
- if (box) {
244
- box.style.removeProperty('transform');
245
- box.style.removeProperty('width');
246
- box.style.removeProperty('height');
247
- }
248
- ph.style.removeProperty('height');
249
- }
250
- function fitWideAd(ph) {
251
- try {
252
- if (!ph?.isConnected) return;
253
- const wrap = ph.closest?.(`.${WRAP_CLASS}`);
254
- if (!wrap) return;
255
- if (!isFilled(ph)) return clearAdScale(ph);
256
- const size = getAdIntrinsicSize(ph);
257
- if (!size?.w || !size?.h) return;
258
- rememberAdSize(ph, size.w, size.h);
259
- const wrapWidth = Math.max(0, Math.floor(wrap.clientWidth || wrap.getBoundingClientRect().width || 0));
260
- if (!wrapWidth) return;
261
- if (size.w <= wrapWidth + 2) return clearAdScale(ph);
262
- const scale = Math.max(0.1, Math.min(1, wrapWidth / size.w));
263
- const scaledH = Math.ceil(size.h * scale);
264
- const box = ensureScaleBox(ph);
265
- if (!box) return;
266
- wrap.classList.add('is-scaled');
267
- box.style.width = `${size.w}px`;
268
- box.style.height = `${size.h}px`;
269
- box.style.transform = `scale(${scale})`;
270
- ph.style.height = `${scaledH}px`;
271
- wrap.style.height = `${scaledH}px`;
272
- } catch (_) {}
273
- }
274
- function applyUnusedShell(wrap, ph) {
275
- try {
276
- if (!wrap) return;
277
- const lastH = parseInt(wrap.getAttribute(A_LAST_H) || '0', 10) || 0;
278
- const h = Math.max(MIN_SHELL_HEIGHT, lastH || 0);
211
+ if (isFilled(ph)) {
279
212
  wrap.classList.remove('is-empty');
280
- wrap.classList.add('is-unused-shell');
281
- wrap.style.minHeight = `${h}px`;
282
- if (ph) ph.style.minHeight = `${h}px`;
283
- } catch (_) {}
284
- }
285
- function clearUnusedShell(wrap, ph) {
286
- try {
287
- wrap?.classList?.remove('is-unused-shell');
288
- wrap?.style?.removeProperty('min-height');
289
- ph?.style?.removeProperty('min-height');
290
- } catch (_) {}
291
- }
292
- function isProtectedFromDrop(wrap) {
293
- try {
294
- const now = Date.now();
295
- const shownTs = parseInt(wrap?.getAttribute?.(A_SHOWN) || '0', 10) || 0;
296
- const filledTs = parseInt(wrap?.getAttribute?.(A_FILLED) || '0', 10) || 0;
297
- if (shownTs && (now - shownTs) < MIN_LIVE_AFTER_SHOW_MS) return true;
298
- if (filledTs && (now - filledTs) < MIN_LIVE_AFTER_FILL_MS) return true;
299
- if (shouldKeepShellAfterUnused(wrap)) return true;
300
- } catch (_) {}
301
- return false;
302
- }
303
- function uncollapseIfFilled(ph) {
304
- try {
305
- const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
306
- if (!wrap) return;
307
- if (isFilled(ph)) {
308
- wrap.classList.remove('is-empty');
309
- clearUnusedShell(wrap, ph);
310
- markFilledOnce(ph);
311
- rememberCurrentAdSize(ph);
312
- fitWideAd(ph);
313
- }
314
- } catch (_) {}
315
- }
316
- function watchPlaceholderFill(ph) {
317
- if (!ph || ph.__nbbEzFillObs) return;
318
- const obs = new MutationObserver(() => { uncollapseIfFilled(ph); });
319
- try { obs.observe(ph, { childList: true, subtree: true, attributes: true }); } catch (_) { return; }
320
- ph.__nbbEzFillObs = obs;
213
+ clearUnusedShell(wrap, ph);
214
+ markFilledOnce(ph);
215
+ }
216
+ } catch (_) {}
217
+ }
218
+ function unwatchPlaceholderFill(id) {
219
+ const obs = S.fillObsById.get(id);
220
+ if (obs) { try { obs.disconnect(); } catch (_) {} S.fillObsById.delete(id); }
221
+ }
222
+ function watchPlaceholderFill(id) {
223
+ try {
224
+ const ph = document.getElementById(`${PH_PREFIX}${id}`);
225
+ if (!ph || !ph.isConnected || S.fillObsById.has(id)) return;
226
+ const obs = new MutationObserver(() => uncollapseIfFilled(ph));
227
+ obs.observe(ph, { childList: true, subtree: true, attributes: true });
228
+ S.fillObsById.set(id, obs);
321
229
  uncollapseIfFilled(ph);
322
- }
323
- function unwatchPlaceholderFill(ph) {
324
- try { ph?.__nbbEzFillObs?.disconnect?.(); } catch (_) {}
325
- try { delete ph.__nbbEzFillObs; } catch (_) {}
326
- }
327
- let fitRaf = 0;
328
- function scheduleRefitAll() {
329
- if (fitRaf) return;
330
- fitRaf = requestAnimationFrame(() => {
331
- fitRaf = 0;
332
- document.querySelectorAll(`[id^="${PH_PREFIX}"]`).forEach(ph => { try { fitWideAd(ph); } catch (_) {} });
333
- });
230
+ } catch (_) {}
231
+ }
232
+
233
+ function mutate(fn) {
234
+ S.mutGuard++;
235
+ try { fn(); } finally { S.mutGuard--; }
334
236
  }
335
237
 
336
238
  // ── Config ─────────────────────────────────────────────────────────────────
@@ -520,27 +422,27 @@
520
422
 
521
423
  const best = bestEmpty ?? bestFilled;
522
424
  if (!best) return null;
523
- if (isProtectedFromDrop(best)) return null;
524
425
  const id = parseInt(best.getAttribute(A_WRAPID), 10);
525
426
  if (!Number.isFinite(id)) return null;
526
427
 
527
428
  const oldKey = best.getAttribute(A_ANCHOR);
528
429
  // Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
529
430
  // parasite si le nœud était encore dans la zone IO_MARGIN.
530
- try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); clearAdScale(ph); clearUnusedShell(best, ph); } } catch (_) {}
431
+ try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
531
432
  mutate(() => {
532
433
  best.setAttribute(A_ANCHOR, newKey);
533
434
  best.setAttribute(A_CREATED, String(ts()));
534
435
  best.setAttribute(A_SHOWN, '0');
535
- best.classList.remove('is-empty', 'is-unused-shell');
436
+ best.classList.remove('is-empty');
437
+ best.classList.remove('is-unused-shell');
438
+ best.removeAttribute(A_FILLED);
439
+ best.removeAttribute(A_LAST_H);
536
440
  const ph = best.querySelector(`#${PH_PREFIX}${id}`);
537
- if (ph) { ph.innerHTML = ''; ph.style.removeProperty('min-height'); ph.style.removeProperty('height'); }
538
- best.removeAttribute(A_FILLED); best.removeAttribute(A_LAST_W); best.removeAttribute(A_LAST_H);
441
+ if (ph) { ph.innerHTML = ''; ph.style.removeProperty('min-height'); }
539
442
  targetEl.insertAdjacentElement('afterend', best);
540
443
  });
541
444
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
542
445
  S.wrapByKey.set(newKey, best);
543
- try { const ph2 = best.querySelector(`#${PH_PREFIX}${id}`); if (ph2) watchPlaceholderFill(ph2); } catch (_) {}
544
446
 
545
447
  // Délais requis : destroyPlaceholders est asynchrone en interne
546
448
  const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
@@ -577,26 +479,28 @@
577
479
  mutate(() => el.insertAdjacentElement('afterend', w));
578
480
  S.mountedIds.add(id);
579
481
  S.wrapByKey.set(key, w);
482
+ watchPlaceholderFill(id);
580
483
  return w;
581
484
  }
582
485
 
583
486
  function dropWrap(w) {
584
487
  try {
585
- if (isProtectedFromDrop(w)) {
586
- const keepPh = w.querySelector(`[id^="${PH_PREFIX}"]`);
587
- if (shouldKeepShellAfterUnused(w)) applyUnusedShell(w, keepPh);
588
- return false;
589
- }
590
488
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
591
- if (ph instanceof Element) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); clearAdScale(ph); clearUnusedShell(w, ph); }
592
489
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
593
- if (Number.isFinite(id)) { S.mountedIds.delete(id); clearEmptyChecks(id); }
490
+ if (isProtectedFromDrop(w) || shouldKeepShellAfterUnused(w)) {
491
+ if (ph) applyUnusedShell(w, ph);
492
+ return;
493
+ }
494
+ if (ph instanceof Element) S.io?.unobserve(ph);
495
+ if (Number.isFinite(id)) {
496
+ clearEmptyChecks(id);
497
+ unwatchPlaceholderFill(id);
498
+ S.mountedIds.delete(id);
499
+ }
594
500
  const key = w.getAttribute(A_ANCHOR);
595
501
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
596
502
  w.remove();
597
- return true;
598
503
  } catch (_) {}
599
- return false;
600
504
  }
601
505
 
602
506
  // ── Prune (topics de catégorie uniquement) ────────────────────────────────
@@ -699,9 +603,10 @@
699
603
 
700
604
  function observePh(id) {
701
605
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
702
- if (!ph?.isConnected) return;
703
- try { watchPlaceholderFill(ph); } catch (_) {}
704
- try { getIO()?.observe(ph); } catch (_) {}
606
+ if (ph?.isConnected) {
607
+ watchPlaceholderFill(id);
608
+ try { getIO()?.observe(ph); } catch (_) {}
609
+ }
705
610
  }
706
611
 
707
612
  function enqueueShow(id) {
@@ -739,27 +644,21 @@
739
644
  try {
740
645
  if (isBlocked()) { clearTimeout(timer); return release(); }
741
646
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
742
- if (!ph?.isConnected || isFilled(ph)) { try { if (ph) uncollapseIfFilled(ph); } catch (_) {} clearTimeout(timer); return release(); }
647
+ if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
648
+ clearEmptyChecks(id);
649
+ try { const wrap = ph.closest?.(`.${WRAP_CLASS}`); wrap?.classList?.remove('is-empty'); clearUnusedShell(wrap, ph); } catch (_) {}
743
650
 
744
651
  const t = ts();
745
652
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
746
653
  S.lastShow.set(id, t);
747
654
 
748
- try {
749
- const wrap = ph.closest?.(`.${WRAP_CLASS}`);
750
- wrap?.setAttribute(A_SHOWN, String(t));
751
- wrap?.classList?.remove('is-empty');
752
- clearUnusedShell(wrap, ph);
753
- clearEmptyChecks(id);
754
- } catch (_) {}
655
+ try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
755
656
 
756
657
  window.ezstandalone = window.ezstandalone || {};
757
658
  const ez = window.ezstandalone;
758
659
  const doShow = () => {
759
660
  try { ez.showAds(id); } catch (_) {}
760
661
  scheduleEmptyCheck(id, t);
761
- setTimeout(() => { try { const p = document.getElementById(`${PH_PREFIX}${id}`); if (p) fitWideAd(p); } catch (_) {} }, 500);
762
- setTimeout(() => { try { const p = document.getElementById(`${PH_PREFIX}${id}`); if (p) fitWideAd(p); } catch (_) {} }, 2500);
763
662
  setTimeout(() => { clearTimeout(timer); release(); }, 700);
764
663
  };
765
664
  Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
@@ -768,8 +667,6 @@
768
667
  }
769
668
 
770
669
  function scheduleEmptyCheck(id, showTs) {
771
- clearEmptyChecks(id);
772
-
773
670
  const runCheck = () => {
774
671
  try {
775
672
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
@@ -781,25 +678,24 @@
781
678
  wrap.classList.remove('is-empty');
782
679
  clearUnusedShell(wrap, ph);
783
680
  markFilledOnce(ph);
784
- rememberCurrentAdSize(ph);
785
- fitWideAd(ph);
786
681
  return;
787
682
  }
788
683
 
789
- if (hasEverFilled(wrap) || shouldKeepShellAfterUnused(wrap)) {
684
+ if (shouldKeepShellAfterUnused(wrap)) {
790
685
  applyUnusedShell(wrap, ph);
791
686
  return;
792
687
  }
793
688
 
794
689
  clearUnusedShell(wrap, ph);
795
- clearAdScale(ph);
796
690
  wrap.classList.add('is-empty');
797
691
  } catch (_) {}
798
692
  };
799
693
 
800
- queueEmptyCheck(id, setTimeout(runCheck, EMPTY_CHECK_MS));
801
- queueEmptyCheck(id, setTimeout(runCheck, EMPTY_RECHECK_2_MS));
802
- queueEmptyCheck(id, setTimeout(runCheck, EMPTY_RECHECK_3_MS));
694
+ clearEmptyChecks(id);
695
+ [EMPTY_CHECK_MS, EMPTY_CHECK_MS + 5000, EMPTY_CHECK_MS + 15000].forEach(delay => {
696
+ const t = setTimeout(runCheck, delay);
697
+ queueEmptyCheck(id, t);
698
+ });
803
699
  }
804
700
 
805
701
  // ── Patch Ezoic showAds ────────────────────────────────────────────────────
@@ -925,9 +821,9 @@
925
821
  S.cursors = { topics: 0, posts: 0, categories: 0 };
926
822
  S.mountedIds.clear();
927
823
  S.lastShow.clear();
824
+ for (const id of Array.from(S.emptyChecks.keys())) clearEmptyChecks(id);
825
+ for (const id of Array.from(S.fillObsById.keys())) unwatchPlaceholderFill(id);
928
826
  S.wrapByKey.clear();
929
- for (const timers of S.emptyChecks.values()) for (const t of timers) clearTimeout(t);
930
- S.emptyChecks.clear();
931
827
  S.inflight = 0;
932
828
  S.pending = [];
933
829
  S.pendingSet.clear();
@@ -1059,10 +955,6 @@
1059
955
  }, { passive: true });
1060
956
  }
1061
957
 
1062
- function bindResize() {
1063
- window.addEventListener('resize', scheduleRefitAll, { passive: true });
1064
- }
1065
-
1066
958
  // ── Boot ───────────────────────────────────────────────────────────────────
1067
959
 
1068
960
  S.pageKey = pageKey();
@@ -1074,7 +966,6 @@
1074
966
  ensureDomObserver();
1075
967
  bindNodeBB();
1076
968
  bindScroll();
1077
- bindResize();
1078
969
  blockedUntil = 0;
1079
970
  requestBurst();
1080
971
 
package/public/style.css CHANGED
@@ -8,7 +8,7 @@
8
8
  width: 100%;
9
9
  margin: 0 !important;
10
10
  padding: 0 !important;
11
- overflow: hidden;
11
+ overflow: visible;
12
12
  contain: layout style;
13
13
  }
14
14
 
@@ -77,7 +77,8 @@
77
77
  padding: 0 !important;
78
78
  }
79
79
 
80
- /* ── Hardening async fill + responsive creatives ───────────────────────── */
80
+
81
+ /* Filet de sécurité : si un fill arrive tard alors que .is-empty est resté, ne pas écraser */
81
82
  .nodebb-ezoic-wrap.is-empty:has(iframe, ins, img, video, [data-google-container-id]) {
82
83
  height: auto !important;
83
84
  min-height: 1px !important;
@@ -85,16 +86,20 @@
85
86
  overflow: visible !important;
86
87
  }
87
88
 
88
- .nodebb-ezoic-wrap {
89
- max-width: 100%;
90
- overflow-x: clip;
91
- overflow-y: visible;
89
+ /* Shell conservé quand Ezoic repasse en "unused" après un affichage initial */
90
+ .nodebb-ezoic-wrap.is-unused-shell {
91
+ overflow: hidden !important;
92
+ }
93
+ .nodebb-ezoic-wrap.is-unused-shell > [id^="ezoic-pub-ad-placeholder-"] {
94
+ display: block;
95
+ width: 100%;
92
96
  }
93
97
 
98
+ /* Responsive hardening conservatif (sans scale JS) */
99
+ .nodebb-ezoic-wrap,
94
100
  .nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
95
101
  max-width: 100%;
96
102
  }
97
-
98
103
  .nodebb-ezoic-wrap .ezoic-ad,
99
104
  .nodebb-ezoic-wrap span.ezoic-ad {
100
105
  max-width: 100% !important;
@@ -102,34 +107,9 @@
102
107
  box-sizing: border-box !important;
103
108
  }
104
109
 
105
- .nodebb-ezoic-wrap [id^="google_ads_iframe_"][id$="__container__"],
106
- .nodebb-ezoic-wrap div[id$="__container__"] {
107
- max-width: 100% !important;
108
- margin-left: auto !important;
109
- margin-right: auto !important;
110
- overflow: visible !important;
111
- }
112
-
113
- .nodebb-ezoic-wrap iframe {
114
- margin-left: auto !important;
115
- margin-right: auto !important;
116
- max-width: none !important;
117
- }
118
-
119
- .nodebb-ezoic-wrap.is-scaled {
120
- overflow: hidden !important;
121
- }
122
-
123
- .nodebb-ezoic-wrap .nbb-ez-scale-box {
124
- transform-origin: top center;
125
- will-change: transform;
126
- }
127
-
128
- .nodebb-ezoic-wrap.is-unused-shell {
129
- overflow: hidden !important;
130
- }
131
-
132
- .nodebb-ezoic-wrap.is-unused-shell > [id^="ezoic-pub-ad-placeholder-"] {
133
- display: block;
134
- width: 100%;
110
+ /* Neutralisation sticky plus défensive dans les wraps du plugin */
111
+ .nodebb-ezoic-wrap .ezads-sticky-intradiv,
112
+ .nodebb-ezoic-wrap [style*="position: sticky"] {
113
+ position: static !important;
114
+ top: auto !important;
135
115
  }