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>
This commit is contained in:
parent
1687120b7f
commit
df82d40a96
6 changed files with 64 additions and 1 deletions
|
|
@ -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],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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%),
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/>
|
||||
</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">
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export default async function Home() {
|
|||
<div className="min-h-screen">
|
||||
<SiteHeader locale={locale} />
|
||||
|
||||
<main id="main">
|
||||
<HeroBanner
|
||||
locale={locale}
|
||||
stats={{
|
||||
|
|
@ -96,6 +97,7 @@ export default async function Home() {
|
|||
</p>
|
||||
<DocListFilters docs={docs} />
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<ChatBubble context={{}} />
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,9 @@ export async function FeaturedCase({ locale }: { locale: "pt-br" | "en" }) {
|
|||
<img
|
||||
src={heroImg}
|
||||
alt={title}
|
||||
fetchPriority="high"
|
||||
width={1280}
|
||||
height={720}
|
||||
className={`absolute inset-0 w-full h-full object-cover ${heroIsArt ? "object-center" : "object-top"} opacity-95 group-hover:opacity-100 group-hover:scale-[1.02] transition-all duration-700`}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -31,13 +31,16 @@ export function HeroBanner({ locale, stats, heroDocId, heroPage }: {
|
|||
|
||||
return (
|
||||
<section className="relative overflow-hidden border-b border-[rgba(224,192,128,0.15)]">
|
||||
{/* Background art layer */}
|
||||
{/* Background art layer — high-priority because it's above the fold */}
|
||||
{heroImg && (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
src={heroImg}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
fetchPriority="high"
|
||||
width={1600}
|
||||
height={900}
|
||||
className="absolute inset-0 w-full h-full object-cover opacity-[0.18] mix-blend-luminosity"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in a new issue