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

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