354 lines
18 KiB
Markdown
354 lines
18 KiB
Markdown
# @whynot/design
|
||
|
||
The neutral, mostly-black-and-white visual language for **whynot** — Tegwick's prototype and market-signal organisation. Wireframe-leaning. Quiet. Built for artefacts that should look deliberately unfinished.
|
||
|
||
> A prototype is a question made tangible. — `whynot-control/INTENT.md`
|
||
|
||
This repository is the **implementation surface** for `whynot`'s visual language. It ships:
|
||
|
||
- **Tokens** (`tokens/*.json`) — source-of-truth colour, type, spacing values.
|
||
- **CSS** (`src/styles/*.css`) — drop-in stylesheets for any HTML context.
|
||
- **Web components** (`src/elements/*.js`) — Lit-based custom elements that work in React, Django, Vue, Svelte, plain HTML, anywhere.
|
||
- **Adapters** (`adapters/django/`) — optional `{% include %}` partials for Django teams.
|
||
|
||
Framework-agnostic by design. Consumers do **not** re-implement components per framework — they use the same `<wn-button>` everywhere.
|
||
|
||
## Read first
|
||
|
||
| File | What's in it |
|
||
|---|---|
|
||
| `DesignSystemIntroduction.md` | How this repo relates to `whynot-control`, the Claude atelier, and consuming apps. Three-layer architecture, propagation pipeline, versioning, A1 staging. |
|
||
| `MultiFrameworkSupport.md` | How to consume from React, Django, HTMX, Vue, plain HTML. Per-framework setup, SSR specifics, hydration story. |
|
||
| `SKILL.md` | Agent Skill manifest, cross-compatible with Claude Code. Drop into `.claude/skills/` of any consuming repo. |
|
||
| This README | Full design language: tokens, components, content rules, iconography. |
|
||
| `CONTRIBUTING.md` | How to propose, review, and ship a change. |
|
||
| `BOOTSTRAP.md` | First-push instructions for priming this repo. Delete after use. |
|
||
|
||
## Quick start
|
||
|
||
### Plain HTML
|
||
|
||
```html
|
||
<link rel="stylesheet" href="/whynot/colors_and_type.css">
|
||
<link rel="stylesheet" href="/whynot/components.css">
|
||
<script type="module" src="/whynot/index.js"></script>
|
||
|
||
<wn-button variant="primary">Promote prototype</wn-button>
|
||
```
|
||
|
||
### Node-tooled consumer (React, Vite, Next, Vue, …)
|
||
|
||
```sh
|
||
pnpm add git+ssh://git@gitea.example.com/whynot/whynot-design.git#v0.2.0
|
||
```
|
||
|
||
```js
|
||
// once, at app root
|
||
import "@whynot/design/styles/colors_and_type.css";
|
||
import "@whynot/design/styles/components.css";
|
||
import "@whynot/design";
|
||
```
|
||
|
||
```jsx
|
||
<wn-button variant="primary">Promote prototype</wn-button>
|
||
<wn-pipeline active-idx="3"></wn-pipeline>
|
||
```
|
||
|
||
### Django
|
||
|
||
```django
|
||
{# templates/base.html #}
|
||
{% include "whynot/_base_head.html" %}
|
||
|
||
{# any template #}
|
||
<wn-page-header eyebrow="whynot · prototypes" title="Prototypes"></wn-page-header>
|
||
<wn-prototype-card card-id="{{ p.id }}" signal="{{ p.signal }}">
|
||
<span slot="pitch">{{ p.pitch }}</span>
|
||
</wn-prototype-card>
|
||
```
|
||
|
||
See `MultiFrameworkSupport.md` for the full integration story per framework.
|
||
|
||
## What lives where
|
||
|
||
```
|
||
whynot-design/
|
||
├── tokens/ Source-of-truth design tokens (JSON).
|
||
├── src/
|
||
│ ├── styles/
|
||
│ │ ├── colors_and_type.css Layer 1 — tokens + semantic element styles.
|
||
│ │ └── components.css Layer 1 — utility classes for every component.
|
||
│ ├── elements/ Layer 2 — Lit web components.
|
||
│ │ ├── atoms.js wn-button, wn-tag, wn-eyebrow, wn-stamp,
|
||
│ │ │ wn-stage-dot, wn-phase-dot, wn-icon
|
||
│ │ ├── form.js wn-input, wn-textarea, wn-select,
|
||
│ │ │ wn-search-input, wn-field-row
|
||
│ │ ├── layout.js wn-card, wn-modal, wn-table, wn-banner,
|
||
│ │ │ wn-toast, wn-empty-state, wn-breadcrumb
|
||
│ │ ├── chrome.js wn-top-nav, wn-sidebar, wn-page-header,
|
||
│ │ │ wn-pipeline, wn-prototype-card
|
||
│ │ └── icons.js Lucide-derived icon paths.
|
||
│ └── index.js Side-effect import — registers all elements.
|
||
├── adapters/django/ Layer 3 — optional Django partials.
|
||
├── examples/
|
||
│ ├── showcase/index.html Every component on one page. Playwright target.
|
||
│ └── whynot-control/index.html Full app composition (React + custom elements).
|
||
└── assets/ Logo, mark.
|
||
```
|
||
|
||
## Open this locally
|
||
|
||
```sh
|
||
pnpm install
|
||
pnpm showcase # then visit http://localhost:4321/examples/showcase/
|
||
pnpm example # then visit http://localhost:4322 (whynot-control kit)
|
||
```
|
||
|
||
---
|
||
|
||
> The remainder of this README is the full design language — colour reasoning, type stack, content rules, iconography. Treat it as authoritative.
|
||
|
||
|
||
## CONTENT FUNDAMENTALS
|
||
|
||
### Voice
|
||
|
||
The voice is **quiet, structured, evidence-oriented, and careful about overclaiming**. It comes directly out of the `AGENT_RULES.md` and `OPERATING_MODEL.md`:
|
||
|
||
> Agent outputs should be concise, evidence-oriented, explicit about uncertainty, and careful to separate idea, hypothesis, signal, and decision.
|
||
>
|
||
> — `whynot-control/AGENT_RULES.md`
|
||
|
||
The voice should sound like a careful field-notebook — not a startup landing page, not a product manifesto, not marketing copy.
|
||
|
||
### Casing
|
||
|
||
- **Sentence case** for headings, buttons, labels, links — *not* Title Case. ("Smallest useful test", not "Smallest Useful Test".)
|
||
- **lowercase** for the organization name in body: `whynot`, never `WhyNot` or `WHYNOT`. The logo wordmark may use `WhyWhyNot` for legacy reasons.
|
||
- **UPPERCASE** is reserved for short eyebrow labels (`PROTOTYPE`, `SIGNAL S2`, `STAGE`, `IN BETA`) — set in mono, with letterspacing.
|
||
- **`code-case`** for repo names, doc names, folder names: `whynot-control`, `INTENT.md`, `inbox/`. Always in monospace.
|
||
|
||
### Person
|
||
|
||
**Third person, with the project as subject.** Avoid "we", avoid "you", avoid "I".
|
||
|
||
- ✅ "A prototype is a question made tangible."
|
||
- ✅ "The repository helps the user capture unusual but potentially useful ideas."
|
||
- ❌ "We help you discover weird ideas."
|
||
- ❌ "You'll love how easy this is."
|
||
|
||
Direct second-person is reserved for *imperatives in a checklist* (e.g. "Read `INTENT.md`.").
|
||
|
||
### Tone
|
||
|
||
- **Curious, not enthusiastic.** "This may be worth a closer look" beats "🚀 huge if true!".
|
||
- **Hedged, not promotional.** Use *may*, *could*, *seems*, *appears to*. Avoid *will*, *guaranteed*, *the best*.
|
||
- **Distinguish idea / hypothesis / signal / decision.** Never collapse them.
|
||
- **Lack of signal is also information.** Silence is a finding, not a failure.
|
||
|
||
### Phrasing patterns to imitate
|
||
|
||
Lifted from the existing control documents:
|
||
|
||
- "A prototype is a question made tangible."
|
||
- "Signal beats enthusiasm."
|
||
- "Signals are evidence, not vibes."
|
||
- "Capture is not commitment."
|
||
- "Low-cost learning first."
|
||
- "A prototype can be interesting and still be parked."
|
||
|
||
The pattern is: **short declarative claim → small qualifier or counter-claim**. Two beats, no exclamation.
|
||
|
||
### Phrasing to avoid
|
||
|
||
- ❌ "Revolutionize…", "Reimagine…", "Unlock…", "Empower…"
|
||
- ❌ "🚀", "✨", "🔥", any other hype emoji.
|
||
- ❌ "We believe…", "We're on a mission to…"
|
||
- ❌ "Beta — sign up now!" (use "Closed beta. Invitation only.")
|
||
- ❌ Numbers without context ("10x faster"). Use signal-record format instead.
|
||
|
||
### Examples
|
||
|
||
| ❌ Avoid | ✅ Prefer |
|
||
|---|---|
|
||
| "Get started — it's free!" | "Inbox is open. Capture is not commitment." |
|
||
| "Our amazing new prototype" | "Prototype `WNO-014`. Stage: experiment." |
|
||
| "Users love it!" | "S2 — repeated interest, three concrete use-cases." |
|
||
| "Coming soon — sign up!" | "Closed beta. Five seats. Ends 2026-04-01." |
|
||
| "🎉 Launched!" | "Promoted to Helix on 2026-03-12. See `DECISIONS.md`." |
|
||
|
||
### Emoji & punctuation
|
||
|
||
- **No emoji** in body copy, headings, or UI.
|
||
- **`?` and `!`** are the brand's punctuation — they appear in the logo and may appear, sparingly, in display headlines (`try($idea) until success;`, `why? why not!`).
|
||
- **`→`** (U+2192) for "promotes to / goes to" links between stages. Not `->`.
|
||
- **`—`** (em dash) for parenthetical, not `--`.
|
||
|
||
---
|
||
|
||
## VISUAL FOUNDATIONS
|
||
|
||
### Overall posture
|
||
|
||
The system reads like **engineering graph paper** — precise hairlines, lots of whitespace, monospace labels in margins, content blocks that look like fields in a form rather than cards in a feed. The aesthetic is closer to a Bauhaus wall-chart or a `man` page than to a SaaS dashboard.
|
||
|
||
### Color
|
||
|
||
- **Mostly black on white**, with a few flat greys.
|
||
- **One accent only**: a warm yellow (`--hi: #FFE14A`) lifted from the LEGO brick in the logo. It appears as **highlighter / annotation / signal-marker** — never as a button fill, never as a hero background.
|
||
- **No gradients.** Anywhere. Including subtle ones.
|
||
- **No tinted whites** — `--paper` is a true `#FFFFFF`; `--paper-2` and `--paper-3` are barely-warm off-whites (`#FAFAF7`, `#F4F4EF`) reserved for sheets and recessed code blocks.
|
||
- Status colors (S0–S4 signal strength) are **rendered as desaturated grey ramps**, not red/yellow/green. S4 ("commercial signal") is the only one that uses the yellow accent, because that's the threshold where a prototype actually matters commercially.
|
||
|
||
### Type
|
||
|
||
- **Family**: `IBM Plex Sans` for everything UI/body. `IBM Plex Mono` for labels, code, and stage markers. `IBM Plex Serif` for the occasional editorial pull-quote. (See font substitution note in *Fonts* below.)
|
||
- **Weights**: 300 (display only), 400 (body), 500 (UI / headings), 600 (occasional emphasis). Never 700+ — too marketing.
|
||
- **Tracking**: tight on display (`-0.035em`), neutral on body, **wide on uppercase labels** (`0.08em` — this is the one signature move).
|
||
- **Eyebrows everywhere**: short uppercase mono labels above titles (`STAGE`, `SIGNAL`, `PROTOTYPE`). They are the system's main rhythmic element.
|
||
|
||
### Spacing
|
||
|
||
- **4px base unit**, exposed as `--sp-1` (4) through `--sp-10` (128).
|
||
- Generous: a content block typically has `--sp-7` (48px) of internal padding. Lists separated by `--sp-5` (24px) minimum.
|
||
- **Section breaks are big** (`--sp-9`, 96px). Reads like a printed report.
|
||
|
||
### Backgrounds
|
||
|
||
- **`--paper` (#FFFFFF) by default.** Period.
|
||
- **`--paper-2`** for full-width "sheet" sections (e.g. between hero and content).
|
||
- **`--paper-3`** for recessed surfaces only — code blocks, inline pre, inset cards.
|
||
- **No images as backgrounds.** No hand-drawn illustrations. No repeating patterns. No textures. No noise. No grain. (Exception: a 1px hairline grid may be used on a literal "wireframe" mock; see `preview/grid-paper.html`.)
|
||
|
||
### Animation
|
||
|
||
- **Minimal.** This is a document system, not a product UI.
|
||
- Transitions on hover only: `120ms ease` on `text-decoration-color` for links, `border-color` for inputs.
|
||
- **No spring physics, no bounce, no fade-in-on-scroll.** Anything that draws attention is not in the spirit of "low-cost learning first".
|
||
- Exception: the cursor-blink animation on a `<TerminalLine>` is permitted because it's a literal terminal motif.
|
||
|
||
### Hover & press states
|
||
|
||
- **Links** — underline color goes from `--border-strong` to `--fg-1` on hover.
|
||
- **Buttons (primary, dark)** — `--ink` → `--ink-2` on hover. On press, no transform, just `--ink` (back to baseline).
|
||
- **Buttons (secondary, outline)** — border `--border` → `--ink`. On press, background flashes `--bg-3`.
|
||
- **Cards** — *do not have hover states.* They are documents, not interactive surfaces. Exception: prototype cards in an index list get a 1px black left border on hover.
|
||
- **No scale transforms**, no shadow lifts, no glow effects.
|
||
|
||
### Borders
|
||
|
||
- **1px solid `--border` (#E5E5E2)** is the default. Used everywhere.
|
||
- **`--border-strong` (#C9C9C5)** for section dividers and the outline of a primary block.
|
||
- **Hairline `--border-soft` (#F0F0EC)** for internal rules within a card.
|
||
- **No double borders, no inset borders, no dashed borders** except in one specific case: dashed `--border-strong` is used to indicate "placeholder / not yet defined" content (see Components / Empty State).
|
||
|
||
### Shadows
|
||
|
||
- **`--shadow-0`: none.** This is the default. Most cards and panels have no shadow.
|
||
- **`--shadow-1`: `0 1px 0 var(--line)`** — a 1px bottom-line, used in place of bottom-border on sticky headers.
|
||
- **`--shadow-2`: `0 1px 0 var(--line-strong)`** — slightly stronger version.
|
||
- **`--shadow-3`** is reserved for *floating* elements only (a popover, a focus-trapped modal). Even then it's a soft 4-12px diffuse shadow at 10% opacity — never a "card lift" shadow.
|
||
|
||
### Protection gradients vs capsules
|
||
|
||
- **No protection gradients.** Backgrounds are solid; never overlay a gradient to "rescue" text from a busy background, because backgrounds are never busy.
|
||
- **Capsules (pills with rounded ends)** are used only for `--label` and tag elements — never for buttons. Buttons are slightly rounded rectangles (`--r-2`, 4px).
|
||
|
||
### Layout rules
|
||
|
||
- **Single-column reading width** of ~640px for body, ~880px for documents with sidebars.
|
||
- **Constant page padding** of `--sp-7` (48px) on desktop, `--sp-4` (16px) on mobile.
|
||
- **Sticky element**: top navigation bar, height 56px, bottom hairline.
|
||
- **Sidebar (where used)**: 256px fixed width, `--paper-2` background, no border on right (uses whitespace to separate from main).
|
||
- **Grids** for structured data only — never as a "card wall". 12-column with 24px gutters.
|
||
|
||
### Transparency & blur
|
||
|
||
- **Almost never used.** No frosted glass. No backdrop-filter.
|
||
- One permitted use: a `rgba(255,255,255,0.92)` on the sticky top nav so that scrolled content is faintly visible behind it. No blur.
|
||
|
||
### Imagery
|
||
|
||
- **Black & white only.** All photography (when used) is rendered with `filter: grayscale(1) contrast(0.95)` — slightly low-contrast, like a Risograph print.
|
||
- **Aspect ratios**: 4:3 (preferred — feels like a document figure), 1:1 (for portraits / icons). Never 16:9 in body.
|
||
- **No people-stock-photography.** Prefer objects, diagrams, or screenshots.
|
||
- **No AI-generated imagery** unless explicitly labelled as such with a `[generated]` caption.
|
||
|
||
### Corner radii
|
||
|
||
- **`--r-1` (2px)** for inputs and tags.
|
||
- **`--r-2` (4px)** for buttons and small cards.
|
||
- **`--r-3` (8px)** for large cards and modals.
|
||
- **`--r-pill` (999px)** for label capsules only.
|
||
- **`--r-0` (0px / square)** is the default for documents, sheets, and any element wider than ~600px. Big things are square.
|
||
|
||
### Cards
|
||
|
||
A "card" in this system is **a bordered rectangle**, not a shadowed object floating off the page.
|
||
|
||
- 1px `--border` outline.
|
||
- `--paper` background.
|
||
- 4px or 8px radius depending on size.
|
||
- **No shadow.**
|
||
- Internal padding `--sp-5` (24px) minimum.
|
||
- A monospace eyebrow at top-left + a stage label at top-right is the canonical card header.
|
||
- On hover (when interactive): the top-left eyebrow tints to `--fg-1`, and a 2px black bar appears flush against the left edge. Nothing else moves.
|
||
|
||
---
|
||
|
||
## ICONOGRAPHY
|
||
|
||
The codebase did **not** ship an icon font, an icon sprite, or any SVG icons — `whynot-control` is a documents-only repo. The only visual asset is the LEGO-brick logo.
|
||
|
||
### Approach
|
||
|
||
- **Lucide icons via CDN** is the chosen icon set. It matches the system's stroke weight (1.5px), neutral geometry, and "wireframe artefact" feel better than Material, Heroicons, or Phosphor. Load with `<script src="https://unpkg.com/lucide@latest"></script>` and call `lucide.createIcons()`.
|
||
- **Stroke weight is always 1.5px.** Override Lucide's default 2px via `stroke-width="1.5"` on each `<svg>` or via CSS.
|
||
- **Color**: `currentColor`. Icons inherit from text. No two-tone, no fills.
|
||
- **Size**: 16px (inline), 20px (button), 24px (heading-adjacent), 32px (feature). Never larger — large icons read as decoration, and decoration is not in the spirit.
|
||
|
||
### When to use icons
|
||
|
||
- In navigation labels, button labels, and inline status — **only when the icon adds parsing speed**. If the word reads faster than the icon, skip the icon.
|
||
- In document margins to mark stage transitions (e.g. `→ Helix`).
|
||
- **Not** as decorative chrome on cards.
|
||
- **Not** as "feature icons" in a 3-up grid on a marketing page (this system has no marketing pages).
|
||
|
||
### Emoji
|
||
|
||
- **Never.** Emoji are not used anywhere — in copy, labels, alt text, or as fallback for missing icons.
|
||
|
||
### Unicode characters used as icons
|
||
|
||
These are allowed and preferred over raster icons in some contexts:
|
||
|
||
| Char | Used for |
|
||
|---|---|
|
||
| `→` | "promotes to" / pipeline arrow |
|
||
| `←` | back / previous |
|
||
| `·` | inline bullet separator (in nav, in meta lines) |
|
||
| `—` | em dash, in metadata |
|
||
| `?` `!` | the brand's signature punctuation, in display headlines only |
|
||
| `§` | section marker, in long documents |
|
||
|
||
### The logo
|
||
|
||
- The primary mark is **the LEGO brick with `?` and `!` underneath**, in pure black & white. `assets/whynot-logo.png` (300×300 raster, transparent background).
|
||
- For very small uses (favicon, footer mark), a **simplified mark** is recommended: just the `?!` pair in `IBM Plex Sans 600`, with the brick implied by a 2×4 dot grid above. See `preview/logo.html`.
|
||
- The brick should **never be coloured** (no LEGO-red, LEGO-blue, etc). It is always black-outlined on white, or white-outlined on black.
|
||
- Minimum size: 32px square. Below that, fall back to the `?!` wordmark.
|
||
|
||
---
|
||
|
||
## A note on font substitution
|
||
|
||
The control repo did not ship font files. **IBM Plex Sans / Mono / Serif** were chosen as a fresh pairing because:
|
||
|
||
- The "Plex" family was designed by IBM as an explicitly *neutral, technical-document* family — the same use-case as this system.
|
||
- All three (sans, mono, serif) share metrics, so they mix cleanly in templates and tables.
|
||
- They are openly licensed (SIL OFL) and available on Google Fonts.
|
||
|
||
Plex is currently loaded from Google Fonts (see top of `colors_and_type.css`). For offline use, drop the `.woff2` files into `fonts/` and swap the `@import` for a local `@font-face` block.
|
||
|
||
> **🟨 Substitution flagged**: there was no specified brand font; IBM Plex is a choice made here. If `whynot` later adopts a different brand font, replace `--ff-sans` / `--ff-mono` / `--ff-serif` in `colors_and_type.css` and everything downstream will follow.
|