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"
>
+
+
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}
>
-
+