import Link from "next/link"; import { createClient } from "@supabase/supabase-js"; import { rewriteLegacyImageUrl } from "@/lib/legacy-image"; import StarRating from "@/components/StarRating"; import AdImage from "@/components/AdImage"; import { pickAds } from "@/lib/ads"; import { cleanExcerpt } from "@/lib/articles/excerpt"; export const dynamic = "force-dynamic"; interface Row { wp_id: number; wp_slug: string; title: string; excerpt: string | null; category: string | null; featured_image: string | null; featured_image_alt: string | null; author_name: string | null; wp_published_at: string | null; featured: boolean; } function ago(iso: string | null): string { if (!iso) return ""; const t = Date.now() - new Date(iso).getTime(); const m = Math.floor(t / 60000); if (m < 60) return `${m}m ago`; const h = Math.floor(m / 60); if (h < 24) return `${h}h ago`; const d = Math.floor(h / 24); return d < 30 ? `${d}d ago` : new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } export default async function FeaturedBento() { const sb = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { db: { schema: (process.env.NEXT_PUBLIC_SUPABASE_SCHEMA || "av") as "public" }, auth: { persistSession: false }, } ); // Pool: every published row in the Featured category. Sort: pinned // hero (featured=true) first, then by date desc. const { data } = await sb .from("wp_imported_posts") .select("wp_id, wp_slug, title, excerpt, category, featured_image, featured_image_alt, author_name, wp_published_at, featured") .eq("status", "published") .ilike("category", "featured") .order("featured", { ascending: false }) .order("wp_published_at", { ascending: false, nullsFirst: false }) .limit(7); const rows = (data || []) as Row[]; if (rows.length === 0) return null; const hero = rows[0]; // 3-up rail: room is reserved on the right of the same row for the // Blackmagic 300x600 banner — see grid below. Dropped the 4th card so // the banner can sit baseline-aligned with the hero's right edge. const rail = rows.slice(1, 4); const heroAd = pickAds("300x600", 1)[0] || null; function img(r: Row): string { return r.featured_image ? rewriteLegacyImageUrl(r.featured_image) : "/assets/images/article-placeholder.svg"; } return (
{/* Section header — same shape as "The Latest" on ArticleFeed: section-label utility + flex-1 hr + accent-blue "all →" link. */}
Featured Editorial
All featured →
{/* Two-column outer grid: left column holds the 21:9 hero + 3-up rail; right column is the Blackmagic 300x600. The hero now sits NARROWER (constrained to the 1fr-300px column) but keeps its 21:9 / 21:8 aspect ratio — height shrinks proportionally. Banner moves UP to start at the very top of the row, baseline-aligned with the hero. Mobile collapses to single column (banner hidden via lg:block). */}
{/* Cinematic 21:9 hero — fills left column, aspect ratio preserved */}
{hero.featured_image_alt
Feature Story
{hero.category && (
{hero.category}
)}

{hero.title}

{hero.excerpt && (

{cleanExcerpt(hero.excerpt)}

)}
{hero.author_name || "AV Beat"}
{/* 3-up rail — flex-1 so it fills the exact remaining height inside the 600px left column. Each card uses h-full + flex-col so the image keeps its 16:10 ratio while the text area absorbs any leftover pixels (overflow-hidden line-clamps the title cleanly). Result: bottom edge of the rail lines up with the bottom of the 300x600 banner regardless of hero or text-content height. */}
{rail.map((r) => (
{r.featured_image_alt
{r.category || "News"}

{r.title}

))}
); }