privateboard 0.1.20 → 0.1.21

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.
@@ -32,9 +32,9 @@
32
32
  position: relative;
33
33
  width: 100%;
34
34
  max-width: 640px;
35
- background: var(--panel, #131312);
36
- border: 0.5px solid var(--lime, #6FB572);
37
- color: var(--text, #C8C5BE);
35
+ background: var(--panel);
36
+ border: 0.5px solid var(--lime);
37
+ color: var(--text);
38
38
  display: flex;
39
39
  flex-direction: column;
40
40
  min-height: 0;
@@ -52,7 +52,7 @@
52
52
  content: "";
53
53
  position: absolute;
54
54
  width: 14px; height: 14px;
55
- border: 2px solid var(--lime, #6FB572);
55
+ border: 2px solid var(--lime);
56
56
  pointer-events: none;
57
57
  }
58
58
  .onb-modal::before { top: -2px; left: -2px; border-right: none; border-bottom: none; }
@@ -60,23 +60,23 @@
60
60
 
61
61
  /* Header */
62
62
  .onb-classification {
63
- background: var(--panel-2, #1A1A18);
64
- border-bottom: 0.5px solid var(--line-bright, #2A2A26);
63
+ background: var(--panel-2);
64
+ border-bottom: 0.5px solid var(--line-bright);
65
65
  padding: 6px 14px;
66
66
  font-size: 9px;
67
67
  letter-spacing: 0.2em;
68
68
  text-transform: uppercase;
69
- color: var(--lime, #6FB572);
69
+ color: var(--lime);
70
70
  display: flex;
71
71
  justify-content: space-between;
72
72
  align-items: center;
73
73
  flex: 0 0 auto;
74
74
  }
75
- .onb-classification .right { color: var(--text-faint, #3A382F); letter-spacing: 0.12em; }
75
+ .onb-classification .right { color: var(--text-faint); letter-spacing: 0.12em; }
76
76
 
77
77
  .onb-head {
78
78
  padding: 22px 28px 14px;
79
- border-bottom: 0.5px dashed var(--line-bright, #2A2A26);
79
+ border-bottom: 0.5px dashed var(--line-bright);
80
80
  flex: 0 0 auto;
81
81
  }
82
82
  .onb-tag {
@@ -85,14 +85,14 @@
85
85
  font-weight: 700;
86
86
  letter-spacing: 0.18em;
87
87
  text-transform: uppercase;
88
- color: var(--lime, #6FB572);
88
+ color: var(--lime);
89
89
  margin-bottom: 10px;
90
90
  }
91
91
  .onb-title {
92
92
  font-family: var(--font-human, var(--mono));
93
93
  font-size: 22px;
94
94
  font-weight: 700;
95
- color: var(--text, #C8C5BE);
95
+ color: var(--text);
96
96
  letter-spacing: -0.014em;
97
97
  line-height: 1.22;
98
98
  margin-bottom: 6px;
@@ -110,7 +110,7 @@
110
110
  .onb-deck {
111
111
  font-family: var(--font-human, var(--mono));
112
112
  font-size: 13px;
113
- color: var(--text-soft, #8E8B83);
113
+ color: var(--text-soft);
114
114
  line-height: 1.55;
115
115
  margin-top: 8px;
116
116
  }
@@ -120,17 +120,17 @@
120
120
  display: flex;
121
121
  gap: 6px;
122
122
  padding: 10px 28px;
123
- border-bottom: 0.5px dashed var(--line-bright, #2A2A26);
123
+ border-bottom: 0.5px dashed var(--line-bright);
124
124
  flex: 0 0 auto;
125
125
  }
126
126
  .onb-dot {
127
127
  flex: 1;
128
128
  height: 3px;
129
- background: var(--line-strong, #3A3A35);
129
+ background: var(--line-strong);
130
130
  transition: background 0.16s;
131
131
  }
132
- .onb-dot.active { background: var(--lime, #6FB572); }
133
- .onb-dot.done { background: var(--lime-dim, #2D5532); }
132
+ .onb-dot.active { background: var(--lime); }
133
+ .onb-dot.done { background: var(--lime-dim); }
134
134
 
135
135
  /* Body — pane is scrollable; nav is fixed at footer */
136
136
  .onb-body {
@@ -148,34 +148,34 @@
148
148
  font-weight: 700;
149
149
  letter-spacing: 0.14em;
150
150
  text-transform: uppercase;
151
- color: var(--text-faint, #3A382F);
151
+ color: var(--text-faint);
152
152
  margin-bottom: 6px;
153
153
  }
154
154
  .onb-field-hint {
155
155
  font-family: var(--font-human, var(--mono));
156
156
  font-size: 12px;
157
- color: var(--text-dim, #5C5A52);
157
+ color: var(--text-dim);
158
158
  line-height: 1.5;
159
159
  margin-top: 5px;
160
160
  }
161
161
  .onb-field-hint a {
162
- color: var(--lime, #6FB572);
162
+ color: var(--lime);
163
163
  text-decoration: underline;
164
- text-decoration-color: var(--lime-dim, #2D5532);
164
+ text-decoration-color: var(--lime-dim);
165
165
  text-underline-offset: 2px;
166
166
  }
167
167
 
168
168
  .onb-input-wrap {
169
- border: 0.5px solid var(--line-strong, #3A3A35);
170
- background: var(--bg, #0A0A0A);
169
+ border: 0.5px solid var(--line-strong);
170
+ background: var(--bg);
171
171
  display: flex;
172
172
  align-items: stretch;
173
173
  transition: border-color 0.12s;
174
174
  }
175
- .onb-input-wrap:focus-within { border-color: var(--lime, #6FB572); }
175
+ .onb-input-wrap:focus-within { border-color: var(--lime); }
176
176
  .onb-input-wrap::before {
177
177
  content: ">";
178
- color: var(--lime, #6FB572);
178
+ color: var(--lime);
179
179
  font-weight: 700;
180
180
  font-size: 13px;
181
181
  font-family: var(--mono);
@@ -188,13 +188,13 @@
188
188
  background: transparent;
189
189
  font-family: var(--font-human, var(--mono));
190
190
  font-size: 14px;
191
- color: var(--text, #C8C5BE);
191
+ color: var(--text);
192
192
  outline: none;
193
193
  padding: 9px 12px;
194
194
  letter-spacing: -0.003em;
195
195
  width: 100%;
196
196
  }
197
- .onb-input::placeholder { color: var(--text-faint, #3A382F); }
197
+ .onb-input::placeholder { color: var(--text-faint); }
198
198
  /* Serif variant used by the name field on step 0. */
199
199
  .onb-input-serif {
200
200
  font-family: "Tiempos Text", "Charter", Georgia, "Times New Roman", serif;
@@ -208,7 +208,7 @@
208
208
  .onb-input-reveal {
209
209
  background: none;
210
210
  border: 0;
211
- color: var(--text-dim, #5C5A52);
211
+ color: var(--text-dim);
212
212
  cursor: pointer;
213
213
  font-family: var(--mono);
214
214
  font-size: 9px;
@@ -218,17 +218,17 @@
218
218
  align-self: stretch;
219
219
  transition: color 0.12s;
220
220
  }
221
- .onb-input-reveal:hover { color: var(--text-soft, #8E8B83); }
222
- .onb-input-reveal[aria-pressed="true"] { color: var(--lime, #6FB572); }
221
+ .onb-input-reveal:hover { color: var(--text-soft); }
222
+ .onb-input-reveal[aria-pressed="true"] { color: var(--lime); }
223
223
 
224
224
  /* Autofill suppression (mirrors user-settings.css) */
225
225
  .onb-input:-webkit-autofill,
226
226
  .onb-input:-webkit-autofill:hover,
227
227
  .onb-input:-webkit-autofill:focus,
228
228
  .onb-input:-webkit-autofill:active {
229
- -webkit-text-fill-color: var(--text, #C8C5BE);
230
- -webkit-box-shadow: 0 0 0 1000px var(--bg, #0A0A0A) inset;
231
- caret-color: var(--text, #C8C5BE);
229
+ -webkit-text-fill-color: var(--text);
230
+ -webkit-box-shadow: 0 0 0 1000px var(--bg) inset;
231
+ caret-color: var(--text);
232
232
  transition: background-color 0s 9999s;
233
233
  }
234
234
  :root[data-theme="dark"] .onb-input { color-scheme: dark; }
@@ -245,14 +245,14 @@
245
245
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
246
246
  font-size: 14px;
247
247
  line-height: 1.7;
248
- color: var(--text-soft, #8E8B83);
248
+ color: var(--text-soft);
249
249
  margin: 0;
250
250
  }
251
251
  .onb-narrative-note {
252
252
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
253
253
  font-size: 12px;
254
254
  line-height: 1.6;
255
- color: var(--text-dim, #5C5A52);
255
+ color: var(--text-dim);
256
256
  margin: 0;
257
257
  padding-left: 12px;
258
258
  border-left: 0;
@@ -265,7 +265,7 @@
265
265
  font-size: 9px;
266
266
  letter-spacing: 0.18em;
267
267
  text-transform: uppercase;
268
- color: var(--lime, #6FB572);
268
+ color: var(--lime);
269
269
  margin-bottom: 4px;
270
270
  padding-left: 0;
271
271
  }
@@ -280,19 +280,19 @@
280
280
  .onb-voice { display: flex; flex-direction: column; gap: 14px; }
281
281
  .onb-voice-banner {
282
282
  height: 180px;
283
- border: 0.5px solid var(--line-bright, #2A2A26);
284
- background: var(--bg, #0A0A0A);
283
+ border: 0.5px solid var(--line-bright);
284
+ background: var(--bg);
285
285
  }
286
286
  .onb-voice-banner-fallback {
287
287
  width: 100%;
288
288
  height: 100%;
289
- background: var(--panel-2, #1A1A18);
289
+ background: var(--panel-2);
290
290
  }
291
291
  .onb-voice-pitch {
292
292
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
293
293
  font-size: 13px;
294
294
  line-height: 1.6;
295
- color: var(--text-soft, #8E8B83);
295
+ color: var(--text-soft);
296
296
  margin: 0;
297
297
  }
298
298
  .onb-voice-providers { margin-top: 2px; }
@@ -317,7 +317,7 @@
317
317
  font-weight: 700;
318
318
  letter-spacing: 0.16em;
319
319
  text-transform: uppercase;
320
- color: var(--text-faint, #3A382F);
320
+ color: var(--text-faint);
321
321
  }
322
322
  .onb-cast-next-flow {
323
323
  display: flex;
@@ -330,8 +330,8 @@
330
330
  align-items: baseline;
331
331
  gap: 8px;
332
332
  padding: 8px 12px;
333
- background: var(--bg, #0A0A0A);
334
- border: 0.5px solid var(--line-strong, #3A3A35);
333
+ background: var(--bg);
334
+ border: 0.5px solid var(--line-strong);
335
335
  min-width: 0;
336
336
  flex: 1 1 auto;
337
337
  }
@@ -340,20 +340,20 @@
340
340
  font-size: 11px;
341
341
  font-weight: 700;
342
342
  letter-spacing: 0.04em;
343
- color: var(--lime, #6FB572);
343
+ color: var(--lime);
344
344
  flex-shrink: 0;
345
345
  }
346
346
  .onb-cast-next-label {
347
347
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
348
348
  font-size: 12px;
349
349
  line-height: 1.4;
350
- color: var(--text, #C8C5BE);
350
+ color: var(--text);
351
351
  }
352
352
  .onb-cast-next-arrow {
353
353
  font-family: var(--mono);
354
354
  font-size: 13px;
355
355
  font-weight: 700;
356
- color: var(--text-faint, #3A382F);
356
+ color: var(--text-faint);
357
357
  align-self: center;
358
358
  flex-shrink: 0;
359
359
  }
@@ -367,7 +367,7 @@
367
367
  font-weight: 700;
368
368
  letter-spacing: 0.16em;
369
369
  text-transform: uppercase;
370
- color: var(--text-faint, #3A382F);
370
+ color: var(--text-faint);
371
371
  }
372
372
  .onb-cast-grid {
373
373
  display: grid;
@@ -407,7 +407,7 @@
407
407
  font-size: 10px;
408
408
  font-weight: 700;
409
409
  letter-spacing: 0.04em;
410
- color: var(--text, #C8C5BE);
410
+ color: var(--text);
411
411
  white-space: nowrap;
412
412
  overflow: hidden;
413
413
  text-overflow: ellipsis;
@@ -417,7 +417,7 @@
417
417
  font-family: var(--font-human);
418
418
  font-style: italic;
419
419
  font-size: 10px;
420
- color: var(--text-dim, #5C5A52);
420
+ color: var(--text-dim);
421
421
  white-space: nowrap;
422
422
  overflow: hidden;
423
423
  text-overflow: ellipsis;
@@ -443,21 +443,21 @@
443
443
  position: relative;
444
444
  display: block;
445
445
  text-align: left;
446
- background: var(--bg, #0A0A0A);
447
- border: 0.5px solid var(--line-strong, #3A3A35);
446
+ background: var(--bg);
447
+ border: 0.5px solid var(--line-strong);
448
448
  padding: 12px 14px;
449
449
  cursor: pointer;
450
450
  font-family: inherit;
451
- color: var(--text-soft, #8E8B83);
451
+ color: var(--text-soft);
452
452
  transition: border-color 0.14s, color 0.14s, background 0.14s;
453
453
  }
454
454
  .onb-key-recommend:hover {
455
- border-color: var(--lime-dim, #2D5532);
455
+ border-color: var(--lime-dim);
456
456
  }
457
457
  .onb-key-recommend.active {
458
- border-color: var(--lime, #6FB572);
459
- color: var(--text, #C8C5BE);
460
- background: var(--panel-2, #1A1A18);
458
+ border-color: var(--lime);
459
+ color: var(--text);
460
+ background: var(--panel-2);
461
461
  }
462
462
  .onb-key-recommend-head {
463
463
  display: flex;
@@ -471,7 +471,7 @@
471
471
  font-weight: 700;
472
472
  letter-spacing: 0.18em;
473
473
  text-transform: uppercase;
474
- color: var(--lime, #6FB572);
474
+ color: var(--lime);
475
475
  }
476
476
  .onb-key-recommend-name {
477
477
  font-family: var(--font-human);
@@ -481,13 +481,13 @@
481
481
  letter-spacing: -0.005em;
482
482
  }
483
483
  .onb-key-recommend.active .onb-key-recommend-name {
484
- color: var(--text, #C8C5BE);
484
+ color: var(--text);
485
485
  }
486
486
  .onb-key-recommend-dot {
487
487
  width: 6px;
488
488
  height: 6px;
489
489
  border-radius: 50%;
490
- background: var(--lime, #6FB572);
490
+ background: var(--lime);
491
491
  display: inline-block;
492
492
  font-size: 0;
493
493
  line-height: 0;
@@ -497,10 +497,10 @@
497
497
  font-family: var(--font-human);
498
498
  font-size: 12px;
499
499
  line-height: 1.55;
500
- color: var(--text-dim, #5C5A52);
500
+ color: var(--text-dim);
501
501
  }
502
502
  .onb-key-recommend.active .onb-key-recommend-body {
503
- color: var(--text-soft, #8E8B83);
503
+ color: var(--text-soft);
504
504
  }
505
505
 
506
506
  /* "// or" divider · mono kicker between recommend card and direct
@@ -514,7 +514,7 @@
514
514
  .onb-key-or-line {
515
515
  flex: 1;
516
516
  height: 1px;
517
- background: var(--line-bright, #2A2A26);
517
+ background: var(--line-bright);
518
518
  }
519
519
  .onb-key-or-text {
520
520
  font-family: var(--mono);
@@ -522,13 +522,13 @@
522
522
  font-weight: 700;
523
523
  letter-spacing: 0.2em;
524
524
  text-transform: uppercase;
525
- color: var(--text-faint, #3A382F);
525
+ color: var(--text-faint);
526
526
  }
527
527
  .onb-key-or-body {
528
528
  font-family: var(--font-human);
529
529
  font-size: 12px;
530
530
  line-height: 1.55;
531
- color: var(--text-dim, #5C5A52);
531
+ color: var(--text-dim);
532
532
  margin: 0;
533
533
  }
534
534
 
@@ -545,9 +545,9 @@
545
545
  align-items: center;
546
546
  gap: 8px;
547
547
  padding: 6px 12px;
548
- background: var(--bg, #0A0A0A);
549
- border: 0.5px solid var(--line-strong, #3A3A35);
550
- color: var(--text-dim, #5C5A52);
548
+ background: var(--bg);
549
+ border: 0.5px solid var(--line-strong);
550
+ color: var(--text-dim);
551
551
  cursor: pointer;
552
552
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
553
553
  font-size: 13px;
@@ -557,13 +557,13 @@
557
557
  transition: border-color 0.12s, color 0.12s, background 0.12s;
558
558
  }
559
559
  .onb-key-direct:hover {
560
- border-color: var(--lime-dim, #2D5532);
561
- color: var(--text-soft, #8E8B83);
560
+ border-color: var(--lime-dim);
561
+ color: var(--text-soft);
562
562
  }
563
563
  .onb-key-direct.active {
564
- border-color: var(--lime, #6FB572);
565
- color: var(--text, #C8C5BE);
566
- background: var(--panel-2, #1A1A18);
564
+ border-color: var(--lime);
565
+ color: var(--text);
566
+ background: var(--panel-2);
567
567
  font-weight: 600;
568
568
  }
569
569
  .onb-key-direct-label { font: inherit; }
@@ -571,7 +571,7 @@
571
571
  width: 5px;
572
572
  height: 5px;
573
573
  border-radius: 50%;
574
- background: var(--lime, #6FB572);
574
+ background: var(--lime);
575
575
  display: inline-block;
576
576
  font-size: 0;
577
577
  line-height: 0;
@@ -585,14 +585,14 @@
585
585
  text-transform: uppercase;
586
586
  margin-top: 6px;
587
587
  }
588
- .onb-key-status.ok { color: var(--lime, #6FB572); }
589
- .onb-key-status.warn { color: var(--amber, #B59560); }
590
- .onb-key-status.error { color: var(--red, #B5706A); }
588
+ .onb-key-status.ok { color: var(--lime); }
589
+ .onb-key-status.warn { color: var(--amber); }
590
+ .onb-key-status.error { color: var(--red); }
591
591
 
592
592
  /* Footer · nav */
593
593
  .onb-foot {
594
- border-top: 0.5px solid var(--line-bright, #2A2A26);
595
- background: var(--panel-2, #1A1A18);
594
+ border-top: 0.5px solid var(--line-bright);
595
+ background: var(--panel-2);
596
596
  padding: 12px 18px;
597
597
  display: flex;
598
598
  justify-content: space-between;
@@ -610,20 +610,20 @@
610
610
  text-transform: uppercase;
611
611
  letter-spacing: 0.1em;
612
612
  padding: 7px 13px;
613
- border: 0.5px solid var(--line-strong, #3A3A35);
613
+ border: 0.5px solid var(--line-strong);
614
614
  background: transparent;
615
- color: var(--text-soft, #8E8B83);
615
+ color: var(--text-soft);
616
616
  cursor: pointer;
617
617
  text-decoration: none;
618
618
  transition: all 0.12s;
619
619
  }
620
- .onb-btn:hover { border-color: var(--lime, #6FB572); color: var(--lime, #6FB572); }
620
+ .onb-btn:hover { border-color: var(--lime); color: var(--lime); }
621
621
  .onb-btn.primary {
622
- background: var(--lime, #6FB572);
623
- color: var(--bg, #0A0A0A);
624
- border-color: var(--lime, #6FB572);
622
+ background: var(--lime);
623
+ color: var(--bg);
624
+ border-color: var(--lime);
625
625
  }
626
- .onb-btn.primary:hover { background: transparent; color: var(--lime, #6FB572); }
626
+ .onb-btn.primary:hover { background: transparent; color: var(--lime); }
627
627
  .onb-btn:disabled,
628
628
  .onb-btn.primary:disabled {
629
629
  opacity: 0.4;
@@ -647,9 +647,9 @@
647
647
  z-index: 1400;
648
648
  max-width: 320px;
649
649
  padding: 14px 16px 12px;
650
- background: var(--panel, #131312);
651
- border: 0.5px solid var(--lime, #6FB572);
652
- color: var(--text, #C8C5BE);
650
+ background: var(--panel);
651
+ border: 0.5px solid var(--lime);
652
+ color: var(--text);
653
653
  font-family: var(--font-human, "Inter", system-ui, sans-serif);
654
654
  opacity: 0;
655
655
  transform: translateY(-4px);
@@ -663,9 +663,9 @@
663
663
  left: 28px;
664
664
  width: 12px;
665
665
  height: 12px;
666
- background: var(--panel, #131312);
667
- border-top: 0.5px solid var(--lime, #6FB572);
668
- border-left: 0.5px solid var(--lime, #6FB572);
666
+ background: var(--panel);
667
+ border-top: 0.5px solid var(--lime);
668
+ border-left: 0.5px solid var(--lime);
669
669
  transform: rotate(45deg);
670
670
  }
671
671
  .onb-composer-hint-kicker {
@@ -674,7 +674,7 @@
674
674
  font-weight: 700;
675
675
  letter-spacing: 0.18em;
676
676
  text-transform: uppercase;
677
- color: var(--lime, #6FB572);
677
+ color: var(--lime);
678
678
  margin-bottom: 8px;
679
679
  }
680
680
  .onb-composer-hint-body {
@@ -683,7 +683,7 @@
683
683
  font-size: 15px;
684
684
  line-height: 1.45;
685
685
  letter-spacing: -0.005em;
686
- color: var(--text, #C8C5BE);
686
+ color: var(--text);
687
687
  margin-bottom: 10px;
688
688
  }
689
689
  .onb-composer-hint-dismiss {
@@ -694,12 +694,12 @@
694
694
  text-transform: uppercase;
695
695
  padding: 5px 10px;
696
696
  background: transparent;
697
- border: 0.5px solid var(--line-strong, #3A3A35);
698
- color: var(--text-soft, #8E8B83);
697
+ border: 0.5px solid var(--line-strong);
698
+ color: var(--text-soft);
699
699
  cursor: pointer;
700
700
  transition: all 0.12s;
701
701
  }
702
702
  .onb-composer-hint-dismiss:hover {
703
- border-color: var(--lime, #6FB572);
704
- color: var(--lime, #6FB572);
703
+ border-color: var(--lime);
704
+ color: var(--lime);
705
705
  }
@@ -111,8 +111,10 @@
111
111
  onb_v2_key_title: "Pick one brain — or pick many.",
112
112
  onb_v2_key_body: "Your key stays on this machine. We never upload it.",
113
113
  onb_v2_key_recommend_badge: "// recommended",
114
- onb_v2_key_recommend_name: "OpenRouter · one key, every model",
115
- onb_v2_key_recommend_body: "Each director can run on a different model — Claude as the skeptic, GPT as the pattern hunter, Gemini as the long-horizon strategist. The chair routes each turn to the right brain.",
114
+ onb_v2_key_recommend_openrouter_name: "OpenRouter · one key, every model",
115
+ onb_v2_key_recommend_openrouter_body: "Each director can run on a different model — Claude as the skeptic, GPT as the pattern hunter, Gemini as the long-horizon strategist. The chair routes each turn to the right brain.",
116
+ onb_v2_key_recommend_bai_name: "B.AI · one key, every model",
117
+ onb_v2_key_recommend_bai_body: "A second universal aggregator with its own pricing and access channels. Pick whichever has the credits or regional pricing that suits you, or use it as a fallback when an OpenRouter route is throttled.",
116
118
  onb_v2_key_or: "or — a direct provider",
117
119
  onb_v2_key_or_body: "Same model for every director. Personas stay distinct, but they all share one brain underneath.",
118
120
  onb_v2_voice_kicker: "03 — Give them a voice · optional",
@@ -151,9 +153,12 @@
151
153
  }
152
154
 
153
155
  // ── Provider catalogue ─────────────────────────────────
154
- // Model providers shown on step 2. OpenRouter leads — it's the
155
- // universal router that unlocks every model from a single key, so
156
- // it's the lowest-friction first stop for new users.
156
+ // Model providers shown on step 2. Universal aggregators (OpenRouter
157
+ // and B.AI) lead — each unlocks every model from a single key, so
158
+ // they're the lowest-friction first stop. Both carry `recommend: true`
159
+ // and render as the prominent value-prop cards at the top of the
160
+ // step; the rest fall through to the smaller "or — a direct provider"
161
+ // chip row beneath.
157
162
  // `slug` matches /api/keys/{slug} on the backend.
158
163
  const KEY_PROVIDERS = [
159
164
  {
@@ -163,6 +168,16 @@
163
168
  placeholder: "sk-or-v1-…",
164
169
  help: "openrouter.ai/keys",
165
170
  helpUrl: "https://openrouter.ai/keys",
171
+ recommend: true,
172
+ },
173
+ {
174
+ slug: "bai",
175
+ label: "B.AI",
176
+ sub: "all-in-one aggregator",
177
+ placeholder: "sk-…",
178
+ help: "b.ai",
179
+ helpUrl: "https://b.ai/",
180
+ recommend: true,
166
181
  },
167
182
  {
168
183
  slug: "anthropic",
@@ -228,6 +243,7 @@
228
243
  let prefsCache = { name: "", intro: "" };
229
244
  let providerConfigured = {
230
245
  openrouter: false,
246
+ bai: false,
231
247
  anthropic: false,
232
248
  openai: false,
233
249
  google: false,
@@ -440,25 +456,38 @@
440
456
 
441
457
  else if (currentStep === 2) {
442
458
  const active = KEY_PROVIDERS.find((p) => p.slug === activeProvider) || KEY_PROVIDERS[0];
443
- const isOr = active.slug === "openrouter";
444
-
445
- // Recommended path · OpenRouter as a value-prop card.
446
- const orConfigured = providerConfigured.openrouter;
447
- const recommendCard = `
448
- <button type="button"
449
- class="onb-key-recommend${isOr ? " active" : ""}${orConfigured ? " configured" : ""}"
450
- data-onb-provider="openrouter">
451
- <div class="onb-key-recommend-head">
452
- <span class="onb-key-recommend-badge">${escape(t("onb_v2_key_recommend_badge"))}</span>
453
- <span class="onb-key-recommend-name">${escape(t("onb_v2_key_recommend_name"))}</span>
454
- ${orConfigured ? `<span class="onb-key-recommend-dot" title="configured">●</span>` : ""}
455
- </div>
456
- <div class="onb-key-recommend-body">${escape(t("onb_v2_key_recommend_body"))}</div>
457
- </button>
458
- `;
459
459
 
460
- // Direct providers · same-model alternative.
461
- const directProviders = KEY_PROVIDERS.filter((p) => p.slug !== "openrouter");
460
+ // Recommended path · render a value-prop card for every provider
461
+ // tagged `recommend: true` (currently OpenRouter + B.AI, the two
462
+ // universal aggregators). Cards stack vertically via the parent
463
+ // `.onb-key-frame`'s flex column; selecting any card flips it to
464
+ // active and swaps the field below to that provider's key form.
465
+ const recommendProviders = KEY_PROVIDERS.filter((p) => p.recommend);
466
+ const recommendCards = recommendProviders.map((p) => {
467
+ const isActive = p.slug === active.slug;
468
+ const isConfigured = providerConfigured[p.slug];
469
+ // Per-provider i18n: `onb_v2_key_recommend_<slug>_name` and
470
+ // `_body`. The shared `_badge` ("// recommended") is the same
471
+ // for every recommend card.
472
+ const nameText = t(`onb_v2_key_recommend_${p.slug}_name`);
473
+ const bodyText = t(`onb_v2_key_recommend_${p.slug}_body`);
474
+ return `
475
+ <button type="button"
476
+ class="onb-key-recommend${isActive ? " active" : ""}${isConfigured ? " configured" : ""}"
477
+ data-onb-provider="${escape(p.slug)}">
478
+ <div class="onb-key-recommend-head">
479
+ <span class="onb-key-recommend-badge">${escape(t("onb_v2_key_recommend_badge"))}</span>
480
+ <span class="onb-key-recommend-name">${escape(nameText)}</span>
481
+ ${isConfigured ? `<span class="onb-key-recommend-dot" title="configured">●</span>` : ""}
482
+ </div>
483
+ <div class="onb-key-recommend-body">${escape(bodyText)}</div>
484
+ </button>
485
+ `;
486
+ }).join("");
487
+
488
+ // Direct providers · same-model alternative · everything NOT
489
+ // tagged `recommend: true` falls into the smaller chip row.
490
+ const directProviders = KEY_PROVIDERS.filter((p) => !p.recommend);
462
491
  const directChips = directProviders.map((p) => {
463
492
  const isActive = p.slug === active.slug;
464
493
  const isConfigured = providerConfigured[p.slug];
@@ -483,7 +512,7 @@
483
512
  `;
484
513
  body.innerHTML = `
485
514
  <div class="onb-key-frame">
486
- ${recommendCard}
515
+ ${recommendCards}
487
516
  <div class="onb-key-or">
488
517
  <span class="onb-key-or-line"></span>
489
518
  <span class="onb-key-or-text">${escape(t("onb_v2_key_or"))}</span>