166 lines
7.1 KiB
JavaScript
166 lines
7.1 KiB
JavaScript
// =============================================================
|
|
// Chrome — TopNav, Sidebar, PageHeader, PipelineStrip
|
|
// =============================================================
|
|
|
|
function TopNav({ onNew }) {
|
|
return (
|
|
<nav style={{
|
|
height: 56,
|
|
background: 'rgba(255,255,255,0.92)',
|
|
borderBottom: '1px solid var(--border)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 28,
|
|
padding: '0 24px',
|
|
position: 'sticky',
|
|
top: 0,
|
|
zIndex: 10,
|
|
}}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
<img src="../../assets/whynot-logo.png" alt="" style={{ width: 22, height: 22 }} />
|
|
<span style={{ font: '500 14px var(--ff-sans)' }}>whynot</span>
|
|
<span style={{ font: '400 12px var(--ff-mono)', color: 'var(--fg-3)', letterSpacing: '0.04em' }}>/ control</span>
|
|
</div>
|
|
<div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
<div style={{
|
|
font: '400 12px var(--ff-mono)',
|
|
color: 'var(--fg-3)',
|
|
border: '1px solid var(--border)',
|
|
padding: '6px 10px',
|
|
borderRadius: 'var(--r-1)',
|
|
display: 'flex', alignItems: 'center', gap: 10,
|
|
minWidth: 240,
|
|
}}>
|
|
<Icon name="search" size={14} />
|
|
<span>Search ideas, prototypes, signals…</span>
|
|
<span style={{ marginLeft: 'auto', padding: '1px 5px', border: '1px solid var(--border)', borderRadius: 2, fontSize: 10 }}>⌘ K</span>
|
|
</div>
|
|
<Button variant="primary" icon="plus" onClick={onNew}>New idea</Button>
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
const NAV_ITEMS = [
|
|
{ key: 'inbox', label: 'Inbox', icon: 'inbox', count: 7 },
|
|
{ key: 'prototypes', label: 'Prototypes', icon: 'flask-conical', count: 4 },
|
|
{ key: 'signals', label: 'Signals', icon: 'activity', count: 12 },
|
|
{ key: 'betas', label: 'Betas', icon: 'users', count: 1 },
|
|
{ key: 'decisions', label: 'Decisions', icon: 'check-square', count: 3 },
|
|
];
|
|
|
|
const DOC_ITEMS = [
|
|
{ key: 'intent', label: 'INTENT.md' },
|
|
{ key: 'scope', label: 'SCOPE.md' },
|
|
{ key: 'operating', label: 'OPERATING_MODEL.md' },
|
|
{ key: 'pipeline', label: 'PROTOTYPE_PIPELINE.md' },
|
|
{ key: 'agent', label: 'AGENT_RULES.md' },
|
|
];
|
|
|
|
function Sidebar({ current, onNav }) {
|
|
const itemStyle = (active) => ({
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
padding: '8px 12px',
|
|
borderRadius: 4,
|
|
color: active ? 'var(--fg-1)' : 'var(--fg-2)',
|
|
background: active ? 'var(--paper)' : 'transparent',
|
|
boxShadow: active ? '0 0 0 1px var(--border) inset' : 'none',
|
|
font: '500 13px var(--ff-sans)',
|
|
cursor: 'pointer',
|
|
textDecoration: 'none',
|
|
transition: 'background 120ms ease, color 120ms ease',
|
|
});
|
|
return (
|
|
<aside style={{
|
|
width: 240, flex: 'none',
|
|
background: 'var(--paper-2)',
|
|
borderRight: '1px solid var(--border)',
|
|
padding: '24px 16px',
|
|
display: 'flex', flexDirection: 'column', gap: 24,
|
|
height: 'calc(100vh - 56px)',
|
|
position: 'sticky', top: 56,
|
|
overflowY: 'auto',
|
|
}}>
|
|
<div>
|
|
<Eyebrow style={{ paddingLeft: 12, marginBottom: 8, display: 'block' }}>Work</Eyebrow>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
{NAV_ITEMS.map(item => (
|
|
<a key={item.key} onClick={() => onNav(item.key)} style={itemStyle(current === item.key)}>
|
|
<Icon name={item.icon} size={16} />
|
|
<span>{item.label}</span>
|
|
<span style={{ marginLeft: 'auto', font: '400 11px var(--ff-mono)', color: 'var(--fg-3)' }}>{item.count}</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<Eyebrow style={{ paddingLeft: 12, marginBottom: 8, display: 'block' }}>Control docs</Eyebrow>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
{DOC_ITEMS.map(item => (
|
|
<a key={item.key} onClick={() => onNav('doc:' + item.key)} style={{ ...itemStyle(current === 'doc:' + item.key), font: '400 12px var(--ff-mono)' }}>
|
|
<Icon name="file-text" size={14} />
|
|
<span>{item.label}</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div style={{ marginTop: 'auto', paddingTop: 12, borderTop: '1px solid var(--border)' }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 12px' }}>
|
|
<span style={{ width: 6, height: 6, borderRadius: 999, background: 'var(--hi-2)' }}></span>
|
|
<span style={{ font: '500 11px var(--ff-mono)', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--fg-2)' }}>A1 · Incubating</span>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
function PageHeader({ eyebrow, title, lede, actions }) {
|
|
return (
|
|
<header style={{ marginBottom: 32, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
|
|
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 24 }}>
|
|
<h1 style={{ font: '500 32px/1.15 var(--ff-sans)', letterSpacing: '-0.015em', margin: 0, flex: 1 }}>{title}</h1>
|
|
{actions && <div style={{ display: 'flex', gap: 8 }}>{actions}</div>}
|
|
</div>
|
|
{lede && <p style={{ font: '400 16px/1.55 var(--ff-sans)', color: 'var(--fg-2)', margin: 0, maxWidth: '60ch' }}>{lede}</p>}
|
|
</header>
|
|
);
|
|
}
|
|
|
|
function PipelineStrip({ activeIdx = 3 }) {
|
|
const stages = [
|
|
{ num: 'Stage 0', name: 'Raw idea', meta: 'inbox/' },
|
|
{ num: 'Stage 1', name: 'Triage', meta: '2026-02-12' },
|
|
{ num: 'Stage 2', name: 'Prototype card', meta: 'prototypes/' },
|
|
{ num: 'Stage 3', name: 'Experiment', meta: 'ends 2026-04-01' },
|
|
{ num: 'Stage 4', name: 'Signal review', meta: '— pending' },
|
|
];
|
|
return (
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 0, position: 'relative', margin: '0 0 32px' }}>
|
|
{stages.map((s, i) => {
|
|
const state = i < activeIdx ? 'done' : i === activeIdx ? 'active' : 'pending';
|
|
const topColor = state === 'done' ? 'var(--ink)' : state === 'active' ? 'var(--hi-2)' : 'var(--border)';
|
|
return (
|
|
<div key={i} style={{
|
|
padding: '10px 12px 14px',
|
|
borderTop: `2px solid ${topColor}`,
|
|
display: 'flex', flexDirection: 'column', gap: 4,
|
|
position: 'relative',
|
|
}}>
|
|
<span style={{ font: '500 10px/1 var(--ff-mono)', letterSpacing: '0.1em', textTransform: 'uppercase', color: state === 'pending' ? 'var(--fg-3)' : 'var(--fg-1)' }}>{s.num}</span>
|
|
<span style={{ font: '500 14px/1.25 var(--ff-sans)', color: state === 'pending' ? 'var(--fg-3)' : 'var(--fg-1)' }}>{s.name}</span>
|
|
<span style={{ font: '400 11px/1.35 var(--ff-mono)', color: 'var(--fg-3)' }}>{s.meta}</span>
|
|
{i > 0 && (
|
|
<span style={{ position: 'absolute', top: -8, right: -7, font: '400 14px var(--ff-mono)', color: state === 'pending' ? 'var(--ink-5)' : 'var(--ink)' }}>→</span>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
Object.assign(window, { TopNav, Sidebar, PageHeader, PipelineStrip, NAV_ITEMS, DOC_ITEMS });
|