sheleg-design-skill 1.41.0 → 1.43.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 +114 -0
- package/README.md +7 -5
- package/bin/cli.js +9 -2
- package/cursor/rules/sheleg-design.mdc +10 -1
- package/kits/bulletin/.design-sync/config.json +14 -0
- package/kits/bulletin/.design-sync/conventions.md +55 -0
- package/kits/bulletin/README.md +38 -0
- package/kits/bulletin/package.json +29 -0
- package/kits/bulletin/src/Band.md +17 -0
- package/kits/bulletin/src/Band.tsx +19 -0
- package/kits/bulletin/src/Button.md +20 -0
- package/kits/bulletin/src/Button.tsx +33 -0
- package/kits/bulletin/src/Card.md +18 -0
- package/kits/bulletin/src/Card.tsx +24 -0
- package/kits/bulletin/src/Chip.md +16 -0
- package/kits/bulletin/src/Chip.tsx +25 -0
- package/kits/bulletin/src/Heading.md +17 -0
- package/kits/bulletin/src/Heading.tsx +19 -0
- package/kits/bulletin/src/Panel.md +15 -0
- package/kits/bulletin/src/Panel.tsx +11 -0
- package/kits/bulletin/src/Rail.md +16 -0
- package/kits/bulletin/src/Rail.tsx +28 -0
- package/kits/bulletin/src/Rule.md +15 -0
- package/kits/bulletin/src/Rule.tsx +18 -0
- package/kits/bulletin/src/Skeleton.md +15 -0
- package/kits/bulletin/src/Skeleton.tsx +19 -0
- package/kits/bulletin/src/Stat.md +15 -0
- package/kits/bulletin/src/Stat.tsx +17 -0
- package/kits/bulletin/src/StatusDot.md +17 -0
- package/kits/bulletin/src/StatusDot.tsx +17 -0
- package/kits/bulletin/src/index.ts +25 -0
- package/kits/bulletin/src/styles.css +671 -0
- package/kits/bulletin/tsconfig.json +15 -0
- package/kits/proscenium/.design-sync/config.json +14 -0
- package/kits/proscenium/.design-sync/conventions.md +50 -0
- package/kits/proscenium/README.md +27 -0
- package/kits/proscenium/package.json +29 -0
- package/kits/proscenium/src/Button.md +23 -0
- package/kits/proscenium/src/Button.tsx +33 -0
- package/kits/proscenium/src/Card.md +26 -0
- package/kits/proscenium/src/Card.tsx +24 -0
- package/kits/proscenium/src/Chip.md +18 -0
- package/kits/proscenium/src/Chip.tsx +25 -0
- package/kits/proscenium/src/Frame.md +23 -0
- package/kits/proscenium/src/Frame.tsx +25 -0
- package/kits/proscenium/src/Heading.md +20 -0
- package/kits/proscenium/src/Heading.tsx +19 -0
- package/kits/proscenium/src/Rule.md +16 -0
- package/kits/proscenium/src/Rule.tsx +18 -0
- package/kits/proscenium/src/Skeleton.md +17 -0
- package/kits/proscenium/src/Skeleton.tsx +17 -0
- package/kits/proscenium/src/Stage.md +22 -0
- package/kits/proscenium/src/Stage.tsx +15 -0
- package/kits/proscenium/src/Stat.md +17 -0
- package/kits/proscenium/src/Stat.tsx +17 -0
- package/kits/proscenium/src/StatusDot.md +20 -0
- package/kits/proscenium/src/StatusDot.tsx +19 -0
- package/kits/proscenium/src/index.ts +23 -0
- package/kits/proscenium/src/styles.css +589 -0
- package/kits/proscenium/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/MOBILE_SURFACES.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +6 -2
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +6 -4
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
- package/plugins/sheleg-design/skills/sheleg-design/styles/bulletin.md +355 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/orchard.md +6 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/pigeonhole.md +4 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/proscenium.md +312 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +9 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/bulletin.css +279 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/proscenium.css +225 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,120 @@ All notable changes to this project are documented in this file. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
|
5
5
|
follow [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.43.0] - 2026-08-17
|
|
8
|
+
|
|
9
|
+
**The twenty-ninth pack, and the first one whose signature is a defect its reference
|
|
10
|
+
ships.** `bulletin` is extracted from [socialchamp.com](https://www.socialchamp.com) by
|
|
11
|
+
enumerating all 748 URLs in its page sitemap, fetching every one, and reading the 58
|
|
12
|
+
distinct stylesheets they resolve to plus the shared layer its theme ships. What it
|
|
13
|
+
takes is the *drawn* elevation — a 1px ink outline over a hard zero-blur ink offset, 185
|
|
14
|
+
of them against roughly 50 blurred shadows across the whole site — and the press that
|
|
15
|
+
goes with it: a control travels exactly as far as its offset shrinks, so the ink
|
|
16
|
+
displaced is constant.
|
|
17
|
+
|
|
18
|
+
The reference's primary CTA is white on `#ff6900` at 2.89:1, on every one of those 748
|
|
19
|
+
pages. That is under WCAG AA for body text and under the large-text floor as well, so no
|
|
20
|
+
type size rescues it; the pack keeps the measured hue and darkens it in oklab until white
|
|
21
|
+
clears AA. Two more corrections travel with it, and all three are in the pack's Gotchas
|
|
22
|
+
with their numbers.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`bulletin`**, from [socialchamp.com](https://www.socialchamp.com) — warm cream paper
|
|
27
|
+
(`#fcfaf4`) cut by three flat pastel bands, one ink (`#464646`) doing four jobs at once
|
|
28
|
+
(text, outline, offset and the dark band), an orange that fills and marks but never
|
|
29
|
+
carries a word, a display face at **800 inside controls above the headline's 700**, and
|
|
30
|
+
**zero tracking at every size** — `letter-spacing` appears seven times in 58
|
|
31
|
+
stylesheets and never on a heading. Widened contract, all thirteen headings.
|
|
32
|
+
- **`styles/tokens/bulletin.css`** — the ready-made token layer, with a
|
|
33
|
+
`[data-surface="ink"]` block for the dark band measured off the reference's own dark
|
|
34
|
+
footer, where the outline and the offset both invert to white. Every value is marked
|
|
35
|
+
MEASURED, SELECTED or DERIVED at its declaration.
|
|
36
|
+
- **`kits/bulletin`** — the twenty-ninth reference kit: the six-name spine plus
|
|
37
|
+
`StatusDot`, `Rail` (the row of outlined circles that says *many*), `Band`, `Panel` and
|
|
38
|
+
`Skeleton`. Its `styles.css` opens with the token layer byte for byte.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **`MOTION_DOCTRINE.md` — a fourth standalone pack pins its own ceiling.** `bulletin`
|
|
43
|
+
caps `MOTION_INTENSITY` at **3**, the lowest in the library, because the reference's
|
|
44
|
+
entire measured motion budget across 748 pages is an entrance fade, a 0.12s press and a
|
|
45
|
+
0.3s hover: no scroll clock, no parallax, no scrub, no pinning. Its depth is drawn
|
|
46
|
+
rather than animated, and animating the offset is what flattens it.
|
|
47
|
+
- **Every count that names the library moves to twenty-nine** — the pack tables in
|
|
48
|
+
`SKILL.md` and `README.md`, both manifests, the CLI banner, the Cursor rule, the slash
|
|
49
|
+
command's by-name fast path, and the core-contract remainder (seven core, twenty-two
|
|
50
|
+
widened).
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **The rail collapsed to a column, and only rendering it showed that.**
|
|
55
|
+
`container-type: inline-size` applies inline-size containment, so an element stops
|
|
56
|
+
taking its width from its contents — on a shrink-to-fit box (a grid item under
|
|
57
|
+
`justify-items: center`, a flex item, an inline-block) `.bl-rail` went to near-zero
|
|
58
|
+
width, stacked one circle per line and fired its own narrow branch, hiding every
|
|
59
|
+
platform name. All three gates were green over it, because a gate reads structure and
|
|
60
|
+
not layout. `width: 100%` is the fix and the reason is now a comment beside it.
|
|
61
|
+
Rendered and confirmed at 1440×1000, including the press: the primary's offset goes
|
|
62
|
+
3px → 1px as the control travels 2px, while the secondary beside it keeps its 3px.
|
|
63
|
+
- **Three stale counts in `SURFACE_COMPOSITION.md` that no gate could see.** It said the
|
|
64
|
+
token names were not uniform "across the twenty-one" when twenty-nine packs ship, and
|
|
65
|
+
that the accent is `--accent` "in eighteen" when it is twenty-seven — `--brand` in
|
|
66
|
+
`field-notes` and `--cta` in `orchard` are still the only two exceptions, counted. The
|
|
67
|
+
third was a count of packs carrying an `@role non-text:` colour, which is now stated as
|
|
68
|
+
sixteen token layers because that is what the grep returns.
|
|
69
|
+
|
|
70
|
+
## [1.42.0] - 2026-08-17
|
|
71
|
+
|
|
72
|
+
**The twenty-eighth pack, and the first one measured for its tempo rather than its
|
|
73
|
+
surface.** `proscenium` is extracted from [mailmodo.com](https://www.mailmodo.com/), read
|
|
74
|
+
off live computed styles in a headless Chrome at 1440×1000 and at 390, 768 and 1024 for
|
|
75
|
+
the ramp. What it takes from the reference is the *cadence* — two acts, then the same call
|
|
76
|
+
to action, again, with one dark act at the middle — which is why it forks against
|
|
77
|
+
`showroom` on tempo rather than on look.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **`proscenium`**, from [mailmodo.com](https://www.mailmodo.com/) — a white field
|
|
82
|
+
carrying two cool acts and **one deep indigo act at the middle** (`#07061d` to `#2a0b78`
|
|
83
|
+
at 86.41%, stops measured), ink that is an indigo rather than a grey, an electric violet
|
|
84
|
+
at `#5a45fe` filling a control that stays **nearly square at 4px against cards at 16**,
|
|
85
|
+
one family at nine weights, and a framed product panel the fold cuts off. Widened
|
|
86
|
+
contract, addressable origin, every stated ratio recomputed by the palette gate.
|
|
87
|
+
- **A reference kit**, with the six-component spine plus `StatusDot`, `Skeleton`, `Frame`
|
|
88
|
+
and this pack's own `Stage` — the one dark act, which the kit's stylesheet teaches to
|
|
89
|
+
strip elevation from any card standing inside it.
|
|
90
|
+
|
|
91
|
+
### Three values the reference has and this pack declines, each marked at its declaration
|
|
92
|
+
|
|
93
|
+
- **The rem base.** Mailmodo steps the root font size by viewport — 10px at 390 and 768,
|
|
94
|
+
11px at 1024, 13px at 1440 — and lets every rem follow. It is coherent and it overrides
|
|
95
|
+
the reader's own text-size preference, so the pack takes the *endpoints* (27px→62px on
|
|
96
|
+
the display, 28px→62px on an act heading) and ships them as clamp slopes with a rem term
|
|
97
|
+
in the sum.
|
|
98
|
+
- **The heading ink.** The reference sets headings to pure black against an indigo body
|
|
99
|
+
ink. The move is worth keeping and the literal is not: pure black as a field or an ink is
|
|
100
|
+
banned library-wide as an unfinished default and the slop lint fails on it, so the pack
|
|
101
|
+
ships `#05041c` — 20.18:1 on the field against black's 21.00.
|
|
102
|
+
- **The amber.** `#9e7613` is 4.15:1 on white, under AA for body text. The hue is the
|
|
103
|
+
reference's; the step down to `#8a6510` (5.32 / 4.91 / 4.68) is the pack's.
|
|
104
|
+
|
|
105
|
+
`--ok`, `--danger` and the entire dark register are pack decisions rather than
|
|
106
|
+
measurements — the reference paints no success and no error state and has no dark mode. The
|
|
107
|
+
dark register is derived from the one dark thing the reference does have: the stage act's
|
|
108
|
+
own two stops carry the field and the panel.
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- **A self-test plant that had stopped finding its target.** The core-contract remainder
|
|
113
|
+
fixture matched `The other \w+ answer all four`; at the twenty-eighth pack the remainder
|
|
114
|
+
became "twenty-one", which a bare `\w+` cannot match, so the plant changed nothing and
|
|
115
|
+
the self-test still reported it as caught. A fixture that cannot find its own target is a
|
|
116
|
+
hole in the gate, and it opens on exactly the release the plant exists to catch.
|
|
117
|
+
|
|
118
|
+
### Ratchet
|
|
119
|
+
|
|
120
|
+
`test/floors.json` raised 2422/1287/504 → 3222/1811/615.
|
|
7
121
|
## [1.41.0] - 2026-08-17
|
|
8
122
|
|
|
9
123
|
**A style pack is a token layer and a set of rules. It does not ship a button** — and until
|
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 **twenty-
|
|
14
|
+
decides whether to animate before it decides how, and **twenty-nine 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
|
|
|
@@ -71,6 +71,8 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
71
71
|
| `notation` | a near-white page drawn **entirely in hairlines instead of cards**, radii of 2 and 4px, a slab serif held at weight 300 against a monospace, **no bold anywhere**, an ink primary that leaves the accent free to mark what can be read, and one chamfered corner per page | **developer and technical products sold on restraint** — open source front pages, workspaces for people who dislike being sold to, documentation homes (standalone) |
|
|
72
72
|
| `almanac` | **oatmeal paper rather than white**, seams at 2px and 4px with **no 1px anywhere**, a 104px display at weight 500 with a line-height below one that locks its lines into a block, uppercase mono tags notched through the edges of drawn boxes, and one object per page floating on four stacked shadow stops | **pages that assert a category** — a manifesto, a company saying what this kind of thing is, a product whose argument is editorial rather than functional (standalone) |
|
|
73
73
|
| `vitrine` | a white field drawn **entirely in hairlines**, a serif display over a sans body, an ink primary so the accent stays free to mark what can be read, a grey panel that groups without lifting, and **one framed record** with a 1px inset highlight carrying the page's evidence | **the front door of a product sold on trust** — B2B software under evaluation, security and compliance surfaces, specification and comparison pages (standalone) |
|
|
74
|
+
| `proscenium` | a white field carrying two cool acts and **one deep indigo act at the middle**, ink that is an indigo rather than a grey, an electric violet that fills a control staying **nearly square at 4px** against cards at 16, one family at nine weights, and a framed product panel the fold cuts off | **product-led marketing front doors whose argument is a demonstration** — SaaS home pages, launch and tour pages, any page with six or more acts that needs a repeated beat (standalone) |
|
|
75
|
+
| `bulletin` | warm cream paper cut by **flat pastel bands**, every card and control a 1px ink outline standing on a **hard zero-blur ink offset it travels into when pressed** — 185 of them against 50 blurred shadows in the reference — a display face at 800 inside controls above the headline's 700, and **no tracking at any size** | **front doors whose argument is breadth** — a tool doing many things across many channels for many clients, sold cheerfully to a small team or an agency: social and content platforms, scheduling and inbox products, all-in-one SMB SaaS (standalone) |
|
|
74
76
|
| `ledger` | warm cream paper where elevation is a **1px hairline at 12% ink** and no card casts a shadow, radii of 7.5/10/15/20 nested concentrically, an **ink** primary button, and a terracotta accent forbidden from filling any control — it labels, as a 10px monospace uppercase kicker — over 32px data rows, with a seal on every card stating how its number is known | the console of a product that answers questions **about data** — AI analysts, BI surfaces, query workspaces, agents that read a warehouse and write back a figure |
|
|
75
77
|
|
|
76
78
|
Each pack locks palette, type, texture, motion tokens, signature motifs and
|
|
@@ -146,7 +148,7 @@ skills.
|
|
|
146
148
|
| `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 |
|
|
147
149
|
| `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 |
|
|
148
150
|
| `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 |
|
|
149
|
-
| `styles/*.md` | The twenty-
|
|
151
|
+
| `styles/*.md` | The twenty-nine style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
150
152
|
| `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) |
|
|
151
153
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
152
154
|
|
|
@@ -217,7 +219,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
217
219
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
218
220
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
219
221
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
220
|
-
identical in all twenty-
|
|
222
|
+
identical in all twenty-nine kits, so switching packs swaps identity rather than API,
|
|
221
223
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
222
224
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
223
225
|
stops an agent inventing motion to fill the silence.
|
|
@@ -263,10 +265,10 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
263
265
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
264
266
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
265
267
|
it ships the right files is CI, which installs the bundle through **both**
|
|
266
|
-
installers and `diff -r`s the result against the source, then builds all twenty-
|
|
268
|
+
installers and `diff -r`s the result against the source, then builds all twenty-nine
|
|
267
269
|
kits.
|
|
268
270
|
|
|
269
|
-
`test/scenarios.md` (T1–
|
|
271
|
+
`test/scenarios.md` (T1–T30) is the behavioral harness: fresh subagents given a
|
|
270
272
|
task, checking that the skill is discovered, applied and quoted correctly.
|
|
271
273
|
Re-run the affected scenarios after any edit to `SKILL.md`, a pack or the
|
|
272
274
|
reference.
|
package/bin/cli.js
CHANGED
|
@@ -234,7 +234,7 @@ ${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/ twenty-
|
|
237
|
+
styles/ twenty-nine 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
240
|
atrium (warm cream consumer health), orchard (friendly
|
|
@@ -276,7 +276,14 @@ ${c("bold", "What it installs")}
|
|
|
276
276
|
almanac (oatmeal paper at 2px with no 1px anywhere and a
|
|
277
277
|
display set below a line-height of one), vitrine (a white
|
|
278
278
|
hairline field with a serif display, an ink primary, and
|
|
279
|
-
one framed record carrying the evidence)
|
|
279
|
+
one framed record carrying the evidence),
|
|
280
|
+
proscenium (a white field with two cool acts and one deep
|
|
281
|
+
indigo act at the middle, a violet filling a control that
|
|
282
|
+
stays nearly square at 4px against cards at 16, and a
|
|
283
|
+
framed product panel the fold cuts off),
|
|
284
|
+
bulletin (warm cream paper cut by flat pastel bands,
|
|
285
|
+
every card and control a 1px ink outline on a hard
|
|
286
|
+
zero-blur ink offset it travels into when pressed) —
|
|
280
287
|
plus a ready-made token CSS per pack and
|
|
281
288
|
STYLE_PACK_TEMPLATE.md for authoring more
|
|
282
289
|
`);
|
|
@@ -86,7 +86,16 @@ four stacked shadow stops, for pages that assert a category;
|
|
|
86
86
|
vitrine — a white field drawn entirely in hairlines, a serif display over a sans
|
|
87
87
|
body, an ink primary, a grey panel that groups without lifting, and one framed
|
|
88
88
|
record with a 1px inset highlight carrying the page's evidence, for the front
|
|
89
|
-
door of a product sold on trust
|
|
89
|
+
door of a product sold on trust;
|
|
90
|
+
proscenium — a white field carrying two cool acts and one deep indigo act at the
|
|
91
|
+
middle, an electric violet filling a control that stays nearly square at 4px
|
|
92
|
+
against cards at 16, one family at nine weights, and a framed product panel the
|
|
93
|
+
fold cuts off, for product-led marketing front doors whose argument is a
|
|
94
|
+
demonstration;
|
|
95
|
+
bulletin — warm cream paper cut by flat pastel bands, every card and control a
|
|
96
|
+
1px ink outline standing on a hard zero-blur ink offset it travels into when
|
|
97
|
+
pressed, a display face at 800 inside controls and 700 in the headline, and no
|
|
98
|
+
tracking at any size, for front doors whose argument is breadth);
|
|
90
99
|
otherwise follow the contract below (self-contained on purpose).
|
|
91
100
|
|
|
92
101
|
## Whether to animate at all — before how
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/bulletin",
|
|
3
|
+
"globalName": "ShelegBulletin",
|
|
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,55 @@
|
|
|
1
|
+
# Bulletin — the contract this design system ships under
|
|
2
|
+
|
|
3
|
+
**Register.** Choose Bulletin for **front doors whose argument is breadth**: a
|
|
4
|
+
tool that does many things across many channels for many clients, sold
|
|
5
|
+
cheerfully to a small team or an agency — social and content platforms,
|
|
6
|
+
scheduling and inbox products, all-in-one SMB SaaS, marketplaces of small
|
|
7
|
+
features. Warm cream paper, flat pastel bands, and every object drawn rather
|
|
8
|
+
than tinted. Build every screen against `var(--…)` and never a literal.
|
|
9
|
+
|
|
10
|
+
**Elevation is a hard offset and it never blurs.** A 1px `--line` outline plus a
|
|
11
|
+
zero-blur ink shadow — `--shadow-1` 2px for a chip, `--shadow-2` 3px for a
|
|
12
|
+
control, `--shadow-3` 4px for a card, `--shadow-4` for the one framed panel per
|
|
13
|
+
page. One soft shadow anywhere and the pack is gone. An offset without an
|
|
14
|
+
outline reads as a rendering fault.
|
|
15
|
+
|
|
16
|
+
**The press.** A control translates by `--press-travel` exactly as its offset
|
|
17
|
+
shrinks to `--shadow-press`, so the ink displaced stays constant; `:active`
|
|
18
|
+
travels one more pixel and the offset goes to nothing. Hover and
|
|
19
|
+
`:focus-visible` share the rule, so a keyboard user sees it. A **surface** does
|
|
20
|
+
the opposite: its offset grows to `--shadow-3-wide`. One move per kind of
|
|
21
|
+
object.
|
|
22
|
+
|
|
23
|
+
**The accent rule.** There is exactly one accent (`--accent`) and it is a fill
|
|
24
|
+
and a mark, never a word — at 2.77:1 on the paper it sits below the non-text
|
|
25
|
+
floor. The filled control uses `--action`, a derived value: the reference sets
|
|
26
|
+
white on the measured orange at 2.89:1, which is under AA and under the
|
|
27
|
+
large-text floor, so no type size rescues it. Links use `--link` with an
|
|
28
|
+
underline. At most one accent fill per view.
|
|
29
|
+
|
|
30
|
+
**Status is never by colour alone.** `--good` / `--warn` / `--danger` /
|
|
31
|
+
`--info` are state, never decoration, and every one of them ships beside a word.
|
|
32
|
+
Under deuteranopia `--danger` and `--warn` separate by 0.7; the word is the
|
|
33
|
+
message and the colour reinforces it. The `[data-surface="ink"]` block remaps
|
|
34
|
+
all four — a theme that remaps its ink and leaves its statuses behind paints
|
|
35
|
+
1.3–2.0:1.
|
|
36
|
+
|
|
37
|
+
**Type.** Two families: Bricolage Grotesque for display, DM Sans for body. The
|
|
38
|
+
control label is **heavier than the headline** — 800 against 700 — and that is
|
|
39
|
+
measured, not a flourish. **Tracking is zero at every size.**
|
|
40
|
+
|
|
41
|
+
**Bans** (verbatim from the pack):
|
|
42
|
+
|
|
43
|
+
- No blur on any elevation. No offset without an outline.
|
|
44
|
+
- No second orange fill in one viewport; no accent as a word.
|
|
45
|
+
- No tracking, anywhere.
|
|
46
|
+
- No gradient in a band — the bands are flat.
|
|
47
|
+
- No status by colour alone.
|
|
48
|
+
- No motion beyond the press and a fade: no parallax, no scrub, no sticky
|
|
49
|
+
choreography, no marquee.
|
|
50
|
+
- No white page. White is a card; the field is `--bg`.
|
|
51
|
+
|
|
52
|
+
**Motion does not cross.** A kit is the static half of a pack. The press is
|
|
53
|
+
specified in `styles.css` and described here; nothing else in this package
|
|
54
|
+
animates, and `prefers-reduced-motion` collapses every duration and both travel
|
|
55
|
+
distances to zero at the token layer.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @sheleg-design/bulletin
|
|
2
|
+
|
|
3
|
+
The React reference kit for the SHELEG **Bulletin** style pack — warm cream
|
|
4
|
+
paper cut by flat pastel bands, where every card and control is a 1px ink
|
|
5
|
+
outline standing on a hard zero-blur ink offset it travels into when pressed.
|
|
6
|
+
|
|
7
|
+
It is generated from the pack, not authored beside it: `src/styles.css` opens
|
|
8
|
+
with `styles/tokens/bulletin.css` byte for byte, and the rules the design agent
|
|
9
|
+
must obey are in [`.design-sync/conventions.md`](./.design-sync/conventions.md).
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install && npm run build
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## The spine
|
|
16
|
+
|
|
17
|
+
`Button`, `Card`, `Chip`, `Stat`, `Heading`, `Rule` — identical names, props and
|
|
18
|
+
types in every SHELEG kit, so switching packs swaps identity rather than API.
|
|
19
|
+
|
|
20
|
+
## This pack's own
|
|
21
|
+
|
|
22
|
+
`StatusDot` (the word is required, never the colour alone), `Rail` (the row of
|
|
23
|
+
outlined circles that says *many*), `Band` (one flat pastel per act, and the
|
|
24
|
+
`ink` tone that flips the whole block to the dark register), `Panel` (one per
|
|
25
|
+
page, the widest offset in the ramp) and `Skeleton`.
|
|
26
|
+
|
|
27
|
+
## The two rules that carry the kit
|
|
28
|
+
|
|
29
|
+
1. **An outline and an offset always travel together.** A hard shadow under an
|
|
30
|
+
edgeless box reads as a rendering fault rather than as depth.
|
|
31
|
+
2. **A control presses into its offset; a surface grows out of it.** One move
|
|
32
|
+
per kind of object. Give a card the button's press and the grid stops reading
|
|
33
|
+
as a set of objects; give a button the card's growth and the click has no
|
|
34
|
+
feedback.
|
|
35
|
+
|
|
36
|
+
**Motion does not cross into a design tool.** A kit is the static half of a
|
|
37
|
+
pack: the press is described here and in the pack, and it is implemented in
|
|
38
|
+
`styles.css`, but nothing in this package animates on its own.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/bulletin",
|
|
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,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
One flat pastel per act, full-bleed, used to **change subject** rather than to
|
|
6
|
+
decorate. The ink clears AA on every band — 5.76:1 on the lilac, 6.53 on the
|
|
7
|
+
sky, 8.36 on the peach — which is why the bands can be saturated without a
|
|
8
|
+
second text colour.
|
|
9
|
+
|
|
10
|
+
`ink` switches the whole band to the dark register by setting
|
|
11
|
+
`data-surface="ink"`, where the outline and the offset both invert to white. A
|
|
12
|
+
gradient in a band is banned: the reference paints them flat, and a gradient
|
|
13
|
+
reads as a different product.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Band tone="sky"><Heading level={2}>One inbox for every client</Heading></Band>
|
|
17
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface BandProps {
|
|
4
|
+
/** One flat pastel per act. The pack forbids a gradient here. */
|
|
5
|
+
tone?: 'paper' | 'peach' | 'sky' | 'lilac' | 'lemon' | 'ink';
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Band({ tone = 'paper', children, className }: BandProps) {
|
|
11
|
+
return (
|
|
12
|
+
<section
|
|
13
|
+
className={['bl-band', `bl-band--${tone}`, className].filter(Boolean).join(' ')}
|
|
14
|
+
data-surface={tone === 'ink' ? 'ink' : undefined}
|
|
15
|
+
>
|
|
16
|
+
<div className="bl-band__inner">{children}</div>
|
|
17
|
+
</section>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Actions
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The press is this pack's signature, and it lives here. A button stands `3px`
|
|
6
|
+
proud of the paper on a hard zero-blur ink offset; hover translates it `2px` and
|
|
7
|
+
shrinks the offset to `1px`; `:active` translates `3px` and removes the offset
|
|
8
|
+
entirely — the control has bottomed out. The ink displaced is constant at every
|
|
9
|
+
step, which is why it reads as movement rather than as a restyle.
|
|
10
|
+
|
|
11
|
+
`primary` fills with `--action`, not `--accent`: the measured orange carries
|
|
12
|
+
white at 2.89:1 and no type size rescues that. `secondary` is the same outline
|
|
13
|
+
and offset over `--surface`. `ghost` keeps neither, for a toolbar row where an
|
|
14
|
+
outline would add a line the eye has to parse.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<Button onClick={start}>Start free</Button>
|
|
18
|
+
<Button variant="secondary" onClick={book}>Book a demo</Button>
|
|
19
|
+
<Button variant="ghost" size="sm" onClick={dismiss}>Not now</Button>
|
|
20
|
+
```
|
|
@@ -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
|
+
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={['bl-btn', `bl-btn--${variant}`, `bl-btn--${size}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.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 card is `--surface` at `--r-lg` with a 1px `--line` outline and
|
|
6
|
+
`--shadow-3` — the outline and the offset always travel together, because a hard
|
|
7
|
+
shadow under an edgeless box reads as a rendering fault rather than as depth.
|
|
8
|
+
|
|
9
|
+
A card is for a *group*. A list of statements takes a `Rule`, and a single
|
|
10
|
+
figure takes a `Stat` on the bare paper. Hover on a card **grows** the offset to
|
|
11
|
+
`--shadow-3-wide` — the opposite of the button's press, and mixing the two moves
|
|
12
|
+
is the way this pack breaks.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<Card title="Approvals" meta="4 waiting">
|
|
16
|
+
<p>Every post routes through the client before it publishes.</p>
|
|
17
|
+
</Card>
|
|
18
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
const head = title !== undefined || meta !== undefined;
|
|
13
|
+
return (
|
|
14
|
+
<section className={['bl-card', className].filter(Boolean).join(' ')}>
|
|
15
|
+
{head && (
|
|
16
|
+
<div className="bl-card__head">
|
|
17
|
+
{title !== undefined && <h3 className="bl-card__title">{title}</h3>}
|
|
18
|
+
{meta !== undefined && <span className="bl-card__meta">{meta}</span>}
|
|
19
|
+
</div>
|
|
20
|
+
)}
|
|
21
|
+
<div className="bl-card__body">{children}</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A chip is the small outlined object the pack repeats everywhere: a tag, a
|
|
6
|
+
filter, a platform label. `--r-pill`, a 1px `--line`, and `--shadow-1` at 2px —
|
|
7
|
+
the smallest step of the offset ramp, because the offset grows with the object.
|
|
8
|
+
|
|
9
|
+
`accent` tints with `--accent-wash` rather than filling with `--accent`: the
|
|
10
|
+
accent is a fill for exactly one control per view and a chip is never it.
|
|
11
|
+
`selected` keeps its state after the pointer leaves.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Chip>Instagram</Chip>
|
|
15
|
+
<Chip tone="accent" selected>Agencies</Chip>
|
|
16
|
+
```
|
|
@@ -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
|
+
'bl-chip',
|
|
15
|
+
`bl-chip--${tone}`,
|
|
16
|
+
selected ? 'bl-chip--selected' : undefined,
|
|
17
|
+
className,
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ')}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The type ramp made visible: 1 is the hero at `--t-hero` 84 / weight 700 /
|
|
6
|
+
line-height 1.1, 2 the section at `--t-page` 48 / 1.2, 3 the card title at
|
|
7
|
+
`--t-card` 20 / weight 600 / 1.4.
|
|
8
|
+
|
|
9
|
+
**Tracking is zero at every level.** The reference sets `letter-spacing` seven
|
|
10
|
+
times in 58 stylesheets and never on a heading, so a negative track here is the
|
|
11
|
+
fastest way to stop the pack looking like itself. Level 1 clamps fluidly and the
|
|
12
|
+
clamp is monotonic — the reference's own ramp is not, and that is a correction
|
|
13
|
+
rather than a copy.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Heading level={1}>Post once. Show up everywhere</Heading>
|
|
17
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps {
|
|
4
|
+
/** 1 = hero (84px), 2 = section (48px), 3 = card title (20px). */
|
|
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 = `h${level}` as 'h1' | 'h2' | 'h3';
|
|
12
|
+
return (
|
|
13
|
+
<Tag
|
|
14
|
+
className={['bl-heading', `bl-heading--${level}`, className].filter(Boolean).join(' ')}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</Tag>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**One per page.** The framed block at `--r-2xl` with `--shadow-4` — the widest
|
|
6
|
+
offset in the ramp — and 70px of padding, holding the page's densest claim: the
|
|
7
|
+
comparison, the pricing table, the screenshot that has to be believed.
|
|
8
|
+
|
|
9
|
+
A second panel and neither is the panel. Everything else on the page is a card
|
|
10
|
+
at `--shadow-3` or a chip at `--shadow-1`, and the step between them is what
|
|
11
|
+
tells the reader which block matters.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Panel><PricingTable /></Panel>
|
|
15
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface PanelProps {
|
|
4
|
+
/** The page's densest claim. One per page — the pack says so. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Panel({ children, className }: PanelProps) {
|
|
10
|
+
return <div className={['bl-panel', className].filter(Boolean).join(' ')}>{children}</div>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The row of outlined circles that says *many*. Each mark is a 60px chip at
|
|
6
|
+
`--r-pill` with a 1px `--line` and `--shadow-2-wide`; the rail of them is how
|
|
7
|
+
this pack states breadth without a paragraph.
|
|
8
|
+
|
|
9
|
+
It belongs in the first viewport. Putting it below the fold is the most common
|
|
10
|
+
way to build this hero wrong — the rail *is* the argument, and the headline only
|
|
11
|
+
introduces it. The circles carry no text, so `name` is what assistive tech
|
|
12
|
+
reads and what the visible caption renders.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<Rail items={[{ mark: 'in', name: 'LinkedIn' }, { mark: 'ig', name: 'Instagram' }]} />
|
|
16
|
+
```
|