@workerdeck/ui 0.6.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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/build/index.d.mts +927 -0
  4. package/build/index.mjs +5236 -0
  5. package/build/index.mjs.map +1 -0
  6. package/package.json +73 -0
  7. package/src/components/agent/Composer.tsx +139 -0
  8. package/src/components/agent/Conversation.tsx +59 -0
  9. package/src/components/agent/FileCard.tsx +50 -0
  10. package/src/components/agent/Loader.tsx +21 -0
  11. package/src/components/agent/Message.tsx +44 -0
  12. package/src/components/agent/ModelSelect.tsx +87 -0
  13. package/src/components/agent/PermissionModeSelect.tsx +94 -0
  14. package/src/components/agent/PermissionPrompt.tsx +52 -0
  15. package/src/components/agent/QuestionPrompt.tsx +193 -0
  16. package/src/components/agent/Reasoning.tsx +58 -0
  17. package/src/components/agent/Response.tsx +31 -0
  18. package/src/components/agent/SessionList.tsx +93 -0
  19. package/src/components/agent/SessionPanel.tsx +149 -0
  20. package/src/components/agent/StatusBar.tsx +140 -0
  21. package/src/components/agent/ToolCallCard.tsx +94 -0
  22. package/src/components/agent/Transcript.tsx +114 -0
  23. package/src/components/agent/status.ts +16 -0
  24. package/src/components/prompt-area/animated-placeholder.tsx +42 -0
  25. package/src/components/prompt-area/clipboard-helpers.ts +206 -0
  26. package/src/components/prompt-area/cursor-helpers.ts +244 -0
  27. package/src/components/prompt-area/dom-helpers.ts +721 -0
  28. package/src/components/prompt-area/file-strip.tsx +250 -0
  29. package/src/components/prompt-area/html-to-markdown.ts +278 -0
  30. package/src/components/prompt-area/image-strip.tsx +49 -0
  31. package/src/components/prompt-area/index.ts +23 -0
  32. package/src/components/prompt-area/prompt-area-engine.ts +705 -0
  33. package/src/components/prompt-area/prompt-area-list-ops.ts +499 -0
  34. package/src/components/prompt-area/prompt-area.tsx +375 -0
  35. package/src/components/prompt-area/remove-button.tsx +37 -0
  36. package/src/components/prompt-area/segment-helpers.ts +62 -0
  37. package/src/components/prompt-area/trigger-popover.tsx +139 -0
  38. package/src/components/prompt-area/trigger-presets.ts +143 -0
  39. package/src/components/prompt-area/types.ts +360 -0
  40. package/src/components/prompt-area/use-markdown-mode.ts +113 -0
  41. package/src/components/prompt-area/use-prompt-area-events.ts +470 -0
  42. package/src/components/prompt-area/use-prompt-area-state.ts +131 -0
  43. package/src/components/prompt-area/use-prompt-area.ts +1507 -0
  44. package/src/components/prompt-area/use-trigger-search.ts +115 -0
  45. package/src/components/ui/AlertDialog.tsx +56 -0
  46. package/src/components/ui/Badge.tsx +42 -0
  47. package/src/components/ui/Button.tsx +47 -0
  48. package/src/components/ui/Card.tsx +29 -0
  49. package/src/components/ui/CodeBlock.tsx +31 -0
  50. package/src/components/ui/CopyButton.tsx +28 -0
  51. package/src/components/ui/Input.tsx +20 -0
  52. package/src/components/ui/ProgressRing.tsx +49 -0
  53. package/src/components/ui/Select.tsx +80 -0
  54. package/src/components/ui/Sonner.tsx +22 -0
  55. package/src/components/ui/Spinner.tsx +6 -0
  56. package/src/components/ui/Textarea.tsx +21 -0
  57. package/src/components/ui/Tooltip.tsx +34 -0
  58. package/src/index.ts +99 -0
  59. package/src/lib/format.ts +67 -0
  60. package/src/lib/utils.ts +33 -0
  61. package/src/styles/theme.css +413 -0
package/src/index.ts ADDED
@@ -0,0 +1,99 @@
1
+ // Primitives
2
+ export { Button, buttonVariants, type ButtonProps } from './components/ui/Button.tsx'
3
+ export { Badge, badgeVariants, type BadgeProps } from './components/ui/Badge.tsx'
4
+ export { Card, CardContent, CardHeader, CardTitle } from './components/ui/Card.tsx'
5
+ export { Input } from './components/ui/Input.tsx'
6
+ export { Textarea } from './components/ui/Textarea.tsx'
7
+ export {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectItemText,
12
+ SelectTrigger,
13
+ SelectValue,
14
+ } from './components/ui/Select.tsx'
15
+ export {
16
+ AlertDialog,
17
+ AlertDialogClose,
18
+ AlertDialogContent,
19
+ AlertDialogDescription,
20
+ AlertDialogTitle,
21
+ AlertDialogTrigger,
22
+ } from './components/ui/AlertDialog.tsx'
23
+ export { Tip, TooltipContent, TooltipProvider } from './components/ui/Tooltip.tsx'
24
+ export { Toaster, toast } from './components/ui/Sonner.tsx'
25
+ export { CopyButton, type CopyButtonProps } from './components/ui/CopyButton.tsx'
26
+ export { Spinner } from './components/ui/Spinner.tsx'
27
+ export { CodeBlock, type CodeBlockProps } from './components/ui/CodeBlock.tsx'
28
+ export { ProgressRing, type ProgressRingProps } from './components/ui/ProgressRing.tsx'
29
+ // Prompt input (vendored just-marketing/prompt-area, themed to these tokens)
30
+ export {
31
+ PromptArea,
32
+ usePromptAreaState,
33
+ commandTrigger,
34
+ mentionTrigger,
35
+ hashtagTrigger,
36
+ segmentsToPlainText,
37
+ plainTextToSegments,
38
+ isSegmentsEmpty,
39
+ getChipsByTrigger,
40
+ type PromptAreaHandle,
41
+ type PromptAreaProps,
42
+ type Segment,
43
+ type ChipSegment,
44
+ type TextSegment,
45
+ type TriggerConfig,
46
+ type TriggerSuggestion,
47
+ } from './components/prompt-area/index.ts'
48
+
49
+ // Agent-control components
50
+ export { SessionPanel, type SessionPanelProps } from './components/agent/SessionPanel.tsx'
51
+ export { Transcript, type TranscriptProps } from './components/agent/Transcript.tsx'
52
+ export {
53
+ Conversation,
54
+ ConversationContent,
55
+ ConversationScrollButton,
56
+ type ConversationProps,
57
+ } from './components/agent/Conversation.tsx'
58
+ export { Message, MessageContent, type MessageProps } from './components/agent/Message.tsx'
59
+ export { Response, type ResponseProps } from './components/agent/Response.tsx'
60
+ export { Reasoning, type ReasoningProps } from './components/agent/Reasoning.tsx'
61
+ export { Loader } from './components/agent/Loader.tsx'
62
+ export { ToolCallCard, type ToolCallCardProps, type ToolCallItem } from './components/agent/ToolCallCard.tsx'
63
+ export { FileCard, type FileCardProps, type FileDeliveredItem } from './components/agent/FileCard.tsx'
64
+ export { PermissionPrompt, type PermissionPromptProps } from './components/agent/PermissionPrompt.tsx'
65
+ export {
66
+ QuestionPrompt,
67
+ QUESTION_BEHAVIORS,
68
+ parseUserQuestions,
69
+ type QuestionPromptProps,
70
+ type QuestionBehaviorMeta,
71
+ } from './components/agent/QuestionPrompt.tsx'
72
+ export { Composer, type ComposerProps } from './components/agent/Composer.tsx'
73
+ export { ModelSelect, type ModelSelectProps } from './components/agent/ModelSelect.tsx'
74
+ export {
75
+ PERMISSION_MODES,
76
+ PermissionModeSelect,
77
+ type PermissionModeMeta,
78
+ type PermissionModeSelectProps,
79
+ } from './components/agent/PermissionModeSelect.tsx'
80
+ export { StatusBar, type StatusBarProps } from './components/agent/StatusBar.tsx'
81
+ export {
82
+ SessionList,
83
+ SessionListItem,
84
+ type SessionListItemProps,
85
+ type SessionListProps,
86
+ } from './components/agent/SessionList.tsx'
87
+ export { STATUS_META } from './components/agent/status.ts'
88
+
89
+ // Utilities
90
+ export { cn } from './lib/utils.ts'
91
+ export {
92
+ formatBytes,
93
+ formatCost,
94
+ formatCountdown,
95
+ formatDuration,
96
+ formatRelativeTime,
97
+ formatTokens,
98
+ toolInputPreview,
99
+ } from './lib/format.ts'
@@ -0,0 +1,67 @@
1
+ export function formatCost(usd: number | undefined): string {
2
+ if (usd === undefined || Number.isNaN(usd)) return '—'
3
+ if (usd === 0) return '$0.00'
4
+ if (usd < 0.01) return '<$0.01'
5
+ return `$${usd.toFixed(2)}`
6
+ }
7
+
8
+ export function formatDuration(ms: number): string {
9
+ if (ms < 1000) return `${Math.round(ms)}ms`
10
+ const s = ms / 1000
11
+ if (s < 60) return `${s.toFixed(1)}s`
12
+ const m = Math.floor(s / 60)
13
+ return `${m}m ${Math.round(s % 60)}s`
14
+ }
15
+
16
+ /** Compact token count, Claude Code-style: 850 → "850", 359_000 → "359.0k", 1_200_000 → "1.2M". */
17
+ export function formatTokens(tokens: number): string {
18
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`
19
+ if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`
20
+ return String(Math.round(tokens))
21
+ }
22
+
23
+ export function formatBytes(bytes: number): string {
24
+ if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
25
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`
26
+ return `${bytes} B`
27
+ }
28
+
29
+ /** Countdown to an epoch-ms deadline: "2h 18m", "12m", "<1m"; "now" once passed. */
30
+ export function formatCountdown(untilEpochMs: number, now = Date.now()): string {
31
+ const remaining = untilEpochMs - now
32
+ if (remaining <= 0) return 'now'
33
+ const minutes = Math.ceil(remaining / 60_000)
34
+ if (minutes < 1) return '<1m'
35
+ if (minutes < 60) return `${minutes}m`
36
+ const days = Math.floor(minutes / (60 * 24))
37
+ if (days >= 1) return `${days}d ${Math.floor((minutes % (60 * 24)) / 60)}h`
38
+ return `${Math.floor(minutes / 60)}h ${minutes % 60}m`
39
+ }
40
+
41
+ export function formatRelativeTime(epochMs: number | undefined, now = Date.now()): string {
42
+ if (!epochMs) return '—'
43
+ const diff = Math.max(0, now - epochMs)
44
+ const s = Math.floor(diff / 1000)
45
+ if (s < 60) return 'just now'
46
+ const m = Math.floor(s / 60)
47
+ if (m < 60) return `${m}m ago`
48
+ const h = Math.floor(m / 60)
49
+ if (h < 24) return `${h}h ago`
50
+ const d = Math.floor(h / 24)
51
+ return `${d}d ago`
52
+ }
53
+
54
+ /** Compact one-line preview of a tool input for card headers. */
55
+ export function toolInputPreview(input: unknown, max = 80): string {
56
+ if (input === null || input === undefined) return ''
57
+ if (typeof input === 'object') {
58
+ const o = input as Record<string, unknown>
59
+ const primary =
60
+ o.command ?? o.file_path ?? o.path ?? o.url ?? o.pattern ?? o.query ?? o.description
61
+ if (typeof primary === 'string') {
62
+ return primary.length > max ? primary.slice(0, max - 1) + '…' : primary
63
+ }
64
+ }
65
+ const text = JSON.stringify(input) ?? ''
66
+ return text.length > max ? text.slice(0, max - 1) + '…' : text
67
+ }
@@ -0,0 +1,33 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { extendTailwindMerge } from 'tailwind-merge'
3
+
4
+ // Register the custom text-* font-size utilities as a font-size class group, or
5
+ // tailwind-merge collapses a size class and a color class (e.g. `text-label
6
+ // text-muted-foreground`) into one bucket and silently drops the size.
7
+ const twMerge = extendTailwindMerge({
8
+ extend: {
9
+ classGroups: {
10
+ 'font-size': [
11
+ {
12
+ text: [
13
+ 'display-xl',
14
+ 'display-lg',
15
+ 'display-md',
16
+ 'display-sm',
17
+ 'heading-1',
18
+ 'heading-2',
19
+ 'heading-3',
20
+ 'body',
21
+ 'body-sm',
22
+ 'label',
23
+ 'code',
24
+ ],
25
+ },
26
+ ],
27
+ },
28
+ },
29
+ })
30
+
31
+ export function cn(...inputs: ClassValue[]) {
32
+ return twMerge(clsx(inputs))
33
+ }
@@ -0,0 +1,413 @@
1
+ /*
2
+ * @workerdeck/ui theme — design tokens + Tailwind v4 theme mapping.
3
+ *
4
+ * Consumer wiring (the app's tailwind entry CSS):
5
+ *
6
+ * @import 'tailwindcss';
7
+ * @import '@workerdeck/ui/theme.css';
8
+ * @source '../node_modules/@workerdeck/ui'; (or the package src path in a monorepo)
9
+ *
10
+ * Do NOT import tailwindcss here — the consumer owns that. Theme switching is driven by
11
+ * <html data-theme="light|dark"> (set it before first paint to avoid a flash).
12
+ *
13
+ * Two tiers: a raw palette swapped by [data-theme], then `@theme inline` mapping raw
14
+ * tokens to Tailwind utilities (`inline` preserves the var() reference so flipping
15
+ * data-theme re-themes with zero JS).
16
+ */
17
+
18
+ :root,
19
+ [data-theme='light'] {
20
+ /* ---------- Backgrounds ---------- */
21
+ --bg: #ffffff;
22
+ --bg-surface: #fafafa;
23
+ --bg-surface-hover: #f2f2f2;
24
+ --bg-code: #f5f5f5;
25
+ --bg-elevated: #ffffffcc;
26
+
27
+ /* ---------- Borders ---------- */
28
+ --border: #ebebeb;
29
+ --border-light: #e0e0e0;
30
+ --border-strong: #c9c9c9;
31
+
32
+ /* ---------- Foreground / Text ---------- */
33
+ --fg-1: #171717;
34
+ --fg-2: #454545;
35
+ --fg-3: #737373;
36
+ --fg-4: #a1a1a1;
37
+ --fg-disabled: #c9c9c9;
38
+
39
+ /* ---------- Accent = the inverse of the canvas ---------- */
40
+ --accent: #171717;
41
+ --accent-hover: #000000;
42
+ --accent-dim: #737373;
43
+ --accent-bg: #f2f2f2;
44
+ --accent-ring: #17171733;
45
+
46
+ /* ---------- Semantic ---------- */
47
+ --success: #0f7b4f;
48
+ --success-bg: #edfaf3;
49
+ --warning: #a35200;
50
+ --warning-bg: #fff6ed;
51
+ --danger: #d93036;
52
+ --danger-bg: #fef1f1;
53
+ --info: #0072f5;
54
+ --info-bg: #eff6ff;
55
+
56
+ /* ---------- Elevation (flat by default; shadows are for overlays only) ---------- */
57
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);
58
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
59
+ --shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
60
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
61
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.1);
62
+
63
+ /* ---------- Inset frame ---------- */
64
+ --shine: #dcdcdc;
65
+ --frame-shadow: var(--shadow-xs);
66
+
67
+ /* ---------- aliases (bridge for the @theme utilities) ---------- */
68
+ --surface: var(--bg-surface);
69
+ --sidebar: #f4f4f4;
70
+ --text: var(--fg-1);
71
+ --text-muted: var(--fg-3);
72
+ --accent-tint: var(--accent-bg);
73
+ --accent-fg: #ffffff;
74
+ --warn: var(--warning);
75
+ --ring: var(--fg-1);
76
+ }
77
+
78
+ [data-theme='dark'] {
79
+ /* ---------- Backgrounds ---------- */
80
+ --bg: #000000;
81
+ --bg-surface: #0a0a0a;
82
+ --bg-surface-hover: #141414;
83
+ --bg-code: #141414;
84
+ --bg-elevated: #0a0a0acc;
85
+
86
+ /* ---------- Borders ---------- */
87
+ --border: #1f1f1f;
88
+ --border-light: #292929;
89
+ --border-strong: #3d3d3d;
90
+
91
+ /* ---------- Foreground / Text ---------- */
92
+ --fg-1: #ededed;
93
+ --fg-2: #a1a1a1;
94
+ --fg-3: #8f8f8f;
95
+ --fg-4: #6e6e6e;
96
+ --fg-disabled: #454545;
97
+
98
+ /* ---------- Accent = the inverse of the canvas ---------- */
99
+ --accent: #ededed;
100
+ --accent-hover: #ffffff;
101
+ --accent-dim: #8f8f8f;
102
+ --accent-bg: #1f1f1f;
103
+ --accent-ring: #ffffff3d;
104
+
105
+ /* ---------- Semantic ---------- */
106
+ --success: #3ecf8e;
107
+ --success-bg: #0d2a1d;
108
+ --warning: #f5a623;
109
+ --warning-bg: #2a1f04;
110
+ --danger: #ff6166;
111
+ --danger-bg: #2a1114;
112
+ --info: #0091ff;
113
+ --info-bg: #0a1f33;
114
+
115
+ /* ---------- Elevation ---------- */
116
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.5);
117
+ --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.5);
118
+ --shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
119
+ --shadow-md: 0 8px 20px rgba(0, 0, 0, 0.6);
120
+ --shadow-lg: 0 24px 48px rgba(0, 0, 0, 0.7);
121
+
122
+ /* ---------- Inset frame ---------- */
123
+ --shine: #303030;
124
+ --frame-shadow:
125
+ 0 4px 24px rgba(0, 0, 0, 0.55),
126
+ 0 2px 6px rgba(0, 0, 0, 0.45);
127
+
128
+ /* ---------- aliases ---------- */
129
+ --surface: var(--bg-surface);
130
+ --sidebar: #131313;
131
+ --text: var(--fg-1);
132
+ --text-muted: var(--fg-3);
133
+ --accent-tint: var(--accent-bg);
134
+ --accent-fg: #0a0a0a;
135
+ --warn: var(--warning);
136
+ --ring: var(--fg-1);
137
+ }
138
+
139
+ /* Theme-independent tokens — type tracking, easing, fonts.
140
+ * Hosts get real fonts by importing @fontsource/inter + @fontsource/jetbrains-mono;
141
+ * the stacks below degrade gracefully without them. */
142
+ :root {
143
+ --tracking-tight: -0.02em;
144
+ --tracking-normal: 0;
145
+ --tracking-wide: 0.04em;
146
+ --tracking-caps: 0.1em;
147
+
148
+ --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
149
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
150
+
151
+ --cw-font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
152
+ --cw-font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, Consolas, monospace;
153
+
154
+ --motion-fast: 120ms;
155
+ --motion-base: 150ms;
156
+ --motion-slow: 220ms;
157
+
158
+ /* Stock-name bridge → raw custom properties (shadcn idiom compatibility). */
159
+ --primary: var(--accent);
160
+ --primary-foreground: var(--accent-fg);
161
+ --secondary: var(--surface);
162
+ --secondary-foreground: var(--text);
163
+ --muted: var(--sidebar);
164
+ --muted-foreground: var(--text-muted);
165
+ --background: var(--bg);
166
+ --foreground: var(--text);
167
+ --destructive: var(--danger);
168
+ --radius: var(--radius-md);
169
+ }
170
+
171
+ /* Drive dark: off the same <html data-theme> the tokens swap on. */
172
+ @custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));
173
+
174
+ @theme inline {
175
+ /* Semantic colors → utilities. */
176
+ --color-bg: var(--bg);
177
+ --color-surface: var(--surface);
178
+ --color-sidebar: var(--sidebar);
179
+ --color-border: var(--border);
180
+ --color-text: var(--text);
181
+ --color-accent: var(--accent);
182
+ --color-accent-hover: var(--accent-hover);
183
+ --color-accent-tint: var(--accent-tint);
184
+ --color-accent-fg: var(--accent-fg);
185
+ --color-success: var(--success);
186
+ --color-warn: var(--warn);
187
+ --color-danger: var(--danger);
188
+ --color-ring: var(--ring);
189
+
190
+ --color-surface-hover: var(--bg-surface-hover);
191
+ /* Named code-bg (not code) so it can't shadow the --text-code font-size token:
192
+ with a --color-code defined, `text-code` would resolve to the color namespace
193
+ and paint text in the code *background* color — invisible in both themes. */
194
+ --color-code-bg: var(--bg-code);
195
+ --color-border-light: var(--border-light);
196
+ --color-border-strong: var(--border-strong);
197
+ --color-fg-1: var(--fg-1);
198
+ --color-fg-2: var(--fg-2);
199
+ --color-fg-3: var(--fg-3);
200
+ --color-fg-4: var(--fg-4);
201
+ --color-accent-bg: var(--accent-bg);
202
+ --color-accent-dim: var(--accent-dim);
203
+ --color-success-bg: var(--success-bg);
204
+ --color-warning: var(--warning);
205
+ --color-warning-bg: var(--warning-bg);
206
+ --color-danger-bg: var(--danger-bg);
207
+ --color-info: var(--info);
208
+ --color-info-bg: var(--info-bg);
209
+
210
+ /* Stock-name bridge → utilities (bg-primary / text-muted-foreground / ...). */
211
+ --color-primary: var(--primary);
212
+ --color-primary-foreground: var(--primary-foreground);
213
+ --color-secondary: var(--secondary);
214
+ --color-secondary-foreground: var(--secondary-foreground);
215
+ --color-muted: var(--muted);
216
+ --color-muted-foreground: var(--muted-foreground);
217
+ --color-background: var(--background);
218
+ --color-foreground: var(--foreground);
219
+ --color-destructive: var(--destructive);
220
+
221
+ /* Font roles */
222
+ --font-sans: var(--cw-font-sans);
223
+ --font-mono: var(--cw-font-mono);
224
+
225
+ /* Type scale — size + line-height. Display sizes carry tight tracking. */
226
+ --text-display-xl: 2.5rem;
227
+ --text-display-xl--line-height: 3rem;
228
+ --text-display-xl--letter-spacing: -0.02em;
229
+ --text-display-lg: 2rem;
230
+ --text-display-lg--line-height: 2.5rem;
231
+ --text-display-lg--letter-spacing: -0.02em;
232
+ --text-display-md: 1.5rem;
233
+ --text-display-md--line-height: 2rem;
234
+ --text-display-md--letter-spacing: -0.02em;
235
+ --text-display-sm: 1.25rem;
236
+ --text-display-sm--line-height: 1.75rem;
237
+ --text-display-sm--letter-spacing: -0.02em;
238
+ --text-heading-1: 1.5rem;
239
+ --text-heading-1--line-height: 2rem;
240
+ --text-heading-2: 1.25rem;
241
+ --text-heading-2--line-height: 1.75rem;
242
+ --text-heading-3: 1rem;
243
+ --text-heading-3--line-height: 1.5rem;
244
+ --text-body: 0.9375rem;
245
+ --text-body--line-height: 1.5rem;
246
+ --text-body-sm: 0.8125rem;
247
+ --text-body-sm--line-height: 1.25rem;
248
+ --text-label: 0.75rem;
249
+ --text-label--line-height: 1rem;
250
+ --text-label--letter-spacing: 0.02em;
251
+ --text-code: 0.84375rem;
252
+ --text-code--line-height: 1.375rem;
253
+
254
+ /* Radii — 6px is the workhorse. */
255
+ --radius-sm: 4px;
256
+ --radius-md: 6px;
257
+ --radius-lg: 8px;
258
+ --radius-full: 9999px;
259
+
260
+ /* Motion */
261
+ --ease-standard: cubic-bezier(0.2, 0, 0, 1);
262
+ --ease-emphasized: cubic-bezier(0.3, 0, 0, 1);
263
+ }
264
+
265
+ @layer base {
266
+ * {
267
+ border-color: var(--border);
268
+ }
269
+
270
+ html {
271
+ -webkit-text-size-adjust: 100%;
272
+ }
273
+
274
+ body {
275
+ margin: 0;
276
+ background-color: var(--bg);
277
+ color: var(--text);
278
+ font-family: var(--cw-font-sans);
279
+ font-size: var(--text-body);
280
+ line-height: 1.5rem;
281
+ -webkit-font-smoothing: antialiased;
282
+ text-rendering: optimizeLegibility;
283
+ }
284
+
285
+ /* Standard focus ring: accent-derived, 2px + 2px offset. */
286
+ :focus-visible {
287
+ outline: 2px solid var(--ring);
288
+ outline-offset: 2px;
289
+ border-radius: 2px;
290
+ }
291
+
292
+ @media (prefers-reduced-motion: reduce) {
293
+ *,
294
+ *::before,
295
+ *::after {
296
+ animation-duration: 0.001ms !important;
297
+ animation-iteration-count: 1 !important;
298
+ transition-duration: 0.001ms !important;
299
+ scroll-behavior: auto !important;
300
+ }
301
+ }
302
+
303
+ /* Lucide icons ship a hardcoded 24px; default them to 16px. :where() keeps specificity 0. */
304
+ :where(svg.lucide) {
305
+ width: 1rem;
306
+ height: 1rem;
307
+ }
308
+ }
309
+
310
+ /* The inset frame's gradient "shine" edge (app shells). Do NOT add bg-* on top. */
311
+ .frame-shine {
312
+ border: 1px solid transparent;
313
+ background:
314
+ linear-gradient(var(--bg), var(--bg)) padding-box,
315
+ linear-gradient(
316
+ 135deg,
317
+ var(--shine) 0%,
318
+ color-mix(in srgb, var(--shine) 55%, transparent) 55%,
319
+ color-mix(in srgb, var(--shine) 30%, transparent) 100%
320
+ )
321
+ border-box;
322
+ box-shadow: var(--frame-shadow);
323
+ }
324
+
325
+ /* ── prompt-area (vendored from just-marketing/prompt-area, MIT) ──────────────
326
+ Hand-authored styles its contentEditable chips/markers depend on; colors ride
327
+ the stock-name bridge vars above (--secondary/--foreground). */
328
+ .prompt-area-chip {
329
+ display: inline-flex;
330
+ align-items: center;
331
+ padding: 1px 6px;
332
+ border-radius: 4px;
333
+ font-size: 0.8125rem;
334
+ font-weight: 500;
335
+ cursor: pointer;
336
+ user-select: none;
337
+ vertical-align: baseline;
338
+ margin: 0 1px;
339
+ background-color: var(--secondary);
340
+ color: var(--foreground);
341
+ transition:
342
+ background-color 0.2s ease,
343
+ transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
344
+ box-shadow 0.2s ease;
345
+ position: relative;
346
+ overflow: hidden;
347
+ }
348
+ .prompt-area-chip:hover {
349
+ background-color: color-mix(in srgb, var(--secondary) 80%, var(--foreground) 20%);
350
+ box-shadow: 0 1px 4px color-mix(in srgb, var(--foreground) 10%, transparent);
351
+ }
352
+ .prompt-area-chip:active {
353
+ transform: scale(0.93);
354
+ background-color: color-mix(in srgb, var(--secondary) 70%, var(--foreground) 30%);
355
+ box-shadow: 0 0 0 transparent;
356
+ transition:
357
+ background-color 0.05s ease,
358
+ transform 0.08s cubic-bezier(0.4, 0, 0.2, 1),
359
+ box-shadow 0.05s ease;
360
+ }
361
+ @keyframes prompt-area-chip-ripple {
362
+ 0% {
363
+ transform: scale(0);
364
+ opacity: 0.4;
365
+ }
366
+ 100% {
367
+ transform: scale(2.5);
368
+ opacity: 0;
369
+ }
370
+ }
371
+ .prompt-area-chip-ripple {
372
+ position: absolute;
373
+ border-radius: 50%;
374
+ width: 100%;
375
+ aspect-ratio: 1;
376
+ background: color-mix(in srgb, var(--foreground) 15%, transparent);
377
+ pointer-events: none;
378
+ animation: prompt-area-chip-ripple 0.45s ease-out forwards;
379
+ }
380
+ .prompt-area-md-marker {
381
+ font-size: 0;
382
+ display: inline;
383
+ }
384
+ .prompt-area-list-bullet {
385
+ font-size: 0;
386
+ display: inline-flex;
387
+ align-items: center;
388
+ vertical-align: middle;
389
+ transform: translateY(-1.5px);
390
+ margin-right: 4px;
391
+ }
392
+ .prompt-area-list-bullet::before {
393
+ content: '';
394
+ width: var(--prompt-area-bullet-size, 6px);
395
+ height: var(--prompt-area-bullet-size, 6px);
396
+ border-radius: 50%;
397
+ background: currentColor;
398
+ }
399
+ .prompt-area-list-indent {
400
+ display: inline-block;
401
+ white-space: pre;
402
+ }
403
+ .prompt-area-chip--inline {
404
+ padding: 0;
405
+ border-radius: 0;
406
+ margin: 0;
407
+ font-weight: 700;
408
+ }
409
+ @media (prefers-reduced-motion: reduce) {
410
+ .prompt-area-editor {
411
+ transition: none !important;
412
+ }
413
+ }