@doan-labs/peek 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js ADDED
@@ -0,0 +1,2302 @@
1
+ // src/vue.ts
2
+ import {
3
+ computed,
4
+ defineComponent,
5
+ h,
6
+ onBeforeUnmount,
7
+ onMounted,
8
+ ref,
9
+ useId,
10
+ watch
11
+ } from "vue";
12
+
13
+ // src/tables.ts
14
+ var INK = {
15
+ ink: "#1A1918",
16
+ bone: "#F3F0E8",
17
+ paper: "#FFFDF8"
18
+ };
19
+ var FACES = {
20
+ diamond: {
21
+ body: { d: "M170 20L320 170L170 320L20 170Z" },
22
+ bottom: 320,
23
+ sink: 30,
24
+ eyes: [
25
+ [132.5, 155],
26
+ [207.5, 155]
27
+ ],
28
+ rx: 20,
29
+ ry: 27.5,
30
+ pr: 10,
31
+ brow: { y: 102.5, hw: 18.75, arch: 15, w: 5.62 },
32
+ cheeks: [
33
+ [117.5, 200],
34
+ [222.5, 200]
35
+ ],
36
+ crx: 11.25,
37
+ cry: 5.62,
38
+ mouth: { x: 170, y: 218.75, w: 15, d: 16.25, sw: 4.38 },
39
+ crown: {
40
+ poly: [
41
+ [170, 20],
42
+ [320, 170],
43
+ [170, 320],
44
+ [20, 170]
45
+ ],
46
+ r: 0
47
+ }
48
+ },
49
+ semicircle: {
50
+ body: { d: "M0 255A170 170 0 0 1 340 255Z" },
51
+ bottom: 255,
52
+ sink: 0,
53
+ eyes: [
54
+ [102.5, 175],
55
+ [237.5, 175]
56
+ ],
57
+ rx: 30,
58
+ ry: 25,
59
+ pr: 11.25,
60
+ brow: { y: 127.5, hw: 25, arch: 15, w: 5.62 },
61
+ cheeks: [
62
+ [55, 212.5],
63
+ [285, 212.5]
64
+ ],
65
+ crx: 11.25,
66
+ cry: 5.62,
67
+ mouth: { x: 170, y: 221.25, w: 16.25, d: 16.25, sw: 4.38 },
68
+ crown: { poly: [[170, 255]], r: 170 }
69
+ },
70
+ circle: {
71
+ body: { circle: [170, 170, 150] },
72
+ bottom: 320,
73
+ sink: 20,
74
+ eyes: [
75
+ [112.8, 162.2],
76
+ [227.2, 162.2]
77
+ ],
78
+ rx: 29.9,
79
+ ry: 29.9,
80
+ pr: 13,
81
+ brow: { y: 110.2, hw: 23.4, arch: 15.6, w: 5.85 },
82
+ cheeks: [
83
+ [84.2, 203.8],
84
+ [255.8, 203.8]
85
+ ],
86
+ crx: 11.7,
87
+ cry: 5.85,
88
+ mouth: { x: 170, y: 223.3, w: 16.9, d: 16.9, sw: 4.55 },
89
+ crown: { poly: [[170, 170]], r: 150 }
90
+ },
91
+ triangle: {
92
+ body: { d: "M170 40.8L312.8 299.2L27.2 299.2Z", stroke: 27.2 },
93
+ bottom: 312.8,
94
+ sink: 0,
95
+ eyes: [
96
+ [132.6, 210.8],
97
+ [207.4, 210.8]
98
+ ],
99
+ rx: 20.4,
100
+ ry: 27.2,
101
+ pr: 10,
102
+ brow: { y: 166.6, hw: 18.7, arch: 15.3, w: 5.62 },
103
+ cheeks: [
104
+ [98.6, 238],
105
+ [241.4, 238]
106
+ ],
107
+ crx: 11.5,
108
+ cry: 5.75,
109
+ mouth: { x: 170, y: 254, w: 17, d: 16.25, sw: 4.38 },
110
+ crown: {
111
+ poly: [
112
+ [170, 40.8],
113
+ [312.8, 299.2],
114
+ [27.2, 299.2]
115
+ ],
116
+ r: 13.6
117
+ }
118
+ }
119
+ };
120
+ var COLORS = {
121
+ lavender: { body: "#D8CDF0", deep: "#BDAEE6" },
122
+ fog: { body: "#C8D6E8", deep: "#A8BCDC" },
123
+ clay: { body: "#F1CDBF", deep: "#E2A893" },
124
+ mint: { body: "#CFE7D6", deep: "#A5CDB1" },
125
+ butter: { body: "#ECE2B9", deep: "#D9C284" },
126
+ rose: { body: "#EFCAD7", deep: "#DFA4BA" },
127
+ aqua: { body: "#BDE1E5", deep: "#8FC7D1" }
128
+ };
129
+ var VARIANTS = ["peek", "haze", "bloc"];
130
+ var HAZE = {
131
+ lavender: { tint: "#C8B1F5", glow: "#F2EDFD" },
132
+ fog: { tint: "#9EC3F2", glow: "#E6F1FF" },
133
+ clay: { tint: "#F4AE93", glow: "#FEECE5" },
134
+ mint: { tint: "#A3DAB5", glow: "#E5F5EA" },
135
+ butter: { tint: "#E1CC7A", glow: "#F4F0E0" },
136
+ rose: { tint: "#F0A9C4", glow: "#FDEAF1" },
137
+ aqua: { tint: "#85D3DC", glow: "#E0F5F7" }
138
+ };
139
+ var WHEEL = [
140
+ "clay",
141
+ "butter",
142
+ "mint",
143
+ "aqua",
144
+ "fog",
145
+ "lavender",
146
+ "rose"
147
+ ];
148
+ var BLOC = {
149
+ lavender: "#B093E5",
150
+ fog: "#7BA8DF",
151
+ clay: "#E38A66",
152
+ mint: "#82CB9B",
153
+ butter: "#E2CA6A",
154
+ rose: "#DF84A8",
155
+ aqua: "#5EC3CD"
156
+ };
157
+ var PARTS = {
158
+ eyes: ["oval", "bead", "ring"],
159
+ brows: ["arch", "bar", "wedge", "dash"],
160
+ mouth: ["poly", "round", "line", "box"],
161
+ cheeks: ["oval", "dots", "lines"],
162
+ trait: ["square", "fin", "ring", "dot", "peak"]
163
+ };
164
+ var ACCESSORY_INK = { knot: "#E5412D" };
165
+ var ACCESSORIES = {
166
+ eyewear: ["none", "glasses", "sunglasses"],
167
+ headwear: ["none", "bow", "cap", "sprout"],
168
+ neckwear: ["none", "tie"]
169
+ };
170
+ var VERSIONS = {
171
+ 1: {
172
+ face: 4,
173
+ color: 7,
174
+ eyes: 3,
175
+ brows: 4,
176
+ mouth: 4,
177
+ cheeks: 3,
178
+ trait: 5,
179
+ eyewear: 1,
180
+ headwear: 1,
181
+ neckwear: 1
182
+ }
183
+ };
184
+ var LATEST = 1;
185
+ var GROUPS = {
186
+ eyes: ["lid", "lower", "lidTilt", "eyeS", "lidAsym"],
187
+ pupil: ["pupil", "gx", "gy", "shine"],
188
+ brows: ["browY", "browTilt", "browArch", "browW", "browAsym"],
189
+ mouth: ["mw", "mt", "mb", "mx", "my", "mk"],
190
+ body: ["alt", "rot", "x", "sx", "sy"],
191
+ extra: ["blush", "hair", "dim", "thing"]
192
+ };
193
+ var BASE = {
194
+ alt: 0,
195
+ rot: 0,
196
+ x: 0,
197
+ sx: 1,
198
+ sy: 1,
199
+ lid: 0.1,
200
+ lower: 0,
201
+ lidTilt: 0,
202
+ eyeS: 1,
203
+ lidAsym: 0,
204
+ pupil: 1,
205
+ gx: 0,
206
+ gy: 0,
207
+ shine: 0.7,
208
+ browY: 0,
209
+ browTilt: 0,
210
+ browArch: 0.4,
211
+ browW: 1,
212
+ browAsym: 0,
213
+ mw: 0.72,
214
+ mt: 0,
215
+ mb: 0.5,
216
+ mx: 0,
217
+ my: 0,
218
+ mk: 0,
219
+ blush: 0,
220
+ hair: 0,
221
+ dim: 0,
222
+ thing: 0
223
+ };
224
+ var S = (o) => ({ ...BASE, ...o });
225
+ var EXPRESSIONS = {
226
+ normal: S({}),
227
+ happy: S({
228
+ lid: 0,
229
+ lower: 0.42,
230
+ shine: 1,
231
+ gy: -0.05,
232
+ browY: -5,
233
+ browArch: 1,
234
+ mw: 1,
235
+ mb: 1,
236
+ blush: 1,
237
+ hair: 0.45,
238
+ alt: 0.05
239
+ }),
240
+ sad: S({
241
+ lid: 0.42,
242
+ lidTilt: 16,
243
+ pupil: 0.95,
244
+ gy: 0.8,
245
+ shine: 0,
246
+ browY: 3,
247
+ browTilt: 14,
248
+ browArch: 0,
249
+ mw: 0.8,
250
+ mt: -0.55,
251
+ mb: -0.55,
252
+ my: 6,
253
+ alt: -0.16,
254
+ sy: 0.985,
255
+ hair: -0.75
256
+ }),
257
+ angry: S({
258
+ lid: 0.34,
259
+ lidTilt: -22,
260
+ lower: 0.12,
261
+ pupil: 0.82,
262
+ gy: 0.1,
263
+ shine: 0,
264
+ browY: 5,
265
+ browTilt: -21,
266
+ browArch: 0,
267
+ browW: 1.3,
268
+ mw: 0.8,
269
+ mt: -0.08,
270
+ mb: -0.08,
271
+ my: 3,
272
+ sx: 1.02,
273
+ sy: 0.98,
274
+ hair: 0.25
275
+ }),
276
+ sleepy: S({
277
+ lid: 1,
278
+ lidTilt: 4,
279
+ pupil: 0.9,
280
+ gy: 0.5,
281
+ shine: 0,
282
+ browY: 6,
283
+ browTilt: 3,
284
+ browArch: 0.15,
285
+ browW: 0.9,
286
+ mw: 0.35,
287
+ mt: -0.3,
288
+ mb: 0.3,
289
+ my: 2,
290
+ alt: -1.12,
291
+ rot: -3,
292
+ hair: -1
293
+ }),
294
+ curious: S({
295
+ lid: 0,
296
+ eyeS: 1.06,
297
+ pupil: 1.05,
298
+ shine: 1,
299
+ browY: -3,
300
+ browAsym: 9,
301
+ browArch: 0.8,
302
+ mw: 0.38,
303
+ mt: -0.36,
304
+ mb: 0.36,
305
+ rot: 6,
306
+ alt: -0.32,
307
+ hair: 0.3,
308
+ thing: 1
309
+ }),
310
+ surprised: S({
311
+ lid: 0,
312
+ eyeS: 1.2,
313
+ pupil: 0.66,
314
+ shine: 0.8,
315
+ browY: -13,
316
+ browArch: 1,
317
+ mw: 0.55,
318
+ mt: -0.62,
319
+ mb: 0.62,
320
+ alt: 0.36,
321
+ hair: 1
322
+ }),
323
+ excited: S({
324
+ lid: 0,
325
+ lower: 0.5,
326
+ eyeS: 1.08,
327
+ pupil: 1.08,
328
+ shine: 1,
329
+ browY: -9,
330
+ browArch: 1,
331
+ mw: 1.15,
332
+ mb: 1.3,
333
+ blush: 1,
334
+ hair: 1,
335
+ alt: 0.12
336
+ }),
337
+ confused: S({
338
+ lid: 0.22,
339
+ lidAsym: 0.28,
340
+ lidTilt: -4,
341
+ pupil: 0.92,
342
+ gx: -0.35,
343
+ gy: -0.45,
344
+ shine: 0.3,
345
+ browY: -2,
346
+ browAsym: 11,
347
+ browTilt: -5,
348
+ browArch: 0.3,
349
+ mw: 0.6,
350
+ mt: -0.1,
351
+ mb: 0.12,
352
+ mx: 5,
353
+ mk: 13,
354
+ rot: -7,
355
+ alt: -0.05,
356
+ hair: 0.1
357
+ }),
358
+ bored: S({
359
+ lid: 0.5,
360
+ pupil: 0.9,
361
+ gx: -0.55,
362
+ gy: 0.25,
363
+ shine: 0,
364
+ browY: 3,
365
+ browArch: 0.1,
366
+ mw: 0.55,
367
+ mt: 0,
368
+ mb: 0.04,
369
+ mx: -5,
370
+ alt: -0.34,
371
+ hair: -0.45,
372
+ dim: 1
373
+ }),
374
+ attentive: S({
375
+ lid: 0,
376
+ eyeS: 1.1,
377
+ pupil: 0.8,
378
+ gy: -0.1,
379
+ shine: 1,
380
+ browY: -7,
381
+ browArch: 0.7,
382
+ mw: 0.55,
383
+ mb: 0.4,
384
+ alt: 0.15,
385
+ sy: 1.03,
386
+ hair: 0.65
387
+ })
388
+ };
389
+
390
+ // src/bloc.ts
391
+ var RAD = Math.PI / 180;
392
+ var TRI = [
393
+ [170, 12.8],
394
+ [320, 312.8],
395
+ [20, 312.8]
396
+ ];
397
+ var BODY = {
398
+ diamond: FACES.diamond,
399
+ semicircle: FACES.semicircle,
400
+ circle: FACES.circle,
401
+ triangle: {
402
+ body: { d: `M${TRI.map((p) => p.join(" ")).join("L")}Z` },
403
+ crown: { poly: TRI, r: 0 }
404
+ }
405
+ };
406
+ var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.6;
407
+ var boostFor = (px) => px < 30 ? 1.25 : px < 56 ? 1.12 : 1;
408
+ var turn = (x, y, deg) => {
409
+ const c = Math.cos(deg * RAD);
410
+ const s = Math.sin(deg * RAD);
411
+ return [x * c - y * s, x * s + y * c];
412
+ };
413
+ var poly = (pts) => `M${pts.map(([x, y]) => `${f2(x)} ${f2(y)}`).join("L")}Z`;
414
+ function blocParts({ who, pose, o, defs, g }) {
415
+ const sh = FACES[who.face];
416
+ const { body, crown } = BODY[who.face];
417
+ const id = o.id;
418
+ const P = who.persona;
419
+ const spread = P.spread;
420
+ const st = strokeFor(o.size);
421
+ const up = boostFor(o.size);
422
+ const strong = BLOC[who.color];
423
+ const light = o.frame === "bone" || o.frame === "paper" ? COLORS[who.color].deep : COLORS[who.color].body;
424
+ const lineW = sh.brow.w * st;
425
+ defs.push(n("clipPath", "half", { id: `${id}-r` }, [
426
+ n("rect", "half.shape", { x: 170, y: -30, width: 200, height: 400 })
427
+ ]));
428
+ const block = (key, fill, clip) => ("d" in body) ? n("path", key, { d: body.d, fill, "clip-path": clip }) : n("circle", key, {
429
+ cx: body.circle[0],
430
+ cy: body.circle[1],
431
+ r: body.circle[2],
432
+ fill,
433
+ "clip-path": clip
434
+ });
435
+ const eyeS = g("eyeS");
436
+ const r0 = Math.sqrt(sh.rx * sh.ry) * up;
437
+ const lid0 = g("lid");
438
+ const lidAsym = g("lidAsym");
439
+ const lower = clamp(g("lower"), 0, 1);
440
+ const pupil = g("pupil");
441
+ const gx = clamp(g("gx"), -1.1, 1.1);
442
+ const gy = clamp(g("gy"), -1.1, 1.1);
443
+ const tilt = g("lidTilt");
444
+ const bead = who.eyes === "bead";
445
+ const eyes = sh.eyes.map(([ex, ey], i) => {
446
+ const side = i === 0 ? -1 : 1;
447
+ const kk = `eye${i}`;
448
+ let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1);
449
+ lid = lid + (1 - lid) * pose.blink;
450
+ const cut = clamp((lid - 0.1) / 0.9, 0, 1);
451
+ const r = bead ? r0 * 0.7 * eyeS * (0.4 + 0.6 * pupil) : r0 * eyeS;
452
+ const [tx, ty] = bead ? [ex + side * spread + gx * 6, ey + gy * 6] : [ex + side * spread, ey];
453
+ const a = side * tilt;
454
+ const edge = -r - 1 + cut * (2 * r + 2);
455
+ const floor = r + 1 - lower * r * 2.2;
456
+ const W = r + 2;
457
+ const lidAt = (x) => x * Math.tan(a * RAD) + edge / Math.cos(a * RAD);
458
+ const [t0, t1] = [lidAt(-W), lidAt(W)];
459
+ defs.push(n("clipPath", `${kk}.clip`, { id: `${id}-e${i}` }, [
460
+ n("path", `${kk}.clip.shape`, {
461
+ d: poly([
462
+ [-W, t0],
463
+ [W, t1],
464
+ [W, Math.max(floor, t1)],
465
+ [-W, Math.max(floor, t0)]
466
+ ])
467
+ })
468
+ ]));
469
+ const pr = sh.pr * pupil * up;
470
+ const px = gx * Math.max(0, r - pr) * 0.85;
471
+ const py = gy * Math.max(0, r - pr) * 0.75;
472
+ const rim = Math.min(r * 0.27 * Math.min(st, 1.6), r * 0.4);
473
+ const inner = bead ? [n("circle", `${kk}.bead`, { cx: 0, cy: 0, r: f2(r), fill: INK.ink })] : [
474
+ n("circle", `${kk}.white`, {
475
+ cx: 0,
476
+ cy: 0,
477
+ r: f2(r),
478
+ fill: INK.paper
479
+ }),
480
+ n("circle", `${kk}.pupil`, {
481
+ cx: f2(px),
482
+ cy: f2(py),
483
+ r: f2(pr),
484
+ fill: INK.ink
485
+ })
486
+ ];
487
+ if (who.eyes === "ring")
488
+ inner.push(n("circle", `${kk}.rim`, {
489
+ cx: 0,
490
+ cy: 0,
491
+ r: f2(r - rim / 2),
492
+ fill: "none",
493
+ stroke: INK.ink,
494
+ "stroke-width": f2(rim)
495
+ }));
496
+ const co = smooth(0.86, 1, lid);
497
+ const yy = Math.min(edge, r) - lineW / 2;
498
+ return n("g", kk, { transform: `translate(${f2(tx)} ${f2(ty)})` }, [
499
+ n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
500
+ n("path", `${kk}.closed`, {
501
+ d: `M${f2(-r * 0.95)} ${f2(yy)}H${f2(r * 0.95)}`,
502
+ transform: `rotate(${f2(a * 0.6)})`,
503
+ stroke: INK.ink,
504
+ "stroke-width": f2(lineW * 1.4),
505
+ opacity: co > 0.01 ? f2(co) : 0
506
+ })
507
+ ]);
508
+ });
509
+ const bt = pose.browTilt;
510
+ const basym = g("browAsym");
511
+ const th = lineW * 2 * g("browW");
512
+ const hw = sh.eyes[1][0] - 170 + spread + Math.sqrt(sh.rx * sh.ry) * P.mul.eyeS * up;
513
+ const gap = smooth(0, 8, Math.abs(bt)) * th * 0.9;
514
+ const slant = P.add.browTilt * 0.3 - Math.atan(1.3 * basym / (2 * hw)) / RAD;
515
+ const y0 = sh.brow.y + g("browY") - (eyeS - 1) * sh.ry * 0.9 - 0.35 * basym;
516
+ const halves = (dy, len2 = 1) => [-1, 1].map((s2) => {
517
+ const [ox, oy] = turn(s2 * gap / 2, 0, slant);
518
+ const x0 = 170 + ox;
519
+ const yy = y0 + dy + oy;
520
+ const ang = slant + s2 * bt;
521
+ const [ux, uy] = turn(s2 * (hw * len2 - gap / 2), 0, ang);
522
+ const [nx, ny] = turn(0, th / 2, ang);
523
+ return [
524
+ [x0 - nx, yy - ny],
525
+ [x0 + ux - nx, yy + uy - ny],
526
+ [x0 + ux + nx, yy + uy + ny],
527
+ [x0 + nx, yy + ny]
528
+ ];
529
+ });
530
+ const inside = (dy, len2 = 1) => halves(dy, len2).every((q) => q.every(([x, y]) => depth(crown, x, y) + 2 < 0));
531
+ let lo = 0;
532
+ let hi = 0;
533
+ if (!inside(0)) {
534
+ hi = 60;
535
+ for (let k = 0;k < 12; k++) {
536
+ const mid2 = (lo + hi) / 2;
537
+ if (inside(mid2))
538
+ hi = mid2;
539
+ else
540
+ lo = mid2;
541
+ }
542
+ }
543
+ let len = 1;
544
+ if (!inside(hi)) {
545
+ let short = 0;
546
+ for (let k = 0;k < 12; k++) {
547
+ const mid2 = (short + len) / 2;
548
+ if (inside(hi, mid2))
549
+ short = mid2;
550
+ else
551
+ len = mid2;
552
+ }
553
+ len = short;
554
+ }
555
+ const bars = halves(hi, len).map((q, i) => n("path", `bar${i}`, { d: poly(q), fill: INK.ink }));
556
+ const m = sh.mouth;
557
+ const big = (who.face === "diamond" || who.face === "triangle" ? 1.25 : 1) * up;
558
+ const mw = m.w * Math.max(0.05, g("mw")) * big;
559
+ const mt = g("mt") * m.d * big;
560
+ const mb = g("mb") * m.d * big;
561
+ const mid = (mt + mb) / 2;
562
+ const kb = 4 / 3;
563
+ let d;
564
+ if (who.mouth === "poly") {
565
+ const s2 = Math.max(mw * 0.9, (mb - mt) / 2);
566
+ d = poly([
567
+ [-s2, mid],
568
+ [0, mid - s2],
569
+ [s2, mid],
570
+ [0, mid + s2]
571
+ ]);
572
+ } else if (who.mouth === "box") {
573
+ const h = Math.max(mb - mt, lineW * 1.6) / 2;
574
+ const w = mw * 0.85;
575
+ d = poly([
576
+ [-w, mid - h],
577
+ [w, mid - h],
578
+ [w, mid + h],
579
+ [-w, mid + h]
580
+ ]);
581
+ } else if (who.mouth === "line") {
582
+ const h = lineW * 1.4 / 2;
583
+ const w = mw * 1.5;
584
+ const y = mid * 0.5;
585
+ d = poly([
586
+ [-w, y - h],
587
+ [w, y - h],
588
+ [w, y + h],
589
+ [-w, y + h]
590
+ ]);
591
+ } else {
592
+ d = `M${f2(-mw)} 0C${f2(-mw)} ${f2(mb * kb)} ${f2(mw)} ${f2(mb * kb)} ${f2(mw)} 0C${f2(mw)} ${f2(mt * kb)} ${f2(-mw)} ${f2(mt * kb)} ${f2(-mw)} 0Z`;
593
+ }
594
+ const mouth = n("path", "mouth", {
595
+ d,
596
+ transform: `translate(${f2(m.x + g("mx"))} ${f2(m.y + g("my"))}) rotate(${f2(g("mk"))})`,
597
+ fill: INK.ink
598
+ });
599
+ const hair = g("hair");
600
+ const s = seat(crown, Math.max(0, hair) * 8, 110 + Math.max(0, -hair) * 40);
601
+ const shape = who.trait === "square" ? n("rect", "trait.shape", { x: -21, y: -21, width: 42, height: 42 }) : who.trait === "dot" ? n("circle", "trait.shape", { cx: 0, cy: 0, r: 22 }) : who.trait === "ring" ? n("circle", "trait.shape", {
602
+ cx: 0,
603
+ cy: 0,
604
+ r: 17.5,
605
+ fill: "none",
606
+ stroke: strong,
607
+ "stroke-width": 13
608
+ }) : who.trait === "fin" ? n("path", "trait.shape", { d: "M-27 27V-27A54 54 0 0 1 27 27Z" }) : n("rect", "trait.shape", {
609
+ x: -16,
610
+ y: -30,
611
+ width: 32,
612
+ height: 60
613
+ });
614
+ const trait = n("g", "trait", {
615
+ transform: `translate(${f2(s.x)} ${f2(s.y + pose.lag)}) scale(${f2(up)})`,
616
+ fill: strong
617
+ }, [shape]);
618
+ return [
619
+ [block("body", strong), block("body.light", light, `url(#${id}-r)`), trait],
620
+ [...eyes, ...bars, mouth]
621
+ ];
622
+ }
623
+
624
+ // src/identity.ts
625
+ var STYLE = "peek";
626
+ var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
627
+ function fnv1a(str) {
628
+ let h = 2166136261;
629
+ for (const b of new TextEncoder().encode(str)) {
630
+ h ^= b;
631
+ h = Math.imul(h, 16777619) >>> 0;
632
+ }
633
+ return h >>> 0;
634
+ }
635
+ function mulberry32(a) {
636
+ return () => {
637
+ a = a + 1831565813 | 0;
638
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
639
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
640
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
641
+ };
642
+ }
643
+ var LISTS = {
644
+ face: Object.keys(FACES),
645
+ color: Object.keys(COLORS),
646
+ ...PARTS,
647
+ ...ACCESSORIES
648
+ };
649
+ var AXES = Object.keys(LISTS);
650
+ function seed(name, axis, version = LATEST) {
651
+ return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
652
+ }
653
+ function identify(name, { version = LATEST } = {}) {
654
+ const lengths = VERSIONS[version];
655
+ if (!lengths)
656
+ throw new Error(`peek@${version} does not exist`);
657
+ const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
658
+ const rnd = mulberry32(seed(name, "persona", version));
659
+ const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
660
+ const persona = {
661
+ spread: r(-7, 7),
662
+ blink: r(0.75, 1.4),
663
+ add: {
664
+ browY: r(-4, 4),
665
+ browTilt: r(-5, 5),
666
+ browArch: r(-0.2, 0.3),
667
+ rot: r(-3, 3),
668
+ hair: r(-0.2, 0.35),
669
+ gx: r(-0.12, 0.12),
670
+ my: r(-2, 3)
671
+ },
672
+ mul: {
673
+ eyeS: r(0.92, 1.08),
674
+ pupil: r(0.9, 1.12),
675
+ browW: r(0.85, 1.2),
676
+ mw: r(0.85, 1.18)
677
+ }
678
+ };
679
+ const key = tidy(name);
680
+ return {
681
+ key,
682
+ hash: fnv1a(key),
683
+ version,
684
+ face: pick("face"),
685
+ color: pick("color"),
686
+ eyes: pick("eyes"),
687
+ brows: pick("brows"),
688
+ mouth: pick("mouth"),
689
+ cheeks: pick("cheeks"),
690
+ trait: pick("trait"),
691
+ eyewear: pick("eyewear"),
692
+ headwear: pick("headwear"),
693
+ neckwear: pick("neckwear"),
694
+ persona
695
+ };
696
+ }
697
+
698
+ // src/draw.ts
699
+ var VB = [-30, -30, 400, 400];
700
+ var FLOOR = 370;
701
+ var THING = [330, 20];
702
+ var clamp = (v, a, b) => v < a ? a : v > b ? b : v;
703
+ var smooth = (a, b, v) => {
704
+ const t = clamp((v - a) / (b - a), 0, 1);
705
+ return t * t * (3 - 2 * t);
706
+ };
707
+ var f2 = (v) => String(Math.round(v * 100) / 100);
708
+ var f3 = (v) => String(Math.round(v * 1000) / 1000);
709
+ var strokeFor2 = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
710
+ var RAD2 = Math.PI / 180;
711
+ var STEM = "M0 0Q3 -19 0 -36";
712
+ var LEAF0 = "M0 -33C-24 -30 -38 -52 -25 -53C-12 -54 -2 -43 0 -33Z";
713
+ var LEAF1 = "M0 -33C5 -54 28 -61 27 -48C26 -36 11 -31 0 -33Z";
714
+ function frameOf(face) {
715
+ const baseY = FLOOR + face.sink - face.bottom;
716
+ const eyeY = face.eyes[0][1];
717
+ return { baseY, eyeY, R: FLOOR - (eyeY + baseY) };
718
+ }
719
+ function thingGaze(face, x, alt) {
720
+ const { baseY, eyeY, R } = frameOf(face);
721
+ const ey = eyeY + baseY - alt * R;
722
+ return [
723
+ clamp((THING[0] - 170 - x) / 150, -1, 1),
724
+ clamp((THING[1] - ey) / 150, -1, 1)
725
+ ];
726
+ }
727
+ function restPose(who, expression, gaze) {
728
+ const s = EXPRESSIONS[expression];
729
+ const pose = { ...s, blink: 0, lag: 0, expression };
730
+ if (expression === "curious")
731
+ pose.rot = 7;
732
+ const g = gaze ?? (expression === "curious" ? thingGaze(FACES[who.face], 0, s.alt) : null);
733
+ if (g)
734
+ [pose.gx, pose.gy] = g;
735
+ return pose;
736
+ }
737
+ function seat(crown, d, u) {
738
+ const R = Math.max(0, crown.r + d);
739
+ const at = ([x, y], b2) => ({
740
+ x: x + R * Math.sin(b2),
741
+ y: y - R * Math.cos(b2),
742
+ a: b2 / RAD2
743
+ });
744
+ const P = crown.poly;
745
+ if (P.length === 1)
746
+ return at(P[0], R ? u / R : 0);
747
+ const n2 = P.length;
748
+ const dir = u >= 0 ? 1 : -1;
749
+ let left = Math.abs(u);
750
+ let i = 0;
751
+ let b = 0;
752
+ for (let step = 0;step < n2; step++) {
753
+ const j = (i + dir + n2) % n2;
754
+ const [x0, y0] = P[i];
755
+ const [x1, y1] = P[j];
756
+ const ex = x1 - x0;
757
+ const ey = y1 - y0;
758
+ const L = Math.hypot(ex, ey);
759
+ const nx = dir * ey / L;
760
+ const ny = -dir * ex / L;
761
+ const be = Math.atan2(nx, -ny);
762
+ let db = be - b;
763
+ while (dir * db < 0)
764
+ db += dir * 2 * Math.PI;
765
+ const arc = R * Math.abs(db);
766
+ if (left <= arc)
767
+ return at(P[i], R ? b + dir * left / R : b);
768
+ left -= arc;
769
+ if (left <= L)
770
+ return {
771
+ x: x0 + ex / L * left + R * nx,
772
+ y: y0 + ey / L * left + R * ny,
773
+ a: be / RAD2
774
+ };
775
+ left -= L;
776
+ i = j;
777
+ b = be;
778
+ }
779
+ return at(P[i], b);
780
+ }
781
+ function depth(crown, x, y) {
782
+ const P = crown.poly;
783
+ if (P.length === 1)
784
+ return Math.hypot(x - P[0][0], y - P[0][1]) - crown.r;
785
+ let out = -Infinity;
786
+ let near = Infinity;
787
+ for (let i = 0;i < P.length; i++) {
788
+ const [ax, ay] = P[i];
789
+ const [bx, by] = P[(i + 1) % P.length];
790
+ const ex = bx - ax;
791
+ const ey = by - ay;
792
+ const L2 = ex * ex + ey * ey;
793
+ const L = Math.sqrt(L2);
794
+ out = Math.max(out, ((x - ax) * ey - (y - ay) * ex) / L);
795
+ const t = clamp(((x - ax) * ex + (y - ay) * ey) / L2, 0, 1);
796
+ near = Math.min(near, Math.hypot(x - ax - t * ex, y - ay - t * ey));
797
+ }
798
+ return (out <= 0 ? out : near) - crown.r;
799
+ }
800
+ var RING_U = 158 * 35 * RAD2;
801
+ function ringWalk(sh) {
802
+ const ex = sh.eyes[1][0] + sh.brow.hw + 7;
803
+ const top = sh.brow.y - 30;
804
+ const bottom = sh.brow.y + 8;
805
+ let u = RING_U;
806
+ for (;u < 400; u += 2) {
807
+ const s = seat(sh.crown, 8, u);
808
+ const dx = s.x - ex;
809
+ const dy = s.y - clamp(s.y, top, bottom);
810
+ if (s.x > ex ? Math.hypot(dx, dy) > 27 : s.y < top - 27)
811
+ break;
812
+ }
813
+ return u > RING_U ? { rest: u + 20, min: u } : { rest: u, min: -Infinity };
814
+ }
815
+ function n(tag, key, attrs, children = []) {
816
+ const out = {};
817
+ for (const k in attrs) {
818
+ const v = attrs[k];
819
+ if (v !== undefined && v !== false)
820
+ out[k] = String(v);
821
+ }
822
+ return { tag, key, attrs: out, children };
823
+ }
824
+ function prune(node) {
825
+ if (node.attrs.opacity === "0")
826
+ return null;
827
+ const children = node.children.map(prune).filter((c) => c !== null);
828
+ if (node.tag === "g" && children.length === 0)
829
+ return null;
830
+ return { ...node, children };
831
+ }
832
+ function draw(who, pose, o) {
833
+ const sh = FACES[who.face];
834
+ const P = who.persona;
835
+ const mul = P.mul;
836
+ const add = P.add;
837
+ const g = (c) => pose[c] * (mul[c] ?? 1) + (add[c] ?? 0);
838
+ const id = o.id;
839
+ const { baseY, R } = frameOf(sh);
840
+ const defs = [];
841
+ const VBr = { x: VB[0], y: VB[1], width: VB[2], height: VB[3] };
842
+ defs.push(n("clipPath", "frame.clip", { id: `${id}-f` }, [
843
+ o.square ? n("rect", "frame.clip.shape", VBr) : n("circle", "frame.clip.shape", { cx: 170, cy: 170, r: 200 })
844
+ ]));
845
+ const k = o.riso ? smooth(120, 480, o.size) : 0;
846
+ const riso = k > 0.01;
847
+ const upp = VB[2] / Math.max(o.size, 1);
848
+ const mk = k * k;
849
+ const plate = (p, seedA, seedB, sc2) => n("filter", `riso.${p}`, {
850
+ id: `${id}-p${p}`,
851
+ x: "-20%",
852
+ y: "-20%",
853
+ width: "140%",
854
+ height: "140%",
855
+ "color-interpolation-filters": "sRGB"
856
+ }, [
857
+ n("feTurbulence", `riso.${p}.warp`, {
858
+ type: "fractalNoise",
859
+ baseFrequency: "0.032",
860
+ numOctaves: 2,
861
+ seed: seedA,
862
+ result: "warp"
863
+ }),
864
+ n("feDisplacementMap", `riso.${p}.disp`, {
865
+ in: "SourceGraphic",
866
+ in2: "warp",
867
+ scale: f2(sc2 * k),
868
+ xChannelSelector: "R",
869
+ yChannelSelector: "G",
870
+ result: "moved"
871
+ }),
872
+ n("feTurbulence", `riso.${p}.grain`, {
873
+ type: "fractalNoise",
874
+ baseFrequency: "0.85",
875
+ numOctaves: 2,
876
+ seed: seedB,
877
+ result: "grain"
878
+ }),
879
+ n("feColorMatrix", `riso.${p}.mask`, {
880
+ in: "grain",
881
+ type: "matrix",
882
+ values: `0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ${f3(-8 * mk)} 0 0 0 ${f3(1 + 5.85 * mk)}`,
883
+ result: "mask"
884
+ }),
885
+ n("feComposite", `riso.${p}.out`, {
886
+ in: "moved",
887
+ in2: "mask",
888
+ operator: "in"
889
+ })
890
+ ]);
891
+ const grainy = riso && o.frame !== "none";
892
+ if (riso)
893
+ defs.push(plate("A", 4, 11, 3.2), plate("B", 9, 23, 2.2));
894
+ if (grainy) {
895
+ const [r, gg, b] = o.frame === "ink" ? [0.953, 0.941, 0.91] : [0.102, 0.098, 0.094];
896
+ defs.push(n("filter", "grain.filter", {
897
+ id: `${id}-grain`,
898
+ x: 0,
899
+ y: 0,
900
+ width: 1,
901
+ height: 1,
902
+ "color-interpolation-filters": "sRGB"
903
+ }, [
904
+ n("feTurbulence", "grain.noise", {
905
+ type: "fractalNoise",
906
+ baseFrequency: "0.9",
907
+ numOctaves: 2,
908
+ seed: 5
909
+ }),
910
+ n("feColorMatrix", "grain.matrix", {
911
+ type: "matrix",
912
+ values: `0 0 0 0 ${r} 0 0 0 0 ${gg} 0 0 0 0 ${b} 7 0 0 0 -5.1`
913
+ })
914
+ ]));
915
+ }
916
+ const dim = clamp(g("dim"), 0, 1);
917
+ if (o.live || dim > 0.01)
918
+ defs.push(n("filter", "dim.filter", { id: `${id}-dim`, "color-interpolation-filters": "sRGB" }, [
919
+ n("feColorMatrix", "dim.matrix", {
920
+ type: "saturate",
921
+ values: f3(1 - dim * 0.55)
922
+ })
923
+ ]));
924
+ const alt = g("alt");
925
+ const rot = g("rot");
926
+ const x = g("x");
927
+ const sx = g("sx");
928
+ const sy = g("sy");
929
+ const B = sh.bottom;
930
+ const ty = baseY - alt * R;
931
+ const sc = (v) => Math.round(v * 1e5) / 1e5;
932
+ const [front, back] = (o.variant === "bloc" ? blocParts : peekParts)({
933
+ who,
934
+ pose,
935
+ o,
936
+ defs,
937
+ g
938
+ });
939
+ const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
940
+ const av = n("g", "av", {
941
+ transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
942
+ filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
943
+ }, [
944
+ n("g", "plateA", {
945
+ transform: off(-0.55, 0.45),
946
+ filter: riso ? `url(#${id}-pA)` : undefined
947
+ }, front),
948
+ n("g", "plateB", {
949
+ transform: off(0.6, -0.5),
950
+ filter: riso ? `url(#${id}-pB)` : undefined
951
+ }, back)
952
+ ]);
953
+ const scene = [];
954
+ if (o.frame !== "none") {
955
+ const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
956
+ scene.push(n("rect", "bg", { ...VBr, fill: bg }));
957
+ const th = clamp(g("thing"), 0, 1);
958
+ const dots = [];
959
+ for (let j = 0;j < 3; j++)
960
+ for (let i = 0;i < 3; i++) {
961
+ const q = j * 3 + i;
962
+ const kk = smooth(q / 12, q / 12 + 0.35, th);
963
+ dots.push(n("circle", `lattice.${q}`, {
964
+ cx: 312 + i * 18,
965
+ cy: 2 + j * 18,
966
+ r: f2(1.2 + 2.2 * kk),
967
+ opacity: f3(kk * 0.55)
968
+ }));
969
+ }
970
+ scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
971
+ }
972
+ scene.push(av);
973
+ if (grainy)
974
+ scene.push(n("rect", "grain", {
975
+ ...VBr,
976
+ filter: `url(#${id}-grain)`,
977
+ opacity: o.frame === "ink" ? 0.3 : 0.16,
978
+ "pointer-events": "none"
979
+ }));
980
+ const root = n("svg", "root", {
981
+ xmlns: "http://www.w3.org/2000/svg",
982
+ viewBox: VB.join(" "),
983
+ width: o.size,
984
+ height: o.size,
985
+ role: o.title === false ? undefined : "img",
986
+ "aria-label": o.title === false ? undefined : o.title,
987
+ "aria-hidden": o.title === false ? "true" : undefined
988
+ }, [
989
+ n("defs", "defs", {}, defs),
990
+ n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
991
+ ]);
992
+ return o.live ? root : prune(root) ?? root;
993
+ }
994
+ var HAZE_LEVEL = {
995
+ medium: {
996
+ deep: [0.502, 0.224],
997
+ glow: [0.749, 0.337],
998
+ tint: [0.4, 0.5],
999
+ tintStops: [
1000
+ [0, 0.878],
1001
+ [0.15, 0.878],
1002
+ [0.618, 0.396]
1003
+ ]
1004
+ },
1005
+ light: {
1006
+ deep: [0.298, 0.133],
1007
+ glow: [0.549, 0.247],
1008
+ tint: [0.31, 0.387],
1009
+ tintStops: [
1010
+ [0, 0.549],
1011
+ [0.55, 0.247]
1012
+ ]
1013
+ }
1014
+ };
1015
+ function hazePaint(who, o) {
1016
+ const sh = FACES[who.face];
1017
+ const id = o.id;
1018
+ const L = HAZE_LEVEL[o.size < 32 ? "light" : "medium"];
1019
+ const xs = sh.crown.poly.map((p) => p[0]);
1020
+ const ys = sh.crown.poly.map((p) => p[1]);
1021
+ const x0 = Math.min(...xs) - sh.crown.r;
1022
+ const y0 = Math.min(...ys) - sh.crown.r;
1023
+ const W = Math.max(...xs) + sh.crown.r - x0;
1024
+ const H = sh.bottom - y0;
1025
+ const rnd = mulberry32(fnv1a(`haze:${who.key ?? ""}`));
1026
+ const warm = rnd() < 0.5;
1027
+ const left = rnd() < 0.5;
1028
+ const t = rnd();
1029
+ const tintX = 0.04 + 0.174 * t;
1030
+ const tintY = 0.649 + 0.202 * t;
1031
+ const glowX = 0.38 - 0.12 * rnd();
1032
+ const glowY = 0.12 + 0.099 * rnd();
1033
+ const deepX = 0.251 - 0.151 * rnd();
1034
+ const tintA = 90 - 180 * rnd();
1035
+ const at = (inset, onLeft) => x0 + W * (onLeft ? inset : 1 - inset);
1036
+ const i = WHEEL.indexOf(who.color);
1037
+ const near = WHEEL[(i + (warm ? WHEEL.length - 1 : 1)) % WHEEL.length];
1038
+ const { body, deep } = COLORS[who.color];
1039
+ const blob = (k, color, [cx, cy, a, rx, ry], stops) => n("radialGradient", `haze.${k}`, {
1040
+ id: `${id}-h${k[0]}`,
1041
+ gradientUnits: "userSpaceOnUse",
1042
+ cx: 0,
1043
+ cy: 0,
1044
+ r: 1,
1045
+ gradientTransform: `translate(${f2(cx)} ${f2(cy)}) rotate(${f2(a)}) scale(${f2(rx)} ${f2(ry)})`
1046
+ }, [...stops, [1, 0]].map(([at2, op], j) => n("stop", `haze.${k}.${j}`, {
1047
+ offset: at2,
1048
+ "stop-color": color,
1049
+ "stop-opacity": op
1050
+ })));
1051
+ const fade = ([a, b]) => [
1052
+ [0, a],
1053
+ [0.55, b]
1054
+ ];
1055
+ const blobs = [
1056
+ blob("deep", deep, [at(deepX, !left), y0 + H * 0.95, 0, W * 0.5, W * 0.35], fade(L.deep)),
1057
+ blob("tint", HAZE[near].tint, [at(tintX, left), y0 + H * tintY, tintA, W * L.tint[0], W * L.tint[1]], L.tintStops),
1058
+ blob("glow", HAZE[who.color].glow, [at(glowX, !left), y0 + H * glowY, 0, W * 0.475, W * 0.356], fade(L.glow))
1059
+ ];
1060
+ const shape = (key, paint) => ("d" in sh.body) ? n("path", key, {
1061
+ d: sh.body.d,
1062
+ fill: paint,
1063
+ stroke: sh.body.stroke ? paint : undefined,
1064
+ "stroke-width": sh.body.stroke,
1065
+ "stroke-linejoin": sh.body.stroke ? "round" : undefined
1066
+ }) : n("circle", key, {
1067
+ cx: sh.body.circle[0],
1068
+ cy: sh.body.circle[1],
1069
+ r: sh.body.circle[2],
1070
+ fill: paint
1071
+ });
1072
+ return [
1073
+ ...blobs,
1074
+ n("g", "haze", { id: `${id}-hz` }, [
1075
+ shape("haze.body", body),
1076
+ ...["deep", "tint", "glow"].map((k) => shape(`haze.${k}.fill`, `url(#${id}-h${k[0]})`))
1077
+ ])
1078
+ ];
1079
+ }
1080
+ function peekParts({ who, pose, o, defs, g }) {
1081
+ const sh = FACES[who.face];
1082
+ const { body: fill, deep } = COLORS[who.color];
1083
+ const P = who.persona;
1084
+ const spread = P.spread;
1085
+ const id = o.id;
1086
+ const st = strokeFor2(o.size);
1087
+ const haze = o.variant === "haze";
1088
+ if (haze)
1089
+ defs.push(...hazePaint(who, o));
1090
+ const bodyNode = haze ? n("use", "body", { href: `#${id}-hz` }) : ("d" in sh.body) ? n("path", "body", {
1091
+ d: sh.body.d,
1092
+ fill,
1093
+ stroke: sh.body.stroke ? fill : undefined,
1094
+ "stroke-width": sh.body.stroke,
1095
+ "stroke-linejoin": sh.body.stroke ? "round" : undefined
1096
+ }) : n("circle", "body", {
1097
+ cx: sh.body.circle[0],
1098
+ cy: sh.body.circle[1],
1099
+ r: sh.body.circle[2],
1100
+ fill
1101
+ });
1102
+ const eyeS = g("eyeS");
1103
+ const rx = sh.rx * eyeS;
1104
+ const ry = sh.ry * eyeS;
1105
+ const lid0 = g("lid");
1106
+ const lidAsym = g("lidAsym");
1107
+ const lower = clamp(g("lower"), 0, 1);
1108
+ const blink = pose.blink;
1109
+ const pupil = g("pupil");
1110
+ const gx = clamp(g("gx"), -1.1, 1.1);
1111
+ const gy = clamp(g("gy"), -1.1, 1.1);
1112
+ const tilt = g("lidTilt");
1113
+ const shine = clamp(g("shine"), 0, 1);
1114
+ const sw = sh.brow.w * st;
1115
+ const bead = who.eyes === "bead";
1116
+ const eyes = sh.eyes.map(([ex, ey], i) => {
1117
+ const side = i === 0 ? -1 : 1;
1118
+ const kk = `eye${i}`;
1119
+ let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1);
1120
+ lid = lid + (1 - lid) * blink;
1121
+ defs.push(n("clipPath", `${kk}.clip`, { id: `${id}-e${i}` }, [
1122
+ n("ellipse", `${kk}.clip.shape`, {
1123
+ cx: 0,
1124
+ cy: 0,
1125
+ rx: f2(rx),
1126
+ ry: f2(ry)
1127
+ })
1128
+ ]), n("clipPath", `${kk}.lidclip`, { id: `${id}-l${i}` }, [
1129
+ n("ellipse", `${kk}.lidclip.shape`, {
1130
+ cx: 0,
1131
+ cy: 0,
1132
+ rx: f2(rx + 1.6),
1133
+ ry: f2(ry + 1.6)
1134
+ })
1135
+ ]));
1136
+ const br = Math.min(sh.rx, sh.ry) * 0.62;
1137
+ const pr = bead ? br * eyeS * (0.4 + 0.6 * pupil) : sh.pr * pupil;
1138
+ const base = bead ? br : sh.pr;
1139
+ const px = gx * Math.max(0, sh.rx - base) * 0.9;
1140
+ const py = gy * Math.max(0, sh.ry - base) * 0.8;
1141
+ const rimW = Math.min(sw * 0.8, Math.min(sh.rx, sh.ry) * 0.28);
1142
+ const inner = [];
1143
+ if (!bead)
1144
+ inner.push(n("ellipse", `${kk}.white`, {
1145
+ cx: 0,
1146
+ cy: 0,
1147
+ rx: f2(rx),
1148
+ ry: f2(ry),
1149
+ fill: INK.paper
1150
+ }));
1151
+ inner.push(n("circle", `${kk}.pupil`, {
1152
+ cx: f2(px),
1153
+ cy: f2(py),
1154
+ r: f2(pr),
1155
+ fill: INK.ink
1156
+ }), n("circle", `${kk}.shine`, {
1157
+ cx: f2(px + pr * 0.44),
1158
+ cy: f2(py - pr * 0.44),
1159
+ r: f2(pr * (bead ? 0.26 : 0.3)),
1160
+ fill: INK.paper,
1161
+ opacity: f2(shine)
1162
+ }));
1163
+ if (who.eyes === "ring")
1164
+ inner.push(n("ellipse", `${kk}.rim`, {
1165
+ cx: 0,
1166
+ cy: 0,
1167
+ rx: f2(rx - rimW / 2),
1168
+ ry: f2(ry - rimW / 2),
1169
+ fill: "none",
1170
+ stroke: INK.ink,
1171
+ "stroke-width": f2(rimW)
1172
+ }));
1173
+ const edge = -ry - 1.6 + lid * (2 * ry + 3.2);
1174
+ const a = side * tilt;
1175
+ const lrx = rx * 1.55;
1176
+ const lry = ry * 1.15;
1177
+ const top = ry + 1.6 - lower * ry * 1.5;
1178
+ const [etx, ety] = bead ? [ex + side * spread + gx * 6, ey + gy * 6] : [ex + side * spread, ey];
1179
+ const lids = [
1180
+ n("rect", `${kk}.lid`, {
1181
+ x: f2(-rx * 2.2),
1182
+ y: f2(-ry * 3),
1183
+ width: f2(rx * 4.4),
1184
+ height: f2(Math.max(0, edge + ry * 3)),
1185
+ transform: `rotate(${f2(a)})`,
1186
+ fill
1187
+ }),
1188
+ n("ellipse", `${kk}.lower`, {
1189
+ cx: 0,
1190
+ cy: f2(top + lry),
1191
+ rx: f2(lrx),
1192
+ ry: f2(lry),
1193
+ fill
1194
+ })
1195
+ ];
1196
+ if (haze)
1197
+ defs.push(n("clipPath", `${kk}.lidshape`, { id: `${id}-s${i}` }, lids));
1198
+ const parts = [
1199
+ n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
1200
+ n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, haze ? [
1201
+ n("g", `${kk}.lidpaint`, { "clip-path": `url(#${id}-s${i})` }, [
1202
+ n("use", `${kk}.lidbody`, {
1203
+ href: `#${id}-hz`,
1204
+ transform: `translate(${f2(-etx)} ${f2(-ety)})`
1205
+ })
1206
+ ])
1207
+ ] : lids)
1208
+ ];
1209
+ if (who.eyes === "ring")
1210
+ parts.push(n("g", `${kk}.edges`, {
1211
+ "clip-path": `url(#${id}-e${i})`,
1212
+ fill: "none",
1213
+ stroke: INK.ink,
1214
+ "stroke-width": f2(rimW)
1215
+ }, [
1216
+ n("path", `${kk}.edge`, {
1217
+ d: `M${f2(-rx * 2.2)} ${f2(edge)}H${f2(rx * 2.2)}`,
1218
+ transform: `rotate(${f2(a)})`,
1219
+ opacity: lid > 0.02 ? 1 : 0
1220
+ }),
1221
+ n("ellipse", `${kk}.edge.lower`, {
1222
+ cx: 0,
1223
+ cy: f2(top + lry),
1224
+ rx: f2(lrx),
1225
+ ry: f2(lry),
1226
+ opacity: lower > 0.02 ? 1 : 0
1227
+ })
1228
+ ]));
1229
+ const co = smooth(0.86, 1, lid);
1230
+ const yy = Math.min(edge, ry) - 1;
1231
+ const hw = rx * 0.92;
1232
+ parts.push(n("path", `${kk}.closed`, {
1233
+ d: `M${f2(-hw)} ${f2(yy - 2)}Q0 ${f2(yy + 6)} ${f2(hw)} ${f2(yy - 2)}`,
1234
+ transform: `rotate(${f2(a * 0.6)})`,
1235
+ fill: "none",
1236
+ stroke: INK.ink,
1237
+ "stroke-linecap": "round",
1238
+ "stroke-width": f2(sw * 0.85),
1239
+ opacity: co > 0.01 ? f2(co) : 0
1240
+ }));
1241
+ return n("g", kk, { transform: `translate(${f2(etx)} ${f2(ety)})` }, parts);
1242
+ });
1243
+ const browY = g("browY") - (eyeS - 1) * sh.ry * 0.9;
1244
+ const btilt = g("browTilt");
1245
+ const arch = g("browArch");
1246
+ const basym = g("browAsym");
1247
+ const bw = sw * g("browW");
1248
+ const brows = sh.eyes.map(([ex], i) => {
1249
+ const side = i === 0 ? -1 : 1;
1250
+ const hw = sh.brow.hw;
1251
+ const c = -sh.brow.arch * arch;
1252
+ const [w2, ca, k2] = who.brows === "bar" ? [hw * 0.9, c * 0.3, 1.75] : who.brows === "dash" ? [hw * 0.42, c * 0.25, 1.9] : [hw, c, 1];
1253
+ const ti = bw * 1.7;
1254
+ const to = bw * 0.45;
1255
+ const [tl, tr] = side < 0 ? [to, ti] : [ti, to];
1256
+ const wedge = who.brows === "wedge";
1257
+ const [hl, hr] = wedge ? [tl / 2 + to / 2, tr / 2 + to / 2] : [0, 0];
1258
+ const half = (t) => wedge ? hl + (hr - hl) * t : bw * k2 / 2;
1259
+ const bx = ex + side * spread;
1260
+ const by = sh.brow.y + browY + (i === 1 ? -basym : basym * 0.3);
1261
+ const cos = Math.cos(side * btilt * RAD2);
1262
+ const sin = Math.sin(side * btilt * RAD2);
1263
+ const inside = (dy) => {
1264
+ for (let t = 0;t <= 1; t += 0.25) {
1265
+ const lx = w2 * (2 * t - 1);
1266
+ const ly = 2 * t * (1 - t) * ca;
1267
+ const px = bx + lx * cos - ly * sin;
1268
+ const py = by + dy + lx * sin + ly * cos;
1269
+ if (depth(sh.crown, px, py) + half(t) + 2 > 0)
1270
+ return false;
1271
+ }
1272
+ return true;
1273
+ };
1274
+ let lo = 0;
1275
+ let hi = 0;
1276
+ if (!inside(0)) {
1277
+ hi = 40;
1278
+ for (let k = 0;k < 12; k++) {
1279
+ const mid2 = (lo + hi) / 2;
1280
+ if (inside(mid2))
1281
+ hi = mid2;
1282
+ else
1283
+ lo = mid2;
1284
+ }
1285
+ }
1286
+ const transform = `translate(${f2(bx)} ${f2(by + hi)}) rotate(${f2(side * btilt)})`;
1287
+ const key = `brow${i}`;
1288
+ if (wedge) {
1289
+ const dm = (tl + tr) / 2 * 0.75;
1290
+ return n("path", key, {
1291
+ d: `M${f2(-hw)} ${f2(-tl / 2)}Q0 ${f2(c - dm)} ${f2(hw)} ${f2(-tr / 2)}L${f2(hw)} ${f2(tr / 2)}Q0 ${f2(c + dm)} ${f2(-hw)} ${f2(tl / 2)}Z`,
1292
+ transform,
1293
+ fill: INK.ink,
1294
+ stroke: INK.ink,
1295
+ "stroke-linejoin": "round",
1296
+ "stroke-width": f2(to)
1297
+ });
1298
+ }
1299
+ return n("path", key, {
1300
+ d: `M${f2(-w2)} 0Q0 ${f2(ca)} ${f2(w2)} 0`,
1301
+ transform,
1302
+ fill: "none",
1303
+ stroke: INK.ink,
1304
+ "stroke-linecap": "round",
1305
+ "stroke-linejoin": "round",
1306
+ "stroke-width": f2(bw * k2)
1307
+ });
1308
+ });
1309
+ const m = sh.mouth;
1310
+ const mw = m.w * Math.max(0.05, g("mw"));
1311
+ const mt = g("mt") * m.d;
1312
+ const mb = g("mb") * m.d;
1313
+ const kb = 4 / 3;
1314
+ const w7 = mw * 0.72;
1315
+ const mid = (mt + mb) / 2 * kb;
1316
+ const d = who.mouth === "poly" ? `M${f2(-mw)} 0L0 ${f2(mt)}L${f2(mw)} 0L0 ${f2(mb)}Z` : who.mouth === "box" ? `M${f2(-mw)} 0L${f2(-w7)} ${f2(mb)}L${f2(w7)} ${f2(mb)}L${f2(mw)} 0L${f2(w7)} ${f2(mt)}L${f2(-w7)} ${f2(mt)}Z` : who.mouth === "line" ? `M${f2(-mw)} 0C${f2(-mw * 0.5)} ${f2(mid)} ${f2(mw * 0.5)} ${f2(mid)} ${f2(mw)} 0` : `M${f2(-mw)} 0C${f2(-mw)} ${f2(mb * kb)} ${f2(mw)} ${f2(mb * kb)} ${f2(mw)} 0C${f2(mw)} ${f2(mt * kb)} ${f2(-mw)} ${f2(mt * kb)} ${f2(-mw)} 0Z`;
1317
+ const open = who.mouth === "line";
1318
+ const mouth = n("path", "mouth", {
1319
+ d,
1320
+ transform: `translate(${f2(m.x + g("mx"))} ${f2(m.y + g("my"))}) rotate(${f2(g("mk"))})`,
1321
+ fill: open ? "none" : INK.ink,
1322
+ stroke: INK.ink,
1323
+ "stroke-linecap": "round",
1324
+ "stroke-linejoin": "round",
1325
+ "stroke-width": f2(open ? sw : m.sw * st)
1326
+ });
1327
+ const blush = clamp(g("blush"), 0, 1);
1328
+ const cheeks = sh.cheeks.map(([cx, cy], i) => {
1329
+ const key = `cheek${i}`;
1330
+ const at = {
1331
+ opacity: f2(blush),
1332
+ transform: `translate(${f2(cx + (i === 0 ? -1 : 1) * spread * 0.6)} ${f2(cy - lower * 4)}) scale(${f2(0.6 + 0.4 * blush)}) translate(${-cx} ${-cy})`
1333
+ };
1334
+ if (who.cheeks === "dots") {
1335
+ const r = sh.cry * 0.9;
1336
+ return n("g", key, { ...at, fill: deep }, [
1337
+ n("circle", `${key}.0`, { cx: f2(cx - sh.crx * 1.05), cy, r: f2(r) }),
1338
+ n("circle", `${key}.1`, { cx, cy: f2(cy + sh.cry * 0.5), r: f2(r) }),
1339
+ n("circle", `${key}.2`, { cx: f2(cx + sh.crx * 1.05), cy, r: f2(r) })
1340
+ ]);
1341
+ }
1342
+ if (who.cheeks === "lines") {
1343
+ const h = sh.cry * 1.3;
1344
+ return n("g", key, {
1345
+ ...at,
1346
+ fill: "none",
1347
+ stroke: deep,
1348
+ "stroke-linecap": "round",
1349
+ "stroke-width": f2(sh.cry * 0.78)
1350
+ }, [-1, 0, 1].map((j) => {
1351
+ const x0 = cx + j * sh.crx * 0.8;
1352
+ return n("path", `${key}.${j + 1}`, {
1353
+ d: `M${f2(x0 + h * 0.5)} ${f2(cy - h)}L${f2(x0 - h * 0.5)} ${f2(cy + h)}`
1354
+ });
1355
+ }));
1356
+ }
1357
+ return n("ellipse", key, {
1358
+ cx,
1359
+ cy,
1360
+ rx: sh.crx,
1361
+ ry: sh.cry,
1362
+ fill: deep,
1363
+ ...at
1364
+ });
1365
+ });
1366
+ const hair = g("hair");
1367
+ const lag = pose.lag;
1368
+ let traitT;
1369
+ let shape;
1370
+ if (who.trait === "square") {
1371
+ const s = seat(sh.crown, 22 + hair * 10, 0);
1372
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag)}) rotate(${f2(s.a + 22 + hair * 23)})`;
1373
+ shape = n("rect", "trait.shape", {
1374
+ x: -11,
1375
+ y: -11,
1376
+ width: 22,
1377
+ height: 22,
1378
+ fill: deep
1379
+ });
1380
+ } else if (who.trait === "fin") {
1381
+ const s = seat(sh.crown, -6, -164 * 22 * RAD2);
1382
+ const r = s.a + (hair >= 0 ? hair * 20 : hair * 55) - lag * 0.8;
1383
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag * 0.25)}) rotate(${f2(r)})`;
1384
+ shape = n("path", "trait.shape", {
1385
+ d: "M0 0L0 -40A40 40 0 0 1 40 0Z",
1386
+ fill: deep
1387
+ });
1388
+ } else if (who.trait === "ring") {
1389
+ const b = 35 - hair * 14 - (hair < 0 ? hair * -21 : 0) + lag * 0.9;
1390
+ const w2 = ringWalk(sh);
1391
+ const s = seat(sh.crown, 8, Math.max(w2.min, w2.rest + 158 * (b - 35) * RAD2));
1392
+ traitT = `translate(${f2(s.x)} ${f2(s.y)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
1393
+ shape = n("circle", "trait.shape", {
1394
+ cx: 0,
1395
+ cy: 0,
1396
+ r: 12.5,
1397
+ fill: "none",
1398
+ stroke: deep,
1399
+ "stroke-width": f2(7 * (pose.expression === "angry" ? 1.25 : 1))
1400
+ });
1401
+ } else if (who.trait === "dot") {
1402
+ const s = clamp(-hair, 0, 1.2);
1403
+ const th = Math.min(1, s / 0.35) * 61.05 * RAD2;
1404
+ const travel = Math.max(0, s - 0.35) / 0.65 * 92;
1405
+ const p = seat(sh.crown, 12.5, 26.1 * th + travel);
1406
+ let cy = p.y;
1407
+ if (hair > 0)
1408
+ cy -= hair * 18;
1409
+ cy += lag * (s > 0.35 ? 0.3 : 1);
1410
+ traitT = `translate(${f2(p.x)} ${f2(cy)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
1411
+ shape = n("circle", "trait.shape", { cx: 0, cy: 0, r: 12.5, fill: deep });
1412
+ } else {
1413
+ const s = seat(sh.crown, 4 + Math.max(0, hair) * 9, 0);
1414
+ const r = s.a + (hair < 0 ? -hair * 38 : 0) - lag * 0.5;
1415
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag)}) rotate(${f2(r)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
1416
+ shape = n("path", "trait.shape", {
1417
+ d: "M0 -27L13 -3L-13 -3Z",
1418
+ fill: deep,
1419
+ stroke: deep,
1420
+ "stroke-width": 6,
1421
+ "stroke-linejoin": "round"
1422
+ });
1423
+ }
1424
+ const trait = n("g", "trait", { transform: traitT }, [shape]);
1425
+ const wardrobe = [];
1426
+ const w = (px) => f2(px * Math.min(st, 1.6));
1427
+ const line = {
1428
+ fill: "none",
1429
+ stroke: INK.ink,
1430
+ "stroke-width": w(5.25),
1431
+ "stroke-linecap": "round",
1432
+ "stroke-linejoin": "round"
1433
+ };
1434
+ if (who.eyewear !== "none") {
1435
+ const lx = sh.eyes[0][0] - spread;
1436
+ const rr = sh.eyes[1][0] + spread;
1437
+ const ey = sh.eyes[0][1];
1438
+ const gap = rr - lx;
1439
+ const erx = Math.min(rx + 9, (gap - 15) / 2);
1440
+ const ery = ry + 9;
1441
+ const glasses = who.eyewear === "glasses";
1442
+ const lensX = glasses ? erx : Math.min(rx + 11, (gap - 12) / 2);
1443
+ const top = glasses ? ey - 4 : ey - ry - 2;
1444
+ const frames = [
1445
+ n("path", "eyewear.bridge", {
1446
+ ...line,
1447
+ d: `M${f2(lx + lensX)} ${f2(top)}Q${f2((lx + rr) / 2)} ${f2(top - (glasses ? 13 : 0))} ${f2(rr - lensX)} ${f2(top)}`
1448
+ })
1449
+ ];
1450
+ for (const [i, cx] of [lx, rr].entries()) {
1451
+ const key = `eyewear.lens${i}`;
1452
+ frames.push(n("path", `${key}.arm`, {
1453
+ ...line,
1454
+ d: i === 0 ? `M${f2(cx - lensX - 9)} ${f2(top + 2)}L${f2(cx - lensX)} ${f2(top)}` : `M${f2(cx + lensX)} ${f2(top)}L${f2(cx + lensX + 9)} ${f2(top + 2)}`
1455
+ }));
1456
+ if (glasses) {
1457
+ frames.push(n("ellipse", key, {
1458
+ ...line,
1459
+ cx: f2(cx),
1460
+ cy: ey,
1461
+ rx: f2(erx),
1462
+ ry: f2(ery)
1463
+ }));
1464
+ frames.push(n("path", `${key}.glint`, {
1465
+ ...line,
1466
+ stroke: INK.paper,
1467
+ "stroke-width": w(3),
1468
+ d: `M${f2(cx - erx * 0.65)} ${f2(ey - ery * 0.45)}Q${f2(cx - erx * 0.5)} ${f2(ey - ery * 0.8)} ${f2(cx - erx * 0.2)} ${f2(ey - ery * 0.86)}`
1469
+ }));
1470
+ } else {
1471
+ const h = ry * 2 + 9;
1472
+ frames.push(n("path", key, {
1473
+ d: `M${f2(cx - lensX)} ${f2(top)}H${f2(cx + lensX)}V${f2(top + h * 0.55)}Q${f2(cx + lensX)} ${f2(top + h)} ${f2(cx)} ${f2(top + h)}Q${f2(cx - lensX)} ${f2(top + h)} ${f2(cx - lensX)} ${f2(top + h * 0.55)}Z`,
1474
+ fill: INK.ink,
1475
+ stroke: INK.ink,
1476
+ "stroke-width": w(3),
1477
+ "stroke-linejoin": "round"
1478
+ }));
1479
+ frames.push(n("path", `${key}.glint`, {
1480
+ ...line,
1481
+ stroke: INK.paper,
1482
+ "stroke-width": w(4),
1483
+ d: `M${f2(cx - lensX * 0.58)} ${f2(top + 19)}l8 -8m-1 15 6 -6`
1484
+ }), n("circle", `${key}.pin`, {
1485
+ cx: f2(cx + lensX * 0.55),
1486
+ cy: f2(top + h - 9),
1487
+ r: 1.7,
1488
+ fill: INK.paper
1489
+ }));
1490
+ }
1491
+ }
1492
+ wardrobe.push(n("g", "eyewear", {}, frames));
1493
+ }
1494
+ if (who.headwear !== "none") {
1495
+ const side = who.trait === "ring" || who.trait === "dot" ? -1 : 1;
1496
+ const cap = who.headwear === "cap";
1497
+ const sprout = who.headwear === "sprout";
1498
+ const inset = sprout ? -6 : cap ? -8 : 0;
1499
+ const offset = sprout ? 34 : 40;
1500
+ let lo = 0;
1501
+ let hi = 160;
1502
+ for (let i = 0;i < 16; i++) {
1503
+ const mid2 = (lo + hi) / 2;
1504
+ const p = seat(sh.crown, inset, side * mid2);
1505
+ if (side * (p.x - 170) < offset)
1506
+ lo = mid2;
1507
+ else
1508
+ hi = mid2;
1509
+ }
1510
+ const anchor = seat(sh.crown, inset, side * (lo + hi) / 2);
1511
+ const angle = clamp(anchor.a, -40, 40);
1512
+ const parts = [];
1513
+ if (cap) {
1514
+ parts.push(n("path", "headwear.crown", {
1515
+ ...line,
1516
+ fill: INK.paper,
1517
+ d: "M-33 0C-35 -20 -20 -39 0 -39C20 -39 35 -20 33 0Z"
1518
+ }), n("path", "headwear.seam", {
1519
+ ...line,
1520
+ "stroke-width": w(3.5),
1521
+ d: "M-4 -38Q9 -24 8 -1"
1522
+ }), n("path", "headwear.brim", {
1523
+ ...line,
1524
+ "stroke-width": w(7),
1525
+ d: side > 0 ? "M-35 1Q8 4 52 3" : "M-52 3Q-8 4 35 1"
1526
+ }), n("circle", "headwear.button", {
1527
+ cx: 0,
1528
+ cy: -40,
1529
+ r: 3.5,
1530
+ fill: INK.ink
1531
+ }));
1532
+ } else if (sprout) {
1533
+ parts.push(n("path", "headwear.halo", {
1534
+ ...line,
1535
+ stroke: fill,
1536
+ "stroke-width": w(11),
1537
+ d: `${STEM}${LEAF0}${LEAF1}`
1538
+ }), n("path", "headwear.stem", {
1539
+ ...line,
1540
+ "stroke-width": w(4.5),
1541
+ d: STEM
1542
+ }), n("path", "headwear.leaf0", {
1543
+ ...line,
1544
+ "stroke-width": w(4),
1545
+ fill: COLORS.mint.body,
1546
+ d: LEAF0
1547
+ }), n("path", "headwear.leaf1", {
1548
+ ...line,
1549
+ "stroke-width": w(4),
1550
+ fill: COLORS.mint.deep,
1551
+ d: LEAF1
1552
+ }));
1553
+ } else {
1554
+ parts.push(n("path", "headwear.wings", {
1555
+ d: "M-3 0C-13 -16 -28 -23 -29 -10C-31 2 -26 19 -16 13L-3 4L3 4L16 13C26 19 31 2 29 -10C28 -23 13 -16 3 0Z",
1556
+ fill: INK.ink,
1557
+ stroke: fill,
1558
+ "stroke-width": w(6),
1559
+ "stroke-linejoin": "round",
1560
+ "paint-order": "stroke"
1561
+ }), n("ellipse", "headwear.knot", {
1562
+ cx: 0,
1563
+ cy: 1,
1564
+ rx: 6,
1565
+ ry: 8,
1566
+ fill: ACCESSORY_INK.knot
1567
+ }));
1568
+ }
1569
+ wardrobe.push(n("g", "headwear", {
1570
+ transform: `translate(${f2(anchor.x)} ${f2(anchor.y)}) rotate(${f2(sprout ? angle * 0.5 : angle)})`
1571
+ }, parts));
1572
+ }
1573
+ if (who.neckwear === "tie") {
1574
+ const skew = Math.abs(Math.sin(g("mk") * RAD2)) * mw;
1575
+ const bottom = Math.max(0, mt * kb, mb * kb) + skew + m.sw * st;
1576
+ wardrobe.push(n("g", "neckwear", {
1577
+ transform: `translate(${m.x} ${f2(m.y + g("my") + bottom + 8)})`,
1578
+ fill: INK.ink
1579
+ }, [
1580
+ n("path", "neckwear.knot", {
1581
+ d: "M-9 0Q-11 0 -9 4L-5 11Q0 15 5 11L9 4Q11 0 9 0Z"
1582
+ }),
1583
+ n("path", "neckwear.blade", {
1584
+ d: "M-4 15L-13 63Q-14 66 -11 69L0 79L11 69Q14 66 13 63L4 15Z"
1585
+ })
1586
+ ]));
1587
+ }
1588
+ return [
1589
+ [bodyNode, ...cheeks, trait],
1590
+ [...eyes, ...brows, mouth, ...wardrobe]
1591
+ ];
1592
+ }
1593
+
1594
+ // src/animate.ts
1595
+ var GROUP_OF = {};
1596
+ for (const gr of Object.keys(GROUPS))
1597
+ for (const c of GROUPS[gr])
1598
+ GROUP_OF[c] = gr;
1599
+ var CHANNELS = Object.keys(GROUP_OF);
1600
+ var DEF_DELAY = {
1601
+ eyes: 0,
1602
+ pupil: 50,
1603
+ brows: 110,
1604
+ mouth: 170,
1605
+ extra: 140,
1606
+ body: 230
1607
+ };
1608
+ var DEF_SPR = {
1609
+ eyes: [14, 0.82],
1610
+ pupil: [15, 0.8],
1611
+ brows: [11, 0.72],
1612
+ mouth: [12, 0.74],
1613
+ body: [7, 0.72],
1614
+ extra: [8, 0.5]
1615
+ };
1616
+ var CHOREO = {
1617
+ happy: {
1618
+ delay: {
1619
+ eyes: 0,
1620
+ pupil: 40,
1621
+ brows: 120,
1622
+ mouth: 200,
1623
+ extra: 220,
1624
+ body: 280
1625
+ },
1626
+ spr: { body: [9, 0.42], extra: [9, 0.35] },
1627
+ keys: [
1628
+ [280, { alt: 0.2 }, { body: [11, 0.5] }],
1629
+ [520, { alt: 0.05 }, { body: [8, 0.38] }]
1630
+ ]
1631
+ },
1632
+ sad: {
1633
+ delay: {
1634
+ eyes: 0,
1635
+ pupil: 160,
1636
+ brows: 260,
1637
+ mouth: 380,
1638
+ extra: 420,
1639
+ body: 560
1640
+ },
1641
+ spr: {
1642
+ eyes: [6, 0.95],
1643
+ pupil: [5, 0.95],
1644
+ brows: [6, 0.95],
1645
+ mouth: [6, 0.9],
1646
+ body: [3.2, 0.95],
1647
+ extra: [3, 0.9]
1648
+ }
1649
+ },
1650
+ angry: {
1651
+ delay: { brows: 0, eyes: 40, pupil: 60, mouth: 160, body: 100, extra: 60 },
1652
+ spr: { brows: [18, 0.7], eyes: [16, 0.75], body: [14, 0.5] }
1653
+ },
1654
+ sleepy: {
1655
+ delay: {
1656
+ eyes: 0,
1657
+ pupil: 200,
1658
+ brows: 750,
1659
+ mouth: 950,
1660
+ extra: 900,
1661
+ body: 1150
1662
+ },
1663
+ spr: {
1664
+ eyes: [2.6, 1],
1665
+ pupil: [3, 1],
1666
+ brows: [3.2, 1],
1667
+ mouth: [3, 1],
1668
+ extra: [2.4, 0.9],
1669
+ body: [1.9, 1]
1670
+ },
1671
+ keys: [
1672
+ [0, { lid: 0.62 }],
1673
+ [1300, { lid: 1 }]
1674
+ ]
1675
+ },
1676
+ curious: {
1677
+ delay: { eyes: 0, pupil: 0, brows: 220, mouth: 320, extra: 0, body: 0 },
1678
+ spr: { body: [6, 0.95] },
1679
+ keys: [
1680
+ [0, { alt: -0.8, rot: 0, gx: 0, gy: -0.1, thing: 0 }],
1681
+ [900, { alt: -0.56 }, { body: [2.2, 1] }],
1682
+ [1500, { thing: 1 }, { extra: [5, 0.9] }],
1683
+ [2200, { look: 1 }, { pupil: [6, 0.9] }],
1684
+ [2500, { rot: 7 }, { body: [3, 0.9] }],
1685
+ [2900, { alt: -0.32 }, { body: [2, 1] }]
1686
+ ]
1687
+ },
1688
+ surprised: {
1689
+ delay: { eyes: 0, pupil: 0, brows: 0, mouth: 40, extra: 60, body: 0 },
1690
+ spr: {
1691
+ eyes: [30, 0.62],
1692
+ pupil: [26, 0.7],
1693
+ brows: [24, 0.55],
1694
+ mouth: [22, 0.6],
1695
+ extra: [15, 0.32]
1696
+ },
1697
+ keys: [
1698
+ [0, { alt: (v) => v - 0.1, sy: 0.9, sx: 1.05 }, { body: [30, 1] }],
1699
+ [110, { alt: 0.36, sy: 1.08, sx: 0.97 }, { body: [17, 0.42] }],
1700
+ [380, { sy: 1, sx: 1 }, { body: [10, 0.5] }]
1701
+ ]
1702
+ },
1703
+ excited: {
1704
+ delay: { eyes: 0, pupil: 30, brows: 80, mouth: 140, extra: 120, body: 180 },
1705
+ spr: { body: [9, 0.4], extra: [12, 0.3] }
1706
+ },
1707
+ confused: {
1708
+ delay: {
1709
+ eyes: 0,
1710
+ brows: 60,
1711
+ pupil: 100,
1712
+ mouth: 250,
1713
+ body: 200,
1714
+ extra: 200
1715
+ },
1716
+ spr: { body: [5, 0.7] }
1717
+ },
1718
+ bored: {
1719
+ delay: {
1720
+ eyes: 0,
1721
+ pupil: 300,
1722
+ brows: 500,
1723
+ mouth: 600,
1724
+ extra: 700,
1725
+ body: 800
1726
+ },
1727
+ spr: {
1728
+ eyes: [3, 1],
1729
+ pupil: [2.5, 1],
1730
+ brows: [3, 1],
1731
+ mouth: [3, 1],
1732
+ body: [1.6, 1],
1733
+ extra: [2, 1]
1734
+ }
1735
+ },
1736
+ attentive: {
1737
+ delay: {
1738
+ eyes: 0,
1739
+ pupil: 180,
1740
+ brows: 90,
1741
+ mouth: 240,
1742
+ extra: 200,
1743
+ body: 360
1744
+ },
1745
+ spr: {
1746
+ eyes: [20, 0.8],
1747
+ pupil: [20, 0.85],
1748
+ brows: [16, 0.7],
1749
+ body: [5, 0.78],
1750
+ extra: [11, 0.4]
1751
+ }
1752
+ }
1753
+ };
1754
+ var ATTENTION = {
1755
+ normal: 0.8,
1756
+ happy: 0.6,
1757
+ sad: 0.2,
1758
+ angry: 0.6,
1759
+ sleepy: 0,
1760
+ curious: 1,
1761
+ surprised: 0.5,
1762
+ excited: 0.5,
1763
+ confused: 0.3,
1764
+ bored: 0.1,
1765
+ attentive: 1
1766
+ };
1767
+ var BLINK = {
1768
+ normal: [2.4, 5],
1769
+ happy: [2.2, 4],
1770
+ sad: [3, 6],
1771
+ angry: [3.5, 6],
1772
+ sleepy: null,
1773
+ curious: [3.5, 6],
1774
+ surprised: [3, 5],
1775
+ excited: [1.8, 3.2],
1776
+ confused: [2, 4],
1777
+ bored: [4.5, 7.5],
1778
+ attentive: [4.5, 7.5]
1779
+ };
1780
+ var rand = (a, b) => a + Math.random() * (b - a);
1781
+ var zero = () => {
1782
+ const o = {};
1783
+ for (const c of CHANNELS)
1784
+ o[c] = 0;
1785
+ return o;
1786
+ };
1787
+ var ALL = new Set;
1788
+ var LIVE = new Set;
1789
+ var raf = 0;
1790
+ var last = 0;
1791
+ function tick(now) {
1792
+ const dt = Math.min(0.05, (now - last) / 1000);
1793
+ last = now;
1794
+ if (!document.hidden) {
1795
+ for (const l of LIVE)
1796
+ if (l.visible)
1797
+ l.measure();
1798
+ for (const l of LIVE)
1799
+ if (l.visible)
1800
+ l.step(dt);
1801
+ }
1802
+ raf = LIVE.size ? requestAnimationFrame(tick) : 0;
1803
+ }
1804
+ function run() {
1805
+ if (raf)
1806
+ return;
1807
+ last = performance.now();
1808
+ raf = requestAnimationFrame(tick);
1809
+ }
1810
+ var motionQuery = null;
1811
+ function reducedMotion() {
1812
+ if (!motionQuery) {
1813
+ motionQuery = matchMedia("(prefers-reduced-motion: reduce)");
1814
+ motionQuery.addEventListener("change", (e) => {
1815
+ for (const l of ALL)
1816
+ l.setReduced(e.matches);
1817
+ });
1818
+ }
1819
+ return motionQuery.matches;
1820
+ }
1821
+ var pointer = null;
1822
+ var listening = false;
1823
+ function listen() {
1824
+ if (listening)
1825
+ return;
1826
+ listening = true;
1827
+ const at = (e) => {
1828
+ pointer = [e.clientX, e.clientY];
1829
+ };
1830
+ document.addEventListener("pointermove", at, { passive: true });
1831
+ document.addEventListener("pointerdown", at, { passive: true });
1832
+ document.documentElement.addEventListener("pointerleave", () => {
1833
+ pointer = null;
1834
+ });
1835
+ }
1836
+
1837
+ class Live {
1838
+ svg;
1839
+ who;
1840
+ opts;
1841
+ visible = true;
1842
+ state;
1843
+ reduced;
1844
+ nodes = new Map;
1845
+ prev = new Map;
1846
+ io = null;
1847
+ clock = 0;
1848
+ enteredAt = 0;
1849
+ val;
1850
+ vel = zero();
1851
+ base;
1852
+ spr = {};
1853
+ out = zero();
1854
+ queue = [];
1855
+ bumps = [];
1856
+ blinkAt = rand(1, 3);
1857
+ blinkT = -1;
1858
+ blinkDur = 0.16;
1859
+ blinkV = 0;
1860
+ doubleBlink = false;
1861
+ saccAt = rand(0.6, 1.8);
1862
+ sacc = { gx: 0, gy: 0 };
1863
+ nodAt = 0;
1864
+ lookThing;
1865
+ lag = { y: 0, v: 0 };
1866
+ fixed = null;
1867
+ watch = false;
1868
+ rect = null;
1869
+ constructor(svg, tree, who, opts, expression, gaze) {
1870
+ this.svg = svg;
1871
+ this.who = who;
1872
+ this.opts = opts;
1873
+ this.state = expression;
1874
+ this.bind(tree, svg);
1875
+ this.setGaze(gaze);
1876
+ const start = restPose(who, expression, this.fixed ?? undefined);
1877
+ this.val = { ...start };
1878
+ this.base = { ...EXPRESSIONS[expression] };
1879
+ if (this.fixed)
1880
+ [this.base.gx, this.base.gy] = this.fixed;
1881
+ for (const c of CHANNELS)
1882
+ this.spr[c] = DEF_SPR[GROUP_OF[c]];
1883
+ this.lookThing = expression === "curious";
1884
+ this.lag.y = -start.alt * frameOf(FACES[who.face]).R;
1885
+ this.reduced = reducedMotion();
1886
+ this.paint();
1887
+ this.io = new IntersectionObserver((es) => {
1888
+ for (const e of es)
1889
+ this.visible = e.isIntersecting;
1890
+ });
1891
+ this.io.observe(svg);
1892
+ ALL.add(this);
1893
+ if (this.reduced)
1894
+ return;
1895
+ LIVE.add(this);
1896
+ run();
1897
+ }
1898
+ destroy() {
1899
+ ALL.delete(this);
1900
+ LIVE.delete(this);
1901
+ this.io?.disconnect();
1902
+ }
1903
+ setReduced(reduced) {
1904
+ if (reduced === this.reduced)
1905
+ return;
1906
+ this.reduced = reduced;
1907
+ if (!reduced) {
1908
+ LIVE.add(this);
1909
+ run();
1910
+ return;
1911
+ }
1912
+ LIVE.delete(this);
1913
+ this.snap();
1914
+ }
1915
+ measure() {
1916
+ this.rect = this.watch && pointer ? this.svg.getBoundingClientRect() : null;
1917
+ }
1918
+ bind(node, el) {
1919
+ this.nodes.set(node.key, el);
1920
+ const now = {};
1921
+ for (const a of Array.from(el.attributes))
1922
+ if (a.name !== "class" && a.name !== "style")
1923
+ now[a.name] = a.value;
1924
+ this.prev.set(node.key, now);
1925
+ node.children.forEach((c, i) => {
1926
+ const child = el.children[i];
1927
+ if (child)
1928
+ this.bind(c, child);
1929
+ });
1930
+ }
1931
+ write(node) {
1932
+ const el = this.nodes.get(node.key);
1933
+ const prev = this.prev.get(node.key) ?? {};
1934
+ if (el) {
1935
+ for (const k in node.attrs)
1936
+ if (prev[k] !== node.attrs[k])
1937
+ el.setAttribute(k, node.attrs[k]);
1938
+ for (const k in prev)
1939
+ if (!(k in node.attrs))
1940
+ el.removeAttribute(k);
1941
+ }
1942
+ this.prev.set(node.key, node.attrs);
1943
+ for (const c of node.children)
1944
+ this.write(c);
1945
+ }
1946
+ setGaze(gaze) {
1947
+ this.fixed = Array.isArray(gaze) ? gaze : null;
1948
+ this.watch = gaze === "pointer";
1949
+ if (this.watch)
1950
+ listen();
1951
+ if (this.reduced && this.fixed) {
1952
+ [this.val.gx, this.val.gy] = this.fixed;
1953
+ this.paint();
1954
+ }
1955
+ }
1956
+ setExpression(name) {
1957
+ if (name === this.state)
1958
+ return;
1959
+ const prev = this.state;
1960
+ this.state = name;
1961
+ this.enteredAt = this.clock;
1962
+ this.lookThing = false;
1963
+ this.queue = [];
1964
+ const tgt = EXPRESSIONS[name];
1965
+ const ch = CHOREO[name] ?? {};
1966
+ const delay = { ...DEF_DELAY, ...ch.delay };
1967
+ const spr = { ...DEF_SPR, ...ch.spr };
1968
+ if (this.reduced) {
1969
+ this.snap();
1970
+ return;
1971
+ }
1972
+ for (const gr of Object.keys(GROUPS)) {
1973
+ const set = {};
1974
+ for (const c of GROUPS[gr])
1975
+ set[c] = tgt[c];
1976
+ this.queue.push({
1977
+ at: this.clock + delay[gr] / 1000,
1978
+ set,
1979
+ spr: { [gr]: spr[gr] }
1980
+ });
1981
+ }
1982
+ for (const [t, set, s] of ch.keys ?? [])
1983
+ this.queue.push({ at: this.clock + t / 1000 + 0.0001, set, spr: s });
1984
+ this.queue.sort((a, b) => a.at - b.at);
1985
+ if (name === "surprised") {
1986
+ this.blinkAt = this.clock + 0.75;
1987
+ this.doubleBlink = true;
1988
+ }
1989
+ if (name === "sleepy")
1990
+ this.nodAt = this.clock + rand(6, 8);
1991
+ if (prev === "sleepy")
1992
+ this.bumps = [];
1993
+ }
1994
+ snap() {
1995
+ const rest = restPose(this.who, this.state, this.fixed ?? undefined);
1996
+ for (const c of CHANNELS) {
1997
+ this.base[c] = EXPRESSIONS[this.state][c];
1998
+ this.val[c] = rest[c];
1999
+ this.vel[c] = 0;
2000
+ }
2001
+ this.lookThing = this.state === "curious";
2002
+ this.queue = [];
2003
+ this.bumps = [];
2004
+ this.blinkT = -1;
2005
+ this.blinkV = 0;
2006
+ this.out = zero();
2007
+ this.paint();
2008
+ }
2009
+ bump(ch, amt, a, h, r) {
2010
+ this.bumps.push({ ch, amt, t0: this.clock, a, h, r });
2011
+ }
2012
+ step(dt) {
2013
+ this.clock += dt;
2014
+ const t = this.clock;
2015
+ const age = t - this.enteredAt;
2016
+ const st = this.state;
2017
+ const face = FACES[this.who.face];
2018
+ while (this.queue.length && this.queue[0].at <= t) {
2019
+ const q = this.queue.shift();
2020
+ if (q.spr)
2021
+ for (const gr of Object.keys(q.spr)) {
2022
+ const s = q.spr[gr];
2023
+ if (s)
2024
+ for (const c of GROUPS[gr])
2025
+ this.spr[c] = s;
2026
+ }
2027
+ for (const c of Object.keys(q.set)) {
2028
+ if (c === "look") {
2029
+ this.lookThing = true;
2030
+ continue;
2031
+ }
2032
+ const v = q.set[c];
2033
+ this.base[c] = typeof v === "function" ? v(this.val[c]) : v;
2034
+ }
2035
+ }
2036
+ const tgt = zero();
2037
+ const out = zero();
2038
+ let gx = this.base.gx;
2039
+ let gy = this.base.gy;
2040
+ if (this.lookThing)
2041
+ [gx, gy] = thingGaze(face, this.val.x, this.val.alt);
2042
+ if (this.fixed)
2043
+ [gx, gy] = this.fixed;
2044
+ const still = st === "sleepy" || st === "bored";
2045
+ if (!still && t >= this.saccAt) {
2046
+ this.sacc = { gx: rand(-0.17, 0.17), gy: rand(-0.1, 0.1) };
2047
+ this.saccAt = t + rand(0.8, 2.6);
2048
+ }
2049
+ if (!still) {
2050
+ gx += this.sacc.gx;
2051
+ gy += this.sacc.gy;
2052
+ }
2053
+ const w = ATTENTION[st];
2054
+ const r = this.rect;
2055
+ if (pointer && r && w > 0) {
2056
+ if (r.width > 0) {
2057
+ const fx = VB[0] + (pointer[0] - r.left) / r.width * VB[2];
2058
+ const fy = VB[1] + (pointer[1] - r.top) / r.height * VB[3];
2059
+ const { baseY, eyeY, R: R2 } = frameOf(face);
2060
+ const ex = 170 + this.val.x;
2061
+ const ey = eyeY + baseY - this.val.alt * R2;
2062
+ const px = clamp((fx - ex) / 170, -1, 1);
2063
+ const py = clamp((fy - ey) / 170, -1, 1);
2064
+ gx += (px - gx) * w;
2065
+ gy += (py - gy) * w;
2066
+ }
2067
+ }
2068
+ tgt.gx = gx - this.base.gx;
2069
+ tgt.gy = gy - this.base.gy;
2070
+ const br = st === "sleepy" ? 1.3 : st === "bored" ? 1.2 : st === "attentive" ? 2.6 : 2.1;
2071
+ const ba = st === "sleepy" ? 0.018 : st === "attentive" ? 0.004 : 0.007;
2072
+ out.sy += ba * Math.sin(t * br);
2073
+ out.alt += (st === "sleepy" ? 0.03 : 0.008) * Math.sin(t * br + 1.1);
2074
+ if (st === "happy")
2075
+ out.rot += 1.4 * Math.sin(t * 1.7);
2076
+ if (st === "angry") {
2077
+ out.x += 7 * Math.sin(age * 42) * Math.exp(-age * 6);
2078
+ out.x += 0.35 * Math.sin(t * 31);
2079
+ }
2080
+ if (st === "excited") {
2081
+ const ph = age % 1.5;
2082
+ if (ph < 0.9) {
2083
+ const h2 = Math.max(0, Math.sin(ph / 0.45 * Math.PI));
2084
+ out.alt += h2 * 0.17;
2085
+ out.sy += h2 * 0.045;
2086
+ out.rot += 2 * Math.sin(t * 9) * h2;
2087
+ }
2088
+ }
2089
+ if (st === "confused") {
2090
+ tgt.gx += 0.45 * Math.sin(age * 1.1);
2091
+ out.rot += 2.2 * Math.sin(age * 0.7);
2092
+ }
2093
+ if (st === "bored")
2094
+ tgt.gx += 0.22 * Math.sin(t * 0.33);
2095
+ if (st === "sleepy" && t >= this.nodAt && age > 3) {
2096
+ this.bump("alt", 0.3, 0.14, 0.5, 1.6);
2097
+ this.bump("lid", -0.5, 0.14, 0.45, 1.3);
2098
+ this.bump("hair", 0.6, 0.12, 0.4, 1.2);
2099
+ this.nodAt = t + rand(6.5, 9.5);
2100
+ }
2101
+ if ((st === "sad" || st === "bored") && Math.random() < dt / 7) {
2102
+ this.bump("sy", -0.035, 0.5, 0.25, 0.9);
2103
+ this.bump("alt", -0.05, 0.5, 0.25, 0.9);
2104
+ }
2105
+ const h = dt / 2;
2106
+ for (const c of CHANNELS) {
2107
+ const [om, ze] = this.spr[c];
2108
+ const target = this.base[c] + tgt[c];
2109
+ let x = this.val[c];
2110
+ let v = this.vel[c];
2111
+ for (let i = 0;i < 2; i++) {
2112
+ v += (om * om * (target - x) - 2 * ze * om * v) * h;
2113
+ x += v * h;
2114
+ }
2115
+ this.val[c] = x;
2116
+ this.vel[c] = v;
2117
+ }
2118
+ this.bumps = this.bumps.filter((b) => {
2119
+ const u = t - b.t0;
2120
+ if (u > b.a + b.h + b.r)
2121
+ return false;
2122
+ const e = u < b.a ? smooth(0, 1, u / b.a) : u < b.a + b.h ? 1 : 1 - smooth(0, 1, (u - b.a - b.h) / b.r);
2123
+ out[b.ch] += b.amt * e;
2124
+ return true;
2125
+ });
2126
+ const bl = BLINK[st];
2127
+ let blink = 0;
2128
+ if (bl && this.val.lid < 0.75) {
2129
+ if (this.blinkT < 0 && t >= this.blinkAt) {
2130
+ this.blinkT = 0;
2131
+ this.blinkDur = st === "bored" ? 0.42 : 0.16;
2132
+ }
2133
+ if (this.blinkT >= 0) {
2134
+ this.blinkT += dt;
2135
+ const u = this.blinkT / this.blinkDur;
2136
+ if (u >= 1) {
2137
+ this.blinkT = -1;
2138
+ if (this.doubleBlink) {
2139
+ this.doubleBlink = false;
2140
+ this.blinkAt = t + 0.09;
2141
+ } else {
2142
+ const k = this.who.persona.blink;
2143
+ this.blinkAt = t + rand(bl[0] * k, bl[1] * k);
2144
+ }
2145
+ } else
2146
+ blink = Math.sin(u * Math.PI);
2147
+ }
2148
+ } else if (!bl)
2149
+ this.blinkT = -1;
2150
+ this.blinkV = blink;
2151
+ const R = frameOf(face).R;
2152
+ const avY = -(this.val.alt + out.alt) * R;
2153
+ const lom = 10;
2154
+ const lze = 0.32;
2155
+ this.lag.v += (lom * lom * (avY - this.lag.y) - 2 * lze * lom * this.lag.v) * dt;
2156
+ this.lag.y += this.lag.v * dt;
2157
+ this.out = out;
2158
+ this.paint();
2159
+ }
2160
+ paint() {
2161
+ const R = frameOf(FACES[this.who.face]).R;
2162
+ const pose = { blink: this.blinkV, expression: this.state };
2163
+ for (const c of CHANNELS)
2164
+ pose[c] = this.val[c] + this.out[c];
2165
+ pose.lag = this.reduced ? 0 : clamp((this.lag.y + pose.alt * R) * 0.4, -16, 16);
2166
+ this.write(draw(this.who, pose, this.opts));
2167
+ }
2168
+ }
2169
+
2170
+ // src/svg.ts
2171
+ var wear = (o, base, s) => ACCESSORIES[s].includes(o[s]) ? o[s] : base[s];
2172
+ function settle(name, o, live = false) {
2173
+ const base = identify(name, { version: o.version });
2174
+ const who = {
2175
+ ...base,
2176
+ face: o.face ?? base.face,
2177
+ color: o.color ?? base.color,
2178
+ eyes: o.eyes ?? base.eyes,
2179
+ brows: o.brows ?? base.brows,
2180
+ mouth: o.mouth ?? base.mouth,
2181
+ cheeks: o.cheeks ?? base.cheeks,
2182
+ trait: o.trait ?? base.trait,
2183
+ eyewear: wear(o, base, "eyewear"),
2184
+ headwear: wear(o, base, "headwear"),
2185
+ neckwear: wear(o, base, "neckwear")
2186
+ };
2187
+ const pose = restPose(who, o.expression ?? "normal", o.gaze);
2188
+ const variant = VARIANTS.includes(o.variant) ? o.variant : "peek";
2189
+ const look = {
2190
+ size: o.size ?? 64,
2191
+ frame: o.frame ?? "ink",
2192
+ square: o.square ?? true,
2193
+ riso: o.riso ?? false,
2194
+ live,
2195
+ ...variant !== "peek" && { variant }
2196
+ };
2197
+ const { eyewear, headwear, neckwear, ...legacy } = who;
2198
+ const wardrobe = eyewear !== "none" || headwear !== "none" || neckwear !== "none";
2199
+ const id = o.id ?? `peek-${fnv1a(JSON.stringify([wardrobe ? who : legacy, pose, look])).toString(36)}`;
2200
+ return { who, pose, opts: { ...look, id, title: o.title ?? name } };
2201
+ }
2202
+ var ILLEGAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
2203
+ var esc = (s) => s.replace(ILLEGAL, "�").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
2204
+ function serialize(node) {
2205
+ let a = "";
2206
+ for (const k in node.attrs)
2207
+ a += ` ${k}="${esc(node.attrs[k])}"`;
2208
+ if (!node.children.length)
2209
+ return `<${node.tag}${a}/>`;
2210
+ return `<${node.tag}${a}>${node.children.map(serialize).join("")}</${node.tag}>`;
2211
+ }
2212
+ function toSvg(name, props = {}) {
2213
+ const { who, pose, opts } = settle(name, props);
2214
+ return serialize(draw(who, pose, opts));
2215
+ }
2216
+ // src/vue.ts
2217
+ function toVue(node, ref2) {
2218
+ return h(node.tag, { key: node.key, ...node.attrs, ref: ref2 }, node.children.map((c) => toVue(c)));
2219
+ }
2220
+ var any = { type: null, default: undefined };
2221
+ var flag = { type: Boolean, default: undefined };
2222
+ var Peek = defineComponent((props) => {
2223
+ const id = `peek${useId().replace(/[^\w-]/g, "")}`;
2224
+ const svg = ref();
2225
+ let live = null;
2226
+ const fixedOf = (g) => Array.isArray(g) ? g : undefined;
2227
+ const first = {
2228
+ expression: props.expression ?? "normal",
2229
+ gaze: fixedOf(props.gaze)
2230
+ };
2231
+ const view = computed(() => {
2232
+ const { name, gaze, animate, ...rest } = props;
2233
+ const { who, pose, opts } = settle(name, { ...rest, gaze: fixedOf(gaze), id }, animate);
2234
+ const tree = draw(who, animate ? restPose(who, first.expression, first.gaze) : pose, opts);
2235
+ return { who, opts, tree, sig: JSON.stringify([who, opts]) };
2236
+ });
2237
+ const expression = () => props.expression ?? "normal";
2238
+ onMounted(() => {
2239
+ watch([() => props.animate, () => view.value.sig], () => {
2240
+ live?.destroy();
2241
+ live = null;
2242
+ if (!props.animate || !svg.value)
2243
+ return;
2244
+ const { tree, who, opts } = view.value;
2245
+ live = new Live(svg.value, tree, who, opts, expression(), props.gaze);
2246
+ }, { immediate: true, flush: "post" });
2247
+ watch(expression, (e) => live?.setExpression(e), { flush: "post" });
2248
+ watch(() => String(props.gaze ?? ""), () => live?.setGaze(props.gaze), { flush: "post" });
2249
+ });
2250
+ onBeforeUnmount(() => live?.destroy());
2251
+ return () => toVue(view.value.tree, svg);
2252
+ }, {
2253
+ name: "Peek",
2254
+ props: {
2255
+ name: { type: String, required: true },
2256
+ gaze: any,
2257
+ animate: { type: Boolean, default: false },
2258
+ size: { type: Number, default: undefined },
2259
+ expression: any,
2260
+ frame: any,
2261
+ square: flag,
2262
+ title: {
2263
+ type: [String, Boolean],
2264
+ default: undefined
2265
+ },
2266
+ riso: flag,
2267
+ version: { type: Number, default: undefined },
2268
+ variant: any,
2269
+ face: any,
2270
+ color: any,
2271
+ eyes: any,
2272
+ brows: any,
2273
+ mouth: any,
2274
+ cheeks: any,
2275
+ trait: any,
2276
+ eyewear: any,
2277
+ headwear: any,
2278
+ neckwear: any
2279
+ }
2280
+ });
2281
+ export {
2282
+ ACCESSORIES,
2283
+ AXES,
2284
+ BLOC,
2285
+ COLORS,
2286
+ EXPRESSIONS,
2287
+ FACES,
2288
+ GROUPS,
2289
+ HAZE,
2290
+ LATEST,
2291
+ LISTS,
2292
+ PARTS,
2293
+ Peek,
2294
+ VARIANTS,
2295
+ VERSIONS,
2296
+ WHEEL,
2297
+ draw,
2298
+ identify,
2299
+ settle,
2300
+ tidy,
2301
+ toSvg
2302
+ };