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

304 lines
13 KiB
React

// wf-kit.jsx — Wiggleverse ecomm wireframe primitives.
// Mid-fi: real Wiggleverse tokens + composed DS components (Button, Soul, Eyebrow, Tag, Notice).
// Everything exported to window at the bottom so sibling babel scripts can use it.
const DS = window.WiggleverseDesignSystem_94cd80;
const { Button, Soul, Eyebrow, Tag, Notice } = DS;
// ---- token shorthands (resolved from the linked token stylesheets) -------
const T = {
sky: 'var(--wv-midnight)',
raised: 'var(--wv-indigo)',
raisedHi: 'var(--wv-indigo-2)',
night: 'var(--wv-night)',
star: 'var(--wv-starlight)',
soft: 'var(--wv-starlight-78)',
mute: 'var(--wv-starlight-60)',
lilac: 'var(--wv-lilac)',
gold: 'var(--wv-gold)',
goldInk: 'var(--wv-gold-ink)',
hair: 'var(--border-soft)',
hairCard: 'var(--border-card)',
disp: 'var(--wv-font-display)',
body: 'var(--wv-font-body)',
};
// faint scattered starfield — the brand's one decorative texture (off-center, low alpha)
const STARFIELD =
'radial-gradient(1.4px 1.4px at 18% 24%, rgba(237,234,255,.5), transparent),' +
'radial-gradient(1.4px 1.4px at 73% 16%, rgba(237,234,255,.35), transparent),' +
'radial-gradient(1.2px 1.2px at 88% 52%, rgba(155,140,255,.45), transparent),' +
'radial-gradient(1.6px 1.6px at 32% 70%, rgba(237,234,255,.4), transparent),' +
'radial-gradient(1.2px 1.2px at 58% 84%, rgba(237,234,255,.3), transparent),' +
'radial-gradient(1.3px 1.3px at 8% 58%, rgba(155,140,255,.4), transparent),' +
'radial-gradient(1.1px 1.1px at 46% 40%, rgba(237,234,255,.28), transparent)';
// ── Screen — fills the artboard, the midnight ground, column flow ──────────
function Screen({ children, starfield = false, bg = T.sky, style = {} }) {
return (
<div style={{
width: '100%', height: '100%', boxSizing: 'border-box',
background: starfield ? `${STARFIELD}, ${bg}` : bg,
color: T.star, fontFamily: T.body,
display: 'flex', flexDirection: 'column', position: 'relative', overflow: 'hidden',
...style,
}}>{children}</div>
);
}
// ── Wordmark — the mark tile + "ecomm" (Space Grotesk) ─────────────────────
function Wordmark({ size = 26, muted = false }) {
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: size * 0.42 }}>
<img src="assets/mark-tile.svg" width={size} height={size}
style={{ borderRadius: size * 0.22, display: 'block', opacity: muted ? 0.85 : 1 }} alt="" />
<span style={{
fontFamily: T.disp, fontWeight: 700, fontSize: size * 0.86,
letterSpacing: '-0.015em', color: T.star,
}}>ecomm</span>
</span>
);
}
// ── TopBar — the chrome shared by every authenticated surface ──────────────
function TopBar({ left, right, pad = '0 32px', height = 64 }) {
return (
<header style={{
flex: '0 0 auto', height, padding: pad,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
borderBottom: `1px solid ${T.hair}`,
background: 'rgba(14,18,48,.6)', backdropFilter: 'blur(10px)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>{left}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>{right}</div>
</header>
);
}
// account chip used at top-right of authenticated surfaces
function AccountChip({ email = 'mara@studiofern.com', menu = false }) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 14, position: 'relative' }}>
<span style={{ fontFamily: T.body, fontSize: 14, color: T.soft }}>{email}</span>
<span style={{ width: 1, height: 18, background: T.hair }} />
<button style={{
fontFamily: T.disp, fontWeight: 500, fontSize: 14, color: T.star,
background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
}}>Sign out</button>
{menu && (
<div style={{
position: 'absolute', top: 30, right: 0, minWidth: 200, zIndex: 9,
background: T.raised, border: `1px solid ${T.hairCard}`, borderRadius: 12,
padding: 6, boxShadow: '0 8px 24px rgba(9,12,34,.4)',
}}>
<MenuRow>{email}</MenuRow>
<div style={{ borderTop: `1px solid ${T.hair}`, margin: '6px 4px' }} />
<MenuRow strong>Sign out</MenuRow>
</div>
)}
</div>
);
}
function MenuRow({ children, strong }) {
return <div style={{
padding: '8px 12px', fontFamily: T.body, fontSize: 13.5,
color: strong ? T.star : T.mute, fontWeight: strong ? 600 : 400, borderRadius: 7,
}}>{children}</div>;
}
// ── Field — label + input + helper/error. Real token styling. ──────────────
function Field({ label, placeholder, value, helper, error, focus = false, optional = false }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
<label style={{
fontFamily: T.body, fontWeight: 500, fontSize: 13.5, color: T.soft,
display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
}}>
<span>{label}</span>
{optional && <span style={{ fontSize: 12.5, color: T.mute, fontWeight: 400 }}>optional</span>}
</label>
<div style={{
height: 50, borderRadius: 10, padding: '0 15px', display: 'flex', alignItems: 'center',
background: 'rgba(237,234,255,.04)',
border: `1.5px solid ${error ? T.gold : focus ? T.gold : T.hair}`,
boxShadow: focus ? '0 0 0 3px var(--wv-gold-28)' : 'none',
fontFamily: T.body, fontSize: 15.5,
color: value ? T.star : T.mute,
}}>
{value || placeholder}
{focus && !value && <span style={{
width: 1.5, height: 20, marginLeft: 1, background: T.gold,
}} />}
</div>
{error && <FieldNote tone="attn">{error}</FieldNote>}
{helper && !error && <FieldNote>{helper}</FieldNote>}
</div>
);
}
function FieldNote({ children, tone }) {
return <span style={{
fontFamily: T.body, fontSize: 13, lineHeight: 1.4,
color: tone === 'attn' ? T.gold : T.mute,
display: 'flex', gap: 6, alignItems: 'baseline',
}}>
{tone === 'attn' && <span aria-hidden="true" style={{ color: T.gold }}></span>}
<span>{children}</span>
</span>;
}
// ── CodeField — one-time-code entry, 6 cells ───────────────────────────────
function CodeField({ filled = 0, error = false }) {
const cells = [0, 1, 2, 3, 4, 5];
const vals = ['4', '1', '9', '', '', ''];
return (
<div style={{ display: 'flex', gap: 10 }}>
{cells.map((i) => {
const active = i === filled;
const has = i < filled;
return (
<div key={i} style={{
width: 50, height: 60, borderRadius: 10,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'rgba(237,234,255,.04)',
border: `1.5px solid ${error ? T.gold : active ? T.gold : T.hair}`,
boxShadow: active ? '0 0 0 3px var(--wv-gold-28)' : 'none',
fontFamily: T.disp, fontWeight: 500, fontSize: 24, color: T.star,
}}>
{has ? vals[i] : (active ? <span style={{ width: 1.5, height: 26, background: T.gold }} /> : '')}
</div>
);
})}
</div>
);
}
// ── Banner — inline status. attn = gold (no red in palette); info = lilac ──
function Banner({ children, tone = 'attn', title }) {
const attn = tone === 'attn';
return (
<div style={{
display: 'flex', gap: 12, padding: '13px 16px', borderRadius: 12,
background: attn ? 'rgba(244,199,107,.10)' : 'var(--wv-lilac-08)',
border: `1px solid ${attn ? 'var(--wv-gold-40)' : 'var(--wv-lilac-18)'}`,
fontFamily: T.body, alignItems: 'flex-start',
}}>
<span aria-hidden="true" style={{ color: attn ? T.gold : T.lilac, fontSize: 14, lineHeight: '21px' }}>
{attn ? '◆' : '◇'}
</span>
<div style={{ fontSize: 14, lineHeight: 1.5, color: T.soft }}>
{title && <div style={{ color: T.star, fontWeight: 600, marginBottom: 2 }}>{title}</div>}
{children}
</div>
</div>
);
}
// ── AuthCard — the centered raised surface for Direction A ─────────────────
function AuthCard({ children, width = 420 }) {
return (
<div style={{
width, maxWidth: '100%', boxSizing: 'border-box',
background: T.raised, border: `1px solid ${T.hairCard}`, borderRadius: 14,
padding: '38px 38px 34px',
display: 'flex', flexDirection: 'column', gap: 22,
}}>{children}</div>
);
}
// ── BrandRail — the persistent identity panel for Direction B ──────────────
function BrandRail({ compact = false, soul = 'Your storefront is yours.' }) {
return (
<aside style={{
flex: compact ? '0 0 auto' : '0 0 42%',
background: `${STARFIELD}, ${T.night}`,
borderRight: compact ? 'none' : `1px solid ${T.hair}`,
borderBottom: compact ? `1px solid ${T.hair}` : 'none',
padding: compact ? '26px 28px' : '44px 46px',
display: 'flex', flexDirection: 'column', justifyContent: compact ? 'flex-start' : 'space-between',
position: 'relative', overflow: 'hidden', gap: compact ? 18 : 0,
}}>
<Wordmark size={compact ? 24 : 28} />
{!compact && (
<div style={{ maxWidth: 360 }}>
<Soul size="lg" tone="gold" as="p">Treat humans as humans <br />everything else follows.</Soul>
<p style={{ fontFamily: T.body, fontSize: 14.5, lineHeight: 1.6, color: T.mute, marginTop: 20 }}>
Honest commerce, built on one shared set of ethics. No trial clock, no plan wall just your storefront.
</p>
</div>
)}
{compact && <Soul size="sm" tone="gold" as="p">{soul}</Soul>}
{!compact && (
<div style={{ display: 'flex', alignItems: 'center', gap: 10, fontFamily: T.body, fontSize: 12, color: T.mute }}>
<span style={{ width: 22, height: 1, background: T.hair }} />
<span>A Wiggleverse line · Open Core</span>
</div>
)}
</aside>
);
}
// ── Footer — minimal project identity + the standing dedication ────────────
function Footer({ onDark = true }) {
return (
<footer style={{
flex: '0 0 auto', padding: '20px 32px', borderTop: `1px solid ${T.hair}`,
display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16,
fontFamily: T.body, fontSize: 12.5, color: T.mute,
}}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 9 }}>
<img src="assets/mark-mono-gold.svg" width={16} height={16} alt="" style={{ opacity: 0.8 }} />
ecomm · a Wiggleverse line
</span>
<span>Privacy · Open Core</span>
</footer>
);
}
// ── Section wrapper used in the canvas to title state stacks ───────────────
function StateBlock({ label, note, children, h }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
<Tag variant="soon">{label}</Tag>
{note && <span style={{ fontFamily: T.body, fontSize: 12.5, color: T.mute }}>{note}</span>}
</div>
<div style={{
background: T.raised, border: `1px solid ${T.hairCard}`, borderRadius: 12,
padding: 22, height: h, boxSizing: 'border-box',
}}>{children}</div>
</div>
);
}
// ── Honest-empty admin body — the heart of PUC-8 ───────────────────────────
function EmptyAdmin({ storefront = 'Studio Fern', compact = false }) {
return (
<div style={{
flex: 1, display: 'flex', flexDirection: 'column',
alignItems: 'flex-start', justifyContent: 'center',
padding: compact ? '32px 26px' : '0 64px', maxWidth: 720, margin: compact ? 0 : '0 auto',
width: '100%', boxSizing: 'border-box',
}}>
<Eyebrow style={{ margin: '0 0 14px' }}>Your storefront</Eyebrow>
<h1 style={{
fontFamily: T.disp, fontWeight: 700, letterSpacing: '-0.015em', lineHeight: 1.05,
fontSize: compact ? 30 : 'var(--text-h1)', color: T.star, margin: '0 0 16px',
}}>{storefront}</h1>
<p style={{
fontFamily: T.body, fontSize: compact ? 15 : 17, lineHeight: 1.6, color: T.soft,
maxWidth: 480, margin: 0,
}}>
There's nothing to manage yet. Catalog, orders, and settings will appear
here as ecomm grows.
</p>
</div>
);
}
Object.assign(window, {
WF_T: T, WF_STARFIELD: STARFIELD,
Screen, Wordmark, TopBar, AccountChip, MenuRow, Field, FieldNote, CodeField,
Banner, AuthCard, BrandRail, Footer, StateBlock, EmptyAdmin,
WF_DS: DS,
});