@tldraw/collaboration 0.0.0-bootstrap → 5.3.0-canary.31036673d3f8

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/collaboration.css CHANGED
@@ -2,87 +2,227 @@
2
2
  /* This file is created by the copy-css-files.mjs script in packages/collaboration. */
3
3
  /* It combines the stylesheets of the collaboration umbrella's sub-packages. */
4
4
 
5
+ /* avatar — a person's image, or a single-initial coloured circle. Shared by the mention picker and,
6
+ via @tldraw/commenting, the comment UI. */
7
+ .tlui-cmt-avatar {
8
+ width: 28px;
9
+ height: 28px;
10
+ border-radius: 50%;
11
+ background: var(--tl-color-selected);
12
+ color: #fff;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ font-size: 11px;
17
+ font-weight: 600;
18
+ flex-shrink: 0;
19
+ }
20
+
21
+ .tlui-cmt-avatar--image {
22
+ object-fit: cover;
23
+ background: none;
24
+ }
25
+
26
+ /* mention pill */
27
+ .tlui-cmt-mention {
28
+ color: var(--tl-color-selected);
29
+ font-weight: 600;
30
+ background: color-mix(in srgb, var(--tl-color-selected) 12%, transparent);
31
+ padding: 0 4px;
32
+ border-radius: 4px;
33
+ }
34
+
35
+ /* mention picker */
36
+ .tlui-cmt-mention-list {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 4px;
40
+ padding: 4px;
41
+ width: 100%;
42
+ box-sizing: border-box;
43
+ max-height: 260px;
44
+ overflow-y: auto;
45
+ color: var(--tl-color-text-1, #1d1d1d);
46
+ background: var(--tl-color-panel, #fff);
47
+ border-radius: 10px;
48
+ /* shadow-3 (like the context menu) already carries a 1px inset edge — a separate `border`
49
+ on top of it double-outlines the popup. */
50
+ box-shadow: var(--tl-shadow-3, 0 2px 12px rgba(0, 0, 0, 0.14));
51
+ }
52
+
53
+ .tlui-cmt-mention-list__item {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ padding: 6px 8px;
58
+ border: none;
59
+ background: transparent;
60
+ border-radius: 6px;
61
+ cursor: pointer;
62
+ text-align: left;
63
+ color: var(--tl-color-text-1);
64
+ font: inherit;
65
+ }
66
+
67
+ .tlui-cmt-mention-list__item--active,
68
+ .tlui-cmt-mention-list__item:hover {
69
+ background: var(--tl-color-muted-2, rgba(0, 0, 0, 0.06));
70
+ }
71
+
72
+ .tlui-cmt-mention-list__text {
73
+ display: flex;
74
+ flex-direction: column;
75
+ min-width: 0;
76
+ line-height: 1.3;
77
+ }
78
+
79
+ .tlui-cmt-mention-list__name {
80
+ font-size: 12px;
81
+ font-weight: 500;
82
+ white-space: nowrap;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ }
86
+
87
+ .tlui-cmt-mention-list__you {
88
+ margin-left: 4px;
89
+ font-weight: 400;
90
+ color: var(--tl-color-text-3);
91
+ }
92
+
93
+ .tlui-cmt-mention-list__secondary {
94
+ font-size: 12px;
95
+ color: var(--tl-color-text-3);
96
+ white-space: nowrap;
97
+ overflow: hidden;
98
+ text-overflow: ellipsis;
99
+ }
100
+
101
+ .tlui-cmt-mention-list--empty {
102
+ padding: 8px 10px;
103
+ width: 100%;
104
+ box-sizing: border-box;
105
+ color: var(--tl-color-text-3);
106
+ font-size: 13px;
107
+ }
108
+
109
+ /* the @-picker popup — anchored under the editor field, positioned by the suggestion plugin */
110
+ .tlui-cmt-mention-popup {
111
+ position: fixed;
112
+ z-index: var(--tl-layer-menus);
113
+ }
114
+
5
115
  /* Real comment UI components. Co-located so styles travel to any view (studio,
6
116
  canvas), and themed via tldraw's --tl-color tokens so they follow light/dark. */
7
- .cmt-card {
117
+ .tlui-cmt-card {
8
118
  position: relative;
9
119
  display: flex;
10
- gap: 10px;
11
- padding: 12px;
120
+ flex-direction: column;
121
+ gap: 8px;
122
+ padding: 8px;
12
123
  width: 260px;
13
124
  background: var(--tl-color-panel);
14
125
  border: 1px solid var(--tl-color-divider);
15
- border-radius: 12px;
126
+ border-radius: var(--tl-radius-3);
127
+ }
128
+
129
+ /* The avatar lives in the byline row (see Byline), so the body text below indents to line up with
130
+ the name — the avatar's width plus the head's gap. */
131
+ .tlui-cmt-body {
132
+ padding-left: calc(28px + 6px);
16
133
  }
17
134
 
18
- /* hover-revealed controls (edit…) at the card's top-right */
19
- .cmt-card__actions {
135
+ /* hover-revealed controls (edit, react…) stacked at the card's top-right */
136
+ .tlui-cmt-card__actions {
20
137
  position: absolute;
21
138
  top: 6px;
22
139
  right: 6px;
23
140
  display: flex;
24
- gap: 2px;
141
+ gap: 0;
25
142
  opacity: 0;
26
143
  }
27
- .cmt-card:hover .cmt-card__actions,
28
- .cmt-card:focus-within .cmt-card__actions {
144
+ .tlui-cmt-card:hover .tlui-cmt-card__actions,
145
+ .tlui-cmt-card:focus-within .tlui-cmt-card__actions,
146
+ /* an open menu is portaled out of the card, so neither :hover nor :focus-within survives moving
147
+ the pointer into it — keep the controls up while their menu is open */
148
+ .tlui-cmt-card__actions:has([data-state='open']) {
29
149
  opacity: 1;
30
150
  }
31
151
 
32
- .cmt-body {
152
+ .tlui-cmt-body {
33
153
  flex: 1;
34
154
  min-width: 0;
35
155
  }
36
156
 
37
- .cmt-head {
157
+ .tlui-cmt-head {
38
158
  display: flex;
39
- align-items: baseline;
159
+ align-items: center;
40
160
  gap: 6px;
41
161
  }
42
162
 
43
- .cmt-author {
44
- font-weight: 600;
45
- font-size: 13px;
163
+ .tlui-cmt-author {
164
+ font-weight: 500;
165
+ font-size: 12px;
46
166
  color: var(--tl-color-text-1);
167
+ /* a long name ellipsizes rather than pushing the time out or running under the actions */
168
+ min-width: 0;
169
+ overflow: hidden;
170
+ text-overflow: ellipsis;
171
+ white-space: nowrap;
47
172
  }
48
173
 
49
- .cmt-time {
50
- font-size: 11px;
174
+ .tlui-cmt-time {
175
+ font-size: 12px;
51
176
  color: var(--tl-color-text-3);
177
+ flex-shrink: 0;
178
+ white-space: nowrap;
52
179
  }
53
180
 
54
- .cmt-text {
55
- margin: 3px 0 0;
56
- font-size: 13px;
181
+ /* In the hover preview a long comment truncates with an ellipsis rather than growing the card —
182
+ ~7 lines (≈120px at this line height). Preview only. */
183
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-text {
184
+ display: -webkit-box;
185
+ -webkit-box-orient: vertical;
186
+ -webkit-line-clamp: 7;
187
+ line-clamp: 7;
188
+ overflow: hidden;
189
+ }
190
+
191
+ .tlui-cmt-text {
192
+ margin: 0 0 6px;
193
+ font-size: 12px;
57
194
  line-height: 1.4;
58
195
  color: var(--tl-color-text-1);
196
+ /* Break long unbroken strings (URLs, keyboard-mashing) so they wrap inside the card instead of
197
+ overflowing it. `anywhere` also lets the flex body shrink to the card width. */
198
+ overflow-wrap: anywhere;
59
199
  }
60
200
 
61
- .cmt-edited {
201
+ .tlui-cmt-edited {
62
202
  font-style: italic;
63
203
  }
64
204
 
65
205
  /* rendered rich text inside a comment body — bare tags emitted by the comment renderer
66
206
  (paragraphs, lists, code, highlight, links), matching the limited comment extension set. */
67
- .cmt-text p {
207
+ .tlui-cmt-text p {
68
208
  margin: 0;
69
209
  }
70
210
 
71
- .cmt-text p + p,
72
- .cmt-text p + ul,
73
- .cmt-text p + ol,
74
- .cmt-text ul + p,
75
- .cmt-text ol + p {
211
+ .tlui-cmt-text p + p,
212
+ .tlui-cmt-text p + ul,
213
+ .tlui-cmt-text p + ol,
214
+ .tlui-cmt-text ul + p,
215
+ .tlui-cmt-text ol + p {
76
216
  margin-top: 6px;
77
217
  }
78
218
 
79
- .cmt-text ul,
80
- .cmt-text ol {
219
+ .tlui-cmt-text ul,
220
+ .tlui-cmt-text ol {
81
221
  margin: 0;
82
222
  padding-left: 18px;
83
223
  }
84
224
 
85
- .cmt-text code {
225
+ .tlui-cmt-text code {
86
226
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
87
227
  font-size: 0.92em;
88
228
  background: var(--tl-color-muted-2);
@@ -90,212 +230,263 @@
90
230
  padding: 0 3px;
91
231
  }
92
232
 
93
- .cmt-text mark {
233
+ .tlui-cmt-text mark {
94
234
  background: color-mix(in srgb, #ffeb3b 60%, transparent);
95
235
  color: inherit;
96
236
  border-radius: 2px;
97
237
  }
98
238
 
99
- .cmt-text a {
239
+ .tlui-cmt-text a {
100
240
  color: var(--tl-color-selected);
101
241
  text-decoration: underline;
102
242
  }
103
243
 
104
- .cmt-avatar {
105
- width: 28px;
106
- height: 28px;
107
- border-radius: 50%;
108
- background: var(--tl-color-selected);
109
- color: #fff;
110
- display: flex;
111
- align-items: center;
112
- justify-content: center;
113
- font-size: 11px;
114
- font-weight: 600;
115
- flex-shrink: 0;
116
- }
117
-
118
- .cmt-avatar--image {
119
- object-fit: cover;
120
- background: none;
121
- }
244
+ /* Avatar and mention styles live in @tldraw/mentions (src/mention.css) and are bundled into
245
+ commenting.css by copy-css-files.mjs. */
122
246
 
123
247
  /* composer — an avatar beside a rounded-rect field holding the rich-text input and the send button.
124
248
  Formatting is applied via markdown and keyboard shortcuts (no toolbar). The avatar aligns to the
125
249
  top so it stays put as the field grows to multiple lines. */
126
- .cmt-composer {
250
+ .tlui-cmt-composer {
127
251
  display: flex;
128
252
  align-items: flex-start;
129
253
  gap: 10px;
130
254
  padding: 4px 0 8px 4px;
131
255
  }
132
256
 
133
- .cmt-composer__field {
257
+ .tlui-cmt-composer__field {
134
258
  flex: 1;
135
259
  min-width: 0;
136
260
  display: flex;
137
261
  align-items: center;
138
262
  gap: 6px;
139
- padding: 3px 3px 3px 12px;
140
- background: var(--tl-color-muted-2);
263
+ padding: 3px 3px 3px 8px;
264
+ background: var(--tl-color-muted-0);
141
265
  border: 1px solid var(--tl-color-divider);
142
- border-radius: var(--tl-radius-3);
266
+ border-radius: var(--tl-radius-2);
267
+ /* The whole field reads as the text input — clicking its empty area focuses the editor
268
+ (see CommentComposer). The send button restores its own pointer cursor. */
269
+ cursor: text;
143
270
  }
144
271
 
145
- .cmt-composer__field:focus-within {
272
+ .tlui-cmt-composer__field:focus-within {
146
273
  border-color: var(--tl-color-selected);
274
+ background: var(--tl-color-panel);
275
+ }
276
+
277
+ /* Expanded: the content no longer fits on one line beside the send button, so the input takes
278
+ the full width and the send button drops to its own row, right-aligned. */
279
+ .tlui-cmt-composer__field--expanded {
280
+ flex-wrap: wrap;
281
+ /* No vertical gap between the input and the send-button row it wraps onto. */
282
+ row-gap: 0;
283
+ }
284
+ .tlui-cmt-composer__field--expanded .tlui-cmt-composer__input-wrap {
285
+ flex-basis: 100%;
286
+ /* The field pads 8px left but only 3px right (the send button's inset). With the input
287
+ spanning the full width, give the text the same 8px on the right. */
288
+ padding-right: 5px;
289
+ }
290
+ .tlui-cmt-composer__field--expanded .tlui-cmt-send {
291
+ margin-left: auto;
147
292
  }
148
293
 
149
294
  /* the editable area, with the placeholder overlaid while it's empty */
150
- .cmt-composer__input-wrap {
295
+ .tlui-cmt-composer__input-wrap {
151
296
  position: relative;
152
297
  flex: 1;
153
298
  min-width: 0;
154
- padding: 3px 0;
299
+ /* Frames the input's line height symmetrically so the text sits centred in the field. Paired
300
+ with the input's min-height, which equals its line height (no gap for the text to top-align
301
+ into). Keeps the field's overall height. */
302
+ padding: 4.5px 0;
155
303
  }
156
304
 
157
- .cmt-input {
305
+ /* The hidden single-line measuring clone of the editor's content (see CommentComposer). */
306
+ .tlui-cmt-composer__mirror {
307
+ position: absolute;
308
+ top: 0;
309
+ left: 0;
310
+ visibility: hidden;
311
+ pointer-events: none;
312
+ white-space: nowrap;
313
+ width: max-content;
314
+ max-height: none;
315
+ overflow: visible;
316
+ }
317
+
318
+ .tlui-cmt-input {
158
319
  font: inherit;
159
- font-size: 13px;
320
+ font-size: 12px;
160
321
  line-height: 1.4;
161
322
  color: var(--tl-color-text-1);
162
323
  outline: none;
163
- min-height: 20px;
164
- max-height: 40vh;
165
- overflow-y: auto;
324
+ min-height: 17px;
166
325
  white-space: pre-wrap;
167
326
  word-break: break-word;
168
327
  }
169
328
 
170
- .cmt-input p {
329
+ .tlui-cmt-input p {
171
330
  margin: 0;
172
331
  }
173
332
 
174
- .cmt-input p + p,
175
- .cmt-input p + ul,
176
- .cmt-input p + ol {
333
+ .tlui-cmt-input p + p,
334
+ .tlui-cmt-input p + ul,
335
+ .tlui-cmt-input p + ol {
177
336
  margin-top: 4px;
178
337
  }
179
338
 
180
- .cmt-input ul,
181
- .cmt-input ol {
339
+ .tlui-cmt-input ul,
340
+ .tlui-cmt-input ol {
182
341
  margin: 0;
183
342
  padding-left: 18px;
184
343
  }
185
344
 
186
- .cmt-input__placeholder {
345
+ .tlui-cmt-input__placeholder {
187
346
  position: absolute;
188
- top: 3px;
347
+ top: 4.5px;
189
348
  left: 0;
190
- font-size: 13px;
349
+ font-size: 12px;
191
350
  line-height: 1.4;
192
351
  color: var(--tl-color-text-3);
193
352
  pointer-events: none;
194
353
  }
195
354
 
196
- .cmt-send {
355
+ /* A bare up-arrow icon button (matching the placement composer): no fill by default, a grey square
356
+ on hover, grey while there's nothing to send and dark once there is. */
357
+ .tlui-cmt-send {
358
+ flex-shrink: 0;
359
+ display: inline-flex;
360
+ align-items: center;
361
+ justify-content: center;
362
+ width: 28px;
363
+ height: 28px;
197
364
  border: 0;
198
365
  border-radius: var(--tl-radius-2);
199
- background: var(--tl-color-selected);
200
- color: #fff;
201
- font: inherit;
202
- font-size: 12px;
203
- font-weight: 600;
204
- padding: 6px 12px;
366
+ background: transparent;
367
+ color: var(--tl-color-text-3);
205
368
  cursor: pointer;
206
- align-self: flex-end;
369
+ align-self: center;
370
+ }
371
+
372
+ .tlui-cmt-send:not(:disabled) {
373
+ color: var(--tl-color-text-1);
207
374
  }
208
375
 
209
- .cmt-send:hover,
210
- .pseudo-hover .cmt-send {
211
- background: color-mix(in srgb, var(--tl-color-selected) 88%, black);
376
+ .tlui-cmt-send:hover:not(:disabled) {
377
+ background: var(--tl-color-muted-2);
212
378
  }
213
379
 
214
- .cmt-send:active,
215
- .pseudo-active .cmt-send {
216
- background: color-mix(in srgb, var(--tl-color-selected) 78%, black);
380
+ .tlui-cmt-send:active:not(:disabled),
381
+ .pseudo-active .tlui-cmt-send:not(:disabled) {
382
+ background: var(--tl-color-muted-1);
217
383
  }
218
384
 
219
- .cmt-send:focus-visible,
220
- .pseudo-focus-visible .cmt-send {
385
+ .tlui-cmt-send:focus-visible,
386
+ .pseudo-focus-visible .tlui-cmt-send {
221
387
  outline: 2px solid var(--tl-color-selected);
222
388
  outline-offset: 2px;
223
389
  }
224
390
 
225
- .cmt-send:disabled {
226
- opacity: 0.45;
391
+ .tlui-cmt-send:disabled {
392
+ background: transparent;
393
+ color: var(--tl-color-text-3);
227
394
  cursor: not-allowed;
228
395
  }
229
396
 
230
- /* pin */
231
- .cmt-pin {
232
- width: 34px;
233
- height: 34px;
234
- border-radius: 50% 50% 50% 3px;
235
- background: var(--tl-color-selected);
397
+ /* The shared marker treatment. To the pointer a comment pin and a count badge are the same
398
+ object — a token sitting on the canvas — so they rest and lift identically; everything shared
399
+ lives here so the two can't drift apart. */
400
+ .tlui-cmt-marker {
401
+ box-shadow: var(--tlui-cmt-marker-shadow);
402
+ cursor: pointer;
403
+ transition:
404
+ transform var(--tlui-cmt-marker-transition),
405
+ box-shadow var(--tlui-cmt-marker-transition);
406
+ }
407
+
408
+ .tlui-cmt-marker:hover {
409
+ transform: scale(var(--tlui-cmt-marker-hover-scale));
410
+ }
411
+
412
+ /* The hover shadow is for closed markers only — an open one keeps its selection ring, which this
413
+ box-shadow would otherwise override (the hover selector outranks the --open rule). */
414
+ .tlui-cmt-marker:not(.tlui-cmt-marker--open):hover {
415
+ box-shadow: var(--tlui-cmt-marker-shadow-hover);
416
+ }
417
+
418
+ /* Count badges don't react to hover — no scale, no shadow lift. Only pins do. */
419
+ .tlui-cmt-count-badge:hover {
420
+ transform: none;
421
+ box-shadow: var(--tlui-cmt-marker-shadow);
422
+ }
423
+
424
+ /* Pins don't scale on hover — only the shadow lifts. */
425
+ .tlui-cmt-pin:hover {
426
+ transform: none;
427
+ }
428
+
429
+ /* open: the thread (or a stack's list) is showing — the active marker just keeps its lift shadow,
430
+ no selection ring. */
431
+ .tlui-cmt-marker--open {
432
+ box-shadow: var(--tlui-cmt-marker-shadow);
433
+ }
434
+
435
+ /* pin — a canvas marker: a white teardrop holding the author's avatar circle, so a thread is
436
+ identifiable at a glance. */
437
+ .tlui-cmt-pin {
438
+ --tlui-cmt-marker-size: var(--tlui-cmt-pin-size);
439
+ width: var(--tlui-cmt-marker-size);
440
+ height: var(--tlui-cmt-marker-size);
441
+ border-radius: 50% 50% 50% 0;
442
+ background: var(--tl-color-panel);
236
443
  color: #fff;
237
444
  display: flex;
238
445
  align-items: center;
239
446
  justify-content: center;
240
447
  font-size: 14px;
241
448
  font-weight: 600;
242
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
243
- cursor: pointer;
244
- transition:
245
- transform 0.08s ease,
246
- box-shadow 0.08s ease;
247
449
  }
248
-
249
- .cmt-pin:hover,
250
- .pseudo-hover .cmt-pin {
251
- transform: scale(1.08);
252
- box-shadow: 0 3px 10px rgba(0, 0, 0, 0.32);
253
- }
254
-
255
- /* open: the thread is showing — a selection ring marks the active pin */
256
- .cmt-pin--open {
257
- box-shadow:
258
- 0 0 0 3px var(--tl-color-panel),
259
- 0 0 0 5px var(--tl-color-selected),
260
- 0 2px 6px rgba(0, 0, 0, 0.25);
450
+ /* The pin holds the author's 22px avatar circle, centred in its 28px face. */
451
+ .tlui-cmt-pin .tlui-cmt-avatar {
452
+ width: 22px;
453
+ height: 22px;
261
454
  }
262
455
 
263
456
  /* resolved: a solid, muted grey (the default --tl-color-muted-1 is 10% black, so it goes
264
457
  translucent over the canvas). Dark mode gets its own grey — the token's dark value is near-black. */
265
- .cmt-pin--resolved {
458
+ .tlui-cmt-pin--resolved {
266
459
  background: #dfe0e1;
267
460
  color: hsl(214, 8%, 42%);
268
461
  }
269
- .tl-theme__dark .cmt-pin--resolved {
462
+ .tl-theme__dark .tlui-cmt-pin--resolved {
270
463
  background: hsl(240, 5%, 30%);
271
464
  color: hsl(0, 0%, 72%);
272
465
  }
273
466
 
274
- /* count badge */
275
- .cmt-count-badge {
276
- width: 34px;
277
- height: 34px;
467
+ /* count badge — the same marker as a pin. */
468
+ .tlui-cmt-count-badge {
469
+ width: var(--tlui-cmt-marker-size);
470
+ height: var(--tlui-cmt-marker-size);
278
471
  border-radius: 50%;
279
472
  display: flex;
280
473
  align-items: center;
281
474
  justify-content: center;
282
475
  background: var(--tl-color-text-1);
283
476
  color: var(--tl-color-panel);
284
- font-size: 13px;
477
+ font-size: 12px;
285
478
  font-weight: 700;
286
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
287
479
  user-select: none;
288
480
  }
289
481
 
290
482
  /* thread */
291
- .cmt-thread {
483
+ .tlui-cmt-thread {
292
484
  display: flex;
293
485
  flex-direction: column;
294
- gap: 8px;
295
486
  width: 300px;
296
487
  }
297
488
 
298
- .cmt-thread__head {
489
+ .tlui-cmt-thread__head {
299
490
  display: flex;
300
491
  align-items: center;
301
492
  gap: 8px;
@@ -306,7 +497,7 @@
306
497
  letter-spacing: 0.04em;
307
498
  }
308
499
 
309
- .cmt-badge {
500
+ .tlui-cmt-badge {
310
501
  font-size: 10px;
311
502
  font-weight: 600;
312
503
  text-transform: none;
@@ -318,134 +509,105 @@
318
509
  }
319
510
 
320
511
  /* reactions */
321
- .cmt-reactions {
512
+ .tlui-cmt-reactions {
322
513
  display: flex;
323
- gap: 6px;
514
+ flex-wrap: wrap;
515
+ gap: 4px;
516
+ margin-top: 6px;
324
517
  }
325
518
 
326
- .cmt-reaction {
519
+ .tlui-cmt-reaction {
327
520
  display: inline-flex;
328
521
  align-items: center;
329
- gap: 5px;
330
- padding: 3px 9px;
331
- border-radius: 999px;
332
- border: 1px solid var(--tl-color-divider);
333
- background: var(--tl-color-panel);
522
+ gap: 4px;
523
+ padding: 2px 6px;
524
+ border: none;
525
+ border-radius: var(--tl-radius-1);
526
+ background: var(--tl-color-muted-2);
334
527
  font: inherit;
335
- font-size: 12px;
528
+ font-size: 10px;
529
+ font-weight: 400;
336
530
  color: var(--tl-color-text-1);
337
531
  cursor: pointer;
338
532
  }
339
533
 
340
- .cmt-reaction:hover,
341
- .pseudo-hover .cmt-reaction {
342
- border-color: var(--tl-color-selected);
534
+ /* Hover deepens the grey — no border to tint now. Mixing toward the text colour is theme-aware:
535
+ it darkens the fill in light mode and lightens it in dark, tracking the pill's own contrast. */
536
+ .tlui-cmt-reaction:hover {
537
+ background: var(--tl-color-muted-1);
343
538
  }
344
539
 
345
- .cmt-reaction--active {
346
- border-color: var(--tl-color-selected);
347
- background: color-mix(in srgb, var(--tl-color-selected) 12%, var(--tl-color-panel));
540
+ /* a reaction you're part of fills with the selection colour, so "I reacted with this" stands out
541
+ from the grey pills of reactions you haven't joined. */
542
+ .tlui-cmt-reaction--active {
543
+ background: color-mix(in srgb, var(--tl-color-selected) 28%, var(--tl-color-panel));
348
544
  }
349
545
 
350
- .cmt-reaction__count {
351
- color: var(--tl-color-text-3);
546
+ .tlui-cmt-reaction--active:hover {
547
+ background: color-mix(in srgb, var(--tl-color-selected) 38%, var(--tl-color-panel));
352
548
  }
353
549
 
354
- .cmt-reaction--add {
550
+ /* the emoji sits at the same 10px as the count beside it */
551
+ .tlui-cmt-reaction__emoji {
552
+ font-size: 10px;
553
+ line-height: 1;
554
+ }
555
+
556
+ .tlui-cmt-reaction__count {
355
557
  color: var(--tl-color-text-3);
356
- font-weight: 600;
558
+ font-variant-numeric: tabular-nums;
357
559
  }
358
560
 
359
- /* mention */
360
- .cmt-mention {
361
- color: var(--tl-color-selected);
362
- font-weight: 600;
363
- background: color-mix(in srgb, var(--tl-color-selected) 12%, transparent);
364
- padding: 0 4px;
365
- border-radius: 4px;
561
+ .tlui-cmt-reaction--active .tlui-cmt-reaction__count {
562
+ color: var(--tl-color-text-1);
366
563
  }
367
564
 
368
- /* mention picker */
369
- .cmt-mention-list {
370
- display: flex;
371
- flex-direction: column;
372
- gap: 1px;
565
+ /* emoji picker */
566
+ .tlui-cmt-emoji-picker {
567
+ display: grid;
568
+ grid-template-columns: repeat(5, 40px);
569
+ gap: 0;
373
570
  padding: 4px;
374
- width: 100%;
375
- box-sizing: border-box;
376
- max-height: 260px;
377
- overflow-y: auto;
378
- color: var(--tl-color-text-1, #1d1d1d);
379
- background: var(--tl-color-panel, #fff);
380
- border: 1px solid var(--tl-color-divider, rgba(0, 0, 0, 0.08));
381
- border-radius: 10px;
382
- box-shadow: var(--tl-shadow-2, 0 2px 12px rgba(0, 0, 0, 0.14));
383
571
  }
384
572
 
385
- .cmt-mention-list__item {
573
+ .tlui-cmt-emoji-picker__item {
574
+ position: relative;
386
575
  display: flex;
387
576
  align-items: center;
388
- gap: 8px;
389
- padding: 6px 8px;
577
+ justify-content: center;
578
+ width: 40px;
579
+ height: 40px;
580
+ padding: 0;
390
581
  border: none;
391
- background: transparent;
392
- border-radius: 6px;
393
- cursor: pointer;
394
- text-align: left;
395
- color: var(--tl-color-text-1);
582
+ background: none;
396
583
  font: inherit;
584
+ font-size: 21px;
585
+ line-height: 1;
586
+ cursor: pointer;
397
587
  }
398
588
 
399
- .cmt-mention-list__item--active,
400
- .cmt-mention-list__item:hover {
401
- background: var(--tl-color-muted-2, rgba(0, 0, 0, 0.06));
402
- }
403
-
404
- .cmt-mention-list__text {
405
- display: flex;
406
- flex-direction: column;
407
- min-width: 0;
408
- line-height: 1.3;
409
- }
410
-
411
- .cmt-mention-list__name {
412
- font-size: 13px;
413
- font-weight: 500;
414
- white-space: nowrap;
415
- overflow: hidden;
416
- text-overflow: ellipsis;
417
- }
418
-
419
- .cmt-mention-list__you {
420
- margin-left: 4px;
421
- font-weight: 400;
422
- color: var(--tl-color-text-3);
589
+ /* Inset wash behind the glyph — the 40px hit cells tile edge-to-edge, the squares sit 4px apart,
590
+ like the thread action buttons. Sits under the emoji so an opaque selected fill can't hide it. */
591
+ .tlui-cmt-emoji-picker__item::after {
592
+ content: '';
593
+ position: absolute;
594
+ inset: 2px;
595
+ z-index: -1;
596
+ border-radius: var(--tl-radius-2);
423
597
  }
424
-
425
- .cmt-mention-list__secondary {
426
- font-size: 12px;
427
- color: var(--tl-color-text-3);
428
- white-space: nowrap;
429
- overflow: hidden;
430
- text-overflow: ellipsis;
598
+ .tlui-cmt-emoji-picker__item:hover {
599
+ z-index: 1;
431
600
  }
432
-
433
- .cmt-mention-list--empty {
434
- padding: 8px 10px;
435
- width: 100%;
436
- box-sizing: border-box;
437
- color: var(--tl-color-text-3);
438
- font-size: 13px;
601
+ .tlui-cmt-emoji-picker__item:hover::after {
602
+ background: var(--tl-color-muted-2);
439
603
  }
440
604
 
441
- /* the @-picker popup — caret-anchored above the page, positioned by the suggestion plugin */
442
- .cmt-mention-popup {
443
- position: fixed;
444
- z-index: var(--tl-layer-menus);
605
+ .tlui-cmt-emoji-picker__item--active::after {
606
+ background: color-mix(in srgb, var(--tl-color-selected) 28%, var(--tl-color-panel));
445
607
  }
446
608
 
447
609
  /* empty state */
448
- .cmt-empty {
610
+ .tlui-cmt-empty {
449
611
  display: flex;
450
612
  flex-direction: column;
451
613
  align-items: center;
@@ -455,50 +617,116 @@
455
617
  width: 240px;
456
618
  }
457
619
 
458
- .cmt-empty__icon {
620
+ .tlui-cmt-empty__icon {
459
621
  font-size: 28px;
460
622
  }
461
623
 
462
- .cmt-empty__message {
624
+ .tlui-cmt-empty__message {
463
625
  margin: 0;
464
626
  color: var(--tl-color-text-3);
465
- font-size: 13px;
627
+ font-size: 12px;
466
628
  line-height: 1.4;
467
629
  }
468
630
 
469
- /* a comment thread: an elevated panel the same surface treatment as the toolbar — holding a
470
- header, an optional resolved banner, the comments, and a reply composer */
471
- .cmt-thread {
631
+ /* a comment thread: an elevated panel holding a header, an optional resolved banner, the
632
+ comments, and a reply composer */
633
+ /* The thread panel's layout metrics live on the container because two separate portals need them:
634
+ the panel itself, and the canvas hover preview, which has to land its first comment on exactly
635
+ the pixel the panel would. Deriving the preview's offset from these means the two can't drift —
636
+ retune the padding or the header here and the preview follows. */
637
+ .tl-container {
638
+ /* Everything that has to line up against a pin — its preview's bridge, the draft composer's
639
+ offsets, `PIN_SIZE` in thread-view.tsx — derives from this, so the pin's size stays one
640
+ number. */
641
+ --tlui-cmt-pin-size: 28px;
642
+ /* A cluster badge and a stack badge are this square — the same size as a pin, so the marker
643
+ kinds read as one family. The hover preview measures its bridge against it, so the bridge
644
+ stops at the marker's edge instead of covering it. Mirrored by `MARKER_SIZE` in
645
+ thread-view.tsx. */
646
+ --tlui-cmt-marker-size: var(--tlui-cmt-pin-size);
647
+ /* How a marker sits, lifts, and rings. Shared so a pin and a badge respond identically.
648
+ shadow-3's depth without its `inset 1px panel-contrast` edge — that inset reads as a white
649
+ border on a coloured pin (it's invisible on the panel-white menus shadow-3 is meant for). */
650
+ --tlui-cmt-marker-shadow: 0px 1px 2px hsl(0, 0%, 0%, 28%), 0px 2px 6px hsl(0, 0%, 0%, 14%);
651
+ --tlui-cmt-marker-shadow-hover: var(--tlui-cmt-marker-shadow);
652
+ --tlui-cmt-marker-hover-scale: 1.08;
653
+ --tlui-cmt-marker-transition: 0.08s ease;
654
+ --tlui-cmt-thread-padding: 12px;
655
+ --tlui-cmt-thread-gap: 0px;
656
+ --tlui-cmt-thread-action-size: 26px;
657
+ /* A floating pill's edge — its hairline ring is the only thing separating it from the panel it
658
+ overlaps, so the ring is a theme token rather than a literal black: a near-black hairline
659
+ vanishes on a dark panel, where the divider token lightens instead. The drop below it stays
660
+ literal; a soft dark blur reads on both themes. */
661
+ --tlui-cmt-pill-shadow: 0 0 0 1px var(--tl-color-divider), 0 1px 3px hsl(0, 0%, 0%, 0.05);
662
+ }
663
+
664
+ .tlui-cmt-thread {
665
+ position: relative;
472
666
  display: flex;
473
667
  flex-direction: column;
474
- gap: 6px;
475
668
  width: 300px;
476
- padding: 8px;
477
669
  background: var(--tl-color-panel);
478
670
  border-radius: var(--tl-radius-4);
479
- box-shadow: var(--tl-shadow-2);
671
+ box-shadow: var(--tl-shadow-3);
672
+ }
673
+ /* A pill of its own, floating above the panel's top-right corner and overlapping its top edge. */
674
+ .tlui-cmt-thread__header {
675
+ position: absolute;
676
+ right: 10px;
677
+ bottom: calc(100% - 15px);
678
+ display: flex;
679
+ gap: 2px;
680
+ padding: 2px;
681
+ background: var(--tl-color-panel);
682
+ border-radius: var(--tl-radius-3);
683
+ box-shadow: var(--tlui-cmt-pill-shadow);
684
+ transition: opacity 0.1s ease;
685
+ }
686
+ /* The first comment's hover actions surface right where the header pill sits, so hide the pill
687
+ while that comment is hovered — one pill in the corner, never two stacked. */
688
+ .tlui-cmt-thread:has(.tlui-cmt-thread__list > :first-child:hover)
689
+ .tlui-cmt-thread__header:not(:has(.tlui-cmt-thread__title)) {
690
+ opacity: 0;
691
+ pointer-events: none;
480
692
  }
481
- .cmt-thread__header {
482
- display: flex;
693
+ /* A title puts the header back in the panel, in flow. The pill above is tuned for the actions-only
694
+ case tldraw itself uses: right-anchored and only as wide as its buttons, so a title inside it
695
+ would grow leftwards off the panel's edge and float over whatever sits above. `header` is public
696
+ API (see `CommentThreadProps`), so it has to keep laying out sensibly for anyone passing one. */
697
+ .tlui-cmt-thread__header:has(.tlui-cmt-thread__title) {
698
+ position: static;
483
699
  align-items: center;
484
- justify-content: space-between;
485
- padding: 2px 2px 2px 4px;
700
+ padding: 0;
701
+ background: none;
702
+ border-radius: 0;
703
+ box-shadow: none;
486
704
  }
487
- .cmt-thread__title {
705
+ .tlui-cmt-thread__title {
706
+ padding: 10px 12px;
488
707
  font-size: 12px;
489
- font-weight: 600;
708
+ line-height: 20px;
709
+ font-weight: 500;
490
710
  color: var(--tl-color-text-1);
491
711
  }
492
- .cmt-thread__actions {
712
+ .tlui-cmt-thread__actions {
493
713
  display: flex;
494
- gap: 2px;
495
- }
496
- .cmt-thread__action {
714
+ gap: 0;
715
+ /* Right-align the actions whether or not the header has a title (it currently has none, so
716
+ space-between alone would leave them at the left). */
717
+ margin-left: auto;
718
+ }
719
+ /* Follows tldraw's toolbar buttons (.tlui-button), used by both the header and card actions: the
720
+ button is the 40px hit target that tiles with its neighbours; its 32px ::after is the visual
721
+ square carrying the hover wash, so the hit area (not the ::after) is what's clickable and it
722
+ never reaches past its row. */
723
+ .tlui-cmt-thread__action {
724
+ position: relative;
497
725
  display: flex;
498
726
  align-items: center;
499
727
  justify-content: center;
500
- width: 22px;
501
- height: 22px;
728
+ width: 40px;
729
+ height: 40px;
502
730
  padding: 0;
503
731
  border: none;
504
732
  border-radius: 6px;
@@ -508,39 +736,156 @@
508
736
  line-height: 1;
509
737
  cursor: pointer;
510
738
  }
511
- .cmt-thread__action:hover {
512
- background: var(--tl-color-muted-2);
739
+ /* Overlap each button 4px into the previous so the 32px visual squares sit 4px apart, not 8px.
740
+ Left-only, so the last button's hit area still stops at its row's right edge. */
741
+ .tlui-cmt-thread__action + .tlui-cmt-thread__action {
742
+ margin-left: -4px;
743
+ }
744
+ .tlui-cmt-thread__action::after {
745
+ content: '';
746
+ position: absolute;
747
+ inset: 4px;
748
+ border-radius: var(--tl-radius-2);
749
+ }
750
+ .tlui-cmt-thread__action:hover {
513
751
  color: var(--tl-color-text-1);
752
+ /* Raise the hovered button above its overlapping neighbours so its 40px hit area wins the
753
+ overlap zone — the same z-index flip tldraw's own toolbar buttons use. */
754
+ z-index: 1;
755
+ }
756
+ .tlui-cmt-thread__action:hover::after {
757
+ background: var(--tl-color-muted-2);
758
+ }
759
+ .tlui-cmt-thread__action--danger:hover {
760
+ color: var(--tl-color-danger);
514
761
  }
515
- .cmt-thread__resolved {
762
+ .tlui-cmt-thread__resolved {
763
+ margin: 4px 12px;
516
764
  padding: 6px 10px;
517
765
  border-radius: 8px;
518
766
  background: var(--tl-color-muted-2);
519
767
  font-size: 12px;
520
768
  color: var(--tl-color-text-3);
521
769
  }
522
- .cmt-thread__list {
770
+ .tlui-cmt-thread__list {
523
771
  display: flex;
524
772
  flex-direction: column;
525
- gap: 6px;
773
+ gap: var(--tlui-cmt-thread-gap);
774
+ }
775
+ /* When nothing follows the list (a resolved thread has no composer or footer) the last comment
776
+ would sit against the panel's rounded bottom — restore the trailing inset the composer supplies. */
777
+ .tlui-cmt-thread__list:last-child {
778
+ padding-bottom: 12px;
779
+ }
780
+ /* The footer slot sits where the composer would — its content (e.g. a sign-in prompt) sits at
781
+ the thread's trailing edge, like the composer's send button. */
782
+ .tlui-cmt-thread__footer {
783
+ display: flex;
784
+ justify-content: flex-end;
785
+ padding: 4px 2px 2px;
786
+ }
787
+ /* Inside a thread, author identity is a small colour dot, no initial — the letter doesn't fit
788
+ at this size. */
789
+ .tlui-cmt-thread .tlui-cmt-avatar,
790
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-avatar,
791
+ .tlui-cmt-canvas-preview--list .tlui-cmt-avatar,
792
+ .tlui-cmt-stack-list .tlui-cmt-avatar {
793
+ width: 12px;
794
+ height: 12px;
795
+ font-size: 0;
796
+ }
797
+
798
+ .tlui-cmt-thread .tlui-cmt-composer {
799
+ padding: 12px;
800
+ }
801
+ .tlui-cmt-thread .tlui-cmt-composer .tlui-cmt-avatar {
802
+ display: none;
803
+ }
804
+
805
+ /* The sidebar's rows use the same small colour dot as the thread view. */
806
+ .tlui-cmt-list .tlui-cmt-avatar {
807
+ width: 14px;
808
+ height: 14px;
809
+ font-size: 0;
810
+ margin-top: 2px;
811
+ }
812
+ /* The sidebar row already leads with its own avatar (the flex row's first column); the byline's
813
+ avatar — added for the canvas thread views — would double it, so hide the byline's inside a row. */
814
+ .tlui-cmt-list .tlui-cmt-head .tlui-cmt-avatar {
815
+ display: none;
816
+ }
817
+
818
+ /* The dot treatment is for colour-only identity — an image avatar stays full size. */
819
+ .tlui-cmt-thread .tlui-cmt-avatar--image,
820
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-avatar--image,
821
+ .tlui-cmt-canvas-preview--list .tlui-cmt-avatar--image,
822
+ .tlui-cmt-stack-list .tlui-cmt-avatar--image {
823
+ /* Match the 12px colour dot's width so the body's calc(12px + 6px) indent still lines up under
824
+ the byline name when the author has an image rather than a dot. */
825
+ width: 12px;
826
+ height: 12px;
827
+ margin-top: 0;
828
+ }
829
+ .tlui-cmt-list .tlui-cmt-avatar--image {
830
+ width: 24px;
831
+ height: 24px;
832
+ margin-top: 0;
526
833
  }
834
+
527
835
  /* inside the panel, comments are plain content — no surrounding box; the composer's field carries
528
- its own surface. */
529
- .cmt-thread .cmt-card {
836
+ its own surface. The hover preview is the same panel without a header, so its comment is laid
837
+ out by the same rule rather than a copy of it. */
838
+ .tlui-cmt-thread .tlui-cmt-card,
839
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-card {
530
840
  width: 100%;
531
- padding: 6px 0 6px 4px;
841
+ gap: 0;
842
+ padding: 0 12px;
532
843
  background: transparent;
533
844
  border: none;
534
845
  }
846
+ /* The avatar is a 12px dot, so the body lines up 12px + the head's 6px gap in from the row. */
847
+ .tlui-cmt-thread .tlui-cmt-body,
848
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-body,
849
+ .tlui-cmt-canvas-preview--list .tlui-cmt-body,
850
+ .tlui-cmt-stack-list .tlui-cmt-body {
851
+ padding-left: calc(12px + 6px);
852
+ }
853
+ /* Inside a panel the actions float as their own pill — the same pill the thread header is, in the
854
+ same corner (which is why the header hides while the first comment is hovered). It sits just
855
+ above the card's top edge rather than on the byline. */
856
+ /* Line the card actions up with the header actions: flush to the full-width card's right edge (the
857
+ panel edge, matching the header ✕) and flush on the 40px byline row, so the 40px buttons fill it. */
858
+ .tlui-cmt-thread .tlui-cmt-card .tlui-cmt-card__actions {
859
+ top: 0;
860
+ right: 0;
861
+ }
862
+ /* The byline row is 40px: on the opened thread it fits the action pills (react/edit/delete) that
863
+ float in its corner without the text running underneath; previews and collapsed cards carry the
864
+ same height so every comment row reads identically, solo or in a stack. */
865
+ .tlui-cmt-thread .tlui-cmt-card .tlui-cmt-head,
866
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-card .tlui-cmt-head,
867
+ .tlui-cmt-canvas-preview--list .tlui-cmt-card .tlui-cmt-head,
868
+ .tlui-cmt-stack-list .tlui-cmt-card .tlui-cmt-head {
869
+ min-height: 40px;
870
+ }
871
+ /* Only the opened thread's comments actually carry the pills, so only they reserve room for them.
872
+ Derived from the action size so it can't drift: n buttons + (n−1) 2px gaps + the pill's 2px
873
+ padding either side + its 2px right inset. One button (react) on others' comments. */
874
+ .tlui-cmt-thread .tlui-cmt-card .tlui-cmt-head {
875
+ padding-right: calc(var(--tlui-cmt-thread-action-size) + 6px);
876
+ }
877
+ .tlui-cmt-thread .tlui-cmt-card--you .tlui-cmt-head {
878
+ padding-right: calc(3 * var(--tlui-cmt-thread-action-size) + 10px);
879
+ }
535
880
 
536
881
  /* comments list — thread summaries, one per row (used by the canvas sidebar) */
537
- .cmt-list {
882
+ .tlui-cmt-list {
538
883
  display: flex;
539
884
  flex-direction: column;
540
885
  width: 100%;
541
886
  min-height: 0;
542
887
  }
543
- .cmt-list__header {
888
+ .tlui-cmt-list__header {
544
889
  flex-shrink: 0;
545
890
  display: flex;
546
891
  align-items: center;
@@ -549,16 +894,15 @@
549
894
  padding: 6px 6px 6px 14px;
550
895
  border-bottom: 1px solid var(--tl-color-divider);
551
896
  }
552
- .cmt-list__header-title {
553
- font-size: 11px;
554
- font-weight: 600;
555
- letter-spacing: 0.06em;
556
- text-transform: uppercase;
557
- color: var(--tl-color-text-3);
897
+ .tlui-cmt-list__header-title {
898
+ font-size: 12px;
899
+ font-weight: 500;
900
+ color: var(--tl-color-text-1);
558
901
  }
559
902
 
560
903
  /* a small square icon button in the sidebar header (filter funnel, overflow …) */
561
- .cmt-header-btn {
904
+ .tlui-cmt-header-btn {
905
+ position: relative;
562
906
  display: flex;
563
907
  align-items: center;
564
908
  justify-content: center;
@@ -571,51 +915,83 @@
571
915
  color: var(--tl-color-text-2);
572
916
  cursor: pointer;
573
917
  }
574
- .cmt-header-btn:hover,
575
- .cmt-header-btn[data-state='open'] {
918
+ /* Same hit-target expansion as the thread actions, tiled the same way — this row's gap is 2px too
919
+ (see `.tlui-cmt-list__header-actions`). */
920
+ .tlui-cmt-header-btn::after {
921
+ content: '';
922
+ position: absolute;
923
+ inset: -6px -1px;
924
+ }
925
+ .tlui-cmt-header-btn:first-child::after {
926
+ left: -6px;
927
+ }
928
+ .tlui-cmt-header-btn:last-child::after {
929
+ right: -6px;
930
+ }
931
+ .tlui-cmt-header-btn:hover,
932
+ .tlui-cmt-header-btn[data-state='open'] {
576
933
  background: var(--tl-color-muted-2);
577
934
  color: var(--tl-color-text-1);
578
935
  }
579
- .cmt-list__header-actions {
936
+ .tlui-cmt-list__header-actions {
580
937
  display: flex;
581
938
  align-items: center;
582
939
  gap: 2px;
583
940
  }
584
941
 
942
+ /* The commenting dropdowns' surface — a little air between the items and the menu edge. */
943
+ .tlui-cmt-menu {
944
+ min-width: 100px;
945
+ padding: 4px;
946
+ }
947
+
585
948
  /* a menu row with a label and a right-aligned shortcut hint (overflow menu) */
586
- .cmt-menu-item {
949
+ .tlui-cmt-menu-item {
950
+ position: relative;
587
951
  display: flex;
588
952
  align-items: center;
589
953
  justify-content: space-between;
590
954
  gap: 24px;
591
955
  width: 100%;
592
- padding: 6px 8px;
956
+ min-height: 40px;
957
+ padding: 0 10px;
593
958
  border: none;
594
- border-radius: 6px;
595
959
  background: transparent;
596
960
  font: inherit;
597
- font-size: 13px;
961
+ font-size: 12px;
598
962
  color: var(--tl-color-text-1);
599
963
  cursor: pointer;
600
964
  }
601
- .cmt-menu-item:hover {
965
+ /* 36px visual inset within the 40px row — the hover wash the action and emoji buttons use. */
966
+ .tlui-cmt-menu-item::after {
967
+ content: '';
968
+ position: absolute;
969
+ inset: 2px;
970
+ border-radius: var(--tl-radius-2);
971
+ }
972
+ .tlui-cmt-menu-item:hover::after {
602
973
  background: var(--tl-color-muted-2);
603
974
  }
604
- .cmt-menu-item__shortcut {
975
+ /* Destructive menu entries (delete) — the same danger colour as tldraw's danger buttons. */
976
+ .tlui-cmt-menu-item--danger {
977
+ color: var(--tl-color-danger);
978
+ }
979
+
980
+ .tlui-cmt-menu-item__shortcut {
605
981
  color: var(--tl-color-text-3);
606
982
  font-size: 12px;
607
983
  }
608
- .cmt-list__items {
984
+ .tlui-cmt-list__items {
609
985
  flex: 1;
610
986
  min-height: 0;
611
987
  overflow-y: auto;
612
988
  }
613
- .cmt-list__empty {
989
+ .tlui-cmt-list__empty {
614
990
  padding: 20px 14px;
615
991
  color: var(--tl-color-text-3);
616
- font-size: 13px;
992
+ font-size: 12px;
617
993
  }
618
- .cmt-list__item {
994
+ .tlui-cmt-list__item {
619
995
  display: flex;
620
996
  gap: 10px;
621
997
  width: 100%;
@@ -626,34 +1002,48 @@
626
1002
  font: inherit;
627
1003
  text-align: left;
628
1004
  cursor: pointer;
1005
+ /* rows can render as anchors (for open-in-new-tab); keep them looking like list rows */
1006
+ color: inherit;
1007
+ text-decoration: none;
629
1008
  }
630
- .cmt-list__item:hover {
1009
+ .tlui-cmt-list__item:hover {
631
1010
  background: var(--tl-color-muted-2);
632
1011
  }
633
- .cmt-list__item--selected {
634
- background: var(--tl-color-muted-1);
1012
+ /* Selected sits a step above hover so the open thread's row stays distinguishable while
1013
+ pointing at other rows — but only a step: the muted-1 token (10%) reads too heavy here.
1014
+ These rows sit on the opaque sidebar panel, so a translucent tint composes safely. */
1015
+ .tlui-cmt-list__item--selected,
1016
+ .tlui-cmt-list__item--selected:hover {
1017
+ background: hsl(0, 0%, 0%, 5%);
1018
+ }
1019
+ .tl-theme__dark .tlui-cmt-list__item--selected,
1020
+ .tl-theme__dark .tlui-cmt-list__item--selected:hover {
1021
+ background: hsl(0, 0%, 100%, 5%);
635
1022
  }
636
1023
  /* resolved rows mute their content but keep the "Resolved" marker legible */
637
- .cmt-list__item[data-resolved] .cmt-head,
638
- .cmt-list__item[data-resolved] .cmt-list__item-preview {
1024
+ .tlui-cmt-list__item[data-resolved] .tlui-cmt-head,
1025
+ .tlui-cmt-list__item[data-resolved] .tlui-cmt-list__item-preview {
639
1026
  opacity: 0.55;
640
1027
  }
641
- .cmt-list__item-body {
1028
+ .tlui-cmt-list__item-body {
642
1029
  flex: 1;
643
1030
  min-width: 0;
644
1031
  }
645
- .cmt-list__item-preview {
1032
+ .tlui-cmt-list__item-preview {
646
1033
  margin-top: 2px;
647
- font-size: 13px;
1034
+ font-size: 12px;
648
1035
  color: var(--tl-color-text-1);
649
1036
  overflow: hidden;
650
1037
  text-overflow: ellipsis;
651
1038
  display: -webkit-box;
652
1039
  -webkit-line-clamp: 2;
653
1040
  -webkit-box-orient: vertical;
1041
+ /* Long unbroken strings (URLs, mashed keys) wrap onto the clamped lines instead of clipping
1042
+ mid-string — same treatment as the card body. */
1043
+ overflow-wrap: anywhere;
654
1044
  }
655
1045
  /* meta line under the preview: page label and/or a resolved marker */
656
- .cmt-list__item-meta {
1046
+ .tlui-cmt-list__item-meta {
657
1047
  display: flex;
658
1048
  align-items: center;
659
1049
  gap: 8px;
@@ -661,92 +1051,371 @@
661
1051
  font-size: 11px;
662
1052
  color: var(--tl-color-text-3);
663
1053
  }
664
- .cmt-list__item-resolved {
1054
+ .tlui-cmt-list__item-resolved {
665
1055
  display: inline-flex;
666
1056
  align-items: center;
667
1057
  gap: 3px;
668
1058
  font-weight: 600;
669
1059
  }
670
- .cmt-list__item-page {
1060
+ .tlui-cmt-list__item-page {
671
1061
  display: inline-flex;
672
1062
  align-items: center;
673
1063
  padding: 1px 6px;
674
1064
  border-radius: 4px;
675
1065
  background: var(--tl-color-muted-2);
676
1066
  }
677
- .cmt-list__item-count {
678
- flex-shrink: 0;
679
- align-self: center;
1067
+ .tlui-cmt-list__item-replies {
1068
+ display: inline-flex;
1069
+ align-items: center;
1070
+ }
1071
+ /* reply count under a preview card's opening comment — the thread continues past what's shown */
1072
+ .tlui-cmt-card__replies {
1073
+ display: block;
1074
+ margin-top: 6px;
680
1075
  font-size: 12px;
681
1076
  color: var(--tl-color-text-3);
682
1077
  }
683
1078
 
684
1079
  /* The comments layer is portaled into the editor container. Pins live in the canvas-in-front layer
685
1080
  (beneath the UI panels at z-index 300), so the toolbar and style panel draw over them; the open
686
- thread's popover portals up to the menus layer separately (`.cmt-canvas-popover`) so it isn't
1081
+ thread's popover portals up to the menus layer separately (`.tlui-cmt-canvas-popover`) so it isn't
687
1082
  clipped. Click-through except on its own children. */
688
- .cmt-canvas-layer {
1083
+ .tlui-cmt-canvas-layer {
689
1084
  position: absolute;
690
1085
  inset: 0;
691
- z-index: var(--tl-layer-canvas-in-front);
1086
+ /* Just below the collaborator cursor layer, so cursors ride over the pins — and below the
1087
+ in-front content (250), so active tool UI covers both. Derived, not a literal, so the
1088
+ ordering can't silently drift if the cursor slot moves. */
1089
+ z-index: calc(var(--tl-layer-collaborators) - 5);
692
1090
  pointer-events: none;
693
1091
  }
694
1092
 
695
- .cmt-canvas-pin,
696
- .cmt-canvas-cluster,
697
- .cmt-canvas-composer {
1093
+ .tlui-cmt-canvas-pin,
1094
+ .tlui-cmt-canvas-cluster,
1095
+ .tlui-cmt-canvas-composer {
698
1096
  position: absolute;
699
1097
  }
700
1098
 
701
- .cmt-canvas-pin,
702
- .cmt-canvas-composer {
1099
+ .tlui-cmt-canvas-composer {
703
1100
  pointer-events: auto;
704
1101
  }
705
1102
 
706
- /* Badges are clickable (zoom to the cluster's first split). */
707
- .cmt-canvas-cluster {
708
- transform: translate(-50%, -50%);
1103
+ /* The pin's only interactive surface is the square hit layer below — the wrapper itself is
1104
+ inert. Its box sits at the anchor point, not over the (translated) visual, so letting it take
1105
+ events would put a phantom hit zone beside the pin. */
1106
+ .tlui-cmt-canvas-pin {
1107
+ pointer-events: none;
1108
+ }
1109
+
1110
+ .tlui-cmt-button {
1111
+ appearance: none;
1112
+ margin: 0;
1113
+ padding: 0;
1114
+ border: none;
1115
+ background: none;
1116
+ color: inherit;
1117
+ font: inherit;
1118
+ }
1119
+
1120
+ /* Anchored by its bottom-left corner like a pin, so the badge sits up-and-right of the cluster's
1121
+ centroid — the same visual-to-anchor relationship a single comment pin has. */
1122
+ .tlui-cmt-canvas-cluster {
1123
+ transform: translate(0, -100%);
709
1124
  pointer-events: auto;
710
1125
  cursor: pointer;
711
1126
  }
712
1127
 
713
- .cmt-cluster-fade {
1128
+ /* Keyboard focus has to be visible on the canvas, where there's no surrounding chrome to imply
1129
+ it. The ring goes on the marker visual (the rounded pin/badge), not the button box, so it
1130
+ traces the shape rather than a rectangle around it; the button's own outline is suppressed
1131
+ because its box is offset from what you see. */
1132
+ .tlui-cmt-canvas-pin__marker:focus-visible,
1133
+ .tlui-cmt-canvas-cluster:focus-visible {
1134
+ outline: none;
1135
+ }
1136
+ .tlui-cmt-canvas-pin__marker:focus-visible .tlui-cmt-marker,
1137
+ .tlui-cmt-canvas-cluster:focus-visible .tlui-cmt-marker {
1138
+ outline: 2px solid var(--tl-color-selected);
1139
+ outline-offset: 2px;
1140
+ }
1141
+ /* Focus should read like hover, so the pin lifts the same way when tabbed to. */
1142
+ .tlui-cmt-canvas-pin__marker:focus-visible .tlui-cmt-marker {
1143
+ transform: scale(var(--tlui-cmt-marker-hover-scale));
1144
+ }
1145
+
1146
+ .tlui-cmt-cluster-fade {
714
1147
  opacity: 1;
715
1148
  transition: opacity 150ms ease;
716
1149
  }
717
1150
 
718
- .cmt-cluster-fade--entering {
1151
+ .tlui-cmt-cluster-fade--entering {
719
1152
  opacity: 0;
720
1153
  }
721
1154
 
722
- .cmt-cluster-fade--present {
1155
+ .tlui-cmt-cluster-fade--present {
723
1156
  opacity: 1;
724
1157
  }
725
1158
 
726
- .cmt-cluster-fade--exiting {
1159
+ .tlui-cmt-cluster-fade--exiting {
727
1160
  opacity: 0;
728
1161
  }
729
1162
 
730
- .cmt-cluster-fade--entering *,
731
- .cmt-cluster-fade--exiting * {
1163
+ .tlui-cmt-cluster-fade--entering *,
1164
+ .tlui-cmt-cluster-fade--exiting * {
732
1165
  pointer-events: none !important;
733
1166
  }
734
1167
 
735
1168
  @media (prefers-reduced-motion: reduce) {
736
- .cmt-cluster-fade {
1169
+ .tlui-cmt-cluster-fade {
737
1170
  transition: none;
738
1171
  }
739
1172
  }
740
1173
 
741
1174
  /* The open thread (its pin + popover) sits above the other pins, so nearby markers don't
742
1175
  overlap its popover — they tuck behind it. */
743
- .cmt-canvas-pin--open {
1176
+ .tlui-cmt-canvas-pin--open {
744
1177
  z-index: 1;
745
1178
  }
746
1179
 
1180
+ /* The count badge standing in for a stack of coincident pins — same face as a cluster badge,
1181
+ but clicking opens the stack's thread list rather than zooming (no zoom can separate them).
1182
+ Re-enables pointer events: the badge lives inside `.tlui-cmt-canvas-pin`, which is inert
1183
+ (`pointer-events: none`) so its box doesn't shadow the canvas — without this the badge renders
1184
+ but can't be hovered or clicked, like the cluster badge's own `pointer-events: auto`. */
1185
+ .tlui-cmt-canvas-stack-badge {
1186
+ /* Bottom-left anchored like the pins it stands in for, and like the cluster badge. */
1187
+ transform: translate(0, -100%);
1188
+ width: max-content;
1189
+ pointer-events: auto;
1190
+ cursor: pointer;
1191
+ }
1192
+
1193
+ .tlui-cmt-canvas-stack-badge:focus-visible {
1194
+ outline: none;
1195
+ }
1196
+ .tlui-cmt-canvas-stack-badge:focus-visible .tlui-cmt-marker {
1197
+ outline: 2px solid var(--tl-color-selected);
1198
+ outline-offset: 2px;
1199
+ transform: scale(var(--tlui-cmt-marker-hover-scale));
1200
+ }
1201
+
1202
+ /* The stack's popover content: one card per thread, the expanded thread inline among them. */
1203
+ .tlui-cmt-stack-list {
1204
+ display: flex;
1205
+ flex-direction: column;
1206
+ gap: 10px;
1207
+ }
1208
+
1209
+ /* The expanded thread is the only entry with pills floating above its panel — its header actions,
1210
+ and its first comment's hover actions. Those reach ~15px up, past the list's 10px gap, so give
1211
+ this one entry extra room: without it the pills land on the card above, and against the scroll
1212
+ box's clip edge. Collapsed cards keep their own actions inside, so they need none of this. */
1213
+ .tlui-cmt-stack-list__thread {
1214
+ margin-top: 8px;
1215
+ }
1216
+
1217
+ /* Collapsed entries match the expanded thread's surface (same width, panel, elevation) so the
1218
+ list reads as one column of cards with one of them opened up. */
1219
+ .tlui-cmt-stack-list__card {
1220
+ box-sizing: border-box;
1221
+ position: relative;
1222
+ width: 300px;
1223
+ background: var(--tl-color-panel);
1224
+ border-radius: 12px;
1225
+ box-shadow: var(--tl-shadow-3);
1226
+ padding: 2px 12px 10px;
1227
+ cursor: pointer;
1228
+ text-align: left;
1229
+ display: block;
1230
+ }
1231
+
1232
+ /* The card's keyboard affordance, covering the whole surface so it's one target rather than a
1233
+ control tucked in a corner. It's laid under the card's content instead of wrapping it: a comment
1234
+ body renders its links as anchors, which can't be nested inside a button (see the note in
1235
+ `StackThreadCard`). Pointer clicks anywhere still reach the card's own handler by bubbling. */
1236
+ .tlui-cmt-stack-list__card-action {
1237
+ position: absolute;
1238
+ inset: 0;
1239
+ border-radius: inherit;
1240
+ cursor: pointer;
1241
+ }
1242
+
1243
+ .tlui-cmt-stack-list__card-action:focus-visible {
1244
+ outline: 2px solid var(--tl-color-selected);
1245
+ outline-offset: 2px;
1246
+ }
1247
+
1248
+ /* The wrapper is the card's surface — inside it the comment is plain content, mirroring how the
1249
+ thread panel flattens its own cards. Stacked above the action overlay so the body's links stay
1250
+ clickable. */
1251
+ .tlui-cmt-stack-list__card .tlui-cmt-card {
1252
+ position: relative;
1253
+ width: 100%;
1254
+ gap: 0;
1255
+ padding: 0;
1256
+ background: transparent;
1257
+ border: none;
1258
+ }
1259
+
1260
+ /* The hover preview, shown for any marker — a pin, a coincident stack, or a cluster badge. Live,
1261
+ not a tooltip: the pointer travels into it to hover and click the cards.
1262
+
1263
+ Two elements, deliberately: this root is the hover *region* (it reaches back over the marker via
1264
+ the bridge below), and `__panel` inside it is the visible surface. A pseudo-element's hover
1265
+ counts as its host's, so a surface on this root would light up whenever the pointer was on the
1266
+ marker — the pin would appear pressed just for being hovered. */
1267
+ .tlui-cmt-canvas-preview {
1268
+ position: absolute;
1269
+ z-index: var(--tl-layer-menus);
1270
+ pointer-events: auto;
1271
+ }
1272
+
1273
+ /* The bridge over the gap between the marker and the panel. Moving right off the pin would
1274
+ otherwise cross bare canvas, and that leave would retract the panel before the pointer arrived.
1275
+ Invisible, part of the panel's hover region, and only present while the panel is.
1276
+
1277
+ It spans the gap and stops there. Reaching any further would put it over the marker itself, and
1278
+ since it sits in the menus layer — above the pins — it would take the marker's hover and clicks:
1279
+ the pin would go dead exactly where it looks most alive. The marker needs no covering anyway, as
1280
+ it carries the same hover handlers.
1281
+
1282
+ The gap is the panel's own offset (`--tlui-cmt-preview-offset`, set from `POPOVER_OFFSET` so
1283
+ there's one source) less however far the marker reaches toward it, per variant below. */
1284
+ .tlui-cmt-canvas-preview::before {
1285
+ content: '';
1286
+ position: absolute;
1287
+ top: 0;
1288
+ bottom: 0;
1289
+ right: 100%;
1290
+ width: max(0px, var(--tlui-cmt-preview-bridge, 0px));
1291
+ }
1292
+
1293
+ /* A pin is anchored at its bottom-left corner, so its full width lies between anchor and panel.
1294
+ The popover and preview share a first-comment position now that the actions float outside the
1295
+ panel rather than reserving a header row, so no vertical shift is needed. */
1296
+ .tlui-cmt-canvas-preview--thread {
1297
+ --tlui-cmt-preview-bridge: calc(var(--tlui-cmt-preview-offset) - var(--tlui-cmt-pin-size));
1298
+ }
1299
+
1300
+ /* A badge is anchored at its bottom-left corner like a pin, so its full width does too. */
1301
+ .tlui-cmt-canvas-preview--list {
1302
+ --tlui-cmt-preview-bridge: calc(var(--tlui-cmt-preview-offset) - var(--tlui-cmt-marker-size));
1303
+ }
1304
+
1305
+ .tlui-cmt-canvas-preview__panel {
1306
+ display: flex;
1307
+ flex-direction: column;
1308
+ }
1309
+
1310
+ /* Previewing a single pin: the thread panel itself, minus the header — the same surface, width,
1311
+ and flattened comment (see `.tlui-cmt-thread .tlui-cmt-card`), so the hover shows exactly what
1312
+ the click opens. */
1313
+ .tlui-cmt-canvas-preview__panel--thread {
1314
+ box-sizing: border-box;
1315
+ width: 300px;
1316
+ gap: var(--tlui-cmt-thread-gap);
1317
+ /* Top/bottom only — the card pads its own sides now (12px), matching the expanded thread. A
1318
+ little extra below so the (often truncated) last line doesn't sit against the rounded edge. */
1319
+ padding: 4px 0 8px;
1320
+ background: var(--tl-color-panel);
1321
+ border-radius: var(--tl-radius-4);
1322
+ box-shadow: var(--tl-shadow-3);
1323
+ }
1324
+ /* The reply count is a short line that sits closer to the edge than a wrapped comment does, so give
1325
+ it more room below when it's the panel's last line. */
1326
+ .tlui-cmt-canvas-preview__panel--thread:has(.tlui-cmt-card__replies) {
1327
+ padding-bottom: 12px;
1328
+ }
1329
+
1330
+ /* Previewing a stack or cluster: one card per thread, matching the card list a click opens. */
1331
+ .tlui-cmt-canvas-preview__panel--list {
1332
+ gap: 8px;
1333
+ padding: 4px 0 8px;
1334
+ }
1335
+
1336
+ /* Matches the stack list's card surface, so a preview and the list it previews read as the same
1337
+ object seen twice — once on hover, once opened. */
1338
+ .tlui-cmt-preview-card {
1339
+ box-sizing: border-box;
1340
+ width: 300px;
1341
+ background: var(--tl-color-panel);
1342
+ border-radius: 12px;
1343
+ box-shadow: var(--tl-shadow-3);
1344
+ /* A 2px nudge above the byline's centring — a boxed card reads a touch better with it than the
1345
+ single preview's flat card, which pads 0 vertically and leans on the panel + byline. */
1346
+ padding: 2px 12px 10px;
1347
+ }
1348
+
1349
+ .tlui-cmt-preview-card--selectable {
1350
+ cursor: pointer;
1351
+ }
1352
+
1353
+ /* Every card surface that tints on hover — a collapsed thread in a stack list, a preview card, and
1354
+ the single-comment preview panel. One rule, because they're the same object to the eye.
1355
+
1356
+ The opaque low-surface token, not a translucent muted one: these float directly over the canvas,
1357
+ where a translucent fill would let shapes show through.
1358
+
1359
+ The second line is why this isn't a one-liner. `--tl-shadow-3` ends with a 1px *inset* ring in
1360
+ `--tl-color-panel-contrast` — pure white in light mode — and an inset shadow paints over the
1361
+ background. At rest that's invisible (the panel is 99% white under a 100% white ring), but tint
1362
+ the background to grey and the ring stays white, hugging the edge as a pale outline. Re-tinting
1363
+ `--tl-color-panel-contrast` here wouldn't reach it: `--tl-shadow-3` is declared on the container,
1364
+ so its var() was already substituted there and the white is baked into what we inherit. Instead
1365
+ lay a ring of the new surface over the old one — first shadow in the list paints on top. */
1366
+ .tlui-cmt-stack-list__card:hover,
1367
+ .tlui-cmt-preview-card--selectable:hover,
1368
+ .tlui-cmt-canvas-preview__panel--thread.tlui-cmt-canvas-preview__panel--selectable:hover {
1369
+ --tlui-cmt-preview-hover-surface: color-mix(
1370
+ in srgb,
1371
+ var(--tl-color-low) 50%,
1372
+ var(--tl-color-panel)
1373
+ );
1374
+ background: var(--tlui-cmt-preview-hover-surface);
1375
+ box-shadow:
1376
+ inset 0 0 0 1px var(--tlui-cmt-preview-hover-surface),
1377
+ var(--tl-shadow-3);
1378
+ cursor: pointer;
1379
+ }
1380
+
1381
+ .tlui-cmt-preview-card .tlui-cmt-card {
1382
+ width: 100%;
1383
+ gap: 0;
1384
+ padding: 0;
1385
+ background: transparent;
1386
+ border: none;
1387
+ }
1388
+
1389
+ /* A preview is a glance, not a list — past a few cards it says how many were left out rather than
1390
+ growing to the height of the cluster. */
1391
+ .tlui-cmt-preview-more {
1392
+ box-sizing: border-box;
1393
+ width: 300px;
1394
+ padding: 6px 14px;
1395
+ border-radius: 12px;
1396
+ background: var(--tl-color-panel);
1397
+ box-shadow: var(--tl-shadow-3);
1398
+ color: var(--tl-color-text-3);
1399
+ font-size: 12px;
1400
+ }
1401
+
1402
+ @media (prefers-reduced-motion: no-preference) {
1403
+ .tlui-cmt-canvas-preview {
1404
+ animation: tlui-cmt-preview-in 120ms ease;
1405
+ }
1406
+ @keyframes tlui-cmt-preview-in {
1407
+ from {
1408
+ opacity: 0;
1409
+ }
1410
+ to {
1411
+ opacity: 1;
1412
+ }
1413
+ }
1414
+ }
1415
+
747
1416
  /* A region anchor's area: a dashed box under the pins, non-interactive so canvas events pass
748
1417
  through. Drawn for the live drag draft and for a region thread while hovered or open. */
749
- .cmt-canvas-region {
1418
+ .tlui-cmt-canvas-region {
750
1419
  position: absolute;
751
1420
  pointer-events: none;
752
1421
  border: 2px dashed var(--tl-color-selected);
@@ -756,13 +1425,13 @@
756
1425
 
757
1426
  /* A movable region body (the 'body'/'both' move modes): draggable to translate the whole region.
758
1427
  Interactive, so it captures the interior while shown — the tradeoff of dragging the body. */
759
- .cmt-canvas-region--movable {
1428
+ .tlui-cmt-canvas-region--movable {
760
1429
  pointer-events: auto;
761
1430
  cursor: move;
762
1431
  }
763
1432
 
764
1433
  /* Corner resize handles on a region box (three corners; the pin corner is the move handle). */
765
- .cmt-canvas-region-handle {
1434
+ .tlui-cmt-canvas-region-handle {
766
1435
  position: absolute;
767
1436
  width: 9px;
768
1437
  height: 9px;
@@ -774,59 +1443,200 @@
774
1443
  touch-action: none;
775
1444
  }
776
1445
 
777
- /* Centre the pin marker on its anchor point. */
778
- .cmt-canvas-pin__marker {
1446
+ /* Anchor the pin by its bottom-left corner, so the marker's one sharp corner (the pin shape's
1447
+ 3px bottom-left) points at the exact anchor point. */
1448
+ .tlui-cmt-canvas-pin__marker {
1449
+ position: relative;
779
1450
  width: max-content;
780
- transform: translate(-50%, -50%);
781
- cursor: grab;
1451
+ transform: translate(0, -100%);
1452
+ /* Default at rest, like hovering a shape; the editor's move cursor while dragging (below). */
1453
+ cursor: var(--tl-cursor-default);
782
1454
  touch-action: none;
1455
+ pointer-events: none;
1456
+ }
1457
+
1458
+ /* Dragging a pin shows the same move cursor as dragging a shape. */
1459
+ .tlui-cmt-canvas-pin--dragging .tlui-cmt-canvas-pin__marker {
1460
+ cursor: var(--tl-cursor-move);
1461
+ }
1462
+
1463
+ /* The predictable hit area: a fixed square over the marker's box, and the only part of the pin
1464
+ that takes pointer events. Events over it target the marker (a pseudo-element's events report
1465
+ its originating element), so the marker's handlers and :hover keep working — but the hit area
1466
+ no longer stretches with pin content or scales with the hover transform. */
1467
+ .tlui-cmt-canvas-pin__marker::after {
1468
+ content: '';
1469
+ position: absolute;
1470
+ inset: 0;
1471
+ pointer-events: auto;
1472
+ }
1473
+
1474
+ /* With the hit layer targeting the marker, the pin visual is never the hovered element itself
1475
+ (`:hover` matches the target and its ancestors, and the pin is the marker's child) — so the
1476
+ canvas pin's hover has to key off the hit layer instead of the marker's own `:hover`. Only the
1477
+ selector differs: the values come from the same tokens the shared rule uses, so a pin on the
1478
+ canvas and a badge (which is hovered directly) lift by exactly the same amount. */
1479
+ .tlui-cmt-canvas-pin__marker:hover .tlui-cmt-marker {
1480
+ transform: scale(var(--tlui-cmt-marker-hover-scale));
783
1481
  }
784
- .cmt-canvas-pin__marker:active {
785
- cursor: grabbing;
1482
+ .tlui-cmt-canvas-pin__marker:hover .tlui-cmt-marker:not(.tlui-cmt-marker--open) {
1483
+ box-shadow: var(--tlui-cmt-marker-shadow-hover);
1484
+ }
1485
+ /* The comment pin doesn't scale on hover — only the shadow lifts (badges still scale). */
1486
+ .tlui-cmt-canvas-pin__marker:hover .tlui-cmt-pin {
1487
+ transform: none;
786
1488
  }
787
1489
 
788
1490
  /* The open thread's popover — portaled to the menus layer (above the UI), positioned at the pin. */
789
- .cmt-canvas-popover {
1491
+ .tlui-cmt-canvas-popover {
790
1492
  position: absolute;
791
1493
  z-index: var(--tl-layer-menus);
792
1494
  pointer-events: auto;
793
1495
  }
794
1496
 
795
1497
  /* The placement composer — a distinct floating view (portaled to the menus layer, below the click
796
- point): a pencil "draft" avatar beside a pill field that is itself the surface (no wrapping
797
- panel). Scoped here so the in-thread reply composer keeps its own squarer look. */
798
- .cmt-canvas-composer {
1498
+ point): a draft avatar pin beside a pill field that is itself the surface (no wrapping panel).
1499
+ Scoped here so the in-thread reply composer keeps its own squarer look. */
1500
+ .tlui-cmt-canvas-composer {
799
1501
  position: absolute;
800
1502
  z-index: var(--tl-layer-menus);
801
1503
  pointer-events: auto;
802
- transform: translateY(8px);
803
- /* The rich-text editor has no intrinsic width (unlike the old <input>), so give the floating
804
- placement composer a definite width or it collapses to nothing. */
805
- width: 280px;
806
- }
807
- .cmt-canvas-composer .cmt-composer__field {
1504
+ /* Anchor the draft avatar bottom-left like the pins it becomes: it is a pin wide and sits 4px
1505
+ into the row, so its bottom-left corner is 4px right of and 4px + a pin below the composer's
1506
+ top-left corner. */
1507
+ transform: translate(-4px, calc(-4px - var(--tlui-cmt-pin-size)));
1508
+ }
1509
+
1510
+ /* A region placement centres the draft avatar on the region's pin corner — the pin it becomes
1511
+ straddles that corner. The avatar's centre sits 4px + half a pin into the composer. */
1512
+ .tlui-cmt-canvas-composer--region {
1513
+ --tlui-cmt-draft-avatar-centre: calc(4px + var(--tlui-cmt-pin-size) / 2);
1514
+ transform: translate(
1515
+ calc(-1 * var(--tlui-cmt-draft-avatar-centre)),
1516
+ calc(-1 * var(--tlui-cmt-draft-avatar-centre))
1517
+ );
1518
+ }
1519
+
1520
+ /* Fallback placement (no composer to show): no draft avatar to anchor by, so no offset — a small
1521
+ panel hanging off the click point, sized to its content (e.g. the host's sign-in prompt). */
1522
+ .tlui-cmt-canvas-composer--fallback {
1523
+ transform: none;
1524
+ width: auto;
1525
+ display: flex;
1526
+ flex-direction: column;
1527
+ padding: 8px;
808
1528
  background: var(--tl-color-panel);
809
- border-color: transparent;
810
1529
  border-radius: var(--tl-radius-4);
1530
+ box-shadow: var(--tl-shadow-3);
1531
+ }
1532
+ .tlui-cmt-canvas-composer .tlui-cmt-composer {
1533
+ gap: 6px;
1534
+ }
1535
+ /* The card: a fixed 300×42 surface whose uniform 3px padding derives the 294×36 focusable input. */
1536
+ .tlui-cmt-canvas-composer .tlui-cmt-composer__field {
1537
+ position: relative;
1538
+ flex: none;
1539
+ width: 300px;
1540
+ min-height: 42px;
1541
+ padding: 3px;
1542
+ border: none;
1543
+ background: var(--tl-color-panel);
1544
+ border-radius: var(--tl-radius-3);
1545
+ box-shadow: var(--tl-shadow-2);
1546
+ /* The pin is anchored by its bottom corner at the click point and can't move; raise the taller
1547
+ card onto the shorter pin's centre instead. */
1548
+ margin-top: -6px;
1549
+ }
1550
+ /* Focused (the placement composer autofocuses on mount): a selection-blue ring inset 3px from the
1551
+ card edge, so the card frames a bordered input rather than the border sitting on the card edge. */
1552
+ .tlui-cmt-canvas-composer .tlui-cmt-composer__field:focus-within::before {
1553
+ content: '';
1554
+ position: absolute;
1555
+ inset: 3px;
1556
+ border: 1px solid var(--tl-color-selected);
1557
+ border-radius: var(--tl-radius-2);
1558
+ pointer-events: none;
1559
+ }
1560
+ /* The text sits 12px in from the focus ring rather than against it. The placeholder is absolutely
1561
+ positioned (it ignores the wrap's padding), so it carries the same offset itself. */
1562
+ .tlui-cmt-canvas-composer .tlui-cmt-composer__input-wrap {
1563
+ padding: 4.5px 12px;
1564
+ }
1565
+ .tlui-cmt-canvas-composer .tlui-cmt-composer__field--expanded .tlui-cmt-composer__input-wrap {
1566
+ /* The base expanded rule re-pads the right edge for the squarer composer's geometry; here the
1567
+ 12px is already symmetric. */
1568
+ padding-right: 12px;
1569
+ }
1570
+ .tlui-cmt-canvas-composer .tlui-cmt-input__placeholder {
1571
+ left: 12px;
1572
+ }
1573
+ /* The draft avatar previews the pin it becomes — a white 28px pin holding the author's 22px avatar
1574
+ circle — sharing the composer's lift. Not interactive: it drops the pin's pointer cursor and
1575
+ hover affordances (scale + shadow bump), but keeps a 40×40 hit area (the ::after below). */
1576
+ .tlui-cmt-canvas-composer .tlui-cmt-pin {
1577
+ position: relative;
1578
+ box-shadow: var(--tl-shadow-2);
1579
+ cursor: default;
1580
+ }
1581
+ .tlui-cmt-canvas-composer .tlui-cmt-pin::after {
1582
+ content: '';
1583
+ position: absolute;
1584
+ inset: -6px;
1585
+ }
1586
+ .tlui-cmt-canvas-composer .tlui-cmt-pin:hover {
1587
+ transform: none;
811
1588
  box-shadow: var(--tl-shadow-2);
812
1589
  }
1590
+ /* The placement composer's send button — a bare arrow, no pill: grey while empty, black once
1591
+ there's something to send. A 28px square centred in the 36px input height, so its hover wash
1592
+ clears the focus ring rather than butting against it; the ::after keeps a 40×40 hit area. */
1593
+ .tlui-cmt-canvas-composer .tlui-cmt-send {
1594
+ position: relative;
1595
+ width: 28px;
1596
+ height: 28px;
1597
+ margin-right: 4px;
1598
+ background: transparent;
1599
+ border-radius: var(--tl-radius-2);
1600
+ color: var(--tl-color-text-3);
1601
+ }
1602
+ .tlui-cmt-canvas-composer .tlui-cmt-send::after {
1603
+ content: '';
1604
+ position: absolute;
1605
+ inset: -6px;
1606
+ }
1607
+ .tlui-cmt-canvas-composer .tlui-cmt-send:not(:disabled) {
1608
+ color: var(--tl-color-text-1);
1609
+ }
1610
+ /* Hover and press wash grey behind the bare arrow (the same treatment as the thread actions),
1611
+ overriding the base button's blue fill. */
1612
+ .tlui-cmt-canvas-composer .tlui-cmt-send:hover:not(:disabled) {
1613
+ background: var(--tl-color-muted-2);
1614
+ }
1615
+ .tlui-cmt-canvas-composer .tlui-cmt-send:active:not(:disabled) {
1616
+ background: var(--tl-color-muted-1);
1617
+ }
1618
+ /* In the expanded (multi-line) layout the send drops to the bottom-right corner; give it the same
1619
+ 4px gap from the focus ring on the bottom that it already keeps on the right. */
1620
+ .tlui-cmt-canvas-composer .tlui-cmt-composer__field--expanded .tlui-cmt-send {
1621
+ margin-bottom: 4px;
1622
+ }
813
1623
 
814
- /* The comments list panel — a right-side surface shown while the comment tool is active. Below
815
- the popover layer, so an open thread still draws over it. Sits under the top-right share panel
816
- (top offset clears its ~48px height) and sizes to its content, scrolling past max-height rather
817
- than stretching the full canvas height. */
818
- .cmt-canvas-sidebar {
1624
+ /* The comments list panel — a right-side surface shown while the comments sidebar is open. Below
1625
+ the popover layer, so an open thread still draws over it. Its top aligns flush with the style
1626
+ panel (48px from the top, clearing the share panel) and it sizes to its content, scrolling past
1627
+ max-height rather than stretching the full canvas height. */
1628
+ .tlui-cmt-canvas-sidebar {
819
1629
  position: absolute;
820
- top: 56px;
1630
+ top: 48px;
821
1631
  right: 8px;
822
1632
  width: 280px;
823
- max-height: calc(100% - 64px);
1633
+ max-height: calc(100% - 56px);
824
1634
  z-index: var(--tl-layer-panels);
825
1635
  pointer-events: auto;
826
1636
  display: flex;
827
1637
  background: var(--tl-color-panel);
828
1638
  border-radius: var(--tl-radius-4);
829
- box-shadow: var(--tl-shadow-2);
1639
+ box-shadow: var(--tl-shadow-3);
830
1640
  overflow: hidden;
831
1641
  }
832
1642