Files
wiggleverse-ecomm-content/ui/designs/ecomm-login-and-create-storefront-designs/project/wf-signin.jsx
T
ben.stull afe36fd347 add ui design ./ui/designs/ecomm-login-and-create-storefront-designs/
Claude Design export for SD-0001 (login + create-storefront surfaces): wf-*/hf-*
screens, design-system bundle, offline preview. First artifact in the ui/designs/
collection (ecomm#8). Also ignore .DS_Store.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:53:43 -07:00

162 lines
8.8 KiB
React

// wf-signin.jsx — Screen 2: Sign in (PUC-2, PUC-3). Email → one-time code. A/B + states.
const { Screen: SScreen, Wordmark: SWordmark, TopBar: STopBar, Field: SField, CodeField: SCodeField,
Banner: SBanner, AuthCard: SAuthCard, BrandRail: SBrandRail, WF_T: ST, WF_DS: SDS } = window;
const { Button: SButton, Soul: SSoul } = SDS;
function BackLink({ label = 'Back' }) {
return <a href="#" style={{ fontFamily: ST.body, fontSize: 14, color: ST.mute, textDecoration: 'none' }}> {label}</a>;
}
function Heading({ children, sub }) {
return (
<div>
<h1 style={{ fontFamily: ST.disp, fontWeight: 700, letterSpacing: '-0.015em', fontSize: 26, color: ST.star, margin: '0 0 8px', lineHeight: 1.1 }}>{children}</h1>
{sub && <p style={{ fontFamily: ST.body, fontSize: 14.5, lineHeight: 1.5, color: ST.soft, margin: 0 }}>{sub}</p>}
</div>
);
}
const honest = { fontFamily: ST.body, fontSize: 13, color: ST.mute, lineHeight: 1.5, margin: 0 };
// ── Step renderers (shared by A card and B column) ─────────────────────────
function EmailStep({ framing = 'signup', s = {} }) {
const title = framing === 'login' ? 'Log in' : 'Create your storefront';
const sub = framing === 'login' ? 'Enter your email and we\u2019ll send you a code to sign in.' : 'Enter your email to begin. No password to create.';
return (
<>
<Heading sub={sub}>{title}</Heading>
{s.deliveryFail && <SBanner title="We couldn't send the code">The email didn't go out. Try again in a moment — nothing was lost.</SBanner>}
<SField label="Email" placeholder="you@example.com"
value={s.email} focus={!s.emailError && !s.value} error={s.emailError} />
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<SButton variant="primary" href="#" disabled={!!s.cooldown}>
{s.cooldown ? `Resend in ${s.cooldown}s` : 'Send code'}
</SButton>
<p style={honest}>We'll email you a one-time code. That's all we need.</p>
</div>
</>
);
}
function CodeStep({ s = {} }) {
const email = s.email || 'mara@studiofern.com';
let err = null;
if (s.codeWrong) err = `That code didn't match ${s.attempts ?? 2} attempts left.`;
if (s.codeExpired) err = 'That code expired. Send a fresh one below.';
return (
<>
<Heading sub={<>We sent a code to <span style={{ color: ST.star }}>{email}</span>. <a href="#" style={{ color: ST.lilac, textDecoration: 'none' }}>Wrong address?</a></>}>
Check your email
</Heading>
{s.welcome && <SBanner tone="info" title={s.welcome === 'new' ? 'Welcome to ecomm' : 'Welcome back'}>
{s.welcome === 'new' ? 'A new account was created for this email.' : 'Signed in to your existing account.'}
</SBanner>}
{s.codeExhausted && <SBanner title="Too many attempts">That code is no longer valid. Request a fresh one to keep going.</SBanner>}
<div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
<SCodeField filled={s.filled ?? 3} error={!!(s.codeWrong || s.codeExpired || s.codeExhausted)} />
{err && <span style={{ fontFamily: ST.body, fontSize: 13, color: ST.gold, display: 'flex', gap: 6 }}><span aria-hidden="true">◆</span>{err}</span>}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<SButton variant="primary" href="#">Continue</SButton>
<a href="#" style={{ fontFamily: ST.disp, fontWeight: 500, fontSize: 14, color: s.cooldown ? ST.mute : ST.lilac, textDecoration: 'none' }}>
{s.cooldown ? `Resend in ${s.cooldown}s` : 'Resend code'}
</a>
</div>
<p style={honest}>The code is good for 10 minutes. If you didn't request it, ignore this.</p>
</>
);
}
// ── Direction A — centered card ────────────────────────────────────────────
function SignInA({ step = 'email', framing = 'signup', device = 'desktop', s = {} }) {
const desktop = device === 'desktop';
return (
<SScreen starfield>
<STopBar pad={desktop ? '0 40px' : '0 22px'} left={<SWordmark size={desktop ? 24 : 22} />} right={<BackLink />} />
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: desktop ? 32 : 18 }}>
<SAuthCard width={desktop ? 430 : '100%'}>
{step === 'email' ? <EmailStep framing={framing} s={s} /> : <CodeStep s={s} />}
</SAuthCard>
</div>
</SScreen>
);
}
// ── Direction B — split (brand rail + form column) ─────────────────────────
function SignInB({ step = 'email', framing = 'signup', device = 'desktop', s = {} }) {
const desktop = device === 'desktop';
if (!desktop) {
return (
<SScreen>
<div style={{ background: `${window.WF_STARFIELD}, var(--wv-night)`, borderBottom: `1px solid ${ST.hair}`, padding: '22px 24px', display: 'flex', flexDirection: 'column', gap: 12 }}>
<SWordmark size={22} />
<SSoul size="sm" tone="gold">Your storefront is yours.</SSoul>
</div>
<div style={{ flex: 1, padding: '26px 24px', display: 'flex', flexDirection: 'column', gap: 22, justifyContent: 'center' }}>
{step === 'email' ? <EmailStep framing={framing} s={s} /> : <CodeStep s={s} />}
</div>
</SScreen>
);
}
return (
<SScreen style={{ flexDirection: 'row' }}>
<aside style={{ flex: '0 0 44%', background: `${window.WF_STARFIELD}, var(--wv-night)`, borderRight: `1px solid ${ST.hair}`, padding: '40px 46px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden' }}>
<SWordmark size={26} />
<div style={{ maxWidth: 360 }}>
<SSoul size="lg" tone="gold" as="p">We are verbs,<br />not nouns.</SSoul>
<p style={{ fontFamily: ST.body, fontSize: 14.5, lineHeight: 1.6, color: ST.mute, marginTop: 20 }}>
One door, two framings. Whether you're signing up or coming back, it's the same honest step — your email, one code.
</p>
</div>
<BackLink label="Back to start" />
</aside>
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 64px' }}>
<div style={{ width: '100%', maxWidth: 400, display: 'flex', flexDirection: 'column', gap: 22 }}>
{step === 'email' ? <EmailStep framing={framing} s={s} /> : <CodeStep s={s} />}
</div>
</div>
</SScreen>
);
}
// ── States stack ───────────────────────────────────────────────────────────
const { StateBlock: SStateBlock } = window;
function codeNote(text) {
return <span style={{ fontFamily: ST.body, fontSize: 13, color: ST.gold, display: 'flex', gap: 6, marginTop: 9 }}><span aria-hidden="true">◆</span>{text}</span>;
}
function SignInStates() {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
<SStateBlock label="Invalid email" note="inline field error">
<SField label="Email" value="mara@studiofern" error="That doesn't look like an email address." />
</SStateBlock>
<SStateBlock label="Resend cooldown" note="PUC-2c · 60s per email">
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<SButton variant="primary" href="#" disabled>Resend in 47s</SButton>
<span style={honest}>One code per minute keeps inboxes (and abuse) in check.</span>
</div>
</SStateBlock>
<SStateBlock label="Delivery failed" note="502 · honest failure (INV-9)">
<SBanner title="We couldn't send the code">The email didn't go out — try again. We'd never fake a success.</SBanner>
</SStateBlock>
<SStateBlock label="Wrong code" note="PUC-2a">
<SCodeField filled={6} error />
{codeNote("That code didn't match — 2 attempts left.")}
</SStateBlock>
<SStateBlock label="Expired code" note="PUC-2b">
<SCodeField filled={6} error />
{codeNote('That code expired. Send a fresh one below.')}
</SStateBlock>
<SStateBlock label="Attempts exhausted" note="5 tries · code invalidated (INV-3)">
<SBanner title="Too many attempts">That code is no longer valid. Request a fresh one to keep going.</SBanner>
</SStateBlock>
<SStateBlock label="Honest welcome" note="PUC-3 · same flow, true label">
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<SBanner tone="info" title="Welcome to ecomm">A new account was created for this email.</SBanner>
<SBanner tone="info" title="Welcome back">Signed in to your existing account.</SBanner>
</div>
</SStateBlock>
</div>
);
}
Object.assign(window, { SignInA, SignInB, EmailStep, CodeStep, SignInStates });