q-simplified / static /login.html
Deployment Agent
Initial Deploy Q-Simplified v1.0 - The Financial Architect
20511c5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Login β€” Q-Simplified</title>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Work+Sans:wght@300;400;500;600&display=swap" rel="stylesheet"/>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--primary:#003465;
--primary-mid:#004b8d;
--surface:#f8f9fa;
--surface-card:#ffffff;
--surface-low:#f3f4f5;
--tertiary:#8d2400;
--on-tertiary:#ffdbd1;
--text-primary:#191c1d;
--text-secondary:#4e5f7b;
--text-muted:#7a8fa6;
--error:#c0392b;
--success:#1a7a4a;
--radius-sm:0.5rem;
--radius-md:1rem;
--radius-lg:1.5rem;
--radius-xl:3rem;
--radius-full:9999px;
--shadow:0 8px 48px rgba(0,52,101,0.10);
}
body{
font-family:'Work Sans',sans-serif;
background:var(--surface);
color:var(--text-primary);
min-height:100vh;
display:grid;
grid-template-columns:1fr 1fr;
}
/* ── LEFT PANEL ── */
.left-panel{
background:var(--primary);
padding:3rem;
display:flex;
flex-direction:column;
justify-content:space-between;
position:relative;
overflow:hidden;
min-height:100vh;
}
.left-panel::before{
content:'';
position:absolute;
top:-80px;right:-80px;
width:400px;height:400px;
border-radius:50%;
background:rgba(255,255,255,0.03);
}
.left-panel::after{
content:'';
position:absolute;
bottom:-100px;left:-60px;
width:300px;height:300px;
border-radius:50%;
background:rgba(255,255,255,0.03);
}
.brand-logo{
font-family:'Manrope',sans-serif;
font-weight:800;
font-size:1.375rem;
color:#fff;
letter-spacing:-0.02em;
text-decoration:none;
position:relative;
z-index:1;
}
.left-hero{position:relative;z-index:1;flex:1;display:flex;flex-direction:column;justify-content:center;padding:2rem 0}
.left-tagline{
font-size:0.6875rem;
font-weight:600;
letter-spacing:0.12em;
text-transform:uppercase;
color:var(--on-tertiary);
opacity:0.8;
margin-bottom:1rem;
}
.left-headline{
font-family:'Manrope',sans-serif;
font-weight:800;
font-size:2.75rem;
color:#fff;
line-height:1.15;
letter-spacing:-0.03em;
margin-bottom:1.25rem;
}
.left-headline span{color:var(--on-tertiary)}
.left-desc{
font-size:0.9375rem;
color:rgba(255,255,255,0.65);
line-height:1.7;
max-width:380px;
}
.left-stats{
display:flex;
gap:2rem;
margin-top:2.5rem;
position:relative;
z-index:1;
}
.stat-item{}
.stat-num{
font-family:'Manrope',sans-serif;
font-weight:800;
font-size:1.5rem;
color:#fff;
}
.stat-label{
font-size:0.75rem;
color:rgba(255,255,255,0.5);
margin-top:0.125rem;
}
.left-indicators{
position:relative;
z-index:1;
display:flex;
flex-wrap:wrap;
gap:0.5rem;
}
.ind-badge{
background:rgba(255,255,255,0.08);
border-radius:var(--radius-full);
padding:0.375rem 0.875rem;
font-size:0.75rem;
color:rgba(255,255,255,0.75);
font-weight:500;
}
.ind-badge strong{color:#fff;font-weight:700}
/* ── RIGHT PANEL ── */
.right-panel{
display:flex;
align-items:center;
justify-content:center;
padding:3rem 2rem;
min-height:100vh;
}
.auth-card{
width:100%;
max-width:440px;
}
.auth-tabs{
display:flex;
background:var(--surface-low);
border-radius:var(--radius-full);
padding:4px;
margin-bottom:2rem;
}
.auth-tab{
flex:1;
padding:0.625rem;
border-radius:var(--radius-full);
border:none;
background:transparent;
font-family:'Work Sans',sans-serif;
font-size:0.875rem;
font-weight:500;
color:var(--text-muted);
cursor:pointer;
transition:all 0.2s;
}
.auth-tab.active{
background:var(--surface-card);
color:var(--primary);
font-weight:600;
box-shadow:0 2px 12px rgba(0,52,101,0.08);
}
.auth-form{display:none}
.auth-form.active{display:block}
.form-title{
font-family:'Manrope',sans-serif;
font-weight:700;
font-size:1.625rem;
color:var(--text-primary);
letter-spacing:-0.02em;
margin-bottom:0.375rem;
}
.form-subtitle{
font-size:0.875rem;
color:var(--text-muted);
margin-bottom:2rem;
line-height:1.5;
}
.form-group{margin-bottom:1.125rem}
.form-label{
display:block;
font-size:0.8125rem;
font-weight:500;
color:var(--text-secondary);
margin-bottom:0.375rem;
}
.form-input{
width:100%;
padding:0.75rem 1rem;
background:var(--surface-low);
border:2px solid transparent;
border-radius:var(--radius-sm);
font-family:'Work Sans',sans-serif;
font-size:0.9375rem;
color:var(--text-primary);
transition:all 0.2s;
outline:none;
}
.form-input::placeholder{color:var(--text-muted)}
.form-input:focus{
background:var(--surface-card);
border-color:var(--primary);
box-shadow:0 0 0 4px rgba(0,52,101,0.08);
}
.form-input.error{border-color:var(--error)}
.form-error{
font-size:0.75rem;
color:var(--error);
margin-top:0.25rem;
display:none;
}
.form-error.show{display:block}
.password-wrapper{position:relative}
.password-toggle{
position:absolute;
right:0.875rem;top:50%;
transform:translateY(-50%);
background:none;border:none;
cursor:pointer;color:var(--text-muted);
padding:0.25rem;
transition:color 0.2s;
}
.password-toggle:hover{color:var(--text-primary)}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:0.75rem}
.forgot-link{
font-size:0.8125rem;
color:var(--text-secondary);
text-decoration:none;
float:right;
margin-top:-0.875rem;
margin-bottom:0.875rem;
transition:color 0.2s;
}
.forgot-link:hover{color:var(--primary)}
.btn-submit{
width:100%;
padding:0.875rem;
background:linear-gradient(135deg,var(--primary) 0%,var(--primary-mid) 100%);
color:#fff;
border:none;
border-radius:var(--radius-full);
font-family:'Work Sans',sans-serif;
font-size:0.9375rem;
font-weight:600;
cursor:pointer;
transition:all 0.2s;
margin-top:0.5rem;
display:flex;
align-items:center;
justify-content:center;
gap:0.5rem;
position:relative;
overflow:hidden;
}
.btn-submit:hover{transform:translateY(-1px);box-shadow:0 8px 24px rgba(0,52,101,0.25)}
.btn-submit:active{transform:translateY(0)}
.btn-submit:disabled{opacity:0.6;cursor:not-allowed;transform:none}
.btn-submit .spinner{
width:18px;height:18px;
border:2px solid rgba(255,255,255,0.3);
border-top-color:#fff;
border-radius:50%;
animation:spin 0.7s linear infinite;
display:none;
}
.btn-submit.loading .spinner{display:block}
.btn-submit.loading .btn-text{display:none}
@keyframes spin{to{transform:rotate(360deg)}}
.divider{
display:flex;align-items:center;gap:1rem;
margin:1.5rem 0;
color:var(--text-muted);font-size:0.8125rem;
}
.divider::before,.divider::after{
content:'';flex:1;height:1px;background:var(--surface-low);
}
.btn-google{
width:100%;
padding:0.75rem;
background:var(--surface-card);
border:1.5px solid var(--surface-low);
border-radius:var(--radius-full);
font-family:'Work Sans',sans-serif;
font-size:0.875rem;
font-weight:500;
color:var(--text-primary);
cursor:pointer;
display:flex;align-items:center;justify-content:center;gap:0.625rem;
transition:all 0.2s;
}
.btn-google:hover{border-color:var(--text-muted);background:var(--surface-low)}
.terms-text{
font-size:0.75rem;
color:var(--text-muted);
text-align:center;
margin-top:1.25rem;
line-height:1.6;
}
.terms-text a{color:var(--text-secondary);text-decoration:none;font-weight:500}
.terms-text a:hover{color:var(--primary)}
.alert{
padding:0.875rem 1rem;
border-radius:var(--radius-sm);
font-size:0.875rem;
margin-bottom:1rem;
display:none;
}
.alert.show{display:block}
.alert.error{background:#fef2f2;color:var(--error);border:1px solid #fecaca}
.alert.success{background:#f0fdf4;color:var(--success);border:1px solid #bbf7d0}
.password-strength{
height:3px;
border-radius:var(--radius-full);
background:var(--surface-low);
margin-top:0.5rem;
overflow:hidden;
}
.strength-bar{
height:100%;
border-radius:var(--radius-full);
width:0%;
transition:width 0.3s,background 0.3s;
}
/* Forgot password panel */
.forgot-panel{display:none}
.forgot-panel.show{display:block}
.back-link{
display:inline-flex;align-items:center;gap:0.375rem;
font-size:0.8125rem;color:var(--text-muted);
text-decoration:none;cursor:pointer;
background:none;border:none;
padding:0;margin-bottom:1.5rem;
transition:color 0.2s;
}
.back-link:hover{color:var(--primary)}
@media(max-width:768px){
body{grid-template-columns:1fr}
.left-panel{display:none}
.right-panel{padding:2rem 1.25rem}
}
</style>
</head>
<body>
<!-- LEFT PANEL -->
<div class="left-panel">
<a href="/" class="brand-logo">Q-Simplified</a>
<div class="left-hero">
<div class="left-tagline">The Financial Architect</div>
<h1 class="left-headline">
Understand<br/>markets.<br/><span>Grow your</span><br/>wealth.
</h1>
<p class="left-desc">Real-time economic data, expert analysis, and simplified financial education β€” all in one platform built for the modern Indian investor.</p>
<div class="left-stats">
<div class="stat-item">
<div class="stat-num">50K+</div>
<div class="stat-label">Active Readers</div>
</div>
<div class="stat-item">
<div class="stat-num">200+</div>
<div class="stat-label">Expert Articles</div>
</div>
<div class="stat-item">
<div class="stat-num">6</div>
<div class="stat-label">Live Indicators</div>
</div>
</div>
</div>
<div class="left-indicators">
<span class="ind-badge">Sensex <strong>74,248</strong></span>
<span class="ind-badge">Nifty <strong>22,513</strong></span>
<span class="ind-badge">Repo Rate <strong>6.50%</strong></span>
<span class="ind-badge">USD/INR <strong>83.42</strong></span>
<span class="ind-badge">Crude <strong>$82.40</strong></span>
</div>
</div>
<!-- RIGHT PANEL -->
<div class="right-panel">
<div class="auth-card">
<!-- TABS -->
<div class="auth-tabs">
<button class="auth-tab active" onclick="switchTab('login')">Sign In</button>
<button class="auth-tab" onclick="switchTab('register')">Create Account</button>
</div>
<!-- GLOBAL ALERT -->
<div class="alert error" id="global-error"></div>
<div class="alert success" id="global-success"></div>
<!-- ── LOGIN FORM ── -->
<div class="auth-form active" id="login-form">
<h2 class="form-title">Welcome back</h2>
<p class="form-subtitle">Sign in to access your personalized financial dashboard.</p>
<div class="form-group">
<label class="form-label" for="login-email">Email address</label>
<input class="form-input" id="login-email" type="email" placeholder="you@example.com" autocomplete="email"/>
<div class="form-error" id="login-email-err">Please enter a valid email address</div>
</div>
<div class="form-group">
<label class="form-label" for="login-password">Password</label>
<div class="password-wrapper">
<input class="form-input" id="login-password" type="password" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" autocomplete="current-password"/>
<button class="password-toggle" onclick="togglePassword('login-password',this)" type="button" tabindex="-1">
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" id="eye-login"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
</div>
<div class="form-error" id="login-pw-err">Password is required</div>
</div>
<a class="forgot-link" onclick="showForgot()" href="#">Forgot password?</a>
<button class="btn-submit" id="login-btn" onclick="handleLogin()">
<div class="spinner"></div>
<span class="btn-text">Sign In</span>
</button>
<div class="divider">or continue with</div>
<button class="btn-google" onclick="handleGoogleLogin()">
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
Continue with Google
</button>
<p class="terms-text">
Don't have an account? <a onclick="switchTab('register')" href="#">Create one free</a>
</p>
</div>
<!-- ── REGISTER FORM ── -->
<div class="auth-form" id="register-form">
<h2 class="form-title">Create account</h2>
<p class="form-subtitle">Join 50,000+ investors learning to master their finances.</p>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="reg-firstname">First name</label>
<input class="form-input" id="reg-firstname" type="text" placeholder="Priya" autocomplete="given-name"/>
<div class="form-error" id="reg-fn-err">Required</div>
</div>
<div class="form-group">
<label class="form-label" for="reg-lastname">Last name</label>
<input class="form-input" id="reg-lastname" type="text" placeholder="Sharma" autocomplete="family-name"/>
<div class="form-error" id="reg-ln-err">Required</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="reg-email">Email address</label>
<input class="form-input" id="reg-email" type="email" placeholder="you@example.com" autocomplete="email"/>
<div class="form-error" id="reg-email-err">Please enter a valid email</div>
</div>
<div class="form-group">
<label class="form-label" for="reg-password">Password</label>
<div class="password-wrapper">
<input class="form-input" id="reg-password" type="password" placeholder="Min. 8 characters" oninput="checkStrength(this.value)" autocomplete="new-password"/>
<button class="password-toggle" onclick="togglePassword('reg-password',this)" type="button" tabindex="-1">
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
</button>
</div>
<div class="password-strength"><div class="strength-bar" id="strength-bar"></div></div>
<div class="form-error" id="reg-pw-err">Password must be at least 8 characters</div>
</div>
<button class="btn-submit" id="register-btn" onclick="handleRegister()">
<div class="spinner"></div>
<span class="btn-text">Create Account</span>
</button>
<div class="divider">or continue with</div>
<button class="btn-google" onclick="handleGoogleLogin()">
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
Continue with Google
</button>
<p class="terms-text">
By creating an account, you agree to our <a href="/privacy">Privacy Policy</a> and <a href="/terms">Terms of Service</a>.
</p>
</div>
<!-- ── FORGOT PASSWORD PANEL ── -->
<div class="forgot-panel" id="forgot-panel">
<button class="back-link" onclick="hideForgot()">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Back to sign in
</button>
<h2 class="form-title">Reset password</h2>
<p class="form-subtitle">Enter your email and we'll send you a link to reset your password.</p>
<div class="form-group">
<label class="form-label" for="forgot-email">Email address</label>
<input class="form-input" id="forgot-email" type="email" placeholder="you@example.com"/>
</div>
<button class="btn-submit" id="forgot-btn" onclick="handleForgot()">
<div class="spinner"></div>
<span class="btn-text">Send Reset Link</span>
</button>
</div>
</div>
</div>
<script>
// ══════════════════════════════════════════════════════
// CONFIG β€” Change this to your Railway API Gateway URL
// ══════════════════════════════════════════════════════
const API_BASE = ''; // Same origin β€” FastAPI serves both frontend and API // ← UPDATE THIS AFTER RAILWAY DEPLOY
// ══════════════════════════════════════════════════════
// TAB SWITCHING
// ══════════════════════════════════════════════════════
function switchTab(tab) {
document.querySelectorAll('.auth-tab').forEach((t, i) => {
t.classList.toggle('active', (tab === 'login' && i === 0) || (tab === 'register' && i === 1));
});
document.getElementById('login-form').classList.toggle('active', tab === 'login');
document.getElementById('register-form').classList.toggle('active', tab === 'register');
clearAlerts();
}
function showForgot() {
document.getElementById('login-form').style.display = 'none';
document.getElementById('forgot-panel').classList.add('show');
}
function hideForgot() {
document.getElementById('forgot-panel').classList.remove('show');
document.getElementById('login-form').style.display = 'block';
}
// ══════════════════════════════════════════════════════
// ALERTS
// ══════════════════════════════════════════════════════
function showAlert(type, msg) {
clearAlerts();
const el = document.getElementById(`global-${type}`);
el.textContent = msg;
el.classList.add('show');
}
function clearAlerts() {
document.querySelectorAll('.alert').forEach(a => a.classList.remove('show'));
}
// ══════════════════════════════════════════════════════
// VALIDATION HELPERS
// ══════════════════════════════════════════════════════
function isEmail(val) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val); }
function fieldError(inputId, errId, show) {
const inp = document.getElementById(inputId);
const err = document.getElementById(errId);
inp.classList.toggle('error', show);
err.classList.toggle('show', show);
return show;
}
// ══════════════════════════════════════════════════════
// PASSWORD STRENGTH
// ══════════════════════════════════════════════════════
function checkStrength(val) {
const bar = document.getElementById('strength-bar');
let score = 0;
if (val.length >= 8) score++;
if (val.length >= 12) score++;
if (/[A-Z]/.test(val)) score++;
if (/[0-9]/.test(val)) score++;
if (/[^A-Za-z0-9]/.test(val)) score++;
const pct = (score / 5) * 100;
const color = score < 2 ? '#c0392b' : score < 4 ? '#f39c12' : '#1a7a4a';
bar.style.width = pct + '%';
bar.style.background = color;
}
function togglePassword(inputId, btn) {
const inp = document.getElementById(inputId);
const isText = inp.type === 'text';
inp.type = isText ? 'password' : 'text';
btn.style.opacity = isText ? '1' : '0.5';
}
// ══════════════════════════════════════════════════════
// LOADING STATE
// ══════════════════════════════════════════════════════
function setLoading(btnId, loading) {
const btn = document.getElementById(btnId);
btn.classList.toggle('loading', loading);
btn.disabled = loading;
}
// ══════════════════════════════════════════════════════
// API CALLS
// ══════════════════════════════════════════════════════
async function apiCall(endpoint, body) {
const res = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Request failed');
return data;
}
// ══════════════════════════════════════════════════════
// HANDLE LOGIN
// ══════════════════════════════════════════════════════
async function handleLogin() {
clearAlerts();
const email = document.getElementById('login-email').value.trim();
const pw = document.getElementById('login-password').value;
let hasError = false;
if (fieldError('login-email', 'login-email-err', !isEmail(email))) hasError = true;
if (fieldError('login-password', 'login-pw-err', !pw)) hasError = true;
if (hasError) return;
setLoading('login-btn', true);
try {
const data = await apiCall('/api/auth/login', { email, password: pw });
saveSession(data);
showAlert('success', 'Logged in! Redirecting...');
setTimeout(() => window.location.href = '/', 1000);
} catch (err) {
showAlert('error', err.message || 'Login failed. Please check your credentials.');
} finally {
setLoading('login-btn', false);
}
}
// ══════════════════════════════════════════════════════
// HANDLE REGISTER
// ══════════════════════════════════════════════════════
async function handleRegister() {
clearAlerts();
const first = document.getElementById('reg-firstname').value.trim();
const last = document.getElementById('reg-lastname').value.trim();
const email = document.getElementById('reg-email').value.trim();
const pw = document.getElementById('reg-password').value;
let hasError = false;
if (fieldError('reg-firstname', 'reg-fn-err', !first)) hasError = true;
if (fieldError('reg-lastname', 'reg-ln-err', !last)) hasError = true;
if (fieldError('reg-email', 'reg-email-err', !isEmail(email))) hasError = true;
if (fieldError('reg-password', 'reg-pw-err', pw.length < 8)) hasError = true;
if (hasError) return;
setLoading('register-btn', true);
try {
const data = await apiCall('/api/auth/register', {
email,
password: pw,
full_name: `${first} ${last}`,
});
saveSession(data);
showAlert('success', 'Account created! Welcome to Q-Simplified πŸŽ‰');
setTimeout(() => window.location.href = '/', 1500);
} catch (err) {
showAlert('error', err.message || 'Registration failed. Try a different email.');
} finally {
setLoading('register-btn', false);
}
}
// ══════════════════════════════════════════════════════
// FORGOT PASSWORD
// ══════════════════════════════════════════════════════
async function handleForgot() {
const email = document.getElementById('forgot-email').value.trim();
if (!isEmail(email)) {
document.getElementById('forgot-email').classList.add('error');
return;
}
setLoading('forgot-btn', true);
try {
await apiCall('/api/auth/forgot-password', { email });
showAlert('success', 'Reset link sent! Check your inbox.');
setTimeout(hideForgot, 2000);
} catch {
showAlert('error', 'Failed to send reset link. Try again.');
} finally {
setLoading('forgot-btn', false);
}
}
// ══════════════════════════════════════════════════════
// GOOGLE SSO (via Supabase)
// ══════════════════════════════════════════════════════
async function handleGoogleLogin() {
// For Supabase Google OAuth, redirect to Supabase auth URL
showAlert('error', 'Google login requires Supabase project configuration. See deployment guide.');
}
// ══════════════════════════════════════════════════════
// SESSION MANAGEMENT
// ══════════════════════════════════════════════════════
function saveSession(data) {
localStorage.setItem('qs_token', data.access_token);
localStorage.setItem('qs_user', JSON.stringify(data.user));
localStorage.setItem('qs_token_exp', Date.now() + data.expires_in * 1000);
}
// Redirect if already logged in
if (localStorage.getItem('qs_token') && Date.now() < +localStorage.getItem('qs_token_exp')) {
window.location.href = '/';
}
// Enter key support
document.addEventListener('keydown', e => {
if (e.key !== 'Enter') return;
const activeForm = document.querySelector('.auth-form.active');
if (activeForm?.id === 'login-form') handleLogin();
if (activeForm?.id === 'register-form') handleRegister();
});
</script>
</body>
</html>