import Link from "next/link"; import type { Metadata } from "next"; import { PageHeader } from "../components/ui/PageHeader"; import { Section } from "../components/ui/Section"; import { SectionHeading } from "../components/ui/SectionHeading"; import { PostList } from "../components/devblog/PostList"; import { PostFeature } from "../components/devblog/PostFeature"; import { Newsletter } from "../components/forms/Newsletter"; import { getAllPosts } from "@/lib/devblog"; import { games } from "@/lib/games"; export const metadata: Metadata = { title: "Devblog", description: "Behind-the-scenes updates, design decisions, and milestones from Highland Games Studio.", }; const PAGE_SIZE = 10; function buildHref(params: { game?: string; page?: number }) { const search = new URLSearchParams(); if (params.game) search.set("game", params.game); if (params.page && params.page > 1) search.set("page", String(params.page)); const qs = search.toString(); return qs ? `/devblog?${qs}` : "/devblog"; } const filterClass = (active: boolean) => `border px-3 py-1.5 text-xs uppercase tracking-widest transition duration-200 ease-out active:scale-95 ${ active ? "border-accent text-accent" : "border-border text-foreground-muted hover:-translate-y-0.5 hover:border-accent hover:text-accent" }`; type Props = { searchParams: Promise<{ game?: string; page?: string }>; }; export default async function DevblogPage({ searchParams }: Props) { const { game, page } = await searchParams; const allPosts = getAllPosts(); // Only offer a filter for games that actually have posts. const gameOptions = Array.from( new Set(allPosts.map((post) => post.game).filter(Boolean) as string[]), ) .map((slug) => ({ slug, title: games.find((g) => g.slug === slug)?.title ?? slug, })) .sort((a, b) => a.title.localeCompare(b.title)); const filtered = game ? allPosts.filter((post) => post.game === game) : allPosts; const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const currentPage = Math.max(1, Number.parseInt(page ?? "1", 10) || 1); const safePage = Math.min(currentPage, totalPages); const visible = filtered.slice( (safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE, ); // The newest post leads the page, but only where "newest" means anything: // not on page two, and not inside a filtered view. const featured = safePage === 1 && !game ? visible[0] : undefined; const listed = featured ? visible.slice(1) : visible; return ( <> THE JOURNEY,
ONE STEP AT A TIME. } lead="Honest updates from the studio: design decisions, prototypes, art milestones, and everything else that goes into building our worlds." />
{/* One game means the filter offers "All" and "All, again". */} {gameOptions.length > 1 && (
GAME All {gameOptions.map((option) => ( {option.title} ))}
)} {visible.length === 0 && (

{game ? "No posts for this game yet." : "No posts yet. Check back soon."}

)} {featured && } {listed.length > 0 && (
)} {totalPages > 1 && ( )}
); }