pigment-mcp 0.4.0 → 0.5.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.
Files changed (29) hide show
  1. package/cli.js +78 -9
  2. package/content/registry.json +12 -0
  3. package/content/shots.json +18 -0
  4. package/content/templates/inkleaf/1.0.0/CHANGELOG.md +127 -0
  5. package/content/templates/inkleaf/1.0.0/agent/instructions.md +42 -0
  6. package/content/templates/inkleaf/1.0.0/components/avatar.md +8 -0
  7. package/content/templates/inkleaf/1.0.0/components/badge.md +8 -0
  8. package/content/templates/inkleaf/1.0.0/components/button.md +16 -0
  9. package/content/templates/inkleaf/1.0.0/components/card.md +14 -0
  10. package/content/templates/inkleaf/1.0.0/components/checkbox.md +11 -0
  11. package/content/templates/inkleaf/1.0.0/components/dialog.md +10 -0
  12. package/content/templates/inkleaf/1.0.0/components/empty-state.md +8 -0
  13. package/content/templates/inkleaf/1.0.0/components/index.json +18 -0
  14. package/content/templates/inkleaf/1.0.0/components/navigation.md +26 -0
  15. package/content/templates/inkleaf/1.0.0/components/select.md +10 -0
  16. package/content/templates/inkleaf/1.0.0/components/switch.md +11 -0
  17. package/content/templates/inkleaf/1.0.0/components/table.md +25 -0
  18. package/content/templates/inkleaf/1.0.0/components/tabs.md +12 -0
  19. package/content/templates/inkleaf/1.0.0/components/text-field.md +12 -0
  20. package/content/templates/inkleaf/1.0.0/components/toast.md +8 -0
  21. package/content/templates/inkleaf/1.0.0/dist/agent.md +921 -0
  22. package/content/templates/inkleaf/1.0.0/dist/tokens.css +283 -0
  23. package/content/templates/inkleaf/1.0.0/dist/tokens.json +462 -0
  24. package/content/templates/inkleaf/1.0.0/manifest.json +77 -0
  25. package/content/templates/inkleaf/1.0.0/principles/visual-language.md +449 -0
  26. package/content/templates/inkleaf/1.0.0/tokens/primitives.json +214 -0
  27. package/content/templates/inkleaf/1.0.0/tokens/semantic.dark.json +69 -0
  28. package/content/templates/inkleaf/1.0.0/tokens/semantic.light.json +69 -0
  29. package/package.json +1 -1
package/cli.js CHANGED
@@ -34493,7 +34493,7 @@ function resolveTokens(primitives, semantic) {
34493
34493
  for (const [path, token] of all) resolved.set(path, { value: resolveValue(token.value, /* @__PURE__ */ new Set([path])), type: token.type });
34494
34494
  return resolved;
34495
34495
  }
34496
- var TemplateId, SemVer, Hex, AppId, Scenario, TemplateStatus, Manifest, DtcgType, DtcgToken, ComponentCategory, ComponentEntry, ComponentIndex, Analysis, RunRecord, GateResult, CriticScores, EvalReport, Registry;
34496
+ var TemplateId, SemVer, Hex, AppId, Scenario, TemplateStatus, Manifest, DtcgType, DtcgToken, ComponentCategory, ComponentEntry, ComponentIndex, AUDIT_AREAS, AuditArea, Evidence, Signature, Analysis, RunRecord, GateResult, CriticScores, EvalReport, Registry;
34497
34497
  var init_src = __esm({
34498
34498
  "../schema/src/index.ts"() {
34499
34499
  "use strict";
@@ -34507,7 +34507,8 @@ var init_src = __esm({
34507
34507
  Manifest = external_exports.object({
34508
34508
  id: TemplateId,
34509
34509
  version: SemVer,
34510
- schemaVersion: external_exports.literal(1),
34510
+ /** 2: the principles carry Signature elements, Interaction and Applying the concept (validate enforces them). */
34511
+ schemaVersion: external_exports.union([external_exports.literal(1), external_exports.literal(2)]),
34511
34512
  name: external_exports.string().min(2).max(40),
34512
34513
  description: external_exports.string().min(20).max(240),
34513
34514
  status: TemplateStatus,
@@ -34581,10 +34582,56 @@ var init_src = __esm({
34581
34582
  ComponentIndex = external_exports.object({
34582
34583
  components: external_exports.array(ComponentEntry).min(1)
34583
34584
  });
34585
+ AUDIT_AREAS = [
34586
+ "color",
34587
+ "typography",
34588
+ "spacing",
34589
+ "layout-grid",
34590
+ "section-patterns",
34591
+ "shape",
34592
+ "borders",
34593
+ "elevation",
34594
+ "surfaces-and-backgrounds",
34595
+ "buttons",
34596
+ "links",
34597
+ "navigation",
34598
+ "forms",
34599
+ "cards",
34600
+ "disclosure",
34601
+ "tabs",
34602
+ "tables-and-data",
34603
+ "feedback",
34604
+ "overlays",
34605
+ "iconography",
34606
+ "illustration",
34607
+ "media",
34608
+ "motion-load",
34609
+ "motion-scroll",
34610
+ "motion-hover-and-press",
34611
+ "motion-loop",
34612
+ "theming",
34613
+ "responsive",
34614
+ "accessibility",
34615
+ "writing-style"
34616
+ ];
34617
+ AuditArea = external_exports.enum(AUDIT_AREAS);
34618
+ Evidence = external_exports.array(external_exports.string()).min(1).describe("Files under pipeline/<slug>/reference or JSON paths that show it");
34619
+ Signature = external_exports.object({
34620
+ name: external_exports.string(),
34621
+ what: external_exports.string().describe("What it is and how it looks, measured"),
34622
+ evidence: Evidence,
34623
+ frequency: external_exports.string().describe("Where and how often it appears across the surveyed site"),
34624
+ recipe: external_exports.string().describe("How to build an original one: construction, tokens, stroke and shape rules, code-level notes"),
34625
+ motion: external_exports.string().describe("Trigger, keyframes, duration, easing, iterations \u2014 or 'static'"),
34626
+ applicationMap: external_exports.array(external_exports.object({ context: external_exports.string(), use: external_exports.string() })).min(4).describe("Where it goes in any product: page headers, empty states, loading, onboarding, FAQ, section breaks, data screens\u2026"),
34627
+ limits: external_exports.string().describe("How much per screen, where never, reduced-motion behavior"),
34628
+ derived: external_exports.boolean().describe("true when the reference doesn't show it this way and it was derived")
34629
+ });
34584
34630
  Analysis = external_exports.object({
34585
34631
  slug: external_exports.string(),
34586
34632
  references: external_exports.array(external_exports.object({ url: external_exports.string().url(), pages: external_exports.array(external_exports.string()) })),
34587
34633
  capturedAt: external_exports.string(),
34634
+ survey: external_exports.object({ pagesSurveyed: external_exports.number().int(), pageTypes: external_exports.array(external_exports.string()), notes: external_exports.string() }),
34588
34635
  character: external_exports.object({
34589
34636
  summary: external_exports.string(),
34590
34637
  keywords: external_exports.array(external_exports.string()).min(3).max(8),
@@ -34602,10 +34649,32 @@ var init_src = __esm({
34602
34649
  spacing: external_exports.object({ basePx: external_exports.number(), scale: external_exports.array(external_exports.number()), notes: external_exports.string() }),
34603
34650
  shape: external_exports.object({ radii: external_exports.array(external_exports.object({ role: external_exports.string(), px: external_exports.number() })), borderWidths: external_exports.array(external_exports.number()), notes: external_exports.string() }),
34604
34651
  elevation: external_exports.array(external_exports.object({ role: external_exports.string(), css: external_exports.string() })),
34605
- motion: external_exports.object({ durationsMs: external_exports.array(external_exports.number()), easings: external_exports.array(external_exports.string()), notes: external_exports.string() }),
34652
+ motion: external_exports.object({
34653
+ durationsMs: external_exports.array(external_exports.number()),
34654
+ easings: external_exports.array(external_exports.string()),
34655
+ patterns: external_exports.array(
34656
+ external_exports.object({
34657
+ name: external_exports.string(),
34658
+ trigger: external_exports.enum(["load", "scroll", "hover", "focus", "press", "open", "loop", "state-change"]),
34659
+ target: external_exports.string(),
34660
+ durationMs: external_exports.number(),
34661
+ easing: external_exports.string(),
34662
+ keyframes: external_exports.string().describe("What changes, from \u2192 to"),
34663
+ evidence: Evidence
34664
+ })
34665
+ ).min(1),
34666
+ reducedMotion: external_exports.string().describe("What the reference does under prefers-reduced-motion, and what the template will do"),
34667
+ notes: external_exports.string()
34668
+ }),
34606
34669
  layout: external_exports.object({ grid: external_exports.string(), density: Manifest.shape.tags.shape.density, maxWidthPx: external_exports.number().optional(), notes: external_exports.string() }),
34607
- components: external_exports.array(external_exports.object({ name: external_exports.string(), observations: external_exports.string(), states: external_exports.array(external_exports.string()) })),
34670
+ patterns: external_exports.array(external_exports.object({ name: external_exports.string(), description: external_exports.string(), frequency: external_exports.string(), evidence: Evidence })).min(3).describe("Recurring layouts and UI patterns across pages: section rhythm, hero composition, card grids, logo walls, FAQ blocks\u2026"),
34671
+ components: external_exports.array(external_exports.object({ name: external_exports.string(), observations: external_exports.string(), states: external_exports.array(external_exports.string()), evidence: Evidence })),
34672
+ interactions: external_exports.array(external_exports.object({ element: external_exports.string(), hover: external_exports.string(), focus: external_exports.string(), press: external_exports.string(), open: external_exports.string().optional(), evidence: Evidence })).min(3),
34673
+ theming: external_exports.object({ mechanism: external_exports.enum(["prefers-color-scheme", "toggle", "both", "none"]), notes: external_exports.string(), evidence: Evidence }),
34608
34674
  imagery: external_exports.object({ iconography: external_exports.string(), illustration: external_exports.string(), photography: external_exports.string() }),
34675
+ signatures: external_exports.array(Signature).min(1),
34676
+ writing: external_exports.object({ casing: external_exports.string(), tone: external_exports.string(), notes: external_exports.string() }).describe("Style only \u2014 never copy text"),
34677
+ coverage: external_exports.array(external_exports.object({ area: AuditArea, status: external_exports.enum(["observed", "derived", "absent"]), note: external_exports.string() })),
34609
34678
  doNot: external_exports.array(external_exports.string()).describe("Things that would break the character"),
34610
34679
  ipRisks: external_exports.array(external_exports.string()).describe("Brand assets, proprietary fonts or distinctive marks that must NOT be carried over")
34611
34680
  });
@@ -34638,6 +34707,8 @@ var init_src = __esm({
34638
34707
  spacingRhythm: external_exports.number().min(0).max(5),
34639
34708
  componentStates: external_exports.number().min(0).max(5),
34640
34709
  polish: external_exports.number().min(0).max(5),
34710
+ /** Are the signature elements present across all four apps, placed per the application map, moving as specified? Null for packages without signatures. */
34711
+ signature: external_exports.number().min(0).max(5).nullable().optional(),
34641
34712
  notes: external_exports.array(external_exports.string()),
34642
34713
  promptFixes: external_exports.array(external_exports.string()).describe("Concrete spec changes that would fix the observed problems")
34643
34714
  });
@@ -34854,8 +34925,7 @@ var init_src2 = __esm({
34854
34925
  // ../content/src/prompts.ts
34855
34926
  function specRef(id2, delivery) {
34856
34927
  if (delivery === "url") return `Spec: ${SITE_URL}/s/${id2}/agent.md`;
34857
- if (delivery === "mcp") return "Read the spec with the Pigment MCP tools (get_design_system, get_component_spec).";
34858
- return `Spec: dist/agent.md in the ${id2} package, unzipped under ./design-packages/`;
34928
+ return "Read the spec with the Pigment MCP tools (get_design_system, get_component_spec).";
34859
34929
  }
34860
34930
  function buildPrompt(id2, mode2, delivery) {
34861
34931
  const spec = specRef(id2, delivery);
@@ -34898,7 +34968,7 @@ var init_package = __esm({
34898
34968
  "package.json"() {
34899
34969
  package_default = {
34900
34970
  name: "@pigment/mcp",
34901
- version: "0.4.0",
34971
+ version: "0.5.0",
34902
34972
  private: true,
34903
34973
  type: "module",
34904
34974
  description: "MCP server for Pigment design systems: tokens, rules, component specs and installation guides for your coding agent.",
@@ -35038,7 +35108,7 @@ function createServer() {
35038
35108
  "get_references",
35039
35109
  {
35040
35110
  title: "Get references",
35041
- description: "Screenshots of four reference apps (dashboard, store, blog, settings) restyled with this system by an agent \u2014 desktop and mobile, light and dark \u2014 plus links to the system page, spec and offline package.",
35111
+ description: "Screenshots of four reference apps (dashboard, store, blog, settings) restyled with this system by an agent \u2014 desktop and mobile, light and dark \u2014 plus links to the system page and the spec.",
35042
35112
  inputSchema: external_exports.object({ id, app: external_exports.string().optional().describe("Only one app: dashboard, commerce, blog or settings") }),
35043
35113
  annotations: { readOnlyHint: true }
35044
35114
  },
@@ -35050,7 +35120,6 @@ function createServer() {
35050
35120
  updated: updated(args.id),
35051
35121
  page: `${SITE_URL}/s/${args.id}`,
35052
35122
  spec: `${SITE_URL}/s/${args.id}/agent.md`,
35053
- package: `${SITE_URL}/download/${args.id}`,
35054
35123
  screenshots: shots
35055
35124
  },
35056
35125
  null,
@@ -11,6 +11,18 @@
11
11
  "sha256": "e67b252ca8320bac932269460990143e99a2ec25c7fee2fa029af355076e29d1"
12
12
  }
13
13
  ]
14
+ },
15
+ {
16
+ "id": "inkleaf",
17
+ "latest": "1.0.0",
18
+ "versions": [
19
+ {
20
+ "version": "1.0.0",
21
+ "status": "published",
22
+ "publishedAt": "2026-10-14T00:22:11.356Z",
23
+ "sha256": "950225ff2a2540952b0acbdd499d07d8f78576262299a979d5d831145f51174b"
24
+ }
25
+ ]
14
26
  }
15
27
  ]
16
28
  }
@@ -16,5 +16,23 @@
16
16
  "settings.desktopDark": "/showcase/margin-notes/1.0.0/settings/settings-profile-desktop-dark.png",
17
17
  "settings.mobileLight": "/showcase/margin-notes/1.0.0/settings/settings-profile-mobile-light.png",
18
18
  "settings.mobileDark": "/showcase/margin-notes/1.0.0/settings/settings-profile-mobile-dark.png"
19
+ },
20
+ "inkleaf@1.0.0": {
21
+ "blog.desktopLight": "/showcase/inkleaf/1.0.0/blog/home-desktop-light.png",
22
+ "blog.desktopDark": "/showcase/inkleaf/1.0.0/blog/home-desktop-dark.png",
23
+ "blog.mobileLight": "/showcase/inkleaf/1.0.0/blog/home-mobile-light.png",
24
+ "blog.mobileDark": "/showcase/inkleaf/1.0.0/blog/home-mobile-dark.png",
25
+ "commerce.desktopLight": "/showcase/inkleaf/1.0.0/commerce/home-desktop-light.png",
26
+ "commerce.desktopDark": "/showcase/inkleaf/1.0.0/commerce/home-desktop-dark.png",
27
+ "commerce.mobileLight": "/showcase/inkleaf/1.0.0/commerce/home-mobile-light.png",
28
+ "commerce.mobileDark": "/showcase/inkleaf/1.0.0/commerce/home-mobile-dark.png",
29
+ "dashboard.desktopLight": "/showcase/inkleaf/1.0.0/dashboard/home-desktop-light.png",
30
+ "dashboard.desktopDark": "/showcase/inkleaf/1.0.0/dashboard/home-desktop-dark.png",
31
+ "dashboard.mobileLight": "/showcase/inkleaf/1.0.0/dashboard/home-mobile-light.png",
32
+ "dashboard.mobileDark": "/showcase/inkleaf/1.0.0/dashboard/home-mobile-dark.png",
33
+ "settings.desktopLight": "/showcase/inkleaf/1.0.0/settings/settings-profile-desktop-light.png",
34
+ "settings.desktopDark": "/showcase/inkleaf/1.0.0/settings/settings-profile-desktop-dark.png",
35
+ "settings.mobileLight": "/showcase/inkleaf/1.0.0/settings/settings-profile-mobile-light.png",
36
+ "settings.mobileDark": "/showcase/inkleaf/1.0.0/settings/settings-profile-mobile-dark.png"
19
37
  }
20
38
  }
@@ -0,0 +1,127 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ First release of Inkleaf: warm ink on cream paper, a text serif for headings and a quiet sans for everything
6
+ else, with almost no color. Its personality comes from hand-drawn brush-ink pictograms that draw themselves in
7
+ once, frame by frame, and from soft pastel stages that hold product UI. Everything settles slowly.
8
+
9
+ - Ten defining rules and a Never list: paper and ink grounds, one warm accent kept to focus rings and a single
10
+ hero drawing, serif headings in sentence case, depth from 1px rings and tonal bands instead of shadows.
11
+ - Five signature recipes: brush-ink pictograms played once as a stepped sprite, pastel stages and plates,
12
+ serif headings that fade in word by word, a hand-drawn "working" glyph instead of a spinner, and ink-and-paper
13
+ ring controls.
14
+ - Drawing rules an agent can follow: one subject per drawing taken from the app's own domain; hands built from
15
+ long, separate hairpin fingers in four poses (present, grip, pinch, point), no pose on more than a third of an
16
+ app's hands, with a wrist that runs off the edge of its plate or stage; a one-word test for every drawing; and
17
+ each product drawn as itself when it has no photo, in the same plate color on every screen.
18
+ - Large drawings keep a solid, unbroken line that swells in the middle and thins at the ends, like a brush. Plates
19
+ and stages size their drawing by height, at most 416px, so no object is cut off at an edge.
20
+ - Motion tokens for slow settles (750 to 1000ms), stepped loops and hover rings; under reduced motion every
21
+ drawing shows its final frame and nothing stays hidden.
22
+ - 14 specified components, avatar and empty state included, with designed hover, focus, disabled, loading and
23
+ error states, and layout rules for dashboards, stores, articles and settings.
24
+ - Light and dark themes with Source Serif 4, Instrument Sans and JetBrains Mono, all openly licensed.
25
+
26
+ ## 0.1.0-rc.4
27
+
28
+ Fourth candidate, answering the third critic round: drawings show the product they stand for, fill their stages,
29
+ draw hands that read as hands, and stop converging on the same office objects; card stages fit their content and
30
+ plan cards stay visible on mobile; content is never hidden without script.
31
+
32
+ - Missing images: was "the category's drawing on a plate"; now each item's own subject on its plate and the same
33
+ drawing on its detail stage, a category drawing only when one object is true of every item; Never adds one
34
+ product's drawing on another product's card or detail page.
35
+ - Drawing size: the drawing box on a drawing stage or plate was 50% of the inner width; it is now 80% of the full
36
+ width, absolutely centered, so the ink spans 45 to 60%; the Never item names ink, not box.
37
+ - Hands: the demo's diverging wrist strokes that stopped at y 113-114 are replaced by palm sides that run on as a
38
+ near-parallel wrist off the bottom edge; step 1 requires wrists 18 to 26 units apart leaving the box; the
39
+ silhouette test adds "reads as a creature: the wrist is wrong"; Never adds hands that read as creatures.
40
+ - Stages: `aspect-ratio: 16 / 9` moved from `.stage` to `.stage--media`; card and chart stages hug their content
41
+ with `space.48` (`space.24` mobile) padding; under `breakpoint.md` cards on a stage stack in source order, all
42
+ fully visible, in dashboard and settings alike; never a horizontal strip.
43
+ - Subjects: step 1 names the app's domain over generic office work and bans a steaming mug, magnifier, receipt,
44
+ envelope, lost glove, sock or mitten unless the screen is about it; no two drawings in one app share a
45
+ silhouette; header drawings are compared side by side at 48px; straight-edged subjects (set squares, rulers,
46
+ frames) are out unless every edge is bowed. Not-found drawings come from the app's domain, not "something lost".
47
+ - Block rise: was "below-the-fold blocks start hidden"; now only script hides a block (`is-rise-ready`), blocks in
48
+ view on load are never hidden, and no script, print and reduced motion show final states.
49
+ - Verification: grid cards' drawings name their product; plan cards fully visible at 390px; card stages hug
50
+ content; drawings checked on the real screens, never in preview pages or scratch files; `components.md` lists
51
+ each drawing's subject by screen.
52
+ - Budget: SVG path data written without spaces; the stacked-table CSS block became one sentence; the Select menu
53
+ points at the Navigation menu; shorter pictogram and stage Where lines, blog and article layout lines, button
54
+ Use, rule 9 and several Never and Verification items.
55
+
56
+ ## 0.1.0-rc.3
57
+
58
+ Third candidate, answering the second critic round: no more empty pastel blocks, stages keep their proportions,
59
+ drawings read at small sizes and include hands more often, and a handful of product-screen details are pinned.
60
+
61
+ - Missing images: a grid image without a photo was a plate with a `size.iconLg` category icon bottom-left; it is
62
+ now a 4:3 plate holding the category's drawing at 50% of its width, final frame, one drawing per category; a
63
+ grid without categories drops the media slot. Article figures without an image are omitted.
64
+ - Stages: proportion now 16:9 to 1:1 (`.stage` gets `aspect-ratio: 16 / 9` as a minimum), never a banner; new
65
+ drawing stage (`stage--drawing`, 4:3, drawing at 50% of the inner width) for the blog home featured post and
66
+ for article or product images that are missing; a drawing on a stage spans 45 to 60% of its width.
67
+ - Rule 4: was "every section opens with a pictogram"; now prose sections open with a `size.pictoLg` drawing,
68
+ list sections (more posts, related, archive) open with the h2 alone, and a second opener in one viewport is
69
+ `size.pictoMd`.
70
+ - Pictogram recipe: the fixed subject list is replaced by a subject-picking method (task verb, three held
71
+ objects from the app's domain, clearest silhouette not yet drawn); no vehicles or toys, no subject the spec
72
+ names; at least half of each app's drawings include a hand; the defining feature is drawn first and oversized;
73
+ rectangular bodies are 2 to 4 bowed strokes; a 48px silhouette test.
74
+ - Never list: adds a plate or stage holding only an icon or nothing, a stage flatter than 16:9, and a drawing
75
+ under 45% of its stage or plate.
76
+ - Button: full width (`.btn--block`) keeps flex centering, never `display: block`.
77
+ - Navigation: new Menu entry (items `size.control`, `font: inherit`, start-aligned; sign out after a hairline,
78
+ same size).
79
+ - Table: toolbar row spec (title and count head, chips and visually labelled search `space.16` below); stacked
80
+ mobile tables hide the header row and sort with a native select.
81
+ - Avatar: tile color was "hashed from the name"; now (sum of char codes) mod 3 over sage, lilac and apricot.
82
+ - Motion: below-the-fold blocks start hidden and rise on entry; only reduced motion starts them final.
83
+ - Verification: stages and plates never empty, stage proportions, blog featured drawing stage, centered button
84
+ labels at 390px, silhouette test and hand share.
85
+ - Budget cuts: duplicated limits in the glyph, write-in and ring-control recipes, the connector-draw motion line,
86
+ the browser-chrome Never item (covered by components), card padding in Spacing, and assorted wording.
87
+
88
+ ## 0.1.0-rc.2
89
+
90
+ Second candidate, answering the first critic round: pictograms animate as one visible stepped strip, the working
91
+ glyph is honest, and product-screen layouts get explicit rules.
92
+
93
+ - Pictogram recipe: per-frame `cel-flash`/`cel-hold` animations replaced by one sprite strip (`celStrip`) stepped
94
+ with `steps(n, jump-none)`; pre-play state shows the first stroke, never a blank frame; reduced motion shows the
95
+ final frame. Drawing limits: no straight segment over 15 units, boxy subjects bowed; `size.pictoSm` version with
96
+ 5 to 7 strokes at 4.0 to 5.0 units and a 6/5 fill offset; the cup construction demo replaced by a hand holding
97
+ an egg (object, palm, thumb, noodle fingers, wrist).
98
+ - Never list: automation detection to skip animations; artificial delays for a working state; glyph beside a
99
+ button; a second `role="status"`; text or summaries on a stage; small card adrift on a stage; repeated centered
100
+ placeholder icons. Rule 9: glyph only while a real request is pending, inside the control.
101
+ - Motion: in-view-on-load entrances play immediately; nothing hidden more than one frame after entering view.
102
+ - Stage limits: holds a chart, media, pictogram, plan cards or ghosted preview; card at least 60% of inner width.
103
+ - Layout: product-screen header grid (drawing, h1, lede on one left edge, `body` lede), panel rows stretch with
104
+ titles inside cards, cart and checkout columns, store grid at 2 columns under `breakpoint.md`, article table of
105
+ contents after the lede on mobile, article h2 `h4` and h3 `h5`.
106
+ - Components: Avatar spec added (40px, `radius.tile`); stat change colored by meaning; toast reuses an existing
107
+ status region; footer inverse band only with two or more link groups; header product name aligned to sidebar
108
+ text; table row hover on raised cards uses `color.surface.subtle`; missing images use a bottom-left category
109
+ icon in grids and a `size.pictoXl` drawing on detail stages.
110
+ - Reduced motion: entrances never hide content; everything renders in its final state.
111
+ - Budget cuts: the streaming caret companion, the FAQ, toast and dialog rows of Applying the concept, the radius
112
+ list (now one line), duplicate button-hover lines in Interaction and two Never items already covered by rules 2
113
+ and 3.
114
+
115
+ ## 0.1.0-rc.1
116
+
117
+ First release candidate of Inkleaf, derived from the approved reference analysis (captured 2026-10-13).
118
+
119
+ - Tokens: warm paper and ink neutrals, a single warm accent for focus, send square, working glyph and one hero
120
+ drawing; four stage pastels with derived night values; pictogram ink and fills; derived moss, ochre and harbor
121
+ status colors beside the observed brick error; Source Serif 4, Instrument Sans and JetBrains Mono; pixel-named
122
+ spacing; radii 4 to 32; ring widths 1 and 2px; hover, float and overlay shadows; motion from 83ms frames to
123
+ 1200ms breaths on a settle curve; breakpoints 834, 992 and 1200.
124
+ - Principles: ten rules, a Never list, and five signature recipes (cel-drawn ink pictogram, pastel stage, serif
125
+ write-in heading, written-loop working glyph, ink-and-paper ring controls), Interaction, Motion with reduced
126
+ motion, and an Applying the concept table covering 16 contexts including dashboards, tables, settings and stores.
127
+ - Components: the 12 required plus empty state, all specified with states.
@@ -0,0 +1,42 @@
1
+ ## New project
2
+
3
+ 1. Inspect the stack; propose an installation plan and wait for approval.
4
+ 2. Copy `dist/tokens.css` into global styles (or map `dist/tokens.json` into the theme); never retype values. Dark follows `prefers-color-scheme` unless `[data-theme]` overrides it.
5
+ 3. Load Source Serif 4 400/500, Instrument Sans 400/500, JetBrains Mono 400 (`font-display:swap`). Root: the brush filters, global `:focus-visible`, the reduced-motion block, `html, body{overflow-x:clip}`, body `color.surface.default`, `color.text.primary`, sans `font.size.body`, and `pre, code, kbd, samp{font-family:var(--font-family-mono); font-size:var(--font-size-small)}`.
6
+ 4. Build the signature elements first, as reusable pieces: pictogram (stepped strip, play once, boil), stage, drawing stage and plate, write-in heading, working glyph and breathing dot, ring-control classes.
7
+ 5. Build the components with every state; place the signature elements per Applying the concept.
8
+ 6. Create `design-system/` with `VERSION` (system id and the spec's update date, e.g. `inkleaf, updated 2026-01-31`), the tokens, and `components.md`: conventions, where each signature element is used, each drawing's subject by screen and its one word.
9
+ 7. Add to `AGENTS.md` and `CLAUDE.md` (create if missing): "UI work follows design-system/ — read it before building screens."
10
+
11
+ ## Existing project
12
+
13
+ 1. Audit styling, components, routes and tests; propose a migration plan (what changes, what stays) and wait for approval.
14
+ 2. Map the tokens onto existing variables; a brand action color becomes `color.action.primary`, never the accent.
15
+ 3. Build the signature elements, then restyle one representative screen as a sample (header pictogram, written-in h1, its stage where allowed) and wait for review.
16
+ 4. Continue screen by screen, one commit each. Never add delays, toasts or `role="status"` regions the screen did not have. Missing images become drawings (Imagery); spinners the glyph.
17
+ 5. Preserve routes, logic, API contracts, form fields, validation, test ids, text used by tests and accessibility attributes. Restyle existing markup; never replace native inputs.
18
+ 6. Finish with `design-system/` and the pointer lines from New project.
19
+
20
+ ## Ongoing development
21
+
22
+ - Read `design-system/` first; semantic tokens and existing components, no hard-coded values.
23
+ - Every new screen gets the signature elements its context calls for; a screen without them is unfinished. New pictograms get new subjects.
24
+ - Uncovered needs: compose from tokens and note it in `components.md`; conflicting requests (brand color, spinner, bold headings): present options.
25
+
26
+ ## Verification
27
+
28
+ - Build and tests pass; no raw colors, sizes or radii outside the token files.
29
+ - Review under reduced-motion emulation (every block, frame and heading final); never change code, motion or config to review.
30
+ - Each screen at 1440px and 390px, light and dark: no sideways scroll; statuses and row actions visible; product headers identical; panel rows share edges; button labels vertically centered; no text in the browser's bare `monospace` or `serif`.
31
+ - Stages and plates hold content (never a flat block or lone icon); drawing and media stages 16:9 to 1:1, card and chart stages hug content; at 390px every plan card is fully visible. Each grid card's drawing names its product and matches its detail page; its plate color is the same everywhere.
32
+ - Zoom one plate and one drawing stage to 200% at 1440px: strokes continuous, no notches, full in the middle and thin at the ends, ink within the weight targets; the featured drawing never outweighs the h1. On every plate and stage each wrist reaches the edge, the object stays at least `space.16` inside every edge, and at most 35% of the height is empty above the drawing.
33
+ - Tab through every screen (focus always visible); click every checkbox, radio and switch (each toggles).
34
+ - Pictograms: original subjects, none banned, no two alike in one app; every drawing, plates included, passes the one-word test; frames = strokes + 3, none repeated; each steps in as one strip (never blank, no marks from a neighboring frame) and holds. h1s write in once; the glyph runs only during real requests.
35
+ - Line up the app's hands: at least half the drawings; long, separate fingers; no pose on over a third; neighboring plates differ.
36
+ - Check drawings on the real screens; never create preview pages, galleries or scratch files.
37
+ - Walk the ten rules and the Never list. Report files changed, what you could not apply, and your decisions.
38
+
39
+ ## Rollback
40
+
41
+ - Each screen is its own commit and can be reverted alone.
42
+ - Tokens, fonts, root setup and signature pieces are separate commits, reverted last.
@@ -0,0 +1,8 @@
1
+ ### Avatar
2
+
3
+ **Anatomy.** A `size.control` (40px) square tile, `radius.tile`, serif initials `font.size.h5`, 400,
4
+ `color.text.primary`; a photo fills the tile, clipped. Tile color: (sum of the name's char codes) mod 3 picks
5
+ `color.stage.sage`, `color.stage.lilac` or `color.stage.apricot` (header account avatar too).
6
+ Never smaller, except the header account button (`size.controlSm`). Stacks overlap by `space.8` with a 2px `color.surface.default` ring.
7
+
8
+ **States.** Linked: focus-visible outline, no hover change. `alt=""` beside a visible name, otherwise the name.
@@ -0,0 +1,8 @@
1
+ ### Badge
2
+
3
+ **Anatomy.** Height `space.24`, padding 0 `space.8`, `radius.badge`, `font.size.caption`, 500, gap `space.6`.
4
+
5
+ **Variants.** Outline: inset 1px `color.border.control` ring, `color.text.secondary`. Stone:
6
+ `color.action.tertiary`, `color.text.primary`. Status: outline plus a `space.6` dot and text in `color.status.*`;
7
+ running dots breathe; never a filled status color or pastel. Count: `radius.full`, `space.20` min, ink fill,
8
+ `color.text.onAction`, `aria-label` ("3 items in cart"). A state marker ("Current plan") is a badge, never ink.
@@ -0,0 +1,16 @@
1
+ ### Button
2
+
3
+ **Use.** One ink primary per region, paper secondary for the rest, stone tertiary for small utilities; danger
4
+ only confirms a destructive action.
5
+
6
+ **Anatomy.** The ring-controls recipe. Small `size.controlSm`, padding 0 `space.12`, `font.size.small`; default
7
+ `size.control`, 0 `space.16`; large `size.controlLg`, 0 `space.24`, `font.size.body`. Optional `size.icon` icon,
8
+ gap `space.8`; icon-only is square with `aria-label`. Full width
9
+ (`.btn--block`): `display:flex; width:100%`, still centered, never `display:block`. Labels 500, verb first, 1 to 3 words. On
10
+ `color.surface.inverse`: primary fill `color.text.inverse`, label `color.surface.inverse`; ghost transparent,
11
+ `color.text.inverse`, 1px `color.border.inverse` ring.
12
+
13
+ **States.** Hover, focus-visible, active: Interaction. Disabled: `color.surface.subtle`, `color.text.disabled`.
14
+ Loading, only while a real request is pending: glyph and gerund replace the label inside the button, width held,
15
+ `aria-busy="true"`. Danger: `color.action.danger`,
16
+ `color.text.onAction`, hover ring in the same color.
@@ -0,0 +1,14 @@
1
+ ### Card
2
+
3
+ **Anatomy.** `color.surface.raised`, `radius.card`, inset 1px `color.border.default` ring, padding `space.32`
4
+ (`space.24` on product screens and mobile), no resting shadow. Title serif `font.size.h5`, 500; body
5
+ `font.size.ui`, `color.text.secondary`; meta `font.size.caption`, `color.text.tertiary`.
6
+
7
+ **Variants.** Feature or plan: `size.pictoMd` drawing top-left; plan name serif `font.size.plan`; price sans
8
+ `font.size.h3`; full-width primary; checklist with 16px check-circles. Media: a 4:3 plate on top (photo or the item's own drawing),
9
+ body padding `space.20`. Stat: label `font.size.small`; value sans `font.size.h3`, `tabular-nums`; change
10
+ `font.size.caption`, colored by meaning, not sign (fewer incidents is success), secondary if unknown. Numbered trio: hairline top rule, mono "01", serif title.
11
+
12
+ **States.** Linked hover and focus-visible: Interaction; one stretched title link. Selected: 2px inset
13
+ `color.action.primary` ring. Loading: static `color.surface.subtle` skeleton bars. Error: a `color.status.danger`
14
+ line and a secondary "Retry".
@@ -0,0 +1,11 @@
1
+ ### Checkbox and radio
2
+
3
+ **Anatomy.** The native `<input>` with `appearance:none` is the visible control and takes every click:
4
+ `size.iconLg` square, `radius.badge` (radio `radius.full`), `color.surface.raised`, inset 1px `color.border.check`
5
+ ring. The tick is a sibling 12px SVG (1.75px stroke, `color.text.onAction`) over the box with
6
+ `pointer-events:none`; the radio dot is an inset 5px `color.action.primary` ring on a `color.text.onAction` fill.
7
+ Label `font.size.ui` at `space.8`; rows at least `size.touch`; groups in a `<fieldset>`.
8
+
9
+ **States.** Hover: ring `color.text.primary`. Focus-visible: outline. Selected: `color.action.primary` fill,
10
+ `motion.duration.press`. Disabled: `color.surface.subtle`, ring `color.border.default`, label
11
+ `color.text.disabled`. Error: ring `color.status.danger`, message under the group.
@@ -0,0 +1,10 @@
1
+ ### Dialog
2
+
3
+ **Anatomy.** Native `<dialog>` with `showModal()`. `color.surface.raised`, `radius.stage`, padding `space.32`,
4
+ width `min(var(--size-dialog), 100% - 2 * var(--space-16))`, centered, `shadow.overlay`; backdrop `color.surface.overlay`. Title serif `font.size.h4`, 500, static; body `font.size.ui`,
5
+ `color.text.secondary`; actions right, `space.32` below, gap `space.12`: cancel secondary, confirm primary or
6
+ danger; stacked full width under `breakpoint.md`.
7
+
8
+ **States.** Open and close per Motion. Focus-visible on open: first field, or cancel for destructive confirms.
9
+ Loading: glyph in confirm, others disabled. Error: `color.status.danger` line above actions, `role="alert"`.
10
+ Escape closes; focus returns to the trigger.
@@ -0,0 +1,8 @@
1
+ ### Empty state
2
+
3
+ **Anatomy.** Centered, max width `size.dialog`, padding `space.64` `space.24`: a `size.pictoLg` drawing
4
+ (`size.pictoMd` in cards and tables) that plays once, `space.24`, serif `font.size.h4` title written in, `space.8`,
5
+ one `color.text.secondary` line, `space.24`, one action. First
6
+ run: on a small lilac stage over a ghosted skeleton (raised cards at 60% opacity, no text).
7
+
8
+ **Variants.** Filtered-empty: the title names the filter, the action clears it. Not found and error: `size.pictoXl`, serif h1, two actions.
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": [
3
+ { "name": "button", "category": "action", "status": "specified", "file": "button.md", "states": ["default", "hover", "focus-visible", "active", "disabled", "loading"] },
4
+ { "name": "text-field", "category": "form", "status": "specified", "file": "text-field.md", "states": ["default", "hover", "focus-visible", "disabled", "loading", "error"] },
5
+ { "name": "select", "category": "form", "status": "specified", "file": "select.md", "states": ["default", "hover", "focus-visible", "selected", "disabled", "error"] },
6
+ { "name": "checkbox", "category": "form", "status": "specified", "file": "checkbox.md", "states": ["default", "hover", "focus-visible", "selected", "disabled", "error"] },
7
+ { "name": "switch", "category": "form", "status": "specified", "file": "switch.md", "states": ["default", "hover", "focus-visible", "selected", "disabled", "loading"] },
8
+ { "name": "tabs", "category": "navigation", "status": "specified", "file": "tabs.md", "states": ["default", "hover", "focus-visible", "selected", "disabled"] },
9
+ { "name": "navigation", "category": "navigation", "status": "specified", "file": "navigation.md", "states": ["default", "hover", "focus-visible", "selected"] },
10
+ { "name": "card", "category": "surface", "status": "specified", "file": "card.md", "states": ["default", "hover", "focus-visible", "selected", "loading", "error"] },
11
+ { "name": "table", "category": "data-display", "status": "specified", "file": "table.md", "states": ["default", "hover", "focus-visible", "selected", "loading", "error"] },
12
+ { "name": "badge", "category": "data-display", "status": "specified", "file": "badge.md", "states": ["default"] },
13
+ { "name": "dialog", "category": "overlay", "status": "specified", "file": "dialog.md", "states": ["default", "focus-visible", "loading", "error"] },
14
+ { "name": "toast", "category": "feedback", "status": "specified", "file": "toast.md", "states": ["default", "focus-visible", "error"] },
15
+ { "name": "empty-state", "category": "feedback", "status": "specified", "file": "empty-state.md", "states": ["default", "error"] },
16
+ { "name": "avatar", "category": "data-display", "status": "specified", "file": "avatar.md", "states": ["default", "focus-visible"] }
17
+ ]
18
+ }
@@ -0,0 +1,26 @@
1
+ ### Navigation
2
+
3
+ **Header.** `size.header`, sticky, `color.surface.default`, 1px `color.border.default` bottom ring. Product name
4
+ as serif `font.size.h5` text (no invented logo); in a product shell its left edge lines up with the sidebar item
5
+ text (`space.16` + `space.12` from the edge). Links `font.size.ui`, `color.text.secondary`, padded boxes
6
+ `size.controlSm` high, gap `space.16`; current `color.text.primary`, 500, `aria-current="page"`. Right: small
7
+ secondary, then small primary. Below `breakpoint.lg`, more than 3 links collapse into a `size.touch` menu button
8
+ opening a sheet of hairline `size.touch` rows.
9
+
10
+ **Menu** (account, row actions). `color.surface.raised`, `radius.tile`, `shadow.overlay`, padding `space.6`. Every item, link or
11
+ `<button>`, is `size.control` high, full width, `display:flex; align-items:center`, padding 0 `space.12`,
12
+ `font:inherit` at `font.size.ui`, `text-align:start`, `radius.control`. A header line (the email): caption,
13
+ tertiary, not interactive. Sign out and destructive items come last after a hairline, same size and inset.
14
+
15
+ **Sidebar** (from `breakpoint.lg`). `size.sidebar`, right hairline, padding `space.16`; items `size.control`,
16
+ `radius.control`, padding 0 `space.12`, 16px icon; current `color.surface.subtle`, `color.text.primary`, 500; group
17
+ labels `font.size.micro` uppercase `color.text.tertiary`. Below `breakpoint.lg` it collapses into the header's
18
+ `size.touch` menu button and sheet, in every product app; never a link grid above the content.
19
+
20
+ **Breadcrumb.** `font.size.small`, `color.text.secondary`, "/" in `color.text.tertiary`, `space.16` below.
21
+
22
+ **Footer.** Content sites with two or more link groups: `color.surface.inverse` band, padding `space.96` 0,
23
+ caption link columns in `color.text.inverseMuted`. Otherwise, and in product apps: a hairline, then one caption
24
+ line, padding `space.24` 0.
25
+
26
+ **A11y.** `<nav aria-label>`, skip link, Escape closes menus.
@@ -0,0 +1,10 @@
1
+ ### Select
2
+
3
+ **Anatomy.** Text field's box, label, hint and error, `appearance:none`, a 16px chevron in
4
+ `color.text.secondary` at `space.12` from the right. Prefer native `<select>`. Custom menu: the Navigation menu's
5
+ surface and items, max height 320px; selected option 500 with a 16px check.
6
+
7
+ **States.** Hover, focus-visible, disabled, error: as Text field. Open: chevron rotates 180°, menu per Motion.
8
+ Option hover and keyboard-active: `color.surface.hover`.
9
+
10
+ **A11y.** Custom menus: `listbox`, `aria-expanded`, arrows, typeahead, Escape returns focus.
@@ -0,0 +1,11 @@
1
+ ### Switch
2
+
3
+ **Anatomy.** `<input type="checkbox" role="switch">` with `appearance:none` is the track: `size.control` x
4
+ `space.24`, `radius.full`, `color.surface.subtle`, inset 1px `color.border.check` ring. Thumb: sibling span,
5
+ `space.16` circle, `color.surface.raised`, `pointer-events:none`, `space.4` inset. Settings rows: label left,
6
+ `font.size.small` secondary description below, at least `size.touch`, hairline below.
7
+
8
+ **States.** Hover: ring `color.text.primary`. Focus-visible: outline on the track. Selected: track
9
+ `color.action.primary`, thumb `color.text.onAction`, moved `space.16` over `motion.duration.hover`,
10
+ `motion.easing.settle`. Disabled: ring `color.border.default`, label `color.text.disabled`. Applies immediately,
11
+ never behind a Save button.
@@ -0,0 +1,25 @@
1
+ ### Table
2
+
3
+ **Anatomy.** In a clipped card or bare. Head row `size.rowDense`, eyebrow type (Typography). Rows `size.rowDense` (comparison `size.row`), `font.size.dense`, hairlines only, no
4
+ zebra; cells padding 0 `space.16`; first column `color.text.primary`, others secondary; numbers right,
5
+ `tabular-nums`; `table-layout:fixed`. A header cell aligns like its cells (numbers right; text and badges start),
6
+ never at the card edge over a column whose content starts elsewhere. Group rows: serif `font.size.h5`. Booleans: check-circle or thin cross. Status: badge, running ones breathe. Row actions last:
7
+ small secondary buttons or a `size.controlSm` menu button.
8
+
9
+ **Toolbar** (a table in a card). Card head: one row, serif `font.size.h5` title left, count `font.size.caption`
10
+ `color.text.tertiary` right. Toolbar row `space.16` below: filter chips left, search right, both `size.control` high,
11
+ centered; the search label visually hidden, never stacked above the field.
12
+
13
+ **States.** Hover, focus-visible, selected: Interaction. Sortable header: a button, `aria-sort`, sorted column
14
+ `color.text.primary` with a 12px arrow. Loading: 5 static skeleton rows. Empty: the empty state with a
15
+ `size.pictoMd` drawing. Error: danger line and "Retry".
16
+
17
+ **Mobile** (under `breakpoint.md`): tables with status or actions stack and hide the header row; sorting moves to a native "Sort by" select in the toolbar. Others scroll in a
18
+ labelled `role="region"` wrapper, `tabindex="0"`.
19
+
20
+ Stacked: `thead` visually hidden; table, `tbody`, `tr`, `td` `display:block`; rows padded `space.12` `space.16`
21
+ over a hairline; each `td` a grid `minmax(96px, 38%) 1fr`, gap `space.12`, its `data-label` in a `::before` at
22
+ `font.size.caption`, `color.text.tertiary`. Every stacked label and value is start-aligned: reset numeric
23
+ `text-align:right` there.
24
+
25
+ Keep `<table>`, explicit `role`s, `data-label` per cell, `<th scope>`.
@@ -0,0 +1,12 @@
1
+ ### Tabs
2
+
3
+ **Variants.** Segmented (views of one thing, up to 5): track `color.surface.subtle`, `radius.tile`, padding
4
+ `space.4`; segments `size.controlSm`, padding 0 `space.16`, `radius.control`, `color.text.secondary`; selected: a
5
+ `color.surface.raised` pill with 1px `color.border.default` ring, `color.text.primary`, 500. Underline (page
6
+ sections): tabs `size.control`, gap `space.24`, hairline below; selected `color.text.primary`, 500, 2px
7
+ `color.action.primary` rule.
8
+
9
+ **States.** Hover, focus-visible, selected: Interaction. Disabled: `color.text.disabled`, skipped by arrows.
10
+ Mobile: underline tabs scroll in their own strip; segmented go full width.
11
+
12
+ **A11y.** `tablist`, `tab`, `tabpanel`, `aria-selected`, arrows. Filter chips: `aria-pressed` buttons.
@@ -0,0 +1,12 @@
1
+ ### Text field
2
+
3
+ **Anatomy.** Label above (`font.size.ui`, 500), `space.8`; control `size.control` high (never shrinks in a flex
4
+ row), `radius.control`, `color.surface.raised`, inset 1px `color.border.strong` ring, padding 0 `space.12`,
5
+ placeholder `color.text.tertiary`; hint `space.6` below, `font.size.small`, `color.text.secondary`. Search: 16px
6
+ icon at `space.12`, text from `space.40`, own clear button (hide `::-webkit-search-cancel-button`). Textarea: min 3 rows, padding `space.12`. Prompt field:
7
+ `radius.tile`, send square at the right.
8
+
9
+ **States.** Hover: ring `color.border.check`. Focus-visible: outline, ring unchanged. Disabled:
10
+ `color.surface.subtle`, `color.text.disabled`. Error: ring `color.status.danger`; the message replaces the hint in
11
+ `color.status.danger`, `aria-invalid`, `aria-describedby`; no red fill. A failed submit adds "2 fields need attention"
12
+ above the actions (`role="alert"`) and focuses the first invalid field. Loading: glyph inside, right.
@@ -0,0 +1,8 @@
1
+ ### Toast
2
+
3
+ **Anatomy.** Bottom-right at `space.24` (mobile: bottom, full width minus `space.16`). `color.surface.raised`,
4
+ `radius.tile`, inset 1px `color.border.default` ring, `shadow.overlay`, padding `space.12` `space.16`, max width
5
+ `size.dialog`, `font.size.ui`. Leading: the glyph while running, a 16px check-circle on success. Optional action, close button. Error: 2px inset `color.status.danger` left rule.
6
+
7
+ **States.** Enter and leave per Motion; at most 3, gap `space.8`. Success dismisses after 5s, paused on hover or focus;
8
+ errors stay. `role="status"` (errors `alert`); if the screen already has a status region, "Saved" goes there instead.