@robr0/design-system 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +30 -27
  2. package/components/AgentStatus/AgentStatus.css +496 -0
  3. package/components/AgentStatus/AgentStatus.d.ts +41 -0
  4. package/components/AgentStatus/AgentStatus.js +85 -0
  5. package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
  6. package/components/AgentStatus/AgentStatusPatterns.js +20 -0
  7. package/components/AiButton/AiButton.css +163 -0
  8. package/components/AiButton/AiButton.d.ts +36 -0
  9. package/components/AiButton/AiButton.js +58 -0
  10. package/components/AlertDialog/AlertDialog.css +5 -5
  11. package/components/Button/Button.d.ts +14 -2
  12. package/components/ButtonGroup/ButtonGroup.js +2 -9
  13. package/components/ChatHeader/ChatHeader.css +35 -0
  14. package/components/ChatHeader/ChatHeader.d.ts +30 -0
  15. package/components/ChatHeader/ChatHeader.js +17 -0
  16. package/components/ChatMarker/ChatMarker.css +52 -0
  17. package/components/ChatMarker/ChatMarker.d.ts +22 -0
  18. package/components/ChatMarker/ChatMarker.js +18 -0
  19. package/components/ChatMessage/ChatMessage.css +304 -0
  20. package/components/ChatMessage/ChatMessage.d.ts +68 -0
  21. package/components/ChatMessage/ChatMessage.js +69 -0
  22. package/components/ChatThread/ChatThread.css +171 -0
  23. package/components/ChatThread/ChatThread.d.ts +41 -0
  24. package/components/ChatThread/ChatThread.js +174 -0
  25. package/components/Checkbox/Checkbox.d.ts +17 -3
  26. package/components/Chip/Chip.css +27 -0
  27. package/components/Chip/Chip.d.ts +2 -2
  28. package/components/CircularButton/CircularButton.d.ts +31 -10
  29. package/components/CircularButton/CircularButton.js +54 -50
  30. package/components/CodeBlock/CodeBlock.css +2 -2
  31. package/components/Combobox/Combobox.d.ts +10 -2
  32. package/components/Composer/Composer.css +164 -0
  33. package/components/Composer/Composer.d.ts +67 -0
  34. package/components/Composer/Composer.js +120 -0
  35. package/components/ContextMenu/ContextMenu.css +0 -4
  36. package/components/DateInput/DateInput.d.ts +5 -1
  37. package/components/Dialog/Dialog.css +5 -5
  38. package/components/DocumentChip/DocumentChip.css +181 -0
  39. package/components/DocumentChip/DocumentChip.d.ts +41 -0
  40. package/components/DocumentChip/DocumentChip.js +78 -0
  41. package/components/Dropdown/Dropdown.css +2 -4
  42. package/components/Dropdown/Dropdown.d.ts +10 -2
  43. package/components/DropdownMenu/DropdownMenu.css +2 -4
  44. package/components/FileInput/FileInput.d.ts +5 -1
  45. package/components/Input/Input.d.ts +5 -1
  46. package/components/InterruptCard/InterruptCard.css +164 -0
  47. package/components/InterruptCard/InterruptCard.d.ts +55 -0
  48. package/components/InterruptCard/InterruptCard.js +57 -0
  49. package/components/LinkList/LinkList.d.ts +1 -0
  50. package/components/MessageActions/MessageActions.css +76 -0
  51. package/components/MessageActions/MessageActions.d.ts +38 -0
  52. package/components/MessageActions/MessageActions.js +33 -0
  53. package/components/MessageCard/MessageCard.css +112 -0
  54. package/components/MessageCard/MessageCard.d.ts +35 -0
  55. package/components/MessageCard/MessageCard.js +28 -0
  56. package/components/NavList/NavList.css +155 -0
  57. package/components/NavList/NavList.d.ts +56 -0
  58. package/components/NavList/NavList.js +99 -0
  59. package/components/PromptSuggestions/PromptSuggestions.css +87 -0
  60. package/components/PromptSuggestions/PromptSuggestions.d.ts +51 -0
  61. package/components/PromptSuggestions/PromptSuggestions.js +34 -0
  62. package/components/Prose/Prose.css +252 -0
  63. package/components/Prose/Prose.d.ts +21 -0
  64. package/components/Prose/Prose.js +14 -0
  65. package/components/RadioButton/RadioButton.d.ts +17 -3
  66. package/components/Reasoning/Reasoning.css +276 -0
  67. package/components/Reasoning/Reasoning.d.ts +55 -0
  68. package/components/Reasoning/Reasoning.js +98 -0
  69. package/components/SelectionCard/SelectionCard.d.ts +5 -1
  70. package/components/Slider/Slider.d.ts +5 -1
  71. package/components/SourceChip/SourceChip.css +102 -0
  72. package/components/SourceChip/SourceChip.d.ts +32 -0
  73. package/components/SourceChip/SourceChip.js +31 -0
  74. package/components/Stat/Stat.css +2 -2
  75. package/components/Textarea/Textarea.d.ts +5 -1
  76. package/components/Timeline/Timeline.d.ts +2 -0
  77. package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
  78. package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
  79. package/components/ToolCall/ToolCall.css +213 -0
  80. package/components/ToolCall/ToolCall.d.ts +43 -0
  81. package/components/ToolCall/ToolCall.js +89 -0
  82. package/components/registry.json +145 -8
  83. package/components/registry.json.d.ts +145 -8
  84. package/components/registry.json.js +2 -2
  85. package/index.d.ts +18 -0
  86. package/index.js +36 -0
  87. package/package.json +8 -4
  88. package/tokens/registry.json +13 -0
  89. package/tokens/registry.json.d.ts +13 -0
  90. package/tokens/registry.json.js +1 -1
  91. package/tokens/tokens-dark.css +13 -0
  92. package/tokens/tokens-light.css +42 -0
  93. package/tokens/tokens-motion.css +3 -1
  94. package/tokens/tokens-typography.css +24 -0
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- # robr0 Portfolio + Design System
1
+ # robr0: portfolio + AI-ready design system
2
2
 
3
3
  [![CI](https://github.com/robritacca-dotcom/design-system/actions/workflows/ci.yml/badge.svg)](https://github.com/robritacca-dotcom/design-system/actions/workflows/ci.yml)
4
4
  <!-- npm-badge:start -->
5
5
  [![npm](https://img.shields.io/npm/v/@robr0%2Fdesign-system?logo=npm&color=CB3837)](https://www.npmjs.com/package/@robr0/design-system)
6
6
  <!-- npm-badge:end -->
7
7
 
8
- A personal portfolio site built on a custom React design system I designed and engineered from scratch. The DS isn't a side project it's the backbone every portfolio page is built on, ensuring the work is presented consistently and with the same craft standards I apply professionally.
8
+ A personal portfolio site built on an AI-ready React design system, designed and engineered from scratch. Claude Code builds the components from the written specs in this repo (`CLAUDE.md` for the rules, `design.md` for the design language, `content-design.md` for how every word reads), and generated registries keep this README and the docs site from drifting. The design system is the backbone every portfolio page is built on, so the work is presented consistently and to the same craft standards throughout.
9
9
 
10
10
  **[→ Live site](https://robertritacca.com/)** · **[→ Storybook](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)**
11
11
 
12
- The **live site** is the portfolio built on the design system. **Storybook** is the interactive component explorer for the design system itself two separate parts of this repo.
12
+ The **live site** is the portfolio built on the design system. **Storybook** is the interactive component explorer for the design system itself. They are two separate parts of this repo.
13
13
 
14
14
  ---
15
15
 
@@ -17,8 +17,8 @@ The **live site** is the portfolio built on the design system. **Storybook** is
17
17
 
18
18
  | Part | Description |
19
19
  |---|---|
20
- | **Portfolio website** (`/website`) | Next.js app with case studies, work history, writing, and about pages all built exclusively with the design system components below. **[Live site →](https://robertritacca.com/)** |
21
- | **Design system** (`/src`) | <!-- component-count -->61<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
20
+ | **Portfolio website** (`/website`) | Next.js app with case studies, work history, writing, and about pages, all built exclusively with the design system components below. **[Live site →](https://robertritacca.com/)** |
21
+ | **Design system** (`/src`) | <!-- component-count -->78<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
22
22
 
23
23
  ---
24
24
 
@@ -27,12 +27,12 @@ The **live site** is the portfolio built on the design system. **Storybook** is
27
27
  ### Components
28
28
 
29
29
  <!-- component-list:start -->
30
- Accordion · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Kbd · Link list · Navigation · Pagination · Popover · Progress bar · Quote · Radio button · Section title · Segmented control · Selection card · Skeleton · Slider · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tooltip
30
+ Accordion · Agent status · AI button · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Interrupt card · Kbd · Link list · Message actions · Message card · Navigation · Nav list · Pagination · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radio button · Reasoning · Section title · Segmented control · Selection card · Skeleton · Slider · Source chip · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tool call · Tooltip
31
31
  <!-- component-list:end -->
32
32
 
33
33
  ### Using the package
34
34
 
35
- The design system is published as [`@robr0/design-system`](https://www.npmjs.com/package/@robr0/design-system) (React 19+ is a peer dependency) the **[full setup guide](https://www.robertritacca.com/docs/get-started)** covers install, dark mode, fonts, and re-theming:
35
+ The design system is published as [`@robr0/design-system`](https://www.npmjs.com/package/@robr0/design-system) (React 19+ is a peer dependency). The **[full setup guide](https://robertritacca.com/docs/get-started)** covers install, dark mode, fonts, and re-theming:
36
36
 
37
37
  ```bash
38
38
  npm install @robr0/design-system
@@ -53,14 +53,14 @@ Deep imports work too (`@robr0/design-system/components/Button/Button`). Chart c
53
53
  import { BarChart, LineChart } from '@robr0/design-system/charts';
54
54
  ```
55
55
 
56
- **Theming and customization** happen through CSS variables no configuration API. Components are provider-free, with one exception: wrap your tree in `ToastProvider` if (and only if) you use the toast queue via `useToast`.
56
+ **Theming and customisation** happen through CSS variables; there is no configuration API. Components are provider-free, with one exception: wrap your tree in `ToastProvider` if (and only if) you use the toast queue via `useToast`.
57
57
 
58
58
  - **Dark mode**: set `data-theme="dark"` on the root element (light is the default).
59
59
  - **Font**: the whole type scale chains to one token. Load any font you like and override it:
60
60
  ```css
61
61
  :root { --font-family-primary: 'Inter', sans-serif; }
62
62
  ```
63
- - **Colors, radius, spacing**: every semantic token chains to a primitive, so overriding a primitive re-themes everything built on it:
63
+ - **Colours, radius, spacing**: every semantic token chains to a primitive, so overriding a primitive re-themes everything built on it:
64
64
  ```css
65
65
  :root {
66
66
  --primitive-teal-07: #7C3AED; /* your brand color becomes the action color */
@@ -68,11 +68,11 @@ import { BarChart, LineChart } from '@robr0/design-system/charts';
68
68
  }
69
69
  ```
70
70
 
71
- Icons use a bundled Material Symbols Rounded variable font (woff2) no extra setup. Nunito Sans, the system's default typeface, is intentionally *not* bundled: load it yourself (e.g. Google Fonts or `next/font`) or override `--font-family-primary`.
71
+ Icons use a bundled Material Symbols Rounded variable font (woff2), with no extra setup. Nunito Sans, the system's default typeface, is intentionally *not* bundled: load it yourself (e.g. Google Fonts or `next/font`) or override `--font-family-primary`.
72
72
 
73
73
  ### Token architecture
74
74
 
75
- Tokens flow in one direction primitives are never referenced directly in components:
75
+ Tokens flow in one direction, and primitives are never referenced directly in components:
76
76
 
77
77
  ```
78
78
  tokens-primitives.css --primitive-teal-07: #118AB2
@@ -82,40 +82,43 @@ tokens-light/dark.css --color-action-primary-bg: var(--primitive-teal-07)
82
82
  Component CSS background-color: var(--color-action-primary-bg)
83
83
  ```
84
84
 
85
- Dark mode is driven by `data-theme="dark"` on the root element no `prefers-color-scheme` queries in components.
85
+ Dark mode is driven by `data-theme="dark"` on the root element, with no `prefers-color-scheme` queries in components.
86
86
 
87
87
  ### Design principles
88
88
 
89
- - **Single typeface**: Nunito Sans (weight 300 for display, 600 for headings, 400/500 for body)
90
- - **White-floor UI**: depth comes from a container color ramp, not box shadows
91
- - **Teal is the action color**: primary buttons and focus rings only — never decorative
92
- - **Five status variants**: info, positive, warning, error, neutral — shared tokens across Badge, Alert, Toast, ProgressBar
93
- - **Material Symbols Rounded** for all icons
89
+ Stated as token roles on purpose what each role resolves to is the theme, and yours to override:
94
90
 
95
- Full spec in [`design.md`](design.md).
91
+ - **Style with semantic tokens, never raw values** — that is what makes one primitive override cascade through everything
92
+ - **One typeface**, hierarchy carried by weight contrast
93
+ - **The primary-action token means "click here"**: CTAs and focus only, never decoration
94
+ - **Shape is a per-element-type token**, not a per-instance choice
95
+ - **Five status roles** (info, positive, warning, error, neutral) shared by every status-bearing component
96
+ - **Depth is token-owned**: the container ramp plus the system's elevation tokens — components don't bring their own shadows
97
+
98
+ The defaults behind each role, and every component spec, live in [`design.md`](design.md).
96
99
 
97
100
  ---
98
101
 
99
102
  ## Tech
100
103
 
101
- - **React 19 + TypeScript** component library
102
- - **Vite 7** dev server and library build
103
- - **Next.js 16** portfolio site and DS documentation
104
- - **Storybook 10** component explorer
105
- - **Vitest + Playwright** every Storybook story runs as a render test in headless Chromium
106
- - **CSS custom properties** all theming via semantic tokens, no CSS-in-JS
104
+ - **React 19 + TypeScript**: component library
105
+ - **Vite 7**: dev server and library build
106
+ - **Next.js 16**: portfolio site and design system documentation
107
+ - **Storybook 10**: component explorer
108
+ - **Vitest + Playwright**: every Storybook story runs as a render test in headless Chromium
109
+ - **CSS custom properties**: all theming via semantic tokens, no CSS-in-JS
107
110
 
108
111
  ---
109
112
 
110
113
  ## Quality & CI
111
114
 
112
- Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: **lint + library build**, **story tests** (every Storybook story rendered in headless Chromium via Vitest), **Storybook build**, and **website lint + build**. The same checklist runs locally with one command:
115
+ Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: lint + library build, story tests (every Storybook story rendered in headless Chromium via Vitest), Storybook build, and website lint + build. The same checklist runs locally with one command:
113
116
 
114
117
  ```bash
115
118
  npm run verify # lint + library type-check + package build + story tests + Storybook build + website lint + build
116
119
  ```
117
120
 
118
- CI also guards against documentation drift: generated surfaces (this README's component count and list, the website's skills pages, the published CLAUDE.md/design.md blueprints) are rebuilt from their source registries on every build, and CI fails if the committed copies are stale. The numbers on the site are never hand-written.
121
+ CI also guards against documentation drift: generated surfaces (this README's component count and list, the website's skills pages, the published CLAUDE.md, design.md, and content-design.md blueprints) are rebuilt from their source registries on every build, and CI fails if the committed copies are stale. The numbers on the site are never hand-written.
119
122
 
120
123
  ---
121
124
 
@@ -125,7 +128,7 @@ CI also guards against documentation drift: generated surfaces (this README's co
125
128
  # Storybook (the library's dev sandbox)
126
129
  npm run storybook # http://localhost:6006
127
130
 
128
- # Portfolio + documentation website (npm workspace install once at the root)
131
+ # Portfolio + documentation website (npm workspace: install once at the root)
129
132
  npm install
130
133
  npm run dev --workspace website # http://localhost:3000
131
134
  ```
@@ -0,0 +1,496 @@
1
+ /* ============================================
2
+ AGENT STATUS COMPONENT
3
+ A dot-matrix indicator plus a line of status
4
+ text, reporting what an agent is doing now.
5
+
6
+ The matrix is a 4x3 grid of 12 dots, numbered
7
+ row-major:
8
+
9
+ 1 2 3 4
10
+ 5 6 7 8
11
+ 9 10 11 12
12
+
13
+ Every pattern is choreography over that same
14
+ grid: each dot is told which of the cycle's
15
+ twelve slots it lights in (--ds-agent-step),
16
+ and dots a pattern never lights opt out of the
17
+ animation entirely. One grid, one cycle length,
18
+ fifteen patterns — so changing pattern never
19
+ changes the indicator's footprint or rhythm.
20
+ ============================================ */
21
+
22
+ .ds-agent-status {
23
+ --ds-agent-color: var(--color-text-secondary);
24
+ --ds-agent-label-color: var(--color-text-secondary);
25
+ --ds-agent-shimmer-base: var(--color-text-primary);
26
+ --ds-agent-shimmer-sweep: var(--color-text-tertiary);
27
+ --ds-agent-dot-dim: 0.2;
28
+ --ds-agent-dot-rest: 0.82;
29
+ --ds-agent-cycle: var(--motion-duration-loop-matrix);
30
+ --ds-agent-slot: calc(var(--ds-agent-cycle) / 12);
31
+ --ds-agent-matrix-size: var(--icon-size-md);
32
+
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: var(--gap-sm-md);
36
+ font-family: var(--font-paragraph-family);
37
+ font-size: var(--font-paragraph-size);
38
+ font-weight: var(--font-paragraph-weight);
39
+ line-height: var(--font-paragraph-line-height);
40
+ letter-spacing: var(--font-paragraph-letter-spacing);
41
+ }
42
+
43
+ /* ============================================
44
+ MATRIX
45
+ ============================================ */
46
+
47
+ .ds-agent-status__matrix {
48
+ display: grid;
49
+ grid-template-columns: repeat(4, 1fr);
50
+ gap: var(--gap-xxs);
51
+ flex: none;
52
+ width: var(--ds-agent-matrix-size);
53
+ }
54
+
55
+ .ds-agent-status__dot {
56
+ aspect-ratio: 1;
57
+ border-radius: var(--radius-full);
58
+ background-color: var(--ds-agent-color);
59
+ opacity: var(--ds-agent-dot-dim);
60
+ transform: scale(var(--ds-agent-dot-rest));
61
+ }
62
+
63
+ /* ============================================
64
+ LABEL
65
+ ============================================ */
66
+
67
+ .ds-agent-status__label {
68
+ position: relative;
69
+ color: var(--ds-agent-label-color);
70
+ min-width: 0;
71
+ }
72
+
73
+ /* Label crossfade: when the label string changes, the outgoing copy sits
74
+ absolutely over the incoming one and fades up and out while the new one
75
+ fades up and in — one continuous upward motion. */
76
+ .ds-agent-status__label-text {
77
+ display: inline-block;
78
+ }
79
+
80
+ /* The animations live only on the modifier classes, and the modifiers come
81
+ off once the swap settles: a filled animation can pin the span to its
82
+ own compositor layer, which would escape the shimmer's text clip and
83
+ leave the label invisible for good. */
84
+ .ds-agent-status__label-text--in {
85
+ animation: ds-agent-label-in var(--motion-duration-slow) var(--motion-ease-entrance) both;
86
+ animation-delay: calc(var(--motion-duration-slow) / 2);
87
+ }
88
+
89
+ .ds-agent-status__label-text--out {
90
+ position: absolute;
91
+ left: 0;
92
+ top: 0;
93
+ white-space: nowrap;
94
+ animation: ds-agent-label-out var(--motion-duration-slow) var(--motion-ease-standard) both;
95
+ animation-delay: 0s;
96
+ }
97
+
98
+ @keyframes ds-agent-label-in {
99
+ from {
100
+ opacity: 0;
101
+ transform: translateY(8px);
102
+ }
103
+ }
104
+
105
+ @keyframes ds-agent-label-out {
106
+ to {
107
+ opacity: 0;
108
+ transform: translateY(-8px);
109
+ }
110
+ }
111
+
112
+ /* A gradient between the shimmer base and sweep colours, moving left to
113
+ right: with an oversized background image, background-position 100% shows
114
+ its right edge, 0% its left, so counting down walks the sweep band across
115
+ the text. */
116
+ .ds-agent-status__label--shimmer {
117
+ background-image: linear-gradient(
118
+ 100deg,
119
+ var(--ds-agent-shimmer-base) 35%,
120
+ var(--ds-agent-shimmer-sweep) 50%,
121
+ var(--ds-agent-shimmer-base) 65%
122
+ );
123
+ background-size: 250% 100%;
124
+ background-clip: text;
125
+ -webkit-background-clip: text;
126
+ color: transparent;
127
+ animation: ds-agent-shimmer var(--motion-duration-loop-shimmer)
128
+ var(--motion-ease-linear) infinite;
129
+ }
130
+
131
+ /* While a label crossfade runs, the shimmer's text clip stands down: an
132
+ animated descendant composites on its own layer, which escapes
133
+ background-clip: text and renders the text invisible. The swap motion
134
+ covers the pause; the sweep resumes on the settled text. */
135
+ .ds-agent-status__label--shimmer.ds-agent-status__label--swapping {
136
+ background-image: none;
137
+ animation: none;
138
+ color: var(--ds-agent-label-color);
139
+ }
140
+
141
+ /* ============================================
142
+ STATES
143
+ The three working states are deliberately
144
+ monochrome. Colour is reserved for the states
145
+ where it carries meaning: waiting on a person,
146
+ finished, failed.
147
+ ============================================ */
148
+
149
+ .ds-agent-status--idle {
150
+ --ds-agent-color: var(--color-text-tertiary);
151
+ --ds-agent-label-color: var(--color-text-tertiary);
152
+ }
153
+
154
+ .ds-agent-status--waiting {
155
+ --ds-agent-color: var(--color-status-warning-text);
156
+ --ds-agent-label-color: var(--color-status-warning-text);
157
+
158
+ /* Waiting keeps its warning colour through the shimmer — colour is the
159
+ signal, so the sweep only brightens it rather than going monochrome. */
160
+ --ds-agent-shimmer-base: var(--color-status-warning-text);
161
+ --ds-agent-shimmer-sweep: var(--color-text-primary);
162
+ }
163
+
164
+ .ds-agent-status--done {
165
+ --ds-agent-color: var(--color-status-positive-text);
166
+ --ds-agent-label-color: var(--color-status-positive-text);
167
+ }
168
+
169
+ .ds-agent-status--error {
170
+ --ds-agent-color: var(--color-status-error-text);
171
+ --ds-agent-label-color: var(--color-status-error-text);
172
+ }
173
+
174
+ /* Terminal states fill the matrix solid — the shape itself reads as finished */
175
+ .ds-agent-status--done .ds-agent-status__dot,
176
+ .ds-agent-status--error .ds-agent-status__dot {
177
+ opacity: 1;
178
+ transform: none;
179
+ }
180
+
181
+ /* ============================================
182
+ SIZES
183
+ The scale pairs with ChatMessage: default
184
+ matches default message text, compact
185
+ matches compact message text.
186
+ ============================================ */
187
+
188
+ .ds-agent-status--compact {
189
+ --ds-agent-matrix-size: var(--icon-size-sm);
190
+
191
+ gap: var(--gap-sm);
192
+ font-family: var(--font-paragraph-sm-family);
193
+ font-size: var(--font-paragraph-sm-size);
194
+ font-weight: var(--font-paragraph-sm-weight);
195
+ line-height: var(--font-paragraph-sm-line-height);
196
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
197
+ }
198
+
199
+ /* ============================================
200
+ VARIANTS
201
+ ============================================ */
202
+
203
+ .ds-agent-status--bar {
204
+ display: flex;
205
+ width: 100%;
206
+ padding: var(--padding-xs) var(--padding-sm-md);
207
+ background-color: var(--color-bg-container-secondary);
208
+ border: var(--border-xs) solid var(--color-bg-container-border);
209
+ border-radius: var(--radius-md);
210
+ }
211
+
212
+ /* ============================================
213
+ ANIMATION ENGINE
214
+ Each dot blinks once per cycle, offset by the
215
+ slot its pattern assigns it.
216
+ ============================================ */
217
+
218
+ .ds-agent-status--animated .ds-agent-status__dot {
219
+ animation: ds-agent-dot var(--ds-agent-cycle) var(--motion-ease-standard) infinite;
220
+ animation-delay: calc(var(--ds-agent-step, 0) * var(--ds-agent-slot));
221
+ }
222
+
223
+ /* A dot ramps up over roughly one slot and falls away over three, so the
224
+ leading edge arrives as smoothly as the trail leaves. Popping straight to
225
+ full opacity reads as a strobe at this size. */
226
+ @keyframes ds-agent-dot {
227
+ 0% {
228
+ opacity: var(--ds-agent-dot-dim);
229
+ transform: scale(var(--ds-agent-dot-rest));
230
+ }
231
+
232
+ 10% {
233
+ opacity: 1;
234
+ transform: scale(1);
235
+ }
236
+
237
+ 35%,
238
+ 100% {
239
+ opacity: var(--ds-agent-dot-dim);
240
+ transform: scale(var(--ds-agent-dot-rest));
241
+ }
242
+ }
243
+
244
+ /* Fill sweep holds each column lit until the band has passed */
245
+ @keyframes ds-agent-dot-fill {
246
+ 0% {
247
+ opacity: var(--ds-agent-dot-dim);
248
+ transform: scale(var(--ds-agent-dot-rest));
249
+ }
250
+
251
+ 8%,
252
+ 74% {
253
+ opacity: 1;
254
+ transform: scale(1);
255
+ }
256
+
257
+ 86%,
258
+ 100% {
259
+ opacity: var(--ds-agent-dot-dim);
260
+ transform: scale(var(--ds-agent-dot-rest));
261
+ }
262
+ }
263
+
264
+ /* Breathe swells the whole matrix rather than travelling through it */
265
+ @keyframes ds-agent-dot-breathe {
266
+ 0%,
267
+ 100% {
268
+ opacity: var(--ds-agent-dot-dim);
269
+ transform: scale(var(--ds-agent-dot-rest));
270
+ }
271
+
272
+ 50% {
273
+ opacity: 1;
274
+ transform: scale(1);
275
+ }
276
+ }
277
+
278
+ @keyframes ds-agent-shimmer {
279
+ from {
280
+ background-position-x: 100%;
281
+ }
282
+
283
+ to {
284
+ background-position-x: 0%;
285
+ }
286
+ }
287
+
288
+ /* ============================================
289
+ PATTERNS
290
+ One block per pattern: the slot each dot
291
+ lights in, and the dots it leaves dark.
292
+ ============================================ */
293
+
294
+ /* Braille — a 2x3 cell rotating, the right half dark */
295
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
296
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 2; }
297
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 4; }
298
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 6; }
299
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 8; }
300
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 10; }
301
+
302
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(3),
303
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(4),
304
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(7),
305
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(8),
306
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(11),
307
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(12) {
308
+ animation: none;
309
+ }
310
+
311
+ /* Orbit — clockwise around the perimeter, core dark */
312
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
313
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 1; }
314
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 2; }
315
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 3; }
316
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 4; }
317
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 5; }
318
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 6; }
319
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 7; }
320
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 8; }
321
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 9; }
322
+
323
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(6),
324
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(7) {
325
+ animation: none;
326
+ }
327
+
328
+ /* Breathe — every dot together, on its own swell */
329
+ .ds-agent-status--animated.ds-agent-status--breathe .ds-agent-status__dot {
330
+ animation-name: ds-agent-dot-breathe;
331
+ }
332
+
333
+ /* Snake — boustrophedon, one dot at a time */
334
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
335
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 1; }
336
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 2; }
337
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 3; }
338
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 4; }
339
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 5; }
340
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 6; }
341
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 7; }
342
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 8; }
343
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 9; }
344
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 10; }
345
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 11; }
346
+
347
+ /* Fill sweep — columns light and hold, a band wiping left to right */
348
+ .ds-agent-status--animated.ds-agent-status--fill-sweep .ds-agent-status__dot {
349
+ animation-name: ds-agent-dot-fill;
350
+ }
351
+
352
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n + 1) { --ds-agent-step: 0; }
353
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n + 2) { --ds-agent-step: 3; }
354
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n + 3) { --ds-agent-step: 6; }
355
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n) { --ds-agent-step: 9; }
356
+
357
+ /* Pulse — rings expanding from the core */
358
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(6),
359
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 0; }
360
+
361
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(2),
362
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(3),
363
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(5),
364
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(8),
365
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(10),
366
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 4; }
367
+
368
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(1),
369
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(4),
370
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(9),
371
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 8; }
372
+
373
+ /* Columns — odd columns, then even */
374
+ .ds-agent-status--columns .ds-agent-status__dot:nth-child(odd) { --ds-agent-step: 0; }
375
+ .ds-agent-status--columns .ds-agent-status__dot:nth-child(even) { --ds-agent-step: 6; }
376
+
377
+ /* Checkerboard — the two parities alternating */
378
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(1),
379
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(3),
380
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(6),
381
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(8),
382
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(9),
383
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 0; }
384
+
385
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(2),
386
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(4),
387
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(5),
388
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(7),
389
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(10),
390
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 6; }
391
+
392
+ /* Scan — a single column travelling left to right */
393
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n + 1) { --ds-agent-step: 0; }
394
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n + 2) { --ds-agent-step: 3; }
395
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n + 3) { --ds-agent-step: 6; }
396
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n) { --ds-agent-step: 9; }
397
+
398
+ /* Rain — drops falling down each column, columns out of phase */
399
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
400
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 2; }
401
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 4; }
402
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 4; }
403
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 6; }
404
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 8; }
405
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 2; }
406
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 4; }
407
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 6; }
408
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 6; }
409
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 8; }
410
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 10; }
411
+
412
+ /* Cascade — column by column, top to bottom */
413
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
414
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 1; }
415
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 2; }
416
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 3; }
417
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 4; }
418
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 5; }
419
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 6; }
420
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 7; }
421
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 8; }
422
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 9; }
423
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 10; }
424
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 11; }
425
+
426
+ /* Sparkle — a fixed scatter, so it reads as random without being random */
427
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 7; }
428
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 2; }
429
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 10; }
430
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 4; }
431
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 0; }
432
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 9; }
433
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 5; }
434
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 11; }
435
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 3; }
436
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 8; }
437
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 1; }
438
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 6; }
439
+
440
+ /* Wave rows — whole rows breaking top to bottom */
441
+ .ds-agent-status--wave-rows .ds-agent-status__dot:nth-child(-n + 4) { --ds-agent-step: 0; }
442
+ .ds-agent-status--wave-rows .ds-agent-status__dot:nth-child(n + 5):nth-child(-n + 8) { --ds-agent-step: 4; }
443
+ .ds-agent-status--wave-rows .ds-agent-status__dot:nth-child(n + 9) { --ds-agent-step: 8; }
444
+
445
+ /* Helix — two strands crossing, everything off-strand dark */
446
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(1),
447
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 0; }
448
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 3; }
449
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(3),
450
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 6; }
451
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 9; }
452
+
453
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(2),
454
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(4),
455
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(5),
456
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(7),
457
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(10),
458
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(12) {
459
+ animation: none;
460
+ }
461
+
462
+ /* Diagonal swipe — anti-diagonals, top-left to bottom-right */
463
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
464
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(2),
465
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 2; }
466
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(3),
467
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(6),
468
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 4; }
469
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(4),
470
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(7),
471
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 6; }
472
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(8),
473
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 8; }
474
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 10; }
475
+
476
+ /* ============================================
477
+ REDUCED MOTION
478
+ The global guard collapses every animation to
479
+ its final frame, which for the matrix is dark.
480
+ Park it at a legible resting state instead and
481
+ let the label carry the meaning on its own.
482
+ ============================================ */
483
+
484
+ @media (prefers-reduced-motion: reduce) {
485
+ .ds-agent-status--animated .ds-agent-status__dot {
486
+ animation: none;
487
+ opacity: 0.55;
488
+ transform: none;
489
+ }
490
+
491
+ .ds-agent-status__label--shimmer {
492
+ background-image: none;
493
+ color: var(--ds-agent-label-color);
494
+ animation: none;
495
+ }
496
+ }