@silicaclaw/cli 1.0.0-beta.9 → 2026.3.18-3

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.
@@ -4,65 +4,117 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>SilicaClaw Control UI</title>
7
- <meta name="description" content="SilicaClaw local-first agent control console." />
7
+ <meta id="metaDescription" name="description" content="SilicaClaw local-first agent control console." />
8
8
  <meta property="og:type" content="website" />
9
- <meta property="og:title" content="SilicaClaw Local Console" />
10
- <meta property="og:description" content="Local-first control surface for SilicaClaw agents." />
9
+ <meta id="ogTitle" property="og:title" content="SilicaClaw Local Console" />
10
+ <meta id="ogDescription" property="og:description" content="Local-first control surface for SilicaClaw agents." />
11
11
  <meta property="og:image" content="/assets/silicaclaw-logo.png" />
12
12
  <meta name="twitter:card" content="summary_large_image" />
13
- <meta name="twitter:title" content="SilicaClaw Local Console" />
14
- <meta name="twitter:description" content="Local-first control surface for SilicaClaw agents." />
13
+ <meta id="twitterTitle" name="twitter:title" content="SilicaClaw Local Console" />
14
+ <meta id="twitterDescription" name="twitter:description" content="Local-first control surface for SilicaClaw agents." />
15
15
  <meta name="twitter:image" content="/assets/silicaclaw-logo.png" />
16
16
  <link rel="icon" type="image/png" href="/assets/silicaclaw-logo.png" />
17
17
  <link rel="apple-touch-icon" href="/assets/silicaclaw-logo.png" />
18
18
  <style>
19
19
  :root {
20
20
  --bg: #0e1015;
21
+ --bg-accent: #13151b;
21
22
  --bg-elevated: #191c24;
22
23
  --bg-hover: #1f2330;
24
+ --bg-muted: #1f2330;
23
25
  --panel: #0e1015;
26
+ --panel-strong: #191c24;
27
+ --panel-hover: #1f2330;
24
28
  --card: #161920;
25
- --line: #1e2028;
26
- --line-strong: #2e3040;
29
+ --card-foreground: #f0f0f2;
30
+ --card-highlight: rgba(255, 255, 255, 0.04);
31
+ --card-soft: rgba(22, 25, 32, 0.86);
32
+ --card-strong: rgba(25, 28, 36, 0.96);
33
+ --chrome: rgba(14, 16, 21, 0.96);
34
+ --chrome-strong: rgba(14, 16, 21, 0.98);
27
35
  --text: #d4d4d8;
28
36
  --text-strong: #f4f4f5;
29
- --muted: #636370;
30
- --brand: #ff5c5c;
31
- --brand-hover: #ff7070;
32
- --brand-soft: rgba(255, 92, 92, 0.1);
37
+ --muted: #838387;
38
+ --muted-strong: #62626a;
39
+ --border: #1e2028;
40
+ --border-strong: #2e3040;
41
+ --border-hover: #3e4050;
42
+ --accent: #ff5c5c;
43
+ --accent-hover: #ff7070;
44
+ --accent-subtle: rgba(255, 92, 92, 0.1);
45
+ --accent-glow: rgba(255, 92, 92, 0.2);
33
46
  --ok: #22c55e;
34
47
  --warn: #f59e0b;
35
- --err: #ef4444;
36
- --chip: #1f2330;
48
+ --danger: #ef4444;
49
+ --info: #3b82f6;
50
+ --focus-ring: 0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--accent) 60%, transparent);
51
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
52
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.3);
53
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.4);
54
+ --radius-sm: 6px;
55
+ --radius-md: 10px;
56
+ --radius-lg: 14px;
57
+ --radius-xl: 20px;
58
+ --radius-full: 9999px;
59
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
60
+ --duration-fast: 100ms;
61
+ --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
62
+ --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
63
+
37
64
  }
38
65
  :root[data-theme-mode="light"] {
39
66
  --bg: #f8f9fa;
40
- --bg-elevated: #f1f3f5;
67
+ --bg-accent: #f1f3f5;
68
+ --bg-elevated: #ffffff;
41
69
  --bg-hover: #eceef0;
70
+ --bg-muted: #eceef0;
42
71
  --panel: #f8f9fa;
72
+ --panel-strong: #f1f3f5;
73
+ --panel-hover: #e6e8eb;
43
74
  --card: #ffffff;
44
- --line: #e5e5ea;
45
- --line-strong: #d1d1d6;
75
+ --card-foreground: #1a1a1e;
76
+ --card-highlight: rgba(0, 0, 0, 0.02);
77
+ --card-soft: rgba(255, 255, 255, 0.9);
78
+ --card-strong: rgba(255, 255, 255, 0.98);
79
+ --chrome: rgba(248, 249, 250, 0.96);
80
+ --chrome-strong: rgba(248, 249, 250, 0.98);
46
81
  --text: #3c3c43;
47
82
  --text-strong: #1a1a1e;
48
- --muted: #8e8e93;
49
- --brand: #dc2626;
50
- --brand-hover: #ef4444;
51
- --brand-soft: rgba(220, 38, 38, 0.08);
52
- --ok: #16a34a;
53
- --warn: #d97706;
54
- --err: #dc2626;
55
- --chip: #f1f3f5;
83
+ --muted: #6e6e73;
84
+ --muted-strong: #545458;
85
+ --border: #e5e5ea;
86
+ --border-strong: #d1d1d6;
87
+ --border-hover: #aeaeb2;
88
+ --accent: #dc2626;
89
+ --accent-hover: #ef4444;
90
+ --accent-subtle: rgba(220, 38, 38, 0.08);
91
+ --accent-glow: rgba(220, 38, 38, 0.1);
92
+ --ok: #15803d;
93
+ --warn: #b45309;
94
+ --danger: #dc2626;
95
+ --info: #2563eb;
96
+ --focus-ring: 0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--accent) 50%, transparent);
97
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
98
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06);
99
+ --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.08);
100
+
56
101
  }
57
102
  * { box-sizing: border-box; }
58
- html, body { margin: 0; min-height: 100%; }
103
+ html, body {
104
+ margin: 0;
105
+ height: 100%;
106
+ overflow: hidden;
107
+ }
59
108
  body {
60
- font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif;
109
+ font: 400 13.5px/1.55 var(--font-body);
110
+ letter-spacing: -0.01em;
61
111
  color: var(--text);
62
112
  background:
63
113
  radial-gradient(900px 420px at 8% -12%, rgba(255, 92, 92, 0.18), transparent 60%),
64
114
  linear-gradient(180deg, #0e1015 0%, #0e1015 62%, #0f1219 100%);
65
115
  transition: background .2s ease, color .2s ease;
116
+ -webkit-font-smoothing: antialiased;
117
+ -moz-osx-font-smoothing: grayscale;
66
118
  }
67
119
  :root[data-theme-mode="light"] body {
68
120
  background:
@@ -72,35 +124,121 @@
72
124
 
73
125
  .app {
74
126
  display: grid;
75
- grid-template-columns: 240px 1fr;
76
- min-height: 100vh;
127
+ grid-template-columns: 258px 1fr;
128
+ height: 100vh;
129
+ overflow: hidden;
130
+ transition: grid-template-columns .2s ease;
131
+ }
132
+ .app.nav-collapsed {
133
+ grid-template-columns: 78px 1fr;
134
+ }
135
+ .app.focus-mode {
136
+ grid-template-columns: 1fr;
77
137
  }
78
138
 
79
139
  .sidebar {
80
- border-right: 1px solid color-mix(in srgb, var(--line) 74%, transparent);
81
- background: color-mix(in srgb, var(--panel) 96%, transparent);
82
- padding: 18px 14px;
140
+ display: flex;
141
+ flex-direction: column;
142
+ min-height: 0;
143
+ border-right: 1px solid color-mix(in srgb, var(--border) 74%, transparent);
144
+ background: var(--bg);
145
+ padding: 14px 10px 12px;
146
+ position: relative;
147
+ overflow: hidden;
148
+ transition: padding .2s ease;
149
+ }
150
+ :root[data-theme-mode="light"] .sidebar {
151
+ background: var(--panel);
152
+ }
153
+ .app.nav-collapsed .sidebar {
154
+ padding: 12px 8px 10px;
155
+ }
156
+ .sidebar-shell {
157
+ display: flex;
158
+ flex: 1 1 auto;
159
+ min-height: 0;
160
+ flex-direction: column;
161
+ }
162
+ .sidebar-shell__header {
163
+ flex: 0 0 auto;
164
+ display: flex;
165
+ align-items: center;
166
+ justify-content: space-between;
167
+ gap: 12px;
168
+ min-height: 0;
169
+ padding: 0 8px 18px;
170
+ }
171
+ .nav-collapse-toggle {
172
+ width: 36px;
173
+ height: 36px;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ background: var(--bg-elevated);
178
+ border: 1px solid color-mix(in srgb, var(--border-strong) 68%, transparent);
179
+ border-radius: 999px;
180
+ cursor: pointer;
181
+ color: var(--muted);
182
+ flex: 0 0 auto;
183
+ transition:
184
+ background .16s ease,
185
+ border-color .16s ease,
186
+ color .16s ease,
187
+ transform .16s ease;
188
+ }
189
+ .nav-collapse-toggle:hover {
190
+ background: color-mix(in srgb, var(--bg-hover) 90%, transparent);
191
+ border-color: color-mix(in srgb, var(--border-strong) 88%, transparent);
192
+ color: var(--text);
193
+ transform: translateY(-1px);
194
+ }
195
+ .nav-collapse-toggle__icon {
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ width: 16px;
200
+ height: 16px;
201
+ color: inherit;
202
+ }
203
+ .nav-collapse-toggle__icon svg {
204
+ width: 16px;
205
+ height: 16px;
206
+ stroke: currentColor;
207
+ fill: none;
208
+ stroke-width: 1.5px;
209
+ stroke-linecap: round;
210
+ stroke-linejoin: round;
211
+ }
212
+ .sidebar-shell__body {
213
+ flex: 1 1 auto;
214
+ min-height: 0;
215
+ display: flex;
216
+ flex-direction: column;
217
+ }
218
+ .sidebar-shell__footer {
219
+ flex: 0 0 auto;
220
+ padding: 12px 8px 0;
83
221
  }
84
222
  .brand {
85
223
  display: flex;
86
224
  align-items: center;
87
225
  gap: 10px;
88
- margin-bottom: 18px;
226
+ margin-bottom: 0;
89
227
  }
90
228
  .brand-logo {
91
- width: 38px;
92
- height: 38px;
229
+ width: 32px;
230
+ height: 32px;
93
231
  border-radius: 10px;
94
232
  object-fit: cover;
95
233
  display: block;
96
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.24);
97
- border: 1px solid color-mix(in srgb, var(--line) 75%, transparent);
234
+ box-shadow: 0 8px 18px rgba(0, 0, 0, 0.18);
235
+ border: 1px solid color-mix(in srgb, var(--border) 75%, transparent);
98
236
  }
99
237
  .brand-badge {
100
- width: 38px;
101
- height: 38px;
238
+ width: 32px;
239
+ height: 32px;
102
240
  border-radius: 10px;
103
- background: linear-gradient(135deg, var(--brand), var(--brand-hover));
241
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
104
242
  color: #fff;
105
243
  font-weight: 900;
106
244
  display: grid;
@@ -109,98 +247,425 @@
109
247
  .brand-badge.hidden { display: none; }
110
248
  .brand h1 {
111
249
  margin: 0;
112
- font-size: 17px;
250
+ font-size: 15px;
251
+ line-height: 1.1;
252
+ letter-spacing: -0.03em;
113
253
  }
114
254
  .brand p {
115
255
  margin: 2px 0 0;
116
256
  color: var(--muted);
117
- font-size: 12px;
257
+ font-size: 10px;
258
+ line-height: 1.15;
259
+ font-weight: 600;
260
+ letter-spacing: 0.08em;
261
+ text-transform: uppercase;
262
+ }
263
+ .version-dot {
264
+ width: 5px;
265
+ height: 5px;
266
+ border-radius: 999px;
267
+ background: var(--accent);
268
+ opacity: .85;
269
+ }
270
+ .app.nav-collapsed .brand h1,
271
+ .app.nav-collapsed .brand p,
272
+ .app.nav-collapsed .sidebar-foot,
273
+ .app.nav-collapsed .sidebar-nav__label {
274
+ display: none;
275
+ }
276
+ .app.focus-mode .sidebar {
277
+ display: none;
278
+ }
279
+ .app.focus-mode .integration-strip,
280
+ .app.focus-mode .page-hero,
281
+ .app.focus-mode .notice,
282
+ .app.focus-mode #publicDiscoveryHint {
283
+ display: none !important;
284
+ }
285
+ .app.nav-collapsed .brand {
286
+ display: none;
287
+ }
288
+ .app.nav-collapsed .sidebar-shell__header {
289
+ justify-content: center;
290
+ align-items: center;
291
+ gap: 0;
292
+ padding: 0 2px 16px;
293
+ }
294
+ .app.nav-collapsed .nav button {
295
+ justify-content: center;
296
+ width: 44px;
297
+ min-height: 44px;
298
+ padding: 0;
299
+ margin: 0 auto;
300
+ border-radius: 16px;
301
+ border-color: transparent;
302
+ box-shadow: none;
303
+ background: transparent;
304
+ }
305
+ .app.nav-collapsed .nav button .tab-labels {
306
+ display: none;
307
+ }
308
+ .app.nav-collapsed .nav button .tab-icon {
309
+ width: 18px;
310
+ height: 18px;
311
+ }
312
+ .app.nav-collapsed .nav button .tab-icon svg {
313
+ width: 18px;
314
+ height: 18px;
315
+ }
316
+ .app.nav-collapsed .nav button.active::before {
317
+ left: 8px;
318
+ top: 10px;
319
+ bottom: 10px;
320
+ }
321
+ .app.nav-collapsed .sidebar-shell__footer {
322
+ padding: 8px 0 2px;
323
+ }
324
+ .app.nav-collapsed .sidebar-utility {
325
+ display: none;
326
+ }
327
+ .app.nav-collapsed .sidebar-version {
328
+ width: 44px;
329
+ min-height: 44px;
330
+ padding: 0;
331
+ justify-content: center;
332
+ border-radius: 16px;
118
333
  }
119
334
 
120
335
  .nav {
121
336
  display: grid;
122
- gap: 8px;
337
+ gap: 4px;
338
+ min-height: 0;
339
+ overflow: auto;
340
+ padding: 0;
341
+ scrollbar-width: none;
342
+ }
343
+ .nav::-webkit-scrollbar { display: none; }
344
+ .sidebar-nav__label {
345
+ padding: 0 10px 8px;
346
+ color: var(--muted);
347
+ font-size: 12px;
348
+ font-weight: 700;
349
+ letter-spacing: 0.06em;
350
+ text-transform: uppercase;
123
351
  }
124
352
  .nav button {
353
+ position: relative;
354
+ display: flex;
355
+ align-items: center;
356
+ justify-content: flex-start;
357
+ gap: 8px;
358
+ min-height: 40px;
125
359
  text-align: left;
126
360
  border: 1px solid transparent;
127
361
  background: transparent;
128
362
  color: var(--muted);
129
- padding: 10px 12px;
130
- border-radius: 10px;
363
+ padding: 0 9px;
364
+ border-radius: 12px;
131
365
  cursor: pointer;
132
366
  font: inherit;
367
+ transition:
368
+ border-color .16s ease,
369
+ background .16s ease,
370
+ color .16s ease,
371
+ transform .16s ease;
372
+ }
373
+ .nav button .tab-icon {
374
+ width: 16px;
375
+ height: 16px;
376
+ display: inline-flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ flex-shrink: 0;
380
+ opacity: .72;
381
+ }
382
+ .nav button .tab-icon svg {
383
+ width: 16px;
384
+ height: 16px;
385
+ stroke: currentColor;
386
+ fill: none;
387
+ stroke-width: 1.5px;
388
+ stroke-linecap: round;
389
+ stroke-linejoin: round;
390
+ }
391
+ .nav button .tab-labels {
392
+ min-width: 0;
393
+ }
394
+ .nav button .tab-title {
395
+ display: block;
396
+ font-size: 14px;
397
+ font-weight: 600;
398
+ color: inherit;
399
+ white-space: nowrap;
400
+ }
401
+ .nav button .tab-copy {
402
+ display: none;
133
403
  }
134
404
  .nav button.active {
135
- color: #e9f7ff;
136
- border-color: color-mix(in srgb, var(--brand) 28%, transparent);
137
- background: var(--brand-soft);
405
+ color: var(--text-strong);
406
+ border-color: color-mix(in srgb, var(--accent) 18%, transparent);
407
+ background: color-mix(in srgb, var(--accent-subtle) 88%, var(--bg-elevated) 12%);
408
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 10%, transparent);
409
+ }
410
+ .nav button.active .tab-icon {
411
+ opacity: 1;
412
+ color: var(--accent);
413
+ }
414
+ .nav button:hover {
415
+ color: var(--text);
416
+ background: color-mix(in srgb, var(--bg-hover) 68%, transparent);
417
+ border-color: color-mix(in srgb, var(--border) 60%, transparent);
418
+ }
419
+ .nav button:hover .tab-icon {
420
+ opacity: 1;
138
421
  }
139
- .nav button:hover { color: #d2e7ff; }
140
422
 
141
423
  .sidebar-foot {
142
- margin-top: 20px;
143
- border-top: 1px dashed color-mix(in srgb, var(--line-strong) 85%, transparent);
144
- padding-top: 14px;
424
+ margin-top: 12px;
425
+ flex: 0 0 auto;
426
+ border-top: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
427
+ padding-top: 12px;
145
428
  color: var(--muted);
146
429
  font-size: 12px;
147
430
  }
431
+ .sidebar-utility {
432
+ display: grid;
433
+ gap: 8px;
434
+ padding: 12px;
435
+ border-radius: 16px;
436
+ border: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
437
+ background: var(--bg-elevated);
438
+ box-shadow: none;
439
+ }
440
+ .sidebar-utility-row {
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: space-between;
444
+ gap: 10px;
445
+ }
446
+ .sidebar-utility__label {
447
+ font-size: 11px;
448
+ font-weight: 700;
449
+ color: var(--muted);
450
+ text-transform: uppercase;
451
+ letter-spacing: 0.06em;
452
+ }
453
+ .sidebar-utility__value {
454
+ color: var(--text);
455
+ font-size: 12px;
456
+ font-weight: 600;
457
+ }
458
+ .sidebar-version {
459
+ margin-top: 10px;
460
+ display: flex;
461
+ align-items: center;
462
+ justify-content: center;
463
+ min-height: 40px;
464
+ padding: 0 12px;
465
+ border-radius: 14px;
466
+ background: var(--bg-elevated);
467
+ border: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
468
+ box-shadow: none;
469
+ }
470
+ .sidebar-version__text {
471
+ font-size: 12px;
472
+ color: var(--text);
473
+ font-weight: 600;
474
+ line-height: 1.2;
475
+ white-space: nowrap;
476
+ overflow: hidden;
477
+ text-overflow: ellipsis;
478
+ text-align: center;
479
+ }
480
+ .app.nav-collapsed .sidebar-version {
481
+ justify-content: center;
482
+ padding: 0;
483
+ }
148
484
 
149
485
  .main {
150
- padding: 18px;
486
+ display: flex;
487
+ flex-direction: column;
488
+ min-width: 0;
489
+ min-height: 0;
490
+ height: 100vh;
491
+ overflow: hidden;
492
+ padding: 0;
151
493
  }
152
494
  .topbar {
495
+ flex: 0 0 auto;
153
496
  display: flex;
154
497
  justify-content: space-between;
155
- gap: 10px;
498
+ gap: 12px;
156
499
  align-items: center;
157
- margin-bottom: 14px;
500
+ min-height: 52px;
501
+ padding: 0 24px;
502
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 74%, transparent);
503
+ background: var(--chrome);
504
+ backdrop-filter: blur(12px) saturate(1.6);
505
+ -webkit-backdrop-filter: blur(12px) saturate(1.6);
158
506
  }
159
507
  .topbar h2 {
508
+ display: none;
160
509
  margin: 0;
161
- font-size: 22px;
162
510
  }
163
511
  .topbar p {
164
- margin: 4px 0 0;
512
+ display: none;
513
+ margin: 0;
165
514
  color: var(--muted);
166
- font-size: 13px;
515
+ font-size: 12px;
516
+ white-space: nowrap;
517
+ overflow: hidden;
518
+ text-overflow: ellipsis;
519
+ }
520
+ .dashboard-header__breadcrumb {
521
+ display: flex;
522
+ align-items: center;
523
+ gap: 8px;
524
+ min-width: 0;
525
+ overflow: hidden;
526
+ font-size: 12px;
527
+ margin-bottom: 2px;
528
+ letter-spacing: 0.01em;
529
+ }
530
+ .dashboard-header__breadcrumb-link,
531
+ .dashboard-header__breadcrumb-sep {
532
+ color: var(--muted);
533
+ }
534
+ .dashboard-header__breadcrumb-current {
535
+ color: var(--text-strong);
536
+ font-weight: 650;
537
+ white-space: nowrap;
538
+ overflow: hidden;
539
+ text-overflow: ellipsis;
167
540
  }
168
541
 
542
+ .topnav-shell__content {
543
+ min-width: 0;
544
+ flex: 1;
545
+ overflow: hidden;
546
+ }
547
+ .main-scroll {
548
+ flex: 1 1 auto;
549
+ min-height: 0;
550
+ overflow: auto;
551
+ padding: 12px 18px 18px;
552
+ }
169
553
  .status-row {
170
554
  display: flex;
171
555
  gap: 8px;
172
- flex-wrap: wrap;
556
+ flex-wrap: nowrap;
173
557
  align-items: center;
558
+ flex-shrink: 0;
559
+ min-width: max-content;
174
560
  }
175
- .theme-switch {
561
+ .topbar-actions {
176
562
  display: inline-flex;
177
- gap: 4px;
178
- padding: 3px;
563
+ align-items: center;
564
+ gap: 8px;
565
+ }
566
+ .icon-btn {
567
+ width: 30px;
568
+ height: 30px;
569
+ display: inline-flex;
570
+ align-items: center;
571
+ justify-content: center;
179
572
  border-radius: 999px;
180
- border: 1px solid var(--line);
573
+ border: 1px solid color-mix(in srgb, var(--border) 86%, transparent);
181
574
  background: var(--bg-elevated);
575
+ color: var(--muted);
576
+ padding: 0;
577
+ }
578
+ .icon-btn:hover {
579
+ color: var(--text-strong);
580
+ border-color: color-mix(in srgb, var(--accent) 28%, transparent);
581
+ background: color-mix(in srgb, var(--accent-subtle) 65%, var(--bg-elevated));
582
+ }
583
+ .icon-btn.active {
584
+ color: var(--text-strong);
585
+ border-color: color-mix(in srgb, var(--accent) 34%, transparent);
586
+ background: var(--accent-subtle);
182
587
  }
183
- .theme-switch button {
588
+ .icon-btn svg {
589
+ width: 15px;
590
+ height: 15px;
591
+ stroke: currentColor;
592
+ }
593
+ .topbar-theme-mode {
594
+ display: inline-flex;
595
+ align-items: center;
596
+ gap: 2px;
597
+ padding: 3px;
598
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
599
+ border-radius: 999px;
600
+ background: color-mix(in srgb, var(--bg-elevated) 78%, transparent);
601
+ }
602
+ .topbar-theme-mode__btn {
603
+ width: 30px;
604
+ height: 30px;
605
+ display: inline-flex;
606
+ align-items: center;
607
+ justify-content: center;
608
+ padding: 0;
184
609
  border: 1px solid transparent;
610
+ border-radius: 999px;
185
611
  background: transparent;
186
612
  color: var(--muted);
187
- border-radius: 999px;
188
- padding: 5px 10px;
189
- font-size: 12px;
190
- font-weight: 600;
613
+ cursor: pointer;
614
+ transition:
615
+ color var(--duration-fast) ease,
616
+ background var(--duration-fast) ease,
617
+ border-color var(--duration-fast) ease;
191
618
  }
192
- .theme-switch button.active {
193
- color: var(--text-strong);
194
- background: var(--brand-soft);
195
- border-color: color-mix(in srgb, var(--brand) 24%, transparent);
619
+ .topbar-theme-mode__btn:hover {
620
+ color: var(--text);
621
+ background: var(--bg-hover);
622
+ }
623
+ .topbar-theme-mode__btn:focus-visible {
624
+ outline: none;
625
+ box-shadow: var(--focus-ring);
626
+ }
627
+ .topbar-theme-mode__btn--active {
628
+ color: var(--accent);
629
+ background: var(--accent-subtle);
630
+ border-color: color-mix(in srgb, var(--accent) 25%, transparent);
631
+ }
632
+ .topbar-theme-mode__btn svg {
633
+ width: 14px;
634
+ height: 14px;
635
+ stroke: currentColor;
636
+ fill: none;
637
+ stroke-width: 1.75px;
638
+ stroke-linecap: round;
639
+ stroke-linejoin: round;
640
+ }
641
+ .theme-icon {
642
+ width: 14px;
643
+ height: 14px;
644
+ display: inline-flex;
645
+ align-items: center;
646
+ justify-content: center;
647
+ }
648
+ .theme-icon svg {
649
+ width: 14px;
650
+ height: 14px;
651
+ stroke: currentColor;
652
+ fill: none;
653
+ stroke-width: 1.75px;
654
+ stroke-linecap: round;
655
+ stroke-linejoin: round;
196
656
  }
197
657
  .pill {
198
658
  border-radius: 999px;
199
- border: 1px solid var(--line);
200
- background: var(--bg-elevated);
659
+ border: 1px solid color-mix(in srgb, var(--border) 86%, transparent);
660
+ background: color-mix(in srgb, var(--bg-elevated) 96%, transparent);
201
661
  color: var(--muted);
202
- padding: 7px 11px;
662
+ padding: 6px 10px;
203
663
  font-size: 12px;
664
+ font-weight: 500;
665
+ min-height: 32px;
666
+ display: inline-flex;
667
+ align-items: center;
668
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent);
204
669
  }
205
670
  .pill.ok { color: var(--ok); border-color: rgba(34, 197, 94, 0.45); background: rgba(34, 197, 94, 0.08); }
206
671
  .pill.warn { color: var(--warn); border-color: rgba(245, 158, 11, 0.45); background: rgba(245, 158, 11, 0.08); }
@@ -208,8 +673,8 @@
208
673
  .notice {
209
674
  display: none;
210
675
  margin-bottom: 12px;
211
- border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
212
- background: var(--brand-soft);
676
+ border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
677
+ background: var(--accent-subtle);
213
678
  border-radius: 10px;
214
679
  padding: 10px 12px;
215
680
  font-size: 13px;
@@ -219,13 +684,16 @@
219
684
  position: sticky;
220
685
  top: 0;
221
686
  z-index: 9;
222
- margin-bottom: 12px;
223
- border: 1px solid var(--line);
224
- border-radius: 10px;
225
- background: color-mix(in srgb, var(--bg-elevated) 92%, transparent);
226
- padding: 9px 12px;
227
- font-size: 13px;
687
+ margin-bottom: 8px;
688
+ border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
689
+ border-radius: 14px;
690
+ background:
691
+ linear-gradient(180deg, color-mix(in srgb, var(--card-soft) 98%, transparent), color-mix(in srgb, var(--bg-elevated) 94%, transparent));
692
+ padding: 8px 12px;
693
+ font-size: 12px;
694
+ line-height: 1.45;
228
695
  color: var(--text);
696
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent);
229
697
  }
230
698
  .integration-strip.ok {
231
699
  border-color: rgba(34, 197, 94, 0.35);
@@ -233,40 +701,157 @@
233
701
  .integration-strip.warn {
234
702
  border-color: rgba(245, 158, 11, 0.35);
235
703
  }
704
+ .page-hero {
705
+ display: grid;
706
+ grid-template-columns: 1.2fr .8fr;
707
+ gap: 10px;
708
+ margin-bottom: 8px;
709
+ }
710
+ .hero-copy {
711
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
712
+ border-radius: 16px;
713
+ background:
714
+ linear-gradient(180deg, color-mix(in srgb, var(--card-strong) 98%, transparent), color-mix(in srgb, var(--card-soft) 96%, transparent));
715
+ padding: 11px 14px;
716
+ box-shadow:
717
+ inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
718
+ 0 10px 24px color-mix(in srgb, black 9%, transparent);
719
+ }
720
+ .hero-copy h3 {
721
+ margin: 0;
722
+ font-size: 17px;
723
+ color: var(--text-strong);
724
+ letter-spacing: -0.02em;
725
+ }
726
+ .hero-copy p {
727
+ margin: 5px 0 0;
728
+ color: var(--muted);
729
+ font-size: 12px;
730
+ line-height: 1.42;
731
+ }
732
+ .hero-meta {
733
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
734
+ border-radius: 16px;
735
+ background:
736
+ linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 97%, transparent), color-mix(in srgb, var(--panel) 98%, transparent));
737
+ padding: 11px;
738
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent);
739
+ }
740
+ .hero-meta-grid {
741
+ display: grid;
742
+ grid-template-columns: repeat(2, minmax(0, 1fr));
743
+ gap: 6px;
744
+ }
745
+ .hero-meta-item {
746
+ border: 1px solid color-mix(in srgb, var(--border) 76%, transparent);
747
+ border-radius: 12px;
748
+ background: color-mix(in srgb, var(--panel) 90%, transparent);
749
+ padding: 7px 9px;
750
+ }
236
751
 
237
752
  .view { display: none; }
238
- .view.active { display: block; }
753
+ .view.active {
754
+ display: grid;
755
+ gap: 8px;
756
+ }
757
+
758
+ .section-header {
759
+ display: flex;
760
+ align-items: center;
761
+ justify-content: space-between;
762
+ gap: 12px;
763
+ min-width: 0;
764
+ }
765
+ .section-header__copy {
766
+ min-width: 0;
767
+ }
768
+ .section-header__eyebrow {
769
+ margin: 0 0 3px;
770
+ color: var(--muted);
771
+ font-size: 11px;
772
+ font-weight: 700;
773
+ letter-spacing: 0.08em;
774
+ text-transform: uppercase;
775
+ }
776
+ .section-header__title {
777
+ margin: 0;
778
+ color: var(--text-strong);
779
+ font-size: 16px;
780
+ letter-spacing: -0.02em;
781
+ }
782
+ .section-header__body {
783
+ margin: 4px 0 0;
784
+ color: var(--muted);
785
+ font-size: 12px;
786
+ line-height: 1.45;
787
+ }
788
+ .section-surface {
789
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
790
+ border-radius: 16px;
791
+ background:
792
+ linear-gradient(180deg, color-mix(in srgb, var(--card-strong) 98%, transparent), color-mix(in srgb, var(--card-soft) 96%, transparent));
793
+ padding: 12px;
794
+ box-shadow:
795
+ inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
796
+ 0 14px 30px color-mix(in srgb, black 10%, transparent);
797
+ }
798
+ .section-surface.compact {
799
+ padding: 10px 12px;
800
+ }
239
801
 
240
802
  .grid {
241
803
  display: grid;
242
- gap: 10px;
804
+ gap: 8px;
243
805
  grid-template-columns: repeat(4, minmax(0, 1fr));
244
806
  }
807
+ #networkCards,
808
+ #socialPrimaryCards,
809
+ #socialIntegrationCards,
810
+ #socialAdvancedCards {
811
+ grid-template-columns: repeat(3, minmax(0, 1fr));
812
+ }
245
813
  .card {
246
- border: 1px solid var(--line);
247
- border-radius: 14px;
248
- background: var(--card);
814
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
815
+ border-radius: 16px;
816
+ background:
817
+ linear-gradient(180deg, color-mix(in srgb, var(--card-strong) 98%, transparent), color-mix(in srgb, var(--card-soft) 96%, transparent));
249
818
  padding: 12px;
250
- box-shadow: 0 1px 2px rgba(0,0,0,0.25);
819
+ box-shadow:
820
+ inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
821
+ 0 14px 30px color-mix(in srgb, black 10%, transparent);
822
+ transition: border-color .16s ease, transform .16s ease, box-shadow .16s ease;
823
+ }
824
+ .card:hover {
825
+ border-color: color-mix(in srgb, var(--accent) 14%, var(--border));
826
+ box-shadow:
827
+ inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
828
+ 0 18px 36px color-mix(in srgb, black 14%, transparent);
251
829
  }
252
830
  .label { font-size: 12px; color: var(--muted); }
253
- .value { margin-top: 4px; font-size: 24px; font-weight: 800; }
831
+ .value { margin-top: 4px; font-size: 22px; font-weight: 800; letter-spacing: -0.03em; }
254
832
 
255
833
  .split {
256
- margin-top: 10px;
834
+ margin-top: 8px;
257
835
  display: grid;
258
- gap: 10px;
836
+ gap: 8px;
259
837
  grid-template-columns: 1.2fr 1fr;
260
838
  }
261
- .title-sm { margin: 0 0 10px; font-size: 18px; }
839
+ .title-sm { margin: 0 0 10px; font-size: 16px; letter-spacing: -0.02em; }
262
840
  .mono { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
263
841
 
264
842
  .table {
265
843
  width: 100%;
266
844
  border-collapse: collapse;
267
845
  }
846
+ .table thead th {
847
+ color: var(--muted);
848
+ font-size: 11px;
849
+ font-weight: 700;
850
+ text-transform: uppercase;
851
+ letter-spacing: 0.07em;
852
+ }
268
853
  .table th, .table td {
269
- border-bottom: 1px solid var(--line);
854
+ border-bottom: 1px solid var(--border);
270
855
  text-align: left;
271
856
  padding: 8px 6px;
272
857
  font-size: 13px;
@@ -281,22 +866,33 @@
281
866
  input, textarea {
282
867
  margin-top: 5px;
283
868
  width: 100%;
284
- border-radius: 10px;
285
- border: 1px solid var(--line);
286
- background: var(--bg-elevated);
287
- color: #eaf0ff;
869
+ border-radius: 12px;
870
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
871
+ background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 98%, transparent), color-mix(in srgb, var(--panel) 96%, transparent));
872
+ color: var(--text);
288
873
  font: inherit;
289
874
  padding: 10px;
875
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 3%, transparent);
290
876
  }
291
877
  select {
292
878
  margin-top: 5px;
293
879
  width: 100%;
294
- border-radius: 10px;
295
- border: 1px solid var(--line);
296
- background: var(--bg-elevated);
880
+ border-radius: 12px;
881
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
882
+ background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 98%, transparent), color-mix(in srgb, var(--panel) 96%, transparent));
297
883
  color: var(--text);
298
884
  font: inherit;
299
885
  padding: 10px;
886
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 3%, transparent);
887
+ }
888
+ input:focus,
889
+ textarea:focus,
890
+ select:focus {
891
+ outline: none;
892
+ border-color: color-mix(in srgb, var(--accent) 34%, transparent);
893
+ box-shadow:
894
+ inset 0 1px 0 color-mix(in srgb, white 3%, transparent),
895
+ 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
300
896
  }
301
897
  textarea { min-height: 90px; resize: vertical; }
302
898
 
@@ -305,28 +901,87 @@
305
901
  gap: 8px;
306
902
  flex-wrap: wrap;
307
903
  }
904
+ .actions button,
905
+ .action-bar button,
906
+ .toolbar button {
907
+ min-height: 38px;
908
+ }
909
+ .action-bar {
910
+ display: flex;
911
+ gap: 8px;
912
+ flex-wrap: wrap;
913
+ margin-bottom: 8px;
914
+ padding: 2px 0 0;
915
+ }
916
+ .summary-list {
917
+ display: grid;
918
+ gap: 8px;
919
+ }
920
+ .summary-item {
921
+ display: grid;
922
+ gap: 3px;
923
+ padding: 10px;
924
+ border-radius: 12px;
925
+ border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
926
+ background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 98%, transparent), color-mix(in srgb, var(--panel) 94%, transparent));
927
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent);
928
+ }
929
+ .empty-state {
930
+ border: 1px dashed color-mix(in srgb, var(--border-strong) 88%, transparent);
931
+ border-radius: 14px;
932
+ padding: 18px;
933
+ color: var(--muted);
934
+ background: linear-gradient(180deg, color-mix(in srgb, var(--panel) 94%, transparent), color-mix(in srgb, var(--bg-elevated) 88%, transparent));
935
+ line-height: 1.55;
936
+ }
308
937
  button {
309
938
  border: 1px solid transparent;
310
- border-radius: 10px;
311
- background: var(--brand);
939
+ border-radius: 12px;
940
+ background: var(--accent);
312
941
  color: #ffffff;
313
942
  font-weight: 700;
314
943
  padding: 9px 13px;
315
944
  cursor: pointer;
945
+ box-shadow: 0 10px 20px color-mix(in srgb, var(--accent) 16%, transparent);
946
+ }
947
+ button:hover {
948
+ transform: translateY(-1px);
949
+ filter: saturate(1.04);
950
+ }
951
+ .nav button,
952
+ .nav-collapse-toggle,
953
+ .icon-btn,
954
+ .topbar-theme-mode__btn {
955
+ box-shadow: none;
956
+ filter: none;
957
+ }
958
+ .nav button,
959
+ .nav-collapse-toggle,
960
+ .icon-btn {
961
+ background: transparent;
962
+ color: var(--muted);
963
+ }
964
+ .nav button:hover,
965
+ .nav-collapse-toggle:hover,
966
+ .icon-btn:hover,
967
+ .topbar-theme-mode__btn:hover {
968
+ filter: none;
316
969
  }
317
970
  button.secondary {
318
- border-color: var(--line-strong);
319
- background: var(--bg-elevated);
971
+ border-color: var(--border-strong);
972
+ background: color-mix(in srgb, var(--bg-elevated) 96%, transparent);
320
973
  color: var(--text);
974
+ box-shadow: none;
321
975
  }
322
976
 
323
977
  .feedback {
324
- border: 1px solid var(--line);
325
- border-radius: 10px;
326
- background: var(--bg-elevated);
327
- padding: 9px;
978
+ border: 1px solid color-mix(in srgb, var(--border) 84%, transparent);
979
+ border-radius: 12px;
980
+ background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 98%, transparent), color-mix(in srgb, var(--panel) 96%, transparent));
981
+ padding: 10px 11px;
328
982
  font-size: 13px;
329
983
  color: var(--muted);
984
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent);
330
985
  }
331
986
  .profile-layout {
332
987
  display: grid;
@@ -334,10 +989,11 @@
334
989
  grid-template-columns: 1.1fr .9fr;
335
990
  }
336
991
  .profile-meta {
337
- border: 1px solid var(--line);
338
- border-radius: 10px;
992
+ border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
993
+ border-radius: 12px;
339
994
  padding: 10px;
340
- background: color-mix(in srgb, var(--card) 86%, transparent);
995
+ background: linear-gradient(180deg, color-mix(in srgb, var(--card-soft) 98%, transparent), color-mix(in srgb, var(--bg-elevated) 90%, transparent));
996
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent);
341
997
  }
342
998
  .profile-meta h4 {
343
999
  margin: 0 0 8px;
@@ -362,9 +1018,9 @@
362
1018
  flex-wrap: wrap;
363
1019
  }
364
1020
  .tag-chip {
365
- border: 1px solid var(--line-strong);
1021
+ border: 1px solid var(--border-strong);
366
1022
  border-radius: 999px;
367
- background: var(--chip);
1023
+ background: var(--bg-hover);
368
1024
  color: var(--text);
369
1025
  padding: 3px 9px;
370
1026
  font-size: 12px;
@@ -377,14 +1033,15 @@
377
1033
  margin-top: 4px;
378
1034
  color: var(--muted);
379
1035
  font-size: 12px;
1036
+ line-height: 1.45;
380
1037
  }
381
1038
  .field-error {
382
1039
  margin-top: 4px;
383
- color: var(--err);
1040
+ color: var(--danger);
384
1041
  font-size: 12px;
385
1042
  }
386
1043
  .input-invalid {
387
- border-color: color-mix(in srgb, var(--err) 55%, transparent);
1044
+ border-color: color-mix(in srgb, var(--danger) 55%, transparent);
388
1045
  }
389
1046
  .save-busy {
390
1047
  opacity: 0.7;
@@ -394,46 +1051,71 @@
394
1051
  .logs {
395
1052
  max-height: 420px;
396
1053
  overflow: auto;
397
- border: 1px solid var(--line);
398
- border-radius: 12px;
399
- background: var(--panel);
1054
+ border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
1055
+ border-radius: 14px;
1056
+ background: linear-gradient(180deg, color-mix(in srgb, var(--panel) 96%, transparent), color-mix(in srgb, var(--bg-elevated) 90%, transparent));
400
1057
  padding: 10px;
1058
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 3%, transparent);
401
1059
  }
402
1060
  .log-item {
403
- border-bottom: 1px dashed var(--line);
1061
+ border-bottom: 1px dashed var(--border);
404
1062
  padding: 7px 0;
405
1063
  }
406
1064
  .log-item:last-child { border-bottom: 0; }
407
1065
  .log-info { color: #8dc6ff; }
408
1066
  .log-warn { color: var(--warn); }
409
- .log-error { color: var(--err); }
1067
+ .log-error { color: var(--danger); }
410
1068
  .toolbar {
411
1069
  display: flex;
412
1070
  gap: 10px;
413
1071
  flex-wrap: wrap;
414
1072
  align-items: end;
415
1073
  margin-bottom: 10px;
1074
+ padding-bottom: 2px;
416
1075
  }
417
1076
  .toolbar .field {
418
1077
  min-width: 180px;
419
1078
  }
420
1079
  .mono-block {
421
- border: 1px solid var(--line);
422
- border-radius: 10px;
423
- background: color-mix(in srgb, var(--bg-elevated) 85%, transparent);
1080
+ border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
1081
+ border-radius: 12px;
1082
+ background: linear-gradient(180deg, color-mix(in srgb, var(--bg-elevated) 96%, transparent), color-mix(in srgb, var(--panel) 92%, transparent));
424
1083
  padding: 10px;
425
1084
  max-height: 240px;
426
1085
  overflow: auto;
427
1086
  white-space: pre-wrap;
428
1087
  word-break: break-word;
1088
+ box-shadow: inset 0 1px 0 color-mix(in srgb, white 3%, transparent);
1089
+ }
1090
+ .advanced-panel {
1091
+ margin-top: 8px;
1092
+ }
1093
+ .advanced-panel.card > summary,
1094
+ details.card > summary {
1095
+ display: flex;
1096
+ align-items: center;
1097
+ justify-content: space-between;
429
1098
  }
1099
+ .advanced-panel summary {
1100
+ cursor: pointer;
1101
+ list-style: none;
1102
+ user-select: none;
1103
+ }
1104
+ .advanced-panel summary::-webkit-details-marker { display: none; }
1105
+ .advanced-panel summary::after {
1106
+ content: attr(data-i18n-closed-label);
1107
+ float: right;
1108
+ color: var(--muted);
1109
+ font-size: 12px;
1110
+ }
1111
+ .advanced-panel[open] summary::after { content: attr(data-i18n-open-label); }
430
1112
 
431
1113
  .toast {
432
1114
  position: fixed;
433
1115
  right: 16px;
434
1116
  bottom: 16px;
435
1117
  background: var(--bg-elevated);
436
- border: 1px solid color-mix(in srgb, var(--brand) 32%, transparent);
1118
+ border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
437
1119
  color: var(--text);
438
1120
  border-radius: 10px;
439
1121
  padding: 10px 12px;
@@ -449,69 +1131,220 @@
449
1131
  transform: translateY(0);
450
1132
  }
451
1133
 
1134
+ #view-overview .action-bar {
1135
+ margin-bottom: 0;
1136
+ }
1137
+ #view-overview .grid {
1138
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1139
+ }
1140
+ #view-overview .card .field-hint {
1141
+ font-size: 12px;
1142
+ }
1143
+ #view-overview #snapshot {
1144
+ line-height: 1.55;
1145
+ }
1146
+
452
1147
  @media (max-width: 980px) {
453
- .app { grid-template-columns: 1fr; }
454
- .sidebar { border-right: 0; border-bottom: 1px solid var(--line); }
455
- .nav { grid-template-columns: repeat(3, minmax(0,1fr)); }
1148
+ html, body {
1149
+ height: auto;
1150
+ overflow: auto;
1151
+ }
1152
+ .app {
1153
+ grid-template-columns: 1fr;
1154
+ height: auto;
1155
+ overflow: visible;
1156
+ }
1157
+ .sidebar {
1158
+ border-right: 0;
1159
+ border-bottom: 1px solid var(--border);
1160
+ height: auto;
1161
+ overflow: visible;
1162
+ }
1163
+ .sidebar-shell {
1164
+ display: block;
1165
+ }
1166
+ .nav { grid-template-columns: repeat(2, minmax(0,1fr)); }
456
1167
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
457
- .split, .row, .profile-layout { grid-template-columns: 1fr; }
1168
+ .split, .row, .profile-layout, .page-hero { grid-template-columns: 1fr; }
1169
+ .main {
1170
+ height: auto;
1171
+ overflow: visible;
1172
+ padding-bottom: 18px;
1173
+ }
1174
+ .main-scroll {
1175
+ overflow: visible;
1176
+ padding-right: 0;
1177
+ }
458
1178
  }
459
1179
  </style>
460
1180
  </head>
461
1181
  <body>
462
- <div class="app">
1182
+ <div class="app" id="appShell">
463
1183
  <aside class="sidebar">
464
- <div class="brand">
465
- <img id="brandLogo" class="brand-logo" src="/assets/silicaclaw-logo.png" alt="SilicaClaw logo" />
466
- <div id="brandFallback" class="brand-badge hidden">SC</div>
467
- <div>
468
- <h1>SilicaClaw</h1>
469
- <p>Control UI</p>
1184
+ <div class="sidebar-shell">
1185
+ <div class="sidebar-shell__header">
1186
+ <div class="brand">
1187
+ <img id="brandLogo" class="brand-logo" src="/assets/silicaclaw-logo.png" alt="SilicaClaw logo" />
1188
+ <div id="brandFallback" class="brand-badge hidden">SC</div>
1189
+ <div>
1190
+ <h1>SilicaClaw</h1>
1191
+ <p>Control UI</p>
1192
+ </div>
1193
+ </div>
1194
+ <button class="nav-collapse-toggle" id="sidebarToggleBtn" type="button" title="Collapse sidebar" aria-label="Collapse sidebar">
1195
+ <span class="nav-collapse-toggle__icon" aria-hidden="true">
1196
+ <svg viewBox="0 0 24 24">
1197
+ <rect x="3" y="3" width="18" height="18" rx="2"></rect>
1198
+ <path d="M9 3v18"></path>
1199
+ <path d="M16 10l-3 2 3 2"></path>
1200
+ </svg>
1201
+ </span>
1202
+ </button>
1203
+ </div>
1204
+ <div class="sidebar-shell__body">
1205
+ <div class="sidebar-nav__label">Control</div>
1206
+ <nav class="nav">
1207
+ <button class="tab active" data-tab="overview">
1208
+ <span class="tab-icon" aria-hidden="true">
1209
+ <svg viewBox="0 0 24 24">
1210
+ <line x1="12" x2="12" y1="20" y2="10"></line>
1211
+ <line x1="18" x2="18" y1="20" y2="4"></line>
1212
+ <line x1="6" x2="6" y1="20" y2="16"></line>
1213
+ </svg>
1214
+ </span>
1215
+ <span class="tab-labels"><span class="tab-title">Overview</span><span class="tab-copy">Agent summary and discovered peers</span></span>
1216
+ </button>
1217
+ <button class="tab" data-tab="profile">
1218
+ <span class="tab-icon" aria-hidden="true">
1219
+ <svg viewBox="0 0 24 24">
1220
+ <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
1221
+ <polyline points="14 2 14 8 20 8"></polyline>
1222
+ <line x1="16" x2="8" y1="13" y2="13"></line>
1223
+ <line x1="16" x2="8" y1="17" y2="17"></line>
1224
+ <line x1="10" x2="8" y1="9" y2="9"></line>
1225
+ </svg>
1226
+ </span>
1227
+ <span class="tab-labels"><span class="tab-title">Profile</span><span class="tab-copy">Public identity and saved profile</span></span>
1228
+ </button>
1229
+ <button class="tab" data-tab="network">
1230
+ <span class="tab-icon" aria-hidden="true">
1231
+ <svg viewBox="0 0 24 24">
1232
+ <circle cx="12" cy="12" r="2"></circle>
1233
+ <path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
1234
+ </svg>
1235
+ </span>
1236
+ <span class="tab-labels"><span class="tab-title">Network</span><span class="tab-copy">Relay health, broadcast, diagnostics</span></span>
1237
+ </button>
1238
+ <button class="tab" data-tab="social">
1239
+ <span class="tab-icon" aria-hidden="true">
1240
+ <svg viewBox="0 0 24 24">
1241
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1242
+ </svg>
1243
+ </span>
1244
+ <span class="tab-labels"><span class="tab-title">Social</span><span class="tab-copy">social.md config and runtime state</span></span>
1245
+ </button>
1246
+ </nav>
1247
+ </div>
1248
+ <div class="sidebar-shell__footer">
1249
+ <div class="sidebar-version" title="Current version">
1250
+ <span class="sidebar-version__text" id="brandVersion">-</span>
1251
+ </div>
470
1252
  </div>
471
1253
  </div>
472
-
473
- <nav class="nav">
474
- <button class="tab active" data-tab="overview">Overview</button>
475
- <button class="tab" data-tab="profile">Profile</button>
476
- <button class="tab" data-tab="network">Network</button>
477
- <button class="tab" data-tab="peers">Peers</button>
478
- <button class="tab" data-tab="discovery">Discovery Events</button>
479
- <button class="tab" data-tab="social">Social Config</button>
480
- <button class="tab" data-tab="logs">Logs</button>
481
- </nav>
482
-
483
- <div class="sidebar-foot" id="sideMeta">adapter: -<br/>namespace: -</div>
484
1254
  </aside>
485
1255
 
486
1256
  <main class="main">
487
1257
  <div class="topbar">
488
- <div>
489
- <h2>Local Console</h2>
490
- <p>OpenClaw-inspired local-first agent control surface</p>
1258
+ <div class="topnav-shell__content">
1259
+ <div class="dashboard-header__breadcrumb">
1260
+ <span class="dashboard-header__breadcrumb-link">SilicaClaw</span>
1261
+ <span class="dashboard-header__breadcrumb-sep">/</span>
1262
+ <span class="dashboard-header__breadcrumb-current" id="pageBreadcrumb">Overview</span>
1263
+ </div>
1264
+ <h2 id="topbarTitle">Local Console</h2>
1265
+ <p id="topbarSubtitle">OpenClaw-inspired local-first agent control surface</p>
491
1266
  </div>
492
1267
  <div class="status-row">
493
1268
  <div class="pill" id="pillAdapter">adapter: -</div>
494
1269
  <div class="pill" id="pillBroadcast">broadcast: -</div>
495
- <div class="theme-switch">
496
- <button id="themeDarkBtn" type="button">Dark</button>
497
- <button id="themeLightBtn" type="button">Light</button>
1270
+ <div class="topbar-actions">
1271
+ <button class="icon-btn" id="focusModeBtn" type="button" title="Toggle focus mode" aria-label="Toggle focus mode">
1272
+ <svg viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
1273
+ <path d="M4 7V4h3"></path>
1274
+ <path d="M20 7V4h-3"></path>
1275
+ <path d="M4 17v3h3"></path>
1276
+ <path d="M20 17v3h-3"></path>
1277
+ <circle cx="12" cy="12" r="3"></circle>
1278
+ </svg>
1279
+ </button>
1280
+ <div class="topbar-theme-mode" id="themeModeGroup" role="group" aria-label="Color mode">
1281
+ <button class="topbar-theme-mode__btn" data-theme-choice="dark" type="button" title="Dark" aria-label="Color mode: Dark">
1282
+ <span class="theme-icon" aria-hidden="true">
1283
+ <svg viewBox="0 0 24 24">
1284
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9z"></path>
1285
+ </svg>
1286
+ </span>
1287
+ </button>
1288
+ <button class="topbar-theme-mode__btn" data-theme-choice="light" type="button" title="Light" aria-label="Color mode: Light">
1289
+ <span class="theme-icon" aria-hidden="true">
1290
+ <svg viewBox="0 0 24 24">
1291
+ <circle cx="12" cy="12" r="4"></circle>
1292
+ <path d="M12 2v2"></path>
1293
+ <path d="M12 20v2"></path>
1294
+ <path d="M4.93 4.93l1.41 1.41"></path>
1295
+ <path d="M17.66 17.66l1.41 1.41"></path>
1296
+ <path d="M2 12h2"></path>
1297
+ <path d="M20 12h2"></path>
1298
+ <path d="M4.93 19.07l1.41-1.41"></path>
1299
+ <path d="M17.66 6.34l1.41-1.41"></path>
1300
+ </svg>
1301
+ </span>
1302
+ </button>
1303
+ <button class="topbar-theme-mode__btn" data-theme-choice="system" type="button" title="System" aria-label="Color mode: System">
1304
+ <span class="theme-icon" aria-hidden="true">
1305
+ <svg viewBox="0 0 24 24">
1306
+ <rect x="3" y="4" width="18" height="12" rx="2"></rect>
1307
+ <path d="M8 20h8"></path>
1308
+ <path d="M12 16v4"></path>
1309
+ </svg>
1310
+ </span>
1311
+ </button>
1312
+ </div>
498
1313
  </div>
499
1314
  </div>
500
1315
  </div>
501
- <div class="integration-strip warn" id="integrationStatusBar">
502
- Connected to SilicaClaw: - · Network mode: - · Public discovery: -
503
- </div>
1316
+ <div class="main-scroll">
1317
+ <div class="integration-strip warn" id="integrationStatusBar">
1318
+ Connected to SilicaClaw: - · Network mode: - · Public discovery: -
1319
+ </div>
1320
+ <section class="page-hero">
1321
+ <div class="hero-copy">
1322
+ <h3 id="pageHeroTitle">Overview</h3>
1323
+ <p id="pageHeroBody">See whether this node is online and who else is visible.</p>
1324
+ </div>
1325
+ <div class="hero-meta">
1326
+ <div class="hero-meta-grid">
1327
+ <div class="hero-meta-item"><div class="label">Mode</div><div class="mono" id="heroMode">-</div></div>
1328
+ <div class="hero-meta-item"><div class="label">Adapter</div><div class="mono" id="heroAdapter">-</div></div>
1329
+ <div class="hero-meta-item"><div class="label">Relay</div><div class="mono" id="heroRelay">-</div></div>
1330
+ <div class="hero-meta-item"><div class="label">Room</div><div class="mono" id="heroRoom">-</div></div>
1331
+ </div>
1332
+ </div>
1333
+ </section>
504
1334
 
505
- <div class="notice" id="initNotice"></div>
506
- <div class="actions" id="onboardingActions" style="display:none; margin:8px 0 12px;">
507
- <button id="enablePublicDiscoveryBtn">Enable Public Discovery</button>
508
- <button class="secondary" id="disablePublicDiscoveryBtn">Disable Public Discovery</button>
509
- </div>
510
- <div class="field-hint" id="publicDiscoveryHint" style="margin-bottom:12px; display:none;">
511
- Public discovery only shares signed profile/presence records. It does not share private files and does not enable chat or remote control.
512
- </div>
1335
+ <div class="notice" id="initNotice"></div>
1336
+ <div class="field-hint" id="publicDiscoveryHint" style="margin-bottom:12px; display:none;">
1337
+ Use <strong>Profile Public Enabled</strong> as the single public visibility switch.
1338
+ </div>
513
1339
 
514
- <section id="view-overview" class="view active">
1340
+ <section id="view-overview" class="view active">
1341
+ <div class="section-surface compact">
1342
+ <div class="action-bar">
1343
+ <button id="overviewBroadcastNowBtn" type="button">Broadcast Now</button>
1344
+ <button class="secondary" id="overviewGoProfileBtn" type="button">Edit Profile</button>
1345
+ <button class="secondary" id="overviewGoNetworkBtn" type="button">Open Network</button>
1346
+ </div>
1347
+ </div>
515
1348
  <div class="grid" id="overviewCards"></div>
516
1349
  <div class="split">
517
1350
  <div class="card">
@@ -528,9 +1361,16 @@
528
1361
  <div class="mono" id="snapshot"></div>
529
1362
  </div>
530
1363
  </div>
531
- </section>
532
-
533
- <section id="view-profile" class="view">
1364
+ </section>
1365
+
1366
+ <section id="view-profile" class="view">
1367
+ <div class="section-header">
1368
+ <div class="section-header__copy">
1369
+ <div class="section-header__eyebrow">Profile</div>
1370
+ <h3 class="section-header__title">Public profile</h3>
1371
+ <p class="section-header__body">Edit the public card other nodes can see.</p>
1372
+ </div>
1373
+ </div>
534
1374
  <div class="profile-layout">
535
1375
  <div class="card stack">
536
1376
  <h3 class="title-sm">Public Profile Editor</h3>
@@ -561,6 +1401,7 @@
561
1401
  <div class="field-error" id="errTags"></div>
562
1402
  </div>
563
1403
  <label><input type="checkbox" name="public_enabled" /> Public Enabled</label>
1404
+ <div class="field-hint">This is the main public visibility switch used for discovery and relay broadcast.</div>
564
1405
  <div class="actions">
565
1406
  <button type="submit" id="saveProfileBtn">Save Profile</button>
566
1407
  <button type="button" class="secondary" id="refreshProfileBtn">Reload</button>
@@ -592,96 +1433,144 @@
592
1433
  </div>
593
1434
  </div>
594
1435
  </div>
595
- </section>
596
-
597
- <section id="view-network" class="view">
1436
+ </section>
1437
+
1438
+ <section id="view-network" class="view">
1439
+ <div class="section-header">
1440
+ <div class="section-header__copy">
1441
+ <div class="section-header__eyebrow">Network</div>
1442
+ <h3 class="section-header__title">Network</h3>
1443
+ <p class="section-header__body">Check relay status, peers, and broadcast health.</p>
1444
+ </div>
1445
+ </div>
598
1446
  <div class="grid" id="networkCards"></div>
599
1447
  <div class="split">
600
1448
  <div class="card">
601
- <h3 class="title-sm">Runtime Components</h3>
602
- <div class="mono" id="networkComponents"></div>
1449
+ <h3 class="title-sm">Connection Summary</h3>
1450
+ <div class="summary-list" id="networkSummaryList"></div>
603
1451
  </div>
604
1452
  <div class="card">
605
- <h3 class="title-sm">Actions</h3>
1453
+ <h3 class="title-sm">Quick Actions</h3>
1454
+ <div class="field-hint" style="margin-bottom:10px;">Use these first.</div>
606
1455
  <div class="actions">
607
1456
  <button id="startBroadcastBtn">Start Broadcast</button>
608
1457
  <button class="secondary" id="stopBroadcastBtn">Stop Broadcast</button>
609
1458
  <button class="secondary" id="broadcastNowBtn">Broadcast Now</button>
610
- <button class="secondary" id="refreshCacheBtn">Refresh Cache</button>
611
- <button class="secondary" id="clearCacheBtn">Clear Discovered Cache</button>
612
- <button class="secondary" id="quickGlobalPreviewBtn">Enable Cross-network Preview</button>
613
1459
  </div>
1460
+ <details class="advanced-panel">
1461
+ <summary class="title-sm">Advanced Actions</summary>
1462
+ <div class="actions" style="margin-top:10px;">
1463
+ <button class="secondary" id="quickGlobalPreviewBtn">Enable Cross-network Preview</button>
1464
+ </div>
1465
+ </details>
614
1466
  <div id="networkFeedback" class="feedback" style="margin-top:10px;">Ready.</div>
615
1467
  </div>
616
1468
  </div>
617
- <div class="split">
1469
+ <details class="advanced-panel card">
1470
+ <summary class="title-sm">Diagnostics</summary>
618
1471
  <div class="card" style="margin-top:10px;">
619
- <h3 class="title-sm">Config Snapshot</h3>
620
- <div class="mono mono-block" id="networkConfigSnapshot">-</div>
1472
+ <h3 class="title-sm">Runtime Components</h3>
1473
+ <div class="mono" id="networkComponents"></div>
621
1474
  </div>
1475
+ <div class="grid" id="peerCards" style="margin-top:10px;"></div>
622
1476
  <div class="card" style="margin-top:10px;">
623
- <h3 class="title-sm">Stats Snapshot</h3>
624
- <div class="mono mono-block" id="networkStatsSnapshot">-</div>
1477
+ <h3 class="title-sm">Peer Inventory</h3>
1478
+ <div id="peerTableWrap"></div>
625
1479
  </div>
626
- </div>
627
- </section>
628
-
629
- <section id="view-peers" class="view">
630
- <div class="grid" id="peerCards"></div>
631
- <div class="card" style="margin-top:10px;">
632
- <h3 class="title-sm">Peer Inventory</h3>
633
- <div id="peerTableWrap"></div>
634
- </div>
635
- <div class="card" style="margin-top:10px;">
636
- <h3 class="title-sm">Peer Discovery Stats</h3>
637
- <div class="mono mono-block" id="peerStatsWrap">-</div>
638
- </div>
639
- </section>
640
-
641
- <section id="view-discovery" class="view">
642
- <div class="grid" id="discoveryCards"></div>
643
- <div class="split">
644
1480
  <div class="card" style="margin-top:10px;">
645
- <h3 class="title-sm">Recent Discovery Events</h3>
646
- <div class="logs" id="discoveryEventList"></div>
1481
+ <h3 class="title-sm">Peer Discovery Stats</h3>
1482
+ <div class="mono mono-block" id="peerStatsWrap">-</div>
1483
+ </div>
1484
+ <div class="grid" id="discoveryCards" style="margin-top:10px;"></div>
1485
+ <div class="split">
1486
+ <div class="card" style="margin-top:10px;">
1487
+ <h3 class="title-sm">Recent Discovery Events</h3>
1488
+ <div class="logs" id="discoveryEventList"></div>
1489
+ </div>
1490
+ <div class="card" style="margin-top:10px;">
1491
+ <h3 class="title-sm">Discovery Snapshot</h3>
1492
+ <div class="mono mono-block" id="discoverySnapshot">-</div>
1493
+ </div>
647
1494
  </div>
648
1495
  <div class="card" style="margin-top:10px;">
649
- <h3 class="title-sm">Discovery Snapshot</h3>
650
- <div class="mono mono-block" id="discoverySnapshot">-</div>
1496
+ <h3 class="title-sm">Logs</h3>
1497
+ <div class="toolbar">
1498
+ <div class="field">
1499
+ <label for="logLevelFilter">Category</label>
1500
+ <select id="logLevelFilter">
1501
+ <option value="all">all</option>
1502
+ <option value="info">info</option>
1503
+ <option value="warn">warn</option>
1504
+ <option value="error">error</option>
1505
+ </select>
1506
+ </div>
1507
+ <div>
1508
+ <button type="button" class="secondary" id="refreshLogsBtn">Refresh Logs</button>
1509
+ </div>
1510
+ </div>
1511
+ <div class="logs" id="logList"></div>
1512
+ </div>
1513
+ <div class="split">
1514
+ <div class="card" style="margin-top:10px;">
1515
+ <h3 class="title-sm">Config Snapshot</h3>
1516
+ <div class="mono mono-block" id="networkConfigSnapshot">-</div>
1517
+ </div>
1518
+ <div class="card" style="margin-top:10px;">
1519
+ <h3 class="title-sm">Stats Snapshot</h3>
1520
+ <div class="mono mono-block" id="networkStatsSnapshot">-</div>
1521
+ </div>
1522
+ </div>
1523
+ </details>
1524
+ </section>
1525
+
1526
+ <section id="view-social" class="view">
1527
+ <div class="section-header">
1528
+ <div class="section-header__copy">
1529
+ <div class="section-header__eyebrow">Social</div>
1530
+ <h3 class="section-header__title">Social</h3>
1531
+ <p class="section-header__body">Switch mode and export a clean social.md template.</p>
651
1532
  </div>
652
1533
  </div>
653
- </section>
654
-
655
- <section id="view-social" class="view">
656
1534
  <div class="card">
657
1535
  <h3 class="title-sm">Integration Status</h3>
658
1536
  <div class="feedback" id="socialStatusLine">Checking integration status...</div>
659
1537
  <div class="field-hint" id="socialStatusSubline">-</div>
660
1538
  <div class="field-hint" id="socialStateHint">-</div>
661
1539
  </div>
662
- <div class="grid" id="socialPrimaryCards" style="margin-top:10px;"></div>
663
- <div class="grid" id="socialIntegrationCards"></div>
1540
+ <div class="split" style="margin-top:10px;">
1541
+ <div class="card">
1542
+ <h3 class="title-sm">What Is Active</h3>
1543
+ <div class="grid" id="socialPrimaryCards"></div>
1544
+ </div>
1545
+ <div class="card">
1546
+ <h3 class="title-sm">Identity Binding</h3>
1547
+ <div class="grid" id="socialIntegrationCards"></div>
1548
+ </div>
1549
+ </div>
664
1550
  <details class="card" style="margin-top:10px;">
665
1551
  <summary class="title-sm" style="cursor:pointer;">Advanced Network Details</summary>
666
1552
  <div class="grid" id="socialAdvancedCards" style="margin-top:10px;"></div>
667
1553
  <div class="mono mono-block" id="socialAdvancedWrap" style="margin-top:10px;">-</div>
668
1554
  </details>
669
- <div class="split">
670
- <div class="card" style="margin-top:10px;">
671
- <h3 class="title-sm">Source & Parsed Frontmatter</h3>
672
- <div class="mono mono-block" id="socialSourceWrap">-</div>
673
- <div style="height:10px;"></div>
674
- <div class="mono mono-block" id="socialRawWrap">-</div>
1555
+ <details class="advanced-panel card">
1556
+ <summary class="title-sm">Source / Runtime / Template</summary>
1557
+ <div class="split">
1558
+ <div class="card" style="margin-top:10px;">
1559
+ <h3 class="title-sm">Source & Parsed Frontmatter</h3>
1560
+ <div class="mono mono-block" id="socialSourceWrap">-</div>
1561
+ <div style="height:10px;"></div>
1562
+ <div class="mono mono-block" id="socialRawWrap">-</div>
1563
+ </div>
1564
+ <div class="card" style="margin-top:10px;">
1565
+ <h3 class="title-sm">Runtime Summary</h3>
1566
+ <div class="mono mono-block" id="socialRuntimeWrap">-</div>
1567
+ </div>
675
1568
  </div>
676
1569
  <div class="card" style="margin-top:10px;">
677
- <h3 class="title-sm">Runtime Summary</h3>
678
- <div class="mono mono-block" id="socialRuntimeWrap">-</div>
1570
+ <h3 class="title-sm">Export Template Preview</h3>
1571
+ <div class="mono mono-block" id="socialTemplateWrap">-</div>
679
1572
  </div>
680
- </div>
681
- <div class="card" style="margin-top:10px;">
682
- <h3 class="title-sm">Export Template Preview</h3>
683
- <div class="mono mono-block" id="socialTemplateWrap">-</div>
684
- </div>
1573
+ </details>
685
1574
  <div class="card" style="margin-top:10px;">
686
1575
  <h3 class="title-sm">Actions</h3>
687
1576
  <div class="toolbar">
@@ -694,46 +1583,732 @@
694
1583
  </select>
695
1584
  </div>
696
1585
  <div>
697
- <button class="secondary" id="socialModeApplyBtn">Apply Runtime Mode</button>
1586
+ <button id="socialModeApplyBtn">Apply Runtime Mode</button>
698
1587
  </div>
699
1588
  </div>
1589
+ <div class="field-hint" style="margin-top:10px;">Profile visibility is managed in the Profile page.</div>
700
1590
  <div class="actions">
701
- <button id="socialReloadBtn">Reload Config</button>
702
- <button class="secondary" id="socialGenerateBtn">Generate Default social.md</button>
703
1591
  <button class="secondary" id="socialExportBtn">Export social.md template</button>
704
1592
  <button class="secondary" id="socialCopyBtn">Copy Template</button>
705
1593
  <button class="secondary" id="socialDownloadBtn">Download Template</button>
706
1594
  </div>
707
1595
  <div id="socialFeedback" class="feedback" style="margin-top:10px;">Ready.</div>
708
1596
  </div>
709
- </section>
710
-
711
- <section id="view-logs" class="view">
712
- <div class="card">
713
- <h3 class="title-sm">Recent Logs</h3>
714
- <div class="toolbar">
715
- <div class="field">
716
- <label for="logLevelFilter">Category</label>
717
- <select id="logLevelFilter">
718
- <option value="all">all</option>
719
- <option value="info">info</option>
720
- <option value="warn">warn</option>
721
- <option value="error">error</option>
722
- </select>
723
- </div>
724
- <div>
725
- <button type="button" class="secondary" id="refreshLogsBtn">Refresh Logs</button>
726
- </div>
727
- </div>
728
- <div class="logs" id="logList"></div>
729
- </div>
730
- </section>
1597
+ </section>
1598
+ </div>
731
1599
  </main>
732
1600
  </div>
733
1601
 
734
1602
  <div id="toast" class="toast"></div>
735
1603
 
736
1604
  <script>
1605
+ const LOCALE_STORAGE_KEY = 'silicaclaw.i18n.locale';
1606
+ const DEFAULT_LOCALE = 'en';
1607
+ const SUPPORTED_LOCALES = ['en', 'zh-CN'];
1608
+ const TRANSLATIONS = {
1609
+ en: {
1610
+ meta: {
1611
+ title: 'SilicaClaw Control UI',
1612
+ description: 'SilicaClaw local-first agent control console.',
1613
+ socialTitle: 'SilicaClaw Local Console',
1614
+ socialDescription: 'Local-first control surface for SilicaClaw agents.',
1615
+ },
1616
+ common: {
1617
+ control: 'Control',
1618
+ version: 'Version',
1619
+ localConsole: 'Local Console',
1620
+ subtitle: 'OpenClaw-inspired local-first agent control surface',
1621
+ copied: 'Copied',
1622
+ done: 'Done',
1623
+ saving: 'Saving...',
1624
+ saved: 'Saved.',
1625
+ ready: 'Ready.',
1626
+ reload: 'Reload',
1627
+ yes: 'yes',
1628
+ no: 'no',
1629
+ on: 'enabled',
1630
+ off: 'disabled',
1631
+ unknownError: 'Unknown error',
1632
+ requestFailed: 'Request failed ({status})',
1633
+ },
1634
+ pageMeta: {
1635
+ overview: {
1636
+ title: 'Overview',
1637
+ body: 'See whether this node is online and who else is visible.',
1638
+ },
1639
+ profile: {
1640
+ title: 'Profile',
1641
+ body: 'Edit the public card other nodes can see.',
1642
+ },
1643
+ network: {
1644
+ title: 'Network',
1645
+ body: 'Check relay status, peers, and broadcast health.',
1646
+ },
1647
+ social: {
1648
+ title: 'Social',
1649
+ body: 'Switch mode and export a clean social.md template.',
1650
+ },
1651
+ },
1652
+ actions: {
1653
+ broadcastNow: 'Broadcast Now',
1654
+ editProfile: 'Edit Profile',
1655
+ openNetwork: 'Open Network',
1656
+ startBroadcast: 'Start Broadcast',
1657
+ stopBroadcast: 'Stop Broadcast',
1658
+ enablePreview: 'Enable Cross-network Preview',
1659
+ refreshLogs: 'Refresh Logs',
1660
+ exportTemplate: 'Export social.md template',
1661
+ copyTemplate: 'Copy Template',
1662
+ downloadTemplate: 'Download Template',
1663
+ applyRuntimeMode: 'Apply Runtime Mode',
1664
+ saveProfile: 'Save Profile',
1665
+ copyPublicProfilePreview: 'Copy public profile preview summary',
1666
+ },
1667
+ labels: {
1668
+ overviewTabCopy: 'Agent summary and discovered peers',
1669
+ profileTabCopy: 'Public identity and saved profile',
1670
+ networkTabCopy: 'Relay health, broadcast, diagnostics',
1671
+ socialTabCopy: 'social.md config and runtime state',
1672
+ collapseSidebar: 'Collapse sidebar',
1673
+ expandSidebar: 'Expand sidebar',
1674
+ toggleFocusMode: 'Toggle focus mode',
1675
+ exitFocusMode: 'Exit focus mode',
1676
+ colorMode: 'Color mode',
1677
+ dark: 'Dark',
1678
+ light: 'Light',
1679
+ system: 'System',
1680
+ mode: 'Mode',
1681
+ adapter: 'Adapter',
1682
+ relay: 'Relay',
1683
+ room: 'Room',
1684
+ namespace: 'Namespace',
1685
+ port: 'Port',
1686
+ show: 'Show',
1687
+ hide: 'Hide',
1688
+ discoveredAgents: 'Discovered Agents',
1689
+ onlyShowOnline: 'Only show online',
1690
+ nodeSnapshot: 'Node Snapshot',
1691
+ profileEyebrow: 'Profile',
1692
+ publicProfile: 'Public profile',
1693
+ publicProfileEditor: 'Public Profile Editor',
1694
+ displayName: 'Display Name',
1695
+ avatarUrl: 'Avatar URL',
1696
+ bio: 'Bio',
1697
+ tagsCommaSeparated: 'Tags (comma separated)',
1698
+ publicEnabled: 'Public Enabled',
1699
+ livePreview: 'Live Preview',
1700
+ publicCard: 'Public Card',
1701
+ publishStatus: 'Publish Status',
1702
+ publicProfilePreview: 'Public Profile Preview',
1703
+ networkEyebrow: 'Network',
1704
+ connectionSummary: 'Connection Summary',
1705
+ quickActions: 'Quick Actions',
1706
+ advancedActions: 'Advanced Actions',
1707
+ diagnostics: 'Diagnostics',
1708
+ runtimeComponents: 'Runtime Components',
1709
+ peerInventory: 'Peer Inventory',
1710
+ peerDiscoveryStats: 'Peer Discovery Stats',
1711
+ recentDiscoveryEvents: 'Recent Discovery Events',
1712
+ discoverySnapshot: 'Discovery Snapshot',
1713
+ logs: 'Logs',
1714
+ category: 'Category',
1715
+ configSnapshot: 'Config Snapshot',
1716
+ statsSnapshot: 'Stats Snapshot',
1717
+ socialEyebrow: 'Social',
1718
+ integrationStatus: 'Integration Status',
1719
+ whatIsActive: 'What Is Active',
1720
+ identityBinding: 'Identity Binding',
1721
+ advancedNetworkDetails: 'Advanced Network Details',
1722
+ sourceRuntimeTemplate: 'Source / Runtime / Template',
1723
+ sourceParsedFrontmatter: 'Source & Parsed Frontmatter',
1724
+ runtimeSummary: 'Runtime Summary',
1725
+ exportTemplatePreview: 'Export Template Preview',
1726
+ actionsTitle: 'Actions',
1727
+ networkModeRuntime: 'Network Mode (runtime)',
1728
+ },
1729
+ hints: {
1730
+ publicDiscoverySwitch: 'Use Profile -> Public Enabled as the single public visibility switch.',
1731
+ discoverabilityRecommendation: 'Recommended 2-32 chars for better discoverability.',
1732
+ avatarOptional: 'Optional. Must be http(s) URL if provided.',
1733
+ tagsLimit: 'Up to 8 tags, each <= 20 chars.',
1734
+ publicEnabledHint: 'This is the main public visibility switch used for discovery and relay broadcast.',
1735
+ signedPublicProfileHint: 'This is the signed public profile view other nodes/explorer can see.',
1736
+ useTheseFirst: 'Use these first.',
1737
+ profileVisibilityManaged: 'Profile visibility is managed in the Profile page.',
1738
+ checkingIntegration: 'Checking integration status...',
1739
+ allIntegrationChecksPassed: 'All integration checks passed.',
1740
+ },
1741
+ placeholders: {
1742
+ agentName: 'Agent name',
1743
+ bioSummary: 'Public summary',
1744
+ tags: 'ai,browser,local-first',
1745
+ },
1746
+ overview: {
1747
+ discovered: 'Discovered',
1748
+ online: 'Online',
1749
+ offline: 'Offline',
1750
+ presenceTtl: 'Presence TTL',
1751
+ agentsZero: '0 agents',
1752
+ noDiscoveredAgents: 'No discovered agents yet.',
1753
+ agentsOnlineFilter: '{shown}/{total} agents (online filter)',
1754
+ agentsDiscovered: '{count} agents discovered',
1755
+ tableName: 'Name',
1756
+ tableAgentId: 'Agent ID',
1757
+ tableStatus: 'Status',
1758
+ tableUpdated: 'Updated',
1759
+ unnamed: '(unnamed)',
1760
+ onboardingNotice: 'Connected · mode={mode} · discoverable={discoverable} · next: update your profile, turn on Public Enabled, and export social.md',
1761
+ pillRunning: 'broadcast: running',
1762
+ pillPaused: 'broadcast: paused',
1763
+ },
1764
+ preview: {
1765
+ unnamedAgent: '(unnamed agent)',
1766
+ noBioYet: 'No bio yet.',
1767
+ noTags: 'No tags',
1768
+ visibleFields: 'Visible fields: {visible} | Hidden fields: {hidden}',
1769
+ visible: '[visible] {field}',
1770
+ hidden: '[hidden] {field}',
1771
+ },
1772
+ network: {
1773
+ started: 'Started',
1774
+ transportState: 'Transport State',
1775
+ signalingUrl: 'Signaling URL',
1776
+ signalingEndpoints: 'Signaling Endpoints',
1777
+ webrtcRoom: 'WebRTC Room',
1778
+ bootstrapSources: 'Bootstrap Sources',
1779
+ seedPeers: 'Seed Peers',
1780
+ discoveryEvents: 'Discovery Events',
1781
+ recv: 'Recv',
1782
+ sent: 'Sent',
1783
+ peers: 'Peers',
1784
+ onlinePeers: 'Online Peers',
1785
+ activeWebrtcPeers: 'Active WebRTC Peers',
1786
+ reconnectAttempts: 'Reconnect Attempts',
1787
+ lastInbound: 'Last Inbound',
1788
+ lastOutbound: 'Last Outbound',
1789
+ relayHealth: 'Relay health',
1790
+ currentRelay: 'Current relay',
1791
+ currentRoom: 'Current room',
1792
+ lastJoin: 'Last join',
1793
+ lastPoll: 'Last poll',
1794
+ lastPublish: 'Last publish',
1795
+ lastError: 'Last error',
1796
+ degraded: 'degraded',
1797
+ connected: 'connected',
1798
+ none: 'none',
1799
+ total: 'Total',
1800
+ stale: 'Stale',
1801
+ observeCalls: 'Observe Calls',
1802
+ heartbeats: 'Heartbeats',
1803
+ peersAdded: 'Peers Added',
1804
+ peersRemoved: 'Peers Removed',
1805
+ noPeersDiscovered: 'No peers discovered yet. Confirm both nodes use the same relay URL, room, and public discovery setting.',
1806
+ peer: 'Peer',
1807
+ status: 'Status',
1808
+ lastSeen: 'Last Seen',
1809
+ staleSince: 'Stale Since',
1810
+ messages: 'Messages',
1811
+ firstSeen: 'First Seen',
1812
+ meta: 'Meta',
1813
+ eventsTotal: 'Events Total',
1814
+ lastEvent: 'Last Event',
1815
+ noDiscoveryEvents: 'No discovery events yet. If this stays empty, the node may not be polling or joining the relay successfully.',
1816
+ noLogsYet: 'No logs yet.',
1817
+ noLogsForLevel: 'No {level} logs.',
1818
+ },
1819
+ social: {
1820
+ connectedToSilicaClaw: 'Connected to SilicaClaw',
1821
+ notConnectedToSilicaClaw: 'Not connected to SilicaClaw',
1822
+ discoverableInCurrentMode: 'Discoverable in current mode',
1823
+ notDiscoverableInCurrentMode: 'Not discoverable in current mode',
1824
+ usingMode: 'Using {mode}',
1825
+ publicDiscoveryEnabled: 'Public discovery enabled',
1826
+ publicDiscoveryDisabled: 'Public discovery disabled',
1827
+ notConnected: 'Not connected',
1828
+ barStatus: 'Connected to SilicaClaw: {connected} · Network mode: {mode} · Public discovery: {public}',
1829
+ configured: 'Configured',
1830
+ running: 'Running',
1831
+ discoverable: 'Discoverable',
1832
+ publicDiscovery: 'Public discovery',
1833
+ networkMode: 'network mode',
1834
+ connected: 'connected',
1835
+ discoverableReason: 'discoverable reason',
1836
+ displayName: 'display name',
1837
+ agentId: 'agent id',
1838
+ socialFound: 'social.md found',
1839
+ socialSource: 'social.md source',
1840
+ runtimeGenerated: 'runtime generated',
1841
+ reuseOpenClawIdentity: 'reuse OpenClaw identity',
1842
+ mode: 'mode',
1843
+ broadcastStatus: 'broadcast status',
1844
+ namespace: 'namespace',
1845
+ bootstrapHints: 'bootstrap hints',
1846
+ restartRequired: 'restart required',
1847
+ configuredReason: 'Configured: {reason}',
1848
+ runningReason: 'Running: {reason}',
1849
+ discoverableReasonFull: 'Discoverable: {reason}',
1850
+ },
1851
+ feedback: {
1852
+ unsavedChanges: 'You have unsaved changes.',
1853
+ allChangesSaved: 'All changes saved.',
1854
+ fixValidation: 'Please fix validation errors before saving.',
1855
+ savingProfile: 'Saving profile...',
1856
+ profileSaved: 'Profile saved',
1857
+ profileReloaded: 'Profile form reloaded',
1858
+ exportingTemplate: 'Exporting template...',
1859
+ templateExported: 'Template exported from current runtime.',
1860
+ templateCopied: 'Template copied to clipboard.',
1861
+ preparingDownload: 'Preparing download...',
1862
+ runtimeUpdated: 'Runtime mode updated.',
1863
+ copyPreviewFailed: 'Copy preview failed',
1864
+ logsRefreshed: 'Logs refreshed',
1865
+ crossPreviewEnabled: 'Cross-network preview enabled',
1866
+ enableCrossPreviewFailed: 'Enable cross-network preview failed',
1867
+ downloadFailed: 'Download failed',
1868
+ exportFailed: 'Export failed',
1869
+ copyFailed: 'Copy failed',
1870
+ failed: 'Failed',
1871
+ runtimeMode: 'Runtime mode: {mode}',
1872
+ downloaded: 'Downloaded {filename}.',
1873
+ runtimeModeApplying: 'Applying runtime mode: {mode}...',
1874
+ copyingTemplate: 'Copying template...',
1875
+ crossPreviewEnabling: 'Enabling cross-network preview...',
1876
+ promptSignalingUrl: 'Signaling URL (publicly reachable):',
1877
+ promptRoom: 'Room:',
1878
+ },
1879
+ validation: {
1880
+ displayNameShort: 'Display name should be at least 2 chars.',
1881
+ displayNameLong: 'Display name too long.',
1882
+ avatarProtocol: 'Avatar URL must start with http:// or https://',
1883
+ tooManyTags: 'Too many tags (max 8).',
1884
+ tagTooLong: 'Each tag must be <= 20 chars.',
1885
+ leaveConfirm: 'You have unsaved profile changes. Leave anyway?',
1886
+ },
1887
+ },
1888
+ 'zh-CN': {
1889
+ meta: {
1890
+ title: 'SilicaClaw 控制台',
1891
+ description: 'SilicaClaw 本地优先代理控制台。',
1892
+ socialTitle: 'SilicaClaw 本地控制台',
1893
+ socialDescription: '面向 SilicaClaw 代理的本地优先控制界面。',
1894
+ },
1895
+ common: {
1896
+ control: '控制',
1897
+ version: '版本',
1898
+ localConsole: '本地控制台',
1899
+ subtitle: '受 OpenClaw 启发的本地优先代理控制界面',
1900
+ copied: '已复制',
1901
+ done: '完成',
1902
+ saving: '保存中...',
1903
+ saved: '已保存。',
1904
+ ready: '就绪。',
1905
+ reload: '重新加载',
1906
+ yes: '是',
1907
+ no: '否',
1908
+ on: '已启用',
1909
+ off: '已禁用',
1910
+ unknownError: '未知错误',
1911
+ requestFailed: '请求失败 ({status})',
1912
+ },
1913
+ pageMeta: {
1914
+ overview: {
1915
+ title: '概览',
1916
+ body: '查看当前节点是否在线,以及还能看到哪些节点。',
1917
+ },
1918
+ profile: {
1919
+ title: 'Profile',
1920
+ body: '编辑其他节点可见的公开名片。',
1921
+ },
1922
+ network: {
1923
+ title: '网络',
1924
+ body: '检查 relay 状态、对等节点和广播健康度。',
1925
+ },
1926
+ social: {
1927
+ title: '社交',
1928
+ body: '切换模式并导出干净的 social.md 模板。',
1929
+ },
1930
+ },
1931
+ actions: {
1932
+ broadcastNow: '立即广播',
1933
+ editProfile: '编辑 Profile',
1934
+ openNetwork: '打开网络页',
1935
+ startBroadcast: '启动广播',
1936
+ stopBroadcast: '停止广播',
1937
+ enablePreview: '启用跨网络预览',
1938
+ refreshLogs: '刷新日志',
1939
+ exportTemplate: '导出 social.md 模板',
1940
+ copyTemplate: '复制模板',
1941
+ downloadTemplate: '下载模板',
1942
+ applyRuntimeMode: '应用运行时模式',
1943
+ saveProfile: '保存 Profile',
1944
+ copyPublicProfilePreview: '复制公开 Profile 预览摘要',
1945
+ },
1946
+ labels: {
1947
+ overviewTabCopy: '代理摘要与已发现节点',
1948
+ profileTabCopy: '公开身份与已保存的 Profile',
1949
+ networkTabCopy: 'Relay 健康度、广播与诊断',
1950
+ socialTabCopy: 'social.md 配置与运行时状态',
1951
+ collapseSidebar: '折叠侧边栏',
1952
+ expandSidebar: '展开侧边栏',
1953
+ toggleFocusMode: '切换专注模式',
1954
+ exitFocusMode: '退出专注模式',
1955
+ colorMode: '颜色模式',
1956
+ dark: '深色',
1957
+ light: '浅色',
1958
+ system: '跟随系统',
1959
+ mode: '模式',
1960
+ adapter: '适配器',
1961
+ relay: 'Relay',
1962
+ room: '房间',
1963
+ namespace: '命名空间',
1964
+ port: '端口',
1965
+ show: '显示',
1966
+ hide: '隐藏',
1967
+ discoveredAgents: '已发现代理',
1968
+ onlyShowOnline: '只显示在线',
1969
+ nodeSnapshot: '节点快照',
1970
+ profileEyebrow: 'Profile',
1971
+ publicProfile: '公开 Profile',
1972
+ publicProfileEditor: '公开 Profile 编辑器',
1973
+ displayName: '显示名称',
1974
+ avatarUrl: '头像 URL',
1975
+ bio: '简介',
1976
+ tagsCommaSeparated: '标签(逗号分隔)',
1977
+ publicEnabled: '公开启用',
1978
+ livePreview: '实时预览',
1979
+ publicCard: '公开卡片',
1980
+ publishStatus: '发布状态',
1981
+ publicProfilePreview: '公开 Profile 预览',
1982
+ networkEyebrow: '网络',
1983
+ connectionSummary: '连接摘要',
1984
+ quickActions: '快捷操作',
1985
+ advancedActions: '高级操作',
1986
+ diagnostics: '诊断',
1987
+ runtimeComponents: '运行时组件',
1988
+ peerInventory: '节点清单',
1989
+ peerDiscoveryStats: '节点发现统计',
1990
+ recentDiscoveryEvents: '最近发现事件',
1991
+ discoverySnapshot: '发现快照',
1992
+ logs: '日志',
1993
+ category: '类别',
1994
+ configSnapshot: '配置快照',
1995
+ statsSnapshot: '统计快照',
1996
+ socialEyebrow: '社交',
1997
+ integrationStatus: '集成状态',
1998
+ whatIsActive: '当前激活项',
1999
+ identityBinding: '身份绑定',
2000
+ advancedNetworkDetails: '高级网络详情',
2001
+ sourceRuntimeTemplate: '来源 / 运行时 / 模板',
2002
+ sourceParsedFrontmatter: '源文件与解析后的 Frontmatter',
2003
+ runtimeSummary: '运行时摘要',
2004
+ exportTemplatePreview: '导出模板预览',
2005
+ actionsTitle: '操作',
2006
+ networkModeRuntime: '网络模式(运行时)',
2007
+ },
2008
+ hints: {
2009
+ publicDiscoverySwitch: '使用 Profile -> Public Enabled 作为唯一的公开可见性开关。',
2010
+ discoverabilityRecommendation: '建议 2-32 个字符,以提升可发现性。',
2011
+ avatarOptional: '可选。如提供,必须是 http(s) URL。',
2012
+ tagsLimit: '最多 8 个标签,每个不超过 20 个字符。',
2013
+ publicEnabledHint: '这是发现与 relay 广播使用的主公开可见性开关。',
2014
+ signedPublicProfileHint: '这是其他节点或 explorer 可以看到的已签名公开 Profile 视图。',
2015
+ useTheseFirst: '优先使用这些操作。',
2016
+ profileVisibilityManaged: 'Profile 可见性在 Profile 页面管理。',
2017
+ checkingIntegration: '正在检查集成状态...',
2018
+ allIntegrationChecksPassed: '所有集成检查均已通过。',
2019
+ },
2020
+ placeholders: {
2021
+ agentName: '代理名称',
2022
+ bioSummary: '公开摘要',
2023
+ tags: 'ai,browser,local-first',
2024
+ },
2025
+ overview: {
2026
+ discovered: '已发现',
2027
+ online: '在线',
2028
+ offline: '离线',
2029
+ presenceTtl: 'Presence TTL',
2030
+ agentsZero: '0 个代理',
2031
+ noDiscoveredAgents: '还没有发现代理。',
2032
+ agentsOnlineFilter: '{shown}/{total} 个代理(在线筛选)',
2033
+ agentsDiscovered: '已发现 {count} 个代理',
2034
+ tableName: '名称',
2035
+ tableAgentId: '代理 ID',
2036
+ tableStatus: '状态',
2037
+ tableUpdated: '更新时间',
2038
+ unnamed: '(未命名)',
2039
+ onboardingNotice: '已连接 · mode={mode} · discoverable={discoverable} · 下一步:更新 Profile、开启 Public Enabled,并导出 social.md',
2040
+ pillRunning: 'broadcast: 运行中',
2041
+ pillPaused: 'broadcast: 已暂停',
2042
+ },
2043
+ preview: {
2044
+ unnamedAgent: '(未命名代理)',
2045
+ noBioYet: '还没有简介。',
2046
+ noTags: '没有标签',
2047
+ visibleFields: '显示字段: {visible} | 隐藏字段: {hidden}',
2048
+ visible: '[显示] {field}',
2049
+ hidden: '[隐藏] {field}',
2050
+ },
2051
+ network: {
2052
+ started: '已启动',
2053
+ transportState: '传输状态',
2054
+ signalingUrl: 'Signaling URL',
2055
+ signalingEndpoints: 'Signaling 端点',
2056
+ webrtcRoom: 'WebRTC 房间',
2057
+ bootstrapSources: 'Bootstrap 来源',
2058
+ seedPeers: '种子节点',
2059
+ discoveryEvents: '发现事件',
2060
+ recv: '接收',
2061
+ sent: '发送',
2062
+ peers: '节点',
2063
+ onlinePeers: '在线节点',
2064
+ activeWebrtcPeers: '活跃 WebRTC 节点',
2065
+ reconnectAttempts: '重连尝试次数',
2066
+ lastInbound: '最近入站',
2067
+ lastOutbound: '最近出站',
2068
+ relayHealth: 'Relay 健康度',
2069
+ currentRelay: '当前 Relay',
2070
+ currentRoom: '当前房间',
2071
+ lastJoin: '最近加入',
2072
+ lastPoll: '最近轮询',
2073
+ lastPublish: '最近发布',
2074
+ lastError: '最近错误',
2075
+ degraded: '降级',
2076
+ connected: '已连接',
2077
+ none: '无',
2078
+ total: '总数',
2079
+ stale: '陈旧',
2080
+ observeCalls: '观察调用',
2081
+ heartbeats: '心跳',
2082
+ peersAdded: '新增节点',
2083
+ peersRemoved: '移除节点',
2084
+ noPeersDiscovered: '尚未发现节点。请确认两端使用相同的 relay URL、room 与公开发现设置。',
2085
+ peer: '节点',
2086
+ status: '状态',
2087
+ lastSeen: '最近出现',
2088
+ staleSince: '陈旧开始于',
2089
+ messages: '消息数',
2090
+ firstSeen: '首次出现',
2091
+ meta: '元数据',
2092
+ eventsTotal: '事件总数',
2093
+ lastEvent: '最近事件',
2094
+ noDiscoveryEvents: '还没有发现事件。如果这里持续为空,节点可能没有成功轮询或加入 relay。',
2095
+ noLogsYet: '还没有日志。',
2096
+ noLogsForLevel: '没有 {level} 级别日志。',
2097
+ },
2098
+ social: {
2099
+ connectedToSilicaClaw: '已连接到 SilicaClaw',
2100
+ notConnectedToSilicaClaw: '未连接到 SilicaClaw',
2101
+ discoverableInCurrentMode: '当前模式下可发现',
2102
+ notDiscoverableInCurrentMode: '当前模式下不可发现',
2103
+ usingMode: '使用 {mode}',
2104
+ publicDiscoveryEnabled: '公开发现已启用',
2105
+ publicDiscoveryDisabled: '公开发现已禁用',
2106
+ notConnected: '未连接',
2107
+ barStatus: 'Connected to SilicaClaw: {connected} · Network mode: {mode} · Public discovery: {public}',
2108
+ configured: '已配置',
2109
+ running: '运行中',
2110
+ discoverable: '可发现',
2111
+ publicDiscovery: '公开发现',
2112
+ networkMode: '网络模式',
2113
+ connected: '已连接',
2114
+ discoverableReason: '可发现原因',
2115
+ displayName: '显示名称',
2116
+ agentId: '代理 ID',
2117
+ socialFound: '已找到 social.md',
2118
+ socialSource: 'social.md 来源',
2119
+ runtimeGenerated: '运行时生成',
2120
+ reuseOpenClawIdentity: '复用 OpenClaw 身份',
2121
+ mode: '模式',
2122
+ broadcastStatus: '广播状态',
2123
+ namespace: '命名空间',
2124
+ bootstrapHints: 'Bootstrap 提示',
2125
+ restartRequired: '需要重启',
2126
+ configuredReason: 'Configured: {reason}',
2127
+ runningReason: 'Running: {reason}',
2128
+ discoverableReasonFull: 'Discoverable: {reason}',
2129
+ },
2130
+ feedback: {
2131
+ unsavedChanges: '你有尚未保存的更改。',
2132
+ allChangesSaved: '所有更改都已保存。',
2133
+ fixValidation: '请先修复校验错误再保存。',
2134
+ savingProfile: '正在保存 Profile...',
2135
+ profileSaved: 'Profile 已保存',
2136
+ profileReloaded: 'Profile 表单已重新加载',
2137
+ exportingTemplate: '正在导出模板...',
2138
+ templateExported: '已按当前运行时导出模板。',
2139
+ templateCopied: '模板已复制到剪贴板。',
2140
+ preparingDownload: '正在准备下载...',
2141
+ runtimeUpdated: '运行时模式已更新。',
2142
+ copyPreviewFailed: '复制预览失败',
2143
+ logsRefreshed: '日志已刷新',
2144
+ crossPreviewEnabled: '跨网络预览已启用',
2145
+ enableCrossPreviewFailed: '启用跨网络预览失败',
2146
+ downloadFailed: '下载失败',
2147
+ exportFailed: '导出失败',
2148
+ copyFailed: '复制失败',
2149
+ failed: '失败',
2150
+ runtimeMode: '运行时模式: {mode}',
2151
+ downloaded: '已下载 {filename}。',
2152
+ runtimeModeApplying: '正在应用运行时模式: {mode}...',
2153
+ copyingTemplate: '正在复制模板...',
2154
+ crossPreviewEnabling: '正在启用跨网络预览...',
2155
+ promptSignalingUrl: 'Signaling URL(需公网可达):',
2156
+ promptRoom: '房间:',
2157
+ },
2158
+ validation: {
2159
+ displayNameShort: '显示名称至少需要 2 个字符。',
2160
+ displayNameLong: '显示名称过长。',
2161
+ avatarProtocol: '头像 URL 必须以 http:// 或 https:// 开头',
2162
+ tooManyTags: '标签过多(最多 8 个)。',
2163
+ tagTooLong: '每个标签必须不超过 20 个字符。',
2164
+ leaveConfirm: '你有未保存的 Profile 更改,仍要离开吗?',
2165
+ },
2166
+ },
2167
+ };
2168
+ function isSupportedLocale(value) {
2169
+ return SUPPORTED_LOCALES.includes(value);
2170
+ }
2171
+ function resolveNavigatorLocale(language) {
2172
+ return String(language || '').toLowerCase().startsWith('zh') ? 'zh-CN' : DEFAULT_LOCALE;
2173
+ }
2174
+ function resolveInitialLocale() {
2175
+ const saved = localStorage.getItem(LOCALE_STORAGE_KEY);
2176
+ if (isSupportedLocale(saved)) return saved;
2177
+ return resolveNavigatorLocale(globalThis.navigator?.language || '');
2178
+ }
2179
+ let currentLocale = resolveInitialLocale();
2180
+ function t(key, params = {}) {
2181
+ const parts = key.split('.');
2182
+ const resolve = (bundle) => parts.reduce((acc, part) => (acc && typeof acc === 'object' ? acc[part] : undefined), bundle);
2183
+ let value = resolve(TRANSLATIONS[currentLocale]);
2184
+ if (typeof value !== 'string') value = resolve(TRANSLATIONS[DEFAULT_LOCALE]);
2185
+ if (typeof value !== 'string') return key;
2186
+ return value.replace(/\{(\w+)\}/g, (_, name) => params[name] ?? `{${name}}`);
2187
+ }
2188
+ function setLocale(locale) {
2189
+ currentLocale = isSupportedLocale(locale) ? locale : DEFAULT_LOCALE;
2190
+ document.documentElement.lang = currentLocale;
2191
+ }
2192
+ function applyStaticTranslations() {
2193
+ const setText = (selector, text, index = 0) => {
2194
+ const nodes = document.querySelectorAll(selector);
2195
+ if (nodes[index]) nodes[index].textContent = text;
2196
+ };
2197
+ document.title = t('meta.title');
2198
+ document.getElementById('metaDescription').setAttribute('content', t('meta.description'));
2199
+ document.getElementById('ogTitle').setAttribute('content', t('meta.socialTitle'));
2200
+ document.getElementById('ogDescription').setAttribute('content', t('meta.socialDescription'));
2201
+ document.getElementById('twitterTitle').setAttribute('content', t('meta.socialTitle'));
2202
+ document.getElementById('twitterDescription').setAttribute('content', t('meta.socialDescription'));
2203
+ setText('.brand p', 'Control UI');
2204
+ document.querySelectorAll('.advanced-panel summary').forEach((summary) => {
2205
+ summary.setAttribute('data-i18n-closed-label', t('labels.show'));
2206
+ summary.setAttribute('data-i18n-open-label', t('labels.hide'));
2207
+ });
2208
+ setText('.sidebar-nav__label', t('common.control'));
2209
+ setText('[data-tab="overview"] .tab-title', t('pageMeta.overview.title'));
2210
+ setText('[data-tab="overview"] .tab-copy', t('labels.overviewTabCopy'));
2211
+ setText('[data-tab="profile"] .tab-title', t('pageMeta.profile.title'));
2212
+ setText('[data-tab="profile"] .tab-copy', t('labels.profileTabCopy'));
2213
+ setText('[data-tab="network"] .tab-title', t('pageMeta.network.title'));
2214
+ setText('[data-tab="network"] .tab-copy', t('labels.networkTabCopy'));
2215
+ setText('[data-tab="social"] .tab-title', t('pageMeta.social.title'));
2216
+ setText('[data-tab="social"] .tab-copy', t('labels.socialTabCopy'));
2217
+ document.getElementById('sidebarToggleBtn').title = t('labels.collapseSidebar');
2218
+ document.getElementById('sidebarToggleBtn').setAttribute('aria-label', t('labels.collapseSidebar'));
2219
+ document.querySelector('.sidebar-version').title = t('common.version');
2220
+ setText('.dashboard-header__breadcrumb-current', t('pageMeta.overview.title'));
2221
+ document.getElementById('topbarTitle').textContent = t('common.localConsole');
2222
+ document.getElementById('topbarSubtitle').textContent = t('common.subtitle');
2223
+ document.getElementById('focusModeBtn').title = t('labels.toggleFocusMode');
2224
+ document.getElementById('focusModeBtn').setAttribute('aria-label', t('labels.toggleFocusMode'));
2225
+ document.getElementById('themeModeGroup').setAttribute('aria-label', t('labels.colorMode'));
2226
+ document.querySelector('[data-theme-choice="dark"]').title = t('labels.dark');
2227
+ document.querySelector('[data-theme-choice="dark"]').setAttribute('aria-label', `${t('labels.colorMode')}: ${t('labels.dark')}`);
2228
+ document.querySelector('[data-theme-choice="light"]').title = t('labels.light');
2229
+ document.querySelector('[data-theme-choice="light"]').setAttribute('aria-label', `${t('labels.colorMode')}: ${t('labels.light')}`);
2230
+ document.querySelector('[data-theme-choice="system"]').title = t('labels.system');
2231
+ document.querySelector('[data-theme-choice="system"]').setAttribute('aria-label', `${t('labels.colorMode')}: ${t('labels.system')}`);
2232
+ setText('#view-overview .title-sm', t('labels.discoveredAgents'), 0);
2233
+ document.getElementById('agentsCountHint').textContent = t('overview.agentsZero');
2234
+ setText('#view-overview label.field-hint', t('labels.onlyShowOnline'));
2235
+ setText('#view-overview .title-sm', t('labels.nodeSnapshot'), 1);
2236
+ setText('#view-profile .section-header__eyebrow', t('labels.profileEyebrow'));
2237
+ setText('#view-profile .section-header__title', t('labels.publicProfile'));
2238
+ setText('#view-profile .title-sm', t('labels.publicProfileEditor'), 0);
2239
+ setText('#view-profile label', t('labels.displayName'), 0);
2240
+ setText('#view-profile .row > div:nth-child(2) label', t('labels.avatarUrl'));
2241
+ setText('#view-profile div > label + textarea', t('labels.bio'));
2242
+ setText('#view-profile div > label + input[name="tags"]', t('labels.tagsCommaSeparated'));
2243
+ document.querySelector('#view-profile input[name="display_name"]').setAttribute('placeholder', t('placeholders.agentName'));
2244
+ document.querySelector('#view-profile input[name="avatar_url"]').setAttribute('placeholder', 'https://...');
2245
+ document.querySelector('#view-profile textarea[name="bio"]').setAttribute('placeholder', t('placeholders.bioSummary'));
2246
+ document.querySelector('#view-profile input[name="tags"]').setAttribute('placeholder', t('placeholders.tags'));
2247
+ document.querySelector('#view-profile input[name="display_name"]').nextElementSibling.textContent = t('hints.discoverabilityRecommendation');
2248
+ document.querySelector('#view-profile input[name="avatar_url"]').nextElementSibling.textContent = t('hints.avatarOptional');
2249
+ document.querySelector('#view-profile input[name="tags"]').nextElementSibling.textContent = t('hints.tagsLimit');
2250
+ document.querySelector('#view-profile input[name="public_enabled"]').parentElement.childNodes[1].textContent = ` ${t('labels.publicEnabled')}`;
2251
+ setText('#view-profile .field-hint', t('hints.publicEnabledHint'), 3);
2252
+ setText('#view-profile .title-sm', t('labels.livePreview'), 1);
2253
+ setText('#view-profile .profile-meta h4', t('labels.publicCard'), 0);
2254
+ setText('#view-profile .profile-meta h4', t('labels.publishStatus'), 1);
2255
+ setText('#view-profile .profile-meta h4', t('labels.publicProfilePreview'), 2);
2256
+ setText('#view-profile .profile-meta .field-hint', t('hints.signedPublicProfileHint'));
2257
+ setText('#view-network .section-header__eyebrow', t('labels.networkEyebrow'));
2258
+ setText('#view-network .section-header__title', t('pageMeta.network.title'));
2259
+ setText('#view-network .split .card .title-sm', t('labels.connectionSummary'), 0);
2260
+ setText('#view-network .split .card .title-sm', t('labels.quickActions'), 1);
2261
+ setText('#view-network .split .card .field-hint', t('hints.useTheseFirst'));
2262
+ setText('#view-network details.advanced-panel summary', t('labels.advancedActions'), 0);
2263
+ setText('#view-network > details.advanced-panel.card > summary', t('labels.diagnostics'));
2264
+ setText('#view-network .card .title-sm', t('labels.runtimeComponents'), 3);
2265
+ setText('#view-network .card .title-sm', t('labels.peerInventory'), 4);
2266
+ setText('#view-network .card .title-sm', t('labels.peerDiscoveryStats'), 5);
2267
+ setText('#view-network .card .title-sm', t('labels.recentDiscoveryEvents'), 6);
2268
+ setText('#view-network .card .title-sm', t('labels.discoverySnapshot'), 7);
2269
+ setText('#view-network label[for="logLevelFilter"]', t('labels.category'));
2270
+ setText('#view-network .card .title-sm', t('labels.configSnapshot'), 9);
2271
+ setText('#view-network .card .title-sm', t('labels.statsSnapshot'), 10);
2272
+ setText('#view-social .section-header__eyebrow', t('labels.socialEyebrow'));
2273
+ setText('#view-social .section-header__title', t('pageMeta.social.title'));
2274
+ setText('#view-social .card .title-sm', t('labels.integrationStatus'), 0);
2275
+ document.querySelector('#socialStatusLine').textContent = t('hints.checkingIntegration');
2276
+ setText('#view-social .split .card .title-sm', t('labels.whatIsActive'), 0);
2277
+ setText('#view-social .split .card .title-sm', t('labels.identityBinding'), 1);
2278
+ setText('#view-social details.card summary', t('labels.advancedNetworkDetails'));
2279
+ setText('#view-social details.advanced-panel summary', t('labels.sourceRuntimeTemplate'));
2280
+ setText('#view-social .card .title-sm', t('labels.sourceParsedFrontmatter'), 3);
2281
+ setText('#view-social .card .title-sm', t('labels.runtimeSummary'), 4);
2282
+ setText('#view-social .card .title-sm', t('labels.exportTemplatePreview'), 5);
2283
+ setText('#view-social .card .title-sm', t('labels.actionsTitle'), 6);
2284
+ setText('label[for="socialModeSelect"]', t('labels.networkModeRuntime'));
2285
+ setText('#view-social > .card:last-of-type .field-hint', t('hints.profileVisibilityManaged'));
2286
+ setText('.hero-meta-item .label', t('labels.mode'), 0);
2287
+ setText('.hero-meta-item .label', t('labels.adapter'), 1);
2288
+ setText('.hero-meta-item .label', t('labels.relay'), 2);
2289
+ setText('.hero-meta-item .label', t('labels.room'), 3);
2290
+ document.getElementById('publicDiscoveryHint').innerHTML = t('hints.publicDiscoverySwitch');
2291
+ document.getElementById('overviewBroadcastNowBtn').textContent = t('actions.broadcastNow');
2292
+ document.getElementById('overviewGoProfileBtn').textContent = t('actions.editProfile');
2293
+ document.getElementById('overviewGoNetworkBtn').textContent = t('actions.openNetwork');
2294
+ document.getElementById('startBroadcastBtn').textContent = t('actions.startBroadcast');
2295
+ document.getElementById('stopBroadcastBtn').textContent = t('actions.stopBroadcast');
2296
+ document.getElementById('quickGlobalPreviewBtn').textContent = t('actions.enablePreview');
2297
+ document.getElementById('refreshLogsBtn').textContent = t('actions.refreshLogs');
2298
+ document.getElementById('socialExportBtn').textContent = t('actions.exportTemplate');
2299
+ document.getElementById('socialCopyBtn').textContent = t('actions.copyTemplate');
2300
+ document.getElementById('socialDownloadBtn').textContent = t('actions.downloadTemplate');
2301
+ document.getElementById('socialModeApplyBtn').textContent = t('actions.applyRuntimeMode');
2302
+ document.getElementById('copyPublicProfilePreviewBtn').textContent = t('actions.copyPublicProfilePreview');
2303
+ document.getElementById('saveProfileBtn').textContent = t('actions.saveProfile');
2304
+ document.getElementById('refreshProfileBtn').textContent = t('common.reload');
2305
+ document.getElementById('profileFeedback').textContent = t('common.ready');
2306
+ document.getElementById('networkFeedback').textContent = t('common.ready');
2307
+ document.getElementById('socialFeedback').textContent = t('common.ready');
2308
+ }
2309
+ setLocale(currentLocale);
2310
+ applyStaticTranslations();
2311
+
737
2312
  let activeTab = 'overview';
738
2313
  let profileBaseline = '';
739
2314
  let profileDirty = false;
@@ -742,6 +2317,7 @@
742
2317
  let logLevelFilter = 'all';
743
2318
  let socialTemplate = '';
744
2319
  let onlyShowOnline = false;
2320
+ const pageMeta = TRANSLATIONS[currentLocale].pageMeta || TRANSLATIONS[DEFAULT_LOCALE].pageMeta;
745
2321
 
746
2322
  function ago(ts) {
747
2323
  if (!ts) return '-';
@@ -767,6 +2343,12 @@
767
2343
  t.classList.add('show');
768
2344
  setTimeout(() => t.classList.remove('show'), 2000);
769
2345
  }
2346
+ function resolveThemeMode(mode) {
2347
+ if (mode === 'system') {
2348
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
2349
+ }
2350
+ return mode === 'light' ? 'light' : 'dark';
2351
+ }
770
2352
  function flashButton(btn, doneText = 'Done') {
771
2353
  if (!btn) return;
772
2354
  const oldText = btn.textContent || '';
@@ -783,11 +2365,16 @@
783
2365
  el.style.color = level === 'error' ? '#ff6b81' : level === 'warn' ? '#ffb454' : '#9aa7c3';
784
2366
  }
785
2367
  function applyTheme(mode) {
786
- const next = mode === 'light' ? 'light' : 'dark';
2368
+ const raw = mode === 'system' ? 'system' : (mode === 'light' ? 'light' : 'dark');
2369
+ const next = resolveThemeMode(raw);
787
2370
  document.documentElement.setAttribute('data-theme-mode', next);
788
- localStorage.setItem('silicaclaw_theme_mode', next);
789
- document.getElementById('themeDarkBtn').classList.toggle('active', next === 'dark');
790
- document.getElementById('themeLightBtn').classList.toggle('active', next === 'light');
2371
+ localStorage.setItem('silicaclaw_theme_mode', raw);
2372
+ document.querySelectorAll('[data-theme-choice]').forEach((btn) => {
2373
+ btn.classList.toggle('active', btn.dataset.themeChoice === raw);
2374
+ btn.classList.toggle('topbar-theme-mode__btn--active', btn.dataset.themeChoice === raw);
2375
+ btn.setAttribute('aria-checked', btn.dataset.themeChoice === raw ? 'true' : 'false');
2376
+ btn.setAttribute('aria-pressed', btn.dataset.themeChoice === raw ? 'true' : 'false');
2377
+ });
791
2378
  }
792
2379
  function parseTags(raw) {
793
2380
  return String(raw || '')
@@ -822,7 +2409,7 @@
822
2409
  if (fromUserInput && !profileSaving) {
823
2410
  setFeedback(
824
2411
  'profileFeedback',
825
- profileDirty ? 'You have unsaved changes.' : 'All changes saved.',
2412
+ profileDirty ? t('feedback.unsavedChanges') : t('feedback.allChangesSaved'),
826
2413
  profileDirty ? 'warn' : 'info'
827
2414
  );
828
2415
  }
@@ -849,27 +2436,27 @@
849
2436
  let ok = true;
850
2437
  let err = '';
851
2438
  if (displayName.length > 0 && displayName.length < 2) {
852
- err = 'Display name should be at least 2 chars.';
2439
+ err = t('validation.displayNameShort');
853
2440
  ok = false;
854
2441
  } else if (displayName.length > 48) {
855
- err = 'Display name too long.';
2442
+ err = t('validation.displayNameLong');
856
2443
  ok = false;
857
2444
  }
858
2445
  setInputError(displayNameEl, 'errDisplayName', err);
859
2446
 
860
2447
  err = '';
861
2448
  if (avatarUrl && !/^https?:\/\//i.test(avatarUrl)) {
862
- err = 'Avatar URL must start with http:// or https://';
2449
+ err = t('validation.avatarProtocol');
863
2450
  ok = false;
864
2451
  }
865
2452
  setInputError(avatarEl, 'errAvatarUrl', err);
866
2453
 
867
2454
  err = '';
868
2455
  if (tags.length > 8) {
869
- err = 'Too many tags (max 8).';
2456
+ err = t('validation.tooManyTags');
870
2457
  ok = false;
871
2458
  } else if (tags.some((t) => t.length > 20)) {
872
- err = 'Each tag must be <= 20 chars.';
2459
+ err = t('validation.tagTooLong');
873
2460
  ok = false;
874
2461
  }
875
2462
  setInputError(tagsEl, 'errTags', err);
@@ -881,14 +2468,14 @@
881
2468
  const tags = parseTags(field(form, 'tags')?.value || '');
882
2469
  const enabled = !!field(form, 'public_enabled')?.checked;
883
2470
 
884
- document.getElementById('previewName').textContent = displayName || '(unnamed agent)';
885
- document.getElementById('previewBio').textContent = bio || 'No bio yet.';
2471
+ document.getElementById('previewName').textContent = displayName || t('preview.unnamedAgent');
2472
+ document.getElementById('previewBio').textContent = bio || t('preview.noBioYet');
886
2473
  document.getElementById('previewPublish').textContent = `public_enabled: ${enabled}`;
887
2474
  document.getElementById('bioCount').textContent = String(bio.length);
888
2475
 
889
2476
  const tagBox = document.getElementById('previewTags');
890
2477
  if (!tags.length) {
891
- tagBox.innerHTML = '<span class=\"tag-chip muted\">No tags</span>';
2478
+ tagBox.innerHTML = `<span class="tag-chip muted">${t('preview.noTags')}</span>`;
892
2479
  return;
893
2480
  }
894
2481
  tagBox.innerHTML = tags.map((tag) => `<span class=\"tag-chip\">${tag}</span>`).join('');
@@ -898,30 +2485,34 @@
898
2485
  const btn = document.getElementById('saveProfileBtn');
899
2486
  btn.disabled = busy;
900
2487
  btn.classList.toggle('save-busy', busy);
901
- btn.textContent = busy ? 'Saving...' : 'Save Profile';
2488
+ btn.textContent = busy ? t('common.saving') : t('actions.saveProfile');
902
2489
  }
903
2490
 
904
2491
  async function api(path, options = {}) {
905
2492
  const res = await fetch(path, { headers: { 'Content-Type': 'application/json' }, ...options });
906
2493
  const json = await res.json().catch(() => null);
907
2494
  if (!res.ok || !json || !json.ok) {
908
- throw new Error(json?.error?.message || `Request failed (${res.status})`);
2495
+ throw new Error(json?.error?.message || t('common.requestFailed', { status: String(res.status) }));
909
2496
  }
910
2497
  return json;
911
2498
  }
912
2499
 
913
2500
  function switchTab(tab) {
914
2501
  if (activeTab === 'profile' && tab !== 'profile' && profileDirty && !profileSaving) {
915
- const ok = window.confirm('You have unsaved profile changes. Leave anyway?');
2502
+ const ok = window.confirm(t('validation.leaveConfirm'));
916
2503
  if (!ok) {
917
2504
  return;
918
2505
  }
919
2506
  }
920
2507
  activeTab = tab;
921
2508
  document.querySelectorAll('.tab').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));
922
- ['overview', 'profile', 'network', 'peers', 'discovery', 'social', 'logs'].forEach((k) => {
2509
+ ['overview', 'profile', 'network', 'social'].forEach((k) => {
923
2510
  document.getElementById(`view-${k}`).classList.toggle('active', k === tab);
924
2511
  });
2512
+ const meta = pageMeta[tab] || pageMeta.overview;
2513
+ document.getElementById('pageBreadcrumb').textContent = meta.title;
2514
+ document.getElementById('pageHeroTitle').textContent = meta.title;
2515
+ document.getElementById('pageHeroBody').textContent = meta.body;
925
2516
  if (tab === 'profile' && !profileDirty && !profileSaving) {
926
2517
  refreshProfile().catch(() => {});
927
2518
  }
@@ -934,67 +2525,62 @@
934
2525
  const d = onlyShowOnline ? all.filter((agent) => agent.online) : all;
935
2526
 
936
2527
  document.getElementById('overviewCards').innerHTML = [
937
- ['Discovered', o.discovered_count],
938
- ['Online', o.online_count],
939
- ['Offline', o.offline_count],
940
- ['Presence TTL', `${Math.floor(o.presence_ttl_ms / 1000)}s`],
2528
+ [t('overview.discovered'), o.discovered_count],
2529
+ [t('overview.online'), o.online_count],
2530
+ [t('overview.offline'), o.offline_count],
2531
+ [t('overview.presenceTtl'), `${Math.floor(o.presence_ttl_ms / 1000)}s`],
941
2532
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value">${v}</div></div>`).join('');
942
2533
 
2534
+ document.getElementById('brandVersion').textContent = o.app_version ? `v${o.app_version}` : '-';
2535
+
943
2536
  document.getElementById('snapshot').textContent = [
2537
+ `app_version: ${o.app_version || '-'}`,
944
2538
  `agent_id: ${o.agent_id || '-'}`,
945
2539
  `public_enabled: ${o.public_enabled}`,
946
2540
  `broadcast_enabled: ${o.broadcast_enabled}`,
947
2541
  `last_broadcast: ${ago(o.last_broadcast_at)}`,
948
2542
  ].join('\n');
2543
+ document.getElementById('heroMode').textContent = o.social?.network_mode || '-';
949
2544
 
950
- document.getElementById('pillBroadcast').textContent = o.broadcast_enabled ? 'broadcast: running' : 'broadcast: paused';
2545
+ document.getElementById('pillBroadcast').textContent = o.broadcast_enabled ? t('overview.pillRunning') : t('overview.pillPaused');
951
2546
  document.getElementById('pillBroadcast').className = `pill ${o.broadcast_enabled ? 'ok' : 'warn'}`;
952
2547
 
953
2548
  const init = o.init_state || {};
954
2549
  const onboarding = o.onboarding || {};
955
2550
  const notice = document.getElementById('initNotice');
956
- const onboardingActions = document.getElementById('onboardingActions');
957
- const enableBtn = document.getElementById('enablePublicDiscoveryBtn');
958
- const disableBtn = document.getElementById('disablePublicDiscoveryBtn');
959
2551
  const discoveryHint = document.getElementById('publicDiscoveryHint');
960
2552
  const showOnboarding = onboarding.first_run || init.identity_auto_created || init.profile_auto_created || init.social_auto_created;
961
2553
  if (onboarding.first_run || init.identity_auto_created || init.profile_auto_created || init.social_auto_created) {
962
2554
  notice.classList.add('show');
963
- notice.textContent = `OpenClaw already connected to SilicaClaw · mode=${onboarding.mode || '-'} · discoverable=${onboarding.discoverable ? 'yes' : 'no'} · next: update display name and export social.md`;
2555
+ notice.textContent = t('overview.onboardingNotice', {
2556
+ mode: onboarding.mode || '-',
2557
+ discoverable: onboarding.discoverable ? t('common.yes') : t('common.no'),
2558
+ });
964
2559
  } else {
965
2560
  notice.classList.remove('show');
966
2561
  }
967
2562
  if (onboarding.can_enable_public_discovery || onboarding.public_enabled) {
968
- onboardingActions.style.display = 'flex';
969
2563
  discoveryHint.style.display = 'block';
970
- if (onboarding.public_enabled) {
971
- enableBtn.style.display = 'none';
972
- disableBtn.style.display = 'inline-block';
973
- } else {
974
- enableBtn.style.display = 'inline-block';
975
- disableBtn.style.display = 'none';
976
- }
977
2564
  } else {
978
- onboardingActions.style.display = 'none';
979
2565
  discoveryHint.style.display = 'none';
980
2566
  }
981
2567
 
982
2568
  if (!d.length) {
983
- document.getElementById('agentsCountHint').textContent = '0 agents';
984
- document.getElementById('agentsWrap').innerHTML = '<div class="label">No discovered agents yet.</div>';
2569
+ document.getElementById('agentsCountHint').textContent = t('overview.agentsZero');
2570
+ document.getElementById('agentsWrap').innerHTML = `<div class="label">${t('overview.noDiscoveredAgents')}</div>`;
985
2571
  } else {
986
2572
  document.getElementById('agentsCountHint').textContent = onlyShowOnline
987
- ? `${d.length}/${all.length} agents (online filter)`
988
- : `${d.length} agents discovered`;
2573
+ ? t('overview.agentsOnlineFilter', { shown: String(d.length), total: String(all.length) })
2574
+ : t('overview.agentsDiscovered', { count: String(d.length) });
989
2575
  document.getElementById('agentsWrap').innerHTML = `
990
2576
  <table class="table">
991
- <thead><tr><th>Name</th><th>Agent ID</th><th>Status</th><th>Updated</th></tr></thead>
2577
+ <thead><tr><th>${t('overview.tableName')}</th><th>${t('overview.tableAgentId')}</th><th>${t('overview.tableStatus')}</th><th>${t('overview.tableUpdated')}</th></tr></thead>
992
2578
  <tbody>
993
2579
  ${d.map((a) => `
994
2580
  <tr>
995
- <td>${a.display_name || '(unnamed)'}</td>
2581
+ <td>${a.display_name || t('overview.unnamed')}</td>
996
2582
  <td class="mono">${shortId(a.agent_id)}</td>
997
- <td class="${a.online ? 'online' : 'offline'}">${a.online ? 'online' : 'offline'}</td>
2583
+ <td class="${a.online ? 'online' : 'offline'}">${a.online ? t('overview.online') : t('overview.offline')}</td>
998
2584
  <td>${ago(a.updated_at)}</td>
999
2585
  </tr>
1000
2586
  `).join('')}
@@ -1025,10 +2611,10 @@
1025
2611
  const hiddenFields = summary?.public_visibility?.hidden_fields || [];
1026
2612
  const visible = visibleFields.join(', ') || '-';
1027
2613
  const hidden = hiddenFields.join(', ') || '-';
1028
- document.getElementById('publicVisibilityHint').textContent = `Visible fields: ${visible} | Hidden fields: ${hidden}`;
2614
+ document.getElementById('publicVisibilityHint').textContent = t('preview.visibleFields', { visible, hidden });
1029
2615
  const rows = [
1030
- ...visibleFields.map((field) => `[visible] ${field}`),
1031
- ...hiddenFields.map((field) => `[hidden] ${field}`),
2616
+ ...visibleFields.map((field) => t('preview.visible', { field })),
2617
+ ...hiddenFields.map((field) => t('preview.hidden', { field })),
1032
2618
  ];
1033
2619
  document.getElementById('publicVisibilityList').textContent = rows.length ? rows.join('\n') : '-';
1034
2620
  }
@@ -1045,31 +2631,41 @@
1045
2631
  const d = s.adapter_discovery_stats || {};
1046
2632
  const dx = s.adapter_diagnostics_summary || {};
1047
2633
  const ac = s.adapter_config || c.adapter_config || {};
2634
+ document.getElementById('heroAdapter').textContent = c.adapter || '-';
2635
+ document.getElementById('heroRelay').textContent = dx.signaling_url || '-';
2636
+ document.getElementById('heroRoom').textContent = dx.room || '-';
1048
2637
 
1049
2638
  document.getElementById('pillAdapter').textContent = `adapter: ${c.adapter}`;
1050
- document.getElementById('sideMeta').innerHTML = `adapter: ${c.adapter}<br/>namespace: ${c.namespace || '-'}<br/>port: ${c.port ?? '-'}`;
1051
-
1052
2639
  document.getElementById('networkCards').innerHTML = [
1053
- ['Adapter', c.adapter],
1054
- ['Namespace', c.namespace || '-'],
1055
- ['Port', c.port ?? '-'],
1056
- ['Started', ac.started === true ? 'yes' : ac.started === false ? 'no' : '-'],
1057
- ['Transport State', ac.transport?.state || '-'],
1058
- ['Signaling URL', dx.signaling_url || '-'],
1059
- ['Signaling Endpoints', (dx.signaling_endpoints || []).length || 0],
1060
- ['WebRTC Room', dx.room || '-'],
1061
- ['Bootstrap Sources', (dx.bootstrap_sources || []).length || 0],
1062
- ['Seed Peers', dx.seed_peers_count ?? 0],
1063
- ['Discovery Events', dx.discovery_events_total ?? 0],
1064
- ['Recv', msg.received_total ?? 0],
1065
- ['Sent', msg.broadcast_total ?? 0],
1066
- ['Peers', p.total ?? 0],
1067
- ['Online Peers', p.online ?? 0],
1068
- ['Active WebRTC Peers', dx.active_webrtc_peers ?? '-'],
1069
- ['Reconnect Attempts', dx.reconnect_attempts_total ?? '-'],
1070
- ['Last Inbound', ago(msg.last_message_at)],
1071
- ['Last Outbound', ago(msg.last_broadcast_at)],
2640
+ [t('labels.adapter'), c.adapter],
2641
+ [t('labels.namespace'), c.namespace || '-'],
2642
+ [t('labels.port'), c.port ?? '-'],
2643
+ [t('network.started'), ac.started === true ? t('common.yes') : ac.started === false ? t('common.no') : '-'],
2644
+ [t('network.transportState'), ac.transport?.state || '-'],
2645
+ [t('network.signalingUrl'), dx.signaling_url || '-'],
2646
+ [t('network.signalingEndpoints'), (dx.signaling_endpoints || []).length || 0],
2647
+ [t('network.webrtcRoom'), dx.room || '-'],
2648
+ [t('network.bootstrapSources'), (dx.bootstrap_sources || []).length || 0],
2649
+ [t('network.seedPeers'), dx.seed_peers_count ?? 0],
2650
+ [t('network.discoveryEvents'), dx.discovery_events_total ?? 0],
2651
+ [t('network.recv'), msg.received_total ?? 0],
2652
+ [t('network.sent'), msg.broadcast_total ?? 0],
2653
+ [t('network.peers'), p.total ?? 0],
2654
+ [t('network.onlinePeers'), p.online ?? 0],
2655
+ [t('network.activeWebrtcPeers'), dx.active_webrtc_peers ?? '-'],
2656
+ [t('network.reconnectAttempts'), dx.reconnect_attempts_total ?? '-'],
2657
+ [t('network.lastInbound'), ago(msg.last_message_at)],
2658
+ [t('network.lastOutbound'), ago(msg.last_broadcast_at)],
1072
2659
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
2660
+ document.getElementById('networkSummaryList').innerHTML = [
2661
+ [t('network.relayHealth'), dx.last_error ? t('network.degraded') : t('network.connected')],
2662
+ [t('network.currentRelay'), dx.signaling_url || '-'],
2663
+ [t('network.currentRoom'), dx.room || '-'],
2664
+ [t('network.lastJoin'), ago(dx.last_join_at)],
2665
+ [t('network.lastPoll'), ago(dx.last_poll_at)],
2666
+ [t('network.lastPublish'), ago(dx.last_publish_at)],
2667
+ [t('network.lastError'), dx.last_error || t('network.none')],
2668
+ ].map(([k, v]) => `<div class="summary-item"><div class="label">${k}</div><div class="mono">${v}</div></div>`).join('');
1073
2669
 
1074
2670
  const comp = c.components || {};
1075
2671
  const lim = c.limits || {};
@@ -1097,6 +2693,12 @@
1097
2693
  `discovery_heartbeat_send_errors: ${d.heartbeat_send_errors ?? '-'}`,
1098
2694
  `signaling_messages_sent_total: ${dx.signaling_messages_sent_total ?? '-'}`,
1099
2695
  `signaling_messages_received_total: ${dx.signaling_messages_received_total ?? '-'}`,
2696
+ `last_join_at: ${dx.last_join_at ? new Date(dx.last_join_at).toISOString() : '-'}`,
2697
+ `last_poll_at: ${dx.last_poll_at ? new Date(dx.last_poll_at).toISOString() : '-'}`,
2698
+ `last_publish_at: ${dx.last_publish_at ? new Date(dx.last_publish_at).toISOString() : '-'}`,
2699
+ `last_peer_refresh_at: ${dx.last_peer_refresh_at ? new Date(dx.last_peer_refresh_at).toISOString() : '-'}`,
2700
+ `last_error_at: ${dx.last_error_at ? new Date(dx.last_error_at).toISOString() : '-'}`,
2701
+ `last_error: ${dx.last_error || '-'}`,
1100
2702
  `signaling_endpoints: ${Array.isArray(dx.signaling_endpoints) ? dx.signaling_endpoints.join(', ') : '-'}`,
1101
2703
  `bootstrap_sources: ${Array.isArray(dx.bootstrap_sources) ? dx.bootstrap_sources.join(', ') : '-'}`,
1102
2704
  `seed_peers_count: ${dx.seed_peers_count ?? '-'}`,
@@ -1121,25 +2723,28 @@
1121
2723
  const summary = peers.diagnostics_summary || {};
1122
2724
 
1123
2725
  document.getElementById('peerCards').innerHTML = [
1124
- ['Total', peers.total || 0],
1125
- ['Online', peers.online || 0],
1126
- ['Stale', peers.stale || 0],
1127
- ['Namespace', peers.namespace || '-'],
1128
- ['Signaling URL', summary.signaling_url || '-'],
1129
- ['Signaling Endpoints', (summary.signaling_endpoints || []).length || 0],
1130
- ['Room', summary.room || '-'],
1131
- ['Bootstrap Sources', (summary.bootstrap_sources || []).length || 0],
1132
- ['Seed Peers', summary.seed_peers_count ?? 0],
1133
- ['Discovery Events', summary.discovery_events_total ?? 0],
1134
- ['Active WebRTC Peers', summary.active_webrtc_peers ?? '-'],
1135
- ['Observe Calls', ds.observe_calls || 0],
1136
- ['Heartbeats', ds.heartbeat_sent || 0],
1137
- ['Peers Added', ds.peers_added || 0],
1138
- ['Peers Removed', ds.peers_removed || 0],
2726
+ [t('network.total'), peers.total || 0],
2727
+ [t('overview.online'), peers.online || 0],
2728
+ [t('network.stale'), peers.stale || 0],
2729
+ [t('labels.namespace'), peers.namespace || '-'],
2730
+ [t('network.signalingUrl'), summary.signaling_url || '-'],
2731
+ [t('network.signalingEndpoints'), (summary.signaling_endpoints || []).length || 0],
2732
+ [t('labels.room'), summary.room || '-'],
2733
+ [t('network.lastJoin'), ago(summary.last_join_at)],
2734
+ [t('network.lastPoll'), ago(summary.last_poll_at)],
2735
+ [t('network.lastPublish'), ago(summary.last_publish_at)],
2736
+ [t('network.bootstrapSources'), (summary.bootstrap_sources || []).length || 0],
2737
+ [t('network.seedPeers'), summary.seed_peers_count ?? 0],
2738
+ [t('network.discoveryEvents'), summary.discovery_events_total ?? 0],
2739
+ [t('network.activeWebrtcPeers'), summary.active_webrtc_peers ?? '-'],
2740
+ [t('network.observeCalls'), ds.observe_calls || 0],
2741
+ [t('network.heartbeats'), ds.heartbeat_sent || 0],
2742
+ [t('network.peersAdded'), ds.peers_added || 0],
2743
+ [t('network.peersRemoved'), ds.peers_removed || 0],
1139
2744
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
1140
2745
 
1141
2746
  if (!peers.items || !peers.items.length) {
1142
- document.getElementById('peerTableWrap').innerHTML = '<div class="label">No peers discovered yet.</div>';
2747
+ document.getElementById('peerTableWrap').innerHTML = `<div class="empty-state">${t('network.noPeersDiscovered')}</div>`;
1143
2748
  document.getElementById('peerStatsWrap').textContent = toPrettyJson({
1144
2749
  discovery_stats: ds,
1145
2750
  diagnostics_summary: summary,
@@ -1149,7 +2754,7 @@
1149
2754
 
1150
2755
  document.getElementById('peerTableWrap').innerHTML = `
1151
2756
  <table class="table">
1152
- <thead><tr><th>Peer</th><th>Status</th><th>Last Seen</th><th>Stale Since</th><th>Messages</th><th>First Seen</th><th>Meta</th></tr></thead>
2757
+ <thead><tr><th>${t('network.peer')}</th><th>${t('network.status')}</th><th>${t('network.lastSeen')}</th><th>${t('network.staleSince')}</th><th>${t('network.messages')}</th><th>${t('network.firstSeen')}</th><th>${t('network.meta')}</th></tr></thead>
1153
2758
  <tbody>
1154
2759
  ${peers.items.map((peer) => `
1155
2760
  <tr>
@@ -1178,16 +2783,16 @@
1178
2783
  const items = Array.isArray(payload.items) ? payload.items : [];
1179
2784
 
1180
2785
  document.getElementById('discoveryCards').innerHTML = [
1181
- ['Adapter', payload.adapter || '-'],
1182
- ['Namespace', payload.namespace || '-'],
1183
- ['Events Total', payload.total ?? 0],
1184
- ['Last Event', ago(payload.last_event_at)],
1185
- ['Signaling Endpoints', (payload.signaling_endpoints || []).length || 0],
1186
- ['Seed Peers', payload.seed_peers_count ?? 0],
2786
+ [t('labels.adapter'), payload.adapter || '-'],
2787
+ [t('labels.namespace'), payload.namespace || '-'],
2788
+ [t('network.eventsTotal'), payload.total ?? 0],
2789
+ [t('network.lastEvent'), ago(payload.last_event_at)],
2790
+ [t('network.signalingEndpoints'), (payload.signaling_endpoints || []).length || 0],
2791
+ [t('network.seedPeers'), payload.seed_peers_count ?? 0],
1187
2792
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
1188
2793
 
1189
2794
  if (!items.length) {
1190
- document.getElementById('discoveryEventList').innerHTML = '<div class="label">No discovery events yet.</div>';
2795
+ document.getElementById('discoveryEventList').innerHTML = `<div class="empty-state">${t('network.noDiscoveryEvents')}</div>`;
1191
2796
  } else {
1192
2797
  document.getElementById('discoveryEventList').innerHTML = items
1193
2798
  .slice()
@@ -1220,55 +2825,59 @@
1220
2825
  const runtimeNetwork = runtime.resolved_network || {};
1221
2826
  const discoverable = status.discoverable === true;
1222
2827
  const mode = status.network_mode || summary.current_network_mode || network.mode || runtimeNetwork.mode || '-';
1223
- const summaryLine = status.status_line || summary.summary_line || `${summary.connected ? 'Connected to SilicaClaw' : 'Not connected to SilicaClaw'} · ${discoverable ? 'Discoverable in current mode' : 'Not discoverable in current mode'} · Using ${mode}`;
1224
- const publicDiscoveryText = status.public_enabled ? 'Public discovery enabled' : 'Public discovery disabled';
2828
+ const summaryLine = status.status_line || summary.summary_line || `${summary.connected ? t('social.connectedToSilicaClaw') : t('social.notConnectedToSilicaClaw')} · ${discoverable ? t('social.discoverableInCurrentMode') : t('social.notDiscoverableInCurrentMode')} · ${t('social.usingMode', { mode })}`;
2829
+ const publicDiscoveryText = status.public_enabled ? t('social.publicDiscoveryEnabled') : t('social.publicDiscoveryDisabled');
1225
2830
 
1226
- const namespaceText = `${status.connected_to_silicaclaw ? 'Connected to SilicaClaw' : 'Not connected'} · ${publicDiscoveryText} · mode ${mode}`;
2831
+ const namespaceText = `${status.connected_to_silicaclaw ? t('social.connectedToSilicaClaw') : t('social.notConnected')} · ${publicDiscoveryText} · ${t('social.mode')} ${mode}`;
1227
2832
  document.getElementById('socialStatusLine').textContent = summaryLine;
1228
2833
  document.getElementById('socialStatusSubline').textContent = namespaceText;
1229
2834
  const bar = document.getElementById('integrationStatusBar');
1230
2835
  bar.className = `integration-strip ${status.connected_to_silicaclaw && status.public_enabled ? 'ok' : 'warn'}`;
1231
- bar.textContent = `Connected to SilicaClaw: ${status.connected_to_silicaclaw ? 'yes' : 'no'} · Network mode: ${mode} · Public discovery: ${status.public_enabled ? 'enabled' : 'disabled'}`;
2836
+ bar.textContent = t('social.barStatus', {
2837
+ connected: status.connected_to_silicaclaw ? t('common.yes') : t('common.no'),
2838
+ mode,
2839
+ public: status.public_enabled ? t('common.on') : t('common.off'),
2840
+ });
1232
2841
  const reasons = [];
1233
- if (!status.configured && status.configured_reason) reasons.push(`Configured: ${status.configured_reason}`);
1234
- if (!status.running && status.running_reason) reasons.push(`Running: ${status.running_reason}`);
1235
- if (!status.discoverable && status.discoverable_reason) reasons.push(`Discoverable: ${status.discoverable_reason}`);
1236
- document.getElementById('socialStateHint').textContent = reasons.length ? reasons.join(' · ') : 'All integration checks passed.';
2842
+ if (!status.configured && status.configured_reason) reasons.push(t('social.configuredReason', { reason: status.configured_reason }));
2843
+ if (!status.running && status.running_reason) reasons.push(t('social.runningReason', { reason: status.running_reason }));
2844
+ if (!status.discoverable && status.discoverable_reason) reasons.push(t('social.discoverableReasonFull', { reason: status.discoverable_reason }));
2845
+ document.getElementById('socialStateHint').textContent = reasons.length ? reasons.join(' · ') : t('hints.allIntegrationChecksPassed');
1237
2846
  const modeSelect = document.getElementById('socialModeSelect');
1238
2847
  if (modeSelect && mode !== '-') {
1239
2848
  modeSelect.value = mode;
1240
2849
  }
1241
2850
 
1242
2851
  document.getElementById('socialPrimaryCards').innerHTML = [
1243
- ['Configured', status.configured ? 'yes' : 'no'],
1244
- ['Running', status.running ? 'yes' : 'no'],
1245
- ['Discoverable', discoverable ? 'yes' : 'no'],
1246
- ['Public discovery', status.public_enabled ? 'enabled' : 'disabled'],
1247
- ['network mode', mode],
1248
- ['connected', status.connected_to_silicaclaw ? 'yes' : 'no'],
1249
- ['discoverable reason', status.discoverable_reason || '-'],
2852
+ [t('social.configured'), status.configured ? t('common.yes') : t('common.no')],
2853
+ [t('social.running'), status.running ? t('common.yes') : t('common.no')],
2854
+ [t('social.discoverable'), discoverable ? t('common.yes') : t('common.no')],
2855
+ [t('social.publicDiscovery'), status.public_enabled ? t('common.on') : t('common.off')],
2856
+ [t('social.networkMode'), mode],
2857
+ [t('social.connected'), status.connected_to_silicaclaw ? t('common.yes') : t('common.no')],
2858
+ [t('social.discoverableReason'), status.discoverable_reason || '-'],
1250
2859
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
1251
2860
 
1252
2861
  document.getElementById('socialIntegrationCards').innerHTML = [
1253
- ['display name', status.display_name || '(unnamed)'],
1254
- ['agent id', shortId(status.agent_id || '')],
1255
- ['social.md found', summary.social_md_found ? 'yes' : 'no'],
1256
- ['social.md source', summary.social_md_source_path || '-'],
1257
- ['runtime generated', summary.runtime_generated ? 'yes' : 'no'],
1258
- ['reuse OpenClaw identity', summary.reused_openclaw_identity ? 'yes' : 'no'],
1259
- ['mode', mode],
1260
- ['broadcast status', summary.current_broadcast_status || '-'],
2862
+ [t('social.displayName'), status.display_name || t('overview.unnamed')],
2863
+ [t('social.agentId'), shortId(status.agent_id || '')],
2864
+ [t('social.socialFound'), summary.social_md_found ? t('common.yes') : t('common.no')],
2865
+ [t('social.socialSource'), summary.social_md_source_path || '-'],
2866
+ [t('social.runtimeGenerated'), summary.runtime_generated ? t('common.yes') : t('common.no')],
2867
+ [t('social.reuseOpenClawIdentity'), summary.reused_openclaw_identity ? t('common.yes') : t('common.no')],
2868
+ [t('social.mode'), mode],
2869
+ [t('social.broadcastStatus'), summary.current_broadcast_status || '-'],
1261
2870
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
1262
2871
 
1263
2872
  document.getElementById('socialAdvancedCards').innerHTML = [
1264
- ['adapter', runtimeNetwork.adapter || summary.current_adapter || '-'],
1265
- ['namespace', runtimeNetwork.namespace || summary.current_namespace || '-'],
1266
- ['room', runtimeNetwork.room || network.room || '-'],
1267
- ['signaling endpoints', (runtimeNetwork.signaling_urls || []).length || 0],
1268
- ['bootstrap sources', (runtimeNetwork.bootstrap_sources || []).length || 0],
1269
- ['seed peers', (runtimeNetwork.seed_peers || []).length || 0],
1270
- ['bootstrap hints', (runtimeNetwork.bootstrap_hints || []).length || 0],
1271
- ['restart required', social.network_requires_restart ? 'yes' : 'no'],
2873
+ [t('labels.adapter'), runtimeNetwork.adapter || summary.current_adapter || '-'],
2874
+ [t('social.namespace'), runtimeNetwork.namespace || summary.current_namespace || '-'],
2875
+ [t('labels.room'), runtimeNetwork.room || network.room || '-'],
2876
+ [t('network.signalingEndpoints'), (runtimeNetwork.signaling_urls || []).length || 0],
2877
+ [t('network.bootstrapSources'), (runtimeNetwork.bootstrap_sources || []).length || 0],
2878
+ [t('network.seedPeers'), (runtimeNetwork.seed_peers || []).length || 0],
2879
+ [t('social.bootstrapHints'), (runtimeNetwork.bootstrap_hints || []).length || 0],
2880
+ [t('social.restartRequired'), social.network_requires_restart ? t('common.yes') : t('common.no')],
1272
2881
  ].map(([k,v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join('');
1273
2882
  document.getElementById('socialAdvancedWrap').textContent = toPrettyJson({
1274
2883
  runtime_network: runtimeNetwork,
@@ -1295,14 +2904,14 @@
1295
2904
  function renderLogs() {
1296
2905
  const el = document.getElementById('logList');
1297
2906
  if (!logsCache.length) {
1298
- el.innerHTML = '<div class="label">No logs yet.</div>';
2907
+ el.innerHTML = `<div class="empty-state">${t('network.noLogsYet')}</div>`;
1299
2908
  return;
1300
2909
  }
1301
2910
  const filtered = logLevelFilter === 'all'
1302
2911
  ? logsCache
1303
2912
  : logsCache.filter((l) => String(l.level || '').toLowerCase() === logLevelFilter);
1304
2913
  if (!filtered.length) {
1305
- el.innerHTML = `<div class="label">No ${logLevelFilter} logs.</div>`;
2914
+ el.innerHTML = `<div class="empty-state">${t('network.noLogsForLevel', { level: logLevelFilter })}</div>`;
1306
2915
  return;
1307
2916
  }
1308
2917
  el.innerHTML = filtered.map((l) => `
@@ -1320,34 +2929,81 @@
1320
2929
 
1321
2930
  async function refreshAll() {
1322
2931
  try {
1323
- const tasks = [refreshOverview(), refreshNetwork(), refreshPeers(), refreshDiscovery(), refreshSocial(), refreshLogs(), refreshPublicProfilePreview()];
2932
+ const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshPublicProfilePreview()];
2933
+ if (activeTab === 'network') {
2934
+ tasks.push(refreshPeers(), refreshDiscovery(), refreshLogs());
2935
+ }
1324
2936
  const shouldRefreshProfile = !(activeTab === 'profile' && profileDirty);
1325
2937
  if (shouldRefreshProfile) {
1326
2938
  tasks.push(refreshProfile());
1327
2939
  }
1328
2940
  await Promise.all(tasks);
1329
2941
  } catch (e) {
1330
- setFeedback('networkFeedback', e instanceof Error ? e.message : 'Unknown error', 'error');
2942
+ setFeedback('networkFeedback', e instanceof Error ? e.message : t('common.unknownError'), 'error');
1331
2943
  }
1332
2944
  }
1333
2945
 
1334
2946
  document.querySelectorAll('.tab').forEach((btn) => {
1335
2947
  btn.addEventListener('click', () => switchTab(btn.dataset.tab));
1336
2948
  });
1337
- document.getElementById('themeDarkBtn').addEventListener('click', () => applyTheme('dark'));
1338
- document.getElementById('themeLightBtn').addEventListener('click', () => applyTheme('light'));
2949
+ document.getElementById('sidebarToggleBtn').addEventListener('click', () => {
2950
+ const shell = document.getElementById('appShell');
2951
+ const next = !shell.classList.contains('nav-collapsed');
2952
+ shell.classList.toggle('nav-collapsed', next);
2953
+ const btn = document.getElementById('sidebarToggleBtn');
2954
+ const icon = btn.querySelector('.nav-collapse-toggle__icon');
2955
+ btn.classList.toggle('active', next);
2956
+ btn.title = next ? t('labels.expandSidebar') : t('labels.collapseSidebar');
2957
+ btn.setAttribute('aria-label', btn.title);
2958
+ if (icon) {
2959
+ icon.innerHTML = next
2960
+ ? `
2961
+ <svg viewBox="0 0 24 24">
2962
+ <rect x="3" y="3" width="18" height="18" rx="2"></rect>
2963
+ <path d="M9 3v18"></path>
2964
+ <path d="M14 10l3 2-3 2"></path>
2965
+ </svg>
2966
+ `
2967
+ : `
2968
+ <svg viewBox="0 0 24 24">
2969
+ <rect x="3" y="3" width="18" height="18" rx="2"></rect>
2970
+ <path d="M9 3v18"></path>
2971
+ <path d="M16 10l-3 2 3 2"></path>
2972
+ </svg>
2973
+ `;
2974
+ }
2975
+ });
2976
+ document.getElementById('focusModeBtn').addEventListener('click', () => {
2977
+ const shell = document.getElementById('appShell');
2978
+ const next = !shell.classList.contains('focus-mode');
2979
+ shell.classList.toggle('focus-mode', next);
2980
+ const btn = document.getElementById('focusModeBtn');
2981
+ btn.classList.toggle('active', next);
2982
+ btn.title = next ? t('labels.exitFocusMode') : t('labels.toggleFocusMode');
2983
+ btn.setAttribute('aria-label', btn.title);
2984
+ });
2985
+ document.querySelectorAll('[data-theme-choice]').forEach((btn) => {
2986
+ btn.addEventListener('click', () => {
2987
+ applyTheme(btn.dataset.themeChoice || 'dark');
2988
+ });
2989
+ });
2990
+ document.getElementById('overviewBroadcastNowBtn').addEventListener('click', () => {
2991
+ document.getElementById('broadcastNowBtn').click();
2992
+ });
2993
+ document.getElementById('overviewGoProfileBtn').addEventListener('click', () => switchTab('profile'));
2994
+ document.getElementById('overviewGoNetworkBtn').addEventListener('click', () => switchTab('network'));
1339
2995
 
1340
2996
  document.getElementById('profileForm').addEventListener('submit', async (event) => {
1341
2997
  event.preventDefault();
1342
2998
  const f = event.currentTarget;
1343
2999
  const result = validateProfileForm(f);
1344
3000
  if (!result.ok) {
1345
- setFeedback('profileFeedback', 'Please fix validation errors before saving.', 'warn');
3001
+ setFeedback('profileFeedback', t('feedback.fixValidation'), 'warn');
1346
3002
  return;
1347
3003
  }
1348
3004
 
1349
3005
  const tags = result.tags;
1350
- setFeedback('profileFeedback', 'Saving profile...');
3006
+ setFeedback('profileFeedback', t('feedback.savingProfile'));
1351
3007
  setSaveBusy(true);
1352
3008
  try {
1353
3009
  const r = await api('/api/profile', { method: 'PUT', body: JSON.stringify({
@@ -1357,12 +3013,12 @@
1357
3013
  avatar_url: field(f, 'avatar_url').value,
1358
3014
  public_enabled: !!field(f, 'public_enabled').checked,
1359
3015
  })});
1360
- setFeedback('profileFeedback', r.meta?.message || 'Saved.');
1361
- toast('Profile saved');
3016
+ setFeedback('profileFeedback', r.meta?.message || t('common.saved'));
3017
+ toast(t('feedback.profileSaved'));
1362
3018
  field(f, 'tags').value = normalizeTagsInput(field(f, 'tags').value);
1363
3019
  await refreshAll();
1364
3020
  } catch (e) {
1365
- setFeedback('profileFeedback', e instanceof Error ? e.message : 'Failed', 'error');
3021
+ setFeedback('profileFeedback', e instanceof Error ? e.message : t('feedback.failed'), 'error');
1366
3022
  } finally {
1367
3023
  setSaveBusy(false);
1368
3024
  }
@@ -1370,7 +3026,7 @@
1370
3026
 
1371
3027
  document.getElementById('refreshProfileBtn').addEventListener('click', async () => {
1372
3028
  await refreshProfile();
1373
- toast('Profile form reloaded');
3029
+ toast(t('feedback.profileReloaded'));
1374
3030
  });
1375
3031
  const profileFormEl = document.getElementById('profileForm');
1376
3032
  ['input', 'change'].forEach((evt) => {
@@ -1405,27 +3061,21 @@
1405
3061
  setFeedback('networkFeedback', text);
1406
3062
  try {
1407
3063
  const r = await api(path, { method: 'POST' });
1408
- setFeedback('networkFeedback', r.meta?.message || 'Done', level);
1409
- toast(r.meta?.message || 'Done');
3064
+ setFeedback('networkFeedback', r.meta?.message || t('common.done'), level);
3065
+ toast(r.meta?.message || t('common.done'));
1410
3066
  await refreshAll();
1411
3067
  } catch (e) {
1412
- setFeedback('networkFeedback', e instanceof Error ? e.message : 'Failed', 'error');
3068
+ setFeedback('networkFeedback', e instanceof Error ? e.message : t('feedback.failed'), 'error');
1413
3069
  }
1414
3070
  }
1415
- document.getElementById('startBroadcastBtn').addEventListener('click', () => runAction('/api/broadcast/start', 'Starting...'));
1416
- document.getElementById('stopBroadcastBtn').addEventListener('click', () => runAction('/api/broadcast/stop', 'Stopping...', 'warn'));
1417
- document.getElementById('broadcastNowBtn').addEventListener('click', () => runAction('/api/broadcast/now', 'Broadcasting now...'));
1418
- document.getElementById('refreshCacheBtn').addEventListener('click', () => runAction('/api/cache/refresh', 'Refreshing cache...'));
1419
- document.getElementById('clearCacheBtn').addEventListener('click', async () => {
1420
- const ok = window.confirm('Clear discovered cache now?\n\nThis removes discovered peer profiles/presence/index references and keeps your self profile.');
1421
- if (!ok) return;
1422
- await runAction('/api/cache/clear', 'Clearing discovered cache...', 'warn');
1423
- });
3071
+ document.getElementById('startBroadcastBtn').addEventListener('click', () => runAction('/api/broadcast/start', t('actions.startBroadcast')));
3072
+ document.getElementById('stopBroadcastBtn').addEventListener('click', () => runAction('/api/broadcast/stop', t('actions.stopBroadcast'), 'warn'));
3073
+ document.getElementById('broadcastNowBtn').addEventListener('click', () => runAction('/api/broadcast/now', t('actions.broadcastNow')));
1424
3074
  document.getElementById('quickGlobalPreviewBtn').addEventListener('click', async () => {
1425
- const currentSignaling = window.prompt('Signaling URL (publicly reachable):', 'http://localhost:4510');
3075
+ const currentSignaling = window.prompt(t('feedback.promptSignalingUrl'), 'http://localhost:4510');
1426
3076
  if (!currentSignaling) return;
1427
- const room = window.prompt('Room:', 'silicaclaw-demo') || 'silicaclaw-demo';
1428
- setFeedback('networkFeedback', 'Enabling cross-network preview...');
3077
+ const room = window.prompt(t('feedback.promptRoom'), 'silicaclaw-global-preview') || 'silicaclaw-global-preview';
3078
+ setFeedback('networkFeedback', t('feedback.crossPreviewEnabling'));
1429
3079
  try {
1430
3080
  const result = await api('/api/network/quick-connect-global-preview', {
1431
3081
  method: 'POST',
@@ -1434,11 +3084,11 @@
1434
3084
  room: room.trim(),
1435
3085
  }),
1436
3086
  });
1437
- setFeedback('networkFeedback', result.meta?.message || 'Cross-network preview enabled');
1438
- toast('Cross-network preview enabled');
3087
+ setFeedback('networkFeedback', result.meta?.message || t('feedback.crossPreviewEnabled'));
3088
+ toast(t('feedback.crossPreviewEnabled'));
1439
3089
  await refreshAll();
1440
3090
  } catch (e) {
1441
- setFeedback('networkFeedback', e instanceof Error ? e.message : 'Enable cross-network preview failed', 'error');
3091
+ setFeedback('networkFeedback', e instanceof Error ? e.message : t('feedback.enableCrossPreviewFailed'), 'error');
1442
3092
  }
1443
3093
  });
1444
3094
  document.getElementById('onlyOnlineToggle').addEventListener('change', async (event) => {
@@ -1447,104 +3097,50 @@
1447
3097
  });
1448
3098
  document.getElementById('refreshLogsBtn').addEventListener('click', async () => {
1449
3099
  await refreshLogs();
1450
- toast('Logs refreshed');
1451
- });
1452
- document.getElementById('enablePublicDiscoveryBtn').addEventListener('click', async () => {
1453
- const ok = window.confirm(
1454
- 'Enable public discovery now?\n\n' +
1455
- '- Only profile/presence are shared.\n' +
1456
- '- Private files are not shared.\n' +
1457
- '- Chat and remote control are not enabled.\n\n' +
1458
- 'This updates runtime state and does not overwrite social.md.'
1459
- );
1460
- if (!ok) return;
1461
- setFeedback('networkFeedback', 'Enabling public discovery...');
1462
- try {
1463
- const res = await api('/api/public-discovery/enable', { method: 'POST' });
1464
- setFeedback('networkFeedback', res.meta?.message || 'Public discovery enabled.');
1465
- toast('Public discovery enabled');
1466
- await refreshAll();
1467
- } catch (e) {
1468
- setFeedback('networkFeedback', e instanceof Error ? e.message : 'Enable public discovery failed', 'error');
1469
- }
1470
- });
1471
- document.getElementById('disablePublicDiscoveryBtn').addEventListener('click', async () => {
1472
- const ok = window.confirm('Disable public discovery now? This updates runtime state and does not overwrite social.md.');
1473
- if (!ok) return;
1474
- setFeedback('networkFeedback', 'Disabling public discovery...');
1475
- try {
1476
- const res = await api('/api/public-discovery/disable', { method: 'POST' });
1477
- setFeedback('networkFeedback', res.meta?.message || 'Public discovery disabled.');
1478
- toast('Public discovery disabled');
1479
- await refreshAll();
1480
- } catch (e) {
1481
- setFeedback('networkFeedback', e instanceof Error ? e.message : 'Disable public discovery failed', 'error');
1482
- }
1483
- });
1484
- document.getElementById('socialReloadBtn').addEventListener('click', async () => {
1485
- setFeedback('socialFeedback', 'Reloading social config...');
1486
- try {
1487
- const res = await api('/api/social/reload', { method: 'POST' });
1488
- setFeedback('socialFeedback', res.meta?.message || 'Reloaded');
1489
- toast('Social config reloaded');
1490
- await refreshAll();
1491
- } catch (e) {
1492
- setFeedback('socialFeedback', e instanceof Error ? e.message : 'Reload failed', 'error');
1493
- }
1494
- });
1495
- document.getElementById('socialGenerateBtn').addEventListener('click', async () => {
1496
- setFeedback('socialFeedback', 'Generating default social.md...');
1497
- try {
1498
- const res = await api('/api/social/generate-default', { method: 'POST' });
1499
- setFeedback('socialFeedback', res.meta?.message || 'Done');
1500
- toast(res.meta?.message || 'Default social.md ready');
1501
- await refreshAll();
1502
- } catch (e) {
1503
- setFeedback('socialFeedback', e instanceof Error ? e.message : 'Generate failed', 'error');
1504
- }
3100
+ toast(t('feedback.logsRefreshed'));
1505
3101
  });
1506
3102
  document.getElementById('socialExportBtn').addEventListener('click', async () => {
1507
- setFeedback('socialFeedback', 'Exporting template...');
3103
+ setFeedback('socialFeedback', t('feedback.exportingTemplate'));
1508
3104
  try {
1509
3105
  await exportSocialTemplate();
1510
- setFeedback('socialFeedback', 'Template exported from current runtime.');
1511
- toast('Template exported');
3106
+ setFeedback('socialFeedback', t('feedback.templateExported'));
3107
+ toast(t('feedback.templateExported'));
1512
3108
  } catch (e) {
1513
- setFeedback('socialFeedback', e instanceof Error ? e.message : 'Export failed', 'error');
3109
+ setFeedback('socialFeedback', e instanceof Error ? e.message : t('feedback.exportFailed'), 'error');
1514
3110
  }
1515
3111
  });
1516
3112
  document.getElementById('socialModeApplyBtn').addEventListener('click', async () => {
1517
3113
  const mode = document.getElementById('socialModeSelect').value;
1518
- setFeedback('socialFeedback', `Applying runtime mode: ${mode}...`);
3114
+ setFeedback('socialFeedback', t('feedback.runtimeModeApplying', { mode }));
1519
3115
  try {
1520
3116
  const res = await api('/api/social/runtime-mode', {
1521
3117
  method: 'POST',
1522
3118
  body: JSON.stringify({ mode }),
1523
3119
  });
1524
- setFeedback('socialFeedback', res.meta?.message || 'Runtime mode updated.');
1525
- toast(`Runtime mode: ${mode}`);
3120
+ setFeedback('socialFeedback', res.meta?.message || t('feedback.runtimeUpdated'));
3121
+ toast(t('feedback.runtimeMode', { mode }));
1526
3122
  await refreshAll();
1527
3123
  } catch (e) {
1528
- setFeedback('socialFeedback', e instanceof Error ? e.message : 'Runtime mode update failed', 'error');
3124
+ setFeedback('socialFeedback', e instanceof Error ? e.message : t('feedback.failed'), 'error');
1529
3125
  }
1530
3126
  });
1531
3127
  document.getElementById('socialCopyBtn').addEventListener('click', async () => {
1532
- setFeedback('socialFeedback', 'Copying template...');
3128
+ setFeedback('socialFeedback', t('feedback.copyingTemplate'));
1533
3129
  const btn = document.getElementById('socialCopyBtn');
1534
3130
  try {
1535
3131
  if (!socialTemplate) {
1536
3132
  await exportSocialTemplate();
1537
3133
  }
1538
3134
  await navigator.clipboard.writeText(socialTemplate);
1539
- setFeedback('socialFeedback', 'Template copied to clipboard.');
1540
- toast('Copied');
1541
- flashButton(btn, 'Copied');
3135
+ setFeedback('socialFeedback', t('feedback.templateCopied'));
3136
+ toast(t('common.copied'));
3137
+ flashButton(btn, t('common.copied'));
1542
3138
  } catch (e) {
1543
- setFeedback('socialFeedback', e instanceof Error ? e.message : 'Copy failed', 'error');
3139
+ setFeedback('socialFeedback', e instanceof Error ? e.message : t('feedback.copyFailed'), 'error');
1544
3140
  }
1545
3141
  });
1546
3142
  document.getElementById('socialDownloadBtn').addEventListener('click', async () => {
1547
- setFeedback('socialFeedback', 'Preparing download...');
3143
+ setFeedback('socialFeedback', t('feedback.preparingDownload'));
1548
3144
  try {
1549
3145
  const payload = await exportSocialTemplate();
1550
3146
  const filename = String(payload.filename || 'social.md');
@@ -1557,10 +3153,10 @@
1557
3153
  anchor.click();
1558
3154
  anchor.remove();
1559
3155
  URL.revokeObjectURL(url);
1560
- setFeedback('socialFeedback', `Downloaded ${filename}.`);
1561
- toast('Downloaded');
3156
+ setFeedback('socialFeedback', t('feedback.downloaded', { filename }));
3157
+ toast(t('feedback.downloaded', { filename }));
1562
3158
  } catch (e) {
1563
- setFeedback('socialFeedback', e instanceof Error ? e.message : 'Download failed', 'error');
3159
+ setFeedback('socialFeedback', e instanceof Error ? e.message : t('feedback.downloadFailed'), 'error');
1564
3160
  }
1565
3161
  });
1566
3162
  document.getElementById('copyPublicProfilePreviewBtn').addEventListener('click', async () => {
@@ -1568,10 +3164,10 @@
1568
3164
  try {
1569
3165
  const summary = (await api('/api/public-profile/preview')).data || null;
1570
3166
  await navigator.clipboard.writeText(toPrettyJson(summary));
1571
- toast('Public profile preview copied');
1572
- flashButton(btn, 'Copied');
3167
+ toast(t('actions.copyPublicProfilePreview'));
3168
+ flashButton(btn, t('common.copied'));
1573
3169
  } catch (e) {
1574
- setFeedback('profileFeedback', e instanceof Error ? e.message : 'Copy preview failed', 'error');
3170
+ setFeedback('profileFeedback', e instanceof Error ? e.message : t('feedback.copyPreviewFailed'), 'error');
1575
3171
  }
1576
3172
  });
1577
3173
  document.getElementById('logLevelFilter').addEventListener('change', (event) => {
@@ -1593,6 +3189,20 @@
1593
3189
  });
1594
3190
  })();
1595
3191
 
3192
+ if (window.matchMedia) {
3193
+ const media = window.matchMedia('(prefers-color-scheme: light)');
3194
+ const handleThemeMedia = () => {
3195
+ if ((localStorage.getItem('silicaclaw_theme_mode') || 'dark') === 'system') {
3196
+ applyTheme('system');
3197
+ }
3198
+ };
3199
+ if (typeof media.addEventListener === 'function') {
3200
+ media.addEventListener('change', handleThemeMedia);
3201
+ } else if (typeof media.addListener === 'function') {
3202
+ media.addListener(handleThemeMedia);
3203
+ }
3204
+ }
3205
+
1596
3206
  applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
1597
3207
  refreshAll();
1598
3208
  exportSocialTemplate().catch(() => {});