diff --git a/src/components/CarbonAds.tsx b/src/components/CarbonAds.tsx new file mode 100644 index 000000000..f36715476 --- /dev/null +++ b/src/components/CarbonAds.tsx @@ -0,0 +1,119 @@ +"use client" +import { useEffect, useRef, useState, useCallback } from "react" +import { usePathname } from "next/navigation" + +const CARBON_SCRIPT_ID = "_carbonads_js" +const CARBON_SCRIPT_SRC = + "https://cdn.carbonads.com/carbon.js?serve=CW7DTKQ7&placement=react-hook-formcom&format=cover" + +// Persist mount state across SPA navigation (module-level, not per instance) +let hasMounted = false + +export default function CarbonAds() { + // --- Refs and State --- + const containerRef = useRef(null) + const debounceRef = useRef(null) + const [fade, setFade] = useState(1) + const [height, setHeight] = useState(null) + const pathname = usePathname() + + /** + * Debounced reload of Carbon Ads script. + * Removes existing ad/script, injects new script, and sets height after load. + * Debounce prevents rapid reloads on fast route changes. + */ + const reloadCarbonAds = useCallback(() => { + if (debounceRef.current) clearTimeout(debounceRef.current) + debounceRef.current = setTimeout(() => { + // Remove existing ad and script if present + const carbon = containerRef.current?.querySelector("#carbonads") + if (carbon) carbon.remove() + const script = document.getElementById(CARBON_SCRIPT_ID) + if (script) script.remove() + + // Inject new Carbon Ads script + const newScript = document.createElement("script") + newScript.id = CARBON_SCRIPT_ID + newScript.async = true + newScript.src = CARBON_SCRIPT_SRC + newScript.type = "text/javascript" + if (containerRef.current) { + containerRef.current.appendChild(newScript) + } + + // Try to set height after ad loads (poll for up to 2s) + let heightSet = false + const trySetHeight = () => { + const carbon = containerRef.current?.querySelector( + "#carbonads" + ) as HTMLElement | null + if (carbon && carbon.offsetHeight > 0) { + setHeight(carbon.offsetHeight) + heightSet = true + } + } + let tries = 0 + const interval = setInterval(() => { + trySetHeight() + tries++ + if (heightSet || tries > 10) clearInterval(interval) + }, 200) + }, 100) + }, []) + + // --- Initial Mount: Only load ad once --- + useEffect(() => { + // Only run on very first mount (SPA safe) + if (!hasMounted) { + reloadCarbonAds() + hasMounted = true + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + // --- Path Change: Reload ad, skip first render --- + useEffect(() => { + // Only reload on subsequent path changes + if (hasMounted) { + reloadCarbonAds() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname]) + + /** + * On scroll, fade and shrink the ad container + * Uses the actual #carbonads height as basis for smoothness. + */ + useEffect(() => { + function handleScroll() { + const maxScroll = 200 // px after which it's fully hidden + const scrollY = window.scrollY + const ratio = Math.max(0, 1 - scrollY / maxScroll) + setFade(ratio) + const carbon = containerRef.current?.querySelector( + "#carbonads" + ) as HTMLElement | null + const baseHeight = carbon ? carbon.offsetHeight : (height ?? 120) + setHeight(Math.max(0, ratio * baseHeight)) + } + window.addEventListener("scroll", handleScroll) + return () => window.removeEventListener("scroll", handleScroll) + }, [height]) + + // --- Render --- + return ( +
+ ) +} diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 2b0b55be8..7a0126a89 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -4,6 +4,7 @@ import styles from "./SideMenu.module.css" import typographyStyles from "../../styles/typography.module.css" import { useRouter } from "next/router" import { Pages } from "../../types/types" +import CarbonAds from "../CarbonAds" function Menu({ pages = [] }: { pages: Pages }) { const router = useRouter() @@ -13,6 +14,7 @@ function Menu({ pages = [] }: { pages: Pages }) { ) diff --git a/src/components/layout.css b/src/components/layout.css index 4a7df600c..2d3e3d3a5 100644 --- a/src/components/layout.css +++ b/src/components/layout.css @@ -946,10 +946,10 @@ pre[class*="language-"] { } } -#carbon-responsive { - margin: 0 auto 50px; +.carbonAdsContainer { + margin-block-end: 2em; } -#carbon-cover { - margin: 0 auto 50px; +#carbon-responsive .carbon-poweredby { + color: inherit; } diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 3a0f228cf..09d9c173a 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -13,12 +13,6 @@ export default function Document() { strategy="afterInteractive" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" /> -