diff --git a/src/app/globals.css b/src/app/globals.css index 87cf654..8fe8fd2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -128,3 +128,8 @@ dl.facts dd { margin: 0; overflow-wrap: anywhere; } .site-footer nav { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.75rem; } .site-footer a { color: var(--muted); } .site-footer + div > .pfs-footer { border-top: 0; } + +/* CrawlProof ad unit: no box until ad.js starts filling it, so an unsold or blocked slot leaves no hole. */ +.ad-unit { display: flex; flex-direction: column; align-items: center; height: 0; overflow: hidden; } +.ad-unit[data-cp-filled] { height: auto; min-height: 106px; margin: 1.5rem 0; overflow: visible; } +.ad-unit[data-cp-filled][data-format="banner_300x250"] { min-height: 266px; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4b57f8c..55b0411 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from "next"; import { Footer } from "@profullstack/footer/react"; import Link from "next/link"; +import { AdUnit } from "@/components/ad-unit"; import { RegisterServiceWorker } from "@/components/register-sw"; import { DISCLOSURE } from "@/lib/site"; import "./globals.css"; @@ -46,6 +47,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{/* CrawlProof stats: a plain async tag so it is in the served HTML (next/script afterInteractive is not). */} + Skip to content @@ -63,6 +65,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }){DISCLOSURE}
diff --git a/src/components/ad-unit.tsx b/src/components/ad-unit.tsx new file mode 100644 index 0000000..e608896 --- /dev/null +++ b/src/components/ad-unit.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +/** CrawlProof ad slot for datamart.help. Manage it at https://crawlproof.com/ads/slots */ +const AD_SLOT = "04392e64-733a-48dd-b17b-c4dc9eb3a399"; + +/** 728px banner plus the .wrap gutters; anything narrower gets the 300x250 rectangle. */ +const LEADERBOARD_QUERY = "(min-width: 760px)"; + +declare global { + interface Window { + crawlproofAds?: { scan: () => void }; + } +} + +/** + * One CrawlProof unit above the footer. The aside is server-rendered without + * data-slot, so ad.js's own DOMContentLoaded pass skips it; the format is picked + * from the real viewport on mount, then we ask ad.js to scan (retrying briefly + * until it has loaded, and giving up so an ad blocker leaves no timer running). + * It stays zero-height until ad.js marks it data-cp-filled. + */ +export function AdUnit() { + const ref = useRef