Prior-session prep for the v0.6 Project Manager, checkpointed on the v0.6-projects branch as a clean base for the parallel builders: - P1 (src/types.ts): ProjectInfo + ProjectSessionRef contracts; Config gains projectRoots/projectScanDepth/projectScanTtlMs/projectDirtyCheck. - P3 (src/config.ts): parseBool + parseProjectRoots helpers; parse the 4 PROJECT_* env vars with defaults ([homeDir]/4/10000/true). - UI: public/style.css indigo -> Amber (#e3a64a) accent theme. - Docs: FEATURE_PROJECT_MANAGER.md (1:N session design), THEME.md, docs/mockups/ (final-amber.png is the locked visual). Typecheck (backend + web) green. No behavior change to runtime code yet; P2/P4/P5/P6 implement the feature on top of this.
176 lines
12 KiB
HTML
176 lines
12 KiB
HTML
<!doctype html>
|
||
<html lang="zh">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Project Manager — final</title>
|
||
<style>
|
||
:root{
|
||
--bg:#100f0d; --panel:#181613; --card:#1b1916;
|
||
--border:rgba(255,255,255,.07); --border-strong:rgba(255,255,255,.14);
|
||
--text:#ece9e3; --text-dim:#a8a299; --text-faint:#6f6a61;
|
||
--idle:#8b8a86; --red:#ff6b6b;
|
||
--shadow:0 12px 44px rgba(0,0,0,.55); --radius:9px; --radius-lg:14px;
|
||
--ui-font:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
||
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
||
/* default = amber */
|
||
--accent:#e3a64a;--accent-2:#c9892f;--soft:rgba(227,166,74,.14);--line:rgba(227,166,74,.36);
|
||
--ink:#1a1305;--working:#e3a64a;--waiting:#e6b84e;--dirty:#e0975a;--glow:rgba(227,166,74,.10);
|
||
}
|
||
body[data-p="sand"]{--accent:#cdb486;--accent-2:#b39c6e;--soft:rgba(205,180,134,.13);--line:rgba(205,180,134,.34);
|
||
--ink:#1a1408;--working:#cdb486;--waiting:#e0975a;--dirty:#d9a26a;--glow:rgba(205,180,134,.09)}
|
||
body[data-p="clay"]{--accent:#cf6b4f;--accent-2:#b1543b;--soft:rgba(207,107,79,.15);--line:rgba(207,107,79,.38);
|
||
--ink:#ffffff;--working:#cf6b4f;--waiting:#e0aa4e;--dirty:#dd9a55;--glow:rgba(207,107,79,.10)}
|
||
body[data-p="brass"]{--accent:#d9b441;--accent-2:#bd9a2c;--soft:rgba(217,180,65,.15);--line:rgba(217,180,65,.38);
|
||
--ink:#1a1404;--working:#d9b441;--waiting:#e08a3c;--dirty:#dca64a;--glow:rgba(217,180,65,.10)}
|
||
body[data-p="slate"]{--accent:#9aa3ab;--accent-2:#7f888f;--soft:rgba(154,163,171,.12);--line:rgba(154,163,171,.30);
|
||
--ink:#12140f;--working:#aab2b9;--waiting:#e3a64a;--dirty:#cfa45e;--glow:rgba(154,163,171,.08)}
|
||
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:radial-gradient(1100px 520px at 82% -12%, var(--glow), transparent 62%),var(--bg);
|
||
color:var(--text);font-family:var(--ui-font);min-height:100vh;-webkit-font-smoothing:antialiased}
|
||
|
||
.topbar{height:50px;display:flex;align-items:center;gap:14px;padding:0 18px;
|
||
border-bottom:1px solid var(--border);background:rgba(24,22,19,.62);backdrop-filter:blur(14px);
|
||
position:sticky;top:0;z-index:5}
|
||
.brand{display:flex;align-items:center;gap:9px;font-weight:650;letter-spacing:.2px}
|
||
.brand .dot{width:11px;height:11px;border-radius:50%;
|
||
background:conic-gradient(from 200deg,var(--accent),var(--accent-2),var(--accent))}
|
||
.seg{display:flex;background:#0c0b09;border:1px solid var(--border);border-radius:999px;padding:3px;gap:2px;margin-left:6px}
|
||
.seg button{all:unset;cursor:pointer;font-size:13px;color:var(--text-dim);padding:5px 15px;border-radius:999px;
|
||
transition:.15s;display:flex;align-items:center;gap:6px}
|
||
.seg button.active{background:var(--soft);color:var(--accent);box-shadow:inset 0 0 0 1px var(--line)}
|
||
.seg .mini{font-size:10px;background:var(--accent);color:var(--ink);border-radius:999px;padding:1px 6px;font-weight:700}
|
||
.spacer{flex:1}
|
||
.icns{display:flex;gap:6px}
|
||
.icns button{all:unset;cursor:pointer;width:33px;height:33px;border-radius:8px;display:grid;place-items:center;
|
||
color:var(--text-dim);font-size:15px;transition:.15s}
|
||
.icns button:hover{background:var(--soft);color:var(--text)}
|
||
|
||
.wrap{max-width:1100px;margin:0 auto;padding:26px 22px 60px}
|
||
.head{display:flex;align-items:baseline;gap:12px;margin:4px 0 22px}
|
||
.head h1{font-size:20px;margin:0;font-weight:680;letter-spacing:.2px}
|
||
.head .sub{color:var(--text-faint);font-size:13px}
|
||
.head .grow{flex:1}
|
||
.search{display:flex;align-items:center;gap:8px;background:var(--panel);border:1px solid var(--border);
|
||
border-radius:999px;padding:8px 15px;width:260px;color:var(--text-faint);font-size:13px;transition:.15s}
|
||
.search:focus-within{border-color:var(--line)}
|
||
.search input{all:unset;flex:1;color:var(--text);font-size:13px}
|
||
|
||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:16px}
|
||
.card{background:linear-gradient(180deg,var(--card),#161410);border:1px solid var(--border);
|
||
border-radius:var(--radius-lg);padding:15px;display:flex;flex-direction:column;gap:11px;
|
||
box-shadow:var(--shadow);transition:.18s;position:relative;overflow:hidden}
|
||
.card.running{border-color:var(--line)}
|
||
.card.running::before{content:"";position:absolute;left:0;right:0;top:0;height:2px;
|
||
background:linear-gradient(90deg,transparent,var(--accent),transparent);opacity:.85}
|
||
.crow{display:flex;align-items:center;gap:8px;min-width:0}
|
||
.name{font-weight:680;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.star{color:var(--waiting);font-size:13px}
|
||
.badge{margin-left:auto;font-size:11px;padding:3px 10px;border-radius:999px;display:flex;align-items:center;gap:6px;white-space:nowrap}
|
||
.badge.idle{background:rgba(255,255,255,.05);color:var(--text-faint)}
|
||
.countbadge{margin-left:auto;font-size:11px;padding:3px 10px;border-radius:999px;background:var(--soft);
|
||
color:var(--accent);display:flex;align-items:center;gap:6px;font-weight:600}
|
||
.countbadge .d{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 9px var(--accent)}
|
||
.meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
|
||
.chip{font-size:11px;padding:3px 9px;border-radius:999px;border:1px solid var(--border);color:var(--text-dim);
|
||
display:flex;align-items:center;gap:5px;font-family:var(--mono)}
|
||
.chip.dirty{color:var(--dirty);border-color:var(--line)}
|
||
.chip .gd{width:6px;height:6px;border-radius:50%;background:var(--dirty)}
|
||
.path{font-family:var(--mono);font-size:11px;color:var(--text-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:-2px}
|
||
|
||
.sessions{display:flex;flex-direction:column;gap:7px}
|
||
.srow{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:10px;
|
||
background:rgba(255,255,255,.025);border:1px solid var(--border);cursor:pointer;transition:.15s;position:relative}
|
||
.srow::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:2px;border-radius:2px;background:var(--rail,transparent)}
|
||
.srow:hover{background:var(--soft);border-color:var(--line)}
|
||
.srow.working{--rail:var(--working)} .srow.waiting{--rail:var(--waiting)} .srow.idle{--rail:var(--idle)}
|
||
.srow .st{width:8px;height:8px;border-radius:50%;flex:0 0 auto;margin-left:3px}
|
||
.st.working{background:var(--working);box-shadow:0 0 9px var(--working)}
|
||
.st.waiting{background:var(--waiting);box-shadow:0 0 9px var(--waiting);animation:blink 1.1s steps(2) infinite}
|
||
.st.idle{background:var(--idle)}
|
||
@keyframes blink{50%{opacity:.25}}
|
||
.srow .stitle{font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.srow .smeta{font-size:11px;color:var(--text-faint);font-family:var(--mono);white-space:nowrap}
|
||
.srow .mir{font-size:10px;color:var(--text-dim);background:rgba(255,255,255,.06);padding:2px 7px;border-radius:999px;white-space:nowrap}
|
||
.srow .x{color:var(--text-faint);font-size:13px;padding:2px 5px;border-radius:6px;transition:.12s}
|
||
.srow .x:hover{color:var(--red);background:rgba(255,107,107,.13)}
|
||
.spush{margin-left:auto}
|
||
|
||
.thumb.empty{height:96px;border-radius:10px;border:1px dashed var(--border-strong);
|
||
background:rgba(255,255,255,.015);display:grid;place-items:center;color:var(--text-faint)}
|
||
.thumb.empty .plus{font-size:13px;color:var(--accent);border:1px dashed var(--line);padding:7px 16px;border-radius:999px}
|
||
|
||
.actions{display:flex;gap:8px;margin-top:2px}
|
||
.btn{all:unset;cursor:pointer;flex:1;text-align:center;font-size:12px;font-weight:680;padding:9px;border-radius:10px;
|
||
background:var(--accent);color:var(--ink);transition:.15s}
|
||
.btn:hover{filter:brightness(1.07)}
|
||
.btn.new{background:var(--soft);color:var(--accent);box-shadow:inset 0 0 0 1px var(--line)}
|
||
.btn.shell{background:rgba(255,255,255,.07);color:var(--text)}
|
||
.btn.ghost{flex:0 0 auto;padding:9px 13px;background:transparent;color:var(--text-dim);border:1px solid var(--border)}
|
||
.btn.ghost:hover{border-color:var(--border-strong);color:var(--text)}
|
||
.card.add{border-style:dashed;align-items:center;justify-content:center;color:var(--text-faint);min-height:236px;gap:6px;cursor:pointer}
|
||
.card.add:hover{color:var(--text);border-color:var(--line)}
|
||
.card.add .big{font-size:30px;color:var(--accent)}
|
||
.note{margin-top:28px;color:var(--text-faint);font-size:12px;text-align:center;line-height:1.7}
|
||
</style>
|
||
</head>
|
||
<body data-p="amber">
|
||
<div class="topbar">
|
||
<div class="brand"><span class="dot"></span> web-terminal</div>
|
||
<div class="seg"><button>Sessions</button><button class="active">Projects <span class="mini">2</span></button></div>
|
||
<div class="spacer"></div>
|
||
<div class="icns"><button>⚙</button><button>🗂</button><button>⌨</button></div>
|
||
</div>
|
||
<div class="wrap">
|
||
<div class="head">
|
||
<h1>Projects</h1><span class="sub" id="palname">Amber</span>
|
||
<span class="grow"></span>
|
||
<div class="search">🔍 <input placeholder="search projects…"></div>
|
||
</div>
|
||
<div class="grid">
|
||
<div class="card running">
|
||
<div class="crow"><span class="star">★</span><span class="name">web-terminal</span><span class="countbadge"><span class="d"></span>3 sessions</span></div>
|
||
<div class="meta"><span class="chip">⎇ main</span><span class="chip dirty"><span class="gd"></span>dirty</span><span class="chip">2h ago</span></div>
|
||
<div class="path">~/Documents/git/web-terminal</div>
|
||
<div class="sessions">
|
||
<div class="srow working"><span class="st working"></span><span class="stitle">claude</span><span class="smeta">⚙ working</span><span class="mir spush">👥 2</span><span class="x">✕</span></div>
|
||
<div class="srow waiting"><span class="st waiting"></span><span class="stitle">claude #2</span><span class="smeta">⏳ waiting approval</span><span class="x spush">✕</span></div>
|
||
<div class="srow idle"><span class="st idle"></span><span class="stitle">shell · npm test</span><span class="smeta">✓ idle</span><span class="x spush">✕</span></div>
|
||
</div>
|
||
<div class="actions"><button class="btn new">+ New claude</button><button class="btn ghost">▾</button></div>
|
||
</div>
|
||
<div class="card running">
|
||
<div class="crow"><span class="name">colacoder-api</span><span class="countbadge"><span class="d"></span>1 session</span></div>
|
||
<div class="meta"><span class="chip">⎇ feat/auth</span><span class="chip">5d ago</span></div>
|
||
<div class="path">~/Documents/git/colacoder-api</div>
|
||
<div class="sessions"><div class="srow idle"><span class="st idle"></span><span class="stitle">claude</span><span class="smeta">✓ idle · 20m</span><span class="x spush">✕</span></div></div>
|
||
<div class="actions"><button class="btn new">+ New claude</button><button class="btn ghost">▾</button></div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="crow"><span class="star">★</span><span class="name">dotfiles</span><span class="badge idle">idle</span></div>
|
||
<div class="meta"><span class="chip">⎇ main</span><span class="chip">12d ago</span></div>
|
||
<div class="path">~/.dotfiles</div>
|
||
<div class="thumb empty"><span class="plus">+ start claude here</span></div>
|
||
<div class="actions"><button class="btn">Open ▸ claude</button><button class="btn ghost">▾</button></div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="crow"><span class="name">scratch</span><span class="badge idle">idle</span></div>
|
||
<div class="meta"><span class="chip" style="opacity:.6">not a git repo</span></div>
|
||
<div class="path">~/scratch</div>
|
||
<div class="thumb empty"><span class="plus">+ start shell here</span></div>
|
||
<div class="actions"><button class="btn shell">Open ▸ shell</button><button class="btn ghost">▾</button></div>
|
||
</div>
|
||
<div class="card add"><div class="big">+</div><div>Add folder…</div><div style="font-size:11px;opacity:.7">browse a path to track</div></div>
|
||
</div>
|
||
<div class="note">UI mockup · 一项目可挂多 session(每行可单独进入 / ✕Kill)· working=强调色 waiting=琥珀 idle=中性灰</div>
|
||
</div>
|
||
<script>
|
||
const NAMES={amber:'Amber 琥珀金',sand:'Sand 沙褐',clay:'Clay 陶土',brass:'Brass 黄铜',slate:'Slate 冷石灰'};
|
||
const p=(location.hash||'#amber').slice(1);
|
||
document.body.dataset.p=p;
|
||
document.getElementById('palname').textContent=NAMES[p]||p;
|
||
</script>
|
||
</body>
|
||
</html>
|