Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- web/app.js +82 -45
- web/index.html +3 -9
- web/styles.css +40 -15
web/app.js
CHANGED
|
@@ -664,13 +664,15 @@ function drawHeadingArrows(svg, mk, pos) {
|
|
| 664 |
if (!n || !n.ground_headings || !n.ground_headings.length) return;
|
| 665 |
var p = pos[n.label];
|
| 666 |
if (!p) return;
|
| 667 |
-
|
|
|
|
|
|
|
| 668 |
|
| 669 |
n.ground_headings.forEach(function (h, k) {
|
| 670 |
if (h == null) return;
|
| 671 |
var th = (Number(h) % 360 + 360) % 360 * Math.PI / 180;
|
| 672 |
var dx = Math.sin(th), dy = -Math.cos(th);
|
| 673 |
-
var on = k ===
|
| 674 |
var r0 = 7.5, r1 = on ? 22 : 14; // 起点让开节点圆点
|
| 675 |
var x0 = p[0] + dx * r0, y0 = p[1] + dy * r0;
|
| 676 |
var x1 = p[0] + dx * r1, y1 = p[1] + dy * r1;
|
|
@@ -703,14 +705,17 @@ function drawHeadingArrows(svg, mk, pos) {
|
|
| 703 |
'stroke-linejoin': 'round', 'paint-order': 'stroke',
|
| 704 |
}));
|
| 705 |
var t = mk('title', {});
|
| 706 |
-
t.textContent = headingLabel(h) +
|
| 707 |
gg.appendChild(t);
|
|
|
|
| 708 |
gg.onclick = function (ev) {
|
| 709 |
ev.stopPropagation();
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
};
|
|
|
|
|
|
|
| 714 |
svg.appendChild(gg);
|
| 715 |
});
|
| 716 |
});
|
|
@@ -809,10 +814,6 @@ function drawAdjacency() {
|
|
| 809 |
});
|
| 810 |
}
|
| 811 |
|
| 812 |
-
// 每个 role(anchor/target)当前看的是第几个朝向。
|
| 813 |
-
// 换节点时保留同一个下标,这样连按 J/K 浏览时朝向不会跳来跳去。
|
| 814 |
-
var groundView = { anchor: 0, target: 0 };
|
| 815 |
-
|
| 816 |
function headingLabel(h) {
|
| 817 |
if (h == null) return '—';
|
| 818 |
var d = ((+h % 360) + 360) % 360; // 文件名里有负角,先归一到 [0,360)
|
|
@@ -820,41 +821,81 @@ function headingLabel(h) {
|
|
| 820 |
return names[Math.round(d / 45) % 8] + ' ' + Math.round(d) + '°';
|
| 821 |
}
|
| 822 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
function drawGroundImages() {
|
| 824 |
var sel = S.selected;
|
| 825 |
if (!sel || !S.scene) return;
|
|
|
|
| 826 |
function fill(role, label) {
|
| 827 |
var n = S.scene.nodes.find(function (x) { return x.label === label; });
|
| 828 |
$(role + 'Badge').textContent = label || '—';
|
| 829 |
var assets = (n && n.ground_assets) || [];
|
| 830 |
var headings = (n && n.ground_headings) || [];
|
| 831 |
-
var i = Math.min(groundView[role] || 0, Math.max(assets.length - 1, 0));
|
| 832 |
-
groundView[role] = i;
|
| 833 |
$(role + 'Caption').textContent = n
|
| 834 |
-
? (n.lat.toFixed(6) + ', ' + n.lon.toFixed(6) + ' · ' +
|
| 835 |
-
|
|
|
|
| 836 |
: '—';
|
| 837 |
-
var img = $(role + 'Image');
|
| 838 |
-
if (assets[i]) img.src = '/asset?path=' + encodeURIComponent(assets[i]);
|
| 839 |
-
else { img.removeAttribute('src'); }
|
| 840 |
|
| 841 |
-
var
|
| 842 |
-
|
| 843 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 844 |
assets.forEach(function (rel, k) {
|
| 845 |
var b = document.createElement('button');
|
| 846 |
b.type = 'button';
|
| 847 |
-
b.
|
| 848 |
-
b.
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 853 |
});
|
| 854 |
}
|
|
|
|
| 855 |
if (sel.kind === 'node') {
|
| 856 |
$('anchorKind').textContent = 'Node';
|
| 857 |
$('targetCard').style.display = 'none';
|
|
|
|
| 858 |
fill('anchor', sel.key[1]);
|
| 859 |
} else {
|
| 860 |
$('anchorKind').textContent = 'Source';
|
|
@@ -864,19 +905,23 @@ function drawGroundImages() {
|
|
| 864 |
}
|
| 865 |
}
|
| 866 |
|
| 867 |
-
/
|
|
|
|
| 868 |
function cycleGround(step) {
|
| 869 |
if (!S.selected || !S.scene) return;
|
| 870 |
-
var
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
|
|
|
|
|
|
|
|
|
| 880 |
}
|
| 881 |
|
| 882 |
function pickNode(n) {
|
|
@@ -1100,14 +1145,6 @@ function boot() {
|
|
| 1100 |
$('helpButton').onclick = function () { $('helpDialog').showModal(); };
|
| 1101 |
$('helpClose').onclick = function () { $('helpDialog').close(); };
|
| 1102 |
$('lightbox').onclick = function () { this.classList.remove('open'); };
|
| 1103 |
-
Array.prototype.forEach.call(document.querySelectorAll('.ground-stage'), function (b) {
|
| 1104 |
-
b.onclick = function () {
|
| 1105 |
-
var img = b.querySelector('img');
|
| 1106 |
-
if (!img || !img.getAttribute('src')) return;
|
| 1107 |
-
$('lightboxImage').src = img.src;
|
| 1108 |
-
$('lightbox').classList.add('open');
|
| 1109 |
-
};
|
| 1110 |
-
});
|
| 1111 |
|
| 1112 |
document.addEventListener('keydown', function (ev) {
|
| 1113 |
var tag = (ev.target.tagName || '').toLowerCase();
|
|
|
|
| 664 |
if (!n || !n.ground_headings || !n.ground_headings.length) return;
|
| 665 |
var p = pos[n.label];
|
| 666 |
if (!p) return;
|
| 667 |
+
// 四个朝向的图片现在同时显示,所以箭头地位平等——不再有"当前朝向"。
|
| 668 |
+
// 只有鼠标悬在某张图片上时,对应的那支才放大加亮。
|
| 669 |
+
var lit = LIT[pair[0]];
|
| 670 |
|
| 671 |
n.ground_headings.forEach(function (h, k) {
|
| 672 |
if (h == null) return;
|
| 673 |
var th = (Number(h) % 360 + 360) % 360 * Math.PI / 180;
|
| 674 |
var dx = Math.sin(th), dy = -Math.cos(th);
|
| 675 |
+
var on = k === lit;
|
| 676 |
var r0 = 7.5, r1 = on ? 22 : 14; // 起点让开节点圆点
|
| 677 |
var x0 = p[0] + dx * r0, y0 = p[1] + dy * r0;
|
| 678 |
var x1 = p[0] + dx * r1, y1 = p[1] + dy * r1;
|
|
|
|
| 705 |
'stroke-linejoin': 'round', 'paint-order': 'stroke',
|
| 706 |
}));
|
| 707 |
var t = mk('title', {});
|
| 708 |
+
t.textContent = headingLabel(h) + ' 街景(点击放大)';
|
| 709 |
gg.appendChild(t);
|
| 710 |
+
// 点箭头 = 放大那个方向的街景;悬停则反向点亮对应的缩略图
|
| 711 |
gg.onclick = function (ev) {
|
| 712 |
ev.stopPropagation();
|
| 713 |
+
var tile = $(pair[0] + 'Grid').children[k];
|
| 714 |
+
var img = tile && tile.querySelector && tile.querySelector('img');
|
| 715 |
+
if (img) { $('lightboxImage').src = img.src; $('lightbox').classList.add('open'); }
|
| 716 |
};
|
| 717 |
+
gg.onmouseenter = function () { setLit(pair[0], k); };
|
| 718 |
+
gg.onmouseleave = function () { setLit(pair[0], -1); };
|
| 719 |
svg.appendChild(gg);
|
| 720 |
});
|
| 721 |
});
|
|
|
|
| 814 |
});
|
| 815 |
}
|
| 816 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
function headingLabel(h) {
|
| 818 |
if (h == null) return '—';
|
| 819 |
var d = ((+h % 360) + 360) % 360; // 文件名里有负角,先归一到 [0,360)
|
|
|
|
| 821 |
return names[Math.round(d / 45) % 8] + ' ' + Math.round(d) + '°';
|
| 822 |
}
|
| 823 |
|
| 824 |
+
/* 四个朝向同时铺开。
|
| 825 |
+
*
|
| 826 |
+
* 旧版一次只显示一张,下面一排按钮来回切——要看全一个路口得点三次,
|
| 827 |
+
* 而且切走之后就记不住上一张长什么样。现在四张并排、各自标方位,
|
| 828 |
+
* 不用做任何选择就能一眼看完。
|
| 829 |
+
*
|
| 830 |
+
* 悬停某一张会点亮地图上对应的箭头(见 drawHeadingArrows),
|
| 831 |
+
* 「图片里的这条路」和「图上的这个方向」就对上了。
|
| 832 |
+
*/
|
| 833 |
+
var LIT = { anchor: -1, target: -1 }; // 当前悬停的朝向下标,-1 = 无
|
| 834 |
+
|
| 835 |
+
/* 双向联动的唯一入口:改 LIT 就同时更新缩略图的边框和地图上的箭头。
|
| 836 |
+
两边各写一次会漏——比如从箭头移开时忘了取消图片的高亮。 */
|
| 837 |
+
function setLit(role, k) {
|
| 838 |
+
if (LIT[role] === k) return;
|
| 839 |
+
LIT[role] = k;
|
| 840 |
+
var grid = $(role + 'Grid');
|
| 841 |
+
if (grid) {
|
| 842 |
+
Array.prototype.forEach.call(grid.children, function (el, i) {
|
| 843 |
+
if (el.classList) el.classList.toggle('lit', i === k);
|
| 844 |
+
});
|
| 845 |
+
}
|
| 846 |
+
drawGraph();
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
function drawGroundImages() {
|
| 850 |
var sel = S.selected;
|
| 851 |
if (!sel || !S.scene) return;
|
| 852 |
+
|
| 853 |
function fill(role, label) {
|
| 854 |
var n = S.scene.nodes.find(function (x) { return x.label === label; });
|
| 855 |
$(role + 'Badge').textContent = label || '—';
|
| 856 |
var assets = (n && n.ground_assets) || [];
|
| 857 |
var headings = (n && n.ground_headings) || [];
|
|
|
|
|
|
|
| 858 |
$(role + 'Caption').textContent = n
|
| 859 |
+
? (n.lat.toFixed(6) + ', ' + n.lon.toFixed(6) + ' · ' +
|
| 860 |
+
(n.corridor_name_auto || '无走廊') +
|
| 861 |
+
(assets.length ? ' · ' + assets.length + ' 个朝向' : ''))
|
| 862 |
: '—';
|
|
|
|
|
|
|
|
|
|
| 863 |
|
| 864 |
+
var grid = $(role + 'Grid');
|
| 865 |
+
grid.innerHTML = '';
|
| 866 |
+
grid.className = 'ground-grid' + (assets.length === 1 ? ' single' : '');
|
| 867 |
+
if (!assets.length) {
|
| 868 |
+
grid.innerHTML = '<div class="ground-empty">该节点没有街景</div>';
|
| 869 |
+
return;
|
| 870 |
+
}
|
| 871 |
assets.forEach(function (rel, k) {
|
| 872 |
var b = document.createElement('button');
|
| 873 |
b.type = 'button';
|
| 874 |
+
b.className = 'ground-tile';
|
| 875 |
+
b.setAttribute('aria-label', '放大 ' + headingLabel(headings[k]) + ' 方向的街景');
|
| 876 |
+
var img = document.createElement('img');
|
| 877 |
+
img.src = '/asset?path=' + encodeURIComponent(rel);
|
| 878 |
+
img.alt = headingLabel(headings[k]) + ' 街景';
|
| 879 |
+
img.loading = 'lazy';
|
| 880 |
+
var tag = document.createElement('b');
|
| 881 |
+
tag.textContent = headingLabel(headings[k]);
|
| 882 |
+
b.appendChild(img);
|
| 883 |
+
b.appendChild(tag);
|
| 884 |
+
b.onclick = function () {
|
| 885 |
+
$('lightboxImage').src = img.src;
|
| 886 |
+
$('lightbox').classList.add('open');
|
| 887 |
+
};
|
| 888 |
+
// 悬停联动:图片 <-> 地图上的箭头
|
| 889 |
+
b.onmouseenter = function () { setLit(role, k); };
|
| 890 |
+
b.onmouseleave = function () { setLit(role, -1); };
|
| 891 |
+
grid.appendChild(b);
|
| 892 |
});
|
| 893 |
}
|
| 894 |
+
|
| 895 |
if (sel.kind === 'node') {
|
| 896 |
$('anchorKind').textContent = 'Node';
|
| 897 |
$('targetCard').style.display = 'none';
|
| 898 |
+
LIT.target = -1;
|
| 899 |
fill('anchor', sel.key[1]);
|
| 900 |
} else {
|
| 901 |
$('anchorKind').textContent = 'Source';
|
|
|
|
| 905 |
}
|
| 906 |
}
|
| 907 |
|
| 908 |
+
/* H 依次放大看四个朝向 —— 四张都在屏幕上了,但缩略图偏小,
|
| 909 |
+
想逐张看大图时不必用鼠标去点。 */
|
| 910 |
function cycleGround(step) {
|
| 911 |
if (!S.selected || !S.scene) return;
|
| 912 |
+
var role = 'anchor', label = S.selected.key[1];
|
| 913 |
+
var n = S.scene.nodes.find(function (x) { return x.label === label; });
|
| 914 |
+
var assets = (n && n.ground_assets) || [];
|
| 915 |
+
if (!assets.length) return;
|
| 916 |
+
var next = (LIT[role] + step) % assets.length;
|
| 917 |
+
if (next < 0) next += assets.length;
|
| 918 |
+
LIT[role] = next;
|
| 919 |
+
var tile = $(role + 'Grid').children[next];
|
| 920 |
+
if (tile && tile.querySelector) {
|
| 921 |
+
var img = tile.querySelector('img');
|
| 922 |
+
if (img) { $('lightboxImage').src = img.src; $('lightbox').classList.add('open'); }
|
| 923 |
+
}
|
| 924 |
+
drawGraph();
|
| 925 |
}
|
| 926 |
|
| 927 |
function pickNode(n) {
|
|
|
|
| 1145 |
$('helpButton').onclick = function () { $('helpDialog').showModal(); };
|
| 1146 |
$('helpClose').onclick = function () { $('helpDialog').close(); };
|
| 1147 |
$('lightbox').onclick = function () { this.classList.remove('open'); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1148 |
|
| 1149 |
document.addEventListener('keydown', function (ev) {
|
| 1150 |
var tag = (ev.target.tagName || '').toLowerCase();
|
web/index.html
CHANGED
|
@@ -217,10 +217,7 @@
|
|
| 217 |
<div class="node-label anchor-label" id="anchorBadge">—</div>
|
| 218 |
<span class="image-kind" id="anchorKind">Node</span>
|
| 219 |
</div>
|
| 220 |
-
<
|
| 221 |
-
<img id="anchorImage" alt="ground view">
|
| 222 |
-
</button>
|
| 223 |
-
<div class="heading-strip" id="anchorStrip" role="tablist" aria-label="街景朝向"></div>
|
| 224 |
<div class="image-caption mono" id="anchorCaption">—</div>
|
| 225 |
</article>
|
| 226 |
<article class="image-card ground-card" id="targetCard">
|
|
@@ -228,10 +225,7 @@
|
|
| 228 |
<div class="node-label target-label" id="targetBadge">—</div>
|
| 229 |
<span class="image-kind">Target</span>
|
| 230 |
</div>
|
| 231 |
-
<
|
| 232 |
-
<img id="targetImage" alt="ground view">
|
| 233 |
-
</button>
|
| 234 |
-
<div class="heading-strip" id="targetStrip" role="tablist" aria-label="街景朝向"></div>
|
| 235 |
<div class="image-caption mono" id="targetCaption">—</div>
|
| 236 |
</article>
|
| 237 |
</div>
|
|
@@ -290,7 +284,7 @@
|
|
| 290 |
<kbd>K</kbd><span>队列上一条</span>
|
| 291 |
<kbd>B</kbd><span>切换卫星图 / 电子地图</span>
|
| 292 |
<kbd>E</kbd><span>切换边的画法:沿路走线 / 直线</span>
|
| 293 |
-
<kbd>H</kbd><span>
|
| 294 |
<kbd>[</kbd> <kbd>]</kbd><span>列表上一页 / 下一页</span>
|
| 295 |
<kbd>Q</kbd><span>切换「全部场景」/「带信号的」</span>
|
| 296 |
<kbd>?</kbd><span>打开本帮助</span>
|
|
|
|
| 217 |
<div class="node-label anchor-label" id="anchorBadge">—</div>
|
| 218 |
<span class="image-kind" id="anchorKind">Node</span>
|
| 219 |
</div>
|
| 220 |
+
<div class="ground-grid" id="anchorGrid" aria-label="四个朝向的街景"></div>
|
|
|
|
|
|
|
|
|
|
| 221 |
<div class="image-caption mono" id="anchorCaption">—</div>
|
| 222 |
</article>
|
| 223 |
<article class="image-card ground-card" id="targetCard">
|
|
|
|
| 225 |
<div class="node-label target-label" id="targetBadge">—</div>
|
| 226 |
<span class="image-kind">Target</span>
|
| 227 |
</div>
|
| 228 |
+
<div class="ground-grid" id="targetGrid" aria-label="四个朝向的街景"></div>
|
|
|
|
|
|
|
|
|
|
| 229 |
<div class="image-caption mono" id="targetCaption">—</div>
|
| 230 |
</article>
|
| 231 |
</div>
|
|
|
|
| 284 |
<kbd>K</kbd><span>队列上一条</span>
|
| 285 |
<kbd>B</kbd><span>切换卫星图 / 电子地图</span>
|
| 286 |
<kbd>E</kbd><span>切换边的画法:沿路走线 / 直线</span>
|
| 287 |
+
<kbd>H</kbd><span>把当前节点的四个朝向依次放大看(<kbd>Shift</kbd>+<kbd>H</kbd> 反向)</span>
|
| 288 |
<kbd>[</kbd> <kbd>]</kbd><span>列表上一页 / 下一页</span>
|
| 289 |
<kbd>Q</kbd><span>切换「全部场景」/「带信号的」</span>
|
| 290 |
<kbd>?</kbd><span>打开本帮助</span>
|
web/styles.css
CHANGED
|
@@ -292,7 +292,7 @@ kbd {
|
|
| 292 |
.scene-metrics span { display: block; color: var(--faint); font: 600 8px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
|
| 293 |
.scene-metrics strong { font: 600 15px var(--display); }
|
| 294 |
|
| 295 |
-
.evidence-grid { display: grid; grid-template-columns: minmax(
|
| 296 |
.image-card { border: 1px solid var(--line); background: var(--panel); }
|
| 297 |
.image-card-head { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border-bottom: 1px solid var(--line-soft); }
|
| 298 |
.image-card-head.compact { padding: 6px 9px; }
|
|
@@ -314,8 +314,6 @@ kbd {
|
|
| 314 |
.image-stage { display: block; width: 100%; padding: 0; border: 0; background: #0b0e0d; position: relative; overflow: hidden; }
|
| 315 |
.image-stage > img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
| 316 |
.satellite-stage { aspect-ratio: 1/1; cursor: crosshair; }
|
| 317 |
-
.ground-stage { aspect-ratio: 16/8.3; cursor: zoom-in; }
|
| 318 |
-
.ground-stage:hover > img { filter: brightness(1.06); }
|
| 319 |
/* .street-map 有 z-index:1 而 .graph-svg 没有,于是电子地图模式下
|
| 320 |
瓦片整个盖在图上——节点和边全看不见。给 SVG 更高的层级。 */
|
| 321 |
.graph-svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; }
|
|
@@ -335,18 +333,45 @@ kbd {
|
|
| 335 |
.map-controls { display: flex; border: 1px solid var(--line); }
|
| 336 |
.map-controls button { background: transparent; border: 0; width: 22px; cursor: pointer; color: var(--muted); font: 700 12px var(--mono); }
|
| 337 |
.map-controls button:hover { color: var(--signal); }
|
| 338 |
-
/* 街景朝向
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
.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); }
|
| 352 |
.image-caption.mono { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
| 292 |
.scene-metrics span { display: block; color: var(--faint); font: 600 8px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
|
| 293 |
.scene-metrics strong { font: 600 15px var(--display); }
|
| 294 |
|
| 295 |
+
.evidence-grid { display: grid; grid-template-columns: minmax(300px, 1fr) minmax(280px, 440px); gap: 14px; align-items: start; }
|
| 296 |
.image-card { border: 1px solid var(--line); background: var(--panel); }
|
| 297 |
.image-card-head { display: flex; align-items: center; gap: 9px; padding: 8px 11px; border-bottom: 1px solid var(--line-soft); }
|
| 298 |
.image-card-head.compact { padding: 6px 9px; }
|
|
|
|
| 314 |
.image-stage { display: block; width: 100%; padding: 0; border: 0; background: #0b0e0d; position: relative; overflow: hidden; }
|
| 315 |
.image-stage > img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
| 316 |
.satellite-stage { aspect-ratio: 1/1; cursor: crosshair; }
|
|
|
|
|
|
|
| 317 |
/* .street-map 有 z-index:1 而 .graph-svg 没有,于是电子地图模式下
|
| 318 |
瓦片整个盖在图上——节点和边全看不见。给 SVG 更高的层级。 */
|
| 319 |
.graph-svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; }
|
|
|
|
| 333 |
.map-controls { display: flex; border: 1px solid var(--line); }
|
| 334 |
.map-controls button { background: transparent; border: 0; width: 22px; cursor: pointer; color: var(--muted); font: 700 12px var(--mono); }
|
| 335 |
.map-controls button:hover { color: var(--signal); }
|
| 336 |
+
/* 街景四朝向方格 ------------------------------------------------------
|
| 337 |
+
* 一个 pano 有 4 个朝向。旧版一次只显示一张、下面一排按钮来回切——
|
| 338 |
+
* 要看全一个路口得点三次,还得记住刚才那张是什么样。现在四张并排,
|
| 339 |
+
* 各自标出方位,一眼看完。
|
| 340 |
+
*
|
| 341 |
+
* 源图是 512x512 的正方形,旧版按 16:8.3 裁成信箱形,上下都切掉了。
|
| 342 |
+
* 方格用 1:1,反而看得更全。
|
| 343 |
+
*/
|
| 344 |
+
.ground-grid {
|
| 345 |
+
display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
|
| 346 |
+
background: var(--line-soft); border-top: 1px solid var(--line-soft);
|
| 347 |
+
}
|
| 348 |
+
.ground-grid:empty { display: none; }
|
| 349 |
+
/* 只有一张时(旧包没有 heading 列)不必占两格 */
|
| 350 |
+
.ground-grid.single { grid-template-columns: 1fr; }
|
| 351 |
+
|
| 352 |
+
.ground-tile {
|
| 353 |
+
position: relative; display: block; padding: 0; border: 0;
|
| 354 |
+
background: #0b0e0d; cursor: zoom-in; overflow: hidden; aspect-ratio: 1/1;
|
| 355 |
+
}
|
| 356 |
+
.ground-tile > img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
| 357 |
+
.ground-tile:hover > img { filter: brightness(1.08); }
|
| 358 |
+
.ground-tile:focus-visible { outline: 2px solid var(--signal); outline-offset: -2px; }
|
| 359 |
+
|
| 360 |
+
/* 方位标签压在图上:图本身已经很小,再单占一行就没地方了 */
|
| 361 |
+
.ground-tile b {
|
| 362 |
+
position: absolute; left: 0; top: 0; padding: 2px 5px;
|
| 363 |
+
background: rgba(8,12,11,.78); color: var(--paper);
|
| 364 |
+
font: 700 9px var(--mono); letter-spacing: .06em;
|
| 365 |
+
border-bottom-right-radius: var(--radius);
|
| 366 |
+
}
|
| 367 |
+
/* 悬停时同时点亮地图上对应的箭头(drawGraph 里加 .arrow-lit) */
|
| 368 |
+
.ground-tile:hover b { background: var(--signal); color: var(--ink); }
|
| 369 |
+
.ground-tile.lit b { background: var(--signal); color: var(--ink); }
|
| 370 |
+
|
| 371 |
+
.ground-empty {
|
| 372 |
+
grid-column: 1 / -1; padding: 18px 10px; text-align: center;
|
| 373 |
+
color: var(--faint); font: 600 10px var(--mono);
|
| 374 |
+
}
|
| 375 |
|
| 376 |
.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); }
|
| 377 |
.image-caption.mono { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|