@botharness/pixel-avatar 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/dist/index.js ADDED
@@ -0,0 +1,2041 @@
1
+ import { escapeAttribute } from "@botharness/pixel-morph";
2
+
3
+ //#region src/figure.ts
4
+ const N = 32;
5
+ const C = 16;
6
+ const INK$1 = "#2a2230";
7
+ const WHITE = "#ffffff";
8
+ const BLUSH = "#f4879a";
9
+ const MOUTH = "#7a2a38";
10
+ const MOUTH_INSIDE = "#b8415a";
11
+ const TONGUE = "#ef6f84";
12
+ const GOLD = "#efb93f";
13
+ const blank = () => Array.from({ length: N }, () => Array(N).fill(void 0));
14
+ const ellipse = (cx, cy, rx, ry) => (x, y) => ((x + .5 - cx) / rx) ** 2 + ((y + .5 - cy) / ry) ** 2 <= 1;
15
+ function channels$1(color) {
16
+ const n = Number.parseInt(color.slice(1), 16);
17
+ return [
18
+ 16,
19
+ 8,
20
+ 0
21
+ ].map((bit) => n >> bit & 255);
22
+ }
23
+ function hex(values) {
24
+ return `#${values.map((v) => Math.min(255, Math.max(0, Math.round(v))).toString(16).padStart(2, "0")).join("")}`;
25
+ }
26
+ const shade = (color, k) => hex(channels$1(color).map((v) => v * k));
27
+ const mix$1 = (base, tint, k) => {
28
+ const t = channels$1(tint);
29
+ return hex(channels$1(base).map((v, i) => v * (1 - k) + t[i] * k));
30
+ };
31
+ function paint(grid, test, color) {
32
+ for (let y = 0; y < N; y++) for (let x = 0; x < N; x++) if (test(x, y)) grid[y][x] = color;
33
+ }
34
+ function sprite(grid, rows, ox, oy, pal, flip = false) {
35
+ rows.forEach((row, y) => [...row].forEach((code, i) => {
36
+ const c = pal[code];
37
+ const x = ox + (flip ? row.length - 1 - i : i);
38
+ if (c && x >= 0 && x < N && oy + y >= 0 && oy + y < N) grid[oy + y][x] = c;
39
+ }));
40
+ }
41
+ function outline(grid) {
42
+ const out = grid.map((row) => [...row]);
43
+ for (let y = 0; y < N; y++) for (let x = 0; x < N; x++) {
44
+ if (grid[y][x]) continue;
45
+ const n = [
46
+ grid[y]?.[x - 1],
47
+ grid[y]?.[x + 1],
48
+ grid[y - 1]?.[x],
49
+ grid[y + 1]?.[x]
50
+ ].find(Boolean);
51
+ if (n) out[y][x] = mix$1(shade(n, .3), INK$1, .62);
52
+ }
53
+ return out;
54
+ }
55
+ const R = 6;
56
+ const inTile = (x, y) => {
57
+ const clamp = (v) => Math.min(Math.max(v, R), N - R);
58
+ return (x + .5 - clamp(x + .5)) ** 2 + (y + .5 - clamp(y + .5)) ** 2 <= R * R;
59
+ };
60
+ const clip = (g) => g.map((row, y) => row.map((c, x) => inTile(x, y) ? c : void 0));
61
+ function rects(grid) {
62
+ let m = "";
63
+ for (let y = 0; y < N; y++) {
64
+ let x = 0;
65
+ while (x < N) {
66
+ const c = grid[y][x];
67
+ if (!c) {
68
+ x++;
69
+ continue;
70
+ }
71
+ let w = 1;
72
+ while (x + w < N && grid[y][x + w] === c) w++;
73
+ m += `<rect x="${x}" y="${y}" width="${w}" height="1" fill="${escapeAttribute(c)}"/>`;
74
+ x += w;
75
+ }
76
+ }
77
+ return m;
78
+ }
79
+ const FACE = {
80
+ round: (s) => ellipse(C + s, 16.6, 8.6, 7.6),
81
+ oval: (s) => ellipse(C + s, 16.6, 8, 8),
82
+ chubby: (s) => ellipse(C + s, 17, 9.4, 7.2),
83
+ long: (s) => ellipse(C + s, 16.6, 7.8, 8.4),
84
+ square: (s) => (x, y) => {
85
+ if (y < 8 || y > 23) return false;
86
+ const dx = Math.abs(x + .5 - C - s);
87
+ const r = 3;
88
+ if (y <= 20) return dx <= 8.5;
89
+ return dx <= 8.5 - (y - 20) ** 1.3 * 1.1 + .3 - (y === 23 ? 1 : 0) || dx <= 8.5 - r;
90
+ },
91
+ heart: (s) => taper(ellipse(C + s, 15.8, 9, 7.4), 16.5, 9, 2.6, 24.4, s),
92
+ vchin: (s) => taper(ellipse(C + s, 15.8, 8.6, 7.4), 17.5, 8.6, 2.2, 24.4, s),
93
+ diamond: (s) => taper((x, y) => y >= 9 && Math.abs(x + .5 - C - s) <= 6.4 + (y - 9) * .45, 15.8, 9.2, 2.6, 24.4, s)
94
+ };
95
+ function taper(top, from, width, end, chin, s) {
96
+ return (x, y) => {
97
+ const cy = y + .5;
98
+ if (cy <= from) return top(x, y);
99
+ if (cy > chin) return false;
100
+ const t = (cy - from) / (chin - from);
101
+ return Math.abs(x + .5 - C - s) <= width - (width - end) * t ** 1.5;
102
+ };
103
+ }
104
+ const EYES = {
105
+ round: [
106
+ "KKKK",
107
+ "DWDD",
108
+ "IDDI",
109
+ "LIIL"
110
+ ],
111
+ dot: [
112
+ "....",
113
+ ".KK.",
114
+ ".KK.",
115
+ "...."
116
+ ],
117
+ sparkle: [
118
+ "KKKK",
119
+ "DWDW",
120
+ "IDDI",
121
+ "LIWL"
122
+ ],
123
+ lashes: [
124
+ "KKKKK",
125
+ "K.DWD",
126
+ "..IDD",
127
+ "..LII"
128
+ ],
129
+ sleepy: [
130
+ "....",
131
+ "KKKK",
132
+ "IDDI",
133
+ "LIIL"
134
+ ],
135
+ happy: [
136
+ "....",
137
+ ".KK.",
138
+ "K..K",
139
+ "...."
140
+ ],
141
+ wink: [
142
+ "KKKK",
143
+ "DWDD",
144
+ "IDDI",
145
+ "LIIL"
146
+ ],
147
+ sharp: [
148
+ "KK..",
149
+ ".KKK",
150
+ ".DWD",
151
+ "..LI"
152
+ ]
153
+ };
154
+ const CLOSED = [
155
+ "....",
156
+ "....",
157
+ "KKKK",
158
+ "...."
159
+ ];
160
+ const BROWS = {
161
+ soft: [".BBB"],
162
+ thick: ["BBBB"],
163
+ raised: [".BB."],
164
+ angry: ["BB..", "..BB"],
165
+ worried: ["..BB", "BB.."],
166
+ none: []
167
+ };
168
+ const MOUTHS = {
169
+ smile: ["K..K", ".KK."],
170
+ grin: ["KWWK", ".KK."],
171
+ open: ["KKK", "MTM"],
172
+ flat: [".KK."],
173
+ smirk: ["...K", ".KK."],
174
+ cat: ["K.K.K", ".K.K."],
175
+ tongue: ["KKKK", ".TT."],
176
+ o: [
177
+ ".K.",
178
+ "KMK",
179
+ ".K."
180
+ ]
181
+ };
182
+ function hair(recipe, s, b) {
183
+ const none = () => false;
184
+ const cap = ellipse(C + s, 14, 11, 10.6);
185
+ const tip = (x, depth = 1) => [
186
+ 0,
187
+ depth,
188
+ depth + 1,
189
+ depth
190
+ ][(x + 40 - s) % 4];
191
+ const bangs = (line, depth = 1, sides = 22) => (x, y) => {
192
+ if (!cap(x, Math.min(y, 13))) return false;
193
+ const dx = x + .5 - C - s;
194
+ if (Math.abs(dx) >= 7) return y <= sides - [
195
+ 0,
196
+ 1,
197
+ 0,
198
+ 2
199
+ ][(x + 40 - s) % 4];
200
+ return y <= line + tip(x, depth) - (Math.abs(dx) > 6 ? 0 : 0);
201
+ };
202
+ const longBack = (bottom, width = 11.4) => (x, y) => ellipse(C + b, 14, width + .4, 11)(x, y) && y <= 16 || y > 15 && y <= bottom && Math.abs(x + .5 - C - b) <= width - (y > bottom - 2 ? 1 : 0);
203
+ switch (recipe.hair) {
204
+ case "none": return {
205
+ back: none,
206
+ front: none
207
+ };
208
+ case "buzz": return {
209
+ back: none,
210
+ front: (x, y) => cap(x, y) && y <= 7
211
+ };
212
+ case "crop": return {
213
+ back: none,
214
+ front: bangs(10, 1, 15)
215
+ };
216
+ case "sweep": return {
217
+ back: none,
218
+ front: (x, y) => cap(x, Math.min(y, 13)) && (y <= 9 + Math.max(0, (x - 6 - s) * .45) || Math.abs(x + .5 - C - s) >= 7 && y <= 16)
219
+ };
220
+ case "spiky": return {
221
+ back: none,
222
+ front: (x, y) => cap(x, Math.max(y, 6)) && y >= [
223
+ 1,
224
+ 3,
225
+ 5,
226
+ 3
227
+ ][(x + 40 - s) % 4] && bangs(11, 2, 15)(x, Math.max(y, 6))
228
+ };
229
+ case "curly": {
230
+ const bumps = Array.from({ length: 9 }, (_, i) => {
231
+ const a = Math.PI * (1.02 + i * .96 / 8);
232
+ return [C + s + 11.5 * Math.cos(a), 14 + 11 * Math.sin(a)];
233
+ });
234
+ return {
235
+ back: none,
236
+ front: (x, y) => bangs(11, 1, 18)(x, y) || y <= 18 && bumps.some(([bx, by]) => (x + .5 - bx) ** 2 + (y + .5 - by) ** 2 <= 6.5)
237
+ };
238
+ }
239
+ case "mohawk": return {
240
+ back: none,
241
+ front: (x, y) => Math.abs(x + .5 - C - s) <= 2 && y >= 1 && y <= 9
242
+ };
243
+ case "bob": return {
244
+ back: longBack(25),
245
+ front: bangs(13, 1, 24)
246
+ };
247
+ case "long": return {
248
+ back: longBack(31),
249
+ front: bangs(13, 1, 26)
250
+ };
251
+ case "bun": return {
252
+ back: none,
253
+ front: (x, y) => bangs(12, 1, 18)(x, y) || ellipse(C + s, 2.6, 4, 2.6)(x, y)
254
+ };
255
+ case "pigtails": return {
256
+ back: (x, y) => ellipse(C + s - 11.5, 21, 3, 4.4)(x, y) || ellipse(C + s + 11.5, 21, 3, 4.4)(x, y),
257
+ front: bangs(13, 1, 21),
258
+ ties: [[C + s - 12, 16], [C + s + 11, 16]]
259
+ };
260
+ case "afro": return {
261
+ back: ellipse(C + b, 12, 15, 11.5),
262
+ front: (x, y) => cap(x, y) && y <= 8
263
+ };
264
+ case "twintails":
265
+ case "drills": return {
266
+ back: (x, y) => y >= 8 && (ellipse(C + s - 13.2, 20, 2.3, 12)(x, y) || ellipse(C + s + 13.2, 20, 2.3, 12)(x, y)),
267
+ front: bangs(13, 1, 24),
268
+ ties: [
269
+ [C + s - 13, 9],
270
+ [C + s - 12, 9],
271
+ [C + s + 11, 9],
272
+ [C + s + 12, 9]
273
+ ],
274
+ bands: recipe.hair === "drills"
275
+ };
276
+ case "ponytail": return {
277
+ back: (x, y) => ellipse(C + s + 11.5, 19, 3, 9)(x, y) && y >= 8,
278
+ front: bangs(12, 1, 20),
279
+ ties: [[C + s + 10, 9], [C + s + 11, 9]]
280
+ };
281
+ case "sidetail": return {
282
+ back: (x, y) => ellipse(C + s - 11.5, 21, 3, 8)(x, y) && y >= 12,
283
+ front: (x, y) => cap(x, Math.min(y, 13)) && (y <= 9 + Math.max(0, (C + s - x) * .45) || Math.abs(x + .5 - C - s) >= 7 && y <= 18),
284
+ ties: [[C + s - 13, 13], [C + s - 12, 13]]
285
+ };
286
+ case "hime": return {
287
+ back: longBack(31),
288
+ front: (x, y) => cap(x, Math.min(y, 13)) && (y <= 12 || Math.abs(x + .5 - C - s) >= 6.5 && y <= 24)
289
+ };
290
+ case "odango": return {
291
+ back: none,
292
+ front: (x, y) => bangs(12, 1, 18)(x, y) || ellipse(C + s - 8, 3.5, 3.3, 3)(x, y) || ellipse(C + s + 8, 3.5, 3.3, 3)(x, y)
293
+ };
294
+ case "messy": return {
295
+ back: none,
296
+ front: (x, y) => cap(x, Math.max(y, 4)) && y >= [
297
+ 2,
298
+ 0,
299
+ 3,
300
+ 1,
301
+ 2
302
+ ][(x + 50 - s) % 5] && bangs(11, 2, 17)(x, Math.max(y, 4))
303
+ };
304
+ case "wavy": return {
305
+ back: (x, y) => longBack(30)(x, y) || y >= 16 && y <= 30 && Math.abs(x + .5 - C - b) <= 12.4 + Math.sin(y * .9) * .9,
306
+ front: bangs(12, 2, 25)
307
+ };
308
+ case "braids": return {
309
+ back: none,
310
+ front: (x, y) => bangs(13, 1, 20)(x, y) || [C + s - 9.5, C + s + 9.5].some((bx) => y >= 20 && y <= 30 && Math.abs(x + .5 - bx) <= 1.6 - ((y + 1) % 3 === 0 ? .6 : 0)),
311
+ ties: [[C + s - 10, 30], [C + s + 9, 30]]
312
+ };
313
+ case "wolf": return {
314
+ back: (x, y) => longBack(24, 11.8)(x, y) && (y < 20 || (x + y) % 3 !== 0),
315
+ front: (x, y) => cap(x, Math.max(y, 5)) && y >= [
316
+ 1,
317
+ 2,
318
+ 0,
319
+ 2
320
+ ][(x + 40 - s) % 4] && bangs(12, 2, 21)(x, Math.max(y, 5))
321
+ };
322
+ case "ahoge": return {
323
+ back: longBack(23),
324
+ front: (x, y) => bangs(13, 1, 22)(x, y) || [
325
+ [0, 0],
326
+ [1, 0],
327
+ [1, 1],
328
+ [2, 1],
329
+ [2, 2],
330
+ [1, 2]
331
+ ].some(([ax, ay]) => x === Math.round(C + s) + ax && y === ay + 1)
332
+ };
333
+ }
334
+ }
335
+ function detailedHair(recipe, s, b) {
336
+ const of = (style) => hair({
337
+ ...recipe,
338
+ hair: style ?? "none"
339
+ }, s, b);
340
+ const bangs = of(recipe.bangs);
341
+ const side = of(recipe.sideHair);
342
+ const back = of(recipe.backHair);
343
+ const length = (recipe.hairLength ?? 0) * 2;
344
+ const lengthen = (mask) => (x, y) => mask(x, y > 18 ? Math.round(y - length) : y);
345
+ return {
346
+ front: (x, y) => y <= 7 ? bangs.front(x, y) || back.front(x, y) : Math.abs(x + .5 - C - s) < 7 ? bangs.front(x, y) : lengthen(side.front)(x, y),
347
+ back: length === 0 ? back.back : lengthen(back.back),
348
+ ...back.ties ? { ties: back.ties } : {},
349
+ ...back.bands ? { bands: back.bands } : {}
350
+ };
351
+ }
352
+ function outfit(recipe, g, s) {
353
+ const shirt = recipe.shirtColor;
354
+ const skin = recipe.skinColor;
355
+ const dark = shade(shirt, .75);
356
+ const light = mix$1(shirt, WHITE, .8);
357
+ const half = (y) => 6.5 + (y - 24) * 1.15;
358
+ const torso = (x, y) => y >= 24 && Math.abs(x + .5 - C - s * .5) <= half(y);
359
+ const dx = (x) => x + .5 - C - s * .5;
360
+ paint(g, (x, y) => y >= 22 && y <= 25 && Math.abs(dx(x)) <= 2.5, shade(skin, .85));
361
+ paint(g, torso, shirt);
362
+ const v = (depth, w) => (x, y) => y >= 24 && y <= 24 + depth && Math.abs(dx(x)) <= w - (y - 24) * (w / (depth + 1));
363
+ switch (recipe.outfit) {
364
+ case "tee":
365
+ paint(g, v(1, 2.5), shade(skin, .85));
366
+ break;
367
+ case "shirttie":
368
+ paint(g, v(3, 4), light);
369
+ paint(g, (x, y) => y >= 24 && Math.abs(dx(x)) <= .5 && y <= 28, "#d22a5a");
370
+ break;
371
+ case "hoodie":
372
+ paint(g, (x, y) => torso(x, y) && y === 24 && Math.abs(dx(x)) >= 3, dark);
373
+ paint(g, (x, y) => y >= 25 && y <= 27 && (Math.abs(dx(x) + 1.5) <= .5 || Math.abs(dx(x) - 1.5) <= .5), WHITE);
374
+ break;
375
+ case "turtleneck":
376
+ paint(g, (x, y) => y >= 22 && y <= 25 && Math.abs(dx(x)) <= 3, shade(shirt, .88));
377
+ break;
378
+ case "sailor":
379
+ paint(g, (x, y) => torso(x, y) && y <= 26 && Math.abs(dx(x)) >= 2, "#2f3a5a");
380
+ paint(g, (x, y) => torso(x, y) && y === 26 && Math.abs(dx(x)) >= 3, WHITE);
381
+ sprite(g, ["R.R", ".R."], Math.round(C + s * .5) - 2, 26, { R: "#e2565f" });
382
+ break;
383
+ case "blazer":
384
+ paint(g, v(4, 4), light);
385
+ paint(g, (x, y) => torso(x, y) && y >= 24 && Math.abs(Math.abs(dx(x)) - (4 - (y - 24) * .8)) <= .5, dark);
386
+ sprite(g, ["G"], Math.round(C + s * .5), 28, { G: GOLD });
387
+ break;
388
+ case "overalls":
389
+ paint(g, torso, light);
390
+ paint(g, (x, y) => torso(x, y) && (y >= 27 || Math.abs(Math.abs(dx(x)) - 4) <= .6), shirt);
391
+ sprite(g, ["G....G"], Math.round(C + s * .5) - 3, 27, { G: GOLD });
392
+ break;
393
+ case "dress":
394
+ paint(g, (x, y) => torso(x, y) && y <= 25 && Math.abs(dx(x)) <= 4.5, WHITE);
395
+ paint(g, (x, y) => torso(x, y) && y === 25 && Math.abs(dx(x)) <= 4.5 && x % 2 === 0, shade(WHITE, .86));
396
+ sprite(g, [
397
+ "RR.RR",
398
+ ".RRR.",
399
+ "R...R"
400
+ ], Math.round(C + s * .5) - 2, 25, { R: "#e5566a" });
401
+ paint(g, (x, y) => torso(x, y) && y === 29, mix$1(shirt, WHITE, .5));
402
+ break;
403
+ case "kimono":
404
+ paint(g, (x, y) => torso(x, y) && y <= 28 && Math.abs(Math.abs(dx(x)) - (y - 23.5)) <= .6, WHITE);
405
+ paint(g, (x, y) => torso(x, y) && y >= 28, "#e2565f");
406
+ paint(g, (x, y) => torso(x, y) && y === 28 && x % 3 === 0, GOLD);
407
+ paint(g, (x, y) => torso(x, y) && y <= 27 && (x * 3 + y * 2) % 7 === 0, mix$1(shirt, WHITE, .45));
408
+ break;
409
+ case "cardigan":
410
+ paint(g, v(5, 3.5), light);
411
+ paint(g, (x, y) => torso(x, y) && Math.abs(Math.abs(dx(x)) - 2) <= .5 && y >= 25, dark);
412
+ sprite(g, [
413
+ "W",
414
+ ".",
415
+ "W",
416
+ ".",
417
+ "W"
418
+ ], Math.round(C + s * .5) - 3, 25, { W: WHITE });
419
+ paint(g, (x, y) => torso(x, y) && y === 29, dark);
420
+ break;
421
+ case "maid":
422
+ paint(g, (x, y) => torso(x, y), "#2f2a3a");
423
+ paint(g, (x, y) => torso(x, y) && y >= 25 && Math.abs(dx(x)) <= 4 - (y - 25) * .2, WHITE);
424
+ paint(g, (x, y) => y === 24 && Math.abs(dx(x)) <= 4.5, WHITE);
425
+ sprite(g, ["RR.RR", ".RRR."], Math.round(C + s * .5) - 2, 24, { R: shirt });
426
+ break;
427
+ case "jacket":
428
+ paint(g, v(6, 4.5), WHITE);
429
+ paint(g, (x, y) => torso(x, y) && Math.abs(Math.abs(dx(x)) - (4.5 - (y - 24) * .6)) <= .6, dark);
430
+ paint(g, (x, y) => torso(x, y) && y >= 27 && Math.abs(dx(x)) <= .5, "#9aa3ad");
431
+ paint(g, (x, y) => torso(x, y) && y === 28 && Math.abs(dx(x)) >= half(y) - 2.5, mix$1(shirt, WHITE, .6));
432
+ break;
433
+ }
434
+ const neck = shade(skin, .85);
435
+ for (let y = 24; y < N; y++) for (let x = 0; x < N; x++) {
436
+ const cell = g[y][x];
437
+ if (!cell || cell === neck || !torso(x, y)) continue;
438
+ const d = dx(x);
439
+ const edge = half(y) - Math.abs(d);
440
+ if (y === 24 && Math.abs(d) <= 4.5) g[y][x] = shade(cell, .78);
441
+ else if (y >= 26 && edge >= 1.6 && edge < 2.6) g[y][x] = shade(cell, .82);
442
+ else if (d > 0 && edge < 1.6) g[y][x] = shade(cell, .84);
443
+ else if (d < 0 && edge < 1.6 && y <= 26) g[y][x] = mix$1(cell, WHITE, .28);
444
+ else if (d < -2 && y <= 25) g[y][x] = mix$1(cell, WHITE, .14);
445
+ }
446
+ }
447
+ function accessory(recipe, g, s, face) {
448
+ const shirt = recipe.shirtColor;
449
+ const cap = ellipse(C + s, 14, 11.6, 11.2);
450
+ switch (recipe.accessory) {
451
+ case "none": return;
452
+ case "beanie":
453
+ paint(g, (x, y) => cap(x, y) && y <= 8, shirt);
454
+ paint(g, (x, y) => cap(x, y) && (y === 8 || y === 9), shade(shirt, .78));
455
+ paint(g, ellipse(C + s, 1.6, 2.2, 1.8), WHITE);
456
+ return;
457
+ case "cap":
458
+ paint(g, (x, y) => cap(x, y) && y <= 8, shirt);
459
+ paint(g, (x, y) => y === 9 && x + .5 >= C + s - 10 && x + .5 <= C + s + 13, shade(shirt, .72));
460
+ return;
461
+ case "headphones": {
462
+ const band = (x, y) => ellipse(C + s, 14, 12, 11.6)(x, y) && !ellipse(C + s, 14, 10.8, 10.4)(x, y) && y <= 13;
463
+ paint(g, band, "#5d5b66");
464
+ paint(g, (x, y) => y >= 12 && y <= 19 && (Math.abs(x + .5 - (C + s - 11)) <= 1.6 || Math.abs(x + .5 - (C + s + 11)) <= 1.6), "#e25d6a");
465
+ return;
466
+ }
467
+ case "flower":
468
+ sprite(g, [
469
+ ".P.P.",
470
+ "PPYPP",
471
+ ".PPP."
472
+ ], Math.round(C + s) + 6, 3, {
473
+ P: "#f59fba",
474
+ Y: GOLD
475
+ });
476
+ return;
477
+ case "bow":
478
+ sprite(g, [
479
+ "RR.RR",
480
+ "RRrRR",
481
+ "RR.RR"
482
+ ], Math.round(C + s) + 6, 3, {
483
+ R: "#e5566a",
484
+ r: "#b83a50"
485
+ });
486
+ return;
487
+ case "earring": {
488
+ const xs = [...Array(N).keys()].filter((x) => face(x, 19));
489
+ if (xs.length) {
490
+ sprite(g, ["G", "G"], xs[0] - 1, 20, { G: GOLD });
491
+ sprite(g, ["G", "G"], xs.at(-1) + 1, 20, { G: GOLD });
492
+ }
493
+ return;
494
+ }
495
+ case "crown":
496
+ sprite(g, [
497
+ "G.G.G.G",
498
+ "GGGGGGG",
499
+ "GrGbGrG"
500
+ ], Math.round(C + s) - 3, 0, {
501
+ G: GOLD,
502
+ r: "#e5566a",
503
+ b: "#5a9be0"
504
+ });
505
+ return;
506
+ case "halo":
507
+ paint(g, (x, y) => ellipse(C + s, 1.6, 6.5, 1.8)(x, y) && !ellipse(C + s, 1.6, 4.4, .8)(x, y), GOLD);
508
+ return;
509
+ case "catears": {
510
+ const ear = [
511
+ "X...",
512
+ "XX..",
513
+ "XPX.",
514
+ "XXXX"
515
+ ];
516
+ sprite(g, ear, Math.round(C + s) - 9, 2, {
517
+ X: recipe.hairColor,
518
+ P: "#f4a3b5"
519
+ });
520
+ sprite(g, ear, Math.round(C + s) + 5, 2, {
521
+ X: recipe.hairColor,
522
+ P: "#f4a3b5"
523
+ }, true);
524
+ return;
525
+ }
526
+ case "hairclip":
527
+ sprite(g, ["CC.CC", ".CCC."], Math.round(C + s) + 3, 9, { C: "#e5566a" });
528
+ return;
529
+ case "horns": {
530
+ const horn = [
531
+ ".H",
532
+ "HH",
533
+ "Hh"
534
+ ];
535
+ sprite(g, horn, Math.round(C + s) - 8, 1, {
536
+ H: "#5b3a6e",
537
+ h: "#7d5694"
538
+ });
539
+ sprite(g, horn, Math.round(C + s) + 6, 1, {
540
+ H: "#5b3a6e",
541
+ h: "#7d5694"
542
+ }, true);
543
+ return;
544
+ }
545
+ case "beret":
546
+ paint(g, (x, y) => ellipse(C + s + 1.5, 4.6, 10.5, 3.6)(x, y), shirt);
547
+ paint(g, (x, y) => ellipse(C + s + 1.5, 4.6, 10.5, 3.6)(x, y) && y >= 6, shade(shirt, .78));
548
+ paint(g, (x, y) => ellipse(C + s + 1.5, 4.6, 10.5, 3.6)(x, y) && y <= 2 && x + .5 < C + s, mix$1(shirt, WHITE, .3));
549
+ sprite(g, ["S"], Math.round(C + s) + 1, 0, { S: shade(shirt, .6) });
550
+ return;
551
+ case "ribbon":
552
+ sprite(g, [
553
+ "RRR.RRR",
554
+ "RrRRRrR",
555
+ "RRR.RRR",
556
+ ".R...R."
557
+ ], Math.round(C + s) - 3, 0, {
558
+ R: "#e5566a",
559
+ r: "#f49aa8"
560
+ });
561
+ return;
562
+ case "headband":
563
+ paint(g, (x, y) => ellipse(C + s, 14, 11.2, 10.8)(x, y) && !ellipse(C + s, 14.2, 10.2, 9.8)(x, y) && y <= 8, shirt);
564
+ paint(g, (x, y) => ellipse(C + s, 14, 11.2, 10.8)(x, y) && !ellipse(C + s, 14.2, 10.2, 9.8)(x, y) && y <= 5, mix$1(shirt, WHITE, .35));
565
+ return;
566
+ case "bunnyears": {
567
+ const ear = [
568
+ "WW",
569
+ "WP",
570
+ "WP",
571
+ "WP",
572
+ "WW"
573
+ ];
574
+ sprite(g, ear, Math.round(C + s) - 6, 0, {
575
+ W: "#f4f1ec",
576
+ P: "#f4a3b5"
577
+ });
578
+ sprite(g, ear, Math.round(C + s) + 4, 0, {
579
+ W: "#f4f1ec",
580
+ P: "#f4a3b5"
581
+ }, true);
582
+ return;
583
+ }
584
+ case "horseears": {
585
+ const ear = [
586
+ ".X.",
587
+ "XPX",
588
+ "XPX",
589
+ "XXX"
590
+ ];
591
+ sprite(g, ear, Math.round(C + s) - 9, 1, {
592
+ X: shade(recipe.hairColor, .9),
593
+ P: "#f4a3b5"
594
+ });
595
+ sprite(g, ear, Math.round(C + s) + 6, 1, {
596
+ X: shade(recipe.hairColor, .9),
597
+ P: "#f4a3b5"
598
+ });
599
+ sprite(g, ["BB", "BB"], Math.round(C + s) + 3, 7, { B: "#5a9be0" });
600
+ return;
601
+ }
602
+ case "flowercrown":
603
+ for (const [fx, color] of [
604
+ [-8, "#f59fba"],
605
+ [-4, GOLD],
606
+ [0, "#f4f1ec"],
607
+ [4, "#f59fba"],
608
+ [8, GOLD]
609
+ ]) sprite(g, [
610
+ ".F.",
611
+ "FCF",
612
+ ".F."
613
+ ], Math.round(C + s) + fx - 1, 4 + Math.round(Math.abs(fx) / 4), {
614
+ F: color,
615
+ C: color === GOLD ? "#e2565f" : GOLD
616
+ });
617
+ paint(g, (x, y) => y === 6 + Math.round(Math.abs(x + .5 - C - s) / 4) && Math.abs(x + .5 - C - s) <= 9 && x % 2 === 1, "#5aa36b");
618
+ return;
619
+ case "witch":
620
+ paint(g, (x, y) => y >= 6 && y <= 7 && Math.abs(x + .5 - C - s) <= 13, "#3a2f52");
621
+ paint(g, (x, y) => y <= 6 && Math.abs(x + .5 - C - s - (6 - y) * .5) <= 1 + y * .9, "#4b3d6b");
622
+ paint(g, (x, y) => y === 5 && Math.abs(x + .5 - C - s - .5) <= 5.2, "#efb93f");
623
+ return;
624
+ case "pins":
625
+ sprite(g, [
626
+ "Y.Y",
627
+ ".Y.",
628
+ "Y.Y",
629
+ "...",
630
+ "B.B",
631
+ ".B.",
632
+ "B.B"
633
+ ], Math.round(C + s) + 6, 7, {
634
+ Y: GOLD,
635
+ B: "#5a9be0"
636
+ });
637
+ return;
638
+ }
639
+ }
640
+ function pixelTileColor(hair) {
641
+ const [r, g, b] = channels$1(hair).map((v) => v / 255);
642
+ const max = Math.max(r, g, b);
643
+ const min = Math.min(r, g, b);
644
+ const lightness = (max + min) / 2;
645
+ const chroma = max - min;
646
+ const saturation = chroma === 0 ? 0 : chroma / (1 - Math.abs(2 * lightness - 1));
647
+ if (saturation < .18 || lightness > .86 || lightness < .16) return "#ece8e1";
648
+ const hue = max === r ? ((g - b) / chroma + 6) % 6 : max === g ? (b - r) / chroma + 2 : (r - g) / chroma + 4;
649
+ const s = Math.min(.55, Math.max(.35, saturation * .6));
650
+ const l = .88;
651
+ const f = (n) => {
652
+ const k = (n + hue * 2) % 12;
653
+ return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));
654
+ };
655
+ return hex([
656
+ f(0),
657
+ f(8),
658
+ f(4)
659
+ ].map((v) => v * 255));
660
+ }
661
+ function pixelFigure(recipe, yawDeg) {
662
+ const yaw = yawDeg * Math.PI / 180;
663
+ const sinY = Math.sin(yaw);
664
+ const s = Math.round(sinY * 3);
665
+ const fs = Math.round(sinY * 10);
666
+ const b = -Math.round(sinY * 3.5);
667
+ const turned = Math.abs(sinY) >= .3;
668
+ const d = Math.sign(sinY);
669
+ const skin = recipe.skinColor;
670
+ const hairColor = recipe.hairColor;
671
+ const front = FACE[recipe.head](s * .5);
672
+ const face = turned ? (x, y) => {
673
+ const dx = x + .5 - C;
674
+ const tuck = y >= 17 && d * dx > 4 ? Math.min(2, (y - 16) * .35) : 0;
675
+ return front(x - Math.round(d * (1 + Math.max(0, y - 20) * .4) - d * tuck), y);
676
+ } : front;
677
+ const masks = recipe.bangs === void 0 ? hair(recipe, s, b) : detailedHair(recipe, s, b);
678
+ const body = blank();
679
+ outfit(recipe, body, s);
680
+ const back = blank();
681
+ paint(back, masks.back, shade(hairColor, .82));
682
+ if (masks.bands) paint(back, (x, y) => masks.back(x, y) && (y + Math.floor(x / 2)) % 4 === 0, shade(hairColor, .68));
683
+ const head = blank();
684
+ paint(head, face, skin);
685
+ const earY = 15;
686
+ const edge = (side) => {
687
+ const xs = [...Array(N).keys()].filter((x) => face(x, earY));
688
+ return side < 0 ? xs[0] - 1 : xs.at(-1) + 1;
689
+ };
690
+ if (!turned) for (const ex of [-1, 1]) sprite(head, [
691
+ "E",
692
+ "E",
693
+ "e"
694
+ ], edge(ex), earY, {
695
+ E: skin,
696
+ e: shade(skin, .86)
697
+ });
698
+ else sprite(head, [
699
+ "EE",
700
+ "Ee",
701
+ "Ee",
702
+ "E."
703
+ ], d > 0 ? edge(-d) - 1 : edge(-d), earY, {
704
+ E: skin,
705
+ e: shade(skin, .8)
706
+ });
707
+ paint(head, masks.front, hairColor);
708
+ for (const [x, y] of masks.ties ?? []) sprite(back, ["T"], Math.round(x), y, { T: shade(recipe.shirtColor, .8) });
709
+ accessory(recipe, head, s, face);
710
+ const shadeHair = (g) => {
711
+ const out = g.map((r) => [...r]);
712
+ const cells = g.flatMap((row, y) => row.flatMap((c, x) => c === hairColor ? [[x, y]] : []));
713
+ const xs = cells.map(([x]) => x);
714
+ const ys = cells.map(([, y]) => y);
715
+ const left = Math.min(...xs);
716
+ const right = Math.max(...xs);
717
+ const top = Math.min(...ys);
718
+ const mid = (left + right) / 2;
719
+ const span = Math.max(1, (right - left) / 2);
720
+ const light = mix$1(hairColor, WHITE, .2);
721
+ const dark = shade(hairColor, .8);
722
+ const middle = shade(hairColor, .9);
723
+ for (const [x, y] of cells) {
724
+ const side = (x + .5 - mid) / span + (y - top) * .025;
725
+ if (side > .72) out[y][x] = dark;
726
+ else if (side > .42) out[y][x] = middle;
727
+ else if (side < -.2 && y <= top + 4) out[y][x] = light;
728
+ }
729
+ for (const [x, y] of cells) if ((g[y][x - 1] === skin || g[y][x + 1] === skin) && y >= 14) out[y][x] = shade(hairColor, .74);
730
+ else if (Math.abs(x + .5 - C - s) >= 7.5 && y >= 14 && (x + 40 - s) % 3 === 0) out[y][x] = shade(out[y][x], .86);
731
+ for (let y = 1; y < N; y++) for (let x = 0; x < N; x++) {
732
+ if (g[y][x] !== skin) continue;
733
+ if (g[y - 1][x] === hairColor) out[y][x] = mix$1(shade(skin, .84), BLUSH, .12);
734
+ else if (x + 1 < N && g[y][x + 1] !== skin && g[y][x + 1] !== void 0 && x > C + s) out[y][x] = shade(skin, .9);
735
+ else if (x + 1 < N && g[y][x + 1] === void 0 && x > C + s) out[y][x] = shade(skin, .9);
736
+ }
737
+ for (let y = 0; y < N - 1; y++) for (let x = 0; x < N; x++) if (g[y][x] === hairColor && g[y + 1][x] === skin) out[y][x] = shade(hairColor, .62);
738
+ for (let x = 0; x < N; x++) for (let y = 3; y < N; y++) if (g[y][x] === hairColor && g[y + 1]?.[x] === skin && (x + 40 - s) % 4 === 0) {
739
+ for (let k = 1; k <= 3; k++) if (g[y - k]?.[x] === hairColor) out[y - k][x] = shade(hairColor, .72);
740
+ }
741
+ const ring = mix$1(hairColor, WHITE, .45);
742
+ for (let x = Math.round(C + s) - 7; x <= Math.round(C + s) + 3; x++) {
743
+ const first = g.findIndex((row) => row[x] === hairColor);
744
+ if (first < 0) continue;
745
+ const y = first + 2 + Math.round(Math.abs(x + .5 - (C + s - 2)) / 4);
746
+ if (g[y]?.[x] === hairColor && x % 3 !== 0) out[y][x] = ring;
747
+ }
748
+ return out;
749
+ };
750
+ const figure = blank();
751
+ for (const layer of [
752
+ back,
753
+ body,
754
+ shadeHair(head)
755
+ ]) for (let y = 0; y < N; y++) for (let x = 0; x < N; x++) if (layer[y][x]) figure[y][x] = layer[y][x];
756
+ const features = blank();
757
+ const eyes = blank();
758
+ const closed = blank();
759
+ const glasses = blank();
760
+ const sp = recipe.spacing ?? 0;
761
+ const h = recipe.height ?? 0;
762
+ const eyeTop = 15 + h;
763
+ const eyeW = (side) => turned && Math.sign(fs) === side ? 3 : 4;
764
+ const eyeX = (side) => {
765
+ const w = eyeW(side);
766
+ return side < 0 ? Math.round(C + fs * .6) - 6 - sp - (w === 3 ? -1 : 0) : Math.round(C + fs * .6) + 2 + sp;
767
+ };
768
+ const left = {
769
+ x: eyeX(-1),
770
+ w: eyeW(-1)
771
+ };
772
+ const right = {
773
+ x: eyeX(1),
774
+ w: eyeW(1)
775
+ };
776
+ const fit = (rows, w) => rows.map((r) => r.slice(Math.max(0, r.length - w)));
777
+ const pal = {
778
+ K: INK$1,
779
+ W: WHITE,
780
+ D: shade(recipe.eyeColor, .55),
781
+ I: recipe.eyeColor,
782
+ L: mix$1(recipe.eyeColor, WHITE, .45)
783
+ };
784
+ const style = EYES[recipe.eyes];
785
+ const extra = style[0].length - 4;
786
+ sprite(eyes, fit(style, left.w + extra), left.x - extra, eyeTop, pal);
787
+ sprite(eyes, recipe.eyes === "wink" ? fit(CLOSED, right.w) : fit(style, right.w + extra), right.x, eyeTop, pal, true);
788
+ sprite(closed, fit(CLOSED, left.w), left.x, eyeTop, pal);
789
+ sprite(closed, fit(CLOSED, right.w), right.x, eyeTop, pal, true);
790
+ const browRows = BROWS[recipe.brows];
791
+ if (browRows.length) {
792
+ sprite(features, fit(browRows, left.w), left.x, eyeTop - 2, { B: shade(hairColor, .55) });
793
+ sprite(features, fit(browRows, right.w), right.x, eyeTop - 2, { B: shade(hairColor, .55) }, true);
794
+ }
795
+ const cx = Math.round(C + fs * (turned ? .85 : .6));
796
+ if (recipe.nose === "dot") sprite(features, ["N"], cx, 18 + h, { N: shade(skin, .78) });
797
+ if (recipe.nose === "button") sprite(features, ["N"], cx, 18 + h, { N: shade(skin, .88) });
798
+ if (recipe.nose === "line") sprite(features, ["N", "N"], cx, 17 + h, { N: shade(skin, .82) });
799
+ const mouth = MOUTHS[recipe.mouth];
800
+ const mx = C - .5 + fs * .6;
801
+ sprite(features, mouth, Math.round(mx - (mouth[0].length - 1) / 2), 20 + h, {
802
+ K: MOUTH,
803
+ M: MOUTH_INSIDE,
804
+ T: TONGUE,
805
+ W: WHITE
806
+ });
807
+ if (recipe.cheeks === "blush") {
808
+ sprite(features, ["PP"], left.x - 1, 19 + h, { P: mix$1(BLUSH, skin, .25) });
809
+ sprite(features, ["PP"], right.x + right.w - 1, 19 + h, { P: mix$1(BLUSH, skin, .25) });
810
+ }
811
+ if (recipe.cheeks === "freckles") {
812
+ sprite(features, ["F.F"], left.x, 19 + h, { F: shade(skin, .7) });
813
+ sprite(features, ["F.F"], right.x + 1, 19 + h, { F: shade(skin, .7) });
814
+ }
815
+ const frame = (fx, w, round) => paint(glasses, (x, y) => {
816
+ const inside = x >= fx && x < fx + w && y >= eyeTop && y <= eyeTop + 3;
817
+ const box = x >= fx - 1 && x <= fx + w && y >= eyeTop - 1 && y <= eyeTop + 4;
818
+ const corner = (x === fx - 1 || x === fx + w) && (y === eyeTop - 1 || y === eyeTop + 4);
819
+ return box && !inside && !(round && corner);
820
+ }, INK$1);
821
+ if (recipe.glasses === "round" || recipe.glasses === "square") {
822
+ frame(left.x, left.w, recipe.glasses === "round");
823
+ frame(right.x, right.w, recipe.glasses === "round");
824
+ paint(glasses, (x, y) => y === eyeTop + 1 && x > left.x + left.w && x < right.x - 1, INK$1);
825
+ }
826
+ if (recipe.glasses === "shades") {
827
+ for (const e of [left, right]) paint(glasses, (x, y) => x >= e.x - 1 && x <= e.x + e.w && y >= eyeTop && y <= eyeTop + 2, INK$1);
828
+ paint(glasses, (x, y) => y === eyeTop && x > left.x + left.w && x < right.x - 1, INK$1);
829
+ for (const e of [left, right]) sprite(glasses, ["W"], e.x, eyeTop, { W: "#7d7b88" });
830
+ }
831
+ if (recipe.glasses === "monocle") {
832
+ frame(right.x, right.w, true);
833
+ sprite(glasses, [
834
+ "G",
835
+ "G",
836
+ "G"
837
+ ], right.x + right.w, eyeTop + 5, { G: GOLD });
838
+ }
839
+ const full = clip(outline(figure));
840
+ const bodyCells = blank();
841
+ const headCells = blank();
842
+ for (let y = 0; y < N; y++) for (let x = 0; x < N; x++) {
843
+ const c = full[y][x];
844
+ if (!c) continue;
845
+ if (y >= 22 && !head[y][x] && !back[y][x]) bodyCells[y][x] = c;
846
+ else headCells[y][x] = c;
847
+ }
848
+ const cells = [];
849
+ for (let y = 0; y < N; y++) for (let x = 0; x < N; x++) {
850
+ const c = glasses[y][x] ?? eyes[y][x] ?? features[y][x] ?? full[y][x];
851
+ if (c) cells.push({
852
+ x,
853
+ y,
854
+ c
855
+ });
856
+ }
857
+ return {
858
+ cells,
859
+ tile: `<rect width="32" height="32" rx="6" fill="${pixelTileColor(recipe.hairColor)}"/>`,
860
+ body: `<g class="bh-illustrated-body">${rects(bodyCells)}</g>`,
861
+ head: [
862
+ `<g class="bh-illustrated-head">${rects(headCells)}`,
863
+ `<g class="bh-illustrated-face">${rects(features)}`,
864
+ `<g class="bh-illustrated-gaze">${rects(eyes)}</g>`,
865
+ `<g class="bh-illustrated-blink" opacity="0">${rects(closed)}</g>`,
866
+ `${rects(glasses)}</g></g>`
867
+ ].join("")
868
+ };
869
+ }
870
+
871
+ //#endregion
872
+ //#region src/random.ts
873
+ function seededRandom(seed) {
874
+ let h = 2166136261;
875
+ for (const char of seed) {
876
+ h ^= char.codePointAt(0);
877
+ h = Math.imul(h, 16777619);
878
+ }
879
+ return () => {
880
+ h = Math.imul(h ^ h >>> 15, 2246822507);
881
+ h = Math.imul(h ^ h >>> 13, 3266489909);
882
+ h ^= h >>> 16;
883
+ return (h >>> 0) / 4294967296;
884
+ };
885
+ }
886
+
887
+ //#endregion
888
+ //#region src/recipe.ts
889
+ const AVATAR_PARTS = {
890
+ head: [
891
+ "round",
892
+ "oval",
893
+ "square",
894
+ "long",
895
+ "heart",
896
+ "vchin",
897
+ "chubby",
898
+ "diamond"
899
+ ],
900
+ pose: [
901
+ "front",
902
+ "left",
903
+ "right"
904
+ ],
905
+ hair: [
906
+ "crop",
907
+ "sweep",
908
+ "spiky",
909
+ "buzz",
910
+ "curly",
911
+ "mohawk",
912
+ "bob",
913
+ "long",
914
+ "bun",
915
+ "pigtails",
916
+ "afro",
917
+ "twintails",
918
+ "drills",
919
+ "ponytail",
920
+ "sidetail",
921
+ "hime",
922
+ "odango",
923
+ "messy",
924
+ "wavy",
925
+ "braids",
926
+ "wolf",
927
+ "ahoge",
928
+ "none"
929
+ ],
930
+ eyes: [
931
+ "round",
932
+ "dot",
933
+ "sparkle",
934
+ "lashes",
935
+ "sleepy",
936
+ "happy",
937
+ "wink",
938
+ "sharp"
939
+ ],
940
+ brows: [
941
+ "soft",
942
+ "thick",
943
+ "raised",
944
+ "angry",
945
+ "worried",
946
+ "none"
947
+ ],
948
+ nose: [
949
+ "button",
950
+ "dot",
951
+ "line",
952
+ "none"
953
+ ],
954
+ mouth: [
955
+ "smile",
956
+ "grin",
957
+ "open",
958
+ "flat",
959
+ "smirk",
960
+ "cat",
961
+ "tongue",
962
+ "o"
963
+ ],
964
+ cheeks: [
965
+ "blush",
966
+ "freckles",
967
+ "none"
968
+ ],
969
+ glasses: [
970
+ "none",
971
+ "round",
972
+ "square",
973
+ "shades",
974
+ "monocle"
975
+ ],
976
+ outfit: [
977
+ "tee",
978
+ "shirttie",
979
+ "hoodie",
980
+ "turtleneck",
981
+ "sailor",
982
+ "blazer",
983
+ "overalls",
984
+ "dress",
985
+ "kimono",
986
+ "cardigan",
987
+ "maid",
988
+ "jacket"
989
+ ],
990
+ backdrop: [
991
+ "sparkles",
992
+ "hearts",
993
+ "stars",
994
+ "dots",
995
+ "none"
996
+ ],
997
+ accessory: [
998
+ "none",
999
+ "beanie",
1000
+ "cap",
1001
+ "headphones",
1002
+ "flower",
1003
+ "bow",
1004
+ "earring",
1005
+ "crown",
1006
+ "halo",
1007
+ "catears",
1008
+ "hairclip",
1009
+ "horns",
1010
+ "beret",
1011
+ "ribbon",
1012
+ "headband",
1013
+ "bunnyears",
1014
+ "horseears",
1015
+ "flowercrown",
1016
+ "witch",
1017
+ "pins"
1018
+ ]
1019
+ };
1020
+ const AVATAR_HAIR_PARTS = {
1021
+ bangs: [
1022
+ "crop",
1023
+ "bob",
1024
+ "sweep",
1025
+ "spiky",
1026
+ "curly",
1027
+ "messy",
1028
+ "wolf",
1029
+ "wavy",
1030
+ "ahoge",
1031
+ "hime",
1032
+ "buzz",
1033
+ "mohawk",
1034
+ "none"
1035
+ ],
1036
+ sideHair: [
1037
+ "crop",
1038
+ "bun",
1039
+ "bob",
1040
+ "long",
1041
+ "hime",
1042
+ "curly",
1043
+ "braids",
1044
+ "none"
1045
+ ],
1046
+ backHair: [
1047
+ "none",
1048
+ "bob",
1049
+ "long",
1050
+ "wavy",
1051
+ "wolf",
1052
+ "bun",
1053
+ "odango",
1054
+ "pigtails",
1055
+ "twintails",
1056
+ "drills",
1057
+ "ponytail",
1058
+ "sidetail",
1059
+ "afro"
1060
+ ]
1061
+ };
1062
+ const HAIR_SPLIT = {
1063
+ crop: {
1064
+ bangs: "crop",
1065
+ sideHair: "crop",
1066
+ backHair: "none"
1067
+ },
1068
+ sweep: {
1069
+ bangs: "sweep",
1070
+ sideHair: "crop",
1071
+ backHair: "none"
1072
+ },
1073
+ spiky: {
1074
+ bangs: "spiky",
1075
+ sideHair: "crop",
1076
+ backHair: "none"
1077
+ },
1078
+ buzz: {
1079
+ bangs: "buzz",
1080
+ sideHair: "none",
1081
+ backHair: "none"
1082
+ },
1083
+ curly: {
1084
+ bangs: "curly",
1085
+ sideHair: "curly",
1086
+ backHair: "none"
1087
+ },
1088
+ mohawk: {
1089
+ bangs: "mohawk",
1090
+ sideHair: "none",
1091
+ backHair: "none"
1092
+ },
1093
+ bob: {
1094
+ bangs: "bob",
1095
+ sideHair: "bob",
1096
+ backHair: "bob"
1097
+ },
1098
+ long: {
1099
+ bangs: "bob",
1100
+ sideHair: "long",
1101
+ backHair: "long"
1102
+ },
1103
+ bun: {
1104
+ bangs: "bob",
1105
+ sideHair: "bun",
1106
+ backHair: "bun"
1107
+ },
1108
+ pigtails: {
1109
+ bangs: "bob",
1110
+ sideHair: "bun",
1111
+ backHair: "pigtails"
1112
+ },
1113
+ afro: {
1114
+ bangs: "buzz",
1115
+ sideHair: "none",
1116
+ backHair: "afro"
1117
+ },
1118
+ twintails: {
1119
+ bangs: "bob",
1120
+ sideHair: "bob",
1121
+ backHair: "twintails"
1122
+ },
1123
+ drills: {
1124
+ bangs: "bob",
1125
+ sideHair: "bob",
1126
+ backHair: "drills"
1127
+ },
1128
+ ponytail: {
1129
+ bangs: "bob",
1130
+ sideHair: "bun",
1131
+ backHair: "ponytail"
1132
+ },
1133
+ sidetail: {
1134
+ bangs: "sweep",
1135
+ sideHair: "bun",
1136
+ backHair: "sidetail"
1137
+ },
1138
+ hime: {
1139
+ bangs: "hime",
1140
+ sideHair: "hime",
1141
+ backHair: "long"
1142
+ },
1143
+ odango: {
1144
+ bangs: "bob",
1145
+ sideHair: "bun",
1146
+ backHair: "odango"
1147
+ },
1148
+ messy: {
1149
+ bangs: "messy",
1150
+ sideHair: "bun",
1151
+ backHair: "none"
1152
+ },
1153
+ wavy: {
1154
+ bangs: "wavy",
1155
+ sideHair: "long",
1156
+ backHair: "wavy"
1157
+ },
1158
+ braids: {
1159
+ bangs: "bob",
1160
+ sideHair: "braids",
1161
+ backHair: "none"
1162
+ },
1163
+ wolf: {
1164
+ bangs: "wolf",
1165
+ sideHair: "bun",
1166
+ backHair: "wolf"
1167
+ },
1168
+ ahoge: {
1169
+ bangs: "ahoge",
1170
+ sideHair: "bob",
1171
+ backHair: "bob"
1172
+ },
1173
+ none: {
1174
+ bangs: "none",
1175
+ sideHair: "none",
1176
+ backHair: "none"
1177
+ }
1178
+ };
1179
+ const AVATAR_RANGES = {
1180
+ spacing: [-1, 1],
1181
+ height: [-1, 1],
1182
+ hairLength: [-2, 2]
1183
+ };
1184
+ const DETAIL_KEYS = [...Object.keys(AVATAR_HAIR_PARTS), ...Object.keys(AVATAR_RANGES)];
1185
+ const AVATAR_COLORS = [
1186
+ "skinColor",
1187
+ "hairColor",
1188
+ "eyeColor",
1189
+ "shirtColor"
1190
+ ];
1191
+ const DEFAULT_RECIPE = {
1192
+ schemaVersion: 1,
1193
+ family: "illustrated",
1194
+ assetVersion: 1,
1195
+ rigVersion: 1,
1196
+ head: "round",
1197
+ pose: "front",
1198
+ hair: "crop",
1199
+ eyes: "round",
1200
+ brows: "soft",
1201
+ nose: "button",
1202
+ mouth: "smile",
1203
+ cheeks: "blush",
1204
+ glasses: "none",
1205
+ outfit: "tee",
1206
+ backdrop: "sparkles",
1207
+ accessory: "none",
1208
+ skinColor: "#f2c9a8",
1209
+ hairColor: "#5a3a2a",
1210
+ eyeColor: "#3f7fbf",
1211
+ shirtColor: "#5b8bd6"
1212
+ };
1213
+ const PART_KEYS = Object.keys(AVATAR_PARTS);
1214
+ function isPixelAvatarRecipe(value) {
1215
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1216
+ const r = value;
1217
+ const detailed = DETAIL_KEYS.filter((key) => Object.hasOwn(r, key)).length;
1218
+ if (detailed !== 0 && detailed !== DETAIL_KEYS.length) return false;
1219
+ if (Object.keys(r).length !== Object.keys(DEFAULT_RECIPE).length + detailed) return false;
1220
+ return (detailed === 0 || Object.keys(AVATAR_HAIR_PARTS).every((key) => typeof r[key] === "string" && AVATAR_HAIR_PARTS[key].includes(r[key])) && Object.keys(AVATAR_RANGES).every((key) => {
1221
+ const v = r[key];
1222
+ const [min, max] = AVATAR_RANGES[key];
1223
+ return typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
1224
+ })) && r["schemaVersion"] === 1 && r["family"] === "illustrated" && r["assetVersion"] === 1 && r["rigVersion"] === 1 && PART_KEYS.every((key) => typeof r[key] === "string" && AVATAR_PARTS[key].includes(r[key])) && AVATAR_COLORS.every((key) => typeof r[key] === "string" && /^#[\da-f]{6}$/iu.test(r[key]));
1225
+ }
1226
+ function canonicalRecipe(recipe) {
1227
+ const canonical = {
1228
+ schemaVersion: 1,
1229
+ family: "illustrated",
1230
+ assetVersion: 1,
1231
+ rigVersion: 1
1232
+ };
1233
+ for (const key of PART_KEYS) canonical[key] = recipe[key];
1234
+ for (const key of AVATAR_COLORS) canonical[key] = recipe[key].toLowerCase();
1235
+ if (recipe.bangs !== void 0) for (const key of DETAIL_KEYS) canonical[key] = recipe[key];
1236
+ return canonical;
1237
+ }
1238
+ function detailedRecipe(recipe) {
1239
+ if (recipe.bangs !== void 0) return recipe;
1240
+ const pick = (part) => AVATAR_HAIR_PARTS[part].includes(recipe.hair) ? recipe.hair : HAIR_SPLIT[recipe.hair][part];
1241
+ return {
1242
+ ...recipe,
1243
+ bangs: pick("bangs"),
1244
+ sideHair: pick("sideHair"),
1245
+ backHair: pick("backHair"),
1246
+ spacing: 0,
1247
+ height: 0,
1248
+ hairLength: 0
1249
+ };
1250
+ }
1251
+ const AVATAR_SWATCHES = {
1252
+ skinColor: [
1253
+ "#ffe3cf",
1254
+ "#f2c9a8",
1255
+ "#e0a87e",
1256
+ "#c68863",
1257
+ "#9a6142",
1258
+ "#6e4129",
1259
+ "#4a2c1c",
1260
+ "#f4f1ec"
1261
+ ],
1262
+ hairColor: [
1263
+ "#1d1b22",
1264
+ "#5a3a2a",
1265
+ "#8a5a36",
1266
+ "#e2b04a",
1267
+ "#c4452f",
1268
+ "#d9475a",
1269
+ "#f06292",
1270
+ "#3fc1b8",
1271
+ "#5a7be0",
1272
+ "#9aa3ad",
1273
+ "#f4f1ec"
1274
+ ],
1275
+ eyeColor: [
1276
+ "#3f7fbf",
1277
+ "#5a3a2a",
1278
+ "#2f9e8f",
1279
+ "#4c8a3c",
1280
+ "#8a5ad0",
1281
+ "#d0533f",
1282
+ "#d9a13a",
1283
+ "#2a2230"
1284
+ ],
1285
+ shirtColor: [
1286
+ "#5b8bd6",
1287
+ "#e07a5f",
1288
+ "#3d9970",
1289
+ "#7a5cc7",
1290
+ "#f2c14e",
1291
+ "#2f3a4a",
1292
+ "#e2565f",
1293
+ "#9ad0c2"
1294
+ ]
1295
+ };
1296
+ const SEEDED_CHOICES = {
1297
+ head: [
1298
+ "round",
1299
+ "oval",
1300
+ "square",
1301
+ "long",
1302
+ "heart",
1303
+ "vchin",
1304
+ "chubby",
1305
+ "diamond"
1306
+ ],
1307
+ pose: ["front"],
1308
+ hair: AVATAR_PARTS.hair.filter((hair) => hair !== "none"),
1309
+ eyes: [
1310
+ "round",
1311
+ "round",
1312
+ "sparkle",
1313
+ "lashes",
1314
+ "dot",
1315
+ "happy",
1316
+ "sharp"
1317
+ ],
1318
+ brows: [
1319
+ "soft",
1320
+ "soft",
1321
+ "thick",
1322
+ "raised"
1323
+ ],
1324
+ nose: [
1325
+ "button",
1326
+ "dot",
1327
+ "none"
1328
+ ],
1329
+ mouth: [
1330
+ "smile",
1331
+ "smile",
1332
+ "grin",
1333
+ "open",
1334
+ "cat"
1335
+ ],
1336
+ cheeks: [
1337
+ "blush",
1338
+ "blush",
1339
+ "freckles",
1340
+ "none"
1341
+ ],
1342
+ glasses: [
1343
+ "none",
1344
+ "none",
1345
+ "none",
1346
+ "none",
1347
+ "round",
1348
+ "square"
1349
+ ],
1350
+ accessory: [
1351
+ "none",
1352
+ "none",
1353
+ "none",
1354
+ "flower",
1355
+ "bow",
1356
+ "hairclip",
1357
+ "catears",
1358
+ "headphones",
1359
+ "beanie",
1360
+ "cap"
1361
+ ],
1362
+ outfit: AVATAR_PARTS.outfit,
1363
+ backdrop: [
1364
+ "sparkles",
1365
+ "hearts",
1366
+ "stars",
1367
+ "dots"
1368
+ ]
1369
+ };
1370
+ /**
1371
+ * Returns a name-seeded recipe factory. Different namespaces give different faces for the same
1372
+ * name; the factory takes the name only, so it is safe to pass straight to `Array.map`.
1373
+ */
1374
+ function createSeededRecipe(namespace) {
1375
+ return (seed) => {
1376
+ const random = seededRandom(`${namespace}:${seed.trim().toLowerCase()}`);
1377
+ const pick = (values) => values[Math.floor(random() * values.length)];
1378
+ const recipe = {
1379
+ schemaVersion: 1,
1380
+ family: "illustrated",
1381
+ assetVersion: 1,
1382
+ rigVersion: 1
1383
+ };
1384
+ for (const part of Object.keys(AVATAR_PARTS)) recipe[part] = pick(SEEDED_CHOICES[part]);
1385
+ for (const color of AVATAR_COLORS) recipe[color] = pick(AVATAR_SWATCHES[color].slice(0, 7));
1386
+ return recipe;
1387
+ };
1388
+ }
1389
+ /** The same name always gives the same face (BotHarness's namespace). */
1390
+ const seededRecipe = createSeededRecipe("botharness-avatar");
1391
+ const preset = (parts) => ({
1392
+ ...DEFAULT_RECIPE,
1393
+ ...parts
1394
+ });
1395
+ const AVATAR_PRESETS = [
1396
+ preset({
1397
+ hair: "wavy",
1398
+ eyes: "lashes",
1399
+ accessory: "beret",
1400
+ outfit: "dress",
1401
+ skinColor: "#ffe3cf",
1402
+ hairColor: "#e2b04a",
1403
+ eyeColor: "#3f7fbf",
1404
+ shirtColor: "#e2565f",
1405
+ backdrop: "stars"
1406
+ }),
1407
+ preset({
1408
+ hair: "braids",
1409
+ accessory: "flowercrown",
1410
+ outfit: "cardigan",
1411
+ skinColor: "#ffe3cf",
1412
+ hairColor: "#8a5a36",
1413
+ eyeColor: "#4c8a3c",
1414
+ shirtColor: "#9ad0c2",
1415
+ backdrop: "hearts"
1416
+ }),
1417
+ preset({
1418
+ hair: "twintails",
1419
+ eyes: "sparkle",
1420
+ accessory: "ribbon",
1421
+ outfit: "maid",
1422
+ skinColor: "#ffe3cf",
1423
+ hairColor: "#3fc1b8",
1424
+ eyeColor: "#2f9e8f",
1425
+ shirtColor: "#e2565f",
1426
+ backdrop: "stars"
1427
+ }),
1428
+ preset({
1429
+ hair: "long",
1430
+ accessory: "horseears",
1431
+ outfit: "jacket",
1432
+ skinColor: "#ffe3cf",
1433
+ hairColor: "#8a5a36",
1434
+ eyeColor: "#d0533f",
1435
+ shirtColor: "#5b8bd6",
1436
+ backdrop: "dots"
1437
+ }),
1438
+ preset({
1439
+ hair: "hime",
1440
+ eyes: "sleepy",
1441
+ accessory: "pins",
1442
+ outfit: "kimono",
1443
+ skinColor: "#f4f1ec",
1444
+ hairColor: "#1d1b22",
1445
+ eyeColor: "#d0533f",
1446
+ shirtColor: "#7a5cc7",
1447
+ backdrop: "none"
1448
+ }),
1449
+ preset({
1450
+ hair: "ahoge",
1451
+ accessory: "headband",
1452
+ outfit: "sailor",
1453
+ skinColor: "#ffe3cf",
1454
+ hairColor: "#f06292",
1455
+ eyeColor: "#2f9e8f",
1456
+ shirtColor: "#f4f1ec",
1457
+ backdrop: "hearts"
1458
+ }),
1459
+ preset({
1460
+ hair: "wolf",
1461
+ eyes: "sharp",
1462
+ outfit: "jacket",
1463
+ skinColor: "#e0a87e",
1464
+ hairColor: "#9aa3ad",
1465
+ eyeColor: "#5a7be0",
1466
+ shirtColor: "#2f3a4a",
1467
+ backdrop: "stars"
1468
+ }),
1469
+ preset({
1470
+ hair: "wavy",
1471
+ eyes: "sparkle",
1472
+ accessory: "witch",
1473
+ outfit: "dress",
1474
+ skinColor: "#ffe3cf",
1475
+ hairColor: "#8a5ad0",
1476
+ eyeColor: "#d9a13a",
1477
+ shirtColor: "#2f3a4a",
1478
+ backdrop: "sparkles"
1479
+ }),
1480
+ preset({
1481
+ hair: "bob",
1482
+ eyes: "happy",
1483
+ accessory: "bunnyears",
1484
+ outfit: "cardigan",
1485
+ skinColor: "#ffe3cf",
1486
+ hairColor: "#f4f1ec",
1487
+ eyeColor: "#d9475a",
1488
+ shirtColor: "#f2c14e",
1489
+ backdrop: "hearts"
1490
+ }),
1491
+ preset({
1492
+ hair: "spiky",
1493
+ glasses: "round",
1494
+ accessory: "headphones",
1495
+ outfit: "hoodie",
1496
+ backdrop: "dots"
1497
+ }),
1498
+ preset({
1499
+ hair: "odango",
1500
+ eyes: "lashes",
1501
+ accessory: "bow",
1502
+ outfit: "kimono",
1503
+ hairColor: "#e2b04a",
1504
+ shirtColor: "#5b8bd6",
1505
+ backdrop: "sparkles"
1506
+ }),
1507
+ preset({
1508
+ hair: "crop",
1509
+ accessory: "cap",
1510
+ outfit: "shirttie",
1511
+ skinColor: "#9a6142",
1512
+ hairColor: "#1d1b22",
1513
+ shirtColor: "#3d9970",
1514
+ backdrop: "dots"
1515
+ })
1516
+ ];
1517
+
1518
+ //#endregion
1519
+ //#region src/svg.ts
1520
+ const YAW = {
1521
+ front: 0,
1522
+ left: -25,
1523
+ right: 25
1524
+ };
1525
+ /** Head-turn offsets (degrees) the thinking animation cycles through. */
1526
+ const AVATAR_TURNS = [
1527
+ -14,
1528
+ -7,
1529
+ 7,
1530
+ 14
1531
+ ];
1532
+ /** Every visible pixel of the face at its pose, in paint order, for morphing. */
1533
+ function faceCells(recipe) {
1534
+ return pixelFigure(recipe, YAW[recipe.pose]).cells;
1535
+ }
1536
+ /** A 32×32 SVG of the avatar: tile, body, head rig, optional turn layers and an empty morph layer. */
1537
+ function pixelAvatarSvg(recipe, options = {}) {
1538
+ if (!isPixelAvatarRecipe(recipe)) throw new Error("invalid Avatar recipe");
1539
+ const base = pixelFigure(recipe, YAW[recipe.pose]);
1540
+ const turns = (options.turns ?? []).map((delta) => {
1541
+ const figure = pixelFigure(recipe, YAW[recipe.pose] + delta);
1542
+ return `<g data-avatar-turn="${delta}" opacity="0">${`${figure.body}${figure.head}`.replaceAll("class=\"bh-illustrated-", "data-turn-part=\"")}</g>`;
1543
+ }).join("");
1544
+ const svg = [
1545
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\" width=\"512\" height=\"512\" shape-rendering=\"crispEdges\" aria-hidden=\"true\">",
1546
+ base.tile,
1547
+ base.body,
1548
+ base.head,
1549
+ turns,
1550
+ "<g data-avatar-pixel-morph=\"\"></g>",
1551
+ "</svg>"
1552
+ ].join("");
1553
+ const prefix = options.classPrefix;
1554
+ return prefix === void 0 || prefix === "bh-illustrated" ? svg : svg.replaceAll("class=\"bh-illustrated-", `class="${prefix}-`);
1555
+ }
1556
+
1557
+ //#endregion
1558
+ //#region src/symbols.ts
1559
+ const SYMBOLS = {
1560
+ thinking: [
1561
+ "........................",
1562
+ "........................",
1563
+ "........................",
1564
+ "...........AAAA.........",
1565
+ "..........AAAAAA........",
1566
+ ".........AAAAAAAA.......",
1567
+ ".......AAAAAAAAAAA......",
1568
+ ".....AAAAAAAAAAAAA......",
1569
+ "....AAAAAAAAAAAAAAAA....",
1570
+ "....AAAAAAAAAAAAAAAAA...",
1571
+ "...AAAAAAAAAAAAAAAAAAA..",
1572
+ "...AAAAHHAAAHHAAAHHAAA..",
1573
+ "....AAAHHAAAHHAAAHHAAA..",
1574
+ "....AAAAAAAAAAAAAAAAAA..",
1575
+ ".....AAAAAAAAAAAAAAAA...",
1576
+ ".....AAAAAAAAAAAAAAA....",
1577
+ ".......AAAAAAAAAAA......",
1578
+ "........................",
1579
+ "....AA..................",
1580
+ "...AAAA.................",
1581
+ "...AAAA.................",
1582
+ "..A.AA..................",
1583
+ ".AAA....................",
1584
+ "..A....................."
1585
+ ],
1586
+ ask: [
1587
+ "........................",
1588
+ "........................",
1589
+ "...........AAA..........",
1590
+ "........AAAAAAAA........",
1591
+ ".......AAAAAAAAAA.......",
1592
+ ".......AAAAAAAAAA.......",
1593
+ "......AAAAA...AAAA......",
1594
+ "......AAAA....AAAA......",
1595
+ "......AAA.....AAAA......",
1596
+ "......AAA.....AAAA......",
1597
+ "..............AAAA......",
1598
+ ".............AAAAA......",
1599
+ "............AAAAA.......",
1600
+ "...........AAAAA........",
1601
+ "...........AAA..........",
1602
+ "..........AAAA..........",
1603
+ "..........AAAA..........",
1604
+ "...........AA...........",
1605
+ "........................",
1606
+ "..........AAAA..........",
1607
+ "..........AAAA..........",
1608
+ "..........AAAA..........",
1609
+ "...........AA...........",
1610
+ "........................"
1611
+ ],
1612
+ read: [
1613
+ "........................",
1614
+ "........................",
1615
+ "........................",
1616
+ "........................",
1617
+ "..AAAAAAA......AAAAAAA..",
1618
+ ".AAAAAAAAAA..AAAAAAAAAA.",
1619
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1620
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1621
+ ".AAAHHHHHAAAAAAHHHHHAAA.",
1622
+ ".AAAHHHHHAAAAAAHHHHHAAA.",
1623
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1624
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1625
+ ".AAAHHHHHAAAAAAHHHHHAAA.",
1626
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1627
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1628
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1629
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1630
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1631
+ "...........AA...........",
1632
+ "........................",
1633
+ "........................",
1634
+ "........................",
1635
+ "........................",
1636
+ "........................"
1637
+ ],
1638
+ write: [
1639
+ "........................",
1640
+ ".....AAAAAAAAAA.........",
1641
+ "....AAAAAAAAAAAA........",
1642
+ "....AAAAAAAAAAAAA.......",
1643
+ "....AAAAAAAAAAAAAA......",
1644
+ "....AAAAAAAAAAAAAAA.....",
1645
+ "....AAAAAAAAAAAAAAAA....",
1646
+ "....AAAAAAAAAAAAAAAA....",
1647
+ "....AAAAAAAAAAAAAAAA....",
1648
+ "....AAAAAAAHHAAAAAAA....",
1649
+ "....AAAAAAAHHAAAAAAA....",
1650
+ "....AAAAAAAHHAAAAAAA....",
1651
+ "....AAAAAAAHHAAAAAAA....",
1652
+ "....AAAHHHHHHHHHHAAA....",
1653
+ "....AAAAAAAHHAAAAAAA....",
1654
+ "....AAAAAAAHHAAAAAAA....",
1655
+ "....AAAAAAAHHAAAAAAA....",
1656
+ "....AAAAAAAHHAAAAAAA....",
1657
+ "....AAAAAAAAAAAAAAAA....",
1658
+ "....AAAAAAAAAAAAAAAA....",
1659
+ "....AAAAAAAAAAAAAAAA....",
1660
+ "....AAAAAAAAAAAAAAAA....",
1661
+ ".....AAAAAAAAAAAAAA.....",
1662
+ "........................"
1663
+ ],
1664
+ edit: [
1665
+ "........................",
1666
+ "........................",
1667
+ "........................",
1668
+ "...............AA.......",
1669
+ ".............HHAAA......",
1670
+ ".............HHHAAA.....",
1671
+ "............AAHHHAAA....",
1672
+ "...........AAAAHHHAAA...",
1673
+ "..........AAAAAAHHHAA...",
1674
+ ".........AAAAAAAAHHH....",
1675
+ "........AAAAAAAAAAHH....",
1676
+ ".......AAAAAAAAAAA......",
1677
+ "......AAAAAAAAAAA.......",
1678
+ ".....AAAAAAAAAAA........",
1679
+ "....AAAAAAAAAAA.........",
1680
+ "...AAAAAAAAAAA..........",
1681
+ "...AAHHAAAAAA...........",
1682
+ "...AAHHHAAAA............",
1683
+ "...AAAHHAAA.............",
1684
+ "...AAAAAAA..............",
1685
+ "...AAAAAA...............",
1686
+ "........................",
1687
+ "........................",
1688
+ "........................"
1689
+ ],
1690
+ bash: [
1691
+ "........................",
1692
+ "........................",
1693
+ "........................",
1694
+ "....AAAAAAAAAAAAAAAA....",
1695
+ "..AAAAAAAAAAAAAAAAAAAA..",
1696
+ "..AAAAAAAAAAAAAAAAAAAA..",
1697
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1698
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1699
+ ".AAAAHHAAAAAAAAAAAAAAAA.",
1700
+ ".AAAAHHHAAAAAAAAAAAAAAA.",
1701
+ ".AAAAAHHHAAAAAAAAAAAAAA.",
1702
+ ".AAAAAAHHHAAAAAAAAAAAAA.",
1703
+ ".AAAAAAHHHAAAAAAAAAAAAA.",
1704
+ ".AAAAAHHHAAAAAAAAAAAAAA.",
1705
+ ".AAAAHHHAAAAAAAAAAAAAAA.",
1706
+ ".AAAAHHAAAAHHHHHHHAAAAA.",
1707
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1708
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1709
+ "..AAAAAAAAAAAAAAAAAAAA..",
1710
+ "..AAAAAAAAAAAAAAAAAAAA..",
1711
+ "....AAAAAAAAAAAAAAAA....",
1712
+ "........................",
1713
+ "........................",
1714
+ "........................"
1715
+ ],
1716
+ search: [
1717
+ "........................",
1718
+ "........................",
1719
+ ".......AAAAAA...........",
1720
+ "......AAAAAAAA..........",
1721
+ "....AAAAAAAAAAAA........",
1722
+ "....AAAAAAAAAAAA........",
1723
+ "...AAAA......AAAA.......",
1724
+ "..AAAA........AAAA......",
1725
+ "..AAAA........AAAA......",
1726
+ "..AAAA........AAAA......",
1727
+ "..AAAA........AAAA......",
1728
+ "..AAAA........AAAA......",
1729
+ "..AAAA........AAAA......",
1730
+ "...AAAA......AAAA.......",
1731
+ "....AAAAAAAAAAAAA.......",
1732
+ "....AAAAAAAAAAAAAA......",
1733
+ "......AAAAAAAAAAAAA.....",
1734
+ ".......AAAAAA..AAAAA....",
1735
+ "................AAAAA...",
1736
+ ".................AAAAA..",
1737
+ "..................AAAAA.",
1738
+ "...................AAAA.",
1739
+ "....................AA..",
1740
+ "........................"
1741
+ ],
1742
+ web: [
1743
+ "........................",
1744
+ "........................",
1745
+ ".........AHHHHA.........",
1746
+ ".......AAHHHHHHAA.......",
1747
+ ".....AAAAHHAAHHAAAA.....",
1748
+ "....AAAAHHAAAAHHAAAA....",
1749
+ "....HHHHHHHHHHHHHHHH....",
1750
+ "...AHHHHHHHHHHHHHHHHA...",
1751
+ "...AAAAAHAAAAAAHAAAAA...",
1752
+ "..AAAAAHHAAAAAAHAAAAAA..",
1753
+ "..AAAAAHHAAAAAAHHAAAAA..",
1754
+ "..HHHHHHHHHHHHHHHHHHHH..",
1755
+ "..HHHHHHHHHHHHHHHHHHHH..",
1756
+ "..AAAAAHHAAAAAAHHAAAAA..",
1757
+ "..AAAAAHHAAAAAAHAAAAAA..",
1758
+ "...AAAAAHAAAAAAHAAAAA...",
1759
+ "...AHHHHHHHHHHHHHHHHA...",
1760
+ "....HHHHHHHHHHHHHHHH....",
1761
+ "....AAAAHHAAAAHHAAAA....",
1762
+ ".....AAAAHHAAHHAAAA.....",
1763
+ ".......AAHHHHHHAA.......",
1764
+ ".........AHHHHA.........",
1765
+ "........................",
1766
+ "........................"
1767
+ ],
1768
+ fetch: [
1769
+ "........................",
1770
+ "..........AAA...........",
1771
+ "..........AAAA..........",
1772
+ "..........AAAA..........",
1773
+ "..........AAAA..........",
1774
+ "..........AAAA..........",
1775
+ "..........AAAA..........",
1776
+ ".....AA...AAAA...AA.....",
1777
+ "....AAAA..AAAA..AAAA....",
1778
+ "....AAAAA.AAAA.AAAAA....",
1779
+ ".....AAAAAAAAAAAAAA.....",
1780
+ "......AAAAAAAAAAAA......",
1781
+ ".......AAAAAAAAAA.......",
1782
+ "........AAAAAAAA........",
1783
+ ".........AAAAAA.........",
1784
+ "..AAA.....AAAA.....AAA..",
1785
+ "..AAA......AA......AAA..",
1786
+ "..AAA..............AAA..",
1787
+ "..AAA..............AAA..",
1788
+ "..AAAAAAAAAAAAAAAAAAAA..",
1789
+ "..AAAAAAAAAAAAAAAAAAAA..",
1790
+ "..AAAAAAAAAAAAAAAAAAAA..",
1791
+ "...AAAAAAAAAAAAAAAAAA...",
1792
+ "........................"
1793
+ ],
1794
+ todo: [
1795
+ "........................",
1796
+ "........................",
1797
+ "....AAAAAAAAAAAAAAAA....",
1798
+ "....AAAAAAAAAAAAAAAA....",
1799
+ "...AAAAAAAAAAAAAAAAAA...",
1800
+ "...AAAAAAAAAAAAAAAAAA...",
1801
+ "...AAAAAAAAHAAAAAAAAA...",
1802
+ "...AAAAAAAHHAAAAAAAAA...",
1803
+ "...AAAHHAHHAAHHHHHAAA...",
1804
+ "...AAAAHHHAAAHHHHHAAA...",
1805
+ "...AAAAAHAAAAAAAAAAAA...",
1806
+ "...AAAAAAAAAAAAAAAAAA...",
1807
+ "...AAAAAAAAAAAAAAAAAA...",
1808
+ "...AAAAAAAAHAAAAAAAAA...",
1809
+ "...AAAAAAAHHAAAAAAAAA...",
1810
+ "...AAAHHAHHAAHHHHHAAA...",
1811
+ "...AAAAHHHAAAHHHHHAAA...",
1812
+ "...AAAAAHAAAAAAAAAAAA...",
1813
+ "...AAAAAAAAAAAAAAAAAA...",
1814
+ "...AAAAAAAAAAAAAAAAAA...",
1815
+ "...AAAAAAAAAAAAAAAAAA...",
1816
+ "....AAAAAAAAAAAAAAAA....",
1817
+ ".....AAAAAAAAAAAAAA.....",
1818
+ "........................"
1819
+ ],
1820
+ subagent: [
1821
+ "........................",
1822
+ "........................",
1823
+ "........................",
1824
+ "........................",
1825
+ ".....AAAA......AAAA.....",
1826
+ "....AAAAAA....AAAAAA....",
1827
+ "....AAAAAAA..AAAAAAA....",
1828
+ "....AAAAAAA..AAAAAAA....",
1829
+ "....AAAAAAA..AAAAAAA....",
1830
+ "....AAAAAA...AAAAAAA....",
1831
+ ".....AAAAA....AAAAA.....",
1832
+ "........................",
1833
+ "........................",
1834
+ "........................",
1835
+ ".......A................",
1836
+ "....AAAAAAA..AAAAAAA....",
1837
+ "...AAAAAAAAAAAAAAAAAA...",
1838
+ "..AAAAAAAAAAAAAAAAAAAA..",
1839
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1840
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1841
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1842
+ "........................",
1843
+ "........................",
1844
+ "........................"
1845
+ ],
1846
+ workflow: [
1847
+ "........................",
1848
+ "........................",
1849
+ "........................",
1850
+ "..AAAAAA.........AAAAA..",
1851
+ "..AAAAAA........AAAAAAA.",
1852
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1853
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1854
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1855
+ ".AAAAAAA....AAA.AAAAAAA.",
1856
+ "..AAAAAA....AAA..AAAAA..",
1857
+ "............AAA.........",
1858
+ "............AAA.........",
1859
+ "............AAA.........",
1860
+ "............AAA.........",
1861
+ "............AAA..AAAAA..",
1862
+ "............AAA.AAAAAAA.",
1863
+ ".............AAAAAAAAAA.",
1864
+ ".............AAAAAAAAAA.",
1865
+ "...............AAAAAAAA.",
1866
+ "................AAAAAAA.",
1867
+ ".................AAAAA..",
1868
+ "........................",
1869
+ "........................",
1870
+ "........................"
1871
+ ],
1872
+ goal: [
1873
+ "........................",
1874
+ "....AA..................",
1875
+ "...AAAA.................",
1876
+ "...AAAAAAAAAAAAAAAAA....",
1877
+ "...AAAAAAAAAAAAAAAA.....",
1878
+ "...AAAAAAAAAAAAAAA......",
1879
+ "...AAAAAAAAAAAAAAA......",
1880
+ "...AAAAAAAAAAAAAA.......",
1881
+ "...AAAAAAAAAAAAAA.......",
1882
+ "...AAAAAAAAAAAAAAA......",
1883
+ "...AAAAAAAAAAAAAAAA.....",
1884
+ "...AAAAAAAAAAAAAAAAA....",
1885
+ "...AAAA.................",
1886
+ "...AAAA.................",
1887
+ "...AAAA.................",
1888
+ "...AAAA.................",
1889
+ "...AAAA.................",
1890
+ "...AAAA.................",
1891
+ "...AAAA.................",
1892
+ "...AAAA.................",
1893
+ "...AAAA.................",
1894
+ "...AAAA.................",
1895
+ "...AAAA.................",
1896
+ "....AA.................."
1897
+ ],
1898
+ present: [
1899
+ "........................",
1900
+ "........................",
1901
+ "...AAAAAAAAAAAAAAAAAA...",
1902
+ "..AAAAAAAAAAAAAAAAAAAA..",
1903
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1904
+ ".AAAAAAAAAAAAAAAAAHAAAA.",
1905
+ ".AAAAAAAAAAAAAAAAAHAAAA.",
1906
+ ".AAAAAAAAAHAAAAAAAHAAAA.",
1907
+ ".AAAAAAAAAHAAAHAAAHAAAA.",
1908
+ ".AAAAAHAAAHAAAHAAAHAAAA.",
1909
+ ".AAAAAHAAAHAAAHAAAHAAAA.",
1910
+ ".AAAAAHAAAHAAAHAAAHAAAA.",
1911
+ ".AAAAAHAAAHAAAHAAAHAAAA.",
1912
+ ".AAAAAHAAAHAAAHAAAHAAAA.",
1913
+ ".AAAAAAAAAAAAAAAAAAAAAA.",
1914
+ "..AAAAAAAAAAAAAAAAAAAA..",
1915
+ "...AAAAAAAAAAAAAAAAAA...",
1916
+ ".........AAAAAA.........",
1917
+ "........AAAAAAAA........",
1918
+ "........AAA..AAA........",
1919
+ ".......AAA....AAA.......",
1920
+ ".......AAA....AAA.......",
1921
+ ".......AA......AA.......",
1922
+ "........................"
1923
+ ],
1924
+ approval: [
1925
+ "........................",
1926
+ "........................",
1927
+ ".........AAAAAA.........",
1928
+ "......AAAAAAAAAAAA......",
1929
+ "....AAAAAAAAAAAAAAAA....",
1930
+ "...AAAAAAAAHHAAAAAAAA...",
1931
+ "...AAAAAAAAHHAAAAAAAA...",
1932
+ "...AAAAAAAAHHAAAAAAAA...",
1933
+ "...AAAAAAAAHHAAAAAAAA...",
1934
+ "...AAAAAAAAHHAAAAAAAA...",
1935
+ "...AAAAAAAAHHAAAAAAAA...",
1936
+ "...AAAAAAAAHHAAAAAAAA...",
1937
+ "...AAAAAAAAHHAAAAAAAA...",
1938
+ "....AAAAAAAHHAAAAAAA....",
1939
+ "....AAAAAAAAAAAAAAAA....",
1940
+ "....AAAAAAAAAAAAAAAA....",
1941
+ ".....AAAAAAHHAAAAAA.....",
1942
+ ".....AAAAAAHHAAAAAA.....",
1943
+ "......AAAAAAAAAAAA......",
1944
+ ".......AAAAAAAAAA.......",
1945
+ "........AAAAAAAA........",
1946
+ ".........AAAAA..........",
1947
+ "...........AA...........",
1948
+ "........................"
1949
+ ],
1950
+ other: [
1951
+ "........................",
1952
+ "........................",
1953
+ "........................",
1954
+ "...........AA...........",
1955
+ "...........AA...........",
1956
+ "..........AAAA..........",
1957
+ "..........AAAA..........",
1958
+ "..........AAAA..........",
1959
+ ".........AAAAAA.........",
1960
+ "........AAAAAAAA........",
1961
+ ".....AAAAAAAAAAAAAA.....",
1962
+ "...AAAAAAAAAAAAAAAAAA...",
1963
+ "...AAAAAAAAAAAAAAAAAA...",
1964
+ ".....AAAAAAAAAAAAAA.....",
1965
+ "........AAAAAAAA........",
1966
+ ".........AAAAAA.........",
1967
+ "..........AAAA..........",
1968
+ "..........AAAA..........",
1969
+ "..........AAAA..........",
1970
+ "...........AA...........",
1971
+ "...........AA...........",
1972
+ "........................",
1973
+ "........................",
1974
+ "........................"
1975
+ ]
1976
+ };
1977
+ const PIXEL_SYMBOLS = Object.keys(SYMBOLS);
1978
+ const INK = "#2a2230";
1979
+ const channels = (color) => {
1980
+ const n = Number.parseInt(color.slice(1), 16);
1981
+ return [
1982
+ 16,
1983
+ 8,
1984
+ 0
1985
+ ].map((bit) => n >> bit & 255);
1986
+ };
1987
+ const mix = (base, tint, k) => {
1988
+ const t = channels(tint);
1989
+ return `#${channels(base).map((v, i) => Math.round(v * (1 - k) + t[i] * k).toString(16).padStart(2, "0")).join("")}`;
1990
+ };
1991
+ function pixelSymbolCells(symbol, color) {
1992
+ return symbolArtCells(SYMBOLS[symbol], color);
1993
+ }
1994
+ /**
1995
+ * Colours a 24×24 character-art symbol (`A` body, `H` highlight, anything else empty) into
1996
+ * cells on the 32 grid: the body in `color`, highlights lighter, the lower-right edge darker,
1997
+ * and a one-pixel ink outline. This is how every built-in symbol is drawn, so a host can add
1998
+ * its own symbols in the same style without touching the package.
1999
+ */
2000
+ function symbolArtCells(rows, color) {
2001
+ const body = /* @__PURE__ */ new Map();
2002
+ rows.forEach((row, y) => [...row].forEach((code, x) => {
2003
+ if (code === "A" || code === "H") body.set(`${x + 4},${y + 4}`, code);
2004
+ }));
2005
+ const light = mix(color, "#ffffff", .75);
2006
+ const low = mix(color, INK, .25);
2007
+ const cells = [];
2008
+ for (const [key, code] of body) {
2009
+ const [x, y] = key.split(",").map(Number);
2010
+ const edge = x + y > 34 && !body.has(`${x + 1},${y + 1}`);
2011
+ cells.push({
2012
+ x,
2013
+ y,
2014
+ c: code === "H" ? light : edge ? low : color
2015
+ });
2016
+ }
2017
+ const outline = /* @__PURE__ */ new Set();
2018
+ for (const key of body.keys()) {
2019
+ const [x, y] = key.split(",").map(Number);
2020
+ for (const [dx, dy] of [
2021
+ [1, 0],
2022
+ [-1, 0],
2023
+ [0, 1],
2024
+ [0, -1]
2025
+ ]) {
2026
+ const next = `${x + dx},${y + dy}`;
2027
+ if (body.has(next) || outline.has(next)) continue;
2028
+ outline.add(next);
2029
+ cells.push({
2030
+ x: x + dx,
2031
+ y: y + dy,
2032
+ c: INK
2033
+ });
2034
+ }
2035
+ }
2036
+ return cells;
2037
+ }
2038
+
2039
+ //#endregion
2040
+ export { AVATAR_COLORS, AVATAR_HAIR_PARTS, AVATAR_PARTS, AVATAR_PRESETS, AVATAR_RANGES, AVATAR_SWATCHES, AVATAR_TURNS, DEFAULT_RECIPE, PIXEL_SYMBOLS, canonicalRecipe, createSeededRecipe, detailedRecipe, faceCells, isPixelAvatarRecipe, pixelAvatarSvg, pixelFigure, pixelSymbolCells, pixelTileColor, seededRandom, seededRecipe, symbolArtCells };
2041
+ //# sourceMappingURL=index.js.map