diff --git a/web/app/c/[slug]/page.tsx b/web/app/c/[slug]/page.tsx index a8d2c85..c39ba57 100644 --- a/web/app/c/[slug]/page.tsx +++ b/web/app/c/[slug]/page.tsx @@ -121,9 +121,18 @@ export default async function CaseReportPage({ const locale = (await getLocale()) === "en" ? "en" : "pt-br"; const c = await loadCase(slug); if (!c) notFound(); - const { fm, body } = c; + const { fm } = c; - const title = locale === "pt-br" ? (fm.topic_pt_br ?? fm.topic ?? slug) : (fm.topic ?? slug); + // The narrator writes two H1s in the body: "# " then + // "# <Título PT-BR>". Prefer those over the generic frontmatter topic + // (which is often an auto-derived "Doc 18 100754 …"). Pick by locale and + // strip BOTH leading H1s from the body so the headline isn't duplicated. + const h1s = [...c.body.matchAll(/^#\s+(.+)$/gm)].map((m) => m[1].trim()); + const bodyTitle = locale === "pt-br" ? (h1s[1] ?? h1s[0]) : h1s[0]; + // Strip H1 lines ("# Title") — the "## section" headers survive since the + // regex requires whitespace after a single #. Avoids duplicating the title. + const body = c.body.replace(/^#\s+.+$\n?/gm, "").replace(/^\s+/, ""); + const title = bodyTitle ?? (locale === "pt-br" ? (fm.topic_pt_br ?? fm.topic ?? slug) : (fm.topic ?? slug)); 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); diff --git a/web/components/case-library.tsx b/web/components/case-library.tsx index edcd76a..3f8b8d2 100644 --- a/web/components/case-library.tsx +++ b/web/components/case-library.tsx @@ -93,10 +93,15 @@ async function loadCases(locale: "pt-br" | "en"): Promise<CaseFile[]> { const md = await readFile(full, "utf-8"); const st = await stat(full); const { fm, body } = parseFrontmatter(md); + // Prefer the narrator's body H1 (magazine title) over the generic + // auto-derived frontmatter topic. Two H1s: EN then PT-BR. + const h1s = [...body.matchAll(/^#\s+(.+)$/gm)].map((m) => m[1].trim()); + const enTitle = h1s[0] ?? fm.topic ?? f; + const ptTitle = h1s[1] ?? h1s[0] ?? fm.topic_pt_br ?? fm.topic ?? f; items.push({ slug: f.replace(/\.md$/, ""), - topic: fm.topic ?? f, - topic_pt_br: fm.topic_pt_br ?? null, + topic: enTitle, + topic_pt_br: ptTitle, opening: pickOpening(body, locale), mtimeMs: st.mtimeMs, }); diff --git a/web/components/featured-case.tsx b/web/components/featured-case.tsx index 601a487..d8fd5d9 100644 --- a/web/components/featured-case.tsx +++ b/web/components/featured-case.tsx @@ -81,10 +81,11 @@ async function loadFeatured(locale: "pt-br" | "en"): Promise<FeaturedCaseData | const { fm, body } = parseFrontmatter(md); const docRef = extractFirstDocRef(body); const slug = f.replace(/\.md$/, ""); + const h1s = [...body.matchAll(/^#\s+(.+)$/gm)].map((m) => m[1].trim()); items.push({ slug, - topic: fm.topic ?? f, - topic_pt_br: fm.topic_pt_br ?? null, + topic: h1s[0] ?? fm.topic ?? f, + topic_pt_br: h1s[1] ?? h1s[0] ?? fm.topic_pt_br ?? null, opening: pickOpening(body, locale), hero_doc_id: docRef?.doc_id ?? null, hero_page: docRef?.page ?? null, @@ -92,6 +93,11 @@ async function loadFeatured(locale: "pt-br" | "en"): Promise<FeaturedCaseData | mtimeMs: st.mtimeMs, }); } + // Pin a flagship to the featured slot (it has a curated hero painting). + // Falls back to most-recent if the flagship isn't present. + const FLAGSHIP = "green-fireballs-narrative"; + const flagship = items.find((i) => i.slug === FLAGSHIP); + if (flagship) return flagship; items.sort((a, b) => b.mtimeMs - a.mtimeMs); return items[0] ?? null; } catch {