@doan-labs/peek 1.1.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/svg.js CHANGED
@@ -114,6 +114,34 @@ var COLORS = {
114
114
  rose: { body: "#EFCAD7", deep: "#DFA4BA" },
115
115
  aqua: { body: "#BDE1E5", deep: "#8FC7D1" }
116
116
  };
117
+ var VARIANTS = ["peek", "haze", "bloc"];
118
+ var HAZE = {
119
+ lavender: { tint: "#C8B1F5", glow: "#F2EDFD" },
120
+ fog: { tint: "#9EC3F2", glow: "#E6F1FF" },
121
+ clay: { tint: "#F4AE93", glow: "#FEECE5" },
122
+ mint: { tint: "#A3DAB5", glow: "#E5F5EA" },
123
+ butter: { tint: "#E1CC7A", glow: "#F4F0E0" },
124
+ rose: { tint: "#F0A9C4", glow: "#FDEAF1" },
125
+ aqua: { tint: "#85D3DC", glow: "#E0F5F7" }
126
+ };
127
+ var WHEEL = [
128
+ "clay",
129
+ "butter",
130
+ "mint",
131
+ "aqua",
132
+ "fog",
133
+ "lavender",
134
+ "rose"
135
+ ];
136
+ var BLOC = {
137
+ lavender: "#B093E5",
138
+ fog: "#7BA8DF",
139
+ clay: "#E38A66",
140
+ mint: "#82CB9B",
141
+ butter: "#E2CA6A",
142
+ rose: "#DF84A8",
143
+ aqua: "#5EC3CD"
144
+ };
117
145
  var PARTS = {
118
146
  eyes: ["oval", "bead", "ring"],
119
147
  brows: ["arch", "bar", "wedge", "dash"],
@@ -339,6 +367,314 @@ var EXPRESSIONS = {
339
367
  })
340
368
  };
341
369
 
370
+ // src/bloc.ts
371
+ var RAD = Math.PI / 180;
372
+ var TRI = [
373
+ [170, 12.8],
374
+ [320, 312.8],
375
+ [20, 312.8]
376
+ ];
377
+ var BODY = {
378
+ diamond: FACES.diamond,
379
+ semicircle: FACES.semicircle,
380
+ circle: FACES.circle,
381
+ triangle: {
382
+ body: { d: `M${TRI.map((p) => p.join(" ")).join("L")}Z` },
383
+ crown: { poly: TRI, r: 0 }
384
+ }
385
+ };
386
+ var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.6;
387
+ var boostFor = (px) => px < 30 ? 1.25 : px < 56 ? 1.12 : 1;
388
+ var turn = (x, y, deg) => {
389
+ const c = Math.cos(deg * RAD);
390
+ const s = Math.sin(deg * RAD);
391
+ return [x * c - y * s, x * s + y * c];
392
+ };
393
+ var poly = (pts) => `M${pts.map(([x, y]) => `${f2(x)} ${f2(y)}`).join("L")}Z`;
394
+ function blocParts({ who, pose, o, defs, g }) {
395
+ const sh = FACES[who.face];
396
+ const { body, crown } = BODY[who.face];
397
+ const id = o.id;
398
+ const P = who.persona;
399
+ const spread = P.spread;
400
+ const st = strokeFor(o.size);
401
+ const up = boostFor(o.size);
402
+ const strong = BLOC[who.color];
403
+ const light = o.frame === "bone" || o.frame === "paper" ? COLORS[who.color].deep : COLORS[who.color].body;
404
+ const lineW = sh.brow.w * st;
405
+ defs.push(n("clipPath", "half", { id: `${id}-r` }, [
406
+ n("rect", "half.shape", { x: 170, y: -30, width: 200, height: 400 })
407
+ ]));
408
+ const block = (key, fill, clip) => ("d" in body) ? n("path", key, { d: body.d, fill, "clip-path": clip }) : n("circle", key, {
409
+ cx: body.circle[0],
410
+ cy: body.circle[1],
411
+ r: body.circle[2],
412
+ fill,
413
+ "clip-path": clip
414
+ });
415
+ const eyeS = g("eyeS");
416
+ const r0 = Math.sqrt(sh.rx * sh.ry) * up;
417
+ const lid0 = g("lid");
418
+ const lidAsym = g("lidAsym");
419
+ const lower = clamp(g("lower"), 0, 1);
420
+ const pupil = g("pupil");
421
+ const gx = clamp(g("gx"), -1.1, 1.1);
422
+ const gy = clamp(g("gy"), -1.1, 1.1);
423
+ const tilt = g("lidTilt");
424
+ const bead = who.eyes === "bead";
425
+ const eyes = sh.eyes.map(([ex, ey], i) => {
426
+ const side = i === 0 ? -1 : 1;
427
+ const kk = `eye${i}`;
428
+ let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1);
429
+ lid = lid + (1 - lid) * pose.blink;
430
+ const cut = clamp((lid - 0.1) / 0.9, 0, 1);
431
+ const r = bead ? r0 * 0.7 * eyeS * (0.4 + 0.6 * pupil) : r0 * eyeS;
432
+ const [tx, ty] = bead ? [ex + side * spread + gx * 6, ey + gy * 6] : [ex + side * spread, ey];
433
+ const a = side * tilt;
434
+ const edge = -r - 1 + cut * (2 * r + 2);
435
+ const floor = r + 1 - lower * r * 2.2;
436
+ const W = r + 2;
437
+ const lidAt = (x) => x * Math.tan(a * RAD) + edge / Math.cos(a * RAD);
438
+ const [t0, t1] = [lidAt(-W), lidAt(W)];
439
+ defs.push(n("clipPath", `${kk}.clip`, { id: `${id}-e${i}` }, [
440
+ n("path", `${kk}.clip.shape`, {
441
+ d: poly([
442
+ [-W, t0],
443
+ [W, t1],
444
+ [W, Math.max(floor, t1)],
445
+ [-W, Math.max(floor, t0)]
446
+ ])
447
+ })
448
+ ]));
449
+ const pr = sh.pr * pupil * up;
450
+ const px = gx * Math.max(0, r - pr) * 0.85;
451
+ const py = gy * Math.max(0, r - pr) * 0.75;
452
+ const rim = Math.min(r * 0.27 * Math.min(st, 1.6), r * 0.4);
453
+ const inner = bead ? [n("circle", `${kk}.bead`, { cx: 0, cy: 0, r: f2(r), fill: INK.ink })] : [
454
+ n("circle", `${kk}.white`, {
455
+ cx: 0,
456
+ cy: 0,
457
+ r: f2(r),
458
+ fill: INK.paper
459
+ }),
460
+ n("circle", `${kk}.pupil`, {
461
+ cx: f2(px),
462
+ cy: f2(py),
463
+ r: f2(pr),
464
+ fill: INK.ink
465
+ })
466
+ ];
467
+ if (who.eyes === "ring")
468
+ inner.push(n("circle", `${kk}.rim`, {
469
+ cx: 0,
470
+ cy: 0,
471
+ r: f2(r - rim / 2),
472
+ fill: "none",
473
+ stroke: INK.ink,
474
+ "stroke-width": f2(rim)
475
+ }));
476
+ const co = smooth(0.86, 1, lid);
477
+ const yy = Math.min(edge, r) - lineW / 2;
478
+ return n("g", kk, { transform: `translate(${f2(tx)} ${f2(ty)})` }, [
479
+ n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
480
+ n("path", `${kk}.closed`, {
481
+ d: `M${f2(-r * 0.95)} ${f2(yy)}H${f2(r * 0.95)}`,
482
+ transform: `rotate(${f2(a * 0.6)})`,
483
+ stroke: INK.ink,
484
+ "stroke-width": f2(lineW * 1.4),
485
+ opacity: co > 0.01 ? f2(co) : 0
486
+ })
487
+ ]);
488
+ });
489
+ const bt = pose.browTilt;
490
+ const basym = g("browAsym");
491
+ const th = lineW * 2 * g("browW");
492
+ const hw = sh.eyes[1][0] - 170 + spread + Math.sqrt(sh.rx * sh.ry) * P.mul.eyeS * up;
493
+ const gap = smooth(0, 8, Math.abs(bt)) * th * 0.9;
494
+ const slant = P.add.browTilt * 0.3 - Math.atan(1.3 * basym / (2 * hw)) / RAD;
495
+ const y0 = sh.brow.y + g("browY") - (eyeS - 1) * sh.ry * 0.9 - 0.35 * basym;
496
+ const halves = (dy, len2 = 1) => [-1, 1].map((s2) => {
497
+ const [ox, oy] = turn(s2 * gap / 2, 0, slant);
498
+ const x0 = 170 + ox;
499
+ const yy = y0 + dy + oy;
500
+ const ang = slant + s2 * bt;
501
+ const [ux, uy] = turn(s2 * (hw * len2 - gap / 2), 0, ang);
502
+ const [nx, ny] = turn(0, th / 2, ang);
503
+ return [
504
+ [x0 - nx, yy - ny],
505
+ [x0 + ux - nx, yy + uy - ny],
506
+ [x0 + ux + nx, yy + uy + ny],
507
+ [x0 + nx, yy + ny]
508
+ ];
509
+ });
510
+ const inside = (dy, len2 = 1) => halves(dy, len2).every((q) => q.every(([x, y]) => depth(crown, x, y) + 2 < 0));
511
+ let lo = 0;
512
+ let hi = 0;
513
+ if (!inside(0)) {
514
+ hi = 60;
515
+ for (let k = 0;k < 12; k++) {
516
+ const mid2 = (lo + hi) / 2;
517
+ if (inside(mid2))
518
+ hi = mid2;
519
+ else
520
+ lo = mid2;
521
+ }
522
+ }
523
+ let len = 1;
524
+ if (!inside(hi)) {
525
+ let short = 0;
526
+ for (let k = 0;k < 12; k++) {
527
+ const mid2 = (short + len) / 2;
528
+ if (inside(hi, mid2))
529
+ short = mid2;
530
+ else
531
+ len = mid2;
532
+ }
533
+ len = short;
534
+ }
535
+ const bars = halves(hi, len).map((q, i) => n("path", `bar${i}`, { d: poly(q), fill: INK.ink }));
536
+ const m = sh.mouth;
537
+ const big = (who.face === "diamond" || who.face === "triangle" ? 1.25 : 1) * up;
538
+ const mw = m.w * Math.max(0.05, g("mw")) * big;
539
+ const mt = g("mt") * m.d * big;
540
+ const mb = g("mb") * m.d * big;
541
+ const mid = (mt + mb) / 2;
542
+ const kb = 4 / 3;
543
+ let d;
544
+ if (who.mouth === "poly") {
545
+ const s2 = Math.max(mw * 0.9, (mb - mt) / 2);
546
+ d = poly([
547
+ [-s2, mid],
548
+ [0, mid - s2],
549
+ [s2, mid],
550
+ [0, mid + s2]
551
+ ]);
552
+ } else if (who.mouth === "box") {
553
+ const h = Math.max(mb - mt, lineW * 1.6) / 2;
554
+ const w = mw * 0.85;
555
+ d = poly([
556
+ [-w, mid - h],
557
+ [w, mid - h],
558
+ [w, mid + h],
559
+ [-w, mid + h]
560
+ ]);
561
+ } else if (who.mouth === "line") {
562
+ const h = lineW * 1.4 / 2;
563
+ const w = mw * 1.5;
564
+ const y = mid * 0.5;
565
+ d = poly([
566
+ [-w, y - h],
567
+ [w, y - h],
568
+ [w, y + h],
569
+ [-w, y + h]
570
+ ]);
571
+ } else {
572
+ 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`;
573
+ }
574
+ const mouth = n("path", "mouth", {
575
+ d,
576
+ transform: `translate(${f2(m.x + g("mx"))} ${f2(m.y + g("my"))}) rotate(${f2(g("mk"))})`,
577
+ fill: INK.ink
578
+ });
579
+ const hair = g("hair");
580
+ const s = seat(crown, Math.max(0, hair) * 8, 110 + Math.max(0, -hair) * 40);
581
+ 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", {
582
+ cx: 0,
583
+ cy: 0,
584
+ r: 17.5,
585
+ fill: "none",
586
+ stroke: strong,
587
+ "stroke-width": 13
588
+ }) : who.trait === "fin" ? n("path", "trait.shape", { d: "M-27 27V-27A54 54 0 0 1 27 27Z" }) : n("rect", "trait.shape", {
589
+ x: -16,
590
+ y: -30,
591
+ width: 32,
592
+ height: 60
593
+ });
594
+ const trait = n("g", "trait", {
595
+ transform: `translate(${f2(s.x)} ${f2(s.y + pose.lag)}) scale(${f2(up)})`,
596
+ fill: strong
597
+ }, [shape]);
598
+ return [
599
+ [block("body", strong), block("body.light", light, `url(#${id}-r)`), trait],
600
+ [...eyes, ...bars, mouth]
601
+ ];
602
+ }
603
+
604
+ // src/identity.ts
605
+ var STYLE = "peek";
606
+ var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
607
+ function fnv1a(str) {
608
+ let h = 2166136261;
609
+ for (const b of new TextEncoder().encode(str)) {
610
+ h ^= b;
611
+ h = Math.imul(h, 16777619) >>> 0;
612
+ }
613
+ return h >>> 0;
614
+ }
615
+ function mulberry32(a) {
616
+ return () => {
617
+ a = a + 1831565813 | 0;
618
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
619
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
620
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
621
+ };
622
+ }
623
+ var LISTS = {
624
+ face: Object.keys(FACES),
625
+ color: Object.keys(COLORS),
626
+ ...PARTS,
627
+ ...ACCESSORIES
628
+ };
629
+ var AXES = Object.keys(LISTS);
630
+ function seed(name, axis, version = LATEST) {
631
+ return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
632
+ }
633
+ function identify(name, { version = LATEST } = {}) {
634
+ const lengths = VERSIONS[version];
635
+ if (!lengths)
636
+ throw new Error(`peek@${version} does not exist`);
637
+ const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
638
+ const rnd = mulberry32(seed(name, "persona", version));
639
+ const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
640
+ const persona = {
641
+ spread: r(-7, 7),
642
+ blink: r(0.75, 1.4),
643
+ add: {
644
+ browY: r(-4, 4),
645
+ browTilt: r(-5, 5),
646
+ browArch: r(-0.2, 0.3),
647
+ rot: r(-3, 3),
648
+ hair: r(-0.2, 0.35),
649
+ gx: r(-0.12, 0.12),
650
+ my: r(-2, 3)
651
+ },
652
+ mul: {
653
+ eyeS: r(0.92, 1.08),
654
+ pupil: r(0.9, 1.12),
655
+ browW: r(0.85, 1.2),
656
+ mw: r(0.85, 1.18)
657
+ }
658
+ };
659
+ const key = tidy(name);
660
+ return {
661
+ key,
662
+ hash: fnv1a(key),
663
+ version,
664
+ face: pick("face"),
665
+ color: pick("color"),
666
+ eyes: pick("eyes"),
667
+ brows: pick("brows"),
668
+ mouth: pick("mouth"),
669
+ cheeks: pick("cheeks"),
670
+ trait: pick("trait"),
671
+ eyewear: pick("eyewear"),
672
+ headwear: pick("headwear"),
673
+ neckwear: pick("neckwear"),
674
+ persona
675
+ };
676
+ }
677
+
342
678
  // src/draw.ts
343
679
  var VB = [-30, -30, 400, 400];
344
680
  var FLOOR = 370;
@@ -350,8 +686,8 @@ var smooth = (a, b, v) => {
350
686
  };
351
687
  var f2 = (v) => String(Math.round(v * 100) / 100);
352
688
  var f3 = (v) => String(Math.round(v * 1000) / 1000);
353
- var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
354
- var RAD = Math.PI / 180;
689
+ var strokeFor2 = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
690
+ var RAD2 = Math.PI / 180;
355
691
  var STEM = "M0 0Q3 -19 0 -36";
356
692
  var LEAF0 = "M0 -33C-24 -30 -38 -52 -25 -53C-12 -54 -2 -43 0 -33Z";
357
693
  var LEAF1 = "M0 -33C5 -54 28 -61 27 -48C26 -36 11 -31 0 -33Z";
@@ -383,18 +719,18 @@ function seat(crown, d, u) {
383
719
  const at = ([x, y], b2) => ({
384
720
  x: x + R * Math.sin(b2),
385
721
  y: y - R * Math.cos(b2),
386
- a: b2 / RAD
722
+ a: b2 / RAD2
387
723
  });
388
724
  const P = crown.poly;
389
725
  if (P.length === 1)
390
726
  return at(P[0], R ? u / R : 0);
391
- const n = P.length;
727
+ const n2 = P.length;
392
728
  const dir = u >= 0 ? 1 : -1;
393
729
  let left = Math.abs(u);
394
730
  let i = 0;
395
731
  let b = 0;
396
- for (let step = 0;step < n; step++) {
397
- const j = (i + dir + n) % n;
732
+ for (let step = 0;step < n2; step++) {
733
+ const j = (i + dir + n2) % n2;
398
734
  const [x0, y0] = P[i];
399
735
  const [x1, y1] = P[j];
400
736
  const ex = x1 - x0;
@@ -414,7 +750,7 @@ function seat(crown, d, u) {
414
750
  return {
415
751
  x: x0 + ex / L * left + R * nx,
416
752
  y: y0 + ey / L * left + R * ny,
417
- a: be / RAD
753
+ a: be / RAD2
418
754
  };
419
755
  left -= L;
420
756
  i = j;
@@ -441,7 +777,7 @@ function depth(crown, x, y) {
441
777
  }
442
778
  return (out <= 0 ? out : near) - crown.r;
443
779
  }
444
- var RING_U = 158 * 35 * RAD;
780
+ var RING_U = 158 * 35 * RAD2;
445
781
  function ringWalk(sh) {
446
782
  const ex = sh.eyes[1][0] + sh.brow.hw + 7;
447
783
  const top = sh.brow.y - 30;
@@ -475,14 +811,11 @@ function prune(node) {
475
811
  }
476
812
  function draw(who, pose, o) {
477
813
  const sh = FACES[who.face];
478
- const { body: fill, deep } = COLORS[who.color];
479
814
  const P = who.persona;
480
815
  const mul = P.mul;
481
816
  const add = P.add;
482
817
  const g = (c) => pose[c] * (mul[c] ?? 1) + (add[c] ?? 0);
483
- const spread = P.spread;
484
818
  const id = o.id;
485
- const st = strokeFor(o.size);
486
819
  const { baseY, R } = frameOf(sh);
487
820
  const defs = [];
488
821
  const VBr = { x: VB[0], y: VB[1], width: VB[2], height: VB[3] };
@@ -576,7 +909,165 @@ function draw(who, pose, o) {
576
909
  const B = sh.bottom;
577
910
  const ty = baseY - alt * R;
578
911
  const sc = (v) => Math.round(v * 1e5) / 1e5;
579
- const bodyNode = "d" in sh.body ? n("path", "body", {
912
+ const [front, back] = (o.variant === "bloc" ? blocParts : peekParts)({
913
+ who,
914
+ pose,
915
+ o,
916
+ defs,
917
+ g
918
+ });
919
+ const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
920
+ const av = n("g", "av", {
921
+ transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
922
+ filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
923
+ }, [
924
+ n("g", "plateA", {
925
+ transform: off(-0.55, 0.45),
926
+ filter: riso ? `url(#${id}-pA)` : undefined
927
+ }, front),
928
+ n("g", "plateB", {
929
+ transform: off(0.6, -0.5),
930
+ filter: riso ? `url(#${id}-pB)` : undefined
931
+ }, back)
932
+ ]);
933
+ const scene = [];
934
+ if (o.frame !== "none") {
935
+ const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
936
+ scene.push(n("rect", "bg", { ...VBr, fill: bg }));
937
+ const th = clamp(g("thing"), 0, 1);
938
+ const dots = [];
939
+ for (let j = 0;j < 3; j++)
940
+ for (let i = 0;i < 3; i++) {
941
+ const q = j * 3 + i;
942
+ const kk = smooth(q / 12, q / 12 + 0.35, th);
943
+ dots.push(n("circle", `lattice.${q}`, {
944
+ cx: 312 + i * 18,
945
+ cy: 2 + j * 18,
946
+ r: f2(1.2 + 2.2 * kk),
947
+ opacity: f3(kk * 0.55)
948
+ }));
949
+ }
950
+ scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
951
+ }
952
+ scene.push(av);
953
+ if (grainy)
954
+ scene.push(n("rect", "grain", {
955
+ ...VBr,
956
+ filter: `url(#${id}-grain)`,
957
+ opacity: o.frame === "ink" ? 0.3 : 0.16,
958
+ "pointer-events": "none"
959
+ }));
960
+ const root = n("svg", "root", {
961
+ xmlns: "http://www.w3.org/2000/svg",
962
+ viewBox: VB.join(" "),
963
+ width: o.size,
964
+ height: o.size,
965
+ role: o.title === false ? undefined : "img",
966
+ "aria-label": o.title === false ? undefined : o.title,
967
+ "aria-hidden": o.title === false ? "true" : undefined
968
+ }, [
969
+ n("defs", "defs", {}, defs),
970
+ n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
971
+ ]);
972
+ return o.live ? root : prune(root) ?? root;
973
+ }
974
+ var HAZE_LEVEL = {
975
+ medium: {
976
+ deep: [0.502, 0.224],
977
+ glow: [0.749, 0.337],
978
+ tint: [0.4, 0.5],
979
+ tintStops: [
980
+ [0, 0.878],
981
+ [0.15, 0.878],
982
+ [0.618, 0.396]
983
+ ]
984
+ },
985
+ light: {
986
+ deep: [0.298, 0.133],
987
+ glow: [0.549, 0.247],
988
+ tint: [0.31, 0.387],
989
+ tintStops: [
990
+ [0, 0.549],
991
+ [0.55, 0.247]
992
+ ]
993
+ }
994
+ };
995
+ function hazePaint(who, o) {
996
+ const sh = FACES[who.face];
997
+ const id = o.id;
998
+ const L = HAZE_LEVEL[o.size < 32 ? "light" : "medium"];
999
+ const xs = sh.crown.poly.map((p) => p[0]);
1000
+ const ys = sh.crown.poly.map((p) => p[1]);
1001
+ const x0 = Math.min(...xs) - sh.crown.r;
1002
+ const y0 = Math.min(...ys) - sh.crown.r;
1003
+ const W = Math.max(...xs) + sh.crown.r - x0;
1004
+ const H = sh.bottom - y0;
1005
+ const rnd = mulberry32(fnv1a(`haze:${who.key ?? ""}`));
1006
+ const warm = rnd() < 0.5;
1007
+ const left = rnd() < 0.5;
1008
+ const t = rnd();
1009
+ const tintX = 0.04 + 0.174 * t;
1010
+ const tintY = 0.649 + 0.202 * t;
1011
+ const glowX = 0.38 - 0.12 * rnd();
1012
+ const glowY = 0.12 + 0.099 * rnd();
1013
+ const deepX = 0.251 - 0.151 * rnd();
1014
+ const tintA = 90 - 180 * rnd();
1015
+ const at = (inset, onLeft) => x0 + W * (onLeft ? inset : 1 - inset);
1016
+ const i = WHEEL.indexOf(who.color);
1017
+ const near = WHEEL[(i + (warm ? WHEEL.length - 1 : 1)) % WHEEL.length];
1018
+ const { body, deep } = COLORS[who.color];
1019
+ const blob = (k, color, [cx, cy, a, rx, ry], stops) => n("radialGradient", `haze.${k}`, {
1020
+ id: `${id}-h${k[0]}`,
1021
+ gradientUnits: "userSpaceOnUse",
1022
+ cx: 0,
1023
+ cy: 0,
1024
+ r: 1,
1025
+ gradientTransform: `translate(${f2(cx)} ${f2(cy)}) rotate(${f2(a)}) scale(${f2(rx)} ${f2(ry)})`
1026
+ }, [...stops, [1, 0]].map(([at2, op], j) => n("stop", `haze.${k}.${j}`, {
1027
+ offset: at2,
1028
+ "stop-color": color,
1029
+ "stop-opacity": op
1030
+ })));
1031
+ const fade = ([a, b]) => [
1032
+ [0, a],
1033
+ [0.55, b]
1034
+ ];
1035
+ const blobs = [
1036
+ blob("deep", deep, [at(deepX, !left), y0 + H * 0.95, 0, W * 0.5, W * 0.35], fade(L.deep)),
1037
+ blob("tint", HAZE[near].tint, [at(tintX, left), y0 + H * tintY, tintA, W * L.tint[0], W * L.tint[1]], L.tintStops),
1038
+ blob("glow", HAZE[who.color].glow, [at(glowX, !left), y0 + H * glowY, 0, W * 0.475, W * 0.356], fade(L.glow))
1039
+ ];
1040
+ const shape = (key, paint) => ("d" in sh.body) ? n("path", key, {
1041
+ d: sh.body.d,
1042
+ fill: paint,
1043
+ stroke: sh.body.stroke ? paint : undefined,
1044
+ "stroke-width": sh.body.stroke,
1045
+ "stroke-linejoin": sh.body.stroke ? "round" : undefined
1046
+ }) : n("circle", key, {
1047
+ cx: sh.body.circle[0],
1048
+ cy: sh.body.circle[1],
1049
+ r: sh.body.circle[2],
1050
+ fill: paint
1051
+ });
1052
+ return [
1053
+ ...blobs,
1054
+ n("g", "haze", { id: `${id}-hz` }, [
1055
+ shape("haze.body", body),
1056
+ ...["deep", "tint", "glow"].map((k) => shape(`haze.${k}.fill`, `url(#${id}-h${k[0]})`))
1057
+ ])
1058
+ ];
1059
+ }
1060
+ function peekParts({ who, pose, o, defs, g }) {
1061
+ const sh = FACES[who.face];
1062
+ const { body: fill, deep } = COLORS[who.color];
1063
+ const P = who.persona;
1064
+ const spread = P.spread;
1065
+ const id = o.id;
1066
+ const st = strokeFor2(o.size);
1067
+ const haze = o.variant === "haze";
1068
+ if (haze)
1069
+ defs.push(...hazePaint(who, o));
1070
+ const bodyNode = haze ? n("use", "body", { href: `#${id}-hz` }) : ("d" in sh.body) ? n("path", "body", {
580
1071
  d: sh.body.d,
581
1072
  fill,
582
1073
  stroke: sh.body.stroke ? fill : undefined,
@@ -664,25 +1155,36 @@ function draw(who, pose, o) {
664
1155
  const lrx = rx * 1.55;
665
1156
  const lry = ry * 1.15;
666
1157
  const top = ry + 1.6 - lower * ry * 1.5;
1158
+ const [etx, ety] = bead ? [ex + side * spread + gx * 6, ey + gy * 6] : [ex + side * spread, ey];
1159
+ const lids = [
1160
+ n("rect", `${kk}.lid`, {
1161
+ x: f2(-rx * 2.2),
1162
+ y: f2(-ry * 3),
1163
+ width: f2(rx * 4.4),
1164
+ height: f2(Math.max(0, edge + ry * 3)),
1165
+ transform: `rotate(${f2(a)})`,
1166
+ fill
1167
+ }),
1168
+ n("ellipse", `${kk}.lower`, {
1169
+ cx: 0,
1170
+ cy: f2(top + lry),
1171
+ rx: f2(lrx),
1172
+ ry: f2(lry),
1173
+ fill
1174
+ })
1175
+ ];
1176
+ if (haze)
1177
+ defs.push(n("clipPath", `${kk}.lidshape`, { id: `${id}-s${i}` }, lids));
667
1178
  const parts = [
668
1179
  n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
669
- n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, [
670
- n("rect", `${kk}.lid`, {
671
- x: f2(-rx * 2.2),
672
- y: f2(-ry * 3),
673
- width: f2(rx * 4.4),
674
- height: f2(Math.max(0, edge + ry * 3)),
675
- transform: `rotate(${f2(a)})`,
676
- fill
677
- }),
678
- n("ellipse", `${kk}.lower`, {
679
- cx: 0,
680
- cy: f2(top + lry),
681
- rx: f2(lrx),
682
- ry: f2(lry),
683
- fill
684
- })
685
- ])
1180
+ n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, haze ? [
1181
+ n("g", `${kk}.lidpaint`, { "clip-path": `url(#${id}-s${i})` }, [
1182
+ n("use", `${kk}.lidbody`, {
1183
+ href: `#${id}-hz`,
1184
+ transform: `translate(${f2(-etx)} ${f2(-ety)})`
1185
+ })
1186
+ ])
1187
+ ] : lids)
686
1188
  ];
687
1189
  if (who.eyes === "ring")
688
1190
  parts.push(n("g", `${kk}.edges`, {
@@ -716,9 +1218,7 @@ function draw(who, pose, o) {
716
1218
  "stroke-width": f2(sw * 0.85),
717
1219
  opacity: co > 0.01 ? f2(co) : 0
718
1220
  }));
719
- return n("g", kk, {
720
- transform: bead ? `translate(${f2(ex + side * spread + gx * 6)} ${f2(ey + gy * 6)})` : `translate(${f2(ex + side * spread)} ${ey})`
721
- }, parts);
1221
+ return n("g", kk, { transform: `translate(${f2(etx)} ${f2(ety)})` }, parts);
722
1222
  });
723
1223
  const browY = g("browY") - (eyeS - 1) * sh.ry * 0.9;
724
1224
  const btilt = g("browTilt");
@@ -738,8 +1238,8 @@ function draw(who, pose, o) {
738
1238
  const half = (t) => wedge ? hl + (hr - hl) * t : bw * k2 / 2;
739
1239
  const bx = ex + side * spread;
740
1240
  const by = sh.brow.y + browY + (i === 1 ? -basym : basym * 0.3);
741
- const cos = Math.cos(side * btilt * RAD);
742
- const sin = Math.sin(side * btilt * RAD);
1241
+ const cos = Math.cos(side * btilt * RAD2);
1242
+ const sin = Math.sin(side * btilt * RAD2);
743
1243
  const inside = (dy) => {
744
1244
  for (let t = 0;t <= 1; t += 0.25) {
745
1245
  const lx = w2 * (2 * t - 1);
@@ -755,7 +1255,7 @@ function draw(who, pose, o) {
755
1255
  let hi = 0;
756
1256
  if (!inside(0)) {
757
1257
  hi = 40;
758
- for (let k3 = 0;k3 < 12; k3++) {
1258
+ for (let k = 0;k < 12; k++) {
759
1259
  const mid2 = (lo + hi) / 2;
760
1260
  if (inside(mid2))
761
1261
  hi = mid2;
@@ -858,7 +1358,7 @@ function draw(who, pose, o) {
858
1358
  fill: deep
859
1359
  });
860
1360
  } else if (who.trait === "fin") {
861
- const s = seat(sh.crown, -6, -164 * 22 * RAD);
1361
+ const s = seat(sh.crown, -6, -164 * 22 * RAD2);
862
1362
  const r = s.a + (hair >= 0 ? hair * 20 : hair * 55) - lag * 0.8;
863
1363
  traitT = `translate(${f2(s.x)} ${f2(s.y + lag * 0.25)}) rotate(${f2(r)})`;
864
1364
  shape = n("path", "trait.shape", {
@@ -868,7 +1368,7 @@ function draw(who, pose, o) {
868
1368
  } else if (who.trait === "ring") {
869
1369
  const b = 35 - hair * 14 - (hair < 0 ? hair * -21 : 0) + lag * 0.9;
870
1370
  const w2 = ringWalk(sh);
871
- const s = seat(sh.crown, 8, Math.max(w2.min, w2.rest + 158 * (b - 35) * RAD));
1371
+ const s = seat(sh.crown, 8, Math.max(w2.min, w2.rest + 158 * (b - 35) * RAD2));
872
1372
  traitT = `translate(${f2(s.x)} ${f2(s.y)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
873
1373
  shape = n("circle", "trait.shape", {
874
1374
  cx: 0,
@@ -880,7 +1380,7 @@ function draw(who, pose, o) {
880
1380
  });
881
1381
  } else if (who.trait === "dot") {
882
1382
  const s = clamp(-hair, 0, 1.2);
883
- const th = Math.min(1, s / 0.35) * 61.05 * RAD;
1383
+ const th = Math.min(1, s / 0.35) * 61.05 * RAD2;
884
1384
  const travel = Math.max(0, s - 0.35) / 0.65 * 92;
885
1385
  const p = seat(sh.crown, 12.5, 26.1 * th + travel);
886
1386
  let cy = p.y;
@@ -1051,7 +1551,7 @@ function draw(who, pose, o) {
1051
1551
  }, parts));
1052
1552
  }
1053
1553
  if (who.neckwear === "tie") {
1054
- const skew = Math.abs(Math.sin(g("mk") * RAD)) * mw;
1554
+ const skew = Math.abs(Math.sin(g("mk") * RAD2)) * mw;
1055
1555
  const bottom = Math.max(0, mt * kb, mb * kb) + skew + m.sw * st;
1056
1556
  wardrobe.push(n("g", "neckwear", {
1057
1557
  transform: `translate(${m.x} ${f2(m.y + g("my") + bottom + 8)})`,
@@ -1065,134 +1565,10 @@ function draw(who, pose, o) {
1065
1565
  })
1066
1566
  ]));
1067
1567
  }
1068
- const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
1069
- const av = n("g", "av", {
1070
- transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
1071
- filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
1072
- }, [
1073
- n("g", "plateA", {
1074
- transform: off(-0.55, 0.45),
1075
- filter: riso ? `url(#${id}-pA)` : undefined
1076
- }, [bodyNode, ...cheeks, trait]),
1077
- n("g", "plateB", {
1078
- transform: off(0.6, -0.5),
1079
- filter: riso ? `url(#${id}-pB)` : undefined
1080
- }, [...eyes, ...brows, mouth, ...wardrobe])
1081
- ]);
1082
- const scene = [];
1083
- if (o.frame !== "none") {
1084
- const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
1085
- scene.push(n("rect", "bg", { ...VBr, fill: bg }));
1086
- const th = clamp(g("thing"), 0, 1);
1087
- const dots = [];
1088
- for (let j = 0;j < 3; j++)
1089
- for (let i = 0;i < 3; i++) {
1090
- const q = j * 3 + i;
1091
- const kk = smooth(q / 12, q / 12 + 0.35, th);
1092
- dots.push(n("circle", `lattice.${q}`, {
1093
- cx: 312 + i * 18,
1094
- cy: 2 + j * 18,
1095
- r: f2(1.2 + 2.2 * kk),
1096
- opacity: f3(kk * 0.55)
1097
- }));
1098
- }
1099
- scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
1100
- }
1101
- scene.push(av);
1102
- if (grainy)
1103
- scene.push(n("rect", "grain", {
1104
- ...VBr,
1105
- filter: `url(#${id}-grain)`,
1106
- opacity: o.frame === "ink" ? 0.3 : 0.16,
1107
- "pointer-events": "none"
1108
- }));
1109
- const root = n("svg", "root", {
1110
- xmlns: "http://www.w3.org/2000/svg",
1111
- viewBox: VB.join(" "),
1112
- width: o.size,
1113
- height: o.size,
1114
- role: o.title === false ? undefined : "img",
1115
- "aria-label": o.title === false ? undefined : o.title,
1116
- "aria-hidden": o.title === false ? "true" : undefined
1117
- }, [
1118
- n("defs", "defs", {}, defs),
1119
- n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
1120
- ]);
1121
- return o.live ? root : prune(root) ?? root;
1122
- }
1123
-
1124
- // src/identity.ts
1125
- var STYLE = "peek";
1126
- var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
1127
- function fnv1a(str) {
1128
- let h = 2166136261;
1129
- for (const b of new TextEncoder().encode(str)) {
1130
- h ^= b;
1131
- h = Math.imul(h, 16777619) >>> 0;
1132
- }
1133
- return h >>> 0;
1134
- }
1135
- function mulberry32(a) {
1136
- return () => {
1137
- a = a + 1831565813 | 0;
1138
- let t = Math.imul(a ^ a >>> 15, 1 | a);
1139
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
1140
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
1141
- };
1142
- }
1143
- var LISTS = {
1144
- face: Object.keys(FACES),
1145
- color: Object.keys(COLORS),
1146
- ...PARTS,
1147
- ...ACCESSORIES
1148
- };
1149
- var AXES = Object.keys(LISTS);
1150
- function seed(name, axis, version = LATEST) {
1151
- return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
1152
- }
1153
- function identify(name, { version = LATEST } = {}) {
1154
- const lengths = VERSIONS[version];
1155
- if (!lengths)
1156
- throw new Error(`peek@${version} does not exist`);
1157
- const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
1158
- const rnd = mulberry32(seed(name, "persona", version));
1159
- const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
1160
- const persona = {
1161
- spread: r(-7, 7),
1162
- blink: r(0.75, 1.4),
1163
- add: {
1164
- browY: r(-4, 4),
1165
- browTilt: r(-5, 5),
1166
- browArch: r(-0.2, 0.3),
1167
- rot: r(-3, 3),
1168
- hair: r(-0.2, 0.35),
1169
- gx: r(-0.12, 0.12),
1170
- my: r(-2, 3)
1171
- },
1172
- mul: {
1173
- eyeS: r(0.92, 1.08),
1174
- pupil: r(0.9, 1.12),
1175
- browW: r(0.85, 1.2),
1176
- mw: r(0.85, 1.18)
1177
- }
1178
- };
1179
- const key = tidy(name);
1180
- return {
1181
- key,
1182
- hash: fnv1a(key),
1183
- version,
1184
- face: pick("face"),
1185
- color: pick("color"),
1186
- eyes: pick("eyes"),
1187
- brows: pick("brows"),
1188
- mouth: pick("mouth"),
1189
- cheeks: pick("cheeks"),
1190
- trait: pick("trait"),
1191
- eyewear: pick("eyewear"),
1192
- headwear: pick("headwear"),
1193
- neckwear: pick("neckwear"),
1194
- persona
1195
- };
1568
+ return [
1569
+ [bodyNode, ...cheeks, trait],
1570
+ [...eyes, ...brows, mouth, ...wardrobe]
1571
+ ];
1196
1572
  }
1197
1573
 
1198
1574
  // src/svg.ts
@@ -1213,12 +1589,14 @@ function settle(name, o, live = false) {
1213
1589
  neckwear: wear(o, base, "neckwear")
1214
1590
  };
1215
1591
  const pose = restPose(who, o.expression ?? "normal", o.gaze);
1592
+ const variant = VARIANTS.includes(o.variant) ? o.variant : "peek";
1216
1593
  const look = {
1217
1594
  size: o.size ?? 64,
1218
1595
  frame: o.frame ?? "ink",
1219
1596
  square: o.square ?? true,
1220
1597
  riso: o.riso ?? false,
1221
- live
1598
+ live,
1599
+ ...variant !== "peek" && { variant }
1222
1600
  };
1223
1601
  const { eyewear, headwear, neckwear, ...legacy } = who;
1224
1602
  const wardrobe = eyewear !== "none" || headwear !== "none" || neckwear !== "none";