This commit addresses the issue where DeepSeek's raw XML markup (like <tool_calls> and <think>) was leaking into chat history, causing hallucinations in subsequent turns. It also patches a vulnerability in the git commit tool where arbitrary shell injection was possible. Additionally, it includes UX copy and color contrast adjustments for the marketing homepage breadcrumbs.
334 lines
12 KiB
JavaScript
334 lines
12 KiB
JavaScript
// The Journey — 4 steps from idea → first 100 customers. A visual marker shows
|
|
// where most tools stop. Each step shows a tiny "demo" snippet of what Vibn does.
|
|
|
|
const JOURNEY_STEPS = [
|
|
{
|
|
num: "01",
|
|
title: "You describe it.",
|
|
sub: "The AI builds it.",
|
|
body: "Talk to it like you'd talk to a friend who codes. It builds the screens, the buttons, the logic — whatever your idea needs.",
|
|
demo: "describe",
|
|
},
|
|
{
|
|
num: "02",
|
|
title: "It goes live.",
|
|
sub: "The AI puts it online.",
|
|
body: "Logins, saving your stuff, hosting — handled. You get a live link from minute one. Share it. Show your friends. It just works.",
|
|
demo: "live",
|
|
},
|
|
{
|
|
num: "03",
|
|
title: "It gets seen.",
|
|
sub: "The AI markets it.",
|
|
body: "Posts, emails, social — written, scheduled, and shipped on autopilot. The tone matches your brand because you trained it talking to your AI.",
|
|
demo: "seen",
|
|
},
|
|
{
|
|
num: "04",
|
|
title: "It gets customers.",
|
|
sub: "Your first 100.",
|
|
body: "Through our Google partnership, Vibn helps the right people find your product when they're searching for what you built.",
|
|
demo: "customers",
|
|
},
|
|
];
|
|
|
|
function Journey() {
|
|
return (
|
|
<section className="section journey" id="how">
|
|
<style>{`
|
|
.journey { padding-block: clamp(80px, 11vh, 140px); }
|
|
.journey-head { text-align: center; max-width: 820px; margin: 0 auto 64px; }
|
|
.journey-title {
|
|
font-size: clamp(36px, 4.8vw, 64px);
|
|
font-weight: 500; letter-spacing: -0.025em; line-height: 1.02;
|
|
text-wrap: balance;
|
|
}
|
|
.journey-title .accent { color: var(--accent); }
|
|
.journey-sub {
|
|
margin-top: 20px;
|
|
color: var(--fg-mute); font-size: 17px;
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.journey-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 16px;
|
|
position: relative;
|
|
}
|
|
@media (max-width: 1080px) { .journey-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
@media (max-width: 640px) { .journey-grid { grid-template-columns: 1fr; } }
|
|
|
|
.step {
|
|
position: relative;
|
|
padding: 24px 24px 0;
|
|
border-radius: 16px;
|
|
background: linear-gradient(180deg, oklch(0.20 0.009 60 / 0.55), oklch(0.17 0.008 60 / 0.55));
|
|
border: 1px solid var(--hairline);
|
|
display: flex; flex-direction: column;
|
|
min-height: 380px;
|
|
overflow: hidden;
|
|
isolation: isolate;
|
|
}
|
|
.step::before {
|
|
/* Top accent line that varies by step */
|
|
content: "";
|
|
position: absolute; top: 0; left: 0; right: 0; height: 1px;
|
|
background: linear-gradient(90deg, transparent, var(--accent) 50%, transparent);
|
|
opacity: 0;
|
|
}
|
|
.step.active::before { opacity: .7; }
|
|
.step.stopped {
|
|
opacity: 0.46;
|
|
}
|
|
.step.stopped::after {
|
|
content: "";
|
|
position: absolute; inset: 0;
|
|
background: linear-gradient(180deg, transparent 40%, oklch(0.155 0.008 60 / 0.6));
|
|
pointer-events: none;
|
|
}
|
|
|
|
.step-num {
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--fg-faint);
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.step-title {
|
|
margin-top: 12px;
|
|
font-size: 22px; font-weight: 500;
|
|
letter-spacing: -0.018em;
|
|
}
|
|
.step-sub {
|
|
margin-top: 4px;
|
|
color: var(--accent);
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
}
|
|
.step.stopped .step-sub { color: var(--fg-mute); }
|
|
.step-body {
|
|
margin-top: 12px;
|
|
color: var(--fg-dim);
|
|
font-size: 14px;
|
|
line-height: 1.55;
|
|
}
|
|
.step-demo {
|
|
margin-top: auto;
|
|
margin-inline: -24px; margin-bottom: 0;
|
|
padding: 16px 18px;
|
|
border-top: 1px solid var(--hairline);
|
|
background: oklch(0.16 0.008 60 / 0.6);
|
|
font-family: var(--font-mono);
|
|
font-size: 12px; line-height: 1.55;
|
|
color: var(--fg-dim);
|
|
min-height: 116px;
|
|
display: flex; flex-direction: column;
|
|
gap: 7px;
|
|
}
|
|
|
|
/* Visual marker: where other tools stop */
|
|
.stop-marker {
|
|
position: absolute;
|
|
left: calc(50% - 8px);
|
|
top: 0; bottom: 0;
|
|
width: 16px;
|
|
display: flex; flex-direction: column; align-items: center;
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
}
|
|
@media (max-width: 1080px) { .stop-marker { display: none; } }
|
|
.stop-marker .line {
|
|
flex: 1; width: 1px;
|
|
background: repeating-linear-gradient(180deg, var(--accent) 0 6px, transparent 6px 12px);
|
|
opacity: .7;
|
|
}
|
|
.stop-label {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
color: var(--accent);
|
|
background: var(--bg);
|
|
padding: 6px 12px;
|
|
border-radius: 999px;
|
|
border: 1px solid oklch(0.74 0.175 35 / 0.5);
|
|
white-space: nowrap;
|
|
box-shadow: 0 0 24px var(--accent-glow);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Demo blocks */
|
|
.demo-row { display: flex; gap: 8px; align-items: flex-start; }
|
|
.demo-tag {
|
|
font-family: var(--font-mono); font-size: 10px;
|
|
padding: 1px 6px; border-radius: 4px;
|
|
color: var(--fg-faint);
|
|
background: oklch(0.22 0.01 60);
|
|
letter-spacing: 0.04em;
|
|
flex-shrink: 0;
|
|
margin-top: 1px;
|
|
}
|
|
.demo-tag.you { color: oklch(0.85 0.06 250); background: oklch(0.28 0.04 250); }
|
|
.demo-tag.ai { color: var(--accent); background: oklch(0.35 0.10 35 / 0.4); }
|
|
.demo-line { color: var(--fg-dim); }
|
|
.demo-ok { color: var(--ok); }
|
|
.demo-host {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
color: var(--fg-dim);
|
|
}
|
|
.demo-host i {
|
|
width: 6px; height: 6px; border-radius: 50%;
|
|
background: var(--ok);
|
|
box-shadow: 0 0 6px oklch(0.78 0.16 155 / 0.6);
|
|
}
|
|
.demo-progress {
|
|
height: 4px; border-radius: 999px;
|
|
background: oklch(0.25 0.01 60);
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
.demo-progress span {
|
|
position: absolute; inset: 0;
|
|
background: var(--accent);
|
|
width: 64%;
|
|
box-shadow: 0 0 8px var(--accent-glow);
|
|
}
|
|
.demo-customer {
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.demo-customer .av {
|
|
width: 16px; height: 16px; border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
.demo-num {
|
|
font-family: var(--font-mono); font-size: 22px;
|
|
color: var(--accent);
|
|
letter-spacing: -0.02em;
|
|
font-weight: 500;
|
|
}
|
|
.demo-num small {
|
|
color: var(--fg-mute); font-size: 11px;
|
|
font-weight: 400;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.journey-foot {
|
|
margin-top: 48px;
|
|
text-align: center;
|
|
color: var(--fg-mute);
|
|
font-size: 15px;
|
|
text-wrap: balance;
|
|
}
|
|
.journey-foot b {
|
|
color: var(--fg);
|
|
font-weight: 500;
|
|
}
|
|
`}</style>
|
|
|
|
<div className="wrap">
|
|
<div className="journey-head">
|
|
<Eyebrow>The journey</Eyebrow>
|
|
<h2 className="journey-title" style={{ marginTop: 18 }}>
|
|
From idea to first 100 customers.
|
|
<br/><span className="accent">In one chat.</span>
|
|
</h2>
|
|
<p className="journey-sub">
|
|
Other tools take you to step two and wave goodbye. Vibn keeps building with you.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="journey-grid">
|
|
{/* "Where everyone else stops" marker, sits over the gap between cards 2 and 3 */}
|
|
<div className="stop-marker" style={{ left: "calc(50% - 1px)" }}>
|
|
<div className="line" />
|
|
<span className="stop-label">↑ Where every other tool stops</span>
|
|
<div className="line" />
|
|
</div>
|
|
|
|
{JOURNEY_STEPS.map((step, i) => (
|
|
<StepCard key={step.num} step={step} stopped={i >= 2} />
|
|
))}
|
|
</div>
|
|
|
|
<p className="journey-foot">
|
|
<b>One tool. One chat.</b> From "wouldn't it be cool if…" to <b>real customers paying you money.</b>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function StepCard({ step, stopped }) {
|
|
return (
|
|
<div className={`step${stopped ? "" : " active"}`}>
|
|
<div>
|
|
<div className="step-num">{step.num}</div>
|
|
<h3 className="step-title">{step.title}</h3>
|
|
<div className="step-sub">{step.sub}</div>
|
|
<p className="step-body">{step.body}</p>
|
|
</div>
|
|
<StepDemo demo={step.demo} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function StepDemo({ demo }) {
|
|
if (demo === "describe") {
|
|
return (
|
|
<div className="step-demo">
|
|
<div className="demo-row"><span className="demo-tag you">YOU</span><span className="demo-line">build a booking site for my dog grooming biz</span></div>
|
|
<div className="demo-row"><span className="demo-tag ai">VIBN</span><span className="demo-line">on it — designing screens…</span></div>
|
|
<div className="demo-row" style={{ alignItems: "center" }}>
|
|
<span className="demo-tag ai">VIBN</span>
|
|
<span className="demo-ok">✓ booking flow ready</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
if (demo === "live") {
|
|
return (
|
|
<div className="step-demo">
|
|
<div className="demo-row" style={{ alignItems: "center" }}>
|
|
<span className="demo-tag ai">VIBN</span>
|
|
<span className="demo-line">put it online</span>
|
|
</div>
|
|
<div className="demo-progress"><span /></div>
|
|
<div className="demo-row" style={{ alignItems: "center", marginTop: 2 }}>
|
|
<span className="demo-host"><i /> pawsandposh.vibn.app</span>
|
|
</div>
|
|
<div className="demo-row"><span className="demo-ok">✓ logins · ✓ saving · ✓ live</span></div>
|
|
</div>
|
|
);
|
|
}
|
|
if (demo === "seen") {
|
|
return (
|
|
<div className="step-demo">
|
|
<div className="demo-row"><span className="demo-tag ai">VIBN</span><span className="demo-line">draft a launch post for Instagram + email blast</span></div>
|
|
<div className="demo-row" style={{ color: "var(--fg-faint)" }}>↳ scheduled for Tue 9:00 AM</div>
|
|
<div className="demo-row" style={{ color: "var(--fg-faint)" }}>↳ scheduled for Thu 6:00 PM</div>
|
|
<div className="demo-row"><span className="demo-ok">✓ 3 channels on autopilot</span></div>
|
|
</div>
|
|
);
|
|
}
|
|
if (demo === "customers") {
|
|
return (
|
|
<div className="step-demo">
|
|
<div className="demo-row" style={{ alignItems: "center" }}>
|
|
<span className="demo-num">+47<small>this week</small></span>
|
|
</div>
|
|
<div className="demo-customer">
|
|
<span className="av" style={{ background: "oklch(0.55 0.14 35)" }} />
|
|
<span className="av" style={{ background: "oklch(0.55 0.14 260)" }} />
|
|
<span className="av" style={{ background: "oklch(0.55 0.14 155)" }} />
|
|
<span className="av" style={{ background: "oklch(0.55 0.14 80)" }} />
|
|
<span style={{ color: "var(--fg-mute)" }}>found you via Google</span>
|
|
</div>
|
|
<div className="demo-row"><span className="demo-ok">✓ tracking toward 100</span></div>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Object.assign(window, { Journey });
|