Naslovnica: mobilni mozaik (2 mali + 1 siroka, nato 1 velika + 2 mali), desktop nespremenjen

This commit is contained in:
sebastjan 2026-08-15 16:09:07 +02:00
parent 3c6079d720
commit 6f4cfa8ee9

View File

@ -148,6 +148,77 @@ function getObjectPosition(coverImage: string | null, focalPoints?: Record<strin
return `${fp.x}% ${fp.y}%`;
}
function MosaicTile({ article, focalPoints, tall = false }: { article: Article; focalPoints?: Record<string, { x: number; y: number }>; tall?: boolean }) {
const objPos = getObjectPosition(article.coverImage, focalPoints);
return (
<Link href={`/article/${article.slug}`}>
<div className="relative rounded-lg overflow-hidden" data-testid={`tile-mosaic-${article.id}`}>
<img
src={thumbUrl(article.coverImage)}
alt={article.title}
className={`w-full object-cover ${tall ? "aspect-[16/10]" : "aspect-square"}`}
style={{ objectPosition: objPos }}
loading="lazy"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/85 via-black/30 to-transparent" />
<span className="absolute top-2 left-2 bg-primary text-white text-[10px] font-semibold px-2 py-0.5 rounded">
{article.category || "News"}
</span>
<h3 className={`absolute bottom-2 left-2 right-2 text-white font-semibold leading-snug ${tall ? "text-base line-clamp-2" : "text-xs line-clamp-3"}`}>
{article.title}
</h3>
</div>
</Link>
);
}
function MosaicHeading({ children }: { children: React.ReactNode }) {
return (
<div className="mb-3">
<h2 className="text-primary font-bold text-lg leading-none mb-2">{children}</h2>
<div className="h-0.5 w-16 bg-primary" />
<div className="h-px w-full bg-card-border -mt-px" />
</div>
);
}
function MobileMosaic({ latest, popular, focalPoints }: { latest: Article[]; popular?: Article[]; focalPoints?: Record<string, { x: number; y: number }> }) {
const l = (latest || []).slice(0, 3);
if (l.length === 0) return null;
const p = (popular || []).filter((a) => !l.some((x) => x.id === a.id)).slice(0, 3);
return (
<div className="md:hidden space-y-6" data-testid="section-mobile-mosaic">
<section>
<MosaicHeading>Neueste Artikel</MosaicHeading>
<div className="grid grid-cols-2 gap-2">
{l.slice(0, 2).map((a) => (
<MosaicTile key={a.id} article={a} focalPoints={focalPoints} />
))}
</div>
{l[2] && (
<div className="mt-2">
<MosaicTile article={l[2]} focalPoints={focalPoints} tall />
</div>
)}
</section>
{p.length > 0 && (
<section>
<MosaicHeading>Beliebt</MosaicHeading>
<MosaicTile article={p[0]} focalPoints={focalPoints} tall />
{p.length > 1 && (
<div className="grid grid-cols-2 gap-2 mt-2">
{p.slice(1, 3).map((a) => (
<MosaicTile key={a.id} article={a} focalPoints={focalPoints} />
))}
</div>
)}
</section>
)}
</div>
);
}
function MediumCard({ article, focalPoints }: { article: Article; focalPoints?: Record<string, { x: number; y: number }> }) {
const isVideo = article.category === "Video";
const objPos = getObjectPosition(article.coverImage, focalPoints);
@ -707,7 +778,14 @@ export default function Home() {
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
<div className="lg:col-span-3 space-y-4">
<FeaturedCarousel articles={articles.filter(a => a.featured)} popular={popular} galleryImages={galleryImages} focalPoints={focalPoints} />
<div className="hidden md:block">
<FeaturedCarousel articles={articles.filter(a => a.featured)} popular={popular} galleryImages={galleryImages} focalPoints={focalPoints} />
</div>
<MobileMosaic
latest={[...articles].sort((a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())}
popular={popular}
focalPoints={focalPoints}
/>
</div>
<div className="lg:col-span-1 space-y-4">
<SidebarWeatherWidget />