disclosure-bureau/web/components/iconic-cases.tsx
Luiz Gustavo 1687120b7f
Some checks failed
CI / Web — typecheck + lint + build (push) Failing after 36s
CI / Scripts — Python smoke (push) Failing after 4s
CI / Web — npm audit (push) Failing after 42s
CI / Retrieval — golden set (Recall@5 + MRR) (push) Failing after 6s
W5.6 (Phase 3D): Iconic Cases — curated rail on homepage
8 hand-picked UFO incidents that any enthusiast recognises:
  - Kenneth Arnold 1947 (the genesis — "flying saucers" coined)
  - Roswell 1947 (the original Army press release)
  - Maury Island 1947 (Puget Sound, slag drop, FBI plane crash)
  - Mantell 1948 (first known UFO casualty)
  - Rendlesham Forest 1980 (USAF security police, deputy commander tape)
  - Phoenix Lights 1997 (V-formation across Arizona, governor's reversal)
  - Nimitz Tic-Tac 2004 (USS Nimitz F/A-18, gun-cam released 2017)
  - Green Fireballs Sandia 1948 (copper salts, nuclear-site overflights)

Each case has:
  - Bilingual hand-written editorial blurb (PT-BR + EN, no LLM)
  - Painterly editorial hero illustration at 2K
  - Year + tag chips (military / civilian / modern / early / naval /
    aviation / mass-sighting)
  - Link to /e/events/<id> when indexed in corpus, /search?q=... when not,
    /c/<slug> for the green-fireballs narrative case

Components:
  lib/iconic-cases.ts — IconicCase type + ICONIC_CASES editorial list
  components/iconic-cases.tsx — magazine grid: first two as wide hero pair,
    rest as 3-up tiles below. Hover scale on images, gradient overlays,
    aspect-16/11 hero + 16/10 compact, lazy-loaded.
  app/page.tsx — inserted between <FeaturedCase /> and <PortalGrid />

4 new hero illustrations generated this session:
  - iconic-nimitz-tic-tac-2004.png       (Nano Banana Pro)
  - iconic-phoenix-lights-1997.png       (Nano Banana Pro)
  - iconic-rendlesham-forest-1980.png    (gpt-image-1.5 via Codex)
  - iconic-mantell-1948.png              (Nano Banana Pro)
Per user direction: mixed generators (Nano Banana primary, Codex
co-pilot) so the homepage has stylistic variety while keeping the
painterly editorial register.

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

119 lines
4.8 KiB
TypeScript

/**
* IconicCases — the curated cover-story rail.
*
* Magazine grid of hand-picked iconic UFO incidents with painterly hero
* illustrations. Sits high on the homepage so the casual reader lands on
* recognisable history within two seconds: Roswell, Nimitz, Phoenix,
* Rendlesham.
*/
import Link from "next/link";
import { ICONIC_CASES, type IconicCase } from "@/lib/iconic-cases";
const ART_BASE = "/api/static/processing/case-art";
export function IconicCases({ locale }: { locale: "pt-br" | "en" }) {
const cases = ICONIC_CASES;
if (cases.length === 0) return null;
// Split: first 2 go into a wide hero pair, the rest tile below in a 3-up grid.
const [first, second, ...rest] = cases;
return (
<section className="mx-auto max-w-7xl px-4 md:px-8 py-12 md:py-16">
<div className="flex items-end justify-between mb-6 flex-wrap gap-3">
<div>
<div className="text-[10px] font-mono uppercase tracking-[0.18em] text-[#5a6678] mb-2">
{locale === "en" ? "// The iconic record" : "// Os clássicos da divulgação"}
</div>
<h2 className="font-display text-2xl md:text-4xl text-[#e7ecf3] tracking-tight">
{locale === "en"
? "The cases every UFO enthusiast knows"
: "Os casos que todo entusiasta UFO conhece"}
</h2>
</div>
<div className="text-[11px] font-mono text-[#5a6678]">
{cases.length} {locale === "en" ? "stories" : "histórias"}
</div>
</div>
{/* Hero pair */}
<div className="grid md:grid-cols-2 gap-4 md:gap-5 mb-4 md:mb-5">
{[first, second].filter(Boolean).map((c) => (
<HeroCard key={c.slug} c={c} locale={locale} />
))}
</div>
{/* Rest grid */}
{rest.length > 0 && (
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-5">
{rest.map((c) => <CompactCard key={c.slug} c={c} locale={locale} />)}
</div>
)}
</section>
);
}
function HeroCard({ c, locale }: { c: IconicCase; locale: "pt-br" | "en" }) {
const title = locale === "pt-br" ? c.title_pt_br : c.title_en;
const blurb = locale === "pt-br" ? c.blurb_pt_br : c.blurb_en;
return (
<Link
href={c.link}
className="group relative block rounded-2xl overflow-hidden border border-[rgba(224,192,128,0.15)] bg-[#0d1220] aspect-[16/11]"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`${ART_BASE}/${c.image}`}
alt={title}
loading="lazy"
className="absolute inset-0 w-full h-full object-cover group-hover:scale-[1.03] transition-transform duration-700"
/>
<div className="absolute inset-0 bg-gradient-to-t from-[#0a0e1a] via-[#0a0e1a]/40 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-r from-[#0a0e1a]/60 via-transparent to-transparent" />
<div className="absolute bottom-0 left-0 right-0 p-5 md:p-7">
<div className="text-[10px] font-mono uppercase tracking-[0.18em] text-[#e0c080] mb-2">
{c.year} · {c.tags.slice(0, 3).join(" · ")}
</div>
<h3 className="font-display text-2xl md:text-3xl text-white leading-tight mb-2 group-hover:text-[#e0c080] transition-colors">
{title}
</h3>
<p className="text-[13px] md:text-sm text-[#cbd2dd] leading-relaxed line-clamp-3 max-w-prose">
{blurb}
</p>
</div>
</Link>
);
}
function CompactCard({ c, locale }: { c: IconicCase; locale: "pt-br" | "en" }) {
const title = locale === "pt-br" ? c.title_pt_br : c.title_en;
const blurb = locale === "pt-br" ? c.blurb_pt_br : c.blurb_en;
return (
<Link
href={c.link}
className="group block rounded-xl overflow-hidden border border-[rgba(224,192,128,0.15)] bg-[#0d1220] hover:border-[#e0c080]/50 transition-all"
>
<div className="relative aspect-[16/10] overflow-hidden">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`${ART_BASE}/${c.image}`}
alt={title}
loading="lazy"
className="absolute inset-0 w-full h-full object-cover group-hover:scale-[1.05] transition-transform duration-500"
/>
<div className="absolute inset-0 bg-gradient-to-t from-[#0a0e1a]/60 via-transparent to-transparent" />
<div className="absolute top-3 left-3 px-2 py-0.5 rounded bg-[#0a0e1a]/80 text-[10px] font-mono text-[#e0c080]">
{c.year}
</div>
</div>
<div className="p-4 md:p-5">
<h3 className="font-display text-lg md:text-xl text-[#e7ecf3] leading-snug mb-2 group-hover:text-[#e0c080] transition-colors">
{title}
</h3>
<p className="text-[12px] text-[#9aa6b8] leading-relaxed line-clamp-3">
{blurb}
</p>
</div>
</Link>
);
}