feat: add game cover page with table of contents and dynamic routing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
33
src/app/[game]/page.tsx
Normal file
33
src/app/[game]/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { getAllGames, getGame } from "@/lib/content";
|
||||||
|
import BookCover from "@/components/BookCover";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: { game: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
const games = getAllGames();
|
||||||
|
return games.map((game) => ({ game: game.slug }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }: PageProps) {
|
||||||
|
try {
|
||||||
|
const game = getGame(params.game);
|
||||||
|
return {
|
||||||
|
title: `${game.title} | Cronicas de los Reinos`,
|
||||||
|
description: game.description,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { title: "No encontrado" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GamePage({ params }: PageProps) {
|
||||||
|
try {
|
||||||
|
const game = getGame(params.game);
|
||||||
|
return <BookCover game={game} />;
|
||||||
|
} catch {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/components/BookCover.tsx
Normal file
91
src/components/BookCover.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { GameWithChapters } from "@/types";
|
||||||
|
|
||||||
|
export default function BookCover({ game }: { game: GameWithChapters }) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-parchment text-stone-800">
|
||||||
|
{/* Back to shelf */}
|
||||||
|
<div className="p-4 md:p-6">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex items-center text-stone-500 hover:text-stone-700 font-lora text-sm transition-colors"
|
||||||
|
>
|
||||||
|
<span className="mr-2">←</span> Volver a la estanteria
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cover */}
|
||||||
|
<div className="max-w-2xl mx-auto px-6 py-12 text-center">
|
||||||
|
{/* Decorative top */}
|
||||||
|
<div
|
||||||
|
className="w-24 h-1 mx-auto mb-8 rounded"
|
||||||
|
style={{ backgroundColor: game.accentColor }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
className="font-playfair text-4xl md:text-5xl font-bold mb-3"
|
||||||
|
style={{ color: game.colorDark }}
|
||||||
|
>
|
||||||
|
{game.title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="font-lora italic text-lg mb-8"
|
||||||
|
style={{ color: game.color }}
|
||||||
|
>
|
||||||
|
{game.subtitle}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Decorative divider */}
|
||||||
|
<div className="flex items-center justify-center gap-4 mb-8">
|
||||||
|
<div className="w-16 h-px bg-stone-300" />
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rotate-45"
|
||||||
|
style={{ backgroundColor: game.accentColor }}
|
||||||
|
/>
|
||||||
|
<div className="w-16 h-px bg-stone-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="font-lora text-stone-600 text-base leading-relaxed max-w-lg mx-auto mb-16">
|
||||||
|
{game.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Table of Contents */}
|
||||||
|
<div className="text-left max-w-md mx-auto">
|
||||||
|
<h2 className="font-playfair text-xl font-semibold text-stone-700 mb-6 text-center tracking-wide uppercase">
|
||||||
|
Indice
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
{game.chapters.map((chapter) => (
|
||||||
|
<Link
|
||||||
|
key={chapter.slug}
|
||||||
|
href={`/${game.slug}/${chapter.slug}`}
|
||||||
|
className="flex items-baseline gap-3 py-3 px-2 rounded hover:bg-parchment-dark transition-colors group"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="font-playfair text-sm font-bold min-w-[2rem]"
|
||||||
|
style={{ color: game.color }}
|
||||||
|
>
|
||||||
|
{String(chapter.number).padStart(2, "0")}
|
||||||
|
</span>
|
||||||
|
<span className="font-lora text-stone-700 group-hover:text-stone-900 flex-1">
|
||||||
|
{chapter.title}
|
||||||
|
</span>
|
||||||
|
<span className="text-stone-300 group-hover:text-stone-500 text-sm">
|
||||||
|
→
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Decorative bottom */}
|
||||||
|
<div
|
||||||
|
className="w-24 h-1 mx-auto mt-16 rounded"
|
||||||
|
style={{ backgroundColor: game.accentColor }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user