@doan-labs/peek 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,1700 @@
1
+ // src/tables.ts
2
+ var INK = {
3
+ ink: "#1A1918",
4
+ bone: "#F3F0E8",
5
+ paper: "#FFFDF8"
6
+ };
7
+ var FACES = {
8
+ diamond: {
9
+ body: { d: "M170 20L320 170L170 320L20 170Z" },
10
+ bottom: 320,
11
+ sink: 30,
12
+ eyes: [
13
+ [132.5, 155],
14
+ [207.5, 155]
15
+ ],
16
+ rx: 20,
17
+ ry: 27.5,
18
+ pr: 10,
19
+ brow: { y: 102.5, hw: 18.75, arch: 15, w: 5.62 },
20
+ cheeks: [
21
+ [117.5, 200],
22
+ [222.5, 200]
23
+ ],
24
+ crx: 11.25,
25
+ cry: 5.62,
26
+ mouth: { x: 170, y: 218.75, w: 15, d: 16.25, sw: 4.38 },
27
+ crown: {
28
+ poly: [
29
+ [170, 20],
30
+ [320, 170],
31
+ [170, 320],
32
+ [20, 170]
33
+ ],
34
+ r: 0
35
+ }
36
+ },
37
+ semicircle: {
38
+ body: { d: "M0 255A170 170 0 0 1 340 255Z" },
39
+ bottom: 255,
40
+ sink: 0,
41
+ eyes: [
42
+ [102.5, 175],
43
+ [237.5, 175]
44
+ ],
45
+ rx: 30,
46
+ ry: 25,
47
+ pr: 11.25,
48
+ brow: { y: 127.5, hw: 25, arch: 15, w: 5.62 },
49
+ cheeks: [
50
+ [55, 212.5],
51
+ [285, 212.5]
52
+ ],
53
+ crx: 11.25,
54
+ cry: 5.62,
55
+ mouth: { x: 170, y: 221.25, w: 16.25, d: 16.25, sw: 4.38 },
56
+ crown: { poly: [[170, 255]], r: 170 }
57
+ },
58
+ circle: {
59
+ body: { circle: [170, 170, 150] },
60
+ bottom: 320,
61
+ sink: 20,
62
+ eyes: [
63
+ [112.8, 162.2],
64
+ [227.2, 162.2]
65
+ ],
66
+ rx: 29.9,
67
+ ry: 29.9,
68
+ pr: 13,
69
+ brow: { y: 110.2, hw: 23.4, arch: 15.6, w: 5.85 },
70
+ cheeks: [
71
+ [84.2, 203.8],
72
+ [255.8, 203.8]
73
+ ],
74
+ crx: 11.7,
75
+ cry: 5.85,
76
+ mouth: { x: 170, y: 223.3, w: 16.9, d: 16.9, sw: 4.55 },
77
+ crown: { poly: [[170, 170]], r: 150 }
78
+ },
79
+ triangle: {
80
+ body: { d: "M170 40.8L312.8 299.2L27.2 299.2Z", stroke: 27.2 },
81
+ bottom: 312.8,
82
+ sink: 0,
83
+ eyes: [
84
+ [132.6, 210.8],
85
+ [207.4, 210.8]
86
+ ],
87
+ rx: 20.4,
88
+ ry: 27.2,
89
+ pr: 10,
90
+ brow: { y: 166.6, hw: 18.7, arch: 15.3, w: 5.62 },
91
+ cheeks: [
92
+ [98.6, 238],
93
+ [241.4, 238]
94
+ ],
95
+ crx: 11.5,
96
+ cry: 5.75,
97
+ mouth: { x: 170, y: 254, w: 17, d: 16.25, sw: 4.38 },
98
+ crown: {
99
+ poly: [
100
+ [170, 40.8],
101
+ [312.8, 299.2],
102
+ [27.2, 299.2]
103
+ ],
104
+ r: 13.6
105
+ }
106
+ }
107
+ };
108
+ var COLORS = {
109
+ lavender: { body: "#D8CDF0", deep: "#BDAEE6" },
110
+ fog: { body: "#C8D6E8", deep: "#A8BCDC" },
111
+ clay: { body: "#F1CDBF", deep: "#E2A893" },
112
+ mint: { body: "#CFE7D6", deep: "#A5CDB1" },
113
+ butter: { body: "#ECE2B9", deep: "#D9C284" },
114
+ rose: { body: "#EFCAD7", deep: "#DFA4BA" },
115
+ aqua: { body: "#BDE1E5", deep: "#8FC7D1" }
116
+ };
117
+ var PARTS = {
118
+ eyes: ["oval", "bead", "ring"],
119
+ brows: ["arch", "bar", "wedge", "dash"],
120
+ mouth: ["poly", "round", "line", "box"],
121
+ cheeks: ["oval", "dots", "lines"],
122
+ trait: ["square", "fin", "ring", "dot", "peak"]
123
+ };
124
+ var VERSIONS = {
125
+ 1: { face: 4, color: 7, eyes: 3, brows: 4, mouth: 4, cheeks: 3, trait: 5 }
126
+ };
127
+ var LATEST = 1;
128
+ var GROUPS = {
129
+ eyes: ["lid", "lower", "lidTilt", "eyeS", "lidAsym"],
130
+ pupil: ["pupil", "gx", "gy", "shine"],
131
+ brows: ["browY", "browTilt", "browArch", "browW", "browAsym"],
132
+ mouth: ["mw", "mt", "mb", "mx", "my", "mk"],
133
+ body: ["alt", "rot", "x", "sx", "sy"],
134
+ extra: ["blush", "hair", "dim", "thing"]
135
+ };
136
+ var BASE = {
137
+ alt: 0,
138
+ rot: 0,
139
+ x: 0,
140
+ sx: 1,
141
+ sy: 1,
142
+ lid: 0.1,
143
+ lower: 0,
144
+ lidTilt: 0,
145
+ eyeS: 1,
146
+ lidAsym: 0,
147
+ pupil: 1,
148
+ gx: 0,
149
+ gy: 0,
150
+ shine: 0.7,
151
+ browY: 0,
152
+ browTilt: 0,
153
+ browArch: 0.4,
154
+ browW: 1,
155
+ browAsym: 0,
156
+ mw: 0.72,
157
+ mt: 0,
158
+ mb: 0.5,
159
+ mx: 0,
160
+ my: 0,
161
+ mk: 0,
162
+ blush: 0,
163
+ hair: 0,
164
+ dim: 0,
165
+ thing: 0
166
+ };
167
+ var S = (o) => ({ ...BASE, ...o });
168
+ var EXPRESSIONS = {
169
+ normal: S({}),
170
+ happy: S({
171
+ lid: 0,
172
+ lower: 0.42,
173
+ shine: 1,
174
+ gy: -0.05,
175
+ browY: -5,
176
+ browArch: 1,
177
+ mw: 1,
178
+ mb: 1,
179
+ blush: 1,
180
+ hair: 0.45,
181
+ alt: 0.05
182
+ }),
183
+ sad: S({
184
+ lid: 0.42,
185
+ lidTilt: 16,
186
+ pupil: 0.95,
187
+ gy: 0.8,
188
+ shine: 0,
189
+ browY: 3,
190
+ browTilt: 14,
191
+ browArch: 0,
192
+ mw: 0.8,
193
+ mt: -0.55,
194
+ mb: -0.55,
195
+ my: 6,
196
+ alt: -0.16,
197
+ sy: 0.985,
198
+ hair: -0.75
199
+ }),
200
+ angry: S({
201
+ lid: 0.34,
202
+ lidTilt: -22,
203
+ lower: 0.12,
204
+ pupil: 0.82,
205
+ gy: 0.1,
206
+ shine: 0,
207
+ browY: 5,
208
+ browTilt: -21,
209
+ browArch: 0,
210
+ browW: 1.3,
211
+ mw: 0.8,
212
+ mt: -0.08,
213
+ mb: -0.08,
214
+ my: 3,
215
+ sx: 1.02,
216
+ sy: 0.98,
217
+ hair: 0.25
218
+ }),
219
+ sleepy: S({
220
+ lid: 1,
221
+ lidTilt: 4,
222
+ pupil: 0.9,
223
+ gy: 0.5,
224
+ shine: 0,
225
+ browY: 6,
226
+ browTilt: 3,
227
+ browArch: 0.15,
228
+ browW: 0.9,
229
+ mw: 0.35,
230
+ mt: -0.3,
231
+ mb: 0.3,
232
+ my: 2,
233
+ alt: -1.12,
234
+ rot: -3,
235
+ hair: -1
236
+ }),
237
+ curious: S({
238
+ lid: 0,
239
+ eyeS: 1.06,
240
+ pupil: 1.05,
241
+ shine: 1,
242
+ browY: -3,
243
+ browAsym: 9,
244
+ browArch: 0.8,
245
+ mw: 0.38,
246
+ mt: -0.36,
247
+ mb: 0.36,
248
+ rot: 6,
249
+ alt: -0.32,
250
+ hair: 0.3,
251
+ thing: 1
252
+ }),
253
+ surprised: S({
254
+ lid: 0,
255
+ eyeS: 1.2,
256
+ pupil: 0.66,
257
+ shine: 0.8,
258
+ browY: -13,
259
+ browArch: 1,
260
+ mw: 0.55,
261
+ mt: -0.62,
262
+ mb: 0.62,
263
+ alt: 0.36,
264
+ hair: 1
265
+ }),
266
+ excited: S({
267
+ lid: 0,
268
+ lower: 0.5,
269
+ eyeS: 1.08,
270
+ pupil: 1.08,
271
+ shine: 1,
272
+ browY: -9,
273
+ browArch: 1,
274
+ mw: 1.15,
275
+ mb: 1.3,
276
+ blush: 1,
277
+ hair: 1,
278
+ alt: 0.12
279
+ }),
280
+ confused: S({
281
+ lid: 0.22,
282
+ lidAsym: 0.28,
283
+ lidTilt: -4,
284
+ pupil: 0.92,
285
+ gx: -0.35,
286
+ gy: -0.45,
287
+ shine: 0.3,
288
+ browY: -2,
289
+ browAsym: 11,
290
+ browTilt: -5,
291
+ browArch: 0.3,
292
+ mw: 0.6,
293
+ mt: -0.1,
294
+ mb: 0.12,
295
+ mx: 5,
296
+ mk: 13,
297
+ rot: -7,
298
+ alt: -0.05,
299
+ hair: 0.1
300
+ }),
301
+ bored: S({
302
+ lid: 0.5,
303
+ pupil: 0.9,
304
+ gx: -0.55,
305
+ gy: 0.25,
306
+ shine: 0,
307
+ browY: 3,
308
+ browArch: 0.1,
309
+ mw: 0.55,
310
+ mt: 0,
311
+ mb: 0.04,
312
+ mx: -5,
313
+ alt: -0.34,
314
+ hair: -0.45,
315
+ dim: 1
316
+ }),
317
+ attentive: S({
318
+ lid: 0,
319
+ eyeS: 1.1,
320
+ pupil: 0.8,
321
+ gy: -0.1,
322
+ shine: 1,
323
+ browY: -7,
324
+ browArch: 0.7,
325
+ mw: 0.55,
326
+ mb: 0.4,
327
+ alt: 0.15,
328
+ sy: 1.03,
329
+ hair: 0.65
330
+ })
331
+ };
332
+
333
+ // src/identity.ts
334
+ var STYLE = "peek";
335
+ var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
336
+ function fnv1a(str) {
337
+ let h = 2166136261;
338
+ for (const b of new TextEncoder().encode(str)) {
339
+ h ^= b;
340
+ h = Math.imul(h, 16777619) >>> 0;
341
+ }
342
+ return h >>> 0;
343
+ }
344
+ function mulberry32(a) {
345
+ return () => {
346
+ a = a + 1831565813 | 0;
347
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
348
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
349
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
350
+ };
351
+ }
352
+ var LISTS = {
353
+ face: Object.keys(FACES),
354
+ color: Object.keys(COLORS),
355
+ ...PARTS
356
+ };
357
+ var AXES = Object.keys(LISTS);
358
+ function seed(name, axis, version = LATEST) {
359
+ return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
360
+ }
361
+ function identify(name, { version = LATEST } = {}) {
362
+ const lengths = VERSIONS[version];
363
+ if (!lengths)
364
+ throw new Error(`peek@${version} does not exist`);
365
+ const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
366
+ const rnd = mulberry32(seed(name, "persona", version));
367
+ const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
368
+ const persona = {
369
+ spread: r(-7, 7),
370
+ blink: r(0.75, 1.4),
371
+ add: {
372
+ browY: r(-4, 4),
373
+ browTilt: r(-5, 5),
374
+ browArch: r(-0.2, 0.3),
375
+ rot: r(-3, 3),
376
+ hair: r(-0.2, 0.35),
377
+ gx: r(-0.12, 0.12),
378
+ my: r(-2, 3)
379
+ },
380
+ mul: {
381
+ eyeS: r(0.92, 1.08),
382
+ pupil: r(0.9, 1.12),
383
+ browW: r(0.85, 1.2),
384
+ mw: r(0.85, 1.18)
385
+ }
386
+ };
387
+ const key = tidy(name);
388
+ return {
389
+ key,
390
+ hash: fnv1a(key),
391
+ version,
392
+ face: pick("face"),
393
+ color: pick("color"),
394
+ eyes: pick("eyes"),
395
+ brows: pick("brows"),
396
+ mouth: pick("mouth"),
397
+ cheeks: pick("cheeks"),
398
+ trait: pick("trait"),
399
+ persona
400
+ };
401
+ }
402
+ // src/peek.tsx
403
+ import {
404
+ createElement,
405
+ useEffect,
406
+ useId,
407
+ useRef,
408
+ useState
409
+ } from "react";
410
+
411
+ // src/draw.ts
412
+ var VB = [-30, -30, 400, 400];
413
+ var FLOOR = 370;
414
+ var THING = [330, 20];
415
+ var clamp = (v, a, b) => v < a ? a : v > b ? b : v;
416
+ var smooth = (a, b, v) => {
417
+ const t = clamp((v - a) / (b - a), 0, 1);
418
+ return t * t * (3 - 2 * t);
419
+ };
420
+ var f2 = (v) => String(Math.round(v * 100) / 100);
421
+ var f3 = (v) => String(Math.round(v * 1000) / 1000);
422
+ var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
423
+ var RAD = Math.PI / 180;
424
+ function frameOf(face) {
425
+ const baseY = FLOOR + face.sink - face.bottom;
426
+ const eyeY = face.eyes[0][1];
427
+ return { baseY, eyeY, R: FLOOR - (eyeY + baseY) };
428
+ }
429
+ function thingGaze(face, x, alt) {
430
+ const { baseY, eyeY, R } = frameOf(face);
431
+ const ey = eyeY + baseY - alt * R;
432
+ return [
433
+ clamp((THING[0] - 170 - x) / 150, -1, 1),
434
+ clamp((THING[1] - ey) / 150, -1, 1)
435
+ ];
436
+ }
437
+ function restPose(who, expression, gaze) {
438
+ const s = EXPRESSIONS[expression];
439
+ const pose = { ...s, blink: 0, lag: 0, expression };
440
+ if (expression === "curious")
441
+ pose.rot = 7;
442
+ const g = gaze ?? (expression === "curious" ? thingGaze(FACES[who.face], 0, s.alt) : null);
443
+ if (g)
444
+ [pose.gx, pose.gy] = g;
445
+ return pose;
446
+ }
447
+ function seat(crown, d, u) {
448
+ const R = Math.max(0, crown.r + d);
449
+ const at = ([x, y], b2) => ({
450
+ x: x + R * Math.sin(b2),
451
+ y: y - R * Math.cos(b2),
452
+ a: b2 / RAD
453
+ });
454
+ const P = crown.poly;
455
+ if (P.length === 1)
456
+ return at(P[0], R ? u / R : 0);
457
+ const n = P.length;
458
+ const dir = u >= 0 ? 1 : -1;
459
+ let left = Math.abs(u);
460
+ let i = 0;
461
+ let b = 0;
462
+ for (let step = 0;step < n; step++) {
463
+ const j = (i + dir + n) % n;
464
+ const [x0, y0] = P[i];
465
+ const [x1, y1] = P[j];
466
+ const ex = x1 - x0;
467
+ const ey = y1 - y0;
468
+ const L = Math.hypot(ex, ey);
469
+ const nx = dir * ey / L;
470
+ const ny = -dir * ex / L;
471
+ const be = Math.atan2(nx, -ny);
472
+ let db = be - b;
473
+ while (dir * db < 0)
474
+ db += dir * 2 * Math.PI;
475
+ const arc = R * Math.abs(db);
476
+ if (left <= arc)
477
+ return at(P[i], R ? b + dir * left / R : b);
478
+ left -= arc;
479
+ if (left <= L)
480
+ return {
481
+ x: x0 + ex / L * left + R * nx,
482
+ y: y0 + ey / L * left + R * ny,
483
+ a: be / RAD
484
+ };
485
+ left -= L;
486
+ i = j;
487
+ b = be;
488
+ }
489
+ return at(P[i], b);
490
+ }
491
+ function depth(crown, x, y) {
492
+ const P = crown.poly;
493
+ if (P.length === 1)
494
+ return Math.hypot(x - P[0][0], y - P[0][1]) - crown.r;
495
+ let out = -Infinity;
496
+ let near = Infinity;
497
+ for (let i = 0;i < P.length; i++) {
498
+ const [ax, ay] = P[i];
499
+ const [bx, by] = P[(i + 1) % P.length];
500
+ const ex = bx - ax;
501
+ const ey = by - ay;
502
+ const L2 = ex * ex + ey * ey;
503
+ const L = Math.sqrt(L2);
504
+ out = Math.max(out, ((x - ax) * ey - (y - ay) * ex) / L);
505
+ const t = clamp(((x - ax) * ex + (y - ay) * ey) / L2, 0, 1);
506
+ near = Math.min(near, Math.hypot(x - ax - t * ex, y - ay - t * ey));
507
+ }
508
+ return (out <= 0 ? out : near) - crown.r;
509
+ }
510
+ var RING_U = 158 * 35 * RAD;
511
+ function ringWalk(sh) {
512
+ const ex = sh.eyes[1][0] + sh.brow.hw + 7;
513
+ const top = sh.brow.y - 30;
514
+ const bottom = sh.brow.y + 8;
515
+ let u = RING_U;
516
+ for (;u < 400; u += 2) {
517
+ const s = seat(sh.crown, 8, u);
518
+ const dx = s.x - ex;
519
+ const dy = s.y - clamp(s.y, top, bottom);
520
+ if (s.x > ex ? Math.hypot(dx, dy) > 27 : s.y < top - 27)
521
+ break;
522
+ }
523
+ return u > RING_U ? { rest: u + 20, min: u } : { rest: u, min: -Infinity };
524
+ }
525
+ function n(tag, key, attrs, children = []) {
526
+ const out = {};
527
+ for (const k in attrs) {
528
+ const v = attrs[k];
529
+ if (v !== undefined && v !== false)
530
+ out[k] = String(v);
531
+ }
532
+ return { tag, key, attrs: out, children };
533
+ }
534
+ function prune(node) {
535
+ if (node.attrs.opacity === "0")
536
+ return null;
537
+ const children = node.children.map(prune).filter((c) => c !== null);
538
+ if (node.tag === "g" && children.length === 0)
539
+ return null;
540
+ return { ...node, children };
541
+ }
542
+ function draw(who, pose, o) {
543
+ const sh = FACES[who.face];
544
+ const { body: fill, deep } = COLORS[who.color];
545
+ const P = who.persona;
546
+ const mul = P.mul;
547
+ const add = P.add;
548
+ const g = (c) => pose[c] * (mul[c] ?? 1) + (add[c] ?? 0);
549
+ const spread = P.spread;
550
+ const id = o.id;
551
+ const st = strokeFor(o.size);
552
+ const { baseY, R } = frameOf(sh);
553
+ const defs = [];
554
+ const VBr = { x: VB[0], y: VB[1], width: VB[2], height: VB[3] };
555
+ defs.push(n("clipPath", "frame.clip", { id: `${id}-f` }, [
556
+ o.square ? n("rect", "frame.clip.shape", VBr) : n("circle", "frame.clip.shape", { cx: 170, cy: 170, r: 200 })
557
+ ]));
558
+ const k = o.riso ? smooth(120, 480, o.size) : 0;
559
+ const riso = k > 0.01;
560
+ const upp = VB[2] / Math.max(o.size, 1);
561
+ const mk = k * k;
562
+ const plate = (p, seedA, seedB, sc2) => n("filter", `riso.${p}`, {
563
+ id: `${id}-p${p}`,
564
+ x: "-20%",
565
+ y: "-20%",
566
+ width: "140%",
567
+ height: "140%",
568
+ "color-interpolation-filters": "sRGB"
569
+ }, [
570
+ n("feTurbulence", `riso.${p}.warp`, {
571
+ type: "fractalNoise",
572
+ baseFrequency: "0.032",
573
+ numOctaves: 2,
574
+ seed: seedA,
575
+ result: "warp"
576
+ }),
577
+ n("feDisplacementMap", `riso.${p}.disp`, {
578
+ in: "SourceGraphic",
579
+ in2: "warp",
580
+ scale: f2(sc2 * k),
581
+ xChannelSelector: "R",
582
+ yChannelSelector: "G",
583
+ result: "moved"
584
+ }),
585
+ n("feTurbulence", `riso.${p}.grain`, {
586
+ type: "fractalNoise",
587
+ baseFrequency: "0.85",
588
+ numOctaves: 2,
589
+ seed: seedB,
590
+ result: "grain"
591
+ }),
592
+ n("feColorMatrix", `riso.${p}.mask`, {
593
+ in: "grain",
594
+ type: "matrix",
595
+ 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)}`,
596
+ result: "mask"
597
+ }),
598
+ n("feComposite", `riso.${p}.out`, {
599
+ in: "moved",
600
+ in2: "mask",
601
+ operator: "in"
602
+ })
603
+ ]);
604
+ const grainy = riso && o.frame !== "none";
605
+ if (riso)
606
+ defs.push(plate("A", 4, 11, 3.2), plate("B", 9, 23, 2.2));
607
+ if (grainy) {
608
+ const [r, gg, b] = o.frame === "ink" ? [0.953, 0.941, 0.91] : [0.102, 0.098, 0.094];
609
+ defs.push(n("filter", "grain.filter", {
610
+ id: `${id}-grain`,
611
+ x: 0,
612
+ y: 0,
613
+ width: 1,
614
+ height: 1,
615
+ "color-interpolation-filters": "sRGB"
616
+ }, [
617
+ n("feTurbulence", "grain.noise", {
618
+ type: "fractalNoise",
619
+ baseFrequency: "0.9",
620
+ numOctaves: 2,
621
+ seed: 5
622
+ }),
623
+ n("feColorMatrix", "grain.matrix", {
624
+ type: "matrix",
625
+ values: `0 0 0 0 ${r} 0 0 0 0 ${gg} 0 0 0 0 ${b} 7 0 0 0 -5.1`
626
+ })
627
+ ]));
628
+ }
629
+ const dim = clamp(g("dim"), 0, 1);
630
+ if (o.live || dim > 0.01)
631
+ defs.push(n("filter", "dim.filter", { id: `${id}-dim`, "color-interpolation-filters": "sRGB" }, [
632
+ n("feColorMatrix", "dim.matrix", {
633
+ type: "saturate",
634
+ values: f3(1 - dim * 0.55)
635
+ })
636
+ ]));
637
+ const alt = g("alt");
638
+ const rot = g("rot");
639
+ const x = g("x");
640
+ const sx = g("sx");
641
+ const sy = g("sy");
642
+ const B = sh.bottom;
643
+ const ty = baseY - alt * R;
644
+ const sc = (v) => Math.round(v * 1e5) / 1e5;
645
+ const bodyNode = "d" in sh.body ? n("path", "body", {
646
+ d: sh.body.d,
647
+ fill,
648
+ stroke: sh.body.stroke ? fill : undefined,
649
+ "stroke-width": sh.body.stroke,
650
+ "stroke-linejoin": sh.body.stroke ? "round" : undefined
651
+ }) : n("circle", "body", {
652
+ cx: sh.body.circle[0],
653
+ cy: sh.body.circle[1],
654
+ r: sh.body.circle[2],
655
+ fill
656
+ });
657
+ const eyeS = g("eyeS");
658
+ const rx = sh.rx * eyeS;
659
+ const ry = sh.ry * eyeS;
660
+ const lid0 = g("lid");
661
+ const lidAsym = g("lidAsym");
662
+ const lower = clamp(g("lower"), 0, 1);
663
+ const blink = pose.blink;
664
+ const pupil = g("pupil");
665
+ const gx = clamp(g("gx"), -1.1, 1.1);
666
+ const gy = clamp(g("gy"), -1.1, 1.1);
667
+ const tilt = g("lidTilt");
668
+ const shine = clamp(g("shine"), 0, 1);
669
+ const sw = sh.brow.w * st;
670
+ const bead = who.eyes === "bead";
671
+ const eyes = sh.eyes.map(([ex, ey], i) => {
672
+ const side = i === 0 ? -1 : 1;
673
+ const kk = `eye${i}`;
674
+ let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1);
675
+ lid = lid + (1 - lid) * blink;
676
+ defs.push(n("clipPath", `${kk}.clip`, { id: `${id}-e${i}` }, [
677
+ n("ellipse", `${kk}.clip.shape`, {
678
+ cx: 0,
679
+ cy: 0,
680
+ rx: f2(rx),
681
+ ry: f2(ry)
682
+ })
683
+ ]), n("clipPath", `${kk}.lidclip`, { id: `${id}-l${i}` }, [
684
+ n("ellipse", `${kk}.lidclip.shape`, {
685
+ cx: 0,
686
+ cy: 0,
687
+ rx: f2(rx + 1.6),
688
+ ry: f2(ry + 1.6)
689
+ })
690
+ ]));
691
+ const br = Math.min(sh.rx, sh.ry) * 0.62;
692
+ const pr = bead ? br * eyeS * (0.4 + 0.6 * pupil) : sh.pr * pupil;
693
+ const base = bead ? br : sh.pr;
694
+ const px = gx * Math.max(0, sh.rx - base) * 0.9;
695
+ const py = gy * Math.max(0, sh.ry - base) * 0.8;
696
+ const rimW = Math.min(sw * 0.8, Math.min(sh.rx, sh.ry) * 0.28);
697
+ const inner = [];
698
+ if (!bead)
699
+ inner.push(n("ellipse", `${kk}.white`, {
700
+ cx: 0,
701
+ cy: 0,
702
+ rx: f2(rx),
703
+ ry: f2(ry),
704
+ fill: INK.paper
705
+ }));
706
+ inner.push(n("circle", `${kk}.pupil`, {
707
+ cx: f2(px),
708
+ cy: f2(py),
709
+ r: f2(pr),
710
+ fill: INK.ink
711
+ }), n("circle", `${kk}.shine`, {
712
+ cx: f2(px + pr * 0.44),
713
+ cy: f2(py - pr * 0.44),
714
+ r: f2(pr * (bead ? 0.26 : 0.3)),
715
+ fill: INK.paper,
716
+ opacity: f2(shine)
717
+ }));
718
+ if (who.eyes === "ring")
719
+ inner.push(n("ellipse", `${kk}.rim`, {
720
+ cx: 0,
721
+ cy: 0,
722
+ rx: f2(rx - rimW / 2),
723
+ ry: f2(ry - rimW / 2),
724
+ fill: "none",
725
+ stroke: INK.ink,
726
+ "stroke-width": f2(rimW)
727
+ }));
728
+ const edge = -ry - 1.6 + lid * (2 * ry + 3.2);
729
+ const a = side * tilt;
730
+ const lrx = rx * 1.55;
731
+ const lry = ry * 1.15;
732
+ const top = ry + 1.6 - lower * ry * 1.5;
733
+ const parts = [
734
+ n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
735
+ n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, [
736
+ n("rect", `${kk}.lid`, {
737
+ x: f2(-rx * 2.2),
738
+ y: f2(-ry * 3),
739
+ width: f2(rx * 4.4),
740
+ height: f2(Math.max(0, edge + ry * 3)),
741
+ transform: `rotate(${f2(a)})`,
742
+ fill
743
+ }),
744
+ n("ellipse", `${kk}.lower`, {
745
+ cx: 0,
746
+ cy: f2(top + lry),
747
+ rx: f2(lrx),
748
+ ry: f2(lry),
749
+ fill
750
+ })
751
+ ])
752
+ ];
753
+ if (who.eyes === "ring")
754
+ parts.push(n("g", `${kk}.edges`, {
755
+ "clip-path": `url(#${id}-e${i})`,
756
+ fill: "none",
757
+ stroke: INK.ink,
758
+ "stroke-width": f2(rimW)
759
+ }, [
760
+ n("path", `${kk}.edge`, {
761
+ d: `M${f2(-rx * 2.2)} ${f2(edge)}H${f2(rx * 2.2)}`,
762
+ transform: `rotate(${f2(a)})`,
763
+ opacity: lid > 0.02 ? 1 : 0
764
+ }),
765
+ n("ellipse", `${kk}.edge.lower`, {
766
+ cx: 0,
767
+ cy: f2(top + lry),
768
+ rx: f2(lrx),
769
+ ry: f2(lry),
770
+ opacity: lower > 0.02 ? 1 : 0
771
+ })
772
+ ]));
773
+ const co = smooth(0.86, 1, lid);
774
+ const yy = Math.min(edge, ry) - 1;
775
+ const hw = rx * 0.92;
776
+ parts.push(n("path", `${kk}.closed`, {
777
+ d: `M${f2(-hw)} ${f2(yy - 2)}Q0 ${f2(yy + 6)} ${f2(hw)} ${f2(yy - 2)}`,
778
+ transform: `rotate(${f2(a * 0.6)})`,
779
+ fill: "none",
780
+ stroke: INK.ink,
781
+ "stroke-linecap": "round",
782
+ "stroke-width": f2(sw * 0.85),
783
+ opacity: co > 0.01 ? f2(co) : 0
784
+ }));
785
+ return n("g", kk, {
786
+ transform: bead ? `translate(${f2(ex + side * spread + gx * 6)} ${f2(ey + gy * 6)})` : `translate(${f2(ex + side * spread)} ${ey})`
787
+ }, parts);
788
+ });
789
+ const browY = g("browY") - (eyeS - 1) * sh.ry * 0.9;
790
+ const btilt = g("browTilt");
791
+ const arch = g("browArch");
792
+ const basym = g("browAsym");
793
+ const bw = sw * g("browW");
794
+ const brows = sh.eyes.map(([ex], i) => {
795
+ const side = i === 0 ? -1 : 1;
796
+ const hw = sh.brow.hw;
797
+ const c = -sh.brow.arch * arch;
798
+ const [w, 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];
799
+ const ti = bw * 1.7;
800
+ const to = bw * 0.45;
801
+ const [tl, tr] = side < 0 ? [to, ti] : [ti, to];
802
+ const wedge = who.brows === "wedge";
803
+ const [hl, hr] = wedge ? [tl / 2 + to / 2, tr / 2 + to / 2] : [0, 0];
804
+ const half = (t) => wedge ? hl + (hr - hl) * t : bw * k2 / 2;
805
+ const bx = ex + side * spread;
806
+ const by = sh.brow.y + browY + (i === 1 ? -basym : basym * 0.3);
807
+ const cos = Math.cos(side * btilt * RAD);
808
+ const sin = Math.sin(side * btilt * RAD);
809
+ const inside = (dy) => {
810
+ for (let t = 0;t <= 1; t += 0.25) {
811
+ const lx = w * (2 * t - 1);
812
+ const ly = 2 * t * (1 - t) * ca;
813
+ const px = bx + lx * cos - ly * sin;
814
+ const py = by + dy + lx * sin + ly * cos;
815
+ if (depth(sh.crown, px, py) + half(t) + 2 > 0)
816
+ return false;
817
+ }
818
+ return true;
819
+ };
820
+ let lo = 0;
821
+ let hi = 0;
822
+ if (!inside(0)) {
823
+ hi = 40;
824
+ for (let k3 = 0;k3 < 12; k3++) {
825
+ const mid2 = (lo + hi) / 2;
826
+ if (inside(mid2))
827
+ hi = mid2;
828
+ else
829
+ lo = mid2;
830
+ }
831
+ }
832
+ const transform = `translate(${f2(bx)} ${f2(by + hi)}) rotate(${f2(side * btilt)})`;
833
+ const key = `brow${i}`;
834
+ if (wedge) {
835
+ const dm = (tl + tr) / 2 * 0.75;
836
+ return n("path", key, {
837
+ 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`,
838
+ transform,
839
+ fill: INK.ink,
840
+ stroke: INK.ink,
841
+ "stroke-linejoin": "round",
842
+ "stroke-width": f2(to)
843
+ });
844
+ }
845
+ return n("path", key, {
846
+ d: `M${f2(-w)} 0Q0 ${f2(ca)} ${f2(w)} 0`,
847
+ transform,
848
+ fill: "none",
849
+ stroke: INK.ink,
850
+ "stroke-linecap": "round",
851
+ "stroke-linejoin": "round",
852
+ "stroke-width": f2(bw * k2)
853
+ });
854
+ });
855
+ const m = sh.mouth;
856
+ const mw = m.w * Math.max(0.05, g("mw"));
857
+ const mt = g("mt") * m.d;
858
+ const mb = g("mb") * m.d;
859
+ const kb = 4 / 3;
860
+ const w7 = mw * 0.72;
861
+ const mid = (mt + mb) / 2 * kb;
862
+ 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`;
863
+ const open = who.mouth === "line";
864
+ const mouth = n("path", "mouth", {
865
+ d,
866
+ transform: `translate(${f2(m.x + g("mx"))} ${f2(m.y + g("my"))}) rotate(${f2(g("mk"))})`,
867
+ fill: open ? "none" : INK.ink,
868
+ stroke: INK.ink,
869
+ "stroke-linecap": "round",
870
+ "stroke-linejoin": "round",
871
+ "stroke-width": f2(open ? sw : m.sw * st)
872
+ });
873
+ const blush = clamp(g("blush"), 0, 1);
874
+ const cheeks = sh.cheeks.map(([cx, cy], i) => {
875
+ const key = `cheek${i}`;
876
+ const at = {
877
+ opacity: f2(blush),
878
+ 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})`
879
+ };
880
+ if (who.cheeks === "dots") {
881
+ const r = sh.cry * 0.9;
882
+ return n("g", key, { ...at, fill: deep }, [
883
+ n("circle", `${key}.0`, { cx: f2(cx - sh.crx * 1.05), cy, r: f2(r) }),
884
+ n("circle", `${key}.1`, { cx, cy: f2(cy + sh.cry * 0.5), r: f2(r) }),
885
+ n("circle", `${key}.2`, { cx: f2(cx + sh.crx * 1.05), cy, r: f2(r) })
886
+ ]);
887
+ }
888
+ if (who.cheeks === "lines") {
889
+ const h = sh.cry * 1.3;
890
+ return n("g", key, {
891
+ ...at,
892
+ fill: "none",
893
+ stroke: deep,
894
+ "stroke-linecap": "round",
895
+ "stroke-width": f2(sh.cry * 0.78)
896
+ }, [-1, 0, 1].map((j) => {
897
+ const x0 = cx + j * sh.crx * 0.8;
898
+ return n("path", `${key}.${j + 1}`, {
899
+ d: `M${f2(x0 + h * 0.5)} ${f2(cy - h)}L${f2(x0 - h * 0.5)} ${f2(cy + h)}`
900
+ });
901
+ }));
902
+ }
903
+ return n("ellipse", key, {
904
+ cx,
905
+ cy,
906
+ rx: sh.crx,
907
+ ry: sh.cry,
908
+ fill: deep,
909
+ ...at
910
+ });
911
+ });
912
+ const hair = g("hair");
913
+ const lag = pose.lag;
914
+ let traitT;
915
+ let shape;
916
+ if (who.trait === "square") {
917
+ const s = seat(sh.crown, 22 + hair * 10, 0);
918
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag)}) rotate(${f2(s.a + 22 + hair * 23)})`;
919
+ shape = n("rect", "trait.shape", {
920
+ x: -11,
921
+ y: -11,
922
+ width: 22,
923
+ height: 22,
924
+ fill: deep
925
+ });
926
+ } else if (who.trait === "fin") {
927
+ const s = seat(sh.crown, -6, -164 * 22 * RAD);
928
+ const r = s.a + (hair >= 0 ? hair * 20 : hair * 55) - lag * 0.8;
929
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag * 0.25)}) rotate(${f2(r)})`;
930
+ shape = n("path", "trait.shape", {
931
+ d: "M0 0L0 -40A40 40 0 0 1 40 0Z",
932
+ fill: deep
933
+ });
934
+ } else if (who.trait === "ring") {
935
+ const b = 35 - hair * 14 - (hair < 0 ? hair * -21 : 0) + lag * 0.9;
936
+ const w = ringWalk(sh);
937
+ const s = seat(sh.crown, 8, Math.max(w.min, w.rest + 158 * (b - 35) * RAD));
938
+ traitT = `translate(${f2(s.x)} ${f2(s.y)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
939
+ shape = n("circle", "trait.shape", {
940
+ cx: 0,
941
+ cy: 0,
942
+ r: 12.5,
943
+ fill: "none",
944
+ stroke: deep,
945
+ "stroke-width": f2(7 * (pose.expression === "angry" ? 1.25 : 1))
946
+ });
947
+ } else if (who.trait === "dot") {
948
+ const s = clamp(-hair, 0, 1.2);
949
+ const th = Math.min(1, s / 0.35) * 61.05 * RAD;
950
+ const travel = Math.max(0, s - 0.35) / 0.65 * 92;
951
+ const p = seat(sh.crown, 12.5, 26.1 * th + travel);
952
+ let cy = p.y;
953
+ if (hair > 0)
954
+ cy -= hair * 18;
955
+ cy += lag * (s > 0.35 ? 0.3 : 1);
956
+ traitT = `translate(${f2(p.x)} ${f2(cy)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
957
+ shape = n("circle", "trait.shape", { cx: 0, cy: 0, r: 12.5, fill: deep });
958
+ } else {
959
+ const s = seat(sh.crown, 4 + Math.max(0, hair) * 9, 0);
960
+ const r = s.a + (hair < 0 ? -hair * 38 : 0) - lag * 0.5;
961
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag)}) rotate(${f2(r)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
962
+ shape = n("path", "trait.shape", {
963
+ d: "M0 -27L13 -3L-13 -3Z",
964
+ fill: deep,
965
+ stroke: deep,
966
+ "stroke-width": 6,
967
+ "stroke-linejoin": "round"
968
+ });
969
+ }
970
+ const trait = n("g", "trait", { transform: traitT }, [shape]);
971
+ const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
972
+ const av = n("g", "av", {
973
+ transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
974
+ filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
975
+ }, [
976
+ n("g", "plateA", {
977
+ transform: off(-0.55, 0.45),
978
+ filter: riso ? `url(#${id}-pA)` : undefined
979
+ }, [bodyNode, ...cheeks, trait]),
980
+ n("g", "plateB", {
981
+ transform: off(0.6, -0.5),
982
+ filter: riso ? `url(#${id}-pB)` : undefined
983
+ }, [...eyes, ...brows, mouth])
984
+ ]);
985
+ const scene = [];
986
+ if (o.frame !== "none") {
987
+ const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
988
+ scene.push(n("rect", "bg", { ...VBr, fill: bg }));
989
+ const th = clamp(g("thing"), 0, 1);
990
+ const dots = [];
991
+ for (let j = 0;j < 3; j++)
992
+ for (let i = 0;i < 3; i++) {
993
+ const q = j * 3 + i;
994
+ const kk = smooth(q / 12, q / 12 + 0.35, th);
995
+ dots.push(n("circle", `lattice.${q}`, {
996
+ cx: 312 + i * 18,
997
+ cy: 2 + j * 18,
998
+ r: f2(1.2 + 2.2 * kk),
999
+ opacity: f3(kk * 0.55)
1000
+ }));
1001
+ }
1002
+ scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
1003
+ }
1004
+ scene.push(av);
1005
+ if (grainy)
1006
+ scene.push(n("rect", "grain", {
1007
+ ...VBr,
1008
+ filter: `url(#${id}-grain)`,
1009
+ opacity: o.frame === "ink" ? 0.3 : 0.16,
1010
+ "pointer-events": "none"
1011
+ }));
1012
+ const root = n("svg", "root", {
1013
+ xmlns: "http://www.w3.org/2000/svg",
1014
+ viewBox: VB.join(" "),
1015
+ width: o.size,
1016
+ height: o.size,
1017
+ role: o.title === false ? undefined : "img",
1018
+ "aria-label": o.title === false ? undefined : o.title,
1019
+ "aria-hidden": o.title === false ? "true" : undefined
1020
+ }, [
1021
+ n("defs", "defs", {}, defs),
1022
+ n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
1023
+ ]);
1024
+ return o.live ? root : prune(root) ?? root;
1025
+ }
1026
+
1027
+ // src/animate.ts
1028
+ var GROUP_OF = {};
1029
+ for (const gr of Object.keys(GROUPS))
1030
+ for (const c of GROUPS[gr])
1031
+ GROUP_OF[c] = gr;
1032
+ var CHANNELS = Object.keys(GROUP_OF);
1033
+ var DEF_DELAY = {
1034
+ eyes: 0,
1035
+ pupil: 50,
1036
+ brows: 110,
1037
+ mouth: 170,
1038
+ extra: 140,
1039
+ body: 230
1040
+ };
1041
+ var DEF_SPR = {
1042
+ eyes: [14, 0.82],
1043
+ pupil: [15, 0.8],
1044
+ brows: [11, 0.72],
1045
+ mouth: [12, 0.74],
1046
+ body: [7, 0.72],
1047
+ extra: [8, 0.5]
1048
+ };
1049
+ var CHOREO = {
1050
+ happy: {
1051
+ delay: {
1052
+ eyes: 0,
1053
+ pupil: 40,
1054
+ brows: 120,
1055
+ mouth: 200,
1056
+ extra: 220,
1057
+ body: 280
1058
+ },
1059
+ spr: { body: [9, 0.42], extra: [9, 0.35] },
1060
+ keys: [
1061
+ [280, { alt: 0.2 }, { body: [11, 0.5] }],
1062
+ [520, { alt: 0.05 }, { body: [8, 0.38] }]
1063
+ ]
1064
+ },
1065
+ sad: {
1066
+ delay: {
1067
+ eyes: 0,
1068
+ pupil: 160,
1069
+ brows: 260,
1070
+ mouth: 380,
1071
+ extra: 420,
1072
+ body: 560
1073
+ },
1074
+ spr: {
1075
+ eyes: [6, 0.95],
1076
+ pupil: [5, 0.95],
1077
+ brows: [6, 0.95],
1078
+ mouth: [6, 0.9],
1079
+ body: [3.2, 0.95],
1080
+ extra: [3, 0.9]
1081
+ }
1082
+ },
1083
+ angry: {
1084
+ delay: { brows: 0, eyes: 40, pupil: 60, mouth: 160, body: 100, extra: 60 },
1085
+ spr: { brows: [18, 0.7], eyes: [16, 0.75], body: [14, 0.5] }
1086
+ },
1087
+ sleepy: {
1088
+ delay: {
1089
+ eyes: 0,
1090
+ pupil: 200,
1091
+ brows: 750,
1092
+ mouth: 950,
1093
+ extra: 900,
1094
+ body: 1150
1095
+ },
1096
+ spr: {
1097
+ eyes: [2.6, 1],
1098
+ pupil: [3, 1],
1099
+ brows: [3.2, 1],
1100
+ mouth: [3, 1],
1101
+ extra: [2.4, 0.9],
1102
+ body: [1.9, 1]
1103
+ },
1104
+ keys: [
1105
+ [0, { lid: 0.62 }],
1106
+ [1300, { lid: 1 }]
1107
+ ]
1108
+ },
1109
+ curious: {
1110
+ delay: { eyes: 0, pupil: 0, brows: 220, mouth: 320, extra: 0, body: 0 },
1111
+ spr: { body: [6, 0.95] },
1112
+ keys: [
1113
+ [0, { alt: -0.8, rot: 0, gx: 0, gy: -0.1, thing: 0 }],
1114
+ [900, { alt: -0.56 }, { body: [2.2, 1] }],
1115
+ [1500, { thing: 1 }, { extra: [5, 0.9] }],
1116
+ [2200, { look: 1 }, { pupil: [6, 0.9] }],
1117
+ [2500, { rot: 7 }, { body: [3, 0.9] }],
1118
+ [2900, { alt: -0.32 }, { body: [2, 1] }]
1119
+ ]
1120
+ },
1121
+ surprised: {
1122
+ delay: { eyes: 0, pupil: 0, brows: 0, mouth: 40, extra: 60, body: 0 },
1123
+ spr: {
1124
+ eyes: [30, 0.62],
1125
+ pupil: [26, 0.7],
1126
+ brows: [24, 0.55],
1127
+ mouth: [22, 0.6],
1128
+ extra: [15, 0.32]
1129
+ },
1130
+ keys: [
1131
+ [0, { alt: (v) => v - 0.1, sy: 0.9, sx: 1.05 }, { body: [30, 1] }],
1132
+ [110, { alt: 0.36, sy: 1.08, sx: 0.97 }, { body: [17, 0.42] }],
1133
+ [380, { sy: 1, sx: 1 }, { body: [10, 0.5] }]
1134
+ ]
1135
+ },
1136
+ excited: {
1137
+ delay: { eyes: 0, pupil: 30, brows: 80, mouth: 140, extra: 120, body: 180 },
1138
+ spr: { body: [9, 0.4], extra: [12, 0.3] }
1139
+ },
1140
+ confused: {
1141
+ delay: {
1142
+ eyes: 0,
1143
+ brows: 60,
1144
+ pupil: 100,
1145
+ mouth: 250,
1146
+ body: 200,
1147
+ extra: 200
1148
+ },
1149
+ spr: { body: [5, 0.7] }
1150
+ },
1151
+ bored: {
1152
+ delay: {
1153
+ eyes: 0,
1154
+ pupil: 300,
1155
+ brows: 500,
1156
+ mouth: 600,
1157
+ extra: 700,
1158
+ body: 800
1159
+ },
1160
+ spr: {
1161
+ eyes: [3, 1],
1162
+ pupil: [2.5, 1],
1163
+ brows: [3, 1],
1164
+ mouth: [3, 1],
1165
+ body: [1.6, 1],
1166
+ extra: [2, 1]
1167
+ }
1168
+ },
1169
+ attentive: {
1170
+ delay: {
1171
+ eyes: 0,
1172
+ pupil: 180,
1173
+ brows: 90,
1174
+ mouth: 240,
1175
+ extra: 200,
1176
+ body: 360
1177
+ },
1178
+ spr: {
1179
+ eyes: [20, 0.8],
1180
+ pupil: [20, 0.85],
1181
+ brows: [16, 0.7],
1182
+ body: [5, 0.78],
1183
+ extra: [11, 0.4]
1184
+ }
1185
+ }
1186
+ };
1187
+ var ATTENTION = {
1188
+ normal: 0.8,
1189
+ happy: 0.6,
1190
+ sad: 0.2,
1191
+ angry: 0.6,
1192
+ sleepy: 0,
1193
+ curious: 1,
1194
+ surprised: 0.5,
1195
+ excited: 0.5,
1196
+ confused: 0.3,
1197
+ bored: 0.1,
1198
+ attentive: 1
1199
+ };
1200
+ var BLINK = {
1201
+ normal: [2.4, 5],
1202
+ happy: [2.2, 4],
1203
+ sad: [3, 6],
1204
+ angry: [3.5, 6],
1205
+ sleepy: null,
1206
+ curious: [3.5, 6],
1207
+ surprised: [3, 5],
1208
+ excited: [1.8, 3.2],
1209
+ confused: [2, 4],
1210
+ bored: [4.5, 7.5],
1211
+ attentive: [4.5, 7.5]
1212
+ };
1213
+ var rand = (a, b) => a + Math.random() * (b - a);
1214
+ var zero = () => {
1215
+ const o = {};
1216
+ for (const c of CHANNELS)
1217
+ o[c] = 0;
1218
+ return o;
1219
+ };
1220
+ var ALL = new Set;
1221
+ var LIVE = new Set;
1222
+ var raf = 0;
1223
+ var last = 0;
1224
+ function tick(now) {
1225
+ const dt = Math.min(0.05, (now - last) / 1000);
1226
+ last = now;
1227
+ if (!document.hidden) {
1228
+ for (const l of LIVE)
1229
+ if (l.visible)
1230
+ l.measure();
1231
+ for (const l of LIVE)
1232
+ if (l.visible)
1233
+ l.step(dt);
1234
+ }
1235
+ raf = LIVE.size ? requestAnimationFrame(tick) : 0;
1236
+ }
1237
+ function run() {
1238
+ if (raf)
1239
+ return;
1240
+ last = performance.now();
1241
+ raf = requestAnimationFrame(tick);
1242
+ }
1243
+ var motionQuery = null;
1244
+ function reducedMotion() {
1245
+ if (!motionQuery) {
1246
+ motionQuery = matchMedia("(prefers-reduced-motion: reduce)");
1247
+ motionQuery.addEventListener("change", (e) => {
1248
+ for (const l of ALL)
1249
+ l.setReduced(e.matches);
1250
+ });
1251
+ }
1252
+ return motionQuery.matches;
1253
+ }
1254
+ var pointer = null;
1255
+ var listening = false;
1256
+ function listen() {
1257
+ if (listening)
1258
+ return;
1259
+ listening = true;
1260
+ const at = (e) => {
1261
+ pointer = [e.clientX, e.clientY];
1262
+ };
1263
+ document.addEventListener("pointermove", at, { passive: true });
1264
+ document.addEventListener("pointerdown", at, { passive: true });
1265
+ document.documentElement.addEventListener("pointerleave", () => {
1266
+ pointer = null;
1267
+ });
1268
+ }
1269
+
1270
+ class Live {
1271
+ svg;
1272
+ who;
1273
+ opts;
1274
+ visible = true;
1275
+ state;
1276
+ reduced;
1277
+ nodes = new Map;
1278
+ prev = new Map;
1279
+ io = null;
1280
+ clock = 0;
1281
+ enteredAt = 0;
1282
+ val;
1283
+ vel = zero();
1284
+ base;
1285
+ spr = {};
1286
+ out = zero();
1287
+ queue = [];
1288
+ bumps = [];
1289
+ blinkAt = rand(1, 3);
1290
+ blinkT = -1;
1291
+ blinkDur = 0.16;
1292
+ blinkV = 0;
1293
+ doubleBlink = false;
1294
+ saccAt = rand(0.6, 1.8);
1295
+ sacc = { gx: 0, gy: 0 };
1296
+ nodAt = 0;
1297
+ lookThing;
1298
+ lag = { y: 0, v: 0 };
1299
+ fixed = null;
1300
+ watch = false;
1301
+ rect = null;
1302
+ constructor(svg, tree, who, opts, expression, gaze) {
1303
+ this.svg = svg;
1304
+ this.who = who;
1305
+ this.opts = opts;
1306
+ this.state = expression;
1307
+ this.bind(tree, svg);
1308
+ this.setGaze(gaze);
1309
+ const start = restPose(who, expression, this.fixed ?? undefined);
1310
+ this.val = { ...start };
1311
+ this.base = { ...EXPRESSIONS[expression] };
1312
+ if (this.fixed)
1313
+ [this.base.gx, this.base.gy] = this.fixed;
1314
+ for (const c of CHANNELS)
1315
+ this.spr[c] = DEF_SPR[GROUP_OF[c]];
1316
+ this.lookThing = expression === "curious";
1317
+ this.lag.y = -start.alt * frameOf(FACES[who.face]).R;
1318
+ this.reduced = reducedMotion();
1319
+ this.paint();
1320
+ this.io = new IntersectionObserver((es) => {
1321
+ for (const e of es)
1322
+ this.visible = e.isIntersecting;
1323
+ });
1324
+ this.io.observe(svg);
1325
+ ALL.add(this);
1326
+ if (this.reduced)
1327
+ return;
1328
+ LIVE.add(this);
1329
+ run();
1330
+ }
1331
+ destroy() {
1332
+ ALL.delete(this);
1333
+ LIVE.delete(this);
1334
+ this.io?.disconnect();
1335
+ }
1336
+ setReduced(reduced) {
1337
+ if (reduced === this.reduced)
1338
+ return;
1339
+ this.reduced = reduced;
1340
+ if (!reduced) {
1341
+ LIVE.add(this);
1342
+ run();
1343
+ return;
1344
+ }
1345
+ LIVE.delete(this);
1346
+ this.snap();
1347
+ }
1348
+ measure() {
1349
+ this.rect = this.watch && pointer ? this.svg.getBoundingClientRect() : null;
1350
+ }
1351
+ bind(node, el) {
1352
+ this.nodes.set(node.key, el);
1353
+ const now = {};
1354
+ for (const a of Array.from(el.attributes))
1355
+ if (a.name !== "class" && a.name !== "style")
1356
+ now[a.name] = a.value;
1357
+ this.prev.set(node.key, now);
1358
+ node.children.forEach((c, i) => {
1359
+ const child = el.children[i];
1360
+ if (child)
1361
+ this.bind(c, child);
1362
+ });
1363
+ }
1364
+ write(node) {
1365
+ const el = this.nodes.get(node.key);
1366
+ const prev = this.prev.get(node.key) ?? {};
1367
+ if (el) {
1368
+ for (const k in node.attrs)
1369
+ if (prev[k] !== node.attrs[k])
1370
+ el.setAttribute(k, node.attrs[k]);
1371
+ for (const k in prev)
1372
+ if (!(k in node.attrs))
1373
+ el.removeAttribute(k);
1374
+ }
1375
+ this.prev.set(node.key, node.attrs);
1376
+ for (const c of node.children)
1377
+ this.write(c);
1378
+ }
1379
+ setGaze(gaze) {
1380
+ this.fixed = Array.isArray(gaze) ? gaze : null;
1381
+ this.watch = gaze === "pointer";
1382
+ if (this.watch)
1383
+ listen();
1384
+ if (this.reduced && this.fixed) {
1385
+ [this.val.gx, this.val.gy] = this.fixed;
1386
+ this.paint();
1387
+ }
1388
+ }
1389
+ setExpression(name) {
1390
+ if (name === this.state)
1391
+ return;
1392
+ const prev = this.state;
1393
+ this.state = name;
1394
+ this.enteredAt = this.clock;
1395
+ this.lookThing = false;
1396
+ this.queue = [];
1397
+ const tgt = EXPRESSIONS[name];
1398
+ const ch = CHOREO[name] ?? {};
1399
+ const delay = { ...DEF_DELAY, ...ch.delay };
1400
+ const spr = { ...DEF_SPR, ...ch.spr };
1401
+ if (this.reduced) {
1402
+ this.snap();
1403
+ return;
1404
+ }
1405
+ for (const gr of Object.keys(GROUPS)) {
1406
+ const set = {};
1407
+ for (const c of GROUPS[gr])
1408
+ set[c] = tgt[c];
1409
+ this.queue.push({
1410
+ at: this.clock + delay[gr] / 1000,
1411
+ set,
1412
+ spr: { [gr]: spr[gr] }
1413
+ });
1414
+ }
1415
+ for (const [t, set, s] of ch.keys ?? [])
1416
+ this.queue.push({ at: this.clock + t / 1000 + 0.0001, set, spr: s });
1417
+ this.queue.sort((a, b) => a.at - b.at);
1418
+ if (name === "surprised") {
1419
+ this.blinkAt = this.clock + 0.75;
1420
+ this.doubleBlink = true;
1421
+ }
1422
+ if (name === "sleepy")
1423
+ this.nodAt = this.clock + rand(6, 8);
1424
+ if (prev === "sleepy")
1425
+ this.bumps = [];
1426
+ }
1427
+ snap() {
1428
+ const rest = restPose(this.who, this.state, this.fixed ?? undefined);
1429
+ for (const c of CHANNELS) {
1430
+ this.base[c] = EXPRESSIONS[this.state][c];
1431
+ this.val[c] = rest[c];
1432
+ this.vel[c] = 0;
1433
+ }
1434
+ this.lookThing = this.state === "curious";
1435
+ this.queue = [];
1436
+ this.bumps = [];
1437
+ this.blinkT = -1;
1438
+ this.blinkV = 0;
1439
+ this.out = zero();
1440
+ this.paint();
1441
+ }
1442
+ bump(ch, amt, a, h, r) {
1443
+ this.bumps.push({ ch, amt, t0: this.clock, a, h, r });
1444
+ }
1445
+ step(dt) {
1446
+ this.clock += dt;
1447
+ const t = this.clock;
1448
+ const age = t - this.enteredAt;
1449
+ const st = this.state;
1450
+ const face = FACES[this.who.face];
1451
+ while (this.queue.length && this.queue[0].at <= t) {
1452
+ const q = this.queue.shift();
1453
+ if (q.spr)
1454
+ for (const gr of Object.keys(q.spr)) {
1455
+ const s = q.spr[gr];
1456
+ if (s)
1457
+ for (const c of GROUPS[gr])
1458
+ this.spr[c] = s;
1459
+ }
1460
+ for (const c of Object.keys(q.set)) {
1461
+ if (c === "look") {
1462
+ this.lookThing = true;
1463
+ continue;
1464
+ }
1465
+ const v = q.set[c];
1466
+ this.base[c] = typeof v === "function" ? v(this.val[c]) : v;
1467
+ }
1468
+ }
1469
+ const tgt = zero();
1470
+ const out = zero();
1471
+ let gx = this.base.gx;
1472
+ let gy = this.base.gy;
1473
+ if (this.lookThing)
1474
+ [gx, gy] = thingGaze(face, this.val.x, this.val.alt);
1475
+ if (this.fixed)
1476
+ [gx, gy] = this.fixed;
1477
+ const still = st === "sleepy" || st === "bored";
1478
+ if (!still && t >= this.saccAt) {
1479
+ this.sacc = { gx: rand(-0.17, 0.17), gy: rand(-0.1, 0.1) };
1480
+ this.saccAt = t + rand(0.8, 2.6);
1481
+ }
1482
+ if (!still) {
1483
+ gx += this.sacc.gx;
1484
+ gy += this.sacc.gy;
1485
+ }
1486
+ const w = ATTENTION[st];
1487
+ const r = this.rect;
1488
+ if (pointer && r && w > 0) {
1489
+ if (r.width > 0) {
1490
+ const fx = VB[0] + (pointer[0] - r.left) / r.width * VB[2];
1491
+ const fy = VB[1] + (pointer[1] - r.top) / r.height * VB[3];
1492
+ const { baseY, eyeY, R: R2 } = frameOf(face);
1493
+ const ex = 170 + this.val.x;
1494
+ const ey = eyeY + baseY - this.val.alt * R2;
1495
+ const px = clamp((fx - ex) / 170, -1, 1);
1496
+ const py = clamp((fy - ey) / 170, -1, 1);
1497
+ gx += (px - gx) * w;
1498
+ gy += (py - gy) * w;
1499
+ }
1500
+ }
1501
+ tgt.gx = gx - this.base.gx;
1502
+ tgt.gy = gy - this.base.gy;
1503
+ const br = st === "sleepy" ? 1.3 : st === "bored" ? 1.2 : st === "attentive" ? 2.6 : 2.1;
1504
+ const ba = st === "sleepy" ? 0.018 : st === "attentive" ? 0.004 : 0.007;
1505
+ out.sy += ba * Math.sin(t * br);
1506
+ out.alt += (st === "sleepy" ? 0.03 : 0.008) * Math.sin(t * br + 1.1);
1507
+ if (st === "happy")
1508
+ out.rot += 1.4 * Math.sin(t * 1.7);
1509
+ if (st === "angry") {
1510
+ out.x += 7 * Math.sin(age * 42) * Math.exp(-age * 6);
1511
+ out.x += 0.35 * Math.sin(t * 31);
1512
+ }
1513
+ if (st === "excited") {
1514
+ const ph = age % 1.5;
1515
+ if (ph < 0.9) {
1516
+ const h2 = Math.max(0, Math.sin(ph / 0.45 * Math.PI));
1517
+ out.alt += h2 * 0.17;
1518
+ out.sy += h2 * 0.045;
1519
+ out.rot += 2 * Math.sin(t * 9) * h2;
1520
+ }
1521
+ }
1522
+ if (st === "confused") {
1523
+ tgt.gx += 0.45 * Math.sin(age * 1.1);
1524
+ out.rot += 2.2 * Math.sin(age * 0.7);
1525
+ }
1526
+ if (st === "bored")
1527
+ tgt.gx += 0.22 * Math.sin(t * 0.33);
1528
+ if (st === "sleepy" && t >= this.nodAt && age > 3) {
1529
+ this.bump("alt", 0.3, 0.14, 0.5, 1.6);
1530
+ this.bump("lid", -0.5, 0.14, 0.45, 1.3);
1531
+ this.bump("hair", 0.6, 0.12, 0.4, 1.2);
1532
+ this.nodAt = t + rand(6.5, 9.5);
1533
+ }
1534
+ if ((st === "sad" || st === "bored") && Math.random() < dt / 7) {
1535
+ this.bump("sy", -0.035, 0.5, 0.25, 0.9);
1536
+ this.bump("alt", -0.05, 0.5, 0.25, 0.9);
1537
+ }
1538
+ const h = dt / 2;
1539
+ for (const c of CHANNELS) {
1540
+ const [om, ze] = this.spr[c];
1541
+ const target = this.base[c] + tgt[c];
1542
+ let x = this.val[c];
1543
+ let v = this.vel[c];
1544
+ for (let i = 0;i < 2; i++) {
1545
+ v += (om * om * (target - x) - 2 * ze * om * v) * h;
1546
+ x += v * h;
1547
+ }
1548
+ this.val[c] = x;
1549
+ this.vel[c] = v;
1550
+ }
1551
+ this.bumps = this.bumps.filter((b) => {
1552
+ const u = t - b.t0;
1553
+ if (u > b.a + b.h + b.r)
1554
+ return false;
1555
+ 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);
1556
+ out[b.ch] += b.amt * e;
1557
+ return true;
1558
+ });
1559
+ const bl = BLINK[st];
1560
+ let blink = 0;
1561
+ if (bl && this.val.lid < 0.75) {
1562
+ if (this.blinkT < 0 && t >= this.blinkAt) {
1563
+ this.blinkT = 0;
1564
+ this.blinkDur = st === "bored" ? 0.42 : 0.16;
1565
+ }
1566
+ if (this.blinkT >= 0) {
1567
+ this.blinkT += dt;
1568
+ const u = this.blinkT / this.blinkDur;
1569
+ if (u >= 1) {
1570
+ this.blinkT = -1;
1571
+ if (this.doubleBlink) {
1572
+ this.doubleBlink = false;
1573
+ this.blinkAt = t + 0.09;
1574
+ } else {
1575
+ const k = this.who.persona.blink;
1576
+ this.blinkAt = t + rand(bl[0] * k, bl[1] * k);
1577
+ }
1578
+ } else
1579
+ blink = Math.sin(u * Math.PI);
1580
+ }
1581
+ } else if (!bl)
1582
+ this.blinkT = -1;
1583
+ this.blinkV = blink;
1584
+ const R = frameOf(face).R;
1585
+ const avY = -(this.val.alt + out.alt) * R;
1586
+ const lom = 10;
1587
+ const lze = 0.32;
1588
+ this.lag.v += (lom * lom * (avY - this.lag.y) - 2 * lze * lom * this.lag.v) * dt;
1589
+ this.lag.y += this.lag.v * dt;
1590
+ this.out = out;
1591
+ this.paint();
1592
+ }
1593
+ paint() {
1594
+ const R = frameOf(FACES[this.who.face]).R;
1595
+ const pose = { blink: this.blinkV, expression: this.state };
1596
+ for (const c of CHANNELS)
1597
+ pose[c] = this.val[c] + this.out[c];
1598
+ pose.lag = this.reduced ? 0 : clamp((this.lag.y + pose.alt * R) * 0.4, -16, 16);
1599
+ this.write(draw(this.who, pose, this.opts));
1600
+ }
1601
+ }
1602
+
1603
+ // src/svg.ts
1604
+ function settle(name, o, live = false) {
1605
+ const base = identify(name, { version: o.version });
1606
+ const who = {
1607
+ ...base,
1608
+ face: o.face ?? base.face,
1609
+ color: o.color ?? base.color,
1610
+ eyes: o.eyes ?? base.eyes,
1611
+ brows: o.brows ?? base.brows,
1612
+ mouth: o.mouth ?? base.mouth,
1613
+ cheeks: o.cheeks ?? base.cheeks,
1614
+ trait: o.trait ?? base.trait
1615
+ };
1616
+ const pose = restPose(who, o.expression ?? "normal", o.gaze);
1617
+ const look = {
1618
+ size: o.size ?? 64,
1619
+ frame: o.frame ?? "ink",
1620
+ square: o.square ?? true,
1621
+ riso: o.riso ?? false,
1622
+ live
1623
+ };
1624
+ const id = o.id ?? `peek-${fnv1a(JSON.stringify([who, pose, look])).toString(36)}`;
1625
+ return { who, pose, opts: { ...look, id, title: o.title ?? name } };
1626
+ }
1627
+ var ILLEGAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1628
+ var esc = (s) => s.replace(ILLEGAL, "�").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1629
+ function serialize(node) {
1630
+ let a = "";
1631
+ for (const k in node.attrs)
1632
+ a += ` ${k}="${esc(node.attrs[k])}"`;
1633
+ if (!node.children.length)
1634
+ return `<${node.tag}${a}/>`;
1635
+ return `<${node.tag}${a}>${node.children.map(serialize).join("")}</${node.tag}>`;
1636
+ }
1637
+ function toSvg(name, props = {}) {
1638
+ const { who, pose, opts } = settle(name, props);
1639
+ return serialize(draw(who, pose, opts));
1640
+ }
1641
+
1642
+ // src/peek.tsx
1643
+ var camel = (k) => k.startsWith("aria-") ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1644
+ function toReact(node, root) {
1645
+ const props = { key: node.key, ...root };
1646
+ for (const k in node.attrs)
1647
+ props[camel(k)] = node.attrs[k];
1648
+ return createElement(node.tag, props, ...node.children.map((c) => toReact(c)));
1649
+ }
1650
+ function Peek({
1651
+ name,
1652
+ gaze,
1653
+ animate = false,
1654
+ className,
1655
+ style,
1656
+ ...rest
1657
+ }) {
1658
+ const id = `peek${useId().replace(/[^\w-]/g, "")}`;
1659
+ const fixed = Array.isArray(gaze) ? gaze : undefined;
1660
+ const { who, pose, opts } = settle(name, { ...rest, gaze: fixed, id }, animate);
1661
+ const svg = useRef(null);
1662
+ const live = useRef(null);
1663
+ const expression = rest.expression ?? "normal";
1664
+ const [first] = useState(() => ({ expression, gaze: fixed }));
1665
+ const sig = JSON.stringify([who, opts]);
1666
+ const tree = draw(who, animate ? restPose(who, first.expression, first.gaze) : pose, opts);
1667
+ const gazeKey = String(gaze ?? "");
1668
+ useEffect(() => {
1669
+ if (!animate || !svg.current)
1670
+ return;
1671
+ const l = new Live(svg.current, tree, who, opts, expression, gaze);
1672
+ live.current = l;
1673
+ return () => {
1674
+ l.destroy();
1675
+ live.current = null;
1676
+ };
1677
+ }, [animate, sig]);
1678
+ useEffect(() => {
1679
+ live.current?.setExpression(expression);
1680
+ }, [expression]);
1681
+ useEffect(() => {
1682
+ live.current?.setGaze(gaze);
1683
+ }, [gazeKey]);
1684
+ return toReact(tree, { ref: svg, className, style });
1685
+ }
1686
+ export {
1687
+ AXES,
1688
+ COLORS,
1689
+ EXPRESSIONS,
1690
+ FACES,
1691
+ GROUPS,
1692
+ LATEST,
1693
+ LISTS,
1694
+ PARTS,
1695
+ Peek,
1696
+ VERSIONS,
1697
+ identify,
1698
+ tidy,
1699
+ toSvg
1700
+ };