nodebb-plugin-ezoic-infinite 1.7.84 → 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.84",
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,16 +76,19 @@
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
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
80
81
 
81
82
  const EMPTY_CHECK_MS = 20_000; // délai avant collapse d'un wrap vide post-show
83
+ const MIN_LIVE_AFTER_SHOW_MS = 15_000;
84
+ const MIN_LIVE_AFTER_FILL_MS = 25_000;
85
+ const KEEP_SHELL_AFTER_UNUSED_MS = 90_000;
86
+ const MIN_SHELL_HEIGHT = 120;
82
87
  const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
83
88
  const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
84
89
  const MAX_INFLIGHT = 4; // max showAds() simultanés
85
90
  const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
86
91
  const BURST_COOLDOWN_MS = 200; // délai min entre deux déclenchements de burst
87
- const MIN_LIVE_AFTER_SHOW_MS = 15_000; // grâce post-show avant drop/recycle
88
- const MIN_LIVE_AFTER_FILL_MS = 25_000; // grâce post-fill réel avant drop/recycle
89
92
 
90
93
  // Marges IO larges et fixes — observer créé une seule fois au boot
91
94
  const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
@@ -130,8 +133,8 @@
130
133
  pending: [], // ids en attente de slot inflight
131
134
  pendingSet: new Set(),
132
135
  wrapByKey: new Map(), // anchorKey → wrap DOM node
133
- emptyChecks: new Map(), // id -> timeout ids for empty checks
134
- fillObs: new Map(), // id -> MutationObserver (late fill uncollapse + responsive fit)
136
+ emptyChecks: new Map(), // id [timerIds]
137
+ fillObsById: new Map(), // id MutationObserver
135
138
  runQueued: false,
136
139
  burstActive: false,
137
140
  burstDeadline: 0,
@@ -147,153 +150,85 @@
147
150
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
148
151
  const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
149
152
 
150
- function clearEmptyChecks(id) {
151
- const timers = S.emptyChecks.get(id);
152
- if (timers) {
153
- for (const t of timers) { try { clearTimeout(t); } catch (_) {} }
154
- S.emptyChecks.delete(id);
155
- }
156
- }
157
-
158
- function queueEmptyCheck(id, timerId) {
159
- if (!S.emptyChecks.has(id)) S.emptyChecks.set(id, []);
160
- S.emptyChecks.get(id).push(timerId);
161
- }
162
153
 
163
- function markFilledOnce(ph) {
164
- try {
165
- const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
166
- if (!wrap) return;
167
- if (!wrap.getAttribute(A_FILLED) && isFilled(ph)) {
168
- wrap.setAttribute(A_FILLED, String(ts()));
169
- }
170
- } catch (_) {}
171
- }
172
-
173
- function isProtectedFromDrop(wrap) {
174
- try {
175
- if (!wrap) return false;
176
- const now = ts();
177
- const shownTs = parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) || 0;
178
- const filledTs = parseInt(wrap.getAttribute(A_FILLED) || '0', 10) || 0;
179
- if (shownTs && (now - shownTs) < MIN_LIVE_AFTER_SHOW_MS) return true;
180
- if (filledTs && (now - filledTs) < MIN_LIVE_AFTER_FILL_MS) return true;
181
- return false;
182
- } catch (_) { return false; }
183
- }
184
-
185
- function getAdIntrinsicSize(ph) {
186
- if (!ph) return null;
187
- const iframe = ph.querySelector('iframe');
188
- if (iframe) {
189
- const wAttr = parseInt(iframe.getAttribute('width') || iframe.getAttribute('ezaw') || '0', 10);
190
- const hAttr = parseInt(iframe.getAttribute('height') || iframe.getAttribute('ezah') || '0', 10);
191
- const cs = window.getComputedStyle(iframe);
192
- const wCss = parseFloat(cs.width) || 0;
193
- const hCss = parseFloat(cs.height) || 0;
194
- const w = wAttr || Math.round(wCss);
195
- const h = hAttr || Math.round(hCss);
196
- if (w > 0 && h > 0) return { w, h };
197
- }
198
- const gpt = ph.querySelector('[id$="__container__"]');
199
- if (gpt) {
200
- const cs = window.getComputedStyle(gpt);
201
- const w = Math.round(parseFloat(cs.width) || gpt.offsetWidth || 0);
202
- const h = Math.round(parseFloat(cs.height) || gpt.offsetHeight || 0);
203
- if (w > 0 && h > 0) return { w, h };
204
- }
205
- const ez = ph.querySelector('.ezoic-ad');
206
- if (ez) {
207
- const cs = window.getComputedStyle(ez);
208
- const w = Math.round(parseFloat(cs.width) || ez.offsetWidth || 0);
209
- const h = Math.round(parseFloat(cs.height) || ez.offsetHeight || 0);
210
- if (w > 0 && h > 0) return { w, h };
211
- }
212
- return null;
213
- }
214
-
215
- function ensureScaleBox(ph) {
216
- let box = ph.querySelector(':scope > .nbb-ez-scale-box');
217
- if (box) return box;
218
- const kids = Array.from(ph.childNodes).filter(n => {
219
- if (n.nodeType === 1) return true;
220
- if (n.nodeType === 3) return (n.textContent || '').trim().length > 0;
221
- return false;
222
- });
223
- if (!kids.length) return null;
224
- box = document.createElement('div');
225
- box.className = 'nbb-ez-scale-box';
226
- while (ph.firstChild) box.appendChild(ph.firstChild);
227
- ph.appendChild(box);
228
- return box;
229
- }
230
-
231
- 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 {
232
209
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
233
210
  if (!wrap) return;
234
- wrap.classList.remove('is-scaled');
235
- wrap.style.removeProperty('height');
236
- const box = ph.querySelector(':scope > .nbb-ez-scale-box');
237
- if (box) {
238
- box.style.removeProperty('transform');
239
- box.style.removeProperty('width');
240
- box.style.removeProperty('height');
211
+ if (isFilled(ph)) {
212
+ wrap.classList.remove('is-empty');
213
+ clearUnusedShell(wrap, ph);
214
+ markFilledOnce(ph);
241
215
  }
242
- ph.style.removeProperty('height');
243
- }
244
-
245
- function fitWideAd(ph) {
246
- try {
247
- if (!ph?.isConnected) return;
248
- const wrap = ph.closest?.(`.${WRAP_CLASS}`);
249
- if (!wrap) return;
250
- if (!isFilled(ph)) { clearAdScale(ph); return; }
251
- const size = getAdIntrinsicSize(ph);
252
- if (!size?.w || !size?.h) return;
253
- const wrapWidth = Math.max(0, Math.floor(wrap.clientWidth || wrap.getBoundingClientRect().width || 0));
254
- if (!wrapWidth) return;
255
- if (size.w <= wrapWidth + 2) { clearAdScale(ph); return; }
256
- const scale = Math.max(0.1, Math.min(1, wrapWidth / size.w));
257
- const scaledH = Math.ceil(size.h * scale);
258
- const box = ensureScaleBox(ph);
259
- if (!box) return;
260
- wrap.classList.add('is-scaled');
261
- box.style.width = `${size.w}px`;
262
- box.style.height = `${size.h}px`;
263
- box.style.transform = `scale(${scale})`;
264
- ph.style.height = `${scaledH}px`;
265
- wrap.style.height = `${scaledH}px`;
266
- } catch (_) {}
267
- }
268
-
269
- function uncollapseIfFilled(ph) {
270
- try {
271
- const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
272
- if (!wrap) return;
273
- if (isFilled(ph)) {
274
- wrap.classList.remove('is-empty');
275
- markFilledOnce(ph);
276
- fitWideAd(ph);
277
- }
278
- } catch (_) {}
279
- }
280
-
281
- function unwatchPlaceholderFill(phOrId) {
282
- const id = typeof phOrId === 'number' ? phOrId : parseInt(phOrId?.getAttribute?.('data-ezoic-id'), 10);
283
- if (!Number.isFinite(id)) return;
284
- const obs = S.fillObs.get(id);
285
- if (obs) { try { obs.disconnect(); } catch (_) {} S.fillObs.delete(id); }
286
- }
287
-
288
- function watchPlaceholderFill(ph) {
289
- const id = parseInt(ph?.getAttribute?.('data-ezoic-id'), 10);
290
- if (!ph || !Number.isFinite(id) || S.fillObs.has(id)) { try { if (ph) uncollapseIfFilled(ph); } catch (_) {} return; }
291
- const obs = new MutationObserver(() => { uncollapseIfFilled(ph); try { fitWideAd(ph); } catch (_) {} });
292
- try { obs.observe(ph, { childList: true, subtree: true, attributes: true }); } catch (_) { return; }
293
- S.fillObs.set(id, obs);
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);
294
229
  uncollapseIfFilled(ph);
295
- try { fitWideAd(ph); } catch (_) {}
296
- }
230
+ } catch (_) {}
231
+ }
297
232
 
298
233
  function mutate(fn) {
299
234
  S.mutGuard++;
@@ -475,7 +410,6 @@
475
410
 
476
411
  document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(wrap => {
477
412
  try {
478
- if (isProtectedFromDrop(wrap)) return;
479
413
  const rect = wrap.getBoundingClientRect();
480
414
  if (rect.bottom > threshold) return;
481
415
  if (!isFilled(wrap)) {
@@ -494,16 +428,17 @@
494
428
  const oldKey = best.getAttribute(A_ANCHOR);
495
429
  // Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
496
430
  // parasite si le nœud était encore dans la zone IO_MARGIN.
497
- try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) { S.io?.unobserve(ph); clearAdScale(ph); unwatchPlaceholderFill(ph); } } catch (_) {}
498
- clearEmptyChecks(id);
431
+ try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
499
432
  mutate(() => {
500
433
  best.setAttribute(A_ANCHOR, newKey);
501
434
  best.setAttribute(A_CREATED, String(ts()));
502
435
  best.setAttribute(A_SHOWN, '0');
503
- best.setAttribute(A_FILLED, '0');
504
436
  best.classList.remove('is-empty');
437
+ best.classList.remove('is-unused-shell');
438
+ best.removeAttribute(A_FILLED);
439
+ best.removeAttribute(A_LAST_H);
505
440
  const ph = best.querySelector(`#${PH_PREFIX}${id}`);
506
- if (ph) { ph.innerHTML = ''; ph.style.removeProperty('height'); }
441
+ if (ph) { ph.innerHTML = ''; ph.style.removeProperty('min-height'); }
507
442
  targetEl.insertAdjacentElement('afterend', best);
508
443
  });
509
444
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
@@ -515,7 +450,6 @@
515
450
  const doDisplay = () => { try { ez.displayMore([id]); } catch (_) {} };
516
451
  try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
517
452
 
518
- try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) { observePh(id); watchPlaceholderFill(ph); } } catch (_) {}
519
453
  return { id, wrap: best };
520
454
  }
521
455
 
@@ -528,7 +462,6 @@
528
462
  w.setAttribute(A_WRAPID, String(id));
529
463
  w.setAttribute(A_CREATED, String(ts()));
530
464
  w.setAttribute(A_SHOWN, '0');
531
- w.setAttribute(A_FILLED, '0');
532
465
  w.style.cssText = 'width:100%;display:block;';
533
466
  const ph = document.createElement('div');
534
467
  ph.id = `${PH_PREFIX}${id}`;
@@ -546,17 +479,24 @@
546
479
  mutate(() => el.insertAdjacentElement('afterend', w));
547
480
  S.mountedIds.add(id);
548
481
  S.wrapByKey.set(key, w);
549
- try { const ph = w.querySelector(`#${PH_PREFIX}${id}`); if (ph) watchPlaceholderFill(ph); } catch (_) {}
482
+ watchPlaceholderFill(id);
550
483
  return w;
551
484
  }
552
485
 
553
486
  function dropWrap(w) {
554
487
  try {
555
- if (isProtectedFromDrop(w)) return;
556
488
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
557
- if (ph instanceof Element) { S.io?.unobserve(ph); clearAdScale(ph); unwatchPlaceholderFill(ph); }
558
489
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
559
- 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
+ }
560
500
  const key = w.getAttribute(A_ANCHOR);
561
501
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
562
502
  w.remove();
@@ -664,8 +604,8 @@
664
604
  function observePh(id) {
665
605
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
666
606
  if (ph?.isConnected) {
607
+ watchPlaceholderFill(id);
667
608
  try { getIO()?.observe(ph); } catch (_) {}
668
- try { watchPlaceholderFill(ph); } catch (_) {}
669
609
  }
670
610
  }
671
611
 
@@ -704,29 +644,21 @@
704
644
  try {
705
645
  if (isBlocked()) { clearTimeout(timer); return release(); }
706
646
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
707
- if (!ph?.isConnected || isFilled(ph)) { try { if (ph?.isConnected) fitWideAd(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 (_) {}
708
650
 
709
651
  const t = ts();
710
652
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
711
653
  S.lastShow.set(id, t);
712
654
 
713
- try {
714
- const wrap = ph.closest?.(`.${WRAP_CLASS}`);
715
- wrap?.setAttribute(A_SHOWN, String(t));
716
- wrap?.setAttribute(A_FILLED, '0');
717
- wrap?.classList.remove('is-empty');
718
- if (wrap) wrap.style.removeProperty('height');
719
- ph.style.removeProperty('height');
720
- } catch (_) {}
721
- clearEmptyChecks(id);
655
+ try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
722
656
 
723
657
  window.ezstandalone = window.ezstandalone || {};
724
658
  const ez = window.ezstandalone;
725
659
  const doShow = () => {
726
660
  try { ez.showAds(id); } catch (_) {}
727
661
  scheduleEmptyCheck(id, t);
728
- setTimeout(() => { try { const p = document.getElementById(`${PH_PREFIX}${id}`); if (p) fitWideAd(p); } catch (_) {} }, 500);
729
- setTimeout(() => { try { const p = document.getElementById(`${PH_PREFIX}${id}`); if (p) fitWideAd(p); } catch (_) {} }, 2500);
730
662
  setTimeout(() => { clearTimeout(timer); release(); }, 700);
731
663
  };
732
664
  Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
@@ -735,22 +667,31 @@
735
667
  }
736
668
 
737
669
  function scheduleEmptyCheck(id, showTs) {
738
- clearEmptyChecks(id);
739
670
  const runCheck = () => {
740
671
  try {
741
672
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
742
673
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
743
674
  if (!wrap || !ph?.isConnected) return;
744
675
  if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
745
- if ((parseInt(wrap.getAttribute(A_FILLED) || '0', 10) || 0) > 0) {
676
+
677
+ if (isFilled(ph)) {
746
678
  wrap.classList.remove('is-empty');
747
- if (isFilled(ph)) fitWideAd(ph);
679
+ clearUnusedShell(wrap, ph);
680
+ markFilledOnce(ph);
681
+ return;
682
+ }
683
+
684
+ if (shouldKeepShellAfterUnused(wrap)) {
685
+ applyUnusedShell(wrap, ph);
748
686
  return;
749
687
  }
750
- wrap.classList.toggle('is-empty', !isFilled(ph));
751
- if (isFilled(ph)) fitWideAd(ph);
688
+
689
+ clearUnusedShell(wrap, ph);
690
+ wrap.classList.add('is-empty');
752
691
  } catch (_) {}
753
692
  };
693
+
694
+ clearEmptyChecks(id);
754
695
  [EMPTY_CHECK_MS, EMPTY_CHECK_MS + 5000, EMPTY_CHECK_MS + 15000].forEach(delay => {
755
696
  const t = setTimeout(runCheck, delay);
756
697
  queueEmptyCheck(id, t);
@@ -869,17 +810,6 @@
869
810
  step();
870
811
  }
871
812
 
872
- let fitRaf = 0;
873
- function scheduleRefitAll() {
874
- if (fitRaf) return;
875
- fitRaf = requestAnimationFrame(() => {
876
- fitRaf = 0;
877
- document.querySelectorAll(`[id^="${PH_PREFIX}"]`).forEach(ph => {
878
- try { fitWideAd(ph); } catch (_) {}
879
- });
880
- });
881
- }
882
-
883
813
  // ── Cleanup navigation ─────────────────────────────────────────────────────
884
814
 
885
815
  function cleanup() {
@@ -891,17 +821,14 @@
891
821
  S.cursors = { topics: 0, posts: 0, categories: 0 };
892
822
  S.mountedIds.clear();
893
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);
894
826
  S.wrapByKey.clear();
895
- for (const [, timers] of S.emptyChecks) { for (const t of timers) { try { clearTimeout(t); } catch (_) {} } }
896
- S.emptyChecks.clear();
897
- for (const [, obs] of S.fillObs) { try { obs.disconnect(); } catch (_) {} }
898
- S.fillObs.clear();
899
827
  S.inflight = 0;
900
828
  S.pending = [];
901
829
  S.pendingSet.clear();
902
830
  S.burstActive = false;
903
831
  S.runQueued = false;
904
- if (fitRaf) { try { cancelAnimationFrame(fitRaf); } catch (_) {} fitRaf = 0; }
905
832
  }
906
833
 
907
834
  // ── MutationObserver ───────────────────────────────────────────────────────
@@ -1039,7 +966,6 @@
1039
966
  ensureDomObserver();
1040
967
  bindNodeBB();
1041
968
  bindScroll();
1042
- window.addEventListener('resize', scheduleRefitAll, { passive: true });
1043
969
  blockedUntil = 0;
1044
970
  requestBurst();
1045
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
 
@@ -78,7 +78,7 @@
78
78
  }
79
79
 
80
80
 
81
- /* Si le wrap contient une pub, ne jamais le laisser en mode vide */
81
+ /* Filet de sécurité : si un fill arrive tard alors que .is-empty est resté, ne pas écraser */
82
82
  .nodebb-ezoic-wrap.is-empty:has(iframe, ins, img, video, [data-google-container-id]) {
83
83
  height: auto !important;
84
84
  min-height: 1px !important;
@@ -86,17 +86,20 @@
86
86
  overflow: visible !important;
87
87
  }
88
88
 
89
- /* ── Responsive hardening Ezoic ─────────────────────────────────────────── */
90
- .nodebb-ezoic-wrap {
91
- max-width: 100%;
92
- overflow-x: clip;
93
- 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%;
94
96
  }
95
97
 
98
+ /* Responsive hardening conservatif (sans scale JS) */
99
+ .nodebb-ezoic-wrap,
96
100
  .nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
97
101
  max-width: 100%;
98
102
  }
99
-
100
103
  .nodebb-ezoic-wrap .ezoic-ad,
101
104
  .nodebb-ezoic-wrap span.ezoic-ad {
102
105
  max-width: 100% !important;
@@ -104,30 +107,9 @@
104
107
  box-sizing: border-box !important;
105
108
  }
106
109
 
107
- .nodebb-ezoic-wrap [id^="google_ads_iframe_"][id$="__container__"],
108
- .nodebb-ezoic-wrap div[id$="__container__"] {
109
- max-width: 100% !important;
110
- margin-left: auto !important;
111
- margin-right: auto !important;
112
- overflow: visible !important;
113
- }
114
-
115
- .nodebb-ezoic-wrap iframe {
116
- display: block !important;
117
- margin-left: auto !important;
118
- margin-right: auto !important;
119
- max-width: none !important;
120
- }
121
-
122
- .nodebb-ezoic-wrap.is-scaled {
123
- overflow: hidden !important;
124
- }
125
-
126
- .nodebb-ezoic-wrap .nbb-ez-scale-box {
127
- transform-origin: top center;
128
- will-change: transform;
129
- }
130
-
131
- @supports not (overflow: clip) {
132
- .nodebb-ezoic-wrap { overflow-x: hidden; }
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;
133
115
  }