Files
wiggleverse-ecomm-content/ui/designs/ecomm-login-and-create-storefront-designs/project/wf-intro.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

176 lines
11 KiB
React

// wf-intro.jsx — System framing artboards: the two directions + entry-routing spine.
const { Screen: IScreen, Wordmark: IWordmark, WF_T: IT, WF_DS: IDS } = window;
const { Eyebrow: IEyebrow, Soul: ISoul, Tag: ITag } = IDS;
// ── The two directions, side by side ───────────────────────────────────────
function TwoDirections() {
return (
<IScreen starfield style={{ padding: '40px 44px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<IWordmark size={26} />
<ITag variant="live">SD-0001 · MVP wireframes</ITag>
</div>
<div style={{ marginTop: 26 }}>
<IEyebrow style={{ margin: '0 0 12px' }}>One A/B, run across all four screens</IEyebrow>
<h1 style={{ fontFamily: IT.disp, fontWeight: 700, letterSpacing: '-0.015em', fontSize: 34, lineHeight: 1.05, color: IT.star, margin: '0 0 10px' }}>
Two bets on the lasting frame
</h1>
<p style={{ fontFamily: IT.body, fontSize: 15, lineHeight: 1.6, color: IT.soft, maxWidth: 620, margin: 0 }}>
These are the first screens of a product headed for hundreds. The variation isn't cosmetic — each
direction is a different answer to <em>how much frame to commit to on day one</em>. Same content, same tokens,
same honest copy; different skeleton.
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 22, marginTop: 30 }}>
<DirCard letter="A" name="Quiet centered" tone="lilac"
bet="Keep chrome minimal until features earn it."
lines={['Auth & onboarding are one centered column on the sky.',
'Admin is a slim top bar over a centered empty state.',
'Fewest moving parts the calm voice carries it.']}
best="Reads calmest now; less to maintain." cost="A bigger reframe when the nav finally arrives." />
<DirCard letter="B" name="Durable frame" tone="gold"
bet="Set the skeleton now; let screens slot in."
lines={['Entry uses a split: a persistent brand/identity rail + a work column.',
'Admin ships the real shell sidebar + content region.',
'Catalog, orders, settings drop into an existing frame later.']}
best="No redesign when features land." cost="More frame than four screens strictly need." />
</div>
<div style={{ marginTop: 26, padding: '16px 20px', borderRadius: 12, background: 'var(--wv-lilac-08)', border: `1px solid var(--wv-lilac-18)`, display: 'flex', gap: 13, alignItems: 'flex-start' }}>
<span aria-hidden="true" style={{ color: IT.lilac }}></span>
<p style={{ fontFamily: IT.body, fontSize: 13.5, lineHeight: 1.55, color: IT.soft, margin: 0 }}>
<strong style={{ color: IT.star }}>Both honor the OHM bar.</strong> No trial clock, no plan wall, no fabricated metrics or
locked-feature teasers. Empty is an honest, finished state not a sales surface. Attention states use <span style={{ color: IT.gold }}>gold</span>,
since the palette carries no red.
</p>
</div>
</IScreen>
);
}
function DirCard({ letter, name, bet, lines, best, cost, tone }) {
const c = tone === 'gold' ? IT.gold : IT.lilac;
return (
<div style={{ background: IT.raised, border: `1px solid ${IT.hairCard}`, borderRadius: 14, padding: '22px 22px 20px', display: 'flex', flexDirection: 'column', gap: 14 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<span style={{ width: 34, height: 34, borderRadius: 9, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: IT.disp, fontWeight: 700, fontSize: 17, color: tone === 'gold' ? IT.goldInk : '#0E1230', background: c }}>{letter}</span>
<div>
<div style={{ fontFamily: IT.disp, fontWeight: 700, fontSize: 18, color: IT.star, letterSpacing: '-0.01em' }}>{name}</div>
<div style={{ fontFamily: IT.body, fontStyle: 'italic', fontSize: 13, color: c }}>{bet}</div>
</div>
</div>
<ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 8 }}>
{lines.map((l, i) => (
<li key={i} style={{ display: 'flex', gap: 9, fontFamily: IT.body, fontSize: 13.5, lineHeight: 1.45, color: IT.soft }}>
<span aria-hidden="true" style={{ color: c, flex: '0 0 auto' }}>·</span>{l}
</li>
))}
</ul>
<div style={{ borderTop: `1px solid ${IT.hair}`, paddingTop: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
<TradeRow k="Wins" v={best} c={IT.lilac} />
<TradeRow k="Costs" v={cost} c={IT.gold} />
</div>
</div>
);
}
function TradeRow({ k, v, c }) {
return (
<div style={{ display: 'flex', gap: 10, fontFamily: IT.body, fontSize: 12.5, lineHeight: 1.4 }}>
<span style={{ flex: '0 0 42px', color: c, fontWeight: 600 }}>{k}</span>
<span style={{ color: IT.mute }}>{v}</span>
</div>
);
}
// ── Entry routing — the exhaustive arrival rule (§6.5) ─────────────────────
function EntryRouting() {
const lanes = [
{ sess: 'No session', store: '—', arrow: 'Landing', puc: 'PUC-1', tone: 'mute' },
{ sess: 'Valid session', store: 'No storefront', arrow: 'Create storefront', puc: 'PUC-5', tone: 'gold' },
{ sess: 'Valid session', store: 'Has storefront', arrow: 'Admin shell', puc: 'PUC-6', tone: 'lilac' },
];
return (
<IScreen style={{ padding: '40px 38px' }}>
<IEyebrow style={{ margin: '0 0 12px' }}>The shared spine</IEyebrow>
<h1 style={{ fontFamily: IT.disp, fontWeight: 700, letterSpacing: '-0.015em', fontSize: 27, lineHeight: 1.08, color: IT.star, margin: '0 0 10px' }}>
Entry routing
</h1>
<p style={{ fontFamily: IT.body, fontSize: 14, lineHeight: 1.55, color: IT.soft, margin: '0 0 8px' }}>
One server answer <code style={{ fontFamily: 'ui-monospace,monospace', fontSize: 12.5, color: IT.lilac, background: 'var(--wv-lilac-08)', padding: '1px 6px', borderRadius: 5 }}>GET /api/auth/me</code>
decides where every arrival lands. Every screen here obeys it.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 24 }}>
{lanes.map((l, i) => {
const c = l.tone === 'gold' ? IT.gold : l.tone === 'lilac' ? IT.lilac : IT.mute;
return (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '16px 18px', borderRadius: 12, background: IT.raised, border: `1px solid ${IT.hairCard}` }}>
<div style={{ display: 'flex', gap: 8, flex: '0 0 auto' }}>
<Cond>{l.sess}</Cond>
<Cond>{l.store}</Cond>
</div>
<span aria-hidden="true" style={{ color: c, fontSize: 18, flex: '0 0 auto' }}></span>
<div style={{ flex: 1, display: 'flex', alignItems: 'baseline', gap: 10, justifyContent: 'flex-end' }}>
<span style={{ fontFamily: IT.disp, fontWeight: 700, fontSize: 17, color: c, letterSpacing: '-0.01em' }}>{l.arrow}</span>
<span style={{ fontFamily: IT.body, fontSize: 11.5, color: IT.mute }}>{l.puc}</span>
</div>
</div>
);
})}
</div>
<p style={{ fontFamily: IT.body, fontSize: 13, lineHeight: 1.55, color: IT.mute, margin: '22px 0 0', display: 'flex', gap: 9 }}>
<span aria-hidden="true" style={{ color: IT.lilac }}></span>
Exhaustive by design no state lands nowhere. A merchant without a storefront is guided to create one, never stranded.
</p>
<div style={{ flex: 1 }} />
</IScreen>
);
}
function Cond({ children }) {
return <span style={{ fontFamily: IT.body, fontSize: 13, color: IT.soft, padding: '5px 11px', borderRadius: 999, background: 'rgba(237,234,255,.05)', border: `1px solid ${IT.hair}`, whiteSpace: 'nowrap' }}>{children}</span>;
}
// ── Direction A, chosen — the system this locks in ─────────────────────────
function SystemA() {
const patterns = [
['Entry surfaces', 'One centered card on the midnight sky — landing, sign-in, create. Content-led, no side chrome.'],
['Admin', 'A slim top bar over a centered state. The storefront name is the anchor; no sidebar yet.'],
['Actions', 'One primary gold CTA per screen. Lilac for secondary links. Ghost buttons stay rare.'],
['Attention', 'Errors and waits read in gold — the palette carries no red. Honest failure, never a fake success.'],
['Emptiness', 'Honestly empty everywhere: no zeroed tiles, no locked-feature teasers, no manufactured aspiration.'],
];
return (
<IScreen starfield style={{ padding: '40px 44px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<IWordmark size={26} />
<ITag variant="live">SD-0001 · MVP wireframes</ITag>
</div>
<div style={{ marginTop: 26 }}>
<IEyebrow style={{ margin: '0 0 12px' }}>Chosen direction</IEyebrow>
<h1 style={{ fontFamily: IT.disp, fontWeight: 700, letterSpacing: '-0.015em', fontSize: 34, lineHeight: 1.05, color: IT.star, margin: '0 0 10px' }}>
Quiet centered
</h1>
<p style={{ fontFamily: IT.body, fontSize: 15, lineHeight: 1.6, color: IT.soft, maxWidth: 600, margin: 0 }}>
A single calm column on the sky, with the least chrome the job allows. The honest voice carries the rest
and every screen here uses the same tokens, the same copy register, the same primary action.
</p>
</div>
<div style={{ marginTop: 26, display: 'flex', flexDirection: 'column', gap: 0, border: `1px solid ${IT.hairCard}`, borderRadius: 14, overflow: 'hidden', background: IT.raised }}>
{patterns.map(([k, v], i) => (
<div key={k} style={{ display: 'grid', gridTemplateColumns: '150px 1fr', gap: 18, padding: '15px 20px', borderTop: i ? `1px solid ${IT.hair}` : 'none' }}>
<span style={{ fontFamily: IT.disp, fontWeight: 500, fontSize: 14, color: IT.lilac }}>{k}</span>
<span style={{ fontFamily: IT.body, fontSize: 13.5, lineHeight: 1.5, color: IT.soft }}>{v}</span>
</div>
))}
</div>
<div style={{ marginTop: 22, padding: '15px 20px', borderRadius: 12, background: 'rgba(244,199,107,.08)', border: `1px solid var(--wv-gold-40)`, display: 'flex', gap: 13, alignItems: 'flex-start' }}>
<span aria-hidden="true" style={{ color: IT.gold }}></span>
<p style={{ fontFamily: IT.body, fontSize: 13.5, lineHeight: 1.55, color: IT.soft, margin: 0 }}>
<strong style={{ color: IT.star }}>Deferred, not forgotten.</strong> When the product grows past forms, a persistent
nav frame gets introduced as its own deliberate step designed then, against real sections, not guessed at now.
</p>
</div>
</IScreen>
);
}
Object.assign(window, { SystemA, TwoDirections, EntryRouting });