@aparte/core 0.2.0-alpha.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/client/aparte-client.d.ts +326 -0
  4. package/dist/client/aparte-client.d.ts.map +1 -0
  5. package/dist/client/stream-adapter.d.ts +197 -0
  6. package/dist/client/stream-adapter.d.ts.map +1 -0
  7. package/dist/components/bubble/aparte-chat-bubble.d.ts +153 -0
  8. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -0
  9. package/dist/components/bubble/bubble-sync.d.ts +44 -0
  10. package/dist/components/bubble/bubble-sync.d.ts.map +1 -0
  11. package/dist/components/bubble/index.d.ts +4 -0
  12. package/dist/components/bubble/index.d.ts.map +1 -0
  13. package/dist/components/chat/aparte-chat.d.ts +49 -0
  14. package/dist/components/chat/aparte-chat.d.ts.map +1 -0
  15. package/dist/components/chat/index.d.ts +2 -0
  16. package/dist/components/chat/index.d.ts.map +1 -0
  17. package/dist/components/composer/aparte-composer-action.d.ts +37 -0
  18. package/dist/components/composer/aparte-composer-action.d.ts.map +1 -0
  19. package/dist/components/composer/aparte-composer-add-attachment.d.ts +27 -0
  20. package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -0
  21. package/dist/components/composer/aparte-composer-attachments.d.ts +26 -0
  22. package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -0
  23. package/dist/components/composer/aparte-composer-cancel.d.ts +21 -0
  24. package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -0
  25. package/dist/components/composer/aparte-composer-input.d.ts +63 -0
  26. package/dist/components/composer/aparte-composer-input.d.ts.map +1 -0
  27. package/dist/components/composer/aparte-composer-send.d.ts +25 -0
  28. package/dist/components/composer/aparte-composer-send.d.ts.map +1 -0
  29. package/dist/components/composer/aparte-composer.d.ts +150 -0
  30. package/dist/components/composer/aparte-composer.d.ts.map +1 -0
  31. package/dist/components/composer/index.d.ts +9 -0
  32. package/dist/components/composer/index.d.ts.map +1 -0
  33. package/dist/components/conversation-list/aparte-conversation-list.d.ts +50 -0
  34. package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -0
  35. package/dist/components/conversation-list/index.d.ts +2 -0
  36. package/dist/components/conversation-list/index.d.ts.map +1 -0
  37. package/dist/components/elicitation/aparte-elicitation.d.ts +29 -0
  38. package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -0
  39. package/dist/components/index.d.ts +10 -0
  40. package/dist/components/index.d.ts.map +1 -0
  41. package/dist/components/status/aparte-chat-status.d.ts +34 -0
  42. package/dist/components/status/aparte-chat-status.d.ts.map +1 -0
  43. package/dist/components/status/index.d.ts +2 -0
  44. package/dist/components/status/index.d.ts.map +1 -0
  45. package/dist/components/viewport/aparte-chat-viewport.d.ts +358 -0
  46. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -0
  47. package/dist/components/viewport/index.d.ts +2 -0
  48. package/dist/components/viewport/index.d.ts.map +1 -0
  49. package/dist/config/action-provider.d.ts +59 -0
  50. package/dist/config/action-provider.d.ts.map +1 -0
  51. package/dist/config/aparte-config.d.ts +490 -0
  52. package/dist/config/aparte-config.d.ts.map +1 -0
  53. package/dist/config/attachment-renderer.d.ts +28 -0
  54. package/dist/config/attachment-renderer.d.ts.map +1 -0
  55. package/dist/config/avatar-provider.d.ts +35 -0
  56. package/dist/config/avatar-provider.d.ts.map +1 -0
  57. package/dist/config/bubble-shell-renderer.d.ts +41 -0
  58. package/dist/config/bubble-shell-renderer.d.ts.map +1 -0
  59. package/dist/config/config-context.d.ts +47 -0
  60. package/dist/config/config-context.d.ts.map +1 -0
  61. package/dist/config/error-renderer.d.ts +26 -0
  62. package/dist/config/error-renderer.d.ts.map +1 -0
  63. package/dist/config/icon-provider.d.ts +63 -0
  64. package/dist/config/icon-provider.d.ts.map +1 -0
  65. package/dist/config/index.d.ts +19 -0
  66. package/dist/config/index.d.ts.map +1 -0
  67. package/dist/config/locale.d.ts +60 -0
  68. package/dist/config/locale.d.ts.map +1 -0
  69. package/dist/config/sanitize.d.ts +31 -0
  70. package/dist/config/sanitize.d.ts.map +1 -0
  71. package/dist/config/sibling-nav-renderer.d.ts +27 -0
  72. package/dist/config/sibling-nav-renderer.d.ts.map +1 -0
  73. package/dist/config/skeleton-provider.d.ts +26 -0
  74. package/dist/config/skeleton-provider.d.ts.map +1 -0
  75. package/dist/config/status-renderer.d.ts +23 -0
  76. package/dist/config/status-renderer.d.ts.map +1 -0
  77. package/dist/conversations/conversation-controller.d.ts +126 -0
  78. package/dist/conversations/conversation-controller.d.ts.map +1 -0
  79. package/dist/conversations/conversation-manager.d.ts +98 -0
  80. package/dist/conversations/conversation-manager.d.ts.map +1 -0
  81. package/dist/conversations/index.d.ts +5 -0
  82. package/dist/conversations/index.d.ts.map +1 -0
  83. package/dist/conversations/types.d.ts +172 -0
  84. package/dist/conversations/types.d.ts.map +1 -0
  85. package/dist/custom-elements.json +15833 -0
  86. package/dist/elicitation/index.d.ts +20 -0
  87. package/dist/elicitation/index.d.ts.map +1 -0
  88. package/dist/elicitation/panel.d.ts +18 -0
  89. package/dist/elicitation/panel.d.ts.map +1 -0
  90. package/dist/elicitation/types.d.ts +98 -0
  91. package/dist/elicitation/types.d.ts.map +1 -0
  92. package/dist/host/aparte-chat-host.d.ts +176 -0
  93. package/dist/host/aparte-chat-host.d.ts.map +1 -0
  94. package/dist/host/index.d.ts +2 -0
  95. package/dist/host/index.d.ts.map +1 -0
  96. package/dist/index-D89k-n2Q.js +6294 -0
  97. package/dist/index-D89k-n2Q.js.map +1 -0
  98. package/dist/index.css +2554 -0
  99. package/dist/index.d.ts +62 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +3763 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/index.node.d.ts +53 -0
  104. package/dist/index.node.d.ts.map +1 -0
  105. package/dist/index.node.js +49 -0
  106. package/dist/index.node.js.map +1 -0
  107. package/dist/interop/element-props.d.ts +23 -0
  108. package/dist/interop/element-props.d.ts.map +1 -0
  109. package/dist/parsers/aparte-event-stream.d.ts +13 -0
  110. package/dist/parsers/aparte-event-stream.d.ts.map +1 -0
  111. package/dist/parsers/aparte-stream-parser.d.ts +137 -0
  112. package/dist/parsers/aparte-stream-parser.d.ts.map +1 -0
  113. package/dist/parsers/index.d.ts +4 -0
  114. package/dist/parsers/index.d.ts.map +1 -0
  115. package/dist/primitives/index.d.ts +8 -0
  116. package/dist/primitives/index.d.ts.map +1 -0
  117. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts +25 -0
  118. package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -0
  119. package/dist/primitives/progress-spinner/index.d.ts +2 -0
  120. package/dist/primitives/progress-spinner/index.d.ts.map +1 -0
  121. package/dist/primitives/select/aparte-optgroup.d.ts +32 -0
  122. package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -0
  123. package/dist/primitives/select/aparte-option.d.ts +30 -0
  124. package/dist/primitives/select/aparte-option.d.ts.map +1 -0
  125. package/dist/primitives/select/aparte-select.d.ts +74 -0
  126. package/dist/primitives/select/aparte-select.d.ts.map +1 -0
  127. package/dist/primitives/select/index.d.ts +9 -0
  128. package/dist/primitives/select/index.d.ts.map +1 -0
  129. package/dist/renderers/index.d.ts +5 -0
  130. package/dist/renderers/index.d.ts.map +1 -0
  131. package/dist/renderers/segment-renderers.d.ts +31 -0
  132. package/dist/renderers/segment-renderers.d.ts.map +1 -0
  133. package/dist/runtime/message-repository.d.ts +111 -0
  134. package/dist/runtime/message-repository.d.ts.map +1 -0
  135. package/dist/transport/backend-handler.d.ts +50 -0
  136. package/dist/transport/backend-handler.d.ts.map +1 -0
  137. package/dist/transport/backend-transport.d.ts +39 -0
  138. package/dist/transport/backend-transport.d.ts.map +1 -0
  139. package/dist/transport/direct-transport.d.ts +36 -0
  140. package/dist/transport/direct-transport.d.ts.map +1 -0
  141. package/dist/transport/index.d.ts +6 -0
  142. package/dist/transport/index.d.ts.map +1 -0
  143. package/dist/transport/types.d.ts +73 -0
  144. package/dist/transport/types.d.ts.map +1 -0
  145. package/dist/types/chat.d.ts +223 -0
  146. package/dist/types/chat.d.ts.map +1 -0
  147. package/dist/types/errors.d.ts +25 -0
  148. package/dist/types/errors.d.ts.map +1 -0
  149. package/dist/types/event-map.d.ts +41 -0
  150. package/dist/types/event-map.d.ts.map +1 -0
  151. package/dist/types/events.d.ts +290 -0
  152. package/dist/types/events.d.ts.map +1 -0
  153. package/dist/types/imperative-api.d.ts +38 -0
  154. package/dist/types/imperative-api.d.ts.map +1 -0
  155. package/dist/types/index.d.ts +22 -0
  156. package/dist/types/index.d.ts.map +1 -0
  157. package/dist/types/model-provider.d.ts +229 -0
  158. package/dist/types/model-provider.d.ts.map +1 -0
  159. package/dist/types/models.d.ts +173 -0
  160. package/dist/types/models.d.ts.map +1 -0
  161. package/dist/types/parsers.d.ts +42 -0
  162. package/dist/types/parsers.d.ts.map +1 -0
  163. package/dist/types/providers.d.ts +93 -0
  164. package/dist/types/providers.d.ts.map +1 -0
  165. package/dist/types/segments.d.ts +221 -0
  166. package/dist/types/segments.d.ts.map +1 -0
  167. package/dist/types/theming.d.ts +44 -0
  168. package/dist/types/theming.d.ts.map +1 -0
  169. package/dist/types/tools.d.ts +98 -0
  170. package/dist/types/tools.d.ts.map +1 -0
  171. package/dist/utils/assert-never.d.ts +9 -0
  172. package/dist/utils/assert-never.d.ts.map +1 -0
  173. package/dist/utils/css-escape.d.ts +15 -0
  174. package/dist/utils/css-escape.d.ts.map +1 -0
  175. package/package.json +68 -0
package/dist/index.css ADDED
@@ -0,0 +1,2554 @@
1
+ /*
2
+ * Aparte CSS
3
+ * Single source of truth for all component styles.
4
+ * Theme is controlled via CSS variables - app sets [data-aparte-theme="dark"] to switch.
5
+ */
6
+
7
+ /* =========================================
8
+ Light Theme (Default)
9
+ ========================================= */
10
+ :root,
11
+ :host {
12
+ /* Base Colors */
13
+ --aparte-primary: #b07d33;
14
+ --aparte-primary-hover: #a06f28;
15
+ --aparte-secondary: #9a8fb0;
16
+ --aparte-neutral: #6d6479;
17
+
18
+ /* Backgrounds & Surfaces */
19
+ --aparte-bg: #f6f2ea;
20
+ --aparte-surface-1: #fffdf8;
21
+ /* Bubbles, cards */
22
+ --aparte-surface-2: #f1ebdf;
23
+ /* Code blocks, headers */
24
+ --aparte-surface-3: #e7dfce;
25
+ /* Skeletons, disabled */
26
+
27
+ /* Text */
28
+ --aparte-text: #241f2b;
29
+ --aparte-text-muted: #6d6479;
30
+ --aparte-text-inverse: #fffdf8;
31
+
32
+ /* Borders */
33
+ --aparte-border: #e5ddce;
34
+ --aparte-border-focus: var(--aparte-primary);
35
+ --aparte-border-width: 1px;
36
+
37
+ /* Status Colors */
38
+ --aparte-info: #3b82f6;
39
+ --aparte-success: #10b981;
40
+ --aparte-warning: #f59e0b;
41
+ --aparte-error: #ef4444;
42
+
43
+ /* Radius scale */
44
+ --aparte-radius-xs: 2px;
45
+ --aparte-radius-sm: 4px;
46
+ --aparte-radius-md: 6px;
47
+ --aparte-radius-lg: 8px;
48
+ --aparte-radius-xl: 12px;
49
+ --aparte-radius-full: 9999px;
50
+
51
+ /* Component-specific radius (overridable per app) */
52
+ --aparte-radius-input: var(--aparte-radius-lg);
53
+ --aparte-radius-input-footer: var(--aparte-radius-lg);
54
+ --aparte-radius-send-btn: var(--aparte-radius-md);
55
+ --aparte-radius-action-btn: var(--aparte-radius-sm);
56
+ --aparte-radius-select: var(--aparte-radius-md);
57
+ --aparte-radius-avatar: var(--aparte-radius-md);
58
+ --aparte-radius-bubble: var(--aparte-radius-lg);
59
+ --aparte-radius-code: var(--aparte-radius-lg);
60
+ --aparte-radius-thinking: var(--aparte-radius-lg);
61
+ --aparte-radius-terminal: var(--aparte-radius-lg);
62
+ --aparte-radius-error: var(--aparte-radius-md);
63
+ --aparte-radius-file-tree: var(--aparte-radius-lg);
64
+
65
+ /* Spacing scale — every gap / padding / margin / offset routes through these,
66
+ so there are no raw px spacings in the rules. Incidental, pixel-tuned values
67
+ (e.g. an overlay inset) get their own component var instead of a scale step. */
68
+ --aparte-space-1: 2px;
69
+ --aparte-space-2: 4px;
70
+ --aparte-space-3: 6px;
71
+ --aparte-space-4: 8px;
72
+ --aparte-space-5: 10px;
73
+ --aparte-space-6: 12px;
74
+ --aparte-space-7: 14px;
75
+ --aparte-space-8: 16px;
76
+
77
+ /* Font-size scale — component text sizes reference these (no raw px sizes). */
78
+ --aparte-font-size-2xs: 10px;
79
+ --aparte-font-size-xs: 11px;
80
+ --aparte-font-size-sm: 12px;
81
+ --aparte-font-size-md: 13px;
82
+ --aparte-font-size-lg: 14px;
83
+ --aparte-font-size-base: 15px;
84
+
85
+ /* Font-weight scale — every font-weight references these (no raw weights). */
86
+ --aparte-font-weight-normal: 400;
87
+ --aparte-font-weight-medium: 500;
88
+ --aparte-font-weight-semibold: 600;
89
+ --aparte-font-weight-bold: 700;
90
+
91
+ /* Line-height scale — every line-height references these (no raw values). */
92
+ --aparte-line-height-none: 1;
93
+ --aparte-line-height-tight: 1.25;
94
+ --aparte-line-height-normal: 1.5;
95
+ --aparte-line-height-relaxed: 1.6;
96
+ --aparte-line-height-loose: 1.7;
97
+
98
+ /* Typography */
99
+ --aparte-font-family: system-ui, -apple-system, sans-serif;
100
+
101
+ /* Text/icon over a --aparte-primary (brass) fill — send button, run button. */
102
+ --aparte-on-primary: #ffffff;
103
+ /* Focus ring — derived from the accent so it themes automatically. */
104
+ --aparte-focus-ring: 0 0 0 3px color-mix(in srgb, var(--aparte-primary) 15%, transparent);
105
+ /* Outline width for keyboard focus + drag-over indicators. */
106
+ --aparte-focus-outline-width: 2px;
107
+ /* Scrollbars rendered by the library. */
108
+ --aparte-scrollbar-width: 6px;
109
+ /* Comfortable hit-area on coarse (touch) pointers. */
110
+ --aparte-touch-target-size: 44px;
111
+
112
+ /* Messages */
113
+ --aparte-message-gap: 12px;
114
+ --aparte-message-padding: 16px 12px;
115
+ --aparte-message-max-width: 800px;
116
+
117
+ /* Bubbles — opt-in. By default both roles render as a transparent row with
118
+ no bubble background; consumers can flip these to get a chat-app look. */
119
+ --aparte-message-content-bg-user: #efe7f6;
120
+ --aparte-message-content-bg-assistant: transparent;
121
+ --aparte-message-content-text-user: var(--aparte-text);
122
+ --aparte-message-content-text-assistant: var(--aparte-text);
123
+ --aparte-message-content-padding: 10px 14px;
124
+ --aparte-message-content-radius: 14px;
125
+
126
+ /* Aliases used by some segment renderers (artifact card, tool pill).
127
+ Mapped to existing tokens so consumers that override --aparte-surface-1/2
128
+ get cohesive results without touching every rule. */
129
+ --aparte-surface: var(--aparte-surface-1);
130
+ --aparte-surface-hover: var(--aparte-surface-2);
131
+ --aparte-text-secondary: var(--aparte-text-muted);
132
+ --aparte-accent: var(--aparte-primary);
133
+ --aparte-success-border: var(--aparte-success);
134
+ --aparte-success-surface: color-mix(in oklch, var(--aparte-success) 12%, transparent);
135
+ --aparte-error-border-soft: var(--aparte-error);
136
+ --aparte-error-surface: color-mix(in oklch, var(--aparte-error) 12%, transparent);
137
+
138
+ /* Code typography — exposed so apps can swap in Geist Mono / Cascadia / … */
139
+ --aparte-code-font-family: ui-monospace, 'Cascadia Code', 'JetBrains Mono', 'Geist Mono', SFMono-Regular, Consolas, 'Liberation Mono', monospace;
140
+
141
+ /* Attachments */
142
+ --aparte-attachment-chip-bg: var(--aparte-surface-2);
143
+ --aparte-attachment-chip-border: var(--aparte-border);
144
+ --aparte-attachment-chip-radius: var(--aparte-radius-sm);
145
+ --aparte-attachment-chip-color: var(--aparte-text);
146
+ --aparte-attachment-image-size: 72px;
147
+ --aparte-attachments-max-height: 140px;
148
+
149
+ /* Attachment thumbnail tile (composer preview + sent-message strip) */
150
+ --aparte-thumb-radius: var(--aparte-radius-lg);
151
+ --aparte-thumb-name-color: #ffffff;
152
+ --aparte-thumb-name-scrim: linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0));
153
+ --aparte-thumb-name-padding: 14px 5px 4px;
154
+ --aparte-thumb-remove-size: 18px;
155
+ --aparte-thumb-remove-inset: 3px;
156
+ --aparte-thumb-remove-bg: rgba(0, 0, 0, 0.6);
157
+ --aparte-thumb-remove-bg-hover: rgba(0, 0, 0, 0.85);
158
+ --aparte-thumb-remove-color: #ffffff;
159
+
160
+ /* Action Bar */
161
+ --aparte-action-bar-gap: 4px;
162
+ --aparte-action-bar-btn-size: 28px;
163
+ --aparte-action-bar-btn-color: var(--aparte-text-muted);
164
+ --aparte-action-bar-btn-hover-bg: var(--aparte-surface-2);
165
+ --aparte-action-bar-btn-hover-color: var(--aparte-text);
166
+
167
+ /* Branch Picker */
168
+ --aparte-branch-picker-gap: 4px;
169
+ --aparte-branch-picker-btn-size: 20px;
170
+ --aparte-branch-picker-btn-icon-size: 16px;
171
+ --aparte-branch-picker-btn-color: var(--aparte-text-muted);
172
+ --aparte-branch-picker-btn-hover-color: var(--aparte-text);
173
+ --aparte-branch-picker-label-size: 12px;
174
+ --aparte-branch-picker-label-color: var(--aparte-text-muted);
175
+ --aparte-branch-picker-label-min-width: 32px;
176
+
177
+ /* Avatars */
178
+ --aparte-avatar-size: 32px;
179
+ --aparte-avatar-font-size: 14px;
180
+ --aparte-avatar-radius: var(--aparte-radius-avatar);
181
+ --aparte-avatar-bg-user: var(--aparte-primary);
182
+ --aparte-avatar-text-user: var(--aparte-text-inverse);
183
+ --aparte-avatar-bg-assistant: var(--aparte-surface-3);
184
+ --aparte-avatar-text-assistant: var(--aparte-text-inverse);
185
+ --aparte-avatar-image-user: none;
186
+ --aparte-avatar-image-assistant: none;
187
+ --aparte-avatar-image-size: 90%;
188
+
189
+ /* Text Sizes */
190
+ --aparte-name-font-size: 14px;
191
+ --aparte-name-color: var(--aparte-text);
192
+ --aparte-timestamp-font-size: 12px;
193
+ --aparte-timestamp-color: var(--aparte-text-muted);
194
+ --aparte-content-font-size: 15px;
195
+ --aparte-content-color: var(--aparte-text);
196
+
197
+ /* Input */
198
+ --aparte-input-bg: var(--aparte-surface-1);
199
+ --aparte-input-border: var(--aparte-border);
200
+ --aparte-input-text: var(--aparte-text);
201
+ --aparte-input-placeholder: var(--aparte-text-muted);
202
+ --aparte-input-focus-border: var(--aparte-primary);
203
+ --aparte-input-gap: 8px;
204
+ --aparte-input-editor-font-size: 15px;
205
+ --aparte-input-editor-max-height: 200px;
206
+ --aparte-input-container-min-height: 44px;
207
+
208
+ /* Send button + inline action buttons */
209
+ --aparte-send-btn-size: 36px;
210
+ --aparte-input-action-btn-size: 36px;
211
+ --aparte-input-action-btn-icon-size: 20px;
212
+ --aparte-select-min-width: 120px;
213
+
214
+ /* Status Indicator */
215
+ --aparte-status-color: var(--aparte-text-muted);
216
+ --aparte-status-bg: transparent;
217
+ --aparte-status-dot-size: 6px;
218
+
219
+ /* Thinking Segment */
220
+ --aparte-thinking-border: var(--aparte-border);
221
+ --aparte-thinking-bg: var(--aparte-surface-2);
222
+ --aparte-thinking-text: var(--aparte-text-muted);
223
+ --aparte-thinking-content: var(--aparte-text);
224
+ --aparte-thinking-content-bg: var(--aparte-surface-1);
225
+ --aparte-thinking-content-max-height: 300px;
226
+ --aparte-thinking-content-line-height: 1.55;
227
+ --aparte-thinking-toggle-size: 9px;
228
+ --aparte-thinking-rail-width: 2px;
229
+
230
+ /* Code Blocks */
231
+ --aparte-code-bg: var(--aparte-surface-1);
232
+ --aparte-code-header-bg: var(--aparte-surface-2);
233
+ --aparte-code-border: var(--aparte-border);
234
+ --aparte-code-header-text: var(--aparte-text-muted);
235
+ --aparte-code-copy-hover-bg: rgba(0, 0, 0, 0.05);
236
+ --aparte-code-copy-icon-size: 14px;
237
+ --aparte-code-inline-font-size: 0.875em;
238
+
239
+ /* Terminal */
240
+ --aparte-terminal-bg: #1e293b;
241
+ --aparte-terminal-text: #e2e8f0;
242
+ --aparte-terminal-icon: #64748b;
243
+ --aparte-terminal-hover: #334155;
244
+ --aparte-terminal-running: #22c55e;
245
+ --aparte-terminal-output-bg: #0f172a;
246
+ --aparte-terminal-output-text: #94a3b8;
247
+ --aparte-terminal-copy-btn-size: 26px;
248
+ --aparte-terminal-font-size: 13px;
249
+ --aparte-terminal-output-max-height: 200px;
250
+
251
+ /* Progress */
252
+ --aparte-progress-bar-bg: var(--aparte-surface-3);
253
+ --aparte-progress-bar-fill: var(--aparte-primary);
254
+ --aparte-progress-label: var(--aparte-text);
255
+
256
+ /* Scroll-to-bottom button */
257
+ --aparte-scroll-btn-size: 36px;
258
+ --aparte-scroll-btn-bg: var(--aparte-surface-1);
259
+ --aparte-scroll-btn-color: var(--aparte-text);
260
+ --aparte-scroll-btn-border: var(--aparte-border);
261
+ --aparte-scroll-btn-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
262
+ --aparte-scroll-btn-hover-bg: var(--aparte-surface-2);
263
+
264
+ /* File Tree */
265
+ --aparte-file-tree-bg: var(--aparte-surface-1);
266
+ --aparte-file-tree-border: var(--aparte-border);
267
+ --aparte-file-tree-header-bg: var(--aparte-surface-2);
268
+ --aparte-file-tree-hover: var(--aparte-surface-2);
269
+ --aparte-file-tree-title: var(--aparte-text-muted);
270
+ /* Git-status file colours reuse the semantic status tokens by default. */
271
+ --aparte-file-status-added: var(--aparte-success);
272
+ --aparte-file-status-modified: var(--aparte-info);
273
+ --aparte-file-status-deleted: var(--aparte-error);
274
+
275
+ /* Error segment */
276
+ --aparte-error-bg: #fef2f2;
277
+ --aparte-error-border: #fecaca;
278
+ --aparte-error-solid: #dc2626;
279
+ --aparte-error-text: #991b1b;
280
+ --aparte-error-title: #7f1d1d;
281
+ --aparte-error-icon-size: 20px;
282
+ --aparte-error-rail-width: 4px;
283
+
284
+ /* Prose (segment-content) */
285
+ --aparte-prose-p-margin: 0.4em 0;
286
+ --aparte-prose-h1-size: 1.4em;
287
+ --aparte-prose-h2-size: 1.25em;
288
+ --aparte-prose-h3-size: 1.1em;
289
+ --aparte-prose-list-indent: 1.5em;
290
+ --aparte-prose-list-margin: 0.4em 0;
291
+ --aparte-prose-li-margin: 0.15em 0;
292
+ --aparte-prose-blockquote-border: var(--aparte-border);
293
+ --aparte-prose-blockquote-border-width: 3px;
294
+ --aparte-prose-code-bg: var(--aparte-surface-2);
295
+
296
+ /* Viewport */
297
+ --aparte-viewport-padding: 16px;
298
+
299
+ /* Content */
300
+ --aparte-content-line-height: var(--aparte-line-height-loose, 1.7);
301
+ --aparte-cursor-color: var(--aparte-primary);
302
+
303
+ /* Status */
304
+ --aparte-status-font-size: 13px;
305
+
306
+ /* Input (composer) */
307
+ --aparte-input-font-size: 14px;
308
+ --aparte-input-line-height: var(--aparte-line-height-normal, 1.5);
309
+ --aparte-input-padding-x: 12px;
310
+ --aparte-input-padding-y: 10px;
311
+
312
+ /* Conversation List */
313
+ --aparte-conv-list-gap: 2px;
314
+ --aparte-conv-item-padding: 7px 10px;
315
+ --aparte-conv-item-radius: var(--aparte-radius-md);
316
+ --aparte-conv-item-color: var(--aparte-text-muted);
317
+ --aparte-conv-item-color-active: var(--aparte-text);
318
+ --aparte-conv-item-bg-hover: var(--aparte-surface-3);
319
+ --aparte-conv-item-bg-active: var(--aparte-surface-3);
320
+ --aparte-conv-item-font-size: 0.8125rem;
321
+ --aparte-conv-item-font-weight-active: var(--aparte-font-weight-medium, 500);
322
+ --aparte-conv-delete-color: var(--aparte-text-muted);
323
+ --aparte-conv-delete-bg-hover: var(--aparte-error);
324
+ --aparte-conv-delete-color-hover: var(--aparte-text-inverse);
325
+ --aparte-conv-delete-radius: var(--aparte-radius-sm);
326
+ --aparte-conv-item-gap: 6px;
327
+ --aparte-conv-action-btn-size: 20px;
328
+ }
329
+
330
+ /* =========================================
331
+ Dark Theme (App-controlled via attribute)
332
+ Usage: <div data-aparte-theme="dark">...</div>
333
+ ========================================= */
334
+ [data-aparte-theme="dark"] {
335
+ /* Backgrounds */
336
+ --aparte-bg: #17141c;
337
+ --aparte-surface-1: #211b28;
338
+ --aparte-surface-2: #2a2333;
339
+ --aparte-surface-3: #342a40;
340
+
341
+ /* Bubbles — user only; assistant is plain prose (AI-chat convention) */
342
+ --aparte-message-content-bg-user: #2f2740;
343
+ --aparte-message-content-bg-assistant: transparent;
344
+
345
+ /* Text */
346
+ --aparte-text: #f2eee7;
347
+ --aparte-text-muted: #a89bb6;
348
+ --aparte-text-inverse: #17141c;
349
+ --aparte-name-color: #f2eee7;
350
+ --aparte-content-color: #ece8e2;
351
+ --aparte-timestamp-color: #7a7088;
352
+
353
+ /* Borders */
354
+ --aparte-border: #342a40;
355
+
356
+ /* Primary */
357
+ --aparte-primary: #d9a24b;
358
+ --aparte-primary-hover: #e7c588;
359
+
360
+ /* Input */
361
+ --aparte-input-bg: #211b28;
362
+ --aparte-input-border: #342a40;
363
+ --aparte-input-text: #f2eee7;
364
+ --aparte-input-placeholder: #7a7088;
365
+
366
+ /* Status */
367
+ --aparte-status-color: #94a3b8;
368
+
369
+ /* Thinking */
370
+ --aparte-thinking-border: #334155;
371
+ --aparte-thinking-bg: #1e293b;
372
+ --aparte-thinking-text: #94a3b8;
373
+ --aparte-thinking-content: #e5e7eb;
374
+ --aparte-thinking-content-bg: #0f172a;
375
+
376
+ /* Code */
377
+ --aparte-code-bg: #1e293b;
378
+ --aparte-code-header-bg: #334155;
379
+ --aparte-code-border: #475569;
380
+ --aparte-code-header-text: #94a3b8;
381
+
382
+ /* Progress */
383
+ --aparte-progress-bar-bg: #334155;
384
+ --aparte-scroll-btn-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
385
+ --aparte-progress-label: #e5e7eb;
386
+
387
+ /* File Tree */
388
+ --aparte-file-tree-bg: #1e293b;
389
+ --aparte-file-tree-border: #334155;
390
+ --aparte-file-tree-header-bg: #0f172a;
391
+ --aparte-file-tree-hover: #334155;
392
+ --aparte-file-tree-title: #94a3b8;
393
+
394
+ /* Error segment */
395
+ --aparte-error-bg: #2d1b1b;
396
+ --aparte-error-border: #7f1d1d;
397
+ --aparte-error-solid: #f87171;
398
+ --aparte-error-text: #fca5a5;
399
+ --aparte-error-title: #fecaca;
400
+
401
+ /* Prose */
402
+ --aparte-prose-blockquote-border: #475569;
403
+ --aparte-prose-code-bg: #334155;
404
+
405
+ /* Conversation List (dark overrides) */
406
+ --aparte-conv-item-bg-hover: #334155;
407
+ --aparte-conv-item-bg-active: #334155;
408
+ --aparte-conv-delete-color: #64748b;
409
+ }
410
+
411
+ /* =========================================
412
+ aparte-chat (the drop-in shell)
413
+ Composes a viewport + composer as a flex column. Size the element yourself
414
+ (a height, or let it fill a sized parent); the viewport grows and scrolls and
415
+ the composer sits below it. Mirrors the framework auto-center layout for the
416
+ vanilla viewport, which otherwise has no flex sizing of its own.
417
+ ========================================= */
418
+
419
+ /* Both host shapes core recognises get the same layout: the vanilla
420
+ <aparte-chat> element AND a framework wrapper's `[data-aparte-chat]` root
421
+ (React/Vue/Svelte/Angular render `<div data-aparte-chat>`). `[data-aparte-chat]`
422
+ is the SAME selector core uses to resolve the host (see aparte-client /
423
+ aparte-chat-bubble), so one rule covers both — no wrapper-specific class. */
424
+ aparte-chat,
425
+ [data-aparte-chat] {
426
+ display: flex;
427
+ flex-direction: column;
428
+ /* Fill the parent by default so the height:100% chain the inner scroll container
429
+ relies on has a definite anchor. Size the element yourself — a height on it, or
430
+ a sized parent. */
431
+ height: 100%;
432
+ width: 100%;
433
+ overflow: hidden;
434
+ }
435
+
436
+ /* The viewport takes the space left by the composer; the standalone height:100%
437
+ default resolves against the shell's definite height above, and the inner
438
+ .aparte-viewport-container keeps doing the scrolling (styled scrollbar, wheel,
439
+ auto-scroll, scroll button all stay wired to it). */
440
+ aparte-chat > aparte-chat-viewport,
441
+ [data-aparte-chat] > aparte-chat-viewport {
442
+ flex: 1 1 auto;
443
+ min-height: 0;
444
+ }
445
+
446
+ /* Model-selection gate (opt-in via AparteConfig.setRequireModelSelection): grey the
447
+ composer and block its send button until a model is selected — e.g. while the
448
+ model selector is still fetching. Ungates automatically on selection.
449
+ Unscoped so it greys the composer in EVERY host (vanilla shell + all wrappers),
450
+ not only a `[data-aparte-chat]` direct child. */
451
+ aparte-composer[data-model-gated] {
452
+ opacity: 0.55;
453
+ }
454
+ aparte-composer[data-model-gated] aparte-composer-send {
455
+ pointer-events: none;
456
+ }
457
+
458
+ aparte-composer {
459
+ flex: 0 0 auto;
460
+ }
461
+
462
+ /* center-empty: the shell centers the composer as a welcome state until the first
463
+ message, then slides to the normal layout. The component toggles [data-empty]
464
+ on itself by watching its viewport — only `flex-grow` animates, so the composer
465
+ glides to the bottom instead of snapping. */
466
+ aparte-chat[center-empty] > aparte-chat-viewport {
467
+ transition: flex-grow 0.3s ease;
468
+ }
469
+
470
+ aparte-chat[center-empty][data-empty] {
471
+ justify-content: center;
472
+ }
473
+
474
+ aparte-chat[center-empty][data-empty] > aparte-chat-viewport {
475
+ flex-grow: 0;
476
+ /* Release the standalone `height: 100%` (set on the viewport element for the
477
+ scroll chain): an explicit height ignores `flex-grow: 0`, so the empty viewport
478
+ would still fill the shell and leave `justify-content: center` no room to center
479
+ the composer. Empty = nothing to scroll, so auto height is safe here; the moment
480
+ a message lands, [data-empty] drops and height: 100% (scroll chain) is back. */
481
+ height: auto;
482
+ }
483
+
484
+ @media (prefers-reduced-motion: reduce) {
485
+ aparte-chat[center-empty] > aparte-chat-viewport {
486
+ transition-duration: 0.01ms;
487
+ }
488
+ }
489
+
490
+ /* =========================================
491
+ aparte-chat-viewport
492
+ ========================================= */
493
+
494
+ aparte-chat-viewport {
495
+ display: block;
496
+ width: 100%;
497
+ height: 100%;
498
+ overflow: hidden;
499
+ contain: layout style;
500
+ /* Container-query root: children adapt to the WIDGET's width, not the
501
+ viewport's — an embedded chat can be narrow inside a wide page. */
502
+ container-type: inline-size;
503
+ text-align: left;
504
+ }
505
+
506
+ .aparte-viewport-container {
507
+ display: flex;
508
+ flex-direction: column;
509
+ height: 100%;
510
+ width: 100%;
511
+ overflow-y: auto;
512
+ overflow-x: hidden;
513
+ overscroll-behavior: contain;
514
+ scrollbar-width: thin;
515
+ scrollbar-color: var(--aparte-neutral, #6b7280) transparent;
516
+ }
517
+
518
+ .aparte-viewport-container::-webkit-scrollbar {
519
+ width: var(--aparte-scrollbar-width, 6px);
520
+ }
521
+
522
+ .aparte-viewport-container::-webkit-scrollbar-track {
523
+ background: transparent;
524
+ }
525
+
526
+ .aparte-viewport-container::-webkit-scrollbar-thumb {
527
+ background: var(--aparte-neutral, #6b7280);
528
+ border-radius: var(--aparte-radius-xs);
529
+ }
530
+
531
+ .aparte-viewport-container::-webkit-scrollbar-thumb:hover {
532
+ background: var(--aparte-secondary, #9ca3af);
533
+ }
534
+
535
+ .aparte-messages-wrapper {
536
+ display: flex;
537
+ flex-direction: column;
538
+ gap: var(--aparte-message-gap, 0);
539
+ padding: var(--aparte-viewport-padding, 16px);
540
+ }
541
+
542
+ /* ─── Scroll-to-bottom button ─────────────────────────────────────────────── */
543
+
544
+ .aparte-scroll-btn {
545
+ position: absolute;
546
+ bottom: var(--aparte-space-8, 16px);
547
+ left: 50%;
548
+ transform: translateX(-50%);
549
+ z-index: 10;
550
+ width: var(--aparte-scroll-btn-size, 36px);
551
+ height: var(--aparte-scroll-btn-size, 36px);
552
+ border-radius: 50%;
553
+ background: var(--aparte-scroll-btn-bg);
554
+ color: var(--aparte-scroll-btn-color);
555
+ border: var(--aparte-border-width, 1px) solid var(--aparte-scroll-btn-border);
556
+ box-shadow: var(--aparte-scroll-btn-shadow);
557
+ cursor: pointer;
558
+ display: flex;
559
+ align-items: center;
560
+ justify-content: center;
561
+ transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s ease;
562
+ pointer-events: auto;
563
+ }
564
+
565
+ .aparte-scroll-btn:hover {
566
+ background: var(--aparte-scroll-btn-hover-bg);
567
+ }
568
+
569
+ .aparte-scroll-btn--hidden {
570
+ opacity: 0;
571
+ pointer-events: none;
572
+ transform: translateX(-50%) translateY(8px);
573
+ }
574
+
575
+ /* ─── Bottom spacer ───────────────────────────────────────────────────────── */
576
+
577
+ .aparte-bottom-spacer {
578
+ flex-shrink: 0;
579
+ height: 0px;
580
+ }
581
+
582
+ /* ─── Framework-managed viewport ──────────────────────────────────────────────
583
+ When a framework wrapper (React/Vue/Svelte/Angular) owns the bubble elements,
584
+ the viewport does NOT relocate them into an internal wrapper (that would break
585
+ the framework's DOM reconciliation). Instead the HOST itself is the scroll
586
+ surface — these rules mirror .aparte-viewport-container + .aparte-messages-wrapper
587
+ onto the host, and the bottom spacer becomes an additive padding (no element,
588
+ so it can't collide with the framework's children). */
589
+ aparte-chat-viewport.aparte-viewport--framework,
590
+ aparte-chat-viewport[framework-managed] {
591
+ overflow-y: auto;
592
+ overflow-x: hidden;
593
+ overscroll-behavior: contain;
594
+ display: flex;
595
+ flex-direction: column;
596
+ gap: var(--aparte-message-gap, 0);
597
+ padding: var(--aparte-viewport-padding, 16px);
598
+ padding-bottom: calc(var(--aparte-viewport-padding, 16px) + var(--aparte-fw-spacer, 0px));
599
+ scrollbar-width: thin;
600
+ scrollbar-color: var(--aparte-neutral, #6b7280) transparent;
601
+ }
602
+
603
+ /* The framework wrapper renders bubbles as DIRECT flex children of the viewport
604
+ (no inner wrapper, unlike the vanilla path). With the viewport as a fixed-height
605
+ flex column, those children would `flex-shrink` to fit — compressing content
606
+ instead of overflowing, so nothing scrolls. Pin their size so the content grows
607
+ past the viewport and the scroll surface actually scrolls. */
608
+ aparte-chat-viewport.aparte-viewport--framework > *,
609
+ aparte-chat-viewport[framework-managed] > * {
610
+ flex-shrink: 0;
611
+ }
612
+
613
+ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar {
614
+ width: var(--aparte-scrollbar-width, 6px);
615
+ }
616
+
617
+ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-track {
618
+ background: transparent;
619
+ }
620
+
621
+ aparte-chat-viewport.aparte-viewport--framework::-webkit-scrollbar-thumb {
622
+ background: var(--aparte-neutral, #6b7280);
623
+ border-radius: var(--aparte-radius-xs);
624
+ }
625
+
626
+ /* The scroll button lives INSIDE the scrolling host here (last child), so it
627
+ floats with `position: sticky` instead of the `absolute` used in core mode
628
+ (where it sits outside the scroll container). */
629
+ aparte-chat-viewport.aparte-viewport--framework > .aparte-scroll-btn {
630
+ position: sticky;
631
+ bottom: var(--aparte-space-8, 16px);
632
+ left: auto;
633
+ align-self: center;
634
+ transform: none;
635
+ flex-shrink: 0;
636
+ }
637
+
638
+ aparte-chat-viewport.aparte-viewport--framework > .aparte-scroll-btn.aparte-scroll-btn--hidden {
639
+ transform: translateY(8px);
640
+ }
641
+
642
+ /* =========================================
643
+ aparte-chat-bubble
644
+ ========================================= */
645
+
646
+ aparte-chat-bubble {
647
+ display: block;
648
+ width: 100%;
649
+ overflow: hidden;
650
+ /* Each bubble is its own layout/paint boundary, and bubbles scrolled out
651
+ of the viewport are skipped entirely (layout + paint) via
652
+ content-visibility. While a message streams the user is pinned to the
653
+ bottom, so every bubble above the live one stops costing a paint on each
654
+ token — that reclaimed GPU time goes back to the WebGPU decode.
655
+ contain-intrinsic-size keeps the scrollbar stable: the `auto` keyword
656
+ reuses a bubble's real height once it has been measured; the 320px is
657
+ only a fallback for bubbles that have never been rendered yet. */
658
+ contain: layout style paint;
659
+ content-visibility: auto;
660
+ contain-intrinsic-size: auto 320px;
661
+ }
662
+
663
+ .aparte-message {
664
+ display: flex;
665
+ gap: var(--aparte-message-gap, 12px);
666
+ padding: var(--aparte-message-padding, 16px 12px);
667
+ width: 100%;
668
+ max-width: var(--aparte-message-max-width, 800px);
669
+ margin: 0 auto;
670
+ min-width: 0;
671
+ }
672
+
673
+ .aparte-avatar {
674
+ flex-shrink: 0;
675
+ width: var(--aparte-avatar-size, 32px);
676
+ height: var(--aparte-avatar-size, 32px);
677
+ border-radius: var(--aparte-avatar-radius, 6px);
678
+ display: flex;
679
+ align-items: center;
680
+ justify-content: center;
681
+ font-size: var(--aparte-avatar-font-size, 14px);
682
+ font-weight: var(--aparte-font-weight-semibold, 600);
683
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
684
+ }
685
+
686
+ /* No message avatar by default — the slot only shows once an AvatarProvider
687
+ (or a consumer) fills it. Role is carried by layout + colour instead. */
688
+ .aparte-avatar:empty {
689
+ display: none;
690
+ }
691
+
692
+ .aparte-avatar[data-role="user"] {
693
+ background: var(--aparte-avatar-bg-user, #6366f1);
694
+ color: var(--aparte-avatar-text-user, #ffffff);
695
+ background-image: var(--aparte-avatar-image-user, none);
696
+ background-size: var(--aparte-avatar-image-size, 90%);
697
+ background-repeat: no-repeat;
698
+ background-position: center;
699
+ }
700
+
701
+ .aparte-avatar[data-role="assistant"] {
702
+ background: var(--aparte-avatar-bg-assistant, #10b981);
703
+ color: var(--aparte-avatar-text-assistant, #ffffff);
704
+ background-image: var(--aparte-avatar-image-assistant, none);
705
+ background-size: var(--aparte-avatar-image-size, 90%);
706
+ background-repeat: no-repeat;
707
+ background-position: center;
708
+ }
709
+
710
+ .aparte-body {
711
+ flex: 1;
712
+ min-width: 0;
713
+ }
714
+
715
+ /* The message bubble — the surface fills ONLY the content (attachments +
716
+ segments + text). The sender name (above) and the action bar (below) are
717
+ siblings in .aparte-body, so they stay OUTSIDE the coloured surface. */
718
+ .aparte-message-content {
719
+ border-radius: var(--aparte-message-content-radius, 0);
720
+ max-width: 100%;
721
+ }
722
+
723
+ /* AI-chat convention (not messaging): the USER message is a subtle bubble that
724
+ hugs its text; the ASSISTANT message is plain full-width prose — no bubble,
725
+ like ChatGPT / Claude. */
726
+ .aparte-message[data-role="user"] .aparte-message-content {
727
+ width: fit-content;
728
+ padding: var(--aparte-message-content-padding, 0);
729
+ background: var(--aparte-message-content-bg-user, transparent);
730
+ color: var(--aparte-message-content-text-user, var(--aparte-text));
731
+ }
732
+
733
+ .aparte-message[data-role="assistant"] .aparte-message-content {
734
+ background: var(--aparte-message-content-bg-assistant, transparent);
735
+ color: var(--aparte-message-content-text-assistant, var(--aparte-text));
736
+ }
737
+
738
+ .aparte-header {
739
+ display: flex;
740
+ align-items: center;
741
+ gap: var(--aparte-space-4, 8px);
742
+ margin-bottom: var(--aparte-space-3, 6px);
743
+ }
744
+
745
+ .aparte-name {
746
+ font-size: var(--aparte-name-font-size, 14px);
747
+ font-weight: var(--aparte-font-weight-semibold, 600);
748
+ color: var(--aparte-name-color, #1f2937);
749
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
750
+ }
751
+
752
+ .aparte-timestamp {
753
+ font-size: var(--aparte-timestamp-font-size, 12px);
754
+ color: var(--aparte-timestamp-color, #9ca3af);
755
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
756
+ }
757
+
758
+ .aparte-content {
759
+ font-size: var(--aparte-content-font-size, 15px);
760
+ line-height: var(--aparte-content-line-height, 1.7);
761
+ color: var(--aparte-content-color, #374151);
762
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
763
+ white-space: normal;
764
+ word-wrap: break-word;
765
+ }
766
+
767
+ .aparte-segments {
768
+ font-size: var(--aparte-content-font-size, 15px);
769
+ line-height: var(--aparte-content-line-height, 1.7);
770
+ color: var(--aparte-content-color, #374151);
771
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
772
+ }
773
+
774
+ .aparte-segments:empty {
775
+ display: none;
776
+ }
777
+
778
+ /* =========================================
779
+ Attachments
780
+ ========================================= */
781
+
782
+ .aparte-attachments {
783
+ display: flex;
784
+ flex-wrap: wrap;
785
+ /* Bubble attachments only ever render for user messages — anchor right. */
786
+ justify-content: flex-end;
787
+ gap: var(--aparte-space-3, 6px);
788
+ margin-bottom: var(--aparte-space-4, 8px);
789
+ /* Really small tiles inside the conversation. */
790
+ --aparte-attachment-image-size: 40px;
791
+ /* Many attachments → cap the height and scroll instead of growing forever. */
792
+ max-height: var(--aparte-attachments-max-height, 140px);
793
+ overflow-y: auto;
794
+ }
795
+
796
+ /* The class `display: flex` above would otherwise override the `hidden`
797
+ attribute — restore it so an empty attachment strip takes no space. */
798
+ .aparte-attachments[hidden] {
799
+ display: none;
800
+ }
801
+
802
+ /* ── Attachment thumbnail tile ──────────────────────────────────────────────
803
+ Shared by the composer attachment preview and the sent-message attachment
804
+ strip. A square that shows the image itself (or a file glyph); the filename
805
+ and — in the composer — a remove button surface on hover. */
806
+ .aparte-thumb {
807
+ position: relative;
808
+ width: var(--aparte-attachment-image-size, 56px);
809
+ height: var(--aparte-attachment-image-size, 56px);
810
+ flex-shrink: 0;
811
+ border-radius: var(--aparte-thumb-radius, 8px);
812
+ overflow: hidden;
813
+ box-sizing: border-box;
814
+ background: var(--aparte-attachment-chip-bg, var(--aparte-surface-2));
815
+ border: var(--aparte-border-width, 1px) solid var(--aparte-attachment-chip-border, var(--aparte-border));
816
+ }
817
+
818
+ .aparte-thumb--image {
819
+ cursor: pointer;
820
+ }
821
+
822
+ .aparte-thumb__img {
823
+ width: 100%;
824
+ height: 100%;
825
+ object-fit: cover;
826
+ display: block;
827
+ }
828
+
829
+ .aparte-thumb--file {
830
+ display: flex;
831
+ align-items: center;
832
+ justify-content: center;
833
+ }
834
+
835
+ .aparte-thumb__ext {
836
+ font-size: var(--aparte-font-size-xs, 11px);
837
+ font-weight: var(--aparte-font-weight-bold, 700);
838
+ letter-spacing: 0.03em;
839
+ color: var(--aparte-text-muted, var(--aparte-text));
840
+ }
841
+
842
+ .aparte-thumb__name {
843
+ position: absolute;
844
+ left: 0;
845
+ right: 0;
846
+ bottom: 0;
847
+ padding: var(--aparte-thumb-name-padding, 14px 5px 4px);
848
+ font-size: var(--aparte-font-size-2xs, 10px);
849
+ line-height: var(--aparte-line-height-tight, 1.25);
850
+ color: var(--aparte-thumb-name-color, #fff);
851
+ background: var(--aparte-thumb-name-scrim, linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0)));
852
+ white-space: nowrap;
853
+ overflow: hidden;
854
+ text-overflow: ellipsis;
855
+ opacity: 0;
856
+ transition: opacity 0.15s ease;
857
+ pointer-events: none;
858
+ }
859
+ .aparte-thumb:hover .aparte-thumb__name {
860
+ opacity: 1;
861
+ }
862
+
863
+ .aparte-thumb__remove {
864
+ position: absolute;
865
+ top: var(--aparte-thumb-remove-inset, 3px);
866
+ right: var(--aparte-thumb-remove-inset, 3px);
867
+ width: var(--aparte-thumb-remove-size, 18px);
868
+ height: var(--aparte-thumb-remove-size, 18px);
869
+ display: flex;
870
+ align-items: center;
871
+ justify-content: center;
872
+ padding: 0;
873
+ border: none;
874
+ border-radius: 50%;
875
+ background: var(--aparte-thumb-remove-bg, rgba(0, 0, 0, 0.6));
876
+ color: var(--aparte-thumb-remove-color, #fff);
877
+ cursor: pointer;
878
+ opacity: 0;
879
+ transition: opacity 0.15s ease, background 0.15s ease;
880
+ }
881
+ .aparte-thumb:hover .aparte-thumb__remove {
882
+ opacity: 1;
883
+ }
884
+ .aparte-thumb__remove:hover {
885
+ background: var(--aparte-thumb-remove-bg-hover, rgba(0, 0, 0, 0.85));
886
+ }
887
+
888
+ /* =========================================
889
+ Action Bar
890
+ ========================================= */
891
+
892
+ .aparte-footer {
893
+ display: flex;
894
+ align-items: center;
895
+ gap: var(--aparte-action-bar-gap, 4px);
896
+ margin-top: var(--aparte-space-3, 6px);
897
+ min-height: var(--aparte-action-bar-btn-size, 28px);
898
+ }
899
+
900
+ .aparte-action-bar {
901
+ display: flex;
902
+ align-items: center;
903
+ gap: var(--aparte-action-bar-gap, 4px);
904
+ opacity: 0;
905
+ transition: opacity 0.15s ease;
906
+ height: var(--aparte-action-bar-btn-size, 28px);
907
+ }
908
+
909
+ .aparte-message:hover .aparte-action-bar,
910
+ .aparte-message:focus-within .aparte-action-bar {
911
+ opacity: 1;
912
+ }
913
+
914
+ /* Last assistant message: action bar always visible — but NOT while streaming */
915
+ aparte-chat-bubble:last-of-type .aparte-message[data-role="assistant"]:not(.aparte-message-streaming) .aparte-action-bar {
916
+ opacity: 1;
917
+ }
918
+
919
+ /* Hide footer (action bar + branch picker) entirely while streaming */
920
+ .aparte-message-streaming .aparte-footer {
921
+ display: none;
922
+ }
923
+
924
+ .aparte-action-btn {
925
+ display: inline-flex;
926
+ align-items: center;
927
+ justify-content: center;
928
+ width: var(--aparte-action-bar-btn-size, 28px);
929
+ height: var(--aparte-action-bar-btn-size, 28px);
930
+ border: none;
931
+ border-radius: var(--aparte-radius-sm, 4px);
932
+ background: transparent;
933
+ color: var(--aparte-action-bar-btn-color, var(--aparte-text-muted));
934
+ cursor: pointer;
935
+ padding: 0;
936
+ transition: background 0.15s ease, color 0.15s ease;
937
+ flex-shrink: 0;
938
+ }
939
+
940
+ .aparte-action-btn:hover {
941
+ background: var(--aparte-action-bar-btn-hover-bg, var(--aparte-surface-2));
942
+ color: var(--aparte-action-bar-btn-hover-color, var(--aparte-text));
943
+ }
944
+
945
+ .aparte-action-btn[data-copied] {
946
+ color: var(--aparte-success, #10b981);
947
+ }
948
+
949
+ .aparte-action-btn[data-submitted] {
950
+ color: var(--aparte-primary, #6366f1);
951
+ }
952
+
953
+ /* =========================================
954
+ Branch Picker
955
+ ========================================= */
956
+
957
+ .aparte-branch-picker {
958
+ display: flex;
959
+ align-items: center;
960
+ gap: var(--aparte-branch-picker-gap, 4px);
961
+ }
962
+
963
+ /* [hidden] is overridden by display:flex — force it back */
964
+ .aparte-branch-picker[hidden] {
965
+ display: none;
966
+ }
967
+
968
+ .aparte-branch-prev,
969
+ .aparte-branch-next {
970
+ display: inline-flex;
971
+ align-items: center;
972
+ justify-content: center;
973
+ width: var(--aparte-branch-picker-btn-size, 20px);
974
+ height: var(--aparte-branch-picker-btn-size, 20px);
975
+ border: none;
976
+ border-radius: var(--aparte-radius-sm, 4px);
977
+ background: transparent;
978
+ color: var(--aparte-branch-picker-btn-color, var(--aparte-text-muted));
979
+ cursor: pointer;
980
+ padding: 0;
981
+ font-size: var(--aparte-branch-picker-btn-icon-size, 16px);
982
+ line-height: var(--aparte-line-height-none, 1);
983
+ transition: color 0.15s ease, background 0.15s ease;
984
+ }
985
+
986
+ .aparte-branch-prev:hover:not(:disabled),
987
+ .aparte-branch-next:hover:not(:disabled) {
988
+ color: var(--aparte-branch-picker-btn-hover-color, var(--aparte-text));
989
+ background: var(--aparte-action-bar-btn-hover-bg, var(--aparte-surface-2));
990
+ }
991
+
992
+ .aparte-branch-prev:disabled,
993
+ .aparte-branch-next:disabled {
994
+ opacity: 0.3;
995
+ cursor: default;
996
+ }
997
+
998
+ .aparte-branch-label {
999
+ font-size: var(--aparte-branch-picker-label-size, 12px);
1000
+ color: var(--aparte-branch-picker-label-color, var(--aparte-text-muted));
1001
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
1002
+ min-width: var(--aparte-branch-picker-label-min-width, 32px);
1003
+ text-align: center;
1004
+ }
1005
+
1006
+ /* =========================================
1007
+ Edit mode
1008
+ ========================================= */
1009
+
1010
+ /* Inline editor: the composer's contenteditable primitive, given the composer
1011
+ shell's box (bordered surface) so it reads as the same field. The inner
1012
+ .aparte-ci-editor inherits its padding/font/caret from the shared composer rule. */
1013
+ .aparte-message[data-editing] aparte-composer-input {
1014
+ display: block;
1015
+ margin: var(--aparte-space-2, 4px) 0;
1016
+ background: var(--aparte-input-bg, var(--aparte-surface-1));
1017
+ border: var(--aparte-border-width, 1px) solid var(--aparte-input-border, var(--aparte-border));
1018
+ border-radius: var(--aparte-radius-input, 12px);
1019
+ box-sizing: border-box;
1020
+ transition: border-color 0.2s;
1021
+ }
1022
+ .aparte-message[data-editing] aparte-composer-input:focus-within {
1023
+ border-color: var(--aparte-input-focus-border, var(--aparte-primary));
1024
+ }
1025
+
1026
+ /* Edit mode keeps the action bar (now ✓ / ✗) visible even when the editor
1027
+ is not focused — otherwise the user can't reach the save button. */
1028
+ .aparte-message[data-editing] .aparte-action-bar {
1029
+ opacity: 1;
1030
+ }
1031
+
1032
+ .aparte-action-btn.aparte-action-edit-save {
1033
+ color: var(--aparte-success, #16a34a);
1034
+ }
1035
+ .aparte-action-btn.aparte-action-edit-save:hover {
1036
+ background: var(--aparte-success-surface, rgba(34, 197, 94, 0.14));
1037
+ color: var(--aparte-success, #16a34a);
1038
+ }
1039
+ .aparte-action-btn.aparte-action-edit-cancel {
1040
+ color: var(--aparte-error, #dc2626);
1041
+ }
1042
+ .aparte-action-btn.aparte-action-edit-cancel:hover {
1043
+ background: var(--aparte-error-surface, rgba(239, 68, 68, 0.14));
1044
+ color: var(--aparte-error, #dc2626);
1045
+ }
1046
+
1047
+
1048
+ /* =========================================
1049
+ Legacy composer input utility classes
1050
+ ========================================= */
1051
+
1052
+ .aparte-input-wrapper {
1053
+ display: flex;
1054
+ align-items: flex-end;
1055
+ gap: var(--aparte-input-gap, 8px);
1056
+ padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
1057
+ background: transparent;
1058
+ }
1059
+
1060
+ /* Action slots */
1061
+ .aparte-actions-left,
1062
+ .aparte-actions-right {
1063
+ display: flex;
1064
+ align-items: center;
1065
+ gap: var(--aparte-space-2, 4px);
1066
+ flex-shrink: 0;
1067
+ }
1068
+
1069
+ .aparte-input-container {
1070
+ flex: 1;
1071
+ min-height: var(--aparte-input-container-min-height, 44px);
1072
+ /* Matches _minHeight default */
1073
+ display: flex;
1074
+ flex-direction: column;
1075
+ /* justify-content: center; Removed to fix cursor pos */
1076
+
1077
+ }
1078
+
1079
+ .aparte-editor {
1080
+ width: 100%;
1081
+ max-height: var(--aparte-input-editor-max-height, 200px);
1082
+ /* Matches _maxHeight default */
1083
+ overflow-y: auto;
1084
+ outline: none;
1085
+ border: none;
1086
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
1087
+ font-size: var(--aparte-input-editor-font-size, 15px);
1088
+ line-height: var(--aparte-line-height-normal, 1.5);
1089
+ color: var(--aparte-input-text, #374151);
1090
+ background: transparent;
1091
+ padding: var(--aparte-space-5, 10px) 0;
1092
+ resize: none;
1093
+ text-align: left;
1094
+ }
1095
+
1096
+ /* Placeholder */
1097
+ .aparte-editor:empty:before {
1098
+ content: attr(data-placeholder);
1099
+ color: var(--aparte-input-placeholder, #9ca3af);
1100
+ pointer-events: none;
1101
+ display: block;
1102
+ /* Ensures proper rendering */
1103
+ }
1104
+
1105
+ /* Disabled state */
1106
+ .aparte-editor[contenteditable="false"] {
1107
+ opacity: 0.6;
1108
+ cursor: not-allowed;
1109
+ }
1110
+
1111
+ .aparte-send-button {
1112
+ flex-shrink: 0;
1113
+ width: var(--aparte-send-btn-size, 36px);
1114
+ height: var(--aparte-send-btn-size, 36px);
1115
+ border-radius: var(--aparte-radius-send-btn);
1116
+ border: none;
1117
+ background: var(--aparte-primary, #6366f1);
1118
+ color: var(--aparte-on-primary, white);
1119
+ display: flex;
1120
+ align-items: center;
1121
+ justify-content: center;
1122
+ cursor: pointer;
1123
+ transition: background 0.2s, opacity 0.2s;
1124
+ padding: 0;
1125
+ }
1126
+
1127
+ .aparte-send-button:hover:not(:disabled) {
1128
+ background: var(--aparte-primary-hover, #4f46e5);
1129
+ }
1130
+
1131
+ .aparte-send-button:disabled {
1132
+ opacity: 0.45;
1133
+ cursor: not-allowed;
1134
+ background: var(--aparte-send-disabled-bg, var(--aparte-primary));
1135
+ }
1136
+
1137
+ /* Action Buttons in Input */
1138
+ .aparte-action-button {
1139
+ background: transparent;
1140
+ border: none;
1141
+ color: var(--aparte-neutral, #6b7280);
1142
+ padding: var(--aparte-space-4, 8px);
1143
+ border-radius: var(--aparte-radius-action-btn);
1144
+ cursor: pointer;
1145
+ display: flex;
1146
+ align-items: center;
1147
+ justify-content: center;
1148
+ transition: color 0.15s, background-color 0.15s;
1149
+ width: var(--aparte-input-action-btn-size, 36px);
1150
+ height: var(--aparte-input-action-btn-size, 36px);
1151
+ }
1152
+
1153
+ .aparte-action-button:hover {
1154
+ color: var(--aparte-primary, #6366f1);
1155
+ /* Derive the hover tint from --aparte-primary instead of hardcoding indigo, so a
1156
+ custom theme's action buttons tint with its own accent. */
1157
+ background-color: color-mix(in srgb, var(--aparte-primary, #6366f1) 10%, transparent);
1158
+ }
1159
+
1160
+ .aparte-action-button svg {
1161
+ width: var(--aparte-input-action-btn-icon-size, 20px);
1162
+ height: var(--aparte-input-action-btn-icon-size, 20px);
1163
+ }
1164
+
1165
+ /* =========================================
1166
+ Input Upper (for file chips, previews, etc.)
1167
+ ========================================= */
1168
+
1169
+ .aparte-input-upper {
1170
+ display: none; /* shown by JS when chips are added */
1171
+ flex-wrap: wrap;
1172
+ align-items: center;
1173
+ gap: var(--aparte-space-4, 8px);
1174
+ padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
1175
+ border-bottom: var(--aparte-border-width, 1px) solid var(--aparte-border, #e5e7eb);
1176
+ font-size: var(--aparte-font-size-md, 13px);
1177
+ }
1178
+
1179
+ .aparte-input-upper.has-content {
1180
+ display: flex;
1181
+ }
1182
+
1183
+ /* =========================================
1184
+ Input Footer (for Model Selector, etc.)
1185
+ ========================================= */
1186
+
1187
+ .aparte-input-footer {
1188
+ display: flex;
1189
+ align-items: center;
1190
+ gap: var(--aparte-space-6, 12px);
1191
+ padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
1192
+ border-top: var(--aparte-border-width, 1px) solid var(--aparte-border, #e5e7eb);
1193
+ font-size: var(--aparte-font-size-md, 13px);
1194
+ }
1195
+
1196
+ .aparte-input-footer:empty {
1197
+ display: none;
1198
+ }
1199
+
1200
+ /* Slot areas */
1201
+ .aparte-input-footer slot[name="footer-left"] {
1202
+ display: contents;
1203
+ }
1204
+
1205
+ .aparte-input-footer slot[name="footer-center"] {
1206
+ flex: 1;
1207
+ display: contents;
1208
+ }
1209
+
1210
+ .aparte-input-footer slot[name="footer-right"] {
1211
+ display: contents;
1212
+ margin-inline-start: auto;
1213
+ }
1214
+
1215
+ /* Model Selector Component */
1216
+ aparte-model-selector {
1217
+ display: flex;
1218
+ align-items: center;
1219
+ gap: var(--aparte-space-4, 8px);
1220
+ }
1221
+ /* Respect native hidden attribute — overrides the display:flex above */
1222
+ aparte-model-selector[hidden] { display: none !important; }
1223
+
1224
+ .aparte-model-selector {
1225
+ display: flex;
1226
+ align-items: center;
1227
+ gap: var(--aparte-space-4, 8px);
1228
+ }
1229
+
1230
+ .aparte-provider-select,
1231
+ .aparte-model-select {
1232
+ appearance: none;
1233
+ background: var(--aparte-surface-1, #ffffff);
1234
+ border: var(--aparte-border-width, 1px) solid var(--aparte-border, #e5e7eb);
1235
+ border-radius: var(--aparte-radius-select);
1236
+ padding: var(--aparte-space-3, 6px) 28px var(--aparte-space-3, 6px) var(--aparte-space-5, 10px);
1237
+ font-size: var(--aparte-font-size-md, 13px);
1238
+ font-family: var(--aparte-font-family, system-ui);
1239
+ color: var(--aparte-text, #1f2937);
1240
+ cursor: pointer;
1241
+ min-width: var(--aparte-select-min-width, 120px);
1242
+ /* Chevron: baked into a data-URI SVG (CSS vars can't interpolate into url()).
1243
+ The stroke colour is theme-matched via the [data-aparte-theme] override
1244
+ below rather than a single var — the one spot that needs two variants. */
1245
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
1246
+ background-repeat: no-repeat;
1247
+ background-position: right var(--aparte-space-4, 8px) center;
1248
+ transition: border-color 0.15s, box-shadow 0.15s;
1249
+ }
1250
+
1251
+ .aparte-provider-select:hover,
1252
+ .aparte-model-select:hover {
1253
+ border-color: var(--aparte-primary, #6366f1);
1254
+ }
1255
+
1256
+ .aparte-provider-select:focus,
1257
+ .aparte-model-select:focus {
1258
+ outline: none;
1259
+ border-color: var(--aparte-primary, #6366f1);
1260
+ box-shadow: var(--aparte-focus-ring, 0 0 0 3px rgba(99, 102, 241, 0.15));
1261
+ }
1262
+
1263
+ /* Dark mode overrides for selects */
1264
+ [data-aparte-theme="dark"] .aparte-provider-select,
1265
+ [data-aparte-theme="dark"] .aparte-model-select {
1266
+ background-color: var(--aparte-surface-1, #1e293b);
1267
+ border-color: var(--aparte-border, #334155);
1268
+ color: var(--aparte-text, #f1f5f9);
1269
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
1270
+ }
1271
+
1272
+
1273
+ /* =========================================
1274
+ aparte-chat-status
1275
+ ========================================= */
1276
+
1277
+ aparte-chat-status {
1278
+ display: block;
1279
+ width: 100%;
1280
+ contain: layout style paint;
1281
+ }
1282
+
1283
+ /* No layout footprint until the typing indicator is actually shown — otherwise the
1284
+ always-present element reserves height (visible via flex-shrink:0 in the
1285
+ framework-managed viewport) even on the empty/welcome screen. */
1286
+ aparte-chat-status:not([visible]) {
1287
+ display: none;
1288
+ }
1289
+
1290
+ .aparte-status-container {
1291
+ display: flex;
1292
+ align-items: center;
1293
+ opacity: 0;
1294
+ transform: translateY(8px);
1295
+ transition: opacity 0.2s ease, transform 0.2s ease;
1296
+ pointer-events: none;
1297
+ }
1298
+
1299
+ .aparte-status-content {
1300
+ display: flex;
1301
+ align-items: center;
1302
+ gap: var(--aparte-space-4, 8px);
1303
+ }
1304
+
1305
+ .aparte-status-container[data-visible="true"] {
1306
+ opacity: 1;
1307
+ transform: translateY(0);
1308
+ pointer-events: auto;
1309
+ }
1310
+
1311
+ .aparte-status-text {
1312
+ font-size: var(--aparte-status-font-size, 13px);
1313
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
1314
+ color: var(--aparte-status-color, #6b7280);
1315
+ font-style: italic;
1316
+ }
1317
+
1318
+ .aparte-dots {
1319
+ display: flex;
1320
+ align-items: center;
1321
+ }
1322
+
1323
+ .aparte-dot {
1324
+ width: var(--aparte-status-dot-size, 6px);
1325
+ height: var(--aparte-status-dot-size, 6px);
1326
+ background: var(--aparte-status-color, #6b7280);
1327
+ border-radius: 50%;
1328
+ animation: aparte-pulse 1.2s infinite ease-in-out;
1329
+ }
1330
+
1331
+ /* Respect prefers-reduced-motion. Scoped to Aparte's own custom elements (never
1332
+ the consumer's page) — this covers every animated element the library renders
1333
+ inside them: typing dots, spinners, pipeline/artifact pulses, segment
1334
+ animations. Activity stays conveyed via aria-live / aria-busy. */
1335
+ @media (prefers-reduced-motion: reduce) {
1336
+ aparte-chat-viewport *,
1337
+ aparte-chat-bubble *,
1338
+ aparte-composer *,
1339
+ aparte-chat-status *,
1340
+ aparte-conversation-list *,
1341
+ aparte-select *,
1342
+ aparte-progress-spinner * {
1343
+ animation-duration: 0.01ms !important;
1344
+ animation-iteration-count: 1 !important;
1345
+ transition-duration: 0.01ms !important;
1346
+ }
1347
+ }
1348
+
1349
+ @keyframes aparte-pulse {
1350
+ 0%, 100% {
1351
+ opacity: 0.3;
1352
+ transform: scale(0.75);
1353
+ }
1354
+ 50% {
1355
+ opacity: 1;
1356
+ transform: scale(1);
1357
+ }
1358
+ }
1359
+
1360
+ /* =========================================
1361
+ Segment Renderers (Global Defaults)
1362
+ ========================================= */
1363
+
1364
+ /* Text */
1365
+ .segment-text {
1366
+ word-wrap: break-word;
1367
+ }
1368
+
1369
+ /* =========================================
1370
+ Prose — reset-agnostic typography
1371
+ Compact defaults for chat context.
1372
+ Customizable via --aparte-prose-* variables.
1373
+ ========================================= */
1374
+
1375
+ .aparte-content,
1376
+ .segment-content {
1377
+ white-space: normal;
1378
+ }
1379
+
1380
+ .aparte-content p,
1381
+ .segment-content p {
1382
+ margin: var(--aparte-prose-p-margin, 0.4em 0);
1383
+ }
1384
+
1385
+ .aparte-content p:first-child,
1386
+ .segment-content p:first-child {
1387
+ margin-top: 0;
1388
+ }
1389
+
1390
+ .aparte-content p:last-child,
1391
+ .segment-content p:last-child {
1392
+ margin-bottom: 0;
1393
+ }
1394
+
1395
+ .aparte-content h1, .segment-content h1 { font-size: var(--aparte-prose-h1-size, 1.4em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.6em 0 0.2em; }
1396
+ .aparte-content h2, .segment-content h2 { font-size: var(--aparte-prose-h2-size, 1.25em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.5em 0 0.2em; }
1397
+ .aparte-content h3, .segment-content h3 { font-size: var(--aparte-prose-h3-size, 1.1em); font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.4em 0 0.15em; }
1398
+ .aparte-content h4, .segment-content h4,
1399
+ .aparte-content h5, .segment-content h5,
1400
+ .aparte-content h6, .segment-content h6 { font-size: 1em; font-weight: var(--aparte-font-weight-semibold, 600); margin: 0.3em 0 0.1em; }
1401
+
1402
+ .aparte-content ul,
1403
+ .segment-content ul {
1404
+ list-style: disc;
1405
+ padding-left: var(--aparte-prose-list-indent, 1.5em);
1406
+ margin: var(--aparte-prose-list-margin, 0.4em 0);
1407
+ }
1408
+
1409
+ .aparte-content ol,
1410
+ .segment-content ol {
1411
+ list-style: decimal;
1412
+ padding-left: var(--aparte-prose-list-indent, 1.5em);
1413
+ margin: var(--aparte-prose-list-margin, 0.4em 0);
1414
+ }
1415
+
1416
+ .aparte-content li,
1417
+ .segment-content li {
1418
+ margin: var(--aparte-prose-li-margin, 0.15em 0);
1419
+ }
1420
+
1421
+ .aparte-content blockquote,
1422
+ .segment-content blockquote {
1423
+ border-left: var(--aparte-prose-blockquote-border-width, 3px) solid var(--aparte-prose-blockquote-border, var(--aparte-border, #e5e7eb));
1424
+ padding-left: 1em;
1425
+ margin: 0.4em 0;
1426
+ color: var(--aparte-text-muted, #6b7280);
1427
+ font-style: italic;
1428
+ }
1429
+
1430
+ .aparte-content strong,
1431
+ .segment-content strong {
1432
+ font-weight: var(--aparte-font-weight-semibold, 600);
1433
+ }
1434
+
1435
+ .aparte-content em,
1436
+ .segment-content em {
1437
+ font-style: italic;
1438
+ }
1439
+
1440
+ .aparte-content code,
1441
+ .segment-content code {
1442
+ font-family: var(--aparte-code-font-family, ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace);
1443
+ font-size: var(--aparte-code-inline-font-size, 0.875em);
1444
+ background: var(--aparte-prose-code-bg, var(--aparte-code-bg, #f3f4f6));
1445
+ padding: 0.1em 0.3em;
1446
+ border-radius: var(--aparte-radius-xs);
1447
+ }
1448
+
1449
+ .aparte-content a,
1450
+ .segment-content a {
1451
+ color: var(--aparte-primary, #6366f1);
1452
+ text-decoration: underline;
1453
+ }
1454
+
1455
+ .aparte-content hr,
1456
+ .segment-content hr {
1457
+ border: none;
1458
+ border-top: var(--aparte-border-width, 1px) solid var(--aparte-border, #e5e7eb);
1459
+ margin: 0.75em 0;
1460
+ }
1461
+
1462
+ /* Thinking */
1463
+ .segment-thinking {
1464
+ /* Understated disclosure: a muted left rail, not a filled card. */
1465
+ border-left: var(--aparte-thinking-rail-width, 2px) solid var(--aparte-thinking-border, #e5e7eb);
1466
+ margin: var(--aparte-space-5, 10px) 0;
1467
+ background: transparent;
1468
+ }
1469
+
1470
+ .thinking-header {
1471
+ display: flex;
1472
+ align-items: center;
1473
+ gap: var(--aparte-space-3, 6px);
1474
+ padding: var(--aparte-space-1, 2px) 0 var(--aparte-space-1, 2px) var(--aparte-space-5, 10px);
1475
+ cursor: pointer;
1476
+ user-select: none;
1477
+ font-size: var(--aparte-font-size-sm, 12px);
1478
+ color: var(--aparte-thinking-text, #6b7280);
1479
+ list-style: none;
1480
+ }
1481
+
1482
+ .thinking-header::-webkit-details-marker {
1483
+ display: none;
1484
+ }
1485
+
1486
+ .thinking-toggle {
1487
+ margin-inline-start: auto;
1488
+ /* Logical property */
1489
+ transition: transform 0.2s;
1490
+ opacity: 0.55;
1491
+ }
1492
+
1493
+ details[open] .thinking-toggle {
1494
+ transform: rotate(180deg);
1495
+ }
1496
+
1497
+ .thinking-toggle::after {
1498
+ content: '▼';
1499
+ font-size: var(--aparte-thinking-toggle-size, 9px);
1500
+ }
1501
+
1502
+ .thinking-content {
1503
+ padding: var(--aparte-space-2, 4px) 0 var(--aparte-space-1, 2px) var(--aparte-space-5, 10px);
1504
+ font-size: var(--aparte-font-size-md, 13px);
1505
+ line-height: var(--aparte-thinking-content-line-height, 1.55);
1506
+ white-space: pre-wrap;
1507
+ color: var(--aparte-thinking-content, #4b5563);
1508
+ background: transparent;
1509
+ max-height: var(--aparte-thinking-content-max-height, 300px);
1510
+ overflow-y: auto;
1511
+ }
1512
+
1513
+ /* Code */
1514
+ .segment-code {
1515
+ margin: var(--aparte-space-6, 12px) 0;
1516
+ border-radius: var(--aparte-radius-code);
1517
+ overflow: hidden;
1518
+ border: var(--aparte-border-width, 1px) solid var(--aparte-code-border, #e5e7eb);
1519
+ }
1520
+
1521
+ .code-header {
1522
+ display: flex;
1523
+ align-items: center;
1524
+ padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
1525
+ background: var(--aparte-code-header-bg, #f3f4f6);
1526
+ border-bottom: var(--aparte-border-width, 1px) solid var(--aparte-code-border, #e5e7eb);
1527
+ font-size: var(--aparte-font-size-sm, 12px);
1528
+ color: var(--aparte-code-header-text, #4b5563);
1529
+ }
1530
+
1531
+ .code-filename {
1532
+ font-weight: var(--aparte-font-weight-semibold, 600);
1533
+ margin-inline-end: auto;
1534
+ /* Logical property: pushes language + copy to the right when filename is present */
1535
+ }
1536
+
1537
+ .code-header-filler {
1538
+ flex: 1;
1539
+ /* No-filename placeholder — keeps language + copy right-aligned */
1540
+ }
1541
+
1542
+ .code-language {
1543
+ margin-inline-end: var(--aparte-space-4, 8px);
1544
+ /* Logical property */
1545
+ text-transform: uppercase;
1546
+ opacity: 0.7;
1547
+ }
1548
+
1549
+ .code-copy {
1550
+ background: transparent;
1551
+ border: none;
1552
+ cursor: pointer;
1553
+ padding: var(--aparte-space-2, 4px);
1554
+ border-radius: var(--aparte-radius-sm);
1555
+ color: inherit;
1556
+ display: flex;
1557
+ align-items: center;
1558
+ justify-content: center;
1559
+ }
1560
+
1561
+ .code-copy:hover {
1562
+ background: var(--aparte-code-copy-hover-bg, rgba(0, 0, 0, 0.05));
1563
+ }
1564
+
1565
+ .code-copy svg {
1566
+ width: var(--aparte-code-copy-icon-size, 14px);
1567
+ height: var(--aparte-code-copy-icon-size, 14px);
1568
+ }
1569
+
1570
+ .code-content-wrapper pre {
1571
+ margin: 0;
1572
+ padding: var(--aparte-space-6, 12px);
1573
+ /* Wrap long lines instead of a horizontal scrollbar. `pre-wrap` honours
1574
+ newlines + indentation but soft-wraps overflowing lines; `overflow-wrap:
1575
+ anywhere` breaks unbreakable tokens (long URLs, hashes). Applies to both
1576
+ the plain <pre><code> initial render and the shiki <pre class="shiki">
1577
+ replacement, since both sit inside .code-content-wrapper. */
1578
+ white-space: pre-wrap;
1579
+ overflow-wrap: anywhere;
1580
+ overflow-x: hidden;
1581
+ background: var(--aparte-code-bg, #ffffff);
1582
+ }
1583
+
1584
+ /* Terminal */
1585
+ .segment-terminal {
1586
+ margin: var(--aparte-space-6, 12px) 0;
1587
+ font-family: var(--aparte-code-font-family, 'Fira Code', 'Monaco', 'Consolas', monospace);
1588
+ font-size: var(--aparte-terminal-font-size, 13px);
1589
+ }
1590
+
1591
+ .terminal-command-block {
1592
+ display: flex;
1593
+ align-items: center;
1594
+ gap: var(--aparte-space-5, 10px);
1595
+ padding: var(--aparte-space-5, 10px) var(--aparte-space-7, 14px);
1596
+ background: var(--aparte-terminal-bg, #1e293b);
1597
+ border-radius: var(--aparte-radius-terminal);
1598
+ color: var(--aparte-terminal-text, #e2e8f0);
1599
+ }
1600
+
1601
+ .terminal-icon {
1602
+ flex-shrink: 0;
1603
+ color: var(--aparte-terminal-icon, #64748b);
1604
+ display: flex;
1605
+ align-items: center;
1606
+ }
1607
+
1608
+ .terminal-command {
1609
+ flex: 1;
1610
+ word-break: break-all;
1611
+ background: transparent;
1612
+ color: inherit;
1613
+ font-family: inherit;
1614
+ }
1615
+
1616
+ .terminal-actions {
1617
+ display: flex;
1618
+ align-items: center;
1619
+ gap: var(--aparte-space-3, 6px);
1620
+ flex-shrink: 0;
1621
+ }
1622
+
1623
+ .terminal-run-btn {
1624
+ padding: var(--aparte-space-2, 4px) var(--aparte-space-6, 12px);
1625
+ background: var(--aparte-primary, #6366f1);
1626
+ color: var(--aparte-on-primary, white);
1627
+ border: none;
1628
+ border-radius: var(--aparte-radius-sm);
1629
+ font-size: var(--aparte-font-size-sm, 12px);
1630
+ font-weight: var(--aparte-font-weight-medium, 500);
1631
+ cursor: pointer;
1632
+ transition: background 0.15s;
1633
+ }
1634
+
1635
+ .terminal-run-btn:hover {
1636
+ background: var(--aparte-primary-hover, #4f46e5);
1637
+ }
1638
+
1639
+ .terminal-copy-btn {
1640
+ width: var(--aparte-terminal-copy-btn-size, 26px);
1641
+ height: var(--aparte-terminal-copy-btn-size, 26px);
1642
+ padding: 0;
1643
+ background: transparent;
1644
+ border: none;
1645
+ color: var(--aparte-terminal-icon, #64748b);
1646
+ cursor: pointer;
1647
+ border-radius: var(--aparte-radius-sm);
1648
+ display: flex;
1649
+ align-items: center;
1650
+ justify-content: center;
1651
+ }
1652
+
1653
+ .terminal-copy-btn:hover {
1654
+ background: var(--aparte-terminal-hover, #334155);
1655
+ color: var(--aparte-terminal-text, #e2e8f0);
1656
+ }
1657
+
1658
+ .terminal-running {
1659
+ display: flex;
1660
+ align-items: center;
1661
+ gap: var(--aparte-space-3, 6px);
1662
+ color: var(--aparte-terminal-running, #22c55e);
1663
+ font-size: var(--aparte-font-size-sm, 12px);
1664
+ }
1665
+
1666
+ .terminal-running .spinner {
1667
+ width: var(--aparte-space-6, 12px);
1668
+ height: var(--aparte-space-6, 12px);
1669
+ border: 2px solid currentColor;
1670
+ border-top-color: transparent;
1671
+ border-radius: 50%;
1672
+ animation: aparte-spin 0.8s linear infinite;
1673
+ }
1674
+
1675
+ @keyframes aparte-spin {
1676
+ to {
1677
+ transform: rotate(360deg);
1678
+ }
1679
+ }
1680
+
1681
+ .terminal-output {
1682
+ margin-top: var(--aparte-space-4, 8px);
1683
+ padding: var(--aparte-space-5, 10px) var(--aparte-space-7, 14px);
1684
+ background: var(--aparte-terminal-output-bg, #0f172a);
1685
+ border-radius: var(--aparte-radius-md);
1686
+ color: var(--aparte-terminal-output-text, #94a3b8);
1687
+ white-space: pre-wrap;
1688
+ line-height: var(--aparte-line-height-normal, 1.5);
1689
+ max-height: var(--aparte-terminal-output-max-height, 200px);
1690
+ overflow-y: auto;
1691
+ }
1692
+
1693
+ .terminal-error {
1694
+ margin-top: var(--aparte-space-3, 6px);
1695
+ padding: var(--aparte-space-3, 6px) var(--aparte-space-5, 10px);
1696
+ background: var(--aparte-error-bg, #fef2f2);
1697
+ border-radius: var(--aparte-radius-sm);
1698
+ color: var(--aparte-error-text, #dc2626);
1699
+ font-size: var(--aparte-font-size-sm, 12px);
1700
+ }
1701
+
1702
+ /* Errored bubble state — the whole message carries `data-error` while it holds
1703
+ an error segment (set by the bubble in _reflectError). Kept subtle by default
1704
+ since the error segment itself is already styled; this is a theming hook —
1705
+ restyle `.aparte-message[data-error]` (or its parts) freely. */
1706
+ .aparte-message[data-error] .aparte-avatar {
1707
+ box-shadow: 0 0 0 2px var(--aparte-error-solid, #dc2626);
1708
+ }
1709
+
1710
+ /* Error */
1711
+ .segment-error {
1712
+ display: flex;
1713
+ gap: var(--aparte-space-6, 12px);
1714
+ padding: var(--aparte-space-6, 12px) var(--aparte-space-8, 16px);
1715
+ background: var(--aparte-error-bg, #fef2f2);
1716
+ border: var(--aparte-border-width, 1px) solid var(--aparte-error-border, #fecaca);
1717
+ border-left: var(--aparte-error-rail-width, 4px) solid var(--aparte-error-solid, #dc2626);
1718
+ border-radius: var(--aparte-radius-error);
1719
+ margin: var(--aparte-space-6, 12px) 0;
1720
+ color: var(--aparte-error-text, #991b1b);
1721
+ font-family: var(--aparte-font-family, system-ui, sans-serif);
1722
+ }
1723
+
1724
+ .error-icon-wrapper {
1725
+ flex-shrink: 0;
1726
+ color: var(--aparte-error-solid, #dc2626);
1727
+ display: flex;
1728
+ align-items: flex-start;
1729
+ padding-top: var(--aparte-space-1, 2px);
1730
+ }
1731
+
1732
+ .error-icon-wrapper svg {
1733
+ width: var(--aparte-error-icon-size, 20px);
1734
+ height: var(--aparte-error-icon-size, 20px);
1735
+ }
1736
+
1737
+ .error-content {
1738
+ flex: 1;
1739
+ min-width: 0;
1740
+ }
1741
+
1742
+ .error-title {
1743
+ font-weight: var(--aparte-font-weight-semibold, 600);
1744
+ font-size: var(--aparte-font-size-lg, 14px);
1745
+ margin-bottom: var(--aparte-space-2, 4px);
1746
+ color: var(--aparte-error-title, #7f1d1d);
1747
+ }
1748
+
1749
+ .error-message {
1750
+ font-size: var(--aparte-font-size-lg, 14px);
1751
+ line-height: var(--aparte-line-height-normal, 1.5);
1752
+ }
1753
+
1754
+ .error-details {
1755
+ margin-top: var(--aparte-space-4, 8px);
1756
+ padding-top: var(--aparte-space-4, 8px);
1757
+ border-top: var(--aparte-border-width, 1px) solid var(--aparte-error-border, #fecaca);
1758
+ font-family: var(--aparte-code-font-family, monospace);
1759
+ font-size: var(--aparte-font-size-sm, 12px);
1760
+ opacity: 0.9;
1761
+ white-space: pre-wrap;
1762
+ }
1763
+
1764
+ /* Progress */
1765
+ .segment-progress {
1766
+ margin: var(--aparte-space-6, 12px) 0;
1767
+ }
1768
+
1769
+ .progress-header {
1770
+ display: flex;
1771
+ justify-content: space-between;
1772
+ align-items: center;
1773
+ margin-bottom: var(--aparte-space-2, 4px);
1774
+ color: var(--aparte-progress-label, #374151);
1775
+ font-size: var(--aparte-font-size-lg, 14px);
1776
+ }
1777
+
1778
+ .progress-bar {
1779
+ height: var(--aparte-space-2, 4px);
1780
+ background: var(--aparte-progress-bar-bg, #e5e7eb);
1781
+ border-radius: var(--aparte-radius-xs);
1782
+ margin-top: var(--aparte-space-4, 8px);
1783
+ overflow: hidden;
1784
+ }
1785
+
1786
+ .progress-fill {
1787
+ height: 100%;
1788
+ background: var(--aparte-progress-bar-fill, #6366f1);
1789
+ border-radius: var(--aparte-radius-xs);
1790
+ transition: width 0.3s ease;
1791
+ }
1792
+
1793
+ /* File Tree */
1794
+ .segment-file-tree {
1795
+ background: var(--aparte-file-tree-bg, #f8fafc);
1796
+ border: var(--aparte-border-width, 1px) solid var(--aparte-file-tree-border, #e2e8f0);
1797
+ border-radius: var(--aparte-radius-file-tree);
1798
+ margin: var(--aparte-space-6, 12px) 0;
1799
+ overflow: hidden;
1800
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
1801
+ font-size: var(--aparte-font-size-md, 13px);
1802
+ }
1803
+
1804
+ .file-tree-title {
1805
+ padding: var(--aparte-space-5, 10px) var(--aparte-space-7, 14px);
1806
+ background: var(--aparte-file-tree-header-bg, #f1f5f9);
1807
+ border-bottom: var(--aparte-border-width, 1px) solid var(--aparte-file-tree-border, #e2e8f0);
1808
+ font-weight: var(--aparte-font-weight-medium, 500);
1809
+ color: var(--aparte-file-tree-title, #475569);
1810
+ }
1811
+
1812
+ .file-tree-content {
1813
+ padding: var(--aparte-space-4, 8px) 0;
1814
+ }
1815
+
1816
+ .file-node {
1817
+ display: flex;
1818
+ align-items: center;
1819
+ gap: var(--aparte-space-4, 8px);
1820
+ padding: var(--aparte-space-2, 4px) var(--aparte-space-7, 14px);
1821
+ cursor: default;
1822
+ }
1823
+
1824
+ .file-node:hover {
1825
+ background: var(--aparte-file-tree-hover, #f1f5f9);
1826
+ }
1827
+
1828
+ .file-node.file-status-added .file-name { color: var(--aparte-file-status-added, #16a34a); }
1829
+ .file-node.file-status-modified .file-name { color: var(--aparte-file-status-modified, #0284c7); }
1830
+ .file-node.file-status-deleted .file-name { color: var(--aparte-file-status-deleted, #dc2626); text-decoration: line-through; opacity: 0.7; }
1831
+
1832
+ .file-icon {
1833
+ font-size: var(--aparte-font-size-lg, 14px);
1834
+ flex-shrink: 0;
1835
+ opacity: 0.7;
1836
+ }
1837
+
1838
+ /* ─────────────────────────────────────────────────────────────────────────────
1839
+ Composer Primitives
1840
+ Headless by design — consumers own layout. These rules provide only the
1841
+ minimum defaults needed for correctness (display, box model, interaction).
1842
+ ───────────────────────────────────────────────────────────────────────────── */
1843
+
1844
+ aparte-composer {
1845
+ display: block;
1846
+ /* Container-query root — see aparte-chat-viewport. */
1847
+ container-type: inline-size;
1848
+ }
1849
+
1850
+ aparte-composer-input {
1851
+ display: block;
1852
+ flex: 1 1 auto;
1853
+ min-width: 0;
1854
+ }
1855
+
1856
+ /* Contenteditable editor inside aparte-composer-input */
1857
+ aparte-composer-input .aparte-ci-editor {
1858
+ display: block;
1859
+ width: 100%;
1860
+ min-height: var(--aparte-composer-control-size, 44px);
1861
+ padding: var(--aparte-input-padding-y, 10px) var(--aparte-input-padding-x, 12px);
1862
+ font-size: var(--aparte-input-font-size, 14px);
1863
+ line-height: var(--aparte-input-line-height, 1.5);
1864
+ color: var(--aparte-text);
1865
+ background: transparent;
1866
+ border: none;
1867
+ outline: none;
1868
+ resize: none;
1869
+ white-space: pre-wrap;
1870
+ word-wrap: break-word;
1871
+ overflow-y: hidden;
1872
+ box-sizing: border-box;
1873
+ caret-color: var(--aparte-text);
1874
+ }
1875
+
1876
+ /* Placeholder via CSS */
1877
+ aparte-composer-input .aparte-ci-editor:empty::before {
1878
+ content: attr(data-placeholder);
1879
+ color: var(--aparte-input-placeholder, var(--aparte-text-muted, #9ca3af));
1880
+ pointer-events: none;
1881
+ }
1882
+
1883
+ aparte-composer-input .aparte-ci-editor[aria-disabled="true"] {
1884
+ opacity: 0.6;
1885
+ cursor: not-allowed;
1886
+ }
1887
+
1888
+ aparte-composer-send {
1889
+ display: contents;
1890
+ }
1891
+
1892
+ aparte-composer-cancel {
1893
+ display: contents;
1894
+ }
1895
+
1896
+ /* ── Attachment chips list ── */
1897
+ aparte-composer-attachments {
1898
+ display: flex;
1899
+ flex-wrap: wrap;
1900
+ gap: var(--aparte-space-3, 6px);
1901
+ padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
1902
+ width: 100%;
1903
+ box-sizing: border-box;
1904
+ border-bottom: var(--aparte-border-width, 1px) solid var(--aparte-border);
1905
+ }
1906
+
1907
+ aparte-composer-attachments[hidden] {
1908
+ display: none;
1909
+ }
1910
+
1911
+ /* Composer attachment strip — lays out the thumbnail tiles (.aparte-thumb). */
1912
+ aparte-composer-attachments {
1913
+ display: flex;
1914
+ flex-wrap: wrap;
1915
+ gap: var(--aparte-space-3, 6px);
1916
+ --aparte-attachment-image-size: 56px;
1917
+ /* Many attachments → cap the height and scroll. */
1918
+ max-height: var(--aparte-attachments-max-height, 140px);
1919
+ overflow-y: auto;
1920
+ }
1921
+
1922
+ /* ── Add attachment button ── */
1923
+ aparte-composer-add-attachment {
1924
+ display: contents;
1925
+ }
1926
+
1927
+ /* ── Generic action button ── */
1928
+ aparte-composer-action {
1929
+ display: contents;
1930
+ }
1931
+
1932
+ /* ── Drag-over state on root ── */
1933
+ aparte-composer.is-dragover .aparte-composer-shell {
1934
+ outline: var(--aparte-focus-outline-width, 2px) dashed var(--aparte-primary);
1935
+ outline-offset: -2px;
1936
+ border-radius: var(--aparte-radius-input, 12px);
1937
+ }
1938
+ /* fallback: no shell */
1939
+ aparte-composer.is-dragover:not(:has(.aparte-composer-shell)) {
1940
+ outline: var(--aparte-focus-outline-width, 2px) dashed var(--aparte-primary);
1941
+ outline-offset: -2px;
1942
+ border-radius: var(--aparte-radius-input, 12px);
1943
+ }
1944
+
1945
+ /* ─────────────────────────────────────────────────────────────────────────────
1946
+ Composer Layout Helpers (opt-in, not required)
1947
+ Use these classes when you want the composer look out of the box.
1948
+
1949
+ <aparte-composer>
1950
+ <div class="aparte-composer-shell">
1951
+ <aparte-composer-attachments></aparte-composer-attachments>
1952
+ <div class="aparte-composer-row">
1953
+ <aparte-composer-add-attachment></aparte-composer-add-attachment>
1954
+ <aparte-composer-input></aparte-composer-input>
1955
+ <aparte-composer-send></aparte-composer-send>
1956
+ </div>
1957
+ </div>
1958
+ </aparte-composer>
1959
+ ───────────────────────────────────────────────────────────────────────────── */
1960
+
1961
+ /* Outer shell — visual border + radius. Mirrors .aparte-input-wrapper */
1962
+ .aparte-composer-shell {
1963
+ background: var(--aparte-input-bg, var(--aparte-surface-1));
1964
+ border: var(--aparte-border-width, 1px) solid var(--aparte-input-border, var(--aparte-border));
1965
+ border-radius: var(--aparte-radius-input, 12px);
1966
+ max-width: var(--aparte-message-max-width, 800px);
1967
+ margin: 0 auto;
1968
+ width: 100%;
1969
+ box-sizing: border-box;
1970
+ transition: border-color 0.2s;
1971
+ }
1972
+
1973
+ .aparte-composer-shell:focus-within {
1974
+ border-color: var(--aparte-primary);
1975
+ }
1976
+
1977
+ /* Inner row — flex container for input + buttons */
1978
+ .aparte-composer-row {
1979
+ display: flex;
1980
+ align-items: flex-end;
1981
+ gap: var(--aparte-space-4, 8px);
1982
+ padding: var(--aparte-space-4, 8px) var(--aparte-space-6, 12px);
1983
+ }
1984
+
1985
+ /* Action buttons sized to the input's single-line height — BOTH derive from
1986
+ --aparte-composer-control-size (default 44px), so a theme resizes the whole
1987
+ composer control set with ONE variable and alignment holds. Combined with
1988
+ the row's `align-items: flex-end` this makes the buttons read as vertically
1989
+ centered against a single line of text, and keeps them anchored to the
1990
+ bottom once the input grows multi-line — no magic margins. */
1991
+ .aparte-composer-row button {
1992
+ width: var(--aparte-composer-control-size, 44px);
1993
+ height: var(--aparte-composer-control-size, 44px);
1994
+ box-sizing: border-box;
1995
+ flex-shrink: 0;
1996
+ display: flex;
1997
+ align-items: center;
1998
+ justify-content: center;
1999
+ }
2000
+
2001
+ /* Footer row below the input row (model selector etc.) */
2002
+ .aparte-composer-footer {
2003
+ display: flex;
2004
+ align-items: center;
2005
+ gap: var(--aparte-space-4, 8px);
2006
+ padding: var(--aparte-space-3, 6px) var(--aparte-space-6, 12px);
2007
+ border-top: var(--aparte-border-width, 1px) solid var(--aparte-border);
2008
+ }
2009
+
2010
+ .aparte-composer-footer:empty {
2011
+ display: none;
2012
+ }
2013
+
2014
+ /* Cancel/dictate button — same visual style as action buttons */
2015
+ aparte-composer-cancel .aparte-cc-button[hidden],
2016
+ aparte-composer-dictate .aparte-cd-button[hidden] {
2017
+ display: none !important;
2018
+ }
2019
+
2020
+ aparte-composer-cancel .aparte-cc-button,
2021
+ aparte-composer-dictate .aparte-cd-button {
2022
+ background: transparent;
2023
+ border: none;
2024
+ color: var(--aparte-neutral, #6b7280);
2025
+ padding: var(--aparte-space-3, 6px);
2026
+ border-radius: var(--aparte-radius-action-btn, 6px);
2027
+ cursor: pointer;
2028
+ display: flex;
2029
+ align-items: center;
2030
+ justify-content: center;
2031
+ flex-shrink: 0;
2032
+ transition: color 0.15s, background 0.15s;
2033
+ }
2034
+
2035
+ aparte-composer-cancel .aparte-cc-button:hover,
2036
+ aparte-composer-dictate .aparte-cd-button:hover {
2037
+ color: var(--aparte-text);
2038
+ background: var(--aparte-surface-2);
2039
+ }
2040
+
2041
+ /* ─────────────────────────────────────────────────────────────────────────────
2042
+ Opt-in "centered composer when empty" — add the `--auto-center` modifier
2043
+ (the wrappers do this from their `centerWhenEmpty` prop). Consumers that never
2044
+ opt in keep just the base layout above.
2045
+
2046
+ Empty → the [welcome + composer] group sits vertically centered.
2047
+ Active → on the first message the list grows and the composer slides to the
2048
+ bottom (~0.3s). Only `flex-grow` toggles between the two states
2049
+ (flex-basis/height stay constant) so the transition slides instead
2050
+ of snapping. Scoped to `.aparte-viewport--framework` so the native
2051
+ (vanilla) viewport is never touched.
2052
+ ───────────────────────────────────────────────────────────────────────────── */
2053
+
2054
+ .aparte-chat-container--auto-center {
2055
+ display: flex;
2056
+ flex-direction: column;
2057
+ height: 100%;
2058
+ min-height: 0;
2059
+ }
2060
+
2061
+ .aparte-chat-container--auto-center aparte-chat-viewport.aparte-viewport--framework {
2062
+ flex: 1 1 auto;
2063
+ min-height: 0;
2064
+ height: auto;
2065
+ transition: flex-grow 0.3s ease; /* the "slide to bottom" on first message */
2066
+ }
2067
+
2068
+ .aparte-chat-container--auto-center aparte-composer {
2069
+ flex: 0 0 auto;
2070
+ }
2071
+
2072
+ /* Empty delta — a wrapper sets [data-aparte-empty] only while centerWhenEmpty is
2073
+ on AND the message list is empty. `justify-content` flips instantly (not an
2074
+ animatable property) — fine, it only differs between the two steady states. */
2075
+ .aparte-chat-container--auto-center[data-aparte-empty] {
2076
+ justify-content: center;
2077
+ }
2078
+
2079
+ .aparte-chat-container--auto-center[data-aparte-empty]
2080
+ aparte-chat-viewport.aparte-viewport--framework {
2081
+ flex-grow: 0;
2082
+ }
2083
+
2084
+ /* The pre-existing reduced-motion guard only matches `aparte-chat-viewport *`
2085
+ (descendants), not the element itself — so the transition above needs its
2086
+ own guard. */
2087
+ @media (prefers-reduced-motion: reduce) {
2088
+ .aparte-chat-container--auto-center
2089
+ aparte-chat-viewport.aparte-viewport--framework {
2090
+ transition-duration: 0.01ms;
2091
+ }
2092
+ }
2093
+
2094
+ /* =========================================
2095
+ aparte-conversation-list
2096
+ ========================================= */
2097
+
2098
+ aparte-conversation-list {
2099
+ display: flex;
2100
+ flex-direction: column;
2101
+ gap: var(--aparte-conv-list-gap, 2px);
2102
+ overflow-y: auto;
2103
+ overflow-x: hidden;
2104
+ }
2105
+
2106
+ .aparte-conv-item {
2107
+ display: flex;
2108
+ align-items: center;
2109
+ gap: var(--aparte-conv-item-gap, 6px);
2110
+ padding: var(--aparte-conv-item-padding, 7px 10px);
2111
+ border-radius: var(--aparte-conv-item-radius, 6px);
2112
+ cursor: pointer;
2113
+ min-width: 0;
2114
+ color: var(--aparte-conv-item-color, var(--aparte-text-muted));
2115
+ font-size: var(--aparte-conv-item-font-size, 0.8125rem);
2116
+ font-family: var(--aparte-font-family, system-ui, -apple-system, sans-serif);
2117
+ transition: background 0.12s;
2118
+ outline: none;
2119
+ }
2120
+
2121
+ .aparte-conv-item:hover {
2122
+ background: var(--aparte-conv-item-bg-hover, var(--aparte-surface-3));
2123
+ }
2124
+
2125
+ .aparte-conv-item:focus-visible {
2126
+ outline: var(--aparte-focus-outline-width, 2px) solid var(--aparte-primary);
2127
+ outline-offset: -2px;
2128
+ }
2129
+
2130
+ .aparte-conv-item:hover .aparte-conv-item__delete {
2131
+ opacity: 1;
2132
+ }
2133
+
2134
+ .aparte-conv-item:hover .aparte-conv-item__archive {
2135
+ opacity: 1;
2136
+ }
2137
+
2138
+ /* Keyboard access (WCAG 2.1.1): the archive/delete actions are opacity:0 until
2139
+ hover, which would hide keyboard focus. Reveal + ring them on focus. */
2140
+ .aparte-conv-item:focus-within .aparte-conv-item__archive,
2141
+ .aparte-conv-item:focus-within .aparte-conv-item__delete {
2142
+ opacity: 1;
2143
+ }
2144
+
2145
+ .aparte-conv-item__archive:focus-visible,
2146
+ .aparte-conv-item__delete:focus-visible {
2147
+ opacity: 1;
2148
+ outline: var(--aparte-focus-outline-width, 2px) solid var(--aparte-primary);
2149
+ outline-offset: -2px;
2150
+ }
2151
+
2152
+ .aparte-conv-item--active {
2153
+ background: var(--aparte-conv-item-bg-active, var(--aparte-surface-3));
2154
+ color: var(--aparte-conv-item-color-active, var(--aparte-text));
2155
+ font-weight: var(--aparte-conv-item-font-weight-active, 500);
2156
+ }
2157
+
2158
+ .aparte-conv-item--active .aparte-conv-item__delete {
2159
+ opacity: 0;
2160
+ }
2161
+
2162
+ .aparte-conv-item--active .aparte-conv-item__archive {
2163
+ opacity: 0;
2164
+ }
2165
+
2166
+ .aparte-conv-item__title {
2167
+ flex: 1;
2168
+ white-space: nowrap;
2169
+ overflow: hidden;
2170
+ text-overflow: ellipsis;
2171
+ min-width: 0;
2172
+ }
2173
+
2174
+ .aparte-conv-item__delete {
2175
+ flex-shrink: 0;
2176
+ opacity: 0;
2177
+ display: flex;
2178
+ align-items: center;
2179
+ justify-content: center;
2180
+ width: var(--aparte-conv-action-btn-size, 20px);
2181
+ height: var(--aparte-conv-action-btn-size, 20px);
2182
+ border: none;
2183
+ background: none;
2184
+ color: var(--aparte-conv-delete-color, var(--aparte-text-muted));
2185
+ border-radius: var(--aparte-conv-delete-radius, var(--aparte-radius-sm));
2186
+ cursor: pointer;
2187
+ padding: 0;
2188
+ transition: opacity 0.12s, background 0.12s, color 0.12s;
2189
+ }
2190
+
2191
+ .aparte-conv-item__delete:hover {
2192
+ background: var(--aparte-conv-delete-bg-hover, var(--aparte-error));
2193
+ color: var(--aparte-conv-delete-color-hover, var(--aparte-text-inverse));
2194
+ }
2195
+
2196
+ .aparte-conv-item__archive {
2197
+ flex-shrink: 0;
2198
+ opacity: 0;
2199
+ display: flex;
2200
+ align-items: center;
2201
+ justify-content: center;
2202
+ width: var(--aparte-conv-action-btn-size, 20px);
2203
+ height: var(--aparte-conv-action-btn-size, 20px);
2204
+ border: none;
2205
+ background: none;
2206
+ color: var(--aparte-conv-archive-color, var(--aparte-text-muted));
2207
+ border-radius: var(--aparte-conv-archive-radius, var(--aparte-radius-sm));
2208
+ cursor: pointer;
2209
+ padding: 0;
2210
+ transition: opacity 0.12s, background 0.12s, color 0.12s;
2211
+ }
2212
+
2213
+ .aparte-conv-item__archive:hover {
2214
+ background: var(--aparte-conv-archive-bg-hover, var(--aparte-surface-4, var(--aparte-surface-3)));
2215
+ color: var(--aparte-conv-archive-color-hover, var(--aparte-text));
2216
+ }
2217
+
2218
+ .aparte-conv-item--archived .aparte-conv-item__title {
2219
+ font-style: italic;
2220
+ opacity: 0.85;
2221
+ }
2222
+
2223
+ /* =========================================
2224
+ Provider-HTML overflow guards
2225
+ ========================================= */
2226
+
2227
+ /* Markdown providers inject arbitrary HTML into .segment-text, and the bubble
2228
+ clips overflow (`aparte-chat-bubble { overflow: hidden }`) — without these
2229
+ guards a wide image or table is silently cut at the bubble edge. */
2230
+ .segment-text img {
2231
+ max-width: 100%;
2232
+ height: auto;
2233
+ }
2234
+
2235
+ .segment-text table {
2236
+ display: block;
2237
+ max-width: 100%;
2238
+ overflow-x: auto;
2239
+ border-collapse: collapse;
2240
+ }
2241
+
2242
+ /* =========================================
2243
+ Adaptive layout (container width + input modality)
2244
+ ========================================= */
2245
+
2246
+ /* Narrow containers (embedded widgets, mobile) — tighter chrome. The theming
2247
+ variables are defined at :root, so `var(x, fallback)` fallbacks can never
2248
+ fire — instead each rule REASSIGNS the variable at the element, the same
2249
+ per-context pattern .aparte-attachments already uses. To retheme the narrow
2250
+ state, override these variables inside your own @container block. Browsers
2251
+ without container-query support keep the wide defaults. */
2252
+ @container (max-width: 520px) {
2253
+ .aparte-message {
2254
+ --aparte-message-padding: var(--aparte-space-6, 12px) var(--aparte-space-4, 8px);
2255
+ --aparte-message-gap: var(--aparte-space-4, 8px);
2256
+ }
2257
+
2258
+ .aparte-avatar {
2259
+ --aparte-avatar-size: 24px;
2260
+ }
2261
+
2262
+ .aparte-messages-wrapper {
2263
+ --aparte-viewport-padding: var(--aparte-space-6, 12px) var(--aparte-space-4, 8px);
2264
+ }
2265
+
2266
+ .aparte-composer-row {
2267
+ padding: var(--aparte-space-4, 8px);
2268
+ }
2269
+ }
2270
+
2271
+ /* Coarse pointers (touch) — comfortable hit areas. */
2272
+ @media (pointer: coarse) {
2273
+ .aparte-scroll-btn {
2274
+ --aparte-scroll-btn-size: var(--aparte-touch-target-size, 44px);
2275
+ }
2276
+
2277
+ .aparte-send-button {
2278
+ --aparte-send-btn-size: var(--aparte-touch-target-size, 44px);
2279
+ }
2280
+
2281
+ .aparte-action-button {
2282
+ --aparte-input-action-btn-size: var(--aparte-touch-target-size, 44px);
2283
+ }
2284
+
2285
+ /* Hover cannot reveal these on touch — keep them visible and tappable. */
2286
+ .aparte-conv-item__delete,
2287
+ .aparte-conv-item__archive {
2288
+ opacity: 1;
2289
+ --aparte-conv-action-btn-size: 28px;
2290
+ }
2291
+ }
2292
+ /**
2293
+ * Aparte Select CSS
2294
+ * Stylable via CSS variables
2295
+ */
2296
+
2297
+ aparte-select {
2298
+ display: inline-block;
2299
+ position: relative;
2300
+ min-width: 200px;
2301
+ font-family: var(--aparte-font-family, inherit);
2302
+ }
2303
+
2304
+ /* Trigger */
2305
+ .aparte-select-trigger {
2306
+ display: flex;
2307
+ align-items: center;
2308
+ justify-content: space-between;
2309
+ gap: 0.5rem;
2310
+ padding: 0.5rem 0.75rem;
2311
+ background: var(--aparte-select-bg, var(--aparte-surface-1, #fff));
2312
+ border: 1px solid var(--aparte-select-border, var(--aparte-border, #e2e8f0));
2313
+ border-radius: var(--aparte-select-radius, 0.5rem);
2314
+ cursor: pointer;
2315
+ transition: border-color 0.15s, box-shadow 0.15s;
2316
+ }
2317
+
2318
+ .aparte-select-trigger:hover {
2319
+ border-color: var(--aparte-select-border-hover, var(--aparte-primary, #3b82f6));
2320
+ }
2321
+
2322
+ .aparte-select-trigger:focus {
2323
+ outline: none;
2324
+ border-color: var(--aparte-select-border-focus, var(--aparte-primary, #3b82f6));
2325
+ box-shadow: 0 0 0 2px var(--aparte-select-ring, rgba(59, 130, 246, 0.2));
2326
+ }
2327
+
2328
+ aparte-select[disabled] .aparte-select-trigger {
2329
+ opacity: 0.5;
2330
+ cursor: not-allowed;
2331
+ }
2332
+
2333
+ .aparte-select-label {
2334
+ flex: 1;
2335
+ overflow: hidden;
2336
+ text-overflow: ellipsis;
2337
+ white-space: nowrap;
2338
+ color: var(--aparte-select-text, var(--aparte-text, #1e293b));
2339
+ }
2340
+
2341
+ .aparte-select-chevron {
2342
+ flex-shrink: 0;
2343
+ color: var(--aparte-select-chevron, var(--aparte-text-muted, #94a3b8));
2344
+ transition: transform 0.2s;
2345
+ }
2346
+
2347
+ aparte-select[open] .aparte-select-chevron {
2348
+ transform: rotate(180deg);
2349
+ }
2350
+
2351
+ /* Dropdown — position:fixed to escape overflow:hidden ancestors */
2352
+ .aparte-select-dropdown {
2353
+ position: fixed;
2354
+ /* top / left / width are set dynamically by _updatePosition() */
2355
+ background: var(--aparte-select-dropdown-bg, var(--aparte-surface-1, #fff));
2356
+ border: 1px solid var(--aparte-select-border, var(--aparte-border, #e2e8f0));
2357
+ border-radius: var(--aparte-select-radius, 0.5rem);
2358
+ box-shadow: var(--aparte-select-shadow, 0 4px 12px rgba(0, 0, 0, 0.1));
2359
+ z-index: var(--aparte-select-z, 1000);
2360
+ max-height: 300px;
2361
+ overflow: auto;
2362
+ }
2363
+
2364
+ .aparte-select-dropdown[hidden] {
2365
+ display: none;
2366
+ }
2367
+
2368
+ /* Search */
2369
+ .aparte-select-search {
2370
+ display: block;
2371
+ width: 100%;
2372
+ padding: 0.5rem 0.75rem;
2373
+ border: none;
2374
+ border-bottom: 1px solid var(--aparte-border, #e2e8f0);
2375
+ background: transparent;
2376
+ font-size: inherit;
2377
+ font-family: inherit;
2378
+ }
2379
+
2380
+ .aparte-select-search:focus {
2381
+ outline: none;
2382
+ }
2383
+
2384
+ /* Options container */
2385
+ .aparte-select-options {
2386
+ padding: 0.25rem;
2387
+ }
2388
+
2389
+ /* Option */
2390
+ aparte-option {
2391
+ display: flex;
2392
+ align-items: center;
2393
+ justify-content: space-between;
2394
+ padding: 0.5rem 0.75rem;
2395
+ border-radius: 0.25rem;
2396
+ cursor: pointer;
2397
+ transition: background-color 0.1s;
2398
+ color: var(--aparte-select-text, var(--aparte-text, #1e293b));
2399
+ }
2400
+
2401
+ aparte-option:hover {
2402
+ background: var(--aparte-select-option-hover, var(--aparte-surface-2, #f1f5f9));
2403
+ }
2404
+
2405
+ /* Keyboard-active option (roving highlight driven by aria-activedescendant).
2406
+ Distinct from :hover so keyboard users get an unambiguous focus indicator. */
2407
+ aparte-option[data-active] {
2408
+ background: var(--aparte-select-option-hover, var(--aparte-surface-2, #f1f5f9));
2409
+ box-shadow: inset 0 0 0 2px var(--aparte-primary, #3b82f6);
2410
+ }
2411
+
2412
+ aparte-option[selected] {
2413
+ background: var(--aparte-select-option-selected, var(--aparte-primary, #3b82f6));
2414
+ color: var(--aparte-select-option-selected-text, #fff);
2415
+ }
2416
+
2417
+ aparte-option[disabled] {
2418
+ opacity: 0.5;
2419
+ cursor: not-allowed;
2420
+ }
2421
+
2422
+ /* Status dot injected by AparteOption when data-status is set */
2423
+ .aparte-status-dot {
2424
+ width: 8px;
2425
+ height: 8px;
2426
+ border-radius: 50%;
2427
+ flex-shrink: 0;
2428
+ margin-left: 8px;
2429
+ }
2430
+
2431
+ .aparte-status-dot[data-status="ready"] {
2432
+ background-color: #22c55e;
2433
+ }
2434
+
2435
+ .aparte-status-dot[data-status="cached"] {
2436
+ background-color: #f59e0b;
2437
+ }
2438
+
2439
+ .aparte-status-dot[data-status="not-downloaded"] {
2440
+ background-color: var(--aparte-border, #94a3b8);
2441
+ }
2442
+
2443
+ /* Optgroup */
2444
+ aparte-optgroup {
2445
+ display: block;
2446
+ }
2447
+
2448
+ .aparte-optgroup-header {
2449
+ display: flex;
2450
+ align-items: center;
2451
+ justify-content: space-between;
2452
+ padding: 0.5rem 0.75rem;
2453
+ font-size: 0.75rem;
2454
+ font-weight: 600;
2455
+ text-transform: uppercase;
2456
+ letter-spacing: 0.05em;
2457
+ color: var(--aparte-text-muted, #94a3b8);
2458
+ }
2459
+
2460
+ .aparte-optgroup-chevron {
2461
+ width: 0;
2462
+ height: 0;
2463
+ border-left: 4px solid transparent;
2464
+ border-right: 4px solid transparent;
2465
+ border-top: 5px solid currentColor;
2466
+ transition: transform 0.2s;
2467
+ }
2468
+
2469
+ aparte-optgroup[collapsed] .aparte-optgroup-chevron {
2470
+ transform: rotate(-90deg);
2471
+ }
2472
+
2473
+ /* Dark mode */
2474
+ [data-aparte-theme="dark"] aparte-select .aparte-select-trigger,
2475
+ [data-aparte-theme="dark"] aparte-select .aparte-select-dropdown {
2476
+ background: var(--aparte-select-bg, var(--aparte-surface-1, #1e293b));
2477
+ border-color: var(--aparte-select-border, var(--aparte-border, #334155));
2478
+ }
2479
+
2480
+ [data-aparte-theme="dark"] aparte-option:hover {
2481
+ background: var(--aparte-select-option-hover, var(--aparte-surface-2, #334155));
2482
+ }
2483
+
2484
+ .aparte-optgroup-loader {
2485
+ padding: 0.5rem 0.75rem;
2486
+ font-size: 0.75rem;
2487
+ color: var(--aparte-text-muted, #94a3b8);
2488
+ font-style: italic;
2489
+ display: flex;
2490
+ align-items: center;
2491
+ gap: 0.5rem;
2492
+ }
2493
+
2494
+ .aparte-spinner-small {
2495
+ width: 12px;
2496
+ height: 12px;
2497
+ border: 2px solid currentColor;
2498
+ border-top-color: transparent;
2499
+ border-radius: 50%;
2500
+ animation: aparte-spin 0.6s linear infinite;
2501
+ }
2502
+
2503
+ @keyframes aparte-spin {
2504
+ to {
2505
+ transform: rotate(360deg);
2506
+ }
2507
+ }/**
2508
+ * AparteProgressSpinner — CSS
2509
+ *
2510
+ * All colors are driven by CSS custom properties so consumers can theme freely.
2511
+ * The `color` property on the element controls both fill and track by default.
2512
+ */
2513
+
2514
+ aparte-progress-spinner {
2515
+ display: inline-flex;
2516
+ align-items: center;
2517
+ justify-content: center;
2518
+ width: var(--aparte-spinner-size, 14px);
2519
+ height: var(--aparte-spinner-size, 14px);
2520
+ flex-shrink: 0;
2521
+ }
2522
+
2523
+ aparte-progress-spinner svg {
2524
+ width: 100%;
2525
+ height: 100%;
2526
+ /* Start arc from 12 o'clock */
2527
+ transform: rotate(-90deg);
2528
+ transform-origin: center;
2529
+ overflow: visible;
2530
+ }
2531
+
2532
+ .aparte-spinner-track {
2533
+ fill: none;
2534
+ stroke: var(--aparte-spinner-track, color-mix(in srgb, currentColor 15%, transparent));
2535
+ stroke-width: var(--aparte-spinner-stroke, 2.5);
2536
+ }
2537
+
2538
+ .aparte-spinner-fill {
2539
+ fill: none;
2540
+ stroke: var(--aparte-spinner-color, currentColor);
2541
+ stroke-width: var(--aparte-spinner-stroke, 2.5);
2542
+ stroke-linecap: round;
2543
+ transition: stroke-dashoffset 0.25s ease;
2544
+ }
2545
+
2546
+ /* Indeterminate: rotate the whole SVG continuously */
2547
+ aparte-progress-spinner:not([value]) svg {
2548
+ animation: aparte-spinner-rotate 0.9s linear infinite;
2549
+ }
2550
+
2551
+ @keyframes aparte-spinner-rotate {
2552
+ from { transform: rotate(-90deg); }
2553
+ to { transform: rotate(270deg); }
2554
+ }