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.
Files changed (39) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +5 -4
  3. package/bin/cli.js +3 -2
  4. package/cursor/rules/sheleg-design.mdc +3 -1
  5. package/kits/patchbay/.design-sync/config.json +14 -0
  6. package/kits/patchbay/.design-sync/conventions.md +38 -0
  7. package/kits/patchbay/package.json +14 -0
  8. package/kits/patchbay/src/Button.md +17 -0
  9. package/kits/patchbay/src/Button.tsx +33 -0
  10. package/kits/patchbay/src/Card.md +18 -0
  11. package/kits/patchbay/src/Card.tsx +23 -0
  12. package/kits/patchbay/src/Chip.md +15 -0
  13. package/kits/patchbay/src/Chip.tsx +21 -0
  14. package/kits/patchbay/src/Diagram.md +39 -0
  15. package/kits/patchbay/src/Diagram.tsx +91 -0
  16. package/kits/patchbay/src/Heading.md +15 -0
  17. package/kits/patchbay/src/Heading.tsx +13 -0
  18. package/kits/patchbay/src/Rule.md +12 -0
  19. package/kits/patchbay/src/Rule.tsx +8 -0
  20. package/kits/patchbay/src/Stat.md +17 -0
  21. package/kits/patchbay/src/Stat.tsx +26 -0
  22. package/kits/patchbay/src/StatusDot.md +19 -0
  23. package/kits/patchbay/src/StatusDot.tsx +15 -0
  24. package/kits/patchbay/src/index.ts +8 -0
  25. package/kits/patchbay/src/styles.css +272 -0
  26. package/kits/patchbay/tsconfig.json +15 -0
  27. package/package.json +2 -2
  28. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  29. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  30. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  31. package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +2 -2
  32. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  33. package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +31 -0
  34. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +4 -3
  35. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
  36. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +3 -2
  37. package/plugins/sheleg-design/skills/sheleg-design/styles/patchbay.md +418 -0
  38. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
  39. 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-one locked style
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-one style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
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-one kits, so switching packs swaps identity rather than API,
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-one
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-one style packs — instrument-console (dark console),
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), babylove (white +
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; babyloveseven brand tokens over Tailwind
18
+ accent, fluted-glass hero; patchbaya 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,8 @@
1
+ export interface RuleProps {
2
+ tone?: 'hairline' | 'strong';
3
+ className?: string;
4
+ }
5
+
6
+ export function Rule({ tone = 'hairline', className }: RuleProps) {
7
+ return <hr className={['pb-rule', `pb-rule--${tone}`, className].filter(Boolean).join(' ')} />;
8
+ }
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ export * from './Button';
2
+ export * from './Card';
3
+ export * from './Chip';
4
+ export * from './Diagram';
5
+ export * from './Heading';
6
+ export * from './Rule';
7
+ export * from './Stat';
8
+ export * from './StatusDot';