aaavatars 2.0.0 → 3.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/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")
@@ -0,0 +1,2 @@
1
+ export declare function restartGazeCentred(svg: SVGSVGElement): void;
2
+ export declare function watchPointer(svg: SVGSVGElement): () => void;
@@ -0,0 +1,95 @@
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;
6
+ const watchers = new Set();
7
+ let pointer = null;
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
+ }
36
+ function aim() {
37
+ frame = 0;
38
+ const targets = [...watchers]
39
+ .filter((svg) => !svg.hasAttribute("data-offscreen"))
40
+ .map((svg) => ({ svg, bounds: svg.getBoundingClientRect() }));
41
+ for (const { svg, bounds } of targets) {
42
+ if (!pointer || bounds.bottom < 0 || bounds.top > window.innerHeight) {
43
+ release(svg);
44
+ continue;
45
+ }
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`);
60
+ }
61
+ }
62
+ function schedule() {
63
+ if (!frame)
64
+ frame = requestAnimationFrame(aim);
65
+ }
66
+ function handleMove(event) {
67
+ if (event.pointerType === "touch")
68
+ return;
69
+ pointer = { x: event.clientX, y: event.clientY };
70
+ schedule();
71
+ clearTimeout(staleTimer);
72
+ staleTimer = setTimeout(handleLeave, STALE_MS);
73
+ }
74
+ function handleLeave() {
75
+ clearTimeout(staleTimer);
76
+ pointer = null;
77
+ schedule();
78
+ }
79
+ export function watchPointer(svg) {
80
+ if (watchers.size === 0) {
81
+ window.addEventListener("pointermove", handleMove, { passive: true });
82
+ document.documentElement.addEventListener("pointerleave", handleLeave);
83
+ }
84
+ watchers.add(svg);
85
+ return () => {
86
+ watchers.delete(svg);
87
+ release(svg);
88
+ if (watchers.size === 0) {
89
+ window.removeEventListener("pointermove", handleMove);
90
+ document.documentElement.removeEventListener("pointerleave", handleLeave);
91
+ clearTimeout(staleTimer);
92
+ pointer = null;
93
+ }
94
+ };
95
+ }
package/dist/spec.d.ts CHANGED
@@ -21,8 +21,10 @@ export interface AvatarSpec {
21
21
  mouth: Mouth;
22
22
  offsetX: number;
23
23
  offsetY: number;
24
+ phase: number;
24
25
  rotate: number;
25
26
  shape: Shape;
27
+ tempo: number;
26
28
  }
27
29
  export interface SpecOptions {
28
30
  accessory?: Accessory;
package/dist/spec.js CHANGED
@@ -14,6 +14,8 @@ export const ACCESSORIES = ["none", "antenna", "sprout", "hat", "glasses", "bowt
14
14
  export const FILLS = ["solid", "gradient"];
15
15
  const BLINK_MIN_SECONDS = 5;
16
16
  const BLINK_SPREAD_SECONDS = 4;
17
+ const TEMPO_MIN = 0.85;
18
+ const TEMPO_SPREAD = 0.3;
17
19
  export const DEFAULT_COLORS = ["#264653", "#2A9D8F", "#E9C46A", "#F4A261", "#E76F51"];
18
20
  function hash(value) {
19
21
  let result = 2166136261;
@@ -37,6 +39,9 @@ function pick(items, random) {
37
39
  function roundTenth(value) {
38
40
  return Math.round(value * 10) / 10;
39
41
  }
42
+ function roundHundredth(value) {
43
+ return Math.round(value * 100) / 100;
44
+ }
40
45
  function signedRange(random, limit) {
41
46
  return Math.round((random() * 2 - 1) * limit);
42
47
  }
@@ -64,6 +69,8 @@ export function createSpec(name, options = {}) {
64
69
  const accent = accentCandidates.length ? pick(accentCandidates, random) : colors[0];
65
70
  const blinkDuration = roundTenth(BLINK_MIN_SECONDS + random() * BLINK_SPREAD_SECONDS);
66
71
  const blinkDelay = -roundTenth(random() * blinkDuration);
72
+ const tempo = roundHundredth(TEMPO_MIN + random() * TEMPO_SPREAD);
73
+ const phase = roundHundredth(random() * 0.99);
67
74
  const background = options.background ?? colors[backgroundIndex];
68
75
  const body = options.body ?? colors[bodyIndex];
69
76
  return {
@@ -78,8 +85,10 @@ export function createSpec(name, options = {}) {
78
85
  mouth: options.mouth ?? "none",
79
86
  offsetX,
80
87
  offsetY,
88
+ phase,
81
89
  rotate,
82
90
  shape: options.shape ?? shape,
91
+ tempo,
83
92
  };
84
93
  }
85
94
  export function shadeHex(hex, amount) {
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-bob 3.6s ease-in-out infinite}\n.aaa[data-status=thinking] .aaa-body-motion{animation:aaa-sway 3s ease-in-out infinite}\n.aaa[data-status=thinking] .aaa-eyes-motion{transform:translate(-5px,-6px);animation:aaa-ponder 2.4s ease-in-out infinite}\n.aaa[data-status=working] .aaa-body-motion{animation:aaa-bob .9s ease-in-out infinite}\n.aaa[data-status=working] .aaa-eyes-motion{animation:aaa-scan 1.1s ease-in-out infinite}\n.aaa[data-status=waiting] .aaa-body-motion{animation:aaa-hop 1.6s ease-out 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 4.2s ease-in-out 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-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,60 +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-bob 3.6s ease-in-out infinite}
14
- .aaa[data-status=thinking] .aaa-body-motion{animation:aaa-sway 3s ease-in-out infinite}
15
- .aaa[data-status=thinking] .aaa-eyes-motion{transform:translate(-5px,-6px);animation:aaa-ponder 2.4s ease-in-out infinite}
16
- .aaa[data-status=working] .aaa-body-motion{animation:aaa-bob .9s ease-in-out infinite}
17
- .aaa[data-status=working] .aaa-eyes-motion{animation:aaa-scan 1.1s ease-in-out infinite}
18
- .aaa[data-status=waiting] .aaa-body-motion{animation:aaa-hop 1.6s ease-out 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 4.2s ease-in-out 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)}}
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)}}
48
243
  @keyframes aaa-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
49
244
  @keyframes aaa-sway{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(4deg)}}
50
- @keyframes aaa-ponder{0%,100%{transform:translate(-5px,-6px)}50%{transform:translate(5px,-6px)}}
51
- @keyframes aaa-scan{0%,100%{transform:translateX(-5px)}50%{transform:translateX(5px)}}
52
245
  @keyframes aaa-breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.035,.97)}}
53
246
  @keyframes aaa-signal{0%,100%{opacity:1}50%{opacity:.25}}
54
247
  @keyframes aaa-acc-sway{0%,100%{transform:rotate(-8deg)}50%{transform:rotate(8deg)}}
55
248
  @keyframes aaa-wobble{0%,100%{transform:rotate(-12deg)}50%{transform:rotate(12deg)}}
56
249
  @keyframes aaa-glasses-think{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
57
250
  @keyframes aaa-snore{0%,100%{transform:scale(.7)}50%{transform:scale(1.1)}}
58
- @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}}
59
254
  @media (prefers-reduced-motion:reduce){.aaa *{animation:none!important;transition:none!important}}
60
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.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Deterministic 2D character avatars with a play() animation API",
5
5
  "keywords": [
6
6
  "avatar",