diff --git a/web/app/c/[slug]/page.tsx b/web/app/c/[slug]/page.tsx index b20159a..45b3a6d 100644 --- a/web/app/c/[slug]/page.tsx +++ b/web/app/c/[slug]/page.tsx @@ -50,6 +50,17 @@ async function loadCase(slug: string): Promise<{ fm: Frontmatter; body: string } } } +async function hasIllustration(slug: string): Promise { + const fs = await import("node:fs/promises"); + const UFO_ROOT = process.env.UFO_ROOT || "/data/ufo"; + try { + await fs.stat(path.join(UFO_ROOT, "processing", "case-art", `${slug}.png`)); + return true; + } catch { + return false; + } +} + /** * Extract the first prose paragraph from the body for the meta description * + OG. We pick the locale-preferred sub-section's opener. @@ -109,6 +120,7 @@ export default async function CaseReportPage({ const dateLabel = fm.created_at ? new Date(fm.created_at).toLocaleDateString(locale === "pt-br" ? "pt-BR" : "en-US", { day: "numeric", month: "long", year: "numeric" }) : null; const canonical = `${SITE_URL}/c/${slug}`; const lead = pickLead(body, locale).slice(0, 280); + const heroArt = (await hasIllustration(slug)) ? `/api/static/processing/case-art/${slug}.png` : null; return (
@@ -118,21 +130,54 @@ export default async function CaseReportPage({ ]} /> -
-
-
- {locale === "en" ? "Declassified case file" : "Arquivo desclassificado"} - {dateLabel && <> · {dateLabel}} + {/* Full-bleed editorial hero — only when a painterly illustration + exists for this case. Other cases get the plain title header. */} + {heroArt && ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {title} +
+
+
+
+
+ {locale === "en" ? "Declassified case file" : "Arquivo desclassificado"} + {dateLabel && <> · {dateLabel}} +
+

+ {title} +

+
+
+
+ {locale === "en" ? "Editorial illustration" : "Ilustração editorial"} +
-

- {title} -

- {lead && ( -

- {lead} -

- )} -
+
+ )} + +
+ {!heroArt && ( +
+
+ {locale === "en" ? "Declassified case file" : "Arquivo desclassificado"} + {dateLabel && <> · {dateLabel}} +
+

+ {title} +

+
+ )} + {lead && ( +

+ {lead} +

+ )}
.png — + * a painterly editorial illustration generated for this case. Prefer + * this over the doc-page thumbnail. */ + hero_illustration: string | null; } const CASE_ROOT = process.env.CASE_ROOT || "/data/ufo/case"; @@ -51,6 +55,19 @@ function extractFirstDocRef(body: string): { doc_id: string; page: number } | nu return { doc_id: m[1], page: parseInt(m[2], 10) }; } +const UFO_ROOT = process.env.UFO_ROOT || "/data/ufo"; + +async function illustrationFor(slug: string): Promise { + const fs = await import("node:fs/promises"); + const p = path.join(UFO_ROOT, "processing", "case-art", `${slug}.png`); + try { + await fs.stat(p); + return `/api/static/processing/case-art/${slug}.png`; + } catch { + return null; + } +} + async function loadFeatured(locale: "pt-br" | "en"): Promise { const dir = path.join(CASE_ROOT, "reports"); try { @@ -63,13 +80,15 @@ async function loadFeatured(locale: "pt-br" | "en"): Promise @@ -116,19 +139,23 @@ export async function FeaturedCase({ locale }: { locale: "pt-br" | "en" }) {
{/* Hero image column */} -
+
{heroImg ? ( // eslint-disable-next-line @next/next/no-img-element {title} ) : (
)} -
- {c.hero_doc_id && ( +
+ {heroIsArt ? ( +
+ {locale === "en" ? "Editorial illustration" : "Ilustração editorial"} +
+ ) : c.hero_doc_id && (
{locale === "en" ? "Source · " : "Fonte · "}{c.hero_doc_id} / p{String(c.hero_page).padStart(3, "0")}