privateboard 0.1.32 → 0.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot.js +482 -208
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +482 -208
- package/dist/cli.js.map +1 -1
- package/dist/server.js +482 -208
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -2
- package/public/adjourn-overlay.css +8 -0
- package/public/agent-profile.css +46 -0
- package/public/agent-profile.js +247 -48
- package/public/app.js +799 -717
- package/public/avatars/chair-blink.svg +1 -0
- package/public/home-3d-loader.js +6 -0
- package/public/home.html +1 -1
- package/public/i18n.js +122 -0
- package/public/icons/folded-sidebar.png +0 -0
- package/public/index.html +898 -990
- package/public/report.html +27 -7
- package/public/room-settings.css +18 -0
- package/public/themes.css +11 -0
- package/public/user-settings.js +37 -20
- package/public/voice-3d-banner.js +110 -36
- package/public/voice-3d.js +206 -6
- package/public/icons/share.png +0 -0
package/public/index.html
CHANGED
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
--panel-2: #1A1A18;
|
|
29
29
|
--panel-3: #21211E;
|
|
30
30
|
--hi: #2A2A26;
|
|
31
|
+
/* Sidebar chrome surface · used by `.sidebar`, `.sessions-scroll`,
|
|
32
|
+
`.agents-scroll`, the sticky `.section-header`, and the
|
|
33
|
+
`.sidebar-foot::before` scroll-fade. One token so the whole
|
|
34
|
+
sidebar column moves together. */
|
|
35
|
+
--sidebar-bg: #191918;
|
|
31
36
|
|
|
32
37
|
--line: #26241F;
|
|
33
38
|
--line-bright: #3A3934;
|
|
@@ -64,6 +69,9 @@
|
|
|
64
69
|
system-ui, sans-serif;
|
|
65
70
|
|
|
66
71
|
--sidebar-w: 280px;
|
|
72
|
+
/* Collapsed icon-rail width · matches the ChatGPT folded sidebar.
|
|
73
|
+
Wide enough for a centered 32px hit target with breathing room. */
|
|
74
|
+
--mini-sidebar-w: 56px;
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
/* Logo stays on Inter so the brandmark reads consistently
|
|
@@ -484,7 +492,12 @@
|
|
|
484
492
|
SIDEBAR
|
|
485
493
|
═══════════════════════════════════════════ */
|
|
486
494
|
.sidebar {
|
|
487
|
-
|
|
495
|
+
/* Sidebar runs on its own token `--sidebar-bg` (#191918) so the
|
|
496
|
+
column can be tuned independently of `--panel` / `--panel-2`.
|
|
497
|
+
Every inner surface that needs to match (`.sessions-scroll`,
|
|
498
|
+
`.agents-scroll`, sticky `.section-header`, `.sidebar-foot`
|
|
499
|
+
fade) reads the same token. */
|
|
500
|
+
background: var(--sidebar-bg);
|
|
488
501
|
/* Right edge owns the region divider · brighter than the other
|
|
489
502
|
three sides (--line-bright vs --line) so the sidebar reads as
|
|
490
503
|
visually distinct from the main column. Pairs with
|
|
@@ -505,12 +518,10 @@
|
|
|
505
518
|
the 22px collapse-button's box; after the button was shrunk to a
|
|
506
519
|
16px icon, the row would collapse 6px shorter without this lock. */
|
|
507
520
|
min-height: 38px;
|
|
508
|
-
border-bottom: 0.5px solid var(--line-bright);
|
|
509
521
|
display: flex;
|
|
510
522
|
justify-content: space-between;
|
|
511
523
|
align-items: center;
|
|
512
524
|
gap: 10px;
|
|
513
|
-
background: var(--panel-2);
|
|
514
525
|
}
|
|
515
526
|
/* Lock-up · brand mark + wordmark in the slot the prior
|
|
516
527
|
"// CONTROL" title held. Anchored as a link so a click on the
|
|
@@ -662,14 +673,6 @@
|
|
|
662
673
|
html.is-electron-mac .room-head .head-cast img {
|
|
663
674
|
-webkit-app-region: no-drag;
|
|
664
675
|
}
|
|
665
|
-
/* The expand-btn's ::after halo paints OUTSIDE the button rect
|
|
666
|
-
(inset: -16px), so it doesn't inherit the button's no-drag and
|
|
667
|
-
gets eaten by `.room-head { drag }` above. Stamp no-drag on the
|
|
668
|
-
pseudo directly so the 48×48 hit zone keeps registering clicks.
|
|
669
|
-
(Same shape as the sidebar-collapse-btn::after override.) */
|
|
670
|
-
html.is-electron-mac .room-head .room-head-expand::after {
|
|
671
|
-
-webkit-app-region: no-drag;
|
|
672
|
-
}
|
|
673
676
|
/* ─── macOS Electron · top-strip drag coverage for non-room views.
|
|
674
677
|
When the room view is hidden (All Reports / All Notes / Search /
|
|
675
678
|
Agent Profile) or the room view is shown with no current room (the
|
|
@@ -687,21 +690,16 @@
|
|
|
687
690
|
agent-profile card (agent-profile.js). No interactive descendants,
|
|
688
691
|
so the no-drag exception below mostly no-ops for it.
|
|
689
692
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
`.search-page` gets matching top padding (only in electron) so
|
|
694
|
-
content sits below the strip instead of underneath it. */
|
|
693
|
+
(Search no longer has a main-view of its own — it lives as a
|
|
694
|
+
floating overlay since the v0.1.33 redesign — so no drag-strip
|
|
695
|
+
equivalent is needed.) */
|
|
695
696
|
/* `position: absolute` so the 36px drag strip floats above
|
|
696
|
-
`.chat-col` without taking a grid-row of vertical space.
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
fall through to the composer below — the drag region itself
|
|
703
|
-
activates via `-webkit-app-region: drag` which Electron handles
|
|
704
|
-
before the renderer's hit-testing. */
|
|
697
|
+
`.chat-col` without taking a grid-row of vertical space. The
|
|
698
|
+
strip stays transparent (`background: transparent`) and
|
|
699
|
+
`pointer-events: none` lets clicks fall through to the composer
|
|
700
|
+
below — the drag region itself activates via
|
|
701
|
+
`-webkit-app-region: drag` which Electron handles before the
|
|
702
|
+
renderer's hit-testing. */
|
|
705
703
|
html.is-electron-mac.no-room .room-head:empty {
|
|
706
704
|
display: block;
|
|
707
705
|
position: absolute;
|
|
@@ -739,24 +737,6 @@
|
|
|
739
737
|
html.is-electron-mac .notes-page-head [contenteditable="true"] {
|
|
740
738
|
-webkit-app-region: no-drag;
|
|
741
739
|
}
|
|
742
|
-
html.is-electron-mac .main-view[data-main-view="search"] {
|
|
743
|
-
position: relative;
|
|
744
|
-
}
|
|
745
|
-
html.is-electron-mac .main-view[data-main-view="search"]::before {
|
|
746
|
-
content: "";
|
|
747
|
-
position: sticky;
|
|
748
|
-
top: 0;
|
|
749
|
-
display: block;
|
|
750
|
-
width: 100%;
|
|
751
|
-
height: 36px;
|
|
752
|
-
margin-bottom: -36px;
|
|
753
|
-
background: transparent;
|
|
754
|
-
-webkit-app-region: drag;
|
|
755
|
-
z-index: 50;
|
|
756
|
-
}
|
|
757
|
-
html.is-electron-mac .main-view[data-main-view="search"] .search-page {
|
|
758
|
-
padding-top: 36px;
|
|
759
|
-
}
|
|
760
740
|
/* When the sidebar is floating as a peek overlay, every drag region
|
|
761
741
|
declared above for the main-view's top strip MUST be suppressed.
|
|
762
742
|
drag regions are computed as bounding rectangles regardless of
|
|
@@ -792,9 +772,6 @@
|
|
|
792
772
|
html.is-electron-mac body.sidebar-peek .room-head:empty {
|
|
793
773
|
-webkit-app-region: no-drag;
|
|
794
774
|
}
|
|
795
|
-
html.is-electron-mac body.sidebar-peek .main-view[data-main-view="search"]::before {
|
|
796
|
-
-webkit-app-region: no-drag;
|
|
797
|
-
}
|
|
798
775
|
/* macOS Electron · round the four outer corners of the entire
|
|
799
776
|
`.body-grid` (sidebar + 8px resizer + main as one rectangle), not
|
|
800
777
|
each panel individually. The grid already has `overflow: hidden`
|
|
@@ -819,6 +796,22 @@
|
|
|
819
796
|
`enter-full-screen` / `leave-full-screen` events. */
|
|
820
797
|
html.is-fullscreen .control { padding: 0; }
|
|
821
798
|
html.is-fullscreen .body-grid { border-radius: 0; }
|
|
799
|
+
/* macOS fullscreen safe-area · the system menu bar (incl. the
|
|
800
|
+
traffic-light cluster + the macOS screen-recording indicator)
|
|
801
|
+
slides down over the top ~28 px of the window when the cursor
|
|
802
|
+
enters that zone. In normal windowed mode the menu bar lives
|
|
803
|
+
above the window and isn't a concern, but in fullscreen our
|
|
804
|
+
content extends edge-to-edge so anything we paint in the top
|
|
805
|
+
28 px gets obscured the moment the user hovers up to access
|
|
806
|
+
traffic lights — and the things they're trying to access
|
|
807
|
+
(window close / minimize, the head-record button at the
|
|
808
|
+
right) sit precisely there. Push the .room-head contents
|
|
809
|
+
down by 28 px in fullscreen so the action buttons clear the
|
|
810
|
+
reveal zone. The .room-head itself still starts at top: 0
|
|
811
|
+
(drag region for window-drag), only its inner content shifts. */
|
|
812
|
+
html.is-fullscreen.is-electron-mac .room-head {
|
|
813
|
+
padding-top: 28px;
|
|
814
|
+
}
|
|
822
815
|
/* macOS Electron · let the BrowserWindow's `vibrancy: "under-window"`
|
|
823
816
|
layer show through. The vibrancy lives behind the renderer, so
|
|
824
817
|
anything painted with a fully opaque background covers it. We
|
|
@@ -841,22 +834,28 @@
|
|
|
841
834
|
room. Single column = main fills the whole body-grid cleanly.
|
|
842
835
|
The user's preferred --sidebar-w stays in localStorage so
|
|
843
836
|
expanding restores the same width. */
|
|
837
|
+
/* Collapsed · the full sidebar + resizer drop out and a fixed-width
|
|
838
|
+
mini icon rail takes column 1. Source order [sidebar, resizer,
|
|
839
|
+
mini, main] + both hidden siblings → grid auto-places mini in
|
|
840
|
+
col 1, main in col 2. */
|
|
844
841
|
body.sidebar-collapsed .body-grid {
|
|
845
|
-
grid-template-columns: 1fr !important;
|
|
842
|
+
grid-template-columns: var(--mini-sidebar-w) 1fr !important;
|
|
846
843
|
}
|
|
847
844
|
body.sidebar-collapsed .sidebar,
|
|
848
845
|
body.sidebar-collapsed .col-resizer {
|
|
849
846
|
display: none;
|
|
850
847
|
}
|
|
848
|
+
body.sidebar-collapsed .mini-sidebar {
|
|
849
|
+
display: flex;
|
|
850
|
+
}
|
|
851
851
|
/* ─── Hover peek · floating sidebar while collapsed ───
|
|
852
|
-
When the
|
|
853
|
-
`.sidebar-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
never reclaims its grid track. */
|
|
852
|
+
When the cursor reaches the viewport's left edge (over the mini
|
|
853
|
+
rail), JS adds `.sidebar-peek` to body. The full sidebar reappears
|
|
854
|
+
as a floating overlay anchored to the body-grid's top-left edge so
|
|
855
|
+
it sits ABOVE the chat content (and the mini rail) without
|
|
856
|
+
re-flowing the grid. Mouseleave drops the class and the float
|
|
857
|
+
disappears again, leaving the mini rail. This is purely an addition
|
|
858
|
+
to the collapsed state — the column never reclaims its grid track. */
|
|
860
859
|
body.sidebar-collapsed.sidebar-peek .sidebar {
|
|
861
860
|
display: flex;
|
|
862
861
|
position: absolute;
|
|
@@ -892,10 +891,10 @@
|
|
|
892
891
|
glass with hairline border, lime on hover. z-index sits above
|
|
893
892
|
chat content but below modal overlays (which use 1500+). */
|
|
894
893
|
/* Round chip · fold glyph centred in a 28px circle. `--bg` fill
|
|
895
|
-
(white on light, near-black on dark)
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
894
|
+
(white on light, near-black on dark) sits against the composer
|
|
895
|
+
panel; `--lime-dim` ring anchors it to the product's accent.
|
|
896
|
+
Hover intensifies both ring + icon to full `--lime` for an
|
|
897
|
+
obvious interactive state. */
|
|
899
898
|
.sidebar-expand-btn {
|
|
900
899
|
display: none;
|
|
901
900
|
position: absolute;
|
|
@@ -915,12 +914,14 @@
|
|
|
915
914
|
appearance: none;
|
|
916
915
|
padding: 0;
|
|
917
916
|
}
|
|
918
|
-
/*
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
917
|
+
/* Retired · the persistent mini icon rail now owns the collapsed
|
|
918
|
+
state, and its logo button expands. The old floating round chip
|
|
919
|
+
would sit on top of the rail's top icon (both pin to the body-grid
|
|
920
|
+
top-left), so it stays hidden in every state. Kept in the DOM (and
|
|
921
|
+
this rule kept as `display: none`) so the edge-peek mousemove guard
|
|
922
|
+
that references `[data-sidebar-expand]` still resolves cleanly. */
|
|
922
923
|
html.no-room body.sidebar-collapsed .sidebar-expand-btn {
|
|
923
|
-
display:
|
|
924
|
+
display: none;
|
|
924
925
|
}
|
|
925
926
|
.sidebar-expand-btn:hover {
|
|
926
927
|
color: var(--lime);
|
|
@@ -963,110 +964,310 @@
|
|
|
963
964
|
-webkit-app-region: no-drag;
|
|
964
965
|
}
|
|
965
966
|
|
|
966
|
-
/* ───
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
967
|
+
/* ─── Mini (collapsed) sidebar · ChatGPT-style icon rail ───
|
|
968
|
+
A persistent narrow rail shown while collapsed. `display: none`
|
|
969
|
+
by default; flipped to `flex` by the collapsed-state rule above.
|
|
970
|
+
Shares the sidebar chrome surface (`--sidebar-bg`) + a hairline
|
|
971
|
+
right edge so it reads as the same column the full sidebar used. */
|
|
972
|
+
.mini-sidebar {
|
|
973
|
+
display: none;
|
|
974
|
+
flex-direction: column;
|
|
975
|
+
align-items: center;
|
|
976
|
+
width: var(--mini-sidebar-w);
|
|
977
|
+
height: 100%;
|
|
978
|
+
min-height: 0;
|
|
979
|
+
background: var(--sidebar-bg);
|
|
980
|
+
border-right: 1px solid var(--line);
|
|
981
|
+
padding: 10px 0;
|
|
982
|
+
/* `visible` (not `hidden`) so the per-icon hover tooltips can
|
|
983
|
+
escape the 56px rail and pop out to the right. The icon set is
|
|
984
|
+
short enough that it never overflows the rail vertically, so
|
|
985
|
+
there's nothing to clip anyway. The enclosing `.body-grid`
|
|
986
|
+
still bounds everything. */
|
|
987
|
+
overflow: visible;
|
|
988
|
+
}
|
|
989
|
+
.mini-top {
|
|
990
|
+
display: flex;
|
|
991
|
+
flex-direction: column;
|
|
992
|
+
align-items: center;
|
|
993
|
+
gap: 4px;
|
|
994
|
+
flex: 1 1 auto;
|
|
995
|
+
min-height: 0;
|
|
996
|
+
width: 100%;
|
|
997
|
+
}
|
|
998
|
+
.mini-foot {
|
|
999
|
+
display: flex;
|
|
1000
|
+
flex-direction: column;
|
|
1001
|
+
align-items: center;
|
|
1002
|
+
flex: 0 0 auto;
|
|
1003
|
+
padding-top: 8px;
|
|
1004
|
+
}
|
|
1005
|
+
/* Icon button · 32×32 centered hit target. Stroke icons ride
|
|
1006
|
+
currentColor (text-soft idle → text on hover) via the shared
|
|
1007
|
+
mask-image vocabulary, matching the full sidebar's nav glyphs. */
|
|
1008
|
+
.mini-btn {
|
|
1009
|
+
position: relative;
|
|
1010
|
+
display: flex;
|
|
1011
|
+
align-items: center;
|
|
1012
|
+
justify-content: center;
|
|
1013
|
+
width: 32px;
|
|
1014
|
+
height: 32px;
|
|
1015
|
+
padding: 0;
|
|
1016
|
+
border: none;
|
|
978
1017
|
background: transparent;
|
|
979
|
-
border: 0;
|
|
980
1018
|
color: var(--text-soft);
|
|
1019
|
+
border-radius: 8px;
|
|
981
1020
|
cursor: pointer;
|
|
982
|
-
|
|
983
|
-
height: 16px;
|
|
984
|
-
padding: 0;
|
|
1021
|
+
text-decoration: none;
|
|
985
1022
|
flex-shrink: 0;
|
|
986
|
-
transition: color 0.12s;
|
|
987
|
-
|
|
988
|
-
the cursor doesn't have to land precisely on the 16px icon. */
|
|
989
|
-
position: relative;
|
|
990
|
-
display: none;
|
|
1023
|
+
transition: background 0.12s, color 0.12s;
|
|
1024
|
+
appearance: none;
|
|
991
1025
|
}
|
|
992
|
-
|
|
1026
|
+
/* Hover tooltip · CSS-only via ::after + data-tip, mirroring the
|
|
1027
|
+
`.ib-action::after` pattern. Pops to the RIGHT of the icon (the
|
|
1028
|
+
rail hugs the window's left edge, so above/left would clip) after
|
|
1029
|
+
~300ms dwell. data-tip is populated from data-i18n-tip by i18n.js,
|
|
1030
|
+
with a static English fallback for the pre-i18n frame. */
|
|
1031
|
+
.mini-btn[data-tip]::after {
|
|
1032
|
+
content: attr(data-tip);
|
|
1033
|
+
position: absolute;
|
|
1034
|
+
left: calc(100% + 10px);
|
|
1035
|
+
top: 50%;
|
|
1036
|
+
transform: translateY(-50%) translateX(-3px);
|
|
1037
|
+
background: var(--panel-2);
|
|
1038
|
+
border: 0.5px solid var(--line-strong);
|
|
1039
|
+
padding: 5px 9px;
|
|
1040
|
+
font-family: var(--mono);
|
|
1041
|
+
font-size: 10px;
|
|
1042
|
+
letter-spacing: 0.04em;
|
|
1043
|
+
color: var(--text);
|
|
1044
|
+
white-space: nowrap;
|
|
1045
|
+
pointer-events: none;
|
|
1046
|
+
opacity: 0;
|
|
1047
|
+
visibility: hidden;
|
|
1048
|
+
box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.55);
|
|
1049
|
+
transition: opacity 0.14s ease, transform 0.14s ease, visibility 0s linear 0.18s;
|
|
1050
|
+
z-index: 250;
|
|
1051
|
+
}
|
|
1052
|
+
.mini-btn[data-tip]:hover::after,
|
|
1053
|
+
.mini-btn[data-tip]:focus-visible::after {
|
|
1054
|
+
opacity: 1;
|
|
1055
|
+
visibility: visible;
|
|
1056
|
+
transform: translateY(-50%) translateX(0);
|
|
1057
|
+
transition: opacity 0.14s ease 0.3s, transform 0.14s ease 0.3s, visibility 0s linear 0.3s;
|
|
1058
|
+
}
|
|
1059
|
+
.mini-btn:hover {
|
|
1060
|
+
background: var(--panel-2);
|
|
1061
|
+
color: var(--text);
|
|
1062
|
+
}
|
|
1063
|
+
/* Selected state · the icon for the current destination (reports /
|
|
1064
|
+
notes / new-room / new-agent via shared trigger attrs, rooms /
|
|
1065
|
+
agents via setMiniRailContext). Settled `--panel-3` chip + full
|
|
1066
|
+
--text glyph, matching the full sidebar's `.new-btn.active`
|
|
1067
|
+
language (background-only, no accent — per project CSS rules).
|
|
1068
|
+
Placed after :hover so an active icon keeps its chip on hover
|
|
1069
|
+
instead of dropping to the lighter hover tone. */
|
|
1070
|
+
.mini-btn.active,
|
|
1071
|
+
.mini-btn.active:hover {
|
|
1072
|
+
background: var(--panel-3);
|
|
1073
|
+
color: var(--text);
|
|
1074
|
+
}
|
|
1075
|
+
/* Glyph · same 18px Lucide mask the full sidebar nav buttons use.
|
|
1076
|
+
`--icon` is supplied per-button by the shared rules below (data-
|
|
1077
|
+
attribute scoped so both the .new-btn and .mini-btn carriers pick
|
|
1078
|
+
it up from one definition). */
|
|
1079
|
+
.mini-btn::before {
|
|
993
1080
|
content: "";
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1081
|
+
width: 18px;
|
|
1082
|
+
height: 18px;
|
|
1083
|
+
flex-shrink: 0;
|
|
997
1084
|
background-color: currentColor;
|
|
998
|
-
-webkit-mask-image:
|
|
999
|
-
mask-image:
|
|
1085
|
+
-webkit-mask-image: var(--icon, none);
|
|
1086
|
+
mask-image: var(--icon, none);
|
|
1000
1087
|
-webkit-mask-repeat: no-repeat;
|
|
1001
1088
|
mask-repeat: no-repeat;
|
|
1002
1089
|
-webkit-mask-position: center;
|
|
1003
1090
|
mask-position: center;
|
|
1004
|
-
-webkit-mask-size:
|
|
1005
|
-
mask-size:
|
|
1006
|
-
|
|
1007
|
-
body.sidebar-collapsed · since this button is ONLY visible
|
|
1008
|
-
in the collapsed state, the flip is unconditional here. */
|
|
1009
|
-
transform: scaleX(-1);
|
|
1091
|
+
-webkit-mask-size: 18px 18px;
|
|
1092
|
+
mask-size: 18px 18px;
|
|
1093
|
+
transition: color 0.12s;
|
|
1010
1094
|
}
|
|
1011
|
-
/*
|
|
1012
|
-
|
|
1013
|
-
|
|
1095
|
+
/* Tab + user buttons render real children (.ic / avatar), not a mask
|
|
1096
|
+
glyph. Suppress the shared ::before — without an `--icon` its
|
|
1097
|
+
`currentColor` fill would paint a solid square block next to the
|
|
1098
|
+
real content. (The logo keeps its ::before for the hover fold-icon
|
|
1099
|
+
below.) */
|
|
1100
|
+
.mini-tab::before,
|
|
1101
|
+
.mini-user::before { content: none; }
|
|
1102
|
+
.mini-tab .ic { width: 18px; height: 18px; }
|
|
1103
|
+
/* Logo → fold glyph on hover · telegraphs "click to expand" using the
|
|
1104
|
+
same Lucide PanelLeft + 3-rows icon as the in-sidebar collapse
|
|
1105
|
+
button. At rest the brand mark shows; on hover it fades out and the
|
|
1106
|
+
fold glyph (overlaid, currentColor) fades in. The explicit fold
|
|
1107
|
+
mask here also overrides the generic .mini-btn::before, which —
|
|
1108
|
+
lacking an --icon — would otherwise paint a solid square. */
|
|
1109
|
+
.mini-logo { position: relative; }
|
|
1110
|
+
/* The chair avatar is a two-layer stack: open-eye frame always
|
|
1111
|
+
visible, closed-eye frame flashed in by the blink keyframe. The
|
|
1112
|
+
wrapper carries an occasional idle hop. */
|
|
1113
|
+
.mini-logo-av {
|
|
1114
|
+
position: relative;
|
|
1115
|
+
width: 26px;
|
|
1116
|
+
height: 26px;
|
|
1117
|
+
transition: opacity 0.12s;
|
|
1118
|
+
animation: chair-hop 7s ease-in-out infinite;
|
|
1119
|
+
}
|
|
1120
|
+
.mini-logo-av img {
|
|
1121
|
+
position: absolute;
|
|
1122
|
+
inset: 0;
|
|
1123
|
+
width: 100%;
|
|
1124
|
+
height: 100%;
|
|
1125
|
+
image-rendering: pixelated;
|
|
1126
|
+
image-rendering: crisp-edges;
|
|
1127
|
+
}
|
|
1128
|
+
/* Closed-eye frame · hidden at rest, briefly opaque on the blink
|
|
1129
|
+
keyframe. A different cycle length from the hop keeps the two from
|
|
1130
|
+
locking into a robotic sync. */
|
|
1131
|
+
.mini-logo-av .cl-blink {
|
|
1132
|
+
opacity: 0;
|
|
1133
|
+
animation: chair-blink 5.7s ease-in-out infinite;
|
|
1134
|
+
}
|
|
1135
|
+
.mini-logo:hover .mini-logo-av { opacity: 0; }
|
|
1136
|
+
/* Occasional double-bounce hop · still ~85% of the cycle, then a
|
|
1137
|
+
quick two-step hop so the chair reads as "alive" without being
|
|
1138
|
+
busy. */
|
|
1139
|
+
@keyframes chair-hop {
|
|
1140
|
+
0%, 60%, 100% { transform: translateY(0); }
|
|
1141
|
+
66% { transform: translateY(-4px); }
|
|
1142
|
+
72% { transform: translateY(0); }
|
|
1143
|
+
77% { transform: translateY(-2px); }
|
|
1144
|
+
82% { transform: translateY(0); }
|
|
1145
|
+
}
|
|
1146
|
+
/* Single crisp blink near the end of the cycle (~170ms closed). */
|
|
1147
|
+
@keyframes chair-blink {
|
|
1148
|
+
0%, 93.5%, 100% { opacity: 0; }
|
|
1149
|
+
94%, 96.5% { opacity: 1; }
|
|
1150
|
+
97% { opacity: 0; }
|
|
1151
|
+
}
|
|
1152
|
+
/* Respect reduced-motion · hold the open-eye frame, no hop / blink. */
|
|
1153
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1154
|
+
.mini-logo-av,
|
|
1155
|
+
.mini-logo-av .cl-blink { animation: none; }
|
|
1156
|
+
}
|
|
1157
|
+
.mini-logo::before {
|
|
1014
1158
|
content: "";
|
|
1015
1159
|
position: absolute;
|
|
1016
|
-
|
|
1160
|
+
top: 50%;
|
|
1161
|
+
left: 50%;
|
|
1162
|
+
/* scaleX(-1) mirrors the fold so the rows flip to the right edge —
|
|
1163
|
+
the same direction the in-sidebar collapse button uses in its
|
|
1164
|
+
collapsed state to telegraph "the panel expands out from here". */
|
|
1165
|
+
transform: translate(-50%, -50%) scaleX(-1);
|
|
1166
|
+
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
|
|
1167
|
+
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='3' width='18' height='18' rx='2'/><path d='M9 3v18'/><path d='M5 8h2'/><path d='M5 12h2'/><path d='M5 16h2'/></svg>");
|
|
1168
|
+
opacity: 0;
|
|
1169
|
+
transition: opacity 0.12s;
|
|
1017
1170
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
margin
|
|
1171
|
+
.mini-logo:hover::before { opacity: 1; }
|
|
1172
|
+
/* Divider between the action group and the rooms/agents switchers. */
|
|
1173
|
+
.mini-sep {
|
|
1174
|
+
width: 20px;
|
|
1175
|
+
height: 1px;
|
|
1176
|
+
background: var(--line-bright);
|
|
1177
|
+
margin: 6px 0;
|
|
1178
|
+
flex-shrink: 0;
|
|
1179
|
+
}
|
|
1180
|
+
.mini-user { width: 32px; height: 32px; }
|
|
1181
|
+
.mini-user .mini-user-av { width: 26px; height: 26px; }
|
|
1182
|
+
.mini-user:hover { background: var(--panel-2); }
|
|
1183
|
+
|
|
1184
|
+
/* macOS Electron · clicks must reach the rail's buttons (the window
|
|
1185
|
+
drag region otherwise swallows them), and the traffic-light cluster
|
|
1186
|
+
is hidden while collapsed (see syncElectronTrafficLights), so the
|
|
1187
|
+
logo can sit near the top with normal padding. */
|
|
1188
|
+
html.is-electron-mac .mini-sidebar {
|
|
1189
|
+
-webkit-app-region: no-drag;
|
|
1025
1190
|
}
|
|
1026
|
-
.room-head-expand:hover { color: var(--lime); }
|
|
1027
1191
|
|
|
1028
|
-
/* ─── Sidebar tabs (Rooms / Agents) —
|
|
1192
|
+
/* ─── Sidebar tabs (Rooms / Agents) — bar-style nav ───
|
|
1193
|
+
Reference: `public/icons/bar.png`. Two layers:
|
|
1194
|
+
1. Outer "bar" container · its OWN background, sits inset from
|
|
1195
|
+
the sidebar edges, with a small-radius rounded rectangle
|
|
1196
|
+
frame. NO inner padding — the active tab fills the bar
|
|
1197
|
+
edge-to-edge so the hairline border IS the divider, not an
|
|
1198
|
+
inset gap.
|
|
1199
|
+
2. ACTIVE tab · rounded rectangle, hairline border, brighter
|
|
1200
|
+
bg, shows icon + label. Height matches the container's so
|
|
1201
|
+
there's zero gap between them on top / bottom.
|
|
1202
|
+
3. INACTIVE tabs · icon-only, sit transparently on the bar with
|
|
1203
|
+
generous breathing room around the glyph. */
|
|
1029
1204
|
.sidebar-tabs {
|
|
1030
1205
|
display: flex;
|
|
1031
|
-
|
|
1032
|
-
|
|
1206
|
+
align-items: stretch; /* tabs fill container height edge-to-edge */
|
|
1207
|
+
/* No justify-content · `space-between` glued the inactive tab to
|
|
1208
|
+
the far right which doesn't match the reference. Instead the
|
|
1209
|
+
active tab takes its DOM position (left edge) and the inactive
|
|
1210
|
+
tab uses auto margins (below) to float centred in the leftover
|
|
1211
|
+
space — mirrors bar.png where the "Chat" pill hugs the left
|
|
1212
|
+
edge and the icon-only siblings sit in the bar's middle/right
|
|
1213
|
+
area with empty space on both sides. */
|
|
1214
|
+
margin: 5px 10px 10px;
|
|
1215
|
+
/* No inner padding · the active tab's hairline border is flush
|
|
1216
|
+
with the container's edge, matching the reference. */
|
|
1217
|
+
padding: 0;
|
|
1033
1218
|
flex-shrink: 0;
|
|
1034
|
-
background: var(--panel);
|
|
1219
|
+
background: var(--panel-2);
|
|
1220
|
+
border-radius: 10px;
|
|
1035
1221
|
}
|
|
1036
1222
|
.sidebar-tab {
|
|
1037
|
-
|
|
1038
|
-
|
|
1223
|
+
/* Equal-width slots · bar.png's three-item distribution adapted
|
|
1224
|
+
for our 2-tab case. Each tab occupies 50% of the container's
|
|
1225
|
+
inner width so the two halves stay visually balanced regardless
|
|
1226
|
+
of which side carries the pill chrome. Without this the active
|
|
1227
|
+
was content-sized + the inactive icon-sized → wildly mismatched
|
|
1228
|
+
slot widths that read as a layout bug. */
|
|
1229
|
+
flex: 1 1 0;
|
|
1230
|
+
padding: 0 14px;
|
|
1231
|
+
min-height: 30px;
|
|
1039
1232
|
display: inline-flex;
|
|
1040
1233
|
align-items: center;
|
|
1041
1234
|
justify-content: center;
|
|
1042
|
-
gap:
|
|
1235
|
+
gap: 7px;
|
|
1043
1236
|
font-family: var(--sans);
|
|
1044
|
-
font-size:
|
|
1237
|
+
font-size: 13px;
|
|
1045
1238
|
font-weight: 500;
|
|
1046
1239
|
color: var(--text-dim);
|
|
1047
1240
|
cursor: pointer;
|
|
1048
1241
|
text-decoration: none;
|
|
1049
|
-
transition: background 0.15s, color 0.15s;
|
|
1242
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
1050
1243
|
letter-spacing: -0.005em;
|
|
1051
|
-
border-radius:
|
|
1244
|
+
border-radius: 10px;
|
|
1245
|
+
/* Transparent placeholder border keeps inactive + active tabs the
|
|
1246
|
+
same outer height so toggling doesn't shift the row by 1px. */
|
|
1247
|
+
border: 1px solid transparent;
|
|
1248
|
+
background: transparent;
|
|
1052
1249
|
}
|
|
1053
1250
|
.sidebar-tab .ico {
|
|
1054
|
-
width:
|
|
1055
|
-
height:
|
|
1251
|
+
width: 15px;
|
|
1252
|
+
height: 15px;
|
|
1056
1253
|
flex-shrink: 0;
|
|
1057
1254
|
color: currentColor;
|
|
1058
|
-
opacity: 0.
|
|
1255
|
+
opacity: 0.9;
|
|
1059
1256
|
}
|
|
1257
|
+
/* Label hidden by default; revealed only on the active tab. Keeps the
|
|
1258
|
+
`<span data-i18n>` in the DOM for translation + screen readers. */
|
|
1259
|
+
.sidebar-tab > span { display: none; }
|
|
1060
1260
|
.sidebar-tab:hover {
|
|
1061
|
-
background: var(--panel-2);
|
|
1062
1261
|
color: var(--text-soft);
|
|
1063
1262
|
}
|
|
1064
1263
|
.sidebar-tab.active {
|
|
1065
|
-
background: var(--panel-3);
|
|
1066
1264
|
color: var(--text);
|
|
1067
1265
|
font-weight: 600;
|
|
1266
|
+
background: var(--panel-3);
|
|
1267
|
+
border-color: var(--line-bright);
|
|
1068
1268
|
}
|
|
1069
1269
|
.sidebar-tab.active .ico { opacity: 1; }
|
|
1270
|
+
.sidebar-tab.active > span { display: inline; }
|
|
1070
1271
|
|
|
1071
1272
|
/* Sidebar tab panels */
|
|
1072
1273
|
.sidebar-panel {
|
|
@@ -1086,28 +1287,41 @@
|
|
|
1086
1287
|
pins flush to the scroll-viewport edge. Any padding-top here
|
|
1087
1288
|
creates a sliver between the static nav buttons above and the
|
|
1088
1289
|
pinned header which can briefly expose the body --bg through
|
|
1089
|
-
sub-pixel render races during fast scroll. Explicit
|
|
1090
|
-
|
|
1290
|
+
sub-pixel render races during fast scroll. Explicit sidebar
|
|
1291
|
+
bg belts-and-braces the inheritance chain. Shares
|
|
1292
|
+
`--sidebar-bg` with the rest of the sidebar column. */
|
|
1091
1293
|
padding: 0 4px 2px;
|
|
1092
|
-
background: var(--
|
|
1294
|
+
background: var(--sidebar-bg);
|
|
1093
1295
|
}
|
|
1296
|
+
/* Unified with `.section-header` (rooms list dividers) · same
|
|
1297
|
+
padding, type scale, sticky pin behavior, and sidebar-bg
|
|
1298
|
+
box-shadow chrome that masks the parent's lateral padding +
|
|
1299
|
+
absorbs the sub-pixel sliver during fast scroll. */
|
|
1094
1300
|
.agents-section-header {
|
|
1095
|
-
padding: 8px 8px 6px;
|
|
1096
|
-
font-family: var(--mono);
|
|
1097
|
-
font-size: 9px;
|
|
1098
|
-
letter-spacing: 0.14em;
|
|
1099
|
-
text-transform: uppercase;
|
|
1100
|
-
color: var(--text-faint);
|
|
1101
1301
|
display: flex;
|
|
1102
1302
|
align-items: center;
|
|
1103
1303
|
gap: 6px;
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1304
|
+
padding: 8px 12px 4px 10px;
|
|
1305
|
+
font-family: var(--mono);
|
|
1306
|
+
font-size: 10px;
|
|
1307
|
+
letter-spacing: 0.14em;
|
|
1308
|
+
text-transform: uppercase;
|
|
1309
|
+
font-weight: 400;
|
|
1310
|
+
color: var(--text-dim);
|
|
1311
|
+
position: sticky;
|
|
1312
|
+
top: 0;
|
|
1313
|
+
z-index: 2;
|
|
1314
|
+
background: var(--sidebar-bg);
|
|
1315
|
+
box-shadow:
|
|
1316
|
+
-4px 0 0 var(--sidebar-bg),
|
|
1317
|
+
4px 0 0 var(--sidebar-bg),
|
|
1318
|
+
0 -2px 0 var(--sidebar-bg);
|
|
1110
1319
|
}
|
|
1320
|
+
/* Right-side divider line is no longer drawn (mirrors `.section-
|
|
1321
|
+
header` which leaves the trailing space for an optional badge);
|
|
1322
|
+
the markup still emits an empty <span class="line"> so existing
|
|
1323
|
+
JS isn't touched. */
|
|
1324
|
+
.agents-section-header .line { display: none; }
|
|
1111
1325
|
|
|
1112
1326
|
/* Shell wraps the agent-row link + delete button as siblings — same
|
|
1113
1327
|
pattern as session-row-shell · the X click never gets absorbed by
|
|
@@ -1234,7 +1448,7 @@
|
|
|
1234
1448
|
.agent-row.is-chair {
|
|
1235
1449
|
background: transparent;
|
|
1236
1450
|
margin: 1px 6px;
|
|
1237
|
-
border-radius:
|
|
1451
|
+
border-radius: 10px;
|
|
1238
1452
|
}
|
|
1239
1453
|
.agent-row.is-chair:hover { background: var(--panel-2); }
|
|
1240
1454
|
.agent-row.is-chair.active { background: var(--panel-3); }
|
|
@@ -1308,14 +1522,10 @@
|
|
|
1308
1522
|
|
|
1309
1523
|
/* "Building" section · in-flight Full persona build placeholder.
|
|
1310
1524
|
Lime accent on the header to distinguish from inert pinned /
|
|
1311
|
-
custom / core sections. */
|
|
1525
|
+
custom / core sections · mirrors `.section-header.live`. */
|
|
1312
1526
|
.agents-section-header.building {
|
|
1313
1527
|
color: var(--lime);
|
|
1314
1528
|
}
|
|
1315
|
-
.agents-section-header.building .line {
|
|
1316
|
-
background: var(--lime-dim, var(--line));
|
|
1317
|
-
opacity: 0.4;
|
|
1318
|
-
}
|
|
1319
1529
|
/* Building row · same dimensions as a normal agent row but the
|
|
1320
1530
|
avatar slot is a pulsing CRT-style frame instead of a face,
|
|
1321
1531
|
and the row carries a small "BUILD" / "READY" tag chip.
|
|
@@ -1421,7 +1631,7 @@
|
|
|
1421
1631
|
letter-spacing: -0.005em;
|
|
1422
1632
|
text-align: left;
|
|
1423
1633
|
flex-shrink: 0;
|
|
1424
|
-
border-radius:
|
|
1634
|
+
border-radius: 10px;
|
|
1425
1635
|
transition: background 0.12s, color 0.12s;
|
|
1426
1636
|
display: flex;
|
|
1427
1637
|
align-items: center;
|
|
@@ -1463,27 +1673,33 @@
|
|
|
1463
1673
|
[data-convene-trigger]::before {
|
|
1464
1674
|
--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='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/><path d='M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z'/></svg>");
|
|
1465
1675
|
}
|
|
1466
|
-
/* "New agent" ·
|
|
1467
|
-
|
|
1676
|
+
/* "New agent" · UserRoundPlus (Lucide) — rounder, bigger-headed
|
|
1677
|
+
"add person" glyph. Replaces the thinner UserPlus, whose small
|
|
1678
|
+
low-left silhouette + floating plus read a size smaller than the
|
|
1679
|
+
box-filling neighbours (SquarePen / FileText) — most visible in
|
|
1680
|
+
the collapsed mini rail where icons stand alone without labels. */
|
|
1468
1681
|
[data-agent-composer-trigger]::before {
|
|
1469
|
-
--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='
|
|
1682
|
+
--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='M2 21a8 8 0 0 1 13.292-6'/><circle cx='10' cy='8' r='5'/><path d='M19 16v6'/><path d='M22 19h-6'/></svg>");
|
|
1470
1683
|
}
|
|
1471
1684
|
/* "All Reports" · FileText (Lucide) — single document with three
|
|
1472
1685
|
text lines, cleaner than the previous gradient-stack glyph. */
|
|
1473
|
-
.new-btn.nav-reports::before
|
|
1686
|
+
.new-btn.nav-reports::before,
|
|
1687
|
+
.mini-reports::before {
|
|
1474
1688
|
--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>");
|
|
1475
1689
|
}
|
|
1476
1690
|
/* "All Notes" · Bookmark (Lucide) — pennant-shaped bookmark glyph
|
|
1477
1691
|
mirroring the qcta save-button icon. Matches the chairman's-notes
|
|
1478
1692
|
vocabulary across the app (sidebar entry, save action, in-room
|
|
1479
1693
|
overlay all share the bookmark register). */
|
|
1480
|
-
.new-btn.nav-notes::before
|
|
1694
|
+
.new-btn.nav-notes::before,
|
|
1695
|
+
.mini-notes::before {
|
|
1481
1696
|
--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>");
|
|
1482
1697
|
}
|
|
1483
1698
|
/* "Search" · Search (Lucide) — circle + diagonal handle. Standard
|
|
1484
1699
|
magnifying-glass glyph in the same Lucide line-icon vocabulary as
|
|
1485
1700
|
the rest of the sidebar. */
|
|
1486
|
-
.new-btn.nav-search::before
|
|
1701
|
+
.new-btn.nav-search::before,
|
|
1702
|
+
.mini-search::before {
|
|
1487
1703
|
--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'><circle cx='11' cy='11' r='8'/><path d='m21 21-4.3-4.3'/></svg>");
|
|
1488
1704
|
}
|
|
1489
1705
|
/* All Reports / All Notes / Search nav-button shape · label-only. */
|
|
@@ -1522,11 +1738,12 @@
|
|
|
1522
1738
|
header that can briefly expose the body --bg through sub-pixel
|
|
1523
1739
|
render races during fast scroll (visible as a thin "see-through"
|
|
1524
1740
|
seam right above the ADJOURNED / LIVE / PAUSED divider).
|
|
1525
|
-
Explicit
|
|
1526
|
-
region is unambiguously
|
|
1527
|
-
through.
|
|
1741
|
+
Explicit sidebar bg belts-and-braces the inheritance chain so
|
|
1742
|
+
the region is unambiguously `--sidebar-bg` even if a child's
|
|
1743
|
+
bg falls through. Tracks `.sidebar`'s surface — change via
|
|
1744
|
+
the shared `--sidebar-bg` token. */
|
|
1528
1745
|
padding: 0 4px 2px;
|
|
1529
|
-
background: var(--
|
|
1746
|
+
background: var(--sidebar-bg);
|
|
1530
1747
|
}
|
|
1531
1748
|
|
|
1532
1749
|
.section-header {
|
|
@@ -1548,27 +1765,26 @@
|
|
|
1548
1765
|
color: var(--text-dim);
|
|
1549
1766
|
text-transform: uppercase;
|
|
1550
1767
|
letter-spacing: 0.14em;
|
|
1551
|
-
font-weight:
|
|
1768
|
+
font-weight: 400;
|
|
1552
1769
|
/* Sticky · the LIVE / PAUSED / ADJOURNED dividers stay pinned
|
|
1553
1770
|
to the top of the scroll viewport as the user scrolls down
|
|
1554
1771
|
a long room list. Parent `.sessions-scroll` is the scroll
|
|
1555
|
-
container. Background
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
the
|
|
1563
|
-
the scroll transition. */
|
|
1772
|
+
container. Background matches `.sidebar` so rows passing
|
|
1773
|
+
under the header don't bleed through; the box-shadow extends
|
|
1774
|
+
that background 4px to either side (mask the parent's
|
|
1775
|
+
lateral 4px padding) and 2px upward (absorb sub-pixel sliver
|
|
1776
|
+
between the scroll-viewport edge and the pinned header
|
|
1777
|
+
during fast scroll). z-index keeps the header above the rows
|
|
1778
|
+
during the scroll transition. Tracks `.sidebar`'s surface
|
|
1779
|
+
via the shared `--sidebar-bg` token. */
|
|
1564
1780
|
position: sticky;
|
|
1565
1781
|
top: 0;
|
|
1566
1782
|
z-index: 2;
|
|
1567
|
-
background: var(--
|
|
1783
|
+
background: var(--sidebar-bg);
|
|
1568
1784
|
box-shadow:
|
|
1569
|
-
-4px 0 0 var(--
|
|
1570
|
-
4px 0 0 var(--
|
|
1571
|
-
0 -2px 0 var(--
|
|
1785
|
+
-4px 0 0 var(--sidebar-bg),
|
|
1786
|
+
4px 0 0 var(--sidebar-bg),
|
|
1787
|
+
0 -2px 0 var(--sidebar-bg);
|
|
1572
1788
|
}
|
|
1573
1789
|
.section-header.live { color: var(--lime); }
|
|
1574
1790
|
.section-header.draft { color: var(--amber); }
|
|
@@ -1584,13 +1800,6 @@
|
|
|
1584
1800
|
}
|
|
1585
1801
|
.row-status.paused { color: var(--amber); }
|
|
1586
1802
|
.section-header .line { flex: 1; }
|
|
1587
|
-
.pin-glyph {
|
|
1588
|
-
width: 10px;
|
|
1589
|
-
height: 10px;
|
|
1590
|
-
fill: currentColor;
|
|
1591
|
-
flex-shrink: 0;
|
|
1592
|
-
}
|
|
1593
|
-
.agents-section-header.pinned { color: var(--text-soft); }
|
|
1594
1803
|
|
|
1595
1804
|
/* ─── Pin toggle (per-row · hover-revealed for non-pinned) ───
|
|
1596
1805
|
Positioned absolutely so it overlays the right edge of the row
|
|
@@ -1636,7 +1845,7 @@
|
|
|
1636
1845
|
.session-row-shell {
|
|
1637
1846
|
position: relative;
|
|
1638
1847
|
margin: 2px 6px;
|
|
1639
|
-
border-radius:
|
|
1848
|
+
border-radius: 10px;
|
|
1640
1849
|
overflow: hidden;
|
|
1641
1850
|
}
|
|
1642
1851
|
.session-row-shell.active .session-row { background: var(--panel-3); }
|
|
@@ -1645,10 +1854,10 @@
|
|
|
1645
1854
|
display: block;
|
|
1646
1855
|
/* Horizontal padding kept in sync with .agent-row so the agents-tab
|
|
1647
1856
|
and rooms-tab lists share the same horizontal rhythm in the
|
|
1648
|
-
sidebar. Vertical
|
|
1649
|
-
|
|
1857
|
+
sidebar. Vertical 6px gives the row a slightly more comfortable
|
|
1858
|
+
hitbox while still reading as compact alongside the .new-btn nav
|
|
1650
1859
|
buttons above. */
|
|
1651
|
-
padding:
|
|
1860
|
+
padding: 6px 10px;
|
|
1652
1861
|
text-decoration: none;
|
|
1653
1862
|
color: var(--text);
|
|
1654
1863
|
cursor: pointer;
|
|
@@ -1794,7 +2003,7 @@
|
|
|
1794
2003
|
}
|
|
1795
2004
|
|
|
1796
2005
|
.sidebar-foot {
|
|
1797
|
-
|
|
2006
|
+
position: relative;
|
|
1798
2007
|
padding: 0 10px;
|
|
1799
2008
|
/* Fixed 44px min-height so the sidebar footer sits on a
|
|
1800
2009
|
predictable baseline regardless of which main view is
|
|
@@ -1807,7 +2016,23 @@
|
|
|
1807
2016
|
align-items: center;
|
|
1808
2017
|
gap: 8px;
|
|
1809
2018
|
flex-shrink: 0;
|
|
1810
|
-
|
|
2019
|
+
}
|
|
2020
|
+
/* Scroll fade · the rooms / agents list above .sidebar-foot can
|
|
2021
|
+
scroll behind it (foot is now transparent, no border-top). This
|
|
2022
|
+
::before sits just above the foot and fades content from fully
|
|
2023
|
+
transparent to the sidebar's surface colour, so list items
|
|
2024
|
+
dissolve instead of clipping hard against the foot.
|
|
2025
|
+
pointer-events: none keeps clicks reaching whatever's behind.
|
|
2026
|
+
End-stop matches `--sidebar-bg` so the fade lands seamlessly. */
|
|
2027
|
+
.sidebar-foot::before {
|
|
2028
|
+
content: "";
|
|
2029
|
+
position: absolute;
|
|
2030
|
+
left: 0;
|
|
2031
|
+
right: 0;
|
|
2032
|
+
bottom: 100%;
|
|
2033
|
+
height: 24px;
|
|
2034
|
+
background: linear-gradient(to bottom, transparent 0%, var(--sidebar-bg) 100%);
|
|
2035
|
+
pointer-events: none;
|
|
1811
2036
|
}
|
|
1812
2037
|
.user-block {
|
|
1813
2038
|
display: flex;
|
|
@@ -1835,11 +2060,12 @@
|
|
|
1835
2060
|
overflow: hidden;
|
|
1836
2061
|
}
|
|
1837
2062
|
/* Pixel-art variant · when prefs.avatarSeed exists, app.renderUserBlock
|
|
1838
|
-
swaps the initial-letter chip for an AvatarSkill SVG.
|
|
1839
|
-
|
|
1840
|
-
|
|
2063
|
+
swaps the initial-letter chip for an AvatarSkill SVG. The pixel SVG
|
|
2064
|
+
is transparent, so we drop the fill entirely (was --lime, then
|
|
2065
|
+
--bg) and let it sit directly on the surface behind it — no boxed
|
|
2066
|
+
square around the character. */
|
|
1841
2067
|
.user-av.has-pixel-av {
|
|
1842
|
-
background:
|
|
2068
|
+
background: transparent;
|
|
1843
2069
|
}
|
|
1844
2070
|
.user-av.has-pixel-av svg {
|
|
1845
2071
|
width: 100%;
|
|
@@ -1983,20 +2209,6 @@
|
|
|
1983
2209
|
|
|
1984
2210
|
/* All Notes view · same scroll register as All Reports so the two
|
|
1985
2211
|
cross-room aggregation destinations read as a paired set. */
|
|
1986
|
-
/* Search view · same scroll chrome as reports / notes so the
|
|
1987
|
-
three cross-cutting destinations behave identically. */
|
|
1988
|
-
.main-view[data-main-view="search"] {
|
|
1989
|
-
display: flex;
|
|
1990
|
-
flex-direction: column;
|
|
1991
|
-
overflow-y: auto;
|
|
1992
|
-
scrollbar-width: none;
|
|
1993
|
-
}
|
|
1994
|
-
.main-view[data-main-view="search"]::-webkit-scrollbar { width: 8px; }
|
|
1995
|
-
.main-view[data-main-view="search"]::-webkit-scrollbar-thumb { background: transparent; }
|
|
1996
|
-
.main-view[data-main-view="search"]::-webkit-scrollbar-track { background: transparent; }
|
|
1997
|
-
.main-view[data-main-view="search"]:hover { scrollbar-width: thin; }
|
|
1998
|
-
.main-view[data-main-view="search"]:hover::-webkit-scrollbar-thumb { background: var(--line-bright); }
|
|
1999
|
-
|
|
2000
2212
|
.main-view[data-main-view="notes"] {
|
|
2001
2213
|
display: block;
|
|
2002
2214
|
overflow-y: auto;
|
|
@@ -2062,11 +2274,13 @@
|
|
|
2062
2274
|
}
|
|
2063
2275
|
|
|
2064
2276
|
/* ─── Filter strip · All / Today / This week / Earlier ────────────
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
chip
|
|
2277
|
+
Segmented control · multiple chips must be visible at once (the
|
|
2278
|
+
time-range filter is a pick-one-of-N for a list view), so this
|
|
2279
|
+
stays SEGMENTED rather than tracking the sidebar tabs' newer
|
|
2280
|
+
pill-shape "active grows, inactive collapses to icon-only" vocab.
|
|
2281
|
+
Active chip gets the panel-3 bg + text colour upgrade; hover gets
|
|
2282
|
+
panel-2; 4px corner radius keeps it clearly chip-not-pill. The
|
|
2283
|
+
count rides inline after the label as quiet mono micro-type.
|
|
2070
2284
|
Comma-extended to `.notes-filters` / `.notes-filter-chip` so the
|
|
2071
2285
|
All Notes page reuses the exact same chip vocabulary — both
|
|
2072
2286
|
cross-room aggregation destinations stay visually identical. */
|
|
@@ -2458,705 +2672,227 @@
|
|
|
2458
2672
|
}
|
|
2459
2673
|
|
|
2460
2674
|
/* ──────────────────────────────────────────────────────────
|
|
2461
|
-
Search
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
value / cursor position survive the swap. */
|
|
2476
|
-
.search-page {
|
|
2477
|
-
/* No `flex: 1` here · search-page must grow naturally with
|
|
2478
|
-
its content (especially long result lists) so the sticky
|
|
2479
|
-
`.search-card`'s containing block extends through all the
|
|
2480
|
-
rows. With `flex: 1`, the search-page was clamped to one
|
|
2481
|
-
viewport tall and the sticky card un-stuck after scrolling
|
|
2482
|
-
past that — exactly the "sticky disappears after a few
|
|
2483
|
-
scrolls" bug. `min-height: 100%` is still the floor so the
|
|
2484
|
-
is-initial hero can vertical-centre against a viewport-
|
|
2485
|
-
sized stage. */
|
|
2486
|
-
width: 100%;
|
|
2487
|
-
max-width: 920px;
|
|
2488
|
-
margin: 0 auto;
|
|
2489
|
-
padding: 32px 32px 40px;
|
|
2675
|
+
Search overlay · floating command-palette layer that
|
|
2676
|
+
replaces the prior `.search-page` standalone view. Triggered
|
|
2677
|
+
by `[data-search-trigger]` (sidebar Search nav) and Cmd+K /
|
|
2678
|
+
Ctrl+K globally. Layers over whatever main-view is mounted
|
|
2679
|
+
(room / agent / reports / notes) — the view stays mounted
|
|
2680
|
+
underneath so closing returns the user exactly where they
|
|
2681
|
+
were. Esc / scrim-click / X close. Result rows link to
|
|
2682
|
+
`#/r/{roomId}?m={msgId}&q={q}` (hash router), preserving
|
|
2683
|
+
click-navigation + keyword flash on the destination message
|
|
2684
|
+
from the prior page. */
|
|
2685
|
+
.search-overlay {
|
|
2686
|
+
position: fixed;
|
|
2687
|
+
inset: 0;
|
|
2688
|
+
z-index: 9000;
|
|
2490
2689
|
display: flex;
|
|
2491
|
-
|
|
2492
|
-
min-height: 100%;
|
|
2493
|
-
box-sizing: border-box;
|
|
2494
|
-
/* NO `position: relative` here · the deco layers below
|
|
2495
|
-
anchor to `.main-view[data-main-view="search"]` instead
|
|
2496
|
-
(which IS position:relative) so they fill main-view's
|
|
2497
|
-
width — the visible room content area to the right of
|
|
2498
|
-
the sidebar — rather than the .search-page's own 920px
|
|
2499
|
-
column. Earlier the deco was anchored here and used a
|
|
2500
|
-
`width: 100vw` trick to escape, but `vw` units include
|
|
2501
|
-
the sidebar's slice of the viewport, so the deco's left
|
|
2502
|
-
edge ended up underneath the sidebar (and the corner
|
|
2503
|
-
brackets sat where the user couldn't see them). */
|
|
2504
|
-
}
|
|
2505
|
-
/* ─── Initial / empty state · 8-bit ambient deco + wide card ───
|
|
2506
|
-
The page surface gets a static 8-bit "constellation" overlay
|
|
2507
|
-
at the top — scattered pixel dots + a few lime accents —
|
|
2508
|
-
drawn with `shape-rendering: crispEdges` to match the
|
|
2509
|
-
round-table stage / chair-sprite vocabulary. Hero is text-
|
|
2510
|
-
only (longer wordmark, subline) since the prior pixel mark
|
|
2511
|
-
read too noisy. The card itself is 760px and `width: 100%`
|
|
2512
|
-
so it actually fills the centred column instead of shrinking
|
|
2513
|
-
to the input's intrinsic width. */
|
|
2514
|
-
.search-page.is-initial {
|
|
2690
|
+
align-items: flex-start;
|
|
2515
2691
|
justify-content: center;
|
|
2516
|
-
|
|
2517
|
-
visually weighted. */
|
|
2518
|
-
padding-bottom: 12vh;
|
|
2519
|
-
padding-top: 0;
|
|
2520
|
-
}
|
|
2521
|
-
/* Has-results · inner-scroll layout. The PAGE itself is sized
|
|
2522
|
-
to exactly main-view height (so nothing scrolls at this
|
|
2523
|
-
level), and the results list inside gets its own scroll
|
|
2524
|
-
via `flex: 1 + overflow-y: auto`. This puts the head row
|
|
2525
|
-
in a non-scrolling top slot — no `position: sticky`
|
|
2526
|
-
gymnastics, no containing-block-too-short failure mode.
|
|
2527
|
-
The previous sticky approach disappeared as soon as the
|
|
2528
|
-
user scrolled past one screen because the sticky's
|
|
2529
|
-
containing block (the page) ran out; this design has no
|
|
2530
|
-
such ceiling. */
|
|
2531
|
-
.search-page.has-results {
|
|
2532
|
-
height: 100%;
|
|
2533
|
-
min-height: 0;
|
|
2534
|
-
padding-top: 32px;
|
|
2535
|
-
padding-bottom: 0;
|
|
2536
|
-
flex-shrink: 0;
|
|
2537
|
-
}
|
|
2538
|
-
/* 8-bit ambient deco · absolute-positioned overlay at the top
|
|
2539
|
-
of the page. Pure decoration · pointer-events: none, aria-
|
|
2540
|
-
hidden, fades out the lower edge with a CSS mask so it
|
|
2541
|
-
doesn't compete with the hero / card below.
|
|
2542
|
-
|
|
2543
|
-
Width · `left: 0; right: 0` resolves against the nearest
|
|
2544
|
-
positioned ancestor, which is `.main-view[data-main-view=
|
|
2545
|
-
"search"]` (it sits to the right of the sidebar). The deco
|
|
2546
|
-
therefore spans the FULL main-view width, not the viewport
|
|
2547
|
-
— so the sidebar never overlaps. .search-page is static
|
|
2548
|
-
(no position:relative) so the deco escapes its 920px cap
|
|
2549
|
-
naturally. */
|
|
2550
|
-
.search-bg-deco {
|
|
2551
|
-
position: absolute;
|
|
2552
|
-
top: 0;
|
|
2553
|
-
left: 0;
|
|
2554
|
-
right: 0;
|
|
2555
|
-
height: 280px;
|
|
2692
|
+
padding: 88px 24px 24px;
|
|
2556
2693
|
pointer-events: none;
|
|
2557
|
-
z-index: -1;
|
|
2558
|
-
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
2559
|
-
mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
2560
|
-
opacity: 0.85;
|
|
2561
|
-
}
|
|
2562
|
-
.search-bg-deco svg { display: block; width: 100%; height: 100%; }
|
|
2563
|
-
|
|
2564
|
-
/* Positioning anchor for the deco layers · main-view sits
|
|
2565
|
-
to the right of the sidebar so its bounds = the visible
|
|
2566
|
-
"room content" width. `isolation: isolate` scopes the
|
|
2567
|
-
deco's z-index: -1 so it sits behind search-page content
|
|
2568
|
-
without leaking past main-view's background. */
|
|
2569
|
-
.main-view[data-main-view="search"] {
|
|
2570
|
-
position: relative;
|
|
2571
|
-
isolation: isolate;
|
|
2572
2694
|
}
|
|
2573
|
-
|
|
2574
|
-
.search-
|
|
2575
|
-
/* Compact in has-results · keep a slim band of pixel
|
|
2576
|
-
constellation behind the head row so the page still
|
|
2577
|
-
reads as the search page rather than a generic list. */
|
|
2578
|
-
height: 130px;
|
|
2579
|
-
opacity: 0.45;
|
|
2580
|
-
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 50%, transparent 100%);
|
|
2581
|
-
mask-image: linear-gradient(180deg, #000 0%, #000 50%, transparent 100%);
|
|
2582
|
-
transition: opacity 0.18s ease, height 0.22s ease;
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
/* Results-only deco · second layer that ONLY shows in
|
|
2586
|
-
has-results. Adds proper 8-bit characters on top of the
|
|
2587
|
-
constellation: CRT scanlines (CSS), pixel antennas at
|
|
2588
|
-
the corners, scattered pixel "plus" sparkles, and a
|
|
2589
|
-
dashed horizon line at the bottom edge. The combination
|
|
2590
|
-
gives the results header genuine "search station"
|
|
2591
|
-
atmosphere instead of a thin star field. */
|
|
2592
|
-
.search-results-deco {
|
|
2695
|
+
.search-overlay[hidden] { display: none; }
|
|
2696
|
+
.search-overlay-scrim {
|
|
2593
2697
|
position: absolute;
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
sits to the right of the sidebar. */
|
|
2600
|
-
left: 0;
|
|
2601
|
-
right: 0;
|
|
2602
|
-
height: 130px;
|
|
2603
|
-
pointer-events: none;
|
|
2604
|
-
z-index: -1;
|
|
2605
|
-
opacity: 0;
|
|
2606
|
-
transition: opacity 0.22s ease;
|
|
2607
|
-
/* Faint CRT scanlines · 1px lime-tinted line every 5px.
|
|
2608
|
-
Subtle enough not to compete with the input but
|
|
2609
|
-
persistent enough to read as "this surface is alive." */
|
|
2610
|
-
background-image: repeating-linear-gradient(
|
|
2611
|
-
0deg,
|
|
2612
|
-
transparent 0px,
|
|
2613
|
-
transparent 4px,
|
|
2614
|
-
rgba(111, 181, 114, 0.035) 4px,
|
|
2615
|
-
rgba(111, 181, 114, 0.035) 5px
|
|
2616
|
-
);
|
|
2617
|
-
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
|
|
2618
|
-
mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
|
|
2619
|
-
}
|
|
2620
|
-
.search-page.has-results .search-results-deco {
|
|
2621
|
-
opacity: 0.85;
|
|
2622
|
-
}
|
|
2623
|
-
.search-results-deco svg { display: block; width: 100%; height: 100%; }
|
|
2624
|
-
.search-hero {
|
|
2625
|
-
text-align: center;
|
|
2626
|
-
max-width: 760px;
|
|
2627
|
-
margin: 0 auto 26px;
|
|
2628
|
-
overflow: hidden;
|
|
2629
|
-
transition:
|
|
2630
|
-
opacity 0.22s ease,
|
|
2631
|
-
max-height 0.24s ease,
|
|
2632
|
-
margin 0.24s ease;
|
|
2633
|
-
}
|
|
2634
|
-
.search-page.is-initial .search-hero {
|
|
2635
|
-
opacity: 1;
|
|
2636
|
-
max-height: 220px;
|
|
2637
|
-
}
|
|
2638
|
-
.search-page.has-results .search-hero {
|
|
2639
|
-
opacity: 0;
|
|
2640
|
-
max-height: 0;
|
|
2641
|
-
margin-top: 0;
|
|
2642
|
-
margin-bottom: 0;
|
|
2643
|
-
pointer-events: none;
|
|
2644
|
-
}
|
|
2645
|
-
.search-hero-title {
|
|
2646
|
-
font-family: var(--font-human);
|
|
2647
|
-
font-size: 28px;
|
|
2648
|
-
font-weight: 600;
|
|
2649
|
-
letter-spacing: -0.02em;
|
|
2650
|
-
color: var(--text);
|
|
2651
|
-
line-height: 1.05;
|
|
2652
|
-
margin: 0 0 12px 0;
|
|
2653
|
-
}
|
|
2654
|
-
.search-hero-sub {
|
|
2655
|
-
font-family: var(--mono);
|
|
2656
|
-
font-size: 11px;
|
|
2657
|
-
letter-spacing: 0.18em;
|
|
2658
|
-
text-transform: uppercase;
|
|
2659
|
-
color: var(--text-faint);
|
|
2660
|
-
margin: 0;
|
|
2698
|
+
inset: 0;
|
|
2699
|
+
background: rgba(0, 0, 0, 0.42);
|
|
2700
|
+
-webkit-backdrop-filter: blur(2px);
|
|
2701
|
+
backdrop-filter: blur(2px);
|
|
2702
|
+
pointer-events: auto;
|
|
2661
2703
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
is-initial state. `width: 100%` is load-bearing — without
|
|
2665
|
-
it the card collapses to its content's intrinsic width
|
|
2666
|
-
(the input had no flex anchor in is-initial because the
|
|
2667
|
-
wrap was display:flex with the input as a single child),
|
|
2668
|
-
making the whole hero look narrow. With `width: 100%`
|
|
2669
|
-
plus `max-width: 760px` the card always fills the
|
|
2670
|
-
centred column. In has-results the card chrome strips
|
|
2671
|
-
and the inner input-wrap takes back its own border so
|
|
2672
|
-
the compact head row can flex input + meta side-by-side. */
|
|
2673
|
-
.search-card {
|
|
2674
|
-
display: block;
|
|
2704
|
+
.search-overlay-card {
|
|
2705
|
+
position: relative;
|
|
2675
2706
|
width: 100%;
|
|
2676
|
-
max-width:
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
--accent-line` hairline border, same 14px radius, same
|
|
2685
|
-
`overflow: hidden` so the inner `.search-topbar` panel-strip
|
|
2686
|
-
sits flush with the rounded corners. */
|
|
2687
|
-
background: var(--bg);
|
|
2688
|
-
border: 0.5px solid var(--accent-line);
|
|
2707
|
+
max-width: 640px;
|
|
2708
|
+
max-height: calc(100vh - 112px);
|
|
2709
|
+
display: flex;
|
|
2710
|
+
flex-direction: column;
|
|
2711
|
+
background: var(--panel-2);
|
|
2712
|
+
-webkit-backdrop-filter: blur(20px) saturate(160%);
|
|
2713
|
+
backdrop-filter: blur(20px) saturate(160%);
|
|
2714
|
+
border: 0.5px solid var(--line-bright);
|
|
2689
2715
|
border-radius: 14px;
|
|
2716
|
+
box-shadow:
|
|
2717
|
+
0 24px 60px -20px rgba(0, 0, 0, 0.55),
|
|
2718
|
+
0 6px 16px -8px rgba(0, 0, 0, 0.4);
|
|
2690
2719
|
overflow: hidden;
|
|
2691
|
-
|
|
2692
|
-
border-color 0.18s,
|
|
2693
|
-
max-width 0.22s ease,
|
|
2694
|
-
margin 0.22s ease,
|
|
2695
|
-
padding 0.22s ease;
|
|
2696
|
-
}
|
|
2697
|
-
.search-card:focus-within {
|
|
2698
|
-
/* Default border is `--lime-dim` (subtle brand tint); focus lifts
|
|
2699
|
-
to full `--lime` for a clear "active" register. */
|
|
2700
|
-
border-color: var(--lime);
|
|
2701
|
-
}
|
|
2702
|
-
/* Topbar inside the search card · mirrors `.cmp-topbar` from the
|
|
2703
|
-
new-room composer · brand-tinted `--strip-bg` fill with an
|
|
2704
|
-
`--accent-line` bottom hairline. Hosts the old `.search-hero-sub`
|
|
2705
|
-
hint line ("across every room · keyword · message body · room
|
|
2706
|
-
name") so the card itself surfaces its scope instead of needing
|
|
2707
|
-
a separate sub-headline above. Only present in is-initial — the
|
|
2708
|
-
has-results state collapses it alongside the hero. */
|
|
2709
|
-
.search-topbar {
|
|
2710
|
-
display: flex;
|
|
2711
|
-
align-items: center;
|
|
2712
|
-
flex-wrap: wrap;
|
|
2713
|
-
gap: 4px;
|
|
2714
|
-
padding: 6px 14px;
|
|
2715
|
-
background: var(--strip-bg);
|
|
2716
|
-
border-bottom: 0.5px solid var(--accent-line);
|
|
2717
|
-
min-height: 36px;
|
|
2718
|
-
}
|
|
2719
|
-
.search-topbar-hint {
|
|
2720
|
-
font-family: var(--mono);
|
|
2721
|
-
font-size: 10px;
|
|
2722
|
-
letter-spacing: 0.16em;
|
|
2723
|
-
text-transform: uppercase;
|
|
2724
|
-
color: var(--text-faint);
|
|
2725
|
-
/* Same baseline tonality as `.search-hero-sub` had outside the
|
|
2726
|
-
card · text-faint mono micro-caps sit quietly above the input. */
|
|
2720
|
+
pointer-events: auto;
|
|
2727
2721
|
}
|
|
2728
|
-
.search-
|
|
2729
|
-
.search-page.has-results .search-card {
|
|
2722
|
+
.search-overlay-input-row {
|
|
2730
2723
|
display: flex;
|
|
2731
2724
|
align-items: center;
|
|
2732
|
-
gap:
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
the head row moved INSIDE `.search-results` (via the
|
|
2736
|
-
::before pseudo down at line ~XXX). With it on the card,
|
|
2737
|
-
that 18px lived OUTSIDE the scroll area and permanently
|
|
2738
|
-
stole vertical real estate; the scroll content was
|
|
2739
|
-
truncated 18px earlier than the visible band. Moving the
|
|
2740
|
-
gap into the scroll content means it scrolls away
|
|
2741
|
-
naturally as the user reads down. */
|
|
2742
|
-
margin: 0;
|
|
2743
|
-
padding-top: 12px;
|
|
2744
|
-
padding-bottom: 14px;
|
|
2745
|
-
/* The head row is now in a NON-scrolling top slot of
|
|
2746
|
-
search-page (the page is fixed at main-view height; the
|
|
2747
|
-
scroll lives inside `.search-results`). No `position:
|
|
2748
|
-
sticky` needed — the card stays put because the surface
|
|
2749
|
-
around it doesn't move. */
|
|
2750
|
-
background: var(--panel);
|
|
2751
|
-
border: none;
|
|
2725
|
+
gap: 10px;
|
|
2726
|
+
padding: 12px 12px 12px 16px;
|
|
2727
|
+
border-bottom: 1px solid var(--line-bright);
|
|
2752
2728
|
flex-shrink: 0;
|
|
2753
|
-
z-index: 2;
|
|
2754
|
-
/* Stacking context for the full-width ::after pseudo. */
|
|
2755
|
-
position: relative;
|
|
2756
|
-
isolation: isolate;
|
|
2757
|
-
}
|
|
2758
|
-
/* Full-width head band · ::after extends a panel-coloured
|
|
2759
|
-
strip + 0.5px under-line from -100vmax to +100vmax past
|
|
2760
|
-
the card's 920px box, clipped by main-view's overflow.
|
|
2761
|
-
z-index: -1 keeps it behind the card's children (input /
|
|
2762
|
-
chips / meta) inside the card's own stacking context. */
|
|
2763
|
-
.search-page.has-results .search-card::after {
|
|
2764
|
-
content: "";
|
|
2765
|
-
position: absolute;
|
|
2766
|
-
inset: 0 -100vmax;
|
|
2767
|
-
background: var(--panel);
|
|
2768
|
-
border-bottom: 0.5px solid var(--line);
|
|
2769
|
-
z-index: -1;
|
|
2770
|
-
pointer-events: none;
|
|
2771
2729
|
}
|
|
2772
|
-
.search-
|
|
2773
|
-
|
|
2774
|
-
border-bottom-color: var(--lime);
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
/* Input wrap · borderless inside the card in is-initial,
|
|
2778
|
-
bordered pill in has-results. */
|
|
2779
|
-
.search-input-wrap {
|
|
2780
|
-
position: relative;
|
|
2781
|
-
display: flex;
|
|
2782
|
-
align-items: center;
|
|
2730
|
+
.search-overlay-icon { color: var(--text-dim); flex-shrink: 0; }
|
|
2731
|
+
.search-overlay-input {
|
|
2783
2732
|
flex: 1;
|
|
2784
2733
|
min-width: 0;
|
|
2785
|
-
transition: border-color 0.18s, background 0.18s;
|
|
2786
|
-
}
|
|
2787
|
-
.search-page.is-initial .search-input-wrap {
|
|
2788
|
-
background: transparent;
|
|
2789
2734
|
border: none;
|
|
2790
|
-
|
|
2791
|
-
}
|
|
2792
|
-
.search-page.has-results .search-input-wrap {
|
|
2793
|
-
background: var(--panel-2);
|
|
2794
|
-
border: 0.5px solid var(--line);
|
|
2795
|
-
}
|
|
2796
|
-
.search-page.has-results .search-input-wrap:focus-within {
|
|
2797
|
-
border-color: var(--lime);
|
|
2798
|
-
background: var(--panel);
|
|
2799
|
-
}
|
|
2800
|
-
.search-input-icon {
|
|
2801
|
-
display: flex;
|
|
2802
|
-
align-items: center;
|
|
2803
|
-
justify-content: center;
|
|
2804
|
-
color: var(--text-faint);
|
|
2805
|
-
flex-shrink: 0;
|
|
2806
|
-
transition: width 0.22s ease, color 0.12s;
|
|
2807
|
-
}
|
|
2808
|
-
/* Hide the leading magnifier in BOTH states · the hero card
|
|
2809
|
-
is its own affordance, and in has-results the input is
|
|
2810
|
-
unmistakably a search field by context (sort chips +
|
|
2811
|
-
result-count meta beside it). The icon was reading as
|
|
2812
|
-
redundant chrome. */
|
|
2813
|
-
.search-input-icon { display: none; }
|
|
2814
|
-
.search-input-wrap:focus-within .search-input-icon { color: var(--lime); }
|
|
2815
|
-
.search-input {
|
|
2816
|
-
flex: 1;
|
|
2817
|
-
min-width: 0;
|
|
2735
|
+
outline: none;
|
|
2818
2736
|
background: transparent;
|
|
2819
|
-
border: none;
|
|
2820
2737
|
color: var(--text);
|
|
2821
2738
|
font-family: var(--font-human);
|
|
2739
|
+
font-size: 15px;
|
|
2740
|
+
line-height: 1.4;
|
|
2822
2741
|
letter-spacing: -0.005em;
|
|
2823
|
-
|
|
2824
|
-
transition: padding 0.22s ease, font-size 0.22s ease;
|
|
2825
|
-
}
|
|
2826
|
-
.search-page.is-initial .search-input {
|
|
2827
|
-
/* Type spec matches `.cmp-input` (14 px sans / line-height 1.55 /
|
|
2828
|
-
-0.003em letter-spacing) and we reuse the same `14 px`
|
|
2829
|
-
horizontal padding. No `min-height` here · an `<input>` always
|
|
2830
|
-
vertical-centres its text content, so a forced 84 px height
|
|
2831
|
-
(which works for the cmp's textarea where text top-aligns)
|
|
2832
|
-
just leaves a tall band of empty space above the cursor. The
|
|
2833
|
-
hero card's presence comes from the bottom toolbar + frame
|
|
2834
|
-
padding, not from a stretched input. */
|
|
2835
|
-
padding: 8px 14px 12px;
|
|
2836
|
-
font-size: 14px;
|
|
2837
|
-
line-height: 1.55;
|
|
2838
|
-
letter-spacing: -0.003em;
|
|
2742
|
+
padding: 4px 0;
|
|
2839
2743
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
own surface color so the input stays visually consistent
|
|
2845
|
-
with the rest of the card whether autofill engaged or not. */
|
|
2846
|
-
.search-input:-webkit-autofill,
|
|
2847
|
-
.search-input:-webkit-autofill:hover,
|
|
2848
|
-
.search-input:-webkit-autofill:focus {
|
|
2849
|
-
-webkit-box-shadow: 0 0 0 1000px var(--bg) inset !important;
|
|
2744
|
+
.search-overlay-input::placeholder { color: var(--text-dim); font-weight: 400; }
|
|
2745
|
+
.search-overlay-input:-webkit-autofill,
|
|
2746
|
+
.search-overlay-input:-webkit-autofill:focus {
|
|
2747
|
+
-webkit-box-shadow: 0 0 0 1000px var(--panel-2) inset !important;
|
|
2850
2748
|
-webkit-text-fill-color: var(--text) !important;
|
|
2851
2749
|
caret-color: var(--text);
|
|
2852
2750
|
transition: background-color 5000s ease-in-out 0s;
|
|
2853
2751
|
}
|
|
2854
|
-
.search-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
}
|
|
2868
|
-
/* Override the OLDER `.search-input:focus` rule (line ~796)
|
|
2869
|
-
from the sidebar's search-block · that rule sets
|
|
2870
|
-
`background: var(--panel-2)` on focus, which turned our
|
|
2871
|
-
hero input GRAY whenever the user clicked into it. Both
|
|
2872
|
-
features share the class name `.search-input`, so an
|
|
2873
|
-
unscoped focus rule there leaks here. Scoping under
|
|
2874
|
-
`.search-page` raises specificity and pins the input
|
|
2875
|
-
transparent on focus. */
|
|
2876
|
-
.search-page .search-input:focus,
|
|
2877
|
-
.search-page .search-input:focus-visible {
|
|
2878
|
-
background: transparent;
|
|
2879
|
-
border: none;
|
|
2880
|
-
outline: none;
|
|
2881
|
-
}
|
|
2882
|
-
.search-input-clear {
|
|
2883
|
-
background: transparent;
|
|
2884
|
-
border: none;
|
|
2885
|
-
color: var(--text-faint);
|
|
2886
|
-
cursor: pointer;
|
|
2887
|
-
transition:
|
|
2888
|
-
color 0.12s,
|
|
2889
|
-
width 0.22s ease,
|
|
2890
|
-
height 0.22s ease;
|
|
2891
|
-
flex-shrink: 0;
|
|
2892
|
-
display: inline-flex;
|
|
2893
|
-
align-items: center;
|
|
2894
|
-
justify-content: center;
|
|
2895
|
-
}
|
|
2896
|
-
.search-page.is-initial .search-input-clear {
|
|
2897
|
-
width: 36px;
|
|
2898
|
-
height: 36px;
|
|
2899
|
-
font-size: 13px;
|
|
2900
|
-
margin-right: 6px;
|
|
2901
|
-
}
|
|
2902
|
-
.search-page.has-results .search-input-clear {
|
|
2903
|
-
width: 32px;
|
|
2904
|
-
height: 32px;
|
|
2905
|
-
font-size: 12px;
|
|
2906
|
-
}
|
|
2907
|
-
.search-input-clear:hover { color: var(--text); }
|
|
2908
|
-
/* Hide the clear button when the input is empty (set by JS
|
|
2909
|
-
via the .is-empty class on .search-card). Keeps the
|
|
2910
|
-
hero's right edge clean when the user hasn't typed yet. */
|
|
2911
|
-
.search-card.is-empty .search-input-clear { display: none; }
|
|
2912
|
-
|
|
2913
|
-
/* Internal toolbar · footer of the card in is-initial.
|
|
2914
|
-
Mono hint on the left, lime send button on the right.
|
|
2915
|
-
Hidden entirely in has-results (the card is collapsed
|
|
2916
|
-
to a head row by then). */
|
|
2917
|
-
/* `.search-input-toolbar` and its mono "keyword · message body ·
|
|
2918
|
-
room name" hint were retired · the frosted card alone is the
|
|
2919
|
-
affordance, no extra footer-bar needed. */
|
|
2920
|
-
/* Starter chips · static suggestions below the card in
|
|
2921
|
-
is-initial. Click → pre-fill the input + trigger search.
|
|
2922
|
-
Hidden in has-results. */
|
|
2923
|
-
.search-starters {
|
|
2924
|
-
display: flex;
|
|
2925
|
-
align-items: center;
|
|
2926
|
-
justify-content: center;
|
|
2927
|
-
gap: 6px;
|
|
2928
|
-
margin: 20px auto 0;
|
|
2929
|
-
max-width: 760px;
|
|
2930
|
-
font-family: var(--mono);
|
|
2931
|
-
font-size: 10px;
|
|
2932
|
-
letter-spacing: 0.14em;
|
|
2933
|
-
text-transform: uppercase;
|
|
2934
|
-
color: var(--text-faint);
|
|
2935
|
-
flex-wrap: wrap;
|
|
2936
|
-
}
|
|
2937
|
-
.search-page.has-results .search-starters { display: none; }
|
|
2938
|
-
.search-starters-label { color: var(--text-faint); margin-right: 4px; }
|
|
2939
|
-
.search-starter {
|
|
2940
|
-
background: transparent;
|
|
2941
|
-
border: 0.5px solid var(--line);
|
|
2942
|
-
color: var(--text-soft);
|
|
2943
|
-
font: inherit;
|
|
2944
|
-
letter-spacing: inherit;
|
|
2945
|
-
text-transform: inherit;
|
|
2946
|
-
cursor: pointer;
|
|
2947
|
-
padding: 5px 10px;
|
|
2948
|
-
transition: color 0.12s, border-color 0.12s;
|
|
2949
|
-
}
|
|
2950
|
-
.search-starter:hover {
|
|
2951
|
-
color: var(--lime);
|
|
2952
|
-
border-color: var(--lime);
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
/* Result-count meta · sits beside the shrunk input in the
|
|
2956
|
-
has-results head row. Hidden entirely in is-initial. */
|
|
2957
|
-
.search-results-meta {
|
|
2958
|
-
font-family: var(--mono);
|
|
2959
|
-
font-size: 10px;
|
|
2960
|
-
letter-spacing: 0.14em;
|
|
2961
|
-
text-transform: uppercase;
|
|
2962
|
-
color: var(--text-faint);
|
|
2963
|
-
white-space: nowrap;
|
|
2964
|
-
flex-shrink: 0;
|
|
2965
|
-
}
|
|
2966
|
-
.search-page.is-initial .search-results-meta { display: none; }
|
|
2967
|
-
|
|
2968
|
-
/* Sort chip group · "Newest" / "Oldest" toggle that re-sorts
|
|
2969
|
-
the result list client-side (no re-fetch). Only visible in
|
|
2970
|
-
has-results · the head row's flex layout slots it between
|
|
2971
|
-
the input and the meta count. Active chip uses the lime
|
|
2972
|
-
accent border + caps; inactives are line-faint until hover. */
|
|
2973
|
-
.search-results-sort {
|
|
2974
|
-
display: none;
|
|
2975
|
-
align-items: center;
|
|
2976
|
-
gap: 4px;
|
|
2977
|
-
flex-shrink: 0;
|
|
2978
|
-
font-family: var(--mono);
|
|
2979
|
-
font-size: 10px;
|
|
2980
|
-
letter-spacing: 0.14em;
|
|
2981
|
-
text-transform: uppercase;
|
|
2982
|
-
}
|
|
2983
|
-
.search-page.has-results .search-results-sort {
|
|
2984
|
-
display: inline-flex;
|
|
2985
|
-
}
|
|
2986
|
-
.search-results-sort .srs-label {
|
|
2987
|
-
color: var(--text-faint);
|
|
2988
|
-
margin-right: 4px;
|
|
2989
|
-
}
|
|
2990
|
-
.search-results-sort button {
|
|
2991
|
-
background: transparent;
|
|
2992
|
-
border: 0.5px solid var(--line);
|
|
2993
|
-
color: var(--text-faint);
|
|
2994
|
-
font: inherit;
|
|
2995
|
-
letter-spacing: inherit;
|
|
2996
|
-
text-transform: inherit;
|
|
2997
|
-
padding: 5px 9px;
|
|
2998
|
-
cursor: pointer;
|
|
2999
|
-
transition: color 0.12s, border-color 0.12s;
|
|
3000
|
-
}
|
|
3001
|
-
.search-results-sort button:hover {
|
|
3002
|
-
color: var(--text-soft);
|
|
3003
|
-
border-color: var(--text-faint);
|
|
3004
|
-
}
|
|
3005
|
-
.search-results-sort button.active {
|
|
3006
|
-
color: var(--lime);
|
|
3007
|
-
border-color: var(--lime);
|
|
3008
|
-
}
|
|
3009
|
-
.search-results-sort button.active:hover {
|
|
3010
|
-
color: var(--lime);
|
|
3011
|
-
border-color: var(--lime);
|
|
2752
|
+
.search-overlay-close-btn {
|
|
2753
|
+
background: transparent;
|
|
2754
|
+
border: none;
|
|
2755
|
+
color: var(--text-dim);
|
|
2756
|
+
cursor: pointer;
|
|
2757
|
+
width: 28px;
|
|
2758
|
+
height: 28px;
|
|
2759
|
+
display: inline-flex;
|
|
2760
|
+
align-items: center;
|
|
2761
|
+
justify-content: center;
|
|
2762
|
+
border-radius: 6px;
|
|
2763
|
+
transition: color 0.12s, background 0.12s;
|
|
2764
|
+
flex-shrink: 0;
|
|
3012
2765
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
.search-results {
|
|
3017
|
-
padding-top: 4px;
|
|
2766
|
+
.search-overlay-close-btn:hover {
|
|
2767
|
+
color: var(--text);
|
|
2768
|
+
background: var(--surface-hover, rgba(255, 255, 255, 0.05));
|
|
3018
2769
|
}
|
|
3019
|
-
.search-
|
|
3020
|
-
/* Inner scroll container · in has-results, `.search-results`
|
|
3021
|
-
owns the vertical scroll instead of `.main-view`. The
|
|
3022
|
-
search-page itself is sized to exactly main-view height,
|
|
3023
|
-
so the head row (`.search-card`) sits in a non-scrolling
|
|
3024
|
-
top slot and stays there permanently. */
|
|
3025
|
-
.search-page.has-results .search-results {
|
|
2770
|
+
.search-overlay-body {
|
|
3026
2771
|
flex: 1;
|
|
3027
2772
|
min-height: 0;
|
|
3028
2773
|
overflow-y: auto;
|
|
3029
|
-
padding-bottom: 40px;
|
|
3030
2774
|
scrollbar-width: thin;
|
|
3031
2775
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
height: 18px;
|
|
3044
|
-
}
|
|
3045
|
-
.search-page.has-results .search-results::-webkit-scrollbar { width: 8px; }
|
|
3046
|
-
.search-page.has-results .search-results::-webkit-scrollbar-thumb { background: transparent; }
|
|
3047
|
-
.search-page.has-results .search-results::-webkit-scrollbar-track { background: transparent; }
|
|
3048
|
-
.search-page.has-results .search-results:hover::-webkit-scrollbar-thumb { background: var(--line-bright); }
|
|
3049
|
-
|
|
3050
|
-
/* Google-style result row · 3 stacked text lines (mono
|
|
3051
|
-
source breadcrumb · sans link title · sans snippet body).
|
|
3052
|
-
No row hover background, no per-row border — only the
|
|
3053
|
-
title line lights up on hover so the row reads calm. */
|
|
3054
|
-
.search-results-list {
|
|
3055
|
-
list-style: none;
|
|
3056
|
-
margin: 0;
|
|
3057
|
-
padding: 0;
|
|
3058
|
-
}
|
|
3059
|
-
.search-results-list > li {
|
|
3060
|
-
margin-bottom: 22px;
|
|
2776
|
+
.search-overlay-body::-webkit-scrollbar { width: 8px; }
|
|
2777
|
+
.search-overlay-body::-webkit-scrollbar-thumb { background: transparent; }
|
|
2778
|
+
.search-overlay-body::-webkit-scrollbar-track { background: transparent; }
|
|
2779
|
+
.search-overlay-body:hover::-webkit-scrollbar-thumb { background: var(--line-bright); }
|
|
2780
|
+
.search-overlay-empty {
|
|
2781
|
+
padding: 56px 24px;
|
|
2782
|
+
display: flex;
|
|
2783
|
+
flex-direction: column;
|
|
2784
|
+
align-items: center;
|
|
2785
|
+
text-align: center;
|
|
2786
|
+
color: var(--text-dim);
|
|
3061
2787
|
}
|
|
3062
|
-
.search-
|
|
3063
|
-
.
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
color:
|
|
2788
|
+
.search-overlay-empty-icon { color: var(--text-dim); margin-bottom: 18px; }
|
|
2789
|
+
.search-overlay-empty-text {
|
|
2790
|
+
font-family: var(--font-human);
|
|
2791
|
+
font-size: 14px;
|
|
2792
|
+
color: var(--text-soft);
|
|
2793
|
+
margin-bottom: 6px;
|
|
2794
|
+
letter-spacing: -0.003em;
|
|
3067
2795
|
}
|
|
3068
|
-
.
|
|
3069
|
-
display: flex;
|
|
3070
|
-
align-items: baseline;
|
|
3071
|
-
gap: 6px;
|
|
2796
|
+
.search-overlay-empty-hint {
|
|
3072
2797
|
font-family: var(--mono);
|
|
3073
2798
|
font-size: 10px;
|
|
3074
|
-
letter-spacing: 0.
|
|
2799
|
+
letter-spacing: 0.14em;
|
|
3075
2800
|
text-transform: uppercase;
|
|
3076
|
-
color: var(--text-
|
|
3077
|
-
margin-bottom: 4px;
|
|
3078
|
-
overflow: hidden;
|
|
3079
|
-
white-space: nowrap;
|
|
3080
|
-
text-overflow: ellipsis;
|
|
2801
|
+
color: var(--text-dim);
|
|
3081
2802
|
}
|
|
3082
|
-
.
|
|
3083
|
-
|
|
3084
|
-
|
|
2803
|
+
.search-overlay-list { list-style: none; margin: 0; padding: 6px 0; }
|
|
2804
|
+
.search-overlay-list > li { margin: 0; }
|
|
2805
|
+
.so-row {
|
|
2806
|
+
display: grid;
|
|
2807
|
+
grid-template-columns: 20px 1fr auto;
|
|
2808
|
+
column-gap: 12px;
|
|
2809
|
+
row-gap: 2px;
|
|
2810
|
+
align-items: center;
|
|
2811
|
+
padding: 8px 16px;
|
|
2812
|
+
text-decoration: none;
|
|
2813
|
+
color: inherit;
|
|
2814
|
+
transition: background 0.08s;
|
|
2815
|
+
cursor: pointer;
|
|
3085
2816
|
}
|
|
3086
|
-
.
|
|
3087
|
-
|
|
3088
|
-
|
|
2817
|
+
.so-row:hover,
|
|
2818
|
+
.so-row:focus-visible {
|
|
2819
|
+
background: var(--surface-hover, rgba(255, 255, 255, 0.04));
|
|
2820
|
+
outline: none;
|
|
3089
2821
|
}
|
|
3090
|
-
.
|
|
3091
|
-
|
|
2822
|
+
.so-row-icon {
|
|
2823
|
+
grid-column: 1;
|
|
2824
|
+
grid-row: 1 / 3;
|
|
2825
|
+
color: var(--text-dim);
|
|
2826
|
+
display: flex;
|
|
2827
|
+
align-items: center;
|
|
2828
|
+
justify-content: center;
|
|
2829
|
+
align-self: start;
|
|
2830
|
+
margin-top: 1px;
|
|
3092
2831
|
}
|
|
3093
|
-
.
|
|
2832
|
+
.so-row-title {
|
|
2833
|
+
grid-column: 2;
|
|
2834
|
+
grid-row: 1;
|
|
3094
2835
|
font-family: var(--font-human);
|
|
3095
|
-
font-size:
|
|
3096
|
-
|
|
3097
|
-
line-height: 1.3;
|
|
2836
|
+
font-size: 14px;
|
|
2837
|
+
line-height: 1.35;
|
|
3098
2838
|
color: var(--text);
|
|
3099
|
-
|
|
3100
|
-
letter-spacing: -0.
|
|
3101
|
-
transition: color 0.12s;
|
|
2839
|
+
font-weight: 500;
|
|
2840
|
+
letter-spacing: -0.003em;
|
|
3102
2841
|
overflow: hidden;
|
|
3103
2842
|
text-overflow: ellipsis;
|
|
3104
2843
|
white-space: nowrap;
|
|
3105
2844
|
}
|
|
3106
|
-
.
|
|
3107
|
-
|
|
2845
|
+
.so-row-meta {
|
|
2846
|
+
grid-column: 3;
|
|
2847
|
+
grid-row: 1;
|
|
2848
|
+
font-family: var(--mono);
|
|
2849
|
+
font-size: 10px;
|
|
2850
|
+
letter-spacing: 0.08em;
|
|
2851
|
+
text-transform: uppercase;
|
|
2852
|
+
color: var(--text-dim);
|
|
2853
|
+
white-space: nowrap;
|
|
2854
|
+
flex-shrink: 0;
|
|
2855
|
+
}
|
|
2856
|
+
.so-row-snippet {
|
|
2857
|
+
grid-column: 2 / 4;
|
|
2858
|
+
grid-row: 2;
|
|
3108
2859
|
font-family: var(--font-human);
|
|
3109
|
-
font-size:
|
|
3110
|
-
line-height: 1.
|
|
2860
|
+
font-size: 12px;
|
|
2861
|
+
line-height: 1.45;
|
|
3111
2862
|
color: var(--text-soft);
|
|
3112
|
-
word-break: break-word;
|
|
3113
|
-
display: -webkit-box;
|
|
3114
|
-
-webkit-line-clamp: 2;
|
|
3115
|
-
-webkit-box-orient: vertical;
|
|
3116
2863
|
overflow: hidden;
|
|
2864
|
+
text-overflow: ellipsis;
|
|
2865
|
+
white-space: nowrap;
|
|
3117
2866
|
}
|
|
3118
|
-
.
|
|
3119
|
-
background:
|
|
3120
|
-
color: var(--
|
|
3121
|
-
padding: 0 2px;
|
|
3122
|
-
border-radius: 2px;
|
|
2867
|
+
.so-row-snippet mark {
|
|
2868
|
+
background: transparent;
|
|
2869
|
+
color: var(--lime);
|
|
3123
2870
|
font-weight: 600;
|
|
2871
|
+
padding: 0;
|
|
2872
|
+
border-radius: 0;
|
|
3124
2873
|
}
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
so it's tied to the has-results state. Centered block, calm
|
|
3128
|
-
copy. */
|
|
3129
|
-
.search-empty {
|
|
3130
|
-
padding: 40px 0;
|
|
3131
|
-
text-align: center;
|
|
3132
|
-
color: var(--text-faint);
|
|
3133
|
-
}
|
|
3134
|
-
.search-empty-kicker {
|
|
2874
|
+
.search-overlay-status { padding: 40px 16px; text-align: center; color: var(--text-dim); }
|
|
2875
|
+
.search-overlay-status-kicker {
|
|
3135
2876
|
display: block;
|
|
3136
2877
|
font-family: var(--mono);
|
|
3137
2878
|
font-size: 10px;
|
|
3138
2879
|
letter-spacing: 0.18em;
|
|
3139
2880
|
text-transform: uppercase;
|
|
3140
2881
|
color: var(--lime);
|
|
3141
|
-
margin-bottom:
|
|
2882
|
+
margin-bottom: 6px;
|
|
3142
2883
|
}
|
|
3143
|
-
.search-
|
|
2884
|
+
.search-overlay-status-msg {
|
|
3144
2885
|
font-family: var(--font-human);
|
|
3145
|
-
font-size:
|
|
2886
|
+
font-size: 13px;
|
|
3146
2887
|
color: var(--text-soft);
|
|
3147
2888
|
line-height: 1.5;
|
|
3148
|
-
max-width:
|
|
2889
|
+
max-width: 360px;
|
|
3149
2890
|
margin: 0 auto;
|
|
3150
2891
|
}
|
|
3151
|
-
|
|
3152
|
-
/* Reduced-motion · snap state changes, no transitions. */
|
|
3153
2892
|
@media (prefers-reduced-motion: reduce) {
|
|
3154
|
-
.
|
|
3155
|
-
.search-
|
|
3156
|
-
.search-
|
|
3157
|
-
.search-input-icon,
|
|
3158
|
-
.search-input-clear,
|
|
3159
|
-
.sr-title { transition: none; }
|
|
2893
|
+
.so-row { transition: none; }
|
|
2894
|
+
.search-overlay-scrim,
|
|
2895
|
+
.search-overlay-card { backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
3160
2896
|
}
|
|
3161
2897
|
|
|
3162
2898
|
/* Search-result jump · in-place keyword pulse + article outline.
|
|
@@ -4159,15 +3895,6 @@
|
|
|
4159
3895
|
-webkit-backdrop-filter: none;
|
|
4160
3896
|
}
|
|
4161
3897
|
}
|
|
4162
|
-
/* When the sidebar is collapsed the room-head gains a leading
|
|
4163
|
-
auto-sized track for the in-header `.room-head-expand` button.
|
|
4164
|
-
When expanded the button is display:none, so a 0-width track
|
|
4165
|
-
would still leave a `gap` artifact — switching to a 3-track
|
|
4166
|
-
template only in the collapsed state keeps the header visually
|
|
4167
|
-
identical to before whenever the sidebar is open. */
|
|
4168
|
-
body.sidebar-collapsed .room-head {
|
|
4169
|
-
grid-template-columns: auto 1fr auto;
|
|
4170
|
-
}
|
|
4171
3898
|
/* `overflow: visible` so the tone-tag hover tooltip (positioned via
|
|
4172
3899
|
::after below the tag) can escape this container. The room-subject
|
|
4173
3900
|
has its own overflow:hidden + text-overflow ellipsis rule, so the
|
|
@@ -6186,15 +5913,15 @@
|
|
|
6186
5913
|
|
|
6187
5914
|
.followup-children {
|
|
6188
5915
|
/* Width-match the brief card (.ending-block: max-width 760px,
|
|
6189
|
-
margin auto)
|
|
6190
|
-
|
|
6191
|
-
|
|
5916
|
+
margin auto) AND mirror `.session-analytics` chrome · same
|
|
5917
|
+
panel-2 surface, same --line-bright hairline, same banner-body
|
|
5918
|
+
split (head = panel-3 strip with mono kicker, list = body
|
|
5919
|
+
block with its own padding). No container-level padding ·
|
|
5920
|
+
inner elements own it. */
|
|
6192
5921
|
max-width: 760px;
|
|
6193
5922
|
margin: 24px auto 0;
|
|
6194
|
-
padding: 14px 16px;
|
|
6195
5923
|
background: var(--panel-2);
|
|
6196
|
-
border: 0.5px solid var(--line);
|
|
6197
|
-
font-family: var(--mono);
|
|
5924
|
+
border: 0.5px solid var(--line-bright);
|
|
6198
5925
|
}
|
|
6199
5926
|
|
|
6200
5927
|
/* ─── Session analytics card · post-adjourn summary ───
|
|
@@ -6335,21 +6062,27 @@
|
|
|
6335
6062
|
padding: 0;
|
|
6336
6063
|
display: flex;
|
|
6337
6064
|
flex-direction: column;
|
|
6338
|
-
gap:
|
|
6065
|
+
gap: 0;
|
|
6339
6066
|
}
|
|
6067
|
+
/* All columns `auto` (previously `10px 1fr auto auto`) so swatch +
|
|
6068
|
+
name + pct + tokens cluster on the left rather than `1fr` on the
|
|
6069
|
+
name column stretching short labels and shoving the numbers to
|
|
6070
|
+
the far right of the legend. min-width on pct + tokens keeps the
|
|
6071
|
+
numeric columns roughly aligned across rows so the eye can still
|
|
6072
|
+
scan vertically. */
|
|
6340
6073
|
.sa-legend-row {
|
|
6341
6074
|
display: grid;
|
|
6342
|
-
grid-template-columns:
|
|
6343
|
-
align-items:
|
|
6344
|
-
gap:
|
|
6075
|
+
grid-template-columns: 8px auto auto auto;
|
|
6076
|
+
align-items: center;
|
|
6077
|
+
gap: 6px;
|
|
6345
6078
|
font-family: var(--mono);
|
|
6346
6079
|
font-size: 10px;
|
|
6080
|
+
line-height: 1.35;
|
|
6347
6081
|
color: var(--text-soft);
|
|
6348
6082
|
}
|
|
6349
6083
|
.sa-legend-swatch {
|
|
6350
6084
|
width: 8px;
|
|
6351
6085
|
height: 8px;
|
|
6352
|
-
align-self: center;
|
|
6353
6086
|
border-radius: 0;
|
|
6354
6087
|
}
|
|
6355
6088
|
.sa-legend-name {
|
|
@@ -6360,12 +6093,13 @@
|
|
|
6360
6093
|
color: var(--text-soft);
|
|
6361
6094
|
font-variant-numeric: tabular-nums;
|
|
6362
6095
|
text-align: right;
|
|
6096
|
+
min-width: 30px;
|
|
6363
6097
|
}
|
|
6364
6098
|
.sa-legend-tokens {
|
|
6365
6099
|
color: var(--text-faint);
|
|
6366
6100
|
font-variant-numeric: tabular-nums;
|
|
6367
6101
|
text-align: right;
|
|
6368
|
-
min-width:
|
|
6102
|
+
min-width: 40px;
|
|
6369
6103
|
}
|
|
6370
6104
|
|
|
6371
6105
|
/* What you valued · chips for counts + list of ▲-voted points.
|
|
@@ -6449,15 +6183,26 @@
|
|
|
6449
6183
|
color: var(--text-faint);
|
|
6450
6184
|
letter-spacing: 0.04em;
|
|
6451
6185
|
}
|
|
6186
|
+
/* Banner head · matches `.sa-banner` shape (panel-3 strip with mono
|
|
6187
|
+
kicker, hairline divider beneath). The text-only label slots in
|
|
6188
|
+
where `.sa-banner-tag` would sit in the analytics card. */
|
|
6452
6189
|
.followup-children-head {
|
|
6190
|
+
padding: 5px 14px;
|
|
6191
|
+
display: flex;
|
|
6192
|
+
align-items: center;
|
|
6193
|
+
gap: 12px;
|
|
6194
|
+
font-family: var(--mono);
|
|
6453
6195
|
font-size: 10px;
|
|
6454
|
-
color: var(--text-soft);
|
|
6455
|
-
letter-spacing: 0.16em;
|
|
6456
|
-
text-transform: uppercase;
|
|
6457
6196
|
font-weight: 700;
|
|
6458
|
-
|
|
6197
|
+
letter-spacing: 0.18em;
|
|
6198
|
+
text-transform: uppercase;
|
|
6199
|
+
color: var(--lime);
|
|
6200
|
+
background: var(--panel-3);
|
|
6201
|
+
border-bottom: 0.5px solid var(--line);
|
|
6459
6202
|
}
|
|
6203
|
+
/* Body block · matches `.sa-body` padding. */
|
|
6460
6204
|
.followup-children-list {
|
|
6205
|
+
padding: 12px 14px 14px;
|
|
6461
6206
|
display: flex;
|
|
6462
6207
|
flex-direction: column;
|
|
6463
6208
|
gap: 6px;
|
|
@@ -6627,6 +6372,14 @@
|
|
|
6627
6372
|
flex: 1 1 auto;
|
|
6628
6373
|
min-height: 0;
|
|
6629
6374
|
overflow-y: auto;
|
|
6375
|
+
/* Chat scroller is on `--panel` — same surface as the sidebar,
|
|
6376
|
+
so the message stream reads as a flat continuous canvas
|
|
6377
|
+
(vs `.chat-col` which sits at `--panel-2` underneath as a
|
|
6378
|
+
lifted frame the chat sits on top of). The lifted `--panel-2`
|
|
6379
|
+
shows through wherever `.chat` doesn't paint (cmp starter
|
|
6380
|
+
panel, gutters when chat is hidden), giving the column a
|
|
6381
|
+
subtle warm border that the message stream itself doesn't
|
|
6382
|
+
inherit. */
|
|
6630
6383
|
background: var(--panel);
|
|
6631
6384
|
/* Top padding · clears the frosted-glass `.room-head` (which is
|
|
6632
6385
|
now absolutely positioned, ~56px tall). Without this, the first
|
|
@@ -6643,7 +6396,7 @@
|
|
|
6643
6396
|
common case (textarea + one strip); tall textarea +
|
|
6644
6397
|
multiple strips can still cover the bottom message, the
|
|
6645
6398
|
user scrolls a touch — acceptable. */
|
|
6646
|
-
padding: calc(14px + var(--room-head-h, 56px))
|
|
6399
|
+
padding: calc(14px + var(--room-head-h, 56px)) 50px 140px 40px;
|
|
6647
6400
|
transition: opacity 0.18s ease-out;
|
|
6648
6401
|
}
|
|
6649
6402
|
/* Note-jump loading state · the user clicked a note in the All
|
|
@@ -7810,6 +7563,42 @@
|
|
|
7810
7563
|
.msg-bubble p:last-child { margin-bottom: 0; }
|
|
7811
7564
|
.msg-bubble strong { color: var(--text); font-weight: 600; }
|
|
7812
7565
|
.msg-bubble em { font-style: normal; color: var(--lime); font-weight: 500; }
|
|
7566
|
+
/* Karaoke-style TTS sentence highlight · when voice replay reads a
|
|
7567
|
+
message aloud, app.js wraps each sentence in a `.tts-sentence`
|
|
7568
|
+
span and adds `.is-current` to whichever one matches the audio
|
|
7569
|
+
cursor (currentTime / duration → character offset → sentence
|
|
7570
|
+
index). The wrap survives across messages and is harmless without
|
|
7571
|
+
`.is-current`. Highlight uses the theme accent so the user can
|
|
7572
|
+
scan back to "where it's reading" without re-reading the whole
|
|
7573
|
+
bubble. Smooth color transition · feels like a soft sweep
|
|
7574
|
+
instead of a strobing chip when the cursor advances. */
|
|
7575
|
+
.msg-bubble .tts-sentence,
|
|
7576
|
+
.cd-body .tts-sentence,
|
|
7577
|
+
.ci-body .tts-sentence {
|
|
7578
|
+
transition: color 0.18s ease, background 0.18s ease;
|
|
7579
|
+
border-radius: 2px;
|
|
7580
|
+
}
|
|
7581
|
+
.msg-bubble .tts-sentence.is-current,
|
|
7582
|
+
.cd-body .tts-sentence.is-current,
|
|
7583
|
+
.ci-body .tts-sentence.is-current {
|
|
7584
|
+
color: var(--lime);
|
|
7585
|
+
background: color-mix(in srgb, var(--lime) 12%, transparent);
|
|
7586
|
+
/* Keep the highlight readable when the sentence wraps across
|
|
7587
|
+
multiple lines · `box-decoration-break: clone` paints the
|
|
7588
|
+
background on each line fragment instead of one giant box. */
|
|
7589
|
+
-webkit-box-decoration-break: clone;
|
|
7590
|
+
box-decoration-break: clone;
|
|
7591
|
+
}
|
|
7592
|
+
/* Keep <em> inside the current sentence visually consistent with
|
|
7593
|
+
the surrounding highlight (otherwise the `em { color: var(--lime) }`
|
|
7594
|
+
above + the wrapper's lime tint blend into one undifferentiated
|
|
7595
|
+
accent and the italic emphasis loses signal). Subtle weight bump
|
|
7596
|
+
instead. */
|
|
7597
|
+
.msg-bubble .tts-sentence.is-current em,
|
|
7598
|
+
.cd-body .tts-sentence.is-current em,
|
|
7599
|
+
.ci-body .tts-sentence.is-current em {
|
|
7600
|
+
font-weight: 600;
|
|
7601
|
+
}
|
|
7813
7602
|
/* Markdown blockquote · designed inset card, not a raw `>` line.
|
|
7814
7603
|
Per the no-coloured-left-borders rule, the callout treatment
|
|
7815
7604
|
uses a top mono kicker + panel-2 surface + indent (NOT a left
|
|
@@ -7893,9 +7682,6 @@
|
|
|
7893
7682
|
(--font-agent) so the visual continuity reads as "another voice in
|
|
7894
7683
|
the room". Only the name color + avatar border distinguish them. */
|
|
7895
7684
|
.msg.chair .msg-name { color: var(--cyan, #6A9B97); }
|
|
7896
|
-
.msg.chair .msg-av {
|
|
7897
|
-
border: 0.5px solid var(--cyan, #6A9B97);
|
|
7898
|
-
}
|
|
7899
7685
|
/* Settings-change pings stay deliberately small — they're a status
|
|
7900
7686
|
line, not a turn. */
|
|
7901
7687
|
.msg.chair.kind-settings .msg-bubble {
|
|
@@ -9012,6 +8798,25 @@
|
|
|
9012
8798
|
.rt-avatar[data-agent]:hover {
|
|
9013
8799
|
filter: brightness(1.18);
|
|
9014
8800
|
}
|
|
8801
|
+
/* Speaking squash-blink · the avatar's eyes are baked into the
|
|
8802
|
+
sprite (img / inline pixel-SVG), so a quick vertical scaleY
|
|
8803
|
+
squash is the cheapest "blink while talking" cue. Only the
|
|
8804
|
+
speaking seat animates; the dip is brief + shallow so it reads
|
|
8805
|
+
as a blink, not a bounce. transform keeps the static
|
|
8806
|
+
translateX(-50%) so the sprite stays centered on the chair. */
|
|
8807
|
+
.rt-seat-speaking .rt-avatar {
|
|
8808
|
+
transform-origin: center 45%;
|
|
8809
|
+
animation: rt-blink-squash 4.2s ease-in-out infinite;
|
|
8810
|
+
}
|
|
8811
|
+
@keyframes rt-blink-squash {
|
|
8812
|
+
0%, 88%, 100% { transform: translateX(-50%) scaleY(1); }
|
|
8813
|
+
92% { transform: translateX(-50%) scaleY(0.88); }
|
|
8814
|
+
96% { transform: translateX(-50%) scaleY(1); }
|
|
8815
|
+
}
|
|
8816
|
+
/* Respect reduced-motion · hold the avatar at full height. */
|
|
8817
|
+
@media (prefers-reduced-motion: reduce) {
|
|
8818
|
+
.rt-seat-speaking .rt-avatar { animation: none; }
|
|
8819
|
+
}
|
|
9015
8820
|
|
|
9016
8821
|
/* Name plate · small mono caption ABOVE the avatar (was below,
|
|
9017
8822
|
but back-row director seats had their names land on the table
|
|
@@ -11599,94 +11404,6 @@
|
|
|
11599
11404
|
margin: 0 auto;
|
|
11600
11405
|
padding: 32px 32px;
|
|
11601
11406
|
width: 100%;
|
|
11602
|
-
/* `isolation: isolate` creates a stacking context here WITHOUT
|
|
11603
|
-
making cmp the containing block for absolute descendants.
|
|
11604
|
-
Result: `.cmp-bg-deco` belongs to cmp's stacking context
|
|
11605
|
-
(so `z-index: -1` puts it just below cmp's static content
|
|
11606
|
-
but ABOVE the lower-layer chat / chat-col panel bgs), while
|
|
11607
|
-
its SPATIAL containing block is `.chat-col` (positioned
|
|
11608
|
-
above) — so the deco can span the full chat-col width
|
|
11609
|
-
without being constrained to cmp's 760px. The two-axis
|
|
11610
|
-
split is what makes the Search-page-style backdrop work
|
|
11611
|
-
inside a max-width-narrower section. */
|
|
11612
|
-
isolation: isolate;
|
|
11613
|
-
}
|
|
11614
|
-
/* 8-bit ambient backdrop · pinned at the top of the composer.
|
|
11615
|
-
Same visual language + position as `.search-bg-deco`. The
|
|
11616
|
-
backdrop ESCAPES `.cmp`'s max-width (760px) so it spans the
|
|
11617
|
-
full main-view width — same `left: 50%; margin-left: -50vw;
|
|
11618
|
-
width: 100vw` pattern the Search page uses. `.main-view`'s
|
|
11619
|
-
own `overflow: hidden` clips it to the visible content area,
|
|
11620
|
-
so "full width" resolves to the room-content rect (sidebar
|
|
11621
|
-
not included). Scene-tuned SVG content is injected by
|
|
11622
|
-
`composerBgDecoSvg(scene)` in app.js · room mode draws a
|
|
11623
|
-
mini boardroom (table + chairs), agent mode draws a row of
|
|
11624
|
-
pixel character heads + speech bubble. */
|
|
11625
|
-
.cmp-bg-deco {
|
|
11626
|
-
/* Containing block is `.chat-col` (set position: relative above)
|
|
11627
|
-
so `top/left/right: 0` snap to the FULL CHAT COLUMN — exactly
|
|
11628
|
-
the right-pane width the user wants. No `100vw` escape · the
|
|
11629
|
-
previous viewport-relative width spilled behind the sidebar
|
|
11630
|
-
because the sidebar lives OUTSIDE this main-view's overflow
|
|
11631
|
-
clip, so the deco rendered under it. Anchoring to `.chat-col`
|
|
11632
|
-
gets us the right pane's actual content rect and nothing
|
|
11633
|
-
beyond it. */
|
|
11634
|
-
position: absolute;
|
|
11635
|
-
top: 0;
|
|
11636
|
-
left: 0;
|
|
11637
|
-
right: 0;
|
|
11638
|
-
height: auto;
|
|
11639
|
-
pointer-events: none;
|
|
11640
|
-
z-index: -1;
|
|
11641
|
-
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
11642
|
-
mask-image: linear-gradient(180deg, #000 0%, #000 55%, transparent 100%);
|
|
11643
|
-
/* Opacity tuned down from 0.85 — the field of small pixel motifs
|
|
11644
|
-
read as "fragmented dirt" especially in light, where white bg
|
|
11645
|
-
amplifies every dot's contrast. Light gets an extra step down. */
|
|
11646
|
-
opacity: 0.6;
|
|
11647
|
-
}
|
|
11648
|
-
:root[data-theme="light"] .cmp-bg-deco { opacity: 0.4; }
|
|
11649
|
-
.cmp-bg-deco svg { display: block; width: 100%; height: 100%; }
|
|
11650
|
-
/* 8-bit deco animations · keep subtle so the field reads as
|
|
11651
|
-
"alive" rather than "demanding attention". All animations are
|
|
11652
|
-
opacity / quantised transforms so the pixel-art register
|
|
11653
|
-
holds (no smooth tweens). Each element gets an inline
|
|
11654
|
-
`animation-delay` from the SVG generator so siblings don't
|
|
11655
|
-
pulse in lockstep — the field shimmers organically. */
|
|
11656
|
-
@keyframes deco-twinkle {
|
|
11657
|
-
0%, 100% { opacity: 1; }
|
|
11658
|
-
50% { opacity: 0.35; }
|
|
11659
|
-
}
|
|
11660
|
-
@keyframes deco-shine {
|
|
11661
|
-
0%, 100% { opacity: 1; }
|
|
11662
|
-
50% { opacity: 0.55; }
|
|
11663
|
-
}
|
|
11664
|
-
@keyframes deco-spark {
|
|
11665
|
-
0%, 100% { opacity: 1; transform: scale(1); }
|
|
11666
|
-
50% { opacity: 0.4; transform: scale(0.7); }
|
|
11667
|
-
}
|
|
11668
|
-
@keyframes deco-bob {
|
|
11669
|
-
0%, 100% { transform: translateY(0); }
|
|
11670
|
-
50% { transform: translateY(-1.5px); }
|
|
11671
|
-
}
|
|
11672
|
-
@keyframes deco-blink {
|
|
11673
|
-
0%, 70%, 100% { opacity: 1; }
|
|
11674
|
-
80%, 92% { opacity: 0.15; }
|
|
11675
|
-
}
|
|
11676
|
-
.cmp-bg-deco .deco-twinkle { animation: deco-twinkle 4.5s ease-in-out infinite; }
|
|
11677
|
-
.cmp-bg-deco .deco-shine { animation: deco-shine 3.2s ease-in-out infinite; }
|
|
11678
|
-
.cmp-bg-deco .deco-spark { animation: deco-spark 2.4s ease-in-out infinite;
|
|
11679
|
-
transform-box: fill-box; transform-origin: center; }
|
|
11680
|
-
.cmp-bg-deco .deco-bob { animation: deco-bob 3.0s ease-in-out infinite;
|
|
11681
|
-
transform-box: fill-box; transform-origin: center; }
|
|
11682
|
-
.cmp-bg-deco .deco-blink { animation: deco-blink 4.0s ease-in-out infinite; }
|
|
11683
|
-
/* Reduced-motion · stop animation but keep the deco visible. */
|
|
11684
|
-
@media (prefers-reduced-motion: reduce) {
|
|
11685
|
-
.cmp-bg-deco .deco-twinkle,
|
|
11686
|
-
.cmp-bg-deco .deco-shine,
|
|
11687
|
-
.cmp-bg-deco .deco-spark,
|
|
11688
|
-
.cmp-bg-deco .deco-bob,
|
|
11689
|
-
.cmp-bg-deco .deco-blink { animation: none; }
|
|
11690
11407
|
}
|
|
11691
11408
|
/* Default composer mode (content fits viewport).
|
|
11692
11409
|
Toggled by JS via `.chat--composer` (added in
|
|
@@ -12588,7 +12305,7 @@
|
|
|
12588
12305
|
}
|
|
12589
12306
|
|
|
12590
12307
|
/* ─── Celebrity seed cards · new-agent composer "hire a known
|
|
12591
|
-
mind" rail. Replaces the older AGENT_STARTERS_EN list.
|
|
12308
|
+
mind" rail. Replaces the older AGENT_STARTERS_EN list. Four
|
|
12592
12309
|
portrait-style cards in a 2-col grid, each a one-click preset
|
|
12593
12310
|
for the full-mode persona builder.
|
|
12594
12311
|
|
|
@@ -14650,6 +14367,70 @@
|
|
|
14650
14367
|
font-size: 10px;
|
|
14651
14368
|
color: var(--text-faint);
|
|
14652
14369
|
}
|
|
14370
|
+
/* ─── Cast-edit popover (room header "Add director" button) ───
|
|
14371
|
+
Extends `.composer-pick-pop` with an explicit footer carrying
|
|
14372
|
+
Cancel / Confirm so the membership change is intentional. The
|
|
14373
|
+
list rows reuse `.composer-pick-row` styling — identical visual
|
|
14374
|
+
register to the new-room composer's director picker so the user
|
|
14375
|
+
reads it as the same primitive. */
|
|
14376
|
+
.cast-edit-pop {
|
|
14377
|
+
position: fixed;
|
|
14378
|
+
z-index: 9001;
|
|
14379
|
+
width: 340px;
|
|
14380
|
+
max-width: calc(100vw - 32px);
|
|
14381
|
+
max-height: 70vh;
|
|
14382
|
+
display: flex;
|
|
14383
|
+
flex-direction: column;
|
|
14384
|
+
background: var(--panel);
|
|
14385
|
+
border: 0.5px solid var(--line-strong);
|
|
14386
|
+
}
|
|
14387
|
+
.cast-edit-pop .composer-pick-list {
|
|
14388
|
+
flex: 1;
|
|
14389
|
+
overflow-y: auto;
|
|
14390
|
+
}
|
|
14391
|
+
.cast-edit-foot {
|
|
14392
|
+
display: flex;
|
|
14393
|
+
justify-content: flex-end;
|
|
14394
|
+
align-items: center;
|
|
14395
|
+
gap: 8px;
|
|
14396
|
+
padding: 8px 10px;
|
|
14397
|
+
border-top: 0.5px solid var(--line);
|
|
14398
|
+
background: var(--panel-2);
|
|
14399
|
+
}
|
|
14400
|
+
.cast-edit-floor-msg {
|
|
14401
|
+
flex: 1;
|
|
14402
|
+
font-family: var(--mono);
|
|
14403
|
+
font-size: 9px;
|
|
14404
|
+
letter-spacing: 0.06em;
|
|
14405
|
+
color: var(--amber, #B59560);
|
|
14406
|
+
visibility: hidden;
|
|
14407
|
+
}
|
|
14408
|
+
.cast-edit-floor-msg.is-visible { visibility: visible; }
|
|
14409
|
+
.cast-edit-btn {
|
|
14410
|
+
appearance: none;
|
|
14411
|
+
background: transparent;
|
|
14412
|
+
border: 0.5px solid var(--line-bright);
|
|
14413
|
+
color: var(--text);
|
|
14414
|
+
font-family: var(--mono);
|
|
14415
|
+
font-size: 10px;
|
|
14416
|
+
letter-spacing: 0.12em;
|
|
14417
|
+
text-transform: uppercase;
|
|
14418
|
+
padding: 5px 12px;
|
|
14419
|
+
cursor: pointer;
|
|
14420
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
14421
|
+
}
|
|
14422
|
+
.cast-edit-btn:hover { color: var(--lime); border-color: var(--lime); }
|
|
14423
|
+
.cast-edit-btn.is-primary {
|
|
14424
|
+
color: var(--lime);
|
|
14425
|
+
border-color: var(--lime);
|
|
14426
|
+
}
|
|
14427
|
+
.cast-edit-btn.is-primary:hover { background: var(--lime-dim, transparent); }
|
|
14428
|
+
.cast-edit-btn:disabled {
|
|
14429
|
+
color: var(--text-dim);
|
|
14430
|
+
border-color: var(--line);
|
|
14431
|
+
cursor: not-allowed;
|
|
14432
|
+
}
|
|
14433
|
+
.cast-edit-btn:disabled:hover { background: transparent; }
|
|
14653
14434
|
/* ─── Brief picker popover · the [View Report] click target on
|
|
14654
14435
|
multi-brief rooms. Anchored under the room-head's button via
|
|
14655
14436
|
position: fixed and right-aligned so it visually drops out of
|
|
@@ -15144,24 +14925,21 @@
|
|
|
15144
14925
|
`margin: 0 14px` so the gutters match the live-chat layout. */
|
|
15145
14926
|
}
|
|
15146
14927
|
|
|
15147
|
-
/* Chat column wraps the message stream + input bar.
|
|
14928
|
+
/* Chat column wraps the message stream + input bar. Surface is
|
|
14929
|
+
`--panel-2` — one step warmer than the sidebar's `--panel` —
|
|
14930
|
+
so the content reads as a lifted stage against the sidebar
|
|
14931
|
+
chrome. Sat at `--panel-3` previously which read too pale.
|
|
14932
|
+
#07 gray-step from sidebar is subtle but intentional; floating
|
|
14933
|
+
chrome above the chat (`.input-bar`, `.room-head`,
|
|
14934
|
+
`.speaking-queue` trio) keeps its own frosted-glass treatment,
|
|
14935
|
+
so it still differentiates from the lifted base. */
|
|
15148
14936
|
.chat-col {
|
|
15149
14937
|
display: flex;
|
|
15150
14938
|
flex-direction: column;
|
|
15151
14939
|
min-height: 0;
|
|
15152
14940
|
overflow: hidden;
|
|
15153
|
-
background: var(--panel);
|
|
14941
|
+
background: var(--panel-2);
|
|
15154
14942
|
height: 100%;
|
|
15155
|
-
/* `position: relative` makes chat-col the CONTAINING BLOCK
|
|
15156
|
-
for `.cmp-bg-deco` (positioned absolute) so the deco's
|
|
15157
|
-
`left/right: 0` snap to the full chat-col width. NOTE: no
|
|
15158
|
-
`isolation` here · we DON'T want chat-col to also be the
|
|
15159
|
-
stacking context. The deco's `z-index: -1` is meant to
|
|
15160
|
-
paint within `.cmp`'s isolated stacking context (which sits
|
|
15161
|
-
on top of chat / chat-col bgs), not get trapped below
|
|
15162
|
-
chat-col's own panel bg. Splitting the two roles lets the
|
|
15163
|
-
deco stretch wide while still rendering above the chat
|
|
15164
|
-
panel — see `.cmp` + `.cmp-bg-deco` below. */
|
|
15165
14943
|
position: relative;
|
|
15166
14944
|
}
|
|
15167
14945
|
|
|
@@ -16293,6 +16071,45 @@
|
|
|
16293
16071
|
<!-- Resizable handle: sidebar | main -->
|
|
16294
16072
|
<div class="col-resizer" data-resize data-var="--sidebar-w" data-side="left" data-min="220" data-max="480" data-i18n-title="col_resize"></div>
|
|
16295
16073
|
|
|
16074
|
+
<!-- ─── Mini (collapsed) sidebar · ChatGPT-style icon rail ───
|
|
16075
|
+
Shown ONLY while body.sidebar-collapsed (the full .sidebar is
|
|
16076
|
+
display:none then). Source order matters: it sits between the
|
|
16077
|
+
(hidden) resizer and <main> so grid auto-placement drops it in
|
|
16078
|
+
column 1 and main in column 2 when collapsed.
|
|
16079
|
+
Icon order: logo (→ expand), new room, new agent, search,
|
|
16080
|
+
reports, notes, divider, rooms (→ expand+tab), agents
|
|
16081
|
+
(→ expand+tab). Foot: user avatar (→ settings). New room /
|
|
16082
|
+
agent / search / reports / notes keep the room collapsed (they
|
|
16083
|
+
reuse the same document-delegated data-* triggers the full
|
|
16084
|
+
sidebar uses); only the logo + rooms + agents expand. -->
|
|
16085
|
+
<aside class="mini-sidebar" aria-label="Collapsed sidebar navigation">
|
|
16086
|
+
<div class="mini-top">
|
|
16087
|
+
<button type="button" class="mini-btn mini-logo" data-sidebar-expand data-i18n-tip="sidebar_expand" data-tip="Expand sidebar" aria-label="Expand sidebar">
|
|
16088
|
+
<span class="mini-logo-av">
|
|
16089
|
+
<img class="cl-open" src="/avatars/chair.svg" alt="" aria-hidden="true">
|
|
16090
|
+
<img class="cl-blink" src="/avatars/chair-blink.svg" alt="" aria-hidden="true">
|
|
16091
|
+
</span>
|
|
16092
|
+
</button>
|
|
16093
|
+
<button type="button" class="mini-btn mini-new-room" data-convene-trigger data-i18n-tip="sidebar_new_room" data-tip="New room" aria-label="New room"></button>
|
|
16094
|
+
<button type="button" class="mini-btn mini-new-agent" data-agent-composer-trigger data-i18n-tip="sidebar_new_agent" data-tip="New agent" aria-label="New agent"></button>
|
|
16095
|
+
<button type="button" class="mini-btn mini-search" data-search-trigger data-tip="Search" aria-label="Search"></button>
|
|
16096
|
+
<button type="button" class="mini-btn mini-reports" data-reports-trigger data-i18n-tip="sidebar_all_reports" data-tip="Reports" aria-label="All Reports"></button>
|
|
16097
|
+
<button type="button" class="mini-btn mini-notes" data-notes-trigger data-i18n-tip="sidebar_all_notes" data-tip="Notes" aria-label="All Notes"></button>
|
|
16098
|
+
<span class="mini-sep" aria-hidden="true"></span>
|
|
16099
|
+
<button type="button" class="mini-btn mini-tab" data-mini-tab="rooms" data-i18n-tip="sidebar_tab_rooms" data-tip="Rooms" aria-label="Rooms">
|
|
16100
|
+
<i class="ic ic-rooms"></i>
|
|
16101
|
+
</button>
|
|
16102
|
+
<button type="button" class="mini-btn mini-tab" data-mini-tab="agents" data-i18n-tip="sidebar_tab_agents" data-tip="Agents" aria-label="Agents">
|
|
16103
|
+
<i class="ic ic-agents"></i>
|
|
16104
|
+
</button>
|
|
16105
|
+
</div>
|
|
16106
|
+
<div class="mini-foot">
|
|
16107
|
+
<a href="#" class="mini-btn mini-user" data-user-settings-trigger data-i18n-tip="settings_title" data-tip="Settings" aria-label="Settings">
|
|
16108
|
+
<div class="user-av mini-user-av" data-user-avatar>K</div>
|
|
16109
|
+
</a>
|
|
16110
|
+
</div>
|
|
16111
|
+
</aside>
|
|
16112
|
+
|
|
16296
16113
|
<!-- ═══════════════ MAIN: room view + agent profile view ═══════════════ -->
|
|
16297
16114
|
<main class="main">
|
|
16298
16115
|
|
|
@@ -16724,15 +16541,69 @@
|
|
|
16724
16541
|
<div class="notes-page" data-notes-page></div>
|
|
16725
16542
|
</div>
|
|
16726
16543
|
|
|
16727
|
-
<!-- Search view · cross-room keyword search results.
|
|
16728
|
-
Filled by app.renderSearchPage() on demand. -->
|
|
16729
|
-
<div class="main-view" data-main-view="search" hidden>
|
|
16730
|
-
<div class="search-page" data-search-page></div>
|
|
16731
|
-
</div>
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
16544
|
</main>
|
|
16735
16545
|
|
|
16546
|
+
<!-- Search overlay · floating command-palette-style search.
|
|
16547
|
+
Triggered by `[data-search-trigger]` (sidebar nav) and Cmd+K /
|
|
16548
|
+
Ctrl+K globally. Replaces the old `.main-view[data-main-view=
|
|
16549
|
+
"search"]` page — current room / agent / etc. stay mounted
|
|
16550
|
+
underneath; the overlay layers on top with a scrim. Result row
|
|
16551
|
+
hrefs (`#/r/{roomId}?m={msgId}&q={q}`) reuse the hash-router
|
|
16552
|
+
navigation that the old search page used, so click behavior is
|
|
16553
|
+
unchanged. Esc / scrim-click / X close. -->
|
|
16554
|
+
<div class="search-overlay" data-search-overlay hidden aria-hidden="true">
|
|
16555
|
+
<div class="search-overlay-scrim" data-search-overlay-close></div>
|
|
16556
|
+
<div class="search-overlay-card" role="dialog" aria-modal="true" aria-labelledby="search-overlay-input">
|
|
16557
|
+
<div class="search-overlay-input-row">
|
|
16558
|
+
<svg class="search-overlay-icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
|
16559
|
+
<circle cx="11" cy="11" r="7"/>
|
|
16560
|
+
<line x1="20" y1="20" x2="16.5" y2="16.5"/>
|
|
16561
|
+
</svg>
|
|
16562
|
+
<input
|
|
16563
|
+
id="search-overlay-input"
|
|
16564
|
+
type="text"
|
|
16565
|
+
class="search-overlay-input"
|
|
16566
|
+
data-search-input
|
|
16567
|
+
data-i18n-placeholder="search_overlay_placeholder"
|
|
16568
|
+
placeholder="Search rooms and messages"
|
|
16569
|
+
autocomplete="off"
|
|
16570
|
+
spellcheck="false"
|
|
16571
|
+
/>
|
|
16572
|
+
<button
|
|
16573
|
+
type="button"
|
|
16574
|
+
class="search-overlay-close-btn"
|
|
16575
|
+
data-search-overlay-close
|
|
16576
|
+
data-i18n-aria="search_overlay_close"
|
|
16577
|
+
data-i18n-title="search_overlay_close"
|
|
16578
|
+
aria-label="Close"
|
|
16579
|
+
title="Close"
|
|
16580
|
+
>
|
|
16581
|
+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
|
16582
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
16583
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
16584
|
+
</svg>
|
|
16585
|
+
</button>
|
|
16586
|
+
</div>
|
|
16587
|
+
<div class="search-overlay-body" data-search-results>
|
|
16588
|
+
<!-- Empty state · shown when the query is blank. `runSearch("")`
|
|
16589
|
+
restores this exact HTML; `runSearch("query")` replaces it
|
|
16590
|
+
first with a "searching…" placeholder, then with the
|
|
16591
|
+
`<ul class="search-overlay-list">` of result rows. -->
|
|
16592
|
+
<div class="search-overlay-empty">
|
|
16593
|
+
<svg class="search-overlay-empty-icon" viewBox="0 0 64 64" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
|
|
16594
|
+
<circle cx="27" cy="27" r="17"/>
|
|
16595
|
+
<line x1="50" y1="50" x2="39" y2="39"/>
|
|
16596
|
+
<line x1="21" y1="27" x2="33" y2="27" opacity="0.45"/>
|
|
16597
|
+
<line x1="21" y1="22" x2="28" y2="22" opacity="0.3"/>
|
|
16598
|
+
<line x1="21" y1="32" x2="30" y2="32" opacity="0.3"/>
|
|
16599
|
+
</svg>
|
|
16600
|
+
<div class="search-overlay-empty-text" data-i18n="search_overlay_empty_title">Search across rooms and messages</div>
|
|
16601
|
+
<div class="search-overlay-empty-hint" data-i18n="search_overlay_empty_hint">Type to filter live · click a result to jump</div>
|
|
16602
|
+
</div>
|
|
16603
|
+
</div>
|
|
16604
|
+
</div>
|
|
16605
|
+
</div>
|
|
16606
|
+
|
|
16736
16607
|
<!-- Floating expand affordance · only visible when the sidebar is
|
|
16737
16608
|
collapsed (body.sidebar-collapsed). Lives INSIDE .body-grid
|
|
16738
16609
|
(which is position: relative) so the button's absolute
|
|
@@ -16881,6 +16752,21 @@
|
|
|
16881
16752
|
clearSidebarPeek();
|
|
16882
16753
|
return;
|
|
16883
16754
|
}
|
|
16755
|
+
// Mini-rail rooms / agents · expand the full sidebar. The tab
|
|
16756
|
+
// switch + first-room/agent default is owned by the sidebar-tab
|
|
16757
|
+
// IIFE's click handler (it has ROOMS_KEY / appFirstRoomId /
|
|
16758
|
+
// activate in scope — they're NOT visible here). Both handlers
|
|
16759
|
+
// fire on the same click: this one expands, that one navigates.
|
|
16760
|
+
// The other mini buttons (new room / agent / search / reports /
|
|
16761
|
+
// notes) deliberately do NOT expand — they fire their own
|
|
16762
|
+
// document-delegated triggers and the rail stays collapsed.
|
|
16763
|
+
if (e.target.closest("[data-mini-tab]")) {
|
|
16764
|
+
e.preventDefault();
|
|
16765
|
+
applySidebarCollapsed(false);
|
|
16766
|
+
writeSidebarCollapsed(false);
|
|
16767
|
+
clearSidebarPeek();
|
|
16768
|
+
return;
|
|
16769
|
+
}
|
|
16884
16770
|
});
|
|
16885
16771
|
|
|
16886
16772
|
// ─── Sidebar edge-peek · Arc-browser style ───
|
|
@@ -16930,6 +16816,10 @@
|
|
|
16930
16816
|
// miss `.sidebar` during the open animation. Immediate close on
|
|
16931
16817
|
// exit is handled by the in→out transition branch below, which
|
|
16932
16818
|
// bypasses this lock.
|
|
16819
|
+
// The floated full sidebar is opaque (--sidebar-bg) at 280px and
|
|
16820
|
+
// pins to left:0, so it cleanly covers the 56px mini rail while
|
|
16821
|
+
// peeked — hovering the left edge reveals the full sidebar, and
|
|
16822
|
+
// moving off it drops back to the rail.
|
|
16933
16823
|
setPeek(true);
|
|
16934
16824
|
peekLockedUntil = Date.now() + PEEK_OPEN_LOCK_MS;
|
|
16935
16825
|
}
|
|
@@ -17186,17 +17076,6 @@
|
|
|
17186
17076
|
}
|
|
17187
17077
|
return;
|
|
17188
17078
|
}
|
|
17189
|
-
// "search" → cross-room keyword search view. Restores on
|
|
17190
|
-
// refresh same as reports / notes — without this branch
|
|
17191
|
-
// the saved token fell through to the roomId resolver,
|
|
17192
|
-
// failed `appRoomExists("search")`, and bounced the user
|
|
17193
|
-
// back to the new-room composer.
|
|
17194
|
-
if (sub === "search") {
|
|
17195
|
-
if (window.app && typeof window.app.openSearch === "function") {
|
|
17196
|
-
window.app.openSearch();
|
|
17197
|
-
}
|
|
17198
|
-
return;
|
|
17199
|
-
}
|
|
17200
17079
|
if (!sub || sub === "new") {
|
|
17201
17080
|
if (window.app && typeof window.app.setComposerMode === "function") {
|
|
17202
17081
|
window.app.setComposerMode("room");
|
|
@@ -17299,9 +17178,9 @@
|
|
|
17299
17178
|
// when there's no saved sub-state at all.
|
|
17300
17179
|
const saved = lsGet(ROOMS_KEY);
|
|
17301
17180
|
let target;
|
|
17302
|
-
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes"
|
|
17181
|
+
if (saved && saved !== "new" && saved !== "reports" && saved !== "notes") {
|
|
17303
17182
|
target = saved; // explicit room id
|
|
17304
|
-
} else if (saved === "reports" || saved === "notes" || saved === "new"
|
|
17183
|
+
} else if (saved === "reports" || saved === "notes" || saved === "new") {
|
|
17305
17184
|
target = saved; // explicit destination preserved on any nav
|
|
17306
17185
|
} else if (fromTabClick) {
|
|
17307
17186
|
// No saved sub-state · fresh user clicking the tab. Prefer
|
|
@@ -17356,6 +17235,34 @@
|
|
|
17356
17235
|
tracker. These don't conflict with stopPropagation because they
|
|
17357
17236
|
match elements that no other capture handler swallows. */
|
|
17358
17237
|
document.addEventListener("click", (e) => {
|
|
17238
|
+
// Mini-rail rooms / agents switcher · expand is handled by the
|
|
17239
|
+
// collapse IIFE; here we own the tab switch + default selection.
|
|
17240
|
+
// Seed the FIRST room / agent when coming from a non-room /
|
|
17241
|
+
// non-agent context (reports, notes, composer, the other tab) so
|
|
17242
|
+
// the icon actually lands you on content instead of restoring the
|
|
17243
|
+
// last sub-state (e.g. "new" / "reports"). If you're already
|
|
17244
|
+
// viewing a room / agent profile, leave it so expand keeps place.
|
|
17245
|
+
const miniTab = e.target.closest("[data-mini-tab]");
|
|
17246
|
+
if (miniTab) {
|
|
17247
|
+
e.preventDefault();
|
|
17248
|
+
const which = miniTab.getAttribute("data-mini-tab");
|
|
17249
|
+
if (which === "rooms") {
|
|
17250
|
+
if (!window.app || !window.app.currentRoomId) {
|
|
17251
|
+
const first = appFirstRoomId();
|
|
17252
|
+
if (first) lsSet(ROOMS_KEY, first);
|
|
17253
|
+
}
|
|
17254
|
+
} else if (which === "agents") {
|
|
17255
|
+
const agentView = document.querySelector('[data-main-view="agent"]');
|
|
17256
|
+
const inAgentProfile = agentView && !agentView.hasAttribute("hidden");
|
|
17257
|
+
if (!inAgentProfile) {
|
|
17258
|
+
const firstAgent = document.querySelector('[data-sidebar-panel="agents"] .agent-row[data-agent-profile]');
|
|
17259
|
+
const id = firstAgent && firstAgent.getAttribute("data-agent-profile");
|
|
17260
|
+
if (id) lsSet(AGENTS_KEY, id);
|
|
17261
|
+
}
|
|
17262
|
+
}
|
|
17263
|
+
activate(which, { fromTabClick: true });
|
|
17264
|
+
return;
|
|
17265
|
+
}
|
|
17359
17266
|
// Tab click
|
|
17360
17267
|
const tab = e.target.closest(".sidebar-tab[data-sidebar-tab]");
|
|
17361
17268
|
if (tab) {
|
|
@@ -17391,11 +17298,12 @@
|
|
|
17391
17298
|
}
|
|
17392
17299
|
return;
|
|
17393
17300
|
}
|
|
17394
|
-
// "Search" trigger ·
|
|
17301
|
+
// "Search" trigger · floating search overlay. No `lsSet`
|
|
17302
|
+
// persistence — the overlay is a transient lens, not a
|
|
17303
|
+
// sub-state worth restoring across reloads.
|
|
17395
17304
|
const searchBtn = e.target.closest("[data-search-trigger]");
|
|
17396
17305
|
if (searchBtn) {
|
|
17397
17306
|
e.preventDefault();
|
|
17398
|
-
lsSet(ROOMS_KEY, "search");
|
|
17399
17307
|
if (window.app && typeof window.app.openSearch === "function") {
|
|
17400
17308
|
window.app.openSearch();
|
|
17401
17309
|
}
|