invadrs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/chunk-5SOVCTHT.js +166 -0
- package/dist/index.cjs +179 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +1 -0
- package/dist/react/index.cjs +195 -0
- package/dist/react/index.d.cts +23 -0
- package/dist/react/index.d.ts +23 -0
- package/dist/react/index.js +56 -0
- package/dist/types-C4QooPnZ.d.cts +23 -0
- package/dist/types-C4QooPnZ.d.ts +23 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Goodwin
|
|
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,58 @@
|
|
|
1
|
+
# invadrs
|
|
2
|
+
|
|
3
|
+
Deterministic space-invaders-style pixel avatars from any string. Zero
|
|
4
|
+
dependencies. Same id in, same avatar out, forever.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { invadr, spawn, dataUri } from "invadrs";
|
|
10
|
+
|
|
11
|
+
invadr("matt"); // hand-drawn creature, SVG string
|
|
12
|
+
spawn("matt"); // procedural creature, SVG string
|
|
13
|
+
invadr("matt", { palette: "sunset", size: 32 });
|
|
14
|
+
dataUri(invadr("matt")); // data:image/svg+xml,...
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Primitives
|
|
18
|
+
|
|
19
|
+
- `invadr(id, options?)` — one of 16 hand-drawn creatures.
|
|
20
|
+
- `spawn(id, options?)` — a unique procedural symmetric creature.
|
|
21
|
+
|
|
22
|
+
## Options
|
|
23
|
+
|
|
24
|
+
`{ size?, palette?, padding?, background?, title?, resolution? }`
|
|
25
|
+
(`resolution` is `spawn`-only; when `size` is omitted no `width`/`height` is set,
|
|
26
|
+
so host CSS controls the size.)
|
|
27
|
+
|
|
28
|
+
## Palettes & theming
|
|
29
|
+
|
|
30
|
+
Built-ins, each its own colour mood: `tokyoNight` (default, balanced rainbow),
|
|
31
|
+
`neon` (electric, dark-bg), `sunset` (warm), `ocean` (cool), `forest`
|
|
32
|
+
(greens/earth), `mono` (grayscale). Pass a name, your own `string[]`, a full
|
|
33
|
+
`{ colors, background }` object, or `"css-vars"` to emit `var(--accent)`… fills
|
|
34
|
+
that follow your theme.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { palettes } from "invadrs";
|
|
38
|
+
const brand = { colors: [...palettes.tokyoNight.colors, "#ff00aa"] };
|
|
39
|
+
invadr("matt", { palette: brand });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## React
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { Invadr, Spawn, InvadrsProvider } from "invadrs/react";
|
|
46
|
+
|
|
47
|
+
<Invadr id="matt" palette="css-vars" /> {/* hand-drawn creature */}
|
|
48
|
+
<Spawn id="matt" /> {/* procedural creature */}
|
|
49
|
+
|
|
50
|
+
{/* app-wide defaults; explicit props on a component win */}
|
|
51
|
+
<InvadrsProvider palette="tokyoNight" size={20}>…</InvadrsProvider>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Stability contract
|
|
55
|
+
|
|
56
|
+
The hash, the order of the built-in creatures, the procedural generator, and the
|
|
57
|
+
`css-vars` color order are **frozen**. Changing any of them alters existing
|
|
58
|
+
avatars and is only ever done in a major release.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/hash.ts
|
|
2
|
+
function hashStr(s) {
|
|
3
|
+
let h = 2166136261;
|
|
4
|
+
for (let i = 0; i < s.length; i++) {
|
|
5
|
+
h ^= s.charCodeAt(i);
|
|
6
|
+
h = Math.imul(h, 16777619);
|
|
7
|
+
}
|
|
8
|
+
return h >>> 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/palettes.ts
|
|
12
|
+
var palettes = {
|
|
13
|
+
// balanced rainbow (default) — the Tokyo Night editor colours
|
|
14
|
+
tokyoNight: { colors: ["#7aa2f7", "#9ece6a", "#e0af68", "#bb9af7", "#7dcfff", "#f7768e"] },
|
|
15
|
+
// electric high-saturation — reads best on dark backgrounds
|
|
16
|
+
neon: { colors: ["#ff2e97", "#00eaff", "#39ff14", "#b026ff", "#faff00", "#ff5900"] },
|
|
17
|
+
// warm only — crimson, vermilion, orange, gold, rose, deep red
|
|
18
|
+
sunset: { colors: ["#e5383b", "#ff6b35", "#f77f00", "#ffca3a", "#ff5c8a", "#c1121f"] },
|
|
19
|
+
// cool only — navy, teal, sky, seafoam, blue, aqua
|
|
20
|
+
ocean: { colors: ["#1d3557", "#2a9d8f", "#48cae4", "#56e39f", "#4361ee", "#7bdff2"] },
|
|
21
|
+
// greens and earth — pine, leaf, lime-olive, fern, bark, tan
|
|
22
|
+
forest: { colors: ["#386641", "#6a994e", "#a7c957", "#588157", "#b08968", "#dda15e"] },
|
|
23
|
+
// grayscale
|
|
24
|
+
mono: { colors: ["#e6e6e6", "#bdbdbd", "#9e9e9e", "#757575", "#bdbdbd", "#e6e6e6"] }
|
|
25
|
+
};
|
|
26
|
+
var CSS_VARS = {
|
|
27
|
+
colors: ["var(--accent)", "var(--green)", "var(--amber)", "var(--purple)", "var(--cyan)", "var(--red)"]
|
|
28
|
+
};
|
|
29
|
+
function resolvePalette(input) {
|
|
30
|
+
if (input === void 0) return palettes.tokyoNight;
|
|
31
|
+
if (input === "css-vars") return CSS_VARS;
|
|
32
|
+
if (typeof input === "string") return palettes[input];
|
|
33
|
+
if (Array.isArray(input)) return { colors: input };
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
36
|
+
function pickColor(seed, palette) {
|
|
37
|
+
return palette.colors[(seed >>> 4) % palette.colors.length];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/render.ts
|
|
41
|
+
function escapeXml(s) {
|
|
42
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
43
|
+
}
|
|
44
|
+
function resolveCommon(seed, grid, options) {
|
|
45
|
+
const palette = resolvePalette(options?.palette);
|
|
46
|
+
return {
|
|
47
|
+
grid,
|
|
48
|
+
color: pickColor(seed, palette),
|
|
49
|
+
size: options?.size,
|
|
50
|
+
padding: options?.padding ?? 1,
|
|
51
|
+
background: options?.background ?? palette.background,
|
|
52
|
+
title: options?.title
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function renderSvg(s) {
|
|
56
|
+
const n = s.grid.length;
|
|
57
|
+
const min = -s.padding;
|
|
58
|
+
const span = n + s.padding * 2;
|
|
59
|
+
const rects = [];
|
|
60
|
+
if (s.background) {
|
|
61
|
+
rects.push(`<rect x="${min}" y="${min}" width="${span}" height="${span}" fill="${s.background}"/>`);
|
|
62
|
+
}
|
|
63
|
+
for (let y = 0; y < n; y++) {
|
|
64
|
+
for (let x = 0; x < n; x++) {
|
|
65
|
+
if (s.grid[y][x]) rects.push(`<rect x="${x}" y="${y}" width="1" height="1"/>`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const dims = s.size !== void 0 ? ` width="${s.size}" height="${s.size}"` : "";
|
|
69
|
+
const a11y = s.title ? `role="img"` : `aria-hidden="true"`;
|
|
70
|
+
const titleEl = s.title ? `<title>${escapeXml(s.title)}</title>` : "";
|
|
71
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${min} ${min} ${span} ${span}"${dims} shape-rendering="crispEdges" fill="${s.color}" ${a11y}>${titleEl}${rects.join("")}</svg>`;
|
|
72
|
+
}
|
|
73
|
+
function dataUri(svg) {
|
|
74
|
+
return "data:image/svg+xml," + encodeURIComponent(svg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/invadr.ts
|
|
78
|
+
var INVADR_SPRITES = [
|
|
79
|
+
[24, 60, 126, 219, 255, 36, 90, 165],
|
|
80
|
+
// invader
|
|
81
|
+
[36, 126, 219, 255, 255, 102, 36, 66],
|
|
82
|
+
// crab
|
|
83
|
+
[60, 126, 219, 255, 255, 255, 255, 165],
|
|
84
|
+
// ghost
|
|
85
|
+
[60, 66, 165, 129, 165, 153, 66, 60],
|
|
86
|
+
// smiley
|
|
87
|
+
[24, 24, 126, 255, 255, 102, 102, 231],
|
|
88
|
+
// mech
|
|
89
|
+
[66, 36, 126, 219, 255, 165, 36, 66],
|
|
90
|
+
// alien
|
|
91
|
+
[231, 60, 126, 219, 255, 219, 126, 102],
|
|
92
|
+
// robot
|
|
93
|
+
[129, 195, 255, 219, 255, 255, 255, 165],
|
|
94
|
+
// cat
|
|
95
|
+
[60, 126, 255, 219, 255, 126, 90, 36],
|
|
96
|
+
// skull
|
|
97
|
+
[153, 126, 255, 219, 255, 255, 126, 165],
|
|
98
|
+
// beetle
|
|
99
|
+
[195, 255, 219, 255, 126, 60, 24, 36],
|
|
100
|
+
// owl
|
|
101
|
+
[36, 126, 255, 231, 255, 126, 24, 24],
|
|
102
|
+
// flower
|
|
103
|
+
[24, 60, 126, 255, 255, 126, 60, 24],
|
|
104
|
+
// gem
|
|
105
|
+
[102, 255, 255, 255, 126, 126, 60, 24],
|
|
106
|
+
// heart
|
|
107
|
+
[60, 126, 255, 255, 90, 24, 24, 60],
|
|
108
|
+
// mushroom
|
|
109
|
+
[36, 90, 255, 189, 255, 90, 165, 66]
|
|
110
|
+
// amoeba
|
|
111
|
+
];
|
|
112
|
+
function byteRowsToGrid(rows) {
|
|
113
|
+
return rows.map((row) => {
|
|
114
|
+
const cells = [];
|
|
115
|
+
for (let x = 0; x < 8; x++) cells.push((row & 128 >> x) !== 0);
|
|
116
|
+
return cells;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function invadrGrid(seed) {
|
|
120
|
+
return byteRowsToGrid(INVADR_SPRITES[seed % INVADR_SPRITES.length]);
|
|
121
|
+
}
|
|
122
|
+
function resolveInvadr(id, options) {
|
|
123
|
+
const seed = hashStr(id);
|
|
124
|
+
return resolveCommon(seed, invadrGrid(seed), options);
|
|
125
|
+
}
|
|
126
|
+
function invadr(id, options) {
|
|
127
|
+
return renderSvg(resolveInvadr(id, options));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/spawn.ts
|
|
131
|
+
var DENSITY = 0.5;
|
|
132
|
+
function mulberry32(seed) {
|
|
133
|
+
let a = seed >>> 0;
|
|
134
|
+
return function() {
|
|
135
|
+
a = a + 1831565813 | 0;
|
|
136
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
137
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
138
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function spawnGrid(seed, resolution) {
|
|
142
|
+
const n = resolution;
|
|
143
|
+
const half = n / 2;
|
|
144
|
+
const rng = mulberry32(seed);
|
|
145
|
+
const grid = [];
|
|
146
|
+
for (let y = 0; y < n; y++) {
|
|
147
|
+
const row = new Array(n).fill(false);
|
|
148
|
+
for (let x = 0; x < half; x++) {
|
|
149
|
+
const on = rng() < DENSITY;
|
|
150
|
+
row[x] = on;
|
|
151
|
+
row[n - 1 - x] = on;
|
|
152
|
+
}
|
|
153
|
+
grid.push(row);
|
|
154
|
+
}
|
|
155
|
+
return grid;
|
|
156
|
+
}
|
|
157
|
+
function resolveSpawn(id, options) {
|
|
158
|
+
const seed = hashStr(id);
|
|
159
|
+
const resolution = options?.resolution ?? 8;
|
|
160
|
+
return resolveCommon(seed, spawnGrid(seed, resolution), options);
|
|
161
|
+
}
|
|
162
|
+
function spawn(id, options) {
|
|
163
|
+
return renderSvg(resolveSpawn(id, options));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { CSS_VARS, INVADR_SPRITES, dataUri, hashStr, invadr, palettes, pickColor, renderSvg, resolveInvadr, resolvePalette, resolveSpawn, spawn };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/hash.ts
|
|
4
|
+
function hashStr(s) {
|
|
5
|
+
let h = 2166136261;
|
|
6
|
+
for (let i = 0; i < s.length; i++) {
|
|
7
|
+
h ^= s.charCodeAt(i);
|
|
8
|
+
h = Math.imul(h, 16777619);
|
|
9
|
+
}
|
|
10
|
+
return h >>> 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/palettes.ts
|
|
14
|
+
var palettes = {
|
|
15
|
+
// balanced rainbow (default) — the Tokyo Night editor colours
|
|
16
|
+
tokyoNight: { colors: ["#7aa2f7", "#9ece6a", "#e0af68", "#bb9af7", "#7dcfff", "#f7768e"] },
|
|
17
|
+
// electric high-saturation — reads best on dark backgrounds
|
|
18
|
+
neon: { colors: ["#ff2e97", "#00eaff", "#39ff14", "#b026ff", "#faff00", "#ff5900"] },
|
|
19
|
+
// warm only — crimson, vermilion, orange, gold, rose, deep red
|
|
20
|
+
sunset: { colors: ["#e5383b", "#ff6b35", "#f77f00", "#ffca3a", "#ff5c8a", "#c1121f"] },
|
|
21
|
+
// cool only — navy, teal, sky, seafoam, blue, aqua
|
|
22
|
+
ocean: { colors: ["#1d3557", "#2a9d8f", "#48cae4", "#56e39f", "#4361ee", "#7bdff2"] },
|
|
23
|
+
// greens and earth — pine, leaf, lime-olive, fern, bark, tan
|
|
24
|
+
forest: { colors: ["#386641", "#6a994e", "#a7c957", "#588157", "#b08968", "#dda15e"] },
|
|
25
|
+
// grayscale
|
|
26
|
+
mono: { colors: ["#e6e6e6", "#bdbdbd", "#9e9e9e", "#757575", "#bdbdbd", "#e6e6e6"] }
|
|
27
|
+
};
|
|
28
|
+
var CSS_VARS = {
|
|
29
|
+
colors: ["var(--accent)", "var(--green)", "var(--amber)", "var(--purple)", "var(--cyan)", "var(--red)"]
|
|
30
|
+
};
|
|
31
|
+
function resolvePalette(input) {
|
|
32
|
+
if (input === void 0) return palettes.tokyoNight;
|
|
33
|
+
if (input === "css-vars") return CSS_VARS;
|
|
34
|
+
if (typeof input === "string") return palettes[input];
|
|
35
|
+
if (Array.isArray(input)) return { colors: input };
|
|
36
|
+
return input;
|
|
37
|
+
}
|
|
38
|
+
function pickColor(seed, palette) {
|
|
39
|
+
return palette.colors[(seed >>> 4) % palette.colors.length];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/render.ts
|
|
43
|
+
function escapeXml(s) {
|
|
44
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
45
|
+
}
|
|
46
|
+
function resolveCommon(seed, grid, options) {
|
|
47
|
+
const palette = resolvePalette(options?.palette);
|
|
48
|
+
return {
|
|
49
|
+
grid,
|
|
50
|
+
color: pickColor(seed, palette),
|
|
51
|
+
size: options?.size,
|
|
52
|
+
padding: options?.padding ?? 1,
|
|
53
|
+
background: options?.background ?? palette.background,
|
|
54
|
+
title: options?.title
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function renderSvg(s) {
|
|
58
|
+
const n = s.grid.length;
|
|
59
|
+
const min = -s.padding;
|
|
60
|
+
const span = n + s.padding * 2;
|
|
61
|
+
const rects = [];
|
|
62
|
+
if (s.background) {
|
|
63
|
+
rects.push(`<rect x="${min}" y="${min}" width="${span}" height="${span}" fill="${s.background}"/>`);
|
|
64
|
+
}
|
|
65
|
+
for (let y = 0; y < n; y++) {
|
|
66
|
+
for (let x = 0; x < n; x++) {
|
|
67
|
+
if (s.grid[y][x]) rects.push(`<rect x="${x}" y="${y}" width="1" height="1"/>`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const dims = s.size !== void 0 ? ` width="${s.size}" height="${s.size}"` : "";
|
|
71
|
+
const a11y = s.title ? `role="img"` : `aria-hidden="true"`;
|
|
72
|
+
const titleEl = s.title ? `<title>${escapeXml(s.title)}</title>` : "";
|
|
73
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${min} ${min} ${span} ${span}"${dims} shape-rendering="crispEdges" fill="${s.color}" ${a11y}>${titleEl}${rects.join("")}</svg>`;
|
|
74
|
+
}
|
|
75
|
+
function dataUri(svg) {
|
|
76
|
+
return "data:image/svg+xml," + encodeURIComponent(svg);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/invadr.ts
|
|
80
|
+
var INVADR_SPRITES = [
|
|
81
|
+
[24, 60, 126, 219, 255, 36, 90, 165],
|
|
82
|
+
// invader
|
|
83
|
+
[36, 126, 219, 255, 255, 102, 36, 66],
|
|
84
|
+
// crab
|
|
85
|
+
[60, 126, 219, 255, 255, 255, 255, 165],
|
|
86
|
+
// ghost
|
|
87
|
+
[60, 66, 165, 129, 165, 153, 66, 60],
|
|
88
|
+
// smiley
|
|
89
|
+
[24, 24, 126, 255, 255, 102, 102, 231],
|
|
90
|
+
// mech
|
|
91
|
+
[66, 36, 126, 219, 255, 165, 36, 66],
|
|
92
|
+
// alien
|
|
93
|
+
[231, 60, 126, 219, 255, 219, 126, 102],
|
|
94
|
+
// robot
|
|
95
|
+
[129, 195, 255, 219, 255, 255, 255, 165],
|
|
96
|
+
// cat
|
|
97
|
+
[60, 126, 255, 219, 255, 126, 90, 36],
|
|
98
|
+
// skull
|
|
99
|
+
[153, 126, 255, 219, 255, 255, 126, 165],
|
|
100
|
+
// beetle
|
|
101
|
+
[195, 255, 219, 255, 126, 60, 24, 36],
|
|
102
|
+
// owl
|
|
103
|
+
[36, 126, 255, 231, 255, 126, 24, 24],
|
|
104
|
+
// flower
|
|
105
|
+
[24, 60, 126, 255, 255, 126, 60, 24],
|
|
106
|
+
// gem
|
|
107
|
+
[102, 255, 255, 255, 126, 126, 60, 24],
|
|
108
|
+
// heart
|
|
109
|
+
[60, 126, 255, 255, 90, 24, 24, 60],
|
|
110
|
+
// mushroom
|
|
111
|
+
[36, 90, 255, 189, 255, 90, 165, 66]
|
|
112
|
+
// amoeba
|
|
113
|
+
];
|
|
114
|
+
function byteRowsToGrid(rows) {
|
|
115
|
+
return rows.map((row) => {
|
|
116
|
+
const cells = [];
|
|
117
|
+
for (let x = 0; x < 8; x++) cells.push((row & 128 >> x) !== 0);
|
|
118
|
+
return cells;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function invadrGrid(seed) {
|
|
122
|
+
return byteRowsToGrid(INVADR_SPRITES[seed % INVADR_SPRITES.length]);
|
|
123
|
+
}
|
|
124
|
+
function resolveInvadr(id, options) {
|
|
125
|
+
const seed = hashStr(id);
|
|
126
|
+
return resolveCommon(seed, invadrGrid(seed), options);
|
|
127
|
+
}
|
|
128
|
+
function invadr(id, options) {
|
|
129
|
+
return renderSvg(resolveInvadr(id, options));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/spawn.ts
|
|
133
|
+
var DENSITY = 0.5;
|
|
134
|
+
function mulberry32(seed) {
|
|
135
|
+
let a = seed >>> 0;
|
|
136
|
+
return function() {
|
|
137
|
+
a = a + 1831565813 | 0;
|
|
138
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
139
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
140
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function spawnGrid(seed, resolution) {
|
|
144
|
+
const n = resolution;
|
|
145
|
+
const half = n / 2;
|
|
146
|
+
const rng = mulberry32(seed);
|
|
147
|
+
const grid = [];
|
|
148
|
+
for (let y = 0; y < n; y++) {
|
|
149
|
+
const row = new Array(n).fill(false);
|
|
150
|
+
for (let x = 0; x < half; x++) {
|
|
151
|
+
const on = rng() < DENSITY;
|
|
152
|
+
row[x] = on;
|
|
153
|
+
row[n - 1 - x] = on;
|
|
154
|
+
}
|
|
155
|
+
grid.push(row);
|
|
156
|
+
}
|
|
157
|
+
return grid;
|
|
158
|
+
}
|
|
159
|
+
function resolveSpawn(id, options) {
|
|
160
|
+
const seed = hashStr(id);
|
|
161
|
+
const resolution = options?.resolution ?? 8;
|
|
162
|
+
return resolveCommon(seed, spawnGrid(seed, resolution), options);
|
|
163
|
+
}
|
|
164
|
+
function spawn(id, options) {
|
|
165
|
+
return renderSvg(resolveSpawn(id, options));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
exports.CSS_VARS = CSS_VARS;
|
|
169
|
+
exports.INVADR_SPRITES = INVADR_SPRITES;
|
|
170
|
+
exports.dataUri = dataUri;
|
|
171
|
+
exports.hashStr = hashStr;
|
|
172
|
+
exports.invadr = invadr;
|
|
173
|
+
exports.palettes = palettes;
|
|
174
|
+
exports.pickColor = pickColor;
|
|
175
|
+
exports.renderSvg = renderSvg;
|
|
176
|
+
exports.resolveInvadr = resolveInvadr;
|
|
177
|
+
exports.resolvePalette = resolvePalette;
|
|
178
|
+
exports.resolveSpawn = resolveSpawn;
|
|
179
|
+
exports.spawn = spawn;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { G as Grid, S as SpriteOptions, P as Palette, a as PaletteName, b as PaletteInput } from './types-C4QooPnZ.cjs';
|
|
2
|
+
|
|
3
|
+
/** A fully-resolved sprite: geometry + paint + presentation, ready to render. */
|
|
4
|
+
type ResolvedSprite = {
|
|
5
|
+
grid: Grid;
|
|
6
|
+
color: string;
|
|
7
|
+
size?: number;
|
|
8
|
+
padding: number;
|
|
9
|
+
background?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
};
|
|
12
|
+
/** Render a ResolvedSprite to a standalone SVG string. */
|
|
13
|
+
declare function renderSvg(s: ResolvedSprite): string;
|
|
14
|
+
/** Wrap an SVG string as a data: URI usable in `src`/`background-image`. */
|
|
15
|
+
declare function dataUri(svg: string): string;
|
|
16
|
+
|
|
17
|
+
/** Hand-designed 8x8 creatures (one byte per row, MSB = left pixel).
|
|
18
|
+
Curated so every id reads as a deliberate sprite; each is left-right
|
|
19
|
+
symmetric. ORDER IS FROZEN — reordering changes users' avatars. */
|
|
20
|
+
declare const INVADR_SPRITES: number[][];
|
|
21
|
+
/** Resolve a hand-drawn creature for an id (structured form). */
|
|
22
|
+
declare function resolveInvadr(id: string, options?: SpriteOptions): ResolvedSprite;
|
|
23
|
+
/** A hand-drawn creature for an id, as an SVG string. */
|
|
24
|
+
declare function invadr(id: string, options?: SpriteOptions): string;
|
|
25
|
+
|
|
26
|
+
/** Resolve a procedural creature for an id (structured form). */
|
|
27
|
+
declare function resolveSpawn(id: string, options?: SpriteOptions): ResolvedSprite;
|
|
28
|
+
/** A procedural symmetric creature for an id, as an SVG string. */
|
|
29
|
+
declare function spawn(id: string, options?: SpriteOptions): string;
|
|
30
|
+
|
|
31
|
+
/** Built-in palettes. `tokyoNight` is the default and matches mr-board.
|
|
32
|
+
Each palette is its own colour mood (not the same rainbow re-shaded), so a
|
|
33
|
+
given id looks distinct from one palette to the next. */
|
|
34
|
+
declare const palettes: Record<PaletteName, Palette>;
|
|
35
|
+
/** Palette that emits CSS custom properties instead of literal colors so the
|
|
36
|
+
host theme drives the sprite color. Order is frozen for mr-board parity. */
|
|
37
|
+
declare const CSS_VARS: Palette;
|
|
38
|
+
/** Resolve any PaletteInput to a concrete Palette. */
|
|
39
|
+
declare function resolvePalette(input?: PaletteInput): Palette;
|
|
40
|
+
/** Deterministically pick a color from the palette for a given seed.
|
|
41
|
+
Uses `seed >>> 4` so it is independent of the sprite index (`seed % n`). */
|
|
42
|
+
declare function pickColor(seed: number, palette: Palette): string;
|
|
43
|
+
|
|
44
|
+
/** FNV-1a string hash -> unsigned 32-bit seed. Frozen: changing this changes
|
|
45
|
+
every avatar, so it is a semver-major change. */
|
|
46
|
+
declare function hashStr(s: string): number;
|
|
47
|
+
|
|
48
|
+
export { CSS_VARS, Grid, INVADR_SPRITES, Palette, PaletteInput, PaletteName, type ResolvedSprite, SpriteOptions, dataUri, hashStr, invadr, palettes, pickColor, renderSvg, resolveInvadr, resolvePalette, resolveSpawn, spawn };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { G as Grid, S as SpriteOptions, P as Palette, a as PaletteName, b as PaletteInput } from './types-C4QooPnZ.js';
|
|
2
|
+
|
|
3
|
+
/** A fully-resolved sprite: geometry + paint + presentation, ready to render. */
|
|
4
|
+
type ResolvedSprite = {
|
|
5
|
+
grid: Grid;
|
|
6
|
+
color: string;
|
|
7
|
+
size?: number;
|
|
8
|
+
padding: number;
|
|
9
|
+
background?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
};
|
|
12
|
+
/** Render a ResolvedSprite to a standalone SVG string. */
|
|
13
|
+
declare function renderSvg(s: ResolvedSprite): string;
|
|
14
|
+
/** Wrap an SVG string as a data: URI usable in `src`/`background-image`. */
|
|
15
|
+
declare function dataUri(svg: string): string;
|
|
16
|
+
|
|
17
|
+
/** Hand-designed 8x8 creatures (one byte per row, MSB = left pixel).
|
|
18
|
+
Curated so every id reads as a deliberate sprite; each is left-right
|
|
19
|
+
symmetric. ORDER IS FROZEN — reordering changes users' avatars. */
|
|
20
|
+
declare const INVADR_SPRITES: number[][];
|
|
21
|
+
/** Resolve a hand-drawn creature for an id (structured form). */
|
|
22
|
+
declare function resolveInvadr(id: string, options?: SpriteOptions): ResolvedSprite;
|
|
23
|
+
/** A hand-drawn creature for an id, as an SVG string. */
|
|
24
|
+
declare function invadr(id: string, options?: SpriteOptions): string;
|
|
25
|
+
|
|
26
|
+
/** Resolve a procedural creature for an id (structured form). */
|
|
27
|
+
declare function resolveSpawn(id: string, options?: SpriteOptions): ResolvedSprite;
|
|
28
|
+
/** A procedural symmetric creature for an id, as an SVG string. */
|
|
29
|
+
declare function spawn(id: string, options?: SpriteOptions): string;
|
|
30
|
+
|
|
31
|
+
/** Built-in palettes. `tokyoNight` is the default and matches mr-board.
|
|
32
|
+
Each palette is its own colour mood (not the same rainbow re-shaded), so a
|
|
33
|
+
given id looks distinct from one palette to the next. */
|
|
34
|
+
declare const palettes: Record<PaletteName, Palette>;
|
|
35
|
+
/** Palette that emits CSS custom properties instead of literal colors so the
|
|
36
|
+
host theme drives the sprite color. Order is frozen for mr-board parity. */
|
|
37
|
+
declare const CSS_VARS: Palette;
|
|
38
|
+
/** Resolve any PaletteInput to a concrete Palette. */
|
|
39
|
+
declare function resolvePalette(input?: PaletteInput): Palette;
|
|
40
|
+
/** Deterministically pick a color from the palette for a given seed.
|
|
41
|
+
Uses `seed >>> 4` so it is independent of the sprite index (`seed % n`). */
|
|
42
|
+
declare function pickColor(seed: number, palette: Palette): string;
|
|
43
|
+
|
|
44
|
+
/** FNV-1a string hash -> unsigned 32-bit seed. Frozen: changing this changes
|
|
45
|
+
every avatar, so it is a semver-major change. */
|
|
46
|
+
declare function hashStr(s: string): number;
|
|
47
|
+
|
|
48
|
+
export { CSS_VARS, Grid, INVADR_SPRITES, Palette, PaletteInput, PaletteName, type ResolvedSprite, SpriteOptions, dataUri, hashStr, invadr, palettes, pickColor, renderSvg, resolveInvadr, resolvePalette, resolveSpawn, spawn };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CSS_VARS, INVADR_SPRITES, dataUri, hashStr, invadr, palettes, pickColor, renderSvg, resolveInvadr, resolvePalette, resolveSpawn, spawn } from './chunk-5SOVCTHT.js';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/react/index.tsx
|
|
7
|
+
|
|
8
|
+
// src/hash.ts
|
|
9
|
+
function hashStr(s) {
|
|
10
|
+
let h = 2166136261;
|
|
11
|
+
for (let i = 0; i < s.length; i++) {
|
|
12
|
+
h ^= s.charCodeAt(i);
|
|
13
|
+
h = Math.imul(h, 16777619);
|
|
14
|
+
}
|
|
15
|
+
return h >>> 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/palettes.ts
|
|
19
|
+
var palettes = {
|
|
20
|
+
// balanced rainbow (default) — the Tokyo Night editor colours
|
|
21
|
+
tokyoNight: { colors: ["#7aa2f7", "#9ece6a", "#e0af68", "#bb9af7", "#7dcfff", "#f7768e"] },
|
|
22
|
+
// electric high-saturation — reads best on dark backgrounds
|
|
23
|
+
neon: { colors: ["#ff2e97", "#00eaff", "#39ff14", "#b026ff", "#faff00", "#ff5900"] },
|
|
24
|
+
// warm only — crimson, vermilion, orange, gold, rose, deep red
|
|
25
|
+
sunset: { colors: ["#e5383b", "#ff6b35", "#f77f00", "#ffca3a", "#ff5c8a", "#c1121f"] },
|
|
26
|
+
// cool only — navy, teal, sky, seafoam, blue, aqua
|
|
27
|
+
ocean: { colors: ["#1d3557", "#2a9d8f", "#48cae4", "#56e39f", "#4361ee", "#7bdff2"] },
|
|
28
|
+
// greens and earth — pine, leaf, lime-olive, fern, bark, tan
|
|
29
|
+
forest: { colors: ["#386641", "#6a994e", "#a7c957", "#588157", "#b08968", "#dda15e"] },
|
|
30
|
+
// grayscale
|
|
31
|
+
mono: { colors: ["#e6e6e6", "#bdbdbd", "#9e9e9e", "#757575", "#bdbdbd", "#e6e6e6"] }
|
|
32
|
+
};
|
|
33
|
+
var CSS_VARS = {
|
|
34
|
+
colors: ["var(--accent)", "var(--green)", "var(--amber)", "var(--purple)", "var(--cyan)", "var(--red)"]
|
|
35
|
+
};
|
|
36
|
+
function resolvePalette(input) {
|
|
37
|
+
if (input === void 0) return palettes.tokyoNight;
|
|
38
|
+
if (input === "css-vars") return CSS_VARS;
|
|
39
|
+
if (typeof input === "string") return palettes[input];
|
|
40
|
+
if (Array.isArray(input)) return { colors: input };
|
|
41
|
+
return input;
|
|
42
|
+
}
|
|
43
|
+
function pickColor(seed, palette) {
|
|
44
|
+
return palette.colors[(seed >>> 4) % palette.colors.length];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/render.ts
|
|
48
|
+
function resolveCommon(seed, grid, options) {
|
|
49
|
+
const palette = resolvePalette(options?.palette);
|
|
50
|
+
return {
|
|
51
|
+
grid,
|
|
52
|
+
color: pickColor(seed, palette),
|
|
53
|
+
size: options?.size,
|
|
54
|
+
padding: options?.padding ?? 1,
|
|
55
|
+
background: options?.background ?? palette.background,
|
|
56
|
+
title: options?.title
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/invadr.ts
|
|
61
|
+
var INVADR_SPRITES = [
|
|
62
|
+
[24, 60, 126, 219, 255, 36, 90, 165],
|
|
63
|
+
// invader
|
|
64
|
+
[36, 126, 219, 255, 255, 102, 36, 66],
|
|
65
|
+
// crab
|
|
66
|
+
[60, 126, 219, 255, 255, 255, 255, 165],
|
|
67
|
+
// ghost
|
|
68
|
+
[60, 66, 165, 129, 165, 153, 66, 60],
|
|
69
|
+
// smiley
|
|
70
|
+
[24, 24, 126, 255, 255, 102, 102, 231],
|
|
71
|
+
// mech
|
|
72
|
+
[66, 36, 126, 219, 255, 165, 36, 66],
|
|
73
|
+
// alien
|
|
74
|
+
[231, 60, 126, 219, 255, 219, 126, 102],
|
|
75
|
+
// robot
|
|
76
|
+
[129, 195, 255, 219, 255, 255, 255, 165],
|
|
77
|
+
// cat
|
|
78
|
+
[60, 126, 255, 219, 255, 126, 90, 36],
|
|
79
|
+
// skull
|
|
80
|
+
[153, 126, 255, 219, 255, 255, 126, 165],
|
|
81
|
+
// beetle
|
|
82
|
+
[195, 255, 219, 255, 126, 60, 24, 36],
|
|
83
|
+
// owl
|
|
84
|
+
[36, 126, 255, 231, 255, 126, 24, 24],
|
|
85
|
+
// flower
|
|
86
|
+
[24, 60, 126, 255, 255, 126, 60, 24],
|
|
87
|
+
// gem
|
|
88
|
+
[102, 255, 255, 255, 126, 126, 60, 24],
|
|
89
|
+
// heart
|
|
90
|
+
[60, 126, 255, 255, 90, 24, 24, 60],
|
|
91
|
+
// mushroom
|
|
92
|
+
[36, 90, 255, 189, 255, 90, 165, 66]
|
|
93
|
+
// amoeba
|
|
94
|
+
];
|
|
95
|
+
function byteRowsToGrid(rows) {
|
|
96
|
+
return rows.map((row) => {
|
|
97
|
+
const cells = [];
|
|
98
|
+
for (let x = 0; x < 8; x++) cells.push((row & 128 >> x) !== 0);
|
|
99
|
+
return cells;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function invadrGrid(seed) {
|
|
103
|
+
return byteRowsToGrid(INVADR_SPRITES[seed % INVADR_SPRITES.length]);
|
|
104
|
+
}
|
|
105
|
+
function resolveInvadr(id, options) {
|
|
106
|
+
const seed = hashStr(id);
|
|
107
|
+
return resolveCommon(seed, invadrGrid(seed), options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/spawn.ts
|
|
111
|
+
var DENSITY = 0.5;
|
|
112
|
+
function mulberry32(seed) {
|
|
113
|
+
let a = seed >>> 0;
|
|
114
|
+
return function() {
|
|
115
|
+
a = a + 1831565813 | 0;
|
|
116
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
117
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
118
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function spawnGrid(seed, resolution) {
|
|
122
|
+
const n = resolution;
|
|
123
|
+
const half = n / 2;
|
|
124
|
+
const rng = mulberry32(seed);
|
|
125
|
+
const grid = [];
|
|
126
|
+
for (let y = 0; y < n; y++) {
|
|
127
|
+
const row = new Array(n).fill(false);
|
|
128
|
+
for (let x = 0; x < half; x++) {
|
|
129
|
+
const on = rng() < DENSITY;
|
|
130
|
+
row[x] = on;
|
|
131
|
+
row[n - 1 - x] = on;
|
|
132
|
+
}
|
|
133
|
+
grid.push(row);
|
|
134
|
+
}
|
|
135
|
+
return grid;
|
|
136
|
+
}
|
|
137
|
+
function resolveSpawn(id, options) {
|
|
138
|
+
const seed = hashStr(id);
|
|
139
|
+
const resolution = options?.resolution ?? 8;
|
|
140
|
+
return resolveCommon(seed, spawnGrid(seed, resolution), options);
|
|
141
|
+
}
|
|
142
|
+
var InvadrsContext = react.createContext({});
|
|
143
|
+
function InvadrsProvider({ children, ...defaults }) {
|
|
144
|
+
const inherited = react.useContext(InvadrsContext);
|
|
145
|
+
return /* @__PURE__ */ jsxRuntime.jsx(InvadrsContext.Provider, { value: { ...inherited, ...defaults }, children });
|
|
146
|
+
}
|
|
147
|
+
function renderCells(s) {
|
|
148
|
+
const nodes = [];
|
|
149
|
+
const n = s.grid.length;
|
|
150
|
+
if (s.background) {
|
|
151
|
+
nodes.push(
|
|
152
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: -s.padding, y: -s.padding, width: n + s.padding * 2, height: n + s.padding * 2, fill: s.background }, "bg")
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
for (let y = 0; y < n; y++) {
|
|
156
|
+
for (let x = 0; x < n; x++) {
|
|
157
|
+
if (s.grid[y][x]) nodes.push(/* @__PURE__ */ jsxRuntime.jsx("rect", { x, y, width: 1, height: 1 }, `${x},${y}`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return nodes;
|
|
161
|
+
}
|
|
162
|
+
function svgFrom(resolved, className) {
|
|
163
|
+
const n = resolved.grid.length;
|
|
164
|
+
const min = -resolved.padding;
|
|
165
|
+
const span = n + resolved.padding * 2;
|
|
166
|
+
const dims = resolved.size !== void 0 ? { width: resolved.size, height: resolved.size } : {};
|
|
167
|
+
const a11y = resolved.title ? { role: "img" } : { "aria-hidden": true };
|
|
168
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
169
|
+
"svg",
|
|
170
|
+
{
|
|
171
|
+
className,
|
|
172
|
+
viewBox: `${min} ${min} ${span} ${span}`,
|
|
173
|
+
...dims,
|
|
174
|
+
shapeRendering: "crispEdges",
|
|
175
|
+
fill: resolved.color,
|
|
176
|
+
...a11y,
|
|
177
|
+
children: [
|
|
178
|
+
resolved.title ? /* @__PURE__ */ jsxRuntime.jsx("title", { children: resolved.title }) : null,
|
|
179
|
+
renderCells(resolved)
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
function Invadr(props) {
|
|
185
|
+
const { id, className, ...options } = { ...react.useContext(InvadrsContext), ...props };
|
|
186
|
+
return svgFrom(resolveInvadr(id, options), className);
|
|
187
|
+
}
|
|
188
|
+
function Spawn(props) {
|
|
189
|
+
const { id, className, ...options } = { ...react.useContext(InvadrsContext), ...props };
|
|
190
|
+
return svgFrom(resolveSpawn(id, options), className);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
exports.Invadr = Invadr;
|
|
194
|
+
exports.InvadrsProvider = InvadrsProvider;
|
|
195
|
+
exports.Spawn = Spawn;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { S as SpriteOptions } from '../types-C4QooPnZ.cjs';
|
|
4
|
+
|
|
5
|
+
/** Props for the avatar components. `<Invadr>` and `<Spawn>` take the same
|
|
6
|
+
shape; the component you pick chooses the primitive (no `from` prop). */
|
|
7
|
+
type InvadrProps = SpriteOptions & {
|
|
8
|
+
id: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
type SpawnProps = InvadrProps;
|
|
12
|
+
type Defaults = Partial<Omit<InvadrProps, "id">>;
|
|
13
|
+
/** Supply default avatar props (palette, size, padding, …) to descendants.
|
|
14
|
+
Explicit props on an `<Invadr>`/`<Spawn>` override these. */
|
|
15
|
+
declare function InvadrsProvider({ children, ...defaults }: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
} & Defaults): react.JSX.Element;
|
|
18
|
+
/** A hand-drawn creature (one of 16) for an id, as inline SVG. */
|
|
19
|
+
declare function Invadr(props: InvadrProps): ReactNode;
|
|
20
|
+
/** A procedural, unique-per-id creature, as inline SVG. */
|
|
21
|
+
declare function Spawn(props: SpawnProps): ReactNode;
|
|
22
|
+
|
|
23
|
+
export { Invadr, type InvadrProps, InvadrsProvider, Spawn, type SpawnProps };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { S as SpriteOptions } from '../types-C4QooPnZ.js';
|
|
4
|
+
|
|
5
|
+
/** Props for the avatar components. `<Invadr>` and `<Spawn>` take the same
|
|
6
|
+
shape; the component you pick chooses the primitive (no `from` prop). */
|
|
7
|
+
type InvadrProps = SpriteOptions & {
|
|
8
|
+
id: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
type SpawnProps = InvadrProps;
|
|
12
|
+
type Defaults = Partial<Omit<InvadrProps, "id">>;
|
|
13
|
+
/** Supply default avatar props (palette, size, padding, …) to descendants.
|
|
14
|
+
Explicit props on an `<Invadr>`/`<Spawn>` override these. */
|
|
15
|
+
declare function InvadrsProvider({ children, ...defaults }: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
} & Defaults): react.JSX.Element;
|
|
18
|
+
/** A hand-drawn creature (one of 16) for an id, as inline SVG. */
|
|
19
|
+
declare function Invadr(props: InvadrProps): ReactNode;
|
|
20
|
+
/** A procedural, unique-per-id creature, as inline SVG. */
|
|
21
|
+
declare function Spawn(props: SpawnProps): ReactNode;
|
|
22
|
+
|
|
23
|
+
export { Invadr, type InvadrProps, InvadrsProvider, Spawn, type SpawnProps };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { resolveInvadr, resolveSpawn } from '../chunk-5SOVCTHT.js';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var InvadrsContext = createContext({});
|
|
6
|
+
function InvadrsProvider({ children, ...defaults }) {
|
|
7
|
+
const inherited = useContext(InvadrsContext);
|
|
8
|
+
return /* @__PURE__ */ jsx(InvadrsContext.Provider, { value: { ...inherited, ...defaults }, children });
|
|
9
|
+
}
|
|
10
|
+
function renderCells(s) {
|
|
11
|
+
const nodes = [];
|
|
12
|
+
const n = s.grid.length;
|
|
13
|
+
if (s.background) {
|
|
14
|
+
nodes.push(
|
|
15
|
+
/* @__PURE__ */ jsx("rect", { x: -s.padding, y: -s.padding, width: n + s.padding * 2, height: n + s.padding * 2, fill: s.background }, "bg")
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
for (let y = 0; y < n; y++) {
|
|
19
|
+
for (let x = 0; x < n; x++) {
|
|
20
|
+
if (s.grid[y][x]) nodes.push(/* @__PURE__ */ jsx("rect", { x, y, width: 1, height: 1 }, `${x},${y}`));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return nodes;
|
|
24
|
+
}
|
|
25
|
+
function svgFrom(resolved, className) {
|
|
26
|
+
const n = resolved.grid.length;
|
|
27
|
+
const min = -resolved.padding;
|
|
28
|
+
const span = n + resolved.padding * 2;
|
|
29
|
+
const dims = resolved.size !== void 0 ? { width: resolved.size, height: resolved.size } : {};
|
|
30
|
+
const a11y = resolved.title ? { role: "img" } : { "aria-hidden": true };
|
|
31
|
+
return /* @__PURE__ */ jsxs(
|
|
32
|
+
"svg",
|
|
33
|
+
{
|
|
34
|
+
className,
|
|
35
|
+
viewBox: `${min} ${min} ${span} ${span}`,
|
|
36
|
+
...dims,
|
|
37
|
+
shapeRendering: "crispEdges",
|
|
38
|
+
fill: resolved.color,
|
|
39
|
+
...a11y,
|
|
40
|
+
children: [
|
|
41
|
+
resolved.title ? /* @__PURE__ */ jsx("title", { children: resolved.title }) : null,
|
|
42
|
+
renderCells(resolved)
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function Invadr(props) {
|
|
48
|
+
const { id, className, ...options } = { ...useContext(InvadrsContext), ...props };
|
|
49
|
+
return svgFrom(resolveInvadr(id, options), className);
|
|
50
|
+
}
|
|
51
|
+
function Spawn(props) {
|
|
52
|
+
const { id, className, ...options } = { ...useContext(InvadrsContext), ...props };
|
|
53
|
+
return svgFrom(resolveSpawn(id, options), className);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Invadr, InvadrsProvider, Spawn };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** A square pixel grid, row-major; `grid[y][x]` true = filled. */
|
|
2
|
+
type Grid = boolean[][];
|
|
3
|
+
/** A color set (and optional background) used to paint a sprite. */
|
|
4
|
+
type Palette = {
|
|
5
|
+
colors: string[];
|
|
6
|
+
background?: string;
|
|
7
|
+
};
|
|
8
|
+
/** Names of the built-in palettes. */
|
|
9
|
+
type PaletteName = "tokyoNight" | "neon" | "sunset" | "ocean" | "forest" | "mono";
|
|
10
|
+
/** Anything accepted for the `palette` option. `"css-vars"` emits
|
|
11
|
+
`var(--...)` fills so the host theme drives the color. */
|
|
12
|
+
type PaletteInput = PaletteName | "css-vars" | string[] | Palette;
|
|
13
|
+
/** Options shared by both primitives. `resolution` applies to `spawn` only. */
|
|
14
|
+
type SpriteOptions = {
|
|
15
|
+
size?: number;
|
|
16
|
+
palette?: PaletteInput;
|
|
17
|
+
padding?: number;
|
|
18
|
+
background?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
resolution?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type { Grid as G, Palette as P, SpriteOptions as S, PaletteName as a, PaletteInput as b };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** A square pixel grid, row-major; `grid[y][x]` true = filled. */
|
|
2
|
+
type Grid = boolean[][];
|
|
3
|
+
/** A color set (and optional background) used to paint a sprite. */
|
|
4
|
+
type Palette = {
|
|
5
|
+
colors: string[];
|
|
6
|
+
background?: string;
|
|
7
|
+
};
|
|
8
|
+
/** Names of the built-in palettes. */
|
|
9
|
+
type PaletteName = "tokyoNight" | "neon" | "sunset" | "ocean" | "forest" | "mono";
|
|
10
|
+
/** Anything accepted for the `palette` option. `"css-vars"` emits
|
|
11
|
+
`var(--...)` fills so the host theme drives the color. */
|
|
12
|
+
type PaletteInput = PaletteName | "css-vars" | string[] | Palette;
|
|
13
|
+
/** Options shared by both primitives. `resolution` applies to `spawn` only. */
|
|
14
|
+
type SpriteOptions = {
|
|
15
|
+
size?: number;
|
|
16
|
+
palette?: PaletteInput;
|
|
17
|
+
padding?: number;
|
|
18
|
+
background?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
resolution?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type { Grid as G, Palette as P, SpriteOptions as S, PaletteName as a, PaletteInput as b };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "invadrs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Deterministic space-invaders-style pixel avatars from any string.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Matthew Goodwin",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/m4ttheweric/invadrs.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/m4ttheweric/invadrs#readme",
|
|
13
|
+
"bugs": "https://github.com/m4ttheweric/invadrs/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"avatar",
|
|
16
|
+
"identicon",
|
|
17
|
+
"pixel",
|
|
18
|
+
"pixel-art",
|
|
19
|
+
"sprite",
|
|
20
|
+
"space-invaders",
|
|
21
|
+
"svg",
|
|
22
|
+
"deterministic",
|
|
23
|
+
"generative",
|
|
24
|
+
"react"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": ["dist"],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./react": {
|
|
35
|
+
"types": "./dist/react/index.d.ts",
|
|
36
|
+
"import": "./dist/react/index.js",
|
|
37
|
+
"require": "./dist/react/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"test": "bun test",
|
|
43
|
+
"prepare": "tsup"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": { "optional": true }
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/bun": "latest",
|
|
53
|
+
"@types/react": "^19.0.0",
|
|
54
|
+
"@types/react-dom": "^19.0.0",
|
|
55
|
+
"react": "^19.0.0",
|
|
56
|
+
"react-dom": "^19.0.0",
|
|
57
|
+
"tsup": "^8.0.0",
|
|
58
|
+
"typescript": "^5.4.0"
|
|
59
|
+
}
|
|
60
|
+
}
|