@sphoro/bugcatch-web-sdk 0.16.0 → 0.18.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 (52) hide show
  1. package/README.md +92 -5
  2. package/dist/bugcatch-call.es.js +586 -0
  3. package/dist/bugcatch-call.es.js.map +1 -0
  4. package/dist/bugcatch-call.umd.js +2 -0
  5. package/dist/bugcatch-call.umd.js.map +1 -0
  6. package/dist/bugcatch-core.es.js +3214 -0
  7. package/dist/bugcatch-core.es.js.map +1 -0
  8. package/dist/bugcatch-panel.es.js +2402 -0
  9. package/dist/bugcatch-panel.es.js.map +1 -0
  10. package/dist/bugcatch-panel.umd.js +1240 -0
  11. package/dist/bugcatch-panel.umd.js.map +1 -0
  12. package/dist/bugcatch.es.js +2 -5522
  13. package/dist/bugcatch.es.js.map +1 -1
  14. package/dist/bugcatch.standalone.umd.js +1536 -0
  15. package/dist/bugcatch.standalone.umd.js.map +1 -0
  16. package/dist/bugcatch.umd.js +89 -1183
  17. package/dist/bugcatch.umd.js.map +1 -1
  18. package/package.json +6 -6
  19. package/src/agent-issues.js +30 -0
  20. package/src/announcements.js +147 -0
  21. package/src/busy.js +18 -0
  22. package/src/call.js +7 -0
  23. package/src/capture.js +9 -0
  24. package/src/chunks.js +56 -0
  25. package/src/gesture.js +33 -0
  26. package/src/icons.js +18 -0
  27. package/src/identity.js +14 -9
  28. package/src/incoming-call.js +8 -30
  29. package/src/index.d.ts +50 -0
  30. package/src/index.js +170 -37
  31. package/src/lazy.js +23 -0
  32. package/src/notice.js +68 -0
  33. package/src/panel/call.js +242 -0
  34. package/src/panel/compose.js +416 -0
  35. package/src/panel/composer.js +235 -0
  36. package/src/panel/icons.js +34 -0
  37. package/src/panel/index.js +251 -0
  38. package/src/panel/known.js +130 -0
  39. package/src/panel/messages.js +176 -0
  40. package/src/panel/notices.js +34 -0
  41. package/src/panel/picks.js +61 -0
  42. package/src/panel/refs.js +321 -0
  43. package/src/panel/render.js +124 -0
  44. package/src/panel/signin.js +228 -0
  45. package/src/panel/styles.js +960 -0
  46. package/src/panel/thread.js +384 -0
  47. package/src/session.js +8 -3
  48. package/src/umd/call.js +6 -0
  49. package/src/umd/lazy.js +98 -0
  50. package/src/umd/panel.js +6 -0
  51. package/src/updates.js +20 -7
  52. package/src/widget.js +243 -3164
@@ -0,0 +1,960 @@
1
+ // The panel's stylesheet: everything the dialog draws, from the form to the
2
+ // thread to the call bar.
3
+ //
4
+ // Kept apart from the launcher's (widget.js) because it is most of the
5
+ // widget's CSS and none of it is needed until the panel is first opened. It is
6
+ // added to the same shadow root the first time that happens, after the
7
+ // launcher's sheet — so the custom properties, the theme and the base rules
8
+ // declared there are already in force, and anything here that could collide
9
+ // with them comes later in the cascade, where it always was.
10
+
11
+ export const PANEL_CSS = `
12
+ @keyframes bc-fade-in { from { opacity: 0 } to { opacity: 1 } }
13
+ @keyframes bc-fade-out { to { opacity: 0 } }
14
+ @keyframes bc-panel-in { from { opacity: 0; transform: translateY(14px) scale(.965) } to { opacity: 1; transform: none } }
15
+ @keyframes bc-panel-out { to { opacity: 0; transform: translateY(10px) scale(.975) } }
16
+ @keyframes bc-sheet-in { from { transform: translateY(100%) } to { transform: none } }
17
+ @keyframes bc-sheet-out { to { transform: translateY(100%) } }
18
+ @keyframes bc-rise { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }
19
+ @keyframes bc-pulse { 0%, 100% { opacity: 1; transform: scale(1) } 50% { opacity: .3; transform: scale(.78) } }
20
+ @keyframes bc-spin { to { transform: rotate(360deg) } }
21
+ @keyframes bc-pop { 0% { transform: scale(.5); opacity: 0 } 65% { transform: scale(1.1) } 100% { transform: scale(1); opacity: 1 } }
22
+ @keyframes bc-nudge { 25% { transform: translateX(-3px) } 75% { transform: translateX(3px) } }
23
+ @keyframes bc-typing { 0%, 60%, 100% { transform: translateY(0); opacity: .45 } 30% { transform: translateY(-3px); opacity: 1 } }
24
+ @keyframes bc-shimmer { from { background-position: 200% 0 } to { background-position: -200% 0 } }
25
+
26
+ /* ---------- scrim + panel ---------- */
27
+ .scrim {
28
+ position: fixed; inset: 0; z-index: 2147483001;
29
+ background: rgba(10,12,16,.32);
30
+ animation: bc-fade-in 200ms ease both;
31
+ }
32
+ .scrim.closing { animation: bc-fade-out 170ms ease both; }
33
+
34
+ /* The panel belongs to the launcher, so it opens out of that corner rather
35
+ than in the middle of the screen. --bc-dock-h is measured at open time
36
+ because the dock's height depends on whether the launcher is showing.
37
+ A column: the header and footer stay put and only the middle scrolls. */
38
+ .panel {
39
+ position: fixed;
40
+ display: flex; flex-direction: column;
41
+ width: calc(100vw - 40px); max-width: 420px;
42
+ max-height: calc(100vh - var(--bc-dock-h, 84px) - 52px);
43
+ overflow: hidden;
44
+ background: var(--bc-bg); color: var(--bc-fg);
45
+ border-radius: var(--bc-r-xl); box-shadow: var(--bc-shadow);
46
+ border: 1px solid color-mix(in srgb, var(--bc-border) 70%, transparent);
47
+ animation: bc-panel-in 340ms var(--bc-ease) both;
48
+ }
49
+ /* A conversation wants a height of its own, so the thread fills the panel
50
+ rather than shrinking to its first two messages. */
51
+ .panel.is-chat { height: 640px; }
52
+ .scrim.at-right .panel { right: 20px; transform-origin: bottom right; }
53
+ .scrim.at-left .panel { left: 20px; transform-origin: bottom left; }
54
+ .scrim.at-bottom .panel { bottom: calc(20px + var(--bc-dock-h, 84px) + 12px); }
55
+ .scrim.at-top .panel { top: calc(20px + var(--bc-dock-h, 84px) + 12px); transform-origin: top right; }
56
+ .scrim.closing .panel { animation: bc-panel-out 170ms ease both; }
57
+
58
+ /* ---------- header ---------- */
59
+ .head {
60
+ flex: none; position: relative;
61
+ display: flex; align-items: center; gap: 12px;
62
+ padding: 16px 14px 14px var(--bc-pad);
63
+ background: linear-gradient(180deg, var(--bc-accent-soft), var(--bc-bg));
64
+ border-bottom: 1px solid var(--bc-border);
65
+ }
66
+ .head .mark, .avatar {
67
+ flex: none; display: grid; place-items: center; position: relative;
68
+ width: 38px; height: 38px; border-radius: 50%;
69
+ background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));
70
+ color: var(--bc-on-accent); font-size: 19px;
71
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--bc-accent) 30%, transparent);
72
+ }
73
+ /* The little "online" dot says someone — or something — is on the other end. */
74
+ .head .mark::after {
75
+ content: ''; position: absolute; right: -1px; bottom: -1px;
76
+ width: 11px; height: 11px; border-radius: 50%;
77
+ background: #22c55e; border: 2px solid var(--bc-bg);
78
+ }
79
+ .head .titles { flex: 1; min-width: 0; }
80
+ .head h2 {
81
+ margin: 0 0 2px; font-size: var(--bc-fs-lg); font-weight: 680; letter-spacing: -.02em; line-height: 1.25;
82
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
83
+ }
84
+ .head p { margin: 0; font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted); }
85
+ .icon-btn {
86
+ flex: none; display: grid; place-items: center;
87
+ width: 32px; height: 32px; padding: 0; font-size: 16px;
88
+ border: none; border-radius: 10px; background: none; color: var(--bc-muted);
89
+ cursor: pointer; transition: background 160ms, color 160ms, transform 160ms var(--bc-ease);
90
+ }
91
+ .icon-btn:hover { background: color-mix(in srgb, var(--bc-fg) 7%, transparent); color: var(--bc-fg); }
92
+ .icon-btn:active { transform: scale(.92); }
93
+ .icon-btn.back { margin: 0 -6px 0 -8px; }
94
+
95
+ .body {
96
+ flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
97
+ padding: 16px var(--bc-pad) 6px;
98
+ }
99
+
100
+ label { display: block; font-size: var(--bc-fs-sm); font-weight: 620; color: var(--bc-fg); margin-bottom: 7px; }
101
+ label .opt { font-weight: 450; color: var(--bc-faint); }
102
+ input[type=text], input[type=email], textarea {
103
+ width: 100%; padding: 11px 13px; font-size: var(--bc-fs-md); line-height: 1.45;
104
+ color: var(--bc-fg); background: var(--bc-elevated);
105
+ border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);
106
+ transition: border-color 160ms, box-shadow 160ms, background 160ms;
107
+ }
108
+ input[type=text]::placeholder, input[type=email]::placeholder, textarea::placeholder { color: var(--bc-faint); }
109
+ input[type=text]:hover, input[type=email]:hover, textarea:hover { border-color: color-mix(in srgb, var(--bc-fg) 18%, var(--bc-border)); }
110
+ input[type=text]:focus, input[type=email]:focus, textarea:focus {
111
+ outline: none; border-color: var(--bc-accent); box-shadow: var(--bc-ring);
112
+ }
113
+ input.invalid, input.invalid:focus, textarea.invalid {
114
+ border-color: var(--bc-danger); animation: bc-nudge 240ms ease;
115
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--bc-danger) 16%, transparent);
116
+ }
117
+ .field-err {
118
+ display: flex; align-items: center; gap: 6px;
119
+ margin: 7px 0 0; font-size: var(--bc-fs-xs); font-weight: 550; color: var(--bc-danger);
120
+ animation: bc-rise 200ms var(--bc-ease) both;
121
+ }
122
+ .field-err svg { font-size: 13px; flex: none; }
123
+ textarea { min-height: 88px; resize: vertical; }
124
+ .field { margin-bottom: 16px; }
125
+ .field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
126
+ .field-head .count { font-size: var(--bc-fs-xs); color: var(--bc-faint); font-variant-numeric: tabular-nums; }
127
+
128
+ /* The draft line: a quiet note, or a card offering to pick up where they left off. */
129
+ .compose > .note { font-size: var(--bc-fs-xs); color: var(--bc-faint); margin: -4px 0 10px; }
130
+ .compose > .note:empty { display: none; }
131
+ .compose > .note:has(button) {
132
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
133
+ margin: 0 0 14px; padding: 10px 12px; border-radius: var(--bc-r-md);
134
+ background: var(--bc-accent-soft); border: 1px solid var(--bc-accent-line);
135
+ font-size: var(--bc-fs-sm); color: var(--bc-fg);
136
+ }
137
+ .compose > .note button {
138
+ padding: 5px 11px; font: inherit; font-size: var(--bc-fs-xs); font-weight: 620;
139
+ border-radius: 999px; cursor: pointer;
140
+ border: 1px solid var(--bc-accent); background: var(--bc-accent); color: var(--bc-on-accent);
141
+ }
142
+ .compose > .note button[data-discard] { background: transparent; color: var(--bc-muted); border-color: var(--bc-border); }
143
+
144
+ /* ---------- what is already known ----------
145
+ A help surface in front of the form, not a step in it. Everything here is
146
+ dismissible by ignoring it: the reporter can read what is already known and
147
+ leave, tap "this is happening to me too" and leave, or type straight past all
148
+ of it and file the report they came to file. Nothing is ever in their way,
149
+ because a reporter who feels handled rather than helped stops reporting. */
150
+ .known { margin-bottom: 16px; }
151
+ .known .known-h {
152
+ margin: 0 0 8px; font-size: 11px; font-weight: 680; letter-spacing: .06em;
153
+ text-transform: uppercase; color: var(--bc-faint);
154
+ }
155
+ .known .issue {
156
+ display: flex; align-items: center; gap: 10px;
157
+ padding: 10px 12px; margin-bottom: 6px;
158
+ background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);
159
+ animation: bc-rise 240ms var(--bc-ease) both;
160
+ }
161
+ .known .issue.is-joined { border-color: color-mix(in srgb, var(--bc-ok) 55%, var(--bc-border)); }
162
+ .known .issue .who { flex: 1; min-width: 0; }
163
+ .known .issue .t {
164
+ display: block; font-size: 13px; font-weight: 620; line-height: 1.4;
165
+ color: var(--bc-fg); overflow-wrap: anywhere;
166
+ }
167
+ .known .issue .meta {
168
+ display: block; margin-top: 3px; font-size: var(--bc-fs-xs); line-height: 1.45; color: var(--bc-muted);
169
+ }
170
+ .known .issue .join {
171
+ flex: none; padding: 7px 12px; font-size: 12px; font-weight: 620;
172
+ color: var(--bc-accent); background: var(--bc-bg);
173
+ border: 1px solid var(--bc-accent-line); border-radius: 999px; cursor: pointer;
174
+ transition: background 160ms, border-color 160ms;
175
+ }
176
+ .known .issue .join:hover { border-color: var(--bc-accent); background: var(--bc-accent-soft); }
177
+ .known .issue .join:disabled { color: var(--bc-ok); border-color: var(--bc-border); background: var(--bc-bg); cursor: default; opacity: 1; }
178
+ .known .ask { display: flex; gap: 8px; margin: 2px 0 8px; }
179
+ .known .ask input { flex: 1; min-width: 0; }
180
+ .known .ask button { flex: none; }
181
+ .known .err { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-danger); }
182
+
183
+ /* ---------- questions the assistant answers by itself ---------- */
184
+ .picks { margin-bottom: 16px; }
185
+ .picks .picks-h {
186
+ margin: 0 0 8px; font-size: 11px; font-weight: 680; letter-spacing: .06em;
187
+ text-transform: uppercase; color: var(--bc-faint);
188
+ }
189
+ .picks .picks-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px; }
190
+ .picks .pick {
191
+ display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
192
+ padding: 9px 11px; text-align: left; cursor: pointer; font: inherit;
193
+ color: var(--bc-fg); background: var(--bc-subtle);
194
+ border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);
195
+ transition: background 160ms, border-color 160ms, transform 160ms;
196
+ animation: bc-rise 240ms var(--bc-ease) both;
197
+ }
198
+ .picks .pick:hover { border-color: var(--bc-accent-line); background: var(--bc-accent-soft); }
199
+ .picks .pick[aria-pressed="true"] { border-color: var(--bc-accent); background: var(--bc-accent-soft); }
200
+ .picks .pick .t { font-size: 13px; font-weight: 620; line-height: 1.35; overflow-wrap: anywhere; }
201
+ .picks .pick .h { font-size: var(--bc-fs-xs); line-height: 1.4; color: var(--bc-muted); }
202
+ .picks .picks-note {
203
+ display: flex; gap: 6px; align-items: flex-start; margin: 8px 0 0;
204
+ font-size: 12px; line-height: 1.45; color: var(--bc-muted);
205
+ }
206
+ .picks .picks-note svg { flex: none; margin-top: 2px; color: var(--bc-ok); }
207
+
208
+ /* ---------- who is reporting ----------
209
+ A strip above the form rather than a screen in front of it: where signing in
210
+ is optional this is a line the reporter may ignore, and it should read as
211
+ one. Where it is required the composer below is not rendered at all and this
212
+ strip is the whole panel. */
213
+ .whoami {
214
+ display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
215
+ margin-bottom: 16px; padding: 10px 12px;
216
+ font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted);
217
+ background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);
218
+ }
219
+ .whoami.asking { display: block; padding: 14px; }
220
+ .whoami .who { flex: 1; min-width: 0; }
221
+ .whoami .who strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }
222
+ .link {
223
+ padding: 0; font: inherit; font-size: var(--bc-fs-sm); font-weight: 620; line-height: 1.45;
224
+ color: var(--bc-accent); background: none; border: none; cursor: pointer; border-radius: 4px;
225
+ }
226
+ .link:hover { text-decoration: underline; text-underline-offset: 2px; }
227
+ .link:disabled { color: var(--bc-faint); cursor: default; text-decoration: none; }
228
+ .whoami .why { margin: 0 0 10px; font-size: var(--bc-fs-sm); line-height: 1.5; color: var(--bc-muted); }
229
+ .whoami .why strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }
230
+ .whoami .row { display: flex; gap: 8px; align-items: stretch; }
231
+ .whoami .row input { flex: 1; min-width: 0; }
232
+ .whoami .row button { flex: none; }
233
+ .whoami .after { display: flex; gap: 14px; align-items: center; margin-top: 9px; }
234
+ .whoami .err { color: var(--bc-danger); }
235
+ .whoami .code { letter-spacing: .28em; font-variant-numeric: tabular-nums; }
236
+
237
+ /* ---------- which record is this about ----------
238
+ Drawn only after Send has been pressed and only when what they wrote did not
239
+ already answer it, so most people never see this at all. Presented as the
240
+ assistant asking one friendly follow-up, not as a form field that appeared. */
241
+ .refs {
242
+ position: relative;
243
+ margin-bottom: 16px; padding: 14px;
244
+ font-size: var(--bc-fs-sm); line-height: 1.45; color: var(--bc-muted);
245
+ background: var(--bc-accent-soft);
246
+ border: 1px solid var(--bc-accent-line); border-radius: var(--bc-r-lg);
247
+ animation: bc-item-in 280ms var(--bc-ease) both;
248
+ }
249
+ .refs .ask-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 11px; }
250
+ .refs .ask-head .avatar { width: 28px; height: 28px; font-size: 14px; box-shadow: none; }
251
+ .refs .ask-head strong { display: block; font-size: 13.5px; font-weight: 650; color: var(--bc-fg); margin-bottom: 1px; }
252
+ .refs .why { margin: 0; font-size: var(--bc-fs-sm); line-height: 1.5; color: var(--bc-muted); }
253
+ .refs .row { display: flex; gap: 8px; align-items: stretch; }
254
+ .refs .row input { flex: 1; min-width: 0; background: var(--bc-bg); }
255
+ .refs select {
256
+ flex: none; max-width: 45%; padding: 10px 10px; font-size: 13px;
257
+ color: var(--bc-fg); background: var(--bc-bg);
258
+ border: 1px solid var(--bc-border); border-radius: var(--bc-r-md);
259
+ }
260
+ .refs select:focus { outline: none; border-color: var(--bc-accent); box-shadow: var(--bc-ring); }
261
+ .refs .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }
262
+ .refs .chips button {
263
+ padding: 5px 11px; font: inherit; font-size: 12px; font-weight: 600; border-radius: 999px; cursor: pointer;
264
+ color: var(--bc-muted); background: var(--bc-bg); border: 1px solid var(--bc-border);
265
+ transition: background 160ms, color 160ms, border-color 160ms;
266
+ }
267
+ .refs .chips button[aria-pressed="true"] { color: var(--bc-on-accent); background: var(--bc-accent); border-color: var(--bc-accent); }
268
+ .refs .hint { margin: 8px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-muted); }
269
+ .refs .after { display: flex; gap: 12px; align-items: center; margin-top: 9px; }
270
+ .refs .err { color: var(--bc-danger); font-weight: 550; }
271
+ .refs .link { margin-top: 10px; font-size: 12px; color: var(--bc-muted); }
272
+ .refs .link:hover { color: var(--bc-fg); }
273
+ .refs .chips.cands button { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 550; }
274
+ /* "We read your ID" — under the description, while they type. */
275
+ .ref-found {
276
+ display: flex; align-items: center; gap: 6px;
277
+ margin: -6px 0 14px; font-size: 12px; line-height: 1.45; color: var(--bc-muted);
278
+ }
279
+ .ref-found svg { width: 14px; height: 14px; flex: none; color: var(--bc-ok); }
280
+ .ref-found b { font-weight: 600; color: var(--bc-fg); }
281
+
282
+ /* ---------- severity ---------- */
283
+ .seg { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; padding: 3px; background: var(--bc-subtle); border: 1px solid var(--bc-border); border-radius: var(--bc-r-md); }
284
+ .seg button {
285
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
286
+ padding: 8px 4px; font-size: var(--bc-fs-sm); font-weight: 550; line-height: 1;
287
+ color: var(--bc-muted); background: none; border: none; border-radius: 9px;
288
+ cursor: pointer; transition: background 180ms var(--bc-ease), color 180ms, box-shadow 180ms;
289
+ }
290
+ .seg button::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--sev, var(--bc-faint)); opacity: .55; transition: opacity 180ms; }
291
+ .seg button[data-prio="LOW"] { --sev: #94a3b8; }
292
+ .seg button[data-prio="MEDIUM"] { --sev: #3b82f6; }
293
+ .seg button[data-prio="HIGH"] { --sev: #f59e0b; }
294
+ .seg button[data-prio="CRITICAL"] { --sev: #ef4444; }
295
+ .seg button:hover { color: var(--bc-fg); }
296
+ .seg button[aria-pressed=true] {
297
+ background: var(--bc-raised); color: var(--bc-fg); font-weight: 650;
298
+ box-shadow: 0 1px 3px rgba(10,12,16,.14), 0 0 0 1px color-mix(in srgb, var(--bc-border) 60%, transparent);
299
+ }
300
+ .seg button[aria-pressed=true]::before { opacity: 1; }
301
+ .seg button:focus-visible { outline-offset: -2px; }
302
+
303
+ /* ---------- attachments ---------- */
304
+ .tools { display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 8px; }
305
+ .tool {
306
+ display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
307
+ padding: 12px 8px; font-size: 12px; font-weight: 600; line-height: 1;
308
+ color: var(--bc-fg); background: var(--bc-elevated);
309
+ border: 1px dashed color-mix(in srgb, var(--bc-fg) 20%, var(--bc-border)); border-radius: var(--bc-r-md); cursor: pointer;
310
+ transition: border-color 160ms, background 160ms, color 160ms, transform 160ms var(--bc-ease);
311
+ }
312
+ .tool svg { font-size: 19px; color: var(--bc-muted); transition: color 160ms; }
313
+ .tool:hover:not(:disabled) { border-color: var(--bc-accent); border-style: solid; background: var(--bc-accent-soft); transform: translateY(-1px); }
314
+ .tool:hover:not(:disabled) svg { color: var(--bc-accent); }
315
+ .tool:active:not(:disabled) { transform: translateY(0) scale(.98); }
316
+ .tool:disabled { opacity: .45; cursor: not-allowed; }
317
+ .attach-hint { margin: 8px 0 0; font-size: var(--bc-fs-xs); color: var(--bc-faint); }
318
+
319
+ .thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
320
+ .thumbs:empty { display: none; }
321
+ .thumb {
322
+ position: relative; width: 76px; height: 76px; flex: none;
323
+ border-radius: var(--bc-r-md); overflow: hidden;
324
+ border: 1px solid var(--bc-border); background: var(--bc-subtle);
325
+ box-shadow: var(--bc-shadow-sm);
326
+ animation: bc-item-in 300ms var(--bc-ease) both;
327
+ }
328
+ .thumb img, .thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
329
+ .thumb .kind {
330
+ position: absolute; left: 4px; bottom: 4px;
331
+ display: inline-flex; align-items: center; gap: 3px;
332
+ padding: 2px 5px; border-radius: 5px;
333
+ background: rgba(10,12,16,.72); color: #fff; font-size: 9.5px; font-weight: 600; line-height: 1.3;
334
+ }
335
+ .thumb .drop {
336
+ position: absolute; top: 4px; right: 4px;
337
+ width: 20px; height: 20px; font-size: 11px; border-radius: 50%;
338
+ background: rgba(10,12,16,.7); color: #fff; border: none;
339
+ display: grid; place-items: center; cursor: pointer;
340
+ opacity: 0; transition: opacity 160ms, background 160ms;
341
+ }
342
+ .thumb:hover .drop, .thumb:focus-within .drop { opacity: 1; }
343
+ .thumb .drop:hover { background: var(--bc-danger); }
344
+ .thumb .veil {
345
+ position: absolute; inset: 0; display: grid; place-items: center; gap: 6px;
346
+ background: rgba(10,12,16,.62); color: #fff; font-size: 10.5px; font-weight: 600;
347
+ align-content: center;
348
+ }
349
+ .thumb .veil.err { background: color-mix(in srgb, var(--bc-danger) 82%, transparent); }
350
+ .spinner {
351
+ width: 17px; height: 17px; border-radius: 50%;
352
+ border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
353
+ animation: bc-spin 720ms linear infinite;
354
+ }
355
+ .bar { width: 46px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.3); overflow: hidden; }
356
+ .bar i { display: block; height: 100%; width: 0; background: #fff; transition: width 180ms linear; }
357
+ .veil button {
358
+ background: none; border: none; color: #fff; font-size: 14px; padding: 2px;
359
+ cursor: pointer; display: grid; place-items: center;
360
+ }
361
+
362
+ /* ---------- footer ---------- */
363
+ .foot {
364
+ flex: none;
365
+ display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
366
+ padding: 12px var(--bc-pad); border-top: 1px solid var(--bc-border);
367
+ background: var(--bc-bg);
368
+ }
369
+ .foot .spacer { flex: 1; }
370
+ .foot .note { flex-basis: 100%; order: -1; font-size: var(--bc-fs-xs); color: var(--bc-muted); }
371
+ .foot .note:empty { display: none; }
372
+ /* Quiet, but still legible: this sits in someone else's product, so it identifies
373
+ where the report goes without competing with their interface. Quiet is bought
374
+ with colour (--bc-brand-ink sits a step below body text) rather than with a
375
+ blanket opacity — the BUGCATCH half is only about 3px of cap height at this
376
+ size, and dimming it on top of that left it as a smudge in both themes. */
377
+ .powered {
378
+ flex: none; display: flex; align-items: center; justify-content: center; gap: 6px;
379
+ padding: 7px var(--bc-pad) 9px; font-size: 10.5px; color: var(--bc-faint);
380
+ background: var(--bc-subtle); border-top: 1px solid var(--bc-border);
381
+ }
382
+ .brand-credit { display: flex; align-items: center; transition: color 0.15s; border-radius: 4px; }
383
+ .bc-brand { height: 16px; width: auto; display: block; color: var(--bc-brand-ink); }
384
+ .brand-credit:hover .bc-brand { color: var(--bc-fg); }
385
+ button.ghost, button.primary {
386
+ display: inline-flex; align-items: center; justify-content: center; gap: 7px;
387
+ height: 38px; padding: 0 15px; font: inherit; font-size: 13px; font-weight: 620; line-height: 1;
388
+ border-radius: 999px; cursor: pointer; white-space: nowrap;
389
+ transition: transform 160ms var(--bc-ease), filter 160ms, background 160ms, border-color 160ms, color 160ms, box-shadow 160ms;
390
+ }
391
+ button.ghost { background: var(--bc-bg); border: 1px solid var(--bc-border); color: var(--bc-fg); }
392
+ button.ghost:hover:not(:disabled) { background: var(--bc-subtle); border-color: color-mix(in srgb, var(--bc-fg) 20%, var(--bc-border)); }
393
+ button.ghost svg, button.primary svg { font-size: 15px; }
394
+ button.primary {
395
+ padding: 0 18px;
396
+ background: var(--bc-accent); border: 1px solid var(--bc-accent); color: var(--bc-on-accent);
397
+ box-shadow: 0 4px 12px color-mix(in srgb, var(--bc-accent) 30%, transparent);
398
+ }
399
+ button.primary:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.07); }
400
+ button.primary:active:not(:disabled) { transform: none; }
401
+ button:disabled { opacity: .5; cursor: not-allowed; }
402
+ button.primary:disabled { box-shadow: none; }
403
+ button.primary .spinner { border-color: rgba(255,255,255,.4); border-top-color: #fff; width: 14px; height: 14px; }
404
+ button.quiet {
405
+ padding: 0 4px; height: 38px; font: inherit; font-size: 13px; font-weight: 600;
406
+ border: none; background: none; color: var(--bc-muted); cursor: pointer; border-radius: 8px;
407
+ }
408
+ button.quiet:hover { color: var(--bc-accent); }
409
+
410
+ /* ---------- your reports ---------- */
411
+ .history {
412
+ flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
413
+ padding: 8px 12px 14px;
414
+ }
415
+ /* On the conversation screen the inner thread does the scrolling, so this
416
+ wrapper must not — two nested scrollers means the composer drifts out of
417
+ reach exactly when someone reaches for it. */
418
+ .history.chatting {
419
+ overflow: hidden; display: flex; flex-direction: column; padding: 0;
420
+ }
421
+ .history .row {
422
+ display: flex; align-items: center; gap: 12px;
423
+ padding: 12px 10px; border-bottom: 1px solid var(--bc-border);
424
+ }
425
+ .history .row:last-child { border-bottom: 0; }
426
+ .history .who { flex: 1; min-width: 0; }
427
+ .history .ttl {
428
+ display: block; font-size: 13.5px; font-weight: 600; color: var(--bc-fg);
429
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
430
+ }
431
+ .history .sub { display: block; margin-top: 3px; font-size: var(--bc-fs-xs); color: var(--bc-faint); }
432
+ .history .row-ico {
433
+ flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px;
434
+ background: var(--bc-subtle); color: var(--bc-muted); font-size: 16px;
435
+ }
436
+ .history .chev { flex: none; color: var(--bc-faint); font-size: 15px; transition: transform 160ms var(--bc-ease), color 160ms; }
437
+ .empty {
438
+ display: flex; flex-direction: column; align-items: center; gap: 10px;
439
+ padding: 40px 16px; text-align: center; font-size: var(--bc-fs-sm); color: var(--bc-muted);
440
+ }
441
+ .empty .empty-ico {
442
+ display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%;
443
+ background: var(--bc-accent-soft); color: var(--bc-accent); font-size: 20px;
444
+ }
445
+ .skel {
446
+ display: block; height: 12px; border-radius: 6px; margin: 6px 0;
447
+ background: linear-gradient(90deg, var(--bc-subtle) 25%, color-mix(in srgb, var(--bc-fg) 8%, var(--bc-subtle)) 50%, var(--bc-subtle) 75%);
448
+ background-size: 200% 100%; animation: bc-shimmer 1.4s linear infinite;
449
+ }
450
+ /* A history row is a button now — it opens the report — so the UA styling has
451
+ to go, and the hit area has to read as one. */
452
+ .history button.row {
453
+ width: 100%; background: none; border: 0; margin-bottom: 2px;
454
+ font: inherit; color: inherit; text-align: left; cursor: pointer;
455
+ border-radius: var(--bc-r-md); transition: background 160ms;
456
+ animation: bc-rise 260ms var(--bc-ease) both;
457
+ }
458
+ .history button.row:hover { background: var(--bc-subtle); }
459
+ .history button.row:hover .chev { transform: translateX(2px); color: var(--bc-accent); }
460
+ .history button.row:focus-visible { outline-offset: -2px; }
461
+
462
+ /* ---------- one report, and what happened to it ---------- */
463
+ .report-head {
464
+ flex: none;
465
+ display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: center;
466
+ padding: 10px var(--bc-pad); border-bottom: 1px solid var(--bc-border);
467
+ background: var(--bc-bg);
468
+ }
469
+ .report-head .ttl { font-size: 13px; font-weight: 620; color: var(--bc-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
470
+ .report-head .sub { font-size: var(--bc-fs-xs); color: var(--bc-faint); }
471
+ .report-head .pill { grid-row: 1 / span 2; grid-column: 2; align-self: center; }
472
+
473
+ /* The thread scrolls; the composer under it does not. Both live inside the
474
+ panel, which stops scrolling as a whole on this screen — a composer that
475
+ scrolls away is a composer people think isn't there. */
476
+ .thread {
477
+ flex: 1 1 auto; min-height: 140px;
478
+ display: flex; flex-direction: column; gap: 4px;
479
+ padding: 14px var(--bc-pad) 12px; overflow-y: auto; overscroll-behavior: contain;
480
+ }
481
+
482
+ /* The day a run of messages happened on, as a centred label on a rule. */
483
+ .day-sep {
484
+ display: flex; align-items: center; gap: 10px; margin: 10px 0 6px;
485
+ font-size: 11px; font-weight: 620; letter-spacing: .02em; color: var(--bc-faint);
486
+ }
487
+ .day-sep::before, .day-sep::after { content: ''; flex: 1; height: 1px; background: var(--bc-border); }
488
+
489
+ /* A status change is not a message and is not drawn as one: no side, no
490
+ bubble, no author. Centred and quiet, the way every chat marks the things
491
+ that happened rather than the things that were said. */
492
+ .sys {
493
+ align-self: center; max-width: 90%; text-align: center; margin: 6px 0;
494
+ padding: 4px 10px; border-radius: 999px; background: var(--bc-subtle);
495
+ font-size: var(--bc-fs-xs); line-height: 1.45; color: var(--bc-muted);
496
+ animation: bc-fade-in 240ms ease both;
497
+ }
498
+ .sys .dot {
499
+ display: inline-block; width: 6px; height: 6px; margin-right: 6px;
500
+ border-radius: 50%; background: var(--bc-muted); vertical-align: middle;
501
+ }
502
+ .sys .t { color: var(--bc-faint); }
503
+ .sys.k-status .dot { background: #4da3ff; }
504
+ .sys.k-severity .dot { background: #e8912a; }
505
+ .sys.k-assignment .dot { background: #8b7dff; }
506
+ .sys.k-filed .dot { background: var(--bc-faint); }
507
+
508
+ /* ---------- the conversation ----------
509
+
510
+ A status change and a message are different things and are drawn as such: a
511
+ change is a line on the rail above, a message is a bubble with a side. The
512
+ reporter's own words sit right and accented, the other side's left beside an
513
+ avatar that says who it is — the arrangement every chat has taught people to
514
+ read without a legend. */
515
+ .msg-row, .typing-row {
516
+ display: grid; grid-template-columns: 28px minmax(0, 1fr); column-gap: 8px;
517
+ max-width: 88%; margin-top: 10px;
518
+ animation: bc-rise 260ms var(--bc-ease) both;
519
+ }
520
+ .msg-row.cont { margin-top: 3px; }
521
+ .msg-row > *, .typing-row > * { grid-column: 2; }
522
+ /* Fixed rows, so the avatar lines up with the bubble whatever else the
523
+ message carries (a name above it, answers or a time below it). */
524
+ .msg-row > .msg-who, .typing-row > .msg-who { grid-row: 1; }
525
+ .msg-row > .bubble, .typing-row > .bubble { grid-row: 2; }
526
+ .msg-row > .quick { grid-row: 3; }
527
+ .msg-row > .msg-when { grid-row: 4; }
528
+ .msg-row > .av, .typing-row > .av { grid-column: 1; grid-row: 2; align-self: end; }
529
+ /* In a run of messages the avatar sits by the last bubble and the time under
530
+ it, the way a chat marks where one turn ends. */
531
+ .msg-row:has(+ .msg-row.cont) > .av { visibility: hidden; }
532
+ .msg-row:has(+ .msg-row.cont) > .msg-when { display: none; }
533
+ .msg-row.theirs, .typing-row { align-self: flex-start; }
534
+ .msg-row.mine { align-self: flex-end; grid-template-columns: minmax(0, 1fr); justify-items: end; }
535
+ .msg-row.mine > * { grid-column: 1; }
536
+ .av {
537
+ display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%;
538
+ font-size: 11px; font-weight: 700; letter-spacing: .02em;
539
+ background: color-mix(in srgb, var(--bc-fg) 10%, var(--bc-bg)); color: var(--bc-fg);
540
+ }
541
+ .av.bot {
542
+ background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));
543
+ color: var(--bc-on-accent); font-size: 14px;
544
+ }
545
+ .msg-who { font-size: var(--bc-fs-xs); font-weight: 620; color: var(--bc-muted); padding: 0 4px 4px; }
546
+ .msg-who.auto { color: var(--bc-accent); display: inline-flex; align-items: center; gap: 5px; }
547
+ .msg-who .tag {
548
+ padding: 1px 6px; border-radius: 999px; font-size: 9.5px; font-weight: 700; letter-spacing: .04em;
549
+ text-transform: uppercase; background: var(--bc-accent-soft); color: var(--bc-accent);
550
+ }
551
+ .bubble {
552
+ justify-self: start; max-width: 100%;
553
+ padding: 9px 13px; border-radius: 18px; font-size: 13.5px; line-height: 1.5;
554
+ background: var(--bc-subtle); color: var(--bc-fg);
555
+ border: 1px solid var(--bc-border);
556
+ border-bottom-left-radius: 6px;
557
+ }
558
+ /* pre-wrap belongs to the text, not the box — on the box it would also render
559
+ the markup's own indentation. */
560
+ .msg-text { display: block; white-space: pre-wrap; overflow-wrap: anywhere; }
561
+ .msg-row.mine .bubble {
562
+ justify-self: end;
563
+ background: var(--bc-accent); border-color: var(--bc-accent); color: var(--bc-on-accent);
564
+ border-bottom-left-radius: 18px; border-bottom-right-radius: 6px;
565
+ }
566
+ .msg-row.cont.theirs .bubble { border-top-left-radius: 6px; }
567
+ .msg-row.cont.mine .bubble { border-top-right-radius: 6px; }
568
+ /* A reply nobody wrote: tinted with the accent and labelled "Assistant" above
569
+ it, so it reads the same to someone who cannot see the colour at all. */
570
+ .msg-row.auto .bubble { background: var(--bc-accent-soft); border-color: var(--bc-accent-line); }
571
+ /* An attachment-only message has no text to wrap, so the bubble would be an
572
+ empty coloured box around it. */
573
+ .bubble.silent { padding: 0; background: none !important; border: 0; }
574
+ .msg-when { font-size: 10.5px; color: var(--bc-faint); padding: 4px 4px 0; }
575
+ .msg-row.pending { opacity: .6; }
576
+ .msg-row.pending .msg-when::after { content: ' · Sending…'; }
577
+ .msg-row.failed .bubble { border: 1px solid var(--bc-danger); }
578
+ .msg-row.failed .msg-when { color: var(--bc-danger); }
579
+ .msg-row.failed .msg-when::after { content: ' · Not sent'; }
580
+
581
+ /* The assistant, thinking. Three dots in a bubble where its next message will be. */
582
+ .typing { display: inline-flex; align-items: center; gap: 4px; padding: 12px 14px; justify-self: start; }
583
+ .typing i {
584
+ width: 7px; height: 7px; border-radius: 50%; background: var(--bc-accent); opacity: .45;
585
+ animation: bc-typing 1.2s ease-in-out infinite;
586
+ }
587
+ .typing i:nth-child(2) { animation-delay: .15s; }
588
+ .typing i:nth-child(3) { animation-delay: .3s; }
589
+ .typing-row .msg-when { padding-top: 4px; }
590
+
591
+ .att { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
592
+ .bubble.silent .att { margin-top: 0; }
593
+ .att img {
594
+ display: block; max-width: 100%; max-height: 190px; border-radius: var(--bc-r-md);
595
+ border: 1px solid var(--bc-border); object-fit: cover; cursor: zoom-in;
596
+ }
597
+ .att audio { width: 232px; max-width: 100%; height: 34px; }
598
+ .att video { max-width: 100%; max-height: 190px; border-radius: var(--bc-r-md); }
599
+ .att .file {
600
+ display: flex; align-items: center; gap: 8px; text-decoration: none;
601
+ padding: 8px 10px; border-radius: 10px; font-size: 12px;
602
+ background: var(--bc-elevated); border: 1px solid var(--bc-border); color: var(--bc-fg);
603
+ }
604
+ .att .file:hover { border-color: var(--bc-accent); }
605
+ .att .file .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
606
+ .att .file .sz { flex: none; color: var(--bc-faint); font-size: 11px; }
607
+
608
+ /* ---------- the assistant's live card ----------
609
+ A status header pinned between the report and the conversation: where the
610
+ investigation is, as words and as a stepper. It does not scroll with the
611
+ thread, because "is anything happening?" is the question on screen the
612
+ whole time. */
613
+ .assistant {
614
+ flex: none; position: relative; overflow: hidden;
615
+ margin: 10px 12px 0; padding: 12px 14px 12px;
616
+ border-radius: var(--bc-r-lg); border: 1px solid var(--bc-border);
617
+ background: var(--bc-subtle);
618
+ transition: background 400ms ease, border-color 400ms ease;
619
+ }
620
+ .assistant.analyzing, .assistant.confirming, .assistant.needs_info {
621
+ background: var(--bc-accent-soft); border-color: var(--bc-accent-line);
622
+ }
623
+ .assistant.resolved { background: color-mix(in srgb, var(--bc-ok) 9%, var(--bc-bg)); border-color: color-mix(in srgb, var(--bc-ok) 35%, var(--bc-border)); }
624
+ /* On a call: the call's own card is what they are watching. This one states
625
+ what is happening and stops moving — no spinner, no travelling bar. */
626
+ .assistant.calling { background: var(--bc-bg); }
627
+ .assistant.calling .as-progress i { transition: none; }
628
+ .as-body.entering { animation: bc-rise 360ms var(--bc-ease) both; }
629
+ .as-top { display: flex; gap: 11px; align-items: center; }
630
+ .as-orb {
631
+ flex: none; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;
632
+ color: var(--bc-on-accent); font-size: 15px;
633
+ background: linear-gradient(140deg, var(--bc-accent), color-mix(in srgb, var(--bc-accent) 60%, #fff));
634
+ }
635
+ .assistant.resolved .as-orb { background: var(--bc-ok); }
636
+ .assistant.reviewing .as-orb { background: color-mix(in srgb, var(--bc-fg) 55%, var(--bc-bg)); }
637
+ .as-spin { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(255,255,255,.4); border-top-color: #fff; animation: as-rot .8s linear infinite; }
638
+ @keyframes as-rot { to { transform: rotate(360deg); } }
639
+ .as-text { min-width: 0; flex: 1; }
640
+ .as-title { font-weight: 650; font-size: 13.5px; letter-spacing: -.01em; color: var(--bc-fg); }
641
+ .as-detail { font-size: 12px; line-height: 1.45; color: var(--bc-muted); margin-top: 2px; }
642
+ .as-progress {
643
+ height: 4px; margin: 12px 0 0; border-radius: 999px; overflow: hidden;
644
+ background: color-mix(in srgb, var(--bc-fg) 9%, transparent);
645
+ }
646
+ .as-progress i {
647
+ display: block; height: 100%; border-radius: inherit; background: var(--bc-accent);
648
+ transition: width 700ms var(--bc-ease);
649
+ }
650
+ .assistant.resolved .as-progress i { background: var(--bc-ok); }
651
+ .assistant.analyzing .as-progress i {
652
+ background: linear-gradient(90deg, var(--bc-accent) 0%, color-mix(in srgb, var(--bc-accent) 45%, #fff) 50%, var(--bc-accent) 100%);
653
+ background-size: 200% 100%; animation: bc-shimmer 1.6s linear infinite;
654
+ }
655
+ .as-steps {
656
+ list-style: none; display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
657
+ margin: 8px 0 0; padding: 0; font-size: 10.5px; color: var(--bc-faint);
658
+ }
659
+ .as-steps li { display: flex; align-items: center; gap: 5px; min-width: 0; white-space: nowrap; }
660
+ .as-steps li .lbl { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
661
+ .as-steps li .dot { flex: none; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid currentColor; }
662
+ .as-steps li.done { color: var(--bc-muted); }
663
+ .as-steps li.done .dot { background: var(--bc-accent); border-color: var(--bc-accent); }
664
+ .assistant.resolved .as-steps li.done .dot { background: var(--bc-ok); border-color: var(--bc-ok); }
665
+ .as-steps li.active { color: var(--bc-fg); font-weight: 650; }
666
+ .as-steps li.active .dot { border-color: var(--bc-accent); background: var(--bc-bg); animation: as-pulse 1.2s ease-in-out infinite; }
667
+ @keyframes as-pulse { 50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--bc-accent) 25%, transparent); } }
668
+ @media (prefers-reduced-motion: reduce) { .as-spin, .as-steps li.active .dot, .assistant.analyzing .as-progress i, .typing i { animation: none; } }
669
+
670
+ /* Quick answers under the confirm question — only on the latest message. */
671
+ .msg-row .quick { display: none; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
672
+ .msg-row:last-child .quick { display: flex; }
673
+ .quick button {
674
+ display: inline-flex; align-items: center; gap: 6px;
675
+ font: inherit; font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: 999px; cursor: pointer;
676
+ border: 1px solid var(--bc-accent); background: var(--bc-bg); color: var(--bc-accent);
677
+ transition: background 160ms, color 160ms, transform 160ms var(--bc-ease);
678
+ animation: bc-item-in 300ms var(--bc-ease) both;
679
+ }
680
+ .quick button:first-child { background: var(--bc-accent); color: var(--bc-on-accent); }
681
+ .quick button:nth-child(2) { animation-delay: 60ms; }
682
+ .quick button svg { font-size: 13px; }
683
+ .quick button:hover { transform: translateY(-1px); background: var(--bc-accent-soft); }
684
+ .quick button:first-child:hover { background: var(--bc-accent); filter: brightness(1.07); }
685
+
686
+ /* ---------- the composer ---------- */
687
+ .composer {
688
+ flex: none; position: relative;
689
+ padding: 10px 12px max(12px, env(safe-area-inset-bottom, 0px));
690
+ /* The panel's own background, not --bc-elevated: the two are the same white
691
+ in light mode but differ in dark, where a raised composer reads as a band
692
+ laid over the panel rather than the foot of it. */
693
+ background: var(--bc-bg);
694
+ border-top: 1px solid var(--bc-border);
695
+ }
696
+ /* One rounded field holding the tools, the text and the send button — the
697
+ shape every chat box has, so nobody has to look for where to type. */
698
+ .composer .row {
699
+ display: flex; align-items: flex-end; gap: 4px;
700
+ padding: 5px; border-radius: 22px;
701
+ background: var(--bc-subtle); border: 1px solid var(--bc-border);
702
+ transition: border-color 160ms, box-shadow 160ms;
703
+ }
704
+ .composer .row:focus-within { border-color: var(--bc-accent); box-shadow: var(--bc-ring); }
705
+ .composer textarea {
706
+ flex: 1; min-width: 0; min-height: 34px; max-height: 120px; resize: none; padding: 7px 6px;
707
+ font: inherit; font-size: 13.5px; line-height: 1.45;
708
+ color: var(--bc-fg); background: transparent; border: 0; border-radius: 0;
709
+ }
710
+ .composer textarea:hover, .composer textarea:focus { border: 0; box-shadow: none; outline: none; }
711
+ .composer .tools { display: flex; gap: 0; grid-template-columns: none; }
712
+ .composer .tool {
713
+ width: 34px; height: 34px; min-height: 0; padding: 0; display: grid; place-items: center; cursor: pointer;
714
+ border-radius: 50%; border: 0; background: none; font-size: 17px;
715
+ color: var(--bc-muted);
716
+ }
717
+ .composer .tool svg { font-size: 17px; }
718
+ .composer .tool:hover:not(:disabled) { background: color-mix(in srgb, var(--bc-fg) 8%, transparent); color: var(--bc-fg); border: 0; transform: none; }
719
+ .composer .tool:hover:not(:disabled) svg { color: var(--bc-fg); }
720
+ .composer .tool:disabled { opacity: .4; cursor: default; }
721
+ .composer .tool.recording { color: var(--bc-danger); background: color-mix(in srgb, var(--bc-danger) 12%, transparent); }
722
+ .composer .tool.recording svg { color: var(--bc-danger); }
723
+ .composer .send {
724
+ width: 34px; height: 34px; flex: none; border-radius: 50%; cursor: pointer; font-size: 16px;
725
+ background: var(--bc-accent); border: 0; color: var(--bc-on-accent);
726
+ display: grid; place-items: center;
727
+ transition: transform 160ms var(--bc-ease), opacity 160ms, filter 160ms;
728
+ }
729
+ .composer .send:hover:not(:disabled) { filter: brightness(1.08); transform: scale(1.05); }
730
+ .composer .send:disabled { opacity: .35; cursor: default; background: var(--bc-faint); }
731
+ .composer .staged { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 0 8px; }
732
+ .composer .chip {
733
+ display: flex; align-items: center; gap: 6px; max-width: 200px;
734
+ padding: 5px 7px 5px 10px; border-radius: 999px; font-size: var(--bc-fs-xs);
735
+ background: var(--bc-subtle); border: 1px solid var(--bc-border); color: var(--bc-muted);
736
+ animation: bc-item-in 240ms var(--bc-ease) both;
737
+ }
738
+ .composer .chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--bc-fg); }
739
+ .composer .chip button {
740
+ flex: none; width: 18px; height: 18px; display: grid; place-items: center;
741
+ border: 0; border-radius: 50%; background: none; color: var(--bc-faint); cursor: pointer;
742
+ }
743
+ .composer .chip button:hover { color: var(--bc-danger); }
744
+ .composer .chip.uploading { border-style: dashed; }
745
+ .composer .note { padding: 6px 6px 0; font-size: var(--bc-fs-xs); color: var(--bc-muted); }
746
+ .composer .note.err { color: var(--bc-danger); }
747
+ .reccount { font-variant-numeric: tabular-nums; }
748
+
749
+ /* Status carries the only colour in this list, so it is the thing the eye
750
+ lands on — which is what someone opening this screen came to check. */
751
+ .pill {
752
+ flex: none; display: inline-flex; align-items: center; gap: 5px;
753
+ font-size: 11px; font-weight: 650;
754
+ padding: 3px 9px; border-radius: 999px;
755
+ color: var(--bc-muted); background: var(--bc-subtle);
756
+ }
757
+ .pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
758
+ .pill.open { color: #c2410c; background: color-mix(in srgb, #f59e0b 14%, var(--bc-bg)); }
759
+ .pill.in_progress { color: #1d6fd1; background: color-mix(in srgb, #3b82f6 14%, var(--bc-bg)); }
760
+ .pill.resolved { color: #0f8a5a; background: color-mix(in srgb, #10b981 14%, var(--bc-bg)); }
761
+ .pill.closed { color: var(--bc-muted); }
762
+ :host(.bc-dark) .pill.open { color: #fbbf24; }
763
+ :host(.bc-dark) .pill.in_progress { color: #7cb8ff; }
764
+ :host(.bc-dark) .pill.resolved { color: #4ade9a; }
765
+ @media (prefers-color-scheme: dark) {
766
+ :host(.bc-auto) .pill.open { color: #fbbf24; }
767
+ :host(.bc-auto) .pill.in_progress { color: #7cb8ff; }
768
+ :host(.bc-auto) .pill.resolved { color: #4ade9a; }
769
+ }
770
+
771
+ /* ---------- does the fix work ---------- */
772
+ .fix-verification {
773
+ flex: none; margin: 10px 12px 0; padding: 12px 14px;
774
+ border-radius: var(--bc-r-lg); border: 1px solid color-mix(in srgb, var(--bc-ok) 35%, var(--bc-border));
775
+ background: color-mix(in srgb, var(--bc-ok) 8%, var(--bc-bg));
776
+ font-size: var(--bc-fs-sm); color: var(--bc-fg);
777
+ animation: bc-rise 280ms var(--bc-ease) both;
778
+ }
779
+ .fix-verification p { margin: 0 0 8px; font-weight: 620; }
780
+ .fix-verification textarea { min-height: 56px; margin-bottom: 8px; font-size: 13px; background: var(--bc-bg); }
781
+ .fix-verification button {
782
+ padding: 7px 13px; font: inherit; font-size: 12.5px; font-weight: 620; border-radius: 999px; cursor: pointer;
783
+ border: 1px solid var(--bc-border); background: var(--bc-bg); color: var(--bc-fg);
784
+ }
785
+ .fix-verification button[data-result="CONFIRMED"] { background: var(--bc-ok); border-color: var(--bc-ok); color: #fff; }
786
+ .fix-verification .feedback-status { margin: 6px 0 0; font-weight: 450; color: var(--bc-danger); font-size: var(--bc-fs-xs); }
787
+ .fix-verification .feedback-status:empty { display: none; }
788
+
789
+ /* A call in progress, in place of the form.
790
+ ---------------------------------------------------------------------------
791
+ Deliberately not styled as a chat. Nothing arrives here to read — the
792
+ conversation is happening out loud — so this is a state, a line of
793
+ reassurance about the report, and a way to hang up. */
794
+ .call { display: flex; flex-direction: column; padding: 26px 0 0; animation: bc-fade-in 240ms ease both; }
795
+ .call-head, .call-note { padding: 0 var(--bc-pad); }
796
+ .call-head { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; }
797
+ .call-head strong { font-size: 15.5px; font-weight: 680; letter-spacing: -.01em; }
798
+ .call-dot {
799
+ width: 9px; height: 9px; border-radius: 50%; background: var(--bc-accent);
800
+ animation: bc-call-pulse 1.6s ease-in-out infinite;
801
+ }
802
+ .call-dot.off { background: var(--bc-faint); animation: none; }
803
+ @keyframes bc-call-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
804
+ /* Said while it is still ringing, because that is when somebody is deciding
805
+ whether it is worth waiting. */
806
+ .call-note { margin: 0 0 18px; font-size: 13px; color: var(--bc-muted); line-height: 1.55; }
807
+
808
+ /* ---------- the same call, on top of the report it is about ---------- */
809
+ /* A bar rather than its own screen, unlike the one the composer opens. The
810
+ thread underneath is exactly what both people are talking about, so taking it
811
+ off screen would be taking away the only shared reference. */
812
+ .livecall {
813
+ flex: none;
814
+ display: flex; align-items: center; gap: 9px;
815
+ margin: 10px 12px 0; padding: 9px 11px;
816
+ border: 1px solid var(--bc-accent-line); border-radius: var(--bc-r-md);
817
+ background: var(--bc-accent-soft); font-size: var(--bc-fs-sm); color: var(--bc-fg);
818
+ animation: bc-rise 240ms var(--bc-ease) both;
819
+ }
820
+ .livecall .who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
821
+ .livecall .call-kept { font-size: 11px; opacity: .75; }
822
+ .livecall .secs { font-variant-numeric: tabular-nums; color: var(--bc-muted); }
823
+ .livecall .act {
824
+ padding: 5px 11px; border-radius: 999px; border: 1px solid var(--bc-border);
825
+ background: var(--bc-bg); color: var(--bc-fg);
826
+ font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
827
+ }
828
+ .livecall .act:hover { background: var(--bc-subtle); }
829
+ .livecall .act.danger { border-color: transparent; background: var(--bc-danger); color: #fff; }
830
+ .livecall.over { color: var(--bc-muted); background: var(--bc-subtle); border-color: var(--bc-border); }
831
+ .call-note code { font-size: 11px; }
832
+ @media (prefers-reduced-motion: reduce) { .call-dot { animation: none; } }
833
+
834
+ /* div.done, not .done: the assistant's stepper marks finished steps li.done,
835
+ and this screen's padding was landing on every one of them. */
836
+ div.done { padding: 44px 26px 44px; text-align: center; animation: bc-fade-in 240ms ease both; }
837
+ .done .tick {
838
+ width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 50%;
839
+ display: grid; place-items: center; font-size: 26px;
840
+ background: color-mix(in srgb, var(--bc-ok) 14%, transparent); color: var(--bc-ok);
841
+ box-shadow: 0 0 0 8px color-mix(in srgb, var(--bc-ok) 6%, transparent);
842
+ animation: bc-pop 420ms var(--bc-ease) both;
843
+ }
844
+ .done h3 { margin: 0 0 6px; font-size: 17px; font-weight: 680; letter-spacing: -.015em; }
845
+ .done p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--bc-muted); }
846
+ .done code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; padding: 1px 5px; border-radius: 5px; background: var(--bc-subtle); color: var(--bc-fg); }
847
+ /* The likely wait. Set apart from the reference line above it by a rule rather
848
+ than by weight: one is bookkeeping and the other is the answer to the
849
+ question the reporter actually has, and they should not read as one
850
+ paragraph. */
851
+ .done .eta {
852
+ margin: 14px auto 0;
853
+ padding-top: 12px;
854
+ border-top: 1px solid var(--bc-border);
855
+ max-width: 30ch;
856
+ line-height: 1.5;
857
+ }
858
+ .done .eta strong { color: var(--bc-fg); font-weight: 620; }
859
+ /* The same figure on the report the reporter came back to. Sits under the
860
+ status pill in the header block, muted — it is context, not the headline the
861
+ status is. */
862
+ .report-head .eta-line {
863
+ grid-column: 1 / -1;
864
+ font-size: var(--bc-fs-xs);
865
+ color: var(--bc-muted);
866
+ margin-top: 2px;
867
+ }
868
+
869
+ /* ---------- recording bar ---------- */
870
+ .recbar {
871
+ position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
872
+ z-index: 2147483002;
873
+ display: flex; align-items: center; gap: 12px;
874
+ padding: 8px 8px 8px 16px; border-radius: 999px;
875
+ background: var(--bc-elevated); color: var(--bc-fg);
876
+ border: 1px solid var(--bc-border); box-shadow: var(--bc-shadow);
877
+ animation: bc-item-in 300ms var(--bc-ease) both;
878
+ }
879
+ .recbar .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--bc-danger); animation: bc-pulse 1.4s ease-in-out infinite; }
880
+ .recbar .time { font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 650; min-width: 38px; }
881
+ .recbar .hint { font-size: 12px; color: var(--bc-muted); }
882
+ .recbar button { height: 34px; padding: 0 14px; font-size: 12.5px; font-weight: 620; border-radius: 999px; cursor: pointer; }
883
+ .recbar .stop { background: var(--bc-danger); border: 1px solid var(--bc-danger); color: #fff; }
884
+ .recbar .stop:hover { filter: brightness(1.08); }
885
+
886
+ /* ---------- phones ----------------------------------------------------------
887
+ This widget runs inside a shadow root on somebody else's site, so its own
888
+ stylesheet is the whole story: the host page cannot correct any of this, and
889
+ anything that goes wrong here goes wrong on their site, in front of their
890
+ visitor, at the moment that visitor is already annoyed enough to file a bug.
891
+ -------------------------------------------------------------------------- */
892
+
893
+ /* 100vh on a phone is the viewport with the browser chrome hidden, so the
894
+ panel could be taller than the space it actually has and put "Send report"
895
+ under the address bar. */
896
+ @supports (height: 100dvh) {
897
+ .panel { max-height: calc(100dvh - var(--bc-dock-h, 84px) - 52px); }
898
+ }
899
+
900
+ /* Short desktop windows: the chat height gives way to the space there is. */
901
+ @media (max-height: 820px) { .panel.is-chat { height: calc(100vh - var(--bc-dock-h, 84px) - 52px); } }
902
+
903
+ /* A phone gets the whole screen: a sheet that slides up from the bottom, with
904
+ the notch and the home indicator kept clear. The dock steps aside while it
905
+ is open — the sheet has its own close button, and a launcher floating over
906
+ the send button would be in the way of the one thing that matters. */
907
+ @media (max-width: 480px) {
908
+ .scrim { background: rgba(10,12,16,.4); }
909
+ .panel, .scrim.at-right .panel, .scrim.at-left .panel, .scrim.at-bottom .panel, .scrim.at-top .panel {
910
+ inset: 0; width: 100%; max-width: none; height: 100%; max-height: none;
911
+ border-radius: 0; border: 0;
912
+ animation: bc-sheet-in 380ms var(--bc-ease) both;
913
+ }
914
+ @supports (height: 100dvh) { .panel { height: 100dvh; max-height: none; } }
915
+ .panel.is-chat { height: 100%; }
916
+ .scrim.closing .panel { animation: bc-sheet-out 200ms ease both; }
917
+ .head { padding-top: max(14px, env(safe-area-inset-top, 0px)); padding-left: max(var(--bc-pad), env(safe-area-inset-left, 0px)); padding-right: max(12px, env(safe-area-inset-right, 0px)); }
918
+ .foot, .powered { padding-bottom: max(10px, env(safe-area-inset-bottom, 0px)); }
919
+ .foot + .powered { padding-bottom: max(9px, env(safe-area-inset-bottom, 0px)); }
920
+ .foot:has(+ .powered) { padding-bottom: 12px; }
921
+ .recbar .hint { display: none; }
922
+ .msg-row, .typing-row { max-width: 92%; }
923
+ }
924
+
925
+ @media (pointer: coarse) {
926
+ /* iOS zooms the whole page when a field it focuses is under 16px, and does
927
+ not zoom back out. On the host's site that is not the widget's page to
928
+ zoom — the visitor is left on a magnified checkout wondering what they
929
+ touched. !important because this is a property of the device, not a
930
+ preference about type. */
931
+ input, textarea, select { font-size: 16px !important; }
932
+
933
+ /* 44px is the floor a thumb hits reliably. Every control in this form was
934
+ under it, on the one surface in the product designed for someone who is
935
+ not an operator and gets no second attempt. */
936
+ .seg button { min-height: 44px; }
937
+ .tool { min-height: 56px; }
938
+ button.primary, button.ghost { height: 44px; }
939
+ .composer .send, .composer .tool { width: 40px; height: 40px; min-height: 0; }
940
+ textarea { min-height: 104px; }
941
+ .composer textarea { min-height: 40px; }
942
+
943
+ /* The close button and the thumbnails' remove buttons are small glyphs whose
944
+ boxes cannot grow without moving what is beside them, so each keeps its
945
+ size and gets an invisible target drawn around it instead. The theme
946
+ switch gets the same in the launcher's own sheet (widget.js). */
947
+ .head .icon-btn, .thumb .drop, .brand-credit { position: relative; }
948
+ .thumb .drop { position: absolute; opacity: 1; }
949
+ .head .icon-btn::after, .thumb .drop::after, .brand-credit::after {
950
+ content: '';
951
+ position: absolute;
952
+ inset: -8px;
953
+ }
954
+ }
955
+
956
+ /* Hover is a lie on a touchscreen: it latches on tap and stays. */
957
+ @media (hover: none) {
958
+ .tool:hover:not(:disabled) { transform: none; }
959
+ }
960
+ `;