privateboard 0.1.2 → 0.1.4
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 +3357 -928
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-profile.js +17 -7
- package/public/app.js +3775 -434
- package/public/index.html +2524 -269
- package/public/onboarding.js +36 -9
- package/public/quote-cta.css +49 -5
- package/public/quote-cta.js +215 -17
- package/public/report/spines/a16z-thesis.css +212 -97
- package/public/report/spines/anthropic-essay.css +564 -221
- package/public/report/spines/boardroom-dark.css +130 -72
- package/public/report/spines/gartner-note.css +83 -48
- package/public/report/spines/mckinsey-deck.css +81 -31
- package/public/report/spines/openai-paper.css +96 -35
- package/public/report.html +3576 -197
- package/public/room-settings.js +11 -8
- package/public/themes.css +15 -2
- package/public/user-settings.css +19 -8
- package/public/user-settings.js +37 -162
package/public/index.html
CHANGED
|
@@ -37,9 +37,18 @@
|
|
|
37
37
|
|
|
38
38
|
/* Chat page chrome (sidebar, queue, room head, etc.) runs on Human
|
|
39
39
|
Sans. Agent message bubbles override to --font-agent;
|
|
40
|
-
the logo is pinned back to Inter further down the stylesheet.
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
the logo is pinned back to Inter further down the stylesheet.
|
|
41
|
+
PingFang SC is named explicitly in the CJK fallback chain so
|
|
42
|
+
Chinese glyphs render as PingFang on macOS instead of whatever
|
|
43
|
+
`system-ui` resolves to. */
|
|
44
|
+
--mono: "Human Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
|
45
|
+
"PingFang SC", "PingFang TC", "Hiragino Sans GB", "Microsoft YaHei",
|
|
46
|
+
"Source Han Sans CN", "Noto Sans CJK SC",
|
|
47
|
+
system-ui, sans-serif;
|
|
48
|
+
--sans: "Human Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
|
|
49
|
+
"PingFang SC", "PingFang TC", "Hiragino Sans GB", "Microsoft YaHei",
|
|
50
|
+
"Source Han Sans CN", "Noto Sans CJK SC",
|
|
51
|
+
system-ui, sans-serif;
|
|
43
52
|
|
|
44
53
|
--sidebar-w: 280px;
|
|
45
54
|
}
|
|
@@ -205,6 +214,11 @@
|
|
|
205
214
|
gap: 0;
|
|
206
215
|
overflow: hidden;
|
|
207
216
|
min-height: 0;
|
|
217
|
+
/* `position: relative` so the floating sidebar-expand-btn (absolute-
|
|
218
|
+
positioned) anchors to the body-grid's top-left edge instead of
|
|
219
|
+
the viewport — that puts the button below the topbar / brand
|
|
220
|
+
logo where the user expects it, not on top of it. */
|
|
221
|
+
position: relative;
|
|
208
222
|
}
|
|
209
223
|
|
|
210
224
|
/* Drag handles for resizable columns */
|
|
@@ -287,6 +301,134 @@
|
|
|
287
301
|
}
|
|
288
302
|
.sidebar-head-meta .lime { color: var(--lime); }
|
|
289
303
|
|
|
304
|
+
/* ─── Sidebar collapse toggle ───
|
|
305
|
+
Sits at the right edge of sidebar-head. Pure CSS glyph (no svg)
|
|
306
|
+
so we can flip the direction via class without re-rendering DOM.
|
|
307
|
+
Hover lime per the new-btn vocabulary. */
|
|
308
|
+
.sidebar-collapse-btn {
|
|
309
|
+
appearance: none;
|
|
310
|
+
background: var(--panel-3);
|
|
311
|
+
border: 0.5px solid var(--line-strong);
|
|
312
|
+
color: var(--text-soft);
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
width: 24px;
|
|
315
|
+
height: 22px;
|
|
316
|
+
padding: 0;
|
|
317
|
+
line-height: 1;
|
|
318
|
+
font-family: var(--mono);
|
|
319
|
+
font-size: 16px;
|
|
320
|
+
font-weight: 700;
|
|
321
|
+
display: inline-flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
justify-content: center;
|
|
324
|
+
transition: color 0.12s, background 0.12s, border-color 0.12s;
|
|
325
|
+
flex-shrink: 0;
|
|
326
|
+
}
|
|
327
|
+
.sidebar-collapse-btn::before { content: "‹"; }
|
|
328
|
+
body.sidebar-collapsed .sidebar-collapse-btn::before { content: "›"; }
|
|
329
|
+
.sidebar-collapse-btn:hover {
|
|
330
|
+
color: var(--lime);
|
|
331
|
+
border-color: var(--lime-dim);
|
|
332
|
+
background: var(--panel-2);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* ─── Collapsed state · sidebar fully hidden ───
|
|
336
|
+
Sidebar + resizer collapse out of layout via display:none. The
|
|
337
|
+
grid drops to a single 1fr column · auto-placement would otherwise
|
|
338
|
+
try to drop the .main into the now-empty first track (still
|
|
339
|
+
reserved by template), zero-width, and the user sees an empty
|
|
340
|
+
room. Single column = main fills the whole body-grid cleanly.
|
|
341
|
+
The user's preferred --sidebar-w stays in localStorage so
|
|
342
|
+
expanding restores the same width. */
|
|
343
|
+
body.sidebar-collapsed .body-grid {
|
|
344
|
+
grid-template-columns: 1fr !important;
|
|
345
|
+
}
|
|
346
|
+
body.sidebar-collapsed .sidebar,
|
|
347
|
+
body.sidebar-collapsed .col-resizer {
|
|
348
|
+
display: none;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Floating expand button · ONLY shown in the empty / no-room
|
|
352
|
+
state, where there's no room-head to host the in-header
|
|
353
|
+
expand control (rendered by renderHeader). Positioned absolute
|
|
354
|
+
relative to .body-grid (which carries `position: relative`),
|
|
355
|
+
pinned to the top-left of the body-grid area. Frosted black
|
|
356
|
+
glass with hairline border, lime on hover. z-index sits above
|
|
357
|
+
chat content but below modal overlays (which use 1500+). */
|
|
358
|
+
.sidebar-expand-btn {
|
|
359
|
+
display: none;
|
|
360
|
+
position: absolute;
|
|
361
|
+
top: 12px;
|
|
362
|
+
left: 12px;
|
|
363
|
+
width: 34px;
|
|
364
|
+
height: 34px;
|
|
365
|
+
align-items: center;
|
|
366
|
+
justify-content: center;
|
|
367
|
+
background: rgba(8, 8, 8, 0.55);
|
|
368
|
+
-webkit-backdrop-filter: blur(12px) saturate(1.05);
|
|
369
|
+
backdrop-filter: blur(12px) saturate(1.05);
|
|
370
|
+
border: 0.5px solid var(--line-strong);
|
|
371
|
+
color: var(--text-soft);
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
z-index: 200;
|
|
374
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
375
|
+
appearance: none;
|
|
376
|
+
padding: 0;
|
|
377
|
+
}
|
|
378
|
+
/* Show floating button only when sidebar is collapsed AND we're
|
|
379
|
+
in the empty / no-room state. When a room is loaded, the
|
|
380
|
+
in-header `.room-head-expand` button takes over instead, so
|
|
381
|
+
the icon doesn't sit on top of the room title. */
|
|
382
|
+
html.no-room body.sidebar-collapsed .sidebar-expand-btn {
|
|
383
|
+
display: inline-flex;
|
|
384
|
+
}
|
|
385
|
+
.sidebar-expand-btn:hover {
|
|
386
|
+
color: var(--lime);
|
|
387
|
+
border-color: var(--lime-dim);
|
|
388
|
+
background: rgba(8, 8, 8, 0.72);
|
|
389
|
+
}
|
|
390
|
+
.sidebar-expand-btn svg { display: block; }
|
|
391
|
+
|
|
392
|
+
/* ─── In-header expand button ───
|
|
393
|
+
Lives at the leading edge of `.room-head` (rendered by
|
|
394
|
+
renderHeader() in app.js). Twin of `.sidebar-collapse-btn` —
|
|
395
|
+
naked CSS-glyph, no border, faint text, lime on hover. The
|
|
396
|
+
▸ glyph mirrors the ◂ on the collapse counterpart so the
|
|
397
|
+
two controls read as a paired set. Hidden by default; shown
|
|
398
|
+
only when the sidebar is collapsed AND a room is loaded. */
|
|
399
|
+
.room-head-expand {
|
|
400
|
+
appearance: none;
|
|
401
|
+
background: var(--panel-3);
|
|
402
|
+
border: 0.5px solid var(--line-strong);
|
|
403
|
+
color: var(--text-soft);
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
width: 24px;
|
|
406
|
+
height: 22px;
|
|
407
|
+
padding: 0;
|
|
408
|
+
line-height: 1;
|
|
409
|
+
font-family: var(--mono);
|
|
410
|
+
font-size: 16px;
|
|
411
|
+
font-weight: 700;
|
|
412
|
+
display: none;
|
|
413
|
+
align-items: center;
|
|
414
|
+
justify-content: center;
|
|
415
|
+
transition: color 0.12s, background 0.12s, border-color 0.12s;
|
|
416
|
+
flex-shrink: 0;
|
|
417
|
+
}
|
|
418
|
+
.room-head-expand::before { content: "›"; }
|
|
419
|
+
body.sidebar-collapsed .room-head-expand {
|
|
420
|
+
display: inline-flex;
|
|
421
|
+
}
|
|
422
|
+
.room-head-expand:hover {
|
|
423
|
+
color: var(--lime);
|
|
424
|
+
border-color: var(--lime-dim);
|
|
425
|
+
background: var(--panel-2);
|
|
426
|
+
}
|
|
427
|
+
.room-head-expand:hover {
|
|
428
|
+
color: var(--lime);
|
|
429
|
+
background: var(--panel-3);
|
|
430
|
+
}
|
|
431
|
+
|
|
290
432
|
/* ─── Sidebar tabs (Rooms / Agents) — segmented control with icon ─── */
|
|
291
433
|
.sidebar-tabs {
|
|
292
434
|
display: flex;
|
|
@@ -422,26 +564,48 @@
|
|
|
422
564
|
flex-shrink: 0;
|
|
423
565
|
}
|
|
424
566
|
.agent-row .agent-row-content { min-width: 0; }
|
|
567
|
+
/* Top-line · matches `.row-top-line` in the rooms list so the
|
|
568
|
+
agent's name + time read with the same baseline/gap rhythm. The
|
|
569
|
+
trailing pin button sits AFTER the time and is hover-revealed,
|
|
570
|
+
so we don't need `space-between` to push it right — `flex: 1` on
|
|
571
|
+
the title handles that. */
|
|
425
572
|
.agent-row .agent-row-top-line {
|
|
426
573
|
display: flex;
|
|
427
|
-
|
|
428
|
-
gap:
|
|
429
|
-
|
|
430
|
-
margin-bottom: 2px;
|
|
574
|
+
align-items: baseline;
|
|
575
|
+
gap: 4px;
|
|
576
|
+
margin-bottom: 1px;
|
|
431
577
|
}
|
|
432
578
|
.agent-row .agent-row-title {
|
|
433
579
|
color: var(--text);
|
|
434
580
|
font-family: var(--font-human);
|
|
435
581
|
font-weight: 600;
|
|
436
|
-
font-size:
|
|
582
|
+
font-size: 14px;
|
|
437
583
|
letter-spacing: -0.005em;
|
|
584
|
+
/* Truncate long agent names with ellipsis instead of wrapping
|
|
585
|
+
to a second line. Without `min-width: 0` here, flex children
|
|
586
|
+
default to min-width: auto = content-size and refuse to
|
|
587
|
+
shrink below their text — which produced the wrapping the
|
|
588
|
+
user saw. `flex: 1 1 0` + `min-width: 0` lets the title
|
|
589
|
+
shrink as needed; the trailing time tag is locked via
|
|
590
|
+
flex-shrink: 0 below so it never gets eaten. */
|
|
591
|
+
flex: 1 1 0;
|
|
592
|
+
min-width: 0;
|
|
593
|
+
white-space: nowrap;
|
|
594
|
+
overflow: hidden;
|
|
595
|
+
text-overflow: ellipsis;
|
|
438
596
|
}
|
|
597
|
+
/* Time tag · same register as `.row-time` in the rooms list
|
|
598
|
+
(10.5px mono, text-dim) so both lists read as one consistent
|
|
599
|
+
"title · time" rhythm. Earlier this was 9px uppercase faint —
|
|
600
|
+
visually a different "kind of metadata" from the rooms time. */
|
|
439
601
|
.agent-row .agent-row-time {
|
|
440
|
-
color: var(--text-
|
|
441
|
-
font-size:
|
|
442
|
-
|
|
602
|
+
color: var(--text-dim);
|
|
603
|
+
font-size: 10.5px;
|
|
604
|
+
font-family: var(--mono);
|
|
605
|
+
letter-spacing: 0;
|
|
606
|
+
text-transform: none;
|
|
443
607
|
white-space: nowrap;
|
|
444
|
-
|
|
608
|
+
flex-shrink: 0;
|
|
445
609
|
}
|
|
446
610
|
.agent-row .agent-row-subtitle {
|
|
447
611
|
font-family: var(--mono);
|
|
@@ -483,7 +647,17 @@
|
|
|
483
647
|
accent · outlined (hairline border + cyan text on transparent
|
|
484
648
|
bg) instead of a solid cyan fill that screamed at the user.
|
|
485
649
|
· Section header reverts to the standard text-faint kicker. */
|
|
486
|
-
|
|
650
|
+
/* Chair row · NOT wrapped in `.agent-row-shell` (the shell provides
|
|
651
|
+
the 1px×6px inset + 4px border-radius for director rows). To make
|
|
652
|
+
the chair's hover/active background match — same inset, same
|
|
653
|
+
rounded corners — fold those rules onto the chair anchor itself.
|
|
654
|
+
Without this, director hover felt "cushioned" while chair hover
|
|
655
|
+
painted edge-to-edge with sharp corners. */
|
|
656
|
+
.agent-row.is-chair {
|
|
657
|
+
background: transparent;
|
|
658
|
+
margin: 1px 6px;
|
|
659
|
+
border-radius: 4px;
|
|
660
|
+
}
|
|
487
661
|
.agent-row.is-chair:hover { background: var(--panel-2); }
|
|
488
662
|
.agent-row.is-chair.active { background: var(--panel-3); }
|
|
489
663
|
.agent-row.is-chair .agent-row-av.chair-av {
|
|
@@ -563,7 +737,7 @@
|
|
|
563
737
|
color: var(--text);
|
|
564
738
|
border: none;
|
|
565
739
|
font-family: var(--sans);
|
|
566
|
-
font-size:
|
|
740
|
+
font-size: 14px;
|
|
567
741
|
font-weight: 500;
|
|
568
742
|
cursor: pointer;
|
|
569
743
|
text-decoration: none;
|
|
@@ -589,8 +763,8 @@
|
|
|
589
763
|
adjacent label without per-icon overrides. */
|
|
590
764
|
.new-btn::before {
|
|
591
765
|
content: "";
|
|
592
|
-
width:
|
|
593
|
-
height:
|
|
766
|
+
width: 16px;
|
|
767
|
+
height: 16px;
|
|
594
768
|
flex-shrink: 0;
|
|
595
769
|
background-color: currentColor;
|
|
596
770
|
/* Inherit `color` from .new-btn so the icon picks up the same
|
|
@@ -602,8 +776,8 @@
|
|
|
602
776
|
mask-repeat: no-repeat;
|
|
603
777
|
-webkit-mask-position: center;
|
|
604
778
|
mask-position: center;
|
|
605
|
-
-webkit-mask-size:
|
|
606
|
-
mask-size:
|
|
779
|
+
-webkit-mask-size: 16px 16px;
|
|
780
|
+
mask-size: 16px 16px;
|
|
607
781
|
transition: color 0.12s;
|
|
608
782
|
}
|
|
609
783
|
/* "New room" · SquarePen (Lucide) — square frame with a pen
|
|
@@ -622,6 +796,53 @@
|
|
|
622
796
|
.new-btn.nav-reports::before {
|
|
623
797
|
--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='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z'/><path d='M14 2v4a2 2 0 0 0 2 2h4'/><path d='M10 9H8'/><path d='M16 13H8'/><path d='M16 17H8'/></svg>");
|
|
624
798
|
}
|
|
799
|
+
/* "All Notes" · Bookmark (Lucide) — pennant-shaped bookmark glyph
|
|
800
|
+
mirroring the qcta save-button icon. Matches the chairman's-notes
|
|
801
|
+
vocabulary across the app (sidebar entry, save action, in-room
|
|
802
|
+
overlay all share the bookmark register). */
|
|
803
|
+
.new-btn.nav-notes::before {
|
|
804
|
+
--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='M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z'/></svg>");
|
|
805
|
+
}
|
|
806
|
+
/* All Reports / All Notes nav-button shape · the count badge floats
|
|
807
|
+
to the right of the label, so the link's flex layout grows to fill
|
|
808
|
+
the row. `.nav-label` flex-grows, `.nav-count` is auto-sized and
|
|
809
|
+
hugs the trailing edge. */
|
|
810
|
+
.new-btn.nav-notes,
|
|
811
|
+
.new-btn.nav-reports {
|
|
812
|
+
justify-content: flex-start;
|
|
813
|
+
}
|
|
814
|
+
.new-btn.nav-notes .nav-label,
|
|
815
|
+
.new-btn.nav-reports .nav-label {
|
|
816
|
+
flex: 1;
|
|
817
|
+
min-width: 0;
|
|
818
|
+
}
|
|
819
|
+
/* Count badge · subtle mono micro-tag. Uses --text-faint on its own
|
|
820
|
+
and inherits the link's color cascade on hover/active so the badge
|
|
821
|
+
tracks the label's lime shift. Hidden until at least one item
|
|
822
|
+
exists (the `hidden` attr is removed once count > 0). Same visual
|
|
823
|
+
vocabulary as the section-header .badge below — both render as
|
|
824
|
+
mono pills so the sidebar reads as one consistent counting
|
|
825
|
+
system. */
|
|
826
|
+
.new-btn.nav-notes .nav-count,
|
|
827
|
+
.new-btn.nav-reports .nav-count {
|
|
828
|
+
font-family: var(--mono);
|
|
829
|
+
font-size: 10px;
|
|
830
|
+
font-weight: 600;
|
|
831
|
+
letter-spacing: 0.04em;
|
|
832
|
+
color: var(--text-faint);
|
|
833
|
+
padding: 1px 6px;
|
|
834
|
+
background: var(--panel-3);
|
|
835
|
+
border-radius: 3px;
|
|
836
|
+
flex-shrink: 0;
|
|
837
|
+
transition: color 0.12s, background 0.12s;
|
|
838
|
+
}
|
|
839
|
+
.new-btn.nav-notes:hover .nav-count,
|
|
840
|
+
.new-btn.nav-notes.active .nav-count,
|
|
841
|
+
.new-btn.nav-reports:hover .nav-count,
|
|
842
|
+
.new-btn.nav-reports.active .nav-count {
|
|
843
|
+
color: var(--lime);
|
|
844
|
+
background: rgba(190, 242, 100, 0.08);
|
|
845
|
+
}
|
|
625
846
|
.new-btn:hover {
|
|
626
847
|
background: var(--panel-2);
|
|
627
848
|
color: var(--lime);
|
|
@@ -646,7 +867,17 @@
|
|
|
646
867
|
display: flex;
|
|
647
868
|
align-items: center;
|
|
648
869
|
gap: 6px;
|
|
649
|
-
|
|
870
|
+
/* Trailing count badge column-aligns with the .new-btn.nav-*
|
|
871
|
+
badges above. Geometry:
|
|
872
|
+
nav badge inset · 6px margin + 10px padding = 16px from
|
|
873
|
+
the sidebar edge
|
|
874
|
+
section badge inset · 4px sessions-scroll padding + this
|
|
875
|
+
rule's right padding
|
|
876
|
+
So this rule's right padding must be 16 − 4 = 12px. The
|
|
877
|
+
parent's 4px padding was missed in an earlier version that
|
|
878
|
+
set this to 16px and made the section badges drift 4px
|
|
879
|
+
further left than the nav badges. */
|
|
880
|
+
padding: 8px 12px 4px 10px;
|
|
650
881
|
font-size: 9px;
|
|
651
882
|
color: var(--text-dim);
|
|
652
883
|
text-transform: uppercase;
|
|
@@ -668,12 +899,27 @@
|
|
|
668
899
|
}
|
|
669
900
|
.row-status.paused { color: var(--amber); }
|
|
670
901
|
.section-header .line { flex: 1; border-top: 0.5px dashed var(--line-bright); }
|
|
902
|
+
/* Section badge · same visual vocabulary as .new-btn.nav-* .nav-count
|
|
903
|
+
above so the sidebar reads as one consistent counting system.
|
|
904
|
+
Mono pill, panel-3 bg, faint text, rounded — overrides the
|
|
905
|
+
.section-header parent's sans uppercase letter-spacing because a
|
|
906
|
+
count is a number, not a label. The .live variant still takes its
|
|
907
|
+
pulsing dot via ::before. */
|
|
671
908
|
.section-header .badge {
|
|
672
|
-
font-
|
|
673
|
-
|
|
674
|
-
font-weight:
|
|
675
|
-
|
|
676
|
-
|
|
909
|
+
font-family: var(--mono);
|
|
910
|
+
font-size: 10px;
|
|
911
|
+
font-weight: 600;
|
|
912
|
+
letter-spacing: 0.04em;
|
|
913
|
+
text-transform: none;
|
|
914
|
+
color: var(--text-faint);
|
|
915
|
+
padding: 1px 6px;
|
|
916
|
+
background: var(--panel-3);
|
|
917
|
+
border-radius: 3px;
|
|
918
|
+
flex-shrink: 0;
|
|
919
|
+
}
|
|
920
|
+
.section-header.live .badge {
|
|
921
|
+
color: var(--lime);
|
|
922
|
+
background: rgba(190, 242, 100, 0.08);
|
|
677
923
|
}
|
|
678
924
|
.section-header.live .badge::before {
|
|
679
925
|
content: "● ";
|
|
@@ -687,9 +933,20 @@
|
|
|
687
933
|
}
|
|
688
934
|
.agents-section-header.pinned { color: var(--text-soft); }
|
|
689
935
|
|
|
690
|
-
/* ─── Pin toggle (per-row · hover-revealed for non-pinned) ───
|
|
936
|
+
/* ─── Pin toggle (per-row · hover-revealed for non-pinned) ───
|
|
937
|
+
Positioned absolutely so the in-flow time tag (.agent-row-time /
|
|
938
|
+
.row-time) sits flush against the right edge — same as the rooms
|
|
939
|
+
list. Earlier the pin took 22px (18 + 4 margin) of in-flow width
|
|
940
|
+
even when invisible (opacity:0 still occupies space), which shoved
|
|
941
|
+
the agent's "7h" tag inward compared to the rooms list and made
|
|
942
|
+
the two columns of timestamps look mis-aligned. The row-shell
|
|
943
|
+
ancestor needs `position: relative` for this to anchor correctly. */
|
|
944
|
+
.agent-row-shell, .session-row-shell { position: relative; }
|
|
691
945
|
.row-top-line .pin-toggle,
|
|
692
946
|
.agent-row-top-line .pin-toggle {
|
|
947
|
+
position: absolute;
|
|
948
|
+
top: 8px;
|
|
949
|
+
right: 8px;
|
|
693
950
|
width: 18px;
|
|
694
951
|
height: 18px;
|
|
695
952
|
border: none;
|
|
@@ -700,10 +957,8 @@
|
|
|
700
957
|
align-items: center;
|
|
701
958
|
justify-content: center;
|
|
702
959
|
padding: 0;
|
|
703
|
-
margin-left: 4px;
|
|
704
960
|
opacity: 0;
|
|
705
961
|
transition: opacity 0.12s, color 0.12s;
|
|
706
|
-
flex-shrink: 0;
|
|
707
962
|
}
|
|
708
963
|
.row-top-line .pin-toggle svg,
|
|
709
964
|
.agent-row-top-line .pin-toggle svg {
|
|
@@ -713,6 +968,10 @@
|
|
|
713
968
|
}
|
|
714
969
|
.session-row:hover .pin-toggle,
|
|
715
970
|
.agent-row:hover .pin-toggle { opacity: 0.55; }
|
|
971
|
+
/* Hover · hide the in-flow time so the absolutely-positioned pin
|
|
972
|
+
button has the right edge to itself. Same pattern as the rooms
|
|
973
|
+
list (`.session-row-shell:hover .row-time { visibility: hidden }`). */
|
|
974
|
+
.agent-row-shell:hover .agent-row-time { visibility: hidden; }
|
|
716
975
|
.pin-toggle:hover { opacity: 1 !important; color: var(--lime); }
|
|
717
976
|
.session-row.pinned .pin-toggle,
|
|
718
977
|
.agent-row.pinned .pin-toggle {
|
|
@@ -853,7 +1112,7 @@
|
|
|
853
1112
|
margin-bottom: 1px;
|
|
854
1113
|
}
|
|
855
1114
|
.row-title {
|
|
856
|
-
font-size:
|
|
1115
|
+
font-size: 14px;
|
|
857
1116
|
font-weight: 600;
|
|
858
1117
|
color: var(--text);
|
|
859
1118
|
overflow: hidden;
|
|
@@ -1066,6 +1325,20 @@
|
|
|
1066
1325
|
.main-view[data-main-view="reports"]:hover { scrollbar-width: thin; }
|
|
1067
1326
|
.main-view[data-main-view="reports"]:hover::-webkit-scrollbar-thumb { background: var(--line-bright); }
|
|
1068
1327
|
|
|
1328
|
+
/* All Notes view · same scroll register as All Reports so the two
|
|
1329
|
+
cross-room aggregation destinations read as a paired set. */
|
|
1330
|
+
.main-view[data-main-view="notes"] {
|
|
1331
|
+
display: block;
|
|
1332
|
+
overflow-y: auto;
|
|
1333
|
+
overscroll-behavior: none;
|
|
1334
|
+
scrollbar-width: none;
|
|
1335
|
+
background: var(--panel);
|
|
1336
|
+
}
|
|
1337
|
+
.main-view[data-main-view="notes"]::-webkit-scrollbar-thumb { background: transparent; }
|
|
1338
|
+
.main-view[data-main-view="notes"]::-webkit-scrollbar-track { background: transparent; }
|
|
1339
|
+
.main-view[data-main-view="notes"]:hover { scrollbar-width: thin; }
|
|
1340
|
+
.main-view[data-main-view="notes"]:hover::-webkit-scrollbar-thumb { background: var(--line-bright); }
|
|
1341
|
+
|
|
1069
1342
|
/* Hidden takes precedence over the per-view display rules above.
|
|
1070
1343
|
Declared LAST + with !important so the variant rules (which match
|
|
1071
1344
|
at the same specificity) can never override it back to block —
|
|
@@ -1123,14 +1396,19 @@
|
|
|
1123
1396
|
bg + lime label), same 4px corner radius, same hover (panel-2 bg).
|
|
1124
1397
|
One coherent vocabulary across every segmented control. The count
|
|
1125
1398
|
rides inline after the label as quiet mono micro-type — no nested
|
|
1126
|
-
chip chrome.
|
|
1127
|
-
|
|
1399
|
+
chip chrome.
|
|
1400
|
+
Comma-extended to `.notes-filters` / `.notes-filter-chip` so the
|
|
1401
|
+
All Notes page reuses the exact same chip vocabulary — both
|
|
1402
|
+
cross-room aggregation destinations stay visually identical. */
|
|
1403
|
+
.reports-filters,
|
|
1404
|
+
.notes-filters {
|
|
1128
1405
|
display: flex;
|
|
1129
1406
|
gap: 2px;
|
|
1130
1407
|
margin: 22px 0 6px;
|
|
1131
1408
|
flex-wrap: wrap;
|
|
1132
1409
|
}
|
|
1133
|
-
.reports-filter-chip
|
|
1410
|
+
.reports-filter-chip,
|
|
1411
|
+
.notes-filter-chip {
|
|
1134
1412
|
background: transparent;
|
|
1135
1413
|
border: none;
|
|
1136
1414
|
color: var(--text-dim);
|
|
@@ -1146,27 +1424,67 @@
|
|
|
1146
1424
|
gap: 6px;
|
|
1147
1425
|
transition: background 0.12s, color 0.12s;
|
|
1148
1426
|
}
|
|
1149
|
-
.reports-filter-chip:hover
|
|
1427
|
+
.reports-filter-chip:hover,
|
|
1428
|
+
.notes-filter-chip:hover {
|
|
1150
1429
|
background: var(--panel-2);
|
|
1151
1430
|
color: var(--text-soft);
|
|
1152
1431
|
}
|
|
1153
|
-
.reports-filter-chip.on
|
|
1432
|
+
.reports-filter-chip.on,
|
|
1433
|
+
.notes-filter-chip.on {
|
|
1154
1434
|
background: var(--panel-3);
|
|
1155
1435
|
color: var(--text);
|
|
1156
1436
|
font-weight: 600;
|
|
1157
1437
|
}
|
|
1158
|
-
.reports-filter-count
|
|
1438
|
+
.reports-filter-count,
|
|
1439
|
+
.notes-filter-count {
|
|
1159
1440
|
font-family: var(--mono);
|
|
1160
1441
|
font-size: 10.5px;
|
|
1161
1442
|
color: var(--text-faint);
|
|
1162
1443
|
font-weight: 400;
|
|
1163
1444
|
letter-spacing: 0;
|
|
1164
1445
|
}
|
|
1165
|
-
.reports-filter-chip.on .reports-filter-count
|
|
1446
|
+
.reports-filter-chip.on .reports-filter-count,
|
|
1447
|
+
.notes-filter-chip.on .notes-filter-count { color: var(--text-dim); }
|
|
1166
1448
|
|
|
1167
1449
|
/* ─── Reading list ─────────────────────────────────────────────── */
|
|
1168
1450
|
.reports-list-wrap { margin-top: 18px; }
|
|
1169
1451
|
|
|
1452
|
+
/* Load-more sentinel · sits at the bottom of the list when more
|
|
1453
|
+
items are available beyond the current page (default 20). The
|
|
1454
|
+
IntersectionObserver fires when this enters the viewport (200px
|
|
1455
|
+
pre-roll) and bumps the visible count by 20. The button is also
|
|
1456
|
+
a click target for users who'd rather page explicitly. */
|
|
1457
|
+
.reports-load-sentinel {
|
|
1458
|
+
margin-top: 24px;
|
|
1459
|
+
padding: 16px 0 32px;
|
|
1460
|
+
display: flex;
|
|
1461
|
+
justify-content: center;
|
|
1462
|
+
}
|
|
1463
|
+
.reports-load-more {
|
|
1464
|
+
display: inline-flex;
|
|
1465
|
+
align-items: center;
|
|
1466
|
+
gap: 8px;
|
|
1467
|
+
padding: 8px 16px;
|
|
1468
|
+
background: transparent;
|
|
1469
|
+
color: var(--text-soft);
|
|
1470
|
+
border: 1px solid var(--line-bright);
|
|
1471
|
+
border-radius: 4px;
|
|
1472
|
+
font-family: var(--mono);
|
|
1473
|
+
font-size: 11px;
|
|
1474
|
+
letter-spacing: 0.04em;
|
|
1475
|
+
cursor: pointer;
|
|
1476
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
1477
|
+
}
|
|
1478
|
+
.reports-load-more:hover {
|
|
1479
|
+
color: var(--lime);
|
|
1480
|
+
border-color: var(--lime);
|
|
1481
|
+
background: var(--panel-2);
|
|
1482
|
+
}
|
|
1483
|
+
.reports-load-more-arrow {
|
|
1484
|
+
font-size: 13px;
|
|
1485
|
+
line-height: 1;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1170
1488
|
/* Date-bucket label · sits flush left, hairline rule fills the
|
|
1171
1489
|
remaining width. Reads as a quiet section break. */
|
|
1172
1490
|
.reports-group + .reports-group { margin-top: 32px; }
|
|
@@ -1418,76 +1736,424 @@
|
|
|
1418
1736
|
.reports-item-room { max-width: 140px; }
|
|
1419
1737
|
}
|
|
1420
1738
|
|
|
1421
|
-
/*
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1739
|
+
/* ─── All Notes view · chairman's saved excerpts ──────────────
|
|
1740
|
+
Same column geometry + typographic register as `.reports-page`
|
|
1741
|
+
so the two cross-room aggregation destinations read as a paired
|
|
1742
|
+
set. The substantive difference is the "passage" cell — each
|
|
1743
|
+
note row foregrounds the saved quote with a thick dotted lime
|
|
1744
|
+
underline (text-decoration only, NOT bold-weight type), wrapped
|
|
1745
|
+
by faded sentence-context that gradient-masks toward the
|
|
1746
|
+
edges. The visual treatment is the same overlay the in-room
|
|
1747
|
+
`.note-highlight` uses, so a note seen on this page reads with
|
|
1748
|
+
the same identity when the user jumps back to the source. */
|
|
1749
|
+
.notes-page {
|
|
1750
|
+
max-width: 820px;
|
|
1751
|
+
margin: 0 auto;
|
|
1752
|
+
padding: 56px 48px 80px;
|
|
1431
1753
|
}
|
|
1432
|
-
|
|
1433
|
-
::after below the tag) can escape this container. The room-subject
|
|
1434
|
-
has its own overflow:hidden + text-overflow ellipsis rule, so the
|
|
1435
|
-
long-title truncation behaviour is preserved without needing it
|
|
1436
|
-
at this level. */
|
|
1437
|
-
.room-info { min-width: 0; overflow: visible; }
|
|
1438
|
-
|
|
1439
|
-
.room-id {
|
|
1754
|
+
.notes-page-head {
|
|
1440
1755
|
display: flex;
|
|
1441
|
-
align-items:
|
|
1442
|
-
|
|
1443
|
-
|
|
1756
|
+
align-items: baseline;
|
|
1757
|
+
justify-content: space-between;
|
|
1758
|
+
gap: 16px;
|
|
1759
|
+
margin-bottom: 8px;
|
|
1760
|
+
border-bottom: 0.5px solid var(--line);
|
|
1761
|
+
padding-bottom: 14px;
|
|
1444
1762
|
}
|
|
1445
|
-
.
|
|
1763
|
+
.notes-page-title {
|
|
1764
|
+
font-family: var(--font-human);
|
|
1765
|
+
font-size: 26px;
|
|
1766
|
+
font-weight: 700;
|
|
1767
|
+
letter-spacing: -0.01em;
|
|
1768
|
+
color: var(--text);
|
|
1769
|
+
}
|
|
1770
|
+
.notes-page-kicker {
|
|
1771
|
+
font-family: var(--mono);
|
|
1446
1772
|
font-size: 9.5px;
|
|
1447
|
-
|
|
1448
|
-
background: var(--lime);
|
|
1449
|
-
padding: 1px 7px;
|
|
1773
|
+
letter-spacing: 0.22em;
|
|
1450
1774
|
text-transform: uppercase;
|
|
1451
|
-
|
|
1775
|
+
color: var(--text-faint);
|
|
1452
1776
|
font-weight: 700;
|
|
1777
|
+
margin-bottom: 4px;
|
|
1453
1778
|
}
|
|
1454
|
-
.
|
|
1455
|
-
font-
|
|
1779
|
+
.notes-page-meta {
|
|
1780
|
+
font-family: var(--mono);
|
|
1781
|
+
font-size: 10.5px;
|
|
1782
|
+
letter-spacing: 0.04em;
|
|
1456
1783
|
color: var(--text-dim);
|
|
1457
|
-
text-transform: uppercase;
|
|
1458
|
-
letter-spacing: 0.08em;
|
|
1459
1784
|
}
|
|
1460
1785
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
max-width: 100%;
|
|
1786
|
+
/* Date-bucket sections · each opens with its own hairline-divided
|
|
1787
|
+
label row, matching the reports `.reports-group` rhythm. */
|
|
1788
|
+
.notes-group + .notes-group { margin-top: 32px; }
|
|
1789
|
+
.notes-group:first-of-type { margin-top: 22px; }
|
|
1790
|
+
.notes-group-head {
|
|
1791
|
+
display: flex;
|
|
1792
|
+
align-items: baseline;
|
|
1793
|
+
justify-content: space-between;
|
|
1794
|
+
padding-bottom: 8px;
|
|
1795
|
+
border-bottom: 0.5px solid var(--line);
|
|
1796
|
+
margin-bottom: 4px;
|
|
1473
1797
|
}
|
|
1474
|
-
|
|
1475
|
-
Reusable key::value pill used by the room subtitle, the empty-state
|
|
1476
|
-
starter cards, and the onboarding wizard starter cards. Defined
|
|
1477
|
-
unscoped so any context can opt in by adding `meta-tag` (+ optional
|
|
1478
|
-
`tag-tone` / `tag-intensity` / `tag-report` for accent colors). */
|
|
1479
|
-
.meta-tag {
|
|
1480
|
-
display: inline-flex;
|
|
1481
|
-
align-items: stretch;
|
|
1482
|
-
border: 0.5px solid var(--line-bright);
|
|
1483
|
-
background: var(--panel);
|
|
1484
|
-
overflow: hidden;
|
|
1798
|
+
.notes-group-label {
|
|
1485
1799
|
font-family: var(--mono);
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
color: var(--text-
|
|
1800
|
+
font-size: 10px;
|
|
1801
|
+
font-weight: 700;
|
|
1802
|
+
letter-spacing: 0.22em;
|
|
1803
|
+
text-transform: uppercase;
|
|
1804
|
+
color: var(--text-faint);
|
|
1805
|
+
}
|
|
1806
|
+
.notes-group-count {
|
|
1807
|
+
font-family: var(--mono);
|
|
1808
|
+
font-size: 10px;
|
|
1809
|
+
color: var(--text-dim);
|
|
1810
|
+
letter-spacing: 0.04em;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
.notes-list { list-style: none; margin: 0; padding: 0; }
|
|
1814
|
+
.notes-item { border-bottom: 0.5px solid var(--line); }
|
|
1815
|
+
.notes-item:last-child { border-bottom: none; }
|
|
1816
|
+
.notes-item-link {
|
|
1817
|
+
display: block;
|
|
1818
|
+
padding: 18px 4px;
|
|
1819
|
+
text-decoration: none;
|
|
1820
|
+
color: inherit;
|
|
1821
|
+
cursor: pointer;
|
|
1822
|
+
}
|
|
1823
|
+
/* Hover lifts ONLY the meta room-tag + bumps the quote underline
|
|
1824
|
+
to full-lime — the quote text itself stays at --text so the
|
|
1825
|
+
dotted-underline does the focal work. */
|
|
1826
|
+
.notes-item-link:hover .notes-item-room { color: var(--lime); }
|
|
1827
|
+
.notes-item-link:hover .note-quote { text-decoration-color: var(--lime); }
|
|
1828
|
+
|
|
1829
|
+
.notes-item-meta {
|
|
1830
|
+
display: flex;
|
|
1831
|
+
align-items: baseline;
|
|
1832
|
+
gap: 6px;
|
|
1833
|
+
flex-wrap: wrap;
|
|
1834
|
+
font-family: var(--mono);
|
|
1835
|
+
font-size: 9.5px;
|
|
1836
|
+
letter-spacing: 0.14em;
|
|
1837
|
+
text-transform: uppercase;
|
|
1838
|
+
color: var(--text-faint);
|
|
1839
|
+
font-weight: 700;
|
|
1840
|
+
margin-bottom: 8px;
|
|
1841
|
+
}
|
|
1842
|
+
.notes-item-room { color: var(--text-soft); transition: color 0.14s; }
|
|
1843
|
+
.notes-item-subject {
|
|
1844
|
+
color: var(--text-dim);
|
|
1845
|
+
text-transform: none;
|
|
1846
|
+
letter-spacing: -0.005em;
|
|
1847
|
+
font-family: var(--font-human);
|
|
1848
|
+
font-size: 11px;
|
|
1849
|
+
font-weight: 600;
|
|
1850
|
+
overflow: hidden;
|
|
1851
|
+
text-overflow: ellipsis;
|
|
1852
|
+
white-space: nowrap;
|
|
1853
|
+
max-width: 280px;
|
|
1854
|
+
}
|
|
1855
|
+
.notes-item-director {
|
|
1856
|
+
color: var(--text-soft);
|
|
1857
|
+
text-transform: none;
|
|
1858
|
+
letter-spacing: 0;
|
|
1859
|
+
font-family: var(--font-human);
|
|
1860
|
+
font-size: 11px;
|
|
1861
|
+
font-weight: 600;
|
|
1862
|
+
}
|
|
1863
|
+
.notes-item-time {
|
|
1864
|
+
margin-left: auto;
|
|
1865
|
+
color: var(--text-faint);
|
|
1866
|
+
letter-spacing: 0.06em;
|
|
1867
|
+
}
|
|
1868
|
+
.notes-item-sep { color: var(--text-faint); opacity: 0.7; }
|
|
1869
|
+
|
|
1870
|
+
/* The reading passage · context_before + quote + context_after
|
|
1871
|
+
in a single inline flow. Contexts use a horizontal mask-image
|
|
1872
|
+
fade toward the outer edges so the quote sits in a "spotlight"
|
|
1873
|
+
of full-strength text while the surrounding sentences taper
|
|
1874
|
+
visually — reads as "supporting context" without sacrificing
|
|
1875
|
+
readability. The mask applies to every wrapped line, which is
|
|
1876
|
+
what we want: each line's far edge is the "least important"
|
|
1877
|
+
spatially, so each fades. */
|
|
1878
|
+
.notes-item-passage {
|
|
1879
|
+
font-family: var(--font-human);
|
|
1880
|
+
font-size: 14px;
|
|
1881
|
+
line-height: 1.6;
|
|
1882
|
+
color: var(--text);
|
|
1883
|
+
margin: 0;
|
|
1884
|
+
word-break: normal;
|
|
1885
|
+
overflow-wrap: anywhere;
|
|
1886
|
+
}
|
|
1887
|
+
.note-context { color: var(--text-soft); }
|
|
1888
|
+
.note-context-before {
|
|
1889
|
+
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 32%);
|
|
1890
|
+
mask-image: linear-gradient(to right, transparent 0%, black 32%);
|
|
1891
|
+
}
|
|
1892
|
+
.note-context-after {
|
|
1893
|
+
-webkit-mask-image: linear-gradient(to right, black 68%, transparent 100%);
|
|
1894
|
+
mask-image: linear-gradient(to right, black 68%, transparent 100%);
|
|
1895
|
+
}
|
|
1896
|
+
/* The quote span · pure typography accent. Text weight stays
|
|
1897
|
+
normal (do NOT bold the type — it would collide with markdown's
|
|
1898
|
+
own `**emphasis**`); only the underline grows thicker. Dotted
|
|
1899
|
+
style differentiates from inline-link's solid 1px underline.
|
|
1900
|
+
The lime accent identifies the note's identity across the app
|
|
1901
|
+
— the same treatment appears on the in-room highlight overlay. */
|
|
1902
|
+
.note-quote {
|
|
1903
|
+
color: var(--text);
|
|
1904
|
+
text-decoration: underline dotted;
|
|
1905
|
+
text-decoration-thickness: 3px;
|
|
1906
|
+
text-decoration-color: var(--lime-dim);
|
|
1907
|
+
text-underline-offset: 4px;
|
|
1908
|
+
transition: text-decoration-color 0.14s;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
/* In-room highlight overlay · injected into director-message
|
|
1912
|
+
`.msg-bubble` text by app.applyNoteHighlightsForMessage. Same
|
|
1913
|
+
visual register as `.note-quote` so a saved span reads with the
|
|
1914
|
+
same identity wherever it appears — the user can re-open a room
|
|
1915
|
+
and see at a glance what they've already collected. Hover
|
|
1916
|
+
strengthens the underline to full lime; the cursor uses `help`
|
|
1917
|
+
so the saved-tooltip is discoverable (the title attr renders
|
|
1918
|
+
"Saved to Notes"). */
|
|
1919
|
+
.note-highlight {
|
|
1920
|
+
text-decoration: underline dotted;
|
|
1921
|
+
text-decoration-thickness: 3px;
|
|
1922
|
+
text-decoration-color: var(--lime-dim);
|
|
1923
|
+
text-underline-offset: 4px;
|
|
1924
|
+
cursor: help;
|
|
1925
|
+
transition: text-decoration-color 0.14s, background 0.18s;
|
|
1926
|
+
}
|
|
1927
|
+
.note-highlight:hover { text-decoration-color: var(--lime); }
|
|
1928
|
+
/* Flash · briefly blooms a soft lime backdrop when the user jumps
|
|
1929
|
+
to a note from the All-Notes view, so the eye lands on the
|
|
1930
|
+
right span. Fades back to the resting underline after 1.6s
|
|
1931
|
+
(matches scrollToNote's setTimeout in app.js). */
|
|
1932
|
+
.note-highlight-flash {
|
|
1933
|
+
animation: note-highlight-flash 1.6s ease-out;
|
|
1934
|
+
}
|
|
1935
|
+
@keyframes note-highlight-flash {
|
|
1936
|
+
0% { background: rgba(190, 242, 100, 0.28); }
|
|
1937
|
+
60% { background: rgba(190, 242, 100, 0.14); }
|
|
1938
|
+
100% { background: transparent; }
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
/* Hover tooltip · custom pop replacing the native `title` attribute
|
|
1942
|
+
so it appears immediately, not after the browser's 1–2s delay.
|
|
1943
|
+
Same visual register as the qcta floating bar (panel-2 surface,
|
|
1944
|
+
hairline border, mono micro-type). pointer-events:none so the
|
|
1945
|
+
tooltip can never block the hover that triggers it. */
|
|
1946
|
+
.note-tip {
|
|
1947
|
+
position: absolute;
|
|
1948
|
+
z-index: 1450;
|
|
1949
|
+
display: none;
|
|
1950
|
+
align-items: center;
|
|
1951
|
+
gap: 6px;
|
|
1952
|
+
background: var(--panel-2);
|
|
1953
|
+
border: 0.5px solid var(--line-strong);
|
|
1954
|
+
font-family: var(--mono);
|
|
1955
|
+
font-size: 11px;
|
|
1956
|
+
letter-spacing: 0.04em;
|
|
1957
|
+
padding: 6px 10px;
|
|
1958
|
+
color: var(--text);
|
|
1959
|
+
white-space: nowrap;
|
|
1960
|
+
pointer-events: none;
|
|
1961
|
+
box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.55);
|
|
1962
|
+
}
|
|
1963
|
+
.note-tip.open { display: inline-flex; }
|
|
1964
|
+
.note-tip-mark {
|
|
1965
|
+
color: var(--lime);
|
|
1966
|
+
font-weight: 700;
|
|
1967
|
+
font-size: 12px;
|
|
1968
|
+
line-height: 1;
|
|
1969
|
+
}
|
|
1970
|
+
.note-tip-label { color: var(--text); }
|
|
1971
|
+
.note-tip-sep { color: var(--text-faint); opacity: 0.7; }
|
|
1972
|
+
.note-tip-meta { color: var(--text-faint); }
|
|
1973
|
+
|
|
1974
|
+
/* Empty state · single placeholder card matching the visual
|
|
1975
|
+
register of `.reports-list-empty`, with a one-line hint and
|
|
1976
|
+
two `.kbd` pills explaining how to save the first note. */
|
|
1977
|
+
.notes-list-empty {
|
|
1978
|
+
margin-top: 28px;
|
|
1979
|
+
padding: 28px 24px 30px;
|
|
1980
|
+
background: var(--panel-2);
|
|
1981
|
+
border: 0.5px solid var(--line);
|
|
1982
|
+
border-radius: 6px;
|
|
1983
|
+
display: flex;
|
|
1984
|
+
flex-direction: column;
|
|
1985
|
+
gap: 10px;
|
|
1986
|
+
align-items: flex-start;
|
|
1987
|
+
}
|
|
1988
|
+
.notes-empty-mark {
|
|
1989
|
+
font-family: var(--mono);
|
|
1990
|
+
font-size: 18px;
|
|
1991
|
+
color: var(--text-faint);
|
|
1992
|
+
line-height: 1;
|
|
1993
|
+
}
|
|
1994
|
+
.notes-empty-title {
|
|
1995
|
+
font-family: var(--font-human);
|
|
1996
|
+
font-size: 15px;
|
|
1997
|
+
font-weight: 600;
|
|
1998
|
+
line-height: 1.3;
|
|
1999
|
+
color: var(--text-soft);
|
|
2000
|
+
}
|
|
2001
|
+
.notes-empty-deck {
|
|
2002
|
+
font-family: var(--font-human);
|
|
2003
|
+
font-size: 12.5px;
|
|
2004
|
+
line-height: 1.6;
|
|
2005
|
+
color: var(--text-dim);
|
|
2006
|
+
max-width: 540px;
|
|
2007
|
+
}
|
|
2008
|
+
.notes-list-empty .kbd {
|
|
2009
|
+
display: inline-flex;
|
|
2010
|
+
align-items: center;
|
|
2011
|
+
font-family: var(--mono);
|
|
2012
|
+
font-size: 10.5px;
|
|
2013
|
+
font-weight: 600;
|
|
2014
|
+
padding: 1px 7px;
|
|
2015
|
+
margin: 0 1px;
|
|
2016
|
+
border: 0.5px solid var(--line-bright);
|
|
2017
|
+
border-radius: 3px;
|
|
2018
|
+
color: var(--text);
|
|
2019
|
+
background: var(--panel-3);
|
|
2020
|
+
}
|
|
2021
|
+
/* Window-empty CTA · "← Show all notes" button shown when a
|
|
2022
|
+
non-All filter has zero matches. Same visual register as
|
|
2023
|
+
`.reports-list-empty-cta`. */
|
|
2024
|
+
.notes-empty-cta {
|
|
2025
|
+
margin-top: 6px;
|
|
2026
|
+
display: inline-flex;
|
|
2027
|
+
align-items: center;
|
|
2028
|
+
gap: 7px;
|
|
2029
|
+
padding: 6px 12px;
|
|
2030
|
+
background: transparent;
|
|
2031
|
+
border: 0.5px solid var(--line-bright);
|
|
2032
|
+
color: var(--text-soft);
|
|
2033
|
+
font-family: var(--mono);
|
|
2034
|
+
font-size: 10px;
|
|
2035
|
+
font-weight: 700;
|
|
2036
|
+
letter-spacing: 0.14em;
|
|
2037
|
+
text-transform: uppercase;
|
|
2038
|
+
cursor: pointer;
|
|
2039
|
+
border-radius: 4px;
|
|
2040
|
+
transition: border-color 0.12s, color 0.12s, background 0.12s;
|
|
2041
|
+
}
|
|
2042
|
+
.notes-empty-cta:hover {
|
|
2043
|
+
border-color: var(--lime);
|
|
2044
|
+
color: var(--lime);
|
|
2045
|
+
background: var(--panel-3);
|
|
2046
|
+
}
|
|
2047
|
+
.notes-empty-cta-arrow {
|
|
2048
|
+
font-family: var(--mono);
|
|
2049
|
+
font-size: 11px;
|
|
2050
|
+
line-height: 1;
|
|
2051
|
+
}
|
|
2052
|
+
/* Wrap around the date-grouped list · matches the reports list
|
|
2053
|
+
wrap so the chip strip → list spacing reads identically. */
|
|
2054
|
+
.notes-list-wrap { margin-top: 18px; }
|
|
2055
|
+
|
|
2056
|
+
/* Loading skeleton · matches `.reports-skeleton` rhythm and
|
|
2057
|
+
reuses its pulse keyframe. */
|
|
2058
|
+
.notes-skeleton {
|
|
2059
|
+
display: flex;
|
|
2060
|
+
flex-direction: column;
|
|
2061
|
+
gap: 18px;
|
|
2062
|
+
margin-top: 32px;
|
|
2063
|
+
}
|
|
2064
|
+
.notes-skeleton-card {
|
|
2065
|
+
height: 84px;
|
|
2066
|
+
background: var(--panel-2);
|
|
2067
|
+
border: 0.5px solid var(--line);
|
|
2068
|
+
border-radius: 4px;
|
|
2069
|
+
animation: reports-skeleton-pulse 1.6s ease-in-out infinite;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
@media (max-width: 720px) {
|
|
2073
|
+
.notes-page { padding: 36px 22px 60px; }
|
|
2074
|
+
.notes-page-title { font-size: 22px; }
|
|
2075
|
+
.notes-item-subject { max-width: 160px; }
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/* Room head — generously padded so the title has breathing
|
|
2079
|
+
room above and below. */
|
|
2080
|
+
.room-head {
|
|
2081
|
+
background: var(--panel-2);
|
|
2082
|
+
border-bottom: 0.5px solid var(--line-bright);
|
|
2083
|
+
padding: 16px 18px;
|
|
2084
|
+
display: grid;
|
|
2085
|
+
grid-template-columns: 1fr auto;
|
|
2086
|
+
gap: 14px;
|
|
2087
|
+
align-items: center;
|
|
2088
|
+
}
|
|
2089
|
+
/* When the sidebar is collapsed the room-head gains a leading
|
|
2090
|
+
auto-sized track for the in-header `.room-head-expand` button.
|
|
2091
|
+
When expanded the button is display:none, so a 0-width track
|
|
2092
|
+
would still leave a `gap` artifact — switching to a 3-track
|
|
2093
|
+
template only in the collapsed state keeps the header visually
|
|
2094
|
+
identical to before whenever the sidebar is open. */
|
|
2095
|
+
body.sidebar-collapsed .room-head {
|
|
2096
|
+
grid-template-columns: auto 1fr auto;
|
|
2097
|
+
}
|
|
2098
|
+
/* `overflow: visible` so the tone-tag hover tooltip (positioned via
|
|
2099
|
+
::after below the tag) can escape this container. The room-subject
|
|
2100
|
+
has its own overflow:hidden + text-overflow ellipsis rule, so the
|
|
2101
|
+
long-title truncation behaviour is preserved without needing it
|
|
2102
|
+
at this level. */
|
|
2103
|
+
.room-info { min-width: 0; overflow: visible; }
|
|
2104
|
+
|
|
2105
|
+
.room-id {
|
|
2106
|
+
display: flex;
|
|
2107
|
+
align-items: center;
|
|
2108
|
+
gap: 8px;
|
|
2109
|
+
margin-bottom: 6px;
|
|
2110
|
+
}
|
|
2111
|
+
.room-name {
|
|
2112
|
+
font-size: 9.5px;
|
|
2113
|
+
color: var(--bg);
|
|
2114
|
+
background: var(--lime);
|
|
2115
|
+
padding: 1px 7px;
|
|
2116
|
+
text-transform: uppercase;
|
|
2117
|
+
letter-spacing: 0.1em;
|
|
2118
|
+
font-weight: 700;
|
|
2119
|
+
}
|
|
2120
|
+
.session-num {
|
|
2121
|
+
font-size: 9.5px;
|
|
2122
|
+
color: var(--text-dim);
|
|
2123
|
+
text-transform: uppercase;
|
|
2124
|
+
letter-spacing: 0.08em;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
.room-subject {
|
|
2128
|
+
font-size: 17px;
|
|
2129
|
+
font-weight: 600;
|
|
2130
|
+
color: var(--text);
|
|
2131
|
+
line-height: 1.3;
|
|
2132
|
+
margin-bottom: 10px;
|
|
2133
|
+
font-family: var(--sans);
|
|
2134
|
+
letter-spacing: -0.012em;
|
|
2135
|
+
overflow: hidden;
|
|
2136
|
+
text-overflow: ellipsis;
|
|
2137
|
+
white-space: nowrap;
|
|
2138
|
+
max-width: 100%;
|
|
2139
|
+
}
|
|
2140
|
+
/* ─── Meta tag chip ───
|
|
2141
|
+
Reusable key::value pill used by the room subtitle, the empty-state
|
|
2142
|
+
starter cards, and the onboarding wizard starter cards. Defined
|
|
2143
|
+
unscoped so any context can opt in by adding `meta-tag` (+ optional
|
|
2144
|
+
`tag-tone` / `tag-intensity` / `tag-report` for accent colors). */
|
|
2145
|
+
.meta-tag {
|
|
2146
|
+
display: inline-flex;
|
|
2147
|
+
align-items: stretch;
|
|
2148
|
+
border: 0.5px solid var(--line-bright);
|
|
2149
|
+
background: var(--panel);
|
|
2150
|
+
overflow: hidden;
|
|
2151
|
+
font-family: var(--mono);
|
|
2152
|
+
line-height: 1.4;
|
|
2153
|
+
}
|
|
2154
|
+
.meta-tag .k {
|
|
2155
|
+
background: var(--panel-3);
|
|
2156
|
+
color: var(--text-soft);
|
|
1491
2157
|
text-transform: uppercase;
|
|
1492
2158
|
letter-spacing: 0.14em;
|
|
1493
2159
|
font-size: 9px;
|
|
@@ -1768,9 +2434,18 @@
|
|
|
1768
2434
|
the SSE `room-paused` event arrives. */
|
|
1769
2435
|
html.pause-pending .input-bar .input-wrap { display: none; }
|
|
1770
2436
|
html.pause-pending .input-bar .speaking-queue { display: none; }
|
|
2437
|
+
/* Hide the session-control toolbar mid-transition · prevents double-
|
|
2438
|
+
clicking pause while the previous pause is still resolving (and
|
|
2439
|
+
adjourn while we're stopping cleanly is also confusing UX). */
|
|
2440
|
+
html.pause-pending .input-bar .input-bar-actions { display: none; }
|
|
1771
2441
|
html.pause-pending .input-bar::before {
|
|
1772
2442
|
content: "⌛ pausing after the current turn finishes…";
|
|
1773
2443
|
display: block;
|
|
2444
|
+
/* `.input-bar` is now `display: flex` (for the new icon toolbar),
|
|
2445
|
+
so this pseudo is a flex child. Without `flex: 1` it shrinks
|
|
2446
|
+
to its text width instead of spanning the bar. The grow-to-fill
|
|
2447
|
+
restores the pre-flex banner behaviour. */
|
|
2448
|
+
flex: 1 1 auto;
|
|
1774
2449
|
text-align: center;
|
|
1775
2450
|
font-family: var(--mono);
|
|
1776
2451
|
font-size: 11px;
|
|
@@ -1821,6 +2496,39 @@
|
|
|
1821
2496
|
}
|
|
1822
2497
|
.resume-btn-lg:hover { background: var(--bg); color: var(--lime); }
|
|
1823
2498
|
|
|
2499
|
+
/* Adjourn button on the paused-bar · same shape as resume-btn-lg
|
|
2500
|
+
but secondary (ghost) treatment so resume reads as the primary
|
|
2501
|
+
action when both are present. Goes warn-coloured on hover for
|
|
2502
|
+
the destructive cue (matches the input-bar's ib-adjourn). */
|
|
2503
|
+
.adjourn-btn-lg {
|
|
2504
|
+
display: inline-flex;
|
|
2505
|
+
align-items: center;
|
|
2506
|
+
gap: 5px;
|
|
2507
|
+
padding: 6px 12px;
|
|
2508
|
+
background: transparent;
|
|
2509
|
+
color: var(--text-soft);
|
|
2510
|
+
border: 0.5px solid var(--line-bright);
|
|
2511
|
+
font-family: var(--mono);
|
|
2512
|
+
font-size: 10px;
|
|
2513
|
+
font-weight: 700;
|
|
2514
|
+
line-height: 1.2;
|
|
2515
|
+
cursor: pointer;
|
|
2516
|
+
text-decoration: none;
|
|
2517
|
+
text-transform: uppercase;
|
|
2518
|
+
letter-spacing: 0.1em;
|
|
2519
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
2520
|
+
}
|
|
2521
|
+
.adjourn-btn-lg .adjourn-glyph {
|
|
2522
|
+
color: var(--text-faint);
|
|
2523
|
+
letter-spacing: -0.04em;
|
|
2524
|
+
}
|
|
2525
|
+
.adjourn-btn-lg:hover {
|
|
2526
|
+
color: var(--red, #C75450);
|
|
2527
|
+
border-color: var(--red, #C75450);
|
|
2528
|
+
background: var(--bg);
|
|
2529
|
+
}
|
|
2530
|
+
.adjourn-btn-lg:hover .adjourn-glyph { color: var(--red, #C75450); }
|
|
2531
|
+
|
|
1824
2532
|
/* ─── Adjourned-state controls (room is archived) ─── */
|
|
1825
2533
|
.adjourned-pill {
|
|
1826
2534
|
display: inline-flex;
|
|
@@ -1858,20 +2566,30 @@
|
|
|
1858
2566
|
letter-spacing: 0.1em;
|
|
1859
2567
|
}
|
|
1860
2568
|
.view-report-btn:hover { background: var(--bg); color: var(--lime); }
|
|
1861
|
-
/*
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2569
|
+
/* Generate-report variant · shown in the head when an adjourned
|
|
2570
|
+
room has no brief yet. Active CTA (lime filled, same register as
|
|
2571
|
+
the "View Report" button next door) — clicking POSTs to
|
|
2572
|
+
/api/rooms/:id/brief and kicks off the post-hoc 3-stage pipeline.
|
|
2573
|
+
Replaces the earlier muted `[ ⊘ No Report ]` static span which
|
|
2574
|
+
gave users no path back to a brief once they'd skipped at adjourn. */
|
|
2575
|
+
.view-report-btn.generate-report {
|
|
2576
|
+
display: inline-flex;
|
|
2577
|
+
align-items: center;
|
|
2578
|
+
gap: 6px;
|
|
1870
2579
|
}
|
|
1871
|
-
.view-report-btn.
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2580
|
+
.view-report-btn.generate-report .vr-mark {
|
|
2581
|
+
font-size: 10px;
|
|
2582
|
+
line-height: 1;
|
|
2583
|
+
}
|
|
2584
|
+
/* Pending state · applied to either the header anchor or the
|
|
2585
|
+
no-brief-card button while the brief pipeline is mid-flight.
|
|
2586
|
+
Reads as "action accepted, working on it" without disabling the
|
|
2587
|
+
surrounding region. */
|
|
2588
|
+
.view-report-btn[data-pending="1"],
|
|
2589
|
+
[data-generate-brief][data-pending="1"] {
|
|
2590
|
+
cursor: progress;
|
|
2591
|
+
opacity: 0.7;
|
|
2592
|
+
pointer-events: none;
|
|
1875
2593
|
}
|
|
1876
2594
|
|
|
1877
2595
|
.adjourned-bar {
|
|
@@ -2052,6 +2770,21 @@
|
|
|
2052
2770
|
text-transform: uppercase;
|
|
2053
2771
|
margin: 0;
|
|
2054
2772
|
}
|
|
2773
|
+
/* Subtle middot separator between meta items (authors · word count).
|
|
2774
|
+
Faint enough that the eye walks the items, not the divider. */
|
|
2775
|
+
.brief-meta-sep {
|
|
2776
|
+
color: var(--text-faint);
|
|
2777
|
+
font-family: var(--mono);
|
|
2778
|
+
font-size: 9px;
|
|
2779
|
+
line-height: 1;
|
|
2780
|
+
opacity: 0.6;
|
|
2781
|
+
}
|
|
2782
|
+
/* Word / character count · same register as `.brief-meta-line` but
|
|
2783
|
+
keep numerals tabular so the figure doesn't jitter while the
|
|
2784
|
+
parent flex-row gets re-rendered (e.g. on tab switch). */
|
|
2785
|
+
.brief-meta-words {
|
|
2786
|
+
font-variant-numeric: tabular-nums;
|
|
2787
|
+
}
|
|
2055
2788
|
|
|
2056
2789
|
/* Signatures · folded into the meta line, no top border */
|
|
2057
2790
|
.brief-signed {
|
|
@@ -2112,44 +2845,54 @@
|
|
|
2112
2845
|
40% { opacity: 1; transform: translateY(-1px); }
|
|
2113
2846
|
}
|
|
2114
2847
|
|
|
2115
|
-
|
|
2848
|
+
/* ── Loading layout · active card + horizontal pip rail ────────
|
|
2849
|
+
Replaces the older 3-row stacked checklist. The active stage
|
|
2850
|
+
gets full visual weight (label + rotating italic sub-line +
|
|
2851
|
+
timing + bottom progress line); the pip rail underneath is the
|
|
2852
|
+
"where am I in the sequence" reference. Container scales from
|
|
2853
|
+
3 to N pips without breaking. */
|
|
2854
|
+
.brief-progress {
|
|
2116
2855
|
display: flex;
|
|
2117
2856
|
flex-direction: column;
|
|
2118
|
-
gap:
|
|
2119
|
-
|
|
2120
|
-
.brief-stage-row {
|
|
2121
|
-
display: grid;
|
|
2122
|
-
grid-template-columns: 16px 1fr;
|
|
2123
|
-
gap: 8px;
|
|
2124
|
-
align-items: start;
|
|
2125
|
-
padding: 6px 0;
|
|
2126
|
-
transition: opacity 0.2s, color 0.2s;
|
|
2857
|
+
gap: 14px;
|
|
2858
|
+
padding: 4px 0;
|
|
2127
2859
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2860
|
+
|
|
2861
|
+
/* Active card · the protagonist row. Hairline frame on top + bottom
|
|
2862
|
+
edges only (no left/right border, per project rule against left
|
|
2863
|
+
callout treatments). The bottom edge is overdrawn by the lime
|
|
2864
|
+
progress line as the stage advances. */
|
|
2865
|
+
.brief-active-card {
|
|
2866
|
+
position: relative;
|
|
2867
|
+
padding: 10px 0 14px;
|
|
2868
|
+
border-top: 1px solid var(--line);
|
|
2869
|
+
border-bottom: 1px solid var(--line);
|
|
2870
|
+
min-height: 76px;
|
|
2133
2871
|
}
|
|
2134
|
-
.brief-
|
|
2872
|
+
.brief-active-head {
|
|
2135
2873
|
display: flex;
|
|
2136
2874
|
align-items: baseline;
|
|
2137
|
-
|
|
2875
|
+
justify-content: space-between;
|
|
2876
|
+
gap: 12px;
|
|
2138
2877
|
flex-wrap: wrap;
|
|
2139
2878
|
}
|
|
2140
|
-
.brief-
|
|
2141
|
-
font-family: var(--
|
|
2142
|
-
font-size:
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
transition: opacity 0.2s ease;
|
|
2879
|
+
.brief-active-label {
|
|
2880
|
+
font-family: var(--font-human);
|
|
2881
|
+
font-size: 14px;
|
|
2882
|
+
font-weight: 600;
|
|
2883
|
+
line-height: 1.4;
|
|
2884
|
+
color: var(--text);
|
|
2885
|
+
letter-spacing: 0.005em;
|
|
2148
2886
|
}
|
|
2149
|
-
.brief-
|
|
2887
|
+
.brief-active-card.brief-active-pending .brief-active-label,
|
|
2888
|
+
.brief-active-card.brief-active-done .brief-active-label {
|
|
2150
2889
|
color: var(--text-soft);
|
|
2890
|
+
font-weight: 500;
|
|
2151
2891
|
}
|
|
2152
|
-
.brief-
|
|
2892
|
+
.brief-active-meta {
|
|
2893
|
+
display: inline-flex;
|
|
2894
|
+
align-items: baseline;
|
|
2895
|
+
gap: 10px;
|
|
2153
2896
|
font-family: var(--mono);
|
|
2154
2897
|
font-size: 9.5px;
|
|
2155
2898
|
letter-spacing: 0.06em;
|
|
@@ -2158,72 +2901,200 @@
|
|
|
2158
2901
|
white-space: nowrap;
|
|
2159
2902
|
margin-left: auto;
|
|
2160
2903
|
}
|
|
2161
|
-
.brief-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
.brief-
|
|
2904
|
+
.brief-active-meta .meta-detail { color: var(--text-soft); }
|
|
2905
|
+
.brief-active-meta .meta-timing { color: var(--lime); }
|
|
2906
|
+
.brief-active-card.brief-active-pending .brief-active-meta .meta-timing,
|
|
2907
|
+
.brief-active-card.brief-active-done .brief-active-meta .meta-timing {
|
|
2165
2908
|
color: var(--text-faint);
|
|
2166
|
-
opacity: 0.55;
|
|
2167
|
-
}
|
|
2168
|
-
.brief-stage-row.brief-stage-active {
|
|
2169
|
-
color: var(--text);
|
|
2170
2909
|
}
|
|
2171
|
-
|
|
2910
|
+
/* Italic-serif rotator under the head · changes every 3s while
|
|
2911
|
+
active. Empty placeholder reserves the line so the layout
|
|
2912
|
+
doesn't jump on the first render before substage populates. */
|
|
2913
|
+
.brief-active-substage {
|
|
2914
|
+
font-family: var(--font-headline, "Charter", "Songti SC", Georgia, serif);
|
|
2915
|
+
font-style: italic;
|
|
2916
|
+
font-size: 13px;
|
|
2917
|
+
line-height: 1.55;
|
|
2172
2918
|
color: var(--text-soft);
|
|
2919
|
+
margin-top: 6px;
|
|
2920
|
+
min-height: 20px;
|
|
2173
2921
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2922
|
+
/* Bottom progress line · overdraws the card's bottom border in
|
|
2923
|
+
lime as elapsed advances. JS sets width via inline style; the
|
|
2924
|
+
CSS transition smooths each tick. */
|
|
2925
|
+
.brief-active-progressline {
|
|
2926
|
+
position: absolute;
|
|
2927
|
+
bottom: -1px;
|
|
2928
|
+
left: 0;
|
|
2929
|
+
height: 1.5px;
|
|
2930
|
+
background: var(--lime);
|
|
2931
|
+
width: 0%;
|
|
2932
|
+
transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1);
|
|
2933
|
+
pointer-events: none;
|
|
2182
2934
|
}
|
|
2183
|
-
.brief-
|
|
2184
|
-
|
|
2185
|
-
font-weight: 700;
|
|
2935
|
+
.brief-active-card.brief-active-pending .brief-active-progressline {
|
|
2936
|
+
background: var(--text-faint);
|
|
2186
2937
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2938
|
+
|
|
2939
|
+
/* Pip rail · horizontal sequence indicator. Each pip = dot + tiny
|
|
2940
|
+
mono caption underneath. Connectors are 1px lines between dots,
|
|
2941
|
+
coloured by the LEFT pip's status so a done→active transition
|
|
2942
|
+
reads as "completed crossing into the present." */
|
|
2943
|
+
.brief-pip-rail {
|
|
2944
|
+
display: flex;
|
|
2945
|
+
align-items: flex-start;
|
|
2946
|
+
padding: 0 2px;
|
|
2189
2947
|
}
|
|
2190
|
-
.brief-
|
|
2191
|
-
|
|
2948
|
+
.brief-pip {
|
|
2949
|
+
display: flex;
|
|
2950
|
+
flex-direction: column;
|
|
2951
|
+
align-items: center;
|
|
2952
|
+
gap: 6px;
|
|
2953
|
+
flex: 0 0 auto;
|
|
2954
|
+
min-width: 0;
|
|
2192
2955
|
}
|
|
2193
|
-
.brief-
|
|
2194
|
-
width:
|
|
2956
|
+
.brief-pip-dot {
|
|
2957
|
+
width: 10px;
|
|
2958
|
+
height: 10px;
|
|
2195
2959
|
border-radius: 50%;
|
|
2960
|
+
border: 1px solid var(--line-bright);
|
|
2961
|
+
background: var(--bg);
|
|
2962
|
+
position: relative;
|
|
2963
|
+
transition: background 0.4s ease, border-color 0.4s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
2964
|
+
}
|
|
2965
|
+
.brief-pip.is-done .brief-pip-dot {
|
|
2196
2966
|
background: var(--lime);
|
|
2197
|
-
|
|
2198
|
-
animation: brief-stage-pulse 1.4s ease-out infinite;
|
|
2967
|
+
border-color: var(--lime);
|
|
2199
2968
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2969
|
+
.brief-pip.is-done .brief-pip-dot::after {
|
|
2970
|
+
content: "";
|
|
2971
|
+
position: absolute;
|
|
2972
|
+
inset: 2px;
|
|
2973
|
+
border-radius: 50%;
|
|
2974
|
+
background: var(--bg);
|
|
2204
2975
|
}
|
|
2205
|
-
.brief-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
font-family: var(--font-human);
|
|
2210
|
-
font-size: 12.5px;
|
|
2211
|
-
line-height: 1.35;
|
|
2976
|
+
.brief-pip.is-active .brief-pip-dot {
|
|
2977
|
+
background: var(--lime);
|
|
2978
|
+
border-color: var(--lime);
|
|
2979
|
+
animation: brief-pip-pulse 1.4s ease-out infinite;
|
|
2212
2980
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2981
|
+
@keyframes brief-pip-pulse {
|
|
2982
|
+
0% { box-shadow: 0 0 0 0 rgba(124, 184, 88, 0.55); }
|
|
2983
|
+
70% { box-shadow: 0 0 0 7px rgba(124, 184, 88, 0); }
|
|
2984
|
+
100% { box-shadow: 0 0 0 0 rgba(124, 184, 88, 0); }
|
|
2215
2985
|
}
|
|
2216
|
-
.brief-
|
|
2986
|
+
.brief-pip-label {
|
|
2217
2987
|
font-family: var(--mono);
|
|
2218
|
-
font-size:
|
|
2219
|
-
letter-spacing: 0.
|
|
2988
|
+
font-size: 9px;
|
|
2989
|
+
letter-spacing: 0.1em;
|
|
2220
2990
|
text-transform: uppercase;
|
|
2221
2991
|
color: var(--text-faint);
|
|
2222
2992
|
white-space: nowrap;
|
|
2993
|
+
line-height: 1;
|
|
2994
|
+
transition: color 0.3s ease;
|
|
2223
2995
|
}
|
|
2224
|
-
.brief-
|
|
2225
|
-
|
|
2996
|
+
.brief-pip.is-active .brief-pip-label { color: var(--lime); font-weight: 700; }
|
|
2997
|
+
.brief-pip.is-done .brief-pip-label { color: var(--text-soft); }
|
|
2998
|
+
|
|
2999
|
+
/* Connector · stretches to fill remaining horizontal space between
|
|
3000
|
+
two pip columns, sits at the dot's vertical center (5px from top
|
|
3001
|
+
of the column). Done connectors are lime; pending stay neutral. */
|
|
3002
|
+
.brief-pip-line {
|
|
3003
|
+
flex: 1 1 0;
|
|
3004
|
+
height: 1px;
|
|
3005
|
+
background: var(--line-bright);
|
|
3006
|
+
margin: 5px 6px 0;
|
|
3007
|
+
align-self: flex-start;
|
|
3008
|
+
transition: background 0.4s ease;
|
|
3009
|
+
}
|
|
3010
|
+
.brief-pip-line.is-done { background: var(--lime); }
|
|
3011
|
+
|
|
3012
|
+
/* One-shot "settle" spring when a pip first transitions to done.
|
|
3013
|
+
The is-fresh-done class is applied by renderBriefStages ONLY on
|
|
3014
|
+
the first render where the pip is done (subsequent re-renders
|
|
3015
|
+
check the per-brief seenDone Set and skip the class). The brief
|
|
3016
|
+
moment of motion reads as a chip falling into place — not a
|
|
3017
|
+
particle, not a flash, just a small spring on the dot. */
|
|
3018
|
+
.brief-pip.is-fresh-done .brief-pip-dot {
|
|
3019
|
+
animation: brief-pip-settle 360ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
3020
|
+
}
|
|
3021
|
+
@keyframes brief-pip-settle {
|
|
3022
|
+
0% { transform: scale(0.6); }
|
|
3023
|
+
55% { transform: scale(1.18); }
|
|
3024
|
+
100% { transform: scale(1.0); }
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
/* Stats row · accumulates harvested numbers + director chips below
|
|
3028
|
+
the pip rail. Empty by default — the renderer hides the wrapper
|
|
3029
|
+
entirely when no fragments are present so the layout doesn't show
|
|
3030
|
+
a phantom border on first render. */
|
|
3031
|
+
.brief-stats-row {
|
|
3032
|
+
display: flex;
|
|
3033
|
+
flex-wrap: wrap;
|
|
3034
|
+
gap: 6px 10px;
|
|
3035
|
+
align-items: center;
|
|
3036
|
+
padding-top: 10px;
|
|
3037
|
+
border-top: 1px solid var(--line);
|
|
3038
|
+
font-family: var(--mono);
|
|
3039
|
+
font-size: 9.5px;
|
|
3040
|
+
letter-spacing: 0.04em;
|
|
3041
|
+
color: var(--text-faint);
|
|
2226
3042
|
}
|
|
3043
|
+
.brief-stat-roster {
|
|
3044
|
+
display: inline-flex;
|
|
3045
|
+
flex-wrap: wrap;
|
|
3046
|
+
gap: 4px;
|
|
3047
|
+
align-items: center;
|
|
3048
|
+
}
|
|
3049
|
+
.brief-stat-chip {
|
|
3050
|
+
display: inline-flex;
|
|
3051
|
+
align-items: center;
|
|
3052
|
+
gap: 5px;
|
|
3053
|
+
padding: 2px 8px;
|
|
3054
|
+
font-family: var(--mono);
|
|
3055
|
+
font-size: 9.5px;
|
|
3056
|
+
letter-spacing: 0.04em;
|
|
3057
|
+
color: var(--text-soft);
|
|
3058
|
+
background: transparent;
|
|
3059
|
+
border: 1px solid var(--line-bright);
|
|
3060
|
+
border-radius: 9px;
|
|
3061
|
+
line-height: 1.45;
|
|
3062
|
+
white-space: nowrap;
|
|
3063
|
+
}
|
|
3064
|
+
/* Top-kind tag · italic afterthought next to the count. The
|
|
3065
|
+
renderer only emits this when the dominant kind has ≥ 2 entries,
|
|
3066
|
+
keeping the chip uncluttered on the long tail. */
|
|
3067
|
+
.brief-stat-chip-tag {
|
|
3068
|
+
font-style: italic;
|
|
3069
|
+
color: var(--text-faint);
|
|
3070
|
+
letter-spacing: 0.02em;
|
|
3071
|
+
}
|
|
3072
|
+
/* Entrance animation runs ONLY on the render where the chip first
|
|
3073
|
+
appears · the renderer applies `.is-fresh` only when the
|
|
3074
|
+
director's id isn't yet in `_briefSeenHarvestKeys[briefId]`, then
|
|
3075
|
+
drops it on subsequent ticks. Without this gate, every 1-second
|
|
3076
|
+
re-render would re-trigger the keyframe and the chips would fade
|
|
3077
|
+
in over and over (the "flickering" bug). Stagger gives the row a
|
|
3078
|
+
roll-call feel rather than a single dump. */
|
|
3079
|
+
.brief-stat-chip.is-fresh {
|
|
3080
|
+
animation: brief-stat-chip-in 280ms ease-out both;
|
|
3081
|
+
}
|
|
3082
|
+
.brief-stat-chip.is-fresh:nth-child(1) { animation-delay: 0ms; }
|
|
3083
|
+
.brief-stat-chip.is-fresh:nth-child(2) { animation-delay: 60ms; }
|
|
3084
|
+
.brief-stat-chip.is-fresh:nth-child(3) { animation-delay: 120ms; }
|
|
3085
|
+
.brief-stat-chip.is-fresh:nth-child(4) { animation-delay: 180ms; }
|
|
3086
|
+
.brief-stat-chip.is-fresh:nth-child(5) { animation-delay: 240ms; }
|
|
3087
|
+
.brief-stat-chip.is-fresh:nth-child(6) { animation-delay: 300ms; }
|
|
3088
|
+
@keyframes brief-stat-chip-in {
|
|
3089
|
+
0% { opacity: 0; transform: translateY(2px); }
|
|
3090
|
+
100% { opacity: 1; transform: translateY(0); }
|
|
3091
|
+
}
|
|
3092
|
+
.brief-stat-fact {
|
|
3093
|
+
color: var(--text-soft);
|
|
3094
|
+
text-transform: uppercase;
|
|
3095
|
+
letter-spacing: 0.06em;
|
|
3096
|
+
}
|
|
3097
|
+
.brief-stat-fact.brief-stat-live { color: var(--lime); }
|
|
2227
3098
|
|
|
2228
3099
|
/* Brief error / interrupted state · retry button matches the
|
|
2229
3100
|
supplement-row treatment but uses red accent for "something went
|
|
@@ -2261,6 +3132,72 @@
|
|
|
2261
3132
|
line-height: 1;
|
|
2262
3133
|
}
|
|
2263
3134
|
|
|
3135
|
+
/* Salvage-path retry banner · shown when a regeneration fails but a
|
|
3136
|
+
prior successful brief is still available. The banner sits AT the
|
|
3137
|
+
top of the brief area (in flow, not overlay) so the existing
|
|
3138
|
+
report below stays fully visible. Keeps the retry affordance
|
|
3139
|
+
within reach without burying the user's last good output. */
|
|
3140
|
+
.brief-retry-banner {
|
|
3141
|
+
display: flex;
|
|
3142
|
+
align-items: center;
|
|
3143
|
+
gap: 10px;
|
|
3144
|
+
padding: 8px 12px;
|
|
3145
|
+
margin: 0 0 14px;
|
|
3146
|
+
background: color-mix(in srgb, var(--red) 6%, var(--panel-2));
|
|
3147
|
+
border: 0.5px solid color-mix(in srgb, var(--red) 40%, var(--line-bright));
|
|
3148
|
+
font-family: var(--mono);
|
|
3149
|
+
font-size: 11px;
|
|
3150
|
+
color: var(--text);
|
|
3151
|
+
}
|
|
3152
|
+
.brief-retry-banner .brb-mark {
|
|
3153
|
+
color: var(--red);
|
|
3154
|
+
font-size: 12px;
|
|
3155
|
+
line-height: 1;
|
|
3156
|
+
}
|
|
3157
|
+
.brief-retry-banner .brb-text {
|
|
3158
|
+
flex: 1;
|
|
3159
|
+
min-width: 0;
|
|
3160
|
+
color: var(--text-soft);
|
|
3161
|
+
letter-spacing: 0.02em;
|
|
3162
|
+
overflow: hidden;
|
|
3163
|
+
text-overflow: ellipsis;
|
|
3164
|
+
white-space: nowrap;
|
|
3165
|
+
}
|
|
3166
|
+
.brief-retry-banner .brb-retry {
|
|
3167
|
+
display: inline-flex;
|
|
3168
|
+
align-items: center;
|
|
3169
|
+
gap: 5px;
|
|
3170
|
+
padding: 4px 10px;
|
|
3171
|
+
background: transparent;
|
|
3172
|
+
border: 0.5px solid var(--lime);
|
|
3173
|
+
color: var(--lime);
|
|
3174
|
+
cursor: pointer;
|
|
3175
|
+
font-family: var(--mono);
|
|
3176
|
+
font-size: 9.5px;
|
|
3177
|
+
font-weight: 700;
|
|
3178
|
+
letter-spacing: 0.12em;
|
|
3179
|
+
text-transform: uppercase;
|
|
3180
|
+
transition: background 0.12s, color 0.12s;
|
|
3181
|
+
}
|
|
3182
|
+
.brief-retry-banner .brb-retry:hover {
|
|
3183
|
+
background: var(--lime);
|
|
3184
|
+
color: var(--bg);
|
|
3185
|
+
}
|
|
3186
|
+
.brief-retry-banner .brb-retry-mark {
|
|
3187
|
+
font-size: 11px;
|
|
3188
|
+
line-height: 1;
|
|
3189
|
+
}
|
|
3190
|
+
.brief-retry-banner .brb-dismiss {
|
|
3191
|
+
background: transparent;
|
|
3192
|
+
border: none;
|
|
3193
|
+
color: var(--text-faint);
|
|
3194
|
+
cursor: pointer;
|
|
3195
|
+
font-size: 13px;
|
|
3196
|
+
line-height: 1;
|
|
3197
|
+
padding: 2px 4px;
|
|
3198
|
+
}
|
|
3199
|
+
.brief-retry-banner .brb-dismiss:hover { color: var(--red); }
|
|
3200
|
+
|
|
2264
3201
|
/* Brief version tabs · scrollable horizontal strip at the top of
|
|
2265
3202
|
the brief card when ≥ 2 briefs have been filed for this room.
|
|
2266
3203
|
Each tab shows a mono number + a short label (Initial / supplement
|
|
@@ -2348,6 +3285,25 @@
|
|
|
2348
3285
|
.brief-version-tab.active .brief-version-label {
|
|
2349
3286
|
color: var(--text);
|
|
2350
3287
|
}
|
|
3288
|
+
/* Failure marker · small "!" glyph next to the version number when
|
|
3289
|
+
a brief is errored / interrupted / timed-out. Lets the user spot
|
|
3290
|
+
which tab needs retrying without entering it. The full error UI
|
|
3291
|
+
surfaces once the tab is clicked (bypassSalvage path). */
|
|
3292
|
+
.brief-version-state {
|
|
3293
|
+
display: inline-flex;
|
|
3294
|
+
align-items: center;
|
|
3295
|
+
justify-content: center;
|
|
3296
|
+
width: 12px;
|
|
3297
|
+
height: 12px;
|
|
3298
|
+
margin-left: 4px;
|
|
3299
|
+
border-radius: 50%;
|
|
3300
|
+
background: var(--red);
|
|
3301
|
+
color: var(--bg);
|
|
3302
|
+
font-family: var(--mono);
|
|
3303
|
+
font-size: 8.5px;
|
|
3304
|
+
font-weight: 700;
|
|
3305
|
+
line-height: 1;
|
|
3306
|
+
}
|
|
2351
3307
|
|
|
2352
3308
|
/* Add-perspective + delete row · sits at the bottom of the brief
|
|
2353
3309
|
card with a hairline divider above. The supplement button leads
|
|
@@ -2461,94 +3417,575 @@
|
|
|
2461
3417
|
padding: 16px 22px 14px;
|
|
2462
3418
|
border-bottom: 0.5px solid var(--line);
|
|
2463
3419
|
}
|
|
2464
|
-
.supplement-head .meta {
|
|
3420
|
+
.supplement-head .meta {
|
|
3421
|
+
font-family: var(--mono);
|
|
3422
|
+
font-size: 10px;
|
|
3423
|
+
letter-spacing: 0.06em;
|
|
3424
|
+
color: var(--text-faint);
|
|
3425
|
+
text-transform: uppercase;
|
|
3426
|
+
margin-bottom: 6px;
|
|
3427
|
+
}
|
|
3428
|
+
.supplement-head .meta span { color: var(--text-soft); text-transform: none; letter-spacing: 0; }
|
|
3429
|
+
.supplement-head .title {
|
|
3430
|
+
font-family: var(--font-human);
|
|
3431
|
+
font-size: 18px;
|
|
3432
|
+
font-weight: 700;
|
|
3433
|
+
color: var(--text);
|
|
3434
|
+
letter-spacing: -0.01em;
|
|
3435
|
+
line-height: 1.25;
|
|
3436
|
+
}
|
|
3437
|
+
.supplement-close {
|
|
3438
|
+
background: transparent;
|
|
3439
|
+
border: 0.5px solid var(--line-bright);
|
|
3440
|
+
color: var(--text-soft);
|
|
3441
|
+
width: 24px; height: 24px;
|
|
3442
|
+
cursor: pointer;
|
|
3443
|
+
transition: color 0.12s, border-color 0.12s;
|
|
3444
|
+
font-size: 11px;
|
|
3445
|
+
line-height: 1;
|
|
3446
|
+
}
|
|
3447
|
+
.supplement-close:hover { color: var(--lime); border-color: var(--lime); }
|
|
3448
|
+
.supplement-body {
|
|
3449
|
+
padding: 16px 22px 12px;
|
|
3450
|
+
}
|
|
3451
|
+
.supplement-input {
|
|
3452
|
+
display: block;
|
|
3453
|
+
width: 100%;
|
|
3454
|
+
background: var(--bg);
|
|
3455
|
+
border: 0.5px solid var(--line-strong);
|
|
3456
|
+
color: var(--text);
|
|
3457
|
+
font-family: var(--font-human);
|
|
3458
|
+
font-size: 13.5px;
|
|
3459
|
+
line-height: 1.55;
|
|
3460
|
+
padding: 12px 14px;
|
|
3461
|
+
resize: vertical;
|
|
3462
|
+
min-height: 132px;
|
|
3463
|
+
transition: border-color 0.12s;
|
|
3464
|
+
}
|
|
3465
|
+
.supplement-input:focus { outline: none; border-color: var(--lime); }
|
|
3466
|
+
.supplement-input::placeholder {
|
|
3467
|
+
color: var(--text-faint);
|
|
3468
|
+
white-space: pre-wrap;
|
|
3469
|
+
}
|
|
3470
|
+
.supplement-hint {
|
|
3471
|
+
margin: 10px 0 0;
|
|
3472
|
+
font-size: 11.5px;
|
|
3473
|
+
line-height: 1.5;
|
|
3474
|
+
color: var(--text-faint);
|
|
3475
|
+
}
|
|
3476
|
+
.supplement-foot {
|
|
3477
|
+
display: flex;
|
|
3478
|
+
justify-content: flex-end;
|
|
3479
|
+
gap: 10px;
|
|
3480
|
+
padding: 12px 22px 16px;
|
|
3481
|
+
border-top: 0.5px solid var(--line);
|
|
3482
|
+
}
|
|
3483
|
+
.supplement-cancel,
|
|
3484
|
+
.supplement-confirm {
|
|
3485
|
+
font-family: var(--mono);
|
|
3486
|
+
font-size: 10.5px;
|
|
3487
|
+
font-weight: 700;
|
|
3488
|
+
letter-spacing: 0.14em;
|
|
3489
|
+
text-transform: uppercase;
|
|
3490
|
+
padding: 8px 14px;
|
|
3491
|
+
cursor: pointer;
|
|
3492
|
+
background: transparent;
|
|
3493
|
+
border: 0.5px solid var(--line-strong);
|
|
3494
|
+
color: var(--text-soft);
|
|
3495
|
+
transition: border-color 0.1s, color 0.1s, background 0.1s;
|
|
3496
|
+
}
|
|
3497
|
+
.supplement-cancel:hover { border-color: var(--text-soft); color: var(--text); }
|
|
3498
|
+
.supplement-confirm {
|
|
3499
|
+
background: var(--lime);
|
|
3500
|
+
border-color: var(--lime);
|
|
3501
|
+
color: var(--bg);
|
|
3502
|
+
}
|
|
3503
|
+
.supplement-confirm:hover { background: transparent; color: var(--lime); }
|
|
3504
|
+
.supplement-confirm[disabled] {
|
|
3505
|
+
opacity: 0.55;
|
|
3506
|
+
pointer-events: none;
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
/* ─── Follow-up overlay · widens the supplement modal slightly so
|
|
3510
|
+
the multi-field form (subject + cast + tone/intensity) breathes,
|
|
3511
|
+
and adds shape for the parent reference card + form fields.
|
|
3512
|
+
Inherits supplement-overlay's classification / head / footer
|
|
3513
|
+
chrome — only the inside form differs. */
|
|
3514
|
+
.supplement-modal.followup-modal {
|
|
3515
|
+
max-width: 640px;
|
|
3516
|
+
}
|
|
3517
|
+
.followup-parent-card {
|
|
3518
|
+
background: var(--panel-2);
|
|
3519
|
+
border: 0.5px solid var(--line-bright);
|
|
3520
|
+
padding: 10px 14px;
|
|
3521
|
+
margin-bottom: 18px;
|
|
3522
|
+
font-family: var(--mono);
|
|
3523
|
+
}
|
|
3524
|
+
.followup-parent-subject {
|
|
3525
|
+
font-size: 13px;
|
|
3526
|
+
font-weight: 600;
|
|
3527
|
+
color: var(--text);
|
|
3528
|
+
line-height: 1.4;
|
|
3529
|
+
margin-bottom: 4px;
|
|
3530
|
+
}
|
|
3531
|
+
.followup-parent-meta {
|
|
3532
|
+
font-size: 10px;
|
|
3533
|
+
color: var(--text-soft);
|
|
3534
|
+
letter-spacing: 0.06em;
|
|
3535
|
+
text-transform: uppercase;
|
|
3536
|
+
}
|
|
3537
|
+
/* Context note · explains to the user that this room's content
|
|
3538
|
+
will be packaged as the follow-up's prior context. Sits inside
|
|
3539
|
+
the parent reference card with a hairline separator above it
|
|
3540
|
+
so it reads as a footnote on the card, not a separate block. */
|
|
3541
|
+
.followup-parent-note {
|
|
3542
|
+
margin-top: 8px;
|
|
3543
|
+
padding-top: 8px;
|
|
3544
|
+
border-top: 0.5px dashed var(--line-bright);
|
|
3545
|
+
font-family: var(--font-human);
|
|
3546
|
+
font-size: 11.5px;
|
|
3547
|
+
line-height: 1.45;
|
|
3548
|
+
color: var(--text-soft);
|
|
3549
|
+
letter-spacing: -0.003em;
|
|
3550
|
+
text-transform: none;
|
|
3551
|
+
}
|
|
3552
|
+
.followup-field {
|
|
3553
|
+
display: flex;
|
|
3554
|
+
flex-direction: column;
|
|
3555
|
+
margin-bottom: 14px;
|
|
3556
|
+
}
|
|
3557
|
+
.followup-field-label {
|
|
3558
|
+
display: flex;
|
|
3559
|
+
align-items: baseline;
|
|
3560
|
+
gap: 8px;
|
|
3561
|
+
font-family: var(--mono);
|
|
3562
|
+
font-size: 10px;
|
|
3563
|
+
font-weight: 700;
|
|
3564
|
+
letter-spacing: 0.16em;
|
|
3565
|
+
text-transform: uppercase;
|
|
3566
|
+
color: var(--text-soft);
|
|
3567
|
+
margin-bottom: 6px;
|
|
3568
|
+
}
|
|
3569
|
+
.followup-field-hint {
|
|
3570
|
+
font-family: var(--mono);
|
|
3571
|
+
font-size: 9px;
|
|
3572
|
+
font-weight: 400;
|
|
3573
|
+
letter-spacing: 0.08em;
|
|
3574
|
+
color: var(--text-faint);
|
|
3575
|
+
text-transform: uppercase;
|
|
3576
|
+
}
|
|
3577
|
+
/* Cast / tone / intensity row · cast button + tune dropdowns on
|
|
3578
|
+
ONE line, mirroring the new-room composer's toolbar layout
|
|
3579
|
+
(cast left, separator, tune dropdowns) but in a form-row
|
|
3580
|
+
register. The wrapper is `.cmp-tune` so the inline-flex + gap +
|
|
3581
|
+
wrap behaviour is inherited as-is. */
|
|
3582
|
+
.followup-cast-row {
|
|
3583
|
+
align-items: stretch;
|
|
3584
|
+
}
|
|
3585
|
+
.followup-cast-btn {
|
|
3586
|
+
/* Hairline visible at rest in the overlay (vs the toolbar
|
|
3587
|
+
variant which is borderless until hover). Makes the picker
|
|
3588
|
+
obviously interactive in the form context. */
|
|
3589
|
+
border-color: var(--line-bright);
|
|
3590
|
+
}
|
|
3591
|
+
.followup-cast-btn[disabled] {
|
|
3592
|
+
cursor: not-allowed;
|
|
3593
|
+
opacity: 0.7;
|
|
3594
|
+
border-style: dashed;
|
|
3595
|
+
}
|
|
3596
|
+
.followup-cast-btn[data-cast-mode="same-as-last"] {
|
|
3597
|
+
border-color: var(--lime-dim);
|
|
3598
|
+
background: var(--panel-2);
|
|
3599
|
+
}
|
|
3600
|
+
/* Visual separator between cast button and the tune group ·
|
|
3601
|
+
mirrors `.cmp-toolbar-sep` from the inline composer toolbar. */
|
|
3602
|
+
.followup-cast-row-sep {
|
|
3603
|
+
display: inline-block;
|
|
3604
|
+
width: 0.5px;
|
|
3605
|
+
align-self: stretch;
|
|
3606
|
+
background: var(--line-bright);
|
|
3607
|
+
margin: 0 4px;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
/* "Same cast as last session" checkbox · sits below the cast
|
|
3611
|
+
button. Native checkbox restyled to match the radio chrome
|
|
3612
|
+
used elsewhere in this overlay. */
|
|
3613
|
+
.followup-checkbox {
|
|
3614
|
+
display: flex;
|
|
3615
|
+
align-items: center;
|
|
3616
|
+
gap: 8px;
|
|
3617
|
+
padding: 8px 0 0;
|
|
3618
|
+
font-family: var(--mono);
|
|
3619
|
+
font-size: 12px;
|
|
3620
|
+
color: var(--text-soft);
|
|
3621
|
+
cursor: pointer;
|
|
3622
|
+
user-select: none;
|
|
3623
|
+
}
|
|
3624
|
+
.followup-checkbox input[type="checkbox"] {
|
|
3625
|
+
appearance: none;
|
|
3626
|
+
-webkit-appearance: none;
|
|
3627
|
+
width: 12px; height: 12px;
|
|
3628
|
+
border: 0.5px solid var(--line-strong);
|
|
3629
|
+
background: var(--bg);
|
|
3630
|
+
cursor: pointer;
|
|
3631
|
+
flex-shrink: 0;
|
|
3632
|
+
transition: border-color 0.1s, background 0.1s;
|
|
3633
|
+
position: relative;
|
|
3634
|
+
}
|
|
3635
|
+
.followup-checkbox input[type="checkbox"]:hover { border-color: var(--text-soft); }
|
|
3636
|
+
.followup-checkbox input[type="checkbox"]:checked {
|
|
3637
|
+
background: var(--lime);
|
|
3638
|
+
border-color: var(--lime);
|
|
3639
|
+
}
|
|
3640
|
+
.followup-checkbox input[type="checkbox"]:checked::after {
|
|
3641
|
+
content: "";
|
|
3642
|
+
position: absolute;
|
|
3643
|
+
inset: 2px;
|
|
3644
|
+
background: var(--bg);
|
|
3645
|
+
clip-path: polygon(14% 50%, 0 65%, 38% 100%, 100% 30%, 86% 16%, 38% 70%);
|
|
3646
|
+
}
|
|
3647
|
+
.followup-checkbox input[type="checkbox"]:disabled {
|
|
3648
|
+
opacity: 0.5;
|
|
3649
|
+
cursor: not-allowed;
|
|
3650
|
+
}
|
|
3651
|
+
.followup-checkbox:has(input:checked) { color: var(--text); }
|
|
3652
|
+
|
|
3653
|
+
/* ─── Follow-up tree · parent banner + child list ─────────────────
|
|
3654
|
+
parent banner sits at the top of the chat in a follow-up room
|
|
3655
|
+
(under the room head, before the first message); child list
|
|
3656
|
+
renders below the brief card on a parent room that's spawned
|
|
3657
|
+
follow-ups. Both use the hairline + mono-tag visual register. */
|
|
3658
|
+
.followup-parent-banner {
|
|
3659
|
+
/* Width-match the chat messages cap (960px, centred) so the
|
|
3660
|
+
banner sits flush with the column underneath instead of
|
|
3661
|
+
spanning the full chat scroller on wide monitors. Without
|
|
3662
|
+
this, the banner reads as a viewport-wide alert bar — out
|
|
3663
|
+
of register with everything else. */
|
|
3664
|
+
max-width: 960px;
|
|
3665
|
+
margin: 0 auto 14px;
|
|
3666
|
+
padding: 8px 14px;
|
|
3667
|
+
background: var(--panel-2);
|
|
3668
|
+
border: 0.5px solid var(--line-bright);
|
|
3669
|
+
display: flex;
|
|
3670
|
+
align-items: center;
|
|
3671
|
+
gap: 10px;
|
|
3672
|
+
cursor: pointer;
|
|
3673
|
+
text-decoration: none;
|
|
3674
|
+
font-family: var(--mono);
|
|
3675
|
+
transition: border-color 0.1s, background 0.1s;
|
|
3676
|
+
}
|
|
3677
|
+
.followup-parent-banner:hover {
|
|
3678
|
+
border-color: var(--lime-dim);
|
|
3679
|
+
background: var(--panel-3);
|
|
3680
|
+
}
|
|
3681
|
+
.followup-parent-banner .label {
|
|
3682
|
+
font-size: 9.5px;
|
|
3683
|
+
color: var(--text-soft);
|
|
3684
|
+
letter-spacing: 0.14em;
|
|
3685
|
+
text-transform: uppercase;
|
|
3686
|
+
flex-shrink: 0;
|
|
3687
|
+
}
|
|
3688
|
+
.followup-parent-banner .room-num {
|
|
3689
|
+
font-size: 11px;
|
|
3690
|
+
color: var(--lime);
|
|
3691
|
+
font-weight: 700;
|
|
3692
|
+
flex-shrink: 0;
|
|
3693
|
+
}
|
|
3694
|
+
.followup-parent-banner .subject {
|
|
3695
|
+
font-size: 12px;
|
|
3696
|
+
color: var(--text);
|
|
3697
|
+
flex: 1;
|
|
3698
|
+
min-width: 0;
|
|
3699
|
+
white-space: nowrap;
|
|
3700
|
+
overflow: hidden;
|
|
3701
|
+
text-overflow: ellipsis;
|
|
3702
|
+
}
|
|
3703
|
+
.followup-parent-banner .arrow {
|
|
3704
|
+
font-size: 11px;
|
|
3705
|
+
color: var(--text-dim);
|
|
3706
|
+
flex-shrink: 0;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
.followup-children {
|
|
3710
|
+
/* Width-match the brief card (.ending-block: max-width 760px,
|
|
3711
|
+
margin auto). The follow-ups list slots in directly below the
|
|
3712
|
+
brief, so they need to share the same gutter — otherwise the
|
|
3713
|
+
follow-ups span full chat width and the alignment breaks. */
|
|
3714
|
+
max-width: 760px;
|
|
3715
|
+
margin: 24px auto 0;
|
|
3716
|
+
padding: 14px 16px;
|
|
3717
|
+
background: var(--panel-2);
|
|
3718
|
+
border: 0.5px solid var(--line);
|
|
3719
|
+
font-family: var(--mono);
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
/* ─── Session analytics card · post-adjourn summary ───
|
|
3723
|
+
Sits below the brief (and any follow-ups) when the room is
|
|
3724
|
+
adjourned. Editorial mood: panel-2 surface, lime banner kicker,
|
|
3725
|
+
hero token-count headline, model-usage stacked bar tinted by
|
|
3726
|
+
provider, "what you valued" highlights at the bottom. Width
|
|
3727
|
+
matches the brief / follow-ups column (760px max, centred). */
|
|
3728
|
+
/* Compact post-adjourn analytics tile · earlier iteration was 22px
|
|
3729
|
+
padding + 22px section gaps + 32px hero numerals, which read as
|
|
3730
|
+
"celebratory dashboard" rather than "summary stripe". Tightened
|
|
3731
|
+
across the board so the tile reads as a dense info row sitting
|
|
3732
|
+
above the brief, not as its own visual event. Hero numerals
|
|
3733
|
+
dropped to 21px, section gaps to 10px, banner padding to 5px,
|
|
3734
|
+
sub-tile padding cut by ~30%. */
|
|
3735
|
+
.session-analytics {
|
|
3736
|
+
max-width: 760px;
|
|
3737
|
+
margin: 18px auto 10px;
|
|
3738
|
+
background: var(--panel-2);
|
|
3739
|
+
border: 0.5px solid var(--line-bright);
|
|
3740
|
+
}
|
|
3741
|
+
.sa-banner {
|
|
3742
|
+
padding: 5px 14px;
|
|
3743
|
+
display: flex;
|
|
3744
|
+
justify-content: space-between;
|
|
3745
|
+
align-items: center;
|
|
3746
|
+
gap: 12px;
|
|
3747
|
+
font-family: var(--mono);
|
|
3748
|
+
border-bottom: 0.5px solid var(--line);
|
|
3749
|
+
background: var(--panel-3);
|
|
3750
|
+
}
|
|
3751
|
+
.sa-banner-tag {
|
|
3752
|
+
font-size: 9.5px;
|
|
3753
|
+
font-weight: 700;
|
|
3754
|
+
letter-spacing: 0.18em;
|
|
3755
|
+
text-transform: uppercase;
|
|
3756
|
+
color: var(--lime);
|
|
3757
|
+
}
|
|
3758
|
+
.sa-banner-stamp {
|
|
3759
|
+
font-size: 9px;
|
|
3760
|
+
letter-spacing: 0.18em;
|
|
3761
|
+
text-transform: uppercase;
|
|
3762
|
+
color: var(--text-faint);
|
|
3763
|
+
}
|
|
3764
|
+
.sa-body {
|
|
3765
|
+
padding: 12px 14px 14px;
|
|
3766
|
+
display: flex;
|
|
3767
|
+
flex-direction: column;
|
|
3768
|
+
gap: 12px;
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
/* Headline metric grid · 4 cells. Hero (tokens) is the only one
|
|
3772
|
+
in accent colour; the others read as siblings at the same size.
|
|
3773
|
+
Compact register · all values 17px, hero 21px, label 9px. */
|
|
3774
|
+
.sa-headline {
|
|
3775
|
+
display: grid;
|
|
3776
|
+
grid-template-columns: 1.4fr 1fr 1fr 1fr;
|
|
3777
|
+
gap: 10px;
|
|
3778
|
+
align-items: end;
|
|
3779
|
+
}
|
|
3780
|
+
@media (max-width: 600px) {
|
|
3781
|
+
.sa-headline { grid-template-columns: 1fr 1fr; }
|
|
3782
|
+
}
|
|
3783
|
+
.sa-metric {
|
|
3784
|
+
display: flex;
|
|
3785
|
+
flex-direction: column;
|
|
3786
|
+
gap: 2px;
|
|
3787
|
+
min-width: 0;
|
|
3788
|
+
}
|
|
3789
|
+
.sa-metric-value {
|
|
3790
|
+
font-family: "SF Mono", "JetBrains Mono", "Menlo", monospace;
|
|
3791
|
+
font-size: 17px;
|
|
3792
|
+
font-weight: 700;
|
|
3793
|
+
color: var(--text);
|
|
3794
|
+
letter-spacing: -0.01em;
|
|
3795
|
+
font-variant-numeric: tabular-nums;
|
|
3796
|
+
line-height: 1;
|
|
3797
|
+
}
|
|
3798
|
+
.sa-metric-hero .sa-metric-value {
|
|
3799
|
+
font-size: 21px;
|
|
3800
|
+
color: var(--lime);
|
|
3801
|
+
}
|
|
3802
|
+
.sa-metric-label {
|
|
3803
|
+
font-family: var(--mono);
|
|
3804
|
+
font-size: 9px;
|
|
3805
|
+
letter-spacing: 0.14em;
|
|
3806
|
+
text-transform: uppercase;
|
|
3807
|
+
color: var(--text-faint);
|
|
3808
|
+
font-weight: 700;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
/* Model breakdown · stacked bar + legend. Section head sits
|
|
3812
|
+
inline · 9px mono kicker, 6px gap to the bar, 6px height bar
|
|
3813
|
+
(down from 8). Legend rows lose their padding so they read as
|
|
3814
|
+
a dense column not a list. */
|
|
3815
|
+
.sa-section { display: flex; flex-direction: column; gap: 6px; }
|
|
3816
|
+
.sa-section-head {
|
|
3817
|
+
font-family: var(--mono);
|
|
3818
|
+
font-size: 9px;
|
|
3819
|
+
letter-spacing: 0.18em;
|
|
3820
|
+
text-transform: uppercase;
|
|
3821
|
+
font-weight: 700;
|
|
3822
|
+
color: var(--text-soft);
|
|
3823
|
+
}
|
|
3824
|
+
.sa-bar {
|
|
3825
|
+
display: flex;
|
|
3826
|
+
height: 6px;
|
|
3827
|
+
overflow: hidden;
|
|
3828
|
+
background: var(--bg);
|
|
3829
|
+
border: 0.5px solid var(--line);
|
|
3830
|
+
}
|
|
3831
|
+
.sa-bar-seg {
|
|
3832
|
+
height: 100%;
|
|
3833
|
+
transition: opacity 0.15s;
|
|
3834
|
+
}
|
|
3835
|
+
.sa-bar-seg:hover { opacity: 0.85; }
|
|
3836
|
+
.sa-legend {
|
|
3837
|
+
list-style: none;
|
|
3838
|
+
margin: 2px 0 0;
|
|
3839
|
+
padding: 0;
|
|
3840
|
+
display: flex;
|
|
3841
|
+
flex-direction: column;
|
|
3842
|
+
gap: 1px;
|
|
3843
|
+
}
|
|
3844
|
+
.sa-legend-row {
|
|
3845
|
+
display: grid;
|
|
3846
|
+
grid-template-columns: 10px 1fr auto auto;
|
|
3847
|
+
align-items: baseline;
|
|
3848
|
+
gap: 8px;
|
|
3849
|
+
font-family: var(--mono);
|
|
3850
|
+
font-size: 10.5px;
|
|
3851
|
+
color: var(--text-soft);
|
|
3852
|
+
}
|
|
3853
|
+
.sa-legend-swatch {
|
|
3854
|
+
width: 8px;
|
|
3855
|
+
height: 8px;
|
|
3856
|
+
align-self: center;
|
|
3857
|
+
border-radius: 0;
|
|
3858
|
+
}
|
|
3859
|
+
.sa-legend-name {
|
|
3860
|
+
color: var(--text);
|
|
3861
|
+
letter-spacing: -0.003em;
|
|
3862
|
+
}
|
|
3863
|
+
.sa-legend-pct {
|
|
3864
|
+
color: var(--text-soft);
|
|
3865
|
+
font-variant-numeric: tabular-nums;
|
|
3866
|
+
text-align: right;
|
|
3867
|
+
}
|
|
3868
|
+
.sa-legend-tokens {
|
|
3869
|
+
color: var(--text-faint);
|
|
3870
|
+
font-variant-numeric: tabular-nums;
|
|
3871
|
+
text-align: right;
|
|
3872
|
+
min-width: 44px;
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
/* What you valued · chips for counts + list of ▲-voted points.
|
|
3876
|
+
Smaller chip padding + tighter point rows so the section reads
|
|
3877
|
+
as a tight strip rather than a separate panel. */
|
|
3878
|
+
.sa-chips {
|
|
3879
|
+
display: flex;
|
|
3880
|
+
flex-wrap: wrap;
|
|
3881
|
+
gap: 4px;
|
|
3882
|
+
}
|
|
3883
|
+
.sa-chip {
|
|
3884
|
+
display: inline-flex;
|
|
3885
|
+
align-items: center;
|
|
3886
|
+
gap: 4px;
|
|
3887
|
+
padding: 2px 7px;
|
|
3888
|
+
background: var(--bg);
|
|
3889
|
+
border: 0.5px solid var(--line-bright);
|
|
3890
|
+
font-family: var(--mono);
|
|
3891
|
+
font-size: 9.5px;
|
|
3892
|
+
letter-spacing: 0.06em;
|
|
3893
|
+
color: var(--text-soft);
|
|
3894
|
+
}
|
|
3895
|
+
.sa-chip-mark {
|
|
3896
|
+
color: var(--lime);
|
|
3897
|
+
font-weight: 700;
|
|
3898
|
+
}
|
|
3899
|
+
.sa-points {
|
|
3900
|
+
list-style: none;
|
|
3901
|
+
margin: 2px 0 0;
|
|
3902
|
+
padding: 0;
|
|
3903
|
+
display: flex;
|
|
3904
|
+
flex-direction: column;
|
|
3905
|
+
gap: 3px;
|
|
3906
|
+
}
|
|
3907
|
+
.sa-point {
|
|
3908
|
+
display: grid;
|
|
3909
|
+
grid-template-columns: 12px 1fr;
|
|
3910
|
+
gap: 6px;
|
|
3911
|
+
align-items: baseline;
|
|
3912
|
+
padding: 5px 8px;
|
|
3913
|
+
background: var(--bg);
|
|
3914
|
+
border: 0.5px solid var(--line);
|
|
3915
|
+
}
|
|
3916
|
+
.sa-point-mark {
|
|
3917
|
+
color: var(--lime);
|
|
3918
|
+
font-size: 9.5px;
|
|
3919
|
+
font-weight: 700;
|
|
3920
|
+
line-height: 1.4;
|
|
3921
|
+
}
|
|
3922
|
+
.sa-point-body {
|
|
3923
|
+
font-family: var(--font-human);
|
|
3924
|
+
font-size: 12px;
|
|
3925
|
+
line-height: 1.42;
|
|
3926
|
+
color: var(--text);
|
|
3927
|
+
letter-spacing: -0.003em;
|
|
3928
|
+
}
|
|
3929
|
+
.sa-empty {
|
|
2465
3930
|
font-family: var(--mono);
|
|
2466
3931
|
font-size: 10px;
|
|
2467
|
-
letter-spacing: 0.06em;
|
|
2468
3932
|
color: var(--text-faint);
|
|
2469
|
-
|
|
2470
|
-
margin-bottom: 6px;
|
|
2471
|
-
}
|
|
2472
|
-
.supplement-head .meta span { color: var(--text-soft); text-transform: none; letter-spacing: 0; }
|
|
2473
|
-
.supplement-head .title {
|
|
2474
|
-
font-family: var(--font-human);
|
|
2475
|
-
font-size: 18px;
|
|
2476
|
-
font-weight: 700;
|
|
2477
|
-
color: var(--text);
|
|
2478
|
-
letter-spacing: -0.01em;
|
|
2479
|
-
line-height: 1.25;
|
|
3933
|
+
letter-spacing: 0.04em;
|
|
2480
3934
|
}
|
|
2481
|
-
.
|
|
2482
|
-
|
|
2483
|
-
border: 0.5px solid var(--line-bright);
|
|
3935
|
+
.followup-children-head {
|
|
3936
|
+
font-size: 10px;
|
|
2484
3937
|
color: var(--text-soft);
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
line-height: 1;
|
|
3938
|
+
letter-spacing: 0.16em;
|
|
3939
|
+
text-transform: uppercase;
|
|
3940
|
+
font-weight: 700;
|
|
3941
|
+
margin-bottom: 10px;
|
|
2490
3942
|
}
|
|
2491
|
-
.
|
|
2492
|
-
|
|
2493
|
-
|
|
3943
|
+
.followup-children-list {
|
|
3944
|
+
display: flex;
|
|
3945
|
+
flex-direction: column;
|
|
3946
|
+
gap: 6px;
|
|
2494
3947
|
}
|
|
2495
|
-
.
|
|
2496
|
-
display:
|
|
2497
|
-
|
|
3948
|
+
.followup-child-tile {
|
|
3949
|
+
display: grid;
|
|
3950
|
+
grid-template-columns: 32px 1fr auto;
|
|
3951
|
+
gap: 10px;
|
|
3952
|
+
align-items: center;
|
|
3953
|
+
padding: 8px 10px;
|
|
2498
3954
|
background: var(--bg);
|
|
2499
|
-
border: 0.5px solid var(--line
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
line-height: 1.55;
|
|
2504
|
-
padding: 12px 14px;
|
|
2505
|
-
resize: vertical;
|
|
2506
|
-
min-height: 132px;
|
|
2507
|
-
transition: border-color 0.12s;
|
|
3955
|
+
border: 0.5px solid var(--line);
|
|
3956
|
+
cursor: pointer;
|
|
3957
|
+
text-decoration: none;
|
|
3958
|
+
transition: border-color 0.1s, background 0.1s;
|
|
2508
3959
|
}
|
|
2509
|
-
.
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
white-space: pre-wrap;
|
|
3960
|
+
.followup-child-tile:hover {
|
|
3961
|
+
border-color: var(--lime-dim);
|
|
3962
|
+
background: var(--panel);
|
|
2513
3963
|
}
|
|
2514
|
-
.
|
|
2515
|
-
|
|
2516
|
-
font-size: 11.5px;
|
|
2517
|
-
line-height: 1.5;
|
|
3964
|
+
.followup-child-tile .num {
|
|
3965
|
+
font-size: 9.5px;
|
|
2518
3966
|
color: var(--text-faint);
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
display: flex;
|
|
2522
|
-
justify-content: flex-end;
|
|
2523
|
-
gap: 10px;
|
|
2524
|
-
padding: 12px 22px 16px;
|
|
2525
|
-
border-top: 0.5px solid var(--line);
|
|
2526
|
-
}
|
|
2527
|
-
.supplement-cancel,
|
|
2528
|
-
.supplement-confirm {
|
|
2529
|
-
font-family: var(--mono);
|
|
2530
|
-
font-size: 10.5px;
|
|
3967
|
+
letter-spacing: 0.1em;
|
|
3968
|
+
text-align: center;
|
|
2531
3969
|
font-weight: 700;
|
|
2532
|
-
letter-spacing: 0.14em;
|
|
2533
|
-
text-transform: uppercase;
|
|
2534
|
-
padding: 8px 14px;
|
|
2535
|
-
cursor: pointer;
|
|
2536
|
-
background: transparent;
|
|
2537
|
-
border: 0.5px solid var(--line-strong);
|
|
2538
|
-
color: var(--text-soft);
|
|
2539
|
-
transition: border-color 0.1s, color 0.1s, background 0.1s;
|
|
2540
3970
|
}
|
|
2541
|
-
.
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
3971
|
+
.followup-child-tile .subject {
|
|
3972
|
+
font-size: 12px;
|
|
3973
|
+
color: var(--text);
|
|
3974
|
+
line-height: 1.3;
|
|
3975
|
+
white-space: nowrap;
|
|
3976
|
+
overflow: hidden;
|
|
3977
|
+
text-overflow: ellipsis;
|
|
2546
3978
|
}
|
|
2547
|
-
.
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
3979
|
+
.followup-child-tile .meta {
|
|
3980
|
+
font-size: 9.5px;
|
|
3981
|
+
color: var(--text-soft);
|
|
3982
|
+
letter-spacing: 0.06em;
|
|
3983
|
+
text-transform: uppercase;
|
|
3984
|
+
font-weight: 600;
|
|
2551
3985
|
}
|
|
3986
|
+
.followup-child-tile .meta.live { color: var(--lime); }
|
|
3987
|
+
.followup-child-tile .meta.paused { color: var(--amber, #B59560); }
|
|
3988
|
+
.followup-child-tile .meta.adjourned { color: var(--text-dim); }
|
|
2552
3989
|
|
|
2553
3990
|
/* Open CTA · compact pill anchored to the right */
|
|
2554
3991
|
.brief-open {
|
|
@@ -2648,6 +4085,31 @@
|
|
|
2648
4085
|
overflow-y: auto;
|
|
2649
4086
|
background: var(--panel);
|
|
2650
4087
|
padding: 14px 20px 18px;
|
|
4088
|
+
transition: opacity 0.18s ease-out;
|
|
4089
|
+
}
|
|
4090
|
+
/* Note-jump loading state · the user clicked a note in the All
|
|
4091
|
+
Notes view; openRoom is fetching the room body + the notes list
|
|
4092
|
+
before renderChat can scroll to the saved span. Keep the chat
|
|
4093
|
+
invisible while that work happens so the user doesn't see the
|
|
4094
|
+
stale-content → repaint → scroll-to-position transition as a
|
|
4095
|
+
flicker. The class is added at openRoom entry and removed once
|
|
4096
|
+
scrollToNote has landed (with a 1.2s timeout fallback in case
|
|
4097
|
+
the scroll never resolves). */
|
|
4098
|
+
body.note-jump-loading .chat { opacity: 0; }
|
|
4099
|
+
/* Cap reading measure on the inner messages container · pure
|
|
4100
|
+
percentage layout reads as a wall of text on wide monitors
|
|
4101
|
+
(~110+ chars / line) and tanks reading comprehension. ~820px
|
|
4102
|
+
keeps prose around 70-78 chars / line — Bringhurst's comfort
|
|
4103
|
+
band — while still leaving room for tables, tool-use rows
|
|
4104
|
+
(max-width 760px), and message chrome to sit inside without
|
|
4105
|
+
truncation. Centred so the column doesn't drift left of the
|
|
4106
|
+
viewport on ultra-wide displays. The .chat scroller stays
|
|
4107
|
+
full-width so the scrollbar lives at the viewport edge, not
|
|
4108
|
+
beside the prose. */
|
|
4109
|
+
.chat > [data-chat-messages] {
|
|
4110
|
+
max-width: 960px;
|
|
4111
|
+
margin-left: auto;
|
|
4112
|
+
margin-right: auto;
|
|
2651
4113
|
}
|
|
2652
4114
|
|
|
2653
4115
|
/* ─── Tool-use row ───────────────────────────────────────────
|
|
@@ -3463,6 +4925,53 @@
|
|
|
3463
4925
|
.chair-intervention .ci-body strong { color: var(--text); font-weight: 600; }
|
|
3464
4926
|
.chair-intervention .ci-body em { color: var(--lime); font-style: normal; font-weight: 500; }
|
|
3465
4927
|
|
|
4928
|
+
/* Chair-pending placeholder · transient "preparing…" card injected
|
|
4929
|
+
directly into [data-chat-messages] while the chair does silent
|
|
4930
|
+
server-side work (haiku discipline gate, pre-fetch tools, LLM
|
|
4931
|
+
startup). Mirrors the .chair-intervention skeleton — same lime
|
|
4932
|
+
accent, mono kicker, centered layout — but adds the bouncing
|
|
4933
|
+
thinking-dots so the user sees motion. Removed when the real chair
|
|
4934
|
+
bubble lands or the pipeline aborts. */
|
|
4935
|
+
.chair-pending {
|
|
4936
|
+
margin: 14px auto 18px;
|
|
4937
|
+
max-width: 640px;
|
|
4938
|
+
padding: 14px 22px 14px;
|
|
4939
|
+
background: transparent;
|
|
4940
|
+
}
|
|
4941
|
+
.chair-pending .cp-rule {
|
|
4942
|
+
width: 28px;
|
|
4943
|
+
height: 1px;
|
|
4944
|
+
background: var(--lime);
|
|
4945
|
+
margin: 0 auto 10px;
|
|
4946
|
+
opacity: 0.85;
|
|
4947
|
+
}
|
|
4948
|
+
.chair-pending .cp-kicker {
|
|
4949
|
+
text-align: center;
|
|
4950
|
+
font-family: var(--mono);
|
|
4951
|
+
font-size: 9.5px;
|
|
4952
|
+
font-weight: 600;
|
|
4953
|
+
letter-spacing: 0.22em;
|
|
4954
|
+
text-transform: uppercase;
|
|
4955
|
+
color: var(--lime);
|
|
4956
|
+
margin-bottom: 10px;
|
|
4957
|
+
opacity: 0.95;
|
|
4958
|
+
}
|
|
4959
|
+
.chair-pending .cp-body {
|
|
4960
|
+
display: flex;
|
|
4961
|
+
align-items: center;
|
|
4962
|
+
justify-content: center;
|
|
4963
|
+
gap: 10px;
|
|
4964
|
+
font-family: var(--font-agent);
|
|
4965
|
+
font-size: 13.5px;
|
|
4966
|
+
line-height: 1.65;
|
|
4967
|
+
color: var(--text-soft);
|
|
4968
|
+
letter-spacing: -0.005em;
|
|
4969
|
+
}
|
|
4970
|
+
.chair-pending .cp-text {
|
|
4971
|
+
font-style: italic;
|
|
4972
|
+
color: var(--text-faint);
|
|
4973
|
+
}
|
|
4974
|
+
|
|
3466
4975
|
/* Chair billing notice · same skeleton as chair-intervention but with
|
|
3467
4976
|
amber accent so it reads as a warning rather than a moderator
|
|
3468
4977
|
re-frame. Posted by the orchestrator when an upstream API rejects
|
|
@@ -3580,6 +5089,9 @@
|
|
|
3580
5089
|
justify-content: center;
|
|
3581
5090
|
}
|
|
3582
5091
|
.no-brief-card .nb-cta {
|
|
5092
|
+
display: inline-flex;
|
|
5093
|
+
align-items: center;
|
|
5094
|
+
gap: 8px;
|
|
3583
5095
|
font-family: var(--mono);
|
|
3584
5096
|
font-size: 10px;
|
|
3585
5097
|
font-weight: 700;
|
|
@@ -3592,15 +5104,19 @@
|
|
|
3592
5104
|
cursor: pointer;
|
|
3593
5105
|
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
3594
5106
|
}
|
|
5107
|
+
.no-brief-card .nb-cta-mark { color: var(--lime); font-size: 11px; line-height: 1; }
|
|
5108
|
+
.no-brief-card .nb-cta-text { line-height: 1; }
|
|
3595
5109
|
.no-brief-card .nb-cta:hover {
|
|
3596
5110
|
color: var(--lime);
|
|
3597
5111
|
border-color: var(--lime);
|
|
3598
5112
|
background: var(--panel-2);
|
|
3599
5113
|
}
|
|
5114
|
+
.no-brief-card .nb-cta:hover .nb-cta-mark { color: var(--lime); }
|
|
3600
5115
|
.no-brief-card .nb-cta[disabled] {
|
|
3601
5116
|
opacity: 0.6;
|
|
3602
5117
|
cursor: not-allowed;
|
|
3603
5118
|
}
|
|
5119
|
+
.no-brief-card .nb-cta[disabled] .nb-cta-mark { color: var(--text-faint); }
|
|
3604
5120
|
|
|
3605
5121
|
.msg {
|
|
3606
5122
|
display: grid;
|
|
@@ -4310,6 +5826,80 @@
|
|
|
4310
5826
|
text-align: center;
|
|
4311
5827
|
}
|
|
4312
5828
|
|
|
5829
|
+
/* Chair's tone-shift proposal callout · sits between the key-points
|
|
5830
|
+
list and the CTAs when the chair appended a MODE-SHIFT block to the
|
|
5831
|
+
round-end output. Mono kicker matches the existing kp-eyebrow
|
|
5832
|
+
register; serif body mirrors a pull-quote so the reasoning reads
|
|
5833
|
+
differently from the procedural ping. */
|
|
5834
|
+
.kp-mode-shift {
|
|
5835
|
+
margin-top: 14px;
|
|
5836
|
+
padding: 10px 12px;
|
|
5837
|
+
border-top: 0.5px solid var(--line-bright);
|
|
5838
|
+
border-bottom: 0.5px solid var(--line-bright);
|
|
5839
|
+
background: color-mix(in srgb, var(--amber) 6%, var(--panel));
|
|
5840
|
+
}
|
|
5841
|
+
.kp-shift-eyebrow {
|
|
5842
|
+
font-family: var(--mono);
|
|
5843
|
+
font-size: 9.5px;
|
|
5844
|
+
color: var(--amber);
|
|
5845
|
+
letter-spacing: 0.16em;
|
|
5846
|
+
text-transform: uppercase;
|
|
5847
|
+
margin-bottom: 6px;
|
|
5848
|
+
}
|
|
5849
|
+
.kp-shift-eyebrow strong {
|
|
5850
|
+
color: var(--amber);
|
|
5851
|
+
font-weight: 700;
|
|
5852
|
+
}
|
|
5853
|
+
.kp-shift-because {
|
|
5854
|
+
font-size: 12.5px;
|
|
5855
|
+
color: var(--text);
|
|
5856
|
+
line-height: 1.45;
|
|
5857
|
+
}
|
|
5858
|
+
/* Suppress the kp-ctas top rule when the mode-shift callout sits
|
|
5859
|
+
directly above it · two adjacent 0.5px borders read as a doubled
|
|
5860
|
+
frame. The mode-shift's own border-bottom is the divider here. */
|
|
5861
|
+
.kp-mode-shift + .kp-ctas {
|
|
5862
|
+
border-top: none;
|
|
5863
|
+
margin-top: 0;
|
|
5864
|
+
padding-top: 12px;
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
/* 3-button layout · only used when the chair proposed a tone shift
|
|
5868
|
+
(kp-ctas-shift). The primary "switch to <tone>" action takes ~50%
|
|
5869
|
+
of the row and each ghost button takes ~25%. Without this, three
|
|
5870
|
+
`flex: 1` buttons compress each to ~33% width and longer tone
|
|
5871
|
+
names ("constructive", "brainstorm") wrap inside the button.
|
|
5872
|
+
The wrap fallback (`flex-wrap: wrap` + flex-basis on each item)
|
|
5873
|
+
handles narrow chat viewports: when the row can't fit all three
|
|
5874
|
+
side-by-side, the primary spans the row and the two ghosts share
|
|
5875
|
+
the line below. Letter-spacing and uppercase are also relaxed for
|
|
5876
|
+
this row so the labels stay narrow. */
|
|
5877
|
+
.kp-ctas-shift { flex-wrap: wrap; }
|
|
5878
|
+
.kp-ctas-shift .kp-cta.primary { flex: 2 1 240px; }
|
|
5879
|
+
.kp-ctas-shift .kp-cta.ghost { flex: 1 1 110px; }
|
|
5880
|
+
.kp-ctas-shift .kp-cta {
|
|
5881
|
+
text-transform: none;
|
|
5882
|
+
letter-spacing: 0.04em;
|
|
5883
|
+
padding: 8px 10px;
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
/* Degraded round-end card · shown when the chair finished streaming
|
|
5887
|
+
but the parser couldn't extract any key points from the body.
|
|
5888
|
+
Quieter eyebrow than the regular kp-eyebrow so the user reads it
|
|
5889
|
+
as "this round didn't produce a vote ballot" rather than an error.
|
|
5890
|
+
The continue / adjourn buttons below stay primary so the room can
|
|
5891
|
+
still move forward. Earlier failure mode here was an indefinite
|
|
5892
|
+
"drafting key points…" lock-up because the skeleton kept rendering
|
|
5893
|
+
after streaming ended. */
|
|
5894
|
+
.kp-eyebrow-degraded {
|
|
5895
|
+
font-family: var(--mono);
|
|
5896
|
+
font-size: 9.5px;
|
|
5897
|
+
color: var(--text-faint);
|
|
5898
|
+
letter-spacing: 0.16em;
|
|
5899
|
+
text-transform: uppercase;
|
|
5900
|
+
margin-bottom: 10px;
|
|
5901
|
+
}
|
|
5902
|
+
|
|
4313
5903
|
/* Inline @mention / /handle pill. Two flavours so the addressee is
|
|
4314
5904
|
glanceable: agent mentions get amber, user mentions get lime.
|
|
4315
5905
|
Unscoped so the styling lands in any context (chat bubble, convene
|
|
@@ -5597,6 +7187,21 @@
|
|
|
5597
7187
|
transform: rotate(180deg);
|
|
5598
7188
|
}
|
|
5599
7189
|
|
|
7190
|
+
/* Composer-toolbar fitting for the reused .ap-skill-row-toggle
|
|
7191
|
+
vocabulary · same control as agent-profile's web-search row but
|
|
7192
|
+
in a slightly different surrounding context (cmp-toolbar is
|
|
7193
|
+
denser than the skill row). Just whitespace tuning — the toggle
|
|
7194
|
+
visuals come from agent-profile.css. */
|
|
7195
|
+
.cmp-toolbar .cmp-ws-toggle {
|
|
7196
|
+
/* Full label + state takes more horizontal room than the
|
|
7197
|
+
cmp-dd dropdowns; tighten letter-spacing slightly so the row
|
|
7198
|
+
still fits the model dropdown + manual button + Convene CTA
|
|
7199
|
+
without wrapping at common widths. */
|
|
7200
|
+
letter-spacing: 0.12em;
|
|
7201
|
+
padding-left: 4px;
|
|
7202
|
+
padding-right: 4px;
|
|
7203
|
+
}
|
|
7204
|
+
|
|
5600
7205
|
/* Tune dropdown · row layout matches the director picker exactly:
|
|
5601
7206
|
same padding (5px 10px), same name (12.5px sans) + tag (8.5px
|
|
5602
7207
|
mono uppercase) inline-baseline pairing, same active treatment
|
|
@@ -5882,9 +7487,121 @@
|
|
|
5882
7487
|
margin-top: 22px;
|
|
5883
7488
|
background: var(--panel-2);
|
|
5884
7489
|
border: 0.5px solid var(--line-bright);
|
|
5885
|
-
padding: 18px 22px 16px;
|
|
5886
|
-
position: relative;
|
|
5887
|
-
overflow: hidden;
|
|
7490
|
+
padding: 18px 22px 16px;
|
|
7491
|
+
position: relative;
|
|
7492
|
+
overflow: hidden;
|
|
7493
|
+
}
|
|
7494
|
+
/* Recovery card · shown when /generate-spec hits the 5-min hard
|
|
7495
|
+
timeout or returns an error. Same outer shell as ag-gen-card so
|
|
7496
|
+
the layout doesn't jump between generating → error. */
|
|
7497
|
+
.ag-gen-error-card {
|
|
7498
|
+
margin-top: 22px;
|
|
7499
|
+
background: var(--panel-2);
|
|
7500
|
+
border: 0.5px solid var(--amber, #B59560);
|
|
7501
|
+
padding: 22px 24px 20px;
|
|
7502
|
+
}
|
|
7503
|
+
.ag-gen-error-kicker {
|
|
7504
|
+
font-family: var(--mono);
|
|
7505
|
+
font-size: 9.5px;
|
|
7506
|
+
letter-spacing: 0.16em;
|
|
7507
|
+
text-transform: uppercase;
|
|
7508
|
+
color: var(--amber, #B59560);
|
|
7509
|
+
margin-bottom: 10px;
|
|
7510
|
+
}
|
|
7511
|
+
.ag-gen-error-title {
|
|
7512
|
+
font-family: var(--font-human);
|
|
7513
|
+
font-size: 18px;
|
|
7514
|
+
font-weight: 600;
|
|
7515
|
+
color: var(--text);
|
|
7516
|
+
line-height: 1.35;
|
|
7517
|
+
margin: 0 0 10px;
|
|
7518
|
+
}
|
|
7519
|
+
.ag-gen-error-hint {
|
|
7520
|
+
font-family: var(--font-human);
|
|
7521
|
+
font-size: 13.5px;
|
|
7522
|
+
line-height: 1.55;
|
|
7523
|
+
color: var(--text-soft);
|
|
7524
|
+
margin: 0 0 12px;
|
|
7525
|
+
}
|
|
7526
|
+
.ag-gen-error-detail {
|
|
7527
|
+
font-family: var(--mono);
|
|
7528
|
+
font-size: 10.5px;
|
|
7529
|
+
line-height: 1.55;
|
|
7530
|
+
color: var(--text-faint);
|
|
7531
|
+
background: var(--bg);
|
|
7532
|
+
border: 0.5px solid var(--line);
|
|
7533
|
+
padding: 8px 12px;
|
|
7534
|
+
margin: 0 0 14px;
|
|
7535
|
+
word-break: break-word;
|
|
7536
|
+
max-height: 120px;
|
|
7537
|
+
overflow-y: auto;
|
|
7538
|
+
}
|
|
7539
|
+
.ag-gen-error-desc {
|
|
7540
|
+
margin: 0 0 16px;
|
|
7541
|
+
padding-top: 12px;
|
|
7542
|
+
border-top: 0.5px solid var(--line);
|
|
7543
|
+
}
|
|
7544
|
+
.ag-gen-error-desc-label {
|
|
7545
|
+
font-family: var(--mono);
|
|
7546
|
+
font-size: 9.5px;
|
|
7547
|
+
letter-spacing: 0.12em;
|
|
7548
|
+
text-transform: uppercase;
|
|
7549
|
+
color: var(--text-faint);
|
|
7550
|
+
margin-bottom: 6px;
|
|
7551
|
+
}
|
|
7552
|
+
.ag-gen-error-desc-body {
|
|
7553
|
+
font-family: var(--font-human);
|
|
7554
|
+
font-size: 13px;
|
|
7555
|
+
line-height: 1.55;
|
|
7556
|
+
color: var(--text-soft);
|
|
7557
|
+
}
|
|
7558
|
+
.ag-gen-error-actions {
|
|
7559
|
+
display: flex;
|
|
7560
|
+
align-items: center;
|
|
7561
|
+
gap: 12px;
|
|
7562
|
+
}
|
|
7563
|
+
.ag-gen-error-retry {
|
|
7564
|
+
appearance: none;
|
|
7565
|
+
background: var(--lime);
|
|
7566
|
+
color: var(--bg);
|
|
7567
|
+
border: 0.5px solid var(--lime);
|
|
7568
|
+
padding: 9px 18px;
|
|
7569
|
+
font-family: var(--mono);
|
|
7570
|
+
font-size: 11px;
|
|
7571
|
+
font-weight: 700;
|
|
7572
|
+
letter-spacing: 0.14em;
|
|
7573
|
+
text-transform: uppercase;
|
|
7574
|
+
cursor: pointer;
|
|
7575
|
+
display: inline-flex;
|
|
7576
|
+
align-items: center;
|
|
7577
|
+
gap: 8px;
|
|
7578
|
+
transition: background 0.12s, color 0.12s;
|
|
7579
|
+
}
|
|
7580
|
+
.ag-gen-error-retry:hover {
|
|
7581
|
+
background: transparent;
|
|
7582
|
+
color: var(--lime);
|
|
7583
|
+
}
|
|
7584
|
+
.ag-gen-error-retry-mark {
|
|
7585
|
+
font-size: 13px;
|
|
7586
|
+
line-height: 1;
|
|
7587
|
+
}
|
|
7588
|
+
.ag-gen-error-discard {
|
|
7589
|
+
appearance: none;
|
|
7590
|
+
background: transparent;
|
|
7591
|
+
color: var(--text-soft);
|
|
7592
|
+
border: 0.5px solid var(--line-bright);
|
|
7593
|
+
padding: 9px 16px;
|
|
7594
|
+
font-family: var(--mono);
|
|
7595
|
+
font-size: 10.5px;
|
|
7596
|
+
font-weight: 600;
|
|
7597
|
+
letter-spacing: 0.12em;
|
|
7598
|
+
text-transform: uppercase;
|
|
7599
|
+
cursor: pointer;
|
|
7600
|
+
transition: color 0.12s, border-color 0.12s;
|
|
7601
|
+
}
|
|
7602
|
+
.ag-gen-error-discard:hover {
|
|
7603
|
+
color: var(--text);
|
|
7604
|
+
border-color: var(--text-soft);
|
|
5888
7605
|
}
|
|
5889
7606
|
/* Subtle scanline texture across the card · adds an "active system"
|
|
5890
7607
|
atmosphere without being distracting. Slow upward drift. */
|
|
@@ -6663,6 +8380,119 @@
|
|
|
6663
8380
|
color: var(--lime);
|
|
6664
8381
|
}
|
|
6665
8382
|
|
|
8383
|
+
/* ─── Brief picker popover · the [View Report] click target on
|
|
8384
|
+
multi-brief rooms. Anchored under the room-head's button via
|
|
8385
|
+
position: fixed and right-aligned so it visually drops out of
|
|
8386
|
+
the trigger. Each row is a plain anchor to /report.html so
|
|
8387
|
+
middle-click / cmd-click work for opening multiple reports
|
|
8388
|
+
in tabs. ─── */
|
|
8389
|
+
.brief-picker-pop {
|
|
8390
|
+
position: fixed;
|
|
8391
|
+
z-index: 9001;
|
|
8392
|
+
width: 380px;
|
|
8393
|
+
max-width: calc(100vw - 32px);
|
|
8394
|
+
overflow-y: auto;
|
|
8395
|
+
background: var(--panel);
|
|
8396
|
+
border: 0.5px solid var(--line-strong);
|
|
8397
|
+
}
|
|
8398
|
+
.brief-picker-head {
|
|
8399
|
+
display: flex;
|
|
8400
|
+
justify-content: space-between;
|
|
8401
|
+
align-items: baseline;
|
|
8402
|
+
padding: 8px 12px;
|
|
8403
|
+
border-bottom: 0.5px solid var(--line);
|
|
8404
|
+
}
|
|
8405
|
+
.brief-picker-title-head {
|
|
8406
|
+
font-family: var(--mono);
|
|
8407
|
+
font-size: 14px;
|
|
8408
|
+
font-weight: 700;
|
|
8409
|
+
letter-spacing: 0.04em;
|
|
8410
|
+
color: var(--text);
|
|
8411
|
+
}
|
|
8412
|
+
.brief-picker-count {
|
|
8413
|
+
font-family: var(--mono);
|
|
8414
|
+
font-size: 10px;
|
|
8415
|
+
letter-spacing: 0.08em;
|
|
8416
|
+
color: var(--text-faint);
|
|
8417
|
+
background: var(--panel-2);
|
|
8418
|
+
border: 0.5px solid var(--line);
|
|
8419
|
+
padding: 2px 8px;
|
|
8420
|
+
}
|
|
8421
|
+
.brief-picker-list {
|
|
8422
|
+
padding: 2px 0;
|
|
8423
|
+
}
|
|
8424
|
+
.brief-picker-row {
|
|
8425
|
+
display: grid;
|
|
8426
|
+
grid-template-columns: 32px 1fr auto auto;
|
|
8427
|
+
gap: 10px;
|
|
8428
|
+
align-items: center;
|
|
8429
|
+
padding: 10px 12px;
|
|
8430
|
+
text-decoration: none;
|
|
8431
|
+
color: inherit;
|
|
8432
|
+
border-bottom: 0.5px solid var(--line);
|
|
8433
|
+
transition: background 0.1s;
|
|
8434
|
+
}
|
|
8435
|
+
.brief-picker-row:last-child { border-bottom: none; }
|
|
8436
|
+
.brief-picker-row:hover { background: var(--panel-2); }
|
|
8437
|
+
.brief-picker-num {
|
|
8438
|
+
font-family: var(--mono);
|
|
8439
|
+
font-size: 11px;
|
|
8440
|
+
font-weight: 700;
|
|
8441
|
+
color: var(--text-faint);
|
|
8442
|
+
letter-spacing: 0.06em;
|
|
8443
|
+
}
|
|
8444
|
+
.brief-picker-row:hover .brief-picker-num { color: var(--lime); }
|
|
8445
|
+
.brief-picker-main {
|
|
8446
|
+
display: flex;
|
|
8447
|
+
flex-direction: column;
|
|
8448
|
+
gap: 2px;
|
|
8449
|
+
min-width: 0;
|
|
8450
|
+
}
|
|
8451
|
+
.brief-picker-title {
|
|
8452
|
+
font-family: var(--font-human, system-ui, sans-serif);
|
|
8453
|
+
font-size: 13px;
|
|
8454
|
+
font-weight: 600;
|
|
8455
|
+
color: var(--text);
|
|
8456
|
+
line-height: 1.3;
|
|
8457
|
+
overflow: hidden;
|
|
8458
|
+
display: -webkit-box;
|
|
8459
|
+
-webkit-line-clamp: 2;
|
|
8460
|
+
-webkit-box-orient: vertical;
|
|
8461
|
+
}
|
|
8462
|
+
.brief-picker-sub {
|
|
8463
|
+
font-family: var(--mono);
|
|
8464
|
+
font-size: 10px;
|
|
8465
|
+
letter-spacing: 0.04em;
|
|
8466
|
+
color: var(--text-faint);
|
|
8467
|
+
overflow: hidden;
|
|
8468
|
+
text-overflow: ellipsis;
|
|
8469
|
+
white-space: nowrap;
|
|
8470
|
+
}
|
|
8471
|
+
.brief-picker-time {
|
|
8472
|
+
font-family: var(--mono);
|
|
8473
|
+
font-size: 9.5px;
|
|
8474
|
+
letter-spacing: 0.04em;
|
|
8475
|
+
color: var(--text-faint);
|
|
8476
|
+
white-space: nowrap;
|
|
8477
|
+
}
|
|
8478
|
+
.brief-picker-arrow {
|
|
8479
|
+
font-family: var(--mono);
|
|
8480
|
+
font-size: 12px;
|
|
8481
|
+
color: var(--text-faint);
|
|
8482
|
+
transition: color 0.12s, transform 0.12s;
|
|
8483
|
+
}
|
|
8484
|
+
.brief-picker-row:hover .brief-picker-arrow {
|
|
8485
|
+
color: var(--lime);
|
|
8486
|
+
transform: translate(2px, -2px);
|
|
8487
|
+
}
|
|
8488
|
+
/* The "· N" count chip rendered inline with the View Report button
|
|
8489
|
+
when multiple briefs exist · subtle, blends with the bracket
|
|
8490
|
+
monospace label, mirrors how `.session-num` reads. */
|
|
8491
|
+
.view-report-btn .vr-count {
|
|
8492
|
+
color: var(--text-faint);
|
|
8493
|
+
font-weight: 500;
|
|
8494
|
+
}
|
|
8495
|
+
|
|
6666
8496
|
/* Convene opener · the room's seed question, distinct from chat bubbles. */
|
|
6667
8497
|
.convene-opener {
|
|
6668
8498
|
margin: 0 auto 24px;
|
|
@@ -6700,6 +8530,44 @@
|
|
|
6700
8530
|
margin: 0 0 10px;
|
|
6701
8531
|
}
|
|
6702
8532
|
.convene-body p { margin: 0; }
|
|
8533
|
+
|
|
8534
|
+
/* Long-opener clamp · when the user wrote more than a sentence of
|
|
8535
|
+
context, the card would otherwise dominate the viewport. We
|
|
8536
|
+
clamp the body to ~2.5 lines with a fade-out overlay; a
|
|
8537
|
+
`<button data-convene-toggle>` below toggles `.expanded` on the
|
|
8538
|
+
opener parent to reveal the rest. 2.5 × line-height (1.32 ×
|
|
8539
|
+
19px ≈ 25px) = 63px. */
|
|
8540
|
+
.convene-opener-clamped:not(.expanded) .convene-body {
|
|
8541
|
+
max-height: 63px;
|
|
8542
|
+
overflow: hidden;
|
|
8543
|
+
position: relative;
|
|
8544
|
+
}
|
|
8545
|
+
.convene-opener-clamped:not(.expanded) .convene-body::after {
|
|
8546
|
+
content: "";
|
|
8547
|
+
position: absolute;
|
|
8548
|
+
left: 0;
|
|
8549
|
+
right: 0;
|
|
8550
|
+
bottom: 0;
|
|
8551
|
+
height: 32px;
|
|
8552
|
+
background: linear-gradient(to bottom, transparent, var(--panel-2));
|
|
8553
|
+
pointer-events: none;
|
|
8554
|
+
}
|
|
8555
|
+
.convene-toggle {
|
|
8556
|
+
appearance: none;
|
|
8557
|
+
background: transparent;
|
|
8558
|
+
border: 0;
|
|
8559
|
+
color: var(--text-soft);
|
|
8560
|
+
cursor: pointer;
|
|
8561
|
+
font-family: var(--mono);
|
|
8562
|
+
font-size: 10px;
|
|
8563
|
+
font-weight: 700;
|
|
8564
|
+
letter-spacing: 0.14em;
|
|
8565
|
+
text-transform: uppercase;
|
|
8566
|
+
padding: 4px 0 6px;
|
|
8567
|
+
margin: 4px 0 6px;
|
|
8568
|
+
transition: color 0.12s;
|
|
8569
|
+
}
|
|
8570
|
+
.convene-toggle:hover { color: var(--lime); }
|
|
6703
8571
|
.convene-meta {
|
|
6704
8572
|
font-family: var(--mono);
|
|
6705
8573
|
font-size: 10px;
|
|
@@ -6714,6 +8582,74 @@
|
|
|
6714
8582
|
.convene-meta .convene-time { color: var(--text-faint); }
|
|
6715
8583
|
.convene-meta .convene-cast { color: var(--text-dim); }
|
|
6716
8584
|
|
|
8585
|
+
/* Follow-up origin row · only present when this room was started as
|
|
8586
|
+
a continuation of a prior adjourned room. Sits between the
|
|
8587
|
+
eyebrow and the question body, click-navigates back to the parent
|
|
8588
|
+
room via hash route. Visually distinct from the eyebrow (mono +
|
|
8589
|
+
↩ glyph + brighter than meta-row) but quieter than the headline. */
|
|
8590
|
+
.convene-origin {
|
|
8591
|
+
display: inline-flex;
|
|
8592
|
+
align-items: baseline;
|
|
8593
|
+
gap: 6px;
|
|
8594
|
+
margin: -2px 0 10px;
|
|
8595
|
+
padding: 4px 8px 4px 6px;
|
|
8596
|
+
background: var(--bg);
|
|
8597
|
+
border: 0.5px solid var(--line-bright);
|
|
8598
|
+
font-family: var(--mono);
|
|
8599
|
+
font-size: 10.5px;
|
|
8600
|
+
letter-spacing: 0.04em;
|
|
8601
|
+
color: var(--text-soft);
|
|
8602
|
+
text-decoration: none;
|
|
8603
|
+
transition: border-color 0.12s, color 0.12s, background 0.12s;
|
|
8604
|
+
max-width: 100%;
|
|
8605
|
+
/* Lock chrome to one line. CJK ("继续自") has no inter-character
|
|
8606
|
+
wrap stopper by default and would otherwise break mid-word
|
|
8607
|
+
when the row got tight. */
|
|
8608
|
+
white-space: nowrap;
|
|
8609
|
+
}
|
|
8610
|
+
.convene-origin:hover {
|
|
8611
|
+
border-color: var(--lime-dim);
|
|
8612
|
+
color: var(--text);
|
|
8613
|
+
background: var(--panel-3);
|
|
8614
|
+
}
|
|
8615
|
+
/* Lock the labels so flex shrinking only consumes the subject
|
|
8616
|
+
(which has its own ellipsis). Otherwise every child shares the
|
|
8617
|
+
shrink and the labels squeeze before the subject does. */
|
|
8618
|
+
.convene-origin-arrow,
|
|
8619
|
+
.convene-origin-label,
|
|
8620
|
+
.convene-origin-room,
|
|
8621
|
+
.convene-origin-sep {
|
|
8622
|
+
flex-shrink: 0;
|
|
8623
|
+
}
|
|
8624
|
+
.convene-origin-arrow {
|
|
8625
|
+
color: var(--lime);
|
|
8626
|
+
font-weight: 700;
|
|
8627
|
+
line-height: 1;
|
|
8628
|
+
}
|
|
8629
|
+
.convene-origin-label {
|
|
8630
|
+
color: var(--text-soft);
|
|
8631
|
+
text-transform: uppercase;
|
|
8632
|
+
letter-spacing: 0.1em;
|
|
8633
|
+
font-weight: 600;
|
|
8634
|
+
font-size: 9.5px;
|
|
8635
|
+
}
|
|
8636
|
+
.convene-origin-room {
|
|
8637
|
+
color: var(--lime);
|
|
8638
|
+
font-weight: 700;
|
|
8639
|
+
font-variant-numeric: tabular-nums;
|
|
8640
|
+
}
|
|
8641
|
+
.convene-origin-sep { color: var(--text-faint); }
|
|
8642
|
+
.convene-origin-subject {
|
|
8643
|
+
color: var(--text-soft);
|
|
8644
|
+
text-transform: none;
|
|
8645
|
+
letter-spacing: -0.003em;
|
|
8646
|
+
overflow: hidden;
|
|
8647
|
+
text-overflow: ellipsis;
|
|
8648
|
+
white-space: nowrap;
|
|
8649
|
+
min-width: 0;
|
|
8650
|
+
flex-shrink: 1;
|
|
8651
|
+
}
|
|
8652
|
+
|
|
6717
8653
|
/* ─── Convening card · multi-stage placeholder while the room opens ───
|
|
6718
8654
|
Lives at the tail of the chat from the moment createRoom resolves
|
|
6719
8655
|
until the chair's first message lands (~10s). Three stages tied to
|
|
@@ -6917,14 +8853,123 @@
|
|
|
6917
8853
|
background: var(--panel);
|
|
6918
8854
|
}
|
|
6919
8855
|
|
|
6920
|
-
/* Input bar — sits inside the chat column
|
|
8856
|
+
/* Input bar — sits inside the chat column. Live state hosts a
|
|
8857
|
+
small action toolbar (pause / adjourn) on the left, then the
|
|
8858
|
+
input pill which grows to fill remaining width. */
|
|
6921
8859
|
.input-bar {
|
|
6922
8860
|
flex: 0 0 auto;
|
|
6923
8861
|
border-top: 0.5px solid var(--line-bright);
|
|
6924
8862
|
padding: 8px 14px;
|
|
6925
8863
|
background: var(--panel-2);
|
|
8864
|
+
display: flex;
|
|
8865
|
+
align-items: stretch;
|
|
8866
|
+
gap: 10px;
|
|
8867
|
+
}
|
|
8868
|
+
/* Session-control icons · sit to the LEFT of the input pill as
|
|
8869
|
+
bare glyph buttons. Borderless in the resting state so they
|
|
8870
|
+
don't compete with the input pill's hairline frame.
|
|
8871
|
+
──────────────────────────────────────────────────────────────
|
|
8872
|
+
Visual vocabulary mirrors the sidebar's nav icons (Lucide-style
|
|
8873
|
+
stroked SVG, 16px, mask-image fill via currentColor). Hover
|
|
8874
|
+
state matches the sidebar's `.new-btn:hover` — panel-2 bg + text
|
|
8875
|
+
colour shift, no accent tint. The destructive cue for adjourn
|
|
8876
|
+
lives in the confirm overlay, not in the icon, so neither
|
|
8877
|
+
button needs an accent colour to broadcast severity here. Hit
|
|
8878
|
+
target 32×32; icon 16×16 sits centred inside it. */
|
|
8879
|
+
.input-bar-actions {
|
|
8880
|
+
display: flex;
|
|
8881
|
+
align-items: center;
|
|
8882
|
+
gap: 2px;
|
|
8883
|
+
}
|
|
8884
|
+
.ib-action {
|
|
8885
|
+
position: relative;
|
|
8886
|
+
width: 32px;
|
|
8887
|
+
height: 32px;
|
|
8888
|
+
display: inline-flex;
|
|
8889
|
+
align-items: center;
|
|
8890
|
+
justify-content: center;
|
|
8891
|
+
padding: 0;
|
|
8892
|
+
background: transparent;
|
|
8893
|
+
border: none;
|
|
8894
|
+
color: var(--text-faint);
|
|
8895
|
+
cursor: pointer;
|
|
8896
|
+
transition: color 0.12s, background 0.12s;
|
|
8897
|
+
}
|
|
8898
|
+
.ib-action::before {
|
|
8899
|
+
content: "";
|
|
8900
|
+
width: 16px;
|
|
8901
|
+
height: 16px;
|
|
8902
|
+
background-color: currentColor;
|
|
8903
|
+
-webkit-mask-image: var(--icon, none);
|
|
8904
|
+
mask-image: var(--icon, none);
|
|
8905
|
+
-webkit-mask-repeat: no-repeat;
|
|
8906
|
+
mask-repeat: no-repeat;
|
|
8907
|
+
-webkit-mask-position: center;
|
|
8908
|
+
mask-position: center;
|
|
8909
|
+
-webkit-mask-size: 16px 16px;
|
|
8910
|
+
mask-size: 16px 16px;
|
|
8911
|
+
}
|
|
8912
|
+
.ib-action:hover { background: var(--panel-2); color: var(--text); }
|
|
8913
|
+
/* Hover tooltip · CSS-only via ::after + data-tip. The native
|
|
8914
|
+
`title` attribute pops after a 1-2s OS delay, which is sluggish;
|
|
8915
|
+
this version reveals at ~300ms — slow enough not to be noisy
|
|
8916
|
+
when the cursor only passes through, fast enough to feel
|
|
8917
|
+
responsive on intentional dwell. Mirrors the .note-tip visual
|
|
8918
|
+
register (panel-2 surface, mono micro-type, hairline frame).
|
|
8919
|
+
Pointer-events:none so the tooltip never blocks the hover that
|
|
8920
|
+
triggers it. */
|
|
8921
|
+
.ib-action::after {
|
|
8922
|
+
content: attr(data-tip);
|
|
8923
|
+
position: absolute;
|
|
8924
|
+
bottom: calc(100% + 8px);
|
|
8925
|
+
/* Anchor the tooltip's LEFT edge to the button's left edge so
|
|
8926
|
+
the tip extends RIGHTWARD into the chat column. Centered
|
|
8927
|
+
anchoring would push the left half past the chat-column edge
|
|
8928
|
+
where `.body-grid { overflow: hidden }` (line ~215) clips it
|
|
8929
|
+
behind the sidebar — the user reported this as "tip gets cut
|
|
8930
|
+
off by the sidebar". Right-anchored gives clearance because
|
|
8931
|
+
the chat column has plenty of width to the right. */
|
|
8932
|
+
left: 0;
|
|
8933
|
+
transform: translateY(3px);
|
|
8934
|
+
background: var(--panel-2);
|
|
8935
|
+
border: 0.5px solid var(--line-strong);
|
|
8936
|
+
padding: 5px 9px;
|
|
8937
|
+
font-family: var(--mono);
|
|
8938
|
+
font-size: 10px;
|
|
8939
|
+
letter-spacing: 0.04em;
|
|
8940
|
+
color: var(--text);
|
|
8941
|
+
white-space: nowrap;
|
|
8942
|
+
pointer-events: none;
|
|
8943
|
+
opacity: 0;
|
|
8944
|
+
visibility: hidden;
|
|
8945
|
+
box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.55);
|
|
8946
|
+
/* On exit the visibility flip is delayed past the opacity fade
|
|
8947
|
+
so the tooltip stays interactable until it has fully
|
|
8948
|
+
disappeared visually. */
|
|
8949
|
+
transition: opacity 0.14s ease, transform 0.14s ease, visibility 0s linear 0.18s;
|
|
8950
|
+
z-index: 50;
|
|
8951
|
+
}
|
|
8952
|
+
.ib-action:hover::after,
|
|
8953
|
+
.ib-action:focus-visible::after {
|
|
8954
|
+
opacity: 1;
|
|
8955
|
+
visibility: visible;
|
|
8956
|
+
transform: translateY(0);
|
|
8957
|
+
/* On entry · 0.3s delay before the fade starts; visibility
|
|
8958
|
+
flips immediately so the transform animates from offset. */
|
|
8959
|
+
transition: opacity 0.14s ease 0.3s, transform 0.14s ease 0.3s, visibility 0s linear 0.3s;
|
|
8960
|
+
}
|
|
8961
|
+
/* Pause · Lucide pause (two rounded vertical bars). */
|
|
8962
|
+
.ib-pause {
|
|
8963
|
+
--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'><rect x='14' y='3' width='5' height='18' rx='1'/><rect x='5' y='3' width='5' height='18' rx='1'/></svg>");
|
|
8964
|
+
}
|
|
8965
|
+
/* Adjourn · Lucide log-out (door + arrow exit). Reads as "leave
|
|
8966
|
+
this session"; the confirm overlay clarifies brief filing. */
|
|
8967
|
+
.ib-adjourn {
|
|
8968
|
+
--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='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'/><polyline points='16 17 21 12 16 7'/><line x1='21' x2='9' y1='12' y2='12'/></svg>");
|
|
6926
8969
|
}
|
|
6927
8970
|
.input-wrap {
|
|
8971
|
+
flex: 1 1 auto;
|
|
8972
|
+
min-width: 0;
|
|
6928
8973
|
background: var(--bg);
|
|
6929
8974
|
border: 0.5px solid var(--line-strong);
|
|
6930
8975
|
padding: 0 8px;
|
|
@@ -7066,6 +9111,7 @@
|
|
|
7066
9111
|
<div class="sidebar-head">
|
|
7067
9112
|
<div class="sidebar-head-title">// CONTROL</div>
|
|
7068
9113
|
<div class="sidebar-head-meta"><span class="lime">●</span> <span data-sidebar-summary>0 LIVE / 0 AGENTS</span></div>
|
|
9114
|
+
<button type="button" class="sidebar-collapse-btn" data-sidebar-collapse aria-label="Collapse sidebar" title="Collapse sidebar"></button>
|
|
7069
9115
|
</div>
|
|
7070
9116
|
|
|
7071
9117
|
<div class="sidebar-tabs" role="tablist">
|
|
@@ -7090,7 +9136,20 @@
|
|
|
7090
9136
|
"New room" so it reads as a peer destination, not a list
|
|
7091
9137
|
item, with a square glyph echoing the dashboard glyphs
|
|
7092
9138
|
elsewhere in the sidebar. -->
|
|
7093
|
-
<a href="#" class="new-btn nav-reports" data-reports-trigger>
|
|
9139
|
+
<a href="#" class="new-btn nav-reports" data-reports-trigger>
|
|
9140
|
+
<span class="nav-label">All Reports</span>
|
|
9141
|
+
<span class="nav-count" data-reports-count hidden></span>
|
|
9142
|
+
</a>
|
|
9143
|
+
<!-- "All Notes" · chairman's-notes index. Sits directly below
|
|
9144
|
+
All Reports because both are cross-cutting aggregation views
|
|
9145
|
+
(read-only collections that span every room), distinct from
|
|
9146
|
+
the room list below. Bookmark glyph differentiates from
|
|
9147
|
+
Reports' FileText glyph. The data-notes-count badge updates
|
|
9148
|
+
live as new notes are saved or deleted. -->
|
|
9149
|
+
<a href="#" class="new-btn nav-notes" data-notes-trigger>
|
|
9150
|
+
<span class="nav-label">All Notes</span>
|
|
9151
|
+
<span class="nav-count" data-notes-count hidden></span>
|
|
9152
|
+
</a>
|
|
7094
9153
|
|
|
7095
9154
|
<div class="sessions-scroll">
|
|
7096
9155
|
<div data-rooms-list></div>
|
|
@@ -7165,6 +9224,17 @@
|
|
|
7165
9224
|
</div>
|
|
7166
9225
|
|
|
7167
9226
|
<footer class="input-bar">
|
|
9227
|
+
<!-- Session-control toolbar · pause + adjourn live left of
|
|
9228
|
+
the input pill so they're reachable without leaving
|
|
9229
|
+
the typing area. Both reuse the existing data-pause /
|
|
9230
|
+
data-adjourn handlers; no new backend routes. The
|
|
9231
|
+
visual grammar matches head-actions (mono caps,
|
|
9232
|
+
brackets) but at a tighter scale. Adjourn turns warn
|
|
9233
|
+
on hover to flag its destructiveness. -->
|
|
9234
|
+
<div class="input-bar-actions">
|
|
9235
|
+
<button type="button" class="ib-action ib-pause" data-pause aria-label="Pause discussion" data-tip="Pause · you can resume later"></button>
|
|
9236
|
+
<button type="button" class="ib-action ib-adjourn" data-adjourn aria-label="Adjourn the room" data-tip="Adjourn · file the report and end"></button>
|
|
9237
|
+
</div>
|
|
7168
9238
|
<div class="input-wrap">
|
|
7169
9239
|
<input type="text" placeholder="interject anytime · @first_p to direct a director..." data-send-input>
|
|
7170
9240
|
<button class="send-btn" data-send-button>[ Send ]</button>
|
|
@@ -7177,6 +9247,7 @@
|
|
|
7177
9247
|
</div>
|
|
7178
9248
|
<div class="paused-bar-actions">
|
|
7179
9249
|
<a href="?status=live" class="resume-btn-lg">[ ▶ Resume Discussion ]</a>
|
|
9250
|
+
<a href="#" class="adjourn-btn-lg" data-adjourn>[ <span class="adjourn-glyph">⏏</span> Adjourn ]</a>
|
|
7180
9251
|
</div>
|
|
7181
9252
|
</footer>
|
|
7182
9253
|
|
|
@@ -7186,6 +9257,7 @@
|
|
|
7186
9257
|
</div>
|
|
7187
9258
|
<div class="adjourned-bar-actions">
|
|
7188
9259
|
<a href="#" class="ghost-btn" data-room-export>[↓] Export</a>
|
|
9260
|
+
<a href="#" class="ghost-btn" data-room-followup>[→] Convene Follow-up</a>
|
|
7189
9261
|
</div>
|
|
7190
9262
|
</footer>
|
|
7191
9263
|
</div>
|
|
@@ -7203,8 +9275,30 @@
|
|
|
7203
9275
|
<div class="reports-page" data-reports-page></div>
|
|
7204
9276
|
</div>
|
|
7205
9277
|
|
|
9278
|
+
<!-- All Notes view · cross-room chairman's-notes index. Vertical
|
|
9279
|
+
timeline grouped by date (Today / This Week / Earlier).
|
|
9280
|
+
Filled by app.renderNotesPage() on demand. -->
|
|
9281
|
+
<div class="main-view" data-main-view="notes" hidden>
|
|
9282
|
+
<div class="notes-page" data-notes-page></div>
|
|
9283
|
+
</div>
|
|
9284
|
+
|
|
7206
9285
|
</main>
|
|
7207
9286
|
|
|
9287
|
+
<!-- Floating expand affordance · only visible when the sidebar is
|
|
9288
|
+
collapsed (body.sidebar-collapsed). Lives INSIDE .body-grid
|
|
9289
|
+
(which is position: relative) so the button's absolute
|
|
9290
|
+
positioning anchors to the body-grid's top-left edge — i.e.
|
|
9291
|
+
below the topbar / brand logo, where the sidebar's own header
|
|
9292
|
+
sat before collapse. Frosted black glass with a 3-line
|
|
9293
|
+
hamburger glyph. -->
|
|
9294
|
+
<button type="button" class="sidebar-expand-btn" data-sidebar-expand aria-label="Expand sidebar" title="Expand sidebar">
|
|
9295
|
+
<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
|
|
9296
|
+
<line x1="2.5" y1="4.25" x2="13.5" y2="4.25"/>
|
|
9297
|
+
<line x1="2.5" y1="8" x2="13.5" y2="8"/>
|
|
9298
|
+
<line x1="2.5" y1="11.75" x2="13.5" y2="11.75"/>
|
|
9299
|
+
</svg>
|
|
9300
|
+
</button>
|
|
9301
|
+
|
|
7208
9302
|
</div>
|
|
7209
9303
|
|
|
7210
9304
|
</div>
|
|
@@ -7274,6 +9368,45 @@
|
|
|
7274
9368
|
}
|
|
7275
9369
|
|
|
7276
9370
|
document.querySelectorAll("[data-resize]").forEach(attach);
|
|
9371
|
+
|
|
9372
|
+
// ─── Sidebar collapse / expand ───
|
|
9373
|
+
// Three affordances, all event-delegated on document:
|
|
9374
|
+
// · `data-sidebar-collapse` inside the sidebar head ·
|
|
9375
|
+
// visible only while the sidebar is open · collapses
|
|
9376
|
+
// · `data-sidebar-expand` inside `.room-head` (rendered by
|
|
9377
|
+
// renderHeader) · visible when collapsed AND a room is
|
|
9378
|
+
// loaded · sits at the leading edge of the room title bar
|
|
9379
|
+
// · `data-sidebar-expand` floating button at top-left of
|
|
9380
|
+
// `.body-grid` · visible only when collapsed AND no room
|
|
9381
|
+
// is loaded (empty / no-room state, where there's no
|
|
9382
|
+
// room-head to host the in-header button)
|
|
9383
|
+
// All flip `body.sidebar-collapsed` and persist to localStorage.
|
|
9384
|
+
const COLLAPSE_KEY = "boardroom.sidebar.collapsed";
|
|
9385
|
+
function applySidebarCollapsed(collapsed) {
|
|
9386
|
+
document.body.classList.toggle("sidebar-collapsed", collapsed);
|
|
9387
|
+
}
|
|
9388
|
+
function readSidebarCollapsed() {
|
|
9389
|
+
try { return localStorage.getItem(COLLAPSE_KEY) === "1"; }
|
|
9390
|
+
catch { return false; }
|
|
9391
|
+
}
|
|
9392
|
+
function writeSidebarCollapsed(v) {
|
|
9393
|
+
try { localStorage.setItem(COLLAPSE_KEY, v ? "1" : "0"); } catch {}
|
|
9394
|
+
}
|
|
9395
|
+
applySidebarCollapsed(readSidebarCollapsed());
|
|
9396
|
+
document.addEventListener("click", (e) => {
|
|
9397
|
+
if (e.target.closest("[data-sidebar-collapse]")) {
|
|
9398
|
+
e.preventDefault();
|
|
9399
|
+
applySidebarCollapsed(true);
|
|
9400
|
+
writeSidebarCollapsed(true);
|
|
9401
|
+
return;
|
|
9402
|
+
}
|
|
9403
|
+
if (e.target.closest("[data-sidebar-expand]")) {
|
|
9404
|
+
e.preventDefault();
|
|
9405
|
+
applySidebarCollapsed(false);
|
|
9406
|
+
writeSidebarCollapsed(false);
|
|
9407
|
+
return;
|
|
9408
|
+
}
|
|
9409
|
+
});
|
|
7277
9410
|
})();
|
|
7278
9411
|
|
|
7279
9412
|
/* ─── Speaking queue: collapse / expand persistence ───
|
|
@@ -7381,11 +9514,28 @@
|
|
|
7381
9514
|
const TAB_KEY = "boardroom.sidebar.tab";
|
|
7382
9515
|
const ROOMS_KEY = "boardroom.sidebar.rooms";
|
|
7383
9516
|
const AGENTS_KEY = "boardroom.sidebar.agents";
|
|
9517
|
+
/* Mirror of the last actually-opened roomId. Diverges from ROOMS_KEY
|
|
9518
|
+
when the user lands on +New Room / All Reports / All Notes — those
|
|
9519
|
+
overwrite ROOMS_KEY with "new" / "reports" / "notes" but should not
|
|
9520
|
+
erase the memory of which ROOM the user had open. Used as a tab-
|
|
9521
|
+
switch fallback so re-entering the Rooms tab restores a meaningful
|
|
9522
|
+
selection rather than blanking it. */
|
|
9523
|
+
const ROOMS_LAST_KEY = "boardroom.sidebar.rooms.last";
|
|
7384
9524
|
const VALID_TABS = new Set(["rooms", "agents"]);
|
|
7385
9525
|
|
|
7386
9526
|
const lsGet = (k) => { try { return localStorage.getItem(k); } catch (_) { return null; } };
|
|
7387
9527
|
const lsSet = (k, v) => { try { localStorage.setItem(k, v); } catch (_) {} };
|
|
7388
9528
|
|
|
9529
|
+
/* Fall back to the first session row in the sidebar when we have
|
|
9530
|
+
no explicit user choice. Ordering matches DOM order (live first,
|
|
9531
|
+
then paused, then adjourned), which is the same ordering the user
|
|
9532
|
+
sees — picking [0] always means "the one at the top." Returns null
|
|
9533
|
+
when the sidebar has no rooms at all. */
|
|
9534
|
+
function firstRoomId() {
|
|
9535
|
+
const row = document.querySelector(".session-row-shell[data-room-id]");
|
|
9536
|
+
return row ? row.dataset.roomId : null;
|
|
9537
|
+
}
|
|
9538
|
+
|
|
7389
9539
|
function applyRoomsSubState(sub) {
|
|
7390
9540
|
// Make sure the room main view is visible (in case the agent
|
|
7391
9541
|
// profile main view was up). setComposerMode also does this when
|
|
@@ -7401,6 +9551,13 @@
|
|
|
7401
9551
|
}
|
|
7402
9552
|
return;
|
|
7403
9553
|
}
|
|
9554
|
+
// "notes" → cross-room chairman's-notes index.
|
|
9555
|
+
if (sub === "notes") {
|
|
9556
|
+
if (window.app && typeof window.app.openAllNotes === "function") {
|
|
9557
|
+
window.app.openAllNotes();
|
|
9558
|
+
}
|
|
9559
|
+
return;
|
|
9560
|
+
}
|
|
7404
9561
|
if (!sub || sub === "new") {
|
|
7405
9562
|
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
7406
9563
|
window.app.setComposerMode("room");
|
|
@@ -7408,20 +9565,40 @@
|
|
|
7408
9565
|
return;
|
|
7409
9566
|
}
|
|
7410
9567
|
// Navigate to the saved room — but only if it still exists in
|
|
7411
|
-
// the sidebar list.
|
|
7412
|
-
|
|
9568
|
+
// the sidebar list. A stale id (deleted room, never-existed)
|
|
9569
|
+
// falls back to the most-recently-viewed room, then the first
|
|
9570
|
+
// row, before resorting to the composer. Without the fallback,
|
|
9571
|
+
// a deleted-room id would land the user on a blank composer
|
|
9572
|
+
// every tab-switch even when other rooms are available.
|
|
9573
|
+
let exists = !!document.querySelector(`.session-row-shell[data-room-id="${sub}"]`);
|
|
7413
9574
|
if (!exists) {
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
9575
|
+
const last = lsGet(ROOMS_LAST_KEY);
|
|
9576
|
+
const candidate = (last && document.querySelector(`.session-row-shell[data-room-id="${last}"]`))
|
|
9577
|
+
? last
|
|
9578
|
+
: firstRoomId();
|
|
9579
|
+
if (candidate) {
|
|
9580
|
+
sub = candidate;
|
|
9581
|
+
lsSet(ROOMS_KEY, candidate);
|
|
9582
|
+
exists = true;
|
|
9583
|
+
} else {
|
|
9584
|
+
lsSet(ROOMS_KEY, "new");
|
|
9585
|
+
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
9586
|
+
window.app.setComposerMode("room");
|
|
9587
|
+
}
|
|
9588
|
+
return;
|
|
7417
9589
|
}
|
|
7418
|
-
return;
|
|
7419
9590
|
}
|
|
7420
9591
|
const want = "#/r/" + sub;
|
|
7421
9592
|
if (location.hash !== want) {
|
|
7422
9593
|
location.hash = want;
|
|
7423
9594
|
} else if (window.app && window.app.currentRoomId !== sub && typeof window.app.openRoom === "function") {
|
|
7424
9595
|
window.app.openRoom(sub);
|
|
9596
|
+
} else if (window.app && typeof window.app.markActiveRoom === "function") {
|
|
9597
|
+
// Both hash and currentRoomId already match — but the sidebar
|
|
9598
|
+
// session-row .active highlight may have been cleared by an
|
|
9599
|
+
// intervening agent-profile open (markActiveAgent wipes it).
|
|
9600
|
+
// Re-apply so the row reads as selected on tab return.
|
|
9601
|
+
window.app.markActiveRoom(sub);
|
|
7425
9602
|
}
|
|
7426
9603
|
}
|
|
7427
9604
|
|
|
@@ -7446,6 +9623,7 @@
|
|
|
7446
9623
|
function activate(which, opts) {
|
|
7447
9624
|
if (!VALID_TABS.has(which)) return;
|
|
7448
9625
|
const persist = !opts || opts.persist !== false;
|
|
9626
|
+
const fromTabClick = !!(opts && opts.fromTabClick);
|
|
7449
9627
|
document.querySelectorAll(".sidebar-tab[data-sidebar-tab]").forEach((t) => {
|
|
7450
9628
|
const on = t.dataset.sidebarTab === which;
|
|
7451
9629
|
t.classList.toggle("active", on);
|
|
@@ -7457,7 +9635,41 @@
|
|
|
7457
9635
|
else p.setAttribute("hidden", "");
|
|
7458
9636
|
});
|
|
7459
9637
|
|
|
7460
|
-
if (which === "rooms")
|
|
9638
|
+
if (which === "rooms") {
|
|
9639
|
+
// Resolve which sub-state to apply. The room id ROOMS_KEY
|
|
9640
|
+
// points to is the user's explicit choice when set; on
|
|
9641
|
+
// user-driven tab switches we additionally prefer the last
|
|
9642
|
+
// actually-opened room over a blank "new"-composer landing,
|
|
9643
|
+
// so re-entering the Rooms tab feels continuous (selection
|
|
9644
|
+
// restored) instead of resetting. Initial-load (refresh /
|
|
9645
|
+
// boot) keeps the legacy "lands on composer" intent so a
|
|
9646
|
+
// fresh user without prior history sees the new-room invite.
|
|
9647
|
+
const saved = lsGet(ROOMS_KEY);
|
|
9648
|
+
let target;
|
|
9649
|
+
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes") {
|
|
9650
|
+
target = saved; // explicit room id
|
|
9651
|
+
} else if (saved === "reports" || saved === "notes") {
|
|
9652
|
+
target = saved; // cross-room destinations preserve on any nav
|
|
9653
|
+
} else if (fromTabClick) {
|
|
9654
|
+
// User-driven tab switch · prefer the last actually-opened
|
|
9655
|
+
// room (or first row) over the +New Room composer, so
|
|
9656
|
+
// re-entering the Rooms tab feels continuous instead of
|
|
9657
|
+
// resetting to the composer. The "new" intent only persists
|
|
9658
|
+
// within the rooms tab — once the user leaves and returns,
|
|
9659
|
+
// we surface their last room.
|
|
9660
|
+
const last = lsGet(ROOMS_LAST_KEY);
|
|
9661
|
+
if (last && document.querySelector(`.session-row-shell[data-room-id="${last}"]`)) {
|
|
9662
|
+
target = last;
|
|
9663
|
+
} else {
|
|
9664
|
+
target = firstRoomId() || "new";
|
|
9665
|
+
}
|
|
9666
|
+
} else if (saved === "new") {
|
|
9667
|
+
target = "new"; // initial load · preserve composer intent
|
|
9668
|
+
} else {
|
|
9669
|
+
target = "new"; // initial load with no history → composer
|
|
9670
|
+
}
|
|
9671
|
+
applyRoomsSubState(target);
|
|
9672
|
+
}
|
|
7461
9673
|
else if (which === "agents") applyAgentsSubState(lsGet(AGENTS_KEY) || "new");
|
|
7462
9674
|
|
|
7463
9675
|
if (persist) lsSet(TAB_KEY, which);
|
|
@@ -7489,7 +9701,7 @@
|
|
|
7489
9701
|
const tab = e.target.closest(".sidebar-tab[data-sidebar-tab]");
|
|
7490
9702
|
if (tab) {
|
|
7491
9703
|
e.preventDefault();
|
|
7492
|
-
activate(tab.dataset.sidebarTab);
|
|
9704
|
+
activate(tab.dataset.sidebarTab, { fromTabClick: true });
|
|
7493
9705
|
return;
|
|
7494
9706
|
}
|
|
7495
9707
|
// Track sub-state · "+ New room" → rooms = "new"
|
|
@@ -7508,10 +9720,47 @@
|
|
|
7508
9720
|
}
|
|
7509
9721
|
return;
|
|
7510
9722
|
}
|
|
7511
|
-
//
|
|
9723
|
+
// "All Notes" trigger · cross-room chairman's-notes index. Same
|
|
9724
|
+
// sub-state pattern as reports — special token "notes" so the
|
|
9725
|
+
// sidebar persists which destination was last visited.
|
|
9726
|
+
const notesBtn = e.target.closest("[data-notes-trigger]");
|
|
9727
|
+
if (notesBtn) {
|
|
9728
|
+
e.preventDefault();
|
|
9729
|
+
lsSet(ROOMS_KEY, "notes");
|
|
9730
|
+
if (window.app && typeof window.app.openAllNotes === "function") {
|
|
9731
|
+
window.app.openAllNotes();
|
|
9732
|
+
}
|
|
9733
|
+
return;
|
|
9734
|
+
}
|
|
9735
|
+
// Track sub-state · clicked a session row → rooms = roomId.
|
|
9736
|
+
// Also stamp ROOMS_LAST_KEY so a later +New Room / All Reports
|
|
9737
|
+
// / All Notes click that overwrites ROOMS_KEY doesn't erase the
|
|
9738
|
+
// memory of which actual room was last open.
|
|
7512
9739
|
const sessRow = e.target.closest(".session-row-shell[data-room-id]");
|
|
7513
9740
|
if (sessRow && sessRow.dataset.roomId) {
|
|
7514
|
-
|
|
9741
|
+
const id = sessRow.dataset.roomId;
|
|
9742
|
+
lsSet(ROOMS_KEY, id);
|
|
9743
|
+
lsSet(ROOMS_LAST_KEY, id);
|
|
9744
|
+
// Anchor `href="#/r/<id>"` clicks rely on `hashchange` →
|
|
9745
|
+
// handleRoute → openRoom. When the hash already equals the
|
|
9746
|
+
// target (e.g. user just left an agent profile that didn't
|
|
9747
|
+
// change the hash, then clicks the same room), the browser
|
|
9748
|
+
// suppresses hashchange and openRoom never runs — the row
|
|
9749
|
+
// stays unselected. Re-trigger the room load + highlight
|
|
9750
|
+
// manually for the same-hash case.
|
|
9751
|
+
const want = "#/r/" + id;
|
|
9752
|
+
if (location.hash === want && window.app) {
|
|
9753
|
+
if (window.app.currentRoomId !== id && typeof window.app.openRoom === "function") {
|
|
9754
|
+
window.app.openRoom(id);
|
|
9755
|
+
} else {
|
|
9756
|
+
if (typeof window.closeAgentProfile === "function") {
|
|
9757
|
+
try { window.closeAgentProfile(); } catch (_) {}
|
|
9758
|
+
}
|
|
9759
|
+
if (typeof window.app.markActiveRoom === "function") {
|
|
9760
|
+
window.app.markActiveRoom(id);
|
|
9761
|
+
}
|
|
9762
|
+
}
|
|
9763
|
+
}
|
|
7515
9764
|
return;
|
|
7516
9765
|
}
|
|
7517
9766
|
// Track sub-state · "+ New agent" → agents = "new"
|
|
@@ -7522,10 +9771,15 @@
|
|
|
7522
9771
|
});
|
|
7523
9772
|
|
|
7524
9773
|
/* ─── hashchange · keep rooms sub-state synced when navigation
|
|
7525
|
-
happens via URL bar / browser history.
|
|
9774
|
+
happens via URL bar / browser history. ROOMS_LAST_KEY mirrors
|
|
9775
|
+
the same id so it survives later +New Room / All Reports
|
|
9776
|
+
overwrites of ROOMS_KEY. */
|
|
7526
9777
|
window.addEventListener("hashchange", () => {
|
|
7527
9778
|
const m = (location.hash || "").match(/^#\/r\/([a-z0-9]+)/i);
|
|
7528
|
-
if (m && m[1])
|
|
9779
|
+
if (m && m[1]) {
|
|
9780
|
+
lsSet(ROOMS_KEY, m[1]);
|
|
9781
|
+
lsSet(ROOMS_LAST_KEY, m[1]);
|
|
9782
|
+
}
|
|
7529
9783
|
});
|
|
7530
9784
|
|
|
7531
9785
|
/* Wrap window.openAgentProfile so EVERY open call (sidebar click,
|
|
@@ -7561,6 +9815,7 @@
|
|
|
7561
9815
|
const m = (location.hash || "").match(/^#\/r\/([a-z0-9]+)/i);
|
|
7562
9816
|
if (m && m[1]) {
|
|
7563
9817
|
lsSet(ROOMS_KEY, m[1]);
|
|
9818
|
+
lsSet(ROOMS_LAST_KEY, m[1]);
|
|
7564
9819
|
activate("rooms", { persist: false });
|
|
7565
9820
|
return;
|
|
7566
9821
|
}
|