"use client" import { AnimatePresence, motion } from "motion/react" import { useState, useEffect } from "react" import Link from "next/link" import Image from "next/image" import { Gamepad2Icon, SearchIcon, TrendingUpIcon, GaugeIcon, ClockIcon, } from "lucide-react" import { useRouter } from "next/navigation" import { PlayabilityBadge } from "@/components/playability-badge" import { cn } from "@/lib/utils" interface GameCard { id: string title: string capsule_image: string | null header_image: string | null playability_status?: string | null activity_score?: number benchmark_count?: number comment_count?: number upvote_count?: number avg_fps?: number report_count?: number release_date?: string | null created_at?: string | null } interface SectionData { recentBenchmarks: GameCard[] trending: GameCard[] mostTested: GameCard[] } function SkeletonSections() { return ( <> {[1, 2, 3].map((i) => (
{[1, 2, 3, 4].map((j) => (
))}
))} ) } function GameSection({ title, icon: Icon, games, statKey, statLabel, statFormatter, accentColor = "text-text/50", muted = false, }: { title: string icon: React.ElementType games: GameCard[] statKey: string statLabel: string statFormatter?: (v: unknown) => string accentColor?: string muted?: boolean }) { const router = useRouter() const formatStat = (v: unknown): string => { if (statFormatter) return statFormatter(v) if (typeof v === "number") return `${Math.round(v)} ${statLabel}` return `${v} ${statLabel}` } return (

{title}

{games.map((game, idx) => ( router.push(`/game/${game.id}?sync=1`)} >
{game.capsule_image ? ( {game.title} ) : (
)} {/* Playability badge pinned at bottom of image */} {game.playability_status && game.playability_status !== "unknown" && (
)}

{game.title}

{/* Performance badges */} {game.avg_fps !== undefined && game.avg_fps !== null && (
{game.avg_fps >= 60 ? ( ⚡ RAW PERFORMER ) : game.avg_fps < 30 ? ( ⚠ POOR PERFORMANCE ) : null} {Math.round(game.avg_fps)}fps
)}

{formatStat( ( game as unknown as Record< string, unknown > )[statKey], )}

))}
) } export default function Landing() { const router = useRouter() const words = ["benchmarks", "settings", "reviews"] const [currentWord, setCurrentWord] = useState(0) const [searchQuery, setSearchQuery] = useState("") // Landing section state const [sections, setSections] = useState({ recentBenchmarks: [], trending: [], mostTested: [], }) const [sectionsLoading, setSectionsLoading] = useState(true) // Animated words cycle — use useEffect with proper cleanup useEffect(() => { const interval = setInterval(() => { setCurrentWord((prev) => (prev + 1) % words.length) }, 2000) return () => clearInterval(interval) }, [words.length]) // Fetch all 4 sections in parallel on mount useEffect(() => { let cancelled = false async function fetchSections() { try { const [recentBenchmarks, trending, mostTested] = await Promise.all([ fetch("/api/dashboard/recent-benchmarks").then((r) => r.ok ? r.json() : [], ), fetch("/api/dashboard/trending").then((r) => r.ok ? r.json() : [], ), fetch("/api/dashboard/most-tested").then((r) => r.ok ? r.json() : [], ), ]) if (!cancelled) { setSections({ recentBenchmarks: Array.isArray(recentBenchmarks) ? recentBenchmarks : [], trending: Array.isArray(trending) ? trending : [], mostTested: Array.isArray(mostTested) ? mostTested : [], }) } } catch { // Silently fail — sections are best-effort } finally { if (!cancelled) setSectionsLoading(false) } } fetchSections() return () => { cancelled = true } }, []) const handleSearchSubmit = () => { if (searchQuery.trim()) { sessionStorage.setItem("focusSearch", "true") router.push(`/search?q=${encodeURIComponent(searchQuery.trim())}`) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { handleSearchSubmit() } } return ( <> {/* ── Hero Section ── */}
DeckyVault beta {"Find your game".split(" ").map((word, index) => ( {word} ))} {words[currentWord]} setSearchQuery(e.target.value)} onKeyDown={handleKeyDown} className='flex-1 outline-none bg-transparent text-xl min-w-0' /> search See what{"'"}s new. 2026 DeckyVault. Github. v{process.env.NEXT_PUBLIC_APP_VERSION}
{/* ── Landing Sections ── */}
{sectionsLoading ? ( ) : ( <> {sections.recentBenchmarks.length > 0 && ( )} {sections.trending.length > 0 && ( )} {sections.mostTested.length > 0 && ( )} )}