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

207 lines
5.7 KiB
TypeScript

import React, {
useState,
useEffect,
useRef,
useMemo,
useCallback,
} from "react";
import {
WizardTop,
WizardBody,
WizardQ,
WizardFooter,
} from "./onboarding-primitives";
// Step 1: the only branching question — "which describes you?"
// Quiet radio-style cards. No quotes, no marketing, no glow theatrics.
const FORKS = [
{
id: "entrepreneur",
label: "I am building a tool to market to many users",
hint: "Launch a SaaS, platform, or app with a public audience.",
icon: (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<circle cx="9" cy="9" r="3" />
<path d="M9 2.5v2M9 13.5v2M2.5 9h2M13.5 9h2" />
</svg>
),
},
{
id: "owner",
label: "I'm building an internal tool for my work or business",
hint: "Automate your operations or replace the software you rent.",
icon: (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M3 6h12l-1 9H4L3 6Z" />
<path d="M6 6V4.5a3 3 0 0 1 6 0V6" />
</svg>
),
},
{
id: "undecided",
label: "I'm not sure yet.",
hint: "Explore possibilities and see what AI can build.",
icon: (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<circle cx="9" cy="9" r="6" />
<path d="M9 11v1M9 6a2 2 0 0 1 2 2c0 2-2 2-2 2" />
</svg>
),
},
];
export function ForkScreen({ name, value, onChange, onClose, onNext }) {
return (
<>
<WizardTop
onBack={null}
onClose={onClose}
stepText="Pick your lane"
current={1}
total={4}
/>
<WizardBody>
<WizardQ
title={
name
? `Welcome, ${name}. Which sounds like you?`
: "Which one sounds like you?"
}
sub="Vibn asks different questions on the next screens depending on the answer. You can change this later."
/>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{FORKS.map((f) => {
const active = value === f.id;
return (
<button
key={f.id}
type="button"
onClick={() => onChange(f.id)}
onDoubleClick={() => {
onChange(f.id);
onNext();
}}
className={active ? "fork-card active" : "fork-card"}
>
<span
style={{
width: 36,
height: 36,
flexShrink: 0,
borderRadius: 9,
background: active
? "oklch(0.74 0.175 35 / 0.18)"
: "oklch(0.22 0.011 60)",
border: "1px solid var(--hairline)",
color: active ? "var(--accent)" : "var(--fg-mute)",
display: "grid",
placeItems: "center",
}}
>
{f.icon}
</span>
<span
style={{
display: "flex",
flexDirection: "column",
gap: 2,
flex: 1,
}}
>
<span
style={{
fontSize: 15,
fontWeight: 500,
letterSpacing: "-0.008em",
}}
>
{f.label}
</span>
<span
style={{
fontSize: 13,
color: "var(--fg-mute)",
lineHeight: 1.4,
}}
>
{f.hint}
</span>
</span>
<span
style={{
width: 18,
height: 18,
flexShrink: 0,
borderRadius: "50%",
border: `1.5px solid ${active ? "var(--accent)" : "var(--hairline-2)"}`,
background: active ? "var(--accent)" : "transparent",
display: "grid",
placeItems: "center",
color: "var(--accent-fg)",
transition: "border-color .15s, background .15s",
}}
>
{active && (
<svg
width="10"
height="10"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="m3 8.5 3.2 3.2L13 5" />
</svg>
)}
</span>
</button>
);
})}
</div>
<WizardFooter
canNext={!!value}
onNext={onNext}
nextLabel="Continue"
hint={value ? "Press ⌘↵" : null}
/>
</WizardBody>
</>
);
}