sheleg-design-skill 1.48.1 → 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 (44) hide show
  1. package/CHANGELOG.md +76 -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/rimlight/.design-sync/config.json +14 -0
  6. package/kits/rimlight/.design-sync/conventions.md +62 -0
  7. package/kits/rimlight/README.md +39 -0
  8. package/kits/rimlight/package.json +29 -0
  9. package/kits/rimlight/src/Act.md +19 -0
  10. package/kits/rimlight/src/Act.tsx +21 -0
  11. package/kits/rimlight/src/Button.md +17 -0
  12. package/kits/rimlight/src/Button.tsx +33 -0
  13. package/kits/rimlight/src/Card.md +14 -0
  14. package/kits/rimlight/src/Card.tsx +24 -0
  15. package/kits/rimlight/src/Chip.md +12 -0
  16. package/kits/rimlight/src/Chip.tsx +25 -0
  17. package/kits/rimlight/src/Heading.md +14 -0
  18. package/kits/rimlight/src/Heading.tsx +19 -0
  19. package/kits/rimlight/src/Label.md +16 -0
  20. package/kits/rimlight/src/Label.tsx +18 -0
  21. package/kits/rimlight/src/LitButton.md +22 -0
  22. package/kits/rimlight/src/LitButton.tsx +26 -0
  23. package/kits/rimlight/src/Rule.md +13 -0
  24. package/kits/rimlight/src/Rule.tsx +18 -0
  25. package/kits/rimlight/src/Stat.md +13 -0
  26. package/kits/rimlight/src/Stat.tsx +17 -0
  27. package/kits/rimlight/src/Tile.md +16 -0
  28. package/kits/rimlight/src/Tile.tsx +22 -0
  29. package/kits/rimlight/src/index.ts +23 -0
  30. package/kits/rimlight/src/styles.css +546 -0
  31. package/kits/rimlight/tsconfig.json +15 -0
  32. package/package.json +2 -2
  33. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  34. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  35. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  36. package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +1 -1
  37. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  38. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +4 -3
  39. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
  40. package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +1 -1
  41. package/plugins/sheleg-design/skills/sheleg-design/styles/rimlight.md +342 -0
  42. package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +1 -0
  43. package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
  44. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/rimlight.css +306 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,82 @@ 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
+
9
85
  ## [1.48.1] - 2026-08-24
10
86
 
11
87
  ### The assembly rule is written down, and the render step earned its place within the hour
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,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
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ `--surface` on `--field`, or `--field` on `--bg`, at `--r-sm` (12px), with **no border
6
+ and no shadow**. Inside a dark `Act` it becomes `#242424` on `#1b1b1b` — separated by
7
+ tone in both cases, never by a line.
8
+
9
+ 483 of the reference's 487 visible elements carry no shadow at all. A card is not one
10
+ of the four that do.
11
+
12
+ ```tsx
13
+ <Card title="Discovery" meta="01">…</Card>
14
+ ```
@@ -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={['rl-card', className].filter(Boolean).join(' ')}>
15
+ {head && (
16
+ <div className="rl-card__head">
17
+ {title !== undefined && <h3 className="rl-card__title">{title}</h3>}
18
+ {meta !== undefined && <span className="rl-card__meta">{meta}</span>}
19
+ </div>
20
+ )}
21
+ <div className="rl-card__body">{children}</div>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,12 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ A small tag at `--r-sm`, `--field` fill, label in the monospace at `--t-xs`. `accent`
6
+ tints with `--accent-wash` and takes `--accent-ink` for the word — **never `--accent`
7
+ itself**, which is 3.03:1 and legal only above 24px.
8
+
9
+ ```tsx
10
+ <Chip>SaaS</Chip>
11
+ <Chip tone="accent" selected>DevTools</Chip>
12
+ ```
@@ -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
+ 'rl-chip',
15
+ `rl-chip--${tone}`,
16
+ selected ? 'rl-chip--selected' : undefined,
17
+ className,
18
+ ]
19
+ .filter(Boolean)
20
+ .join(' ')}
21
+ >
22
+ {children}
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ Three levels in Archivo at `--weight-body` (400) — never bold — with
6
+ `--track-display` (−0.02em): 1 is the hero at `--t-hero` (86px, and 40px at 390 where
7
+ the tracking tightens to −0.04em), 2 is the section at `--t-section` (60px), 3 a card
8
+ title at `--t-card` (32px).
9
+
10
+ The display is loud because it is large, not because it is heavy.
11
+
12
+ ```tsx
13
+ <Heading level={1}>Web design for SaaS</Heading>
14
+ ```
@@ -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={['rl-heading', `rl-heading--${level}`, className].filter(Boolean).join(' ')}
15
+ >
16
+ {children}
17
+ </Tag>
18
+ );
19
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ The monospace, uppercase, at `--t-label` (16px) and `--weight-label` (500). It carries
6
+ every nav item, every button and every section eyebrow — the grotesque carries only
7
+ sentences, and that split is the pack's most legible rule.
8
+
9
+ **Do not track it.** `letter-spacing` computes to `normal` on every label in the
10
+ reference; the openness is Source Code Pro's own advance width. Adding tracking is the
11
+ trap this pack sets for anyone matching it from a screenshot.
12
+
13
+ ```tsx
14
+ <Label>Services</Label>
15
+ <Label role="eyebrow">How we measure success</Label>
16
+ ```
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface LabelProps {
4
+ children: ReactNode;
5
+ /** `chrome` is nav and button furniture; `eyebrow` opens a section. */
6
+ role?: 'chrome' | 'eyebrow';
7
+ className?: string;
8
+ }
9
+
10
+ /** Every label in this pack is the monospace. It is how a reader tells a control
11
+ * from a statement without reading either. */
12
+ export function Label({ children, role = 'chrome', className }: LabelProps) {
13
+ return (
14
+ <span className={['rl-label', `rl-label--${role}`, className].filter(Boolean).join(' ')}>
15
+ {children}
16
+ </span>
17
+ );
18
+ }
@@ -0,0 +1,22 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ The pack's signature and its whole idea: a near-black pill at `--r-glow` (100px)
6
+ wearing `--glow-rig` — sixteen shadow layers, six lit and ten held at alpha 0, thrown
7
+ from below and to the left. `surface="light"` swaps to `--glow-rig-light`, where the
8
+ rim goes black and the inset goes white.
9
+
10
+ **One per viewport.** Two lit controls and the light stops meaning *this one*.
11
+
12
+ **The rig does not change on hover.** It is a static light; the fill moves one step
13
+ and the rig stays. Pulsing, rotating or growing it is the pack's first ban, and it is
14
+ the fastest way to turn this pack into a novelty. Disabled drops the rig entirely.
15
+
16
+ The label is the monospace at 18px/500 uppercase with `letter-spacing: normal` — the
17
+ openness is the face's own width, not tracking.
18
+
19
+ ```tsx
20
+ <LitButton>Book a call</LitButton>
21
+ <LitButton surface="light">Request a quote</LitButton>
22
+ ```
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface LitButtonProps {
4
+ children: ReactNode;
5
+ /** `dark` is the near-black control on a light field; `light` is its inverse.
6
+ * Both wear the same rig — only the rim and the inset swap. */
7
+ surface?: 'dark' | 'light';
8
+ disabled?: boolean;
9
+ onClick?: () => void;
10
+ className?: string;
11
+ }
12
+
13
+ /** The pack's signature, and there is ONE per viewport. The rig is a static light:
14
+ * it does not change on hover, and animating it is the pack's first ban. */
15
+ export function LitButton({ children, surface = 'dark', disabled = false, onClick, className }: LitButtonProps) {
16
+ return (
17
+ <button
18
+ type="button"
19
+ className={['rl-lit', `rl-lit--${surface}`, className].filter(Boolean).join(' ')}
20
+ disabled={disabled}
21
+ onClick={onClick}
22
+ >
23
+ {children}
24
+ </button>
25
+ );
26
+ }
@@ -0,0 +1,13 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ `hairline` is a 1px `--ink-faint` line — the only line the pack draws. `strong` takes
6
+ `--ink-quiet`.
7
+
8
+ There is no other border width here, and a rule is never a substitute for the field
9
+ change that normally separates two acts.
10
+
11
+ ```tsx
12
+ <Rule />
13
+ ```
@@ -0,0 +1,18 @@
1
+ export interface RuleProps {
2
+ tone?: 'hairline' | 'strong';
3
+ className?: string;
4
+ }
5
+
6
+ export function Rule({ tone = 'hairline', className }: RuleProps) {
7
+ return (
8
+ <hr
9
+ className={[
10
+ 'rl-rule',
11
+ tone === 'strong' ? 'rl-rule--strong' : undefined,
12
+ className,
13
+ ]
14
+ .filter(Boolean)
15
+ .join(' ')}
16
+ />
17
+ );
18
+ }