"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 (
{games.map((game, idx) => (
router.push(`/game/${game.id}?sync=1`)}
>
{game.capsule_image ? (
) : (
)}
{/* 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 ── */}
{/* ── Landing Sections ── */}
{sectionsLoading ? (
) : (
<>
{sections.recentBenchmarks.length > 0 && (
)}
{sections.trending.length > 0 && (
)}
{sections.mostTested.length > 0 && (
)}
>
)}
>
)
}