feat: flatten routes and merge marketing and onboarding directories

This commit is contained in:
2026-06-06 18:52:03 -07:00
parent 47417d13a0
commit 0480b306f1
139 changed files with 36409 additions and 229 deletions

View File

@@ -1,70 +0,0 @@
# 📢 Marketing Directory
This directory contains all marketing-specific code for the VIBN landing pages and promotional content.
## 📁 Structure
```
marketing/
├── components/ # Marketing-specific React components
│ ├── hero.tsx # Hero section with main CTA
│ ├── features.tsx # Features grid with cards
│ ├── cta.tsx # Call-to-action sections
│ └── index.ts # Component exports
├── content/ # Marketing copy and content
│ └── homepage.ts # Homepage content/copy
└── styles/ # Marketing-specific styles (future)
```
## 🎯 Purpose
This directory keeps marketing code separate from application code, making it easier to:
- Update messaging and copy
- Redesign marketing pages without affecting the app
- Collaborate with designers/copywriters
- Maintain consistent branding
## 🔗 Usage
Marketing components are used in the `app/(marketing)/` routes:
```tsx
import { Hero, Features, CTA } from "@/marketing/components";
export default function Page() {
return (
<>
<Hero />
<Features />
<CTA />
</>
);
}
```
## 📝 Content Management
All marketing copy is centralized in `content/homepage.ts` for easy updates:
```typescript
import { homepage } from "@/marketing/content/homepage";
// Use in components
<h1>{homepage.hero.title}</h1>
```
## 🚀 Adding New Sections
1. Create component in `components/`
2. Add content to `content/homepage.ts`
3. Export from `components/index.ts`
4. Use in `app/(marketing)/page.tsx`
## 📖 Design Guidelines
Follow the guidelines in `/PROJECT_INSTRUCTIONS.md` for:
- Voice & tone
- Design principles
- SEO requirements
- Accessibility standards

View File

@@ -1,145 +0,0 @@
import { Nav, Footer } from "@/marketing/components/new-site";
import "../../styles/new-site.css";
export const metadata = {
title: "Vibn — Our Mission",
};
export default function MissionPage() {
return (
<div className="new-site-wrapper" style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
<Nav />
<main className="mission-page" style={{ flex: 1 }}>
<style>{`
.mission-hero {
padding-block: clamp(80px, 15vh, 160px) clamp(40px, 8vh, 80px);
text-align: center;
}
.mission-eyebrow {
display: inline-flex; align-items: center; gap: 8px;
font-family: var(--font-mono);
font-size: 13px;
color: var(--accent);
letter-spacing: 0.14em;
text-transform: uppercase;
margin-bottom: 24px;
}
.mission-eyebrow::before {
content: ""; width: 6px; height: 6px; border-radius: 50%;
background: var(--accent); box-shadow: 0 0 12px var(--accent-glow);
}
.mission-quote {
font-size: clamp(32px, 5vw, 56px);
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1.1;
text-wrap: balance;
max-width: 960px;
margin: 0 auto;
color: var(--fg);
}
.mission-quote .highlight {
color: var(--accent);
text-shadow: 0 0 30px var(--accent-glow);
}
.mission-content {
max-width: 680px;
margin: 0 auto;
padding-block: 40px 120px;
font-size: 18px;
line-height: 1.65;
color: var(--fg-dim);
}
.mission-content h2 {
font-size: 28px;
color: var(--fg);
margin-top: 64px;
margin-bottom: 24px;
font-weight: 500;
letter-spacing: -0.015em;
}
.mission-content p {
margin-bottom: 24px;
}
.mission-content p:last-child {
margin-bottom: 0;
}
.mission-content strong {
color: var(--fg);
font-weight: 500;
}
.mission-signoff {
margin-top: 64px;
padding-top: 32px;
border-top: 1px solid var(--hairline);
font-family: var(--font-mono);
font-size: 14px;
color: var(--fg-mute);
}
.mission-signoff .author {
color: var(--fg);
font-weight: 500;
font-size: 16px;
font-family: var(--font-sans);
margin-bottom: 4px;
}
@media (max-width: 760px) {
.mission-quote { font-size: clamp(28px, 8vw, 36px); }
.mission-content { font-size: 16px; padding-block: 20px 80px; }
.mission-content h2 { font-size: 24px; margin-top: 48px; margin-bottom: 20px; }
}
`}</style>
<div className="wrap">
<section className="mission-hero">
<span className="mission-eyebrow">Our Mission</span>
<h1 className="mission-quote">
"Look at your subscription costs, ask if they're doing the job, <span className="highlight">if not, get building.</span>"
</h1>
</section>
<article className="mission-content">
<h2>A letter from the founder</h2>
<p>I've spent the last ten years trying to help small businesses grow.</p>
<p>Startups. Clubs. Plumbers. Bookkeepers. Family restaurants. Single-location retail. The businesses that make neighborhoods what they are. I've watched they struggle and stagnate.</p>
<p>Here's what I learned: the biggest thing holding small businesses back isn't the economy, or competition, or marketing. <strong>It's the owner.</strong> Their hesitations. Their (very reasonable) skepticism of change. Most small business owners are run off their feet — they don't have the time or appetite to adopt new tools, new systems, new anything. And when someone tries to sell them on change, they bristle. They've been burned too many times.</p>
<p>But I've also learned something else. <strong>When an owner makes an idea theirs, they adopt it instantly.</strong></p>
<p>That's what AI changes. For the first time, software can have a real conversation with a small business owner. It can listen to their problem in their words, propose a solution that feels obvious to them, and build it on the spot. The owner isn't being sold to. They're being heard. And when an idea is theirs, the resistance disappears.</p>
<p>That's the unlock. That's why this moment matters.</p>
<h2>Why small business never got the software it deserved</h2>
<p>People love to blame SaaS for squeezing small business. I don't think that's quite right.</p>
<p>The real story is structural. For the last two decades, the math of venture capital pushed every promising software company toward enterprise. It wasn't a conspiracy it was incentives. Small business has high churn. Enterprise has multi-year contracts. LPs expect returns on a fund timeline that small business revenue can't deliver. So founders with great ideas for small business software kept getting nudged upmarket — by their investors, their boards, their boards' investors until eventually the SMB version of every product was an afterthought, and the real product was built for a 500-person finance team.</p>
<p>Small business got the leftovers. Monthly subscriptions for software that almost-but-not-quite solves the problem.</p>
<p>Nobody set out to underserve small business. The system just didn't reward serving them well. That gap — the gap between what small businesses actually needed and what got built — is the gap Vibn fills.</p>
<h2>Why now</h2>
<p>There's a wave coming. AI is going to displace a lot of people software engineers especially, but knowledge workers across the board. The doom narrative says this is the end of opportunity. I think it's the opposite.</p>
<p><strong>Small business is where the next generation of careers gets built.</strong></p>
<p>Not as a consolation prize. As an upgrade. Owning the bakery instead of writing code for a company that sells software to bakeries. Building custom tools for the plumber down the street instead of building dashboards for a Series C SaaS. Working at a thriving local business that owns its own software, instead of grinding through layoffs at companies that don't know what they want to be.</p>
<p>For laid-off engineers, this is a place to land and not a small one. The same skills that built SaaS for the enterprise can build extraordinary things for small businesses now that the tools exist. For young entrepreneurs, this is the cheapest, fastest, most legitimate path to running a real business that has ever existed. For everyone who wants to help small businesses thrive, this is the moment to do it.</p>
<h2>What Vibn is, really</h2>
<p>Vibn is a vibe coding platform. That's the surface.</p>
<p>Underneath, it's a wager: that if you make it possible for a small business to have custom software built by the owner, or by a local freelancer who hands it over without subscriptions, without endless tool sprawl, without code, without engineering teams you start to fix something that's been broken for twenty years.</p>
<p>Owners build tools and own them outright. Freelancers build custom solutions for their community and get paid like the craftsmen they are. Subscriptions get cancelled. Margins go back to the businesses earning them. Software stops being something small businesses rent forever and starts being something they own.</p>
<p>That's the golden age. Not abstract. Concrete. One business at a time. One tool at a time.</p>
<h2>What you can do right now</h2>
<p><strong>If you own a small business:</strong> pull up your bank statement and look at the subscription line. Look at every tool you pay for every month. Ask one question is this actually doing the job for my business today? If the answer is no, even partially, you should be building.</p>
<p><strong>If you're an engineer who got laid off, or never got the job:</strong> there is real work here. Real businesses that need real tools. You don't need a startup, a co-founder, or a Series A. You need one local small business and a willingness to build them exactly what they need.</p>
<p><strong>If you're an entrepreneur looking for a wave to ride:</strong> this is it. The tools are here. The customers are here. The moment is here.</p>
<p>We built Vibn for all of you.</p>
<p>Let's build the golden age.</p>
<div className="mission-signoff">
<div className="author">Mark Henderson</div>
Founder, Vibn
</div>
</article>
</div>
</main>
<Footer />
</div>
);
}

View File

@@ -1,5 +0,0 @@
"use client";
import OnboardingPage from "@/_onboarding/page";
export default OnboardingPage;

View File

@@ -0,0 +1,261 @@
import { Nav, Footer } from "@/marketing/new-site";
import "../styles/new-site.css";
export const metadata = {
title: "Vibn — Our Mission",
};
export default function MissionPage() {
return (
<div
className="new-site-wrapper"
style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}
>
<Nav />
<main className="mission-page" style={{ flex: 1 }}>
<style>{`
.mission-hero {
padding-block: clamp(80px, 15vh, 160px) clamp(40px, 8vh, 80px);
text-align: center;
}
.mission-eyebrow {
display: inline-flex; align-items: center; gap: 8px;
font-family: var(--font-mono);
font-size: 13px;
color: var(--accent);
letter-spacing: 0.14em;
text-transform: uppercase;
margin-bottom: 24px;
}
.mission-eyebrow::before {
content: ""; width: 6px; height: 6px; border-radius: 50%;
background: var(--accent); box-shadow: 0 0 12px var(--accent-glow);
}
.mission-quote {
font-size: clamp(32px, 5vw, 56px);
font-weight: 500;
letter-spacing: -0.025em;
line-height: 1.1;
text-wrap: balance;
max-width: 960px;
margin: 0 auto;
color: var(--fg);
}
.mission-quote .highlight {
color: var(--accent);
text-shadow: 0 0 30px var(--accent-glow);
}
.mission-content {
max-width: 680px;
margin: 0 auto;
padding-block: 40px 120px;
font-size: 18px;
line-height: 1.65;
color: var(--fg-dim);
}
.mission-content h2 {
font-size: 28px;
color: var(--fg);
margin-top: 64px;
margin-bottom: 24px;
font-weight: 500;
letter-spacing: -0.015em;
}
.mission-content p {
margin-bottom: 24px;
}
.mission-content p:last-child {
margin-bottom: 0;
}
.mission-content strong {
color: var(--fg);
font-weight: 500;
}
.mission-signoff {
margin-top: 64px;
padding-top: 32px;
border-top: 1px solid var(--hairline);
font-family: var(--font-mono);
font-size: 14px;
color: var(--fg-mute);
}
.mission-signoff .author {
color: var(--fg);
font-weight: 500;
font-size: 16px;
font-family: var(--font-sans);
margin-bottom: 4px;
}
@media (max-width: 760px) {
.mission-quote { font-size: clamp(28px, 8vw, 36px); }
.mission-content { font-size: 16px; padding-block: 20px 80px; }
.mission-content h2 { font-size: 24px; margin-top: 48px; margin-bottom: 20px; }
}
`}</style>
<div className="wrap">
<section className="mission-hero">
<span className="mission-eyebrow">Our Mission</span>
<h1 className="mission-quote">
"Look at your subscription costs, ask if they're doing the job,{" "}
<span className="highlight">if not, get building.</span>"
</h1>
</section>
<article className="mission-content">
<h2>A letter from the founder</h2>
<p>
I've spent the last ten years trying to help small businesses
grow.
</p>
<p>
Startups. Clubs. Plumbers. Bookkeepers. Family restaurants.
Single-location retail. The businesses that make neighborhoods
what they are. I've watched they struggle and stagnate.
</p>
<p>
Here's what I learned: the biggest thing holding small businesses
back isn't the economy, or competition, or marketing.{" "}
<strong>It's the owner.</strong> Their hesitations. Their (very
reasonable) skepticism of change. Most small business owners are
run off their feet — they don't have the time or appetite to adopt
new tools, new systems, new anything. And when someone tries to
sell them on change, they bristle. They've been burned too many
times.
</p>
<p>
But I've also learned something else.{" "}
<strong>
When an owner makes an idea theirs, they adopt it instantly.
</strong>
</p>
<p>
That's what AI changes. For the first time, software can have a
real conversation with a small business owner. It can listen to
their problem in their words, propose a solution that feels
obvious to them, and build it on the spot. The owner isn't being
sold to. They're being heard. And when an idea is theirs, the
resistance disappears.
</p>
<p>That's the unlock. That's why this moment matters.</p>
<h2>Why small business never got the software it deserved</h2>
<p>
People love to blame SaaS for squeezing small business. I don't
think that's quite right.
</p>
<p>
The real story is structural. For the last two decades, the math
of venture capital pushed every promising software company toward
enterprise. It wasn't a conspiracy it was incentives. Small
business has high churn. Enterprise has multi-year contracts. LPs
expect returns on a fund timeline that small business revenue
can't deliver. So founders with great ideas for small business
software kept getting nudged upmarket — by their investors, their
boards, their boards' investors until eventually the SMB version
of every product was an afterthought, and the real product was
built for a 500-person finance team.
</p>
<p>
Small business got the leftovers. Monthly subscriptions for
software that almost-but-not-quite solves the problem.
</p>
<p>
Nobody set out to underserve small business. The system just
didn't reward serving them well. That gap — the gap between what
small businesses actually needed and what got built — is the gap
Vibn fills.
</p>
<h2>Why now</h2>
<p>
There's a wave coming. AI is going to displace a lot of people
software engineers especially, but knowledge workers across the
board. The doom narrative says this is the end of opportunity. I
think it's the opposite.
</p>
<p>
<strong>
Small business is where the next generation of careers gets
built.
</strong>
</p>
<p>
Not as a consolation prize. As an upgrade. Owning the bakery
instead of writing code for a company that sells software to
bakeries. Building custom tools for the plumber down the street
instead of building dashboards for a Series C SaaS. Working at a
thriving local business that owns its own software, instead of
grinding through layoffs at companies that don't know what they
want to be.
</p>
<p>
For laid-off engineers, this is a place to land and not a small
one. The same skills that built SaaS for the enterprise can build
extraordinary things for small businesses now that the tools
exist. For young entrepreneurs, this is the cheapest, fastest,
most legitimate path to running a real business that has ever
existed. For everyone who wants to help small businesses thrive,
this is the moment to do it.
</p>
<h2>What Vibn is, really</h2>
<p>Vibn is a vibe coding platform. That's the surface.</p>
<p>
Underneath, it's a wager: that if you make it possible for a small
business to have custom software built by the owner, or by a
local freelancer who hands it over without subscriptions,
without endless tool sprawl, without code, without engineering
teams you start to fix something that's been broken for twenty
years.
</p>
<p>
Owners build tools and own them outright. Freelancers build custom
solutions for their community and get paid like the craftsmen they
are. Subscriptions get cancelled. Margins go back to the
businesses earning them. Software stops being something small
businesses rent forever and starts being something they own.
</p>
<p>
That's the golden age. Not abstract. Concrete. One business at a
time. One tool at a time.
</p>
<h2>What you can do right now</h2>
<p>
<strong>If you own a small business:</strong> pull up your bank
statement and look at the subscription line. Look at every tool
you pay for every month. Ask one question is this actually doing
the job for my business today? If the answer is no, even
partially, you should be building.
</p>
<p>
<strong>
If you're an engineer who got laid off, or never got the job:
</strong>{" "}
there is real work here. Real businesses that need real tools. You
don't need a startup, a co-founder, or a Series A. You need one
local small business and a willingness to build them exactly what
they need.
</p>
<p>
<strong>
If you're an entrepreneur looking for a wave to ride:
</strong>{" "}
this is it. The tools are here. The customers are here. The moment
is here.
</p>
<p>We built Vibn for all of you.</p>
<p>Let's build the golden age.</p>
<div className="mission-signoff">
<div className="author">Mark Henderson</div>
Founder, Vibn
</div>
</article>
</div>
</main>
<Footer />
</div>
);
}

View File

@@ -1,5 +1,20 @@
import React, { useState, useEffect, useRef, useMemo, useCallback } from "react";
import { WizardTop, WizardBody, WizardQ, WizardFooter, Label, LANE_LABELS, PresetGroup, Field } from "./onboarding-primitives";
import React, {
useState,
useEffect,
useRef,
useMemo,
useCallback,
} from "react";
import {
WizardTop,
WizardBody,
WizardQ,
WizardFooter,
Label,
LANE_LABELS,
PresetGroup,
Field,
} from "./onboarding-primitives";
// Consultant path — 4 steps for freelancers building for a client.
const CONS_TOTAL = 4;
@@ -29,7 +44,11 @@ export function ConsClient({ clientName, industry, contact, onChange }) {
onChange={(e) => onChange({ industry: e.target.value })}
/>
</Field>
<Field label="Your point of contact" optional hint="So Vibn can address them in the handoff.">
<Field
label="Your point of contact"
optional
hint="So Vibn can address them in the handoff."
>
<input
className="wiz-input"
placeholder="Marisol Rivera, Owner"
@@ -42,14 +61,26 @@ export function ConsClient({ clientName, industry, contact, onChange }) {
}
const BRIEF_TEMPLATES = [
{ id: "quote_tool", label: "Quote tool",
body: "Customers request a quote with a few photos and a project description. The team reviews, sends a polished PDF, customer signs and pays a deposit online." },
{ id: "booking", label: "Booking system",
body: "Customers see real availability, book a service window, and get reminders. The team has a daily view of jobs with addresses and contact info." },
{ id: "portal", label: "Customer portal",
body: "Logged-in customers see past jobs, invoices, documents, and can message the business. The business sees a single page per customer." },
{ id: "internal", label: "Internal ops",
body: "Replace the spreadsheets the team is currently using. CRUD on jobs/customers, simple reports, role-based access, export to accounting." },
{
id: "quote_tool",
label: "Quote tool",
body: "Customers request a quote with a few photos and a project description. The team reviews, sends a polished PDF, customer signs and pays a deposit online.",
},
{
id: "booking",
label: "Booking system",
body: "Customers see real availability, book a service window, and get reminders. The team has a daily view of jobs with addresses and contact info.",
},
{
id: "portal",
label: "Customer portal",
body: "Logged-in customers see past jobs, invoices, documents, and can message the business. The business sees a single page per customer.",
},
{
id: "internal",
label: "Internal ops",
body: "Replace the spreadsheets the team is currently using. CRUD on jobs/customers, simple reports, role-based access, export to accounting.",
},
];
function ConsBrief({ brief, onChange }) {
@@ -88,10 +119,43 @@ function ConsBrief({ brief, onChange }) {
}
const SCOPE_GROUPS = [
{ label: "Foundations", items: ["Auth & accounts", "Roles & permissions", "Database & admin", "Hosting & domain"] },
{ label: "Customer-facing", items: ["Marketing site", "Booking / scheduling", "Quote requests", "Customer portal", "Self-serve forms"] },
{ label: "Money", items: ["Stripe / payments", "Invoices & receipts", "Subscriptions", "Refunds & disputes"] },
{ label: "Ops", items: ["Dashboards & reports", "CSV import / export", "Email + SMS notifs", "Audit log"] },
{
label: "Foundations",
items: [
"Auth & accounts",
"Roles & permissions",
"Database & admin",
"Hosting & domain",
],
},
{
label: "Customer-facing",
items: [
"Marketing site",
"Booking / scheduling",
"Quote requests",
"Customer portal",
"Self-serve forms",
],
},
{
label: "Money",
items: [
"Stripe / payments",
"Invoices & receipts",
"Subscriptions",
"Refunds & disputes",
],
},
{
label: "Ops",
items: [
"Dashboards & reports",
"CSV import / export",
"Email + SMS notifs",
"Audit log",
],
},
];
function ConsScope({ scope, onChange }) {
@@ -125,8 +189,11 @@ function ConsScope({ scope, onChange }) {
<div
className="mono"
style={{
fontSize: 10.5, letterSpacing: "0.12em", textTransform: "uppercase",
color: "var(--fg-mute)", marginBottom: 10,
fontSize: 10.5,
letterSpacing: "0.12em",
textTransform: "uppercase",
color: "var(--fg-mute)",
marginBottom: 10,
}}
>
{g.label}
@@ -140,7 +207,9 @@ function ConsScope({ scope, onChange }) {
type="button"
onClick={() => toggle(it)}
style={{
display: "flex", alignItems: "center", gap: 10,
display: "flex",
alignItems: "center",
gap: 10,
textAlign: "left",
padding: "6px 4px",
color: active ? "var(--fg)" : "var(--fg-dim)",
@@ -150,19 +219,30 @@ function ConsScope({ scope, onChange }) {
>
<span
style={{
width: 16, height: 16,
width: 16,
height: 16,
flexShrink: 0,
borderRadius: 4,
border: `1px solid ${active ? "var(--accent)" : "var(--hairline-2)"}`,
background: active ? "var(--accent)" : "transparent",
color: "var(--accent-fg)",
display: "grid", placeItems: "center",
display: "grid",
placeItems: "center",
}}
>
{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
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>
@@ -185,7 +265,11 @@ function ConsHandoff({ data, onChange }) {
title="And finally — delivery."
sub="Where it lives, how you bill. Change later from settings."
/>
<Field label="Brand colors" optional hint="Hex, or just describe it. Paste a link, drop a screenshot — Vibn will figure it out.">
<Field
label="Brand colors"
optional
hint="Hex, or just describe it. Paste a link, drop a screenshot — Vibn will figure it out."
>
<input
className="wiz-input"
placeholder="#1B4D3E and #F2E2C4 — matches their truck wraps"
@@ -196,24 +280,47 @@ function ConsHandoff({ data, onChange }) {
<Field label="Where should it live?">
<PresetGroup
options={[
{ id: "subdomain", label: "Vibn subdomain", desc: "client-name.vibn.app — fastest." },
{ id: "custom", label: "Their custom domain", desc: "We'll walk you through DNS." },
{ id: "transfer", label: "Transfer ownership", desc: "They own it. You stay billable as editor." },
{
id: "subdomain",
label: "Vibn subdomain",
desc: "client-name.vibn.app — fastest.",
},
{
id: "custom",
label: "Their custom domain",
desc: "We'll walk you through DNS.",
},
{
id: "transfer",
label: "Transfer ownership",
desc: "They own it. You stay billable as editor.",
},
]}
value={data.handoff}
onChange={(v) => onChange({ handoff: v })}
columns={1}
/>
</Field>
<Field label="Your hourly rate" optional hint="Used on the handoff doc & invoice template.">
<Field
label="Your hourly rate"
optional
hint="Used on the handoff doc & invoice template."
>
<div style={{ position: "relative" }}>
<span
className="mono"
style={{
position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)",
color: "var(--fg-faint)", fontSize: 14.5, pointerEvents: "none",
position: "absolute",
left: 14,
top: "50%",
transform: "translateY(-50%)",
color: "var(--fg-faint)",
fontSize: 14.5,
pointerEvents: "none",
}}
>$</span>
>
$
</span>
<input
className="wiz-input"
type="number"
@@ -226,11 +333,18 @@ function ConsHandoff({ data, onChange }) {
<span
className="mono"
style={{
position: "absolute", right: 14, top: "50%", transform: "translateY(-50%)",
color: "var(--fg-faint)", fontSize: 11.5, letterSpacing: "0.04em",
position: "absolute",
right: 14,
top: "50%",
transform: "translateY(-50%)",
color: "var(--fg-faint)",
fontSize: 11.5,
letterSpacing: "0.04em",
pointerEvents: "none",
}}
>/ hour</span>
>
/ hour
</span>
</div>
</Field>
</>
@@ -238,7 +352,15 @@ function ConsHandoff({ data, onChange }) {
}
// ── Path wrapper ───────────────────────────────────────────────────────────
export function ConsultantPath({ data, onUpdate, onBack, onClose, onComplete, onJumpToStep, step }) {
export function ConsultantPath({
data,
onUpdate,
onBack,
onClose,
onComplete,
onJumpToStep,
step,
}) {
const next = () => {
if (step < CONS_TOTAL - 1) onJumpToStep(step + 1);
else onComplete();
@@ -258,12 +380,24 @@ export function ConsultantPath({ data, onUpdate, onBack, onClose, onComplete, on
onChange={onUpdate}
/>
);
canNext = (data.clientName || "").trim().length >= 2 && (data.industry || "").trim().length >= 3;
canNext =
(data.clientName || "").trim().length >= 2 &&
(data.industry || "").trim().length >= 3;
} else if (step === 1) {
body = <ConsBrief brief={data.brief || ""} onChange={(v) => onUpdate({ brief: v })} />;
body = (
<ConsBrief
brief={data.brief || ""}
onChange={(v) => onUpdate({ brief: v })}
/>
);
canNext = (data.brief || "").trim().length >= 30;
} else if (step === 2) {
body = <ConsScope scope={data.scope || []} onChange={(v) => onUpdate({ scope: v })} />;
body = (
<ConsScope
scope={data.scope || []}
onChange={(v) => onUpdate({ scope: v })}
/>
);
canNext = (data.scope || []).length >= 2;
} else {
body = <ConsHandoff data={data} onChange={onUpdate} />;
@@ -292,5 +426,3 @@ export function ConsultantPath({ data, onUpdate, onBack, onClose, onComplete, on
</>
);
}

View File

@@ -1,5 +1,5 @@
import NewSite from "@/marketing/components/new-site";
import "../styles/new-site.css";
import NewSite from "@/marketing/new-site";
import "./styles/new-site.css";
export const metadata = {
title: "Vibn — Keep vibing. All the way to launch.",

View File

@@ -20,8 +20,8 @@
},
],
"paths": {
"@/marketing/*": ["./_marketing/*"],
"@/onboarding/*": ["./_onboarding/*"],
"@/marketing/*": ["./components/marketing/*"],
"@/onboarding/*": ["./app/onboarding/*"],
"@/*": ["./*"],
},
},