// vibn — Projects Dashboard
// Restyled from original (DM Sans + purple/colour accents) → Ink & parchment
// Design: Lora serif + Inter sans, #1a1510 ink, #f7f4ee paper, no colour accent
// Usage: default export, no required props
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",
border2:"#d3d1c7",
};
const F = { serif: "'Lora', Georgia, serif", sans: "'Inter', sans-serif" };
// ─── Shared primitives ─────────────────────────────────────────────────────────
function StatusPill({ label, variant = "default" }) {
const styles = {
live: { bg: T.cream, text: T.ink3, border: T.border },
building: { bg: T.cream, text: T.ink3, border: T.border },
default: { bg: T.paper, text: T.muted, border: T.border },
invoiced: { bg: T.ink, text: T.paper, border: T.ink },
unbilled: { bg: T.cream, text: T.ink3, border: T.border },
scheduled: { bg: T.parch, text: T.ink2, border: T.border2 },
};
const s = styles[variant] || styles.default;
return (
{label}
);
}
function InkBtn({ children, onClick, small, outline }) {
return (
);
}
// ─── Nav ───────────────────────────────────────────────────────────────────────
function Nav({ screen, setScreen }) {
return (
);
}
// ─── Data ──────────────────────────────────────────────────────────────────────
const PROJECTS = [
{
id: "launchpad", label: "Launchpad", initial: "L",
type: "own", status: "live", url: "launchpad.vibn.app",
stats: { visitors: "2.4k", signups: 183, mrr: "$840" },
},
{
id: "flowmatic", label: "Flowmatic", initial: "F",
type: "client", status: "live", url: "flowmatic.app",
client: "Acme Corp",
stats: { visitors: "890", signups: 54, mrr: "$210" },
costs: { total: 48.20, llm: 29.20, compute: 11.60, other: 7.40, billed: false },
},
{
id: "taskly", label: "Taskly", initial: "T",
type: "client", status: "building", url: null,
client: "Beta Labs", buildProgress: 60,
costs: { total: 12.40, llm: 9.20, compute: 3.20, other: 0, billed: false },
},
];
const ACTIVITY = [
{ text: "Launchpad — Blog post published:", detail: '"How to launch faster with AI"', time: "2h ago" },
{ text: "Flowmatic — New signup:", detail: "marcus@email.com", time: "4h ago" },
{ text: "Taskly — Checkout page built and deployed", detail: "", time: "6h ago" },
{ text: "Launchpad — Newsletter #12", detail: "scheduled", time: "Yesterday" },
];
const BILLING_ROWS = [
{ label: "Flowmatic", initial: "F", client: "Acme Corp", llm: 29.20, compute: 11.60, other: 7.40, total: 48.20, billed: false },
{ label: "Taskly", initial: "T", client: "Beta Labs", llm: 9.20, compute: 3.20, other: 0, total: 12.40, billed: false },
{ label: "Flowmatic", initial: "F", client: "Acme · Feb", llm: 22.10, compute: 8.40, other: 4.20, total: 34.70, billed: true },
];
const COST_LOG = [
{ time: "2h ago", desc: "LLM: Homepage copy generation", project: "Flowmatic", cost: 0.82 },
{ time: "3h ago", desc: "LLM: Checkout page code", project: "Taskly", cost: 1.24 },
{ time: "5h ago", desc: "LLM: Weekly newsletter draft", project: "Flowmatic", cost: 0.43 },
{ time: "6h ago", desc: "Compute: Build pipeline run", project: "Taskly", cost: 0.18 },
{ time: "8h ago", desc: "LLM: Discover phase Q&A", project: "Flowmatic", cost: 0.31 },
{ time: "Yesterday", desc: "Email delivery · 240 recipients", project: "Flowmatic", cost: 0.96 },
];
// ─── Project card ──────────────────────────────────────────────────────────────
function ProjectCard({ project }) {
const isClient = project.type === "client";
const isBuilding = project.status === "building";
return (
{/* Header preview */}
{isBuilding ? (
Build phase · {project.buildProgress}% complete
{isClient && (
Client
)}
) : (
{isClient ? "Client" : "My product"}
)}
{/* Identity row */}
{project.initial}
{project.label}
{isClient ? `${project.client} · ` : ""}
{project.url || "Setting up pages…"}
{/* Cost strip — client + building */}
{isClient && project.costs && isBuilding && (
Costs so far
${project.costs.total.toFixed(2)}
)}
{/* Cost strip — client + live */}
{isClient && project.costs && !isBuilding && (
Costs this month
${project.costs.total.toFixed(2)}
LLM ${project.costs.llm.toFixed(2)}
Compute ${project.costs.compute.toFixed(2)}
{!project.costs.billed && (
)}
)}
{/* Stats */}
{!isBuilding && project.stats && (
{[["visitors", project.stats.visitors], ["signups", project.stats.signups], ["MRR", project.stats.mrr]].map(([k, v]) => (
))}
)}
{/* Actions */}
{isBuilding ? (
) : (
{[["⬡", "Build"], ["◈", "Grow"]].map(([icon, label]) => (
{icon}
{label}
))}
↗
)}
);
}
// ─── Projects screen ───────────────────────────────────────────────────────────
function ProjectsScreen({ setScreen }) {
const totalUnbilled = PROJECTS
.filter(p => p.type === "client" && p.costs?.billed === false)
.reduce((s, p) => s + p.costs.total, 0);
return (
Your projects
3 active · 1 building
{totalUnbilled > 0 && (
)}
+ New project
{PROJECTS.map(p =>
)}
{/* New project CTA card */}
e.currentTarget.style.background = T.cream}
onMouseLeave={e => e.currentTarget.style.background = "transparent"}
>
+
New project
For yourself or a client
{/* Activity feed */}
Recent activity
{ACTIVITY.map((a, i) => (
{a.text}{" "}{a.detail && {a.detail}}
{a.time}
))}
);
}
// ─── Billing screen ────────────────────────────────────────────────────────────
function BillingScreen() {
const [tab, setTab] = useState("billing");
const unbilled = BILLING_ROWS.filter(r => !r.billed).reduce((s, r) => s + r.total, 0);
return (
{/* Sub-tabs */}
{[["billing", "Client billing"], ["costs", "Cost tracker"]].map(([id, label]) => (
))}
{tab === "billing" && <>
Client billing
All costs tracked and ready to invoice
Generate invoice
{[
{ label: "Total unbilled", value: `$${unbilled.toFixed(2)}` },
{ label: "LLM costs", value: "$38.40" },
{ label: "Compute", value: "$14.80" },
{ label: "Other", value: "$7.40" },
].map(c => (
))}
Breakdown by client
{["Project / Client", "LLM", "Compute", "Other", "Total", "Status"].map(h => (
{h}
))}
{BILLING_ROWS.map((r, i) => (
${r.llm.toFixed(2)}
${r.compute.toFixed(2)}
${r.other.toFixed(2)}
${r.total.toFixed(2)}
{!r.billed && (
)}
))}
>}
{tab === "costs" && <>
Cost tracker
Every dollar spent, broken down by type and project
LLM usage
{[
{ label: "Code generation", amount: 21.40, pct: 56 },
{ label: "Content & marketing", amount: 10.20, pct: 27 },
{ label: "Chat assist", amount: 6.80, pct: 18 },
].map(r => (
{r.label}
${r.amount.toFixed(2)}
))}
Total LLM
$38.40
Infrastructure
{[
{ label: "Hosting & compute", amount: 11.60 },
{ label: "Database", amount: 3.20 },
{ label: "Email delivery", amount: 4.20 },
{ label: "Domain & SSL", amount: 3.20 },
].map(r => (
{r.label}
${r.amount.toFixed(2)}
))}
Total infra
$22.20
Recent charges
{["Time", "Description", "Project", "Cost"].map(h => (
{h}
))}
{COST_LOG.map((row, i) => (
{row.time}
{row.desc}
{row.project}
${row.cost.toFixed(2)}
))}
>}
);
}
// ─── Root ──────────────────────────────────────────────────────────────────────
export default function Dashboard() {
const [screen, setScreen] = useState("projects");
return (
{screen === "projects" &&
}
{screen === "billing" &&
}
);
}