@nadicodeai/ui 8.6.0 → 9.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/AGENTS.md +1 -1
- package/LICENSES/THINKING-ORBS-MIT.txt +21 -0
- package/NOTICE +7 -10
- package/dist/components/agent-orb-appearance.d.ts +54 -0
- package/dist/components/agent-orb-appearance.d.ts.map +1 -0
- package/dist/components/agent-orb-appearance.js +87 -0
- package/dist/components/agent-orb.d.ts +5 -3
- package/dist/components/agent-orb.d.ts.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/internal/agent-orb-motion.d.ts +3 -3
- package/dist/internal/agent-orb-motion.d.ts.map +1 -1
- package/dist/internal/agent-orb-motion.js +1 -0
- package/dist/internal/agent-orb.d.ts +11 -1
- package/dist/internal/agent-orb.d.ts.map +1 -1
- package/dist/internal/agent-orb.js +80 -5
- package/docs/agent-orb.md +47 -14
- package/docs/contract.md +8 -29
- package/docs/migration/9.0.0.md +35 -0
- package/package.json +2 -3
- package/LICENSES/PAPER-SHADERS-APACHE-2.0.txt +0 -186
- package/dist/components/agent-visual.d.ts +0 -17
- package/dist/components/agent-visual.d.ts.map +0 -1
- package/dist/components/agent-visual.js +0 -63
- package/dist/components/forma-appearance.d.ts +0 -13
- package/dist/components/forma-appearance.d.ts.map +0 -1
- package/dist/components/forma-appearance.js +0 -11
- package/dist/components/forma-avatar.d.ts +0 -23
- package/dist/components/forma-avatar.d.ts.map +0 -1
- package/dist/components/forma-avatar.js +0 -95
- package/dist/internal/agent-visual/color.d.ts +0 -4
- package/dist/internal/agent-visual/color.d.ts.map +0 -1
- package/dist/internal/agent-visual/color.js +0 -78
- package/dist/internal/agent-visual/poster.d.ts +0 -6
- package/dist/internal/agent-visual/poster.d.ts.map +0 -1
- package/dist/internal/agent-visual/poster.js +0 -35
- package/dist/internal/agent-visual/recipes.d.ts +0 -15
- package/dist/internal/agent-visual/recipes.d.ts.map +0 -1
- package/dist/internal/agent-visual/recipes.js +0 -72
- package/dist/internal/agent-visual/renderer.d.ts +0 -10
- package/dist/internal/agent-visual/renderer.d.ts.map +0 -1
- package/dist/internal/agent-visual/renderer.js +0 -112
- package/dist/internal/agent-visual/shader.d.ts +0 -2
- package/dist/internal/agent-visual/shader.d.ts.map +0 -1
- package/dist/internal/agent-visual/shader.js +0 -158
- package/dist/internal/forma/frame.d.ts +0 -10
- package/dist/internal/forma/frame.d.ts.map +0 -1
- package/dist/internal/forma/frame.js +0 -211
- package/dist/internal/forma/geometry.d.ts +0 -41
- package/dist/internal/forma/geometry.d.ts.map +0 -1
- package/dist/internal/forma/geometry.js +0 -235
- package/dist/internal/forma/hallmark.d.ts +0 -5
- package/dist/internal/forma/hallmark.d.ts.map +0 -1
- package/dist/internal/forma/hallmark.js +0 -4
- package/dist/internal/forma/model.d.ts +0 -53
- package/dist/internal/forma/model.d.ts.map +0 -1
- package/dist/internal/forma/model.js +0 -70
- package/dist/internal/forma/motion.d.ts +0 -42
- package/dist/internal/forma/motion.d.ts.map +0 -1
- package/dist/internal/forma/motion.js +0 -194
- package/dist/internal/forma/rig.d.ts +0 -10
- package/dist/internal/forma/rig.d.ts.map +0 -1
- package/dist/internal/forma/rig.js +0 -211
- package/dist/internal/forma/still.d.ts +0 -4
- package/dist/internal/forma/still.d.ts.map +0 -1
- package/dist/internal/forma/still.js +0 -8
- package/dist/internal/forma/stored-appearance.d.ts +0 -29
- package/dist/internal/forma/stored-appearance.d.ts.map +0 -1
- package/dist/internal/forma/stored-appearance.js +0 -53
- package/docs/agent-visual.md +0 -114
- package/docs/forma-avatar.md +0 -110
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ResolvedFormaOptions } from "./model.js";
|
|
2
|
-
export interface FormaHandle {
|
|
3
|
-
update(options: ResolvedFormaOptions): void;
|
|
4
|
-
dispose(): void;
|
|
5
|
-
}
|
|
6
|
-
export interface FormaMountCallbacks {
|
|
7
|
-
onModeChange?: (mode: "live" | "still") => void;
|
|
8
|
-
}
|
|
9
|
-
export declare function mountForma(svg: SVGSVGElement, options: ResolvedFormaOptions, callbacks?: FormaMountCallbacks): FormaHandle;
|
|
10
|
-
//# sourceMappingURL=rig.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rig.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/rig.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUpD,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,IAAI,CAAC;CACjB;AACD,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;CACjD;AAyLD,wBAAgB,UAAU,CACxB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,GAAE,mBAAwB,GAClC,WAAW,CAwCb"}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { computeFrame } from "./frame.js";
|
|
2
|
-
import { createMotion, needsMotion, settled, stepPose, updateMotion, } from "./motion.js";
|
|
3
|
-
const attributeName = (prop) => prop.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
4
|
-
function write(element, attributes) {
|
|
5
|
-
for (const prop in attributes) {
|
|
6
|
-
const value = attributes[prop] ?? "";
|
|
7
|
-
if (prop === "display") {
|
|
8
|
-
element.style.display = value;
|
|
9
|
-
continue;
|
|
10
|
-
}
|
|
11
|
-
const name = attributeName(prop);
|
|
12
|
-
if (element.getAttribute(name) !== value)
|
|
13
|
-
element.setAttribute(name, value);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
const pools = new WeakMap();
|
|
17
|
-
class Pool {
|
|
18
|
-
document;
|
|
19
|
-
entries = new Map();
|
|
20
|
-
observer;
|
|
21
|
-
reduced = typeof matchMedia === "function"
|
|
22
|
-
? matchMedia("(prefers-reduced-motion: reduce)")
|
|
23
|
-
: { matches: false, addEventListener() { }, removeEventListener() { } };
|
|
24
|
-
pointer = { x: NaN, y: NaN };
|
|
25
|
-
frame;
|
|
26
|
-
lastFrame = -Infinity;
|
|
27
|
-
constructor(document) {
|
|
28
|
-
this.document = document;
|
|
29
|
-
this.observer =
|
|
30
|
-
typeof IntersectionObserver === "undefined"
|
|
31
|
-
? undefined
|
|
32
|
-
: new IntersectionObserver((records) => {
|
|
33
|
-
for (const record of records) {
|
|
34
|
-
const entry = this.entries.get(record.target);
|
|
35
|
-
if (!entry)
|
|
36
|
-
continue;
|
|
37
|
-
if (record.isIntersecting && !entry.visible)
|
|
38
|
-
entry.motion.snap = true;
|
|
39
|
-
entry.visible = record.isIntersecting;
|
|
40
|
-
entry.dirty = true;
|
|
41
|
-
}
|
|
42
|
-
this.request();
|
|
43
|
-
}, { rootMargin: "80px" });
|
|
44
|
-
document.addEventListener("visibilitychange", this.onWake);
|
|
45
|
-
document.addEventListener("pointermove", this.onPointer, { passive: true });
|
|
46
|
-
document.addEventListener("pointerleave", this.onPointerLeave);
|
|
47
|
-
this.reduced.addEventListener("change", this.onWake);
|
|
48
|
-
window.addEventListener("resize", this.onWake);
|
|
49
|
-
}
|
|
50
|
-
onWake = () => {
|
|
51
|
-
for (const entry of this.entries.values()) {
|
|
52
|
-
entry.dirty = true;
|
|
53
|
-
entry.motion.snap = true;
|
|
54
|
-
entry.rect = null;
|
|
55
|
-
}
|
|
56
|
-
if (this.document.hidden && this.frame !== undefined) {
|
|
57
|
-
cancelAnimationFrame(this.frame);
|
|
58
|
-
this.frame = undefined;
|
|
59
|
-
}
|
|
60
|
-
this.request();
|
|
61
|
-
};
|
|
62
|
-
onPointer = (event) => {
|
|
63
|
-
if (event.pointerType === "touch")
|
|
64
|
-
return;
|
|
65
|
-
this.pointer.x = event.clientX;
|
|
66
|
-
this.pointer.y = event.clientY;
|
|
67
|
-
this.request();
|
|
68
|
-
};
|
|
69
|
-
onPointerLeave = () => {
|
|
70
|
-
this.pointer.x = NaN;
|
|
71
|
-
this.pointer.y = NaN;
|
|
72
|
-
this.request();
|
|
73
|
-
};
|
|
74
|
-
request() {
|
|
75
|
-
if (this.frame === undefined &&
|
|
76
|
-
!this.document.hidden &&
|
|
77
|
-
this.entries.size > 0 &&
|
|
78
|
-
typeof requestAnimationFrame === "function")
|
|
79
|
-
this.frame = requestAnimationFrame(this.tick);
|
|
80
|
-
}
|
|
81
|
-
aim(entry, now) {
|
|
82
|
-
const { motion } = entry;
|
|
83
|
-
if (motion.still || motion.view !== "front" || Number.isNaN(this.pointer.x)) {
|
|
84
|
-
motion.pointer.x = 0;
|
|
85
|
-
motion.pointer.y = 0;
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (!entry.rect || now - entry.rectAt > 500) {
|
|
89
|
-
entry.rect = entry.svg.getBoundingClientRect();
|
|
90
|
-
entry.rectAt = now;
|
|
91
|
-
}
|
|
92
|
-
const rect = entry.rect;
|
|
93
|
-
if (!rect.width)
|
|
94
|
-
return;
|
|
95
|
-
const cx = rect.left + rect.width / 2;
|
|
96
|
-
const cy = rect.top + rect.height / 2;
|
|
97
|
-
const reach = Math.max(rect.width, 32) * 2.5;
|
|
98
|
-
const dx = this.pointer.x - cx;
|
|
99
|
-
const dy = this.pointer.y - cy;
|
|
100
|
-
const distance = Math.hypot(dx, dy);
|
|
101
|
-
const k = Math.min(1, distance / reach) / (distance || 1);
|
|
102
|
-
motion.pointer.x = Math.max(-1, Math.min(1, dx * k));
|
|
103
|
-
motion.pointer.y = Math.max(-1, Math.min(1, dy * k));
|
|
104
|
-
}
|
|
105
|
-
tick = (now) => {
|
|
106
|
-
this.frame = undefined;
|
|
107
|
-
if (this.document.hidden)
|
|
108
|
-
return;
|
|
109
|
-
if (now - this.lastFrame < 1000 / 30) {
|
|
110
|
-
this.request();
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const dt = Math.min(0.05, Math.max(0, (now - this.lastFrame) / 1000));
|
|
114
|
-
this.lastFrame = now;
|
|
115
|
-
let again = false;
|
|
116
|
-
for (const entry of this.entries.values()) {
|
|
117
|
-
if (!entry.visible)
|
|
118
|
-
continue;
|
|
119
|
-
const { motion } = entry;
|
|
120
|
-
motion.still = motion.paused || this.reduced.matches;
|
|
121
|
-
this.aim(entry, now);
|
|
122
|
-
const moving = needsMotion(motion, now);
|
|
123
|
-
if (entry.dirty || moving || entry.terminalFrameDue) {
|
|
124
|
-
const pose = stepPose(motion, now, entry.dirty ? 0 : dt);
|
|
125
|
-
this.paint(entry, pose, now);
|
|
126
|
-
this.setMode(entry, motion.still ? "still" : "live");
|
|
127
|
-
entry.dirty = false;
|
|
128
|
-
entry.terminalFrameDue = moving;
|
|
129
|
-
}
|
|
130
|
-
again ||= needsMotion(motion, now) && !settled(motion, now);
|
|
131
|
-
if (Number.isFinite(this.pointer.x) && motion.state === "idle" && !motion.still)
|
|
132
|
-
again = true;
|
|
133
|
-
}
|
|
134
|
-
if (again)
|
|
135
|
-
this.request();
|
|
136
|
-
};
|
|
137
|
-
paint(entry, pose, now) {
|
|
138
|
-
const frame = computeFrame(entry.motion, pose, now);
|
|
139
|
-
for (const part in frame) {
|
|
140
|
-
const element = entry.parts.get(part);
|
|
141
|
-
if (element)
|
|
142
|
-
write(element, frame[part]);
|
|
143
|
-
}
|
|
144
|
-
entry.svg.dataset.state = entry.motion.state;
|
|
145
|
-
}
|
|
146
|
-
setMode(entry, mode) {
|
|
147
|
-
if (entry.svg.dataset.renderMode === mode)
|
|
148
|
-
return;
|
|
149
|
-
entry.svg.dataset.renderMode = mode;
|
|
150
|
-
entry.callbacks.onModeChange?.(mode);
|
|
151
|
-
}
|
|
152
|
-
release(svg) {
|
|
153
|
-
this.observer?.unobserve(svg);
|
|
154
|
-
this.entries.delete(svg);
|
|
155
|
-
if (this.entries.size !== 0)
|
|
156
|
-
return;
|
|
157
|
-
if (this.frame !== undefined)
|
|
158
|
-
cancelAnimationFrame(this.frame);
|
|
159
|
-
this.observer?.disconnect();
|
|
160
|
-
this.document.removeEventListener("visibilitychange", this.onWake);
|
|
161
|
-
this.document.removeEventListener("pointermove", this.onPointer);
|
|
162
|
-
this.document.removeEventListener("pointerleave", this.onPointerLeave);
|
|
163
|
-
this.reduced.removeEventListener("change", this.onWake);
|
|
164
|
-
window.removeEventListener("resize", this.onWake);
|
|
165
|
-
pools.delete(this.document);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
export function mountForma(svg, options, callbacks = {}) {
|
|
169
|
-
const doc = svg.ownerDocument;
|
|
170
|
-
let pool = pools.get(doc);
|
|
171
|
-
if (!pool) {
|
|
172
|
-
pool = new Pool(doc);
|
|
173
|
-
pools.set(doc, pool);
|
|
174
|
-
}
|
|
175
|
-
if (pool.entries.has(svg))
|
|
176
|
-
throw new Error("This element already has a Forma avatar");
|
|
177
|
-
const parts = new Map();
|
|
178
|
-
for (const element of svg.querySelectorAll("[data-part]"))
|
|
179
|
-
parts.set(element.getAttribute("data-part"), element);
|
|
180
|
-
const entry = {
|
|
181
|
-
svg,
|
|
182
|
-
parts,
|
|
183
|
-
motion: createMotion(options, performance.now()),
|
|
184
|
-
visible: !pool.observer,
|
|
185
|
-
dirty: true,
|
|
186
|
-
terminalFrameDue: false,
|
|
187
|
-
callbacks,
|
|
188
|
-
rect: null,
|
|
189
|
-
rectAt: -Infinity,
|
|
190
|
-
};
|
|
191
|
-
const activePool = pool;
|
|
192
|
-
pool.entries.set(svg, entry);
|
|
193
|
-
pool.observer?.observe(svg);
|
|
194
|
-
pool.request();
|
|
195
|
-
let disposed = false;
|
|
196
|
-
return {
|
|
197
|
-
update(next) {
|
|
198
|
-
if (disposed)
|
|
199
|
-
return;
|
|
200
|
-
updateMotion(entry.motion, next, performance.now());
|
|
201
|
-
entry.dirty = true;
|
|
202
|
-
activePool.request();
|
|
203
|
-
},
|
|
204
|
-
dispose() {
|
|
205
|
-
if (disposed)
|
|
206
|
-
return;
|
|
207
|
-
disposed = true;
|
|
208
|
-
activePool.release(svg);
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/still.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAgB,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAGpD,wBAAgB,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,KAAK,CAK/D"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { computeFrame } from "./frame.js";
|
|
2
|
-
import { createMotion, stepPose } from "./motion.js";
|
|
3
|
-
export function stillFrame(options) {
|
|
4
|
-
const motion = createMotion({ ...options, paused: true }, 0);
|
|
5
|
-
motion.still = true;
|
|
6
|
-
const pose = stepPose(motion, 0, 0);
|
|
7
|
-
return computeFrame(motion, pose, 0);
|
|
8
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { type FormaChoice, type FormaColor } from "./model.js";
|
|
2
|
-
/** What Hermes stores for an Agent's look. Both may be absent or unknown. */
|
|
3
|
-
export interface StoredAppearance {
|
|
4
|
-
shape?: string | null;
|
|
5
|
-
color?: string | null;
|
|
6
|
-
}
|
|
7
|
-
/** The `shape` value a chosen form is stored as. */
|
|
8
|
-
export declare const shapeFor: (form: FormaChoice) => string;
|
|
9
|
-
/** The stored shape of the form an Agent wears until a customer chooses one. */
|
|
10
|
-
export declare const defaultShape: string;
|
|
11
|
-
/**
|
|
12
|
-
* The hex each palette colour is stored as. Hermes keeps an Agent's colour as
|
|
13
|
-
* a CSS colour for its own surfaces, so the stored spelling is a value rather
|
|
14
|
-
* than a token reference; `stored-appearance.test.ts` holds this table to the
|
|
15
|
-
* shipped design-system tokens, and the ESLint colour-literal exemption in the
|
|
16
|
-
* root config names this file.
|
|
17
|
-
*/
|
|
18
|
-
export declare const formaColorValues: Record<FormaColor, string>;
|
|
19
|
-
/**
|
|
20
|
-
* Resolve the stored strings to what `FormaAvatar` draws. An unknown shape is
|
|
21
|
-
* the default form and an unknown colour is undefined, which leaves the form
|
|
22
|
-
* its own pigment. The root Agent is the caller's business: a caller that owns
|
|
23
|
-
* one passes `form="nadia"` instead of asking here.
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveAppearance(stored: StoredAppearance): {
|
|
26
|
-
form: FormaChoice;
|
|
27
|
-
color: FormaColor | undefined;
|
|
28
|
-
};
|
|
29
|
-
//# sourceMappingURL=stored-appearance.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stored-appearance.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/stored-appearance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAA0C,MAAM,SAAS,CAAC;AAEpG,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,oDAAoD;AACpD,eAAO,MAAM,QAAQ,GAAI,MAAM,WAAW,KAAG,MAAyB,CAAC;AAEvE,gFAAgF;AAChF,eAAO,MAAM,YAAY,EAAE,MAA8B,CAAC;AAc1D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAOvD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG;IAC3D,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;CAC/B,CAEA"}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { defaultForm, formaChoices, formaColors } from "./model.js";
|
|
2
|
-
/** The `shape` value a chosen form is stored as. */
|
|
3
|
-
export const shapeFor = (form) => `forma:${form}`;
|
|
4
|
-
/** The stored shape of the form an Agent wears until a customer chooses one. */
|
|
5
|
-
export const defaultShape = shapeFor(defaultForm);
|
|
6
|
-
/**
|
|
7
|
-
* The five role names the launch Agents were stored under, each kept at the
|
|
8
|
-
* form it wore. Forms are shapes, never roles: nothing new is stored this way.
|
|
9
|
-
*/
|
|
10
|
-
const legacyShapes = new Map([
|
|
11
|
-
["forma:research", "uovo"],
|
|
12
|
-
["forma:build", "goccia"],
|
|
13
|
-
["forma:design", "nuvola"],
|
|
14
|
-
["forma:operate", "quadrato"],
|
|
15
|
-
["forma:review", "esagono"],
|
|
16
|
-
]);
|
|
17
|
-
/**
|
|
18
|
-
* The hex each palette colour is stored as. Hermes keeps an Agent's colour as
|
|
19
|
-
* a CSS colour for its own surfaces, so the stored spelling is a value rather
|
|
20
|
-
* than a token reference; `stored-appearance.test.ts` holds this table to the
|
|
21
|
-
* shipped design-system tokens, and the ESLint colour-literal exemption in the
|
|
22
|
-
* root config names this file.
|
|
23
|
-
*/
|
|
24
|
-
export const formaColorValues = {
|
|
25
|
-
verde: "#00a85a",
|
|
26
|
-
cobalto: "#1e3cff",
|
|
27
|
-
rosso: "#d3302f",
|
|
28
|
-
arancio: "#ff5a1f",
|
|
29
|
-
giallo: "#ffc220",
|
|
30
|
-
avorio: "#e9e7db",
|
|
31
|
-
};
|
|
32
|
-
/**
|
|
33
|
-
* Resolve the stored strings to what `FormaAvatar` draws. An unknown shape is
|
|
34
|
-
* the default form and an unknown colour is undefined, which leaves the form
|
|
35
|
-
* its own pigment. The root Agent is the caller's business: a caller that owns
|
|
36
|
-
* one passes `form="nadia"` instead of asking here.
|
|
37
|
-
*/
|
|
38
|
-
export function resolveAppearance(stored) {
|
|
39
|
-
return { form: formFor(stored.shape), color: colorFor(stored.color) };
|
|
40
|
-
}
|
|
41
|
-
function formFor(shape) {
|
|
42
|
-
if (!shape)
|
|
43
|
-
return defaultForm;
|
|
44
|
-
// A Map, not an object: the shape is a stored string from a machine, and an
|
|
45
|
-
// object lookup would hand back `toString` or `constructor` for those spellings.
|
|
46
|
-
return (formaChoices.find((form) => shape === shapeFor(form)) ?? legacyShapes.get(shape) ?? defaultForm);
|
|
47
|
-
}
|
|
48
|
-
function colorFor(color) {
|
|
49
|
-
if (!color)
|
|
50
|
-
return undefined;
|
|
51
|
-
const stored = color.toLowerCase();
|
|
52
|
-
return formaColors.find((name) => stored === formaColorValues[name] || color === name);
|
|
53
|
-
}
|
package/docs/agent-visual.md
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Agent Visual System
|
|
2
|
-
|
|
3
|
-
Legacy API for existing consumers. New Agent workspace surfaces use
|
|
4
|
-
[AgentOrb](agent-orb.md) and the shared
|
|
5
|
-
[product language](../../design-system/PRODUCT_LANGUAGE.md#agent-section-contract).
|
|
6
|
-
The multicolour recipes below do not govern new Portal agent representations.
|
|
7
|
-
|
|
8
|
-
The Agent visual is the decorative identity for a persistent Agent in Nadia.
|
|
9
|
-
It is a living, multicolour metaball form whose recipe is assigned by Agent
|
|
10
|
-
type. It does not represent status, capability, runtime activity, or work
|
|
11
|
-
output. Those facts stay in text, status components, assistant-ui, and the
|
|
12
|
-
product surfaces that own them.
|
|
13
|
-
|
|
14
|
-
This system is distinct from Nadia's portrait. Nadia keeps the one canonical
|
|
15
|
-
identity in `brand/AGENTS.md` "Nadia's identity"; Agent visuals identify the other named,
|
|
16
|
-
role-specific Agents that a company operates inside Nadia.
|
|
17
|
-
|
|
18
|
-
## Approved form
|
|
19
|
-
|
|
20
|
-
- One connected metaball entity sits on a true-black field.
|
|
21
|
-
- The entity is always multicolour and draws its five hues from the Agent
|
|
22
|
-
identity palette: Cobalto, Verde, Arancio, Giallo, and Rosso. That palette
|
|
23
|
-
is owned by `packages/design-system/DESIGN.md`, which holds the token names
|
|
24
|
-
and values; this document holds the recipes that arrange them.
|
|
25
|
-
- The entity itself carries the surface. Texture never paints the background.
|
|
26
|
-
- The four approved surfaces are solid, ordered points, borderless triangular
|
|
27
|
-
wire, and irregular living particles.
|
|
28
|
-
- Ordered points form a regular, readable field. Living particles vary in
|
|
29
|
-
position, size, and brightness so the two surfaces remain visibly distinct.
|
|
30
|
-
- Triangular wire has no outer stroke. The clipped triangular network alone
|
|
31
|
-
defines the body.
|
|
32
|
-
- The visual has no initials, letters, face, avatar frame, status tint, or
|
|
33
|
-
caller-selected colour.
|
|
34
|
-
|
|
35
|
-
## Closed recipes
|
|
36
|
-
|
|
37
|
-
The five recipes below are the complete public set. Their names, palettes,
|
|
38
|
-
surface assignments, ball counts, sizes, and deterministic frames are fixed.
|
|
39
|
-
Applications assign one recipe explicitly. They never hash an id, inspect a
|
|
40
|
-
free-form role label, randomize a recipe, or expose these parameters to a
|
|
41
|
-
caller.
|
|
42
|
-
|
|
43
|
-
| Recipe | Surface | Palette order | Count | Size | Still frame |
|
|
44
|
-
| ------------- | ---------------- | -------------------------------------- | ----: | ---: | ----------: |
|
|
45
|
-
| General | Solid | Cobalto, Verde, Arancio, Giallo, Rosso | 10 | 0.83 | 0 |
|
|
46
|
-
| Reasoning | Ordered points | Cobalto, Verde, Giallo | 10 | 0.83 | 0 |
|
|
47
|
-
| Coordination | Triangular wire | Verde, Cobalto, Arancio | 8 | 0.76 | 2700 |
|
|
48
|
-
| Communication | Solid | Giallo, Arancio, Rosso | 12 | 0.69 | 5400 |
|
|
49
|
-
| Execution | Living particles | Rosso, Arancio, Cobalto | 6 | 0.92 | 8100 |
|
|
50
|
-
|
|
51
|
-
`general` is the deliberate fallback when a product has not stored an Agent's
|
|
52
|
-
recipe yet. It is not an inference from the Agent's name, id, role, status, or
|
|
53
|
-
position in a list.
|
|
54
|
-
|
|
55
|
-
## Placement and motion
|
|
56
|
-
|
|
57
|
-
The same recipe keeps the same identity wherever it appears. Each recipe owns
|
|
58
|
-
both its approved deterministic SVG poster form and its ambient shader
|
|
59
|
-
parameters in one internal record. The two presentations are intentionally
|
|
60
|
-
recognizable realizations of one recipe, not pixel-identical renderers.
|
|
61
|
-
|
|
62
|
-
- Registry cells, `AgentCard`, focal Agent detail, and the design-system
|
|
63
|
-
showcase use the ambient reference motion at speed 0.24. The text name
|
|
64
|
-
remains the primary identity, and motion never reports status or activity.
|
|
65
|
-
- The shared component mounts the renderer only while the visual intersects
|
|
66
|
-
the viewport and releases its WebGL context offscreen; the vendor pauses it
|
|
67
|
-
when the document is hidden. Compact registry placement therefore keeps the
|
|
68
|
-
same identity without retaining offscreen shader canvases.
|
|
69
|
-
- Reduced-motion, missing WebGL2, shader initialization failure, and context
|
|
70
|
-
loss all keep the same deterministic still. They do not substitute a new
|
|
71
|
-
shape, colour, icon, or blank box.
|
|
72
|
-
- The still renders before the client shader is eligible so server rendering
|
|
73
|
-
and hydration never present an empty visual slot.
|
|
74
|
-
|
|
75
|
-
The visual is normally hidden from assistive technology because the adjacent
|
|
76
|
-
Agent name carries the identity. A picker in which the visual is itself the
|
|
77
|
-
choice may give it an accessible name. Status and job information never enter
|
|
78
|
-
that name.
|
|
79
|
-
|
|
80
|
-
## Implementation ownership
|
|
81
|
-
|
|
82
|
-
`@nadicodeai/ui` owns the typed `AgentVisual` implementation and the
|
|
83
|
-
presentational `AgentCard` composition. The API exposes the closed recipe and
|
|
84
|
-
approved presentation roles, not raw colours, shader source, speed, count,
|
|
85
|
-
frame, density, material, or arbitrary tuning.
|
|
86
|
-
|
|
87
|
-
Consuming products own the stored recipe assignment, Agent data, routing,
|
|
88
|
-
actions, permissions, and placement. An Agent registry remains a table when
|
|
89
|
-
the job is comparison; the visual may appear inside its identity cell without
|
|
90
|
-
turning the row into a new `AgentRow` component.
|
|
91
|
-
|
|
92
|
-
## Source and modification record
|
|
93
|
-
|
|
94
|
-
The solid form mounts the published `metaballsFragmentShader` from
|
|
95
|
-
`@paper-design/shaders` version `0.0.78` verbatim. The other three surfaces use
|
|
96
|
-
a NadicodeAI modification of that release's published Metaballs shader. The
|
|
97
|
-
modification preserves metaball generation, motion, colour blending, edge
|
|
98
|
-
calculation, and background composition, then changes only the fill clipped
|
|
99
|
-
inside the final entity shape.
|
|
100
|
-
|
|
101
|
-
The package is Apache-2.0 and includes a `NOTICE` file. Any distributed
|
|
102
|
-
modified source must keep the Apache-2.0 license, retain the upstream notices,
|
|
103
|
-
carry a prominent modification notice, and include the upstream NOTICE text:
|
|
104
|
-
|
|
105
|
-
```text
|
|
106
|
-
Paper Shaders
|
|
107
|
-
Copyright 2026 Paper
|
|
108
|
-
|
|
109
|
-
Powered by Paper Shaders:
|
|
110
|
-
https://shaders.paper.design
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
The identity system is one closed, package-owned system; additions go through
|
|
114
|
-
this document, never through a new ad-hoc visual.
|
package/docs/forma-avatar.md
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# Forma Avatars
|
|
2
|
-
|
|
3
|
-
Forma is the avatar family for the Agents a company operates inside Nadia,
|
|
4
|
-
and for Nadia herself. It is a character: a coloured shell with a visor and
|
|
5
|
-
two eyes, alive through the approved lifecycle gestures. `@nadicodeai/ui`
|
|
6
|
-
owns its rendering as one SVG per avatar; consuming products own the stored
|
|
7
|
-
appearance, the Agent data, and every fact about status or work.
|
|
8
|
-
|
|
9
|
-
This system is distinct from Nadia's portrait, which stays the one canonical
|
|
10
|
-
identity in `brand/AGENTS.md` "Nadia's identity". Nadia's avatar here is her
|
|
11
|
-
approved Forma shell: the tricolour, the signature eyes and lash flicks, and
|
|
12
|
-
the Italy hallmark on her right flank (Natural Earth 110m Italy from
|
|
13
|
-
AshKyd/geojson-regions, projected once with d3-geo 3.1.1 into a 512-unit box).
|
|
14
|
-
|
|
15
|
-
## Forms and colours
|
|
16
|
-
|
|
17
|
-
A customer picks a form and a colour. Forms are shapes, never roles: choosing
|
|
18
|
-
Goccia grants no tools and changes no instructions, and the product names the
|
|
19
|
-
job. The closed set is Nadia plus seven forms:
|
|
20
|
-
|
|
21
|
-
| Form | Silhouette | Default shell | Visor | Eyes |
|
|
22
|
-
| --- | --- | --- | --- | --- |
|
|
23
|
-
| `nadia` | The approved shell, tricolour, fixed | verde | black | signature mint |
|
|
24
|
-
| `goccia` | Drop | rosso | black | white |
|
|
25
|
-
| `nuvola` | Cloud | cobalto | ivory | ink |
|
|
26
|
-
| `triangolo` | Rounded triangle | verde | ivory | ink |
|
|
27
|
-
| `esagono` | Hexagon | giallo | black | white |
|
|
28
|
-
| `quadrato` | Squircle | arancio | ivory | ink |
|
|
29
|
-
| `pillola` | Stadium | verde | black | white |
|
|
30
|
-
| `uovo` | Egg | cobalto | black | white |
|
|
31
|
-
|
|
32
|
-
Shell colours are the Agent identity palette (`identity-verde`, `-cobalto`,
|
|
33
|
-
`-rosso`, `-arancio`, `-giallo`) plus `forma-avorio`; the visor, bezel,
|
|
34
|
-
Nadia's eye light and the blocked sand are the `forma-*` tokens. All of them are owned by
|
|
35
|
-
`packages/design-system/DESIGN.md` and read through their CSS custom
|
|
36
|
-
properties, so an avatar carries no colour of its own. `uovo` is the form an
|
|
37
|
-
Agent wears until a customer chooses; it is never inferred from a name, an
|
|
38
|
-
id, a role label, or a position in a list.
|
|
39
|
-
|
|
40
|
-
## Construction
|
|
41
|
-
|
|
42
|
-
Every form carries a core sphere inside its silhouette. The one approved
|
|
43
|
-
visor and the form's eyes ride on that sphere, seen through the engine's
|
|
44
|
-
camera (perspective from 5.2 radii), and the silhouette clips them: the face
|
|
45
|
-
is the same size and curvature on every form, and turning the head moves it
|
|
46
|
-
across the shell the same way, foreshortening at the rim.
|
|
47
|
-
|
|
48
|
-
The lighting reproduces the engine's studio, number for number where a
|
|
49
|
-
gradient can: the shell is enamel deepened by its own pigment toward the
|
|
50
|
-
rim (a multiply layer), shaded by the hemisphere light toward the floor,
|
|
51
|
-
lit by the wide top-left key card, a hot spot, and the right rim card; the
|
|
52
|
-
visor is black glass with one crisp reflection at its top-left corner, a
|
|
53
|
-
soft haze across the top, the right-edge reflection, and a thin bezel lit
|
|
54
|
-
only where the key reaches it; the eyes carry their halo and a glint, and
|
|
55
|
-
Nadia's Italy hallmark is the engine's ivory engraving at longitude 1.24 on
|
|
56
|
-
her right flank, visible from the side. Blocked morphs every form into the same torus and drains
|
|
57
|
-
its pigment 28% toward `forma-sabbia`, as the approved renders do.
|
|
58
|
-
|
|
59
|
-
## States and motion
|
|
60
|
-
|
|
61
|
-
Six states: `idle`, `working`, `thinking`, `waiting`, `blocked`, `done`. The
|
|
62
|
-
choreography is the approved engine's, number for number: the idle sway and
|
|
63
|
-
pointer-following gaze; working's acknowledging nod, scan, beat, three pulsing
|
|
64
|
-
ticks and two orbiting bead trails; thinking's tilt and three thought beads;
|
|
65
|
-
waiting's pause bars; blocked's morph into the ring and bar; done's wind-up,
|
|
66
|
-
full turn, hop, happy eyes and the three-ribbon celebration. A completion
|
|
67
|
-
celebrates once per real transition or `motionKey` change; an initially
|
|
68
|
-
completed avatar holds its finished pose. Waiting and blocked always face
|
|
69
|
-
forward; `view="side"` shows the flank otherwise.
|
|
70
|
-
|
|
71
|
-
One loop per document drives every mounted avatar at up to 30 frames a
|
|
72
|
-
second. Offscreen and hidden avatars do not render, settled states stop the
|
|
73
|
-
loop, and the pointer turns the heads nearest to it. `paused` and reduced
|
|
74
|
-
motion hold the state's deterministic resting frame, which is also what the
|
|
75
|
-
server renders, effects included (a working still carries its orbit beads, a
|
|
76
|
-
thinking still its thought beads), so no surface ever shows an empty slot and
|
|
77
|
-
no still image is shipped.
|
|
78
|
-
|
|
79
|
-
## Package shape
|
|
80
|
-
|
|
81
|
-
`FormaAvatar` (`@nadicodeai/ui/components/forma-avatar`) takes `form`,
|
|
82
|
-
`color`, `state`, `size` (12–512 CSS pixels), `paused`, `view`, and
|
|
83
|
-
`motionKey`. It is decorative by default; a visual that is itself the
|
|
84
|
-
choice, as in a picker, opts into `decorative={false}` with a `label`. It
|
|
85
|
-
exposes `data-forma-avatar`, `data-state`, `data-color` on its host and
|
|
86
|
-
`data-render-mode` (`still` or `live`) on the SVG. The model's closed lists
|
|
87
|
-
(`formaForms`, `formaChoices`, `formaColors`, `formaStates`, `formDetails`,
|
|
88
|
-
`defaultForm`) are exported beside it for pickers and stored-appearance
|
|
89
|
-
mapping; the geometry, rig and frame computation are internal.
|
|
90
|
-
|
|
91
|
-
The stored appearance resolves in this package too, so every product draws one
|
|
92
|
-
Agent the same way. `StoredAppearance` is the pair of strings a product stores, `shape`
|
|
93
|
-
and `color`, either of which may be absent or unknown. `resolveAppearance`
|
|
94
|
-
answers the `form` and `color` the avatar takes: an unknown shape is
|
|
95
|
-
`defaultForm`, the five retired role shapes keep the form each one wore, and an
|
|
96
|
-
unknown colour is `undefined`, which leaves the form its own pigment. A stored
|
|
97
|
-
colour matches a palette hex in any case or the bare colour name.
|
|
98
|
-
The resolver lives in `@nadicodeai/ui/components/forma-appearance`, which
|
|
99
|
-
carries no `"use client"`: `FormaAvatar` is a client component, and a pure
|
|
100
|
-
function re-exported from a client module becomes a client reference a server
|
|
101
|
-
cannot call, so the two entry points do not re-export each other and every
|
|
102
|
-
consumer imports the look from one and the avatar from the other.
|
|
103
|
-
`shapeFor(form)` writes the stored spelling of a chosen form, `defaultShape` is
|
|
104
|
-
`shapeFor(defaultForm)`, and `formaColorValues` is the hex each palette colour
|
|
105
|
-
is stored as, held to the shipped design-system tokens by a test beside the
|
|
106
|
-
resolver. The root Agent is the product's decision, not the resolver's: a
|
|
107
|
-
product that owns one passes `form="nadia"` itself.
|
|
108
|
-
|
|
109
|
-
Additions to the family go through this document and the design-system
|
|
110
|
-
token contract, never through an ad-hoc visual.
|