nodebb-plugin-ezoic-infinite 1.7.83 → 1.7.85

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.83",
3
+ "version": "1.7.85",
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,8 +76,17 @@
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
82
 
80
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;
81
90
  const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
82
91
  const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
83
92
  const MAX_INFLIGHT = 4; // max showAds() simultanés
@@ -127,12 +136,12 @@
127
136
  pending: [], // ids en attente de slot inflight
128
137
  pendingSet: new Set(),
129
138
  wrapByKey: new Map(), // anchorKey → wrap DOM node
139
+ emptyChecks: new Map(), // id -> timeout ids
130
140
  runQueued: false,
131
141
  burstActive: false,
132
142
  burstDeadline: 0,
133
143
  burstCount: 0,
134
144
  lastBurstTs: 0,
135
- emptyChecks: new Map(), // id -> timeout ids[]
136
145
  };
137
146
 
138
147
  let blockedUntil = 0;
@@ -143,47 +152,185 @@
143
152
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
144
153
  const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
145
154
 
155
+ function mutate(fn) {
156
+ S.mutGuard++;
157
+ try { fn(); } finally { S.mutGuard--; }
158
+ }
159
+
146
160
  function clearEmptyChecks(id) {
147
161
  const timers = S.emptyChecks.get(id);
148
162
  if (!timers) return;
149
- for (const t of timers) { try { clearTimeout(t); } catch (_) {} }
163
+ for (const t of timers) clearTimeout(t);
150
164
  S.emptyChecks.delete(id);
151
165
  }
152
166
 
153
167
  function queueEmptyCheck(id, timerId) {
154
- const arr = S.emptyChecks.get(id) || [];
155
- arr.push(timerId);
156
- S.emptyChecks.set(id, arr);
168
+ if (!S.emptyChecks.has(id)) S.emptyChecks.set(id, []);
169
+ S.emptyChecks.get(id).push(timerId);
157
170
  }
158
171
 
159
- function uncollapseIfFilled(ph) {
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) {
160
179
  try {
161
180
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
162
- if (!wrap) return false;
163
- if (!isFilled(ph)) return false;
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) {
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) {
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);
164
279
  wrap.classList.remove('is-empty');
165
- return true;
166
- } catch (_) { return false; }
280
+ wrap.classList.add('is-unused-shell');
281
+ wrap.style.minHeight = `${h}px`;
282
+ if (ph) ph.style.minHeight = `${h}px`;
283
+ } catch (_) {}
167
284
  }
168
-
169
- function watchPlaceholderFill(ph) {
170
- if (!ph || ph.__nbbFillObs) return;
285
+ function clearUnusedShell(wrap, ph) {
171
286
  try {
172
- const obs = new MutationObserver(() => { if (uncollapseIfFilled(ph)) return; });
173
- obs.observe(ph, { childList: true, subtree: true, attributes: true });
174
- ph.__nbbFillObs = obs;
287
+ wrap?.classList?.remove('is-unused-shell');
288
+ wrap?.style?.removeProperty('min-height');
289
+ ph?.style?.removeProperty('min-height');
175
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;
176
321
  uncollapseIfFilled(ph);
177
322
  }
178
-
179
323
  function unwatchPlaceholderFill(ph) {
180
- try { ph?.__nbbFillObs?.disconnect?.(); } catch (_) {}
181
- try { if (ph) delete ph.__nbbFillObs; } catch (_) {}
324
+ try { ph?.__nbbEzFillObs?.disconnect?.(); } catch (_) {}
325
+ try { delete ph.__nbbEzFillObs; } catch (_) {}
182
326
  }
183
-
184
- function mutate(fn) {
185
- S.mutGuard++;
186
- try { fn(); } finally { S.mutGuard--; }
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
+ });
187
334
  }
188
335
 
189
336
  // ── Config ─────────────────────────────────────────────────────────────────
@@ -373,25 +520,27 @@
373
520
 
374
521
  const best = bestEmpty ?? bestFilled;
375
522
  if (!best) return null;
523
+ if (isProtectedFromDrop(best)) return null;
376
524
  const id = parseInt(best.getAttribute(A_WRAPID), 10);
377
525
  if (!Number.isFinite(id)) return null;
378
526
 
379
527
  const oldKey = best.getAttribute(A_ANCHOR);
380
528
  // Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
381
529
  // parasite si le nœud était encore dans la zone IO_MARGIN.
382
- try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
383
- clearEmptyChecks(id);
530
+ try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); clearAdScale(ph); clearUnusedShell(best, ph); } } catch (_) {}
384
531
  mutate(() => {
385
532
  best.setAttribute(A_ANCHOR, newKey);
386
533
  best.setAttribute(A_CREATED, String(ts()));
387
534
  best.setAttribute(A_SHOWN, '0');
388
- best.classList.remove('is-empty');
535
+ best.classList.remove('is-empty', 'is-unused-shell');
389
536
  const ph = best.querySelector(`#${PH_PREFIX}${id}`);
390
- if (ph) { ph.innerHTML = ''; watchPlaceholderFill(ph); }
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);
391
539
  targetEl.insertAdjacentElement('afterend', best);
392
540
  });
393
541
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
394
542
  S.wrapByKey.set(newKey, best);
543
+ try { const ph2 = best.querySelector(`#${PH_PREFIX}${id}`); if (ph2) watchPlaceholderFill(ph2); } catch (_) {}
395
544
 
396
545
  // Délais requis : destroyPlaceholders est asynchrone en interne
397
546
  const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
@@ -428,20 +577,26 @@
428
577
  mutate(() => el.insertAdjacentElement('afterend', w));
429
578
  S.mountedIds.add(id);
430
579
  S.wrapByKey.set(key, w);
431
- try { watchPlaceholderFill(w.querySelector(`#${PH_PREFIX}${id}`)); } catch (_) {}
432
580
  return w;
433
581
  }
434
582
 
435
583
  function dropWrap(w) {
436
584
  try {
585
+ if (isProtectedFromDrop(w)) {
586
+ const keepPh = w.querySelector(`[id^="${PH_PREFIX}"]`);
587
+ if (shouldKeepShellAfterUnused(w)) applyUnusedShell(w, keepPh);
588
+ return false;
589
+ }
437
590
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
438
- if (ph instanceof Element) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); }
591
+ if (ph instanceof Element) { S.io?.unobserve(ph); unwatchPlaceholderFill(ph); clearAdScale(ph); clearUnusedShell(w, ph); }
439
592
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
440
593
  if (Number.isFinite(id)) { S.mountedIds.delete(id); clearEmptyChecks(id); }
441
594
  const key = w.getAttribute(A_ANCHOR);
442
595
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
443
596
  w.remove();
597
+ return true;
444
598
  } catch (_) {}
599
+ return false;
445
600
  }
446
601
 
447
602
  // ── Prune (topics de catégorie uniquement) ────────────────────────────────
@@ -545,7 +700,7 @@
545
700
  function observePh(id) {
546
701
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
547
702
  if (!ph?.isConnected) return;
548
- watchPlaceholderFill(ph);
703
+ try { watchPlaceholderFill(ph); } catch (_) {}
549
704
  try { getIO()?.observe(ph); } catch (_) {}
550
705
  }
551
706
 
@@ -584,21 +739,27 @@
584
739
  try {
585
740
  if (isBlocked()) { clearTimeout(timer); return release(); }
586
741
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
587
- if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
588
- clearEmptyChecks(id);
589
- try { ph.closest?.(`.${WRAP_CLASS}`)?.classList.remove('is-empty'); } catch (_) {}
742
+ if (!ph?.isConnected || isFilled(ph)) { try { if (ph) uncollapseIfFilled(ph); } catch (_) {} clearTimeout(timer); return release(); }
590
743
 
591
744
  const t = ts();
592
745
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
593
746
  S.lastShow.set(id, t);
594
747
 
595
- try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
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 (_) {}
596
755
 
597
756
  window.ezstandalone = window.ezstandalone || {};
598
757
  const ez = window.ezstandalone;
599
758
  const doShow = () => {
600
759
  try { ez.showAds(id); } catch (_) {}
601
760
  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);
602
763
  setTimeout(() => { clearTimeout(timer); release(); }, 700);
603
764
  };
604
765
  Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
@@ -608,18 +769,37 @@
608
769
 
609
770
  function scheduleEmptyCheck(id, showTs) {
610
771
  clearEmptyChecks(id);
611
- const delays = [EMPTY_CHECK_MS, EMPTY_CHECK_MS + 5000, EMPTY_CHECK_MS + 15000];
772
+
612
773
  const runCheck = () => {
613
774
  try {
614
775
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
615
776
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
616
777
  if (!wrap || !ph?.isConnected) return;
617
778
  if (parseInt(wrap.getAttribute(A_SHOWN) || '0', 10) > showTs) return;
618
- if (uncollapseIfFilled(ph)) 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);
619
796
  wrap.classList.add('is-empty');
620
797
  } catch (_) {}
621
798
  };
622
- for (const d of delays) queueEmptyCheck(id, setTimeout(runCheck, d));
799
+
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));
623
803
  }
624
804
 
625
805
  // ── Patch Ezoic showAds ────────────────────────────────────────────────────
@@ -746,11 +926,11 @@
746
926
  S.mountedIds.clear();
747
927
  S.lastShow.clear();
748
928
  S.wrapByKey.clear();
929
+ for (const timers of S.emptyChecks.values()) for (const t of timers) clearTimeout(t);
930
+ S.emptyChecks.clear();
749
931
  S.inflight = 0;
750
932
  S.pending = [];
751
933
  S.pendingSet.clear();
752
- for (const timers of S.emptyChecks.values()) for (const t of timers) { try { clearTimeout(t); } catch (_) {} }
753
- S.emptyChecks.clear();
754
934
  S.burstActive = false;
755
935
  S.runQueued = false;
756
936
  }
@@ -879,6 +1059,10 @@
879
1059
  }, { passive: true });
880
1060
  }
881
1061
 
1062
+ function bindResize() {
1063
+ window.addEventListener('resize', scheduleRefitAll, { passive: true });
1064
+ }
1065
+
882
1066
  // ── Boot ───────────────────────────────────────────────────────────────────
883
1067
 
884
1068
  S.pageKey = pageKey();
@@ -890,6 +1074,7 @@
890
1074
  ensureDomObserver();
891
1075
  bindNodeBB();
892
1076
  bindScroll();
1077
+ bindResize();
893
1078
  blockedUntil = 0;
894
1079
  requestBurst();
895
1080
 
package/public/style.css CHANGED
@@ -77,11 +77,59 @@
77
77
  padding: 0 !important;
78
78
  }
79
79
 
80
-
81
- /* Filet de sécurité : si Ezoic a rempli le wrap, annuler le collapse même si .is-empty est resté */
80
+ /* ── Hardening async fill + responsive creatives ───────────────────────── */
82
81
  .nodebb-ezoic-wrap.is-empty:has(iframe, ins, img, video, [data-google-container-id]) {
83
82
  height: auto !important;
84
83
  min-height: 1px !important;
85
84
  max-height: none !important;
86
85
  overflow: visible !important;
87
86
  }
87
+
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%;
135
+ }