@frontera-sdk/chat-ui 1.50.40

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 (148) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +348 -0
  3. package/dist/af-DK_CjRn6-OPBHDTNZ.js +197 -0
  4. package/dist/agent-picker.d.ts +38 -0
  5. package/dist/agent-picker.js +8 -0
  6. package/dist/as-BPtSu6VN-O4ZZEAJG.js +11 -0
  7. package/dist/be-CrQ0smca-5HIE5K27.js +11 -0
  8. package/dist/bg-CrQqGwni-3IRVPZQ7.js +11 -0
  9. package/dist/bn-NCLZcWHi-XOZJFNIB.js +11 -0
  10. package/dist/ca-SUf6Y2Om-V6F2EBCA.js +38 -0
  11. package/dist/capture-NO5ZIGP6.js +1652 -0
  12. package/dist/chat.css +3555 -0
  13. package/dist/chat.d.ts +161 -0
  14. package/dist/chat.js +60498 -0
  15. package/dist/chunk-2KEBRV3W.js +191 -0
  16. package/dist/chunk-6BPHEBCF.js +639 -0
  17. package/dist/chunk-6HYPVNVA.js +25 -0
  18. package/dist/chunk-7ADM3CKD.js +10 -0
  19. package/dist/chunk-AFVU5VT6.js +5601 -0
  20. package/dist/chunk-BEJ3HQG4.js +93 -0
  21. package/dist/chunk-CCVPZVFK.js +28 -0
  22. package/dist/chunk-DGDHKMDB.js +10 -0
  23. package/dist/chunk-FXKXDR5K.js +2166 -0
  24. package/dist/chunk-GTQH7MPF.js +129 -0
  25. package/dist/chunk-I7SXWUAN.js +1258 -0
  26. package/dist/chunk-IQRPGFLI.js +234 -0
  27. package/dist/chunk-IXDM5R55.js +23 -0
  28. package/dist/chunk-JD6ETTXC.js +178 -0
  29. package/dist/chunk-OP4MLJDA.js +215 -0
  30. package/dist/chunk-PQZ2CS26.js +2315 -0
  31. package/dist/chunk-PR4QN5HX.js +38 -0
  32. package/dist/chunk-PUIVKDV5.js +271 -0
  33. package/dist/chunk-QSSFBZAS.js +6914 -0
  34. package/dist/chunk-RAKKA3WU.js +5896 -0
  35. package/dist/chunk-RB2Y4NYC.js +681 -0
  36. package/dist/chunk-VDZH5Z3K.js +72 -0
  37. package/dist/chunk-WMN6BPPS.js +49 -0
  38. package/dist/chunk-WO6DJLFE.js +10857 -0
  39. package/dist/chunk-X5E2DJVO.js +47634 -0
  40. package/dist/chunk-XJFTVKLW.js +2687 -0
  41. package/dist/chunk-ZS6PXIZP.js +187 -0
  42. package/dist/client.d.ts +89 -0
  43. package/dist/client.js +20 -0
  44. package/dist/composer.d.ts +23 -0
  45. package/dist/composer.js +89 -0
  46. package/dist/cop-C3sk0IUo-CIZA2H43.js +11 -0
  47. package/dist/credential.d.ts +75 -0
  48. package/dist/credential.js +3 -0
  49. package/dist/cs-CmAteokq-HAKEQPB4.js +17 -0
  50. package/dist/cu-CBUlrW6Y-QTZ6ZLA4.js +32 -0
  51. package/dist/cy-BBPa6z2x-MX6DGDNW.js +11 -0
  52. package/dist/da-CCDXfXtG-5YEOHMG3.js +11 -0
  53. package/dist/de-1901-CWoAOigE-63QFSF2A.js +11 -0
  54. package/dist/de-1996-BL6awyxG-R2PHKMAB.js +11 -0
  55. package/dist/de-ch-1901-hVThVHVb-JSDB53OZ.js +11 -0
  56. package/dist/el-monoton-D3aKqTz2-2V4OOFBB.js +11 -0
  57. package/dist/el-polyton-ogVWcTzh-3U7NGQMT.js +11 -0
  58. package/dist/en-gb-B7Al27b_-UKCBQFSU.js +21 -0
  59. package/dist/es-DcaaHPU_-NOBXSOZK.js +11 -0
  60. package/dist/esm-MHF25KDL.js +3 -0
  61. package/dist/et-B_spaleu-5UAYIW62.js +11 -0
  62. package/dist/eu-Ctv_kq9Z-V5DREOPN.js +11 -0
  63. package/dist/fi-RW5VB8Yp-YSXBK7LW.js +11 -0
  64. package/dist/fr-BC2onEz9-7KH5T76G.js +11 -0
  65. package/dist/frame.d.ts +59 -0
  66. package/dist/frame.js +111 -0
  67. package/dist/fur-AC12wySk-4PD2Y72H.js +11 -0
  68. package/dist/ga-Cn6LkudM-RRAAJT3N.js +58 -0
  69. package/dist/gl-DZGc-K_G-KYSXXTCY.js +11 -0
  70. package/dist/grc-DOlGgO8T-2HXBZVJ5.js +11 -0
  71. package/dist/gu-Bjl0l-cf-4CFSVZHZ.js +11 -0
  72. package/dist/header.d.ts +78 -0
  73. package/dist/header.js +18 -0
  74. package/dist/hi-DsAaTPCC-KMCFBWK7.js +11 -0
  75. package/dist/hr-DT1SBBes-X2TIX6EI.js +11 -0
  76. package/dist/hsb-DC_ITc1e-PXLERA7R.js +46 -0
  77. package/dist/hu-DVk7Y_ka-PJPVU54B.js +11 -0
  78. package/dist/hy-e995ysyO-MZBRFX52.js +11 -0
  79. package/dist/ia-Cr4TJH0c-D3O2FHGP.js +22 -0
  80. package/dist/id-DifDyWg9-MZ2BGHWI.js +58 -0
  81. package/dist/is-BT8UVRze-CZZCKEFI.js +11 -0
  82. package/dist/it-Bt_Meacx-T4Y5JYV6.js +11 -0
  83. package/dist/ka-jyoLzyDE-ES6EXZLW.js +11 -0
  84. package/dist/kmr-Cfwhwuff-TSW2R2KS.js +11 -0
  85. package/dist/kn-C6Lov-DP-B4BL47CE.js +11 -0
  86. package/dist/la-DuvHjUWw-6K7TFU6X.js +11 -0
  87. package/dist/la-x-classic-yTyAOQEt-YWTKMM2L.js +11 -0
  88. package/dist/la-x-liturgic-CXeQb4FY-B772L5E7.js +11 -0
  89. package/dist/lib-3ZKX25RJ.js +4511 -0
  90. package/dist/lt-BBe8DlDF-4YINVOZR.js +11 -0
  91. package/dist/lv-Q2BBHnzp-TMVQ3MX2.js +11 -0
  92. package/dist/message.d.ts +64 -0
  93. package/dist/message.js +18 -0
  94. package/dist/ml-2xLVYkVY-NGZLAPIN.js +11 -0
  95. package/dist/mn-cyrl-lHM4n0oC-DGBYYUT3.js +11 -0
  96. package/dist/mn-cyrl-x-lmc-3aetruI8-QHJABMOO.js +11 -0
  97. package/dist/mr-CjWJPVmH-X5UHV65O.js +11 -0
  98. package/dist/mul-ethi-D_CMSMEq-H7UMQSOQ.js +11 -0
  99. package/dist/nb-DI8u59ch-J4CAVI4Y.js +122 -0
  100. package/dist/new-session-Dw7JOgzu.d.ts +141 -0
  101. package/dist/new-session.d.ts +4 -0
  102. package/dist/new-session.js +10 -0
  103. package/dist/nl-BHQJ6Gvn-7BH55RA7.js +52 -0
  104. package/dist/nn-ntZvarUK-KKNCHHJ4.js +122 -0
  105. package/dist/no-B8ItS0F3-3ZXIRJ6U.js +120 -0
  106. package/dist/oauth-callback.d.ts +35 -0
  107. package/dist/oauth-callback.js +18 -0
  108. package/dist/oc-P_i3-xLA-XZM6E5YZ.js +11 -0
  109. package/dist/or-50rxpoq4-5VELAFMJ.js +11 -0
  110. package/dist/pa-C54UG8eN-WEJPMZ6Z.js +11 -0
  111. package/dist/parts.d.ts +49 -0
  112. package/dist/parts.js +106 -0
  113. package/dist/pdf-cover-thumbnail-inner-4N2Q5AAG.js +36 -0
  114. package/dist/pdf-deck-view-inner-FZVLAEQR.js +575 -0
  115. package/dist/pdf-document-view-inner-6DF54JWK.js +585 -0
  116. package/dist/pdf-viewer-inner-GPES3KG2.js +256 -0
  117. package/dist/pending-cards.d.ts +26 -0
  118. package/dist/pending-cards.js +9 -0
  119. package/dist/pi-B9cgjyG1-GD5ASDDU.js +11 -0
  120. package/dist/pl-9fg-ESVt-UMX7MCJM.js +32 -0
  121. package/dist/platform.css +4013 -0
  122. package/dist/pms-BRGfm5gS-SSE3AUW5.js +11 -0
  123. package/dist/pt-Cum2_NUC-W35JYVR4.js +11 -0
  124. package/dist/rm-C0MIK9N4-IWL4YTNT.js +11 -0
  125. package/dist/ro-0fmdH0HA-46WUTOYH.js +11 -0
  126. package/dist/ru-v4l-ayaq-BUGK6XCN.js +196 -0
  127. package/dist/sa-CKY9c_Vq-4KIJOI5Y.js +11 -0
  128. package/dist/sh-cyrl-BpCRFOmq-DSLQ4UY2.js +128 -0
  129. package/dist/sh-latn-sRy4Tuaj-UNQOR63V.js +128 -0
  130. package/dist/sk-_iOeDNrQ-NCTQKH25.js +17 -0
  131. package/dist/sl-DpiklF-D-6GG5TDVN.js +11 -0
  132. package/dist/sr-cyrl-Dpzrbd0W-4Q5226YJ.js +142 -0
  133. package/dist/sv-CFm5y56M-VTWNPZ7I.js +11 -0
  134. package/dist/ta-BcghgTbD-NRT35DW6.js +11 -0
  135. package/dist/te-B5Spl2Jk-5VE6CLN4.js +11 -0
  136. package/dist/th-DWX5iPSo-N5GMOQOP.js +11 -0
  137. package/dist/theme.d.ts +49 -0
  138. package/dist/theme.js +3 -0
  139. package/dist/tk-BMbKB10q-HGWWY5L3.js +11 -0
  140. package/dist/tr-CIHIuzqc-6ISBNYLV.js +11 -0
  141. package/dist/types-BDqLaBfH.d.ts +60 -0
  142. package/dist/uk-Dir3oxLs-YQ3QZRI4.js +11 -0
  143. package/dist/univer-workbook-BSSUE5TG.css +3555 -0
  144. package/dist/univer-workbook-N5IIN7CH.js +290849 -0
  145. package/dist/vega-chart-block-NQADYNWQ.js +2857 -0
  146. package/dist/zh-latn-pinyin-BdYb-Ey_-RB542AHF.js +11 -0
  147. package/package.json +137 -0
  148. package/src/styles.css +996 -0
package/src/styles.css ADDED
@@ -0,0 +1,996 @@
1
+ /**
2
+ * Frontera chat UI.
3
+ *
4
+ * Every colour and metric is a custom property, so a host application themes
5
+ * the surface without overriding rules. Class names are prefixed `fr-` because
6
+ * this stylesheet lands in someone else's page and must not collide with it.
7
+ *
8
+ * Import once:
9
+ * import '@frontera-sdk/chat-ui/styles.css'
10
+ */
11
+
12
+ /*
13
+ The outer surface: conversation on the left, artifact pane on the right.
14
+
15
+ Themed here rather than on `.fr-chat` because the pane is a sibling of the
16
+ conversation, not a child of it — tokens set one level down would not reach
17
+ it, and the pane would render in the host page's palette.
18
+ */
19
+ .fr-shell {
20
+ position: relative;
21
+ display: flex;
22
+ min-height: 0;
23
+ height: 100%;
24
+ isolation: isolate;
25
+ /*
26
+ Tokens live HERE, not on `.fr-chat`. The pane is a SIBLING of the
27
+ conversation — tokens declared one level down never reach it, and every
28
+ `var(--fr-*)` inside the pane resolved to nothing: a transparent (black on
29
+ a dark host) pane with an unresolved border. See `.fr-chat` for what each
30
+ alias means and why the fallbacks are load-bearing.
31
+ */
32
+ --fr-bg: var(--surface-chat, hsl(0 0% 100%));
33
+ --fr-surface: var(--surface, hsl(0 0% 98%));
34
+ --fr-bubble: var(--surface-raised-active, hsl(0 0% 94%));
35
+ --fr-border: var(--border, hsl(240 100% 6% / 0.08));
36
+ --fr-text: hsl(var(--foreground, 47 13% 14%));
37
+ --fr-muted: var(--muted-foreground, hsl(240 4% 46%));
38
+ --fr-accent: var(--primary, hsl(221 83% 53%));
39
+ --fr-accent-text: var(--primary-foreground, hsl(0 0% 100%));
40
+ --fr-danger: var(--destructive, hsl(0 72% 45%));
41
+ --fr-code: var(--inline-code, hsl(9 62% 45%));
42
+ --fr-code-bg: var(--inline-code-bg, hsl(0 0% 95.5%));
43
+ --fr-radius: 1rem;
44
+ --fr-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
45
+ /* The answer is 15px; the prompt one step under, at 14px — see .fr-bubble. */
46
+ --fr-font-size: 15px;
47
+ }
48
+
49
+ /*
50
+ BOTH panels ease `flex-grow`, which is the platform's push: one gives up
51
+ exactly the width the other takes (`PANE_ANIM_CLASS` in
52
+ chat-content-chrome.tsx). Suspended while the handle is being dragged — an
53
+ eased width would leave the panel lagging behind the cursor.
54
+ */
55
+ .fr-panel-anim {
56
+ transition: flex-grow 150ms ease-out;
57
+ }
58
+ .fr-split:has([data-resize-handle-state="drag"]) .fr-panel-anim,
59
+ /* Expand/restore snaps — `.fr-snap` is set for exactly one frame while the
60
+ fullscreen layout commits, matching the platform's mode switch. */
61
+ .fr-split.fr-snap .fr-panel-anim {
62
+ transition: none;
63
+ }
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .fr-panel-anim { transition: none; }
66
+ }
67
+
68
+ /* The panel group fills the shell; the library sets the panels' widths. */
69
+ .fr-split {
70
+ flex: 1;
71
+ min-width: 0;
72
+ height: 100%;
73
+ /* The platform's group carries `overflow-hidden`; without it anything the
74
+ panels fail to contain widens the page itself. */
75
+ overflow: hidden;
76
+ }
77
+
78
+ /* The pane holds a fixed column; the conversation takes whatever is left and
79
+ is allowed to shrink — `min-width: 0`, without which a long unbroken line
80
+ in a reply pushes the pane off the edge instead of wrapping. */
81
+ /* Width is the panel library's; this only paints the pane and its seam. The
82
+ border lives on the pane rather than the handle so a collapsed pane leaves
83
+ no line behind. */
84
+ .fr-side-pane {
85
+ min-height: 0;
86
+ /* min-width matters since the pane's content stays MOUNTED while shut: a
87
+ flex item's default `min-width: auto` refuses to shrink below the
88
+ content's min-content width, so the collapsed 0% panel held the viewer's
89
+ width and pushed the whole page into a horizontal scroll. */
90
+ min-width: 0;
91
+ overflow: hidden;
92
+ background: var(--fr-bg);
93
+ border-left: 1px solid transparent;
94
+ transition: border-color 150ms ease-out;
95
+ }
96
+ .fr-side-pane:not([data-panel-size="0.0"]) {
97
+ border-left-color: var(--fr-border);
98
+ }
99
+ /* Fullscreen: the conversation is collapsed, so the pane's leading edge is the
100
+ surface's own — a seam there reads as a rendering artifact. `~` because the
101
+ resize handle sits between the two panels. */
102
+ .fr-split > [data-panel-size="0.0"] ~ .fr-side-pane {
103
+ border-left-color: transparent;
104
+ }
105
+
106
+ /*
107
+ On a phone the pane is a SHEET, not a column.
108
+
109
+ The platform drops its split layout entirely under 768px and surfaces the
110
+ same content as a full-screen overlay — the canvas, the computer panel, an
111
+ artifact, the sources. This package kept the split at every width, so a
112
+ 360px screen was asked to hold a readable conversation AND a document side
113
+ by side: the thread squeezed to its `minSize`, the pane to a strip, and the
114
+ summary and files the platform shows inline were effectively unreachable.
115
+
116
+ Done here rather than by branching the tree, deliberately. The panel group
117
+ owns the open/closed state, the mount/unmount of the viewer and the restore
118
+ of a conversation's tabs; a second layout would have to reproduce all three
119
+ and then drift from it. Taking the pane out of flow changes where it PAINTS
120
+ and leaves every one of those behaviours where it already works.
121
+
122
+ `!important` because `react-resizable-panels` writes `flex` inline on each
123
+ panel, and inline styles outrank a stylesheet. The overrides are scoped to
124
+ this breakpoint and to this package's own classes.
125
+ */
126
+ @media (max-width: 767px) {
127
+ .fr-shell .fr-side-pane:not([data-panel-size="0.0"]) {
128
+ position: fixed;
129
+ inset: 0;
130
+ z-index: 60;
131
+ /* Above the composer's own stacking, and clear of the home indicator. */
132
+ padding-bottom: env(safe-area-inset-bottom);
133
+ flex: none !important;
134
+ width: 100% !important;
135
+ border-left: none;
136
+ }
137
+ /*
138
+ A collapsed pane is `0%` wide in the flow and invisible. Fixed, it would
139
+ be a full-screen transparent layer over the conversation swallowing every
140
+ tap — so the closed state has to leave the page rather than shrink.
141
+ */
142
+ .fr-shell .fr-side-pane[data-panel-size="0.0"] {
143
+ display: none;
144
+ }
145
+ /* The conversation takes the whole width once the pane no longer occupies
146
+ any of it — otherwise it keeps the percentage the group assigned and
147
+ leaves a blank column beside the sheet. */
148
+ .fr-shell .fr-split > [data-panel]:first-child {
149
+ flex: 1 1 0% !important;
150
+ }
151
+ /* Nothing to drag between a thread and an overlay. */
152
+ .fr-shell .fr-split > [data-panel-resize-handle-id] {
153
+ display: none;
154
+ }
155
+ }
156
+
157
+ /* The pane's content chain: `RightPanel` is `h-full` + its own ScrollArea, so
158
+ this must actually BE the pane's height or the panel renders collapsed —
159
+ which looked like a bordered box floating over a void. */
160
+ .fr-side-pane-surface {
161
+ height: 100%;
162
+ min-height: 0;
163
+ background: var(--fr-bg);
164
+ /* The pane is OUTSIDE `.fr-chat`, so without these it renders in the host
165
+ page's font and colour — which is what made the Outputs list look like a
166
+ different product from the conversation beside it. */
167
+ font-family: var(--fr-font);
168
+ color: var(--fr-text);
169
+ line-height: 1.5;
170
+ }
171
+
172
+ /* The positioning context for the floating composer. */
173
+ .fr-chat {
174
+ position: relative;
175
+ min-width: 0;
176
+ /*
177
+ DERIVED from the platform's tokens, not a second copy of them.
178
+
179
+ These used to be literal colours, with a `prefers-color-scheme` block of
180
+ their own — a whole parallel palette beside the one `platform.css` defines.
181
+ Two palettes meant two dark switches, and they disagreed: the shell turned
182
+ dark with the OS while the copied components waited for a `dark` class, so
183
+ white cards sat on a black background and the surface looked half-themed.
184
+
185
+ One source of truth now. Each `--fr-*` is an alias, so a host overriding
186
+ either layer moves both, and dark happens once.
187
+
188
+ Aliased to the RAW tokens (`--background`), not Tailwind's `--color-*`.
189
+ That is not a style preference — it is the only thing that works. Tailwind
190
+ declares `--color-background: hsl(var(--background))` on `:root`, where it
191
+ is substituted once; descendants inherit the already-computed colour. So a
192
+ `dark` class further down the tree changes `--background` and the `--color-*`
193
+ alias never notices. The raw tokens ARE redeclared inside `.dark`, which is
194
+ what makes them follow.
195
+
196
+ Two shapes, deliberately preserved: some tokens hold bare channels for
197
+ `hsl()` to wrap, others a finished colour. Wrapping a finished colour, or
198
+ failing to wrap bare channels, yields an invalid value and the property
199
+ silently drops.
200
+
201
+ The fallbacks are load-bearing: `styles.css` can be imported WITHOUT
202
+ `platform.css` by a consumer composing their own layout from `./message`
203
+ and `./composer`, and an unresolved custom property paints nothing at all.
204
+ */
205
+ /*
206
+ `--surface-chat`, not `--background`.
207
+
208
+ The platform paints its whole chat container `bg-surface-chat`
209
+ (`chat-view.tsx`), and several components lean on that: the reasoning row's
210
+ icon sits in a `bg-surface-chat` pill that is invisible BECAUSE it matches
211
+ what is behind it. Using `--background` here left those pills a shade off
212
+ the thread, so a spacer read as a badge around the icon.
213
+ */
214
+ /* Declared on `.fr-shell` too — the pane is a sibling of `.fr-chat` and
215
+ needs the same tokens. Kept here as well so a consumer composing their own
216
+ layout from `./message` inside a bare `.fr-chat` still gets them. */
217
+ --fr-bg: var(--surface-chat, hsl(0 0% 100%));
218
+ --fr-surface: var(--surface, hsl(0 0% 98%));
219
+ --fr-bubble: var(--surface-raised-active, hsl(0 0% 94%));
220
+ --fr-border: var(--border, hsl(240 100% 6% / 0.08));
221
+ --fr-text: hsl(var(--foreground, 47 13% 14%));
222
+ --fr-muted: var(--muted-foreground, hsl(240 4% 46%));
223
+ --fr-accent: var(--primary, hsl(221 83% 53%));
224
+ --fr-accent-text: var(--primary-foreground, hsl(0 0% 100%));
225
+ --fr-danger: var(--destructive, hsl(0 72% 45%));
226
+ --fr-code: var(--inline-code, hsl(9 62% 45%));
227
+ --fr-code-bg: var(--inline-code-bg, hsl(0 0% 95.5%));
228
+ --fr-radius: 1rem;
229
+ --fr-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
230
+ --fr-font-size: 15px;
231
+
232
+ display: flex;
233
+ flex-direction: column;
234
+ min-height: 0;
235
+ height: 100%;
236
+ /*
237
+ The platform's `@container/chatpane` context. The vendored components
238
+ ship rules against this container — the summary card's gutter, the
239
+ composer's narrow-width folds — and every one of them was inert here
240
+ because nothing declared the container. Declared on `.fr-chat` because
241
+ that IS the pane the platform measures: it narrows when the side pane
242
+ opens, which is exactly what those rules respond to.
243
+ */
244
+ container: chatpane / inline-size;
245
+ /* The summary card is `absolute inset-y-0 right-0`, so it needs a
246
+ positioning context. Without one it escapes to the nearest positioned
247
+ ancestor — the host's page — and lands somewhere nobody chose. */
248
+ position: relative;
249
+ /* The platform's inner column carries `overflow-x-hidden`: a wide code
250
+ block or table scrolls INSIDE its message, never the conversation. */
251
+ overflow-x: hidden;
252
+ background: var(--fr-bg);
253
+ color: var(--fr-text);
254
+ font-family: var(--fr-font);
255
+ font-size: var(--fr-font-size);
256
+ line-height: 1.6;
257
+ }
258
+
259
+ /*
260
+ Dark mode lives in `platform.css`, keyed off the `dark` class that
261
+ `<FronteraChat>` puts on this element — the same mechanism the platform uses,
262
+ so every `dark:` utility inside the copied components switches with it.
263
+
264
+ There is deliberately no `prefers-color-scheme` block here any more. It was a
265
+ SECOND switch on a second palette, and the two could not agree; the component
266
+ reads the OS itself and sets the class, which keeps one decision in one place
267
+ and lets a host override it with the `dark` prop.
268
+ */
269
+
270
+ .fr-chat *,
271
+ .fr-chat *::before,
272
+ .fr-chat *::after {
273
+ box-sizing: border-box;
274
+ }
275
+
276
+ /*
277
+ The composer on an empty conversation.
278
+
279
+ Sits directly under the greeting instead of at the bottom edge. `max-width`
280
+ matches the thread column so the input is the same width it will be once the
281
+ conversation starts — otherwise sending the first message visibly resizes the
282
+ control someone just typed into.
283
+
284
+ The dock's own positioning (absolute, bottom, scrollbar inset) belongs to the
285
+ dock; here the input is ordinary flow content.
286
+ */
287
+ .fr-empty-composer {
288
+ width: 100%;
289
+ max-width: 49rem;
290
+ margin: 20px auto 0;
291
+ }
292
+
293
+ /* ── thread ─────────────────────────────────────────────────────────── */
294
+
295
+ /*
296
+ The platform's TWO-LAYER thread, reproduced exactly:
297
+
298
+ `.fr-thread` is the VIEWPORT — full width, owns the scroll, carries the
299
+ summary-card gutter (`pr-[320px]` lives on the platform's viewport too) and
300
+ nothing else.
301
+
302
+ `.fr-thread-column` is the CONTENT COLUMN — `max-w-view mx-auto`, the
303
+ platform's `gap-6 px-3 pt-6 pb-24 md:gap-2 md:px-4 md:pt-16` plus the
304
+ composer-tracked bottom padding. Putting the paddings on the viewport
305
+ instead produced an asymmetric box (`padding: 64px 320px 166px 0`) that
306
+ matched the platform's numbers but not its geometry.
307
+ */
308
+ .fr-thread {
309
+ flex: 1;
310
+ min-height: 0;
311
+ overflow-y: auto;
312
+ transition: padding-right 150ms ease-out;
313
+ /* The platform viewport's `overscroll-contain`: reaching the end of the
314
+ thread must not start scrolling the host page. */
315
+ overscroll-behavior: contain;
316
+ }
317
+
318
+ .fr-thread-column {
319
+ display: flex;
320
+ flex-direction: column;
321
+ width: 100%;
322
+ /* 49rem — `max-w-view` / `--container-3xl`, centred like `mx-auto`. */
323
+ max-width: 49rem;
324
+ margin-inline: auto;
325
+ /* So the empty state's `margin: auto` can centre vertically. */
326
+ min-height: 100%;
327
+ /* The platform column's `gap-6 md:gap-2`. */
328
+ gap: 24px;
329
+ /*
330
+ Top clears the floating header (`md:pt-16`). Bottom is the composer's
331
+ measured height plus THREAD_TAIL_GAP_PX (64px) — the fallback covers the
332
+ first frame, before the observer has measured.
333
+ */
334
+ padding: 64px 12px calc(var(--fr-composer-h, 96px) + 64px);
335
+ }
336
+ @media (min-width: 768px) {
337
+ .fr-thread-column {
338
+ gap: 8px;
339
+ padding-inline: 16px;
340
+ }
341
+ }
342
+
343
+ /* Burst spacing: consecutive bubbles from the person pull together, the
344
+ platform's `-mt-5 md:-mt-1` on the message wrapper — net ~4px against the
345
+ thread gap at either width. */
346
+ .fr-turn-user + .fr-turn-user {
347
+ margin-top: -20px;
348
+ }
349
+ @media (min-width: 768px) {
350
+ .fr-turn-user + .fr-turn-user {
351
+ margin-top: -4px;
352
+ }
353
+ }
354
+
355
+ .fr-empty {
356
+ margin: auto;
357
+ }
358
+
359
+ /*
360
+ Room for the floating summary card — the platform's
361
+ `md:@max-[1520px]/chatpane:pr-[320px]` on both the thread and the composer.
362
+ On a wide pane the card overlays empty gutter and costs nothing; under
363
+ 1520px the message column and the composer both step aside, or the card
364
+ sits on top of the conversation it is summarising.
365
+ */
366
+ @container chatpane (max-width: 1520px) and (min-width: 768px) {
367
+ .fr-thread[data-reserve-gutter],
368
+ .fr-composer-dock[data-reserve-gutter] {
369
+ padding-right: 320px;
370
+ }
371
+ }
372
+
373
+ /* One turn — a plain block inside the column, like the platform's
374
+ `.thread-message` wrapper. Width, inset and centring all belong to
375
+ `.fr-thread-column` now; a turn only stacks its own parts. */
376
+ .fr-turn {
377
+ display: flex;
378
+ flex-direction: column;
379
+ gap: 6px;
380
+ width: 100%;
381
+ }
382
+
383
+ /* The user's turn is a bubble; the agent's is not. An agent reply can carry
384
+ headings, code and tables, and boxing that reads as cramped.
385
+ The turn keeps the column's full width and aligns its BUBBLE right —
386
+ `align-self: flex-end` would instead throw the whole column to the edge of
387
+ the screen, away from the reply. */
388
+ .fr-turn-user {
389
+ align-items: flex-end;
390
+ }
391
+
392
+ .fr-bubble {
393
+ background: var(--fr-bubble);
394
+ color: var(--fr-text);
395
+ /* 12px / 8px and a full 1rem radius — the platform's own bubble metrics. */
396
+ padding: 8px 12px;
397
+ border-radius: var(--fr-radius);
398
+ /* One step under the answer: a prompt is something you wrote and glance back
399
+ at, not the thing you came back to read. */
400
+ font-size: 0.933em;
401
+ max-width: 100%;
402
+ overflow-wrap: anywhere;
403
+ }
404
+
405
+ .fr-author {
406
+ display: flex;
407
+ align-items: center;
408
+ gap: 6px;
409
+ font-size: 12px;
410
+ font-weight: 600;
411
+ color: var(--fr-muted);
412
+ letter-spacing: 0.01em;
413
+ }
414
+
415
+ /*
416
+ The composer, floating over the foot of the scroll area.
417
+
418
+ Absolute so the thread's scrollbar spans the pane's full height and reaches
419
+ the true bottom, the way the platform's does — a composer in the flow cuts
420
+ the scroll region short and leaves the thumb ending above it.
421
+ */
422
+ /*
423
+ Content fading out under the composer.
424
+
425
+ A hard cut at the composer's top edge reads as content being clipped; the
426
+ gradient says it continues beneath. Matches the platform: 4rem tall, from the
427
+ chat surface colour to transparent.
428
+ */
429
+ .fr-scroll-fade {
430
+ position: absolute;
431
+ left: 0;
432
+ /* ABOVE the composer, not behind it — the platform moves its fade with the
433
+ composer's measured height for exactly this reason. Anchored at 0 it sat
434
+ entirely under the opaque dock and did nothing, so content met the
435
+ composer at a hard edge. */
436
+ bottom: var(--fr-composer-h, 96px);
437
+ z-index: 10;
438
+ height: 4rem;
439
+ pointer-events: none;
440
+ background: linear-gradient(to top, var(--fr-bg), transparent);
441
+ }
442
+
443
+ /* Scroll-to-bottom, the platform's own affordance: a raised circle floating
444
+ just above the composer, centred on the column (nudged by half the
445
+ scrollbar). Shown only once the reader has scrolled away from the tail. */
446
+ .fr-scroll-down {
447
+ position: absolute;
448
+ bottom: calc(var(--fr-composer-h, 96px) + 16px);
449
+ transform: translateX(-50%);
450
+ z-index: 30;
451
+ display: inline-flex;
452
+ align-items: center;
453
+ justify-content: center;
454
+ width: 36px;
455
+ height: 36px;
456
+ border-radius: 999px;
457
+ border: 1px solid var(--fr-border);
458
+ background: var(--surface-raised, var(--fr-surface));
459
+ color: var(--fr-text);
460
+ cursor: pointer;
461
+ box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);
462
+ }
463
+ .fr-scroll-down:hover {
464
+ background: var(--surface-raised-hover, var(--fr-bubble));
465
+ }
466
+
467
+ .fr-composer-dock {
468
+ position: absolute;
469
+ left: 0;
470
+ bottom: 0;
471
+ z-index: 20;
472
+ transition: padding-right 150ms ease-out;
473
+ /*
474
+ OPAQUE, and that is the point: the composer floats over the scroll area, so
475
+ without a background the conversation scrolls visibly through it.
476
+ */
477
+ background: var(--fr-bg);
478
+ /*
479
+ Horizontal only. The composer carries its own bottom padding —
480
+ `pb-4 max-md:pb-[max(1rem,env(safe-area-inset-bottom))]` — which also
481
+ clears the home indicator on a phone. Adding any here double-pads it and
482
+ breaks the safe-area handling.
483
+ */
484
+ padding: 0 20px;
485
+ }
486
+
487
+ /* ── header ─────────────────────────────────────────────────────────── */
488
+
489
+ /*
490
+ Sits above the thread, full width, and stays put while the conversation
491
+ scrolls under it — the title and the summary control are about the whole
492
+ conversation, not the part currently in view.
493
+ */
494
+ /*
495
+ A transparent overlay floating above the conversation, exactly the
496
+ platform's (`session-view.tsx`): `absolute inset-x-0 top-0 z-20 h-10` with
497
+ `transition-colors duration-200`. It only turns solid — background, blur and
498
+ a bottom border — when the centred message column would slide under the
499
+ title or the actions (see the clash measurement in `header.tsx`); on a wide
500
+ view the column clears both sides and the header stays invisible.
501
+ */
502
+ .fr-header {
503
+ position: absolute;
504
+ inset-inline: 0;
505
+ top: 0;
506
+ z-index: 20;
507
+ height: 40px;
508
+ display: flex;
509
+ align-items: center;
510
+ gap: 2px;
511
+ padding: 0 12px;
512
+ border-bottom: 1px solid transparent;
513
+ background: transparent;
514
+ transition: background-color 200ms, border-color 200ms;
515
+ }
516
+ .fr-header[data-solid] {
517
+ border-bottom-color: var(--border-secondary, var(--fr-border));
518
+ background: color-mix(in srgb, var(--fr-bg) 95%, transparent);
519
+ backdrop-filter: blur(4px);
520
+ }
521
+
522
+ /*
523
+ The phone's bar: solid, in the flow, and tall enough to tap.
524
+
525
+ The floating header works on a desktop because the thread has gutters for it
526
+ to float over. At 360px it sat directly on the first reply, and the pinned
527
+ pane toggle sat on the conversation. The platform's `MobileChatHeader` is
528
+ `sticky`, `h-13` and opaque for exactly that reason; this matches it.
529
+ */
530
+ .fr-header-mobile {
531
+ position: sticky;
532
+ inset-inline: auto;
533
+ height: auto;
534
+ min-height: 52px;
535
+ /* Under the notch on a fullscreen phone browser, not behind it. */
536
+ padding-top: env(safe-area-inset-top);
537
+ padding-inline: 12px;
538
+ gap: 8px;
539
+ }
540
+ /*
541
+ The host's session control, sized like the ones opposite it.
542
+
543
+ The slot takes whatever the host passes — a bare `<button>`, their own
544
+ icon-button component, an anchor — so the 40px tap target lives on the
545
+ wrapper rather than depending on how they styled the thing inside.
546
+ */
547
+ .fr-header-mobile-lead {
548
+ display: inline-flex;
549
+ flex-shrink: 0;
550
+ align-items: center;
551
+ justify-content: center;
552
+ min-width: 40px;
553
+ min-height: 40px;
554
+ color: var(--fr-muted);
555
+ }
556
+ .fr-header-mobile-lead > * {
557
+ /* A host button brings its own background; inside this bar it should read
558
+ as part of the row, the way the platform's ghost hamburger does. */
559
+ background: none;
560
+ border: none;
561
+ color: inherit;
562
+ cursor: pointer;
563
+ }
564
+
565
+ /* The title takes the row; the controls keep their size at its expense. */
566
+ .fr-header-mobile .fr-header-title {
567
+ flex: 1;
568
+ }
569
+ /*
570
+ The Files control, squared off rather than a pill.
571
+
572
+ The platform marks it pressed while its sheet is up — the same "the surface I
573
+ opened is open" mark the summary toggle wears — because the sheet docks below
574
+ this bar, so the button that opened it stays on screen. Left at rest, the bar
575
+ reads as if nothing were showing and the sheet as if it arrived on its own.
576
+ */
577
+ .fr-header-files {
578
+ display: inline-flex;
579
+ align-items: center;
580
+ justify-content: center;
581
+ /* 40px: a tap target smaller than a fingertip is a miss. */
582
+ width: 40px;
583
+ height: 40px;
584
+ border: none;
585
+ border-radius: calc(var(--fr-radius, 8px) - 2px);
586
+ background: transparent;
587
+ color: var(--fr-muted);
588
+ cursor: pointer;
589
+ }
590
+ .fr-header-files:hover {
591
+ color: var(--fr-text);
592
+ }
593
+ /* The summary shares the Files control's box but stays a pill, matching the
594
+ platform's round ghost button beside its squared-off Files mark. */
595
+ .fr-header-summary-button {
596
+ border-radius: 999px;
597
+ }
598
+ .fr-header-files[data-open] {
599
+ background: var(--surface-raised-active, color-mix(in srgb, var(--fr-text) 10%, transparent));
600
+ color: var(--fr-text);
601
+ }
602
+
603
+ .fr-header-mobile-actions {
604
+ display: flex;
605
+ flex-shrink: 0;
606
+ align-items: center;
607
+ gap: 2px;
608
+ }
609
+ /* No pinned overlay at this width, so the header keeps its own inset — the
610
+ desktop rules above reserve 44px for a control that is now in the row. */
611
+ @media (max-width: 767px) {
612
+ .fr-shell:has(.fr-header-actions) .fr-header {
613
+ padding-right: 12px;
614
+ }
615
+ }
616
+
617
+ /* The platform's always-on soften at the top of the thread:
618
+ `h-12 bg-gradient-to-b from-surface-chat/50 to-transparent z-10`. */
619
+ .fr-top-fade {
620
+ position: absolute;
621
+ top: 0;
622
+ left: 0;
623
+ right: 0;
624
+ z-index: 10;
625
+ height: 3rem;
626
+ pointer-events: none;
627
+ background: linear-gradient(
628
+ to bottom,
629
+ color-mix(in srgb, var(--fr-bg) 50%, transparent),
630
+ transparent
631
+ );
632
+ }
633
+
634
+ /* Avatar + title hug each other so the group's right edge can be measured
635
+ against the message column for clash detection (see header.tsx). */
636
+ .fr-header-title-group {
637
+ display: flex;
638
+ min-width: 0;
639
+ align-items: center;
640
+ gap: 8px;
641
+ }
642
+
643
+ /* The pinned actions overlay (`.fr-header-actions`) occupies the header's
644
+ right edge; the in-header summary control stops short of it — but only
645
+ while the pane is SHUT. Open, the pinned toggle sits over the pane, the
646
+ chat header gets its full width back, and the summary control returns
647
+ flush to the column's edge exactly as the platform's does. */
648
+ .fr-shell:has(.fr-header-actions) .fr-header {
649
+ padding-right: 44px;
650
+ }
651
+ .fr-shell:has(.fr-side-pane:not([data-panel-size="0.0"])) .fr-header {
652
+ padding-right: 12px;
653
+ }
654
+
655
+ .fr-header-title {
656
+ font-size: 14px;
657
+ font-weight: 550;
658
+ color: var(--fr-text);
659
+ overflow: hidden;
660
+ text-overflow: ellipsis;
661
+ white-space: nowrap;
662
+ min-width: 0;
663
+ }
664
+
665
+ /* The summary, at the trailing edge of the CONVERSATION column — it
666
+ summarises the session, so it travels with it. */
667
+ .fr-header-summary {
668
+ margin-left: auto;
669
+ flex-shrink: 0;
670
+ }
671
+
672
+ /*
673
+ The pane's own toggle, pinned across the whole surface.
674
+
675
+ `inset-inline: 0` with `justify-content: flex-end` puts these at the right
676
+ edge of everything on screen — the conversation when the pane is shut, the
677
+ pane when it is open. The controls therefore never move; the layout animates
678
+ underneath them, which is what makes the handover look seamless instead of
679
+ the buttons sliding across.
680
+
681
+ The row height and inset match the header's, so it lands exactly where a
682
+ trailing header button would have sat.
683
+ */
684
+ .fr-header-actions {
685
+ position: absolute;
686
+ inset-inline: 0;
687
+ top: 0;
688
+ z-index: 30;
689
+ display: flex;
690
+ align-items: center;
691
+ justify-content: flex-end;
692
+ gap: 2px;
693
+ height: 40px;
694
+ padding-inline: 12px;
695
+ /* Click-through: the conversation and the pane's tab strip sit underneath
696
+ this row and must stay reachable. Only the buttons take the pointer. */
697
+ pointer-events: none;
698
+ }
699
+ .fr-header-actions > * {
700
+ pointer-events: auto;
701
+ }
702
+
703
+ .fr-body {
704
+ display: flex;
705
+ flex-direction: column;
706
+ gap: 10px;
707
+ overflow-wrap: anywhere;
708
+ }
709
+
710
+ .fr-text {
711
+ margin: 0;
712
+ white-space: pre-wrap;
713
+ }
714
+
715
+ /* ── markdown ───────────────────────────────────────────────────────── */
716
+
717
+ .fr-markdown > *:first-child { margin-top: 0; }
718
+ .fr-markdown > *:last-child { margin-bottom: 0; }
719
+ .fr-markdown p { margin: 0 0 0.75em; }
720
+ .fr-markdown h1, .fr-markdown h2, .fr-markdown h3 {
721
+ margin: 1.2em 0 0.5em;
722
+ line-height: 1.3;
723
+ font-weight: 650;
724
+ }
725
+ .fr-markdown h1 { font-size: 1.4em; }
726
+ .fr-markdown h2 { font-size: 1.2em; }
727
+ .fr-markdown h3 { font-size: 1.05em; }
728
+ .fr-markdown ul, .fr-markdown ol { margin: 0 0 0.75em; padding-left: 1.4em; }
729
+ .fr-markdown li { margin: 0.2em 0; }
730
+ .fr-markdown a { color: var(--fr-accent); }
731
+ .fr-markdown code {
732
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
733
+ font-size: 0.9em;
734
+ /* A warm code accent on a fill one step off the surface, so the chip still
735
+ reads inside a raised bubble. */
736
+ color: var(--fr-code);
737
+ background: var(--fr-code-bg);
738
+ border: 1px solid var(--fr-border);
739
+ border-radius: 5px;
740
+ padding: 0.1em 0.35em;
741
+ }
742
+ .fr-markdown pre {
743
+ background: var(--fr-surface);
744
+ border: 1px solid var(--fr-border);
745
+ border-radius: 10px;
746
+ padding: 12px 14px;
747
+ overflow-x: auto;
748
+ margin: 0 0 0.75em;
749
+ }
750
+ .fr-markdown pre code { background: none; border: 0; padding: 0; color: inherit; }
751
+ .fr-markdown blockquote {
752
+ margin: 0 0 0.75em;
753
+ padding-left: 12px;
754
+ border-left: 3px solid var(--fr-border);
755
+ color: var(--fr-muted);
756
+ }
757
+ /* Wide tables scroll inside the message rather than stretching the layout. */
758
+ .fr-markdown table { display: block; overflow-x: auto; border-collapse: collapse; }
759
+ .fr-markdown th, .fr-markdown td {
760
+ border: 1px solid var(--fr-border);
761
+ padding: 6px 10px;
762
+ text-align: left;
763
+ }
764
+
765
+ /* ── reasoning ──────────────────────────────────────────────────────── */
766
+
767
+ .fr-reasoning {
768
+ border: 1px solid var(--fr-border);
769
+ border-radius: 10px;
770
+ padding: 8px 12px;
771
+ color: var(--fr-muted);
772
+ font-size: 0.93em;
773
+ }
774
+ .fr-reasoning summary { cursor: pointer; user-select: none; }
775
+ .fr-reasoning-body { margin-top: 8px; white-space: pre-wrap; }
776
+
777
+ /* ── tool activity ──────────────────────────────────────────────────── */
778
+
779
+ .fr-tool {
780
+ display: inline-flex;
781
+ flex-direction: column;
782
+ gap: 6px;
783
+ align-self: flex-start;
784
+ border: 1px solid var(--fr-border);
785
+ border-radius: 999px;
786
+ padding: 5px 12px;
787
+ font-size: 0.88em;
788
+ color: var(--fr-muted);
789
+ background: var(--fr-surface);
790
+ }
791
+ .fr-tool.is-running { border-color: var(--fr-accent); color: var(--fr-accent); }
792
+ .fr-tool.is-failed { border-color: var(--fr-danger); color: var(--fr-danger); }
793
+ .fr-tool-head { display: flex; align-items: center; gap: 7px; }
794
+ .fr-tool-name { font-weight: 500; }
795
+ .fr-tool-error { color: var(--fr-danger); font-size: 0.95em; }
796
+
797
+ /* ── attachments and artifacts ──────────────────────────────────────── */
798
+
799
+ .fr-image { max-width: 100%; border-radius: 10px; }
800
+
801
+ .fr-file,
802
+ .fr-artifact {
803
+ display: inline-flex;
804
+ align-items: center;
805
+ gap: 8px;
806
+ align-self: flex-start;
807
+ border: 1px solid var(--fr-border);
808
+ border-radius: 10px;
809
+ background: var(--fr-surface);
810
+ color: inherit;
811
+ font: inherit;
812
+ padding: 8px 12px;
813
+ text-decoration: none;
814
+ cursor: pointer;
815
+ }
816
+ .fr-artifact:disabled { cursor: default; }
817
+ .fr-file:hover,
818
+ .fr-artifact:not(:disabled):hover { border-color: var(--fr-accent); }
819
+
820
+ /* ── typing ─────────────────────────────────────────────────────────── */
821
+
822
+ .fr-typing { display: flex; gap: 4px; padding: 6px 0; }
823
+ .fr-typing span {
824
+ width: 6px;
825
+ height: 6px;
826
+ border-radius: 50%;
827
+ background: var(--fr-muted);
828
+ animation: fr-bounce 1.2s infinite ease-in-out;
829
+ }
830
+ .fr-typing span:nth-child(2) { animation-delay: 0.15s; }
831
+ .fr-typing span:nth-child(3) { animation-delay: 0.3s; }
832
+
833
+ @keyframes fr-bounce {
834
+ 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
835
+ 30% { opacity: 1; transform: translateY(-3px); }
836
+ }
837
+
838
+ .fr-icon { width: 15px; height: 15px; flex: none; }
839
+ .fr-spin { animation: fr-rotate 0.9s linear infinite; }
840
+
841
+ @keyframes fr-rotate { to { transform: rotate(360deg); } }
842
+
843
+ /* Respect a stated preference for less motion; the states remain legible
844
+ without it. */
845
+ @media (prefers-reduced-motion: reduce) {
846
+ .fr-typing span, .fr-spin { animation: none; }
847
+ }
848
+
849
+ /* ── errors ─────────────────────────────────────────────────────────── */
850
+
851
+ .fr-error {
852
+ border: 1px solid var(--fr-danger);
853
+ border-radius: 10px;
854
+ padding: 10px 12px;
855
+ color: var(--fr-danger);
856
+ font-size: 0.93em;
857
+ }
858
+
859
+ /* ── composer ───────────────────────────────────────────────────────── */
860
+
861
+ /* The composer sits under the conversation, in the same column — an input
862
+ spanning a wide screen edge to edge does not read as part of the thread
863
+ above it. */
864
+ .fr-composer {
865
+ display: flex;
866
+ align-items: flex-end;
867
+ gap: 8px;
868
+ width: 100%;
869
+ max-width: 46rem;
870
+ margin: 0 auto;
871
+ padding: 12px 20px 16px;
872
+ background: var(--fr-bg);
873
+ }
874
+
875
+ .fr-composer-input {
876
+ flex: 1;
877
+ resize: none;
878
+ /* Thin, and only when the text actually overflows: a full scrollbar with
879
+ arrows inside a one-line field reads as a broken control. */
880
+ scrollbar-width: thin;
881
+ font: inherit;
882
+ color: inherit;
883
+ background: var(--fr-surface);
884
+ border: 1px solid var(--fr-border);
885
+ border-radius: var(--fr-radius);
886
+ padding: 10px 14px;
887
+ max-height: 200px;
888
+ overflow-y: auto;
889
+ }
890
+ .fr-composer-input:focus {
891
+ outline: none;
892
+ border-color: var(--fr-accent);
893
+ }
894
+ .fr-composer-input::placeholder { color: var(--fr-muted); }
895
+
896
+ .fr-btn {
897
+ display: inline-flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ width: 38px;
901
+ height: 38px;
902
+ border-radius: 10px;
903
+ border: 1px solid transparent;
904
+ background: var(--fr-accent);
905
+ color: var(--fr-accent-text);
906
+ cursor: pointer;
907
+ flex: none;
908
+ }
909
+ .fr-btn:disabled { opacity: 0.45; cursor: not-allowed; }
910
+ .fr-btn-stop { background: var(--fr-surface); color: var(--fr-danger); border-color: var(--fr-border); }
911
+ .fr-btn:focus-visible { outline: 2px solid var(--fr-accent); outline-offset: 2px; }
912
+
913
+ /* A delegated task is work happening on the reader's behalf, not a mechanism
914
+ they invoked — given a little more room than a plain tool chip. */
915
+ .fr-tool.is-delegation {
916
+ border-radius: 10px;
917
+ padding: 7px 12px;
918
+ }
919
+ .fr-tool-task {
920
+ color: var(--fr-text);
921
+ opacity: 0.85;
922
+ }
923
+ .fr-tool-head { flex-wrap: wrap; }
924
+
925
+ /* ── new session ────────────────────────────────────────────────────── */
926
+
927
+ /* The empty state is centred in the thread, not pinned to the top: before a
928
+ conversation exists there is no reading order to respect, and a greeting at
929
+ the top of an empty column reads as a page that failed to load. */
930
+ .fr-empty {
931
+ /* Centred in the column, which already carries the width and inset. */
932
+ margin: auto;
933
+ width: 100%;
934
+ }
935
+
936
+ .fr-new-session {
937
+ display: flex;
938
+ flex-direction: column;
939
+ gap: 18px;
940
+ align-items: center;
941
+ text-align: center;
942
+ }
943
+
944
+ .fr-new-session-title {
945
+ margin: 0;
946
+ font-size: 1.55em;
947
+ font-weight: 650;
948
+ line-height: 1.25;
949
+ color: var(--fr-text);
950
+ }
951
+
952
+ /* Two columns, because four cards in a row become unreadably narrow and a
953
+ single column pushes the composer off a short screen. */
954
+ .fr-shortcuts {
955
+ display: grid;
956
+ grid-template-columns: repeat(2, minmax(0, 1fr));
957
+ gap: 10px;
958
+ width: 100%;
959
+ }
960
+
961
+ .fr-shortcut {
962
+ display: flex;
963
+ flex-direction: column;
964
+ gap: 3px;
965
+ align-items: flex-start;
966
+ text-align: left;
967
+ padding: 12px 14px;
968
+ border-radius: var(--fr-radius);
969
+ border: 1px solid var(--fr-border);
970
+ background: var(--fr-surface);
971
+ color: var(--fr-text);
972
+ font: inherit;
973
+ cursor: pointer;
974
+ }
975
+ .fr-shortcut:hover { border-color: var(--fr-accent); }
976
+ .fr-shortcut:focus-visible { outline: 2px solid var(--fr-accent); outline-offset: 2px; }
977
+
978
+ .fr-shortcut-label { font-weight: 550; }
979
+ .fr-shortcut-desc { color: var(--fr-muted); font-size: 0.9em; }
980
+
981
+ @media (max-width: 640px) {
982
+ .fr-shortcuts { grid-template-columns: 1fr; }
983
+ }
984
+
985
+ /* ── pending decisions ───────────────────────────────────────────────── */
986
+
987
+ /*
988
+ Nothing here any more, on purpose.
989
+
990
+ The approval and connect rail is styled with the platform's own Tailwind
991
+ classes rather than `fr-*` rules, because it sits directly on the copied
992
+ composer: a hand-written near-match reads as a bug in the product, not as a
993
+ difference in ours. Tailwind scans this package's `src`, so those classes
994
+ compile into `dist/platform.css` alongside the vendored components'.
995
+ */
996
+