nodebb-plugin-ezoic-infinite 1.7.85 → 1.7.87

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.87",
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,17 +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
82
-
83
- 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
- const MIN_LIVE_AFTER_SHOW_MS = 15_000;
87
- const MIN_LIVE_AFTER_FILL_MS = 25_000;
88
- const KEEP_SHELL_AFTER_UNUSED_MS = 90_000;
89
- const MIN_SHELL_HEIGHT = 120;
79
+
80
+ // anti-race fill async (Ezoic peut remplir bien après showAds)
81
+ const EMPTY_CHECK_PASSES = [20_000, 25_000, 35_000];
82
+
90
83
  const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
91
84
  const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
92
85
  const MAX_INFLIGHT = 4; // max showAds() simultanés
@@ -129,6 +122,8 @@
129
122
  cursors: { topics: 0, posts: 0, categories: 0 },
130
123
  mountedIds: new Set(),
131
124
  lastShow: new Map(),
125
+ emptyChecks: new Map(), // id -> [timerIds] checks is-empty multi-pass
126
+ fillObs: new Map(), // id -> MutationObserver placeholder fill tardif
132
127
  io: null,
133
128
  domObs: null,
134
129
  mutGuard: 0, // >0 : mutations DOM en cours (MutationObserver ignoré)
@@ -136,7 +131,6 @@
136
131
  pending: [], // ids en attente de slot inflight
137
132
  pendingSet: new Set(),
138
133
  wrapByKey: new Map(), // anchorKey → wrap DOM node
139
- emptyChecks: new Map(), // id -> timeout ids
140
134
  runQueued: false,
141
135
  burstActive: false,
142
136
  burstDeadline: 0,
@@ -152,185 +146,51 @@
152
146
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
153
147
  const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
154
148
 
155
- function mutate(fn) {
156
- S.mutGuard++;
157
- try { fn(); } finally { S.mutGuard--; }
158
- }
159
-
160
149
  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);
150
+ const arr = S.emptyChecks.get(id);
151
+ if (arr) {
152
+ for (const t of arr) clearTimeout(t);
153
+ S.emptyChecks.delete(id);
154
+ }
165
155
  }
166
156
 
167
157
  function queueEmptyCheck(id, timerId) {
168
- if (!S.emptyChecks.has(id)) S.emptyChecks.set(id, []);
169
- S.emptyChecks.get(id).push(timerId);
158
+ const arr = S.emptyChecks.get(id) || [];
159
+ arr.push(timerId);
160
+ S.emptyChecks.set(id, arr);
170
161
  }
171
162
 
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) {
163
+ function uncollapseIfFilled(ph) {
186
164
  try {
187
165
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
188
166
  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);
167
+ if (isFilled(ph)) wrap.classList.remove('is-empty');
220
168
  } catch (_) {}
221
169
  }
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) {
238
- const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
239
- 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) {
170
+
171
+ function watchPlaceholderFill(id) {
251
172
  try {
173
+ const ph = document.getElementById(`${PH_PREFIX}${id}`);
252
174
  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);
279
- 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');
175
+ if (S.fillObs.has(id)) return;
176
+ const obs = new MutationObserver(() => uncollapseIfFilled(ph));
177
+ obs.observe(ph, { childList: true, subtree: true, attributes: true });
178
+ S.fillObs.set(id, obs);
179
+ uncollapseIfFilled(ph);
290
180
  } catch (_) {}
291
181
  }
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;
321
- uncollapseIfFilled(ph);
322
- }
323
- function unwatchPlaceholderFill(ph) {
324
- try { ph?.__nbbEzFillObs?.disconnect?.(); } catch (_) {}
325
- try { delete ph.__nbbEzFillObs; } catch (_) {}
182
+
183
+ function unwatchPlaceholderFill(id) {
184
+ const obs = S.fillObs.get(id);
185
+ if (obs) {
186
+ try { obs.disconnect(); } catch (_) {}
187
+ S.fillObs.delete(id);
188
+ }
326
189
  }
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
- });
190
+
191
+ function mutate(fn) {
192
+ S.mutGuard++;
193
+ try { fn(); } finally { S.mutGuard--; }
334
194
  }
335
195
 
336
196
  // ── Config ─────────────────────────────────────────────────────────────────
@@ -520,27 +380,26 @@
520
380
 
521
381
  const best = bestEmpty ?? bestFilled;
522
382
  if (!best) return null;
523
- if (isProtectedFromDrop(best)) return null;
524
383
  const id = parseInt(best.getAttribute(A_WRAPID), 10);
525
384
  if (!Number.isFinite(id)) return null;
526
385
 
527
386
  const oldKey = best.getAttribute(A_ANCHOR);
528
387
  // Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
529
388
  // 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 (_) {}
389
+ try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
390
+ if (Number.isFinite(id)) clearEmptyChecks(id);
531
391
  mutate(() => {
532
392
  best.setAttribute(A_ANCHOR, newKey);
533
393
  best.setAttribute(A_CREATED, String(ts()));
534
394
  best.setAttribute(A_SHOWN, '0');
535
- best.classList.remove('is-empty', 'is-unused-shell');
395
+ best.classList.remove('is-empty');
536
396
  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);
397
+ if (ph) ph.innerHTML = '';
539
398
  targetEl.insertAdjacentElement('afterend', best);
540
399
  });
541
400
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
542
401
  S.wrapByKey.set(newKey, best);
543
- try { const ph2 = best.querySelector(`#${PH_PREFIX}${id}`); if (ph2) watchPlaceholderFill(ph2); } catch (_) {}
402
+ observePh(id);
544
403
 
545
404
  // Délais requis : destroyPlaceholders est asynchrone en interne
546
405
  const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
@@ -582,21 +441,15 @@
582
441
 
583
442
  function dropWrap(w) {
584
443
  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
444
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
591
- if (ph instanceof Element) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); clearAdScale(ph); clearUnusedShell(w, ph); }
445
+ if (ph instanceof Element) S.io?.unobserve(ph);
592
446
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
593
- if (Number.isFinite(id)) { S.mountedIds.delete(id); clearEmptyChecks(id); }
447
+ if (Number.isFinite(id)) { clearEmptyChecks(id); unwatchPlaceholderFill(id); }
448
+ if (Number.isFinite(id)) S.mountedIds.delete(id);
594
449
  const key = w.getAttribute(A_ANCHOR);
595
450
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
596
451
  w.remove();
597
- return true;
598
452
  } catch (_) {}
599
- return false;
600
453
  }
601
454
 
602
455
  // ── Prune (topics de catégorie uniquement) ────────────────────────────────
@@ -699,9 +552,10 @@
699
552
 
700
553
  function observePh(id) {
701
554
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
702
- if (!ph?.isConnected) return;
703
- try { watchPlaceholderFill(ph); } catch (_) {}
704
- try { getIO()?.observe(ph); } catch (_) {}
555
+ if (ph?.isConnected) {
556
+ try { getIO()?.observe(ph); } catch (_) {}
557
+ watchPlaceholderFill(id);
558
+ }
705
559
  }
706
560
 
707
561
  function enqueueShow(id) {
@@ -739,27 +593,22 @@
739
593
  try {
740
594
  if (isBlocked()) { clearTimeout(timer); return release(); }
741
595
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
742
- if (!ph?.isConnected || isFilled(ph)) { try { if (ph) uncollapseIfFilled(ph); } catch (_) {} clearTimeout(timer); return release(); }
596
+ if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
597
+
598
+ clearEmptyChecks(id);
599
+ try { ph.closest?.(`.${WRAP_CLASS}`)?.classList.remove('is-empty'); } catch (_) {}
743
600
 
744
601
  const t = ts();
745
602
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
746
603
  S.lastShow.set(id, t);
747
604
 
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 (_) {}
605
+ try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
755
606
 
756
607
  window.ezstandalone = window.ezstandalone || {};
757
608
  const ez = window.ezstandalone;
758
609
  const doShow = () => {
759
610
  try { ez.showAds(id); } catch (_) {}
760
611
  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
612
  setTimeout(() => { clearTimeout(timer); release(); }, 700);
764
613
  };
765
614
  Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
@@ -776,30 +625,16 @@
776
625
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
777
626
  if (!wrap || !ph?.isConnected) return;
778
627
  if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
779
-
780
- if (isFilled(ph)) {
781
- wrap.classList.remove('is-empty');
782
- clearUnusedShell(wrap, ph);
783
- markFilledOnce(ph);
784
- rememberCurrentAdSize(ph);
785
- fitWideAd(ph);
786
- return;
787
- }
788
-
789
- if (hasEverFilled(wrap) || shouldKeepShellAfterUnused(wrap)) {
790
- applyUnusedShell(wrap, ph);
791
- return;
792
- }
793
-
794
- clearUnusedShell(wrap, ph);
795
- clearAdScale(ph);
796
- wrap.classList.add('is-empty');
628
+ const filled = isFilled(ph);
629
+ if (filled) wrap.classList.remove('is-empty');
630
+ else wrap.classList.add('is-empty');
797
631
  } catch (_) {}
798
632
  };
799
633
 
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));
634
+ for (const delay of EMPTY_CHECK_PASSES) {
635
+ const tid = setTimeout(runCheck, delay);
636
+ queueEmptyCheck(id, tid);
637
+ }
803
638
  }
804
639
 
805
640
  // ── Patch Ezoic showAds ────────────────────────────────────────────────────
@@ -926,11 +761,13 @@
926
761
  S.mountedIds.clear();
927
762
  S.lastShow.clear();
928
763
  S.wrapByKey.clear();
929
- for (const timers of S.emptyChecks.values()) for (const t of timers) clearTimeout(t);
930
- S.emptyChecks.clear();
931
764
  S.inflight = 0;
932
765
  S.pending = [];
933
766
  S.pendingSet.clear();
767
+ S.emptyChecks.forEach(arr => { try { arr.forEach(clearTimeout); } catch (_) {} });
768
+ S.emptyChecks.clear();
769
+ S.fillObs.forEach(obs => { try { obs.disconnect(); } catch (_) {} });
770
+ S.fillObs.clear();
934
771
  S.burstActive = false;
935
772
  S.runQueued = false;
936
773
  }
@@ -1059,10 +896,6 @@
1059
896
  }, { passive: true });
1060
897
  }
1061
898
 
1062
- function bindResize() {
1063
- window.addEventListener('resize', scheduleRefitAll, { passive: true });
1064
- }
1065
-
1066
899
  // ── Boot ───────────────────────────────────────────────────────────────────
1067
900
 
1068
901
  S.pageKey = pageKey();
@@ -1074,7 +907,6 @@
1074
907
  ensureDomObserver();
1075
908
  bindNodeBB();
1076
909
  bindScroll();
1077
- bindResize();
1078
910
  blockedUntil = 0;
1079
911
  requestBurst();
1080
912
 
package/public/style.css CHANGED
@@ -71,13 +71,7 @@
71
71
  overflow: hidden !important;
72
72
  }
73
73
 
74
- /* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
75
- .ezoic-ad {
76
- margin: 0 !important;
77
- padding: 0 !important;
78
- }
79
-
80
- /* ── Hardening async fill + responsive creatives ───────────────────────── */
74
+ /* Filet de sécurité : si un fill est présent malgré is-empty, on ne collapse pas */
81
75
  .nodebb-ezoic-wrap.is-empty:has(iframe, ins, img, video, [data-google-container-id]) {
82
76
  height: auto !important;
83
77
  min-height: 1px !important;
@@ -85,51 +79,8 @@
85
79
  overflow: visible !important;
86
80
  }
87
81
 
88
- .nodebb-ezoic-wrap {
89
- max-width: 100%;
90
- overflow-x: clip;
91
- overflow-y: visible;
92
- }
93
-
94
- .nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
95
- max-width: 100%;
96
- }
97
-
98
- .nodebb-ezoic-wrap .ezoic-ad,
99
- .nodebb-ezoic-wrap span.ezoic-ad {
100
- max-width: 100% !important;
101
- min-width: 0 !important;
102
- box-sizing: border-box !important;
103
- }
104
-
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%;
82
+ /* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
83
+ .ezoic-ad {
84
+ margin: 0 !important;
85
+ padding: 0 !important;
135
86
  }