Files
vibn-agent-runner/vibn-frontend/_onboarding/onboarding-entrepreneur.tsx

2561 lines
69 KiB
TypeScript
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.
import React, {
useState,
useEffect,
useRef,
useMemo,
useCallback,
} from "react";
import {
WizardTop,
WizardBody,
WizardQ,
WizardFooter,
LANE_LABELS,
ChipGroup,
PresetGroup,
Field,
} from "./onboarding-primitives";
// Entrepreneur path — 4 steps. Each step is a focused question.
const ENTREP_TOTAL = 4;
const ENTREP_STEP_NAMES = ["Type", "Style", "Idea", "Look"];
const IDEA_PROMPTS = [
"A community for indie game devs to swap playtesters, with weekly demo nights",
"An AI tool that turns my handwritten recipe notes into a clean cookbook for my family",
"A waitlist + scheduler for my pottery studio — small classes, six people max",
"A subscription box service for cold-brew enthusiasts, with monthly tasting cards",
"A simple tool that turns my Strava data into framed art prints I can sell",
];
export function EntrepIdea({ value, onChange }) {
const [phIdx, setPhIdx] = React.useState(0);
const [phChars, setPhChars] = React.useState(0);
const [deleting, setDeleting] = React.useState(false);
React.useEffect(() => {
if (value.length > 0) return undefined;
const full = IDEA_PROMPTS[phIdx];
const speed = deleting ? 18 : 38;
const t = setTimeout(() => {
if (!deleting) {
if (phChars < full.length) setPhChars(phChars + 1);
else setTimeout(() => setDeleting(true), 1500);
} else {
if (phChars > 0) setPhChars(phChars - 1);
else {
setDeleting(false);
setPhIdx((phIdx + 1) % IDEA_PROMPTS.length);
}
}
}, speed);
return () => clearTimeout(t);
}, [value, phIdx, phChars, deleting]);
return (
<>
<WizardQ
title="What are you building?"
sub="Don't worry if it's not crisp yet — just dump your thoughts. Talk like you would to a friend."
/>
<div style={{ position: "relative" }}>
<textarea
className="wiz-input"
style={{ minHeight: 200, fontSize: 15 }}
value={value}
onChange={(e) => onChange(e.target.value)}
autoFocus
aria-label="Describe your idea"
/>
{value.length === 0 && (
<div
style={{
position: "absolute",
top: 12,
left: 14,
right: 14,
pointerEvents: "none",
color: "var(--fg-faint)",
font: "14.5px/1.5 var(--font-sans)",
}}
>
{IDEA_PROMPTS[phIdx].slice(0, phChars)}
<span
style={{
display: "inline-block",
width: 7,
height: 14,
verticalAlign: "-2px",
background: "var(--accent)",
marginLeft: 1,
animation: "blink 1s steps(2) infinite",
boxShadow: "0 0 10px var(--accent-glow)",
}}
/>
</div>
)}
</div>
<div
className="mono"
style={{
fontSize: 11,
color: "var(--fg-faint)",
letterSpacing: "0.06em",
marginTop: -16,
}}
>
{value.length} chars · be specific where it matters
</div>
</>
);
}
const ARCHETYPES = [
{
id: "saas",
label: "Web App / SaaS",
desc: "Dashboards, tools, interactive portals",
},
{
id: "marketplace",
label: "Marketplace",
desc: "Directories, bookings, listings",
},
{
id: "marketing",
label: "Marketing Site",
desc: "Portfolios, lead capture, landing pages",
},
{
id: "ecommerce",
label: "Online Store",
desc: "Carts, checkouts, selling physical/digital goods",
},
{
id: "mobile",
label: "Mobile App",
desc: "iOS and Android mobile applications",
},
{
id: "blog",
label: "Blog / Publication",
desc: "Newsletters, articles, content hubs",
},
{
id: "not_sure",
label: "I'm not sure",
desc: "Let Vibn help you decide based on your description",
fullWidth: true,
},
];
function EntrepType({ value, onChange }) {
return (
<>
<WizardQ
title="What kind of product is it?"
sub="Helps Vibn set up the right database, integrations, and starting code."
/>
<PresetGroup
options={ARCHETYPES.map((a) => ({
id: a.id,
label: a.label,
desc: a.desc,
icon: undefined,
fullWidth: a.fullWidth,
}))}
value={value}
onChange={onChange}
columns={2}
/>
</>
);
}
function LayoutPreview({ styleId }: { styleId: string }) {
// Ultra-high-fidelity CSS-rendered miniatures representing your actual page-dashboard.jsx designs!
const isDark =
styleId === "sidebar_dark" ||
styleId === "topbar_dark" ||
styleId === "rail";
const accentColor = "var(--accent)";
// Theme palettes from page-dashboard.jsx
const c = isDark
? {
bg: "#0e0f12",
panel: "#16171d",
border: "rgba(255,255,255,0.06)",
text: "#f5f4f2",
subtext: "#a09fa6",
muted: "#686972",
accent: "var(--accent)",
up: "#22c55e",
down: "#ff4d5e",
}
: {
bg: "#fafaf9",
panel: "#ffffff",
border: "#ebebe6",
text: "#1a1a1a",
subtext: "#5a5a5e",
muted: "#a09a90",
accent: "var(--accent)",
up: "#2e7d32",
down: "#d32f2f",
};
const wireLine = (w: string | number, opacity = 0.25) => (
<div
style={{
height: 2.5,
width: w,
borderRadius: 1.5,
background: isDark ? "#ffffff" : "#000000",
opacity,
}}
/>
);
// High-fidelity Avatars matching your screenshots (MR, TR, DP, SK)
const MiniAvatar = ({
text: label,
color,
}: {
text: string;
color: string;
}) => (
<div
style={{
width: 10,
height: 10,
borderRadius: "50%",
background: color,
color: "#3a2210",
fontSize: 4.5,
fontWeight: 700,
display: "grid",
placeItems: "center",
flexShrink: 0,
}}
>
{label}
</div>
);
// Common high-fidelity dashboard body matching page-dashboard.jsx
const renderDashboardMockup = () => (
<div
style={{
flex: 1,
padding: "12px 14px",
display: "flex",
flexDirection: "column",
gap: 8,
background: c.bg,
color: c.text,
overflow: "hidden",
filter: "blur(3.5px)",
pointerEvents: "none",
}}
>
{/* Header */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-end",
borderBottom: `1px solid ${c.border}`,
paddingBottom: 6,
width: "100%",
}}
>
<div>
<span
style={{
fontSize: 5,
textTransform: "uppercase",
color: c.muted,
letterSpacing: "0.04em",
display: "block",
marginBottom: 1,
fontWeight: 600,
}}
>
Workspace dashboard
</span>
<span
style={{
fontSize: 11.5,
fontWeight: 700,
letterSpacing: "-0.01em",
display: "block",
lineHeight: 1,
}}
>
Good afternoon, Mira
</span>
<span
style={{
fontSize: 6,
color: c.subtext,
display: "block",
marginTop: 2,
}}
>
3 deals moved stage today · 12 unread in Inbox · 1 task overdue
</span>
</div>
<div style={{ display: "flex", gap: 4, paddingBottom: 1 }}>
<div
style={{
width: 38,
height: 11,
borderRadius: 3,
background: c.panel,
border: `1px solid ${c.border}`,
display: "flex",
alignItems: "center",
paddingLeft: 3,
}}
>
<div
style={{ height: 2, width: 22, background: c.text, opacity: 0.6 }}
/>
</div>
<div
style={{
width: 22,
height: 11,
borderRadius: 3,
background: c.panel,
border: `1px solid ${c.border}`,
}}
/>
<div
style={{
width: 28,
height: 11,
borderRadius: 3,
background: isDark ? "#ffffff" : "#1a1a1a",
}}
/>
</div>
</div>
{/* KPI Strip */}
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: 4,
}}
>
{[
{ l: "Revenue · MTD", v: "€286,420", d: "+18.4%", up: true },
{ l: "Active deals", v: "168", d: "+12", up: true },
{ l: "Win rate · 30d", v: "34.2%", d: "1.1%", up: false },
{ l: "Pipeline ratio", v: "4.8×", d: "healthy", up: true },
].map((k, i) => (
<div
key={i}
style={{
padding: "4px 5px",
borderRadius: 4,
background: c.panel,
border: `1px solid ${c.border}`,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "baseline",
marginBottom: 2,
}}
>
<span style={{ fontSize: 4, color: c.muted, fontWeight: 500 }}>
{k.l}
</span>
<span
style={{
fontSize: 3.5,
color: k.up
? c.up
: k.d.startsWith("") || k.d.startsWith("-")
? c.down
: c.up,
fontWeight: 600,
}}
>
{k.d}
</span>
</div>
<div
style={{
fontSize: 8,
fontWeight: 700,
letterSpacing: "-0.01em",
fontVariantNumeric: "tabular-nums",
}}
>
{k.v}
</div>
<svg
viewBox="0 0 100 20"
style={{
width: "100%",
height: 5,
display: "block",
marginTop: 2,
}}
preserveAspectRatio="none"
>
<path
d={
k.up
? "M0,15 L20,12 L40,14 L60,8 L80,10 L100,2"
: "M0,2 L20,5 L40,4 L60,12 L80,14 L100,18"
}
fill="none"
stroke={k.up ? c.up : c.down}
strokeWidth="2"
/>
</svg>
</div>
))}
</div>
{/* Main Grid: charts, activity and leaderboards */}
<div
style={{
display: "grid",
gridTemplateColumns: "1.4fr 1fr",
gap: 5,
flex: 1,
minHeight: 0,
}}
>
{/* Left column: Daily Revenue Chart + Recent Activity */}
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
{/* Revenue Daily Chart */}
<div
style={{
padding: 5,
borderRadius: 5,
background: c.panel,
border: `1px solid ${c.border}`,
display: "flex",
flexDirection: "column",
gap: 3,
}}
>
<span style={{ fontSize: 4.5, color: c.muted, fontWeight: 600 }}>
Revenue, daily
</span>
<div
style={{
display: "flex",
gap: 1.5,
alignItems: "flex-end",
height: 16,
}}
>
{[10, 16, 22, 18, 25, 8, 6, 14, 20, 26, 22, 28, 12, 10].map(
(h, idx) => (
<div
key={idx}
style={{
flex: 1,
height: `${(h / 30) * 100}%`,
background:
idx === 11
? c.accent
: isDark
? "rgba(255,255,255,0.08)"
: "rgba(0,0,0,0.06)",
borderRadius: 0.5,
}}
/>
),
)}
</div>
</div>
{/* Recent Activity Card */}
<div
style={{
flex: 1,
padding: 5,
borderRadius: 5,
background: c.panel,
border: `1px solid ${c.border}`,
display: "flex",
flexDirection: "column",
gap: 3,
overflow: "hidden",
}}
>
<span style={{ fontSize: 4.5, color: c.muted, fontWeight: 600 }}>
Recent activity
</span>
<div style={{ display: "flex", flexDirection: "column", gap: 2.5 }}>
{[
{
av: "MR",
col: "#d4b8a8",
name: "Mira Reyes",
action: "moved Q3 to ",
highlight: "Negotiation",
hlCol: c.accent,
},
{
av: "TR",
col: "#c2d3a8",
name: "Theo Roux",
action: "logged call with ",
highlight: "Sun Kim",
hlCol: c.text,
},
{
av: "DP",
col: "#b8d4e8",
name: "Devi Patel",
action: "closed ",
highlight: "Halcyon",
hlCol: c.text,
},
].map((act, i) => (
<div
key={i}
style={{ display: "flex", gap: 4, alignItems: "center" }}
>
<MiniAvatar text={act.av} color={act.col} />
<div
style={{
display: "flex",
flexDirection: "column",
gap: 0.5,
}}
>
<div
style={{
fontSize: 3.5,
color: c.text,
fontWeight: 500,
lineHeight: 1,
}}
>
{act.name}
</div>
<div
style={{ fontSize: 3, color: c.subtext, lineHeight: 1 }}
>
{act.action}
<span style={{ color: act.hlCol, fontWeight: 600 }}>
{act.highlight}
</span>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Right column: Pipeline Funnel + Team leaderboard */}
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
{/* Pipeline Funnel */}
<div
style={{
padding: 5,
borderRadius: 5,
background: c.panel,
border: `1px solid ${c.border}`,
display: "flex",
flexDirection: "column",
gap: 3,
}}
>
<span style={{ fontSize: 4.5, color: c.muted, fontWeight: 600 }}>
Pipeline funnel
</span>
<div style={{ display: "flex", flexDirection: "column", gap: 1.5 }}>
{[
{ s: "New", w: "100%", col: "#5e5cff" },
{ s: "Qual", w: "80%", col: "#6d5cff" },
{ s: "Prop", w: "60%", col: "#7c5cff" },
{ s: "Nego", w: "40%", col: "#8c5cff" },
{ s: "Won", w: "20%", col: "#22c55e" },
].map((f, i) => (
<div
key={i}
style={{ display: "flex", alignItems: "center", gap: 4 }}
>
<span
style={{
fontSize: 3.5,
color: c.muted,
width: 12,
textAlign: "right",
whiteSpace: "nowrap",
}}
>
{f.s}
</span>
<div
style={{
flex: 1,
height: 3,
background: isDark
? "rgba(255,255,255,0.03)"
: "rgba(0,0,0,0.03)",
borderRadius: 1,
overflow: "hidden",
}}
>
<div
style={{
width: f.w,
height: "100%",
background: f.col,
borderRadius: 1,
}}
/>
</div>
</div>
))}
</div>
</div>
{/* Team Leaders Card */}
<div
style={{
flex: 1,
padding: 5,
borderRadius: 5,
background: c.panel,
border: `1px solid ${c.border}`,
display: "flex",
flexDirection: "column",
gap: 3,
overflow: "hidden",
}}
>
<span style={{ fontSize: 4.5, color: c.muted, fontWeight: 600 }}>
Team · this month
</span>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 3,
justifyContent: "center",
flex: 1,
}}
>
{[
{ av: "MR", col: "#d4b8a8", name: "Mira Reyes", val: "€124k" },
{ av: "DP", col: "#b8d4e8", name: "Devi Patel", val: "€86k" },
{ av: "TR", col: "#c2d3a8", name: "Theo Roux", val: "€62k" },
].map((team, i) => (
<div
key={i}
style={{ display: "flex", alignItems: "center", gap: 4 }}
>
<MiniAvatar text={team.av} color={team.col} />
<div
style={{
display: "flex",
flexDirection: "column",
gap: 1,
flex: 1,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "baseline",
}}
>
<span
style={{
fontSize: 3.5,
color: c.text,
fontWeight: 500,
}}
>
{team.name}
</span>
<span
style={{
fontSize: 3.5,
color: c.muted,
fontWeight: 600,
}}
>
{team.val}
</span>
</div>
<div
style={{
width: "100%",
height: 2,
background: isDark
? "rgba(255,255,255,0.03)"
: "rgba(0,0,0,0.03)",
borderRadius: 1,
overflow: "hidden",
}}
>
<div
style={{
width: i === 0 ? "80%" : i === 1 ? "55%" : "40%",
height: "100%",
background: c.accent,
opacity: 0.8,
}}
/>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
let content;
if (styleId === "sidebar_light") {
// Left-side menu column + main dashboard area — Light/Minimal CRM theme (Screen 3)
content = (
<div
style={{
display: "flex",
width: "100%",
height: "100%",
background: "#fcfbfa",
}}
>
{/* Sidebar */}
<div
style={{
width: 52,
borderRight: "1px solid #eae6de",
padding: "8px 6px",
display: "flex",
flexDirection: "column",
gap: 6,
background: "#f5f4ef",
}}
>
{/* Logo / Workspace Selector */}
<div
style={{
display: "flex",
gap: 3,
alignItems: "center",
borderBottom: "1px solid #eae6de",
paddingBottom: 4,
}}
>
<div
style={{
width: 10,
height: 10,
borderRadius: 2.5,
background: accentColor,
opacity: 0.9,
}}
/>
<div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
<span
style={{
fontSize: 4.5,
fontWeight: 700,
color: "#1a1a1a",
letterSpacing: "-0.01em",
lineHeight: 1,
}}
>
Lattice Studio
</span>
<span
style={{ fontSize: 3, color: "var(--fg-mute)", lineHeight: 1 }}
>
Free · 4 members
</span>
</div>
</div>
{/* Mini Search box */}
<div
style={{
width: "100%",
height: 8,
borderRadius: 2,
border: "1px solid #eae6de",
background: "#fff",
display: "flex",
alignItems: "center",
paddingLeft: 3,
}}
>
{wireLine(16, 0.15)}
</div>
{/* Menu Sections (VIEWS, TOOLS, ADMIN) */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: 5,
marginTop: 2,
}}
>
{/* Standard Links */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<div style={{ display: "flex", gap: 3, alignItems: "center" }}>
<div
style={{
width: 4,
height: 4,
borderRadius: "50%",
background: accentColor,
}}
/>
{wireLine(12, 0.55)}
</div>
<div
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(14, 0.35)}
</div>
<div
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(16, 0.35)}
</div>
</div>
{/* Views */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<span
style={{
fontSize: 3.5,
color: c.muted,
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
}}
>
VIEWS
</span>
{["Companies", "People", "Opportunities"].map((v) => (
<div
key={v}
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(24, 0.35)}
</div>
))}
</div>
{/* Tools */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<span
style={{
fontSize: 3.5,
color: c.muted,
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
}}
>
TOOLS
</span>
{["Insights", "Automations", "Docs"].map((v) => (
<div
key={v}
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(20, 0.35)}
</div>
))}
</div>
</div>
</div>
{/* Main Dashboard (Light) */}
{renderDashboardMockup()}
</div>
);
} else if (styleId === "sidebar_dark") {
// Left-side menu column + main dashboard area — Full Dark theme (Screen 3 Dark)
content = (
<div
style={{
display: "flex",
width: "100%",
height: "100%",
background: "#0e0f12",
}}
>
{/* Sidebar */}
<div
style={{
width: 52,
borderRight: "1px solid rgba(255,255,255,0.08)",
padding: "8px 6px",
display: "flex",
flexDirection: "column",
gap: 6,
background: "#16171d",
}}
>
{/* Logo / Workspace Selector */}
<div
style={{
display: "flex",
gap: 3,
alignItems: "center",
borderBottom: "1px solid rgba(255,255,255,0.08)",
paddingBottom: 4,
}}
>
<div
style={{
width: 10,
height: 10,
borderRadius: 2.5,
background: accentColor,
opacity: 0.9,
}}
/>
<div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
<span
style={{
fontSize: 4.5,
fontWeight: 700,
color: "#fff",
letterSpacing: "-0.01em",
lineHeight: 1,
}}
>
Lattice Studio
</span>
<span
style={{
fontSize: 3,
color: "rgba(255,255,255,0.4)",
lineHeight: 1,
}}
>
Free · 4 members
</span>
</div>
</div>
{/* Mini Search box */}
<div
style={{
width: "100%",
height: 8,
borderRadius: 2,
border: "1px solid rgba(255,255,255,0.08)",
background: "rgba(255,255,255,0.05)",
display: "flex",
alignItems: "center",
paddingLeft: 3,
}}
>
{wireLine(16, 0.15)}
</div>
{/* Menu Sections (VIEWS, TOOLS, ADMIN) */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: 5,
marginTop: 2,
}}
>
{/* Standard Links */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<div style={{ display: "flex", gap: 3, alignItems: "center" }}>
<div
style={{
width: 4,
height: 4,
borderRadius: "50%",
background: accentColor,
}}
/>
{wireLine(12, 0.55)}
</div>
<div
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(14, 0.35)}
</div>
<div
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(16, 0.35)}
</div>
</div>
{/* Views */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<span
style={{
fontSize: 3.5,
color: c.muted,
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
}}
>
VIEWS
</span>
{["Companies", "People", "Opportunities"].map((v) => (
<div
key={v}
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(24, 0.35)}
</div>
))}
</div>
{/* Tools */}
<div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
<span
style={{
fontSize: 3.5,
color: c.muted,
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
}}
>
TOOLS
</span>
{["Insights", "Automations", "Docs"].map((v) => (
<div
key={v}
style={{
display: "flex",
gap: 3,
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(20, 0.35)}
</div>
))}
</div>
</div>
</div>
{/* Main Dashboard (Dark) */}
{renderDashboardMockup()}
</div>
);
} else if (styleId === "topbar_light") {
// Top Horizontal Dark Header Bar + Light Dashboard (Screen 2)
content = (
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
background: "#fcfbfa",
}}
>
{/* Top Dark Header */}
<div
style={{
height: 20,
borderBottom: "1px solid rgba(255,255,255,0.08)",
padding: "0 10px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
background: "#121110",
}}
>
<div style={{ display: "flex", gap: 6, alignItems: "center" }}>
<div
style={{
width: 8,
height: 8,
borderRadius: 2,
background: accentColor,
opacity: 0.9,
}}
/>
<span
style={{
fontSize: 5.5,
fontWeight: 700,
color: "#fff",
fontFamily: "var(--font-sans)",
letterSpacing: "-0.01em",
}}
>
Lattice
</span>
<span style={{ fontSize: 4.5, color: "rgba(255,255,255,0.3)" }}>
/
</span>
<div style={{ display: "flex", alignItems: "center", gap: 2 }}>
<div
style={{
width: 6,
height: 6,
borderRadius: "50%",
background: "#d4b8a8",
}}
/>
<span
style={{
fontSize: 4.5,
color: "rgba(255,255,255,0.6)",
fontWeight: 500,
}}
>
mira-reyes
</span>
</div>
<span style={{ fontSize: 4.5, color: "rgba(255,255,255,0.3)" }}>
/
</span>
<span
style={{
fontSize: 4.5,
color: "rgba(255,255,255,0.8)",
fontWeight: 500,
}}
>
dashboard
</span>
</div>
{/* Miniature ⌘K Bar */}
<div
style={{
width: 56,
height: 10,
borderRadius: 4,
border: "1px solid rgba(255,255,255,0.15)",
background: "rgba(255,255,255,0.05)",
display: "flex",
alignItems: "center",
paddingLeft: 4,
}}
>
<div
style={{
height: 2,
width: 22,
background: "#fff",
opacity: 0.25,
}}
/>
</div>
<div
style={{
width: 10,
height: 10,
borderRadius: "50%",
background: "rgba(255,255,255,0.2)",
}}
/>
</div>
{/* Page tabs secondary bar */}
<div
style={{
height: 14,
background: "#1a1918",
borderBottom: "1px solid rgba(255,255,255,0.04)",
padding: "0 10px",
display: "flex",
alignItems: "center",
gap: 10,
}}
>
{["Overview", "Reports", "Goals", "Anomalies", "Custom"].map(
(tab, idx) => (
<span
key={tab}
style={{
fontSize: 4.5,
color: idx === 0 ? "#fff" : "rgba(255,255,255,0.4)",
fontWeight: idx === 0 ? 600 : 400,
}}
>
{tab}
</span>
),
)}
</div>
{/* Light Dashboard below */}
{renderDashboardMockup()}
</div>
);
} else if (styleId === "topbar_dark") {
// Top Horizontal Dark Header Bar + Dark Dashboard (Screen 2 Dark)
content = (
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
background: "#0e0f12",
}}
>
{/* Top Dark Header */}
<div
style={{
height: 20,
borderBottom: "1px solid rgba(255,255,255,0.08)",
padding: "0 10px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
background: "#121110",
}}
>
<div style={{ display: "flex", gap: 6, alignItems: "center" }}>
<div
style={{
width: 8,
height: 8,
borderRadius: 2,
background: accentColor,
opacity: 0.9,
}}
/>
<span
style={{
fontSize: 5.5,
fontWeight: 700,
color: "#fff",
fontFamily: "var(--font-sans)",
letterSpacing: "-0.01em",
}}
>
Lattice
</span>
<span style={{ fontSize: 4.5, color: "rgba(255,255,255,0.3)" }}>
/
</span>
<div style={{ display: "flex", alignItems: "center", gap: 2 }}>
<div
style={{
width: 6,
height: 6,
borderRadius: "50%",
background: "#d4b8a8",
}}
/>
<span
style={{
fontSize: 4.5,
color: "rgba(255,255,255,0.6)",
fontWeight: 500,
}}
>
mira-reyes
</span>
</div>
<span style={{ fontSize: 4.5, color: "rgba(255,255,255,0.3)" }}>
/
</span>
<span
style={{
fontSize: 4.5,
color: "rgba(255,255,255,0.8)",
fontWeight: 500,
}}
>
dashboard
</span>
</div>
{/* Miniature ⌘K Bar */}
<div
style={{
width: 56,
height: 10,
borderRadius: 4,
border: "1px solid rgba(255,255,255,0.15)",
background: "rgba(255,255,255,0.05)",
display: "flex",
alignItems: "center",
paddingLeft: 4,
}}
>
<div
style={{
height: 2,
width: 22,
background: "#fff",
opacity: 0.25,
}}
/>
</div>
<div
style={{
width: 10,
height: 10,
borderRadius: "50%",
background: "rgba(255,255,255,0.2)",
}}
/>
</div>
{/* Page tabs secondary bar */}
<div
style={{
height: 14,
background: "#1a1918",
borderBottom: "1px solid rgba(255,255,255,0.04)",
padding: "0 10px",
display: "flex",
alignItems: "center",
gap: 10,
}}
>
{["Overview", "Reports", "Goals", "Anomalies", "Custom"].map(
(tab, idx) => (
<span
key={tab}
style={{
fontSize: 4.5,
color: idx === 0 ? "#fff" : "rgba(255,255,255,0.4)",
fontWeight: idx === 0 ? 600 : 400,
}}
>
{tab}
</span>
),
)}
</div>
{/* Dark Dashboard below */}
{renderDashboardMockup()}
</div>
);
} else if (styleId === "rail") {
// Icon rail + secondary vertical list panel — Complete Dark-mode Dashboard (Screen 1)
content = (
<div
style={{
display: "flex",
width: "100%",
height: "100%",
background: "#0e0f12",
}}
>
{/* Far-left Icon Rail (Black) */}
<div
style={{
width: 18,
borderRight: "1px solid rgba(255,255,255,0.08)",
padding: "8px 0",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 6,
background: "#121110",
}}
>
<div
style={{
width: 10,
height: 10,
borderRadius: 3,
background: accentColor,
opacity: 0.9,
}}
/>
{[1, 2, 3, 4].map((i) => (
<div
key={i}
style={{
width: 8,
height: 8,
borderRadius: 2,
background: "rgba(255,255,255,0.18)",
}}
/>
))}
</div>
{/* Secondary List Panel (Dark) */}
<div
style={{
width: 52,
borderRight: "1px solid rgba(255,255,255,0.08)",
padding: "8px 6px",
display: "flex",
flexDirection: "column",
gap: 5,
background: "#1a1918",
}}
>
{/* Group Header */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span
style={{
fontSize: 3.5,
color: c.muted,
fontWeight: 600,
letterSpacing: "0.04em",
textTransform: "uppercase",
}}
>
MY DASHBOARDS
</span>
</div>
{/* Active lists */}
<div style={{ display: "flex", flexDirection: "column", gap: 3.5 }}>
{[
{ n: "Workspace overview", s: "default", active: true },
{ n: "Revenue · weekly", s: "shared by Theo" },
{ n: "Pipeline health", s: "auto-refresh" },
{ n: "Team performance", s: "private" },
].map((dash, idx) => (
<div
key={idx}
style={{ display: "flex", flexDirection: "column", gap: 1 }}
>
<span
style={{
fontSize: 4,
color: dash.active ? "#fff" : c.subtext,
fontWeight: dash.active ? 600 : 400,
}}
>
{dash.n}
</span>
<span style={{ fontSize: 3, color: c.muted }}>{dash.s}</span>
</div>
))}
</div>
</div>
{/* Main Content Area (Dark) */}
{renderDashboardMockup()}
</div>
);
} else if (styleId === "flux") {
// Icon rail + secondary vertical list panel — Complete Dark-mode Dashboard (Screen 1)
content = (
<div
style={{
display: "flex",
width: "100%",
height: "100%",
background: "#0e0f12",
}}
>
{/* Far-left Icon Rail (Black) */}
<div
style={{
width: 18,
borderRight: "1px solid rgba(255,255,255,0.08)",
padding: "8px 0",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 6,
background: "#121110",
}}
>
<div
style={{
width: 10,
height: 10,
borderRadius: 3,
background: accentColor,
opacity: 0.9,
}}
/>
{[1, 2, 3, 4].map((i) => (
<div
key={i}
style={{
width: 8,
height: 8,
borderRadius: 2,
background: "rgba(255,255,255,0.18)",
}}
/>
))}
</div>
{/* Secondary List Panel (Dark) */}
<div
style={{
width: 48,
borderRight: "1px solid rgba(255,255,255,0.08)",
padding: 6,
display: "flex",
flexDirection: "column",
gap: 5,
background: "#1a1918",
}}
>
<div style={{ display: "flex", gap: 4, alignItems: "center" }}>
{wireLine(28, 0.45)}
</div>
<div style={{ height: 1, background: "rgba(255,255,255,0.08)" }} />
{wireLine(36, 0.35)}
{wireLine(24, 0.2)}
{wireLine(32, 0.2)}
{wireLine(28, 0.2)}
</div>
{/* Main Content Area (Dark) */}
<div
style={{
flex: 1,
padding: 8,
display: "flex",
flexDirection: "column",
gap: 6,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{wireLine(36, 0.65)}
<div
style={{
width: 8,
height: 8,
borderRadius: "50%",
background: "rgba(255,255,255,0.2)",
}}
/>
</div>
{/* 4 Mini Cards Grid (Dark) */}
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: 4,
}}
>
{[1, 2, 3, 4].map((i) => (
<div
key={i}
style={{
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 4,
padding: "4px 6px",
display: "flex",
flexDirection: "column",
gap: 2,
background: "#1a1918",
}}
>
{wireLine("50%", 0.35)}
<div
style={{
height: 2,
width: "100%",
background:
i === 3 ? "oklch(0.65 0.18 25)" : "oklch(0.78 0.16 155)",
opacity: 0.8,
}}
/>
</div>
))}
</div>
{/* Charts Row (Dark) */}
<div style={{ display: "flex", gap: 6, flex: 1 }}>
<div
style={{
flex: 1.5,
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 5,
padding: 6,
display: "flex",
flexDirection: "column",
gap: 4,
background: "#1a1918",
}}
>
<div
style={{
display: "flex",
gap: 3,
alignItems: "flex-end",
flex: 1,
paddingTop: 4,
}}
>
{[6, 12, 10, 16, 20, 14, 8].map((h, idx) => (
<div
key={idx}
style={{
flex: 1,
height: h,
background:
idx === 4
? "oklch(0.6 0.18 250)"
: "rgba(255,255,255,0.08)",
borderRadius: 1,
}}
/>
))}
</div>
</div>
<div
style={{
flex: 1,
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 5,
background: "#1a1918",
padding: 6,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
{wireLine("70%", 0.45)}
{wireLine("100%", 0.2)}
</div>
</div>
</div>
</div>
);
} else if (styleId === "flux") {
// Top horizontal header bar + multi-column page
content = (
<div
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
}}
>
{/* Topbar */}
<div
style={{
height: 20,
borderBottom: "1px solid var(--hairline)",
padding: "0 8px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
background: "oklch(0.19 0.009 60 / 0.3)",
}}
>
<div style={{ display: "flex", gap: 6 }}>
<div
style={{
width: 8,
height: 8,
borderRadius: "50%",
background: accentColor,
opacity: 0.7,
}}
/>
{wireLine(12, 0.3)}
{wireLine(12, 0.3)}
</div>
{/* Miniature ⌘K Bar */}
<div
style={{
width: 44,
height: 10,
borderRadius: 5,
border: "1px solid var(--hairline)",
display: "flex",
alignItems: "center",
paddingLeft: 4,
}}
>
{wireLine(8, 0.15)}
</div>
<div
style={{
width: 8,
height: 8,
borderRadius: "50%",
background: "rgba(255,255,255,0.15)",
}}
/>
</div>
{/* Main Grid */}
<div
style={{
flex: 1,
padding: 8,
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: 6,
}}
>
{[1, 2, 3].map((i) => (
<div
key={i}
style={{
border: "1px solid var(--hairline)",
borderRadius: 6,
padding: 6,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
{wireLine("50%", 0.3)}
{wireLine("100%", 0.15)}
</div>
))}
</div>
</div>
);
} else if (styleId === "rail") {
// Icon rail + secondary vertical list panel — Dark CRM/SaaS theme
content = (
<div style={{ display: "flex", width: "100%", height: "100%" }}>
{/* Far-left Icon Rail */}
<div
style={{
width: 18,
borderRight: "1px solid var(--hairline)",
padding: "6px 0",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 6,
background: "oklch(0.14 0.008 60 / 0.9)",
}}
>
<div
style={{
width: 10,
height: 10,
borderRadius: 3,
background: accentColor,
opacity: 0.8,
}}
/>
{[1, 2, 3].map((i) => (
<div
key={i}
style={{
width: 8,
height: 8,
borderRadius: 2,
background: "rgba(255,255,255,0.12)",
}}
/>
))}
</div>
{/* Secondary Vertical List Panel */}
<div
style={{
width: 48,
borderRight: "1px solid var(--hairline)",
padding: 6,
display: "flex",
flexDirection: "column",
gap: 5,
background: "oklch(0.17 0.008 60 / 0.5)",
}}
>
{wireLine(36, 0.4)}
<div style={{ height: 1, background: "var(--hairline)" }} />
{wireLine(28, 0.25)}
{wireLine(32, 0.15)}
{wireLine(24, 0.25)}
{wireLine(28, 0.15)}
</div>
{/* Main Content Area */}
<div
style={{
flex: 1,
padding: 8,
display: "flex",
flexDirection: "column",
gap: 6,
}}
>
{wireLine(36, 0.45)}
<div
style={{
flex: 1,
border: "1px solid var(--hairline)",
borderRadius: 6,
padding: 8,
display: "flex",
flexDirection: "column",
gap: 5,
background: "rgba(255,255,255,0.02)",
}}
>
<div style={{ display: "flex", gap: 6 }}>
<div
style={{
width: 12,
height: 12,
borderRadius: 3,
background: "rgba(255,255,255,0.08)",
}}
/>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 4,
paddingTop: 2,
}}
>
{wireLine("40%", 0.3)}
{wireLine("20%", 0.15)}
</div>
</div>
{wireLine("100%", 0.15)}
</div>
</div>
</div>
);
} else if (styleId === "flux") {
// Glass aurora: purple background with a floating card
content = (
<div
style={{
width: "100%",
height: "100%",
position: "relative",
display: "grid",
placeItems: "center",
background: "#0c0a1a",
}}
>
{/* Aurora Glow */}
<div
style={{
position: "absolute",
width: 80,
height: 80,
borderRadius: "50%",
background:
"radial-gradient(circle, oklch(0.6 0.18 300) 0%, transparent 70%)",
filter: "blur(14px)",
opacity: 0.7,
top: "10%",
left: "30%",
}}
/>
{/* Frosted Card */}
<div
style={{
width: "80%",
height: "70%",
borderRadius: 8,
border: "1px solid rgba(255,255,255,0.14)",
background: "rgba(255,255,255,0.04)",
backdropFilter: "blur(12px)",
padding: 8,
display: "flex",
flexDirection: "column",
gap: 5,
boxShadow: "0 8px 32px rgba(0,0,0,0.3)",
}}
>
<div style={{ display: "flex", gap: 4 }}>
<div
style={{
width: 8,
height: 8,
borderRadius: "50%",
background: accentColor,
opacity: 0.8,
}}
/>
{wireLine(18, 0.5)}
</div>
{wireLine("80%", 0.3)}
{wireLine("40%", 0.15)}
</div>
</div>
);
} else if (styleId === "minimal") {
// Classic Minimal: parchment background, gridded rule lines
content = (
<div
style={{
width: "100%",
height: "100%",
background: "#f5f4ef",
display: "flex",
flexDirection: "column",
}}
>
<div
style={{
height: 18,
borderBottom: "1px solid #d4d0c8",
padding: "0 8px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
{wireLine(24, 0.6)}
{wireLine(12, 0.4)}
</div>
<div
style={{
flex: 1,
padding: 8,
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 8,
}}
>
<div
style={{
borderRight: "1px dashed #d4d0c8",
paddingRight: 4,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
{wireLine("90%", 0.5)}
{wireLine("50%", 0.25)}
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
{wireLine("100%", 0.5)}
{wireLine("70%", 0.25)}
</div>
</div>
</div>
);
} else if (styleId === "bento") {
// Dark bento grid
content = (
<div
style={{
width: "100%",
height: "100%",
padding: 8,
display: "grid",
gridTemplateColumns: "1.2fr 1fr",
gridTemplateRows: "1fr 1fr",
gap: 5,
}}
>
<div
style={{
gridRow: "span 2",
border: "1px solid var(--hairline)",
borderRadius: 6,
padding: 6,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
{wireLine("50%", 0.4)}
<div
style={{
flex: 1,
background: "rgba(255,255,255,0.03)",
borderRadius: 4,
display: "grid",
placeItems: "center",
}}
>
<div
style={{
width: 14,
height: 14,
borderRadius: "50%",
border: "1px solid var(--accent)",
opacity: 0.7,
}}
/>
</div>
</div>
<div
style={{
border: "1px solid var(--hairline)",
borderRadius: 6,
padding: 6,
display: "flex",
flexDirection: "column",
gap: 3,
}}
>
{wireLine("80%", 0.3)}
{wireLine("40%", 0.15)}
</div>
<div
style={{
border: "1px solid var(--hairline)",
borderRadius: 6,
padding: 6,
display: "flex",
flexDirection: "column",
gap: 3,
}}
>
{wireLine("100%", 0.3)}
</div>
</div>
);
} else if (styleId === "swiss") {
// Editorial swiss: White background, bold geometric layouts
content = (
<div
style={{
width: "100%",
height: "100%",
background: "#ffffff",
padding: 10,
display: "flex",
flexDirection: "column",
gap: 6,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "baseline",
}}
>
<span
style={{
fontSize: 13,
fontWeight: 900,
color: "#000",
fontFamily: "sans-serif",
letterSpacing: "-0.04em",
lineHeight: 1,
}}
>
VIBN.
</span>
{wireLine(16, 0.6)}
</div>
<div style={{ height: 1, background: "#000000", opacity: 0.15 }} />
<div style={{ display: "flex", gap: 10, flex: 1 }}>
<div
style={{
flex: 1.2,
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
<div
style={{
height: 8,
width: "100%",
background: "#000",
opacity: 0.85,
}}
/>
<div
style={{
height: 8,
width: "70%",
background: "#000",
opacity: 0.85,
}}
/>
</div>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 4,
paddingTop: 2,
}}
>
{wireLine("100%", 0.4)}
{wireLine("100%", 0.4)}
{wireLine("60%", 0.2)}
</div>
</div>
</div>
);
} else if (styleId === "brutalist") {
// Neo-brutalist yellow card, thick borders, heavy offsets
content = (
<div
style={{
width: "100%",
height: "100%",
background: "#f0eee9",
display: "grid",
placeItems: "center",
}}
>
<div
style={{
width: "75%",
height: "70%",
background: "#fcf05a",
border: "2px solid #000",
borderRadius: 4,
boxShadow: "3px 3px 0 #000",
padding: 6,
display: "flex",
flexDirection: "column",
gap: 5,
}}
>
<div style={{ height: 8, width: "100%", background: "#000" }} />
<div style={{ display: "flex", gap: 4, alignItems: "center" }}>
<div
style={{
width: 12,
height: 12,
borderRadius: "50%",
background: "#ff5bf5",
border: "1.5px solid #000",
}}
/>
{wireLine(24, 0.85)}
</div>
{wireLine("60%", 0.6)}
</div>
</div>
);
} else {
// I'm not sure / Undecided
content = (
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 6,
background:
"radial-gradient(circle at center, rgba(255,255,255,0.03) 0%, transparent 60%)",
}}
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="var(--accent)"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
style={{
opacity: 0.75,
filter: "drop-shadow(0 0 8px var(--accent-glow))",
}}
>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01" />
</svg>
<span
style={{
fontSize: 10,
color: "var(--fg-faint)",
textTransform: "uppercase",
letterSpacing: "0.12em",
fontFamily: "var(--font-mono)",
}}
>
Automatic
</span>
</div>
);
}
return (
<div
style={{
width: "100%",
height: "100%",
position: "relative",
background: isDark ? "oklch(0.155 0.008 60)" : "transparent",
}}
>
{content}
</div>
);
}
interface StyleOption {
id: string;
label: string;
desc: string;
illustration: React.ReactNode;
fullWidth?: boolean;
}
const SAAS_STYLES: StyleOption[] = [
{
id: "sidebar_light",
label: "Vertical Sidebar — Light",
desc: "Clean light sidebar + light dashboard layouts.",
illustration: <LayoutPreview styleId="sidebar_light" />,
},
{
id: "sidebar_dark",
label: "Vertical Sidebar — Dark",
desc: "Full dark sidebar + dark dashboard layouts.",
illustration: <LayoutPreview styleId="sidebar_dark" />,
},
{
id: "topbar_light",
label: "Top Horizontal — Light",
desc: "Sleek dark topbar + light dashboard layouts.",
illustration: <LayoutPreview styleId="topbar_light" />,
},
{
id: "topbar_dark",
label: "Top Horizontal — Dark",
desc: "Full dark topbar + dark dashboard layouts.",
illustration: <LayoutPreview styleId="topbar_dark" />,
},
{
id: "not_sure",
label: "I'm not sure",
desc: "Let Vibn help you decide based on your description",
fullWidth: true,
illustration: <LayoutPreview styleId="not_sure" />,
},
];
const MARKETPLACE_STYLES: StyleOption[] = [
{
id: "flux",
label: "Dark Glass / Flux",
desc: "Modern dark-glass panels with glowing fuchsia aurora backdrops.",
illustration: <LayoutPreview styleId="flux" />,
},
{
id: "minimal",
label: "Classic Minimal",
desc: "Warm parchment neutrals, high-contrast typography and clean grids.",
illustration: <LayoutPreview styleId="minimal" />,
},
];
const GENERAL_STYLES: StyleOption[] = [
{
id: "bento",
label: "Dark Bento",
desc: "Modern dark UI, bento-box card clusters.",
illustration: <LayoutPreview styleId="bento" />,
},
{
id: "swiss",
label: "Editorial Swiss",
desc: "Type-led, gridded, lots of white space — clean and academic.",
illustration: <LayoutPreview styleId="swiss" />,
},
{
id: "brutalist",
label: "Neo-Brutalist",
desc: "Bold offsets, thick hand-drawn borders, highly tactile and organic.",
illustration: <LayoutPreview styleId="brutalist" />,
},
];
function EntrepStyle({ productType, value, onChange }) {
// Dynamically tailor the styles array based on what they picked on Page 2
const isSaas = productType === "saas";
const isMarketplace = productType === "marketplace";
const styles = isSaas
? SAAS_STYLES
: isMarketplace
? MARKETPLACE_STYLES
: GENERAL_STYLES;
return (
<>
<WizardQ
title="Choose a starting design style"
sub={
isSaas
? "Select the navigation layout that fits your app's density."
: isMarketplace
? "Select the design aesthetic for your marketplace templates."
: "Select the design layout for your custom pages."
}
/>
<PresetGroup
options={styles.map((s) => ({
id: s.id,
label: s.label,
desc: s.desc,
icon: undefined,
illustration: s.illustration,
fullWidth: s.fullWidth,
}))}
value={value}
onChange={onChange}
columns={2}
minimal
/>
</>
);
}
const VIBES = [
{
id: "warm",
name: "Warm coral",
swatch: "linear-gradient(135deg, #E27855, #B33B2A)",
desc: "Confident, hand-built, warm.",
},
{
id: "ink",
name: "Ink & paper",
swatch: "linear-gradient(135deg, #1d1d1d, #4a4a4a)",
desc: "Editorial, serif, quiet.",
},
{
id: "sage",
name: "Sage matte",
swatch: "linear-gradient(135deg, #7BA890, #3F6B57)",
desc: "Calm, modern, slightly herbal.",
},
{
id: "neon",
name: "Neon arcade",
swatch: "linear-gradient(135deg, #5B6CFF, #FF3DDB)",
desc: "Loud, fun, late-night.",
},
{
id: "cream",
name: "Cream linen",
swatch: "linear-gradient(135deg, #F2E7D5, #C9A977)",
desc: "Cozy and beige.",
},
{
id: "later",
name: "Decide later",
swatch:
"repeating-linear-gradient(45deg, oklch(0.30 0.010 60), oklch(0.30 0.010 60) 6px, oklch(0.22 0.010 60) 6px, oklch(0.22 0.010 60) 12px)",
desc: "Vibn picks one that fits.",
},
];
function EntrepVibe({ value, onChange }) {
return (
<>
<WizardQ
title="Pick a starting vibe."
sub="Every color and font is a tweak away once the site is live."
/>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: 10,
}}
>
{VIBES.map((v) => {
const active = value === v.id;
return (
<button
key={v.id}
type="button"
onClick={() => onChange(v.id)}
style={{
padding: "10px 10px 10px",
borderRadius: 11,
border: `1px solid ${active ? "var(--accent)" : "var(--hairline)"}`,
background: active
? "oklch(0.20 0.04 35 / 0.4)"
: "oklch(0.18 0.009 60 / 0.6)",
boxShadow: active
? "0 0 0 3px oklch(0.74 0.175 35 / 0.1)"
: "none",
textAlign: "left",
color: "var(--fg)",
display: "flex",
flexDirection: "column",
gap: 8,
transition: "border-color .15s, background .15s",
}}
>
<span
style={{
height: 52,
borderRadius: 7,
background: v.swatch,
border: "1px solid oklch(1 0 0 / 0.08)",
boxShadow: "inset 0 1px 0 oklch(1 0 0 / 0.18)",
}}
/>
<span
style={{
fontSize: 13,
fontWeight: 500,
letterSpacing: "-0.005em",
}}
>
{v.name}
</span>
<span
style={{
fontSize: 11.5,
color: "var(--fg-mute)",
lineHeight: 1.4,
}}
>
{v.desc}
</span>
</button>
);
})}
</div>
</>
);
}
// ── Path wrapper ───────────────────────────────────────────────────────────
export function EntrepreneurPath({
data,
onUpdate,
onBack,
onClose,
onComplete,
onJumpToStep,
step,
}) {
const next = () => {
if (step < ENTREP_TOTAL - 1) onJumpToStep(step + 1);
else onComplete();
};
const back = () => {
if (step === 0) onBack();
else onJumpToStep(step - 1);
};
let body,
canNext,
onSkip = null;
if (step === 0) {
body = (
<EntrepType
value={data.productType || ""}
onChange={(v) => onUpdate({ productType: v })}
/>
);
canNext = !!data.productType;
} else if (step === 1) {
body = (
<EntrepStyle
productType={data.productType}
value={data.designStyle || ""}
onChange={(v) => onUpdate({ designStyle: v })}
/>
);
canNext = !!data.designStyle;
} else if (step === 2) {
body = (
<EntrepIdea
value={data.idea || ""}
onChange={(v) => onUpdate({ idea: v })}
/>
);
canNext = (data.idea || "").trim().length >= 8;
} else {
body = (
<EntrepVibe value={data.vibe} onChange={(v) => onUpdate({ vibe: v })} />
);
canNext = !!data.vibe;
onSkip = () => {
onUpdate({ vibe: "later" });
next();
};
}
// 5 total: fork(1) + 4 path steps
return (
<>
<WizardTop
onBack={back}
onClose={onClose}
lane={LANE_LABELS.entrepreneur}
stepText={ENTREP_STEP_NAMES[step]}
current={step + 2}
total={5}
/>
<WizardBody
width={step === 1 ? "xwide" : step === 2 || step === 3 ? "wide" : null}
>
{body}
<WizardFooter
onNext={next}
canNext={canNext}
nextLabel={step === ENTREP_TOTAL - 1 ? "Build →" : "Continue"}
hint={canNext ? "⌘↵" : null}
onSkip={onSkip}
skipLabel={step === 0 ? "I'm not sure" : "Pick for me"}
/>
</WizardBody>
</>
);
}