Restored to 'eede1dca5c3b2cb640881195fc428ec171b5dce3'

Replit-Restored-To: eede1dca5c
This commit is contained in:
sebastjanartic
2026-03-05 12:18:32 +00:00
parent 9e0c1719db
commit d6b620233b
43 changed files with 675 additions and 1716 deletions
+41 -102
View File
@@ -3,12 +3,12 @@ import { Link } from "wouter";
import { type Article } from "@shared/schema";
import { format } from "date-fns";
import { de } from "date-fns/locale";
import { usePageMeta } from "@/hooks/use-page-meta";
import { Eye, Play, Images } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import Header from "@/components/header";
import Footer from "@/components/footer";
import AdSense, { ArticleCardAd, SidebarAd } from "@/components/adsense";
import AdSense, { ArticleCardAd, MultiplexAd, SidebarAd } from "@/components/adsense";
import ArtistPatternBg from "@/components/artist-pattern-bg";
import { PhotoGalleryWidget } from "@/components/photo-gallery";
import { HoroscopeWidget } from "@/components/horoscope-widget";
import { RecipeWidget } from "@/components/recipe-widget";
@@ -398,54 +398,25 @@ function FeaturedCarousel({ articles, popular, galleryImages, focalPoints }: { a
const pageSize = 3;
const totalPages = Math.max(1, Math.ceil(Math.min(articles.length, 9) / pageSize));
const [page, setPage] = useState(0);
const [displayPage, setDisplayPage] = useState(0);
const [fading, setFading] = useState(false);
const [paused, setPaused] = useState(false);
const changePage = useCallback((newPage: number) => {
if (newPage === displayPage) return;
setFading(true);
setTimeout(() => {
setDisplayPage(newPage);
setFading(false);
}, 300);
}, [displayPage]);
const next = useCallback(() => {
setPage((p) => {
const np = (p + 1) % totalPages;
return np;
});
setPage((p) => (p + 1) % totalPages);
}, [totalPages]);
useEffect(() => {
changePage(page);
}, [page]);
useEffect(() => {
if (paused || totalPages <= 1) return;
const timer = setInterval(next, 8000);
return () => clearInterval(timer);
}, [paused, next, totalPages]);
const start = displayPage * pageSize;
const start = page * pageSize;
const hero = articles[start];
const sideCards = articles.slice(start + 1, start + 3);
const nextPageStart = ((displayPage + 1) % totalPages) * pageSize;
const nextPageArticles = articles.slice(nextPageStart, nextPageStart + pageSize);
return (
<section data-testid="featured-carousel" onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
{nextPageArticles.length > 0 && (
<div className="hidden" aria-hidden="true">
{nextPageArticles.map((a) => a.coverImage && <img key={`preload-${a.id}`} src={a.coverImage} alt="" />)}
</div>
)}
<div
className="grid grid-cols-1 lg:grid-cols-5 gap-4 transition-opacity duration-300"
style={{ opacity: fading ? 0 : 1 }}
>
<div className="grid grid-cols-1 lg:grid-cols-5 gap-4">
<div className="lg:col-span-3">
{hero && <FeaturedHeroCard article={hero} focalPoints={focalPoints} />}
</div>
@@ -483,7 +454,6 @@ function BentoSkeleton() {
}
export default function Home() {
usePageMeta("Volksmusik & Schlager News", "FOLX TV – Aktuelle Nachrichten, Musikvideos und Interviews aus der Welt der Volksmusik und des Schlagers.");
const { data: articles, isLoading } = useQuery<Article[]>({
queryKey: ["/api/articles"],
});
@@ -515,10 +485,6 @@ export default function Home() {
{ id: "recipe", el: <RecipeWidget key="recipe" /> },
{ id: "breaking", el: <div key="breaking" className="flex flex-col gap-4"><BreakingNewsWidget /></div> },
{ id: "gallery2", el: <PhotoGalleryWidget key="gallery2" reverseOrder={true} /> },
{ id: "horoscope2", el: <HoroscopeWidget key="horoscope2" /> },
{ id: "news2", el: <div key="news2" className="flex flex-col gap-4"><NewsWidget /></div> },
{ id: "recipe2", el: <RecipeWidget key="recipe2" /> },
{ id: "breaking2", el: <div key="breaking2" className="flex flex-col gap-4"><BreakingNewsWidget /></div> },
], []);
const gridItems = useMemo(() => {
@@ -549,7 +515,7 @@ export default function Home() {
}
const totalRows = items.length / 4;
const adRows = [1, 3, 5, 7, 9, 11, 13];
const adRows = [1, 3, 5, 7];
let adCount = 0;
for (const row of adRows) {
if (row >= totalRows) continue;
@@ -602,16 +568,6 @@ export default function Home() {
return articles.filter((a) => !usedIds.has(a.id)).slice(0, 3);
}, [articles, widePickedArticles]);
const extraBottomArticles = useMemo(() => {
if (!articles || articles.length < 15) return [];
const usedIds = new Set([
...articles.slice(0, 9).map((a) => a.id),
...widePickedArticles.map((a) => a.id),
...bottomArticles.map((a) => a.id),
]);
return articles.filter((a) => !usedIds.has(a.id)).slice(0, 6);
}, [articles, widePickedArticles, bottomArticles]);
const bottomSection = useMemo(() => {
const items: { type: "widget" | "ad" | "article"; el: JSX.Element }[] = [
{ type: "widget", el: <NewsWidget key="bottom-news" /> },
@@ -624,14 +580,9 @@ export default function Home() {
})),
{ type: "ad", el: <div key="ad-bottom-1"><ArticleCardAd /></div> },
{ type: "widget", el: <PhotoGalleryWidget key="bottom-gallery" /> },
{ type: "ad", el: <div key="ad-bottom-2"><ArticleCardAd /></div> },
...extraBottomArticles.slice(0, 3).map((a) => ({
type: "article" as const,
el: <BlogCard key={`bottom-extra-${a.id}`} article={a} focalPoints={focalPoints} />,
})),
];
return items;
}, [bottomArticles, extraBottomArticles, focalPoints]);
}, [bottomArticles, focalPoints]);
if (isLoading || !articles) {
return (
@@ -679,41 +630,47 @@ export default function Home() {
</div>
</div>
<ArticleCardAd />
{widePickedArticles.length > 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{widePickedArticles.map((a) => (
<WideCard key={`wide-top-${a.id}`} article={a} focalPoints={focalPoints} />
))}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
<ArtistPatternBg className="hidden lg:block rounded-lg overflow-hidden bg-card border border-card-border" seed={99}>
<AdSense
slot="3854634730"
format="fluid"
layoutKey="-6r+cy-10+8a-3"
style={{ display: "block" }}
className="w-full h-full min-h-[250px]"
/>
</ArtistPatternBg>
<div className="lg:col-span-3">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{widePickedArticles.map((a) => (
<WideCard key={`wide-top-${a.id}`} article={a} focalPoints={focalPoints} />
))}
</div>
</div>
</div>
)}
{gridRows.map((row, ri) => (
<div key={`row-group-${ri}`}>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row.map((item) =>
item.type === "widget"
? <div key={item.key}>{item.widget!.el}</div>
: item.type === "ad"
? <div key={item.key} className="h-full" data-testid={`ad-grid-${item.key}`}><ArticleCardAd /></div>
: item.article
? <MediumCard key={item.key} article={item.article} focalPoints={focalPoints} />
: null
)}
{ri === gridRows.length - 1 && widePickedArticles.length > 0 && (
<div className="sm:col-span-2 lg:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-4">
<WideCardClassic article={widePickedArticles[0]} focalPoints={focalPoints} />
{widePickedArticles[1] && <WideCardClassic article={widePickedArticles[1]} focalPoints={focalPoints} />}
</div>
)}
</div>
{ri % 3 === 2 && ri < gridRows.length - 1 && <ArticleCardAd />}
<div key={`row-${ri}`} className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{row.map((item) =>
item.type === "widget"
? <div key={item.key}>{item.widget!.el}</div>
: item.type === "ad"
? <div key={item.key} className="h-full" data-testid={`ad-grid-${item.key}`}><ArticleCardAd /></div>
: item.article
? <MediumCard key={item.key} article={item.article} focalPoints={focalPoints} />
: null
)}
{ri === gridRows.length - 1 && widePickedArticles.length > 0 && (
<div className="sm:col-span-2 lg:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-4">
<WideCardClassic article={widePickedArticles[0]} focalPoints={focalPoints} />
{widePickedArticles[1] && <WideCardClassic article={widePickedArticles[1]} focalPoints={focalPoints} />}
</div>
)}
</div>
))}
<ArticleCardAd />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{bottomSection.map((item, i) => (
<div key={`bottom-${i}`}>
@@ -722,25 +679,7 @@ export default function Home() {
))}
</div>
<ArticleCardAd />
{extraBottomArticles.length > 3 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{extraBottomArticles.slice(3, 6).map((a) => (
<BlogCard key={`extra-bottom-${a.id}`} article={a} focalPoints={focalPoints} />
))}
</div>
)}
<ArticleCardAd />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<PhotoGalleryWidget key="extra-gallery" />
<NewsWidget key="extra-news" />
<RecipeWidget key="extra-recipe" />
</div>
<ArticleCardAd />
<MultiplexAd />
</main>
<Footer />