sheleg-design-skill 1.23.0 → 1.24.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.
- package/CHANGELOG.md +92 -0
- package/README.md +5 -4
- package/bin/cli.js +5 -3
- package/cursor/rules/sheleg-design.mdc +5 -1
- package/kits/roster/.design-sync/config.json +14 -0
- package/kits/roster/.design-sync/conventions.md +62 -0
- package/kits/roster/README.md +45 -0
- package/kits/roster/package.json +29 -0
- package/kits/roster/src/Button.md +19 -0
- package/kits/roster/src/Button.tsx +33 -0
- package/kits/roster/src/Card.md +12 -0
- package/kits/roster/src/Card.tsx +24 -0
- package/kits/roster/src/Chip.md +11 -0
- package/kits/roster/src/Chip.tsx +25 -0
- package/kits/roster/src/Eyebrow.md +16 -0
- package/kits/roster/src/Eyebrow.tsx +16 -0
- package/kits/roster/src/FaqList.md +12 -0
- package/kits/roster/src/FaqList.tsx +30 -0
- package/kits/roster/src/Heading.md +20 -0
- package/kits/roster/src/Heading.tsx +19 -0
- package/kits/roster/src/IndustryColumn.md +25 -0
- package/kits/roster/src/IndustryColumn.tsx +32 -0
- package/kits/roster/src/LogoTile.md +16 -0
- package/kits/roster/src/LogoTile.tsx +29 -0
- package/kits/roster/src/Rule.md +11 -0
- package/kits/roster/src/Rule.tsx +18 -0
- package/kits/roster/src/Stat.md +12 -0
- package/kits/roster/src/Stat.tsx +17 -0
- package/kits/roster/src/StepCard.md +19 -0
- package/kits/roster/src/StepCard.tsx +38 -0
- package/kits/roster/src/index.ts +25 -0
- package/kits/roster/src/styles.css +604 -0
- package/kits/roster/tsconfig.json +15 -0
- package/package.json +2 -2
- package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
- package/plugins/sheleg-design/commands/sheleg-design.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +8 -5
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
- package/plugins/sheleg-design/skills/sheleg-design/styles/manpage.md +7 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/pigeonhole.md +29 -9
- package/plugins/sheleg-design/skills/sheleg-design/styles/roster.md +425 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +19 -3
- package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +12 -3
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/roster.css +216 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,98 @@ All notable changes to this project are documented in this file. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
|
5
5
|
follow [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.24.0] - 2026-08-13
|
|
8
|
+
|
|
9
|
+
A seventeenth style pack, whose whole argument is other people's names — and a reference
|
|
10
|
+
whose `h1` is one pixel wide.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`roster`** — the seventeenth pack, extracted from `babylovegrowth.ai` off the
|
|
15
|
+
server-rendered HTML of `/en` (1,415,414 bytes), its two shipped stylesheets (466,577
|
|
16
|
+
bytes, 410 custom properties) and then off **computed styles on the live page** at
|
|
17
|
+
1440×900, 768×1168 and 390×790 — **5,936 rendered elements** at the widest. A white
|
|
18
|
+
field in a faint grid of squares, hairlines instead of shadows, the pill as the most
|
|
19
|
+
frequent shape, and one orange that may never carry a word. For products whose argument
|
|
20
|
+
is *who already carries them*: AI-search and GEO visibility, SEO and content platforms,
|
|
21
|
+
agencies, marketplaces. Widened contract, a light-only token layer, a full reference kit,
|
|
22
|
+
and reciprocal forks written into `scoreboard`, `showroom`, `pigeonhole` and `manpage`.
|
|
23
|
+
- **The fork against `scoreboard` is the pack's reason to exist.** Both serve growth, ads
|
|
24
|
+
and SEO products, so the category cannot decide it — the **kind of proof** can. One is
|
|
25
|
+
built around a figure that ticks up; the other around a name that appears. Ask what the
|
|
26
|
+
page loses if you delete its proof, and the giveaway is that `roster` sets its largest
|
|
27
|
+
figure (*4,000+*) in the same 16px eyebrow as everything else.
|
|
28
|
+
- **`RESOLVED`, a fourth provenance family.** The reference computes its neutrals in
|
|
29
|
+
`lab()`, which the palette gate refuses on purpose, so each was resolved to the sRGB the
|
|
30
|
+
browser actually paints — **34 distinct values**, each painted into a 1×1 canvas and read
|
|
31
|
+
back as bytes. They turn out to be Tailwind v4's defaults: 7,234 borders at
|
|
32
|
+
`lab(91.6229 …)` = `#e5e7eb`. The bespoke layer beside them is four near-blacks and two
|
|
33
|
+
oranges wide, and the pack ships one of each with the criterion written at the
|
|
34
|
+
declaration.
|
|
35
|
+
- **`IndustryColumn`, the signature element** — a pill-labelled column of other companies'
|
|
36
|
+
marks, hairline-divided, six across at 1440 — plus `LogoTile`, `Eyebrow` and `StepCard`.
|
|
37
|
+
Both container breakpoints are **derived from the component's own geometry** (220px for
|
|
38
|
+
the column's two-up mark grid, 640px for the step card's split) rather than carried over
|
|
39
|
+
from the viewport, which is what B-032 exists to prevent.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **The pack refuses the reference's heading structure.** Its `h1` is `.sr-only` — 1×1px,
|
|
44
|
+
white, `clip-path: inset(50%)` — and the visible 68px line is a `<span>`, while all
|
|
45
|
+
sixteen `h2`s are small orange eyebrows. So the document outline says *eyebrow* where the
|
|
46
|
+
page says *section head*, and the largest text on the page is not a heading at all. This
|
|
47
|
+
pack teaches the opposite and `manpage` now carries the fork from the other end.
|
|
48
|
+
- **The accent may not carry a word.** `#fa5c12` is 3.18:1 on white; white on `#f25533` at
|
|
49
|
+
the nav pill's 16px/600 is **3.43:1**. The pack ships `--accent-ink` at 4.52:1 for
|
|
50
|
+
anything read, keeps the accent for fills and large text, and makes the primary button
|
|
51
|
+
black — 19.66:1, which is what the reference itself does for its hero.
|
|
52
|
+
- **The dominant secondary ink fails on its own band**: `#6a7282` is 4.84:1 on white and
|
|
53
|
+
**4.35:1** on the `#f0f3f8` band it is painted on. `--ink-soft` is the darkened form and
|
|
54
|
+
clears both.
|
|
55
|
+
- **No `--warn` is shipped.** The reference paints no amber anywhere, so one here would be
|
|
56
|
+
invented rather than selected. Stated in the Palette instead of filled in.
|
|
57
|
+
|
|
58
|
+
### Notes
|
|
59
|
+
|
|
60
|
+
- **Reduced motion covers six of the reference's animations out of roughly twenty.** Its
|
|
61
|
+
branch names classes one by one, leaving `arrow-nudge`, `skeleton-blink`, both spinners,
|
|
62
|
+
`settings-ripple`, `meta-preview-float` and the accordions running — the opposite failure
|
|
63
|
+
to `pigeonhole`'s reference, which collapsed everything with one `*` rule and strobed its
|
|
64
|
+
marquee by doing so. This pack collapses unconditionally and pauses its two floats in the
|
|
65
|
+
component layer.
|
|
66
|
+
- **A claim the measurement refused.** The hero sets a third party's wordmark inline after
|
|
67
|
+
the word *from*; it was sampled seven times across 5.4 seconds with no change, so the
|
|
68
|
+
pack specifies **one mark chosen per page** and makes no claim that it rotates.
|
|
69
|
+
- Gates: validate 1938 → 2066, palette 906 → 956, sloplint 436 → 450, floors raised with
|
|
70
|
+
the reason.
|
|
71
|
+
|
|
72
|
+
## [1.23.1] - 2026-08-13
|
|
73
|
+
|
|
74
|
+
Two of the seven answers 1.23.0 shipped told an implementer to do something the same
|
|
75
|
+
release had just described as impossible.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- **`pigeonhole` contradicted the SELF category it shipped beside.** Its new Responsive
|
|
80
|
+
answer said the labelled row and the FAQ "take `container-type: inline-size` on their
|
|
81
|
+
wrapper" — but the row's axis and the list's columns are properties of the row and the
|
|
82
|
+
`<dl>` **themselves**, so neither can query its own width. That is exactly the SELF case
|
|
83
|
+
the skeleton now defines. The corrected answer is the useful one: both live inside a
|
|
84
|
+
list the consumer already owns, so **the consumer's list is the container** — a
|
|
85
|
+
component library may ask for `container-type` on it and may not wrap someone's markup
|
|
86
|
+
to get it. Descendants inside them (the preview's truncation, the date's visibility)
|
|
87
|
+
are ordinary CONTAINER cases and need nothing from the consumer.
|
|
88
|
+
- **`showroom` named a wrapper its kit does not ship.** "container-type on the table
|
|
89
|
+
wrapper" became the **specimen frame**, which is the component that actually holds the
|
|
90
|
+
table (`Specimen`), so the data row and the column header query something that exists.
|
|
91
|
+
|
|
92
|
+
### Notes
|
|
93
|
+
|
|
94
|
+
Found by checking every component named in the seven new answers against the components
|
|
95
|
+
its kit actually ships — a check worth running whenever pack prose starts naming parts.
|
|
96
|
+
Five of the seven were already right; `maquette`'s "agent prompt" is named by the pack and
|
|
97
|
+
absent from the kit, which is a pre-existing pack/kit gap rather than this release's.
|
|
98
|
+
|
|
7
99
|
## [1.23.0] - 2026-08-13
|
|
8
100
|
|
|
9
101
|
A component library that sized itself by the screen, and a contract bullet nobody had
|
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 **
|
|
14
|
+
decides whether to animate before it decides how, and **seventeen 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
|
|
|
@@ -61,6 +61,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
61
61
|
| `datasheet` | an off-white spec sheet ruled with dashed page guides, a live instrument built from hairline cells at radius zero, one vivid orange, Inter over JetBrains Mono, concentric radii from 16 to 2, and a dark alarm state the instrument enters when it detects the reader is hiding | B2B SaaS whose product is a verdict about the visitor, the request or the device — fraud and bot detection, device intelligence, identity and verification, API products sold on their payload |
|
|
62
62
|
| `manpage` | cream paper under the reader's own system monospace — zero webfont bytes for the display face — a 48px display that never grows louder, a 576px argument column, coral label chips that are real `<h2>`s, `└` tree glyphs in their own grid column, and one dark code frame as the focal point | developer products whose buyer reads code — APIs, SDKs, CLIs, MCP servers, developer infrastructure, where the honest hero is the call itself |
|
|
63
63
|
| `pigeonhole` | a white field ruled by hairlines, a display face that never passes weight 400 with exactly one italic word, and **nine categories in which a hue is the category**, drawn from an eleven-ramp pastel system — each rendered as a two-layer chip, 8px outside and 7px inside, whose label word is mandatory because the hue cannot carry the meaning alone | products whose job is to sort the reader's incoming mess into named categories — email triage, ticket routing, notification digests, file organisers, CRM inboxes |
|
|
64
|
+
| `roster` | a white field in a faint grid of squares, hairlines instead of shadows, a display set in the **body** face while the section heads take another, and one orange that may never carry a word | products whose argument is **who already carries them** — AI-search and GEO visibility, SEO and content platforms, agencies, marketplaces, integration-led tooling |
|
|
64
65
|
|
|
65
66
|
Each pack locks palette, type, texture, motion tokens, signature motifs and
|
|
66
67
|
bans — and ships a `tokens/<pack>.css` to copy verbatim, so the agent never
|
|
@@ -135,7 +136,7 @@ skills.
|
|
|
135
136
|
| `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 |
|
|
136
137
|
| `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 |
|
|
137
138
|
| `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 |
|
|
138
|
-
| `styles/*.md` | The
|
|
139
|
+
| `styles/*.md` | The seventeen style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
139
140
|
| `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) |
|
|
140
141
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
141
142
|
|
|
@@ -206,7 +207,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
206
207
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
207
208
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
208
209
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
209
|
-
identical in all
|
|
210
|
+
identical in all seventeen kits, so switching packs swaps identity rather than API,
|
|
210
211
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
211
212
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
212
213
|
stops an agent inventing motion to fill the silence.
|
|
@@ -252,7 +253,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
252
253
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
253
254
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
254
255
|
it ships the right files is CI, which installs the bundle through **both**
|
|
255
|
-
installers and `diff -r`s the result against the source, then builds all
|
|
256
|
+
installers and `diff -r`s the result against the source, then builds all seventeen
|
|
256
257
|
kits.
|
|
257
258
|
|
|
258
259
|
`test/scenarios.md` (T1–T19) is the behavioral harness: fresh subagents given a
|
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/
|
|
237
|
+
styles/ seventeen style packs — instrument-console (dark console),
|
|
238
238
|
editorial-luxury (warm editorial), workbench (light/dark
|
|
239
239
|
product UI), briefing-room (dark 16:9 presentation deck),
|
|
240
240
|
atrium (warm cream consumer health), orchard (friendly
|
|
@@ -249,7 +249,9 @@ ${c("bold", "What it installs")}
|
|
|
249
249
|
manpage (a developer landing page set in the reader's
|
|
250
250
|
own system monospace, cream paper, coral label chips),
|
|
251
251
|
pigeonhole (a white sorting wall whose nine pastel
|
|
252
|
-
categories are a filing scheme, one hue each)
|
|
252
|
+
categories are a filing scheme, one hue each),
|
|
253
|
+
roster (a white wall of other people's marks: the proof
|
|
254
|
+
is a name rather than a number) —
|
|
253
255
|
plus a ready-made token CSS per pack and
|
|
254
256
|
STYLE_PACK_TEMPLATE.md for authoring more
|
|
255
257
|
`);
|
|
@@ -331,7 +333,7 @@ function main() {
|
|
|
331
333
|
` ${c("dim", "SKILL.md")} the agent skill\n` +
|
|
332
334
|
` ${c("dim", "SHELEG_DESIGN.md")} the full reference\n` +
|
|
333
335
|
` ${c("dim", "MOTION_DOCTRINE.md")} whether to animate at all — read before any animation\n` +
|
|
334
|
-
` ${c("dim", "styles/")} style packs + token CSS (instrument-console / editorial-luxury / workbench / briefing-room / atrium / orchard / field-notes / cyclorama / showroom / blueprint / prism / maquette / scoreboard / datasheet / manpage / pigeonhole)\n\n` +
|
|
336
|
+
` ${c("dim", "styles/")} style packs + token CSS (instrument-console / editorial-luxury / workbench / briefing-room / atrium / orchard / field-notes / cyclorama / showroom / blueprint / prism / maquette / scoreboard / datasheet / manpage / pigeonhole / roster)\n\n` +
|
|
335
337
|
`Your Cursor / Claude agent can now discover the skill and build\n` +
|
|
336
338
|
`cinematic, scroll-driven pages — or style product UI (dashboards, admin,\n` +
|
|
337
339
|
`internal tools) from a standalone pack — on its principles.\n\n` +
|
|
@@ -39,7 +39,11 @@ monospace on cream paper, coral label chips that are real headings and a
|
|
|
39
39
|
dark code frame as the argument, for APIs, SDKs and CLIs; pigeonhole — a
|
|
40
40
|
white sorting wall whose nine pastel categories are a taxonomy rather than
|
|
41
41
|
a mood, each a two-layer chip whose label word is mandatory, for products
|
|
42
|
-
that file the reader's incoming mess into named categories
|
|
42
|
+
that file the reader's incoming mess into named categories; roster — a white
|
|
43
|
+
field in a faint grid of squares whose whole argument is other people's
|
|
44
|
+
marks, with client logotypes in pill-labelled industry columns and an
|
|
45
|
+
engine's wordmark inside the headline, for products sold on who already
|
|
46
|
+
carries them);
|
|
43
47
|
otherwise follow the contract below (self-contained on purpose).
|
|
44
48
|
|
|
45
49
|
## Whether to animate at all — before how
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/roster",
|
|
3
|
+
"globalName": "ShelegRoster",
|
|
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
|
+
# Roster — the design contract
|
|
2
|
+
|
|
3
|
+
The reference is <https://www.babylovegrowth.ai/en>, measured 2026-08-13 off computed
|
|
4
|
+
styles at 1440×900 (5,936 rendered elements), 768×1168 and 390×790. The full pack is
|
|
5
|
+
`styles/roster.md`; this file is what a design agent must not get wrong.
|
|
6
|
+
|
|
7
|
+
## The one thing this pack is
|
|
8
|
+
|
|
9
|
+
**The proof is a name, not a number.** A white field in a faint grid of squares, hairlines
|
|
10
|
+
instead of shadows, and an argument made entirely of other people's marks: an engine's
|
|
11
|
+
wordmark inside the headline, client logotypes in six pill-labelled industry columns, a
|
|
12
|
+
review score somebody else computed.
|
|
13
|
+
|
|
14
|
+
## Non-negotiable
|
|
15
|
+
|
|
16
|
+
- **Never white on the accent below large text.** `#fa5c12` is 3.18:1 on white and the
|
|
17
|
+
reference's own nav pill puts white on `#f25533` at 16px/600 — **3.43:1**, its one clear
|
|
18
|
+
failure. The accent is a fill and a large-text colour; `--accent-ink` (4.52:1) carries a
|
|
19
|
+
word.
|
|
20
|
+
- **The primary action is black.** `--cta` at 19.66:1, which is what the reference itself
|
|
21
|
+
uses for the hero.
|
|
22
|
+
- **No elevation system.** The reference's shadow slot holds an all-transparent ring
|
|
23
|
+
composite on 101 elements. Separation is hairline (`--rule`, on 7,234 borders) and pill.
|
|
24
|
+
- **The display is set in the body face** at 68px, and the section heads in the *other*
|
|
25
|
+
family at 52px. That inversion is the pack.
|
|
26
|
+
- **Body weight is 300.** A very long page (13,627px at 1440) stays light because its
|
|
27
|
+
paragraphs are.
|
|
28
|
+
- **The display steps, it does not slide** — 36 / 60 / 68 — because neither stylesheet
|
|
29
|
+
contains a `clamp()`.
|
|
30
|
+
- **Status never by colour alone.** `--accent` and `--danger` are 10.2 apart at full
|
|
31
|
+
colour against a hard floor of 10; a danger state always carries its word or icon.
|
|
32
|
+
- **`:focus-visible` is `--accent-ink`**, never `--accent`.
|
|
33
|
+
|
|
34
|
+
## Banned
|
|
35
|
+
|
|
36
|
+
- White on the accent at body size. A shadow used as elevation. A scroll clock — the
|
|
37
|
+
reference has none: zero `animation-timeline`, `scroll-behavior: auto`.
|
|
38
|
+
- **An invented `--warn`.** The reference paints no amber anywhere.
|
|
39
|
+
- **A hidden `h1`.** The reference's is `.sr-only` at 1×1px and all sixteen of its `h2`s
|
|
40
|
+
are eyebrows; this kit keeps the outline and the page in agreement.
|
|
41
|
+
- **A tidied roster.** No equalising logo sizes, no single-colour tinting, no dividerless
|
|
42
|
+
wall. Greyscale at rest is the only normalisation.
|
|
43
|
+
- **Pixel numerals.** The biggest figure on the reference is set in a 16px eyebrow; a
|
|
44
|
+
number as the subject is `scoreboard`, not this.
|
|
45
|
+
- A dark theme. None was measured.
|
|
46
|
+
|
|
47
|
+
## Container queries, not viewport ones
|
|
48
|
+
|
|
49
|
+
`IndustryColumn` and `StepCard` each set `container-type: inline-size`, and their
|
|
50
|
+
breakpoints are derived from their own geometry rather than carried over: 220px for the
|
|
51
|
+
column's two-up mark grid, 640px for the step card's split. Only the display's three
|
|
52
|
+
steps stay viewport rules, and they are marked `PAGE` at the block.
|
|
53
|
+
|
|
54
|
+
## Motion
|
|
55
|
+
|
|
56
|
+
Entrance and hover, plus two floats at 5.5s and 6.5s offset by a second so the pair never
|
|
57
|
+
syncs. Every transition runs on `--ease`. Under `prefers-reduced-motion` the durations
|
|
58
|
+
collapse in the token layer and the floats are **paused** in the component layer — a
|
|
59
|
+
duration cannot stop an infinite animation, it strobes it.
|
|
60
|
+
|
|
61
|
+
The reference's own reduced-motion branch names six classes and leaves roughly fourteen
|
|
62
|
+
animations running. Do not copy that shape.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @sheleg-design/roster
|
|
2
|
+
|
|
3
|
+
The React reference kit for the SHELEG **Roster** style pack — a white field in a faint
|
|
4
|
+
grid of squares whose argument is other people's names: an engine's wordmark inside the
|
|
5
|
+
headline, client logotypes in six pill-labelled industry columns, a review score somebody
|
|
6
|
+
else computed.
|
|
7
|
+
|
|
8
|
+
It is generated from the pack, not authored beside it: `src/styles.css` opens with
|
|
9
|
+
`styles/tokens/roster.css` byte for byte, and the rules the design agent must obey are in
|
|
10
|
+
[`.design-sync/conventions.md`](./.design-sync/conventions.md).
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install && npm run build
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then run `/design-sync` in Claude Code from this directory to push it to
|
|
17
|
+
claude.ai/design.
|
|
18
|
+
|
|
19
|
+
## The spine, and this pack's five
|
|
20
|
+
|
|
21
|
+
`Button`, `Card`, `Chip`, `Stat`, `Heading` and `Rule` are identical in name, props and
|
|
22
|
+
types across every SHELEG kit — switching packs swaps identity, not API.
|
|
23
|
+
|
|
24
|
+
| Component | What it is |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `IndustryColumn` | a pill-labelled column of other companies' marks, hairline-divided — the **signature element** |
|
|
27
|
+
| `LogoTile` | one third-party mark, greyscale at rest, full colour on hover, and it never moves |
|
|
28
|
+
| `Eyebrow` | the only tracked type in the pack, and a `<p>` rather than an `<h2>` |
|
|
29
|
+
| `StepCard` | copy beside a product shot, with a two-digit number in the accent |
|
|
30
|
+
| `FaqList` | a `<dl>` whose answers are always visible |
|
|
31
|
+
|
|
32
|
+
## Why the accent never carries a word
|
|
33
|
+
|
|
34
|
+
Both of the reference's oranges fail as a text or label colour: `#fa5c12` is 3.18:1 on
|
|
35
|
+
white, and white on `#f25533` at the nav pill's 16px/600 is **3.43:1**. The kit ships
|
|
36
|
+
`--accent-ink` at 4.52:1 for anything that has to be read, keeps the accent for fills and
|
|
37
|
+
large text, and makes the primary button black — which is what the reference itself does
|
|
38
|
+
for its hero, at 19.66:1.
|
|
39
|
+
|
|
40
|
+
## Two breakpoints, both derived
|
|
41
|
+
|
|
42
|
+
`IndustryColumn` goes two-up to one-up below **220px of its own width**; `StepCard`
|
|
43
|
+
collapses its split below **640px of its own**. Neither number is the viewport breakpoint
|
|
44
|
+
the reference used, because a column that is 300px wide on a 1440px screen needs the
|
|
45
|
+
narrow layout and a viewport query cannot see that.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/roster",
|
|
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
|
+
The primary action is **black**, and that is the reference's own choice rather than
|
|
6
|
+
this pack's invention: of its two CTAs, white on `--cta` measures 19.66:1 and white on
|
|
7
|
+
the accent 3.43:1. The page had already answered the question.
|
|
8
|
+
|
|
9
|
+
The `secondary` variant is the accent fill and it carries **`--ink`, not white**. An
|
|
10
|
+
accent button that must be light-on-dark has to reach large text first (≥24px, or
|
|
11
|
+
≥18.66px bold); below that the contrast is not there.
|
|
12
|
+
|
|
13
|
+
`:focus-visible` is `--accent-ink`, never `--accent` — 3.18:1 is too little for a ring
|
|
14
|
+
a keyboard user has to find.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<Button>Start 3-day trial</Button>
|
|
18
|
+
<Button variant="secondary">Try free</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={['ro-btn', `ro-btn--${variant}`, `ro-btn--${size}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
`--surface` at `--radius-card`, one `--rule` hairline, and **no shadow** — the
|
|
6
|
+
reference's shadow slot holds an all-transparent ring composite on 101 elements, so
|
|
7
|
+
this pack has no elevation system at all. `:hover` moves the hairline to
|
|
8
|
+
`--rule-strong`; nothing lifts.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<Card title="Backlinks" meta="weekly">Placements you did not have to pitch.</Card>
|
|
12
|
+
```
|
|
@@ -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={['ro-card', className].filter(Boolean).join(' ')}>
|
|
15
|
+
{head && (
|
|
16
|
+
<div className="ro-card__head">
|
|
17
|
+
{title !== undefined && <h3 className="ro-card__title">{title}</h3>}
|
|
18
|
+
{meta !== undefined && <span className="ro-card__meta">{meta}</span>}
|
|
19
|
+
</div>
|
|
20
|
+
)}
|
|
21
|
+
<div className="ro-card__body">{children}</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A small bordered label. `accent` carries `--accent-ink` on `--surface` rather than a
|
|
6
|
+
tinted fill, because the accent is a fill colour and not a text colour.
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
<Chip>Keyword Discovery</Chip>
|
|
10
|
+
<Chip tone="accent" selected>User Prompts</Chip>
|
|
11
|
+
```
|
|
@@ -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
|
+
'ro-chip',
|
|
15
|
+
`ro-chip--${tone}`,
|
|
16
|
+
selected ? 'ro-chip--selected' : undefined,
|
|
17
|
+
className,
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ')}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The small tracked line above a section head — `--track-eyebrow` 0.4px is the only
|
|
6
|
+
tracked type in the pack — in `--accent-ink` rather than `--accent`, because 3.18:1
|
|
7
|
+
is not enough for 16px text.
|
|
8
|
+
|
|
9
|
+
It renders a `<p>`. The reference marks all sixteen of its eyebrows as `<h2>`, which
|
|
10
|
+
is how a document outline ends up saying "eyebrow" where the page says "section
|
|
11
|
+
head".
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Eyebrow>How it works</Eyebrow>
|
|
15
|
+
<Heading level={2}>Organic growth made simple</Heading>
|
|
16
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface EyebrowProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The small tracked line above a section head. It is a `<p>`, never an `<h2>`: the
|
|
10
|
+
* reference marks all sixteen of its eyebrows as level-two headings and hides its
|
|
11
|
+
* real `h1`, so its document outline says "eyebrow" where the page says "section
|
|
12
|
+
* head". This pack keeps the outline and the page in agreement.
|
|
13
|
+
*/
|
|
14
|
+
export function Eyebrow({ children, className }: EyebrowProps) {
|
|
15
|
+
return <p className={['ro-eyebrow', className].filter(Boolean).join(' ')}>{children}</p>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A `<dl>`, always open, hairline between rows. The reference animates its accordion
|
|
6
|
+
over 0.7s (`--dur-accordion`); this component does not collapse at all, because an
|
|
7
|
+
answer a crawler cannot read without running JavaScript is an answer that is not
|
|
8
|
+
there.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<FaqList entries={[{ q: 'Does it work for local SEO?', a: 'Yes.' }]} />
|
|
12
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FaqEntry {
|
|
4
|
+
q: string;
|
|
5
|
+
a: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface FaqListProps {
|
|
9
|
+
entries: FaqEntry[];
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A definition list, always open. There is deliberately no `collapsed` prop: the
|
|
15
|
+
* reference ships seven `dt`/`dd` pairs in served HTML and no `<details>`, and an
|
|
16
|
+
* answer a crawler cannot read without running JavaScript is an answer that is
|
|
17
|
+
* not there.
|
|
18
|
+
*/
|
|
19
|
+
export function FaqList({ entries, className }: FaqListProps) {
|
|
20
|
+
return (
|
|
21
|
+
<dl className={['ro-faq', className].filter(Boolean).join(' ')}>
|
|
22
|
+
{entries.map(({ q, a }) => (
|
|
23
|
+
<div className="ro-faq__row" key={q}>
|
|
24
|
+
<dt className="ro-faq__q">{q}</dt>
|
|
25
|
+
<dd className="ro-faq__a">{a}</dd>
|
|
26
|
+
</div>
|
|
27
|
+
))}
|
|
28
|
+
</dl>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Level 1 is the display and it is set in the **body** face at `--size-display`; level 2
|
|
6
|
+
is the section head and it is set in `--font-head`. That inversion is the pack: the
|
|
7
|
+
display face only ever appears at 52px, and the body face has to hold a poster.
|
|
8
|
+
|
|
9
|
+
Level 1 steps at breakpoints — 36px, 60px, 68px — because **neither of the
|
|
10
|
+
reference's stylesheets contains a single `clamp()`**, and a fluid ramp here would be
|
|
11
|
+
an invented value.
|
|
12
|
+
|
|
13
|
+
**Use the level for the level.** The reference hides its real `h1` in an `.sr-only`
|
|
14
|
+
span and marks all sixteen of its eyebrows as `h2`, so its outline says "eyebrow"
|
|
15
|
+
where the page says "section head". This kit keeps the outline and the page in
|
|
16
|
+
agreement.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<Heading level={1}>Grow organic traffic on autopilot</Heading>
|
|
20
|
+
```
|
|
@@ -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={['ro-heading', `ro-heading--${level}`, className].filter(Boolean).join(' ')}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</Tag>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**The pack's signature element.** A `--radius-pill` label over a column of other
|
|
6
|
+
companies' marks, divided from its neighbours by a 1px `--rule` — six across at 1440,
|
|
7
|
+
three at 768, two below, and the divider survives every step because the divider is
|
|
8
|
+
what makes it a wall rather than a pile.
|
|
9
|
+
|
|
10
|
+
It sets `container-type: inline-size`, so the marks inside answer to the column's own
|
|
11
|
+
width. Below **220px** the two-up grid goes to one, and that number is derived rather
|
|
12
|
+
than carried over from a viewport: two tiles at the `--tile` ceiling of 40px plus the
|
|
13
|
+
24px gap plus 24px of padding each side is 152px, and a logotype is wider than its
|
|
14
|
+
tile, so the pair crowds at roughly 220.
|
|
15
|
+
|
|
16
|
+
**Do not tidy the roster.** Equalising the logos' optical weights, tinting them to one
|
|
17
|
+
colour, or dropping the dividers each turn it into a decoration. Greyscale at rest is
|
|
18
|
+
the only normalisation allowed, and `LogoTile` does that.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<IndustryColumn label="SaaS & Tech">
|
|
22
|
+
<LogoTile label="ConsentStack"><img src="/consentstack.svg" alt="" /></LogoTile>
|
|
23
|
+
<LogoTile label="Yardstick"><img src="/yardstick.svg" alt="" /></LogoTile>
|
|
24
|
+
</IndustryColumn>
|
|
25
|
+
```
|