Restored to '3905f52571b4fa7e048e16d7a3b94ee66307d072'
Replit-Restored-To: 3905f52571
This commit is contained in:
+102
-41
@@ -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, MultiplexAd, SidebarAd } from "@/components/adsense";
|
||||
import ArtistPatternBg from "@/components/artist-pattern-bg";
|
||||
import AdSense, { ArticleCardAd, SidebarAd } from "@/components/adsense";
|
||||
import { PhotoGalleryWidget } from "@/components/photo-gallery";
|
||||
import { HoroscopeWidget } from "@/components/horoscope-widget";
|
||||
import { RecipeWidget } from "@/components/recipe-widget";
|
||||
@@ -398,25 +398,54 @@ 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) => (p + 1) % totalPages);
|
||||
setPage((p) => {
|
||||
const np = (p + 1) % totalPages;
|
||||
return np;
|
||||
});
|
||||
}, [totalPages]);
|
||||
|
||||
useEffect(() => {
|
||||
changePage(page);
|
||||
}, [page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (paused || totalPages <= 1) return;
|
||||
const timer = setInterval(next, 8000);
|
||||
return () => clearInterval(timer);
|
||||
}, [paused, next, totalPages]);
|
||||
|
||||
const start = page * pageSize;
|
||||
const start = displayPage * 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)}>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-4">
|
||||
{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="lg:col-span-3">
|
||||
{hero && <FeaturedHeroCard article={hero} focalPoints={focalPoints} />}
|
||||
</div>
|
||||
@@ -454,6 +483,7 @@ 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"],
|
||||
});
|
||||
@@ -485,6 +515,10 @@ 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(() => {
|
||||
@@ -515,7 +549,7 @@ export default function Home() {
|
||||
}
|
||||
|
||||
const totalRows = items.length / 4;
|
||||
const adRows = [1, 3, 5, 7];
|
||||
const adRows = [1, 3, 5, 7, 9, 11, 13];
|
||||
let adCount = 0;
|
||||
for (const row of adRows) {
|
||||
if (row >= totalRows) continue;
|
||||
@@ -568,6 +602,16 @@ 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" /> },
|
||||
@@ -580,9 +624,14 @@ 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, focalPoints]);
|
||||
}, [bottomArticles, extraBottomArticles, focalPoints]);
|
||||
|
||||
if (isLoading || !articles) {
|
||||
return (
|
||||
@@ -630,47 +679,41 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ArticleCardAd />
|
||||
|
||||
{widePickedArticles.length > 0 && (
|
||||
<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 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>
|
||||
)}
|
||||
|
||||
{gridRows.map((row, ri) => (
|
||||
<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 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>
|
||||
))}
|
||||
|
||||
<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}`}>
|
||||
@@ -679,7 +722,25 @@ export default function Home() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<MultiplexAd />
|
||||
<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 />
|
||||
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
Reference in New Issue
Block a user