@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/index.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"],
@@ -347,6 +375,314 @@ var EXPRESSIONS = {
347
375
  })
348
376
  };
349
377
 
378
+ // src/bloc.ts
379
+ var RAD = Math.PI / 180;
380
+ var TRI = [
381
+ [170, 12.8],
382
+ [320, 312.8],
383
+ [20, 312.8]
384
+ ];
385
+ var BODY = {
386
+ diamond: FACES.diamond,
387
+ semicircle: FACES.semicircle,
388
+ circle: FACES.circle,
389
+ triangle: {
390
+ body: { d: `M${TRI.map((p) => p.join(" ")).join("L")}Z` },
391
+ crown: { poly: TRI, r: 0 }
392
+ }
393
+ };
394
+ var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.6;
395
+ var boostFor = (px) => px < 30 ? 1.25 : px < 56 ? 1.12 : 1;
396
+ var turn = (x, y, deg) => {
397
+ const c = Math.cos(deg * RAD);
398
+ const s = Math.sin(deg * RAD);
399
+ return [x * c - y * s, x * s + y * c];
400
+ };
401
+ var poly = (pts) => `M${pts.map(([x, y]) => `${f2(x)} ${f2(y)}`).join("L")}Z`;
402
+ function blocParts({ who, pose, o, defs, g }) {
403
+ const sh = FACES[who.face];
404
+ const { body, crown } = BODY[who.face];
405
+ const id = o.id;
406
+ const P = who.persona;
407
+ const spread = P.spread;
408
+ const st = strokeFor(o.size);
409
+ const up = boostFor(o.size);
410
+ const strong = BLOC[who.color];
411
+ const light = o.frame === "bone" || o.frame === "paper" ? COLORS[who.color].deep : COLORS[who.color].body;
412
+ const lineW = sh.brow.w * st;
413
+ defs.push(n("clipPath", "half", { id: `${id}-r` }, [
414
+ n("rect", "half.shape", { x: 170, y: -30, width: 200, height: 400 })
415
+ ]));
416
+ const block = (key, fill, clip) => ("d" in body) ? n("path", key, { d: body.d, fill, "clip-path": clip }) : n("circle", key, {
417
+ cx: body.circle[0],
418
+ cy: body.circle[1],
419
+ r: body.circle[2],
420
+ fill,
421
+ "clip-path": clip
422
+ });
423
+ const eyeS = g("eyeS");
424
+ const r0 = Math.sqrt(sh.rx * sh.ry) * up;
425
+ const lid0 = g("lid");
426
+ const lidAsym = g("lidAsym");
427
+ const lower = clamp(g("lower"), 0, 1);
428
+ const pupil = g("pupil");
429
+ const gx = clamp(g("gx"), -1.1, 1.1);
430
+ const gy = clamp(g("gy"), -1.1, 1.1);
431
+ const tilt = g("lidTilt");
432
+ const bead = who.eyes === "bead";
433
+ const eyes = sh.eyes.map(([ex, ey], i) => {
434
+ const side = i === 0 ? -1 : 1;
435
+ const kk = `eye${i}`;
436
+ let lid = clamp(lid0 + (i === 0 ? lidAsym : -lidAsym * 0.3), 0, 1);
437
+ lid = lid + (1 - lid) * pose.blink;
438
+ const cut = clamp((lid - 0.1) / 0.9, 0, 1);
439
+ const r = bead ? r0 * 0.7 * eyeS * (0.4 + 0.6 * pupil) : r0 * eyeS;
440
+ const [tx, ty] = bead ? [ex + side * spread + gx * 6, ey + gy * 6] : [ex + side * spread, ey];
441
+ const a = side * tilt;
442
+ const edge = -r - 1 + cut * (2 * r + 2);
443
+ const floor = r + 1 - lower * r * 2.2;
444
+ const W = r + 2;
445
+ const lidAt = (x) => x * Math.tan(a * RAD) + edge / Math.cos(a * RAD);
446
+ const [t0, t1] = [lidAt(-W), lidAt(W)];
447
+ defs.push(n("clipPath", `${kk}.clip`, { id: `${id}-e${i}` }, [
448
+ n("path", `${kk}.clip.shape`, {
449
+ d: poly([
450
+ [-W, t0],
451
+ [W, t1],
452
+ [W, Math.max(floor, t1)],
453
+ [-W, Math.max(floor, t0)]
454
+ ])
455
+ })
456
+ ]));
457
+ const pr = sh.pr * pupil * up;
458
+ const px = gx * Math.max(0, r - pr) * 0.85;
459
+ const py = gy * Math.max(0, r - pr) * 0.75;
460
+ const rim = Math.min(r * 0.27 * Math.min(st, 1.6), r * 0.4);
461
+ const inner = bead ? [n("circle", `${kk}.bead`, { cx: 0, cy: 0, r: f2(r), fill: INK.ink })] : [
462
+ n("circle", `${kk}.white`, {
463
+ cx: 0,
464
+ cy: 0,
465
+ r: f2(r),
466
+ fill: INK.paper
467
+ }),
468
+ n("circle", `${kk}.pupil`, {
469
+ cx: f2(px),
470
+ cy: f2(py),
471
+ r: f2(pr),
472
+ fill: INK.ink
473
+ })
474
+ ];
475
+ if (who.eyes === "ring")
476
+ inner.push(n("circle", `${kk}.rim`, {
477
+ cx: 0,
478
+ cy: 0,
479
+ r: f2(r - rim / 2),
480
+ fill: "none",
481
+ stroke: INK.ink,
482
+ "stroke-width": f2(rim)
483
+ }));
484
+ const co = smooth(0.86, 1, lid);
485
+ const yy = Math.min(edge, r) - lineW / 2;
486
+ return n("g", kk, { transform: `translate(${f2(tx)} ${f2(ty)})` }, [
487
+ n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
488
+ n("path", `${kk}.closed`, {
489
+ d: `M${f2(-r * 0.95)} ${f2(yy)}H${f2(r * 0.95)}`,
490
+ transform: `rotate(${f2(a * 0.6)})`,
491
+ stroke: INK.ink,
492
+ "stroke-width": f2(lineW * 1.4),
493
+ opacity: co > 0.01 ? f2(co) : 0
494
+ })
495
+ ]);
496
+ });
497
+ const bt = pose.browTilt;
498
+ const basym = g("browAsym");
499
+ const th = lineW * 2 * g("browW");
500
+ const hw = sh.eyes[1][0] - 170 + spread + Math.sqrt(sh.rx * sh.ry) * P.mul.eyeS * up;
501
+ const gap = smooth(0, 8, Math.abs(bt)) * th * 0.9;
502
+ const slant = P.add.browTilt * 0.3 - Math.atan(1.3 * basym / (2 * hw)) / RAD;
503
+ const y0 = sh.brow.y + g("browY") - (eyeS - 1) * sh.ry * 0.9 - 0.35 * basym;
504
+ const halves = (dy, len2 = 1) => [-1, 1].map((s2) => {
505
+ const [ox, oy] = turn(s2 * gap / 2, 0, slant);
506
+ const x0 = 170 + ox;
507
+ const yy = y0 + dy + oy;
508
+ const ang = slant + s2 * bt;
509
+ const [ux, uy] = turn(s2 * (hw * len2 - gap / 2), 0, ang);
510
+ const [nx, ny] = turn(0, th / 2, ang);
511
+ return [
512
+ [x0 - nx, yy - ny],
513
+ [x0 + ux - nx, yy + uy - ny],
514
+ [x0 + ux + nx, yy + uy + ny],
515
+ [x0 + nx, yy + ny]
516
+ ];
517
+ });
518
+ const inside = (dy, len2 = 1) => halves(dy, len2).every((q) => q.every(([x, y]) => depth(crown, x, y) + 2 < 0));
519
+ let lo = 0;
520
+ let hi = 0;
521
+ if (!inside(0)) {
522
+ hi = 60;
523
+ for (let k = 0;k < 12; k++) {
524
+ const mid2 = (lo + hi) / 2;
525
+ if (inside(mid2))
526
+ hi = mid2;
527
+ else
528
+ lo = mid2;
529
+ }
530
+ }
531
+ let len = 1;
532
+ if (!inside(hi)) {
533
+ let short = 0;
534
+ for (let k = 0;k < 12; k++) {
535
+ const mid2 = (short + len) / 2;
536
+ if (inside(hi, mid2))
537
+ short = mid2;
538
+ else
539
+ len = mid2;
540
+ }
541
+ len = short;
542
+ }
543
+ const bars = halves(hi, len).map((q, i) => n("path", `bar${i}`, { d: poly(q), fill: INK.ink }));
544
+ const m = sh.mouth;
545
+ const big = (who.face === "diamond" || who.face === "triangle" ? 1.25 : 1) * up;
546
+ const mw = m.w * Math.max(0.05, g("mw")) * big;
547
+ const mt = g("mt") * m.d * big;
548
+ const mb = g("mb") * m.d * big;
549
+ const mid = (mt + mb) / 2;
550
+ const kb = 4 / 3;
551
+ let d;
552
+ if (who.mouth === "poly") {
553
+ const s2 = Math.max(mw * 0.9, (mb - mt) / 2);
554
+ d = poly([
555
+ [-s2, mid],
556
+ [0, mid - s2],
557
+ [s2, mid],
558
+ [0, mid + s2]
559
+ ]);
560
+ } else if (who.mouth === "box") {
561
+ const h = Math.max(mb - mt, lineW * 1.6) / 2;
562
+ const w = mw * 0.85;
563
+ d = poly([
564
+ [-w, mid - h],
565
+ [w, mid - h],
566
+ [w, mid + h],
567
+ [-w, mid + h]
568
+ ]);
569
+ } else if (who.mouth === "line") {
570
+ const h = lineW * 1.4 / 2;
571
+ const w = mw * 1.5;
572
+ const y = mid * 0.5;
573
+ d = poly([
574
+ [-w, y - h],
575
+ [w, y - h],
576
+ [w, y + h],
577
+ [-w, y + h]
578
+ ]);
579
+ } else {
580
+ 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`;
581
+ }
582
+ const mouth = n("path", "mouth", {
583
+ d,
584
+ transform: `translate(${f2(m.x + g("mx"))} ${f2(m.y + g("my"))}) rotate(${f2(g("mk"))})`,
585
+ fill: INK.ink
586
+ });
587
+ const hair = g("hair");
588
+ const s = seat(crown, Math.max(0, hair) * 8, 110 + Math.max(0, -hair) * 40);
589
+ 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", {
590
+ cx: 0,
591
+ cy: 0,
592
+ r: 17.5,
593
+ fill: "none",
594
+ stroke: strong,
595
+ "stroke-width": 13
596
+ }) : who.trait === "fin" ? n("path", "trait.shape", { d: "M-27 27V-27A54 54 0 0 1 27 27Z" }) : n("rect", "trait.shape", {
597
+ x: -16,
598
+ y: -30,
599
+ width: 32,
600
+ height: 60
601
+ });
602
+ const trait = n("g", "trait", {
603
+ transform: `translate(${f2(s.x)} ${f2(s.y + pose.lag)}) scale(${f2(up)})`,
604
+ fill: strong
605
+ }, [shape]);
606
+ return [
607
+ [block("body", strong), block("body.light", light, `url(#${id}-r)`), trait],
608
+ [...eyes, ...bars, mouth]
609
+ ];
610
+ }
611
+
612
+ // src/identity.ts
613
+ var STYLE = "peek";
614
+ var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
615
+ function fnv1a(str) {
616
+ let h = 2166136261;
617
+ for (const b of new TextEncoder().encode(str)) {
618
+ h ^= b;
619
+ h = Math.imul(h, 16777619) >>> 0;
620
+ }
621
+ return h >>> 0;
622
+ }
623
+ function mulberry32(a) {
624
+ return () => {
625
+ a = a + 1831565813 | 0;
626
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
627
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
628
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
629
+ };
630
+ }
631
+ var LISTS = {
632
+ face: Object.keys(FACES),
633
+ color: Object.keys(COLORS),
634
+ ...PARTS,
635
+ ...ACCESSORIES
636
+ };
637
+ var AXES = Object.keys(LISTS);
638
+ function seed(name, axis, version = LATEST) {
639
+ return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
640
+ }
641
+ function identify(name, { version = LATEST } = {}) {
642
+ const lengths = VERSIONS[version];
643
+ if (!lengths)
644
+ throw new Error(`peek@${version} does not exist`);
645
+ const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
646
+ const rnd = mulberry32(seed(name, "persona", version));
647
+ const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
648
+ const persona = {
649
+ spread: r(-7, 7),
650
+ blink: r(0.75, 1.4),
651
+ add: {
652
+ browY: r(-4, 4),
653
+ browTilt: r(-5, 5),
654
+ browArch: r(-0.2, 0.3),
655
+ rot: r(-3, 3),
656
+ hair: r(-0.2, 0.35),
657
+ gx: r(-0.12, 0.12),
658
+ my: r(-2, 3)
659
+ },
660
+ mul: {
661
+ eyeS: r(0.92, 1.08),
662
+ pupil: r(0.9, 1.12),
663
+ browW: r(0.85, 1.2),
664
+ mw: r(0.85, 1.18)
665
+ }
666
+ };
667
+ const key = tidy(name);
668
+ return {
669
+ key,
670
+ hash: fnv1a(key),
671
+ version,
672
+ face: pick("face"),
673
+ color: pick("color"),
674
+ eyes: pick("eyes"),
675
+ brows: pick("brows"),
676
+ mouth: pick("mouth"),
677
+ cheeks: pick("cheeks"),
678
+ trait: pick("trait"),
679
+ eyewear: pick("eyewear"),
680
+ headwear: pick("headwear"),
681
+ neckwear: pick("neckwear"),
682
+ persona
683
+ };
684
+ }
685
+
350
686
  // src/draw.ts
351
687
  var VB = [-30, -30, 400, 400];
352
688
  var FLOOR = 370;
@@ -358,8 +694,8 @@ var smooth = (a, b, v) => {
358
694
  };
359
695
  var f2 = (v) => String(Math.round(v * 100) / 100);
360
696
  var f3 = (v) => String(Math.round(v * 1000) / 1000);
361
- var strokeFor = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
362
- var RAD = Math.PI / 180;
697
+ var strokeFor2 = (px) => px >= 96 ? 1 : px >= 56 ? 1.25 : px >= 40 ? 1.6 : px >= 30 ? 2 : 2.4;
698
+ var RAD2 = Math.PI / 180;
363
699
  var STEM = "M0 0Q3 -19 0 -36";
364
700
  var LEAF0 = "M0 -33C-24 -30 -38 -52 -25 -53C-12 -54 -2 -43 0 -33Z";
365
701
  var LEAF1 = "M0 -33C5 -54 28 -61 27 -48C26 -36 11 -31 0 -33Z";
@@ -391,18 +727,18 @@ function seat(crown, d, u) {
391
727
  const at = ([x, y], b2) => ({
392
728
  x: x + R * Math.sin(b2),
393
729
  y: y - R * Math.cos(b2),
394
- a: b2 / RAD
730
+ a: b2 / RAD2
395
731
  });
396
732
  const P = crown.poly;
397
733
  if (P.length === 1)
398
734
  return at(P[0], R ? u / R : 0);
399
- const n = P.length;
735
+ const n2 = P.length;
400
736
  const dir = u >= 0 ? 1 : -1;
401
737
  let left = Math.abs(u);
402
738
  let i = 0;
403
739
  let b = 0;
404
- for (let step = 0;step < n; step++) {
405
- const j = (i + dir + n) % n;
740
+ for (let step = 0;step < n2; step++) {
741
+ const j = (i + dir + n2) % n2;
406
742
  const [x0, y0] = P[i];
407
743
  const [x1, y1] = P[j];
408
744
  const ex = x1 - x0;
@@ -422,7 +758,7 @@ function seat(crown, d, u) {
422
758
  return {
423
759
  x: x0 + ex / L * left + R * nx,
424
760
  y: y0 + ey / L * left + R * ny,
425
- a: be / RAD
761
+ a: be / RAD2
426
762
  };
427
763
  left -= L;
428
764
  i = j;
@@ -449,7 +785,7 @@ function depth(crown, x, y) {
449
785
  }
450
786
  return (out <= 0 ? out : near) - crown.r;
451
787
  }
452
- var RING_U = 158 * 35 * RAD;
788
+ var RING_U = 158 * 35 * RAD2;
453
789
  function ringWalk(sh) {
454
790
  const ex = sh.eyes[1][0] + sh.brow.hw + 7;
455
791
  const top = sh.brow.y - 30;
@@ -483,14 +819,11 @@ function prune(node) {
483
819
  }
484
820
  function draw(who, pose, o) {
485
821
  const sh = FACES[who.face];
486
- const { body: fill, deep } = COLORS[who.color];
487
822
  const P = who.persona;
488
823
  const mul = P.mul;
489
824
  const add = P.add;
490
825
  const g = (c) => pose[c] * (mul[c] ?? 1) + (add[c] ?? 0);
491
- const spread = P.spread;
492
826
  const id = o.id;
493
- const st = strokeFor(o.size);
494
827
  const { baseY, R } = frameOf(sh);
495
828
  const defs = [];
496
829
  const VBr = { x: VB[0], y: VB[1], width: VB[2], height: VB[3] };
@@ -584,7 +917,165 @@ function draw(who, pose, o) {
584
917
  const B = sh.bottom;
585
918
  const ty = baseY - alt * R;
586
919
  const sc = (v) => Math.round(v * 1e5) / 1e5;
587
- const bodyNode = "d" in sh.body ? n("path", "body", {
920
+ const [front, back] = (o.variant === "bloc" ? blocParts : peekParts)({
921
+ who,
922
+ pose,
923
+ o,
924
+ defs,
925
+ g
926
+ });
927
+ const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
928
+ const av = n("g", "av", {
929
+ transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
930
+ filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
931
+ }, [
932
+ n("g", "plateA", {
933
+ transform: off(-0.55, 0.45),
934
+ filter: riso ? `url(#${id}-pA)` : undefined
935
+ }, front),
936
+ n("g", "plateB", {
937
+ transform: off(0.6, -0.5),
938
+ filter: riso ? `url(#${id}-pB)` : undefined
939
+ }, back)
940
+ ]);
941
+ const scene = [];
942
+ if (o.frame !== "none") {
943
+ const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
944
+ scene.push(n("rect", "bg", { ...VBr, fill: bg }));
945
+ const th = clamp(g("thing"), 0, 1);
946
+ const dots = [];
947
+ for (let j = 0;j < 3; j++)
948
+ for (let i = 0;i < 3; i++) {
949
+ const q = j * 3 + i;
950
+ const kk = smooth(q / 12, q / 12 + 0.35, th);
951
+ dots.push(n("circle", `lattice.${q}`, {
952
+ cx: 312 + i * 18,
953
+ cy: 2 + j * 18,
954
+ r: f2(1.2 + 2.2 * kk),
955
+ opacity: f3(kk * 0.55)
956
+ }));
957
+ }
958
+ scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
959
+ }
960
+ scene.push(av);
961
+ if (grainy)
962
+ scene.push(n("rect", "grain", {
963
+ ...VBr,
964
+ filter: `url(#${id}-grain)`,
965
+ opacity: o.frame === "ink" ? 0.3 : 0.16,
966
+ "pointer-events": "none"
967
+ }));
968
+ const root = n("svg", "root", {
969
+ xmlns: "http://www.w3.org/2000/svg",
970
+ viewBox: VB.join(" "),
971
+ width: o.size,
972
+ height: o.size,
973
+ role: o.title === false ? undefined : "img",
974
+ "aria-label": o.title === false ? undefined : o.title,
975
+ "aria-hidden": o.title === false ? "true" : undefined
976
+ }, [
977
+ n("defs", "defs", {}, defs),
978
+ n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
979
+ ]);
980
+ return o.live ? root : prune(root) ?? root;
981
+ }
982
+ var HAZE_LEVEL = {
983
+ medium: {
984
+ deep: [0.502, 0.224],
985
+ glow: [0.749, 0.337],
986
+ tint: [0.4, 0.5],
987
+ tintStops: [
988
+ [0, 0.878],
989
+ [0.15, 0.878],
990
+ [0.618, 0.396]
991
+ ]
992
+ },
993
+ light: {
994
+ deep: [0.298, 0.133],
995
+ glow: [0.549, 0.247],
996
+ tint: [0.31, 0.387],
997
+ tintStops: [
998
+ [0, 0.549],
999
+ [0.55, 0.247]
1000
+ ]
1001
+ }
1002
+ };
1003
+ function hazePaint(who, o) {
1004
+ const sh = FACES[who.face];
1005
+ const id = o.id;
1006
+ const L = HAZE_LEVEL[o.size < 32 ? "light" : "medium"];
1007
+ const xs = sh.crown.poly.map((p) => p[0]);
1008
+ const ys = sh.crown.poly.map((p) => p[1]);
1009
+ const x0 = Math.min(...xs) - sh.crown.r;
1010
+ const y0 = Math.min(...ys) - sh.crown.r;
1011
+ const W = Math.max(...xs) + sh.crown.r - x0;
1012
+ const H = sh.bottom - y0;
1013
+ const rnd = mulberry32(fnv1a(`haze:${who.key ?? ""}`));
1014
+ const warm = rnd() < 0.5;
1015
+ const left = rnd() < 0.5;
1016
+ const t = rnd();
1017
+ const tintX = 0.04 + 0.174 * t;
1018
+ const tintY = 0.649 + 0.202 * t;
1019
+ const glowX = 0.38 - 0.12 * rnd();
1020
+ const glowY = 0.12 + 0.099 * rnd();
1021
+ const deepX = 0.251 - 0.151 * rnd();
1022
+ const tintA = 90 - 180 * rnd();
1023
+ const at = (inset, onLeft) => x0 + W * (onLeft ? inset : 1 - inset);
1024
+ const i = WHEEL.indexOf(who.color);
1025
+ const near = WHEEL[(i + (warm ? WHEEL.length - 1 : 1)) % WHEEL.length];
1026
+ const { body, deep } = COLORS[who.color];
1027
+ const blob = (k, color, [cx, cy, a, rx, ry], stops) => n("radialGradient", `haze.${k}`, {
1028
+ id: `${id}-h${k[0]}`,
1029
+ gradientUnits: "userSpaceOnUse",
1030
+ cx: 0,
1031
+ cy: 0,
1032
+ r: 1,
1033
+ gradientTransform: `translate(${f2(cx)} ${f2(cy)}) rotate(${f2(a)}) scale(${f2(rx)} ${f2(ry)})`
1034
+ }, [...stops, [1, 0]].map(([at2, op], j) => n("stop", `haze.${k}.${j}`, {
1035
+ offset: at2,
1036
+ "stop-color": color,
1037
+ "stop-opacity": op
1038
+ })));
1039
+ const fade = ([a, b]) => [
1040
+ [0, a],
1041
+ [0.55, b]
1042
+ ];
1043
+ const blobs = [
1044
+ blob("deep", deep, [at(deepX, !left), y0 + H * 0.95, 0, W * 0.5, W * 0.35], fade(L.deep)),
1045
+ blob("tint", HAZE[near].tint, [at(tintX, left), y0 + H * tintY, tintA, W * L.tint[0], W * L.tint[1]], L.tintStops),
1046
+ blob("glow", HAZE[who.color].glow, [at(glowX, !left), y0 + H * glowY, 0, W * 0.475, W * 0.356], fade(L.glow))
1047
+ ];
1048
+ const shape = (key, paint) => ("d" in sh.body) ? n("path", key, {
1049
+ d: sh.body.d,
1050
+ fill: paint,
1051
+ stroke: sh.body.stroke ? paint : undefined,
1052
+ "stroke-width": sh.body.stroke,
1053
+ "stroke-linejoin": sh.body.stroke ? "round" : undefined
1054
+ }) : n("circle", key, {
1055
+ cx: sh.body.circle[0],
1056
+ cy: sh.body.circle[1],
1057
+ r: sh.body.circle[2],
1058
+ fill: paint
1059
+ });
1060
+ return [
1061
+ ...blobs,
1062
+ n("g", "haze", { id: `${id}-hz` }, [
1063
+ shape("haze.body", body),
1064
+ ...["deep", "tint", "glow"].map((k) => shape(`haze.${k}.fill`, `url(#${id}-h${k[0]})`))
1065
+ ])
1066
+ ];
1067
+ }
1068
+ function peekParts({ who, pose, o, defs, g }) {
1069
+ const sh = FACES[who.face];
1070
+ const { body: fill, deep } = COLORS[who.color];
1071
+ const P = who.persona;
1072
+ const spread = P.spread;
1073
+ const id = o.id;
1074
+ const st = strokeFor2(o.size);
1075
+ const haze = o.variant === "haze";
1076
+ if (haze)
1077
+ defs.push(...hazePaint(who, o));
1078
+ const bodyNode = haze ? n("use", "body", { href: `#${id}-hz` }) : ("d" in sh.body) ? n("path", "body", {
588
1079
  d: sh.body.d,
589
1080
  fill,
590
1081
  stroke: sh.body.stroke ? fill : undefined,
@@ -672,25 +1163,36 @@ function draw(who, pose, o) {
672
1163
  const lrx = rx * 1.55;
673
1164
  const lry = ry * 1.15;
674
1165
  const top = ry + 1.6 - lower * ry * 1.5;
1166
+ const [etx, ety] = bead ? [ex + side * spread + gx * 6, ey + gy * 6] : [ex + side * spread, ey];
1167
+ const lids = [
1168
+ n("rect", `${kk}.lid`, {
1169
+ x: f2(-rx * 2.2),
1170
+ y: f2(-ry * 3),
1171
+ width: f2(rx * 4.4),
1172
+ height: f2(Math.max(0, edge + ry * 3)),
1173
+ transform: `rotate(${f2(a)})`,
1174
+ fill
1175
+ }),
1176
+ n("ellipse", `${kk}.lower`, {
1177
+ cx: 0,
1178
+ cy: f2(top + lry),
1179
+ rx: f2(lrx),
1180
+ ry: f2(lry),
1181
+ fill
1182
+ })
1183
+ ];
1184
+ if (haze)
1185
+ defs.push(n("clipPath", `${kk}.lidshape`, { id: `${id}-s${i}` }, lids));
675
1186
  const parts = [
676
1187
  n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
677
- n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, [
678
- n("rect", `${kk}.lid`, {
679
- x: f2(-rx * 2.2),
680
- y: f2(-ry * 3),
681
- width: f2(rx * 4.4),
682
- height: f2(Math.max(0, edge + ry * 3)),
683
- transform: `rotate(${f2(a)})`,
684
- fill
685
- }),
686
- n("ellipse", `${kk}.lower`, {
687
- cx: 0,
688
- cy: f2(top + lry),
689
- rx: f2(lrx),
690
- ry: f2(lry),
691
- fill
692
- })
693
- ])
1188
+ n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, haze ? [
1189
+ n("g", `${kk}.lidpaint`, { "clip-path": `url(#${id}-s${i})` }, [
1190
+ n("use", `${kk}.lidbody`, {
1191
+ href: `#${id}-hz`,
1192
+ transform: `translate(${f2(-etx)} ${f2(-ety)})`
1193
+ })
1194
+ ])
1195
+ ] : lids)
694
1196
  ];
695
1197
  if (who.eyes === "ring")
696
1198
  parts.push(n("g", `${kk}.edges`, {
@@ -724,9 +1226,7 @@ function draw(who, pose, o) {
724
1226
  "stroke-width": f2(sw * 0.85),
725
1227
  opacity: co > 0.01 ? f2(co) : 0
726
1228
  }));
727
- return n("g", kk, {
728
- transform: bead ? `translate(${f2(ex + side * spread + gx * 6)} ${f2(ey + gy * 6)})` : `translate(${f2(ex + side * spread)} ${ey})`
729
- }, parts);
1229
+ return n("g", kk, { transform: `translate(${f2(etx)} ${f2(ety)})` }, parts);
730
1230
  });
731
1231
  const browY = g("browY") - (eyeS - 1) * sh.ry * 0.9;
732
1232
  const btilt = g("browTilt");
@@ -746,8 +1246,8 @@ function draw(who, pose, o) {
746
1246
  const half = (t) => wedge ? hl + (hr - hl) * t : bw * k2 / 2;
747
1247
  const bx = ex + side * spread;
748
1248
  const by = sh.brow.y + browY + (i === 1 ? -basym : basym * 0.3);
749
- const cos = Math.cos(side * btilt * RAD);
750
- const sin = Math.sin(side * btilt * RAD);
1249
+ const cos = Math.cos(side * btilt * RAD2);
1250
+ const sin = Math.sin(side * btilt * RAD2);
751
1251
  const inside = (dy) => {
752
1252
  for (let t = 0;t <= 1; t += 0.25) {
753
1253
  const lx = w2 * (2 * t - 1);
@@ -763,7 +1263,7 @@ function draw(who, pose, o) {
763
1263
  let hi = 0;
764
1264
  if (!inside(0)) {
765
1265
  hi = 40;
766
- for (let k3 = 0;k3 < 12; k3++) {
1266
+ for (let k = 0;k < 12; k++) {
767
1267
  const mid2 = (lo + hi) / 2;
768
1268
  if (inside(mid2))
769
1269
  hi = mid2;
@@ -866,7 +1366,7 @@ function draw(who, pose, o) {
866
1366
  fill: deep
867
1367
  });
868
1368
  } else if (who.trait === "fin") {
869
- const s = seat(sh.crown, -6, -164 * 22 * RAD);
1369
+ const s = seat(sh.crown, -6, -164 * 22 * RAD2);
870
1370
  const r = s.a + (hair >= 0 ? hair * 20 : hair * 55) - lag * 0.8;
871
1371
  traitT = `translate(${f2(s.x)} ${f2(s.y + lag * 0.25)}) rotate(${f2(r)})`;
872
1372
  shape = n("path", "trait.shape", {
@@ -876,7 +1376,7 @@ function draw(who, pose, o) {
876
1376
  } else if (who.trait === "ring") {
877
1377
  const b = 35 - hair * 14 - (hair < 0 ? hair * -21 : 0) + lag * 0.9;
878
1378
  const w2 = ringWalk(sh);
879
- const s = seat(sh.crown, 8, Math.max(w2.min, w2.rest + 158 * (b - 35) * RAD));
1379
+ const s = seat(sh.crown, 8, Math.max(w2.min, w2.rest + 158 * (b - 35) * RAD2));
880
1380
  traitT = `translate(${f2(s.x)} ${f2(s.y)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
881
1381
  shape = n("circle", "trait.shape", {
882
1382
  cx: 0,
@@ -888,7 +1388,7 @@ function draw(who, pose, o) {
888
1388
  });
889
1389
  } else if (who.trait === "dot") {
890
1390
  const s = clamp(-hair, 0, 1.2);
891
- const th = Math.min(1, s / 0.35) * 61.05 * RAD;
1391
+ const th = Math.min(1, s / 0.35) * 61.05 * RAD2;
892
1392
  const travel = Math.max(0, s - 0.35) / 0.65 * 92;
893
1393
  const p = seat(sh.crown, 12.5, 26.1 * th + travel);
894
1394
  let cy = p.y;
@@ -1059,7 +1559,7 @@ function draw(who, pose, o) {
1059
1559
  }, parts));
1060
1560
  }
1061
1561
  if (who.neckwear === "tie") {
1062
- const skew = Math.abs(Math.sin(g("mk") * RAD)) * mw;
1562
+ const skew = Math.abs(Math.sin(g("mk") * RAD2)) * mw;
1063
1563
  const bottom = Math.max(0, mt * kb, mb * kb) + skew + m.sw * st;
1064
1564
  wardrobe.push(n("g", "neckwear", {
1065
1565
  transform: `translate(${m.x} ${f2(m.y + g("my") + bottom + 8)})`,
@@ -1073,133 +1573,56 @@ function draw(who, pose, o) {
1073
1573
  })
1074
1574
  ]));
1075
1575
  }
1076
- const off = (dx, dy) => riso ? `translate(${f2(dx * k * upp)} ${f2(dy * k * upp)})` : undefined;
1077
- const av = n("g", "av", {
1078
- transform: `translate(${f2(x)} ${f2(ty)}) rotate(${f2(rot)} 170 ${B}) translate(170 ${B}) scale(${sc(sx)} ${sc(sy)}) translate(-170 ${-B})`,
1079
- filter: dim > 0.01 ? `url(#${id}-dim)` : undefined
1080
- }, [
1081
- n("g", "plateA", {
1082
- transform: off(-0.55, 0.45),
1083
- filter: riso ? `url(#${id}-pA)` : undefined
1084
- }, [bodyNode, ...cheeks, trait]),
1085
- n("g", "plateB", {
1086
- transform: off(0.6, -0.5),
1087
- filter: riso ? `url(#${id}-pB)` : undefined
1088
- }, [...eyes, ...brows, mouth, ...wardrobe])
1089
- ]);
1090
- const scene = [];
1091
- if (o.frame !== "none") {
1092
- const bg = o.frame === "ink" ? INK.ink : o.frame === "bone" ? INK.bone : INK.paper;
1093
- scene.push(n("rect", "bg", { ...VBr, fill: bg }));
1094
- const th = clamp(g("thing"), 0, 1);
1095
- const dots = [];
1096
- for (let j = 0;j < 3; j++)
1097
- for (let i = 0;i < 3; i++) {
1098
- const q = j * 3 + i;
1099
- const kk = smooth(q / 12, q / 12 + 0.35, th);
1100
- dots.push(n("circle", `lattice.${q}`, {
1101
- cx: 312 + i * 18,
1102
- cy: 2 + j * 18,
1103
- r: f2(1.2 + 2.2 * kk),
1104
- opacity: f3(kk * 0.55)
1105
- }));
1106
- }
1107
- scene.push(n("g", "lattice", { fill: o.frame === "ink" ? INK.bone : INK.ink }, dots));
1108
- }
1109
- scene.push(av);
1110
- if (grainy)
1111
- scene.push(n("rect", "grain", {
1112
- ...VBr,
1113
- filter: `url(#${id}-grain)`,
1114
- opacity: o.frame === "ink" ? 0.3 : 0.16,
1115
- "pointer-events": "none"
1116
- }));
1117
- const root = n("svg", "root", {
1118
- xmlns: "http://www.w3.org/2000/svg",
1119
- viewBox: VB.join(" "),
1120
- width: o.size,
1121
- height: o.size,
1122
- role: o.title === false ? undefined : "img",
1123
- "aria-label": o.title === false ? undefined : o.title,
1124
- "aria-hidden": o.title === false ? "true" : undefined
1125
- }, [
1126
- n("defs", "defs", {}, defs),
1127
- n("g", "frame", { "clip-path": `url(#${id}-f)` }, scene)
1128
- ]);
1129
- return o.live ? root : prune(root) ?? root;
1130
- }
1131
- // src/identity.ts
1132
- var STYLE = "peek";
1133
- var tidy = (s) => s.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
1134
- function fnv1a(str) {
1135
- let h = 2166136261;
1136
- for (const b of new TextEncoder().encode(str)) {
1137
- h ^= b;
1138
- h = Math.imul(h, 16777619) >>> 0;
1139
- }
1140
- return h >>> 0;
1576
+ return [
1577
+ [bodyNode, ...cheeks, trait],
1578
+ [...eyes, ...brows, mouth, ...wardrobe]
1579
+ ];
1141
1580
  }
1142
- function mulberry32(a) {
1143
- return () => {
1144
- a = a + 1831565813 | 0;
1145
- let t = Math.imul(a ^ a >>> 15, 1 | a);
1146
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
1147
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
1581
+ // src/svg.ts
1582
+ var wear = (o, base, s) => ACCESSORIES[s].includes(o[s]) ? o[s] : base[s];
1583
+ function settle(name, o, live = false) {
1584
+ const base = identify(name, { version: o.version });
1585
+ const who = {
1586
+ ...base,
1587
+ face: o.face ?? base.face,
1588
+ color: o.color ?? base.color,
1589
+ eyes: o.eyes ?? base.eyes,
1590
+ brows: o.brows ?? base.brows,
1591
+ mouth: o.mouth ?? base.mouth,
1592
+ cheeks: o.cheeks ?? base.cheeks,
1593
+ trait: o.trait ?? base.trait,
1594
+ eyewear: wear(o, base, "eyewear"),
1595
+ headwear: wear(o, base, "headwear"),
1596
+ neckwear: wear(o, base, "neckwear")
1597
+ };
1598
+ const pose = restPose(who, o.expression ?? "normal", o.gaze);
1599
+ const variant = VARIANTS.includes(o.variant) ? o.variant : "peek";
1600
+ const look = {
1601
+ size: o.size ?? 64,
1602
+ frame: o.frame ?? "ink",
1603
+ square: o.square ?? true,
1604
+ riso: o.riso ?? false,
1605
+ live,
1606
+ ...variant !== "peek" && { variant }
1148
1607
  };
1608
+ const { eyewear, headwear, neckwear, ...legacy } = who;
1609
+ const wardrobe = eyewear !== "none" || headwear !== "none" || neckwear !== "none";
1610
+ const id = o.id ?? `peek-${fnv1a(JSON.stringify([wardrobe ? who : legacy, pose, look])).toString(36)}`;
1611
+ return { who, pose, opts: { ...look, id, title: o.title ?? name } };
1149
1612
  }
1150
- var LISTS = {
1151
- face: Object.keys(FACES),
1152
- color: Object.keys(COLORS),
1153
- ...PARTS,
1154
- ...ACCESSORIES
1155
- };
1156
- var AXES = Object.keys(LISTS);
1157
- function seed(name, axis, version = LATEST) {
1158
- return fnv1a(`${STYLE}@${version}:${axis}:${tidy(name)}`);
1613
+ var ILLEGAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1614
+ var esc = (s) => s.replace(ILLEGAL, "�").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1615
+ function serialize(node) {
1616
+ let a = "";
1617
+ for (const k in node.attrs)
1618
+ a += ` ${k}="${esc(node.attrs[k])}"`;
1619
+ if (!node.children.length)
1620
+ return `<${node.tag}${a}/>`;
1621
+ return `<${node.tag}${a}>${node.children.map(serialize).join("")}</${node.tag}>`;
1159
1622
  }
1160
- function identify(name, { version = LATEST } = {}) {
1161
- const lengths = VERSIONS[version];
1162
- if (!lengths)
1163
- throw new Error(`peek@${version} does not exist`);
1164
- const pick = (axis) => LISTS[axis][seed(name, axis, version) % lengths[axis]];
1165
- const rnd = mulberry32(seed(name, "persona", version));
1166
- const r = (a, b) => Math.round((a + rnd() * (b - a)) * 100) / 100;
1167
- const persona = {
1168
- spread: r(-7, 7),
1169
- blink: r(0.75, 1.4),
1170
- add: {
1171
- browY: r(-4, 4),
1172
- browTilt: r(-5, 5),
1173
- browArch: r(-0.2, 0.3),
1174
- rot: r(-3, 3),
1175
- hair: r(-0.2, 0.35),
1176
- gx: r(-0.12, 0.12),
1177
- my: r(-2, 3)
1178
- },
1179
- mul: {
1180
- eyeS: r(0.92, 1.08),
1181
- pupil: r(0.9, 1.12),
1182
- browW: r(0.85, 1.2),
1183
- mw: r(0.85, 1.18)
1184
- }
1185
- };
1186
- const key = tidy(name);
1187
- return {
1188
- key,
1189
- hash: fnv1a(key),
1190
- version,
1191
- face: pick("face"),
1192
- color: pick("color"),
1193
- eyes: pick("eyes"),
1194
- brows: pick("brows"),
1195
- mouth: pick("mouth"),
1196
- cheeks: pick("cheeks"),
1197
- trait: pick("trait"),
1198
- eyewear: pick("eyewear"),
1199
- headwear: pick("headwear"),
1200
- neckwear: pick("neckwear"),
1201
- persona
1202
- };
1623
+ function toSvg(name, props = {}) {
1624
+ const { who, pose, opts } = settle(name, props);
1625
+ return serialize(draw(who, pose, opts));
1203
1626
  }
1204
1627
  // src/peek.tsx
1205
1628
  import {
@@ -1786,51 +2209,6 @@ class Live {
1786
2209
  }
1787
2210
  }
1788
2211
 
1789
- // src/svg.ts
1790
- var wear = (o, base, s) => ACCESSORIES[s].includes(o[s]) ? o[s] : base[s];
1791
- function settle(name, o, live = false) {
1792
- const base = identify(name, { version: o.version });
1793
- const who = {
1794
- ...base,
1795
- face: o.face ?? base.face,
1796
- color: o.color ?? base.color,
1797
- eyes: o.eyes ?? base.eyes,
1798
- brows: o.brows ?? base.brows,
1799
- mouth: o.mouth ?? base.mouth,
1800
- cheeks: o.cheeks ?? base.cheeks,
1801
- trait: o.trait ?? base.trait,
1802
- eyewear: wear(o, base, "eyewear"),
1803
- headwear: wear(o, base, "headwear"),
1804
- neckwear: wear(o, base, "neckwear")
1805
- };
1806
- const pose = restPose(who, o.expression ?? "normal", o.gaze);
1807
- const look = {
1808
- size: o.size ?? 64,
1809
- frame: o.frame ?? "ink",
1810
- square: o.square ?? true,
1811
- riso: o.riso ?? false,
1812
- live
1813
- };
1814
- const { eyewear, headwear, neckwear, ...legacy } = who;
1815
- const wardrobe = eyewear !== "none" || headwear !== "none" || neckwear !== "none";
1816
- const id = o.id ?? `peek-${fnv1a(JSON.stringify([wardrobe ? who : legacy, pose, look])).toString(36)}`;
1817
- return { who, pose, opts: { ...look, id, title: o.title ?? name } };
1818
- }
1819
- var ILLEGAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1820
- var esc = (s) => s.replace(ILLEGAL, "�").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1821
- function serialize(node) {
1822
- let a = "";
1823
- for (const k in node.attrs)
1824
- a += ` ${k}="${esc(node.attrs[k])}"`;
1825
- if (!node.children.length)
1826
- return `<${node.tag}${a}/>`;
1827
- return `<${node.tag}${a}>${node.children.map(serialize).join("")}</${node.tag}>`;
1828
- }
1829
- function toSvg(name, props = {}) {
1830
- const { who, pose, opts } = settle(name, props);
1831
- return serialize(draw(who, pose, opts));
1832
- }
1833
-
1834
2212
  // src/peek.tsx
1835
2213
  var camel = (k) => k.startsWith("aria-") ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1836
2214
  function toReact(node, root) {
@@ -1878,15 +2256,19 @@ function Peek({
1878
2256
  export {
1879
2257
  ACCESSORIES,
1880
2258
  AXES,
2259
+ BLOC,
1881
2260
  COLORS,
1882
2261
  EXPRESSIONS,
1883
2262
  FACES,
1884
2263
  GROUPS,
2264
+ HAZE,
1885
2265
  LATEST,
1886
2266
  LISTS,
1887
2267
  PARTS,
1888
2268
  Peek,
2269
+ VARIANTS,
1889
2270
  VERSIONS,
2271
+ WHEEL,
1890
2272
  draw,
1891
2273
  identify,
1892
2274
  settle,