sheleg-design-skill 1.54.1 → 1.55.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 (60) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +5 -4
  3. package/bin/cli.js +5 -1
  4. package/cursor/rules/sheleg-design.mdc +8 -0
  5. package/kits/chorus/.design-sync/config.json +14 -0
  6. package/kits/chorus/.design-sync/conventions.md +50 -0
  7. package/kits/chorus/README.md +36 -0
  8. package/kits/chorus/package.json +29 -0
  9. package/kits/chorus/src/Bubble.md +23 -0
  10. package/kits/chorus/src/Bubble.tsx +40 -0
  11. package/kits/chorus/src/Button.md +23 -0
  12. package/kits/chorus/src/Button.tsx +33 -0
  13. package/kits/chorus/src/Capsule.md +20 -0
  14. package/kits/chorus/src/Capsule.tsx +21 -0
  15. package/kits/chorus/src/Card.md +20 -0
  16. package/kits/chorus/src/Card.tsx +23 -0
  17. package/kits/chorus/src/Chip.md +15 -0
  18. package/kits/chorus/src/Chip.tsx +25 -0
  19. package/kits/chorus/src/Delta.md +19 -0
  20. package/kits/chorus/src/Delta.tsx +24 -0
  21. package/kits/chorus/src/Empty.md +18 -0
  22. package/kits/chorus/src/Empty.tsx +19 -0
  23. package/kits/chorus/src/Field.md +16 -0
  24. package/kits/chorus/src/Field.tsx +40 -0
  25. package/kits/chorus/src/GridFrame.md +19 -0
  26. package/kits/chorus/src/GridFrame.tsx +34 -0
  27. package/kits/chorus/src/Heading.md +16 -0
  28. package/kits/chorus/src/Heading.tsx +17 -0
  29. package/kits/chorus/src/NavPill.md +19 -0
  30. package/kits/chorus/src/NavPill.tsx +19 -0
  31. package/kits/chorus/src/Rule.md +14 -0
  32. package/kits/chorus/src/Rule.tsx +12 -0
  33. package/kits/chorus/src/Skeleton.md +16 -0
  34. package/kits/chorus/src/Skeleton.tsx +27 -0
  35. package/kits/chorus/src/Slab.md +18 -0
  36. package/kits/chorus/src/Slab.tsx +17 -0
  37. package/kits/chorus/src/Stat.md +15 -0
  38. package/kits/chorus/src/Stat.tsx +17 -0
  39. package/kits/chorus/src/Sweep.md +17 -0
  40. package/kits/chorus/src/Sweep.tsx +16 -0
  41. package/kits/chorus/src/Well.md +15 -0
  42. package/kits/chorus/src/Well.tsx +16 -0
  43. package/kits/chorus/src/index.ts +37 -0
  44. package/kits/chorus/src/styles.css +878 -0
  45. package/kits/chorus/tsconfig.json +15 -0
  46. package/package.json +2 -2
  47. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  48. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  49. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  50. package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +1 -1
  51. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  52. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +2 -2
  53. package/plugins/sheleg-design/skills/sheleg-design/STYLE_PACK_INDEX.md +3 -2
  54. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +6 -5
  55. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +1 -1
  56. package/plugins/sheleg-design/skills/sheleg-design/styles/chorus.md +474 -0
  57. package/plugins/sheleg-design/skills/sheleg-design/styles/deskmate.md +10 -0
  58. package/plugins/sheleg-design/skills/sheleg-design/styles/surveyor.md +11 -0
  59. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
  60. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/chorus.css +386 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,80 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.55.0] - 2026-08-30
10
+
11
+ ### The thirty-ninth pack — somebody else's question, on a sheet that never stops being ruled
12
+
13
+ `chorus`, extracted from `crowdreply.io`. Warm off-white paper under a construction
14
+ grid that never switches off — two 1px verticals at `x=162` and `x=1278` of 1440,
15
+ crossed by horizontals carrying a small plus at each intersection, 63 rule elements in
16
+ the census, and the same grid continued across the dark band. The set piece is a
17
+ **cut-corner bubble**: 24px on three corners and 0 on the top right, measured on all 24
18
+ instances of one object, holding a stranger's question in the display face.
19
+
20
+ Named for the register per ADR-0001, with `switchboard`, `soapbox`, `quorum` and
21
+ `earshot` weighed and rejected — the record is in the ADR.
22
+
23
+ **Measured, not restated.** The site is Framer, and the token dump and the render
24
+ disagree: of its 52 declared `--token-<uuid>` properties, 15 are referenced once and
25
+ reach no painted surface, so an area-weighted census over all 2,439 elements of an
26
+ 11,750px page decided every value. Read through CDP at 1440×900, an emulated
27
+ 768×1024×2 and an emulated 390×844×2. Ratios computed by importing this repository's
28
+ own palette gate.
29
+
30
+ **Six corrections, each a number the reference produces and the pack refuses.**
31
+
32
+ - The primary CTA fails AA and no size saves it — `#ffffff` on `#f96f4b` is **2.84:1**,
33
+ under the 3:1 large floor too. The fix keeps the brand hue exactly and changes the
34
+ label: `#1b181c` on `#f96f4b` is **6.20:1**. Darkening the coral was rejected.
35
+ - The coral cannot be a word: `#f96f4b` on `#fbfaf9` is **2.72:1**, the link token
36
+ `#ff5d30` is **2.94:1**. `--coral-ink` at 4.59:1 is derived at hue 35.7°.
37
+ - A card is separated from the field by **1.04:1** and nothing else — no border and no
38
+ shadow on any card over 250×150px in the census. Every card gains a 1px `--line`.
39
+ - **Zero `:focus` rules and zero `:focus-visible` rules** across 274,355 bytes, plus two
40
+ `outline: none` declarations. The whole mechanism is the pack's, including the
41
+ dark-surface re-declaration: the ink ring is 1.00:1 against `--slab`.
42
+ - The muted step does not read: `#8a8692` on `#fbfaf9` is **3.41:1** at 14px/400, 38
43
+ instances.
44
+ - **Zero reduced-motion rules against 383 script-set inline transforms** and 1,057
45
+ script-set opacities. The media query the pack adds cannot reach them, so the JS half
46
+ of the contract is stated in the pack rather than implied.
47
+
48
+ Two more facts recorded because they will surprise an implementer: the controls are
49
+ 36px and miss the 44px tap floor, so the kit pads the hit area rather than resizing the
50
+ button; and the declared amber `#ffc300` paints nothing at any of the three widths,
51
+ which is why this pack has three status roles rather than four and says so.
52
+
53
+ **The mint is dark-only, and that is a constraint rather than an omission.** `#36ff94`
54
+ is 13.31:1 in a well and reaches AA on paper only at near-black, which is a different
55
+ colour rather than a step — so the light ladder is a separate answer, not a
56
+ translation.
57
+
58
+ ### Also in this release
59
+
60
+ - Reciprocal forks: `chorus` ↔ `surveyor` (the AI-visibility collision — a reading
61
+ against a conversation) and `chorus` ↔ `deskmate` (the speech-bubble collision —
62
+ whose bubble it is).
63
+ - `kits/chorus`: the six-name spine plus `Bubble`, `GridFrame`, `Slab`, `Well`,
64
+ `Delta`, `NavPill`, `Sweep`, `Field`, `Empty`, `Skeleton`, `Capsule`. Rendered at
65
+ 1440 / 768 / 390 and its computed values read back against the pack's claims before
66
+ the tag; the log is in `docs/evidence/plans/2026-08-30-chorus-pack-acceptance.md`.
67
+ - The kit's button carries no border on any variant: the reference's control is 36px
68
+ with 8px/16px padding and no edge, and a 1px border measured 38px. The ghost
69
+ variant's line is an inset ring instead, so no variant shifts when it swaps.
70
+ - Counted claims moved 38 → 39 across eleven files, and the widened-contract remainder
71
+ moved 32 → 33 — the number that had been stale in two consecutive releases.
72
+ - `SURFACE_COMPOSITION.md`: `chorus` joins the status map, and the accent-role
73
+ paragraph gains the fourth pack that names its accent itself (`--coral`).
74
+
75
+ ### Shipped with a stated debt
76
+
77
+ Scenario **T37** (`chorus` against `surveyor`, with its negative branch) is **written,
78
+ not yet run** — the filing session was instructed not to spawn subagents, so the two
79
+ blind branches could not execute before the tag. Per standing instruction 3 the debt
80
+ ships in the same commit as the artifact and is on the board. The render check behind
81
+ REQ-8 is *not* part of that debt: it ran in full.
82
+
9
83
  ## [1.54.1] - 2026-08-29
10
84
 
11
85
  ### The installer refuses the shadow it documents
package/README.md CHANGED
@@ -31,7 +31,7 @@ problem — invented colors, six accent hues, dark mode retrofitted later.
31
31
 
32
32
  This skill is the taste layer. It gives a coding agent **one motion
33
33
  methodology** for cinematic, scroll-driven pages, **a motion doctrine** that
34
- decides whether to animate before it decides how, and **thirty-eight locked style
34
+ decides whether to animate before it decides how, and **thirty-nine locked style
35
35
  packs** with ready-made design tokens, so what it builds reads as one system
36
36
  instead of a pile of effects.
37
37
 
@@ -78,6 +78,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
78
78
  | `deskmate` | a warm beige working day lit from **one source above the top edge** — every gradient origin in the reference sits at or above `y=0` — where a single four-stop ramp (peach, lilac, violet, deep navy) washes the field, fills a panel and fills one word of a heading, and everything a hand touches is a 56px pill against 32px slabs | **products sold as a colleague rather than a tool** — AI employees and chat-native agents, assistants living in Slack or Teams, delegation and approval surfaces (standalone) |
79
79
  | `test-drive` | warm paper and white cards where **the live product runs inside drawn browser chrome** — traffic-light dots, a real address, the engine on — one coral in two tokens (the brand hue lights, fills and blinks; a darker step carries every body-size word), every control lit by a four-layer shadow in its own hue, a handwritten face and a yellow marker annotating from the margin | **self-serve SaaS sold by handing over the running product** — analytics, dashboards, developer tools with a visible console, indie SaaS (standalone) |
80
80
  | `surveyor` | peach paper where **an unseen terrain is mapped** — fine contour lines as the one texture, flat white and tinted cards with no shadow anywhere, a teal whose text-bearing steps are the reference's own hover ladder, a pink that answers but may not speak, and the product appearing only as flat portraits | **visibility, monitoring and benchmark tools** — AI-search and SEO trackers, share-of-voice and brand-listening platforms sold to marketing teams (standalone) |
81
+ | `chorus` | warm off-white paper under **a construction grid that never switches off** — two 1px verticals crossed by horizontals with a small plus at every intersection, continued across the dark band — where the set piece is **somebody else's question** in a cut-corner bubble, 24px on three corners and 0 on the top right, set in the display face; white cards with a hairline and no shadow, near-black slabs cut 30px into the sheet, one coral that fills but may never be a word | **AI-search visibility, brand monitoring and community marketing** — answer-engine and share-of-voice trackers, social listening, review and reputation tools whose proof is other people's conversations (standalone) |
81
82
  | `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 |
82
83
  | `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 |
83
84
  | `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 |
@@ -178,7 +179,7 @@ skills.
178
179
  | `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 |
179
180
  | `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 |
180
181
  | `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 |
181
- | `styles/*.md` | The thirty-eight style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
182
+ | `styles/*.md` | The thirty-nine style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
182
183
  | `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) |
183
184
  | `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
184
185
 
@@ -249,7 +250,7 @@ cd ./ds-workbench && npm install && npm run build
249
250
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
250
251
  pack's **bans** as the design system's own README, `styles.css` built from
251
252
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
252
- identical in all thirty-eight kits, so switching packs swaps identity rather than API,
253
+ identical in all thirty-nine kits, so switching packs swaps identity rather than API,
253
254
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
254
255
  not cross into Figma: a kit is the static half of a pack, and saying so is what
255
256
  stops an agent inventing motion to fill the silence.
@@ -299,7 +300,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
299
300
  One honest limit: the npx installer is checked by asserting its runtime bundle
300
301
  walker exists, not by reading a file list — it has none by design. What proves
301
302
  it ships the right files is CI, which installs the bundle through **both**
302
- installers and `diff -r`s the result against the source, then builds all thirty-eight
303
+ installers and `diff -r`s the result against the source, then builds all thirty-nine
303
304
  kits.
304
305
 
305
306
  `test/scenarios.md` (T1–T34) is the behavioral harness: fresh subagents given a
package/bin/cli.js CHANGED
@@ -241,7 +241,7 @@ ${c("bold", "What it installs")}
241
241
  DESIGN_SYNC_BRIDGE.md the Claude Design contract (what a pack sends, and
242
242
  what does not cross)
243
243
  AI_PRODUCT_PATTERNS.md chat / agent / streaming surfaces (honest state)
244
- styles/ thirty-eight style packs — instrument-console (dark console),
244
+ styles/ thirty-nine style packs — instrument-console (dark console),
245
245
  editorial-luxury (warm editorial), workbench (light/dark
246
246
  product UI), briefing-room (dark 16:9 presentation deck),
247
247
  atrium (warm cream consumer health), patchbay (dark live
@@ -265,6 +265,10 @@ ${c("bold", "What it installs")}
265
265
  contour lines, flat shadowless cards, a teal that
266
266
  states and a pink that answers, and the product
267
267
  shown only as portraits),
268
+ chorus (warm paper under a crosshair grid, a
269
+ cut-corner bubble carrying somebody else's
270
+ question, near-black slabs cut into the sheet, one
271
+ coral that fills but never writes),
268
272
  babylove (white +
269
273
  one orange, seven tokens over Tailwind defaults),
270
274
  outrank (white +
@@ -138,6 +138,14 @@ detached nav, one teal whose text-bearing steps climb the reference's own hover
138
138
  ladder, a pink that answers but may not speak, TT Fors at a 600 ceiling with tabular
139
139
  numerals, and the product shown only as flat portraits, for visibility, monitoring
140
140
  and benchmark tools sold to marketing teams;
141
+ chorus — warm off-white paper under a construction grid that never switches off, two
142
+ 1px verticals crossed by horizontals with a small plus at every intersection and
143
+ continued across the dark band; the set piece is somebody else's question in a
144
+ cut-corner bubble (24px on three corners, 0 on the top right) set in the display face,
145
+ white cards with no shadow and a hairline edge, near-black slabs cut 30px into the
146
+ sheet, one coral that fills but may never be a word, and a periwinkle that only exists
147
+ in a gradient or on the dark, for AI-search visibility, brand monitoring, social
148
+ listening and community marketing whose proof is other people's conversations;
141
149
  otherwise follow the contract below (self-contained on purpose).
142
150
 
143
151
  ## Whether to animate at all — before how
@@ -0,0 +1,14 @@
1
+ {
2
+ "pkg": "@sheleg-design/chorus",
3
+ "globalName": "ShelegChorus",
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,50 @@
1
+ # Chorus — conventions for a design agent
2
+
3
+ This kit is the built form of the Chorus style pack. Read these before generating
4
+ anything with it; they are the pack's bans, stated where a design tool will see them.
5
+
6
+ ## Colour
7
+
8
+ - **The coral fills and never writes.** `--coral` (#f96f4b) is a fill, a flat block, a
9
+ gradient stop and an icon beside a label that carries the meaning. As a word it is
10
+ 2.72:1 on the field; under a white label it is 2.84:1. Both are below the 3:1
11
+ large-text floor, so no size rescues either. A coral word takes `--coral-ink`
12
+ (#cb441f, 4.59:1 on the field).
13
+ - **The primary button's label is INK**, `--on-coral` at 6.20:1. This is the pack's
14
+ correction to the reference and it keeps the brand hue exactly rather than darkening
15
+ it.
16
+ - **The mint lives only on the dark.** `--good-on-dark` (#36ff94) is 13.31:1 in a
17
+ well and does not exist on paper — held at its own hue it reaches AA on paper only
18
+ at near-black. A green figure on a card takes `--good` (#198400).
19
+ - **Status is never by colour alone.** `--good` and `--danger` are 6.2 apart under
20
+ deuteranopia. Every delta ships its arrow and its number, both.
21
+ - **There is no warn.** Three status roles. The reference declares an amber that paints
22
+ nothing; a fourth severity here would be invented.
23
+ - The periwinkle is a gradient stop and a series line on the dark. It is not a second
24
+ button colour and never carries a word on paper.
25
+
26
+ ## Surface and edge
27
+
28
+ - A card is `--surface` at `--r-card` with a 1px `--line` — **32px padding and 32px
29
+ gap at every width**, including 390. Do not tighten it on narrow screens.
30
+ - Four objects carry a shadow and nothing else may: the bubble, the floating panel,
31
+ the hero deck, the nav. Everything else takes `--line` for its edge.
32
+ - The construction grid never switches off, and it continues across the dark slab.
33
+ The plus at each intersection is part of it.
34
+ - The dark slab is a **surface**, not a theme. The page never inverts.
35
+
36
+ ## Type
37
+
38
+ - Outfit sets two things: the display, and the question inside a bubble. Nothing else.
39
+ - Display tracking **relaxes** as the type shrinks. Do not tighten a small display.
40
+ - Body line-height is 1.7. No italic anywhere; nothing above weight 700.
41
+
42
+ ## Geometry and motion
43
+
44
+ - `--r-bubble` is 24px on three corners and 0 on the top right. It is the pack's whole
45
+ geometry, and it belongs to one object.
46
+ - Nothing lifts, scales or parallaxes; there is no scroll clock; gradients do not move.
47
+ - Loading is a static skeleton, never a spinner and never a shimmer.
48
+ - The reduced-motion contract has a JavaScript half: a reveal observer must read
49
+ `matchMedia('(prefers-reduced-motion: reduce)')` and mount at the final transform.
50
+ The media query in the token layer cannot reach a transform a script sets.
@@ -0,0 +1,36 @@
1
+ # @sheleg-design/chorus
2
+
3
+ The React reference kit for the **Chorus** style pack — warm off-white paper under a
4
+ construction grid that never switches off, where the set piece is somebody else's
5
+ question in a cut-corner bubble.
6
+
7
+ The pack itself is the authority:
8
+ [`styles/chorus.md`](../../plugins/sheleg-design/skills/sheleg-design/styles/chorus.md).
9
+ This kit is one built reading of it, not a second source of truth. `src/styles.css`
10
+ opens with the pack's token layer copied byte for byte from
11
+ `styles/tokens/chorus.css`; everything below the `/* ── components ── */` marker
12
+ consumes `var(--…)` and contains no colour literal.
13
+
14
+ ## Build
15
+
16
+ ```bash
17
+ npm install
18
+ npm run build # tsc only — no bundler, no runtime dependency but React
19
+ ```
20
+
21
+ ## What is here
22
+
23
+ The six-name spine every SHELEG kit shares — `Button`, `Card`, `Chip`, `Stat`,
24
+ `Heading`, `Rule` — with identical props everywhere, so switching packs swaps identity
25
+ rather than API. Then this pack's own: `Bubble` (the signature element), `GridFrame`,
26
+ `Slab`, `Well`, `Delta`, `NavPill`, `Sweep`, `Field`, `Empty`, `Skeleton`, `Capsule`.
27
+
28
+ Each component carries a `.md` beside it with its category and the numbers that
29
+ license its colours.
30
+
31
+ ## The three rules that are easiest to break
32
+
33
+ 1. `--coral` may not be a word, at any size, and its button's label is ink.
34
+ 2. `--r-bubble`'s cut corner belongs to one object and may not be restyled.
35
+ 3. The reduced-motion contract has a JavaScript half — see
36
+ `.design-sync/conventions.md`.
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@sheleg-design/chorus",
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": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.6.0",
27
+ "@types/react": "^18.3.0"
28
+ }
29
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ **The pack's signature element, and the one component that may not be restyled.**
6
+
7
+ `--r-bubble` is 24px on three corners and **0 on the top right** — a speech-bubble
8
+ tail drawn by subtraction, measured on all 24 instances of this object in the
9
+ reference. The question is 24px/600 in `--font-display` at 18.88:1 on `--surface`;
10
+ `--shadow-bubble` is its own three-stop shadow and belongs to nothing else.
11
+
12
+ `surface="slab"` is the same object on the dark side of a pair, `--on-slab` at
13
+ 16.54:1. `mirrored` swaps the cut to the top left for the answering side and for RTL
14
+ — the reference ships only one direction, so the mirror is the pack's decision and is
15
+ labelled as one.
16
+
17
+ Change its corner, its face or its shadow and the pack is gone. If the page has no
18
+ stranger's words to put in it, the page does not want this pack.
19
+
20
+ ```tsx
21
+ <Bubble question="Best CRM for B2B companies?" source="r/sales · 18 Mar" />
22
+ <Bubble question="Have you tried it?" surface="slab" mirrored />
23
+ ```
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface BubbleProps {
4
+ /** The quoted question. Set in the display face — that is the point. */
5
+ question: string;
6
+ /** Where it was asked: a platform name, a subreddit, a date. */
7
+ source?: string;
8
+ /** `paper` is a stranger's card; `slab` is the answering side of a pair. */
9
+ surface?: 'paper' | 'slab';
10
+ /** Mirrors the cut corner for the answering side and for RTL. */
11
+ mirrored?: boolean;
12
+ children?: ReactNode;
13
+ className?: string;
14
+ }
15
+
16
+ export function Bubble({
17
+ question,
18
+ source,
19
+ surface = 'paper',
20
+ mirrored = false,
21
+ children,
22
+ className,
23
+ }: BubbleProps) {
24
+ return (
25
+ <div
26
+ className={[
27
+ 'ch-bubble',
28
+ `ch-bubble--${surface}`,
29
+ mirrored ? 'ch-bubble--mirrored' : null,
30
+ className,
31
+ ]
32
+ .filter(Boolean)
33
+ .join(' ')}
34
+ >
35
+ <p className="ch-bubble__q">{question}</p>
36
+ {source && <p className="ch-bubble__src">{source}</p>}
37
+ {children}
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ 36px tall at `--r-control`, 8px/16px padding on a 14px/500 label, and a fill that
6
+ only ever changes colour — nothing lifts, scales or travels.
7
+
8
+ `primary` is the reference's own coral with the pack's correction: `--coral` fill and
9
+ an `--on-coral` label at 6.20:1. **A white label here measures 2.84:1 and is banned at
10
+ every size** — that is the whole reason this variant exists in this shape. `secondary`
11
+ is `--ink-strong` with `--on-slab`. `ghost` is `--surface` with a 1px `--line`.
12
+
13
+ Hover steps the fill one value; active repeats hover. Focus paints a 2px
14
+ `--focus-color` ring **and** keeps the fill step. On a slab the ring resolves to
15
+ `--focus-color-on-dark`, because the ink ring is 1.00:1 there.
16
+
17
+ The 36px height is measured and misses the 44px tap floor: the hit area is padded to
18
+ `--tap-min` on touch rather than the button being resized.
19
+
20
+ ```tsx
21
+ <Button>Start free trial</Button>
22
+ <Button variant="secondary">Book a demo</Button>
23
+ ```
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ButtonProps {
4
+ /** `primary` is the coral fill with an INK label — never a white one. */
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
+ export function Button({
14
+ variant = 'primary',
15
+ size = 'md',
16
+ disabled = false,
17
+ onClick,
18
+ children,
19
+ className,
20
+ }: ButtonProps) {
21
+ return (
22
+ <button
23
+ type="button"
24
+ className={['ch-btn', `ch-btn--${variant}`, `ch-btn--${size}`, className]
25
+ .filter(Boolean)
26
+ .join(' ')}
27
+ disabled={disabled}
28
+ onClick={onClick}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ The closing band's scatter: stadium-radius pills at `--r-pill`, some outlined in
6
+ `--line-on-slab`, some filled `--coral`, `--periwinkle` or `--parchment`, jumbled at
7
+ angles between platform marks. Many voices, one page — it is the pack's name made
8
+ visible.
9
+
10
+ The tilt is a static transform, not an animation: nothing in this pack moves on
11
+ scroll. Keep it under about 12 degrees, which is the range the reference's own scatter
12
+ occupies.
13
+
14
+ Decorative by construction — a capsule carrying a word would be a coral or periwinkle
15
+ label, and neither may be text.
16
+
17
+ ```tsx
18
+ <Capsule tone="coral" tilt={-6} />
19
+ <Capsule tone="outline" tilt={3} />
20
+ ```
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface CapsuleProps {
4
+ /** `outline` is the drawn one; the three fills are the measured set. */
5
+ tone?: 'outline' | 'coral' | 'periwinkle' | 'parchment';
6
+ /** Degrees of tilt. The reference scatters them; keep it under 12. */
7
+ tilt?: number;
8
+ children?: ReactNode;
9
+ className?: string;
10
+ }
11
+
12
+ export function Capsule({ tone = 'outline', tilt = 0, children, className }: CapsuleProps) {
13
+ return (
14
+ <span
15
+ className={['ch-capsule', `ch-capsule--${tone}`, className].filter(Boolean).join(' ')}
16
+ style={{ transform: tilt ? `rotate(${tilt}deg)` : undefined }}
17
+ >
18
+ {children}
19
+ </span>
20
+ );
21
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ `--surface` at `--r-card` with **32px padding and a 32px internal gap, unchanged at
6
+ every width** — that constancy is measured, and halving it at 390 makes a different
7
+ pack.
8
+
9
+ The one correction the pack applies to the reference: a 1px `--line` edge. The
10
+ reference draws none, and `--surface` is 1.04:1 on `--bg`, which is a value step
11
+ rather than an edge.
12
+
13
+ No shadow. Only four objects in this pack carry one — the bubble, the floating panel,
14
+ the hero deck and the nav — and a card is none of them.
15
+
16
+ ```tsx
17
+ <Card title="Prompt Tracking" meta="ChatGPT">
18
+ Which prompts trigger your brand in AI answers.
19
+ </Card>
20
+ ```
@@ -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 source, a count, a date. */
6
+ meta?: string;
7
+ children: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ export function Card({ title, meta, children, className }: CardProps) {
12
+ return (
13
+ <div className={['ch-card', className].filter(Boolean).join(' ')}>
14
+ {(title || meta) && (
15
+ <div className="ch-card__head">
16
+ {title && <h3 className="ch-card__title">{title}</h3>}
17
+ {meta && <span className="ch-card__meta">{meta}</span>}
18
+ </div>
19
+ )}
20
+ <div className="ch-card__body">{children}</div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ The eyebrow and the tab: `--surface` at `--r-chip` with a 1px `--line`, 8px/10px
6
+ padding and a 14px/500 label in `--ink-body` at 9.15:1.
7
+
8
+ `accent` does **not** paint the label coral — `--coral` may not be a word. It moves
9
+ the label to `--coral-ink` at 4.59:1 on `--bg` and tints the border. Selected adds a
10
+ 1px `--coral` ring, which is a mark rather than text and is legal at that role.
11
+
12
+ ```tsx
13
+ <Chip>AI Search</Chip>
14
+ <Chip tone="accent" selected>Ranked</Chip>
15
+ ```
@@ -0,0 +1,25 @@
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={[
14
+ 'ch-chip',
15
+ `ch-chip--${tone}`,
16
+ selected ? 'is-selected' : null,
17
+ className,
18
+ ]
19
+ .filter(Boolean)
20
+ .join(' ')}
21
+ >
22
+ {children}
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ **Status here is never by colour alone**, and this component is why the rule is
6
+ enforceable: it always renders the arrow *and* the number, plus a screen-reader word.
7
+
8
+ `--good` and `--danger` sit 32.4 apart at full colour and **6.2** apart under
9
+ deuteranopia — green against red is the classic collision, and no re-stepping of this
10
+ hue pair fixes it. The arrow is the separation.
11
+
12
+ `surface="dark"` switches to `--good-on-dark` / `--danger-on-dark`. That is a
13
+ different ladder rather than a translation: the mint reaches AA on paper only at
14
+ near-black, which is a different colour.
15
+
16
+ ```tsx
17
+ <Delta value="0.48%" direction="up" />
18
+ <Delta value="3 places" direction="down" surface="dark" />
19
+ ```
@@ -0,0 +1,24 @@
1
+ export interface DeltaProps {
2
+ /** The signed change, already formatted: "0.48%", "12". */
3
+ value: string;
4
+ direction: 'up' | 'down';
5
+ /** `dark` picks the on-slab ladder; the mint does not exist on paper. */
6
+ surface?: 'paper' | 'dark';
7
+ className?: string;
8
+ }
9
+
10
+ export function Delta({ value, direction, surface = 'paper', className }: DeltaProps) {
11
+ return (
12
+ <span
13
+ className={['ch-delta', `ch-delta--${direction}`, `ch-delta--${surface}`, className]
14
+ .filter(Boolean)
15
+ .join(' ')}
16
+ >
17
+ <span aria-hidden="true" className="ch-delta__arrow">
18
+ {direction === 'up' ? '\u2191' : '\u2193'}
19
+ </span>
20
+ <span className="ch-delta__value">{value}</span>
21
+ <span className="ch-delta__sr">{direction === 'up' ? 'up' : 'down'}</span>
22
+ </span>
23
+ );
24
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ The reference has no empty state, because every surface on it is a canned demo — so
6
+ this is derived from the parts it does have, and it is stated here so it is not
7
+ invented twice.
8
+
9
+ A `--paper` well at `--r-well`, a centred 18px/600 title in `--ink`, a 16px/400
10
+ explanation in `--ink-body`, one `--coral` button. **No illustration.** The only thing
11
+ above the text is an empty bubble at `--r-bubble` drawn hollow in `--line-quiet` with
12
+ no shadow — the page's own object with nothing in it, which is exactly what an empty
13
+ state means here.
14
+
15
+ ```tsx
16
+ <Empty title="No threads yet" body="We are watching four platforms."
17
+ action={<Button>Add a keyword</Button>} />
18
+ ```
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface EmptyProps {
4
+ title: string;
5
+ body?: string;
6
+ action?: ReactNode;
7
+ className?: string;
8
+ }
9
+
10
+ export function Empty({ title, body, action, className }: EmptyProps) {
11
+ return (
12
+ <div className={['ch-empty', className].filter(Boolean).join(' ')}>
13
+ <span aria-hidden="true" className="ch-empty__bubble" />
14
+ <p className="ch-empty__title">{title}</p>
15
+ {body && <p className="ch-empty__body">{body}</p>}
16
+ {action && <div className="ch-empty__action">{action}</div>}
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ The reference paints exactly one input — a search field inside a demo well — and this
6
+ is it: `--surface` at `--r-control` with a 1px `--line`, a 16px/400 value in
7
+ `--ink-body`, the placeholder in `--ink-ghost`, and a filled `--coral` action seated
8
+ inside the right edge with its label in `--on-coral`.
9
+
10
+ Focus paints the ring **and** keeps the border. There is no other field type on the
11
+ page: a select or a checkbox built here takes this geometry and the Palette's colours
12
+ rather than a new value.
13
+
14
+ ```tsx
15
+ <Field label="Prompt" placeholder="best online payment methods" action="Monitor" />
16
+ ```