Implement a dynamic content layout with featured hero and extended top stories

Update the homepage layout to feature a larger hero article on the left, two smaller articles below it, and an extended "Top Stories" sidebar on the right, dynamically adjusting content display and sizing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cf44f9f8-1298-4129-97e8-18418de82c02
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/0ZGabQy
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic 2026-02-28 21:44:03 +00:00
parent 5c92bc69c8
commit 0f4a569bf3
2 changed files with 48 additions and 8 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File

@ -235,9 +235,9 @@ function TopStoriesList({ articles }: { articles: Article[] }) {
Top-Storys Top-Storys
</h3> </h3>
<div className="space-y-0"> <div className="space-y-0">
{articles.slice(0, 6).map((article) => ( {articles.slice(0, 10).map((article) => (
<Link key={article.id} href={`/article/${article.slug}`}> <Link key={article.id} href={`/article/${article.slug}`}>
<div className="group cursor-pointer py-1.5 border-b border-card-border last:border-0" data-testid={`card-top-${article.id}`}> <div className="group cursor-pointer py-2 border-b border-card-border last:border-0" data-testid={`card-top-${article.id}`}>
<h4 className="text-xs font-medium text-card-foreground line-clamp-2 group-hover:text-primary transition-colors leading-snug">{article.title}</h4> <h4 className="text-xs font-medium text-card-foreground line-clamp-2 group-hover:text-primary transition-colors leading-snug">{article.title}</h4>
<div className="flex items-center gap-1.5 mt-0.5"> <div className="flex items-center gap-1.5 mt-0.5">
<span className="text-[10px] text-muted-foreground">{article.author}</span> <span className="text-[10px] text-muted-foreground">{article.author}</span>
@ -251,16 +251,55 @@ function TopStoriesList({ articles }: { articles: Article[] }) {
); );
} }
function FeaturedHeroCard({ article, focalPoints }: { article: Article; focalPoints?: Record<string, { x: number; y: number }> }) {
const isVideo = article.category === "Video";
const objPos = getObjectPosition(article.coverImage, focalPoints);
return (
<Link href={`/article/${article.slug}`}>
<div className="relative group cursor-pointer rounded-lg overflow-hidden h-full" data-testid={`card-featured-hero-${article.id}`}>
<div className="relative h-full min-h-[260px]">
<img src={article.coverImage || ""} alt={article.title} className="w-full h-full object-cover absolute inset-0 transition-transform duration-700 group-hover:scale-105" style={{ objectPosition: objPos }} loading="lazy" />
{isVideo && (
<div className="absolute inset-0 flex items-center justify-center z-10">
<div className="w-14 h-14 rounded-full bg-primary/90 flex items-center justify-center group-hover:bg-primary transition-colors shadow-lg">
<Play className="w-6 h-6 text-white ml-0.5" fill="white" />
</div>
</div>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/30 to-transparent" />
<div className="absolute bottom-0 left-0 right-0 p-4">
<div className="flex items-center gap-2 mb-1.5">
<span className="text-[10px] font-medium text-primary">{article.author}</span>
<span className="text-white/50 text-[10px]">{timeAgo(new Date(article.publishedAt))}</span>
</div>
<h3 className="text-white font-bold text-base md:text-lg leading-tight line-clamp-3 group-hover:text-primary/90 transition-colors">{article.title}</h3>
</div>
</div>
</div>
</Link>
);
}
function FeaturedCarousel({ articles, popular, galleryImages, focalPoints }: { articles: Article[]; popular?: Article[]; galleryImages?: GalleryImage[]; focalPoints?: Record<string, { x: number; y: number }> }) { function FeaturedCarousel({ articles, popular, galleryImages, focalPoints }: { articles: Article[]; popular?: Article[]; galleryImages?: GalleryImage[]; focalPoints?: Record<string, { x: number; y: number }> }) {
const featured = articles.slice(0, 3); const hero = articles[0];
const bottom = articles.slice(1, 3);
return ( return (
<section data-testid="featured-carousel"> <section data-testid="featured-carousel">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{featured.map((a) => ( <div className="lg:col-span-2">
<MediumCard key={a.id} article={a} focalPoints={focalPoints} /> <div className="grid grid-cols-1 gap-4">
))} {hero && <FeaturedHeroCard article={hero} focalPoints={focalPoints} />}
{popular && popular.length > 0 && <TopStoriesList articles={popular} />} <div className="grid grid-cols-2 gap-4">
{bottom.map((a) => (
<MediumCard key={a.id} article={a} focalPoints={focalPoints} />
))}
</div>
</div>
</div>
<div className="lg:col-span-1">
{popular && popular.length > 0 && <TopStoriesList articles={popular} />}
</div>
</div> </div>
</section> </section>
); );
@ -310,6 +349,7 @@ export default function Home() {
const row2 = articles.slice(3, 5); const row2 = articles.slice(3, 5);
const row3 = articles.slice(5, 7); const row3 = articles.slice(5, 7);
const remaining = articles.slice(7); const remaining = articles.slice(7);
const topStoriesCount = Math.min(12, articles.length);
const rows: Article[][] = []; const rows: Article[][] = [];
for (let i = 0; i < remaining.length; i += 4) { for (let i = 0; i < remaining.length; i += 4) {
rows.push(remaining.slice(i, i + 4)); rows.push(remaining.slice(i, i + 4));