Seeded claude design
Some checks failed
ci / check (push) Has been cancelled
ci / release (push) Has been cancelled

This commit is contained in:
2026-05-23 16:34:14 +02:00
commit 9419f166ce
32 changed files with 2553 additions and 0 deletions

102
src/components/Atoms.jsx Normal file
View File

@@ -0,0 +1,102 @@
// =============================================================
// Atoms — Eyebrow, Tag, Button, StageDot, Stamp, IconBtn
// =============================================================
function Eyebrow({ children, style }) {
return (
<span style={{
font: '500 11px/1.2 var(--ff-mono)',
letterSpacing: '0.08em',
textTransform: 'uppercase',
color: 'var(--fg-3)',
...style,
}}>{children}</span>
);
}
function Tag({ children, active, draft, style }) {
const base = {
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.1em',
textTransform: 'uppercase',
padding: '5px 10px',
borderRadius: 'var(--r-pill)',
border: '1px solid var(--border)',
color: 'var(--fg-2)',
background: 'var(--paper)',
display: 'inline-block',
};
if (active) Object.assign(base, { background: 'var(--ink)', color: 'var(--paper)', borderColor: 'var(--ink)' });
if (draft) Object.assign(base, { background: 'var(--hi)', color: 'var(--hi-ink)', borderColor: 'transparent' });
return <span style={{ ...base, ...style }}>{children}</span>;
}
function Button({ children, variant = 'secondary', onClick, style, icon }) {
const base = {
font: '500 13px var(--ff-sans)',
letterSpacing: '-0.005em',
padding: '9px 14px',
borderRadius: 'var(--r-2)',
border: '1px solid var(--border)',
background: 'var(--paper)',
color: 'var(--ink)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 8,
whiteSpace: 'nowrap',
transition: 'background 120ms ease, border-color 120ms ease',
};
if (variant === 'primary') Object.assign(base, { background: 'var(--ink)', color: 'var(--paper)', borderColor: 'var(--ink)' });
if (variant === 'ghost') Object.assign(base, { background: 'transparent', borderColor: 'transparent', padding: '7px 10px' });
return (
<button onClick={onClick} style={{ ...base, ...style }}>
{icon && <i data-lucide={icon} style={{ width: 14, height: 14, strokeWidth: 1.5 }}></i>}
{children}
</button>
);
}
const STAGE_COLORS = {
S0: '#B5B5B3', S1: '#8A8A8A', S2: '#5C5C5C', S3: '#0A0A0A', S4: '#FFD400',
};
function StageDot({ level = 'S2', label, style }) {
return (
<span style={{
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.1em',
textTransform: 'uppercase',
color: 'var(--fg-2)',
display: 'inline-flex',
alignItems: 'center',
gap: 6,
...style,
}}>
<span style={{ width: 8, height: 8, borderRadius: 999, background: STAGE_COLORS[level] }}></span>
{label || level}
</span>
);
}
function Stamp({ children, style }) {
return (
<span style={{
display: 'inline-block',
background: 'var(--hi)',
color: 'var(--hi-ink)',
padding: '5px 10px 3px',
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.12em',
textTransform: 'uppercase',
transform: 'rotate(-1.5deg)',
...style,
}}>{children}</span>
);
}
function Icon({ name, size = 16, style }) {
return <i data-lucide={name} style={{ width: size, height: size, strokeWidth: 1.5, ...style }}></i>;
}
Object.assign(window, { Eyebrow, Tag, Button, StageDot, Stamp, Icon, STAGE_COLORS });

165
src/components/Chrome.jsx Normal file
View File

@@ -0,0 +1,165 @@
// =============================================================
// 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 });

18
src/index.js Normal file
View File

@@ -0,0 +1,18 @@
// @whynot/design — barrel export.
//
// At A1 there is no build step: consumers import these JSX files directly.
// Any modern bundler (Vite, Next.js, Webpack 5 with @babel/preset-react,
// esbuild, Bun) handles JSX-in-.jsx out of the box.
//
// If you need to support a bundler that doesn't, fall back to either
// (a) importing from `examples/whynot-control/` as inline <script type="text/babel">
// or (b) adding a build step here when you next bump minor.
export * from "./components/Atoms.jsx";
export * from "./components/Chrome.jsx";
// CSS is exported as a side-effect import:
//
// import "@whynot/design/styles/colors_and_type.css";
//
// Do this once, at the app root.

View File

@@ -0,0 +1,273 @@
/* ============================================================
WhyNot Design System — Colors & Type
------------------------------------------------------------
Neutral, mostly black/white. Color is used SPARINGLY — only
one warm accent (annotation yellow) borrowed from the LEGO
brick in the logo. The system favours light grey wireframe
artefacts over heavy fills.
============================================================ */
/* ---------- Webfonts (Google Fonts, see /fonts for offline) ---------- */
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@300;400;500;600;700&family=IBM+Plex+Serif:ital,wght@0,400;0,500;1,400&display=swap");
:root {
/* ---------- Base palette: neutrals ---------- */
--ink: #0A0A0A; /* near-black, the only "fill" most of the time */
--ink-2: #1F1F1F;
--ink-3: #5C5C5C;
--ink-4: #8A8A8A;
--ink-5: #B5B5B3; /* placeholder text, wireframe labels */
--line: #E5E5E2; /* default 1px wireframe rule */
--line-strong: #C9C9C5; /* dividers between sections */
--line-soft: #F0F0EC; /* hairline within a card */
--paper: #FFFFFF; /* canvas */
--paper-2: #FAFAF7; /* sheet, dim canvas */
--paper-3: #F4F4EF; /* recessed surface, code block bg */
/* ---------- Foreground / background semantic ---------- */
--fg-1: var(--ink);
--fg-2: var(--ink-3);
--fg-3: var(--ink-4);
--fg-mute: var(--ink-5);
--fg-on-dark: #FAFAF7;
--bg-1: var(--paper);
--bg-2: var(--paper-2);
--bg-3: var(--paper-3);
--bg-invert: var(--ink);
--border: var(--line);
--border-strong: var(--line-strong);
--border-soft: var(--line-soft);
/* ---------- The single accent: annotation yellow ---------- */
/* Lifted from the LEGO brick. Used as highlighter, "draft"
stamp, signal-marker. Never as a button fill. */
--hi: #FFE14A;
--hi-2: #FFD400;
--hi-ink: #1A1500; /* text on yellow */
/* ---------- Status (for prototype lifecycle, signal strength) ---------- */
/* Kept deliberately desaturated so they read as labels, not UI. */
--status-raw: #B5B5B3; /* S0 — no signal */
--status-weak: #8A8A8A; /* S1 — weak signal */
--status-medium: #5C5C5C; /* S2 — medium signal */
--status-strong: #0A0A0A; /* S3 — strong signal */
--status-commercial: #FFD400; /* S4 — commercial */
/* ---------- Type families ---------- */
--ff-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
--ff-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
--ff-serif: "IBM Plex Serif", "Iowan Old Style", Georgia, serif;
/* ---------- Type scale (modular, ~1.2) ---------- */
--fs-xs: 11px;
--fs-sm: 13px;
--fs-base: 15px;
--fs-md: 17px;
--fs-lg: 20px;
--fs-xl: 24px;
--fs-2xl: 32px;
--fs-3xl: 44px;
--fs-4xl: 64px;
--fs-5xl: 96px;
--lh-tight: 1.05;
--lh-snug: 1.25;
--lh-base: 1.5;
--lh-loose: 1.7;
--tr-tight: -0.02em;
--tr-snug: -0.01em;
--tr-base: 0em;
--tr-mono: 0.02em;
--tr-label: 0.08em; /* uppercase eyebrow labels */
/* ---------- Spacing (4px base) ---------- */
--sp-1: 4px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 24px;
--sp-6: 32px;
--sp-7: 48px;
--sp-8: 64px;
--sp-9: 96px;
--sp-10: 128px;
/* ---------- Radii — small, mostly square ---------- */
--r-0: 0px;
--r-1: 2px;
--r-2: 4px;
--r-3: 8px;
--r-pill: 999px;
/* ---------- Elevation — almost none. This is a wireframe system. ---------- */
--shadow-0: none;
--shadow-1: 0 1px 0 var(--line);
--shadow-2: 0 1px 0 var(--line-strong);
--shadow-3: 0 4px 12px -6px rgba(10,10,10,0.10);
}
/* ============================================================
Semantic element styles
============================================================ */
html {
font-family: var(--ff-sans);
font-size: var(--fs-base);
line-height: var(--lh-base);
color: var(--fg-1);
background: var(--bg-1);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-feature-settings: "ss01", "cv11";
text-wrap: pretty;
}
/* ---------- Headings ---------- */
h1, .h1 {
font: 600 var(--fs-3xl)/var(--lh-tight) var(--ff-sans);
letter-spacing: var(--tr-tight);
margin: 0 0 var(--sp-5);
color: var(--fg-1);
}
h2, .h2 {
font: 500 var(--fs-2xl)/var(--lh-snug) var(--ff-sans);
letter-spacing: var(--tr-snug);
margin: 0 0 var(--sp-4);
}
h3, .h3 {
font: 500 var(--fs-xl)/var(--lh-snug) var(--ff-sans);
letter-spacing: var(--tr-snug);
margin: 0 0 var(--sp-3);
}
h4, .h4 {
font: 500 var(--fs-lg)/var(--lh-snug) var(--ff-sans);
margin: 0 0 var(--sp-2);
}
h5, .h5 {
font: 500 var(--fs-md)/var(--lh-snug) var(--ff-sans);
margin: 0 0 var(--sp-2);
}
/* ---------- Display (for hero / title slides) ---------- */
.display-1 {
font: 300 var(--fs-5xl)/0.95 var(--ff-sans);
letter-spacing: -0.035em;
color: var(--fg-1);
}
.display-2 {
font: 400 var(--fs-4xl)/1.0 var(--ff-sans);
letter-spacing: var(--tr-tight);
}
/* ---------- Body ---------- */
p {
margin: 0 0 var(--sp-4);
line-height: var(--lh-base);
color: var(--fg-1);
}
.lead {
font-size: var(--fs-md);
line-height: 1.55;
color: var(--fg-2);
}
small, .small {
font-size: var(--fs-sm);
color: var(--fg-2);
}
/* ---------- Eyebrow / uppercase labels (very common in this system) ---------- */
.eyebrow,
.label {
font: 500 var(--fs-xs)/1.2 var(--ff-mono);
letter-spacing: var(--tr-label);
text-transform: uppercase;
color: var(--fg-3);
}
/* ---------- Code / mono ---------- */
code, kbd, samp, pre, .mono {
font-family: var(--ff-mono);
font-size: 0.92em;
letter-spacing: var(--tr-mono);
}
code {
background: var(--bg-3);
padding: 1px 6px;
border-radius: var(--r-1);
color: var(--ink-2);
}
pre {
background: var(--bg-3);
border: 1px solid var(--border);
padding: var(--sp-4);
overflow-x: auto;
border-radius: var(--r-2);
font-size: var(--fs-sm);
line-height: var(--lh-snug);
}
pre code { background: none; padding: 0; }
/* ---------- Editorial serif moments ---------- */
.serif { font-family: var(--ff-serif); }
.serif-quote {
font: 400 italic var(--fs-xl)/1.4 var(--ff-serif);
color: var(--fg-2);
}
/* ---------- Links ---------- */
a {
color: var(--fg-1);
text-decoration: underline;
text-decoration-color: var(--border-strong);
text-underline-offset: 3px;
text-decoration-thickness: 1px;
transition: text-decoration-color 120ms ease, color 120ms ease;
}
a:hover {
text-decoration-color: var(--fg-1);
}
/* ---------- HR ---------- */
hr {
border: 0;
border-top: 1px solid var(--border);
margin: var(--sp-5) 0;
}
/* ---------- Highlighter (the one place yellow appears in body copy) ---------- */
mark, .mark {
background: var(--hi);
color: var(--hi-ink);
padding: 0 2px;
}
/* ---------- Tables (used in templates) ---------- */
table {
width: 100%;
border-collapse: collapse;
font-size: var(--fs-sm);
}
th, td {
text-align: left;
padding: var(--sp-3) var(--sp-4);
border-bottom: 1px solid var(--border);
}
th {
font-weight: 500;
color: var(--fg-2);
font-family: var(--ff-mono);
font-size: var(--fs-xs);
letter-spacing: var(--tr-label);
text-transform: uppercase;
}
/* ---------- Selection ---------- */
::selection { background: var(--hi); color: var(--hi-ink); }