privateboard 0.1.13 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2623 -333
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/adjourn-overlay.css +6 -6
- package/public/agent-build-bgm.js +292 -0
- package/public/agent-overlay.css +14 -14
- package/public/agent-profile.css +408 -87
- package/public/agent-profile.js +254 -0
- package/public/app.js +2486 -384
- package/public/home.html +26 -26
- package/public/i18n.js +1890 -21
- package/public/icons/logo2.png +0 -0
- package/public/icons/private-board-vi.html +1716 -0
- package/public/index.html +2954 -1018
- package/public/magazine.html +12 -12
- package/public/new-agent.css +29 -29
- package/public/newspaper.html +20 -20
- package/public/onboarding.css +350 -272
- package/public/onboarding.js +614 -323
- package/public/quote-cta.css +4 -4
- package/public/report.html +2008 -1673
- package/public/room-settings.css +192 -24
- package/public/room-settings.js +5 -0
- package/public/share-cover-svg-creator.js +736 -0
- package/public/themes.css +0 -34
- package/public/typing-sfx.js +176 -3
- package/public/user-settings.css +50 -27
- package/public/user-settings.js +43 -14
- package/public/voice-onboarding.css +425 -0
- package/public/voice-onboarding.js +144 -0
- package/public/voice-replay.css +31 -38
- package/public/voice-replay.js +12 -11
package/public/themes.css
CHANGED
|
@@ -218,40 +218,6 @@
|
|
|
218
218
|
--magenta: #6E4F73;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
/* ─── APPLE · pure-white minimalism · Apple.com aesthetic ─────
|
|
222
|
-
Light theme inspired by Apple's brand language: pure-white
|
|
223
|
-
primary background, the iconic #F5F5F7 off-white for cards,
|
|
224
|
-
Apple's near-black #1D1D1F for text, and system-blue #0071E3
|
|
225
|
-
mapped onto --lime (the codebase's primary-accent role) so
|
|
226
|
-
every CTA reads as that signature blue. */
|
|
227
|
-
:root[data-theme="apple"] {
|
|
228
|
-
--bg: #FFFFFF;
|
|
229
|
-
--panel: #F5F5F7;
|
|
230
|
-
--panel-2: #FBFBFD;
|
|
231
|
-
--panel-3: #ECECEE;
|
|
232
|
-
--hi: #D2D2D7;
|
|
233
|
-
|
|
234
|
-
--line: #E5E5EA;
|
|
235
|
-
--line-bright: #D2D2D7;
|
|
236
|
-
--line-strong: #86868B;
|
|
237
|
-
|
|
238
|
-
--text: #1D1D1F;
|
|
239
|
-
--text-soft: #424245;
|
|
240
|
-
--text-dim: #6E6E73;
|
|
241
|
-
--text-faint: #86868B;
|
|
242
|
-
|
|
243
|
-
--lime: #0071E3;
|
|
244
|
-
--lime-deep: #0051A8;
|
|
245
|
-
--lime-dim: #D6E8FA;
|
|
246
|
-
|
|
247
|
-
--amber: #FF9500;
|
|
248
|
-
--amber-dim: #FFE6C2;
|
|
249
|
-
--red: #FF3B30;
|
|
250
|
-
--red-dim: #FFE0DC;
|
|
251
|
-
--cyan: #5AC8FA;
|
|
252
|
-
--magenta: #BF5AF2;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
221
|
/* ─── PINTEREST · clean white surface, signature red accent ───
|
|
256
222
|
Light theme inspired by Pinterest's brand: true-white
|
|
257
223
|
background, soft gray cards, neutral grayscale text, and the
|
package/public/typing-sfx.js
CHANGED
|
@@ -79,7 +79,17 @@
|
|
|
79
79
|
|
|
80
80
|
function ensureContext() {
|
|
81
81
|
if (_ctxFailed) return null;
|
|
82
|
-
if (_ctx)
|
|
82
|
+
if (_ctx) {
|
|
83
|
+
// Resume on EVERY call when suspended · we proactively suspend
|
|
84
|
+
// the context between SFX bursts (see `releaseContextSoon`) to
|
|
85
|
+
// release the audio session for the HTMLAudioElement TTS path.
|
|
86
|
+
// Without this re-check, a context suspended for TTS would
|
|
87
|
+
// stay silent on the next tick/blip/gavel.
|
|
88
|
+
if (_ctx.state === "suspended") {
|
|
89
|
+
_ctx.resume().catch(() => { /* swallow */ });
|
|
90
|
+
}
|
|
91
|
+
return _ctx;
|
|
92
|
+
}
|
|
83
93
|
if (!_hadGesture) return null; // refuse to create until gestured
|
|
84
94
|
try {
|
|
85
95
|
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
@@ -97,6 +107,33 @@
|
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
|
|
110
|
+
/** Suspend the AudioContext shortly after `setThinking(false)` so
|
|
111
|
+
* the system audio session is released for the HTMLAudioElement
|
|
112
|
+
* TTS path. Browsers (Safari / iOS especially) treat a "running"
|
|
113
|
+
* AudioContext as the active audio source; an `<audio>` element
|
|
114
|
+
* starting `play()` against that state can be blocked or silently
|
|
115
|
+
* routed away.
|
|
116
|
+
*
|
|
117
|
+
* We don't suspend instantly · a small grace lets the thinking
|
|
118
|
+
* fade-out finish without click. */
|
|
119
|
+
let _releaseTimer = null;
|
|
120
|
+
function releaseContextSoon() {
|
|
121
|
+
if (!_ctx) return;
|
|
122
|
+
if (_releaseTimer) { clearTimeout(_releaseTimer); _releaseTimer = null; }
|
|
123
|
+
_releaseTimer = setTimeout(() => {
|
|
124
|
+
_releaseTimer = null;
|
|
125
|
+
// Only suspend when nothing has restarted the loop in the
|
|
126
|
+
// meantime · belt-and-suspenders against rapid toggle races.
|
|
127
|
+
if (!_thinkingInterval && _ctx && _ctx.state === "running") {
|
|
128
|
+
_ctx.suspend().catch(() => { /* swallow */ });
|
|
129
|
+
}
|
|
130
|
+
}, 200);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function cancelRelease() {
|
|
134
|
+
if (_releaseTimer) { clearTimeout(_releaseTimer); _releaseTimer = null; }
|
|
135
|
+
}
|
|
136
|
+
|
|
100
137
|
function tick() {
|
|
101
138
|
if (!_enabled) return;
|
|
102
139
|
if (document.visibilityState !== "visible") return; // background tab
|
|
@@ -171,6 +208,91 @@
|
|
|
171
208
|
osc.stop(t0 + dur);
|
|
172
209
|
}
|
|
173
210
|
|
|
211
|
+
/** Director thinking cue · loops the original 8-bit "blip-blip"
|
|
212
|
+
* pair while a voice-room seat shows the thought-bubble. Each
|
|
213
|
+
* cycle: two pulse-wave blips spaced 60 ms apart (G5 → B5, a
|
|
214
|
+
* minor third up · reads as "thought lifting"), each blip about
|
|
215
|
+
* 60 ms long with a fast attack and exponential decay. Cycles
|
|
216
|
+
* repeat every ~1100 ms — generous silence between pairs so the
|
|
217
|
+
* rhythm feels like classic NES dialog blips, not an alarm.
|
|
218
|
+
*
|
|
219
|
+
* Per-blip oscillator creation is recreated on each tick · the
|
|
220
|
+
* earlier TTS conflict turned out to be a missing TTS provider
|
|
221
|
+
* key, not AudioContext churn, so we can safely use setInterval
|
|
222
|
+
* for the loop. */
|
|
223
|
+
let _thinkingInterval = null;
|
|
224
|
+
let _thinkingPhase = 0;
|
|
225
|
+
|
|
226
|
+
function _playThinkingPair() {
|
|
227
|
+
if (!_enabled) { setThinking(false); return; }
|
|
228
|
+
if (document.visibilityState !== "visible") return;
|
|
229
|
+
const ctx = ensureContext();
|
|
230
|
+
if (!ctx) return;
|
|
231
|
+
const t0 = ctx.currentTime;
|
|
232
|
+
// Two blips · G5 (784 Hz) then B5 (988 Hz). 60 ms gap between
|
|
233
|
+
// blip starts so the ear hears them as a pair, not a chord.
|
|
234
|
+
// Phase-flip every cycle (B5→G5 alternation across cycles)
|
|
235
|
+
// adds slight melodic interest, like a thinker switching gears.
|
|
236
|
+
const ascending = _thinkingPhase % 2 === 0;
|
|
237
|
+
_thinkingPhase = (_thinkingPhase + 1) % 1024;
|
|
238
|
+
const blips = ascending
|
|
239
|
+
? [{ freq: 784, start: 0.000 }, { freq: 988, start: 0.060 }]
|
|
240
|
+
: [{ freq: 988, start: 0.000 }, { freq: 784, start: 0.060 }];
|
|
241
|
+
for (const b of blips) {
|
|
242
|
+
const t = t0 + b.start;
|
|
243
|
+
const osc = ctx.createOscillator();
|
|
244
|
+
osc.type = "square";
|
|
245
|
+
osc.frequency.value = b.freq;
|
|
246
|
+
// Low-pass softens the harsh square edges so the blip sits
|
|
247
|
+
// in the room ambience rather than slicing through it.
|
|
248
|
+
const lpf = ctx.createBiquadFilter();
|
|
249
|
+
lpf.type = "lowpass";
|
|
250
|
+
lpf.frequency.value = 4500;
|
|
251
|
+
lpf.Q.value = 0.7;
|
|
252
|
+
const gain = ctx.createGain();
|
|
253
|
+
gain.gain.setValueAtTime(0.0001, t);
|
|
254
|
+
gain.gain.exponentialRampToValueAtTime(0.05, t + 0.005);
|
|
255
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.06);
|
|
256
|
+
osc.connect(lpf).connect(gain).connect(ctx.destination);
|
|
257
|
+
osc.start(t);
|
|
258
|
+
const stopAt = t + 0.08;
|
|
259
|
+
osc.stop(stopAt);
|
|
260
|
+
// Cleanup · disconnect after stop so GainNodes aren't retained
|
|
261
|
+
// across hundreds of blips on a long thinking phase.
|
|
262
|
+
osc.onended = () => {
|
|
263
|
+
try { osc.disconnect(); } catch { /* ignore */ }
|
|
264
|
+
try { lpf.disconnect(); } catch { /* ignore */ }
|
|
265
|
+
try { gain.disconnect(); } catch { /* ignore */ }
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function setThinking(on) {
|
|
271
|
+
if (on) {
|
|
272
|
+
cancelRelease();
|
|
273
|
+
if (_thinkingInterval) return; // already looping · idempotent
|
|
274
|
+
if (!_enabled) return;
|
|
275
|
+
// First pair immediately so the user hears feedback the moment
|
|
276
|
+
// the bubble appears; subsequent pairs on a ~1100 ms cadence.
|
|
277
|
+
// 1100 ms (~0.9 Hz) is intentionally slow — earlier 700 ms /
|
|
278
|
+
// 1.4 Hz read as an alarm tempo; this sits closer to a
|
|
279
|
+
// contemplative NES dialog cadence.
|
|
280
|
+
_thinkingPhase = 0;
|
|
281
|
+
_playThinkingPair();
|
|
282
|
+
_thinkingInterval = setInterval(_playThinkingPair, 1100);
|
|
283
|
+
} else {
|
|
284
|
+
if (!_thinkingInterval) return;
|
|
285
|
+
clearInterval(_thinkingInterval);
|
|
286
|
+
_thinkingInterval = null;
|
|
287
|
+
_thinkingPhase = 0;
|
|
288
|
+
// Suspend the AudioContext after a brief grace so the audio
|
|
289
|
+
// session is fully released for any HTMLAudioElement TTS that
|
|
290
|
+
// might be about to play. ensureContext resumes automatically
|
|
291
|
+
// on the next SFX call.
|
|
292
|
+
releaseContextSoon();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
174
296
|
/** Chair gavel cue · fires before chair voice playback begins in
|
|
175
297
|
* voice mode. Two-strike wooden knock that reads as "court is in
|
|
176
298
|
* session — listen up." Designed by ear:
|
|
@@ -228,10 +350,61 @@
|
|
|
228
350
|
}
|
|
229
351
|
}
|
|
230
352
|
|
|
353
|
+
/** Continue / vote auto-fire countdown beep · fires once per second
|
|
354
|
+
* on the 10 → 1 visible tick so the user feels the timer urgency
|
|
355
|
+
* audibly, not just visually. Two tiers:
|
|
356
|
+
* · seconds 10-4 · low square-wave (~600 Hz), 50 ms, quiet — a
|
|
357
|
+
* steady "metronome" tick that's clearly background.
|
|
358
|
+
* · seconds 3-1 · higher square-wave (~880 Hz), 80 ms, louder —
|
|
359
|
+
* the "3-2-1!" alarm register that signals imminent fire.
|
|
360
|
+
* Second 0 is skipped here; the auto-continue action that fires
|
|
361
|
+
* on hit-zero has its own UX (the room moves on). Low-pass keeps
|
|
362
|
+
* the square edges from slicing through, same approach as the
|
|
363
|
+
* thinking-blip pair. */
|
|
364
|
+
function countdownTick(secondsLeft) {
|
|
365
|
+
if (!_enabled) return;
|
|
366
|
+
if (document.visibilityState !== "visible") return;
|
|
367
|
+
if (typeof secondsLeft !== "number" || secondsLeft <= 0) return;
|
|
368
|
+
const ctx = ensureContext();
|
|
369
|
+
if (!ctx) return;
|
|
370
|
+
|
|
371
|
+
const urgent = secondsLeft <= 3;
|
|
372
|
+
const freq = urgent ? 880 : 600;
|
|
373
|
+
const dur = urgent ? 0.08 : 0.05;
|
|
374
|
+
const peak = urgent ? 0.075 : 0.045;
|
|
375
|
+
|
|
376
|
+
const t0 = ctx.currentTime;
|
|
377
|
+
const osc = ctx.createOscillator();
|
|
378
|
+
osc.type = "square";
|
|
379
|
+
osc.frequency.value = freq;
|
|
380
|
+
const lpf = ctx.createBiquadFilter();
|
|
381
|
+
lpf.type = "lowpass";
|
|
382
|
+
lpf.frequency.value = 4000;
|
|
383
|
+
lpf.Q.value = 0.7;
|
|
384
|
+
const gain = ctx.createGain();
|
|
385
|
+
gain.gain.setValueAtTime(0.0001, t0);
|
|
386
|
+
gain.gain.exponentialRampToValueAtTime(peak, t0 + 0.005);
|
|
387
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
|
388
|
+
osc.connect(lpf).connect(gain).connect(ctx.destination);
|
|
389
|
+
osc.start(t0);
|
|
390
|
+
osc.stop(t0 + dur + 0.02);
|
|
391
|
+
osc.onended = () => {
|
|
392
|
+
try { osc.disconnect(); } catch { /* ignore */ }
|
|
393
|
+
try { lpf.disconnect(); } catch { /* ignore */ }
|
|
394
|
+
try { gain.disconnect(); } catch { /* ignore */ }
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
231
398
|
function setEnabled(on) {
|
|
232
399
|
_enabled = !!on;
|
|
233
400
|
writeEnabled(_enabled);
|
|
234
|
-
//
|
|
401
|
+
// Stop any in-flight thinking loop when SFX gets disabled · we
|
|
402
|
+
// don't want a stale setInterval ticking silently and resuming
|
|
403
|
+
// audio the moment the user toggles back on inside the same
|
|
404
|
+
// thinking phase. Re-entry happens cleanly via the next
|
|
405
|
+
// `setThinking(true)` call from the render loop.
|
|
406
|
+
if (!_enabled) setThinking(false);
|
|
407
|
+
// No-op for AudioContext when disabling · stays alive cheaply
|
|
235
408
|
// (a few KB) and an outright close() leaves us re-paying the
|
|
236
409
|
// creation cost if the user toggles back on within the session.
|
|
237
410
|
}
|
|
@@ -240,5 +413,5 @@
|
|
|
240
413
|
|
|
241
414
|
// Public surface · attached to window so app.js (and the
|
|
242
415
|
// user-settings toggle) can reach it without an import.
|
|
243
|
-
window.boardroomTypingSfx = { tick, speakerChange, gavel, setEnabled, isEnabled };
|
|
416
|
+
window.boardroomTypingSfx = { tick, speakerChange, setThinking, gavel, countdownTick, setEnabled, isEnabled };
|
|
244
417
|
})();
|
package/public/user-settings.css
CHANGED
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
font-family: var(--mono);
|
|
182
182
|
}
|
|
183
183
|
.us-nav-foot-label {
|
|
184
|
-
font-size:
|
|
184
|
+
font-size: 8px;
|
|
185
185
|
letter-spacing: 0.22em;
|
|
186
186
|
text-transform: uppercase;
|
|
187
187
|
color: var(--text-faint, #3A382F);
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
.us-row-field { min-width: 0; }
|
|
258
258
|
.us-row-meta {
|
|
259
259
|
font-family: var(--mono);
|
|
260
|
-
font-size:
|
|
260
|
+
font-size: 10px;
|
|
261
261
|
letter-spacing: 0.06em;
|
|
262
262
|
text-transform: uppercase;
|
|
263
263
|
color: var(--text-faint, #3A382F);
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
outline-offset: 2px;
|
|
326
326
|
}
|
|
327
327
|
.us-switch-label {
|
|
328
|
-
font-size:
|
|
328
|
+
font-size: 10px;
|
|
329
329
|
letter-spacing: 0.18em;
|
|
330
330
|
text-transform: uppercase;
|
|
331
331
|
font-weight: 700;
|
|
@@ -343,6 +343,29 @@
|
|
|
343
343
|
min-width: 0;
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
/* Ghost button · inline action in a settings row (e.g. "Replay
|
|
347
|
+
onboarding"). Matches the .onb-btn family — mono uppercase, hairline
|
|
348
|
+
border, lime on hover — so the affordance reads consistent across
|
|
349
|
+
the user-settings and onboarding overlays. */
|
|
350
|
+
.us-btn-ghost {
|
|
351
|
+
font-family: var(--mono);
|
|
352
|
+
font-size: 10px;
|
|
353
|
+
font-weight: 700;
|
|
354
|
+
text-transform: uppercase;
|
|
355
|
+
letter-spacing: 0.1em;
|
|
356
|
+
padding: 7px 13px;
|
|
357
|
+
border: 0.5px solid var(--line-strong, #3A3A35);
|
|
358
|
+
background: transparent;
|
|
359
|
+
color: var(--text-soft, #8E8B83);
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
transition: border-color 0.12s, color 0.12s;
|
|
362
|
+
flex-shrink: 0;
|
|
363
|
+
}
|
|
364
|
+
.us-btn-ghost:hover {
|
|
365
|
+
border-color: var(--lime, #6FB572);
|
|
366
|
+
color: var(--lime, #6FB572);
|
|
367
|
+
}
|
|
368
|
+
|
|
346
369
|
/* Preferences → Other · EN / Zh (moved off the sidebar chrome) */
|
|
347
370
|
.us-pane-body .locale-switch {
|
|
348
371
|
display: inline-flex;
|
|
@@ -570,7 +593,7 @@
|
|
|
570
593
|
.us-theme.active .us-theme-name { color: var(--lime, #6FB572); }
|
|
571
594
|
.us-theme-desc {
|
|
572
595
|
font-family: var(--font-human);
|
|
573
|
-
font-size:
|
|
596
|
+
font-size: 10px;
|
|
574
597
|
color: var(--text-dim, #5C5A52);
|
|
575
598
|
line-height: 1.4;
|
|
576
599
|
margin-top: 1px;
|
|
@@ -618,7 +641,7 @@
|
|
|
618
641
|
display: inline-block;
|
|
619
642
|
margin-left: 6px;
|
|
620
643
|
padding: 1px 5px;
|
|
621
|
-
font-size:
|
|
644
|
+
font-size: 8px;
|
|
622
645
|
font-weight: 700;
|
|
623
646
|
letter-spacing: 0.14em;
|
|
624
647
|
text-transform: uppercase;
|
|
@@ -629,7 +652,7 @@
|
|
|
629
652
|
}
|
|
630
653
|
.us-key-status {
|
|
631
654
|
font-family: var(--mono);
|
|
632
|
-
font-size:
|
|
655
|
+
font-size: 10px;
|
|
633
656
|
letter-spacing: 0.1em;
|
|
634
657
|
text-transform: uppercase;
|
|
635
658
|
}
|
|
@@ -700,7 +723,7 @@
|
|
|
700
723
|
border: 0.5px solid var(--line-bright, #2A2A26);
|
|
701
724
|
color: var(--text-soft, #8E8B83);
|
|
702
725
|
font-family: var(--mono);
|
|
703
|
-
font-size:
|
|
726
|
+
font-size: 10px;
|
|
704
727
|
font-weight: 600;
|
|
705
728
|
letter-spacing: 0.16em;
|
|
706
729
|
text-transform: uppercase;
|
|
@@ -814,7 +837,7 @@
|
|
|
814
837
|
justify-content: space-between;
|
|
815
838
|
align-items: center;
|
|
816
839
|
font-family: var(--mono);
|
|
817
|
-
font-size:
|
|
840
|
+
font-size: 10px;
|
|
818
841
|
color: var(--text-faint, #3A382F);
|
|
819
842
|
letter-spacing: 0.1em;
|
|
820
843
|
text-transform: uppercase;
|
|
@@ -845,7 +868,7 @@
|
|
|
845
868
|
}
|
|
846
869
|
.us-foot .us-website {
|
|
847
870
|
font-family: var(--mono);
|
|
848
|
-
font-size:
|
|
871
|
+
font-size: 10px;
|
|
849
872
|
font-weight: 600;
|
|
850
873
|
letter-spacing: 0.1em;
|
|
851
874
|
text-transform: uppercase;
|
|
@@ -894,7 +917,7 @@
|
|
|
894
917
|
line-height: 1;
|
|
895
918
|
}
|
|
896
919
|
.us-usage-empty-text {
|
|
897
|
-
font-size:
|
|
920
|
+
font-size: 12px;
|
|
898
921
|
letter-spacing: 0.04em;
|
|
899
922
|
color: var(--text-soft, #8E8B83);
|
|
900
923
|
text-align: center;
|
|
@@ -918,7 +941,7 @@
|
|
|
918
941
|
border: 0.5px solid var(--line-bright, #2A2A26);
|
|
919
942
|
color: var(--text-soft, #8E8B83);
|
|
920
943
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
921
|
-
font-size:
|
|
944
|
+
font-size: 10px;
|
|
922
945
|
letter-spacing: 0.12em;
|
|
923
946
|
text-transform: uppercase;
|
|
924
947
|
font-weight: 600;
|
|
@@ -957,7 +980,7 @@
|
|
|
957
980
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
958
981
|
}
|
|
959
982
|
.us-chart-meta-label {
|
|
960
|
-
font-size:
|
|
983
|
+
font-size: 10px;
|
|
961
984
|
letter-spacing: 0.18em;
|
|
962
985
|
text-transform: uppercase;
|
|
963
986
|
color: var(--text-faint, #3A382F);
|
|
@@ -1054,7 +1077,7 @@
|
|
|
1054
1077
|
margin-top: 4px;
|
|
1055
1078
|
height: 10px;
|
|
1056
1079
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1057
|
-
font-size:
|
|
1080
|
+
font-size: 8px;
|
|
1058
1081
|
letter-spacing: 0.06em;
|
|
1059
1082
|
color: var(--text-faint, #3A382F);
|
|
1060
1083
|
text-align: center;
|
|
@@ -1077,14 +1100,14 @@
|
|
|
1077
1100
|
}
|
|
1078
1101
|
.us-day-empty-tag {
|
|
1079
1102
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1080
|
-
font-size:
|
|
1103
|
+
font-size: 10px;
|
|
1081
1104
|
letter-spacing: 0.18em;
|
|
1082
1105
|
text-transform: uppercase;
|
|
1083
1106
|
color: var(--text-soft, #8E8B83);
|
|
1084
1107
|
font-weight: 700;
|
|
1085
1108
|
}
|
|
1086
1109
|
.us-day-empty-text {
|
|
1087
|
-
font-size:
|
|
1110
|
+
font-size: 12px;
|
|
1088
1111
|
color: var(--text-faint, #3A382F);
|
|
1089
1112
|
}
|
|
1090
1113
|
|
|
@@ -1093,7 +1116,7 @@
|
|
|
1093
1116
|
kicker so the user knows what scope the head numbers describe. */
|
|
1094
1117
|
.us-usage-total-scope {
|
|
1095
1118
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1096
|
-
font-size:
|
|
1119
|
+
font-size: 10px;
|
|
1097
1120
|
letter-spacing: 0.18em;
|
|
1098
1121
|
text-transform: uppercase;
|
|
1099
1122
|
color: var(--text-faint, #3A382F);
|
|
@@ -1139,7 +1162,7 @@
|
|
|
1139
1162
|
gap: 12px;
|
|
1140
1163
|
justify-content: flex-end;
|
|
1141
1164
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1142
|
-
font-size:
|
|
1165
|
+
font-size: 10px;
|
|
1143
1166
|
}
|
|
1144
1167
|
.us-usage-meta-label {
|
|
1145
1168
|
color: var(--text-faint, #3A382F);
|
|
@@ -1176,7 +1199,7 @@
|
|
|
1176
1199
|
.us-usage-section { display: flex; flex-direction: column; gap: 10px; }
|
|
1177
1200
|
.us-usage-section-tag {
|
|
1178
1201
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1179
|
-
font-size:
|
|
1202
|
+
font-size: 10px;
|
|
1180
1203
|
letter-spacing: 0.22em;
|
|
1181
1204
|
text-transform: uppercase;
|
|
1182
1205
|
color: var(--text-faint, #3A382F);
|
|
@@ -1213,7 +1236,7 @@
|
|
|
1213
1236
|
}
|
|
1214
1237
|
.us-model-provider {
|
|
1215
1238
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1216
|
-
font-size:
|
|
1239
|
+
font-size: 10px;
|
|
1217
1240
|
letter-spacing: 0.18em;
|
|
1218
1241
|
text-transform: uppercase;
|
|
1219
1242
|
color: var(--text-faint, #3A382F);
|
|
@@ -1243,7 +1266,7 @@
|
|
|
1243
1266
|
letter-spacing: -0.01em;
|
|
1244
1267
|
}
|
|
1245
1268
|
.us-model-pct {
|
|
1246
|
-
font-size:
|
|
1269
|
+
font-size: 10px;
|
|
1247
1270
|
color: var(--text-soft, #8E8B83);
|
|
1248
1271
|
letter-spacing: 0.04em;
|
|
1249
1272
|
font-variant-numeric: tabular-nums;
|
|
@@ -1251,7 +1274,7 @@
|
|
|
1251
1274
|
text-align: right;
|
|
1252
1275
|
}
|
|
1253
1276
|
.us-model-agents {
|
|
1254
|
-
font-size:
|
|
1277
|
+
font-size: 10px;
|
|
1255
1278
|
letter-spacing: 0.18em;
|
|
1256
1279
|
text-transform: uppercase;
|
|
1257
1280
|
color: var(--text-faint, #3A382F);
|
|
@@ -1307,7 +1330,7 @@
|
|
|
1307
1330
|
.us-agent-bar > span { display: block; height: 100%; }
|
|
1308
1331
|
.us-agent-tokens {
|
|
1309
1332
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1310
|
-
font-size:
|
|
1333
|
+
font-size: 12px;
|
|
1311
1334
|
font-weight: 700;
|
|
1312
1335
|
color: var(--text, #C8C5BE);
|
|
1313
1336
|
font-variant-numeric: tabular-nums;
|
|
@@ -1317,7 +1340,7 @@
|
|
|
1317
1340
|
.us-agent-silent {
|
|
1318
1341
|
margin-top: 6px;
|
|
1319
1342
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1320
|
-
font-size:
|
|
1343
|
+
font-size: 10px;
|
|
1321
1344
|
letter-spacing: 0.16em;
|
|
1322
1345
|
text-transform: uppercase;
|
|
1323
1346
|
color: var(--text-faint, #3A382F);
|
|
@@ -1386,7 +1409,7 @@
|
|
|
1386
1409
|
}
|
|
1387
1410
|
.us-key-group-tag {
|
|
1388
1411
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1389
|
-
font-size:
|
|
1412
|
+
font-size: 10px;
|
|
1390
1413
|
letter-spacing: 0.28em;
|
|
1391
1414
|
text-transform: uppercase;
|
|
1392
1415
|
color: var(--text-faint, #3A382F);
|
|
@@ -1448,7 +1471,7 @@
|
|
|
1448
1471
|
}
|
|
1449
1472
|
.us-models-provider-tag {
|
|
1450
1473
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1451
|
-
font-size:
|
|
1474
|
+
font-size: 10px;
|
|
1452
1475
|
letter-spacing: 0.22em;
|
|
1453
1476
|
text-transform: uppercase;
|
|
1454
1477
|
color: var(--text-faint, #3A382F);
|
|
@@ -1503,7 +1526,7 @@
|
|
|
1503
1526
|
}
|
|
1504
1527
|
.us-models-default-label {
|
|
1505
1528
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1506
|
-
font-size:
|
|
1529
|
+
font-size: 10px;
|
|
1507
1530
|
letter-spacing: 0.22em;
|
|
1508
1531
|
text-transform: uppercase;
|
|
1509
1532
|
color: var(--text-faint, #3A382F);
|
|
@@ -1551,7 +1574,7 @@
|
|
|
1551
1574
|
}
|
|
1552
1575
|
.us-models-default-note {
|
|
1553
1576
|
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
1554
|
-
font-size:
|
|
1577
|
+
font-size: 10px;
|
|
1555
1578
|
letter-spacing: 0.18em;
|
|
1556
1579
|
text-transform: uppercase;
|
|
1557
1580
|
color: var(--text-faint, #3A382F);
|
package/public/user-settings.js
CHANGED
|
@@ -29,8 +29,6 @@
|
|
|
29
29
|
swatches: ["#FBFBF7","#F4F2EC","#2E7D32","#1B5E20","#A86C2A","#A8403D","#2E7D7A","#1F1E1A"] },
|
|
30
30
|
{ slug: "pinterest", name: "Pinterest", desc: "clean white · Pinterest red · light",
|
|
31
31
|
swatches: ["#FFFFFF","#FAFAFA","#E60023","#AD081B","#F4A100","#E60023","#2E7D7A","#111111"] },
|
|
32
|
-
{ slug: "apple", name: "Apple", desc: "pure white · system blue · Apple.com aesthetic · light",
|
|
33
|
-
swatches: ["#FFFFFF","#F5F5F7","#0071E3","#0051A8","#FF9500","#FF3B30","#5AC8FA","#1D1D1F"] },
|
|
34
32
|
{ slug: "alanpeabody", name: "Alan Peabody", desc: "cool blue · git-green accents",
|
|
35
33
|
swatches: ["#0E1419","#131A21","#6BAFE0","#3F7AAA","#C8A463","#D67373","#6FB5A8","#C8D0DA"] },
|
|
36
34
|
{ slug: "amuse", name: "Amuse", desc: "magenta + cyan · playful",
|
|
@@ -269,7 +267,7 @@
|
|
|
269
267
|
<div class="us-row-field">
|
|
270
268
|
<button type="button" class="cmp-dd" data-cmp-dropdown="locale" title="${escape(tr("us_locale_label"))}" data-i18n-aria="aria_language" aria-label="">
|
|
271
269
|
<span class="cmp-dd-label" data-i18n="us_locale_label">${escape(tr("us_locale_label"))}</span>
|
|
272
|
-
<span class="cmp-dd-value" data-cmp-dd-value="locale">${escape(tr(window.I18n && window.I18n.getLocale && window.I18n.getLocale()
|
|
270
|
+
<span class="cmp-dd-value" data-cmp-dd-value="locale">${escape(tr(`locale_${(window.I18n && window.I18n.getLocale && window.I18n.getLocale()) || "en"}`))}</span>
|
|
273
271
|
<span class="cmp-dd-chevron">▾</span>
|
|
274
272
|
</button>
|
|
275
273
|
<p class="us-locale-deck">${escape(tr("us_locale_deck"))}</p>
|
|
@@ -290,6 +288,16 @@
|
|
|
290
288
|
</div>
|
|
291
289
|
</div>
|
|
292
290
|
</div>
|
|
291
|
+
|
|
292
|
+
<div class="us-row">
|
|
293
|
+
<div class="us-row-label">${escape(tr("us_replay_onb_label"))}</div>
|
|
294
|
+
<div class="us-row-field">
|
|
295
|
+
<div class="us-toggle-row">
|
|
296
|
+
<button type="button" class="us-btn-ghost" data-us-replay-onb>${escape(tr("us_replay_onb_btn"))}</button>
|
|
297
|
+
<span class="us-toggle-deck">${escape(tr("us_replay_onb_deck"))}</span>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
293
301
|
</div>
|
|
294
302
|
`;
|
|
295
303
|
}
|
|
@@ -326,6 +334,32 @@
|
|
|
326
334
|
// toggled also serves as the gesture the AudioContext needs,
|
|
327
335
|
// so this tick is actually heard.
|
|
328
336
|
if (next) window.boardroomTypingSfx.tick();
|
|
337
|
+
// Re-evaluate the agent-build ambient · this toggle is the
|
|
338
|
+
// master gate. Flipping OFF silences any active build BGM;
|
|
339
|
+
// flipping ON resumes it if a build is currently running on
|
|
340
|
+
// the user's foreground composer.
|
|
341
|
+
try { window.app?._syncAgentBuildBgm?.(); } catch { /* ignore */ }
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Replay onboarding · close the settings overlay first so the user
|
|
346
|
+
// lands on a clean dashboard, then trigger the storyline overlay.
|
|
347
|
+
// The replay helper in onboarding.js handles step reset + the
|
|
348
|
+
// once-only composer-hint flag.
|
|
349
|
+
const replayBtn = paneEl.querySelector("[data-us-replay-onb]");
|
|
350
|
+
if (replayBtn) {
|
|
351
|
+
replayBtn.addEventListener("click", () => {
|
|
352
|
+
try { if (typeof window.closeUserSettings === "function") window.closeUserSettings(); } catch { /* ignore */ }
|
|
353
|
+
// Settings overlay teardown sets up a 220ms close animation
|
|
354
|
+
// (see modal close handler); kick off onboarding after that so
|
|
355
|
+
// the two overlays don't briefly stack.
|
|
356
|
+
setTimeout(() => {
|
|
357
|
+
if (typeof window.boardroomReplayOnboarding === "function") {
|
|
358
|
+
window.boardroomReplayOnboarding();
|
|
359
|
+
} else if (typeof window.boardroomShowOnboarding === "function") {
|
|
360
|
+
window.boardroomShowOnboarding();
|
|
361
|
+
}
|
|
362
|
+
}, 240);
|
|
329
363
|
});
|
|
330
364
|
}
|
|
331
365
|
}
|
|
@@ -1437,24 +1471,19 @@
|
|
|
1437
1471
|
fetchAppVersion();
|
|
1438
1472
|
}
|
|
1439
1473
|
|
|
1440
|
-
let _versionCache = null;
|
|
1441
1474
|
async function fetchAppVersion() {
|
|
1442
1475
|
const slot = overlay && overlay.querySelector("[data-us-version-value]");
|
|
1443
1476
|
if (!slot) return;
|
|
1444
|
-
//
|
|
1445
|
-
//
|
|
1446
|
-
//
|
|
1447
|
-
|
|
1448
|
-
slot.textContent = _versionCache;
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1477
|
+
// No cache · every overlay open hits /api/version so a dev-server
|
|
1478
|
+
// restart (npm version bump + new build) reflects immediately
|
|
1479
|
+
// in the foot without requiring a hard reload. The call is one
|
|
1480
|
+
// tiny round-trip with no DB hit, so it's cheap to repeat.
|
|
1451
1481
|
try {
|
|
1452
|
-
const r = await fetch("/api/version");
|
|
1482
|
+
const r = await fetch("/api/version", { cache: "no-store" });
|
|
1453
1483
|
if (!r.ok) return;
|
|
1454
1484
|
const j = await r.json();
|
|
1455
1485
|
if (j && typeof j.version === "string") {
|
|
1456
|
-
|
|
1457
|
-
slot.textContent = _versionCache;
|
|
1486
|
+
slot.textContent = `v${j.version}`;
|
|
1458
1487
|
}
|
|
1459
1488
|
} catch { /* swallow · the foot just stays at "·" if offline */ }
|
|
1460
1489
|
}
|