const t=500,e=10,i=100,a=.5,s=60,n=1/30,r=[{r:13,g:248,b:122,a:102},{r:114,g:255,b:92,a:89},{r:114,g:255,b:92,a:0}],o=[{r:242,g:242,b:242,a:102},{r:242,g:242,b:242,a:89},{r:242,g:242,b:242,a:0}];function l(t,e){const i=[[0,0,0],[0,0,0],[0,0,0]];for(let a=0;a<3;a++)for(let s=0;s<3;s++)i[a][s]=t[a][0]*e[0][s]+t[a][1]*e[1][s]+t[a][2]*e[2][s];return i}function d(t,e){return[t[0][0]*e[0]+t[0][1]*e[1]+t[0][2]*e[2],t[1][0]*e[0]+t[1][1]*e[1]+t[1][2]*e[2],t[2][0]*e[0]+t[2][1]*e[1]+t[2][2]*e[2]]}function h(t,e,i,a){const s=t[0][0]*e+t[0][1]*i+t[0][2]*a,n=t[1][0]*e+t[1][1]*i+t[1][2]*a,r=t[2][0]*e+t[2][1]*i+t[2][2]*a;return Math.abs(r)<1e-6?{x:0,y:0,z:0,valid:!1}:{x:s/r,y:n/r,z:r,valid:!0}}function c(t,e,i){return Math.max(e,Math.min(i,t))}function _(t){const[e,i,a]=t,s=Math.cos(e),n=Math.sin(e),r=Math.cos(i),o=Math.sin(i),l=Math.cos(a),d=Math.sin(a);return[[l*r,l*o*n-d*s,l*o*s+d*n],[d*r,d*o*n+l*s,d*o*s-l*n],[-o,r*n,r*s]]}const g=[[0,-1,0],[0,0,-1],[1,0,0]],u={tici:{fcam:{intrinsics:[[2648,0,964],[0,2648,604],[0,0,1]],width:1928,height:1208}},mici:{fcam:{intrinsics:[[1141.5,0,672],[0,1141.5,380],[0,0,1]],width:1344,height:760}}};class p{constructor(){window.debug,this._longitudinal_control=!1,this._experimental_mode=!1,this._blend_factor=1,this._prev_allow_throttle=!0,this._lane_line_probs=new Float32Array(4),this._road_edge_stds=new Float32Array(2),this._lead_vehicles=[{},{}],this._path_offset_z=1.22,this._use_simple_lines=!0,this._path={raw_points:[],projected_points:[]},this._lane_lines=[{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]}],this._road_edges=[{raw_points:[],projected_points:[]},{raw_points:[],projected_points:[]}],this._acceleration_x=new Float32Array(0),this._maxPoints=200,this._leftPointsBuffer=new Array(this._maxPoints),this._rightPointsBuffer=new Array(this._maxPoints),this._transformCache=new Map,this._transformCacheValid=!1,this._batchTransformEnabled=!0,this._batchLeftX=new Float32Array(this._maxPoints),this._batchLeftY=new Float32Array(this._maxPoints),this._batchLeftZ=new Float32Array(this._maxPoints),this._batchRightX=new Float32Array(this._maxPoints),this._batchRightY=new Float32Array(this._maxPoints),this._batchRightZ=new Float32Array(this._maxPoints),this._gradientCache=new Map,this._gradientCacheMaxSize=20,this._pathCache=new Map,this._pathCacheMaxSize=10,this._lastPathPoints=null,this._lastPathHash=null,this._pathLengthCache=new Map,this._lastPathLengthCacheClear=0,this._framePathLengthCache=new Map,this._lastFrameTime=0,this._car_space_transform=[[1,0,0],[0,1,0],[0,0,1]],this._transform_dirty=!0,this._clip_region=null,this._rect=null,setInterval(()=>{this._transformCache.clear(),this._pathLengthCache.clear(),this._gradientCache.clear(),window.debug},3e4),this._exp_gradient={start:[0,1],end:[0,0],colors:[],stops:[]}}set_transform(t){window.debug,this._car_space_transform=t,this._transform_dirty=!0,this._transformCache.clear(),this._transformCacheValid=!1,this._gradientCache.clear(),this._transform_logged||(this._transform_logged=!0)}set_simple_lines_mode(t){window.debug,this._use_simple_lines=t}clear_caches(){window.debug,this._transformCache.clear(),this._transformCacheValid=!1,this._gradientCache.clear(),this._pathCache.clear(),this._lastPathHash=null}render(t,e,i){if(window.debug,this._rect=t,this.ctx=i,i.lineCap="round",i.lineJoin="round",this._debug_logged||(this._debug_logged=!0),!e.liveCalibration||!e.modelV2)return;this._clip_region={x:t.x,y:t.y,width:t.width,height:t.height},e.selfdriveState&&(this._experimental_mode=e.selfdriveState.experimentalMode||!1);const a=e.liveCalibration;a&&a.height&&a.height.length>0&&(this._path_offset_z=a.height[0]),e.updated&&e.updated.carParams?this._longitudinal_control=e.carParams.openpilotLongitudinalControl||!1:e.seen&&e.seen.carParams||(this._longitudinal_control=!0);const s=e.modelV2;if(!s)return;const n=e.valid&&e.valid.radarState?e.radarState:null,r=n?n.leadOne:null,o=this._longitudinal_control&&null!==n;!this._radar_logged&&n&&(this._radar_logged=!0);const l=e.updated&&e.updated.modelV2,d=e.updated&&e.updated.radarState;if(this._update_logged||!l&&!d||(this._update_logged=!0),l||d||this._transform_dirty){l&&this._update_raw_points(s);const t=this._path.raw_points.map(t=>t[0]);if(0===t.length)return;this._update_model(r,t),o&&this._update_leads(n,t),this._transform_dirty=!1}this._draw_lane_lines(),this._draw_path(e),o&&n&&this._draw_lead_indicator()}_update_raw_points(t){if(window.debug,t.position){this._path.raw_points.length=0;for(let e=0;e200){Array.from(this._transformCache.keys()).slice(0,100).forEach(t=>this._transformCache.delete(t)),window.debug}if(this._pathCache.size>this._pathCacheMaxSize){const t=Array.from(this._pathCache.entries()).slice(-5);this._pathCache.clear(),t.forEach(([t,e])=>this._pathCache.set(t,e))}t.laneLines&&t.laneLines.forEach((t,e)=>{this._lane_lines[e].raw_points.length=0;for(let i=0;i{this._road_edges[e].raw_points.length=0;for(let i=0;it[0]),s);this._batch_update_lines(n,t,a),this._update_experimental_gradient(this._rect.height)}_batch_update_lines(t,a,s){const n=[];for(let e=0;et[0]>=0);t.length>0&&(i.push({...a,points:t,startIdx:e,numPoints:t.length}),e+=2*t.length)}if(0===e)return;const a=new Float32Array(e),s=new Float32Array(e),n=new Float32Array(e);let r=0;for(const t of i)for(let e=0;e1){const t=[],e=[];let s=1/0;for(let n=0;ne.push(t));for(let t=a.length-1;t>=0;t--)e.push(a[t]);t.line.projected_points=e}}_update_leads(t,e){window.debug,this._lead_vehicles=[{},{}];const i=[t.leadOne,t.leadTwo],a=[],s={x:[],y:[],z:[]};for(let t=0;t=1e-6){const t=1/v;s[n]={x:m*t,y:b*t,valid:!0},r++}else s[n]={x:0,y:0,valid:!1}}return{points:s,validCount:r}}_map_line_to_polygon(t,e,i,a,s=!0){if(window.debug,0===t.length)return[];let n=t.slice(0,a+1);if(n=n.filter(t=>t[0]>=0),0===n.length)return[];const r=n.length,o=[],l=[];if(this._batchTransformEnabled&&r>10){for(let t=0;t1){const t=[],e=[];let i=1/0;for(let a=0;ad.push(t));for(let t=l.length-1;t>=0;t--)d.push(l[t]);return d}_get_path_length_idx(t,e){if(window.debug,0===t.length)return 0;const i=`${t.length}_${e.toFixed(2)}`,a=Date.now();if(a!==this._lastFrameTime&&(this._framePathLengthCache.clear(),this._lastFrameTime=a),this._framePathLengthCache.has(i))return this._framePathLengthCache.get(i);if(this._pathLengthCache.has(i)){const t=this._pathLengthCache.get(i);return this._framePathLengthCache.set(i,t),t}a-this._lastPathLengthCacheClear>6e4&&(this._pathLengthCache.clear(),this._lastPathLengthCacheClear=a);let s=0;for(let i=0;i{if(0===t.projected_points.length)return;const i=c(this._lane_line_probs[e],0,.7);this._draw_polygon(t.projected_points,`rgba(255, 255, 255, ${i})`)}),this._road_edges.forEach((t,e)=>{if(0===t.projected_points.length)return;const i=c(1-this._road_edge_stds[e],0,1);this._draw_polygon(t.projected_points,`rgba(255, 0, 0, ${i})`)}))}_batch_draw_all_lines(){const t=[];if(this._lane_lines.forEach((e,i)=>{if(e.raw_points.length<2)return;const a=this._lane_line_probs[i];if(a<.1)return;const s=c(a,0,.7);t.push({points:e.raw_points,color:`rgba(255, 255, 255, ${s})`,lineWidth:4})}),this._road_edges.forEach((e,i)=>{if(e.raw_points.length<2)return;const a=this._road_edge_stds[i];if(a>.9)return;const s=c(1-a,0,1);t.push({points:e.raw_points,color:`rgba(255, 0, 0, ${s})`,lineWidth:4})}),0===t.length)return;let e=0;const i=[];for(const a of t){const t=[];for(let e=0;e=0&&i[0]<=100&&t.push(e)}t.length>=2&&(i.push({...a,validIndices:t,startIdx:e,numPoints:t.length}),e+=t.length)}if(0===e)return;const a=new Float32Array(e),s=new Float32Array(e),n=new Float32Array(e);let r=0;for(const t of i)for(const e of t.validIndices){const i=t.points[e];a[r]=i[0],s[r]=i[1],n[r]=i[2],r++}const o=this._batch_transform(a,s,n,e),l=this.ctx;for(const t of i){l.strokeStyle=t.color,l.lineWidth=t.lineWidth,l.beginPath();let e=!1;for(let i=0;i0&&t.validIndices[i]-t.validIndices[i-1]===1?l.lineTo(s.x,s.y):(l.stroke(),l.beginPath(),l.moveTo(s.x,s.y)):(l.moveTo(s.x,s.y),e=!0):e&&(l.stroke(),l.beginPath(),e=!1)}e&&l.stroke()}}_draw_path(t){if(window.debug,0!==this._path.projected_points.length)if(this._experimental_mode)this._exp_gradient.colors.length>2?this._draw_polygon_gradient(this._path.projected_points,this._exp_gradient):this._draw_polygon(this._path.projected_points,"rgba(255, 255, 255, 0.12)");else{const e=t.longitudinalPlan&&t.longitudinalPlan.allowThrottle||!this._longitudinal_control;e!==this._prev_allow_throttle&&(this._prev_allow_throttle=e,this._blend_factor=Math.max(1-this._blend_factor,0)),this._blend_factor<1&&(this._blend_factor=Math.min(this._blend_factor+n,1));const i=e?o:r,a=e?r:o,s={start:[0,1],end:[0,0],colors:this._blend_colors(i,a,this._blend_factor),stops:[0,.5,1]};this._draw_polygon_gradient(this._path.projected_points,s)}}_draw_polygon(t,e){if(window.debug,t.length<3)return;const i=this.ctx;i.fillStyle=e;const a=this._getCachedPath2D(t);i.fill(a)}_getCachedPath2D(t){window.debug;const e=this._generatePointsHash(t);if(this._pathCache.has(e))return this._pathCache.get(e);const i=new Path2D;i.moveTo(t[0][0],t[0][1]);for(let e=1;ethis._pathCacheMaxSize){const t=this._pathCache.keys().next().value;this._pathCache.delete(t)}return i}_generatePointsHash(t){if(window.debug,t.length<3)return"empty";const e=t[0],i=t[Math.floor(t.length/2)],a=t[t.length-1],s=t=>Math.round(10*t)/10;return`${t.length}:${s(e[0])},${s(e[1])}:${s(i[0])},${s(i[1])}:${s(a[0])},${s(a[1])}`}_draw_simple_line(t,e,a=2){if(window.debug,t.length<2)return;const s=this.ctx;s.strokeStyle=e,s.lineWidth=a;const n=Math.min(i,t[t.length-1][0]||i),r=[],o=[],l=[],d=[];for(let e=0;e=0&&i[0]<=n&&(r.push(e),o.push(i[0]),l.push(i[1]),d.push(i[2]))}if(r.length<2)return;const h=this._batch_transform(new Float32Array(o),new Float32Array(l),new Float32Array(d),o.length);let c=!1;s.beginPath();for(let t=0;t0&&r[t]-r[t-1]===1?s.lineTo(e.x,e.y):(s.stroke(),s.beginPath(),s.moveTo(e.x,e.y)):(s.moveTo(e.x,e.y),c=!0):c&&(s.stroke(),s.beginPath(),c=!1)}c&&s.stroke()}_draw_polygon_gradient(t,e){if(window.debug,t.length<3)return;const i=this.ctx,a=this._rect,s=e.start[0]*a.width+a.x,n=e.start[1]*a.height+a.y,r=e.end[0]*a.width+a.x,o=e.end[1]*a.height+a.y,l=this._getCachedGradient(i,s,n,r,o,e),d=this._getCachedPath2D(t);i.fillStyle=l,i.fill(d)}_getCachedGradient(t,e,i,a,s,n){window.debug;const r=`${Math.round(e)},${Math.round(i)},${Math.round(a)},${Math.round(s)}:${n.colors.map(t=>`${t.r},${t.g},${t.b},${t.a}`).join("|")}:${n.stops.join(",")}`;if(this._gradientCache.has(r))return this._gradientCache.get(r);const o=t.createLinearGradient(e,i,a,s);for(let t=0;tthis._gradientCacheMaxSize){const t=this._gradientCache.keys().next().value;this._gradientCache.delete(t)}return o}_blend_colors(t,e,i){if(window.debug,i>=1)return e;if(i<=0)return t;const a=1-i;return t.map((t,s)=>{const n=e[s];return{r:Math.round(a*t.r+i*n.r),g:Math.round(a*t.g+i*n.g),b:Math.round(a*t.b+i*n.b),a:Math.round(a*t.a+i*n.a)}})}_update_experimental_gradient(t){window.debug,this._exp_gradient.colors=[],this._exp_gradient.stops=[]}_update_lead_vehicle(t,e,i,a){window.debug;const s=1.57*c(750/(t/3+30),15,30)*1;this._lead_size_logged||(this._lead_size_logged=!0);const n=c(i[0],0,a.width-s/2),r=Math.min(i[1],a.height-.6*s),o=s/5,l=s/10;let d=0;return t<40&&(d=255*(1-t/40),e<0&&(d+=e/10*-1*255),d=Math.min(d,255)),{glow:[[n+1.35*s+o,r+s+l],[n,r-l],[n-1.35*s-o,r+s+l]],chevron:[[n+1.25*s,r+s],[n,r],[n-1.25*s,r+s]],fill_alpha:d}}_draw_lead_indicator(){window.debug,this._lead_debug_logged||(this._lead_debug_logged=!0),this._lead_vehicles.forEach(t=>{t.glow&&t.chevron&&(this._draw_triangle_fan(t.glow,"rgba(218, 202, 37, 1)"),this._draw_triangle_fan(t.chevron,`rgba(201, 34, 49, ${t.fill_alpha/255})`))})}_draw_triangle_fan(t,e){if(window.debug,t.length<3)return;const i=this.ctx;i.fillStyle=e;const a=t[0],s=new Path2D;for(let e=1;e0&&this.set_speed0&&e-this.started_time>5e3?G:null;t.updated&&t.updated.selfdriveState&&(this.last_selfdrive_time=e);const i=t.deviceState;if(i&&["tici","tizi","mici"].includes(i.deviceType)&&this.last_selfdrive_time>0){const i=(e-this.last_selfdrive_time)/1e3;if(i>5){const e=t.selfdriveState;return e&&e.enabled&&i-515?A:I;this.ctx.font=`bold ${i}px Arial`,this.ctx.textBaseline="middle",this.ctx.fillText(e.text1,t.x+t.width/2,t.y+t.height/2),this.ctx.font="bold 48px Arial",this.ctx.textBaseline="top",this.ctx.fillText(e.text2,t.x+t.width/2,t.y+2*t.height/3)}}}window.AlertRenderer=j;const N=8,H="calibrated",W=[1.22],q={DISENGAGED:{r:23,g:51,b:73,a:200},OVERRIDE:{r:145,g:155,b:149,a:241},ENGAGED:{r:23,g:134,b:68,a:241}};function c(t,e,i){return Math.max(e,Math.min(i,t))}class U{constructor(){window.debug,this.device_camera=null,this.view_from_calib=[...g.map(t=>[...t])],this._last_calib_time=0,this._last_rect_dims={width:0,height:0},this._cached_matrix=null,this._content_rect={x:0,y:0,width:0,height:0},this.model_renderer=new p,this._hud_renderer=new C,this.alert_renderer=new j,this._click_callback=null}render(t,e,i){window.debug,this.sm=e,this.ctx=i,this._update_calibration();const a=t.width-16,s=t.height-16,n=this.device_camera||u.tici,r=a/s,o=n.fcam.width/n.fcam.height;let l=a,d=s,h=t.x+8,c=t.y+8;r>o?(l=s*o,h=t.x+8+(a-l)/2):(d=a/o,c=t.y+8+(s-d)/2),this._content_rect={x:h,y:c,width:l,height:d},this._draw_border(t),i.save(),i.beginPath(),i.rect(Math.floor(t.x),Math.floor(t.y),Math.floor(t.width),Math.floor(t.height)),i.clip(),this._calc_frame_matrix(),this.model_renderer.render(t,e,i),i.restore(),this._hud_renderer.render(t,e,i);this.alert_renderer.render(t,e,i);this._hud_renderer.handle_mouse_event(t)||this._click_callback}set_callbacks(t=null){window.debug,this._click_callback=t}invalidate_transform_cache(){window.debug,this._cached_matrix=null,this._last_rect_dims={width:0,height:0},this._last_calib_time=0,this.model_renderer&&this.model_renderer.clear_caches(),this.model_renderer&&(this.model_renderer._transform_dirty=!0)}_update_calibration(){window.debug;const t=this.sm;if(!this.device_camera&&t.seen&&t.seen.roadCameraState&&t.seen.deviceState){const e=t.deviceState?t.deviceState.deviceType:"tici";this.device_camera="mici"===e?u.mici:u.tici}if(!t.liveCalibration)return;const e=t.liveCalibration;if(!e.rpyCalib||3!==e.rpyCalib.length||e.calStatus!==H)return void(e.calStatus!==H&&(this.view_from_calib=[...g.map(t=>[...t])]));const i=_(e.rpyCalib);this.view_from_calib=l(g,i),this._cached_matrix=null}_calc_frame_matrix(){window.debug;const t=this.sm.updated&&this.sm.updated.liveCalibration,e=this.sm.recv_frame?this.sm.recv_frame.liveCalibration:Date.now(),i=this.ctx.canvas.width,a=this.ctx.canvas.height,s={width:i,height:a};if(!t&&this._last_calib_time===e&&this._last_rect_dims.width===s.width&&this._last_rect_dims.height===s.height&&null!==this._cached_matrix)return this._cached_matrix;const n=this.device_camera||u.tici,r=n.fcam.intrinsics,o=n.fcam.width,d=n.fcam.height,h=this.view_from_calib,c=(this._content_rect.x,this._content_rect.y,this._content_rect.width,this._content_rect.height,Math.max(i/o,a/d)),_=r[0][0]*c*1.1,g=l([[-_,0,i/2],[0,-_,a/2],[0,0,1]],h);return this.model_renderer.set_transform(g),this._last_calib_time=e,this._last_rect_dims=s,this._cached_matrix=g,this._cached_matrix}_draw_border(t){window.debug;const e=this.sm.selfdriveState&&this.sm.selfdriveState.engageable?"ENGAGED":"DISENGAGED",i=q[e]||q.DISENGAGED;this.ctx.strokeStyle=`rgba(${i.r}, ${i.g}, ${i.b}, ${i.a/255})`,this.ctx.lineWidth=8,this.ctx.strokeRect(t.x,t.y,t.width,t.height)}}window.AugmentedRoadView=U;const X=new URLSearchParams(window.location.search),Y="true"===X.get("debug"),J="true"===localStorage.getItem("debug");if(window.debug=Y||J||!1,Y!==J&&(Y?localStorage.setItem("debug","true"):X.has("debug")&&localStorage.removeItem("debug")),window._originalConsoleLog=console.log,console.log=function(...t){window.debug&&window._originalConsoleLog(...t)},window.debug){window._originalConsoleLog("%c๐Ÿ› Debug Mode Enabled","color: #00ff00; font-weight: bold; font-size: 16px"),window._originalConsoleLog("To disable: add ?debug=false to URL or run dashy.debug(false)");const t=document.createElement("div");t.id="debug-indicator",t.textContent="๐Ÿ› DEBUG",t.style.cssText="\n position: fixed;\n top: 10px;\n right: 10px;\n background: rgba(255, 0, 0, 0.8);\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n font-family: monospace;\n font-size: 12px;\n z-index: 10000;\n pointer-events: none;\n ",document.addEventListener("DOMContentLoaded",()=>{document.body.appendChild(t)})}window.dashy={debug:function(t=!0){return window.debug=t,t?(localStorage.setItem("debug","true"),window._originalConsoleLog("%c๐Ÿ› Debug Mode Enabled","color: #00ff00; font-weight: bold"),window._originalConsoleLog("Refresh page to see all debug logs")):(localStorage.removeItem("debug"),window._originalConsoleLog("%c๐Ÿ”‡ Debug Mode Disabled","color: #ff0000; font-weight: bold")),t?"Debug enabled":"Debug disabled"},perf:function(){const t={fps:window._fps||0,frameTime:window._frameTime||0,drawCalls:window._drawCalls||0,canvasOps:window._canvasOps||0};return window._originalConsoleLog("%cPerformance Metrics","color: #00aaff; font-weight: bold"),window._originalConsoleLog(t),t},help:function(){window._originalConsoleLog("%cDashy Debug Commands:","color: #ffaa00; font-weight: bold; font-size: 14px"),window._originalConsoleLog("dashy.debug(true/false) - Enable/disable debug mode"),window._originalConsoleLog("dashy.perf() - Show performance metrics"),window._originalConsoleLog("dashy.help() - Show this help"),window._originalConsoleLog("\nURL Parameters:"),window._originalConsoleLog("?debug=true - Enable debug mode"),window._originalConsoleLog("?debug=false - Disable debug mode")}},function(){let t=0,e=null,i=0;function a(a,s,n){const r=document.getElementById("driving-page");if(!r||!r.classList.contains("active"))return;const o=window.innerWidth,l=window.innerHeight;if(a>o-100&&s>l-100){const r=Date.now();if(r-i<100)return void window._originalConsoleLog(`[Debug Toggle] Ignoring duplicate ${n} event`);if(i=r,t++,window._originalConsoleLog(`[Debug Toggle] ${n} count: ${t}/5 (at ${a},${s})`),e&&clearTimeout(e),e=setTimeout(()=>{t=0,window._originalConsoleLog("[Debug Toggle] Tap count reset to 0")},1e3),5===t){t=0;const e=!window.debug;window.dashy.debug(e);const i=document.createElement("div");i.textContent=e?"๐Ÿ› Debug Enabled":"๐Ÿ”‡ Debug Disabled",i.style.cssText=`\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: ${e?"rgba(0, 255, 0, 0.9)":"rgba(255, 0, 0, 0.9)"};\n color: white;\n padding: 20px 40px;\n border-radius: 10px;\n font-size: 24px;\n font-weight: bold;\n z-index: 10001;\n pointer-events: none;\n `,document.body.appendChild(i),navigator.vibrate&&navigator.vibrate(e?[100,50,100]:[200]),setTimeout(()=>{i.remove(),location.reload()},1500)}}}document.addEventListener("touchstart",t=>{const e=t.touches[0];e.clientX>window.innerWidth-100&&e.clientY>window.innerHeight-100&&(a(e.clientX,e.clientY,"Touch"),t.preventDefault())},{passive:!1}),document.addEventListener("click",t=>{t.clientX>window.innerWidth-100&&t.clientY>window.innerHeight-100&&a(t.clientX,t.clientY,"Click")})}();const Z={sections:[{title:"Visual",settings:[{id:"HudModeEnabled",type:"toggle",label:"Heads-up Display (HUD) Mode",description:"Mirror the display for windshield projection.",defaultValue:!1,storage:"local"},{id:"ShowVideoInHud",type:"toggle",label:"Show Video",description:"Show or hide video stream in HUD mode.",defaultValue:!0,storage:"local",indent:!0,dependsOn:"HudModeEnabled"}]}]},K={events:{},on(t,e){return this.events[t]||(this.events[t]=[]),this.events[t].push(e),()=>this.off(t,e)},off(t,e){this.events[t]&&(this.events[t]=this.events[t].filter(t=>t!==e))},emit(t,e){this.events[t]&&this.events[t].forEach(t=>t(e))}},Q={_data:{settings:{},localSettings:{},ui:{currentPage:"files",navBarVisible:!1,hudModeEnabled:!1,showVideoInHud:!0},files:{currentPath:"/",items:[]}},get(t){return t.split(".").reduce((t,e)=>t?.[e],this._data)},set(t,e){const i=t.split("."),a=i.pop(),s=i.reduce((t,e)=>(t[e]||(t[e]={}),t[e]),this._data),n=s[a];n!==e&&(s[a]=e,K.emit("state:change",{path:t,value:e,oldValue:n}),K.emit(`state:${t}`,e))},subscribe:(t,e)=>K.on(`state:${t}`,e)},tt={fileRow:(t,e)=>{const i=t.is_dir?"๐Ÿ“":t.name.endsWith(".ts")?"๐ŸŽž๏ธ":"๐Ÿ“„",a=`${e}/${t.name}`.replace("//","/");return`\n \n ${i}${t.name.endsWith(".ts")?`โ–ถ`:""}\n ${t.is_dir?`${t.name}`:`${t.name}`}\n ${t.mtime}\n ${t.is_dir?"-":Dt(t.size)}\n \n `},breadcrumb:t=>{const e=t.split("/").filter(Boolean);let i="";const a=['root'];return e.forEach((t,s)=>{i+="/"+t,s===e.length-1?a.push(t):a.push(`${t}`)}),a.join(" / ")},alert:(t,e,i="normal")=>`\n
\n

${t}

\n ${e?`

${e}

`:""}\n
\n `};function et(t,...e){const i=t(...e),a=document.createElement("div");return a.innerHTML=i,a.firstElementChild||a.childNodes}function it(t,e,i={}){const a=document.createElement(t);return e&&(a.className=e),Object.entries(i).forEach(([t,e])=>{"dataset"===t?Object.entries(e).forEach(([t,e])=>{a.dataset[t]=e}):a[t]=e}),a}function at(t){const e=it("label","toggle-switch"),i=it("input","",{type:"checkbox",dataset:{param:t.id}});t.storage&&(i.dataset.storage=t.storage),t.defaultValue&&(i.checked=!0);const a=it("span","toggle-slider");return e.appendChild(i),e.appendChild(a),e}function st(t){const e=it("div","setting-control stepper",{dataset:{param:t.id}});void 0!==t.min&&(e.dataset.min=t.min),void 0!==t.max&&(e.dataset.max=t.max);const i=it("button","",{dataset:{step:-t.step},textContent:"-"}),a=it("span","stepper-value",{textContent:t.defaultValue}),s=it("button","",{dataset:{step:t.step},textContent:"+"});return e.appendChild(i),e.appendChild(a),e.appendChild(s),e}function nt(t){const e=it("div","setting-control btn-group",{dataset:{param:t.id}});return t.storage&&(e.dataset.storage=t.storage),t.options.forEach(i=>{const a=it("button","",{dataset:{value:i.value},textContent:i.text});i.value===t.defaultValue&&a.classList.add("active"),e.appendChild(a)}),e}function rt(t){return it("input","text-input",{type:"text",placeholder:t.placeholder||"",dataset:{param:t.id}})}function ot(t){const e=it("div","setting-item",{id:"ShowVideoInHud"===t.id?"show-video-setting":""});t.indent&&(e.style.marginLeft="2rem"),t.dependsOn&&(e.style.display="none");const i=it("div","setting-label"),a=it("p","",{textContent:t.label}),s=it("span","",{textContent:t.description});i.appendChild(a),i.appendChild(s);const n=it("div","setting-control");let r;switch(t.type){case"toggle":r=at(t);break;case"stepper":return r=st(t),e.appendChild(i),e.appendChild(r),e;case"btn-group":return r=nt(t),e.appendChild(i),e.appendChild(r),e;case"text-input":r=rt(t),n.appendChild(r)}return"toggle"===t.type&&n.appendChild(r),e.appendChild(i),e.appendChild(n),e}function lt(t,e){t.sections.forEach(t=>{const i=it("div","settings-category"),a=it("h2","",{textContent:t.title});i.appendChild(a),t.settings.forEach(t=>{i.appendChild(ot(t))}),e.appendChild(i)})}const dt=document.getElementById("app-container"),ht=document.getElementById("driving-page"),ct=document.getElementById("files-page"),_t=document.getElementById("settings-page"),gt=document.getElementById("local-settings-page"),ut=document.getElementById("nav-driving"),pt=document.getElementById("nav-files"),ft=document.getElementById("nav-settings"),wt=document.getElementById("nav-local-settings"),mt=document.getElementById("files-breadcrumbs"),bt=document.querySelector("#files-table tbody"),vt=document.getElementById("driving-page-content"),xt=document.getElementById("videoPlayer"),yt=document.getElementById("uiCanvas"),Ct=yt.getContext("2d");function St(){window.debug,document.addEventListener("click",t=>{if(t.target.matches("nav button")){const e=t.target.id.replace("nav-","");return void It(e)}if(t.target.matches(".stepper button")){const e=t.target.parentElement,i=e.querySelector(".stepper-value"),a=parseFloat(t.target.dataset.step),s=parseFloat(e.dataset.min)||-1/0,n=parseFloat(e.dataset.max)||1/0;let r=parseFloat(i.textContent)+a;String(a).includes(".")&&(r=parseFloat(r.toFixed(2))),i.textContent=Math.max(s,Math.min(n,r));const o=t.target.closest("[data-param]");return void(o&&"local"===o.dataset.storage&&Le(()=>{const t=o.dataset.param,e=Et(!0)[t];void 0!==e&&Mt(t,e)},50))}if(t.target.matches(".btn-group button")){t.target.parentElement.querySelectorAll("button").forEach(t=>t.classList.remove("active")),t.target.classList.add("active");const e=t.target.closest("[data-param]");return void(e&&"local"===e.dataset.storage&&Le(()=>{const t=e.dataset.param,i=Et(!0)[t];void 0!==i&&Mt(t,i)},50))}if(t.target.matches("#files-table a, #files-breadcrumbs a")){if(t.target.href&&t.target.href.includes("/api/play"))return;const e=t.target.dataset.path;return void(void 0!==e&&(t.preventDefault(),Bt(e)))}}),document.addEventListener("change",t=>{const e=t.target.closest("[data-param]");e&&"local"===e.dataset.storage&&Le(()=>{const t=e.dataset.param,i=Et(!0)[t];void 0!==i&&Mt(t,i)},50)}),document.addEventListener("input",t=>{const e=t.target.closest('[data-param][data-storage="local"]');e&&t.target.matches(".text-input")&&Mt(e.dataset.param,t.target.value)})}function Et(t=!0){window.debug;const e={},i=t?'[data-param][data-storage="local"]':'[data-param]:not([data-storage="local"])';return document.querySelectorAll(i).forEach(t=>{const i=t.dataset.param;t.matches('input[type="checkbox"]')?e[i]=t.checked:t.matches(".stepper")?e[i]=parseFloat(t.querySelector(".stepper-value").textContent):t.matches(".btn-group")?e[i]=t.querySelector("button.active").dataset.value:t.matches(".text-input")&&(e[i]=t.value)}),e}function Mt(t,e){window.debug;try{let i=JSON.parse(localStorage.getItem("dashySettings"))||{};i[t]=e,localStorage.setItem("dashySettings",JSON.stringify(i)),Pt(t,e)}catch(t){}}let Tt;function Lt(){const t=Q.get("ui.hudModeEnabled"),e=Q.get("ui.showVideoInHud");xt.style.display=t&&!e?"none":""}function Pt(t,e){window.debug,Q.set(`ui.${t.charAt(0).toLowerCase()+t.slice(1)}`,e)}function $t(){Q.subscribe("ui.theme",t=>{}),Q.subscribe("ui.hudModeEnabled",t=>{if(t){xt.classList.add("hud-mode");const t=document.getElementById("show-video-setting");t&&(t.style.display="")}else{xt.classList.remove("hud-mode");const t=document.getElementById("show-video-setting");t&&(t.style.display="none"),xt.style.display=""}Lt()}),Q.subscribe("ui.showVideoInHud",t=>{Lt()}),Q.subscribe("ui.navBarVisible",t=>{K.emit("nav:visibility",t)}),Q.subscribe("ui.currentPage",t=>{K.emit("page:change",t)})}function At(){window.debug;try{const t=JSON.parse(localStorage.getItem("dashySettings"))||{};for(const[e,i]of Object.entries(t)){const t=document.querySelector(`[data-param="${e}"][data-storage="local"]`);if(t)if(t.matches('input[type="checkbox"]'))t.checked=i;else if(t.matches(".stepper"))t.querySelector(".stepper-value").textContent=i;else if(t.matches(".btn-group")){t.querySelectorAll("button").forEach(t=>t.classList.remove("active"));const e=t.querySelector(`[data-value="${i}"]`);e&&e.classList.add("active")}else t.matches(".text-input")&&(t.value=i);Pt(e,i)}Q.get("ui.hudModeEnabled")?document.getElementById("show-video-setting").style.display="":document.getElementById("show-video-setting").style.display="none"}catch(t){}}function It(t){window.debug,document.querySelectorAll(".page").forEach(t=>t.classList.remove("active")),document.querySelectorAll("nav button").forEach(t=>t.classList.remove("active")),document.getElementById(`${t}-page`).classList.add("active"),document.getElementById(`nav-${t}`).classList.add("active"),"files"!==t||bt.hasChildNodes()||Bt("/"),"driving"===t?(Me.classList.remove("visible"),$e(Te)):(Me.classList.add("visible"),$e(Te))}function Dt(t){if(0===t)return"0 B";const e=Math.floor(Math.log(t)/Math.log(1024));return parseFloat((t/Math.pow(1024,e)).toFixed(1))+" "+["B","KB","MB","GB","TB"][e]}function Rt(t){if(window.debug,Nt===t)return;Nt=t;const e=1===t||"1"===t,i=2===t||"2"===t;ut&&(ut.style.display=i?"":"none"),wt&&(wt.style.display=i?"":"none"),pt&&(pt.style.display=""),ft&&(ft.style.display=e||i?"none":"");let a="files";Q.get("ui.currentPage")!==a&&It(a)}function kt(t,e){let i;return function(...a){clearTimeout(i),i=setTimeout(()=>{clearTimeout(i),t(...a)},e)}}const zt=new Map;async function Bt(t="/"){window.debug;const e=zt.get(t);e&&e.abort();const i=new AbortController;zt.set(t,i);try{bt.innerHTML='Loading...';const e=await fetch(`/api/files?path=${encodeURIComponent(t)}`,{signal:i.signal});if(!e.ok)throw new Error(`Server error: ${e.statusText}`);const a=await e.json();Ft(a.path),Gt(a.path,a.files)}catch(t){if("AbortError"===t.name)return;bt.innerHTML=`Failed to load files: ${t.message}`}finally{zt.delete(t)}}function Ft(t){mt.innerHTML="Path: "+tt.breadcrumb(t),Q.set("files.currentPath",t)}function Gt(t,e){Q.set("files.items",e);let i="";if(t){const e=t.substring(0,t.lastIndexOf("/"))||"/";i+=`โคด๏ธ.. (Parent Directory)`}e.forEach(e=>{i+=tt.fileRow(e,t)}),bt.innerHTML=i}let Vt,Ot,jt=!1,Nt=null,Ht=null,Wt=null;const qt={modelV2:null,liveCalibration:null,longitudinalPlan:null,radarState:null,selfdriveState:null,deviceState:null,carState:null,controlsState:null,roadCameraState:null,driverStateV2:null,driverMonitoringState:null,recv_frame:{},recv_time:{},updated:{},valid:{},seen:{},frame:0};let Ut=0,Xt=0,Yt=0,Jt=0,Zt=0;const Kt={sm:qt,started_frame:0,is_metric:!1,status:"DISENGAGED",engaged:!1},Qt={textContent:""},te={style:{backgroundColor:""}},ee=20,ie=50;let ae=0,se=0,ne=0,re=!1;function oe(){window.debug;const{width:t,height:e}=vt.getBoundingClientRect();t>0&&(yt.width!==t||yt.height!==e)&&(yt.width=t,yt.height=e,Wt&&Wt.invalidate_transform_cache(),!Wt&&yt.width>0&&yt.height>0&&(Wt=new U,Wt.set_callbacks(()=>{qt.selfdriveState&&(qt.selfdriveState.experimentalMode=!qt.selfdriveState.experimentalMode)}),requestAnimationFrame(Fe)))}const le=new ResizeObserver(()=>oe());function de(){window.debug,!Wt&&yt.width>0&&yt.height>0&&(Wt=new U,Wt.set_callbacks(()=>{qt.selfdriveState&&(qt.selfdriveState.experimentalMode=!qt.selfdriveState.experimentalMode)}),requestAnimationFrame(Fe))}function he(t,e){window.debug,Qt.textContent=t;const i={green:"#48bb78",yellow:"#f6e05e",red:"#f56565"};te.style.backgroundColor=i[e]||i.red,we=!0,$e(Tt),Tt=Le(()=>{Qt.textContent="",we=!0},5e3)}le.observe(vt),window.addEventListener("orientationchange",()=>oe());let ce=0,_e=[];const ge=500;function ue(){window.debug;const t=Date.now();if(t-ce0&&t-Yt>2e3&&e.push({text:"Model data stale",severity:"warning"}),Jt>0&&t-Jt>2e3&&e.push({text:"Car state stale",severity:"warning"}),Zt>0&&t-Zt>3e3&&e.push({text:"Selfdrive state stale",severity:"critical"}),_e=e,e}let pe="",fe="",we=!0,me=null,be=null,ve=null;function xe(){window.debug;const t=Qt.textContent,e=te.style.backgroundColor;t===pe&&e===fe||(we=!0,pe=t,fe=e);const i=ue();if(i.length>0&&!t){const t=i.find(t=>"critical"===t.severity)||i[0];return void he(t.text,"critical"===t.severity?"red":"yellow")}if(t){if(ve&&ve.width===yt.width&&ve.height===yt.height||(we=!0,ve={width:yt.width,height:yt.height}),we){we=!1;const i=12,a=4,s=12;Ct.font=`${s}px Arial`;const n=Ct.measureText(t).width+2*i+6+4,r=s+2*a;me&&me.width===n&&me.height===r||(me=document.createElement("canvas"),me.width=n,me.height=r,be=me.getContext("2d")),be.clearRect(0,0,n,r);const o=5;be.fillStyle="rgba(0, 0, 0, 0.7)",be.beginPath(),be.roundRect(0,0,n,r,o),be.fill();const l=3,d=i+l,h=r/2;be.fillStyle=e,be.beginPath(),be.arc(d,h,l,0,2*Math.PI),be.fill(),be.fillStyle="white",be.font=`${s}px Arial`,be.textAlign="left",be.textBaseline="middle";const c=d+l+4;be.fillText(t,c,h)}if(me){const t=15,e=yt.width/2-me.width/2,i=yt.height-me.height-t;Ct.drawImage(me,e,i)}}}function ye(t){window.debug;const e=t.split("\r\n");let i=-1;const a=[];for(let t=0;t!n.includes(t)),l=r.slice(0,3).concat(n,o).join(" ");return e[i]=l,e.join("\r\n")}const Ce=new Set,Se=new Set;let Ee,Me,Te;function Le(t,e){const i=setTimeout(()=>{t(),Ce.delete(i)},e);return Ce.add(i),i}function Pe(t,e){const i=setInterval(t,e);return Se.add(i),i}function $e(t){clearTimeout(t),Ce.delete(t)}function Ae(t){clearInterval(t),Se.delete(t)}function Ie(){window.debug,Ce.forEach(t=>clearTimeout(t)),Ce.clear(),Se.forEach(t=>clearInterval(t)),Se.clear(),clearTimeout(Ee),clearInterval(Ht),Ht=null,Vt&&(Vt.close(),Vt=null),Ot&&(Ot.close(),Ot=null),Wt=null,window.animationFrameId&&cancelAnimationFrame(window.animationFrameId)}function De(){window.debug;const t=window.location.hostname;Ie(),ht.classList.contains("active")||2===Nt&&It("driving"),ke(t),de()}async function Re(){window.debug;const t=window.location.hostname;he(`Connecting to ${t}...`,"yellow");try{const e=await fetch(`http://${t}:5088/api/init`,{method:"GET",signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Connection failed: ${e.statusText}`);{const i=await e.json();jt=i.is_metric,Kt.is_metric=jt,void 0!==i.dp_dev_dashy?(Rt(i.dp_dev_dashy),2===i.dp_dev_dashy&&ke(t)):Rt(null)}}catch(e){he(`Could not connect to device at ${t}.`,"red")}}async function ke(t){window.debug,he("Initializing WebRTC...","yellow"),clearTimeout(Ee),lastDataReceivedTime=Date.now(),Vt&&(Vt.close(),Vt=null);try{Vt=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]});Be(Vt.createDataChannel("data")),Vt.ontrack=t=>{"video"===t.track.kind&&(xt.srcObject=t.streams[0],oe(),he("Video connected","green"),xt.onplaying=()=>{clearTimeout(Ee)})},Vt.onconnectionstatechange=()=>{"failed"===Vt.connectionState||"disconnected"===Vt.connectionState||"closed"===Vt.connectionState?(he("WebRTC Error: Could not connect to server (server may be down or inaccessible)","red"),"driving"===Q.get("ui.currentPage")&&(Ee=Le(()=>De(),5e3))):"connected"===Vt.connectionState&&(he("WebRTC Connected","green"),clearTimeout(Ee))},Vt.addTransceiver("video",{direction:"recvonly"});const e=await Vt.createOffer();e.sdp=ye(e.sdp),await Vt.setLocalDescription(e);const i=await fetch(`http://${t}:5001/stream`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sdp:Vt.localDescription.sdp,cameras:["road"],bridge_services_in:[],bridge_services_out:["modelV2","liveCalibration","longitudinalPlan","radarState","selfdriveState","deviceState","carState","controlsState"]})});if(!i.ok){const t=await i.text();throw new Error(`HTTP Error! Status: ${i.status}, Message: ${t}`)}const a=await i.json();await Vt.setRemoteDescription(new RTCSessionDescription(a)),ze()}catch(t){let e=`WebRTC Error: ${t.message}`;t.message.includes("Failed to fetch")&&(e="WebRTC Error: Could not connect to server (server may be down or inaccessible)"),he(e,"red"),t.message.includes("Failed to fetch")&&"driving"===Q.get("ui.currentPage")&&(Ee=setTimeout(()=>De(),5e3))}}function ze(){window.debug,Ae(Ht),lastDataReceivedTime=Date.now(),lastVideoPlaybackTime=xt.currentTime,Ht=Pe(()=>{let t=!1,e="";Date.now()-lastDataReceivedTime>5e3&&(t=!0,e="Data stream stalled"),t||xt.paused||xt.currentTime!==lastVideoPlaybackTime||(t=!0,e="Video stream stalled"),t&&(he(`${e}. Reconnecting...`,"yellow"),De()),lastVideoPlaybackTime=xt.currentTime},2e3)}function Be(t){window.debug;const e=new TextDecoder("utf-8");let i="";function a(t){try{const e=JSON.parse(t.replace(/\bNaN\b/g,"null"));lastDataReceivedTime=Date.now();const i=Date.now();e.data&&void 0!==e.data.dp_dev_dashy&&Rt(e.data.dp_dev_dashy);const a=e.type;qt[a]=e.data,qt.recv_frame[a]=qt.frame,qt.recv_time[a]=i/1e3,qt.updated[a]=!0,qt.valid[a]=!0,qt.seen[a]=!0,"selfdriveState"===a?(Zt=i,0===Ut&&(Ut=i,Xt=qt.frame)):"modelV2"===a?Yt=i:"carState"===a&&(Jt=i),qt.frame++}catch(t){}}t.onmessage=t=>{const s="string"==typeof t.data?t.data:e.decode(t.data,{stream:!0});let n;for(i+=s;-1!==(n=i.indexOf("}{"));){a(i.substring(0,n+1)),i=i.substring(n+1)}i.length>0&&(a(i),i="")}}function Fe(t){window.animationFrameId=requestAnimationFrame(Fe);const e=t-ae;if(!(et.classList.remove("active")),document.querySelectorAll("nav button").forEach(t=>t.classList.remove("active")),document.getElementById(`${t}-page`).classList.add("active"),document.getElementById(`nav-${t}`).classList.add("active"),"files"!==t||bt.hasChildNodes()||Bt("/"),"driving"===e&&"driving"!==t&&Ie(),"driving"===t&&"driving"!==e&&De(),"driving"===t?(Me.classList.remove("visible"),Q.set("ui.navBarVisible",!1),vt.addEventListener("click",Ge)):(Me.classList.add("visible"),Q.set("ui.navBarVisible",!0),vt.removeEventListener("click",Ge),clearTimeout(Te))}function Ge(){Me||(Me=document.querySelector("nav")),Me.classList.toggle("visible"),Me.classList.contains("visible")?($e(Te),Te=Le(()=>{Me.classList.remove("visible")},3e3)):$e(Te)}async function Ve(){window.debug;document.getElementById("settings-content");const t=document.getElementById("local-settings-content");lt(Z,t),Me=document.querySelector("nav"),ut&&(ut.style.display="none"),wt&&(wt.style.display="none"),St();document.getElementById("driving-page-content").addEventListener("click",t=>{ht.classList.contains("active")&&Ge()}),xt.addEventListener("click",t=>{ht.classList.contains("active")&&Ge()}),yt.addEventListener("click",t=>{ht.classList.contains("active")&&Ge()}),$t(),At(),await Re(),It("files"),de(),xt.onstalled=()=>{"driving"===Q.get("ui.currentPage")&&De()},xt.onplaying=()=>{clearTimeout(Ee)}}window.addEventListener("beforeunload",Ie),window.addEventListener("unload",Ie),Ve();