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
992 lines
64 KiB
TypeScript
992 lines
64 KiB
TypeScript
import { useState, useEffect, useRef } from "react";
|
||
|
||
const FontLoader = () => (
|
||
<style>{`
|
||
@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400&family=Outfit:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');
|
||
* { margin:0; padding:0; box-sizing:border-box; }
|
||
body { background: #f6f4f0; }
|
||
@keyframes enter { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
|
||
@keyframes blink { 0%,100%{opacity:.2} 50%{opacity:.8} }
|
||
@keyframes breathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.15)} }
|
||
::selection { background: #1a1a1a; color: #fff; }
|
||
::-webkit-scrollbar { width: 4px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb { background: #d0ccc4; border-radius: 10px; }
|
||
input::placeholder { color: #b5b0a6; }
|
||
input:focus, textarea:focus { outline: none; }
|
||
button { font-family: 'Outfit', sans-serif; cursor: pointer; }
|
||
textarea { font-family: 'Outfit', sans-serif; resize: vertical; }
|
||
`}</style>
|
||
);
|
||
|
||
/* ─── DATA ─── */
|
||
const projects = [
|
||
{ id: 1, name: "Meridian", desc: "Client portal for boutique agencies", status: "building", progress: 68, features: 12, phase: "Frontend Gen", lastActive: "2h ago", color: "#3d5afe", domain: "meridian-app.stackless.dev", repo: "stackless/meridian-build", created: "Jan 12, 2026" },
|
||
{ id: 2, name: "Tidepool", desc: "Marine research data platform", status: "prd", progress: 45, features: 7, phase: "Features", lastActive: "20m ago", color: "#00897b", domain: null, repo: null, created: "Feb 3, 2026" },
|
||
{ id: 3, name: "Canopy", desc: "Internal team knowledge base", status: "live", progress: 100, features: 18, phase: "Deployed", lastActive: "1d ago", color: "#2e7d32", domain: "canopy.stackless.dev", customDomain: "kb.acmecorp.com", repo: "stackless/canopy-build", created: "Nov 28, 2025" },
|
||
{ id: 4, name: "Foxglove", desc: "Prescription mgmt for pharmacies", status: "prd", progress: 20, features: 3, phase: "Discovery", lastActive: "now", color: "#e65100", domain: null, repo: null, created: "Feb 27, 2026" },
|
||
];
|
||
|
||
const activityFeed = [
|
||
{ time: "2 min ago", project: "Foxglove", action: "Atlas completed Users & Personas phase", type: "atlas" },
|
||
{ time: "18 min ago", project: "Foxglove", action: "You described the core prescription workflow", type: "user" },
|
||
{ time: "1h ago", project: "Meridian", action: "Build: Dashboard UI component generated", type: "build" },
|
||
{ time: "2h ago", project: "Meridian", action: "Build: Authentication system passed all tests", type: "build" },
|
||
{ time: "3h ago", project: "Tidepool", action: "Atlas captured 7 features in MoSCoW framework", type: "atlas" },
|
||
{ time: "5h ago", project: "Tidepool", action: "You approved Problem Statement section", type: "user" },
|
||
{ time: "8h ago", project: "Meridian", action: "Build: Database schema deployed", type: "build" },
|
||
{ time: "1d ago", project: "Canopy", action: "Custom domain kb.acmecorp.com verified and active", type: "deploy" },
|
||
{ time: "1d ago", project: "Canopy", action: "v1.2 deployed — added search filters", type: "deploy" },
|
||
{ time: "2d ago", project: "Meridian", action: "PRD approved — build pipeline started", type: "deploy" },
|
||
{ time: "2d ago", project: "Tidepool", action: "Project created", type: "user" },
|
||
{ time: "3d ago", project: "Foxglove", action: "Project created", type: "user" },
|
||
];
|
||
|
||
const chatHistory = [
|
||
{ from: "atlas", text: "I see you're building Foxglove — prescription management for small pharmacies. We've locked in the problem statement and identified your primary user.\n\nNow let's map the core workflow. When a pharmacist opens Foxglove first thing in the morning, what do they need to see?" },
|
||
{ from: "user", text: "They need to see incoming prescriptions from doctors. The current systems are super clunky. They want to see new scripts, verify them, and mark them as filled." },
|
||
{ from: "atlas", text: "Clean workflow. Three stages:\n\n1. Receive — new scripts appear in a queue\n2. Verify — check dosage, interactions, patient history\n3. Fill — mark dispensed, update stock\n\nTwo things I want to nail down: does the pharmacist need to message the prescribing doctor back through Foxglove if there's a dosage flag? And are we building for single-location pharmacies or chains with 2–3 stores?" },
|
||
];
|
||
|
||
const prdData = [
|
||
{ name: "Executive Summary", status: "done", pct: 100 },
|
||
{ name: "Problem Statement", status: "done", pct: 100 },
|
||
{ name: "Users & Personas", status: "done", pct: 100 },
|
||
{ name: "User Flows", status: "active", pct: 60 },
|
||
{ name: "Feature Requirements", status: "pending", pct: 20 },
|
||
{ name: "Screen Specs", status: "pending", pct: 0 },
|
||
{ name: "Business Model", status: "pending", pct: 0 },
|
||
{ name: "Non-Functional Reqs", status: "pending", pct: 0 },
|
||
{ name: "Risks", status: "pending", pct: 0 },
|
||
];
|
||
|
||
const discoveryPhases = [
|
||
{ name: "Big Picture", done: true },
|
||
{ name: "Users", done: true },
|
||
{ name: "Features", active: true },
|
||
{ name: "Business Model" },
|
||
{ name: "Screens" },
|
||
{ name: "Risks" },
|
||
];
|
||
|
||
/* ─── Micro Components ─── */
|
||
const Tag = ({ children, color = "#1a1a1a", bg = "#1a1a1a10" }) => (
|
||
<span style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "3px 9px", borderRadius: 4, fontSize: "0.68rem", fontWeight: 600, letterSpacing: "0.02em", color, background: bg, fontFamily: "Outfit" }}>{children}</span>
|
||
);
|
||
|
||
const StatusDot = ({ status }) => {
|
||
const c = status === "live" ? "#2e7d32" : status === "building" ? "#3d5afe" : "#d4a04a";
|
||
return <span style={{ width: 7, height: 7, borderRadius: "50%", background: c, display: "inline-block", flexShrink: 0, animation: status === "building" ? "breathe 2.5s ease infinite" : "none" }} />;
|
||
};
|
||
|
||
const SectionLabel = ({ children }) => (
|
||
<div style={{ fontSize: "0.6rem", fontWeight: 600, color: "#a09a90", letterSpacing: "0.1em", textTransform: "uppercase", marginBottom: 12 }}>{children}</div>
|
||
);
|
||
|
||
const Card = ({ children, style: s = {}, hover = true, ...rest }) => {
|
||
const [hovered, setHovered] = useState(false);
|
||
return (
|
||
<div
|
||
onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}
|
||
style={{ background: "#fff", border: `1px solid ${hovered && hover ? "#d0ccc4" : "#e8e4dc"}`, borderRadius: 10, boxShadow: hovered && hover ? "0 2px 8px #1a1a1a0a" : "0 1px 2px #1a1a1a05", transition: "all 0.15s", ...s }}
|
||
{...rest}
|
||
>{children}</div>
|
||
);
|
||
};
|
||
|
||
const Btn = ({ children, variant = "primary", style: s = {}, ...rest }) => {
|
||
const styles = variant === "primary"
|
||
? { background: "#1a1a1a", color: "#fff", border: "1px solid #1a1a1a" }
|
||
: variant === "secondary"
|
||
? { background: "#fff", color: "#1a1a1a", border: "1px solid #e0dcd4" }
|
||
: { background: "transparent", color: "#a09a90", border: "1px solid transparent" };
|
||
return (
|
||
<button style={{ padding: "8px 16px", borderRadius: 7, fontSize: "0.78rem", fontWeight: 600, transition: "opacity 0.15s", ...styles, ...s }}
|
||
onMouseEnter={e => e.currentTarget.style.opacity = "0.8"}
|
||
onMouseLeave={e => e.currentTarget.style.opacity = "1"}
|
||
{...rest}
|
||
>{children}</button>
|
||
);
|
||
};
|
||
|
||
const InputField = ({ label, value, onChange, placeholder, type = "text", mono = false }) => (
|
||
<div style={{ marginBottom: 16 }}>
|
||
<div style={{ fontSize: "0.72rem", fontWeight: 600, color: "#6b6560", marginBottom: 6 }}>{label}</div>
|
||
<input type={type} value={value} onChange={onChange} placeholder={placeholder}
|
||
style={{ width: "100%", padding: "9px 13px", borderRadius: 7, border: "1px solid #e0dcd4", background: "#faf8f5", fontSize: "0.84rem", fontFamily: mono ? "IBM Plex Mono" : "Outfit", color: "#1a1a1a" }} />
|
||
</div>
|
||
);
|
||
|
||
const Toggle = ({ on, onToggle, label }) => (
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 0", borderBottom: "1px solid #f0ece4" }}>
|
||
<span style={{ fontSize: "0.84rem", color: "#1a1a1a" }}>{label}</span>
|
||
<button onClick={onToggle} style={{
|
||
width: 38, height: 22, borderRadius: 11, border: "none", padding: 2,
|
||
background: on ? "#1a1a1a" : "#ddd8d0", transition: "background 0.2s",
|
||
display: "flex", alignItems: "center",
|
||
}}>
|
||
<div style={{ width: 18, height: 18, borderRadius: "50%", background: "#fff", transition: "transform 0.2s", transform: on ? "translateX(16px)" : "translateX(0)" }} />
|
||
</button>
|
||
</div>
|
||
);
|
||
|
||
/* ─── SIDEBAR ─── */
|
||
const Sidebar = ({ activeProject, setActiveProject, view, setView }) => (
|
||
<nav style={{ width: 220, height: "100vh", background: "#fff", borderRight: "1px solid #e8e4dc", display: "flex", flexDirection: "column", fontFamily: "Outfit, sans-serif", flexShrink: 0 }}>
|
||
<div style={{ padding: "22px 18px 18px", display: "flex", alignItems: "center", gap: 9 }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 7, background: "#1a1a1a", display: "flex", alignItems: "center", justifyContent: "center", color: "#fff", fontSize: "0.82rem", fontWeight: 700, fontFamily: "Newsreader, serif" }}>S</div>
|
||
<span style={{ fontSize: "0.95rem", fontWeight: 600, color: "#1a1a1a", letterSpacing: "-0.03em" }}>stackless</span>
|
||
</div>
|
||
|
||
<div style={{ padding: "4px 10px" }}>
|
||
{[
|
||
{ id: "home", label: "Projects", icon: "⌗" },
|
||
{ id: "activity", label: "Activity", icon: "↗" },
|
||
{ id: "settings", label: "Settings", icon: "⚙" },
|
||
].map(n => (
|
||
<button key={n.id}
|
||
onClick={() => { setView(n.id); setActiveProject(null); }}
|
||
style={{
|
||
width: "100%", display: "flex", alignItems: "center", gap: 9,
|
||
padding: "8px 10px", borderRadius: 6, border: "none",
|
||
background: view === n.id && !activeProject ? "#f6f4f0" : "transparent",
|
||
color: view === n.id && !activeProject ? "#1a1a1a" : "#6b6560",
|
||
fontSize: "0.82rem", fontWeight: view === n.id && !activeProject ? 600 : 500,
|
||
transition: "all 0.12s",
|
||
}}
|
||
onMouseEnter={e => e.currentTarget.style.background = "#f6f4f0"}
|
||
onMouseLeave={e => { if (!(view === n.id && !activeProject)) e.currentTarget.style.background = "transparent"; }}
|
||
>
|
||
<span style={{ fontSize: "0.8rem", opacity: 0.45, width: 18, textAlign: "center" }}>{n.icon}</span>
|
||
{n.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ height: 1, background: "#eae6de", margin: "10px 18px" }} />
|
||
|
||
<div style={{ padding: "2px 10px", flex: 1, overflow: "auto" }}>
|
||
<div style={{ fontSize: "0.6rem", fontWeight: 600, color: "#a09a90", letterSpacing: "0.1em", textTransform: "uppercase", padding: "6px 10px 8px" }}>Projects</div>
|
||
{projects.map(p => (
|
||
<button key={p.id}
|
||
onClick={() => { setActiveProject(p); setView("project"); }}
|
||
style={{
|
||
width: "100%", display: "flex", alignItems: "center", gap: 9,
|
||
padding: "7px 10px", borderRadius: 6, border: "none",
|
||
background: activeProject?.id === p.id ? "#f6f4f0" : "transparent",
|
||
color: "#1a1a1a", fontSize: "0.82rem", fontWeight: activeProject?.id === p.id ? 600 : 450,
|
||
transition: "background 0.12s", textAlign: "left",
|
||
}}
|
||
onMouseEnter={e => e.currentTarget.style.background = "#f6f4f0"}
|
||
onMouseLeave={e => { if (activeProject?.id !== p.id) e.currentTarget.style.background = "transparent"; }}
|
||
>
|
||
<StatusDot status={p.status} />
|
||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ padding: "14px 18px", borderTop: "1px solid #eae6de", display: "flex", alignItems: "center", gap: 9 }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: "50%", background: "#f0ece4", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.72rem", fontWeight: 600, color: "#8a8478" }}>M</div>
|
||
<div>
|
||
<div style={{ fontSize: "0.78rem", fontWeight: 500, color: "#1a1a1a" }}>Michael</div>
|
||
<div style={{ fontSize: "0.62rem", color: "#a09a90" }}>Pro plan</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
);
|
||
|
||
/* ─── ACTIVITY PAGE ─── */
|
||
const ActivityPage = ({ setActiveProject, setView }) => {
|
||
const [filter, setFilter] = useState("all");
|
||
const filtered = filter === "all" ? activityFeed : activityFeed.filter(a => a.type === filter);
|
||
const typeIcon = (t) => t === "atlas" ? "A" : t === "build" ? "⚡" : t === "deploy" ? "▲" : "●";
|
||
const typeColor = (t) => t === "atlas" ? "#1a1a1a" : t === "build" ? "#3d5afe" : t === "deploy" ? "#2e7d32" : "#8a8478";
|
||
|
||
return (
|
||
<div style={{ padding: "44px 52px", maxWidth: 720, fontFamily: "Outfit, sans-serif", animation: "enter 0.35s ease both" }}>
|
||
<h1 style={{ fontFamily: "Newsreader, serif", fontSize: "1.9rem", fontWeight: 400, color: "#1a1a1a", letterSpacing: "-0.03em", marginBottom: 4 }}>Activity</h1>
|
||
<p style={{ fontSize: "0.82rem", color: "#a09a90", marginBottom: 28 }}>Everything happening across your projects</p>
|
||
|
||
{/* Filters */}
|
||
<div style={{ display: "flex", gap: 4, marginBottom: 24 }}>
|
||
{[
|
||
{ id: "all", label: "All" },
|
||
{ id: "atlas", label: "Atlas" },
|
||
{ id: "build", label: "Builds" },
|
||
{ id: "deploy", label: "Deploys" },
|
||
{ id: "user", label: "You" },
|
||
].map(f => (
|
||
<button key={f.id} onClick={() => setFilter(f.id)}
|
||
style={{
|
||
padding: "6px 14px", borderRadius: 6, border: "none",
|
||
background: filter === f.id ? "#1a1a1a" : "#fff",
|
||
color: filter === f.id ? "#fff" : "#6b6560",
|
||
fontSize: "0.75rem", fontWeight: 600, transition: "all 0.12s",
|
||
}}>{f.label}</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Feed */}
|
||
<div style={{ position: "relative", paddingLeft: 24 }}>
|
||
{/* Timeline line */}
|
||
<div style={{ position: "absolute", left: 8, top: 8, bottom: 8, width: 1, background: "#e8e4dc" }} />
|
||
|
||
{filtered.map((item, i) => (
|
||
<div key={i} style={{
|
||
display: "flex", gap: 14, marginBottom: 4, padding: "12px 16px",
|
||
animation: `enter 0.3s ease ${i * 0.03}s both`,
|
||
borderRadius: 8, transition: "background 0.12s", position: "relative",
|
||
}}
|
||
onMouseEnter={e => e.currentTarget.style.background = "#fff"}
|
||
onMouseLeave={e => e.currentTarget.style.background = "transparent"}
|
||
>
|
||
{/* Dot on timeline */}
|
||
<div style={{
|
||
position: "absolute", left: -20, top: 18,
|
||
width: 9, height: 9, borderRadius: "50%",
|
||
background: typeColor(item.type), border: "2px solid #f6f4f0",
|
||
}} />
|
||
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 3 }}>
|
||
<button onClick={() => {
|
||
const proj = projects.find(p => p.name === item.project);
|
||
if (proj) { setActiveProject(proj); setView("project"); }
|
||
}} style={{
|
||
background: "none", border: "none", padding: 0,
|
||
fontSize: "0.82rem", fontWeight: 600, color: "#1a1a1a",
|
||
textDecoration: "none",
|
||
}}
|
||
onMouseEnter={e => e.currentTarget.style.textDecoration = "underline"}
|
||
onMouseLeave={e => e.currentTarget.style.textDecoration = "none"}
|
||
>{item.project}</button>
|
||
<span style={{ fontSize: "0.68rem", color: "#b5b0a6" }}>·</span>
|
||
<span style={{ fontSize: "0.72rem", color: "#b5b0a6" }}>{item.time}</span>
|
||
</div>
|
||
<div style={{ fontSize: "0.82rem", color: "#6b6560", lineHeight: 1.5 }}>{item.action}</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
/* ─── SETTINGS PAGE ─── */
|
||
const SettingsPage = () => {
|
||
const [settingsTab, setSettingsTab] = useState("account");
|
||
const [emailNotifs, setEmailNotifs] = useState(true);
|
||
const [buildNotifs, setBuildNotifs] = useState(true);
|
||
const [atlasDigest, setAtlasDigest] = useState(false);
|
||
const [darkMode, setDarkMode] = useState(false);
|
||
|
||
return (
|
||
<div style={{ display: "flex", height: "100vh", fontFamily: "Outfit, sans-serif", animation: "enter 0.35s ease both" }}>
|
||
{/* Settings nav */}
|
||
<div style={{ width: 200, padding: "44px 8px 44px 52px", flexShrink: 0 }}>
|
||
<h1 style={{ fontFamily: "Newsreader, serif", fontSize: "1.9rem", fontWeight: 400, color: "#1a1a1a", letterSpacing: "-0.03em", marginBottom: 28 }}>Settings</h1>
|
||
{[
|
||
{ id: "account", label: "Account" },
|
||
{ id: "notifications", label: "Notifications" },
|
||
{ id: "billing", label: "Plan & Billing" },
|
||
{ id: "team", label: "Team" },
|
||
{ id: "domains", label: "Domains" },
|
||
{ id: "api", label: "API Keys" },
|
||
{ id: "danger", label: "Danger Zone" },
|
||
].map(t => (
|
||
<button key={t.id} onClick={() => setSettingsTab(t.id)}
|
||
style={{
|
||
display: "block", width: "100%", textAlign: "left",
|
||
padding: "7px 12px", borderRadius: 6, border: "none",
|
||
background: settingsTab === t.id ? "#fff" : "transparent",
|
||
color: settingsTab === t.id ? "#1a1a1a" : "#8a8478",
|
||
fontSize: "0.82rem", fontWeight: settingsTab === t.id ? 600 : 450,
|
||
marginBottom: 2, transition: "all 0.12s",
|
||
boxShadow: settingsTab === t.id ? "0 1px 3px #1a1a1a08" : "none",
|
||
}}>{t.label}</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Settings content */}
|
||
<div style={{ flex: 1, padding: "44px 52px", overflow: "auto" }}>
|
||
{settingsTab === "account" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>Account</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Manage your profile and preferences</p>
|
||
|
||
<Card style={{ padding: "24px", marginBottom: 20 }} hover={false}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 16, marginBottom: 24 }}>
|
||
<div style={{ width: 52, height: 52, borderRadius: "50%", background: "#f0ece4", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "1.1rem", fontWeight: 600, color: "#8a8478" }}>M</div>
|
||
<div>
|
||
<div style={{ fontSize: "0.95rem", fontWeight: 600, color: "#1a1a1a" }}>Michael</div>
|
||
<div style={{ fontSize: "0.78rem", color: "#a09a90" }}>michael@example.com</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ marginLeft: "auto", padding: "6px 14px", fontSize: "0.72rem" }}>Change photo</Btn>
|
||
</div>
|
||
<InputField label="Full name" value="Michael" onChange={() => {}} />
|
||
<InputField label="Email" value="michael@example.com" onChange={() => {}} type="email" />
|
||
<InputField label="Company" value="" onChange={() => {}} placeholder="Optional" />
|
||
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 8 }}>
|
||
<Btn variant="primary" style={{ padding: "8px 20px" }}>Save changes</Btn>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card style={{ padding: "24px" }} hover={false}>
|
||
<h3 style={{ fontSize: "0.88rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>Preferences</h3>
|
||
<p style={{ fontSize: "0.75rem", color: "#a09a90", marginBottom: 12 }}>Customize your workspace</p>
|
||
<Toggle on={darkMode} onToggle={() => setDarkMode(!darkMode)} label="Dark mode" />
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 0", borderBottom: "1px solid #f0ece4" }}>
|
||
<span style={{ fontSize: "0.84rem", color: "#1a1a1a" }}>Default project view</span>
|
||
<select style={{ padding: "4px 10px", borderRadius: 5, border: "1px solid #e0dcd4", fontSize: "0.78rem", fontFamily: "Outfit", color: "#1a1a1a", background: "#faf8f5" }}>
|
||
<option>Chat</option>
|
||
<option>PRD</option>
|
||
<option>Build</option>
|
||
</select>
|
||
</div>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 0" }}>
|
||
<span style={{ fontSize: "0.84rem", color: "#1a1a1a" }}>Atlas personality</span>
|
||
<select style={{ padding: "4px 10px", borderRadius: 5, border: "1px solid #e0dcd4", fontSize: "0.78rem", fontFamily: "Outfit", color: "#1a1a1a", background: "#faf8f5" }}>
|
||
<option>Balanced</option>
|
||
<option>Concise</option>
|
||
<option>Thorough</option>
|
||
</select>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{settingsTab === "notifications" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>Notifications</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Choose what you hear about and when</p>
|
||
<Card style={{ padding: "24px" }} hover={false}>
|
||
<Toggle on={emailNotifs} onToggle={() => setEmailNotifs(!emailNotifs)} label="Email notifications" />
|
||
<Toggle on={buildNotifs} onToggle={() => setBuildNotifs(!buildNotifs)} label="Build completion alerts" />
|
||
<Toggle on={atlasDigest} onToggle={() => setAtlasDigest(!atlasDigest)} label="Daily Atlas digest" />
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 0" }}>
|
||
<span style={{ fontSize: "0.84rem", color: "#1a1a1a" }}>Notification frequency</span>
|
||
<select style={{ padding: "4px 10px", borderRadius: 5, border: "1px solid #e0dcd4", fontSize: "0.78rem", fontFamily: "Outfit", color: "#1a1a1a", background: "#faf8f5" }}>
|
||
<option>Real-time</option>
|
||
<option>Hourly digest</option>
|
||
<option>Daily digest</option>
|
||
</select>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{settingsTab === "billing" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>Plan & Billing</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Manage your subscription and usage</p>
|
||
|
||
<Card style={{ padding: "24px", marginBottom: 16 }} hover={false}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 18 }}>
|
||
<div>
|
||
<div style={{ fontSize: "0.62rem", fontWeight: 600, color: "#a09a90", letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: 4 }}>Current plan</div>
|
||
<div style={{ fontFamily: "Newsreader", fontSize: "1.4rem", color: "#1a1a1a" }}>Pro</div>
|
||
</div>
|
||
<Tag color="#2e7d32" bg="#2e7d3210">Active</Tag>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 24, paddingTop: 16, borderTop: "1px solid #f0ece4" }}>
|
||
{[
|
||
{ label: "Projects", value: "10", used: "4" },
|
||
{ label: "Builds/mo", value: "20", used: "3" },
|
||
{ label: "Deploys", value: "Unlimited", used: "—" },
|
||
].map((m, i) => (
|
||
<div key={i}>
|
||
<div style={{ fontSize: "0.62rem", color: "#b5b0a6", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 3 }}>{m.label}</div>
|
||
<div style={{ fontSize: "0.88rem", color: "#1a1a1a" }}>
|
||
<span style={{ fontFamily: "IBM Plex Mono", fontWeight: 500 }}>{m.used}</span>
|
||
<span style={{ color: "#b5b0a6" }}> / {m.value}</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
|
||
<Card style={{ padding: "20px", display: "flex", alignItems: "center", justifyContent: "space-between" }} hover={false}>
|
||
<div>
|
||
<div style={{ fontSize: "0.84rem", fontWeight: 500, color: "#1a1a1a" }}>Payment method</div>
|
||
<div style={{ fontSize: "0.78rem", color: "#a09a90", fontFamily: "IBM Plex Mono" }}>•••• 4242 — expires 08/27</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ padding: "6px 14px", fontSize: "0.72rem" }}>Update</Btn>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{settingsTab === "team" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>Team</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Manage collaborators and permissions</p>
|
||
<Card style={{ padding: "24px" }} hover={false}>
|
||
{[
|
||
{ name: "Michael", email: "michael@example.com", role: "Owner" },
|
||
{ name: "Craig F.", email: "craig@example.com", role: "Editor" },
|
||
].map((m, i) => (
|
||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 0", borderBottom: i === 0 ? "1px solid #f0ece4" : "none" }}>
|
||
<div style={{ width: 32, height: 32, borderRadius: "50%", background: "#f0ece4", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.72rem", fontWeight: 600, color: "#8a8478" }}>{m.name[0]}</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.84rem", fontWeight: 500, color: "#1a1a1a" }}>{m.name}</div>
|
||
<div style={{ fontSize: "0.72rem", color: "#a09a90" }}>{m.email}</div>
|
||
</div>
|
||
<Tag color="#6b6560" bg="#f0ece4">{m.role}</Tag>
|
||
</div>
|
||
))}
|
||
<div style={{ marginTop: 16 }}>
|
||
<Btn variant="secondary" style={{ width: "100%", fontSize: "0.78rem" }}>+ Invite team member</Btn>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{settingsTab === "domains" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>Domains</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Custom domains for your deployed projects</p>
|
||
<Card style={{ padding: "24px" }} hover={false}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 0", borderBottom: "1px solid #f0ece4" }}>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.84rem", fontFamily: "IBM Plex Mono", fontWeight: 500, color: "#1a1a1a" }}>kb.acmecorp.com</div>
|
||
<div style={{ fontSize: "0.72rem", color: "#a09a90" }}>→ Canopy</div>
|
||
</div>
|
||
<Tag color="#2e7d32" bg="#2e7d3210">Verified</Tag>
|
||
</div>
|
||
<div style={{ marginTop: 16 }}>
|
||
<Btn variant="secondary" style={{ width: "100%", fontSize: "0.78rem" }}>+ Add custom domain</Btn>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{settingsTab === "api" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", marginBottom: 4 }}>API Keys</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Access your projects programmatically</p>
|
||
<Card style={{ padding: "24px" }} hover={false}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 0", borderBottom: "1px solid #f0ece4" }}>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.82rem", fontWeight: 500, color: "#1a1a1a" }}>Production key</div>
|
||
<div style={{ fontSize: "0.78rem", fontFamily: "IBM Plex Mono", color: "#a09a90" }}>sk_live_••••••••••••3kF9</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ padding: "5px 12px", fontSize: "0.7rem" }}>Reveal</Btn>
|
||
</div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 0" }}>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.82rem", fontWeight: 500, color: "#1a1a1a" }}>Test key</div>
|
||
<div style={{ fontSize: "0.78rem", fontFamily: "IBM Plex Mono", color: "#a09a90" }}>sk_test_••••••••••••7mR2</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ padding: "5px 12px", fontSize: "0.7rem" }}>Reveal</Btn>
|
||
</div>
|
||
<div style={{ marginTop: 16 }}>
|
||
<Btn variant="secondary" style={{ width: "100%", fontSize: "0.78rem" }}>+ Generate new key</Btn>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{settingsTab === "danger" && (
|
||
<div style={{ maxWidth: 480, animation: "enter 0.25s ease" }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#d32f2f", marginBottom: 4 }}>Danger Zone</h2>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 28 }}>Irreversible actions</p>
|
||
<Card style={{ padding: "20px", borderColor: "#f5d5d5" }} hover={false}>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||
<div>
|
||
<div style={{ fontSize: "0.84rem", fontWeight: 500, color: "#1a1a1a" }}>Delete account</div>
|
||
<div style={{ fontSize: "0.75rem", color: "#a09a90" }}>Permanently remove your account and all projects</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ color: "#d32f2f", borderColor: "#f5d5d5", padding: "6px 14px", fontSize: "0.72rem" }}>Delete</Btn>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
/* ─── PROJECT DETAIL ─── */
|
||
const ProjectDetail = ({ project }) => {
|
||
const [tab, setTab] = useState("chat");
|
||
const [msgs, setMsgs] = useState(chatHistory);
|
||
const [input, setInput] = useState("");
|
||
const [typing, setTyping] = useState(false);
|
||
const endRef = useRef(null);
|
||
|
||
useEffect(() => { endRef.current?.scrollIntoView({ behavior: "smooth" }); }, [msgs, typing]);
|
||
|
||
const send = () => {
|
||
if (!input.trim()) return;
|
||
setMsgs(p => [...p, { from: "user", text: input }]);
|
||
setInput("");
|
||
setTyping(true);
|
||
setTimeout(() => {
|
||
setTyping(false);
|
||
setMsgs(p => [...p, { from: "atlas", text: "Good instinct. For multi-location, the core question is inventory: shared pool vs per-store tracking.\n\nOption A — Shared inventory. Simpler, but Store B can't trust the count if Store A just dispensed.\n\nOption B — Per-location with transfers. Accurate, adds a \"request stock\" workflow.\n\nMost independents start single-location and add multi-store as v2. Want to scope it that way?" }]);
|
||
}, 2000);
|
||
};
|
||
|
||
const prdPct = Math.round(prdData.reduce((a, s) => a + s.pct, 0) / prdData.length);
|
||
|
||
return (
|
||
<div style={{ display: "flex", height: "100vh", fontFamily: "Outfit, sans-serif", animation: "enter 0.3s ease" }}>
|
||
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
|
||
{/* Header */}
|
||
<div style={{ padding: "18px 32px", borderBottom: "1px solid #e8e4dc", display: "flex", alignItems: "center", justifyContent: "space-between", background: "#fff" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 14 }}>
|
||
<div style={{ width: 34, height: 34, borderRadius: 9, background: project.color + "12", display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||
<span style={{ fontFamily: "Newsreader", fontSize: "1rem", fontWeight: 500, color: project.color }}>{project.name[0]}</span>
|
||
</div>
|
||
<div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<h2 style={{ fontSize: "1.05rem", fontWeight: 600, color: "#1a1a1a", letterSpacing: "-0.02em" }}>{project.name}</h2>
|
||
<Tag color={project.status === "live" ? "#2e7d32" : project.status === "building" ? "#3d5afe" : "#9a7b3a"}
|
||
bg={project.status === "live" ? "#2e7d3210" : project.status === "building" ? "#3d5afe10" : "#d4a04a12"}>
|
||
{project.status === "live" ? "Live" : project.status === "building" ? "Building" : "Defining"}
|
||
</Tag>
|
||
</div>
|
||
<p style={{ fontSize: "0.75rem", color: "#a09a90", marginTop: 1 }}>{project.desc}</p>
|
||
</div>
|
||
</div>
|
||
<div style={{ fontFamily: "IBM Plex Mono", fontSize: "0.78rem", fontWeight: 500, color: "#1a1a1a", background: "#f6f4f0", padding: "6px 12px", borderRadius: 6 }}>
|
||
{project.progress}%
|
||
</div>
|
||
</div>
|
||
|
||
{/* Tabs */}
|
||
<div style={{ padding: "0 32px", borderBottom: "1px solid #e8e4dc", display: "flex", gap: 0, background: "#fff" }}>
|
||
{[
|
||
{ id: "chat", label: "Atlas" },
|
||
{ id: "prd", label: "PRD" },
|
||
{ id: "build", label: "Build" },
|
||
{ id: "deploy", label: "Deploy" },
|
||
{ id: "projsettings", label: "Settings" },
|
||
].map(t => (
|
||
<button key={t.id} onClick={() => setTab(t.id)} style={{
|
||
padding: "12px 18px", border: "none", background: "none",
|
||
fontSize: "0.8rem", fontWeight: 500, cursor: "pointer",
|
||
color: tab === t.id ? "#1a1a1a" : "#a09a90",
|
||
borderBottom: tab === t.id ? "2px solid #1a1a1a" : "2px solid transparent",
|
||
transition: "all 0.12s", fontFamily: "Outfit",
|
||
}}>{t.label}</button>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ flex: 1, overflow: "hidden", display: "flex", background: "#f6f4f0" }}>
|
||
|
||
{/* CHAT */}
|
||
{tab === "chat" && (
|
||
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
|
||
<div style={{ flex: 1, overflow: "auto", padding: "28px 32px" }}>
|
||
{msgs.map((m, i) => (
|
||
<div key={i} style={{ display: "flex", gap: 12, marginBottom: 22, animation: i >= chatHistory.length ? "enter 0.3s ease" : `enter 0.35s ease ${i * 0.08}s both` }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 7, flexShrink: 0, marginTop: 2, background: m.from === "atlas" ? "#1a1a1a" : "#e8e4dc", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.68rem", fontWeight: 700, color: m.from === "atlas" ? "#fff" : "#8a8478", fontFamily: m.from === "atlas" ? "Newsreader" : "Outfit" }}>
|
||
{m.from === "atlas" ? "A" : "M"}
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.68rem", fontWeight: 600, color: "#a09a90", marginBottom: 5, textTransform: "uppercase", letterSpacing: "0.04em" }}>{m.from === "atlas" ? "Atlas" : "You"}</div>
|
||
<div style={{ fontSize: "0.88rem", color: "#2a2824", lineHeight: 1.72, whiteSpace: "pre-wrap" }}>
|
||
{m.text.split(/(\*\*.*?\*\*)/).map((seg, j) => seg.startsWith("**") && seg.endsWith("**") ? <strong key={j} style={{ fontWeight: 600, color: "#1a1a1a" }}>{seg.slice(2, -2)}</strong> : seg)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
{typing && (
|
||
<div style={{ display: "flex", gap: 12, animation: "enter 0.2s ease" }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 7, background: "#1a1a1a", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.68rem", fontWeight: 700, color: "#fff", fontFamily: "Newsreader" }}>A</div>
|
||
<div style={{ display: "flex", gap: 5, paddingTop: 10 }}>
|
||
{[0,1,2].map(d => <div key={d} style={{ width: 5, height: 5, borderRadius: "50%", background: "#b5b0a6", animation: `blink 1s ease ${d * 0.15}s infinite` }} />)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div ref={endRef} />
|
||
</div>
|
||
<div style={{ padding: "14px 32px 22px" }}>
|
||
<div style={{ display: "flex", gap: 8, padding: "5px 5px 5px 16px", background: "#fff", border: "1px solid #e0dcd4", borderRadius: 10, alignItems: "center", boxShadow: "0 1px 4px #1a1a1a06" }}>
|
||
<input value={input} onChange={e => setInput(e.target.value)} onKeyDown={e => e.key === "Enter" && send()} placeholder="Describe your thinking..."
|
||
style={{ flex: 1, border: "none", background: "none", fontSize: "0.86rem", fontFamily: "Outfit", color: "#1a1a1a", padding: "8px 0" }} />
|
||
<button onClick={send} style={{ padding: "9px 16px", borderRadius: 7, border: "none", background: input.trim() ? "#1a1a1a" : "#eae6de", color: input.trim() ? "#fff" : "#b5b0a6", fontSize: "0.78rem", fontWeight: 600, transition: "all 0.15s" }}>Send</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* PRD */}
|
||
{tab === "prd" && (
|
||
<div style={{ flex: 1, overflow: "auto", padding: "28px 32px", animation: "enter 0.3s ease" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 16, padding: "16px 20px", background: "#fff", border: "1px solid #e8e4dc", borderRadius: 10, marginBottom: 20, boxShadow: "0 1px 2px #1a1a1a05" }}>
|
||
<div style={{ fontFamily: "IBM Plex Mono", fontSize: "1.4rem", fontWeight: 500, color: "#1a1a1a", minWidth: 48 }}>{prdPct}%</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ height: 4, borderRadius: 2, background: "#eae6de" }}>
|
||
<div style={{ height: "100%", borderRadius: 2, width: `${prdPct}%`, background: "#1a1a1a", transition: "width 0.6s ease" }} />
|
||
</div>
|
||
</div>
|
||
<span style={{ fontSize: "0.75rem", color: "#a09a90" }}>{prdData.filter(s => s.status === "done").length}/{prdData.length} approved</span>
|
||
</div>
|
||
{prdData.map((s, i) => (
|
||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 12, padding: "14px 18px", marginBottom: 4, background: "#fff", borderRadius: 8, border: "1px solid #e8e4dc", animation: `enter 0.3s ease ${i * 0.04}s both`, cursor: "pointer", transition: "border-color 0.12s" }}
|
||
onMouseEnter={e => e.currentTarget.style.borderColor = "#d0ccc4"}
|
||
onMouseLeave={e => e.currentTarget.style.borderColor = "#e8e4dc"}>
|
||
<div style={{ width: 24, height: 24, borderRadius: 6, flexShrink: 0, background: s.status === "done" ? "#2e7d3210" : s.status === "active" ? "#d4a04a12" : "#f6f4f0", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.65rem", fontWeight: 700, color: s.status === "done" ? "#2e7d32" : s.status === "active" ? "#9a7b3a" : "#c5c0b8" }}>
|
||
{s.status === "done" ? "✓" : s.status === "active" ? "◐" : "○"}
|
||
</div>
|
||
<span style={{ flex: 1, fontSize: "0.84rem", color: "#1a1a1a", fontWeight: 450 }}>{s.name}</span>
|
||
<div style={{ width: 60, height: 3, borderRadius: 2, background: "#eae6de" }}>
|
||
<div style={{ height: "100%", borderRadius: 2, width: `${s.pct}%`, background: s.status === "done" ? "#2e7d32" : s.status === "active" ? "#d4a04a" : "#d0ccc4" }} />
|
||
</div>
|
||
<span style={{ fontSize: "0.68rem", fontFamily: "IBM Plex Mono", color: s.status === "done" ? "#2e7d32" : "#a09a90", fontWeight: 500, minWidth: 28, textAlign: "right" }}>{s.pct}%</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* BUILD */}
|
||
{tab === "build" && (
|
||
<div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: 40, animation: "enter 0.3s ease" }}>
|
||
{project.status === "prd" ? (
|
||
<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 }}>Approve all sections with Atlas, then the builder unlocks automatically.</p>
|
||
<div style={{ display: "inline-flex", padding: "8px 16px", borderRadius: 7, background: "#fff", border: "1px solid #e0dcd4", fontSize: "0.78rem", color: "#6b6560", fontWeight: 500 }}>
|
||
<span style={{ fontFamily: "IBM Plex Mono", fontWeight: 600, color: "#1a1a1a", marginRight: 6 }}>{prdPct}%</span>complete
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div style={{ width: "100%", maxWidth: 500 }}>
|
||
<h3 style={{ fontFamily: "Newsreader", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", marginBottom: 18 }}>Build progress</h3>
|
||
{["Auth System", "Database", "Dashboard UI", "Rx Queue", "Inventory", "API"].map((f, i) => {
|
||
const pct = Math.max(0, Math.min(100, project.progress + (i * -12)));
|
||
return (
|
||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 16px", marginBottom: 4, borderRadius: 8, background: "#fff", border: "1px solid #e8e4dc", animation: `enter 0.3s ease ${i * 0.05}s both` }}>
|
||
<StatusDot status={pct >= 100 ? "live" : pct > 0 ? "building" : "prd"} />
|
||
<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: `${pct}%`, borderRadius: 2, background: pct >= 100 ? "#2e7d32" : "#3d5afe" }} />
|
||
</div>
|
||
<span style={{ fontFamily: "IBM Plex Mono", fontSize: "0.7rem", color: "#a09a90", minWidth: 28, textAlign: "right" }}>{pct}%</span>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* DEPLOY */}
|
||
{tab === "deploy" && (
|
||
<div style={{ flex: 1, overflow: "auto", padding: "28px 32px", animation: "enter 0.3s ease" }}>
|
||
<div style={{ maxWidth: 560 }}>
|
||
<h3 style={{ fontFamily: "Newsreader", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", marginBottom: 4 }}>Deployment</h3>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 24 }}>Links, environments, and hosting for {project.name}</p>
|
||
|
||
{/* URLs card */}
|
||
<Card style={{ padding: "22px", marginBottom: 12 }} hover={false}>
|
||
<SectionLabel>Project URLs</SectionLabel>
|
||
{project.domain ? (
|
||
<>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 0", borderBottom: "1px solid #f0ece4" }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 6, background: "#f6f4f0", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.7rem", color: "#8a8478" }}>▲</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.68rem", color: "#b5b0a6", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.04em" }}>Staging</div>
|
||
<div style={{ fontSize: "0.84rem", fontFamily: "IBM Plex Mono", color: "#3d5afe", fontWeight: 500 }}>{project.domain}</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ padding: "5px 12px", fontSize: "0.7rem" }}>Open ↗</Btn>
|
||
</div>
|
||
{project.customDomain && (
|
||
<div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 0", borderBottom: "1px solid #f0ece4" }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 6, background: "#2e7d3210", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.7rem", color: "#2e7d32" }}>●</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.68rem", color: "#b5b0a6", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.04em" }}>Production</div>
|
||
<div style={{ fontSize: "0.84rem", fontFamily: "IBM Plex Mono", color: "#2e7d32", fontWeight: 500 }}>{project.customDomain}</div>
|
||
</div>
|
||
<Tag color="#2e7d32" bg="#2e7d3210">SSL Active</Tag>
|
||
<Btn variant="secondary" style={{ padding: "5px 12px", fontSize: "0.7rem" }}>Open ↗</Btn>
|
||
</div>
|
||
)}
|
||
<div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 0" }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 6, background: "#f6f4f0", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.7rem", color: "#8a8478" }}>⚙</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: "0.68rem", color: "#b5b0a6", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.04em" }}>Build repo</div>
|
||
<div style={{ fontSize: "0.84rem", fontFamily: "IBM Plex Mono", color: "#6b6560", fontWeight: 500 }}>{project.repo}</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ padding: "5px 12px", fontSize: "0.7rem" }}>View ↗</Btn>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div style={{ padding: "18px 0", textAlign: "center" }}>
|
||
<p style={{ fontSize: "0.82rem", color: "#a09a90", marginBottom: 12 }}>No deployment yet — complete your PRD and build to get a live URL.</p>
|
||
<div style={{ display: "inline-flex", padding: "6px 14px", borderRadius: 6, background: "#f6f4f0", fontSize: "0.75rem", color: "#8a8478" }}>
|
||
<span style={{ fontFamily: "IBM Plex Mono", marginRight: 6 }}>{project.progress}%</span> to deployment
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
|
||
{/* Custom domain */}
|
||
{project.domain && !project.customDomain && (
|
||
<Card style={{ padding: "22px", marginBottom: 12 }} hover={false}>
|
||
<SectionLabel>Custom Domain</SectionLabel>
|
||
<p style={{ fontSize: "0.82rem", color: "#6b6560", lineHeight: 1.6, marginBottom: 14 }}>
|
||
Point your own domain to this project. We'll handle SSL certificates automatically.
|
||
</p>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<input placeholder="app.yourdomain.com" style={{ flex: 1, padding: "9px 13px", borderRadius: 7, border: "1px solid #e0dcd4", background: "#faf8f5", fontSize: "0.84rem", fontFamily: "IBM Plex Mono", color: "#1a1a1a" }} />
|
||
<Btn variant="primary" style={{ padding: "9px 18px" }}>Connect</Btn>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Environment vars */}
|
||
<Card style={{ padding: "22px", marginBottom: 12 }} hover={false}>
|
||
<SectionLabel>Environment Variables</SectionLabel>
|
||
{project.domain ? (
|
||
<>
|
||
{[
|
||
{ key: "DATABASE_URL", val: "••••••••••••" },
|
||
{ key: "API_SECRET", val: "••••••••••••" },
|
||
{ key: "SMTP_HOST", val: "mail.stackless.dev" },
|
||
].map((env, i) => (
|
||
<div key={i} style={{ display: "flex", gap: 10, padding: "8px 0", borderBottom: i < 2 ? "1px solid #f0ece4" : "none", alignItems: "center" }}>
|
||
<span style={{ fontFamily: "IBM Plex Mono", fontSize: "0.78rem", fontWeight: 500, color: "#1a1a1a", minWidth: 130 }}>{env.key}</span>
|
||
<span style={{ fontFamily: "IBM Plex Mono", fontSize: "0.78rem", color: "#a09a90", flex: 1 }}>{env.val}</span>
|
||
<button style={{ background: "none", border: "none", fontSize: "0.7rem", color: "#a09a90", padding: "2px 6px" }}>Edit</button>
|
||
</div>
|
||
))}
|
||
<div style={{ marginTop: 14 }}>
|
||
<Btn variant="secondary" style={{ fontSize: "0.75rem", padding: "7px 14px" }}>+ Add variable</Btn>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<p style={{ fontSize: "0.82rem", color: "#a09a90", padding: "10px 0" }}>Available after first build completes.</p>
|
||
)}
|
||
</Card>
|
||
|
||
{/* Deploy history */}
|
||
<Card style={{ padding: "22px" }} hover={false}>
|
||
<SectionLabel>Deploy History</SectionLabel>
|
||
{project.status === "live" ? (
|
||
<>
|
||
{[
|
||
{ version: "v1.2", time: "1 day ago", status: "live", note: "Added search filters" },
|
||
{ version: "v1.1", time: "5 days ago", status: "previous", note: "Bug fix: auth timeout" },
|
||
{ version: "v1.0", time: "2 weeks ago", status: "previous", note: "Initial launch" },
|
||
].map((d, i) => (
|
||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 0", borderBottom: i < 2 ? "1px solid #f0ece4" : "none" }}>
|
||
<span style={{ fontFamily: "IBM Plex Mono", fontSize: "0.78rem", fontWeight: 600, color: "#1a1a1a", minWidth: 36 }}>{d.version}</span>
|
||
<span style={{ flex: 1, fontSize: "0.82rem", color: "#6b6560" }}>{d.note}</span>
|
||
<span style={{ fontSize: "0.72rem", color: "#b5b0a6" }}>{d.time}</span>
|
||
{d.status === "live" && <Tag color="#2e7d32" bg="#2e7d3210">Current</Tag>}
|
||
</div>
|
||
))}
|
||
</>
|
||
) : project.domain ? (
|
||
<p style={{ fontSize: "0.82rem", color: "#a09a90", padding: "10px 0" }}>First deploy will appear here once the build completes.</p>
|
||
) : (
|
||
<p style={{ fontSize: "0.82rem", color: "#a09a90", padding: "10px 0" }}>No deploys yet.</p>
|
||
)}
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* PROJECT SETTINGS */}
|
||
{tab === "projsettings" && (
|
||
<div style={{ flex: 1, overflow: "auto", padding: "28px 32px", animation: "enter 0.3s ease" }}>
|
||
<div style={{ maxWidth: 480 }}>
|
||
<h3 style={{ fontFamily: "Newsreader", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", marginBottom: 4 }}>Project Settings</h3>
|
||
<p style={{ fontSize: "0.8rem", color: "#a09a90", marginBottom: 24 }}>Configure {project.name}</p>
|
||
|
||
<Card style={{ padding: "22px", marginBottom: 12 }} hover={false}>
|
||
<SectionLabel>General</SectionLabel>
|
||
<InputField label="Project name" value={project.name} onChange={() => {}} />
|
||
<div style={{ marginBottom: 16 }}>
|
||
<div style={{ fontSize: "0.72rem", fontWeight: 600, color: "#6b6560", marginBottom: 6 }}>Description</div>
|
||
<textarea value={project.desc} onChange={() => {}} rows={2}
|
||
style={{ width: "100%", padding: "9px 13px", borderRadius: 7, border: "1px solid #e0dcd4", background: "#faf8f5", fontSize: "0.84rem", color: "#1a1a1a", fontFamily: "Outfit" }} />
|
||
</div>
|
||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||
<Btn variant="primary" style={{ padding: "8px 20px" }}>Save</Btn>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card style={{ padding: "22px", marginBottom: 12 }} hover={false}>
|
||
<SectionLabel>Collaborators</SectionLabel>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 0" }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: "50%", background: "#f0ece4", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.68rem", fontWeight: 600, color: "#8a8478" }}>M</div>
|
||
<span style={{ flex: 1, fontSize: "0.82rem", color: "#1a1a1a" }}>Michael</span>
|
||
<Tag color="#6b6560" bg="#f0ece4">Owner</Tag>
|
||
</div>
|
||
<Btn variant="secondary" style={{ width: "100%", marginTop: 12, fontSize: "0.75rem" }}>+ Invite to project</Btn>
|
||
</Card>
|
||
|
||
<Card style={{ padding: "22px", marginBottom: 12 }} hover={false}>
|
||
<SectionLabel>Export</SectionLabel>
|
||
<p style={{ fontSize: "0.82rem", color: "#6b6560", marginBottom: 14, lineHeight: 1.6 }}>Download your PRD or project data for external use.</p>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<Btn variant="secondary" style={{ fontSize: "0.75rem" }}>Export PRD as PDF</Btn>
|
||
<Btn variant="secondary" style={{ fontSize: "0.75rem" }}>Export as JSON</Btn>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card style={{ padding: "20px", borderColor: "#f5d5d5" }} hover={false}>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||
<div>
|
||
<div style={{ fontSize: "0.84rem", fontWeight: 500, color: "#d32f2f" }}>Delete project</div>
|
||
<div style={{ fontSize: "0.75rem", color: "#a09a90" }}>This action cannot be undone</div>
|
||
</div>
|
||
<Btn variant="secondary" style={{ color: "#d32f2f", borderColor: "#f5d5d5", padding: "6px 14px", fontSize: "0.72rem" }}>Delete</Btn>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Right panel */}
|
||
<div style={{ width: 230, borderLeft: "1px solid #e8e4dc", background: "#fff", padding: "22px 18px", overflow: "auto", flexShrink: 0 }}>
|
||
<SectionLabel>Discovery</SectionLabel>
|
||
{discoveryPhases.map((ph, i) => (
|
||
<div key={i} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 0", borderBottom: i < discoveryPhases.length - 1 ? "1px solid #f0ece4" : "none" }}>
|
||
<div style={{ width: 20, height: 20, borderRadius: 5, flexShrink: 0, background: ph.done ? "#2e7d3210" : ph.active ? "#d4a04a12" : "#f6f4f0", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.58rem", fontWeight: 700, color: ph.done ? "#2e7d32" : ph.active ? "#9a7b3a" : "#c5c0b8" }}>
|
||
{ph.done ? "✓" : ph.active ? "→" : i + 1}
|
||
</div>
|
||
<span style={{ fontSize: "0.78rem", fontWeight: ph.active ? 600 : 400, color: ph.done ? "#6b6560" : ph.active ? "#1a1a1a" : "#b5b0a6" }}>{ph.name}</span>
|
||
</div>
|
||
))}
|
||
|
||
<div style={{ height: 1, background: "#f0ece4", margin: "16px 0" }} />
|
||
|
||
<SectionLabel>Captured</SectionLabel>
|
||
{[
|
||
{ k: "Users", v: "Pharmacist" },
|
||
{ k: "Workflow", v: "Receive → Verify → Fill" },
|
||
{ k: "Scope", v: "Single-location MVP" },
|
||
{ k: "Open", v: "Multi-location support?" },
|
||
].map((item, i) => (
|
||
<div key={i} style={{ marginBottom: 14 }}>
|
||
<div style={{ fontSize: "0.62rem", color: "#b5b0a6", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 3, fontWeight: 600 }}>{item.k}</div>
|
||
<div style={{ fontSize: "0.8rem", color: "#4a4640", lineHeight: 1.45 }}>{item.v}</div>
|
||
</div>
|
||
))}
|
||
|
||
<div style={{ height: 1, background: "#f0ece4", margin: "16px 0" }} />
|
||
|
||
<SectionLabel>Project Info</SectionLabel>
|
||
{[
|
||
{ k: "Created", v: project.created },
|
||
{ k: "Last active", v: project.lastActive },
|
||
{ k: "Features", v: `${project.features} defined` },
|
||
].map((item, i) => (
|
||
<div key={i} style={{ marginBottom: 12 }}>
|
||
<div style={{ fontSize: "0.62rem", color: "#b5b0a6", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 3, fontWeight: 600 }}>{item.k}</div>
|
||
<div style={{ fontSize: "0.8rem", color: "#4a4640" }}>{item.v}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
/* ─── DASHBOARD ─── */
|
||
const Home = ({ setActiveProject, setView }) => {
|
||
const [showNew, setShowNew] = useState(false);
|
||
|
||
return (
|
||
<div style={{ padding: "44px 52px", maxWidth: 900, fontFamily: "Outfit, sans-serif", animation: "enter 0.35s ease both" }}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: 36 }}>
|
||
<div>
|
||
<h1 style={{ fontFamily: "Newsreader, serif", fontSize: "1.9rem", fontWeight: 400, color: "#1a1a1a", letterSpacing: "-0.03em", lineHeight: 1.15, marginBottom: 4 }}>Projects</h1>
|
||
<p style={{ fontSize: "0.82rem", color: "#a09a90" }}>4 total · 2 in definition · 1 building · 1 live</p>
|
||
</div>
|
||
<Btn variant="primary" onClick={() => setShowNew(!showNew)} style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
||
<span style={{ fontSize: "1rem", lineHeight: 1, fontWeight: 300 }}>+</span> New project
|
||
</Btn>
|
||
</div>
|
||
|
||
{showNew && (
|
||
<Card style={{ padding: "22px 26px", marginBottom: 28, animation: "enter 0.25s ease" }} hover={false}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 14 }}>
|
||
<div style={{ width: 28, height: 28, borderRadius: 7, background: "#1a1a1a", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "0.72rem", fontWeight: 700, color: "#fff", fontFamily: "Newsreader" }}>A</div>
|
||
<div>
|
||
<span style={{ fontSize: "0.82rem", fontWeight: 600, color: "#1a1a1a" }}>Atlas</span>
|
||
<span style={{ fontSize: "0.72rem", color: "#a09a90", marginLeft: 8 }}>product strategist</span>
|
||
</div>
|
||
</div>
|
||
<p style={{ fontSize: "0.84rem", color: "#6b6560", lineHeight: 1.6, marginBottom: 16 }}>Tell me what you want to build. One sentence is fine — I'll ask the right questions to figure out the rest.</p>
|
||
<div style={{ display: "flex", gap: 8 }}>
|
||
<input placeholder="e.g. A scheduling tool for independent yoga instructors..." style={{ flex: 1, padding: "10px 14px", borderRadius: 7, border: "1px solid #e0dcd4", background: "#faf8f5", fontSize: "0.84rem", fontFamily: "Outfit", color: "#1a1a1a" }} />
|
||
<Btn variant="primary">Start →</Btn>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||
{projects.map((p, i) => (
|
||
<button key={p.id} onClick={() => { setActiveProject(p); setView("project"); }}
|
||
style={{
|
||
width: "100%", display: "flex", alignItems: "center", padding: "18px 22px", borderRadius: 10,
|
||
background: "#fff", border: "1px solid #e8e4dc", cursor: "pointer", fontFamily: "Outfit",
|
||
transition: "all 0.15s", textAlign: "left", animation: `enter 0.35s ease ${i * 0.05}s both`,
|
||
boxShadow: "0 1px 2px #1a1a1a05",
|
||
}}
|
||
onMouseEnter={e => { e.currentTarget.style.borderColor = "#d0ccc4"; e.currentTarget.style.boxShadow = "0 2px 8px #1a1a1a0a"; }}
|
||
onMouseLeave={e => { e.currentTarget.style.borderColor = "#e8e4dc"; e.currentTarget.style.boxShadow = "0 1px 2px #1a1a1a05"; }}
|
||
>
|
||
<div style={{ width: 36, height: 36, borderRadius: 9, marginRight: 16, background: p.color + "12", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
|
||
<span style={{ fontFamily: "Newsreader, serif", fontSize: "1.05rem", fontWeight: 500, color: p.color }}>{p.name[0]}</span>
|
||
</div>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 2 }}>
|
||
<span style={{ fontSize: "0.9rem", fontWeight: 600, color: "#1a1a1a" }}>{p.name}</span>
|
||
<Tag color={p.status === "live" ? "#2e7d32" : p.status === "building" ? "#3d5afe" : "#9a7b3a"} bg={p.status === "live" ? "#2e7d3210" : p.status === "building" ? "#3d5afe10" : "#d4a04a12"}>
|
||
<StatusDot status={p.status} /> {p.status === "live" ? "Live" : p.status === "building" ? "Building" : "Defining"}
|
||
</Tag>
|
||
</div>
|
||
<span style={{ fontSize: "0.78rem", color: "#a09a90" }}>{p.desc}</span>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 28, alignItems: "center", flexShrink: 0 }}>
|
||
<div style={{ textAlign: "right" }}>
|
||
<div style={{ fontSize: "0.62rem", color: "#b5b0a6", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 2 }}>Phase</div>
|
||
<div style={{ fontSize: "0.78rem", color: "#6b6560" }}>{p.phase}</div>
|
||
</div>
|
||
<div style={{ textAlign: "right" }}>
|
||
<div style={{ fontSize: "0.62rem", color: "#b5b0a6", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 2 }}>Features</div>
|
||
<div style={{ fontSize: "0.78rem", color: "#6b6560" }}>{p.features}</div>
|
||
</div>
|
||
<div style={{ textAlign: "right", minWidth: 40 }}>
|
||
<div style={{ fontSize: "0.62rem", color: "#b5b0a6", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 2 }}>Progress</div>
|
||
<div style={{ fontSize: "0.78rem", color: "#1a1a1a", fontWeight: 600, fontFamily: "IBM Plex Mono" }}>{p.progress}%</div>
|
||
</div>
|
||
<div style={{ width: 60, height: 3, borderRadius: 2, background: "#eae6de", flexShrink: 0 }}>
|
||
<div style={{ height: "100%", borderRadius: 2, width: `${p.progress}%`, background: p.status === "live" ? "#2e7d32" : p.status === "building" ? "#3d5afe" : "#d4a04a", transition: "width 0.6s ease" }} />
|
||
</div>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
/* ─── ROOT ─── */
|
||
export default function Stackless() {
|
||
const [view, setView] = useState("home");
|
||
const [activeProject, setActiveProject] = useState(null);
|
||
|
||
return (
|
||
<div style={{ display: "flex", height: "100vh", background: "#f6f4f0", overflow: "hidden" }}>
|
||
<FontLoader />
|
||
<Sidebar activeProject={activeProject} setActiveProject={setActiveProject} view={view} setView={setView} />
|
||
<div style={{ flex: 1, overflow: "auto" }}>
|
||
{view === "home" && !activeProject && <Home setActiveProject={setActiveProject} setView={setView} />}
|
||
{view === "activity" && <ActivityPage setActiveProject={setActiveProject} setView={setView} />}
|
||
{view === "settings" && <SettingsPage />}
|
||
{activeProject && <ProjectDetail project={activeProject} />}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|