sheleg-design-skill 1.34.0 → 1.35.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 +116 -0
- package/README.md +6 -4
- package/bin/cli.js +9 -2
- package/cursor/rules/sheleg-design.mdc +9 -1
- package/kits/awning/.design-sync/config.json +14 -0
- package/kits/awning/.design-sync/conventions.md +34 -0
- package/kits/awning/README.md +15 -0
- package/kits/awning/package.json +29 -0
- package/kits/awning/src/Button.md +23 -0
- package/kits/awning/src/Button.tsx +33 -0
- package/kits/awning/src/Card.md +18 -0
- package/kits/awning/src/Card.tsx +23 -0
- package/kits/awning/src/Chip.md +22 -0
- package/kits/awning/src/Chip.tsx +21 -0
- package/kits/awning/src/FeatureRow.md +15 -0
- package/kits/awning/src/FeatureRow.tsx +21 -0
- package/kits/awning/src/Heading.md +15 -0
- package/kits/awning/src/Heading.tsx +17 -0
- package/kits/awning/src/PlanCard.md +19 -0
- package/kits/awning/src/PlanCard.tsx +40 -0
- package/kits/awning/src/ProductFrame.md +18 -0
- package/kits/awning/src/ProductFrame.tsx +20 -0
- package/kits/awning/src/Rule.md +13 -0
- package/kits/awning/src/Rule.tsx +15 -0
- package/kits/awning/src/Stat.md +12 -0
- package/kits/awning/src/Stat.tsx +17 -0
- package/kits/awning/src/index.ts +21 -0
- package/kits/awning/src/styles.css +363 -0
- package/kits/awning/tsconfig.json +20 -0
- package/kits/ledger/.design-sync/config.json +12 -0
- package/kits/ledger/.design-sync/conventions.md +58 -0
- package/kits/ledger/README.md +20 -0
- package/kits/ledger/package.json +29 -0
- package/kits/ledger/src/Button.md +20 -0
- package/kits/ledger/src/Button.tsx +33 -0
- package/kits/ledger/src/Card.md +21 -0
- package/kits/ledger/src/Card.tsx +24 -0
- package/kits/ledger/src/Chip.md +18 -0
- package/kits/ledger/src/Chip.tsx +25 -0
- package/kits/ledger/src/DataTable.md +25 -0
- package/kits/ledger/src/DataTable.tsx +70 -0
- package/kits/ledger/src/EmptyState.md +19 -0
- package/kits/ledger/src/EmptyState.tsx +47 -0
- package/kits/ledger/src/Heading.md +18 -0
- package/kits/ledger/src/Heading.tsx +19 -0
- package/kits/ledger/src/Kicker.md +16 -0
- package/kits/ledger/src/Kicker.tsx +16 -0
- package/kits/ledger/src/Rule.md +12 -0
- package/kits/ledger/src/Rule.tsx +18 -0
- package/kits/ledger/src/Seal.md +30 -0
- package/kits/ledger/src/Seal.tsx +34 -0
- package/kits/ledger/src/SegmentedControl.md +21 -0
- package/kits/ledger/src/SegmentedControl.tsx +41 -0
- package/kits/ledger/src/Stat.md +17 -0
- package/kits/ledger/src/Stat.tsx +17 -0
- package/kits/ledger/src/StatusDot.md +17 -0
- package/kits/ledger/src/StatusDot.tsx +32 -0
- package/kits/ledger/src/index.ts +27 -0
- package/kits/ledger/src/styles.css +702 -0
- package/kits/ledger/tsconfig.json +20 -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 +11 -7
- package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
- package/plugins/sheleg-design/skills/sheleg-design/styles/awning.md +278 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/ledger.md +387 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/paperclip.md +9 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +8 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +16 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/awning.css +196 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/ledger.css +233 -0
- package/plugins/sheleg-design/skills/sheleg-design/styles/workbench.md +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,122 @@ 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.35.0] - 2026-08-15
|
|
8
|
+
|
|
9
|
+
**The twenty-first pack, and two counts that had been wrong for eight releases.**
|
|
10
|
+
`ledger` was extracted from `basedash.com` — a warm cream console for a product
|
|
11
|
+
that answers questions about data. Adding it walked the library past twenty for
|
|
12
|
+
the first time, which is where two gates turned out to have been counting with a
|
|
13
|
+
table that stopped at twenty and a regex that read one modifier word.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`ledger` — style pack 21 (widened contract, standalone product UI).** Warm
|
|
18
|
+
cream field `#fcf9f5` under `#14100c` ink, elevation as a **1px hairline at 12%
|
|
19
|
+
ink and no shadow on any card**, the reference's ×1.25 radius ramp nested
|
|
20
|
+
concentrically (a 15px track with 4px of padding holds a 10px thumb), an **ink**
|
|
21
|
+
primary button, and a terracotta `#c2410c` that **never fills a control** — of
|
|
22
|
+
eleven accent-coloured elements on the reference, five are a 10px monospace
|
|
23
|
+
uppercase kicker and none is a button. 32px data rows in the system monospace;
|
|
24
|
+
the large figure on a stat tile is the UI face at 34px, not the mono, which is
|
|
25
|
+
what the reference actually does. Light `:root` plus a `[data-theme="dark"]`
|
|
26
|
+
twin whose alpha ramp is a different set of steps rather than the light one
|
|
27
|
+
inverted.
|
|
28
|
+
- **Signature element: the seal in a card's title row** — `Verified` /
|
|
29
|
+
`Inferred` / `Unverified`, linking to the proof. It is
|
|
30
|
+
`AI_PRODUCT_PATTERNS.md` §4's provenance tag applied to a card instead of a
|
|
31
|
+
span, and it maps onto three tokens the pack already ships, so implementing
|
|
32
|
+
it adds no hue.
|
|
33
|
+
- **`--warn` is derived and says so at the declaration**, per the template's
|
|
34
|
+
rule 3: the reference declares an amber in its theme layer and never paints a
|
|
35
|
+
warning. The step differs per theme because at amber-500 the *dark* accent and
|
|
36
|
+
the warning are 7.1 apart at full colour — one colour with two meanings, below
|
|
37
|
+
the hard floor. amber-300 is 16.2 apart and clears every dichromacy.
|
|
38
|
+
- **`--chart-1` … `--chart-5` are named for shadcn/ui**, so a Recharts series
|
|
39
|
+
reads the pack's chart ramp through `ChartConfig` with no adapter.
|
|
40
|
+
- **`kits/ledger`** — twelve components: the six-name spine plus `Seal`,
|
|
41
|
+
`Kicker`, `DataTable`, `SegmentedControl`, `StatusDot` and an `EmptyState` that
|
|
42
|
+
carries the capability and two runnable example questions.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **The counted-claims gate could not count past twenty.** `NUMBER_WORDS` stopped
|
|
47
|
+
there, so every correct "twenty-one packs" was read as **"one packs"** and
|
|
48
|
+
failed, in nine files at once. The table now runs to thirty, the alternation is
|
|
49
|
+
longest-first, and a lookbehind refuses a match that starts mid-compound. Two
|
|
50
|
+
planted-defect fixtures had the same hyphen blindness and one of them had
|
|
51
|
+
stopped mutating anything — a plant that changes nothing reports BROKEN, which
|
|
52
|
+
is how it was found.
|
|
53
|
+
- **A count with two modifier words was never read as a count at all.** The regex
|
|
54
|
+
allowed one of `visual `/`style `, and both manifests say "pluggable **visual
|
|
55
|
+
style** packs". Widening it to `*` immediately surfaced two stale numbers: the
|
|
56
|
+
plugin description said **twenty** on the day twenty-one shipped, and
|
|
57
|
+
`marketplace.json`'s own top-level description had said **thirteen** since the
|
|
58
|
+
thirteenth pack — eight releases, in the two files an agent host reads first.
|
|
59
|
+
- **The core-contract paragraph's two regexes matched `\w+`**, so a hyphenated
|
|
60
|
+
total failed to parse and the check reported the paragraph missing rather than
|
|
61
|
+
wrong.
|
|
62
|
+
|
|
63
|
+
### Known limitation
|
|
64
|
+
|
|
65
|
+
- `validate_pack_enumerations` tests membership by **substring**, so `ledger`
|
|
66
|
+
passed in four files that merely described `scoreboard` as carrying "a dark
|
|
67
|
+
ledger". The four were filled in by hand and are now genuinely exhaustive; the
|
|
68
|
+
check that would have caught it — a name in a context that routes to it — is
|
|
69
|
+
not written.
|
|
70
|
+
|
|
71
|
+
## [1.35.0] - 2026-08-15
|
|
72
|
+
|
|
73
|
+
**A twenty-second pack, and the reference it was asked for did not survive measurement.**
|
|
74
|
+
The request was "a pack in the style of taskip.net". Taskip turned out to be WordPress and
|
|
75
|
+
Elementor over a bought theme (Xilancer), carrying **three token systems that disagree** —
|
|
76
|
+
the theme's declared primary `#6074f6` appears nowhere on the rendered page, the visible
|
|
77
|
+
emerald lives in a second sheet, and a third set of values is Tailwind slate. Twelve radius
|
|
78
|
+
values, six shadows, seven weights, four font families, and `.3s` / `0.3s` / `300ms` written
|
|
79
|
+
as three different things. That is a page-builder output, not a vocabulary, and this library
|
|
80
|
+
exists on the premise that a pack is the reference's own vocabulary rather than a
|
|
81
|
+
reconstruction of one. It was declined as a source and the register was re-sourced instead.
|
|
82
|
+
|
|
83
|
+
### Added — `awning`, the twenty-second pack
|
|
84
|
+
|
|
85
|
+
Origin: <https://www.shopify.com>, read 2026-08-15 from the served HTML plus its six linked
|
|
86
|
+
stylesheets. Chosen after measuring five candidates on the same axes; it was the only one
|
|
87
|
+
that paired the register with a real system.
|
|
88
|
+
|
|
89
|
+
- **The accent is black, and it is a resolved chain rather than a stylistic absence.**
|
|
90
|
+
`--color-component-button-primary-bg` → `--color-theme-bg-cta` → `#000`, with hover, active
|
|
91
|
+
and disabled declared beside it. No hue reaches the chrome at all, which is what leaves
|
|
92
|
+
every colour on the page belonging to the product screenshot inside it.
|
|
93
|
+
- **A three-tier token layer** — primitives, semantic roles, per-component states — so the
|
|
94
|
+
pack ships the indirection rather than the resolved values. `--radius-component-button:
|
|
95
|
+
var(--radius-theme-full)` is the only place the system says *why* a button is a pill.
|
|
96
|
+
- **420 and 550, and no 700 anywhere.** A variable grotesque used as one. Setting a heading
|
|
97
|
+
in 700 here is not a small deviation; it is the one number the system was built to avoid.
|
|
98
|
+
- **Tracking crosses zero inside one family** — negative on the display ramp, positive on the
|
|
99
|
+
body ramp, crossover around 1.375rem — where most packs in this library split tracking
|
|
100
|
+
across two faces.
|
|
101
|
+
- **Leading ships paired with size in `rem`**, not as a ratio, and the ratio *changes* down
|
|
102
|
+
the ramp: 1.08 at display, 1.12 at t2, 1.30 at t7.
|
|
103
|
+
- **One shadow, three layers** — ambient, contact, and a `0 0 2px` hairline edge that is what
|
|
104
|
+
stops a card dissolving on pure white. That third layer is the one people drop when they
|
|
105
|
+
copy a shadow by eye.
|
|
106
|
+
- Six Gotchas, three of them defects in the reference: `--ink-faint` at **4.40:1 on the
|
|
107
|
+
system's own second field**; three eases named as tokens against **ten unnamed inline
|
|
108
|
+
durations**; and `ease-in` shipped as a token while the doctrine bans it in UI.
|
|
109
|
+
- Routed through all seven surfaces the validator checks, mirrored to `.cursor`, added to
|
|
110
|
+
`install.sh`, and shipped with `kits/awning` — the six-name spine on the canonical API plus
|
|
111
|
+
`ProductFrame`, `PlanCard` and `FeatureRow`.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- `paperclip` and `showroom` now link **back** to `awning`. The validator caught the one-way
|
|
116
|
+
fork: a neighbour reference that only points one direction is a dead end for anyone who
|
|
117
|
+
reaches the other pack first.
|
|
118
|
+
- Nine stated counts moved from twenty-one to twenty-two across `SKILL.md`, `README.md`,
|
|
119
|
+
`package.json`, both manifests, `bin/cli.js`, `MOBILE_SURFACES.md`, `SURFACE_COMPOSITION.md`
|
|
120
|
+
and `DESIGN_SYNC_BRIDGE.md`, plus core-contract packs from six to seven. Every one was
|
|
121
|
+
caught by the gate rather than remembered.
|
|
122
|
+
|
|
7
123
|
## [1.34.0] - 2026-08-15
|
|
8
124
|
|
|
9
125
|
**The pack was applied correctly and the result still read as somebody else's typography.**
|
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 **twenty locked style
|
|
14
|
+
decides whether to animate before it decides how, and **twenty-two 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
|
|
|
@@ -65,6 +65,8 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
|
|
|
65
65
|
| `ora` | a warm coal field with cream ink and **no third hue** — the accent is the inverted field, so the one solid object on a page is the one meant to be pressed — a serif doing the sans job over a monospace that carries every machine fact, a terminal surface cut *below* the page plane, a six-step verdict ramp, and dark as the default theme rather than the option | products whose output is **a machine's verdict about the reader** — agent-readiness and crawlability scores, SEO and answer-engine audits, agent-run traces, MCP and protocol surfaces, bot observability |
|
|
66
66
|
| `tenor` | warm paper with **zero radius and zero shadow anywhere**, a single hairline weight assembling every lattice, and one orange that exists only on hover and on focus — so the page screenshots with no colour in it at all — a sans held at weight 400 and tracked negative against a mono tracked positive, display at a line-height below one in an eight-to-twelve-character measure, and proof delivered as silent looping video in a 1px rectangle | products arguing a **management thesis** — that a new kind of thing has to be run like an existing organisation: AI-workforce and agent-operations platforms, autonomous back-office, revenue and sales operations, sold to the director who will have to manage it |
|
|
67
67
|
| `paperclip` | neutral coal with **no functional colour at all** — every control monochrome, elevation made of hairlines, and the whole chromatic budget spent on a curtain of 96 gradient capsules and twelve gradient section badges that cannot be clicked — a tight grotesque over a plain one over a monospace, and the capsule as the shape of everything from a button to a 10 × 20 schedule tick | products that ask a person to **run something that runs itself** — agent teams and orchestrators, autonomous back-office, schedulers, job runners, budget-governed compute |
|
|
68
|
+
| `awning` | white forecourt where **the accent is black** and no hue reaches the chrome at all; a pill whose radius is a declared component token, one variable grotesque at **420 / 550 with no 700**, two rule weights and a single three-layer shadow | commerce and platform front doors — the surface that sells a system other businesses will run their storefront, payroll, billing or logistics on |
|
|
69
|
+
| `ledger` | warm cream paper where elevation is a **1px hairline at 12% ink** and no card casts a shadow, radii of 7.5/10/15/20 nested concentrically, an **ink** primary button, and a terracotta accent forbidden from filling any control — it labels, as a 10px monospace uppercase kicker — over 32px data rows, with a seal on every card stating how its number is known | the console of a product that answers questions **about data** — AI analysts, BI surfaces, query workspaces, agents that read a warehouse and write back a figure |
|
|
68
70
|
|
|
69
71
|
Each pack locks palette, type, texture, motion tokens, signature motifs and
|
|
70
72
|
bans — and ships a `tokens/<pack>.css` to copy verbatim, so the agent never
|
|
@@ -139,7 +141,7 @@ skills.
|
|
|
139
141
|
| `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 |
|
|
140
142
|
| `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 |
|
|
141
143
|
| `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 |
|
|
142
|
-
| `styles/*.md` | The twenty style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
144
|
+
| `styles/*.md` | The twenty-two style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
|
|
143
145
|
| `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) |
|
|
144
146
|
| `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
|
|
145
147
|
|
|
@@ -210,7 +212,7 @@ cd ./ds-workbench && npm install && npm run build
|
|
|
210
212
|
then `/design-sync` in that directory, from Claude Code. Three layers cross: the
|
|
211
213
|
pack's **bans** as the design system's own README, `styles.css` built from
|
|
212
214
|
`tokens/<pack>.css` verbatim, and the components — a six-name spine that is
|
|
213
|
-
identical in all twenty kits, so switching packs swaps identity rather than API,
|
|
215
|
+
identical in all twenty-two kits, so switching packs swaps identity rather than API,
|
|
214
216
|
plus each pack's signature parts. **Motion does not cross**, exactly as it does
|
|
215
217
|
not cross into Figma: a kit is the static half of a pack, and saying so is what
|
|
216
218
|
stops an agent inventing motion to fill the silence.
|
|
@@ -256,7 +258,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
|
|
|
256
258
|
One honest limit: the npx installer is checked by asserting its runtime bundle
|
|
257
259
|
walker exists, not by reading a file list — it has none by design. What proves
|
|
258
260
|
it ships the right files is CI, which installs the bundle through **both**
|
|
259
|
-
installers and `diff -r`s the result against the source, then builds all twenty
|
|
261
|
+
installers and `diff -r`s the result against the source, then builds all twenty-two
|
|
260
262
|
kits.
|
|
261
263
|
|
|
262
264
|
`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/ twenty style packs — instrument-console (dark console),
|
|
237
|
+
styles/ twenty-two 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
|
|
@@ -258,7 +258,14 @@ ${c("bold", "What it installs")}
|
|
|
258
258
|
on hover, and proof delivered as silent looping video),
|
|
259
259
|
paperclip (neutral coal with no functional colour at all
|
|
260
260
|
and one curtain of gradient capsules that cannot be
|
|
261
|
-
clicked, for products that run themselves)
|
|
261
|
+
clicked, for products that run themselves),
|
|
262
|
+
ledger (warm cream paper ruled by a hairline at 12% ink,
|
|
263
|
+
an ink primary button, a terracotta that only labels, and
|
|
264
|
+
a seal on every card saying how its number is known),
|
|
265
|
+
awning (a white forecourt where the accent is black and
|
|
266
|
+
no hue reaches the chrome, a pill whose radius is a
|
|
267
|
+
declared component token, and one variable grotesque at
|
|
268
|
+
420/550 with no 700) —
|
|
262
269
|
plus a ready-made token CSS per pack and
|
|
263
270
|
STYLE_PACK_TEMPLATE.md for authoring more
|
|
264
271
|
`);
|
|
@@ -56,7 +56,15 @@ existing organisation; paperclip — neutral coal with no functional colour
|
|
|
56
56
|
anywhere, every control monochrome and elevation made of hairlines, the whole
|
|
57
57
|
chromatic budget spent on a curtain of gradient capsules and a row of gradient
|
|
58
58
|
section badges that cannot be clicked, for products that ask a person to run
|
|
59
|
-
something that runs itself);
|
|
59
|
+
something that runs itself); ledger — warm cream paper where elevation is a
|
|
60
|
+
hairline at 12% ink and no card casts a shadow, an ink primary button and a
|
|
61
|
+
terracotta that never fills a control but labels in 10px monospace uppercase,
|
|
62
|
+
over 32px data rows, with a seal on every card saying how its number is known,
|
|
63
|
+
for the console of a product that answers questions about data);
|
|
64
|
+
awning — a white forecourt where the accent is black and no hue reaches the
|
|
65
|
+
chrome at all, a pill whose radius is a declared component token, one variable
|
|
66
|
+
grotesque at 420/550 with no 700, two rule weights and a single three-layer
|
|
67
|
+
shadow, for commerce and platform front doors);
|
|
60
68
|
otherwise follow the contract below (self-contained on purpose).
|
|
61
69
|
|
|
62
70
|
## Whether to animate at all — before how
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pkg": "@sheleg-design/awning",
|
|
3
|
+
"globalName": "ShelegAwning",
|
|
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,34 @@
|
|
|
1
|
+
# Awning — the rules this design system is built on
|
|
2
|
+
|
|
3
|
+
A pure white forecourt for a product other businesses will run their own
|
|
4
|
+
business on. **The accent is black.** No hue reaches the chrome at all, which is
|
|
5
|
+
what leaves every colour on the page belonging to the thing being sold.
|
|
6
|
+
Extracted from a production reference (shopify.com, 2026-08-15) whose delivered
|
|
7
|
+
CSS carries a real three-tier token system; the full pack, with every
|
|
8
|
+
measurement and every trap, is `styles/awning.md` in the sheleg-design skill.
|
|
9
|
+
|
|
10
|
+
**This is the static half of a pack.** Motion does not cross this boundary:
|
|
11
|
+
build screens from these components, never a scroll narrative around them.
|
|
12
|
+
|
|
13
|
+
## Bans — what this system never does
|
|
14
|
+
|
|
15
|
+
- **No accent hue.** Not on a button, a link, a tab, a badge or a focus ring.
|
|
16
|
+
The primary action is black; the moment chrome takes a colour, the screenshot
|
|
17
|
+
inside it stops being the only colourful thing on the page.
|
|
18
|
+
- **No weight of 700.** Body is **420** and bold is **550**, both off the
|
|
19
|
+
standard axis and both reachable only because the face is variable. Setting a
|
|
20
|
+
heading in 700 is the one number this system was built to avoid.
|
|
21
|
+
- **No second shadow.** One three-layer token — ambient, contact, and a hairline
|
|
22
|
+
edge that keeps a card legible on pure white. Dropping the third layer is why
|
|
23
|
+
a copied card floats without sitting.
|
|
24
|
+
- **No `clamp()` in the type layer.** Size and leading ship as a paired `rem`
|
|
25
|
+
value so they cannot drift; a fluid step breaks the pair.
|
|
26
|
+
- **Status is never by colour alone.** The derived red and green collapse toward
|
|
27
|
+
each other under deuteranopia at a measured 5.2 against a floor of 8.0, so
|
|
28
|
+
every state carries its word.
|
|
29
|
+
|
|
30
|
+
## The two shapes
|
|
31
|
+
|
|
32
|
+
Surfaces take the radius scale — `0.375 / 0.5 / 0.75 / 1rem`. **Buttons take
|
|
33
|
+
`--radius-full`,** and they take it through `--radius-button`, which is the only
|
|
34
|
+
place the system says why a button is a pill. Keep the indirection.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @sheleg-design/awning
|
|
2
|
+
|
|
3
|
+
The static half of the SHELEG **Awning** style pack — a white forecourt where the
|
|
4
|
+
accent is black and no hue reaches the chrome.
|
|
5
|
+
|
|
6
|
+
Extracted from a production reference (shopify.com, read 2026-08-15). The full
|
|
7
|
+
pack, with every measurement and every trap, is
|
|
8
|
+
`styles/awning.md` in the sheleg-design skill.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install && npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`src/styles.css` opens with `styles/tokens/awning.css` copied byte for byte;
|
|
15
|
+
everything under `/* ── components ── */` consumes `var(--…)` only.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sheleg-design/awning",
|
|
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
|
+
The primary button is a **black pill**. Its radius comes from `--radius-button`,
|
|
6
|
+
which points at `--radius-full` — the indirection is the only place the system
|
|
7
|
+
says why a button is that shape, so keep it rather than writing `9999px`.
|
|
8
|
+
|
|
9
|
+
Hover changes **two** properties: the fill lightens to `--accent-hover` and the
|
|
10
|
+
label moves to `--accent-ink-hover`. Moving only the fill reads as a lightening;
|
|
11
|
+
moving both reads as a press. `secondary` is transparent at every state — fill,
|
|
12
|
+
hover, active and disabled alike — so its whole identity is the border and the
|
|
13
|
+
label, which is why this pack ships two rule weights.
|
|
14
|
+
|
|
15
|
+
Disabled is a **value**, not an opacity: `--accent-disabled` fill with
|
|
16
|
+
`--accent-ink-disabled` content. Nothing in this system fades, so a disabled
|
|
17
|
+
control keeps its edges and stays measurable.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<Button onClick={start}>Start free trial</Button>
|
|
21
|
+
<Button variant="secondary" onClick={demo}>Watch the demo</Button>
|
|
22
|
+
<Button variant="ghost" size="sm" onClick={more}>Compare plans</Button>
|
|
23
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ButtonProps {
|
|
4
|
+
/** `primary` is the BLACK pill — the accent, and there is no hue in the control set. */
|
|
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={['aw-btn', `aw-btn--${variant}`, `aw-btn--${size}`, className]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ')}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
`raised` carries the system's **one** shadow — three layers: a wide ambient, a
|
|
6
|
+
close contact, and a `0 0 2px` hairline edge. That third layer is what people
|
|
7
|
+
drop when they copy a shadow by eye, and dropping it is why a copied card floats
|
|
8
|
+
on white without sitting on it. There is no second shadow and no elevation
|
|
9
|
+
scale, so a card that needs to feel heavier needs a different layout, not a
|
|
10
|
+
bigger blur.
|
|
11
|
+
|
|
12
|
+
`flat` sits on `--bg-deep` with no shadow — use it when cards are tiled and the
|
|
13
|
+
shadows would otherwise stack into noise.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Card><Heading level={3}>Point of sale</Heading><p>Sell anywhere.</p></Card>
|
|
17
|
+
<Card elevation="flat" radius="2xl">…</Card>
|
|
18
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
return (
|
|
13
|
+
<div className={['aw-card', className].filter(Boolean).join(' ')}>
|
|
14
|
+
{title ? (
|
|
15
|
+
<div className="aw-card__head">
|
|
16
|
+
<span className="aw-card__title">{title}</span>
|
|
17
|
+
{meta ? <span className="aw-card__meta">{meta}</span> : null}
|
|
18
|
+
</div>
|
|
19
|
+
) : null}
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Data
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
A chip is a **pill**, like the button — this system has two shapes and a chip
|
|
6
|
+
takes the round one.
|
|
7
|
+
|
|
8
|
+
`accent` is the **ink** fill, not a hue: this pack has no colour to tint a chip
|
|
9
|
+
with, so a selected or emphasised chip inverts exactly as the primary button
|
|
10
|
+
does. That constraint is the register rather than a limitation — it is what
|
|
11
|
+
keeps every colour on the page belonging to the product screenshot.
|
|
12
|
+
|
|
13
|
+
Where a chip must carry a *state*, put the word in it and take the colour from
|
|
14
|
+
`--good` / `--warn` / `--danger` in your own rule. Those three are derived, and
|
|
15
|
+
the red and the green collapse toward each other under deuteranopia at a
|
|
16
|
+
measured 5.2 against a floor of 8.0 — so the word is the primary encoding and
|
|
17
|
+
the colour is the second. A status chip with no word is a bug here.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<Chip>Draft</Chip>
|
|
21
|
+
<Chip tone="accent" selected>Monthly</Chip>
|
|
22
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ChipProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
selected?: boolean;
|
|
6
|
+
/** `accent` is the INK fill — this pack has no hue to tint a chip with. */
|
|
7
|
+
tone?: 'neutral' | 'accent';
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Chip({ children, selected = false, tone = 'neutral', className }: ChipProps) {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
className={['aw-chip', `aw-chip--${tone}`, selected ? 'aw-chip--selected' : undefined, className]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(' ')}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</span>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The catalogue unit. This register's page is long and sectioned, and a buyer
|
|
6
|
+
scrolls it to **compare** — so a capability is a row with a title and one
|
|
7
|
+
paragraph, not a card with an illustration. Rows are separated by `Rule`, never
|
|
8
|
+
by a gap alone, and the paragraph sits in `--ink-soft` because `--ink-faint`
|
|
9
|
+
does not clear AA on the second field.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<FeatureRow index="01" title="Checkout">
|
|
13
|
+
The fastest checkout on the internet, and it converts.
|
|
14
|
+
</FeatureRow>
|
|
15
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FeatureRowProps {
|
|
4
|
+
title: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** A short mono index — `01`, `02` — when the row belongs to an ordered set. */
|
|
7
|
+
index?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function FeatureRow({ title, children, index, className }: FeatureRowProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className={['aw-feature', className].filter(Boolean).join(' ')}>
|
|
14
|
+
{index ? <span className="aw-feature__index">{index}</span> : null}
|
|
15
|
+
<div className="aw-feature__body">
|
|
16
|
+
<span className="aw-feature__title">{title}</span>
|
|
17
|
+
<p className="aw-feature__text">{children}</p>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Foundations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Three steps off the reference's nine-step title ramp. Each carries its **paired**
|
|
6
|
+
size and leading — `3.5rem / 3.78rem`, `2.75rem / 2.97rem` — because the system
|
|
7
|
+
ships them as one token so they cannot drift apart. Do not substitute a ratio.
|
|
8
|
+
|
|
9
|
+
Weight is **550**, never 700. Tracking runs negative here and **positive** on
|
|
10
|
+
body text, both within the same family; the crossover sits around `1.375rem`.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Heading level={1}>Everything you need to sell</Heading>
|
|
14
|
+
<Heading level={3}>Payments</Heading>
|
|
15
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps {
|
|
4
|
+
/** 1 = page title (`dsp`), 2 = section (`t1`), 3 = card title (`t3`). */
|
|
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 className={['aw-h', `aw-h--${level}`, className].filter(Boolean).join(' ')}>
|
|
14
|
+
{children}
|
|
15
|
+
</Tag>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The commerce front door's load-bearing section. **A featured plan is featured by
|
|
6
|
+
fill, not by hue** — it inverts to `--ink` with `--bg` content, exactly as the
|
|
7
|
+
primary button does, because there is no accent colour to tint it with. At most
|
|
8
|
+
one per row; two featured plans is a row with no recommendation in it.
|
|
9
|
+
|
|
10
|
+
The price takes the `t1` step and the cadence the `b4`, so the number carries
|
|
11
|
+
and the interval does not compete with it.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<PlanCard
|
|
15
|
+
name="Basic" price="$29" cadence="per month"
|
|
16
|
+
features={['2 staff accounts', '10 locations']}
|
|
17
|
+
action={<Button size="sm">Start free trial</Button>}
|
|
18
|
+
/>
|
|
19
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface PlanCardProps {
|
|
4
|
+
name: string;
|
|
5
|
+
price: ReactNode;
|
|
6
|
+
cadence?: string;
|
|
7
|
+
features: string[];
|
|
8
|
+
/** At most ONE plan per row is featured, and it is featured by fill, not by hue. */
|
|
9
|
+
featured?: boolean;
|
|
10
|
+
action: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function PlanCard({
|
|
15
|
+
name,
|
|
16
|
+
price,
|
|
17
|
+
cadence,
|
|
18
|
+
features,
|
|
19
|
+
featured = false,
|
|
20
|
+
action,
|
|
21
|
+
className,
|
|
22
|
+
}: PlanCardProps) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={['aw-plan', featured ? 'aw-plan--featured' : '', className]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join(' ')}
|
|
28
|
+
>
|
|
29
|
+
<span className="aw-plan__name">{name}</span>
|
|
30
|
+
<span className="aw-plan__price">{price}</span>
|
|
31
|
+
{cadence ? <span className="aw-plan__cadence">{cadence}</span> : null}
|
|
32
|
+
<ul className="aw-plan__features">
|
|
33
|
+
{features.map((f) => (
|
|
34
|
+
<li key={f}>{f}</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
<div className="aw-plan__action">{action}</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Signature
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**The colourless chrome.** A full-colour screenshot of the product sits in a
|
|
6
|
+
frame that contains no hue at all — the card's radius, the one shadow, and
|
|
7
|
+
nothing else. This is the pack's second motif and the reason the accent is
|
|
8
|
+
black: every colour a reader sees on the page belongs to the thing being sold.
|
|
9
|
+
|
|
10
|
+
The frame carries a real `aria-label` describing what the shot shows. A frame
|
|
11
|
+
whose label repeats the section heading is a frame that told a screen-reader
|
|
12
|
+
user nothing.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
<ProductFrame label="The orders list, filtered to unfulfilled">
|
|
16
|
+
<img src={shot} alt="" />
|
|
17
|
+
</ProductFrame>
|
|
18
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ProductFrameProps {
|
|
4
|
+
/** Describe what the shot shows — this is the accessible name, not decoration. */
|
|
5
|
+
label: string;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ProductFrame({ label, children, className }: ProductFrameProps) {
|
|
11
|
+
return (
|
|
12
|
+
<figure
|
|
13
|
+
className={['aw-frame', className].filter(Boolean).join(' ')}
|
|
14
|
+
role="img"
|
|
15
|
+
aria-label={label}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</figure>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
category: Surfaces
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
This pack ships **two** border weights, which is unusual in this library.
|
|
6
|
+
`--line` goes between rows; `--line-strong` goes around a control. The
|
|
7
|
+
difference is load-bearing: it is what makes an outlined secondary button read
|
|
8
|
+
as a control at all, given its fill is transparent in every state.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<Rule />
|
|
12
|
+
<Rule tone="strong" />
|
|
13
|
+
```
|