File size: 32,861 Bytes
3613e77
 
 
 
 
 
 
 
 
cdff6a6
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeeb56e
 
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
cdff6a6
 
 
 
 
3613e77
 
 
 
 
cdff6a6
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
cdff6a6
 
3613e77
cdff6a6
 
3613e77
cdff6a6
3613e77
cdff6a6
3613e77
cdff6a6
 
 
 
 
 
 
 
 
 
3613e77
 
cdff6a6
 
 
 
 
 
 
 
eeeb56e
 
cdff6a6
 
 
 
 
 
 
 
 
 
 
 
 
3613e77
cdff6a6
3613e77
0c571a4
 
 
 
 
 
 
 
 
 
 
 
3613e77
 
cdff6a6
 
3613e77
 
cdff6a6
 
 
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cdff6a6
 
 
 
 
 
 
 
 
 
 
3613e77
 
cdff6a6
3613e77
cdff6a6
3613e77
cdff6a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3613e77
 
 
 
cdff6a6
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
cdff6a6
 
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825672c
3613e77
 
 
 
 
 
 
 
 
 
 
0c571a4
3613e77
 
eeeb56e
 
3613e77
 
 
 
 
cdff6a6
 
 
3613e77
 
 
cdff6a6
 
 
 
 
 
 
 
 
 
3613e77
 
 
825672c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ffe24e3
825672c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ffe24e3
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeeb56e
 
 
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0c571a4
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
0c571a4
 
 
3613e77
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/* SetVL node graph 复核台
 *
 * 视觉系统沿用上一代 pair 复核台:同一套 topographic/asphalt 配色、
 * DIN Condensed 标题、三栏骨架。变的只是数据模型——从「一对节点的关系」
 * 变成「一张图上的节点与边」。
 */
:root {
  --ink: #0d1211;
  --asphalt: #141a18;
  --rail-w: 320px;          /* 左栏:比原来的 294px 宽,场景 id 少截断 */
  --inspect-w: 340px;
  --topbar-h: 46px;         /* 原来 70px 顶栏 + 41px 进度条 = 111px,省出 65px */
  --panel: #1a211f;
  --panel-raised: #202926;
  --line: #34403c;
  --line-soft: #29332f;
  --paper: #e8e5da;
  --muted: #9aa69f;
  --faint: #66736d;
  --signal: #f1a43c;
  --signal-dark: #a86112;
  --cyan: #68c8c0;
  --yes: #70c79b;
  --no: #e27667;
  --uncertain: #d0b87b;
  --anchor: #ef5b4d;
  --target: #3c8ee8;
  /* 边的三种来源,沿用旧版的语义配色逻辑 */
  --chain: #68c8c0;
  --intersection: #f1a43c;
  --bridge: #b8a2e0;
  /* merged_policy_0915 §1-§4 的四色。策略正文就是用蓝/绿/橙/红称呼它们的,
     配色按那个来,不再沿用旧策略的「走廊链/路口/桥接」三色。 */
  --e-corridor: #6ba8e8;          /* 蓝 §1 同走廊相邻 */
  --e-road: #68c8a0;              /* 绿 §2 路网可达 */
  --e-short: #f1a43c;             /* 橙 §3 短直连 */
  --e-merge: #e8646b;             /* 红 §4 分量合并 */
  --road: #70c79b;
  --radius: 4px;
  --shadow: 0 24px 70px rgba(0, 0, 0, .28);
  --display: "DIN Condensed", "Avenir Next Condensed", "Franklin Gothic Condensed", sans-serif;
  --body: "Avenir Next", "Helvetica Neue", sans-serif;
  --mono: "SFMono-Regular", "Roboto Mono", "Courier New", monospace;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  /* 之前 .app-shell 用 calc(100vh - 70px - 41px) 写死两条头部的高度。
     窗口一窄,顶栏换行变高,app-shell 就被挤出视口下沿,而 body 是
     overflow:hidden——底部那排 Prev/Next 直接被裁掉。改成 flex 列,
     让 app-shell 吃掉剩余高度,头部多高都不会溢出。 */
  display: flex; flex-direction: column; height: 100vh; max-height: 100vh;
  margin: 0; overflow: hidden; color: var(--paper);
  background: radial-gradient(circle at 74% 8%, rgba(87,126,111,.15), transparent 28rem), var(--ink);
  font-family: var(--body); font-size: 14px;
}
button, input, select, textarea { font: inherit; }
/* 任何带 display 的类选择器都会压过浏览器对 [hidden] 的 UA 样式
   (display:none 来自 UA 样式表,特异度最低)。这里一次性钉死,
   否则 .drawer-group / .token-row 这类 display:flex 的元素
   设了 hidden 也照样显示。 */
[hidden] { display: none !important; }
button { color: inherit; }

.topographic-grid {
  position: fixed; inset: 0; pointer-events: none; opacity: .24;
  background-image:
    linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px);
  background-size: 42px 42px;
  mask-image: linear-gradient(to bottom, black, transparent 72%);
}

/* ---------------------------------------------------------------- 顶栏 */
/* 一行搞定:品牌 / 读数 / 构成条 / 帮助。旧版拆成 topbar + progress-strip
   两行共 111px,装饰占掉的纵向空间比左侧列表还多。 */
.topbar {
  flex: 0 0 auto; height: var(--topbar-h);
  display: flex; align-items: center; gap: 15px; padding: 0 15px;
  border-bottom: 1px solid var(--line); background: rgba(20,26,24,.96);
  position: relative; z-index: 5; white-space: nowrap;
}
.brand { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
.brand-mark {
  width: 24px; height: 24px; display: grid; place-items: center;
  background: var(--signal); color: var(--ink); font: 800 10px var(--mono); letter-spacing: .04em;
}
.brand h1 {
  margin: 0; font: 600 13px var(--display); letter-spacing: .09em; text-transform: uppercase;
  display: flex; align-items: baseline; gap: 8px;
}
.brand h1 em {
  font: 600 8px var(--mono); letter-spacing: .14em; color: var(--faint);
  text-transform: uppercase; font-style: normal;
}

/* 读数:数字实、单位虚,细竖线断开,不用边框盒子 */
.readout { display: flex; align-items: baseline; gap: 6px; font: 600 10px var(--mono); min-width: 0; overflow: hidden; }
.readout > b { color: var(--paper); font: 650 13px var(--display); letter-spacing: .03em; }
.readout > span { color: var(--faint); letter-spacing: .1em; text-transform: uppercase; }
.readout > i { width: 1px; height: 13px; background: var(--line); margin: 0 4px; align-self: center; flex: 0 0 auto; }
.readout-origins { display: flex; gap: 10px; min-width: 0; overflow: hidden; }
.readout-origins em { font-style: normal; display: flex; align-items: baseline; gap: 3px; color: var(--faint); letter-spacing: .06em; }
.readout-origins b { font: 650 11px var(--mono); }
.o-corridor b { color: var(--e-corridor); } .o-road b { color: var(--e-road); }
.o-short b { color: var(--e-short); } .o-merge b { color: var(--e-merge); }

.origin-meter { flex: 1 1 auto; min-width: 50px; height: 2px; background: var(--line-soft); }
.origin-meter span { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--signal), #ffd38b); transition: width .45s ease; }
.readout-label { flex: 0 0 auto; color: var(--faint); font: 600 9px var(--mono); letter-spacing: .1em; text-transform: uppercase; }

/* 窄窗口下按重要性依次藏:构成明细 -> 构成条与标签 */
@media (max-width: 1400px) { .readout-origins { display: none; } }
@media (max-width: 1150px) { .origin-meter, .readout-label { display: none; } }

.icon-button {
  flex: 0 0 auto; margin-left: auto;
  width: 26px; height: 26px; border: 1px solid var(--line); background: transparent;
  color: var(--muted); cursor: pointer; border-radius: var(--radius); font: 700 13px var(--mono);
}
.icon-button:hover { border-color: var(--signal); color: var(--signal); }

/* 语言切换:和 SAT/MAP、LINE/ROAD 同一种分段按钮,钉在顶栏最右 */
.lang-switch {
  flex: 0 0 auto; display: flex; height: 26px;
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
}
.lang-switch button {
  background: transparent; border: 0; padding: 0 8px; cursor: pointer;
  color: var(--faint); font: 700 10px var(--mono); letter-spacing: .06em;
}
.lang-switch button:hover { color: var(--signal); }
.lang-switch button.active { background: var(--signal); color: var(--ink); cursor: default; }

/* ---------------------------------------------------------------- 三栏 */
.app-shell {
  flex: 1 1 auto; min-height: 0; overflow: hidden;
  display: grid; grid-template-columns: var(--rail-w) minmax(420px, 1fr) var(--inspect-w);
  position: relative; z-index: 2;
}
/* 窄窗口下先收右栏,再收左栏;三栏的最小可用宽度守住,避免内容被压烂 */
@media (max-width: 1500px) { :root { --rail-w: 300px; --inspect-w: 310px; } }
@media (max-width: 1280px) { :root { --rail-w: 286px; --inspect-w: 280px; } }
@media (max-width: 1100px) {
  .app-shell { grid-template-columns: var(--rail-w) minmax(360px, 1fr); }
  .inspector-panel { display: none; }
}
.queue-panel, .inspector-panel, .evidence-panel { min-height: 0; }
.queue-panel, .inspector-panel { background: rgba(20,26,24,.96); }
.queue-panel { border-right: 1px solid var(--line); display: flex; flex-direction: column; }
.inspector-panel { border-left: 1px solid var(--line); overflow-y: auto; }
.evidence-panel { overflow-y: auto; padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }

.panel-heading { padding: 14px 16px 11px; border-bottom: 1px solid var(--line); }
.panel-heading h2 { margin: 2px 0 0; font: 600 16px var(--display); letter-spacing: .05em; text-transform: uppercase; }
.section-index { margin: 0; color: var(--signal); font: 700 9px var(--mono); letter-spacing: .16em; }
.inspector-heading { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
.relation-id { color: var(--muted); font: 700 12px var(--mono); }

/* ---------------------------------------------------------------- 队列 */
.mode-switch { display: flex; border-bottom: 1px solid var(--line); }
.mode-switch button {
  flex: 1; background: transparent; border: 0; padding: 8px; cursor: pointer;
  color: var(--faint); font: 700 10px var(--mono); letter-spacing: .08em;
  border-bottom: 2px solid transparent;
}
.mode-switch button.active { color: var(--signal); border-bottom-color: var(--signal); }
.scene-item-meta { display: flex; gap: 8px; flex-wrap: wrap; }
.badge { padding: 0 4px; font: 700 9px var(--mono); border: 1px solid; }
.badge.edited { color: var(--yes); border-color: var(--yes); }
.badge.orphan { color: var(--anchor); border-color: var(--anchor); }
.badge.cost { color: var(--uncertain); border-color: var(--uncertain); }
.badge.frag { color: var(--no); border-color: var(--no); }

/* --- 筛选:三级信息层级 ------------------------------------------------
   旧版把搜索框、4 个下拉、3 个勾选框平铺成一堵墙,全部常驻、权重相同,
   既看不出主次,又吃掉了列表的纵向空间。现在:
     一级 搜索     —— 常驻,最宽
     二级 City/Sort —— 压成一行紧凑控件(实测这两个用得最多)
     三级 其余     —— 收进抽屉,已激活项用 token 回显,收起也看得见
*/
.filter-stack { padding: 9px 12px 8px; border-bottom: 1px solid var(--line); display: flex; flex-direction: column; gap: 7px; flex: 0 0 auto; }

.search-wrap { position: relative; display: flex; align-items: center; }
.search-icon { position: absolute; left: 8px; width: 13px; height: 13px; color: var(--faint); pointer-events: none; }
.search-wrap input {
  width: 100%; background: var(--ink); border: 1px solid var(--line); color: var(--paper);
  padding: 7px 9px 7px 27px; border-radius: var(--radius); font: 500 12px var(--body);
}
.search-wrap input::-webkit-search-cancel-button { filter: invert(.6); }
.search-wrap input:focus { outline: none; border-color: var(--signal); }
.search-wrap:focus-within .search-icon { color: var(--signal); }

.filter-row { display: flex; gap: 6px; align-items: flex-end; }
.ctl { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ctl > span { color: var(--faint); font: 600 8px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.ctl select, .ctl input {
  width: 100%; background: var(--ink); border: 1px solid var(--line); color: var(--paper);
  padding: 5px 6px; border-radius: var(--radius); font: 500 11px var(--body); min-width: 0;
}
.ctl select:focus, .ctl input:focus { outline: none; border-color: var(--signal); }

.filter-toggle {
  flex: 0 0 auto; display: flex; align-items: center; gap: 5px; cursor: pointer;
  background: transparent; border: 1px solid var(--line); color: var(--muted);
  padding: 5px 8px; border-radius: var(--radius); font: 600 10px var(--mono);
  letter-spacing: .06em;
}
.filter-toggle:hover { border-color: var(--signal); color: var(--signal); }
.filter-toggle svg { width: 9px; height: 6px; transition: transform .18s ease; }
.filter-toggle[aria-expanded="true"] { border-color: var(--signal); color: var(--signal); }
.filter-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.filter-count {
  min-width: 14px; height: 14px; padding: 0 3px; display: grid; place-items: center;
  background: var(--signal); color: var(--ink); font: 800 9px var(--mono); border-radius: 7px;
}

/* 已激活筛选的 token:抽屉收起时状态依然可见,点 × 单独撤销 */
.token-row { display: flex; flex-wrap: wrap; gap: 4px; }
.token {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  background: rgba(241,164,60,.1); border: 1px solid rgba(241,164,60,.4);
  color: var(--signal); padding: 2px 4px 2px 6px; border-radius: var(--radius);
  font: 600 9px var(--mono); letter-spacing: .04em;
}
.token:hover { background: rgba(241,164,60,.2); }
.token b { color: var(--paper); font-weight: 700; }
.token i { font-style: normal; opacity: .65; font-size: 11px; line-height: 1; }

.filter-drawer {
  display: flex; flex-direction: column; gap: 8px;
  padding: 9px 10px; border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: rgba(10,14,13,.45);
  animation: drawerIn .16s ease;
}
@keyframes drawerIn { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }
.drawer-group { display: flex; flex-direction: column; gap: 6px; }
.drawer-title { margin: 0; color: var(--faint); font: 700 8px var(--mono); letter-spacing: .16em; text-transform: uppercase; }
.drawer-reset {
  align-self: flex-start; background: transparent; border: 0; cursor: pointer;
  color: var(--faint); font: 600 9px var(--mono); letter-spacing: .08em;
  text-decoration: underline; text-underline-offset: 2px; padding: 0;
}
.drawer-reset:hover { color: var(--no); }

.chip-row { display: flex; flex-wrap: wrap; gap: 4px; }
.chip {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius); padding: 3px 7px;
  color: var(--muted); font: 600 9px var(--mono); letter-spacing: .04em;
  transition: border-color .14s ease, color .14s ease;
}
.chip:hover { border-color: var(--muted); color: var(--paper); }
.chip input { appearance: none; width: 7px; height: 7px; margin: 0; border: 1px solid var(--muted); border-radius: 50%; }
.chip:has(input:checked) { border-color: var(--signal); color: var(--signal); background: rgba(241,164,60,.08); }
.chip input:checked { background: var(--signal); border-color: var(--signal); }

/* 列表计数条:把「当前看到多少条」从底部挪上来,底部只留导航 */
.list-meter {
  flex: 0 0 auto; padding: 4px 13px; border-bottom: 1px solid var(--line-soft);
  color: var(--faint); font: 600 9px var(--mono); letter-spacing: .1em; text-transform: uppercase;
}

.queue-list { flex: 1; overflow-y: auto; }
.queue-item {
  width: 100%; text-align: left; border: 0; border-left: 2px solid transparent;
  border-bottom: 1px solid var(--line-soft); background: transparent;
  padding: 7px 12px; cursor: pointer; position: relative; display: block;
}
.queue-item:hover { background: rgba(255,255,255,.025); }
.queue-item.active { background: rgba(241,164,60,.075); border-left-color: var(--signal); }
.queue-item.reviewed::after { content: ""; position: absolute; right: 9px; top: 11px; width: 6px; height: 6px; border-radius: 50%; background: var(--yes); }
.queue-item.adjudication::after { content: ""; position: absolute; right: 9px; top: 11px; width: 6px; height: 6px; border-radius: 50%; background: var(--no); }
.queue-top { display: flex; align-items: baseline; gap: 8px; }
.queue-top strong { color: var(--paper); font: 650 12px var(--mono); }
.queue-top span { color: var(--muted); font-size: 11px; margin-left: auto; }
.queue-meta { margin-top: 7px; display: flex; align-items: center; gap: 5px; color: var(--faint); font: 600 9px var(--mono); flex-wrap: wrap; }
.tier-pill { padding: 1px 5px; color: var(--ink); font: 800 9px var(--mono); }
.tier-1 { background: var(--no); } .tier-2 { background: var(--signal); } .tier-3 { background: var(--uncertain); }
.sig-code { color: var(--signal); font-weight: 800; }
.queue-scene { margin-top: 4px; color: #56635c; font: 500 9px var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 场景行的第二行:城市 + 徽章同排,没徽章就只有城市,不留占位 */
.queue-sub { margin-top: 3px; display: flex; align-items: center; gap: 6px; min-width: 0; }
.queue-sub em { font-style: normal; color: #56635c; font: 600 9px var(--mono); letter-spacing: .08em; flex: 0 0 auto; }
.queue-badges { display: flex; gap: 4px; flex-wrap: nowrap; overflow: hidden; }
.queue-top strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.queue-top span { flex: 0 0 auto; }
.queue-footer { padding: 9px 13px; border-top: 1px solid var(--line); display: flex; gap: 7px; }
.quiet-button {
  flex: 1; background: transparent; border: 1px solid var(--line); color: var(--muted);
  padding: 6px; cursor: pointer; border-radius: var(--radius); font: 600 10px var(--mono);
}
.quiet-button:hover:not(:disabled) { border-color: var(--signal); color: var(--signal); }
.quiet-button:disabled { opacity: .35; cursor: default; }
kbd {
  display: inline-block; padding: 0 4px; margin: 0 2px; border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 3px; background: var(--ink);
  font: 700 9px var(--mono); color: var(--muted);
}

/* ---------------------------------------------------------------- 证据区 */
.scene-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.scene-title-row { display: flex; align-items: baseline; gap: 10px; }
.scene-title-row h2 { margin: 0; font: 600 20px var(--display); letter-spacing: .04em; }
.scene-city { color: var(--cyan); font: 700 10px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.scene-id { margin: 3px 0 0; color: var(--faint); font: 500 10px var(--mono); }
.scene-metrics { display: flex; gap: 16px; }
.scene-metrics div { text-align: right; }
.scene-metrics span { display: block; color: var(--faint); font: 600 8px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.scene-metrics strong { font: 600 15px var(--display); }

.evidence-grid { display: grid; grid-template-columns: minmax(300px, 1fr) minmax(280px, 440px); gap: 14px; align-items: start; }
.image-card { border: 1px solid var(--line); background: var(--panel); }
.image-card-head { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border-bottom: 1px solid var(--line-soft); }
.image-card-head.compact { padding: 6px 9px; }
.image-kind { color: var(--faint); font: 700 9px var(--mono); letter-spacing: .14em; text-transform: uppercase; }
.map-card-tools { margin-left: auto; display: flex; align-items: center; gap: 9px; }
.basemap-switch { display: flex; border: 1px solid var(--line); }
.basemap-switch button {
  background: transparent; border: 0; padding: 3px 9px; cursor: pointer;
  color: var(--faint); font: 700 9px var(--mono); letter-spacing: .08em;
}
.basemap-switch button.active { background: var(--signal); color: var(--ink); }
.legend { display: flex; flex-wrap: wrap; gap: 4px 9px; }   /* 英文图例更长,放不下就折行,不溢出卡片 */
.legend i { display: inline-block; width: 12px; height: 2px; margin-right: 3px; vertical-align: middle; }
.legend span { color: var(--faint); font: 600 8px var(--mono); white-space: nowrap; }
.lg-corridor { background: var(--e-corridor); } .lg-road { background: var(--e-road); }
.lg-short { background: var(--e-short); } .lg-merge { background: var(--e-merge); }
.lg-orphan { width: 7px !important; height: 7px !important; border-radius: 50%; background: var(--anchor); }

.image-stage { display: block; width: 100%; padding: 0; border: 0; background: #0b0e0d; position: relative; overflow: hidden; }
.image-stage > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.satellite-stage { aspect-ratio: 1/1; cursor: crosshair; }
/* .street-map 有 z-index:1 而 .graph-svg 没有,于是电子地图模式下
   瓦片整个盖在图上——节点和边全看不见。给 SVG 更高的层级。 */
.graph-svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; }
.street-map { display: none; position: absolute; inset: 0; z-index: 1; background: #d7d5cb; }
.satellite-stage.map-mode .street-map { display: block; }
.map-tile-layer { position: absolute; inset: 0; }
/* 瓦片按 stage 的百分比定位,与 SVG 的 400x400 viewBox 共用同一套坐标,
   缩放窗口或换 zoom 都不会和图层错位(旧版用固定 256px,必然错位)。 */
.map-tile-layer img { position: absolute; }
/* 电子地图是浅色底(OSM 标准样式接近白),走廊配色是为深色卫星图挑的
   高亮色,直接画上去会糊。整层加一圈深色投影把图元从底图上"抬"出来。 */
.satellite-stage.map-mode .graph-svg {
  filter: drop-shadow(0 0 1px rgba(8,12,11,.9)) drop-shadow(0 0 2.5px rgba(8,12,11,.55));
}
/* 电子地图底图压暗一点,让图元成为视觉主角 */
.satellite-stage.map-mode .map-tile-layer { filter: brightness(.94) saturate(.85); }
.map-controls { display: flex; border: 1px solid var(--line); }
.map-controls button { background: transparent; border: 0; width: 22px; cursor: pointer; color: var(--muted); font: 700 12px var(--mono); }
.map-controls button:hover { color: var(--signal); }
/* 街景四朝向方格 ------------------------------------------------------
 * 一个 pano 有 4 个朝向。旧版一次只显示一张、下面一排按钮来回切——
 * 要看全一个路口得点三次,还得记住刚才那张是什么样。现在四张并排,
 * 各自标出方位,一眼看完。
 *
 * 源图是 512x512 的正方形,旧版按 16:8.3 裁成信箱形,上下都切掉了。
 * 方格用 1:1,反而看得更全。
 */
.ground-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--line-soft); border-top: 1px solid var(--line-soft);
}
.ground-grid:empty { display: none; }
/* 只有一张时(旧包没有 heading 列)不必占两格 */
.ground-grid.single { grid-template-columns: 1fr; }

.ground-tile {
  position: relative; display: block; padding: 0; border: 0;
  background: #0b0e0d; cursor: zoom-in; overflow: hidden; aspect-ratio: 1/1;
}
.ground-tile > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ground-tile:hover > img { filter: brightness(1.08); }
.ground-tile:focus-visible { outline: 2px solid var(--signal); outline-offset: -2px; }

/* 方位标签压在图上:图本身已经很小,再单占一行就没地方了 */
.ground-tile b {
  position: absolute; left: 0; top: 0; padding: 2px 5px;
  background: rgba(8,12,11,.78); color: var(--paper);
  font: 700 9px var(--mono); letter-spacing: .06em;
  border-bottom-right-radius: var(--radius);
}
/* 悬停时同时点亮地图上对应的箭头(drawGraph 里加 .arrow-lit) */
.ground-tile:hover b { background: var(--signal); color: var(--ink); }
.ground-tile.lit b { background: var(--signal); color: var(--ink); }

.ground-empty {
  grid-column: 1 / -1; padding: 18px 10px; text-align: center;
  color: var(--faint); font: 600 10px var(--mono);
}

.image-caption { min-height: 34px; padding: 8px 11px; display: flex; justify-content: space-between; gap: 10px; color: var(--faint); border-top: 1px solid var(--line-soft); font: 600 9px var(--mono); }
.image-caption.mono { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.ground-stack { display: grid; grid-template-rows: auto auto; gap: 12px; }
.node-label { width: 29px; height: 29px; display: grid; place-items: center; font: 800 14px var(--mono); border: 1px solid; }
.anchor-label { color: var(--anchor); border-color: var(--anchor); background: rgba(239,91,77,.1); }
.target-label { color: var(--target); border-color: var(--target); background: rgba(60,142,232,.1); }

.context-rail {
  min-height: 70px; display: grid; grid-template-columns: 160px 1fr auto;
  align-items: center; gap: 14px; padding: 10px 12px;
  border: 1px solid var(--line); background: var(--panel);
}
.context-rail span.image-kind { display: block; }
.context-rail p { margin: 3px 0 0; color: var(--faint); font-size: 10px; }
.node-thumbnails { display: flex; gap: 5px; flex-wrap: wrap; }
.node-chip {
  width: 26px; height: 26px; display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--line); background: var(--ink); color: var(--muted);
  font: 700 11px var(--mono);
}
.node-chip:hover { border-color: var(--signal); }
.node-chip.active { border-color: var(--signal); color: var(--signal); background: rgba(241,164,60,.12); }
.node-chip.orphan { border-color: var(--anchor); color: var(--anchor); }
.node-chip.reviewed::after { content: "·"; position: absolute; }
.corridor-legend { display: flex; flex-direction: column; gap: 3px; max-height: 62px; overflow-y: auto; }
.corridor-legend div { display: flex; align-items: center; gap: 5px; color: var(--muted); font: 600 9px var(--mono); white-space: nowrap; }
.corridor-legend i { width: 8px; height: 8px; border-radius: 50%; flex: none; }

/* ---------------------------------------------------------------- 邻接矩阵 */
.graph-card { border: 1px solid var(--line); background: var(--panel); }
.graph-head { display: flex; align-items: center; gap: 14px; padding: 10px 12px; border-bottom: 1px solid var(--line-soft); }
.graph-head h3 { margin: 2px 0 0; font: 600 15px var(--display); letter-spacing: .04em; text-transform: uppercase; }
.graph-counts { margin-left: auto; display: flex; gap: 12px; color: var(--faint); font: 600 9px var(--mono); text-transform: uppercase; }
.graph-counts b { color: var(--paper); }
.graph-rule { margin: 0; padding: 8px 12px; color: var(--faint); font-size: 10px; border-bottom: 1px solid var(--line-soft); }
.graph-body { display: grid; grid-template-columns: auto 1fr; gap: 12px; padding: 11px 12px; align-items: start; }
.adjacency-table { border-collapse: collapse; font: 700 10px var(--mono); }
.adjacency-table th, .adjacency-table td {
  width: 21px; height: 21px; text-align: center; border: 1px solid var(--line-soft); color: var(--faint);
}
.adjacency-table td.edge { cursor: pointer; }
/* 旧策略的 e-chain 已随规则作废 */
.adjacency-table td.e-corridor { background: rgba(107,168,232,.32); color: var(--paper); }
.adjacency-table td.e-road_route { background: rgba(104,200,160,.32); color: var(--paper); }
.adjacency-table td.e-short_link { background: rgba(241,164,60,.32); color: var(--paper); }
.adjacency-table td.e-component_merge { background: rgba(232,100,107,.32); color: var(--paper); }
.adjacency-table td.diag { background: var(--line-soft); }
.adjacency-table td.addable { cursor: cell; color: #3d4a45; }
.adjacency-table td.addable:hover { background: rgba(104,200,192,.18); color: var(--cyan); }
.adjacency-table td.rejected { opacity: .4; text-decoration: line-through; }
.edge-row.rejected { opacity: .45; }
.cost-tag { color: var(--uncertain); font-weight: 800; letter-spacing: -1px; }
.adjacency-table td.sel { outline: 2px solid var(--signal); outline-offset: -2px; }
.graph-edge-list { max-height: 190px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.edge-row {
  display: flex; align-items: center; gap: 7px; padding: 3px 6px; cursor: pointer;
  border: 1px solid transparent; font: 600 10px var(--mono); color: var(--muted);
}
.edge-row:hover { background: rgba(255,255,255,.03); }
.edge-row.sel { border-color: var(--signal); color: var(--paper); }
.edge-row i { width: 10px; height: 2px; flex: none; }
.edge-row .len { margin-left: auto; color: var(--faint); }

/* ---------------------------------------------------------------- 判定区 */
.signal-block { padding: 12px 14px; border-bottom: 1px solid var(--line); }
.block-title { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 8px; }
.block-title span { color: var(--faint); font: 700 9px var(--mono); letter-spacing: .12em; text-transform: uppercase; }
.text-button { background: none; border: 0; color: var(--signal); cursor: pointer; font: 600 10px var(--mono); padding: 0; }
.signal-row { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--line-soft); }
.signal-row:last-child { border-bottom: 0; }
.signal-row b { color: var(--no); font: 800 11px var(--mono); }
.signal-row em { color: var(--muted); font-style: normal; font: 600 10px var(--mono); }
.conf-meter { height: 3px; background: var(--line-soft); margin-top: 8px; }
.conf-meter span { display: block; height: 100%; background: var(--signal); }

.field-block { padding: 12px 14px; border-bottom: 1px solid var(--line); }
.choice-row { display: flex; gap: 6px; margin-top: 6px; }
.choice {
  flex: 1; padding: 7px 4px; cursor: pointer; text-align: center;
  border: 1px solid var(--line); background: var(--ink); color: var(--muted);
  font: 700 10px var(--mono); letter-spacing: .05em;
}
.choice:hover { border-color: var(--muted); }
.choice.on-yes { border-color: var(--yes); color: var(--yes); background: rgba(112,199,155,.12); }
.choice.on-no { border-color: var(--no); color: var(--no); background: rgba(226,118,103,.12); }
.choice.on-uncertain { border-color: var(--uncertain); color: var(--uncertain); background: rgba(208,184,123,.12); }
.corridor-picker { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; max-height: 190px; overflow-y: auto; }
.corridor-option {
  display: flex; align-items: center; gap: 7px; padding: 6px 8px; cursor: pointer;
  border: 1px solid var(--line); background: var(--ink); color: var(--muted);
  font: 600 11px var(--mono); text-align: left;
}
.corridor-option:hover { border-color: var(--muted); }
.corridor-option.active { border-color: var(--signal); color: var(--paper); background: rgba(241,164,60,.1); }
.corridor-option i { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.corridor-option kbd { margin-left: auto; }
.corridor-option .auto-tag { color: var(--cyan); font-size: 9px; }
.corridor-option span:first-of-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.corridor-option .dist-tag { color: var(--faint); font-size: 9px; flex: none; }
.notes-field { width: 100%; margin-top: 6px; background: var(--ink); border: 1px solid var(--line); color: var(--paper); padding: 7px 9px; border-radius: var(--radius); resize: vertical; min-height: 52px; font: 500 11px var(--body); }
.notes-field:focus { outline: none; border-color: var(--signal); }
.commit-row { padding: 12px 14px; display: flex; flex-direction: column; gap: 7px; }
.primary-button {
  padding: 9px; cursor: pointer; border: 1px solid var(--signal); background: var(--signal);
  color: var(--ink); font: 800 11px var(--mono); letter-spacing: .08em; text-transform: uppercase;
}
.primary-button:hover { background: #ffc069; }
.ghost-button {
  padding: 8px; cursor: pointer; border: 1px solid var(--line); background: transparent;
  color: var(--muted); font: 700 10px var(--mono); letter-spacing: .06em; text-transform: uppercase;
}
.ghost-button:hover { border-color: var(--uncertain); color: var(--uncertain); }
.kv { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; font: 600 10px var(--mono); }
.kv dt { color: var(--faint); }
/* anywhere 而不是 break-all:长 JSON / id 照样能断开,但有空格时先在空格处折行,
   英文值不会被从单词中间劈开(break-all 会把 neighbours 断成 neighbo|urs) */
.kv dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
.revision-list { list-style: none; margin: 6px 0 0; padding: 0; max-height: 150px; overflow-y: auto; }
.revision-list li { padding: 3px 0; border-bottom: 1px solid var(--line-soft); color: var(--faint); font: 500 9px var(--mono); word-break: break-all; }

/* ---------------------------------------------------------------- 浮层 */
dialog { border: 1px solid var(--line); background: var(--panel); color: var(--paper); padding: 0; box-shadow: var(--shadow); }
dialog::backdrop { background: rgba(6,9,8,.72); }
.dialog-head { display: flex; justify-content: space-between; align-items: center; padding: 13px 16px; border-bottom: 1px solid var(--line); }
.dialog-head h3 { margin: 0; font: 600 16px var(--display); letter-spacing: .05em; text-transform: uppercase; }
.dialog-body { padding: 14px 16px; max-width: 520px; }
.shortcut-grid { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; align-items: baseline; font-size: 12px; }
.shortcut-grid span { color: var(--muted); }
/* 键帽按内容宽度,不被 grid 拉成长条;「[ ]」两个键包在同一格(.keys)里,
   否则多出的一格会把后面每一行都挤错一列 */
.shortcut-grid > kbd, .shortcut-grid > .keys { justify-self: start; }
.lightbox { position: fixed; inset: 0; z-index: 50; display: none; place-items: center; background: rgba(6,9,8,.9); cursor: zoom-out; }
.lightbox.open { display: grid; }
.lightbox img { max-width: 92vw; max-height: 92vh; border: 1px solid var(--line); }
.toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 60;
  padding: 9px 16px; border: 1px solid var(--line); background: var(--panel-raised);
  color: var(--paper); font: 600 11px var(--mono); opacity: 0; pointer-events: none;
  transition: opacity .2s ease;
}
.toast.show { opacity: 1; }
.toast.err { border-color: var(--no); color: var(--no); }
.empty-note { color: var(--faint); font-size: 12px; padding: 16px; }