Seeded claude design
This commit is contained in:
282
examples/whynot-control/Screens.jsx
Normal file
282
examples/whynot-control/Screens.jsx
Normal file
@@ -0,0 +1,282 @@
|
||||
// =============================================================
|
||||
// Screens — Inbox, PrototypesIndex, PrototypeDetail, SignalsIndex, DocView, BetasIndex, DecisionsIndex
|
||||
// =============================================================
|
||||
|
||||
function Inbox({ onCapture }) {
|
||||
const [draft, setDraft] = React.useState('');
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
eyebrow="whynot-control / inbox"
|
||||
title="Inbox"
|
||||
lede="Temporary capture for rough ideas, weird observations, user comments, market hints, and product fragments. Capture is not commitment."
|
||||
/>
|
||||
<div style={{
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-2)',
|
||||
padding: 16,
|
||||
background: 'var(--paper)',
|
||||
marginBottom: 28,
|
||||
display: 'flex', flexDirection: 'column', gap: 10,
|
||||
}}>
|
||||
<Eyebrow>Capture</Eyebrow>
|
||||
<textarea
|
||||
value={draft}
|
||||
onChange={e => setDraft(e.target.value)}
|
||||
placeholder="An idea, an observation, a fragment. No filter, no judgement, no commitment."
|
||||
style={{
|
||||
font: '400 14px/1.5 var(--ff-sans)',
|
||||
border: 'none', outline: 'none', resize: 'none',
|
||||
minHeight: 64, padding: 0, background: 'transparent', color: 'var(--fg-1)',
|
||||
}}
|
||||
/>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ font: '400 11px var(--ff-mono)', color: 'var(--fg-3)', marginRight: 'auto' }}>
|
||||
⌘ ↵ to capture · stored in <code className="mono">inbox/</code>
|
||||
</span>
|
||||
<Button variant="ghost" onClick={() => setDraft('')}>Discard</Button>
|
||||
<Button variant="primary" icon="inbox" onClick={() => { onCapture && onCapture(draft); setDraft(''); }}>Capture</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
|
||||
<Eyebrow>Recent · 7</Eyebrow>
|
||||
<div style={{ flex: 1, borderTop: '1px solid var(--border-soft)' }}></div>
|
||||
<span style={{ font: '400 11px var(--ff-mono)', color: 'var(--fg-3)' }}>↓ newest first</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{INBOX.map(item => (
|
||||
<div key={item.id} style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '140px 1fr 100px',
|
||||
gap: 20,
|
||||
padding: '14px 4px',
|
||||
borderBottom: '1px solid var(--border-soft)',
|
||||
alignItems: 'flex-start',
|
||||
}}>
|
||||
<span style={{ font: '400 11px var(--ff-mono)', color: 'var(--fg-3)', paddingTop: 2 }}>{item.ts}</span>
|
||||
<p style={{ margin: 0, font: '400 14px/1.5 var(--ff-sans)', color: 'var(--fg-1)' }}>{item.text}</p>
|
||||
<span style={{ font: '500 10px/1 var(--ff-mono)', letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-3)', textAlign: 'right', paddingTop: 4 }}>{item.from}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PrototypeListCard({ p, onOpen }) {
|
||||
const [hover, setHover] = React.useState(false);
|
||||
return (
|
||||
<article
|
||||
onClick={() => onOpen(p.id)}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
style={{
|
||||
background: 'var(--paper)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--r-2)',
|
||||
padding: '20px 22px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 10,
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 120ms ease',
|
||||
borderColor: hover ? 'var(--ink)' : 'var(--border)',
|
||||
}}>
|
||||
{hover && <span style={{ position: 'absolute', left: -1, top: -1, bottom: -1, width: 2, background: 'var(--ink)', borderRadius: '2px 0 0 2px' }}></span>}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<Eyebrow style={{ color: hover ? 'var(--fg-1)' : 'var(--fg-3)' }}>{p.id} · Prototype</Eyebrow>
|
||||
<StageDot level={p.signal} label={p.stageLabel} />
|
||||
</div>
|
||||
<h3 style={{ font: '500 17px/1.35 var(--ff-sans)', margin: '4px 0 8px', color: 'var(--fg-1)' }}>{p.pitch}</h3>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '110px 1fr', gap: '6px 12px', fontSize: 13, color: 'var(--fg-1)' }}>
|
||||
<span style={{ font: '500 11px/1.5 var(--ff-mono)', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>Learning q.</span>
|
||||
<span style={{ lineHeight: 1.45 }}>{p.learning}</span>
|
||||
<span style={{ font: '500 11px/1.5 var(--ff-mono)', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>Smallest test</span>
|
||||
<span style={{ lineHeight: 1.45 }}>{p.test}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', paddingTop: 12, marginTop: 4, borderTop: '1px solid var(--border-soft)', font: '500 11px var(--ff-mono)', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>
|
||||
<span>→ {p.target}</span>
|
||||
<span>{p.signal} signal</span>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function PrototypesIndex({ onOpen }) {
|
||||
const [filter, setFilter] = React.useState('All');
|
||||
const filters = ['All', 'Experiment', 'Signal review', 'Parked'];
|
||||
const list = filter === 'All' ? PROTOTYPES : PROTOTYPES.filter(p => p.stageLabel === filter);
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
eyebrow="whynot-control / prototypes"
|
||||
title="Prototypes"
|
||||
lede="Structured prototype cards. A prototype card defines a learning question and the smallest useful test."
|
||||
actions={<Button variant="primary" icon="plus">New prototype</Button>}
|
||||
/>
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 24, alignItems: 'center' }}>
|
||||
{filters.map(f => (
|
||||
<Tag key={f} active={filter === f} style={{ cursor: 'pointer' }} >
|
||||
<span onClick={() => setFilter(f)}>{f}</span>
|
||||
</Tag>
|
||||
))}
|
||||
<span style={{ marginLeft: 'auto', font: '400 11px var(--ff-mono)', color: 'var(--fg-3)' }}>{list.length} of {PROTOTYPES.length}</span>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
||||
{list.map(p => <PrototypeListCard key={p.id} p={p} onOpen={onOpen} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PrototypeDetail({ id, onBack }) {
|
||||
const p = PROTOTYPES.find(p => p.id === id) || PROTOTYPES[0];
|
||||
const stageIdx = { 'parked': 0, 'experiment': 3, 'signal': 4, 'experiment-active': 3 }[p.stage] ?? 3;
|
||||
return (
|
||||
<div>
|
||||
<a onClick={onBack} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, font: '400 12px var(--ff-mono)', color: 'var(--fg-2)', textDecoration: 'none', marginBottom: 18, cursor: 'pointer' }}>
|
||||
<Icon name="arrow-left" size={14} /> Back to prototypes
|
||||
</a>
|
||||
<PageHeader
|
||||
eyebrow={`${p.id} · Prototype`}
|
||||
title={p.pitch}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<Button variant="secondary" icon="archive">Park</Button>
|
||||
<Button variant="primary" icon="arrow-right">Promote → {p.target}</Button>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
<PipelineStrip activeIdx={stageIdx} />
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 32 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
|
||||
<Field label="Learning question" value={p.learning} />
|
||||
<Field label="Smallest useful test" value={p.test} />
|
||||
<Field label="Expected signal" value="At least one person asks for a concrete next step, gives specific use-case feedback, or identifies a realistic context where the idea would matter." />
|
||||
<Field label="Risks" value={p.risks} />
|
||||
</div>
|
||||
<aside style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<SidebarField label="Stage" value={<Tag active>{p.stageLabel}</Tag>} />
|
||||
<SidebarField label="Signal" value={<StageDot level={p.signal} />} />
|
||||
<SidebarField label="Target" value={<code className="mono">→ {p.target}</code>} />
|
||||
<SidebarField label="Audience" value="Potential early users, collaborators, or customers." />
|
||||
<SidebarField label="Agentic suitability" value="Agents may help turn rough notes into a sharper prototype card." />
|
||||
<div style={{ marginTop: 6, border: '1px dashed var(--border-strong)', borderRadius: 4, padding: 14 }}>
|
||||
<Eyebrow style={{ display: 'block', marginBottom: 8 }}>Caveat</Eyebrow>
|
||||
<p style={{ margin: 0, font: '400 13px/1.55 var(--ff-sans)', color: 'var(--fg-2)' }}>
|
||||
A prototype can be interesting and still be parked. <code className="mono">whynot</code> exists to reduce uncertainty, not create more obligations.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Field({ label, value }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<Eyebrow>{label}</Eyebrow>
|
||||
<p style={{ margin: 0, font: '400 15px/1.55 var(--ff-sans)', color: 'var(--fg-1)' }}>{value}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarField({ label, value }) {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<Eyebrow>{label}</Eyebrow>
|
||||
<div style={{ font: '400 13px/1.5 var(--ff-sans)', color: 'var(--fg-1)' }}>{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SignalsIndex() {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
eyebrow="whynot-control / signals"
|
||||
title="Signals"
|
||||
lede="Market-signal and feedback records. A signal is evidence. Record what happened, who did it, and how strong the evidence is."
|
||||
actions={<Button variant="primary" icon="plus">Record signal</Button>}
|
||||
/>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', tableLayout: 'fixed' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: 80 }}>ID</th>
|
||||
<th style={{ width: 90 }}>Prototype</th>
|
||||
<th style={{ width: 72 }}>Level</th>
|
||||
<th>What happened</th>
|
||||
<th style={{ width: 110 }}>Source</th>
|
||||
<th style={{ width: 90 }}>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{SIGNALS.map(s => (
|
||||
<tr key={s.id}>
|
||||
<td><code className="mono">{s.id}</code></td>
|
||||
<td><code className="mono">{s.proto}</code></td>
|
||||
<td><StageDot level={s.level} /></td>
|
||||
<td style={{ color: 'var(--fg-1)', fontSize: 13, lineHeight: 1.5 }}>{s.what}</td>
|
||||
<td style={{ font: '400 12px var(--ff-mono)', color: 'var(--fg-2)' }}>{s.source}</td>
|
||||
<td style={{ font: '400 12px var(--ff-mono)', color: 'var(--fg-3)' }}>{s.date}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BetasIndex() {
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
eyebrow="whynot-control / betas"
|
||||
title="Betas"
|
||||
lede="Closed beta plans and beta review notes. A beta should have a clear learning question, entry criteria, and exit outcome."
|
||||
/>
|
||||
<div style={{
|
||||
border: '1px dashed var(--border-strong)',
|
||||
padding: 32,
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
|
||||
textAlign: 'center',
|
||||
color: 'var(--fg-3)',
|
||||
borderRadius: 4,
|
||||
}}>
|
||||
<Icon name="users" size={20} />
|
||||
<div style={{ font: '500 14px var(--ff-sans)', color: 'var(--fg-2)' }}>One beta plan in draft.</div>
|
||||
<div style={{ font: '400 12px var(--ff-mono)', color: 'var(--fg-3)' }}>WNO-021 · Concierge triage · pending Binky approval</div>
|
||||
<a href="#" style={{ font: '500 12px var(--ff-mono)', color: 'var(--fg-1)', marginTop: 4 }}>Open draft →</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DecisionsIndex() {
|
||||
const decisions = [
|
||||
{ id: 'DEC-001', title: 'Shorten organisation name from whywhynot to whynot.', status: 'Accepted', date: '2026-01-08' },
|
||||
{ id: 'DEC-002', title: 'Maintain A1 Incubating until first prototype candidates review.', status: 'Open', date: '—' },
|
||||
{ id: 'DEC-003', title: 'Initial promotion targets: Helix, Coulomb, Sloppers, Plenitude, Binky, Tegwick.', status: 'Open', date: '—' },
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<PageHeader eyebrow="whynot-control / decisions" title="Decisions" lede="A promotion record is required before any prototype moves to Helix, Coulomb, Sloppers, Plenitude, Binky, or Tegwick." />
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{decisions.map(d => (
|
||||
<div key={d.id} style={{ display: 'grid', gridTemplateColumns: '90px 1fr 130px 100px', gap: 20, alignItems: 'baseline', padding: '16px 4px', borderBottom: '1px solid var(--border-soft)' }}>
|
||||
<code className="mono" style={{ background: 'none', padding: 0, color: 'var(--fg-1)' }}>{d.id}</code>
|
||||
<span style={{ font: '500 15px var(--ff-sans)', color: 'var(--fg-1)' }}>{d.title}</span>
|
||||
<Tag active={d.status === 'Accepted'} draft={d.status === 'Open'}>{d.status}</Tag>
|
||||
<span style={{ font: '400 12px var(--ff-mono)', color: 'var(--fg-3)', textAlign: 'right' }}>{d.date}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(window, { Inbox, PrototypesIndex, PrototypeDetail, SignalsIndex, BetasIndex, DecisionsIndex, Field, SidebarField, PrototypeListCard });
|
||||
Reference in New Issue
Block a user