diff --git a/landing/src/components/HeroSection.astro b/landing/src/components/HeroSection.astro index 74b033228..8994be5b9 100644 --- a/landing/src/components/HeroSection.astro +++ b/landing/src/components/HeroSection.astro @@ -11,7 +11,7 @@ import { Button } from "@/components/ui/button"; className="font-aeonik text-primary-foreground flex flex-col items-center justify-center gap-10 py-12 md:py-16" >
@@ -81,3 +81,16 @@ import { Button } from "@/components/ui/button"; />
+ + diff --git a/landing/src/components/ui/AdBanner.tsx b/landing/src/components/ui/AdBanner.tsx index 6b8ed4618..f4ad99ede 100644 --- a/landing/src/components/ui/AdBanner.tsx +++ b/landing/src/components/ui/AdBanner.tsx @@ -1,119 +1,128 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; + +const MIN_VISIBLE_HEIGHT = 5; export interface AdBannerProps { zoneId: string; contentId: string; + setBannerHeight?: (height: number) => void; } -export const AdBanner = ({ zoneId, contentId }: AdBannerProps) => { - const [isAdLoaded, setIsAdLoaded] = useState(false); - const adContainerRef = useRef(null); - const adContentRef = useRef(null); - const adDetectedRef = useRef(false); - - useEffect(() => { - const adContainer = adContainerRef.current; - if (!adContainer) return; +export const AdBanner = ({ + zoneId, + contentId, + setBannerHeight, +}: AdBannerProps) => { + const containerRef = useRef(null); + const contentRef = useRef(null); + const insRef = useRef(null); - const insElement = adContainer.querySelector("ins"); - if (!insElement) return; + const [hasBanner, setHasBanner] = useState(false); - const checkForAd = () => { - if (adDetectedRef.current) return true; - - const isLoaded = - insElement.getAttribute("data-content-loaded") === "1" || - insElement.querySelector(".revive-banner") !== null || - insElement.querySelector("a.revive-banner") !== null || - (insElement.children.length > 0 && insElement.offsetHeight > 10); + useEffect(() => { + const container = containerRef.current; + if (!container) return; - if (isLoaded) { - adDetectedRef.current = true; - setIsAdLoaded(true); - return true; + const getIns = () => { + const current = container.querySelector("ins"); + if (current && current !== insRef.current) { + insRef.current = current as HTMLModElement; } - return false; + return insRef.current; }; - const observer = new MutationObserver(() => { - checkForAd(); - }); + const detectBanner = () => { + const ins = getIns(); + if (!ins) return false; + const hasChildren = ins.children.length > 0; + const height = ins.offsetHeight; + const hasVisible = height >= MIN_VISIBLE_HEIGHT; + return hasChildren && hasVisible; + }; - observer.observe(insElement, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ["data-content-loaded", "style", "class", "id"], - }); + const updateState = () => { + setHasBanner(detectBanner()); + }; - observer.observe(adContainer, { + // container observer + const containerObserver = new MutationObserver(updateState); + containerObserver.observe(container, { childList: true, subtree: true, attributes: true, }); - const handleAdLoaded = () => { - if (!adDetectedRef.current) { - adDetectedRef.current = true; - setIsAdLoaded(true); - } - }; - - const eventName = `content-${contentId}-loaded`; - document.addEventListener(eventName, handleAdLoaded); - - checkForAd(); + // content observer + const ins = getIns(); + let insObserver: MutationObserver | null = null; + if (ins) { + insObserver = new MutationObserver(updateState); + insObserver.observe(ins, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["data-content-loaded", "style", "class", "id"], + }); + } - const intervals = [ - setTimeout(checkForAd, 500), - setTimeout(checkForAd, 1000), - setTimeout(checkForAd, 2000), - setTimeout(checkForAd, 3000), - setTimeout(checkForAd, 5000), - ]; + updateState(); return () => { - observer.disconnect(); - intervals.forEach(clearTimeout); - document.removeEventListener(eventName, handleAdLoaded); + containerObserver.disconnect(); + if (insObserver) insObserver.disconnect(); }; }, [contentId]); + // measure height and trigger animation useEffect(() => { - const container = adContainerRef.current; - const content = adContentRef.current; + const container = containerRef.current; + const content = contentRef.current; if (!container || !content) return; - if (isAdLoaded) { - const updateHeight = () => { - const height = content.scrollHeight || content.offsetHeight || 100; - container.style.maxHeight = `${Math.max(height, 100)}px`; - container.style.overflow = "visible"; - }; - requestAnimationFrame(updateHeight); - setTimeout(updateHeight, 100); - setTimeout(updateHeight, 500); - } else { - container.style.maxHeight = "0px"; + if (!hasBanner) { + container.style.height = "0px"; container.style.overflow = "hidden"; + setBannerHeight?.(0); + document.documentElement.dataset.bannerLoaded = "false"; + return; } - }, [isAdLoaded]); + + requestAnimationFrame(() => { + const measuredHeight = Math.max( + content.scrollHeight, + content.offsetHeight, + ); + + container.style.height = `${measuredHeight}px`; + container.style.overflow = "visible"; + setBannerHeight?.(measuredHeight); + document.documentElement.dataset.bannerLoaded = "true"; + }); + }, [hasBanner, setBannerHeight]); return (
-
- +
+
+ />
); }; diff --git a/landing/src/components/ui/Header.tsx b/landing/src/components/ui/Header.tsx index 4b53245df..7400ad9cf 100644 --- a/landing/src/components/ui/Header.tsx +++ b/landing/src/components/ui/Header.tsx @@ -48,6 +48,7 @@ const Header = React.forwardRef( const [showIndicator, setShowIndicator] = useState(false); const [activeTheme, setActiveTheme] = useState("dark"); const [latestNewsId, setLatestNewsId] = useState(DEFAULT_LATEST_NEWS_ID); + const [bannerHeight, setBannerHeight] = useState(0); const localHeaderRef = useRef(null); useImperativeHandle(ref, () => localHeaderRef.current!); @@ -110,7 +111,7 @@ const Header = React.forwardRef(
( : "border-b border-transparent", className, )} + style={{ + transform: + bannerHeight > 0 ? `translateY(${bannerHeight}px)` : undefined, + }} {...props} > - +