sheleg-design-skill 1.52.0 → 1.53.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 +51 -0
- package/README.md +5 -4
- package/bin/cli.js +5 -1
- package/cursor/rules/sheleg-design.mdc +7 -0
- package/kits/test-drive/.design-sync/config.json +14 -0
- package/kits/test-drive/.design-sync/conventions.md +40 -0
- package/kits/test-drive/README.md +34 -0
- package/kits/test-drive/package.json +29 -0
- package/kits/test-drive/src/Annotation.md +13 -0
- package/kits/test-drive/src/Annotation.tsx +20 -0
- package/kits/test-drive/src/Button.md +23 -0
- package/kits/test-drive/src/Button.tsx +33 -0
- package/kits/test-drive/src/Card.md +14 -0
- package/kits/test-drive/src/Card.tsx +24 -0
- package/kits/test-drive/src/Chip.md +13 -0
- package/kits/test-drive/src/Chip.tsx +25 -0
- package/kits/test-drive/src/Delta.md +11 -0
- package/kits/test-drive/src/Delta.tsx +23 -0
- package/kits/test-drive/src/DemoFrame.md +19 -0
- package/kits/test-drive/src/DemoFrame.tsx +25 -0
- package/kits/test-drive/src/Empty.md +13 -0
- package/kits/test-drive/src/Empty.tsx +21 -0
- package/kits/test-drive/src/Field.md +18 -0
- package/kits/test-drive/src/Field.tsx +38 -0
- package/kits/test-drive/src/Heading.md +13 -0
- package/kits/test-drive/src/Heading.tsx +19 -0
- package/kits/test-drive/src/Machine.md +18 -0
- package/kits/test-drive/src/Machine.tsx +26 -0
- package/kits/test-drive/src/Marker.md +11 -0
- package/kits/test-drive/src/Marker.tsx +12 -0
- package/kits/test-drive/src/NavBar.md +14 -0
- package/kits/test-drive/src/NavBar.tsx +20 -0
- package/kits/test-drive/src/Rule.md +11 -0
- package/kits/test-drive/src/Rule.tsx +18 -0
- package/kits/test-drive/src/Stat.md +18 -0
- package/kits/test-drive/src/Stat.tsx +17 -0
- package/kits/test-drive/src/ThinkingDots.md +15 -0
- package/kits/test-drive/src/ThinkingDots.tsx +19 -0
- package/kits/test-drive/src/index.ts +33 -0
- package/kits/test-drive/src/styles.css +833 -0
- package/kits/test-drive/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 +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/FIGMA_BRIDGE.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/STYLE_PACK_INDEX.md +3 -2
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +4 -4
- package/plugins/sheleg-design/skills/sheleg-design/styles/STYLE_PACK_TEMPLATE.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/datasheet.md +5 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +6 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +5 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/tenor.md +1 -1
- package/plugins/sheleg-design/skills/sheleg-design/styles/test-drive.md +454 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/test-drive.css +389 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,57 @@ follow [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.53.0] - 2026-08-27
|
|
10
|
+
|
|
11
|
+
### The thirty-seventh pack — the visitor is handed the keys
|
|
12
|
+
|
|
13
|
+
**`test-drive`**, extracted from a revenue-analytics product's front page and named per
|
|
14
|
+
ADR-0001 for the register it encodes: a page that sells by handing over the running
|
|
15
|
+
product — the set piece is the live app inside drawn browser chrome, and a founder's
|
|
16
|
+
hand (a script face, a yellow marker, a drawn arrow) annotates the tour.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `styles/test-drive.md` — widened, all thirteen headings; `Themes: light + dark — a
|
|
21
|
+
full theme twin`; three forks written from both sides: against `datasheet` (whose
|
|
22
|
+
live instrument reads *the visitor*, where this one hands over *the product's own
|
|
23
|
+
workspace*), against `showroom` (a still exhibit against a running machine), and
|
|
24
|
+
against `scoreboard` (one public total against a dashboard's vital signs on the
|
|
25
|
+
same warm paper and orange-red family).
|
|
26
|
+
- `styles/tokens/test-drive.css` — measured off the render, not the stylesheet, and
|
|
27
|
+
the reference is the trap the skeleton's rule 7 describes: its DaisyUI theme
|
|
28
|
+
declares a teal accent that paints **nothing** (the area-weighted census found it
|
|
29
|
+
on zero elements), so the acting coral is the brand and the teal ships nowhere.
|
|
30
|
+
The coral splits into two tokens because the reference's own CTA fails AA —
|
|
31
|
+
`#ffffff` on `#e16540` is 3.42:1 — so `--accent` lights, fills and blinks while
|
|
32
|
+
`--action` `#c04a28` carries every body-size word at 4.94:1 under white. The lit
|
|
33
|
+
control recipe (contact shadow, hue ring, glow, inner bevel) is read byte-for-byte
|
|
34
|
+
out of the reference's authored `.btn-primary`/`.btn-simple` classes, in both
|
|
35
|
+
themes. The machine ladder (`--machine-*`, `#101010`–`#202020`) is quarantined and
|
|
36
|
+
ignores the theme.
|
|
37
|
+
- `kits/test-drive/` — the six-name spine plus `DemoFrame`, `Annotation`, `Marker`,
|
|
38
|
+
`Delta`, `Machine`+`Caret`, `ThinkingDots`, `Field`, `NavBar`, `Empty`. Rendered at
|
|
39
|
+
1440, 768 and an emulated 390 before shipping (CONTRIBUTING step 8): the render
|
|
40
|
+
caught the nav computing 66px against a stated 65 (a content-box border), the link
|
|
41
|
+
row overflowing a 390px page at 428px, and the touch floor missing — all three
|
|
42
|
+
fixed pre-tag. The stat row is a container (`container-type: inline-size`),
|
|
43
|
+
stepping 2 → 4 → 7 columns by its own width.
|
|
44
|
+
- `test/scenarios.md` T35 — `test-drive` against `datasheet`, the live-instrument
|
|
45
|
+
fork, with its negative branch per standing instruction 4.
|
|
46
|
+
- ADR-0001 gains its fourteenth application: `test-drive`, with `showfloor`,
|
|
47
|
+
`sandbox`, `marginalia` and `demo-day` weighed and rejected on the ADR's own
|
|
48
|
+
criteria.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- `datasheet`, `showroom`, `scoreboard` — each gains the reciprocal fork clause
|
|
53
|
+
naming `test-drive`.
|
|
54
|
+
- Counted claims moved with the tree: thirty-seven packs and kits across `SKILL.md`,
|
|
55
|
+
the index, `SURFACE_COMPOSITION.md` (the accent is `--accent` in 34 of the 37),
|
|
56
|
+
`FIGMA_BRIDGE.md` (twelve of the thirty-seven ship a twin), `MOBILE_SURFACES.md`,
|
|
57
|
+
`DESIGN_SYNC_BRIDGE.md`, the template's theme split (12/16/9), README, DOCMAP,
|
|
58
|
+
both installers and all three manifests.
|
|
59
|
+
|
|
9
60
|
## [1.52.0] - 2026-08-27
|
|
10
61
|
|
|
11
62
|
### The front door is the designs
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ problem — invented colors, six accent hues, dark mode retrofitted later.
|
|
|
31
31
|
|
|
32
32
|
This skill is the taste layer. It gives a coding agent **one motion
|
|
33
33
|
methodology** for cinematic, scroll-driven pages, **a motion doctrine** that
|
|
34
|
-
decides whether to animate before it decides how, and **thirty-
|
|
34
|
+
decides whether to animate before it decides how, and **thirty-seven locked style
|
|
35
35
|
packs** with ready-made design tokens, so what it builds reads as one system
|
|
36
36
|
instead of a pile of effects.
|
|
37
37
|
|
|
@@ -76,6 +76,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
76
76
|
| `rimlight` | a white field with a cool grey act separator and one near-black act, a grotesque for every sentence and a **monospace for every piece of chrome**, square on 84% of its elements, tracked negative at every size and with **no bold anywhere** — and an elevation made of **coloured light**: a sixteen-layer rig, six layers lit and ten held at alpha 0, thrown from below-left onto one control per viewport | **a studio's own front door and the pages that sell what it makes** — design and engineering agencies, product studios, service and case-study pages (standalone) |
|
|
77
77
|
| `onionskin` | a white technical sheet at **96.5% zero radius** — the squarest page in the library — where **two bases do all the work and everything quiet is one of them at an alpha**: text dims through the ink, structure through a navy that is never a word, so there is no grey ramp at all. A dot grid, dashed hairlines, three faces with one job each, and an **11px working size** | **developer and AI infrastructure whose front page is a working document** — memory and context engines, retrieval services, agent runtimes, evaluation and observability surfaces (standalone) |
|
|
78
78
|
| `deskmate` | a warm beige working day lit from **one source above the top edge** — every gradient origin in the reference sits at or above `y=0` — where a single four-stop ramp (peach, lilac, violet, deep navy) washes the field, fills a panel and fills one word of a heading, and everything a hand touches is a 56px pill against 32px slabs | **products sold as a colleague rather than a tool** — AI employees and chat-native agents, assistants living in Slack or Teams, delegation and approval surfaces (standalone) |
|
|
79
|
+
| `test-drive` | warm paper and white cards where **the live product runs inside drawn browser chrome** — traffic-light dots, a real address, the engine on — one coral in two tokens (the brand hue lights, fills and blinks; a darker step carries every body-size word), every control lit by a four-layer shadow in its own hue, a handwritten face and a yellow marker annotating from the margin | **self-serve SaaS sold by handing over the running product** — analytics, dashboards, developer tools with a visible console, indie SaaS (standalone) |
|
|
79
80
|
| `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 |
|
|
80
81
|
| `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 |
|
|
81
82
|
| `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 |
|
|
@@ -176,7 +177,7 @@ skills.
|
|
|
176
177
|
| `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 |
|
|
177
178
|
| `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 |
|
|
178
179
|
| `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 |
|
|
179
|
-
| `styles/*.md` | The thirty-
|
|
180
|
+
| `styles/*.md` | The thirty-seven style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
180
181
|
| `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) |
|
|
181
182
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
182
183
|
|
|
@@ -247,7 +248,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
247
248
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
248
249
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
249
250
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
250
|
-
identical in all thirty-
|
|
251
|
+
identical in all thirty-seven kits, so switching packs swaps identity rather than API,
|
|
251
252
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
252
253
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
253
254
|
stops an agent inventing motion to fill the silence.
|
|
@@ -297,7 +298,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
297
298
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
298
299
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
299
300
|
it ships the right files is CI, which installs the bundle through **both**
|
|
300
|
-
installers and `diff -r`s the result against the source, then builds all thirty-
|
|
301
|
+
installers and `diff -r`s the result against the source, then builds all thirty-seven
|
|
301
302
|
kits.
|
|
302
303
|
|
|
303
304
|
`test/scenarios.md` (T1–T34) 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/ thirty-
|
|
237
|
+
styles/ thirty-seven 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
|
|
@@ -250,6 +250,10 @@ ${c("bold", "What it installs")}
|
|
|
250
250
|
size), deskmate (warm beige lit from above the top
|
|
251
251
|
edge, one four-stop ramp, 56px pills against 32px
|
|
252
252
|
slabs, and a framed transcript as the set piece),
|
|
253
|
+
test-drive (warm paper where the live product runs
|
|
254
|
+
inside drawn browser chrome, controls lit by a
|
|
255
|
+
four-layer shadow in their own hue, and a
|
|
256
|
+
handwritten hand annotating the tour),
|
|
253
257
|
babylove (white +
|
|
254
258
|
one orange, seven tokens over Tailwind defaults),
|
|
255
259
|
outrank (white +
|
|
@@ -125,6 +125,13 @@ slabs, elevation is a field step with two shadows on the whole page, and the set
|
|
|
125
125
|
piece is a framed transcript whose quoted chat client keeps its own face and colours
|
|
126
126
|
under a --quoted-* namespace, for products sold as a colleague rather than a tool:
|
|
127
127
|
AI employees and chat-native agents);
|
|
128
|
+
test-drive — warm paper and white cards where the set piece is the live product
|
|
129
|
+
running inside drawn browser chrome with traffic-light dots, one coral in two tokens
|
|
130
|
+
(the brand hue lights, fills and blinks; a darker step carries every body-size word),
|
|
131
|
+
every control lit by a four-layer shadow in its own hue, a handwritten face and a
|
|
132
|
+
yellow marker annotating from the margin, machine surfaces quarantined in their own
|
|
133
|
+
dark ladder that ignores the theme, for self-serve SaaS sold by handing over the
|
|
134
|
+
running product;
|
|
128
135
|
otherwise follow the contract below (self-contained on purpose).
|
|
129
136
|
|
|
130
137
|
## Whether to animate at all — before how
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/test-drive",
|
|
3
|
+
"globalName": "ShelegTestDrive",
|
|
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,40 @@
|
|
|
1
|
+
# Test-drive — conventions for a design agent
|
|
2
|
+
|
|
3
|
+
This kit is the built form of the Test-drive style pack. Read these before generating
|
|
4
|
+
anything with it; they are the pack's bans, stated where a design tool will see them.
|
|
5
|
+
|
|
6
|
+
## Colour
|
|
7
|
+
|
|
8
|
+
- **One coral, two tokens.** `--accent` (#e16540) lights controls, fills bars and
|
|
9
|
+
blinks carets; it carries text only at display sizes. Every body-size coral word is
|
|
10
|
+
`--action` (#c04a28). Swapping them ships the reference's own AA failure.
|
|
11
|
+
- **The declared teal is dead.** The reference's theme names an accent it never
|
|
12
|
+
paints; nothing here may resurrect it.
|
|
13
|
+
- **Money is coral, traffic is blue** (`--chart-money`, `--chart-traffic`), and the
|
|
14
|
+
blue never carries a word.
|
|
15
|
+
- **Status is never colour alone** — a state takes the word or the glyph beside it;
|
|
16
|
+
`Delta` ships the arrow for exactly this reason.
|
|
17
|
+
|
|
18
|
+
## Geometry
|
|
19
|
+
|
|
20
|
+
- A control is 8px (`--r-control`), a card 16px (`--r-card`), the frame 20.8px
|
|
21
|
+
(`--r-frame`), the badge a pill. The frame's window is concentric:
|
|
22
|
+
`calc(var(--r-frame) - var(--frame-inset))`.
|
|
23
|
+
- The nav is 65px, static, unfrosted, in the field's own colour.
|
|
24
|
+
|
|
25
|
+
## Elevation
|
|
26
|
+
|
|
27
|
+
- A card is ringed (`--ring-card`); a control is lit (`--lit-action` /
|
|
28
|
+
`--lit-quiet`); the frame gets the one big drop (`--shadow-frame`). There is no
|
|
29
|
+
fourth shadow.
|
|
30
|
+
|
|
31
|
+
## The machine
|
|
32
|
+
|
|
33
|
+
- Terminal, CLI and agent surfaces take `--machine-*` and ignore the theme. Their
|
|
34
|
+
tokens never reach a card, and the caret never leaves them.
|
|
35
|
+
|
|
36
|
+
## Motion
|
|
37
|
+
|
|
38
|
+
- 0.2s for anything a pointer caused; press scales to 0.95 over 0.15s; demos narrate
|
|
39
|
+
themselves on double-digit clocks. Nothing is scroll-driven. Under reduced motion a
|
|
40
|
+
demo shows its final frame, not its hidden first one.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @sheleg-design/test-drive
|
|
2
|
+
|
|
3
|
+
The React reference kit for the **Test-drive** style pack — warm paper and white cards
|
|
4
|
+
where the set piece is the live product running inside drawn browser chrome, and a
|
|
5
|
+
founder's hand annotates the tour.
|
|
6
|
+
|
|
7
|
+
The pack document is the source of truth for every value here:
|
|
8
|
+
`plugins/sheleg-design/skills/sheleg-design/styles/test-drive.md`. This kit is what
|
|
9
|
+
those values look like when they are built, and `src/styles.css` opens with the pack's
|
|
10
|
+
token layer copied byte for byte — never transcribed.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install && npm run build # tsc only; there is no bundler in this kit
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What ships
|
|
17
|
+
|
|
18
|
+
**The spine**, identical in name, props and types across every SHELEG kit, so switching
|
|
19
|
+
packs swaps identity rather than API: `Button`, `Card`, `Chip`, `Stat`, `Heading`,
|
|
20
|
+
`Rule`.
|
|
21
|
+
|
|
22
|
+
**The signature**, this pack's own: `DemoFrame` (the browser chrome around a running
|
|
23
|
+
interior — the signature element), `Annotation` (the hand), `Marker` (the
|
|
24
|
+
highlighter), `Delta` (a vital sign's movement), `Machine` + `Caret` (the quarantined
|
|
25
|
+
terminal), `ThinkingDots` (the machine's loader), `Field` (and the `td-join` pair),
|
|
26
|
+
`NavBar`, `Empty`.
|
|
27
|
+
|
|
28
|
+
## The three rules a generator loses first
|
|
29
|
+
|
|
30
|
+
1. The coral splits: `--accent` lights, fills and blinks; `--action` carries every
|
|
31
|
+
body-size word. `#ffffff` on `#e16540` is 3.42:1 — that is why both exist.
|
|
32
|
+
2. The lit shadow belongs to controls; a card takes `--ring-card` and nothing else.
|
|
33
|
+
3. The `DemoFrame` interior runs. A screenshot inside the chrome is `showroom`'s
|
|
34
|
+
exhibit, not this pack's proof.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/test-drive",
|
|
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,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The founder's hand: `--font-hand` (Fuzzy Bubbles) at `--t-label`, in `--ink-mute` or
|
|
6
|
+
— for an offer — `--action`. It annotates from the margin, always beside or above the
|
|
7
|
+
thing it points at. It never announces: no headings, no controls, no body copy in the
|
|
8
|
+
hand.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<Annotation>Interactive demo</Annotation>
|
|
12
|
+
<Annotation tone="accent">2 months free</Annotation>
|
|
13
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface AnnotationProps {
|
|
4
|
+
/** `accent` writes in --action; default writes in --ink-mute. */
|
|
5
|
+
tone?: 'mute' | 'accent';
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Annotation({ tone = 'mute', children, className }: AnnotationProps) {
|
|
11
|
+
return (
|
|
12
|
+
<span
|
|
13
|
+
className={['td-annotation', `td-annotation--${tone}`, className]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(' ')}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</span>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Actions
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Every control is an 8px-radius block at `--control-h` (48px) with `--control-px` of
|
|
6
|
+
horizontal padding, 16px at `--weight-control`. `sm` is `--control-h-sm` (32px) — the
|
|
7
|
+
nav's measured pair, and the reason `--tap-min` exists as a correction.
|
|
8
|
+
|
|
9
|
+
`primary` fills with `--action`, labels in `--on-action` at 4.94:1 on `#c04a28`, and is
|
|
10
|
+
lit by `--lit-action` — the four-layer coral recipe. Hover moves the fill to
|
|
11
|
+
`--action-hover`, the resolved form of the reference's own
|
|
12
|
+
`color-mix(in oklab, fill 90%, #000)`. `secondary` is `--surface` with a `--hairline`
|
|
13
|
+
border and `--lit-quiet`; hover brightens the glow to `--lit-quiet-hover`, and the
|
|
14
|
+
control itself does not move. `ghost` is transparent until hover fills it with
|
|
15
|
+
`--hairline`.
|
|
16
|
+
|
|
17
|
+
Press scales to `--press-scale` (0.95) over `--dur-press`. Focus is one mechanism: a
|
|
18
|
+
`--focus-w` outline in `--focus-color` at `--focus-offset`.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<Button>Add my website</Button>
|
|
22
|
+
<Button variant="secondary" size="sm">Log in</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={['td-btn', `td-btn--${variant}`, `td-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
|
+
A card is `--surface` at `--r-card` with `--ring-card` — a 1px 6% ring plus a soft
|
|
6
|
+
1–2px drop, no border property, and never the lit shadow, which belongs to controls.
|
|
7
|
+
The title is `--t-h3` at `--weight-display`; meta sits right-aligned in `--ink-mute`.
|
|
8
|
+
Body copy holds `--measure-body`.
|
|
9
|
+
|
|
10
|
+
Cards do not hover, do not nest, and never paint a `--machine-*` token.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Card title="Web analytics" meta="live">…</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={['td-card', className].filter(Boolean).join(' ')}>
|
|
15
|
+
{head && (
|
|
16
|
+
<div className="td-card__head">
|
|
17
|
+
{title !== undefined && <h3 className="td-card__title">{title}</h3>}
|
|
18
|
+
{meta !== undefined && <span className="td-card__meta">{meta}</span>}
|
|
19
|
+
</div>
|
|
20
|
+
)}
|
|
21
|
+
<div className="td-card__body">{children}</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A pill at `--r-badge` on `--surface` with a `--hairline` border, `--t-label` at
|
|
6
|
+
`--weight-control`. `accent` fills with `--accent-wash` and writes in `--action`;
|
|
7
|
+
`selected` moves the border and label to `--action`. A chip never carries a status
|
|
8
|
+
colour — states live in `Delta` and the word beside it.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<Chip>codefa.st</Chip>
|
|
12
|
+
<Chip tone="accent" selected>Last 30 days</Chip>
|
|
13
|
+
```
|
|
@@ -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
|
+
'td-chip',
|
|
15
|
+
`td-chip--${tone}`,
|
|
16
|
+
selected ? 'td-chip--selected' : undefined,
|
|
17
|
+
className,
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ')}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A vital-sign's movement: the number first, the arrow second, the hue third — `--good`
|
|
6
|
+
up, `--danger` down, and never the colour alone, which is why the arrow ships inside
|
|
7
|
+
the component. Sits in a `Stat`'s source slot.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<Stat value="83%" label="Bounce rate" source={<Delta direction="up">1%</Delta>} />
|
|
11
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DeltaProps {
|
|
4
|
+
/** The arrow is the second encoding; the number beside it is the first. */
|
|
5
|
+
direction: 'up' | 'down';
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Delta({ direction, children, className }: DeltaProps) {
|
|
11
|
+
return (
|
|
12
|
+
<span
|
|
13
|
+
className={['td-delta', `td-delta--${direction}`, className]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(' ')}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
<span aria-hidden="true" className="td-delta__arrow">
|
|
19
|
+
{direction === 'up' ? '\u2191' : '\u2193'}
|
|
20
|
+
</span>
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**The signature element.** Browser chrome at `--r-frame` in `--machine-chrome` — three
|
|
6
|
+
traffic-light dots in the measured `--frame-dot-*` hues, the real address centered in
|
|
7
|
+
`--on-machine` — around a running interior on `--surface`, with `--shadow-frame`
|
|
8
|
+
under it. The window is concentric: `calc(var(--r-frame) - var(--frame-inset))`.
|
|
9
|
+
|
|
10
|
+
The interior must run — an iframe of the live product, or a self-narrating demo built
|
|
11
|
+
from `Machine`, `Caret` and `ThinkingDots`. A frozen screenshot inside this chrome is
|
|
12
|
+
the one thing the pack bans outright. Anchor an `Annotation` outside the frame's top
|
|
13
|
+
corner to say what the reader should notice.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<DemoFrame url="https://datafa.st/codefa.st">
|
|
17
|
+
<iframe src="/demo" title="Live demo" />
|
|
18
|
+
</DemoFrame>
|
|
19
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DemoFrameProps {
|
|
4
|
+
/** The address shown in the chrome bar — a real one; the frame quotes a live app. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** The running interior — an iframe, or a self-narrating demo. Never a still. */
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function DemoFrame({ url, children, className }: DemoFrameProps) {
|
|
12
|
+
return (
|
|
13
|
+
<figure className={['td-frame', className].filter(Boolean).join(' ')}>
|
|
14
|
+
<div className="td-frame__chrome">
|
|
15
|
+
<span className="td-frame__dots" aria-hidden="true">
|
|
16
|
+
<i className="td-frame__dot td-frame__dot--red" />
|
|
17
|
+
<i className="td-frame__dot td-frame__dot--amber" />
|
|
18
|
+
<i className="td-frame__dot td-frame__dot--green" />
|
|
19
|
+
</span>
|
|
20
|
+
<span className="td-frame__url">{url}</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div className="td-frame__window">{children}</div>
|
|
23
|
+
</figure>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
An empty screen is a state, not an absence: a `--surface` card holding one 16px/500
|
|
6
|
+
line, one sentence in `--ink-mute`, and one quiet control that starts the demo — this
|
|
7
|
+
pack always has a demo to offer. A zero in a metric renders as a figure in its tile,
|
|
8
|
+
the way the reference's dashboard ships "Online 0".
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<Empty title="No data yet" hint="Add your website to start the tour."
|
|
12
|
+
action={<Button variant="secondary">Try the live demo</Button>} />
|
|
13
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface EmptyProps {
|
|
4
|
+
/** One line in --ink, 16px/500. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** One sentence in --ink-mute. */
|
|
7
|
+
hint?: string;
|
|
8
|
+
/** One quiet control that starts the demo — this pack always has one to offer. */
|
|
9
|
+
action?: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Empty({ title, hint, action, className }: EmptyProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div className={['td-empty', className].filter(Boolean).join(' ')}>
|
|
16
|
+
<span className="td-empty__title">{title}</span>
|
|
17
|
+
{hint !== undefined && <span className="td-empty__hint">{hint}</span>}
|
|
18
|
+
{action !== undefined && <span className="td-empty__action">{action}</span>}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Actions
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A `--control-h` input on `--surface` with a `--hairline-strong` border at
|
|
6
|
+
`--r-control`, placeholder in `--ink-mute`. Focus is the pack's one focus contract.
|
|
7
|
+
Invalid moves the border to `--danger` **and** shows the message — never the colour
|
|
8
|
+
alone.
|
|
9
|
+
|
|
10
|
+
The hero's joined pair wraps a `Field` and a `Button` in `td-join`: one 8px outer
|
|
11
|
+
radius, square seam edges — the measured shape of the reference's URL capture.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<div className="td-join">
|
|
15
|
+
<Field placeholder="website.com" />
|
|
16
|
+
<Button>Add my website</Button>
|
|
17
|
+
</div>
|
|
18
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface FieldProps {
|
|
2
|
+
label?: string;
|
|
3
|
+
placeholder?: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
onChange?: (value: string) => void;
|
|
6
|
+
invalid?: boolean;
|
|
7
|
+
/** Shown under the field when invalid — the colour never carries the state alone. */
|
|
8
|
+
message?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Field({
|
|
13
|
+
label,
|
|
14
|
+
placeholder,
|
|
15
|
+
value,
|
|
16
|
+
onChange,
|
|
17
|
+
invalid = false,
|
|
18
|
+
message,
|
|
19
|
+
className,
|
|
20
|
+
}: FieldProps) {
|
|
21
|
+
return (
|
|
22
|
+
<label className={['td-field', className].filter(Boolean).join(' ')}>
|
|
23
|
+
{label !== undefined && <span className="td-field__label">{label}</span>}
|
|
24
|
+
<input
|
|
25
|
+
className={['td-field__input', invalid ? 'td-field__input--invalid' : undefined]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join(' ')}
|
|
28
|
+
placeholder={placeholder}
|
|
29
|
+
value={value}
|
|
30
|
+
aria-invalid={invalid || undefined}
|
|
31
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
32
|
+
/>
|
|
33
|
+
{invalid && message !== undefined && (
|
|
34
|
+
<span className="td-field__message">{message}</span>
|
|
35
|
+
)}
|
|
36
|
+
</label>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
One face announces everything: DM Sans at `--weight-display` (800), tracked
|
|
6
|
+
`--track-display` (−0.025em) at every display size. Level 1 is the display — 36px
|
|
7
|
+
below 48rem, 60px from it, stepping rather than flowing, at `--lh-display` (1). Level
|
|
8
|
+
2 steps 30 → 48; level 3 holds 36. The hand (`Annotation`) never takes a heading's
|
|
9
|
+
job.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Heading level={1}>Revenue-first analytics</Heading>
|
|
13
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps {
|
|
4
|
+
/** 1 = display (60px), 2 = section (48px), 3 = card head (36px). */
|
|
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={['td-heading', `td-heading--${level}`, className].filter(Boolean).join(' ')}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</Tag>
|
|
18
|
+
);
|
|
19
|
+
}
|