From bc275e272f7ea026a7c6bbf193d8d8bc5f0979bc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 07:10:37 +0200 Subject: [PATCH] oglasi in CMP nazaj na stanje pred 28. 8. 2026 (Discover padec) - index.html: Googlovo CMP sporocilo spet zaduseno (controlledMessagingFunction + skrivanje fc-*), kot do 28. 8. - adsense.tsx in mobile-sticky-ad.tsx: stara verzija (stare enote 4154017639, 7672178246, 4593001335, 3854634730, 2398082838; in-article okvir se izrise sele ob polnem oglasu; sticky 320x50) - clanek: vrhnji pas nad naslovnico, brez display oglasa pred besedilom, najvec dva in-article oglasa - naslovnica: postavitev ostane, GridBannerAd na stari enoti 4154017639 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01ViFFkiQmVM2yhrd6pU1UBQ --- client/index.html | 26 ++ client/src/components/adsense.tsx | 317 +++++++-------------- client/src/components/mobile-sticky-ad.tsx | 38 +-- client/src/pages/article.tsx | 23 +- 4 files changed, 140 insertions(+), 264 deletions(-) diff --git a/client/index.html b/client/index.html index 91e5d6f..5e7ef19 100644 --- a/client/index.html +++ b/client/index.html @@ -35,6 +35,32 @@ + diff --git a/client/src/components/adsense.tsx b/client/src/components/adsense.tsx index 19d47e6..aa85d3f 100644 --- a/client/src/components/adsense.tsx +++ b/client/src/components/adsense.tsx @@ -11,7 +11,6 @@ interface AdSenseProps { style?: Record; layout?: string; layoutKey?: string; - eager?: boolean; onAdStatus?: (filled: boolean) => void; } @@ -23,11 +22,6 @@ export default function AdSense({ style, layout, layoutKey, - // Privzeto zahtevamo takoj. Odlozena zahteva dvigne vidnost, a znizza stevilo - // prikazov pri enotah globlje na strani - in prav te (in-article, multiplex) - // nosijo prihodek. Lazy pot ostaja na voljo prek eager={false}, a je nikjer - // ne uporabljamo, dokler ucinka ne moremo izmeriti v AdSense. - eager = true, onAdStatus, }: AdSenseProps) { const adRef = useRef(null); @@ -35,100 +29,44 @@ export default function AdSense({ useEffect(() => { if (pushed.current) return; - const ins = adRef.current; + try { + const adsbygoogle = (window as any).adsbygoogle || []; + adsbygoogle.push({}); + pushed.current = true; + } catch (e) { + } - let io: IntersectionObserver | null = null; - let obs: MutationObserver | null = null; - let timer: ReturnType | null = null; - let decided = false; + if (!onAdStatus) return; + const ins = adRef.current; + if (!ins) return; // Google postavi data-ad-status na "filled", "unfilled" ali "unfill-optimized". // Ce statusa ni, se zanesemo na dejansko visino. Odlocimo se enkrat. - // AdSense odzivni enoti vnaprej doloci visino po sirini zaslona. Ce je - // postrezena kreativa nizja, ostane pod njo prazen rezerviran pas znotraj - // okvirja. Visino zato poravnamo na dejansko visino oglasnega okvirja. - // Nic se ne odreze - visina je natanko taksna kot oglas. - let fitTimers: ReturnType[] = []; - const fitToCreative = () => { - // Enota s trdno visino v style (fiksne Display enote) se ne prilagaja - // kreativi, sicer nam Google 300x400 razpotegne na 300x600. - if (style && typeof (style as any).height === "string") return; - const f = ins?.querySelector("iframe") as HTMLIFrameElement | null; - if (!ins || !f) return; - const h = f.getBoundingClientRect().height; - if (h > 10 && Math.abs(ins.getBoundingClientRect().height - h) > 2) { - ins.style.height = `${Math.ceil(h)}px`; - } - }; - const scheduleFit = () => { - fitTimers.forEach(clearTimeout); - fitTimers = [0, 300, 1000, 2500].map((d) => setTimeout(fitToCreative, d)); - }; - + let decided = false; const decide = (filled: boolean) => { if (decided) return; decided = true; - obs?.disconnect(); - if (timer) clearTimeout(timer); - if (filled) scheduleFit(); - onAdStatus?.(filled); + obs.disconnect(); + clearTimeout(timer); + onAdStatus(filled); }; // Visina samega ni dokaz - nekatere enote imajo rezerviran prostor. // Napolnjen oglas ima notri okvir (iframe) z dejansko visino. const hasFrame = () => { - const f = ins?.querySelector("iframe"); + const f = ins.querySelector("iframe"); return !!f && (f as HTMLIFrameElement).clientHeight > 10; }; const check = () => { - const st = ins?.getAttribute("data-ad-status") || ""; + const st = ins.getAttribute("data-ad-status") || ""; if (st === "filled") return decide(true); if (st.startsWith("unfill")) return decide(false); if (hasFrame()) return decide(true); }; - - // Oglas zahtevamo sele, ko se enota priblizuje vidnemu polju (200 px prej). - // Enote globoko pod pregibom so se prej zahtevale takoj ob nalaganju strani in - // vecine uporabnik ni nikoli videl - nizka vidnost pomeni nizjo ceno. - // Odlocitev o praznosti steje sele od zahteve naprej, ne od mounta. - const push = () => { - if (pushed.current) return; - try { - const adsbygoogle = (window as any).adsbygoogle || []; - adsbygoogle.push({}); - pushed.current = true; - } catch (e) { - } - if (!ins) return; - // Opazujemo tudi pri enotah brez onAdStatus - potrebujemo jih za poravnavo - // visine na kreativo. - obs = new MutationObserver(() => { check(); fitToCreative(); }); - obs.observe(ins, { attributes: true, childList: true, subtree: true, attributeFilter: ["data-ad-status", "style"] }); - timer = setTimeout(() => decide(hasFrame()), 3000); - window.addEventListener("resize", scheduleFit); - check(); - scheduleFit(); - }; - - if (eager || !ins || typeof IntersectionObserver === "undefined") { - push(); - } else { - io = new IntersectionObserver((entries) => { - if (entries.some((e) => e.isIntersecting)) { - io?.disconnect(); - io = null; - push(); - } - }, { rootMargin: "200px 0px" }); - io.observe(ins); - } - - return () => { - io?.disconnect(); - obs?.disconnect(); - if (timer) clearTimeout(timer); - fitTimers.forEach(clearTimeout); - window.removeEventListener("resize", scheduleFit); - }; + const obs = new MutationObserver(check); + obs.observe(ins, { attributes: true, childList: true, subtree: true, attributeFilter: ["data-ad-status", "style"] }); + const timer = setTimeout(() => decide(hasFrame()), 3000); + check(); + return () => { obs.disconnect(); clearTimeout(timer); }; }, []); return ( @@ -139,8 +77,8 @@ export default function AdSense({ style={style || { display: "block" }} data-ad-client="ca-pub-4465464714854276" data-ad-slot={slot} - {...(format ? { "data-ad-format": format } : {})} - data-full-width-responsive={responsive ? "true" : "false"} + data-ad-format={format} + data-full-width-responsive={responsive ? "true" : undefined} {...(layout ? { "data-ad-layout": layout } : {})} {...(layoutKey ? { "data-ad-layout-key": layoutKey } : {})} /> @@ -150,54 +88,20 @@ export default function AdSense({ let adSeedCounter = 1; -// Vodoravni pas med vrsticami mreze. Oglas v celici je vrstico raztegnil na -// svojo visino (546 px proti 399 px kartice) in pod izvlecki je ostalo prazno. -// Pas cez vso sirino na visino kartic ne vpliva in ima boljso zapolnjenost. -export function GridBannerAd() { +export function ArticleCardAd() { + const seed = useMemo(() => adSeedCounter++, []); const [filled, setFilled] = useState(null); + if (filled === false) return null; return ( - // Pas cez vso sirino je bil visok 248 do 280 px in je bralca prekinil bolj, - // kot je prinesel. Zozimo ga na 728 px: pri tej sirini Google servira - // leaderboard 728x90, na telefonu pa 320x100. Rezanja ne uporabljamo, - // ker je obrezan oglas krsitev pravil. -
-
- Anzeige -
- {/* FOLX 2026 - Leaderboard */} + -
- ); -} - -export function ArticleCardAd() { - const [filled, setFilled] = useState(null); - if (filled === false) return null; - // 31. 8. 2026: oglas mora biti enak kartici, torej cela celica in ne nizji - // blok z luknjo pod njim. Okvir je zato h-full in prevzame visino vrstice, - // oglas pa je v njem centriran. Enota 9916590834 je fiksna 300x400: fiksne - // mere Googlova skripta ne prevaja, zato se ne razteguje kot responsive. - return ( -
- {/* FOLX 2026 - Grid, fiksna Display enota 300x600. Fiksna mera pomeni, - da Googlova skripta visine ne prevaja; stara 9916590834 je bila - Responsiv in se je razteznila na 548 px. */} - -
+ ); } @@ -215,71 +119,36 @@ export function TopBannerAd() { {shown && (
Anzeige
)} - {/* format="auto" + full-width-responsive: Google dobi celo sirino in - izbere kreativo, ki jo zapolni. Pri "horizontal" s trdo visino je - vracal 320x50 in okrog je ostal prazen rob. */} - {/* FOLX 2026 - Leaderboard */} ); } -// Navaden odzivni display v toku besedila. Za razliko od fluid in-article enote -// dobi Google celotno sirino in lahko postrezhe visoko kreativo (336x280, 300x600), -// torej en cel oglas, mimo katerega bralec odskrola - namesto vec drobnih. -export function InContentDisplayAd() { - const [filled, setFilled] = useState(null); - if (filled === false) return null; - const shown = filled === true; - return ( -
- {shown && ( -
Anzeige
- )} - {/* Sirina 728 px: pri njej Google servira leaderboard 728x90, na telefonu - 320x100. Pri polni sirini clanka je servirala velik nativni oglas - visok 250 px in bralcu je zakrila zacetek besedila. */} -
- -
-
- ); -} - export function InArticleAd() { // Prava AdSense In-article enota (fluid + data-ad-layout="in-article"). // Kadar oglasa ni, se okvir in rezerviran prostor ne izriseta. const [filled, setFilled] = useState(null); - // 1. 9. 2026: prej se je okvir dodal sele po nalozitvi oglasa in stran je - // poskocila - meritev je pokazala CLS 0,10 na clanku in 0,15 na naslovnici, - // Search Console pa 225 naslovov cez prag. Zdaj je prostor rezerviran - // vnaprej in se po nalozitvi nic ne premakne. + if (filled === false) return null; + const shown = filled === true; return ( -
-
Anzeige
- {/* FOLX 2026 - In-Article */} +
+ {shown && ( +
Anzeige
+ )} @@ -294,60 +163,39 @@ export function MultiplexAd() { return (
); } -// Oba stolpca ostaneta (nic se ne odvzame), a se prilagodita sirini zaslona: -// 160x600 od 1536 px, 300x600 od 1700 px naprej, kjer je prostora dovolj. -// Sirino dolocz CSS, AdSense jo prebere ob zahtevi. Dodan onAdStatus, da prazna -// enota ne pusti luknje - prej je ni imela. -function SideRail({ side, offset }: { side: "left" | "right"; offset: number }) { - const [filled, setFilled] = useState(null); - if (filled === false) return null; - return ( -
-
- {/* Trdna mera 160x600 (skyscraper). Prej je bil pas na sirokih zaslonih - 300 px in z responsive vertical si je Google visino racunal sam, - zato se je raztegoval cez pol zaslona. */} - {/* FOLX 2026 - Skyscraper, fiksna Display enota 160x600. */} - -
-
- ); -} - export function PageSideAds({ contentHalfWidth = 640 }: { contentHalfWidth?: number }) { - // 31. 8. 2026: pasova sta bila prilepljena na rob zaslona, ker je bila - // spremenljivka --rail-w nad 1700 px se vedno 300, enota pa je zdaj fiksna - // 160 px - odmik se je racunal s 140 px prevelike sirine. Zdaj je povsod - // 160, razmik do vsebine pa majhen, da sta pasova tik ob clanku. - const gap = 8; + const gap = 10; const offset = contentHalfWidth + gap; return ( -
- - +
+
+
+ +
+
+
+
+ +
+
); } @@ -358,14 +206,41 @@ export function SidebarAd() { return (
- {/* FOLX 2026 - Sidebar, fiksna Display enota 300x600. */} setVisible(filled)} />
); } + +// Vodoravni pas med vrsticami mreze na naslovnici. 4. 9. 2026: nazaj na staro +// enoto 4154017639 (horizontal), enako kot vrhnji pas pred 28. 8. +export function GridBannerAd() { + const [filled, setFilled] = useState(null); + if (filled === false) return null; + const shown = filled === true; + return ( +
+
+ {shown && ( +
Anzeige
+ )} + +
+
+ ); +} diff --git a/client/src/components/mobile-sticky-ad.tsx b/client/src/components/mobile-sticky-ad.tsx index 5e19d8e..4ca4df5 100644 --- a/client/src/components/mobile-sticky-ad.tsx +++ b/client/src/components/mobile-sticky-ad.tsx @@ -8,7 +8,6 @@ const DISMISS_KEY = "folx-sticky-ad-dismissed"; export default function MobileStickyAd() { const isMobile = useIsMobile(); const [visible, setVisible] = useState(false); - const [filled, setFilled] = useState(null); useEffect(() => { if (!isMobile) return; @@ -24,37 +23,26 @@ export default function MobileStickyAd() { sessionStorage.setItem(DISMISS_KEY, "1"); }; - if (!isMobile || !visible || filled === false) return null; - const shown = filled === true; + if (!isMobile || !visible) return null; return (
-
- {shown && ( - - )} - {shown && ( -
Anzeige
- )} - {/* Vodoravni format brez full-width-responsive: enota se drzi sirine - svojega vsebnika in da 320x50 ali 320x100. Z vklopljenim - full-width-responsive je Google vracal 390x390 kvadrat. */} -
+
+ +
Anzeige
+
diff --git a/client/src/pages/article.tsx b/client/src/pages/article.tsx index 9841546..58b41ee 100644 --- a/client/src/pages/article.tsx +++ b/client/src/pages/article.tsx @@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import Header from "@/components/header"; import Footer from "@/components/footer"; -import { InArticleAd, InContentDisplayAd, PageSideAds, MultiplexAd } from "@/components/adsense"; +import { InArticleAd, PageSideAds, MultiplexAd, TopBannerAd } from "@/components/adsense"; import { useIsMobile } from "@/hooks/use-mobile"; import DOMPurify from "dompurify"; import ShareButtons from "@/components/share-buttons"; @@ -344,8 +344,7 @@ export default function ArticlePage() {
- {/* Zgornjega pasu na clanku namenoma ni: potiskal je naslovnico in naslov - pod pregib, placan pa je najslabse. Prvi oglas pride sele v besedilu. */} +
- {/* Enota "Folx News - In-Article" (4154017639) je bila prej vrhnji pas nad - naslovnico. Ostane na clanku, a pod naslovom in tik pred besedilom: - isti prikazi, boljse mesto, naslov ne pade pod pregib. */} - - + {(() => { const sanitized = sanitizeContent(articleBody || article.content); const blocks = sanitized.split(/(?=<(?:p|h[2-4]|div)[\s>])/i).filter(Boolean); @@ -435,13 +430,8 @@ export default function ArticlePage() { // Vsi so fluid in-article enota 7672178246 (najvecji zasluzkar). const last = segments.length - 1; - const extra = Math.min(3, Math.max(0, Math.floor((blocks.length - 10) / 5))); - const adCount = last <= 0 ? 0 : (last === 1 ? 1 : Math.min(2 + extra, last)); - const AD_AFTER: number[] = []; - for (let i = 0; i < adCount; i++) { - const pos = i === 0 ? 0 : Math.round((i * last) / adCount); - if (pos < last && !AD_AFTER.includes(pos)) AD_AFTER.push(pos); - } + // Najvec dva oglasa v besedilu: po prvem odstavku in na sredini (stanje pred 28. 8. 2026). + const AD_AFTER = last <= 0 ? [] : (last === 1 ? [0] : [0, Math.floor(last / 2)]); return (
{ if (!el) return; @@ -462,9 +452,6 @@ export default function ArticlePage() { dangerouslySetInnerHTML={{ __html: html }} data-testid={idx === 0 ? "article-content" : `article-content-${idx}`} /> - {/* VSA mesta v besedilu so 7672178246 - to je enota z najvecjim - prihodkom (98,93 EUR v 7 dneh). Prej sta bili tam dve, zdaj - dve do pet. Enota 4154017639 ima svoje mesto pred besedilom. */} {AD_AFTER.includes(idx) && }
))}