diff --git a/web/app/c/[slug]/page.tsx b/web/app/c/[slug]/page.tsx index 45b3a6d..f5e227b 100644 --- a/web/app/c/[slug]/page.tsx +++ b/web/app/c/[slug]/page.tsx @@ -84,6 +84,11 @@ export async function generateMetadata( const title = locale === "pt-br" ? (c.fm.topic_pt_br ?? c.fm.topic ?? slug) : (c.fm.topic ?? slug); const desc = pickLead(c.body, locale).slice(0, 200); const canonical = `${SITE_URL}/c/${slug}`; + // OG image — use the case's editorial illustration when present. WhatsApp, + // Twitter, Slack, Telegram, ChatGPT search all pull this as the link card. + const ogImage = (await hasIllustration(slug)) + ? `${SITE_URL}/api/static/processing/case-art/${slug}.png` + : `${SITE_URL}/api/static/processing/case-art/green-fireballs-narrative.png`; return { title, description: desc, @@ -96,11 +101,13 @@ export async function generateMetadata( siteName: "The Disclosure Bureau", locale: locale === "pt-br" ? "pt_BR" : "en_US", publishedTime: c.fm.created_at, + images: [{ url: ogImage, width: 2000, height: 1125, alt: title }], }, twitter: { card: "summary_large_image", title, description: desc, + images: [ogImage], }, }; } diff --git a/web/app/globals.css b/web/app/globals.css index 308dedf..32ba13e 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -29,6 +29,45 @@ html, body { font-feature-settings: "ss01", "ss02"; } +/* Skip-to-content link (a11y) — hidden until keyboard focus reveals it. */ +.skip-link { + position: fixed; + top: -40px; + left: 8px; + z-index: 100; + padding: 8px 16px; + background: #e0c080; + color: #0a0e1a; + font-family: var(--font-mono), monospace; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + text-decoration: none; + transition: top 0.15s ease-out; +} +.skip-link:focus { + top: 8px; + outline: 2px solid #fff; + outline-offset: 2px; +} + +/* Mobile-first overflow safety on prose blockquotes. */ +.prose blockquote { + overflow-wrap: anywhere; +} + +/* Honour user "reduce motion" preference for our hover-scale transitions. */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + body { background-image: radial-gradient(ellipse 60% 50% at 92% 8%, rgba(0, 255, 156, 0.05) 0%, transparent 65%), diff --git a/web/app/layout.tsx b/web/app/layout.tsx index aabfc7a..f8888e1 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -43,11 +43,17 @@ export const metadata: Metadata = { 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: "/", @@ -94,6 +100,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo /> + + {locale === "en" ? "Skip to content" : "Pular para o conteúdo"} + {children}
diff --git a/web/app/page.tsx b/web/app/page.tsx index ab9be6c..5e260e1 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -59,6 +59,7 @@ export default async function Home() {
+
+
diff --git a/web/components/featured-case.tsx b/web/components/featured-case.tsx index 7462602..601a487 100644 --- a/web/components/featured-case.tsx +++ b/web/components/featured-case.tsx @@ -145,6 +145,9 @@ export async function FeaturedCase({ locale }: { locale: "pt-br" | "en" }) { {title} ) : ( diff --git a/web/components/hero-banner.tsx b/web/components/hero-banner.tsx index d582532..6afba16 100644 --- a/web/components/hero-banner.tsx +++ b/web/components/hero-banner.tsx @@ -31,13 +31,16 @@ export function HeroBanner({ locale, stats, heroDocId, heroPage }: { return (
- {/* Background art layer */} + {/* Background art layer — high-priority because it's above the fold */} {heroImg && ( // eslint-disable-next-line @next/next/no-img-element )}