diff --git a/dashboards/forza_composite/index.html b/dashboards/forza_composite/index.html
index 72e5088..f58b9e4 100644
--- a/dashboards/forza_composite/index.html
+++ b/dashboards/forza_composite/index.html
@@ -90,35 +90,63 @@ function draw(cur,max){
ctx.clearRect(0,0,W,H);
const sx=Ca/640*W,sy=Cb/480*H,sr=Cr/640*W;
const gw=Math.max(36,W/18);
- const ae=d2r(Ae),ex=sx+sr*Math.cos(ae),ey=sy+sr*Math.sin(ae);
+ const ae=d2r(Ae),ex=sx+sr*Math.cos(ae),ey=sy+sr*Math.sin(ae),lx=W*.93;
+ const arcLen=Math.PI*2*sr*((Ae-As)/360),lineLen=lx-ex,totalLen=arcLen+lineLen;
const rl=max*.85;
+ // point on total path (0→1 mapped to arc+line)
+ function pathPt(t){
+ const ad=t*totalLen;
+ if(ad<=arcLen){
+ const a=d2r(As+(Ae-As)*(ad/arcLen));
+ return{x:sx+sr*Math.cos(a),y:sy+sr*Math.sin(a)};
+ }
+ const lp=(ad-arcLen)/lineLen;
+ return{x:ex+lp*(lx-ex),y:ey};
+ }
+
// background: arc + line
ctx.beginPath();
ctx.arc(sx,sy,sr,d2r(As),d2r(Ae),false);
- ctx.lineTo(W*.93,ey);
+ ctx.lineTo(lx,ey);
ctx.strokeStyle='rgba(255,255,255,.04)';
ctx.lineWidth=gw;
ctx.stroke();
- // progress: ARC ONLY (0→max RPM fills the 90° arc)
+ // progress along full path
const pc=Math.min(cur/max,1);
if(pc>.005){
+ const ep=pathPt(pc);
ctx.beginPath();
- ctx.arc(sx,sy,sr,d2r(As),d2r(As+(Ae-As)*pc),false);
+ const ad=pc*totalLen;
+ if(ad<=arcLen){
+ ctx.arc(sx,sy,sr,d2r(As),d2r(As+(Ae-As)*(ad/arcLen)),false);
+ }else{
+ ctx.arc(sx,sy,sr,d2r(As),d2r(Ae),false);
+ ctx.lineTo(ep.x,ey);
+ }
ctx.strokeStyle=cur>=rl?'rgba(255,50,35,.95)':'rgba(255,255,255,.85)';
ctx.lineWidth=gw;
ctx.stroke();
}
- // scale marks
+ // scale marks along TOTAL path (arc + line)
const mr=sr-gw/2-6,pr=sr+gw/2+6;
for(let r=0;r<=max;r+=2000){
- const a=d2r(As+(Ae-As)*(r/max));
- ctx.beginPath();ctx.moveTo(sx+mr*Math.cos(a),sy+mr*Math.sin(a));ctx.lineTo(sx+pr*Math.cos(a),sy+pr*Math.sin(a));
- ctx.strokeStyle=r>=rl?'rgba(255,255,255,.15)':'rgba(255,255,255,.08)';
- ctx.lineWidth=r%4000===0?2.5:1;ctx.stroke();
- if(r%4000===0){ctx.fillStyle='rgba(255,255,255,.22)';ctx.font=`${Math.max(13,W/38)|0}px 'NEXT ART',sans-serif`;ctx.textAlign='center';ctx.fillText(Math.round(r/1000),sx+(pr+22)*Math.cos(a),sy+(pr+22)*Math.sin(a)+5)}
+ const t=r/max,p=pathPt(t);
+ if(r*totalLen/max<=arcLen){
+ const a=d2r(As+(Ae-As)*t);
+ ctx.beginPath();ctx.moveTo(sx+mr*Math.cos(a),sy+mr*Math.sin(a));ctx.lineTo(sx+pr*Math.cos(a),sy+pr*Math.sin(a));
+ ctx.strokeStyle=r>=rl?'rgba(255,255,255,.15)':'rgba(255,255,255,.08)';
+ ctx.lineWidth=r%4000===0?2.5:1;ctx.stroke();
+ if(r%4000===0){ctx.fillStyle='rgba(255,255,255,.22)';ctx.font=`${Math.max(13,W/38)|0}px 'NEXT ART',sans-serif`;ctx.textAlign='center';ctx.fillText(Math.round(r/1000),sx+(pr+22)*Math.cos(a),sy+(pr+22)*Math.sin(a)+5)}
+ }else{
+ // on line portion: vertical marks
+ ctx.beginPath();ctx.moveTo(p.x,ey-gw/2-4);ctx.lineTo(p.x,ey+gw/2+4);
+ ctx.strokeStyle=r>=rl?'rgba(255,255,255,.15)':'rgba(255,255,255,.08)';
+ ctx.lineWidth=r%4000===0?2.5:1;ctx.stroke();
+ if(r%4000===0){ctx.fillStyle='rgba(255,255,255,.22)';ctx.font=`${Math.max(13,W/38)|0}px 'NEXT ART',sans-serif`;ctx.textAlign='center';ctx.fillText(Math.round(r/1000),p.x,ey+gw/2+22)}
+ }
}
// RPM number