privateboard 0.1.9 → 0.1.11
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/cli.js +3732 -2160
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-overlay.js +59 -36
- package/public/agent-profile.css +392 -100
- package/public/agent-profile.js +551 -59
- package/public/app.js +1341 -681
- package/public/i18n.js +1990 -0
- package/public/index.html +251 -94
- package/public/keys-store.js +63 -0
- package/public/new-agent.css +60 -0
- package/public/new-agent.js +121 -53
- package/public/room-settings.js +2 -1
- package/public/user-settings.css +68 -0
- package/public/user-settings.js +241 -87
- package/public/voice-replay.css +607 -0
- package/public/voice-replay.js +533 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════
|
|
2
|
+
Voice Replay overlay
|
|
3
|
+
═══════════════════════════════════════════
|
|
4
|
+
Floating playback card pinned to the bottom-right while replay
|
|
5
|
+
is in progress. Shares the dark / lime / mono register of the
|
|
6
|
+
rest of the app — reads as the same family as the dream-cycle
|
|
7
|
+
modal and the room-settings overlay.
|
|
8
|
+
|
|
9
|
+
States rendered into [data-vr-body]:
|
|
10
|
+
· Loading (vr-spinner-row) · while the key check + first
|
|
11
|
+
audio fetch are in flight
|
|
12
|
+
· Key prompt (vr-key-prompt) · no TTS provider configured
|
|
13
|
+
· Empty (vr-key-prompt variant) · room has no playable msgs
|
|
14
|
+
· Player (vr-speaker / vr-progress / vr-controls) · normal play
|
|
15
|
+
· Done (vr-key-prompt with ✓) · all messages played
|
|
16
|
+
═══════════════════════════════════════════ */
|
|
17
|
+
.voice-replay-overlay {
|
|
18
|
+
position: fixed;
|
|
19
|
+
right: 24px;
|
|
20
|
+
bottom: 24px;
|
|
21
|
+
z-index: 9300;
|
|
22
|
+
width: 380px;
|
|
23
|
+
max-width: calc(100vw - 32px);
|
|
24
|
+
background: var(--panel, #131312);
|
|
25
|
+
border: 0.5px solid var(--lime-dim, #2D5532);
|
|
26
|
+
color: var(--text);
|
|
27
|
+
font-family: var(--mono);
|
|
28
|
+
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.5);
|
|
29
|
+
animation: vr-rise 0.24s ease-out;
|
|
30
|
+
}
|
|
31
|
+
@keyframes vr-rise {
|
|
32
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
33
|
+
to { opacity: 1; transform: translateY(0); }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.vr-head {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
padding: 8px 12px;
|
|
41
|
+
background: var(--panel-2, #1A1A18);
|
|
42
|
+
border-bottom: 0.5px solid var(--line);
|
|
43
|
+
}
|
|
44
|
+
.vr-kicker {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 6px;
|
|
48
|
+
font-size: 9.5px;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
letter-spacing: 0.18em;
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
color: var(--lime, #6FB572);
|
|
53
|
+
}
|
|
54
|
+
.vr-kicker-glyph {
|
|
55
|
+
font-family: var(--font-human);
|
|
56
|
+
font-size: 13px;
|
|
57
|
+
letter-spacing: 0;
|
|
58
|
+
}
|
|
59
|
+
.vr-close {
|
|
60
|
+
width: 22px;
|
|
61
|
+
height: 22px;
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
background: transparent;
|
|
66
|
+
border: 0.5px solid var(--line-bright);
|
|
67
|
+
color: var(--text-dim);
|
|
68
|
+
font-size: 11px;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
transition: border-color 0.12s, color 0.12s;
|
|
71
|
+
}
|
|
72
|
+
.vr-close:hover { border-color: var(--lime, #6FB572); color: var(--lime, #6FB572); }
|
|
73
|
+
|
|
74
|
+
.vr-body {
|
|
75
|
+
padding: 14px 14px 14px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ─── Loading row ───
|
|
79
|
+
Centered both axes so the loading state reads as a focused
|
|
80
|
+
placeholder, not a left-pinned debug log. The dots row sits
|
|
81
|
+
above the label with a small gap so the label has its own
|
|
82
|
+
visual weight, and the whole block is padded vertically to
|
|
83
|
+
approximate the height of the player view it'll be replaced
|
|
84
|
+
by — minimises the visual jump on transition. */
|
|
85
|
+
.vr-spinner-row {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
gap: 12px;
|
|
91
|
+
padding: 28px 0 32px;
|
|
92
|
+
text-align: center;
|
|
93
|
+
}
|
|
94
|
+
.vr-spinner-dots {
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 8px;
|
|
98
|
+
}
|
|
99
|
+
.vr-spinner-dot {
|
|
100
|
+
width: 7px; height: 7px;
|
|
101
|
+
background: var(--lime, #6FB572);
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
opacity: 0.35;
|
|
104
|
+
animation: vr-spinner 1.2s ease-in-out infinite;
|
|
105
|
+
box-shadow: 0 0 0 0 rgba(111, 181, 114, 0);
|
|
106
|
+
}
|
|
107
|
+
.vr-spinner-dots .vr-spinner-dot:nth-child(2) { animation-delay: 0.2s; }
|
|
108
|
+
.vr-spinner-dots .vr-spinner-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
109
|
+
.vr-spinner-text {
|
|
110
|
+
font-family: var(--mono);
|
|
111
|
+
font-size: 10.5px;
|
|
112
|
+
font-weight: 700;
|
|
113
|
+
letter-spacing: 0.18em;
|
|
114
|
+
color: var(--text-soft);
|
|
115
|
+
text-transform: uppercase;
|
|
116
|
+
}
|
|
117
|
+
@keyframes vr-spinner {
|
|
118
|
+
0%, 80%, 100% {
|
|
119
|
+
opacity: 0.25;
|
|
120
|
+
transform: scale(0.85);
|
|
121
|
+
box-shadow: 0 0 0 0 rgba(111, 181, 114, 0);
|
|
122
|
+
}
|
|
123
|
+
40% {
|
|
124
|
+
opacity: 1;
|
|
125
|
+
transform: scale(1.1);
|
|
126
|
+
box-shadow: 0 0 6px 1px rgba(111, 181, 114, 0.5);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ─── Speaker card ─── */
|
|
131
|
+
.vr-speaker {
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 10px;
|
|
135
|
+
margin-bottom: 10px;
|
|
136
|
+
}
|
|
137
|
+
.vr-avatar {
|
|
138
|
+
width: 32px; height: 32px;
|
|
139
|
+
background: var(--bg);
|
|
140
|
+
border: 0.5px solid var(--line-bright);
|
|
141
|
+
image-rendering: pixelated;
|
|
142
|
+
image-rendering: crisp-edges;
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
}
|
|
148
|
+
.vr-avatar-placeholder {
|
|
149
|
+
font-family: var(--mono);
|
|
150
|
+
font-size: 13px;
|
|
151
|
+
font-weight: 700;
|
|
152
|
+
color: var(--text-soft);
|
|
153
|
+
}
|
|
154
|
+
.vr-speaker-text {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
gap: 2px;
|
|
158
|
+
min-width: 0;
|
|
159
|
+
}
|
|
160
|
+
.vr-speaker-name {
|
|
161
|
+
font-family: var(--font-human, var(--mono));
|
|
162
|
+
font-size: 12.5px;
|
|
163
|
+
font-weight: 700;
|
|
164
|
+
letter-spacing: -0.005em;
|
|
165
|
+
color: var(--text);
|
|
166
|
+
white-space: nowrap;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
text-overflow: ellipsis;
|
|
169
|
+
}
|
|
170
|
+
.vr-author-role {
|
|
171
|
+
color: var(--text-soft);
|
|
172
|
+
font-weight: 500;
|
|
173
|
+
}
|
|
174
|
+
.vr-speaker-kind {
|
|
175
|
+
font-size: 8.5px;
|
|
176
|
+
letter-spacing: 0.18em;
|
|
177
|
+
text-transform: uppercase;
|
|
178
|
+
color: var(--text-faint);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ─── Message preview · 2-line clamp ─── */
|
|
182
|
+
.vr-preview {
|
|
183
|
+
font-family: var(--font-human, var(--mono));
|
|
184
|
+
font-size: 11.5px;
|
|
185
|
+
line-height: 1.4;
|
|
186
|
+
color: var(--text-soft);
|
|
187
|
+
letter-spacing: -0.003em;
|
|
188
|
+
margin-bottom: 12px;
|
|
189
|
+
display: -webkit-box;
|
|
190
|
+
-webkit-line-clamp: 2;
|
|
191
|
+
-webkit-box-orient: vertical;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
/* Subtle quote feel · italic, faint left border accent. */
|
|
194
|
+
font-style: italic;
|
|
195
|
+
border-left: 0.5px solid var(--lime-dim, #2D5532);
|
|
196
|
+
padding-left: 8px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* ─── Progress strip ─── */
|
|
200
|
+
.vr-progress-row {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 8px;
|
|
204
|
+
margin-bottom: 10px;
|
|
205
|
+
}
|
|
206
|
+
.vr-progress-counter {
|
|
207
|
+
font-size: 9px;
|
|
208
|
+
letter-spacing: 0.14em;
|
|
209
|
+
color: var(--text-dim);
|
|
210
|
+
white-space: nowrap;
|
|
211
|
+
}
|
|
212
|
+
.vr-progress-bar {
|
|
213
|
+
flex: 1;
|
|
214
|
+
height: 3px;
|
|
215
|
+
background: var(--line);
|
|
216
|
+
position: relative;
|
|
217
|
+
overflow: hidden;
|
|
218
|
+
}
|
|
219
|
+
.vr-progress-fill {
|
|
220
|
+
height: 100%;
|
|
221
|
+
background: var(--lime, #6FB572);
|
|
222
|
+
transition: width 0.24s ease-out;
|
|
223
|
+
}
|
|
224
|
+
.vr-progress-pct {
|
|
225
|
+
font-size: 9px;
|
|
226
|
+
letter-spacing: 0.04em;
|
|
227
|
+
color: var(--text-soft);
|
|
228
|
+
min-width: 28px;
|
|
229
|
+
text-align: right;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* ─── Controls row ─── */
|
|
233
|
+
.vr-controls {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 6px;
|
|
237
|
+
flex-wrap: wrap;
|
|
238
|
+
}
|
|
239
|
+
.vr-btn {
|
|
240
|
+
appearance: none;
|
|
241
|
+
background: transparent;
|
|
242
|
+
border: 0.5px solid var(--line-bright);
|
|
243
|
+
color: var(--text);
|
|
244
|
+
font-family: var(--mono);
|
|
245
|
+
font-size: 9.5px;
|
|
246
|
+
font-weight: 700;
|
|
247
|
+
letter-spacing: 0.12em;
|
|
248
|
+
text-transform: uppercase;
|
|
249
|
+
padding: 4px 8px;
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
transition: border-color 0.12s, color 0.12s, background 0.12s;
|
|
252
|
+
}
|
|
253
|
+
.vr-btn:hover {
|
|
254
|
+
border-color: var(--lime, #6FB572);
|
|
255
|
+
color: var(--lime, #6FB572);
|
|
256
|
+
}
|
|
257
|
+
.vr-btn-speed {
|
|
258
|
+
min-width: 38px;
|
|
259
|
+
text-align: center;
|
|
260
|
+
}
|
|
261
|
+
.vr-toggle {
|
|
262
|
+
display: inline-flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
gap: 6px;
|
|
265
|
+
margin-left: auto;
|
|
266
|
+
font-size: 9px;
|
|
267
|
+
letter-spacing: 0.12em;
|
|
268
|
+
text-transform: uppercase;
|
|
269
|
+
color: var(--text-soft);
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
}
|
|
272
|
+
.vr-toggle input[type="checkbox"] {
|
|
273
|
+
accent-color: var(--lime, #6FB572);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* ─── Key prompt / empty / done ─── */
|
|
277
|
+
.vr-key-prompt {
|
|
278
|
+
display: flex;
|
|
279
|
+
flex-direction: column;
|
|
280
|
+
align-items: stretch;
|
|
281
|
+
gap: 12px;
|
|
282
|
+
padding: 4px 0;
|
|
283
|
+
}
|
|
284
|
+
.vr-key-icon {
|
|
285
|
+
width: 32px; height: 32px;
|
|
286
|
+
display: inline-flex;
|
|
287
|
+
align-items: center;
|
|
288
|
+
justify-content: center;
|
|
289
|
+
border: 0.5px solid var(--lime, #6FB572);
|
|
290
|
+
color: var(--lime, #6FB572);
|
|
291
|
+
font-size: 16px;
|
|
292
|
+
line-height: 1;
|
|
293
|
+
align-self: flex-start;
|
|
294
|
+
}
|
|
295
|
+
.vr-key-icon-done {
|
|
296
|
+
background: var(--lime, #6FB572);
|
|
297
|
+
color: var(--bg, #0A0A0A);
|
|
298
|
+
}
|
|
299
|
+
.vr-key-text {
|
|
300
|
+
display: flex;
|
|
301
|
+
flex-direction: column;
|
|
302
|
+
gap: 4px;
|
|
303
|
+
}
|
|
304
|
+
.vr-key-title {
|
|
305
|
+
font-family: var(--font-human, var(--mono));
|
|
306
|
+
font-size: 13px;
|
|
307
|
+
font-weight: 700;
|
|
308
|
+
color: var(--text);
|
|
309
|
+
letter-spacing: -0.005em;
|
|
310
|
+
}
|
|
311
|
+
.vr-key-deck {
|
|
312
|
+
font-family: var(--font-human, var(--mono));
|
|
313
|
+
font-size: 11.5px;
|
|
314
|
+
line-height: 1.45;
|
|
315
|
+
color: var(--text-soft);
|
|
316
|
+
letter-spacing: -0.003em;
|
|
317
|
+
}
|
|
318
|
+
.vr-key-actions {
|
|
319
|
+
display: flex;
|
|
320
|
+
align-items: center;
|
|
321
|
+
gap: 8px;
|
|
322
|
+
flex-wrap: wrap;
|
|
323
|
+
}
|
|
324
|
+
.vr-cta {
|
|
325
|
+
appearance: none;
|
|
326
|
+
background: var(--lime, #6FB572);
|
|
327
|
+
color: var(--bg, #0A0A0A);
|
|
328
|
+
border: 0.5px solid var(--lime, #6FB572);
|
|
329
|
+
padding: 6px 12px;
|
|
330
|
+
font-family: var(--mono);
|
|
331
|
+
font-size: 10px;
|
|
332
|
+
font-weight: 700;
|
|
333
|
+
letter-spacing: 0.14em;
|
|
334
|
+
text-transform: uppercase;
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
transition: background 0.12s, color 0.12s;
|
|
337
|
+
}
|
|
338
|
+
.vr-cta:hover {
|
|
339
|
+
background: var(--bg, #0A0A0A);
|
|
340
|
+
color: var(--lime, #6FB572);
|
|
341
|
+
}
|
|
342
|
+
.vr-ghost {
|
|
343
|
+
appearance: none;
|
|
344
|
+
background: transparent;
|
|
345
|
+
border: 0.5px solid var(--line-bright);
|
|
346
|
+
color: var(--text-soft);
|
|
347
|
+
padding: 6px 12px;
|
|
348
|
+
font-family: var(--mono);
|
|
349
|
+
font-size: 10px;
|
|
350
|
+
font-weight: 700;
|
|
351
|
+
letter-spacing: 0.14em;
|
|
352
|
+
text-transform: uppercase;
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
}
|
|
355
|
+
.vr-ghost:hover { border-color: var(--text); color: var(--text); }
|
|
356
|
+
|
|
357
|
+
/* ─── Inline error inside player body ─── */
|
|
358
|
+
.vr-error {
|
|
359
|
+
margin-top: 10px;
|
|
360
|
+
padding: 8px 10px;
|
|
361
|
+
font-family: var(--font-human, var(--mono));
|
|
362
|
+
font-size: 11px;
|
|
363
|
+
line-height: 1.45;
|
|
364
|
+
color: var(--red, #B5706A);
|
|
365
|
+
border: 0.5px solid var(--red, #B5706A);
|
|
366
|
+
background: var(--panel-2);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* ═══════════════════════════════════════════
|
|
370
|
+
Currently-playing message highlight (in chat)
|
|
371
|
+
═══════════════════════════════════════════
|
|
372
|
+
Applied to any message DOM node by
|
|
373
|
+
voice-replay.js while it's the active speaker.
|
|
374
|
+
|
|
375
|
+
Game-y · RTS-unit-selection inspired:
|
|
376
|
+
· 4 L-shaped corner brackets at the 4 corners,
|
|
377
|
+
drawn via 2 pseudo-elements with multi-
|
|
378
|
+
gradient backgrounds (each pseudo paints 2
|
|
379
|
+
opposing corners). Brackets pulse with a
|
|
380
|
+
2s ease-in-out cycle that mirrors the
|
|
381
|
+
audio's breathing rhythm.
|
|
382
|
+
· Subtle lime tint + breathing inner glow on
|
|
383
|
+
the bubble itself so the message feels "lit
|
|
384
|
+
up" instead of just outlined.
|
|
385
|
+
· Floating `▶ SPEAKING` chip at top-right,
|
|
386
|
+
absolute-positioned with a pulsing dot.
|
|
387
|
+
|
|
388
|
+
Everything is non-blocking · pointer-events:
|
|
389
|
+
none on the decorations so the underlying
|
|
390
|
+
message stays clickable / hoverable. */
|
|
391
|
+
/* Same frame treatment is shared by THREE states · the only
|
|
392
|
+
difference is which class triggers it:
|
|
393
|
+
· `.is-replay-active` · adjourned-room voice replay (the
|
|
394
|
+
user clicked Voice Replay; the
|
|
395
|
+
JS handler adds + removes this).
|
|
396
|
+
· `.streaming` · live director / chair message
|
|
397
|
+
that's currently streaming text
|
|
398
|
+
(added by updateMessageBodyDom).
|
|
399
|
+
· `.is-streaming` · same as above but on chair
|
|
400
|
+
custom cards (chair-direct,
|
|
401
|
+
chair-intervention) which use a
|
|
402
|
+
different class machinery.
|
|
403
|
+
Bundling them under one rule means the live experience
|
|
404
|
+
inherits the replay decoration automatically — when a
|
|
405
|
+
director starts speaking in a live voice room (or just
|
|
406
|
+
streaming text in a normal text room), the same corner
|
|
407
|
+
brackets + breathing tint frame their bubble, then drop
|
|
408
|
+
the moment they go to "final." */
|
|
409
|
+
.is-replay-active,
|
|
410
|
+
[data-message-id].streaming,
|
|
411
|
+
[data-message-id].is-streaming {
|
|
412
|
+
position: relative;
|
|
413
|
+
/* Background uses the warm text token at very low alpha so
|
|
414
|
+
the bubble feels "lit" without leaning chartreuse —
|
|
415
|
+
earlier rev's lime tint + lime ring stacked into a
|
|
416
|
+
yellow-green halo on bright displays. The ring itself is
|
|
417
|
+
gone now; framing comes from the corner brackets, which
|
|
418
|
+
sit OUTSIDE the bubble so there's real breathing room
|
|
419
|
+
between the decoration and content. */
|
|
420
|
+
background: rgba(200, 197, 190, 0.05);
|
|
421
|
+
box-shadow: 0 0 36px rgba(200, 197, 190, 0.08);
|
|
422
|
+
animation: replay-active-breathe 2.4s ease-in-out infinite;
|
|
423
|
+
transition: background 0.24s, box-shadow 0.24s;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@keyframes replay-active-breathe {
|
|
427
|
+
0%, 100% {
|
|
428
|
+
background: rgba(200, 197, 190, 0.04);
|
|
429
|
+
box-shadow: 0 0 28px rgba(200, 197, 190, 0.06);
|
|
430
|
+
}
|
|
431
|
+
50% {
|
|
432
|
+
background: rgba(200, 197, 190, 0.08);
|
|
433
|
+
box-shadow: 0 0 44px rgba(200, 197, 190, 0.14);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Corner brackets · 4 corners painted by 2 pseudo-elements.
|
|
438
|
+
Each pseudo uses 4 linear-gradient backgrounds (one per
|
|
439
|
+
bracket arm) anchored to the corner.
|
|
440
|
+
`inset: -12px` pulls the brackets well outside the bubble
|
|
441
|
+
so there's clear visual breathing room between the bubble's
|
|
442
|
+
text padding and the bracket arms — a tight `-6px` made
|
|
443
|
+
the brackets feel glued to the bubble edge.
|
|
444
|
+
Color is the warm text token, not lime · the brackets are
|
|
445
|
+
FRAME chrome (passive), the SPEAKING chip is the ACTION
|
|
446
|
+
accent (lime). Splitting the duties keeps the visual
|
|
447
|
+
register cohesive with the rest of the theme.
|
|
448
|
+
Pulse uses transform + opacity so layout doesn't reflow. */
|
|
449
|
+
.is-replay-active::before,
|
|
450
|
+
.is-replay-active::after,
|
|
451
|
+
[data-message-id].streaming::before,
|
|
452
|
+
[data-message-id].streaming::after,
|
|
453
|
+
[data-message-id].is-streaming::before,
|
|
454
|
+
[data-message-id].is-streaming::after {
|
|
455
|
+
content: "";
|
|
456
|
+
position: absolute;
|
|
457
|
+
inset: -12px;
|
|
458
|
+
pointer-events: none;
|
|
459
|
+
animation: replay-bracket-pulse 1.8s ease-in-out infinite;
|
|
460
|
+
}
|
|
461
|
+
.is-replay-active::before,
|
|
462
|
+
[data-message-id].streaming::before,
|
|
463
|
+
[data-message-id].is-streaming::before {
|
|
464
|
+
/* Top-left + top-right corners. */
|
|
465
|
+
background-image:
|
|
466
|
+
linear-gradient(to right, var(--text-soft, #8E8B83) 16px, transparent 16px),
|
|
467
|
+
linear-gradient(to bottom, var(--text-soft, #8E8B83) 16px, transparent 16px),
|
|
468
|
+
linear-gradient(to left, var(--text-soft, #8E8B83) 16px, transparent 16px),
|
|
469
|
+
linear-gradient(to bottom, var(--text-soft, #8E8B83) 16px, transparent 16px);
|
|
470
|
+
background-position:
|
|
471
|
+
top left, /* TL horizontal */
|
|
472
|
+
top left, /* TL vertical */
|
|
473
|
+
top right, /* TR horizontal */
|
|
474
|
+
top right; /* TR vertical */
|
|
475
|
+
background-size:
|
|
476
|
+
16px 1.5px,
|
|
477
|
+
1.5px 16px,
|
|
478
|
+
16px 1.5px,
|
|
479
|
+
1.5px 16px;
|
|
480
|
+
background-repeat: no-repeat;
|
|
481
|
+
}
|
|
482
|
+
.is-replay-active::after,
|
|
483
|
+
[data-message-id].streaming::after,
|
|
484
|
+
[data-message-id].is-streaming::after {
|
|
485
|
+
/* Bottom-left + bottom-right corners. */
|
|
486
|
+
background-image:
|
|
487
|
+
linear-gradient(to right, var(--text-soft, #8E8B83) 16px, transparent 16px),
|
|
488
|
+
linear-gradient(to top, var(--text-soft, #8E8B83) 16px, transparent 16px),
|
|
489
|
+
linear-gradient(to left, var(--text-soft, #8E8B83) 16px, transparent 16px),
|
|
490
|
+
linear-gradient(to top, var(--text-soft, #8E8B83) 16px, transparent 16px);
|
|
491
|
+
background-position:
|
|
492
|
+
bottom left,
|
|
493
|
+
bottom left,
|
|
494
|
+
bottom right,
|
|
495
|
+
bottom right;
|
|
496
|
+
background-size:
|
|
497
|
+
16px 1.5px,
|
|
498
|
+
1.5px 16px,
|
|
499
|
+
16px 1.5px,
|
|
500
|
+
1.5px 16px;
|
|
501
|
+
background-repeat: no-repeat;
|
|
502
|
+
/* Stagger the pulse so the four corners breathe in pairs
|
|
503
|
+
(TL+TR together, BL+BR together) for a more alive feel
|
|
504
|
+
than a single synced pulse across all four. */
|
|
505
|
+
animation-delay: 0.9s;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
@keyframes replay-bracket-pulse {
|
|
509
|
+
0%, 100% {
|
|
510
|
+
opacity: 0.55;
|
|
511
|
+
transform: scale(1);
|
|
512
|
+
}
|
|
513
|
+
50% {
|
|
514
|
+
opacity: 0.95;
|
|
515
|
+
transform: scale(1.012);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* "▶ SPEAKING" chip · floating tag pinned just-above-right of
|
|
520
|
+
the active bubble. Injected by voice-replay.js when a
|
|
521
|
+
message becomes active, removed on advance. Pulsing dot
|
|
522
|
+
beside the label signals that audio is actively flowing;
|
|
523
|
+
the chip itself reads "speaker" before "animation". */
|
|
524
|
+
.vr-now-playing {
|
|
525
|
+
position: absolute;
|
|
526
|
+
top: -10px;
|
|
527
|
+
right: 12px;
|
|
528
|
+
z-index: 5;
|
|
529
|
+
display: inline-flex;
|
|
530
|
+
align-items: center;
|
|
531
|
+
gap: 6px;
|
|
532
|
+
padding: 3px 8px 3px 7px;
|
|
533
|
+
background: var(--bg, #0A0A0A);
|
|
534
|
+
border: 0.5px solid var(--lime, #6FB572);
|
|
535
|
+
font-family: var(--mono);
|
|
536
|
+
font-size: 8.5px;
|
|
537
|
+
font-weight: 700;
|
|
538
|
+
letter-spacing: 0.18em;
|
|
539
|
+
text-transform: uppercase;
|
|
540
|
+
color: var(--lime, #6FB572);
|
|
541
|
+
pointer-events: none;
|
|
542
|
+
box-shadow: 0 2px 12px rgba(111, 181, 114, 0.25);
|
|
543
|
+
animation: vr-np-rise 0.18s ease-out;
|
|
544
|
+
}
|
|
545
|
+
.vr-np-mark {
|
|
546
|
+
font-family: var(--mono);
|
|
547
|
+
font-size: 9px;
|
|
548
|
+
letter-spacing: 0;
|
|
549
|
+
}
|
|
550
|
+
.vr-np-dot {
|
|
551
|
+
width: 5px;
|
|
552
|
+
height: 5px;
|
|
553
|
+
background: var(--lime, #6FB572);
|
|
554
|
+
border-radius: 50%;
|
|
555
|
+
animation: vr-np-dot-pulse 1.1s ease-in-out infinite;
|
|
556
|
+
}
|
|
557
|
+
.vr-np-text {
|
|
558
|
+
line-height: 1;
|
|
559
|
+
}
|
|
560
|
+
@keyframes vr-np-rise {
|
|
561
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
562
|
+
to { opacity: 1; transform: translateY(0); }
|
|
563
|
+
}
|
|
564
|
+
@keyframes vr-np-dot-pulse {
|
|
565
|
+
0%, 100% { opacity: 0.35; transform: scale(0.85); box-shadow: 0 0 0 0 rgba(111, 181, 114, 0); }
|
|
566
|
+
50% { opacity: 1; transform: scale(1.1); box-shadow: 0 0 6px 1px rgba(111, 181, 114, 0.7); }
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* Reduced-motion fallback · users with prefers-reduced-motion
|
|
570
|
+
get a solid lime ring + static brackets + static chip dot,
|
|
571
|
+
no pulsing / breathing. */
|
|
572
|
+
@media (prefers-reduced-motion: reduce) {
|
|
573
|
+
.is-replay-active,
|
|
574
|
+
.is-replay-active::before,
|
|
575
|
+
.is-replay-active::after,
|
|
576
|
+
[data-message-id].streaming,
|
|
577
|
+
[data-message-id].streaming::before,
|
|
578
|
+
[data-message-id].streaming::after,
|
|
579
|
+
[data-message-id].is-streaming,
|
|
580
|
+
[data-message-id].is-streaming::before,
|
|
581
|
+
[data-message-id].is-streaming::after,
|
|
582
|
+
.vr-now-playing,
|
|
583
|
+
.vr-np-dot {
|
|
584
|
+
animation: none !important;
|
|
585
|
+
}
|
|
586
|
+
.is-replay-active,
|
|
587
|
+
[data-message-id].streaming,
|
|
588
|
+
[data-message-id].is-streaming {
|
|
589
|
+
background: rgba(200, 197, 190, 0.06);
|
|
590
|
+
box-shadow: 0 0 22px rgba(200, 197, 190, 0.10);
|
|
591
|
+
}
|
|
592
|
+
.is-replay-active::before,
|
|
593
|
+
.is-replay-active::after,
|
|
594
|
+
[data-message-id].streaming::before,
|
|
595
|
+
[data-message-id].streaming::after,
|
|
596
|
+
[data-message-id].is-streaming::before,
|
|
597
|
+
[data-message-id].is-streaming::after { opacity: 0.7; }
|
|
598
|
+
.vr-np-dot { opacity: 1; transform: none; }
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* Sidebar collapsed · the overlay should never overlap with the
|
|
602
|
+
floating sidebar-expand button · push it left a touch when the
|
|
603
|
+
page has the no-room flag (which gates the expand button's
|
|
604
|
+
visibility too). */
|
|
605
|
+
html.no-room .voice-replay-overlay {
|
|
606
|
+
bottom: 56px;
|
|
607
|
+
}
|