mirror of
https://github.com/Narcooo/inkos.git
synced 2026-09-01 15:08:51 +08:00
style(studio): fix spacing — generous padding, centered content, breathing room
Sidebar: 200px→220px, generous logo area (pt-7 pb-6), wider item padding Main area: max-w-4xl→max-w-3xl, px-6→px-10, py-8→py-12 Empty state: min-h-[60vh] vertical centering, larger ornament spacing Chat bar: centered to max-w-3xl matching content, py-2.5→py-3.5 Header strip: px-4→px-8, h-11→h-12 Book cards: px-5→px-6, py-4→py-5 All borders softened to /20-/30 opacity for subtlety
This commit is contained in:
@@ -1 +0,0 @@
|
||||
{"type":"server-started","port":60485,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:60485","screen_dir":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943"}
|
||||
@@ -0,0 +1 @@
|
||||
{"reason":"owner process exited","timestamp":1773820583908}
|
||||
@@ -1,3 +1,4 @@
|
||||
{"type":"server-started","port":60485,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:60485","screen_dir":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943"}
|
||||
{"type":"screen-added","file":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943/layout-concept.html"}
|
||||
{"type":"screen-added","file":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943/layout-refined.html"}
|
||||
{"type":"server-stopped","reason":"owner process exited"}
|
||||
|
||||
@@ -41,12 +41,9 @@ export function App() {
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
}, [isDark]);
|
||||
|
||||
// Check if language has been set
|
||||
useEffect(() => {
|
||||
if (project) {
|
||||
setLanguageSet(!!project.language && project.language !== "zh" ? true : true);
|
||||
// For now, always consider language "set" — the selector is for first-time experience
|
||||
// In a real implementation, we'd check a "hasCompletedSetup" flag
|
||||
setLanguageSet(true);
|
||||
}
|
||||
}, [project]);
|
||||
|
||||
@@ -63,42 +60,38 @@ export function App() {
|
||||
toLogs: () => setRoute({ page: "logs" }),
|
||||
};
|
||||
|
||||
// Derive active page for sidebar highlighting
|
||||
const activePage =
|
||||
route.page === "book" || route.page === "chapter" || route.page === "truth" || route.page === "analytics"
|
||||
? `book:${(route as { bookId: string }).bookId}`
|
||||
: route.page;
|
||||
|
||||
// Language selector for first-time setup
|
||||
if (languageSet === null) {
|
||||
return <div className="min-h-screen bg-background" />; // Loading
|
||||
return <div className="min-h-screen bg-background" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-background text-foreground flex overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<Sidebar nav={nav} activePage={activePage} t={t} />
|
||||
|
||||
{/* Main area + Chat bar */}
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
{/* Header strip */}
|
||||
<div className="h-11 shrink-0 border-b border-border/30 flex items-center justify-end px-4 gap-3">
|
||||
{/* Thin utility strip — pushed inward */}
|
||||
<div className="h-12 shrink-0 border-b border-border/20 flex items-center justify-end px-8 gap-4">
|
||||
<button
|
||||
onClick={() => setTheme(isDark ? "light" : "dark")}
|
||||
className="w-7 h-7 flex items-center justify-center rounded-full bg-secondary/50 text-muted-foreground hover:text-foreground transition-colors text-xs"
|
||||
className="w-7 h-7 flex items-center justify-center rounded-full bg-secondary/40 text-muted-foreground/60 hover:text-foreground hover:bg-secondary transition-all text-xs"
|
||||
>
|
||||
{isDark ? "☀" : "☽"}
|
||||
</button>
|
||||
{sse.connected && (
|
||||
<span className="text-[10px] text-muted-foreground/40 uppercase tracking-wider">
|
||||
<span className="text-[10px] text-muted-foreground/30 uppercase tracking-[0.15em]">
|
||||
{t("nav.connected")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Scrollable main content */}
|
||||
{/* Scrollable main — centered with generous inset */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="max-w-4xl mx-auto px-6 py-8">
|
||||
<div className="max-w-3xl mx-auto px-10 py-12">
|
||||
{route.page === "dashboard" && <Dashboard nav={nav} sse={sse} theme={theme} t={t} />}
|
||||
{route.page === "book" && <BookDetail bookId={route.bookId} nav={nav} theme={theme} t={t} />}
|
||||
{route.page === "book-create" && <BookCreate nav={nav} theme={theme} t={t} />}
|
||||
@@ -111,7 +104,7 @@ export function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat bar - always at bottom */}
|
||||
{/* Chat bar — inset to match content width */}
|
||||
<ChatBar t={t} sse={sse} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -118,38 +118,40 @@ export function ChatBar({ t, sse }: {
|
||||
: "Tell InkOS what to do...";
|
||||
|
||||
return (
|
||||
<div className="border-t border-border/40 bg-card/80 backdrop-blur-sm">
|
||||
<div className="border-t border-border/20 bg-card/60 backdrop-blur-md">
|
||||
{/* Expanded message area */}
|
||||
{expanded && messages.length > 0 && (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="max-h-[200px] overflow-y-auto px-4 py-3 space-y-2 border-b border-border/20"
|
||||
>
|
||||
{messages.map((msg, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-[13px] leading-relaxed ${
|
||||
msg.role === "user"
|
||||
? "text-foreground"
|
||||
: msg.content.startsWith("✗")
|
||||
? "text-destructive"
|
||||
: msg.content.startsWith("⋯")
|
||||
? "text-muted-foreground"
|
||||
: "text-primary"
|
||||
}`}
|
||||
>
|
||||
{msg.role === "user" && <span className="text-muted-foreground/50 mr-1.5">›</span>}
|
||||
{msg.content}
|
||||
</div>
|
||||
))}
|
||||
{loading && !messages.some((m) => m.content.startsWith("⋯")) && (
|
||||
<div className="text-[13px] text-muted-foreground animate-pulse">⋯</div>
|
||||
)}
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="max-h-[200px] overflow-y-auto px-10 py-4 space-y-2.5"
|
||||
>
|
||||
{messages.map((msg, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`text-[13px] leading-relaxed ${
|
||||
msg.role === "user"
|
||||
? "text-foreground"
|
||||
: msg.content.startsWith("✗")
|
||||
? "text-destructive"
|
||||
: msg.content.startsWith("⋯")
|
||||
? "text-muted-foreground"
|
||||
: "text-primary"
|
||||
}`}
|
||||
>
|
||||
{msg.role === "user" && <span className="text-muted-foreground/40 mr-2">›</span>}
|
||||
{msg.content}
|
||||
</div>
|
||||
))}
|
||||
{loading && !messages.some((m) => m.content.startsWith("⋯")) && (
|
||||
<div className="text-[13px] text-muted-foreground animate-pulse">⋯</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input bar */}
|
||||
<div className="px-4 py-2.5 flex items-center gap-3">
|
||||
{/* Input bar — centered to match main content */}
|
||||
<div className="max-w-3xl mx-auto px-10 py-3.5 flex items-center gap-4">
|
||||
<div className="flex-1 relative">
|
||||
<input
|
||||
ref={inputRef}
|
||||
@@ -160,14 +162,14 @@ export function ChatBar({ t, sse }: {
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
|
||||
placeholder={chatPlaceholder}
|
||||
disabled={loading}
|
||||
className="w-full bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground/30 focus:outline-none disabled:opacity-50"
|
||||
className="w-full bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground/25 focus:outline-none disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className="flex items-center gap-3 shrink-0">
|
||||
{expanded && messages.length > 0 && (
|
||||
<button
|
||||
onClick={() => { setExpanded(false); setMessages([]); }}
|
||||
className="text-[10px] text-muted-foreground/40 hover:text-muted-foreground transition-colors"
|
||||
className="text-[10px] text-muted-foreground/30 hover:text-muted-foreground transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -175,7 +177,7 @@ export function ChatBar({ t, sse }: {
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={!input.trim() || loading}
|
||||
className="w-6 h-6 rounded bg-primary/80 text-primary-foreground flex items-center justify-center text-xs hover:bg-primary transition-colors disabled:opacity-20"
|
||||
className="w-7 h-7 rounded-md bg-primary/70 text-primary-foreground flex items-center justify-center text-xs hover:bg-primary transition-all disabled:opacity-15"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
|
||||
@@ -27,36 +27,36 @@ export function Sidebar({ nav, activePage, t }: {
|
||||
const { data: daemon } = useApi<{ running: boolean }>("/daemon");
|
||||
|
||||
return (
|
||||
<aside className="w-[200px] shrink-0 border-r border-border/40 bg-card/50 flex flex-col h-full overflow-y-auto">
|
||||
{/* Logo */}
|
||||
<div className="px-4 py-5 border-b border-border/30">
|
||||
<button onClick={nav.toDashboard} className="flex items-baseline gap-0.5 hover:opacity-80 transition-opacity">
|
||||
<span className="font-serif text-xl italic text-primary">Ink</span>
|
||||
<span className="text-base font-semibold tracking-tight">OS</span>
|
||||
<aside className="w-[220px] shrink-0 border-r border-border/30 bg-card/30 flex flex-col h-full overflow-y-auto">
|
||||
{/* Logo — generous vertical breathing room */}
|
||||
<div className="px-6 pt-7 pb-6">
|
||||
<button onClick={nav.toDashboard} className="flex items-baseline gap-0.5 hover:opacity-70 transition-opacity">
|
||||
<span className="font-serif text-2xl italic text-primary">Ink</span>
|
||||
<span className="text-lg font-semibold tracking-tight">OS</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Books section */}
|
||||
<div className="flex-1 py-3">
|
||||
<div className="px-4 mb-2 flex items-center justify-between">
|
||||
<span className="text-[10px] uppercase tracking-widest text-muted-foreground/50">{t("nav.books")}</span>
|
||||
<div className="flex-1 px-4">
|
||||
<div className="px-2 mb-3 flex items-center justify-between">
|
||||
<span className="text-[10px] uppercase tracking-[0.15em] text-muted-foreground/40 font-medium">{t("nav.books")}</span>
|
||||
<button
|
||||
onClick={nav.toBookCreate}
|
||||
className="text-[10px] text-primary/60 hover:text-primary transition-colors"
|
||||
className="w-5 h-5 flex items-center justify-center rounded text-[11px] text-muted-foreground/40 hover:text-primary hover:bg-primary/10 transition-all"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-0.5 px-2">
|
||||
<div className="space-y-1">
|
||||
{data?.books.map((book) => (
|
||||
<button
|
||||
key={book.id}
|
||||
onClick={() => nav.toBook(book.id)}
|
||||
className={`w-full text-left px-2 py-1.5 rounded text-[13px] truncate transition-colors ${
|
||||
className={`w-full text-left px-3 py-2 rounded-md text-[13px] truncate transition-all duration-150 ${
|
||||
activePage === `book:${book.id}`
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/30"
|
||||
? "bg-primary/10 text-primary font-medium"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||
}`}
|
||||
>
|
||||
{book.title}
|
||||
@@ -64,15 +64,15 @@ export function Sidebar({ nav, activePage, t }: {
|
||||
))}
|
||||
|
||||
{(!data?.books || data.books.length === 0) && (
|
||||
<div className="px-2 py-3 text-[11px] text-muted-foreground/40 italic">
|
||||
<div className="px-3 py-4 text-[11px] text-muted-foreground/30 italic leading-relaxed">
|
||||
{t("dash.noBooks")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* System nav */}
|
||||
<div className="border-t border-border/30 py-3 px-2 space-y-0.5">
|
||||
{/* System nav — generous spacing from book list */}
|
||||
<div className="border-t border-border/20 mt-4 pt-4 pb-5 px-4 space-y-1">
|
||||
<SidebarItem
|
||||
label={t("nav.config")}
|
||||
icon="⚙"
|
||||
@@ -109,15 +109,15 @@ function SidebarItem({ label, icon, active, onClick, badge, badgeColor }: {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`w-full text-left px-2 py-1.5 rounded text-[13px] flex items-center gap-2 transition-colors ${
|
||||
className={`w-full text-left px-3 py-2 rounded-md text-[13px] flex items-center gap-2.5 transition-all duration-150 ${
|
||||
active
|
||||
? "bg-secondary text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/30"
|
||||
? "bg-secondary text-foreground font-medium"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||
}`}
|
||||
>
|
||||
<span className="text-xs w-4 text-center opacity-50">{icon}</span>
|
||||
<span className="text-[11px] w-4 text-center opacity-40">{icon}</span>
|
||||
<span className="flex-1">{label}</span>
|
||||
{badge && <span className={`text-[10px] ${badgeColor ?? ""}`}>{badge}</span>}
|
||||
{badge && <span className={`text-[9px] ${badgeColor ?? ""}`}>{badge}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,16 +47,16 @@ export function Dashboard({ nav, sse, theme, t }: { nav: Nav; sse: { messages: R
|
||||
if (loading) return <div className="text-muted-foreground py-20 text-center text-sm">Loading...</div>;
|
||||
if (error) return <div className="text-destructive py-20 text-center">Error: {error}</div>;
|
||||
|
||||
/* ── Empty state ── */
|
||||
/* ── Empty state — vertically centered in the available viewport ── */
|
||||
if (!data?.books.length) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-32">
|
||||
<div className="text-6xl mb-6 opacity-20">✦</div>
|
||||
<h2 className="font-serif text-3xl italic text-foreground/50 mb-3">{t("dash.noBooks")}</h2>
|
||||
<p className="text-sm text-muted-foreground mb-8">{t("dash.createFirst")}</p>
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<div className="text-5xl mb-8 opacity-15 select-none">✦</div>
|
||||
<h2 className="font-serif text-3xl italic text-foreground/40 mb-3">{t("dash.noBooks")}</h2>
|
||||
<p className="text-sm text-muted-foreground/60 mb-10">{t("dash.createFirst")}</p>
|
||||
<button
|
||||
onClick={nav.toBookCreate}
|
||||
className="px-6 py-3 rounded-md text-sm font-medium bg-primary text-primary-foreground hover:opacity-90 transition-opacity"
|
||||
className="px-7 py-3 rounded-md text-sm font-medium bg-primary text-primary-foreground hover:opacity-90 transition-opacity"
|
||||
>
|
||||
{t("nav.newBook")}
|
||||
</button>
|
||||
@@ -85,7 +85,7 @@ export function Dashboard({ nav, sse, theme, t }: { nav: Nav; sse: { messages: R
|
||||
key={book.id}
|
||||
className={`group border ${c.card} rounded-lg overflow-hidden`}
|
||||
>
|
||||
<div className="px-5 py-4 flex items-start justify-between">
|
||||
<div className="px-6 py-5 flex items-start justify-between">
|
||||
<div className="min-w-0">
|
||||
<button
|
||||
onClick={() => nav.toBook(book.id)}
|
||||
|
||||
Reference in New Issue
Block a user