diff --git a/.superpowers/brainstorm/26716-1777131188/content/auth-layout.html b/.superpowers/brainstorm/26716-1777131188/content/auth-layout.html
deleted file mode 100644
index a84572e..0000000
--- a/.superpowers/brainstorm/26716-1777131188/content/auth-layout.html
+++ /dev/null
@@ -1,103 +0,0 @@
-
Auth Page Layout
-Centered card layout used across all auth pages (login, signup, forgot-password, reset-password)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
๐ DeckyVault
-
Sign in to your account
-
-
-
-
- ๐ต Google
- ๐ฃ Discord
-
-
-
-
-
-
or continue with email
-
-
-
-
-
- Email
-
-
-
-
-
- Password
-
-
-
-
-
Continue
-
-
-
-
-
-
-
-
-
-
-
-
-
-
๐ DeckyVault
-
Sign in to your account
-
-
-
- ๐ต Google
- ๐ฃ Discord
-
-
-
-
-
-
-
-
Continue
-
-
-
-
-
-
-
-
-
Layout Specs
-
- Container: Full viewport height minus navbar, centered content
- Card: Max-width 400px, glass-morphism style (subtle border, blur backdrop)
- Background: Subtle radial gradient orbs for depth (pink + purple)
- Responsive: Card goes full-width on mobile with reduced padding
- Animations: Card fades in with Motion (consistent with existing navbar pattern)
-
-
\ No newline at end of file
diff --git a/.superpowers/brainstorm/26716-1777131188/content/login-flow.html b/.superpowers/brainstorm/26716-1777131188/content/login-flow.html
deleted file mode 100644
index 48f65ba..0000000
--- a/.superpowers/brainstorm/26716-1777131188/content/login-flow.html
+++ /dev/null
@@ -1,124 +0,0 @@
-Login Flow โ Email-First with Conditional Passkey
-Two-phase login: email check โ password/passkey
-
-
-
Phase 1: Email Entry
-
-
-
-
-
-
Welcome back
-
Sign in to DeckyVault
-
-
-
-
- ๐ต Google
- ๐ฃ Discord
-
-
-
-
-
or continue with email
-
-
-
-
-
Email
-
-
-
Continue
-
-
-
-
-
-
-
- Key: Only email field is shown initially. The autocomplete="username webauthn" attribute enables passkey conditional UI โ browsers with passkey support will offer to autofill passkey credentials.
-
-
-
-
-
-
Phase 2a: Email Exists โ Password Entry
-
-
-
-
-
-
Welcome back
-
Signing in as
user@example.com change
-
-
-
-
Password
-
-
-
-
-
Sign in
-
-
-
-
๐ Your browser may offer to sign in with a passkey
-
-
-
-
-
-
- Conditional UI: The password field has autocomplete="current-password webauthn". On mount, we call authClient.signIn.passkey({ autoFill: true }) to preload passkeys. Browsers that support Conditional UI (Chrome, Safari, Edge) will show a passkey autofill dropdown when the user interacts with the password field.
-
-
-
-
-
-
Phase 2b: Email NOT Found โ Error + Signup Link
-
-
-
-
-
-
Welcome back
-
Sign in to DeckyVault
-
-
-
-
Email
-
-
-
-
-
-
Continue
-
-
-
-
-
-
-
How Email Check Works
-
-
- User enters email and clicks "Continue"
- Client calls authClient.signIn.email() with email + empty password
- Better-auth returns an error if email doesn't exist
- If error: show "No account found" with signup link
- If email exists: transition to password field with email displayed
-
-
- Note: We use signIn.email() as the check mechanism rather than a separate endpoint. This avoids leaking whether an email exists via a dedicated check API.
-
-
-
\ No newline at end of file
diff --git a/.superpowers/brainstorm/26716-1777131188/content/password-reset.html b/.superpowers/brainstorm/26716-1777131188/content/password-reset.html
deleted file mode 100644
index 55aff6f..0000000
--- a/.superpowers/brainstorm/26716-1777131188/content/password-reset.html
+++ /dev/null
@@ -1,127 +0,0 @@
-Password Reset Flow โ Email OTP
-Two-page flow: request reset โ verify OTP + set new password
-
-
-
Page 1: Forgot Password โ Request OTP
-
-
-
-
-
-
๐
-
Forgot your password?
-
Enter your email and we'll send you a verification code to reset your password.
-
-
-
Email
-
-
-
Send verification code
-
-
-
-
-
-
-
- API: Calls authClient.emailOtp.requestPasswordReset({ email }). This triggers the server to send a "forget-password" OTP via the existing sendVerificationOTP handler.
-
-
-
-
-
-
Page 2: Reset Password โ OTP + New Password
-
-
-
-
-
-
๐ง
-
Check your email
-
We sent a 6-digit code to user@example.com
-
-
-
-
Verification code
-
-
_
-
_
-
_
-
_
-
_
-
_
-
-
-
- Resend code in
- 4:23
-
-
-
-
-
-
then set new password
-
-
-
-
-
New password
-
-
-
-
-
-
-
Confirm new password
-
-
-
Reset password
-
-
-
-
-
-
-
-
-
Success State
-
-
-
-
-
โ
-
Password reset successful
-
Your password has been updated. You can now sign in with your new password.
-
Sign in
-
-
-
-
-
-
-
API Flow
-
-
- User enters email on /forgot-password
- Client calls authClient.emailOtp.requestPasswordReset({ email })
- Server sends OTP with type "forget-password" via sendVerificationOTP
- User is redirected to /reset-password?email=user@example.com
- User enters OTP + new password + confirm password
- Client calls authClient.emailOtp.checkVerificationOtp({ email, otp, type: "forget-password" }) (optional validation)
- Client calls authClient.resetPassword({ newPassword, otp })
- On success: show success screen, then redirect to /login
-
-
-
\ No newline at end of file
diff --git a/.superpowers/brainstorm/26716-1777131188/content/signup-wizard.html b/.superpowers/brainstorm/26716-1777131188/content/signup-wizard.html
deleted file mode 100644
index bfd1a22..0000000
--- a/.superpowers/brainstorm/26716-1777131188/content/signup-wizard.html
+++ /dev/null
@@ -1,192 +0,0 @@
-Signup Wizard โ Step-by-Step Flow
-3 steps for email signup, 2 steps for social signup
-
-
-
Step 1: Account Creation
-
-
-
-
-
-
-
-
-
Create your account
-
Choose how you'd like to sign up
-
-
-
-
- ๐ต Google
- ๐ฃ Discord
-
-
-
-
-
-
Email
-
-
-
Password
-
-
-
-
-
-
Weak โ add uppercase, number, special char
-
-
-
Continue
-
-
-
Already have an account?
-
Sign in
-
-
-
-
-
-
-
-
Step 2: Email Verification (OTP)
-
-
-
-
-
-
-
-
-
Verify your email
-
We sent a 6-digit code to user@example.com
-
-
-
-
-
4
-
7
-
_
-
_
-
_
-
_
-
-
-
-
- Resend code in
- 4:23
-
-
-
Verify
-
-
-
-
-
-
-
-
-
Step 3: Passkey Setup (Optional)
-
-
-
-
-
-
-
-
-
๐
-
Set up a passkey
-
Sign in faster with biometrics or your device's security key. No password needed.
-
-
-
-
-
- โ Faster sign-in with fingerprint or face
-
-
- โ More secure than passwords
-
-
- โ Works across your devices
-
-
-
-
Set up passkey
-
Skip for now
-
-
-
-
-
-
-
Social Signup Flow (2 steps)
-
-
- Step 1: User clicks Google/Discord โ OAuth redirect โ authenticated
- Step 2: Passkey setup (optional, same as Step 3 above) โ Done
- OTP step is skipped โ email is already verified by the OAuth provider.
-
-
-
-
-
-
Password Strength Meter โ States
-
-
-
-
Weak โ add 8+ chars, uppercase, number, special char
-
-
-
-
Fair โ add a special character and more length
-
-
-
-
Strong โ great password!
-
-
-
\ No newline at end of file
diff --git a/.superpowers/brainstorm/26716-1777131188/content/waiting.html b/.superpowers/brainstorm/26716-1777131188/content/waiting.html
deleted file mode 100644
index f92c257..0000000
--- a/.superpowers/brainstorm/26716-1777131188/content/waiting.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
Continuing in terminal...
-
\ No newline at end of file
diff --git a/.superpowers/brainstorm/26716-1777131188/state/server-stopped b/.superpowers/brainstorm/26716-1777131188/state/server-stopped
deleted file mode 100644
index 65871d1..0000000
--- a/.superpowers/brainstorm/26716-1777131188/state/server-stopped
+++ /dev/null
@@ -1 +0,0 @@
-{"reason":"idle timeout","timestamp":1777134909078}
diff --git a/.superpowers/brainstorm/26716-1777131188/state/server.log b/.superpowers/brainstorm/26716-1777131188/state/server.log
deleted file mode 100644
index abb8131..0000000
--- a/.superpowers/brainstorm/26716-1777131188/state/server.log
+++ /dev/null
@@ -1,7 +0,0 @@
-{"type":"server-started","port":54158,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:54158","screen_dir":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/content","state_dir":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/state"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/content/auth-layout.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/content/signup-wizard.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/content/login-flow.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/content/password-reset.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/26716-1777131188/content/waiting.html"}
-{"type":"server-stopped","reason":"idle timeout"}
diff --git a/.superpowers/brainstorm/26716-1777131188/state/server.pid b/.superpowers/brainstorm/26716-1777131188/state/server.pid
deleted file mode 100644
index 35e769c..0000000
--- a/.superpowers/brainstorm/26716-1777131188/state/server.pid
+++ /dev/null
@@ -1 +0,0 @@
-26725
diff --git a/.superpowers/brainstorm/44094-1777186296/content/badge-naming.html b/.superpowers/brainstorm/44094-1777186296/content/badge-naming.html
deleted file mode 100644
index a25f64a..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/badge-naming.html
+++ /dev/null
@@ -1,33 +0,0 @@
-Badge Naming โ "Native Champion" Alternatives
-This badge marks games/presets that hit 60+ FPS with zero FSR and zero frame generation. Which name fits the brand best?
-
-
-
-
A
-
-
Raw Performer
-
Emphasizes unassisted performance. Technical, no-nonsense. "Raw" signals no upscaling tricks.
-
-
-
-
B
-
-
Pure Performance
-
Clean and clear. "Pure" = untouched settings. Easy to understand at a glance.
-
-
-
-
C
-
-
Native Champion
-
Your original idea. "Champion" has a competitive/gaming feel. Slightly longer.
-
-
-
-
D
-
-
Untouched
-
Short, punchy. Implies the game runs great without any modifications. Memorable.
-
-
-
diff --git a/.superpowers/brainstorm/44094-1777186296/content/game-page-layout.html b/.superpowers/brainstorm/44094-1777186296/content/game-page-layout.html
deleted file mode 100644
index 9fc9326..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/game-page-layout.html
+++ /dev/null
@@ -1,149 +0,0 @@
-Game Page Layout โ Overall Structure
-Here's the proposed page structure top-to-bottom. Each section is labeled.
-
-
-
-
-
-
-
- 2:3 Cover
-
-
-
-
Game Title Here
-
Developer ยท Publisher ยท Genre, Genre
-
-
- ๐ฎ 42 Benchmarks
- โ๏ธ 8 Presets
- ๐ฌ 15 Comments
- Metascore: 87
- $29.99
-
-
-
- Steam โ
- ProtonDB โ
- SteamDB โ
-
-
-
- ๐ 42 benchmarks
- ๐ 3 game versions
- โ
12 verified entries
- ๐ Last updated 2 days ago
-
-
-
-
-
-
-
-
-
-
-
-
About
-
Game description text goes here. This is the Steam short description that gets synced from the store page. It provides context about what the game is about...
-
-
-
Details
-
-
Source: Steam
-
AppID: 1234567
-
Added: Jan 15, 2026
-
Last Sync: 2 days ago
-
-
-
-
-
-
-
-
-
-
-
Select Devices
-
-
โ Steam Deck OLED
-
Steam Deck LCD
-
Steam Machine
-
-
-
Filters
-
-
- All Proton Versions
-
-
- All SteamOS Versions
-
-
- FSR: Any
-
-
- Frame Gen: Any
-
-
-
-
-
-
-
-
-
-
-
-
AVG FPS Distribution (Boxplot)
-
- [Boxplot: Min / Q1 / Median / Q3 / Max per device]
-
-
-
-
-
Historical AVG FPS (Stacked Area)
-
- [Area chart: FPS over time by proton/OS version]
-
-
-
-
-
FPS by Upscaler & Frame Gen
-
- [Grouped bar: FPS by FSR version ร frame gen method]
-
-
-
-
-
Entries per Device
-
- [Pie/donut or bar: count per device]
-
-
-
-
-
-
-
-
-
-
Community Presets
-
-
-
-
"Best Quality" by @user1
-
Steam Deck OLED ยท โ 24 ยท 8 settings
-
-
Avg 45 FPS
-
-
-
-
"Battery Saver" by @user2
-
Steam Deck OLED ยท โ 18 ยท 6 settings
-
-
Avg 60 FPS
-
-
-
-
diff --git a/.superpowers/brainstorm/44094-1777186296/content/game-page-v2.html b/.superpowers/brainstorm/44094-1777186296/content/game-page-v2.html
deleted file mode 100644
index 5eee674..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/game-page-v2.html
+++ /dev/null
@@ -1,223 +0,0 @@
-Game Page v2 โ Updated Layout
-Changes: "Raw Performer" badge, no duplicate stats, full-width priority charts, enhanced preset cards.
-
-
-
-
-
-
-
-
-
-
- 2:3 Cover
-
-
-
-
- Game Title Here
-
- โก RAW PERFORMER
-
-
Developer ยท Publisher ยท Genre, Genre
-
-
- ๐ฎ 42 Benchmarks
- โ๏ธ 8 Presets
- ๐ฌ 15 Comments
- Metascore: 87
- $29.99
-
-
-
- Steam โ
- ProtonDB โ
- SteamDB โ
-
-
-
- ๐ 3 versions
- โ
12 verified
- ๐ Synced 2 days ago
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
About
-
Game description text goes here. This is the Steam short description that gets synced from the store page...
-
-
-
Details
-
-
Source: Steam
-
AppID: 1234567
-
Added: Jan 15, 2026
-
Last Sync: 2 days ago
-
-
-
-
-
-
-
-
-
-
-
-
-
Select Devices
-
-
โ Steam Deck OLED
-
Steam Deck LCD
-
-
-
Filters
-
- All Proton Versions
- All SteamOS Versions
- FSR: Any
- Frame Gen: Any
-
-
-
-
-
-
-
-
-
-
-
-
-
-
๐ Historical AVG FPS โ Full Width
-
- [Stacked Area: AVG FPS trend over time, grouped by Proton/SteamOS version]
-
-
-
-
-
-
๐ฎ FPS by Upscaler & Frame Gen โ Full Width
-
- [Grouped Bar: FPS by FSR version (none/1/2/3) ร Frame Gen (none/fsr_fg/dlss_fg), with label rotation]
-
-
-
-
-
-
-
๐ AVG FPS Boxplot
-
- [Boxplot per device: Min/Q1/Median/Q3/Max]
-
-
-
-
๐ฅ๏ธ Entries per Device
-
- [Donut: benchmark count per device]
-
-
-
-
-
-
-
-
๐ FPS Range (Low vs High)
-
- [Range bar: fpsLow โ fpsHigh per entry]
-
-
-
-
โฑ๏ธ Load Times (SSD vs SD)
-
- [Bar: SSD vs SD load times โ only shows if data exists]
-
-
-
-
-
-
-
โ
Community Trust โ Full Width
-
- [Horizontal bar: verified badge count + upvote/downvote ratio per benchmark entry]
-
-
-
-
-
-
-
-
-
-
-
-
Community Presets
-
-
-
-
-
-
- "Vanilla 60fps"
- โก RAW PERFORMER
-
-
by @user1 ยท Steam Deck OLED ยท โ 24 votes ยท 8 settings
-
No FSR ยท No Frame Gen ยท Proton 9.0-4 ยท SteamOS 3.6.20
-
-
-
62 FPS
-
avg ยท 48 low ยท 71 high
-
-
-
-
-
-
-
-
-
- "Max Quality + FSR"
-
-
by @user2 ยท Steam Deck OLED ยท โ 18 votes ยท 10 settings
-
- FSR 3 ยท FSR Frame Gen ยท Proton 9.0-4 ยท SteamOS 3.6.20
-
-
-
-
85 FPS
-
avg ยท 72 low ยท 94 high
-
-
-
-
-
-
-
-
-
- "Balanced + DLSS FG"
-
-
by @user3 ยท Steam Deck LCD ยท โ 9 votes ยท 7 settings
-
- FSR 2 ยท DLSS Frame Gen ยท Proton 8.0-5 ยท SteamOS 3.5.19
-
-
-
-
72 FPS
-
avg ยท 58 low ยท 81 high
-
-
-
-
-
diff --git a/.superpowers/brainstorm/44094-1777186296/content/search-badge.html b/.superpowers/brainstorm/44094-1777186296/content/search-badge.html
deleted file mode 100644
index 9762868..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/search-badge.html
+++ /dev/null
@@ -1,60 +0,0 @@
-Search Card โ Raw Performer Badge
-How the badge appears on search result cards. Same green pill style as the game page hero.
-
-
-
-
-
-
-
-
- 2:3
-
-
-
-
-
- Baldur's Gate 3
- โก RAW PERFORMER
-
-
Larian Studios ยท Publisher
-
Gather your party and return to the Forgotten Realms in a tale of fellowship and betrayal...
-
-
- RPG
- Strategy
-
-
-
- ๐ฎ 28 Benchmarks
- โ๏ธ 5 Presets
- ๐ฌ 12 Comments
-
-
-
-
-
-
-
- 2:3
-
-
-
- Cyberpunk 2077
-
-
-
CD Projekt Red ยท Publisher
-
An open-world action-adventure story set in Night City...
-
- RPG
- Action
-
-
- ๐ฎ 45 Benchmarks
- โ๏ธ 12 Presets
- ๐ฌ 34 Comments
-
-
-
-
-
diff --git a/.superpowers/brainstorm/44094-1777186296/content/search-card-final.html b/.superpowers/brainstorm/44094-1777186296/content/search-card-final.html
deleted file mode 100644
index 7bf2b75..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/search-card-final.html
+++ /dev/null
@@ -1,130 +0,0 @@
-Search Card โ Final Design
-Existing layout preserved. Added: Raw Performer badge, real Avg FPS, real Version. No other layout changes.
-
-
-
-
-
-
-
- 2:3
-
-
-
-
-
- Baldur's Gate 3
- โก RAW PERFORMER
-
-
Larian Studios ยท Publisher
-
Gather your party and return to the Forgotten Realms...
-
-
- RPG
- Strategy
-
-
-
- ๐ฎ 28 Benchmarks
- โ๏ธ 5 Presets
- ๐ฌ 12 Comments
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2:3
-
-
-
-
Cyberpunk 2077
-
CD Projekt Red ยท Publisher
-
An open-world action-adventure story set in Night City...
-
- RPG
-
-
- ๐ฎ 45 Benchmarks
- โ๏ธ 12 Presets
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2:3
-
-
-
Elden Ring
-
FromSoftware ยท Bandai Namco
-
THE NEW FANTASY ACTION RPG...
-
- RPG
- Action
-
-
-
- Steam โ
- ProtonDB โ
- SteamDB โ
-
-
-
-
-
-
-
diff --git a/.superpowers/brainstorm/44094-1777186296/content/stats-layouts.html b/.superpowers/brainstorm/44094-1777186296/content/stats-layouts.html
deleted file mode 100644
index b3928f9..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/stats-layouts.html
+++ /dev/null
@@ -1,191 +0,0 @@
-Stats Dashboard โ Layout Alternatives
-Three layout options for the statistics section. Click the one that feels best.
-
-
-
-
-
-
-
-
A
-
Featured Row + Asymmetric Grid
-
Top chart gets a larger container with summary stats beside it. Remaining charts in a flowing grid.
-
-
-
-
-
-
๐ Historical AVG FPS FEATURED
-
[Stacked Area Chart โ full width of container]
-
-
-
-
-
-
-
๐ฎ FPS by Upscaler & Frame Gen FULL
-
[Grouped Bar โ FSR version ร Frame Gen method]
-
-
-
-
-
-
๐ Boxplot
-
[FPS distribution per device]
-
-
-
๐ FPS Range
-
[Low โ High per entry]
-
-
-
๐ฅ๏ธ Devices
-
[Donut: entries per device]
-
-
-
-
-
-
โ
Community Trust FULL
-
[Horizontal bar: verified + vote ratio per entry]
-
-
-
-
-
-
-
-
-
B
-
Dashboard Cards with KPI Strip
-
Quick-glance KPI numbers at top, then a clean 2ร2 grid of charts, with full-width charts breaking the rhythm where needed.
-
-
-
-
-
-
-
-
๐ Historical AVG FPS FULL
-
[Stacked Area]
-
-
-
-
-
-
๐ฎ FPS by Upscaler & Frame Gen
-
[Grouped Bar]
-
-
-
๐ FPS Distribution (Boxplot)
-
[Boxplot per device]
-
-
-
๐ FPS Range
-
[Low โ High]
-
-
-
๐ฅ๏ธ Entries per Device
-
[Donut]
-
-
-
-
-
-
โ
Community Trust FULL
-
[Horizontal bar]
-
-
-
-
-
-
-
-
-
C
-
Sectioned with Visual Separator
-
Divides stats into logical groups with subtle dividers. Each group has a label. Full-width charts anchor each section.
-
-
-
-
-
Performance Overview
-
-
๐ Historical AVG FPS FULL
-
[Stacked Area]
-
-
-
-
๐ FPS Distribution
-
[Boxplot]
-
-
-
๐ FPS Range
-
[Low โ High]
-
-
-
-
-
-
-
Technology Impact
-
-
๐ฎ FPS by Upscaler & Frame Gen FULL
-
[Grouped Bar]
-
-
-
-
-
-
Data Coverage & Trust
-
-
-
๐ฅ๏ธ Devices
-
[Donut]
-
-
-
โ
Community Trust FULL
-
[Horizontal bar]
-
-
-
-
-
diff --git a/.superpowers/brainstorm/44094-1777186296/content/stats-priority.html b/.superpowers/brainstorm/44094-1777186296/content/stats-priority.html
deleted file mode 100644
index 24d8f04..0000000
--- a/.superpowers/brainstorm/44094-1777186296/content/stats-priority.html
+++ /dev/null
@@ -1,63 +0,0 @@
-Stats Dashboard โ Chart Priority
-Which charts should be full-width (high importance) vs half-width? Select all that you want full-width.
-
-
-
-
๐
-
-
AVG FPS Boxplot
-
Min / Q1 / Median / Q3 / Max per device. Shows spread and outliers. Core metric.
-
-
-
-
๐
-
-
Historical AVG FPS (Stacked Area)
-
FPS trend over time by Proton/SteamOS version. Shows if updates help or hurt performance.
-
-
-
-
๐ฎ
-
-
FPS by Upscaler & Frame Gen (Grouped Bar)
-
Compare FPS across FSR versions ร frame gen methods. Shows the real cost/benefit of each combo.
-
-
-
-
๐ฅ๏ธ
-
-
Entries per Device (Donut)
-
Simple count of benchmarks per hardware. Shows data coverage.
-
-
-
-
๐
-
-
FPS Range: Low vs High (Range Bar)
-
Visualizes fpsLow to fpsHigh spread per entry. Shows consistency โ tight range = stable performance.
-
-
-
-
โฑ๏ธ
-
-
Load Times: SSD vs SD (Bar)
-
Compare storage load times. Only shows when data exists. Useful for games with long loads.
-
-
-
-
๐ง
-
-
Proton Version Breakdown (Stacked Bar)
-
Distribution of entries by Proton version. Shows which Proton builds are most tested.
-
-
-
-
โ
-
-
Community Trust: Verified & Votes (Horizontal Bar)
-
Shows verified count + upvote/downvote ratio per entry. Helps identify the most trusted benchmarks.
-
-
-
-
-Charts you select will be full-width (one per row). Unselected charts will be half-width (2 per row).
diff --git a/.superpowers/brainstorm/44094-1777186296/state/server-stopped b/.superpowers/brainstorm/44094-1777186296/state/server-stopped
deleted file mode 100644
index f1eba36..0000000
--- a/.superpowers/brainstorm/44094-1777186296/state/server-stopped
+++ /dev/null
@@ -1 +0,0 @@
-{"reason":"idle timeout","timestamp":1777190496647}
diff --git a/.superpowers/brainstorm/44094-1777186296/state/server.log b/.superpowers/brainstorm/44094-1777186296/state/server.log
deleted file mode 100644
index 4f016ae..0000000
--- a/.superpowers/brainstorm/44094-1777186296/state/server.log
+++ /dev/null
@@ -1,16 +0,0 @@
-{"type":"server-started","port":49813,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:49813","screen_dir":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content","state_dir":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/state"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/game-page-layout.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/badge-naming.html"}
-{"source":"user-event","type":"click","text":"A\n \n Raw Performer\n Emphasizes unassisted performance. Technical, no-nonsense. \"Raw\" signals no upscaling tricks.","choice":"raw-performer","id":null,"timestamp":1777186841487}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/stats-priority.html"}
-{"source":"user-event","type":"click","text":"๐\n \n Historical AVG FPS (Stacked Area)\n FPS trend over time by Proton/SteamOS version. Shows if updates help or hurt performance.","choice":"historical","id":null,"timestamp":1777186897413}
-{"source":"user-event","type":"click","text":"โ
\n \n Community Trust: Verified & Votes (Horizontal Bar)\n Shows verified count + upvote/downvote ratio per entry. Helps identify the most trusted benchmarks.","choice":"verified-sentiment","id":null,"timestamp":1777186960801}
-{"source":"user-event","type":"click","text":"๐ฅ๏ธ\n \n Entries per Device (Donut)\n Simple count of benchmarks per hardware. Shows data coverage.","choice":"device-pie","id":null,"timestamp":1777186972946}
-{"source":"user-event","type":"click","text":"๐ฅ๏ธ\n \n Entries per Device (Donut)\n Simple count of benchmarks per hardware. Shows data coverage.","choice":"device-pie","id":null,"timestamp":1777186978613}
-{"source":"user-event","type":"click","text":"๐ฎ\n \n FPS by Upscaler & Frame Gen (Grouped Bar)\n Compare FPS across FSR versions ร frame gen methods. Shows the real cost/benefit of each combo.","choice":"upscaler-bar","id":null,"timestamp":1777186980875}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/game-page-v2.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/stats-layouts.html"}
-{"source":"user-event","type":"click","text":"A\n Featured Row + Asymmetric Grid\n Top chart gets a larger container with summary stats beside it. Remaining charts in a flowing grid.\n\n \n \n \n \n ๐ Historical AVG FPS FEATURED\n [Stacked Area Chart โ full width of container]\n \n \n \n Overall Avg\n 54 FPS\n \n \n Best Device\n OLED\n \n \n Entries\n 42\n \n \n \n\n \n \n ๐ฎ FPS by Upscaler & Frame Gen FULL\n [Grouped Bar โ FSR version ร Frame Gen method]\n \n\n \n \n \n ๐ Boxplot\n [FPS distribution per device]\n \n \n ๐ FPS Range\n [Low โ High per entry]\n \n \n ๐ฅ๏ธ Devices\n [Donut: entries per device]\n \n \n\n \n \n โ
Community Trust FULL\n [Horizontal bar: verified + vote ratio per entry]","choice":"a","id":null,"timestamp":1777187409898}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/search-badge.html"}
-{"type":"screen-added","file":"/Users/adrianbonpin/Documents/Code/personal/deckyvault/.superpowers/brainstorm/44094-1777186296/content/search-card-final.html"}
-{"type":"server-stopped","reason":"idle timeout"}
diff --git a/.superpowers/brainstorm/44094-1777186296/state/server.pid b/.superpowers/brainstorm/44094-1777186296/state/server.pid
deleted file mode 100644
index 9442c77..0000000
--- a/.superpowers/brainstorm/44094-1777186296/state/server.pid
+++ /dev/null
@@ -1 +0,0 @@
-44103
diff --git a/app/game/[id]/game-page-client.tsx b/app/game/[id]/game-page-client.tsx
index 6626c9d..63402cb 100644
--- a/app/game/[id]/game-page-client.tsx
+++ b/app/game/[id]/game-page-client.tsx
@@ -367,7 +367,7 @@ export function GamePageClient({
{session && (
Add Benchmark
@@ -382,7 +382,7 @@ export function GamePageClient({
href={game.storeUrl}
target="_blank"
rel="noopener noreferrer"
- className="inline-flex items-center gap-1 text-xs text-text/60 hover:text-primary transition-colors"
+ className="inline-flex items-center gap-1 text-xs text-text/60 hover:text-primary transition-colors cursor-pointer"
>
Steam
@@ -394,7 +394,7 @@ export function GamePageClient({
href={protonDbUrl}
target="_blank"
rel="noopener noreferrer"
- className="inline-flex items-center gap-1 text-xs text-text/60 hover:text-primary transition-colors"
+ className="inline-flex items-center gap-1 text-xs text-text/60 hover:text-primary transition-colors cursor-pointer"
>
ProtonDB
@@ -405,7 +405,7 @@ export function GamePageClient({
href={steamDbUrl}
target="_blank"
rel="noopener noreferrer"
- className="inline-flex items-center gap-1 text-xs text-text/60 hover:text-primary transition-colors"
+ className="inline-flex items-center gap-1 text-xs text-text/60 hover:text-primary transition-colors cursor-pointer"
>
SteamDB
@@ -537,7 +537,7 @@ export function GamePageClient({
toggleDevice(device.hardwareSlug)}
- className={`shrink-0 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors ${
+ className={`shrink-0 px-3 py-1.5 rounded-full text-xs font-medium border transition-colors cursor-pointer ${
active
? "bg-primary/20 border-primary text-primary"
: "bg-transparent border-border text-text/60 hover:text-text/80"
diff --git a/app/not-found.tsx b/app/not-found.tsx
index 0a5a32b..76f58b2 100644
--- a/app/not-found.tsx
+++ b/app/not-found.tsx
@@ -35,7 +35,7 @@ export default function NotFound() {
>
Back to Home
diff --git a/app/page.tsx b/app/page.tsx
index 831a1ce..86cddad 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -111,7 +111,7 @@ export default function Landing() {
Github.
diff --git a/app/profile/page.tsx b/app/profile/page.tsx
index 9d21c20..6d338c5 100644
--- a/app/profile/page.tsx
+++ b/app/profile/page.tsx
@@ -18,179 +18,215 @@ type Tab = "overview" | "saved" | "settings"
type SettingsTab = "profile" | "security" | "accounts"
export default function ProfilePage() {
- const router = useRouter()
- const { data: session, isPending: isSessionLoading } = useSession()
- const [activeTab, setActiveTab] = useState("overview")
- const [settingsSubTab, setSettingsSubTab] = useState("profile")
- const [profile, setProfile] = useState<{
- id: string
- name: string
- email: string
- image: string | null
- role: string | null
- createdAt: string
- contributions: number
- verifiedEntries: number
- reputation: number
- verified: boolean
- } | null>(null)
- const [contributions, setContributions] = useState([])
- const [isLoading, setIsLoading] = useState(true)
+ const router = useRouter()
+ const { data: session, isPending: isSessionLoading } = useSession()
+ const [activeTab, setActiveTab] = useState("overview")
+ const [settingsSubTab, setSettingsSubTab] = useState("profile")
+ const [profile, setProfile] = useState<{
+ id: string
+ name: string
+ email: string
+ image: string | null
+ role: string | null
+ createdAt: string
+ contributions: number
+ verifiedEntries: number
+ reputation: number
+ verified: boolean
+ } | null>(null)
+ const [contributions, setContributions] = useState([])
+ const [isLoading, setIsLoading] = useState(true)
- useEffect(() => {
- if (!isSessionLoading && !session) {
- router.push("/login")
- }
- }, [session, isSessionLoading, router])
-
- useEffect(() => {
- if (!session) return
-
- async function fetchProfile() {
- try {
- const [profileRes, contribRes] = await Promise.all([
- fetch("/api/user/me"),
- fetch("/api/user/profile/" + session!.user.id + "/contributions?limit=10"),
- ])
-
- if (profileRes.ok) {
- setProfile(await profileRes.json())
+ useEffect(() => {
+ if (!isSessionLoading && !session) {
+ router.push("/login")
}
- if (contribRes.ok) {
- const data = await contribRes.json()
- setContributions(data.data)
+ }, [session, isSessionLoading, router])
+
+ useEffect(() => {
+ if (!session) return
+
+ async function fetchProfile() {
+ try {
+ const [profileRes, contribRes] = await Promise.all([
+ fetch("/api/user/me"),
+ fetch(
+ "/api/user/profile/" +
+ session!.user.id +
+ "/contributions?limit=10",
+ ),
+ ])
+
+ if (profileRes.ok) {
+ setProfile(await profileRes.json())
+ }
+ if (contribRes.ok) {
+ const data = await contribRes.json()
+ setContributions(data.data)
+ }
+ } catch (err) {
+ console.error("Failed to fetch profile:", err)
+ } finally {
+ setIsLoading(false)
+ }
}
- } catch (err) {
- console.error("Failed to fetch profile:", err)
- } finally {
- setIsLoading(false)
- }
+
+ fetchProfile()
+ }, [session])
+
+ if (isSessionLoading || isLoading) {
+ return (
+
+
+
+ )
}
- fetchProfile()
- }, [session])
+ if (!session || !profile) {
+ return null
+ }
+
+ const tabs: { id: Tab; label: string; icon: typeof Bookmark }[] = [
+ { id: "overview", label: "Overview", icon: TrendingUp },
+ { id: "saved", label: "Saved Games", icon: Bookmark },
+ { id: "settings", label: "Settings", icon: Settings },
+ ]
- if (isSessionLoading || isLoading) {
return (
-
-
-
- )
- }
-
- if (!session || !profile) {
- return null
- }
-
- const tabs: { id: Tab; label: string; icon: typeof Bookmark }[] = [
- { id: "overview", label: "Overview", icon: TrendingUp },
- { id: "saved", label: "Saved Games", icon: Bookmark },
- { id: "settings", label: "Settings", icon: Settings },
- ]
-
- return (
-
- {/* Profile header section */}
-
-
-
-
- {/* Stats section */}
-
-
-
-
-
-
- {/* Tabs + content section */}
-
-
- {/* Tabs */}
-
- {tabs.map((tab) => (
- setActiveTab(tab.id)}
- className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
- activeTab === tab.id
- ? "border-primary text-primary"
- : "border-transparent text-text/50 hover:text-text/70"
- }`}
- >
-
- {tab.label}
-
- ))}
-
-
- {/* Tab Content */}
-
- {activeTab === "overview" && (
-
-
Recent Contributions
-
-
- )}
-
- {activeTab === "saved" && }
-
- {activeTab === "settings" && (
-
- {/* Settings sub-tabs */}
-
- {([
- { id: "profile" as SettingsTab, label: "Profile" },
- { id: "security" as SettingsTab, label: "Security" },
- { id: "accounts" as SettingsTab, label: "Linked Accounts" },
- ]).map((subTab) => (
-
setSettingsSubTab(subTab.id)}
- className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
- settingsSubTab === subTab.id
- ? "border-primary text-primary"
- : "border-transparent text-text/50 hover:text-text/70"
- }`}
- >
- {subTab.label}
-
- ))}
+
+ {/* Profile header section */}
+
+
+
- {/* Settings sub-tab content */}
- {settingsSubTab === "profile" && (
-
- )}
- {settingsSubTab === "security" &&
}
- {settingsSubTab === "accounts" &&
}
-
- )}
-
+ {/* Stats section */}
+
+
+
+
+
+
+ {/* Tabs + content section */}
+
+
+ {/* Tabs */}
+
+ {tabs.map((tab) => (
+ setActiveTab(tab.id)}
+ className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px cursor-pointer ${
+ activeTab === tab.id
+ ? "border-primary text-primary"
+ : "border-transparent text-text/50 hover:text-text/70"
+ }`}
+ >
+
+ {tab.label}
+
+ ))}
+
+
+ {/* Tab Content */}
+
+ {activeTab === "overview" && (
+
+
+ Recent Contributions
+
+
+
+ )}
+
+ {activeTab === "saved" && }
+
+ {activeTab === "settings" && (
+
+ {/* Settings sub-tabs */}
+
+ {[
+ {
+ id: "profile" as SettingsTab,
+ label: "Profile",
+ },
+ {
+ id: "security" as SettingsTab,
+ label: "Security",
+ },
+ {
+ id: "accounts" as SettingsTab,
+ label: "Linked Accounts",
+ },
+ ].map((subTab) => (
+
+ setSettingsSubTab(subTab.id)
+ }
+ className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px cursor-pointer ${
+ settingsSubTab === subTab.id
+ ? "border-primary text-primary"
+ : "border-transparent text-text/50 hover:text-text/70"
+ }`}
+ >
+ {subTab.label}
+
+ ))}
+
+
+ {/* Settings sub-tab content */}
+ {settingsSubTab === "profile" && (
+
+ )}
+ {settingsSubTab === "security" && (
+
+ )}
+ {settingsSubTab === "accounts" && (
+
+ )}
+
+ )}
+
+
+
-
-
- )
+ )
}
diff --git a/app/search/page.tsx b/app/search/page.tsx
index acf9d79..034fafb 100644
--- a/app/search/page.tsx
+++ b/app/search/page.tsx
@@ -374,7 +374,7 @@ function SearchResultCard({
href={`https://store.steampowered.com/app/${result.appId}`}
target="_blank"
rel="noopener noreferrer"
- className="relative z-10 inline-flex items-center justify-center gap-1.5 px-2.5 py-1 rounded-md bg-[#1b2838] border border-[#2a475e] text-[#c6d4df] text-[11px] hover:bg-[#2a475e] transition-colors w-full"
+ className="relative z-10 inline-flex items-center justify-center gap-1.5 px-2.5 py-1 rounded-md bg-[#1b2838] border border-[#2a475e] text-[#c6d4df] text-[11px] hover:bg-[#2a475e] transition-colors w-full cursor-pointer"
onClick={(e) => e.stopPropagation()}
title="Open store page on Steam"
>
@@ -386,7 +386,7 @@ function SearchResultCard({
href={`https://www.protondb.com/app/${result.appId}`}
target="_blank"
rel="noopener noreferrer"
- className="relative z-10 inline-flex items-center justify-center gap-1.5 px-2.5 py-1 rounded-md bg-purple-500/10 border border-purple-500/20 text-purple-400 text-[11px] hover:bg-purple-500/20 transition-colors w-full"
+ className="relative z-10 inline-flex items-center justify-center gap-1.5 px-2.5 py-1 rounded-md bg-purple-500/10 border border-purple-500/20 text-purple-400 text-[11px] hover:bg-purple-500/20 transition-colors w-full cursor-pointer"
onClick={(e) => e.stopPropagation()}
title="View Proton compatibility on ProtonDB"
>
@@ -398,7 +398,7 @@ function SearchResultCard({
href={`https://steamdb.info/app/${result.appId}`}
target="_blank"
rel="noopener noreferrer"
- className="relative z-10 inline-flex items-center justify-center gap-1.5 px-2.5 py-1 rounded-md bg-blue-500/10 border border-blue-500/20 text-blue-400 text-[11px] hover:bg-blue-500/20 transition-colors w-full"
+ className="relative z-10 inline-flex items-center justify-center gap-1.5 px-2.5 py-1 rounded-md bg-blue-500/10 border border-blue-500/20 text-blue-400 text-[11px] hover:bg-blue-500/20 transition-colors w-full cursor-pointer"
onClick={(e) => e.stopPropagation()}
title="View app details on SteamDB"
>
diff --git a/components/auth/forgot-password-form.tsx b/components/auth/forgot-password-form.tsx
index 30e5769..ca267bd 100644
--- a/components/auth/forgot-password-form.tsx
+++ b/components/auth/forgot-password-form.tsx
@@ -85,7 +85,7 @@ export default function ForgotPasswordForm() {
Back to sign in
diff --git a/components/auth/login-form.tsx b/components/auth/login-form.tsx
index dcfb8a8..eea6dd9 100644
--- a/components/auth/login-form.tsx
+++ b/components/auth/login-form.tsx
@@ -190,7 +190,7 @@ export default function LoginForm() {
Forgot password?
@@ -218,7 +218,7 @@ export default function LoginForm() {
Don't have an account?{" "}
Create one
diff --git a/components/auth/otp-verification-step.tsx b/components/auth/otp-verification-step.tsx
index 6854e6a..c551f67 100644
--- a/components/auth/otp-verification-step.tsx
+++ b/components/auth/otp-verification-step.tsx
@@ -133,7 +133,7 @@ export default function OtpVerificationStep({
Back to signup
diff --git a/components/auth/reset-password-form.tsx b/components/auth/reset-password-form.tsx
index 03c0185..c6207aa 100644
--- a/components/auth/reset-password-form.tsx
+++ b/components/auth/reset-password-form.tsx
@@ -208,7 +208,7 @@ export default function ResetPasswordForm({
Back to sign in
diff --git a/components/auth/signup-form-step.tsx b/components/auth/signup-form-step.tsx
index 6ed102a..03f1fbc 100644
--- a/components/auth/signup-form-step.tsx
+++ b/components/auth/signup-form-step.tsx
@@ -143,7 +143,7 @@ export default function SignupFormStep({ onSuccess }: SignupFormStepProps) {
Already have an account?{" "}
-
+
Sign in
diff --git a/components/navbar.tsx b/components/navbar.tsx
index 50ed2c6..d85818e 100644
--- a/components/navbar.tsx
+++ b/components/navbar.tsx
@@ -217,7 +217,7 @@ export default function Navbar() {
key={route.href}
href={route.href}
title={`Navigate to ${route.title}`}
- className='text-sm font-medium hover:text-primary transition-colors uppercase'
+ className='text-sm font-medium hover:text-primary transition-colors uppercase cursor-pointer'
>
{route.title}
@@ -260,7 +260,7 @@ export default function Navbar() {
key={route.href}
href={route.href}
onClick={() => setUserMenuOpen(false)}
- className='w-full flex items-center gap-2 px-3 py-2 text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors'
+ className='w-full flex items-center gap-2 px-3 py-2 text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors cursor-pointer'
>
{route.icon === "User" && }
{route.icon === "Bookmark" && }
@@ -273,7 +273,7 @@ export default function Navbar() {
setUserMenuOpen(false)
await authClient.signOut()
}}
- className='w-full flex items-center gap-2 px-3 py-2 text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors'
+ className='w-full flex items-center gap-2 px-3 py-2 text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors cursor-pointer'
>
Sign out
@@ -291,7 +291,7 @@ export default function Navbar() {
>
login
@@ -303,7 +303,7 @@ export default function Navbar() {
{/* Mobile Hamburger Button */}
setMobileMenuOpen(true)}
- className='flex md:hidden flex-row items-center justify-center p-2 -mr-2 rounded-md hover:bg-text/5 transition-colors'
+ className='flex md:hidden flex-row items-center justify-center p-2 -mr-2 rounded-md hover:bg-text/5 transition-colors cursor-pointer'
aria-label='Open menu'
>
@@ -338,7 +338,7 @@ export default function Navbar() {
Menu
setMobileMenuOpen(false)}
- className='p-2 -mr-2 rounded-md hover:bg-text/5 transition-colors'
+ className='p-2 -mr-2 rounded-md hover:bg-text/5 transition-colors cursor-pointer'
aria-label='Close menu'
>
@@ -350,7 +350,7 @@ export default function Navbar() {
key={route.href}
href={route.href}
onClick={() => setMobileMenuOpen(false)}
- className={`text-sm font-medium hover:text-primary transition-colors uppercase px-3 py-2 rounded-md hover:bg-text/5 ${
+ className={`text-sm font-medium hover:text-primary transition-colors uppercase px-3 py-2 rounded-md hover:bg-text/5 cursor-pointer ${
pathname === route.href
? "text-primary bg-text/5"
: ""
@@ -371,7 +371,7 @@ export default function Navbar() {
key={route.href}
href={route.href}
onClick={() => setMobileMenuOpen(false)}
- className='flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors'
+ className='flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors cursor-pointer'
>
{route.icon === "User" && }
{route.icon === "Bookmark" && }
@@ -383,7 +383,7 @@ export default function Navbar() {
setMobileMenuOpen(false)
await authClient.signOut()
}}
- className='w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg border border-white/10 text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors'
+ className='w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg border border-white/10 text-sm text-text/70 hover:text-text hover:bg-text/5 transition-colors cursor-pointer'
>
Sign out
@@ -394,7 +394,7 @@ export default function Navbar() {
setMobileMenuOpen(false)}
- className='block w-full text-center px-3 py-2 rounded-lg border border-white/10 text-sm text-text hover:bg-text/5 transition-colors'
+ className='block w-full text-center px-3 py-2 rounded-lg border border-white/10 text-sm text-text hover:bg-text/5 transition-colors cursor-pointer'
>
Login
diff --git a/components/profile/settings-security-tab.tsx b/components/profile/settings-security-tab.tsx
index ec384d3..9b2d092 100644
--- a/components/profile/settings-security-tab.tsx
+++ b/components/profile/settings-security-tab.tsx
@@ -47,7 +47,7 @@ export function SettingsSecurityTab() {
let cancelled = false
Promise.all([
fetch("/api/user/me/auth-methods", { credentials: "include" }).then(r => r.ok ? r.json() : null).catch(() => null),
- fetch("/api/auth/passkey/list-user-passkeys", { credentials: "include" }).then(r => r.ok ? r.json() : []).catch(() => []),
+ fetch("/api/auth/passkey/list", { credentials: "include" }).then(r => r.ok ? r.json() : []).catch(() => []),
]).then(([methodsData, passkeyData]) => {
if (cancelled) return
if (methodsData) setAuthMethods(methodsData)
@@ -65,7 +65,7 @@ export function SettingsSecurityTab() {
}
const refreshPasskeys = async () => {
- const res = await fetch("/api/auth/passkey/list-user-passkeys", { credentials: "include" })
+ const res = await fetch("/api/auth/passkey/list", { credentials: "include" })
if (res.ok) {
const data = await res.json()
setPasskeys(Array.isArray(data) ? data : [])
@@ -146,7 +146,7 @@ export function SettingsSecurityTab() {
setIsDeletingPasskey(id)
try {
- const res = await fetch("/api/auth/passkey/delete-passkey", {
+ const res = await fetch("/api/user/me/passkey/delete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id }),
@@ -176,7 +176,7 @@ export function SettingsSecurityTab() {
const handleRenamePasskey = async (id: string) => {
setIsUpdatingPasskey(true)
try {
- const res = await fetch("/api/auth/passkey/update-passkey", {
+ const res = await fetch("/api/user/me/passkey/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id, name: editingName }),
@@ -347,7 +347,7 @@ export function SettingsSecurityTab() {
handleRenamePasskey(pk.id)}
disabled={isUpdatingPasskey}
- className="p-1 rounded hover:bg-green-500/10 text-green-400 transition-colors"
+ className="p-1 rounded hover:bg-green-500/10 text-green-400 transition-colors cursor-pointer disabled:cursor-not-allowed"
>
{isUpdatingPasskey ? (
@@ -357,7 +357,7 @@ export function SettingsSecurityTab() {
diff --git a/components/saved-games/bookmark-button.tsx b/components/saved-games/bookmark-button.tsx
index 3f9aebb..3dd5d71 100644
--- a/components/saved-games/bookmark-button.tsx
+++ b/components/saved-games/bookmark-button.tsx
@@ -77,7 +77,7 @@ export function BookmarkButton({ gameId, className = "" }: BookmarkButtonProps)
whileTap={{ scale: 0.9 }}
onClick={toggleSave}
disabled={isToggling}
- className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors ${
+ className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors cursor-pointer ${
isSaved
? "bg-primary/10 border-primary/30 text-primary"
: "bg-text/5 border-border text-text/50 hover:text-text hover:border-primary/30"
diff --git a/components/saved-games/saved-games-grid.tsx b/components/saved-games/saved-games-grid.tsx
index 2ea5a7d..85d3307 100644
--- a/components/saved-games/saved-games-grid.tsx
+++ b/components/saved-games/saved-games-grid.tsx
@@ -117,7 +117,7 @@ export function SavedGamesGrid() {
removeGame(game.id, game.gameId)}
disabled={removingId === game.id}
- className="absolute top-2 right-2 p-1.5 rounded-full bg-black/50 text-text/70 hover:text-red-400 hover:bg-black/70 transition-colors opacity-0 group-hover:opacity-100"
+ className="absolute top-2 right-2 p-1.5 rounded-full bg-black/50 text-text/70 hover:text-red-400 hover:bg-black/70 transition-colors opacity-0 group-hover:opacity-100 cursor-pointer disabled:cursor-not-allowed"
>
{removingId === game.id ? (
diff --git a/components/tiptap-editor.tsx b/components/tiptap-editor.tsx
index 1c44bc5..125423c 100644
--- a/components/tiptap-editor.tsx
+++ b/components/tiptap-editor.tsx
@@ -81,7 +81,7 @@ export function TiptapEditor({
editor.chain().focus().toggleBold().run()}
- className={`p-1.5 rounded hover:bg-text/10 transition-colors ${
+ className={`p-1.5 rounded hover:bg-text/10 transition-colors cursor-pointer ${
editor.isActive("bold") ? "bg-text/10 text-primary" : "text-text/50"
}`}
>
@@ -90,7 +90,7 @@ export function TiptapEditor({
editor.chain().focus().toggleItalic().run()}
- className={`p-1.5 rounded hover:bg-text/10 transition-colors ${
+ className={`p-1.5 rounded hover:bg-text/10 transition-colors cursor-pointer ${
editor.isActive("italic") ? "bg-text/10 text-primary" : "text-text/50"
}`}
>
@@ -100,7 +100,7 @@ export function TiptapEditor({
editor.chain().focus().toggleBulletList().run()}
- className={`p-1.5 rounded hover:bg-text/10 transition-colors ${
+ className={`p-1.5 rounded hover:bg-text/10 transition-colors cursor-pointer ${
editor.isActive("bulletList") ? "bg-text/10 text-primary" : "text-text/50"
}`}
>
@@ -109,7 +109,7 @@ export function TiptapEditor({
editor.chain().focus().toggleOrderedList().run()}
- className={`p-1.5 rounded hover:bg-text/10 transition-colors ${
+ className={`p-1.5 rounded hover:bg-text/10 transition-colors cursor-pointer ${
editor.isActive("orderedList") ? "bg-text/10 text-primary" : "text-text/50"
}`}
>
@@ -119,7 +119,7 @@ export function TiptapEditor({
diff --git a/components/wizard/step-indicator.tsx b/components/wizard/step-indicator.tsx
index 1aba357..ce2d6fe 100644
--- a/components/wizard/step-indicator.tsx
+++ b/components/wizard/step-indicator.tsx
@@ -109,7 +109,7 @@ export function StepIndicator({
type="button"
onMouseEnter={() => setShowTooltip(currentStep)}
onMouseLeave={() => setShowTooltip(null)}
- className="text-text/40 hover:text-text transition-colors"
+ className="text-text/40 hover:text-text transition-colors cursor-pointer"
>
@@ -137,7 +137,7 @@ export function StepIndicator({
key={index}
type="button"
onClick={() => index <= currentStep && onStepClick?.(index)}
- className={`h-1.5 rounded-full transition-all ${
+ className={`h-1.5 rounded-full transition-all cursor-pointer ${
index === currentStep
? "w-6 bg-primary"
: index < currentStep
diff --git a/lib/api/user.ts b/lib/api/user.ts
index f79ac7d..f530d7f 100644
--- a/lib/api/user.ts
+++ b/lib/api/user.ts
@@ -311,3 +311,84 @@ export const userRoutes = new Elysia({ prefix: "/user" })
}),
},
)
+ // Passkey management endpoints (better-auth doesn't provide these)
+ .post(
+ "/me/passkey/delete",
+ async ({ request, body, set }) => {
+ const session = await auth.api.getSession({
+ headers: request.headers,
+ })
+
+ if (!session) {
+ set.status = 401
+ return { error: "Unauthorized" }
+ }
+
+ // Verify the passkey belongs to the user
+ const [pk] = await db
+ .select({ id: passkey.id })
+ .from(passkey)
+ .where(and(
+ eq(passkey.id, body.id),
+ eq(passkey.userId, session.user.id)
+ ))
+ .limit(1)
+
+ if (!pk) {
+ set.status = 404
+ return { error: "Passkey not found" }
+ }
+
+ await db
+ .delete(passkey)
+ .where(eq(passkey.id, body.id))
+
+ return { success: true }
+ },
+ {
+ body: t.Object({
+ id: t.String(),
+ }),
+ },
+ )
+ .post(
+ "/me/passkey/update",
+ async ({ request, body, set }) => {
+ const session = await auth.api.getSession({
+ headers: request.headers,
+ })
+
+ if (!session) {
+ set.status = 401
+ return { error: "Unauthorized" }
+ }
+
+ // Verify the passkey belongs to the user
+ const [pk] = await db
+ .select({ id: passkey.id })
+ .from(passkey)
+ .where(and(
+ eq(passkey.id, body.id),
+ eq(passkey.userId, session.user.id)
+ ))
+ .limit(1)
+
+ if (!pk) {
+ set.status = 404
+ return { error: "Passkey not found" }
+ }
+
+ await db
+ .update(passkey)
+ .set({ name: body.name })
+ .where(eq(passkey.id, body.id))
+
+ return { success: true }
+ },
+ {
+ body: t.Object({
+ id: t.String(),
+ name: t.String(),
+ }),
+ },
+ )