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

268 lines
15 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// hf-kit.jsx — Wiggleverse ecomm HI-FI primitives (Direction A · Quiet centered).
// Depth = layered surfaces + hairlines (the brand avoids shadows). Hover = 13px lift.
// Merchant persona: Ben's Bets · ben@bensbets.com
const HFDS = window.WiggleverseDesignSystem_94cd80;
const { Button: HFButton, Soul: HFSoul, Eyebrow: HFEyebrow, Tag: HFTag } = HFDS;
// Resolve a brand asset: blob URL when bundled offline (window.__resources), else relative path.
const HF_ASSET = (id, path) => (window.__resources && window.__resources[id]) || path;
window.HF_ASSET = HF_ASSET;
// ---- token shorthands ----------------------------------------------------
const H = {
sky: 'var(--wv-midnight)', night: 'var(--wv-night)',
raised: 'var(--wv-indigo)', raisedHi: 'var(--wv-indigo-2)',
star: 'var(--wv-starlight)', soft: 'var(--wv-starlight-78)',
mute: 'var(--wv-starlight-60)', faint: 'var(--wv-starlight-55)',
lilac: 'var(--wv-lilac)', violet: 'var(--wv-violet)',
gold: 'var(--wv-gold)', goldHi: 'var(--wv-gold-hi)', goldInk: 'var(--wv-gold-ink)',
hair: 'var(--border-soft)', hairCard: 'var(--border-card)', hairStrong: 'var(--border-strong)',
disp: 'var(--wv-font-display)', body: 'var(--wv-font-body)', soul: 'var(--wv-font-human)',
shadow: 'var(--shadow-soft)',
};
const MENU_EMAIL = 'ben@bensbets.com';
const STORE = "Ben's Bets";
// barely-there starfield (Direction A: restrained) — a few asymmetric nodes
const HF_STARS =
'radial-gradient(1.3px 1.3px at 16% 22%, rgba(237,234,255,.34), transparent),' +
'radial-gradient(1.2px 1.2px at 78% 14%, rgba(237,234,255,.22), transparent),' +
'radial-gradient(1.1px 1.1px at 88% 46%, rgba(155,140,255,.30), transparent),' +
'radial-gradient(1.3px 1.3px at 30% 72%, rgba(237,234,255,.24), transparent),' +
'radial-gradient(1.1px 1.1px at 62% 86%, rgba(237,234,255,.18), transparent),' +
'radial-gradient(1.2px 1.2px at 7% 56%, rgba(155,140,255,.26), transparent)';
// gold horizon — the brand's warmth, a low glow at the bottom of the sky
const HF_HORIZON = 'radial-gradient(120% 70% at 50% 142%, rgba(244,199,107,.16) 0%, rgba(244,199,107,.05) 38%, transparent 64%)';
// One-time injected stylesheet: hover + focus affordances (subtle motion only).
function HFStyles() {
return (
<style dangerouslySetInnerHTML={{ __html: `
.hf { --r: var(--radius-card); }
.hf a.lk { color: var(--wv-lilac); text-decoration: none; transition: color .15s ease; }
.hf a.lk:hover { color: var(--wv-gold); }
.hf .row-lift { transition: transform .15s ease, border-color .15s ease, background .15s ease; }
.hf .row-lift:hover { transform: translateY(-1px); border-color: var(--border-strong); background: var(--wv-indigo-2); }
.hf .field { transition: border-color .15s ease, box-shadow .15s ease, background .15s ease; }
.hf .field:hover { border-color: var(--border-strong); }
.hf .field.is-focus { border-color: var(--wv-gold); box-shadow: 0 0 0 3px var(--wv-gold-28); background: rgba(237,234,255,.06); }
.hf .field.is-attn { border-color: var(--wv-gold); }
.hf .caret { display:inline-block; width:1.5px; height:1.1em; background: var(--wv-gold); margin-left:1px; vertical-align:-2px; animation: hfblink 1.1s steps(1) infinite; }
@keyframes hfblink { 50% { opacity: 0; } }
.hf .signout { font-family: var(--wv-font-display); font-weight:500; font-size:14px; color: var(--wv-starlight); background:transparent; border:none; cursor:pointer; padding:0; transition: color .15s ease; }
.hf .signout:hover { color: var(--wv-gold); }
.hf .navitem { transition: background .15s ease, color .15s ease; }
` }} />
);
}
// ── Screen ground ──────────────────────────────────────────────────────────
function HFScreen({ children, horizon = false, stars = true, bg = H.sky, style = {} }) {
const layers = [];
if (stars) layers.push(HF_STARS);
if (horizon) layers.unshift(HF_HORIZON);
const background = layers.length ? `${layers.join(',')}, ${bg}` : bg;
return (
<div className="hf" style={{
width: '100%', height: '100%', boxSizing: 'border-box', background,
color: H.star, fontFamily: H.body, display: 'flex', flexDirection: 'column',
position: 'relative', overflow: 'hidden', ...style,
}}>{children}</div>
);
}
// ── Wordmark — mark tile + "ecomm" in Space Grotesk ───────────────────────
function HFWordmark({ size = 26, sub = false }) {
return (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: size * 0.42 }}>
<img src={HF_ASSET('markTile', 'assets/mark-tile.svg')} width={size} height={size}
style={{ borderRadius: size * 0.22, display: 'block' }} alt="" />
<span style={{ display: 'inline-flex', alignItems: 'baseline', gap: size * 0.3 }}>
<span style={{ fontFamily: H.disp, fontWeight: 700, fontSize: size * 0.86, letterSpacing: '-0.015em', color: H.star }}>ecomm</span>
{sub && <span style={{ fontFamily: H.body, fontSize: size * 0.42, color: H.mute, letterSpacing: '0.02em' }}>by Wiggleverse</span>}
</span>
</span>
);
}
// ── TopBar — glass chrome ──────────────────────────────────────────────────
function HFTopBar({ left, right, pad = '0 36px', height = 68 }) {
return (
<header style={{
flex: '0 0 auto', height, padding: pad, display: 'flex', alignItems: 'center',
justifyContent: 'space-between', borderBottom: `1px solid ${H.hair}`,
background: 'var(--glass-sky)', backdropFilter: 'blur(var(--glass-blur))',
position: 'relative', zIndex: 5,
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>{left}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 18 }}>{right}</div>
</header>
);
}
function HFAccountChip({ email = MENU_EMAIL, menu = false }) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 14, position: 'relative' }}>
<span style={{ width: 30, height: 30, borderRadius: 15, background: 'var(--wv-lilac-16)', border: `1px solid ${H.hairCard}`, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: H.disp, fontWeight: 700, fontSize: 13, color: H.star }}>B</span>
<span style={{ fontFamily: H.body, fontSize: 14, color: H.soft }}>{email}</span>
<span style={{ width: 1, height: 18, background: H.hair }} />
<button className="signout">Sign out</button>
{menu && (
<div style={{ position: 'absolute', top: 40, right: 0, minWidth: 216, zIndex: 9, background: H.raised, border: `1px solid ${H.hairCard}`, borderRadius: 12, padding: 7, boxShadow: H.shadow }}>
<div style={{ padding: '9px 12px' }}>
<div style={{ fontFamily: H.disp, fontWeight: 500, fontSize: 13.5, color: H.star }}>Ben</div>
<div style={{ fontFamily: H.body, fontSize: 12.5, color: H.mute }}>{email}</div>
</div>
<div style={{ borderTop: `1px solid ${H.hair}`, margin: '6px 4px' }} />
<div className="navitem" style={{ padding: '9px 12px', fontFamily: H.body, fontSize: 13.5, color: H.soft, borderRadius: 8, cursor: 'pointer' }}>Account settings</div>
<div className="navitem" style={{ padding: '9px 12px', fontFamily: H.body, fontSize: 13.5, color: H.gold, fontWeight: 600, borderRadius: 8, cursor: 'pointer' }}>Sign out</div>
</div>
)}
</div>
);
}
// ── Field ──────────────────────────────────────────────────────────────────
function HFField({ label, placeholder, value, helper, error, focus = false, optional = false, hint }) {
const cls = `field${focus ? ' is-focus' : ''}${error ? ' is-attn' : ''}`;
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<label style={{ fontFamily: H.body, fontWeight: 500, fontSize: 13.5, color: H.soft, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
<span>{label}</span>
{optional && <span style={{ fontSize: 12.5, color: H.mute, fontWeight: 400 }}>optional</span>}
</label>
<div className={cls} style={{
height: 52, borderRadius: 10, padding: '0 16px', display: 'flex', alignItems: 'center',
background: 'rgba(237,234,255,.035)', border: `1.5px solid ${H.hair}`,
fontFamily: H.body, fontSize: 16, color: value ? H.star : H.mute,
}}>
{value || placeholder}
{focus && !value && <span className="caret" />}
{hint && <span style={{ marginLeft: 'auto', fontFamily: H.body, fontSize: 12.5, color: H.mute }}>{hint}</span>}
</div>
{error && <HFNote tone="attn">{error}</HFNote>}
{helper && !error && <HFNote>{helper}</HFNote>}
</div>
);
}
function HFNote({ children, tone }) {
return <span style={{ fontFamily: H.body, fontSize: 13, lineHeight: 1.45, color: tone === 'attn' ? H.gold : H.mute, display: 'flex', gap: 7, alignItems: 'baseline' }}>
{tone === 'attn' && <span aria-hidden="true" style={{ color: H.gold, fontSize: 11 }}></span>}
<span>{children}</span>
</span>;
}
// ── CodeField — 6 cells ─────────────────────────────────────────────────────
function HFCodeField({ filled = 0, error = false, vals = ['4', '1', '9', '2', '0', '4'] }) {
return (
<div style={{ display: 'flex', gap: 11 }}>
{[0, 1, 2, 3, 4, 5].map((i) => {
const active = i === filled && !error;
const has = i < filled;
return (
<div key={i} className={`field${active ? ' is-focus' : ''}${error ? ' is-attn' : ''}`} style={{
width: 52, height: 64, borderRadius: 11, display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'rgba(237,234,255,.035)', border: `1.5px solid ${H.hair}`,
fontFamily: H.disp, fontWeight: 500, fontSize: 26, color: H.star,
}}>
{has ? vals[i] : (active ? <span className="caret" style={{ height: 28 }} /> : '')}
</div>
);
})}
</div>
);
}
// ── Banner — gold attention / lilac info (no red in palette) ───────────────
function HFBanner({ children, tone = 'attn', title }) {
const attn = tone === 'attn';
return (
<div style={{
display: 'flex', gap: 12, padding: '14px 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)'}`,
alignItems: 'flex-start',
}}>
<span aria-hidden="true" style={{ color: attn ? H.gold : H.lilac, fontSize: 13, lineHeight: '22px' }}>{attn ? '◆' : '◇'}</span>
<div style={{ fontFamily: H.body, fontSize: 14, lineHeight: 1.5, color: H.soft }}>
{title && <div style={{ color: H.star, fontWeight: 600, marginBottom: 2 }}>{title}</div>}
{children}
</div>
</div>
);
}
// ── AuthCard — the one surface that earns a real shadow ────────────────────
function HFAuthCard({ children, width = 440 }) {
return (
<div style={{
width, maxWidth: '100%', boxSizing: 'border-box', background: H.raised,
border: `1px solid ${H.hairCard}`, borderRadius: 'var(--radius-card)',
padding: '40px 40px 34px', display: 'flex', flexDirection: 'column', gap: 22,
boxShadow: H.shadow, position: 'relative', zIndex: 2,
}}>{children}</div>
);
}
// ── Primary action wrapper (DS Button, full-width option) ──────────────────
function HFPrimary({ children, full = true, disabled = false }) {
return (
<HFButton variant="primary" href={disabled ? undefined : '#'} disabled={disabled}
style={{ justifyContent: 'center', width: full ? '100%' : 'auto', padding: '.85rem 1.4rem', fontSize: 15.5, boxSizing: 'border-box' }}>
{children}
</HFButton>
);
}
// ── Footer ──────────────────────────────────────────────────────────────────
function HFFooter({ pad = '20px 36px' }) {
return (
<footer style={{ flex: '0 0 auto', padding: pad, borderTop: `1px solid ${H.hair}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16, fontFamily: H.body, fontSize: 12.5, color: H.mute, background: 'rgba(9,12,34,.4)' }}>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 9 }}>
<img src={HF_ASSET('markGold', 'assets/mark-mono-gold.svg')} width={16} height={16} alt="" style={{ opacity: 0.85 }} />
ecomm · a Wiggleverse line
</span>
<span style={{ display: 'inline-flex', gap: 18 }}>
<a className="lk" href="#" style={{ color: H.mute }}>Privacy</a>
<a className="lk" href="#" style={{ color: H.mute }}>Open Core</a>
<a className="lk" href="#" style={{ color: H.mute }}>Terms</a>
</span>
</footer>
);
}
// ── Heading helper ───────────────────────────────────────────────────────────
function HFHeading({ children, sub, size = 27 }) {
return (
<div>
<h1 style={{ fontFamily: H.disp, fontWeight: 700, letterSpacing: '-0.015em', fontSize: size, color: H.star, margin: '0 0 9px', lineHeight: 1.08 }}>{children}</h1>
{sub && <p style={{ fontFamily: H.body, fontSize: 14.5, lineHeight: 1.55, color: H.soft, margin: 0 }}>{sub}</p>}
</div>
);
}
const hfHonest = { fontFamily: H.body, fontSize: 13, color: H.mute, lineHeight: 1.5, margin: 0 };
// ── StateCard — frames a single state in the states stacks ─────────────────
function HFStateCard({ label, note, children }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 9, flexWrap: 'wrap' }}>
<HFTag variant="soon">{label}</HFTag>
{note && <span style={{ fontFamily: H.body, fontSize: 12.5, color: H.mute }}>{note}</span>}
</div>
<div style={{ background: H.raised, border: `1px solid ${H.hairCard}`, borderRadius: 12, padding: 22 }}>{children}</div>
</div>
);
}
function HFStates({ children }) {
return <div className="hf" style={{ height: '100%', background: `${HF_STARS}, ${H.sky}`, padding: 26, boxSizing: 'border-box', overflow: 'hidden', fontFamily: H.body, display: 'flex', flexDirection: 'column', gap: 20 }}>{children}</div>;
}
Object.assign(window, {
HF_H: H, HF_STARS, HF_HORIZON, HF_STORE: STORE, HF_EMAIL: MENU_EMAIL, HF_DSC: HFDS,
HFStyles, HFScreen, HFWordmark, HFTopBar, HFAccountChip, HFField, HFNote, HFCodeField,
HFBanner, HFAuthCard, HFPrimary, HFFooter, HFHeading, HFStateCard, HFStates,
hfHonest,
});