Theia rip-out (parent):
- Remove theia submodule entry (the local fork, Gitea repo, Coolify app,
Cloud Run services, and Artifact Registry image are all gone)
- Drop README.md + INFRASTRUCTURE.md (obsolete "Project OS" snapshots
that also leaked API tokens) and setup.sh (Theia clone bootstrap)
- Delete UI-DESIGN-GUIDE.md, BACKEND_AGENTS_PLAN.md, VIBN_BUILD_PLAN.md,
VISUAL_EDITOR_PLAN.md, core-packages.md, ai-packages.md, tools-list.md
(all 100% Theia-specific or superseded)
- Surgical scrubs of remaining Theia mentions in
AGENT_EXECUTION_ARCHITECTURE.md and TURBOREPO_MIGRATION_PLAN.md
Submodule bumps:
- vibn-agent-runner: Theia rip-out + MCP refactor (api/wrapper/server
pattern across shell/file/git/memory/prd/search/agent/gitea/coolify)
- vibn-frontend: Theia rip-out + P5.1 attach E2E + Justine UI WIP
Retire platform/ scaffold:
- Remove platform/backend/ (control-plane, executors, mcp-adapter),
platform/client-ide/ (gcp-productos extension), platform/contracts/,
platform/infra/terraform/, platform/scripts/templates/turborepo/
(replaced by vibn-agent-runner + vibn-frontend + Coolify direct)
- Drop architecture.md, technical_spec.md, vision-ext.md,
"1.Generate Control Plane API scaffold.md" (same era)
Docs / planning snapshots (new):
- AI_CAPABILITIES.md, AI_CAPABILITIES_ROADMAP.md
- AGENT_TELEMETRY_STREAMING_PROJECT.md
- VIBN_PRD.md, product-idea-a.md
Design assets (new):
- branding/{coolify,gitea,ux-testing}/ static brand collateral
- justine/ HTML mockups for the new onboarding/build flows
- preview-assist-ui/ Vite scratch app
- master-ai.code-workspace
Infra helpers (new):
- setup-coolify-montreal.sh provisioner
- gitea-docker-compose.yml
- vibn-coolify-schema.sql for the Coolify Postgres extensions
- prd-agent-prompt.pdf, prompt, root.txt, remixed-9edec9e9.tsx scratch
- flatten.sh helper
.gitignore: ignore **/node_modules, **/.next, **/.turbo, **/coverage
Made-with: Cursor
289 lines
15 KiB
JavaScript
289 lines
15 KiB
JavaScript
// vibn — Marketing Website
|
||
// Design: Ink & parchment — Lora serif + Inter sans, no colour accent
|
||
// Usage: <Website onGetStarted={fn} onLogin={fn} />
|
||
|
||
import { useState } from "react";
|
||
|
||
const T = {
|
||
ink: "#1a1510",
|
||
ink2: "#2c2c2a",
|
||
ink3: "#444441",
|
||
mid: "#5f5e5a",
|
||
muted: "#888780",
|
||
stone: "#b4b2a9",
|
||
parch: "#d3d1c7",
|
||
cream: "#f1efe8",
|
||
paper: "#f7f4ee",
|
||
white: "#fdfcfa",
|
||
border: "#e8e2d9",
|
||
};
|
||
|
||
const F = { serif: "'Lora', Georgia, serif", sans: "'Inter', sans-serif" };
|
||
|
||
// ─── Primitives ────────────────────────────────────────────────────────────────
|
||
|
||
function Eyebrow({ children }) {
|
||
return (
|
||
<div style={{ fontFamily: F.sans, fontSize: 11, fontWeight: 600, letterSpacing: "0.13em", textTransform: "uppercase", color: T.muted, marginBottom: 16 }}>
|
||
{children}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function PrimaryBtn({ children, onClick, large }) {
|
||
return (
|
||
<button onClick={onClick} style={{
|
||
background: T.ink, color: T.paper, border: "none",
|
||
fontFamily: F.sans, fontWeight: 600,
|
||
fontSize: large ? 15 : 14,
|
||
padding: large ? "14px 34px" : "10px 24px",
|
||
borderRadius: 10, cursor: "pointer",
|
||
}}>{children}</button>
|
||
);
|
||
}
|
||
|
||
// ─── Nav ───────────────────────────────────────────────────────────────────────
|
||
|
||
function Nav({ onGetStarted, onLogin }) {
|
||
return (
|
||
<nav style={{
|
||
background: T.paper, borderBottom: `1px solid ${T.border}`,
|
||
padding: "0 48px", height: 60,
|
||
display: "flex", alignItems: "center", justifyContent: "space-between",
|
||
position: "sticky", top: 0, zIndex: 50,
|
||
}}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||
<div style={{ width: 30, height: 30, background: T.ink, borderRadius: 7, display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||
<span style={{ fontFamily: F.serif, fontSize: 15, fontWeight: 700, color: T.paper }}>V</span>
|
||
</div>
|
||
<span style={{ fontFamily: F.serif, fontSize: 19, fontWeight: 700, color: T.ink, letterSpacing: "-0.02em" }}>vibn</span>
|
||
</div>
|
||
|
||
<div style={{ display: "flex", gap: 32 }}>
|
||
{["Product", "Pricing", "Stories", "Blog"].map(l => (
|
||
<span key={l} style={{ fontFamily: F.sans, fontSize: 14, color: T.muted, cursor: "pointer" }}>{l}</span>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||
<span onClick={onLogin} style={{ fontFamily: F.sans, fontSize: 14, color: T.muted, cursor: "pointer" }}>Log in</span>
|
||
<PrimaryBtn onClick={onGetStarted}>Get started free</PrimaryBtn>
|
||
</div>
|
||
</nav>
|
||
);
|
||
}
|
||
|
||
// ─── Hero ──────────────────────────────────────────────────────────────────────
|
||
|
||
function Hero({ onCta }) {
|
||
return (
|
||
<section style={{ maxWidth: 960, margin: "0 auto", padding: "88px 48px 72px" }}>
|
||
<Eyebrow>For non-technical founders</Eyebrow>
|
||
<h1 style={{
|
||
fontFamily: F.serif, fontSize: 64, fontWeight: 700, color: T.ink,
|
||
letterSpacing: "-0.03em", lineHeight: 1.07, marginBottom: 28, maxWidth: 680,
|
||
}}>
|
||
You have the idea.<br />
|
||
We handle<br />
|
||
<em style={{ fontStyle: "italic", color: T.ink3 }}>everything else.</em>
|
||
</h1>
|
||
<p style={{ fontFamily: F.sans, fontSize: 17.5, color: T.mid, lineHeight: 1.75, maxWidth: 480, marginBottom: 40 }}>
|
||
No backend. No DevOps. No marketing agency. Describe your idea and vibn
|
||
builds, deploys, and promotes it — automatically.
|
||
</p>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 18, marginBottom: 14 }}>
|
||
<PrimaryBtn onClick={onCta} large>Start free — no code needed</PrimaryBtn>
|
||
<span style={{ fontFamily: F.sans, fontSize: 13.5, color: T.stone }}>★★★★★ 280 founders launched</span>
|
||
</div>
|
||
<p style={{ fontFamily: F.sans, fontSize: 12, color: T.stone }}>No credit card required · Free forever plan</p>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ─── Quote band ────────────────────────────────────────────────────────────────
|
||
|
||
const QUOTES = [
|
||
{ q: "I had the idea for 2 years. The backend terrified me. vibn shipped it in 4 days and handles all my marketing.", by: "Alex K.", role: "Founder, Taskly" },
|
||
{ q: "I have zero coding experience. Three weeks in I have 300 paying users. That's entirely because of vibn.", by: "Marcus L.", role: "Founder, Flowmatic" },
|
||
{ q: "The marketing autopilot alone saved me ten hours a week. My blog runs itself. I just focus on my product.", by: "Sara R.", role: "Founder, Nudge" },
|
||
];
|
||
|
||
function QuoteBand() {
|
||
return (
|
||
<section style={{ background: T.ink, padding: "52px 48px" }}>
|
||
<div style={{ maxWidth: 960, margin: "0 auto", display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 40 }}>
|
||
{QUOTES.map((q, i) => (
|
||
<div key={i} style={{ display: "flex", gap: 20 }}>
|
||
<div style={{ width: 3, background: T.mid, borderRadius: 2, flexShrink: 0 }} />
|
||
<div>
|
||
<p style={{ fontFamily: F.serif, fontSize: 15, color: T.parch, lineHeight: 1.72, fontStyle: "italic", marginBottom: 12 }}>"{q.q}"</p>
|
||
<span style={{ fontFamily: F.sans, fontSize: 11.5, color: T.muted, fontWeight: 600 }}>— {q.by}, {q.role}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ─── How it works ──────────────────────────────────────────────────────────────
|
||
|
||
const PHASES = [
|
||
{ n: "01", id: "Discover", title: "Define your idea", body: "Six guided questions turn a rough idea into a full product plan — pages, architecture, revenue model. No jargon." },
|
||
{ n: "02", id: "Design", title: "Choose your style", body: "Pick a visual style and see your exact site and emails live before a single line of code is written." },
|
||
{ n: "03", id: "Build", title: "Your app, live", body: "AI writes every line. Auth, database, payments, all pages — deployed and live. Describe changes in plain English." },
|
||
{ n: "04", id: "Grow", title: "Market & automate", body: "AI generates your blog, emails, and social schedule — publishing on autopilot so you can focus on your users." },
|
||
];
|
||
|
||
function HowItWorks() {
|
||
return (
|
||
<section style={{ maxWidth: 960, margin: "0 auto", padding: "80px 48px" }}>
|
||
<Eyebrow>How it works</Eyebrow>
|
||
<h2 style={{ fontFamily: F.serif, fontSize: 40, fontWeight: 700, color: T.ink, letterSpacing: "-0.02em", lineHeight: 1.15, marginBottom: 52, maxWidth: 460 }}>
|
||
Four phases.<br />One complete product.
|
||
</h2>
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", border: `1px solid ${T.border}`, borderRadius: 14, overflow: "hidden" }}>
|
||
{PHASES.map((p, i) => (
|
||
<div key={i} style={{
|
||
padding: "38px 42px", background: T.white,
|
||
borderRight: (i % 2 === 0) ? `1px solid ${T.border}` : "none",
|
||
borderBottom: (i < 2) ? `1px solid ${T.border}` : "none",
|
||
}}>
|
||
<div style={{ fontFamily: F.sans, fontSize: 11, fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: T.muted, marginBottom: 14 }}>
|
||
{p.n} — {p.id}
|
||
</div>
|
||
<div style={{ fontFamily: F.serif, fontSize: 21, fontWeight: 700, color: T.ink, marginBottom: 10 }}>{p.title}</div>
|
||
<p style={{ fontFamily: F.sans, fontSize: 13.5, color: T.mid, lineHeight: 1.7 }}>{p.body}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ─── Stats bar ─────────────────────────────────────────────────────────────────
|
||
|
||
const STATS = [
|
||
{ n: "280+", label: "founders launched" },
|
||
{ n: "72h", label: "average time to first version" },
|
||
{ n: "4.9★", label: "average rating" },
|
||
{ n: "3×", label: "faster than hiring a developer" },
|
||
];
|
||
|
||
function StatsBar() {
|
||
return (
|
||
<section style={{ background: T.white, borderTop: `1px solid ${T.border}`, borderBottom: `1px solid ${T.border}` }}>
|
||
<div style={{ maxWidth: 960, margin: "0 auto", padding: "0 48px", display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr" }}>
|
||
{STATS.map((s, i) => (
|
||
<div key={i} style={{ padding: "38px 0", paddingLeft: i > 0 ? 36 : 0, borderRight: i < 3 ? `1px solid ${T.border}` : "none" }}>
|
||
<div style={{ fontFamily: F.serif, fontSize: 38, fontWeight: 700, color: T.ink, letterSpacing: "-0.03em", marginBottom: 6 }}>{s.n}</div>
|
||
<div style={{ fontFamily: F.sans, fontSize: 13, color: T.muted }}>{s.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ─── Empathy section ───────────────────────────────────────────────────────────
|
||
|
||
const PAINS = [
|
||
{ title: "No more \"I need to hire a developer first\"", body: "vibn is your developer. Start building the moment you have an idea." },
|
||
{ title: "No more staring at a blank marketing calendar", body: "AI generates and publishes your content every single week." },
|
||
{ title: "No more \"I'll launch when it's ready\"", body: "Most founders ship their first version in under 72 hours." },
|
||
];
|
||
|
||
function EmpathySection() {
|
||
return (
|
||
<section style={{ background: T.cream, borderTop: `1px solid ${T.border}`, borderBottom: `1px solid ${T.border}`, padding: "76px 48px" }}>
|
||
<div style={{ maxWidth: 960, margin: "0 auto", display: "grid", gridTemplateColumns: "1fr 1fr", gap: 68, alignItems: "center" }}>
|
||
<div>
|
||
<Eyebrow>Sound familiar?</Eyebrow>
|
||
<h2 style={{ fontFamily: F.serif, fontSize: 36, fontWeight: 700, color: T.ink, lineHeight: 1.18, marginBottom: 24, letterSpacing: "-0.02em" }}>
|
||
The idea is the hard part. Everything else shouldn't be.
|
||
</h2>
|
||
<p style={{ fontFamily: F.sans, fontSize: 15, color: T.mid, lineHeight: 1.8, marginBottom: 20 }}>
|
||
You know exactly what you want to build and who it's for. But the moment you think
|
||
about servers, databases, deployment pipelines, SEO strategies — the whole thing stalls.
|
||
</p>
|
||
<p style={{ fontFamily: F.sans, fontSize: 15, color: T.mid, lineHeight: 1.8 }}>
|
||
vibn exists to remove all of that. Not abstract it —{" "}
|
||
<em style={{ fontFamily: F.serif, fontStyle: "italic" }}>remove it entirely.</em>
|
||
</p>
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
|
||
{PAINS.map((p, i) => (
|
||
<div key={i} style={{ background: T.white, border: `1px solid ${T.border}`, borderRadius: 12, padding: "18px 20px", display: "flex", gap: 14, alignItems: "flex-start" }}>
|
||
<div style={{ width: 20, height: 20, borderRadius: "50%", border: `1.5px solid ${T.stone}`, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, marginTop: 2 }}>
|
||
<div style={{ width: 7, height: 7, borderRadius: "50%", background: T.ink }} />
|
||
</div>
|
||
<div>
|
||
<div style={{ fontFamily: F.serif, fontSize: 14, fontWeight: 600, color: T.ink, marginBottom: 4 }}>{p.title}</div>
|
||
<div style={{ fontFamily: F.sans, fontSize: 13, color: T.muted, lineHeight: 1.6 }}>{p.body}</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ─── Final CTA ─────────────────────────────────────────────────────────────────
|
||
|
||
function FinalCta({ onCta }) {
|
||
return (
|
||
<section style={{ maxWidth: 660, margin: "0 auto", padding: "92px 48px", textAlign: "center" }}>
|
||
<h2 style={{ fontFamily: F.serif, fontSize: 46, fontWeight: 700, color: T.ink, letterSpacing: "-0.03em", lineHeight: 1.1, marginBottom: 20 }}>
|
||
Your idea deserves to exist.
|
||
</h2>
|
||
<p style={{ fontFamily: F.sans, fontSize: 16, color: T.mid, lineHeight: 1.75, marginBottom: 36 }}>
|
||
Don't let the backend be the reason it doesn't. Start today — free, no code, no credit card.
|
||
</p>
|
||
<PrimaryBtn onClick={onCta} large>Build my product — free</PrimaryBtn>
|
||
<div style={{ fontFamily: F.sans, fontSize: 12.5, color: T.stone, marginTop: 16 }}>
|
||
Joins 280+ non-technical founders already live
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// ─── Footer ────────────────────────────────────────────────────────────────────
|
||
|
||
function Footer() {
|
||
return (
|
||
<footer style={{ borderTop: `1px solid ${T.border}`, padding: "32px 48px", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||
<span style={{ fontFamily: F.serif, fontSize: 16, fontWeight: 700, color: T.ink }}>vibn</span>
|
||
<div style={{ display: "flex", gap: 28 }}>
|
||
{["Product", "Pricing", "Stories", "Blog", "Privacy", "Terms"].map(l => (
|
||
<span key={l} style={{ fontFamily: F.sans, fontSize: 13, color: T.stone, cursor: "pointer" }}>{l}</span>
|
||
))}
|
||
</div>
|
||
<span style={{ fontFamily: F.sans, fontSize: 12.5, color: T.stone }}>© 2026 vibn</span>
|
||
</footer>
|
||
);
|
||
}
|
||
|
||
// ─── Root export ───────────────────────────────────────────────────────────────
|
||
|
||
export default function Website({ onGetStarted = () => {}, onLogin = () => {} }) {
|
||
return (
|
||
<div style={{ background: T.paper, color: T.ink, minHeight: "100vh" }}>
|
||
<style>{`
|
||
@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;0,700;1,400;1,600&family=Inter:wght@400;500;600&display=swap');
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { font-family: 'Inter', sans-serif; }
|
||
button { font-family: inherit; }
|
||
`}</style>
|
||
<Nav onGetStarted={onGetStarted} onLogin={onLogin} />
|
||
<Hero onCta={onGetStarted} />
|
||
<QuoteBand />
|
||
<HowItWorks />
|
||
<StatsBar />
|
||
<EmpathySection />
|
||
<FinalCta onCta={onGetStarted} />
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|