privateboard 0.1.37 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot.js +1415 -91
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +1415 -91
- package/dist/cli.js.map +1 -1
- package/dist/server.js +1271 -81
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/public/__avatar3d_test.html +156 -0
- package/public/adjourn-overlay.css +2 -2
- package/public/agent-overlay.css +27 -15
- package/public/agent-overlay.js +3 -1
- package/public/agent-profile.css +331 -41
- package/public/agent-profile.js +499 -75
- package/public/app-updater.css +1 -1
- package/public/app.js +2090 -547
- package/public/avatar-3d-snap.js +205 -0
- package/public/avatar-3d.js +792 -0
- package/public/avatar-customizer.html +274 -0
- package/public/avatar3d-editor.css +240 -0
- package/public/avatar3d-editor.js +481 -0
- package/public/avatars/3d/chair.png +0 -0
- package/public/avatars/3d/first-principles.png +0 -0
- package/public/avatars/3d/historian.png +0 -0
- package/public/avatars/3d/long-horizon.png +0 -0
- package/public/avatars/3d/phenomenologist.png +0 -0
- package/public/avatars/3d/socrates.png +0 -0
- package/public/avatars/3d/user-empathy.png +0 -0
- package/public/avatars/3d/value-investor.png +0 -0
- package/public/core-avatars.js +86 -0
- package/public/home-3d-loader.js +15 -4
- package/public/home-3d-mock.js +18 -7
- package/public/home.html +80 -18
- package/public/i18n.js +279 -4
- package/public/icons/avatar_1779855104027.glb +0 -0
- package/public/icons/logo.png +0 -0
- package/public/icons/new-style.glb +0 -0
- package/public/icons/new-style2.glb +0 -0
- package/public/icons/new-style3.glb +0 -0
- package/public/icons/new-style4.glb +0 -0
- package/public/icons/new-style5.glb +0 -0
- package/public/icons/office.glb +0 -0
- package/public/icons/stuff.glb +0 -0
- package/public/index.html +203 -182
- package/public/mention-picker.js +1 -1
- package/public/new-agent.css +7 -7
- package/public/new-agent.js +46 -20
- package/public/office-viewer.html +340 -0
- package/public/onboarding.css +5 -5
- package/public/quote-cta.css +5 -4
- package/public/quote-cta.js +50 -5
- package/public/room-settings.css +24 -9
- package/public/stuff-viewer.html +330 -0
- package/public/thread.css +1211 -0
- package/public/user-settings.css +16 -19
- package/public/user-settings.js +86 -78
- package/public/vendor/BufferGeometryUtils.js +1434 -0
- package/public/vendor/DRACOLoader.js +739 -0
- package/public/vendor/GLTFLoader.js +4860 -0
- package/public/vendor/RoomEnvironment.js +185 -0
- package/public/vendor/SkeletonUtils.js +496 -0
- package/public/vendor/draco/draco_decoder.js +34 -0
- package/public/vendor/draco/draco_decoder.wasm +0 -0
- package/public/vendor/draco/draco_encoder.js +33 -0
- package/public/vendor/draco/draco_wasm_wrapper.js +117 -0
- package/public/vendor/meshopt_decoder.module.js +196 -0
- package/public/voice-3d-banner.js +12 -0
- package/public/voice-3d.js +1407 -432
- package/public/voice-clone.css +875 -0
- package/public/voice-clone.js +1351 -0
- package/public/voice-replay.css +3 -3
- package/public/voice-replay.js +21 -0
- package/public/avatar-skill.js +0 -629
- package/public/icons/folded-sidebar.png +0 -0
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
2
|
+
thread.css · Private 1:1 thread float window (Slack DM-style)
|
|
3
|
+
═══════════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
Why this lives in its own file:
|
|
6
|
+
- Thread is an "epic feature" — it'll grow more chrome (dock,
|
|
7
|
+
pinned threads, search). Keeping its styles isolated makes
|
|
8
|
+
review + iteration easier than burying them in index.html's
|
|
9
|
+
inline block.
|
|
10
|
+
- The visual vocabulary deliberately copies adjourn / cast-edit
|
|
11
|
+
overlays — same border colors, same panel hierarchy — so the
|
|
12
|
+
float window reads as part of the same product family, just
|
|
13
|
+
in a more casual register (rounded corners, smaller padding,
|
|
14
|
+
no classification stripe).
|
|
15
|
+
|
|
16
|
+
Architectural notes:
|
|
17
|
+
- The float root uses `position: fixed` with explicit `right/bottom`
|
|
18
|
+
so the user's screen real estate isn't disturbed (main room
|
|
19
|
+
keeps running underneath). Drag updates `transform: translate(...)`
|
|
20
|
+
so we don't fight the fixed positioning baseline.
|
|
21
|
+
- z-index sits above adjourn (9400) and supplement (9000) overlays
|
|
22
|
+
but below the agent-profile dropdowns the user might still want
|
|
23
|
+
to use; we land at 8500 — high enough to win the main view,
|
|
24
|
+
low enough that "real" modals supersede.
|
|
25
|
+
- Multiple threads stack with per-instance right-offset (set
|
|
26
|
+
inline by the mounting JS); we don't try to manage that in CSS.
|
|
27
|
+
═══════════════════════════════════════════════════════════════════ */
|
|
28
|
+
|
|
29
|
+
.thread-float {
|
|
30
|
+
position: fixed;
|
|
31
|
+
right: 24px;
|
|
32
|
+
bottom: 24px;
|
|
33
|
+
/* Default 360px in both float and docked forms · matches the
|
|
34
|
+
sidebar's room-list width register so the panel reads as
|
|
35
|
+
"extended sidebar real estate" rather than a heavy overlay.
|
|
36
|
+
The dock variant overrides to a CSS variable (clamped 320-720)
|
|
37
|
+
so the user can drag-resize. */
|
|
38
|
+
width: 360px;
|
|
39
|
+
height: 520px;
|
|
40
|
+
max-height: calc(100vh - 96px);
|
|
41
|
+
z-index: 8500;
|
|
42
|
+
/* Frosted-glass header + composer overlay the messages list;
|
|
43
|
+
scroll-under requires `position: relative` so the absolute
|
|
44
|
+
children anchor to the panel. */
|
|
45
|
+
background: var(--panel);
|
|
46
|
+
border: 0.5px solid var(--line-strong);
|
|
47
|
+
border-radius: 6px;
|
|
48
|
+
box-shadow:
|
|
49
|
+
0 8px 24px -8px rgba(0, 0, 0, 0.45),
|
|
50
|
+
0 4px 12px -4px rgba(0, 0, 0, 0.3);
|
|
51
|
+
font-family: var(--mono);
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
/* Header height token · single-line title + subtitle stack +
|
|
54
|
+
padding. Consumed by `.thread-float-messages` via
|
|
55
|
+
`padding-top: calc(var(--thread-head-h) + 14px)` so the
|
|
56
|
+
scrollable area starts cleanly below the absolute-positioned
|
|
57
|
+
head. Mirror of `.main-view`'s `--room-head-h` pattern. */
|
|
58
|
+
--thread-head-h: 60px;
|
|
59
|
+
/* No `transition` on transform · the JS drag handler writes
|
|
60
|
+
`transform: translate(x, y)` on every mousemove (60Hz); if
|
|
61
|
+
transform were animated, each frame would queue a 150ms ease
|
|
62
|
+
that fights the next frame's write, making the window visibly
|
|
63
|
+
lag behind the cursor. Fixed positioning + bare transform
|
|
64
|
+
writes track the mouse 1:1. */
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Minimized state · the window collapses into a compact pill that
|
|
68
|
+
shows just the director's avatar + name. Lives in the dock bar
|
|
69
|
+
along with any other minimized threads. Restoring expands back
|
|
70
|
+
to the full window. */
|
|
71
|
+
.thread-float.is-minimized {
|
|
72
|
+
position: relative;
|
|
73
|
+
right: auto;
|
|
74
|
+
bottom: auto;
|
|
75
|
+
width: auto;
|
|
76
|
+
height: auto;
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
padding: 4px 12px 4px 4px;
|
|
79
|
+
/* Now that the base `.thread-float` no longer sets `display:
|
|
80
|
+
flex` (the absolute-positioned children don't need it), be
|
|
81
|
+
explicit here so the minimized pill still lays out as a row. */
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: row;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 8px;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
box-shadow: 0 2px 6px -2px rgba(0, 0, 0, 0.35);
|
|
88
|
+
overflow: visible;
|
|
89
|
+
}
|
|
90
|
+
.thread-float.is-minimized .thread-float-messages,
|
|
91
|
+
.thread-float.is-minimized .thread-float-composer,
|
|
92
|
+
.thread-float.is-minimized .thread-float-head-controls {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
.thread-float.is-minimized .thread-float-head {
|
|
96
|
+
/* In minimized form the header is the pill's only visible
|
|
97
|
+
content · pull it out of the absolute layer and back into the
|
|
98
|
+
pill's flex row. Clear the frosted-glass chrome too — the
|
|
99
|
+
pill itself draws the background. */
|
|
100
|
+
position: static;
|
|
101
|
+
background: transparent;
|
|
102
|
+
backdrop-filter: none;
|
|
103
|
+
-webkit-backdrop-filter: none;
|
|
104
|
+
border-bottom: 0;
|
|
105
|
+
border-radius: 999px;
|
|
106
|
+
padding: 0;
|
|
107
|
+
}
|
|
108
|
+
.thread-float.is-minimized .thread-float-head-title {
|
|
109
|
+
font-size: 11px;
|
|
110
|
+
}
|
|
111
|
+
.thread-float.is-minimized .thread-float-head-subtitle {
|
|
112
|
+
display: none;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ── Header · drag handle + identity + window controls ──────────
|
|
116
|
+
Frosted glass · sits absolutely positioned over the top of the
|
|
117
|
+
messages list. Same backdrop-filter recipe as the room's
|
|
118
|
+
`.room-head` so the two surfaces read as one chrome family.
|
|
119
|
+
Messages list scrolls UNDER this header (padding-top on the
|
|
120
|
+
messages container creates the initial offset). */
|
|
121
|
+
.thread-float-head {
|
|
122
|
+
position: absolute;
|
|
123
|
+
top: 0;
|
|
124
|
+
left: 0;
|
|
125
|
+
right: 0;
|
|
126
|
+
z-index: 10;
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: 8px;
|
|
130
|
+
padding: 12px 10px 8px 12px;
|
|
131
|
+
background: color-mix(in srgb, var(--panel-3) 78%, var(--bg) 22%);
|
|
132
|
+
background: color-mix(in srgb, color-mix(in srgb, var(--panel-3) 78%, var(--bg) 22%) 88%, transparent);
|
|
133
|
+
backdrop-filter: blur(24px) saturate(180%);
|
|
134
|
+
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
|
135
|
+
border-bottom: 0.5px solid var(--line-bright, rgba(255, 255, 255, 0.18));
|
|
136
|
+
border-radius: 6px 6px 0 0;
|
|
137
|
+
cursor: grab;
|
|
138
|
+
user-select: none;
|
|
139
|
+
}
|
|
140
|
+
.thread-float-head:active { cursor: grabbing; }
|
|
141
|
+
/* Header title block · two lines now (thread name + director
|
|
142
|
+
subtitle). Mirrors the room-head's title / subtitle stack
|
|
143
|
+
register so the two surfaces read the same way. */
|
|
144
|
+
.thread-float-head-title {
|
|
145
|
+
font-family: var(--sans, "Inter", system-ui, sans-serif);
|
|
146
|
+
font-size: 14px;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
color: var(--text);
|
|
149
|
+
white-space: nowrap;
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
text-overflow: ellipsis;
|
|
152
|
+
}
|
|
153
|
+
.thread-float-head-subtitle {
|
|
154
|
+
font-size: 10px;
|
|
155
|
+
letter-spacing: 0.04em;
|
|
156
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.55));
|
|
157
|
+
white-space: nowrap;
|
|
158
|
+
overflow: hidden;
|
|
159
|
+
text-overflow: ellipsis;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* ── Edge resize handles ──────────────────────────────────────────
|
|
163
|
+
The float is bottom-anchored. The TOP handle grows/shrinks height
|
|
164
|
+
with the bottom pinned; the BOTTOM handle moves the bottom edge
|
|
165
|
+
with the cursor (top pinned) — both wired in `_wireThreadResize`.
|
|
166
|
+
Each sits over the header / composer edge-padding strip; the
|
|
167
|
+
header drag-to-move and composer still work below/above them. A
|
|
168
|
+
short centered grip bar fades in on hover. */
|
|
169
|
+
.thread-float-resize {
|
|
170
|
+
position: absolute;
|
|
171
|
+
left: 0;
|
|
172
|
+
right: 0;
|
|
173
|
+
height: 8px;
|
|
174
|
+
cursor: ns-resize;
|
|
175
|
+
z-index: 2;
|
|
176
|
+
-webkit-user-select: none;
|
|
177
|
+
user-select: none;
|
|
178
|
+
}
|
|
179
|
+
.thread-float-resize-top { top: 0; border-radius: 6px 6px 0 0; }
|
|
180
|
+
.thread-float-resize-bottom { bottom: 0; border-radius: 0 0 6px 6px; }
|
|
181
|
+
.thread-float-resize::after {
|
|
182
|
+
content: "";
|
|
183
|
+
position: absolute;
|
|
184
|
+
left: 50%;
|
|
185
|
+
transform: translateX(-50%);
|
|
186
|
+
width: 28px;
|
|
187
|
+
height: 2px;
|
|
188
|
+
border-radius: 1px;
|
|
189
|
+
background: var(--line-strong);
|
|
190
|
+
opacity: 0;
|
|
191
|
+
transition: opacity 0.12s;
|
|
192
|
+
}
|
|
193
|
+
.thread-float-resize-top::after { top: 2px; }
|
|
194
|
+
.thread-float-resize-bottom::after { bottom: 2px; }
|
|
195
|
+
.thread-float-resize:hover::after { opacity: 0.6; }
|
|
196
|
+
/* Minimized pill has no resize affordance. */
|
|
197
|
+
.thread-float.is-minimized .thread-float-resize { display: none; }
|
|
198
|
+
|
|
199
|
+
.thread-float-head-avatar {
|
|
200
|
+
/* Display the director's portrait raw · 32px square, no
|
|
201
|
+
circular mask, no panel-3 fallback ring. SVG/pixel-art
|
|
202
|
+
avatars in this project carry their own framing; cropping
|
|
203
|
+
them to a circle clipped the character art. Click opens
|
|
204
|
+
the director's agent-profile overlay — wired through the
|
|
205
|
+
standard `[data-agent-profile]` capture-phase handler in
|
|
206
|
+
agent-profile.js. */
|
|
207
|
+
width: 32px;
|
|
208
|
+
height: 32px;
|
|
209
|
+
flex-shrink: 0;
|
|
210
|
+
image-rendering: pixelated;
|
|
211
|
+
image-rendering: crisp-edges;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
transition: opacity 0.12s;
|
|
214
|
+
}
|
|
215
|
+
.thread-float-head-avatar:hover { opacity: 0.78; }
|
|
216
|
+
|
|
217
|
+
.thread-float-head-meta {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
gap: 2px;
|
|
221
|
+
flex: 1;
|
|
222
|
+
min-width: 0;
|
|
223
|
+
}
|
|
224
|
+
.thread-float-head-name {
|
|
225
|
+
font-family: var(--sans, "Inter", system-ui, sans-serif);
|
|
226
|
+
font-size: 12px;
|
|
227
|
+
font-weight: 600;
|
|
228
|
+
color: var(--text);
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
text-overflow: ellipsis;
|
|
232
|
+
}
|
|
233
|
+
.thread-float-head-kicker {
|
|
234
|
+
font-size: 9px;
|
|
235
|
+
letter-spacing: 0.16em;
|
|
236
|
+
text-transform: uppercase;
|
|
237
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.45));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.thread-float-head-controls {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
gap: 2px;
|
|
244
|
+
}
|
|
245
|
+
/* Head buttons · sidebar-nav register · 16px Lucide line icons
|
|
246
|
+
painted via mask-image so the glyph inherits `currentColor` (the
|
|
247
|
+
button text color cascades to the icon, matching the sidebar's
|
|
248
|
+
`.new-btn::before` pattern at index.html:1651). Each button gets
|
|
249
|
+
its own `--icon` custom property. The legacy Unicode-character
|
|
250
|
+
variant (— / ✕ / ⛶) read as different sizes / weights on every
|
|
251
|
+
OS and clashed with the sidebar's Lucide vocabulary. */
|
|
252
|
+
.thread-float-head-btn {
|
|
253
|
+
width: 28px;
|
|
254
|
+
height: 28px;
|
|
255
|
+
display: inline-flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
background: transparent;
|
|
259
|
+
border: 0;
|
|
260
|
+
color: var(--text-soft, rgba(255, 255, 255, 0.65));
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
border-radius: 6px;
|
|
263
|
+
padding: 0;
|
|
264
|
+
/* Empty button content · the ::before paints the icon. Keeping
|
|
265
|
+
font-size: 0 ensures any accidental whitespace inside the
|
|
266
|
+
button doesn't push the icon off-center. */
|
|
267
|
+
font-size: 0;
|
|
268
|
+
line-height: 0;
|
|
269
|
+
transition: background 0.12s, color 0.12s;
|
|
270
|
+
}
|
|
271
|
+
.thread-float-head-btn::before {
|
|
272
|
+
content: "";
|
|
273
|
+
width: 16px;
|
|
274
|
+
height: 16px;
|
|
275
|
+
background-color: currentColor;
|
|
276
|
+
-webkit-mask-image: var(--icon, none);
|
|
277
|
+
mask-image: var(--icon, none);
|
|
278
|
+
-webkit-mask-repeat: no-repeat;
|
|
279
|
+
mask-repeat: no-repeat;
|
|
280
|
+
-webkit-mask-position: center;
|
|
281
|
+
mask-position: center;
|
|
282
|
+
-webkit-mask-size: 16px 16px;
|
|
283
|
+
mask-size: 16px 16px;
|
|
284
|
+
}
|
|
285
|
+
.thread-float-head-btn:hover {
|
|
286
|
+
background: var(--panel-3);
|
|
287
|
+
color: var(--text);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* Minimize · Lucide Minus (a single short horizontal bar) */
|
|
291
|
+
[data-thread-minimize] {
|
|
292
|
+
--icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M5 12h14'/></svg>");
|
|
293
|
+
}
|
|
294
|
+
/* Close · Lucide X */
|
|
295
|
+
[data-thread-close] {
|
|
296
|
+
--icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 6 6 18'/><path d='m6 6 12 12'/></svg>");
|
|
297
|
+
}
|
|
298
|
+
/* Close gets a soft red tint on hover · destructive-leaning action.
|
|
299
|
+
Matches the trash-chip color in `.thread-list-row-delete:hover`
|
|
300
|
+
so close affordances across the thread surface share one
|
|
301
|
+
"this removes something" register. */
|
|
302
|
+
.thread-float-head-btn[data-thread-close]:hover {
|
|
303
|
+
color: var(--red, #B5706A);
|
|
304
|
+
background: color-mix(in srgb, var(--red, #B5706A) 14%, transparent);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* ── Messages list · reuses .msg / .msg-content / .msg-bubble
|
|
308
|
+
styles from the main chat, just inside our scrollable
|
|
309
|
+
container. The compact density tweaks below scope to messages
|
|
310
|
+
inside a thread float so the main chat is unaffected. ─── */
|
|
311
|
+
|
|
312
|
+
.thread-float-messages {
|
|
313
|
+
/* Full-bleed under the absolute header + composer · messages
|
|
314
|
+
scroll behind both, mirroring the room chat's chat-content
|
|
315
|
+
sliding under .room-head + .ib-stack. */
|
|
316
|
+
position: absolute;
|
|
317
|
+
inset: 0;
|
|
318
|
+
overflow-y: auto;
|
|
319
|
+
padding: calc(var(--thread-head-h, 56px) + 14px) 22px 130px;
|
|
320
|
+
display: flex;
|
|
321
|
+
flex-direction: column;
|
|
322
|
+
gap: 16px;
|
|
323
|
+
/* No `scroll-behavior: smooth` · entering an existing thread
|
|
324
|
+
snaps to the last message (seed-history sets `scrollTop =
|
|
325
|
+
scrollHeight`) and the jump-top button snaps to the first;
|
|
326
|
+
both should be instant so the user reads "now I'm at the
|
|
327
|
+
other end" not "watch the panel animate." Smooth scrolling
|
|
328
|
+
also amplifies the cost of any SSE re-jump-to-bottom on
|
|
329
|
+
each appended chunk during streaming. */
|
|
330
|
+
/* Scrollbar auto-hide · same pattern as `.ib-textarea` in
|
|
331
|
+
index.html (line 15024). Default: invisible; the global
|
|
332
|
+
scroll listener in app.js adds `.is-scrolling` on each
|
|
333
|
+
scroll event and removes it ~800ms after the last one.
|
|
334
|
+
Result: clean reading surface in idle state, scrollbar
|
|
335
|
+
appears the moment the user scrolls. */
|
|
336
|
+
scrollbar-width: none;
|
|
337
|
+
}
|
|
338
|
+
.thread-float-messages::-webkit-scrollbar {
|
|
339
|
+
width: 0;
|
|
340
|
+
height: 0;
|
|
341
|
+
/* Override the browser default white scrollbar gutter even
|
|
342
|
+
when widthless · WebKit otherwise paints the track stripe
|
|
343
|
+
visibly on light themes. */
|
|
344
|
+
background: transparent;
|
|
345
|
+
}
|
|
346
|
+
.thread-float-messages.is-scrolling { scrollbar-width: thin; }
|
|
347
|
+
.thread-float-messages.is-scrolling::-webkit-scrollbar {
|
|
348
|
+
width: 5px;
|
|
349
|
+
background: transparent;
|
|
350
|
+
}
|
|
351
|
+
.thread-float-messages.is-scrolling::-webkit-scrollbar-thumb {
|
|
352
|
+
background: color-mix(in srgb, var(--ink, currentColor) 28%, transparent);
|
|
353
|
+
border-radius: 4px;
|
|
354
|
+
}
|
|
355
|
+
.thread-float-messages.is-scrolling::-webkit-scrollbar-track {
|
|
356
|
+
background: transparent;
|
|
357
|
+
}
|
|
358
|
+
/* Firefox can't be styled per-element track-vs-thumb · use
|
|
359
|
+
`scrollbar-color` with the same translucent thumb + transparent
|
|
360
|
+
track recipe. */
|
|
361
|
+
.thread-float-messages.is-scrolling {
|
|
362
|
+
scrollbar-color: color-mix(in srgb, var(--ink, currentColor) 28%, transparent) transparent;
|
|
363
|
+
}
|
|
364
|
+
.thread-float-messages article.msg {
|
|
365
|
+
/* Slightly tighter than the main chat density · float is small. */
|
|
366
|
+
margin: 0;
|
|
367
|
+
/* Collapse the 32px avatar column · this is a 1:1 chat with one
|
|
368
|
+
director and the panel header already shows their avatar +
|
|
369
|
+
name. Repeating the head-shot on every message is redundant
|
|
370
|
+
and chews real estate that ChatGPT-style threads spend on
|
|
371
|
+
prose. Override the main chat's grid (32px / 1fr) to a single
|
|
372
|
+
1fr column so the message content flows edge-to-edge inside
|
|
373
|
+
the messages list's 24px horizontal padding. */
|
|
374
|
+
grid-template-columns: 1fr;
|
|
375
|
+
}
|
|
376
|
+
/* Hide ALL avatars inside thread bubbles (director + chair + user
|
|
377
|
+
alike). User already has its own `.msg.user .msg-av { display:
|
|
378
|
+
none }` rule below; this generalises to every author kind. */
|
|
379
|
+
.thread-float-messages article.msg .msg-av {
|
|
380
|
+
display: none;
|
|
381
|
+
}
|
|
382
|
+
.thread-float-messages article.msg .msg-meta {
|
|
383
|
+
font-size: 10px;
|
|
384
|
+
/* Tighter than the main chat's 10px · the row-gap (vertical) also
|
|
385
|
+
governs the distance down to the wrapped timestamp line below,
|
|
386
|
+
which read too loose at 10px. column-gap stays a touch wider for
|
|
387
|
+
name / model / tag separation. */
|
|
388
|
+
gap: 3px 8px;
|
|
389
|
+
}
|
|
390
|
+
/* Timestamp drops onto its own line beneath the director's name
|
|
391
|
+
(instead of the main chat's right-aligned, same-row placement).
|
|
392
|
+
`flex-basis: 100%` makes it wrap in the flex meta row; `margin-left`
|
|
393
|
+
reset + `order` keep it last and left-aligned under the name.
|
|
394
|
+
User messages hide `.msg-meta` entirely in threads, so this only
|
|
395
|
+
affects director rows. */
|
|
396
|
+
.thread-float-messages article.msg .msg-time {
|
|
397
|
+
flex-basis: 100%;
|
|
398
|
+
margin-left: 0;
|
|
399
|
+
order: 99;
|
|
400
|
+
}
|
|
401
|
+
.thread-float-messages article.msg .msg-bubble {
|
|
402
|
+
/* Match the room's `.msg-bubble { font-size: 16px; line-height:
|
|
403
|
+
1.65 }` (index.html:7557). Keeps prose-reading rhythm
|
|
404
|
+
identical between main chat and thread. */
|
|
405
|
+
font-size: 16px;
|
|
406
|
+
line-height: 1.65;
|
|
407
|
+
}
|
|
408
|
+
/* In threads we don't show round-end vote cards or chair pick
|
|
409
|
+
kickers — there are no rounds, no chair. Hide aggressively. */
|
|
410
|
+
.thread-float-messages .chair-pick-kicker,
|
|
411
|
+
.thread-float-messages .round-end-card,
|
|
412
|
+
.thread-float-messages .msg-context {
|
|
413
|
+
display: none;
|
|
414
|
+
}
|
|
415
|
+
/* Hide the per-message model pill in threads · 1:1 private chat,
|
|
416
|
+
the user already knows which director / model they're talking
|
|
417
|
+
to (header carries the director's name + subtitle). Showing the
|
|
418
|
+
model on every bubble eats ~80-120px of the meta line on a
|
|
419
|
+
360px panel, which forced the `🔍 web-search` badge to wrap
|
|
420
|
+
below the director's name instead of sitting inline with it
|
|
421
|
+
like in the main room. Hiding the model frees enough room for
|
|
422
|
+
`name + tag + ws-badge` to live on one line. */
|
|
423
|
+
.thread-float-messages article.msg .msg-model {
|
|
424
|
+
display: none;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* ── Web search · chair-style tool-card promoted above the bubble ──
|
|
428
|
+
In thread mode `messageHtml` synthesises a `.msg-tool-card` (the
|
|
429
|
+
same component the chair's web-search uses in main rooms) and
|
|
430
|
+
inserts it ABOVE the director's bubble. The room's default card
|
|
431
|
+
geometry assumes a wide content column (`margin: 14px auto 28px;
|
|
432
|
+
max-width: 760px`), which doesn't fit a 316px-wide thread panel.
|
|
433
|
+
Override only the geometry — colors / banner / mark / caret
|
|
434
|
+
stay shared with the room card so the visual vocabulary reads
|
|
435
|
+
the same. */
|
|
436
|
+
.thread-float-messages .msg-tool-card {
|
|
437
|
+
margin: 6px 0 10px;
|
|
438
|
+
max-width: 100%;
|
|
439
|
+
}
|
|
440
|
+
.thread-float-messages .msg-tool-banner {
|
|
441
|
+
padding: 6px 10px;
|
|
442
|
+
gap: 8px;
|
|
443
|
+
}
|
|
444
|
+
.thread-float-messages .msg-tool-banner-tag { font-size: 9px; }
|
|
445
|
+
.thread-float-messages .msg-tool-banner-stamp { font-size: 9px; }
|
|
446
|
+
.thread-float-messages .msg-tool-card-body {
|
|
447
|
+
padding: 10px 12px;
|
|
448
|
+
gap: 8px;
|
|
449
|
+
font-size: 11px;
|
|
450
|
+
}
|
|
451
|
+
.thread-float-messages .msg-tool-card .msg-tool-sources-list {
|
|
452
|
+
/* Sources rows inside the card · default 28px num column shrinks
|
|
453
|
+
to 22px so the title/url has more room in the 316px panel. */
|
|
454
|
+
font-size: 11px;
|
|
455
|
+
}
|
|
456
|
+
.thread-float-messages .msg-tool-card .msg-tool-sources-list li {
|
|
457
|
+
padding: 6px 12px;
|
|
458
|
+
}
|
|
459
|
+
.thread-float-messages .msg-tool-card .msg-tool-sources-num {
|
|
460
|
+
width: 20px;
|
|
461
|
+
min-width: 20px;
|
|
462
|
+
}
|
|
463
|
+
.thread-float-messages .msg-tool-card .msg-tool-sources-title-text { font-size: 12px; }
|
|
464
|
+
.thread-float-messages .msg-tool-card .msg-tool-sources-desc { font-size: 11px; }
|
|
465
|
+
.thread-float-messages .msg-tool-card .msg-tool-sources-expand {
|
|
466
|
+
font-size: 10px;
|
|
467
|
+
padding: 8px 12px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* ── ChatGPT-style user bubbles · right-aligned with surface ───
|
|
471
|
+
User-authored messages in a thread should read as the user's
|
|
472
|
+
voice in a 1:1 chat, not as a peer in a multi-director room.
|
|
473
|
+
Override the main chat's grid layout (32px avatar + 1fr content)
|
|
474
|
+
with a right-aligned flex row, hide the avatar entirely, and
|
|
475
|
+
give the bubble its own panel-2 surface with rounded corners.
|
|
476
|
+
Director messages stay in the default layout (avatar + plain
|
|
477
|
+
bubble · matches "assistant message" register in ChatGPT). */
|
|
478
|
+
.thread-float-messages article.msg.user {
|
|
479
|
+
display: flex;
|
|
480
|
+
justify-content: flex-end;
|
|
481
|
+
grid-template-columns: none;
|
|
482
|
+
gap: 0;
|
|
483
|
+
}
|
|
484
|
+
.thread-float-messages article.msg.user .msg-av,
|
|
485
|
+
.thread-float-messages article.msg.user .msg-meta,
|
|
486
|
+
.thread-float-messages article.msg.user .msg-toolbar {
|
|
487
|
+
display: none;
|
|
488
|
+
}
|
|
489
|
+
.thread-float-messages article.msg.user .msg-content {
|
|
490
|
+
/* Cap so a long single-line message wraps within the window
|
|
491
|
+
instead of stretching to the full width and reading flat. */
|
|
492
|
+
max-width: 82%;
|
|
493
|
+
min-width: 0;
|
|
494
|
+
}
|
|
495
|
+
.thread-float-messages article.msg.user .msg-bubble {
|
|
496
|
+
background: var(--panel-2);
|
|
497
|
+
color: var(--text);
|
|
498
|
+
border-radius: 14px;
|
|
499
|
+
padding: 8px 12px;
|
|
500
|
+
font-family: var(--font-human, var(--sans));
|
|
501
|
+
/* Soft border on light themes (transparent on dark since
|
|
502
|
+
panel-2 already separates from the chat bg) — color-mix
|
|
503
|
+
keeps both palettes balanced. */
|
|
504
|
+
border: 0.5px solid color-mix(in srgb, var(--line-bright, rgba(255, 255, 255, 0.12)) 60%, transparent);
|
|
505
|
+
/* Hint the layout system this block can shrink — without it
|
|
506
|
+
long unbroken strings (URLs) would push the bubble past the
|
|
507
|
+
82% cap. */
|
|
508
|
+
word-break: break-word;
|
|
509
|
+
overflow-wrap: anywhere;
|
|
510
|
+
}
|
|
511
|
+
/* Empty-state hint · before the user types anything */
|
|
512
|
+
.thread-float-empty {
|
|
513
|
+
display: flex;
|
|
514
|
+
flex-direction: column;
|
|
515
|
+
align-items: center;
|
|
516
|
+
justify-content: center;
|
|
517
|
+
flex: 1;
|
|
518
|
+
text-align: center;
|
|
519
|
+
padding: 24px;
|
|
520
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.45));
|
|
521
|
+
font-size: 11px;
|
|
522
|
+
line-height: 1.6;
|
|
523
|
+
letter-spacing: 0.02em;
|
|
524
|
+
gap: 8px;
|
|
525
|
+
}
|
|
526
|
+
.thread-float-empty-tag {
|
|
527
|
+
font-size: 9px;
|
|
528
|
+
letter-spacing: 0.2em;
|
|
529
|
+
text-transform: uppercase;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* ── Composer · 1:1 mirror of the room's `.input-bar` chrome ─────
|
|
533
|
+
Two-row column layout matching `.input-bar` in index.html: row
|
|
534
|
+
one is the textarea (autosize 24-200px), row two is the controls
|
|
535
|
+
strip (send / stop on the right, future per-thread actions can
|
|
536
|
+
slot on the left). Earlier single-row inline placement read as a
|
|
537
|
+
different chrome family from the room — same border-radius +
|
|
538
|
+
typography but visually noisy with the send chip floating next
|
|
539
|
+
to the textarea. */
|
|
540
|
+
.thread-float-composer {
|
|
541
|
+
/* Absolute-bottom + frosted-glass · scroll-under chrome, same
|
|
542
|
+
pattern as the room's floating `.input-bar`. Messages list
|
|
543
|
+
extends behind this card; the card's frosted glass softens
|
|
544
|
+
the prose behind so the visual hierarchy stays "compose >
|
|
545
|
+
read". 14px lateral inset = same gutter as the head's
|
|
546
|
+
padding-left so the composer left-edge aligns with the
|
|
547
|
+
room-head's title column. */
|
|
548
|
+
position: absolute;
|
|
549
|
+
left: 14px;
|
|
550
|
+
right: 14px;
|
|
551
|
+
bottom: 14px;
|
|
552
|
+
z-index: 10;
|
|
553
|
+
display: flex;
|
|
554
|
+
flex-direction: column;
|
|
555
|
+
gap: 4px;
|
|
556
|
+
padding: 6px 8px 8px;
|
|
557
|
+
background: color-mix(in srgb, var(--panel-3) 78%, var(--bg) 22%);
|
|
558
|
+
background: color-mix(in srgb, color-mix(in srgb, var(--panel-3) 78%, var(--bg) 22%) 88%, transparent);
|
|
559
|
+
backdrop-filter: blur(24px) saturate(180%);
|
|
560
|
+
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
|
561
|
+
border: 0.5px solid var(--line-strong);
|
|
562
|
+
border-radius: 14px;
|
|
563
|
+
transition: border-color 0.12s;
|
|
564
|
+
}
|
|
565
|
+
/* Top row · textarea sits flush with the card edges (same as
|
|
566
|
+
`.ib-text-row` in index.html). */
|
|
567
|
+
.thread-float-composer-text-row {
|
|
568
|
+
display: flex;
|
|
569
|
+
align-items: flex-start;
|
|
570
|
+
padding: 6px 8px 0;
|
|
571
|
+
min-height: 32px;
|
|
572
|
+
}
|
|
573
|
+
/* Bottom row · controls strip. Send / stop right-aligned via
|
|
574
|
+
`margin-left: auto` on the trailing slot (matches
|
|
575
|
+
`.ib-controls-row { gap: 6px; padding: 0 4px 0 4px }`). */
|
|
576
|
+
.thread-float-composer-controls-row {
|
|
577
|
+
display: flex;
|
|
578
|
+
align-items: center;
|
|
579
|
+
gap: 6px;
|
|
580
|
+
padding: 0 4px 0 4px;
|
|
581
|
+
/* Push the lone send/stop button to the right edge of the
|
|
582
|
+
card so the row geometry mirrors the room's input-bar
|
|
583
|
+
(where pause / adjourn / vote cluster on the LEFT and
|
|
584
|
+
send chips on the RIGHT). Even though thread has no
|
|
585
|
+
left-cluster actions yet, the spacer keeps the right-side
|
|
586
|
+
button anchored to where the room input-bar's send sits. */
|
|
587
|
+
}
|
|
588
|
+
.thread-float-composer-controls-row > .thread-float-send,
|
|
589
|
+
.thread-float-composer-controls-row > .thread-float-stop {
|
|
590
|
+
margin-left: auto;
|
|
591
|
+
}
|
|
592
|
+
.thread-float-composer:focus-within {
|
|
593
|
+
border-color: var(--lime, #6FB572);
|
|
594
|
+
}
|
|
595
|
+
.thread-float-textarea {
|
|
596
|
+
flex: 1;
|
|
597
|
+
width: 100%;
|
|
598
|
+
font-family: var(--sans, "Inter", system-ui, sans-serif);
|
|
599
|
+
font-size: 14px;
|
|
600
|
+
line-height: 1.45;
|
|
601
|
+
color: var(--text);
|
|
602
|
+
background: transparent;
|
|
603
|
+
border: 0;
|
|
604
|
+
border-radius: 0;
|
|
605
|
+
padding: 4px 0;
|
|
606
|
+
resize: none;
|
|
607
|
+
/* Match the room's autosize bounds exactly · room caps at 200px
|
|
608
|
+
before flipping into internal scroll. */
|
|
609
|
+
max-height: 200px;
|
|
610
|
+
min-height: 24px;
|
|
611
|
+
outline: none;
|
|
612
|
+
box-sizing: border-box;
|
|
613
|
+
/* Match the main bar's auto-hide scrollbar treatment. */
|
|
614
|
+
scrollbar-width: none;
|
|
615
|
+
}
|
|
616
|
+
.thread-float-textarea::-webkit-scrollbar { width: 0; height: 0; }
|
|
617
|
+
.thread-float-textarea::placeholder {
|
|
618
|
+
color: var(--text-faint);
|
|
619
|
+
font-family: var(--sans, "Inter", system-ui, sans-serif);
|
|
620
|
+
font-size: 14px;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/* Send / Stop · icon-only circular buttons matching the main
|
|
624
|
+
room's input-bar send button (`.ib-controls-row .send-btn` in
|
|
625
|
+
index.html · 32×32 lime circle with an arrow-up glyph mask).
|
|
626
|
+
Visible text ("Send" / "Stop") is hidden via font-size:0 but
|
|
627
|
+
stays in the accessibility tree for screen-reader names. The
|
|
628
|
+
glyph itself is a CSS mask painted by `::before` so we don't
|
|
629
|
+
pay an extra DOM node. */
|
|
630
|
+
.thread-float-send,
|
|
631
|
+
.thread-float-stop {
|
|
632
|
+
width: 32px;
|
|
633
|
+
height: 32px;
|
|
634
|
+
padding: 0;
|
|
635
|
+
border-radius: 50%;
|
|
636
|
+
border: none;
|
|
637
|
+
cursor: pointer;
|
|
638
|
+
flex-shrink: 0;
|
|
639
|
+
align-self: flex-end;
|
|
640
|
+
position: relative;
|
|
641
|
+
/* Hide text content visually; ::before draws the glyph mask. */
|
|
642
|
+
font-size: 0;
|
|
643
|
+
line-height: 0;
|
|
644
|
+
overflow: hidden;
|
|
645
|
+
transition: background 0.12s, color 0.12s, transform 0.12s;
|
|
646
|
+
}
|
|
647
|
+
.thread-float-send {
|
|
648
|
+
background: var(--lime, #6FB572);
|
|
649
|
+
color: var(--bg);
|
|
650
|
+
}
|
|
651
|
+
.thread-float-send::before {
|
|
652
|
+
content: "";
|
|
653
|
+
position: absolute;
|
|
654
|
+
inset: 0;
|
|
655
|
+
margin: auto;
|
|
656
|
+
width: 16px;
|
|
657
|
+
height: 16px;
|
|
658
|
+
background-color: currentColor;
|
|
659
|
+
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><line x1='12' y1='19' x2='12' y2='5'/><polyline points='5 12 12 5 19 12'/></svg>");
|
|
660
|
+
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><line x1='12' y1='19' x2='12' y2='5'/><polyline points='5 12 12 5 19 12'/></svg>");
|
|
661
|
+
-webkit-mask-repeat: no-repeat;
|
|
662
|
+
mask-repeat: no-repeat;
|
|
663
|
+
-webkit-mask-position: center;
|
|
664
|
+
mask-position: center;
|
|
665
|
+
-webkit-mask-size: 16px 16px;
|
|
666
|
+
mask-size: 16px 16px;
|
|
667
|
+
}
|
|
668
|
+
.thread-float-send:hover { transform: scale(1.04); }
|
|
669
|
+
.thread-float-send:active { transform: scale(0.96); }
|
|
670
|
+
.thread-float-send[disabled] {
|
|
671
|
+
opacity: 0.45;
|
|
672
|
+
cursor: not-allowed;
|
|
673
|
+
transform: none;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* Stop · same circle geometry, amber-tinted so the user reads
|
|
677
|
+
"interrupting an action in flight" rather than "primary send". */
|
|
678
|
+
.thread-float-stop {
|
|
679
|
+
background: color-mix(in srgb, var(--amber, #C99B4F) 22%, var(--panel-3));
|
|
680
|
+
color: var(--amber, #C99B4F);
|
|
681
|
+
}
|
|
682
|
+
.thread-float-stop::before {
|
|
683
|
+
content: "";
|
|
684
|
+
position: absolute;
|
|
685
|
+
inset: 0;
|
|
686
|
+
margin: auto;
|
|
687
|
+
width: 10px;
|
|
688
|
+
height: 10px;
|
|
689
|
+
background-color: currentColor;
|
|
690
|
+
/* Square glyph · universal "stop" indicator. Drawn as a CSS
|
|
691
|
+
square (no SVG mask needed) since the shape is flat. */
|
|
692
|
+
border-radius: 1px;
|
|
693
|
+
}
|
|
694
|
+
.thread-float-stop:hover {
|
|
695
|
+
background: color-mix(in srgb, var(--amber, #C99B4F) 32%, var(--panel-3));
|
|
696
|
+
transform: scale(1.04);
|
|
697
|
+
}
|
|
698
|
+
.thread-float-stop:active { transform: scale(0.96); }
|
|
699
|
+
|
|
700
|
+
/* The legacy glyph span inside the button is no longer needed —
|
|
701
|
+
the ::before mask above paints the icon. Keep the rule so any
|
|
702
|
+
leftover DOM nodes don't render as a stray block. */
|
|
703
|
+
.thread-float-stop-glyph { display: none; }
|
|
704
|
+
|
|
705
|
+
/* Toggle visibility · the composer always carries both buttons in
|
|
706
|
+
the DOM; we flip a state class on the composer to swap them. */
|
|
707
|
+
.thread-float-composer .thread-float-stop { display: none; }
|
|
708
|
+
.thread-float-composer.is-streaming .thread-float-send { display: none; }
|
|
709
|
+
.thread-float-composer.is-streaming .thread-float-stop { display: inline-flex; }
|
|
710
|
+
|
|
711
|
+
/* Head icon · "All threads in this room" trigger. Mirrors the
|
|
712
|
+
masking-via-CSS-variable convention used by .head-add-cast /
|
|
713
|
+
.head-divergence in room-settings.css (the parent
|
|
714
|
+
`.head-icon-btn` rule already masks `--icon` onto a fixed-size
|
|
715
|
+
button shape). The glyph is Lucide MessagesSquare · two chat
|
|
716
|
+
bubbles stacked to read as "multiple private threads". */
|
|
717
|
+
.head-threads {
|
|
718
|
+
--icon: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z'/><path d='M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1'/></svg>");
|
|
719
|
+
}
|
|
720
|
+
/* Threads stay accessible in adjourned rooms — the user often
|
|
721
|
+
wants to dig into a finished session's perspectives privately
|
|
722
|
+
while the main transcript is now read-only. No `display: none`
|
|
723
|
+
on adjourned status here. */
|
|
724
|
+
|
|
725
|
+
/* Popover anchored under the head-threads icon. Lists every thread
|
|
726
|
+
spawned from this main room with the director's avatar + name +
|
|
727
|
+
last-updated time. Click a row to mount the thread float window.
|
|
728
|
+
Mirrors the `.cast-edit-pop` style (composer-pick rows) so the
|
|
729
|
+
visual vocabulary inside the room head stays one family. */
|
|
730
|
+
.thread-list-pop {
|
|
731
|
+
position: fixed;
|
|
732
|
+
z-index: 9000;
|
|
733
|
+
background: var(--panel);
|
|
734
|
+
border: 0.5px solid var(--line-strong);
|
|
735
|
+
border-radius: 6px;
|
|
736
|
+
width: 320px;
|
|
737
|
+
max-height: 60vh;
|
|
738
|
+
display: flex;
|
|
739
|
+
flex-direction: column;
|
|
740
|
+
box-shadow:
|
|
741
|
+
0 8px 24px -8px rgba(0, 0, 0, 0.5),
|
|
742
|
+
0 4px 12px -4px rgba(0, 0, 0, 0.3);
|
|
743
|
+
}
|
|
744
|
+
.thread-list-head {
|
|
745
|
+
padding: 12px 14px 8px;
|
|
746
|
+
border-bottom: 0.5px solid var(--line-bright, rgba(255, 255, 255, 0.18));
|
|
747
|
+
font-family: var(--mono);
|
|
748
|
+
font-size: 10px;
|
|
749
|
+
letter-spacing: 0.18em;
|
|
750
|
+
text-transform: uppercase;
|
|
751
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.5));
|
|
752
|
+
}
|
|
753
|
+
.thread-list-body {
|
|
754
|
+
flex: 1;
|
|
755
|
+
min-height: 0;
|
|
756
|
+
overflow-y: auto;
|
|
757
|
+
padding: 4px 0;
|
|
758
|
+
}
|
|
759
|
+
.thread-list-empty {
|
|
760
|
+
display: flex;
|
|
761
|
+
flex-direction: column;
|
|
762
|
+
align-items: center;
|
|
763
|
+
gap: 12px;
|
|
764
|
+
padding: 32px 18px 28px;
|
|
765
|
+
font-family: var(--mono);
|
|
766
|
+
font-size: 11px;
|
|
767
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.5));
|
|
768
|
+
text-align: center;
|
|
769
|
+
line-height: 1.55;
|
|
770
|
+
}
|
|
771
|
+
.thread-list-empty-icon {
|
|
772
|
+
width: 48px;
|
|
773
|
+
height: 48px;
|
|
774
|
+
display: inline-flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
justify-content: center;
|
|
777
|
+
background: var(--panel-2);
|
|
778
|
+
border: 0.5px solid var(--line-bright, rgba(255, 255, 255, 0.18));
|
|
779
|
+
border-radius: 50%;
|
|
780
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.4));
|
|
781
|
+
flex-shrink: 0;
|
|
782
|
+
}
|
|
783
|
+
.thread-list-empty-icon svg {
|
|
784
|
+
width: 22px;
|
|
785
|
+
height: 22px;
|
|
786
|
+
}
|
|
787
|
+
/* List rows · positioned as the relative anchor for the hover-
|
|
788
|
+
revealed delete button. Switched off a <button> (which was the
|
|
789
|
+
single click target) to a flex container with two distinct
|
|
790
|
+
click targets — the body and the trash chip. Click handlers in
|
|
791
|
+
app.js look at [data-thread-open] for the row body and
|
|
792
|
+
[data-thread-delete] for the trash. */
|
|
793
|
+
.thread-list-row {
|
|
794
|
+
position: relative;
|
|
795
|
+
display: flex;
|
|
796
|
+
align-items: center;
|
|
797
|
+
gap: 10px;
|
|
798
|
+
padding: 8px 14px;
|
|
799
|
+
background: transparent;
|
|
800
|
+
border: 0;
|
|
801
|
+
width: 100%;
|
|
802
|
+
text-align: left;
|
|
803
|
+
font-family: var(--mono);
|
|
804
|
+
transition: background 0.12s;
|
|
805
|
+
}
|
|
806
|
+
.thread-list-row-body {
|
|
807
|
+
flex: 1;
|
|
808
|
+
display: flex;
|
|
809
|
+
align-items: center;
|
|
810
|
+
gap: 10px;
|
|
811
|
+
background: transparent;
|
|
812
|
+
border: 0;
|
|
813
|
+
padding: 0;
|
|
814
|
+
cursor: pointer;
|
|
815
|
+
min-width: 0;
|
|
816
|
+
text-align: left;
|
|
817
|
+
}
|
|
818
|
+
.thread-list-row:hover {
|
|
819
|
+
background: var(--panel-2);
|
|
820
|
+
}
|
|
821
|
+
.thread-list-row-av {
|
|
822
|
+
width: 28px;
|
|
823
|
+
height: 28px;
|
|
824
|
+
border-radius: 50%;
|
|
825
|
+
flex-shrink: 0;
|
|
826
|
+
background: var(--panel-3);
|
|
827
|
+
}
|
|
828
|
+
.thread-list-row-meta {
|
|
829
|
+
flex: 1;
|
|
830
|
+
display: flex;
|
|
831
|
+
flex-direction: column;
|
|
832
|
+
gap: 2px;
|
|
833
|
+
min-width: 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/* Delete trash chip · hover-only on its row. Click triggers a
|
|
837
|
+
confirm() dialog · destructive action so we want the extra
|
|
838
|
+
guard rail. */
|
|
839
|
+
.thread-list-row-delete {
|
|
840
|
+
display: inline-flex;
|
|
841
|
+
align-items: center;
|
|
842
|
+
justify-content: center;
|
|
843
|
+
width: 24px;
|
|
844
|
+
height: 24px;
|
|
845
|
+
background: transparent;
|
|
846
|
+
border: 0;
|
|
847
|
+
border-radius: 4px;
|
|
848
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.45));
|
|
849
|
+
cursor: pointer;
|
|
850
|
+
opacity: 0;
|
|
851
|
+
flex-shrink: 0;
|
|
852
|
+
transition: opacity 0.12s, color 0.12s, background 0.12s;
|
|
853
|
+
}
|
|
854
|
+
.thread-list-row-delete svg {
|
|
855
|
+
width: 13px;
|
|
856
|
+
height: 13px;
|
|
857
|
+
pointer-events: none;
|
|
858
|
+
}
|
|
859
|
+
.thread-list-row:hover .thread-list-row-delete,
|
|
860
|
+
.thread-list-row-delete:focus-visible {
|
|
861
|
+
opacity: 1;
|
|
862
|
+
}
|
|
863
|
+
.thread-list-row-delete:hover {
|
|
864
|
+
color: var(--red, #B5706A);
|
|
865
|
+
background: color-mix(in srgb, var(--red, #B5706A) 14%, transparent);
|
|
866
|
+
outline: none;
|
|
867
|
+
}
|
|
868
|
+
.thread-list-row-name {
|
|
869
|
+
font-family: var(--sans, "Inter", system-ui, sans-serif);
|
|
870
|
+
font-size: 14px;
|
|
871
|
+
font-weight: 400;
|
|
872
|
+
color: var(--text);
|
|
873
|
+
white-space: nowrap;
|
|
874
|
+
overflow: hidden;
|
|
875
|
+
text-overflow: ellipsis;
|
|
876
|
+
}
|
|
877
|
+
.thread-list-row-time {
|
|
878
|
+
font-size: 9px;
|
|
879
|
+
letter-spacing: 0.14em;
|
|
880
|
+
text-transform: uppercase;
|
|
881
|
+
color: var(--text-faint, rgba(255, 255, 255, 0.45));
|
|
882
|
+
}
|
|
883
|
+
.thread-list-row-active {
|
|
884
|
+
font-size: 9px;
|
|
885
|
+
letter-spacing: 0.14em;
|
|
886
|
+
text-transform: uppercase;
|
|
887
|
+
color: var(--lime, #6FB572);
|
|
888
|
+
flex-shrink: 0;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/* ── Dock bar · holds minimized threads at the bottom-right of
|
|
892
|
+
the viewport ─────────────────────────────────────────────── */
|
|
893
|
+
|
|
894
|
+
.thread-dock {
|
|
895
|
+
position: fixed;
|
|
896
|
+
right: 24px;
|
|
897
|
+
bottom: 24px;
|
|
898
|
+
z-index: 8400;
|
|
899
|
+
display: flex;
|
|
900
|
+
flex-direction: row-reverse;
|
|
901
|
+
gap: 8px;
|
|
902
|
+
pointer-events: none;
|
|
903
|
+
}
|
|
904
|
+
.thread-dock > .thread-float {
|
|
905
|
+
pointer-events: auto;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/* ── Thread trigger affordance · per-bubble + head-cast ────────
|
|
909
|
+
Two surfaces, ONE icon vocabulary so the user reads "this opens
|
|
910
|
+
a private 1:1" regardless of which surface they click.
|
|
911
|
+
|
|
912
|
+
Per-bubble · Discord-style message hover toolbar. A floating
|
|
913
|
+
chrome bar pinned to the top-right of the message that appears
|
|
914
|
+
on hover and overlaps the message's upper edge. Icon-only (no
|
|
915
|
+
text label) — Discord uses pure icons for the reaction / reply /
|
|
916
|
+
pin / more row, on the assumption that the action vocabulary is
|
|
917
|
+
small and discoverable on hover. The toolbar's outer chrome is
|
|
918
|
+
the wrapper; the reply icon is one of its slots (future per-
|
|
919
|
+
message actions slot into the same row). */
|
|
920
|
+
/* Toolbar wrapper · Discord-style hover container floating at the
|
|
921
|
+
top-right of the message. Houses one (or more, future) per-message
|
|
922
|
+
actions. Hidden by default, slides in on article hover. */
|
|
923
|
+
.msg-toolbar {
|
|
924
|
+
position: absolute;
|
|
925
|
+
/* Sits at the RIGHT END of the meta row (the timestamp no longer
|
|
926
|
+
pins to the right — see `.msg-time` — so the far-right is free),
|
|
927
|
+
vertically aligned with that line rather than floating above the
|
|
928
|
+
message. The small negative top centres the ~26px chip on the
|
|
929
|
+
~16px meta line; right:0 puts it flush with the content column's
|
|
930
|
+
right edge. */
|
|
931
|
+
top: -4px;
|
|
932
|
+
right: 0;
|
|
933
|
+
z-index: 3;
|
|
934
|
+
display: inline-flex;
|
|
935
|
+
align-items: center;
|
|
936
|
+
gap: 2px;
|
|
937
|
+
background: var(--panel);
|
|
938
|
+
border: 0.5px solid var(--line-strong, rgba(255, 255, 255, 0.32));
|
|
939
|
+
border-radius: 999px;
|
|
940
|
+
padding: 2px;
|
|
941
|
+
opacity: 0;
|
|
942
|
+
transform: translateY(2px);
|
|
943
|
+
transition: opacity 0.12s ease, transform 0.12s ease;
|
|
944
|
+
box-shadow:
|
|
945
|
+
0 3px 10px -3px rgba(0, 0, 0, 0.45),
|
|
946
|
+
0 1px 2px rgba(0, 0, 0, 0.22);
|
|
947
|
+
}
|
|
948
|
+
article.msg:hover .msg-toolbar,
|
|
949
|
+
.msg-toolbar:focus-within {
|
|
950
|
+
opacity: 1;
|
|
951
|
+
transform: translateY(0);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/* Reply chip · icon + "Thread" label as a compact rounded pill.
|
|
955
|
+
Tag-style — Slack-like reply affordance with the action name
|
|
956
|
+
spelled out next to the icon so the user reads it as a button,
|
|
957
|
+
not just a glyph. */
|
|
958
|
+
.msg-thread-trigger {
|
|
959
|
+
display: inline-flex;
|
|
960
|
+
align-items: center;
|
|
961
|
+
gap: 4px;
|
|
962
|
+
height: 22px;
|
|
963
|
+
padding: 0 9px 0 7px;
|
|
964
|
+
background: transparent;
|
|
965
|
+
border: 0;
|
|
966
|
+
color: var(--text-soft, rgba(255, 255, 255, 0.68));
|
|
967
|
+
cursor: pointer;
|
|
968
|
+
border-radius: 999px;
|
|
969
|
+
font-family: var(--mono);
|
|
970
|
+
font-size: 10px;
|
|
971
|
+
letter-spacing: 0.06em;
|
|
972
|
+
line-height: 1;
|
|
973
|
+
transition: color 0.12s, background 0.12s;
|
|
974
|
+
}
|
|
975
|
+
.msg-thread-trigger svg {
|
|
976
|
+
width: 12px;
|
|
977
|
+
height: 12px;
|
|
978
|
+
pointer-events: none;
|
|
979
|
+
flex-shrink: 0;
|
|
980
|
+
}
|
|
981
|
+
.msg-thread-trigger span {
|
|
982
|
+
text-transform: uppercase;
|
|
983
|
+
pointer-events: none;
|
|
984
|
+
}
|
|
985
|
+
.msg-thread-trigger:hover,
|
|
986
|
+
.msg-thread-trigger:focus-visible {
|
|
987
|
+
color: var(--lime, #6FB572);
|
|
988
|
+
background: var(--panel-2);
|
|
989
|
+
outline: none;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/* Anchor target · the toolbar is `position: absolute`; the .msg
|
|
993
|
+
article needs an explicit positioning context so the toolbar
|
|
994
|
+
pins to the message corner instead of escaping up to the chat
|
|
995
|
+
scroll container. Inert when no toolbar is present (non-director
|
|
996
|
+
bubbles), so safe to apply globally. */
|
|
997
|
+
article.msg {
|
|
998
|
+
position: relative;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/* Head-cast trigger · the same reply icon as a hover badge on each
|
|
1002
|
+
director avatar in the room header. Click of avatar still opens
|
|
1003
|
+
the agent profile overlay (existing behavior · data-agent), so
|
|
1004
|
+
the badge gets its own click target via the wrapping <span>.
|
|
1005
|
+
Lets users open a thread with a director who hasn't spoken yet
|
|
1006
|
+
— without this, the per-bubble trigger was the only entry and it
|
|
1007
|
+
only existed on directors who'd already taken a turn. */
|
|
1008
|
+
.head-cast-wrap {
|
|
1009
|
+
position: relative;
|
|
1010
|
+
display: inline-block;
|
|
1011
|
+
line-height: 0;
|
|
1012
|
+
}
|
|
1013
|
+
.head-cast-thread {
|
|
1014
|
+
position: absolute;
|
|
1015
|
+
right: -4px;
|
|
1016
|
+
bottom: -4px;
|
|
1017
|
+
width: 16px;
|
|
1018
|
+
height: 16px;
|
|
1019
|
+
display: inline-flex;
|
|
1020
|
+
align-items: center;
|
|
1021
|
+
justify-content: center;
|
|
1022
|
+
background: var(--panel-2);
|
|
1023
|
+
border: 0.5px solid var(--line-strong, rgba(255, 255, 255, 0.32));
|
|
1024
|
+
color: var(--text);
|
|
1025
|
+
border-radius: 50%;
|
|
1026
|
+
cursor: pointer;
|
|
1027
|
+
padding: 0;
|
|
1028
|
+
opacity: 0;
|
|
1029
|
+
transform: scale(0.85);
|
|
1030
|
+
transition: opacity 0.12s, transform 0.12s, color 0.12s, background 0.12s;
|
|
1031
|
+
}
|
|
1032
|
+
.head-cast-thread svg {
|
|
1033
|
+
width: 9px;
|
|
1034
|
+
height: 9px;
|
|
1035
|
+
pointer-events: none;
|
|
1036
|
+
}
|
|
1037
|
+
.head-cast-wrap:hover .head-cast-thread {
|
|
1038
|
+
opacity: 1;
|
|
1039
|
+
transform: scale(1);
|
|
1040
|
+
}
|
|
1041
|
+
.head-cast-thread:hover {
|
|
1042
|
+
color: var(--lime, #6FB572);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
1046
|
+
Docked mode · the thread expands into a 420px right side panel
|
|
1047
|
+
sitting next to the chat-col instead of floating bottom-right.
|
|
1048
|
+
═══════════════════════════════════════════════════════════════════
|
|
1049
|
+
|
|
1050
|
+
Layout strategy:
|
|
1051
|
+
- `body.has-thread-dock` flips `.main-view[data-main-view="room"]`
|
|
1052
|
+
to a 2-column grid: chat-col on the left (1fr), docked panel on
|
|
1053
|
+
the right (420px fixed).
|
|
1054
|
+
- The thread element gets `.is-docked` which (a) resets its
|
|
1055
|
+
fixed-positioning anchors so it lays out as a grid child, and
|
|
1056
|
+
(b) flattens its border-radius / shadow so it reads as part of
|
|
1057
|
+
the room chrome, not a floating overlay.
|
|
1058
|
+
- Drag handles, resize edges, and the minimize button are
|
|
1059
|
+
`display: none` in docked mode — none of them make sense for a
|
|
1060
|
+
full-height side panel.
|
|
1061
|
+
- Below 1200px viewport width the dock falls back to floating
|
|
1062
|
+
(the chat-col gets too narrow otherwise). The JS guard
|
|
1063
|
+
`_canDockHere()` already prevents NEW docks at narrow widths;
|
|
1064
|
+
this CSS catches the case where a docked thread is open and
|
|
1065
|
+
the window is resized down.
|
|
1066
|
+
═══════════════════════════════════════════════════════════════════ */
|
|
1067
|
+
|
|
1068
|
+
body.has-thread-dock .main-view[data-main-view="room"] {
|
|
1069
|
+
display: grid;
|
|
1070
|
+
/* Width is variable-driven · `--thread-dock-w` is set inline by
|
|
1071
|
+
the resize handler on body, defaulting to 360px. Min/max clamp
|
|
1072
|
+
guards against the user dragging into uncomfortable extremes:
|
|
1073
|
+
the chat-col (1fr) would otherwise compress below the input-bar
|
|
1074
|
+
content width or the dock would balloon past two-thirds of the
|
|
1075
|
+
viewport. */
|
|
1076
|
+
grid-template-columns: 1fr clamp(320px, var(--thread-dock-w, 360px), 720px);
|
|
1077
|
+
grid-template-rows: 1fr;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.thread-float.is-docked {
|
|
1081
|
+
/* `position: relative` (not static) so the absolute-positioned
|
|
1082
|
+
left-edge resize handle, head, messages, and composer all
|
|
1083
|
+
anchor to the panel itself. */
|
|
1084
|
+
position: relative;
|
|
1085
|
+
right: auto;
|
|
1086
|
+
bottom: auto;
|
|
1087
|
+
width: 100%;
|
|
1088
|
+
height: 100%;
|
|
1089
|
+
box-sizing: border-box;
|
|
1090
|
+
max-height: none;
|
|
1091
|
+
min-height: 0;
|
|
1092
|
+
border: 0;
|
|
1093
|
+
border-left: 0.5px solid var(--line-bright);
|
|
1094
|
+
border-radius: 0;
|
|
1095
|
+
box-shadow: none;
|
|
1096
|
+
z-index: auto;
|
|
1097
|
+
transform: none !important;
|
|
1098
|
+
/* The left-edge resize handle sits at `left: -3px` (straddling
|
|
1099
|
+
the chat ↔ panel seam). The float-mode `overflow: hidden`
|
|
1100
|
+
would otherwise clip it; allow overflow on the docked variant
|
|
1101
|
+
since the rounded-corner scrollbar concern doesn't apply when
|
|
1102
|
+
the panel has flat edges. */
|
|
1103
|
+
overflow: visible;
|
|
1104
|
+
}
|
|
1105
|
+
/* Docked · header drops below the room's own .room-head strip so
|
|
1106
|
+
the room's icons (// Threads / divergence / etc on the right
|
|
1107
|
+
side of head-actions) stay reachable. The room-head is
|
|
1108
|
+
`position: absolute; top: 0` on the main-view at z-index 40 and
|
|
1109
|
+
spans full main-view width; the thread head needs to start
|
|
1110
|
+
BELOW it instead of overlapping. */
|
|
1111
|
+
.thread-float.is-docked .thread-float-head {
|
|
1112
|
+
cursor: default;
|
|
1113
|
+
border-radius: 0;
|
|
1114
|
+
top: var(--room-head-h, 56px);
|
|
1115
|
+
}
|
|
1116
|
+
/* Docked messages list · clear BOTH room-head and the thread's
|
|
1117
|
+
own head before content starts. */
|
|
1118
|
+
.thread-float.is-docked .thread-float-messages {
|
|
1119
|
+
padding-top: calc(var(--room-head-h, 56px) + var(--thread-head-h, 50px) + 14px);
|
|
1120
|
+
}
|
|
1121
|
+
/* Hide affordances that have no docked-mode meaning */
|
|
1122
|
+
.thread-float.is-docked .thread-float-resize,
|
|
1123
|
+
.thread-float.is-docked [data-thread-minimize] {
|
|
1124
|
+
display: none !important;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/* Left-edge resize handle · only visible in docked mode. A thin
|
|
1128
|
+
vertical strip the user can drag to widen / narrow the side
|
|
1129
|
+
panel. The handle sits ON the panel's left border (slightly
|
|
1130
|
+
negative left offset so it straddles the seam between chat-col
|
|
1131
|
+
and dock panel), with `col-resize` cursor. The visible track
|
|
1132
|
+
only blooms on hover so the resting state stays clean. JS in
|
|
1133
|
+
`_wireThreadDockResize` reads pointer-x and writes
|
|
1134
|
+
`--thread-dock-w` on body. */
|
|
1135
|
+
.thread-dock-resize-handle {
|
|
1136
|
+
position: absolute;
|
|
1137
|
+
top: 0;
|
|
1138
|
+
bottom: 0;
|
|
1139
|
+
left: -3px;
|
|
1140
|
+
width: 6px;
|
|
1141
|
+
cursor: col-resize;
|
|
1142
|
+
z-index: 3;
|
|
1143
|
+
display: none;
|
|
1144
|
+
user-select: none;
|
|
1145
|
+
}
|
|
1146
|
+
.thread-dock-resize-handle::after {
|
|
1147
|
+
content: "";
|
|
1148
|
+
position: absolute;
|
|
1149
|
+
top: 50%;
|
|
1150
|
+
left: 50%;
|
|
1151
|
+
transform: translate(-50%, -50%);
|
|
1152
|
+
width: 2px;
|
|
1153
|
+
height: 32px;
|
|
1154
|
+
background: var(--line-strong);
|
|
1155
|
+
border-radius: 1px;
|
|
1156
|
+
opacity: 0;
|
|
1157
|
+
transition: opacity 0.12s, background 0.12s;
|
|
1158
|
+
}
|
|
1159
|
+
.thread-dock-resize-handle:hover::after,
|
|
1160
|
+
body.is-thread-dock-resizing .thread-dock-resize-handle::after {
|
|
1161
|
+
opacity: 0.6;
|
|
1162
|
+
}
|
|
1163
|
+
body.is-thread-dock-resizing .thread-dock-resize-handle::after {
|
|
1164
|
+
background: var(--lime, #6FB572);
|
|
1165
|
+
}
|
|
1166
|
+
/* Only the docked variant shows the handle. The element stays in
|
|
1167
|
+
the DOM in floating mode (so we don't have to re-render the
|
|
1168
|
+
thread on every maximize toggle) — just keep it `display:
|
|
1169
|
+
none` until the panel docks. */
|
|
1170
|
+
.thread-float.is-docked .thread-dock-resize-handle { display: block; }
|
|
1171
|
+
body.is-thread-dock-resizing { cursor: col-resize; user-select: none; }
|
|
1172
|
+
body.is-thread-dock-resizing .thread-float.is-docked,
|
|
1173
|
+
body.is-thread-dock-resizing .chat-col {
|
|
1174
|
+
/* Suppress text selection across both columns while dragging so
|
|
1175
|
+
the user doesn't accidentally highlight messages. */
|
|
1176
|
+
user-select: none;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/* Narrow-viewport fallback · the 420px side panel + chat-col
|
|
1180
|
+
becomes unreadable below 1200px (chat-col gets ~< 600px which
|
|
1181
|
+
eats the input-bar's 960px max-width content rhythm). Force the
|
|
1182
|
+
docked panel back to floating geometry so the chat keeps its
|
|
1183
|
+
full real estate. The grid override on the room view also
|
|
1184
|
+
reverts here so the layout doesn't fight the float positioning. */
|
|
1185
|
+
@media (max-width: 1200px) {
|
|
1186
|
+
body.has-thread-dock .main-view[data-main-view="room"] {
|
|
1187
|
+
display: grid;
|
|
1188
|
+
grid-template-columns: 1fr;
|
|
1189
|
+
grid-template-rows: 1fr;
|
|
1190
|
+
}
|
|
1191
|
+
.thread-float.is-docked {
|
|
1192
|
+
position: fixed;
|
|
1193
|
+
right: 24px;
|
|
1194
|
+
bottom: 24px;
|
|
1195
|
+
width: 420px;
|
|
1196
|
+
height: 520px;
|
|
1197
|
+
max-height: calc(100vh - 96px);
|
|
1198
|
+
border: 0.5px solid var(--line-strong);
|
|
1199
|
+
border-radius: 6px;
|
|
1200
|
+
box-shadow:
|
|
1201
|
+
0 8px 24px -8px rgba(0, 0, 0, 0.45),
|
|
1202
|
+
0 4px 12px -4px rgba(0, 0, 0, 0.3);
|
|
1203
|
+
}
|
|
1204
|
+
.thread-float.is-docked .thread-float-head {
|
|
1205
|
+
cursor: grab;
|
|
1206
|
+
border-radius: 6px 6px 0 0;
|
|
1207
|
+
}
|
|
1208
|
+
/* Resize handle has no purpose when the dock is fixed-positioned
|
|
1209
|
+
(it would drag a non-existent grid column). Hide. */
|
|
1210
|
+
.thread-float.is-docked .thread-dock-resize-handle { display: none; }
|
|
1211
|
+
}
|