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.
- package/CHANGELOG.md +74 -0
- package/README.md +5 -4
- package/bin/cli.js +5 -1
- package/cursor/rules/sheleg-design.mdc +8 -0
- package/kits/chorus/.design-sync/config.json +14 -0
- package/kits/chorus/.design-sync/conventions.md +50 -0
- package/kits/chorus/README.md +36 -0
- package/kits/chorus/package.json +29 -0
- package/kits/chorus/src/Bubble.md +23 -0
- package/kits/chorus/src/Bubble.tsx +40 -0
- package/kits/chorus/src/Button.md +23 -0
- package/kits/chorus/src/Button.tsx +33 -0
- package/kits/chorus/src/Capsule.md +20 -0
- package/kits/chorus/src/Capsule.tsx +21 -0
- package/kits/chorus/src/Card.md +20 -0
- package/kits/chorus/src/Card.tsx +23 -0
- package/kits/chorus/src/Chip.md +15 -0
- package/kits/chorus/src/Chip.tsx +25 -0
- package/kits/chorus/src/Delta.md +19 -0
- package/kits/chorus/src/Delta.tsx +24 -0
- package/kits/chorus/src/Empty.md +18 -0
- package/kits/chorus/src/Empty.tsx +19 -0
- package/kits/chorus/src/Field.md +16 -0
- package/kits/chorus/src/Field.tsx +40 -0
- package/kits/chorus/src/GridFrame.md +19 -0
- package/kits/chorus/src/GridFrame.tsx +34 -0
- package/kits/chorus/src/Heading.md +16 -0
- package/kits/chorus/src/Heading.tsx +17 -0
- package/kits/chorus/src/NavPill.md +19 -0
- package/kits/chorus/src/NavPill.tsx +19 -0
- package/kits/chorus/src/Rule.md +14 -0
- package/kits/chorus/src/Rule.tsx +12 -0
- package/kits/chorus/src/Skeleton.md +16 -0
- package/kits/chorus/src/Skeleton.tsx +27 -0
- package/kits/chorus/src/Slab.md +18 -0
- package/kits/chorus/src/Slab.tsx +17 -0
- package/kits/chorus/src/Stat.md +15 -0
- package/kits/chorus/src/Stat.tsx +17 -0
- package/kits/chorus/src/Sweep.md +17 -0
- package/kits/chorus/src/Sweep.tsx +16 -0
- package/kits/chorus/src/Well.md +15 -0
- package/kits/chorus/src/Well.tsx +16 -0
- package/kits/chorus/src/index.ts +37 -0
- package/kits/chorus/src/styles.css +878 -0
- package/kits/chorus/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 +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/STYLE_PACK_INDEX.md +3 -2
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +6 -5
- package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/chorus.md +474 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/deskmate.md +10 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/surveyor.md +11 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
+
```
|