aaavatars 2.1.0 → 3.0.1

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/parts.js CHANGED
@@ -3,17 +3,111 @@ const STROKE = { fill: "none", strokeLinecap: "round", strokeWidth: 4 };
3
3
  export function mouthSlot(geometry, color) {
4
4
  return { color, width: geometry.eyeGap * 1.2, x: 50, y: geometry.eyeY + 17 };
5
5
  }
6
- export function EyePair({ eyes, gap, y }) {
7
- return (_jsx(_Fragment, { children: [50 - gap, 50 + gap].map((x) => {
8
- if (eyes === "dot")
9
- return _jsx("circle", { cx: x, cy: y, r: 7 }, x);
10
- if (eyes === "tall") {
11
- return _jsx("rect", { x: x - 5, y: y - 11, width: 10, height: 22, rx: 5 }, x);
12
- }
13
- if (eyes === "wide")
14
- return _jsx("ellipse", { cx: x, cy: y, rx: 11, ry: 7 }, x);
15
- return _jsx("ellipse", { cx: x, cy: y, rx: 8, ry: 10 }, x);
16
- }) }));
6
+ const EYE_HALF_HEIGHT = { dot: 7, round: 10, tall: 11, wide: 7 };
7
+ const EYE_HALF_WIDTH = { dot: 7, round: 8, tall: 5, wide: 11 };
8
+ const SEGMENTS = 12;
9
+ const CROSS = [
10
+ [0, -3.5],
11
+ [5, -8.5],
12
+ [8.5, -5],
13
+ [3.5, 0],
14
+ [8.5, 5],
15
+ [5, 8.5],
16
+ [0, 3.5],
17
+ [-5, 8.5],
18
+ [-8.5, 5],
19
+ [-3.5, 0],
20
+ [-8.5, -5],
21
+ [-5, -8.5],
22
+ ];
23
+ function arc(cx, cy, rx, ry, from, to) {
24
+ const k = (4 / 3) * Math.tan((to - from) / 4);
25
+ const point = (angle) => [cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)];
26
+ const tangent = (angle) => [-rx * Math.sin(angle), ry * Math.cos(angle)];
27
+ const [x0, y0] = point(from);
28
+ const [x1, y1] = point(to);
29
+ const [tx0, ty0] = tangent(from);
30
+ const [tx1, ty1] = tangent(to);
31
+ return [
32
+ [x0 + k * tx0, y0 + k * ty0],
33
+ [x1 - k * tx1, y1 - k * ty1],
34
+ [x1, y1],
35
+ ];
36
+ }
37
+ function line([x0, y0], [x1, y1]) {
38
+ return [
39
+ [x0 + (x1 - x0) / 3, y0 + (y1 - y0) / 3],
40
+ [x0 + ((x1 - x0) * 2) / 3, y0 + ((y1 - y0) * 2) / 3],
41
+ [x1, y1],
42
+ ];
43
+ }
44
+ function toPath(start, segments) {
45
+ const at = ([x, y]) => `${+x.toFixed(2)} ${+y.toFixed(2)}`;
46
+ return `M${at(start)}${segments.map((segment) => `C${segment.map(at).join(" ")}`).join("")}Z`;
47
+ }
48
+ const DEG = Math.PI / 180;
49
+ function ellipse(rx, ry) {
50
+ const step = (2 * Math.PI) / SEGMENTS;
51
+ return toPath([0, -ry], Array.from({ length: SEGMENTS }, (_, index) => arc(0, 0, rx, ry, -90 * DEG + index * step, -90 * DEG + (index + 1) * step)));
52
+ }
53
+ function stadium(r, half) {
54
+ const top = r - half;
55
+ const bottom = half - r;
56
+ return toPath([0, -half], [
57
+ arc(0, top, r, r, 270 * DEG, 315 * DEG),
58
+ arc(0, top, r, r, 315 * DEG, 360 * DEG),
59
+ line([r, top], [r, 0]),
60
+ line([r, 0], [r, bottom]),
61
+ ...[0, 45, 90, 135].map((from) => arc(0, bottom, r, r, from * DEG, (from + 45) * DEG)),
62
+ line([-r, bottom], [-r, 0]),
63
+ line([-r, 0], [-r, top]),
64
+ arc(0, top, r, r, 180 * DEG, 225 * DEG),
65
+ arc(0, top, r, r, 225 * DEG, 270 * DEG),
66
+ ]);
67
+ }
68
+ function polygon(vertices, dx = 0) {
69
+ const shifted = vertices.map(([x, y]) => [x + dx, y]);
70
+ const edges = shifted.map((from, index) => ({ from, to: shifted[(index + 1) % shifted.length], parts: 1 }));
71
+ for (let count = edges.length; count < SEGMENTS; count += 1) {
72
+ const longest = edges.reduce((best, edge) => Math.hypot(edge.to[0] - edge.from[0], edge.to[1] - edge.from[1]) / edge.parts >
73
+ Math.hypot(best.to[0] - best.from[0], best.to[1] - best.from[1]) / best.parts
74
+ ? edge
75
+ : best);
76
+ longest.parts += 1;
77
+ }
78
+ const anchors = edges.flatMap(({ from, to, parts }) => Array.from({ length: parts }, (_, index) => [
79
+ from[0] + ((to[0] - from[0]) * index) / parts,
80
+ from[1] + ((to[1] - from[1]) * index) / parts,
81
+ ]));
82
+ const cx = shifted.reduce((sum, [x]) => sum + x, 0) / shifted.length;
83
+ const cy = shifted.reduce((sum, [, y]) => sum + y, 0) / shifted.length;
84
+ const offTop = ([x, y]) => Math.abs(Math.atan2(x - cx, cy - y));
85
+ const first = anchors.reduce((best, anchor, index) => (offTop(anchor) < offTop(anchors[best]) ? index : best), 0);
86
+ const ordered = [...anchors.slice(first), ...anchors.slice(0, first)];
87
+ return toPath(ordered[0], ordered.map((anchor, index) => line(anchor, ordered[(index + 1) % ordered.length])));
88
+ }
89
+ export function eyePath(eyes, pose, side, gap) {
90
+ if (pose === "cross")
91
+ return polygon(CROSS);
92
+ if (pose === "cursor")
93
+ return polygon([[-2, -13.5], [2, -13.5], [2, 13.5], [-2, 13.5]], -side * gap);
94
+ if (pose === "prompt" && side === -1) {
95
+ return polygon([[-8, -14], [0, -14], [10, 0], [0, 14], [-8, 14], [2, 0]], gap - 11.5);
96
+ }
97
+ if (pose === "prompt")
98
+ return polygon([[-9, 8.5], [9, 8.5], [9, 14], [-9, 14]], 10.5 - gap);
99
+ if (eyes === "tall")
100
+ return stadium(5, 11);
101
+ return ellipse(EYE_HALF_WIDTH[eyes], EYE_HALF_HEIGHT[eyes]);
102
+ }
103
+ function LidMask({ eyes, id, side }) {
104
+ const half = EYE_HALF_HEIGHT[eyes];
105
+ return (_jsxs("mask", { id: id, maskUnits: "userSpaceOnUse", x: -20, y: -20, width: 40, height: 40, children: [_jsx("rect", { x: -20, y: -20, width: 40, height: 40, fill: "#fff" }), _jsx("rect", { className: `aaa-lid aaa-lid-top aaa-lid-${side}`, "data-h": half, x: -20, y: -half - 20, width: 40, height: 20, fill: "#000" }), _jsx("rect", { className: `aaa-lid aaa-lid-bottom aaa-lid-${side}`, "data-h": half, x: -20, y: half, width: 40, height: 20, fill: "#000" })] }));
106
+ }
107
+ export function EyePair({ eyes, gap, id, pose, y, }) {
108
+ const h = EYE_HALF_HEIGHT[eyes];
109
+ const w = EYE_HALF_WIDTH[eyes];
110
+ return (_jsx(_Fragment, { children: ["l", "r"].map((side) => (_jsx("g", { transform: `translate(${side === "l" ? 50 - gap : 50 + gap} ${y})`, children: _jsxs("g", { className: `aaa-eye aaa-eye-${side}`, "data-gap": gap, "data-h": h, "data-shape": pose, "data-w": w, style: { "--aaa-eye-h": h, "--aaa-eye-w": w }, children: [_jsx(LidMask, { eyes: eyes, id: `${id}-lid-${side}`, side: side }), _jsx("g", { mask: `url(#${id}-lid-${side})`, children: _jsx("path", { className: "aaa-shape", d: eyePath(eyes, pose, side === "l" ? -1 : 1, gap) }) })] }) }, side))) }));
17
111
  }
18
112
  export function MouthShape({ mouth, slot }) {
19
113
  const half = slot.width / 2;
@@ -28,11 +122,11 @@ export function MouthShape({ mouth, slot }) {
28
122
  }
29
123
  return null;
30
124
  }
31
- export const MOODS = ["happy", "sad", "o", "small"];
125
+ export const MOODS = ["happy", "sad", "o", "small", "flat"];
32
126
  export function MoodMouths({ slot }) {
33
127
  const half = slot.width / 2;
34
128
  const { color, x, y } = slot;
35
- return (_jsxs(_Fragment, { children: [_jsx("g", { className: "aaa-mood aaa-mood-happy", children: _jsx("path", { d: `M${x - half} ${y - 3}Q${x} ${y + 12} ${x + half} ${y - 3}Z`, fill: color }) }), _jsx("g", { className: "aaa-mood aaa-mood-sad", children: _jsx("path", { d: `M${x - half * 0.8} ${y + 4}Q${x} ${y - 5} ${x + half * 0.8} ${y + 4}`, stroke: color, ...STROKE }) }), _jsx("g", { className: "aaa-mood aaa-mood-o", children: _jsx("ellipse", { cx: x, cy: y + 1, rx: 5, ry: 6.5, fill: color }) }), _jsx("g", { className: "aaa-mood aaa-mood-small", children: _jsx("path", { d: `M${x + 1} ${y}H${x + 9}`, stroke: color, ...STROKE }) })] }));
129
+ return (_jsxs(_Fragment, { children: [_jsx("g", { className: "aaa-mood aaa-mood-happy", children: _jsx("path", { d: `M${x - half} ${y - 3}Q${x} ${y + 12} ${x + half} ${y - 3}Z`, fill: color }) }), _jsx("g", { className: "aaa-mood aaa-mood-sad", children: _jsx("path", { d: `M${x - half * 0.8} ${y + 4}Q${x} ${y - 5} ${x + half * 0.8} ${y + 4}`, stroke: color, ...STROKE }) }), _jsx("g", { className: "aaa-mood aaa-mood-o", children: _jsx("ellipse", { cx: x, cy: y + 1, rx: 5, ry: 6.5, fill: color }) }), _jsx("g", { className: "aaa-mood aaa-mood-small", children: _jsx("path", { d: `M${x + 1} ${y}H${x + 9}`, stroke: color, ...STROKE }) }), _jsx("g", { className: "aaa-mood aaa-mood-flat", children: _jsx("path", { d: `M${x - half * 0.5} ${y + 1}H${x + half * 0.5}`, stroke: color, ...STROKE }) })] }));
36
130
  }
37
131
  export function AccessoryShape(props) {
38
132
  if (props.accessory === "none")
package/dist/pointer.d.ts CHANGED
@@ -1 +1,2 @@
1
+ export declare function restartGazeCentred(svg: SVGSVGElement): void;
1
2
  export declare function watchPointer(svg: SVGSVGElement): () => void;
package/dist/pointer.js CHANGED
@@ -1,23 +1,62 @@
1
- const LOOK_RANGE = 7;
2
- const FALLOFF = 240;
1
+ const LOOK_X = 7;
2
+ const LOOK_Y = 5;
3
+ const HEAD_SHIFT = 2.2;
4
+ const HEAD_TILT = 8;
5
+ const STALE_MS = 900;
3
6
  const watchers = new Set();
4
7
  let pointer = null;
5
8
  let frame = 0;
9
+ let staleTimer;
10
+ const PROPERTIES = [
11
+ "--aaa-look-x",
12
+ "--aaa-look-y",
13
+ "--aaa-look-sx",
14
+ "--aaa-head-x",
15
+ "--aaa-head-y",
16
+ "--aaa-head-r",
17
+ ];
18
+ const CENTRED_PHASES = [0, 0.41, 0.7];
19
+ export function restartGazeCentred(svg) {
20
+ const phase = Number.parseFloat(svg.style.getPropertyValue("--aaa-phase")) || 0;
21
+ const cohort = Math.min(Math.floor(phase * CENTRED_PHASES.length), CENTRED_PHASES.length - 1);
22
+ const glancing = svg.dataset.gaze === "follow" || svg.dataset.gaze === "wander";
23
+ svg.style.setProperty("--aaa-gaze-phase", glancing ? String(CENTRED_PHASES[cohort]) : "0");
24
+ }
25
+ function release(svg) {
26
+ if (!svg.hasAttribute("data-pointer"))
27
+ return;
28
+ for (const property of PROPERTIES)
29
+ svg.style.removeProperty(property);
30
+ restartGazeCentred(svg);
31
+ svg.removeAttribute("data-pointer");
32
+ }
33
+ function clamp(value, limit) {
34
+ return Math.max(-limit, Math.min(limit, value));
35
+ }
6
36
  function aim() {
7
37
  frame = 0;
8
- const targets = [...watchers].map((svg) => ({ svg, bounds: svg.getBoundingClientRect() }));
38
+ const targets = [...watchers]
39
+ .filter((svg) => !svg.hasAttribute("data-offscreen"))
40
+ .map((svg) => ({ svg, bounds: svg.getBoundingClientRect() }));
9
41
  for (const { svg, bounds } of targets) {
10
42
  if (!pointer || bounds.bottom < 0 || bounds.top > window.innerHeight) {
11
- svg.style.removeProperty("--aaa-look-x");
12
- svg.style.removeProperty("--aaa-look-y");
43
+ release(svg);
13
44
  continue;
14
45
  }
15
- const dx = pointer.x - (bounds.left + bounds.width / 2);
16
- const dy = pointer.y - (bounds.top + bounds.height / 2);
17
- const distance = Math.hypot(dx, dy) || 1;
18
- const reach = (LOOK_RANGE * Math.min(distance, FALLOFF)) / FALLOFF;
19
- svg.style.setProperty("--aaa-look-x", `${((dx / distance) * reach).toFixed(2)}px`);
20
- svg.style.setProperty("--aaa-look-y", `${((dy / distance) * reach).toFixed(2)}px`);
46
+ const size = Math.max(bounds.width, 1);
47
+ const dx = (pointer.x - (bounds.left + bounds.width / 2)) / size;
48
+ const dy = (pointer.y - (bounds.top + bounds.height / 2)) / size;
49
+ const x = clamp(dx * LOOK_X, LOOK_X);
50
+ const y = clamp(dy * LOOK_Y * 0.9, LOOK_Y);
51
+ const nx = x / LOOK_X;
52
+ const ny = y / LOOK_Y;
53
+ svg.setAttribute("data-pointer", "");
54
+ svg.style.setProperty("--aaa-look-x", `${x.toFixed(2)}px`);
55
+ svg.style.setProperty("--aaa-look-y", `${y.toFixed(2)}px`);
56
+ svg.style.setProperty("--aaa-look-sx", (1 - 0.06 * nx * nx).toFixed(3));
57
+ svg.style.setProperty("--aaa-head-x", `${(nx * HEAD_SHIFT).toFixed(2)}px`);
58
+ svg.style.setProperty("--aaa-head-y", `${(ny * HEAD_SHIFT).toFixed(2)}px`);
59
+ svg.style.setProperty("--aaa-head-r", `${(nx * ny * HEAD_TILT).toFixed(2)}deg`);
21
60
  }
22
61
  }
23
62
  function schedule() {
@@ -29,8 +68,11 @@ function handleMove(event) {
29
68
  return;
30
69
  pointer = { x: event.clientX, y: event.clientY };
31
70
  schedule();
71
+ clearTimeout(staleTimer);
72
+ staleTimer = setTimeout(handleLeave, STALE_MS);
32
73
  }
33
74
  function handleLeave() {
75
+ clearTimeout(staleTimer);
34
76
  pointer = null;
35
77
  schedule();
36
78
  }
@@ -42,11 +84,11 @@ export function watchPointer(svg) {
42
84
  watchers.add(svg);
43
85
  return () => {
44
86
  watchers.delete(svg);
45
- svg.style.removeProperty("--aaa-look-x");
46
- svg.style.removeProperty("--aaa-look-y");
87
+ release(svg);
47
88
  if (watchers.size === 0) {
48
89
  window.removeEventListener("pointermove", handleMove);
49
90
  document.documentElement.removeEventListener("pointerleave", handleLeave);
91
+ clearTimeout(staleTimer);
50
92
  pointer = null;
51
93
  }
52
94
  };
package/dist/styles.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const AVATAR_CSS = "\n.aaa-body-motion,.aaa-react,.aaa-eyes-react,.aaa-eyes-motion,.aaa-blink,.aaa-squish{transform-box:fill-box;transform-origin:center}\n.aaa-mouth-rest,.aaa-mood{transform-box:fill-box;transform-origin:center;transition:opacity .2s,transform .2s}\n.aaa-mood{opacity:0}\n.aaa-accessory{transform-box:fill-box;transform-origin:50% 100%;transition:transform .3s ease-out}\n.aaa-accessory[data-accessory=glasses],.aaa-accessory[data-accessory=bowtie]{transform-origin:center}\n.aaa-squish{transition:transform .45s cubic-bezier(.34,1.56,.64,1)}\n.aaa-squish[data-dragging]{transition:none}\n.aaa[data-interactive]{cursor:grab;touch-action:none}\n.aaa[data-interactive]:active{cursor:grabbing}\n.aaa-look{transform:translate(var(--aaa-look-x,0px),var(--aaa-look-y,0px));transition:transform .25s ease-out}\n.aaa-blink{animation:aaa-blink 7s infinite}\n.aaa[data-status=idle] .aaa-body-motion{animation:aaa-drift calc(11s*var(--aaa-tempo,1)) ease-in-out calc(-11s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=thinking] .aaa-body-motion{animation:aaa-sway calc(3s*var(--aaa-tempo,1)) ease-in-out calc(-3s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=thinking] .aaa-eyes-motion{transform:translate(-5px,-6px);animation:aaa-ponder calc(2.4s*var(--aaa-tempo,1)) ease-in-out calc(-2.4s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=working] .aaa-body-motion{animation:aaa-bob calc(0.9s*var(--aaa-tempo,1)) ease-in-out calc(-0.9s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=working] .aaa-eyes-motion{animation:aaa-scan calc(1.1s*var(--aaa-tempo,1)) ease-in-out calc(-1.1s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=waiting] .aaa-body-motion{animation:aaa-hop calc(1.6s*var(--aaa-tempo,1)) ease-out calc(-1.6s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=waiting] .aaa-eyes-motion{transform:scale(1.18)}\n.aaa[data-status=error] .aaa-body-motion{transform:rotate(-10deg)}\n.aaa[data-status=error] .aaa-eyes-motion{transform:scaleY(.25)}\n.aaa[data-status=error] .aaa-blink{animation:none}\n.aaa[data-status=sleeping] .aaa-body-motion{animation:aaa-breathe calc(4.2s*var(--aaa-tempo,1)) ease-in-out calc(-4.2s*var(--aaa-phase,0)) infinite}\n.aaa[data-status=sleeping] .aaa-eyes-motion{transform:translateY(2px) scaleY(.12)}\n.aaa[data-status=sleeping] .aaa-blink{animation:none}\n.aaa[data-status=working] .aaa-antenna-tip{animation:aaa-signal .9s ease-in-out infinite}\n.aaa[data-status=thinking] .aaa-mouth-rest{opacity:0}\n.aaa[data-status=thinking] .aaa-mood-small{opacity:1}\n.aaa[data-status=thinking] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){animation:aaa-acc-sway 2.4s ease-in-out infinite}\n.aaa[data-status=thinking] .aaa-accessory[data-accessory=glasses]{animation:aaa-glasses-think 2.4s ease-in-out infinite}\n.aaa[data-status=working] .aaa-mouth-rest{transform:scaleX(.7)}\n.aaa[data-status=working] .aaa-accessory[data-accessory=bowtie]{animation:aaa-wobble .9s ease-in-out infinite}\n.aaa[data-status=waiting] .aaa-mouth-rest{opacity:0}\n.aaa[data-status=waiting] .aaa-mood-happy{opacity:1}\n.aaa[data-status=waiting] .aaa-accessory:not([data-accessory=glasses]){animation:aaa-wobble 1.6s ease-in-out infinite}\n.aaa[data-status=error] .aaa-mouth-rest{opacity:0}\n.aaa[data-status=error] .aaa-mood-sad{opacity:1}\n.aaa[data-status=error] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){transform:rotate(28deg)}\n.aaa[data-status=error] .aaa-accessory:is([data-accessory=hat],[data-accessory=glasses]){transform:translateY(3px) rotate(8deg)}\n.aaa[data-status=error] .aaa-accessory[data-accessory=bowtie]{transform:rotate(18deg)}\n.aaa[data-status=sleeping] .aaa-mouth-rest{opacity:0}\n.aaa[data-status=sleeping] .aaa-mood-o{opacity:1;animation:aaa-snore 4.2s ease-in-out infinite}\n.aaa[data-status=sleeping] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){transform:rotate(-35deg)}\n.aaa[data-status=sleeping] .aaa-accessory:is([data-accessory=hat],[data-accessory=glasses]){transform:translateY(5px)}\n.aaa[data-blink=off] .aaa-blink{animation:none}\n.aaa[data-motion=off] *{animation:none!important}\n@keyframes aaa-blink{0%,28%,32%,90%,94%,100%{transform:scaleY(1)}30%,92%{transform:scaleY(.1)}}\n@keyframes aaa-drift{0%,100%{transform:translate(0,0) rotate(0deg) scale(1)}20%{transform:translate(4px,-3px) rotate(5deg) scale(1.02,.98)}45%{transform:translate(-2px,-5px) rotate(-3deg) scale(.98,1.02)}70%{transform:translate(-5px,-1px) rotate(-6deg) scale(1.02,.98)}85%{transform:translate(-1px,1px) rotate(2deg) scale(1)}}\n@keyframes aaa-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}\n@keyframes aaa-sway{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(4deg)}}\n@keyframes aaa-ponder{0%,100%{transform:translate(-5px,-6px)}50%{transform:translate(5px,-6px)}}\n@keyframes aaa-scan{0%,100%{transform:translateX(-5px)}50%{transform:translateX(5px)}}\n@keyframes aaa-breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.035,.97)}}\n@keyframes aaa-signal{0%,100%{opacity:1}50%{opacity:.25}}\n@keyframes aaa-acc-sway{0%,100%{transform:rotate(-8deg)}50%{transform:rotate(8deg)}}\n@keyframes aaa-wobble{0%,100%{transform:rotate(-12deg)}50%{transform:rotate(12deg)}}\n@keyframes aaa-glasses-think{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}\n@keyframes aaa-snore{0%,100%{transform:scale(.7)}50%{transform:scale(1.1)}}\n@keyframes aaa-hop{0%,55%,100%{transform:translateY(0)}68%{transform:translateY(-7px)}80%{transform:translateY(0)}90%{transform:translateY(-3px)}}\n@media (prefers-reduced-motion:reduce){.aaa *{animation:none!important;transition:none!important}}\n";
1
+ export declare const AVATAR_CSS: string;
package/dist/styles.js CHANGED
@@ -1,61 +1,255 @@
1
+ const EASE_OUT = "cubic-bezier(.22,1,.36,1)";
2
+ const EASE_SPRING = "cubic-bezier(.34,1.56,.64,1)";
3
+ const EASE_SMOOTH = "cubic-bezier(.37,0,.63,1)";
4
+ function gazeTransform(x, y) {
5
+ const squash = 1 - 0.06 * Math.min((x / 6) ** 2, 1);
6
+ return `translate(${x}px,${y}px) scaleX(${squash.toFixed(3)})`;
7
+ }
8
+ function glances(name, points, move) {
9
+ const frames = points.flatMap(([percent, x, y], index) => {
10
+ const next = points[index + 1];
11
+ const transform = gazeTransform(x, y);
12
+ const arrive = `${percent}%{transform:${transform};animation-timing-function:linear}`;
13
+ if (!next)
14
+ return [arrive];
15
+ const leave = `${Math.max(percent, next[0] - move)}%{transform:${transform};animation-timing-function:${EASE_OUT}}`;
16
+ return [arrive, leave];
17
+ });
18
+ return `@keyframes ${name}{${frames.join("")}}`;
19
+ }
20
+ function orbit(name, side) {
21
+ const frames = Array.from({ length: 17 }, (_, index) => {
22
+ const angle = (index / 16) * Math.PI * 4;
23
+ const cos = Math.cos(angle);
24
+ const depth = 1 - side * 0.08 * Math.sin(angle);
25
+ const foreshorten = 0.72 + 0.28 * Math.abs(cos);
26
+ const shift = (-side * (1 - cos)).toFixed(4);
27
+ return `${(index / 16) * 100}%{transform:translateX(calc(var(--aaa-gap)*${shift}px)) scale(${(foreshorten * depth).toFixed(3)},${depth.toFixed(3)})}`;
28
+ });
29
+ return `@keyframes ${name}{${frames.join("")}}`;
30
+ }
31
+ function blinks(percents, half) {
32
+ const open = [0, 100, ...percents.flatMap((at) => [at - half, at + half])];
33
+ return `@keyframes aaa-blink{${open.map((at) => `${at.toFixed(2)}%`).join(",")}{transform:scaleY(1)}${percents.map((at) => `${at}%`).join(",")}{transform:scaleY(.08)}}`;
34
+ }
35
+ const lid = (amount) => `translateY(calc(var(--aaa-eye-h)*${amount}px))`;
36
+ const HEART = (side) => `translateX(calc((var(--aaa-gap) + 3.6)*${-side}px)) rotate(${side * 42}deg) scale(calc(7.5/var(--aaa-eye-w)),calc(11.5/var(--aaa-eye-h)))`;
37
+ const timeline = (name, total, stops) => `@keyframes ${name}{${stops.map(([ms, transform]) => `${+((ms / total) * 100).toFixed(3)}%{transform:${transform};animation-timing-function:${EASE_SMOOTH}}`).join("")}}`;
38
+ const peek = (amount) => `translateY(calc(var(--aaa-eye-h)*${amount}px))`;
39
+ const loop = (seconds, easing = "ease-in-out") => `calc(${seconds}s*var(--aaa-tempo,1)) ${easing} calc(-${seconds}s*var(--aaa-phase,0)) infinite`;
40
+ const gazeLoop = (seconds) => `calc(${seconds}s*var(--aaa-tempo,1)) linear calc(-${seconds}s*var(--aaa-gaze-phase,var(--aaa-phase,0))) infinite`;
1
41
  export const AVATAR_CSS = `
2
- .aaa-body-motion,.aaa-react,.aaa-eyes-react,.aaa-eyes-motion,.aaa-blink,.aaa-squish{transform-box:fill-box;transform-origin:center}
42
+ .aaa-body-motion,.aaa-react,.aaa-eyes-react,.aaa-gaze,.aaa-eyes-motion,.aaa-blink,.aaa-squish,.aaa-head,.aaa-look,.aaa-eye{transform-box:fill-box;transform-origin:center}
3
43
  .aaa-mouth-rest,.aaa-mood{transform-box:fill-box;transform-origin:center;transition:opacity .2s,transform .2s}
4
44
  .aaa-mood{opacity:0}
5
45
  .aaa-accessory{transform-box:fill-box;transform-origin:50% 100%;transition:transform .3s ease-out}
6
46
  .aaa-accessory[data-accessory=glasses],.aaa-accessory[data-accessory=bowtie]{transform-origin:center}
7
- .aaa-squish{transition:transform .45s cubic-bezier(.34,1.56,.64,1)}
47
+ .aaa-squish{transition:transform .45s ${EASE_SPRING}}
8
48
  .aaa-squish[data-dragging]{transition:none}
9
49
  .aaa[data-interactive]{cursor:grab;touch-action:none}
10
50
  .aaa[data-interactive]:active{cursor:grabbing}
11
- .aaa-look{transform:translate(var(--aaa-look-x,0px),var(--aaa-look-y,0px));transition:transform .25s ease-out}
12
- .aaa-blink{animation:aaa-blink 7s infinite}
13
- .aaa[data-status=idle] .aaa-body-motion{animation:aaa-drift calc(11s*var(--aaa-tempo,1)) ease-in-out calc(-11s*var(--aaa-phase,0)) infinite}
14
- .aaa[data-status=thinking] .aaa-body-motion{animation:aaa-sway calc(3s*var(--aaa-tempo,1)) ease-in-out calc(-3s*var(--aaa-phase,0)) infinite}
15
- .aaa[data-status=thinking] .aaa-eyes-motion{transform:translate(-5px,-6px);animation:aaa-ponder calc(2.4s*var(--aaa-tempo,1)) ease-in-out calc(-2.4s*var(--aaa-phase,0)) infinite}
16
- .aaa[data-status=working] .aaa-body-motion{animation:aaa-bob calc(0.9s*var(--aaa-tempo,1)) ease-in-out calc(-0.9s*var(--aaa-phase,0)) infinite}
17
- .aaa[data-status=working] .aaa-eyes-motion{animation:aaa-scan calc(1.1s*var(--aaa-tempo,1)) ease-in-out calc(-1.1s*var(--aaa-phase,0)) infinite}
18
- .aaa[data-status=waiting] .aaa-body-motion{animation:aaa-hop calc(1.6s*var(--aaa-tempo,1)) ease-out calc(-1.6s*var(--aaa-phase,0)) infinite}
19
- .aaa[data-status=waiting] .aaa-eyes-motion{transform:scale(1.18)}
20
- .aaa[data-status=error] .aaa-body-motion{transform:rotate(-10deg)}
21
- .aaa[data-status=error] .aaa-eyes-motion{transform:scaleY(.25)}
22
- .aaa[data-status=error] .aaa-blink{animation:none}
23
- .aaa[data-status=sleeping] .aaa-body-motion{animation:aaa-breathe calc(4.2s*var(--aaa-tempo,1)) ease-in-out calc(-4.2s*var(--aaa-phase,0)) infinite}
24
- .aaa[data-status=sleeping] .aaa-eyes-motion{transform:translateY(2px) scaleY(.12)}
25
- .aaa[data-status=sleeping] .aaa-blink{animation:none}
26
- .aaa[data-status=working] .aaa-antenna-tip{animation:aaa-signal .9s ease-in-out infinite}
27
- .aaa[data-status=thinking] .aaa-mouth-rest{opacity:0}
28
- .aaa[data-status=thinking] .aaa-mood-small{opacity:1}
29
- .aaa[data-status=thinking] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){animation:aaa-acc-sway 2.4s ease-in-out infinite}
30
- .aaa[data-status=thinking] .aaa-accessory[data-accessory=glasses]{animation:aaa-glasses-think 2.4s ease-in-out infinite}
31
- .aaa[data-status=working] .aaa-mouth-rest{transform:scaleX(.7)}
32
- .aaa[data-status=working] .aaa-accessory[data-accessory=bowtie]{animation:aaa-wobble .9s ease-in-out infinite}
33
- .aaa[data-status=waiting] .aaa-mouth-rest{opacity:0}
34
- .aaa[data-status=waiting] .aaa-mood-happy{opacity:1}
35
- .aaa[data-status=waiting] .aaa-accessory:not([data-accessory=glasses]){animation:aaa-wobble 1.6s ease-in-out infinite}
36
- .aaa[data-status=error] .aaa-mouth-rest{opacity:0}
37
- .aaa[data-status=error] .aaa-mood-sad{opacity:1}
38
- .aaa[data-status=error] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){transform:rotate(28deg)}
39
- .aaa[data-status=error] .aaa-accessory:is([data-accessory=hat],[data-accessory=glasses]){transform:translateY(3px) rotate(8deg)}
40
- .aaa[data-status=error] .aaa-accessory[data-accessory=bowtie]{transform:rotate(18deg)}
41
- .aaa[data-status=sleeping] .aaa-mouth-rest{opacity:0}
42
- .aaa[data-status=sleeping] .aaa-mood-o{opacity:1;animation:aaa-snore 4.2s ease-in-out infinite}
43
- .aaa[data-status=sleeping] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){transform:rotate(-35deg)}
44
- .aaa[data-status=sleeping] .aaa-accessory:is([data-accessory=hat],[data-accessory=glasses]){transform:translateY(5px)}
51
+ .aaa-look{transform:translate(var(--aaa-look-x,0px),var(--aaa-look-y,0px)) scaleX(var(--aaa-look-sx,1));transition:transform .28s ${EASE_OUT}}
52
+ .aaa-head{transform:translate(var(--aaa-head-x,0px),var(--aaa-head-y,0px)) rotate(var(--aaa-head-r,0deg));transition:transform .6s ${EASE_SPRING}}
53
+ .aaa-eyes-motion{transition:transform .32s cubic-bezier(.16,1,.3,1)}
54
+ .aaa-eye{transition:transform .32s cubic-bezier(.16,1,.3,1)}
55
+ .aaa-blink{animation:aaa-blink 14s linear infinite}
56
+ .aaa-lid{transform-box:fill-box;transition:transform .32s cubic-bezier(.16,1,.3,1)}
57
+ .aaa-lid-top{transform-origin:50% 100%}
58
+ .aaa-lid-bottom{transform-origin:50% 0}
59
+ .aaa-shape{transition:d .32s cubic-bezier(.16,1,.3,1)}
60
+ .aaa-eye:not([data-shape=eye]) .aaa-lid-top{transform:translateY(-12px)}
61
+ .aaa-eye:not([data-shape=eye]) .aaa-lid-bottom{transform:translateY(12px)}
62
+ .aaa[data-activity=idle] .aaa-body-motion{animation:aaa-drift ${loop(11)}}
63
+ .aaa[data-activity=thinking] .aaa-body-motion{animation:aaa-sway ${loop(3)}}
64
+ .aaa[data-activity=coding] .aaa-body-motion{animation:aaa-bob ${loop(1.6)}}
65
+ .aaa[data-activity=coding][data-expression=neutral] .aaa-blink{animation:none}
66
+ .aaa[data-activity=coding][data-expression=neutral] .aaa-eye-r{animation:aaa-caret 1.06s steps(1) .32s infinite}
67
+ .aaa[data-activity=working] .aaa-body-motion{animation:aaa-bob ${loop(0.9)}}
68
+ .aaa[data-activity=waiting] .aaa-eye-l{animation:aaa-orbit-l 3.2s linear .32s infinite}
69
+ .aaa[data-activity=waiting] .aaa-eye-r{animation:aaa-orbit-r 3.2s linear .32s infinite}
70
+ .aaa[data-activity=waiting][data-expression=neutral] .aaa-blink{animation:none}
71
+ .aaa[data-activity=asking] .aaa-body-motion{transform:rotate(-7deg)}
72
+ .aaa[data-activity=asking][data-expression=neutral] .aaa-eye-r{transform:scale(1.25)}
73
+ .aaa[data-activity=stuck] .aaa-body-motion{transform:rotate(-10deg)}
74
+ .aaa[data-activity=stuck][data-expression=neutral] .aaa-blink{animation:none}
75
+ .aaa[data-activity=sleeping] .aaa-body-motion{animation:aaa-breathe ${loop(4.2)}}
76
+ .aaa[data-activity=sleeping] .aaa-lid-top{transform:${lid(".85")}}
77
+ .aaa[data-activity=sleeping] .aaa-lid-bottom{transform:${lid("-.85")}}
78
+ .aaa[data-activity=sleeping] .aaa-react{transform:translateY(3px)}
79
+ .aaa[data-activity=sleeping][data-expression=neutral] .aaa-blink{animation:none}
80
+ .aaa[data-activity=listening] .aaa-body-motion{animation:aaa-nod ${loop(2.8)}}
81
+ .aaa[data-activity=listening][data-expression=neutral] .aaa-eye{animation:aaa-caret 1.06s steps(1) .32s infinite}
82
+ .aaa[data-activity=listening][data-expression=neutral] .aaa-blink{animation:none}
83
+ .aaa[data-activity=reviewing] .aaa-lid-top{transform:${lid(".78")}}
84
+ .aaa[data-activity=reviewing] .aaa-lid-bottom{transform:${lid("-.78")}}
85
+ .aaa[data-activity=reviewing][data-expression=neutral] .aaa-blink{animation:none}
86
+ .aaa[data-activity=reviewing] .aaa-eye{animation:aaa-inspect ${loop(6.08, "linear")}}
87
+ .aaa[data-activity=reviewing] .aaa-body-motion{animation:aaa-inspect-head ${loop(6.08, "linear")}}
88
+ .aaa[data-activity=speaking] .aaa-body-motion{animation:aaa-bob ${loop(1.4)}}
89
+ .aaa[data-activity=speaking] .aaa-mouth-rest{animation:aaa-talk .32s ease-in-out infinite}
90
+ .aaa[data-gaze=follow]:not([data-pointer]) .aaa-gaze,.aaa[data-gaze=wander] .aaa-gaze{animation:aaa-saccade ${gazeLoop(16)}}
91
+ .aaa[data-gaze=ponder] .aaa-gaze{animation:aaa-ponder ${gazeLoop(3.6)}}
92
+ .aaa[data-gaze=scan] .aaa-gaze{animation:aaa-scan ${gazeLoop(1.6)}}
93
+ .aaa[data-gaze=read] .aaa-gaze{animation:aaa-read ${gazeLoop(4.8)}}
94
+ .aaa[data-gaze=down] .aaa-gaze{animation:aaa-down ${gazeLoop(7)}}
95
+ .aaa[data-gaze=away] .aaa-gaze{animation:aaa-away ${gazeLoop(9)}}
96
+ .aaa[data-activity=searching] .aaa-body-motion{transform:translateY(1px) scale(1.04)}
97
+ .aaa[data-activity=searching][data-expression=neutral] .aaa-eye{animation:aaa-search ${loop(2.6, "linear")}}
98
+ .aaa[data-activity=writing] .aaa-body-motion{animation:aaa-bob ${loop(2.4)}}
99
+ .aaa[data-activity=working] .aaa-antenna-tip{animation:aaa-signal .9s ease-in-out infinite}
100
+ .aaa[data-activity=thinking][data-expression=neutral] .aaa-mouth-rest{opacity:0}
101
+ .aaa[data-activity=thinking][data-expression=neutral] .aaa-mood-small{opacity:1}
102
+ .aaa[data-activity=thinking] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){animation:aaa-acc-sway 2.4s ease-in-out infinite}
103
+ .aaa[data-activity=thinking] .aaa-accessory[data-accessory=glasses]{animation:aaa-glasses-think 2.4s ease-in-out infinite}
104
+ .aaa[data-activity=working] .aaa-mouth-rest{transform:scaleX(.7)}
105
+ .aaa[data-activity=working] .aaa-accessory[data-accessory=bowtie]{animation:aaa-wobble .9s ease-in-out infinite}
106
+ .aaa[data-activity=waiting][data-expression=neutral] .aaa-mouth-rest{opacity:0}
107
+ .aaa[data-activity=waiting][data-expression=neutral] .aaa-mood-happy{opacity:1}
108
+ .aaa[data-activity=waiting] .aaa-accessory:not([data-accessory=glasses]){animation:aaa-wobble 1.6s ease-in-out infinite}
109
+ .aaa[data-activity=stuck][data-expression=neutral] .aaa-mouth-rest{opacity:0}
110
+ .aaa[data-activity=stuck][data-expression=neutral] .aaa-mood-sad{opacity:1}
111
+ .aaa[data-activity=stuck] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){transform:rotate(28deg)}
112
+ .aaa[data-activity=stuck] .aaa-accessory:is([data-accessory=hat],[data-accessory=glasses]){transform:translateY(3px) rotate(8deg)}
113
+ .aaa[data-activity=stuck] .aaa-accessory[data-accessory=bowtie]{transform:rotate(18deg)}
114
+ .aaa[data-activity=sleeping][data-expression=neutral] .aaa-mouth-rest{opacity:0}
115
+ .aaa[data-activity=sleeping][data-expression=neutral] .aaa-mood-o{opacity:1;animation:aaa-snore 4.2s ease-in-out infinite}
116
+ .aaa[data-activity=sleeping] .aaa-accessory:is([data-accessory=antenna],[data-accessory=sprout]){transform:rotate(-35deg)}
117
+ .aaa[data-activity=sleeping] .aaa-accessory:is([data-accessory=hat],[data-accessory=glasses]){transform:translateY(5px)}
118
+ .aaa:not([data-expression=neutral]) .aaa-mouth-rest{opacity:0}
119
+ .aaa:is([data-expression=happy],[data-expression=love]) .aaa-mood-happy{opacity:1}
120
+ .aaa:is([data-expression=sad],[data-expression=angry]) .aaa-mood-sad{opacity:1}
121
+ .aaa[data-expression=confused] .aaa-mood-small{opacity:1}
122
+ .aaa[data-expression=tired] .aaa-mood-flat{opacity:1}
123
+ .aaa[data-expression=curious] .aaa-mood-o{opacity:1;transform:scale(.6)}
124
+ .aaa:not([data-expression=neutral]) .aaa-lid{transform:none}
125
+ .aaa:not([data-expression=neutral]) .aaa-eye{animation:none}
126
+ .aaa[data-expression=happy] .aaa-lid-bottom{transform:${lid("-1")}}
127
+ .aaa[data-expression=sad] .aaa-lid-top.aaa-lid-l{transform:${lid(".8")} rotate(-16deg)}
128
+ .aaa[data-expression=sad] .aaa-lid-top.aaa-lid-r{transform:${lid(".8")} rotate(16deg)}
129
+ .aaa[data-expression=angry] .aaa-lid-top.aaa-lid-l{transform:${lid(".83")} rotate(16deg)}
130
+ .aaa[data-expression=angry] .aaa-lid-top.aaa-lid-r{transform:${lid(".83")} rotate(-16deg)}
131
+ .aaa[data-expression=confused] .aaa-lid-top.aaa-lid-r{transform:${lid(".55")} rotate(-8deg)}
132
+ .aaa[data-expression=love] .aaa-eye{transform-origin:50% 100%}
133
+ .aaa[data-expression=love] .aaa-eye-l{transform:${HEART(-1)}}
134
+ .aaa[data-expression=love] .aaa-eye-r{transform:${HEART(1)}}
135
+ .aaa[data-expression=love] .aaa-eyes-react{animation:aaa-heartbeat 1.52s infinite}
136
+ .aaa[data-expression=tired] .aaa-lid-top.aaa-lid-l{transform:${lid(".6")} rotate(-7deg)}
137
+ .aaa[data-expression=tired] .aaa-lid-top.aaa-lid-r{transform:${lid(".6")} rotate(7deg)}
138
+ .aaa[data-expression=tired] .aaa-lid-bottom{transform:${lid("-.12")}}
139
+ .aaa[data-expression=curious] .aaa-eyes-motion{transform:scale(1.12)}
140
+ .aaa[data-reacting] .aaa-gaze,.aaa[data-reacting] .aaa-blink,.aaa[data-reacting] .aaa-eye{animation:none}
141
+ .aaa[data-reacting] .aaa-look{--aaa-look-x:0px;--aaa-look-y:0px;--aaa-look-sx:1}
142
+ .aaa[data-reacting] .aaa-head{--aaa-head-x:0px;--aaa-head-y:0px;--aaa-head-r:0deg}
45
143
  .aaa[data-blink=off] .aaa-blink{animation:none}
46
144
  .aaa[data-motion=off] *{animation:none!important}
47
- @keyframes aaa-blink{0%,28%,32%,90%,94%,100%{transform:scaleY(1)}30%,92%{transform:scaleY(.1)}}
145
+ .aaa[data-offscreen] *{animation-play-state:paused!important}
146
+ ${blinks([3, 21, 47, 66, 88], 0.6)}
147
+ ${glances("aaa-saccade", [
148
+ [0, 0, 0],
149
+ [9, -4, -1],
150
+ [22, 3, 2],
151
+ [33, 5, -3],
152
+ [41, 0, 0],
153
+ [52, -5, -4],
154
+ [61, 5, -4],
155
+ [70, 0, 0],
156
+ [78, -3, 3],
157
+ [90, 2, -1],
158
+ [100, 0, 0],
159
+ ], 1.2)}
160
+ ${glances("aaa-ponder", [
161
+ [0, 0, 0],
162
+ [8, -5, -4],
163
+ [50, 5, -4],
164
+ [92, 0, 0],
165
+ [100, 0, 0],
166
+ ], 7)}
167
+ ${glances("aaa-read", [
168
+ [0, -5, -2],
169
+ [8, -2, -2],
170
+ [16, 1, -2],
171
+ [24, 4, -2],
172
+ [33, -5, 0.5],
173
+ [41, -2, 0.5],
174
+ [49, 1, 0.5],
175
+ [57, 4, 0.5],
176
+ [66, -5, 3],
177
+ [74, -2, 3],
178
+ [82, 1, 3],
179
+ [90, 4, 3],
180
+ [100, -5, -2],
181
+ ], 2)}
182
+ ${timeline("aaa-inspect", 6080, [
183
+ [0, peek("0")],
184
+ [880, peek("0")],
185
+ [1280, peek(".55")],
186
+ [1840, peek(".55")],
187
+ [2000, peek("-.55")],
188
+ [2580, peek("-.55")],
189
+ [2720, peek("0")],
190
+ [3480, peek("0")],
191
+ [3620, peek(".55")],
192
+ [4240, peek(".55")],
193
+ [4420, peek("0")],
194
+ [6080, peek("0")],
195
+ ])}
196
+ ${timeline("aaa-inspect-head", 6080, [
197
+ [0, "translateY(0)"],
198
+ [880, "translateY(0)"],
199
+ [1280, "translateY(2.5px)"],
200
+ [1840, "translateY(2.5px)"],
201
+ [2000, "translateY(-2px)"],
202
+ [2580, "translateY(-2px)"],
203
+ [2720, "translateY(0)"],
204
+ [6080, "translateY(0)"],
205
+ ])}
206
+ ${glances("aaa-down", [
207
+ [0, -2, 5],
208
+ [30, 1, 5.5],
209
+ [55, 3, 5],
210
+ [70, 3, 5],
211
+ [85, -1, 5.5],
212
+ [100, -2, 5],
213
+ ], 8)}
214
+ ${glances("aaa-away", [
215
+ [0, 6, 1.5],
216
+ [40, 6, 2],
217
+ [48, 5.5, -1],
218
+ [72, 6, 1.5],
219
+ [76, 0, 0],
220
+ [84, 0, 0],
221
+ [88, 6, 1.5],
222
+ [100, 6, 1.5],
223
+ ], 1.5)}
224
+ ${glances("aaa-search", [
225
+ [0, -5, -2],
226
+ [10, 4, -3],
227
+ [20, 4, -3],
228
+ [26, -3, 2],
229
+ [38, -3, 2],
230
+ [44, 5, 1],
231
+ [56, 5, 1],
232
+ [62, -1, -4],
233
+ [74, -1, -4],
234
+ [80, -5, 3],
235
+ [92, -5, 3],
236
+ [100, -5, -2],
237
+ ], 2.5)}
238
+ @keyframes aaa-scan{0%{transform:${gazeTransform(-4, 1)};animation-timing-function:cubic-bezier(.45,.05,.55,.95)}70%{transform:${gazeTransform(4, 1)};animation-timing-function:${EASE_OUT}}80%,100%{transform:${gazeTransform(-4, 1)}}}
239
+ ${orbit("aaa-orbit-l", -1)}
240
+ ${orbit("aaa-orbit-r", 1)}
241
+ @keyframes aaa-heartbeat{0%,24%,48%,100%{transform:scale(1)}6%{transform:scale(1.155)}18%{transform:scale(1.093)}}
48
242
  @keyframes aaa-drift{0%,100%{transform:translate(0,0) rotate(0deg) scale(1)}20%{transform:translate(4px,-3px) rotate(5deg) scale(1.02,.98)}45%{transform:translate(-2px,-5px) rotate(-3deg) scale(.98,1.02)}70%{transform:translate(-5px,-1px) rotate(-6deg) scale(1.02,.98)}85%{transform:translate(-1px,1px) rotate(2deg) scale(1)}}
49
243
  @keyframes aaa-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
50
244
  @keyframes aaa-sway{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(4deg)}}
51
- @keyframes aaa-ponder{0%,100%{transform:translate(-5px,-6px)}50%{transform:translate(5px,-6px)}}
52
- @keyframes aaa-scan{0%,100%{transform:translateX(-5px)}50%{transform:translateX(5px)}}
53
245
  @keyframes aaa-breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.035,.97)}}
54
246
  @keyframes aaa-signal{0%,100%{opacity:1}50%{opacity:.25}}
55
247
  @keyframes aaa-acc-sway{0%,100%{transform:rotate(-8deg)}50%{transform:rotate(8deg)}}
56
248
  @keyframes aaa-wobble{0%,100%{transform:rotate(-12deg)}50%{transform:rotate(12deg)}}
57
249
  @keyframes aaa-glasses-think{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
58
250
  @keyframes aaa-snore{0%,100%{transform:scale(.7)}50%{transform:scale(1.1)}}
59
- @keyframes aaa-hop{0%,55%,100%{transform:translateY(0)}68%{transform:translateY(-7px)}80%{transform:translateY(0)}90%{transform:translateY(-3px)}}
251
+ @keyframes aaa-nod{0%,100%{transform:rotate(-7deg)}50%{transform:rotate(-3deg) translateY(1px)}}
252
+ @keyframes aaa-talk{0%,100%{transform:scaleY(1)}50%{transform:scaleY(.4)}}
253
+ @keyframes aaa-caret{0%{opacity:1}50%{opacity:0}}
60
254
  @media (prefers-reduced-motion:reduce){.aaa *{animation:none!important;transition:none!important}}
61
255
  `;
@@ -0,0 +1 @@
1
+ export declare function watchVisibility(svg: SVGSVGElement): () => void;
@@ -0,0 +1,26 @@
1
+ let observer = null;
2
+ let watched = 0;
3
+ function handleEntries(entries) {
4
+ for (const entry of entries) {
5
+ if (entry.isIntersecting)
6
+ entry.target.removeAttribute("data-offscreen");
7
+ else
8
+ entry.target.setAttribute("data-offscreen", "");
9
+ }
10
+ }
11
+ export function watchVisibility(svg) {
12
+ if (typeof IntersectionObserver !== "function")
13
+ return () => { };
14
+ observer ??= new IntersectionObserver(handleEntries);
15
+ observer.observe(svg);
16
+ watched += 1;
17
+ return () => {
18
+ observer?.unobserve(svg);
19
+ svg.removeAttribute("data-offscreen");
20
+ watched -= 1;
21
+ if (watched === 0) {
22
+ observer?.disconnect();
23
+ observer = null;
24
+ }
25
+ };
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aaavatars",
3
- "version": "2.1.0",
3
+ "version": "3.0.1",
4
4
  "description": "Deterministic 2D character avatars with a play() animation API",
5
5
  "keywords": [
6
6
  "avatar",
package/dist/value.d.ts DELETED
@@ -1,20 +0,0 @@
1
- import { type Accessory, type Eyes, type Fill, type Mouth, type Shape } from "./spec.js";
2
- export interface AvatarValue {
3
- accessory?: Accessory;
4
- background?: string;
5
- body?: string;
6
- eyes?: Eyes;
7
- fill?: Fill;
8
- mouth?: Mouth;
9
- shape?: Shape;
10
- }
11
- export type ParsedAvatarValue = {
12
- ok: true;
13
- value: AvatarValue;
14
- } | {
15
- ok: false;
16
- error: string;
17
- };
18
- export declare const AVATAR_VALUE_MAX_LENGTH = 200;
19
- export declare function parseAvatarValue(raw: string): ParsedAvatarValue;
20
- export declare function formatAvatarValue(value: AvatarValue): string;