sheleg-design-skill 1.12.1 → 1.13.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +6 -5
  3. package/bin/cli.js +4 -3
  4. package/cursor/rules/sheleg-design.mdc +4 -1
  5. package/kits/scoreboard/.design-sync/config.json +14 -0
  6. package/kits/scoreboard/.design-sync/conventions.md +71 -0
  7. package/kits/scoreboard/README.md +16 -0
  8. package/kits/scoreboard/package.json +29 -0
  9. package/kits/scoreboard/src/Button.md +5 -0
  10. package/kits/scoreboard/src/Button.tsx +33 -0
  11. package/kits/scoreboard/src/Card.md +5 -0
  12. package/kits/scoreboard/src/Card.tsx +24 -0
  13. package/kits/scoreboard/src/Chip.md +5 -0
  14. package/kits/scoreboard/src/Chip.tsx +25 -0
  15. package/kits/scoreboard/src/Heading.md +5 -0
  16. package/kits/scoreboard/src/Heading.tsx +19 -0
  17. package/kits/scoreboard/src/Ledger.md +5 -0
  18. package/kits/scoreboard/src/Ledger.tsx +45 -0
  19. package/kits/scoreboard/src/LedgerRow.md +5 -0
  20. package/kits/scoreboard/src/LedgerRow.tsx +28 -0
  21. package/kits/scoreboard/src/Rule.md +5 -0
  22. package/kits/scoreboard/src/Rule.tsx +18 -0
  23. package/kits/scoreboard/src/Stat.md +5 -0
  24. package/kits/scoreboard/src/Stat.tsx +17 -0
  25. package/kits/scoreboard/src/StatusChip.md +5 -0
  26. package/kits/scoreboard/src/StatusChip.tsx +27 -0
  27. package/kits/scoreboard/src/TickHeading.md +5 -0
  28. package/kits/scoreboard/src/TickHeading.tsx +23 -0
  29. package/kits/scoreboard/src/index.ts +23 -0
  30. package/kits/scoreboard/src/styles.css +348 -0
  31. package/kits/scoreboard/tsconfig.json +15 -0
  32. package/package.json +2 -2
  33. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  34. package/plugins/sheleg-design/commands/sheleg-design.md +2 -2
  35. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  36. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +2 -2
  37. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +5 -4
  38. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +3 -3
  39. package/plugins/sheleg-design/skills/sheleg-design/styles/field-notes.md +9 -0
  40. package/plugins/sheleg-design/skills/sheleg-design/styles/scoreboard.md +393 -0
  41. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/scoreboard.css +245 -0
  42. package/plugins/sheleg-design/skills/sheleg-design/styles/workbench.md +7 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,114 @@ 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.13.1] - 2026-08-12
8
+
9
+ `scoreboard`'s routing scenario was run the day it shipped, and it found nine
10
+ things three green gates had not.
11
+
12
+ ### Fixed
13
+
14
+ - **The focus ring was invisible.** 1.13.0 promoted the reference's decorative
15
+ `focus-within` glow — a 20% accent halo with a 40% border — to the pack's focus
16
+ treatment without measuring it. Composited the way a browser does it, the halo
17
+ is **1.29:1** against the paper and the border **1.67:1**, against a WCAG floor
18
+ of 3:1 for a non-text indicator. `--ring-focus` is a solid 2px accent ring now,
19
+ and `--ring-focus-sand` carries `--surface-sand`, the one field where the accent
20
+ falls under the floor at 2.97:1.
21
+ - **No orange in the pack can carry a link, and 1.13.0 said one could.**
22
+ `--accent-hover` was called "the one orange that may carry a link" at 4.12:1 —
23
+ below the same AA threshold the pack cites two paragraphs earlier to ban the
24
+ accent from text. A link is `--ink` with an `--accent` underline.
25
+ - **Four status ratios were stated 0.02–0.08 optimistic**, computed from the
26
+ OKLCH the colours were selected from rather than from the 8-bit hex the token
27
+ layer ships: `--good` 5.09, `--warn` 4.78, `--danger` 7.93, `--info` 6.49. They
28
+ passed the repository's own gate only because its tolerance is 0.1, which is
29
+ how a wrong number survives a green check.
30
+ - **The status chip carried a 10% tint that put an 11px `--warn` label at
31
+ 4.38:1.** The chip has no fill now — the word carries the colour. This also
32
+ removes a disagreement between the pack doc and the kit, which had never
33
+ rendered a tint.
34
+ - **`--bp-md` and `--bp-lg` were referenced in three token comments and defined
35
+ nowhere.** Replaced with the pixel values they meant.
36
+ - **`SKILL.md` miscounted its own library**: "Six of the thirteen are on the core
37
+ contract … the other **six** answer all four." Six plus seven. Introduced by
38
+ 1.13.0's own count edit and found independently by both scenario branches.
39
+ - **`SURFACE_COMPOSITION.md` said only `field-notes` ships a validated
40
+ `--chart-1…N` set.** `scoreboard` ships one too.
41
+ - **The numeral column is a glyph budget, and the pack only warned about it.**
42
+ Press Start 2P advances a full em per glyph, so at 15px the 80px column holds
43
+ five glyphs and the 70px mobile column four: `3.4x` fits, `$9,840` does not.
44
+ Stated as a ceiling with the only two legal answers — shorten the figure, or
45
+ widen the column for the whole ledger.
46
+
47
+ ### Changed
48
+
49
+ - **T23 has a result.** Both branches run in fresh contexts: `scoreboard` chosen
50
+ for the tally brief with the fork quoted from both sides, `field-notes` held for
51
+ the provenance brief. Recorded with every finding's disposition — including one
52
+ **refuted** (`--on-accent` is not a dead token; its consumer is the selected chip
53
+ at 4.92:1), because a refuted claim nobody writes down comes back as folklore.
54
+ - `validate_palette.py`'s floor drops 597 → 596, with the reason in
55
+ `test/floors.json`: a token was deleted rather than a check weakened.
56
+
57
+ ## [1.13.0] - 2026-08-12
58
+
59
+ A thirteenth style pack, and it is the first one in the library whose accent is
60
+ forbidden from carrying a word.
61
+
62
+ ### Added
63
+
64
+ - **`scoreboard`** — the thirteenth pack, extracted from
65
+ <https://www.get-ryze.ai/> on 2026-08-12 off its shipped stylesheet and the
66
+ markup of three pages. Warm paper (`#FAF9F5`), a warm near-black ink
67
+ (`#221D16`, 15.88:1), radii of two and three pixels, an **ink** primary button,
68
+ one hot orange (`#FF4801`) used only as a mark, and a dark ledger of
69
+ dotted-leader rows whose numbers are set in an aliased pixel face. Widened
70
+ contract — all thirteen headings — with `styles/tokens/scoreboard.css` and a
71
+ full reference kit in `kits/scoreboard/`.
72
+ - **The accent measures 3.23:1 and the pack says so at the top.** Above the 3:1
73
+ floor for a non-text mark, below the one for a word. The reference obeys this
74
+ without ever stating it: across three pages its orange is a 3×18px tick, a
75
+ `::marker`, a focus ring, a selection colour, a link underline and one
76
+ oversized chevron — and its primary button is ink. The pack turns that
77
+ observation into a ban, which is the only reason a page in it can carry a
78
+ colour that loud.
79
+ - **Two status sets rather than one filtered set.** The reference paints status
80
+ only on its dark panels; those values measure 1.6–2.6:1 against warm paper. The
81
+ paper set is selected from deeper steps of the Tailwind ramp the reference's own
82
+ stylesheet ships, and the measured on-dark set is kept beside it under
83
+ `--*-on-dark`. Every declaration says which of the two kinds of claim it is.
84
+ - **`TickHeading`, `Ledger`, `LedgerRow` and `StatusChip`** in the kit, on the
85
+ same six-component spine as every other kit. `StatusChip` takes `label` as a
86
+ **required** prop: the paper statuses cluster (the accent and `--warn` separate
87
+ by 6.3 under protanopia), so status in this pack is a chip with its word in it.
88
+
89
+ ### Fixed
90
+
91
+ - **Three corrections to the reference, recorded rather than silently applied.**
92
+ Its positive-delta colour `#00D492` is set at 11px on white — 1.84:1, an
93
+ invisible success state — and is confined here to the dark panel where it
94
+ measures 10.21:1. Its primary button transitions over 500ms, past the 300ms
95
+ ceiling in `MOTION_DOCTRINE.md` §3, and the pack pins `--dur-fast` at .16s. Its
96
+ scan line animates `top`; the pack rebuilds it on `transform`.
97
+ - **`validate.py --self-test` printed FAILED and exited 0.** `main()` returned
98
+ the self-test's status and `__main__` called it bare, so the code was dropped
99
+ on the floor and `npm run selftest` stayed green through a self-test that had
100
+ failed — found because this release's count change broke a plant fixture and
101
+ the suite passed anyway. The argv handling directly above it exists to close
102
+ this exact class one layer up and never reached the exit code. Verified by
103
+ breaking a fixture in a copy of the tree: 0 before, 1 after.
104
+ - **A plant fixture pinned to a literal that changes every release.** The
105
+ stale-count plant searched for `**twelve locked style packs**`; the first time
106
+ the library grew it mutated nothing and stopped testing the check it exists
107
+ for. It now reads whatever number the README claims and makes that wrong.
108
+ - **Reciprocal forks with `field-notes` and `workbench`.** From a distance
109
+ `field-notes` *is* this pack — warm paper, one orange-red accent, hairline
110
+ rules — and the distinction is what the small type does: mono numerals make
111
+ evidence auditable, pixel numerals make results countable. Both neighbours now
112
+ carry the fork back, so an agent arriving at either one first still learns it
113
+ exists.
114
+
7
115
  ## [1.12.1] - 2026-08-11
8
116
 
9
117
  - **The reference-sweep heading named one server for a section about two.** It
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 **twelve locked style
14
+ decides whether to animate before it decides how, and **thirteen 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
 
@@ -56,6 +56,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
56
56
  | `blueprint` | white drawing stock, 32px grid, ruled column edges, corner registration marks, one electric blue, zero radius anywhere | infrastructure sold on precision — vector databases, search, storage and query engines |
57
57
  | `prism` | one static iridescent wash with a hard bottom edge, heavy grotesque display over mono body copy, cyan as a fill only | an open-source project's front door, where the first action is a command |
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
+ | `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 |
59
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 |
60
61
 
61
62
  Each pack locks palette, type, texture, motion tokens, signature motifs and
@@ -126,12 +127,12 @@ skills.
126
127
  |---|---|
127
128
  | `SKILL.md` | The agent-facing skill: discovery triggers, the principles, how to apply them, quick-reference rules, common mistakes |
128
129
  | `SHELEG_DESIGN.md` | The full reference: architecture, layer-by-layer mechanics with code, the exact morph math, the DOM↔WebGL projection bridge, a build-from-scratch recipe, and why each piece works |
129
- | `SURFACE_COMPOSITION.md` | Two decisions the pack layer does not make: the six depth layers of a scene, read before writing CSS for a cinematic page; and the handoff to `dataviz`, read before drawing a chart in any pack — token names are not uniform across the twelve and an undefined custom property fails silently |
130
+ | `SURFACE_COMPOSITION.md` | Two decisions the pack layer does not make: the six depth layers of a scene, read before writing CSS for a cinematic page; and the handoff to `dataviz`, read before drawing a chart in any pack — token names are not uniform across the thirteen and an undefined custom property fails silently |
130
131
  | `MOTION_DOCTRINE.md` | Whether to animate at all, before how: the frequency table that kills motion on high-repetition paths, the easing tree and the `ease-in` ban, the duration ceiling, the forbidden forms, and the reduced-motion contract. `SKILL.md` marks it required before any animation |
131
132
  | `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 |
132
133
  | `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 |
133
134
  | `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 |
134
- | `styles/*.md` | The twelve style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
135
+ | `styles/*.md` | The thirteen style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
135
136
  | `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) |
136
137
  | `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
137
138
 
@@ -202,7 +203,7 @@ cd ./ds-workbench && npm install && npm run build
202
203
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
203
204
  pack's **bans** as the design system's own README, `styles.css` built from
204
205
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
205
- identical in all twelve kits, so switching packs swaps identity rather than API,
206
+ identical in all thirteen kits, so switching packs swaps identity rather than API,
206
207
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
207
208
  not cross into Figma: a kit is the static half of a pack, and saying so is what
208
209
  stops an agent inventing motion to fill the silence.
@@ -248,7 +249,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
248
249
  One honest limit: the npx installer is checked by asserting its runtime bundle
249
250
  walker exists, not by reading a file list — it has none by design. What proves
250
251
  it ships the right files is CI, which installs the bundle through **both**
251
- installers and `diff -r`s the result against the source, then builds all twelve
252
+ installers and `diff -r`s the result against the source, then builds all thirteen
252
253
  kits.
253
254
 
254
255
  `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/ twelve style packs — instrument-console (dark console),
237
+ styles/ thirteen 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
@@ -242,7 +242,8 @@ ${c("bold", "What it installs")}
242
242
  cyclorama (a pastel field on a 32s cycle), showroom
243
243
  (the product as the exhibit), blueprint (a drawing sheet,
244
244
  zero radius), prism (one iridescent wash over mono body),
245
- maquette (cream axonometric models on a dark table) —
245
+ maquette (cream axonometric models on a dark table),
246
+ scoreboard (warm paper, pixel numerals, a dark ledger) —
246
247
  plus a ready-made token CSS per pack and
247
248
  STYLE_PACK_TEMPLATE.md for authoring more
248
249
  `);
@@ -324,7 +325,7 @@ function main() {
324
325
  ` ${c("dim", "SKILL.md")} the agent skill\n` +
325
326
  ` ${c("dim", "SHELEG_DESIGN.md")} the full reference\n` +
326
327
  ` ${c("dim", "MOTION_DOCTRINE.md")} whether to animate at all — read before any animation\n` +
327
- ` ${c("dim", "styles/")} style packs + token CSS (instrument-console / editorial-luxury / workbench / briefing-room / atrium / orchard / field-notes / cyclorama / showroom / blueprint / prism / maquette)\n\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` +
328
329
  `Your Cursor / Claude agent can now discover the skill and build\n` +
329
330
  `cinematic, scroll-driven pages — or style product UI (dashboards, admin,\n` +
330
331
  `internal tools) from a standalone pack — on its principles.\n\n` +
@@ -27,7 +27,10 @@ under a seven-layer shadow; blueprint — a drawing sheet for infrastructure
27
27
  sold on precision: grid, ruled columns, registration marks, one electric
28
28
  blue and zero radius; prism — an open-source project's front door, one
29
29
  static iridescent wash over mono body copy; maquette — enterprise data
30
- infrastructure as a cream axonometric model on a near-black table);
30
+ infrastructure as a cream axonometric model on a near-black table;
31
+ scoreboard — warm paper and a dark ledger of dotted-leader rows whose
32
+ numbers are set in an aliased pixel face, for a product whose argument is
33
+ an accumulating number);
31
34
  otherwise follow the contract below (self-contained on purpose).
32
35
 
33
36
  ## Whether to animate at all — before how
@@ -0,0 +1,14 @@
1
+ {
2
+ "pkg": "@sheleg-design/scoreboard",
3
+ "globalName": "ShelegScoreboard",
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,71 @@
1
+ # Scoreboard — the contract this design system ships under
2
+
3
+ **Register.** Choose Scoreboard for a product whose argument is **an
4
+ accumulating number**: performance marketing and ads operators, SEO and growth
5
+ tools, revenue dashboards sold on results, agency-replacement services. The page
6
+ is a running tally — a dated column of figures the product already produced, with
7
+ the label on the left, the number on the right, and a line of dots between them.
8
+ The fork people get wrong is against `field-notes`, which shares the warm paper
9
+ and the orange-red accent: there the numerals are mono and make evidence
10
+ *auditable*; here they are pixel and make results *countable*. *How do you know
11
+ that?* goes there. *How much, and since when?* stays here. Build every screen
12
+ against `var(--…)` and never a literal.
13
+
14
+ **The accent is a mark, not a voice — and neither is the other orange.**
15
+ `--accent` `#FF4801` measures 3.23:1 on the paper and `--accent-hover` `#E03D00`
16
+ measures 4.12:1; **no orange in this system reaches the WCAG AA floor for a
17
+ word.** The accent is the 3×18px tick, the list marker, the focus ring and the
18
+ link underline — and, filled, a selected chip, where `--on-accent` sits on it at
19
+ 4.92:1. A link is `--ink` with an `--accent` underline, never orange text.
20
+
21
+ **The focus ring is solid.** 1.13.0 shipped the reference's translucent
22
+ `focus-within` glow, which composites to 1.29:1 against the paper — decoration
23
+ wearing an affordance's name. `--ring-focus` is now a solid 2px accent ring, and
24
+ `--ring-focus-sand` is the ink ring for `--surface-sand`, the one surface where
25
+ the accent misses the floor.
26
+
27
+ **The status chip has no fill.** The word carries the colour on the surface it
28
+ sits on. A 10% tint under an 11px `--warn` label lands at 4.38:1, under AA — and
29
+ a chip whose whole job is to be the secondary encoding cannot itself be the
30
+ thing that fails.
31
+
32
+ **The action is ink.** `--action` `#0A0A0A` with a white label is the primary
33
+ button. This is measured off the reference, and it is why the accent survives:
34
+ the loudest colour on the page never competes with the thing you are meant to
35
+ click. A view with an orange CTA has two primaries and no accent.
36
+
37
+ **Status is never by colour alone.** The paper statuses cluster — the accent and
38
+ `--warn` separate by only 6.3 under protanopia, `--danger` and `--warn` by 12.6
39
+ at full colour. Every status is therefore a chip containing its word, which is
40
+ why `StatusChip` takes `label` as a required prop rather than an optional one.
41
+
42
+ **There are two status sets, not one.** On paper use `--good` / `--warn` /
43
+ `--danger` / `--info`; on a `data-surface="panel"` element use the measured
44
+ `--*-on-dark` values. The dark set measures 1.6–2.6:1 against the paper and the
45
+ paper set disappears on the panel. `StatusChip` takes `onPanel` for exactly this.
46
+
47
+ **Radii are two and three pixels.** Across the reference, 107 of 143 radius
48
+ utilities are 2px or 3px. At 8px everywhere this becomes a generic product page
49
+ with an orange tick on it. When containers nest, an inner radius is the outer
50
+ minus the padding between them.
51
+
52
+ **Bans** (verbatim from the pack):
53
+
54
+ - The accent as body text, a heading, or a button fill.
55
+ - A second ledger on the page, a ledger with a rounded marketing number in it,
56
+ or a ledger with no date under it.
57
+ - Antialiased pixel type. Without smoothing off, the numerals are a novelty face.
58
+ - A bare status dot, and any status carried by colour with no word beside it.
59
+ - The paper status set on the dark band, or the dark set on paper.
60
+ - Radii above 8px, and any radius on the ledger's own rows.
61
+ - A spinner where a number will land. The skeleton is the row.
62
+ - Fluid `clamp()` display type; `transition: all`; a second accent; a gradient
63
+ anywhere except the hero's wash.
64
+
65
+ **The body size is 15px and the numeral column is fixed.** Press Start 2P has no
66
+ currency width worth trusting: budget the column in pixels (80px, 70px below the
67
+ medium breakpoint) and right-align it.
68
+
69
+ Motion is not part of this design system and must not be invented: a kit is the
70
+ static half of a pack, and anything that moves — the entrance, the scan line, the
71
+ count-up — stays behind in the pack.
@@ -0,0 +1,16 @@
1
+ # @sheleg-design/scoreboard
2
+
3
+ The React reference kit for the SHELEG **Scoreboard** style pack — warm paper, an
4
+ orange that only ever marks, and a ledger of dotted-leader rows whose numbers are
5
+ set in an aliased pixel face.
6
+
7
+ It is generated from the pack, not authored beside it: `src/styles.css` opens
8
+ with `styles/tokens/scoreboard.css` byte for byte, and the rules the design agent
9
+ must 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/scoreboard",
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,5 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ `primary` is the **ink** fill, not the accent — this pack's loudest colour never competes with the thing you are meant to click. `secondary` is the 4%-ink ghost. Nothing scales on hover: the fill changes and the button presses by one pixel.
@@ -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={['sb-btn', `sb-btn--${variant}`, `sb-btn--${size}`, className]
25
+ .filter(Boolean)
26
+ .join(' ')}
27
+ disabled={disabled}
28
+ onClick={onClick}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ A `--surface` panel with a 1px `--line-weak` and a hairline under it, at `--radius-sm` — three pixels, not eight. This pack divides with rules; `--shadow-card` is reserved for the one report surface a section is built around.
@@ -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={['sb-card', className].filter(Boolean).join(' ')}>
15
+ {head && (
16
+ <div className="sb-card__head">
17
+ {title !== undefined && <h3 className="sb-card__title">{title}</h3>}
18
+ {meta !== undefined && <span className="sb-card__meta">{meta}</span>}
19
+ </div>
20
+ )}
21
+ <div className="sb-card__body">{children}</div>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ A 2px-radius tag at 11px uppercase. `accent` uses `--accent-wash` behind `--accent-hover`, never the raw accent as text. For anything that carries a state, use `StatusChip` instead — it requires the word.
@@ -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
+ 'sb-chip',
15
+ `sb-chip--${tone}`,
16
+ selected ? 'sb-chip--selected' : undefined,
17
+ className,
18
+ ]
19
+ .filter(Boolean)
20
+ .join(' ')}
21
+ >
22
+ {children}
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ Satoshi at 600–700 with the tracking coupled to the size: −0.03em at display, −0.02em at section. Section headings normally arrive through `TickHeading`, which puts the accent tick in front of them.
@@ -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={['sb-heading', `sb-heading--${level}`, className].filter(Boolean).join(' ')}
15
+ >
16
+ {children}
17
+ </Tag>
18
+ );
19
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ The signature element: a dark panel of dotted-leader rows under a bulleted group label, with the date the figures were last true at its foot. One per page. `status` renders beside the live square and is a sentence — the square is never shown alone.
@@ -0,0 +1,45 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface LedgerProps {
4
+ /** The group label: uppercase, +0.14em, with a square bullet ahead of it. */
5
+ label?: string;
6
+ /** The date the figures were last true. A ledger with no date is a poster. */
7
+ updated?: string;
8
+ /** Rendered beside the live square, e.g. "All systems ok". Never a bare dot. */
9
+ status?: string;
10
+ children: ReactNode;
11
+ className?: string;
12
+ }
13
+
14
+ /**
15
+ * The pack's signature element: a dark panel of dotted-leader rows. The scan
16
+ * line that crosses it on the live site is motion and stays in the pack — a kit
17
+ * is the static half.
18
+ */
19
+ export function Ledger({ label, updated, status, children, className }: LedgerProps) {
20
+ return (
21
+ <section
22
+ data-surface="panel"
23
+ className={['sb-ledger', className].filter(Boolean).join(' ')}
24
+ >
25
+ {label !== undefined && (
26
+ <p className="sb-ledger__label">
27
+ <span className="sb-ledger__bullet" aria-hidden="true" />
28
+ {label}
29
+ </p>
30
+ )}
31
+ <div className="sb-ledger__rows">{children}</div>
32
+ {(updated !== undefined || status !== undefined) && (
33
+ <div className="sb-ledger__foot">
34
+ {updated !== undefined && <div>Last updated: {updated}</div>}
35
+ {status !== undefined && (
36
+ <div className="sb-ledger__status">
37
+ <span className="sb-ledger__live" aria-hidden="true" />
38
+ {status}
39
+ </div>
40
+ )}
41
+ </div>
42
+ )}
43
+ </section>
44
+ );
45
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ Label, dotted leader, pixel numeral on a fixed 80px column (70px below the medium breakpoint). The leader absorbs every width change so the row never wraps. `pending` keeps the row and puts an em dash where the number will land — a skeleton, never a spinner.
@@ -0,0 +1,28 @@
1
+ export interface LedgerRowProps {
2
+ /** Left column, two lines at most. It does not wrap past that. */
3
+ label: string;
4
+ /** The figure. Set in the pixel face, right-aligned on a fixed column. */
5
+ value: string;
6
+ /** No figure yet: the row survives and an em dash holds the column. */
7
+ pending?: boolean;
8
+ className?: string;
9
+ }
10
+
11
+ /**
12
+ * Label, a dotted leader, and a pixel numeral on a fixed column. A row that
13
+ * wraps is not a row — the leader absorbs every width change instead.
14
+ */
15
+ export function LedgerRow({ label, value, pending = false, className }: LedgerRowProps) {
16
+ return (
17
+ <div className={['sb-row', className].filter(Boolean).join(' ')}>
18
+ <span className="sb-row__label">{label}</span>
19
+ <span className="sb-row__leader" aria-hidden="true" />
20
+ <span className={['sb-row__value', pending ? 'sb-row__value--pending' : undefined]
21
+ .filter(Boolean)
22
+ .join(' ')}
23
+ >
24
+ {pending ? '—' : value}
25
+ </span>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ The pack's elevation model in one element. `hairline` is `--line-weak`, `strong` is `--line`; a card border and a section divider are the same rule at two weights.
@@ -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
+ 'sb-rule',
11
+ tone === 'strong' ? 'sb-rule--strong' : undefined,
12
+ className,
13
+ ]
14
+ .filter(Boolean)
15
+ .join(' ')}
16
+ />
17
+ );
18
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ A figure in `--ink-strong` above its label. Use it for a single number in the flow of a page; four or more figures that belong together are a `Ledger`, not four Stats. Give `source` whenever the number came from somewhere checkable.
@@ -0,0 +1,17 @@
1
+ export interface StatProps {
2
+ value: string;
3
+ label: string;
4
+ /** Where the figure came from — a query, a window, a dashboard. */
5
+ source?: string;
6
+ className?: string;
7
+ }
8
+
9
+ export function Stat({ value, label, source, className }: StatProps) {
10
+ return (
11
+ <div className={['sb-stat', className].filter(Boolean).join(' ')}>
12
+ <span className="sb-stat__label">{label}</span>
13
+ <span className="sb-stat__value">{value}</span>
14
+ {source !== undefined && <span className="sb-stat__source">{source}</span>}
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ `label` is required, not optional: the paper statuses cluster (the accent and `--warn` separate by 6.3 under protanopia), so a status in this pack is a chip with its word inside. `onPanel` swaps to the measured on-dark set, which is a different palette rather than the same one lightened.