privateboard 0.1.5 → 0.1.7

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/public/home.html CHANGED
@@ -1304,12 +1304,12 @@
1304
1304
  <span class="ok">▸</span> requires <span class="hl">node 18+</span> · opens at <span class="hl">http://localhost:3030</span>
1305
1305
  </div>
1306
1306
  <div class="install-line ghost">
1307
- <span class="dim"># or, if you prefer homebrew</span>
1307
+ <span class="dim"># or install globally</span>
1308
1308
  </div>
1309
1309
  <div class="install-line">
1310
1310
  <span class="prompt">$</span>
1311
- <span class="cmd"><span class="bin">brew</span> install privateboard</span>
1312
- <button type="button" class="copy-btn" data-copy="brew install privateboard" aria-label="Copy command">copy</button>
1311
+ <span class="cmd"><span class="bin">npm</span> install -g privateboard</span>
1312
+ <button type="button" class="copy-btn" data-copy="npm install -g privateboard" aria-label="Copy command">copy</button>
1313
1313
  </div>
1314
1314
  </div>
1315
1315
  </div>
package/public/index.html CHANGED
@@ -55,7 +55,7 @@
55
55
 
56
56
  /* Logo stays on Inter so the brandmark reads consistently
57
57
  across pages, regardless of the chat-page font swap. */
58
- .brand-name,
58
+ .sidebar-brand-name,
59
59
  .brand-mark {
60
60
  font-family: "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
61
61
  }
@@ -131,78 +131,33 @@
131
131
  }
132
132
  .sys-notice-close:hover { color: var(--lime, #6FB572); }
133
133
 
134
- /* ─────────── ROOT ─────────── */
134
+ /* ─────────── ROOT ───────────
135
+ Single-row grid · the previous decorative top "classification
136
+ banner" (PRIVATEBOARD wordmark + SYNC / UID / ROOM placeholder
137
+ metadata) was retired. The brand mark moved into the sidebar's
138
+ header, and the placeholder fields were never real data. Sidebar
139
+ and room now get the full viewport height. */
135
140
  .control {
136
141
  display: grid;
137
- grid-template-rows: auto 1fr;
142
+ grid-template-rows: 1fr;
138
143
  flex: 1 1 auto;
139
144
  min-height: 0;
140
- gap: 8px;
141
145
  padding: 8px;
142
146
  }
143
147
 
144
- /* ═══════════════════════════════════════════
145
- TOP BAR (classification banner)
146
- ═══════════════════════════════════════════ */
147
- .topbar {
148
- background: var(--panel);
149
- border: 0.5px solid var(--line);
150
- padding: 8px 14px;
151
- display: flex;
152
- justify-content: space-between;
153
- align-items: center;
154
- gap: 20px;
155
- position: relative;
156
- }
157
-
158
- .brand-block {
159
- display: flex;
160
- align-items: center;
161
- gap: 10px;
162
- }
148
+ /* ─── Brand mark · 16-pixel "PrivateBoard" logo ───
149
+ The single remaining piece of the retired topbar — kept because
150
+ the sidebar's header re-uses it as the lock-up logo (replacing
151
+ the prior "// CONTROL" text title). The fill swap (lime bg / bg
152
+ fg) produces the inverse-tile look when rendered against the
153
+ sidebar's panel-2 surface. */
163
154
  .brand-mark {
164
- width: 20px; height: 20px;
155
+ width: 16px; height: 16px;
165
156
  display: block;
166
157
  flex-shrink: 0;
167
158
  }
168
159
  .brand-mark .bg { fill: var(--lime); }
169
160
  .brand-mark .fg { fill: var(--bg); }
170
- .brand-name {
171
- font-size: 11px;
172
- font-weight: 700;
173
- color: var(--text);
174
- letter-spacing: 0.1em;
175
- }
176
- .brand-sep {
177
- color: var(--text-dim);
178
- margin: 0 4px;
179
- }
180
- .classification {
181
- color: var(--lime);
182
- background: var(--panel-3);
183
- border: 0.5px solid var(--lime-dim);
184
- padding: 1px 7px;
185
- font-size: 9px;
186
- letter-spacing: 0.14em;
187
- margin-left: 6px;
188
- font-weight: 700;
189
- }
190
- .classification::before { content: "["; margin-right: 3px; }
191
- .classification::after { content: "]"; margin-left: 3px; }
192
-
193
- .topbar-right {
194
- display: flex;
195
- gap: 18px;
196
- align-items: center;
197
- color: var(--text-dim);
198
- font-size: 9.5px;
199
- letter-spacing: 0.06em;
200
- text-transform: uppercase;
201
- }
202
- /* Topbar key label · bumped from text-faint (~2:1) to text-soft
203
- (~6:1) so the key reads cleanly against the topbar bg. */
204
- .topbar-right .key { color: var(--text-soft); margin-right: 4px; }
205
- .topbar-right .val { color: var(--text-soft); }
206
161
  @keyframes pulse { 0%, 60% { opacity: 1; } 80% { opacity: 0.4; } }
207
162
 
208
163
  /* ═══════════════════════════════════════════
@@ -214,10 +169,10 @@
214
169
  gap: 0;
215
170
  overflow: hidden;
216
171
  min-height: 0;
217
- /* `position: relative` so the floating sidebar-expand-btn (absolute-
218
- positioned) anchors to the body-grid's top-left edge instead of
219
- the viewport that puts the button below the topbar / brand
220
- logo where the user expects it, not on top of it. */
172
+ /* `position: relative` so the floating sidebar-expand-btn
173
+ (absolute-positioned) anchors to the body-grid's top-left
174
+ edgewhich, after the topbar's retirement, is the viewport's
175
+ top-left under the 8px page padding. */
221
176
  position: relative;
222
177
  }
223
178
 
@@ -280,27 +235,37 @@
280
235
  }
281
236
 
282
237
  .sidebar-head {
283
- padding: 6px 12px;
238
+ padding: 8px 12px;
284
239
  border-bottom: 0.5px solid var(--line-bright);
285
240
  display: flex;
286
241
  justify-content: space-between;
287
242
  align-items: center;
243
+ gap: 10px;
288
244
  background: var(--panel-2);
289
245
  }
290
- .sidebar-head-title {
291
- font-size: 10px;
246
+ /* Lock-up · brand mark + wordmark in the slot the prior
247
+ "// CONTROL" title held. Anchored as a link so a click on the
248
+ logo returns to the boardroom root (`/`) — same affordance the
249
+ pre-retired topbar offered. */
250
+ .sidebar-brand {
251
+ display: inline-flex;
252
+ align-items: center;
253
+ gap: 8px;
254
+ text-decoration: none;
255
+ color: inherit;
256
+ min-width: 0;
257
+ }
258
+ .sidebar-brand-name {
259
+ font-size: 11px;
292
260
  color: var(--text);
293
- letter-spacing: 0.14em;
261
+ letter-spacing: 0.12em;
294
262
  text-transform: uppercase;
295
263
  font-weight: 700;
264
+ white-space: nowrap;
265
+ overflow: hidden;
266
+ text-overflow: ellipsis;
296
267
  }
297
- .sidebar-head-meta {
298
- font-size: 9px;
299
- color: var(--text-dim);
300
- letter-spacing: 0.06em;
301
- }
302
- .sidebar-head-meta .lime { color: var(--lime); }
303
-
268
+ .sidebar-brand:hover .sidebar-brand-name { color: var(--lime); }
304
269
  /* ─── Sidebar collapse toggle ───
305
270
  Sits at the right edge of sidebar-head. Pure CSS glyph (no svg)
306
271
  so we can flip the direction via class without re-rendering DOM.
@@ -1178,7 +1143,13 @@
1178
1143
 
1179
1144
  .sidebar-foot {
1180
1145
  border-top: 0.5px solid var(--line-bright);
1181
- padding: 8px 10px;
1146
+ padding: 0 10px;
1147
+ /* Pinned to the same min-height as the room's `.adjourned-bar`
1148
+ (the "Convene Follow-up" rail at the bottom of an adjourned
1149
+ room) so the two footers sit on a single visual baseline
1150
+ regardless of which view is showing. */
1151
+ min-height: 44px;
1152
+ box-sizing: border-box;
1182
1153
  display: flex;
1183
1154
  align-items: center;
1184
1155
  gap: 8px;
@@ -1447,7 +1418,14 @@
1447
1418
  .notes-filter-chip.on .notes-filter-count { color: var(--text-dim); }
1448
1419
 
1449
1420
  /* ─── Reading list ─────────────────────────────────────────────── */
1450
- .reports-list-wrap { margin-top: 18px; }
1421
+ /* Divider hair under the filter chips · group headers were dropped
1422
+ in favour of a single rule + flat list (chips already disclose
1423
+ recency, no need for repeated section headers). */
1424
+ .reports-list-wrap {
1425
+ margin-top: 14px;
1426
+ padding-top: 14px;
1427
+ border-top: 1px solid var(--border);
1428
+ }
1451
1429
 
1452
1430
  /* Load-more sentinel · sits at the bottom of the list when more
1453
1431
  items are available beyond the current page (default 20). The
@@ -1485,21 +1463,6 @@
1485
1463
  line-height: 1;
1486
1464
  }
1487
1465
 
1488
- /* Date-bucket label · sits flush left, hairline rule fills the
1489
- remaining width. Reads as a quiet section break. */
1490
- .reports-group + .reports-group { margin-top: 32px; }
1491
- .reports-group-label {
1492
- font-family: var(--mono);
1493
- font-size: 10px;
1494
- font-weight: 700;
1495
- letter-spacing: 0.22em;
1496
- text-transform: uppercase;
1497
- color: var(--text-faint);
1498
- padding-bottom: 8px;
1499
- border-bottom: 0.5px solid var(--line);
1500
- margin-bottom: 4px;
1501
- }
1502
-
1503
1466
  .reports-list {
1504
1467
  list-style: none;
1505
1468
  margin: 0;
@@ -1783,36 +1746,53 @@
1783
1746
  color: var(--text-dim);
1784
1747
  }
1785
1748
 
1786
- /* Date-bucket sections · each opens with its own hairline-divided
1787
- label row, matching the reports `.reports-group` rhythm. */
1788
- .notes-group + .notes-group { margin-top: 32px; }
1789
- .notes-group:first-of-type { margin-top: 22px; }
1790
- .notes-group-head {
1791
- display: flex;
1792
- align-items: baseline;
1793
- justify-content: space-between;
1794
- padding-bottom: 8px;
1749
+ .notes-list { list-style: none; margin: 0; padding: 0; }
1750
+ /* `position: relative` so the absolutely-positioned delete button
1751
+ (top-right of each note row) anchors to its own item. Hover-only
1752
+ reveal the row stays clean during normal scanning. */
1753
+ .notes-item {
1754
+ position: relative;
1795
1755
  border-bottom: 0.5px solid var(--line);
1796
- margin-bottom: 4px;
1797
1756
  }
1798
- .notes-group-label {
1799
- font-family: var(--mono);
1800
- font-size: 10px;
1801
- font-weight: 700;
1802
- letter-spacing: 0.22em;
1803
- text-transform: uppercase;
1757
+ .notes-item:last-child { border-bottom: none; }
1758
+ .notes-item-delete {
1759
+ position: absolute;
1760
+ top: 16px;
1761
+ right: 6px;
1762
+ width: 22px;
1763
+ height: 22px;
1764
+ background: var(--panel-2);
1765
+ border: 0.5px solid var(--line-bright);
1804
1766
  color: var(--text-faint);
1805
- }
1806
- .notes-group-count {
1807
1767
  font-family: var(--mono);
1808
- font-size: 10px;
1809
- color: var(--text-dim);
1810
- letter-spacing: 0.04em;
1768
+ font-size: 11px;
1769
+ line-height: 1;
1770
+ cursor: pointer;
1771
+ display: none;
1772
+ align-items: center;
1773
+ justify-content: center;
1774
+ padding: 0;
1775
+ transition: color 0.12s, border-color 0.12s, background 0.12s;
1776
+ z-index: 2;
1811
1777
  }
1812
-
1813
- .notes-list { list-style: none; margin: 0; padding: 0; }
1814
- .notes-item { border-bottom: 0.5px solid var(--line); }
1815
- .notes-item:last-child { border-bottom: none; }
1778
+ .notes-item:hover .notes-item-delete { display: flex; }
1779
+ .notes-item-delete:hover {
1780
+ color: var(--red);
1781
+ border-color: var(--red);
1782
+ background: var(--bg);
1783
+ }
1784
+ /* Reserve the right-edge gutter ALWAYS (not only on hover) so the
1785
+ passage text doesn't reflow when the delete button appears. An
1786
+ earlier version applied `padding-right: 30px` only on hover —
1787
+ the text visibly jumped each time the user mousemoved across
1788
+ rows. Cost: a fixed 30px of empty space at the right edge of
1789
+ every passage; cheap compared to the jitter. */
1790
+ .notes-item-passage { padding-right: 30px; }
1791
+ /* On hover · hide the trailing time tag so the delete button
1792
+ occupies the right-edge slot cleanly. visibility:hidden keeps
1793
+ the layout box (the meta row doesn't reflow either). Same
1794
+ pattern as `.session-row-shell:hover .row-time` in rooms. */
1795
+ .notes-item:hover .notes-item-time { visibility: hidden; }
1816
1796
  .notes-item-link {
1817
1797
  display: block;
1818
1798
  padding: 18px 4px;
@@ -2018,6 +1998,63 @@
2018
1998
  color: var(--text);
2019
1999
  background: var(--panel-3);
2020
2000
  }
2001
+ /* Cold empty state · 3-step capture workflow rendered as an
2002
+ ordered list. Each step has a circled number on the left + a
2003
+ bilingual instruction on the right (zh + en together because
2004
+ a note may be saved from either language room). The steps are
2005
+ the load-bearing pedagogy of this view — without them, new
2006
+ users hit "All Notes" and have no idea how to get the first
2007
+ note in. */
2008
+ .notes-empty-steps {
2009
+ list-style: none;
2010
+ margin: 6px 0 4px;
2011
+ padding: 0;
2012
+ display: flex;
2013
+ flex-direction: column;
2014
+ gap: 12px;
2015
+ max-width: 560px;
2016
+ }
2017
+ .notes-empty-steps li {
2018
+ display: grid;
2019
+ grid-template-columns: 22px 1fr;
2020
+ gap: 12px;
2021
+ align-items: start;
2022
+ }
2023
+ .notes-empty-step-num {
2024
+ width: 22px;
2025
+ height: 22px;
2026
+ border-radius: 50%;
2027
+ border: 0.5px solid var(--line-bright);
2028
+ background: var(--panel-3);
2029
+ color: var(--lime);
2030
+ font-family: var(--mono);
2031
+ font-size: 10.5px;
2032
+ font-weight: 700;
2033
+ display: inline-flex;
2034
+ align-items: center;
2035
+ justify-content: center;
2036
+ line-height: 1;
2037
+ }
2038
+ .notes-empty-step-text {
2039
+ font-family: var(--font-human);
2040
+ font-size: 12.5px;
2041
+ line-height: 1.55;
2042
+ color: var(--text-dim);
2043
+ }
2044
+ .notes-empty-step-text strong {
2045
+ color: var(--text);
2046
+ font-weight: 600;
2047
+ }
2048
+ .notes-empty-foot {
2049
+ margin-top: 8px;
2050
+ padding-top: 10px;
2051
+ border-top: 0.5px dashed var(--line-bright);
2052
+ font-family: var(--mono);
2053
+ font-size: 10.5px;
2054
+ line-height: 1.6;
2055
+ color: var(--text-faint);
2056
+ max-width: 560px;
2057
+ }
2021
2058
  /* Window-empty CTA · "← Show all notes" button shown when a
2022
2059
  non-All filter has zero matches. Same visual register as
2023
2060
  `.reports-list-empty-cta`. */
@@ -2049,9 +2086,14 @@
2049
2086
  font-size: 11px;
2050
2087
  line-height: 1;
2051
2088
  }
2052
- /* Wrap around the date-grouped list · matches the reports list
2053
- wrap so the chip strip → list spacing reads identically. */
2054
- .notes-list-wrap { margin-top: 18px; }
2089
+ /* Wrap around the flat list · matches the reports list wrap so
2090
+ the chip strip → divider → list rhythm reads identically. Group
2091
+ headers were dropped in favour of a single hair under the chips. */
2092
+ .notes-list-wrap {
2093
+ margin-top: 14px;
2094
+ padding-top: 14px;
2095
+ border-top: 1px solid var(--border);
2096
+ }
2055
2097
 
2056
2098
  /* Loading skeleton · matches `.reports-skeleton` rhythm and
2057
2099
  reuses its pulse keyframe. */
@@ -2463,10 +2505,15 @@
2463
2505
  50% { opacity: 1; }
2464
2506
  }
2465
2507
 
2466
- /* Paused footer bar (sits where input-bar would be) */
2508
+ /* Paused footer bar (sits where input-bar would be) — shares the
2509
+ `.adjourned-bar` height so the bottom edge of the room view stays
2510
+ on the same baseline as the sidebar's user footer regardless of
2511
+ which state (paused vs adjourned) is showing. */
2467
2512
  .paused-bar {
2468
2513
  border-top: 0.5px solid var(--line-bright);
2469
- padding: 8px 14px;
2514
+ padding: 0 14px;
2515
+ min-height: 44px;
2516
+ box-sizing: border-box;
2470
2517
  background: var(--panel-2);
2471
2518
  display: flex;
2472
2519
  align-items: center;
@@ -2594,7 +2641,11 @@
2594
2641
 
2595
2642
  .adjourned-bar {
2596
2643
  border-top: 0.5px solid var(--line-bright);
2597
- padding: 8px 14px;
2644
+ padding: 0 14px;
2645
+ /* Same min-height as `.sidebar-foot` so the bottom edges of the
2646
+ sidebar footer and this room footer sit on a single baseline. */
2647
+ min-height: 44px;
2648
+ box-sizing: border-box;
2598
2649
  background: var(--panel-2);
2599
2650
  display: flex;
2600
2651
  align-items: center;
@@ -9082,6 +9133,7 @@
9082
9133
  <script src="onboarding.js" defer></script>
9083
9134
  <link rel="stylesheet" href="quote-cta.css">
9084
9135
  <script src="quote-cta.js" defer></script>
9136
+ <script src="typing-sfx.js" defer></script>
9085
9137
  <script src="app.js" defer></script>
9086
9138
  <script>
9087
9139
  /* FOUC-prevention: apply saved theme synchronously before paint */
@@ -9113,41 +9165,31 @@
9113
9165
 
9114
9166
  <div class="control">
9115
9167
 
9116
- <!-- ═══════════════ TOP BAR (classification banner) ═══════════════ -->
9117
- <header class="topbar">
9118
- <div class="brand-block">
9119
- <svg class="brand-mark" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" role="img" aria-label="PrivateBoard">
9120
- <rect class="bg" width="16" height="16"/>
9121
- <rect class="fg" x="3" y="2" width="2" height="2"/>
9122
- <rect class="fg" x="7" y="2" width="2" height="2"/>
9123
- <rect class="fg" x="11" y="2" width="2" height="2"/>
9124
- <rect class="fg" x="2" y="4" width="12" height="2"/>
9125
- <rect class="fg" x="1" y="7" width="14" height="2"/>
9126
- <rect class="fg" x="2" y="10" width="1" height="4"/>
9127
- <rect class="fg" x="13" y="10" width="1" height="4"/>
9128
- </svg>
9129
- <span class="brand-name">PRIVATEBOARD</span>
9130
- <span class="brand-sep">//</span>
9131
- <span class="brand-name">CONTROL</span>
9132
- <span class="classification">PRIVATE ACCESS</span>
9133
- </div>
9134
-
9135
- <div class="topbar-right">
9136
- <div><span class="key">SYNC:</span><span class="val">2026·04·28 · 14:42</span></div>
9137
- <div><span class="key">UID:</span><span class="val">0x4A1F</span></div>
9138
- <div><span class="key">ROOM:</span><span class="val">047 · ROUND 03</span></div>
9139
- </div>
9140
- </header>
9141
-
9142
9168
  <!-- ═══════════════ MAIN GRID ═══════════════ -->
9169
+ <!-- The previous `<header class="topbar">` (decorative classification
9170
+ banner with brand mark + SYNC / UID / ROOM placeholder fields)
9171
+ has been retired. The brand mark moved into `.sidebar-head` as
9172
+ the lock-up logo; the placeholder fields were never load-bearing
9173
+ data. Sidebar + room now reach the top of the viewport. -->
9143
9174
  <div class="body-grid">
9144
9175
 
9145
9176
  <!-- ═══════════════ SIDEBAR ═══════════════ -->
9146
9177
  <aside class="sidebar">
9147
9178
 
9148
9179
  <div class="sidebar-head">
9149
- <div class="sidebar-head-title">// CONTROL</div>
9150
- <div class="sidebar-head-meta"><span class="lime">●</span> <span data-sidebar-summary>0 LIVE / 0 AGENTS</span></div>
9180
+ <a href="/" class="sidebar-brand" aria-label="PrivateBoard">
9181
+ <svg class="brand-mark" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" role="img" aria-label="PrivateBoard">
9182
+ <rect class="bg" width="16" height="16"/>
9183
+ <rect class="fg" x="3" y="2" width="2" height="2"/>
9184
+ <rect class="fg" x="7" y="2" width="2" height="2"/>
9185
+ <rect class="fg" x="11" y="2" width="2" height="2"/>
9186
+ <rect class="fg" x="2" y="4" width="12" height="2"/>
9187
+ <rect class="fg" x="1" y="7" width="14" height="2"/>
9188
+ <rect class="fg" x="2" y="10" width="1" height="4"/>
9189
+ <rect class="fg" x="13" y="10" width="1" height="4"/>
9190
+ </svg>
9191
+ <span class="sidebar-brand-name">PRIVATEBOARD</span>
9192
+ </a>
9151
9193
  <button type="button" class="sidebar-collapse-btn" data-sidebar-collapse aria-label="Collapse sidebar" title="Collapse sidebar"></button>
9152
9194
  </div>
9153
9195