sheleg-design-skill 1.19.0 → 1.20.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 +80 -0
- package/README.md +5 -4
- package/bin/cli.js +5 -3
- package/cursor/rules/sheleg-design.mdc +4 -1
- package/kits/manpage/.design-sync/config.json +14 -0
- package/kits/manpage/.design-sync/conventions.md +75 -0
- package/kits/manpage/README.md +39 -0
- package/kits/manpage/package.json +29 -0
- package/kits/manpage/src/Button.md +23 -0
- package/kits/manpage/src/Button.tsx +33 -0
- package/kits/manpage/src/Card.md +16 -0
- package/kits/manpage/src/Card.tsx +24 -0
- package/kits/manpage/src/Chip.md +15 -0
- package/kits/manpage/src/Chip.tsx +25 -0
- package/kits/manpage/src/CodeFrame.md +22 -0
- package/kits/manpage/src/CodeFrame.tsx +26 -0
- package/kits/manpage/src/EndpointRow.md +20 -0
- package/kits/manpage/src/EndpointRow.tsx +33 -0
- package/kits/manpage/src/FaqList.md +24 -0
- package/kits/manpage/src/FaqList.tsx +31 -0
- package/kits/manpage/src/Heading.md +19 -0
- package/kits/manpage/src/Heading.tsx +19 -0
- package/kits/manpage/src/LabelChip.md +23 -0
- package/kits/manpage/src/LabelChip.tsx +26 -0
- package/kits/manpage/src/Rule.md +15 -0
- package/kits/manpage/src/Rule.tsx +18 -0
- package/kits/manpage/src/Stat.md +16 -0
- package/kits/manpage/src/Stat.tsx +17 -0
- package/kits/manpage/src/TreeItem.md +17 -0
- package/kits/manpage/src/TreeItem.tsx +19 -0
- package/kits/manpage/src/index.ts +25 -0
- package/kits/manpage/src/styles.css +529 -0
- package/kits/manpage/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 +5 -4
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
- package/plugins/sheleg-design/skills/sheleg-design/styles/blueprint.md +1 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/datasheet.md +2 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/field-notes.md +4 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/instrument-console.md +4 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/manpage.md +456 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +2 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +2 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/manpage.css +272 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/workbench.md +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,86 @@ 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.20.0] - 2026-08-12
|
|
8
|
+
|
|
9
|
+
A fifteenth style pack, whose display typeface costs zero bytes — and three
|
|
10
|
+
WCAG failures in the reference, one of them on the very element the design is
|
|
11
|
+
remembered by.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`manpage`** — the fifteenth pack, extracted from `zernio.com` off the
|
|
16
|
+
server-rendered HTML of three pages and its two shipped stylesheets, which
|
|
17
|
+
declare 398 custom properties: the Tailwind v4 default ramps plus twelve
|
|
18
|
+
bespoke brand names (coral, cream, ink, charcoal, burgundy, each with a
|
|
19
|
+
`-muted` partner). Cream paper, a 48px display that never grows louder, a
|
|
20
|
+
576px argument column narrower than most prose, coral label chips that are
|
|
21
|
+
real `<h2>`s, `└` tree glyphs in their own grid column, and one dark code
|
|
22
|
+
frame as the focal point. For developer products whose buyer reads code —
|
|
23
|
+
APIs, SDKs, CLIs, MCP servers. Widened contract, a two-theme token layer, a
|
|
24
|
+
full reference kit, and reciprocal forks written into `blueprint`,
|
|
25
|
+
`datasheet`, `field-notes`, `instrument-console`, `scoreboard`, `showroom`
|
|
26
|
+
and `workbench`.
|
|
27
|
+
- **The display face is the system monospace, and that is the whole identity.**
|
|
28
|
+
The reference loads exactly one webfont — a single variable Geist Sans — and
|
|
29
|
+
sets its headline, body, chips, code frames and FAQ in
|
|
30
|
+
`Menlo, Consolas, Monaco, "Liberation Mono", "Courier New", monospace`, which
|
|
31
|
+
is already on the reader's machine. No render-blocking request for the face
|
|
32
|
+
that carries the page, and no swap window on the headline. Substituting a
|
|
33
|
+
webfont mono is banned in the pack: it costs a request to look less native.
|
|
34
|
+
- **The section heading is a chip and the chip is a real heading.** `LabelChip`
|
|
35
|
+
wraps its span in an `<h2>`, which is why the reference keeps a clean outline
|
|
36
|
+
— one `h1`, one `h2` per section — while reading as a printed specification.
|
|
37
|
+
- **`FaqList` is a `<dl>` that never collapses.** The reference ships zero
|
|
38
|
+
`<details>` and zero `<summary>` on its FAQ: every answer is flat text in the
|
|
39
|
+
DOM, paired with its question, extractable without running JavaScript. The
|
|
40
|
+
component has no `collapsed` prop and will not get one.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
Four corrections to the reference, every replacement a colour it already ships:
|
|
45
|
+
|
|
46
|
+
- **The white button label fails AA.** `Start for Free` is white on coral at
|
|
47
|
+
**4.16:1**, on both the hero and the closing CTA. The fill is kept — the coral
|
|
48
|
+
button *is* the identity — and the label darkens to `--on-action` (ink) at
|
|
49
|
+
**4.55:1**. `--action-strong` is the reference's burgundy, carrying white at
|
|
50
|
+
13.34:1.
|
|
51
|
+
- **The signature element is the least readable thing on the page.** The section
|
|
52
|
+
chip paints 12px coral on a coral/8 wash: **3.24:1**, worse than coral on bare
|
|
53
|
+
cream because the wash lifts the field. The wash and edge are kept so the chip
|
|
54
|
+
looks identical; the label becomes `--accent-ink` at **10.40:1**.
|
|
55
|
+
- **The live-status green fails AA at 2.82:1.** `green-600` carries the credit
|
|
56
|
+
balance, the `online` badge and both weekly counters. Its own ramp cannot be
|
|
57
|
+
stepped into a legal set — `green-700` still misses at 4.35:1 and `green-800`
|
|
58
|
+
clears AA but separates by only 3.9 under dichromacy — so success takes
|
|
59
|
+
emerald-800, a ramp the reference also ships in full.
|
|
60
|
+
- **One reduced-motion gate out of eight animations.** The reference gates its
|
|
61
|
+
40s logo marquee behind `motion-safe:` and leaves the hero blur-in, every
|
|
62
|
+
section rise, `fadeInScale`, `slideInRight`, `pulse`, `ping` and a **1.1s
|
|
63
|
+
infinite `waveform`** running for a reader who asked for stillness. The pack
|
|
64
|
+
collapses the whole surface, and infinite motion **stops** rather than
|
|
65
|
+
shortens.
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- `test/floors.json` raised: `validate.py` 1647 → 1788, `validate_palette.py`
|
|
70
|
+
716 → 791, `sloplint.py` 366 → 422.
|
|
71
|
+
- **The stated-ratio checker earned its keep twice on this pack**, catching
|
|
72
|
+
`--ink-strong` claimed at 18.98:1 against a computed 19.44 and
|
|
73
|
+
`--on-action-strong` at 6.71:1 against 7.76 — both authored by hand, both
|
|
74
|
+
wrong, neither visible on inspection.
|
|
75
|
+
- `SURFACE_COMPOSITION.md`: three counts corrected by measurement — the accent
|
|
76
|
+
resolves as `--accent` in **thirteen** packs, `--brand` in `field-notes` and
|
|
77
|
+
`--cta` in `orchard`. **B-016 stays open**: none of the three reaches a check,
|
|
78
|
+
because `in thirteen,` is not followed by a counted noun. This is the third
|
|
79
|
+
release in which they were fixed by hand.
|
|
80
|
+
- `plugins/sheleg-design/.claude-plugin/plugin.json` said **thirteen** style
|
|
81
|
+
packs while fourteen shipped. `validate_counted_claims()` did not catch it:
|
|
82
|
+
its pattern wants `<number> [pluggable|locked] style packs` and the manifest
|
|
83
|
+
wrote `pluggable visual style packs`, so the intervening adjective hid a stale
|
|
84
|
+
count from the gate that exists to find them. Corrected to fifteen; the
|
|
85
|
+
pattern gap is the same class as B-016.
|
|
86
|
+
|
|
7
87
|
## [1.19.0] - 2026-08-12
|
|
8
88
|
|
|
9
89
|
A fourteenth style pack, and the Refero style card it started from was wrong in
|
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 **fifteen 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
|
|
|
@@ -59,6 +59,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
59
59
|
| `scoreboard` | warm paper and warm near-black ink, 2–3px radii, an ink primary button, one hot orange that only ever marks, and a dark ledger of dotted-leader rows whose numbers are set in an aliased pixel face | products whose argument is an accumulating number — ads and SEO operators, growth tools, revenue dashboards sold on results |
|
|
60
60
|
| `cyclorama` | a pale field cycling through six pastel stops on a 32s loop under near-black ink that never moves with it, a monospaced typewriter serif over mono, one orange used only as a fill, a particle organ that holds then redeploys, no shadows anywhere | enterprise AI transformation, applied-AI services, technical consultancies — where what is sold is a change of state and there is no screenshot worth showing |
|
|
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
|
+
| `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 |
|
|
62
63
|
|
|
63
64
|
Each pack locks palette, type, texture, motion tokens, signature motifs and
|
|
64
65
|
bans — and ships a `tokens/<pack>.css` to copy verbatim, so the agent never
|
|
@@ -133,7 +134,7 @@ skills.
|
|
|
133
134
|
| `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 |
|
|
134
135
|
| `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 |
|
|
135
136
|
| `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 |
|
|
136
|
-
| `styles/*.md` | The
|
|
137
|
+
| `styles/*.md` | The fifteen style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
137
138
|
| `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) |
|
|
138
139
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
139
140
|
|
|
@@ -204,7 +205,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
204
205
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
205
206
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
206
207
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
207
|
-
identical in all
|
|
208
|
+
identical in all fifteen kits, so switching packs swaps identity rather than API,
|
|
208
209
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
209
210
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
210
211
|
stops an agent inventing motion to fill the silence.
|
|
@@ -250,7 +251,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
250
251
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
251
252
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
252
253
|
it ships the right files is CI, which installs the bundle through **both**
|
|
253
|
-
installers and `diff -r`s the result against the source, then builds all
|
|
254
|
+
installers and `diff -r`s the result against the source, then builds all fifteen
|
|
254
255
|
kits.
|
|
255
256
|
|
|
256
257
|
`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/ fifteen 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
|
|
@@ -245,7 +245,9 @@ ${c("bold", "What it installs")}
|
|
|
245
245
|
maquette (cream axonometric models on a dark table),
|
|
246
246
|
scoreboard (warm paper, pixel numerals, a dark ledger),
|
|
247
247
|
datasheet (an off-white spec sheet whose live instrument
|
|
248
|
-
goes dark when it catches the reader hiding)
|
|
248
|
+
goes dark when it catches the reader hiding),
|
|
249
|
+
manpage (a developer landing page set in the reader's
|
|
250
|
+
own system monospace, cream paper, coral label chips) —
|
|
249
251
|
plus a ready-made token CSS per pack and
|
|
250
252
|
STYLE_PACK_TEMPLATE.md for authoring more
|
|
251
253
|
`);
|
|
@@ -327,7 +329,7 @@ function main() {
|
|
|
327
329
|
` ${c("dim", "SKILL.md")} the agent skill\n` +
|
|
328
330
|
` ${c("dim", "SHELEG_DESIGN.md")} the full reference\n` +
|
|
329
331
|
` ${c("dim", "MOTION_DOCTRINE.md")} whether to animate at all — read before any animation\n` +
|
|
330
|
-
` ${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)\n\n` +
|
|
332
|
+
` ${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)\n\n` +
|
|
331
333
|
`Your Cursor / Claude agent can now discover the skill and build\n` +
|
|
332
334
|
`cinematic, scroll-driven pages — or style product UI (dashboards, admin,\n` +
|
|
333
335
|
`internal tools) from a standalone pack — on its principles.\n\n` +
|
|
@@ -33,7 +33,10 @@ numbers are set in an aliased pixel face, for a product whose argument is
|
|
|
33
33
|
an accumulating number; datasheet — an off-white spec
|
|
34
34
|
sheet whose focal element is a live instrument ruled at radius zero and
|
|
35
35
|
which re-skins itself dark when it detects the reader is hiding, for B2B
|
|
36
|
-
SaaS whose product is a verdict about the visitor or the device
|
|
36
|
+
SaaS whose product is a verdict about the visitor or the device;
|
|
37
|
+
manpage — a developer landing page set in the reader's own system
|
|
38
|
+
monospace on cream paper, coral label chips that are real headings and a
|
|
39
|
+
dark code frame as the argument, for APIs, SDKs and CLIs);
|
|
37
40
|
otherwise follow the contract below (self-contained on purpose).
|
|
38
41
|
|
|
39
42
|
## Whether to animate at all — before how
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/manpage",
|
|
3
|
+
"globalName": "ShelegManpage",
|
|
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,75 @@
|
|
|
1
|
+
# Manpage — the contract this design system ships under
|
|
2
|
+
|
|
3
|
+
**Register.** Choose Manpage for **a developer product whose buyer reads code for
|
|
4
|
+
a living**: an API, an SDK, a CLI, a protocol, an MCP server, developer
|
|
5
|
+
infrastructure. The landing page *is* the documentation, set in the typeface of
|
|
6
|
+
the documentation — the honest hero is not a screenshot but six lines of a request.
|
|
7
|
+
The fork people get wrong is against `datasheet`, which shares the off-white
|
|
8
|
+
paper, the hairlines and the one warm orange-red: there the page is about **the
|
|
9
|
+
reader** and its mono carries a reading the product produced (*what did you get?*);
|
|
10
|
+
here the page is about **the product** and its mono is the whole body face, showing
|
|
11
|
+
a call the reader will write (*what will you type?*). Build every screen against
|
|
12
|
+
`var(--…)` and never a literal.
|
|
13
|
+
|
|
14
|
+
**The display face is free, and that is the identity.** One webfont ships — a
|
|
15
|
+
single variable Geist Sans — and everything visible is set in `--font-mono`, which
|
|
16
|
+
is the **system** monospace stack: Menlo, Consolas, Monaco. There is nothing to
|
|
17
|
+
download for the face that carries the whole page, no swap window on the headline,
|
|
18
|
+
and the slight variation between machines is the point — it reads as the terminal
|
|
19
|
+
rather than as art direction. Substituting a webfont mono costs a render-blocking
|
|
20
|
+
request to look less native. Do not.
|
|
21
|
+
|
|
22
|
+
**The accent is a mark, a fill and a wash — almost never a word.** `--accent`
|
|
23
|
+
measures 3.61:1 on the field: enough for a non-text mark and for large text at 24px
|
|
24
|
+
and above, not enough at body size. Where a word must be coral, use `--accent-ink`
|
|
25
|
+
at 11.59:1. The primary button keeps the coral fill — the coral button *is* the
|
|
26
|
+
identity — but its label is `--on-action`, which is ink at 4.55:1, because white on
|
|
27
|
+
coral measures 4.16:1 and fails AA. `--action-strong` is the burgundy that carries
|
|
28
|
+
white at 13.34:1.
|
|
29
|
+
|
|
30
|
+
**The section heading is a chip, and the chip is a real heading.** `LabelChip`
|
|
31
|
+
wraps its span in an `<h2>`. That single decision is why the page keeps a clean
|
|
32
|
+
outline — one `h1`, one `h2` per section — while reading as a printed specification.
|
|
33
|
+
An eyebrow that merely sits above a heading is not this component and does not earn
|
|
34
|
+
the identity.
|
|
35
|
+
|
|
36
|
+
**The width ladder is the layout.** The argument runs in `--measure-text` (576px),
|
|
37
|
+
narrower than most prose; the hero and its code frame take `--measure-hero`
|
|
38
|
+
(768px); only evidence widens — `--measure-proof` for the testimonial grid,
|
|
39
|
+
`--measure-foot` for the footer, `--measure-wall` for the logo wall. Below 896px
|
|
40
|
+
every step collapses to `--measure-text` and the rhythm carries the structure
|
|
41
|
+
instead.
|
|
42
|
+
|
|
43
|
+
**Radii stay small and nothing is a pill.** 2px on the label chip, 6px on a
|
|
44
|
+
control, 8px on a button, 12px on a card, 16px on a panel. A fully round control
|
|
45
|
+
breaks the printed-tag reading immediately.
|
|
46
|
+
|
|
47
|
+
**One hairline, no shadow.** `--lift-card` is a single 1px bottom line. Nothing
|
|
48
|
+
casts downward, nothing lifts on hover, nothing scales. The one glow that exists,
|
|
49
|
+
`--glow-accent`, is a glow and not an elevation.
|
|
50
|
+
|
|
51
|
+
**The 4px frame is load-bearing.** `body { padding: var(--frame) }` insets the
|
|
52
|
+
whole document from the window edge so every panel closes against a visible margin
|
|
53
|
+
of paper. It is the cheapest identity move in the system and the easiest to delete
|
|
54
|
+
by accident in a layout refactor — it has a token name so it has something worth
|
|
55
|
+
preserving.
|
|
56
|
+
|
|
57
|
+
**Status is never by colour alone.** Every status carries its word, exactly as the
|
|
58
|
+
reference does — `online`, `Done`, `GET`, `POST`. The light set clears both gate
|
|
59
|
+
floors (17.5 at full colour, 8.1 under dichromacy); the dark set clears the hard
|
|
60
|
+
floor at 10.1 and runs tight at 6.8 under dichromacy, which the word covers. Note
|
|
61
|
+
that `--warning` is a near-black brown by arithmetic rather than by taste: the
|
|
62
|
+
coral accent occupies the warning hue, and every amber that reads as a warning
|
|
63
|
+
collides either with the accent under dichromacy or with danger at full colour.
|
|
64
|
+
|
|
65
|
+
**Motion is entry only, and then the page holds still.** One `fadeInBlur` on the
|
|
66
|
+
headline (0.6s, `--ease-out`, after `--stagger`), one entrance per section on the
|
|
67
|
+
way down, and nothing moves again — no hover lift, no drifting gradient, no
|
|
68
|
+
counter that keeps counting. **No scroll clock, no scrubbing, no parallax:** this
|
|
69
|
+
is the calmest pack in the family. At `prefers-reduced-motion: reduce` every
|
|
70
|
+
duration and the stagger go to zero, the blur never applies, and infinite motion
|
|
71
|
+
**stops** rather than shortens.
|
|
72
|
+
|
|
73
|
+
**The FAQ does not open.** `FaqList` is a `<dl>` whose answers are always visible,
|
|
74
|
+
because a collapsed answer is an answer no machine can extract. There is no
|
|
75
|
+
`collapsed` prop and there will not be one.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @sheleg-design/manpage
|
|
2
|
+
|
|
3
|
+
The React reference kit for the SHELEG **Manpage** style pack — cream paper under
|
|
4
|
+
the reader's own system monospace, a 48px display that never grows louder, coral
|
|
5
|
+
label chips that are real headings, and one dark code frame as the argument.
|
|
6
|
+
|
|
7
|
+
It is generated from the pack, not authored beside it: `src/styles.css` opens with
|
|
8
|
+
`styles/tokens/manpage.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
|
+
Then run `/design-sync` in Claude Code from this directory to push it to
|
|
16
|
+
claude.ai/design.
|
|
17
|
+
|
|
18
|
+
## The spine, and this pack's five
|
|
19
|
+
|
|
20
|
+
`Button`, `Card`, `Chip`, `Stat`, `Heading` and `Rule` are identical in name, props
|
|
21
|
+
and types across every SHELEG kit — switching packs swaps identity, not API.
|
|
22
|
+
|
|
23
|
+
The five that are this pack's own:
|
|
24
|
+
|
|
25
|
+
| Component | What it is |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `LabelChip` | the coral section tag that **is** an `<h2>` — the signature element |
|
|
28
|
+
| `CodeFrame` | the dark panel holding the call; scrolls, never reflows |
|
|
29
|
+
| `TreeItem` | a `└` glyph in its own grid column, never a text prefix |
|
|
30
|
+
| `FaqList` | a `<dl>` whose answers are always visible, so a machine can quote them |
|
|
31
|
+
| `EndpointRow` | a method badge, a path in mono, one line of prose |
|
|
32
|
+
|
|
33
|
+
## The two things most likely to be broken
|
|
34
|
+
|
|
35
|
+
**The 4px body frame.** `body { padding: var(--frame) }` is what makes the page
|
|
36
|
+
read as a sheet laid on a desk. Deleting it produces no error and no failing test.
|
|
37
|
+
|
|
38
|
+
**The label chip's ink.** It is `--accent-ink`, not `--accent`. Coral on the chip's
|
|
39
|
+
own wash measures 3.24:1; burgundy measures 10.40:1, and the chip looks the same.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/manpage",
|
|
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,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Actions
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
One accent fill per view. In this pack the fill is `--action` — the reference's
|
|
6
|
+
own coral, kept because the coral button *is* the identity — but the label is
|
|
7
|
+
`--on-action`, which is ink rather than white. White on coral measures 4.16:1 and
|
|
8
|
+
does not clear AA at the 16px semibold the reference ships; ink clears at 4.55:1.
|
|
9
|
+
Where a white label is non-negotiable, hover onto `--action-strong` (burgundy,
|
|
10
|
+
13.34:1).
|
|
11
|
+
|
|
12
|
+
`secondary` is the white 1px-bordered control the reference pairs with it — on a
|
|
13
|
+
developer page that is usually an OAuth continue. `ghost` changes ink and nothing
|
|
14
|
+
else.
|
|
15
|
+
|
|
16
|
+
Transitions `background-color` over `--dur-instant` and nothing more: no lift, no
|
|
17
|
+
scale, no shadow bloom.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<Button onClick={start}>Start for Free</Button>
|
|
21
|
+
<Button variant="secondary" onClick={google}>Continue with Google</Button>
|
|
22
|
+
<Button variant="ghost" size="sm" onClick={skip}>I'll do this later</Button>
|
|
23
|
+
```
|
|
@@ -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={['mp-btn', `mp-btn--${variant}`, `mp-btn--${size}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
`--surface` on `--rule`, radius `--r-card`, padding `--pad-card`, and a single 1px
|
|
6
|
+
bottom line for lift. The reference uses it for testimonials at
|
|
7
|
+
`--measure-proof`, three across.
|
|
8
|
+
|
|
9
|
+
**No hover state.** The card does not lift, tint or shift on this pack — it is a
|
|
10
|
+
quotation on paper, and paper does not respond to a cursor.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Card title="Dev Singh" meta="Founder, ad-attribution SaaS">
|
|
14
|
+
I love the speed and quality here.
|
|
15
|
+
</Card>
|
|
16
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CardProps {
|
|
4
|
+
title?: string;
|
|
5
|
+
/** Right-aligned metadata on the title row: a count, an id, a timestamp. */
|
|
6
|
+
meta?: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Card({ title, meta, children, className }: CardProps) {
|
|
12
|
+
const head = title !== undefined || meta !== undefined;
|
|
13
|
+
return (
|
|
14
|
+
<section className={['mp-card', className].filter(Boolean).join(' ')}>
|
|
15
|
+
{head && (
|
|
16
|
+
<div className="mp-card__head">
|
|
17
|
+
{title !== undefined && <h3 className="mp-card__title">{title}</h3>}
|
|
18
|
+
{meta !== undefined && <span className="mp-card__meta">{meta}</span>}
|
|
19
|
+
</div>
|
|
20
|
+
)}
|
|
21
|
+
<div className="mp-card__body">{children}</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The generic tag. For the section-heading chip that carries this pack's identity,
|
|
6
|
+
use `LabelChip` instead — it wraps a real `<h2>` and this one does not.
|
|
7
|
+
|
|
8
|
+
`neutral` is `--surface-2` under `--ink-soft`. `accent` is `--accent-wash` under
|
|
9
|
+
`--accent-ink`, never under `--accent`: coral on its own wash is 3.24:1.
|
|
10
|
+
`selected` adds a `--accent-edge` border.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Chip>CASE STUDY</Chip>
|
|
14
|
+
<Chip tone="accent" selected>Free credits</Chip>
|
|
15
|
+
```
|
|
@@ -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
|
+
'mp-chip',
|
|
15
|
+
`mp-chip--${tone}`,
|
|
16
|
+
selected ? 'mp-chip--selected' : undefined,
|
|
17
|
+
className,
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ')}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The focal element of the hero, and the only dark surface on the light theme. A
|
|
6
|
+
`--r-panel` frame with three window dots, a filename, a language label and a copy
|
|
7
|
+
affordance — because the page's argument is the call, not a screenshot of a
|
|
8
|
+
dashboard.
|
|
9
|
+
|
|
10
|
+
Two rules it does not bend. It **scrolls horizontally and never reflows**: a
|
|
11
|
+
wrapped code sample is a wrong code sample. And it **never shrinks its type** below
|
|
12
|
+
`--t-mono` (12px) on small screens — the sample stops being evidence the moment it
|
|
13
|
+
becomes unreadable.
|
|
14
|
+
|
|
15
|
+
Place it at `--measure-hero` and let the fold crop it. The crop is what signals
|
|
16
|
+
there is more.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<CodeFrame filename="zernio.ts" language="TypeScript">
|
|
20
|
+
{snippet}
|
|
21
|
+
</CodeFrame>
|
|
22
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CodeFrameProps {
|
|
4
|
+
/** The filename in the header row — `zernio.ts`, `main.py`. */
|
|
5
|
+
filename: string;
|
|
6
|
+
/** The language label on the right of the header row. */
|
|
7
|
+
language?: string;
|
|
8
|
+
/** Rendered as-is; highlight upstream. Never reflowed, never resized. */
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function CodeFrame({ filename, language, children, className }: CodeFrameProps) {
|
|
14
|
+
return (
|
|
15
|
+
<figure className={['mp-code', className].filter(Boolean).join(' ')}>
|
|
16
|
+
<figcaption className="mp-code__head">
|
|
17
|
+
<span className="mp-code__dots" aria-hidden="true">
|
|
18
|
+
<i /><i /><i />
|
|
19
|
+
</span>
|
|
20
|
+
<span className="mp-code__name">{filename}</span>
|
|
21
|
+
{language !== undefined && <span className="mp-code__lang">{language}</span>}
|
|
22
|
+
</figcaption>
|
|
23
|
+
<pre className="mp-code__body"><code>{children}</code></pre>
|
|
24
|
+
</figure>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The "what you can do" row: a method badge, a path in mono, and one line of prose.
|
|
6
|
+
It is how this pack lists capability without a feature grid — each row is a thing
|
|
7
|
+
you can call, so the list reads as an index rather than as marketing.
|
|
8
|
+
|
|
9
|
+
The badge is a status token on its own tint at `--r-chip`: `--info` for writes,
|
|
10
|
+
`--success` for reads. The method word is always spelled out, never colour alone —
|
|
11
|
+
`GET` and `POST` separate by 29.2 at full colour but only 16.3 under dichromacy,
|
|
12
|
+
and a reader who cannot tell them apart still has to be able to read them.
|
|
13
|
+
|
|
14
|
+
`selected` marks the row the surrounding copy is talking about, using
|
|
15
|
+
`--accent-wash`. At most one per list.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<EndpointRow method="GET" path="/connect/{platform}">One OAuth flow for every platform.</EndpointRow>
|
|
19
|
+
<EndpointRow method="POST" path="/posts" selected>One call, 16 platforms.</EndpointRow>
|
|
20
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
4
|
+
|
|
5
|
+
export interface EndpointRowProps {
|
|
6
|
+
method: Method;
|
|
7
|
+
/** The path, set in mono: `/whatsapp/phone-numbers/purchase`. */
|
|
8
|
+
path: string;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
/** Drawn as the selected row — the one the surrounding copy is about. */
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function EndpointRow({ method, path, children, selected = false, className }: EndpointRowProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
className={[
|
|
19
|
+
'mp-endpoint',
|
|
20
|
+
selected ? 'mp-endpoint--selected' : undefined,
|
|
21
|
+
className,
|
|
22
|
+
]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join(' ')}
|
|
25
|
+
>
|
|
26
|
+
<span className={['mp-endpoint__method', `mp-endpoint__method--${method.toLowerCase()}`].join(' ')}>
|
|
27
|
+
{method}
|
|
28
|
+
</span>
|
|
29
|
+
<code className="mp-endpoint__path">{path}</code>
|
|
30
|
+
{children !== undefined && <span className="mp-endpoint__note">{children}</span>}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A `<dl>` / `<dt>` / `<dd>` whose answers are **always visible**. The reference
|
|
6
|
+
ships zero `<details>` and zero `<summary>` elements on this section, and that is
|
|
7
|
+
the single best decision on its page: every answer is flat text in the DOM,
|
|
8
|
+
semantically paired with its question, readable without running a line of
|
|
9
|
+
JavaScript.
|
|
10
|
+
|
|
11
|
+
There is no `collapsed` prop and there will not be one. An accordion trades an
|
|
12
|
+
extractable answer for a click, and the answer is the reason the section exists.
|
|
13
|
+
Nine questions expanded at 14px cost less vertical space than most people assume,
|
|
14
|
+
and nothing here animates because nothing here opens.
|
|
15
|
+
|
|
16
|
+
Pair it with `FaqSchema` in the host app so the visible text and the `FAQPage`
|
|
17
|
+
JSON-LD come from the same array — the reference publishes the section without the
|
|
18
|
+
schema, which is the gap this component exists to close.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<FaqList entries={[
|
|
22
|
+
{ q: 'What happens when a post fails?', a: 'We auto-retry, then fire a webhook with the reason.' },
|
|
23
|
+
]} />
|
|
24
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
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:
|
|
15
|
+
* an accordion here costs the extractable answer and buys nothing.
|
|
16
|
+
*/
|
|
17
|
+
export function FaqList({ entries, className }: FaqListProps) {
|
|
18
|
+
return (
|
|
19
|
+
<dl className={['mp-faq', className].filter(Boolean).join(' ')}>
|
|
20
|
+
{entries.map(({ q, a }) => (
|
|
21
|
+
<div className="mp-faq__row" key={q}>
|
|
22
|
+
<dt className="mp-faq__q">{q}</dt>
|
|
23
|
+
<dd className="mp-faq__a">
|
|
24
|
+
<span className="mp-faq__glyph" aria-hidden="true">└</span>
|
|
25
|
+
<span>{a}</span>
|
|
26
|
+
</dd>
|
|
27
|
+
</div>
|
|
28
|
+
))}
|
|
29
|
+
</dl>
|
|
30
|
+
);
|
|
31
|
+
}
|