sheleg-design-skill 1.48.0 → 1.49.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 (51) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/README.md +5 -4
  3. package/bin/cli.js +5 -2
  4. package/cursor/rules/sheleg-design.mdc +6 -1
  5. package/kits/nameplate/src/Mark.md +24 -0
  6. package/kits/nameplate/src/Mark.tsx +26 -0
  7. package/kits/nameplate/src/index.ts +2 -0
  8. package/kits/nameplate/src/styles.css +74 -4
  9. package/kits/rimlight/.design-sync/config.json +14 -0
  10. package/kits/rimlight/.design-sync/conventions.md +62 -0
  11. package/kits/rimlight/README.md +39 -0
  12. package/kits/rimlight/package.json +29 -0
  13. package/kits/rimlight/src/Act.md +19 -0
  14. package/kits/rimlight/src/Act.tsx +21 -0
  15. package/kits/rimlight/src/Button.md +17 -0
  16. package/kits/rimlight/src/Button.tsx +33 -0
  17. package/kits/rimlight/src/Card.md +14 -0
  18. package/kits/rimlight/src/Card.tsx +24 -0
  19. package/kits/rimlight/src/Chip.md +12 -0
  20. package/kits/rimlight/src/Chip.tsx +25 -0
  21. package/kits/rimlight/src/Heading.md +14 -0
  22. package/kits/rimlight/src/Heading.tsx +19 -0
  23. package/kits/rimlight/src/Label.md +16 -0
  24. package/kits/rimlight/src/Label.tsx +18 -0
  25. package/kits/rimlight/src/LitButton.md +22 -0
  26. package/kits/rimlight/src/LitButton.tsx +26 -0
  27. package/kits/rimlight/src/Rule.md +13 -0
  28. package/kits/rimlight/src/Rule.tsx +18 -0
  29. package/kits/rimlight/src/Stat.md +13 -0
  30. package/kits/rimlight/src/Stat.tsx +17 -0
  31. package/kits/rimlight/src/Tile.md +16 -0
  32. package/kits/rimlight/src/Tile.tsx +22 -0
  33. package/kits/rimlight/src/index.ts +23 -0
  34. package/kits/rimlight/src/styles.css +546 -0
  35. package/kits/rimlight/tsconfig.json +15 -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/FIGMA_BRIDGE.md +1 -1
  41. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  42. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +4 -3
  43. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
  44. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +50 -1
  45. package/plugins/sheleg-design/skills/sheleg-design/styles/nameplate.md +36 -2
  46. package/plugins/sheleg-design/skills/sheleg-design/styles/pigeonhole.md +1 -0
  47. package/plugins/sheleg-design/skills/sheleg-design/styles/rimlight.md +342 -0
  48. package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +1 -0
  49. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
  50. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/nameplate.css +51 -4
  51. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/rimlight.css +306 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,160 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.49.0] - 2026-08-24
10
+
11
+ ### Rimlight, the thirty-fourth pack — elevation made of light
12
+
13
+ Extracted from `peppermint.global/services/web-design`, a design studio's service page.
14
+ The site is Webflow: one shared stylesheet of 230,354 bytes declaring 82 custom
15
+ properties, and an interaction engine that ships on the page and drives exactly **one**
16
+ element. Every value was read off **computed styles on the live page** through CDP at
17
+ 1440×900 — 487 visible of 562 elements on a 12,410px page — and at a device-emulated
18
+ 390×844.
19
+
20
+ **The signature is a light, not a shadow.** The primary control wears a **sixteen-layer**
21
+ `box-shadow`: six layers lit — a white rim, a teal spill, a cyan throw, two insets that
22
+ keep it from reading as a sticker, and a mint bloom at 44px of blur offset −14.78px in x
23
+ — and **ten pre-declared at alpha 0**, every offset already in place so a direction can
24
+ be lit by moving alpha alone with no relayout. The light sits below and to the left. On a
25
+ page otherwise made of ink, one blue and two greys, that bloom is the only chromatic
26
+ event, so the most saturated thing on the screen is also the thing you are meant to click.
27
+ No pack in the library had elevation made of coloured light.
28
+
29
+ **Reading the render mattered twice, in opposite directions**, which is exactly what the
30
+ template's new rule 7 was written for a release earlier. The uppercase micro-labels *look*
31
+ generously tracked and are not — `letter-spacing` computes to `normal` on every one, and
32
+ the openness is Source Code Pro's own advance width, so a pack matched from a screenshot
33
+ ships a tracking value that does not exist. And the button's elevation *looks* like one
34
+ soft shadow; sixteen layers only resolve under `getComputedStyle`.
35
+
36
+ **The palette is split by field, and that is the pack's central law.** Every one of the
37
+ reference's five secondary hues measures 6.5–10.5:1 on the dark act and **1.65–2.65:1 on
38
+ the white page**. They did not fail — they were designed for the dark, and the light field
39
+ is where they must be held back. So the hues live in `[data-surface="dark"]`, which is a
40
+ **surface variant and not a theme twin**: one section is dark, the document never inverts,
41
+ and the four statuses are remapped rather than inherited because the light set measures
42
+ 1.1–1.5:1 there.
43
+
44
+ **The blue is two tokens because it does two jobs.** `--accent` is 3.03:1 on `--bg` —
45
+ below the 4.5:1 body floor and above the 3:1 large-text one — so it is declared
46
+ `@role non-text` with its exemption stated: legal on a word only at ≥ 24px, which is where
47
+ the reference spends it, at 86px in the headline's first phrase. Anything smaller takes
48
+ `--accent-ink` at 5.16:1.
49
+
50
+ **Four corrections travel with the pack.** Secondary copy is `#9d9fa3` at 2.65:1 on the
51
+ page. The tertiary grey `#767676` passes the page at 4.54:1 and fails the act separator at
52
+ 4.05:1. The reference's secondary set is a palette and not a scheme — eight of its
53
+ twenty-one pairs are too close to separate under dichromacy — so the four status roles were
54
+ derived from its own hues rather than adopted from its names. And 26 of 37 visible
55
+ interactive elements at 390 are under 44px.
56
+
57
+ **Two doctrine corrections the gates caught, both worth recording.** The pack prescribed
58
+ its press at the reference's own 200ms, which sits outside the doctrine's 100–160ms band,
59
+ so `--dur-press` takes the band's ceiling instead of the nearest measured value — the one
60
+ motion value here that is not measured, and it says so. And the pack bans bold while the
61
+ token layer shipped no base rule for it: the UA supplies `strong { font-weight: 700 }`
62
+ whether a pack does or not, so a ban with no rule is invisible. The layer now sets
63
+ `strong, b { font-weight: var(--weight-label) }` — 500, the heaviest weight this pack has.
64
+
65
+ **Rendering the kit caught a defect the gates could not**, for the third release running.
66
+ The light control variant rendered *dark* inside the dark act: it was built on
67
+ `--surface`, which `[data-surface="dark"]` remaps to `#242424`, so a control that is white
68
+ by definition followed the field and the rig lit nothing. `--lit-light-fill` does not
69
+ follow the field. A gate reads structure, not layout.
70
+
71
+ `MOTION_INTENSITY` is pinned at **2** — the lowest ceiling in the library beside
72
+ `bulletin`. 432 of 487 elements compute `transition-duration: 0s`, `animation-timeline`
73
+ appears zero times, and the rig is a **static** light: animating it is the pack's first
74
+ ban and the fastest way to turn it into a toy.
75
+
76
+ The fork against `showroom` is written from both ends: both are white and both spend a big
77
+ layered elevation on one object, and the distinction is that `showroom` lights the
78
+ application in a neutral shadow dropped from above while this lights a control in coloured
79
+ light thrown from the side — and this hero carries no product at all. `ADR-0001` gains a
80
+ further application, with `halation`, `limelight` and `luminaire` weighed and rejected.
81
+
82
+ Gates: 4626 → **4754**, 2681 → **2784**, 692 → **706**. Both palette ceilings held for the
83
+ second consecutive release; `computed_at_least` rises 572 → 600.
84
+
85
+ ## [1.48.1] - 2026-08-24
86
+
87
+ ### The assembly rule is written down, and the render step earned its place within the hour
88
+
89
+ The procedure for adding a pack lived in three homes — `CONTRIBUTING.md`'s authoring
90
+ steps, `docs/DOCMAP.md`'s propagation row, and the release section — and three things
91
+ the `nameplate` run actually did were in none of them. They are now in the home that
92
+ already owns each, rather than in a fourth document, which `DOCMAP`'s single-homes
93
+ discipline forbids.
94
+
95
+ **`STYLE_PACK_TEMPLATE.md` gains rules 7 and 8**, and both ship inside the bundle so an
96
+ author holding only the installed skill sees them. Rule 7 is *read the render, not the
97
+ stylesheet*: a dead token reads as the brand, a gradient hides from a
98
+ `background-color` census because its element's is transparent, and vendor CSS
99
+ outnumbers authored CSS — three traps met in one run, each with the shipped pack that
100
+ met it. Rule 8 is *write a ratio so the gate can compute it*, with all four shapes that
101
+ fail: a line break between the number and its partner, a token whose name contains
102
+ another's (`--on-action` resolves as `--action`, a gradient that pairs with nothing), a
103
+ table row stating a bare ratio, and a line whose **only** token is the partner, so it
104
+ fills both slots and pairs with itself.
105
+
106
+ **`CONTRIBUTING.md` gains step 8: render the kit and check its computed values against
107
+ what the pack claims.** Not a screenshot for the eye — mount it on real content at
108
+ 1440, 768 and an emulated 390, then read `getComputedStyle` back. The step existed
109
+ only as a sentence in `bulletin`'s CHANGELOG entry, so whether it happened depended on
110
+ the next author reading that paragraph. **It caught a defect within the hour of being
111
+ written:** the `Mark` tile added in this same release rendered 60×60 against a declared
112
+ 50, because `.np-mark` was not in the kit's `border-box` list — the same defect as the
113
+ plate one release earlier, in the component added to close a different gap.
114
+
115
+ ### The mark set, and what measuring it changed
116
+
117
+ `nameplate`'s reference repeats one object per feature on the report surface it embeds:
118
+ a 50×50 tile at 16px radius wearing a **5px solid ring**, a wash at 9–12% alpha and a
119
+ glyph in the matching solid. Six hue pairs drive it and the pack carried none of them,
120
+ so an agent building the reference's own feature row would have invented six colours.
121
+
122
+ **Measuring them changed the answer from "carry six hues" to "carry a mechanism and a
123
+ ban".** The set does not separate from itself: seven of its fifteen pairs are tight,
124
+ and the reference's own indigo `#5b4bd4` and violet `#6c5ce7` are 5.58 OKLab units
125
+ apart at full colour — one colour wearing two names. So the hue is variety, never
126
+ meaning; it does not encode which feature this is; and the tokens are **numbered**
127
+ rather than named so nothing can misread them as semantic. That is the measured
128
+ difference from `pigeonhole`, whose hues *are* a taxonomy and whose label word is
129
+ therefore mandatory, and the fork is now written from both ends.
130
+
131
+ The sixth hue was dropped for a second measured reason: the reference's red `#d94558`
132
+ sits **2.92** units from `--action-to` and 1.12 under the worst dichromacy — the same
133
+ colour to every reader, not only to a colourblind one. A mark in the action's colour
134
+ reads as a control, so the coral stays with `--action` and the sixth slot takes
135
+ `--mark-neutral`. All six clear the 3:1 non-text floor on their own composited tile,
136
+ and none may carry a word: `--mark-4` is 3.89:1 on the slab, `--mark-2` is 4.27:1, and
137
+ the dropped red was 3.96:1.
138
+
139
+ Ships `Mark` as the kit's twelfth component, with `tone` as a number precisely because
140
+ it means nothing.
141
+
142
+ ### A claim that was wider than its evidence
143
+
144
+ The pack said *the reference has no dark band anywhere*, and what had been checked was
145
+ the home page at 1440. The authored stylesheets declare **eight** dark fills, and both
146
+ kinds were then checked: `.bg-header-blue` (`#231b66`) is dead CSS no served page uses,
147
+ and the one that renders (`#1f2937`) sits inside a **mock of another product's
148
+ interface** in the hero image. The claim holds in substance and now states the width it
149
+ was measured at, in the Bans and in the token layer's own comment. A claim wider than
150
+ its evidence is this repository's own defect class, and it does not get an exemption
151
+ for being ours.
152
+
153
+ ### Still open, and named rather than quietly dropped
154
+
155
+ `T31` is unrun — it needs fresh contexts reading blind. And 29% of the reference's
156
+ authored CSS is still unmeasured into the pack: the checkout (`order-form-cro.css`,
157
+ 66,546 B) and the report surface (71,302 B) of 500,051 B total. The pack describes a
158
+ front door and says nothing about the payment step. That is an extension to a second
159
+ surface, not a loose end.
160
+
161
+ Gates: 4617 → **4626**, 2659 → **2681**, 692 unchanged. Both palette ceilings held.
162
+
9
163
  ## [1.48.0] - 2026-08-24
10
164
 
11
165
  ### Nameplate, the thirty-third pack — the proof is a borrowed name
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 **thirty-three locked style
14
+ decides whether to animate before it decides how, and **thirty-four 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
 
@@ -53,6 +53,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
53
53
  | `babylove` | white with one orange in six steps and nothing else declared — seven tokens over Tailwind's defaults, two card levels nested at 16 and 8px, no shadow and no dark theme | SEO and AI-visibility SaaS with a long time-to-value — the minimal-system answer to the same brief `outrank` answers maximally |
54
54
  | `patchbay` | near-black under a faint 40px grid, one mint-cyan doing every functional job, elevation made of 8% hairlines with no shadow anywhere, and a live architecture diagram whose cords carry SMIL particles | engines, message buses, pipelines, schedulers and open-source front doors — anything whose argument is an architecture rather than a promise |
55
55
  | `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) |
56
+ | `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) |
56
57
  | `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 |
57
58
  | `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 |
58
59
  | `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 |
@@ -153,7 +154,7 @@ skills.
153
154
  | `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 |
154
155
  | `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 |
155
156
  | `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 |
156
- | `styles/*.md` | The thirty-three style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
157
+ | `styles/*.md` | The thirty-four style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
157
158
  | `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) |
158
159
  | `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
159
160
 
@@ -224,7 +225,7 @@ cd ./ds-workbench && npm install && npm run build
224
225
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
225
226
  pack's **bans** as the design system's own README, `styles.css` built from
226
227
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
227
- identical in all thirty-three kits, so switching packs swaps identity rather than API,
228
+ identical in all thirty-four kits, so switching packs swaps identity rather than API,
228
229
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
229
230
  not cross into Figma: a kit is the static half of a pack, and saying so is what
230
231
  stops an agent inventing motion to fill the silence.
@@ -270,7 +271,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
270
271
  One honest limit: the npx installer is checked by asserting its runtime bundle
271
272
  walker exists, not by reading a file list — it has none by design. What proves
272
273
  it ships the right files is CI, which installs the bundle through **both**
273
- installers and `diff -r`s the result against the source, then builds all thirty-three
274
+ installers and `diff -r`s the result against the source, then builds all thirty-four
274
275
  kits.
275
276
 
276
277
  `test/scenarios.md` (T1–T30) is the behavioral harness: fresh subagents given a
package/bin/cli.js CHANGED
@@ -234,14 +234,17 @@ ${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-three style packs — instrument-console (dark console),
237
+ styles/ thirty-four 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
241
241
  schematic), nameplate (a cool near-white slab under a
242
242
  square page, the one round shape reserved for a white
243
243
  bordered pill carrying somebody else's publication name
244
- as type), babylove (white +
244
+ as type), rimlight (a white field and one near-black
245
+ act, a monospace carrying every label and button, and
246
+ an elevation made of coloured light rather than a
247
+ shadow), babylove (white +
245
248
  one orange, seven tokens over Tailwind defaults),
246
249
  outrank (white +
247
250
  one violet, a landing and a dashboard in one brand),
@@ -107,7 +107,12 @@ the one round shape reserved for a white 1px-bordered pill carrying somebody
107
107
  else's publication name as type, one family with the body at weight 500, and two
108
108
  uppercase registers tracked 0.06em and 0.175em, for pages whose argument is that
109
109
  named third parties will vouch for you: press placement, trust marks,
110
- certification and review aggregation);
110
+ certification and review aggregation;
111
+ rimlight — a white field with a cool grey act separator and one near-black act, a
112
+ grotesque for every sentence and a monospace for every piece of chrome, square on 84%
113
+ of its elements, tracked negative at every size and with no bold anywhere, whose only
114
+ elevation is a sixteen-layer coloured light rig thrown from below-left onto one
115
+ control per viewport, for a studio's front door and its service and case-study pages);
111
116
  otherwise follow the contract below (self-contained on purpose).
112
117
 
113
118
  ## Whether to animate at all — before how
@@ -0,0 +1,24 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ The report surface's one repeated object: a `--mark-tile` (50px) square at
6
+ `--mark-tile-r`, wearing a **5px solid ring** in `--mark-tile-ring` — not a hairline —
7
+ with a 1px `--mark-tile-edge` outside it, a wash background at 9–12% alpha and a glyph
8
+ in the matching solid.
9
+
10
+ **`tone` is variety, not meaning, and that is why it is a number.** The five hues do
11
+ not separate from each other — seven of their fifteen pairs are tight, and the
12
+ reference's own indigo and violet are 5.58 OKLab units apart at full colour — so no
13
+ reader can reliably tell two marks apart and nothing may require it. The icon and the
14
+ label carry which feature this is. `tone="neutral"` exists because the reference's
15
+ sixth hue was its red, 2.92 units from `--action-to`: a mark in the action's colour
16
+ reads as a control, so the coral stays with the action.
17
+
18
+ The glyph clears the 3:1 non-text floor on its own tile and nothing more. It is never
19
+ a word, and the tile is never a button.
20
+
21
+ ```tsx
22
+ <Mark tone={1} label="Distribution">{/* icon */}</Mark>
23
+ <Mark tone="neutral" label="Reporting">{/* icon */}</Mark>
24
+ ```
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface MarkProps {
4
+ /** The glyph. An icon, never a word — the hue clears only the non-text floor. */
5
+ children: ReactNode;
6
+ /** Decorative variety across a grid, 1–5, or the neutral. It carries no meaning:
7
+ * the set does not separate from itself, so nothing may depend on telling two
8
+ * marks apart. The icon and the label say which feature this is. */
9
+ tone?: 1 | 2 | 3 | 4 | 5 | 'neutral';
10
+ /** Accessible name for the tile, since the glyph inside is decorative. */
11
+ label?: string;
12
+ className?: string;
13
+ }
14
+
15
+ export function Mark({ children, tone = 1, label, className }: MarkProps) {
16
+ return (
17
+ <span
18
+ className={['np-mark', `np-mark--${tone}`, className].filter(Boolean).join(' ')}
19
+ role={label ? 'img' : undefined}
20
+ aria-label={label}
21
+ aria-hidden={label ? undefined : true}
22
+ >
23
+ {children}
24
+ </span>
25
+ );
26
+ }
@@ -23,3 +23,5 @@ export { Frame } from './Frame.js';
23
23
  export type { FrameProps } from './Frame.js';
24
24
  export { Eyebrow } from './Eyebrow.js';
25
25
  export type { EyebrowProps } from './Eyebrow.js';
26
+ export { Mark } from './Mark.js';
27
+ export type { MarkProps } from './Mark.js';
@@ -257,6 +257,50 @@
257
257
  --lift-sm: 1px;
258
258
  --reveal-y: 10px;
259
259
 
260
+ /* ---- Marks: decorative variety across a grid, NOT a taxonomy -------- */
261
+ /* MEASURED off the report showcase the home page embeds: a 50x50 icon tile at
262
+ 16px radius, a 5px solid ring in --mark-tile-ring, a 1px outer ring at 8%
263
+ ink, its background one tint at 9-12% alpha and its glyph the matching
264
+ solid. The reference ships six of these pairs and this pack ships five plus
265
+ a neutral, for two measured reasons.
266
+
267
+ FIRST, the sixth is the action. The reference's red mark #d94558 sits 2.92
268
+ OKLab units from --action-to and 1.12 under the worst dichromacy: the same
269
+ colour to everyone, not only to a colourblind reader. A mark in the action's
270
+ colour reads as a control, so the coral belongs to --action alone and the
271
+ sixth slot takes --mark-neutral.
272
+
273
+ SECOND, and this is why the tokens are numbered rather than named: the set
274
+ does NOT separate from itself. Seven of the fifteen pairs are tight, and the
275
+ reference's own indigo #5b4bd4 and violet #6c5ce7 are 5.58 apart at full
276
+ colour, which is one colour wearing two names. So the hue here is variety,
277
+ never meaning -- it does not encode WHICH feature this is, and nothing may
278
+ depend on telling two marks apart. The icon and the label carry that, and a
279
+ numbered token cannot be mistaken for a semantic one. */
280
+ --mark-tile-r: 16px; /* MEASURED */
281
+ --mark-tile: 50px; /* MEASURED */
282
+ --mark-tile-ring: #f7f8fc; /* MEASURED — a 5px solid ring, not a hairline */
283
+ --mark-tile-ring-w: 5px; /* MEASURED */
284
+ --mark-tile-edge: rgba(34, 36, 76, 0.08); /* MEASURED — 0 0 0 1px, outside the ring */
285
+
286
+ /* Each solid clears the 3:1 non-text floor on its own composited tile, which is
287
+ the only floor it owes: it is a glyph. None of them may carry a word — see
288
+ the pack's Bans, where three of the five miss AA on --field as text. */
289
+ --mark-1: #187c59; /* 4.53:1 on #ecf1f2, its own tile */
290
+ --mark-1-wash: rgba(31, 157, 107, 0.1); /* MEASURED */
291
+ --mark-2: #247cb0; /* 4.01:1 on #ecf0f7, its own tile */
292
+ --mark-2-wash: rgba(49, 144, 199, 0.1); /* MEASURED */
293
+ --mark-3: #5b4bd4; /* 5.36:1 on #eeeefa, its own tile */
294
+ --mark-3-wash: rgba(108, 92, 231, 0.1); /* MEASURED */
295
+ --mark-4: #b36b18; /* 3.69:1 on #f5f0ef, its own tile */
296
+ --mark-4-wash: rgba(227, 165, 60, 0.12); /* MEASURED — the one tint at 12% */
297
+ --mark-5: #6c5ce7; /* 4.26:1 on #efeffa, its own tile */
298
+ --mark-5-wash: rgba(108, 92, 231, 0.09); /* MEASURED — 9%, the lightest */
299
+ /* DERIVED, and it replaces the reference's red for the reason above. It is
300
+ --ink-mid, so a grid whose sixth block has no hue left still has a mark. */
301
+ --mark-neutral: #374151; /* 8.84:1 on #ecedf2, its own tile */
302
+ --mark-neutral-wash: rgba(55, 65, 81, 0.1);
303
+
260
304
  /* ---- Status: the action is red, so danger goes dark ----------------- */
261
305
  /* SELECTED, all four — a marketing site paints no error state — and each is a
262
306
  step of a hue the reference itself carries: its #187c59 green, its #b36b18
@@ -281,10 +325,13 @@
281
325
  font-weight: var(--weight-body);
282
326
  }
283
327
 
284
- /* No dark theme block, and that is a measurement rather than an omission: the
285
- page has no dark band anywhere. Its declared layer carries a #181b31 navy and
286
- a set of on-dark link colours, and nothing paints them the footer computes
287
- white. A pack that shipped a dark variant here would be inventing one. */
328
+ /* No dark theme block, and that is a measurement rather than an omission. No dark
329
+ band PAINTS on any served page: every full-bleed block was walked at 1440 and the
330
+ footer computes white. The authored stylesheets do declare eight dark fills, and
331
+ both kinds were checked -- .bg-header-blue (#231b66) is dead CSS no served page
332
+ uses, and the one that renders (#1f2937) sits inside a mock of another product's
333
+ interface in the hero image. The #181b31 navy and the on-dark link colours beside
334
+ it paint nothing. A pack that shipped a dark variant here would be inventing one. */
288
335
 
289
336
  /* The reference ships a real reduced-motion contract — 23 blocks across 7 of
290
337
  its 11 authored stylesheets — and, unusually, gets the important part right:
@@ -325,6 +372,7 @@
325
372
  .np-chip,
326
373
  .np-frame,
327
374
  .np-band,
375
+ .np-mark,
328
376
  .np-shell {
329
377
  box-sizing: border-box;
330
378
  }
@@ -760,3 +808,25 @@ a.np-plate:active {
760
808
  font-size: var(--t-sm);
761
809
  letter-spacing: var(--track-caps-control);
762
810
  }
811
+
812
+ /* ---- The mark tile: variety across a grid, never a control ------------- */
813
+
814
+ .np-mark {
815
+ display: grid;
816
+ place-items: center;
817
+ inline-size: var(--mark-tile);
818
+ block-size: var(--mark-tile);
819
+ border: var(--mark-tile-ring-w) solid var(--mark-tile-ring);
820
+ border-radius: var(--mark-tile-r);
821
+ box-shadow: 0 0 0 var(--line-w) var(--mark-tile-edge);
822
+ transition:
823
+ color var(--dur-base) var(--ease-out),
824
+ background-color var(--dur-base) var(--ease-out);
825
+ }
826
+
827
+ .np-mark--1 { background: var(--mark-1-wash); color: var(--mark-1); }
828
+ .np-mark--2 { background: var(--mark-2-wash); color: var(--mark-2); }
829
+ .np-mark--3 { background: var(--mark-3-wash); color: var(--mark-3); }
830
+ .np-mark--4 { background: var(--mark-4-wash); color: var(--mark-4); }
831
+ .np-mark--5 { background: var(--mark-5-wash); color: var(--mark-5); }
832
+ .np-mark--neutral { background: var(--mark-neutral-wash); color: var(--mark-neutral); }
@@ -0,0 +1,14 @@
1
+ {
2
+ "pkg": "@sheleg-design/rimlight",
3
+ "globalName": "ShelegRimlight",
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,62 @@
1
+ # Rimlight — the contract this design system ships under
2
+
3
+ **Register.** Choose Rimlight for **a studio's own front door and the pages that sell
4
+ what it makes**: design and engineering agencies, product studios, service and
5
+ case-study pages, B2B surfaces whose argument is craft rather than a feature list. A
6
+ white field, a cool grey act separator, one near-black act, and coloured light where
7
+ other systems put a shadow. Build every screen against `var(--…)` and never a literal.
8
+
9
+ **Elevation is a light, and it belongs to one control.** `--glow-rig` is sixteen
10
+ layers — six lit and ten held at alpha 0, thrown from below and to the left. Exactly
11
+ one control per viewport wears it. Two lit controls and the light stops meaning *this
12
+ one*.
13
+
14
+ **The rig is static.** It does not change on hover, press or focus: the fill moves one
15
+ step and the light stays. Pulsing, rotating or hover-growing it is the first ban here,
16
+ and it is the single change that turns this pack into a novelty.
17
+
18
+ **The only other shadows are the tile's.** `--shadow-tile` on the 80px icon tile and
19
+ `--shadow-tile-inner` on its 68px image. Nothing else carries one — 483 of the
20
+ reference's 487 visible elements compute `box-shadow: none`. A card has no shadow and
21
+ no border; it is separated by the field it sits on.
22
+
23
+ **The monospace is the chrome.** Every nav item, button and label is Source Code Pro;
24
+ every sentence is Archivo. That split is how a reader tells a control from a statement
25
+ without reading either, and it is not decorative.
26
+
27
+ **Do not track the monospace.** `letter-spacing` computes to `normal` on every label
28
+ in the reference — the openness is the face's own advance width. Adding tracking is
29
+ the trap this pack sets for anyone matching it from a screenshot.
30
+
31
+ **No bold anywhere.** 500 is the heaviest weight and it belongs to the mono label; the
32
+ display runs at 400 and the lede at 300. The display is loud because it is 86px.
33
+
34
+ **Tracking is negative at every size and tightens as the display shrinks** —
35
+ `--track-display` (−0.02em) at 86px, `--track-display-narrow` (−0.04em) at the 40px
36
+ narrow headline.
37
+
38
+ **The blue is two tokens because it does two jobs.** `--accent` is 3.03:1 on `--bg`:
39
+ legal on a word only at WCAG large text, which at weight 400 means ≥ 24px. Anything
40
+ smaller takes `--accent-ink`. A 16px link in `--accent` is a word below the floor and
41
+ is the commonest way to break this pack while believing you matched it.
42
+
43
+ **The colours live in the dark act.** Every one of the reference's five secondary hues
44
+ is 6.5–10.5:1 on `#1b1b1b` and 1.65–2.65:1 on white. They were designed for the dark;
45
+ on a light field they are not text.
46
+
47
+ **`[data-surface="dark"]` is a section, never the document.** There is no toggle and
48
+ no second palette for the page — one band is dark and the rest stays light. Putting it
49
+ on `:root` inverts a page that was never designed to invert, and the light field's four
50
+ statuses measure 1.1–1.5:1 if they arrive unremapped.
51
+
52
+ **Status is never by colour alone** — every state takes a glyph or a label beside its
53
+ colour. The four roles are derived rather than adopted: the reference's own secondary
54
+ set is a palette, not a scheme, with eight of its twenty-one pairs too close to
55
+ separate under dichromacy.
56
+
57
+ **Every control clears `--tap-min` (44px).** A correction: 26 of the reference's 37
58
+ visible interactive elements at 390px are shorter than that.
59
+
60
+ **Nothing travels in space.** No parallax, no scrub, no `animation-timeline`, no press
61
+ translation. `MOTION_INTENSITY` above 2 has nothing legal to buy, and reduced motion
62
+ collapses every duration to zero.
@@ -0,0 +1,39 @@
1
+ # @sheleg-design/rimlight
2
+
3
+ The React reference kit for the SHELEG **Rimlight** style pack — a white field, one
4
+ near-black act, a monospace carrying every piece of chrome, and an elevation made of
5
+ coloured light rather than shadow.
6
+
7
+ It is generated from the pack, not authored beside it: `src/styles.css` opens with
8
+ `styles/tokens/rimlight.css` byte for byte, and the rules the design agent must obey
9
+ 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 types
18
+ in every SHELEG kit, so switching packs swaps identity rather than API.
19
+
20
+ ## This pack's own
21
+
22
+ `LitButton` (the sixteen-layer rig, one per viewport), `Tile` (the 80px icon square
23
+ that opens a section, and one of only two objects allowed an ordinary shadow), `Label`
24
+ (the monospace chrome — untracked, always) and `Act` (a full-bleed section, including
25
+ the dark one, which is a surface variant and never a document theme).
26
+
27
+ ## The three rules that carry the kit
28
+
29
+ 1. **One lit control per viewport.** The rig is the only chromatic event on a page
30
+ otherwise made of ink, one blue and two greys — so the most saturated thing on the
31
+ screen is also the thing you are meant to click. Two of them and that stops working.
32
+ 2. **The light does not move.** It is deliberately absent from `.rl-lit`'s transition
33
+ list. A static light reads as craft; an animated one reads as a toy.
34
+ 3. **The monospace is chrome and the grotesque is prose.** Never mix the two roles, and
35
+ never track the monospace.
36
+
37
+ **Motion does not cross into a design tool.** A kit is the static half of a pack: this
38
+ one is unusually literal about that, because the pack's whole motion budget is a 0.3s
39
+ colour transition.
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@sheleg-design/rimlight",
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: Surfaces
3
+ ---
4
+
5
+ A full-bleed section on one of the pack's three fields. `field="dark"` sets
6
+ `data-surface="dark"`, which remaps the palette **for that band only**.
7
+
8
+ **It is not a dark mode.** The reference has no toggle and no second palette for the
9
+ document: one section carries the dark treatment and the rest of the page stays light.
10
+ Putting `data-surface="dark"` on `:root` inverts a page that was never designed to
11
+ invert.
12
+
13
+ The dark act is where the reference's colours finally work — every one of its
14
+ secondary hues is 6.5–10.5:1 there against 1.65–2.65:1 on white.
15
+
16
+ ```tsx
17
+ <Act>…</Act>
18
+ <Act field="dark">…</Act>
19
+ ```
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ActProps {
4
+ children: ReactNode;
5
+ /** `page` is white, `field` the cool grey separator, `dark` the near-black act. */
6
+ field?: 'page' | 'field' | 'dark';
7
+ className?: string;
8
+ }
9
+
10
+ /** A full-bleed section. `dark` sets data-surface, which remaps the palette for the
11
+ * band only — it is a surface variant, never a document theme. */
12
+ export function Act({ children, field = 'page', className }: ActProps) {
13
+ return (
14
+ <section
15
+ className={['rl-act', `rl-act--${field}`, className].filter(Boolean).join(' ')}
16
+ data-surface={field === 'dark' ? 'dark' : undefined}
17
+ >
18
+ {children}
19
+ </section>
20
+ );
21
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ The ordinary control: transparent with a `1px solid --ink-faint` border at `--r-pill`
6
+ (160px), its label the monospace at 16–18px/500 uppercase, height `--control-h` (48px)
7
+ or `--control-h-lg` (60px).
8
+
9
+ `primary` fills with `--ink` and takes `--on-ink`. **It does not wear the rig** — that
10
+ belongs to `LitButton`, one per viewport. Hover moves the border to `--ink` and never
11
+ adds a fill; nothing on this page travels in space.
12
+
13
+ Every size honours `--tap-min` (44px) as a floor.
14
+
15
+ ```tsx
16
+ <Button variant="secondary">View project</Button>
17
+ ```
@@ -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={['rl-btn', `rl-btn--${variant}`, `rl-btn--${size}`, className]
25
+ .filter(Boolean)
26
+ .join(' ')}
27
+ disabled={disabled}
28
+ onClick={onClick}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }