@illlustrations/avatars 1.0.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/AI.md +84 -0
- package/ARTWORK.md +34 -0
- package/CHANGELOG.md +21 -0
- package/LICENSE +11 -0
- package/LICENSE-ARTWORK +9 -0
- package/LICENSE-CODE +21 -0
- package/README.md +170 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +88 -0
- package/dist/core/types.d.ts +83 -0
- package/dist/core/types.js +2 -0
- package/dist/internal/render.d.ts +5 -0
- package/dist/internal/render.js +74 -0
- package/dist/internal/style.d.ts +7 -0
- package/dist/internal/style.js +71 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +15 -0
- package/dist/styles/croods-presets.json +530 -0
- package/dist/styles/croods.d.ts +2 -0
- package/dist/styles/croods.js +2 -0
- package/dist/svg/croods-001.svg +68 -0
- package/dist/svg/croods-002.svg +52 -0
- package/dist/svg/croods-003.svg +61 -0
- package/dist/svg/croods-004.svg +37 -0
- package/dist/svg/croods-005.svg +77 -0
- package/dist/svg/croods-006.svg +52 -0
- package/dist/svg/croods-007.svg +64 -0
- package/dist/svg/croods-008.svg +57 -0
- package/dist/svg/croods-009.svg +44 -0
- package/dist/svg/croods-010.svg +68 -0
- package/dist/svg/croods-011.svg +72 -0
- package/dist/svg/croods-012.svg +51 -0
- package/dist/svg/croods-013.svg +73 -0
- package/dist/svg/croods-014.svg +34 -0
- package/dist/svg/croods-015.svg +70 -0
- package/dist/svg/croods-016.svg +58 -0
- package/dist/svg/croods-017.svg +72 -0
- package/dist/svg/croods-018.svg +68 -0
- package/dist/svg/croods-019.svg +38 -0
- package/dist/svg/croods-020.svg +66 -0
- package/dist/svg/croods-021.svg +64 -0
- package/dist/svg/croods-022.svg +63 -0
- package/dist/svg/croods-023.svg +58 -0
- package/dist/svg/croods-024.svg +38 -0
- package/package.json +94 -0
package/AI.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @illlustrations/avatars — notes for coding agents
|
|
2
|
+
|
|
3
|
+
Seeded SVG avatars for JavaScript and React. Everything renders locally: no
|
|
4
|
+
API key, network request or account. Croods is the first style.
|
|
5
|
+
|
|
6
|
+
Install: `npm install @illlustrations/avatars` (Node 20.19+; React 18.3+ or 19
|
|
7
|
+
for the component). Docs: https://illlustrations.co/docs
|
|
8
|
+
|
|
9
|
+
## Imports
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createAvatar, fromJSON } from '@illlustrations/avatars'; // no React
|
|
13
|
+
import { Avatar } from '@illlustrations/avatars/react'; // React 18.3+ or 19
|
|
14
|
+
import { croods } from '@illlustrations/avatars/croods'; // the style
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
There is no default export and no `/core`, `/styles/croods` or `/dist/...`
|
|
18
|
+
import path.
|
|
19
|
+
|
|
20
|
+
## Use
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// React. `assets` is required. Same seed = same avatar, on every device.
|
|
24
|
+
<Avatar assets={croods} seed={user.id} size={40} shape="circle" title={user.name} />
|
|
25
|
+
|
|
26
|
+
// Anywhere else: SVG string, data URI or saved state.
|
|
27
|
+
const avatar = createAvatar(croods, { seed: user.id, size: 128 });
|
|
28
|
+
avatar.toString(); // '<svg ...>'
|
|
29
|
+
avatar.toDataUri(); // for <img src> or CSS; add alt text yourself
|
|
30
|
+
avatar.toJSON(); // store this if the user customizes their avatar
|
|
31
|
+
fromJSON(croods, saved).toString();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
1. Use only the part IDs below. Unknown IDs throw `Unknown <slot> part: <id>`.
|
|
37
|
+
`head` and `upperBody` must be real IDs; the others also accept `none`.
|
|
38
|
+
2. Use a stable seed (user ID, not display name or `Math.random()`) so an
|
|
39
|
+
avatar does not change between renders or on the server and client.
|
|
40
|
+
3. When users pick parts, save `toJSON()` and restore with `fromJSON`. Do not
|
|
41
|
+
store only the seed: explicit selections are not derivable from it.
|
|
42
|
+
4. Pass `assets={croods}` to `<Avatar>`. `style` is the normal React CSS prop.
|
|
43
|
+
5. Several inline SVG strings on one page need unique IDs:
|
|
44
|
+
`toString({ idPrefix: 'user-42' })`. The prefix starts with a letter and uses
|
|
45
|
+
only letters, digits, `_` and `-`: clean user IDs and add the list index, e.g.
|
|
46
|
+
`` `avatar-${i}-${id.replace(/[^A-Za-z0-9_-]/g, '')}` ``. Seed with the raw ID.
|
|
47
|
+
`<Avatar>` and `toDataUri()` handle IDs themselves.
|
|
48
|
+
6. Colors: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `transparent`, or
|
|
49
|
+
`original` for color roles. Named CSS colors throw. `size` must be above 0 and at most 8192.
|
|
50
|
+
7. `<Avatar>` is a client component (`'use client'`). In a React Server
|
|
51
|
+
Component, render it as-is or use `createAvatar(...).toDataUri()` in an `<img>`.
|
|
52
|
+
8. The artwork is CC BY 4.0: credit "Croods by illlustrations" once per app or
|
|
53
|
+
site, somewhere visible (footer, about or credits page). Individual avatars,
|
|
54
|
+
emails and exported SVGs need no caption.
|
|
55
|
+
|
|
56
|
+
## Options
|
|
57
|
+
|
|
58
|
+
| Option | Values |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `seed` | Any string. Omit for the default character. |
|
|
61
|
+
| `selections` | Partial `{ head, face, upperBody, facialHair, accessories }` |
|
|
62
|
+
| `colors` | Partial `{ hair, skin, clothing, stroke }` |
|
|
63
|
+
| `theme` | `'ink'` (blue and white) or `'neutral'` (black and white) |
|
|
64
|
+
| `background` | Hex or `'transparent'` (default) |
|
|
65
|
+
| `shape` | `'square'` (default), `'rounded'`, `'circle'` |
|
|
66
|
+
| `size` | Pixels. Core default 600, React default 64 |
|
|
67
|
+
| `seedPool` | `'v1'` (default). Only for pinning seed results across versions |
|
|
68
|
+
|
|
69
|
+
Resolution order: style defaults → seeded combination → explicit `selections`.
|
|
70
|
+
Explicit `colors` override `theme` colors.
|
|
71
|
+
|
|
72
|
+
## Parts
|
|
73
|
+
|
|
74
|
+
<!-- parts:start (generated by scripts/sync-readme.mjs; do not edit) -->
|
|
75
|
+
| Slot | Required | Default | Valid IDs for `croods` |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| `head` | yes | `default` | `afro-1`, `afro-2`, `bald`, `bangs`, `bowl-cut`, `braid-1`, `braid-2`, `bun-1`, `bun-2`, `bun`, `default`, `dread`, `long-hair`, `long-hair-1`, `long-hair-2`, `messy-afro`, `messy`, `mohawk`, `no-hair`, `normal`, `pixie`, `quiff-1`, `quiff-2`, `shaggy`, `short-hair-1`, `short-hair-2`, `short-hair-3`, `short-hair-4`, `short-hair-5`, `short-hair-6`, `short-hair-7`, `short-hair-8`, `short-hair-9`, `straight-long`, `trimmed`, `wavy-curls` |
|
|
78
|
+
| `face` | no | `normal` | `drool`, `drool-2`, `happy`, `normal`, `open-mouth-1`, `sad`, `none` |
|
|
79
|
+
| `upperBody` | yes | `t-shirt-1` | `blazer`, `hoodie-1`, `shirt-1`, `t-shirt-1`, `t-shirt-bag` |
|
|
80
|
+
| `facialHair` | no | `none` | `beard`, `moustache`, `stubble`, `none` |
|
|
81
|
+
| `accessories` | no | `none` | `glass`, `none` |
|
|
82
|
+
|
|
83
|
+
Seeded avatars never pick facial hair; pass `selections.facialHair` to opt in.
|
|
84
|
+
<!-- parts:end -->
|
package/ARTWORK.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Croods v2 artwork snapshot
|
|
2
|
+
|
|
3
|
+
On September 16, 2026 the owner requested importing all Croods v2 parts marked
|
|
4
|
+
for the avatar layout. This repository now includes the 51 active variants
|
|
5
|
+
whose `layout_positions.avatar` has a valid positive size:
|
|
6
|
+
|
|
7
|
+
- 36 heads
|
|
8
|
+
- 6 faces
|
|
9
|
+
- 5 upper bodies
|
|
10
|
+
- 3 facial-hair parts
|
|
11
|
+
- 1 accessory
|
|
12
|
+
|
|
13
|
+
Seven lower-body parts have no avatar placement and were excluded. Originals
|
|
14
|
+
were downloaded read-only from the configured Supabase storage bucket, using
|
|
15
|
+
the catalog's references to resolve the corresponding private SVG. No database
|
|
16
|
+
or bucket records were changed.
|
|
17
|
+
|
|
18
|
+
`artwork/croods/catalog.json` records public aliases, display names, source file
|
|
19
|
+
hashes, per-part geometry, effective z-index and import time. It contains no
|
|
20
|
+
credentials, database UUIDs or storage URLs. `source/` holds the exact downloaded
|
|
21
|
+
SVG bytes for reproducible comparison. `manifest.json` contains the package
|
|
22
|
+
snapshot and frozen seed pool; `presets.json` contains 24 resolved portraits.
|
|
23
|
+
|
|
24
|
+
Normalization preserves source colors by default. Explicit role overrides
|
|
25
|
+
recolor selected source palette entries while retaining accents and coverage.
|
|
26
|
+
The npm file allowlist includes compiled artwork and preset SVGs, not raw
|
|
27
|
+
catalog/source files, import scripts or the unrelated test fixtures.
|
|
28
|
+
|
|
29
|
+
Croods v2 artwork by [illlustrations / Vijay Verma](https://illlustrations.co)
|
|
30
|
+
is licensed under [CC BY 4.0](LICENSE-ARTWORK). Code uses [MIT](LICENSE-CODE).
|
|
31
|
+
See [README.md](README.md#license) for attribution.
|
|
32
|
+
|
|
33
|
+
`private: true` and the release guard remain until the npm scope and public
|
|
34
|
+
release are reviewed.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
First public release of `@illlustrations/avatars`.
|
|
6
|
+
|
|
7
|
+
- Entry points: `@illlustrations/avatars` (core, no React), `/react` (`<Avatar>`),
|
|
8
|
+
`/croods` (style), `/croods/presets.json` and `/svg/croods-001.svg`–`croods-024.svg`.
|
|
9
|
+
Works with `import` and `require` (Node 20.19+).
|
|
10
|
+
- Croods style 1.0.0: 51 parts (36 heads, 6 faces, 5 upper bodies, 3 facial hair,
|
|
11
|
+
1 accessory) and 24 presets.
|
|
12
|
+
- Seeded avatars from a frozen `v1` pool of 256 combinations. Every seeded avatar
|
|
13
|
+
has a face; facial hair is opt-in through `selections.facialHair`.
|
|
14
|
+
- Colors per role (`hair`, `skin`, `clothing`, `stroke`), `ink` and `neutral`
|
|
15
|
+
themes, background, `square`/`rounded`/`circle` shapes.
|
|
16
|
+
- `toString`, `toDataUri`, `toJSON` and `fromJSON` with validated, versioned state.
|
|
17
|
+
- `AI.md` ships rules and every valid part ID for coding agents.
|
|
18
|
+
- Code: MIT. Croods artwork: CC BY 4.0.
|
|
19
|
+
|
|
20
|
+
Part IDs renamed from the unreleased preview: `t-shit-bag` → `t-shirt-bag`,
|
|
21
|
+
`pixe` → `pixie`, `drool2` → `drool-2`, facial hair `normal` → `stubble`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Copyright (c) 2026 Vijay Verma / illlustrations
|
|
2
|
+
|
|
3
|
+
Code: MIT — see LICENSE-CODE.
|
|
4
|
+
Artwork: CC BY 4.0 — see LICENSE-ARTWORK.
|
|
5
|
+
|
|
6
|
+
The artwork license covers artwork/croods/, including SVG parts, manifests
|
|
7
|
+
and presets, plus the Croods artwork embedded in compiled styles, exported
|
|
8
|
+
SVGs and generated avatars. Code wrappers remain under MIT.
|
|
9
|
+
|
|
10
|
+
Third-party dependencies and other illlustrations collections retain their
|
|
11
|
+
own licenses.
|
package/LICENSE-ARTWORK
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Croods v2 artwork
|
|
2
|
+
Copyright (c) 2026 Vijay Verma / illlustrations
|
|
3
|
+
https://illlustrations.co
|
|
4
|
+
|
|
5
|
+
Licensed under Creative Commons Attribution 4.0 International (CC BY 4.0).
|
|
6
|
+
https://creativecommons.org/licenses/by/4.0/
|
|
7
|
+
|
|
8
|
+
Full legal terms: https://creativecommons.org/licenses/by/4.0/legalcode
|
|
9
|
+
See LICENSE for the scope of artwork and code licensing.
|
package/LICENSE-CODE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vijay Verma / illlustrations
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @illlustrations/avatars
|
|
2
|
+
|
|
3
|
+
Composable, seeded SVG avatars for plain JavaScript and React. Croods is the
|
|
4
|
+
first style. Everything renders locally: no API key, account or network request.
|
|
5
|
+
Docs and a live playground: https://illlustrations.co/docs
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @illlustrations/avatars
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
React projects also need React 18.3.1 or 19. Node 20.19 or newer. The
|
|
12
|
+
Croods v2 collection includes all 51 active parts with a valid avatar
|
|
13
|
+
configuration in the source database: 36 heads, 6 faces, 5 upper bodies,
|
|
14
|
+
3 facial-hair parts and 1 accessory.
|
|
15
|
+
|
|
16
|
+
| Import | Contents |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `@illlustrations/avatars` | `createAvatar`, `fromJSON` and types. No React. |
|
|
19
|
+
| `@illlustrations/avatars/react` | The `<Avatar>` component. |
|
|
20
|
+
| `@illlustrations/avatars/croods` | The Croods style. Future styles get their own path. |
|
|
21
|
+
| `@illlustrations/avatars/croods/presets.json` | 24 resolved preset states. |
|
|
22
|
+
| `@illlustrations/avatars/svg/croods-001.svg` | Preset SVG files, `001` to `024`. |
|
|
23
|
+
|
|
24
|
+
Coding agents: see [AI.md](AI.md) for rules and valid part IDs.
|
|
25
|
+
|
|
26
|
+
## Develop
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install
|
|
30
|
+
npm test
|
|
31
|
+
npm run verify:artwork
|
|
32
|
+
npm run check:package
|
|
33
|
+
npm run preview
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The playground includes every part, original/custom colors, shapes, 24 starter
|
|
37
|
+
portraits, SVG/JSON downloads and JSON restoration. The parts gallery displays
|
|
38
|
+
the saved size and offset. Everything renders locally; no account, API key,
|
|
39
|
+
Supabase connection or network request is required by the package at runtime.
|
|
40
|
+
|
|
41
|
+
To try an unreleased build in another project, run `npm pack` here and
|
|
42
|
+
install the resulting archive there.
|
|
43
|
+
|
|
44
|
+
## React
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { Avatar } from '@illlustrations/avatars/react';
|
|
48
|
+
import { croods } from '@illlustrations/avatars/croods';
|
|
49
|
+
|
|
50
|
+
<Avatar assets={croods} seed="user-42" size={64} title="Sam's avatar" />
|
|
51
|
+
<Avatar assets={croods} shape="circle"
|
|
52
|
+
selections={{ head: 'straight-long', facialHair: 'beard', accessories: 'none' }}
|
|
53
|
+
colors={{ hair: '#143CFF', stroke: '#111111' }} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
React accepts `title`, `className`, `style`, ARIA and SVG presentation props.
|
|
57
|
+
Without a title or accessible label the avatar is decorative. It uses `useId`
|
|
58
|
+
to isolate SVG definitions. Applications with multiple React roots must use
|
|
59
|
+
matching, unique React `identifierPrefix` settings on server and client.
|
|
60
|
+
|
|
61
|
+
## Plain JavaScript
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { createAvatar, fromJSON } from '@illlustrations/avatars';
|
|
65
|
+
import { croods } from '@illlustrations/avatars/croods';
|
|
66
|
+
|
|
67
|
+
const avatar = createAvatar(croods, { seed: 'user-42', background: '#EDEDFF' });
|
|
68
|
+
const svg = avatar.toString();
|
|
69
|
+
const imageUrl = avatar.toDataUri();
|
|
70
|
+
const saved = avatar.toJSON();
|
|
71
|
+
const restored = fromJSON(croods, saved);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For HTML, assign `toDataUri()` to an image's `src` and supply `alt`. For multiple
|
|
75
|
+
inline SVG strings, use a unique `toString({ idPrefix: 'profile-42', title: 'Sam' })`
|
|
76
|
+
prefix for each. The root entry and `/croods` do not load React.
|
|
77
|
+
|
|
78
|
+
Individual SVG exports are available as
|
|
79
|
+
`@illlustrations/avatars/svg/croods-001.svg` through `croods-024.svg`. Use your
|
|
80
|
+
bundler's URL asset handling (for example a Vite `?url` import), or copy these
|
|
81
|
+
files into your public assets directory. An npm specifier is not a browser URL.
|
|
82
|
+
Resolved preset states are in `/croods/presets.json`.
|
|
83
|
+
|
|
84
|
+
## Parts
|
|
85
|
+
|
|
86
|
+
Unknown IDs throw `Unknown <slot> part: <id>`. Use only these:
|
|
87
|
+
|
|
88
|
+
<!-- parts:start (generated by scripts/sync-readme.mjs; do not edit) -->
|
|
89
|
+
| Slot | Required | Default | Valid IDs for `croods` |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| `head` | yes | `default` | `afro-1`, `afro-2`, `bald`, `bangs`, `bowl-cut`, `braid-1`, `braid-2`, `bun-1`, `bun-2`, `bun`, `default`, `dread`, `long-hair`, `long-hair-1`, `long-hair-2`, `messy-afro`, `messy`, `mohawk`, `no-hair`, `normal`, `pixie`, `quiff-1`, `quiff-2`, `shaggy`, `short-hair-1`, `short-hair-2`, `short-hair-3`, `short-hair-4`, `short-hair-5`, `short-hair-6`, `short-hair-7`, `short-hair-8`, `short-hair-9`, `straight-long`, `trimmed`, `wavy-curls` |
|
|
92
|
+
| `face` | no | `normal` | `drool`, `drool-2`, `happy`, `normal`, `open-mouth-1`, `sad`, `none` |
|
|
93
|
+
| `upperBody` | yes | `t-shirt-1` | `blazer`, `hoodie-1`, `shirt-1`, `t-shirt-1`, `t-shirt-bag` |
|
|
94
|
+
| `facialHair` | no | `none` | `beard`, `moustache`, `stubble`, `none` |
|
|
95
|
+
| `accessories` | no | `none` | `glass`, `none` |
|
|
96
|
+
|
|
97
|
+
Seeded avatars never pick facial hair; pass `selections.facialHair` to opt in.
|
|
98
|
+
<!-- parts:end -->
|
|
99
|
+
|
|
100
|
+
## Options
|
|
101
|
+
|
|
102
|
+
| Option | Behavior |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `seed` | String, including empty string. Omit for the default character. |
|
|
105
|
+
| `seedPool` | Versioned selection pool, initially `v1`. |
|
|
106
|
+
| `selections` | Partial `head`, `face`, `upperBody`, `facialHair`, `accessories` overrides. Face, facial hair and accessories support `none`. |
|
|
107
|
+
| `colors` | Partial `hair`, `skin`, `clothing`, `stroke` overrides. Croods defaults to `original` for each role. |
|
|
108
|
+
| `background` | Transparent by default. |
|
|
109
|
+
| `size` | Positive number up to 8192; core defaults to 600, React to 64. |
|
|
110
|
+
| `shape` | `square` (default), `rounded`, or `circle`. |
|
|
111
|
+
| `theme` | Omit for original artwork. `ink` uses `#0040FC` and white; `neutral` uses black and white. Explicit `colors` override theme roles. |
|
|
112
|
+
|
|
113
|
+
Colors accept 3/4/6/8-digit hex, `transparent`, or `original` for color roles.
|
|
114
|
+
Themes also recolor fixed accents, while preserving mask coverage and keeping
|
|
115
|
+
the background independent. Use `<Avatar assets={croods} theme="ink" />` or
|
|
116
|
+
`createAvatar(croods, { theme: 'neutral' })`. Themes are included in saved state.
|
|
117
|
+
`original` preserves each element's source paint, including differently colored
|
|
118
|
+
regions within one role. Background accepts hex or `transparent`. Recoloring
|
|
119
|
+
keeps fixed accent colors, mask coverage and clip paths. The explicit source
|
|
120
|
+
palette mapping is in `scripts/croods-colors.mjs`.
|
|
121
|
+
|
|
122
|
+
Resolution is defaults → seeded combination → explicit selections. Invalid
|
|
123
|
+
values, unknown parts/roles/slots and declared incompatible combinations throw.
|
|
124
|
+
Inline React rendering uses `--avatar-hair`, `--avatar-skin`,
|
|
125
|
+
`--avatar-clothing`, `--avatar-stroke`. Portable exports resolve paints to
|
|
126
|
+
literals. CSS overrides are presentation-only; use `colors` for a palette that
|
|
127
|
+
also appears in saved state and downloads.
|
|
128
|
+
|
|
129
|
+
## Stable state and geometry
|
|
130
|
+
|
|
131
|
+
Every part carries its exact database avatar rectangle and effective layer
|
|
132
|
+
order. The renderer scales the native SVG viewport to that rectangle. This
|
|
133
|
+
preserves each source viewBox, root paint and clipping, including tall hair.
|
|
134
|
+
Geometry and artwork are bundled, versioned snapshots. Database edits do not
|
|
135
|
+
change existing installations; re-import intentionally and update the style
|
|
136
|
+
version when changing rendered output.
|
|
137
|
+
|
|
138
|
+
The v1 algorithm is unsigned 32-bit FNV-1a over JavaScript UTF-16 code units
|
|
139
|
+
(`Math.imul(hash ^ codeUnit, 16777619)`, starting at `2166136261`), modulo the
|
|
140
|
+
frozen ordered pool length. Croods v1 contains 256 explicit tuples covering
|
|
141
|
+
all imported parts except facial hair, which is always `none` in seeded
|
|
142
|
+
avatars; pass `selections.facialHair` to opt in. Manual selections support
|
|
143
|
+
combinations outside that pool.
|
|
144
|
+
The pool is not a claim that every possible combination has been visually
|
|
145
|
+
reviewed. Never reorder/extend a published pool; add an opt-in pool instead.
|
|
146
|
+
Seeds can collide. Preset states are also committed explicitly.
|
|
147
|
+
|
|
148
|
+
JSON stores schema version 1, exact style ID/version, resolved selections,
|
|
149
|
+
colors, background, size and shape. `fromJSON` validates this data and rejects
|
|
150
|
+
mismatched versions. Runtime APIs accept compiled styles, never arbitrary SVG.
|
|
151
|
+
|
|
152
|
+
## Verification and release
|
|
153
|
+
|
|
154
|
+
`npm run verify:artwork` checks source hashes and database rectangles, then
|
|
155
|
+
compares all 51 parts in starter compositions and 24 presets against untouched
|
|
156
|
+
source SVGs at 32, 64, 128 and 600 px. It generates contact sheets and a report
|
|
157
|
+
in `.preview/`. `npm run check:package` checks actual installed core/style/React
|
|
158
|
+
imports, SVG exports, TypeScript declarations, packed files and ESM/SVG sizes.
|
|
159
|
+
|
|
160
|
+
See [ARTWORK.md](ARTWORK.md) for provenance and [docs/curation.md](docs/curation.md)
|
|
161
|
+
for re-import instructions. See [CHANGELOG.md](CHANGELOG.md) for releases.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
Code is licensed under [MIT](LICENSE-CODE). Croods v2 artwork, including parts,
|
|
166
|
+
presets and generated avatars, is licensed under [CC BY 4.0](LICENSE-ARTWORK).
|
|
167
|
+
|
|
168
|
+
Credit: Croods v2 by [illlustrations / Vijay Verma](https://illlustrations.co),
|
|
169
|
+
© 2026, [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
|
170
|
+
Retain required notices and indicate changes when sharing modified artwork.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AvatarOptions, AvatarResult, AvatarStyle } from './types.js';
|
|
2
|
+
export type { AvatarOptions, AvatarResult, AvatarState, AvatarStyle, ColorRole, Colors, RenderOptions, Selections, Shape, Slot, Theme } from './types.js';
|
|
3
|
+
export declare function createAvatar(style: AvatarStyle, options?: AvatarOptions): AvatarResult;
|
|
4
|
+
export declare function fromJSON(style: AvatarStyle, value: unknown): AvatarResult;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { renderDocument } from '../internal/render.js';
|
|
2
|
+
import { assertStyle, isColor, isRoleColor, validateSelections } from '../internal/style.js';
|
|
3
|
+
import { colorRoles, slots } from './types.js';
|
|
4
|
+
const own = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
5
|
+
function record(value, label) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
7
|
+
throw new Error(`Invalid ${label}`);
|
|
8
|
+
}
|
|
9
|
+
function keys(value, allowed, label) {
|
|
10
|
+
for (const key of Object.keys(value))
|
|
11
|
+
if (!allowed.includes(key))
|
|
12
|
+
throw new Error(`Unknown ${label}: ${key}`);
|
|
13
|
+
}
|
|
14
|
+
/** FNV-1a over UTF-16 code units, unsigned 32-bit. This algorithm is frozen for v1. */
|
|
15
|
+
function hash(seed) {
|
|
16
|
+
let value = 2166136261;
|
|
17
|
+
for (let i = 0; i < seed.length; i++)
|
|
18
|
+
value = Math.imul(value ^ seed.charCodeAt(i), 16777619);
|
|
19
|
+
return value >>> 0;
|
|
20
|
+
}
|
|
21
|
+
function validateState(style, state) {
|
|
22
|
+
record(state, 'avatar state');
|
|
23
|
+
keys(state, ['schemaVersion', 'style', 'styleVersion', 'selections', 'colors', 'background', 'size', 'shape', 'theme'], 'state field');
|
|
24
|
+
if (state.schemaVersion !== 1 || state.style !== style.id || state.styleVersion !== style.version)
|
|
25
|
+
throw new Error('Avatar state schema or style version does not match');
|
|
26
|
+
if (state.theme !== undefined && !['ink', 'neutral'].includes(state.theme))
|
|
27
|
+
throw new Error('Unknown avatar theme');
|
|
28
|
+
record(state.selections, 'selections');
|
|
29
|
+
keys(state.selections, slots, 'slot');
|
|
30
|
+
validateSelections(style, state.selections);
|
|
31
|
+
record(state.colors, 'colors');
|
|
32
|
+
keys(state.colors, colorRoles, 'color role');
|
|
33
|
+
for (const role of colorRoles)
|
|
34
|
+
if (!isRoleColor(state.colors[role]))
|
|
35
|
+
throw new Error(`Invalid ${role} color: use a hex color, transparent or original`);
|
|
36
|
+
if (!isColor(state.background))
|
|
37
|
+
throw new Error('Invalid background: use a hex color or transparent');
|
|
38
|
+
if (!Number.isFinite(state.size) || state.size <= 0 || state.size > 8192)
|
|
39
|
+
throw new Error('Size must be greater than 0 and at most 8192');
|
|
40
|
+
if (!['square', 'rounded', 'circle'].includes(state.shape))
|
|
41
|
+
throw new Error('Unknown avatar shape');
|
|
42
|
+
}
|
|
43
|
+
export function createAvatar(style, options = {}) {
|
|
44
|
+
assertStyle(style);
|
|
45
|
+
record(options, 'options');
|
|
46
|
+
keys(options, ['seed', 'seedPool', 'selections', 'colors', 'background', 'size', 'shape', 'theme'], 'option');
|
|
47
|
+
let selections = style.defaults.selections;
|
|
48
|
+
if (options.seed !== undefined && typeof options.seed !== 'string')
|
|
49
|
+
throw new Error('Seed must be a string');
|
|
50
|
+
const poolId = options.seedPool ?? style.defaultSeedPool;
|
|
51
|
+
if (typeof poolId !== 'string' || !own(style.seedPools, poolId))
|
|
52
|
+
throw new Error(`Unknown seed pool: ${poolId}`);
|
|
53
|
+
if (options.seed !== undefined) {
|
|
54
|
+
const pool = style.seedPools[poolId];
|
|
55
|
+
selections = pool[hash(options.seed) % pool.length];
|
|
56
|
+
}
|
|
57
|
+
if (options.selections !== undefined) {
|
|
58
|
+
record(options.selections, 'selections');
|
|
59
|
+
keys(options.selections, slots, 'slot');
|
|
60
|
+
}
|
|
61
|
+
if (options.colors !== undefined) {
|
|
62
|
+
record(options.colors, 'colors');
|
|
63
|
+
keys(options.colors, colorRoles, 'color role');
|
|
64
|
+
}
|
|
65
|
+
const state = {
|
|
66
|
+
schemaVersion: 1, style: style.id, styleVersion: style.version,
|
|
67
|
+
selections: { ...selections, ...options.selections },
|
|
68
|
+
colors: { ...style.defaults.colors, ...(options.theme ? { hair: options.theme === 'ink' ? '#0040FC' : '#000000', stroke: options.theme === 'ink' ? '#0040FC' : '#000000', skin: '#FFFFFF', clothing: '#FFFFFF' } : {}), ...options.colors },
|
|
69
|
+
...(options.theme === undefined ? {} : { theme: options.theme }),
|
|
70
|
+
background: options.background ?? 'transparent', size: options.size ?? 600, shape: options.shape ?? 'square',
|
|
71
|
+
};
|
|
72
|
+
validateState(style, state);
|
|
73
|
+
return result(style, state);
|
|
74
|
+
}
|
|
75
|
+
export function fromJSON(style, value) {
|
|
76
|
+
assertStyle(style);
|
|
77
|
+
record(value, 'avatar state');
|
|
78
|
+
const state = JSON.parse(JSON.stringify(value));
|
|
79
|
+
validateState(style, state);
|
|
80
|
+
return result(style, state);
|
|
81
|
+
}
|
|
82
|
+
function result(style, state) {
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
toJSON: () => JSON.parse(JSON.stringify(state)),
|
|
85
|
+
toString: (options = {}) => renderDocument(style, state, options),
|
|
86
|
+
toDataUri: (options = {}) => `data:image/svg+xml,${encodeURIComponent(renderDocument(style, state, options))}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export declare const slots: readonly ["upperBody", "head", "face", "facialHair", "accessories"];
|
|
2
|
+
export type Slot = (typeof slots)[number];
|
|
3
|
+
export declare const colorRoles: readonly ["hair", "skin", "clothing", "stroke"];
|
|
4
|
+
export type ColorRole = (typeof colorRoles)[number];
|
|
5
|
+
export type Colors = Record<ColorRole, string>;
|
|
6
|
+
export type Selections = Record<Slot, string>;
|
|
7
|
+
export type Theme = 'ink' | 'neutral';
|
|
8
|
+
export type Shape = 'square' | 'rounded' | 'circle';
|
|
9
|
+
export interface AvatarOptions {
|
|
10
|
+
theme?: Theme;
|
|
11
|
+
seed?: string;
|
|
12
|
+
seedPool?: string;
|
|
13
|
+
selections?: Partial<Selections>;
|
|
14
|
+
colors?: Partial<Colors>;
|
|
15
|
+
background?: string;
|
|
16
|
+
size?: number;
|
|
17
|
+
shape?: Shape;
|
|
18
|
+
}
|
|
19
|
+
export interface AvatarState {
|
|
20
|
+
theme?: Theme;
|
|
21
|
+
schemaVersion: 1;
|
|
22
|
+
style: string;
|
|
23
|
+
styleVersion: string;
|
|
24
|
+
selections: Selections;
|
|
25
|
+
colors: Colors;
|
|
26
|
+
background: string;
|
|
27
|
+
size: number;
|
|
28
|
+
shape: Shape;
|
|
29
|
+
}
|
|
30
|
+
export interface RenderOptions {
|
|
31
|
+
/** Unique on the containing page. Standalone image documents need no prefix. */
|
|
32
|
+
idPrefix?: string;
|
|
33
|
+
title?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AvatarResult {
|
|
36
|
+
toString(options?: RenderOptions): string;
|
|
37
|
+
toDataUri(options?: Omit<RenderOptions, 'idPrefix'>): string;
|
|
38
|
+
toJSON(): AvatarState;
|
|
39
|
+
}
|
|
40
|
+
export interface Part {
|
|
41
|
+
id: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
/** Sanitized at build time. Never pass user-supplied SVG here. */
|
|
44
|
+
svg: string;
|
|
45
|
+
/** Snapshot of the builder's effective avatar layer order. */
|
|
46
|
+
zIndex: number;
|
|
47
|
+
/** Exact per-variant avatar placement from the builder database. */
|
|
48
|
+
position: {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export interface StyleDefinition {
|
|
56
|
+
id: string;
|
|
57
|
+
version: string;
|
|
58
|
+
parts: Record<Slot, readonly Part[]>;
|
|
59
|
+
defaults: {
|
|
60
|
+
selections: Selections;
|
|
61
|
+
colors: Colors;
|
|
62
|
+
};
|
|
63
|
+
/** Ordered, frozen compatible tuples. Adding parts must not alter an existing pool. */
|
|
64
|
+
seedPools: Record<string, readonly Selections[]>;
|
|
65
|
+
defaultSeedPool: string;
|
|
66
|
+
/** Each pair declares a combination that must never be rendered. */
|
|
67
|
+
incompatible?: readonly {
|
|
68
|
+
first: {
|
|
69
|
+
slot: Slot;
|
|
70
|
+
id: string;
|
|
71
|
+
};
|
|
72
|
+
second: {
|
|
73
|
+
slot: Slot;
|
|
74
|
+
id: string;
|
|
75
|
+
};
|
|
76
|
+
}[];
|
|
77
|
+
}
|
|
78
|
+
declare const compiledStyle: unique symbol;
|
|
79
|
+
/** Only use style objects shipped by the package's curated style entry points. */
|
|
80
|
+
export interface AvatarStyle extends Readonly<StyleDefinition> {
|
|
81
|
+
readonly [compiledStyle]: true;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AvatarStyle, AvatarState, RenderOptions } from '../core/types.js';
|
|
2
|
+
export declare const escapeXml: (value: string) => string;
|
|
3
|
+
/** Internal React renderer; the package export map only exposes the supported public API. */
|
|
4
|
+
export declare function renderContents(style: AvatarStyle, state: AvatarState, idPrefix: string, cssVariables?: boolean): string;
|
|
5
|
+
export declare function renderDocument(style: AvatarStyle, state: AvatarState, options: RenderOptions): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { slots } from '../core/types.js';
|
|
2
|
+
export const escapeXml = (value) => value.replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char]);
|
|
3
|
+
function record(value, label) {
|
|
4
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
5
|
+
throw new Error(`Invalid ${label}`);
|
|
6
|
+
}
|
|
7
|
+
function keys(value, allowed, label) {
|
|
8
|
+
for (const key of Object.keys(value))
|
|
9
|
+
if (!allowed.includes(key))
|
|
10
|
+
throw new Error(`Unknown ${label}: ${key}`);
|
|
11
|
+
}
|
|
12
|
+
function prefix(value) {
|
|
13
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(value))
|
|
14
|
+
throw new Error('idPrefix must start with a letter and contain only letters, digits, underscores or hyphens');
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
/** Recolor fixed accents while preserving mask/clip coverage and role variables. */
|
|
18
|
+
function themeAccents(svg, ink) {
|
|
19
|
+
let coverage = 0;
|
|
20
|
+
return svg.replace(/<[^>]+>/g, tag => {
|
|
21
|
+
if (/^<\/(?:mask|clipPath)\b/.test(tag)) {
|
|
22
|
+
coverage--;
|
|
23
|
+
return tag;
|
|
24
|
+
}
|
|
25
|
+
const protectedTag = /^<(?:mask|clipPath)\b/.test(tag);
|
|
26
|
+
if (protectedTag && !/\/>$/.test(tag))
|
|
27
|
+
coverage++;
|
|
28
|
+
if (coverage || protectedTag)
|
|
29
|
+
return tag;
|
|
30
|
+
return tag.replace(/(\s(?:fill|stroke|stop-color)=")([^"<>]*)(")/g, (match, start, paint, end) => {
|
|
31
|
+
if (/^(?:none|transparent|var\(|url\()/i.test(paint))
|
|
32
|
+
return match;
|
|
33
|
+
const canonical = paint.toUpperCase();
|
|
34
|
+
// Dark neutral accents are linework; other fixed accents become paper.
|
|
35
|
+
const dark = canonical === 'BLACK' || /^#(?:000|000000|424242)$/.test(canonical);
|
|
36
|
+
if (!/^(?:#[0-9A-F]{3,8}|BLACK|WHITE)$/.test(canonical))
|
|
37
|
+
return match;
|
|
38
|
+
return `${start}${dark ? ink : '#FFFFFF'}${end}`;
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Internal React renderer; the package export map only exposes the supported public API. */
|
|
43
|
+
export function renderContents(style, state, idPrefix, cssVariables = false) {
|
|
44
|
+
const id = prefix(idPrefix);
|
|
45
|
+
const clip = state.shape === 'circle' ? '<circle cx="300" cy="300" r="300"/>'
|
|
46
|
+
: `<rect width="600" height="600" rx="${state.shape === 'rounded' ? 96 : 0}"/>`;
|
|
47
|
+
const layers = slots.flatMap(slot => {
|
|
48
|
+
const part = style.parts[slot].find(item => item.id === state.selections[slot]);
|
|
49
|
+
return part ? [{ slot, part }] : [];
|
|
50
|
+
}).sort((a, b) => a.part.zIndex - b.part.zIndex).map(({ slot, part }) => {
|
|
51
|
+
const source = state.theme ? themeAccents(part.svg, state.theme === 'ink' ? '#0040FC' : '#000000') : part.svg;
|
|
52
|
+
const svg = source.replaceAll('__AVATAR_ID__', `${id}-${slot}-`).replace(/var\(--avatar-(hair|skin|clothing|stroke)(?:,\s*([^)]*))?\)/g, (_, role, original) => {
|
|
53
|
+
const color = state.colors[role] === 'original' ? (original ?? '#000000') : state.colors[role];
|
|
54
|
+
return cssVariables ? `var(--avatar-${role}, ${color})` : color;
|
|
55
|
+
});
|
|
56
|
+
const p = part.position;
|
|
57
|
+
// Match the website export: scale the native viewport to the DB rectangle.
|
|
58
|
+
// Resizing the SVG viewport itself can introduce preserveAspectRatio letterboxing.
|
|
59
|
+
const root = svg.match(/^<svg\b([^>]*)>/)[1];
|
|
60
|
+
const width = Number(root.match(/\swidth="([\d.]+)"/)[1]);
|
|
61
|
+
const height = Number(root.match(/\sheight="([\d.]+)"/)[1]);
|
|
62
|
+
return `<g transform="translate(${p.x} ${p.y}) scale(${p.width / width} ${p.height / height})">${svg}</g>`;
|
|
63
|
+
}).join('');
|
|
64
|
+
return `<defs><clipPath id="${id}-clip">${clip}</clipPath></defs><g clip-path="url(#${id}-clip)"><rect width="600" height="600" fill="${state.background}"/>${layers}</g>`;
|
|
65
|
+
}
|
|
66
|
+
export function renderDocument(style, state, options) {
|
|
67
|
+
record(options, 'render options');
|
|
68
|
+
keys(options, ['idPrefix', 'title'], 'render option');
|
|
69
|
+
if (options.title !== undefined && typeof options.title !== 'string')
|
|
70
|
+
throw new Error('Title must be a string');
|
|
71
|
+
const id = prefix(options.idPrefix ?? 'avatar');
|
|
72
|
+
const title = options.title ? `<title id="${id}-title">${escapeXml(options.title)}</title>` : '';
|
|
73
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 600 600" width="${state.size}" height="${state.size}" ${title ? `role="img" aria-labelledby="${id}-title"` : 'aria-hidden="true"'}>${title}${renderContents(style, state, id)}</svg>`;
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AvatarStyle, Selections, StyleDefinition } from '../core/types.js';
|
|
2
|
+
export declare const isColor: (value: unknown) => value is string;
|
|
3
|
+
export declare const isRoleColor: (value: unknown) => value is string;
|
|
4
|
+
export declare function validateSelections(style: StyleDefinition, selections: Selections): void;
|
|
5
|
+
/** Internal build output loader. Not a public arbitrary-SVG authoring API. */
|
|
6
|
+
export declare function defineStyle(definition: StyleDefinition): AvatarStyle;
|
|
7
|
+
export declare function assertStyle(style: AvatarStyle): void;
|