@crewlethq/icons 0.8.1 → 0.9.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 CHANGED
@@ -11,6 +11,7 @@ All three are React components compiled from SVG files that ship in the package.
11
11
  | Vendor marks | `@crewlethq/icons` | `<VendorMark vendor="slack" />` |
12
12
  | Signature illustrations | `@crewlethq/icons` | `<AgentReading />`, `<Icon name="AgentReading" />` |
13
13
  | The Crewlet character | `@crewlethq/icons` | `<CrewletFigure motion="wave" />` |
14
+ | The thirty agent characters | `@crewlethq/icons/characters` | `<CrewletCharacter character="hexlet" />` |
14
15
 
15
16
  ## Glyphs
16
17
 
@@ -152,6 +153,25 @@ import { CrewletFigure } from '@crewlethq/icons';
152
153
 
153
154
  The figure is sized in `em` like the icons (set `font-size` or pass `width` and `height`) and is `aria-hidden`; give the surrounding element an accessible name when the figure carries meaning. Every motion stops when the user prefers reduced motion. The geometry lives in `src/crewletParts.ts` and the motions in `src/CrewletFigure.css`, which the component imports.
154
155
 
156
+ ### CrewletCharacter
157
+
158
+ `CrewletCharacter` draws one of the thirty Crewlet characters an agent can be drawn as, in the current text colour:
159
+
160
+ ```tsx
161
+ import { CrewletCharacter, CREWLET_CHARACTERS, isCrewletCharacter } from '@crewlethq/icons/characters';
162
+
163
+ <CrewletCharacter character="hexlet" style={{ fontSize: 64, color: 'var(--color-node-cyan)' }} />
164
+ ```
165
+
166
+ | Prop | Type | Default | Purpose |
167
+ | ---- | ---- | ------- | ------- |
168
+ | `character` | `CrewletCharacterId`, one of `CREWLET_CHARACTERS` | required | Which character to draw. `crewlet`, the first, is the mark itself. |
169
+ | `detail` | `'full' \| 'compact'` | `'full'` | `full` draws the keyline floating off the body and the gap round the visor; `compact` leaves both out and opens the eyes, for a character too small for a hairline gap to read. |
170
+
171
+ Every character is cut from the mark's own rules: every corner chamfered at 45 degrees, a keyline at the mark's own proportions, a visor with two upright slits measured from the visor itself, a V notch on a flat crown and block legs under a flat base. A character is framed square and centred on what it draws, so it sits in the middle of any box it is given. Its face and holes are cut out of the body with a mask rather than painted over it, so it is transparent where it is cut and stands on any ground; every instance takes its own mask ids.
172
+
173
+ An id is what a configuration stores, so the set is a contract: `CREWLET_CHARACTERS` lists the ids in the order a picker offers them, `CREWLET_CHARACTER_GEOMETRY` gives each one's name and a few words on its shape, and `isCrewletCharacter` tells an id this build draws from anything else read off the wire. The geometry lives in `src/crewletCharacters.ts`; the original Crewlet is drawn from `svg/crewlet-icon.svg` itself, which `test/characters.test.mjs` holds. `@crewlethq/ui`'s `Avatar` draws a character on a plate in an agent's hue.
174
+
155
175
  ## Stylesheets
156
176
 
157
177
  Two components carry rules, and each imports its own stylesheet as a side effect, so a bundler emits the CSS beside the JavaScript and a consumer gets it without being told to. Nothing here depends on `@crewlethq/tokens`: a rule that wants a token reads it with the literal value as a fallback.
@@ -0,0 +1,104 @@
1
+ import * as react from 'react';
2
+ import { SVGProps } from 'react';
3
+
4
+ /**
5
+ * The Crewlet characters: thirty silhouettes an agent can be drawn as, cut
6
+ * from the mark's own rules so every one of them reads as a Crewlet.
7
+ *
8
+ * FOUR RULES, carried from the mark and never varied by a character:
9
+ *
10
+ * - THE 45 DEGREE CUT. Every outer corner is chamfered along both of its
11
+ * edges, as the mark's crown and shoulders are, so the family has no curve
12
+ * in it. `cut` is that rule as a function.
13
+ * - THE KEYLINE. An outline floats off the body with a transparent gap, as the
14
+ * mark is drawn: a line of KEYLINE_LINE at KEYLINE_GAP, the mark's own
15
+ * proportions. It is drawn at full detail only (see CrewletCharacter).
16
+ * - THE VISOR. One band with two upright slits, each slit's size and place
17
+ * measured from the band itself (EYE), so every face matches the mark's at
18
+ * whatever body it sits on.
19
+ * - THE CROWN AND STANCE. A V notch cut into every flat crown, and block legs
20
+ * under every flat base that stands on them.
21
+ *
22
+ * THIS FILE IS THE GEOMETRY'S ONE SOURCE. Nothing regenerates it, so an edit
23
+ * here is the edit. Every outline is a polygon on a stage 100 units wide whose
24
+ * ground is at GROUND, written clockwise with the chamfer each corner takes;
25
+ * the paths are computed once, when the module loads. The original Crewlet
26
+ * is the exception: it IS the mark, so it is drawn from the mark's own paths
27
+ * (generated from svg/crewlet-icon.svg at build) placed on the same stage.
28
+ *
29
+ * Every outline of one character is drawn as ONE union: the parts overlap and
30
+ * share a winding, so a leg under a body fills as part of it, and the keyline
31
+ * goes round the whole figure rather than round each part.
32
+ */
33
+ /** The characters, in the order a picker offers them. The first is the mark. */
34
+ declare const CREWLET_CHARACTERS: readonly ["crewlet", "hexlet", "peaklet", "prismlet", "towerlet", "bricklet", "gemlet", "coglet", "pluslet", "shieldlet", "stacklet", "sparklet", "cloudlet", "foxlet", "rocketlet", "crownlet", "cactlet", "wisplet", "heartlet", "flasklet", "chiplet", "duolet", "chatlet", "pagelet", "archlet", "dashlet", "octlet", "conelet", "hivelet", "folderlet"];
35
+ /** One character's id, which is what a configuration stores. */
36
+ type CrewletCharacterId = (typeof CREWLET_CHARACTERS)[number];
37
+ /** Whether a value read off the wire names a character this build draws. */
38
+ declare const isCrewletCharacter: (value: unknown) => value is CrewletCharacterId;
39
+ /** A box on the character stage, in its units. */
40
+ interface CharacterBox {
41
+ readonly x: number;
42
+ readonly y: number;
43
+ readonly width: number;
44
+ readonly height: number;
45
+ }
46
+ interface Described {
47
+ /** The character's name, for a label: `Hexlet`. */
48
+ readonly name: string;
49
+ /** What its outline is, in a few words, for a description: `Hexagon with pointed flanks`. */
50
+ readonly shape: string;
51
+ /** Everything it draws, keyline included, which is what a frame centres on. */
52
+ readonly bounds: CharacterBox;
53
+ }
54
+ /** A character drawn from polygons on the stage. */
55
+ interface DrawnCharacter extends Described {
56
+ readonly kind: 'drawn';
57
+ /** Its outlines, one path each, every one clockwise so the overlaps fill as one union. */
58
+ readonly shapes: readonly string[];
59
+ /** What is cut out of its body besides the face: a fold, wheel wells, a mark. */
60
+ readonly holes: readonly string[];
61
+ /** The visor band the eyes are measured from. */
62
+ readonly visor: CharacterBox;
63
+ /** Where the crown's V notch is cut, or null for a crown with no flat top. */
64
+ readonly notch: readonly [number, number] | null;
65
+ }
66
+ /** The original Crewlet, drawn from the mark's own paths. */
67
+ interface MarkCharacter extends Described {
68
+ readonly kind: 'mark';
69
+ /** Places the mark's own units on the stage. */
70
+ readonly transform: string;
71
+ /** The keyline ring, drawn at full detail only. */
72
+ readonly keyline: string;
73
+ /** The body and the visor, with the face already cut through them. */
74
+ readonly body: readonly string[];
75
+ }
76
+ type CrewletCharacterGeometry = DrawnCharacter | MarkCharacter;
77
+ declare const CREWLET_CHARACTER_GEOMETRY: Readonly<Record<CrewletCharacterId, CrewletCharacterGeometry>>;
78
+
79
+ /**
80
+ * How much of a character is drawn.
81
+ *
82
+ * `full` is the mark's own drawing: the keyline floating off the body, and the
83
+ * gap round the visor. `compact` leaves both out and opens the eyes a little,
84
+ * for a character drawn too small for a hairline gap to read (an avatar under
85
+ * 56px): at that size the keyline turns into a blur round the body rather
86
+ * than a line.
87
+ */
88
+ type CrewletCharacterDetail = 'full' | 'compact';
89
+ interface CrewletCharacterProps extends Omit<SVGProps<SVGSVGElement>, 'children' | 'viewBox'> {
90
+ /** Which character to draw. */
91
+ character: CrewletCharacterId;
92
+ /** Defaults to `full`. */
93
+ detail?: CrewletCharacterDetail | undefined;
94
+ }
95
+ /**
96
+ * One Crewlet character, in the current text colour, centred in a square.
97
+ *
98
+ * It is a picture and says nothing on its own: it is hidden from assistive
99
+ * technology, and whatever draws it beside a name (an avatar, a picker's
100
+ * option) carries that name. Colour it with CSS `color` on it or any ancestor.
101
+ */
102
+ declare const CrewletCharacter: ({ character, detail, ...rest }: CrewletCharacterProps) => react.JSX.Element;
103
+
104
+ export { CREWLET_CHARACTERS, CREWLET_CHARACTER_GEOMETRY, type CharacterBox, CrewletCharacter, type CrewletCharacterDetail, type CrewletCharacterGeometry, type CrewletCharacterId, type CrewletCharacterProps, isCrewletCharacter };
@@ -0,0 +1 @@
1
+ export { CREWLET_CHARACTERS, CREWLET_CHARACTER_GEOMETRY, CrewletCharacter_default as CrewletCharacter, isCrewletCharacter } from './chunk-7RRFA6BN.js';
@@ -0,0 +1,761 @@
1
+ import { useId } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/CrewletCharacter.tsx
5
+
6
+ // src/generated/crewletMark.ts
7
+ var CREWLET_MARK = {
8
+ paths: ["M543.42 868.92 l-34.44 -0.24 0 -58.77 0 -58.77 -28.92 0 -28.80 0 -40.20 -39.61 c-119.78 -118.25 -181.49 -179.97 -204.30 -204.30 -15.87 -16.93 -82.75 -96.74 -92.51 -110.38 l-3.76 -5.17 26.80 -18.46 c14.69 -10.11 34.09 -23.51 43.14 -29.86 55.25 -38.32 70.29 -48.43 71.12 -47.49 0.47 0.47 11.64 13.87 24.69 29.74 13.17 15.87 27.98 33.62 32.80 39.50 4.94 5.88 21.63 26.10 37.15 44.90 15.52 18.81 28.56 34.21 29.15 34.21 0.47 0.12 0.82 -30.44 0.82 -67.83 l0 -68.06 37.26 -34.56 c43.38 -40.20 61.83 -57.13 75.11 -69 l9.52 -8.46 261.78 0 261.90 0 47.14 45.73 c25.98 25.16 52.43 51.02 58.66 57.36 l11.52 11.52 0.24 67.94 0.35 67.83 31.74 -40.79 c17.51 -22.33 34.91 -45.02 38.91 -50.19 15.16 -20.34 44.32 -57.72 45.14 -58.07 0.71 -0.24 104.97 75.82 126.83 92.51 2.23 1.65 4.58 3.41 5.17 3.76 1.65 1.18 -7.99 14.22 -46.08 62.07 -35.38 44.32 -49.84 61.13 -92.39 106.97 -19.28 20.81 -38.91 41.73 -148.70 157.40 l-34.68 36.56 -26.45 0.12 c-19.04 0 -26.92 0.35 -27.98 1.41 -1.06 1.06 -1.41 15.05 -1.41 55.72 l0 54.31 -34.91 0.47 c-19.28 0.24 -53.72 0.24 -76.41 0 l-41.49 -0.47 0 -55.72 0 -55.72 -99.33 0 -99.33 0 -0.24 56.19 -0.35 56.07 -39.97 0 c-21.98 0 -55.48 -0.12 -74.29 -0.35z m94.74 -13.87 l7.17 -0.82 0 -54.89 c0 -49.02 0.24 -55.13 1.88 -55.72 1.06 -0.35 51.84 -0.71 112.85 -0.71 l110.97 0 0 55.84 0 55.84 62.89 0 62.89 0 0 -55.84 0 -55.84 31.50 0 31.50 0 14.69 -14.93 c30.80 -31.50 113.20 -118.49 144.47 -152.58 48.66 -53.01 81.34 -90.28 104.85 -119.43 22.80 -28.45 44.43 -56.42 44.43 -57.48 0 -0.59 -2 -2.47 -4.35 -4.23 -2.47 -1.76 -7.05 -5.17 -10.34 -7.64 -3.17 -2.35 -16.93 -12.34 -30.56 -22.22 -13.52 -9.87 -33.50 -24.57 -44.43 -32.68 -16.34 -12.11 -19.98 -14.34 -21.39 -13.17 -0.94 0.82 -5.88 6.82 -10.81 13.40 -4.94 6.47 -13.99 18.46 -20.10 26.57 -6.23 8.11 -26.57 34.44 -45.37 58.54 -18.81 24.10 -36.56 47.02 -39.50 50.78 -5.29 7.05 -5.29 7.05 -11.40 7.05 l-6.23 0 0 -79.58 0 -79.58 -37.85 -37.03 c-20.92 -20.45 -45.14 -44.08 -53.95 -52.66 l-15.99 -15.63 -256.84 0 -256.73 0 -47.49 44.20 c-26.21 24.33 -51.13 47.49 -55.60 51.37 l-7.88 7.05 0 79.11 0 79.23 -6.35 0 -6.35 0 -48.66 -58.54 c-26.80 -32.09 -55.95 -67.24 -64.89 -78.17 -8.93 -10.81 -16.69 -19.63 -17.16 -19.63 -0.94 0 -8.58 5.17 -34.32 22.92 -34.21 23.74 -46.08 31.86 -62.65 43.26 -10.34 7.05 -19.28 13.28 -19.87 13.87 -0.59 0.47 2.35 5.05 6.47 10.11 13.87 16.93 53.13 63.12 66.77 78.40 21.86 24.57 83.81 87.69 145.41 148.23 31.62 31.03 68.65 67.36 82.28 80.76 l24.69 24.45 33.50 0.24 33.38 0.35 0.35 59.13 0.24 59.01 53.37 0 c29.27 0 56.54 -0.35 60.54 -0.71z", "M535.78 783.81 l-0.35 -59.13 -36.67 0 -36.67 0 -6.82 -6.47 c-48.78 -46.43 -176.91 -173.03 -210.41 -207.82 -24.69 -25.74 -75.70 -81.70 -93.69 -102.85 -3.29 -3.88 -5.29 -7.17 -4.70 -7.76 1.29 -1.06 58.30 -40.32 82.05 -56.42 l16.34 -11.05 3.64 4.47 c11.05 13.87 76.99 92.51 98.98 118.14 l25.74 29.97 15.63 0.35 15.52 0.35 0 -82.87 0 -82.75 50.55 -46.67 c27.86 -25.74 50.55 -47.14 50.55 -47.72 0 -0.59 46.78 -1.06 111.55 -1.06 l111.55 0 15.52 14.81 c15.28 14.58 15.52 14.69 17.98 12.58 1.29 -1.18 8.46 -7.76 15.75 -14.58 l13.40 -12.46 17.75 -0.71 c9.76 -0.47 58.77 -0.82 108.85 -0.82 l90.98 0 14.34 13.75 c7.76 7.64 24.57 23.86 37.15 36.20 12.58 12.34 29.15 28.45 36.67 35.85 l13.87 13.40 0.24 82.87 0.35 82.99 14.22 0.35 14.34 0.35 4.47 -5.64 c2.47 -3.06 11.52 -14.11 19.98 -24.45 8.46 -10.34 30.56 -38.32 49.14 -62.30 43.85 -56.42 46.20 -59.36 48.43 -59.36 2.12 0 4.82 1.88 47.96 33.27 43.02 31.27 45.37 33.03 45.37 34.09 0 0.82 -39.03 46.20 -62.42 72.53 -62.42 70.29 -106.50 117.67 -184.08 197.60 l-50.78 52.43 -13.28 0.71 c-7.29 0.47 -23.98 0.82 -37.03 0.82 l-23.86 0 0 57.01 0 57.01 -49.37 0 -49.37 0 0 -56.42 0 -56.42 -114.37 0 c-63.01 0 -120.13 0.35 -126.95 0.82 l-12.58 0.71 0 55.72 0 55.60 -47.61 0 -47.61 0 -0.24 -59.01z m443.16 -373.22 l-0.35 -119.66 -223.93 0.12 -223.93 0.12 -0.35 119.43 -0.24 119.55 224.52 0 224.52 0 -0.24 -119.55z", "M546.36 516.39 c-0.24 -0.59 -0.47 -4.94 -0.35 -9.76 0 -4.82 0.12 -52.54 0.35 -106.15 l0.24 -97.21 209.59 0.24 209.47 0.35 0.35 106.73 0.24 106.62 -209.71 0 c-115.43 0 -209.94 -0.35 -210.18 -0.82z m121.07 -100.50 l-0.35 -66.18 -34.32 -0.35 -34.44 -0.24 0 66.41 0 66.41 34.68 0 34.68 0 -0.24 -66.06z m260.96 0 l-0.35 -66.18 -34.09 0 -34.09 0 0 10.93 c0 5.88 0.12 35.62 0.35 66.06 l0.24 55.25 34.09 0 34.09 0 -0.24 -66.06z"],
9
+ box: { left: 110.48999999999992, top: 196.30999999999992, right: 1389.0800000000002, bottom: 869.2899999999998 }
10
+ };
11
+
12
+ // src/crewletCharacters.ts
13
+ var CREWLET_CHARACTERS = [
14
+ "crewlet",
15
+ "hexlet",
16
+ "peaklet",
17
+ "prismlet",
18
+ "towerlet",
19
+ "bricklet",
20
+ "gemlet",
21
+ "coglet",
22
+ "pluslet",
23
+ "shieldlet",
24
+ "stacklet",
25
+ "sparklet",
26
+ "cloudlet",
27
+ "foxlet",
28
+ "rocketlet",
29
+ "crownlet",
30
+ "cactlet",
31
+ "wisplet",
32
+ "heartlet",
33
+ "flasklet",
34
+ "chiplet",
35
+ "duolet",
36
+ "chatlet",
37
+ "pagelet",
38
+ "archlet",
39
+ "dashlet",
40
+ "octlet",
41
+ "conelet",
42
+ "hivelet",
43
+ "folderlet"
44
+ ];
45
+ var isCrewletCharacter = (value) => typeof value === "string" && CREWLET_CHARACTERS.includes(value);
46
+ var GROUND = 96;
47
+ var KEYLINE_LINE = 1.35;
48
+ var KEYLINE_GAP = 0.9;
49
+ var VISOR_GAP = 1.3;
50
+ var EYE = {
51
+ full: { width: 0.164, height: 0.62 },
52
+ compact: { width: 0.19, height: 0.68 },
53
+ offset: 0.312
54
+ };
55
+ var NOTCH = { half: 3.2, depth: 2.6 };
56
+ var round = (n) => Math.round(n * 100) / 100;
57
+ function cut(points, chamfer) {
58
+ const out = [];
59
+ points.forEach((vertex, i) => {
60
+ const size = typeof chamfer === "number" ? chamfer : chamfer[i] ?? 0;
61
+ if (!size) {
62
+ out.push(vertex);
63
+ return;
64
+ }
65
+ const along = (to) => {
66
+ const dx = to[0] - vertex[0];
67
+ const dy = to[1] - vertex[1];
68
+ const t = Math.min(size / Math.hypot(dx, dy), 0.45);
69
+ return [vertex[0] + dx * t, vertex[1] + dy * t];
70
+ };
71
+ out.push(along(points[(i - 1 + points.length) % points.length]), along(points[(i + 1) % points.length]));
72
+ });
73
+ return out;
74
+ }
75
+ var pathOf = (points) => `M${points.map(([x, y]) => `${round(x)} ${round(y)}`).join("L")}Z`;
76
+ var area = (points) => points.reduce((sum, p, i) => {
77
+ const q = points[(i + 1) % points.length];
78
+ return sum + p[0] * q[1] - q[0] * p[1];
79
+ }, 0);
80
+ function poly(points, chamfer = 0) {
81
+ const chamfers = typeof chamfer === "number" ? points.map(() => chamfer) : chamfer;
82
+ return area(points) < 0 ? pathOf(cut([...points].reverse(), [...chamfers].reverse())) : pathOf(cut(points, chamfers));
83
+ }
84
+ var rect = (x, y, width, height, chamfer = 0) => poly(
85
+ [
86
+ [x, y],
87
+ [x + width, y],
88
+ [x + width, y + height],
89
+ [x, y + height]
90
+ ],
91
+ chamfer
92
+ );
93
+ var legs = (left, right, top, width = 10) => [
94
+ rect(left, top, width, GROUND - top),
95
+ rect(right, top, width, GROUND - top)
96
+ ];
97
+ function gear(cx, cy, outer, inner, teeth) {
98
+ const points = [];
99
+ for (let k = 0; k < teeth; k += 1) {
100
+ const angle = (k * 360 / teeth - 90) * (Math.PI / 180);
101
+ for (const [radius, degrees] of [
102
+ [inner, -15],
103
+ [outer, -8.5],
104
+ [outer, 8.5],
105
+ [inner, 15]
106
+ ]) {
107
+ const a = angle + degrees * (Math.PI / 180);
108
+ points.push([cx + radius * Math.cos(a), cy + radius * Math.sin(a)]);
109
+ }
110
+ }
111
+ return poly(points);
112
+ }
113
+ function boxOf(paths, grow) {
114
+ const numbers = paths.join(" ").match(/-?\d*\.?\d+/g).map(Number);
115
+ let left = Infinity;
116
+ let top = Infinity;
117
+ let right = -Infinity;
118
+ let bottom = -Infinity;
119
+ for (let i = 0; i < numbers.length; i += 2) {
120
+ left = Math.min(left, numbers[i]);
121
+ right = Math.max(right, numbers[i]);
122
+ top = Math.min(top, numbers[i + 1]);
123
+ bottom = Math.max(bottom, numbers[i + 1]);
124
+ }
125
+ return { x: left - grow, y: top - grow, width: right - left + 2 * grow, height: bottom - top + 2 * grow };
126
+ }
127
+ var box = ([x, y, width, height]) => ({ x, y, width, height });
128
+ function drawn(spec) {
129
+ return {
130
+ kind: "drawn",
131
+ name: spec.name,
132
+ shape: spec.shape,
133
+ shapes: spec.shapes,
134
+ holes: spec.holes ?? [],
135
+ visor: box(spec.visor),
136
+ notch: spec.notch === void 0 ? null : [spec.notchX ?? 50, spec.notch],
137
+ // The keyline reaches its gap plus its line past the outline.
138
+ bounds: boxOf(spec.shapes, KEYLINE_GAP + KEYLINE_LINE)
139
+ };
140
+ }
141
+ var MARK_SCALE = 0.095;
142
+ var MARK_CENTRE = 760;
143
+ var MARK_FEET = 869;
144
+ function mark() {
145
+ const { paths, box: own } = CREWLET_MARK;
146
+ const onStage = (x, y) => [
147
+ 50 + (x - MARK_CENTRE) * MARK_SCALE,
148
+ GROUND + (y - MARK_FEET) * MARK_SCALE
149
+ ];
150
+ const [left, top] = onStage(own.left, own.top);
151
+ const [right, bottom] = onStage(own.right, own.bottom);
152
+ return {
153
+ kind: "mark",
154
+ name: "Crewlet",
155
+ shape: "The original mark, arms raised",
156
+ transform: `translate(50 ${GROUND}) scale(${MARK_SCALE}) translate(${-MARK_CENTRE} ${-MARK_FEET})`,
157
+ keyline: paths[0],
158
+ body: [paths[1], paths[2]],
159
+ bounds: { x: left, y: top, width: right - left, height: bottom - top }
160
+ };
161
+ }
162
+ var CREWLET_CHARACTER_GEOMETRY = {
163
+ crewlet: mark(),
164
+ hexlet: drawn({
165
+ name: "Hexlet",
166
+ shape: "Hexagon with pointed flanks",
167
+ shapes: [
168
+ poly(
169
+ [
170
+ [30, 22],
171
+ [70, 22],
172
+ [86, 54],
173
+ [70, 86],
174
+ [30, 86],
175
+ [14, 54]
176
+ ],
177
+ [5, 5, 4, 4, 4, 4]
178
+ ),
179
+ ...legs(34, 56, 84)
180
+ ],
181
+ visor: [28, 38, 44, 20],
182
+ notch: 22
183
+ }),
184
+ peaklet: drawn({
185
+ name: "Peaklet",
186
+ shape: "Pentagon with a peaked roof",
187
+ shapes: [
188
+ poly(
189
+ [
190
+ [50, 12],
191
+ [82, 36],
192
+ [82, 86],
193
+ [18, 86],
194
+ [18, 36]
195
+ ],
196
+ [6, 5, 4, 4, 5]
197
+ ),
198
+ ...legs(28, 62, 84)
199
+ ],
200
+ visor: [26, 44, 48, 20]
201
+ }),
202
+ prismlet: drawn({
203
+ name: "Prismlet",
204
+ shape: "Triangle with cut tips",
205
+ shapes: [
206
+ poly(
207
+ [
208
+ [50, 10],
209
+ [90, 86],
210
+ [10, 86]
211
+ ],
212
+ [9, 8, 8]
213
+ ),
214
+ ...legs(28, 62, 84)
215
+ ],
216
+ visor: [31, 54, 38, 18]
217
+ }),
218
+ towerlet: drawn({
219
+ name: "Towerlet",
220
+ shape: "Tall pillar, narrow stance",
221
+ shapes: [rect(32, 12, 36, 74, [8, 8, 4, 4]), ...legs(35, 55, 84)],
222
+ visor: [36, 24, 28, 16],
223
+ notch: 12
224
+ }),
225
+ bricklet: drawn({
226
+ name: "Bricklet",
227
+ shape: "Wide, low slab",
228
+ shapes: [rect(10, 42, 80, 44, [9, 9, 6, 6]), ...legs(22, 68, 84)],
229
+ visor: [21, 50, 58, 19],
230
+ notch: 42
231
+ }),
232
+ gemlet: drawn({
233
+ name: "Gemlet",
234
+ shape: "Diamond on its point",
235
+ shapes: [
236
+ poly(
237
+ [
238
+ [50, 6],
239
+ [90, 48],
240
+ [50, 90],
241
+ [10, 48]
242
+ ],
243
+ [7, 7, 16, 7]
244
+ ),
245
+ ...legs(40, 52, 76, 8)
246
+ ],
247
+ visor: [27, 38, 46, 20]
248
+ }),
249
+ coglet: drawn({
250
+ name: "Coglet",
251
+ shape: "Eight-tooth gear",
252
+ shapes: [gear(50, 52, 37, 31, 8), ...legs(36, 54, 78)],
253
+ visor: [30, 40, 40, 18]
254
+ }),
255
+ pluslet: drawn({
256
+ name: "Pluslet",
257
+ shape: "Cross with squared arms",
258
+ shapes: [rect(34, 14, 32, 72, [7, 7, 0, 0]), rect(10, 34, 80, 30, 7), ...legs(37, 53, 84)],
259
+ visor: [20, 40, 60, 18],
260
+ notch: 14
261
+ }),
262
+ shieldlet: drawn({
263
+ name: "Shieldlet",
264
+ shape: "Flat crown, pointed base",
265
+ shapes: [
266
+ poly(
267
+ [
268
+ [16, 16],
269
+ [84, 16],
270
+ [84, 54],
271
+ [50, 92],
272
+ [16, 54]
273
+ ],
274
+ [9, 9, 5, 14, 5]
275
+ ),
276
+ ...legs(41, 51, 79, 8)
277
+ ],
278
+ visor: [26, 28, 48, 20],
279
+ notch: 16
280
+ }),
281
+ stacklet: drawn({
282
+ name: "Stacklet",
283
+ shape: "Stepped crown, square base",
284
+ shapes: [
285
+ poly(
286
+ [
287
+ [34, 12],
288
+ [66, 12],
289
+ [66, 26],
290
+ [82, 26],
291
+ [82, 86],
292
+ [18, 86],
293
+ [18, 26],
294
+ [34, 26]
295
+ ],
296
+ [4, 4, 0, 5, 4, 4, 5, 0]
297
+ ),
298
+ ...legs(28, 62, 84)
299
+ ],
300
+ visor: [26, 40, 48, 20],
301
+ notch: 12
302
+ }),
303
+ sparklet: drawn({
304
+ name: "Sparklet",
305
+ shape: "Faceted drop",
306
+ shapes: [
307
+ poly(
308
+ [
309
+ [50, 8],
310
+ [80, 48],
311
+ [78, 74],
312
+ [64, 86],
313
+ [36, 86],
314
+ [22, 74],
315
+ [20, 48]
316
+ ],
317
+ [6, 4, 2, 2, 2, 2, 4]
318
+ ),
319
+ ...legs(36, 54, 84)
320
+ ],
321
+ visor: [30, 48, 40, 18]
322
+ }),
323
+ cloudlet: drawn({
324
+ name: "Cloudlet",
325
+ shape: "Three-lobed cloud",
326
+ shapes: [
327
+ rect(14, 44, 72, 42, [0, 0, 8, 8]),
328
+ rect(16, 30, 32, 32, 9),
329
+ rect(33, 18, 34, 34, 10),
330
+ rect(56, 32, 30, 30, 9),
331
+ ...legs(30, 60, 84)
332
+ ],
333
+ visor: [24, 48, 52, 18],
334
+ notch: 18
335
+ }),
336
+ foxlet: drawn({
337
+ name: "Foxlet",
338
+ shape: "Square body, pointed ears",
339
+ shapes: [
340
+ rect(18, 34, 64, 52, 6),
341
+ poly(
342
+ [
343
+ [20, 36],
344
+ [26, 12],
345
+ [44, 36]
346
+ ],
347
+ [0, 4, 0]
348
+ ),
349
+ poly(
350
+ [
351
+ [56, 36],
352
+ [74, 12],
353
+ [80, 36]
354
+ ],
355
+ [0, 4, 0]
356
+ ),
357
+ ...legs(28, 62, 84)
358
+ ],
359
+ visor: [26, 46, 48, 20]
360
+ }),
361
+ rocketlet: drawn({
362
+ name: "Rocketlet",
363
+ shape: "Nose cone and swept fins",
364
+ shapes: [
365
+ poly(
366
+ [
367
+ [50, 6],
368
+ [66, 26],
369
+ [66, 80],
370
+ [34, 80],
371
+ [34, 26]
372
+ ],
373
+ [5, 3, 3, 3, 3]
374
+ ),
375
+ poly(
376
+ [
377
+ [34, 54],
378
+ [34, 80],
379
+ [18, 90],
380
+ [18, 70]
381
+ ],
382
+ [0, 0, 3, 3]
383
+ ),
384
+ poly(
385
+ [
386
+ [66, 54],
387
+ [82, 70],
388
+ [82, 90],
389
+ [66, 80]
390
+ ],
391
+ [0, 3, 3, 0]
392
+ ),
393
+ ...legs(37, 55, 80, 8)
394
+ ],
395
+ visor: [37, 32, 26, 16]
396
+ }),
397
+ crownlet: drawn({
398
+ name: "Crownlet",
399
+ shape: "Three-point crown",
400
+ shapes: [
401
+ rect(18, 40, 64, 46, [0, 0, 6, 6]),
402
+ poly(
403
+ [
404
+ [18, 44],
405
+ [18, 18],
406
+ [34, 32],
407
+ [50, 12],
408
+ [66, 32],
409
+ [82, 18],
410
+ [82, 44]
411
+ ],
412
+ [0, 3, 0, 3, 0, 3, 0]
413
+ ),
414
+ ...legs(28, 62, 84)
415
+ ],
416
+ visor: [27, 48, 46, 20]
417
+ }),
418
+ cactlet: drawn({
419
+ name: "Cactlet",
420
+ shape: "Trunk with two raised arms",
421
+ shapes: [
422
+ rect(32, 12, 36, 74, [10, 10, 0, 0]),
423
+ rect(16, 50, 18, 9, [0, 0, 0, 3]),
424
+ rect(16, 28, 9, 31, [4.5, 4.5, 0, 3]),
425
+ rect(66, 58, 18, 9, [0, 0, 3, 0]),
426
+ rect(75, 38, 9, 29, [4.5, 4.5, 3, 0]),
427
+ ...legs(36, 54, 84)
428
+ ],
429
+ visor: [37, 26, 26, 16],
430
+ notch: 12
431
+ }),
432
+ wisplet: drawn({
433
+ name: "Wisplet",
434
+ shape: "Tall ghost with a zigzag hem",
435
+ shapes: [
436
+ poly(
437
+ [
438
+ [16, 10],
439
+ [84, 10],
440
+ [84, 92],
441
+ [72.5, 82],
442
+ [61, 92],
443
+ [50, 82],
444
+ [39, 92],
445
+ [27.5, 82],
446
+ [16, 92]
447
+ ],
448
+ [14, 14, 2, 0, 2, 0, 2, 0, 2]
449
+ )
450
+ ],
451
+ visor: [26, 28, 48, 20],
452
+ notch: 10
453
+ }),
454
+ heartlet: drawn({
455
+ name: "Heartlet",
456
+ shape: "Faceted heart",
457
+ shapes: [
458
+ poly(
459
+ [
460
+ [50, 26],
461
+ [62, 14],
462
+ [78, 14],
463
+ [90, 26],
464
+ [90, 44],
465
+ [50, 84],
466
+ [10, 44],
467
+ [10, 26],
468
+ [22, 14],
469
+ [38, 14]
470
+ ],
471
+ [0, 3, 3, 3, 4, 6, 4, 3, 3, 3]
472
+ ),
473
+ ...legs(41, 51, 74, 8)
474
+ ],
475
+ visor: [23, 30, 54, 18]
476
+ }),
477
+ flasklet: drawn({
478
+ name: "Flasklet",
479
+ shape: "Lab flask with a narrow neck",
480
+ shapes: [
481
+ rect(35, 6, 30, 7, 2),
482
+ rect(40, 10, 20, 22),
483
+ poly(
484
+ [
485
+ [40, 28],
486
+ [60, 28],
487
+ [86, 80],
488
+ [86, 86],
489
+ [14, 86],
490
+ [14, 80]
491
+ ],
492
+ [0, 0, 6, 3, 3, 6]
493
+ ),
494
+ ...legs(26, 64, 84)
495
+ ],
496
+ visor: [33, 54, 34, 14],
497
+ notch: 6
498
+ }),
499
+ chiplet: drawn({
500
+ name: "Chiplet",
501
+ shape: "Square chip with pins",
502
+ shapes: [
503
+ rect(24, 20, 52, 60, 6),
504
+ ...[31, 47, 63].map((x) => rect(x, 11, 6, 10)),
505
+ ...[32, 47, 62].map((y) => rect(15, y, 10, 6)),
506
+ ...[32, 47, 62].map((y) => rect(75, y, 10, 6)),
507
+ ...[31, 47, 63].map((x) => rect(x, 78, 6, 18))
508
+ ],
509
+ holes: [rect(29, 70, 5, 5)],
510
+ visor: [31, 34, 38, 18]
511
+ }),
512
+ duolet: drawn({
513
+ name: "Duolet",
514
+ shape: "Small head on a wide body",
515
+ shapes: [rect(30, 10, 40, 34, 8), rect(18, 40, 64, 46, 6), ...legs(28, 62, 84)],
516
+ visor: [36, 19, 28, 15],
517
+ notch: 10
518
+ }),
519
+ chatlet: drawn({
520
+ name: "Chatlet",
521
+ shape: "Speech bubble with a tail",
522
+ shapes: [
523
+ rect(14, 22, 72, 54, 8),
524
+ poly(
525
+ [
526
+ [26, 72],
527
+ [46, 72],
528
+ [22, 94]
529
+ ],
530
+ [0, 0, 2]
531
+ )
532
+ ],
533
+ visor: [24, 34, 52, 22],
534
+ notch: 22
535
+ }),
536
+ pagelet: drawn({
537
+ name: "Pagelet",
538
+ shape: "Page with a folded corner",
539
+ shapes: [
540
+ poly(
541
+ [
542
+ [20, 10],
543
+ [64, 10],
544
+ [80, 26],
545
+ [80, 86],
546
+ [20, 86]
547
+ ],
548
+ [4, 0, 0, 4, 4]
549
+ ),
550
+ ...legs(28, 62, 84)
551
+ ],
552
+ // The fold: two hairline cuts that set the folded corner off the page.
553
+ holes: [rect(63.3, 10, 1.4, 17.4), rect(63.3, 25.3, 17, 1.4)],
554
+ visor: [27, 36, 46, 20],
555
+ notch: 10
556
+ }),
557
+ archlet: drawn({
558
+ name: "Archlet",
559
+ shape: "Gate with built-in legs",
560
+ shapes: [
561
+ poly(
562
+ [
563
+ [16, 18],
564
+ [84, 18],
565
+ [84, 96],
566
+ [64, 96],
567
+ [64, 74],
568
+ [36, 74],
569
+ [36, 96],
570
+ [16, 96]
571
+ ],
572
+ [8, 8, 0, 0, 4, 4, 0, 0]
573
+ )
574
+ ],
575
+ visor: [25, 30, 50, 22],
576
+ notch: 18
577
+ }),
578
+ dashlet: drawn({
579
+ name: "Dashlet",
580
+ shape: "Body leaning forward",
581
+ shapes: [
582
+ poly(
583
+ [
584
+ [29, 14],
585
+ [87, 14],
586
+ [71, 86],
587
+ [13, 86]
588
+ ],
589
+ 6
590
+ ),
591
+ ...legs(20, 48, 84)
592
+ ],
593
+ visor: [31.5, 28, 42, 20],
594
+ // A leaning crown's middle is not the stage's.
595
+ notch: 14,
596
+ notchX: 58
597
+ }),
598
+ octlet: drawn({
599
+ name: "Octlet",
600
+ shape: "Regular octagon",
601
+ shapes: [rect(16, 16, 68, 68, 20), ...legs(35, 55, 81)],
602
+ visor: [26, 36, 48, 22],
603
+ notch: 16
604
+ }),
605
+ conelet: drawn({
606
+ name: "Conelet",
607
+ shape: "Narrow crown, wide base",
608
+ shapes: [
609
+ poly(
610
+ [
611
+ [30, 12],
612
+ [70, 12],
613
+ [88, 84],
614
+ [12, 84]
615
+ ],
616
+ [6, 6, 5, 5]
617
+ ),
618
+ ...legs(26, 64, 82)
619
+ ],
620
+ visor: [28, 40, 44, 20],
621
+ notch: 12
622
+ }),
623
+ hivelet: drawn({
624
+ name: "Hivelet",
625
+ shape: "Upright honeycomb cell",
626
+ shapes: [
627
+ poly(
628
+ [
629
+ [50, 6],
630
+ [84, 24],
631
+ [84, 72],
632
+ [50, 90],
633
+ [16, 72],
634
+ [16, 24]
635
+ ],
636
+ [6, 4, 4, 6, 4, 4]
637
+ ),
638
+ ...legs(40, 52, 80, 8)
639
+ ],
640
+ visor: [25, 36, 50, 20]
641
+ }),
642
+ folderlet: drawn({
643
+ name: "Folderlet",
644
+ shape: "Folder with a tab",
645
+ shapes: [
646
+ poly(
647
+ [
648
+ [12, 14],
649
+ [38, 14],
650
+ [50, 26],
651
+ [88, 26],
652
+ [88, 84],
653
+ [12, 84]
654
+ ],
655
+ [4, 0, 0, 6, 5, 5]
656
+ ),
657
+ ...legs(26, 64, 82)
658
+ ],
659
+ visor: [24, 40, 52, 22]
660
+ })
661
+ };
662
+ var round2 = (n) => Math.round(n * 100) / 100;
663
+ function viewBoxOf({ x, y, width, height }) {
664
+ const side = Math.max(width, height);
665
+ return [x + width / 2 - side / 2, y + height / 2 - side / 2, side, side].map(round2).join(" ");
666
+ }
667
+ function Drawn({ geometry, detail, id }) {
668
+ const { shapes, holes, visor, notch } = geometry;
669
+ const full = detail === "full";
670
+ const d = shapes.join("");
671
+ const eye = full ? EYE.full : EYE.compact;
672
+ const eyeWidth = visor.width * eye.width;
673
+ const eyeHeight = visor.height * eye.height;
674
+ const eyeTop = visor.y + (visor.height - eyeHeight) / 2;
675
+ const middle = visor.x + visor.width / 2;
676
+ const offset = visor.width * EYE.offset;
677
+ const reach = { x: -40, y: -40, width: 180, height: 180 };
678
+ const face = `${id}-face`;
679
+ const ring = `${id}-ring`;
680
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
681
+ /* @__PURE__ */ jsxs("defs", { children: [
682
+ /* @__PURE__ */ jsxs("mask", { id: face, maskUnits: "userSpaceOnUse", ...reach, children: [
683
+ /* @__PURE__ */ jsx("rect", { ...reach, fill: "#fff" }),
684
+ /* @__PURE__ */ jsxs("g", { fill: "#000", children: [
685
+ [middle - offset, middle + offset].map((centre) => /* @__PURE__ */ jsx(
686
+ "rect",
687
+ {
688
+ x: round2(centre - eyeWidth / 2),
689
+ y: round2(eyeTop),
690
+ width: round2(eyeWidth),
691
+ height: round2(eyeHeight)
692
+ },
693
+ centre
694
+ )),
695
+ notch && /* @__PURE__ */ jsx(
696
+ "path",
697
+ {
698
+ d: `M${round2(notch[0] - NOTCH.half)} ${round2(notch[1] - 1)}L${round2(notch[0] + NOTCH.half)} ${round2(
699
+ notch[1] - 1
700
+ )}L${round2(notch[0])} ${round2(notch[1] + NOTCH.depth)}Z`
701
+ }
702
+ ),
703
+ holes.map((hole) => /* @__PURE__ */ jsx("path", { d: hole }, hole))
704
+ ] }),
705
+ full && /* @__PURE__ */ jsx(
706
+ "rect",
707
+ {
708
+ x: visor.x,
709
+ y: visor.y,
710
+ width: visor.width,
711
+ height: visor.height,
712
+ fill: "none",
713
+ stroke: "#000",
714
+ strokeWidth: VISOR_GAP
715
+ }
716
+ )
717
+ ] }),
718
+ full && /* @__PURE__ */ jsxs("mask", { id: ring, maskUnits: "userSpaceOnUse", ...reach, children: [
719
+ /* @__PURE__ */ jsx("rect", { ...reach, fill: "#fff" }),
720
+ /* @__PURE__ */ jsx("path", { d, fill: "#000", stroke: "#000", strokeWidth: KEYLINE_GAP * 2, strokeLinejoin: "miter" })
721
+ ] })
722
+ ] }),
723
+ full && /* @__PURE__ */ jsx(
724
+ "path",
725
+ {
726
+ d,
727
+ mask: `url(#${ring})`,
728
+ fill: "none",
729
+ stroke: "currentColor",
730
+ strokeWidth: (KEYLINE_GAP + KEYLINE_LINE) * 2,
731
+ strokeLinejoin: "miter"
732
+ }
733
+ ),
734
+ /* @__PURE__ */ jsx("path", { d, mask: `url(#${face})`, fill: "currentColor" })
735
+ ] });
736
+ }
737
+ var CrewletCharacter = ({ character, detail = "full", ...rest }) => {
738
+ const id = `crewlet-character-${useId().replace(/[^A-Za-z0-9_-]/g, "")}`;
739
+ const geometry = CREWLET_CHARACTER_GEOMETRY[character];
740
+ return /* @__PURE__ */ jsx(
741
+ "svg",
742
+ {
743
+ xmlns: "http://www.w3.org/2000/svg",
744
+ viewBox: viewBoxOf(geometry.bounds),
745
+ width: "1em",
746
+ height: "1em",
747
+ "aria-hidden": "true",
748
+ focusable: "false",
749
+ "data-character": character,
750
+ "data-detail": detail,
751
+ ...rest,
752
+ children: geometry.kind === "mark" ? /* @__PURE__ */ jsxs("g", { transform: geometry.transform, fill: "currentColor", children: [
753
+ detail === "full" && /* @__PURE__ */ jsx("path", { d: geometry.keyline }),
754
+ geometry.body.map((d) => /* @__PURE__ */ jsx("path", { d }, d))
755
+ ] }) : /* @__PURE__ */ jsx(Drawn, { geometry, detail, id })
756
+ }
757
+ );
758
+ };
759
+ var CrewletCharacter_default = CrewletCharacter;
760
+
761
+ export { CREWLET_CHARACTERS, CREWLET_CHARACTER_GEOMETRY, CrewletCharacter_default, isCrewletCharacter };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { SVGProps } from 'react';
3
+ export { CREWLET_CHARACTERS, CREWLET_CHARACTER_GEOMETRY, CharacterBox, CrewletCharacter, CrewletCharacterDetail, CrewletCharacterGeometry, CrewletCharacterId, CrewletCharacterProps, isCrewletCharacter } from './characters.js';
3
4
  import { G as GlyphSize } from './Glyph-0ZR8lPCf.js';
4
5
 
5
6
  declare const AgentError: (props: SVGProps<SVGSVGElement>) => react.JSX.Element;
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { CREWLET_CHARACTERS, CREWLET_CHARACTER_GEOMETRY, CrewletCharacter_default as CrewletCharacter, isCrewletCharacter } from './chunk-7RRFA6BN.js';
1
2
  import { cssLength } from './chunk-TOSJN7HA.js';
2
3
  import { jsxs, jsx } from 'react/jsx-runtime';
3
4
  import './CrewletFigure-4PMMCAFG.css';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewlethq/icons",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Crewlet signature illustrated icons, vendor marks, and the Lucide glyph set as React components.",
5
5
  "license": "MIT AND ISC",
6
6
  "repository": {
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/glyphs-registry.d.ts",
33
33
  "import": "./dist/glyphs-registry.js"
34
34
  },
35
+ "./characters": {
36
+ "types": "./dist/characters.d.ts",
37
+ "import": "./dist/characters.js"
38
+ },
35
39
  "./favicon/*": "./favicon/*",
36
40
  "./glyphs/*": "./glyphs/*",
37
41
  "./svg/*": "./svg/*"