sheleg-design-skill 1.50.2 → 1.51.1

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 (53) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +7 -5
  3. package/bin/cli.js +5 -2
  4. package/cursor/rules/sheleg-design.mdc +8 -1
  5. package/kits/deskmate/.design-sync/config.json +14 -0
  6. package/kits/deskmate/.design-sync/conventions.md +52 -0
  7. package/kits/deskmate/README.md +36 -0
  8. package/kits/deskmate/package.json +29 -0
  9. package/kits/deskmate/src/Button.md +19 -0
  10. package/kits/deskmate/src/Button.tsx +33 -0
  11. package/kits/deskmate/src/Card.md +16 -0
  12. package/kits/deskmate/src/Card.tsx +24 -0
  13. package/kits/deskmate/src/Chip.md +16 -0
  14. package/kits/deskmate/src/Chip.tsx +25 -0
  15. package/kits/deskmate/src/Empty.md +18 -0
  16. package/kits/deskmate/src/Empty.tsx +19 -0
  17. package/kits/deskmate/src/Eyebrow.md +16 -0
  18. package/kits/deskmate/src/Eyebrow.tsx +20 -0
  19. package/kits/deskmate/src/Field.md +17 -0
  20. package/kits/deskmate/src/Field.tsx +34 -0
  21. package/kits/deskmate/src/Heading.md +16 -0
  22. package/kits/deskmate/src/Heading.tsx +19 -0
  23. package/kits/deskmate/src/Message.md +17 -0
  24. package/kits/deskmate/src/Message.tsx +23 -0
  25. package/kits/deskmate/src/NavSlab.md +22 -0
  26. package/kits/deskmate/src/NavSlab.tsx +20 -0
  27. package/kits/deskmate/src/QuotedCard.md +22 -0
  28. package/kits/deskmate/src/QuotedCard.tsx +30 -0
  29. package/kits/deskmate/src/Rule.md +15 -0
  30. package/kits/deskmate/src/Rule.tsx +18 -0
  31. package/kits/deskmate/src/Skeleton.md +16 -0
  32. package/kits/deskmate/src/Skeleton.tsx +20 -0
  33. package/kits/deskmate/src/Stat.md +13 -0
  34. package/kits/deskmate/src/Stat.tsx +17 -0
  35. package/kits/deskmate/src/Transcript.md +24 -0
  36. package/kits/deskmate/src/Transcript.tsx +24 -0
  37. package/kits/deskmate/src/index.ts +31 -0
  38. package/kits/deskmate/src/styles.css +840 -0
  39. package/kits/deskmate/tsconfig.json +15 -0
  40. package/package.json +2 -2
  41. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  42. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  43. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  44. package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +1 -1
  45. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  46. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +1 -1
  47. package/plugins/sheleg-design/skills/sheleg-design/STYLE_PACK_INDEX.md +3 -2
  48. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
  49. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +1 -1
  50. package/plugins/sheleg-design/skills/sheleg-design/styles/cyclorama.md +7 -0
  51. package/plugins/sheleg-design/skills/sheleg-design/styles/deskmate.md +521 -0
  52. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +7 -1
  53. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/deskmate.css +353 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,71 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.51.1] - 2026-08-27
10
+
11
+ ### `deskmate`, the thirty-sixth pack — a colleague, quoted
12
+
13
+ **`v1.51.0` is a ghost tag and nothing shipped under it.** It was pushed while `main`
14
+ had moved on, so the tag named a commit no clone could reach; the release workflow's
15
+ own guard refused before cutting a release or publishing, which is what that guard is
16
+ for. The tag ruleset forbids moving or deleting a `v*` tag and the branch ruleset
17
+ forbids the merge commit that would make it reachable, so the release ships one patch
18
+ number up rather than as a tag pointing off the default branch. The content is this
19
+ entry's; there is no 1.51.0 release to look for.
20
+
21
+ Extracted from a front page for an AI employee that works inside a chat client, read
22
+ off computed styles at 1440×900 (1,191 visible of 1,888 elements on a 10,211px page)
23
+ plus four more pages for the components the front page does not paint: the form field,
24
+ the search shell, the dusk surface, and the reference's own published brand page.
25
+
26
+ **What the pack contributes.** One light source, and it is above the top edge: every
27
+ gradient origin in the reference's own library sits at or above `y=0`, and every one of
28
+ them runs the same four stops — peach, lilac, violet, deep navy. One ramp does three
29
+ jobs (a field bleed, a panel fill, and one word of a heading through
30
+ `background-clip: text`), everything a hand touches is a 56px pill against 32px slabs,
31
+ and elevation is a field step: 68 white fills on a beige field, 51 hairlines, and two
32
+ shadows on the whole page.
33
+
34
+ **The signature element is a framed transcript**, and the mechanism worth having is its
35
+ quarantine: the quoted chat client keeps its own face, its own ink and its own two
36
+ status colours under a `--quoted-*` namespace, so a status dot cannot reach a third
37
+ party's palette by accident. The frame's radius is concentric and the reference computes
38
+ it at runtime — 47.319px outside, a 15px inset, 32.254px inside — which the pack ships
39
+ as `--r-frame: 48px` and `calc(48px - 15px)`.
40
+
41
+ **Four corrections travel with it.** The `aria-invalid` border misses the 3:1 boundary
42
+ floor by 0.03 (`#b5856c` is 2.97:1 on the field `#faf5f1`); the icon grey is 2.77:1 on
43
+ that field and is declared non-text; 47 interactive elements render under 44px at the
44
+ narrow width; and the light status set measures 2.6–4.1:1 on the dusk field, so that
45
+ surface carries its own four. The status set is lifted out of the quoted transcript
46
+ rather than invented — the brand layer paints no state at all — and `--info` is an alias
47
+ of `--accent` on purpose.
48
+
49
+ ### The render caught four things no gate could
50
+
51
+ The `deskmate` kit was mounted and rendered at 1440, 768 and 390, then read back
52
+ through `getComputedStyle`, per CONTRIBUTING step 8. It found: a navigation slab that kept its link row to 390px and
53
+ pushed its trailing control to **515px against a 390px page**; a secondary control on
54
+ the dusk surface painting **white on white**, because a card there stays white by
55
+ measurement and the control had inherited the card's fill; an accent chip label at
56
+ **3.36:1** on the composited dusk wash, where the brighter step is 6.18:1; and every
57
+ anchor-shaped control carrying the UA's underline. All four are fixed, and the slab's
58
+ collapse is now stated in the pack.
59
+
60
+ ### Ratchets
61
+
62
+ `validate.py` 4882 → 5047, `validate_palette.py` 2875 → 3013, `sloplint.py` 720 → 734,
63
+ and `computed_at_least` 628 → 686. Both ratio ceilings held for the fourth consecutive
64
+ release; two claims that landed unpairable on the first run were rewritten rather than
65
+ pinned.
66
+
67
+ ## [1.50.3] - 2026-08-26
68
+
69
+ ### The shared seam is explicit
70
+
71
+ Both shared validators now state `diverges: none`, completing the umbrella
72
+ mechanism contract.
73
+
9
74
  ## [1.50.2] - 2026-08-26
10
75
 
11
76
  ### Router carriers and shared guard provenance
package/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![CI](https://github.com/ssheleg/sheleg-design-skill/actions/workflows/validate.yml/badge.svg)](https://github.com/ssheleg/sheleg-design-skill/actions/workflows/validate.yml)
5
5
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
6
6
  [![site](https://img.shields.io/badge/docs-skills.sshlg.me-8ab0ff)](https://skills.sshlg.me/skills/sheleg-design/)
7
+ [![skills.sh](https://skills.sh/b/ssheleg/sheleg-design-skill)](https://skills.sh/ssheleg/sheleg-design-skill)
7
8
 
8
9
  **Give coding agents a deliberate visual system: style packs, tokens, motion doctrine and a clean Figma seam.**
9
10
 
@@ -30,7 +31,7 @@ problem — invented colors, six accent hues, dark mode retrofitted later.
30
31
 
31
32
  This skill is the taste layer. It gives a coding agent **one motion
32
33
  methodology** for cinematic, scroll-driven pages, **a motion doctrine** that
33
- decides whether to animate before it decides how, and **thirty-five locked style
34
+ decides whether to animate before it decides how, and **thirty-six locked style
34
35
  packs** with ready-made design tokens, so what it builds reads as one system
35
36
  instead of a pile of effects.
36
37
 
@@ -74,6 +75,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
74
75
  | `nameplate` | a cool near-white slab under a page that is **square on 87% of its elements** — 1,091 of 1,251 measured — where the one round shape is reserved for a white 1px-bordered pill carrying somebody else's publication name **as type rather than as a logo**, one family with the body at **weight 500**, and two uppercase registers tracked 0.06em and 0.175em | **pages whose argument is that named third parties will vouch for you** — press and media placement, PR distribution, trust marks and badges, certification, review aggregation and "as featured in" surfaces (standalone) |
75
76
  | `rimlight` | a white field with a cool grey act separator and one near-black act, a grotesque for every sentence and a **monospace for every piece of chrome**, square on 84% of its elements, tracked negative at every size and with **no bold anywhere** — and an elevation made of **coloured light**: a sixteen-layer rig, six layers lit and ten held at alpha 0, thrown from below-left onto one control per viewport | **a studio's own front door and the pages that sell what it makes** — design and engineering agencies, product studios, service and case-study pages (standalone) |
76
77
  | `onionskin` | a white technical sheet at **96.5% zero radius** — the squarest page in the library — where **two bases do all the work and everything quiet is one of them at an alpha**: text dims through the ink, structure through a navy that is never a word, so there is no grey ramp at all. A dot grid, dashed hairlines, three faces with one job each, and an **11px working size** | **developer and AI infrastructure whose front page is a working document** — memory and context engines, retrieval services, agent runtimes, evaluation and observability surfaces (standalone) |
78
+ | `deskmate` | a warm beige working day lit from **one source above the top edge** — every gradient origin in the reference sits at or above `y=0` — where a single four-stop ramp (peach, lilac, violet, deep navy) washes the field, fills a panel and fills one word of a heading, and everything a hand touches is a 56px pill against 32px slabs | **products sold as a colleague rather than a tool** — AI employees and chat-native agents, assistants living in Slack or Teams, delegation and approval surfaces (standalone) |
77
79
  | `outrank` | white field with one violet that carries text in both directions, a 5px light ring standing in for a button shadow, and two type families split by how long a thing is read | SEO and growth SaaS, agent-run back-office — the pack for a product that ships a landing and a dashboard at once |
78
80
  | `orchard` | warm oat field of rounded slabs, sage brand + one candy-orange action, rounded geometric display, soft-3D pills built from inset light | friendly consumer biotech, DTC wellness, testing kits & supplements |
79
81
  | `field-notes` | warm green-cast paper ruled by hairlines, one rust accent, a hero that dissolves into the page instead of ending, numbered mono eyebrows, crop marks, colour that encodes how a claim is known | open-source & developer tools sold on auditability — code intelligence, provenance, evals, agent memory |
@@ -174,7 +176,7 @@ skills.
174
176
  | `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 |
175
177
  | `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 |
176
178
  | `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 |
177
- | `styles/*.md` | The thirty-five style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
179
+ | `styles/*.md` | The thirty-six style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
178
180
  | `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) |
179
181
  | `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
180
182
 
@@ -245,7 +247,7 @@ cd ./ds-workbench && npm install && npm run build
245
247
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
246
248
  pack's **bans** as the design system's own README, `styles.css` built from
247
249
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
248
- identical in all thirty-five kits, so switching packs swaps identity rather than API,
250
+ identical in all thirty-six kits, so switching packs swaps identity rather than API,
249
251
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
250
252
  not cross into Figma: a kit is the static half of a pack, and saying so is what
251
253
  stops an agent inventing motion to fill the silence.
@@ -295,10 +297,10 @@ a pack's four widened sections used to make two gates *quieter* and still green.
295
297
  One honest limit: the npx installer is checked by asserting its runtime bundle
296
298
  walker exists, not by reading a file list — it has none by design. What proves
297
299
  it ships the right files is CI, which installs the bundle through **both**
298
- installers and `diff -r`s the result against the source, then builds all thirty-five
300
+ installers and `diff -r`s the result against the source, then builds all thirty-six
299
301
  kits.
300
302
 
301
- `test/scenarios.md` (T1–T30) is the behavioral harness: fresh subagents given a
303
+ `test/scenarios.md` (T1–T34) is the behavioral harness: fresh subagents given a
302
304
  task, checking that the skill is discovered, applied and quoted correctly.
303
305
  Re-run the affected scenarios after any edit to `SKILL.md`, a pack or the
304
306
  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/ thirty-five style packs — instrument-console (dark console),
237
+ styles/ thirty-six 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), patchbay (dark live
@@ -247,7 +247,10 @@ ${c("bold", "What it installs")}
247
247
  shadow), onionskin (a white technical sheet at 96.5%
248
248
  zero radius where every quiet value is the ink or a
249
249
  navy at an alpha, over a dot grid, at an 11px working
250
- size), babylove (white +
250
+ size), deskmate (warm beige lit from above the top
251
+ edge, one four-stop ramp, 56px pills against 32px
252
+ slabs, and a framed transcript as the set piece),
253
+ babylove (white +
251
254
  one orange, seven tokens over Tailwind defaults),
252
255
  outrank (white +
253
256
  one violet, a landing and a dashboard in one brand),
@@ -117,7 +117,14 @@ onionskin — a white technical sheet at 96.5% zero radius where two bases do al
117
117
  work and everything quiet is one of them at an alpha: text dims through the ink,
118
118
  structure through a navy that is never a word, over a dot grid with dashed hairlines,
119
119
  three faces with one job each and an 11px working size, for developer and AI
120
- infrastructure whose front page is a working document);
120
+ infrastructure whose front page is a working document;
121
+ deskmate — a warm beige working day lit from one source above the top edge, where a
122
+ single four-stop ramp of peach, lilac, violet and deep navy washes the field, fills a
123
+ panel and fills one word of a heading, every control is a 56px pill against 32px
124
+ slabs, elevation is a field step with two shadows on the whole page, and the set
125
+ piece is a framed transcript whose quoted chat client keeps its own face and colours
126
+ under a --quoted-* namespace, for products sold as a colleague rather than a tool:
127
+ AI employees and chat-native agents);
121
128
  otherwise follow the contract below (self-contained on purpose).
122
129
 
123
130
  ## Whether to animate at all — before how
@@ -0,0 +1,14 @@
1
+ {
2
+ "pkg": "@sheleg-design/deskmate",
3
+ "globalName": "ShelegDeskmate",
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,52 @@
1
+ # Deskmate — conventions for a design agent
2
+
3
+ This kit is the built form of the Deskmate style pack. Read these before generating
4
+ anything with it; they are the pack's bans, stated where a design tool will see them.
5
+
6
+ ## Geometry
7
+
8
+ - **A control is a pill.** `--r-pill` on anything a hand touches, at `--control-h`
9
+ (56px) or `--control-h-sm` (40px), never below `--tap-min` (44px).
10
+ - **A container is a slab** at `--r-card` (32px). Sections and the navigation slab take
11
+ `--r-section` and round their **bottom** corners only — nothing here rounds a top
12
+ corner against the page edge.
13
+ - **The frame is concentric**: `--r-frame` outside, `--frame-inset` of padding, and
14
+ `--r-frame-inner` = `calc(--r-frame - --frame-inset)` inside. Never the same radius on
15
+ both boxes.
16
+
17
+ ## Colour
18
+
19
+ - One accent, `--accent`, which both writes and fills. `--peach` is a gradient stop and
20
+ a mark; it is never a word.
21
+ - **One ramp**, `--gradient-dusk`, and its origin is always above the top edge of the
22
+ box it fills. A gradient lit from below is not this pack.
23
+ - **One gradient word per heading** and one gradient control per page. The word carries
24
+ `--gradient-word-fallback` as a solid `color` first, or it disappears where
25
+ `background-clip: text` is unsupported.
26
+ - **Status is never by colour alone.** Every state takes a word or a glyph beside its
27
+ colour, on the light field and on the dusk surface.
28
+ - `[data-surface="dusk"]` is a **surface variant, not a dark theme**. Put it on a
29
+ section, never on `:root`, and leave the frame alone — it re-declares its own tokens
30
+ on purpose.
31
+
32
+ ## Type
33
+
34
+ - `--font-display` for headings only, at `--weight-regular` for the display and
35
+ `--weight-strong` for a section heading, always at `--track-display`.
36
+ - `--font-body` at **`--weight-body` (500)** for every sentence. An unset weight gives
37
+ 400 and renders the whole page one step light.
38
+ - `--font-quoted` only inside the frame. It is the chat client's face, not the brand's.
39
+
40
+ ## Motion
41
+
42
+ - Two clocks: `--dur-fast` for anything a pointer caused, `--dur-enter` on `--ease-out`
43
+ for anything the scroll revealed. No scroll clock, no parallax, no `animation-timeline`.
44
+ - No shimmer on a skeleton.
45
+ - Reduced motion collapses every duration and `--enter-y`: colour still changes, nothing
46
+ travels.
47
+
48
+ ## Breakpoints
49
+
50
+ A component sizes against its own box. `container-type: inline-size` on the root and
51
+ `@container` on the descendant; a viewport query is only for the page's gutter and the
52
+ display's own size, and both are marked in the stylesheet with the reason.
@@ -0,0 +1,36 @@
1
+ # @sheleg-design/deskmate
2
+
3
+ The React reference kit for the **Deskmate** style pack — a warm beige working day lit
4
+ from one source above the top edge, where the product's own messages are the page's
5
+ illustrations.
6
+
7
+ The pack document is the source of truth for every value here:
8
+ `plugins/sheleg-design/skills/sheleg-design/styles/deskmate.md`. This kit is what those
9
+ values look like when they are built, and `src/styles.css` opens with the pack's token
10
+ layer copied byte for byte — never transcribed.
11
+
12
+ ```bash
13
+ npm install && npm run build # tsc only; there is no bundler in this kit
14
+ ```
15
+
16
+ ## What ships
17
+
18
+ **The spine**, identical in name, props and types across every SHELEG kit, so switching
19
+ packs swaps identity rather than API: `Button`, `Card`, `Chip`, `Stat`, `Heading`,
20
+ `Rule`.
21
+
22
+ **The signature**, which is this pack's own: `Transcript` (the framed quote, and the
23
+ element a page here is remembered by), `Message`, `QuotedCard`, `Eyebrow`, `Field`,
24
+ `NavSlab`, `Empty` and `Skeleton`.
25
+
26
+ ## Three rules this kit exists to keep
27
+
28
+ 1. **A control is a pill at 56px; a container is a slab at 32px.** Swap them and the
29
+ pack inverts. Every control also declares `box-sizing: border-box` and a
30
+ `min-height` of `--tap-min`, because an anchor and a button disagree about padding
31
+ otherwise and the taller one silently wins.
32
+ 2. **Elevation is a field step.** There are two shadow tokens and both are spent — one
33
+ under the frame, one bloom. A third shadow is a defect.
34
+ 3. **Nothing inside the frame is the brand's.** The quoted client keeps `--quoted-*`:
35
+ its own face, its own ink, its own two status colours. A status dot that reaches for
36
+ them has adopted somebody else's design system.
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@sheleg-design/deskmate",
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,19 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ Every control is a pill at `--control-h` (56px) with `--control-px` of horizontal
6
+ padding and a `--rule-w` border, 16px at `--weight-body` and `--track-control`. `sm` is
7
+ `--control-h-sm` (40px) at 24px and 14px — the navigation's size.
8
+
9
+ `primary` fills with `--ink` and labels in `--on-ink`, 17.36:1. `secondary` is
10
+ `--surface` with the ink at 12% and hover mixes 6% of the ink into the fill.
11
+ `ghost` is transparent until hover, which fills with the ink at 5%.
12
+
13
+ Press travels 1px down and nothing scales. Focus moves the border to `--focus-color`
14
+ and adds `--focus-ring` — both, always.
15
+
16
+ ```tsx
17
+ <Button>Get started for free</Button>
18
+ <Button variant="secondary">Book a demo</Button>
19
+ ```
@@ -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={['dm-btn', `dm-btn--${variant}`, `dm-btn--${size}`, className]
25
+ .filter(Boolean)
26
+ .join(' ')}
27
+ disabled={disabled}
28
+ onClick={onClick}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ `--surface` on `--bg` at `--r-card`, no border and no shadow: the field step **is** the
6
+ elevation, and the page has two shadows in total, both spent elsewhere.
7
+
8
+ Padding is 24px above the title and 32px around the body, which is the reference's own
9
+ asymmetry — the title row sits closer to the top edge than the body does to the sides.
10
+ For the two-half card the page is actually made of, see `QuotedCard`.
11
+
12
+ ```tsx
13
+ <Card title="Payroll prep" meta="10:20 AM">
14
+ <p>Attendance and collections reconciled.</p>
15
+ </Card>
16
+ ```
@@ -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={['dm-card', className].filter(Boolean).join(' ')}>
15
+ {head && (
16
+ <div className="dm-card__head">
17
+ {title !== undefined && <h3 className="dm-card__title">{title}</h3>}
18
+ {meta !== undefined && <span className="dm-card__meta">{meta}</span>}
19
+ </div>
20
+ )}
21
+ <div className="dm-card__body">{children}</div>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ A small tile rather than a pill label: `--r-chip` (19.6px), `--surface`, a `--rule-w`
6
+ hairline at the ink's 5%, 10px of padding. It carries a filename, a tool mark, a count
7
+ — something the page is quoting.
8
+
9
+ `accent` swaps the fill for `--accent-wash` and the label for `--accent-deep`, which is
10
+ the eyebrow's pair. `selected` moves the hairline to `--accent` and nothing else moves;
11
+ for a status, use the word and the glyph, because status here is never by colour alone.
12
+
13
+ ```tsx
14
+ <Chip>Weekly-Performance.pdf</Chip>
15
+ <Chip tone="accent" selected>3 discrepancies</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
+ 'dm-chip',
15
+ `dm-chip--${tone}`,
16
+ selected ? 'dm-chip--selected' : undefined,
17
+ className,
18
+ ]
19
+ .filter(Boolean)
20
+ .join(' ')}
21
+ >
22
+ {children}
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ What a screen with no data says. It exists because the reference does not have one: a
6
+ search matching nothing collapses its grid to zero height and prints no message, which
7
+ is indistinguishable from a broken query.
8
+
9
+ A `--surface` card at `--r-card`, a line in `--ink`, one sentence in `--ink-soft`, and
10
+ one control that changes the condition.
11
+
12
+ ```tsx
13
+ <Empty
14
+ title="No integrations match “zzq”."
15
+ detail="Check the spelling, or clear the filter to see all of them."
16
+ action={<Button variant="secondary" size="sm">Clear filter</Button>}
17
+ />
18
+ ```
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface EmptyProps {
4
+ title: string;
5
+ /** One sentence saying what would fill this, or what to change. */
6
+ detail?: string;
7
+ action?: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ export function Empty({ title, detail, action, className }: EmptyProps) {
12
+ return (
13
+ <div className={['dm-empty', className].filter(Boolean).join(' ')}>
14
+ <p className="dm-empty__title">{title}</p>
15
+ {detail !== undefined && <p className="dm-empty__detail">{detail}</p>}
16
+ {action !== undefined && <div className="dm-empty__action">{action}</div>}
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ The 20px pill above a heading: `--wash` fill, `--accent-deep` label, 12px at
6
+ `--track-caps`. It names the section before the heading argues it, and it is the only
7
+ place in the pack where a tracked-open label appears.
8
+
9
+ On a dusk section pass `surface="dusk"` — the fill becomes the on-dusk wash and the
10
+ label `--accent`. The shape is identical, which is the point: a surface variant changes
11
+ values, never geometry.
12
+
13
+ ```tsx
14
+ <Eyebrow>Why it feels like a hire</Eyebrow>
15
+ <Eyebrow surface="dusk">One hire, the output of a team</Eyebrow>
16
+ ```
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface EyebrowProps {
4
+ children: ReactNode;
5
+ /** `dusk` swaps the wash for the on-dusk pair; the shape never changes. */
6
+ surface?: 'light' | 'dusk';
7
+ className?: string;
8
+ }
9
+
10
+ export function Eyebrow({ children, surface = 'light', className }: EyebrowProps) {
11
+ return (
12
+ <span
13
+ className={['dm-eyebrow', `dm-eyebrow--${surface}`, className]
14
+ .filter(Boolean)
15
+ .join(' ')}
16
+ >
17
+ {children}
18
+ </span>
19
+ );
20
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ The pill field: `--control-h` tall, `--surface` fill, a `--rule-w` hairline at the
6
+ ink's 12%, and the same geometry as `Button` so a form row reads as one object. Focus
7
+ moves the border to `--focus-color` **and** adds `--focus-ring-field` — both, because
8
+ the ring alone composites too faint to carry the state.
9
+
10
+ `error` is what makes the invalid state legal: the border takes `--danger` and the
11
+ message appears with it. A red border on its own is a colour carrying meaning alone,
12
+ which this pack bans.
13
+
14
+ ```tsx
15
+ <Field label="Work email" name="email" type="email" placeholder="you@company.com" />
16
+ <Field label="Work email" name="email" type="email" error="Use your work address." />
17
+ ```
@@ -0,0 +1,34 @@
1
+ export interface FieldProps {
2
+ label: string;
3
+ name: string;
4
+ type?: 'text' | 'email' | 'search';
5
+ placeholder?: string;
6
+ /** The message shown beside the invalid border — the colour never travels alone. */
7
+ error?: string;
8
+ className?: string;
9
+ }
10
+
11
+ export function Field({ label, name, type = 'text', placeholder, error, className }: FieldProps) {
12
+ const described = error !== undefined ? `${name}-error` : undefined;
13
+ return (
14
+ <div className={['dm-field', className].filter(Boolean).join(' ')}>
15
+ <label className="dm-field__label" htmlFor={name}>
16
+ {label}
17
+ </label>
18
+ <input
19
+ className="dm-field__input"
20
+ id={name}
21
+ name={name}
22
+ type={type}
23
+ placeholder={placeholder}
24
+ aria-invalid={error !== undefined || undefined}
25
+ aria-describedby={described}
26
+ />
27
+ {error !== undefined && (
28
+ <p className="dm-field__error" id={described}>
29
+ {error}
30
+ </p>
31
+ )}
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ `1` is the display at `--t-display` (80px, stepping to 72 and 64) in `--font-display`
6
+ at `--weight-regular` and `--track-display`. `2` is 48px at `--weight-strong`, same
7
+ tracking. `3` is 24px, and it is the card title.
8
+
9
+ The display holds **two lines** and the column holds the display — there is no measure
10
+ on it. Put one gradient word per heading and no more: wrap it in
11
+ `<span className="dm-word">`, which ships `--gradient-word` with its solid fallback
12
+ `color` underneath.
13
+
14
+ ```tsx
15
+ <Heading level={1}>Not a tool. <span className="dm-word">A hire.</span></Heading>
16
+ ```
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface HeadingProps {
4
+ /** 1 = page title (28px), 2 = section (20px), 3 = card title (15px). */
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={['dm-heading', `dm-heading--${level}`, className].filter(Boolean).join(' ')}
15
+ >
16
+ {children}
17
+ </Tag>
18
+ );
19
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ One turn inside the frame. `person` and `app` are the only kinds, because the whole
6
+ argument of a page in this pack is that two of them alternate — and `app` is the one
7
+ that carries the `APP` badge, drawn in the client's own badge colours rather than in
8
+ the brand's.
9
+
10
+ A message never takes a brand colour. If it needs to say a state, it says it in words
11
+ and takes the glyph beside it: status here is never by colour alone.
12
+
13
+ ```tsx
14
+ <Message author="Agent" kind="app" time="10:20 AM">
15
+ Reconciled attendance and collections.
16
+ </Message>
17
+ ```