@gemboss/ui 0.12.0 → 0.21.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.
package/src/assistant.css CHANGED
@@ -1,558 +1 @@
1
- /* The assistant's popup / status bar / full-screen host — @gemboss/ui (28/09/2026).
2
- *
3
- * Moved from gemboss apps/admin/src/components/assistant/AssistantDockHost.module.css, where it was
4
- * written for the admin's assistant; gemwatcher's and content-radar's assistants now wear the same
5
- * host. Class names took a `gb-dock` prefix instead of CSS-module hashes (a consumer without CSS
6
- * modules must be able to load it); every value is unchanged. The geometry assumes the GemBoss shell
7
- * (@gemboss/ui AppShell): 56px topbar, 240px rail, drawer below 760px.
8
- *
9
- * Rendered by AssistantHost (assistant/host.tsx). State and the rules for when it shows are
10
- * assistant/dock.ts + assistant/provider.tsx.
11
- */
12
-
13
-
14
- .gb-dock {
15
- position: fixed;
16
- z-index: 320; /* above the mobile tab bar (300), below the page editor (400) */
17
- right: 24px;
18
- bottom: 0;
19
- /* MEASURED 2026-09-12, not assumed: the rail is 240px at every width above 760 (the shell's own
20
- `margin-left: 240px`), and there is NO bottom tab bar — `.gemboss-mobile-nav` is not in the DOM
21
- at 320 or 390. An earlier pass here wrote 72px and 56px from a stale comment in AppShell and
22
- the popup covered a third of the rail, swallowing the menu clicks this feature exists to free.
23
- 240 (rail) + 24 (right gutter) + 24 (left gap) = 288. */
24
- /* 460 is the app's own modal width (`.ui-modal`), so the assistant reads as part of the same
25
- family instead of a wider one-off. 600 was a guess; this one already exists. */
26
- width: min(460px, calc(100vw - 288px));
27
- display: flex;
28
- flex-direction: column;
29
- min-height: 0;
30
- /* Frosted, the way the settings dialog reads — translucent surface over a small blur, so the page
31
- underneath shows through as texture rather than detail.
32
- NOT a full-screen scrim, though that is what settings uses. A scrim would block the page, and a
33
- popup you cannot work behind is just the assistant page in a smaller box: the whole point of
34
- this surface is that the screen under it stays usable. */
35
- /* 93%, AND THE 5 POINTS THIS GAINED ON 25/09/2026 ARE A CONTRAST MEASUREMENT.
36
- A translucent surface is `a·white + (1−a)·backdrop`, so the darkest it can ever go is `a·white`.
37
- At 88% that floor was #E0E0E0, and `--gemboss-text-muted` (#686b71) measures 4.05:1 there —
38
- UNDER AA. That is not a hypothetical corner of this component: `.header` and `.miniBar` both
39
- declare `background: transparent` on purpose (see the note beside them), so `.headerStatus`
40
- 12px, `.miniStatus` 12px and `.booting` 14px all sit DIRECTLY on this surface in that colour.
41
- 93% puts the floor at #EDEDED and muted at 4.56:1. Chosen with, and kept equal to,
42
- `.gemboss-popover-surface` in app/gemboss-shell.css — `one-popover-surface-four-wearers.test.ts`
43
- fails if the two numbers ever stop agreeing. Change them THERE too.
44
-
45
- TWO BACKGROUNDS, and the first is not decoration. `color-mix` and `backdrop-filter` ship on
46
- DIFFERENT schedules — Firefox 103–112 and Safari 15.4–16.1 have the blur and not the mix — and
47
- a declaration the parser rejects is DROPPED, not defaulted. Without the plain line, the dock had
48
- NO background at all in those browsers: the page running straight through the chat. It shipped
49
- that way on 12/09/2026 and nobody could see it from here. NOT a custom property: `background:
50
- var(--some-mix-token)` would fail at computed-value time instead, which falls back to `unset`
51
- rather than to the line above — worse than the bug it would be trying to share. */
52
- background: var(--gemboss-surface);
53
- background: color-mix(in srgb, var(--gemboss-surface) 93%, transparent);
54
- -webkit-backdrop-filter: blur(14px) saturate(1.4);
55
- backdrop-filter: blur(14px) saturate(1.4);
56
- border: 1px solid var(--gemboss-divider);
57
- border-bottom: 0;
58
- border-radius: 12px 12px 0 0;
59
- box-shadow: 0 -2px 6px rgba(15, 23, 42, 0.05), 0 24px 64px rgba(15, 23, 42, 0.18);
60
- overflow: hidden;
61
-
62
- /* ── Arriving ────────────────────────────────────────────────────────────────────────────────
63
- Short and plain: 140ms, ease-out, a small rise and a fade. Long enough to read as "it came
64
- from somewhere", short enough that nobody waits for it — a popup a seller opens twenty times
65
- a day must not perform.
66
-
67
- TRANSFORM AND OPACITY ONLY. Height and horizontal offset are set from a pointer, and a
68
- transition on those means the box trails the finger instead of following it. Those two stay
69
- instant by construction, not by luck. */
70
- transition: transform 140ms cubic-bezier(0.2, 0.8, 0.3, 1), opacity 140ms ease-out;
71
- }
72
- /* The entry itself. `@starting-style` is what lets an element animate the frame it appears in
73
- (Chrome 117+/Safari 17.5+; the desktop app is Chromium 132). Anywhere older simply appears —
74
- degrading to instant is the right failure for chrome nobody asked to watch. */
75
- @starting-style {
76
- .gb-dock-dock, .gb-dock-mini { transform: translateY(10px); opacity: 0; }
77
- .gb-dock-side { transform: translateX(12px); opacity: 0; }
78
- }
79
- /* Nothing animates while a pointer is setting the numbers — see `dragging` in the host. */
80
- .gb-dock-dragging, .gb-dock-dragging * { transition: none !important; }
81
- /* Where `backdrop-filter` is missing there is no blur to smear the page, and 93% opaque over sharp
82
- body text is a chat you read through. Back to a solid surface — what this dock wore before it was
83
- frosted. Same reasoning, same shape, as `.gemboss-popover-surface`. */
84
- @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
85
- .gb-dock { background: var(--gemboss-surface); }
86
- }
87
- @media (prefers-reduced-motion: reduce) {
88
- .gb-dock { transition: none; }
89
- }
90
-
91
- /* The floating popup IS the base geometry above; this class exists so the DOM names the state —
92
- `styles[mode]` was `undefined` for it, which is invisible in a screenshot and unnameable in a test. */
93
- .gb-dock-dock { }
94
-
95
- /* Still mounted — that is what keeps a run alive — just not on screen. */
96
- .gb-dock-hidden { display: none; }
97
-
98
- /* ── Full: exactly the area `main` occupies, so the dedicated page looks untouched ── */
99
- .gb-dock-full {
100
- /* BELOW `.gemboss-route-shell` (z-index 5), which covers this exact rectangle while the next route
101
- loads. At the popup's 320 the assistant stayed painted over the holding screen, so leaving this
102
- page looked like nothing happened — and the screen's click-catcher was defeated, so clicks
103
- during the wait landed on chat controls. Fixed position still puts it over `main`'s content. */
104
- z-index: 4;
105
- top: 56px;
106
- left: 240px; /* the rail, measured — see the note on .host */
107
- right: 0;
108
- bottom: 0;
109
- width: auto;
110
- border: 0;
111
- border-radius: 0;
112
- box-shadow: none;
113
- background: var(--gemboss-app-bg);
114
- }
115
-
116
- /* ── Side: a full-height panel pinned to the right edge (01/10/2026) ─────────────────────────────
117
- The third working shape, beside the popup and full screen: a long conversation next to the screen
118
- it is about. Opt-in per surface (`<AssistantHost allowSide>`), because unlike the popup it TAKES
119
- ROOM — the page beside it moves over (rule below), so nothing under it is hidden. That is why the
120
- surface is solid rather than frosted: nothing sits behind it to show through.
121
- Above `.gemboss-route-shell` (5), so the panel stays put while routes change — a sidebar that
122
- vanishes on every click is a popup with extra steps. BELOW the topbar (300): the account menu
123
- opens down out of the topbar's right end, exactly where this panel starts, and at the popup's 320
124
- the menu fell behind the panel (review). 290 still clears the desktop invite (260/280). The host
125
- is rendered inside `main` (AppShell's slot), which works because `main` makes no stacking context
126
- or containing block; a surface that gives its `main` a transform or z-index must mount it outside. */
127
- .gb-dock-side {
128
- z-index: 290;
129
- top: 56px;
130
- right: 0;
131
- bottom: 0;
132
- width: var(--gb-dock-side-w, 400px);
133
- background: var(--gemboss-surface);
134
- -webkit-backdrop-filter: none;
135
- backdrop-filter: none;
136
- border: 0;
137
- border-left: 1px solid var(--gemboss-divider);
138
- border-radius: 0;
139
- box-shadow: none;
140
- overflow: visible; /* the seam's touch band reaches 14px past the left edge */
141
- }
142
- .gb-dock-side .gb-dock-body, .gb-dock-side .gb-dock-pane { overflow: hidden; }
143
- /* The header does not drag a panel that is pinned. */
144
- .gb-dock-side .gb-dock-header { cursor: default; touch-action: auto; }
145
- /* The word for what the dot already says stands down here, as it does in a popup under 420px: the panel has
146
- five controls in its header, and the title needs the room (measured: "Gem assi…" / "Re…" at 400). Visually
147
- only — the dot is aria-hidden, so the word is the one place a screen reader hears "Working" (review #3622). */
148
- .gb-dock-side .gb-dock-status {
149
- flex: 0 0 auto; position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
150
- overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
151
- }
152
- .gb-dock-side .gb-dock-header:active { cursor: default; }
153
- /* The seam: the panel's left edge, the fleet's one `.ui-resizer` (ui.css draws the grip). A 5px strip
154
- centred on the border, ABOVE the page beside it. */
155
- /* `.gb-dock-side >` is not decoration: `.ui-resizer { position: relative }` (ui.css) has the weight of one
156
- class too, and which file a bundler loads last would decide whether the seam is a 5px strip or a 0px
157
- line nobody can grab. */
158
- .gb-dock-side > .gb-dock-sidegrip {
159
- position: absolute;
160
- top: 0;
161
- bottom: 0;
162
- left: -3px;
163
- width: 5px;
164
- z-index: 3;
165
- }
166
- /* The page makes room. `margin-right`, not padding: main's padding is set by four rules in shell.css
167
- and a fifth here would fight them. The provider puts the class and the width on <body>; the default
168
- `bodyClassPrefix` is `gemboss-assistant` — a surface with another prefix writes its own copy of this
169
- one rule. No transition: the seam is set from a pointer, and a page that trails the finger is the
170
- bug `.gb-dock` already avoids. */
171
- @media (min-width: 1100px) {
172
- body.gemboss-assistant-side .gemboss-platform-main { margin-right: var(--gb-assistant-side-w, 400px); }
173
- }
174
-
175
-
176
- /* ── Full: the one control full screen needs — the way back to the popup ──────────────────────
177
- Floated over the chat's own header, whose right half is empty, rather than given a bar of its
178
- own: a whole strip of chrome for one button would push the conversation down on every screen. */
179
- .gb-dock-fullchrome {
180
- position: absolute;
181
- top: 10px;
182
- right: 16px;
183
- z-index: 1;
184
- display: flex;
185
- gap: 8px;
186
- }
187
- .gb-dock-shrink {
188
- display: inline-flex;
189
- align-items: center;
190
- gap: 6px;
191
- min-height: 44px; /* §9.1 control tier */
192
- padding: 0 12px;
193
- border: 1px solid var(--gemboss-divider);
194
- border-radius: var(--gemboss-radius-full);
195
- background: var(--gemboss-surface);
196
- color: var(--gemboss-text-muted);
197
- font: inherit;
198
- font-size: var(--gemboss-fs-caption);
199
- cursor: pointer;
200
- white-space: nowrap;
201
- }
202
- .gb-dock-shrink:hover { background: var(--gemboss-hover); color: var(--gemboss-text); }
203
- .gb-dock-shrink:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: 2px; }
204
- .gb-dock-shrink svg { width: 16px; height: 16px; flex: 0 0 auto; }
205
- /* On a phone the label would crowd the chat header; the icon carries it there. */
206
- @media (max-width: 560px) {
207
- .gb-dock-shrink span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
208
- .gb-dock-shrink { padding: 0; width: 44px; justify-content: center; }
209
- /* Except the way to a person: a head-and-shoulders icon alone does not say "a human answers". */
210
- .gb-dock-shrink-keep span { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
211
- .gb-dock-shrink-keep { padding: 0 12px; width: auto; }
212
- /* Two controls do not fit in the empty half of the chat header at phone width — floated, they sat
213
- on the title ("Ask th…", measured at 320 and 390). In the flow they cost one short strip here,
214
- and only here. */
215
- .gb-dock-fullchrome { position: static; justify-content: flex-end; padding: 8px 12px 0; }
216
- }
217
-
218
- /* ── Mini: a status bar, not a window ── */
219
- .gb-dock-mini {
220
- height: auto;
221
- width: min(320px, calc(100vw - 288px));
222
- -webkit-backdrop-filter: blur(14px) saturate(1.4); /* Safari; the host above carries the rest */
223
- backdrop-filter: blur(14px) saturate(1.4);
224
- border-radius: 12px 12px 0 0;
225
- }
226
- .gb-dock-mini .gb-dock-body { display: none; }
227
- .gb-dock-minirow { display: flex; align-items: center; padding-right: 4px; }
228
- .gb-dock-minirow .gb-dock-minibar { flex: 1 1 auto; min-width: 0; }
229
- /* `width: 44px` is a BASIS, not a floor: the default `flex-shrink: 1` let this button give ground to
230
- the row beside it, so the × measured under 44 at all six widths while the rule above read as done.
231
- MEASURED 2026-09-12 — the same trap as every other "the declaration is there" bug today. */
232
- .gb-dock-minirow .gb-dock-icon { flex: 0 0 auto; }
233
-
234
- .gb-dock-minibar {
235
- display: flex;
236
- align-items: center;
237
- gap: 8px;
238
- width: 100%;
239
- min-height: 48px;
240
- padding: 0 12px;
241
- border: 0;
242
- background: transparent;
243
- font: inherit;
244
- color: var(--gemboss-text-strong);
245
- cursor: pointer;
246
- text-align: left;
247
- }
248
- .gb-dock-minibar:hover { background: var(--gemboss-hover); }
249
- .gb-dock-minibar strong { font-size: 13px; font-weight: 600; flex: 0 0 auto; }
250
- .gb-dock-ministatus {
251
- flex: 1 1 auto;
252
- min-width: 0;
253
- font-size: 12px;
254
- color: var(--gemboss-text-muted);
255
- overflow: hidden;
256
- text-overflow: ellipsis;
257
- white-space: nowrap;
258
- }
259
- .gb-dock-minichevron { font-size: 10px; color: var(--gemboss-text-muted); }
260
-
261
- /* ── Resize grip (dock only) ── */
262
- .gb-dock-grip {
263
- /* OVERLAID on the header's top edge, not stacked above it. As its own 14px row it pushed the
264
- title down and the header read as top-heavy — Chris: "header hơi lệch do padding top hơi nhiều".
265
- The drag area is unchanged; it simply no longer costs vertical space. */
266
- position: absolute;
267
- top: 0;
268
- left: 0;
269
- right: 0;
270
- height: 14px;
271
- z-index: 2;
272
- display: grid;
273
- place-items: center;
274
- cursor: ns-resize;
275
- touch-action: none;
276
- background: transparent;
277
- }
278
- .gb-dock-grip > span {
279
- display: block;
280
- width: 44px; /* a mouse target wide enough to find without aiming */
281
- height: 4px;
282
- border-radius: 2px;
283
- background: var(--gemboss-divider);
284
- }
285
- .gb-dock-grip:hover > span, .gb-dock-grip:focus-visible > span { background: var(--gemboss-text-muted); }
286
- .gb-dock-grip:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: -2px; }
287
-
288
- /* ── Dock header ── */
289
- .gb-dock-header {
290
- flex: 0 0 auto;
291
- cursor: grab; /* the strip itself drags the popup sideways */
292
- touch-action: none;
293
- user-select: none;
294
- display: flex;
295
- align-items: center;
296
- gap: 8px;
297
- padding: 0 4px 0 16px;
298
- min-height: 48px;
299
- border-bottom: 1px solid var(--gemboss-divider);
300
- background: transparent; /* the frosted host shows through; a second fill would double it */
301
- }
302
- .gb-dock-title {
303
- font-size: 13px;
304
- font-weight: 600;
305
- color: var(--gemboss-text-strong);
306
- white-space: nowrap;
307
- /* Let the NAME absorb a squeeze. Adding the + button pushed the header to 290px inside a 277px
308
- popup at 320 — a nowrap title with no min-width cannot give ground, so the row overflowed
309
- instead. Measured 2026-09-12. */
310
- flex: 0 1 auto;
311
- min-width: 0;
312
- overflow: hidden;
313
- text-overflow: ellipsis;
314
- }
315
- .gb-dock-status {
316
- flex: 1 1 auto;
317
- min-width: 0;
318
- font-size: 12px;
319
- color: var(--gemboss-text-muted);
320
- overflow: hidden;
321
- text-overflow: ellipsis;
322
- white-space: nowrap;
323
- }
324
- .gb-dock-header:active { cursor: grabbing; }
325
- /* `margin-left: auto`: the status word was the only thing that stretched, so wherever it stands down (a popup under
326
- 420px, the side panel) the controls followed the title instead of the right edge — 350px adrift in a 720px panel
327
- (review #3622). With the word showing, it still takes the free space first and nothing moves. */
328
- .gb-dock-actions { display: flex; align-items: center; gap: 2px; cursor: default; margin-left: auto; }
329
- /* One ACTION, then the three WINDOW controls — ordered by how much each changes the window: bigger,
330
- smaller, gone. The gap is what says they are two different kinds of button; without it four icons
331
- in a row read as one undifferentiated strip, which is what "các icon đang không đồng đều" was
332
- describing. The 📌 that used to sit in the middle of them is gone entirely — see lib/assistant-dock. */
333
- .gb-dock-action { margin-right: 8px; }
334
-
335
- .gb-dock-icon {
336
- /* 44px control target — Apple HIG / WCAG 2.5.5. The icon inside stays 18px. */
337
- width: 44px;
338
- height: 44px;
339
- display: grid;
340
- place-items: center;
341
- border: 0;
342
- border-radius: 8px;
343
- background: transparent;
344
- color: var(--gemboss-text-muted);
345
- cursor: pointer;
346
- padding: 0;
347
- }
348
- .gb-dock-icon:hover { background: var(--gemboss-hover); color: var(--gemboss-text-strong); }
349
- .gb-dock-icon:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: -2px; }
350
- .gb-dock-icon svg { width: 18px; height: 18px; }
351
- .gb-dock-icon-on { color: var(--gemboss-active-fg); background: var(--gemboss-active-bg); }
352
-
353
- .gb-dock-dot {
354
- flex: 0 0 auto;
355
- width: 8px;
356
- height: 8px;
357
- border-radius: 50%;
358
- background: var(--gemboss-divider);
359
- }
360
- .gb-dock-dot-busy { background: var(--gemboss-primary); animation: gb-dock-pulse 1.4s ease-in-out infinite; }
361
- .gb-dock-dot-unread { background: var(--gemboss-success-strong); }
362
- @keyframes gb-dock-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
363
- @media (prefers-reduced-motion: reduce) { .gb-dock-dot-busy { animation: none; } }
364
-
365
- /* ── Tabs: the assistant, or a person on the Gem team (dock only) ──────────────────────────────
366
- Their own row under the header rather than inside it: at 320px the header already spends 176px
367
- on four 44px controls, and two text tabs do not fit in what is left. */
368
- .gb-dock-tabs {
369
- flex: 0 0 auto;
370
- display: flex;
371
- gap: 4px;
372
- padding: 0 8px;
373
- border-bottom: 1px solid var(--gemboss-divider);
374
- }
375
- .gb-dock-tab {
376
- position: relative;
377
- display: inline-flex;
378
- align-items: center;
379
- gap: 6px;
380
- min-height: 44px; /* §9.1 control tier */
381
- padding: 0 12px;
382
- border: 0;
383
- border-bottom: 2px solid transparent;
384
- background: transparent;
385
- color: var(--gemboss-text-muted);
386
- font: inherit;
387
- font-size: 13px;
388
- cursor: pointer;
389
- white-space: nowrap;
390
- }
391
- .gb-dock-tab:hover { color: var(--gemboss-text-strong); }
392
- .gb-dock-tab[aria-selected="true"] { color: var(--gemboss-text-strong); font-weight: 600; border-bottom-color: var(--gemboss-primary); }
393
- .gb-dock-tab:focus-visible { outline: 2px solid var(--gemboss-primary); outline-offset: -2px; }
394
- .gb-dock-badge {
395
- min-width: 18px;
396
- padding: 0 6px;
397
- border-radius: var(--gemboss-radius-full);
398
- background: var(--gemboss-primary);
399
- color: var(--gemboss-on-primary, #fff);
400
- font-size: 11px;
401
- font-weight: 700;
402
- line-height: 18px;
403
- text-align: center;
404
- }
405
-
406
- /* ── Body: the assistant itself ── */
407
- .gb-dock-body {
408
- flex: 1 1 auto;
409
- min-height: 0;
410
- display: flex;
411
- min-width: 0;
412
- }
413
- .gb-dock-body > * { flex: 1 1 auto; min-width: 0; min-height: 0; }
414
- /* Each tab's pane. A hidden pane stays MOUNTED — `display:none`, never unmount (see the host). */
415
- .gb-dock-pane { display: flex; min-width: 0; min-height: 0; }
416
- .gb-dock-pane > * { flex: 1 1 auto; min-width: 0; min-height: 0; }
417
- /* The package's own workspace (AssistantWorkspaceLayout) sizes itself to the viewport on its own page;
418
- inside the host it fills the host instead. Moved here from the admin's assistant-dock-admin.css
419
- (28/09/2026): every surface that puts the layout in the host needs it, or the layout keeps
420
- `height: calc(100dvh - 56px)` inside a popup a few hundred pixels tall. */
421
- .gb-dock-body .gemboss-assistant-layout,
422
- .gb-dock-body .gemboss-assistant-page { height: auto; min-height: 0; }
423
- .gb-dock-pane[hidden] { display: none; }
424
-
425
- .gb-dock-booting {
426
- flex: 1 1 auto;
427
- display: grid;
428
- place-items: center;
429
- padding: 24px;
430
- color: var(--gemboss-text-muted);
431
- font-size: 14px;
432
- }
433
-
434
- .gb-dock-sr {
435
- position: absolute;
436
- width: 1px; height: 1px;
437
- padding: 0; margin: -1px;
438
- overflow: hidden;
439
- clip: rect(0 0 0 0);
440
- white-space: nowrap;
441
- border: 0;
442
- }
443
-
444
- /* ── Phone: the popup spans the screen above the bottom tab bar; full mode loses the rail gutter ── */
445
- /* ── Narrow: five 44px controls + a title do not fit ────────────────────────────────────────
446
- MEASURED 2026-09-12 at 320px: the header row wanted 342px inside a 291px popup. 5 × 44 = 220
447
- leaves 71px for a title and a status line. So the two controls that are about BROWSING WHILE IT
448
- RUNS — the pin, and the word for what the dot already says — stand down; new chat, full screen,
449
- minimize and close stay. Dropping a control beats letting the row overflow off the edge. */
450
- @media (max-width: 420px) {
451
- .gb-dock-status { display: none; }
452
- }
453
-
454
- /* ── Phone: the rail is an off-canvas drawer, so both geometries reclaim the full width ── */
455
- @media (max-width: 760px) {
456
- .gb-dock {
457
- right: 8px;
458
- left: 8px;
459
- width: auto;
460
- padding-bottom: env(safe-area-inset-bottom);
461
- border-radius: 12px 12px 0 0;
462
- }
463
- .gb-dock-mini { width: auto; }
464
- .gb-dock-full {
465
- left: 0;
466
- right: 0;
467
- top: 56px;
468
- bottom: 0;
469
- border-radius: 0;
470
- }
471
- /* No room beside a phone screen: the side panel takes the screen, the way full does, and there is
472
- nothing to drag. The page is not pushed (the rule above stops at 761). */
473
- .gb-dock-side {
474
- left: 0;
475
- right: 0;
476
- width: auto;
477
- border-left: 0;
478
- border-radius: 0; /* the phone .gb-dock rule above rounds the top; this panel is the screen */
479
- }
480
- .gb-dock-sidegrip { display: none; }
481
- /* …so the button that pins it there would only say "full screen" a second time. */
482
- .gb-dock-pinside { display: none; }
483
- }
484
-
485
- /* 1100 = rail 240 + the panel's narrowest 380 + the page's narrowest 480 (SIDE_MIN_WIDTH, SIDE_PAGE_MIN in
486
- assistant/dock.ts). Below it the two do not fit side by side — MEASURED at 768: pushed, the page kept
487
- 120px and its table ran under the panel. So between the phone and 1100 the panel covers the content
488
- area the way full screen does, nothing is pushed, there is no seam to drag, and the popup does not
489
- offer to pin.
490
- AT THE END OF THE FILE on purpose: `.gb-dock-icon { display: grid }` comes later than the side
491
- section and has the same weight, so placed there this block lost and the pin still showed at 768
492
- (measured). */
493
- @media (min-width: 761px) and (max-width: 1099.98px) {
494
- .gb-dock-side { left: 240px; width: auto; }
495
- .gb-dock-side .gb-dock-sidegrip, .gb-dock-pinside { display: none; }
496
- }
497
-
498
- /* ── AssistantLauncher: the button that opens the assistant. NOT floating (03/10/2026) ────────────────────────────────
499
- 0.4.2 floated it bottom-right (z 280). visual-gate on partner-radar prod, 03/10: `content_covered` red at 5 of 6 widths
500
- on every page measured (/, /apps, /sources): a fixed button always sits on whatever text scrolls under it, and at
501
- scroll 0 that is a table column. GemCommunity had dropped its floating buttons for the same reason (#239). Chris chose
502
- "bỏ nút nổi". It now lives where nothing scrolls under it:
503
- · `--rail` wide screens: the last row of the rail (`railBottom`), a full-width primary button. Hidden ≤760px,
504
- where the rail is an off-canvas drawer nobody sees until they open it.
505
- · `--topbar` phones (≤760px): a 44px icon button in the topbar's `actions`, left of the account chip. Hidden above.
506
- The top-right corner stays the ACCOUNT. A surface renders both; exactly one is displayed at any width. */
507
- .gb-assistant-launcher {
508
- display: inline-flex;
509
- align-items: center;
510
- gap: 8px;
511
- border: 0;
512
- cursor: pointer;
513
- font-family: var(--gemboss-font-sans, system-ui, sans-serif);
514
- }
515
- .gb-assistant-launcher svg { flex: 0 0 auto; }
516
- .gb-assistant-launcher:focus-visible { outline: 2px solid var(--gemboss-accent, #5661f5); outline-offset: 2px; }
517
- .gb-assistant-launcher--rail {
518
- width: 100%;
519
- min-height: 44px; /* a control, not a list link: §9.1 asks 44 (visual-gate red at 768 with 36) */
520
- margin: 8px 0 4px;
521
- padding: 0 12px;
522
- border-radius: var(--gemboss-radius-md, 8px);
523
- /* The SAME iris pill as the admin's rail Assistant (.gnav-item.gnav-assistant, 0.6.8), not a solid near-black button
524
- (0.7.1, 04/10/2026): content-radar and partner-radar open their assistant through this launcher, and after 0.6.8 the
525
- two drew the one AI front door in two different colours beside the admin. */
526
- background: var(--gemboss-accent-soft);
527
- color: var(--gemboss-accent-strong);
528
- font-size: 13px;
529
- font-weight: 600;
530
- line-height: 1;
531
- text-align: left;
532
- }
533
- .gb-assistant-launcher--rail:hover { background: color-mix(in srgb, var(--gemboss-accent) 16%, var(--gemboss-surface)); }
534
- .gb-assistant-launcher--rail .gb-assistant-launcher-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
535
- .gb-assistant-launcher-kbd {
536
- font: inherit; font-size: 11px; opacity: 0.7;
537
- border: 1px solid currentColor; border-radius: 5px; padding: 1px 5px;
538
- }
539
- .gb-assistant-launcher--topbar {
540
- display: none;
541
- position: relative; /* holds the clipped label: an absolute child of a static button escapes to the nearest positioned box */
542
- justify-content: center;
543
- flex: 0 0 auto;
544
- width: 44px;
545
- height: 44px;
546
- padding: 0;
547
- border-radius: var(--gemboss-radius-md, 8px);
548
- background: transparent;
549
- color: var(--gemboss-text, #1b1c1e);
550
- }
551
- .gb-assistant-launcher--topbar:hover, .gb-assistant-launcher--topbar:active { background: var(--gemboss-hover, #eceef2); }
552
- .gb-assistant-launcher--topbar .gb-assistant-launcher-label, .gb-assistant-launcher--topbar .gb-assistant-launcher-kbd {
553
- position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
554
- }
555
- @media (max-width: 760px) {
556
- .gb-assistant-launcher--rail { display: none; }
557
- .gb-assistant-launcher--topbar { display: inline-flex; }
558
- }
1
+ .gb-dock{position:fixed;z-index:320;right:24px;bottom:0;width:min(460px,calc(100vw - 288px));display:flex;flex-direction:column;min-height:0;background:var(--gemboss-surface);background:color-mix(in srgb,var(--gemboss-surface) 93%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.4);backdrop-filter:blur(14px) saturate(1.4);border:1px solid var(--gemboss-divider);border-bottom:0;border-radius:12px 12px 0 0;box-shadow:0 -2px 6px #0f172a0d,0 24px 64px #0f172a2e;overflow:hidden;transition:transform .14s cubic-bezier(.2,.8,.3,1),opacity .14s ease-out}@starting-style{.gb-dock-dock,.gb-dock-mini{transform:translateY(10px);opacity:0}.gb-dock-side{transform:translate(12px);opacity:0}}.gb-dock-dragging,.gb-dock-dragging *{transition:none!important}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.gb-dock{background:var(--gemboss-surface)}}@media (prefers-reduced-motion: reduce){.gb-dock{transition:none}}.gb-dock-hidden{display:none}.gb-dock-full{z-index:4;inset:56px 0 0 240px;width:auto;border:0;border-radius:0;box-shadow:none;background:var(--gemboss-app-bg)}.gb-dock-side{z-index:290;top:56px;right:0;bottom:0;width:var(--gb-dock-side-w, 400px);background:var(--gemboss-surface);-webkit-backdrop-filter:none;backdrop-filter:none;border:0;border-left:1px solid var(--gemboss-divider);border-radius:0;box-shadow:none;overflow:visible}.gb-dock-side .gb-dock-body,.gb-dock-side .gb-dock-pane{overflow:hidden}.gb-dock-side .gb-dock-header{cursor:default;touch-action:auto}.gb-dock-side .gb-dock-status{flex:0 0 auto;position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.gb-dock-side .gb-dock-header:active{cursor:default}.gb-dock-side>.gb-dock-sidegrip{position:absolute;top:0;bottom:0;left:-3px;width:5px;z-index:3}@media (min-width: 1100px){body.gemboss-assistant-side .gemboss-platform-main{margin-right:var(--gb-assistant-side-w, 400px)}}.gb-dock-fullchrome{position:absolute;top:10px;right:16px;z-index:1;display:flex;gap:8px}.gb-dock-shrink{display:inline-flex;align-items:center;gap:6px;min-height:44px;padding:0 12px;border:1px solid var(--gemboss-divider);border-radius:var(--gemboss-radius-full);background:var(--gemboss-surface);color:var(--gemboss-text-muted);font:inherit;font-size:var(--gemboss-fs-caption);cursor:pointer;white-space:nowrap}.gb-dock-shrink:hover{background:var(--gemboss-hover);color:var(--gemboss-text)}.gb-dock-shrink:focus-visible{outline:2px solid var(--gemboss-primary);outline-offset:2px}.gb-dock-shrink svg{width:16px;height:16px;flex:0 0 auto}@media (max-width: 560px){.gb-dock-shrink span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}.gb-dock-shrink{padding:0;width:44px;justify-content:center}.gb-dock-shrink-keep span{position:static;width:auto;height:auto;overflow:visible;clip:auto}.gb-dock-shrink-keep{padding:0 12px;width:auto}.gb-dock-fullchrome{position:static;justify-content:flex-end;padding:8px 12px 0}}.gb-dock-mini{height:auto;width:min(320px,calc(100vw - 288px));-webkit-backdrop-filter:blur(14px) saturate(1.4);backdrop-filter:blur(14px) saturate(1.4);border-radius:12px 12px 0 0}.gb-dock-mini .gb-dock-body{display:none}.gb-dock-minirow{display:flex;align-items:center;padding-right:4px}.gb-dock-minirow .gb-dock-minibar{flex:1 1 auto;min-width:0}.gb-dock-minirow .gb-dock-icon{flex:0 0 auto}.gb-dock-minibar{display:flex;align-items:center;gap:8px;width:100%;min-height:48px;padding:0 12px;border:0;background:transparent;font:inherit;color:var(--gemboss-text-strong);cursor:pointer;text-align:left}.gb-dock-minibar:hover{background:var(--gemboss-hover)}.gb-dock-minibar strong{font-size:13px;font-weight:600;flex:0 0 auto}.gb-dock-ministatus{flex:1 1 auto;min-width:0;font-size:12px;color:var(--gemboss-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gb-dock-minichevron{font-size:10px;color:var(--gemboss-text-muted)}.gb-dock-grip{position:absolute;top:0;left:0;right:0;height:14px;z-index:2;display:grid;place-items:center;cursor:ns-resize;touch-action:none;background:transparent}.gb-dock-grip>span{display:block;width:44px;height:4px;border-radius:2px;background:var(--gemboss-divider)}.gb-dock-grip:hover>span,.gb-dock-grip:focus-visible>span{background:var(--gemboss-text-muted)}.gb-dock-grip:focus-visible{outline:2px solid var(--gemboss-primary);outline-offset:-2px}.gb-dock-header{flex:0 0 auto;cursor:grab;touch-action:none;user-select:none;display:flex;align-items:center;gap:8px;padding:0 4px 0 16px;min-height:48px;border-bottom:1px solid var(--gemboss-divider);background:transparent}.gb-dock-title{font-size:13px;font-weight:600;color:var(--gemboss-text-strong);white-space:nowrap;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis}.gb-dock-status{flex:1 1 auto;min-width:0;font-size:12px;color:var(--gemboss-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gb-dock-header:active{cursor:grabbing}.gb-dock-actions{display:flex;align-items:center;gap:2px;cursor:default;margin-left:auto}.gb-dock-action{margin-right:8px}.gb-dock-icon{width:44px;height:44px;display:grid;place-items:center;border:0;border-radius:8px;background:transparent;color:var(--gemboss-text-muted);cursor:pointer;padding:0}.gb-dock-icon:hover{background:var(--gemboss-hover);color:var(--gemboss-text-strong)}.gb-dock-icon:focus-visible{outline:2px solid var(--gemboss-primary);outline-offset:-2px}.gb-dock-icon svg{width:18px;height:18px}.gb-dock-icon-on{color:var(--gemboss-active-fg);background:var(--gemboss-active-bg)}.gb-dock-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--gemboss-divider)}.gb-dock-dot-busy{background:var(--gemboss-primary);animation:gb-dock-pulse 1.4s ease-in-out infinite}.gb-dock-dot-unread{background:var(--gemboss-success-strong)}@keyframes gb-dock-pulse{0%,to{opacity:1}50%{opacity:.35}}@media (prefers-reduced-motion: reduce){.gb-dock-dot-busy{animation:none}}.gb-dock-tabs{flex:0 0 auto;display:flex;gap:4px;padding:0 8px;border-bottom:1px solid var(--gemboss-divider)}.gb-dock-tab{position:relative;display:inline-flex;align-items:center;gap:6px;min-height:44px;padding:0 12px;border:0;border-bottom:2px solid transparent;background:transparent;color:var(--gemboss-text-muted);font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}.gb-dock-tab:hover{color:var(--gemboss-text-strong)}.gb-dock-tab[aria-selected=true]{color:var(--gemboss-text-strong);font-weight:600;border-bottom-color:var(--gemboss-primary)}.gb-dock-tab:focus-visible{outline:2px solid var(--gemboss-primary);outline-offset:-2px}.gb-dock-badge{min-width:18px;padding:0 6px;border-radius:var(--gemboss-radius-full);background:var(--gemboss-primary);color:var(--gemboss-on-primary, #fff);font-size:11px;font-weight:700;line-height:18px;text-align:center}.gb-dock-body{flex:1 1 auto;min-height:0;display:flex;min-width:0}.gb-dock-body>*{flex:1 1 auto;min-width:0;min-height:0}.gb-dock-pane{display:flex;min-width:0;min-height:0}.gb-dock-pane>*{flex:1 1 auto;min-width:0;min-height:0}.gb-dock-body .gemboss-assistant-layout,.gb-dock-body .gemboss-assistant-page{height:auto;min-height:0}.gb-dock-pane[hidden]{display:none}.gb-dock-booting{flex:1 1 auto;display:grid;place-items:center;padding:24px;color:var(--gemboss-text-muted);font-size:14px}.gb-dock-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}@media (max-width: 420px){.gb-dock-status{display:none}}@media (max-width: 760px){.gb-dock{right:8px;left:8px;width:auto;padding-bottom:env(safe-area-inset-bottom);border-radius:12px 12px 0 0}.gb-dock-mini{width:auto}.gb-dock-full{inset:56px 0 0;border-radius:0}.gb-dock-side{left:0;right:0;width:auto;border-left:0;border-radius:0}.gb-dock-sidegrip,.gb-dock-pinside{display:none}}@media (min-width: 761px) and (max-width: 1099.98px){.gb-dock-side{left:240px;width:auto}.gb-dock-side .gb-dock-sidegrip,.gb-dock-pinside{display:none}}.gb-assistant-launcher{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;font-family:var(--gemboss-font-sans, system-ui, sans-serif)}.gb-assistant-launcher svg{flex:0 0 auto}.gb-assistant-launcher:focus-visible{outline:2px solid var(--gemboss-accent, #5661f5);outline-offset:2px}.gb-assistant-launcher--rail{width:100%;min-height:44px;margin:8px 0 4px;padding:0 12px;border-radius:var(--gemboss-radius-md, 8px);background:var(--gemboss-accent-soft);color:var(--gemboss-accent-strong);font-size:13px;font-weight:600;line-height:1;text-align:left}.gb-assistant-launcher--rail:hover{background:color-mix(in srgb,var(--gemboss-accent) 16%,var(--gemboss-surface))}.gb-assistant-launcher--rail .gb-assistant-launcher-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gb-assistant-launcher-kbd{font:inherit;font-size:11px;opacity:.7;border:1px solid currentColor;border-radius:5px;padding:1px 5px}.gb-assistant-launcher--topbar{display:none;position:relative;justify-content:center;flex:0 0 auto;width:44px;height:44px;padding:0;border-radius:var(--gemboss-radius-md, 8px);background:transparent;color:var(--gemboss-text, #1b1c1e)}.gb-assistant-launcher--topbar:hover,.gb-assistant-launcher--topbar:active{background:var(--gemboss-hover, #eceef2)}.gb-assistant-launcher--topbar .gb-assistant-launcher-label,.gb-assistant-launcher--topbar .gb-assistant-launcher-kbd{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@media (max-width: 760px){.gb-assistant-launcher--rail{display:none}.gb-assistant-launcher--topbar{display:inline-flex}}