const defs=[ {key:'P',title:'红色火星站',color:'#ff3b52',light:'#ff8a9b',dark:'#c40f31',deep:'#4a0416',desc:'火星基地监控台:检查氧气、通讯和火箭准备状态。',facts:['氧气储备 82%','基地温度 -63°C','通讯延迟 3.2 秒']}, {key:'Q',title:'蓝色深海舱',color:'#357bff',light:'#8ab4ff',dark:'#1146d6',deep:'#041348',desc:'深海实验舱控制台:观察压载、声呐和探照灯。',facts:['水深 3820 米','舱内压强 1.0 atm','声呐信号正常']}, {key:'R',title:'绿色能量站',color:'#00d164',light:'#7dffb4',dark:'#009e49',deep:'#02321a',desc:'绿色能源调度台:管理风机、储能和供电负载。',facts:['风机功率 46 MW','储能电量 73%','今日减排 128 吨']}, {key:'T',title:'黄色指挥所',color:'#ffdb00',light:'#fff176',dark:'#ff9800',deep:'#4a2c00',desc:'应急指挥所大屏:核对地图、队伍和命令下达。',facts:['在线队伍 6 组','待处理事件 2 件','指挥频道已加密']}, {key:'Y',title:'紫色观测台',color:'#c159ff',light:'#e4a6ff',dark:'#7c3aed',deep:'#22065a',desc:'星空观测台:记录星图、光谱和观测日志。',facts:['目标星等 8.4','光谱类型 K2','曝光时间 30 秒']}, {key:'U',title:'橙色发射场',color:'#ff7a18',light:'#ffb377',dark:'#e03a00',deep:'#451000',desc:'发射场控制台:确认倒计时、气象和外部通讯。',facts:['倒计时 T-12:00','风速 4.6 m/s','发射窗口 14:40']} ]; const pathPage=Number((location.pathname.match(/\/page\/([1-6])\/?$/)||[])[1]); const no=pathPage||Number(document.body.dataset.trainerPage)||1; const def=defs[no-1]; document.body.classList.add('training'); document.body.style.setProperty('--page-color',def.color); document.body.style.setProperty('--page-light',def.light); document.body.style.setProperty('--page-dark',def.dark); document.body.style.setProperty('--page-deep',def.deep); let themeMeta=document.querySelector('meta[name="theme-color"]'); if(!themeMeta){themeMeta=document.createElement('meta');themeMeta.name='theme-color';document.head.appendChild(themeMeta)} themeMeta.content=def.dark; document.body.dataset.trainerPage=String(no); document.title=`${no}. ${def.title}`; document.getElementById('pageTitle').textContent=`${no}. ${def.title}`; document.getElementById('pageHeading').textContent=`${no}. ${def.title}`; document.getElementById('pageDesc').textContent=def.desc; const facts=document.getElementById('pageFacts');facts.innerHTML='';def.facts.forEach(text=>{const el=document.createElement('div');el.textContent=text;facts.appendChild(el)}); const mark=document.getElementById('mark');mark.textContent=no;mark.style.background='rgba(255,255,255,.18)';mark.style.boxShadow='inset 0 0 0 2px #ffffff70'; const buttons=['火箭准备','燃料检查','通讯开启','系统诊断','备用电源','外部链接']; const ribbon=document.getElementById('ribbon'); let ribbonKey='K'; function openLink(item){ window._linkMenu=false; window._panel=null; const popup=window.open(item.url,'_blank','noopener'); renderRibbon('B',false); flash(popup?`正在打开 ${item.name}`:'浏览器拦截了弹窗,请允许本站弹出式窗口'); reportEvent({type:'url',url:item.url,page:no}); } function openLinkMenu(){ window._panel=null; window._linkMenu=true; renderRibbon('B',true); flash('请按 D 打开百度,或按 F 打开 Bilibili(不必按着 Alt)'); } function activateButton(index,via='click'){ const name=buttons[index-1]; if(name==='外部链接'){ flash(via==='key'?'已打开外部链接菜单':'已点击:外部链接'); reportEvent({type:'button',page:no,index,name}); openLinkMenu(); return; } flash(via==='key'?`快捷键已触发:${index}. ${name}`:`已点击:${name}`); reportEvent({type:'button',page:no,index,name}); } function renderRibbon(activeKey=ribbonKey,keyboardOn=!!window._panel||!!window._linkMenu){ ribbonKey=activeKey; ribbon.className='ribbon'+(keyboardOn?' keyboard-on':''); ribbon.innerHTML=''; const tabs=document.createElement('div'); tabs.className='ribbon-tabs'; RIBBON_PANELS.forEach(panel=>{ const tab=document.createElement('button'); tab.type='button'; tab.className='ribbon-tab'+(panel.key===activeKey?' active':'')+(window._panel===panel.key||(window._linkMenu&&panel.key==='B')?' armed':''); tab.innerHTML=`Alt+${panel.key}${panel.title}`; tab.onclick=()=>{window._panel=null;window._linkMenu=false;renderRibbon(panel.key,false)}; tabs.appendChild(tab); }); const body=document.createElement('div'); body.className='ribbon-body'; if(window._linkMenu){ const pane=document.createElement('div'); pane.className='ribbon-panel active'; const title=document.createElement('div'); title.className='ribbon-subhint'; title.textContent='外部链接:按 D 打开百度,按 F 打开 Bilibili'; body.appendChild(title); LINK_TARGETS.forEach(item=>{ const b=document.createElement('button'); b.type='button'; b.className='ribbon-cmd '+(item.key==='A'?'success':'ghost'); b.innerHTML=`${item.key}${item.name}`; b.onclick=()=>openLink(item); pane.appendChild(b); }); body.appendChild(pane); }else{ RIBBON_PANELS.forEach(panel=>{ const pane=document.createElement('div'); pane.className='ribbon-panel'+(panel.key===activeKey?' active':''); panel.items.forEach(item=>{ const b=document.createElement('button'); b.type='button'; b.className='ribbon-cmd '+(item.index%2?'success':'ghost'); b.innerHTML=`${item.key}${item.index}. ${item.name}`; b.onclick=()=>{window._panel=null;window._linkMenu=false;renderRibbon(panel.key,false);activateButton(item.index,'click')}; pane.appendChild(b); }); body.appendChild(pane); }); } ribbon.appendChild(tabs); ribbon.appendChild(body); } function openRibbonPanel(key){ const panel=RIBBON_PANELS.find(item=>item.key===key); if(!panel)return; window._linkMenu=false; window._panel=key; renderRibbon(key,true); const letters=panel.items.map(item=>item.key).join(' 或 '); flash(`已展开「${panel.title}」面板,请按 ${letters}`); } renderRibbon('K',false); (async()=>{const s=await API.get('/api/student/state');if(s.authenticated){await applyStudentState(s);setInterval(refreshStudent,1000)}else location.href='/'})(); reportEvent({type:'page',page:no}); const ctx=document.getElementById('contextMenu'); document.addEventListener('keydown',e=>{ if(e.repeat)return; const k=keyName(e); const menu=!ctx.classList.contains('hidden'); if(menu){const map={E:'refresh',R:'rename',C:'copy'};if(map[k]){consumeKey(e);ctx.classList.add('hidden');flash(`快捷菜单:${map[k]}`);reportEvent({type:'menu',item:map[k],page:no});return}} if(window._linkMenu){ const item=linkByKey(k); if(item){consumeKey(e);openLink(item);return} if(k==='ESCAPE'){consumeKey(e);window._linkMenu=false;renderRibbon('B',false);return} if(!e.altKey){consumeKey(e);return} } if(e.altKey&&!e.ctrlKey&&!e.metaKey&&!e.shiftKey&&/^[1-6]$/.test(k)){consumeKey(e);window._panel=null;window._linkMenu=false;renderRibbon(ribbonKey,false);activateButton(Number(k),'key');return} if(e.altKey&&!e.ctrlKey&&!e.metaKey&&RIBBON_PANELS.some(panel=>panel.key===k)){consumeKey(e);openRibbonPanel(k);return} if(window._panel){ const panel=RIBBON_PANELS.find(item=>item.key===window._panel); const item=panel.items.find(cmd=>cmd.key===k); if(item){consumeKey(e);window._panel=null;renderRibbon(panel.key,false);activateButton(item.index,'key');return} if(k==='ESCAPE'){consumeKey(e);window._panel=null;renderRibbon(panel.key,false);return} } },true); document.addEventListener('contextmenu',e=>{e.preventDefault();ctx.style.left=Math.min(e.clientX,innerWidth-210)+'px';ctx.style.top=Math.min(e.clientY,innerHeight-160)+'px';ctx.classList.remove('hidden')}); document.addEventListener('click',e=>{if(!ctx.contains(e.target))ctx.classList.add('hidden');if((window._panel||window._linkMenu)&&ribbon&&!ribbon.contains(e.target)){window._panel=null;window._linkMenu=false;renderRibbon(ribbonKey,false)}}); document.querySelectorAll('[data-menu]').forEach(b=>b.onclick=async()=>{ctx.classList.add('hidden');flash('快捷菜单:'+b.dataset.menu);await reportEvent({type:'menu',item:b.dataset.menu,page:no})});