feat: sitemap remediation, PWA activation, search refinements, Steam Deck UX

Sitemap:
- Switch from ISR (revalidate=3600) to force-dynamic for per-request generation
- Flatten into single app/sitemap.ts with inlined static entries and DB queries
- NULL-safe syncStatus filter: ne(games.syncStatus, 'failed') OR isNull(games.syncStatus)
- Structured JSON logging for generated URLs and DB errors
- Delete lib/sitemap/* helpers and app/api/revalidate-sitemap route
- Add basic vitest coverage for sitemap exports

PWA & Offline:
- Add @serwist/next service worker (webpack build) with runtime caching
- Cache strategies: stale-while-revalidate for game pages, network-first for listings/API,
  cache-first for Steam CDN images
- Offline fallback page (public/offline.html)
- Manifest icons: 192px maskable + 512px any
- Viewport meta with viewport-fit=cover, user-scalable=no
- Apple mobile web app meta tags

Search / Filter Refinements:
- Multi-genre OR support in listing API (comma-separated genres)
- Device-scoped FPS filter (min/max FPS constrained to selected device)
- Client-side URL state sync via router.replace for shareable filtered views
- Initialize filter state from URL params on mount
- Auto-collapse filter panel on saved-filter load
- Fix multi-genre saved filter parsing (comma-separated)

Steam Deck / Touch / Gamepad UX:
- WCAG 2.1 AA touch targets (44x44px) on all filter controls
- .gamepad-focus CSS focus ring for controller navigation
- useGamepadNavigation hook: D-pad/left-stick roving tabindex, A/B/X/Y actions
- Integrate gamepad hook into games page (X=search, Y=toggle filters)

Chore:
- Bump version to 2026.0.97
This commit is contained in:
2026-05-05 16:20:28 +08:00
parent 9be64359be
commit dcf6066b2b
25 changed files with 1396 additions and 489 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Offline — DeckyVault</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-lexend, 'Lexend', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
background-color: #100b14;
color: #ebe4f1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
text-align: center;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: #eb3779;
margin-bottom: 2rem;
letter-spacing: -0.02em;
}
h1 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
p {
font-size: 0.875rem;
color: #ebe4f1aa;
max-width: 24rem;
line-height: 1.6;
margin-bottom: 2rem;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: #eb3779;
color: #100b14;
font-weight: 600;
font-size: 0.875rem;
border: none;
border-radius: 0.5rem;
cursor: pointer;
text-decoration: none;
transition: background 0.2s;
}
.back-btn:hover { background: #d42d6a; }
.back-btn:active { background: #b8255c; }
</style>
</head>
<body>
<div class="logo">DeckyVault</div>
<h1>You're offline</h1>
<p>
This page isn't available right now. Check your internet connection and try again.
Previously visited game pages may still be available.
</p>
<button class="back-btn" onclick="history.back()">← Go Back</button>
</body>
</html>