design: increase entrepreneur onboarding textarea to 200px and add reassuring subheader text, resolve unused label import error
This commit is contained in:
@@ -1,5 +1,20 @@
|
|||||||
import React, { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
import React, {
|
||||||
import { WizardTop, WizardBody, WizardQ, WizardFooter, Label, LANE_LABELS, ChipGroup, PresetGroup, Field } from "./onboarding-primitives";
|
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.
|
// Entrepreneur path — 4 steps. Each step is a focused question.
|
||||||
|
|
||||||
const ENTREP_TOTAL = 4;
|
const ENTREP_TOTAL = 4;
|
||||||
@@ -28,7 +43,10 @@ export function EntrepIdea({ value, onChange }) {
|
|||||||
else setTimeout(() => setDeleting(true), 1500);
|
else setTimeout(() => setDeleting(true), 1500);
|
||||||
} else {
|
} else {
|
||||||
if (phChars > 0) setPhChars(phChars - 1);
|
if (phChars > 0) setPhChars(phChars - 1);
|
||||||
else { setDeleting(false); setPhIdx((phIdx + 1) % IDEA_PROMPTS.length); }
|
else {
|
||||||
|
setDeleting(false);
|
||||||
|
setPhIdx((phIdx + 1) % IDEA_PROMPTS.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, speed);
|
}, speed);
|
||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
@@ -38,12 +56,12 @@ export function EntrepIdea({ value, onChange }) {
|
|||||||
<>
|
<>
|
||||||
<WizardQ
|
<WizardQ
|
||||||
title="What are you building?"
|
title="What are you building?"
|
||||||
sub="One paragraph is enough. Talk like you would to a friend."
|
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" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<textarea
|
<textarea
|
||||||
className="wiz-input"
|
className="wiz-input"
|
||||||
style={{ minHeight: 140, fontSize: 15 }}
|
style={{ minHeight: 200, fontSize: 15 }}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -52,7 +70,10 @@ export function EntrepIdea({ value, onChange }) {
|
|||||||
{value.length === 0 && (
|
{value.length === 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: "absolute", top: 12, left: 14, right: 14,
|
position: "absolute",
|
||||||
|
top: 12,
|
||||||
|
left: 14,
|
||||||
|
right: 14,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
color: "var(--fg-faint)",
|
color: "var(--fg-faint)",
|
||||||
font: "14.5px/1.5 var(--font-sans)",
|
font: "14.5px/1.5 var(--font-sans)",
|
||||||
@@ -62,8 +83,11 @@ export function EntrepIdea({ value, onChange }) {
|
|||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
width: 7, height: 14, verticalAlign: "-2px",
|
width: 7,
|
||||||
background: "var(--accent)", marginLeft: 1,
|
height: 14,
|
||||||
|
verticalAlign: "-2px",
|
||||||
|
background: "var(--accent)",
|
||||||
|
marginLeft: 1,
|
||||||
animation: "blink 1s steps(2) infinite",
|
animation: "blink 1s steps(2) infinite",
|
||||||
boxShadow: "0 0 10px var(--accent-glow)",
|
boxShadow: "0 0 10px var(--accent-glow)",
|
||||||
}}
|
}}
|
||||||
@@ -73,7 +97,12 @@ export function EntrepIdea({ value, onChange }) {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="mono"
|
className="mono"
|
||||||
style={{ fontSize: 11, color: "var(--fg-faint)", letterSpacing: "0.06em", marginTop: -16 }}
|
style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: "var(--fg-faint)",
|
||||||
|
letterSpacing: "0.06em",
|
||||||
|
marginTop: -16,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{value.length} chars · be specific where it matters
|
{value.length} chars · be specific where it matters
|
||||||
</div>
|
</div>
|
||||||
@@ -116,18 +145,42 @@ function EntrepAudience({ value, onChange }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GOALS = [
|
const GOALS = [
|
||||||
{ id: "first_customer", icon: "🎯", label: "First real customer",
|
{
|
||||||
desc: "Someone I don't know pays me. Even once." },
|
id: "first_customer",
|
||||||
{ id: "ten_users", icon: "👥", label: "Ten weekly users",
|
icon: "🎯",
|
||||||
desc: "A signal the thing actually does something useful." },
|
label: "First real customer",
|
||||||
{ id: "mrr_1k", icon: "📈", label: "$1k MRR",
|
desc: "Someone I don't know pays me. Even once.",
|
||||||
desc: "Enough to take it seriously." },
|
},
|
||||||
{ id: "side_quit", icon: "🚪", label: "Replace my day job",
|
{
|
||||||
desc: "The long road. Make this the main thing." },
|
id: "ten_users",
|
||||||
{ id: "audience", icon: "📣", label: "Build a tiny audience",
|
icon: "👥",
|
||||||
desc: "200 emails, a community, something I can talk to." },
|
label: "Ten weekly users",
|
||||||
{ id: "ship_it", icon: "🚀", label: "Just ship it",
|
desc: "A signal the thing actually does something useful.",
|
||||||
desc: "I want the thing to exist." },
|
},
|
||||||
|
{
|
||||||
|
id: "mrr_1k",
|
||||||
|
icon: "📈",
|
||||||
|
label: "$1k MRR",
|
||||||
|
desc: "Enough to take it seriously.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "side_quit",
|
||||||
|
icon: "🚪",
|
||||||
|
label: "Replace my day job",
|
||||||
|
desc: "The long road. Make this the main thing.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "audience",
|
||||||
|
icon: "📣",
|
||||||
|
label: "Build a tiny audience",
|
||||||
|
desc: "200 emails, a community, something I can talk to.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ship_it",
|
||||||
|
icon: "🚀",
|
||||||
|
label: "Just ship it",
|
||||||
|
desc: "I want the thing to exist.",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function EntrepGoal({ value, onChange }) {
|
function EntrepGoal({ value, onChange }) {
|
||||||
@@ -139,7 +192,9 @@ function EntrepGoal({ value, onChange }) {
|
|||||||
/>
|
/>
|
||||||
<PresetGroup
|
<PresetGroup
|
||||||
options={GOALS.map((g) => ({
|
options={GOALS.map((g) => ({
|
||||||
id: g.id, label: g.label, desc: g.desc,
|
id: g.id,
|
||||||
|
label: g.label,
|
||||||
|
desc: g.desc,
|
||||||
icon: <span style={{ fontSize: 14 }}>{g.icon}</span>,
|
icon: <span style={{ fontSize: 14 }}>{g.icon}</span>,
|
||||||
}))}
|
}))}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -151,18 +206,43 @@ function EntrepGoal({ value, onChange }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const VIBES = [
|
const VIBES = [
|
||||||
{ id: "warm", name: "Warm coral", swatch: "linear-gradient(135deg, #E27855, #B33B2A)",
|
{
|
||||||
desc: "Confident, hand-built, warm." },
|
id: "warm",
|
||||||
{ id: "ink", name: "Ink & paper", swatch: "linear-gradient(135deg, #1d1d1d, #4a4a4a)",
|
name: "Warm coral",
|
||||||
desc: "Editorial, serif, quiet." },
|
swatch: "linear-gradient(135deg, #E27855, #B33B2A)",
|
||||||
{ id: "sage", name: "Sage matte", swatch: "linear-gradient(135deg, #7BA890, #3F6B57)",
|
desc: "Confident, hand-built, warm.",
|
||||||
desc: "Calm, modern, slightly herbal." },
|
},
|
||||||
{ id: "neon", name: "Neon arcade", swatch: "linear-gradient(135deg, #5B6CFF, #FF3DDB)",
|
{
|
||||||
desc: "Loud, fun, late-night." },
|
id: "ink",
|
||||||
{ id: "cream", name: "Cream linen", swatch: "linear-gradient(135deg, #F2E7D5, #C9A977)",
|
name: "Ink & paper",
|
||||||
desc: "Cozy and beige." },
|
swatch: "linear-gradient(135deg, #1d1d1d, #4a4a4a)",
|
||||||
{ 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: "Editorial, serif, quiet.",
|
||||||
desc: "Vibn picks one that fits." },
|
},
|
||||||
|
{
|
||||||
|
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 }) {
|
function EntrepVibe({ value, onChange }) {
|
||||||
@@ -190,26 +270,45 @@ function EntrepVibe({ value, onChange }) {
|
|||||||
padding: "10px 10px 10px",
|
padding: "10px 10px 10px",
|
||||||
borderRadius: 11,
|
borderRadius: 11,
|
||||||
border: `1px solid ${active ? "var(--accent)" : "var(--hairline)"}`,
|
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)",
|
background: active
|
||||||
boxShadow: active ? "0 0 0 3px oklch(0.74 0.175 35 / 0.1)" : "none",
|
? "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",
|
textAlign: "left",
|
||||||
color: "var(--fg)",
|
color: "var(--fg)",
|
||||||
display: "flex", flexDirection: "column", gap: 8,
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 8,
|
||||||
transition: "border-color .15s, background .15s",
|
transition: "border-color .15s, background .15s",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
height: 52, borderRadius: 7,
|
height: 52,
|
||||||
|
borderRadius: 7,
|
||||||
background: v.swatch,
|
background: v.swatch,
|
||||||
border: "1px solid oklch(1 0 0 / 0.08)",
|
border: "1px solid oklch(1 0 0 / 0.08)",
|
||||||
boxShadow: "inset 0 1px 0 oklch(1 0 0 / 0.18)",
|
boxShadow: "inset 0 1px 0 oklch(1 0 0 / 0.18)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontSize: 13, fontWeight: 500, letterSpacing: "-0.005em" }}>
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 500,
|
||||||
|
letterSpacing: "-0.005em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{v.name}
|
{v.name}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontSize: 11.5, color: "var(--fg-mute)", lineHeight: 1.4 }}>
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 11.5,
|
||||||
|
color: "var(--fg-mute)",
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{v.desc}
|
{v.desc}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -221,7 +320,15 @@ function EntrepVibe({ value, onChange }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Path wrapper ───────────────────────────────────────────────────────────
|
// ── Path wrapper ───────────────────────────────────────────────────────────
|
||||||
export function EntrepreneurPath({ data, onUpdate, onBack, onClose, onComplete, onJumpToStep, step }) {
|
export function EntrepreneurPath({
|
||||||
|
data,
|
||||||
|
onUpdate,
|
||||||
|
onBack,
|
||||||
|
onClose,
|
||||||
|
onComplete,
|
||||||
|
onJumpToStep,
|
||||||
|
step,
|
||||||
|
}) {
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (step < ENTREP_TOTAL - 1) onJumpToStep(step + 1);
|
if (step < ENTREP_TOTAL - 1) onJumpToStep(step + 1);
|
||||||
else onComplete();
|
else onComplete();
|
||||||
@@ -231,20 +338,39 @@ export function EntrepreneurPath({ data, onUpdate, onBack, onClose, onComplete,
|
|||||||
else onJumpToStep(step - 1);
|
else onJumpToStep(step - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let body, canNext, onSkip = null;
|
let body,
|
||||||
|
canNext,
|
||||||
|
onSkip = null;
|
||||||
if (step === 0) {
|
if (step === 0) {
|
||||||
body = <EntrepIdea value={data.idea || ""} onChange={(v) => onUpdate({ idea: v })} />;
|
body = (
|
||||||
|
<EntrepIdea
|
||||||
|
value={data.idea || ""}
|
||||||
|
onChange={(v) => onUpdate({ idea: v })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
canNext = (data.idea || "").trim().length >= 8;
|
canNext = (data.idea || "").trim().length >= 8;
|
||||||
} else if (step === 1) {
|
} else if (step === 1) {
|
||||||
body = <EntrepAudience value={data.audience || ""} onChange={(v) => onUpdate({ audience: v })} />;
|
body = (
|
||||||
|
<EntrepAudience
|
||||||
|
value={data.audience || ""}
|
||||||
|
onChange={(v) => onUpdate({ audience: v })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
canNext = (data.audience || "").trim().length >= 3;
|
canNext = (data.audience || "").trim().length >= 3;
|
||||||
} else if (step === 2) {
|
} else if (step === 2) {
|
||||||
body = <EntrepGoal value={data.goal} onChange={(v) => onUpdate({ goal: v })} />;
|
body = (
|
||||||
|
<EntrepGoal value={data.goal} onChange={(v) => onUpdate({ goal: v })} />
|
||||||
|
);
|
||||||
canNext = !!data.goal;
|
canNext = !!data.goal;
|
||||||
} else {
|
} else {
|
||||||
body = <EntrepVibe value={data.vibe} onChange={(v) => onUpdate({ vibe: v })} />;
|
body = (
|
||||||
|
<EntrepVibe value={data.vibe} onChange={(v) => onUpdate({ vibe: v })} />
|
||||||
|
);
|
||||||
canNext = !!data.vibe;
|
canNext = !!data.vibe;
|
||||||
onSkip = () => { onUpdate({ vibe: "later" }); next(); };
|
onSkip = () => {
|
||||||
|
onUpdate({ vibe: "later" });
|
||||||
|
next();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 total: fork(1) + 4 path steps
|
// 5 total: fork(1) + 4 path steps
|
||||||
@@ -272,5 +398,3 @@ export function EntrepreneurPath({ data, onUpdate, onBack, onClose, onComplete,
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user