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.
@@ -0,0 +1,425 @@
1
+ /* ─────────────── Voice-mode onboarding overlay ───────────────
2
+ Promo overlay that appears when the user clicks the voice
3
+ toggle in the new-room composer without any voice key
4
+ configured. Single themed round-table banner + title/deck +
5
+ CTA → opens user-settings to the MiniMax key row.
6
+
7
+ Chrome (classification bar, .vonb-head topbar, lime corner
8
+ brackets, close button, foot) mirrors `room-settings.css`
9
+ so the overlay reads as native to the rest of the app's
10
+ modal vocabulary. The banner area + the scoped .vrp-* /
11
+ .rt-* primitives are unique to this surface.
12
+ */
13
+
14
+ .vonb-overlay {
15
+ position: fixed;
16
+ inset: 0;
17
+ background: rgba(0, 0, 0, 0.78);
18
+ -webkit-backdrop-filter: blur(4px);
19
+ backdrop-filter: blur(4px);
20
+ z-index: 9400;
21
+ display: none;
22
+ align-items: center;
23
+ justify-content: center;
24
+ padding: 24px;
25
+ overflow: hidden;
26
+ font-family: var(--mono, "Inter", system-ui, sans-serif);
27
+ }
28
+ .vonb-overlay.open {
29
+ display: flex;
30
+ animation: vonb-fade 0.14s ease-out;
31
+ }
32
+ @keyframes vonb-fade { from { opacity: 0; } to { opacity: 1; } }
33
+
34
+ .vonb-backdrop {
35
+ position: absolute;
36
+ inset: 0;
37
+ }
38
+
39
+ .vonb-modal {
40
+ position: relative;
41
+ width: 100%;
42
+ max-width: 620px;
43
+ max-height: calc(100vh - 48px);
44
+ background: var(--panel, #131312);
45
+ border: 0.5px solid var(--line-strong, #3A3A35);
46
+ color: var(--text, #C8C5BE);
47
+ animation: vonb-rise 0.18s ease-out;
48
+ display: flex;
49
+ flex-direction: column;
50
+ min-height: 0;
51
+ }
52
+ .vonb-modal > .vonb-classification,
53
+ .vonb-modal > .vonb-head,
54
+ .vonb-modal > .vonb-foot {
55
+ flex: 0 0 auto;
56
+ }
57
+ .vonb-modal > .vonb-body {
58
+ flex: 1 1 auto;
59
+ min-height: 0;
60
+ overflow-y: auto;
61
+ }
62
+ @keyframes vonb-rise {
63
+ from { transform: translateY(10px); opacity: 0; }
64
+ to { transform: translateY(0); opacity: 1; }
65
+ }
66
+ /* Lime corner brackets · same vocabulary as the room-settings modal. */
67
+ .vonb-modal::before, .vonb-modal::after {
68
+ content: "";
69
+ position: absolute;
70
+ width: 10px;
71
+ height: 10px;
72
+ border: 1.5px solid var(--lime, #6FB572);
73
+ pointer-events: none;
74
+ }
75
+ .vonb-modal::before { top: -1px; left: -1px; border-right: none; border-bottom: none; }
76
+ .vonb-modal::after { bottom: -1px; right: -1px; border-left: none; border-top: none; }
77
+
78
+ /* ─── Classification strip ─── */
79
+ .vonb-classification {
80
+ background: var(--panel-2, #1A1A18);
81
+ border-bottom: 0.5px solid var(--line-bright, #2A2A26);
82
+ padding: 5px 14px;
83
+ font-size: 8px;
84
+ letter-spacing: 0.22em;
85
+ text-transform: uppercase;
86
+ color: var(--lime, #6FB572);
87
+ font-weight: 700;
88
+ display: flex;
89
+ justify-content: space-between;
90
+ align-items: center;
91
+ }
92
+ .vonb-classification .dot { display: inline-block; margin-right: 4px; }
93
+ .vonb-classification .right {
94
+ color: var(--text-faint, #3A382F);
95
+ letter-spacing: 0.12em;
96
+ }
97
+
98
+ /* ─── Topbar · meta line + title + close button ─── */
99
+ .vonb-head {
100
+ display: grid;
101
+ grid-template-columns: 1fr auto;
102
+ gap: 12px;
103
+ align-items: start;
104
+ padding: 14px 16px 12px;
105
+ border-bottom: 0.5px dashed var(--line-bright, #2A2A26);
106
+ }
107
+ .vonb-head-text { min-width: 0; }
108
+ .vonb-head .meta {
109
+ font-family: var(--mono);
110
+ font-size: 9px;
111
+ color: var(--text-dim, #5C5A52);
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.18em;
114
+ margin-bottom: 4px;
115
+ font-weight: 700;
116
+ display: flex;
117
+ gap: 6px;
118
+ align-items: center;
119
+ }
120
+ .vonb-head .meta .live {
121
+ color: var(--lime, #6FB572);
122
+ font-weight: 700;
123
+ }
124
+ .vonb-head .meta .live::before {
125
+ content: "● ";
126
+ animation: vonb-pulse 1.6s ease-in-out infinite;
127
+ }
128
+ @keyframes vonb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
129
+ .vonb-title-wrap {
130
+ display: flex;
131
+ flex-direction: column;
132
+ gap: 4px;
133
+ min-width: 0;
134
+ }
135
+ .vonb-head .title {
136
+ font-size: 16px;
137
+ font-weight: 700;
138
+ color: var(--text, #C8C5BE);
139
+ letter-spacing: -0.01em;
140
+ line-height: 1.3;
141
+ font-family: var(--font-human, system-ui, sans-serif);
142
+ }
143
+ .vonb-head .title::before {
144
+ content: "▸ ";
145
+ color: var(--lime, #6FB572);
146
+ font-family: var(--mono);
147
+ }
148
+ .vonb-head .close-btn {
149
+ width: 24px; height: 24px;
150
+ background: transparent;
151
+ border: 0.5px solid var(--line-bright, #2A2A26);
152
+ color: var(--text-dim, #5C5A52);
153
+ font-size: 12px;
154
+ cursor: pointer;
155
+ font-family: var(--mono);
156
+ border-radius: 3px;
157
+ transition: all 0.12s;
158
+ }
159
+ .vonb-head .close-btn:hover {
160
+ border-color: var(--lime, #6FB572);
161
+ color: var(--lime, #6FB572);
162
+ }
163
+
164
+ /* ─── Body · banner + deck ─── */
165
+ .vonb-body {
166
+ padding: 14px 16px 16px;
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: 14px;
170
+ }
171
+ .vonb-banner {
172
+ position: relative;
173
+ height: 240px;
174
+ background: var(--bg);
175
+ border: 0.5px solid var(--line, #1F1E1A);
176
+ overflow: hidden;
177
+ display: flex;
178
+ }
179
+ .vonb-banner .voice-room-preview {
180
+ flex: 1 1 auto;
181
+ display: flex;
182
+ flex-direction: column;
183
+ border: none;
184
+ background: var(--bg);
185
+ overflow: hidden;
186
+ }
187
+ .vonb-banner .vrp-caption {
188
+ display: none; /* theme name surfaces via .vonb-theme-label below */
189
+ }
190
+ .vonb-body-text {
191
+ display: flex;
192
+ flex-direction: column;
193
+ gap: 8px;
194
+ }
195
+ .vonb-deck {
196
+ font-family: var(--font-human, system-ui, sans-serif);
197
+ font-size: 14px;
198
+ line-height: 1.55;
199
+ color: var(--text-soft, #8E8B83);
200
+ margin: 0;
201
+ }
202
+ .vonb-theme-label {
203
+ font-family: var(--mono);
204
+ font-size: 10px;
205
+ letter-spacing: 0.14em;
206
+ color: var(--text-dim, #5C5A52);
207
+ text-transform: uppercase;
208
+ display: flex;
209
+ gap: 10px;
210
+ align-items: baseline;
211
+ }
212
+ .vonb-theme-label .v-name {
213
+ color: var(--lime, #6FB572);
214
+ font-weight: 700;
215
+ letter-spacing: 0.18em;
216
+ }
217
+ .vonb-theme-label .v-deck {
218
+ color: var(--text-dim, #5C5A52);
219
+ }
220
+
221
+ /* ─── Foot · single CTA ─── */
222
+ .vonb-foot {
223
+ padding: 12px 16px 16px;
224
+ display: flex;
225
+ justify-content: flex-end;
226
+ align-items: center;
227
+ gap: 12px;
228
+ border-top: 0.5px dashed var(--line-bright, #2A2A26);
229
+ }
230
+ .vonb-btn {
231
+ padding: 8px 18px;
232
+ background: transparent;
233
+ border: 0.5px solid var(--line-bright, #2A2A26);
234
+ color: var(--text-soft, #8E8B83);
235
+ font-family: var(--mono);
236
+ font-size: 11px;
237
+ letter-spacing: 0.14em;
238
+ text-transform: uppercase;
239
+ cursor: pointer;
240
+ transition: all 0.12s;
241
+ }
242
+ .vonb-btn:hover {
243
+ border-color: var(--lime, #6FB572);
244
+ color: var(--lime, #6FB572);
245
+ }
246
+ .vonb-btn.primary {
247
+ background: var(--lime, #6FB572);
248
+ border-color: var(--lime, #6FB572);
249
+ color: var(--bg, #0A0A0A);
250
+ font-weight: 700;
251
+ }
252
+ .vonb-btn.primary:hover {
253
+ filter: brightness(1.06);
254
+ color: var(--bg, #0A0A0A);
255
+ }
256
+
257
+ /* ─── Body scroll lock while overlay open ─── */
258
+ body.vonb-locked {
259
+ overflow: hidden;
260
+ }
261
+
262
+ @media (max-width: 600px) {
263
+ .vonb-overlay { padding: 14px; }
264
+ .vonb-head { padding: 12px 14px; }
265
+ .vonb-body { padding: 12px 14px 14px; }
266
+ .vonb-banner { height: 200px; }
267
+ }
268
+
269
+ /* ────────────────────────────────────────────────────────────
270
+ Voice-room preview primitives · scoped to .vonb-banner so
271
+ the live round-table classes in index.html aren't touched.
272
+ Source: public/home.html (.vrp-stage / .rt-* rules ~L1455).
273
+ Keep in sync if home.html's preview palette changes. */
274
+
275
+ .vonb-banner .vrp-stage {
276
+ position: relative;
277
+ width: 100%;
278
+ height: 100%;
279
+ overflow: hidden;
280
+ background-color: var(--floor-bg, #2A2C32);
281
+ background-image:
282
+ radial-gradient(ellipse at 50% 50%,
283
+ transparent 28%,
284
+ rgba(0, 0, 0, 0.40) 78%,
285
+ rgba(0, 0, 0, 0.55) 100%),
286
+ var(--floor-image, none);
287
+ background-repeat: no-repeat, repeat;
288
+ background-size: auto, var(--floor-size, 64px 64px);
289
+ image-rendering: pixelated;
290
+ }
291
+
292
+ /* Floor variants (data-URIs lifted from home.html). */
293
+ .vonb-banner .voice-room-preview[data-preview-theme="eastwood"] .vrp-stage {
294
+ --floor-bg: #5E6B47;
295
+ --floor-size: 64px 64px;
296
+ --floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='128' height='128' shape-rendering='crispEdges'><rect width='128' height='128' fill='%235E6B47'/><rect x='0' y='8' width='20' height='16' fill='%234F5C3D'/><rect x='104' y='20' width='20' height='20' fill='%234F5C3D'/><rect x='88' y='64' width='24' height='16' fill='%234F5C3D'/><rect x='0' y='64' width='12' height='20' fill='%234F5C3D'/><rect x='44' y='64' width='16' height='8' fill='%234F5C3D'/><rect x='72' y='8' width='12' height='8' fill='%236E7A52'/><rect x='120' y='80' width='8' height='12' fill='%236E7A52'/><rect x='48' y='84' width='8' height='8' fill='%236E7A52'/><rect x='48' y='24' width='32' height='32' fill='%235C4838'/><rect x='44' y='28' width='4' height='24' fill='%235C4838'/><rect x='80' y='28' width='4' height='24' fill='%235C4838'/><rect x='52' y='20' width='24' height='4' fill='%235C4838'/><rect x='52' y='56' width='20' height='4' fill='%235C4838'/><rect x='56' y='16' width='12' height='4' fill='%235C4838'/><rect x='56' y='32' width='8' height='8' fill='%234A3A28'/><rect x='68' y='40' width='4' height='8' fill='%234A3A28'/><rect x='60' y='24' width='4' height='4' fill='%236E5A48'/><rect x='68' y='48' width='4' height='4' fill='%236E5A48'/><rect x='58' y='44' width='2' height='2' fill='%236B6258'/><rect x='72' y='32' width='2' height='2' fill='%236B6258'/><rect x='50' y='38' width='2' height='2' fill='%236B6258'/><rect x='16' y='96' width='24' height='20' fill='%235C4838'/><rect x='12' y='100' width='4' height='12' fill='%235C4838'/><rect x='40' y='100' width='4' height='12' fill='%235C4838'/><rect x='20' y='92' width='16' height='4' fill='%235C4838'/><rect x='22' y='104' width='8' height='4' fill='%234A3A28'/><rect x='20' y='100' width='4' height='4' fill='%236E5A48'/><rect x='24' y='108' width='2' height='2' fill='%236B6258'/><rect x='32' y='98' width='2' height='2' fill='%236B6258'/><rect x='8' y='40' width='1' height='2' fill='%238FA068'/><rect x='24' y='72' width='1' height='2' fill='%238FA068'/><rect x='104' y='56' width='1' height='2' fill='%238FA068'/><rect x='120' y='104' width='1' height='2' fill='%238FA068'/><rect x='88' y='44' width='1' height='2' fill='%238FA068'/><rect x='4' y='88' width='1' height='2' fill='%238FA068'/><rect x='64' y='88' width='1' height='2' fill='%238FA068'/><rect x='92' y='24' width='1' height='2' fill='%238FA068'/><rect x='44' y='62' width='1' height='2' fill='%238FA068'/><rect x='80' y='120' width='1' height='2' fill='%238FA068'/><rect x='12' y='12' width='1' height='2' fill='%238FA068'/><rect x='112' y='60' width='1' height='2' fill='%238FA068'/></svg>");
297
+ }
298
+ .vonb-banner .voice-room-preview[data-preview-theme="regent"] .vrp-stage {
299
+ --floor-bg: #2A2C32;
300
+ --floor-size: 64px 64px;
301
+ --floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='128' height='128' shape-rendering='crispEdges'><rect width='128' height='128' fill='%232A2C32'/><rect x='20' y='30' width='30' height='18' fill='%232F3138'/><rect x='75' y='75' width='25' height='25' fill='%2325272D'/><rect x='5' y='90' width='18' height='15' fill='%232D2F36'/><rect x='20' y='25' width='1' height='1' fill='%231F2026'/><rect x='50' y='65' width='1' height='1' fill='%231F2026'/><rect x='90' y='30' width='1' height='1' fill='%231F2026'/><rect x='110' y='100' width='1' height='1' fill='%231F2026'/><rect x='35' y='110' width='1' height='1' fill='%231F2026'/><rect x='65' y='15' width='1' height='1' fill='%231F2026'/><rect x='100' y='55' width='1' height='1' fill='%231F2026'/><rect x='15' y='15' width='1' height='1' fill='%233D4048'/><rect x='40' y='45' width='1' height='1' fill='%233D4048'/><rect x='70' y='35' width='1' height='1' fill='%233D4048'/><rect x='95' y='80' width='1' height='1' fill='%233D4048'/><rect x='25' y='75' width='1' height='1' fill='%233D4048'/><rect x='55' y='100' width='1' height='1' fill='%233D4048'/><rect x='30' y='55' width='1' height='1' fill='%234A4D55'/><rect x='80' y='20' width='1' height='1' fill='%234A4D55'/><rect x='115' y='65' width='1' height='1' fill='%234A4D55'/><rect x='45' y='85' width='2' height='1' fill='%233D4048'/><rect x='100' y='35' width='1' height='2' fill='%233D4048'/><rect x='10' y='50' width='2' height='1' fill='%231F2026'/><rect x='126' y='0' width='2' height='128' fill='%2315161B'/><rect x='0' y='126' width='128' height='2' fill='%2315161B'/></svg>");
302
+ }
303
+ .vonb-banner .voice-room-preview[data-preview-theme="atrium"] .vrp-stage {
304
+ --floor-bg: #BFBBB0;
305
+ --floor-size: 64px 64px;
306
+ --floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128' width='128' height='128' shape-rendering='crispEdges'><rect width='128' height='128' fill='%23BFBBB0'/><rect x='64' y='0' width='64' height='64' fill='%23B5B0A4'/><rect x='0' y='64' width='64' height='64' fill='%23C4C0B5'/><rect x='63' y='0' width='1' height='128' fill='%23A0998D'/><rect x='0' y='63' width='128' height='1' fill='%23A0998D'/><rect x='10' y='20' width='2' height='6' fill='%238E8A7F'/><rect x='12' y='26' width='6' height='2' fill='%238E8A7F'/><rect x='18' y='28' width='4' height='2' fill='%238E8A7F'/><rect x='22' y='30' width='2' height='4' fill='%238E8A7F'/><rect x='80' y='80' width='8' height='2' fill='%238E8A7F'/><rect x='86' y='82' width='2' height='6' fill='%238E8A7F'/><rect x='88' y='88' width='6' height='2' fill='%238E8A7F'/><rect x='40' y='100' width='2' height='4' fill='%238E8A7F'/><rect x='42' y='104' width='8' height='2' fill='%238E8A7F'/><rect x='90' y='10' width='2' height='4' fill='%238E8A7F'/><rect x='92' y='14' width='6' height='2' fill='%238E8A7F'/></svg>");
307
+ }
308
+ .vonb-banner .voice-room-preview[data-preview-theme="nintendo"] .vrp-stage {
309
+ --floor-bg: #5A2A2A;
310
+ --floor-size: 24px 24px;
311
+ --floor-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' shape-rendering='crispEdges'><rect width='32' height='32' fill='%235A2A2A'/><rect x='14' y='14' width='4' height='4' fill='%237A4040'/><rect x='12' y='15' width='8' height='2' fill='%237A4040'/><rect x='15' y='12' width='2' height='8' fill='%237A4040'/><rect x='15' y='4' width='2' height='4' fill='%23753838'/><rect x='15' y='24' width='2' height='4' fill='%23753838'/><rect x='4' y='15' width='4' height='2' fill='%23753838'/><rect x='24' y='15' width='4' height='2' fill='%23753838'/><rect x='2' y='2' width='1' height='1' fill='%23753838'/><rect x='29' y='2' width='1' height='1' fill='%23753838'/><rect x='2' y='29' width='1' height='1' fill='%23753838'/><rect x='29' y='29' width='1' height='1' fill='%23753838'/></svg>");
312
+ }
313
+
314
+ /* Round-table primitives (scoped). */
315
+ .vonb-banner .vrp-stage .rt-table {
316
+ position: absolute;
317
+ top: 36%;
318
+ left: 18%;
319
+ width: 64%;
320
+ height: 28%;
321
+ pointer-events: none;
322
+ shape-rendering: crispEdges;
323
+ }
324
+ .vonb-banner .vrp-stage .rt-seat {
325
+ position: absolute;
326
+ width: 48px;
327
+ height: 48px;
328
+ transform: translate(-50%, -100%);
329
+ pointer-events: none;
330
+ }
331
+ .vonb-banner .vrp-stage .rt-chair {
332
+ position: absolute;
333
+ bottom: 0;
334
+ left: 50%;
335
+ transform: translateX(-50%);
336
+ width: 36px;
337
+ height: 40px;
338
+ shape-rendering: crispEdges;
339
+ z-index: 1;
340
+ }
341
+ .vonb-banner .vrp-stage .rt-chair--mod { color: var(--cyan, #6A9B97); }
342
+ .vonb-banner .vrp-stage .rt-bubble {
343
+ position: absolute;
344
+ top: 0;
345
+ left: 50%;
346
+ transform: translate(-50%, -100%);
347
+ display: inline-flex;
348
+ align-items: center;
349
+ gap: 4px;
350
+ padding: 3px 7px;
351
+ background: var(--panel-3, var(--panel));
352
+ border: 1px solid var(--lime);
353
+ color: var(--lime);
354
+ font-family: var(--mono);
355
+ font-size: 8px;
356
+ font-weight: 700;
357
+ letter-spacing: 0.16em;
358
+ text-transform: uppercase;
359
+ white-space: nowrap;
360
+ z-index: 5;
361
+ pointer-events: none;
362
+ }
363
+ .vonb-banner .vrp-stage .rt-bubble::after {
364
+ content: "";
365
+ position: absolute;
366
+ bottom: -4px;
367
+ left: 50%;
368
+ transform: translateX(-50%);
369
+ width: 0;
370
+ height: 0;
371
+ border-left: 4px solid transparent;
372
+ border-right: 4px solid transparent;
373
+ border-top: 4px solid var(--lime);
374
+ }
375
+ .vonb-banner .vrp-stage .rt-seat-speaking .rt-name { display: none; }
376
+ .vonb-banner .vrp-stage .rt-avatar {
377
+ position: absolute;
378
+ bottom: 6px;
379
+ left: 50%;
380
+ transform: translateX(-50%);
381
+ width: 28px;
382
+ height: 30px;
383
+ image-rendering: pixelated;
384
+ z-index: 2;
385
+ pointer-events: none;
386
+ }
387
+ .vonb-banner .vrp-stage .rt-name {
388
+ position: absolute;
389
+ top: -14px;
390
+ left: 50%;
391
+ transform: translateX(-50%);
392
+ font-family: var(--mono);
393
+ font-size: 8px;
394
+ font-weight: 700;
395
+ letter-spacing: 0.08em;
396
+ text-transform: uppercase;
397
+ white-space: nowrap;
398
+ text-align: center;
399
+ pointer-events: none;
400
+ padding: 2px 5px;
401
+ color: var(--text-soft);
402
+ background: rgba(10, 10, 10, 0.62);
403
+ border: 1px solid rgba(255, 255, 255, 0.08);
404
+ }
405
+ .vonb-banner .vrp-stage .rt-seat-chair .rt-name {
406
+ color: var(--lime);
407
+ top: auto;
408
+ bottom: -16px;
409
+ }
410
+ .vonb-banner .voice-room-preview[data-preview-theme="atrium"] .vrp-stage .rt-name {
411
+ background: rgba(31, 30, 26, 0.78);
412
+ color: #F4F2EC;
413
+ border-color: rgba(0, 0, 0, 0.18);
414
+ }
415
+ .vonb-banner .vrp-stage .rt-plant {
416
+ position: absolute;
417
+ width: 40px;
418
+ height: 50px;
419
+ pointer-events: none;
420
+ image-rendering: pixelated;
421
+ shape-rendering: crispEdges;
422
+ z-index: 2;
423
+ }
424
+ .vonb-banner .vrp-stage .rt-plant-1 { top: 6px; right: 6px; }
425
+ .vonb-banner .vrp-stage .rt-plant-2 { bottom: 6px; left: 6px; }
@@ -0,0 +1,144 @@
1
+ /* ─────────────── Voice-mode onboarding overlay ───────────────
2
+ Promo overlay that appears when the user clicks the voice
3
+ toggle on the new-room composer without any voice-provider
4
+ key configured. The click handler in app.js calls
5
+ window.openVoiceOnboarding(); the CTA inside the overlay
6
+ routes onward into the user-settings panel scrolled to the
7
+ MiniMax key row.
8
+
9
+ Theme rotation: each open cycles through the four themed
10
+ previews (eastwood / regent / atrium / nintendo) using a
11
+ localStorage counter so repeat-opens never feel static.
12
+ */
13
+
14
+ (function () {
15
+ "use strict";
16
+
17
+ const STORAGE_KEY = "boardroom.vonb.themeIdx";
18
+ const THEME_LABELS = {
19
+ eastwood: { name: "Brainstorm", deck: "soil + grass · open ground" },
20
+ regent: { name: "Constructive", deck: "graphite marble · executive" },
21
+ atrium: { name: "Research", deck: "light marble · scholarly" },
22
+ nintendo: { name: "Critique", deck: "burgundy carpet · formal" },
23
+ };
24
+
25
+ let overlayEl = null;
26
+ let bannerEl = null;
27
+ let labelEl = null;
28
+ let wired = false;
29
+
30
+ function $(sel, root) { return (root || document).querySelector(sel); }
31
+
32
+ function readThemes() {
33
+ const tpl = document.getElementById("vonb-themes");
34
+ if (!tpl || !tpl.content) return [];
35
+ return Array.from(tpl.content.querySelectorAll(".voice-room-preview"));
36
+ }
37
+
38
+ function nextThemeIdx(themes) {
39
+ let idx = 0;
40
+ try {
41
+ const raw = localStorage.getItem(STORAGE_KEY);
42
+ idx = raw == null ? 0 : (parseInt(raw, 10) || 0);
43
+ } catch (e) {}
44
+ const n = Math.max(1, themes.length);
45
+ const cur = ((idx % n) + n) % n;
46
+ try { localStorage.setItem(STORAGE_KEY, String((cur + 1) % n)); } catch (e) {}
47
+ return cur;
48
+ }
49
+
50
+ function applyI18n() {
51
+ if (!overlayEl) return;
52
+ const I18n = window.I18n;
53
+ overlayEl.querySelectorAll("[data-i18n]").forEach((el) => {
54
+ const key = el.getAttribute("data-i18n");
55
+ if (!key) return;
56
+ let val = null;
57
+ if (I18n && typeof I18n.t === "function") {
58
+ val = I18n.t(key);
59
+ if (val === key) val = null; // missing-key fallback
60
+ }
61
+ if (val) el.textContent = val;
62
+ });
63
+ }
64
+
65
+ function refreshLabel(themeKey) {
66
+ if (!labelEl) return;
67
+ const I18n = window.I18n;
68
+ const meta = THEME_LABELS[themeKey] || { name: themeKey, deck: "" };
69
+ // i18n keys: vonb_theme_<key>_name / vonb_theme_<key>_deck. Fall back
70
+ // to the hardcoded English labels if no translation registered.
71
+ let name = meta.name;
72
+ let deck = meta.deck;
73
+ if (I18n && typeof I18n.t === "function") {
74
+ const nk = "vonb_theme_" + themeKey + "_name";
75
+ const dk = "vonb_theme_" + themeKey + "_deck";
76
+ const nv = I18n.t(nk);
77
+ const dv = I18n.t(dk);
78
+ if (nv && nv !== nk) name = nv;
79
+ if (dv && dv !== dk) deck = dv;
80
+ }
81
+ labelEl.innerHTML =
82
+ '<span class="v-name"></span><span class="v-deck"></span>';
83
+ labelEl.querySelector(".v-name").textContent = name;
84
+ labelEl.querySelector(".v-deck").textContent = deck;
85
+ }
86
+
87
+ function mountPreview() {
88
+ if (!bannerEl) return;
89
+ const themes = readThemes();
90
+ if (themes.length === 0) return;
91
+ const idx = nextThemeIdx(themes);
92
+ const card = themes[idx].cloneNode(true);
93
+ bannerEl.replaceChildren(card);
94
+ const themeKey = card.getAttribute("data-preview-theme") || "eastwood";
95
+ refreshLabel(themeKey);
96
+ }
97
+
98
+ function wireEvents() {
99
+ if (wired) return;
100
+ wired = true;
101
+ overlayEl.addEventListener("click", (e) => {
102
+ const closer = e.target.closest("[data-vonb-close]");
103
+ if (closer) { e.preventDefault(); window.closeVoiceOnboarding(); return; }
104
+ const cta = e.target.closest("[data-vonb-cta]");
105
+ if (cta) {
106
+ e.preventDefault();
107
+ window.closeVoiceOnboarding();
108
+ if (typeof window.openUserSettings === "function") {
109
+ window.openUserSettings({ section: "keys", focusProvider: "minimax" });
110
+ }
111
+ }
112
+ });
113
+ document.addEventListener("keydown", (e) => {
114
+ if (!overlayEl.classList.contains("open")) return;
115
+ if (e.key === "Escape") {
116
+ e.preventDefault();
117
+ window.closeVoiceOnboarding();
118
+ }
119
+ });
120
+ }
121
+
122
+ window.openVoiceOnboarding = function () {
123
+ overlayEl = document.getElementById("vonb-overlay");
124
+ if (!overlayEl) return;
125
+ bannerEl = overlayEl.querySelector("[data-vonb-banner]");
126
+ labelEl = overlayEl.querySelector("[data-vonb-theme-label]");
127
+ wireEvents();
128
+ applyI18n();
129
+ mountPreview();
130
+ overlayEl.classList.add("open");
131
+ overlayEl.setAttribute("aria-hidden", "false");
132
+ document.body.classList.add("vonb-locked");
133
+ // Focus the CTA for keyboard users.
134
+ const cta = overlayEl.querySelector("[data-vonb-cta]");
135
+ if (cta) setTimeout(() => cta.focus(), 0);
136
+ };
137
+
138
+ window.closeVoiceOnboarding = function () {
139
+ if (!overlayEl) return;
140
+ overlayEl.classList.remove("open");
141
+ overlayEl.setAttribute("aria-hidden", "true");
142
+ document.body.classList.remove("vonb-locked");
143
+ };
144
+ })();