@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/svg.js ADDED
@@ -0,0 +1,1053 @@
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 BASE = {
129
+ alt: 0,
130
+ rot: 0,
131
+ x: 0,
132
+ sx: 1,
133
+ sy: 1,
134
+ lid: 0.1,
135
+ lower: 0,
136
+ lidTilt: 0,
137
+ eyeS: 1,
138
+ lidAsym: 0,
139
+ pupil: 1,
140
+ gx: 0,
141
+ gy: 0,
142
+ shine: 0.7,
143
+ browY: 0,
144
+ browTilt: 0,
145
+ browArch: 0.4,
146
+ browW: 1,
147
+ browAsym: 0,
148
+ mw: 0.72,
149
+ mt: 0,
150
+ mb: 0.5,
151
+ mx: 0,
152
+ my: 0,
153
+ mk: 0,
154
+ blush: 0,
155
+ hair: 0,
156
+ dim: 0,
157
+ thing: 0
158
+ };
159
+ var S = (o) => ({ ...BASE, ...o });
160
+ var EXPRESSIONS = {
161
+ normal: S({}),
162
+ happy: S({
163
+ lid: 0,
164
+ lower: 0.42,
165
+ shine: 1,
166
+ gy: -0.05,
167
+ browY: -5,
168
+ browArch: 1,
169
+ mw: 1,
170
+ mb: 1,
171
+ blush: 1,
172
+ hair: 0.45,
173
+ alt: 0.05
174
+ }),
175
+ sad: S({
176
+ lid: 0.42,
177
+ lidTilt: 16,
178
+ pupil: 0.95,
179
+ gy: 0.8,
180
+ shine: 0,
181
+ browY: 3,
182
+ browTilt: 14,
183
+ browArch: 0,
184
+ mw: 0.8,
185
+ mt: -0.55,
186
+ mb: -0.55,
187
+ my: 6,
188
+ alt: -0.16,
189
+ sy: 0.985,
190
+ hair: -0.75
191
+ }),
192
+ angry: S({
193
+ lid: 0.34,
194
+ lidTilt: -22,
195
+ lower: 0.12,
196
+ pupil: 0.82,
197
+ gy: 0.1,
198
+ shine: 0,
199
+ browY: 5,
200
+ browTilt: -21,
201
+ browArch: 0,
202
+ browW: 1.3,
203
+ mw: 0.8,
204
+ mt: -0.08,
205
+ mb: -0.08,
206
+ my: 3,
207
+ sx: 1.02,
208
+ sy: 0.98,
209
+ hair: 0.25
210
+ }),
211
+ sleepy: S({
212
+ lid: 1,
213
+ lidTilt: 4,
214
+ pupil: 0.9,
215
+ gy: 0.5,
216
+ shine: 0,
217
+ browY: 6,
218
+ browTilt: 3,
219
+ browArch: 0.15,
220
+ browW: 0.9,
221
+ mw: 0.35,
222
+ mt: -0.3,
223
+ mb: 0.3,
224
+ my: 2,
225
+ alt: -1.12,
226
+ rot: -3,
227
+ hair: -1
228
+ }),
229
+ curious: S({
230
+ lid: 0,
231
+ eyeS: 1.06,
232
+ pupil: 1.05,
233
+ shine: 1,
234
+ browY: -3,
235
+ browAsym: 9,
236
+ browArch: 0.8,
237
+ mw: 0.38,
238
+ mt: -0.36,
239
+ mb: 0.36,
240
+ rot: 6,
241
+ alt: -0.32,
242
+ hair: 0.3,
243
+ thing: 1
244
+ }),
245
+ surprised: S({
246
+ lid: 0,
247
+ eyeS: 1.2,
248
+ pupil: 0.66,
249
+ shine: 0.8,
250
+ browY: -13,
251
+ browArch: 1,
252
+ mw: 0.55,
253
+ mt: -0.62,
254
+ mb: 0.62,
255
+ alt: 0.36,
256
+ hair: 1
257
+ }),
258
+ excited: S({
259
+ lid: 0,
260
+ lower: 0.5,
261
+ eyeS: 1.08,
262
+ pupil: 1.08,
263
+ shine: 1,
264
+ browY: -9,
265
+ browArch: 1,
266
+ mw: 1.15,
267
+ mb: 1.3,
268
+ blush: 1,
269
+ hair: 1,
270
+ alt: 0.12
271
+ }),
272
+ confused: S({
273
+ lid: 0.22,
274
+ lidAsym: 0.28,
275
+ lidTilt: -4,
276
+ pupil: 0.92,
277
+ gx: -0.35,
278
+ gy: -0.45,
279
+ shine: 0.3,
280
+ browY: -2,
281
+ browAsym: 11,
282
+ browTilt: -5,
283
+ browArch: 0.3,
284
+ mw: 0.6,
285
+ mt: -0.1,
286
+ mb: 0.12,
287
+ mx: 5,
288
+ mk: 13,
289
+ rot: -7,
290
+ alt: -0.05,
291
+ hair: 0.1
292
+ }),
293
+ bored: S({
294
+ lid: 0.5,
295
+ pupil: 0.9,
296
+ gx: -0.55,
297
+ gy: 0.25,
298
+ shine: 0,
299
+ browY: 3,
300
+ browArch: 0.1,
301
+ mw: 0.55,
302
+ mt: 0,
303
+ mb: 0.04,
304
+ mx: -5,
305
+ alt: -0.34,
306
+ hair: -0.45,
307
+ dim: 1
308
+ }),
309
+ attentive: S({
310
+ lid: 0,
311
+ eyeS: 1.1,
312
+ pupil: 0.8,
313
+ gy: -0.1,
314
+ shine: 1,
315
+ browY: -7,
316
+ browArch: 0.7,
317
+ mw: 0.55,
318
+ mb: 0.4,
319
+ alt: 0.15,
320
+ sy: 1.03,
321
+ hair: 0.65
322
+ })
323
+ };
324
+
325
+ // src/draw.ts
326
+ var VB = [-30, -30, 400, 400];
327
+ var FLOOR = 370;
328
+ var THING = [330, 20];
329
+ var clamp = (v, a, b) => v < a ? a : v > b ? b : v;
330
+ var smooth = (a, b, v) => {
331
+ const t = clamp((v - a) / (b - a), 0, 1);
332
+ return t * t * (3 - 2 * t);
333
+ };
334
+ var f2 = (v) => String(Math.round(v * 100) / 100);
335
+ var f3 = (v) => String(Math.round(v * 1000) / 1000);
336
+ var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
337
+ var RAD = Math.PI / 180;
338
+ function frameOf(face) {
339
+ const baseY = FLOOR + face.sink - face.bottom;
340
+ const eyeY = face.eyes[0][1];
341
+ return { baseY, eyeY, R: FLOOR - (eyeY + baseY) };
342
+ }
343
+ function thingGaze(face, x, alt) {
344
+ const { baseY, eyeY, R } = frameOf(face);
345
+ const ey = eyeY + baseY - alt * R;
346
+ return [
347
+ clamp((THING[0] - 170 - x) / 150, -1, 1),
348
+ clamp((THING[1] - ey) / 150, -1, 1)
349
+ ];
350
+ }
351
+ function restPose(who, expression, gaze) {
352
+ const s = EXPRESSIONS[expression];
353
+ const pose = { ...s, blink: 0, lag: 0, expression };
354
+ if (expression === "curious")
355
+ pose.rot = 7;
356
+ const g = gaze ?? (expression === "curious" ? thingGaze(FACES[who.face], 0, s.alt) : null);
357
+ if (g)
358
+ [pose.gx, pose.gy] = g;
359
+ return pose;
360
+ }
361
+ function seat(crown, d, u) {
362
+ const R = Math.max(0, crown.r + d);
363
+ const at = ([x, y], b2) => ({
364
+ x: x + R * Math.sin(b2),
365
+ y: y - R * Math.cos(b2),
366
+ a: b2 / RAD
367
+ });
368
+ const P = crown.poly;
369
+ if (P.length === 1)
370
+ return at(P[0], R ? u / R : 0);
371
+ const n = P.length;
372
+ const dir = u >= 0 ? 1 : -1;
373
+ let left = Math.abs(u);
374
+ let i = 0;
375
+ let b = 0;
376
+ for (let step = 0;step < n; step++) {
377
+ const j = (i + dir + n) % n;
378
+ const [x0, y0] = P[i];
379
+ const [x1, y1] = P[j];
380
+ const ex = x1 - x0;
381
+ const ey = y1 - y0;
382
+ const L = Math.hypot(ex, ey);
383
+ const nx = dir * ey / L;
384
+ const ny = -dir * ex / L;
385
+ const be = Math.atan2(nx, -ny);
386
+ let db = be - b;
387
+ while (dir * db < 0)
388
+ db += dir * 2 * Math.PI;
389
+ const arc = R * Math.abs(db);
390
+ if (left <= arc)
391
+ return at(P[i], R ? b + dir * left / R : b);
392
+ left -= arc;
393
+ if (left <= L)
394
+ return {
395
+ x: x0 + ex / L * left + R * nx,
396
+ y: y0 + ey / L * left + R * ny,
397
+ a: be / RAD
398
+ };
399
+ left -= L;
400
+ i = j;
401
+ b = be;
402
+ }
403
+ return at(P[i], b);
404
+ }
405
+ function depth(crown, x, y) {
406
+ const P = crown.poly;
407
+ if (P.length === 1)
408
+ return Math.hypot(x - P[0][0], y - P[0][1]) - crown.r;
409
+ let out = -Infinity;
410
+ let near = Infinity;
411
+ for (let i = 0;i < P.length; i++) {
412
+ const [ax, ay] = P[i];
413
+ const [bx, by] = P[(i + 1) % P.length];
414
+ const ex = bx - ax;
415
+ const ey = by - ay;
416
+ const L2 = ex * ex + ey * ey;
417
+ const L = Math.sqrt(L2);
418
+ out = Math.max(out, ((x - ax) * ey - (y - ay) * ex) / L);
419
+ const t = clamp(((x - ax) * ex + (y - ay) * ey) / L2, 0, 1);
420
+ near = Math.min(near, Math.hypot(x - ax - t * ex, y - ay - t * ey));
421
+ }
422
+ return (out <= 0 ? out : near) - crown.r;
423
+ }
424
+ var RING_U = 158 * 35 * RAD;
425
+ function ringWalk(sh) {
426
+ const ex = sh.eyes[1][0] + sh.brow.hw + 7;
427
+ const top = sh.brow.y - 30;
428
+ const bottom = sh.brow.y + 8;
429
+ let u = RING_U;
430
+ for (;u < 400; u += 2) {
431
+ const s = seat(sh.crown, 8, u);
432
+ const dx = s.x - ex;
433
+ const dy = s.y - clamp(s.y, top, bottom);
434
+ if (s.x > ex ? Math.hypot(dx, dy) > 27 : s.y < top - 27)
435
+ break;
436
+ }
437
+ return u > RING_U ? { rest: u + 20, min: u } : { rest: u, min: -Infinity };
438
+ }
439
+ function n(tag, key, attrs, children = []) {
440
+ const out = {};
441
+ for (const k in attrs) {
442
+ const v = attrs[k];
443
+ if (v !== undefined && v !== false)
444
+ out[k] = String(v);
445
+ }
446
+ return { tag, key, attrs: out, children };
447
+ }
448
+ function prune(node) {
449
+ if (node.attrs.opacity === "0")
450
+ return null;
451
+ const children = node.children.map(prune).filter((c) => c !== null);
452
+ if (node.tag === "g" && children.length === 0)
453
+ return null;
454
+ return { ...node, children };
455
+ }
456
+ function draw(who, pose, o) {
457
+ const sh = FACES[who.face];
458
+ const { body: fill, deep } = COLORS[who.color];
459
+ const P = who.persona;
460
+ const mul = P.mul;
461
+ const add = P.add;
462
+ const g = (c) => pose[c] * (mul[c] ?? 1) + (add[c] ?? 0);
463
+ const spread = P.spread;
464
+ const id = o.id;
465
+ const st = strokeFor(o.size);
466
+ const { baseY, R } = frameOf(sh);
467
+ const defs = [];
468
+ const VBr = { x: VB[0], y: VB[1], width: VB[2], height: VB[3] };
469
+ defs.push(n("clipPath", "frame.clip", { id: `${id}-f` }, [
470
+ o.square ? n("rect", "frame.clip.shape", VBr) : n("circle", "frame.clip.shape", { cx: 170, cy: 170, r: 200 })
471
+ ]));
472
+ const k = o.riso ? smooth(120, 480, o.size) : 0;
473
+ const riso = k > 0.01;
474
+ const upp = VB[2] / Math.max(o.size, 1);
475
+ const mk = k * k;
476
+ const plate = (p, seedA, seedB, sc2) => n("filter", `riso.${p}`, {
477
+ id: `${id}-p${p}`,
478
+ x: "-20%",
479
+ y: "-20%",
480
+ width: "140%",
481
+ height: "140%",
482
+ "color-interpolation-filters": "sRGB"
483
+ }, [
484
+ n("feTurbulence", `riso.${p}.warp`, {
485
+ type: "fractalNoise",
486
+ baseFrequency: "0.032",
487
+ numOctaves: 2,
488
+ seed: seedA,
489
+ result: "warp"
490
+ }),
491
+ n("feDisplacementMap", `riso.${p}.disp`, {
492
+ in: "SourceGraphic",
493
+ in2: "warp",
494
+ scale: f2(sc2 * k),
495
+ xChannelSelector: "R",
496
+ yChannelSelector: "G",
497
+ result: "moved"
498
+ }),
499
+ n("feTurbulence", `riso.${p}.grain`, {
500
+ type: "fractalNoise",
501
+ baseFrequency: "0.85",
502
+ numOctaves: 2,
503
+ seed: seedB,
504
+ result: "grain"
505
+ }),
506
+ n("feColorMatrix", `riso.${p}.mask`, {
507
+ in: "grain",
508
+ type: "matrix",
509
+ 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)}`,
510
+ result: "mask"
511
+ }),
512
+ n("feComposite", `riso.${p}.out`, {
513
+ in: "moved",
514
+ in2: "mask",
515
+ operator: "in"
516
+ })
517
+ ]);
518
+ const grainy = riso && o.frame !== "none";
519
+ if (riso)
520
+ defs.push(plate("A", 4, 11, 3.2), plate("B", 9, 23, 2.2));
521
+ if (grainy) {
522
+ const [r, gg, b] = o.frame === "ink" ? [0.953, 0.941, 0.91] : [0.102, 0.098, 0.094];
523
+ defs.push(n("filter", "grain.filter", {
524
+ id: `${id}-grain`,
525
+ x: 0,
526
+ y: 0,
527
+ width: 1,
528
+ height: 1,
529
+ "color-interpolation-filters": "sRGB"
530
+ }, [
531
+ n("feTurbulence", "grain.noise", {
532
+ type: "fractalNoise",
533
+ baseFrequency: "0.9",
534
+ numOctaves: 2,
535
+ seed: 5
536
+ }),
537
+ n("feColorMatrix", "grain.matrix", {
538
+ type: "matrix",
539
+ values: `0 0 0 0 ${r} 0 0 0 0 ${gg} 0 0 0 0 ${b} 7 0 0 0 -5.1`
540
+ })
541
+ ]));
542
+ }
543
+ const dim = clamp(g("dim"), 0, 1);
544
+ if (o.live || dim > 0.01)
545
+ defs.push(n("filter", "dim.filter", { id: `${id}-dim`, "color-interpolation-filters": "sRGB" }, [
546
+ n("feColorMatrix", "dim.matrix", {
547
+ type: "saturate",
548
+ values: f3(1 - dim * 0.55)
549
+ })
550
+ ]));
551
+ const alt = g("alt");
552
+ const rot = g("rot");
553
+ const x = g("x");
554
+ const sx = g("sx");
555
+ const sy = g("sy");
556
+ const B = sh.bottom;
557
+ const ty = baseY - alt * R;
558
+ const sc = (v) => Math.round(v * 1e5) / 1e5;
559
+ const bodyNode = "d" in sh.body ? n("path", "body", {
560
+ d: sh.body.d,
561
+ fill,
562
+ stroke: sh.body.stroke ? fill : undefined,
563
+ "stroke-width": sh.body.stroke,
564
+ "stroke-linejoin": sh.body.stroke ? "round" : undefined
565
+ }) : n("circle", "body", {
566
+ cx: sh.body.circle[0],
567
+ cy: sh.body.circle[1],
568
+ r: sh.body.circle[2],
569
+ fill
570
+ });
571
+ const eyeS = g("eyeS");
572
+ const rx = sh.rx * eyeS;
573
+ const ry = sh.ry * eyeS;
574
+ const lid0 = g("lid");
575
+ const lidAsym = g("lidAsym");
576
+ const lower = clamp(g("lower"), 0, 1);
577
+ const blink = pose.blink;
578
+ const pupil = g("pupil");
579
+ const gx = clamp(g("gx"), -1.1, 1.1);
580
+ const gy = clamp(g("gy"), -1.1, 1.1);
581
+ const tilt = g("lidTilt");
582
+ const shine = clamp(g("shine"), 0, 1);
583
+ const sw = sh.brow.w * st;
584
+ const bead = who.eyes === "bead";
585
+ const eyes = sh.eyes.map(([ex, ey], i) => {
586
+ const side = i === 0 ? -1 : 1;
587
+ const kk = `eye${i}`;
588
+ let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1);
589
+ lid = lid + (1 - lid) * blink;
590
+ defs.push(n("clipPath", `${kk}.clip`, { id: `${id}-e${i}` }, [
591
+ n("ellipse", `${kk}.clip.shape`, {
592
+ cx: 0,
593
+ cy: 0,
594
+ rx: f2(rx),
595
+ ry: f2(ry)
596
+ })
597
+ ]), n("clipPath", `${kk}.lidclip`, { id: `${id}-l${i}` }, [
598
+ n("ellipse", `${kk}.lidclip.shape`, {
599
+ cx: 0,
600
+ cy: 0,
601
+ rx: f2(rx + 1.6),
602
+ ry: f2(ry + 1.6)
603
+ })
604
+ ]));
605
+ const br = Math.min(sh.rx, sh.ry) * 0.62;
606
+ const pr = bead ? br * eyeS * (0.4 + 0.6 * pupil) : sh.pr * pupil;
607
+ const base = bead ? br : sh.pr;
608
+ const px = gx * Math.max(0, sh.rx - base) * 0.9;
609
+ const py = gy * Math.max(0, sh.ry - base) * 0.8;
610
+ const rimW = Math.min(sw * 0.8, Math.min(sh.rx, sh.ry) * 0.28);
611
+ const inner = [];
612
+ if (!bead)
613
+ inner.push(n("ellipse", `${kk}.white`, {
614
+ cx: 0,
615
+ cy: 0,
616
+ rx: f2(rx),
617
+ ry: f2(ry),
618
+ fill: INK.paper
619
+ }));
620
+ inner.push(n("circle", `${kk}.pupil`, {
621
+ cx: f2(px),
622
+ cy: f2(py),
623
+ r: f2(pr),
624
+ fill: INK.ink
625
+ }), n("circle", `${kk}.shine`, {
626
+ cx: f2(px + pr * 0.44),
627
+ cy: f2(py - pr * 0.44),
628
+ r: f2(pr * (bead ? 0.26 : 0.3)),
629
+ fill: INK.paper,
630
+ opacity: f2(shine)
631
+ }));
632
+ if (who.eyes === "ring")
633
+ inner.push(n("ellipse", `${kk}.rim`, {
634
+ cx: 0,
635
+ cy: 0,
636
+ rx: f2(rx - rimW / 2),
637
+ ry: f2(ry - rimW / 2),
638
+ fill: "none",
639
+ stroke: INK.ink,
640
+ "stroke-width": f2(rimW)
641
+ }));
642
+ const edge = -ry - 1.6 + lid * (2 * ry + 3.2);
643
+ const a = side * tilt;
644
+ const lrx = rx * 1.55;
645
+ const lry = ry * 1.15;
646
+ const top = ry + 1.6 - lower * ry * 1.5;
647
+ const parts = [
648
+ n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
649
+ n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, [
650
+ n("rect", `${kk}.lid`, {
651
+ x: f2(-rx * 2.2),
652
+ y: f2(-ry * 3),
653
+ width: f2(rx * 4.4),
654
+ height: f2(Math.max(0, edge + ry * 3)),
655
+ transform: `rotate(${f2(a)})`,
656
+ fill
657
+ }),
658
+ n("ellipse", `${kk}.lower`, {
659
+ cx: 0,
660
+ cy: f2(top + lry),
661
+ rx: f2(lrx),
662
+ ry: f2(lry),
663
+ fill
664
+ })
665
+ ])
666
+ ];
667
+ if (who.eyes === "ring")
668
+ parts.push(n("g", `${kk}.edges`, {
669
+ "clip-path": `url(#${id}-e${i})`,
670
+ fill: "none",
671
+ stroke: INK.ink,
672
+ "stroke-width": f2(rimW)
673
+ }, [
674
+ n("path", `${kk}.edge`, {
675
+ d: `M${f2(-rx * 2.2)} ${f2(edge)}H${f2(rx * 2.2)}`,
676
+ transform: `rotate(${f2(a)})`,
677
+ opacity: lid > 0.02 ? 1 : 0
678
+ }),
679
+ n("ellipse", `${kk}.edge.lower`, {
680
+ cx: 0,
681
+ cy: f2(top + lry),
682
+ rx: f2(lrx),
683
+ ry: f2(lry),
684
+ opacity: lower > 0.02 ? 1 : 0
685
+ })
686
+ ]));
687
+ const co = smooth(0.86, 1, lid);
688
+ const yy = Math.min(edge, ry) - 1;
689
+ const hw = rx * 0.92;
690
+ parts.push(n("path", `${kk}.closed`, {
691
+ d: `M${f2(-hw)} ${f2(yy - 2)}Q0 ${f2(yy + 6)} ${f2(hw)} ${f2(yy - 2)}`,
692
+ transform: `rotate(${f2(a * 0.6)})`,
693
+ fill: "none",
694
+ stroke: INK.ink,
695
+ "stroke-linecap": "round",
696
+ "stroke-width": f2(sw * 0.85),
697
+ opacity: co > 0.01 ? f2(co) : 0
698
+ }));
699
+ return n("g", kk, {
700
+ transform: bead ? `translate(${f2(ex + side * spread + gx * 6)} ${f2(ey + gy * 6)})` : `translate(${f2(ex + side * spread)} ${ey})`
701
+ }, parts);
702
+ });
703
+ const browY = g("browY") - (eyeS - 1) * sh.ry * 0.9;
704
+ const btilt = g("browTilt");
705
+ const arch = g("browArch");
706
+ const basym = g("browAsym");
707
+ const bw = sw * g("browW");
708
+ const brows = sh.eyes.map(([ex], i) => {
709
+ const side = i === 0 ? -1 : 1;
710
+ const hw = sh.brow.hw;
711
+ const c = -sh.brow.arch * arch;
712
+ 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];
713
+ const ti = bw * 1.7;
714
+ const to = bw * 0.45;
715
+ const [tl, tr] = side < 0 ? [to, ti] : [ti, to];
716
+ const wedge = who.brows === "wedge";
717
+ const [hl, hr] = wedge ? [tl / 2 + to / 2, tr / 2 + to / 2] : [0, 0];
718
+ const half = (t) => wedge ? hl + (hr - hl) * t : bw * k2 / 2;
719
+ const bx = ex + side * spread;
720
+ const by = sh.brow.y + browY + (i === 1 ? -basym : basym * 0.3);
721
+ const cos = Math.cos(side * btilt * RAD);
722
+ const sin = Math.sin(side * btilt * RAD);
723
+ const inside = (dy) => {
724
+ for (let t = 0;t <= 1; t += 0.25) {
725
+ const lx = w * (2 * t - 1);
726
+ const ly = 2 * t * (1 - t) * ca;
727
+ const px = bx + lx * cos - ly * sin;
728
+ const py = by + dy + lx * sin + ly * cos;
729
+ if (depth(sh.crown, px, py) + half(t) + 2 > 0)
730
+ return false;
731
+ }
732
+ return true;
733
+ };
734
+ let lo = 0;
735
+ let hi = 0;
736
+ if (!inside(0)) {
737
+ hi = 40;
738
+ for (let k3 = 0;k3 < 12; k3++) {
739
+ const mid2 = (lo + hi) / 2;
740
+ if (inside(mid2))
741
+ hi = mid2;
742
+ else
743
+ lo = mid2;
744
+ }
745
+ }
746
+ const transform = `translate(${f2(bx)} ${f2(by + hi)}) rotate(${f2(side * btilt)})`;
747
+ const key = `brow${i}`;
748
+ if (wedge) {
749
+ const dm = (tl + tr) / 2 * 0.75;
750
+ return n("path", key, {
751
+ 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`,
752
+ transform,
753
+ fill: INK.ink,
754
+ stroke: INK.ink,
755
+ "stroke-linejoin": "round",
756
+ "stroke-width": f2(to)
757
+ });
758
+ }
759
+ return n("path", key, {
760
+ d: `M${f2(-w)} 0Q0 ${f2(ca)} ${f2(w)} 0`,
761
+ transform,
762
+ fill: "none",
763
+ stroke: INK.ink,
764
+ "stroke-linecap": "round",
765
+ "stroke-linejoin": "round",
766
+ "stroke-width": f2(bw * k2)
767
+ });
768
+ });
769
+ const m = sh.mouth;
770
+ const mw = m.w * Math.max(0.05, g("mw"));
771
+ const mt = g("mt") * m.d;
772
+ const mb = g("mb") * m.d;
773
+ const kb = 4 / 3;
774
+ const w7 = mw * 0.72;
775
+ const mid = (mt + mb) / 2 * kb;
776
+ 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`;
777
+ const open = who.mouth === "line";
778
+ const mouth = n("path", "mouth", {
779
+ d,
780
+ transform: `translate(${f2(m.x + g("mx"))} ${f2(m.y + g("my"))}) rotate(${f2(g("mk"))})`,
781
+ fill: open ? "none" : INK.ink,
782
+ stroke: INK.ink,
783
+ "stroke-linecap": "round",
784
+ "stroke-linejoin": "round",
785
+ "stroke-width": f2(open ? sw : m.sw * st)
786
+ });
787
+ const blush = clamp(g("blush"), 0, 1);
788
+ const cheeks = sh.cheeks.map(([cx, cy], i) => {
789
+ const key = `cheek${i}`;
790
+ const at = {
791
+ opacity: f2(blush),
792
+ 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})`
793
+ };
794
+ if (who.cheeks === "dots") {
795
+ const r = sh.cry * 0.9;
796
+ return n("g", key, { ...at, fill: deep }, [
797
+ n("circle", `${key}.0`, { cx: f2(cx - sh.crx * 1.05), cy, r: f2(r) }),
798
+ n("circle", `${key}.1`, { cx, cy: f2(cy + sh.cry * 0.5), r: f2(r) }),
799
+ n("circle", `${key}.2`, { cx: f2(cx + sh.crx * 1.05), cy, r: f2(r) })
800
+ ]);
801
+ }
802
+ if (who.cheeks === "lines") {
803
+ const h = sh.cry * 1.3;
804
+ return n("g", key, {
805
+ ...at,
806
+ fill: "none",
807
+ stroke: deep,
808
+ "stroke-linecap": "round",
809
+ "stroke-width": f2(sh.cry * 0.78)
810
+ }, [-1, 0, 1].map((j) => {
811
+ const x0 = cx + j * sh.crx * 0.8;
812
+ return n("path", `${key}.${j + 1}`, {
813
+ d: `M${f2(x0 + h * 0.5)} ${f2(cy - h)}L${f2(x0 - h * 0.5)} ${f2(cy + h)}`
814
+ });
815
+ }));
816
+ }
817
+ return n("ellipse", key, {
818
+ cx,
819
+ cy,
820
+ rx: sh.crx,
821
+ ry: sh.cry,
822
+ fill: deep,
823
+ ...at
824
+ });
825
+ });
826
+ const hair = g("hair");
827
+ const lag = pose.lag;
828
+ let traitT;
829
+ let shape;
830
+ if (who.trait === "square") {
831
+ const s = seat(sh.crown, 22 + hair * 10, 0);
832
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag)}) rotate(${f2(s.a + 22 + hair * 23)})`;
833
+ shape = n("rect", "trait.shape", {
834
+ x: -11,
835
+ y: -11,
836
+ width: 22,
837
+ height: 22,
838
+ fill: deep
839
+ });
840
+ } else if (who.trait === "fin") {
841
+ const s = seat(sh.crown, -6, -164 * 22 * RAD);
842
+ const r = s.a + (hair >= 0 ? hair * 20 : hair * 55) - lag * 0.8;
843
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag * 0.25)}) rotate(${f2(r)})`;
844
+ shape = n("path", "trait.shape", {
845
+ d: "M0 0L0 -40A40 40 0 0 1 40 0Z",
846
+ fill: deep
847
+ });
848
+ } else if (who.trait === "ring") {
849
+ const b = 35 - hair * 14 - (hair < 0 ? hair * -21 : 0) + lag * 0.9;
850
+ const w = ringWalk(sh);
851
+ const s = seat(sh.crown, 8, Math.max(w.min, w.rest + 158 * (b - 35) * RAD));
852
+ traitT = `translate(${f2(s.x)} ${f2(s.y)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
853
+ shape = n("circle", "trait.shape", {
854
+ cx: 0,
855
+ cy: 0,
856
+ r: 12.5,
857
+ fill: "none",
858
+ stroke: deep,
859
+ "stroke-width": f2(7 * (pose.expression === "angry" ? 1.25 : 1))
860
+ });
861
+ } else if (who.trait === "dot") {
862
+ const s = clamp(-hair, 0, 1.2);
863
+ const th = Math.min(1, s / 0.35) * 61.05 * RAD;
864
+ const travel = Math.max(0, s - 0.35) / 0.65 * 92;
865
+ const p = seat(sh.crown, 12.5, 26.1 * th + travel);
866
+ let cy = p.y;
867
+ if (hair > 0)
868
+ cy -= hair * 18;
869
+ cy += lag * (s > 0.35 ? 0.3 : 1);
870
+ traitT = `translate(${f2(p.x)} ${f2(cy)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
871
+ shape = n("circle", "trait.shape", { cx: 0, cy: 0, r: 12.5, fill: deep });
872
+ } else {
873
+ const s = seat(sh.crown, 4 + Math.max(0, hair) * 9, 0);
874
+ const r = s.a + (hair < 0 ? -hair * 38 : 0) - lag * 0.5;
875
+ traitT = `translate(${f2(s.x)} ${f2(s.y + lag)}) rotate(${f2(r)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
876
+ shape = n("path", "trait.shape", {
877
+ d: "M0 -27L13 -3L-13 -3Z",
878
+ fill: deep,
879
+ stroke: deep,
880
+ "stroke-width": 6,
881
+ "stroke-linejoin": "round"
882
+ });
883
+ }
884
+ const trait = n("g", "trait", { transform: traitT }, [shape]);
885
+ const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
886
+ const av = n("g", "av", {
887
+ transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
888
+ filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
889
+ }, [
890
+ n("g", "plateA", {
891
+ transform: off(-0.55, 0.45),
892
+ filter: riso ? `url(#${id}-pA)` : undefined
893
+ }, [bodyNode, ...cheeks, trait]),
894
+ n("g", "plateB", {
895
+ transform: off(0.6, -0.5),
896
+ filter: riso ? `url(#${id}-pB)` : undefined
897
+ }, [...eyes, ...brows, mouth])
898
+ ]);
899
+ const scene = [];
900
+ if (o.frame !== "none") {
901
+ const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
902
+ scene.push(n("rect", "bg", { ...VBr, fill: bg }));
903
+ const th = clamp(g("thing"), 0, 1);
904
+ const dots = [];
905
+ for (let j = 0;j < 3; j++)
906
+ for (let i = 0;i < 3; i++) {
907
+ const q = j * 3 + i;
908
+ const kk = smooth(q / 12, q / 12 + 0.35, th);
909
+ dots.push(n("circle", `lattice.${q}`, {
910
+ cx: 312 + i * 18,
911
+ cy: 2 + j * 18,
912
+ r: f2(1.2 + 2.2 * kk),
913
+ opacity: f3(kk * 0.55)
914
+ }));
915
+ }
916
+ scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
917
+ }
918
+ scene.push(av);
919
+ if (grainy)
920
+ scene.push(n("rect", "grain", {
921
+ ...VBr,
922
+ filter: `url(#${id}-grain)`,
923
+ opacity: o.frame === "ink" ? 0.3 : 0.16,
924
+ "pointer-events": "none"
925
+ }));
926
+ const root = n("svg", "root", {
927
+ xmlns: "http://www.w3.org/2000/svg",
928
+ viewBox: VB.join(" "),
929
+ width: o.size,
930
+ height: o.size,
931
+ role: o.title === false ? undefined : "img",
932
+ "aria-label": o.title === false ? undefined : o.title,
933
+ "aria-hidden": o.title === false ? "true" : undefined
934
+ }, [
935
+ n("defs", "defs", {}, defs),
936
+ n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
937
+ ]);
938
+ return o.live ? root : prune(root) ?? root;
939
+ }
940
+
941
+ // src/identity.ts
942
+ var STYLE = "peek";
943
+ var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
944
+ function fnv1a(str) {
945
+ let h = 2166136261;
946
+ for (const b of new TextEncoder().encode(str)) {
947
+ h ^= b;
948
+ h = Math.imul(h, 16777619) >>> 0;
949
+ }
950
+ return h >>> 0;
951
+ }
952
+ function mulberry32(a) {
953
+ return () => {
954
+ a = a + 1831565813 | 0;
955
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
956
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
957
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
958
+ };
959
+ }
960
+ var LISTS = {
961
+ face: Object.keys(FACES),
962
+ color: Object.keys(COLORS),
963
+ ...PARTS
964
+ };
965
+ var AXES = Object.keys(LISTS);
966
+ function seed(name, axis, version = LATEST) {
967
+ return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
968
+ }
969
+ function identify(name, { version = LATEST } = {}) {
970
+ const lengths = VERSIONS[version];
971
+ if (!lengths)
972
+ throw new Error(`peek@${version} does not exist`);
973
+ const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
974
+ const rnd = mulberry32(seed(name, "persona", version));
975
+ const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
976
+ const persona = {
977
+ spread: r(-7, 7),
978
+ blink: r(0.75, 1.4),
979
+ add: {
980
+ browY: r(-4, 4),
981
+ browTilt: r(-5, 5),
982
+ browArch: r(-0.2, 0.3),
983
+ rot: r(-3, 3),
984
+ hair: r(-0.2, 0.35),
985
+ gx: r(-0.12, 0.12),
986
+ my: r(-2, 3)
987
+ },
988
+ mul: {
989
+ eyeS: r(0.92, 1.08),
990
+ pupil: r(0.9, 1.12),
991
+ browW: r(0.85, 1.2),
992
+ mw: r(0.85, 1.18)
993
+ }
994
+ };
995
+ const key = tidy(name);
996
+ return {
997
+ key,
998
+ hash: fnv1a(key),
999
+ version,
1000
+ face: pick("face"),
1001
+ color: pick("color"),
1002
+ eyes: pick("eyes"),
1003
+ brows: pick("brows"),
1004
+ mouth: pick("mouth"),
1005
+ cheeks: pick("cheeks"),
1006
+ trait: pick("trait"),
1007
+ persona
1008
+ };
1009
+ }
1010
+
1011
+ // src/svg.ts
1012
+ function settle(name, o, live = false) {
1013
+ const base = identify(name, { version: o.version });
1014
+ const who = {
1015
+ ...base,
1016
+ face: o.face ?? base.face,
1017
+ color: o.color ?? base.color,
1018
+ eyes: o.eyes ?? base.eyes,
1019
+ brows: o.brows ?? base.brows,
1020
+ mouth: o.mouth ?? base.mouth,
1021
+ cheeks: o.cheeks ?? base.cheeks,
1022
+ trait: o.trait ?? base.trait
1023
+ };
1024
+ const pose = restPose(who, o.expression ?? "normal", o.gaze);
1025
+ const look = {
1026
+ size: o.size ?? 64,
1027
+ frame: o.frame ?? "ink",
1028
+ square: o.square ?? true,
1029
+ riso: o.riso ?? false,
1030
+ live
1031
+ };
1032
+ const id = o.id ?? `peek-${fnv1a(JSON.stringify([who, pose, look])).toString(36)}`;
1033
+ return { who, pose, opts: { ...look, id, title: o.title ?? name } };
1034
+ }
1035
+ var ILLEGAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1036
+ var esc = (s) => s.replace(ILLEGAL, "�").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1037
+ function serialize(node) {
1038
+ let a = "";
1039
+ for (const k in node.attrs)
1040
+ a += ` ${k}="${esc(node.attrs[k])}"`;
1041
+ if (!node.children.length)
1042
+ return `<${node.tag}${a}/>`;
1043
+ return `<${node.tag}${a}>${node.children.map(serialize).join("")}</${node.tag}>`;
1044
+ }
1045
+ function toSvg(name, props = {}) {
1046
+ const { who, pose, opts } = settle(name, props);
1047
+ return serialize(draw(who, pose, opts));
1048
+ }
1049
+ export {
1050
+ serialize,
1051
+ settle,
1052
+ toSvg
1053
+ };