sheleg-design-skill 1.42.0 → 1.44.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 (50) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +6 -5
  3. package/bin/cli.js +5 -2
  4. package/cursor/rules/sheleg-design.mdc +5 -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/editorial-luxury/src/styles.css +26 -0
  35. package/kits/instrument-console/src/styles.css +33 -0
  36. package/package.json +2 -2
  37. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  38. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  39. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  40. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  41. package/plugins/sheleg-design/skills/sheleg-design/MOTION_DOCTRINE.md +6 -2
  42. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +5 -4
  43. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
  44. package/plugins/sheleg-design/skills/sheleg-design/styles/bulletin.md +355 -0
  45. package/plugins/sheleg-design/skills/sheleg-design/styles/instrument-console.md +11 -0
  46. package/plugins/sheleg-design/skills/sheleg-design/styles/orchard.md +6 -0
  47. package/plugins/sheleg-design/skills/sheleg-design/styles/pigeonhole.md +4 -1
  48. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/bulletin.css +279 -0
  49. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/editorial-luxury.css +26 -0
  50. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/instrument-console.css +33 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,113 @@ 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.44.0] - 2026-08-19
8
+
9
+ ### Degrade to calm gained an observable
10
+
11
+ The promise had none. `sloplint.py` asserted only that the **string**
12
+ `prefers-reduced-motion` appeared in the doctrine — that the rule was mentioned, not that any
13
+ shipped artifact obeyed it. Measured: **2 of 29 token layers shipped no reduce branch**,
14
+ including the pack that mandates a particle field.
15
+
16
+ `validate_reduced_motion()` asserts two things per layer: a layer declaring `--dur-*` or
17
+ `--ease-*` ships a `prefers-reduced-motion` branch, **and that branch collapses at least one
18
+ time-valued property to ≤1ms**. The second half is the row — doctrine §9 says motion collapses
19
+ "to static or instant, not to slower", so a branch that merely names the query satisfies a grep
20
+ and changes nothing a reader can feel. Durations are matched **by value, not by name**, so
21
+ `paperclip`'s `--t-*` and `instrument-console`'s `--motion-ease` are not read as empty.
22
+
23
+ `editorial-luxury` and `instrument-console` fixed. Where a motif cannot be reached from a custom
24
+ property — a rAF particle field is not stoppable by a duration — the still is now stated for each:
25
+ field to one static frame, spotlight to every section lit, instruments fully drawn. 29 of 29
26
+ layers now carry a branch that bites. B-040 closed.
27
+
28
+ ### A count hyphenated onto its noun was invisible to the gate that counts it
29
+
30
+ `docs/DOCMAP.md` said "a fourteen-kit build matrix" against **29** kits, and the counted-claims
31
+ guard ran green over that string for weeks: `COUNTED` never matched a number spelled as a word and
32
+ joined to its noun by a hyphen. A contributor had already routed around the hole rather than
33
+ report it.
34
+
35
+ The separator is now `[ -]`, **captured and back-referenced**, so a compound is read in one
36
+ alphabet or the other and a half-and-half form is not silently accepted; the plural is required
37
+ for the spaced form only, because a hyphenated count is a singular modifier by grammar.
38
+ Blast radius measured old-pattern-against-new on one tree: the policed set 34 → 36 spans with
39
+ **zero false positives**, the whole tree 333 → 384.
40
+
41
+ The coordination claim is **derived** rather than reworded — DOCMAP now points at the generated
42
+ `AGENT_SYNC.md`, and a new check holds the derivation from both ends so the page, the config and
43
+ the document cannot disagree.
44
+
45
+ ### Plants
46
+
47
+ Self-test 16 → 19, each asserting its **message** rather than merely the red — every token layer
48
+ is copied into its kit byte for byte, so any edit trips the kit-drift check and a plant would
49
+ otherwise report "caught" with the intended check never having run.
50
+
51
+ ## [1.43.0] - 2026-08-17
52
+
53
+ **The twenty-ninth pack, and the first one whose signature is a defect its reference
54
+ ships.** `bulletin` is extracted from [socialchamp.com](https://www.socialchamp.com) by
55
+ enumerating all 748 URLs in its page sitemap, fetching every one, and reading the 58
56
+ distinct stylesheets they resolve to plus the shared layer its theme ships. What it
57
+ takes is the *drawn* elevation — a 1px ink outline over a hard zero-blur ink offset, 185
58
+ of them against roughly 50 blurred shadows across the whole site — and the press that
59
+ goes with it: a control travels exactly as far as its offset shrinks, so the ink
60
+ displaced is constant.
61
+
62
+ The reference's primary CTA is white on `#ff6900` at 2.89:1, on every one of those 748
63
+ pages. That is under WCAG AA for body text and under the large-text floor as well, so no
64
+ type size rescues it; the pack keeps the measured hue and darkens it in oklab until white
65
+ clears AA. Two more corrections travel with it, and all three are in the pack's Gotchas
66
+ with their numbers.
67
+
68
+ ### Added
69
+
70
+ - **`bulletin`**, from [socialchamp.com](https://www.socialchamp.com) — warm cream paper
71
+ (`#fcfaf4`) cut by three flat pastel bands, one ink (`#464646`) doing four jobs at once
72
+ (text, outline, offset and the dark band), an orange that fills and marks but never
73
+ carries a word, a display face at **800 inside controls above the headline's 700**, and
74
+ **zero tracking at every size** — `letter-spacing` appears seven times in 58
75
+ stylesheets and never on a heading. Widened contract, all thirteen headings.
76
+ - **`styles/tokens/bulletin.css`** — the ready-made token layer, with a
77
+ `[data-surface="ink"]` block for the dark band measured off the reference's own dark
78
+ footer, where the outline and the offset both invert to white. Every value is marked
79
+ MEASURED, SELECTED or DERIVED at its declaration.
80
+ - **`kits/bulletin`** — the twenty-ninth reference kit: the six-name spine plus
81
+ `StatusDot`, `Rail` (the row of outlined circles that says *many*), `Band`, `Panel` and
82
+ `Skeleton`. Its `styles.css` opens with the token layer byte for byte.
83
+
84
+ ### Changed
85
+
86
+ - **`MOTION_DOCTRINE.md` — a fourth standalone pack pins its own ceiling.** `bulletin`
87
+ caps `MOTION_INTENSITY` at **3**, the lowest in the library, because the reference's
88
+ entire measured motion budget across 748 pages is an entrance fade, a 0.12s press and a
89
+ 0.3s hover: no scroll clock, no parallax, no scrub, no pinning. Its depth is drawn
90
+ rather than animated, and animating the offset is what flattens it.
91
+ - **Every count that names the library moves to twenty-nine** — the pack tables in
92
+ `SKILL.md` and `README.md`, both manifests, the CLI banner, the Cursor rule, the slash
93
+ command's by-name fast path, and the core-contract remainder (seven core, twenty-two
94
+ widened).
95
+
96
+ ### Fixed
97
+
98
+ - **The rail collapsed to a column, and only rendering it showed that.**
99
+ `container-type: inline-size` applies inline-size containment, so an element stops
100
+ taking its width from its contents — on a shrink-to-fit box (a grid item under
101
+ `justify-items: center`, a flex item, an inline-block) `.bl-rail` went to near-zero
102
+ width, stacked one circle per line and fired its own narrow branch, hiding every
103
+ platform name. All three gates were green over it, because a gate reads structure and
104
+ not layout. `width: 100%` is the fix and the reason is now a comment beside it.
105
+ Rendered and confirmed at 1440×1000, including the press: the primary's offset goes
106
+ 3px → 1px as the control travels 2px, while the secondary beside it keeps its 3px.
107
+ - **Three stale counts in `SURFACE_COMPOSITION.md` that no gate could see.** It said the
108
+ token names were not uniform "across the twenty-one" when twenty-nine packs ship, and
109
+ that the accent is `--accent` "in eighteen" when it is twenty-seven — `--brand` in
110
+ `field-notes` and `--cta` in `orchard` are still the only two exceptions, counted. The
111
+ third was a count of packs carrying an `@role non-text:` colour, which is now stated as
112
+ sixteen token layers because that is what the grep returns.
113
+
7
114
  ## [1.42.0] - 2026-08-17
8
115
 
9
116
  **The twenty-eighth pack, and the first one measured for its tempo rather than its
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-eight 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
 
@@ -72,6 +72,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
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
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) |
75
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 |
76
77
 
77
78
  Each pack locks palette, type, texture, motion tokens, signature motifs and
@@ -147,7 +148,7 @@ skills.
147
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 |
148
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 |
149
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 |
150
- | `styles/*.md` | The twenty-eight 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 |
151
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) |
152
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 |
153
154
 
@@ -218,7 +219,7 @@ cd ./ds-workbench && npm install && npm run build
218
219
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
219
220
  pack's **bans** as the design system's own README, `styles.css` built from
220
221
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
221
- identical in all twenty-eight kits, so switching packs swaps identity rather than API,
222
+ identical in all twenty-nine kits, so switching packs swaps identity rather than API,
222
223
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
223
224
  not cross into Figma: a kit is the static half of a pack, and saying so is what
224
225
  stops an agent inventing motion to fill the silence.
@@ -264,10 +265,10 @@ a pack's four widened sections used to make two gates *quieter* and still green.
264
265
  One honest limit: the npx installer is checked by asserting its runtime bundle
265
266
  walker exists, not by reading a file list — it has none by design. What proves
266
267
  it ships the right files is CI, which installs the bundle through **both**
267
- installers and `diff -r`s the result against the source, then builds all twenty-eight
268
+ installers and `diff -r`s the result against the source, then builds all twenty-nine
268
269
  kits.
269
270
 
270
- `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
271
272
  task, checking that the skill is discovered, applied and quoted correctly.
272
273
  Re-run the affected scenarios after any edit to `SKILL.md`, a pack or the
273
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-eight 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
@@ -280,7 +280,10 @@ ${c("bold", "What it installs")}
280
280
  proscenium (a white field with two cool acts and one deep
281
281
  indigo act at the middle, a violet filling a control that
282
282
  stays nearly square at 4px against cards at 16, and a
283
- framed product panel the fold cuts off) —
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) —
284
287
  plus a ready-made token CSS per pack and
285
288
  STYLE_PACK_TEMPLATE.md for authoring more
286
289
  `);
@@ -91,7 +91,11 @@ proscenium — a white field carrying two cool acts and one deep indigo act at t
91
91
  middle, an electric violet filling a control that stays nearly square at 4px
92
92
  against cards at 16, one family at nine weights, and a framed product panel the
93
93
  fold cuts off, for product-led marketing front doors whose argument is a
94
- demonstration);
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);
95
99
  otherwise follow the contract below (self-contained on purpose).
96
100
 
97
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
+ ```
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface RailItem {
4
+ /** The glyph, mark or one-letter fallback drawn inside the circle. */
5
+ mark: ReactNode;
6
+ /** Read out by assistive tech — the circle carries no text of its own. */
7
+ name: string;
8
+ }
9
+
10
+ export interface RailProps {
11
+ items: RailItem[];
12
+ className?: string;
13
+ }
14
+
15
+ export function Rail({ items, className }: RailProps) {
16
+ return (
17
+ <ul className={['bl-rail', className].filter(Boolean).join(' ')}>
18
+ {items.map((item) => (
19
+ <li className="bl-rail__item" key={item.name}>
20
+ <span className="bl-rail__mark" aria-hidden="true">
21
+ {item.mark}
22
+ </span>
23
+ <span className="bl-rail__name">{item.name}</span>
24
+ </li>
25
+ ))}
26
+ </ul>
27
+ );
28
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ The divider, and the thing to reach for before a second card. `hairline` is 1px
6
+ `--line-weak` for a seam inside a group; `strong` is 1.5px `--line`, the weight
7
+ the reference gives a panel edge.
8
+
9
+ A rule costs no elevation, which is the point: this pack's offsets are a budget
10
+ and a list of statements does not need to spend one.
11
+
12
+ ```tsx
13
+ <Rule />
14
+ <Rule tone="strong" />
15
+ ```