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 });