sheleg-design-skill 1.18.0 → 1.19.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 +74 -0
- package/README.md +5 -4
- package/bin/cli.js +5 -3
- package/cursor/rules/sheleg-design.mdc +4 -1
- package/kits/datasheet/.design-sync/config.json +14 -0
- package/kits/datasheet/.design-sync/conventions.md +72 -0
- package/kits/datasheet/README.md +16 -0
- package/kits/datasheet/package.json +29 -0
- package/kits/datasheet/src/Button.md +17 -0
- package/kits/datasheet/src/Button.tsx +33 -0
- package/kits/datasheet/src/Card.md +15 -0
- package/kits/datasheet/src/Card.tsx +24 -0
- package/kits/datasheet/src/Cell.md +16 -0
- package/kits/datasheet/src/Cell.tsx +23 -0
- package/kits/datasheet/src/Chip.md +16 -0
- package/kits/datasheet/src/Chip.tsx +25 -0
- package/kits/datasheet/src/Heading.md +14 -0
- package/kits/datasheet/src/Heading.tsx +19 -0
- package/kits/datasheet/src/Instrument.md +21 -0
- package/kits/datasheet/src/Instrument.tsx +33 -0
- package/kits/datasheet/src/MonoBadge.md +16 -0
- package/kits/datasheet/src/MonoBadge.tsx +12 -0
- package/kits/datasheet/src/Rule.md +14 -0
- package/kits/datasheet/src/Rule.tsx +18 -0
- package/kits/datasheet/src/Stat.md +13 -0
- package/kits/datasheet/src/Stat.tsx +17 -0
- package/kits/datasheet/src/StatusCell.md +17 -0
- package/kits/datasheet/src/StatusCell.tsx +24 -0
- package/kits/datasheet/src/ValueChip.md +14 -0
- package/kits/datasheet/src/ValueChip.tsx +20 -0
- package/kits/datasheet/src/index.ts +25 -0
- package/kits/datasheet/src/styles.css +577 -0
- package/kits/datasheet/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/MOBILE_SURFACES.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +5 -4
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +2 -2
- package/plugins/sheleg-design/skills/sheleg-design/styles/blueprint.md +10 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/datasheet.md +533 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/field-notes.md +14 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/instrument-console.md +11 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +11 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +11 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/datasheet.css +268 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,80 @@ 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.19.0] - 2026-08-12
|
|
8
|
+
|
|
9
|
+
A fourteenth style pack, and the Refero style card it started from was wrong in
|
|
10
|
+
four measurable places.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`datasheet`** — the fourteenth pack, extracted from `fingerprint.com` off its
|
|
15
|
+
live computed styles and its shipped stylesheet, which declares 140 custom
|
|
16
|
+
properties including ten-step ramps for nine hues. An off-white spec sheet, one
|
|
17
|
+
vivid orange, Inter over JetBrains Mono, a concentric radius family from 16 down
|
|
18
|
+
to 2 — and a **live instrument ruled out of hairlines at radius 0 which re-skins
|
|
19
|
+
itself dark when it detects the reader is hiding**. For B2B SaaS whose product is
|
|
20
|
+
a verdict about the visitor, the request or the device: fraud and bot detection,
|
|
21
|
+
device intelligence, identity, API products sold on their payload. Widened
|
|
22
|
+
contract, a two-theme token layer, a full reference kit, and reciprocal forks
|
|
23
|
+
written into `field-notes`, `instrument-console`, `showroom`, `blueprint` and
|
|
24
|
+
`scoreboard`.
|
|
25
|
+
- **The dark half is a state, not a theme.** `[data-state="alarm"]` — the token
|
|
26
|
+
`--dash-dark` appears in 97 rules on the reference and every one of them is an
|
|
27
|
+
incognito selector; 134 rules in total re-skin the instrument when it detects
|
|
28
|
+
evasion. Wiring that surface to a user preference is banned in the pack, because
|
|
29
|
+
it destroys the only idea the pack has.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **`validate_counted_claims()` did not read the three manifests, and both carried
|
|
34
|
+
a stale count.** `.claude-plugin/marketplace.json` said *"twelve pluggable style
|
|
35
|
+
packs"* above a list of thirteen for two releases, and `package.json` said
|
|
36
|
+
*"thirteen"* on the day the fourteenth landed. Names in those files were already
|
|
37
|
+
checked; the number beside the names was not, because the source list was
|
|
38
|
+
all-markdown plus two scripts. The list now includes both plugin manifests and
|
|
39
|
+
`package.json`, watched saying no against a planted `eleven` in the real file and
|
|
40
|
+
again as a permanent self-test plant that derives its wrong number from whatever
|
|
41
|
+
the manifest currently claims.
|
|
42
|
+
- **Four corrections to the reference, recorded rather than applied silently.** Its
|
|
43
|
+
primary button sets white on `--orange-7` at **3.32:1**, so the pack's resting
|
|
44
|
+
fill moves one ramp step to `--orange-8` (5.34:1) and hovers to `--orange-9`
|
|
45
|
+
(9.02:1) — no colour invented, and the darkening direction kept. Its 8px mono
|
|
46
|
+
badge is set in `--gray-6` at **2.51:1** and the pack refuses that ink. Its `h1`
|
|
47
|
+
is pure black while its `body` is `#141415`, and the pack ships one ink. Its
|
|
48
|
+
`prefers-reduced-motion` block covers one group of hero animations out of roughly
|
|
49
|
+
twenty keyframe sets, and the token layer collapses the whole surface.
|
|
50
|
+
- **Eleven defects in the new pack, found by its own routing scenario and fixed
|
|
51
|
+
before the tag.** T24 ran both branches in fresh contexts — the positive branch
|
|
52
|
+
chose `datasheet`, the negative stayed on `field-notes` — and each was asked to
|
|
53
|
+
read its chosen pack and report defects. The sharpest: in the alarm state
|
|
54
|
+
`--danger` on its own tint measured **4.44:1**, in the one cell that state exists
|
|
55
|
+
to render (the tint moves to `--pink-10`, 6.24:1); and the focus ring at
|
|
56
|
+
`--accent` measured **2.85:1** on `--accent-wash`, the surface the pack itself
|
|
57
|
+
mandates for a selected cell (a new `--focus-color` is `--accent-deep` on paper).
|
|
58
|
+
Also fixed: an accent job list that contradicted the pack's own ban, a button
|
|
59
|
+
border rule with no token behind it, 54 of 118 token declarations carrying
|
|
60
|
+
neither MEASURED nor SELECTED, a "hard floor" argument that sat exactly on the
|
|
61
|
+
floor, a duration measured against the wrong ceiling, an empty state using an ink
|
|
62
|
+
the palette table forbids for content, and two type values outside the ramp. Full
|
|
63
|
+
table in `test/scenarios.md` under T24.
|
|
64
|
+
- **`SKILL.md` said "Six of the fourteen … The other seven answer all four."** Six
|
|
65
|
+
plus seven is thirteen against a fourteen-row table, and the pack left out of the
|
|
66
|
+
sentence was the one this release adds. It was this release's own count edit that
|
|
67
|
+
did it — **and the identical defect was found by a scenario agent in the previous
|
|
68
|
+
pack release**, fixed then as an instance. `validate_contract_split()` now derives
|
|
69
|
+
all three numbers from the table, watched saying no against a planted remainder
|
|
70
|
+
and shipped with a permanent self-test plant that reads whatever the paragraph
|
|
71
|
+
currently claims.
|
|
72
|
+
- **`SURFACE_COMPOSITION.md` said the accent role resolves to `--accent` in ten
|
|
73
|
+
packs.** True at twelve, silently wrong at thirteen, twelve at fourteen. Fixed by
|
|
74
|
+
hand and filed as **B-016**, because the phrase reaches no check: `in ten,` is
|
|
75
|
+
not followed by a counted noun.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- Ratchet floors raised to **1647 / 716 / 366** from 1507 / 603 / 352.
|
|
80
|
+
|
|
7
81
|
## [1.18.0] - 2026-08-12
|
|
8
82
|
|
|
9
83
|
### Changed
|
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 **fourteen 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
|
|
|
@@ -58,6 +58,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
58
58
|
| `maquette` | near-black table, cream ink and cream axonometric models, mono block labels, pale aqua that works as text, one offset shadow | enterprise data infrastructure sold to an architecture buyer |
|
|
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
|
+
| `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 |
|
|
61
62
|
|
|
62
63
|
Each pack locks palette, type, texture, motion tokens, signature motifs and
|
|
63
64
|
bans — and ships a `tokens/<pack>.css` to copy verbatim, so the agent never
|
|
@@ -132,7 +133,7 @@ skills.
|
|
|
132
133
|
| `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 |
|
|
133
134
|
| `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 |
|
|
134
135
|
| `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 |
|
|
135
|
-
| `styles/*.md` | The
|
|
136
|
+
| `styles/*.md` | The fourteen style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
136
137
|
| `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) |
|
|
137
138
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
138
139
|
|
|
@@ -203,7 +204,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
203
204
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
204
205
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
205
206
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
206
|
-
identical in all
|
|
207
|
+
identical in all fourteen kits, so switching packs swaps identity rather than API,
|
|
207
208
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
208
209
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
209
210
|
stops an agent inventing motion to fill the silence.
|
|
@@ -249,7 +250,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
249
250
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
250
251
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
251
252
|
it ships the right files is CI, which installs the bundle through **both**
|
|
252
|
-
installers and `diff -r`s the result against the source, then builds all
|
|
253
|
+
installers and `diff -r`s the result against the source, then builds all fourteen
|
|
253
254
|
kits.
|
|
254
255
|
|
|
255
256
|
`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/ fourteen 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
|
|
@@ -243,7 +243,9 @@ ${c("bold", "What it installs")}
|
|
|
243
243
|
(the product as the exhibit), blueprint (a drawing sheet,
|
|
244
244
|
zero radius), prism (one iridescent wash over mono body),
|
|
245
245
|
maquette (cream axonometric models on a dark table),
|
|
246
|
-
scoreboard (warm paper, pixel numerals, a dark ledger)
|
|
246
|
+
scoreboard (warm paper, pixel numerals, a dark ledger),
|
|
247
|
+
datasheet (an off-white spec sheet whose live instrument
|
|
248
|
+
goes dark when it catches the reader hiding) —
|
|
247
249
|
plus a ready-made token CSS per pack and
|
|
248
250
|
STYLE_PACK_TEMPLATE.md for authoring more
|
|
249
251
|
`);
|
|
@@ -325,7 +327,7 @@ function main() {
|
|
|
325
327
|
` ${c("dim", "SKILL.md")} the agent skill\n` +
|
|
326
328
|
` ${c("dim", "SHELEG_DESIGN.md")} the full reference\n` +
|
|
327
329
|
` ${c("dim", "MOTION_DOCTRINE.md")} whether to animate at all — read before any animation\n` +
|
|
328
|
-
` ${c("dim", "styles/")} style packs + token CSS (instrument-console / editorial-luxury / workbench / briefing-room / atrium / orchard / field-notes / cyclorama / showroom / blueprint / prism / maquette / scoreboard)\n\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` +
|
|
329
331
|
`Your Cursor / Claude agent can now discover the skill and build\n` +
|
|
330
332
|
`cinematic, scroll-driven pages — or style product UI (dashboards, admin,\n` +
|
|
331
333
|
`internal tools) from a standalone pack — on its principles.\n\n` +
|
|
@@ -30,7 +30,10 @@ static iridescent wash over mono body copy; maquette — enterprise data
|
|
|
30
30
|
infrastructure as a cream axonometric model on a near-black table;
|
|
31
31
|
scoreboard — warm paper and a dark ledger of dotted-leader rows whose
|
|
32
32
|
numbers are set in an aliased pixel face, for a product whose argument is
|
|
33
|
-
an accumulating number
|
|
33
|
+
an accumulating number; datasheet — an off-white spec
|
|
34
|
+
sheet whose focal element is a live instrument ruled at radius zero and
|
|
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);
|
|
34
37
|
otherwise follow the contract below (self-contained on purpose).
|
|
35
38
|
|
|
36
39
|
## Whether to animate at all — before how
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/datasheet",
|
|
3
|
+
"globalName": "ShelegDatasheet",
|
|
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,72 @@
|
|
|
1
|
+
# Datasheet — the contract this design system ships under
|
|
2
|
+
|
|
3
|
+
**Register.** Choose Datasheet for **B2B SaaS whose product is a verdict about
|
|
4
|
+
the visitor, the request or the device**: fraud and bot detection, device
|
|
5
|
+
intelligence, identity and verification, anti-abuse, payment risk, API products
|
|
6
|
+
sold on the payload they return. The page is a spec sheet, and the specimen is the
|
|
7
|
+
reader — the instrument shows *their* visitor id, *their* city, *their* IP, and a
|
|
8
|
+
verdict reached seconds ago. The fork people get wrong is against `field-notes`,
|
|
9
|
+
which shares the off-white paper, the hairlines and the one warm accent: there the
|
|
10
|
+
small mono type annotates a **source** so a claim can be checked (*how do you
|
|
11
|
+
know?*); here it carries a **reading** about the reader (*what did you get?*).
|
|
12
|
+
Build every screen against `var(--…)` and never a literal.
|
|
13
|
+
|
|
14
|
+
**The accent is a mark and a fill, not a voice.** `--accent` measures 3.17:1 on
|
|
15
|
+
the field: enough for a non-text mark and for large text at 24px and above, not
|
|
16
|
+
enough for anything at body size. Its jobs are one word of a display headline, the
|
|
17
|
+
mono visitor id, a 2px focus ring, a tick, and a wash on a selected cell. Where an
|
|
18
|
+
orange must carry a word at body size, use `--accent-deep` at 5.11:1. The primary
|
|
19
|
+
button fills with `--action` — one ramp step deeper than the brand orange, because
|
|
20
|
+
white on the brand step measures 3.32:1 and fails AA — and darkens to
|
|
21
|
+
`--action-hover` on press.
|
|
22
|
+
|
|
23
|
+
**The instrument is not a card, and its cells never round.** A card is
|
|
24
|
+
`--surface` at `--r-card` with the inset elevation pair. The instrument is a grid
|
|
25
|
+
whose walls are 1px `--rule` at `--r-cell`, which is **0**. Rounding those cells is
|
|
26
|
+
the single edit that turns this system into a generic SaaS page. Padding is
|
|
27
|
+
`--pad-cell`; every cell is a 9px uppercase label over an 11px value, and machine
|
|
28
|
+
output — ids, IPs, hashes, timestamps — takes the mono face.
|
|
29
|
+
|
|
30
|
+
**Radii are concentric and the arithmetic is real.** 16 outside, 8 inside, 12 on
|
|
31
|
+
cards, 6 on buttons, 4 on controls, 2 on a chip, 0 on the instrument. An inner
|
|
32
|
+
radius is the outer radius minus the padding between them: the frame carries
|
|
33
|
+
`--grid` of padding, so its shell resolves to `--r-inner`. Never the same radius
|
|
34
|
+
twice in a nest.
|
|
35
|
+
|
|
36
|
+
**Elevation is inset, never dropped.** Cards and the frame carry the measured
|
|
37
|
+
two-line inset — a pale top edge, a darker bottom one. Nothing casts a downward
|
|
38
|
+
shadow, nothing lifts on hover, nothing scales.
|
|
39
|
+
|
|
40
|
+
**Status is never by colour alone.** Every status cell tints its whole background
|
|
41
|
+
with the matching `--*-weak` and **writes the verdict out**. The four statuses do
|
|
42
|
+
not separate under dichromacy — success and warning are 3.7 apart under
|
|
43
|
+
deuteranopia against a floor of 8 — so the tint is emphasis and the word is the
|
|
44
|
+
meaning. No bare dots, no traffic lights, and never a hue swap to signal disabled:
|
|
45
|
+
a disabled cell that changes colour reads as a different verdict. Disabled is 0.7
|
|
46
|
+
opacity with pointer events off.
|
|
47
|
+
|
|
48
|
+
**The dark surface is a state, not a theme.** `[data-state="alarm"]` exists
|
|
49
|
+
because the instrument **detected** something — on the reference, that the reader
|
|
50
|
+
is in incognito. Never wire it to a user preference, a toggle or
|
|
51
|
+
`prefers-color-scheme`; that destroys the one idea this system has. In the alarm
|
|
52
|
+
state the accent steps lighter, text on the accent flips to ink, and the status set
|
|
53
|
+
is selected rather than measured, danger from the pink ramp because a red cannot
|
|
54
|
+
sit beside an orange accent on a dark field.
|
|
55
|
+
|
|
56
|
+
**Type.** Two families only: Inter and JetBrains Mono. No serif anywhere. The
|
|
57
|
+
display weight is **500, not 600** — at 48px with -0.0625em tracking, 500 reads as
|
|
58
|
+
engineered and 600 reads as an advertisement. Tracking runs both ways: negative
|
|
59
|
+
and steep above 36px, positive and wide below 12px. A 9px label at neutral tracking
|
|
60
|
+
is the fastest way to make this system look like a generic admin theme.
|
|
61
|
+
|
|
62
|
+
**Motion.** One curve, one measured base duration (`--dur-base`, the reference's
|
|
63
|
+
own single duration token), and one measured stagger of `--stagger` between the
|
|
64
|
+
words of a display headline. Nothing exceeds 300ms. Colour and opacity only —
|
|
65
|
+
never a layout property, and never `ease-in`. Under
|
|
66
|
+
`prefers-reduced-motion: reduce` every duration and the stagger go to zero and the
|
|
67
|
+
headline arrives already in place.
|
|
68
|
+
|
|
69
|
+
**Never fabricate a reading.** If the instrument cannot show live data, it shows a
|
|
70
|
+
labelled last-known one — the reference's own badge reads *this is a demo,
|
|
71
|
+
production accuracy will be higher*. A screen in this system that invents an IP
|
|
72
|
+
address has told its only lie in its loudest element.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @sheleg-design/datasheet
|
|
2
|
+
|
|
3
|
+
The React reference kit for the SHELEG **Datasheet** style pack — an off-white spec
|
|
4
|
+
sheet, a live instrument built from hairline cells at radius zero, and a dark alarm
|
|
5
|
+
state the instrument enters when it detects the reader is hiding.
|
|
6
|
+
|
|
7
|
+
It is generated from the pack, not authored beside it: `src/styles.css` opens with
|
|
8
|
+
`styles/tokens/datasheet.css` byte for byte, and the rules the design agent must
|
|
9
|
+
obey 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.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/datasheet",
|
|
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,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Actions
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
One accent fill per view, and in this pack that fill is `--action` — the
|
|
6
|
+
reference's own orange one ramp step deeper than its brand orange, because white
|
|
7
|
+
on the brand step measures 3.32:1 and does not clear AA. `secondary` is the
|
|
8
|
+
1px-bordered outline in `--accent-deep`; `ghost` is the bare navigation control
|
|
9
|
+
that changes ink and nothing else. Hover moves fill, border and colour over
|
|
10
|
+
`--dur-base`; nothing lifts, scales or gains a shadow. Disabled drops to 0.7
|
|
11
|
+
opacity with pointer events off, measured off the reference.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Button onClick={start}>Get started</Button>
|
|
15
|
+
<Button variant="secondary" onClick={contact}>Contact sales</Button>
|
|
16
|
+
<Button variant="ghost" size="sm" onClick={close}>Dismiss</Button>
|
|
17
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ButtonProps {
|
|
4
|
+
/** `primary` is the pack's default fill — at most one accent action 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={['ds-btn', `ds-btn--${variant}`, `ds-btn--${size}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The bounded surface for prose and features: `--surface` at `--r-card` with the
|
|
6
|
+
measured inset pair for elevation and no border. It is deliberately **not** the
|
|
7
|
+
instrument — the instrument is a hairline grid at radius 0 and never becomes a
|
|
8
|
+
card, because rounding a reading is the edit that turns this pack into a generic
|
|
9
|
+
SaaS page. `meta` sits on the right of the title row in the label style.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Card title="Account takeover" meta="12ms p95">
|
|
13
|
+
<Cell label="Signals" value="device, network, behaviour" />
|
|
14
|
+
</Card>
|
|
15
|
+
```
|
|
@@ -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={['ds-card', className].filter(Boolean).join(' ')}>
|
|
15
|
+
{head && (
|
|
16
|
+
<div className="ds-card__head">
|
|
17
|
+
{title !== undefined && <h3 className="ds-card__title">{title}</h3>}
|
|
18
|
+
{meta !== undefined && <span className="ds-card__meta">{meta}</span>}
|
|
19
|
+
</div>
|
|
20
|
+
)}
|
|
21
|
+
<div className="ds-card__body">{children}</div>
|
|
22
|
+
</section>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The pack's atom: a 9px uppercase label in `--ink-soft` above an 11px value in
|
|
6
|
+
`--ink`, inside a cell walled with 1px `--rule` at radius **0**. Padding is the
|
|
7
|
+
measured `10px 12px`. Set `mono` for anything a machine produced — an id, an IP,
|
|
8
|
+
a hash — because a proportional face makes two similar hashes look identical.
|
|
9
|
+
|
|
10
|
+
The value is never justified and never hyphenated: a broken IP address is a wrong
|
|
11
|
+
IP address.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Cell label="Geolocation">1 location</Cell>
|
|
15
|
+
<Cell label="Visitor ID" mono>JzzA01Muat9b30Sh60KJ</Cell>
|
|
16
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CellProps {
|
|
4
|
+
/** The 9px uppercase label. Always present: a value with no label is a number nobody can use. */
|
|
5
|
+
label: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/** Set for machine output — ids, IPs, hashes, timestamps. */
|
|
8
|
+
mono?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Cell({ label, children, mono = false, className }: CellProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={['ds-cell', className].filter(Boolean).join(' ')}>
|
|
15
|
+
<span className="ds-cell__label">{label}</span>
|
|
16
|
+
<span className={['ds-cell__value', mono ? 'ds-cell__value--mono' : undefined]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' ')}>
|
|
19
|
+
{children}
|
|
20
|
+
</span>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The small static label: 11px in the data face at `--r-chip`, 1px `--rule`,
|
|
6
|
+
padding of 2 by 8. Chips name things that are already true — a region, a plan, a
|
|
7
|
+
version — and never stand in for a button. `tone="accent"` is for the one value
|
|
8
|
+
that is the screen's subject; `selected` fills with `--accent-wash` behind an
|
|
9
|
+
accent edge, which is how the reference marks its active summary cell. A chip
|
|
10
|
+
carrying a verdict is a `ValueChip`; a chip carrying an action is a `Button`.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Chip>eu-central-1</Chip>
|
|
14
|
+
<Chip tone="accent">v3</Chip>
|
|
15
|
+
<Chip selected>Suspect only</Chip>
|
|
16
|
+
```
|
|
@@ -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
|
+
'ds-chip',
|
|
15
|
+
`ds-chip--${tone}`,
|
|
16
|
+
selected ? 'ds-chip--selected' : undefined,
|
|
17
|
+
className,
|
|
18
|
+
]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ')}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Three levels, all Inter at `--w-medium` — 500, not 600, which is the single
|
|
6
|
+
value a second-hand description of this reference gets wrong most often. Tracking
|
|
7
|
+
is negative and steep at the display size (-0.0625em at 48px) and relaxes with
|
|
8
|
+
the size. One word inside a heading may take `--accent` at `--w-semi`; weight and
|
|
9
|
+
colour arrive together, once, and never as a whole phrase.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Heading level={1}>Identify every visitor</Heading>
|
|
13
|
+
<Heading level={2}>What the API returns</Heading>
|
|
14
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps {
|
|
4
|
+
/** 1 = page title, 2 = section, 3 = sub-head. */
|
|
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={['ds-heading', `ds-heading--${level}`, className].filter(Boolean).join(' ')}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</Tag>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The pack's signature element: the frame that reads the visitor's own data back to
|
|
6
|
+
them. `--r-frame` on `--surface`, 8px of padding so an inner shell resolves to
|
|
7
|
+
`--r-inner` (16 − 8 = 8, which the reference proves on itself), and a grid of
|
|
8
|
+
cells whose walls are hairlines at radius 0.
|
|
9
|
+
|
|
10
|
+
`alarm` is the whole idea. On the reference, 134 rules re-skin this frame dark
|
|
11
|
+
when it detects the reader is in incognito — the argument completes itself with no
|
|
12
|
+
copy. It is a **state, not a theme**: drive it from what was detected, never from
|
|
13
|
+
a preference or a media query. Build the light state first and completely.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Instrument title="Hello, visitor JzzA01Muat9b30Sh60KJ" badge="This is real data">
|
|
17
|
+
<Cell label="Weekly visit summary">You visited 1 time</Cell>
|
|
18
|
+
<Cell label="IP address" mono>83.175.182.157</Cell>
|
|
19
|
+
<StatusCell label="Incognito" reading="success">Not detected</StatusCell>
|
|
20
|
+
</Instrument>
|
|
21
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface InstrumentProps {
|
|
4
|
+
/** The reading's subject line — an id, a request, a device. */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** The mono micro-label stating a condition about the data itself. */
|
|
7
|
+
badge?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The alarm state: the instrument re-skins itself dark because of what it
|
|
10
|
+
* DETECTED, never because a user asked for dark. Wiring this to a theme
|
|
11
|
+
* toggle or to prefers-color-scheme destroys the pack's one idea.
|
|
12
|
+
*/
|
|
13
|
+
alarm?: boolean;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Instrument({ title, badge, alarm = false, children, className }: InstrumentProps) {
|
|
19
|
+
return (
|
|
20
|
+
<section
|
|
21
|
+
className={['ds-instrument', className].filter(Boolean).join(' ')}
|
|
22
|
+
data-state={alarm ? 'alarm' : undefined}
|
|
23
|
+
>
|
|
24
|
+
{(title !== undefined || badge !== undefined) && (
|
|
25
|
+
<header className="ds-instrument__head">
|
|
26
|
+
{title !== undefined && <span className="ds-instrument__title">{title}</span>}
|
|
27
|
+
{badge !== undefined && <span className="ds-badge">{badge}</span>}
|
|
28
|
+
</header>
|
|
29
|
+
)}
|
|
30
|
+
<div className="ds-instrument__grid">{children}</div>
|
|
31
|
+
</section>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The uppercase mono micro-label, 8–11px at 0.09em in `--ink-muted`. It states a
|
|
6
|
+
condition about the *data* rather than about the product: *this is real data*,
|
|
7
|
+
*this is a demo, production accuracy will be higher*, *measuring*.
|
|
8
|
+
|
|
9
|
+
The reference sets it in `--gray-6`, which measures 2.51:1 on the field — below
|
|
10
|
+
even the 3:1 non-text floor, at the smallest size on the page. This pack refuses
|
|
11
|
+
that ink: a badge meant to be read takes `--ink-muted` at 5.06:1. Copying the 8px
|
|
12
|
+
size without the ink step is the trap.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<MonoBadge>This is real data</MonoBadge>
|
|
16
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface MonoBadgeProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function MonoBadge({ children, className }: MonoBadgeProps) {
|
|
9
|
+
return (
|
|
10
|
+
<span className={['ds-badge', className].filter(Boolean).join(' ')}>{children}</span>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The divider, and in this pack it is structural rather than decorative: a 1px
|
|
6
|
+
`--rule` hairline is what walls every cell of the instrument. `tone="strong"`
|
|
7
|
+
takes `--rule-strong` for the one heavier edge a layout is allowed. Adjacent
|
|
8
|
+
full-bleed bands on the reference carry a -1px margin so two touching hairlines
|
|
9
|
+
collapse into one, which is worth copying wherever bands meet.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Rule />
|
|
13
|
+
<Rule tone="strong" />
|
|
14
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface RuleProps {
|
|
2
|
+
tone?: 'hairline' | 'strong';
|
|
3
|
+
className?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function Rule({ tone = 'hairline', className }: RuleProps) {
|
|
7
|
+
return (
|
|
8
|
+
<hr
|
|
9
|
+
className={[
|
|
10
|
+
'ds-rule',
|
|
11
|
+
tone === 'strong' ? 'ds-rule--strong' : undefined,
|
|
12
|
+
className,
|
|
13
|
+
]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.join(' ')}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|