@doan-labs/peek 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +64 -1
- package/dist/bloc.d.ts +2 -0
- package/dist/core.d.ts +9 -0
- package/dist/draw.d.ts +24 -2
- package/dist/identity.d.ts +4 -1
- package/dist/identity.js +22 -2
- package/dist/index.d.ts +1 -7
- package/dist/index.js +719 -142
- package/dist/peek.svelte +75 -0
- package/dist/peek.svelte.d.ts +5 -0
- package/dist/svelte-peek.d.ts +14 -0
- package/dist/svelte-peek.js +2205 -0
- package/dist/svelte.d.ts +3 -0
- package/dist/svelte.js +1650 -0
- package/dist/svg.d.ts +3 -1
- package/dist/svg.js +737 -166
- package/dist/tables.d.ts +67 -1
- package/dist/vue.d.ts +17 -0
- package/dist/vue.js +2302 -0
- package/package.json +30 -5
- package/src/bloc.ts +326 -0
- package/src/core.ts +39 -0
- package/src/draw.ts +522 -109
- package/src/identity.ts +11 -0
- package/src/index.ts +1 -26
- package/src/peek.svelte +75 -0
- package/src/peek.svelte.d.ts +5 -0
- package/src/peek.tsx +1 -1
- package/src/svelte-peek.ts +18 -0
- package/src/svelte.ts +3 -0
- package/src/svg.ts +30 -2
- package/src/tables.ts +74 -2
- package/src/vue.ts +133 -0
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"],
|
|
@@ -121,8 +149,25 @@ var PARTS = {
|
|
|
121
149
|
cheeks: ["oval", "dots", "lines"],
|
|
122
150
|
trait: ["square", "fin", "ring", "dot", "peak"]
|
|
123
151
|
};
|
|
152
|
+
var ACCESSORY_INK = { knot: "#E5412D" };
|
|
153
|
+
var ACCESSORIES = {
|
|
154
|
+
eyewear: ["none", "glasses", "sunglasses"],
|
|
155
|
+
headwear: ["none", "bow", "cap", "sprout"],
|
|
156
|
+
neckwear: ["none", "tie"]
|
|
157
|
+
};
|
|
124
158
|
var VERSIONS = {
|
|
125
|
-
1: {
|
|
159
|
+
1: {
|
|
160
|
+
face: 4,
|
|
161
|
+
color: 7,
|
|
162
|
+
eyes: 3,
|
|
163
|
+
brows: 4,
|
|
164
|
+
mouth: 4,
|
|
165
|
+
cheeks: 3,
|
|
166
|
+
trait: 5,
|
|
167
|
+
eyewear: 1,
|
|
168
|
+
headwear: 1,
|
|
169
|
+
neckwear: 1
|
|
170
|
+
}
|
|
126
171
|
};
|
|
127
172
|
var LATEST = 1;
|
|
128
173
|
var BASE = {
|
|
@@ -322,6 +367,314 @@ var EXPRESSIONS = {
|
|
|
322
367
|
})
|
|
323
368
|
};
|
|
324
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
|
+
|
|
325
678
|
// src/draw.ts
|
|
326
679
|
var VB = [-30, -30, 400, 400];
|
|
327
680
|
var FLOOR = 370;
|
|
@@ -333,8 +686,11 @@ var smooth = (a, b, v) => {
|
|
|
333
686
|
};
|
|
334
687
|
var f2 = (v) => String(Math.round(v * 100) / 100);
|
|
335
688
|
var f3 = (v) => String(Math.round(v * 1000) / 1000);
|
|
336
|
-
var
|
|
337
|
-
var
|
|
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;
|
|
691
|
+
var STEM = "M0 0Q3 -19 0 -36";
|
|
692
|
+
var LEAF0 = "M0 -33C-24 -30 -38 -52 -25 -53C-12 -54 -2 -43 0 -33Z";
|
|
693
|
+
var LEAF1 = "M0 -33C5 -54 28 -61 27 -48C26 -36 11 -31 0 -33Z";
|
|
338
694
|
function frameOf(face) {
|
|
339
695
|
const baseY = FLOOR + face.sink - face.bottom;
|
|
340
696
|
const eyeY = face.eyes[0][1];
|
|
@@ -363,18 +719,18 @@ function seat(crown, d, u) {
|
|
|
363
719
|
const at = ([x, y], b2) => ({
|
|
364
720
|
x: x + R * Math.sin(b2),
|
|
365
721
|
y: y - R * Math.cos(b2),
|
|
366
|
-
a: b2 /
|
|
722
|
+
a: b2 / RAD2
|
|
367
723
|
});
|
|
368
724
|
const P = crown.poly;
|
|
369
725
|
if (P.length === 1)
|
|
370
726
|
return at(P[0], R ? u / R : 0);
|
|
371
|
-
const
|
|
727
|
+
const n2 = P.length;
|
|
372
728
|
const dir = u >= 0 ? 1 : -1;
|
|
373
729
|
let left = Math.abs(u);
|
|
374
730
|
let i = 0;
|
|
375
731
|
let b = 0;
|
|
376
|
-
for (let step = 0;step <
|
|
377
|
-
const j = (i + dir +
|
|
732
|
+
for (let step = 0;step < n2; step++) {
|
|
733
|
+
const j = (i + dir + n2) % n2;
|
|
378
734
|
const [x0, y0] = P[i];
|
|
379
735
|
const [x1, y1] = P[j];
|
|
380
736
|
const ex = x1 - x0;
|
|
@@ -394,7 +750,7 @@ function seat(crown, d, u) {
|
|
|
394
750
|
return {
|
|
395
751
|
x: x0 + ex / L * left + R * nx,
|
|
396
752
|
y: y0 + ey / L * left + R * ny,
|
|
397
|
-
a: be /
|
|
753
|
+
a: be / RAD2
|
|
398
754
|
};
|
|
399
755
|
left -= L;
|
|
400
756
|
i = j;
|
|
@@ -421,7 +777,7 @@ function depth(crown, x, y) {
|
|
|
421
777
|
}
|
|
422
778
|
return (out <= 0 ? out : near) - crown.r;
|
|
423
779
|
}
|
|
424
|
-
var RING_U = 158 * 35 *
|
|
780
|
+
var RING_U = 158 * 35 * RAD2;
|
|
425
781
|
function ringWalk(sh) {
|
|
426
782
|
const ex = sh.eyes[1][0] + sh.brow.hw + 7;
|
|
427
783
|
const top = sh.brow.y - 30;
|
|
@@ -455,14 +811,11 @@ function prune(node) {
|
|
|
455
811
|
}
|
|
456
812
|
function draw(who, pose, o) {
|
|
457
813
|
const sh = FACES[who.face];
|
|
458
|
-
const { body: fill, deep } = COLORS[who.color];
|
|
459
814
|
const P = who.persona;
|
|
460
815
|
const mul = P.mul;
|
|
461
816
|
const add = P.add;
|
|
462
817
|
const g = (c) => pose[c] * (mul[c] ?? 1) + (add[c] ?? 0);
|
|
463
|
-
const spread = P.spread;
|
|
464
818
|
const id = o.id;
|
|
465
|
-
const st = strokeFor(o.size);
|
|
466
819
|
const { baseY, R } = frameOf(sh);
|
|
467
820
|
const defs = [];
|
|
468
821
|
const VBr = { x: VB[0], y: VB[1], width: VB[2], height: VB[3] };
|
|
@@ -556,7 +909,165 @@ function draw(who, pose, o) {
|
|
|
556
909
|
const B = sh.bottom;
|
|
557
910
|
const ty = baseY - alt * R;
|
|
558
911
|
const sc = (v) => Math.round(v * 1e5) / 1e5;
|
|
559
|
-
const
|
|
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", {
|
|
560
1071
|
d: sh.body.d,
|
|
561
1072
|
fill,
|
|
562
1073
|
stroke: sh.body.stroke ? fill : undefined,
|
|
@@ -644,25 +1155,36 @@ function draw(who, pose, o) {
|
|
|
644
1155
|
const lrx = rx * 1.55;
|
|
645
1156
|
const lry = ry * 1.15;
|
|
646
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));
|
|
647
1178
|
const parts = [
|
|
648
1179
|
n("g", `${kk}.inner`, { "clip-path": `url(#${id}-e${i})` }, inner),
|
|
649
|
-
n("g", `${kk}.lids`, { "clip-path": `url(#${id}-l${i})` }, [
|
|
650
|
-
n("
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
])
|
|
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)
|
|
666
1188
|
];
|
|
667
1189
|
if (who.eyes === "ring")
|
|
668
1190
|
parts.push(n("g", `${kk}.edges`, {
|
|
@@ -696,9 +1218,7 @@ function draw(who, pose, o) {
|
|
|
696
1218
|
"stroke-width": f2(sw * 0.85),
|
|
697
1219
|
opacity: co > 0.01 ? f2(co) : 0
|
|
698
1220
|
}));
|
|
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);
|
|
1221
|
+
return n("g", kk, { transform: `translate(${f2(etx)} ${f2(ety)})` }, parts);
|
|
702
1222
|
});
|
|
703
1223
|
const browY = g("browY") - (eyeS - 1) * sh.ry * 0.9;
|
|
704
1224
|
const btilt = g("browTilt");
|
|
@@ -709,7 +1229,7 @@ function draw(who, pose, o) {
|
|
|
709
1229
|
const side = i === 0 ? -1 : 1;
|
|
710
1230
|
const hw = sh.brow.hw;
|
|
711
1231
|
const c = -sh.brow.arch * arch;
|
|
712
|
-
const [
|
|
1232
|
+
const [w2, ca, k2] = who.brows === "bar" ? [hw * 0.9, c * 0.3, 1.75] : who.brows === "dash" ? [hw * 0.42, c * 0.25, 1.9] : [hw, c, 1];
|
|
713
1233
|
const ti = bw * 1.7;
|
|
714
1234
|
const to = bw * 0.45;
|
|
715
1235
|
const [tl, tr] = side < 0 ? [to, ti] : [ti, to];
|
|
@@ -718,11 +1238,11 @@ function draw(who, pose, o) {
|
|
|
718
1238
|
const half = (t) => wedge ? hl + (hr - hl) * t : bw * k2 / 2;
|
|
719
1239
|
const bx = ex + side * spread;
|
|
720
1240
|
const by = sh.brow.y + browY + (i === 1 ? -basym : basym * 0.3);
|
|
721
|
-
const cos = Math.cos(side * btilt *
|
|
722
|
-
const sin = Math.sin(side * btilt *
|
|
1241
|
+
const cos = Math.cos(side * btilt * RAD2);
|
|
1242
|
+
const sin = Math.sin(side * btilt * RAD2);
|
|
723
1243
|
const inside = (dy) => {
|
|
724
1244
|
for (let t = 0;t <= 1; t += 0.25) {
|
|
725
|
-
const lx =
|
|
1245
|
+
const lx = w2 * (2 * t - 1);
|
|
726
1246
|
const ly = 2 * t * (1 - t) * ca;
|
|
727
1247
|
const px = bx + lx * cos - ly * sin;
|
|
728
1248
|
const py = by + dy + lx * sin + ly * cos;
|
|
@@ -735,7 +1255,7 @@ function draw(who, pose, o) {
|
|
|
735
1255
|
let hi = 0;
|
|
736
1256
|
if (!inside(0)) {
|
|
737
1257
|
hi = 40;
|
|
738
|
-
for (let
|
|
1258
|
+
for (let k = 0;k < 12; k++) {
|
|
739
1259
|
const mid2 = (lo + hi) / 2;
|
|
740
1260
|
if (inside(mid2))
|
|
741
1261
|
hi = mid2;
|
|
@@ -757,7 +1277,7 @@ function draw(who, pose, o) {
|
|
|
757
1277
|
});
|
|
758
1278
|
}
|
|
759
1279
|
return n("path", key, {
|
|
760
|
-
d: `M${f2(-
|
|
1280
|
+
d: `M${f2(-w2)} 0Q0 ${f2(ca)} ${f2(w2)} 0`,
|
|
761
1281
|
transform,
|
|
762
1282
|
fill: "none",
|
|
763
1283
|
stroke: INK.ink,
|
|
@@ -838,7 +1358,7 @@ function draw(who, pose, o) {
|
|
|
838
1358
|
fill: deep
|
|
839
1359
|
});
|
|
840
1360
|
} else if (who.trait === "fin") {
|
|
841
|
-
const s = seat(sh.crown, -6, -164 * 22 *
|
|
1361
|
+
const s = seat(sh.crown, -6, -164 * 22 * RAD2);
|
|
842
1362
|
const r = s.a + (hair >= 0 ? hair * 20 : hair * 55) - lag * 0.8;
|
|
843
1363
|
traitT = `translate(${f2(s.x)} ${f2(s.y + lag * 0.25)}) rotate(${f2(r)})`;
|
|
844
1364
|
shape = n("path", "trait.shape", {
|
|
@@ -847,8 +1367,8 @@ function draw(who, pose, o) {
|
|
|
847
1367
|
});
|
|
848
1368
|
} else if (who.trait === "ring") {
|
|
849
1369
|
const b = 35 - hair * 14 - (hair < 0 ? hair * -21 : 0) + lag * 0.9;
|
|
850
|
-
const
|
|
851
|
-
const s = seat(sh.crown, 8, Math.max(
|
|
1370
|
+
const w2 = ringWalk(sh);
|
|
1371
|
+
const s = seat(sh.crown, 8, Math.max(w2.min, w2.rest + 158 * (b - 35) * RAD2));
|
|
852
1372
|
traitT = `translate(${f2(s.x)} ${f2(s.y)}) scale(${f2(1 + Math.max(0, hair) * 0.1)})`;
|
|
853
1373
|
shape = n("circle", "trait.shape", {
|
|
854
1374
|
cx: 0,
|
|
@@ -860,7 +1380,7 @@ function draw(who, pose, o) {
|
|
|
860
1380
|
});
|
|
861
1381
|
} else if (who.trait === "dot") {
|
|
862
1382
|
const s = clamp(-hair, 0, 1.2);
|
|
863
|
-
const th = Math.min(1, s / 0.35) * 61.05 *
|
|
1383
|
+
const th = Math.min(1, s / 0.35) * 61.05 * RAD2;
|
|
864
1384
|
const travel = Math.max(0, s - 0.35) / 0.65 * 92;
|
|
865
1385
|
const p = seat(sh.crown, 12.5, 26.1 * th + travel);
|
|
866
1386
|
let cy = p.y;
|
|
@@ -882,133 +1402,177 @@ function draw(who, pose, o) {
|
|
|
882
1402
|
});
|
|
883
1403
|
}
|
|
884
1404
|
const trait = n("g", "trait", { transform: traitT }, [shape]);
|
|
885
|
-
const
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1405
|
+
const wardrobe = [];
|
|
1406
|
+
const w = (px) => f2(px * Math.min(st, 1.6));
|
|
1407
|
+
const line = {
|
|
1408
|
+
fill: "none",
|
|
1409
|
+
stroke: INK.ink,
|
|
1410
|
+
"stroke-width": w(5.25),
|
|
1411
|
+
"stroke-linecap": "round",
|
|
1412
|
+
"stroke-linejoin": "round"
|
|
1413
|
+
};
|
|
1414
|
+
if (who.eyewear !== "none") {
|
|
1415
|
+
const lx = sh.eyes[0][0] - spread;
|
|
1416
|
+
const rr = sh.eyes[1][0] + spread;
|
|
1417
|
+
const ey = sh.eyes[0][1];
|
|
1418
|
+
const gap = rr - lx;
|
|
1419
|
+
const erx = Math.min(rx + 9, (gap - 15) / 2);
|
|
1420
|
+
const ery = ry + 9;
|
|
1421
|
+
const glasses = who.eyewear === "glasses";
|
|
1422
|
+
const lensX = glasses ? erx : Math.min(rx + 11, (gap - 12) / 2);
|
|
1423
|
+
const top = glasses ? ey - 4 : ey - ry - 2;
|
|
1424
|
+
const frames = [
|
|
1425
|
+
n("path", "eyewear.bridge", {
|
|
1426
|
+
...line,
|
|
1427
|
+
d: `M${f2(lx + lensX)} ${f2(top)}Q${f2((lx + rr) / 2)} ${f2(top - (glasses ? 13 : 0))} ${f2(rr - lensX)} ${f2(top)}`
|
|
1428
|
+
})
|
|
1429
|
+
];
|
|
1430
|
+
for (const [i, cx] of [lx, rr].entries()) {
|
|
1431
|
+
const key = `eyewear.lens${i}`;
|
|
1432
|
+
frames.push(n("path", `${key}.arm`, {
|
|
1433
|
+
...line,
|
|
1434
|
+
d: i === 0 ? `M${f2(cx - lensX - 9)} ${f2(top + 2)}L${f2(cx - lensX)} ${f2(top)}` : `M${f2(cx + lensX)} ${f2(top)}L${f2(cx + lensX + 9)} ${f2(top + 2)}`
|
|
1435
|
+
}));
|
|
1436
|
+
if (glasses) {
|
|
1437
|
+
frames.push(n("ellipse", key, {
|
|
1438
|
+
...line,
|
|
1439
|
+
cx: f2(cx),
|
|
1440
|
+
cy: ey,
|
|
1441
|
+
rx: f2(erx),
|
|
1442
|
+
ry: f2(ery)
|
|
1443
|
+
}));
|
|
1444
|
+
frames.push(n("path", `${key}.glint`, {
|
|
1445
|
+
...line,
|
|
1446
|
+
stroke: INK.paper,
|
|
1447
|
+
"stroke-width": w(3),
|
|
1448
|
+
d: `M${f2(cx - erx * 0.65)} ${f2(ey - ery * 0.45)}Q${f2(cx - erx * 0.5)} ${f2(ey - ery * 0.8)} ${f2(cx - erx * 0.2)} ${f2(ey - ery * 0.86)}`
|
|
1449
|
+
}));
|
|
1450
|
+
} else {
|
|
1451
|
+
const h = ry * 2 + 9;
|
|
1452
|
+
frames.push(n("path", key, {
|
|
1453
|
+
d: `M${f2(cx - lensX)} ${f2(top)}H${f2(cx + lensX)}V${f2(top + h * 0.55)}Q${f2(cx + lensX)} ${f2(top + h)} ${f2(cx)} ${f2(top + h)}Q${f2(cx - lensX)} ${f2(top + h)} ${f2(cx - lensX)} ${f2(top + h * 0.55)}Z`,
|
|
1454
|
+
fill: INK.ink,
|
|
1455
|
+
stroke: INK.ink,
|
|
1456
|
+
"stroke-width": w(3),
|
|
1457
|
+
"stroke-linejoin": "round"
|
|
1458
|
+
}));
|
|
1459
|
+
frames.push(n("path", `${key}.glint`, {
|
|
1460
|
+
...line,
|
|
1461
|
+
stroke: INK.paper,
|
|
1462
|
+
"stroke-width": w(4),
|
|
1463
|
+
d: `M${f2(cx - lensX * 0.58)} ${f2(top + 19)}l8 -8m-1 15 6 -6`
|
|
1464
|
+
}), n("circle", `${key}.pin`, {
|
|
1465
|
+
cx: f2(cx + lensX * 0.55),
|
|
1466
|
+
cy: f2(top + h - 9),
|
|
1467
|
+
r: 1.7,
|
|
1468
|
+
fill: INK.paper
|
|
914
1469
|
}));
|
|
915
1470
|
}
|
|
916
|
-
|
|
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;
|
|
1471
|
+
}
|
|
1472
|
+
wardrobe.push(n("g", "eyewear", {}, frames));
|
|
949
1473
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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)
|
|
1474
|
+
if (who.headwear !== "none") {
|
|
1475
|
+
const side = who.trait === "ring" || who.trait === "dot" ? -1 : 1;
|
|
1476
|
+
const cap = who.headwear === "cap";
|
|
1477
|
+
const sprout = who.headwear === "sprout";
|
|
1478
|
+
const inset = sprout ? -6 : cap ? -8 : 0;
|
|
1479
|
+
const offset = sprout ? 34 : 40;
|
|
1480
|
+
let lo = 0;
|
|
1481
|
+
let hi = 160;
|
|
1482
|
+
for (let i = 0;i < 16; i++) {
|
|
1483
|
+
const mid2 = (lo + hi) / 2;
|
|
1484
|
+
const p = seat(sh.crown, inset, side * mid2);
|
|
1485
|
+
if (side * (p.x - 170) < offset)
|
|
1486
|
+
lo = mid2;
|
|
1487
|
+
else
|
|
1488
|
+
hi = mid2;
|
|
993
1489
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1490
|
+
const anchor = seat(sh.crown, inset, side * (lo + hi) / 2);
|
|
1491
|
+
const angle = clamp(anchor.a, -40, 40);
|
|
1492
|
+
const parts = [];
|
|
1493
|
+
if (cap) {
|
|
1494
|
+
parts.push(n("path", "headwear.crown", {
|
|
1495
|
+
...line,
|
|
1496
|
+
fill: INK.paper,
|
|
1497
|
+
d: "M-33 0C-35 -20 -20 -39 0 -39C20 -39 35 -20 33 0Z"
|
|
1498
|
+
}), n("path", "headwear.seam", {
|
|
1499
|
+
...line,
|
|
1500
|
+
"stroke-width": w(3.5),
|
|
1501
|
+
d: "M-4 -38Q9 -24 8 -1"
|
|
1502
|
+
}), n("path", "headwear.brim", {
|
|
1503
|
+
...line,
|
|
1504
|
+
"stroke-width": w(7),
|
|
1505
|
+
d: side > 0 ? "M-35 1Q8 4 52 3" : "M-52 3Q-8 4 35 1"
|
|
1506
|
+
}), n("circle", "headwear.button", {
|
|
1507
|
+
cx: 0,
|
|
1508
|
+
cy: -40,
|
|
1509
|
+
r: 3.5,
|
|
1510
|
+
fill: INK.ink
|
|
1511
|
+
}));
|
|
1512
|
+
} else if (sprout) {
|
|
1513
|
+
parts.push(n("path", "headwear.halo", {
|
|
1514
|
+
...line,
|
|
1515
|
+
stroke: fill,
|
|
1516
|
+
"stroke-width": w(11),
|
|
1517
|
+
d: `${STEM}${LEAF0}${LEAF1}`
|
|
1518
|
+
}), n("path", "headwear.stem", {
|
|
1519
|
+
...line,
|
|
1520
|
+
"stroke-width": w(4.5),
|
|
1521
|
+
d: STEM
|
|
1522
|
+
}), n("path", "headwear.leaf0", {
|
|
1523
|
+
...line,
|
|
1524
|
+
"stroke-width": w(4),
|
|
1525
|
+
fill: COLORS.mint.body,
|
|
1526
|
+
d: LEAF0
|
|
1527
|
+
}), n("path", "headwear.leaf1", {
|
|
1528
|
+
...line,
|
|
1529
|
+
"stroke-width": w(4),
|
|
1530
|
+
fill: COLORS.mint.deep,
|
|
1531
|
+
d: LEAF1
|
|
1532
|
+
}));
|
|
1533
|
+
} else {
|
|
1534
|
+
parts.push(n("path", "headwear.wings", {
|
|
1535
|
+
d: "M-3 0C-13 -16 -28 -23 -29 -10C-31 2 -26 19 -16 13L-3 4L3 4L16 13C26 19 31 2 29 -10C28 -23 13 -16 3 0Z",
|
|
1536
|
+
fill: INK.ink,
|
|
1537
|
+
stroke: fill,
|
|
1538
|
+
"stroke-width": w(6),
|
|
1539
|
+
"stroke-linejoin": "round",
|
|
1540
|
+
"paint-order": "stroke"
|
|
1541
|
+
}), n("ellipse", "headwear.knot", {
|
|
1542
|
+
cx: 0,
|
|
1543
|
+
cy: 1,
|
|
1544
|
+
rx: 6,
|
|
1545
|
+
ry: 8,
|
|
1546
|
+
fill: ACCESSORY_INK.knot
|
|
1547
|
+
}));
|
|
1548
|
+
}
|
|
1549
|
+
wardrobe.push(n("g", "headwear", {
|
|
1550
|
+
transform: `translate(${f2(anchor.x)} ${f2(anchor.y)}) rotate(${f2(sprout ? angle * 0.5 : angle)})`
|
|
1551
|
+
}, parts));
|
|
1552
|
+
}
|
|
1553
|
+
if (who.neckwear === "tie") {
|
|
1554
|
+
const skew = Math.abs(Math.sin(g("mk") * RAD2)) * mw;
|
|
1555
|
+
const bottom = Math.max(0, mt * kb, mb * kb) + skew + m.sw * st;
|
|
1556
|
+
wardrobe.push(n("g", "neckwear", {
|
|
1557
|
+
transform: `translate(${m.x} ${f2(m.y + g("my") + bottom + 8)})`,
|
|
1558
|
+
fill: INK.ink
|
|
1559
|
+
}, [
|
|
1560
|
+
n("path", "neckwear.knot", {
|
|
1561
|
+
d: "M-9 0Q-11 0 -9 4L-5 11Q0 15 5 11L9 4Q11 0 9 0Z"
|
|
1562
|
+
}),
|
|
1563
|
+
n("path", "neckwear.blade", {
|
|
1564
|
+
d: "M-4 15L-13 63Q-14 66 -11 69L0 79L11 69Q14 66 13 63L4 15Z"
|
|
1565
|
+
})
|
|
1566
|
+
]));
|
|
1567
|
+
}
|
|
1568
|
+
return [
|
|
1569
|
+
[bodyNode, ...cheeks, trait],
|
|
1570
|
+
[...eyes, ...brows, mouth, ...wardrobe]
|
|
1571
|
+
];
|
|
1009
1572
|
}
|
|
1010
1573
|
|
|
1011
1574
|
// src/svg.ts
|
|
1575
|
+
var wear = (o, base, s) => ACCESSORIES[s].includes(o[s]) ? o[s] : base[s];
|
|
1012
1576
|
function settle(name, o, live = false) {
|
|
1013
1577
|
const base = identify(name, { version: o.version });
|
|
1014
1578
|
const who = {
|
|
@@ -1019,17 +1583,24 @@ function settle(name, o, live = false) {
|
|
|
1019
1583
|
brows: o.brows ?? base.brows,
|
|
1020
1584
|
mouth: o.mouth ?? base.mouth,
|
|
1021
1585
|
cheeks: o.cheeks ?? base.cheeks,
|
|
1022
|
-
trait: o.trait ?? base.trait
|
|
1586
|
+
trait: o.trait ?? base.trait,
|
|
1587
|
+
eyewear: wear(o, base, "eyewear"),
|
|
1588
|
+
headwear: wear(o, base, "headwear"),
|
|
1589
|
+
neckwear: wear(o, base, "neckwear")
|
|
1023
1590
|
};
|
|
1024
1591
|
const pose = restPose(who, o.expression ?? "normal", o.gaze);
|
|
1592
|
+
const variant = VARIANTS.includes(o.variant) ? o.variant : "peek";
|
|
1025
1593
|
const look = {
|
|
1026
1594
|
size: o.size ?? 64,
|
|
1027
1595
|
frame: o.frame ?? "ink",
|
|
1028
1596
|
square: o.square ?? true,
|
|
1029
1597
|
riso: o.riso ?? false,
|
|
1030
|
-
live
|
|
1598
|
+
live,
|
|
1599
|
+
...variant !== "peek" && { variant }
|
|
1031
1600
|
};
|
|
1032
|
-
const
|
|
1601
|
+
const { eyewear, headwear, neckwear, ...legacy } = who;
|
|
1602
|
+
const wardrobe = eyewear !== "none" || headwear !== "none" || neckwear !== "none";
|
|
1603
|
+
const id = o.id ?? `peek-${fnv1a(JSON.stringify([wardrobe ? who : legacy, pose, look])).toString(36)}`;
|
|
1033
1604
|
return { who, pose, opts: { ...look, id, title: o.title ?? name } };
|
|
1034
1605
|
}
|
|
1035
1606
|
var ILLEGAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|