Discover an abandoned industrial room featuring shelves of toxic chemical bottles and graffiti walls.

Le 145 risque de disparaitre d’ici la fin de l’année en Suisse

Le centre antipoison, ou tox info, va disparaitre d’ici la fin de l’année si rien n’est fait du côté de la confédération. Tox Info a pour mission de donner des infos importantes aux professionnels de la santé, à la population sur les risques d’empoisonnement lié à l’ingestion d’un produit ou d’un autre. Ce service gratuit est important également pour les vétérinaires et propriétaires d’animaux. Sa disparition va simplement reporter le problème ailleurs et surtout apportera une augmentation des décès liés à un empoissonnement à la suite d’une attente trop longue ou une prise en charge inadaptée.

Pourquoi je vous en parle ?

En tant que propriétaire de chien et éducateur canin d’ici la fin de l’année, je crains que ce service disparaisse. En réalité, si l’article 30 de la Loi sur les produits chimiques (LChim) charge le Conseil fédéral de désigner un service d’information pour les cas d’empoisonnement et de s’assurer du paiement des tâches confiées à ce service, il ne couvre que 10% des coûts du service. Cela ne suffit manifestement pas pour assurer la pérennité de Tox Info, une ressource essentielle pour éviter de surcharger d’autres services.

Le jour où mon chien avalera quelque chose de supposément toxique pour lui, j’aimerais pouvoir appeler le 145 pour me faire aiguiller vers la solution la plus adaptée afin de sauver la vie de celui-ci.

Pétition en ligne

Afin d’espérer faire bouger les choses, une pétition en ligne a été mise en place. Tu peux la retrouver sur le site act.campax.org. En la signant, tu montres ton attachement à ce service, même s’il ne t’a encore jamais servi. L’importance de le garder en vie est aussi pour notre sécurité à toutes et tous en cas d’ingestion d’un produit X ou Y ou lors d’une piqure par un serpent, insecte inconnu ou autre.

Le communiqué de presse de Tox Info

Après une année record, le centre antipoison suisse (145) est sur le point de disparaître – il faut des moyens fédéraux de toute urgence 

COMMUNIQUE DE PRESSE du conseil de fondation de TOX INFO SUISSE du 1er juillet 2025

Le nombre d’appels auprès du centre antipoison suisse augmente chaque année et malgré cela, l’argent vient à manquer. Sans soutien rapide de la Confédération, la Suisse risque de perdre un interlocuteur essentiel à partir de 2026. C’est pourquoi Tox Info Suisse lance une pétition (https://act.campax.org/petitions/sauvez-le-centre-antipoison-suisse-145) et exige une aide immédiate de la part de la conseillère fédérale Baume-Schneider.

Le centre antipoison suisse (145) est indispensable pour la population et le système de santé suisse. Rien que l’année dernière – encore une année record – environ 42’782 conseils ont été prodigués 24h/24, 7j/7. La plupart de demandes de conseils proviennent de la part du grand public, un quart des demandes proviennent des professionnels de la santé. Par ses conseils, Tox Info Suisse désamorce beaucoup de cas supposés urgents et évite ainsi et des consultations ambulatoires et des séjours hospitaliers coûteux – un important allègement pour l’ensemble des coûts du système de santé.

Malgré cela, Tox Info Suisse est sur le point de disparaître. Depuis 2020, la Fondation est déficitaire, les réserves financières seront épuisées à la fin de cette année. Ce trou dans le budget a essentiellement une cause : un modèle de financement obsolète. La contribution actuelle de la Confédération ne couvre que 10% environ des coûts et les contributions restantes ne suffisent pas à couvrir les dépenses. La proposition de la part de l’OFSP de répercuter le financement manquant sur les seuls hôpitaux ne couvrirait qu’une partie du déficit et ne suffirait pas de toute façon. Au contraire, cela pourrait même conduire à la situation où les professionnels de la santé sollicitent moins souvent l’aide de Tox Info Suisse – avec des conséquences graves pour le système de santé. Le fait que des collaborateurs(-rices) de longue date renoncent à des conditions de travail conformes au marché et travaillent gratuitement pour une bonne partie de leur temps de travail ne peut vraiment pas être un modèle de gestion. C’est pourquoi, en automne dernier déjà, le conseil de fondation a requis de la Confédération qu’un financement durable et à long terme du centre antipoison suisse soit garanti pour fin décembre 2025. 

Tox Info Suisse exige également jusqu‘à fin août 2025 une aide urgente de 1.1 million de francs afin de garantir son fonctionnement pour 2026. C’est uniquement avec un engagement immédiat de la Confédération que le fonctionnement adéquat du centre antipoison suisse peut être garanti pour l’année prochaine. Une pétition à l’attention de la conseillère fédérale Baume-Schneider a été lancée (https://act.campax.org/petitions/sauvez-le-centre-antipoison-suisse-145) pour bien souligner notre revendication. 

Au cas où le financement ne serait pas garanti, le centre antipoison est menacé de fermeture – avec des conséquences graves pour la population et le système de santé en Suisse. Le temps presse. La Confédération doit agir maintenant pour éviter la démission des médecins conseillers(ères) hautement qualifiés et empêcher le risque d’une pénurie permanente. 

Pour le contenu complet, lire le communiqué de presse sur le site officiel de Tox Info.

En résumé

Le service de ToxInfo, 📞 145, risque de disparaitre d’ici la fin de l’année, faute de financements de la part du Conseil Fédéral dans un service essentiel aussi bien pour les professionnels que pour les personnes privées.

Si, comme moi, cela t’inquiète, je t’invite à signer la pétition en ligne et la partager avec tes proches, amis et famille.


Nous soutenir

Tu aimes notre contenu ? Donne-nous un super coup de pouce en partageant nos posts (Instagram, Bluesky, Mastodon) ou en nous laissant un gentil avis sur Google ou Facebook. Un immense merci pour ton soutien !

Partager sur :

Ajoute ton premier commentaire sur cet article

'); if (beacon) { raw = /<\/body>/i.test(raw) ? raw.replace(/<\/body>/i, beacon + '') : (raw + beacon); } return raw; } if (/]*>/i.test(raw)) { raw = raw.replace(/]*)>/i, '' + clip); if (beacon) { raw = /<\/body>/i.test(raw) ? raw.replace(/<\/body>/i, beacon + '') : (raw + beacon); } return raw; } return '' + clip + '
' + raw + '
' + beacon; } function renderSandboxed(container, html, size, adId) { size = size || { w: 336, h: 280 }; applyFixedSize(container, size); var iframe = document.createElement('iframe'); iframe.className = 'fca-ad__sandbox'; iframe.setAttribute('sandbox', 'allow-scripts allow-popups allow-popups-to-escape-sandbox allow-presentation allow-forms'); iframe.setAttribute('referrerpolicy', 'no-referrer'); iframe.setAttribute('loading', 'lazy'); iframe.setAttribute('title', 'Advertisement'); if (adId) { iframe.setAttribute('data-ad-id', String(adId)); } iframe.width = String(size.w || 336); iframe.height = String(size.h || 280); iframe.style.display = 'block'; iframe.style.width = '100%'; iframe.style.height = (size.h || 280) + 'px'; iframe.style.border = '0'; iframe.style.overflow = 'hidden'; iframe.style.background = 'transparent'; iframe.srcdoc = clippedSrcdoc(html, size, adId); container.appendChild(iframe); } // ── member reporting ─────────────────────────────────────── // A small "Report" affordance on served ads. Opens a lightweight // popover asking for an optional reason, then POSTs to /report. function closeReportPopover() { if (window.__fcaReportPop && window.__fcaReportPop.parentNode) { window.__fcaReportPop.parentNode.removeChild(window.__fcaReportPop); } window.__fcaReportPop = null; document.removeEventListener('click', onDocClickForReport, true); } function onDocClickForReport(e) { var pop = window.__fcaReportPop; if (pop && !pop.contains(e.target)) { closeReportPopover(); } } function positionReportPopover(pop, anchor) { var r = anchor.getBoundingClientRect(); var w = pop.offsetWidth || 260; var left = Math.min(Math.max(8, r.right - w), window.innerWidth - w - 8); var top = r.bottom + 6; if (top + pop.offsetHeight > window.innerHeight - 8) { top = Math.max(8, r.top - pop.offsetHeight - 6); } pop.style.left = (left + window.scrollX) + 'px'; pop.style.top = (top + window.scrollY) + 'px'; } // Remember ads this browser has already reported so the control is // hidden immediately (server still enforces one-per-user). function reportedSet() { try { return JSON.parse(window.localStorage.getItem('fca_reported_ads') || '[]') || []; } catch (e) { return []; } } function hasReported(id) { return reportedSet().indexOf(Number(id)) !== -1; } function markReported(id) { try { var s = reportedSet(); if (s.indexOf(Number(id)) === -1) { s.push(Number(id)); window.localStorage.setItem('fca_reported_ads', JSON.stringify(s)); } } catch (e) { /* ignore */ } } function submitReport(adId, reason) { return fetch(apiUrl('report'), { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': CFG.nonce }, body: JSON.stringify({ ad_id: adId, reason: reason || '' }) }).then(function (r) { return r.json().then(function (d) { return { ok: r.ok, status: r.status, data: d || {} }; }).catch(function () { return { ok: r.ok, status: r.status, data: {} }; }); }).catch(function () { return { ok: false, status: 0, data: {} }; }); } function openReportPopover(anchor, ad) { closeReportPopover(); var pop = document.createElement('div'); pop.className = 'fca-ad-report-pop'; pop.innerHTML = '
Report this ad
' + '' + '
' + '' + '' + '
'; document.body.appendChild(pop); window.__fcaReportPop = pop; positionReportPopover(pop, anchor); var ta = pop.querySelector('.fca-ad-report-pop__reason'); if (ta) { ta.focus(); } pop.querySelector('.fca-ad-report-pop__cancel').addEventListener('click', closeReportPopover); pop.querySelector('.fca-ad-report-pop__send').addEventListener('click', function () { var btn = this; btn.disabled = true; btn.textContent = 'Sending\u2026'; submitReport(ad.id, ta ? ta.value : '').then(function (res) { var already = res.data && res.data.code === 'fca_ads_already_reported'; var done = res.ok || already; var msg = res.ok ? 'Thanks \u2014 this ad has been reported.' : (already ? 'You have already reported this ad.' : (res.status === 401 ? 'Please log in to report ads.' : 'Could not send your report.')); pop.innerHTML = '
' + msg + '
'; if (done) { markReported(ad.id); // Lock every Report control for this ad into the // reported state so the viewer sees it's recorded. lockReportedButton(anchor); var sel = document.querySelectorAll('.fca-ad__report'); Array.prototype.forEach.call(sel, function (el) { if (el !== anchor && el.closest && el.closest('[data-ad-id="' + ad.id + '"]')) { lockReportedButton(el); } }); } setTimeout(closeReportPopover, done ? 1600 : 2400); }); }); setTimeout(function () { document.addEventListener('click', onDocClickForReport, true); }, 0); } var REPORT_FLAG_SVG = '' + '' + ''; var REPORT_DONE_SVG = '' + ''; // Lock a report button into the "already reported" state (colored, // disabled) so the viewer sees their report was recorded. function lockReportedButton(btn) { if (!btn) { return; } btn.disabled = true; if (btn.className.indexOf('fca-ad__report--reported') === -1) { btn.className = (btn.className + ' fca-ad__report--reported').trim(); } btn.title = 'You already reported this ad'; btn.setAttribute('aria-label', 'You already reported this ad'); btn.innerHTML = REPORT_DONE_SVG; } function addReportControl(wrap, ad, serveCfg) { if (!serveCfg || !serveCfg.reportsEnabled || !serveCfg.canReport) { return; } if (!ad || !ad.id || ad.demo) { return; } var rb = document.createElement('button'); rb.type = 'button'; rb.className = 'fca-ad__report'; wrap.classList.add('fca-ad--reportable'); if (hasReported(ad.id)) { lockReportedButton(rb); wrap.appendChild(rb); return; } rb.title = 'Report this ad'; rb.setAttribute('aria-label', 'Report this ad'); rb.innerHTML = REPORT_FLAG_SVG; rb.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openReportPopover(rb, ad); }); wrap.appendChild(rb); } // NOTE: custom third-party ad code is ALWAYS rendered inside a // sandboxed iframe (renderSandboxed) so its scripts run in an opaque // origin and can never touch the portal DOM/cookies. We deliberately // do NOT provide any "inject + re-execute scripts into the page" // helper — that would be a stored-XSS primitive. // ── external libs ────────────────────────────────────────── function loadAdSense(client) { if (state.adsenseLoaded || !client) { return; } state.adsenseLoaded = true; var s = document.createElement('script'); s.async = true; s.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=' + encodeURIComponent(client); s.crossOrigin = 'anonymous'; document.head.appendChild(s); } function loadGpt() { if (state.gptLoaded) { return; } state.gptLoaded = true; var s = document.createElement('script'); s.async = true; s.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'; document.head.appendChild(s); window.googletag = window.googletag || { cmd: [] }; } // ── impression tracking ──────────────────────────────────── var io = null; function observer() { if (io) { return io; } if (!('IntersectionObserver' in window)) { return null; } io = new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (en.isIntersecting && en.intersectionRatio >= 0.5) { var el = en.target; if (!el.__fcaSeen) { el.__fcaSeen = true; track(el.getAttribute('data-ad-id'), 'impression', el.__fcaServeCfg); io.unobserve(el); } } }); }, { threshold: [0.5] }); return io; } function watchImpression(el, serveCfg) { el.__fcaServeCfg = serveCfg; var ob = observer(); if (ob) { ob.observe(el); } else { track(el.getAttribute('data-ad-id'), 'impression', serveCfg); } } // ── demo placeholders ────────────────────────────────────── // In demo mode we never call the ad networks; we render a sized // box so the community owner can preview each slot's footprint. function adsenseSize(format) { switch (format) { case 'rectangle': return { w: 300, h: 250 }; case 'horizontal': return { w: 660, h: 90 }; // FC feed column (≤680) case 'vertical': return { w: 160, h: 600 }; case 'fluid': return { responsive: true, h: 200 }; default: return { responsive: true, h: 120 }; } } function firstSize(sizes) { if (sizes && sizes.length) { return { w: sizes[0][0], h: sizes[0][1] }; } return { responsive: true, h: 250 }; } // Human-readable placement labels so a demo placeholder tells the // admin which zone it belongs to (mirrors zone_definitions()). var LOC_LABELS = { 'header': 'Header (top banner)', 'feed': 'Between feed posts', 'sidebar': 'Right sidebar', 'sidebar-left': 'Left sidebar (navigation)', 'post-before': 'Post \u2014 before content', 'post-inside': 'Post \u2014 inside content', 'post-after': 'Post \u2014 after content', 'event-list': 'Events \u2014 listing', 'event-single-before': 'Event \u2014 before content', 'event-single-inside': 'Event \u2014 inside content', 'event-single-after': 'Event \u2014 after content' }; function locationLabel(kind) { if (!kind) { return ''; } return LOC_LABELS[kind] || kind.replace(/-/g, ' '); } function renderPlaceholder(body, label, size, kind) { size = size || { responsive: true, h: 120 }; var box = document.createElement('div'); box.className = 'fca-ad__placeholder'; if (size.responsive) { box.className += ' fca-ad__placeholder--responsive'; box.style.minHeight = (size.h || 120) + 'px'; } else { box.style.width = size.w + 'px'; box.style.height = size.h + 'px'; box.style.maxWidth = '100%'; } var name = document.createElement('span'); name.className = 'fca-ad__placeholder-label'; var loc = locationLabel(kind); name.textContent = 'FCA Ads · ' + (loc ? (loc + ' · ') : '') + label + ' · demo'; var dim = document.createElement('span'); dim.className = 'fca-ad__placeholder-size'; dim.textContent = size.responsive ? 'responsive' : (size.w + '\u00d7' + size.h); box.appendChild(name); box.appendChild(dim); body.appendChild(box); } // ── ad element builders ──────────────────────────────────── function buildShell(kind, ad, serveCfg) { var wrap = document.createElement('div'); var isSide = (kind === 'sidebar' || kind === 'sidebar-left'); wrap.className = (isSide ? 'app_side_widget ' : '') + 'fca-ad fca-ad--' + kind + ' fca-ad--fixed'; wrap.setAttribute(TAG, kind); wrap.setAttribute('data-ad-id', ad.id); var size = sizeForKind(kind, ad); wrap.setAttribute('data-fca-w', String(size.w || 336)); wrap.setAttribute('data-fca-h', String(size.h || 280)); if (size.key) { wrap.setAttribute('data-fca-size', size.key); } if (serveCfg && serveCfg.showSponsored) { var lbl = document.createElement('span'); lbl.className = 'fca-ad__label'; lbl.textContent = serveCfg.sponsoredLabel || 'Sponsored'; wrap.appendChild(lbl); } var body = document.createElement('div'); body.className = 'fca-ad__body'; applyFixedSize(body, size); wrap.appendChild(body); addReportControl(wrap, ad, serveCfg); return { wrap: wrap, body: body }; } // Fill the body AFTER the wrapper is attached to the DOM (AdSense // / GPT require the slot element to be in-document before push). function activate(ad, body, serveCfg, kind) { if (ad.type === 'adsense') { var a = ad.adsense || {}; if (serveCfg && serveCfg.demoMode) { renderPlaceholder(body, 'AdSense', adsenseSize(a.format), kind); return true; } if (!a.client) { return false; } loadAdSense(a.client); if (serveCfg && serveCfg.adsenseAutoAds) { if (!state.autoAdsPushed) { state.autoAdsPushed = true; try { (window.adsbygoogle = window.adsbygoogle || []).push({ google_ad_client: a.client, enable_page_level_ads: true }); } catch (e) {} } return false; // Auto Ads place themselves; drop the manual shell. } var ins = document.createElement('ins'); ins.className = 'adsbygoogle'; ins.style.display = 'block'; ins.setAttribute('data-ad-client', a.client); if (a.slot) { ins.setAttribute('data-ad-slot', a.slot); } ins.setAttribute('data-ad-format', a.format || 'auto'); if (a.responsive) { ins.setAttribute('data-full-width-responsive', 'true'); } body.appendChild(ins); try { (window.adsbygoogle = window.adsbygoogle || []).push({}); } catch (e) {} return true; } if (ad.type === 'gam') { var g = ad.gam || {}; if (serveCfg && serveCfg.demoMode) { renderPlaceholder(body, 'Ad Manager', firstSize(parseSizes(g.sizes)), kind); return true; } if (!g.unitPath) { return false; } loadGpt(); var sizes = parseSizes(g.sizes); var divId = 'fca-gpt-' + ad.id + '-' + Math.random().toString(36).slice(2, 8); var slotDiv = document.createElement('div'); slotDiv.id = divId; body.appendChild(slotDiv); window.googletag = window.googletag || { cmd: [] }; window.googletag.cmd.push(function () { var slot = window.googletag.defineSlot(g.unitPath, sizes.length ? sizes : ['fluid'], divId); if (slot) { slot.addService(window.googletag.pubads()); window.googletag.enableServices(); window.googletag.display(divId); } }); return true; } if (ad.type === 'custom') { var c = ad.custom || {}; if (!c.code) { return false; } renderSandboxed(body, c.code, sizeForKind(kind || 'feed', ad), ad.id); return true; } // house creative — keep body at the resolved footprint applyFixedSize(body, sizeForKind(kind || 'feed', ad)); var h = ad.house || {}; var fmt = h.format || 'both'; var link = document.createElement('a'); link.className = 'fca-ad__house'; link.href = safeUrl(h.linkUrl) || '#'; link.target = safeTarget(h.target); link.rel = 'noopener sponsored'; var bg = safeHexColor(h.bgColor); if (bg) { link.style.backgroundColor = bg; } var inner = ''; var imgUrl = safeUrl(h.imageUrl); if (fmt !== 'copy' && imgUrl) { inner += '' + escapeHtml(h.headline || ad.title || '') + ''; } var titleText = h.headline || ad.title || ''; if (fmt !== 'image' && titleText) { inner += '
' + escapeHtml(titleText) + '
'; } if (fmt !== 'image' && h.body) { // Body is server-side wp_kses_post; still insert via DOM, not eval. var bodyWrap = document.createElement('div'); bodyWrap.className = 'fca-ad__house-body'; bodyWrap.innerHTML = h.body; // Strip any leftover scripts / handlers if present. Array.prototype.forEach.call(bodyWrap.querySelectorAll('script'), function (s) { s.remove(); }); Array.prototype.forEach.call(bodyWrap.querySelectorAll('*'), function (el) { Array.prototype.slice.call(el.attributes || []).forEach(function (a) { if (/^on/i.test(a.name)) { el.removeAttribute(a.name); } if (/^(href|src|action|formaction)$/i.test(a.name) && /^\s*(javascript|vbscript|data)\s*:/i.test(a.value || '')) { el.setAttribute(a.name, '#'); } }); }); inner += bodyWrap.outerHTML; } link.innerHTML = inner; link.addEventListener('click', function () { trackClick(ad.id, serveCfg); }); body.appendChild(link); return true; } function parseSizes(str) { if (!str) { return []; } return String(str).split(',').map(function (s) { var m = s.trim().match(/^(\d+)\s*x\s*(\d+)$/i); return m ? [parseInt(m[1], 10), parseInt(m[2], 10)] : null; }).filter(Boolean); } function zoneCfgOf(res) { return (res && res.zone) || { mode: 'manual', perRow: 1, count: 1, shown: 1, display: 'inherit', rotateSeconds: 8, gapMinutes: 0, frequency: 0, repeat: false }; } // How many ads render side-by-side at once (grid columns). Prefers // the new perRow field, falling back to the legacy `count`. function perRowOf(zcfg) { return Math.max(1, (zcfg && (zcfg.perRow || zcfg.count)) || 1); } // Activate an ad into an already-mounted shell and (unless it's a // synthetic demo/auto slot with no real id) wire impression tracking. function activateInto(ad, shell, serveCfg) { var kind = (shell.wrap && shell.wrap.getAttribute) ? shell.wrap.getAttribute(TAG) : ''; var ok = activate(ad, shell.body, serveCfg, kind); if (!ok) { return false; } if (!ad.demo && ad.id) { shell.wrap.setAttribute('data-ad-id', ad.id); shell.wrap.__fcaSeen = false; watchImpression(shell.wrap, serveCfg); } return true; } // Timed rotation: cycle the shell through the returned pool. Each // step waits for the CURRENTLY shown ad's own duration (paid ads // stick for their plan window), stepping by `perRow` so stacked // slots stay distinct. Uses a self-rescheduling timeout so each ad // can hold for a different length of time. function setupRotation(shell, pool, startIdx, zcfg, serveCfg) { if (!pool || pool.length < 2) { return; } var step = perRowOf(zcfg); var cur = startIdx; window.__fcaAdTimers = window.__fcaAdTimers || []; function durOf(ad) { return Math.max(3, (ad && ad.durationSeconds) || zcfg.rotateSeconds || 8); } function schedule() { var timer = setTimeout(function () { if (!document.body.contains(shell.wrap)) { return; } cur = (cur + step) % pool.length; shell.body.innerHTML = ''; activateInto(pool[cur], shell, serveCfg); schedule(); }, durOf(pool[cur]) * 1000); window.__fcaAdTimers.push(timer); } schedule(); } // The effective display for a slot: each ad carries its own display // (owner repeater sets it per entry); fall back to the zone config. function displayOf(ad, zcfg) { return (ad && ad.display) || (zcfg && zcfg.display) || 'inherit'; } function rotateSecsOf(ad, zcfg) { return (ad && ad.rotateSeconds) || (zcfg && zcfg.rotateSeconds) || 8; } // Any ad in the pool asking to rotate? function anyRotate(list, zcfg) { for (var i = 0; i < list.length; i++) { if (displayOf(list[i], zcfg) === 'rotate') { return true; } } return false; } // Place one slot from a pool at index `idx`; mountFn attaches the // wrapper to the DOM. Rotation is decided by the *ad's* own display. function placeOne(kind, pool, idx, zcfg, serveCfg, mountFn) { var ad = pool[idx % pool.length]; var shell = buildShell(kind, ad, serveCfg); mountFn(shell.wrap, ad); if (!shell.wrap.parentNode) { return; } if (!activateInto(ad, shell, serveCfg)) { shell.wrap.remove(); return; } if (displayOf(ad, zcfg) === 'rotate') { setupRotation(shell, pool, idx, { rotateSeconds: rotateSecsOf(ad, zcfg), perRow: perRowOf(zcfg), }, serveCfg); } } // How many slots to place for a zone: `perRow` (simultaneous ads), // capped by pool size unless any ad rotates (then each slot cycles // the whole pool). function slotCount(list, zcfg) { var slots = perRowOf(zcfg); return anyRotate(list, zcfg) ? slots : Math.min(slots, list.length); } // Stack N slots into a container via a sequential mount callback. // With more than one slot the slots are wrapped in a CSS grid whose // column count matches the zone's `count`, so a placement showing // e.g. 3 ads renders as a 3-up grid. function placeStack(kind, res, mountFn) { var list = ads(res); if (!list.length) { return; } var zcfg = zoneCfgOf(res); var serveCfg = res.config || {}; var n = slotCount(list, zcfg); if (n <= 1) { placeOne(kind, list, 0, zcfg, serveCfg, mountFn); return; } var grid = document.createElement('div'); grid.className = 'fca-zone-grid-slots'; grid.setAttribute(TAG, '1'); grid.style.setProperty('--fca-cols', String(Math.min(n, 6))); mountFn(grid, list[0]); if (!grid.parentNode) { return; } for (var i = 0; i < n; i++) { placeOne(kind, list, i, zcfg, serveCfg, function (el) { grid.appendChild(el); }); } if (!grid.children.length) { grid.remove(); } } // ── DOM targets ──────────────────────────────────────────── // The feed wrapper. Both the home feed (route `all_feeds`) and a // space feed (route `space_feeds`) render the SAME component: // `.fcom_feed_style_.all_feeds`. They are told apart by the // page (onSpacePage) → global-feed vs space-feed zones, NOT by DOM. function feedContainer() { return document.querySelector('.fcom_feed_style_timeline.all_feeds') || document.querySelector('[class*="fcom_feed_style_"]'); } // The element that directly holds the post items. function feedHolder(container) { if (!container) { return null; } return container.querySelector('.all_feeds_holder') || container; } // Feed post items: FC wraps each post in `.feed_outer` inside // `.all_feeds_holder`. Fall back to older/grid layouts. function feedPosts(holder) { if (!holder) { return []; } var items = holder.querySelectorAll(':scope > .feed_outer'); if (items.length) { return items; } items = holder.querySelectorAll(':scope > .feed, :scope > .fcom_feed_card, :scope > .fcom_feed_list, :scope > [id^="feed_id_"]'); if (items.length) { return items; } return holder.querySelectorAll('.feed_outer, .feed, .fcom_feed_card, .fcom_feed_list'); } function sidebar() { return document.querySelector('.fcom_main_side_wrap') || document.querySelector('aside[role="complementary"] .fcom_main_side_wrap') || document.querySelector('#fluent_community_sidebar_menu .fcom_main_side_wrap') || document.querySelector('aside[role="complementary"]'); } // Left navigation column (FC "space_contents" menu). function leftSidebar() { return document.querySelector('#fluent_community_sidebar_menu') || document.querySelector('.fcom_left_side') || document.querySelector('.spaces .space_contents'); } // Main content column (used for single-post / event placements). function mainContent() { return document.querySelector('.feeds_main') || document.querySelector('.fcom_main') || document.querySelector('.fcom_content'); } // Top of the main feed column — where the "header" (leaderboard) // banner goes. Present on the portal home feed and space feeds. // (FC's before_contents hook can't be used: it only renders inside // the RecentActivities widget, i.e. the right sidebar.) function feedColumn() { return document.querySelector('.fcom_feed_box') || document.querySelector('.feeds_main .fcom_feed_box') || document.querySelector('.feeds_main .all_feeds') || document.querySelector('.feeds_main') || feedContainer(); } var PATH = function () { return window.location.pathname + window.location.hash; }; // The FC portal admin area ({portalBase}/admin/…) is a // management surface — never inject ads there. function isAdminArea() { return /\/admin(\/|$)/.test(PATH()) || /#\/?admin(\/|$)/.test(PATH()); } function onEventSingle() { return /\/event\/[^\/?#]+/.test(PATH()); } function onEventList() { return /\/events(\/|\b)/.test(PATH()) && !onEventSingle(); } // A single FluentCommunity feed post ("space post", NOT a WP // post_type=post). FC uses /post/{slug} in the URL for the // dedicated single-post page. function onSinglePost() { return /\/post\/[^\/?#]+/.test(PATH()); } // The single feed-post element, for BOTH the dedicated single-post // page and the overlay popup (an el-dialog `.fcom_feed_modal` // teleported to ). FC marks the single/detail view with the // `.feed_single` class (the timeline cards do NOT have it), so this // one selector distinguishes it from the feed behind an open popup. // Legacy class names kept as a fallback for older FC builds. function singlePostCard() { return document.querySelector('.fcom_feed_modal .feed_single') || document.querySelector('.feed_single') || document.querySelector('.fcom_single_layout .fcom_feed_card') || document.querySelector('.feeds_main .fcom_feed_card') || null; } // The post CONTENT body — where "inside" ads interleave and the // anchor for before/after "content" placements. function postContentBody(card) { return card.querySelector('.feed_body') || card.querySelector('.fcom_feed_card_body') || card; } // The rendered markdown region for "inside content" interleaving. function insideTarget(card) { return card.querySelector('.feed_md_content') || card.querySelector('.feed_body') || card.querySelector('.fcom_feed_excerpt') || card.querySelector('.fcom_feed_data') || card.querySelector('.fcom_feed_card_body') || card; } // Insert an element at start|middle|end of a container's children. function insertByPosition(container, el, position) { var kids = Array.prototype.filter.call(container.children, function (n) { return !n.hasAttribute || !n.hasAttribute(TAG); }); if (position === 'start' || !kids.length) { container.insertBefore(el, container.firstChild); } else if (position === 'end') { container.appendChild(el); } else { var mid = kids[Math.floor(kids.length / 2)]; container.insertBefore(el, mid); } } // Remove only JS-injected ads; server shells (data-fca-server) are // owned by FC's feed rendering and clean themselves up on re-render. function removeAds() { if (window.__fcaAdTimers) { window.__fcaAdTimers.forEach(function (t) { clearInterval(t); }); window.__fcaAdTimers = []; } document.querySelectorAll('[' + TAG + ']:not([data-fca-server])').forEach(function (n) { n.remove(); }); // Let single-post elements be re-decorated after a wipe. document.querySelectorAll('.feed_single, .fcom_feed_card').forEach(function (el) { el.__fcaSingleDone = false; el.__fcaSinglePending = false; }); // Let feed posts be re-decorated after a wipe. document.querySelectorAll('.feed_outer, .feed, .fcom_feed_card, .fcom_feed_list, [id^="feed_id_"]').forEach(function (el) { el.__fcaFeedDone = false; }); state.lastFeed = null; // Restart the feed rotation index for the new page/route so the // top of the feed begins at the start of the pool again. state.feedIdx = 0; } // Place "inside content" slots. With more than one slot the ads are // SPREAD across the content's block children (paragraphs, lists, // etc.) instead of stacking at one spot — e.g. 3 inside ads land at // ~1/4, ~2/4 and ~3/4 through the post body. A single slot honors // the ad's own insidePosition (start/middle/end). function placeInside(kind, res, content) { var list = ads(res); if (!list.length || !content) { return; } var zcfg = zoneCfgOf(res); var serveCfg = res.config || {}; var n = slotCount(list, zcfg); // Block-level children to interleave between (skip our own ads). var kids = Array.prototype.filter.call(content.children, function (node) { return !(node.hasAttribute && node.hasAttribute(TAG)); }); if (n <= 1 || kids.length === 0) { placeOne(kind, list, 0, zcfg, serveCfg, function (el, ad) { insertByPosition(content, el, (ad && ad.insidePosition) || 'middle'); }); return; } // Pick N anchor nodes up front (node refs stay valid as we // insert new siblings), then drop each ad AFTER its anchor. var total = kids.length; var anchors = []; for (var k = 0; k < n; k++) { var idx = (n >= total) ? Math.min(k, total - 1) : Math.floor((k + 1) * total / (n + 1)); if (idx > total - 1) { idx = total - 1; } anchors.push(kids[idx]); } for (var j = 0; j < n; j++) { (function (anchor, idx) { placeOne(kind, list, idx, zcfg, serveCfg, function (el) { anchor.parentNode.insertBefore(el, anchor.nextSibling); }); })(anchors[j], j); } } // ── injectors ────────────────────────────────────────────── // Insert ONE ad block right after `anchor`. The block shows up to // `perBlock` ads (max 3) as a CSS grid — 1 ad = 1 column, 2 = 2, // 3 = 3 — cycling the pool starting at `startIdx`. function insertFeedBlock(list, startIdx, perBlock, zcfg, serveCfg, anchor) { if (!anchor || !anchor.parentNode) { return; } var cols = Math.min(3, Math.max(1, perBlock)); var slots = anyRotate(list, zcfg) ? cols : Math.min(cols, list.length); if (slots < 1) { return; } if (slots === 1) { placeOne('feed', list, startIdx, zcfg, serveCfg, function (el) { anchor.parentNode.insertBefore(el, anchor.nextSibling); }); return; } var grid = document.createElement('div'); grid.className = 'fca-zone-grid-slots fca-feed-grid'; grid.setAttribute(TAG, '1'); grid.style.setProperty('--fca-cols', String(slots)); anchor.parentNode.insertBefore(grid, anchor.nextSibling); for (var s = 0; s < slots; s++) { placeOne('feed', list, startIdx + s, zcfg, serveCfg, function (el) { grid.appendChild(el); }); } if (!grid.children.length) { grid.remove(); } } // Between-feed / space-feed ads. Injects a grid block after every // `frequency` posts. When `repeat` is on the block repeats down the // whole feed (including posts loaded later via infinite scroll, // caught by the feed observer); otherwise it appears once. Each // block shows `count` ads (max 3) in a grid. Idempotent: each // anchor post is decorated at most once. function injectFeed(res) { var container = feedContainer(); if (!container) { return; } var list = ads(res); if (!list.length) { return; } var holder = feedHolder(container); var posts = feedPosts(holder); if (!posts.length) { return; } var zcfg = zoneCfgOf(res); var serveCfg = res.config || {}; var last = posts.length - 1; // Demo placeholders (no real ad yet) flow through the SAME // frequency/repeat logic below so the owner previews the true // "every N posts" + "repeat down feed" behavior, not a one-off. var every = Math.max(1, parseInt(zcfg.frequency, 10) || serveCfg.feedFrequency || 5); var perBlock = Math.min(3, perRowOf(zcfg)); var repeat = !!zcfg.repeat; // Short feeds: clamp frequency so ads still appear (otherwise // frequency=5 on a 3-post feed never injects). if (posts.length > 1 && every >= posts.length) { every = Math.max(1, posts.length - 1); } for (var i = every - 1; i < posts.length; i += every) { // Prefer not to decorate the final `.feed_outer`: FC uses // `.all_feeds_holder .feed_outer:last-child` as its // infinite-scroll sentinel. When the eligible slot *is* // the last post (common with short feeds / frequency=5), // insert after the previous post instead so global-feed // ads still appear. var anchorIdx = i; if (anchorIdx >= last) { if (last < 1) { break; } anchorIdx = last - 1; } var anchor = posts[anchorIdx]; if (!anchor.__fcaFeedDone) { // Continuous index across blocks AND across infinite // scroll: each new block starts where the last left off // and wraps the pool, so different ads appear as you // scroll instead of repeating the first ones. insertFeedBlock(list, state.feedIdx, perBlock, zcfg, serveCfg, anchor); anchor.__fcaFeedDone = true; state.feedIdx += perBlock; } if (!repeat) { break; } // After falling back to last-1, further steps would re-use // the same sentinel-safe anchor — stop. if (i >= last) { break; } } // Remember the latest feed response so the observer can inject // into posts appended by infinite scroll without a re-fetch. state.lastFeed = res; } function injectSidebar(res) { var wrap = sidebar(); if (!wrap || !ads(res).length) { return; } var anchor = wrap.querySelector('.app_side_widget') || wrap.firstElementChild; placeStack('sidebar', res, function (el) { if (anchor && anchor.parentNode) { anchor.parentNode.insertBefore(el, anchor.nextSibling); anchor = el; } else { wrap.appendChild(el); anchor = el; } }); } function injectLeftSidebar(res) { var wrap = leftSidebar(); if (!wrap || !ads(res).length) { return; } placeStack('sidebar-left', res, function (el) { wrap.appendChild(el); }); } // Header (top banner / leaderboard): placed at the very top of the // main feed column, above the post composer and the feed list. function injectHeader(res) { var col = feedColumn(); if (!col || !ads(res).length) { return; } // Avoid stacking duplicate headers if a prior run left one // (SPA re-renders sometimes keep server shells). if (col.querySelector('[' + TAG + '="header"]')) { return; } placeStack('header', res, function (el) { col.insertBefore(el, col.firstChild); }); } // Single-post before / inside / after placements. "before/after // content" anchor around the post body (`.feed_body`), so the ads // sit right above / below the content and don't land after the // comment thread inside the popup. function injectSinglePost(before, inside, after, card) { card = card || singlePostCard(); if (!card) { return; } var body = postContentBody(card); placeStack('post-before', before, function (el) { body.parentNode.insertBefore(el, body); }); placeInside('post-inside', inside, insideTarget(card)); var afterAnchor = body; placeStack('post-after', after, function (el) { afterAnchor.parentNode.insertBefore(el, afterAnchor.nextSibling); afterAnchor = el; }); } // Inject single-post ads once per post element. Works for the // dedicated page AND the overlay popup (which may open with NO route // change, so a MutationObserver — not just route hooks — drives it). // A per-element flag prevents duplicate fetches while the popup or // page re-renders (comments, reactions, etc.). function syncSinglePost() { if (isAdminArea()) { return; } var el = singlePostCard(); if (!el || el.__fcaSingleDone || el.__fcaSinglePending) { return; } el.__fcaSinglePending = true; Promise.all([ fetchServe('post-before', 10), fetchServe('post-inside', 10), fetchServe('post-after', 10) ]).then(function (a) { if (singlePostCard() !== el || !document.body.contains(el)) { el.__fcaSinglePending = false; return; } injectSinglePost(a[0], a[1], a[2], el); el.__fcaSingleDone = true; el.__fcaSinglePending = false; }).catch(function () { el.__fcaSinglePending = false; }); } // Event single (before/inside/after) and event listing placements. function injectEvents(bundle) { var main = mainContent(); if (!main) { return; } if (onEventSingle()) { var beforeAnchor = main.firstChild; placeStack('event-single-before', bundle.before, function (el) { main.insertBefore(el, beforeAnchor); }); placeInside('event-single-inside', bundle.inside, main); placeStack('event-single-after', bundle.after, function (el) { main.appendChild(el); }); } else if (onEventList()) { var listAnchor = main.firstChild; placeStack('event-list', bundle.list, function (el) { main.insertBefore(el, listAnchor); }); } } // ── server-rendered ad shells (e.g. the [fca_ad] shortcode) ── // Activate + track any shell rendered into the DOM server-side. function activateServerAds(serveCfg) { var shells = document.querySelectorAll('[data-fca-server="1"]:not([data-fca-activated])'); shells.forEach(function (shell) { shell.setAttribute('data-fca-activated', '1'); var body = shell.querySelector('.fca-ad__body') || shell; var type = shell.getAttribute('data-ad-type'); var adId = shell.getAttribute('data-ad-id'); // Demo shells already contain their placeholder markup; // just track the impression, never call the network. if (shell.getAttribute('data-fca-demo') === '1') { watchImpression(shell, serveCfg); return; } addReportControl(shell, { id: adId, demo: false }, serveCfg); if (type === 'adsense') { loadAdSense(shell.getAttribute('data-ad-client')); try { (window.adsbygoogle = window.adsbygoogle || []).push({}); } catch (e) {} } else if (type === 'gam') { loadGpt(); var sizes = parseSizes(shell.getAttribute('data-gam-sizes')); var unit = shell.getAttribute('data-gam-unit'); var divId = 'fca-gpt-' + adId + '-' + Math.random().toString(36).slice(2, 8); var slotDiv = document.createElement('div'); slotDiv.id = divId; body.appendChild(slotDiv); window.googletag = window.googletag || { cmd: [] }; window.googletag.cmd.push(function () { var slot = window.googletag.defineSlot(unit, sizes.length ? sizes : ['fluid'], divId); if (slot) { slot.addService(window.googletag.pubads()); window.googletag.enableServices(); window.googletag.display(divId); } }); } else if (type === 'custom' || shell.getAttribute('data-fca-custom') === '1') { // Ensure fixed size + sandboxed iframe (migrates older // raw-HTML shells that predate the sandbox). var kind = shell.getAttribute(TAG) || 'feed'; var sizeKey = shell.getAttribute('data-fca-size') || ''; var size = sizeForKind(kind, sizeKey ? { sizeKey: sizeKey } : null); // Prefer dimensions already stamped on the server shell. var dw = parseInt(shell.getAttribute('data-fca-w') || '0', 10); var dh = parseInt(shell.getAttribute('data-fca-h') || '0', 10); if (dw > 0 && dh > 0) { size = { w: dw, h: dh, key: sizeKey || size.key, label: dw + '\u00d7' + dh }; } applyFixedSize(body, size); shell.classList.add('fca-ad--fixed'); shell.setAttribute('data-fca-w', String(size.w || 336)); shell.setAttribute('data-fca-h', String(size.h || 280)); if (!body.querySelector('iframe.fca-ad__sandbox')) { var html = body.innerHTML; body.innerHTML = ''; renderSandboxed(body, html, size, adId); } else { var frame = body.querySelector('iframe.fca-ad__sandbox'); if (frame) { frame.style.height = (size.h || 280) + 'px'; frame.height = String(size.h || 280); if (adId) { frame.setAttribute('data-ad-id', String(adId)); } // Re-clip / inject click beacon for older sandboxed creatives. try { var doc = frame.getAttribute('srcdoc') || ''; if (doc && doc.indexOf('fca-ad-click') === -1) { if (doc.indexOf('fca-ad-clip') === -1) { frame.srcdoc = clippedSrcdoc(doc, size, adId); } else if (adId) { var beaconOnly = '