@brunosouzas/design-system 0.1.1 → 0.3.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/README.md +32 -9
- package/dist/src/pieces/cover.d.ts +7 -1
- package/dist/src/pieces/cover.js +27 -5
- package/dist/src/pieces/index.d.ts +1 -1
- package/dist/src/pieces/index.js +1 -1
- package/dist/src/pieces/library.d.ts +12 -1
- package/dist/src/pieces/library.js +11 -3
- package/dist/src/pieces/link-icons.d.ts +22 -0
- package/dist/src/pieces/link-icons.js +80 -0
- package/dist/src/pieces/metro.d.ts +42 -3
- package/dist/src/pieces/metro.js +73 -12
- package/dist/src/pieces/ui-icons.d.ts +20 -0
- package/dist/src/pieces/ui-icons.js +34 -0
- package/dist/tokens/generated/tokens.d.ts +12 -0
- package/dist/tokens/generated/tokens.js +15 -3
- package/package.json +1 -1
- package/tokens/generated/tokens.css +10 -0
- package/tokens/generated/tokens.json +15 -3
- package/tokens/generated/tokens.ts +15 -3
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ The visual identity of [brunosouzas.com](https://brunosouzas.com): design tokens
|
|
|
25
25
|
|
|
26
26
|
## Status
|
|
27
27
|
|
|
28
|
-
Early. The first change of the [roadmap](docs/roadmap.md) is implemented: the token source and its CSS, TypeScript and JSON exports, the motion components, and the circuit-breaker state machine as the first catalogue scene (16:9 and 4:5, Portuguese and English), with commands to render its stills and videos. The first variety scene, `publish-subscribe`, is implemented with independent message copies, Publish acceptance, two ACKs and a local invoice NACK. Its owner stills/video approval is pending; implementation is not visual approval. The illustration pieces (roadmap item 3)
|
|
28
|
+
Early. The first change of the [roadmap](docs/roadmap.md) is implemented: the token source and its CSS, TypeScript and JSON exports, the motion components, and the circuit-breaker state machine as the first catalogue scene (16:9 and 4:5, Portuguese and English), with commands to render its stills and videos. The first variety scene, `publish-subscribe`, is implemented with independent message copies, Publish acceptance, two ACKs and a local invoice NACK. Its owner stills/video approval is pending; implementation is not visual approval. The illustration pieces (roadmap item 3), the metro and diagram pieces (item 6) and stackledger's map markers, zones and icons (AB#167, 0.3.0) are implemented: the static drawing library that covers, banners, diagrams and scenes share, with a command that builds a blog cover from a JSON spec. The published covers are not ported to it yet (item 5).
|
|
29
29
|
|
|
30
30
|
## What it is
|
|
31
31
|
|
|
@@ -90,7 +90,7 @@ npm run stills -- publish-subscribe # 24 phase/final PNGs (six per
|
|
|
90
90
|
npm run stills -- publish-subscribe --dry-run # print 24 commands, no effects
|
|
91
91
|
npm run render -- publish-subscribe --dry-run # print eight commands; real video waits for owner stills approval
|
|
92
92
|
npm run build # compile the package into dist/ (the package test needs it)
|
|
93
|
-
npm run cover -- cover.json --out card.svg # build a blog cover from its spec
|
|
93
|
+
npm run cover -- cover.json --out card.svg # build a blog cover (or a banner) from its spec
|
|
94
94
|
npm run pieces:sheet -- --out out/pieces.svg # every piece once, labelled, for review
|
|
95
95
|
npm run studio # Remotion Studio, for frame-by-frame review
|
|
96
96
|
```
|
|
@@ -130,17 +130,20 @@ The components take every colour and size from the tokens, and what is visible a
|
|
|
130
130
|
|
|
131
131
|
- `toSvg(node)` writes a tree as SVG text; `toReact(node)` turns the same tree into React elements. Both describe the same elements, attributes and text, which the tests check.
|
|
132
132
|
- `pose(node, { x, y, tilt, squash, hop, scale, shake, opacity })` animates any piece around its base point; a scene recomputes the numbers every frame.
|
|
133
|
-
- Every colour parameter is a token path (`"color.healthy"`, `"illustration.accent"`), never a hex value, except the
|
|
134
|
-
- The
|
|
133
|
+
- Every colour parameter is a token path (`"color.healthy"`, `"illustration.accent"`), never a hex value, except the three data fills of the map pieces below. Each piece checks its parameters before drawing and names the first wrong one. Positions and points take any finite number, because a map has no edge; sizes stay within 10000. A drawing that would hold a non-finite number throws `<piece>: the drawing overflows`.
|
|
134
|
+
- The 41 pieces, with their anchors, parameters and defaults, are listed in [design §4](openspec/changes/archive/2026-10-23-illustration-pieces/design.md) (the 26 cover pieces), in [design §3](openspec/changes/archive/2026-10-23-metro-and-diagram-pieces/design.md) of `metro-and-diagram-pieces` (the nine map and diagram pieces) and in [design §3, §5 and §6](openspec/changes/archive/2026-10-23-stackledger-map-and-link-pieces/design.md) of `stackledger-map-and-link-pieces` (the six markers, zones and icons); the rules are in [`openspec/specs/illustration-pieces/`](openspec/specs/illustration-pieces/spec.md). `npm run pieces:sheet -- --out <file.svg>` draws them all.
|
|
135
135
|
|
|
136
136
|
### Maps and diagrams
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
Thirteen pieces draw a metro map and its page diagrams, for [stackledger](https://github.com/brunosouzas/stackledger) and any other map in this style: `lineBadge`, `stationMarker` (stop, interchange as a capsule, deprecated), `pipe`, `countBadge`, `nodeBlock`, `pillNode`, `hexNode` (normal, focus, placeholder), `messageArrow` (curved or straight, with a head and an optional label) `ledgerMark` (stackledger's mark; `surface: "dark"` lines it with a light rim), `renewalRing` (orange bands round a station), `suggestedHalo` (a dashed muted circle), `licenceMark` (a 10-unit disc, green when held, white to buy) and `zone` (the outline the caller gives, widened by a round 32-unit stroke at the surface's zone opacity, with an optional title). They paint what the caller lays out: positions, hulls, text measurement and truncation stay with the map engine.
|
|
139
139
|
|
|
140
|
-
-
|
|
140
|
+
- A `placeholder` node may have a `subtitle` (drawn like a normal node's, all muted), and `lineBadge` may have no `text` (the pill alone, 30 wide by default).
|
|
141
|
+
- The licence's disc is small, so it has its own room rule: `size` at least three outlines (9 at ink scale 1).
|
|
142
|
+
|
|
143
|
+
- `surface: "light" | "dark"` picks the background they sit on (`surface.*` tokens); every map and diagram piece takes it except `pipe`; `ledgerMark` takes only `surface`. On dark the hard shadow takes `surface.dark.shadow` and the outline gets a light rim ([ADR 0003](docs/adr/0003-visual-language-and-colour-meaning.md), amendment of AB#157).
|
|
141
144
|
- `inkScale` (0.25 to 8) thickens the outline, rim and shadow without changing the geometry, on `lineBadge`, `stationMarker`, `countBadge` and the three nodes; the map uses 1, the page diagrams 1.6. A shape's height (a marker's diameter) must be at least four outlines; a placeholder node, which draws no ink, is exempt.
|
|
142
|
-
- A line's colour comes from the map's data, so `pipe.fill` and `
|
|
143
|
-
- The `map.*` tokens hold the measures (outline 3, shadow 3.5, line 12, casing 6, badge 20, node 46…), and `site.*` the page palette, fonts and type scale of brunosouzas.com in light and dark.
|
|
145
|
+
- A line's colour comes from the map's data, so `pipe.fill`, `lineBadge.fill` and `zone.fill` also accept `#RRGGBB`. Every other colour is a token path, and a cover spec rejects any hex.
|
|
146
|
+
- The `map.*` tokens hold the measures (outline 3, shadow 3.5, line 12, casing 6, badge 20, node 46, ring 3, halo 1.5, licence 10, zone padding 32…), `surface.*.zoneOpacity` the zone's opacity (0.12, dark 0.22), and `site.*` the page palette, fonts and type scale of brunosouzas.com in light and dark.
|
|
144
147
|
|
|
145
148
|
```ts
|
|
146
149
|
import { lineBadge, pipe, stationMarker, toSvg } from "@brunosouzas/design-system";
|
|
@@ -151,7 +154,7 @@ const badge = lineBadge({ x: 250, y: 40, text: "SH", fill: "#7B3FE4", surface: "
|
|
|
151
154
|
|
|
152
155
|
### A cover
|
|
153
156
|
|
|
154
|
-
A cover is a JSON spec kept with its article, outside this repository: drafts are private, and only the pieces are public. `npm run cover -- <spec.json> --out <file.svg>` validates it, reports every problem with its JSON path, and writes an 800x420 SVG; the same spec always gives the same bytes.
|
|
157
|
+
A cover is a JSON spec kept with its article, outside this repository: drafts are private, and only the pieces are public. `npm run cover -- <spec.json> --out <file.svg>` validates it, reports every problem with its JSON path, and writes an 800x420 SVG; the same spec always gives the same bytes. With `"format": "banner"` the same command writes a site banner instead (see below).
|
|
155
158
|
|
|
156
159
|
```json
|
|
157
160
|
{
|
|
@@ -173,10 +176,30 @@ A cover is a JSON spec kept with its article, outside this repository: drafts ar
|
|
|
173
176
|
- `custom` (optional) is the cover's one free-form object, a JSON node tree limited to drawing tags and a fixed list of attributes, with no external references. An object that repeats becomes a piece instead.
|
|
174
177
|
- The three published covers that proved the pieces are the test fixtures in `test/fixtures/covers/`.
|
|
175
178
|
|
|
179
|
+
### Banners
|
|
180
|
+
|
|
181
|
+
A spec with `"format": "banner"` (the default is `"cover"`) builds a home banner for a site: `banner.width` x `banner.height` (1200x420) with corners rounded at `banner.radius` (20), the measures of brunosouzas.com's published banner. Same pieces, validation and command. Two things differ from a cover:
|
|
182
|
+
|
|
183
|
+
- the rings go on until they reach the frame's farthest corner (22 rings for a centre at 600, 225 with radius 60), where a cover draws 14;
|
|
184
|
+
- pieces and labels are clipped to the rounded frame too, so nothing pokes past a corner.
|
|
185
|
+
|
|
186
|
+
The banner is one image that scales down as a whole. Words in it become small on a phone (a 19px label is about 6px at 375px wide), so keep them few or none. `test/fixtures/covers/home-banner.json` is the brunosouzas.com home banner redrawn from the pieces.
|
|
187
|
+
|
|
176
188
|
### A new piece
|
|
177
189
|
|
|
178
190
|
Add it to `src/pieces/library.ts` (or `src/pieces/metro.ts` for a map or diagram piece) with `definePiece(name, schema, draw)`, take its colours and sizes from the tokens (a new colour goes to `tokens/source.json` first), register it in `PIECES`, and give it a sample in `scripts/pieces-sheet.ts`. The catalogue test then checks that it draws with only its required parameters and uses token colours only.
|
|
179
191
|
|
|
192
|
+
### UI and link icons
|
|
193
|
+
|
|
194
|
+
- `uiIcon({ name, x, y, size, colour })` draws one of seven glyphs of the sites' chrome in a 24-unit box and one token colour: `sun`, `moon`, `zoomIn`, `zoomOut`, `reset`, `calendar`, `download` (data in `UI_ICONS`).
|
|
195
|
+
- `linkIcon({ icon, x, y, size, surface })` draws a link sticker as brunosouzas.com draws its social icons (46-unit box, light or dark frame): `github`, `linkedin`, `email`, `website` (data in `LINK_ICONS`). The GitHub mark is from [Font Awesome](https://fontawesome.com/), licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
|
196
|
+
- Neither holds a link, a style, a script or a URL, so a consumer can write one with `toSvg` inside its own `<svg xmlns=…>` and ship it as a static file.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { el, linkIcon, toSvg } from "@brunosouzas/design-system";
|
|
200
|
+
const github = toSvg(el("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 46 46", width: 28, height: 28 }, ...linkIcon({ icon: "github", x: 0, y: 0, surface: "dark" }).body));
|
|
201
|
+
```
|
|
202
|
+
|
|
180
203
|
### EIP icons
|
|
181
204
|
|
|
182
205
|
`src/pieces/eip-icons.ts` holds the 26 Enterprise Integration Patterns icons as data. They are redrawn from the icons by Gregor Hohpe and Bobby Woolf ([enterpriseintegrationpatterns.com](https://www.enterpriseintegrationpatterns.com/)), licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); each icon keeps that credit in its description.
|
|
@@ -2,6 +2,9 @@ import { type PieceName } from "./library.ts";
|
|
|
2
2
|
import { type SvgNode } from "./node.ts";
|
|
3
3
|
import { type Problem } from "./params.ts";
|
|
4
4
|
export declare const SAFE_ID: RegExp;
|
|
5
|
+
/** Cover (800x420, the default) or banner (1200x420, rounded corners): spec cover-cli, "Banner output". */
|
|
6
|
+
export declare const FORMATS: readonly ["cover", "banner"];
|
|
7
|
+
export type CoverFormat = (typeof FORMATS)[number];
|
|
5
8
|
export type CoverPiece = {
|
|
6
9
|
piece: PieceName;
|
|
7
10
|
} & Record<string, unknown>;
|
|
@@ -14,6 +17,7 @@ export type CoverSpec = {
|
|
|
14
17
|
id: string;
|
|
15
18
|
title: string;
|
|
16
19
|
description: string;
|
|
20
|
+
format?: CoverFormat;
|
|
17
21
|
background: string;
|
|
18
22
|
rings: {
|
|
19
23
|
cx: number;
|
|
@@ -28,5 +32,7 @@ export declare const backgroundNames: () => string[];
|
|
|
28
32
|
export declare const comparePaths: (a: string, b: string) => number;
|
|
29
33
|
/** Every problem of a parsed spec, sorted by path; empty when the spec is valid. */
|
|
30
34
|
export declare const validateCoverSpec: (spec: unknown) => Problem[];
|
|
31
|
-
/**
|
|
35
|
+
/** Rings a banner draws: out to the frame's farthest corner (spec cover-cli, "Banner rings reach the corners"). */
|
|
36
|
+
export declare const bannerRingCount: (cx: number, cy: number, radius?: number) => number;
|
|
37
|
+
/** The cover's (or banner's) node tree. The spec must be valid (validateCoverSpec returned no problem). */
|
|
32
38
|
export declare const coverTree: (spec: CoverSpec) => SvgNode;
|
package/dist/src/pieces/cover.js
CHANGED
|
@@ -12,7 +12,9 @@ import { PIECE_NAMES, PIECES, rings } from "./library.js";
|
|
|
12
12
|
import { el, g } from "./node.js";
|
|
13
13
|
import { COLOUR_TOKENS, checkParams, isColourLiteral, MAX_SIZE, PATH_DATA } from "./params.js";
|
|
14
14
|
export const SAFE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
15
|
-
const TOP_LEVEL = ["id", "title", "description", "background", "rings", "pieces", "custom"];
|
|
15
|
+
const TOP_LEVEL = ["id", "title", "description", "format", "background", "rings", "pieces", "custom"];
|
|
16
|
+
/** Cover (800x420, the default) or banner (1200x420, rounded corners): spec cover-cli, "Banner output". */
|
|
17
|
+
export const FORMATS = ["cover", "banner"];
|
|
16
18
|
const CUSTOM_TAGS = ["g", "rect", "circle", "ellipse", "line", "polyline", "polygon", "path", "text"];
|
|
17
19
|
// Attributes a free-form object may use (design §6): geometry, paint, text and transform.
|
|
18
20
|
const CUSTOM_ATTRS = new Set([
|
|
@@ -154,7 +156,7 @@ export const validateCoverSpec = (spec) => {
|
|
|
154
156
|
if (!isPlainObject(spec.rings))
|
|
155
157
|
out.push({ path: "rings", message: "missing or not an object (expected cx, cy and an optional radius)" });
|
|
156
158
|
else {
|
|
157
|
-
const ringProblems = checkParams(rings.schema, spec.rings, "rings").filter((problem) => problem.path !== "rings.color");
|
|
159
|
+
const ringProblems = checkParams(rings.schema, spec.rings, "rings").filter((problem) => problem.path !== "rings.color" && problem.path !== "rings.count");
|
|
158
160
|
out.push(...ringProblems);
|
|
159
161
|
// The piece takes any finite centre; a cover spec keeps 0.1.0's ±10000, which bounds a banner's ring count.
|
|
160
162
|
for (const axis of ["cx", "cy"]) {
|
|
@@ -169,6 +171,11 @@ export const validateCoverSpec = (spec) => {
|
|
|
169
171
|
}
|
|
170
172
|
if (isPlainObject(spec.rings) && Object.hasOwn(spec.rings, "color"))
|
|
171
173
|
out.push({ path: "rings.color", message: "unknown parameter: the ring colour comes from the background" });
|
|
174
|
+
if (isPlainObject(spec.rings) && Object.hasOwn(spec.rings, "count"))
|
|
175
|
+
out.push({ path: "rings.count", message: "unknown parameter: the ring count comes from the format" });
|
|
176
|
+
if (spec.format !== undefined && (typeof spec.format !== "string" || !FORMATS.includes(spec.format))) {
|
|
177
|
+
out.push({ path: "format", message: `expected one of ${FORMATS.join(", ")}` });
|
|
178
|
+
}
|
|
172
179
|
if (!Array.isArray(spec.pieces))
|
|
173
180
|
out.push({ path: "pieces", message: "missing or not an array" });
|
|
174
181
|
else
|
|
@@ -254,12 +261,25 @@ const resolveCustom = (node) => {
|
|
|
254
261
|
attrs.stroke = "none";
|
|
255
262
|
return el(node.tag, attrs, ...(node.children ?? []).map((child) => (typeof child === "string" ? child : resolveCustom(child))));
|
|
256
263
|
};
|
|
257
|
-
/**
|
|
264
|
+
/** Rings a banner draws: out to the frame's farthest corner (spec cover-cli, "Banner rings reach the corners"). */
|
|
265
|
+
export const bannerRingCount = (cx, cy, radius = tokens.cover.ringRadius) => {
|
|
266
|
+
const { width, height } = tokens.banner;
|
|
267
|
+
const far = Math.max(...[[0, 0], [width, 0], [0, height], [width, height]].map(([x, y]) => Math.hypot(x - cx, y - cy)));
|
|
268
|
+
return Math.max(1, Math.ceil((far - radius) / tokens.cover.ringStep) + 1);
|
|
269
|
+
};
|
|
270
|
+
/** The cover's (or banner's) node tree. The spec must be valid (validateCoverSpec returned no problem). */
|
|
258
271
|
export const coverTree = (spec) => {
|
|
259
|
-
const
|
|
272
|
+
const banner = spec.format === "banner";
|
|
273
|
+
const { width, height } = banner ? tokens.banner : tokens.cover;
|
|
274
|
+
// A banner's corners are rounded; a cover keeps today's square frame, attribute for attribute.
|
|
275
|
+
const corner = banner ? { rx: tokens.banner.radius } : {};
|
|
260
276
|
const pair = tokens.coverBackground[spec.background];
|
|
261
277
|
const drawnPieces = spec.pieces.map(({ piece, ...params }) => PIECES[piece](params));
|
|
262
278
|
const ringPath = `coverBackground.${spec.background}.ring`;
|
|
279
|
+
const ringCount = banner ? { count: bannerRingCount(spec.rings.cx, spec.rings.cy, spec.rings.radius) } : {};
|
|
280
|
+
const frame = `url(#${spec.id}-frame)`;
|
|
281
|
+
const outlined = g({ stroke: tokens.color.ink, "stroke-width": tokens.outline.block, "stroke-linejoin": "round" }, ...drawnPieces.flatMap((d) => d.body), spec.custom ? resolveCustom(spec.custom) : null);
|
|
282
|
+
const labels = g({ "font-size": tokens.cover.labelSize, "font-weight": tokens.cover.labelWeight, fill: tokens.color.ink, "text-anchor": "middle" }, ...drawnPieces.flatMap((d) => d.text));
|
|
263
283
|
return el("svg", {
|
|
264
284
|
xmlns: "http://www.w3.org/2000/svg",
|
|
265
285
|
width,
|
|
@@ -268,5 +288,7 @@ export const coverTree = (spec) => {
|
|
|
268
288
|
role: "img",
|
|
269
289
|
"aria-labelledby": `${spec.id}-title ${spec.id}-desc`,
|
|
270
290
|
"font-family": tokens.font.sans,
|
|
271
|
-
}, el("title", { id: `${spec.id}-title` }, spec.title), el("desc", { id: `${spec.id}-desc` }, spec.description), el("defs", {}, el("clipPath", { id: `${spec.id}-frame` }, el("rect", { width, height }))), el("rect", { width, height, fill: pair.fill }), g({ "clip-path":
|
|
291
|
+
}, el("title", { id: `${spec.id}-title` }, spec.title), el("desc", { id: `${spec.id}-desc` }, spec.description), el("defs", {}, el("clipPath", { id: `${spec.id}-frame` }, el("rect", { width, height, ...corner }))), el("rect", { width, height, ...corner, fill: pair.fill }), g({ "clip-path": frame }, ...rings({ ...spec.rings, color: ringPath, ...ringCount }).body),
|
|
292
|
+
// On a banner, pieces and labels are clipped too, so nothing pokes past a rounded corner.
|
|
293
|
+
...(banner ? [g({ "clip-path": frame }, outlined, labels)] : [outlined, labels]));
|
|
272
294
|
};
|
|
@@ -5,5 +5,5 @@ export { COLOUR_TOKENS, DATA_COLOUR, MAX_ITEMS, paint, MAX_SIZE, checkParams, co
|
|
|
5
5
|
export { definePiece, type Piece } from "./piece.ts";
|
|
6
6
|
export * from "./library.ts";
|
|
7
7
|
export { EIP_ICONS, EIP_ICON_NAMES, EIP_ICON_HEIGHT, EIP_ICON_WIDTH, type EipIcon } from "./eip-icons.ts";
|
|
8
|
-
export { SAFE_ID, backgroundNames, comparePaths, coverTree, validateCoverSpec, type CoverPiece, type CoverSpec, type CustomNode } from "./cover.ts";
|
|
8
|
+
export { FORMATS, SAFE_ID, backgroundNames, bannerRingCount, comparePaths, coverTree, validateCoverSpec, type CoverFormat, type CoverPiece, type CoverSpec, type CustomNode } from "./cover.ts";
|
|
9
9
|
export { tokens, type Tokens } from "../../tokens/generated/tokens.ts";
|
package/dist/src/pieces/index.js
CHANGED
|
@@ -10,5 +10,5 @@ export { COLOUR_TOKENS, DATA_COLOUR, MAX_ITEMS, paint, MAX_SIZE, checkParams, co
|
|
|
10
10
|
export { definePiece } from "./piece.js";
|
|
11
11
|
export * from "./library.js";
|
|
12
12
|
export { EIP_ICONS, EIP_ICON_NAMES, EIP_ICON_HEIGHT, EIP_ICON_WIDTH } from "./eip-icons.js";
|
|
13
|
-
export { SAFE_ID, backgroundNames, comparePaths, coverTree, validateCoverSpec } from "./cover.js";
|
|
13
|
+
export { FORMATS, SAFE_ID, backgroundNames, bannerRingCount, comparePaths, coverTree, validateCoverSpec } from "./cover.js";
|
|
14
14
|
export { tokens } from "../../tokens/generated/tokens.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./metro.ts";
|
|
2
|
+
export * from "./ui-icons.ts";
|
|
3
|
+
export * from "./link-icons.ts";
|
|
2
4
|
type Point = [number, number];
|
|
3
5
|
export type PillParams = {
|
|
4
6
|
x: number;
|
|
@@ -10,13 +12,16 @@ export type PillParams = {
|
|
|
10
12
|
/** Pill label centred on (x, y), usually overlapping the top edge of what it names. */
|
|
11
13
|
export declare const pill: import("./piece.ts").Piece<PillParams>;
|
|
12
14
|
export declare const RING_COUNT = 14;
|
|
15
|
+
/** Most rings one call draws: enough for a banner's farthest corner from any centre a cover spec accepts (548). */
|
|
16
|
+
export declare const MAX_RINGS = 1000;
|
|
13
17
|
export type RingsParams = {
|
|
14
18
|
cx: number;
|
|
15
19
|
cy: number;
|
|
16
20
|
radius?: number;
|
|
17
21
|
color?: string;
|
|
22
|
+
count?: number;
|
|
18
23
|
};
|
|
19
|
-
/** Concentric rings around (cx, cy): 14
|
|
24
|
+
/** Concentric rings around (cx, cy): `count` rings (14 by default), step and width from the cover tokens. */
|
|
20
25
|
export declare const rings: import("./piece.ts").Piece<RingsParams>;
|
|
21
26
|
export type SheetRow = {
|
|
22
27
|
lead?: "none" | "checkbox" | "checked" | "question";
|
|
@@ -266,6 +271,10 @@ export declare const PIECES: {
|
|
|
266
271
|
readonly hexNode: import("./piece.ts").Piece<import("./metro.ts").NodeParams>;
|
|
267
272
|
readonly messageArrow: import("./piece.ts").Piece<import("./metro.ts").MessageArrowParams>;
|
|
268
273
|
readonly ledgerMark: import("./piece.ts").Piece<import("./metro.ts").LedgerMarkParams>;
|
|
274
|
+
readonly renewalRing: import("./piece.ts").Piece<import("./metro.ts").RenewalRingParams>;
|
|
275
|
+
readonly suggestedHalo: import("./piece.ts").Piece<import("./metro.ts").SuggestedHaloParams>;
|
|
276
|
+
readonly licenceMark: import("./piece.ts").Piece<import("./metro.ts").LicenceMarkParams>;
|
|
277
|
+
readonly zone: import("./piece.ts").Piece<import("./metro.ts").ZoneParams>;
|
|
269
278
|
readonly pill: import("./piece.ts").Piece<PillParams>;
|
|
270
279
|
readonly rings: import("./piece.ts").Piece<RingsParams>;
|
|
271
280
|
readonly sheet: import("./piece.ts").Piece<SheetParams>;
|
|
@@ -292,6 +301,8 @@ export declare const PIECES: {
|
|
|
292
301
|
readonly book: import("./piece.ts").Piece<BookParams>;
|
|
293
302
|
readonly muleFlow: import("./piece.ts").Piece<MuleFlowParams>;
|
|
294
303
|
readonly eipIcon: import("./piece.ts").Piece<EipIconParams>;
|
|
304
|
+
readonly uiIcon: import("./piece.ts").Piece<import("./ui-icons.ts").UiIconParams>;
|
|
305
|
+
readonly linkIcon: import("./piece.ts").Piece<import("./link-icons.ts").LinkIconParams>;
|
|
295
306
|
};
|
|
296
307
|
export type PieceName = keyof typeof PIECES;
|
|
297
308
|
export declare const PIECE_NAMES: PieceName[];
|
|
@@ -12,7 +12,11 @@ import { drawn, el, g, r2, shadowed } from "./node.js";
|
|
|
12
12
|
import { col, colour, count, label, MAX_SIZE, PATH_DATA, pos, size } from "./params.js";
|
|
13
13
|
import { definePiece } from "./piece.js";
|
|
14
14
|
import { METRO_PIECES } from "./metro.js";
|
|
15
|
+
import { uiIcon } from "./ui-icons.js";
|
|
16
|
+
import { linkIcon } from "./link-icons.js";
|
|
15
17
|
export * from "./metro.js";
|
|
18
|
+
export * from "./ui-icons.js";
|
|
19
|
+
export * from "./link-icons.js";
|
|
16
20
|
const ink = tokens.color.ink;
|
|
17
21
|
const mono = tokens.font.mono;
|
|
18
22
|
/** Pill label centred on (x, y), usually overlapping the top edge of what it names. */
|
|
@@ -23,9 +27,11 @@ export const pill = definePiece("pill", { x: pos(), y: pos(), text: label(), fil
|
|
|
23
27
|
});
|
|
24
28
|
// ---------- background ----------
|
|
25
29
|
export const RING_COUNT = 14;
|
|
26
|
-
/**
|
|
27
|
-
export const
|
|
28
|
-
|
|
30
|
+
/** Most rings one call draws: enough for a banner's farthest corner from any centre a cover spec accepts (548). */
|
|
31
|
+
export const MAX_RINGS = 1000;
|
|
32
|
+
/** Concentric rings around (cx, cy): `count` rings (14 by default), step and width from the cover tokens. */
|
|
33
|
+
export const rings = definePiece("rings", { cx: pos(), cy: pos(), radius: size(), color: col(), count: count(1, MAX_RINGS) }, ({ cx, cy, radius = tokens.cover.ringRadius, color = "color.ring", count: rings = RING_COUNT }) => drawn([
|
|
34
|
+
g({ fill: "none", stroke: colour(color, "color"), "stroke-width": tokens.cover.ringWidth }, ...Array.from({ length: rings }, (_, i) => el("circle", { cx, cy, r: radius + i * tokens.cover.ringStep }))),
|
|
29
35
|
]));
|
|
30
36
|
const rowSchema = {
|
|
31
37
|
lead: { kind: "enum", values: ["none", "checkbox", "checked", "question"] },
|
|
@@ -373,5 +379,7 @@ export const PIECES = {
|
|
|
373
379
|
muleFlow,
|
|
374
380
|
eipIcon,
|
|
375
381
|
...METRO_PIECES,
|
|
382
|
+
uiIcon,
|
|
383
|
+
linkIcon,
|
|
376
384
|
};
|
|
377
385
|
export const PIECE_NAMES = Object.keys(PIECES);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SvgNode } from "./node.ts";
|
|
2
|
+
import { type SurfaceName } from "./metro.ts";
|
|
3
|
+
export type LinkIcon = {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly description: string; /** A colour token path. */
|
|
7
|
+
readonly face: string;
|
|
8
|
+
readonly glyph: readonly SvgNode[];
|
|
9
|
+
};
|
|
10
|
+
/** Every link icon draws in a 46x46 box. */
|
|
11
|
+
export declare const LINK_ICON_SIZE = 46;
|
|
12
|
+
export declare const LINK_ICONS: readonly LinkIcon[];
|
|
13
|
+
export declare const LINK_ICON_NAMES: string[];
|
|
14
|
+
export type LinkIconParams = {
|
|
15
|
+
icon: string;
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
size?: number;
|
|
19
|
+
surface?: SurfaceName;
|
|
20
|
+
};
|
|
21
|
+
/** One link sticker at (x, y), `size` wide (46 by default), on the light or the dark page. */
|
|
22
|
+
export declare const linkIcon: import("./piece.ts").Piece<LinkIconParams>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Link icons as data (spec illustration-pieces, "Link icons as data"; design §6
|
|
2
|
+
// of stackledger-map-and-link-pieces): the social stickers of brunosouzas.com
|
|
3
|
+
// (public/images/home/social-*.svg at e29b3dd), with every colour replaced by
|
|
4
|
+
// its token, plus a `website` globe in the same style. `linkIcon` draws the
|
|
5
|
+
// site's frame (light, or dark with its rim) around the face and the glyph, at
|
|
6
|
+
// the site's measures (46 box, face 38 at 2, radius 9, outline 2.5).
|
|
7
|
+
//
|
|
8
|
+
// Credit: the GitHub mark is Font Awesome's (fontawesome.com), licensed
|
|
9
|
+
// CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/); its description
|
|
10
|
+
// keeps the credit.
|
|
11
|
+
//
|
|
12
|
+
// What can go wrong: an unknown icon is rejected by the schema. No node holds
|
|
13
|
+
// a link, a style, a script or a URL, so the output can ship as a static file.
|
|
14
|
+
import { tokens } from "../../tokens/generated/tokens.js";
|
|
15
|
+
import { drawn, el, g, r2 } from "./node.js";
|
|
16
|
+
import { colour, pos, size } from "./params.js";
|
|
17
|
+
import { definePiece } from "./piece.js";
|
|
18
|
+
import { SURFACES } from "./metro.js";
|
|
19
|
+
const C = tokens.color;
|
|
20
|
+
const INK = C.ink;
|
|
21
|
+
/** Every link icon draws in a 46x46 box. */
|
|
22
|
+
export const LINK_ICON_SIZE = 46;
|
|
23
|
+
export const LINK_ICONS = [
|
|
24
|
+
{
|
|
25
|
+
name: "github",
|
|
26
|
+
title: "GitHub",
|
|
27
|
+
description: "GitHub icon. The GitHub mark is from Font Awesome (fontawesome.com), CC BY 4.0.",
|
|
28
|
+
face: "color.white",
|
|
29
|
+
glyph: [g({ transform: "translate(9.5 9) scale(0.0455)" }, el("path", { d: "M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z", fill: INK, stroke: "none" }))],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "linkedin",
|
|
33
|
+
title: "LinkedIn",
|
|
34
|
+
description: "LinkedIn icon.",
|
|
35
|
+
face: "color.caller",
|
|
36
|
+
glyph: [
|
|
37
|
+
el("rect", { x: 12, y: 18.5, width: 4, height: 11.5, rx: 1, fill: INK, stroke: "none" }),
|
|
38
|
+
el("circle", { cx: 14, cy: 13.8, r: 2.4, fill: INK, stroke: "none" }),
|
|
39
|
+
el("path", { d: "M19.5,30 V18.5 H23.5 V20.3 C24.5,19 26.2,18.1 28,18.1 C31.2,18.1 32.5,20.2 32.5,23.6 V30 H28.5 V24.3 C28.5,22.7 27.8,21.9 26.6,21.9 C25.2,21.9 23.5,22.8 23.5,24.7 V30 Z", fill: INK, stroke: "none" }),
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "email",
|
|
44
|
+
title: "Email",
|
|
45
|
+
description: "Email icon.",
|
|
46
|
+
face: "color.transitional",
|
|
47
|
+
glyph: [
|
|
48
|
+
el("rect", { x: 9, y: 13, width: 24, height: 17, rx: 3, fill: C.white, stroke: INK, "stroke-width": 2.5 }),
|
|
49
|
+
el("path", { d: "M10,15 L21,23 L32,15", fill: "none", stroke: INK, "stroke-width": 2.5, "stroke-linejoin": "round" }),
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "website",
|
|
54
|
+
title: "Website",
|
|
55
|
+
description: "Website icon: a globe.",
|
|
56
|
+
face: "color.healthy",
|
|
57
|
+
glyph: [
|
|
58
|
+
el("circle", { cx: 21, cy: 21, r: 10.5, fill: C.white, stroke: INK, "stroke-width": 2.5 }),
|
|
59
|
+
el("ellipse", { cx: 21, cy: 21, rx: 4.6, ry: 10.5, fill: "none", stroke: INK, "stroke-width": 2 }),
|
|
60
|
+
el("path", { d: "M10.5 21H31.5M12.5 15.5H29.5M12.5 26.5H29.5", fill: "none", stroke: INK, "stroke-width": 2, "stroke-linecap": "round" }),
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
export const LINK_ICON_NAMES = LINK_ICONS.map((icon) => icon.name);
|
|
65
|
+
/** The site's frame: the shadow (and on dark the rim) behind the face. */
|
|
66
|
+
const frame = (surface) => {
|
|
67
|
+
const s = tokens.surface[surface];
|
|
68
|
+
return s.rimWidth > 0
|
|
69
|
+
? [
|
|
70
|
+
el("rect", { x: 4, y: 4, width: 41, height: 41, rx: 11, fill: s.shadow, stroke: "none" }),
|
|
71
|
+
el("rect", { x: 0.5, y: 0.5, width: 41, height: 41, rx: 11, fill: s.rim, stroke: "none" }),
|
|
72
|
+
]
|
|
73
|
+
: [el("rect", { x: 5, y: 5, width: 38, height: 38, rx: 9, fill: s.shadow, stroke: "none" })];
|
|
74
|
+
};
|
|
75
|
+
/** One link sticker at (x, y), `size` wide (46 by default), on the light or the dark page. */
|
|
76
|
+
export const linkIcon = definePiece("linkIcon", { icon: { kind: "enum", required: true, values: LINK_ICON_NAMES }, x: pos(), y: pos(), size: size(), surface: { kind: "enum", values: SURFACES } }, ({ icon, x, y, size: side = LINK_ICON_SIZE, surface = "light" }) => {
|
|
77
|
+
const data = LINK_ICONS.find((candidate) => candidate.name === icon);
|
|
78
|
+
const face = el("rect", { x: 2, y: 2, width: 38, height: 38, rx: 9, fill: colour(data.face, "face"), stroke: INK, "stroke-width": 2.5 });
|
|
79
|
+
return drawn([g({ transform: `translate(${r2(x)} ${r2(y)}) scale(${side / LINK_ICON_SIZE})`, stroke: "none" }, ...frame(surface), face, ...data.glyph)]);
|
|
80
|
+
});
|
|
@@ -8,13 +8,13 @@ export type Ink = {
|
|
|
8
8
|
export type LineBadgeParams = Ink & {
|
|
9
9
|
x: number;
|
|
10
10
|
y: number;
|
|
11
|
-
text
|
|
11
|
+
text?: string;
|
|
12
12
|
fill: string;
|
|
13
13
|
width?: number;
|
|
14
14
|
height?: number;
|
|
15
15
|
flat?: boolean;
|
|
16
16
|
};
|
|
17
|
-
/** A line's terminal badge: a pill in the line colour with its code in white, centred on (x, y). */
|
|
17
|
+
/** A line's terminal badge: a pill in the line colour with its code in white, centred on (x, y); without `text`, the pill alone. */
|
|
18
18
|
export declare const lineBadge: import("./piece.ts").Piece<LineBadgeParams>;
|
|
19
19
|
export type StationMarkerParams = Ink & {
|
|
20
20
|
cx: number;
|
|
@@ -76,9 +76,44 @@ export type LedgerMarkParams = {
|
|
|
76
76
|
y: number;
|
|
77
77
|
size?: number;
|
|
78
78
|
line?: string;
|
|
79
|
+
surface?: SurfaceName;
|
|
79
80
|
};
|
|
80
|
-
/** stackledger's mark (favicon, header): an ink square with a bent line and two stations, 32 units scaled to `size`.
|
|
81
|
+
/** stackledger's mark (favicon, header): an ink square with a bent line and two stations, 32 units scaled to `size`.
|
|
82
|
+
* On a surface with a rim (dark), a light rim of 2 × rimWidth lines the square inside its 32 box (card 167). */
|
|
81
83
|
export declare const ledgerMark: import("./piece.ts").Piece<LedgerMarkParams>;
|
|
84
|
+
export type RenewalRingParams = Ink & {
|
|
85
|
+
cx: number;
|
|
86
|
+
cy: number;
|
|
87
|
+
r: number;
|
|
88
|
+
width?: number;
|
|
89
|
+
};
|
|
90
|
+
/** The renewal ring: bands like a bent pipe round a station (rim on dark, ink, then color.blocked), no shadow. */
|
|
91
|
+
export declare const renewalRing: import("./piece.ts").Piece<RenewalRingParams>;
|
|
92
|
+
export type SuggestedHaloParams = {
|
|
93
|
+
cx: number;
|
|
94
|
+
cy: number;
|
|
95
|
+
r: number;
|
|
96
|
+
surface?: SurfaceName;
|
|
97
|
+
};
|
|
98
|
+
/** The suggested-position halo: a dashed muted circle, no ink. */
|
|
99
|
+
export declare const suggestedHalo: import("./piece.ts").Piece<SuggestedHaloParams>;
|
|
100
|
+
export type LicenceMarkParams = Ink & {
|
|
101
|
+
cx: number;
|
|
102
|
+
cy: number;
|
|
103
|
+
state?: "owned" | "toBuy";
|
|
104
|
+
size?: number;
|
|
105
|
+
};
|
|
106
|
+
/** A licence: a small disc sticker, green when held, white to buy. Its own room rule: size ≥ 3 × outline. */
|
|
107
|
+
export declare const licenceMark: import("./piece.ts").Piece<LicenceMarkParams>;
|
|
108
|
+
export type ZoneParams = {
|
|
109
|
+
points: Point[];
|
|
110
|
+
fill: string;
|
|
111
|
+
title?: string;
|
|
112
|
+
titleAt?: Point;
|
|
113
|
+
surface?: SurfaceName;
|
|
114
|
+
};
|
|
115
|
+
/** A zone: the outline the caller gives (stackledger's hull), widened by a round stroke, at the surface's zone opacity; an optional title. */
|
|
116
|
+
export declare const zone: import("./piece.ts").Piece<ZoneParams>;
|
|
82
117
|
export declare const METRO_PIECES: {
|
|
83
118
|
readonly lineBadge: import("./piece.ts").Piece<LineBadgeParams>;
|
|
84
119
|
readonly stationMarker: import("./piece.ts").Piece<StationMarkerParams>;
|
|
@@ -89,5 +124,9 @@ export declare const METRO_PIECES: {
|
|
|
89
124
|
readonly hexNode: import("./piece.ts").Piece<NodeParams>;
|
|
90
125
|
readonly messageArrow: import("./piece.ts").Piece<MessageArrowParams>;
|
|
91
126
|
readonly ledgerMark: import("./piece.ts").Piece<LedgerMarkParams>;
|
|
127
|
+
readonly renewalRing: import("./piece.ts").Piece<RenewalRingParams>;
|
|
128
|
+
readonly suggestedHalo: import("./piece.ts").Piece<SuggestedHaloParams>;
|
|
129
|
+
readonly licenceMark: import("./piece.ts").Piece<LicenceMarkParams>;
|
|
130
|
+
readonly zone: import("./piece.ts").Piece<ZoneParams>;
|
|
92
131
|
};
|
|
93
132
|
export {};
|
package/dist/src/pieces/metro.js
CHANGED
|
@@ -50,9 +50,10 @@ const sticker = (shape, face, inkParams) => {
|
|
|
50
50
|
// Every text sets its own anchor, size and weight, so a surrounding group (a cover's label style) does not leak in.
|
|
51
51
|
const words = (x, y, value, attrs) => el("text", { x: r2(x), y: r2(y), "font-family": font, ...attrs }, value);
|
|
52
52
|
const pill = (x, y, w, h) => (dx, dy, attrs) => el("rect", { x: r2(x + dx), y: r2(y + dy), width: r2(w), height: r2(h), rx: r2(h / 2), ...attrs });
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
// Without a text, the default width is the one-character width (card 167).
|
|
54
|
+
const badgeWidth = ({ width, text }) => width ?? Math.max(30, 7.6 * (text === undefined ? 1 : Array.from(text).length) + 18);
|
|
55
|
+
/** A line's terminal badge: a pill in the line colour with its code in white, centred on (x, y); without `text`, the pill alone. */
|
|
56
|
+
export const lineBadge = definePiece("lineBadge", { x: pos(), y: pos(), text: label(false), fill: dataColour(), width: size(), height: size(), flat: { kind: "boolean" }, ...inkSchema }, (params) => {
|
|
56
57
|
const { x, y, text, fill, height = M.badgeHeight, flat = false } = params;
|
|
57
58
|
const width = badgeWidth(params);
|
|
58
59
|
const face = paint(fill);
|
|
@@ -60,7 +61,7 @@ export const lineBadge = definePiece("lineBadge", { x: pos(), y: pos(), text: la
|
|
|
60
61
|
const inset = flat ? outline / 2 : 0;
|
|
61
62
|
const shape = pill(x - width / 2 + inset, y - height / 2 + inset, width - 2 * inset, height - 2 * inset);
|
|
62
63
|
const body = flat ? [shape(0, 0, { fill: face, stroke: ink, "stroke-width": r2(outline), "stroke-linejoin": "round" })] : sticker(shape, face, params);
|
|
63
|
-
return drawn(body, [words(x, y + 4.5, text, { fill: white, "font-size": M.badgeTextSize, "font-weight": bold, "text-anchor": "middle" })]);
|
|
64
|
+
return drawn(body, text === undefined ? [] : [words(x, y + 4.5, text, { fill: white, "font-size": M.badgeTextSize, "font-weight": bold, "text-anchor": "middle" })]);
|
|
64
65
|
}, (params) => {
|
|
65
66
|
const height = params.height ?? M.badgeHeight;
|
|
66
67
|
return limits(atLeast("width", badgeWidth(params), height, "the height"), roomFor("height", height, params));
|
|
@@ -125,10 +126,11 @@ const nodeSchema = {
|
|
|
125
126
|
const nodeLimits = (kind) => (params) => {
|
|
126
127
|
const { width, height = M.nodeHeight, subtitle, state = "normal" } = params;
|
|
127
128
|
const radius = params.radius ?? M.nodeRadius;
|
|
128
|
-
return limits(atLeast("width", width, 2 * height, "twice the height"), kind === "box" && radius > height / 2 && { path: "radius", message: `must be at most half the height (${r2(height / 2)}), found ${r2(radius)}` }, state
|
|
129
|
+
return limits(atLeast("width", width, 2 * height, "twice the height"), kind === "box" && radius > height / 2 && { path: "radius", message: `must be at most half the height (${r2(height / 2)}), found ${r2(radius)}` }, state !== "placeholder" && roomFor("height", height, params));
|
|
129
130
|
};
|
|
130
131
|
const drawNode = (kind, params) => {
|
|
131
132
|
const { x, y, width, height = M.nodeHeight, title, subtitle, state = "normal" } = params;
|
|
133
|
+
const inset = kind === "box" ? 12 : height / 2;
|
|
132
134
|
const radius = kind === "pill" ? height / 2 : (params.radius ?? M.nodeRadius);
|
|
133
135
|
const shape = kind === "hex"
|
|
134
136
|
? (dx, dy, attrs) => {
|
|
@@ -140,10 +142,16 @@ const drawNode = (kind, params) => {
|
|
|
140
142
|
const { s } = inkOf(params);
|
|
141
143
|
const middle = y + height / 2;
|
|
142
144
|
if (state === "placeholder") {
|
|
143
|
-
|
|
145
|
+
const dashed = [shape(0, 0, { fill: "none", stroke: s.muted, "stroke-width": 1.5, "stroke-dasharray": "5 4", "stroke-linejoin": "round" })];
|
|
146
|
+
// With a subtitle, a normal node's text layout, every word muted (stackledger's unlinked box; card 167).
|
|
147
|
+
return drawn(dashed, subtitle === undefined
|
|
148
|
+
? [words(x + width / 2, middle + 4.5, title, { fill: s.muted, "font-size": M.labelSize, "font-weight": 400, "text-anchor": "middle" })]
|
|
149
|
+
: [
|
|
150
|
+
words(x + inset, middle - 3, title, { fill: s.muted, "font-size": M.labelSize, "font-weight": bold, "text-anchor": "start" }),
|
|
151
|
+
words(x + inset, middle + 14, subtitle, { fill: s.muted, "font-size": 11, "font-weight": 400, "text-anchor": "start" }),
|
|
152
|
+
]);
|
|
144
153
|
}
|
|
145
154
|
const focus = state === "focus";
|
|
146
|
-
const inset = kind === "box" ? 12 : height / 2;
|
|
147
155
|
const titleFill = focus ? ink : s.text;
|
|
148
156
|
const text = subtitle === undefined
|
|
149
157
|
? [words(x + inset, middle + 4.5, title, { fill: titleFill, "font-size": M.labelSize, "font-weight": bold, "text-anchor": "start" })]
|
|
@@ -198,8 +206,61 @@ export const messageArrow = definePiece("messageArrow", {
|
|
|
198
206
|
const distance = Math.hypot(to[0] - from[0], to[1] - from[1]);
|
|
199
207
|
return limits(distance === 0 && { path: "to", message: "must differ from from" }, distance > 0 && head && distance < 10 * width && { path: "to", message: `is too close to from for the head: needs at least ${r2(10 * width)}, found ${r2(distance)}` });
|
|
200
208
|
});
|
|
201
|
-
/** stackledger's mark (favicon, header): an ink square with a bent line and two stations, 32 units scaled to `size`.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
]
|
|
205
|
-
|
|
209
|
+
/** stackledger's mark (favicon, header): an ink square with a bent line and two stations, 32 units scaled to `size`.
|
|
210
|
+
* On a surface with a rim (dark), a light rim of 2 × rimWidth lines the square inside its 32 box (card 167). */
|
|
211
|
+
export const ledgerMark = definePiece("ledgerMark", { x: pos(), y: pos(), size: { kind: "number", min: 12, max: 512 }, line: col(), surface: surfaceParam }, ({ x, y, size: side = 32, line = "illustration.accent", surface = "light" }) => {
|
|
212
|
+
const { rimWidth, rim } = tokens.surface[surface];
|
|
213
|
+
const band = 2 * rimWidth;
|
|
214
|
+
const square = rimWidth > 0
|
|
215
|
+
? [
|
|
216
|
+
el("rect", { x: 0, y: 0, width: 32, height: 32, rx: 7, fill: rim, stroke: "none" }),
|
|
217
|
+
el("rect", { x: band, y: band, width: 32 - 2 * band, height: 32 - 2 * band, rx: 7 - band, fill: ink, stroke: "none" }),
|
|
218
|
+
]
|
|
219
|
+
: [el("rect", { x: 0, y: 0, width: 32, height: 32, rx: 7, fill: ink, stroke: "none" })];
|
|
220
|
+
return drawn([
|
|
221
|
+
g({ transform: `translate(${r2(x)} ${r2(y)}) scale(${side / 32})` }, ...square, el("path", { d: "M7 23 L14 16 L25 16", fill: "none", stroke: colour(line, "line"), "stroke-width": 4.5, "stroke-linecap": "round", "stroke-linejoin": "round" }), el("circle", { cx: 14, cy: 16, r: 4, fill: white, stroke: ink, "stroke-width": 2 }), el("circle", { cx: 25, cy: 16, r: 2.6, fill: white, stroke: ink, "stroke-width": 1.6 })),
|
|
222
|
+
]);
|
|
223
|
+
});
|
|
224
|
+
/** The renewal ring: bands like a bent pipe round a station (rim on dark, ink, then color.blocked), no shadow. */
|
|
225
|
+
export const renewalRing = definePiece("renewalRing", { cx: pos(), cy: pos(), r: size(true), width: { kind: "number", min: 0, exclusiveMin: true, max: 50 }, ...inkSchema }, (params) => {
|
|
226
|
+
const { cx, cy, r, width = M.ringWidth } = params;
|
|
227
|
+
const { s, outline, rim } = inkOf(params);
|
|
228
|
+
// Radius and band widths as given, like pipe's widths: a valid hairline must not round to zero.
|
|
229
|
+
const band = (stroke, strokeWidth) => el("circle", { cx: r2(cx), cy: r2(cy), r, fill: "none", stroke, "stroke-width": strokeWidth });
|
|
230
|
+
return drawn([...(s.rimWidth > 0 ? [band(s.rim, width + outline + 2 * rim)] : []), band(ink, width + outline), band(tokens.color.blocked, width)]);
|
|
231
|
+
}, (params) => {
|
|
232
|
+
const { outline, rim } = inkOf(params);
|
|
233
|
+
const need = ((params.width ?? M.ringWidth) + outline + 2 * rim) / 2;
|
|
234
|
+
return limits(params.r < need && { path: "r", message: `leaves no room for the ring's bands: needs at least ${r2(need)}` });
|
|
235
|
+
});
|
|
236
|
+
/** The suggested-position halo: a dashed muted circle, no ink. */
|
|
237
|
+
export const suggestedHalo = definePiece("suggestedHalo", { cx: pos(), cy: pos(), r: size(true), surface: surfaceParam }, ({ cx, cy, r, surface = "light" }) => drawn([el("circle", { cx: r2(cx), cy: r2(cy), r, fill: "none", stroke: tokens.surface[surface].muted, "stroke-width": M.haloWidth, "stroke-dasharray": "4 3" })]));
|
|
238
|
+
/** A licence: a small disc sticker, green when held, white to buy. Its own room rule: size ≥ 3 × outline. */
|
|
239
|
+
export const licenceMark = definePiece("licenceMark", { cx: pos(), cy: pos(), state: { kind: "enum", values: ["owned", "toBuy"] }, size: size(), ...inkSchema }, (params) => {
|
|
240
|
+
const { cx, cy, state = "owned", size: side = M.licenceSize } = params;
|
|
241
|
+
const shape = (dx, dy, attrs) => el("circle", { cx: r2(cx + dx), cy: r2(cy + dy), r: r2(side / 2), ...attrs });
|
|
242
|
+
return drawn(sticker(shape, state === "owned" ? tokens.color.healthy : white, params));
|
|
243
|
+
}, (params) => {
|
|
244
|
+
const need = 3 * inkOf(params).outline;
|
|
245
|
+
const side = params.size ?? M.licenceSize;
|
|
246
|
+
return limits(side < need && { path: "size", message: `leaves no room for the licence's face: needs at least ${r2(need)} at this ink scale` });
|
|
247
|
+
});
|
|
248
|
+
/** Mixes a #RRGGBB colour with white by `amount` (stackledger's zone title tint). */
|
|
249
|
+
const tint = (hex, amount) => "#" + [1, 3, 5].map((i) => Math.round(parseInt(hex.slice(i, i + 2), 16) * (1 - amount) + 255 * amount).toString(16).padStart(2, "0").toUpperCase()).join("");
|
|
250
|
+
/** A zone: the outline the caller gives (stackledger's hull), widened by a round stroke, at the surface's zone opacity; an optional title. */
|
|
251
|
+
export const zone = definePiece("zone", { points: { kind: "array", required: true, minItems: 1, maxItems: 10000, of: point() }, fill: dataColour(), title: label(false), titleAt: point(false), surface: surfaceParam }, ({ points, fill, title, titleAt, surface = "light" }) => {
|
|
252
|
+
const colourOf = paint(fill);
|
|
253
|
+
const pad = M.zonePadding;
|
|
254
|
+
const at = (p) => `${r2(p[0])},${r2(p[1])}`;
|
|
255
|
+
const shape = points.length === 1
|
|
256
|
+
? el("circle", { cx: r2(points[0][0]), cy: r2(points[0][1]), r: pad, fill: colourOf })
|
|
257
|
+
: points.length === 2
|
|
258
|
+
? el("line", { x1: r2(points[0][0]), y1: r2(points[0][1]), x2: r2(points[1][0]), y2: r2(points[1][1]), stroke: colourOf, "stroke-width": 2 * pad, "stroke-linecap": "round" })
|
|
259
|
+
: el("polygon", { points: points.map(at).join(" "), fill: colourOf, stroke: colourOf, "stroke-width": 2 * pad, "stroke-linejoin": "round" });
|
|
260
|
+
const titleFill = surface === "dark" ? tint(colourOf, M.zoneTitleTint) : colourOf;
|
|
261
|
+
const text = title !== undefined && titleAt !== undefined
|
|
262
|
+
? [words(titleAt[0], titleAt[1] + 13, title, { fill: titleFill, "font-size": M.labelSize, "font-weight": bold, "text-anchor": "start" })]
|
|
263
|
+
: [];
|
|
264
|
+
return drawn([g({ opacity: tokens.surface[surface].zoneOpacity, stroke: "none" }, shape)], text);
|
|
265
|
+
}, ({ title, titleAt }) => limits(title !== undefined && titleAt === undefined && { path: "titleAt", message: "is required with a title" }, titleAt !== undefined && title === undefined && { path: "title", message: "is required with titleAt" }));
|
|
266
|
+
export const METRO_PIECES = { lineBadge, stationMarker, pipe, countBadge, nodeBlock, pillNode, hexNode, messageArrow, ledgerMark, renewalRing, suggestedHalo, licenceMark, zone };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SvgNode } from "./node.ts";
|
|
2
|
+
export type UiIcon = {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly title: string;
|
|
5
|
+
readonly description: string;
|
|
6
|
+
readonly nodes: readonly SvgNode[];
|
|
7
|
+
};
|
|
8
|
+
/** Every UI icon draws in a 24x24 box. */
|
|
9
|
+
export declare const UI_ICON_SIZE = 24;
|
|
10
|
+
export declare const UI_ICONS: readonly UiIcon[];
|
|
11
|
+
export declare const UI_ICON_NAMES: string[];
|
|
12
|
+
export type UiIconParams = {
|
|
13
|
+
name: string;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
size?: number;
|
|
17
|
+
colour?: string;
|
|
18
|
+
};
|
|
19
|
+
/** One UI icon at (x, y), `size` wide (24 by default), in one token colour (`color.ink` by default). */
|
|
20
|
+
export declare const uiIcon: import("./piece.ts").Piece<UiIconParams>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { el, g, drawn, r2 } from "./node.js";
|
|
2
|
+
import { col, pos, size } from "./params.js";
|
|
3
|
+
import { definePiece } from "./piece.js";
|
|
4
|
+
import { colour } from "./params.js";
|
|
5
|
+
/** Every UI icon draws in a 24x24 box. */
|
|
6
|
+
export const UI_ICON_SIZE = 24;
|
|
7
|
+
const stroked = (d) => el("path", { d, fill: "none", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" });
|
|
8
|
+
const filled = (d) => el("path", { d, stroke: "none" });
|
|
9
|
+
export const UI_ICONS = [
|
|
10
|
+
{
|
|
11
|
+
name: "sun",
|
|
12
|
+
title: "Sun",
|
|
13
|
+
description: "Light theme: a disc with eight rays.",
|
|
14
|
+
nodes: [el("circle", { cx: 12, cy: 12, r: 4.5, stroke: "none" }), stroked("M12 2.5v2.5M12 19v2.5M2.5 12H5M19 12h2.5M5.3 5.3l1.8 1.8M16.9 16.9l1.8 1.8M5.3 18.7l1.8-1.8M16.9 7.1l1.8-1.8")],
|
|
15
|
+
},
|
|
16
|
+
{ name: "moon", title: "Moon", description: "Dark theme: a crescent moon.", nodes: [filled("M20.5 14.6A8.6 8.6 0 0 1 9.4 3.5a8.6 8.6 0 1 0 11.1 11.1Z")] },
|
|
17
|
+
{ name: "zoomIn", title: "Zoom in", description: "A magnifier with a plus.", nodes: [stroked("M10.5 4a6.5 6.5 0 1 0 0 13a6.5 6.5 0 1 0 0-13ZM15.3 15.3l5.2 5.2M7.5 10.5h6M10.5 7.5v6")] },
|
|
18
|
+
{ name: "zoomOut", title: "Zoom out", description: "A magnifier with a minus.", nodes: [stroked("M10.5 4a6.5 6.5 0 1 0 0 13a6.5 6.5 0 1 0 0-13ZM15.3 15.3l5.2 5.2M7.5 10.5h6")] },
|
|
19
|
+
{ name: "reset", title: "Reset", description: "A circular arrow: back to the start.", nodes: [stroked("M4.5 12A7.5 7.5 0 1 0 6.7 6.7M4 3.5v4.5h4.5")] },
|
|
20
|
+
{
|
|
21
|
+
name: "calendar",
|
|
22
|
+
title: "Calendar",
|
|
23
|
+
description: "A calendar page with one day marked.",
|
|
24
|
+
nodes: [stroked("M5.5 5h13a2 2 0 0 1 2 2v11.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2ZM3.5 10h17M8 3v4M16 3v4"), el("rect", { x: 7, y: 13, width: 3, height: 3, rx: 0.5, stroke: "none" })],
|
|
25
|
+
},
|
|
26
|
+
{ name: "download", title: "Download", description: "An arrow down into a tray.", nodes: [stroked("M12 3.5v11M7.5 10l4.5 4.5 4.5-4.5M4.5 16.5v3h15v-3")] },
|
|
27
|
+
];
|
|
28
|
+
export const UI_ICON_NAMES = UI_ICONS.map((icon) => icon.name);
|
|
29
|
+
/** One UI icon at (x, y), `size` wide (24 by default), in one token colour (`color.ink` by default). */
|
|
30
|
+
export const uiIcon = definePiece("uiIcon", { name: { kind: "enum", required: true, values: UI_ICON_NAMES }, x: pos(), y: pos(), size: size(), colour: col() }, ({ name, x, y, size: side = UI_ICON_SIZE, colour: path = "color.ink" }) => {
|
|
31
|
+
const icon = UI_ICONS.find((candidate) => candidate.name === name);
|
|
32
|
+
const paint = colour(path, "colour");
|
|
33
|
+
return drawn([g({ transform: `translate(${r2(x)} ${r2(y)}) scale(${side / UI_ICON_SIZE})`, fill: paint, stroke: paint }, ...icon.nodes)]);
|
|
34
|
+
});
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export declare const tokens: {
|
|
2
|
+
readonly banner: {
|
|
3
|
+
readonly height: 420;
|
|
4
|
+
readonly radius: 20;
|
|
5
|
+
readonly width: 1200;
|
|
6
|
+
};
|
|
2
7
|
readonly color: {
|
|
3
8
|
readonly backend: "#FF6B6B";
|
|
4
9
|
readonly blocked: "#FF8A3D";
|
|
@@ -131,14 +136,19 @@ export declare const tokens: {
|
|
|
131
136
|
readonly countHeight: 16;
|
|
132
137
|
readonly countTextSize: 11;
|
|
133
138
|
readonly edgeWidth: 2.5;
|
|
139
|
+
readonly haloWidth: 1.5;
|
|
134
140
|
readonly interchangeExtra: 2;
|
|
135
141
|
readonly labelSize: 13;
|
|
142
|
+
readonly licenceSize: 10;
|
|
136
143
|
readonly lineWidth: 12;
|
|
137
144
|
readonly nodeHeight: 46;
|
|
138
145
|
readonly nodeRadius: 10;
|
|
139
146
|
readonly outline: 3;
|
|
147
|
+
readonly ringWidth: 3;
|
|
140
148
|
readonly shadow: 3.5;
|
|
141
149
|
readonly stopRadius: 12;
|
|
150
|
+
readonly zonePadding: 32;
|
|
151
|
+
readonly zoneTitleTint: 0.6;
|
|
142
152
|
};
|
|
143
153
|
readonly motion: {
|
|
144
154
|
readonly dimOpacity: 0.42;
|
|
@@ -228,6 +238,7 @@ export declare const tokens: {
|
|
|
228
238
|
readonly rimWidth: 1;
|
|
229
239
|
readonly shadow: "#4C5CB8";
|
|
230
240
|
readonly text: "#EEF2FF";
|
|
241
|
+
readonly zoneOpacity: 0.22;
|
|
231
242
|
};
|
|
232
243
|
readonly light: {
|
|
233
244
|
readonly background: "#EEF2FF";
|
|
@@ -238,6 +249,7 @@ export declare const tokens: {
|
|
|
238
249
|
readonly rimWidth: 0;
|
|
239
250
|
readonly shadow: "#0A1033";
|
|
240
251
|
readonly text: "#0A1033";
|
|
252
|
+
readonly zoneOpacity: 0.12;
|
|
241
253
|
};
|
|
242
254
|
};
|
|
243
255
|
};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// Generated from tokens/source.json by scripts/tokens.ts (npm run tokens). Do not edit by hand.
|
|
2
2
|
export const tokens = {
|
|
3
|
+
"banner": {
|
|
4
|
+
"height": 420,
|
|
5
|
+
"radius": 20,
|
|
6
|
+
"width": 1200
|
|
7
|
+
},
|
|
3
8
|
"color": {
|
|
4
9
|
"backend": "#FF6B6B",
|
|
5
10
|
"blocked": "#FF8A3D",
|
|
@@ -132,14 +137,19 @@ export const tokens = {
|
|
|
132
137
|
"countHeight": 16,
|
|
133
138
|
"countTextSize": 11,
|
|
134
139
|
"edgeWidth": 2.5,
|
|
140
|
+
"haloWidth": 1.5,
|
|
135
141
|
"interchangeExtra": 2,
|
|
136
142
|
"labelSize": 13,
|
|
143
|
+
"licenceSize": 10,
|
|
137
144
|
"lineWidth": 12,
|
|
138
145
|
"nodeHeight": 46,
|
|
139
146
|
"nodeRadius": 10,
|
|
140
147
|
"outline": 3,
|
|
148
|
+
"ringWidth": 3,
|
|
141
149
|
"shadow": 3.5,
|
|
142
|
-
"stopRadius": 12
|
|
150
|
+
"stopRadius": 12,
|
|
151
|
+
"zonePadding": 32,
|
|
152
|
+
"zoneTitleTint": 0.6
|
|
143
153
|
},
|
|
144
154
|
"motion": {
|
|
145
155
|
"dimOpacity": 0.42,
|
|
@@ -228,7 +238,8 @@ export const tokens = {
|
|
|
228
238
|
"rim": "#EEF2FF",
|
|
229
239
|
"rimWidth": 1,
|
|
230
240
|
"shadow": "#4C5CB8",
|
|
231
|
-
"text": "#EEF2FF"
|
|
241
|
+
"text": "#EEF2FF",
|
|
242
|
+
"zoneOpacity": 0.22
|
|
232
243
|
},
|
|
233
244
|
"light": {
|
|
234
245
|
"background": "#EEF2FF",
|
|
@@ -238,7 +249,8 @@ export const tokens = {
|
|
|
238
249
|
"rim": "#EEF2FF",
|
|
239
250
|
"rimWidth": 0,
|
|
240
251
|
"shadow": "#0A1033",
|
|
241
|
-
"text": "#0A1033"
|
|
252
|
+
"text": "#0A1033",
|
|
253
|
+
"zoneOpacity": 0.12
|
|
242
254
|
}
|
|
243
255
|
}
|
|
244
256
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brunosouzas/design-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Design tokens and illustration pieces of brunosouzas.com: tokens as CSS, JSON and TypeScript, pieces that draw SVG (and React), and a cover command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/* Generated from tokens/source.json by scripts/tokens.ts (npm run tokens). Do not edit by hand. */
|
|
2
2
|
:root {
|
|
3
|
+
--ds-banner-height: 420px;
|
|
4
|
+
--ds-banner-radius: 20px;
|
|
5
|
+
--ds-banner-width: 1200px;
|
|
3
6
|
--ds-color-backend: #FF6B6B;
|
|
4
7
|
--ds-color-blocked: #FF8A3D;
|
|
5
8
|
--ds-color-caller: #8FB2FF;
|
|
@@ -79,14 +82,19 @@
|
|
|
79
82
|
--ds-map-count-height: 16px;
|
|
80
83
|
--ds-map-count-text-size: 11px;
|
|
81
84
|
--ds-map-edge-width: 2.5px;
|
|
85
|
+
--ds-map-halo-width: 1.5px;
|
|
82
86
|
--ds-map-interchange-extra: 2px;
|
|
83
87
|
--ds-map-label-size: 13px;
|
|
88
|
+
--ds-map-licence-size: 10px;
|
|
84
89
|
--ds-map-line-width: 12px;
|
|
85
90
|
--ds-map-node-height: 46px;
|
|
86
91
|
--ds-map-node-radius: 10px;
|
|
87
92
|
--ds-map-outline: 3px;
|
|
93
|
+
--ds-map-ring-width: 3px;
|
|
88
94
|
--ds-map-shadow: 3.5px;
|
|
89
95
|
--ds-map-stop-radius: 12px;
|
|
96
|
+
--ds-map-zone-padding: 32px;
|
|
97
|
+
--ds-map-zone-title-tint: 0.6;
|
|
90
98
|
--ds-motion-dim-opacity: 0.42;
|
|
91
99
|
--ds-motion-draw-arrow: 20;
|
|
92
100
|
--ds-motion-fade: 10;
|
|
@@ -149,6 +157,7 @@
|
|
|
149
157
|
--ds-surface-dark-rim-width: 1px;
|
|
150
158
|
--ds-surface-dark-shadow: #4C5CB8;
|
|
151
159
|
--ds-surface-dark-text: #EEF2FF;
|
|
160
|
+
--ds-surface-dark-zone-opacity: 0.22;
|
|
152
161
|
--ds-surface-light-background: #EEF2FF;
|
|
153
162
|
--ds-surface-light-grid: #E0E7FB;
|
|
154
163
|
--ds-surface-light-muted: #5B6694;
|
|
@@ -157,4 +166,5 @@
|
|
|
157
166
|
--ds-surface-light-rim-width: 0px;
|
|
158
167
|
--ds-surface-light-shadow: #0A1033;
|
|
159
168
|
--ds-surface-light-text: #0A1033;
|
|
169
|
+
--ds-surface-light-zone-opacity: 0.12;
|
|
160
170
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"banner": {
|
|
3
|
+
"height": 420,
|
|
4
|
+
"radius": 20,
|
|
5
|
+
"width": 1200
|
|
6
|
+
},
|
|
2
7
|
"color": {
|
|
3
8
|
"backend": "#FF6B6B",
|
|
4
9
|
"blocked": "#FF8A3D",
|
|
@@ -131,14 +136,19 @@
|
|
|
131
136
|
"countHeight": 16,
|
|
132
137
|
"countTextSize": 11,
|
|
133
138
|
"edgeWidth": 2.5,
|
|
139
|
+
"haloWidth": 1.5,
|
|
134
140
|
"interchangeExtra": 2,
|
|
135
141
|
"labelSize": 13,
|
|
142
|
+
"licenceSize": 10,
|
|
136
143
|
"lineWidth": 12,
|
|
137
144
|
"nodeHeight": 46,
|
|
138
145
|
"nodeRadius": 10,
|
|
139
146
|
"outline": 3,
|
|
147
|
+
"ringWidth": 3,
|
|
140
148
|
"shadow": 3.5,
|
|
141
|
-
"stopRadius": 12
|
|
149
|
+
"stopRadius": 12,
|
|
150
|
+
"zonePadding": 32,
|
|
151
|
+
"zoneTitleTint": 0.6
|
|
142
152
|
},
|
|
143
153
|
"motion": {
|
|
144
154
|
"dimOpacity": 0.42,
|
|
@@ -227,7 +237,8 @@
|
|
|
227
237
|
"rim": "#EEF2FF",
|
|
228
238
|
"rimWidth": 1,
|
|
229
239
|
"shadow": "#4C5CB8",
|
|
230
|
-
"text": "#EEF2FF"
|
|
240
|
+
"text": "#EEF2FF",
|
|
241
|
+
"zoneOpacity": 0.22
|
|
231
242
|
},
|
|
232
243
|
"light": {
|
|
233
244
|
"background": "#EEF2FF",
|
|
@@ -237,7 +248,8 @@
|
|
|
237
248
|
"rim": "#EEF2FF",
|
|
238
249
|
"rimWidth": 0,
|
|
239
250
|
"shadow": "#0A1033",
|
|
240
|
-
"text": "#0A1033"
|
|
251
|
+
"text": "#0A1033",
|
|
252
|
+
"zoneOpacity": 0.12
|
|
241
253
|
}
|
|
242
254
|
}
|
|
243
255
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// Generated from tokens/source.json by scripts/tokens.ts (npm run tokens). Do not edit by hand.
|
|
2
2
|
|
|
3
3
|
export const tokens = {
|
|
4
|
+
"banner": {
|
|
5
|
+
"height": 420,
|
|
6
|
+
"radius": 20,
|
|
7
|
+
"width": 1200
|
|
8
|
+
},
|
|
4
9
|
"color": {
|
|
5
10
|
"backend": "#FF6B6B",
|
|
6
11
|
"blocked": "#FF8A3D",
|
|
@@ -133,14 +138,19 @@ export const tokens = {
|
|
|
133
138
|
"countHeight": 16,
|
|
134
139
|
"countTextSize": 11,
|
|
135
140
|
"edgeWidth": 2.5,
|
|
141
|
+
"haloWidth": 1.5,
|
|
136
142
|
"interchangeExtra": 2,
|
|
137
143
|
"labelSize": 13,
|
|
144
|
+
"licenceSize": 10,
|
|
138
145
|
"lineWidth": 12,
|
|
139
146
|
"nodeHeight": 46,
|
|
140
147
|
"nodeRadius": 10,
|
|
141
148
|
"outline": 3,
|
|
149
|
+
"ringWidth": 3,
|
|
142
150
|
"shadow": 3.5,
|
|
143
|
-
"stopRadius": 12
|
|
151
|
+
"stopRadius": 12,
|
|
152
|
+
"zonePadding": 32,
|
|
153
|
+
"zoneTitleTint": 0.6
|
|
144
154
|
},
|
|
145
155
|
"motion": {
|
|
146
156
|
"dimOpacity": 0.42,
|
|
@@ -229,7 +239,8 @@ export const tokens = {
|
|
|
229
239
|
"rim": "#EEF2FF",
|
|
230
240
|
"rimWidth": 1,
|
|
231
241
|
"shadow": "#4C5CB8",
|
|
232
|
-
"text": "#EEF2FF"
|
|
242
|
+
"text": "#EEF2FF",
|
|
243
|
+
"zoneOpacity": 0.22
|
|
233
244
|
},
|
|
234
245
|
"light": {
|
|
235
246
|
"background": "#EEF2FF",
|
|
@@ -239,7 +250,8 @@ export const tokens = {
|
|
|
239
250
|
"rim": "#EEF2FF",
|
|
240
251
|
"rimWidth": 0,
|
|
241
252
|
"shadow": "#0A1033",
|
|
242
|
-
"text": "#0A1033"
|
|
253
|
+
"text": "#0A1033",
|
|
254
|
+
"zoneOpacity": 0.12
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
} as const;
|