@originkit/avatars 0.1.0 → 0.2.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 +22 -0
- package/dist/controls.d.ts +1 -4
- package/dist/controls.js +55 -5
- package/dist/kinds.js +16 -16
- package/dist/share.js +2 -2
- package/dist/variants.d.ts +2 -0
- package/dist/variants.js +2 -0
- package/package.json +17 -3
- package/react/index.d.ts +28 -0
- package/react/index.js +869 -0
- package/react/styles.js +1 -0
package/README.md
CHANGED
|
@@ -30,6 +30,28 @@ In a web page, use an `<img>` with a data URI rather than inline SVG, because in
|
|
|
30
30
|
<img src={`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`} alt="" width={48} height={48} />
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Live avatars in React
|
|
34
|
+
|
|
35
|
+
```jsx
|
|
36
|
+
import { Avatar } from "@originkit/avatars/react";
|
|
37
|
+
|
|
38
|
+
<Avatar style="glances" name="user-8f3a2c" size={48} />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The same avatars as the studio: they blink, follow the pointer and react to taps. On the server, and until the page runs, `Avatar` shows the still SVG, so nothing jumps. Bots, Blobs, Gummies and Mallows draw in 3D on a WebGL canvas of their own, and show the still where WebGL can't run. Its CSS comes with it, with nothing to import.
|
|
42
|
+
|
|
43
|
+
| Prop | |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `style` | A style id, as `styles` lists them |
|
|
46
|
+
| `name` | What it's generated from |
|
|
47
|
+
| `overrides` | The studio's settings, as its How to use guide prints them |
|
|
48
|
+
| `size` | Width and height, in pixels or any CSS length (96) |
|
|
49
|
+
| `follow` | Whether the eyes follow the pointer (on) |
|
|
50
|
+
| `animate` | Whether it blinks, moves and reacts to taps (on, and off for reduced motion) |
|
|
51
|
+
| `label` | Read out for the image (empty: an avatar beside a name adds nothing) |
|
|
52
|
+
|
|
53
|
+
React 18 or later. It's an optional peer: the library, the CLI and the MCP server work without it.
|
|
54
|
+
|
|
33
55
|
## CLI
|
|
34
56
|
|
|
35
57
|
Run it with npx, or install it and call `avatars`.
|
package/dist/controls.d.ts
CHANGED
|
@@ -86,10 +86,7 @@ export type ControlBuilder = (name: string, overrides: Overrides) => Section[];
|
|
|
86
86
|
*/
|
|
87
87
|
export declare function splitEssentials(sections: Section[], ids: string[]): {
|
|
88
88
|
essentials: Control[];
|
|
89
|
-
advanced:
|
|
90
|
-
title: string;
|
|
91
|
-
controls: Control[];
|
|
92
|
-
}[];
|
|
89
|
+
advanced: Section[];
|
|
93
90
|
};
|
|
94
91
|
export interface Entry {
|
|
95
92
|
label: string;
|
package/dist/controls.js
CHANGED
|
@@ -14,13 +14,63 @@ import { colorKey } from "./traits.js";
|
|
|
14
14
|
export function splitEssentials(sections, ids) {
|
|
15
15
|
const all = sections.flatMap((section) => section.controls);
|
|
16
16
|
const essentials = ids.flatMap((id) => all.filter((control) => control.id === id));
|
|
17
|
-
const
|
|
18
|
-
.map((section) => ({
|
|
17
|
+
const rest = sections.map((section) => ({
|
|
19
18
|
...section,
|
|
20
19
|
controls: section.controls.filter((control) => !essentials.includes(control)),
|
|
21
|
-
}))
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
}));
|
|
21
|
+
return { essentials, advanced: regroup(rest) };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* More options, in the same groups and order for every type. Possibilities leads, with no
|
|
25
|
+
* heading of its own, as it governs all the rest.
|
|
26
|
+
*/
|
|
27
|
+
const GROUPS = ["", "Shape", "Face", "Details", "Colour", "Container"];
|
|
28
|
+
/** Where a type's own sections land when nothing about a control says otherwise. */
|
|
29
|
+
const FROM_SECTION = {
|
|
30
|
+
Colour: "Colour",
|
|
31
|
+
Paint: "Colour",
|
|
32
|
+
Eyes: "Face",
|
|
33
|
+
Face: "Face",
|
|
34
|
+
Words: "Details",
|
|
35
|
+
Parts: "Details",
|
|
36
|
+
Accessories: "Details",
|
|
37
|
+
Hair: "Details",
|
|
38
|
+
Lines: "Details",
|
|
39
|
+
Reactions: "Details",
|
|
40
|
+
Container: "Container",
|
|
41
|
+
};
|
|
42
|
+
/** A control's group, read from what it sets. The type's own sections are only a fallback. */
|
|
43
|
+
function groupOf(control, section) {
|
|
44
|
+
const id = control.id;
|
|
45
|
+
if (id === "variance")
|
|
46
|
+
return "";
|
|
47
|
+
if (/container/.test(id))
|
|
48
|
+
return "Container";
|
|
49
|
+
// A type's Colour section holds only colours, so its word is taken as is.
|
|
50
|
+
if (FROM_SECTION[section] === "Colour")
|
|
51
|
+
return "Colour";
|
|
52
|
+
if (/colou?r|paint|tile|fill|skin|palette|^hue|^tone|ink|fur|^glow|frost|^pool|grain|blend|^mask|speckle/.test(id))
|
|
53
|
+
return "Colour";
|
|
54
|
+
if (/hover|effect|hat|glasses|earring|beard|^hair|mark|spots|^top$|^side|straps|studs|topper|holes|wobble|weight|gesture|title|lumps|clothing/.test(id))
|
|
55
|
+
return "Details";
|
|
56
|
+
if (/eye|brow|mouth|grin|teeth|^face|mood|feeling|expression|emotion|cheek|nose|blush|iris|formation|gaze|patch/.test(id))
|
|
57
|
+
return "Face";
|
|
58
|
+
if (/^pose|mirrored|roundness|angle|^body|^head|width|waist|softness|turn|depth|fluff|stretch/.test(id))
|
|
59
|
+
return "Shape";
|
|
60
|
+
return FROM_SECTION[section] ?? (GROUPS.includes(section) ? section : "Shape");
|
|
61
|
+
}
|
|
62
|
+
function regroup(sections) {
|
|
63
|
+
const groups = new Map();
|
|
64
|
+
for (const section of sections) {
|
|
65
|
+
for (const control of section.controls) {
|
|
66
|
+
const group = groupOf(control, section.title);
|
|
67
|
+
groups.set(group, [...(groups.get(group) ?? []), control]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const rank = (title) => (GROUPS.includes(title) ? GROUPS.indexOf(title) : GROUPS.length);
|
|
71
|
+
return [...groups]
|
|
72
|
+
.map(([title, controls]) => ({ title, controls }))
|
|
73
|
+
.sort((a, b) => rank(a.title) - rank(b.title));
|
|
24
74
|
}
|
|
25
75
|
/** Just below 1, because a position of exactly 1 is past the end of every list. */
|
|
26
76
|
const TOP = 0.999;
|
package/dist/kinds.js
CHANGED
|
@@ -44,7 +44,7 @@ export const KINDS = [
|
|
|
44
44
|
defaultName: "Nova",
|
|
45
45
|
controls: botControls,
|
|
46
46
|
// The container's size and colour join it only while it's on, which may make 8.
|
|
47
|
-
essentials: ["head", "eye", "paint"],
|
|
47
|
+
essentials: ["head", "face", "eye", "mouth", "paint"],
|
|
48
48
|
styleKeys: ["head"],
|
|
49
49
|
displayKeys: ["container.size"],
|
|
50
50
|
svg: generateBot,
|
|
@@ -72,7 +72,7 @@ export const KINDS = [
|
|
|
72
72
|
defaultName: "Juju",
|
|
73
73
|
controls: gummyControls,
|
|
74
74
|
// Its colour controls and Possibilities were asked for up front, so it shows 9.
|
|
75
|
-
essentials: ["body", "face", "glow"],
|
|
75
|
+
essentials: ["body", "topper", "face", "glow", "gummy.colors"],
|
|
76
76
|
styleKeys: ["body", "face", "topper", "glow"],
|
|
77
77
|
svg: gummySvg,
|
|
78
78
|
parts: gummyParts,
|
|
@@ -86,7 +86,7 @@ export const KINDS = [
|
|
|
86
86
|
controls: mallowControls,
|
|
87
87
|
// Eyes and Pose were asked for up front beside the rest, so it shows 8. As with bots, the
|
|
88
88
|
// container's size and colour join it only while it's on.
|
|
89
|
-
essentials: ["type", "face", "colour"],
|
|
89
|
+
essentials: ["type", "face", "eyes", "colour", "mallow.ink"],
|
|
90
90
|
// One bun for every name, as asked, so only its feeling sets variants apart.
|
|
91
91
|
styleKeys: ["face"],
|
|
92
92
|
displayKeys: ["container.size"],
|
|
@@ -100,7 +100,7 @@ export const KINDS = [
|
|
|
100
100
|
hint: "Its eyes follow you. Tap it to see what it does",
|
|
101
101
|
defaultName: "Maya",
|
|
102
102
|
controls: faceControls,
|
|
103
|
-
essentials: ["eyes", "mouth", "skin"],
|
|
103
|
+
essentials: ["eyes", "eyebrows", "mouth", "glasses", "skin"],
|
|
104
104
|
styleKeys: ["mouth"],
|
|
105
105
|
svg: faceSvg,
|
|
106
106
|
},
|
|
@@ -111,7 +111,7 @@ export const KINDS = [
|
|
|
111
111
|
hint: "Its eyes follow you. Write anything on it",
|
|
112
112
|
defaultName: "Felix",
|
|
113
113
|
controls: bubbleControls,
|
|
114
|
-
essentials: ["shape", "mouth", "palette"],
|
|
114
|
+
essentials: ["shape", "eye.kind", "mouth", "palette", "bubble.colors"],
|
|
115
115
|
styleKeys: ["shape", "palette"],
|
|
116
116
|
svg: bubbleSvg,
|
|
117
117
|
},
|
|
@@ -122,7 +122,7 @@ export const KINDS = [
|
|
|
122
122
|
hint: "Its eyes follow you. Hover over it and it perks up",
|
|
123
123
|
defaultName: "Finn",
|
|
124
124
|
controls: colorControls,
|
|
125
|
-
essentials: ["hair", "hair.color", "hat.color"
|
|
125
|
+
essentials: ["hair", "hat", "skin", "hair.color", "hat.color"],
|
|
126
126
|
styleKeys: ["hair"],
|
|
127
127
|
svg: (name, overrides) => pixelSvg(name, "color", overrides),
|
|
128
128
|
},
|
|
@@ -133,7 +133,7 @@ export const KINDS = [
|
|
|
133
133
|
hint: "Its eyes follow you. Hover over it and it grows",
|
|
134
134
|
defaultName: "Priya",
|
|
135
135
|
controls: glyphControls,
|
|
136
|
-
essentials: ["shape", "fill", "tile"],
|
|
136
|
+
essentials: ["shape", "eyes.kind", "fill", "tile"],
|
|
137
137
|
styleKeys: ["shape"],
|
|
138
138
|
svg: glyphSvg,
|
|
139
139
|
},
|
|
@@ -145,7 +145,7 @@ export const KINDS = [
|
|
|
145
145
|
defaultName: "Milo",
|
|
146
146
|
controls: peekControls,
|
|
147
147
|
// Possibilities was asked for up front beside the rest, so it shows 7.
|
|
148
|
-
essentials: ["body", "eye.kind", "tile"],
|
|
148
|
+
essentials: ["body", "pose", "eye.kind", "tile", "peek.colors"],
|
|
149
149
|
styleKeys: ["body", "pose", "tile"],
|
|
150
150
|
svg: peekSvg,
|
|
151
151
|
},
|
|
@@ -156,7 +156,7 @@ export const KINDS = [
|
|
|
156
156
|
hint: "Its eyes follow you. Hover over it and it perks up",
|
|
157
157
|
defaultName: "Kiko",
|
|
158
158
|
controls: doodleControls,
|
|
159
|
-
essentials: ["mood", "tile", "colourful"],
|
|
159
|
+
essentials: ["mood", "tile", "colourful", "ink"],
|
|
160
160
|
// Colourful is off until switched on, so Colourway isn't always there to step through.
|
|
161
161
|
styleKeys: ["mood", "tile"],
|
|
162
162
|
svg: doodleSvg,
|
|
@@ -168,7 +168,7 @@ export const KINDS = [
|
|
|
168
168
|
hint: "A cat, a dog, an owl, a bear or a tiger, drawn like the cats. Hover over it and its ears twitch",
|
|
169
169
|
defaultName: "Biscuit",
|
|
170
170
|
controls: animalControls,
|
|
171
|
-
essentials: ["animal", "mood", "tile"],
|
|
171
|
+
essentials: ["animal", "mood", "eyes.color", "tile", "fur"],
|
|
172
172
|
styleKeys: ["mood", "ears", "tile"],
|
|
173
173
|
svg: animalSvg,
|
|
174
174
|
},
|
|
@@ -181,7 +181,7 @@ export const KINDS = [
|
|
|
181
181
|
hint: "A tabby or a calico, up to something. Hover over it and its eyes widen",
|
|
182
182
|
defaultName: "Mochi",
|
|
183
183
|
controls: buddyControls,
|
|
184
|
-
essentials: ["character", "mood", "tile"],
|
|
184
|
+
essentials: ["character", "mood", "gesture", "tile", "fur"],
|
|
185
185
|
styleKeys: ["gesture", "mood", "tile"],
|
|
186
186
|
svg: buddySvg,
|
|
187
187
|
},
|
|
@@ -192,7 +192,7 @@ export const KINDS = [
|
|
|
192
192
|
hint: "A face put together from bold shapes. Its eyes follow you. Tap it and they hop",
|
|
193
193
|
defaultName: "Lumi",
|
|
194
194
|
controls: shapeControls,
|
|
195
|
-
essentials: ["eye.left", "eye.right", "mouth", "tile"],
|
|
195
|
+
essentials: ["eye.left", "eye.right", "mouth", "tile", "colourway"],
|
|
196
196
|
styleKeys: ["eye.left", "eye.right", "mouth", "colourway"],
|
|
197
197
|
svg: shapeSvg,
|
|
198
198
|
},
|
|
@@ -203,7 +203,7 @@ export const KINDS = [
|
|
|
203
203
|
hint: "A monster, a cyclops, a bear, a duck, a chick, an imp or a grump, up close. It watches you and blinks, and pops when tapped",
|
|
204
204
|
defaultName: "Gobo",
|
|
205
205
|
controls: critterControls,
|
|
206
|
-
essentials: ["kind", "feeling", "body"],
|
|
206
|
+
essentials: ["kind", "feeling", "eye.style", "body", "iris"],
|
|
207
207
|
// Variants step through the creatures in the generator, as the animals' are different animals.
|
|
208
208
|
styleKeys: ["feeling", "body"],
|
|
209
209
|
svg: critterSvg,
|
|
@@ -215,7 +215,7 @@ export const KINDS = [
|
|
|
215
215
|
hint: "A button of a face, a tile or round, with a big toothy grin. Its googly eyes follow you and blink, and it chomps when tapped",
|
|
216
216
|
defaultName: "Zuzu",
|
|
217
217
|
controls: grinControls,
|
|
218
|
-
essentials: ["eyes", "mouth", "body"],
|
|
218
|
+
essentials: ["eyes", "mouth", "teeth", "body"],
|
|
219
219
|
styleKeys: ["eyes", "mouth", "body"],
|
|
220
220
|
svg: grinSvg,
|
|
221
221
|
},
|
|
@@ -239,7 +239,7 @@ export const KINDS = [
|
|
|
239
239
|
hint: "Just two eyes. They follow you, glance about and blink, and pop when tapped",
|
|
240
240
|
defaultName: "Nyx",
|
|
241
241
|
controls: glanceControls,
|
|
242
|
-
essentials: ["eye.left", "eye.right", "tile"],
|
|
242
|
+
essentials: ["eye.left", "eye.right", "tile", "ink"],
|
|
243
243
|
styleKeys: ["eye.left", "eye.right", "tile"],
|
|
244
244
|
svg: glanceSvg,
|
|
245
245
|
},
|
|
@@ -254,7 +254,7 @@ export const KINDS = [
|
|
|
254
254
|
controls: auraControls,
|
|
255
255
|
// The type and a sky's horizon were asked for up front beside the rest, as the animals'
|
|
256
256
|
// type is. Formation is only an aura's and Horizon only a sky's, so it shows 7.
|
|
257
|
-
essentials: ["type", "flow", "palette", "face"],
|
|
257
|
+
essentials: ["type", "flow", "palette", "aura.colors", "face"],
|
|
258
258
|
// Variants step through the types, formations and palettes themselves, as Mallows do,
|
|
259
259
|
// since each type has palettes of its own. See `stepped` in auras/index.ts.
|
|
260
260
|
styleKeys: [],
|
package/dist/share.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* query string. Whatever comes back in is untrusted, so anything that doesn't check out is
|
|
4
4
|
* dropped rather than thrown on.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { MAX_VARIANT } from "./variants.js";
|
|
7
7
|
/** As long as the name box allows. */
|
|
8
8
|
const MAX_NAME = 256;
|
|
9
9
|
const MAX_STRING = 40;
|
|
@@ -75,7 +75,7 @@ export function decodeShare(search, sections) {
|
|
|
75
75
|
return {
|
|
76
76
|
section,
|
|
77
77
|
name: (params.get("n") ?? "").trim().slice(0, MAX_NAME),
|
|
78
|
-
variant: Number.isInteger(variant) && variant >= 0 && variant <
|
|
78
|
+
variant: Number.isInteger(variant) && variant >= 0 && variant < MAX_VARIANT ? variant : 0,
|
|
79
79
|
overrides,
|
|
80
80
|
};
|
|
81
81
|
}
|
package/dist/variants.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type { AvatarKind } from "./kinds.ts";
|
|
3
3
|
import type { Overrides } from "./traits.ts";
|
|
4
4
|
export declare const VARIANT_COUNT = 8;
|
|
5
|
+
/** The most variants the open tray scrolls to, or a share link may ask for. */
|
|
6
|
+
export declare const MAX_VARIANT = 10000;
|
|
5
7
|
/** The name a variant is generated from. The first is the name itself. */
|
|
6
8
|
export declare const variantName: (name: string, variant: number) => string;
|
|
7
9
|
/**
|
package/dist/variants.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Other takes on the selected avatar, shown in a strip beneath it. */
|
|
2
2
|
import { patched } from "./history.js";
|
|
3
3
|
export const VARIANT_COUNT = 8;
|
|
4
|
+
/** The most variants the open tray scrolls to, or a share link may ask for. */
|
|
5
|
+
export const MAX_VARIANT = 10_000;
|
|
4
6
|
/** Enough for a few names' worth of variants before a typing session starts over. */
|
|
5
7
|
const MAX_REMEMBERED = 256;
|
|
6
8
|
/** The name a variant is generated from. The first is the name itself. */
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@originkit/avatars",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OriginKit Avatars: turn any name into an avatar. 18 styles, SVG out,
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OriginKit Avatars: turn any name into an avatar. 18 styles, SVG out, live React avatars, a CLI and an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./react": {
|
|
12
|
+
"types": "./react/index.d.ts",
|
|
13
|
+
"default": "./react/index.js"
|
|
10
14
|
}
|
|
11
15
|
},
|
|
12
16
|
"bin": {
|
|
@@ -14,6 +18,7 @@
|
|
|
14
18
|
},
|
|
15
19
|
"files": [
|
|
16
20
|
"dist",
|
|
21
|
+
"react",
|
|
17
22
|
"bin",
|
|
18
23
|
"mcp",
|
|
19
24
|
"GUIDE.md",
|
|
@@ -21,7 +26,7 @@
|
|
|
21
26
|
"README.md"
|
|
22
27
|
],
|
|
23
28
|
"scripts": {
|
|
24
|
-
"build": "rm -rf dist && tsc -p tsconfig.json && cp ../../LICENSE .",
|
|
29
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && node scripts/build-react.mjs && cp ../../LICENSE .",
|
|
25
30
|
"test": "node --test \"test/*.test.js\"",
|
|
26
31
|
"prepublishOnly": "npm run build && npm test"
|
|
27
32
|
},
|
|
@@ -29,6 +34,15 @@
|
|
|
29
34
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
30
35
|
"zod": "^3.25 || ^4.0"
|
|
31
36
|
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"react": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"sideEffects": false,
|
|
32
46
|
"publishConfig": {
|
|
33
47
|
"access": "public"
|
|
34
48
|
},
|
package/react/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
|
|
3
|
+
/** The studio's settings for an avatar, as its How to use guide prints them. */
|
|
4
|
+
export type Overrides = Record<string, number | string>;
|
|
5
|
+
|
|
6
|
+
export interface AvatarProps {
|
|
7
|
+
/** A style id, as `styles` from "@originkit/avatars" lists them, such as "bots" or "glances". */
|
|
8
|
+
style: string;
|
|
9
|
+
/** What it's generated from. The same name always gives the same avatar. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** The studio's settings for it. */
|
|
12
|
+
overrides?: Overrides;
|
|
13
|
+
/** Width and height, in pixels or any CSS length. 96 by default. */
|
|
14
|
+
size?: number | string;
|
|
15
|
+
/** Whether the eyes follow the pointer. On by default. */
|
|
16
|
+
follow?: boolean;
|
|
17
|
+
/** Whether it blinks, moves and reacts to taps. On by default, and off for reduced motion. */
|
|
18
|
+
animate?: boolean;
|
|
19
|
+
/** Read out for the image. Empty by default, as an avatar beside a name adds nothing. */
|
|
20
|
+
label?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A live avatar: it blinks, follows the pointer and reacts to taps. On the server, and until
|
|
26
|
+
* the page is running, it shows the still SVG of the same avatar.
|
|
27
|
+
*/
|
|
28
|
+
export function Avatar(props: AvatarProps): JSX.Element;
|