diff --git a/cmd/server/web/dist/assets/index-DIrlWzGj.js b/cmd/server/web/dist/assets/index-DIrlWzGj.js new file mode 100644 index 0000000..60aea20 --- /dev/null +++ b/cmd/server/web/dist/assets/index-DIrlWzGj.js @@ -0,0 +1,7 @@ +var hl=Object.defineProperty;var bl=(e,s,n)=>s in e?hl(e,s,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[s]=n;var Bt=(e,s,n)=>bl(e,typeof s!="symbol"?s+"":s,n);(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const b of i.addedNodes)b.tagName==="LINK"&&b.rel==="modulepreload"&&a(b)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();function Se(){}function ul(e){return e()}function Zt(){return Object.create(null)}function ot(e){e.forEach(ul)}function vl(e){return typeof e=="function"}function ft(e,s){return e!=e?s==s:e!==s||e&&typeof e=="object"||typeof e=="function"}function gl(e){return Object.keys(e).length===0}function t(e,s){e.appendChild(s)}function Me(e,s,n){e.insertBefore(s,n||null)}function Te(e){e.parentNode&&e.parentNode.removeChild(e)}function It(e,s){for(let n=0;ne.removeEventListener(s,n,a)}function l(e,s,n){n==null?e.removeAttribute(s):e.getAttribute(s)!==n&&e.setAttribute(s,n)}function wl(e){return Array.from(e.childNodes)}function A(e,s){s=""+s,e.data!==s&&(e.data=s)}function wt(e,s){e.value=s??""}function re(e,s,n,a){n==null?e.style.removeProperty(s):e.style.setProperty(s,n,"")}function jt(e,s,n){for(let a=0;ae.indexOf(a)===-1?s.push(a):n.push(a)),n.forEach(a=>a()),Ot=s}const Dt=new Set;let Sl;function ht(e,s){e&&e.i&&(Dt.delete(e),e.i(s))}function Tt(e,s,n,a){if(e&&e.o){if(Dt.has(e))return;Dt.add(e),Sl.c.push(()=>{Dt.delete(e)}),e.o(s)}}function vt(e){return(e==null?void 0:e.length)!==void 0?e:Array.from(e)}function Ft(e){e&&e.c()}function bt(e,s,n){const{fragment:a,after_update:r}=e.$$;a&&a.m(s,n),Lt(()=>{const i=e.$$.on_mount.map(ul).filter(vl);e.$$.on_destroy?e.$$.on_destroy.push(...i):ot(i),e.$$.on_mount=[]}),r.forEach(Lt)}function gt(e,s){const n=e.$$;n.fragment!==null&&(Fl(n.after_update),ot(n.on_destroy),n.fragment&&n.fragment.d(s),n.on_destroy=n.fragment=null,n.ctx=[])}function Ml(e,s){e.$$.dirty[0]===-1&&(Mt.push(e),Cl(),e.$$.dirty.fill(0)),e.$$.dirty[s/31|0]|=1<{const y=g.length?g[0]:d;return c.ctx&&r(c.ctx[f],c.ctx[f]=y)&&(!c.skip_bound&&c.bound[f]&&c.bound[f](y),w&&Ml(e,f)),d}):[],c.update(),w=!0,ot(c.before_update),c.fragment=a?a(c.ctx):!1,s.target){if(s.hydrate){const f=wl(s.target);c.fragment&&c.fragment.l(f),f.forEach(Te)}else c.fragment&&c.fragment.c();s.intro&&ht(e.$$.fragment),bt(e,s.target,s.anchor),pl()}Nt(h)}class yt{constructor(){Bt(this,"$$");Bt(this,"$$set")}$destroy(){gt(this,1),this.$destroy=Se}$on(s,n){if(!vl(n))return Se;const a=this.$$.callbacks[s]||(this.$$.callbacks[s]=[]);return a.push(n),()=>{const r=a.indexOf(n);r!==-1&&a.splice(r,1)}}$set(s){this.$$set&&!gl(s)&&(this.$$.skip_bound=!0,this.$$set(s),this.$$.skip_bound=!1)}}const jl="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(jl);const St=[];function Gt(e,s=Se){let n;const a=new Set;function r(v){if(ft(e,v)&&(e=v,n)){const h=!St.length;for(const c of a)c[1](),St.push(c,e);if(h){for(let c=0;c{a.delete(c),a.size===0&&n&&(n(),n=null)}}return{set:r,update:i,subscribe:b}}const Ht=Gt(!1),ml=Gt(null),Ol=Gt(null);class Pl{constructor(){this.ws=null,this.reconnectTimeout=null,this.reconnectDelay=3e3}connect(){const n=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`;try{this.ws=new WebSocket(n),this.ws.onopen=()=>{console.log("WebSocket connected"),Ht.set(!0)},this.ws.onmessage=a=>{try{const r=JSON.parse(a.data);r.type==="update"&&(console.log("System status updated:",r.data),ml.set(r.data),Ol.set(new Date(r.timestamp)))}catch(r){console.error("Error parsing message:",r)}},this.ws.onerror=a=>{console.error("WebSocket error:",a)},this.ws.onclose=()=>{console.log("WebSocket disconnected"),Ht.set(!1),this.scheduleReconnect()}}catch(a){console.error("Error creating WebSocket:",a),this.scheduleReconnect()}}scheduleReconnect(){this.reconnectTimeout&&clearTimeout(this.reconnectTimeout),this.reconnectTimeout=setTimeout(()=>{console.log("Attempting to reconnect..."),this.connect()},this.reconnectDelay)}send(s){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(s))}disconnect(){this.reconnectTimeout&&clearTimeout(this.reconnectTimeout),this.ws&&this.ws.close()}}const el=new Pl,Al="/api";async function ge(e,s={}){try{const n=await fetch(`${Al}${e}`,{...s,headers:{"Content-Type":"application/json",...s.headers}});if(!n.ok)throw new Error(`HTTP error! status: ${n.status}`);return await n.json()}catch(n){throw console.error("API request failed:",n),n}}const Fe={getStatus:()=>ge("/status"),getConfig:()=>ge("/config"),webswitch:{relayOn:e=>ge(`/webswitch/relay/on?relay=${e}`,{method:"POST"}),relayOff:e=>ge(`/webswitch/relay/off?relay=${e}`,{method:"POST"}),allOn:()=>ge("/webswitch/all/on",{method:"POST"}),allOff:()=>ge("/webswitch/all/off",{method:"POST"})},rotator:{move:(e,s)=>ge("/rotator/move",{method:"POST",body:JSON.stringify({rotator:e,azimuth:s})}),cw:e=>ge(`/rotator/cw?rotator=${e}`,{method:"POST"}),ccw:e=>ge(`/rotator/ccw?rotator=${e}`,{method:"POST"}),stop:()=>ge("/rotator/stop",{method:"POST"})},tuner:{setOperate:e=>ge("/tuner/operate",{method:"POST",body:JSON.stringify({value:e})}),setBypass:e=>ge("/tuner/bypass",{method:"POST",body:JSON.stringify({value:e})}),autoTune:()=>ge("/tuner/autotune",{method:"POST"})},antenna:{selectAntenna:(e,s)=>ge("/antenna/select",{method:"POST",body:JSON.stringify({port:e,antenna:s})}),reboot:()=>ge("/antenna/reboot",{method:"POST"})},power:{setFanMode:e=>ge("/power/fanmode",{method:"POST",body:JSON.stringify({mode:e})}),setOperate:e=>ge("/power/operate",{method:"POST",body:JSON.stringify({value:e})})},rotator:{setHeading:e=>ge("/rotator/heading",{method:"POST",body:JSON.stringify({heading:e})}),rotateCW:()=>ge("/rotator/cw",{method:"POST"}),rotateCCW:()=>ge("/rotator/ccw",{method:"POST"}),stop:()=>ge("/rotator/stop",{method:"POST"})},ultrabeam:{setFrequency:(e,s)=>ge("/ultrabeam/frequency",{method:"POST",body:JSON.stringify({frequency:e,direction:s})}),retract:()=>ge("/ultrabeam/retract",{method:"POST"}),setAutoTrack:(e,s)=>ge("/ultrabeam/autotrack",{method:"POST",body:JSON.stringify({enabled:e,threshold:s})})}};function tl(e,s,n){var b;const a=e.slice();a[10]=s[n];const r=a[1].find(function(...h){return e[9](a[10],...h)});a[11]=r;const i=((b=a[11])==null?void 0:b.state)||!1;return a[12]=i,a}function ll(e){let s,n,a,r,i,b,v=e[12]?"ON":"OFF",h,c,w,f,d,g,y,k;function m(){return e[8](e[10])}return{c(){s=o("div"),n=o("div"),a=o("div"),r=o("div"),r.textContent=`${e[3][e[10]]}`,i=u(),b=o("div"),h=_(v),c=u(),w=o("button"),f=o("div"),f.innerHTML='
',g=u(),l(r,"class","relay-name svelte-z2csmj"),l(b,"class","relay-status svelte-z2csmj"),l(a,"class","relay-details svelte-z2csmj"),l(n,"class","relay-info svelte-z2csmj"),l(f,"class","toggle-track svelte-z2csmj"),l(w,"class","relay-toggle svelte-z2csmj"),w.disabled=d=e[0][e[10]],B(w,"active",e[12]),B(w,"loading",e[0][e[10]]),l(s,"class","relay-card svelte-z2csmj"),B(s,"relay-on",e[12])},m(T,F){Me(T,s,F),t(s,n),t(n,a),t(a,r),t(a,i),t(a,b),t(b,h),t(s,c),t(s,w),t(w,f),t(s,g),y||(k=ke(w,"click",m),y=!0)},p(T,F){e=T,F&2&&v!==(v=e[12]?"ON":"OFF")&&A(h,v),F&1&&d!==(d=e[0][e[10]])&&(w.disabled=d),F&2&&B(w,"active",e[12]),F&1&&B(w,"loading",e[0][e[10]]),F&2&&B(s,"relay-on",e[12])},d(T){T&&Te(s),y=!1,k()}}}function Nl(e){let s,n,a,r,i,b,v,h,c,w,f,d,g,y,k,m=vt([1,2,3,4,5]),T=[];for(let F=0;F<5;F+=1)T[F]=ll(tl(e,m,F));return{c(){s=o("div"),n=o("div"),a=o("h2"),a.textContent="WebSwitch",r=u(),i=o("span"),b=u(),v=o("div"),h=o("div");for(let F=0;F<5;F+=1)T[F].c();c=u(),w=o("div"),f=o("button"),f.innerHTML=` + ALL ON`,d=u(),g=o("button"),g.innerHTML=` + ALL OFF`,l(a,"class","svelte-z2csmj"),l(i,"class","status-dot svelte-z2csmj"),B(i,"disconnected",!e[2]),l(n,"class","card-header svelte-z2csmj"),l(h,"class","relays svelte-z2csmj"),l(f,"class","control-btn all-on svelte-z2csmj"),l(g,"class","control-btn all-off svelte-z2csmj"),l(w,"class","controls svelte-z2csmj"),l(v,"class","metrics svelte-z2csmj"),l(s,"class","card svelte-z2csmj")},m(F,p){Me(F,s,p),t(s,n),t(n,a),t(n,r),t(n,i),t(s,b),t(s,v),t(v,h);for(let P=0;P<5;P+=1)T[P]&&T[P].m(h,null);t(v,c),t(v,w),t(w,f),t(w,d),t(w,g),y||(k=[ke(f,"click",e[5]),ke(g,"click",e[6])],y=!0)},p(F,[p]){if(p&4&&B(i,"disconnected",!F[2]),p&27){m=vt([1,2,3,4,5]);let P;for(P=0;P<5;P+=1){const R=tl(F,m,P);T[P]?T[P].p(R,p):(T[P]=ll(R),T[P].c(),T[P].m(h,null))}for(;P<5;P+=1)T[P].d(1)}},i:Se,o:Se,d(F){F&&Te(s),It(T,F),y=!1,ot(k)}}}function El(e,s,n){let a,r,{status:i}=s;const b={1:"Power Supply",2:"PGXL",3:"TGXL",4:"Flex Radio Start",5:"Reserve"};let v={};async function h(g){const y=a.find(m=>m.number===g),k=(y==null?void 0:y.state)||!1;n(0,v[g]=!0,v);try{k?await Fe.webswitch.relayOff(g):await Fe.webswitch.relayOn(g)}catch(m){console.error("Failed to toggle relay:",m),alert("Failed to control relay")}finally{n(0,v[g]=!1,v)}}async function c(){try{await Fe.webswitch.allOn()}catch(g){console.error("Failed to turn all on:",g)}}async function w(){try{await Fe.webswitch.allOff()}catch(g){console.error("Failed to turn all off:",g)}}const f=g=>h(g),d=(g,y)=>y.number===g;return e.$$set=g=>{"status"in g&&n(7,i=g.status)},e.$$.update=()=>{e.$$.dirty&128&&n(1,a=(i==null?void 0:i.relays)||[]),e.$$.dirty&128&&n(2,r=(i==null?void 0:i.connected)||!1)},[v,a,r,b,h,c,w,i,f,d]}class Ll extends yt{constructor(s){super(),_t(this,s,El,Nl,ft,{status:7})}}function Dl(e){let s;return{c(){s=o("span"),s.textContent="High!",l(s,"class","status-text danger svelte-vjv5wg")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function Il(e){let s;return{c(){s=o("span"),s.textContent="Caution",l(s,"class","status-text warning svelte-vjv5wg")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function Bl(e){let s;return{c(){s=o("span"),s.textContent="Good",l(s,"class","status-text ok svelte-vjv5wg")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function Rl(e){let s;return{c(){s=o("span"),s.textContent="Excellent",l(s,"class","status-text good svelte-vjv5wg")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function zl(e){let s,n,a,r,i,b,v,h,c,w,f,d,g,y,k=e[1].toFixed(0)+"",m,T,F,p,P,R,E,L,D,ne,j,J,z,ae,G=e[2].toFixed(2)+"",K,ue,q,N,M,V,S,x,ee,ve=e[3].toFixed(1)+"",_e,Z,fe,le,we,ie,W,se,he,X,je=e[12].toFixed(1)+"",ye,Le,be,Pe,Ce,Y,O,Q,C,te,$,He,tt,Ge=e[15].toFixed(0)+"",Ie,ut,Ae,Je,at,Ve,We=e[14].toFixed(1)+"",Be,Re,ze,Ze,lt,Ne,Qe=e[13].toFixed(1)+"",Ke,$e,De,Oe,Ee,qe,H,pe,st,rt,Xe,nt,pt,xe,Pt,Ue,me,At,Ye,it,ct,dt,kt,I;function ce(U,oe){return U[2]<1.5?Rl:U[2]<2?Bl:U[2]<3?Il:Dl}let mt=ce(e),et=mt(e);return{c(){s=o("div"),n=o("div"),a=o("h2"),a.textContent="Power Genius XL",r=u(),i=o("div"),b=o("button"),v=_(e[7]),h=u(),c=o("span"),w=u(),f=o("div"),d=o("div"),g=o("div"),y=o("div"),m=_(k),T=o("span"),T.textContent="W",F=u(),p=o("div"),p.textContent="Forward Power",P=u(),R=o("div"),E=o("div"),L=o("div"),D=u(),ne=o("div"),ne.innerHTML="0 1000 2000",j=u(),J=o("div"),z=o("div"),ae=o("div"),K=_(G),ue=u(),q=o("div"),q.textContent="SWR",N=u(),M=o("div"),et.c(),V=u(),S=o("div"),x=o("div"),ee=o("div"),_e=_(ve),Z=_("°"),fe=u(),le=o("div"),le.textContent="PA Temp",we=u(),ie=o("div"),W=o("div"),se=u(),he=o("div"),X=o("div"),ye=_(je),Le=_("°"),be=u(),Pe=o("div"),Pe.textContent="HL Temp",Ce=u(),Y=o("div"),O=o("div"),Q=u(),C=o("div"),te=o("div"),$=o("div"),$.textContent="VAC",He=u(),tt=o("div"),Ie=_(Ge),ut=u(),Ae=o("div"),Je=o("div"),Je.textContent="VDD",at=u(),Ve=o("div"),Be=_(We),Re=u(),ze=o("div"),Ze=o("div"),Ze.textContent="ID Peak",lt=u(),Ne=o("div"),Ke=_(Qe),$e=u(),De=o("div"),Oe=o("div"),Ee=o("span"),Ee.textContent="Band A",qe=u(),H=o("span"),pe=_(e[10]),st=u(),rt=o("div"),Xe=o("span"),Xe.textContent="Band B",nt=u(),pt=o("span"),xe=_(e[9]),Pt=u(),Ue=o("div"),me=o("label"),me.textContent="Fan Mode",At=u(),Ye=o("select"),it=o("option"),it.textContent="Standard",ct=o("option"),ct.textContent="Contest",dt=o("option"),dt.textContent="Broadcast",l(a,"class","svelte-vjv5wg"),l(b,"class","state-badge svelte-vjv5wg"),B(b,"idle",e[0]==="IDLE"),B(b,"transmit",e[0].includes("TRANSMIT")),l(c,"class","status-dot svelte-vjv5wg"),B(c,"disconnected",!e[8]),l(i,"class","header-right svelte-vjv5wg"),l(n,"class","card-header svelte-vjv5wg"),l(T,"class","unit svelte-vjv5wg"),l(y,"class","power-value svelte-vjv5wg"),l(p,"class","power-label svelte-vjv5wg"),l(g,"class","power-main svelte-vjv5wg"),l(L,"class","power-bar-glow svelte-vjv5wg"),l(E,"class","power-bar-fill svelte-vjv5wg"),re(E,"width",e[4]+"%"),l(ne,"class","power-scale svelte-vjv5wg"),l(R,"class","power-bar svelte-vjv5wg"),l(d,"class","power-display svelte-vjv5wg"),l(ae,"class","swr-value svelte-vjv5wg"),l(q,"class","swr-label svelte-vjv5wg"),l(z,"class","swr-circle svelte-vjv5wg"),re(z,"--swr-color",e[5]),l(M,"class","swr-status svelte-vjv5wg"),l(J,"class","swr-container svelte-vjv5wg"),l(ee,"class","temp-value svelte-vjv5wg"),re(ee,"color",e[6]),l(le,"class","temp-label svelte-vjv5wg"),l(W,"class","temp-mini-fill svelte-vjv5wg"),re(W,"width",e[3]/80*100+"%"),re(W,"background",e[6]),l(ie,"class","temp-mini-bar svelte-vjv5wg"),l(x,"class","temp-item svelte-vjv5wg"),l(X,"class","temp-value svelte-vjv5wg"),re(X,"color",e[6]),l(Pe,"class","temp-label svelte-vjv5wg"),l(O,"class","temp-mini-fill svelte-vjv5wg"),re(O,"width",e[12]/80*100+"%"),re(O,"background",e[6]),l(Y,"class","temp-mini-bar svelte-vjv5wg"),l(he,"class","temp-item svelte-vjv5wg"),l(S,"class","temp-group svelte-vjv5wg"),l($,"class","param-label svelte-vjv5wg"),l(tt,"class","param-value svelte-vjv5wg"),l(te,"class","param-box svelte-vjv5wg"),l(Je,"class","param-label svelte-vjv5wg"),l(Ve,"class","param-value svelte-vjv5wg"),l(Ae,"class","param-box svelte-vjv5wg"),l(Ze,"class","param-label svelte-vjv5wg"),l(Ne,"class","param-value svelte-vjv5wg"),l(ze,"class","param-box svelte-vjv5wg"),l(C,"class","params-grid svelte-vjv5wg"),l(Ee,"class","band-label svelte-vjv5wg"),l(H,"class","band-value svelte-vjv5wg"),l(Oe,"class","band-item svelte-vjv5wg"),l(Xe,"class","band-label svelte-vjv5wg"),l(pt,"class","band-value svelte-vjv5wg"),l(rt,"class","band-item svelte-vjv5wg"),l(De,"class","band-display svelte-vjv5wg"),l(me,"for","fan-mode-select"),l(me,"class","control-label svelte-vjv5wg"),it.__value="STANDARD",wt(it,it.__value),ct.__value="CONTEST",wt(ct,ct.__value),dt.__value="BROADCAST",wt(dt,dt.__value),l(Ye,"id","fan-mode-select"),l(Ye,"class","svelte-vjv5wg"),l(Ue,"class","fan-control svelte-vjv5wg"),l(f,"class","metrics svelte-vjv5wg"),l(s,"class","card svelte-vjv5wg")},m(U,oe){Me(U,s,oe),t(s,n),t(n,a),t(n,r),t(n,i),t(i,b),t(b,v),t(i,h),t(i,c),t(s,w),t(s,f),t(f,d),t(d,g),t(g,y),t(y,m),t(y,T),t(g,F),t(g,p),t(d,P),t(d,R),t(R,E),t(E,L),t(R,D),t(R,ne),t(f,j),t(f,J),t(J,z),t(z,ae),t(ae,K),t(z,ue),t(z,q),t(J,N),t(J,M),et.m(M,null),t(f,V),t(f,S),t(S,x),t(x,ee),t(ee,_e),t(ee,Z),t(x,fe),t(x,le),t(x,we),t(x,ie),t(ie,W),t(S,se),t(S,he),t(he,X),t(X,ye),t(X,Le),t(he,be),t(he,Pe),t(he,Ce),t(he,Y),t(Y,O),t(f,Q),t(f,C),t(C,te),t(te,$),t(te,He),t(te,tt),t(tt,Ie),t(C,ut),t(C,Ae),t(Ae,Je),t(Ae,at),t(Ae,Ve),t(Ve,Be),t(C,Re),t(C,ze),t(ze,Ze),t(ze,lt),t(ze,Ne),t(Ne,Ke),t(f,$e),t(f,De),t(De,Oe),t(Oe,Ee),t(Oe,qe),t(Oe,H),t(H,pe),t(De,st),t(De,rt),t(rt,Xe),t(rt,nt),t(rt,pt),t(pt,xe),t(f,Pt),t(f,Ue),t(Ue,me),t(Ue,At),t(Ue,Ye),t(Ye,it),t(Ye,ct),t(Ye,dt),jt(Ye,e[11]),kt||(I=[ke(b,"click",e[17]),ke(Ye,"change",e[19])],kt=!0)},p(U,[oe]){oe&128&&A(v,U[7]),oe&1&&B(b,"idle",U[0]==="IDLE"),oe&1&&B(b,"transmit",U[0].includes("TRANSMIT")),oe&256&&B(c,"disconnected",!U[8]),oe&2&&k!==(k=U[1].toFixed(0)+"")&&A(m,k),oe&16&&re(E,"width",U[4]+"%"),oe&4&&G!==(G=U[2].toFixed(2)+"")&&A(K,G),oe&32&&re(z,"--swr-color",U[5]),mt!==(mt=ce(U))&&(et.d(1),et=mt(U),et&&(et.c(),et.m(M,null))),oe&8&&ve!==(ve=U[3].toFixed(1)+"")&&A(_e,ve),oe&64&&re(ee,"color",U[6]),oe&8&&re(W,"width",U[3]/80*100+"%"),oe&64&&re(W,"background",U[6]),oe&4096&&je!==(je=U[12].toFixed(1)+"")&&A(ye,je),oe&64&&re(X,"color",U[6]),oe&4096&&re(O,"width",U[12]/80*100+"%"),oe&64&&re(O,"background",U[6]),oe&32768&&Ge!==(Ge=U[15].toFixed(0)+"")&&A(Ie,Ge),oe&16384&&We!==(We=U[14].toFixed(1)+"")&&A(Be,We),oe&8192&&Qe!==(Qe=U[13].toFixed(1)+"")&&A(Ke,Qe),oe&1024&&A(pe,U[10]),oe&512&&A(xe,U[9]),oe&2048&&jt(Ye,U[11])},i:Se,o:Se,d(U){U&&Te(s),et.d(),kt=!1,ot(I)}}}function Hl(e,s,n){let a,r,i,b,v,h,c,w,f,d,g,y,k,m,T,F,{status:p}=s;async function P(L){try{await Fe.power.setFanMode(L)}catch(D){console.error("Failed to set fan mode:",D),alert("Failed to set fan mode")}}async function R(){try{const L=f==="IDLE"?0:1;await Fe.power.setOperate(L)}catch(L){console.error("Failed to toggle operate:",L),alert("Failed to toggle operate mode")}}const E=L=>P(L.target.value);return e.$$set=L=>{"status"in L&&n(18,p=L.status)},e.$$.update=()=>{e.$$.dirty&262144&&n(1,a=(p==null?void 0:p.power_forward)||0),e.$$.dirty&262144&&p!=null&&p.power_reflected,e.$$.dirty&262144&&n(2,r=(p==null?void 0:p.swr)||1),e.$$.dirty&262144&&n(15,i=(p==null?void 0:p.voltage)||0),e.$$.dirty&262144&&n(14,b=(p==null?void 0:p.vdd)||0),e.$$.dirty&262144&&p!=null&&p.current,e.$$.dirty&262144&&n(13,v=(p==null?void 0:p.peak_current)||0),e.$$.dirty&262144&&n(3,h=(p==null?void 0:p.temperature)||0),e.$$.dirty&262144&&n(12,c=(p==null?void 0:p.harmonic_load_temp)||0),e.$$.dirty&262144&&n(11,w=(p==null?void 0:p.fan_mode)||"CONTEST"),e.$$.dirty&262144&&n(0,f=(p==null?void 0:p.state)||"IDLE"),e.$$.dirty&262144&&n(10,d=(p==null?void 0:p.band_a)||"0"),e.$$.dirty&262144&&n(9,g=(p==null?void 0:p.band_b)||"0"),e.$$.dirty&262144&&n(8,y=(p==null?void 0:p.connected)||!1),e.$$.dirty&1&&n(7,k=f.replace("TRANSMIT_A","TRANSMIT").replace("TRANSMIT_B","TRANSMIT")),e.$$.dirty&8&&n(6,m=h<40?"#4caf50":h<60?"#ffc107":h<75?"#ff9800":"#f44336"),e.$$.dirty&4&&n(5,T=r<1.5?"#4caf50":r<2?"#ffc107":r<3?"#ff9800":"#f44336"),e.$$.dirty&2&&n(4,F=Math.min(a/2e3*100,100))},[f,a,r,h,F,T,m,k,y,g,d,w,c,v,b,i,P,R,p,E]}class Gl extends yt{constructor(s){super(),_t(this,s,Hl,zl,ft,{status:18})}}function Wl(e){let s;return{c(){s=o("span"),s.textContent="High!",l(s,"class","status-text danger svelte-ulw0vm")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function ql(e){let s;return{c(){s=o("span"),s.textContent="Caution",l(s,"class","status-text warning svelte-ulw0vm")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function Ul(e){let s;return{c(){s=o("span"),s.textContent="Good",l(s,"class","status-text ok svelte-ulw0vm")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function Jl(e){let s;return{c(){s=o("span"),s.textContent="Excellent",l(s,"class","status-text good svelte-ulw0vm")},m(n,a){Me(n,s,a)},d(n){n&&Te(s)}}}function Xl(e){let s,n,a,r,i,b,v,h,c,w,f,d,g,y,k=e[0].toFixed(0)+"",m,T,F,p,P,R,E,L,D,ne,j,J,z,ae,G=e[1].toFixed(2)+"",K,ue,q,N,M,V,S,x,ee,ve,_e,Z,fe,le,we,ie,W,se,he,X,je,ye,Le,be,Pe,Ce,Y,O,Q,C,te=(e[11]/1e3).toFixed(3)+"",$,He,tt,Ge,Ie,ut,Ae,Je=(e[10]/1e3).toFixed(3)+"",at,Ve,We,Be,Re,ze=e[8]===1?"OPERATE":"STANDBY",Ze,lt,Ne,Qe,Ke,$e,De;function Oe(H,pe){return H[1]<1.5?Jl:H[1]<2?Ul:H[1]<3?ql:Wl}let Ee=Oe(e),qe=Ee(e);return{c(){s=o("div"),n=o("div"),a=o("h2"),a.textContent="Tuner Genius XL",r=u(),i=o("div"),b=o("span"),v=_(e[12]),h=u(),c=o("span"),w=u(),f=o("div"),d=o("div"),g=o("div"),y=o("div"),m=_(k),T=o("span"),T.textContent="W",F=u(),p=o("div"),p.textContent="Forward Power",P=u(),R=o("div"),E=o("div"),L=o("div"),D=u(),ne=o("div"),ne.innerHTML="0 1000 2000",j=u(),J=o("div"),z=o("div"),ae=o("div"),K=_(G),ue=u(),q=o("div"),q.textContent="SWR",N=u(),M=o("div"),qe.c(),V=u(),S=o("div"),x=o("div"),ee=o("div"),ve=_(e[7]),_e=u(),Z=o("div"),Z.textContent="C1",fe=u(),le=o("div"),we=o("div"),ie=_(e[6]),W=u(),se=o("div"),se.textContent="L",he=u(),X=o("div"),je=o("div"),ye=_(e[5]),Le=u(),be=o("div"),be.textContent="C2",Pe=u(),Ce=o("div"),Y=o("div"),O=o("div"),O.textContent="Freq A",Q=u(),C=o("div"),$=_(te),He=o("span"),He.textContent="MHz",tt=u(),Ge=o("div"),Ie=o("div"),Ie.textContent="Freq B",ut=u(),Ae=o("div"),at=_(Je),Ve=o("span"),Ve.textContent="MHz",We=u(),Be=o("div"),Re=o("button"),Ze=_(ze),lt=u(),Ne=o("button"),Ne.textContent="BYPASS",Qe=u(),Ke=o("button"),Ke.innerHTML=` + AUTO TUNE`,l(a,"class","svelte-ulw0vm"),l(b,"class","tuning-badge svelte-ulw0vm"),B(b,"tuning",e[12]==="TUNING"),l(c,"class","status-dot svelte-ulw0vm"),B(c,"disconnected",!e[4]),l(i,"class","header-right svelte-ulw0vm"),l(n,"class","card-header svelte-ulw0vm"),l(T,"class","unit svelte-ulw0vm"),l(y,"class","power-value svelte-ulw0vm"),l(p,"class","power-label svelte-ulw0vm"),l(g,"class","power-main svelte-ulw0vm"),l(L,"class","power-bar-glow svelte-ulw0vm"),l(E,"class","power-bar-fill svelte-ulw0vm"),re(E,"width",e[2]+"%"),l(ne,"class","power-scale svelte-ulw0vm"),l(R,"class","power-bar svelte-ulw0vm"),l(d,"class","power-display svelte-ulw0vm"),l(ae,"class","swr-value svelte-ulw0vm"),l(q,"class","swr-label svelte-ulw0vm"),l(z,"class","swr-circle svelte-ulw0vm"),re(z,"--swr-color",e[3]),l(M,"class","swr-status svelte-ulw0vm"),l(J,"class","swr-container svelte-ulw0vm"),l(ee,"class","cap-value svelte-ulw0vm"),l(Z,"class","cap-label svelte-ulw0vm"),l(x,"class","cap-item svelte-ulw0vm"),l(we,"class","cap-value svelte-ulw0vm"),l(se,"class","cap-label svelte-ulw0vm"),l(le,"class","cap-item svelte-ulw0vm"),l(je,"class","cap-value svelte-ulw0vm"),l(be,"class","cap-label svelte-ulw0vm"),l(X,"class","cap-item svelte-ulw0vm"),l(S,"class","capacitors svelte-ulw0vm"),l(O,"class","freq-label svelte-ulw0vm"),l(He,"class","freq-unit svelte-ulw0vm"),l(C,"class","freq-value svelte-ulw0vm"),l(Y,"class","freq-item svelte-ulw0vm"),l(Ie,"class","freq-label svelte-ulw0vm"),l(Ve,"class","freq-unit svelte-ulw0vm"),l(Ae,"class","freq-value svelte-ulw0vm"),l(Ge,"class","freq-item svelte-ulw0vm"),l(Ce,"class","freq-display svelte-ulw0vm"),l(Re,"class","control-btn operate svelte-ulw0vm"),B(Re,"active",e[8]===1),l(Ne,"class","control-btn bypass svelte-ulw0vm"),B(Ne,"active",e[9]),l(Be,"class","controls svelte-ulw0vm"),l(Ke,"class","tune-btn svelte-ulw0vm"),l(f,"class","metrics svelte-ulw0vm"),l(s,"class","card svelte-ulw0vm")},m(H,pe){Me(H,s,pe),t(s,n),t(n,a),t(n,r),t(n,i),t(i,b),t(b,v),t(i,h),t(i,c),t(s,w),t(s,f),t(f,d),t(d,g),t(g,y),t(y,m),t(y,T),t(g,F),t(g,p),t(d,P),t(d,R),t(R,E),t(E,L),t(R,D),t(R,ne),t(f,j),t(f,J),t(J,z),t(z,ae),t(ae,K),t(z,ue),t(z,q),t(J,N),t(J,M),qe.m(M,null),t(f,V),t(f,S),t(S,x),t(x,ee),t(ee,ve),t(x,_e),t(x,Z),t(S,fe),t(S,le),t(le,we),t(we,ie),t(le,W),t(le,se),t(S,he),t(S,X),t(X,je),t(je,ye),t(X,Le),t(X,be),t(f,Pe),t(f,Ce),t(Ce,Y),t(Y,O),t(Y,Q),t(Y,C),t(C,$),t(C,He),t(Ce,tt),t(Ce,Ge),t(Ge,Ie),t(Ge,ut),t(Ge,Ae),t(Ae,at),t(Ae,Ve),t(f,We),t(f,Be),t(Be,Re),t(Re,Ze),t(Be,lt),t(Be,Ne),t(f,Qe),t(f,Ke),$e||(De=[ke(Re,"click",e[17]),ke(Ne,"click",e[18]),ke(Ke,"click",e[13])],$e=!0)},p(H,[pe]){pe&4096&&A(v,H[12]),pe&4096&&B(b,"tuning",H[12]==="TUNING"),pe&16&&B(c,"disconnected",!H[4]),pe&1&&k!==(k=H[0].toFixed(0)+"")&&A(m,k),pe&4&&re(E,"width",H[2]+"%"),pe&2&&G!==(G=H[1].toFixed(2)+"")&&A(K,G),pe&8&&re(z,"--swr-color",H[3]),Ee!==(Ee=Oe(H))&&(qe.d(1),qe=Ee(H),qe&&(qe.c(),qe.m(M,null))),pe&128&&A(ve,H[7]),pe&64&&A(ie,H[6]),pe&32&&A(ye,H[5]),pe&2048&&te!==(te=(H[11]/1e3).toFixed(3)+"")&&A($,te),pe&1024&&Je!==(Je=(H[10]/1e3).toFixed(3)+"")&&A(at,Je),pe&256&&ze!==(ze=H[8]===1?"OPERATE":"STANDBY")&&A(Ze,ze),pe&256&&B(Re,"active",H[8]===1),pe&512&&B(Ne,"active",H[9])},i:Se,o:Se,d(H){H&&Te(s),qe.d(),$e=!1,ot(De)}}}function Yl(e,s,n){let a,r,i,b,v,h,c,w,f,d,g,y,k,{status:m}=s;async function T(){try{await Fe.tuner.autoTune()}catch(E){console.error("Failed to tune:",E),alert("Failed to start tuning")}}async function F(E){try{await Fe.tuner.setBypass(E)}catch(L){console.error("Failed to set bypass:",L),alert("Failed to set bypass")}}async function p(E){try{await Fe.tuner.setOperate(E)}catch(L){console.error("Failed to set operate:",L),alert("Failed to set operate")}}const P=()=>p(c===1?0:1),R=()=>F(h?0:1);return e.$$set=E=>{"status"in E&&n(16,m=E.status)},e.$$.update=()=>{e.$$.dirty&65536&&n(0,a=(m==null?void 0:m.power_forward)||0),e.$$.dirty&65536&&n(1,r=(m==null?void 0:m.swr)||1),e.$$.dirty&65536&&n(12,i=(m==null?void 0:m.tuning_status)||"READY"),e.$$.dirty&65536&&n(11,b=(m==null?void 0:m.frequency_a)||0),e.$$.dirty&65536&&n(10,v=(m==null?void 0:m.frequency_b)||0),e.$$.dirty&65536&&n(9,h=(m==null?void 0:m.bypass)||!1),e.$$.dirty&65536&&n(8,c=(m==null?void 0:m.state)||0),e.$$.dirty&65536&&n(7,w=(m==null?void 0:m.c1)||0),e.$$.dirty&65536&&n(6,f=(m==null?void 0:m.l)||0),e.$$.dirty&65536&&n(5,d=(m==null?void 0:m.c2)||0),e.$$.dirty&65536&&n(4,g=(m==null?void 0:m.connected)||!1),e.$$.dirty&2&&n(3,y=r<1.5?"#4caf50":r<2?"#ffc107":r<3?"#ff9800":"#f44336"),e.$$.dirty&1&&n(2,k=Math.min(a/2e3*100,100))},[a,r,k,y,g,d,f,w,c,h,v,b,i,T,F,p,m,P,R]}class Vl extends yt{constructor(s){super(),_t(this,s,Yl,Xl,ft,{status:16})}}function sl(e,s,n){const a=e.slice();a[12]=s[n];const r=a[1].tx&&a[1].tx_ant===a[12].number;a[13]=r;const i=a[0].tx&&a[0].tx_ant===a[12].number;a[14]=i;const b=!a[1].tx&&a[1].rx_ant===a[12].number;a[15]=b;const v=!a[0].tx&&a[0].rx_ant===a[12].number;a[16]=v;const h=a[13]||a[14];a[17]=h;const c=a[15]||a[13];a[18]=c;const w=a[16]||a[14];return a[19]=w,a}function nl(e){let s,n,a=e[12].name+"",r,i,b,v,h,c,w,f,d;function g(){return e[9](e[12])}function y(){return e[10](e[12])}return{c(){s=o("div"),n=o("div"),r=_(a),i=u(),b=o("div"),v=o("button"),v.textContent="A",h=u(),c=o("button"),c.textContent="B",w=u(),l(n,"class","antenna-name svelte-1r71rs8"),l(v,"class","port-btn svelte-1r71rs8"),B(v,"active",e[18]),l(c,"class","port-btn svelte-1r71rs8"),B(c,"active",e[19]),l(b,"class","antenna-ports svelte-1r71rs8"),l(s,"class","antenna-card svelte-1r71rs8"),B(s,"tx",e[17]),B(s,"active-a",e[18]),B(s,"active-b",e[19])},m(k,m){Me(k,s,m),t(s,n),t(n,r),t(s,i),t(s,b),t(b,v),t(b,h),t(b,c),t(s,w),f||(d=[ke(v,"click",g),ke(c,"click",y)],f=!0)},p(k,m){e=k,m&16&&a!==(a=e[12].name+"")&&A(r,a),m&18&&B(v,"active",e[18]),m&17&&B(c,"active",e[19]),m&19&&B(s,"tx",e[17]),m&18&&B(s,"active-a",e[18]),m&17&&B(s,"active-b",e[19])},d(k){k&&Te(s),f=!1,ot(d)}}}function Kl(e){let s,n,a,r,i,b,v,h,c,w,f=(e[1].source||"FLEX")+"",d,g,y,k,m=(e[0].source||"FLEX")+"",T,F,p,P,R,E,L,D,ne,j,J,z,ae,G,K,ue,q=vt(e[4]),N=[];for(let M=0;M🔄 + REBOOT`,l(a,"class","svelte-1r71rs8"),l(i,"class","status-dot svelte-1r71rs8"),B(i,"disconnected",!e[5]),l(n,"class","card-header svelte-1r71rs8"),l(w,"class","source-label svelte-1r71rs8"),l(c,"class","source-item svelte-1r71rs8"),l(k,"class","source-label svelte-1r71rs8"),l(y,"class","source-item svelte-1r71rs8"),l(h,"class","sources svelte-1r71rs8"),l(R,"class","band-value svelte-1r71rs8"),l(P,"class","band-item svelte-1r71rs8"),l(ne,"class","band-value svelte-1r71rs8"),l(D,"class","band-item svelte-1r71rs8"),l(p,"class","bands svelte-1r71rs8"),l(z,"class","antennas svelte-1r71rs8"),l(G,"class","reboot-btn svelte-1r71rs8"),l(v,"class","metrics svelte-1r71rs8"),l(s,"class","card svelte-1r71rs8")},m(M,V){Me(M,s,V),t(s,n),t(n,a),t(n,r),t(n,i),t(s,b),t(s,v),t(v,h),t(h,c),t(c,w),t(w,d),t(h,g),t(h,y),t(y,k),t(k,T),t(v,F),t(v,p),t(p,P),t(P,R),t(R,E),t(p,L),t(p,D),t(D,ne),t(ne,j),t(v,J),t(v,z);for(let S=0;Sf(1,k.number),y=k=>f(2,k.number);return e.$$set=k=>{"status"in k&&n(8,c=k.status)},e.$$.update=()=>{e.$$.dirty&256&&n(5,a=(c==null?void 0:c.connected)||!1),e.$$.dirty&256&&n(1,r=(c==null?void 0:c.port_a)||{}),e.$$.dirty&256&&n(0,i=(c==null?void 0:c.port_b)||{}),e.$$.dirty&256&&n(4,b=(c==null?void 0:c.antennas)||[]),e.$$.dirty&2&&n(3,v=w[r.band]||"None"),e.$$.dirty&1&&n(2,h=w[i.band]||"None")},[i,r,h,v,b,a,f,d,c,g,y]}class Ql extends yt{constructor(s){super(),_t(this,s,Zl,Kl,ft,{status:8})}}function ol(e,s,n){const a=e.slice();a[9]=s[n];const r=150+125*Math.sin(a[9]*Math.PI/180);a[10]=r;const i=150-125*Math.cos(a[9]*Math.PI/180);return a[11]=i,a}function al(e){let s,n,a;return{c(){s=de("text"),n=_(e[9]),a=_("°"),l(s,"x",e[10]),l(s,"y",e[11]),l(s,"text-anchor","middle"),l(s,"dominant-baseline","middle"),l(s,"class","degree-label svelte-cd0075")},m(r,i){Me(r,s,i),t(s,n),t(s,a)},p:Se,d(r){r&&Te(s)}}}function $l(e){let s,n,a,r,i,b,v,h,c,w,f,d,g,y,k,m,T,F,p,P,R,E,L,D,ne,j,J,z,ae,G,K,ue,q,N,M,V,S,x,ee,ve,_e,Z,fe,le,we,ie,W,se,he,X,je,ye,Le,be,Pe,Ce=vt([45,135,225,315]),Y=[];for(let O=0;O<4;O+=1)Y[O]=al(ol(e,Ce,O));return{c(){s=o("div"),n=o("div"),a=o("h2"),a.textContent="Rotator Genius",r=u(),i=o("span"),b=u(),v=o("div"),h=o("div"),c=o("div"),w=o("div"),w.textContent="CURRENT HEADING",f=u(),d=o("div"),g=_(e[0]),y=_("°"),k=u(),m=o("div"),T=o("button"),T.textContent="↺",F=u(),p=o("button"),p.textContent="■",P=u(),R=o("button"),R.textContent="↻",E=u(),L=o("div"),D=de("svg"),ne=de("defs"),j=de("radialGradient"),J=de("stop"),z=de("stop"),ae=de("circle"),G=de("circle"),K=de("circle"),ue=de("circle"),q=de("g"),N=de("g"),M=de("path"),V=de("line"),S=de("line"),x=de("g"),ee=de("polygon"),_e=de("circle"),Z=de("animate"),fe=de("circle"),le=de("animate"),we=de("animate"),ie=de("text"),W=_("N"),se=de("text"),he=_("E"),X=de("text"),je=_("S"),ye=de("text"),Le=_("W");for(let O=0;O<4;O+=1)Y[O].c();l(a,"class","svelte-cd0075"),l(i,"class","status-dot svelte-cd0075"),B(i,"disconnected",!e[1]),l(n,"class","card-header svelte-cd0075"),l(w,"class","heading-label svelte-cd0075"),l(d,"class","heading-value svelte-cd0075"),l(c,"class","heading-display-compact svelte-cd0075"),l(T,"class","btn-mini ccw svelte-cd0075"),l(T,"title","Rotate Counter-Clockwise"),l(p,"class","btn-mini stop svelte-cd0075"),l(p,"title","Stop Rotation"),l(R,"class","btn-mini cw svelte-cd0075"),l(R,"title","Rotate Clockwise"),l(m,"class","controls-compact svelte-cd0075"),l(h,"class","heading-controls-row svelte-cd0075"),l(J,"offset","0%"),re(J,"stop-color","rgba(79, 195, 247, 0.7)"),re(J,"stop-opacity","1"),l(z,"offset","100%"),re(z,"stop-color","rgba(79, 195, 247, 0)"),re(z,"stop-opacity","0"),l(j,"id","beamGradient"),l(ae,"cx","150"),l(ae,"cy","150"),l(ae,"r","140"),l(ae,"fill","rgba(30, 64, 175, 0.15)"),l(ae,"stroke","rgba(79, 195, 247, 0.4)"),l(ae,"stroke-width","2"),l(G,"cx","150"),l(G,"cy","150"),l(G,"r","105"),l(G,"fill","none"),l(G,"stroke","rgba(79,195,247,0.2)"),l(G,"stroke-width","1"),l(G,"stroke-dasharray","3,3"),l(K,"cx","150"),l(K,"cy","150"),l(K,"r","70"),l(K,"fill","none"),l(K,"stroke","rgba(79,195,247,0.2)"),l(K,"stroke-width","1"),l(K,"stroke-dasharray","3,3"),l(ue,"cx","150"),l(ue,"cy","150"),l(ue,"r","35"),l(ue,"fill","none"),l(ue,"stroke","rgba(79,195,247,0.2)"),l(ue,"stroke-width","1"),l(ue,"stroke-dasharray","3,3"),l(M,"d","M 0,0 L "+-Math.sin(15*Math.PI/180)*130+","+-Math.cos(15*Math.PI/180)*130+` \r + A 130,130 0 0,1 `+Math.sin(15*Math.PI/180)*130+","+-Math.cos(15*Math.PI/180)*130+" Z"),l(M,"fill","url(#beamGradient)"),l(M,"opacity","0.85"),l(V,"x1","0"),l(V,"y1","0"),l(V,"x2",-Math.sin(15*Math.PI/180)*130),l(V,"y2",-Math.cos(15*Math.PI/180)*130),l(V,"stroke","#4fc3f7"),l(V,"stroke-width","2"),l(V,"opacity","0.9"),l(S,"x1","0"),l(S,"y1","0"),l(S,"x2",Math.sin(15*Math.PI/180)*130),l(S,"y2",-Math.cos(15*Math.PI/180)*130),l(S,"stroke","#4fc3f7"),l(S,"stroke-width","2"),l(S,"opacity","0.9"),l(ee,"points","0,-20 -8,5 0,0 8,5"),l(ee,"fill","#4fc3f7"),l(ee,"stroke","#0288d1"),l(ee,"stroke-width","2"),re(ee,"filter","drop-shadow(0 0 10px rgba(79, 195, 247, 1))"),l(x,"transform","translate(0, -110)"),l(N,"transform",ve="rotate("+e[0]+")"),l(Z,"attributeName","r"),l(Z,"values","5;7;5"),l(Z,"dur","2s"),l(Z,"repeatCount","indefinite"),l(_e,"cx","0"),l(_e,"cy","0"),l(_e,"r","5"),l(_e,"fill","#f44336"),l(_e,"stroke","#fff"),l(_e,"stroke-width","2"),l(le,"attributeName","r"),l(le,"values","10;16;10"),l(le,"dur","2s"),l(le,"repeatCount","indefinite"),l(we,"attributeName","opacity"),l(we,"values","0.5;0;0.5"),l(we,"dur","2s"),l(we,"repeatCount","indefinite"),l(fe,"cx","0"),l(fe,"cy","0"),l(fe,"r","10"),l(fe,"fill","none"),l(fe,"stroke","#f44336"),l(fe,"stroke-width","1.5"),l(fe,"opacity","0.5"),l(q,"transform","translate(150, 150)"),l(ie,"x","150"),l(ie,"y","20"),l(ie,"text-anchor","middle"),l(ie,"class","cardinal svelte-cd0075"),l(se,"x","280"),l(se,"y","155"),l(se,"text-anchor","middle"),l(se,"class","cardinal svelte-cd0075"),l(X,"x","150"),l(X,"y","285"),l(X,"text-anchor","middle"),l(X,"class","cardinal svelte-cd0075"),l(ye,"x","20"),l(ye,"y","155"),l(ye,"text-anchor","middle"),l(ye,"class","cardinal svelte-cd0075"),l(D,"viewBox","0 0 300 300"),l(D,"class","map-svg clickable-compass svelte-cd0075"),l(L,"class","map-container svelte-cd0075"),l(v,"class","metrics svelte-cd0075"),l(s,"class","card svelte-cd0075")},m(O,Q){Me(O,s,Q),t(s,n),t(n,a),t(n,r),t(n,i),t(s,b),t(s,v),t(v,h),t(h,c),t(c,w),t(c,f),t(c,d),t(d,g),t(d,y),t(h,k),t(h,m),t(m,T),t(m,F),t(m,p),t(m,P),t(m,R),t(v,E),t(v,L),t(L,D),t(D,ne),t(ne,j),t(j,J),t(j,z),t(D,ae),t(D,G),t(D,K),t(D,ue),t(D,q),t(q,N),t(N,M),t(N,V),t(N,S),t(N,x),t(x,ee),t(q,_e),t(_e,Z),t(q,fe),t(fe,le),t(fe,we),t(D,ie),t(ie,W),t(D,se),t(se,he),t(D,X),t(X,je),t(D,ye),t(ye,Le);for(let C=0;C<4;C+=1)Y[C]&&Y[C].m(D,null);be||(Pe=[ke(T,"click",e[3]),ke(p,"click",e[4]),ke(R,"click",e[2]),ke(D,"click",e[5])],be=!0)},p(O,[Q]){if(Q&2&&B(i,"disconnected",!O[1]),Q&1&&A(g,O[0]),Q&1&&ve!==(ve="rotate("+O[0]+")")&&l(N,"transform",ve),Q&0){Ce=vt([45,135,225,315]);let C;for(C=0;C<4;C+=1){const te=ol(O,Ce,C);Y[C]?Y[C].p(te,Q):(Y[C]=al(te),Y[C].c(),Y[C].m(D,null))}for(;C<4;C+=1)Y[C].d(1)}},i:Se,o:Se,d(O){O&&Te(s),It(Y,O),be=!1,ot(Pe)}}}function xl(e,s,n){let{status:a}=s,r=0,i=!1,b=0;async function v(){if(b<0||b>359){alert("Heading must be between 0 and 359");return}try{const d=(b-10+360)%360;await Fe.rotator.setHeading(d)}catch(d){console.error("Failed to set heading:",d),alert("Failed to rotate")}}async function h(){try{await Fe.rotator.rotateCW()}catch(d){console.error("Failed to rotate CW:",d)}}async function c(){try{await Fe.rotator.rotateCCW()}catch(d){console.error("Failed to rotate CCW:",d)}}async function w(){try{await Fe.rotator.stop()}catch(d){console.error("Failed to stop:",d)}}function f(d){const y=d.currentTarget.getBoundingClientRect(),k=y.width/2,m=y.height/2,T=d.clientX-y.left-k,F=d.clientY-y.top-m;let p=Math.atan2(T,-F)*(180/Math.PI);p<0&&(p+=360),b=Math.round(p/5)*5,v()}return e.$$set=d=>{"status"in d&&n(6,a=d.status)},e.$$.update=()=>{e.$$.dirty&64&&(a==null?void 0:a.heading)!==void 0&&(a==null?void 0:a.heading)!==null&&n(0,r=a.heading),e.$$.dirty&64&&n(1,i=(a==null?void 0:a.connected)||!1)},[r,i,h,c,w,f,a]}class es extends yt{constructor(s){super(),_t(this,s,xl,$l,ft,{status:6})}}function rl(e,s,n){const a=e.slice();return a[29]=s[n],a}function il(e){let s,n=e[29].label+"",a;return{c(){s=o("option"),a=_(n),s.__value=e[29].value,wt(s,s.__value)},m(r,i){Me(r,s,i),t(s,a)},p:Se,d(r){r&&Te(s)}}}function cl(e){let s,n,a,r,i,b,v,h,c,w=e[6].toFixed(0)+"",f,d;return{c(){s=o("div"),n=o("h3"),n.textContent="Motors Moving...",a=u(),r=o("div"),i=o("div"),b=u(),v=o("div"),h=_(e[0]),c=_(" / 60 ("),f=_(w),d=_("%)"),l(n,"class","svelte-1bbmoku"),l(i,"class","progress-fill svelte-1bbmoku"),re(i,"width",e[6]+"%"),l(r,"class","progress-bar svelte-1bbmoku"),l(v,"class","progress-text svelte-1bbmoku"),l(s,"class","progress-section svelte-1bbmoku")},m(g,y){Me(g,s,y),t(s,n),t(s,a),t(s,r),t(r,i),t(s,b),t(s,v),t(v,h),t(v,c),t(v,f),t(v,d)},p(g,y){y[0]&64&&re(i,"width",g[6]+"%"),y[0]&1&&A(h,g[0]),y[0]&64&&w!==(w=g[6].toFixed(0)+"")&&A(f,w)},d(g){g&&Te(s)}}}function ts(e){let s,n,a,r,i,b,v,h,c,w,f,d,g=(e[1]/1e3).toFixed(3)+"",y,k,m,T,F,p,P,R,E,L,D,ne,j,J=e[10][e[2]]+"",z,ae,G,K,ue,q,N,M,V,S,x,ee,ve,_e,Z,fe,le,we,ie,W,se,he,X,je,ye,Le,be,Pe,Ce,Y=vt(e[11]),O=[];for(let C=0;C0&&cl(e);return{c(){s=o("div"),n=o("div"),a=o("h2"),a.textContent="Ultrabeam VL2.3",r=u(),i=o("span"),b=u(),v=o("div"),h=o("div"),c=o("div"),w=o("div"),w.textContent="Frequency",f=u(),d=o("div"),y=_(g),k=_(" MHz"),m=u(),T=o("div"),F=o("div"),F.textContent="Band",p=u(),P=o("div"),R=_(e[7]),E=u(),L=o("div"),D=o("div"),D.textContent="Direction",ne=u(),j=o("div"),z=_(J),ae=u(),G=o("div"),K=o("h3"),K.textContent="Auto Tracking",ue=u(),q=o("div"),N=o("label"),M=o("input"),V=u(),S=o("span"),S.textContent="Enable Auto-Track from Tuner",x=u(),ee=o("div"),ve=o("label"),ve.textContent="Threshold:",_e=u(),Z=o("select");for(let C=0;C↓ + Retract Elements`,l(a,"class","svelte-1bbmoku"),l(i,"class","status-dot svelte-1bbmoku"),B(i,"disconnected",!e[9]),l(n,"class","card-header svelte-1bbmoku"),l(w,"class","status-label svelte-1bbmoku"),l(d,"class","status-value freq svelte-1bbmoku"),l(c,"class","status-item svelte-1bbmoku"),l(F,"class","status-label svelte-1bbmoku"),l(P,"class","status-value band svelte-1bbmoku"),l(T,"class","status-item svelte-1bbmoku"),l(D,"class","status-label svelte-1bbmoku"),l(j,"class","status-value direction svelte-1bbmoku"),l(L,"class","status-item svelte-1bbmoku"),l(h,"class","status-grid svelte-1bbmoku"),l(K,"class","svelte-1bbmoku"),l(M,"type","checkbox"),l(M,"class","svelte-1bbmoku"),l(N,"class","toggle-label svelte-1bbmoku"),l(ve,"for","threshold-select"),l(ve,"class","svelte-1bbmoku"),l(Z,"id","threshold-select"),l(Z,"class","svelte-1bbmoku"),e[4]===void 0&&Lt(()=>e[19].call(Z)),l(ee,"class","threshold-group svelte-1bbmoku"),l(we,"for","dir-select"),l(we,"class","svelte-1bbmoku"),se.__value=0,wt(se,se.__value),he.__value=1,wt(he,he.__value),X.__value=2,wt(X,X.__value),l(W,"id","dir-select"),l(W,"class","svelte-1bbmoku"),e[5]===void 0&&Lt(()=>e[20].call(W)),l(le,"class","direction-group svelte-1bbmoku"),l(q,"class","auto-track-controls svelte-1bbmoku"),l(G,"class","control-section compact svelte-1bbmoku"),l(be,"class","btn-danger svelte-1bbmoku"),l(Le,"class","actions svelte-1bbmoku"),l(v,"class","metrics svelte-1bbmoku"),l(s,"class","card svelte-1bbmoku")},m(C,te){Me(C,s,te),t(s,n),t(n,a),t(n,r),t(n,i),t(s,b),t(s,v),t(v,h),t(h,c),t(c,w),t(c,f),t(c,d),t(d,y),t(d,k),t(h,m),t(h,T),t(T,F),t(T,p),t(T,P),t(P,R),t(h,E),t(h,L),t(L,D),t(L,ne),t(L,j),t(j,z),t(v,ae),t(v,G),t(G,K),t(G,ue),t(G,q),t(q,N),t(N,M),M.checked=e[3],t(N,V),t(N,S),t(q,x),t(q,ee),t(ee,ve),t(ee,_e),t(ee,Z);for(let $=0;$0?Q?Q.p(C,te):(Q=cl(C),Q.c(),Q.m(v,ye)):Q&&(Q.d(1),Q=null)},i:Se,o:Se,d(C){C&&Te(s),It(O,C),Q&&Q.d(),Pe=!1,ot(Ce)}}}function ls(e,s,n){let a,r,i,b,v,h,c,w,f,{status:d}=s;const g=["6M","10M","12M","15M","17M","20M","30M","40M"];function y(j,J){return J>=0&&J<=7?g[J]:j>=7e3&&j<=7300?"40M":j>=10100&&j<=10150?"30M":j>=14e3&&j<=14350?"20M":j>=18068&&j<=18168?"17M":j>=21e3&&j<=21450?"15M":j>=24890&&j<=24990?"12M":j>=28e3&&j<=29700?"10M":j>=5e4&&j<=54e3?"6M":"Unknown"}const k=["Normal","180°","Bi-Dir"],m=[{value:25,label:"25 kHz"},{value:50,label:"50 kHz"},{value:100,label:"100 kHz"}];let T=!0,F=25,p=0;async function P(){if(r!==0)try{await Fe.ultrabeam.setFrequency(r,p),await Fe.ultrabeam.setDirection(p)}catch(j){console.log("Direction change sent (may show code 30 if busy):",j)}}async function R(){try{await Fe.ultrabeam.setAutoTrack(T,F)}catch(j){console.error("Failed to update auto-track:",j),alert("Failed to update auto-track settings")}}async function E(){if(confirm("Retract all antenna elements?"))try{await Fe.ultrabeam.retract()}catch(j){console.error("Failed to retract:",j),alert("Failed to retract")}}function L(){T=this.checked,n(3,T)}function D(){F=Qt(this),n(4,F),n(11,m)}function ne(){p=Qt(this),n(5,p),n(2,b),n(15,d)}return e.$$set=j=>{"status"in j&&n(15,d=j.status)},e.$$.update=()=>{e.$$.dirty[0]&32768&&n(9,a=(d==null?void 0:d.connected)||!1),e.$$.dirty[0]&32768&&n(1,r=(d==null?void 0:d.frequency)||0),e.$$.dirty[0]&32768&&n(17,i=(d==null?void 0:d.band)||0),e.$$.dirty[0]&32768&&n(2,b=(d==null?void 0:d.direction)||0),e.$$.dirty[0]&32768&&n(8,v=(d==null?void 0:d.motors_moving)||0),e.$$.dirty[0]&32768&&n(16,h=(d==null?void 0:d.progress_total)||0),e.$$.dirty[0]&32768&&n(0,c=(d==null?void 0:d.progress_current)||0),e.$$.dirty[0]&32768&&d!=null&&d.element_lengths,e.$$.dirty[0]&32768&&d&&`${d.firmware_major}${d.firmware_minor}`,e.$$.dirty[0]&131074&&n(7,w=y(r,i)),e.$$.dirty[0]&4&&n(5,p=b),e.$$.dirty[0]&131072,e.$$.dirty[0]&65537&&n(6,f=h>0?c/60*100:0)},[c,r,b,T,F,p,f,w,v,a,k,m,P,R,E,d,h,i,L,D,ne]}class ss extends yt{constructor(s){super(),_t(this,s,ls,ts,ft,{status:15},null,[-1,-1])}}function ns(e){var At,Ye,it,ct,dt,kt;let s,n,a,r,i,b,v,h,c,w,f=e[1]?"Connected":"Disconnected",d,g,y,k,m,T,F,p=e[5].sfi+"",P,R,E,L,D,ne=e[5].sunspots+"",j,J,z,ae,G,K=e[5].a_index+"",ue,q,N,M,V,S=e[5].k_index+"",x,ee,ve,_e,Z,fe=e[5].geomag+"",le,we,ie,W,se,he,X=e[4].wind_speed.toFixed(1)+"",je,ye,Le,be,Pe,Ce=e[4].wind_gust.toFixed(1)+"",Y,O,Q,C,te,$=e[4].temp.toFixed(1)+"",He,tt,Ge,Ie,ut,Ae=e[4].feels_like.toFixed(1)+"",Je,at,Ve,We,Be,Re=dl(e[2])+"",ze,Ze,lt,Ne=e[2].toLocaleDateString()+"",Qe,Ke,$e,De,Oe,Ee,qe,H,pe,st,rt,Xe,nt,pt,xe,Pt,Ue,me;return Ee=new Ll({props:{status:(At=e[0])==null?void 0:At.webswitch}}),H=new Gl({props:{status:(Ye=e[0])==null?void 0:Ye.power_genius}}),st=new Vl({props:{status:(it=e[0])==null?void 0:it.tuner_genius}}),nt=new Ql({props:{status:(ct=e[0])==null?void 0:ct.antenna_genius}}),xe=new ss({props:{status:(dt=e[0])==null?void 0:dt.ultrabeam}}),Ue=new es({props:{status:(kt=e[0])==null?void 0:kt.rotator_genius}}),{c(){s=o("div"),n=o("header"),a=o("div"),r=o("h1"),i=_(e[3]),b=_(" Shack"),v=u(),h=o("div"),c=o("span"),w=u(),d=_(f),g=u(),y=o("div"),k=o("div"),m=o("span"),T=_("SFI "),F=o("span"),P=_(p),R=u(),E=o("span"),L=_("Spots "),D=o("span"),j=_(ne),J=u(),z=o("span"),ae=_("A "),G=o("span"),ue=_(K),q=u(),N=o("span"),M=_("K "),V=o("span"),x=_(S),ee=u(),ve=o("span"),_e=_("G "),Z=o("span"),le=_(fe),we=u(),ie=o("div"),W=o("div"),se=o("span"),he=_("🌬️ "),je=_(X),ye=_("m/s"),Le=u(),be=o("span"),Pe=_("💨 "),Y=_(Ce),O=_("m/s"),Q=u(),C=o("span"),te=_("🌡️ "),He=_($),tt=_("°C"),Ge=u(),Ie=o("span"),ut=_("→ "),Je=_(Ae),at=_("°C"),Ve=u(),We=o("div"),Be=o("span"),ze=_(Re),Ze=u(),lt=o("span"),Qe=_(Ne),Ke=u(),$e=o("main"),De=o("div"),Oe=o("div"),Ft(Ee.$$.fragment),qe=u(),Ft(H.$$.fragment),pe=u(),Ft(st.$$.fragment),rt=u(),Xe=o("div"),Ft(nt.$$.fragment),pt=u(),Ft(xe.$$.fragment),Pt=u(),Ft(Ue.$$.fragment),l(r,"class","svelte-1hrhory"),l(c,"class","status-indicator"),B(c,"status-online",e[1]),B(c,"status-offline",!e[1]),l(h,"class","connection-status svelte-1hrhory"),l(a,"class","header-left svelte-1hrhory"),l(F,"class","value svelte-1hrhory"),l(m,"class","solar-item svelte-1hrhory"),l(D,"class","value svelte-1hrhory"),l(E,"class","solar-item svelte-1hrhory"),l(G,"class","value svelte-1hrhory"),l(z,"class","solar-item svelte-1hrhory"),l(V,"class","value svelte-1hrhory"),l(N,"class","solar-item svelte-1hrhory"),l(Z,"class","value svelte-1hrhory"),l(ve,"class","solar-item svelte-1hrhory"),l(k,"class","solar-info svelte-1hrhory"),l(y,"class","header-center svelte-1hrhory"),l(se,"title","Wind"),l(be,"title","Gust"),l(C,"title","Temperature"),l(Ie,"title","Feels like"),l(W,"class","weather-info svelte-1hrhory"),l(Be,"class","time svelte-1hrhory"),l(lt,"class","date svelte-1hrhory"),l(We,"class","clock svelte-1hrhory"),l(ie,"class","header-right svelte-1hrhory"),l(n,"class","svelte-1hrhory"),l(Oe,"class","row svelte-1hrhory"),l(Xe,"class","row svelte-1hrhory"),l(De,"class","dashboard-grid svelte-1hrhory"),l($e,"class","svelte-1hrhory"),l(s,"class","app svelte-1hrhory")},m(I,ce){Me(I,s,ce),t(s,n),t(n,a),t(a,r),t(r,i),t(r,b),t(a,v),t(a,h),t(h,c),t(h,w),t(h,d),t(n,g),t(n,y),t(y,k),t(k,m),t(m,T),t(m,F),t(F,P),t(k,R),t(k,E),t(E,L),t(E,D),t(D,j),t(k,J),t(k,z),t(z,ae),t(z,G),t(G,ue),t(k,q),t(k,N),t(N,M),t(N,V),t(V,x),t(k,ee),t(k,ve),t(ve,_e),t(ve,Z),t(Z,le),t(n,we),t(n,ie),t(ie,W),t(W,se),t(se,he),t(se,je),t(se,ye),t(W,Le),t(W,be),t(be,Pe),t(be,Y),t(be,O),t(W,Q),t(W,C),t(C,te),t(C,He),t(C,tt),t(W,Ge),t(W,Ie),t(Ie,ut),t(Ie,Je),t(Ie,at),t(ie,Ve),t(ie,We),t(We,Be),t(Be,ze),t(We,Ze),t(We,lt),t(lt,Qe),t(s,Ke),t(s,$e),t($e,De),t(De,Oe),bt(Ee,Oe,null),t(Oe,qe),bt(H,Oe,null),t(Oe,pe),bt(st,Oe,null),t(De,rt),t(De,Xe),bt(nt,Xe,null),t(Xe,pt),bt(xe,Xe,null),t(Xe,Pt),bt(Ue,Xe,null),me=!0},p(I,[ce]){var Ut,Jt,Xt,Yt,Vt,Kt;(!me||ce&8)&&A(i,I[3]),(!me||ce&2)&&B(c,"status-online",I[1]),(!me||ce&2)&&B(c,"status-offline",!I[1]),(!me||ce&2)&&f!==(f=I[1]?"Connected":"Disconnected")&&A(d,f),(!me||ce&32)&&p!==(p=I[5].sfi+"")&&A(P,p),(!me||ce&32)&&ne!==(ne=I[5].sunspots+"")&&A(j,ne),(!me||ce&32)&&K!==(K=I[5].a_index+"")&&A(ue,K),(!me||ce&32)&&S!==(S=I[5].k_index+"")&&A(x,S),(!me||ce&32)&&fe!==(fe=I[5].geomag+"")&&A(le,fe),(!me||ce&16)&&X!==(X=I[4].wind_speed.toFixed(1)+"")&&A(je,X),(!me||ce&16)&&Ce!==(Ce=I[4].wind_gust.toFixed(1)+"")&&A(Y,Ce),(!me||ce&16)&&$!==($=I[4].temp.toFixed(1)+"")&&A(He,$),(!me||ce&16)&&Ae!==(Ae=I[4].feels_like.toFixed(1)+"")&&A(Je,Ae),(!me||ce&4)&&Re!==(Re=dl(I[2])+"")&&A(ze,Re),(!me||ce&4)&&Ne!==(Ne=I[2].toLocaleDateString()+"")&&A(Qe,Ne);const mt={};ce&1&&(mt.status=(Ut=I[0])==null?void 0:Ut.webswitch),Ee.$set(mt);const et={};ce&1&&(et.status=(Jt=I[0])==null?void 0:Jt.power_genius),H.$set(et);const U={};ce&1&&(U.status=(Xt=I[0])==null?void 0:Xt.tuner_genius),st.$set(U);const oe={};ce&1&&(oe.status=(Yt=I[0])==null?void 0:Yt.antenna_genius),nt.$set(oe);const Wt={};ce&1&&(Wt.status=(Vt=I[0])==null?void 0:Vt.ultrabeam),xe.$set(Wt);const qt={};ce&1&&(qt.status=(Kt=I[0])==null?void 0:Kt.rotator_genius),Ue.$set(qt)},i(I){me||(ht(Ee.$$.fragment,I),ht(H.$$.fragment,I),ht(st.$$.fragment,I),ht(nt.$$.fragment,I),ht(xe.$$.fragment,I),ht(Ue.$$.fragment,I),me=!0)},o(I){Tt(Ee.$$.fragment,I),Tt(H.$$.fragment,I),Tt(st.$$.fragment,I),Tt(nt.$$.fragment,I),Tt(xe.$$.fragment,I),Tt(Ue.$$.fragment,I),me=!1},d(I){I&&Te(s),gt(Ee),gt(H),gt(st),gt(nt),gt(xe),gt(Ue)}}}function dl(e){return e.toTimeString().slice(0,8)}function os(e,s,n){let a,r,i=null,b=!1,v=new Date,h="F4BPO";const c=ml.subscribe(f=>{n(0,i=f)}),w=Ht.subscribe(f=>{n(1,b=f)});return _l(async()=>{el.connect();try{const d=await Fe.getConfig();d.callsign&&n(3,h=d.callsign)}catch(d){console.error("Failed to fetch config:",d)}const f=setInterval(()=>{n(2,v=new Date)},1e3);return()=>{clearInterval(f)}}),yl(()=>{el.disconnect(),c(),w()}),e.$$.update=()=>{e.$$.dirty&1&&n(5,a=(i==null?void 0:i.solar)||{sfi:0,sunspots:0,a_index:0,k_index:0,geomag:"Unknown"}),e.$$.dirty&1&&n(4,r=(i==null?void 0:i.weather)||{wind_speed:0,wind_gust:0,temp:0,feels_like:0})},[i,b,v,h,r,a]}class as extends yt{constructor(s){super(),_t(this,s,os,ns,ft,{})}}new as({target:document.getElementById("app")}); diff --git a/cmd/server/web/dist/assets/index-DvnnYzjx.css b/cmd/server/web/dist/assets/index-DvnnYzjx.css new file mode 100644 index 0000000..9bef444 --- /dev/null +++ b/cmd/server/web/dist/assets/index-DvnnYzjx.css @@ -0,0 +1 @@ +.card.svelte-z2csmj.svelte-z2csmj{background:linear-gradient(135deg,#1a2332,#0f1923);border:1px solid #2d3748;border-radius:8px;padding:0;overflow:hidden;box-shadow:0 4px 6px #0000004d}.card-header.svelte-z2csmj.svelte-z2csmj{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-z2csmj.svelte-z2csmj{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.status-dot.svelte-z2csmj.svelte-z2csmj{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-z2csmj.svelte-z2csmj{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-z2csmj.svelte-z2csmj{padding:16px;display:flex;flex-direction:column;gap:12px}.relays.svelte-z2csmj.svelte-z2csmj{display:flex;flex-direction:column;gap:8px}.relay-card.svelte-z2csmj.svelte-z2csmj{display:flex;justify-content:space-between;align-items:center;padding:12px;background:var(--bg-tertiary);border-radius:6px;border:1px solid var(--border-color);transition:all .3s}.relay-card.relay-on.svelte-z2csmj.svelte-z2csmj{background:#4caf501a;border-color:#4caf504d;box-shadow:0 0 15px #4caf5033}.relay-info.svelte-z2csmj.svelte-z2csmj{display:flex;align-items:center}.relay-details.svelte-z2csmj.svelte-z2csmj{display:flex;flex-direction:column;gap:2px}.relay-name.svelte-z2csmj.svelte-z2csmj{font-size:12px;color:var(--text-primary);font-weight:500}.relay-status.svelte-z2csmj.svelte-z2csmj{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.relay-card.relay-on.svelte-z2csmj .relay-status.svelte-z2csmj{color:#4caf50;font-weight:600}.relay-toggle.svelte-z2csmj.svelte-z2csmj{padding:0;background:transparent;border:none;cursor:pointer}.toggle-track.svelte-z2csmj.svelte-z2csmj{width:52px;height:28px;background:var(--bg-primary);border:2px solid var(--border-color);border-radius:14px;position:relative;transition:all .3s}.relay-toggle.svelte-z2csmj:hover .toggle-track.svelte-z2csmj{border-color:var(--accent-cyan)}.relay-toggle.active.svelte-z2csmj .toggle-track.svelte-z2csmj{background:linear-gradient(135deg,#4caf50,#66bb6a);border-color:#4caf50;box-shadow:0 0 15px #4caf5080}.toggle-thumb.svelte-z2csmj.svelte-z2csmj{width:20px;height:20px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:all .3s;box-shadow:0 2px 4px #0000004d}.relay-toggle.active.svelte-z2csmj .toggle-thumb.svelte-z2csmj{transform:translate(24px)}.relay-toggle.svelte-z2csmj.svelte-z2csmj:disabled{opacity:.5;cursor:not-allowed}.controls.svelte-z2csmj.svelte-z2csmj{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px}.control-btn.svelte-z2csmj.svelte-z2csmj{padding:12px;border-radius:6px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border:none;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:6px}.control-btn.svelte-z2csmj.svelte-z2csmj:hover{transform:translateY(-2px)}.control-btn.svelte-z2csmj.svelte-z2csmj:active{transform:translateY(0)}.btn-icon.svelte-z2csmj.svelte-z2csmj{font-size:16px}.all-on.svelte-z2csmj.svelte-z2csmj{background:linear-gradient(135deg,#4caf50,#66bb6a);color:#fff;box-shadow:0 4px 12px #4caf5066}.all-on.svelte-z2csmj.svelte-z2csmj:hover{box-shadow:0 6px 16px #4caf5080}.all-off.svelte-z2csmj.svelte-z2csmj{background:linear-gradient(135deg,#f44336,#d32f2f);color:#fff;box-shadow:0 4px 12px #f4433666}.all-off.svelte-z2csmj.svelte-z2csmj:hover{box-shadow:0 6px 16px #f4433680}.card.svelte-vjv5wg.svelte-vjv5wg{background:linear-gradient(135deg,#1a2332,#0f1923);border:1px solid #2d3748;border-radius:8px;padding:0;overflow:hidden;box-shadow:0 4px 6px #0000004d}.card-header.svelte-vjv5wg.svelte-vjv5wg{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-vjv5wg.svelte-vjv5wg{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.header-right.svelte-vjv5wg.svelte-vjv5wg{display:flex;align-items:center;gap:8px}.state-badge.svelte-vjv5wg.svelte-vjv5wg{padding:4px 12px;border-radius:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border:none;transition:all .2s}.state-badge.idle.svelte-vjv5wg.svelte-vjv5wg{background:#4caf5033;color:#4caf50}.state-badge.transmit.svelte-vjv5wg.svelte-vjv5wg{background:#ff980033;color:#ff9800;animation:svelte-vjv5wg-pulse 1s infinite}@keyframes svelte-vjv5wg-pulse{0%,to{opacity:1}50%{opacity:.7}}.status-dot.svelte-vjv5wg.svelte-vjv5wg{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-vjv5wg.svelte-vjv5wg{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-vjv5wg.svelte-vjv5wg{padding:16px;display:flex;flex-direction:column;gap:10px}.power-display.svelte-vjv5wg.svelte-vjv5wg{display:flex;flex-direction:column;gap:8px}.power-main.svelte-vjv5wg.svelte-vjv5wg{text-align:center}.power-value.svelte-vjv5wg.svelte-vjv5wg{font-size:48px;font-weight:200;color:var(--accent-cyan);line-height:1;text-shadow:0 0 20px rgba(79,195,247,.5)}.power-value.svelte-vjv5wg .unit.svelte-vjv5wg{font-size:20px;color:var(--text-secondary);margin-left:4px}.power-label.svelte-vjv5wg.svelte-vjv5wg{font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:1px;margin-top:4px}.power-bar.svelte-vjv5wg.svelte-vjv5wg{position:relative;height:8px;background:var(--bg-tertiary);border-radius:4px;overflow:hidden}.power-bar-fill.svelte-vjv5wg.svelte-vjv5wg{position:relative;height:100%;background:linear-gradient(90deg,#4caf50,#ffc107,#ff9800,#f44336);border-radius:4px;transition:width .3s ease}.power-bar-glow.svelte-vjv5wg.svelte-vjv5wg{position:absolute;top:0;right:0;width:20px;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.5));animation:svelte-vjv5wg-shimmer 2s infinite}@keyframes svelte-vjv5wg-shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.power-scale.svelte-vjv5wg.svelte-vjv5wg{display:flex;justify-content:space-between;font-size:9px;color:var(--text-muted);margin-top:4px}.swr-container.svelte-vjv5wg.svelte-vjv5wg{display:flex;align-items:center;gap:10px}.swr-circle.svelte-vjv5wg.svelte-vjv5wg{width:80px;height:80px;border-radius:50%;background:radial-gradient(circle,rgba(79,195,247,.1),transparent);border:3px solid var(--swr-color);display:flex;flex-direction:column;align-items:center;justify-content:center;box-shadow:0 0 20px var(--swr-color)}.swr-value.svelte-vjv5wg.svelte-vjv5wg{font-size:20px;font-weight:300;color:var(--swr-color)}.swr-label.svelte-vjv5wg.svelte-vjv5wg{font-size:10px;color:var(--text-muted);text-transform:uppercase}.swr-status.svelte-vjv5wg.svelte-vjv5wg{flex:1}.status-text.svelte-vjv5wg.svelte-vjv5wg{font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-text.good.svelte-vjv5wg.svelte-vjv5wg{color:#4caf50}.status-text.ok.svelte-vjv5wg.svelte-vjv5wg{color:#ffc107}.status-text.warning.svelte-vjv5wg.svelte-vjv5wg{color:#ff9800}.status-text.danger.svelte-vjv5wg.svelte-vjv5wg{color:#f44336}.temp-group.svelte-vjv5wg.svelte-vjv5wg{display:grid;grid-template-columns:1fr 1fr;gap:12px}.temp-item.svelte-vjv5wg.svelte-vjv5wg{display:flex;flex-direction:column;gap:4px;padding:12px;background:var(--bg-tertiary);border-radius:6px}.temp-value.svelte-vjv5wg.svelte-vjv5wg{font-size:32px;font-weight:300;line-height:1}.temp-label.svelte-vjv5wg.svelte-vjv5wg{font-size:10px;color:var(--text-muted);text-transform:uppercase}.temp-mini-bar.svelte-vjv5wg.svelte-vjv5wg{height:4px;background:#ffffff1a;border-radius:2px;overflow:hidden;margin-top:4px}.temp-mini-fill.svelte-vjv5wg.svelte-vjv5wg{height:100%;border-radius:2px;transition:width .3s ease}.params-grid.svelte-vjv5wg.svelte-vjv5wg{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.param-box.svelte-vjv5wg.svelte-vjv5wg{padding:8px;background:var(--bg-tertiary);border-radius:4px;text-align:center}.param-label.svelte-vjv5wg.svelte-vjv5wg{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.param-value.svelte-vjv5wg.svelte-vjv5wg{font-size:16px;font-weight:300;color:var(--text-primary);margin-top:2px}.band-display.svelte-vjv5wg.svelte-vjv5wg{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:8px;background:#4fc3f70d;border-radius:6px}.band-item.svelte-vjv5wg.svelte-vjv5wg{display:flex;justify-content:space-between;align-items:center}.band-label.svelte-vjv5wg.svelte-vjv5wg{font-size:11px;color:var(--text-muted)}.band-value.svelte-vjv5wg.svelte-vjv5wg{font-size:14px;font-weight:600;color:var(--accent-cyan)}.fan-control.svelte-vjv5wg.svelte-vjv5wg{display:flex;flex-direction:column;gap:6px}.control-label.svelte-vjv5wg.svelte-vjv5wg{font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}select.svelte-vjv5wg.svelte-vjv5wg{background:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;padding:8px;font-size:12px;cursor:pointer;outline:none;transition:all .2s}select.svelte-vjv5wg.svelte-vjv5wg:hover{border-color:var(--accent-cyan)}select.svelte-vjv5wg.svelte-vjv5wg:focus{border-color:var(--accent-cyan);box-shadow:0 0 0 2px #4fc3f733}.card.svelte-ulw0vm.svelte-ulw0vm{background:linear-gradient(135deg,#1a2332,#0f1923);border:1px solid #2d3748;border-radius:8px;padding:0;overflow:hidden;box-shadow:0 4px 6px #0000004d}.card-header.svelte-ulw0vm.svelte-ulw0vm{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-ulw0vm.svelte-ulw0vm{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.header-right.svelte-ulw0vm.svelte-ulw0vm{display:flex;align-items:center;gap:8px}.tuning-badge.svelte-ulw0vm.svelte-ulw0vm{padding:4px 12px;border-radius:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;background:#4caf5033;color:#4caf50}.tuning-badge.tuning.svelte-ulw0vm.svelte-ulw0vm{background:#ff980033;color:#ff9800;animation:svelte-ulw0vm-pulse 1s infinite}@keyframes svelte-ulw0vm-pulse{0%,to{opacity:1}50%{opacity:.7}}.status-dot.svelte-ulw0vm.svelte-ulw0vm{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-ulw0vm.svelte-ulw0vm{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-ulw0vm.svelte-ulw0vm{padding:16px;display:flex;flex-direction:column;gap:10px}.power-display.svelte-ulw0vm.svelte-ulw0vm{display:flex;flex-direction:column;gap:8px}.power-main.svelte-ulw0vm.svelte-ulw0vm{text-align:center}.power-value.svelte-ulw0vm.svelte-ulw0vm{font-size:48px;font-weight:200;color:var(--accent-cyan);line-height:1;text-shadow:0 0 20px rgba(79,195,247,.5)}.power-value.svelte-ulw0vm .unit.svelte-ulw0vm{font-size:20px;color:var(--text-secondary);margin-left:4px}.power-label.svelte-ulw0vm.svelte-ulw0vm{font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:1px;margin-top:4px}.power-bar.svelte-ulw0vm.svelte-ulw0vm{position:relative;height:8px;background:var(--bg-tertiary);border-radius:4px;overflow:hidden}.power-bar-fill.svelte-ulw0vm.svelte-ulw0vm{position:relative;height:100%;background:linear-gradient(90deg,#4caf50,#ffc107,#ff9800,#f44336);border-radius:4px;transition:width .3s ease}.power-bar-glow.svelte-ulw0vm.svelte-ulw0vm{position:absolute;top:0;right:0;width:20px;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.5));animation:svelte-ulw0vm-shimmer 2s infinite}@keyframes svelte-ulw0vm-shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.power-scale.svelte-ulw0vm.svelte-ulw0vm{display:flex;justify-content:space-between;font-size:9px;color:var(--text-muted);margin-top:4px}.swr-container.svelte-ulw0vm.svelte-ulw0vm{display:flex;align-items:center;gap:10px}.swr-circle.svelte-ulw0vm.svelte-ulw0vm{width:80px;height:80px;border-radius:50%;background:radial-gradient(circle,rgba(79,195,247,.1),transparent);border:3px solid var(--swr-color);display:flex;flex-direction:column;align-items:center;justify-content:center;box-shadow:0 0 20px var(--swr-color)}.swr-value.svelte-ulw0vm.svelte-ulw0vm{font-size:20px;font-weight:300;color:var(--swr-color)}.swr-label.svelte-ulw0vm.svelte-ulw0vm{font-size:10px;color:var(--text-muted);text-transform:uppercase}.swr-status.svelte-ulw0vm.svelte-ulw0vm{flex:1}.status-text.svelte-ulw0vm.svelte-ulw0vm{font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-text.good.svelte-ulw0vm.svelte-ulw0vm{color:#4caf50}.status-text.ok.svelte-ulw0vm.svelte-ulw0vm{color:#ffc107}.status-text.warning.svelte-ulw0vm.svelte-ulw0vm{color:#ff9800}.status-text.danger.svelte-ulw0vm.svelte-ulw0vm{color:#f44336}.capacitors.svelte-ulw0vm.svelte-ulw0vm{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;padding:16px;background:#4fc3f70d;border-radius:6px;border:1px solid rgba(79,195,247,.2)}.cap-item.svelte-ulw0vm.svelte-ulw0vm{display:flex;flex-direction:column;align-items:center;gap:4px}.cap-value.svelte-ulw0vm.svelte-ulw0vm{font-size:20px;font-weight:300;color:var(--accent-cyan);text-shadow:0 0 15px rgba(79,195,247,.5)}.cap-label.svelte-ulw0vm.svelte-ulw0vm{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:1px}.freq-display.svelte-ulw0vm.svelte-ulw0vm{display:grid;grid-template-columns:1fr 1fr;gap:12px}.freq-item.svelte-ulw0vm.svelte-ulw0vm{padding:10px;background:var(--bg-tertiary);border-radius:6px;display:flex;flex-direction:column;gap:4px}.freq-label.svelte-ulw0vm.svelte-ulw0vm{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.freq-value.svelte-ulw0vm.svelte-ulw0vm{font-size:16px;font-weight:300;color:var(--text-primary)}.freq-unit.svelte-ulw0vm.svelte-ulw0vm{font-size:11px;color:var(--text-secondary);margin-left:2px}.controls.svelte-ulw0vm.svelte-ulw0vm{display:grid;grid-template-columns:1fr 1fr;gap:8px}.control-btn.svelte-ulw0vm.svelte-ulw0vm{padding:12px;border-radius:6px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border:1px solid var(--border-color);background:var(--bg-tertiary);color:var(--text-primary);transition:all .2s}.control-btn.svelte-ulw0vm.svelte-ulw0vm:hover{border-color:var(--accent-cyan);transform:translateY(-1px)}.control-btn.active.svelte-ulw0vm.svelte-ulw0vm{background:var(--accent-cyan);border-color:var(--accent-cyan);color:#000;box-shadow:0 0 15px #4fc3f780}.tune-btn.svelte-ulw0vm.svelte-ulw0vm{width:100%;padding:14px;border-radius:6px;font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border:none;background:linear-gradient(135deg,#f44336,#d32f2f);color:#fff;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:8px;box-shadow:0 4px 12px #f4433666}.tune-btn.svelte-ulw0vm.svelte-ulw0vm:hover{transform:translateY(-2px);box-shadow:0 6px 16px #f4433680}.tune-btn.svelte-ulw0vm.svelte-ulw0vm:active{transform:translateY(0)}.tune-icon.svelte-ulw0vm.svelte-ulw0vm{font-size:16px}.card.svelte-1r71rs8{background:linear-gradient(135deg,#1a2332,#0f1923);border:1px solid #2d3748;border-radius:8px;padding:0;overflow:hidden;box-shadow:0 4px 6px #0000004d}.card-header.svelte-1r71rs8{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-1r71rs8{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.status-dot.svelte-1r71rs8{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-1r71rs8{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-1r71rs8{padding:16px;display:flex;flex-direction:column;gap:12px}.sources.svelte-1r71rs8{display:grid;grid-template-columns:1fr 1fr;gap:8px}.source-item.svelte-1r71rs8{padding:8px;background:var(--bg-tertiary);border-radius:4px;text-align:center}.source-label.svelte-1r71rs8{font-size:12px;font-weight:600;color:var(--text-primary);text-transform:uppercase}.bands.svelte-1r71rs8{display:grid;grid-template-columns:1fr 1fr;gap:8px}.band-item.svelte-1r71rs8{padding:10px;background:#4fc3f71a;border:1px solid rgba(79,195,247,.3);border-radius:4px;text-align:center}.band-value.svelte-1r71rs8{font-size:16px;font-weight:600;color:var(--accent-cyan)}.antennas.svelte-1r71rs8{display:flex;flex-direction:column;gap:8px}.antenna-card.svelte-1r71rs8{display:flex;justify-content:space-between;align-items:center;padding:12px;background:var(--bg-tertiary);border:2px solid var(--border-color);border-radius:6px;transition:all .3s}.antenna-card.tx.svelte-1r71rs8{background:#f4433633;border-color:#f44336;box-shadow:0 0 20px #f4433666}.antenna-card.active-a.svelte-1r71rs8{background:#4caf5033;border-color:#4caf50;box-shadow:0 0 20px #4caf504d}.antenna-card.active-b.svelte-1r71rs8{background:#2196f333;border-color:#2196f3;box-shadow:0 0 20px #2196f34d}.antenna-name.svelte-1r71rs8{font-size:14px;font-weight:500;color:var(--text-primary)}.antenna-ports.svelte-1r71rs8{display:flex;gap:6px}.port-btn.svelte-1r71rs8{width:36px;height:36px;border-radius:4px;font-size:14px;font-weight:600;cursor:pointer;border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-secondary);transition:all .2s}.port-btn.svelte-1r71rs8:hover{border-color:var(--accent-cyan);transform:scale(1.05)}.port-btn.active.svelte-1r71rs8{background:var(--accent-cyan);border-color:var(--accent-cyan);color:#000;box-shadow:0 0 12px #4fc3f780}.reboot-btn.svelte-1r71rs8{width:100%;padding:12px;border-radius:6px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;cursor:pointer;border:none;background:linear-gradient(135deg,#ff9800,#f57c00);color:#fff;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:6px;box-shadow:0 4px 12px #ff980066;margin-top:8px}.reboot-btn.svelte-1r71rs8:hover{transform:translateY(-2px);box-shadow:0 6px 16px #ff980080}.reboot-btn.svelte-1r71rs8:active{transform:translateY(0)}.reboot-icon.svelte-1r71rs8{font-size:16px}.card.svelte-cd0075{background:linear-gradient(135deg,#1a2332,#0f1923);border:1px solid #2d3748;border-radius:8px;padding:0;overflow:hidden;box-shadow:0 4px 6px #0000004d}.card-header.svelte-cd0075{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-cd0075{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.status-dot.svelte-cd0075{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-cd0075{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-cd0075{padding:16px;display:flex;flex-direction:column;gap:10px}.heading-controls-row.svelte-cd0075{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:8px;background:#4fc3f71a;border-radius:6px;border:1px solid rgba(79,195,247,.3)}.heading-display-compact.svelte-cd0075{flex:1;text-align:center}.controls-compact.svelte-cd0075{display:flex;gap:6px}.btn-mini.svelte-cd0075{width:36px;height:36px;border:none;border-radius:6px;font-size:20px;font-weight:700;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center}.btn-mini.ccw.svelte-cd0075{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.btn-mini.ccw.svelte-cd0075:hover{transform:rotate(-15deg) scale(1.05);box-shadow:0 4px 12px #667eea66}.btn-mini.stop.svelte-cd0075{background:linear-gradient(135deg,#f093fb,#f5576c);color:#fff}.btn-mini.stop.svelte-cd0075:hover{transform:scale(1.05);box-shadow:0 4px 12px #f5576c66}.btn-mini.cw.svelte-cd0075{background:linear-gradient(135deg,#4facfe,#00f2fe);color:#fff}.btn-mini.cw.svelte-cd0075:hover{transform:rotate(15deg) scale(1.05);box-shadow:0 4px 12px #4facfe66}.heading-label.svelte-cd0075{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:4px}.heading-value.svelte-cd0075{font-size:42px;font-weight:200;color:var(--accent-cyan);text-shadow:0 0 20px rgba(79,195,247,.5)}.map-container.svelte-cd0075{display:flex;justify-content:center;padding:10px;background:#0a162899;border-radius:8px}.map-svg.svelte-cd0075{width:100%;max-width:300px;height:auto}.clickable-compass.svelte-cd0075{cursor:crosshair;-webkit-user-select:none;user-select:none}.clickable-compass.svelte-cd0075:hover{filter:brightness(1.1)}.cardinal.svelte-cd0075{fill:var(--accent-cyan);font-size:16px;font-weight:700;text-shadow:0 0 10px rgba(79,195,247,.8)}.degree-label.svelte-cd0075{fill:#4fc3f7b3;font-size:12px;font-weight:600}.card.svelte-1bbmoku.svelte-1bbmoku{background:linear-gradient(135deg,#1e293bf2,#0f172afa);border-radius:16px;padding:16px;box-shadow:0 8px 32px #0006;border:1px solid rgba(79,195,247,.2)}.card-header.svelte-1bbmoku.svelte-1bbmoku{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;padding-bottom:16px;border-bottom:2px solid rgba(79,195,247,.3)}h2.svelte-1bbmoku.svelte-1bbmoku{margin:0;font-size:20px;font-weight:600;background:linear-gradient(135deg,#4fc3f7,#03a9f4);-webkit-background-clip:text;-webkit-text-fill-color:transparent;text-shadow:0 0 20px rgba(79,195,247,.5)}h3.svelte-1bbmoku.svelte-1bbmoku{margin:0 0 12px;font-size:16px;font-weight:500;color:#4fc3f7}.status-dot.svelte-1bbmoku.svelte-1bbmoku{width:12px;height:12px;border-radius:50%;background:#4caf50;box-shadow:0 0 12px #4caf50cc;animation:svelte-1bbmoku-pulse 2s ease-in-out infinite}.status-dot.disconnected.svelte-1bbmoku.svelte-1bbmoku{background:#666;box-shadow:none;animation:none}@keyframes svelte-1bbmoku-pulse{0%,to{opacity:1}50%{opacity:.6}}.metrics.svelte-1bbmoku.svelte-1bbmoku{display:flex;flex-direction:column;gap:12px}.status-grid.svelte-1bbmoku.svelte-1bbmoku{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}.status-item.svelte-1bbmoku.svelte-1bbmoku{background:#0f172a99;padding:16px;border-radius:12px;border:1px solid rgba(79,195,247,.2)}.status-label.svelte-1bbmoku.svelte-1bbmoku{font-size:12px;color:#fff9;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}.status-value.svelte-1bbmoku.svelte-1bbmoku{font-size:20px;font-weight:600;color:#4fc3f7}.status-value.freq.svelte-1bbmoku.svelte-1bbmoku{color:#66bb6a;font-size:20px}.status-value.band.svelte-1bbmoku.svelte-1bbmoku{color:#ffa726}.status-value.direction.svelte-1bbmoku.svelte-1bbmoku{color:#ab47bc}.control-section.svelte-1bbmoku.svelte-1bbmoku{background:#0f172a66;padding:12px;border-radius:12px;border:1px solid rgba(79,195,247,.2)}.control-section.compact.svelte-1bbmoku.svelte-1bbmoku{padding:16px}.auto-track-controls.svelte-1bbmoku.svelte-1bbmoku{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.toggle-label.svelte-1bbmoku.svelte-1bbmoku{display:flex;align-items:center;gap:8px;cursor:pointer;color:#fff;font-size:14px}.toggle-label.svelte-1bbmoku input[type=checkbox].svelte-1bbmoku{width:20px;height:20px;cursor:pointer}.threshold-group.svelte-1bbmoku.svelte-1bbmoku,.direction-group.svelte-1bbmoku.svelte-1bbmoku{display:flex;align-items:center;gap:8px}.threshold-group.svelte-1bbmoku label.svelte-1bbmoku,.direction-group.svelte-1bbmoku label.svelte-1bbmoku{font-size:14px;color:#fffc;white-space:nowrap}select.svelte-1bbmoku.svelte-1bbmoku{background:#0f172acc;border:1px solid rgba(79,195,247,.3);border-radius:8px;padding:10px 12px;color:#fff;font-size:16px;transition:all .2s}select.svelte-1bbmoku.svelte-1bbmoku:focus{outline:none;border-color:#4fc3f7;box-shadow:0 0 12px #4fc3f74d}button.svelte-1bbmoku.svelte-1bbmoku{padding:12px 20px;border-radius:8px;border:none;font-weight:600;font-size:14px;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:8px;justify-content:center}.btn-danger.svelte-1bbmoku.svelte-1bbmoku{background:linear-gradient(135deg,#f44336,#d32f2f);color:#fff;box-shadow:0 4px 16px #f4433666;width:100%}.btn-danger.svelte-1bbmoku.svelte-1bbmoku:hover{transform:translateY(-2px);box-shadow:0 6px 20px #f4433699}.icon.svelte-1bbmoku.svelte-1bbmoku{font-size:16px}.progress-section.svelte-1bbmoku.svelte-1bbmoku{background:#0f172a66;padding:12px;border-radius:12px;border:1px solid rgba(255,193,7,.3)}.progress-bar.svelte-1bbmoku.svelte-1bbmoku{width:100%;height:24px;background:#0f172acc;border-radius:12px;overflow:hidden;margin:12px 0;border:1px solid rgba(79,195,247,.3)}.progress-fill.svelte-1bbmoku.svelte-1bbmoku{height:100%;background:linear-gradient(90deg,#4fc3f7,#66bb6a);transition:width .3s ease;box-shadow:0 0 12px #4fc3f799}.progress-text.svelte-1bbmoku.svelte-1bbmoku{text-align:center;color:#4fc3f7;font-weight:600}.actions.svelte-1bbmoku.svelte-1bbmoku{display:flex;gap:12px}.app.svelte-1hrhory.svelte-1hrhory{min-height:100vh;display:flex;flex-direction:column}header.svelte-1hrhory.svelte-1hrhory{background:linear-gradient(135deg,#1e3c72,#2a5298);padding:16px 24px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 2px 8px #0000004d;flex-wrap:wrap;gap:16px}.header-left.svelte-1hrhory.svelte-1hrhory{display:flex;align-items:center;gap:16px}h1.svelte-1hrhory.svelte-1hrhory{font-size:24px;font-weight:500;margin:0;color:#fff}.connection-status.svelte-1hrhory.svelte-1hrhory{display:flex;align-items:center;gap:8px;font-size:14px;padding:6px 12px;background:#0000004d;border-radius:16px}.header-center.svelte-1hrhory.svelte-1hrhory{flex:1;display:flex;justify-content:center}.solar-info.svelte-1hrhory.svelte-1hrhory{display:flex;gap:20px;font-size:14px}.solar-item.svelte-1hrhory.svelte-1hrhory{color:#fffc}.solar-item.svelte-1hrhory .value.svelte-1hrhory{color:var(--accent-teal);font-weight:500;margin-left:4px}.header-right.svelte-1hrhory.svelte-1hrhory{display:flex;gap:20px;align-items:center}.weather-info.svelte-1hrhory.svelte-1hrhory{display:flex;gap:12px;font-size:14px;color:#ffffffe6}.clock.svelte-1hrhory.svelte-1hrhory{display:flex;flex-direction:column;align-items:flex-end}.time.svelte-1hrhory.svelte-1hrhory{font-size:18px;font-weight:500;color:#fff}.date.svelte-1hrhory.svelte-1hrhory{font-size:12px;color:#ffffffb3}main.svelte-1hrhory.svelte-1hrhory{flex:1;padding:24px;overflow-y:auto}.dashboard-grid.svelte-1hrhory.svelte-1hrhory{display:flex;flex-direction:column;gap:24px;max-width:1800px;margin:0 auto}.row.svelte-1hrhory.svelte-1hrhory{display:flex;gap:24px;flex-wrap:wrap}.row.svelte-1hrhory>*{flex:1;min-width:300px}@media (max-width: 1200px){.row.svelte-1hrhory.svelte-1hrhory{flex-direction:column}}@media (max-width: 768px){header.svelte-1hrhory.svelte-1hrhory{flex-direction:column;align-items:flex-start}.header-center.svelte-1hrhory.svelte-1hrhory,.header-right.svelte-1hrhory.svelte-1hrhory{width:100%;justify-content:flex-start}.solar-info.svelte-1hrhory.svelte-1hrhory{flex-wrap:wrap}}:root{--bg-primary: #0a1628;--bg-secondary: #1a2332;--bg-tertiary: #243447;--bg-hover: #2a3f5f;--text-primary: #e0e6ed;--text-secondary: #a0aec0;--text-muted: #718096;--accent-cyan: #4fc3f7;--accent-blue: #2196f3;--accent-green: #4caf50;--accent-orange: #ff9800;--accent-red: #f44336;--accent-purple: #9c27b0;--accent-yellow: #ffc107;--border-color: #2d3748;--border-light: #374151;--card-shadow: 0 1px 3px rgba(0, 0, 0, .3);--card-radius: 6px;--header-height: 56px;--spacing-xs: 4px;--spacing-sm: 8px;--spacing-md: 12px;--spacing-lg: 16px;--spacing-xl: 20px}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:var(--bg-primary);color:var(--text-primary);font-size:13px;line-height:1.4;overflow-x:hidden}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}header{height:var(--header-height);background:var(--bg-secondary);border-bottom:1px solid var(--border-color);display:flex;align-items:center;justify-content:space-between;padding:0 var(--spacing-lg);flex-shrink:0}.header-left{display:flex;align-items:center;gap:var(--spacing-lg)}.header-left h1{font-size:16px;font-weight:600;color:var(--accent-cyan);letter-spacing:.5px}.connection-status{display:flex;align-items:center;gap:var(--spacing-sm);font-size:12px;color:var(--text-secondary)}.status-indicator{width:8px;height:8px;border-radius:50%;background:var(--accent-red);transition:background .3s}.status-indicator.status-online{background:var(--accent-green);box-shadow:0 0 8px var(--accent-green)}.header-center{display:flex;gap:var(--spacing-xl)}.solar-info{display:flex;gap:var(--spacing-md);font-size:12px}.solar-item{color:var(--text-secondary)}.solar-item .value{color:var(--accent-cyan);font-weight:600;margin-left:var(--spacing-xs)}.header-right{display:flex;align-items:center;gap:var(--spacing-lg)}.weather-info{display:flex;gap:var(--spacing-md);font-size:12px;color:var(--text-secondary)}.clock{display:flex;flex-direction:column;align-items:flex-end;font-size:11px}.clock .time{font-size:14px;font-weight:600;color:var(--text-primary)}.clock .date{color:var(--text-secondary)}main{flex:1;overflow-y:auto;padding:var(--spacing-md)}.dashboard-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--spacing-md);max-width:1800px;margin:0 auto}.card{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--card-radius);padding:var(--spacing-md);box-shadow:var(--card-shadow);transition:border-color .2s}.card:hover{border-color:var(--border-light)}.card h2{font-size:14px;font-weight:600;color:var(--accent-cyan);margin-bottom:var(--spacing-md);display:flex;align-items:center;gap:var(--spacing-sm);letter-spacing:.5px}.card h2:before{content:"";width:3px;height:14px;background:var(--accent-cyan);border-radius:2px}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--accent-green)}.status-dot.disconnected{background:var(--accent-red)}.label{font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:var(--spacing-xs)}.value{font-size:18px;font-weight:300;color:var(--text-primary)}button,.button{background:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;padding:var(--spacing-sm) var(--spacing-md);font-size:12px;font-weight:500;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:.5px}button:hover,.button:hover{background:var(--bg-hover);border-color:var(--border-light)}button:active,.button:active{transform:scale(.98)}button.primary{background:var(--accent-cyan);border-color:var(--accent-cyan);color:#000}button.primary:hover{background:#29b6f6;border-color:#29b6f6}button.success{background:var(--accent-green);border-color:var(--accent-green);color:#fff}button.danger{background:var(--accent-red);border-color:var(--accent-red);color:#fff}button:disabled{opacity:.5;cursor:not-allowed}select{background:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;padding:var(--spacing-sm);font-size:12px;cursor:pointer;outline:none;transition:all .2s}select:hover{border-color:var(--border-light)}select:focus{border-color:var(--accent-cyan)}.badge{display:inline-block;padding:4px 10px;border-radius:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.badge.green{background:#4caf5033;color:var(--accent-green)}.badge.red{background:#f4433633;color:var(--accent-red)}.badge.orange{background:#ff980033;color:var(--accent-orange)}.badge.cyan{background:#4fc3f733;color:var(--accent-cyan)}.badge.purple{background:#9c27b033;color:var(--accent-purple)}.bar{width:100%;height:6px;background:var(--bg-tertiary);border-radius:3px;overflow:hidden;margin:var(--spacing-xs) 0}.bar-fill{height:100%;background:linear-gradient(90deg,var(--accent-green),var(--accent-orange),var(--accent-red));transition:width .3s ease;border-radius:3px}.scale{display:flex;justify-content:space-between;font-size:10px;color:var(--text-muted);margin-top:var(--spacing-xs)}.metrics{display:flex;flex-direction:column;gap:var(--spacing-md)}.metric{display:flex;flex-direction:column;gap:var(--spacing-xs)}.metric-row{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--spacing-md)}.metric.small{min-width:0}.metric-value{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-primary)}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--bg-hover)}@media (max-width: 1400px){.dashboard-grid{grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}}@media (max-width: 768px){header{flex-direction:column;height:auto;padding:var(--spacing-sm);gap:var(--spacing-sm)}.dashboard-grid{grid-template-columns:1fr}.header-center{order:3;width:100%}} diff --git a/cmd/server/web/dist/index.html b/cmd/server/web/dist/index.html index b28a366..7756170 100644 --- a/cmd/server/web/dist/index.html +++ b/cmd/server/web/dist/index.html @@ -7,8 +7,8 @@ - - + +
diff --git a/internal/api/device_manager.go b/internal/api/device_manager.go index bb518e4..714d918 100644 --- a/internal/api/device_manager.go +++ b/internal/api/device_manager.go @@ -34,6 +34,13 @@ type DeviceManager struct { updateInterval time.Duration stopChan chan struct{} + + // Auto frequency tracking + freqThreshold int // Threshold for triggering update (Hz) + autoTrackEnabled bool + ultrabeamDirection int // User-selected direction (0=normal, 1=180, 2=bi-dir) + lastFreqUpdateTime time.Time // Last time we sent frequency update + freqUpdateCooldown time.Duration // Minimum time between updates } type SystemStatus struct { @@ -50,10 +57,14 @@ type SystemStatus struct { func NewDeviceManager(cfg *config.Config, hub *Hub) *DeviceManager { return &DeviceManager{ - config: cfg, - hub: hub, - updateInterval: 1 * time.Second, // Update status every second - stopChan: make(chan struct{}), + config: cfg, + hub: hub, + updateInterval: 1 * time.Second, // Update status every second + stopChan: make(chan struct{}), + freqThreshold: 25000, // 25 kHz default + autoTrackEnabled: true, // Enabled by default + ultrabeamDirection: 0, // Normal direction by default + freqUpdateCooldown: 2 * time.Second, // Wait 2 seconds between updates } } @@ -232,10 +243,68 @@ func (dm *DeviceManager) updateStatus() { // Ultrabeam if ubStatus, err := dm.ultrabeam.GetStatus(); err == nil { status.Ultrabeam = ubStatus + + // Sync direction with Ultrabeam if not yet set (first time or after restart) + // This prevents auto-track from using wrong direction before user changes it + if dm.ultrabeamDirection == 0 && ubStatus.Direction != 0 { + dm.ultrabeamDirection = ubStatus.Direction + log.Printf("Auto-track: Initialized direction from Ultrabeam: %d", dm.ultrabeamDirection) + } } else { log.Printf("Ultrabeam error: %v", err) } + // Auto frequency tracking: Update Ultrabeam when TunerGenius frequency differs from Ultrabeam + if dm.autoTrackEnabled && status.TunerGenius != nil && status.TunerGenius.Connected && status.Ultrabeam != nil && status.Ultrabeam.Connected { + tunerFreqKhz := int(status.TunerGenius.FreqA) // TunerGenius frequency is already in kHz + ultrabeamFreqKhz := status.Ultrabeam.Frequency // Ultrabeam frequency in kHz + + // Ignore invalid frequencies or out of Ultrabeam range (40M-6M) + // This prevents retraction when slice is closed (FreqA becomes 0) + // Ultrabeam VL2.3 only covers 7000-54000 kHz (40M to 6M) + if tunerFreqKhz < 7000 || tunerFreqKhz > 54000 { + return // Out of range, skip auto-track + } + + freqDiff := tunerFreqKhz - ultrabeamFreqKhz + if freqDiff < 0 { + freqDiff = -freqDiff + } + + // Convert diff to Hz for comparison with threshold (which is in Hz) + freqDiffHz := freqDiff * 1000 + + // Don't send command if motors are already moving + if status.Ultrabeam.MotorsMoving != 0 { + // Motors moving - wait for them to finish + return + } + + if freqDiffHz >= dm.freqThreshold { + // Use current Ultrabeam direction if user hasn't explicitly set one + directionToUse := dm.ultrabeamDirection + if directionToUse == 0 && status.Ultrabeam.Direction != 0 { + directionToUse = status.Ultrabeam.Direction + } + + // Check cooldown to prevent rapid fire commands + timeSinceLastUpdate := time.Since(dm.lastFreqUpdateTime) + if timeSinceLastUpdate < dm.freqUpdateCooldown { + log.Printf("Auto-track: Cooldown active (%v remaining), skipping update", dm.freqUpdateCooldown-timeSinceLastUpdate) + } else { + log.Printf("Auto-track: Frequency differs by %d kHz, updating Ultrabeam to %d kHz (direction=%d)", freqDiff, tunerFreqKhz, directionToUse) + + // Send to Ultrabeam with saved or current direction + if err := dm.ultrabeam.SetFrequency(tunerFreqKhz, directionToUse); err != nil { + log.Printf("Auto-track: Failed to update Ultrabeam: %v (will retry)", err) + } else { + log.Printf("Auto-track: Successfully sent frequency to Ultrabeam") + dm.lastFreqUpdateTime = time.Now() // Update cooldown timer + } + } + } + } + // Solar Data (fetched every 15 minutes, cached) if solarData, err := dm.solarClient.GetSolarData(); err == nil { status.Solar = solarData @@ -298,3 +367,13 @@ func (dm *DeviceManager) RotatorGenius() *rotatorgenius.Client { func (dm *DeviceManager) Ultrabeam() *ultrabeam.Client { return dm.ultrabeam } + +func (dm *DeviceManager) SetAutoTrack(enabled bool, thresholdHz int) { + dm.autoTrackEnabled = enabled + dm.freqThreshold = thresholdHz +} + +func (dm *DeviceManager) SetUltrabeamDirection(direction int) { + dm.ultrabeamDirection = direction + log.Printf("Ultrabeam direction set to: %d", direction) +} diff --git a/internal/api/handlers.go b/internal/api/handlers.go index d0bf63a..4ef1134 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -57,6 +57,7 @@ func (s *Server) SetupRoutes() *http.ServeMux { // Ultrabeam endpoints mux.HandleFunc("/api/ultrabeam/frequency", s.handleUltrabeamFrequency) mux.HandleFunc("/api/ultrabeam/retract", s.handleUltrabeamRetract) + mux.HandleFunc("/api/ultrabeam/autotrack", s.handleUltrabeamAutoTrack) // Tuner endpoints mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate) @@ -435,6 +436,27 @@ func (s *Server) handleUltrabeamRetract(w http.ResponseWriter, r *http.Request) s.sendJSON(w, map[string]string{"status": "ok"}) } +func (s *Server) handleUltrabeamAutoTrack(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + Enabled bool `json:"enabled"` + Threshold int `json:"threshold"` // kHz + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + s.deviceManager.SetAutoTrack(req.Enabled, req.Threshold*1000) // Convert kHz to Hz + + s.sendJSON(w, map[string]string{"status": "ok"}) +} + func (s *Server) sendJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) diff --git a/internal/devices/powergenius/powergenius.go b/internal/devices/powergenius/powergenius.go index a9c9048..ecb5239 100644 --- a/internal/devices/powergenius/powergenius.go +++ b/internal/devices/powergenius/powergenius.go @@ -58,8 +58,8 @@ func New(host string, port int) *Client { host: host, port: port, stopChan: make(chan struct{}), - autoFanEnabled: true, // Auto fan management enabled by default - lastFanMode: "Contest", // Default to Contest mode + autoFanEnabled: false, // Auto fan DISABLED - manual control only + lastFanMode: "Contest", } } diff --git a/internal/devices/ultrabeam/ultrabeam.go b/internal/devices/ultrabeam/ultrabeam.go index d7f0895..b4a95f1 100644 --- a/internal/devices/ultrabeam/ultrabeam.go +++ b/internal/devices/ultrabeam/ultrabeam.go @@ -103,17 +103,20 @@ func (c *Client) Stop() { } func (c *Client) pollLoop() { - ticker := time.NewTicker(500 * time.Millisecond) + ticker := time.NewTicker(2 * time.Second) // Increased from 500ms to 2s defer ticker.Stop() for { select { case <-ticker.C: + // Try to connect if not connected c.connMu.Lock() if c.conn == nil { + log.Printf("Ultrabeam: Not connected, attempting connection...") conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), 5*time.Second) if err != nil { + log.Printf("Ultrabeam: Connection failed: %v", err) c.connMu.Unlock() // Mark as disconnected @@ -151,12 +154,6 @@ func (c *Client) pollLoop() { // Mark as connected status.Connected = true - // Query element lengths - lengths, err := c.queryElementLengths() - if err == nil { - status.ElementLengths = lengths - } - // Query progress if motors moving if status.MotorsMoving != 0 { progress, err := c.queryProgress() @@ -312,7 +309,7 @@ func (c *Client) sendCommand(cmd byte, data []byte) ([]byte, error) { } // Read reply with timeout - c.conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + c.conn.SetReadDeadline(time.Now().Add(1 * time.Second)) // Reduced from 2s to 1s // Read until we get a complete packet var buffer []byte @@ -341,6 +338,11 @@ func (c *Client) sendCommand(cmd byte, data []byte) ([]byte, error) { return nil, fmt.Errorf("failed to parse reply: %w", err) } + // Log for debugging unknown codes + if replyCmd != UB_OK && replyCmd != UB_BAD && replyCmd != UB_PAR && replyCmd != UB_ERR { + log.Printf("Ultrabeam: Unknown reply code %d (0x%02X), raw packet: %v", replyCmd, replyCmd, buffer) + } + // Check for errors switch replyCmd { case UB_BAD: @@ -352,7 +354,10 @@ func (c *Client) sendCommand(cmd byte, data []byte) ([]byte, error) { case UB_OK: return payload, nil default: - return nil, fmt.Errorf("unknown reply code: %d", replyCmd) + // Unknown codes might indicate "busy" or "in progress" + // Treat as non-fatal, return empty payload + log.Printf("Ultrabeam: Unusual reply code %d, treating as busy/in-progress", replyCmd) + return []byte{}, nil } } @@ -390,17 +395,59 @@ func (c *Client) queryElementLengths() ([]int, error) { return nil, err } + // Debug: log raw bytes + log.Printf("Ultrabeam element lengths raw reply (%d bytes): %v", len(reply), reply) + + // Try to extract 6 words - the protocol says 6 words (12 bytes) + // But we're receiving 14 bytes, so there might be padding + if len(reply) < 12 { - return nil, fmt.Errorf("element lengths reply too short") + return nil, fmt.Errorf("element lengths reply too short: %d bytes", len(reply)) } lengths := make([]int, 6) + + // Try different interpretations + log.Printf("=== Attempting different parsings ===") + + // Method 1: Standard little-endian from byte 0 + log.Printf("Method 1 (little-endian from 0):") + for i := 0; i < 6 && i*2+1 < len(reply); i++ { + lo := int(reply[i*2]) + hi := int(reply[i*2+1]) + val := lo | (hi << 8) + log.Printf(" Element %d: bytes[%d,%d] = [%d,%d] => %d mm", i, i*2, i*2+1, lo, hi, val) + } + + // Method 2: Big-endian from byte 0 + log.Printf("Method 2 (big-endian from 0):") + for i := 0; i < 6 && i*2+1 < len(reply); i++ { + hi := int(reply[i*2]) + lo := int(reply[i*2+1]) + val := lo | (hi << 8) + log.Printf(" Element %d: bytes[%d,%d] = [%d,%d] => %d mm", i, i*2, i*2+1, hi, lo, val) + } + + // Method 3: Skip first 2 bytes, then little-endian + log.Printf("Method 3 (skip 2 bytes, little-endian):") + for i := 0; i < 6 && i*2+3 < len(reply); i++ { + lo := int(reply[i*2+2]) + hi := int(reply[i*2+3]) + val := lo | (hi << 8) + log.Printf(" Element %d: bytes[%d,%d] = [%d,%d] => %d mm", i, i*2+2, i*2+3, lo, hi, val) + } + + // For now, use method 1 (original) for i := 0; i < 6; i++ { + if i*2+1 >= len(reply) { + break + } lo := int(reply[i*2]) hi := int(reply[i*2+1]) lengths[i] = lo | (hi << 8) } + log.Printf("Final lengths: %v", lengths) return lengths, nil } diff --git a/web/src/App.svelte b/web/src/App.svelte index d519b09..47f32c8 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -117,11 +117,8 @@
- -
- -
+
@@ -181,13 +178,41 @@ } .solar-item { - color: rgba(255, 255, 255, 0.8); + color: rgba(255, 255, 255, 0.9); + font-size: 13px; + font-weight: 600; + letter-spacing: 0.3px; } .solar-item .value { - color: var(--accent-teal); - font-weight: 500; + font-weight: 700; margin-left: 4px; + font-size: 14px; + } + + .solar-item:nth-child(1) .value { /* SFI */ + color: #ffa726; + text-shadow: 0 0 8px rgba(255, 167, 38, 0.5); + } + + .solar-item:nth-child(2) .value { /* Spots */ + color: #66bb6a; + text-shadow: 0 0 8px rgba(102, 187, 106, 0.5); + } + + .solar-item:nth-child(3) .value { /* A */ + color: #42a5f5; + text-shadow: 0 0 8px rgba(66, 165, 245, 0.5); + } + + .solar-item:nth-child(4) .value { /* K */ + color: #ef5350; + text-shadow: 0 0 8px rgba(239, 83, 80, 0.5); + } + + .solar-item:nth-child(5) .value { /* G */ + color: #ab47bc; + text-shadow: 0 0 8px rgba(171, 71, 188, 0.5); } .header-right { diff --git a/web/src/components/PowerGenius.svelte b/web/src/components/PowerGenius.svelte index 54cad13..a8d75fa 100644 --- a/web/src/components/PowerGenius.svelte +++ b/web/src/components/PowerGenius.svelte @@ -62,40 +62,26 @@
- -
-
-
{powerForward.toFixed(0)}W
-
Forward Power
-
-
-
-
+ +
+
+
+ Power + {powerForward.toFixed(0)} W
-
- 0 - 1000 - 2000 +
+
+
+
+
+
-
- -
-
-
{swr.toFixed(2)}
-
SWR
-
-
- {#if swr < 1.5} - Excellent - {:else if swr < 2.0} - Good - {:else if swr < 3.0} - Caution - {:else} - High! - {/if} + +
+
{swr.toFixed(2)}
+
SWR
@@ -147,8 +133,8 @@
- - setFanMode(e.target.value)}> @@ -235,10 +221,108 @@ padding: 16px; display: flex; flex-direction: column; - gap: 16px; + gap: 10px; } /* Power Display */ + /* Power + SWR Row */ + .power-swr-row { + display: flex; + gap: 16px; + align-items: center; + } + + .power-section { + flex: 1; + background: rgba(15, 23, 42, 0.6); + padding: 12px; + border-radius: 10px; + border: 1px solid rgba(79, 195, 247, 0.2); + } + + .power-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + } + + .power-label-inline { + font-size: 12px; + color: rgba(255, 255, 255, 0.7); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .power-value-inline { + font-size: 22px; + font-weight: 600; + color: #66bb6a; + } + + .power-bar-container { + position: relative; + } + + .power-bar-bg { + width: 100%; + height: 28px; + background: rgba(0, 0, 0, 0.3); + border-radius: 14px; + overflow: hidden; + position: relative; + } + + .power-bar-fill { + position: relative; + height: 100%; + background: linear-gradient(90deg, #4caf50, #ffc107, #ff9800, #f44336); + border-radius: 14px; + transition: width 0.3s ease; + } + + .power-bar-glow { + position: absolute; + top: 0; + right: 0; + width: 20px; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5)); + animation: shimmer 2s infinite; + } + + @keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } + } + + .swr-circle-compact { + width: 90px; + height: 90px; + border-radius: 50%; + background: radial-gradient(circle, rgba(79, 195, 247, 0.1), transparent); + border: 4px solid var(--swr-color); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-shadow: 0 0 25px var(--swr-color); + flex-shrink: 0; + } + + .swr-value-compact { + font-size: 28px; + font-weight: 700; + color: var(--swr-color); + } + + .swr-label-compact { + font-size: 11px; + color: rgba(255, 255, 255, 0.6); + text-transform: uppercase; + margin-top: 2px; + } + .power-display { display: flex; flex-direction: column; @@ -258,7 +342,7 @@ } .power-value .unit { - font-size: 24px; + font-size: 20px; color: var(--text-secondary); margin-left: 4px; } @@ -314,7 +398,7 @@ .swr-container { display: flex; align-items: center; - gap: 16px; + gap: 10px; } .swr-circle { @@ -331,7 +415,7 @@ } .swr-value { - font-size: 24px; + font-size: 20px; font-weight: 300; color: var(--swr-color); } @@ -422,7 +506,7 @@ } .param-value { - font-size: 18px; + font-size: 16px; font-weight: 300; color: var(--text-primary); margin-top: 2px; diff --git a/web/src/components/RotatorGenius.svelte b/web/src/components/RotatorGenius.svelte index 578c94e..bef2348 100644 --- a/web/src/components/RotatorGenius.svelte +++ b/web/src/components/RotatorGenius.svelte @@ -53,6 +53,29 @@ console.error('Failed to stop:', err); } } + + // Handle click on compass to set heading + function handleCompassClick(event) { + const svg = event.currentTarget; + const rect = svg.getBoundingClientRect(); + const centerX = rect.width / 2; + const centerY = rect.height / 2; + + // Get click position relative to center + const x = event.clientX - rect.left - centerX; + const y = event.clientY - rect.top - centerY; + + // Calculate angle (0° = North/top, clockwise) + let angle = Math.atan2(x, -y) * (180 / Math.PI); + if (angle < 0) angle += 360; + + // Round to nearest 5 degrees + const roundedHeading = Math.round(angle / 5) * 5; + + // Set target and go + targetHeading = roundedHeading; + goToHeading(); + }
@@ -62,15 +85,29 @@
- -
-
CURRENT HEADING
-
{heading}°
+ +
+
+
CURRENT HEADING
+
{heading}°
+
+ +
+ + + +
- + @@ -139,32 +176,6 @@
-
- - -
- - -
- - - -
@@ -212,7 +223,7 @@ padding: 16px; display: flex; flex-direction: column; - gap: 16px; + gap: 10px; } /* Heading Display */ @@ -223,6 +234,71 @@ border-radius: 6px; border: 1px solid rgba(79, 195, 247, 0.3); } + + .heading-controls-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 8px; + background: rgba(79, 195, 247, 0.1); + border-radius: 6px; + border: 1px solid rgba(79, 195, 247, 0.3); + } + + .heading-display-compact { + flex: 1; + text-align: center; + } + + .controls-compact { + display: flex; + gap: 6px; + } + + .btn-mini { + width: 36px; + height: 36px; + border: none; + border-radius: 6px; + font-size: 20px; + font-weight: bold; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + } + + .btn-mini.ccw { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + } + + .btn-mini.ccw:hover { + transform: rotate(-15deg) scale(1.05); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + } + + .btn-mini.stop { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + color: white; + } + + .btn-mini.stop:hover { + transform: scale(1.05); + box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4); + } + + .btn-mini.cw { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + color: white; + } + + .btn-mini.cw:hover { + transform: rotate(15deg) scale(1.05); + box-shadow: 0 4px 12px rgba(79, 172, 254, 0.4); + } .heading-label { font-size: 10px; @@ -253,10 +329,19 @@ max-width: 300px; height: auto; } + + .clickable-compass { + cursor: crosshair; + user-select: none; + } + + .clickable-compass:hover { + filter: brightness(1.1); + } .cardinal { fill: var(--accent-cyan); - font-size: 18px; + font-size: 16px; font-weight: 700; text-shadow: 0 0 10px rgba(79, 195, 247, 0.8); } @@ -353,4 +438,4 @@ font-size: 20px; line-height: 1; } - \ No newline at end of file + diff --git a/web/src/components/TunerGenius.svelte b/web/src/components/TunerGenius.svelte index 5dd3f1f..6c16322 100644 --- a/web/src/components/TunerGenius.svelte +++ b/web/src/components/TunerGenius.svelte @@ -57,40 +57,26 @@
- -
-
-
{powerForward.toFixed(0)}W
-
Forward Power
-
-
-
-
+ +
+
+
+ Power + {powerForward.toFixed(0)} W
-
- 0 - 1000 - 2000 +
+
+
+
+
+
-
- -
-
-
{swr.toFixed(2)}
-
SWR
-
-
- {#if swr < 1.5} - Excellent - {:else if swr < 2.0} - Good - {:else if swr < 3.0} - Caution - {:else} - High! - {/if} + +
+
{swr.toFixed(2)}
+
SWR
@@ -219,55 +205,63 @@ padding: 16px; display: flex; flex-direction: column; - gap: 16px; + gap: 10px; } /* Power Display */ - .power-display { + /* Power + SWR Row */ + .power-swr-row { display: flex; - flex-direction: column; - gap: 8px; + gap: 16px; + align-items: center; } - .power-main { - text-align: center; + .power-section { + flex: 1; + background: rgba(15, 23, 42, 0.6); + padding: 12px; + border-radius: 10px; + border: 1px solid rgba(79, 195, 247, 0.2); } - .power-value { - font-size: 48px; - font-weight: 200; - color: var(--accent-cyan); - line-height: 1; - text-shadow: 0 0 20px rgba(79, 195, 247, 0.5); + .power-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; } - .power-value .unit { - font-size: 24px; - color: var(--text-secondary); - margin-left: 4px; - } - - .power-label { - font-size: 11px; - color: var(--text-muted); + .power-label-inline { + font-size: 12px; + color: rgba(255, 255, 255, 0.7); text-transform: uppercase; - letter-spacing: 1px; - margin-top: 4px; + letter-spacing: 0.5px; } - .power-bar { + .power-value-inline { + font-size: 22px; + font-weight: 600; + color: #66bb6a; + } + + .power-bar-container { position: relative; - height: 8px; - background: var(--bg-tertiary); - border-radius: 4px; + } + + .power-bar-bg { + width: 100%; + height: 28px; + background: rgba(0, 0, 0, 0.3); + border-radius: 14px; overflow: hidden; + position: relative; } .power-bar-fill { position: relative; height: 100%; background: linear-gradient(90deg, #4caf50, #ffc107, #ff9800, #f44336); - border-radius: 4px; + border-radius: 14px; transition: width 0.3s ease; } @@ -286,19 +280,44 @@ 100% { transform: translateX(100%); } } - .power-scale { + .swr-circle-compact { + width: 90px; + height: 90px; + border-radius: 50%; + background: radial-gradient(circle, rgba(79, 195, 247, 0.1), transparent); + border: 4px solid var(--swr-color); display: flex; - justify-content: space-between; - font-size: 9px; - color: var(--text-muted); - margin-top: 4px; + flex-direction: column; + align-items: center; + justify-content: center; + box-shadow: 0 0 25px var(--swr-color); + flex-shrink: 0; + } + + .swr-value-compact { + font-size: 28px; + font-weight: 700; + color: var(--swr-color); + } + + .swr-label-compact { + font-size: 11px; + color: rgba(255, 255, 255, 0.6); + text-transform: uppercase; + margin-top: 2px; + } + + .power-display { + display: flex; + flex-direction: column; + gap: 8px; } /* SWR Circle */ .swr-container { display: flex; align-items: center; - gap: 16px; + gap: 10px; } .swr-circle { @@ -315,7 +334,7 @@ } .swr-value { - font-size: 24px; + font-size: 20px; font-weight: 300; color: var(--swr-color); } @@ -361,7 +380,7 @@ } .cap-value { - font-size: 32px; + font-size: 20px; font-weight: 300; color: var(--accent-cyan); text-shadow: 0 0 15px rgba(79, 195, 247, 0.5); @@ -472,6 +491,6 @@ } .tune-icon { - font-size: 18px; + font-size: 16px; } \ No newline at end of file diff --git a/web/src/components/Ultrabeam.svelte b/web/src/components/Ultrabeam.svelte index 75d4a93..044a8e4 100644 --- a/web/src/components/Ultrabeam.svelte +++ b/web/src/components/Ultrabeam.svelte @@ -13,34 +13,96 @@ $: elementLengths = status?.element_lengths || []; $: firmwareVersion = status ? `${status.firmware_major}.${status.firmware_minor}` : '0.0'; - // Band names mapping + // Band names mapping - VL2.3 covers 6M to 40M only + // Band 0=6M, 1=10M, 2=12M, 3=15M, 4=17M, 5=20M, 6=30M, 7=40M const bandNames = [ - '160M', '80M', '60M', '40M', '30M', '20M', - '17M', '15M', '12M', '10M', '6M' + '6M', '10M', '12M', '15M', '17M', '20M', '30M', '40M' ]; + // Detect band from frequency + $: detectedBand = detectBandFromFrequency(frequency, band); + + function detectBandFromFrequency(freq, bandIndex) { + // If band index is valid (0-7), use it directly + if (bandIndex >= 0 && bandIndex <= 7) { + return bandNames[bandIndex]; + } + + // Otherwise detect from frequency (in kHz) + if (freq >= 7000 && freq <= 7300) return '40M'; + if (freq >= 10100 && freq <= 10150) return '30M'; + if (freq >= 14000 && freq <= 14350) return '20M'; + if (freq >= 18068 && freq <= 18168) return '17M'; + if (freq >= 21000 && freq <= 21450) return '15M'; + if (freq >= 24890 && freq <= 24990) return '12M'; + if (freq >= 28000 && freq <= 29700) return '10M'; + if (freq >= 50000 && freq <= 54000) return '6M'; + + return 'Unknown'; + } + // Direction names const directionNames = ['Normal', '180°', 'Bi-Dir']; + // Auto-track threshold options + const thresholdOptions = [ + { value: 25, label: '25 kHz' }, + { value: 50, label: '50 kHz' }, + { value: 100, label: '100 kHz' } + ]; + + // Auto-track state + let autoTrackEnabled = true; // Default enabled + let autoTrackThreshold = 25; // Default 25 kHz + // Form state - let targetFreq = 0; let targetDirection = 0; + // Auto-update targetDirection when status changes + $: targetDirection = direction; + + // Element names based on band (corrected order: 0=6M ... 10=160M) + $: elementNames = getElementNames(band); + + function getElementNames(band) { + // 30M (band 6) and 40M (band 7): Reflector (inverted), Radiator (inverted) + if (band === 6 || band === 7) { + return ['Radiator (30/40M)', 'Reflector (30/40M)', null]; + } + // 6M to 20M (bands 0-5): Reflector, Radiator, Director 1 + if (band >= 0 && band <= 5) { + return ['Reflector', 'Radiator', 'Director 1']; + } + // Default + return ['Element 1', 'Element 2', 'Element 3']; + } + // Element calibration state let calibrationMode = false; let selectedElement = 0; let elementAdjustment = 0; - async function setFrequency() { - if (targetFreq < 1800 || targetFreq > 30000) { - alert('Frequency must be between 1.8 MHz and 30 MHz'); - return; + async function setDirection() { + if (frequency === 0) { + return; // Silently skip if no frequency } try { - await api.ultrabeam.setFrequency(targetFreq, targetDirection); + // Send command to antenna with current frequency and new direction + await api.ultrabeam.setFrequency(frequency, targetDirection); + // Also save direction preference for auto-track + await api.ultrabeam.setDirection(targetDirection); } catch (err) { - console.error('Failed to set frequency:', err); - alert('Failed to set frequency'); + // Log error but don't alert - code 30 (busy) is normal + console.log('Direction change sent (may show code 30 if busy):', err); + } + } + + async function updateAutoTrack() { + try { + await api.ultrabeam.setAutoTrack(autoTrackEnabled, autoTrackThreshold); + } catch (err) { + console.error('Failed to update auto-track:', err); + alert('Failed to update auto-track settings'); } } @@ -88,50 +150,56 @@
Band
-
{bandNames[band] || 'Unknown'}
+
{detectedBand}
Direction
{directionNames[direction]}
- -
-
Firmware
-
v{firmwareVersion}
-
- -
-

Frequency Control

-
-
- - -
+ +
+

Auto Tracking

+
+ -
- - + {#each thresholdOptions as option} + + {/each}
- +
+ + + +
@@ -146,22 +214,25 @@
{/if} - + + - + +
@@ -224,7 +296,7 @@ .card { background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%); border-radius: 16px; - padding: 24px; + padding: 16px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); border: 1px solid rgba(79, 195, 247, 0.2); } @@ -240,7 +312,7 @@ h2 { margin: 0; - font-size: 24px; + font-size: 20px; font-weight: 600; background: linear-gradient(135deg, #4fc3f7 0%, #03a9f4 100%); -webkit-background-clip: text; @@ -278,14 +350,14 @@ .metrics { display: flex; flex-direction: column; - gap: 24px; + gap: 12px; } /* Status Grid */ .status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 16px; + gap: 10px; } .status-item { @@ -304,31 +376,139 @@ } .status-value { - font-size: 20px; - font-weight: 600; + font-size: 22px; + font-weight: 700; color: #4fc3f7; + text-shadow: 0 0 10px rgba(79, 195, 247, 0.5); } .status-value.freq { color: #66bb6a; - font-size: 24px; + font-size: 22px; + text-shadow: 0 0 10px rgba(102, 187, 106, 0.5); } .status-value.band { color: #ffa726; + text-shadow: 0 0 10px rgba(255, 167, 38, 0.5); } .status-value.direction { color: #ab47bc; + text-shadow: 0 0 10px rgba(171, 71, 188, 0.5); } /* Control Section */ .control-section { background: rgba(15, 23, 42, 0.4); - padding: 20px; + padding: 12px; border-radius: 12px; border: 1px solid rgba(79, 195, 247, 0.2); } + + .control-section.compact { + padding: 16px; + } + + .auto-track-controls { + display: flex; + align-items: center; + gap: 20px; + flex-wrap: wrap; + } + + .toggle-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + color: #fff; + font-size: 14px; + } + + .toggle-label input[type="checkbox"] { + width: 20px; + height: 20px; + cursor: pointer; + } + + .threshold-group { + display: flex; + align-items: center; + gap: 8px; + } + + .threshold-group label { + font-size: 14px; + color: rgba(255, 255, 255, 0.8); + white-space: nowrap; + } + + .direction-buttons { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + margin-top: 12px; + } + + .dir-btn { + padding: 14px 20px; + border: none; + border-radius: 10px; + font-size: 14px; + font-weight: 700; + text-transform: uppercase; + cursor: pointer; + transition: all 0.3s; + color: white; + letter-spacing: 0.5px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + } + + .dir-btn.normal { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + } + + .dir-btn.normal:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5); + } + + .dir-btn.normal.active { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + box-shadow: 0 0 25px rgba(102, 126, 234, 0.8), 0 6px 20px rgba(102, 126, 234, 0.5); + transform: translateY(-2px); + } + + .dir-btn.rotate180 { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + } + + .dir-btn.rotate180:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(245, 87, 108, 0.5); + } + + .dir-btn.rotate180.active { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + box-shadow: 0 0 25px rgba(245, 87, 108, 0.8), 0 6px 20px rgba(245, 87, 108, 0.5); + transform: translateY(-2px); + } + + .dir-btn.bidir { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + } + + .dir-btn.bidir:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(79, 172, 254, 0.5); + } + + .dir-btn.bidir.active { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + box-shadow: 0 0 25px rgba(79, 172, 254, 0.8), 0 6px 20px rgba(79, 172, 254, 0.5); + transform: translateY(-2px); + } .freq-control { display: grid; @@ -437,7 +617,7 @@ /* Progress */ .progress-section { background: rgba(15, 23, 42, 0.4); - padding: 20px; + padding: 12px; border-radius: 12px; border: 1px solid rgba(255, 193, 7, 0.3); } @@ -468,7 +648,7 @@ /* Elements */ .elements-section { background: rgba(15, 23, 42, 0.4); - padding: 20px; + padding: 12px; border-radius: 12px; border: 1px solid rgba(79, 195, 247, 0.2); } @@ -502,7 +682,7 @@ /* Calibration */ .calibration-section { background: rgba(255, 152, 0, 0.05); - padding: 20px; + padding: 12px; border-radius: 12px; border: 1px solid rgba(255, 152, 0, 0.3); } @@ -517,7 +697,7 @@ .calibration-controls { display: flex; flex-direction: column; - gap: 16px; + gap: 10px; } .warning-text { diff --git a/web/src/lib/api.js b/web/src/lib/api.js index 21c971b..b3c0d39 100644 --- a/web/src/lib/api.js +++ b/web/src/lib/api.js @@ -97,5 +97,9 @@ export const api = { body: JSON.stringify({ frequency, direction }), }), retract: () => request('/ultrabeam/retract', { method: 'POST' }), + setAutoTrack: (enabled, threshold) => request('/ultrabeam/autotrack', { + method: 'POST', + body: JSON.stringify({ enabled, threshold }), + }), }, }; \ No newline at end of file