@nadicodeai/ui 12.1.136 → 12.1.137
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/components/agent-orb.d.ts +5 -1
- package/dist/components/agent-orb.d.ts.map +1 -1
- package/dist/components/agent-orb.js +5 -1
- package/dist/components/company-card/renderer.d.ts +8 -0
- package/dist/components/company-card/renderer.d.ts.map +1 -1
- package/dist/components/company-card/renderer.js +122 -53
- package/dist/components/company-card.d.ts.map +1 -1
- package/dist/components/company-card.js +70 -39
- package/dist/components/hive-scene.d.ts.map +1 -1
- package/dist/components/hive-scene.js +46 -8
- package/dist/components/scroll-area.js +3 -3
- package/dist/internal/agent-orb-motion.d.ts +4 -0
- 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 +1 -0
- package/dist/internal/agent-orb.d.ts.map +1 -1
- package/dist/internal/agent-orb.js +207 -86
- package/dist/showcase/usage.generated.js +2 -2
- package/dist/standalone/index.js +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { finalizeFrame, MODE_FRAMES, makeProj, radiusScale, resolvePreset, } from "thinking-orbs/engine";
|
|
2
|
-
import { motionPose, nativeSpeed, paintOrbCue, reducedBreath, readOrbTiming, stateSpeed, } from "./agent-orb-motion.js";
|
|
2
|
+
import { motionPose, nativeSpeed, ORB_FRAME_RATE, paintOrbCue, reducedBreath, readOrbTiming, stateSpeed, } from "./agent-orb-motion.js";
|
|
3
3
|
const agentOrbForms = {
|
|
4
4
|
orbit: "working",
|
|
5
5
|
globe: "searching",
|
|
@@ -28,14 +28,11 @@ const presenceWeights = {
|
|
|
28
28
|
const nadiaStripes = ["--nc-identity-azzurro", "--nc-identity-verde", null, "--nc-identity-rosso"];
|
|
29
29
|
const celebrationTokens = ["--nc-acqua-700", "--nc-viola-700", "--nc-warning", "--nc-rosso-700"];
|
|
30
30
|
const statusTokens = { waiting: "--nc-warning-deep", blocked: "--nc-error" };
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"--nc-duration-confirm",
|
|
37
|
-
"--nc-duration-transition",
|
|
38
|
-
];
|
|
31
|
+
/**
|
|
32
|
+
* WebKit's canvas rejects `color-mix()` in a gradient stop, so a mix is handed
|
|
33
|
+
* to the canvas as the colour it computes to.
|
|
34
|
+
*/
|
|
35
|
+
const computedColors = new Map();
|
|
39
36
|
/**
|
|
40
37
|
* Sofia's four stripes: cobalto, giallo, the orb's own pigment and arancio, the
|
|
41
38
|
* identity hues Nadia does not wear, so side by side the two carry all six.
|
|
@@ -50,6 +47,14 @@ const figureStripes = {
|
|
|
50
47
|
nadia: nadiaStripes,
|
|
51
48
|
sofia: sofiaStripes,
|
|
52
49
|
};
|
|
50
|
+
const appearanceTokens = [
|
|
51
|
+
...celebrationTokens,
|
|
52
|
+
...[...nadiaStripes, ...sofiaStripes].filter((token) => token !== null),
|
|
53
|
+
...Object.values(statusTokens),
|
|
54
|
+
"--nc-duration-wait",
|
|
55
|
+
"--nc-duration-confirm",
|
|
56
|
+
"--nc-duration-transition",
|
|
57
|
+
];
|
|
53
58
|
/** A stable direction on the unit sphere, the vendor's Fibonacci lattice. */
|
|
54
59
|
function fibonacci(i, n) {
|
|
55
60
|
const y = 1 - (2 * (i + 0.5)) / n;
|
|
@@ -120,10 +125,18 @@ function facing(frame, size) {
|
|
|
120
125
|
dot.x = size - dot.x;
|
|
121
126
|
return frame;
|
|
122
127
|
}
|
|
128
|
+
function detailProfile(drawn) {
|
|
129
|
+
if (drawn <= 32)
|
|
130
|
+
return 20;
|
|
131
|
+
return drawn <= 48 ? 32 : 64;
|
|
132
|
+
}
|
|
133
|
+
function shownState(state, age, motion, timing) {
|
|
134
|
+
return motion === "ambient" || (state === "done" && age >= timing.doneDuration) ? "idle" : state;
|
|
135
|
+
}
|
|
123
136
|
function paintAgentOrb(context, input) {
|
|
124
137
|
const { form, size, seconds, color, state, age, reduced, breath, celebrationColors, stripeColors, } = input;
|
|
125
138
|
const presence = presenceWeights[input.presence];
|
|
126
|
-
const preset = resolvePreset(agentOrbForms[form === "nadia" || form === "sofia" ? "ribbon" : form], size
|
|
139
|
+
const preset = resolvePreset(agentOrbForms[form === "nadia" || form === "sofia" ? "ribbon" : form], detailProfile(size));
|
|
127
140
|
const frame = form === "nadia"
|
|
128
141
|
? stripedRibbonFrame(size, seconds, preset.opts)
|
|
129
142
|
: form === "sofia"
|
|
@@ -131,7 +144,7 @@ function paintAgentOrb(context, input) {
|
|
|
131
144
|
: MODE_FRAMES[preset.mode](size, seconds, preset.opts);
|
|
132
145
|
context.clearRect(0, 0, size, size);
|
|
133
146
|
const done = state === "done" && age >= input.timing.doneDuration;
|
|
134
|
-
const visualState = input.motion
|
|
147
|
+
const visualState = shownState(state, age, input.motion, input.timing);
|
|
135
148
|
const poseAge = input.motion === "ambient" ? 0 : done ? age - input.timing.doneDuration : age;
|
|
136
149
|
const pose = motionPose(visualState, poseAge, input.timing, reduced);
|
|
137
150
|
const cueAge = done ? age - input.timing.doneDuration : age;
|
|
@@ -173,16 +186,137 @@ function paintAgentOrb(context, input) {
|
|
|
173
186
|
paintOrbCue(context, visualState, size, cueAge, reduced, celebrationColors, "front", breath, input.timing, statusColor);
|
|
174
187
|
}
|
|
175
188
|
}
|
|
189
|
+
function createOrbWatchers(stopped) {
|
|
190
|
+
const orbs = new Map();
|
|
191
|
+
const motion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
192
|
+
function changedWithin(roots) {
|
|
193
|
+
const inside = (canvas) => {
|
|
194
|
+
for (let node = canvas; node; node = node.parentElement)
|
|
195
|
+
if (roots.has(node))
|
|
196
|
+
return true;
|
|
197
|
+
return false;
|
|
198
|
+
};
|
|
199
|
+
const writes = [...orbs].flatMap(([canvas, orb]) => (inside(canvas) ? [orb.readChange()] : []));
|
|
200
|
+
for (const write of writes)
|
|
201
|
+
write?.();
|
|
202
|
+
}
|
|
203
|
+
function playAll() {
|
|
204
|
+
for (const orb of orbs.values())
|
|
205
|
+
orb.play();
|
|
206
|
+
}
|
|
207
|
+
// A browser that cannot tell whether an orb is seen shows it as seen.
|
|
208
|
+
const visibility = typeof IntersectionObserver === "undefined"
|
|
209
|
+
? undefined
|
|
210
|
+
: new IntersectionObserver((entries) => {
|
|
211
|
+
for (const entry of entries)
|
|
212
|
+
orbs.get(entry.target)?.setVisible(entry.isIntersecting);
|
|
213
|
+
});
|
|
214
|
+
const appearance = new MutationObserver((records) => {
|
|
215
|
+
changedWithin(new Set(records.map((record) => record.target)));
|
|
216
|
+
});
|
|
217
|
+
appearance.observe(document.documentElement, {
|
|
218
|
+
attributes: true,
|
|
219
|
+
attributeFilter: ["class", "style", "data-theme"],
|
|
220
|
+
subtree: true,
|
|
221
|
+
});
|
|
222
|
+
const box = new ResizeObserver((entries) => {
|
|
223
|
+
const writes = entries.map((entry) => orbs.get(entry.target)?.readResize());
|
|
224
|
+
for (const write of writes)
|
|
225
|
+
write?.();
|
|
226
|
+
});
|
|
227
|
+
// A colour transition on the canvas or on an element it inherits from hands
|
|
228
|
+
// the observer the colour the transition starts from, so the orb reads its
|
|
229
|
+
// colour again when that transition ends. The event bubbles up from where it
|
|
230
|
+
// ran, never down to the canvas, so it is heard on the document.
|
|
231
|
+
function settle(event) {
|
|
232
|
+
if (event.propertyName === "color" && event.target instanceof Node)
|
|
233
|
+
changedWithin(new Set([event.target]));
|
|
234
|
+
}
|
|
235
|
+
function resized() {
|
|
236
|
+
changedWithin(new Set([document.documentElement]));
|
|
237
|
+
}
|
|
238
|
+
document.addEventListener("transitionend", settle, true);
|
|
239
|
+
motion.addEventListener("change", playAll);
|
|
240
|
+
document.addEventListener("visibilitychange", playAll);
|
|
241
|
+
window.addEventListener("resize", resized);
|
|
242
|
+
return {
|
|
243
|
+
motion,
|
|
244
|
+
watch(canvas, orb) {
|
|
245
|
+
orbs.set(canvas, orb);
|
|
246
|
+
visibility?.observe(canvas);
|
|
247
|
+
box.observe(canvas);
|
|
248
|
+
},
|
|
249
|
+
unwatch(canvas, orb) {
|
|
250
|
+
if (orbs.get(canvas) !== orb)
|
|
251
|
+
return;
|
|
252
|
+
orbs.delete(canvas);
|
|
253
|
+
if (orbs.size > 0) {
|
|
254
|
+
visibility?.unobserve(canvas);
|
|
255
|
+
box.unobserve(canvas);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
visibility?.disconnect();
|
|
259
|
+
appearance.disconnect();
|
|
260
|
+
box.disconnect();
|
|
261
|
+
document.removeEventListener("transitionend", settle, true);
|
|
262
|
+
motion.removeEventListener("change", playAll);
|
|
263
|
+
document.removeEventListener("visibilitychange", playAll);
|
|
264
|
+
window.removeEventListener("resize", resized);
|
|
265
|
+
stopped();
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
let pageOrbWatchers;
|
|
270
|
+
function createOrbClock() {
|
|
271
|
+
const running = new Set();
|
|
272
|
+
let request = 0;
|
|
273
|
+
function frame(now) {
|
|
274
|
+
request = 0;
|
|
275
|
+
for (const orb of [...running])
|
|
276
|
+
if (!orb(now))
|
|
277
|
+
running.delete(orb);
|
|
278
|
+
if (running.size > 0 && request === 0)
|
|
279
|
+
request = requestAnimationFrame(frame);
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
run(orb) {
|
|
283
|
+
running.add(orb);
|
|
284
|
+
if (request === 0)
|
|
285
|
+
request = requestAnimationFrame(frame);
|
|
286
|
+
},
|
|
287
|
+
stop(orb) {
|
|
288
|
+
running.delete(orb);
|
|
289
|
+
if (running.size > 0 || request === 0)
|
|
290
|
+
return;
|
|
291
|
+
cancelAnimationFrame(request);
|
|
292
|
+
request = 0;
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const orbClock = createOrbClock();
|
|
297
|
+
/**
|
|
298
|
+
* A display's frames arrive a little early or late, so a paint due within this
|
|
299
|
+
* many milliseconds is painted on the frame at hand: at 60 Hz a 30 fps orb then
|
|
300
|
+
* paints every second frame rather than every second or third.
|
|
301
|
+
*/
|
|
302
|
+
const PAINT_SLACK_MS = 4;
|
|
303
|
+
function orbWatchers() {
|
|
304
|
+
pageOrbWatchers ??= createOrbWatchers(() => {
|
|
305
|
+
pageOrbWatchers = undefined;
|
|
306
|
+
});
|
|
307
|
+
return pageOrbWatchers;
|
|
308
|
+
}
|
|
309
|
+
export const LONGEST_FRAME_SECONDS = 0.1;
|
|
176
310
|
export function mountAgentOrb(canvas, options) {
|
|
177
311
|
const { form, presence = "plain" } = options;
|
|
178
312
|
const context = canvas.getContext("2d");
|
|
179
313
|
if (!context)
|
|
180
314
|
return { destroy() { }, setPaused(_value) { }, setState(_value) { } };
|
|
181
|
-
const
|
|
315
|
+
const page = orbWatchers();
|
|
316
|
+
const { motion } = page;
|
|
182
317
|
let paused = options.paused ?? false;
|
|
183
318
|
let visible = true;
|
|
184
319
|
let disposed = false;
|
|
185
|
-
let request = 0;
|
|
186
320
|
let seconds = 0.6;
|
|
187
321
|
let age = 0;
|
|
188
322
|
let breath = 0;
|
|
@@ -241,11 +375,10 @@ export function mountAgentOrb(canvas, options) {
|
|
|
241
375
|
return !(state === "blocked" && age >= timing.blockedDuration);
|
|
242
376
|
}
|
|
243
377
|
function tick(now) {
|
|
244
|
-
request = 0;
|
|
245
378
|
if (!running())
|
|
246
|
-
return;
|
|
379
|
+
return false;
|
|
247
380
|
if (previous !== null) {
|
|
248
|
-
const elapsed = Math.min((now - previous) / 1000,
|
|
381
|
+
const elapsed = Math.min((now - previous) / 1000, LONGEST_FRAME_SECONDS);
|
|
249
382
|
if (motion.matches)
|
|
250
383
|
breath += elapsed;
|
|
251
384
|
else {
|
|
@@ -257,98 +390,92 @@ export function mountAgentOrb(canvas, options) {
|
|
|
257
390
|
}
|
|
258
391
|
}
|
|
259
392
|
previous = now;
|
|
260
|
-
|
|
393
|
+
const rate = shownState(state, age, orbMotion, timing) === "idle"
|
|
394
|
+
? ORB_FRAME_RATE.resting
|
|
395
|
+
: ORB_FRAME_RATE.busy;
|
|
396
|
+
if (now - lastPaint >= 1000 / rate - PAINT_SLACK_MS || !running()) {
|
|
261
397
|
paint();
|
|
262
398
|
lastPaint = now;
|
|
263
399
|
}
|
|
264
|
-
|
|
265
|
-
request = requestAnimationFrame(tick);
|
|
400
|
+
return running();
|
|
266
401
|
}
|
|
267
402
|
function play() {
|
|
268
|
-
cancelAnimationFrame(request);
|
|
269
|
-
request = 0;
|
|
270
403
|
previous = null;
|
|
271
404
|
paint();
|
|
272
405
|
if (running())
|
|
273
|
-
|
|
406
|
+
orbClock.run(tick);
|
|
407
|
+
else
|
|
408
|
+
orbClock.stop(tick);
|
|
274
409
|
}
|
|
275
|
-
/** WebKit's canvas rejects `color-mix()` in a gradient stop, so a mix is
|
|
276
|
-
* handed to the canvas as the colour it computes to. */
|
|
277
410
|
function computedColor(declaration) {
|
|
411
|
+
const known = computedColors.get(declaration);
|
|
412
|
+
if (known)
|
|
413
|
+
return known;
|
|
278
414
|
const probe = document.createElement("span");
|
|
279
415
|
probe.hidden = true;
|
|
280
416
|
probe.style.color = declaration;
|
|
281
417
|
(canvas.parentElement ?? document.body).append(probe);
|
|
282
418
|
const resolved = getComputedStyle(probe).color;
|
|
283
419
|
probe.remove();
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return
|
|
288
|
-
side,
|
|
289
|
-
window.devicePixelRatio || 1,
|
|
290
|
-
appearance.color,
|
|
291
|
-
...appearanceTokens.map((token) => appearance.getPropertyValue(token)),
|
|
292
|
-
].join("|");
|
|
420
|
+
if (!resolved)
|
|
421
|
+
return color;
|
|
422
|
+
computedColors.set(declaration, resolved);
|
|
423
|
+
return resolved;
|
|
293
424
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
425
|
+
function readAppearance() {
|
|
426
|
+
const style = getComputedStyle(canvas);
|
|
427
|
+
const tokens = new Map(appearanceTokens.map((token) => [token, style.getPropertyValue(token).trim()]));
|
|
428
|
+
const key = [window.devicePixelRatio || 1, style.color, ...tokens.values()].join("|");
|
|
429
|
+
return { key, color: style.color, tokens, timing: readOrbTiming(style) };
|
|
297
430
|
}
|
|
298
|
-
function
|
|
431
|
+
function resize(side) {
|
|
299
432
|
if (disposed || !canvas.isConnected)
|
|
300
433
|
return;
|
|
301
|
-
painted =
|
|
434
|
+
painted = side;
|
|
302
435
|
const backing = Math.max(1, Math.round(painted * Math.min(window.devicePixelRatio || 1, 2)));
|
|
303
436
|
canvas.width = backing;
|
|
304
437
|
canvas.height = backing;
|
|
305
438
|
ratio = backing / painted;
|
|
306
|
-
|
|
307
|
-
|
|
439
|
+
play();
|
|
440
|
+
}
|
|
441
|
+
function refresh(appearance, side) {
|
|
442
|
+
appearanceKey = appearance.key;
|
|
308
443
|
color = appearance.color;
|
|
309
|
-
timing =
|
|
444
|
+
timing = appearance.timing;
|
|
310
445
|
statusColors = {
|
|
311
|
-
waiting: computedColor(appearance.
|
|
312
|
-
blocked: computedColor(appearance.
|
|
446
|
+
waiting: computedColor(appearance.tokens.get(statusTokens.waiting) || color),
|
|
447
|
+
blocked: computedColor(appearance.tokens.get(statusTokens.blocked) || color),
|
|
313
448
|
};
|
|
314
|
-
celebrationColors = celebrationTokens.map((token) => computedColor(`color-mix(in oklch, ${appearance.
|
|
315
|
-
stripeColors = (figureStripes[form] ?? []).map((token) => (token && appearance.
|
|
316
|
-
|
|
449
|
+
celebrationColors = celebrationTokens.map((token) => computedColor(`color-mix(in oklch, ${appearance.tokens.get(token) || color} 75%, white)`));
|
|
450
|
+
stripeColors = (figureStripes[form] ?? []).map((token) => (token && appearance.tokens.get(token)) || color);
|
|
451
|
+
resize(side);
|
|
317
452
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const appearance = new MutationObserver(refreshIfChanged);
|
|
324
|
-
for (let ancestor = canvas; ancestor; ancestor = ancestor.parentElement) {
|
|
325
|
-
appearance.observe(ancestor, {
|
|
326
|
-
attributes: true,
|
|
327
|
-
attributeFilter: ["class", "style", "data-theme"],
|
|
328
|
-
});
|
|
453
|
+
function readResize() {
|
|
454
|
+
if (disposed || !canvas.isConnected)
|
|
455
|
+
return undefined;
|
|
456
|
+
const side = measure();
|
|
457
|
+
return Math.abs(side - painted) >= 0.5 ? () => resize(side) : undefined;
|
|
329
458
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
// the observer the colour the transition starts from, so the orb reads its
|
|
339
|
-
// colour again when that transition ends. The event bubbles up from where it
|
|
340
|
-
// ran, never down to the canvas, so it is heard on the document.
|
|
341
|
-
function settle(event) {
|
|
342
|
-
if (event.propertyName === "color" &&
|
|
343
|
-
event.target instanceof Node &&
|
|
344
|
-
event.target.contains(canvas))
|
|
345
|
-
refreshIfChanged();
|
|
459
|
+
function readChange() {
|
|
460
|
+
if (disposed || !canvas.isConnected)
|
|
461
|
+
return undefined;
|
|
462
|
+
const appearance = readAppearance();
|
|
463
|
+
if (appearance.key === appearanceKey)
|
|
464
|
+
return readResize();
|
|
465
|
+
const side = measure();
|
|
466
|
+
return () => refresh(appearance, side);
|
|
346
467
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
468
|
+
const watched = {
|
|
469
|
+
readChange,
|
|
470
|
+
readResize,
|
|
471
|
+
setVisible(value) {
|
|
472
|
+
visible = value;
|
|
473
|
+
play();
|
|
474
|
+
},
|
|
475
|
+
play,
|
|
476
|
+
};
|
|
477
|
+
page.watch(canvas, watched);
|
|
478
|
+
readChange()?.();
|
|
352
479
|
return {
|
|
353
480
|
setState(value) {
|
|
354
481
|
state = value;
|
|
@@ -362,14 +489,8 @@ export function mountAgentOrb(canvas, options) {
|
|
|
362
489
|
},
|
|
363
490
|
destroy() {
|
|
364
491
|
disposed = true;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
appearance.disconnect();
|
|
368
|
-
box.disconnect();
|
|
369
|
-
document.removeEventListener("transitionend", settle, true);
|
|
370
|
-
motion.removeEventListener("change", play);
|
|
371
|
-
document.removeEventListener("visibilitychange", play);
|
|
372
|
-
window.removeEventListener("resize", refreshIfChanged);
|
|
492
|
+
orbClock.stop(tick);
|
|
493
|
+
page.unwatch(canvas, watched);
|
|
373
494
|
},
|
|
374
495
|
};
|
|
375
496
|
}
|
|
@@ -624,9 +624,9 @@ export const USAGE = [
|
|
|
624
624
|
module: "agent-orb",
|
|
625
625
|
family: "Status",
|
|
626
626
|
summary: "An Agent, anywhere: her orb, the only thing that moves on its own.",
|
|
627
|
-
when:
|
|
627
|
+
when: "An Agent, anywhere: 20 in the sidebar, 32 in a row, 64 on her Profile card, 96 in an Agent card, 160 on entry. There is no other Agent picture. The sizes are 20, 24, 32, 40, 64, 96 and 160 px, and no other. Detail follows the size the orb is drawn at: up to 32 px it draws the engine's lightest dot profile, at 40 px the middle one and from 64 px the full one, since a small orb cannot show the full profile's dots apart. On a photograph the orb takes the stronger `presence=\"over-picture\"` weight; everywhere else it is `plain`.",
|
|
628
628
|
instead: 'A person is an `Avatar`. The system\'s own wait is the waiting line, never an orb. An orb in artwork, such as a sign-in picture, takes `motion="ambient"` and claims no activity. An orb that stands for an Agent shows her real state.',
|
|
629
|
-
behaviour: "The only thing that moves on its own. Idle particles turn at 86.4% of the form's native pace, with a separate 6.4 s breath. Working sweeps a ribbon every 3.2 s; thinking passes three pulses over 2.4 s. Waiting leans softly as centered pause bars arrive; blocked recoils as a centered stop sign lands. Both entrances settle over 2 s, then waiting breathes and blocked holds still. Done celebrates once over 4.8 s, then rests at 72% of native particle speed with the Idle breath. A state change interrupts the previous gesture; `motionKey` replays it and `paused` freezes it. A surface that only knows a machine checked in draws idle. No state draws a circle around the orb. Ambient motion moves the form at its own pace with no state pose or cue. Reduced motion: movement stops, a thinking or working orb breathes between full ink and 0.45 opacity so busy still reads as busy, an ambient orb holds still, and waiting and blocked keep their centered signs without an entrance gesture. An orb is one of seven forms, `ribbon`, `lattice`, `orbit`, `globe`, `network`, `braid` and `morph`, in one colour a person chose: cobalto, verde or arancio, or none, which draws it in the text colour. Giallo is not offered because it fails 3:1 on white; rosso is not offered because it is the error red. nadicode's own two Agents are the only ones with a figure nobody chooses, the same at every size and in every state. Nadia is her striped ribbon, the form `nadia`: azzurro, verde, the text colour and rosso along its whole length. Sofia, the Agent that looks up a new company, is her sister, the form `sofia`: the same ribbon turned to face Nadia's, striped cobalto, giallo, the text colour and arancio, the identity hues Nadia does not wear. Neither figure is ever stored as an Agent's look. An Agent's form is stored as `orb:<form>` and its colour as the identity hex or nothing; a template declares its starting look the same way. The stored-appearance resolver reads it: the root Agent always draws as Nadia, an unknown form draws `orbit`, an unknown colour the text colour, and a stored rosso draws arancio. The retired `forma:*` shapes each draw one of the seven forms, so an Agent that chose one before the orbs keeps a distinct look. An Agent's look is a person's choice and is never derived from a name, id or role. A template that names no look is the one exception: `templateAgentOrb` gives it a form and colour fixed by its slug, so the templates in a gallery differ and the Portal and Nadia draw the same one; Nadia and Sofia keep their figures.",
|
|
629
|
+
behaviour: "The only thing that moves on its own. Idle particles turn at 86.4% of the form's native pace, with a separate 6.4 s breath. Working sweeps a ribbon every 3.2 s; thinking passes three pulses over 2.4 s. Waiting leans softly as centered pause bars arrive; blocked recoils as a centered stop sign lands. Both entrances settle over 2 s, then waiting breathes and blocked holds still. Done celebrates once over 4.8 s, then rests at 72% of native particle speed with the Idle breath. A state change interrupts the previous gesture; `motionKey` replays it and `paused` freezes it. A surface that only knows a machine checked in draws idle. No state draws a circle around the orb. Ambient motion moves the form at its own pace with no state pose or cue. An orb at rest (idle, ambient, or done after its celebration) paints 15 frames a second; every other state, gesture and the celebration paint 30. The pace is the same at either rate. Reduced motion: movement stops, a thinking or working orb breathes between full ink and 0.45 opacity so busy still reads as busy, an ambient orb holds still, and waiting and blocked keep their centered signs without an entrance gesture. An orb is one of seven forms, `ribbon`, `lattice`, `orbit`, `globe`, `network`, `braid` and `morph`, in one colour a person chose: cobalto, verde or arancio, or none, which draws it in the text colour. Giallo is not offered because it fails 3:1 on white; rosso is not offered because it is the error red. nadicode's own two Agents are the only ones with a figure nobody chooses, the same at every size and in every state. Nadia is her striped ribbon, the form `nadia`: azzurro, verde, the text colour and rosso along its whole length. Sofia, the Agent that looks up a new company, is her sister, the form `sofia`: the same ribbon turned to face Nadia's, striped cobalto, giallo, the text colour and arancio, the identity hues Nadia does not wear. Neither figure is ever stored as an Agent's look. An Agent's form is stored as `orb:<form>` and its colour as the identity hex or nothing; a template declares its starting look the same way. The stored-appearance resolver reads it: the root Agent always draws as Nadia, an unknown form draws `orbit`, an unknown colour the text colour, and a stored rosso draws arancio. The retired `forma:*` shapes each draw one of the seven forms, so an Agent that chose one before the orbs keeps a distinct look. An Agent's look is a person's choice and is never derived from a name, id or role. A template that names no look is the one exception: `templateAgentOrb` gives it a form and colour fixed by its slug, so the templates in a gallery differ and the Portal and Nadia draw the same one; Nadia and Sofia keep their figures.",
|
|
630
630
|
words: "Text sits beside the orb, never on it. Where the exact state matters a word names it: *Working*, *Waiting for you*.",
|
|
631
631
|
access: '`role="img"` labelled with the Agent\'s name, and her state where it matters; `decorative` when her name is already beside it.',
|
|
632
632
|
},
|