privateboard 0.1.36 → 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 +45 -4
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +45 -4
- package/dist/cli.js.map +1 -1
- package/dist/server.js +45 -4
- 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/app.js +187 -15
- 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 +12 -0
- package/public/icons/folded-sidebar.png +0 -0
- package/public/index.html +355 -86
- package/public/report.html +27 -7
- package/public/user-settings.js +37 -20
- package/public/voice-3d.js +167 -3
- package/public/icons/search.png +0 -0
package/public/index.html
CHANGED
|
@@ -69,6 +69,9 @@
|
|
|
69
69
|
system-ui, sans-serif;
|
|
70
70
|
|
|
71
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;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
/* Logo stays on Inter so the brandmark reads consistently
|
|
@@ -670,14 +673,6 @@
|
|
|
670
673
|
html.is-electron-mac .room-head .head-cast img {
|
|
671
674
|
-webkit-app-region: no-drag;
|
|
672
675
|
}
|
|
673
|
-
/* The expand-btn's ::after halo paints OUTSIDE the button rect
|
|
674
|
-
(inset: -16px), so it doesn't inherit the button's no-drag and
|
|
675
|
-
gets eaten by `.room-head { drag }` above. Stamp no-drag on the
|
|
676
|
-
pseudo directly so the 48×48 hit zone keeps registering clicks.
|
|
677
|
-
(Same shape as the sidebar-collapse-btn::after override.) */
|
|
678
|
-
html.is-electron-mac .room-head .room-head-expand::after {
|
|
679
|
-
-webkit-app-region: no-drag;
|
|
680
|
-
}
|
|
681
676
|
/* ─── macOS Electron · top-strip drag coverage for non-room views.
|
|
682
677
|
When the room view is hidden (All Reports / All Notes / Search /
|
|
683
678
|
Agent Profile) or the room view is shown with no current room (the
|
|
@@ -839,22 +834,28 @@
|
|
|
839
834
|
room. Single column = main fills the whole body-grid cleanly.
|
|
840
835
|
The user's preferred --sidebar-w stays in localStorage so
|
|
841
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. */
|
|
842
841
|
body.sidebar-collapsed .body-grid {
|
|
843
|
-
grid-template-columns: 1fr !important;
|
|
842
|
+
grid-template-columns: var(--mini-sidebar-w) 1fr !important;
|
|
844
843
|
}
|
|
845
844
|
body.sidebar-collapsed .sidebar,
|
|
846
845
|
body.sidebar-collapsed .col-resizer {
|
|
847
846
|
display: none;
|
|
848
847
|
}
|
|
848
|
+
body.sidebar-collapsed .mini-sidebar {
|
|
849
|
+
display: flex;
|
|
850
|
+
}
|
|
849
851
|
/* ─── Hover peek · floating sidebar while collapsed ───
|
|
850
|
-
When the
|
|
851
|
-
`.sidebar-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
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. */
|
|
858
859
|
body.sidebar-collapsed.sidebar-peek .sidebar {
|
|
859
860
|
display: flex;
|
|
860
861
|
position: absolute;
|
|
@@ -913,12 +914,14 @@
|
|
|
913
914
|
appearance: none;
|
|
914
915
|
padding: 0;
|
|
915
916
|
}
|
|
916
|
-
/*
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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. */
|
|
920
923
|
html.no-room body.sidebar-collapsed .sidebar-expand-btn {
|
|
921
|
-
display:
|
|
924
|
+
display: none;
|
|
922
925
|
}
|
|
923
926
|
.sidebar-expand-btn:hover {
|
|
924
927
|
color: var(--lime);
|
|
@@ -961,67 +964,230 @@
|
|
|
961
964
|
-webkit-app-region: no-drag;
|
|
962
965
|
}
|
|
963
966
|
|
|
964
|
-
/* ───
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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;
|
|
976
1017
|
background: transparent;
|
|
977
|
-
border: 0;
|
|
978
1018
|
color: var(--text-soft);
|
|
1019
|
+
border-radius: 8px;
|
|
979
1020
|
cursor: pointer;
|
|
980
|
-
|
|
981
|
-
height: 16px;
|
|
982
|
-
padding: 0;
|
|
1021
|
+
text-decoration: none;
|
|
983
1022
|
flex-shrink: 0;
|
|
984
|
-
transition: color 0.12s;
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1023
|
+
transition: background 0.12s, color 0.12s;
|
|
1024
|
+
appearance: none;
|
|
1025
|
+
}
|
|
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;
|
|
989
1058
|
}
|
|
990
|
-
.
|
|
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 {
|
|
991
1080
|
content: "";
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1081
|
+
width: 18px;
|
|
1082
|
+
height: 18px;
|
|
1083
|
+
flex-shrink: 0;
|
|
995
1084
|
background-color: currentColor;
|
|
996
|
-
-webkit-mask-image:
|
|
997
|
-
mask-image:
|
|
1085
|
+
-webkit-mask-image: var(--icon, none);
|
|
1086
|
+
mask-image: var(--icon, none);
|
|
998
1087
|
-webkit-mask-repeat: no-repeat;
|
|
999
1088
|
mask-repeat: no-repeat;
|
|
1000
1089
|
-webkit-mask-position: center;
|
|
1001
1090
|
mask-position: center;
|
|
1002
|
-
-webkit-mask-size:
|
|
1003
|
-
mask-size:
|
|
1004
|
-
|
|
1005
|
-
body.sidebar-collapsed · since this button is ONLY visible
|
|
1006
|
-
in the collapsed state, the flip is unconditional here. */
|
|
1007
|
-
transform: scaleX(-1);
|
|
1091
|
+
-webkit-mask-size: 18px 18px;
|
|
1092
|
+
mask-size: 18px 18px;
|
|
1093
|
+
transition: color 0.12s;
|
|
1008
1094
|
}
|
|
1009
|
-
/*
|
|
1010
|
-
|
|
1011
|
-
|
|
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 {
|
|
1012
1158
|
content: "";
|
|
1013
1159
|
position: absolute;
|
|
1014
|
-
|
|
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;
|
|
1015
1170
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
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;
|
|
1023
1190
|
}
|
|
1024
|
-
.room-head-expand:hover { color: var(--lime); }
|
|
1025
1191
|
|
|
1026
1192
|
/* ─── Sidebar tabs (Rooms / Agents) — bar-style nav ───
|
|
1027
1193
|
Reference: `public/icons/bar.png`. Two layers:
|
|
@@ -1507,27 +1673,33 @@
|
|
|
1507
1673
|
[data-convene-trigger]::before {
|
|
1508
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>");
|
|
1509
1675
|
}
|
|
1510
|
-
/* "New agent" ·
|
|
1511
|
-
|
|
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. */
|
|
1512
1681
|
[data-agent-composer-trigger]::before {
|
|
1513
|
-
--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>");
|
|
1514
1683
|
}
|
|
1515
1684
|
/* "All Reports" · FileText (Lucide) — single document with three
|
|
1516
1685
|
text lines, cleaner than the previous gradient-stack glyph. */
|
|
1517
|
-
.new-btn.nav-reports::before
|
|
1686
|
+
.new-btn.nav-reports::before,
|
|
1687
|
+
.mini-reports::before {
|
|
1518
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>");
|
|
1519
1689
|
}
|
|
1520
1690
|
/* "All Notes" · Bookmark (Lucide) — pennant-shaped bookmark glyph
|
|
1521
1691
|
mirroring the qcta save-button icon. Matches the chairman's-notes
|
|
1522
1692
|
vocabulary across the app (sidebar entry, save action, in-room
|
|
1523
1693
|
overlay all share the bookmark register). */
|
|
1524
|
-
.new-btn.nav-notes::before
|
|
1694
|
+
.new-btn.nav-notes::before,
|
|
1695
|
+
.mini-notes::before {
|
|
1525
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>");
|
|
1526
1697
|
}
|
|
1527
1698
|
/* "Search" · Search (Lucide) — circle + diagonal handle. Standard
|
|
1528
1699
|
magnifying-glass glyph in the same Lucide line-icon vocabulary as
|
|
1529
1700
|
the rest of the sidebar. */
|
|
1530
|
-
.new-btn.nav-search::before
|
|
1701
|
+
.new-btn.nav-search::before,
|
|
1702
|
+
.mini-search::before {
|
|
1531
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>");
|
|
1532
1704
|
}
|
|
1533
1705
|
/* All Reports / All Notes / Search nav-button shape · label-only. */
|
|
@@ -1888,11 +2060,12 @@
|
|
|
1888
2060
|
overflow: hidden;
|
|
1889
2061
|
}
|
|
1890
2062
|
/* Pixel-art variant · when prefs.avatarSeed exists, app.renderUserBlock
|
|
1891
|
-
swaps the initial-letter chip for an AvatarSkill SVG.
|
|
1892
|
-
|
|
1893
|
-
|
|
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. */
|
|
1894
2067
|
.user-av.has-pixel-av {
|
|
1895
|
-
background:
|
|
2068
|
+
background: transparent;
|
|
1896
2069
|
}
|
|
1897
2070
|
.user-av.has-pixel-av svg {
|
|
1898
2071
|
width: 100%;
|
|
@@ -3722,15 +3895,6 @@
|
|
|
3722
3895
|
-webkit-backdrop-filter: none;
|
|
3723
3896
|
}
|
|
3724
3897
|
}
|
|
3725
|
-
/* When the sidebar is collapsed the room-head gains a leading
|
|
3726
|
-
auto-sized track for the in-header `.room-head-expand` button.
|
|
3727
|
-
When expanded the button is display:none, so a 0-width track
|
|
3728
|
-
would still leave a `gap` artifact — switching to a 3-track
|
|
3729
|
-
template only in the collapsed state keeps the header visually
|
|
3730
|
-
identical to before whenever the sidebar is open. */
|
|
3731
|
-
body.sidebar-collapsed .room-head {
|
|
3732
|
-
grid-template-columns: auto 1fr auto;
|
|
3733
|
-
}
|
|
3734
3898
|
/* `overflow: visible` so the tone-tag hover tooltip (positioned via
|
|
3735
3899
|
::after below the tag) can escape this container. The room-subject
|
|
3736
3900
|
has its own overflow:hidden + text-overflow ellipsis rule, so the
|
|
@@ -8634,6 +8798,25 @@
|
|
|
8634
8798
|
.rt-avatar[data-agent]:hover {
|
|
8635
8799
|
filter: brightness(1.18);
|
|
8636
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
|
+
}
|
|
8637
8820
|
|
|
8638
8821
|
/* Name plate · small mono caption ABOVE the avatar (was below,
|
|
8639
8822
|
but back-row director seats had their names land on the table
|
|
@@ -15888,6 +16071,45 @@
|
|
|
15888
16071
|
<!-- Resizable handle: sidebar | main -->
|
|
15889
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>
|
|
15890
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
|
+
|
|
15891
16113
|
<!-- ═══════════════ MAIN: room view + agent profile view ═══════════════ -->
|
|
15892
16114
|
<main class="main">
|
|
15893
16115
|
|
|
@@ -16530,6 +16752,21 @@
|
|
|
16530
16752
|
clearSidebarPeek();
|
|
16531
16753
|
return;
|
|
16532
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
|
+
}
|
|
16533
16770
|
});
|
|
16534
16771
|
|
|
16535
16772
|
// ─── Sidebar edge-peek · Arc-browser style ───
|
|
@@ -16579,6 +16816,10 @@
|
|
|
16579
16816
|
// miss `.sidebar` during the open animation. Immediate close on
|
|
16580
16817
|
// exit is handled by the in→out transition branch below, which
|
|
16581
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.
|
|
16582
16823
|
setPeek(true);
|
|
16583
16824
|
peekLockedUntil = Date.now() + PEEK_OPEN_LOCK_MS;
|
|
16584
16825
|
}
|
|
@@ -16994,6 +17235,34 @@
|
|
|
16994
17235
|
tracker. These don't conflict with stopPropagation because they
|
|
16995
17236
|
match elements that no other capture handler swallows. */
|
|
16996
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
|
+
}
|
|
16997
17266
|
// Tab click
|
|
16998
17267
|
const tab = e.target.closest(".sidebar-tab[data-sidebar-tab]");
|
|
16999
17268
|
if (tab) {
|
package/public/report.html
CHANGED
|
@@ -5041,19 +5041,20 @@
|
|
|
5041
5041
|
const intro = typeof parsed.intro === "string" ? parsed.intro.trim() : "";
|
|
5042
5042
|
const chairSynthesis = typeof parsed.chairSynthesis === "string" ? parsed.chairSynthesis.trim() : "";
|
|
5043
5043
|
|
|
5044
|
-
// Resolve director id → display name from the
|
|
5045
|
-
//
|
|
5046
|
-
//
|
|
5047
|
-
//
|
|
5044
|
+
// Resolve director id → display name from the global stash that
|
|
5045
|
+
// `load()` populates from state.members + historicalMembers +
|
|
5046
|
+
// chair. Falls back to scanning `[data-director-id]` chips for
|
|
5047
|
+
// legacy / forward-compat (none currently rendered, but cheap),
|
|
5048
|
+
// and finally to the id itself when nothing matches.
|
|
5048
5049
|
const directorNameById = (() => {
|
|
5049
|
-
const out = {};
|
|
5050
|
+
const out = Object.assign({}, window.__reportAgentNames || {});
|
|
5050
5051
|
try {
|
|
5051
5052
|
const chips = document.querySelectorAll("[data-director-id]");
|
|
5052
5053
|
chips.forEach((el) => {
|
|
5053
5054
|
const id = el.getAttribute("data-director-id");
|
|
5054
|
-
if (id && el.textContent) out[id] = el.textContent.trim();
|
|
5055
|
+
if (id && el.textContent && !out[id]) out[id] = el.textContent.trim();
|
|
5055
5056
|
});
|
|
5056
|
-
} catch (_) { /*
|
|
5057
|
+
} catch (_) { /* defensive · DOM may not be ready */ }
|
|
5057
5058
|
return out;
|
|
5058
5059
|
})();
|
|
5059
5060
|
const dn = (id) => directorNameById[id] || id || "—";
|
|
@@ -6420,6 +6421,25 @@
|
|
|
6420
6421
|
const j = await stateRes.json();
|
|
6421
6422
|
room = j.room;
|
|
6422
6423
|
members = j.members || [];
|
|
6424
|
+
// Author-id → display-name map · drives `renderViewsCompared`'s
|
|
6425
|
+
// director chips ("Every director on the table", alignment
|
|
6426
|
+
// cards, divergence sides). Earlier the helper scraped
|
|
6427
|
+
// `[data-director-id]` chips off the rendered DOM, but no
|
|
6428
|
+
// chip ever carried that attribute — the lookup always fell
|
|
6429
|
+
// through and the chips printed raw agent ids like
|
|
6430
|
+
// `1khezh9da65g`. Stash a real lookup on window from BOTH
|
|
6431
|
+
// active members and historicalMembers (excused directors
|
|
6432
|
+
// still appear in views-compared if they participated) plus
|
|
6433
|
+
// the chair (occasionally referenced too).
|
|
6434
|
+
const nameMap = {};
|
|
6435
|
+
for (const m of (j.members || [])) {
|
|
6436
|
+
if (m && m.id && m.name) nameMap[m.id] = m.name;
|
|
6437
|
+
}
|
|
6438
|
+
for (const m of (j.historicalMembers || [])) {
|
|
6439
|
+
if (m && m.id && m.name && !nameMap[m.id]) nameMap[m.id] = m.name;
|
|
6440
|
+
}
|
|
6441
|
+
if (j.chair && j.chair.id && j.chair.name) nameMap[j.chair.id] = j.chair.name;
|
|
6442
|
+
window.__reportAgentNames = nameMap;
|
|
6423
6443
|
} else {
|
|
6424
6444
|
members = [];
|
|
6425
6445
|
}
|