Files
KSP_project/app/templates/mission_location_board_preview.html

287 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block content %}
<section id="ksp-location-board" aria-label="太阳系地点板">
<header class="location-toolbar">
<nav class="location-breadcrumbs" id="location-breadcrumbs" aria-label="地图层级"></nav>
<div class="location-summary">
<label class="transfer-toggle" for="transfer-toggle">
<input id="transfer-toggle" type="checkbox">
<span>全部转移轨迹</span>
</label>
<span id="scope-count">0 assets</span>
<span class="live-status"><i></i>实时星历</span>
<button class="location-reset" id="location-reset" type="button">重置</button>
</div>
</header>
<div class="location-layout">
<main class="location-map" id="location-map">
<svg id="space-map" viewBox="0 0 1000 620" preserveAspectRatio="xMidYMid meet" role="img" aria-labelledby="map-title map-desc">
<title id="map-title">太阳系资产位置地图</title>
<desc id="map-desc">从太阳系全局钻取到行星系统、行星和卫星,并查看实际转移航路。</desc>
<g id="orbit-layer"></g>
<g id="route-layer"></g>
<g id="asset-layer"></g>
</svg>
<div class="body-layer" id="body-layer"></div>
<div class="map-caption"><span id="view-mode">SOLAR SYSTEM</span><b id="map-caption">选择天体或转移资产</b></div>
<div class="map-legend" aria-label="图例">
<span><i class="asset-dot"></i>资产位置</span>
<span><i class="progress-line"></i>已完成航程</span>
<span><i class="remaining-line"></i>剩余航程</span>
</div>
</main>
<aside class="asset-panel">
<div class="asset-panel-head">
<div><span>ASSETS IN SCOPE</span><h2 id="scope-title">Solar System</h2></div>
<b id="panel-count">0</b>
</div>
<div class="asset-list" id="asset-list"></div>
</aside>
<aside class="asset-drawer" id="asset-drawer" aria-hidden="true">
<button class="drawer-close" id="drawer-close" type="button" aria-label="关闭详情">×</button>
<span class="drawer-eyebrow">ASSET DETAIL</span>
<h2 id="drawer-name"></h2>
<div class="drawer-fields" id="drawer-fields"></div>
<a class="drawer-link" id="drawer-link" href="#">打开资产详情页</a>
</aside>
</div>
</section>
<script id="location-assets-data" type="application/json">{{ asset_positions|tojson }}</script>
<style>
#ksp-location-board{position:absolute;inset:0;display:grid;grid-template-rows:auto minmax(0,1fr);overflow:hidden;background:var(--bg);color:var(--text);font-size:clamp(12px,.28vw + 8px,22px)}
#ksp-location-board button,#ksp-location-board a{font:inherit}
.location-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:48px;padding:8px clamp(14px,1vw,30px);border-bottom:1px solid var(--border);background:var(--surface)}
.location-breadcrumbs,.location-summary{display:flex;align-items:center;gap:9px;min-width:0}
.location-breadcrumbs button{padding:0;border:0;background:transparent;color:var(--muted);cursor:pointer}
.location-breadcrumbs button.current{color:var(--text);font-weight:600}.location-breadcrumbs .sep{color:var(--muted)}
.location-summary{color:var(--muted);white-space:nowrap}.live-status{display:flex;align-items:center;gap:5px}.live-status i{width:7px;height:7px;border-radius:50%;background:var(--amber)}
.transfer-toggle{display:flex;align-items:center;gap:7px;cursor:pointer;color:var(--text)}.transfer-toggle input{accent-color:var(--accent)}
.location-reset{padding:4px 9px;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--muted);cursor:pointer}.location-reset:hover{color:var(--text);background:var(--surface2)}
.location-layout{position:relative;display:grid;grid-template-columns:minmax(0,1fr) clamp(270px,20vw,540px);min-width:0;min-height:0}
.location-map{position:relative;min-width:0;min-height:0;overflow:hidden;background:radial-gradient(circle at 50% 50%,rgba(59,130,246,.09),transparent 44%),var(--bg)}
#space-map{display:block;width:100%;height:100%}.body-layer{position:absolute;inset:0;pointer-events:none}
.asset-panel{display:grid;grid-template-rows:auto minmax(0,1fr);min-height:0;padding:clamp(14px,1vw,28px);border-left:1px solid var(--border);background:var(--surface)}
.asset-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.asset-panel-head span,.drawer-eyebrow{color:var(--muted);font-family:var(--font-data);font-size:.7em;letter-spacing:.08em}.asset-panel-head h2{margin-top:4px;font-size:1.2em}.asset-panel-head b{padding:4px 8px;background:rgba(59,130,246,.12);color:var(--accent);font-family:var(--font-data)}
.asset-list{min-height:0;overflow-y:auto}.asset-button{display:flex;align-items:center;gap:9px;width:100%;padding:clamp(9px,.55vw,18px) 3px;border:0;border-bottom:1px solid var(--border);background:transparent;color:var(--text);text-align:left;cursor:pointer}.asset-button::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--amber);flex:0 0 auto}.asset-button.planned::before{background:var(--purple)}.asset-button:hover,.asset-button.selected{background:rgba(59,130,246,.08)}
.asset-empty{padding:18px 2px;color:var(--muted)}
.body-button{--planet-size:16px;position:absolute;transform:translate(-50%,-50%);display:block;width:var(--planet-size);height:var(--planet-size);padding:0;border:0;background:transparent;color:var(--text);pointer-events:auto;cursor:pointer;overflow:visible}
.planet-disc{display:block;width:100%;height:100%;border:1px solid var(--muted);border-radius:50%;background:var(--surface2)}.body-button.sun .planet-disc{border-color:var(--text);background:#74b4ec;box-shadow:0 0 26px rgba(116,180,236,.42)}.body-button.minor .planet-disc{border-style:dashed;background:rgba(148,163,184,.12)}.body-button:hover .planet-disc,.body-button:focus-visible .planet-disc{outline:2px solid var(--accent);outline-offset:3px}
.body-label{position:absolute;top:calc(100% + 5px);left:50%;transform:translateX(-50%);color:var(--muted);white-space:nowrap;pointer-events:none}
.map-caption{position:absolute;top:16px;left:18px;display:flex;align-items:center;gap:9px;max-width:70%;pointer-events:none}.map-caption span{padding:5px 8px;background:rgba(59,130,246,.12);color:#7ab8f6;font-family:var(--font-data)}.map-caption b{font-weight:500}
.map-legend{position:absolute;bottom:16px;left:18px;display:flex;gap:14px;padding:8px 10px;border:1px solid var(--border);background:rgba(19,19,24,.88);color:var(--text)}.map-legend span{display:flex;align-items:center;gap:6px}.asset-dot{width:7px;height:7px;border-radius:50%;background:var(--amber)}.progress-line,.remaining-line{display:inline-block;width:24px;border-top:2px solid #72b4f4}.remaining-line{border-top:1px dashed var(--muted)}
.orbit{fill:none;stroke:rgba(148,163,184,.18);stroke-width:1.1}.orbit.strong{stroke:rgba(248,250,252,.48);stroke-width:1.35}.orbit.minor{stroke-dasharray:3 7;opacity:.65}
.transfer-track{fill:none;stroke:rgba(148,163,184,.38);stroke-width:1.25;stroke-dasharray:4 8}.transfer-progress{fill:none;stroke:#72b4f4;stroke-width:2.25}.route-endpoint{fill:var(--bg);stroke:#72b4f4;stroke-width:1.6}.route-vessel-dot{fill:var(--bg);stroke:#72b4f4;stroke-width:2.5}.route-vessel-heading{stroke:#72b4f4;stroke-width:2;stroke-linecap:round}.route-label-leader{stroke:var(--muted);stroke-width:1;opacity:.7}.route-label-title,.route-label-meta{font-family:var(--font-ui);paint-order:stroke;stroke:var(--bg);stroke-linejoin:round}.route-label-title{fill:var(--text);font-size:12px;font-weight:600;stroke-width:3}.route-label-meta{fill:var(--muted);font-size:10px;stroke-width:3}.route-group.hidden{display:none}
.map-asset{fill:var(--amber);stroke:var(--bg);stroke-width:2}.map-asset-label{fill:var(--text);font-size:11px;font-family:var(--font-ui);paint-order:stroke;stroke:var(--bg);stroke-width:3;stroke-linejoin:round}
.asset-drawer{--panel-width:clamp(270px,20vw,540px);position:absolute;z-index:8;top:0;right:var(--panel-width);bottom:0;width:clamp(320px,23vw,620px);display:flex;flex-direction:column;gap:clamp(13px,.8vw,24px);padding:clamp(18px,1.2vw,36px);border-left:1px solid var(--border);background:var(--surface2);transform:translateX(calc(100% + var(--panel-width)));transition:transform .18s ease;pointer-events:none}.asset-drawer.open{transform:translateX(0);pointer-events:auto}.drawer-close{align-self:flex-end;width:30px;height:30px;border:1px solid var(--border);border-radius:4px;background:transparent;color:var(--muted);font-size:20px;cursor:pointer}.asset-drawer h2{font-size:1.4em}.drawer-fields{display:grid;gap:10px}.drawer-field{padding-top:9px;border-top:1px solid var(--border)}.drawer-field span,.drawer-field strong{display:block}.drawer-field span{margin-bottom:3px;color:var(--muted);font-size:.78em}.drawer-field strong{font-weight:500;overflow-wrap:anywhere}.drawer-link{margin-top:auto;padding:9px 12px;border-radius:4px;background:var(--accent);color:#fff;text-align:center}.drawer-link:hover{text-decoration:none;filter:brightness(1.08)}
@media(max-width:900px){.location-layout{grid-template-columns:minmax(0,1fr) 230px}.asset-drawer{--panel-width:230px;right:230px;width:300px}.live-status{display:none}.map-legend{display:none}}
@media(max-width:680px){.location-toolbar{align-items:flex-start;flex-direction:column}.location-summary{width:100%;overflow-x:auto}.location-layout{grid-template-columns:minmax(0,1fr) 180px}.asset-drawer{--panel-width:180px;right:180px;width:280px}.map-caption{max-width:90%}}
</style>
{% endblock %}
{% block scripts %}
<script>
(() => {
const root = document.getElementById('ksp-location-board');
const assets = JSON.parse(document.getElementById('location-assets-data').textContent);
const ns = 'http://www.w3.org/2000/svg';
const layers = {
orbit: root.querySelector('#orbit-layer'),
route: root.querySelector('#route-layer'),
asset: root.querySelector('#asset-layer')
};
const systems = {
mercury:{name:'Mercury',moons:[]}, venus:{name:'Venus',moons:[]}, earth:{name:'Earth',moons:['moon']},
mars:{name:'Mars',moons:['phobos','deimos']}, ceres:{name:'Ceres',moons:[]}, vesta:{name:'Vesta',moons:[]},
jupiter:{name:'Jupiter',moons:['io','europa','ganymede','callisto']},
saturn:{name:'Saturn',moons:['mimas','enceladus','tethys','dione','rhea','titan','iapetus']},
uranus:{name:'Uranus',moons:['miranda','ariel','umbriel','titania','oberon']},
neptune:{name:'Neptune',moons:['triton']}, pluto:{name:'Pluto',moons:['charon']}
};
const bodyNames = {sun:'Sun',mercury:'Mercury',venus:'Venus',earth:'Earth',moon:'Moon',mars:'Mars',phobos:'Phobos',deimos:'Deimos',ceres:'Ceres',vesta:'Vesta',jupiter:'Jupiter',io:'Io',europa:'Europa',ganymede:'Ganymede',callisto:'Callisto',saturn:'Saturn',mimas:'Mimas',enceladus:'Enceladus',tethys:'Tethys',dione:'Dione',rhea:'Rhea',titan:'Titan',iapetus:'Iapetus',uranus:'Uranus',miranda:'Miranda',ariel:'Ariel',umbriel:'Umbriel',titania:'Titania',oberon:'Oberon',neptune:'Neptune',triton:'Triton',pluto:'Pluto',charon:'Charon',unknown:'Unknown'};
const moonSystems = {moon:'earth',phobos:'mars',deimos:'mars',io:'jupiter',europa:'jupiter',ganymede:'jupiter',callisto:'jupiter',mimas:'saturn',enceladus:'saturn',tethys:'saturn',dione:'saturn',rhea:'saturn',titan:'saturn',iapetus:'saturn',miranda:'uranus',ariel:'uranus',umbriel:'uranus',titania:'uranus',oberon:'uranus',triton:'neptune',charon:'pluto'};
const planets = [
{id:'mercury',angle:-35,size:9,orbit:50},{id:'venus',angle:60,size:12,orbit:78},{id:'earth',angle:-22,size:13,orbit:110},{id:'mars',angle:125,size:11,orbit:140},
{id:'vesta',angle:-135,size:7,orbit:160,kind:'minor'},{id:'ceres',angle:150,size:8,orbit:176,kind:'minor'},{id:'jupiter',angle:32,size:25,orbit:215},
{id:'saturn',angle:210,size:22,orbit:260},{id:'uranus',angle:-28,size:17,orbit:310},{id:'neptune',angle:145,size:17,orbit:360},{id:'pluto',angle:210,size:10,orbit:420,kind:'minor'}
];
let view = {level:'solar',system:null,body:null};
let selectedRoute = null;
let showAllRoutes = false;
let bodyPoints = {};
function svgEl(tag, attrs, text) {
const node = document.createElementNS(ns, tag);
Object.entries(attrs || {}).forEach(([key, value]) => node.setAttribute(key, String(value)));
if (text !== undefined) node.textContent = text;
return node;
}
function clearMap() {
Object.values(layers).forEach(layer => layer.replaceChildren());
root.querySelector('#body-layer').replaceChildren();
bodyPoints = {};
}
function orbit(cx, cy, rx, ry, strong=false, kind='') {
layers.orbit.append(svgEl('ellipse', {cx,cy,rx,ry,class:'orbit '+(strong?'strong ':'')+(kind==='minor'?'minor':'')}));
}
function planetPoint(planet) {
const angle = planet.angle * Math.PI / 180;
return {x:500 + Math.cos(angle) * planet.orbit, y:310 + Math.sin(angle) * planet.orbit * .6};
}
function mapPoint(x,y) {
const stage = root.querySelector('#location-map');
const width = stage.clientWidth || 1000;
const height = stage.clientHeight || 620;
const scale = Math.min(width/1000,height/620);
return {x:(width-1000*scale)/2+x*scale,y:(height-620*scale)/2+y*scale};
}
function bodyButton(id, name, x, y, size, sun=false, kind='') {
bodyPoints[id] = {x,y};
const point = mapPoint(x,y);
const button = document.createElement('button');
button.type = 'button';
button.className = 'body-button '+(sun?'sun ':'')+(kind==='minor'?'minor':'');
button.style.left = point.x+'px';
button.style.top = point.y+'px';
button.style.setProperty('--planet-size','clamp('+size+'px,'+(size/10).toFixed(2)+'vw,'+(size*2)+'px)');
button.setAttribute('aria-label','打开 '+name);
const disc = document.createElement('span'); disc.className = 'planet-disc';
const label = document.createElement('span'); label.className = 'body-label'; label.textContent = name;
button.append(disc,label);
button.addEventListener('click',()=>openBody(id));
root.querySelector('#body-layer').append(button);
}
function systemForBody(body) { return moonSystems[body] || body; }
function visibleTransfer(asset) {
if (!asset.transfer) return false;
if (view.level === 'system') return asset.scope === view.system;
return view.level === 'solar' || view.system === 'sun' ? asset.scope === 'sun' : false;
}
function transferPoint(body) {
const system = systemForBody(body);
return bodyPoints[body] || bodyPoints[system] || null;
}
function drawTransfer(asset, index) {
if (!visibleTransfer(asset)) return;
const from = transferPoint(asset.transfer.from);
const to = transferPoint(asset.transfer.to);
if (!from || !to) return;
const bend = index % 2 ? -1 : 1;
const mx = (from.x + to.x) / 2;
const my = (from.y + to.y) / 2;
const dx = to.x-from.x, dy = to.y-from.y;
const length = Math.max(1,Math.hypot(dx,dy));
const offset = Math.min(85,length*.24)*bend;
const cx = mx-dy/length*offset, cy = my+dx/length*offset;
const d = `M${from.x} ${from.y} Q${cx} ${cy} ${to.x} ${to.y}`;
const group = svgEl('g',{class:'route-group'+((showAllRoutes||selectedRoute===asset.id)?'':' hidden')});
layers.route.append(group);
const track = svgEl('path',{d,class:'transfer-track',pathLength:100}); group.append(track);
group.append(svgEl('path',{d,class:'transfer-progress',pathLength:100,'stroke-dasharray':asset.transfer.progress+' '+(100-asset.transfer.progress)}));
const total = track.getTotalLength();
const start = track.getPointAtLength(0), end = track.getPointAtLength(total);
const point = track.getPointAtLength(total*asset.transfer.progress/100);
const before = track.getPointAtLength(Math.max(0,total*asset.transfer.progress/100-2));
const angle = Math.atan2(point.y-before.y,point.x-before.x)*180/Math.PI;
group.append(svgEl('circle',{cx:start.x,cy:start.y,r:4,class:'route-endpoint'}));
group.append(svgEl('circle',{cx:end.x,cy:end.y,r:4,class:'route-endpoint'}));
const vessel = svgEl('g',{transform:`translate(${point.x} ${point.y}) rotate(${angle})`});
vessel.append(svgEl('circle',{cx:0,cy:0,r:6,class:'route-vessel-dot'}));
vessel.append(svgEl('line',{x1:5,y1:0,x2:15,y2:0,class:'route-vessel-heading'}));
group.append(vessel);
const side = point.x > 760 ? -1 : 1;
const labelX = point.x+side*24, labelY = point.y>340?point.y-20:point.y+27;
const anchor = side<0?'end':'start';
group.append(svgEl('line',{x1:point.x+side*8,y1:point.y-2,x2:labelX-side*4,y2:labelY-5,class:'route-label-leader'}));
group.append(svgEl('text',{x:labelX,y:labelY,'text-anchor':anchor,class:'route-label-title'},asset.name));
group.append(svgEl('text',{x:labelX,y:labelY+14,'text-anchor':anchor,class:'route-label-meta'},`${bodyNames[asset.transfer.from]||asset.transfer.from}${bodyNames[asset.transfer.to]||asset.transfer.to} · ${asset.transfer.progress}%`));
}
function drawAsset(x,y,label) {
layers.asset.append(svgEl('circle',{cx:x,cy:y,r:5,class:'map-asset'}));
layers.asset.append(svgEl('text',{x:x+10,y:y+4,class:'map-asset-label'},label));
}
function drawSolar() {
planets.forEach(p=>orbit(500,310,p.orbit,p.orbit*.6,p.id==='earth'||p.id==='mars',p.kind));
bodyButton('sun','Sun',500,310,34,true);
planets.forEach(p=>{const point=planetPoint(p);bodyButton(p.id,systems[p.id].name,point.x,point.y,p.size,false,p.kind)});
currentAssets().filter(a=>!a.transfer&&a.scope==='sun').slice(0,12).forEach((a,i)=>{const angle=.55+i*1.15;const radius=48+(i%4)*28;drawAsset(500+Math.cos(angle)*radius,310+Math.sin(angle)*radius*.6,a.name)});
assets.filter(a=>a.transfer).forEach(drawTransfer);
}
function drawSystem(systemId) {
const system = systems[systemId]; if (!system) return;
bodyButton(systemId,system.name,500,310,48);
const step = system.moons.length>5?42:system.moons.length>3?48:58;
system.moons.forEach((moon,index)=>{const radius=105+index*step;orbit(500,310,radius,radius*.7,index===0);const angle=.7+index*1.45;bodyButton(moon,bodyNames[moon],500+Math.cos(angle)*radius,310+Math.sin(angle)*radius*.7,12+(index%3)*2)});
assets.filter(a=>a.transfer).forEach(drawTransfer);
}
function drawLocal(bodyId) {
bodyButton(bodyId,systems[bodyId]?.name||bodyNames[bodyId]||bodyId,500,310,68,bodyId==='sun');
orbit(500,310,115,72,true); orbit(500,310,180,112); orbit(500,310,255,158);
currentAssets().filter(a=>!a.transfer).slice(0,20).forEach((a,index)=>{const angle=.4+index*.78;const radius=115+(index%3)*65;drawAsset(500+Math.cos(angle)*radius,310+Math.sin(angle)*radius*.62,a.name)});
}
function openBody(id) {
selectedRoute = null;
if (view.level==='solar') view = id==='sun'?{level:'local',system:'sun',body:'sun'}:{level:'system',system:id,body:null};
else if (view.level==='system') view = {level:'local',system:view.system,body:id};
else if (view.system==='sun'&&id!=='sun') view = {level:'system',system:id,body:null};
render();
}
function currentAssets() {
if (view.level==='solar') return assets;
if (view.system==='sun') return assets.filter(a=>a.scope==='sun');
if (view.level==='system') return assets.filter(a=>a.scope===view.system);
return assets.filter(a=>a.body===view.body);
}
function scopeName() {
if (view.level==='solar') return 'Solar System';
if (view.system==='sun') return 'Heliocentric Assets';
if (view.level==='system') return systems[view.system].name+' System';
return systems[view.body]?.name||bodyNames[view.body]||view.body;
}
function renderBreadcrumbs() {
const wrap = root.querySelector('#location-breadcrumbs'); wrap.replaceChildren();
const items = [{label:'Solar System',action:()=>{view={level:'solar',system:null,body:null};selectedRoute=null;render()}}];
if (view.level!=='solar'&&view.system!=='sun') items.push({label:systems[view.system].name+' System',action:()=>{view={level:'system',system:view.system,body:null};selectedRoute=null;render()}});
if (view.system==='sun') items.push({label:'Heliocentric Assets'}); else if (view.level==='local') items.push({label:scopeName()});
items.forEach((item,index)=>{if(index){const sep=document.createElement('span');sep.className='sep';sep.textContent='';wrap.append(sep)}const button=document.createElement('button');button.type='button';button.textContent=item.label;button.className=index===items.length-1?'current':'';if(item.action)button.addEventListener('click',item.action);wrap.append(button)});
}
function renderList() {
const rows = currentAssets();
root.querySelector('#scope-title').textContent=scopeName(); root.querySelector('#scope-count').textContent=rows.length+' assets'; root.querySelector('#panel-count').textContent=rows.length;
const list=root.querySelector('#asset-list'); list.replaceChildren();
if(!rows.length){const empty=document.createElement('div');empty.className='asset-empty';empty.textContent='当前范围内没有资产';list.append(empty);return}
rows.forEach(asset=>{const button=document.createElement('button');button.type='button';button.className='asset-button '+(String(asset.state).toLowerCase().includes('planned')?'planned ':'')+(selectedRoute===asset.id?'selected':'');button.textContent=asset.name;button.addEventListener('click',()=>{selectedRoute=visibleTransfer(asset)?asset.id:null;render();openDrawer(asset)});list.append(button)});
}
function openDrawer(asset) {
root.querySelector('#drawer-name').textContent=asset.name;
const fields=root.querySelector('#drawer-fields');fields.replaceChildren();
[['类型',asset.asset_type],['状态',asset.state],['位置',asset.location],['任务',asset.mission],['下一事件',asset.next_event]].forEach(([label,value])=>{const row=document.createElement('div');row.className='drawer-field';const key=document.createElement('span');key.textContent=label;const val=document.createElement('strong');val.textContent=value||'—';row.append(key,val);fields.append(row)});
root.querySelector('#drawer-link').href=asset.detail_url;
root.querySelector('#asset-drawer').classList.add('open'); root.querySelector('#asset-drawer').setAttribute('aria-hidden','false');
}
function closeDrawer(){root.querySelector('#asset-drawer').classList.remove('open');root.querySelector('#asset-drawer').setAttribute('aria-hidden','true')}
function render() {
closeDrawer(); clearMap();
if(view.level==='solar'||view.system==='sun')drawSolar();else if(view.level==='system')drawSystem(view.system);else drawLocal(view.body);
const hasRoutes=currentAssets().some(visibleTransfer);
const selected=assets.find(a=>a.id===selectedRoute);
root.querySelector('#view-mode').textContent=view.level==='solar'?'SOLAR SYSTEM':view.system==='sun'?'HELIOCENTRIC':view.level==='system'?'PLANETARY SYSTEM':'LOCAL ORBITS';
root.querySelector('#map-caption').textContent=selected?'已选择转移轨迹 · '+selected.name:showAllRoutes&&hasRoutes?'正在显示全部转移轨迹':view.level==='system'?'选择行星、卫星或转移资产':'选择天体或转移资产';
const toggle=root.querySelector('#transfer-toggle');toggle.checked=showAllRoutes&&hasRoutes;toggle.disabled=!hasRoutes;
renderBreadcrumbs();renderList();
}
root.querySelector('#drawer-close').addEventListener('click',closeDrawer);
root.querySelector('#transfer-toggle').addEventListener('change',event=>{showAllRoutes=event.target.checked;render()});
root.querySelector('#location-reset').addEventListener('click',()=>{view={level:'solar',system:null,body:null};selectedRoute=null;showAllRoutes=false;render()});
let resizeFrame=0;window.addEventListener('resize',()=>{cancelAnimationFrame(resizeFrame);resizeFrame=requestAnimationFrame(render)});
render();
})();
</script>
{% endblock %}