pigment-mcp 0.4.1 → 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.
- package/cli.js +76 -5
- package/content/registry.json +12 -0
- package/content/shots.json +18 -0
- package/content/templates/inkleaf/1.0.0/CHANGELOG.md +127 -0
- package/content/templates/inkleaf/1.0.0/agent/instructions.md +42 -0
- package/content/templates/inkleaf/1.0.0/components/avatar.md +8 -0
- package/content/templates/inkleaf/1.0.0/components/badge.md +8 -0
- package/content/templates/inkleaf/1.0.0/components/button.md +16 -0
- package/content/templates/inkleaf/1.0.0/components/card.md +14 -0
- package/content/templates/inkleaf/1.0.0/components/checkbox.md +11 -0
- package/content/templates/inkleaf/1.0.0/components/dialog.md +10 -0
- package/content/templates/inkleaf/1.0.0/components/empty-state.md +8 -0
- package/content/templates/inkleaf/1.0.0/components/index.json +18 -0
- package/content/templates/inkleaf/1.0.0/components/navigation.md +26 -0
- package/content/templates/inkleaf/1.0.0/components/select.md +10 -0
- package/content/templates/inkleaf/1.0.0/components/switch.md +11 -0
- package/content/templates/inkleaf/1.0.0/components/table.md +25 -0
- package/content/templates/inkleaf/1.0.0/components/tabs.md +12 -0
- package/content/templates/inkleaf/1.0.0/components/text-field.md +12 -0
- package/content/templates/inkleaf/1.0.0/components/toast.md +8 -0
- package/content/templates/inkleaf/1.0.0/dist/agent.md +921 -0
- package/content/templates/inkleaf/1.0.0/dist/tokens.css +283 -0
- package/content/templates/inkleaf/1.0.0/dist/tokens.json +462 -0
- package/content/templates/inkleaf/1.0.0/manifest.json +77 -0
- package/content/templates/inkleaf/1.0.0/principles/visual-language.md +449 -0
- package/content/templates/inkleaf/1.0.0/tokens/primitives.json +214 -0
- package/content/templates/inkleaf/1.0.0/tokens/semantic.dark.json +69 -0
- package/content/templates/inkleaf/1.0.0/tokens/semantic.light.json +69 -0
- 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
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
});
|
|
@@ -34897,7 +34968,7 @@ var init_package = __esm({
|
|
|
34897
34968
|
"package.json"() {
|
|
34898
34969
|
package_default = {
|
|
34899
34970
|
name: "@pigment/mcp",
|
|
34900
|
-
version: "0.
|
|
34971
|
+
version: "0.5.0",
|
|
34901
34972
|
private: true,
|
|
34902
34973
|
type: "module",
|
|
34903
34974
|
description: "MCP server for Pigment design systems: tokens, rules, component specs and installation guides for your coding agent.",
|
package/content/registry.json
CHANGED
|
@@ -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
|
}
|
package/content/shots.json
CHANGED
|
@@ -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.
|