"use client"
import { useCallback, useState, useEffect, useMemo, useRef } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import Image from "next/image"
import {
Gamepad2Icon,
MessageSquareIcon,
SettingsIcon,
TrendingUpIcon,
ExternalLinkIcon,
ClockIcon,
SparklesIcon,
DatabaseIcon,
Plus,
ThumbsUpIcon,
ThumbsDownIcon,
GaugeIcon,
ChevronDownIcon,
PencilIcon,
} from "lucide-react"
import Link from "next/link"
import { useSession } from "@/lib/auth-client"
import { FaSteam } from "react-icons/fa"
import { motion, AnimatePresence } from "motion/react"
import type { GameSettingCategory } from "@/lib/db/schema/performanceEntries"
import { PresetDetailModal } from "./preset-detail-modal"
import { BookmarkButton } from "@/components/saved-games/bookmark-button"
import { WindowsIcon, MacIcon, LinuxIcon } from "@/app/components/PlatformIcons"
import { AntiCheatBadge } from "@/components/anti-cheat-badge"
import { PlayabilityBadge } from "@/components/playability-badge"
import { SteamReviews } from "@/components/steam-reviews"
import { CommunitySuggestionForm } from "@/components/community-suggestion-form"
// Chart imports
import { HistoricalAreaChart } from "@/components/charts/HistoricalAreaChart"
import { UpscalerBarChart } from "@/components/charts/UpscalerBarChart"
import { FpsBoxplot } from "@/components/charts/FpsBoxplot"
import { FpsRangeChart } from "@/components/charts/FpsRangeChart"
import { DeviceDonut } from "@/components/charts/DeviceDonut"
import { PerformanceTierChart } from "@/components/charts/PerformanceTierChart"
import { StabilityScatterChart } from "@/components/charts/StabilityScatterChart"
import { BatteryLifeChart } from "@/components/charts/BatteryLifeChart"
// Comments
import { CommentSection } from "@/components/comments/comment-section"
// Types
interface Game {
id: string
steamAppId: number | null
title: string
description: string | null
developer: string | null
publisher: string | null
genres: string[] | null
headerImage: string | null
capsuleImage: string | null
storeUrl: string | null
source: string
slug: string | null
lastSync: string | null
syncStatus: string | null
createdAt: string
metascore?: number | null
onlineMultiplayerStatus?: string | null
systemRequirements: {
minimum: string | null
recommended: string | null
} | null
metacriticScore: number | null
metacriticUrl: string | null
recommendationsTotal: number | null
priceCurrent: number | null
priceInitial: number | null
priceCurrency: string | null
isFree: boolean
releaseDate: string | null
categories: string[] | null
platforms: { windows: boolean; mac: boolean; linux: boolean } | null
playabilityStatus?:
| "great"
| "playable"
| "needs_tweaks"
| "unplayable"
| "unknown"
| null
steamReviewScore?: number | null
steamReviewSentiment?: string | null
steamReviewCount?: number | null
}
interface Counts {
benchmarks: number
presets: number
comments: number
}
interface PlatformSupport {
id: string
gameId: string
hardwareSlug: string
isSupported: boolean
protonStatus: string
playabilityStatus:
| "great"
| "playable"
| "needs_tweaks"
| "unplayable"
| "unknown"
| null
antiCheatRelevant: boolean
antiCheatStatus: "none" | "supported" | "unsupported" | "unknown" | null
antiCheatName: string | null
}
interface Preset {
id: string
gameId?: string
hardwareSlug: string
hardwareName: string
upvotes: number
downvotes: number
settingsJson: GameSettingCategory[] | null
settingsCount: number
fpsAvg: number | null
fpsLow: number | null
fpsHigh: number | null
fpsOnePercentLow: number | null
upscalerType: string | null
upscalerVersion: string | null
frameGenMethod: string | null
protonVersion: string | null
osVersion: string | null
launchOptions: string | null
loadTimeSsd: number | null
loadTimeSd: number | null
tdpWatts: number | null
youtubeVideoId: string | null
screenshots: Array<{
id: string
url: string
width: number
height: number
orderIndex: number
}> | null
customSystem: boolean
userNotes: string | null
userId: string
userName: string | null
userImage: string | null
verifiedAt: string | null
isPinned: boolean
pinnedAt: string | null
createdAt: string
hardwareWattHours: number | null
hardwareDeviceType: string | null
versionString: string | null
buildId: string | null
gameAntiCheatName: string | null
gameAntiCheatStatus: "none" | "supported" | "unsupported" | "unknown" | null
}
interface StatsResponse {
summary: {
totalEntries: number
avgFps: number
bestDevice: string
verifiedCount: number
versionCount: number
avgStability: number | null
bestOnePercentLow: number | null
}
isRawPerformer: boolean
isPoorPerformance: boolean
boxplot: Array<{
hardwareSlug: string
hardwareName: string
min: number
q1: number
median: number
onePercentLow?: number
q3: number
max: number
}>
historical: Array<{
period: string
entries: Array<{
hardwareSlug: string
avgFps: number
count: number
}>
}>
upscalerStats: Array<{
upscalerType: string
upscalerVersion?: string
frameGenMethod: string
hardwareSlug: string
avgFps: number
count: number
}>
fpsRange: Array<{
id: string
hardwareSlug: string
fpsLow: number
fpsAvg: number
fpsHigh: number
fpsOnePercentLow: number | null
isRawPerformer: boolean
}>
deviceBreakdown: Array<{
hardwareSlug: string
hardwareName: string
count: number
wattHours: number | null
tdpMax: number | null
deviceType: string | null
}>
performanceTiers: Array<{
hardwareSlug: string
unplayable: number
playable: number
smooth: number
excellent: number
}>
stabilityScatter: Array<{
id: string
hardwareSlug: string
fpsAvg: number
fpsOnePercentLow: number
stabilityRatio: number
}>
batteryLife: Array<{
id: string
hardwareSlug: string
tdpWatts: number
estimatedBatteryHours: number
wattHours: number | null
tdpMax: number | null
estimatedAtMaxTdpMin: number | null
}>
filterOptions: {
protonVersions: string[]
osVersions: string[]
}
}
interface Props {
game: Game
counts: Counts
platformSupport: PlatformSupport[]
presets: Preset[]
gameId: string
}
const UPSCALER_OPTIONS = ["Any", "None", "FSR", "DLSS", "XeSS", "LSFG", "Other"]
const FRAMEGEN_OPTIONS = ["Any", "None", "FSR FG", "DLSS FG", "LSFG", "Other"]
function formatDate(value: string | null): string {
if (!value) return "—"
return new Date(value).toLocaleDateString()
}
function isRawPerformerPreset(preset: Preset): boolean {
return (
preset.fpsAvg !== null &&
preset.fpsAvg >= 60 &&
preset.upscalerType === "none" &&
preset.frameGenMethod === "none"
)
}
function isPoorPerformancePreset(preset: Preset): boolean {
return preset.fpsAvg !== null && preset.fpsAvg < 30
}
function getFpsColor(preset: Preset): string {
if (preset.fpsAvg === null) return "text-text/60"
if (isRawPerformerPreset(preset)) return "text-green-400"
if (preset.frameGenMethod && preset.frameGenMethod !== "none") {
if (preset.frameGenMethod === "dlss_fg") return "text-blue-400"
return "text-orange-400"
}
if (preset.upscalerType && preset.upscalerType !== "none")
return "text-orange-400"
return "text-text/60"
}
function generatePresetName(preset: Preset): string {
const parts = [preset.hardwareName]
if (preset.fpsAvg !== null) parts.push(`${Math.round(preset.fpsAvg)}fps`)
return parts.join(" · ")
}
function HeroInfo({
game,
stats,
session,
counts,
platformSupport,
protonDbUrl,
steamDbUrl,
gameId,
formatDate,
Badge,
}: {
game: Game
stats: StatsResponse | null
session: { user?: { id?: string } } | null
counts: Counts
platformSupport: PlatformSupport[]
protonDbUrl: string | null
steamDbUrl: string | null
gameId: string
formatDate: (value: string | null) => string
Badge: React.ComponentType<{
icon: React.ElementType
value: number
label: string
}>
}) {
return (
<>
{/* Title row */}
{game.title}
{stats?.isRawPerformer && (
Raw Performer
)}
{stats?.isPoorPerformance && (
⚠ Poor Performance
)}
{/* Playability badge */}
{game.playabilityStatus && (
)}
{/* Anti-cheat badge */}
{platformSupport?.some((p) => p.antiCheatRelevant) && (
p.antiCheatRelevant)
?.antiCheatStatus ?? "unknown"
}
antiCheatName={
platformSupport.find((p) => p.antiCheatRelevant)
?.antiCheatName
}
/>
)}
{/* Steam review score */}
{game.steamReviewScore != null && (
{game.steamReviewScore}% Positive
{game.steamReviewSentiment && (
({game.steamReviewSentiment.replace(/_/g, " ")})
)}
)}
{/* Subtitle */}
{game.developer && {game.developer}}
{game.developer && game.publisher && (
·
)}
{game.publisher && {game.publisher}}
{game.genres && game.genres.length > 0 && (
<>
·
{game.genres.slice(0, 3).join(", ")}
>
)}
{/* Stats badges */}
{game.metascore !== undefined && game.metascore !== null && (
★ {game.metascore}
)}
{/* Metadata pills */}
{game.releaseDate && (
{game.releaseDate}
)}
{game.isFree && (
Free to Play
)}
{game.priceCurrent != null &&
!game.isFree &&
game.priceCurrency && (
{new Intl.NumberFormat("en-US", {
style: "currency",
currency: game.priceCurrency,
}).format(game.priceCurrent / 100)}
)}
{game.priceCurrent != null &&
!game.isFree &&
game.priceInitial != null &&
game.priceInitial > game.priceCurrent &&
game.priceCurrency && (
{new Intl.NumberFormat("en-US", {
style: "currency",
currency: game.priceCurrency,
}).format(game.priceInitial / 100)}
)}
{game.metacriticScore != null && (
★ {game.metacriticScore}/100
)}
{game.onlineMultiplayerStatus && (
{game.onlineMultiplayerStatus}
)}
{game.platforms && (
{game.platforms.windows && (
)}
{game.platforms.mac && (
)}
{game.platforms.linux && (
)}
)}
{game.steamAppId !== null && (
AppID {game.steamAppId}
)}
{/* External links */}
{/* DB stats row */}
{stats && (
{stats.summary.versionCount} versions
·
{stats.summary.verifiedCount} verified
{game.lastSync && (
<>
·
Last sync {formatDate(game.lastSync)}
>
)}
)}
{stats && (
{session?.user && game.source !== "steam" && (
Edit Game
)}
{session && (
Add Benchmark
)}
{game.source !== "steam" && (
)}
)}
>
)
}
export function GamePageClient({
game,
counts,
platformSupport,
presets,
gameId,
}: Props) {
const router = useRouter()
const searchParams = useSearchParams()
const { data: session } = useSession()
const [imgError, setImgError] = useState(false)
const [stats, setStats] = useState(null)
const [selectedDevices, setSelectedDevices] = useState([])
const [filters, setFilters] = useState({
proton: "all",
os: "all",
upscaler: "all",
frameGen: "all",
})
const [loading, setLoading] = useState(true)
const [selectedPresetId, setSelectedPresetId] = useState(
null,
)
const [showSystemReq, setShowSystemReq] = useState(true)
const [reportedPresets, setReportedPresets] = useState>(
new Set(),
)
const presetsRef = useRef(null)
// On mount, auto-open preset from URL
useEffect(() => {
const presetId = searchParams.get("preset")
queueMicrotask(() => {
if (presetId) {
setSelectedPresetId(presetId)
} else {
setSelectedPresetId(null)
}
})
}, [searchParams])
const handlePresetOpen = (presetId: string) => {
setSelectedPresetId(presetId)
const url = new URL(window.location.href)
url.searchParams.set("preset", presetId)
router.replace(url.toString(), { scroll: false })
}
const handlePresetClose = () => {
setSelectedPresetId(null)
const url = new URL(window.location.href)
url.searchParams.delete("preset")
router.replace(url.toString(), { scroll: false })
}
const handleDeletePreset = async (presetId: string) => {
try {
const res = await fetch(
`/api/performance/${presetId}/user-delete`,
{
method: "DELETE",
headers: { "Content-Type": "application/json" },
},
)
if (res.ok) {
setSelectedPresetId(null)
router.refresh()
}
} catch (err) {
console.error("Failed to delete preset:", err)
}
}
const handleReportPreset = async (
presetId: string,
reason: "inaccurate" | "spam" | "inappropriate" | "other",
details?: string,
) => {
try {
const res = await fetch(`/api/performance/${presetId}/report`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ reason, details }),
})
if (res.ok) {
setReportedPresets((prev) => new Set(prev).add(presetId))
}
} catch (err) {
console.error("Failed to report preset:", err)
}
}
const handleImgError = useCallback(() => setImgError(true), [])
// Fetch stats on mount
useEffect(() => {
let cancelled = false
async function fetchStats() {
setLoading(true)
try {
const res = await fetch(`/api/games/${gameId}/stats`)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = await res.json()
if (!cancelled) setStats(data)
} catch (err) {
console.error("Failed to fetch stats:", err)
} finally {
if (!cancelled) setLoading(false)
}
}
fetchStats()
return () => {
cancelled = true
}
}, [gameId])
// Initialize selected devices when stats load
const didInitDevices = useRef(false)
useEffect(() => {
if (stats && !didInitDevices.current) {
didInitDevices.current = true
const slugs = stats.deviceBreakdown.map((d) => d.hardwareSlug)
queueMicrotask(() => setSelectedDevices(slugs))
}
}, [stats])
// Filtered stats
const filteredStats = useMemo(() => {
if (!stats) return null
const deviceSet = new Set(selectedDevices)
const filterByDevice = (
arr: T[] | undefined,
) => arr?.filter((item) => deviceSet.has(item.hardwareSlug)) ?? []
const filterUpscaler = (
arr: StatsResponse["upscalerStats"] | undefined,
) =>
(arr ?? []).filter((item) => {
if (!deviceSet.has(item.hardwareSlug)) return false
if (
filters.upscaler !== "all" &&
item.upscalerType !== filters.upscaler
)
return false
if (
filters.frameGen !== "all" &&
item.frameGenMethod !== filters.frameGen
)
return false
return true
})
return {
...stats,
boxplot: filterByDevice(stats.boxplot),
historical: (stats.historical ?? [])
.map((h) => ({
...h,
entries: h.entries.filter((e) =>
deviceSet.has(e.hardwareSlug),
),
}))
.filter((h) => h.entries.length > 0),
upscalerStats: filterUpscaler(stats.upscalerStats),
fpsRange: filterByDevice(stats.fpsRange),
deviceBreakdown: filterByDevice(stats.deviceBreakdown),
performanceTiers: filterByDevice(stats.performanceTiers),
stabilityScatter: filterByDevice(stats.stabilityScatter),
batteryLife: filterByDevice(stats.batteryLife),
}
}, [stats, selectedDevices, filters])
// Filtered presets
const filteredPresets = useMemo(() => {
return presets.filter((p) => {
if (
selectedDevices.length > 0 &&
!selectedDevices.includes(p.hardwareSlug)
)
return false
if (filters.proton !== "all" && p.protonVersion !== filters.proton)
return false
if (filters.os !== "all" && p.osVersion !== filters.os) return false
if (
filters.upscaler !== "all" &&
p.upscalerType !== filters.upscaler
)
return false
if (
filters.frameGen !== "all" &&
p.frameGenMethod !== filters.frameGen
)
return false
return true
})
}, [presets, selectedDevices, filters])
const pinnedPresets = useMemo(
() => filteredPresets.filter((p) => p.isPinned),
[filteredPresets],
)
const regularPresets = useMemo(
() => filteredPresets.filter((p) => !p.isPinned),
[filteredPresets],
)
const coverImage = game.capsuleImage || game.headerImage
const protonDbUrl = game.steamAppId
? `https://www.protondb.com/app/${game.steamAppId}`
: null
const steamDbUrl = game.steamAppId
? `https://steamdb.info/app/${game.steamAppId}`
: null
const toggleDevice = useCallback((slug: string) => {
setSelectedDevices((prev) =>
prev.includes(slug)
? prev.filter((s) => s !== slug)
: [...prev, slug],
)
}, [])
return (
<>
{/* Section 1: Hero Header */}
{/* Mobile hero — full-bleed background */}
{coverImage && !imgError ? (
) : (
)}
{/* Desktop hero — side-by-side layout */}
{/* Cover image */}
{coverImage && !imgError ? (
) : (
)}
{/* Info */}
{/* Section 2: Overview */}
{/* Description */}
About
{game.description ? (
{game.description}
) : (
No description available.
)}
{game.systemRequirements &&
(game.systemRequirements.minimum ||
game.systemRequirements.recommended) && (
{showSystemReq && (
{game.systemRequirements
.minimum && (
)}
{game.systemRequirements
.recommended && (
)}
)}
)}
{/* Platforms */}
{game.platforms && (
)}
{platformSupport.length > 0 && (
Platform Support
{platformSupport.map((ps) => (
{ps.hardwareSlug.replace(
/-/g,
" ",
)}
{ps.isSupported
? "Supported"
: "Unsupported"}
{ps.protonStatus}
{/* Per-device playability */}
{ps.playabilityStatus && (
)}
{/* Per-device anti-cheat */}
{ps.antiCheatRelevant && (
)}
))}
)}
{/* Section 3: Device Selector + Filters */}
{/* Device ribbon */}
{stats && stats.deviceBreakdown.length > 0 && (
{stats.deviceBreakdown.map((device) => {
const active = selectedDevices.includes(
device.hardwareSlug,
)
return (
)
})}
)}
{/* Fine filters */}
setFilters((f) => ({ ...f, proton: v }))
}
/>
setFilters((f) => ({ ...f, os: v }))
}
/>
o.toLowerCase(),
)}
onChange={(v) =>
setFilters((f) => ({ ...f, upscaler: v }))
}
/>
o.toLowerCase().replace(" ", "_"),
)}
onChange={(v) =>
setFilters((f) => ({ ...f, frameGen: v }))
}
/>
{/* Section 4a: Pinned Entries */}
{pinnedPresets.length > 0 && (
📌 Pinned Entries
{pinnedPresets.length} pinned
{pinnedPresets.map((preset) => {
const raw = isRawPerformerPreset(preset)
const fpsColor = getFpsColor(preset)
return (
handlePresetOpen(preset.id)
}
className={`flex flex-col gap-3 p-4 rounded-xl border transition-colors cursor-pointer hover:border-primary/30 ${
raw
? "border-green-500/30 bg-green-500/5"
: "border-yellow-500/30 bg-yellow-500/5"
}`}
data-gamepad-focusable
>
{/* Header */}
{generatePresetName(
preset,
)}
{raw && (
Raw
)}
{isPoorPerformancePreset(
preset,
) &&
preset.fpsAvg !==
null && (
⚠ Slow
)}
{preset.hardwareName}
{preset.youtubeVideoId && (
)}
{preset.upvotes}
{preset.downvotes}
{/* Settings count */}
{preset.settingsCount} settings
{/* FPS */}
{preset.fpsAvg !== null && (
{preset.fpsAvg}
{" "}
avg
{preset.fpsOnePercentLow !==
null && (
{" "}
·{" "}
{
preset.fpsOnePercentLow
}{" "}
1% low
)}
{preset.fpsLow !== null &&
preset.fpsHigh !==
null &&
!preset.fpsOnePercentLow && (
{" "}
({preset.fpsLow}
–
{preset.fpsHigh}
)
)}
)}
{/* Power / Battery quick-look */}
{(() => {
const dev =
stats?.deviceBreakdown.find(
(d) =>
d.hardwareSlug ===
preset.hardwareSlug,
)
const isHandheld =
dev?.deviceType ===
"handheld"
const wh =
dev?.wattHours ?? null
const tdp =
preset.tdpWatts ?? null
const estHours =
wh && tdp && tdp > 0
? wh / tdp
: null
if (
!isHandheld ||
(!tdp && !wh)
)
return null
return (
{tdp && (
⚡{" "}
{Math.round(
tdp,
)}
W
)}
{wh && (
🔋{" "}
{Math.round(wh)}
Wh
)}
{estHours !== null && (
⏱ ~
{estHours.toFixed(
1,
)}
h
)}
)
})()}
{/* Technology tags */}
{preset.upscalerType &&
preset.upscalerType !==
"none" && (
{preset.upscalerType.toUpperCase()}
{preset.upscalerVersion
? ` ${preset.upscalerVersion}`
: ""}
)}
{preset.frameGenMethod &&
preset.frameGenMethod !==
"none" && (
{preset.frameGenMethod ===
"fsr_fg"
? "FSR FG"
: preset.frameGenMethod ===
"dlss_fg"
? "DLSS FG"
: preset.frameGenMethod}
)}
{/* Proton + OS */}
{preset.protonVersion && (
Proton{" "}
{preset.protonVersion}
)}
{preset.osVersion && (
{preset.osVersion}
)}
)
})}
)}
{/* Section 4b: Performance Entries */}
Performance Entries
{regularPresets.length} entr
{regularPresets.length !== 1 ? "ies" : "y"}
{regularPresets.length === 0 ? (
No entries match the selected filters
) : (
{regularPresets.map((preset) => {
const raw = isRawPerformerPreset(preset)
const fpsColor = getFpsColor(preset)
return (
handlePresetOpen(preset.id)
}
className={`flex flex-col gap-3 p-4 rounded-xl border transition-colors cursor-pointer hover:border-primary/30 ${
raw
? "border-green-500/30 bg-green-500/5"
: "border-border bg-text/3"
}`}
data-gamepad-focusable
>
{/* Header */}
{generatePresetName(
preset,
)}
{raw && (
Raw
)}
{isPoorPerformancePreset(
preset,
) &&
preset.fpsAvg !==
null && (
⚠ Slow
)}
{preset.hardwareName}
{preset.youtubeVideoId && (
)}
{preset.upvotes}
{preset.downvotes}
{/* Settings count */}
{preset.settingsCount} settings
{/* FPS */}
{preset.fpsAvg !== null && (
{preset.fpsAvg}
{" "}
avg
{preset.fpsOnePercentLow !==
null && (
{" "}
·{" "}
{
preset.fpsOnePercentLow
}{" "}
1% low
)}
{preset.fpsLow !== null &&
preset.fpsHigh !==
null &&
!preset.fpsOnePercentLow && (
{" "}
({preset.fpsLow}
–
{preset.fpsHigh}
)
)}
)}
{/* Power / Battery quick-look */}
{(() => {
const dev =
stats?.deviceBreakdown.find(
(d) =>
d.hardwareSlug ===
preset.hardwareSlug,
)
const isHandheld =
dev?.deviceType ===
"handheld"
const wh =
dev?.wattHours ?? null
const tdp =
preset.tdpWatts ?? null
const estHours =
wh && tdp && tdp > 0
? wh / tdp
: null
if (
!isHandheld ||
(!tdp && !wh)
)
return null
return (
{tdp && (
⚡{" "}
{Math.round(
tdp,
)}
W
)}
{wh && (
🔋{" "}
{Math.round(wh)}
Wh
)}
{estHours !== null && (
⏱ ~
{estHours.toFixed(
1,
)}
h
)}
)
})()}
{/* Technology tags */}
{preset.upscalerType &&
preset.upscalerType !==
"none" && (
{preset.upscalerType.toUpperCase()}
{preset.upscalerVersion
? ` ${preset.upscalerVersion}`
: ""}
)}
{preset.frameGenMethod &&
preset.frameGenMethod !==
"none" && (
{preset.frameGenMethod ===
"fsr_fg"
? "FSR FG"
: preset.frameGenMethod ===
"dlss_fg"
? "DLSS FG"
: preset.frameGenMethod}
)}
{/* Proton + OS */}
{preset.protonVersion && (
Proton{" "}
{preset.protonVersion}
)}
{preset.osVersion && (
{preset.osVersion}
)}
)
})}
)}
{/* Section 5: Statistics Dashboard */}
{/* Row 1 — Featured */}
{filteredStats && (
Historical Performance
{filteredStats.historical.length > 0 ? (
) : (
No historical data
)}
{filteredStats.summary.avgStability !==
null && (
)}
)}
{/* Loading state */}
{loading && (
Loading statistics...
)}
{/* Row 2 — Upscaler Bar */}
{filteredStats &&
filteredStats.upscalerStats.length > 0 && (
Upscaler Performance
)}
{/* Row 3 — 3-column grid */}
{filteredStats && (
{filteredStats.boxplot.length > 0 && (
FPS Distribution
)}
{filteredStats.fpsRange.length > 0 && (
FPS Range
)}
{filteredStats.deviceBreakdown.length > 0 && (
Device Breakdown
)}
)}
{/* Row 4 — Performance Tiers + Stability Scatter */}
{filteredStats && (
{filteredStats.performanceTiers.length > 0 && (
)}
{filteredStats.stabilityScatter.length > 0 && (
Avg FPS vs 1% Low (Stability)
[
d.hardwareSlug,
d.hardwareName,
],
),
)}
/>
)}
)}
{/* Battery Life Estimates */}
{filteredStats &&
filteredStats.batteryLife &&
filteredStats.batteryLife.length > 0 && (
Battery Life Estimates
[
d.hardwareSlug,
d.hardwareName,
],
),
)}
/>
)}
{/* Steam Reviews */}
{game.steamAppId && (
)}
{/* Section 6: Comments */}
{/* Mobile FAB for Add Benchmark */}
{session && (
)}
{selectedPresetId &&
(() => {
const preset = filteredPresets.find(
(p) => p.id === selectedPresetId,
)
if (!preset) return null
return (
)
})()}
>
)
}
function Badge({
icon: Icon,
value,
label,
}: {
icon: React.ElementType
value: number
label: string
}) {
return (
{value} {label}
)
}
function StatCard({
label,
value,
icon: Icon,
}: {
label: string
value: string
icon: React.ElementType
}) {
return (
)
}
function FilterSelect({
label,
value,
options,
onChange,
}: {
label: string
value: string
options: string[]
onChange: (value: string) => void
}) {
return (
)
}