@nadicodeai/ui 12.1.136 → 12.1.138
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/brand.d.ts +5 -5
- package/dist/components/brand.d.ts.map +1 -1
- package/dist/components/brand.js +5 -5
- package/dist/components/catalog-entry.d.ts +5 -4
- package/dist/components/catalog-entry.d.ts.map +1 -1
- package/dist/components/catalog-entry.js +4 -3
- 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/identity-mark.d.ts +4 -4
- package/dist/components/identity-mark.d.ts.map +1 -1
- package/dist/components/identity-mark.js +2 -3
- package/dist/components/scroll-area.js +3 -3
- package/dist/components/settings-section.d.ts +26 -15
- package/dist/components/settings-section.d.ts.map +1 -1
- package/dist/components/settings-section.js +23 -16
- package/dist/components/switch.d.ts +2 -1
- package/dist/components/switch.d.ts.map +1 -1
- package/dist/components/switch.js +3 -2
- package/dist/components/theme-mode-switcher.d.ts +1 -1
- package/dist/components/theme-mode-switcher.js +1 -1
- 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/specimens/compositions.js +1 -1
- package/dist/showcase/specimens/inputs.d.ts.map +1 -1
- package/dist/showcase/specimens/inputs.js +2 -2
- package/dist/showcase/specimens/status.js +2 -2
- package/dist/showcase/usage.generated.d.ts.map +1 -1
- package/dist/showcase/usage.generated.js +13 -13
- package/dist/standalone/index.js +2 -2
- 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
|
}
|
|
@@ -64,7 +64,7 @@ const CATALOG = [
|
|
|
64
64
|
entries: [
|
|
65
65
|
{
|
|
66
66
|
id: "quotes",
|
|
67
|
-
mark: { kind: "record", icon: Wrench
|
|
67
|
+
mark: { kind: "record", icon: Wrench },
|
|
68
68
|
name: "Write quotes from the price list",
|
|
69
69
|
description: "Reads listino-2026.xlsx and drafts a quote in the customer's language.",
|
|
70
70
|
add: { label: "Add skill", onClick: nothing },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../../src/showcase/specimens/inputs.tsx"],"names":[],"mappings":"AA6BA,iBAAS,gBAAgB,gCAexB;AAED,iBAAS,wBAAwB,gCAqBhC;AAED,iBAAS,sBAAsB,gCAiC9B;AAED,iBAAS,kBAAkB,gCAqC1B;AAED,iBAAS,gBAAgB,gCA2BxB;AAED,iBAAS,cAAc,gCAuCtB;AAqBD,iBAAS,oBAAoB,
|
|
1
|
+
{"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../../src/showcase/specimens/inputs.tsx"],"names":[],"mappings":"AA6BA,iBAAS,gBAAgB,gCAexB;AAED,iBAAS,wBAAwB,gCAqBhC;AAED,iBAAS,sBAAsB,gCAiC9B;AAED,iBAAS,kBAAkB,gCAqC1B;AAED,iBAAS,gBAAgB,gCA2BxB;AAED,iBAAS,cAAc,gCAuCtB;AAqBD,iBAAS,oBAAoB,gCAoD5B;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;CAoHQ,CAAC"}
|
|
@@ -54,7 +54,7 @@ function SettingsRowsSpecimen() {
|
|
|
54
54
|
const [approvals, setApprovals] = useState(true);
|
|
55
55
|
const [reminders, setReminders] = useState(false);
|
|
56
56
|
const [zone, setZone] = useState("Europe/Rome");
|
|
57
|
-
return (_jsxs("div", { className: "grid gap-6", children: [_jsx(SettingsSection, { description: "Choose which events ask for your attention.", title: "Notifications", children: _jsx(SettingsRows, {
|
|
57
|
+
return (_jsxs("div", { className: "grid gap-6", children: [_jsx(SettingsSection, { description: "Choose which events ask for your attention.", title: "Notifications", children: _jsx(SettingsRows, { rows: [
|
|
58
58
|
{
|
|
59
59
|
id: "showcase-rows-approvals",
|
|
60
60
|
label: "Approvals",
|
|
@@ -71,7 +71,7 @@ function SettingsRowsSpecimen() {
|
|
|
71
71
|
onCheckedChange: setReminders,
|
|
72
72
|
name: "reminders",
|
|
73
73
|
},
|
|
74
|
-
], stateLabels: STATE_LABELS }) }), _jsx(SettingsSection, { description: "Dates and times follow this choice.", title: "Region", children: _jsx(SettingsRows, {
|
|
74
|
+
], stateLabels: STATE_LABELS }) }), _jsx(SettingsSection, { description: "Dates and times follow this choice.", title: "Region", children: _jsx(SettingsRows, { rows: [
|
|
75
75
|
{
|
|
76
76
|
id: "showcase-rows-zone",
|
|
77
77
|
label: "Time zone",
|
|
@@ -71,7 +71,7 @@ export const STATUS = {
|
|
|
71
71
|
] }), _jsx(CheckState, { label: "1 check failed", outcome: "failed" })] }), _jsxs("span", { className: "flex items-center gap-3", children: [_jsx("span", { className: "flex-1", children: "Messaging" }), _jsx(CheckState, { label: "All channels connected", outcome: "passed" })] })] })),
|
|
72
72
|
FileTypeIcon: () => (_jsx("div", { className: "grid gap-2 text-sm", children: ["mcp.json", "deploy.sh", "README.md", "schema.sql"].map((path) => (_jsxs("span", { className: "flex items-center gap-2 font-mono", children: [_jsx(FileTypeIcon, { className: "size-4", path: path }), path] }, path))) })),
|
|
73
73
|
Gauge: GaugeSpecimen,
|
|
74
|
-
IdentityMark: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [_jsx(IdentityMark, { kind: "agent", orb: { form: "orbit", color: COBALTO } }), _jsx(IdentityMark, { app: "gmail", kind: "app", logo: composioLogo("gmail") }), _jsx(IdentityMark, { kind: "company", name: "Acme Service & Parts" }), _jsx(IdentityMark, { kind: "company", logo: SAMPLE_LOGO, name: "Studio Rossi" }), _jsx(IdentityMark, { kind: "company", logo: SAMPLE_ICON, name: "Polaris Studio" }), _jsx(IdentityMark, { kind: "person", name: "Ada Rossi" }), _jsx(IdentityMark, { icon: Mail, kind: "record" }), _jsx(IdentityMark, { icon: Wrench, kind: "record", size: 40
|
|
74
|
+
IdentityMark: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [_jsx(IdentityMark, { kind: "agent", orb: { form: "orbit", color: COBALTO } }), _jsx(IdentityMark, { app: "gmail", kind: "app", logo: composioLogo("gmail") }), _jsx(IdentityMark, { kind: "company", name: "Acme Service & Parts" }), _jsx(IdentityMark, { kind: "company", logo: SAMPLE_LOGO, name: "Studio Rossi" }), _jsx(IdentityMark, { kind: "company", logo: SAMPLE_ICON, name: "Polaris Studio" }), _jsx(IdentityMark, { kind: "person", name: "Ada Rossi" }), _jsx(IdentityMark, { icon: Mail, kind: "record" }), _jsx(IdentityMark, { icon: Wrench, kind: "record", size: 40 })] })),
|
|
75
75
|
ModelIdentity: () => (_jsxs("div", { className: "grid gap-2", children: [_jsx(ModelIdentity, { model: "anthropic/claude-opus-5" }), _jsx(ModelIdentity, { model: "openai/gpt-5.6-sol" }), _jsx(ModelIdentity, { model: "x-ai/grok-4.5" })] })),
|
|
76
76
|
ModelMark: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [_jsx(ModelMark, { author: "anthropic", name: "Claude Opus 5" }), _jsx(ModelMark, { author: "mistralai", label: "Mistral" })] })),
|
|
77
77
|
PlatformMark: () => (_jsx("div", { className: "flex flex-wrap items-center gap-4 text-sm", children: ["google", "microsoft", "apple", "telegram", "whatsapp", "slack"].map((platform) => (_jsxs("span", { className: "flex items-center gap-2 capitalize", children: [_jsx(PlatformMark, { platform: platform, size: 20 }), platform] }, platform))) })),
|
|
@@ -103,7 +103,7 @@ export const STATUS = {
|
|
|
103
103
|
action: { label: "Open downloads", onClick: nothing },
|
|
104
104
|
},
|
|
105
105
|
], title: "Customer setup" })),
|
|
106
|
-
RecordIdentity: () => (_jsx("div", { className: "max-w-sm", children: _jsx(RecordIdentity, { detail: "Company skill \u00B7 every Agent has it", hint: "skl_0042", mark: { kind: "record", icon: Wrench
|
|
106
|
+
RecordIdentity: () => (_jsx("div", { className: "max-w-sm", children: _jsx(RecordIdentity, { detail: "Company skill \u00B7 every Agent has it", hint: "skl_0042", mark: { kind: "record", icon: Wrench }, name: "Write quotes from the price list" }) })),
|
|
107
107
|
StatusBadge: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(StatusBadge, { label: "Connected", tone: "success" }), _jsx(StatusBadge, { label: "Waiting for you", since: "2h", tone: "warning" }), _jsx(StatusBadge, { label: "Failed", tone: "danger" }), _jsx(StatusBadge, { label: "Running", tone: "info" }), _jsx(StatusBadge, { label: "Paused", tone: "neutral" })] })),
|
|
108
108
|
ObjectFactValue: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(ObjectFactValue, { kind: "agents", children: "5 active" }), _jsx(ObjectFactValue, { kind: "installations", children: "2" }), _jsx(ObjectFactValue, { kind: "customers", children: "12" })] })),
|
|
109
109
|
Tag: () => (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Tag, { tone: "blue", children: "Rossi Ottica" }), _jsx(Tag, { tone: "teal", children: "Partner" }), _jsx(Tag, { note: "Your account", quiet: true, children: "Direct customer" })] })),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.generated.d.ts","sourceRoot":"","sources":["../../src/showcase/usage.generated.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"usage.generated.d.ts","sourceRoot":"","sources":["../../src/showcase/usage.generated.ts"],"names":[],"mappings":"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"}
|
|
@@ -230,7 +230,7 @@ export const USAGE = [
|
|
|
230
230
|
summary: "A card of one setting that says what changing it does and ends in a footer.",
|
|
231
231
|
when: "One setting a person changes on a SettingsPage or as a section of an ObjectPage: Personal details, Company identity, Notifications. A typed value takes `save`; a switch or a short reversible choice applies at once through its own control's change, with no Save. Also a plan-gated feature, a delete card, one read-only value (an ID in a read-only mono InputGroup with its copy button, no Save), and a paid quantity (its stepper field, the price as the description, a two-fact summary under it).",
|
|
232
232
|
instead: "Read-only facts of an object are a SettingsFactsSection; a table or a list of records is a TableCard; Theme and Language live in the account menu; a record's edit in a list is a RowEditForm.",
|
|
233
|
-
behaviour: '"Groups related form controls inside a bordered card with optional footer actions." (https://vercel.com/geist/fieldset) Ours: one setting per card
|
|
233
|
+
behaviour: '"Groups related form controls inside a bordered card with optional footer actions." (https://vercel.com/geist/fieldset) Ours: one setting per card, a single SettingsField with the description its only help, unless the card\'s description covers several settings: then they are its SettingsRows, switches and selects together. A typed value (a name, a URL, a server, a port, a key) gets its own card. A semantic subgroup of related controls inside a field, such as radios or checkboxes, is a `FieldSet` with a `FieldLegend`. Several settings in one card are declared by SettingsRows, never by classes at the call site. "Use Toggle for a single boolean setting where ON takes effect immediately, like `Password Protection` or `Auto-Cancel Builds`." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: a switch never applies on change; it is a row of the card, a draft until the footer\'s Save, as Vercel\'s settings cards are. A switch blocked from above is shown off and disabled, the footer saying why and never a tooltip. Required: the title and the description. The header\'s actions stand at the title\'s right; on a card narrower than 28 rem they keep the title\'s row and the description runs under both, as Vercel\'s cards read on a phone, and when the title\'s longest word would not fit beside them they move under the title, so they never cover it. The footer\'s left line (`footerHint`, `save.hint`) is optional and specific to the card, never a generic line repeated on every card: a limit, what a value is for, a plan it needs, a link; absent, it is nothing, and a card with neither a line nor a button has no footer. Ours: `save`: a typed value, one card with its own Save in the footer, always visible. The section owns the changed state: Save stays disabled until a field differs from what is saved and says `pendingLabel` while saving with Cancel beside it; a completed save calls `saved`, and the product raises one toast from its catalogue ("Completion toasts follow `{Noun} {past-participle}`: `Blob deleted`, `Domain added`, `Environment variable saved`. Never include `successfully`; the action name implies it." https://vercel.com/geist/toast), and Save returns to disabled, with no saved state in the footer. Ours: the section never raises a toast itself. A failed save keeps every value and shows under its field, or above the footer when no field owns it. `tone` marks a state with a command (a failed payment, a trial ending): a 1px border and a footer tinted in the hue with its line in it, the line saying the condition in words; title, line, fields and buttons stay neutral, an outline secondary then a default primary. A button takes the hue of its command, not of the card: `destructive` (red) for a permanent loss (Delete), `warning` (amber) for a disruptive command that can be undone (Pause, Archive). A delete card is `tone="error"` with the object\'s summary as its body; an archive card is neutral with a `warning` button in the footer. "Use a Note for inline contextual feedback next to the field, card, or section it describes: a region change warning above a region picker, a rate-limit notice next to a usage gauge." (https://vercel.com/geist/note) "One Note per concept." (https://vercel.com/geist/note) Ours: a message about the fields inside is a MessageNotice in the body, the card staying neutral. "Disabled checkboxes still need a Tooltip naming the reason; a greyed box with no explanation reads as a bug." (https://vercel.com/geist/checkbox) Ours: `lockedReason` (Geist\'s disabled wall) is the reason, written as the footer\'s line, not in a tooltip; it keeps the fields in view, inert with muted text, under a not-allowed cursor, the title and line unchanged; `unlock` is the way to get access, left out when nothing the person does unlocks it. A locked section has no `save` and no header `actions`.',
|
|
234
234
|
words: '"Helper text is sentence case, one sentence with a trailing period, on a sibling element wired through `aria-describedby`." (https://vercel.com/geist/input) Ours: the description is that helper text for the whole card, and no field has its own. "Render an optional one-sentence description as a sibling under the label that explains ON only. Don’t describe OFF; it’s the negation." (https://vercel.com/geist/toggle) Ours: a header-switch card\'s description says what turning it on does. The title names the values ("Personal details"); the description says what changing them does, in one line; the footer line is a Learn more link, a limit ("Use 32 characters at most."), what stays unaffected, or what is blocked and why ("Ask an owner to change this."); Save and Saving come from the product\'s catalogue, and so does the toast a `saved` callback raises; a delete card\'s description opens "Permanently delete".',
|
|
235
235
|
access: "\"Provide an accessible name through `children`, `aria-label`, or `aria-labelledby`.\" (https://vercel.com/geist/toggle) Ours: the title is the section's h2 and names the form that saves it, and the header switch. The footer's line is one status, announced politely; a failure under a field marks the control invalid and describes it. Locked fields are inert, so the section's title, description and reason are what a screen reader meets.",
|
|
236
236
|
},
|
|
@@ -405,7 +405,7 @@ export const USAGE = [
|
|
|
405
405
|
family: "Inputs",
|
|
406
406
|
summary: "One setting: its label above its control, and what went wrong under it.",
|
|
407
407
|
when: "The one value of a SettingsSection: a name, an address, a choice, a switch.",
|
|
408
|
-
instead: "A field of a drawer's or a dialog's form is a Field from the form's own owner; a read-only fact is a SettingsFactsSection; several switches
|
|
408
|
+
instead: "A field of a drawer's or a dialog's form is a Field from the form's own owner; a read-only fact is a SettingsFactsSection; several switches and selects in one card are SettingsRows; a switch that is the whole card and applies at once is the section's `headerSwitch`.",
|
|
409
409
|
behaviour: '"Helper text is sentence case, one sentence with a trailing period, on a sibling element wired through `aria-describedby`." (https://vercel.com/geist/input) Ours: label required, no help line: the card\'s description helps, and the (i) `info` is for a part of a value that needs a sentence. The control is 20rem wide under its label (`controlWidth`: `full` for a textarea, `natural` for a segmented control). `control="choice"` puts a checkbox or radio before its sentence; `control="switch"` puts the word for the switch\'s state after it and requires `stateLabel`. "Validate on blur, not on every keystroke; surface the message by passing a string to `error`." (https://vercel.com/geist/input) Ours: a validation `error` updates as the value is corrected, and it or a failed save about this field shows under the control and marks it invalid; colour sits on the control\'s edge and the message, never the label.',
|
|
410
410
|
words: '"Labels are short Title Case nouns: `Project Name`, `Domain`, `Environment Variable Name`." (https://vercel.com/geist/input) Ours: sentence case, a noun for the value ("Display name"). "Validation names the field and the constraint, ends in a period, and skips `please` (`Project name is required.`, `Code must be 6 digits.`)." (https://vercel.com/geist/input) Ours: an error says what to do next; `info.text` is one sentence and `info.label` names its button ("About display name"); a switch shows its state word ("Enabled", "Disabled") beside it.',
|
|
411
411
|
access: '"Don’t wrap a labelled `<Input>` in a `Tooltip`; put the explainer on a sibling icon button so the label stays announced." (https://vercel.com/geist/input) Ours: that sibling button is the (i) `info`. The control receives its id, `aria-invalid` and `aria-describedby` through `controlProps`, so the label, the info and the error are read with it. The info button has its own name and opens on focus.',
|
|
@@ -414,10 +414,10 @@ export const USAGE = [
|
|
|
414
414
|
name: "SettingsRows",
|
|
415
415
|
module: "settings-section",
|
|
416
416
|
family: "Inputs",
|
|
417
|
-
summary: "Several switches
|
|
418
|
-
when: "A card of
|
|
419
|
-
instead: "One setting is a SettingsField.
|
|
420
|
-
behaviour:
|
|
417
|
+
summary: "Several switches and selects in one card: each row its label on the left and its control on the right.",
|
|
418
|
+
when: "A card of switches and selects that the card's one description covers: a project's Git switches, a team's notification channels, a set of defaults, how long a computer stays awake beside whether the window goes to the tray.",
|
|
419
|
+
instead: "One setting is a SettingsField. A text box is a field of its own card.",
|
|
420
|
+
behaviour: "A row with `options` is a select, a row with `checked` a switch, and a row with `control` draws a control whose usage page puts it in a row, such as ThemeModeSwitcher; a card may hold any of them together, as Vercel's Root Directory card holds its switches; `stateLabels` is required once any row is a switch. Required on every row: the label and its `info` (an (i) with its sentence in a tooltip and on the control); no help line sits under a row. A switch row shows its state word beside the switch (`stateLabels`) and is controlled, so the word always follows the switch. Rows stand 16 px apart; below 28rem a row stacks its control under its label. \"Pair with a form footer only when the setting needs an explicit `Save` step.\" (https://vercel.com/geist/toggle) Ours: a switch is a draft the card's Save applies.",
|
|
421
421
|
words: '"Title Case noun phrase, 1–4 words, naming what is true when ON: `Password Protection`, not `Enable Password Protection`." (https://vercel.com/geist/toggle) Ours: sentence case, a noun for what it controls ("Pull request comments"), the same for a switch as for a select. "Render an optional one-sentence description as a sibling under the label that explains ON only. Don’t describe OFF; it’s the negation." (https://vercel.com/geist/toggle) Ours: that sentence is `info.text`. "Options are Title Case when short and match canonical branding (`Next.js`, not `NextJS`); keep the same register across the list." (https://vercel.com/geist/select) Ours: a select\'s options are sentence case.',
|
|
422
422
|
access: "Each row's control is named by the row's label and described by its info and its error. The info button has its own name and opens on focus.",
|
|
423
423
|
},
|
|
@@ -560,7 +560,7 @@ export const USAGE = [
|
|
|
560
560
|
summary: "A setting that is on or off.",
|
|
561
561
|
when: '"Use Toggle for a single boolean setting where ON takes effect immediately, like `Password Protection` or `Auto-Cancel Builds`." (https://vercel.com/geist/toggle) Ours: Geist\'s `Toggle` is our `Switch`, inside a settings section.',
|
|
562
562
|
instead: '"Switch to `Checkbox` for a multi-select list, or to `Switch` for 2–3 mutually exclusive views." (https://vercel.com/geist/toggle) Ours: Geist\'s `Switch` is our `SegmentedControl`. Our `Toggle` is a button that stays pressed, and several of them are a `ToggleGroup`. Choosing between named values is a `RadioGroup`.',
|
|
563
|
-
behaviour: '"`checked` is required and the component is controlled. Own the state and update it from `onChange`." (https://vercel.com/geist/toggle) "Persist on change and surface the result in a `useToasts().success(…)` toast (`Password protection enabled`); don’t leave the user wondering whether the flip stuck." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: `checked` is optional, since a switch also runs uncontrolled with `defaultChecked`; the toast is raised with `showMessageToast`; the reason for a disabled switch is written in the card\'s footer, and it is left off. Always named: `aria-label` or `aria-labelledby`, or an `id` that a `FieldLabel`\'s `htmlFor` points at, and a switch with none of them does not compile. A bare `Switch` is the 28 by 14 px track with an 11 px thumb; `md` is 36 by 20 px and `lg` 40 by 24 px. Colour: off is the input grey, on is
|
|
563
|
+
behaviour: '"`checked` is required and the component is controlled. Own the state and update it from `onChange`." (https://vercel.com/geist/toggle) "Persist on change and surface the result in a `useToasts().success(…)` toast (`Password protection enabled`); don’t leave the user wondering whether the flip stuck." (https://vercel.com/geist/toggle) "Pair with a form footer only when the setting needs an explicit `Save` step." (https://vercel.com/geist/toggle) "Disable Toggle only when the action is impossible (missing plan, locked policy); pair with helper text or a `Tooltip` that names the resolver." (https://vercel.com/geist/toggle) Ours: `checked` is optional, since a switch also runs uncontrolled with `defaultChecked`; the toast is raised with `showMessageToast`; the reason for a disabled switch is written in the card\'s footer, and it is left off. Always named: `aria-label` or `aria-labelledby`, or an `id` that a `FieldLabel`\'s `htmlFor` points at, and a switch with none of them does not compile. A bare `Switch` is the 28 by 14 px track with an 11 px thumb; `md` is 36 by 20 px and `lg` 40 by 24 px. Colour: off is the input grey, on is cobalto, the thumb white in both themes, and a disabled switch turns grey, on or off, with a greyer thumb and no shadow. Space flips it. On a settings page it is a draft its card\'s footer Save applies, and the product\'s toast confirms the save, in one of two places: in the card\'s body under its label, its state word after it (`SettingsField`\'s `control="switch"`); or at the end of a row of a card of several settings, the label to its left. A failed save keeps the draft and says so under the card.',
|
|
564
564
|
words: '"The label is `children`, not a `label` prop. Title Case noun phrase, 1–4 words, naming what is true when ON: `Password Protection`, not `Enable Password Protection`." (https://vercel.com/geist/toggle) "Render an optional one-sentence description as a sibling under the label that explains ON only." (https://vercel.com/geist/toggle) "Don’t describe OFF; it’s the negation." (https://vercel.com/geist/toggle) "Keep `labelCasing="title"` (the default) so labels match other Title Case surfaces." (https://vercel.com/geist/toggle) "Use `labelCasing="normal"` only when sentence-case content sits inline next to the toggle." (https://vercel.com/geist/toggle) Ours: the label is 12 px at medium weight, in sentence case: *Approvals*, with the card\'s description in one sentence. The state word is a separate word beside the switch, not its name.',
|
|
565
565
|
access: '"Provide an accessible name through `children`, `aria-label`, or `aria-labelledby`." (https://vercel.com/geist/toggle) "Geist warns in development if all three are missing." (https://vercel.com/geist/toggle) "Set `aria-label` only when the visible label sits elsewhere on the row; otherwise let `children` carry it so sighted and screen-reader copy stay aligned." (https://vercel.com/geist/toggle) Ours: `role="switch"` with `aria-checked`, the name the same in both states; a missing name does not compile.',
|
|
566
566
|
},
|
|
@@ -580,7 +580,7 @@ export const USAGE = [
|
|
|
580
580
|
module: "theme-mode-switcher",
|
|
581
581
|
family: "Choices",
|
|
582
582
|
summary: "The three appearances side by side, each with its glyph and its name.",
|
|
583
|
-
when: "An Appearance setting on a settings page, where the choice has room to show its names.",
|
|
583
|
+
when: "An Appearance setting on a settings page, where the choice has room to show its names: its own field, or a row of a SettingsRows card, `compact`, beside settings of the same card.",
|
|
584
584
|
instead: "Inside a menu, the same choice is ThemeModeMenu; a choice that is not the appearance is a RadioGroup or a ToggleGroup.",
|
|
585
585
|
behaviour: "Exclusive: one mode is selected; on a settings card the choice is a draft the card's Save applies. The order is System, Light, Dark everywhere.",
|
|
586
586
|
words: 'The setting\'s name, then each mode\'s name from the product\'s catalogue: "System", "Light", "Dark".',
|
|
@@ -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
|
},
|
|
@@ -678,8 +678,8 @@ export const USAGE = [
|
|
|
678
678
|
name: "BrandWordmark",
|
|
679
679
|
module: "brand",
|
|
680
680
|
family: "Status",
|
|
681
|
-
summary: "The
|
|
682
|
-
when: "Wherever the company is named
|
|
681
|
+
summary: "The wordmark: nadicode in full.",
|
|
682
|
+
when: "Wherever the company is named: the Portal's sidebar, the website's header, a footer. `BrandMark`, the n, where the company is recognised by its sign or the name does not fit; never the two side by side.",
|
|
683
683
|
instead: "A product's own mark (Nadia, Alveare) is that product's. A platform's is `PlatformMark`. The company named in a sentence is text.",
|
|
684
684
|
behaviour: "The letters take the text colour of what they sit in, so one form serves every ground. The dot is the logo's own colour, which turns to the brighter cobalt under any dark ancestor.",
|
|
685
685
|
words: "None beyond its name, *nadicode*, written as the glossary defines it.",
|
|
@@ -714,7 +714,7 @@ export const USAGE = [
|
|
|
714
714
|
summary: "What says which thing a record is, at a fixed size at the start of its row, its object's head or its feed line: an Agent's orb, an app's logo, a model maker's mark, a company's logo or initials, a person's picture, or a record kind's icon on its tile.",
|
|
715
715
|
when: "The first thing in a record's row (32), at the head of its object page (64), in a feed or a sidebar identity line (24), on a Library entry (40), for the company at the top of Nadia's list (28): wherever it tells a person which thing they are looking at.",
|
|
716
716
|
instead: "An icon beside a card's title, a KPI's label or a section heading only repeats the words and is left out. A picture for a first use or a whole-page moment is the empty state's or the page's own. A model is its `ModelIdentity`; a maker's mark beside a model's name in a list of models is `ModelMark` at 24.",
|
|
717
|
-
behaviour: "Static, except an Agent's orb, which moves with her state. An Agent is always her real orb from the orb engine, at the largest orb size not above the mark's, with no tile and no ring, never an avatar or a painted circle, and `frame` lets the product wrap that orb, such as in the transition that carries her to her page; an app keeps its own logo in its bordered tile on the page ground, or its first letter when it has no logo; a record that belongs to a model maker, such as a key held at that maker, is the maker's `ModelMark` at the mark's size, with its initials when LobeHub has no mark for it; a company is its own logo, contained whole in its original colours and proportions on a dark ringed tile, as Vercel shows a project's favicon, and until a logo is found, or when the stored logo does not load, its initials in white on that same tile, so a found logo only replaces the letters; a company's mark is the same whatever its standing and whichever directory opened it, and a Partner is said in words where its standing matters, never by a colour; a person is their picture, then their initials, in a round mark; a record with
|
|
717
|
+
behaviour: "Static, except an Agent's orb, which moves with her state. An Agent is always her real orb from the orb engine, at the largest orb size not above the mark's, with no tile and no ring, never an avatar or a painted circle, and `frame` lets the product wrap that orb, such as in the transition that carries her to her page; an app keeps its own logo in its bordered tile on the page ground, or its first letter when it has no logo; a record that belongs to a model maker, such as a key held at that maker, is the maker's `ModelMark` at the mark's size, with its initials when LobeHub has no mark for it; a company is its own logo, contained whole in its original colours and proportions on a dark ringed tile, as Vercel shows a project's favicon, and until a logo is found, or when the stored logo does not load, its initials in white on that same tile, so a found logo only replaces the letters; a company's mark is the same whatever its standing and whichever directory opened it, and a Partner is said in words where its standing matters, never by a colour; a person is their picture, then their initials, in a round mark; a record with no picture of its own takes its kind's own icon, the same icon its section has in the navigation (Mail for a mailbox, a server for an MCP server, a key for a password), in a ringed tile on the card ground; a skill takes the icon its hub card names, and the Skills icon when it names none, on that same neutral ringed tile, so the icon alone tells one skill from another and no colour stands for the kind; a plugin takes the `info-soft` ground with the Plugins icon, without a ring; a record's own object picture, such as a Library skill's or plugin's, is drawn only at 64, at the head of its own page, on its kind's ground in the icon's place, because at a row's size its detail does not read. A record tile takes `{rounded.sm}` (6 px) corners, `{rounded.md}` (8 px) at 40 and 64; a company's tile and an app's tile take `{rounded.md}` at every size.",
|
|
718
718
|
words: "None of its own: the record's name stands beside it. Initials stand for a company or a person: the first letters of the name's first two words. An app with no logo takes the first letter of its name as a monogram, a model maker with no mark the initials of its slug, and no other kind takes letters.",
|
|
719
719
|
access: "Decorative beside the record's name. Alone, as in a narrow rail, `label` names it.",
|
|
720
720
|
},
|