disclosure-bureau/web/app/layout.tsx
Luiz Gustavo df82d40a96
Some checks failed
CI / Web — typecheck + lint + build (push) Failing after 36s
CI / Scripts — Python smoke (push) Failing after 6s
CI / Web — npm audit (push) Failing after 42s
CI / Retrieval — golden set (Recall@5 + MRR) (push) Failing after 5s
W5.7 (Phase 3E): perf + a11y + OG images
A11y:
  - Skip-link in <body> (focus-visible only) that jumps to #main.
    Bilingual ("Skip to content" / "Pular para o conteúdo").
  - <main id="main"> landmark wrapping the homepage body.
  - prefers-reduced-motion media query disables the hover-scale + image
    transitions for users with vestibular sensitivity.
  - Skip-link styled with high contrast (gold-on-dark) + outline on
    keyboard focus.

Performance:
  - HeroBanner background image: fetchPriority="high" + explicit
    width/height (1600x900) for zero CLS.
  - FeaturedCase image: fetchPriority="high" + 1280x720 to prevent
    layout shift while the 2.7MB painting loads.
  - IconicCases tiles already have loading="lazy".
  - prose blockquote: overflow-wrap:anywhere so verbatim quotes don't
    bust the mobile viewport.

Open Graph:
  - app/layout.tsx default OG image set to the green-fireballs
    painting (any page without its own image card inherits this).
  - app/c/[slug] OG image is the case's editorial illustration when
    one exists. WhatsApp, Twitter, Telegram, Slack, ChatGPT search
    all pull this when the link is shared. 2000x1125 for the
    "summary_large_image" twitter card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:55:35 -03:00

114 lines
4.4 KiB
TypeScript

import type { Metadata } from "next";
import { JetBrains_Mono, Inter, Fraunces } from "next/font/google";
import "./globals.css";
import { CommandPalette } from "@/components/command-palette";
import { LocaleToggle, getLocale } from "@/components/locale-toggle";
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono" });
const fraunces = Fraunces({
subsets: ["latin"],
variable: "--font-display",
// No explicit `weight` → next/font treats this as a variable font and
// exposes the full weight range via CSS. `axes` requires variable mode.
axes: ["SOFT", "WONK"],
});
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://disclosure.top";
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: {
default: "The Disclosure Bureau — UAP/UFO desclassificado",
template: "%s · The Disclosure Bureau",
},
description:
"122 documentos desclassificados do Departamento de Guerra dos EUA sobre UAP/UFO. " +
"Pilotos, oficiais e físicos relatam o que viram. Avistamentos, testemunhas, " +
"objetos catalogados — arquivos abertos da divulgação.",
keywords: [
"UAP", "UFO", "ovni", "desclassificado", "war.gov", "Pentagon",
"Kenneth Arnold", "Mantell", "green fireballs", "Sandia",
"Project Blue Book", "Robertson Panel", "AATIP",
"documentos desclassificados", "avistamento", "testemunha",
"disclosure", "divulgação UFO",
],
authors: [{ name: "The Disclosure Bureau" }],
openGraph: {
type: "website",
siteName: "The Disclosure Bureau",
title: "The Disclosure Bureau — UAP/UFO desclassificado",
description:
"122 documentos desclassificados. Pilotos, oficiais, físicos relatam o que viram.",
locale: "pt_BR",
alternateLocale: ["en_US"],
url: SITE_URL,
images: [{
url: `${SITE_URL}/api/static/processing/case-art/green-fireballs-narrative.png`,
width: 2000, height: 1125,
alt: "The Disclosure Bureau — UAP/UFO desclassificado",
}],
},
twitter: {
card: "summary_large_image",
title: "The Disclosure Bureau",
description: "Arquivos UAP/UFO desclassificados, narrados a partir do registro público.",
images: [`${SITE_URL}/api/static/processing/case-art/green-fireballs-narrative.png`],
},
alternates: {
canonical: "/",
languages: { "pt-BR": "/", "en-US": "/" },
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-image-preview": "large",
"max-snippet": -1,
},
},
other: {
// GEO (Generative Engine Optimization) — explicit primary-source statement
// so retrieval-augmented assistants understand what the site is.
"ai:purpose":
"Public, citation-linked archive of declassified UAP/UFO documents from the US Department of War. Each case file is grounded in primary-source memos with verbatim quotes and bbox-cropped imagery.",
"ai:license": "Documents are US Government works in the public domain; site narrative © Disclosure Bureau, CC-BY 4.0.",
},
};
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const locale = await getLocale();
return (
<html lang={locale === "en" ? "en" : "pt-BR"} className="dark" data-locale={locale}>
<head>
{/* JSON-LD: organization-level schema. Per-page Article/Event schemas
are added in their own routes. */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
name: "The Disclosure Bureau",
url: SITE_URL,
description:
"Public archive of declassified UAP/UFO documents from the US Department of War, " +
"with narrated case files grounded in primary sources.",
sameAs: [],
}) }}
/>
</head>
<body className={`${inter.variable} ${mono.variable} ${fraunces.variable}`}>
<a href="#main" className="skip-link">
{locale === "en" ? "Skip to content" : "Pular para o conteúdo"}
</a>
{children}
<CommandPalette />
<div className="fixed bottom-3 left-3 z-40 opacity-70 hover:opacity-100 transition">
<LocaleToggle current={locale} />
</div>
</body>
</html>
);
}