Adopt Stackless UI: warm palette, sidebar, project tab bar with Design tab

- Add Google Fonts (Newsreader/Outfit/IBM Plex Mono) + warm beige CSS palette
- New VIBNSidebar: Stackless-style 220px sidebar with project list + user footer
- New ProjectShell: project header with name/status/progress% + tab bar
- Tabs: Atlas → PRD → Design → Build → Deploy → Settings
- New /prd page: section-by-section progress view
- New /build page: locked until PRD complete
- Projects list page: Stackless-style row layout
- Simplify overview page to just render AtlasChat

Made-with: Cursor
This commit is contained in:
2026-03-02 16:01:33 -08:00
parent 7ba3b9563e
commit aaa3f51592
9 changed files with 1051 additions and 451 deletions

View File

@@ -0,0 +1,176 @@
"use client";
import { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import Link from "next/link";
interface Project {
id: string;
status?: string;
prd?: string;
giteaRepoUrl?: string;
}
const BUILD_FEATURES = [
"Authentication system",
"Database schema",
"API endpoints",
"Core UI",
"Business logic",
"Tests",
];
export default function BuildPage() {
const params = useParams();
const projectId = params.projectId as string;
const workspace = params.workspace as string;
const [project, setProject] = useState<Project | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/projects/${projectId}`)
.then((r) => r.json())
.then((d) => {
setProject(d.project);
setLoading(false);
})
.catch(() => setLoading(false));
}, [projectId]);
if (loading) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%", fontFamily: "Outfit, sans-serif", color: "#a09a90" }}>
Loading
</div>
);
}
const hasRepo = Boolean(project?.giteaRepoUrl);
const hasPRD = Boolean(project?.prd);
if (!hasPRD) {
return (
<div
className="vibn-enter"
style={{
flex: 1, display: "flex", alignItems: "center", justifyContent: "center",
padding: 40, fontFamily: "Outfit, sans-serif",
}}
>
<div style={{ textAlign: "center", maxWidth: 360 }}>
<div style={{
width: 56, height: 56, borderRadius: 14,
background: "#fff", border: "1px solid #e8e4dc",
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: "1.4rem", margin: "0 auto 18px",
boxShadow: "0 2px 8px #1a1a1a08",
}}>
🔒
</div>
<h3 style={{
fontFamily: "Newsreader, serif", fontSize: "1.3rem",
fontWeight: 400, color: "#1a1a1a", marginBottom: 8,
}}>
Complete your PRD first
</h3>
<p style={{ fontSize: "0.82rem", color: "#a09a90", lineHeight: 1.6, marginBottom: 20 }}>
Finish your discovery with Atlas, then the builder unlocks automatically.
</p>
<Link
href={`/${workspace}/project/${projectId}/overview`}
style={{
display: "inline-block",
padding: "9px 20px", borderRadius: 7,
background: "#1a1a1a", color: "#fff",
fontSize: "0.78rem", fontWeight: 600,
fontFamily: "Outfit, sans-serif",
textDecoration: "none",
}}
>
Continue with Atlas
</Link>
</div>
</div>
);
}
if (!hasRepo) {
return (
<div
className="vibn-enter"
style={{
flex: 1, display: "flex", alignItems: "center", justifyContent: "center",
padding: 40, fontFamily: "Outfit, sans-serif",
}}
>
<div style={{ textAlign: "center", maxWidth: 360 }}>
<div style={{
width: 56, height: 56, borderRadius: 14,
background: "#fff", border: "1px solid #e8e4dc",
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: "1.4rem", margin: "0 auto 18px",
boxShadow: "0 2px 8px #1a1a1a08",
}}>
</div>
<h3 style={{
fontFamily: "Newsreader, serif", fontSize: "1.3rem",
fontWeight: 400, color: "#1a1a1a", marginBottom: 8,
}}>
PRD ready build coming soon
</h3>
<p style={{ fontSize: "0.82rem", color: "#a09a90", lineHeight: 1.6 }}>
The Architect agent will generate your project structure and kick off the build pipeline.
This feature is in active development.
</p>
</div>
</div>
);
}
return (
<div
className="vibn-enter"
style={{
flex: 1, display: "flex", alignItems: "center", justifyContent: "center",
padding: 40, fontFamily: "Outfit, sans-serif",
}}
>
<div style={{ width: "100%", maxWidth: 500 }}>
<h3 style={{
fontFamily: "Newsreader, serif", fontSize: "1.2rem",
fontWeight: 400, color: "#1a1a1a", marginBottom: 18,
}}>
Build progress
</h3>
{BUILD_FEATURES.map((f, i) => (
<div
key={i}
className="vibn-enter"
style={{
display: "flex", alignItems: "center", gap: 12,
padding: "12px 16px", marginBottom: 4, borderRadius: 8,
background: "#fff", border: "1px solid #e8e4dc",
animationDelay: `${i * 0.05}s`,
}}
>
<span style={{
width: 7, height: 7, borderRadius: "50%",
background: "#d4a04a", display: "inline-block", flexShrink: 0,
}} />
<span style={{ flex: 1, fontSize: "0.84rem", color: "#1a1a1a" }}>{f}</span>
<div style={{ width: 80, height: 3, borderRadius: 2, background: "#eae6de" }}>
<div style={{ height: "100%", width: "0%", borderRadius: 2, background: "#3d5afe" }} />
</div>
<span style={{
fontFamily: "IBM Plex Mono, monospace",
fontSize: "0.7rem", color: "#a09a90", minWidth: 28, textAlign: "right",
}}>
0%
</span>
</div>
))}
</div>
</div>
);
}