Add exclusive photos from recordings to the gallery and improve site metadata
Integrate AdSense ads into the photo gallery, introduce SSR meta tags for the gallery page, and improve layout stability by reserving space for ads and dynamic content. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 81f3d8bc-2328-4374-a06e-57257dbb713b Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/OPD8Ro3 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -96,7 +96,7 @@ export function ArticleCardAd() {
|
||||
|
||||
export function InArticleAd() {
|
||||
return (
|
||||
<div className="my-6 rounded-md overflow-hidden bg-card border border-card-border">
|
||||
<div className="my-6 rounded-md overflow-hidden bg-card border border-card-border" style={{ minHeight: "106px" }}>
|
||||
<div className="text-[9px] text-muted-foreground/40 text-center pt-1 uppercase tracking-widest">Anzeige</div>
|
||||
<AdSense
|
||||
slot="4154017639"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { ChevronLeft, ChevronRight, X, Images, Maximize2 } from "lucide-react";
|
||||
import { InArticleAd } from "./adsense";
|
||||
|
||||
export interface GalleryImage {
|
||||
folder: string;
|
||||
@@ -340,43 +341,58 @@ export default function GalleryPage() {
|
||||
<p className="text-muted-foreground text-sm mb-4" data-testid="text-gallery-count">
|
||||
{Math.min(visibleCount, totalCount)} von {totalCount} Fotos
|
||||
</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{visibleImages.map((img, i) => (
|
||||
<button
|
||||
key={`${img.folder}-${img.fileName}`}
|
||||
onClick={() => setLightboxIndex(i)}
|
||||
className="group relative rounded-lg overflow-hidden bg-card border border-card-border cursor-pointer flex flex-col"
|
||||
data-testid={`button-gallery-image-${i}`}
|
||||
>
|
||||
<div className="relative w-full aspect-[16/9]">
|
||||
<LazyImage
|
||||
src={img.mobile || img.thumb}
|
||||
alt={img.artist || img.fileName}
|
||||
className="absolute inset-0 w-full h-full transition-transform duration-500 group-hover:scale-110"
|
||||
objectPosition={getObjectPosition(img.fileName, fp)}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
|
||||
{img.artist && (
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-2 hidden md:block opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span className="text-white text-xs font-medium drop-shadow-lg" data-testid={`text-artist-desktop-${i}`}>
|
||||
{img.artist}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-0 right-0 p-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Maximize2 className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
{(() => {
|
||||
const AD_EVERY = 12;
|
||||
const chunks: GalleryImage[][] = [];
|
||||
for (let i = 0; i < visibleImages.length; i += AD_EVERY) {
|
||||
chunks.push(visibleImages.slice(i, i + AD_EVERY));
|
||||
}
|
||||
return chunks.map((chunk, ci) => (
|
||||
<div key={ci}>
|
||||
{ci > 0 && <InArticleAd />}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{chunk.map((img, j) => {
|
||||
const globalIdx = ci * AD_EVERY + j;
|
||||
return (
|
||||
<button
|
||||
key={`${img.folder}-${img.fileName}`}
|
||||
onClick={() => setLightboxIndex(globalIdx)}
|
||||
className="group relative rounded-lg overflow-hidden bg-card border border-card-border cursor-pointer flex flex-col"
|
||||
data-testid={`button-gallery-image-${globalIdx}`}
|
||||
>
|
||||
<div className="relative w-full aspect-[16/9]">
|
||||
<LazyImage
|
||||
src={img.mobile || img.thumb}
|
||||
alt={img.artist || img.fileName}
|
||||
className="absolute inset-0 w-full h-full transition-transform duration-500 group-hover:scale-110"
|
||||
objectPosition={getObjectPosition(img.fileName, fp)}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
|
||||
{img.artist && (
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-2 hidden md:block opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<span className="text-white text-xs font-medium drop-shadow-lg" data-testid={`text-artist-desktop-${globalIdx}`}>
|
||||
{img.artist}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-0 right-0 p-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Maximize2 className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
{img.artist && (
|
||||
<div className="md:hidden px-2 py-1.5 bg-card border-t border-card-border">
|
||||
<span className="text-[11px] text-card-foreground font-medium line-clamp-1" data-testid={`text-artist-mobile-${globalIdx}`}>
|
||||
{img.artist}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{img.artist && (
|
||||
<div className="md:hidden px-2 py-1.5 bg-card border-t border-card-border">
|
||||
<span className="text-[11px] text-card-foreground font-medium line-clamp-1" data-testid={`text-artist-mobile-${i}`}>
|
||||
{img.artist}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
|
||||
{hasMore && (
|
||||
<div ref={sentinelRef} className="flex justify-center py-8" data-testid="gallery-load-more">
|
||||
|
||||
@@ -303,3 +303,12 @@ div[class^="fc-"] {
|
||||
inset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.ad-container {
|
||||
contain: layout style;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.adsbygoogle {
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { usePageMeta } from "@/hooks/use-page-meta";
|
||||
import { InArticleAd, PageSideAds } from "@/components/adsense";
|
||||
|
||||
export default function GalleryPageWrapper() {
|
||||
usePageMeta("Fotogalerie - Volksmusik & Schlager Bilder", "Exklusive Fotos und Bilder von Volksmusik- und Schlager-Stars, Konzerten und Events. Die Volksmusik-Fotogalerie von FOLX TV.");
|
||||
usePageMeta("Fotogalerie – Exklusive Backstage- & Konzertfotos | FOLX TV", "Einzigartige Fotos direkt von unseren Aufzeichnungen und Sendungen: Backstage-Momente, Konzertbilder und exklusive Aufnahmen der Volksmusik- und Schlager-Stars bei FOLX TV.");
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<Header />
|
||||
|
||||
@@ -276,6 +276,7 @@ function SignDetail({ signIndex, onNavigate, aiHoroscopes }: { signIndex: number
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ minHeight: "180px" }}>
|
||||
{tab === "daily" && (
|
||||
<div className="space-y-5" data-testid="section-horoscope-daily">
|
||||
<div>
|
||||
@@ -310,6 +311,7 @@ function SignDetail({ signIndex, onNavigate, aiHoroscopes }: { signIndex: number
|
||||
<p className="text-sm text-foreground/80 leading-relaxed" data-testid="text-horoscope-monthly">{horoscope.monthly}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InArticleAd />
|
||||
|
||||
Reference in New Issue
Block a user