afe36fd347
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>
176 lines
11 KiB
React
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 });
|