sheleg-design-skill 1.46.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +5 -4
- package/bin/cli.js +3 -2
- package/cursor/rules/sheleg-design.mdc +3 -1
- package/kits/patchbay/.design-sync/config.json +14 -0
- package/kits/patchbay/.design-sync/conventions.md +38 -0
- package/kits/patchbay/package.json +14 -0
- package/kits/patchbay/src/Button.md +17 -0
- package/kits/patchbay/src/Button.tsx +33 -0
- package/kits/patchbay/src/Card.md +18 -0
- package/kits/patchbay/src/Card.tsx +23 -0
- package/kits/patchbay/src/Chip.md +15 -0
- package/kits/patchbay/src/Chip.tsx +21 -0
- package/kits/patchbay/src/Diagram.md +39 -0
- package/kits/patchbay/src/Diagram.tsx +91 -0
- package/kits/patchbay/src/Heading.md +15 -0
- package/kits/patchbay/src/Heading.tsx +13 -0
- package/kits/patchbay/src/Rule.md +12 -0
- package/kits/patchbay/src/Rule.tsx +8 -0
- package/kits/patchbay/src/Stat.md +17 -0
- package/kits/patchbay/src/Stat.tsx +26 -0
- package/kits/patchbay/src/StatusDot.md +19 -0
- package/kits/patchbay/src/StatusDot.tsx +15 -0
- package/kits/patchbay/src/index.ts +8 -0
- package/kits/patchbay/src/styles.css +272 -0
- package/kits/patchbay/tsconfig.json +15 -0
- package/package.json +2 -2
- package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
- package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +31 -0
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +4 -3
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
- package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +3 -2
- package/plugins/sheleg-design/skills/sheleg-design/styles/patchbay.md +418 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/patchbay.css +137 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,92 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.47.0] - 2026-08-22
|
|
10
|
+
|
|
11
|
+
### `patchbay` — the thirty-second pack, and the first read off a page with no tokens at all
|
|
12
|
+
|
|
13
|
+
Measured 2026-08-22 off <https://nautilustrader.io/> with `getComputedStyle` on the running
|
|
14
|
+
page. Named for its register under ADR-0001 — named ports, drawn cords, live signal — not for
|
|
15
|
+
the source brand.
|
|
16
|
+
|
|
17
|
+
**The reference declares zero custom properties.** It is Material UI with Emotion, so there was
|
|
18
|
+
no author-side vocabulary to lift and every value in the token layer was read off a painted
|
|
19
|
+
element. Two of the pack's findings come straight out of that: the MUI theme's own defaults
|
|
20
|
+
leak through where a variant was not overridden, so the page's **59.2px hero is tracked
|
|
21
|
+
+0.555px** — positive tracking on a display line, two selectors away from designed styles that
|
|
22
|
+
track −0.3px — and **fourteen text nodes render in Roboto**, which is not one of the three fonts
|
|
23
|
+
the page loads.
|
|
24
|
+
|
|
25
|
+
**The signature element is a live architecture diagram, and it is measured rather than
|
|
26
|
+
admired.** 21 cords carrying 32 particles, distributed 12 / 7 / 2 at one, two and three
|
|
27
|
+
particles each. Every multi-particle cord divides its own period evenly: the seven pairs are
|
|
28
|
+
offset by exactly half their duration — 1.25s on a 2.5s cord, all seven — and both triples by a
|
|
29
|
+
third, at 0.4 / 1.2333 / 2.0667 and 0.5 / 1.3333 / 2.1667. Consecutive cords start 0.1s apart,
|
|
30
|
+
so the board has no visible beginning. Each cord is an inline `<svg>` with a path, an endpoint
|
|
31
|
+
dot, an arrowhead marker and one `<circle>` per particle driven by `<animateMotion>` — no
|
|
32
|
+
JavaScript, no library, one element per dot.
|
|
33
|
+
|
|
34
|
+
Three corrections the arithmetic produced:
|
|
35
|
+
|
|
36
|
+
* **The reference paints two muted greys and only one of them is legible.** `#848895` is
|
|
37
|
+
4.86:1; `#686B75`, in 13 text nodes, is **3.23:1**. There is no room for a corrected third
|
|
38
|
+
step below 4.86, so the pack ships **one** muted tier and says which token replaces the other
|
|
39
|
+
in each of its two uses.
|
|
40
|
+
* **The diagram's group labels are 2.70:1** — 9px mono uppercase at 30% white, the least
|
|
41
|
+
legible text on the page. Corrected to 50%, which is 5.19:1 and which the reference already
|
|
42
|
+
uses for its own diagram legend further down. The ladder already had the step.
|
|
43
|
+
* **The status set is derived and its constraint is unusual.** The accent is a mint-cyan, so
|
|
44
|
+
the ordinary dark-UI green collides with it under dichromacy: `instrument-console`'s `#46D39A`
|
|
45
|
+
separates from this accent by **3.51** under simulated CVD against a floor of 8. Every
|
|
46
|
+
candidate that clears the floor is light, which is why `--ok` sits above `--warn` and
|
|
47
|
+
`--danger` in luminance rather than beside them.
|
|
48
|
+
|
|
49
|
+
### The reduced-motion doctrine gained the three holes this page measures
|
|
50
|
+
|
|
51
|
+
`MOTION_DOCTRINE.md` §9 has always said an animation without a reduced-motion path is a bug.
|
|
52
|
+
What it did not say is that the usual one-rule remedy — zero every duration — **cannot reach
|
|
53
|
+
three layers**, all three of them live on this one reference:
|
|
54
|
+
|
|
55
|
+
* **It cannot stop SMIL.** `<animateMotion>` is not a CSS animation and does not read
|
|
56
|
+
`animation-duration`. All 32 particles keep moving with the preference on. `pauseAnimations()`
|
|
57
|
+
is the fix and it has to be wired by hand; `kits/patchbay/src/Diagram.tsx` wires it.
|
|
58
|
+
* **It cannot stop JavaScript.** The reference's reveal writes `opacity` and `transform` inline
|
|
59
|
+
per scroll frame across 48 wrappers, so with the preference on the content is **still hidden
|
|
60
|
+
until scrolled**.
|
|
61
|
+
* **It teleports a loop that does not end where it began.** Collapsing to `.01ms` jumps to the
|
|
62
|
+
final keyframe. That is safe here only because all four ambient light loops are written with
|
|
63
|
+
`0%` and `100%` identical — so write perpetual loops that way and the cheap remedy stops being
|
|
64
|
+
a gamble.
|
|
65
|
+
|
|
66
|
+
### What the reference gets wrong, kept because a pack that only admires is not evidence
|
|
67
|
+
|
|
68
|
+
* **`0+` renders on a live page, in two separate sections.** The count-up animates from zero and
|
|
69
|
+
has no state for *the fetch did not return*, so the failure renders as a precise, confident,
|
|
70
|
+
wrong number under `GITHUB STARS`, `DOWNLOADS`, `DISCORD MEMBERS` and `TESTING`. `Stat` in the
|
|
71
|
+
kit refuses: an empty value renders the skeleton and announces itself, never a zero.
|
|
72
|
+
* **The reveal freezes on a programmatic jump.** Measured at eight scroll positions from 700px
|
|
73
|
+
to 1700px reached with `scrollTo`: the target stayed `opacity: 0` at every one and the section
|
|
74
|
+
never appeared. Reached by wheel, it reveals normally — which is what makes it a deep-link,
|
|
75
|
+
anchor and print-stylesheet defect rather than a broken page.
|
|
76
|
+
|
|
77
|
+
### Three numbers that were already stale beside this change
|
|
78
|
+
|
|
79
|
+
Found while registering the pack, none of them this run's regression: the slash command said
|
|
80
|
+
**"the twenty-nine are"** over a list of thirty-one — invisible to the count gate because the
|
|
81
|
+
numeral named no noun, which is the exact failure the skeleton's rule 6 warns about, so it now
|
|
82
|
+
names one; `SURFACE_COMPOSITION.md` said **sixteen** token layers declare `@role non-text:` and
|
|
83
|
+
the tree says nineteen; and `FIGMA_BRIDGE.md` said **ten** packs ship a theme twin against a
|
|
84
|
+
derived eleven. The widened remainder in `SKILL.md` went stale for the **third consecutive
|
|
85
|
+
release** and the gate caught it, which is what that check was written for.
|
|
86
|
+
|
|
87
|
+
### ADR-0001 now records that it was broken twice
|
|
88
|
+
|
|
89
|
+
`outrank` and `babylove` both shipped on 2026-08-21 carrying their source brand as the pack
|
|
90
|
+
name, and neither run added an entry. Recorded rather than left to be rediscovered — a register
|
|
91
|
+
showing an unbroken chain of eleven applications was making a claim the library does not
|
|
92
|
+
support. **Neither is renamed:** the ADR's own Consequences make a pack name a public API across
|
|
93
|
+
four channels, which is what makes renaming after release a breaking change.
|
|
94
|
+
|
|
9
95
|
## [1.46.0] - 2026-08-22
|
|
10
96
|
|
|
11
97
|
### `babylove` — the thirty-first pack, and the other end of the same question
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ problem — invented colors, six accent hues, dark mode retrofitted later.
|
|
|
11
11
|
|
|
12
12
|
This skill is the taste layer. It gives a coding agent **one motion
|
|
13
13
|
methodology** for cinematic, scroll-driven pages, **a motion doctrine** that
|
|
14
|
-
decides whether to animate before it decides how, and **thirty-
|
|
14
|
+
decides whether to animate before it decides how, and **thirty-two locked style
|
|
15
15
|
packs** with ready-made design tokens, so what it builds reads as one system
|
|
16
16
|
instead of a pile of effects.
|
|
17
17
|
|
|
@@ -51,6 +51,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
51
51
|
| `briefing-room` | dark 16:9 deck: one blue hue top to bottom (OKLCH), mono slide furniture, 1-bit dithered art, claims as titles | investor & board decks, technical briefings, talks published as a page |
|
|
52
52
|
| `atrium` | warm cream daylight field with no dark bands, one terracotta accent, light serif with italic asides, fluted-glass hero over photography | consumer health, longevity & diagnostics, wellness, premium care, high-trust DTC |
|
|
53
53
|
| `babylove` | white with one orange in six steps and nothing else declared — seven tokens over Tailwind's defaults, two card levels nested at 16 and 8px, no shadow and no dark theme | SEO and AI-visibility SaaS with a long time-to-value — the minimal-system answer to the same brief `outrank` answers maximally |
|
|
54
|
+
| `patchbay` | near-black under a faint 40px grid, one mint-cyan doing every functional job, elevation made of 8% hairlines with no shadow anywhere, and a live architecture diagram whose cords carry SMIL particles | engines, message buses, pipelines, schedulers and open-source front doors — anything whose argument is an architecture rather than a promise |
|
|
54
55
|
| `outrank` | white field with one violet that carries text in both directions, a 5px light ring standing in for a button shadow, and two type families split by how long a thing is read | SEO and growth SaaS, agent-run back-office — the pack for a product that ships a landing and a dashboard at once |
|
|
55
56
|
| `orchard` | warm oat field of rounded slabs, sage brand + one candy-orange action, rounded geometric display, soft-3D pills built from inset light | friendly consumer biotech, DTC wellness, testing kits & supplements |
|
|
56
57
|
| `field-notes` | warm green-cast paper ruled by hairlines, one rust accent, a hero that dissolves into the page instead of ending, numbered mono eyebrows, crop marks, colour that encodes how a claim is known | open-source & developer tools sold on auditability — code intelligence, provenance, evals, agent memory |
|
|
@@ -151,7 +152,7 @@ skills.
|
|
|
151
152
|
| `DESIGN_SYNC_BRIDGE.md` | The Claude Design contract: what a pack sends to claude.ai/design and in what shape, the rule for each of the four reference types, and the border motion does not cross |
|
|
152
153
|
| `FIGMA_BRIDGE.md` | The design↔code contract: how a pack's tokens map onto Figma variable collections and modes, how to implement a design without importing raw values, and what cannot cross the border |
|
|
153
154
|
| `AI_PRODUCT_PATTERNS.md` | The surfaces a model drives: the five states of a call, streaming instead of spinners, latency, provenance and uncertainty, agent confirmations, and the bans that keep it honest |
|
|
154
|
-
| `styles/*.md` | The thirty-
|
|
155
|
+
| `styles/*.md` | The thirty-two style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
155
156
|
| `styles/tokens/*.css` | The ready-made token layer per pack, copied verbatim instead of transcribed (`workbench` and `field-notes` each ship a light `:root` plus a `data-theme="dark"` twin) |
|
|
156
157
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
157
158
|
|
|
@@ -222,7 +223,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
222
223
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
223
224
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
224
225
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
225
|
-
identical in all thirty-
|
|
226
|
+
identical in all thirty-two kits, so switching packs swaps identity rather than API,
|
|
226
227
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
227
228
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
228
229
|
stops an agent inventing motion to fill the silence.
|
|
@@ -268,7 +269,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
268
269
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
269
270
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
270
271
|
it ships the right files is CI, which installs the bundle through **both**
|
|
271
|
-
installers and `diff -r`s the result against the source, then builds all thirty-
|
|
272
|
+
installers and `diff -r`s the result against the source, then builds all thirty-two
|
|
272
273
|
kits.
|
|
273
274
|
|
|
274
275
|
`test/scenarios.md` (T1–T30) is the behavioral harness: fresh subagents given a
|
package/bin/cli.js
CHANGED
|
@@ -234,10 +234,11 @@ ${c("bold", "What it installs")}
|
|
|
234
234
|
DESIGN_SYNC_BRIDGE.md the Claude Design contract (what a pack sends, and
|
|
235
235
|
what does not cross)
|
|
236
236
|
AI_PRODUCT_PATTERNS.md chat / agent / streaming surfaces (honest state)
|
|
237
|
-
styles/ thirty-
|
|
237
|
+
styles/ thirty-two style packs — instrument-console (dark console),
|
|
238
238
|
editorial-luxury (warm editorial), workbench (light/dark
|
|
239
239
|
product UI), briefing-room (dark 16:9 presentation deck),
|
|
240
|
-
atrium (warm cream consumer health),
|
|
240
|
+
atrium (warm cream consumer health), patchbay (dark live
|
|
241
|
+
schematic), babylove (white +
|
|
241
242
|
one orange, seven tokens over Tailwind defaults),
|
|
242
243
|
outrank (white +
|
|
243
244
|
one violet, a landing and a dashboard in one brand),
|
|
@@ -15,7 +15,9 @@ console; editorial-luxury — warm editorial; workbench — quiet light/dark
|
|
|
15
15
|
product UI for dashboards and tools, used standalone without the cinematic
|
|
16
16
|
motion layer; briefing-room — dark 16:9 presentation deck, standalone, slides
|
|
17
17
|
never animate; atrium — warm cream consumer-health field, one terracotta
|
|
18
|
-
accent, fluted-glass hero;
|
|
18
|
+
accent, fluted-glass hero; patchbay — a dark live schematic, one mint-cyan, 8% hairlines,
|
|
19
|
+
no shadow, an architecture diagram whose cords carry SMIL particles;
|
|
20
|
+
babylove — seven brand tokens over Tailwind
|
|
19
21
|
defaults, two nested card levels, first-class disconnected states, for
|
|
20
22
|
SEO and AI-visibility SaaS; outrank — a marketing page and a working tool in
|
|
21
23
|
one brand, white with one violet, for SEO and growth SaaS; orchard — friendly
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/patchbay",
|
|
3
|
+
"globalName": "ShelegPatchbay",
|
|
4
|
+
"shape": "package",
|
|
5
|
+
"buildCmd": "npm run build",
|
|
6
|
+
"srcDir": "src",
|
|
7
|
+
"tsconfig": "tsconfig.json",
|
|
8
|
+
"cssEntry": "src/styles.css",
|
|
9
|
+
"docsDir": "src",
|
|
10
|
+
"readmeHeader": ".design-sync/conventions.md",
|
|
11
|
+
"guidelinesGlob": [
|
|
12
|
+
"guidelines/*.md"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Patchbay — what the design agent may not do
|
|
2
|
+
|
|
3
|
+
A near-black field, one mint-cyan, and a live schematic, measured off
|
|
4
|
+
<https://nautilustrader.io/> on 2026-08-22 with `getComputedStyle`. The
|
|
5
|
+
reference declares no custom properties at all, so every value in the token
|
|
6
|
+
layer was read off a painted element rather than lifted from a stylesheet.
|
|
7
|
+
|
|
8
|
+
## Bans, each with a measurement behind it
|
|
9
|
+
|
|
10
|
+
- **No shadow, anywhere.** Not on a card, not on a button, not on the nav. The
|
|
11
|
+
reference paints none. Elevation is the hairline ladder — 8% white — and a
|
|
12
|
+
shadow reads as a different design system bolted on.
|
|
13
|
+
- **No white on the accent.** `#00CFBE` takes white at **1.97:1**. A filled
|
|
14
|
+
accent control takes `--on-accent` (9.81:1). The reference never fills one.
|
|
15
|
+
- **No second functional hue.** `--accent-far` is one stop in one button's
|
|
16
|
+
gradient. In a diagram, colour means *belongs to the system*; a second hue
|
|
17
|
+
makes the drawing lie.
|
|
18
|
+
- **No `opacity` for a disabled state.** It composites against whatever is
|
|
19
|
+
behind it and the resulting ratio is unknowable. Name the pair.
|
|
20
|
+
- **No 30% white label.** The reference sets its diagram group labels at 9px in
|
|
21
|
+
`rgba(255,255,255,.30)` — **2.70:1**. Use 50%, which is 5.19:1 and which the
|
|
22
|
+
reference already uses for its own legend.
|
|
23
|
+
- **No counter that renders zero when the fetch failed.** The reference ships
|
|
24
|
+
`0+` under `GITHUB STARS` on a live page. `Stat` here refuses.
|
|
25
|
+
- **No SMIL animation without a pause path.** CSS `animation-duration` does not
|
|
26
|
+
reach `<animateMotion>`, so a blanket reduced-motion rule cannot stop it.
|
|
27
|
+
`Diagram` calls `pauseAnimations()`.
|
|
28
|
+
|
|
29
|
+
## Two shapes that are easy to get wrong
|
|
30
|
+
|
|
31
|
+
- **The card is a light, not a fill.** `radial-gradient(circle at 50% 0,
|
|
32
|
+
--card-lit, --card-base)` with a 1px border in the accent at 14%. The centre
|
|
33
|
+
stop is a teal-black, so the card reads as lit from its top edge. Flattening
|
|
34
|
+
it to a solid background loses the whole surface idea.
|
|
35
|
+
- **The node's tint is its only variable.** Every port in the diagram is the
|
|
36
|
+
same box at the same radius with the same type. The ones that belong to the
|
|
37
|
+
system are tinted with the accent; everything else is tinted white. Changing
|
|
38
|
+
the size or the radius to signal importance breaks the drawing's grammar.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/patchbay",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
|
|
10
|
+
"files": ["dist", "src"],
|
|
11
|
+
"scripts": { "build": "tsc -p tsconfig.json" },
|
|
12
|
+
"peerDependencies": { "react": ">=18" },
|
|
13
|
+
"devDependencies": { "typescript": "^5.6.0", "@types/react": "^18.3.0" }
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Actions
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Three variants and none of them is a fill. `primary` is a 135° accent-to-blue
|
|
6
|
+
wash behind a **1.5px `--accent-rim`** border — 3.10:1, which clears the
|
|
7
|
+
boundary floor by 0.10 — with the label in `--ink`. Do not thin that border and
|
|
8
|
+
do not drop its alpha: it is the only edge the control has.
|
|
9
|
+
|
|
10
|
+
Disabled is a named pair (`--ink-soft` on `--wash`, 4.60:1), never an
|
|
11
|
+
`opacity` multiplier, because a multiplier composites against whatever sits
|
|
12
|
+
behind and the ratio then cannot be computed.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<Button variant="primary">Explore docs</Button>
|
|
16
|
+
<Button>Join the channel</Button>
|
|
17
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ButtonProps {
|
|
4
|
+
/** `primary` is the accent fill — at most one per view. */
|
|
5
|
+
variant?: 'primary' | 'secondary' | 'ghost';
|
|
6
|
+
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onClick?: () => void;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** In this pack `primary` is a WASH and a 1.5px rim, not a fill — the reference
|
|
14
|
+
* never fills its accent, because white on `--accent` is 1.97:1. */
|
|
15
|
+
export function Button({
|
|
16
|
+
variant = 'secondary',
|
|
17
|
+
size = 'md',
|
|
18
|
+
disabled = false,
|
|
19
|
+
onClick,
|
|
20
|
+
children,
|
|
21
|
+
className,
|
|
22
|
+
}: ButtonProps) {
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
className={['pb-btn', `pb-btn--${variant}`, `pb-btn--${size}`, className].filter(Boolean).join(' ')}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**A light, not a fill.** The background is
|
|
6
|
+
`radial-gradient(circle at 50% 0, --card-lit, --card-base)` — a teal-black at
|
|
7
|
+
the top edge falling to near-black — with a 1px border in the accent at 14% and
|
|
8
|
+
no shadow at any state. The card reads as illuminated rather than raised.
|
|
9
|
+
|
|
10
|
+
Padding is 24px, which is larger than the 14px radius, so **an element flush
|
|
11
|
+
against that padding has no concentric radius**: it is a rectangle. Reaching for
|
|
12
|
+
`--r-node` there produces two curves that are not concentric.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<Card title="Message bus" meta="pub/sub · req/res">
|
|
16
|
+
<p>Every engine event crosses it.</p>
|
|
17
|
+
</Card>
|
|
18
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CardProps {
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Right-aligned metadata on the title row: a count, an id, a timestamp. */
|
|
6
|
+
meta?: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Card({ title, meta, children, className }: CardProps) {
|
|
12
|
+
return (
|
|
13
|
+
<section className={['pb-card', className].filter(Boolean).join(' ')}>
|
|
14
|
+
{(title || meta) && (
|
|
15
|
+
<header className="pb-card__head">
|
|
16
|
+
{title && <h3 className="pb-card__title">{title}</h3>}
|
|
17
|
+
{meta && <span className="pb-card__meta">{meta}</span>}
|
|
18
|
+
</header>
|
|
19
|
+
)}
|
|
20
|
+
{children}
|
|
21
|
+
</section>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The pack's version chip: a pill at `--accent-wash` behind a 25% accent border,
|
|
6
|
+
set in the mono stack at 12px/500 in `--accent` — which it may be, because the
|
|
7
|
+
accent is 8.74:1 on the field.
|
|
8
|
+
|
|
9
|
+
Its job in the hero is to carry a **machine-checkable fact**: a package name, a
|
|
10
|
+
version, an age. That is the pack's opening move — the page proves it is alive
|
|
11
|
+
instead of claiming it.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Chip tone="accent">pypi · v2.0.0rc3 · 1d ago</Chip>
|
|
15
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ChipProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
selected?: boolean;
|
|
6
|
+
tone?: 'neutral' | 'accent';
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Chip({ children, selected = false, tone = 'neutral', className }: ChipProps) {
|
|
11
|
+
return (
|
|
12
|
+
<span
|
|
13
|
+
className={['pb-chip', tone === 'accent' ? 'pb-chip--accent' : undefined, className]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(' ')}
|
|
16
|
+
aria-pressed={selected || undefined}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</span>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**The live architecture diagram** — the thing a page in this pack is remembered
|
|
6
|
+
by, and the reason the pack is called `patchbay`.
|
|
7
|
+
|
|
8
|
+
Measured off the reference so it can be rebuilt: **21 cords carrying 32
|
|
9
|
+
particles**, distributed 12 cords with one, 7 with two, 2 with three. Every
|
|
10
|
+
multi-particle cord divides its own period **evenly** — the seven pairs are
|
|
11
|
+
offset by exactly half their duration (1.25s on a 2.5s cord, all seven) and both
|
|
12
|
+
triples by a third (0.4 / 1.2333 / 2.0667 and 0.5 / 1.3333 / 2.1667). That is
|
|
13
|
+
what `(n * dur) / count` reproduces. Consecutive cords start **0.1s** apart, so
|
|
14
|
+
the board has no visible beginning.
|
|
15
|
+
|
|
16
|
+
Three things that will bite:
|
|
17
|
+
|
|
18
|
+
- **`<animateMotion>` has no `keyPoints`**, so a particle moves at a constant
|
|
19
|
+
*parametric* rate rather than a constant visual speed — it appears to speed up
|
|
20
|
+
through the curved middle of a bezier. On shallow curves that reads as life; on
|
|
21
|
+
a tight curve it reads as a bug. Keep the control points shallow.
|
|
22
|
+
- **CSS cannot pause SMIL.** `animation-duration: .01ms` under
|
|
23
|
+
`prefers-reduced-motion` does not reach these dots, which is exactly what the
|
|
24
|
+
reference ships. The effect above calls `pauseAnimations()` and listens for the
|
|
25
|
+
preference changing.
|
|
26
|
+
- **`kind="replay"` is a different EDGE, not a quieter one.** The reference uses
|
|
27
|
+
dashed for its event-replay path while every live edge is solid. Dashing a
|
|
28
|
+
cord to de-emphasise it makes the drawing say something untrue.
|
|
29
|
+
|
|
30
|
+
The diagram is `role="img"` with a label, and the port names must also exist as
|
|
31
|
+
real text in the DOM beside it so a screen reader gets the topology as a list.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Diagram width={943} height={713} title="Engine topology" cords={[
|
|
35
|
+
{ d: 'M 60 110 C 60 150, 87 130, 87 170', dur: 2.5, particles: 2 },
|
|
36
|
+
{ d: 'M 87 170 C 87 240, 300 260, 470 300', dur: 3.5 },
|
|
37
|
+
{ d: 'M 470 300 C 300 380, 120 360, 87 300', dur: 3, kind: 'replay' },
|
|
38
|
+
]} />
|
|
39
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface DiagramCord {
|
|
5
|
+
/** An SVG path command string. Keep the control points shallow — see the doc. */
|
|
6
|
+
d: string;
|
|
7
|
+
/** Seconds for one traversal. The pack's measured band is 2–3.5s. */
|
|
8
|
+
dur?: number;
|
|
9
|
+
/** Particles on this cord. They are phase-spaced evenly across `dur`. */
|
|
10
|
+
particles?: 1 | 2 | 3;
|
|
11
|
+
/** A dashed cord is a DIFFERENT KIND of edge, never a quieter one. */
|
|
12
|
+
kind?: 'live' | 'replay';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DiagramProps {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
cords: DiagramCord[];
|
|
19
|
+
/** Index of this cord in the board, used for the 0.1s per-cord stagger. */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
title: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The pack's signature element.
|
|
26
|
+
*
|
|
27
|
+
* Two things here are load-bearing and easy to lose:
|
|
28
|
+
*
|
|
29
|
+
* 1. Particles are SVG `<animateMotion>`, which is declarative — no rAF loop,
|
|
30
|
+
* no library, one element per dot.
|
|
31
|
+
* 2. SMIL does NOT read CSS `animation-duration`, so a
|
|
32
|
+
* `prefers-reduced-motion` media query cannot stop it. That is the defect
|
|
33
|
+
* the reference ships. `pauseAnimations()` below is the fix, and it is the
|
|
34
|
+
* reason this component needs an effect at all.
|
|
35
|
+
*/
|
|
36
|
+
export function Diagram({ width, height, cords, children, title, className }: DiagramProps) {
|
|
37
|
+
const root = useRef<SVGSVGElement>(null);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const svg = root.current;
|
|
41
|
+
if (!svg) return;
|
|
42
|
+
const query = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
43
|
+
const apply = () => (query.matches ? svg.pauseAnimations() : svg.unpauseAnimations());
|
|
44
|
+
apply();
|
|
45
|
+
query.addEventListener('change', apply);
|
|
46
|
+
return () => query.removeEventListener('change', apply);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<svg
|
|
51
|
+
ref={root}
|
|
52
|
+
className={['pb-diagram', className].filter(Boolean).join(' ')}
|
|
53
|
+
width={width}
|
|
54
|
+
height={height}
|
|
55
|
+
viewBox={`0 0 ${width} ${height}`}
|
|
56
|
+
role="img"
|
|
57
|
+
aria-label={title}
|
|
58
|
+
>
|
|
59
|
+
<defs>
|
|
60
|
+
<marker id="pb-arrow" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
|
|
61
|
+
<polygon className="pb-diagram__head" points="0 0, 6 3, 0 6" />
|
|
62
|
+
</marker>
|
|
63
|
+
</defs>
|
|
64
|
+
{cords.map((cord, i) => {
|
|
65
|
+
const dur = cord.dur ?? 2.5;
|
|
66
|
+
const count = cord.particles ?? 1;
|
|
67
|
+
const stagger = (i % 10) * 0.1;
|
|
68
|
+
return (
|
|
69
|
+
<g key={i}>
|
|
70
|
+
<path
|
|
71
|
+
className={`pb-diagram__cord pb-diagram__cord--${cord.kind ?? 'live'}`}
|
|
72
|
+
d={cord.d}
|
|
73
|
+
markerEnd="url(#pb-arrow)"
|
|
74
|
+
/>
|
|
75
|
+
{Array.from({ length: count }, (unused, n) => (
|
|
76
|
+
<circle key={n} className="pb-diagram__dot" r="2">
|
|
77
|
+
<animateMotion
|
|
78
|
+
path={cord.d}
|
|
79
|
+
dur={`${dur}s`}
|
|
80
|
+
begin={`${(stagger + (n * dur) / count).toFixed(4)}s`}
|
|
81
|
+
repeatCount="indefinite"
|
|
82
|
+
/>
|
|
83
|
+
</circle>
|
|
84
|
+
))}
|
|
85
|
+
</g>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
{children}
|
|
89
|
+
</svg>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Tracked **negative above 24px and zero below** — which is the pack's decision,
|
|
6
|
+
not the reference's behaviour. MUI's theme default `letter-spacing: 0.00938em`
|
|
7
|
+
leaks into everything the reference did not override, so its 59.2px hero is
|
|
8
|
+
tracked **+0.555px**: positive tracking on a display line, two selectors away
|
|
9
|
+
from designed styles that track −0.3px.
|
|
10
|
+
|
|
11
|
+
The display face steps rather than clamps: 2.8rem below 900px, 3.7rem above.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Heading level={1}>The fastest open-source trading engine</Heading>
|
|
15
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps {
|
|
4
|
+
/** 1 = page title (28px), 2 = section (20px), 3 = card title (15px). */
|
|
5
|
+
level?: 1 | 2 | 3;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Heading({ level = 2, children, className }: HeadingProps) {
|
|
11
|
+
const Tag = (['h1', 'h2', 'h3'] as const)[level - 1];
|
|
12
|
+
return <Tag className={['pb-h', `pb-h--${level}`, className].filter(Boolean).join(' ')}>{children}</Tag>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The whole elevation model. `hairline` is 8% white at 1.25:1, `strong` is 10% at
|
|
6
|
+
1.34:1, and there is nothing else — no shadow appears anywhere in the reference,
|
|
7
|
+
so separation is a line or it is space.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<Rule />
|
|
11
|
+
<Rule tone="strong" />
|
|
12
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**The component that refuses to lie.** The reference ships `0+` under
|
|
6
|
+
`GITHUB STARS`, `DOWNLOADS` and `DISCORD MEMBERS` on a live page, and `0+`
|
|
7
|
+
under `TESTING` eight thousand pixels further down: its count-up animates from
|
|
8
|
+
zero and has no state for *the fetch did not return*, so the failure renders as
|
|
9
|
+
a precise, confident, wrong number.
|
|
10
|
+
|
|
11
|
+
An empty `value` here renders the pulsing skeleton at the figure's real
|
|
12
|
+
geometry and announces itself to a screen reader. It never renders a zero.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<Stat value="9,412" label="GitHub stars" source="api.github.com · hourly" />
|
|
16
|
+
<Stat value="" label="Downloads" />
|
|
17
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface StatProps {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
/** Where the figure came from — a query, a window, a dashboard. */
|
|
5
|
+
source?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** The reference renders `0+` under GITHUB STARS when its fetch does not return.
|
|
10
|
+
* A confident wrong number is worse than an absent one, so an empty `value`
|
|
11
|
+
* renders the skeleton and never a zero. */
|
|
12
|
+
export function Stat({ value, label, source, className }: StatProps) {
|
|
13
|
+
const pending = value.trim() === '';
|
|
14
|
+
return (
|
|
15
|
+
<div className={['pb-stat', pending ? 'pb-stat--pending' : undefined, className].filter(Boolean).join(' ')}>
|
|
16
|
+
{pending ? (
|
|
17
|
+
<span className="pb-stat__skeleton" aria-hidden="true" />
|
|
18
|
+
) : (
|
|
19
|
+
<span className="pb-stat__value">{value}</span>
|
|
20
|
+
)}
|
|
21
|
+
<span className="pb-stat__label">{label}</span>
|
|
22
|
+
{source && <span className="pb-stat__source">{source}</span>}
|
|
23
|
+
{pending && <span className="pb-sr">{label} is still loading</span>}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**`label` has no default and no optional marker, which is the whole point.**
|
|
6
|
+
Status in this pack is never by colour alone.
|
|
7
|
+
|
|
8
|
+
The three status hues are a **pack decision** — the reference paints no success,
|
|
9
|
+
warning or error state anywhere — and they are derived against a constraint most
|
|
10
|
+
packs do not have: the accent is a mint-cyan, so the ordinary dark-UI green
|
|
11
|
+
collides with it under dichromacy. `instrument-console`'s `#46D39A` separates
|
|
12
|
+
from this accent by **3.51** under simulated CVD against a floor of 8, and was
|
|
13
|
+
rejected for it. Every candidate that clears the floor is light, which is why
|
|
14
|
+
`--ok` sits above `--warn` and `--danger` in luminance rather than beside them.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<StatusDot state="ok" label="Connected" />
|
|
18
|
+
<StatusDot state="danger" label="Venue unreachable" />
|
|
19
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface StatusDotProps {
|
|
2
|
+
state: 'ok' | 'warn' | 'danger';
|
|
3
|
+
/** Mandatory. The word carries the state; the dot only repeats it. */
|
|
4
|
+
label: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function StatusDot({ state, label, className }: StatusDotProps) {
|
|
9
|
+
return (
|
|
10
|
+
<span className={['pb-status', `pb-status--${state}`, className].filter(Boolean).join(' ')}>
|
|
11
|
+
<span className="pb-status__dot" aria-hidden="true" />
|
|
12
|
+
{label}
|
|
13
|
+
</span>
|
|
14
|
+
);
|
|
15
|
+
}
|