87 lines
4.0 KiB
HTML
87 lines
4.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
|
|
<title>TurboSu - 手机仪表</title>
|
|
<style>
|
|
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
|
|
html,body{width:100%;height:100%;overflow:hidden;background:#0a0a0f;color:#fff;
|
|
font-family:'SF Pro Display',-apple-system,system-ui,sans-serif}
|
|
#root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}
|
|
#box{position:relative;overflow:hidden}
|
|
#box.contain{max-width:100vw;max-height:100vh}
|
|
#box.cover{min-width:100vw;min-height:100vh}
|
|
#box.fill{width:100vw;height:100vh}
|
|
#content{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:min(3vh,16px);background:#0a0a0f}
|
|
|
|
.gear{font-size:min(20vw,120px);font-weight:200;line-height:1}
|
|
.rpm-num{font-size:min(10vw,56px);font-weight:300;letter-spacing:-.02em}
|
|
.rpm-unit{font-size:min(3vw,14px);color:rgba(255,255,255,.2);letter-spacing:.1em}
|
|
.rpm-bar{width:min(60vw,200px);height:6px;background:rgba(255,255,255,.06);border-radius:3px;overflow:hidden}
|
|
.rpm-fill{height:100%;border-radius:3px;transition:width .06s,background .2s}
|
|
.shift-strip{display:flex;gap:4px}
|
|
.shift-led{width:min(6vw,28px);height:min(4vw,18px);border-radius:3px;background:rgba(255,255,255,.04);transition:background .06s,box-shadow .06s}
|
|
.shift-led.on{background:#34C759;box-shadow:0 0 8px rgba(52,199,89,.4)}
|
|
.shift-led.warn{background:#FF9F0A;box-shadow:0 0 12px rgba(255,159,10,.5)}
|
|
.shift-led.red{background:#E94634;animation:pulse .3s infinite alternate}
|
|
@keyframes pulse{from{box-shadow:0 0 16px rgba(233,70,52,.6)}to{box-shadow:0 0 32px rgba(255,50,30,1)}}
|
|
|
|
.speed-num{font-size:min(8vw,48px);font-weight:300;color:rgba(255,255,255,.6)}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="root"><div id="box" class="contain"><div id="content">
|
|
<div class="shift-strip" id="shift-row"></div>
|
|
<div class="rpm-num" id="rpm">0</div>
|
|
<div class="rpm-unit">RPM</div>
|
|
<div class="rpm-bar"><div class="rpm-fill" id="rpm-fill"></div></div>
|
|
<div class="gear" id="gear">N</div>
|
|
<div class="speed-num" id="speed">0</div>
|
|
<div class="rpm-unit">KM/H</div>
|
|
</div></div></div>
|
|
<script>
|
|
const M={aspect_ratio:"9:16",render_mode:"contain"},NL=10;
|
|
(function(){
|
|
const box=document.getElementById('box');
|
|
const proto=location.protocol==='https:'?'wss:':'ws:',wsUrl=proto+'//'+location.host+'/ws';
|
|
let ws,rt,leds=[];
|
|
const shiftRow=document.getElementById('shift-row');
|
|
for(let i=0;i<NL;i++){const d=document.createElement('div');d.className='shift-led';shiftRow.appendChild(d);leds.push(d)}
|
|
|
|
function pr(r){if(!r||r==='auto')return null;const p=r.split(':');if(p.length===2)return +p[0]/+p[1]}
|
|
|
|
function apply(){
|
|
const rt=pr(M.aspect_ratio||'9:16'),md=M.render_mode||'contain';
|
|
if(!rt){box.style.width='100vw';box.style.height='100vh';box.className='fill';return}
|
|
const vw=window.innerWidth,vh=window.innerHeight,vr=vw/vh;let cw,ch;
|
|
if(md==='cover'){cw=vw;ch=vw/rt;box.className='cover'}
|
|
else{box.className='contain';cw=vr>rt?vh*rt:vw;ch=vr>rt?vh:vw/rt}
|
|
box.style.width=cw+'px';box.style.height=ch+'px'
|
|
}
|
|
|
|
function cn(){ws=new WebSocket(wsUrl);ws.onopen=()=>{if(rt){clearTimeout(rt);rt=null}}
|
|
ws.onmessage=(e)=>{try{const m=JSON.parse(e.data);if(m.type==='telemetry'){
|
|
const d=m.data,rpm=d.rpm||0,max=d.max_rpm||8000,pct=rpm/max;
|
|
document.getElementById('rpm').textContent=Math.round(rpm);
|
|
const f=document.getElementById('rpm-fill');f.style.width=(pct*100)+'%';
|
|
f.style.background=pct>.9?'#E94634':pct>.7?'#FF9F0A':'#3482FF';
|
|
const g=d.gear||0;document.getElementById('gear').textContent=g===0?'R':String(g);
|
|
document.getElementById('speed').textContent=Math.round(d.speed_kmh||0);
|
|
for(let i=0;i<NL;i++){
|
|
const t=i/NL;let cls='shift-led';
|
|
if(t<=pct){
|
|
if(i>=Math.floor(NL*.85))cls+=' red';
|
|
else if(i>=Math.floor(NL*.65))cls+=' warn';
|
|
else cls+=' on';
|
|
}
|
|
leds[i].className=cls;
|
|
}
|
|
}}catch(e){}};ws.onclose=()=>{rt=setTimeout(cn,2000)}}
|
|
|
|
apply();window.addEventListener('resize',apply);cn()
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|