Refactor: Remove server state files and update server log handling
- Deleted server-stopped, server.log, and server.pid files to clean up unused state. - Updated game-page-client.tsx to enhance button accessibility with cursor-pointer class. - Modified not-found.tsx, page.tsx, and profile/page.tsx for consistent cursor-pointer usage on links. - Adjusted search/page.tsx and components/auth/*.tsx for improved button interactions with cursor-pointer. - Refined navbar.tsx for better user experience with cursor-pointer on navigation links. - Updated settings-security-tab.tsx for passkey management API endpoints and improved error handling. - Enhanced saved-games components for better user interaction with cursor-pointer on buttons. - General code cleanup and consistency improvements across various components.
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
<h2>Auth Page Layout</h2>
|
||||
<p class="subtitle">Centered card layout used across all auth pages (login, signup, forgot-password, reset-password)</p>
|
||||
|
||||
<div class="split">
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Desktop Layout</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 0; min-height: 420px; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<!-- Background decorative elements -->
|
||||
<div style="position: absolute; top: 40px; left: 40px; width: 120px; height: 120px; border-radius: 50%; background: radial-gradient(circle, rgba(235,55,121,0.15), transparent); filter: blur(30px);"></div>
|
||||
<div style="position: absolute; bottom: 60px; right: 60px; width: 100px; height: 100px; border-radius: 50%; background: radial-gradient(circle, rgba(87,27,139,0.2), transparent); filter: blur(25px);"></div>
|
||||
|
||||
<!-- Auth Card -->
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 32px; width: 100%; max-width: 400px; margin: 20px; backdrop-filter: blur(10px);">
|
||||
<!-- Logo -->
|
||||
<div style="text-align: center; margin-bottom: 24px;">
|
||||
<div style="font-size: 20px; font-weight: bold; color: #ebe4f1;">🔐 DeckyVault</div>
|
||||
<div style="font-size: 13px; color: rgba(235,228,241,0.5); margin-top: 4px;">Sign in to your account</div>
|
||||
</div>
|
||||
|
||||
<!-- Social Buttons -->
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px; cursor: pointer;">🔵 Google</button>
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px; cursor: pointer;">🟣 Discord</button>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin: 16px 0;">
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
<span style="font-size: 11px; color: rgba(235,228,241,0.4); text-transform: uppercase;">or continue with email</span>
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
</div>
|
||||
|
||||
<!-- Email Input -->
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Email</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; box-sizing: border-box;" placeholder="you@example.com" autocomplete="username webauthn" />
|
||||
</div>
|
||||
|
||||
<!-- Password Input (conditional) -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Password</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; box-sizing: border-box;" type="password" placeholder="••••••••" autocomplete="current-password webauthn" />
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600; cursor: pointer;">Continue</button>
|
||||
|
||||
<!-- Links -->
|
||||
<div style="text-align: center; margin-top: 16px; font-size: 12px;">
|
||||
<a style="color: rgba(235,228,241,0.5); text-decoration: none;">Forgot password?</a>
|
||||
<span style="color: rgba(235,228,241,0.3); margin: 0 8px;">•</span>
|
||||
<a style="color: #eb3779; text-decoration: none;">Create account</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Mobile Layout</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 0; min-height: 420px; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding-top: 24px;">
|
||||
<!-- Same card but full-width on mobile -->
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 24px 20px; width: calc(100% - 32px); max-width: none; margin: 0 16px;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">🔐 DeckyVault</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px;">Sign in to your account</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px;">🔵 Google</button>
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px;">🟣 Discord</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin: 16px 0;">
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
<span style="font-size: 11px; color: rgba(235,228,241,0.4);">or email</span>
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
</div>
|
||||
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 10px; box-sizing: border-box;" placeholder="you@example.com" />
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 16px; box-sizing: border-box;" type="password" placeholder="••••••••" />
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Continue</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px; font-size: 12px;">
|
||||
<a style="color: rgba(235,228,241,0.5);">Forgot password?</a>
|
||||
<span style="color: rgba(235,228,241,0.3); margin: 0 8px;">•</span>
|
||||
<a style="color: #eb3779;">Create account</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="margin-top: 20px;">
|
||||
<h3>Layout Specs</h3>
|
||||
<ul>
|
||||
<li><strong>Container:</strong> Full viewport height minus navbar, centered content</li>
|
||||
<li><strong>Card:</strong> Max-width 400px, glass-morphism style (subtle border, blur backdrop)</li>
|
||||
<li><strong>Background:</strong> Subtle radial gradient orbs for depth (pink + purple)</li>
|
||||
<li><strong>Responsive:</strong> Card goes full-width on mobile with reduced padding</li>
|
||||
<li><strong>Animations:</strong> Card fades in with Motion (consistent with existing navbar pattern)</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,124 +0,0 @@
|
||||
<h2>Login Flow — Email-First with Conditional Passkey</h2>
|
||||
<p class="subtitle">Two-phase login: email check → password/passkey</p>
|
||||
|
||||
<div class="section">
|
||||
<h3>Phase 1: Email Entry</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Login — Initial Screen</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Welcome back</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px;">Sign in to DeckyVault</div>
|
||||
</div>
|
||||
|
||||
<!-- Social buttons -->
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px;">🔵 Google</button>
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px;">🟣 Discord</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin: 16px 0;">
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
<span style="font-size: 11px; color: rgba(235,228,241,0.4);">or continue with email</span>
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
</div>
|
||||
|
||||
<!-- Email only — NO password field yet -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Email</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 16px; box-sizing: border-box;" placeholder="you@example.com" autocomplete="username webauthn" />
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Continue</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px; font-size: 12px;">
|
||||
<span style="color: rgba(235,228,241,0.5);">Don't have an account? </span>
|
||||
<a style="color: #eb3779;">Create one</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: rgba(34,197,94,0.05); border: 1px solid rgba(34,197,94,0.15); border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||||
<p style="color: rgba(235,228,241,0.7); font-size: 12px; margin: 0;">
|
||||
<strong>Key:</strong> Only email field is shown initially. The <code>autocomplete="username webauthn"</code> attribute enables passkey conditional UI — browsers with passkey support will offer to autofill passkey credentials.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Phase 2a: Email Exists → Password Entry</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Login — Password Step (email exists)</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Welcome back</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px;">Signing in as <strong style="color: #ebe4f1;">user@example.com</strong> <a style="color: #eb3779; font-size: 11px; cursor: pointer;">change</a></div>
|
||||
</div>
|
||||
|
||||
<!-- Password field with autocomplete for passkey conditional UI -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Password</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 8px; box-sizing: border-box;" type="password" placeholder="Enter your password" autocomplete="current-password webauthn" />
|
||||
|
||||
<div style="text-align: right; margin-bottom: 16px;">
|
||||
<a style="font-size: 12px; color: rgba(235,228,241,0.5);">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Sign in</button>
|
||||
|
||||
<!-- Passkey autofill hint -->
|
||||
<div style="margin-top: 12px; padding: 10px; border-radius: 8px; background: rgba(235,55,121,0.05); border: 1px solid rgba(235,55,121,0.1); text-align: center;">
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.6);">🔑 Your browser may offer to sign in with a passkey</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: rgba(34,197,94,0.05); border: 1px solid rgba(34,197,94,0.15); border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||||
<p style="color: rgba(235,228,241,0.7); font-size: 12px; margin: 0;">
|
||||
<strong>Conditional UI:</strong> The password field has <code>autocomplete="current-password webauthn"</code>. On mount, we call <code>authClient.signIn.passkey({ autoFill: true })</code> 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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Phase 2b: Email NOT Found → Error + Signup Link</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Login — Email Not Found</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Welcome back</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px;">Sign in to DeckyVault</div>
|
||||
</div>
|
||||
|
||||
<!-- Email with error state -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Email</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 2px solid #ef4444; background: rgba(239,68,68,0.05); color: #ebe4f1; font-size: 14px; margin-bottom: 8px; box-sizing: border-box;" value="unknown@example.com" />
|
||||
|
||||
<!-- Error message -->
|
||||
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 16px;">
|
||||
<span style="color: #ef4444; font-size: 14px;">⚠</span>
|
||||
<span style="font-size: 12px; color: #ef4444;">No account found with this email.</span>
|
||||
<a style="font-size: 12px; color: #eb3779; white-space: nowrap; font-weight: 600;">Create one →</a>
|
||||
</div>
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>How Email Check Works</h3>
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 16px;">
|
||||
<ol style="color: rgba(235,228,241,0.7); font-size: 13px; margin: 0; padding-left: 20px; line-height: 1.8;">
|
||||
<li>User enters email and clicks "Continue"</li>
|
||||
<li>Client calls <code>authClient.signIn.email()</code> with email + empty password</li>
|
||||
<li>Better-auth returns an error if email doesn't exist</li>
|
||||
<li>If error: show "No account found" with signup link</li>
|
||||
<li>If email exists: transition to password field with email displayed</li>
|
||||
</ol>
|
||||
<p style="color: rgba(235,228,241,0.5); font-size: 11px; margin: 12px 0 0 0;">
|
||||
<strong>Note:</strong> We use <code>signIn.email()</code> as the check mechanism rather than a separate endpoint. This avoids leaking whether an email exists via a dedicated check API.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,127 +0,0 @@
|
||||
<h2>Password Reset Flow — Email OTP</h2>
|
||||
<p class="subtitle">Two-page flow: request reset → verify OTP + set new password</p>
|
||||
|
||||
<div class="section">
|
||||
<h3>Page 1: Forgot Password — Request OTP</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">/forgot-password</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 40px; margin-bottom: 8px;">🔒</div>
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Forgot your password?</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px; line-height: 1.5;">Enter your email and we'll send you a verification code to reset your password.</div>
|
||||
</div>
|
||||
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Email</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 16px; box-sizing: border-box;" placeholder="you@example.com" />
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Send verification code</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px;">
|
||||
<a style="font-size: 12px; color: rgba(235,228,241,0.5);">← Back to sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: rgba(34,197,94,0.05); border: 1px solid rgba(34,197,94,0.15); border-radius: 8px; padding: 12px; margin-top: 12px;">
|
||||
<p style="color: rgba(235,228,241,0.7); font-size: 12px; margin: 0;">
|
||||
<strong>API:</strong> Calls <code>authClient.emailOtp.requestPasswordReset({ email })</code>. This triggers the server to send a "forget-password" OTP via the existing <code>sendVerificationOTP</code> handler.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Page 2: Reset Password — OTP + New Password</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">/reset-password?email=user@example.com</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 450px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 40px; margin-bottom: 8px;">📧</div>
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Check your email</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px; line-height: 1.5;">We sent a 6-digit code to <strong style="color: #ebe4f1;">user@example.com</strong></div>
|
||||
</div>
|
||||
|
||||
<!-- OTP Input -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 8px; text-align: center;">Verification code</label>
|
||||
<div style="display: flex; gap: 8px; justify-content: center; margin-bottom: 12px;">
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 16px; font-size: 12px;">
|
||||
<span style="color: rgba(235,228,241,0.4);">Resend code in </span>
|
||||
<span style="color: #eb3779; font-weight: 600;">4:23</span>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin: 16px 0;">
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
<span style="font-size: 11px; color: rgba(235,228,241,0.4);">then set new password</span>
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
</div>
|
||||
|
||||
<!-- New Password -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">New password</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 8px; box-sizing: border-box;" type="password" placeholder="Min. 10 characters" />
|
||||
|
||||
<!-- Password strength -->
|
||||
<div style="margin-bottom: 12px;">
|
||||
<div style="display: flex; gap: 4px; margin-bottom: 4px;">
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Confirm new password</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 16px; box-sizing: border-box;" type="password" placeholder="Re-enter password" />
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Reset password</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px;">
|
||||
<a style="font-size: 12px; color: rgba(235,228,241,0.5);">← Back to sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Success State</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">After successful reset</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 200px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto; text-align: center;">
|
||||
<div style="font-size: 40px; margin-bottom: 12px;">✅</div>
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1; margin-bottom: 8px;">Password reset successful</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-bottom: 20px;">Your password has been updated. You can now sign in with your new password.</div>
|
||||
<button style="padding: 10px 32px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>API Flow</h3>
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 16px;">
|
||||
<ol style="color: rgba(235,228,241,0.7); font-size: 13px; margin: 0; padding-left: 20px; line-height: 2;">
|
||||
<li>User enters email on <code>/forgot-password</code></li>
|
||||
<li>Client calls <code>authClient.emailOtp.requestPasswordReset({ email })</code></li>
|
||||
<li>Server sends OTP with type "forget-password" via <code>sendVerificationOTP</code></li>
|
||||
<li>User is redirected to <code>/reset-password?email=user@example.com</code></li>
|
||||
<li>User enters OTP + new password + confirm password</li>
|
||||
<li>Client calls <code>authClient.emailOtp.checkVerificationOtp({ email, otp, type: "forget-password" })</code> (optional validation)</li>
|
||||
<li>Client calls <code>authClient.resetPassword({ newPassword, otp })</code></li>
|
||||
<li>On success: show success screen, then redirect to <code>/login</code></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,192 +0,0 @@
|
||||
<h2>Signup Wizard — Step-by-Step Flow</h2>
|
||||
<p class="subtitle">3 steps for email signup, 2 steps for social signup</p>
|
||||
|
||||
<div class="section">
|
||||
<h3>Step 1: Account Creation</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Email Signup — Step 1 of 3</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<!-- Step indicator -->
|
||||
<div style="display: flex; gap: 8px; justify-content: center; margin-bottom: 20px;">
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: #eb3779;"></div>
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Create your account</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px;">Choose how you'd like to sign up</div>
|
||||
</div>
|
||||
|
||||
<!-- Social buttons -->
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px;">🔵 Google</button>
|
||||
<button style="flex: 1; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 13px;">🟣 Discord</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin: 16px 0;">
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
<span style="font-size: 11px; color: rgba(235,228,241,0.4);">or use email</span>
|
||||
<div style="flex: 1; height: 1px; background: rgba(255,255,255,0.08);"></div>
|
||||
</div>
|
||||
|
||||
<!-- Form fields -->
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Email</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 12px; box-sizing: border-box;" placeholder="you@example.com" />
|
||||
|
||||
<label style="font-size: 12px; color: rgba(235,228,241,0.6); display: block; margin-bottom: 4px;">Password</label>
|
||||
<input style="width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); color: #ebe4f1; font-size: 14px; margin-bottom: 8px; box-sizing: border-box;" type="password" placeholder="Min. 10 characters" />
|
||||
|
||||
<!-- Password strength meter -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="display: flex; gap: 4px; margin-bottom: 6px;">
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #ef4444;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #ef4444;">Weak — add uppercase, number, special char</div>
|
||||
</div>
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600;">Continue</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px; font-size: 12px;">
|
||||
<span style="color: rgba(235,228,241,0.5);">Already have an account? </span>
|
||||
<a style="color: #eb3779;">Sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Step 2: Email Verification (OTP)</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Email Signup — Step 2 of 3</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<!-- Step indicator -->
|
||||
<div style="display: flex; gap: 8px; justify-content: center; margin-bottom: 20px;">
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: #eb3779;"></div>
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Verify your email</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px;">We sent a 6-digit code to <strong style="color: #ebe4f1;">user@example.com</strong></div>
|
||||
</div>
|
||||
|
||||
<!-- OTP Input boxes -->
|
||||
<div style="display: flex; gap: 8px; justify-content: center; margin-bottom: 16px;">
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 2px solid #eb3779; background: rgba(235,55,121,0.05); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: #ebe4f1;">4</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: #ebe4f1;">7</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
<div style="width: 44px; height: 52px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: rgba(235,228,241,0.3);">_</div>
|
||||
</div>
|
||||
|
||||
<!-- Timer and resend -->
|
||||
<div style="text-align: center; margin-bottom: 16px; font-size: 12px;">
|
||||
<span style="color: rgba(235,228,241,0.4);">Resend code in </span>
|
||||
<span style="color: #eb3779; font-weight: 600;">4:23</span>
|
||||
</div>
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600; opacity: 0.5;" disabled>Verify</button>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px; font-size: 12px;">
|
||||
<a style="color: rgba(235,228,241,0.5);">← Back to signup</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Step 3: Passkey Setup (Optional)</h3>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Email Signup — Step 3 of 3 (Optional)</div>
|
||||
<div class="mockup-body" style="background: #100b14; padding: 20px; min-height: 380px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 28px; max-width: 380px; margin: 0 auto;">
|
||||
<!-- Step indicator -->
|
||||
<div style="display: flex; gap: 8px; justify-content: center; margin-bottom: 20px;">
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="width: 32px; height: 4px; border-radius: 2px; background: #eb3779;"></div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 40px; margin-bottom: 8px;">🔑</div>
|
||||
<div style="font-size: 18px; font-weight: bold; color: #ebe4f1;">Set up a passkey</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.5); margin-top: 4px; line-height: 1.5;">Sign in faster with biometrics or your device's security key. No password needed.</div>
|
||||
</div>
|
||||
|
||||
<!-- Benefits -->
|
||||
<div style="background: rgba(255,255,255,0.02); border-radius: 8px; padding: 14px; margin-bottom: 20px;">
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.6); display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<span style="color: #22c55e;">✓</span> Faster sign-in with fingerprint or face
|
||||
</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.6); display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<span style="color: #22c55e;">✓</span> More secure than passwords
|
||||
</div>
|
||||
<div style="font-size: 12px; color: rgba(235,228,241,0.6); display: flex; align-items: center; gap: 8px;">
|
||||
<span style="color: #22c55e;">✓</span> Works across your devices
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: none; background: #eb3779; color: white; font-size: 14px; font-weight: 600; margin-bottom: 10px;">Set up passkey</button>
|
||||
<button style="width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: rgba(235,228,241,0.6); font-size: 13px;">Skip for now</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Social Signup Flow (2 steps)</h3>
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 16px;">
|
||||
<p style="color: rgba(235,228,241,0.7); font-size: 13px; margin: 0;">
|
||||
<strong>Step 1:</strong> User clicks Google/Discord → OAuth redirect → authenticated<br>
|
||||
<strong>Step 2:</strong> Passkey setup (optional, same as Step 3 above) → Done<br><br>
|
||||
<em>OTP step is skipped — email is already verified by the OAuth provider.</em>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Password Strength Meter — States</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; gap: 4px; margin-bottom: 4px;">
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #ef4444;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #ef4444;">Weak — add 8+ chars, uppercase, number, special char</div>
|
||||
</div>
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; gap: 4px; margin-bottom: 4px;">
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #f59e0b;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #f59e0b;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.1);"></div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #f59e0b;">Fair — add a special character and more length</div>
|
||||
</div>
|
||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; gap: 4px; margin-bottom: 4px;">
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #22c55e;"></div>
|
||||
<div style="flex: 1; height: 3px; border-radius: 2px; background: #22c55e;"></div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #22c55e;">Strong — great password!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
{"reason":"idle timeout","timestamp":1777134909078}
|
||||
@@ -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"}
|
||||
@@ -1 +0,0 @@
|
||||
26725
|
||||
@@ -1,33 +0,0 @@
|
||||
<h2>Badge Naming — "Native Champion" Alternatives</h2>
|
||||
<p class="subtitle">This badge marks games/presets that hit 60+ FPS with zero FSR and zero frame generation. Which name fits the brand best?</p>
|
||||
|
||||
<div class="options">
|
||||
<div class="option" data-choice="raw-performer" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>Raw Performer</h3>
|
||||
<p>Emphasizes unassisted performance. Technical, no-nonsense. "Raw" signals no upscaling tricks.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="pure-performance" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>Pure Performance</h3>
|
||||
<p>Clean and clear. "Pure" = untouched settings. Easy to understand at a glance.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="native-champion" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>Native Champion</h3>
|
||||
<p>Your original idea. "Champion" has a competitive/gaming feel. Slightly longer.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="untouched" onclick="toggleSelect(this)">
|
||||
<div class="letter">D</div>
|
||||
<div class="content">
|
||||
<h3>Untouched</h3>
|
||||
<p>Short, punchy. Implies the game runs great without any modifications. Memorable.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,149 +0,0 @@
|
||||
<h2>Game Page Layout — Overall Structure</h2>
|
||||
<p class="subtitle">Here's the proposed page structure top-to-bottom. Each section is labeled.</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Section 1: Hero Header — Card Image + Game Info</div>
|
||||
<div class="mockup-body" style="padding: 0; overflow: hidden;">
|
||||
<div style="display: flex; gap: 16px; padding: 16px; background: linear-gradient(135deg, #1a1025, #0d0814);">
|
||||
<!-- Cover image (2:3 aspect ratio) -->
|
||||
<div style="width: 120px; height: 180px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 11px;">2:3 Cover</span>
|
||||
</div>
|
||||
<!-- Info panel -->
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="font-size: 18px; font-weight: 700; color: #ebe4f1;">Game Title Here</div>
|
||||
<div style="font-size: 12px; color: #8b7a9e;">Developer · Publisher · Genre, Genre</div>
|
||||
<!-- 3 info badges + price/metacritic -->
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px;">
|
||||
<span style="background: #eb377920; border: 1px solid #eb377940; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #eb3779;">🎮 42 Benchmarks</span>
|
||||
<span style="background: #571b8b20; border: 1px solid #571b8b40; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #9b6fc7;">⚙️ 8 Presets</span>
|
||||
<span style="background: #fb793c20; border: 1px solid #fb793c40; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #fb793c;">💬 15 Comments</span>
|
||||
<span style="background: #22c55e20; border: 1px solid #22c55e40; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #22c55e;">Metascore: 87</span>
|
||||
<span style="background: #ebe4f110; border: 1px solid #ebe4f120; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #8b7a9e;">$29.99</span>
|
||||
</div>
|
||||
<!-- External links -->
|
||||
<div style="display: flex; gap: 6px; margin-top: 4px;">
|
||||
<span style="background: #1b2838; border: 1px solid #2a475e; padding: 3px 10px; border-radius: 6px; font-size: 10px; color: #c6d4df;">Steam ↗</span>
|
||||
<span style="background: #7c3aed15; border: 1px solid #7c3aed30; padding: 3px 10px; border-radius: 6px; font-size: 10px; color: #a78bfa;">ProtonDB ↗</span>
|
||||
<span style="background: #3b82f615; border: 1px solid #3b82f630; padding: 3px 10px; border-radius: 6px; font-size: 10px; color: #60a5fa;">SteamDB ↗</span>
|
||||
</div>
|
||||
<!-- DB Stats -->
|
||||
<div style="display: flex; gap: 12px; margin-top: 4px; font-size: 10px; color: #6b5a7d;">
|
||||
<span>📊 42 benchmarks</span>
|
||||
<span>📋 3 game versions</span>
|
||||
<span>✅ 12 verified entries</span>
|
||||
<span>🕐 Last updated 2 days ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 2: Overview — Description + Details</div>
|
||||
<div class="mockup-body">
|
||||
<div style="display: flex; gap: 24px;">
|
||||
<div style="flex: 2;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">About</div>
|
||||
<div style="font-size: 13px; color: #8b7a9e; line-height: 1.6;">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...</div>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">Details</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 12px;">
|
||||
<div><span style="color: #6b5a7d;">Source:</span> <span style="color: #8b7a9e;">Steam</span></div>
|
||||
<div><span style="color: #6b5a7d;">AppID:</span> <span style="color: #8b7a9e;">1234567</span></div>
|
||||
<div><span style="color: #6b5a7d;">Added:</span> <span style="color: #8b7a9e;">Jan 15, 2026</span></div>
|
||||
<div><span style="color: #6b5a7d;">Last Sync:</span> <span style="color: #8b7a9e;">2 days ago</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 3: Device Selector Ribbon + Fine Filters</div>
|
||||
<div class="mockup-body">
|
||||
<!-- Device ribbon -->
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 10px;">Select Devices</div>
|
||||
<div style="display: flex; gap: 8px; overflow-x: auto; padding-bottom: 8px;">
|
||||
<div style="background: #eb377930; border: 1px solid #eb3779; padding: 6px 14px; border-radius: 20px; font-size: 12px; color: #eb3779; white-space: nowrap; cursor: pointer;">✓ Steam Deck OLED</div>
|
||||
<div style="background: #ebe4f108; border: 1px solid #ebe4f120; padding: 6px 14px; border-radius: 20px; font-size: 12px; color: #6b5a7d; white-space: nowrap; cursor: pointer;">Steam Deck LCD</div>
|
||||
<div style="background: #ebe4f108; border: 1px solid #ebe4f120; padding: 6px 14px; border-radius: 20px; font-size: 12px; color: #6b5a7d; white-space: nowrap; cursor: pointer;">Steam Machine</div>
|
||||
</div>
|
||||
<!-- Fine filters -->
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin: 12px 0 8px;">Filters</div>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;">
|
||||
<option>All Proton Versions</option>
|
||||
</select>
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;">
|
||||
<option>All SteamOS Versions</option>
|
||||
</select>
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;">
|
||||
<option>FSR: Any</option>
|
||||
</select>
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;">
|
||||
<option>Frame Gen: Any</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 4: Statistics Dashboard (ECharts)</div>
|
||||
<div class="mockup-body">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
||||
<!-- Boxplot -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">AVG FPS Distribution (Boxplot)</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px;">
|
||||
[Boxplot: Min / Q1 / Median / Q3 / Max per device]
|
||||
</div>
|
||||
</div>
|
||||
<!-- Historical -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">Historical AVG FPS (Stacked Area)</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px;">
|
||||
[Area chart: FPS over time by proton/OS version]
|
||||
</div>
|
||||
</div>
|
||||
<!-- FSR/Frame Gen grouped bar -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">FPS by Upscaler & Frame Gen</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px;">
|
||||
[Grouped bar: FPS by FSR version × frame gen method]
|
||||
</div>
|
||||
</div>
|
||||
<!-- Device breakdown -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">Entries per Device</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px;">
|
||||
[Pie/donut or bar: count per device]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 5: Preset Entries</div>
|
||||
<div class="mockup-body">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 10px;">Community Presets</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-size: 13px; color: #ebe4f1; font-weight: 600;">"Best Quality" by @user1</div>
|
||||
<div style="font-size: 11px; color: #6b5a7d; margin-top: 2px;">Steam Deck OLED · ↑ 24 · 8 settings</div>
|
||||
</div>
|
||||
<div style="font-size: 13px; color: #22c55e; font-weight: 600;">Avg 45 FPS</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-size: 13px; color: #ebe4f1; font-weight: 600;">"Battery Saver" by @user2</div>
|
||||
<div style="font-size: 11px; color: #6b5a7d; margin-top: 2px;">Steam Deck OLED · ↑ 18 · 6 settings</div>
|
||||
</div>
|
||||
<div style="font-size: 13px; color: #fb793c; font-weight: 600;">Avg 60 FPS</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,223 +0,0 @@
|
||||
<h2>Game Page v2 — Updated Layout</h2>
|
||||
<p class="subtitle">Changes: "Raw Performer" badge, no duplicate stats, full-width priority charts, enhanced preset cards.</p>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- SECTION 1: HERO HEADER -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Section 1: Hero Header</div>
|
||||
<div class="mockup-body" style="padding: 0; overflow: hidden;">
|
||||
<div style="display: flex; gap: 16px; padding: 16px; background: linear-gradient(135deg, #1a1025, #0d0814);">
|
||||
<!-- Cover image (2:3 aspect ratio) -->
|
||||
<div style="width: 120px; height: 180px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 11px;">2:3 Cover</span>
|
||||
</div>
|
||||
<!-- Info panel -->
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||||
<span style="font-size: 18px; font-weight: 700; color: #ebe4f1;">Game Title Here</span>
|
||||
<!-- Raw Performer badge -->
|
||||
<span style="background: linear-gradient(135deg, #22c55e30, #22c55e10); border: 1px solid #22c55e50; padding: 2px 10px; border-radius: 12px; font-size: 10px; color: #4ade80; font-weight: 600; letter-spacing: 0.5px;">⚡ RAW PERFORMER</span>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #8b7a9e;">Developer · Publisher · Genre, Genre</div>
|
||||
<!-- 3 info badges + price/metacritic -->
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px;">
|
||||
<span style="background: #eb377920; border: 1px solid #eb377940; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #eb3779;">🎮 42 Benchmarks</span>
|
||||
<span style="background: #571b8b20; border: 1px solid #571b8b40; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #9b6fc7;">⚙️ 8 Presets</span>
|
||||
<span style="background: #fb793c20; border: 1px solid #fb793c40; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #fb793c;">💬 15 Comments</span>
|
||||
<span style="background: #22c55e20; border: 1px solid #22c55e40; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #22c55e;">Metascore: 87</span>
|
||||
<span style="background: #ebe4f110; border: 1px solid #ebe4f120; padding: 3px 8px; border-radius: 6px; font-size: 11px; color: #8b7a9e;">$29.99</span>
|
||||
</div>
|
||||
<!-- External links -->
|
||||
<div style="display: flex; gap: 6px; margin-top: 4px;">
|
||||
<span style="background: #1b2838; border: 1px solid #2a475e; padding: 3px 10px; border-radius: 6px; font-size: 10px; color: #c6d4df;">Steam ↗</span>
|
||||
<span style="background: #7c3aed15; border: 1px solid #7c3aed30; padding: 3px 10px; border-radius: 6px; font-size: 10px; color: #a78bfa;">ProtonDB ↗</span>
|
||||
<span style="background: #3b82f615; border: 1px solid #3b82f630; padding: 3px 10px; border-radius: 6px; font-size: 10px; color: #60a5fa;">SteamDB ↗</span>
|
||||
</div>
|
||||
<!-- DB Stats row (no duplicate benchmark count) -->
|
||||
<div style="display: flex; gap: 12px; margin-top: 4px; font-size: 10px; color: #6b5a7d;">
|
||||
<span>📋 3 versions</span>
|
||||
<span>✅ 12 verified</span>
|
||||
<span>🕐 Synced 2 days ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- SECTION 2: OVERVIEW -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 2: Overview</div>
|
||||
<div class="mockup-body">
|
||||
<div style="display: flex; gap: 24px;">
|
||||
<div style="flex: 2;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">About</div>
|
||||
<div style="font-size: 13px; color: #8b7a9e; line-height: 1.6;">Game description text goes here. This is the Steam short description that gets synced from the store page...</div>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">Details</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 12px;">
|
||||
<div><span style="color: #6b5a7d;">Source:</span> <span style="color: #8b7a9e;">Steam</span></div>
|
||||
<div><span style="color: #6b5a7d;">AppID:</span> <span style="color: #8b7a9e;">1234567</span></div>
|
||||
<div><span style="color: #6b5a7d;">Added:</span> <span style="color: #8b7a9e;">Jan 15, 2026</span></div>
|
||||
<div><span style="color: #6b5a7d;">Last Sync:</span> <span style="color: #8b7a9e;">2 days ago</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- SECTION 3: DEVICE SELECTOR + FILTERS -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 3: Device Selector Ribbon + Filters</div>
|
||||
<div class="mockup-body">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 10px;">Select Devices</div>
|
||||
<div style="display: flex; gap: 8px; overflow-x: auto; padding-bottom: 8px;">
|
||||
<div style="background: #eb377930; border: 1px solid #eb3779; padding: 6px 14px; border-radius: 20px; font-size: 12px; color: #eb3779; white-space: nowrap;">✓ Steam Deck OLED</div>
|
||||
<div style="background: #ebe4f108; border: 1px solid #ebe4f120; padding: 6px 14px; border-radius: 20px; font-size: 12px; color: #6b5a7d; white-space: nowrap;">Steam Deck LCD</div>
|
||||
<!-- Only devices with data are shown -->
|
||||
</div>
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin: 12px 0 8px;">Filters</div>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;"><option>All Proton Versions</option></select>
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;"><option>All SteamOS Versions</option></select>
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;"><option>FSR: Any</option></select>
|
||||
<select style="background: #1a1025; border: 1px solid #3d2d52; border-radius: 6px; padding: 4px 8px; font-size: 11px; color: #8b7a9e;"><option>Frame Gen: Any</option></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- SECTION 4: STATISTICS DASHBOARD -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 4: Statistics Dashboard (ECharts)</div>
|
||||
<div class="mockup-body" style="display: flex; flex-direction: column; gap: 12px;">
|
||||
|
||||
<!-- ROW 1: Full-width — Historical AVG FPS -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">📈 Historical AVG FPS — Full Width</div>
|
||||
<div style="height: 140px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Stacked Area: AVG FPS trend over time, grouped by Proton/SteamOS version]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROW 2: Full-width — FPS by Upscaler & Frame Gen -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">🎮 FPS by Upscaler & Frame Gen — Full Width</div>
|
||||
<div style="height: 140px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Grouped Bar: FPS by FSR version (none/1/2/3) × Frame Gen (none/fsr_fg/dlss_fg), with label rotation]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROW 3: Half-width grid — Boxplot + Device breakdown -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">📊 AVG FPS Boxplot</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Boxplot per device: Min/Q1/Median/Q3/Max]
|
||||
</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">🖥️ Entries per Device</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Donut: benchmark count per device]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROW 4: Half-width grid — FPS Range + Load Times -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">📉 FPS Range (Low vs High)</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Range bar: fpsLow → fpsHigh per entry]
|
||||
</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">⏱️ Load Times (SSD vs SD)</div>
|
||||
<div style="height: 120px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Bar: SSD vs SD load times — only shows if data exists]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROW 5: Full-width — Community Trust -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 8px;">✅ Community Trust — Full Width</div>
|
||||
<div style="height: 100px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 12px; border: 1px dashed #3d2d52; border-radius: 6px;">
|
||||
[Horizontal bar: verified badge count + upvote/downvote ratio per benchmark entry]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<!-- SECTION 5: PRESET ENTRIES -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════ -->
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Section 5: Community Presets</div>
|
||||
<div class="mockup-body" style="display: flex; flex-direction: column; gap: 10px;">
|
||||
<div style="font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #6b5a7d; margin-bottom: 4px;">Community Presets</div>
|
||||
|
||||
<!-- Preset 1: Raw Performer -->
|
||||
<div style="border: 1px solid #22c55e40; border-radius: 8px; padding: 12px; background: #22c55e08;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 14px; color: #ebe4f1; font-weight: 600;">"Vanilla 60fps"</span>
|
||||
<span style="background: linear-gradient(135deg, #22c55e30, #22c55e10); border: 1px solid #22c55e50; padding: 1px 8px; border-radius: 10px; font-size: 9px; color: #4ade80; font-weight: 600;">⚡ RAW PERFORMER</span>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #6b5a7d; margin-top: 4px;">by @user1 · Steam Deck OLED · ↑ 24 votes · 8 settings</div>
|
||||
<div style="font-size: 11px; color: #8b7a9e; margin-top: 6px; line-height: 1.4;">No FSR · No Frame Gen · Proton 9.0-4 · SteamOS 3.6.20</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 18px; color: #22c55e; font-weight: 700;">62 FPS</div>
|
||||
<div style="font-size: 10px; color: #6b5a7d;">avg · 48 low · 71 high</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preset 2: With upscaling -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 14px; color: #ebe4f1; font-weight: 600;">"Max Quality + FSR"</span>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #6b5a7d; margin-top: 4px;">by @user2 · Steam Deck OLED · ↑ 18 votes · 10 settings</div>
|
||||
<div style="font-size: 11px; color: #8b7a9e; margin-top: 6px; line-height: 1.4;">
|
||||
<span style="color: #a78bfa;">FSR 3</span> · <span style="color: #fb793c;">FSR Frame Gen</span> · Proton 9.0-4 · SteamOS 3.6.20
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 18px; color: #fb793c; font-weight: 700;">85 FPS</div>
|
||||
<div style="font-size: 10px; color: #6b5a7d;">avg · 72 low · 94 high</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preset 3: With DLSS frame gen -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 14px; color: #ebe4f1; font-weight: 600;">"Balanced + DLSS FG"</span>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: #6b5a7d; margin-top: 4px;">by @user3 · Steam Deck LCD · ↑ 9 votes · 7 settings</div>
|
||||
<div style="font-size: 11px; color: #8b7a9e; margin-top: 6px; line-height: 1.4;">
|
||||
<span style="color: #a78bfa;">FSR 2</span> · <span style="color: #60a5fa;">DLSS Frame Gen</span> · Proton 8.0-5 · SteamOS 3.5.19
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 18px; color: #60a5fa; font-weight: 700;">72 FPS</div>
|
||||
<div style="font-size: 10px; color: #6b5a7d;">avg · 58 low · 81 high</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,60 +0,0 @@
|
||||
<h2>Search Card — Raw Performer Badge</h2>
|
||||
<p class="subtitle">How the badge appears on search result cards. Same green pill style as the game page hero.</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Search Result Card with Raw Performer</div>
|
||||
<div class="mockup-body" style="padding: 0;">
|
||||
<!-- Card with badge -->
|
||||
<div style="display: flex; gap: 14px; padding: 14px; background: #ebe4f105; border: 1px solid #ebe4f110; border-radius: 12px; margin: 12px;">
|
||||
<!-- Cover -->
|
||||
<div style="width: 80px; height: 120px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 10px;">2:3</span>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 6px;">
|
||||
<!-- Title row with badge -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||||
<span style="font-size: 14px; font-weight: 600; color: #ebe4f1;">Baldur's Gate 3</span>
|
||||
<span style="background: linear-gradient(135deg, #22c55e30, #22c55e10); border: 1px solid #22c55e50; padding: 1px 8px; border-radius: 10px; font-size: 9px; color: #4ade80; font-weight: 600; letter-spacing: 0.5px;">⚡ RAW PERFORMER</span>
|
||||
</div>
|
||||
<span style="font-size: 11px; color: #8b7a9e;">Larian Studios · Publisher</span>
|
||||
<p style="font-size: 11px; color: #6b5a7d; line-height: 1.4; margin: 0;">Gather your party and return to the Forgotten Realms in a tale of fellowship and betrayal...</p>
|
||||
<!-- Tags -->
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px;">
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">RPG</span>
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">Strategy</span>
|
||||
</div>
|
||||
<!-- Stats -->
|
||||
<div style="display: flex; gap: 12px; margin-top: 4px;">
|
||||
<span style="font-size: 11px; color: #eb3779;">🎮 28 Benchmarks</span>
|
||||
<span style="font-size: 11px; color: #9b6fc7;">⚙️ 5 Presets</span>
|
||||
<span style="font-size: 11px; color: #fb793c;">💬 12 Comments</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card without badge (for comparison) -->
|
||||
<div style="display: flex; gap: 14px; padding: 14px; background: #ebe4f105; border: 1px solid #ebe4f110; border-radius: 12px; margin: 0 12px 12px;">
|
||||
<div style="width: 80px; height: 120px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 10px;">2:3</span>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 6px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-size: 14px; font-weight: 600; color: #ebe4f1;">Cyberpunk 2077</span>
|
||||
<!-- No badge -->
|
||||
</div>
|
||||
<span style="font-size: 11px; color: #8b7a9e;">CD Projekt Red · Publisher</span>
|
||||
<p style="font-size: 11px; color: #6b5a7d; line-height: 1.4; margin: 0;">An open-world action-adventure story set in Night City...</p>
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px;">
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">RPG</span>
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">Action</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 12px; margin-top: 4px;">
|
||||
<span style="font-size: 11px; color: #eb3779;">🎮 45 Benchmarks</span>
|
||||
<span style="font-size: 11px; color: #9b6fc7;">⚙️ 12 Presets</span>
|
||||
<span style="font-size: 11px; color: #fb793c;">💬 34 Comments</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,130 +0,0 @@
|
||||
<h2>Search Card — Final Design</h2>
|
||||
<p class="subtitle">Existing layout preserved. Added: Raw Performer badge, real Avg FPS, real Version. No other layout changes.</p>
|
||||
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Local Result — With Raw Performer</div>
|
||||
<div class="mockup-body" style="padding: 0;">
|
||||
<div style="display: flex; gap: 14px; padding: 14px; background: #ebe4f105; border: 1px solid #ebe4f110; border-radius: 12px; margin: 12px;">
|
||||
<!-- Cover (unchanged) -->
|
||||
<div style="width: 80px; height: 120px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 10px;">2:3</span>
|
||||
</div>
|
||||
<!-- Content (unchanged layout) -->
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 6px;">
|
||||
<!-- Title + Raw Performer badge (NEW) -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||||
<span style="font-size: 14px; font-weight: 600; color: #ebe4f1;">Baldur's Gate 3</span>
|
||||
<span style="background: linear-gradient(135deg, #22c55e30, #22c55e10); border: 1px solid #22c55e50; padding: 1px 8px; border-radius: 10px; font-size: 9px; color: #4ade80; font-weight: 600;">⚡ RAW PERFORMER</span>
|
||||
</div>
|
||||
<span style="font-size: 11px; color: #8b7a9e;">Larian Studios · Publisher</span>
|
||||
<p style="font-size: 11px; color: #6b5a7d; line-height: 1.4; margin: 0;">Gather your party and return to the Forgotten Realms...</p>
|
||||
<!-- Tags (unchanged) -->
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px;">
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">RPG</span>
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">Strategy</span>
|
||||
</div>
|
||||
<!-- Stats (unchanged) -->
|
||||
<div style="display: flex; gap: 12px; margin-top: 4px;">
|
||||
<span style="font-size: 11px; color: #eb3779;">🎮 28 Benchmarks</span>
|
||||
<span style="font-size: 11px; color: #9b6fc7;">⚙️ 5 Presets</span>
|
||||
<span style="font-size: 11px; color: #fb793c;">💬 12 Comments</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right panel (existing, now with real data) -->
|
||||
<div style="display: flex; flex-direction: column; align-items: end; justify-content: center; gap: 10px; min-width: 100px; border-left: 1px solid #ebe4f110; padding-left: 14px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d; letter-spacing: 0.5px;">Deck</div>
|
||||
<div style="font-size: 12px; color: #22c55e; font-weight: 500;">Native</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d; letter-spacing: 0.5px;">Best FPS</div>
|
||||
<div style="font-size: 12px; color: #22c55e; font-weight: 600; font-variant-numeric: tabular-nums;">68</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d; letter-spacing: 0.5px;">Version</div>
|
||||
<div style="font-size: 12px; color: #8b7a9e; font-variant-numeric: tabular-nums;">v4.1.1.6</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Local Result — Without Raw Performer (for comparison)</div>
|
||||
<div class="mockup-body" style="padding: 0;">
|
||||
<div style="display: flex; gap: 14px; padding: 14px; background: #ebe4f105; border: 1px solid #ebe4f110; border-radius: 12px; margin: 12px;">
|
||||
<div style="width: 80px; height: 120px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 10px;">2:3</span>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 6px;">
|
||||
<!-- Title — no badge -->
|
||||
<span style="font-size: 14px; font-weight: 600; color: #ebe4f1;">Cyberpunk 2077</span>
|
||||
<span style="font-size: 11px; color: #8b7a9e;">CD Projekt Red · Publisher</span>
|
||||
<p style="font-size: 11px; color: #6b5a7d; line-height: 1.4; margin: 0;">An open-world action-adventure story set in Night City...</p>
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px;">
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">RPG</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 12px; margin-top: 4px;">
|
||||
<span style="font-size: 11px; color: #eb3779;">🎮 45 Benchmarks</span>
|
||||
<span style="font-size: 11px; color: #9b6fc7;">⚙️ 12 Presets</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right panel — best FPS lower, no raw performer -->
|
||||
<div style="display: flex; flex-direction: column; align-items: end; justify-content: center; gap: 10px; min-width: 100px; border-left: 1px solid #ebe4f110; padding-left: 14px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d; letter-spacing: 0.5px;">Deck</div>
|
||||
<div style="font-size: 12px; color: #3b82f6; font-weight: 500;">Proton</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d; letter-spacing: 0.5px;">Best FPS</div>
|
||||
<div style="font-size: 12px; color: #fb793c; font-weight: 600; font-variant-numeric: tabular-nums;">42</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d; letter-spacing: 0.5px;">Version</div>
|
||||
<div style="font-size: 12px; color: #8b7a9e; font-variant-numeric: tabular-nums;">v2.13</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mockup" style="margin-top: 16px;">
|
||||
<div class="mockup-header">Steam-Only Result (unchanged — no local data)</div>
|
||||
<div class="mockup-body" style="padding: 0;">
|
||||
<div style="display: flex; gap: 14px; padding: 14px; background: #ebe4f105; border: 1px solid #ebe4f110; border-radius: 12px; margin: 12px;">
|
||||
<div style="width: 80px; height: 120px; border-radius: 8px; background: #2a1f35; display: flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid #3d2d52;">
|
||||
<span style="color: #6b5a7d; font-size: 10px;">2:3</span>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 6px;">
|
||||
<span style="font-size: 14px; font-weight: 600; color: #ebe4f1;">Elden Ring</span>
|
||||
<span style="font-size: 11px; color: #8b7a9e;">FromSoftware · Bandai Namco</span>
|
||||
<p style="font-size: 11px; color: #6b5a7d; line-height: 1.4; margin: 0;">THE NEW FANTASY ACTION RPG...</p>
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px;">
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">RPG</span>
|
||||
<span style="font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #ebe4f108; border: 1px solid #ebe4f115; color: #6b5a7d;">Action</span>
|
||||
</div>
|
||||
<!-- Steam links (unchanged) -->
|
||||
<div style="display: flex; gap: 6px; margin-top: 4px;">
|
||||
<span style="font-size: 10px; padding: 3px 8px; border-radius: 6px; background: #1b2838; border: 1px solid #2a475e; color: #c6d4df;">Steam ↗</span>
|
||||
<span style="font-size: 10px; padding: 3px 8px; border-radius: 6px; background: #7c3aed15; border: 1px solid #7c3aed30; color: #a78bfa;">ProtonDB ↗</span>
|
||||
<span style="font-size: 10px; padding: 3px 8px; border-radius: 6px; background: #3b82f615; border: 1px solid #3b82f630; color: #60a5fa;">SteamDB ↗</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right panel — all dashes (unchanged) -->
|
||||
<div style="display: flex; flex-direction: column; align-items: end; justify-content: center; gap: 10px; min-width: 100px; border-left: 1px solid #ebe4f110; padding-left: 14px;">
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Deck</div>
|
||||
<div style="font-size: 12px; color: #6b5a7d;">—</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Best FPS</div>
|
||||
<div style="font-size: 12px; color: #6b5a7d;">—</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Version</div>
|
||||
<div style="font-size: 12px; color: #6b5a7d;">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,191 +0,0 @@
|
||||
<h2>Stats Dashboard — Layout Alternatives</h2>
|
||||
<p class="subtitle">Three layout options for the statistics section. Click the one that feels best.</p>
|
||||
|
||||
<style>
|
||||
.layout-demo { border: 1px solid #3d2d52; border-radius: 10px; padding: 16px; background: #0d0814; }
|
||||
.chart-placeholder { border: 1px dashed #3d2d52; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #4a3a5c; font-size: 11px; text-align: center; padding: 8px; }
|
||||
.chart-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.8px; color: #6b5a7d; margin-bottom: 6px; }
|
||||
.full-badge { display: inline-block; font-size: 8px; background: #eb377920; color: #eb3779; padding: 1px 5px; border-radius: 4px; margin-left: 4px; vertical-align: middle; }
|
||||
</style>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════ -->
|
||||
<!-- OPTION A: Featured Row + Asymmetric Grid -->
|
||||
<!-- ════════════════════════════════════════════════════════════ -->
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)" style="cursor: pointer; border-color: transparent;">
|
||||
<div class="letter" style="margin-bottom: 12px;">A</div>
|
||||
<h3 style="margin-bottom: 4px;">Featured Row + Asymmetric Grid</h3>
|
||||
<p class="subtitle" style="margin-bottom: 16px;">Top chart gets a larger container with summary stats beside it. Remaining charts in a flowing grid.</p>
|
||||
|
||||
<div class="layout-demo">
|
||||
<!-- Featured: Historical with side stats -->
|
||||
<div style="display: flex; gap: 12px; margin-bottom: 12px;">
|
||||
<div style="flex: 3; border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div class="chart-label">📈 Historical AVG FPS <span class="full-badge">FEATURED</span></div>
|
||||
<div class="chart-placeholder" style="height: 160px;">[Stacked Area Chart — full width of container]</div>
|
||||
</div>
|
||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Overall Avg</div>
|
||||
<div style="font-size: 22px; font-weight: 700; color: #22c55e;">54 FPS</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Best Device</div>
|
||||
<div style="font-size: 13px; font-weight: 600; color: #ebe4f1;">OLED</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Entries</div>
|
||||
<div style="font-size: 22px; font-weight: 700; color: #a78bfa;">42</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-width: Upscaler grouped bar -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
|
||||
<div class="chart-label">🎮 FPS by Upscaler & Frame Gen <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 130px;">[Grouped Bar — FSR version × Frame Gen method]</div>
|
||||
</div>
|
||||
|
||||
<!-- 3-column grid -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin-bottom: 12px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">📊 Boxplot</div>
|
||||
<div class="chart-placeholder" style="height: 100px;">[FPS distribution per device]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">📉 FPS Range</div>
|
||||
<div class="chart-placeholder" style="height: 100px;">[Low → High per entry]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">🖥️ Devices</div>
|
||||
<div class="chart-placeholder" style="height: 100px;">[Donut: entries per device]</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-width: Community Trust -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div class="chart-label">✅ Community Trust <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 80px;">[Horizontal bar: verified + vote ratio per entry]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════ -->
|
||||
<!-- OPTION B: Dashboard Cards with KPI Strip -->
|
||||
<!-- ════════════════════════════════════════════════════════════ -->
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)" style="cursor: pointer; border-color: transparent; margin-top: 24px;">
|
||||
<div class="letter" style="margin-bottom: 12px;">B</div>
|
||||
<h3 style="margin-bottom: 4px;">Dashboard Cards with KPI Strip</h3>
|
||||
<p class="subtitle" style="margin-bottom: 16px;">Quick-glance KPI numbers at top, then a clean 2×2 grid of charts, with full-width charts breaking the rhythm where needed.</p>
|
||||
|
||||
<div class="layout-demo">
|
||||
<!-- KPI Strip -->
|
||||
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-bottom: 14px;">
|
||||
<div style="background: #22c55e10; border: 1px solid #22c55e30; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Avg FPS</div>
|
||||
<div style="font-size: 20px; font-weight: 700; color: #22c55e;">54</div>
|
||||
</div>
|
||||
<div style="background: #3b82f610; border: 1px solid #3b82f630; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Best</div>
|
||||
<div style="font-size: 20px; font-weight: 700; color: #60a5fa;">71</div>
|
||||
</div>
|
||||
<div style="background: #fb793c10; border: 1px solid #fb793c30; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Worst</div>
|
||||
<div style="font-size: 20px; font-weight: 700; color: #fb793c;">38</div>
|
||||
</div>
|
||||
<div style="background: #a78bfa10; border: 1px solid #a78bfa30; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Std Dev</div>
|
||||
<div style="font-size: 20px; font-weight: 700; color: #a78bfa;">±8</div>
|
||||
</div>
|
||||
<div style="background: #ebe4f108; border: 1px solid #ebe4f115; border-radius: 8px; padding: 10px; text-align: center;">
|
||||
<div style="font-size: 9px; text-transform: uppercase; color: #6b5a7d;">Entries</div>
|
||||
<div style="font-size: 20px; font-weight: 700; color: #ebe4f1;">42</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-width: Historical -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px; margin-bottom: 10px;">
|
||||
<div class="chart-label">📈 Historical AVG FPS <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 150px;">[Stacked Area]</div>
|
||||
</div>
|
||||
|
||||
<!-- 2×2 Grid -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">🎮 FPS by Upscaler & Frame Gen</div>
|
||||
<div class="chart-placeholder" style="height: 120px;">[Grouped Bar]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">📊 FPS Distribution (Boxplot)</div>
|
||||
<div class="chart-placeholder" style="height: 120px;">[Boxplot per device]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">📉 FPS Range</div>
|
||||
<div class="chart-placeholder" style="height: 120px;">[Low → High]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">🖥️ Entries per Device</div>
|
||||
<div class="chart-placeholder" style="height: 120px;">[Donut]</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Full-width: Community Trust -->
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div class="chart-label">✅ Community Trust <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 80px;">[Horizontal bar]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════ -->
|
||||
<!-- OPTION C: Sectioned with Visual Separator -->
|
||||
<!-- ════════════════════════════════════════════════════════════ -->
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)" style="cursor: pointer; border-color: transparent; margin-top: 24px;">
|
||||
<div class="letter" style="margin-bottom: 12px;">C</div>
|
||||
<h3 style="margin-bottom: 4px;">Sectioned with Visual Separator</h3>
|
||||
<p class="subtitle" style="margin-bottom: 16px;">Divides stats into logical groups with subtle dividers. Each group has a label. Full-width charts anchor each section.</p>
|
||||
|
||||
<div class="layout-demo">
|
||||
<!-- Section: Performance Overview -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="font-size: 10px; text-transform: uppercase; letter-spacing: 1.2px; color: #eb3779; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid #eb377920;">Performance Overview</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px; margin-bottom: 10px;">
|
||||
<div class="chart-label">📈 Historical AVG FPS <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 150px;">[Stacked Area]</div>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">📊 FPS Distribution</div>
|
||||
<div class="chart-placeholder" style="height: 110px;">[Boxplot]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">📉 FPS Range</div>
|
||||
<div class="chart-placeholder" style="height: 110px;">[Low → High]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Technology Impact -->
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="font-size: 10px; text-transform: uppercase; letter-spacing: 1.2px; color: #a78bfa; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid #a78bfa20;">Technology Impact</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 12px;">
|
||||
<div class="chart-label">🎮 FPS by Upscaler & Frame Gen <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 130px;">[Grouped Bar]</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Data Coverage & Trust -->
|
||||
<div>
|
||||
<div style="font-size: 10px; text-transform: uppercase; letter-spacing: 1.2px; color: #22c55e; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid #22c55e20;">Data Coverage & Trust</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 10px; margin-bottom: 10px;">
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">🖥️ Devices</div>
|
||||
<div class="chart-placeholder" style="height: 100px;">[Donut]</div>
|
||||
</div>
|
||||
<div style="border: 1px solid #3d2d52; border-radius: 8px; padding: 10px;">
|
||||
<div class="chart-label">✅ Community Trust <span class="full-badge">FULL</span></div>
|
||||
<div class="chart-placeholder" style="height: 100px;">[Horizontal bar]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,63 +0,0 @@
|
||||
<h2>Stats Dashboard — Chart Priority</h2>
|
||||
<p class="subtitle">Which charts should be <strong>full-width</strong> (high importance) vs half-width? Select all that you want full-width.</p>
|
||||
|
||||
<div class="options" data-multiselect>
|
||||
<div class="option" data-choice="boxplot" onclick="toggleSelect(this)">
|
||||
<div class="letter">📊</div>
|
||||
<div class="content">
|
||||
<h3>AVG FPS Boxplot</h3>
|
||||
<p>Min / Q1 / Median / Q3 / Max per device. Shows spread and outliers. Core metric.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="historical" onclick="toggleSelect(this)">
|
||||
<div class="letter">📈</div>
|
||||
<div class="content">
|
||||
<h3>Historical AVG FPS (Stacked Area)</h3>
|
||||
<p>FPS trend over time by Proton/SteamOS version. Shows if updates help or hurt performance.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="upscaler-bar" onclick="toggleSelect(this)">
|
||||
<div class="letter">🎮</div>
|
||||
<div class="content">
|
||||
<h3>FPS by Upscaler & Frame Gen (Grouped Bar)</h3>
|
||||
<p>Compare FPS across FSR versions × frame gen methods. Shows the real cost/benefit of each combo.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="device-pie" onclick="toggleSelect(this)">
|
||||
<div class="letter">🖥️</div>
|
||||
<div class="content">
|
||||
<h3>Entries per Device (Donut)</h3>
|
||||
<p>Simple count of benchmarks per hardware. Shows data coverage.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="fps-range" onclick="toggleSelect(this)">
|
||||
<div class="letter">📉</div>
|
||||
<div class="content">
|
||||
<h3>FPS Range: Low vs High (Range Bar)</h3>
|
||||
<p>Visualizes fpsLow to fpsHigh spread per entry. Shows consistency — tight range = stable performance.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="load-times" onclick="toggleSelect(this)">
|
||||
<div class="letter">⏱️</div>
|
||||
<div class="content">
|
||||
<h3>Load Times: SSD vs SD (Bar)</h3>
|
||||
<p>Compare storage load times. Only shows when data exists. Useful for games with long loads.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="proton-breakdown" onclick="toggleSelect(this)">
|
||||
<div class="letter">🐧</div>
|
||||
<div class="content">
|
||||
<h3>Proton Version Breakdown (Stacked Bar)</h3>
|
||||
<p>Distribution of entries by Proton version. Shows which Proton builds are most tested.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="verified-sentiment" onclick="toggleSelect(this)">
|
||||
<div class="letter">✅</div>
|
||||
<div class="content">
|
||||
<h3>Community Trust: Verified & Votes (Horizontal Bar)</h3>
|
||||
<p>Shows verified count + upvote/downvote ratio per entry. Helps identify the most trusted benchmarks.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="subtitle" style="margin-top: 20px;">Charts you select will be full-width (one per row). Unselected charts will be half-width (2 per row).</p>
|
||||
@@ -1 +0,0 @@
|
||||
{"reason":"idle timeout","timestamp":1777190496647}
|
||||
@@ -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"}
|
||||
@@ -1 +0,0 @@
|
||||
44103
|
||||
@@ -367,7 +367,7 @@ export function GamePageClient({
|
||||
{session && (
|
||||
<Link
|
||||
href={`/game/${game.id}/submit`}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors"
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-white text-sm font-semibold hover:bg-primary/90 transition-colors cursor-pointer"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
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"
|
||||
>
|
||||
<FaSteam className="h-3.5 w-3.5" />
|
||||
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
|
||||
<ExternalLinkIcon className="h-3 w-3" />
|
||||
@@ -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
|
||||
<ExternalLinkIcon className="h-3 w-3" />
|
||||
@@ -537,7 +537,7 @@ export function GamePageClient({
|
||||
<button
|
||||
key={device.hardwareSlug}
|
||||
onClick={() => 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"
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export default function NotFound() {
|
||||
>
|
||||
<Link
|
||||
href="/"
|
||||
className="px-6 py-3 rounded-full bg-primary text-background font-semibold hover:bg-primary/80 transition-colors"
|
||||
className="px-6 py-3 rounded-full bg-primary text-background font-semibold hover:bg-primary/80 transition-colors cursor-pointer"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ export default function Landing() {
|
||||
<Link
|
||||
title="Visit our Github Repository"
|
||||
href="https://github.com/AdrianBonpin/deckyvault"
|
||||
className="text-accent opacity-60 hover:opacity-100 transition-opacity"
|
||||
className="text-accent opacity-60 hover:opacity-100 transition-opacity cursor-pointer"
|
||||
>
|
||||
Github.
|
||||
</Link>
|
||||
|
||||
+201
-165
@@ -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<Tab>("overview")
|
||||
const [settingsSubTab, setSettingsSubTab] = useState<SettingsTab>("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<ContributionEntry[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const router = useRouter()
|
||||
const { data: session, isPending: isSessionLoading } = useSession()
|
||||
const [activeTab, setActiveTab] = useState<Tab>("overview")
|
||||
const [settingsSubTab, setSettingsSubTab] = useState<SettingsTab>("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<ContributionEntry[]>([])
|
||||
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 (
|
||||
<div className='flex items-center justify-center min-h-[50vh]'>
|
||||
<Loader2 className='h-8 w-8 animate-spin text-primary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="flex items-center justify-center min-h-[50vh]">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="w-full flex flex-col gap-8 pb-16">
|
||||
{/* Profile header section */}
|
||||
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} className="px-4 md:px-[10svw]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<ProfileHeader
|
||||
name={profile.name}
|
||||
email={profile.email}
|
||||
role={profile.role}
|
||||
verified={profile.verified}
|
||||
createdAt={profile.createdAt}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Stats section */}
|
||||
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.1 }} className="px-4 md:px-[10svw]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<StatsRow
|
||||
contributions={profile.contributions}
|
||||
verifiedEntries={profile.verifiedEntries}
|
||||
reputation={profile.reputation}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Tabs + content section */}
|
||||
<motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.15 }} className="px-4 md:px-[10svw]">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 border-b border-border">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => 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.icon className="h-4 w-4" />
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<motion.div
|
||||
key={activeTab}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="pt-6"
|
||||
>
|
||||
{activeTab === "overview" && (
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-4">Recent Contributions</h2>
|
||||
<ContributionList entries={contributions} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "saved" && <SavedGamesGrid />}
|
||||
|
||||
{activeTab === "settings" && (
|
||||
<div className="space-y-4">
|
||||
{/* Settings sub-tabs */}
|
||||
<div className="flex gap-1 border-b border-border">
|
||||
{([
|
||||
{ id: "profile" as SettingsTab, label: "Profile" },
|
||||
{ id: "security" as SettingsTab, label: "Security" },
|
||||
{ id: "accounts" as SettingsTab, label: "Linked Accounts" },
|
||||
]).map((subTab) => (
|
||||
<button
|
||||
key={subTab.id}
|
||||
onClick={() => 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}
|
||||
</button>
|
||||
))}
|
||||
<div className='w-full flex flex-col gap-8 pt-4'>
|
||||
{/* Profile header section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className='px-4 md:px-[10svw]'
|
||||
>
|
||||
<div className='max-w-7xl mx-auto'>
|
||||
<ProfileHeader
|
||||
name={profile.name}
|
||||
email={profile.email}
|
||||
role={profile.role}
|
||||
verified={profile.verified}
|
||||
createdAt={profile.createdAt}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Settings sub-tab content */}
|
||||
{settingsSubTab === "profile" && (
|
||||
<SettingsProfileTab
|
||||
name={profile.name}
|
||||
email={profile.email}
|
||||
role={profile.role}
|
||||
createdAt={profile.createdAt}
|
||||
/>
|
||||
)}
|
||||
{settingsSubTab === "security" && <SettingsSecurityTab />}
|
||||
{settingsSubTab === "accounts" && <SettingsAccountsTab />}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
{/* Stats section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.1 }}
|
||||
className='px-4 md:px-[10svw]'
|
||||
>
|
||||
<div className='max-w-7xl mx-auto'>
|
||||
<StatsRow
|
||||
contributions={profile.contributions}
|
||||
verifiedEntries={profile.verifiedEntries}
|
||||
reputation={profile.reputation}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Tabs + content section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4, delay: 0.15 }}
|
||||
className='px-4 md:px-[10svw]'
|
||||
>
|
||||
<div className='max-w-7xl mx-auto'>
|
||||
{/* Tabs */}
|
||||
<div className='flex gap-1 border-b border-border'>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => 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.icon className='h-4 w-4' />
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<motion.div
|
||||
key={activeTab}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className='pt-6'
|
||||
>
|
||||
{activeTab === "overview" && (
|
||||
<div>
|
||||
<h2 className='text-lg font-semibold mb-4'>
|
||||
Recent Contributions
|
||||
</h2>
|
||||
<ContributionList entries={contributions} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "saved" && <SavedGamesGrid />}
|
||||
|
||||
{activeTab === "settings" && (
|
||||
<div className='space-y-4'>
|
||||
{/* Settings sub-tabs */}
|
||||
<div className='flex gap-1 border-b border-border'>
|
||||
{[
|
||||
{
|
||||
id: "profile" as SettingsTab,
|
||||
label: "Profile",
|
||||
},
|
||||
{
|
||||
id: "security" as SettingsTab,
|
||||
label: "Security",
|
||||
},
|
||||
{
|
||||
id: "accounts" as SettingsTab,
|
||||
label: "Linked Accounts",
|
||||
},
|
||||
].map((subTab) => (
|
||||
<button
|
||||
key={subTab.id}
|
||||
onClick={() =>
|
||||
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}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Settings sub-tab content */}
|
||||
{settingsSubTab === "profile" && (
|
||||
<SettingsProfileTab
|
||||
name={profile.name}
|
||||
email={profile.email}
|
||||
role={profile.role}
|
||||
createdAt={profile.createdAt}
|
||||
/>
|
||||
)}
|
||||
{settingsSubTab === "security" && (
|
||||
<SettingsSecurityTab />
|
||||
)}
|
||||
{settingsSubTab === "accounts" && (
|
||||
<SettingsAccountsTab />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -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"
|
||||
>
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function ForgotPasswordForm() {
|
||||
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex items-center justify-center gap-2 text-sm text-text/50 hover:text-text transition-colors"
|
||||
className="flex items-center justify-center gap-2 text-sm text-text/50 hover:text-text transition-colors cursor-pointer"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to sign in
|
||||
|
||||
@@ -190,7 +190,7 @@ export default function LoginForm() {
|
||||
<div className="text-right">
|
||||
<Link
|
||||
href="/forgot-password"
|
||||
className="text-sm text-text/50 hover:text-text transition-colors"
|
||||
className="text-sm text-text/50 hover:text-text transition-colors cursor-pointer"
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
@@ -218,7 +218,7 @@ export default function LoginForm() {
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
href="/signup"
|
||||
className="text-primary hover:underline"
|
||||
className="text-primary hover:underline cursor-pointer"
|
||||
>
|
||||
Create one
|
||||
</Link>
|
||||
|
||||
@@ -133,7 +133,7 @@ export default function OtpVerificationStep({
|
||||
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="flex items-center justify-center gap-2 w-full text-sm text-text/50 hover:text-text transition-colors"
|
||||
className="flex items-center justify-center gap-2 w-full text-sm text-text/50 hover:text-text transition-colors cursor-pointer"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to signup
|
||||
|
||||
@@ -208,7 +208,7 @@ export default function ResetPasswordForm({
|
||||
|
||||
<Link
|
||||
href="/login"
|
||||
className="flex items-center justify-center gap-2 text-sm text-text/50 hover:text-text transition-colors"
|
||||
className="flex items-center justify-center gap-2 text-sm text-text/50 hover:text-text transition-colors cursor-pointer"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to sign in
|
||||
|
||||
@@ -143,7 +143,7 @@ export default function SignupFormStep({ onSuccess }: SignupFormStepProps) {
|
||||
|
||||
<p className="text-center text-sm text-text/50">
|
||||
Already have an account?{" "}
|
||||
<Link href="/login" className="text-primary hover:underline">
|
||||
<Link href="/login" className="text-primary hover:underline cursor-pointer">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
+10
-10
@@ -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}
|
||||
</Link>
|
||||
@@ -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" && <User className='h-4 w-4' />}
|
||||
{route.icon === "Bookmark" && <Bookmark className='h-4 w-4' />}
|
||||
@@ -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'
|
||||
>
|
||||
<LogOut className='h-4 w-4' />
|
||||
Sign out
|
||||
@@ -291,7 +291,7 @@ export default function Navbar() {
|
||||
>
|
||||
<Link
|
||||
href='/login'
|
||||
className='text-sm font-medium hover:text-primary transition-colors uppercase'
|
||||
className='text-sm font-medium hover:text-primary transition-colors uppercase cursor-pointer'
|
||||
>
|
||||
login
|
||||
</Link>
|
||||
@@ -303,7 +303,7 @@ export default function Navbar() {
|
||||
{/* Mobile Hamburger Button */}
|
||||
<button
|
||||
onClick={() => 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'
|
||||
>
|
||||
<MenuIcon className='h-5 w-5' />
|
||||
@@ -338,7 +338,7 @@ export default function Navbar() {
|
||||
<span className='font-bold text-lg'>Menu</span>
|
||||
<button
|
||||
onClick={() => 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'
|
||||
>
|
||||
<XIcon className='h-5 w-5' />
|
||||
@@ -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" && <User className='h-4 w-4' />}
|
||||
{route.icon === "Bookmark" && <Bookmark className='h-4 w-4' />}
|
||||
@@ -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'
|
||||
>
|
||||
<LogOut className='h-4 w-4' />
|
||||
Sign out
|
||||
@@ -394,7 +394,7 @@ export default function Navbar() {
|
||||
<Link
|
||||
href='/login'
|
||||
onClick={() => 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
|
||||
</Link>
|
||||
|
||||
@@ -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() {
|
||||
<button
|
||||
onClick={() => 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 ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
@@ -357,7 +357,7 @@ export function SettingsSecurityTab() {
|
||||
</button>
|
||||
<button
|
||||
onClick={cancelEditingPasskey}
|
||||
className="p-1 rounded hover:bg-red-500/10 text-red-400 transition-colors"
|
||||
className="p-1 rounded hover:bg-red-500/10 text-red-400 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -117,7 +117,7 @@ export function SavedGamesGrid() {
|
||||
<button
|
||||
onClick={() => 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 ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
|
||||
@@ -81,7 +81,7 @@ export function TiptapEditor({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => 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({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => 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({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => 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({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => 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({
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleLink}
|
||||
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("link") ? "bg-text/10 text-primary" : "text-text/50"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user