@brunosouzas/design-system 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/README.md +210 -2
- package/dist/scripts/cover.d.ts +25 -0
- package/dist/scripts/cover.js +157 -0
- package/dist/src/pieces/cover.d.ts +32 -0
- package/dist/src/pieces/cover.js +250 -0
- package/dist/src/pieces/eip-icons.d.ts +12 -0
- package/dist/src/pieces/eip-icons.js +525 -0
- package/dist/src/pieces/index.d.ts +9 -0
- package/dist/src/pieces/index.js +14 -0
- package/dist/src/pieces/library.d.ts +297 -0
- package/dist/src/pieces/library.js +377 -0
- package/dist/src/pieces/metro.d.ts +93 -0
- package/dist/src/pieces/metro.js +205 -0
- package/dist/src/pieces/node.d.ts +27 -0
- package/dist/src/pieces/node.js +52 -0
- package/dist/src/pieces/params.d.ts +76 -0
- package/dist/src/pieces/params.js +147 -0
- package/dist/src/pieces/piece.d.ts +9 -0
- package/dist/src/pieces/piece.js +11 -0
- package/dist/src/pieces/pose.d.ts +16 -0
- package/dist/src/pieces/pose.js +30 -0
- package/dist/src/pieces/react.d.ts +4 -0
- package/dist/src/pieces/react.js +13 -0
- package/dist/src/pieces/render.d.ts +2 -0
- package/dist/src/pieces/render.js +21 -0
- package/dist/tokens/generated/tokens.d.ts +244 -0
- package/dist/tokens/generated/tokens.js +244 -0
- package/package.json +78 -4
- package/tokens/generated/tokens.css +160 -0
- package/tokens/generated/tokens.json +243 -0
- package/tokens/generated/tokens.ts +247 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// The illustration pieces (spec illustration-pieces, "Piece catalogue";
|
|
2
|
+
// design §4). Each one is a pure function of its parameters and the tokens.
|
|
3
|
+
// Positions and sizes are cover pixels; colours are token paths, resolved here.
|
|
4
|
+
//
|
|
5
|
+
// Geometry: ported from the approved prototype
|
|
6
|
+
// (~/.hermes/spikes/design-system/illustration-pieces/pieces.ts), or, for the
|
|
7
|
+
// pieces it lacked, measured from the published covers named in design §4.
|
|
8
|
+
// Words: a piece draws only the words its caller passes (ADR 0004).
|
|
9
|
+
import { tokens } from "../../tokens/generated/tokens.js";
|
|
10
|
+
import { EIP_ICONS, EIP_ICON_NAMES } from "./eip-icons.js";
|
|
11
|
+
import { drawn, el, g, r2, shadowed } from "./node.js";
|
|
12
|
+
import { col, colour, count, label, MAX_SIZE, PATH_DATA, pos, size } from "./params.js";
|
|
13
|
+
import { definePiece } from "./piece.js";
|
|
14
|
+
import { METRO_PIECES } from "./metro.js";
|
|
15
|
+
export * from "./metro.js";
|
|
16
|
+
const ink = tokens.color.ink;
|
|
17
|
+
const mono = tokens.font.mono;
|
|
18
|
+
/** Pill label centred on (x, y), usually overlapping the top edge of what it names. */
|
|
19
|
+
export const pill = definePiece("pill", { x: pos(), y: pos(), text: label(), fill: col(), width: size() }, ({ x, y, text, fill = "color.white", width }) => {
|
|
20
|
+
const h = tokens.cover.pillHeight;
|
|
21
|
+
const w = width ?? Math.max(68, Math.round(text.length * 10.5 + 34));
|
|
22
|
+
return drawn([el("rect", { x: x - w / 2, y: y - h / 2, width: w, height: h, rx: h / 2, fill: colour(fill, "fill") })], [el("text", { x, y: y + 7 }, text)]);
|
|
23
|
+
});
|
|
24
|
+
// ---------- background ----------
|
|
25
|
+
export const RING_COUNT = 14;
|
|
26
|
+
/** Concentric rings around (cx, cy): 14 rings, step and width from the cover tokens. */
|
|
27
|
+
export const rings = definePiece("rings", { cx: pos(), cy: pos(), radius: size(), color: col() }, ({ cx, cy, radius = tokens.cover.ringRadius, color = "color.ring" }) => drawn([
|
|
28
|
+
g({ fill: "none", stroke: colour(color, "color"), "stroke-width": tokens.cover.ringWidth }, ...Array.from({ length: RING_COUNT }, (_, i) => el("circle", { cx, cy, r: radius + i * tokens.cover.ringStep }))),
|
|
29
|
+
]));
|
|
30
|
+
const rowSchema = {
|
|
31
|
+
lead: { kind: "enum", values: ["none", "checkbox", "checked", "question"] },
|
|
32
|
+
bar: size(true),
|
|
33
|
+
tail: { kind: "enum", values: ["none", "check"] },
|
|
34
|
+
};
|
|
35
|
+
/** A sheet of paper with rows of text bars, an optional lead mark and an optional tick at the end. */
|
|
36
|
+
export const sheet = definePiece("sheet", {
|
|
37
|
+
x: pos(),
|
|
38
|
+
y: pos(),
|
|
39
|
+
width: size(true, 80),
|
|
40
|
+
height: size(true),
|
|
41
|
+
rows: { kind: "array", required: true, of: { kind: "object", fields: rowSchema } },
|
|
42
|
+
top: size(),
|
|
43
|
+
step: size(),
|
|
44
|
+
fill: col(),
|
|
45
|
+
leadFill: col(),
|
|
46
|
+
tick: col(),
|
|
47
|
+
}, ({ x, y, width, height, rows, top = 46, step = 38, fill = "color.white", leadFill = "color.caller", tick = "illustration.accent" }) => {
|
|
48
|
+
const marks = [];
|
|
49
|
+
const bars = [];
|
|
50
|
+
const ticks = [];
|
|
51
|
+
const glyphs = [];
|
|
52
|
+
rows.forEach((row, i) => {
|
|
53
|
+
const cy = y + top + i * step + 11;
|
|
54
|
+
let barX = x + 22;
|
|
55
|
+
if (row.lead === "checkbox" || row.lead === "checked") {
|
|
56
|
+
const checked = row.lead === "checked";
|
|
57
|
+
marks.push(el("rect", { x: x + 22, y: cy - 11, width: 22, height: 22, rx: 5, fill: checked ? colour(leadFill, "leadFill") : tokens.color.white }));
|
|
58
|
+
if (checked)
|
|
59
|
+
ticks.push(el("path", { d: `M${x + 27},${cy} L${x + 32},${cy + 6} L${x + 40},${cy - 6}`, stroke: colour(tick, "tick") }));
|
|
60
|
+
barX = x + 56;
|
|
61
|
+
}
|
|
62
|
+
else if (row.lead === "question") {
|
|
63
|
+
marks.push(el("circle", { cx: x + 30, cy, r: 13, fill: tokens.color.transitional }));
|
|
64
|
+
glyphs.push(el("text", { x: x + 30, y: cy + 7, "font-size": 19, "font-weight": tokens.font.weightHeavy }, "?"));
|
|
65
|
+
barX = x + 52;
|
|
66
|
+
}
|
|
67
|
+
bars.push(el("rect", { x: barX, y: cy - 5, width: row.bar, height: 10, rx: 5 }));
|
|
68
|
+
if (row.tail === "check") {
|
|
69
|
+
const tx = x + width - 40;
|
|
70
|
+
marks.push(el("rect", { x: tx, y: cy - 11, width: 22, height: 22, rx: 5, fill: tokens.color.healthy }));
|
|
71
|
+
ticks.push(el("path", { d: `M${tx + 5},${cy} L${tx + 10},${cy + 6} L${tx + 18},${cy - 6}`, stroke: ink }));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return drawn([
|
|
75
|
+
...shadowed(el("rect", { x, y, width, height, rx: 14, fill: colour(fill, "fill") })),
|
|
76
|
+
g({ "stroke-width": tokens.outline.small }, ...marks),
|
|
77
|
+
g({ fill: "none", "stroke-width": 3.5, "stroke-linecap": "round" }, ...ticks),
|
|
78
|
+
g({ stroke: "none", fill: tokens.illustration.textBar }, ...bars),
|
|
79
|
+
], glyphs);
|
|
80
|
+
});
|
|
81
|
+
const CODE_COLOURS = { healthy: tokens.color.healthy, caller: tokens.color.caller, white: tokens.color.white, backend: tokens.color.backend };
|
|
82
|
+
/** Dark code panel with coloured lines; a tall line marks the one under review. */
|
|
83
|
+
export const codePanel = definePiece("codePanel", {
|
|
84
|
+
x: pos(),
|
|
85
|
+
y: pos(),
|
|
86
|
+
width: size(true),
|
|
87
|
+
height: size(true),
|
|
88
|
+
lines: {
|
|
89
|
+
kind: "array",
|
|
90
|
+
required: true,
|
|
91
|
+
of: {
|
|
92
|
+
kind: "object",
|
|
93
|
+
fields: { indent: { kind: "number", required: true, min: 0, max: MAX_SIZE }, w: size(true), color: { kind: "enum", required: true, values: Object.keys(CODE_COLOURS) }, tall: { kind: "boolean" } },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
top: size(),
|
|
97
|
+
step: size(),
|
|
98
|
+
}, ({ x, y, width, height, lines, top = 38, step = 22 }) => drawn([
|
|
99
|
+
...shadowed(el("rect", { x, y, width, height, rx: 14, fill: tokens.illustration.codePanel })),
|
|
100
|
+
g({ stroke: "none" }, ...lines.map((line, i) => el("rect", {
|
|
101
|
+
x: x + 20 + line.indent,
|
|
102
|
+
y: y + top + i * step - (line.tall ? 3 : 0),
|
|
103
|
+
width: line.w,
|
|
104
|
+
height: line.tall ? 16 : 10,
|
|
105
|
+
rx: line.tall ? 8 : 5,
|
|
106
|
+
fill: CODE_COLOURS[line.color],
|
|
107
|
+
}))),
|
|
108
|
+
]));
|
|
109
|
+
/** Chat slip with a tail at the bottom left and one white text line. */
|
|
110
|
+
export const speechBubble = definePiece("speechBubble", { x: pos(), y: pos(), width: size(true, 60), height: size(true, 50), fill: col() }, ({ x, y, width: w, height: h, fill = "color.caller" }) => {
|
|
111
|
+
const r = 12;
|
|
112
|
+
const b = y + h;
|
|
113
|
+
const d = `M${x},${y + r} Q${x},${y} ${x + r},${y} H${x + w - r} Q${x + w},${y} ${x + w},${y + r} V${b - r} Q${x + w},${b} ${x + w - r},${b} H${x + 44} L${x + 20},${b + 20} L${x + 24},${b} H${x + r} Q${x},${b} ${x},${b - r} Z`;
|
|
114
|
+
return drawn([
|
|
115
|
+
...shadowed(el("path", { d, fill: colour(fill, "fill") })),
|
|
116
|
+
el("rect", { x: x + 22, y: y + 31, width: w - 44, height: 13, rx: 6.5, fill: tokens.color.white, stroke: "none" }),
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
/** Browser window: title bar with three dots, a hero block and two text lines. */
|
|
120
|
+
export const browserWindow = definePiece("browserWindow", { x: pos(), y: pos(), width: size(false, 60), height: size(false, 130) }, ({ x, y, width: w = 200, height: h = 140 }) => drawn([
|
|
121
|
+
...shadowed(el("rect", { x, y, width: w, height: h, rx: 12, fill: tokens.color.white })),
|
|
122
|
+
el("path", { d: `M${x},${y + 30} H${x + w}`, fill: "none" }),
|
|
123
|
+
...[tokens.color.backend, tokens.color.transitional, tokens.color.healthy].map((fill, i) => el("circle", { cx: x + 18 + i * 16, cy: y + 15, r: 5, fill, "stroke-width": 2.5 })),
|
|
124
|
+
el("rect", { x: x + 20, y: y + 46, width: w - 40, height: 40, rx: 8, fill: tokens.color.caller, "stroke-width": tokens.outline.small }),
|
|
125
|
+
el("rect", { x: x + 20, y: y + 100, width: r2(w * 0.6), height: 10, rx: 5, fill: tokens.illustration.textBar, stroke: "none" }),
|
|
126
|
+
el("rect", { x: x + 20, y: y + 118, width: r2(w * 0.4), height: 10, rx: 5, fill: tokens.illustration.textBar, stroke: "none" }),
|
|
127
|
+
]));
|
|
128
|
+
/** Open folder with two sheets peeking out, measured from the write-in-obsidian cover (190x198 at scale 1). */
|
|
129
|
+
export const folder = definePiece("folder", { x: pos(), y: pos(), scale: size() }, ({ x, y, scale: s = 1 }) => {
|
|
130
|
+
const p = (dx, dy) => `${r2(x + dx * s)},${r2(y + dy * s)}`;
|
|
131
|
+
const back = `M${p(0, 30)} L${p(0, 12)} Q${p(0, 0)} ${p(12, 0)} L${p(66, 0)} Q${p(76, 0)} ${p(80, 10)} L${p(86, 30)} L${p(178, 30)} Q${p(190, 30)} ${p(190, 42)} L${p(190, 198)} L${p(0, 198)} Z`;
|
|
132
|
+
const front = `M${p(-6, 64)} Q${p(-6, 52)} ${p(6, 52)} L${p(184, 52)} Q${p(196, 52)} ${p(196, 64)} L${p(190, 198)} L${p(0, 198)} Z`;
|
|
133
|
+
const paper = (dx, dy, rot) => el("rect", { x: r2(x + dx * s), y: r2(y + dy * s), width: r2(96 * s), height: r2(120 * s), rx: 8, fill: tokens.color.white, transform: `rotate(${rot} ${r2(x + (dx + 48) * s)} ${r2(y + (dy + 60) * s)})` });
|
|
134
|
+
return drawn([
|
|
135
|
+
...shadowed(el("path", { d: back, fill: tokens.illustration.transitionalShade })),
|
|
136
|
+
paper(24, -8, -8),
|
|
137
|
+
paper(66, -16, 6),
|
|
138
|
+
el("path", { d: front, fill: tokens.color.transitional }),
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
/** A closed book seen from the front: back page, front cover, spine and page edges. */
|
|
142
|
+
export const book = definePiece("book", { x: pos(), y: pos(), width: size(false, 30), height: size(false, 30) }, ({ x, y, width: w = 120, height: h = 200 }) => drawn([
|
|
143
|
+
el("rect", { x: x + 18, y: y + 14, width: w - 2, height: h, rx: 8, fill: ink, stroke: "none" }),
|
|
144
|
+
el("rect", { x: x + 14, y: y + 10, width: w - 2, height: h, rx: 8, fill: tokens.color.white }),
|
|
145
|
+
el("rect", { x, y, width: w, height: h, rx: 8, fill: ink, stroke: "none" }),
|
|
146
|
+
el("rect", { x, y, width: w, height: h, rx: 8, fill: tokens.color.white }),
|
|
147
|
+
el("rect", { x, y, width: 20, height: h, rx: 8, fill: ink }),
|
|
148
|
+
el("path", { d: `M${x + w + 4},${y + 16} V${y + h - 6} M${x + w + 8},${y + 20} V${y + h - 2}`, "stroke-width": 2 }),
|
|
149
|
+
]));
|
|
150
|
+
/** Chunky arrow pointing right, centred vertically on y. */
|
|
151
|
+
export const blockArrow = definePiece("blockArrow", { x: pos(), y: pos(), length: size(), fill: col() }, ({ x, y, length = 50, fill = "illustration.accent" }) => {
|
|
152
|
+
const shaft = 12;
|
|
153
|
+
const head = 26;
|
|
154
|
+
const headLength = Math.min(30, length * 0.56);
|
|
155
|
+
const hx = r2(x + length - headLength);
|
|
156
|
+
return drawn([
|
|
157
|
+
el("path", { d: `M${x},${y - shaft} H${hx} V${y - head} L${x + length},${y} L${hx},${y + head} V${y + shaft} H${x} Z`, fill: colour(fill, "fill") }),
|
|
158
|
+
]);
|
|
159
|
+
});
|
|
160
|
+
/** Curved arrow (cubic Bézier) with a chevron head at `to`; solid or dashed. */
|
|
161
|
+
export const arcArrow = definePiece("arcArrow", { from: { kind: "point", required: true }, c1: { kind: "point", required: true }, c2: { kind: "point", required: true }, to: { kind: "point", required: true }, color: col(), dashed: { kind: "boolean" }, width: size() }, ({ from, c1, c2, to, color = "color.white", dashed = false, width }) => {
|
|
162
|
+
const stroke = colour(color, "color");
|
|
163
|
+
const strokeWidth = width ?? (dashed ? 4 : 6);
|
|
164
|
+
const angle = Math.atan2(to[1] - c2[1], to[0] - c2[0]);
|
|
165
|
+
const arm = 14;
|
|
166
|
+
const spread = (40 * Math.PI) / 180;
|
|
167
|
+
const wing = (sign) => `${r2(to[0] - arm * Math.cos(angle + sign * spread))},${r2(to[1] - arm * Math.sin(angle + sign * spread))}`;
|
|
168
|
+
const line = { d: `M${from[0]},${from[1]} C${c1[0]},${c1[1]} ${c2[0]},${c2[1]} ${to[0]},${to[1]}`, fill: "none", stroke, "stroke-width": strokeWidth, "stroke-linecap": "round" };
|
|
169
|
+
if (dashed)
|
|
170
|
+
line["stroke-dasharray"] = "9 8";
|
|
171
|
+
return drawn([
|
|
172
|
+
el("path", line),
|
|
173
|
+
el("path", { d: `M${wing(1)} L${to[0]},${to[1]} L${wing(-1)}`, fill: "none", stroke, "stroke-width": strokeWidth, "stroke-linecap": "round", "stroke-linejoin": "round" }),
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
/** A rail (track) with square nodes spread along it. */
|
|
177
|
+
export const rail = definePiece("rail", { x: pos(), y: pos(), width: size(true, 100), nodes: { kind: "array", required: true, minItems: 1, of: { kind: "color" } }, fill: col(), thickness: size() }, ({ x, y, width: w, nodes, fill = "illustration.codePanel", thickness: t = 44 }) => drawn([
|
|
178
|
+
...shadowed(el("rect", { x, y, width: w, height: t, rx: t / 2, fill: colour(fill, "fill") }), 8),
|
|
179
|
+
el("path", { d: `M${x + 30},${y + t / 2} H${x + w - 30}`, fill: "none", stroke: tokens.color.white, "stroke-width": tokens.outline.small }),
|
|
180
|
+
...nodes.map((node, i) => el("rect", { x: r2(x + 40 + i * ((w - 100) / Math.max(1, nodes.length - 1)) - 10), y: y + t / 2 - 10, width: 20, height: 20, rx: 4, fill: colour(node, `nodes[${i}]`), "stroke-width": tokens.outline.small })),
|
|
181
|
+
]));
|
|
182
|
+
/** A road: a wide grey band along a path with white dashes in the middle. */
|
|
183
|
+
export const road = definePiece("road", { d: { kind: "string", required: true, nonEmpty: true, pattern: PATH_DATA, patternName: "SVG path data (commands, digits, signs, dots, commas, spaces)" } }, ({ d }) => drawn([
|
|
184
|
+
el("path", { d, fill: "none", stroke: tokens.illustration.road, "stroke-width": 40, "stroke-linecap": "round" }),
|
|
185
|
+
el("path", { d, fill: "none", stroke: tokens.color.white, "stroke-width": tokens.outline.block, "stroke-dasharray": "14 12" }),
|
|
186
|
+
]));
|
|
187
|
+
/** A pin standing on (x, y): a stem and a round head with a short label. */
|
|
188
|
+
export const milestonePin = definePiece("milestonePin", { x: pos(), y: pos(), text: label(), fill: col(), stem: size() }, ({ x, y, text, fill = "color.healthy", stem = 44 }) => drawn([el("line", { x1: x, y1: y, x2: x, y2: y - stem }), el("circle", { cx: x, cy: y - stem - 14, r: 18, fill: colour(fill, "fill"), "stroke-width": tokens.outline.small })], [el("text", { x, y: y - stem - 7, "font-size": 18, "font-weight": tokens.font.weightHeavy }, text)]));
|
|
189
|
+
/** Price or version tag pointing left from its tip (x, y), with a hole and an optional string. */
|
|
190
|
+
export const priceTag = definePiece("priceTag", { x: pos(), y: pos(), text: label(), width: size(), fill: col(), stringFrom: { kind: "point" } }, ({ x, y, text, width: w = 128, fill = "color.white", stringFrom }) => {
|
|
191
|
+
const h = 48;
|
|
192
|
+
const top = y - h / 2;
|
|
193
|
+
const bottom = y + h / 2;
|
|
194
|
+
const right = x + w;
|
|
195
|
+
const d = `M${x + 20},${top} L${right - 10},${top} Q${right},${top} ${right},${top + 10} L${right},${bottom - 10} Q${right},${bottom} ${right - 10},${bottom} L${x + 20},${bottom} L${x},${y} Z`;
|
|
196
|
+
const body = [];
|
|
197
|
+
if (stringFrom) {
|
|
198
|
+
body.push(el("path", { d: `M${stringFrom[0]},${stringFrom[1]} C${stringFrom[0] - 14},${r2((stringFrom[1] + y) / 2)} ${x + 2},${y + 4} ${x + 20},${y}`, fill: "none", stroke: tokens.color.white, "stroke-width": tokens.outline.block }));
|
|
199
|
+
}
|
|
200
|
+
body.push(...shadowed(el("path", { d, fill: colour(fill, "fill") }), 6), el("circle", { cx: x + 16, cy: y, r: 5, fill: ink, stroke: "none" }));
|
|
201
|
+
return drawn(body, [el("text", { x: r2(x + 20 + (w - 20) / 2 - 2), y: y + 8, "font-family": mono, "font-size": 22 }, text)]);
|
|
202
|
+
});
|
|
203
|
+
/** A name tag hanging on a string from (hangX, hangY), showing one EIP icon. */
|
|
204
|
+
export const nameTag = definePiece("nameTag", { hangX: pos(), hangY: pos(), cx: pos(), top: pos(), rotate: { kind: "number" }, icon: { kind: "enum", required: true, values: EIP_ICON_NAMES }, fill: col() }, ({ hangX: sx, hangY: sy, cx, top, rotate = 0, icon, fill = "color.transitional" }) => {
|
|
205
|
+
const w = 96;
|
|
206
|
+
const h = 92;
|
|
207
|
+
const x = cx - w / 2;
|
|
208
|
+
const d = `M${x},${top + 14} L${x + 18},${top} H${x + w - 18} L${x + w},${top + 14} V${top + h - 6} Q${x + w},${top + h} ${x + w - 6},${top + h} H${x + 6} Q${x},${top + h} ${x},${top + h - 6} Z`;
|
|
209
|
+
return drawn([
|
|
210
|
+
el("path", { d: `M${sx},${sy} C${sx - 13},${sy + 28} ${sx + 6},${sy + 36} ${cx},${top + 20}`, fill: "none", "stroke-width": tokens.outline.small }),
|
|
211
|
+
g({ transform: `rotate(${rotate} ${cx} ${top + 20})` }, ...shadowed(el("path", { d, fill: colour(fill, "fill") }), 8), el("circle", { cx, cy: top + 14, r: 5, fill: tokens.color.white, "stroke-width": tokens.outline.small }), ...eipIcon({ name: icon, x: cx - 38, y: top + 27, scale: 1.18 }).body),
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
/** Green round badge with a tick. */
|
|
215
|
+
export const checkBadge = definePiece("checkBadge", { cx: pos(), cy: pos(), r: size() }, ({ cx, cy, r = 16 }) => drawn([
|
|
216
|
+
el("circle", { cx, cy, r, fill: tokens.color.healthy, "stroke-width": tokens.outline.small }),
|
|
217
|
+
el("path", { d: `M${r2(cx - r * 0.42)},${cy} L${r2(cx - r * 0.1)},${r2(cy + r * 0.34)} L${r2(cx + r * 0.45)},${r2(cy - r * 0.32)}`, fill: "none", "stroke-width": 3.5, "stroke-linecap": "round" }),
|
|
218
|
+
]));
|
|
219
|
+
/** Rubber stamp, round (dashed inner ring) or rectangular, tilted, with one word. */
|
|
220
|
+
export const stamp = definePiece("stamp", { cx: pos(), cy: pos(), word: label(), fill: col(), r: size(false, 12), rotate: { kind: "number" }, shape: { kind: "enum", values: ["round", "rect"] } }, ({ cx, cy, word, fill = "color.healthy", r = 40, rotate = -12, shape = "round" }) => {
|
|
221
|
+
const turn = `rotate(${rotate} ${cx} ${cy})`;
|
|
222
|
+
const face = shape === "round"
|
|
223
|
+
? [
|
|
224
|
+
...shadowed(el("circle", { cx, cy, r, fill: colour(fill, "fill") }), 8),
|
|
225
|
+
el("circle", { cx, cy, r: r - 10, fill: "none", stroke: tokens.color.white, "stroke-width": tokens.outline.small, "stroke-dasharray": "6 5" }),
|
|
226
|
+
]
|
|
227
|
+
: [
|
|
228
|
+
...shadowed(el("rect", { x: cx - r * 1.6, y: cy - r * 0.7, width: r * 3.2, height: r * 1.4, rx: 8, fill: colour(fill, "fill") }), 8),
|
|
229
|
+
el("rect", { x: cx - r * 1.6 + 8, y: cy - r * 0.7 + 8, width: r * 3.2 - 16, height: r * 1.4 - 16, rx: 4, fill: "none", stroke: tokens.color.white, "stroke-width": tokens.outline.small }),
|
|
230
|
+
];
|
|
231
|
+
return drawn([g({ transform: turn }, ...face)], [el("text", { x: cx, y: cy + 10, "font-size": 27, "font-weight": tokens.font.weightHeavy, transform: turn }, word)]);
|
|
232
|
+
});
|
|
233
|
+
/** Robot: antenna, ears, green body with a visor and two eyes, mouth bars. Scale it with pose. */
|
|
234
|
+
export const robot = definePiece("robot", { cx: pos(), top: pos() }, ({ cx, top }) => {
|
|
235
|
+
const w = 160;
|
|
236
|
+
const h = 152;
|
|
237
|
+
const x = cx - w / 2;
|
|
238
|
+
return drawn([
|
|
239
|
+
el("line", { x1: cx, y1: top, x2: cx, y2: top - 28 }),
|
|
240
|
+
el("circle", { cx, cy: top - 34, r: 10, fill: tokens.color.blocked }),
|
|
241
|
+
el("rect", { x: x - 16, y: top + 44, width: 20, height: 52, rx: 6, fill: tokens.illustration.healthyShade }),
|
|
242
|
+
el("rect", { x: x + w - 4, y: top + 44, width: 20, height: 52, rx: 6, fill: tokens.illustration.healthyShade }),
|
|
243
|
+
...shadowed(el("rect", { x, y: top, width: w, height: h, rx: 22, fill: tokens.color.healthy })),
|
|
244
|
+
el("rect", { x: x + 22, y: top + 22, width: w - 44, height: 72, rx: 14, fill: ink }),
|
|
245
|
+
el("circle", { cx: cx - 23, cy: top + 58, r: 11, fill: tokens.color.healthy, stroke: "none" }),
|
|
246
|
+
el("circle", { cx: cx + 23, cy: top + 58, r: 11, fill: tokens.color.healthy, stroke: "none" }),
|
|
247
|
+
g({ stroke: "none", fill: ink }, el("rect", { x: x + 30, y: top + 108, width: 44, height: 9, rx: 4.5 }), el("rect", { x: x + 82, y: top + 108, width: 48, height: 9, rx: 4.5 }), el("rect", { x: x + 42, y: top + 124, width: 60, height: 9, rx: 4.5 })),
|
|
248
|
+
]);
|
|
249
|
+
});
|
|
250
|
+
/** Magnifier: lens centred on (cx, cy), handle to the bottom right. */
|
|
251
|
+
export const magnifier = definePiece("magnifier", { cx: pos(), cy: pos(), r: size(false, 12), color: col() }, ({ cx, cy, r = 50, color = "illustration.accent" }) => {
|
|
252
|
+
const c = colour(color, "color");
|
|
253
|
+
const hx = r2(cx + r * 0.88);
|
|
254
|
+
const hy = r2(cy + r * 0.74);
|
|
255
|
+
return drawn([
|
|
256
|
+
el("line", { x1: hx, y1: hy, x2: hx + 38, y2: hy + 42, "stroke-width": 24, "stroke-linecap": "round" }),
|
|
257
|
+
el("line", { x1: hx, y1: hy, x2: hx + 38, y2: hy + 42, stroke: c, "stroke-width": 15, "stroke-linecap": "round" }),
|
|
258
|
+
el("circle", { cx, cy, r, fill: tokens.color.white, "fill-opacity": 0.18, stroke: c, "stroke-width": 12 }),
|
|
259
|
+
el("circle", { cx, cy, r: r + 6, fill: "none" }),
|
|
260
|
+
el("circle", { cx, cy, r: r - 6, fill: "none" }),
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
/** Stack of server units; the `failing` unit (0-based) shows a red light. */
|
|
264
|
+
export const serverStack = definePiece("serverStack", { x: pos(), y: pos(), width: size(), units: count(1, 20), fill: col(), failing: count(0, 19) }, ({ x, y, width: w = 150, units = 3, fill = "color.backend", failing }) => {
|
|
265
|
+
const uh = 40;
|
|
266
|
+
const gap = 10;
|
|
267
|
+
return drawn(Array.from({ length: units }, (_, i) => {
|
|
268
|
+
const uy = y + i * (uh + gap);
|
|
269
|
+
return [
|
|
270
|
+
...shadowed(el("rect", { x, y: uy, width: w, height: uh, rx: 8, fill: colour(fill, "fill") })),
|
|
271
|
+
el("rect", { x: x + 16, y: uy + 15, width: r2(w * 0.5), height: 10, rx: 5, fill: tokens.color.white, stroke: "none" }),
|
|
272
|
+
el("circle", { cx: x + w - 24, cy: uy + 20, r: 6, fill: i === failing ? tokens.color.failure : tokens.color.white, "stroke-width": tokens.outline.small }),
|
|
273
|
+
];
|
|
274
|
+
}).flat());
|
|
275
|
+
});
|
|
276
|
+
export const cloud = definePiece("cloud", { cx: pos(), cy: pos(), scale: size(), fill: col() }, ({ cx, cy, scale: s = 1, fill = "color.white" }) => {
|
|
277
|
+
const p = (dx, dy) => `${r2(cx + dx * s)},${r2(cy + dy * s)}`;
|
|
278
|
+
const d = `M${p(-70, 30)} Q${p(-96, 30)} ${p(-94, 4)} Q${p(-90, -20)} ${p(-62, -18)} Q${p(-52, -58)} ${p(-10, -54)} Q${p(26, -52)} ${p(36, -24)} Q${p(86, -30)} ${p(90, 6)} Q${p(92, 30)} ${p(66, 30)} Z`;
|
|
279
|
+
return drawn(shadowed(el("path", { d, fill: colour(fill, "fill") }), 8));
|
|
280
|
+
});
|
|
281
|
+
/** Gear with square teeth and a white hub, as on the maven-release cover. */
|
|
282
|
+
export const gear = definePiece("gear", { cx: pos(), cy: pos(), r: size(), teeth: count(4, 64), fill: col() }, ({ cx, cy, r = 84, teeth = 12, fill = "color.transitional" }) => {
|
|
283
|
+
const root = r * 0.78;
|
|
284
|
+
const points = [];
|
|
285
|
+
const step = (2 * Math.PI) / teeth;
|
|
286
|
+
const at = (radius, angle) => `${r2(cx + radius * Math.cos(angle))},${r2(cy + radius * Math.sin(angle))}`;
|
|
287
|
+
for (let i = 0; i < teeth; i++) {
|
|
288
|
+
const a = i * step;
|
|
289
|
+
points.push(at(root, a - step * 0.3), at(r, a - step * 0.18), at(r, a + step * 0.18), at(root, a + step * 0.3));
|
|
290
|
+
}
|
|
291
|
+
return drawn([...shadowed(el("polygon", { points: points.join(" "), fill: colour(fill, "fill") })), el("circle", { cx, cy, r: r2(r * 0.36), fill: tokens.color.white })]);
|
|
292
|
+
});
|
|
293
|
+
/** Parcel (box) with a lid line and a tape strip. */
|
|
294
|
+
export const parcel = definePiece("parcel", { x: pos(), y: pos(), size: size(), fill: col() }, ({ x, y, size: s = 96, fill = "color.blocked" }) => drawn([
|
|
295
|
+
...shadowed(el("rect", { x, y, width: s, height: r2(s * 0.8), rx: 6, fill: colour(fill, "fill") })),
|
|
296
|
+
el("path", { d: `M${x},${r2(y + s * 0.3)} H${x + s}`, fill: "none", "stroke-width": tokens.outline.small }),
|
|
297
|
+
el("rect", { x: x + s / 2 - 12, y, width: 24, height: r2(s * 0.8), fill: tokens.illustration.tape, "stroke-width": tokens.outline.small }),
|
|
298
|
+
]));
|
|
299
|
+
/** Steps climbing to the right from the bottom-left corner (x, y), as on the obsidian-to-site guide cover. */
|
|
300
|
+
export const steps = definePiece("steps", { x: pos(), y: pos(), count: count(1, 20), width: size(), rise: size(), fill: col() }, ({ x, y, count = 4, width: w = 145, rise = 40, fill = "color.caller" }) => {
|
|
301
|
+
const blocks = Array.from({ length: count }, (_, i) => el("rect", { x: x + i * w, y: y - rise * (i + 1), width: w, height: rise * (i + 1), rx: 10, fill: colour(fill, "fill") }));
|
|
302
|
+
return drawn([...blocks.map((b) => ({ ...b, attrs: { ...b.attrs, fill: ink, transform: `translate(${tokens.shadow.block} ${tokens.shadow.block})` } })), ...blocks]);
|
|
303
|
+
});
|
|
304
|
+
const muleGlyph = (kind, cx, cy) => {
|
|
305
|
+
const line = { fill: "none", "stroke-linecap": "round" };
|
|
306
|
+
if (kind === "scatter-gather")
|
|
307
|
+
return [el("path", { ...line, d: `M${cx - 25},${cy - 20} V${cy + 20} M${cx + 25},${cy - 20} V${cy + 20} M${cx - 17},${cy - 12} H${cx + 17} M${cx - 17},${cy} H${cx + 17} M${cx - 17},${cy + 12} H${cx + 17}` })];
|
|
308
|
+
if (kind === "choice")
|
|
309
|
+
return [el("path", { ...line, d: `M${cx - 27},${cy} H${cx - 17} M${cx - 3},${cy - 14} L${cx + 11},${cy} L${cx - 3},${cy + 14} L${cx - 17},${cy} Z M${cx + 11},${cy} L${cx + 27},${cy - 14} M${cx + 11},${cy} L${cx + 27},${cy + 14}` })];
|
|
310
|
+
return [
|
|
311
|
+
el("path", { ...line, d: `M${r2(cx + 18.2)},${r2(cy - 10.5)} A21,21 0 1 1 ${r2(cx - 14.8)},${r2(cy - 14.8)}` }),
|
|
312
|
+
el("path", { d: `M${r2(cx - 8.5)},${r2(cy - 21.2)} L${r2(cx - 9.9)},${r2(cy - 9.9)} L${r2(cx - 19.8)},${r2(cy - 19.8)} Z`, fill: ink, "stroke-width": 2 }),
|
|
313
|
+
el("rect", { x: cx - 8, y: cy - 8, width: 16, height: 16, fill: tokens.color.white, "stroke-width": tokens.outline.small }),
|
|
314
|
+
];
|
|
315
|
+
};
|
|
316
|
+
/** A Mule flow: a bar with processor tiles linked left to right, each with its glyph. */
|
|
317
|
+
export const muleFlow = definePiece("muleFlow", {
|
|
318
|
+
x: pos(),
|
|
319
|
+
y: pos(),
|
|
320
|
+
width: size(true, 120),
|
|
321
|
+
height: size(false, 40),
|
|
322
|
+
tiles: { kind: "array", required: true, minItems: 1, of: { kind: "object", fields: { x: pos(), kind: { kind: "enum", required: true, values: ["scatter-gather", "choice", "for-each"] } } } },
|
|
323
|
+
tileWidth: size(false, 40),
|
|
324
|
+
fill: col(),
|
|
325
|
+
}, ({ x, y, width: w, height: h = 104, tiles, tileWidth = 86, fill = "color.caller" }) => {
|
|
326
|
+
const cy = y + h / 2;
|
|
327
|
+
const links = [`M${x + 18},${cy} H${tiles[0].x - 6}`];
|
|
328
|
+
const heads = [];
|
|
329
|
+
tiles.forEach((tile, i) => {
|
|
330
|
+
const end = i + 1 < tiles.length ? tiles[i + 1].x - 6 : x + w - 16;
|
|
331
|
+
links.push(`M${tile.x + tileWidth},${cy} H${end}`);
|
|
332
|
+
heads.push(el("path", { d: `M${end - 8},${cy - 7} L${end + 1},${cy} L${end - 8},${cy + 7} Z`, fill: ink, "stroke-width": 2 }));
|
|
333
|
+
});
|
|
334
|
+
return drawn([
|
|
335
|
+
...shadowed(el("rect", { x, y, width: w, height: h, rx: 22, fill: colour(fill, "fill") })),
|
|
336
|
+
el("path", { d: links.join(" "), "stroke-linecap": "round" }),
|
|
337
|
+
...heads,
|
|
338
|
+
...tiles.flatMap((tile) => shadowed(el("rect", { x: tile.x, y: y + 14, width: tileWidth, height: h - 28, rx: 14, fill: tokens.color.white }), 6)),
|
|
339
|
+
...tiles.flatMap((tile) => muleGlyph(tile.kind, tile.x + tileWidth / 2, cy)),
|
|
340
|
+
]);
|
|
341
|
+
});
|
|
342
|
+
/** One of the 26 EIP icons (64x40), placed at (x, y) and scaled; the cover's outline does not leak in. */
|
|
343
|
+
export const eipIcon = definePiece("eipIcon", { name: { kind: "enum", required: true, values: EIP_ICON_NAMES }, x: pos(), y: pos(), scale: size() }, ({ name, x, y, scale = 1 }) => {
|
|
344
|
+
const icon = EIP_ICONS.find((candidate) => candidate.name === name);
|
|
345
|
+
return drawn([g({ transform: `translate(${x} ${y}) scale(${scale})`, stroke: "none" }, ...icon.nodes)]);
|
|
346
|
+
});
|
|
347
|
+
/** Every piece, by name, in catalogue order (spec illustration-pieces, "Piece catalogue"). */
|
|
348
|
+
export const PIECES = {
|
|
349
|
+
pill,
|
|
350
|
+
rings,
|
|
351
|
+
sheet,
|
|
352
|
+
blockArrow,
|
|
353
|
+
arcArrow,
|
|
354
|
+
rail,
|
|
355
|
+
checkBadge,
|
|
356
|
+
priceTag,
|
|
357
|
+
nameTag,
|
|
358
|
+
codePanel,
|
|
359
|
+
serverStack,
|
|
360
|
+
cloud,
|
|
361
|
+
stamp,
|
|
362
|
+
robot,
|
|
363
|
+
magnifier,
|
|
364
|
+
gear,
|
|
365
|
+
parcel,
|
|
366
|
+
road,
|
|
367
|
+
milestonePin,
|
|
368
|
+
browserWindow,
|
|
369
|
+
speechBubble,
|
|
370
|
+
steps,
|
|
371
|
+
folder,
|
|
372
|
+
book,
|
|
373
|
+
muleFlow,
|
|
374
|
+
eipIcon,
|
|
375
|
+
...METRO_PIECES,
|
|
376
|
+
};
|
|
377
|
+
export const PIECE_NAMES = Object.keys(PIECES);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
type Point = [number, number];
|
|
2
|
+
export declare const SURFACES: readonly ["light", "dark"];
|
|
3
|
+
export type SurfaceName = (typeof SURFACES)[number];
|
|
4
|
+
export type Ink = {
|
|
5
|
+
surface?: SurfaceName;
|
|
6
|
+
inkScale?: number;
|
|
7
|
+
};
|
|
8
|
+
export type LineBadgeParams = Ink & {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
text: string;
|
|
12
|
+
fill: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
flat?: boolean;
|
|
16
|
+
};
|
|
17
|
+
/** A line's terminal badge: a pill in the line colour with its code in white, centred on (x, y). */
|
|
18
|
+
export declare const lineBadge: import("./piece.ts").Piece<LineBadgeParams>;
|
|
19
|
+
export type StationMarkerParams = Ink & {
|
|
20
|
+
cx: number;
|
|
21
|
+
cy: number;
|
|
22
|
+
to?: Point;
|
|
23
|
+
r?: number;
|
|
24
|
+
state?: "normal" | "deprecated";
|
|
25
|
+
};
|
|
26
|
+
/** A stop (disc), or with `to` an interchange (capsule from the centre to `to`); deprecated is muted. */
|
|
27
|
+
export declare const stationMarker: import("./piece.ts").Piece<StationMarkerParams>;
|
|
28
|
+
export type PipeParams = {
|
|
29
|
+
d: string;
|
|
30
|
+
fill: string;
|
|
31
|
+
width?: number;
|
|
32
|
+
casing?: number;
|
|
33
|
+
};
|
|
34
|
+
/** A line: its colour over an ink casing, round caps and joins, no shadow. */
|
|
35
|
+
export declare const pipe: import("./piece.ts").Piece<PipeParams>;
|
|
36
|
+
export type CountBadgeParams = Ink & {
|
|
37
|
+
cx: number;
|
|
38
|
+
cy: number;
|
|
39
|
+
text: string;
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
42
|
+
};
|
|
43
|
+
/** A count (rules of a skill): a circle when as wide as high, a pill otherwise, in the surface's panel colour. */
|
|
44
|
+
export declare const countBadge: import("./piece.ts").Piece<CountBadgeParams>;
|
|
45
|
+
export type NodeParams = Ink & {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
width: number;
|
|
49
|
+
height?: number;
|
|
50
|
+
title: string;
|
|
51
|
+
subtitle?: string;
|
|
52
|
+
state?: "normal" | "focus" | "placeholder";
|
|
53
|
+
};
|
|
54
|
+
export type NodeBlockParams = NodeParams & {
|
|
55
|
+
radius?: number;
|
|
56
|
+
};
|
|
57
|
+
/** A diagram box (a skill, a software item): rounded rect with a title and an optional subtitle. */
|
|
58
|
+
export declare const nodeBlock: import("./piece.ts").Piece<NodeBlockParams>;
|
|
59
|
+
/** A diagram pill (a playbook). */
|
|
60
|
+
export declare const pillNode: import("./piece.ts").Piece<NodeParams>;
|
|
61
|
+
/** A diagram hexagon (an agent). */
|
|
62
|
+
export declare const hexNode: import("./piece.ts").Piece<NodeParams>;
|
|
63
|
+
export type MessageArrowParams = {
|
|
64
|
+
from: Point;
|
|
65
|
+
to: Point;
|
|
66
|
+
curve?: "bezier" | "straight";
|
|
67
|
+
head?: boolean;
|
|
68
|
+
label?: string;
|
|
69
|
+
width?: number;
|
|
70
|
+
surface?: SurfaceName;
|
|
71
|
+
};
|
|
72
|
+
/** An edge with a head at `to`: curved (leaves and enters horizontally) or straight, with an optional label. */
|
|
73
|
+
export declare const messageArrow: import("./piece.ts").Piece<MessageArrowParams>;
|
|
74
|
+
export type LedgerMarkParams = {
|
|
75
|
+
x: number;
|
|
76
|
+
y: number;
|
|
77
|
+
size?: number;
|
|
78
|
+
line?: string;
|
|
79
|
+
};
|
|
80
|
+
/** stackledger's mark (favicon, header): an ink square with a bent line and two stations, 32 units scaled to `size`. */
|
|
81
|
+
export declare const ledgerMark: import("./piece.ts").Piece<LedgerMarkParams>;
|
|
82
|
+
export declare const METRO_PIECES: {
|
|
83
|
+
readonly lineBadge: import("./piece.ts").Piece<LineBadgeParams>;
|
|
84
|
+
readonly stationMarker: import("./piece.ts").Piece<StationMarkerParams>;
|
|
85
|
+
readonly pipe: import("./piece.ts").Piece<PipeParams>;
|
|
86
|
+
readonly countBadge: import("./piece.ts").Piece<CountBadgeParams>;
|
|
87
|
+
readonly nodeBlock: import("./piece.ts").Piece<NodeBlockParams>;
|
|
88
|
+
readonly pillNode: import("./piece.ts").Piece<NodeParams>;
|
|
89
|
+
readonly hexNode: import("./piece.ts").Piece<NodeParams>;
|
|
90
|
+
readonly messageArrow: import("./piece.ts").Piece<MessageArrowParams>;
|
|
91
|
+
readonly ledgerMark: import("./piece.ts").Piece<LedgerMarkParams>;
|
|
92
|
+
};
|
|
93
|
+
export {};
|