f40bb30029
Deploy / deploy (push) Successful in 59s
- Champ `cover` dans lib/games.ts - Branché sur la card home, la liste des jeux et le hero page projet - Fallback placeholder logo si pas d'image Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
210 lines
6.8 KiB
TypeScript
210 lines
6.8 KiB
TypeScript
import Image from "next/image";
|
|
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import type { Metadata } from "next";
|
|
import { games, getGame } from "@/lib/games";
|
|
import { getPostsByGame } from "@/lib/devblog";
|
|
import { SITE_URL } from "@/lib/site";
|
|
|
|
function formatDate(date: string) {
|
|
return new Date(date).toLocaleDateString("en-US", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
});
|
|
}
|
|
|
|
type Props = { params: Promise<{ slug: string }> };
|
|
|
|
export function generateStaticParams() {
|
|
return games.map((g) => ({ slug: g.slug }));
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const game = getGame(slug);
|
|
if (!game) return {};
|
|
return {
|
|
title: game.title,
|
|
description: game.tagline,
|
|
};
|
|
}
|
|
|
|
const statusLabel: Record<string, string> = {
|
|
"in-development": "IN DEVELOPMENT",
|
|
released: "RELEASED",
|
|
"coming-soon": "COMING SOON",
|
|
};
|
|
|
|
export default async function GamePage({ params }: Props) {
|
|
const { slug } = await params;
|
|
const game = getGame(slug);
|
|
if (!game) notFound();
|
|
|
|
const relatedPosts = getPostsByGame(slug).slice(0, 3);
|
|
|
|
const jsonLd = {
|
|
"@context": "https://schema.org",
|
|
"@type": "VideoGame",
|
|
name: game.title,
|
|
description: game.description,
|
|
genre: game.genres,
|
|
publisher: {
|
|
"@type": "Organization",
|
|
name: "Highland Games Studio",
|
|
url: SITE_URL,
|
|
},
|
|
url: `${SITE_URL}/games/${game.slug}`,
|
|
};
|
|
|
|
return (
|
|
<article className="pt-24 pb-24">
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
/>
|
|
{/* HERO */}
|
|
<section className="relative aspect-[21/9] sm:aspect-[21/8] bg-surface border-b border-border overflow-hidden">
|
|
{game.cover ? (
|
|
<Image
|
|
src={game.cover}
|
|
alt={game.title}
|
|
fill
|
|
priority
|
|
sizes="100vw"
|
|
className="object-cover"
|
|
/>
|
|
) : (
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<Image
|
|
src="/image/Studio/Logo Flat.png"
|
|
alt=""
|
|
width={220}
|
|
height={148}
|
|
className="opacity-20"
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/40 to-transparent" />
|
|
<div className="absolute bottom-0 left-0 right-0 px-6 lg:px-10 pb-10">
|
|
<div className="max-w-6xl mx-auto">
|
|
<p className="font-display text-xs tracking-[0.3em] text-accent mb-2">
|
|
{statusLabel[game.status]}
|
|
</p>
|
|
<h1 className="font-display text-5xl sm:text-7xl lg:text-8xl tracking-wider leading-[0.95]">
|
|
{game.title.toUpperCase()}
|
|
</h1>
|
|
<p className="mt-4 text-lg sm:text-xl text-foreground-muted max-w-2xl">
|
|
{game.tagline}
|
|
</p>
|
|
{game.steamUrl && (
|
|
<a
|
|
href={game.steamUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-block mt-6 px-8 py-3 bg-accent text-background font-display tracking-widest text-sm hover:bg-foreground hover:scale-[1.03] transition duration-200"
|
|
>
|
|
WISHLIST ON STEAM →
|
|
</a>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* CONTENT */}
|
|
<div className="max-w-6xl mx-auto px-6 lg:px-10 mt-16 grid lg:grid-cols-3 gap-12">
|
|
<div className="lg:col-span-2">
|
|
<h2 className="font-display text-3xl tracking-wider mb-6">ABOUT</h2>
|
|
<p className="text-foreground-muted leading-relaxed text-lg">
|
|
{game.description}
|
|
</p>
|
|
</div>
|
|
|
|
<aside className="space-y-8">
|
|
<div>
|
|
<h3 className="font-display text-xs tracking-[0.3em] text-accent mb-3">
|
|
GENRES
|
|
</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{game.genres.map((g) => (
|
|
<span
|
|
key={g}
|
|
className="text-xs tracking-widest uppercase border border-border px-3 py-1.5"
|
|
>
|
|
{g}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{game.releaseWindow && (
|
|
<div>
|
|
<h3 className="font-display text-xs tracking-[0.3em] text-accent mb-3">
|
|
RELEASE
|
|
</h3>
|
|
<p className="text-foreground">{game.releaseWindow}</p>
|
|
</div>
|
|
)}
|
|
|
|
{game.steamUrl && (
|
|
<Link
|
|
href={game.steamUrl}
|
|
className="block px-6 py-3 bg-accent text-background font-display tracking-widest text-sm text-center hover:bg-foreground hover:scale-[1.03] transition duration-200"
|
|
>
|
|
WISHLIST ON STEAM
|
|
</Link>
|
|
)}
|
|
</aside>
|
|
</div>
|
|
|
|
{relatedPosts.length > 0 && (
|
|
<section className="max-w-6xl mx-auto px-6 lg:px-10 mt-24">
|
|
<div className="flex items-baseline justify-between mb-8">
|
|
<div>
|
|
<p className="font-display text-xs tracking-[0.3em] text-accent mb-2">
|
|
— DEVBLOG
|
|
</p>
|
|
<h2 className="font-display text-3xl sm:text-4xl tracking-wider">
|
|
LATEST ARTICLES
|
|
</h2>
|
|
</div>
|
|
<Link
|
|
href="/devblog"
|
|
className="hidden sm:inline-block font-display text-xs tracking-widest text-foreground-muted hover:text-accent transition-colors"
|
|
>
|
|
ALL POSTS →
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
{relatedPosts.map((post) => (
|
|
<Link
|
|
key={post.slug}
|
|
href={`/devblog/${post.slug}`}
|
|
className="group block py-6 border-t border-border hover:border-accent transition-colors"
|
|
>
|
|
<div className="flex flex-col sm:flex-row sm:items-baseline sm:justify-between gap-2 mb-2">
|
|
<h3 className="font-display text-xl sm:text-2xl tracking-wider group-hover:text-accent transition-colors">
|
|
{post.title.toUpperCase()}
|
|
</h3>
|
|
<time
|
|
dateTime={post.date}
|
|
className="text-xs tracking-widest text-foreground-muted shrink-0"
|
|
>
|
|
{formatDate(post.date)}
|
|
</time>
|
|
</div>
|
|
{post.excerpt && (
|
|
<p className="text-foreground-muted leading-relaxed max-w-2xl">
|
|
{post.excerpt}
|
|
</p>
|
|
)}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</section>
|
|
)}
|
|
</article>
|
|
);
|
|
}
|