Add a mobile-specific sticky ad banner at the bottom of the screen
Introduces a new `MobileStickyAd` component that displays a 320x50px ad banner at the bottom of the screen on mobile devices only. The ad appears after a 2-second delay and can be dismissed by the user for the session. The footer dynamically adjusts its padding to prevent content overlap with the sticky ad. The `replit.md` file is updated to reflect this new feature. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: db8604a4-d491-44c8-b0ac-b67d779b436a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/tdiozLO Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
0248e83a15
commit
dc6648c54d
@ -21,6 +21,7 @@ import KontaktPage from "@/pages/kontakt";
|
|||||||
import AdminGalleryPage from "@/pages/admin-gallery";
|
import AdminGalleryPage from "@/pages/admin-gallery";
|
||||||
import AdminPushPage from "@/pages/admin-push";
|
import AdminPushPage from "@/pages/admin-push";
|
||||||
import CookieConsent from "@/components/cookie-consent";
|
import CookieConsent from "@/components/cookie-consent";
|
||||||
|
import MobileStickyAd from "@/components/mobile-sticky-ad";
|
||||||
|
|
||||||
function ScrollToTop() {
|
function ScrollToTop() {
|
||||||
const [location] = useLocation();
|
const [location] = useLocation();
|
||||||
@ -63,6 +64,7 @@ function App() {
|
|||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Router />
|
<Router />
|
||||||
|
<MobileStickyAd />
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { SiFacebook, SiInstagram, SiYoutube, SiTiktok } from "react-icons/si";
|
import { SiFacebook, SiInstagram, SiYoutube, SiTiktok } from "react-icons/si";
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile";
|
||||||
|
|
||||||
const SOCIAL_LINKS = [
|
const SOCIAL_LINKS = [
|
||||||
{ href: "https://www.facebook.com/folxtv/", icon: SiFacebook, label: "Facebook", testId: "link-social-facebook" },
|
{ href: "https://www.facebook.com/folxtv/", icon: SiFacebook, label: "Facebook", testId: "link-social-facebook" },
|
||||||
@ -8,7 +9,13 @@ const SOCIAL_LINKS = [
|
|||||||
{ href: "https://www.tiktok.com/@folxtv", icon: SiTiktok, label: "TikTok", testId: "link-social-tiktok" },
|
{ href: "https://www.tiktok.com/@folxtv", icon: SiTiktok, label: "TikTok", testId: "link-social-tiktok" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const STICKY_AD_DISMISS_KEY = "folx-sticky-ad-dismissed";
|
||||||
|
|
||||||
export default function Footer({ narrow }: { narrow?: boolean }) {
|
export default function Footer({ narrow }: { narrow?: boolean }) {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const stickyAdDismissed = typeof window !== "undefined" && sessionStorage.getItem(STICKY_AD_DISMISS_KEY);
|
||||||
|
const showStickyPadding = isMobile && !stickyAdDismissed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="border-t border-border mt-10" data-testid="footer">
|
<footer className="border-t border-border mt-10" data-testid="footer">
|
||||||
<div className={`${narrow ? "max-w-4xl" : "max-w-7xl"} mx-auto px-4 sm:px-6 lg:px-8 py-8 relative z-20`}>
|
<div className={`${narrow ? "max-w-4xl" : "max-w-7xl"} mx-auto px-4 sm:px-6 lg:px-8 py-8 relative z-20`}>
|
||||||
@ -113,6 +120,7 @@ export default function Footer({ narrow }: { narrow?: boolean }) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{showStickyPadding && <div className="h-20" aria-hidden="true" />}
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
51
client/src/components/mobile-sticky-ad.tsx
Normal file
51
client/src/components/mobile-sticky-ad.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile";
|
||||||
|
import AdSense from "./adsense";
|
||||||
|
|
||||||
|
const DISMISS_KEY = "folx-sticky-ad-dismissed";
|
||||||
|
|
||||||
|
export default function MobileStickyAd() {
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile) return;
|
||||||
|
const dismissed = sessionStorage.getItem(DISMISS_KEY);
|
||||||
|
if (dismissed) return;
|
||||||
|
|
||||||
|
const timer = setTimeout(() => setVisible(true), 2000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
setVisible(false);
|
||||||
|
sessionStorage.setItem(DISMISS_KEY, "1");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isMobile || !visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-0 left-0 right-0 z-[100] animate-in slide-in-from-bottom duration-300" data-testid="mobile-sticky-ad">
|
||||||
|
<div className="relative bg-card border-t border-card-border shadow-2xl">
|
||||||
|
<button
|
||||||
|
onClick={close}
|
||||||
|
className="absolute -top-8 right-2 w-7 h-7 flex items-center justify-center rounded-full bg-card border border-card-border shadow-lg text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
data-testid="button-sticky-ad-close"
|
||||||
|
aria-label="Schließen"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<div className="text-[9px] text-muted-foreground/40 text-center pt-1 uppercase tracking-widest">Anzeige</div>
|
||||||
|
<div className="flex justify-center pb-1">
|
||||||
|
<AdSense
|
||||||
|
slot="4154017639"
|
||||||
|
format="horizontal"
|
||||||
|
style={{ display: "inline-block", width: "320px", height: "50px" }}
|
||||||
|
responsive={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -30,6 +30,7 @@ The official website for Folx Music Television (folx.tv). Dark-themed bento grid
|
|||||||
- Google AdSense integration (ca-pub-4465464714854276)
|
- Google AdSense integration (ca-pub-4465464714854276)
|
||||||
- Interstitial overlay ad on article pages (3s delay, shows every other article visit, sessionStorage counter)
|
- Interstitial overlay ad on article pages (3s delay, shows every other article visit, sessionStorage counter)
|
||||||
- Parallax/reveal ad below article content (sticky background ad revealed on scroll)
|
- Parallax/reveal ad below article content (sticky background ad revealed on scroll)
|
||||||
|
- Mobile sticky banner ad at bottom of screen (2s delay, session-dismissible, mobile-only via useIsMobile hook)
|
||||||
- Web Push Notifications (bell icon in header, auto-send on new articles, admin panel at /admin/push)
|
- Web Push Notifications (bell icon in header, auto-send on new articles, admin panel at /admin/push)
|
||||||
- Article listing with featured carousel and category filtering
|
- Article listing with featured carousel and category filtering
|
||||||
- HTML content supports embedded iframes (bunny.net, YouTube, Facebook, Instagram, TikTok)
|
- HTML content supports embedded iframes (bunny.net, YouTube, Facebook, Instagram, TikTok)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user