diff --git a/src/app/cards/[slug]/opengraph-image.tsx b/src/app/cards/[slug]/opengraph-image.tsx new file mode 100644 index 0000000..d9bc23a --- /dev/null +++ b/src/app/cards/[slug]/opengraph-image.tsx @@ -0,0 +1,27 @@ +import { cards, getCard, rarityLabel } from '@/data/cards'; +import { OG_CONTENT_TYPE, OG_SIZE, renderCardOg } from '@/lib/og'; + +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; +export const alt = 'Open Source Legends trading card'; + +export function generateStaticParams() { + return cards.map((c) => ({ slug: c.slug })); +} + +export default async function Image({ params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + const card = getCard(slug); + if (!card) throw new Error(`No card for slug ${slug}`); + + return renderCardOg({ + number: card.number, + name: card.name, + title: card.title, + rarity: card.rarity, + rarityLabel: rarityLabel[card.rarity], + impact: card.impact, + front: card.front, + setLabel: 'Series One', + }); +} diff --git a/src/app/cards/[slug]/page.tsx b/src/app/cards/[slug]/page.tsx new file mode 100644 index 0000000..28e80f2 --- /dev/null +++ b/src/app/cards/[slug]/page.tsx @@ -0,0 +1,86 @@ +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import CardDetail from '@/components/CardDetail'; +import { cards, getCard, rarityLabel } from '@/data/cards'; +import { site } from '@/data/site'; + +type Params = { params: Promise<{ slug: string }> }; + +export function generateStaticParams() { + return cards.map((c) => ({ slug: c.slug })); +} + +export async function generateMetadata({ params }: Params): Promise { + const { slug } = await params; + const card = getCard(slug); + if (!card) return { title: 'Card not found' }; + + const title = `${card.name} — ${card.title}`; + const description = `${card.knownFor}. Card #${String(card.number).padStart(3, '0')} in ${site.name} Series One, ${rarityLabel[card.rarity]} rarity, impact ${card.impact}.`; + const url = `${site.url}/cards/${card.slug}`; + + return { + title, + description, + alternates: { canonical: url }, + openGraph: { title, description, url, type: 'profile' }, + twitter: { card: 'summary_large_image', title, description }, + }; +} + +export default async function CardPage({ params }: Params) { + const { slug } = await params; + const card = getCard(slug); + if (!card) notFound(); + + const i = cards.findIndex((c) => c.slug === card.slug); + const prev = i > 0 ? cards[i - 1] : undefined; + const next = i < cards.length - 1 ? cards[i + 1] : undefined; + + const jsonLd = { + '@context': 'https://schema.org', + '@type': 'Person', + name: card.name, + jobTitle: card.title, + description: card.scouting, + nationality: card.nationality, + url: `${site.url}/cards/${card.slug}`, + image: `${site.url}${card.front}`, + subjectOf: { + '@type': 'CreativeWork', + name: `${card.name} — ${site.name} card #${String(card.number).padStart(3, '0')}`, + license: 'https://creativecommons.org/licenses/by-sa/4.0/', + }, + }; + + return ( + <> +