Quand ouvrent les préinscriptions aux cours

Une bonne nouvelle pour tout le monde, les préinscriptions pour mes cours vont ouvrir dès que mon brevet sera en poche. Cela, tu le sais déjà, si tu me suis depuis un petit moment. Si ce n’est pas le cas, voici de quoi faire un petit tour de ce qui t’attend prochainement.

Petit résumé de ma formation d’éducateur canin

C’est après avoir adopté ma chienne Mia il y a deux ans que l’idée d’être éducateur canin m’est venue. Le premier signe a été le suivi de la formation initiale pour aller promener les chiens de la SVPA. Durant celle-ci, j’ai fait la connaissance d’Annik, une éducatrice reconnue dans le canton de Vaud qui donnait la formation. Sa passion et les divers exemples donnés durant celle-ci m’ont parlé. Non pas comme futur promeneur de chien, mais comme futur propriétaire.

La chance a fait qu’une connaissance de ma meilleure amie cherchait à placer sa chienne, car elle n’avait plus le temps de s’en occuper. C’est comme ça que je rencontrai pour la première fois Mia, un adorable Border Collie âgé de seulement trois ans. La promenade faite durant la rencontre m’a semblé bien trop courte. La suite a montré que Mia me faisait confiance. C’est ainsi qu’après 3 jours de tests, quelques jours de réflexions et la mise en place de tout ce qui devait être fait pour un accueil comme il se doit, cette chienne est venue s’installer chez moi.

De fil en aiguille, les jours passants, les mois et maintenant les années, m’ont appris énormément de choses, aussi bien pour moi que pour l’éducation de Mia. Beaucoup de points ont été travaillés pour qu’elle soit bien dans ses pattes et n’ait plus des peurs irrationnelles ou des envies de piquer les bâtons à tout le monde.

C’est après être passé pour faire un bilan comportemental auprès d’Annik, et voir si ce que j’avais mis en place était ok, que l’envie d’aider les personnes dans la même situation que moi s’est concrétisée. Depuis, l’idée de devenir éducateur canin a fait son petit bout de chemin.

C’est en 2023 que j’ai cherché les diverses formations existantes. Une étant à Genève, avec des modules au choix, l’autre étant moins chère, mais peu documentée, et la troisième combinant un peu les deux autres. C’est la 3e que j’ai choisie de suivre, à savoir le brevet MEC (maitre en éducation canine) dispensé par la FRC (fédération romande de cynologie). Pour ce faire, il me fallait 20h en tant qu’aide-moniteur, chose que j’ai mise en place directement, sous la houlette d’Annik qui m’a présenté le club Cyno la Meute.

État actuel de ma formation

À l’heure où j’écris ce texte, j’ai déjà réussi l’examen théorique avec une meilleure note que celle prévue initialement. Le travail autonome a déjà été envoyé auprès de la FRC. Il ne me reste qu’un examen à passer, qui est la pratique. L’examen se déroulera au mois de septembre. Je devrais le passer sans trop de difficultés, pour autant que je garde bien en tête les bases sécuritaires.

Ouverture des préinscriptions

L’ouverture des préinscriptions se fera dès le 7 novembre 2025, soit le jour même de la remise des diplômes. Jusqu’à l’ouverture officielle de l’école canine AmarokSpirit, un rabais important est prévu pour les premiers clients sur les abonnements et leçons privés pour une durée de 2 mois. Après ce laps de temps, les prix de mes cours reviendront à la normale.

Les préinscriptions se feront via ma boutique en ligne, qui est actuellement en construction.

Ce que tu peux déjà faire

Si tu es intéressé·e à ce que je devienne ton futur éducateur canin, tu peux d’ores et déjà prendre contact via Whatsapp et me poser toutes tes questions, que ce soit sur mes futurs services, prix ou encore capacités. Tu peux également me suivre sur les réseaux sociaux, par exemple sur Instagram. Tu retrouveras tous les liens en bas du site.

Le bouche-à-oreille

Tu peux également en parler autour de toi. Si tu connais une personne qui cherche un éducateur canin pour son chiot à venir, ou qui a besoin d’un éducateur à l’aise en ville, je peux lui correspondre.

Balades tranquilles

Si tu veux faire ma connaissance hors cours, tu peux rejoindre le groupe WhatsApp PlayDogs Lausanne. J’y organise des sorties avec d’autres propriétaires de chiens de la région de Lausanne. Ce groupe est là pour mettre en relation les propriétaires de chiens et s’organiser des sorties hors des sentiers battus. Aucun cours n’y est dispensé et donc les promenades sont gratuites.

Rejoindre la communauté

Et si tu désires m’avoir comme moniteur une fois ma formation terminée, tu peux également te créer un compte sur le site. Ceci te permettra de participer à la vie d’AmarokSpirit et d’avoir des échanges grâce au portail communautaire. Tu y retrouveras des tips & trics, des annonces pour les prochains cours et bien d’autres informations intéressantes réservées uniquement à mes clients une fois que j’ouvrirai mon école d’éducation canine.

Petit sondage

Et pour terminer, j’aimerais savoir quels sont les types de cours que tu aimes. Ceci me permettra de faire un projet de programme qui correspond un peu aux attentes de mes éventuels futurs clients ainsi que mettre en place mes futures formations. Car parmi les propositions faites, certaines ne sont pas encore dans mon portefeuille de connaissances, mais elles m’intéressent et je ferai le nécessaire pour pouvoir te donner ces cours avec professionnalisme.

Quels types de cours préfères-tu ?

Voici quelques types de cours que je prévois de donner. Mais lesquels préfères-tu ?


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 = '