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.
Files changed (75) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +7 -5
  3. package/bin/cli.js +9 -2
  4. package/cursor/rules/sheleg-design.mdc +10 -1
  5. package/kits/bulletin/.design-sync/config.json +14 -0
  6. package/kits/bulletin/.design-sync/conventions.md +55 -0
  7. package/kits/bulletin/README.md +38 -0
  8. package/kits/bulletin/package.json +29 -0
  9. package/kits/bulletin/src/Band.md +17 -0
  10. package/kits/bulletin/src/Band.tsx +19 -0
  11. package/kits/bulletin/src/Button.md +20 -0
  12. package/kits/bulletin/src/Button.tsx +33 -0
  13. package/kits/bulletin/src/Card.md +18 -0
  14. package/kits/bulletin/src/Card.tsx +24 -0
  15. package/kits/bulletin/src/Chip.md +16 -0
  16. package/kits/bulletin/src/Chip.tsx +25 -0
  17. package/kits/bulletin/src/Heading.md +17 -0
  18. package/kits/bulletin/src/Heading.tsx +19 -0
  19. package/kits/bulletin/src/Panel.md +15 -0
  20. package/kits/bulletin/src/Panel.tsx +11 -0
  21. package/kits/bulletin/src/Rail.md +16 -0
  22. package/kits/bulletin/src/Rail.tsx +28 -0
  23. package/kits/bulletin/src/Rule.md +15 -0
  24. package/kits/bulletin/src/Rule.tsx +18 -0
  25. package/kits/bulletin/src/Skeleton.md +15 -0
  26. package/kits/bulletin/src/Skeleton.tsx +19 -0
  27. package/kits/bulletin/src/Stat.md +15 -0
  28. package/kits/bulletin/src/Stat.tsx +17 -0
  29. package/kits/bulletin/src/StatusDot.md +17 -0
  30. package/kits/bulletin/src/StatusDot.tsx +17 -0
  31. package/kits/bulletin/src/index.ts +25 -0
  32. package/kits/bulletin/src/styles.css +671 -0
  33. package/kits/bulletin/tsconfig.json +15 -0
  34. package/kits/proscenium/.design-sync/config.json +14 -0
  35. package/kits/proscenium/.design-sync/conventions.md +50 -0
  36. package/kits/proscenium/README.md +27 -0
  37. package/kits/proscenium/package.json +29 -0
  38. package/kits/proscenium/src/Button.md +23 -0
  39. package/kits/proscenium/src/Button.tsx +33 -0
  40. package/kits/proscenium/src/Card.md +26 -0
  41. package/kits/proscenium/src/Card.tsx +24 -0
  42. package/kits/proscenium/src/Chip.md +18 -0
  43. package/kits/proscenium/src/Chip.tsx +25 -0
  44. package/kits/proscenium/src/Frame.md +23 -0
  45. package/kits/proscenium/src/Frame.tsx +25 -0
  46. package/kits/proscenium/src/Heading.md +20 -0
  47. package/kits/proscenium/src/Heading.tsx +19 -0
  48. package/kits/proscenium/src/Rule.md +16 -0
  49. package/kits/proscenium/src/Rule.tsx +18 -0
  50. package/kits/proscenium/src/Skeleton.md +17 -0
  51. package/kits/proscenium/src/Skeleton.tsx +17 -0
  52. package/kits/proscenium/src/Stage.md +22 -0
  53. package/kits/proscenium/src/Stage.tsx +15 -0
  54. package/kits/proscenium/src/Stat.md +17 -0
  55. package/kits/proscenium/src/Stat.tsx +17 -0
  56. package/kits/proscenium/src/StatusDot.md +20 -0
  57. package/kits/proscenium/src/StatusDot.tsx +19 -0
  58. package/kits/proscenium/src/index.ts +23 -0
  59. package/kits/proscenium/src/styles.css +589 -0
  60. package/kits/proscenium/tsconfig.json +15 -0
  61. package/package.json +2 -2
  62. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  63. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  64. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  65. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  66. package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +6 -2
  67. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +6 -4
  68. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
  69. package/plugins/sheleg-design/skills/sheleg-design/styles/bulletin.md +355 -0
  70. package/plugins/sheleg-design/skills/sheleg-design/styles/orchard.md +6 -0
  71. package/plugins/sheleg-design/skills/sheleg-design/styles/pigeonhole.md +4 -1
  72. package/plugins/sheleg-design/skills/sheleg-design/styles/proscenium.md +312 -0
  73. package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +9 -0
  74. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/bulletin.css +279 -0
  75. 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-seven locked style
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-seven style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
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-seven kits, so switching packs swaps identity rather than API,
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-seven
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–T19) is the behavioral harness: fresh subagents given a
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-seven style packs — instrument-console (dark console),
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
+ ```