@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.
Files changed (42) hide show
  1. package/dist/components/agent-orb.d.ts +5 -1
  2. package/dist/components/agent-orb.d.ts.map +1 -1
  3. package/dist/components/agent-orb.js +5 -1
  4. package/dist/components/brand.d.ts +5 -5
  5. package/dist/components/brand.d.ts.map +1 -1
  6. package/dist/components/brand.js +5 -5
  7. package/dist/components/catalog-entry.d.ts +5 -4
  8. package/dist/components/catalog-entry.d.ts.map +1 -1
  9. package/dist/components/catalog-entry.js +4 -3
  10. package/dist/components/company-card/renderer.d.ts +8 -0
  11. package/dist/components/company-card/renderer.d.ts.map +1 -1
  12. package/dist/components/company-card/renderer.js +122 -53
  13. package/dist/components/company-card.d.ts.map +1 -1
  14. package/dist/components/company-card.js +70 -39
  15. package/dist/components/hive-scene.d.ts.map +1 -1
  16. package/dist/components/hive-scene.js +46 -8
  17. package/dist/components/identity-mark.d.ts +4 -4
  18. package/dist/components/identity-mark.d.ts.map +1 -1
  19. package/dist/components/identity-mark.js +2 -3
  20. package/dist/components/scroll-area.js +3 -3
  21. package/dist/components/settings-section.d.ts +26 -15
  22. package/dist/components/settings-section.d.ts.map +1 -1
  23. package/dist/components/settings-section.js +23 -16
  24. package/dist/components/switch.d.ts +2 -1
  25. package/dist/components/switch.d.ts.map +1 -1
  26. package/dist/components/switch.js +3 -2
  27. package/dist/components/theme-mode-switcher.d.ts +1 -1
  28. package/dist/components/theme-mode-switcher.js +1 -1
  29. package/dist/internal/agent-orb-motion.d.ts +4 -0
  30. package/dist/internal/agent-orb-motion.d.ts.map +1 -1
  31. package/dist/internal/agent-orb-motion.js +1 -0
  32. package/dist/internal/agent-orb.d.ts +1 -0
  33. package/dist/internal/agent-orb.d.ts.map +1 -1
  34. package/dist/internal/agent-orb.js +207 -86
  35. package/dist/showcase/specimens/compositions.js +1 -1
  36. package/dist/showcase/specimens/inputs.d.ts.map +1 -1
  37. package/dist/showcase/specimens/inputs.js +2 -2
  38. package/dist/showcase/specimens/status.js +2 -2
  39. package/dist/showcase/usage.generated.d.ts.map +1 -1
  40. package/dist/showcase/usage.generated.js +13 -13
  41. package/dist/standalone/index.js +2 -2
  42. 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
- const appearanceTokens = [
32
- ...celebrationTokens,
33
- ...nadiaStripes.filter((token) => token !== null),
34
- ...Object.values(statusTokens),
35
- "--nc-duration-wait",
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 <= 32 ? 20 : 64);
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 === "ambient" || done ? "idle" : state;
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 motion = window.matchMedia("(prefers-reduced-motion: reduce)");
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, 0.1);
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
- if (now - lastPaint >= 1000 / 30 || !running()) {
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
- if (running())
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
- request = requestAnimationFrame(tick);
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
- return resolved || color;
285
- }
286
- function readAppearanceKey(appearance, side) {
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 refreshIfChanged() {
295
- if (readAppearanceKey(getComputedStyle(canvas), measure()) !== appearanceKey)
296
- refresh();
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 refresh() {
431
+ function resize(side) {
299
432
  if (disposed || !canvas.isConnected)
300
433
  return;
301
- painted = measure();
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
- const appearance = getComputedStyle(canvas);
307
- appearanceKey = readAppearanceKey(appearance, painted);
439
+ play();
440
+ }
441
+ function refresh(appearance, side) {
442
+ appearanceKey = appearance.key;
308
443
  color = appearance.color;
309
- timing = readOrbTiming(appearance);
444
+ timing = appearance.timing;
310
445
  statusColors = {
311
- waiting: computedColor(appearance.getPropertyValue(statusTokens.waiting).trim() || color),
312
- blocked: computedColor(appearance.getPropertyValue(statusTokens.blocked).trim() || color),
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.getPropertyValue(token).trim() || color} 75%, white)`));
315
- stripeColors = (figureStripes[form] ?? []).map((token) => (token && appearance.getPropertyValue(token).trim()) || color);
316
- play();
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
- const visibility = new IntersectionObserver(([entry]) => {
319
- visible = entry?.isIntersecting ?? false;
320
- play();
321
- });
322
- visibility.observe(canvas);
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
- // Layout, not the declared size, decides the pixels, so the box is observed
331
- // rather than remounted on: an orb sized by its container stays sharp.
332
- const box = new ResizeObserver(() => {
333
- if (Math.abs(measure() - painted) >= 0.5)
334
- refresh();
335
- });
336
- box.observe(canvas);
337
- // A colour transition on the canvas or on an element it inherits from hands
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
- document.addEventListener("transitionend", settle, true);
348
- motion.addEventListener("change", play);
349
- document.addEventListener("visibilitychange", play);
350
- window.addEventListener("resize", refreshIfChanged);
351
- refresh();
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
- cancelAnimationFrame(request);
366
- visibility.disconnect();
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, tone: "skill" },
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,gCAsD5B;AAED,eAAO,MAAM,MAAM;;;;;;;;;;;;;;CAoHQ,CAAC"}
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, { kind: "switch", rows: [
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, { kind: "select", rows: [
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, tone: "skill" })] })),
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, tone: "skill" }, name: "Write quotes from the price list" }) })),
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: a single SettingsField, the description its only help. 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`. One more shape exists, declared by a part and never by classes at the call site. A card of several settings holds SettingsRows, label left and control right, only when every row is a switch or every row is a select. "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`.',
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 or selects in one card are SettingsRows; a switch that is the whole card and applies at once is the section's `headerSwitch`.",
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, or several selects, in one card: each row its label on the left and its control on the right.",
418
- when: "A card of settings of one kind, all switches or all selects, that the card's one description covers: a project's Git switches, a team's notification channels, a set of defaults.",
419
- instead: "One setting is a SettingsField. Fields of different kinds, text boxes among them, are one card each.",
420
- behaviour: 'Declares `kind` once; every row is that kind, so a card cannot mix a switch with a select. 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.',
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 ink, 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.',
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: '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. On a photograph the orb takes the stronger `presence="over-picture"` weight; everywhere else it is `plain`.',
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 company logo: nadicode in full.",
682
- when: "Wherever the company is named by its logo: the Portal's sidebar, the website's header, a footer. `BrandMark`, the plain n, where the name does not fit.",
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 its own object picture, such as a Library skill or plugin, draws that picture on its kind's ground; 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 Library skill takes the purple category ground with the Skills icon and a plugin the `info-soft` ground with the Plugins icon, both without a ring, so the ground says which kind it is, and an entry that has its own object picture draws it on that ground in the icon's place. 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.",
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
  },