Redesign sign-in / create-account page: split layout, benefits panel, show/hide password, real form submit; v2.10.6
Publish Container / build (push) Has been cancelled

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-09 10:33:26 -04:00
1 parent b708371cc5
commit 640d0c1c18
3 files changed
+234 -50

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
"version": "2.10.5",
"version": "2.10.6",
"type": "module",
"scripts": {
"dev": "vite",
+181
View File
@@ -4033,6 +4033,187 @@
padding: 0;
}
/* ── Sign in / Create account (split layout) ── */
.auth-page {
min-height: 70vh;
display: flex;
justify-content: center;
padding: clamp(2rem, 6vw, 4rem) 1.25rem;
background:
radial-gradient(ellipse at top, rgba(201, 168, 76, 0.12) 0%, transparent 55%),
var(--brand-black);
}
.auth-shell {
width: min(1000px, 100%);
display: grid;
grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
align-items: start;
gap: clamp(1.5rem, 4vw, 3rem);
}
.auth-shell--single {
grid-template-columns: minmax(0, 520px);
justify-content: center;
}
.auth-aside {
padding: 1rem 0.5rem;
}
.auth-aside-title {
font-family: var(--brand-font-heading);
font-size: clamp(1.7rem, 3.4vw, 2.3rem);
line-height: 1.15;
color: var(--brand-warm-white);
margin: 0.4rem 0 1.5rem;
}
.auth-benefits {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 1.1rem;
}
.auth-benefits li {
display: flex;
gap: 0.9rem;
align-items: flex-start;
}
.auth-benefits li > span {
flex-shrink: 0;
width: 2.4rem;
height: 2.4rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(201, 168, 76, 0.12);
border: 1px solid rgba(201, 168, 76, 0.35);
font-size: 1.05rem;
}
.auth-benefits strong {
display: block;
color: var(--brand-warm-white);
font-family: var(--brand-font-heading);
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.15rem;
}
.auth-benefits p {
margin: 0;
color: #b9aa86;
font-size: 0.95rem;
line-height: 1.55;
}
.auth-card {
background: #121212;
border: 1px solid rgba(201, 168, 76, 0.24);
border-radius: 16px;
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.5);
padding: clamp(1.5rem, 4vw, 2.25rem);
text-align: left;
}
.auth-card h1 {
font-family: var(--brand-font-heading);
font-size: clamp(1.8rem, 4vw, 2.3rem);
color: var(--brand-warm-white);
margin: 0.2rem 0 0.75rem;
}
.auth-card p {
font-family: var(--brand-font-body);
color: var(--brand-muted);
}
.auth-card-eyebrow { margin: 0; }
.auth-card .study-signup-form { margin-top: 0.5rem; }
.auth-card .study-signup-actions { margin-top: 0.75rem; }
.auth-card .study-signup-actions .btn-primary {
width: 100%;
justify-content: center;
}
.auth-password-wrap {
position: relative;
display: flex;
}
.auth-password-wrap input {
width: 100%;
padding-right: 4.5rem;
}
.auth-password-toggle {
position: absolute;
right: 0.35rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--brand-gold);
font-family: var(--brand-font-body);
font-size: 0.78rem;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 0.4rem 0.6rem;
cursor: pointer;
}
.auth-switch {
margin: 1.1rem 0 0;
text-align: center;
font-size: 0.95rem;
}
.auth-link {
background: none;
border: none;
padding: 0;
color: var(--brand-gold);
font: inherit;
text-decoration: underline;
cursor: pointer;
}
.auth-footer-links {
margin: 1.5rem 0 0;
text-align: center;
font-size: 0.9rem;
}
.auth-footer-links a {
color: var(--brand-muted);
text-decoration: none;
}
.auth-footer-links a:hover {
color: var(--brand-warm-white);
text-decoration: underline;
}
@media (max-width: 820px) {
.auth-shell {
grid-template-columns: 1fr;
}
/* Form first on phones; the benefits follow below it */
.auth-aside {
order: 2;
}
}
/* ── Study Signup Page ── */
.study-signup-page-card {
max-width: 680px;
+52 -49
View File
@@ -1130,6 +1130,7 @@ export function StudySignupPage() {
const [resendStatus, setResendStatus] = useState<'idle' | 'sending' | 'sent'>('idle')
// Forgot-password state
const [forgotSent, setForgotSent] = useState(false)
const [showPassword, setShowPassword] = useState(false)
useEffect(() => {
readJson<StudyAuthStatusResponse>('/api/study-auth/status')
@@ -1215,10 +1216,38 @@ export function StudySignupPage() {
}
}, [email])
const showAside = !done && mode !== 'forgot' && !totpPendingToken
return (
<main className="thanks-page" aria-label={mode === 'signup' ? 'Create account' : mode === 'forgot' ? 'Reset password' : 'Sign in'}>
<div className="thanks-card study-signup-page-card">
<p className="eyebrow">Free Student Account</p>
<main className="auth-page" aria-label={mode === 'signup' ? 'Create account' : mode === 'forgot' ? 'Reset password' : 'Sign in'}>
<div className={`auth-shell${showAside ? '' : ' auth-shell--single'}`}>
{showAside && (
<aside className="auth-aside" aria-label={mode === 'signup' ? 'Why create an account' : 'Welcome back'}>
{mode === 'signup' ? (
<>
<p className="eyebrow">Free Student Account</p>
<h2 className="auth-aside-title">Make every lesson your own.</h2>
<ul className="auth-benefits">
<li><span aria-hidden="true">📝</span><div><strong>Notes on every lesson</strong><p>Up to 500 notes across all tracks, each holding about 2,000 words.</p></div></li>
<li><span aria-hidden="true">✅</span><div><strong>Track your progress</strong><p>Pick up exactly where you left off, on any device.</p></div></li>
<li><span aria-hidden="true">🔒</span><div><strong>Private to you</strong><p>Your notes are tied to your account. Nobody else can read them.</p></div></li>
<li><span aria-hidden="true">⚡</span><div><strong>Free, no ads</strong><p>Just an email and password. You stay signed in for 30 days.</p></div></li>
</ul>
</>
) : (
<>
<p className="eyebrow">Free Student Account</p>
<h2 className="auth-aside-title">Pick up where you left off.</h2>
<ul className="auth-benefits">
<li><span aria-hidden="true">📝</span><div><strong>Your notes</strong><p>Every lesson note you've saved, waiting for you.</p></div></li>
<li><span aria-hidden="true">✅</span><div><strong>Your progress</strong><p>Resume your studies right where you stopped.</p></div></li>
</ul>
</>
)}
</aside>
)}
<div className="auth-card">
<p className="eyebrow auth-card-eyebrow">{mode === 'signup' ? 'Create account' : mode === 'forgot' ? 'Account recovery' : 'Sign in'}</p>
<h1>{mode === 'signup' ? 'Create Your Account' : mode === 'forgot' ? 'Reset Your Password' : 'Welcome Back'}</h1>
{done ? (
@@ -1317,7 +1346,7 @@ export function StudySignupPage() {
</div>
) : (
<>
<div className="study-signup-form">
<form className="study-signup-form" noValidate onSubmit={e => { e.preventDefault(); void submit() }}>
<label htmlFor="signup-email">Email</label>
<input
id="signup-email"
@@ -1337,9 +1366,10 @@ export function StudySignupPage() {
/>
{fieldErrors.email && <p className="study-signup-field-error">{fieldErrors.email}</p>}
<label htmlFor="signup-password">Password</label>
<div className="auth-password-wrap">
<input
id="signup-password"
type="password"
type={showPassword ? 'text' : 'password'}
value={password}
onChange={e => { setPassword(e.target.value); if (fieldErrors.password) setFieldErrors(prev => ({ ...prev, password: undefined })) }}
onBlur={() => {
@@ -1349,10 +1379,13 @@ export function StudySignupPage() {
setFieldErrors(prev => ({ ...prev, password: undefined }))
}
}}
placeholder="At least 8 characters"
placeholder={mode === 'signup' ? 'At least 8 characters' : 'Your password'}
autoComplete={mode === 'signup' ? 'new-password' : 'current-password'}
onKeyDown={e => e.key === 'Enter' && submit()}
/>
<button type="button" className="auth-password-toggle" aria-pressed={showPassword} onClick={() => setShowPassword(v => !v)}>
{showPassword ? 'Hide' : 'Show'}
</button>
</div>
{fieldErrors.password && <p className="study-signup-field-error">{fieldErrors.password}</p>}
{mode === 'login' && (
<button type="button" onClick={() => { setMode('forgot'); setMessage(''); setForgotSent(false) }} style={{ background: 'none', border: 'none', color: '#c9a84c', cursor: 'pointer', fontSize: '0.85rem', textDecoration: 'underline', padding: 0, textAlign: 'left', marginTop: '0.5rem', alignSelf: 'flex-start' }}>
@@ -1369,56 +1402,26 @@ export function StudySignupPage() {
<span>Send me updates from Verse by Verse with Nate. I can unsubscribe anytime.</span>
</label>
)}
{message && <p className="study-signup-error">{message}</p>}
{message && <p className="study-signup-error" role="alert">{message}</p>}
<div className="study-signup-actions">
<button type="button" className="btn-primary" disabled={busy} onClick={submit}>
<button type="submit" className="btn-primary" disabled={busy}>
{busy ? (mode === 'signup' ? 'Creating...' : 'Signing in...') : (mode === 'signup' ? 'Create Account' : 'Sign In')}
</button>
</div>
<div className="study-signup-actions" style={{ marginTop: '1rem' }}>
{mode === 'login' && (
<button type="button" className="btn-secondary" onClick={() => { setMode('signup'); setMessage('') }}>
Don't have an account? Sign up here
</button>
)}
{mode === 'signup' && (
<button type="button" className="btn-secondary" onClick={() => { setMode('login'); setMessage('') }}>
Already have an account? Sign in instead
</button>
)}
</div>
</div>
<p className="auth-switch">
{mode === 'login' ? "Don't have an account? " : 'Already have an account? '}
<button type="button" className="auth-link" onClick={() => { setMode(mode === 'login' ? 'signup' : 'login'); setMessage('') }}>
{mode === 'login' ? 'Sign up' : 'Sign in'}
</button>
</p>
</form>
{mode === 'signup' && (
<div className="study-signup-why-grid" style={{ marginTop: '2rem' }}>
<article className="study-signup-why-card">
<p className="study-signup-why-icon" aria-hidden="true">📝</p>
<h3>Notes on Every Lesson</h3>
<p>Save your own notes per lesson — up to 500 notes across all tracks.</p>
</article>
<article className="study-signup-why-card">
<p className="study-signup-why-icon" aria-hidden="true">📖</p>
<h3>Space to Go Deep</h3>
<p>Each note holds ~2,000 words so you can write as much as you need.</p>
</article>
<article className="study-signup-why-card">
<p className="study-signup-why-icon" aria-hidden="true">🔒</p>
<h3>Private to You</h3>
<p>Your notes are tied to your account. Nobody else can read them.</p>
</article>
<article className="study-signup-why-card">
<p className="study-signup-why-icon" aria-hidden="true">⚡</p>
<h3>Free Forever</h3>
<p>Just an email and password. No charge, no ads. Stay signed in 30 days.</p>
</article>
</div>
)}
</>
)}
<div className="study-signup-actions" style={{ marginTop: '1.5rem' }}>
<Link to="/study/account" className="btn-secondary">My Account</Link>
<Link to="/study" className="btn-secondary">Back to Studies</Link>
<p className="auth-footer-links">
<Link to="/study">← Back to Studies</Link>
</p>
</div>
</div>
</main>