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:
sebastjanartic
2026-03-08 07:33:00 +00:00
parent 8a263dee0f
commit 4ece9e4ccd
8 changed files with 133 additions and 38 deletions
+1 -1
View File
@@ -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"
+52 -36
View File
@@ -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">