@medalsocial/meda 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +26 -0
  2. package/dist/brand/index.d.ts +2 -0
  3. package/dist/brand/index.js +1 -0
  4. package/dist/brand/medal-social-mark.d.ts +7 -0
  5. package/dist/brand/medal-social-mark.js +6 -0
  6. package/dist/brand/public.d.ts +2 -0
  7. package/dist/brand/public.js +1 -0
  8. package/dist/chat/latency-badge.js +6 -2
  9. package/dist/chat/tool-call-block.js +1 -1
  10. package/dist/chat/turn-card.js +3 -1
  11. package/dist/components/ui/command.d.ts +19 -0
  12. package/dist/components/ui/command.js +37 -0
  13. package/dist/components/ui/dialog.d.ts +26 -0
  14. package/dist/components/ui/dialog.js +39 -0
  15. package/dist/components/ui/drawer.d.ts +16 -0
  16. package/dist/components/ui/drawer.js +35 -0
  17. package/dist/components/ui/dropdown-menu.d.ts +32 -0
  18. package/dist/components/ui/dropdown-menu.js +68 -0
  19. package/dist/components/ui/tooltip.d.ts +6 -0
  20. package/dist/components/ui/tooltip.js +17 -0
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.js +3 -1
  23. package/dist/lib/utils.d.ts +2 -0
  24. package/dist/lib/utils.js +5 -0
  25. package/dist/marketing/index.d.ts +5 -0
  26. package/dist/marketing/index.js +4 -0
  27. package/dist/marketing/marketing-callout.d.ts +2 -0
  28. package/dist/marketing/marketing-callout.js +8 -0
  29. package/dist/marketing/marketing-contact.d.ts +2 -0
  30. package/dist/marketing/marketing-contact.js +19 -0
  31. package/dist/marketing/marketing-cta-list.d.ts +2 -0
  32. package/dist/marketing/marketing-cta-list.js +21 -0
  33. package/dist/marketing/marketing-lead-magnet.d.ts +2 -0
  34. package/dist/marketing/marketing-lead-magnet.js +9 -0
  35. package/dist/marketing/public.d.ts +1 -0
  36. package/dist/marketing/public.js +1 -0
  37. package/dist/marketing/types.d.ts +67 -0
  38. package/dist/marketing/types.js +1 -0
  39. package/dist/marketing/utils.d.ts +2 -0
  40. package/dist/marketing/utils.js +6 -0
  41. package/dist/panel/inspector-json.js +5 -3
  42. package/dist/shell/app-shell.d.ts +9 -0
  43. package/dist/shell/app-shell.js +11 -0
  44. package/dist/shell/command-palette.d.ts +23 -0
  45. package/dist/shell/command-palette.js +138 -0
  46. package/dist/shell/context-rail.d.ts +31 -0
  47. package/dist/shell/context-rail.js +81 -0
  48. package/dist/shell/extras/index.d.ts +4 -0
  49. package/dist/shell/extras/index.js +4 -0
  50. package/dist/shell/extras/shell-desktop-panel-dock.d.ts +14 -0
  51. package/dist/shell/extras/shell-desktop-panel-dock.js +43 -0
  52. package/dist/shell/extras/shell-scrollable-content.d.ts +9 -0
  53. package/dist/shell/extras/shell-scrollable-content.js +7 -0
  54. package/dist/shell/extras/shell-tab-bar.d.ts +18 -0
  55. package/dist/shell/extras/shell-tab-bar.js +27 -0
  56. package/dist/shell/extras/types.d.ts +64 -0
  57. package/dist/shell/extras/types.js +2 -0
  58. package/dist/shell/extras/workbench-layout.d.ts +10 -0
  59. package/dist/shell/extras/workbench-layout.js +29 -0
  60. package/dist/shell/icon-rail.d.ts +29 -0
  61. package/dist/shell/icon-rail.js +38 -0
  62. package/dist/shell/index.d.ts +24 -15
  63. package/dist/shell/index.js +35 -15
  64. package/dist/shell/layout-state.d.ts +32 -0
  65. package/dist/shell/layout-state.js +98 -0
  66. package/dist/shell/mobile/mobile-bottom-nav.d.ts +15 -0
  67. package/dist/shell/mobile/mobile-bottom-nav.js +76 -0
  68. package/dist/shell/mobile/mobile-drawers.d.ts +20 -0
  69. package/dist/shell/mobile/mobile-drawers.js +62 -0
  70. package/dist/shell/mobile/mobile-header.d.ts +23 -0
  71. package/dist/shell/mobile/mobile-header.js +22 -0
  72. package/dist/shell/motion.d.ts +22 -0
  73. package/dist/shell/motion.js +19 -0
  74. package/dist/shell/public.d.ts +16 -2
  75. package/dist/shell/public.js +20 -1
  76. package/dist/shell/resizable-shell.d.ts +45 -0
  77. package/dist/shell/resizable-shell.js +15 -0
  78. package/dist/shell/right-panel.d.ts +15 -0
  79. package/dist/shell/right-panel.js +126 -0
  80. package/dist/shell/shell-auth-frame.d.ts +22 -0
  81. package/dist/shell/shell-auth-frame.js +60 -0
  82. package/dist/shell/shell-header.d.ts +10 -9
  83. package/dist/shell/shell-header.js +57 -14
  84. package/dist/shell/shell-layout-utils.d.ts +2 -2
  85. package/dist/shell/shell-main.d.ts +8 -0
  86. package/dist/shell/shell-main.js +11 -0
  87. package/dist/shell/shell-provider.d.ts +65 -0
  88. package/dist/shell/shell-provider.js +152 -0
  89. package/dist/shell/shell-route-utils.d.ts +1 -1
  90. package/dist/shell/theme-next-themes.d.ts +4 -0
  91. package/dist/shell/theme-next-themes.js +24 -0
  92. package/dist/shell/theme.d.ts +9 -0
  93. package/dist/shell/theme.js +58 -0
  94. package/dist/shell/types.d.ts +41 -41
  95. package/dist/shell/types.js +1 -0
  96. package/dist/shell/use-shell-viewport.d.ts +2 -0
  97. package/dist/shell/use-shell-viewport.js +40 -0
  98. package/dist/shell/utils.d.ts +1 -1
  99. package/dist/styles/globals.css +26 -0
  100. package/dist/styles/theme.css +147 -0
  101. package/dist/styles/tokens.css +338 -0
  102. package/dist/timeline/date-switcher.js +3 -1
  103. package/dist/timeline/event-card.js +5 -13
  104. package/dist/timeline/live-indicator.js +3 -1
  105. package/dist/timeline/scrub-bar.js +8 -9
  106. package/dist/timeline/timeline-rail.js +6 -4
  107. package/dist/timeline/timeline-tape.js +7 -8
  108. package/dist/voice/voice-level.js +3 -1
  109. package/dist/voice/voice-orb-scene.js +1 -0
  110. package/dist/voice/voice-orb.js +9 -11
  111. package/dist/voice/voice-status-pill.js +4 -9
  112. package/package.json +53 -6
@@ -0,0 +1,338 @@
1
+ /* ============================================================
2
+ Meda design tokens — canonical from 00-design-system.lib.pen
3
+ Source: /Users/ali/Library/CloudStorage/GoogleDrive-ali@medalsocial.com/
4
+ Delte disker/03 Product/Pencil Design/00-design-system.lib.pen
5
+ Retrieved via mcp__pencil__get_variables 2026-04-27.
6
+
7
+ NOTE: 00-design-system.pen and 050-meda-ui.pen are illustrative
8
+ companion boards — not normative. This file is derived from the
9
+ .lib.pen (library file) which is the sole canonical contract.
10
+ ============================================================ */
11
+
12
+ /* ============================================================
13
+ Layer 0 — Color primitives (theme: brand, constant)
14
+ ============================================================ */
15
+ :root {
16
+ /* Brand (purple) — 11 stops */
17
+ --color-brand-50: #fafafd;
18
+ --color-brand-100: #eeeaf5;
19
+ --color-brand-200: #dcd4e8;
20
+ --color-brand-300: #b8a3d2;
21
+ --color-brand-400: #9a6ac2;
22
+ --color-brand-500: #7e3fac;
23
+ --color-brand-600: #6a2e96;
24
+ --color-brand-700: #482070;
25
+ --color-brand-800: #2f1552;
26
+ --color-brand-900: #1c0e38;
27
+ --color-brand-950: #120a24;
28
+
29
+ /* Neutral (zinc-aligned) — 11 stops */
30
+ --color-neutral-50: #fafafa;
31
+ --color-neutral-100: #f4f4f5;
32
+ --color-neutral-200: #e4e4e7;
33
+ --color-neutral-300: #a1a1aa;
34
+ --color-neutral-400: #71717a;
35
+ --color-neutral-500: #52525b;
36
+ --color-neutral-600: #3f3f46;
37
+ --color-neutral-700: #27272a;
38
+ --color-neutral-800: #18181b;
39
+ --color-neutral-900: #111113;
40
+ --color-neutral-950: #09090b;
41
+
42
+ /* Error (rose) — 10 stops */
43
+ --color-error-50: #fff1f2;
44
+ --color-error-100: #ffe4e6;
45
+ --color-error-200: #fecdd3;
46
+ --color-error-300: #fda4af;
47
+ --color-error-400: #fb7185;
48
+ --color-error-500: #f43f5e;
49
+ --color-error-600: #e11d48;
50
+ --color-error-700: #be123c;
51
+ --color-error-800: #9f1239;
52
+ --color-error-900: #881337;
53
+
54
+ /* Info (blue) — 10 stops */
55
+ --color-info-50: #eff6ff;
56
+ --color-info-100: #dbeafe;
57
+ --color-info-200: #bfdbfe;
58
+ --color-info-300: #93c5fd;
59
+ --color-info-400: #60a5fa;
60
+ --color-info-500: #3b82f6;
61
+ --color-info-600: #2563eb;
62
+ --color-info-700: #1d4ed8;
63
+ --color-info-800: #1e3a8a;
64
+ --color-info-900: #172554;
65
+
66
+ /* Success (teal) — 10 stops */
67
+ --color-success-50: #f0fdfa;
68
+ --color-success-100: #ccfbf1;
69
+ --color-success-200: #99f6e4;
70
+ --color-success-300: #5eead4;
71
+ --color-success-400: #2dd4bf;
72
+ --color-success-500: #14b8a6;
73
+ --color-success-600: #0d9488;
74
+ --color-success-700: #0f766e;
75
+ --color-success-800: #115e59;
76
+ --color-success-900: #134e4a;
77
+
78
+ /* Warning (amber) — 10 stops */
79
+ --color-warning-50: #fffbeb;
80
+ --color-warning-100: #fef3c7;
81
+ --color-warning-200: #fde68a;
82
+ --color-warning-300: #fcd34d;
83
+ --color-warning-400: #fbbf24;
84
+ --color-warning-500: #f59e0b;
85
+ --color-warning-600: #d97706;
86
+ --color-warning-700: #b45309;
87
+ --color-warning-800: #92400e;
88
+ --color-warning-900: #78350f;
89
+
90
+ /* Surface primitives (dark-mode only) */
91
+ --color-surface-bg: #09090b;
92
+ --color-surface-card: #18181b;
93
+ --color-surface-muted: #27272a;
94
+ --color-surface-border: #2e2e33;
95
+ --color-surface-sidebar: #0f0f12;
96
+ }
97
+
98
+ /* ============================================================
99
+ Layer 1 — shadcn semantic + status + sidebar + chart + text
100
+ Light mode (default on :root)
101
+ ============================================================ */
102
+ :root {
103
+ /* shadcn shape */
104
+ --background: var(--color-neutral-50);
105
+ --foreground: var(--color-neutral-950);
106
+ --card: var(--color-neutral-50);
107
+ --card-foreground: var(--color-neutral-950);
108
+ --popover: var(--color-neutral-50);
109
+ --popover-foreground: var(--color-neutral-950);
110
+ --primary: var(--color-brand-800);
111
+ --primary-foreground: var(--color-brand-50);
112
+ --secondary: var(--color-neutral-200);
113
+ --secondary-foreground: var(--color-neutral-950);
114
+ --muted: var(--color-neutral-100);
115
+ --muted-foreground: var(--color-neutral-500);
116
+ --muted-foreground-strong: var(--color-neutral-600);
117
+ --accent: var(--color-neutral-200);
118
+ --accent-foreground: var(--color-neutral-950);
119
+ --destructive: var(--color-error-600);
120
+ --destructive-foreground: var(--color-neutral-50);
121
+ --destructive-bg: var(--color-error-50);
122
+ --danger: var(--color-error-600);
123
+ --border: var(--color-neutral-200);
124
+ --input: var(--color-neutral-200);
125
+ --ring: var(--color-brand-600);
126
+
127
+ /* Status */
128
+ --success: var(--color-success-600);
129
+ --success-foreground: var(--color-neutral-950);
130
+ --success-text: var(--color-success-700);
131
+ --warning: var(--color-warning-600);
132
+ --warning-foreground: var(--color-neutral-950);
133
+ --warning-text: var(--color-warning-800);
134
+ --info: var(--color-info-600);
135
+ --info-foreground: var(--color-neutral-50);
136
+
137
+ /* Sidebar */
138
+ --sidebar: var(--color-neutral-100);
139
+ --sidebar-foreground: var(--color-neutral-950);
140
+ --sidebar-primary: var(--color-brand-700);
141
+ --sidebar-primary-foreground: var(--color-neutral-50);
142
+ --sidebar-accent: var(--color-neutral-50);
143
+ --sidebar-accent-foreground: var(--color-neutral-950);
144
+ --sidebar-border: var(--color-neutral-200);
145
+ --sidebar-ring: var(--color-brand-500);
146
+
147
+ /* Chart */
148
+ --chart-1: var(--color-brand-600);
149
+ --chart-2: var(--color-info-600);
150
+ --chart-3: var(--color-success-600);
151
+ --chart-4: var(--color-warning-600);
152
+ --chart-5: var(--color-error-600);
153
+
154
+ /* Text */
155
+ --text: var(--color-neutral-950);
156
+ --text-foreground: var(--color-neutral-950);
157
+ --text-muted: var(--color-neutral-500);
158
+ --text-link: var(--color-brand-600);
159
+ --text-primary: var(--color-neutral-950);
160
+ --text-secondary: var(--color-neutral-600);
161
+ --highlight-foreground: var(--color-brand-500);
162
+ }
163
+
164
+ /* Dark mode overrides — class AND data-theme to support both runtime + Storybook */
165
+ .dark,
166
+ [data-theme="dark"] {
167
+ /* shadcn shape */
168
+ --background: var(--color-surface-bg);
169
+ --foreground: var(--color-neutral-50);
170
+ --card: var(--color-surface-card);
171
+ --card-foreground: var(--color-neutral-50);
172
+ --popover: var(--color-surface-card);
173
+ --popover-foreground: var(--color-neutral-50);
174
+ --primary: var(--color-brand-100);
175
+ --primary-foreground: var(--color-brand-900);
176
+ --secondary: var(--color-surface-muted);
177
+ --secondary-foreground: var(--color-neutral-50);
178
+ --muted: var(--color-surface-muted);
179
+ --muted-foreground: var(--color-neutral-300);
180
+ --muted-foreground-strong: var(--color-neutral-300);
181
+ --accent: var(--color-surface-muted);
182
+ --accent-foreground: var(--color-neutral-50);
183
+ --destructive: var(--color-error-400);
184
+ --destructive-foreground: var(--color-neutral-950);
185
+ --destructive-bg: var(--color-error-900);
186
+ --danger: var(--color-error-400);
187
+ --border: var(--color-surface-border);
188
+ --input: var(--color-surface-muted);
189
+ --ring: var(--color-brand-400);
190
+
191
+ /* Status */
192
+ --success: var(--color-success-400);
193
+ --success-foreground: var(--color-neutral-950);
194
+ --success-text: var(--color-success-400);
195
+ --warning: var(--color-warning-400);
196
+ --warning-foreground: var(--color-neutral-950);
197
+ --warning-text: var(--color-warning-400);
198
+ --info: var(--color-info-400);
199
+ --info-foreground: var(--color-neutral-950);
200
+
201
+ /* Sidebar */
202
+ --sidebar: var(--color-surface-sidebar);
203
+ --sidebar-foreground: var(--color-neutral-50);
204
+ --sidebar-primary: var(--color-brand-400);
205
+ --sidebar-primary-foreground: var(--color-neutral-950);
206
+ --sidebar-accent: var(--color-surface-card);
207
+ --sidebar-accent-foreground: var(--color-neutral-50);
208
+ --sidebar-border: var(--color-surface-border);
209
+ --sidebar-ring: var(--color-brand-400);
210
+
211
+ /* Chart */
212
+ --chart-1: var(--color-brand-500);
213
+ --chart-2: var(--color-info-500);
214
+ --chart-3: var(--color-success-400);
215
+ --chart-4: var(--color-warning-500);
216
+ --chart-5: var(--color-error-400);
217
+
218
+ /* Text */
219
+ --text: var(--color-neutral-50);
220
+ --text-foreground: var(--color-neutral-50);
221
+ --text-muted: var(--color-neutral-300);
222
+ --text-link: var(--color-brand-400);
223
+ --text-primary: var(--color-neutral-100);
224
+ --text-secondary: var(--color-neutral-300);
225
+ --highlight-foreground: var(--color-brand-200);
226
+ }
227
+
228
+ /* ============================================================
229
+ Layer 2 — Shell semantic + sizing
230
+ ============================================================ */
231
+ :root {
232
+ --shell-header: var(--color-neutral-50);
233
+ --shell-rail: var(--color-neutral-50);
234
+ --shell-context: var(--color-neutral-50);
235
+ --shell-main: var(--color-neutral-100);
236
+ --shell-panel: var(--color-neutral-50);
237
+ --shell-border: var(--color-neutral-200);
238
+ --shell-shadow: 0 4px 12px hsl(0 0% 0% / 0.04);
239
+ --shell-header-height: 56px;
240
+ --shell-rail-width: 60px;
241
+ --shell-context-default: 300px;
242
+ --shell-context-min: 240px;
243
+ --shell-context-max: 420px;
244
+ --shell-panel-default: 340px;
245
+ --shell-panel-min: 300px;
246
+ --shell-panel-max: 520px;
247
+ --shell-bottom-nav-height: 56px;
248
+ --z-shell-header: 40;
249
+ --z-shell-panel: 45;
250
+ --z-shell-fullscreen: 60;
251
+ }
252
+
253
+ .dark,
254
+ [data-theme="dark"] {
255
+ --shell-header: var(--color-surface-card);
256
+ --shell-rail: var(--color-surface-sidebar);
257
+ --shell-context: var(--color-surface-sidebar);
258
+ --shell-main: var(--color-surface-bg);
259
+ --shell-panel: var(--color-surface-card);
260
+ --shell-border: var(--color-surface-border);
261
+ --shell-shadow: 0 4px 12px hsl(0 0% 0% / 0.45);
262
+ }
263
+
264
+ /* ============================================================
265
+ Motion (theme: base, constant)
266
+ Mirror of motion.ts (Phase 5)
267
+ ============================================================ */
268
+ :root {
269
+ --motion-fast: 140ms;
270
+ --motion-default: 200ms;
271
+ --motion-panel: 240ms;
272
+ --motion-fullscreen: 280ms;
273
+ --motion-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
274
+ }
275
+
276
+ /* ============================================================
277
+ Typography (theme: base, constant)
278
+ Font family: Geist + Geist Mono only. No Inter.
279
+ ============================================================ */
280
+ :root {
281
+ --font-family-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
282
+ --font-family-mono: "Geist Mono", "SF Mono", "Fira Code", monospace;
283
+ --font-size-display: 36px;
284
+ --font-size-h1: 30px;
285
+ --font-size-h2: 24px;
286
+ --font-size-h3: 20px;
287
+ --font-size-h4: 18px;
288
+ --font-size-body-lg: 16px;
289
+ --font-size-body: 14px;
290
+ --font-size-body-sm: 13px;
291
+ --font-size-caption: 11px;
292
+ --font-size-overline: 10px;
293
+ --font-size-mono-data: 12px;
294
+ --font-size-mono-caption: 10px;
295
+ --font-weight-regular: 400;
296
+ --font-weight-medium: 500;
297
+ --font-weight-semibold: 600;
298
+ --font-weight-bold: 700;
299
+ --line-height-display: 1.1;
300
+ --line-height-heading: 1.15;
301
+ --line-height-tight: 1.2;
302
+ --line-height-snug: 1.25;
303
+ --line-height-normal: 1.5;
304
+ --line-height-relaxed: 1.6;
305
+ --letter-spacing-tighter: -0.5px;
306
+ --letter-spacing-tight: -0.2px;
307
+ --letter-spacing-normal: 0;
308
+ --letter-spacing-wide: 1.2px;
309
+ }
310
+
311
+ /* ============================================================
312
+ Radii (theme: base, constant)
313
+ ============================================================ */
314
+ :root {
315
+ --radius: 8px;
316
+ --radius-sm: 4px;
317
+ --radius-md: 6px;
318
+ --radius-lg: 8px;
319
+ --radius-xl: 12px;
320
+ --radius-2xl: 16px;
321
+ --radius-3xl: 20px;
322
+ --radius-4xl: 24px;
323
+ --radius-full: 9999px;
324
+ }
325
+
326
+ /* ============================================================
327
+ Spacing scale (theme: base, constant)
328
+ ============================================================ */
329
+ :root {
330
+ --spacing-xs: 4px;
331
+ --spacing-sm: 8px;
332
+ --spacing-md: 12px;
333
+ --spacing-lg: 16px;
334
+ --spacing-xl: 24px;
335
+ --spacing-2xl: 32px;
336
+ --spacing-3xl: 48px;
337
+ --spacing-4xl: 64px;
338
+ }
@@ -1,5 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Calendar, ChevronLeft, ChevronRight } from 'lucide-react';
4
+ import { cn } from '../lib/utils.js';
3
5
  // "YYYY-MM-DD" in the given tz (or local if undefined). Comparing these
4
6
  // strings is a robust same-day check that doesn't depend on local-time math.
5
7
  function ymdInTz(d, tz) {
@@ -58,5 +60,5 @@ export function DateSwitcher({ value, now, tz, onChange, className }) {
58
60
  const isToday = isSameDay(value, now, tz);
59
61
  const goPrev = () => onChange(addDays(value, -1, tz));
60
62
  const goNext = () => onChange(addDays(value, 1, tz));
61
- return (_jsxs("div", { className: ['flex items-center gap-2', className ?? ''].join(' '), children: [_jsx("button", { type: "button", "aria-label": "Previous day", onClick: goPrev, className: "inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground", children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsxs("div", { className: "flex flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-sm", children: [_jsx(Calendar, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: fmtDate(value, tz) }), isToday && (_jsx("span", { className: "ml-1 rounded-full bg-emerald-500/15 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-emerald-500", children: "today" }))] }), _jsx("button", { type: "button", "aria-label": "Next day", disabled: isToday, onClick: goNext, className: "inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent", children: _jsx(ChevronRight, { className: "size-4" }) })] }));
63
+ return (_jsxs("div", { className: cn('flex items-center gap-2', className), children: [_jsx("button", { type: "button", "aria-label": "Previous day", onClick: goPrev, className: "inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground", children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsxs("div", { className: "flex flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-sm", children: [_jsx(Calendar, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: fmtDate(value, tz) }), isToday && (_jsx("span", { className: "ml-1 rounded-full bg-success/15 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-success-800", children: "today" }))] }), _jsx("button", { type: "button", "aria-label": "Next day", disabled: isToday, onClick: goNext, className: "inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent", children: _jsx(ChevronRight, { className: "size-4" }) })] }));
62
64
  }
@@ -1,26 +1,18 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../lib/utils.js';
2
4
  export function EventCard({ event, size = 'default', onSelect, className }) {
3
5
  const isLive = event.isLive ?? false;
4
6
  const isSelected = event.selected ?? false;
5
7
  const isError = event.kind === 'error';
6
- const liveClasses = isLive
7
- ? 'border-emerald-500/40 bg-gradient-to-b from-emerald-500/10 to-card'
8
- : '';
8
+ const liveClasses = isLive ? 'border-success/40 bg-gradient-to-b from-success/10 to-card' : '';
9
9
  const errorClasses = isError ? 'border-destructive/40' : '';
10
10
  const selectedClasses = isSelected
11
11
  ? 'border-primary/60 bg-primary/[0.10] shadow-[0_4px_12px_rgba(0,0,0,0.15)]'
12
12
  : '';
13
13
  const sizeClasses = size === 'tiny' ? 'px-2 py-1 text-[11px]' : 'px-2.5 py-2 text-xs';
14
- return (_jsxs("button", { type: "button", "data-live": String(isLive), "data-selected": String(isSelected), "data-kind": event.kind, onClick: () => onSelect?.(event), className: [
15
- 'relative w-full cursor-pointer rounded-md border border-border bg-card text-left transition-[border-color,transform] duration-100',
16
- 'hover:translate-x-px hover:border-primary/40',
17
- sizeClasses,
18
- liveClasses,
19
- errorClasses,
20
- selectedClasses,
21
- className ?? '',
22
- ].join(' '), children: [_jsx("span", { "aria-hidden": "true", className: "absolute -left-2.5 top-1/2 h-px w-2.5 bg-border" }), _jsxs("span", { className: "flex items-center gap-1.5 font-medium", children: [isLive && _jsx(LiveDot, {}), _jsx("span", { className: "min-w-0 flex-1 truncate", children: event.primary })] }), size === 'default' && event.secondary != null && (_jsx("span", { className: "mt-0.5 block text-[10px] text-muted-foreground tabular-nums", children: event.secondary }))] }));
14
+ return (_jsxs("button", { type: "button", "data-live": String(isLive), "data-selected": String(isSelected), "data-kind": event.kind, onClick: () => onSelect?.(event), className: cn('relative w-full cursor-pointer rounded-md border border-border bg-card text-left transition-[border-color,transform] duration-100', 'hover:translate-x-px hover:border-primary/40', sizeClasses, liveClasses, errorClasses, selectedClasses, className), children: [_jsx("span", { "aria-hidden": "true", className: "absolute -left-2.5 top-1/2 h-px w-2.5 bg-border" }), _jsxs("span", { className: "flex items-center gap-1.5 font-medium", children: [isLive && _jsx(LiveDot, {}), _jsx("span", { className: "min-w-0 flex-1 truncate", children: event.primary })] }), size === 'default' && event.secondary != null && (_jsx("span", { className: "mt-0.5 block text-[10px] text-muted-foreground tabular-nums", children: event.secondary }))] }));
23
15
  }
24
16
  function LiveDot() {
25
- return (_jsx("span", { "aria-hidden": "true", className: "inline-block size-1.5 shrink-0 animate-pulse rounded-full bg-emerald-500 shadow-[0_0_0_3px_rgba(16,185,129,0.20)]" }));
17
+ return (_jsx("span", { "aria-hidden": "true", className: "inline-block size-1.5 shrink-0 animate-pulse rounded-full bg-success shadow-[0_0_0_3px_rgba(16,185,129,0.20)]" }));
26
18
  }
@@ -1,5 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { formatClock } from '../lib/format-time.js';
4
+ import { cn } from '../lib/utils.js';
3
5
  export function LiveIndicator({ now, tz, className }) {
4
- return (_jsxs("div", { role: "status", "aria-live": "polite", className: ['relative h-0 pointer-events-none', className ?? ''].join(' '), children: [_jsx("span", { className: "pointer-events-auto absolute left-0 top-0 inline-flex items-center rounded-md bg-emerald-500 px-2.5 py-1 text-[11px] font-bold uppercase tracking-wider text-white shadow-[0_6px_18px_rgba(16,185,129,0.35)]", children: "LIVE" }), _jsx("span", { "aria-hidden": "true", className: "absolute right-0 top-1/2 left-[60px] h-px bg-gradient-to-r from-emerald-500 via-emerald-500/30 to-transparent" }), _jsx("span", { className: "absolute right-1.5 top-[-2px] text-[10px] font-semibold tabular-nums text-emerald-500", children: formatClock(now, { tz }) })] }));
6
+ return (_jsxs("div", { role: "status", "aria-live": "polite", className: cn('relative h-0 pointer-events-none', className), children: [_jsx("span", { className: "pointer-events-auto absolute left-0 top-0 inline-flex items-center rounded-md bg-success-700 px-2.5 py-1 text-[11px] font-bold uppercase tracking-wider text-white shadow-[0_6px_18px_rgba(16,185,129,0.35)]", children: "LIVE" }), _jsx("span", { "aria-hidden": "true", className: "absolute right-0 top-1/2 left-[60px] h-px bg-gradient-to-r from-success via-success/30 to-transparent" }), _jsx("span", { className: "absolute right-1.5 top-[-2px] text-[10px] font-semibold tabular-nums text-success-700", children: formatClock(now, { tz }) })] }));
5
7
  }
@@ -1,11 +1,13 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Pause, Play, SkipBack, SkipForward } from 'lucide-react';
3
4
  import { useRef } from 'react';
4
5
  import { formatDuration } from '../lib/format-time.js';
6
+ import { cn } from '../lib/utils.js';
5
7
  const KIND_COLOR = {
6
8
  turn: 'bg-primary',
7
- tool: 'bg-amber-500',
8
- barge: 'bg-emerald-500',
9
+ tool: 'bg-warning',
10
+ barge: 'bg-success',
9
11
  error: 'bg-destructive',
10
12
  };
11
13
  export function ScrubBar({ durationMs, positionMs, isLive = false, marks, onSeek, isPlaying, onPlayPause, onSkipBack, onSkipForward, className, }) {
@@ -32,13 +34,10 @@ export function ScrubBar({ durationMs, positionMs, isLive = false, marks, onSeek
32
34
  onSeek(Math.min(durationMs, positionMs + step));
33
35
  }
34
36
  };
35
- return (_jsxs("div", { className: ['rounded-2xl border border-border bg-card p-3.5', className ?? ''].join(' '), children: [_jsxs("div", { className: "mb-2.5 flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Timeline \u00B7 this call" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("span", { className: "px-1.5 text-[11px] tabular-nums text-muted-foreground", children: [formatDuration(positionMs), isLive ? ' / live' : ` / ${formatDuration(durationMs)}`] }), onSkipBack && (_jsx(TransportBtn, { label: "Skip back", onClick: onSkipBack, children: _jsx(SkipBack, { className: "size-3.5" }) })), showPlayPause && (_jsx(TransportBtn, { label: isPlayingValue ? 'Pause' : 'Play', variant: "primary", onClick: onPlayPause ?? (() => { }), children: isPlayingValue ? _jsx(Pause, { className: "size-3.5" }) : _jsx(Play, { className: "size-3.5" }) })), onSkipForward && (_jsx(TransportBtn, { label: "Skip forward", onClick: onSkipForward, children: _jsx(SkipForward, { className: "size-3.5" }) }))] })] }), _jsxs("div", { ref: trackRef, role: "slider", "aria-label": "Conversation position", "aria-valuemin": 0, "aria-valuemax": durationMs, "aria-valuenow": positionMs, onClick: handleSeek, onKeyDown: handleKeyDown, tabIndex: 0, className: "relative h-8 cursor-pointer overflow-hidden rounded-md bg-muted focus:outline-none focus:ring-2 focus:ring-primary/50", children: [_jsx("span", { "aria-hidden": "true", className: "absolute inset-y-0 left-0 bg-primary/10", style: { width: `${fillPct}%` } }), marks.map((m) => (_jsx("span", { "data-mark-kind": m.kind, title: m.label ?? m.kind, className: ['absolute inset-y-1 w-0.5 rounded-sm', KIND_COLOR[m.kind]].join(' '), style: { left: `${m.positionPct}%` } }, m.id))), _jsx("span", { "aria-hidden": "true", className: "absolute -inset-y-0.5 w-0.5 bg-foreground", style: { left: `${fillPct}%` } })] }), _jsxs("div", { className: "flex justify-between pt-2 text-[10px] tabular-nums text-muted-foreground", children: [_jsx("span", { children: "0:00" }), _jsx("span", { children: isLive ? 'now' : formatDuration(durationMs) })] })] }));
37
+ return (_jsxs("div", { className: cn('rounded-2xl border border-border bg-card p-3.5', className), children: [_jsxs("div", { className: "mb-2.5 flex items-center justify-between", children: [_jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: "Timeline \u00B7 this call" }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("span", { className: "px-1.5 text-[11px] tabular-nums text-muted-foreground", children: [formatDuration(positionMs), isLive ? ' / live' : ` / ${formatDuration(durationMs)}`] }), onSkipBack && (_jsx(TransportBtn, { label: "Skip back", onClick: onSkipBack, children: _jsx(SkipBack, { className: "size-3.5" }) })), showPlayPause && (_jsx(TransportBtn, { label: isPlayingValue ? 'Pause' : 'Play', variant: "primary", onClick: onPlayPause ?? (() => { }), children: isPlayingValue ? _jsx(Pause, { className: "size-3.5" }) : _jsx(Play, { className: "size-3.5" }) })), onSkipForward && (_jsx(TransportBtn, { label: "Skip forward", onClick: onSkipForward, children: _jsx(SkipForward, { className: "size-3.5" }) }))] })] }), _jsxs("div", { ref: trackRef, role: "slider", "aria-label": "Conversation position", "aria-valuemin": 0, "aria-valuemax": durationMs, "aria-valuenow": positionMs, onClick: handleSeek, onKeyDown: handleKeyDown, tabIndex: 0, className: "relative h-8 cursor-pointer overflow-hidden rounded-md bg-muted focus:outline-none focus:ring-2 focus:ring-primary/50", children: [_jsx("span", { "aria-hidden": "true", className: "absolute inset-y-0 left-0 bg-primary/10", style: { width: `${fillPct}%` } }), marks.map((m) => (_jsx("span", { "data-mark-kind": m.kind, title: m.label ?? m.kind, className: cn('absolute inset-y-1 w-0.5 rounded-sm', KIND_COLOR[m.kind]), style: { left: `${m.positionPct}%` } }, m.id))), _jsx("span", { "aria-hidden": "true", className: "absolute -inset-y-0.5 w-0.5 bg-foreground", style: { left: `${fillPct}%` } })] }), _jsxs("div", { className: "flex justify-between pt-2 text-[10px] tabular-nums text-muted-foreground", children: [_jsx("span", { children: "0:00" }), _jsx("span", { children: isLive ? 'now' : formatDuration(durationMs) })] })] }));
36
38
  }
37
39
  function TransportBtn({ label, onClick, variant, children, }) {
38
- return (_jsx("button", { type: "button", "aria-label": label, onClick: onClick, className: [
39
- 'inline-flex size-6 items-center justify-center rounded',
40
- variant === 'primary'
41
- ? 'bg-primary text-primary-foreground'
42
- : 'text-muted-foreground hover:bg-muted hover:text-foreground',
43
- ].join(' '), children: children }));
40
+ return (_jsx("button", { type: "button", "aria-label": label, onClick: onClick, className: cn('inline-flex size-6 items-center justify-center rounded', variant === 'primary'
41
+ ? 'bg-primary text-primary-foreground'
42
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: children }));
44
43
  }
@@ -1,6 +1,8 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Archive } from 'lucide-react';
3
4
  import { useEffect, useMemo, useRef, useState } from 'react';
5
+ import { cn } from '../lib/utils.js';
4
6
  import { DateSwitcher } from './date-switcher.js';
5
7
  import { LiveIndicator } from './live-indicator.js';
6
8
  import { TimelineTape } from './timeline-tape.js';
@@ -50,10 +52,10 @@ export function TimelineRail({ date, now, events, zoom = 1.2, followLive = true,
50
52
  setIsFollowing(true);
51
53
  onFollowChange?.(true);
52
54
  };
53
- return (_jsxs("aside", { className: [
54
- 'relative flex h-full flex-col border-r border-border bg-card text-sm',
55
- className ?? '',
56
- ].join(' '), "data-meda-component": "timeline-rail", children: [_jsx("div", { className: "px-3.5 pb-2.5 pt-3.5", children: _jsx(DateSwitcher, { value: date, now: now, tz: tz, onChange: (d) => onDateChange?.(d) }) }), _jsxs("div", { className: "flex items-center gap-2.5 px-3.5 pb-3", children: [_jsx(ZoomBtn, { label: "\u2212", onClick: () => onZoomChange?.(Math.max(0.2, zoom - 0.4)) }), _jsx(ZoomTrack, { value: zoom, min: 0.2, max: 6 }), _jsx(ZoomBtn, { label: "+", onClick: () => onZoomChange?.(Math.min(6, zoom + 0.4)) })] }), _jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3.5 pb-2.5 text-[11px] tabular-nums text-muted-foreground", children: [liveCount > 0 && (_jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold text-emerald-500", children: [_jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-emerald-500 shadow-[0_0_0_3px_rgba(16,185,129,0.18)]" }), liveCount, " live"] })), _jsxs("span", { className: "ml-auto", children: [_jsx("strong", { className: "font-semibold text-foreground", children: totalCalls }), " sessions"] })] }), _jsxs("div", { ref: scrollRef, className: "relative flex-1 overflow-y-auto overflow-x-hidden pb-16", children: [_jsx("div", { className: "sticky top-3 z-10 mx-3.5", children: _jsx(LiveIndicator, { now: now, tz: tz }) }), _jsx(TimelineTape, { now: now, events: events, pxPerSec: zoom, futurePadSec: FUTURE_PAD_SEC, pastSpanSec: PAST_SPAN_SEC, tz: tz, onSelect: onSelect })] }), !isFollowing && (_jsx("button", { type: "button", onClick: jumpToLive, className: "absolute right-3 top-32 z-20 inline-flex items-center gap-1.5 rounded-full bg-emerald-500 px-3 py-1 text-[11px] font-semibold text-white shadow-md hover:bg-emerald-600", children: "Jump to live" })), _jsxs("button", { type: "button", onClick: () => onManageRetention?.(), className: "absolute inset-x-0 bottom-0 z-20 flex items-center justify-center gap-1.5 bg-gradient-to-t from-card via-card/85 to-transparent px-3.5 py-2.5 text-[12px] text-primary hover:text-primary/80", children: [_jsx(Archive, { className: "size-3.5" }), " Manage retention"] })] }));
55
+ return (_jsxs("aside", { className: cn('relative flex h-full flex-col border-r border-border bg-card text-sm', className), "data-meda-component": "timeline-rail", children: [_jsx("div", { className: "px-3.5 pb-2.5 pt-3.5", children: _jsx(DateSwitcher, { value: date, now: now, tz: tz, onChange: (d) => onDateChange?.(d) }) }), _jsxs("div", { className: "flex items-center gap-2.5 px-3.5 pb-3", children: [_jsx(ZoomBtn, { label: "\u2212", onClick: () => onZoomChange?.(Math.max(0.2, zoom - 0.4)) }), _jsx(ZoomTrack, { value: zoom, min: 0.2, max: 6 }), _jsx(ZoomBtn, { label: "+", onClick: () => onZoomChange?.(Math.min(6, zoom + 0.4)) })] }), _jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3.5 pb-2.5 text-[11px] tabular-nums text-muted-foreground", children: [liveCount > 0 && (_jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold text-success-700", children: [_jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-success shadow-[0_0_0_3px_rgba(16,185,129,0.18)]" }), liveCount, " live"] })), _jsxs("span", { className: "ml-auto", children: [_jsx("strong", { className: "font-semibold text-foreground", children: totalCalls }), " sessions"] })] }), _jsxs("div", { ref: scrollRef, className: "relative flex-1 overflow-y-auto overflow-x-hidden pb-16", children: [_jsx("div", { className: "sticky top-3 z-10 mx-3.5", children: _jsx(LiveIndicator, { now: now, tz: tz }) }), _jsx(TimelineTape, { now: now, events: events, pxPerSec: zoom, futurePadSec: FUTURE_PAD_SEC, pastSpanSec: PAST_SPAN_SEC, tz: tz, onSelect: onSelect })] }), !isFollowing && (_jsx("button", { type: "button", onClick: jumpToLive,
56
+ // bg-success-700/-800 (vs. -600/-700) so 11px white text clears
57
+ // WCAG AA on hover and at rest.
58
+ className: "absolute right-3 top-32 z-20 inline-flex items-center gap-1.5 rounded-full bg-success-700 px-3 py-1 text-[11px] font-semibold text-white shadow-md hover:bg-success-800", children: "Jump to live" })), _jsxs("button", { type: "button", onClick: () => onManageRetention?.(), className: "absolute inset-x-0 bottom-0 z-20 flex items-center justify-center gap-1.5 bg-gradient-to-t from-card via-card/85 to-transparent px-3.5 py-2.5 text-[12px] text-primary hover:text-primary/80", children: [_jsx(Archive, { className: "size-3.5" }), " Manage retention"] })] }));
57
59
  }
58
60
  function ZoomBtn({ label, onClick }) {
59
61
  return (_jsx("button", { type: "button", onClick: onClick, className: "inline-flex size-5 items-center justify-center rounded bg-muted text-sm leading-none text-muted-foreground hover:text-foreground", children: label }));
@@ -1,5 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { formatClock } from '../lib/format-time.js';
4
+ import { cn } from '../lib/utils.js';
3
5
  import { EventCard } from './event-card.js';
4
6
  const DEFAULT_PX_PER_SEC = 1.2;
5
7
  const DEFAULT_FUTURE_PAD_SEC = 300;
@@ -51,14 +53,11 @@ export function TimelineTape({ now, events, pxPerSec = DEFAULT_PX_PER_SEC, futur
51
53
  const height = Math.max(2, Math.abs(endY - startY));
52
54
  return { event: e, top, height };
53
55
  });
54
- return (_jsxs("div", { className: ['relative ml-3.5 mr-3', className ?? ''].join(' '), style: { height: `${canvasHeight}px` }, "data-canvas-height": canvasHeight, children: [ticks.map((t) => t.major ? (_jsxs("div", { children: [_jsx("span", { className: "absolute left-0 w-[30px] -translate-y-[7px] text-right text-[11px] font-medium tabular-nums text-muted-foreground", style: { top: `${t.y}px` }, children: t.label }), _jsx("span", { className: "absolute h-px w-2 bg-border", style: { left: 32, top: `${t.y}px` } })] }, `maj-${t.y}`)) : (_jsx("span", { className: "absolute h-px w-1.5 bg-border/40", style: { left: 35, top: `${t.y}px` } }, `min-${t.y}`))), segments.map(({ event, top, height }) => (_jsx("span", { "aria-hidden": "true", "data-tape-segment-id": event.id, "data-tape-segment-live": String(event.isLive ?? false), className: [
55
- 'absolute w-1 rounded-sm',
56
- event.isLive
57
- ? 'bg-emerald-500 shadow-[0_0_12px_rgba(16,185,129,0.5)]'
58
- : event.kind === 'error'
59
- ? 'bg-destructive'
60
- : 'bg-sky-400 shadow-[0_0_8px_rgba(56,189,248,0.35)]',
61
- ].join(' '), style: { left: 44, top: `${top}px`, height: `${height}px` } }, `seg-${event.id}`))), events.map((event) => {
56
+ return (_jsxs("div", { className: cn('relative ml-3.5 mr-3', className), style: { height: `${canvasHeight}px` }, "data-canvas-height": canvasHeight, children: [ticks.map((t) => t.major ? (_jsxs("div", { children: [_jsx("span", { className: "absolute left-0 w-[30px] -translate-y-[7px] text-right text-[11px] font-medium tabular-nums text-muted-foreground", style: { top: `${t.y}px` }, children: t.label }), _jsx("span", { className: "absolute h-px w-2 bg-border", style: { left: 32, top: `${t.y}px` } })] }, `maj-${t.y}`)) : (_jsx("span", { className: "absolute h-px w-1.5 bg-border/40", style: { left: 35, top: `${t.y}px` } }, `min-${t.y}`))), segments.map(({ event, top, height }) => (_jsx("span", { "aria-hidden": "true", "data-tape-segment-id": event.id, "data-tape-segment-live": String(event.isLive ?? false), className: cn('absolute w-1 rounded-sm', event.isLive
57
+ ? 'bg-success-600 shadow-[0_0_12px_rgba(16,185,129,0.5)]'
58
+ : event.kind === 'error'
59
+ ? 'bg-destructive'
60
+ : 'bg-info-500 shadow-[0_0_8px_rgba(56,189,248,0.35)]'), style: { left: 44, top: `${top}px`, height: `${height}px` } }, `seg-${event.id}`))), events.map((event) => {
62
61
  const startY = instantToY(event.startedAt, nowMs, pxPerSec, futurePadSec);
63
62
  const isSubEvent = event.kind === 'sub-event';
64
63
  return (_jsx("div", { "data-event-id": event.id, className: "absolute", style: { top: `${startY}px`, left: 56, right: 0 }, children: _jsx(EventCard, { event: event, size: isSubEvent ? 'tiny' : 'default', onSelect: onSelect }) }, event.id));
@@ -1,5 +1,7 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import * as React from 'react';
4
+ import { cn } from '../lib/utils.js';
3
5
  const BAR_COUNT = 9;
4
6
  export function VoiceLevel({ level, variant = 'bars', width = 140, height = 48, className, }) {
5
7
  const historyRef = React.useRef(Array(BAR_COUNT).fill(0));
@@ -14,7 +16,7 @@ export function VoiceLevel({ level, variant = 'bars', width = 140, height = 48,
14
16
  force((v) => v + 1);
15
17
  }, [level, variant]);
16
18
  if (variant === 'bars') {
17
- return (_jsx("div", { role: "presentation", className: ['flex items-end gap-1', className ?? ''].join(' '), style: { width, height }, children: historyRef.current.map((v, i) => (_jsx("span", { className: "flex-1 rounded-sm bg-primary", style: { height: `${15 + v * 85}%`, opacity: 0.4 + v * 0.6 } }, i))) }));
19
+ return (_jsx("div", { role: "presentation", className: cn('flex items-end gap-1', className), style: { width, height }, children: historyRef.current.map((v, i) => (_jsx("span", { className: "flex-1 rounded-sm bg-primary", style: { height: `${15 + v * 85}%`, opacity: 0.4 + v * 0.6 } }, i))) }));
18
20
  }
19
21
  if (variant === 'ring') {
20
22
  const r = Math.min(width, height) / 2 - 4;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  // SPDX-License-Identifier: Apache-2.0
3
4
  // Portions adapted from ElevenLabs UI's Orb component (MIT):
@@ -1,7 +1,9 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  // SPDX-License-Identifier: Apache-2.0
3
4
  import { Canvas } from '@react-three/fiber';
4
5
  import * as React from 'react';
6
+ import { cn } from '../lib/utils.js';
5
7
  import { Scene } from './voice-orb-scene.js';
6
8
  // Note: CSS for `.meda-voice-orb` is shipped via @medalsocial/meda/styles.css
7
9
  // (re-exported from globals.css). We avoid a JS-side CSS import here so
@@ -9,7 +11,11 @@ import { Scene } from './voice-orb-scene.js';
9
11
  // ---------------------------------------------------------------------------
10
12
  // Theming helpers
11
13
  // ---------------------------------------------------------------------------
12
- const FALLBACK_COLOR = '#9A6AC2'; // Pilot purple
14
+ // Fallback colors used only if CSS custom properties fail to resolve.
15
+ // Both values are from the canonical brand ramp (.lib.pen):
16
+ // brand-400 (#9A6AC2) and brand-500 (#7E3FAC).
17
+ // Runtime path reads --primary and --accent first.
18
+ const FALLBACK_COLOR = '#9A6AC2'; // brand-400
13
19
  function hslToHex(h, s, l) {
14
20
  const sl = s / 100;
15
21
  const ll = l / 100;
@@ -70,7 +76,7 @@ function readMedaColors(el) {
70
76
  const colorB = accent ? parseColor(accent) : colorA;
71
77
  return [colorA, colorB];
72
78
  }
73
- const DEFAULT_COLORS = ['#9A6AC2', '#7B4FAB'];
79
+ const DEFAULT_COLORS = ['#9A6AC2', '#7E3FAC']; // brand-400 → brand-500
74
80
  // ---------------------------------------------------------------------------
75
81
  // Component
76
82
  // ---------------------------------------------------------------------------
@@ -111,13 +117,5 @@ export const VoiceOrb = React.forwardRef(function VoiceOrb({ pressed, level = 0,
111
117
  height: size,
112
118
  ...style,
113
119
  };
114
- return (_jsx("button", { ref: buttonRef, type: "button", "aria-pressed": pressed, "aria-label": label, disabled: disabled, "data-phase": phase, "data-pressed": pressed, className: [
115
- 'meda-voice-orb',
116
- 'relative inline-flex items-center justify-center rounded-full',
117
- 'select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
118
- 'transition-transform duration-200 ease-out',
119
- disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer',
120
- pressed ? 'scale-[0.97]' : 'scale-100',
121
- className ?? '',
122
- ].join(' '), style: btnStyle, ...rest, children: _jsx(Canvas, { gl: { alpha: true, antialias: true, premultipliedAlpha: true }, style: { pointerEvents: 'none' }, children: _jsx(Scene, { level: level, outputLevel: outputLevel, phase: phase, colors: resolvedColors, reducedMotion: reducedMotion, pressed: pressed, variant: variant }) }) }));
120
+ return (_jsx("button", { ref: buttonRef, type: "button", "aria-pressed": pressed, "aria-label": label, disabled: disabled, "data-phase": phase, "data-pressed": pressed, className: cn('meda-voice-orb', 'relative inline-flex items-center justify-center rounded-full', 'select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', 'transition-transform duration-200 ease-out', disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer', pressed ? 'scale-[0.97]' : 'scale-100', className), style: btnStyle, ...rest, children: _jsx(Canvas, { gl: { alpha: true, antialias: true, premultipliedAlpha: true }, style: { pointerEvents: 'none' }, children: _jsx(Scene, { level: level, outputLevel: outputLevel, phase: phase, colors: resolvedColors, reducedMotion: reducedMotion, pressed: pressed, variant: variant }) }) }));
123
121
  });
@@ -1,4 +1,6 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from '../lib/utils.js';
2
4
  const PHASE_LABEL = {
3
5
  idle: 'Idle',
4
6
  listening: 'Listening',
@@ -10,17 +12,10 @@ export function VoiceStatusPill({ phase, thinkingForMs, className }) {
10
12
  const tone = phase === 'listening'
11
13
  ? 'bg-primary text-primary-foreground'
12
14
  : phase === 'speaking'
13
- ? 'bg-emerald-500 text-white'
15
+ ? 'bg-success text-success-foreground'
14
16
  : phase === 'error'
15
17
  ? 'bg-destructive text-destructive-foreground'
16
18
  : 'bg-muted text-muted-foreground';
17
19
  const seconds = thinkingForMs ? (thinkingForMs / 1000).toFixed(1) : null;
18
- return (_jsxs("span", { role: "status", "data-phase": phase, className: [
19
- 'inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium',
20
- tone,
21
- className ?? '',
22
- ].join(' '), children: [_jsx("span", { className: [
23
- 'size-1.5 rounded-full bg-current',
24
- phase === 'thinking' ? 'animate-pulse' : '',
25
- ].join(' ') }), PHASE_LABEL[phase], phase === 'thinking' && seconds && _jsxs("span", { className: "opacity-70", children: ["\u00B7 ", seconds, "s"] })] }));
20
+ return (_jsxs("span", { role: "status", "data-phase": phase, className: cn('inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium', tone, className), children: [_jsx("span", { className: cn('size-1.5 rounded-full bg-current', phase === 'thinking' && 'animate-pulse') }), PHASE_LABEL[phase], phase === 'thinking' && seconds && _jsxs("span", { children: ["\u00B7 ", seconds, "s"] })] }));
26
21
  }