@deepseek-ai/dsh-client-ui-dockkit 0.1.5-alpha.1 → 0.1.5-alpha.2

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.
@@ -7,10 +7,11 @@
7
7
  * Colours come from the embedder's token layer; the kit names no literal. Type
8
8
  * follows the embedder's content axis (`--dsh-content-font-size` and its
9
9
  * secondary step) so a surface reads at the same size as the page around it.
10
- * Emphasis — a hovered divider, the drop caret, the drop-zone hint — takes the
11
- * platform's accent (`--dsw-alias-brand-primary-new-colorprimary-new-color`),
12
- * not `--dsw-alias-brand-primary`, which this platform binds to its
13
- * near-black (light) or near-white (dark) foreground.
10
+ * Emphasis — the drop caret, the drop-zone hint — takes the platform's accent
11
+ * (`--dsw-alias-brand-primary-new-colorprimary-new-color`), not
12
+ * `--dsw-alias-brand-primary`, which this platform binds to its near-black
13
+ * (light) or near-white (dark) foreground. A hovered divider takes the
14
+ * caption label ink instead, reading as a handle rather than a highlight.
14
15
  */
15
16
 
16
17
  .split {
@@ -35,25 +36,87 @@
35
36
  min-height: 0;
36
37
  }
37
38
 
39
+ /* The divider owns no layout room (`SPLIT_MINIMUMS.divider` mirrors the 0):
40
+ the halves abut, so a rule a pane draws across its own edge — a header's
41
+ hairline — runs unbroken past the seam. The visible rule is a 0.5px hairline
42
+ centred on the seam, matching the embedder's other borders, and `::after`
43
+ widens the pointer target to 8px by reaching 4px over each neighbour.
44
+ `z-index` keeps that overhang above the later sibling, which would otherwise
45
+ take the hit. */
38
46
  .divider {
39
47
  position: relative;
48
+ z-index: 1;
40
49
  flex: none;
41
- background: var(--dsw-alias-border-l1);
42
50
  touch-action: none;
43
51
  }
44
52
 
53
+ .divider::before,
54
+ .divider::after {
55
+ content: '';
56
+ position: absolute;
57
+ }
58
+
59
+ .divider::before {
60
+ background: var(--dsw-alias-border-l4);
61
+ }
62
+
45
63
  .splitRow > .divider {
46
- width: 4px;
64
+ width: 0;
47
65
  cursor: col-resize;
48
66
  }
49
67
 
68
+ .splitRow > .divider::before {
69
+ top: 0;
70
+ bottom: 0;
71
+ left: -0.25px;
72
+ width: 0.5px;
73
+ }
74
+
75
+ .splitRow > .divider::after {
76
+ top: 0;
77
+ bottom: 0;
78
+ left: -4px;
79
+ right: -4px;
80
+ }
81
+
50
82
  .splitColumn > .divider {
51
- height: 4px;
83
+ height: 0;
52
84
  cursor: row-resize;
53
85
  }
54
86
 
55
- .divider:hover {
56
- background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
87
+ .splitColumn > .divider::before {
88
+ left: 0;
89
+ right: 0;
90
+ top: -0.25px;
91
+ height: 0.5px;
92
+ }
93
+
94
+ .splitColumn > .divider::after {
95
+ left: 0;
96
+ right: 0;
97
+ top: -4px;
98
+ bottom: -4px;
99
+ }
100
+
101
+ /* Hover: a 1px grip fades in over the hairline, its caption ink deepest at
102
+ its middle and fading toward both ends. The grip is painted on the `::after`
103
+ hit target, centred on the seam, and crossfaded through opacity, because a
104
+ gradient cannot transition from the hairline's solid colour. */
105
+ .divider::after {
106
+ opacity: 0;
107
+ transition: opacity 120ms ease-out;
108
+ }
109
+
110
+ .divider:hover::after {
111
+ opacity: 1;
112
+ }
113
+
114
+ .splitRow > .divider::after {
115
+ background: linear-gradient(to bottom, transparent, var(--dsw-alias-label-caption) 50%, transparent) center / 1px 100% no-repeat;
116
+ }
117
+
118
+ .splitColumn > .divider::after {
119
+ background: linear-gradient(to right, transparent, var(--dsw-alias-label-caption) 50%, transparent) center / 100% 1px no-repeat;
57
120
  }
58
121
 
59
122
  /* Both floors: a flex item's minimum is its content's, and a body's longest
@@ -74,17 +137,18 @@
74
137
  min-width: 0;
75
138
  min-height: 0;
76
139
  overflow: hidden;
77
- border: 1px solid transparent;
78
- }
79
-
80
- .pane[data-dockkit-pane-active] {
81
- border-color: var(--dsw-alias-border-l2);
82
140
  }
83
141
 
84
- /* One centre line for everything in the strip: every child — chip, add
85
- control, split control, the embedder's chrome — is 24px tall, and the strip
86
- centres them, so chip text and control glyphs never sit at different heights.
87
- A child with another height would break that; keep them at 24px.
142
+ /* A 38px row whose 28px content band sits at its bottom: chips fill the band,
143
+ so they end flush with the strip's bottom edge, and the 28px add control, the
144
+ 28px split control, and the embedder's 28px chrome centre on the band's one
145
+ centre line, so chip text and control glyphs never sit at different heights.
146
+ The 10px above is the strip's own top margin, inside its box so the hit
147
+ area stays one element; the 10px at the start is the first chip's inset
148
+ from the edge, and the 6px at the end puts the last control's glyph 12px
149
+ from the edge. The strip draws no border: a body
150
+ that wants a rule under it draws its own. A floating panel's header is
151
+ this same row.
88
152
 
89
153
  The strip never clips: the chip box below is its one shrinking part, and
90
154
  every control after it is `flex: none`, so a narrow pane costs chips, never
@@ -94,27 +158,47 @@
94
158
  flex: none;
95
159
  gap: 4px;
96
160
  align-items: center;
97
- height: 36px;
98
- padding: 0 6px;
99
- border-bottom: 0.5px solid var(--dsw-alias-border-l1);
161
+ height: 28px;
162
+ padding: 10px 6px 0 10px;
100
163
  touch-action: none;
101
164
  }
102
165
 
103
- /* The chips. Shrinks to nothing before any control after it moves; what no
104
- longer fits is clipped here. Deliberately not a scroller: a horizontal
105
- scroll container claims a press-and-move as its own gesture and cancels the
106
- pointer, which would abandon every tab drag in a narrow pane. Tabs shrink
107
- and ellipsize first. */
166
+ /* The chips. Shrinks to nothing before any control after it moves; chips keep
167
+ their 80px floor, so what no longer fits scrolls here on the wheel, with no
168
+ scrollbar drawn. `touch-action: none` keeps a touch press-and-move a tab drag
169
+ rather than a pan the scroller would claim and cancel the pointer for.
170
+ `data-dockkit-strip-scroll` names the hidden sides (`start`, `end`, or
171
+ both); each hidden side fades the chips out over 24px into whatever ground
172
+ the pane draws, so the row reads as continuing under the controls. */
108
173
  .stripTabs {
109
174
  display: flex;
110
175
  flex: 0 1 auto;
111
- gap: 4px;
112
176
  align-items: center;
113
177
  min-width: 0;
114
- overflow: hidden;
178
+ overflow-x: auto;
179
+ overflow-y: hidden;
180
+ /* The scroll to a newly active chip glides rather than jumps. */
181
+ scroll-behavior: smooth;
182
+ scrollbar-width: none;
115
183
  touch-action: none;
116
184
  }
117
185
 
186
+ .stripTabs::-webkit-scrollbar {
187
+ display: none;
188
+ }
189
+
190
+ .stripTabs[data-dockkit-strip-scroll='end'] {
191
+ mask-image: linear-gradient(to right, black calc(100% - 24px), transparent);
192
+ }
193
+
194
+ .stripTabs[data-dockkit-strip-scroll='start'] {
195
+ mask-image: linear-gradient(to right, transparent, black 24px);
196
+ }
197
+
198
+ .stripTabs[data-dockkit-strip-scroll='start end'] {
199
+ mask-image: linear-gradient(to right, transparent, black 24px, black calc(100% - 24px), transparent);
200
+ }
201
+
118
202
  /* Takes the free space and gives it all back first: a zero basis shrinks
119
203
  nothing, so shortage lands on the chip box alone. */
120
204
  .stripFill {
@@ -122,37 +206,64 @@
122
206
  min-width: 0;
123
207
  }
124
208
 
125
- /* Embedder controls at the strip's end, set off from the kit's own split
126
- control by a hairline so the two groups read as two groups. */
209
+ /* Embedder controls at the strip's end, spaced from the kit's own split
210
+ control as they are from each other: the strip's 4px gap plus this 4px
211
+ margin equals the 8px between the controls. */
127
212
  .stripChrome {
128
213
  display: flex;
129
214
  flex: none;
130
- gap: 2px;
215
+ gap: 8px;
131
216
  align-items: center;
132
- height: 24px;
133
- margin-left: 2px;
134
- padding-left: 4px;
135
- border-left: 0.5px solid var(--dsw-alias-border-l1);
217
+ height: 28px;
218
+ margin-left: 4px;
136
219
  }
137
220
 
138
- .caret {
221
+ /* The slot between two chips: a 10px box with a hairline down its centre.
222
+ Targeted by a drag, the same box draws the caret instead, so the chips
223
+ around it never move. */
224
+ .slot {
225
+ display: flex;
139
226
  flex: none;
140
- align-self: center;
227
+ align-items: center;
228
+ justify-content: center;
229
+ width: 10px;
230
+ height: 28px;
231
+ }
232
+
233
+ .slot::before {
234
+ content: '';
235
+ width: 0.5px;
236
+ height: 14px;
237
+ background: var(--dsw-alias-border-l4);
238
+ }
239
+
240
+ /* The active chip is a filled capsule and needs no rule against it: the two
241
+ slots beside it go blank, so the row reads as the capsule between bare
242
+ chips. A caret in either slot still draws. */
243
+ .tabActive + .slot:not(.slotCaret)::before,
244
+ .slot:not(.slotCaret):has(+ .tabActive)::before {
245
+ background: transparent;
246
+ }
247
+
248
+ .slotCaret::before {
141
249
  width: 2px;
142
250
  height: 20px;
143
251
  background: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
144
252
  }
145
253
 
254
+ /* The chip: a 28px capsule whose title runs to the right inset. The close
255
+ control is not in the flow — it sits over the title's last 14px and shows
256
+ while the chip is active, hovered, or holds focus — so a chip is the same
257
+ width with and without it and nothing shifts on hover. */
146
258
  .tab {
147
259
  position: relative;
148
260
  display: flex;
149
261
  flex: 0 1 auto;
150
- gap: 4px;
151
262
  align-items: center;
152
- min-width: 44px;
263
+ min-width: 80px;
153
264
  max-width: 170px;
154
- height: 24px;
155
- padding: 0 5px 0 10px;
265
+ height: 28px;
266
+ padding: 0 10px;
156
267
  color: var(--dsw-alias-label-secondary);
157
268
  font-size: var(--dsh-content-font-size-secondary, 13px);
158
269
  line-height: 1;
@@ -163,35 +274,82 @@
163
274
  user-select: none;
164
275
  }
165
276
 
277
+ /* A flex row with a 5px gap, so an embedder's leading glyph (`renderTabTitle`)
278
+ centres on the text's line rather than sitting on its baseline and keeps
279
+ one distance from it. The clip is horizontal in intent; the line box must
280
+ hold the descenders it would otherwise cut, hence a line-height above the
281
+ chip's own 1. No ellipsis: a title wider than its box (`TabTitle` sets
282
+ `data-dockkit-tab-clipped`) fades out over its last 16px instead. A mask on
283
+ the title rather than a painted gradient: the chip's fill is bare,
284
+ translucent hover, or the active tag colour, and a mask matches every one
285
+ without naming it. */
166
286
  .tabTitle {
287
+ display: flex;
288
+ flex: 1 1 auto;
289
+ gap: 5px;
290
+ align-items: center;
291
+ min-width: 0;
167
292
  overflow: hidden;
168
- text-overflow: ellipsis;
293
+ line-height: 1.4;
169
294
  }
170
295
 
296
+ .tabTitle[data-dockkit-tab-clipped] {
297
+ mask-image: linear-gradient(to right, black calc(100% - 16px), transparent);
298
+ }
299
+
300
+ /* While the close shows, the title is gone under the close's circle and fades
301
+ out over the 16px before it: the circle spans the chip's last 24px and the
302
+ title ends 10px inside the chip, so the title's last 14px are under it.
303
+ Later than the clipped mask, so a clipped chip showing its close fades
304
+ under the close. A floating panel's header chip has no close, so it keeps
305
+ the clipped mask alone. */
306
+ .tab:not(.floatTitle):hover .tabTitle,
307
+ .tab:not(.floatTitle):focus-within .tabTitle,
308
+ .tabActive .tabTitle {
309
+ mask-image: linear-gradient(to right, black calc(100% - 30px), transparent calc(100% - 14px));
310
+ }
311
+
312
+ /* A 20px icon button inset 4px from the chip's end, holding a 14px glyph in
313
+ the tertiary ink, so it sits back from the title beside it. */
171
314
  .tabClose {
315
+ position: absolute;
316
+ top: 4px;
317
+ right: 4px;
172
318
  display: flex;
173
- flex: none;
174
319
  align-items: center;
175
320
  justify-content: center;
176
- width: 16px;
177
- height: 16px;
321
+ width: 20px;
322
+ height: 20px;
178
323
  padding: 0;
179
- color: inherit;
324
+ color: var(--dsw-alias-label-tertiary);
180
325
  line-height: 1;
181
326
  background: transparent;
182
327
  border: none;
183
- border-radius: 50%;
184
- corner-shape: round;
328
+ border-radius: 20px;
185
329
  cursor: pointer;
330
+ opacity: 0;
331
+ /* Hidden means untouchable too: without this a touch press on the chip's
332
+ trailing 20px would close the tab instead of activating it. */
333
+ pointer-events: none;
186
334
  }
187
335
 
336
+ /* Shown while the chip is hovered or holds focus, and always on the active
337
+ chip: the tab in view is the one a reader closes next. */
338
+ .tab:hover .tabClose,
339
+ .tab:focus-within .tabClose,
340
+ .tabActive .tabClose {
341
+ opacity: 1;
342
+ pointer-events: auto;
343
+ }
344
+
345
+ /* The chip's height and corner, so it reads as one more capsule in the row. */
188
346
  .addTab {
189
347
  display: flex;
190
348
  flex: none;
191
349
  align-items: center;
192
350
  justify-content: center;
193
- width: 24px;
194
- height: 24px;
351
+ width: 28px;
352
+ height: 28px;
195
353
  padding: 0;
196
354
  color: var(--dsw-alias-label-secondary);
197
355
  line-height: 1;
@@ -208,46 +366,72 @@
208
366
 
209
367
  .tabClose:hover {
210
368
  color: var(--dsw-alias-label-primary);
211
- background: var(--dsw-alias-interactive-bg-hover-solid);
369
+ background: var(--dsw-alias-interactive-bg-hover);
212
370
  }
213
371
 
214
372
  .tab:hover {
215
373
  background: var(--dsw-alias-interactive-bg-hover);
216
374
  }
217
375
 
218
- /* The active chip is the filled capsule; the rest are bare text. */
376
+ /* The active chip is the filled capsule, in the tag fill; the rest are bare text. */
219
377
  .tabActive {
220
378
  color: var(--dsw-alias-label-primary);
221
- background: var(--dsw-alias-interactive-bg-active);
379
+ background: var(--dsw-alias-markdown-tag);
380
+ }
381
+
382
+ /* A pane's lone unclosable chip is a label, not a choice: no capsule, no
383
+ hover fill — the primary ink on the pane's own ground. Its title never
384
+ yields to a close control, so it keeps the clipped fade alone. */
385
+ .tab.tabQuiet,
386
+ .tab.tabQuiet:hover {
387
+ color: var(--dsw-alias-label-primary);
388
+ background: transparent;
389
+ cursor: default;
390
+ }
391
+
392
+ .tab.tabQuiet .tabTitle,
393
+ .tab.tabQuiet:hover .tabTitle,
394
+ .tab.tabQuiet:focus-within .tabTitle {
395
+ mask-image: none;
396
+ }
397
+
398
+ .tab.tabQuiet .tabTitle[data-dockkit-tab-clipped] {
399
+ mask-image: linear-gradient(to right, black calc(100% - 16px), transparent);
222
400
  }
223
401
 
224
402
  .tabDragging {
225
403
  opacity: 0.5;
226
404
  }
227
405
 
406
+ /* The geometry of a message's action row button (ui-chat MessageIconActions):
407
+ a 28px circle around a 15px glyph. */
228
408
  .iconButton {
229
- display: flex;
409
+ display: inline-flex;
230
410
  flex: none;
231
411
  align-items: center;
232
412
  justify-content: center;
233
- width: 24px;
234
- height: 24px;
235
- padding: 0;
413
+ width: 28px;
414
+ height: 28px;
415
+ padding: 6px;
236
416
  color: var(--dsw-alias-label-secondary);
237
417
  line-height: 1;
238
418
  background: transparent;
239
419
  border: none;
240
- border-radius: 4px;
420
+ border-radius: 28px;
241
421
  cursor: pointer;
242
422
  }
243
423
 
424
+ .iconButton svg {
425
+ width: 15px;
426
+ height: 15px;
427
+ }
428
+
244
429
  .iconButton:hover:not(:disabled) {
245
- color: var(--dsw-alias-label-primary);
246
430
  background: var(--dsw-alias-interactive-bg-hover);
247
431
  }
248
432
 
249
433
  .iconButton:disabled {
250
- color: var(--dsw-alias-label-tertiary);
434
+ opacity: 0.4;
251
435
  cursor: default;
252
436
  }
253
437
 
@@ -269,6 +453,10 @@
269
453
  border-radius: 6px;
270
454
  }
271
455
 
456
+ .menu:not(:has([role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio'])) {
457
+ display: none;
458
+ }
459
+
272
460
  .menuItem {
273
461
  padding: 5px 8px;
274
462
  color: var(--dsw-alias-label-primary);
@@ -284,12 +472,14 @@
284
472
  background: var(--dsw-alias-interactive-bg-hover);
285
473
  }
286
474
 
475
+ /* No padding of its own: a tab's body reaches the pane's edges and the strip's
476
+ bottom edge, and keeps its own insets, so a header row it draws sits flush
477
+ under the strip. */
287
478
  .paneBody {
288
479
  position: relative;
289
480
  flex: 1 1 auto;
290
481
  min-width: 0;
291
482
  min-height: 0;
292
- padding: 12px;
293
483
  overflow: auto; /* This sheet draws elevated surfaces (the strip and the menu), so a scroller
294
484
  inside it rebinds the thumb indirection in a complete pair — a base-surface
295
485
  thumb on an elevated ground reads as a smudge. */
@@ -299,14 +489,40 @@
299
489
 
300
490
  .empty {
301
491
  margin: 0;
492
+ padding: 12px;
302
493
  color: var(--dsw-alias-label-tertiary);
303
494
  font-size: var(--dsh-content-font-size-secondary, 13px);
304
495
  }
305
496
 
497
+ /* The drop hint is a scrim and, over it, one box pair per landing region.
498
+ The scrim (`.dockScrim`) covers the whole body with the translucent, blurred
499
+ ground, so the insets between and around the cards dim the body's content
500
+ instead of letting it show through raw. The outer box (`.dockHint`) is the
501
+ region a release fills — the whole body, or 40% (edge bands) or 50%
502
+ (horizontal halves) of it — and draws nothing; it is a padded frame that
503
+ keeps the card inside it 8px clear of the body's edges and, between two
504
+ horizontal halves, 4px clear of the seam, so neighbouring cards sit 8px
505
+ apart like the body's own insets. The card (`.dockHintCard`) is what the eye
506
+ lands on: a dashed frame with the zone's glyph and caption stacked at its
507
+ centre. A card that is not under the pointer stays as a quiet outline so the
508
+ reader sees where the other release would land. */
509
+ .dockScrim {
510
+ position: absolute;
511
+ inset: 0;
512
+ pointer-events: none;
513
+ /* The ground the panes sit on, not a raised layer: in dark mode `bg-layer-2`
514
+ is a lighter bluish step than the column's `bg-base`, which tinted the
515
+ whole scrim away from the content beneath it. */
516
+ background: color-mix(in srgb, var(--dsw-alias-bg-base) 72%, transparent);
517
+ backdrop-filter: blur(6px);
518
+ animation: dockScrimIn 140ms ease-out;
519
+ }
520
+
306
521
  .dockHint {
307
522
  position: absolute;
308
- background: var(--dsw-alias-bg-multi-select);
309
- border: 1px solid var(--dsw-alias-brand-primary-new-colorprimary-new-color);
523
+ display: flex;
524
+ box-sizing: border-box;
525
+ padding: 8px;
310
526
  pointer-events: none;
311
527
  }
312
528
 
@@ -328,15 +544,82 @@
328
544
  width: 40%;
329
545
  }
330
546
 
331
- [data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='left'],
547
+ [data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='left'] {
548
+ width: 50%;
549
+ padding-right: 4px;
550
+ }
551
+
332
552
  [data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='right'] {
333
553
  width: 50%;
554
+ padding-left: 4px;
334
555
  }
335
556
 
336
- [data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='left']:not([data-dockkit-drop-active]),
337
- [data-dockkit-drop-zones='horizontal'] .dockHint[data-dockkit-dock-zone='right']:not([data-dockkit-drop-active]) {
338
- background: transparent;
339
- border-color: var(--dsw-alias-border-l2);
557
+ .dockHintCard {
558
+ display: flex;
559
+ flex: 1 1 auto;
560
+ flex-direction: column;
561
+ gap: 8px;
562
+ align-items: center;
563
+ justify-content: center;
564
+ min-width: 0;
565
+ min-height: 0;
566
+ padding: 12px;
567
+ overflow: hidden;
568
+ color: var(--dsw-alias-label-tertiary);
569
+ border: 1.5px dashed var(--dsw-alias-border-l2);
570
+ border-radius: 12px;
571
+ transition: color 120ms ease-out, background-color 120ms ease-out, border-color 120ms ease-out;
572
+ animation: dockHintIn 140ms ease-out;
573
+ }
574
+
575
+ /* The card under the pointer takes the accent: a tinted ground over the scrim,
576
+ an accent dashed frame, and secondary ink for the glyph and caption. */
577
+ .dockHint[data-dockkit-drop-active] .dockHintCard {
578
+ color: var(--dsw-alias-label-secondary);
579
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color) 8%, transparent);
580
+ border-color: var(--dsw-alias-brand-primary-new-colorprimary-new-color);
581
+ }
582
+
583
+ .dockHintCard svg {
584
+ flex: none;
585
+ width: 20px;
586
+ height: 20px;
587
+ }
588
+
589
+ /* One line, clipped rather than wrapped: the card in a narrow half is still
590
+ wide enough for its glyph, and the caption fades under the frame. */
591
+ .dockHintLabel {
592
+ max-width: 100%;
593
+ overflow: hidden;
594
+ font-size: var(--dsh-content-font-size-secondary, 13px);
595
+ font-weight: 500;
596
+ line-height: 1.4;
597
+ white-space: nowrap;
598
+ text-overflow: ellipsis;
599
+ }
600
+
601
+ @keyframes dockHintIn {
602
+ from {
603
+ opacity: 0;
604
+ transform: scale(0.98);
605
+ }
606
+
607
+ to {
608
+ opacity: 1;
609
+ transform: none;
610
+ }
611
+ }
612
+
613
+ /* The scrim fades in without the cards' scale: a full-body cover that shrinks
614
+ would expose an uncovered rim of raw content on entry. */
615
+ @keyframes dockScrimIn {
616
+ from {
617
+ opacity: 0;
618
+ }
619
+
620
+ to {
621
+ opacity: 1;
622
+ }
340
623
  }
341
624
 
342
625
  .dockHint[data-dockkit-dock-zone='top'] {
@@ -357,43 +640,50 @@
357
640
  position: fixed;
358
641
  display: flex;
359
642
  flex-direction: column;
360
- background: var(--dsw-alias-bg-layer-1);
361
- border: 0.5px solid var(--dsw-alias-border-l2);
362
- border-radius: 8px;
363
- box-shadow: 0 8px 24px var(--dsw-alias-bg-mask-drop);
643
+ /* The body inside is unpadded and reaches the edges, so the frame clips it
644
+ to its own corners. */
645
+ overflow: hidden;
646
+ background: var(--dsw-alias-bg-layer-2);
647
+ border-radius: 20px;
648
+ /* The menu's shadow with the stroke rebound one step lighter than the
649
+ default (l2): the panel reads as the same kind of raised surface, and the
650
+ shadow's hairline outlines it, so the frame draws no border of its own. */
651
+ --dsw-elevation-stroke-color: var(--dsw-alias-border-l2);
652
+ box-shadow: var(--dsw-elevation-prominent);
364
653
  pointer-events: auto;
365
654
  }
366
655
 
367
- /* The active panel keeps the same hairline: it is already on top of the z
368
- order and casts the same shadow, and a heavier or darker frame read as a
369
- defect. `data-dockkit-float-active` stays on the element for tests. */
656
+ /* The active panel keeps the same frame: it is already on top of the z order
657
+ and casts the same shadow, and a heavier or darker frame read as a defect.
658
+ `data-dockkit-float-active` stays on the element for tests. */
370
659
 
660
+ /* The panel's header is the pane strip's row (`.tabStrip` supplies the
661
+ metrics) and the whole of it is the move grip. */
371
662
  .floatHeader {
372
- display: flex;
373
- flex: none;
374
- gap: 2px;
375
- align-items: center;
376
- height: 28px;
377
- padding: 0 4px 0 10px;
378
- border-bottom: 0.5px solid var(--dsw-alias-border-l1);
379
663
  cursor: move;
380
- touch-action: none;
381
664
  user-select: none;
382
665
  }
383
666
 
667
+ /* The one tab, drawn as its chip would be in a strip but never selected,
668
+ hovered, or closable from here: the frame's controls do that. */
384
669
  .floatTitle {
385
- flex: 1 1 auto;
386
- overflow: hidden;
387
- color: var(--dsw-alias-label-primary);
388
- font-size: var(--dsh-content-font-size-secondary, 13px);
389
- white-space: nowrap;
390
- text-overflow: ellipsis;
670
+ flex: 0 1 auto;
671
+ cursor: inherit;
672
+ }
673
+
674
+ .tab.floatTitle:hover {
675
+ background: transparent;
676
+ }
677
+
678
+ /* Mirrored so the filled pane sits at the right, where the docked surface is. */
679
+ .dockGlyph {
680
+ transform: scaleX(-1);
391
681
  }
392
682
 
683
+ /* Unpadded like `.paneBody`: the same body draws the same insets in a float. */
393
684
  .floatBody {
394
685
  flex: 1 1 auto;
395
686
  min-height: 0;
396
- padding: 10px;
397
687
  overflow: auto; /* This sheet draws elevated surfaces (the strip and the menu), so a scroller
398
688
  inside it rebinds the thumb indirection in a complete pair — a base-surface
399
689
  thumb on an elevated ground reads as a smudge. */
@@ -405,19 +695,35 @@
405
695
  position: absolute;
406
696
  right: 0;
407
697
  bottom: 0;
408
- width: 14px;
409
- height: 14px;
698
+ width: 20px;
699
+ height: 20px;
410
700
  cursor: nwse-resize;
411
701
  touch-action: none;
412
702
  }
413
703
 
704
+ /* The grip is a quarter arc concentric with the frame's 20px corner (16px
705
+ radius at a 4px inset), so it reads as part of the frame. It shows only
706
+ while the pointer is over the panel — an idle panel keeps a clean corner —
707
+ and `:active` keeps it lit while a drag holds the pointer capture. */
414
708
  .floatResize::after {
415
- position: absolute;
416
- right: 3px;
417
- bottom: 3px;
418
- width: 6px;
419
- height: 6px;
420
- border-right: 2px solid var(--dsw-alias-label-tertiary);
421
- border-bottom: 2px solid var(--dsw-alias-label-tertiary);
422
709
  content: '';
710
+ position: absolute;
711
+ right: 4px;
712
+ bottom: 4px;
713
+ width: 16px;
714
+ height: 16px;
715
+ border-right: 1.5px solid var(--dsw-alias-label-caption);
716
+ border-bottom: 1.5px solid var(--dsw-alias-label-caption);
717
+ border-bottom-right-radius: 16px;
718
+ opacity: 0;
719
+ transition: opacity 120ms ease-out;
720
+ }
721
+
722
+ .float:hover .floatResize::after,
723
+ .floatResize:active::after {
724
+ opacity: 1;
725
+ }
726
+
727
+ .floatResize:hover::after {
728
+ border-color: var(--dsw-alias-label-tertiary);
423
729
  }