From 46ee44c6c9758d73f6aaf64188c2d5ebc6c237b2 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 10 Jan 2026 23:33:47 +0100 Subject: [PATCH] correct bugs AG --- cmd/server/web/dist/assets/index-DHBARw4b.js | 7 + cmd/server/web/dist/assets/index-dhCTx3KU.css | 1 + cmd/server/web/dist/index.html | 4 +- internal/api/device_manager.go | 72 +++++----- internal/api/handlers.go | 53 +++++++ .../devices/antennagenius/antennagenius.go | 46 +++++- web/src/components/AntennaGenius.svelte | 48 +++++-- web/src/components/PowerGenius.svelte | 133 ++---------------- web/src/components/RotatorGenius.svelte | 20 --- web/src/components/TunerGenius.svelte | 51 +------ web/src/lib/api.js | 8 ++ 11 files changed, 197 insertions(+), 246 deletions(-) create mode 100644 cmd/server/web/dist/assets/index-DHBARw4b.js create mode 100644 cmd/server/web/dist/assets/index-dhCTx3KU.css diff --git a/cmd/server/web/dist/assets/index-DHBARw4b.js b/cmd/server/web/dist/assets/index-DHBARw4b.js new file mode 100644 index 0000000..fda99d2 --- /dev/null +++ b/cmd/server/web/dist/assets/index-DHBARw4b.js @@ -0,0 +1,7 @@ +var pl=Object.defineProperty;var ml=(e,n,s)=>n in e?pl(e,n,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[n]=s;var Et=(e,n,s)=>ml(e,typeof n!="symbol"?n+"":n,s);(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const b of r.addedNodes)b.tagName==="LINK"&&b.rel==="modulepreload"&&o(b)}).observe(document,{childList:!0,subtree:!0});function s(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=s(i);fetch(i.href,r)}})();function Fe(){}function dl(e){return e()}function Zt(){return Object.create(null)}function et(e){e.forEach(dl)}function ul(e){return typeof e=="function"}function rt(e,n){return e!=e?n==n:e!==n||e&&typeof e=="object"||typeof e=="function"}function bl(e){return Object.keys(e).length===0}function t(e,n){e.appendChild(n)}function Xe(e,n,s){e.insertBefore(n,s||null)}function qe(e){e.parentNode&&e.parentNode.removeChild(e)}function Nt(e,n){for(let s=0;se.removeEventListener(n,s,o)}function l(e,n,s){s==null?e.removeAttribute(n):e.getAttribute(n)!==s&&e.setAttribute(n,s)}function gl(e){return Array.from(e.childNodes)}function I(e,n){n=""+n,e.data!==n&&(e.data=n)}function Ot(e,n){e.value=n??""}function oe(e,n,s,o){s==null?e.style.removeProperty(n):e.style.setProperty(n,s,"")}function Pt(e,n,s){for(let o=0;oe.indexOf(o)===-1?n.push(o):s.push(o)),s.forEach(o=>o()),kt=n}const At=new Set;let Fl;function ut(e,n){e&&e.i&&(At.delete(e),e.i(n))}function gt(e,n,s,o){if(e&&e.o){if(At.has(e))return;At.add(e),Fl.c.push(()=>{At.delete(e)}),e.o(n)}}function it(e){return(e==null?void 0:e.length)!==void 0?e:Array.from(e)}function wt(e){e&&e.c()}function ft(e,n,s){const{fragment:o,after_update:i}=e.$$;o&&o.m(n,s),jt(()=>{const r=e.$$.on_mount.map(dl).filter(ul);e.$$.on_destroy?e.$$.on_destroy.push(...r):et(r),e.$$.on_mount=[]}),i.forEach(jt)}function vt(e,n){const s=e.$$;s.fragment!==null&&(Sl(s.after_update),et(s.on_destroy),s.fragment&&s.fragment.d(n),s.on_destroy=s.fragment=null,s.ctx=[])}function Ml(e,n){e.$$.dirty[0]===-1&&(yt.push(e),Tl(),e.$$.dirty.fill(0)),e.$$.dirty[n/31|0]|=1<{const k=g.length?g[0]:d;return c.ctx&&i(c.ctx[h],c.ctx[h]=k)&&(!c.skip_bound&&c.bound[h]&&c.bound[h](k),y&&Ml(e,h)),d}):[],c.update(),y=!0,et(c.before_update),c.fragment=o?o(c.ctx):!1,n.target){if(n.hydrate){const h=gl(n.target);c.fragment&&c.fragment.l(h),h.forEach(qe)}else c.fragment&&c.fragment.c();n.intro&&ut(e.$$.fragment),ft(e,n.target,n.anchor),vl()}Ft(m)}class pt{constructor(){Et(this,"$$");Et(this,"$$set")}$destroy(){vt(this,1),this.$destroy=Fe}$on(n,s){if(!ul(s))return Fe;const o=this.$$.callbacks[n]||(this.$$.callbacks[n]=[]);return o.push(s),()=>{const i=o.indexOf(s);i!==-1&&o.splice(i,1)}}$set(n){this.$$set&&!bl(n)&&(this.$$.skip_bound=!0,this.$$set(n),this.$$.skip_bound=!1)}}const Ol="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(Ol);const _t=[];function It(e,n=Fe){let s;const o=new Set;function i(f){if(rt(e,f)&&(e=f,s)){const m=!_t.length;for(const c of o)c[1](),_t.push(c,e);if(m){for(let c=0;c<_t.length;c+=2)_t[c][0](_t[c+1]);_t.length=0}}}function r(f){i(f(e))}function b(f,m=Fe){const c=[f,m];return o.add(c),o.size===1&&(s=n(i,r)||Fe),f(e),()=>{o.delete(c),o.size===0&&s&&(s(),s=null)}}return{set:i,update:r,subscribe:b}}const Dt=It(!1),hl=It(null),Al=It(null);class Pl{constructor(){this.ws=null,this.reconnectTimeout=null,this.reconnectDelay=3e3}connect(){const s=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`;try{this.ws=new WebSocket(s),this.ws.onopen=()=>{console.log("WebSocket connected"),Dt.set(!0)},this.ws.onmessage=o=>{try{const i=JSON.parse(o.data);i.type==="update"&&(console.log("System status updated:",i.data),hl.set(i.data),Al.set(new Date(i.timestamp)))}catch(i){console.error("Error parsing message:",i)}},this.ws.onerror=o=>{console.error("WebSocket error:",o)},this.ws.onclose=()=>{console.log("WebSocket disconnected"),Dt.set(!1),this.scheduleReconnect()}}catch(o){console.error("Error creating WebSocket:",o),this.scheduleReconnect()}}scheduleReconnect(){this.reconnectTimeout&&clearTimeout(this.reconnectTimeout),this.reconnectTimeout=setTimeout(()=>{console.log("Attempting to reconnect..."),this.connect()},this.reconnectDelay)}send(n){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(n))}disconnect(){this.reconnectTimeout&&clearTimeout(this.reconnectTimeout),this.ws&&this.ws.close()}}const xt=new Pl,jl="/api";async function he(e,n={}){try{const s=await fetch(`${jl}${e}`,{...n,headers:{"Content-Type":"application/json",...n.headers}});if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return await s.json()}catch(s){throw console.error("API request failed:",s),s}}const Se={getStatus:()=>he("/status"),getConfig:()=>he("/config"),webswitch:{relayOn:e=>he(`/webswitch/relay/on?relay=${e}`,{method:"POST"}),relayOff:e=>he(`/webswitch/relay/off?relay=${e}`,{method:"POST"}),allOn:()=>he("/webswitch/all/on",{method:"POST"}),allOff:()=>he("/webswitch/all/off",{method:"POST"})},rotator:{move:(e,n)=>he("/rotator/move",{method:"POST",body:JSON.stringify({rotator:e,azimuth:n})}),cw:e=>he(`/rotator/cw?rotator=${e}`,{method:"POST"}),ccw:e=>he(`/rotator/ccw?rotator=${e}`,{method:"POST"}),stop:()=>he("/rotator/stop",{method:"POST"})},tuner:{setOperate:e=>he("/tuner/operate",{method:"POST",body:JSON.stringify({value:e})}),setBypass:e=>he("/tuner/bypass",{method:"POST",body:JSON.stringify({value:e})}),autoTune:()=>he("/tuner/autotune",{method:"POST"})},antenna:{selectAntenna:(e,n)=>he("/antenna/select",{method:"POST",body:JSON.stringify({port:e,antenna:n})}),reboot:()=>he("/antenna/reboot",{method:"POST"})},power:{setFanMode:e=>he("/power/fanmode",{method:"POST",body:JSON.stringify({mode:e})}),setOperate:e=>he("/power/operate",{method:"POST",body:JSON.stringify({value:e})})},rotator:{setHeading:e=>he("/rotator/heading",{method:"POST",body:JSON.stringify({heading:e})}),rotateCW:()=>he("/rotator/cw",{method:"POST"}),rotateCCW:()=>he("/rotator/ccw",{method:"POST"}),stop:()=>he("/rotator/stop",{method:"POST"})},ultrabeam:{setFrequency:(e,n)=>he("/ultrabeam/frequency",{method:"POST",body:JSON.stringify({frequency:e,direction:n})}),retract:()=>he("/ultrabeam/retract",{method:"POST"}),setAutoTrack:(e,n)=>he("/ultrabeam/autotrack",{method:"POST",body:JSON.stringify({enabled:e,threshold:n})})}};function el(e,n,s){var b;const o=e.slice();o[10]=n[s];const i=o[1].find(function(...m){return e[9](o[10],...m)});o[11]=i;const r=((b=o[11])==null?void 0:b.state)||!1;return o[12]=r,o}function tl(e){let n,s,o,i,r,b,f=e[12]?"ON":"OFF",m,c,y,h,d,g,k,C;function p(){return e[8](e[10])}return{c(){n=a("div"),s=a("div"),o=a("div"),i=a("div"),i.textContent=`${e[3][e[10]]}`,r=u(),b=a("div"),m=_(f),c=u(),y=a("button"),h=a("div"),h.innerHTML='
',g=u(),l(i,"class","relay-name svelte-z2csmj"),l(b,"class","relay-status svelte-z2csmj"),l(o,"class","relay-details svelte-z2csmj"),l(s,"class","relay-info svelte-z2csmj"),l(h,"class","toggle-track svelte-z2csmj"),l(y,"class","relay-toggle svelte-z2csmj"),y.disabled=d=e[0][e[10]],N(y,"active",e[12]),N(y,"loading",e[0][e[10]]),l(n,"class","relay-card svelte-z2csmj"),N(n,"relay-on",e[12])},m(w,T){Xe(w,n,T),t(n,s),t(s,o),t(o,i),t(o,r),t(o,b),t(b,m),t(n,c),t(n,y),t(y,h),t(n,g),k||(C=be(y,"click",p),k=!0)},p(w,T){e=w,T&2&&f!==(f=e[12]?"ON":"OFF")&&I(m,f),T&1&&d!==(d=e[0][e[10]])&&(y.disabled=d),T&2&&N(y,"active",e[12]),T&1&&N(y,"loading",e[0][e[10]]),T&2&&N(n,"relay-on",e[12])},d(w){w&&qe(n),k=!1,C()}}}function Nl(e){let n,s,o,i,r,b,f,m,c,y,h,d,g,k,C,p=it([1,2,3,4,5]),w=[];for(let T=0;T<5;T+=1)w[T]=tl(el(e,p,T));return{c(){n=a("div"),s=a("div"),o=a("h2"),o.textContent="WebSwitch",i=u(),r=a("span"),b=u(),f=a("div"),m=a("div");for(let T=0;T<5;T+=1)w[T].c();c=u(),y=a("div"),h=a("button"),h.innerHTML=` + ALL ON`,d=u(),g=a("button"),g.innerHTML=` + ALL OFF`,l(o,"class","svelte-z2csmj"),l(r,"class","status-dot svelte-z2csmj"),N(r,"disconnected",!e[2]),l(s,"class","card-header svelte-z2csmj"),l(m,"class","relays svelte-z2csmj"),l(h,"class","control-btn all-on svelte-z2csmj"),l(g,"class","control-btn all-off svelte-z2csmj"),l(y,"class","controls svelte-z2csmj"),l(f,"class","metrics svelte-z2csmj"),l(n,"class","card svelte-z2csmj")},m(T,v){Xe(T,n,v),t(n,s),t(s,o),t(s,i),t(s,r),t(n,b),t(n,f),t(f,m);for(let O=0;O<5;O+=1)w[O]&&w[O].m(m,null);t(f,c),t(f,y),t(y,h),t(y,d),t(y,g),k||(C=[be(h,"click",e[5]),be(g,"click",e[6])],k=!0)},p(T,[v]){if(v&4&&N(r,"disconnected",!T[2]),v&27){p=it([1,2,3,4,5]);let O;for(O=0;O<5;O+=1){const U=el(T,p,O);w[O]?w[O].p(U,v):(w[O]=tl(U),w[O].c(),w[O].m(m,null))}for(;O<5;O+=1)w[O].d(1)}},i:Fe,o:Fe,d(T){T&&qe(n),Nt(w,T),k=!1,et(C)}}}function El(e,n,s){let o,i,{status:r}=n;const b={1:"Power Supply",2:"PGXL",3:"TGXL",4:"Flex Radio Start",5:"Reserve"};let f={};async function m(g){const k=o.find(p=>p.number===g),C=(k==null?void 0:k.state)||!1;s(0,f[g]=!0,f);try{C?await Se.webswitch.relayOff(g):await Se.webswitch.relayOn(g)}catch(p){console.error("Failed to toggle relay:",p),alert("Failed to control relay")}finally{s(0,f[g]=!1,f)}}async function c(){try{await Se.webswitch.allOn()}catch(g){console.error("Failed to turn all on:",g)}}async function y(){try{await Se.webswitch.allOff()}catch(g){console.error("Failed to turn all off:",g)}}const h=g=>m(g),d=(g,k)=>k.number===g;return e.$$set=g=>{"status"in g&&s(7,r=g.status)},e.$$.update=()=>{e.$$.dirty&128&&s(1,o=(r==null?void 0:r.relays)||[]),e.$$.dirty&128&&s(2,i=(r==null?void 0:r.connected)||!1)},[f,o,i,b,m,c,y,r,h,d]}class Ll extends pt{constructor(n){super(),ht(this,n,El,Nl,rt,{status:7})}}function Bl(e){let n,s,o,i,r,b,f,m,c,y,h,d,g,k,C,p,w,T=e[1].toFixed(0)+"",v,O,U,G,D,A,ie,re,H,S,J=e[2].toFixed(2)+"",q,Z,Q,X,M,F,R,B=e[3].toFixed(1)+"",we,fe,ge,le,Y,te,K,ve,$,W,ne=e[12].toFixed(1)+"",_e,ye,Oe,pe,ke,ce,Te,Ce,L,j,P,E,V,Me=e[15].toFixed(0)+"",Ue,ot,Ee,je,tt,Le,Ye=e[14].toFixed(1)+"",Ie,Re,Ae,ze,Ze,Ne,Qe=e[13].toFixed(1)+"",Be,Ve,Ge,x,se,lt,He,Ke,Tt,De,ct,$e,dt,Je,xe,nt,We,Ct,Pe,de,st,at,mt,St;return{c(){n=a("div"),s=a("div"),o=a("h2"),o.textContent="Power Genius XL",i=u(),r=a("div"),b=a("button"),f=_(e[7]),m=u(),c=a("span"),y=u(),h=a("div"),d=a("div"),g=a("div"),k=a("div"),C=a("span"),C.textContent="Power",p=u(),w=a("span"),v=_(T),O=_(" W"),U=u(),G=a("div"),D=a("div"),A=a("div"),ie=a("div"),re=u(),H=a("div"),S=a("div"),q=_(J),Z=u(),Q=a("div"),Q.textContent="SWR",X=u(),M=a("div"),F=a("div"),R=a("div"),we=_(B),fe=_("°"),ge=u(),le=a("div"),le.textContent="PA Temp",Y=u(),te=a("div"),K=a("div"),ve=u(),$=a("div"),W=a("div"),_e=_(ne),ye=_("°"),Oe=u(),pe=a("div"),pe.textContent="HL Temp",ke=u(),ce=a("div"),Te=a("div"),Ce=u(),L=a("div"),j=a("div"),P=a("div"),P.textContent="VAC",E=u(),V=a("div"),Ue=_(Me),ot=u(),Ee=a("div"),je=a("div"),je.textContent="VDD",tt=u(),Le=a("div"),Ie=_(Ye),Re=u(),Ae=a("div"),ze=a("div"),ze.textContent="ID Peak",Ze=u(),Ne=a("div"),Be=_(Qe),Ve=u(),Ge=a("div"),x=a("div"),se=a("span"),se.textContent="Band A",lt=u(),He=a("span"),Ke=_(e[10]),Tt=u(),De=a("div"),ct=a("span"),ct.textContent="Band B",$e=u(),dt=a("span"),Je=_(e[9]),xe=u(),nt=a("div"),We=a("label"),We.textContent="Fan Mode",Ct=u(),Pe=a("select"),de=a("option"),de.textContent="Standard",st=a("option"),st.textContent="Contest",at=a("option"),at.textContent="Broadcast",l(o,"class","svelte-1h3han9"),l(b,"class","state-badge svelte-1h3han9"),N(b,"idle",e[0]==="IDLE"),N(b,"transmit",e[0].includes("TRANSMIT")),l(c,"class","status-dot svelte-1h3han9"),N(c,"disconnected",!e[8]),l(r,"class","header-right svelte-1h3han9"),l(s,"class","card-header svelte-1h3han9"),l(C,"class","power-label-inline svelte-1h3han9"),l(w,"class","power-value-inline svelte-1h3han9"),l(k,"class","power-header svelte-1h3han9"),l(ie,"class","power-bar-glow svelte-1h3han9"),l(A,"class","power-bar-fill svelte-1h3han9"),oe(A,"width",e[4]+"%"),l(D,"class","power-bar-bg svelte-1h3han9"),l(G,"class","power-bar-container svelte-1h3han9"),l(g,"class","power-section svelte-1h3han9"),l(S,"class","swr-value-compact svelte-1h3han9"),l(Q,"class","swr-label-compact svelte-1h3han9"),l(H,"class","swr-circle-compact svelte-1h3han9"),oe(H,"--swr-color",e[5]),l(d,"class","power-swr-row svelte-1h3han9"),l(R,"class","temp-value svelte-1h3han9"),oe(R,"color",e[6]),l(le,"class","temp-label svelte-1h3han9"),l(K,"class","temp-mini-fill svelte-1h3han9"),oe(K,"width",e[3]/80*100+"%"),oe(K,"background",e[6]),l(te,"class","temp-mini-bar svelte-1h3han9"),l(F,"class","temp-item svelte-1h3han9"),l(W,"class","temp-value svelte-1h3han9"),oe(W,"color",e[6]),l(pe,"class","temp-label svelte-1h3han9"),l(Te,"class","temp-mini-fill svelte-1h3han9"),oe(Te,"width",e[12]/80*100+"%"),oe(Te,"background",e[6]),l(ce,"class","temp-mini-bar svelte-1h3han9"),l($,"class","temp-item svelte-1h3han9"),l(M,"class","temp-group svelte-1h3han9"),l(P,"class","param-label svelte-1h3han9"),l(V,"class","param-value svelte-1h3han9"),l(j,"class","param-box svelte-1h3han9"),l(je,"class","param-label svelte-1h3han9"),l(Le,"class","param-value svelte-1h3han9"),l(Ee,"class","param-box svelte-1h3han9"),l(ze,"class","param-label svelte-1h3han9"),l(Ne,"class","param-value svelte-1h3han9"),l(Ae,"class","param-box svelte-1h3han9"),l(L,"class","params-grid svelte-1h3han9"),l(se,"class","band-label svelte-1h3han9"),l(He,"class","band-value svelte-1h3han9"),l(x,"class","band-item svelte-1h3han9"),l(ct,"class","band-label svelte-1h3han9"),l(dt,"class","band-value svelte-1h3han9"),l(De,"class","band-item svelte-1h3han9"),l(Ge,"class","band-display svelte-1h3han9"),l(We,"for","fan-mode-select"),l(We,"class","control-label svelte-1h3han9"),de.__value="STANDARD",Ot(de,de.__value),st.__value="CONTEST",Ot(st,st.__value),at.__value="BROADCAST",Ot(at,at.__value),l(Pe,"id","fan-mode-select"),l(Pe,"class","svelte-1h3han9"),l(nt,"class","fan-control svelte-1h3han9"),l(h,"class","metrics svelte-1h3han9"),l(n,"class","card svelte-1h3han9")},m(ee,ae){Xe(ee,n,ae),t(n,s),t(s,o),t(s,i),t(s,r),t(r,b),t(b,f),t(r,m),t(r,c),t(n,y),t(n,h),t(h,d),t(d,g),t(g,k),t(k,C),t(k,p),t(k,w),t(w,v),t(w,O),t(g,U),t(g,G),t(G,D),t(D,A),t(A,ie),t(d,re),t(d,H),t(H,S),t(S,q),t(H,Z),t(H,Q),t(h,X),t(h,M),t(M,F),t(F,R),t(R,we),t(R,fe),t(F,ge),t(F,le),t(F,Y),t(F,te),t(te,K),t(M,ve),t(M,$),t($,W),t(W,_e),t(W,ye),t($,Oe),t($,pe),t($,ke),t($,ce),t(ce,Te),t(h,Ce),t(h,L),t(L,j),t(j,P),t(j,E),t(j,V),t(V,Ue),t(L,ot),t(L,Ee),t(Ee,je),t(Ee,tt),t(Ee,Le),t(Le,Ie),t(L,Re),t(L,Ae),t(Ae,ze),t(Ae,Ze),t(Ae,Ne),t(Ne,Be),t(h,Ve),t(h,Ge),t(Ge,x),t(x,se),t(x,lt),t(x,He),t(He,Ke),t(Ge,Tt),t(Ge,De),t(De,ct),t(De,$e),t(De,dt),t(dt,Je),t(h,xe),t(h,nt),t(nt,We),t(nt,Ct),t(nt,Pe),t(Pe,de),t(Pe,st),t(Pe,at),Pt(Pe,e[11]),mt||(St=[be(b,"click",e[17]),be(Pe,"change",e[19])],mt=!0)},p(ee,[ae]){ae&128&&I(f,ee[7]),ae&1&&N(b,"idle",ee[0]==="IDLE"),ae&1&&N(b,"transmit",ee[0].includes("TRANSMIT")),ae&256&&N(c,"disconnected",!ee[8]),ae&2&&T!==(T=ee[1].toFixed(0)+"")&&I(v,T),ae&16&&oe(A,"width",ee[4]+"%"),ae&4&&J!==(J=ee[2].toFixed(2)+"")&&I(q,J),ae&32&&oe(H,"--swr-color",ee[5]),ae&8&&B!==(B=ee[3].toFixed(1)+"")&&I(we,B),ae&64&&oe(R,"color",ee[6]),ae&8&&oe(K,"width",ee[3]/80*100+"%"),ae&64&&oe(K,"background",ee[6]),ae&4096&&ne!==(ne=ee[12].toFixed(1)+"")&&I(_e,ne),ae&64&&oe(W,"color",ee[6]),ae&4096&&oe(Te,"width",ee[12]/80*100+"%"),ae&64&&oe(Te,"background",ee[6]),ae&32768&&Me!==(Me=ee[15].toFixed(0)+"")&&I(Ue,Me),ae&16384&&Ye!==(Ye=ee[14].toFixed(1)+"")&&I(Ie,Ye),ae&8192&&Qe!==(Qe=ee[13].toFixed(1)+"")&&I(Be,Qe),ae&1024&&I(Ke,ee[10]),ae&512&&I(Je,ee[9]),ae&2048&&Pt(Pe,ee[11])},i:Fe,o:Fe,d(ee){ee&&qe(n),mt=!1,et(St)}}}function Dl(e,n,s){let o,i,r,b,f,m,c,y,h,d,g,k,C,p,w,T,{status:v}=n;async function O(D){try{await Se.power.setFanMode(D)}catch(A){console.error("Failed to set fan mode:",A)}}async function U(){try{const D=h==="IDLE"?0:1;await Se.power.setOperate(D)}catch(D){console.error("Failed to toggle operate:",D)}}const G=D=>O(D.target.value);return e.$$set=D=>{"status"in D&&s(18,v=D.status)},e.$$.update=()=>{e.$$.dirty&262144&&s(1,o=(v==null?void 0:v.power_forward)||0),e.$$.dirty&262144&&v!=null&&v.power_reflected,e.$$.dirty&262144&&s(2,i=(v==null?void 0:v.swr)||1),e.$$.dirty&262144&&v&&console.log("PowerGenius status update:",{powerForward:v.power_forward,swr:v.swr,state:v.state,connected:v.connected}),e.$$.dirty&262144&&s(15,r=(v==null?void 0:v.voltage)||0),e.$$.dirty&262144&&s(14,b=(v==null?void 0:v.vdd)||0),e.$$.dirty&262144&&v!=null&&v.current,e.$$.dirty&262144&&s(13,f=(v==null?void 0:v.peak_current)||0),e.$$.dirty&262144&&s(3,m=(v==null?void 0:v.temperature)||0),e.$$.dirty&262144&&s(12,c=(v==null?void 0:v.harmonic_load_temp)||0),e.$$.dirty&262144&&s(11,y=(v==null?void 0:v.fan_mode)||"CONTEST"),e.$$.dirty&262144&&s(0,h=(v==null?void 0:v.state)||"IDLE"),e.$$.dirty&262144&&s(10,d=(v==null?void 0:v.band_a)||"0"),e.$$.dirty&262144&&s(9,g=(v==null?void 0:v.band_b)||"0"),e.$$.dirty&262144&&s(8,k=(v==null?void 0:v.connected)||!1),e.$$.dirty&1&&s(7,C=h.replace("TRANSMIT_A","TRANSMIT").replace("TRANSMIT_B","TRANSMIT")),e.$$.dirty&8&&s(6,p=m<40?"#4caf50":m<60?"#ffc107":m<75?"#ff9800":"#f44336"),e.$$.dirty&4&&s(5,w=i<1.5?"#4caf50":i<2?"#ffc107":i<3?"#ff9800":"#f44336"),e.$$.dirty&2&&s(4,T=Math.min(o/2e3*100,100))},[h,o,i,m,T,w,p,C,k,g,d,y,c,f,b,r,O,U,v,G]}class Il extends pt{constructor(n){super(),ht(this,n,Dl,Bl,rt,{status:18})}}function Rl(e){let n,s,o,i,r,b,f,m,c,y,h,d,g,k,C,p,w,T=e[0].toFixed(0)+"",v,O,U,G,D,A,ie,re,H,S,J=e[1].toFixed(2)+"",q,Z,Q,X,M,F,R,B,we,fe,ge,le,Y,te,K,ve,$,W,ne,_e,ye,Oe,pe,ke,ce,Te,Ce,L,j=(e[11]/1e3).toFixed(3)+"",P,E,V,Me,Ue,ot,Ee,je=(e[10]/1e3).toFixed(3)+"",tt,Le,Ye,Ie,Re,Ae=e[8]===1?"OPERATE":"STANDBY",ze,Ze,Ne,Qe,Be,Ve,Ge;return{c(){n=a("div"),s=a("div"),o=a("h2"),o.textContent="Tuner Genius XL",i=u(),r=a("div"),b=a("span"),f=_(e[12]),m=u(),c=a("span"),y=u(),h=a("div"),d=a("div"),g=a("div"),k=a("div"),C=a("span"),C.textContent="Power",p=u(),w=a("span"),v=_(T),O=_(" W"),U=u(),G=a("div"),D=a("div"),A=a("div"),ie=a("div"),re=u(),H=a("div"),S=a("div"),q=_(J),Z=u(),Q=a("div"),Q.textContent="SWR",X=u(),M=a("div"),F=a("div"),R=a("div"),B=_(e[7]),we=u(),fe=a("div"),fe.textContent="C1",ge=u(),le=a("div"),Y=a("div"),te=_(e[6]),K=u(),ve=a("div"),ve.textContent="L",$=u(),W=a("div"),ne=a("div"),_e=_(e[5]),ye=u(),Oe=a("div"),Oe.textContent="C2",pe=u(),ke=a("div"),ce=a("div"),Te=a("div"),Te.textContent="Freq A",Ce=u(),L=a("div"),P=_(j),E=a("span"),E.textContent="MHz",V=u(),Me=a("div"),Ue=a("div"),Ue.textContent="Freq B",ot=u(),Ee=a("div"),tt=_(je),Le=a("span"),Le.textContent="MHz",Ye=u(),Ie=a("div"),Re=a("button"),ze=_(Ae),Ze=u(),Ne=a("button"),Ne.textContent="BYPASS",Qe=u(),Be=a("button"),Be.innerHTML=` + AUTO TUNE`,l(o,"class","svelte-s5par0"),l(b,"class","tuning-badge svelte-s5par0"),N(b,"tuning",e[12]==="TUNING"),l(c,"class","status-dot svelte-s5par0"),N(c,"disconnected",!e[4]),l(r,"class","header-right svelte-s5par0"),l(s,"class","card-header svelte-s5par0"),l(C,"class","power-label-inline svelte-s5par0"),l(w,"class","power-value-inline svelte-s5par0"),l(k,"class","power-header svelte-s5par0"),l(ie,"class","power-bar-glow svelte-s5par0"),l(A,"class","power-bar-fill svelte-s5par0"),oe(A,"width",e[2]+"%"),l(D,"class","power-bar-bg svelte-s5par0"),l(G,"class","power-bar-container svelte-s5par0"),l(g,"class","power-section svelte-s5par0"),l(S,"class","swr-value-compact svelte-s5par0"),l(Q,"class","swr-label-compact svelte-s5par0"),l(H,"class","swr-circle-compact svelte-s5par0"),oe(H,"--swr-color",e[3]),l(d,"class","power-swr-row svelte-s5par0"),l(R,"class","cap-value svelte-s5par0"),l(fe,"class","cap-label svelte-s5par0"),l(F,"class","cap-item svelte-s5par0"),l(Y,"class","cap-value svelte-s5par0"),l(ve,"class","cap-label svelte-s5par0"),l(le,"class","cap-item svelte-s5par0"),l(ne,"class","cap-value svelte-s5par0"),l(Oe,"class","cap-label svelte-s5par0"),l(W,"class","cap-item svelte-s5par0"),l(M,"class","capacitors svelte-s5par0"),l(Te,"class","freq-label svelte-s5par0"),l(E,"class","freq-unit svelte-s5par0"),l(L,"class","freq-value svelte-s5par0"),l(ce,"class","freq-item svelte-s5par0"),l(Ue,"class","freq-label svelte-s5par0"),l(Le,"class","freq-unit svelte-s5par0"),l(Ee,"class","freq-value svelte-s5par0"),l(Me,"class","freq-item svelte-s5par0"),l(ke,"class","freq-display svelte-s5par0"),l(Re,"class","control-btn operate svelte-s5par0"),N(Re,"active",e[8]===1),l(Ne,"class","control-btn bypass svelte-s5par0"),N(Ne,"active",e[9]),l(Ie,"class","controls svelte-s5par0"),l(Be,"class","tune-btn svelte-s5par0"),l(h,"class","metrics svelte-s5par0"),l(n,"class","card svelte-s5par0")},m(x,se){Xe(x,n,se),t(n,s),t(s,o),t(s,i),t(s,r),t(r,b),t(b,f),t(r,m),t(r,c),t(n,y),t(n,h),t(h,d),t(d,g),t(g,k),t(k,C),t(k,p),t(k,w),t(w,v),t(w,O),t(g,U),t(g,G),t(G,D),t(D,A),t(A,ie),t(d,re),t(d,H),t(H,S),t(S,q),t(H,Z),t(H,Q),t(h,X),t(h,M),t(M,F),t(F,R),t(R,B),t(F,we),t(F,fe),t(M,ge),t(M,le),t(le,Y),t(Y,te),t(le,K),t(le,ve),t(M,$),t(M,W),t(W,ne),t(ne,_e),t(W,ye),t(W,Oe),t(h,pe),t(h,ke),t(ke,ce),t(ce,Te),t(ce,Ce),t(ce,L),t(L,P),t(L,E),t(ke,V),t(ke,Me),t(Me,Ue),t(Me,ot),t(Me,Ee),t(Ee,tt),t(Ee,Le),t(h,Ye),t(h,Ie),t(Ie,Re),t(Re,ze),t(Ie,Ze),t(Ie,Ne),t(h,Qe),t(h,Be),Ve||(Ge=[be(Re,"click",e[17]),be(Ne,"click",e[18]),be(Be,"click",e[13])],Ve=!0)},p(x,[se]){se&4096&&I(f,x[12]),se&4096&&N(b,"tuning",x[12]==="TUNING"),se&16&&N(c,"disconnected",!x[4]),se&1&&T!==(T=x[0].toFixed(0)+"")&&I(v,T),se&4&&oe(A,"width",x[2]+"%"),se&2&&J!==(J=x[1].toFixed(2)+"")&&I(q,J),se&8&&oe(H,"--swr-color",x[3]),se&128&&I(B,x[7]),se&64&&I(te,x[6]),se&32&&I(_e,x[5]),se&2048&&j!==(j=(x[11]/1e3).toFixed(3)+"")&&I(P,j),se&1024&&je!==(je=(x[10]/1e3).toFixed(3)+"")&&I(tt,je),se&256&&Ae!==(Ae=x[8]===1?"OPERATE":"STANDBY")&&I(ze,Ae),se&256&&N(Re,"active",x[8]===1),se&512&&N(Ne,"active",x[9])},i:Fe,o:Fe,d(x){x&&qe(n),Ve=!1,et(Ge)}}}function zl(e,n,s){let o,i,r,b,f,m,c,y,h,d,g,k,C,{status:p}=n;async function w(){try{await Se.tuner.autoTune()}catch(G){console.error("Failed to tune:",G),alert("Failed to start tuning")}}async function T(G){try{await Se.tuner.setBypass(G)}catch(D){console.error("Failed to set bypass:",D),alert("Failed to set bypass")}}async function v(G){try{await Se.tuner.setOperate(G)}catch(D){console.error("Failed to set operate:",D),alert("Failed to set operate")}}const O=()=>v(c===1?0:1),U=()=>T(m?0:1);return e.$$set=G=>{"status"in G&&s(16,p=G.status)},e.$$.update=()=>{e.$$.dirty&65536&&s(0,o=(p==null?void 0:p.power_forward)||0),e.$$.dirty&65536&&s(1,i=(p==null?void 0:p.swr)||1),e.$$.dirty&65536&&s(12,r=(p==null?void 0:p.tuning_status)||"READY"),e.$$.dirty&65536&&s(11,b=(p==null?void 0:p.frequency_a)||0),e.$$.dirty&65536&&s(10,f=(p==null?void 0:p.frequency_b)||0),e.$$.dirty&65536&&s(9,m=(p==null?void 0:p.bypass)||!1),e.$$.dirty&65536&&s(8,c=(p==null?void 0:p.state)||0),e.$$.dirty&65536&&s(7,y=(p==null?void 0:p.c1)||0),e.$$.dirty&65536&&s(6,h=(p==null?void 0:p.l)||0),e.$$.dirty&65536&&s(5,d=(p==null?void 0:p.c2)||0),e.$$.dirty&65536&&s(4,g=(p==null?void 0:p.connected)||!1),e.$$.dirty&2&&s(3,k=i<1.5?"#4caf50":i<2?"#ffc107":i<3?"#ff9800":"#f44336"),e.$$.dirty&1&&s(2,C=Math.min(o/2e3*100,100))},[o,i,C,k,g,d,h,y,c,m,f,b,r,w,T,v,p,O,U]}class Gl extends pt{constructor(n){super(),ht(this,n,zl,Rl,rt,{status:16})}}function ll(e,n,s){const o=e.slice();o[14]=n[s];const i=o[1].tx&&o[1].tx_ant===o[14].number;o[15]=i;const r=o[0].tx&&o[0].tx_ant===o[14].number;o[16]=r;const b=!o[1].tx&&o[1].rx_ant===o[14].number;o[17]=b;const f=!o[0].tx&&o[0].rx_ant===o[14].number;o[18]=f;const m=o[15]||o[16];o[19]=m;const c=o[17]||o[15];o[20]=c;const y=o[18]||o[16];return o[21]=y,o}function nl(e){let n,s,o=e[14].name+"",i,r,b,f,m,c,y,h,d;function g(){return e[11](e[14])}function k(){return e[12](e[14])}return{c(){n=a("div"),s=a("div"),i=_(o),r=u(),b=a("div"),f=a("button"),f.textContent="A",m=u(),c=a("button"),c.textContent="B",y=u(),l(s,"class","antenna-name svelte-vlabwb"),l(f,"class","port-btn svelte-vlabwb"),N(f,"active",e[20]),l(c,"class","port-btn svelte-vlabwb"),N(c,"active",e[21]),l(b,"class","antenna-ports svelte-vlabwb"),l(n,"class","antenna-card svelte-vlabwb"),N(n,"tx",e[19]),N(n,"active-a",e[20]),N(n,"active-b",e[21])},m(C,p){Xe(C,n,p),t(n,s),t(s,i),t(n,r),t(n,b),t(b,f),t(b,m),t(b,c),t(n,y),h||(d=[be(f,"click",g),be(c,"click",k)],h=!0)},p(C,p){e=C,p&16&&o!==(o=e[14].name+"")&&I(i,o),p&18&&N(f,"active",e[20]),p&17&&N(c,"active",e[21]),p&19&&N(n,"tx",e[19]),p&18&&N(n,"active-a",e[20]),p&17&&N(n,"active-b",e[21])},d(C){C&&qe(n),h=!1,et(d)}}}function Hl(e){let n,s,o,i,r,b,f,m,c,y,h=(e[1].source||"FLEX")+"",d,g,k,C,p=(e[0].source||"FLEX")+"",w,T,v,O,U,G,D,A,ie,re,H,S,J,q,Z,Q,X=it(e[4]),M=[];for(let F=0;F🔄 + REBOOT`,l(o,"class","svelte-vlabwb"),l(r,"class","status-dot svelte-vlabwb"),N(r,"disconnected",!e[5]),l(s,"class","card-header svelte-vlabwb"),l(y,"class","source-label svelte-vlabwb"),l(c,"class","source-item svelte-vlabwb"),l(C,"class","source-label svelte-vlabwb"),l(k,"class","source-item svelte-vlabwb"),l(m,"class","sources svelte-vlabwb"),l(U,"class","band-value svelte-vlabwb"),l(O,"class","band-item svelte-vlabwb"),l(ie,"class","band-value svelte-vlabwb"),l(A,"class","band-item svelte-vlabwb"),l(v,"class","bands svelte-vlabwb"),l(S,"class","antennas svelte-vlabwb"),l(q,"class","reboot-btn svelte-vlabwb"),l(f,"class","metrics svelte-vlabwb"),l(n,"class","card svelte-vlabwb")},m(F,R){Xe(F,n,R),t(n,s),t(s,o),t(s,i),t(s,r),t(n,b),t(n,f),t(f,m),t(m,c),t(c,y),t(y,d),t(m,g),t(m,k),t(k,C),t(C,w),t(f,T),t(f,v),t(v,O),t(O,U),t(U,G),t(v,D),t(v,A),t(A,ie),t(ie,re),t(f,H),t(f,S);for(let B=0;Bh(1,w.number),p=w=>h(2,w.number);return e.$$set=w=>{"status"in w&&s(8,c=w.status)},e.$$.update=()=>{e.$$.dirty&256&&s(5,o=(c==null?void 0:c.connected)||!1),e.$$.dirty&256&&s(1,i=(c==null?void 0:c.port_a)||{}),e.$$.dirty&256&&s(0,r=(c==null?void 0:c.port_b)||{}),e.$$.dirty&256&&s(4,b=(c==null?void 0:c.antennas)||[]),e.$$.dirty&2&&s(3,f=y[i.band]||"None"),e.$$.dirty&1&&s(2,m=y[r.band]||"None"),e.$$.dirty&1795&&c&&(i.tx!==d||r.tx!==g)&&(console.log("AntennaGenius TX state changed:",{portA_tx:i.tx,portB_tx:r.tx,portA_tx_ant:i.tx_ant,portB_tx_ant:r.tx_ant}),s(9,d=i.tx),s(10,g=r.tx))},[r,i,m,f,b,o,h,k,c,d,g,C,p]}class ql extends pt{constructor(n){super(),ht(this,n,Wl,Hl,rt,{status:8})}}function sl(e,n,s){const o=e.slice();o[9]=n[s];const i=150+125*Math.sin(o[9]*Math.PI/180);o[10]=i;const r=150-125*Math.cos(o[9]*Math.PI/180);return o[11]=r,o}function al(e){let n,s,o;return{c(){n=ue("text"),s=_(e[9]),o=_("°"),l(n,"x",e[10]),l(n,"y",e[11]),l(n,"text-anchor","middle"),l(n,"dominant-baseline","middle"),l(n,"class","degree-label svelte-cd0075")},m(i,r){Xe(i,n,r),t(n,s),t(n,o)},p:Fe,d(i){i&&qe(n)}}}function Ul(e){let n,s,o,i,r,b,f,m,c,y,h,d,g,k,C,p,w,T,v,O,U,G,D,A,ie,re,H,S,J,q,Z,Q,X,M,F,R,B,we,fe,ge,le,Y,te,K,ve,$,W,ne,_e,ye,Oe,pe,ke,ce,Te,Ce=it([45,135,225,315]),L=[];for(let j=0;j<4;j+=1)L[j]=al(sl(e,Ce,j));return{c(){n=a("div"),s=a("div"),o=a("h2"),o.textContent="Rotator Genius",i=u(),r=a("span"),b=u(),f=a("div"),m=a("div"),c=a("div"),y=a("div"),y.textContent="CURRENT HEADING",h=u(),d=a("div"),g=_(e[0]),k=_("°"),C=u(),p=a("div"),w=a("button"),w.textContent="↺",T=u(),v=a("button"),v.textContent="■",O=u(),U=a("button"),U.textContent="↻",G=u(),D=a("div"),A=ue("svg"),ie=ue("defs"),re=ue("radialGradient"),H=ue("stop"),S=ue("stop"),J=ue("circle"),q=ue("circle"),Z=ue("circle"),Q=ue("circle"),X=ue("g"),M=ue("g"),F=ue("path"),R=ue("line"),B=ue("line"),we=ue("g"),fe=ue("polygon"),le=ue("circle"),Y=ue("animate"),te=ue("circle"),K=ue("animate"),ve=ue("animate"),$=ue("text"),W=_("N"),ne=ue("text"),_e=_("E"),ye=ue("text"),Oe=_("S"),pe=ue("text"),ke=_("W");for(let j=0;j<4;j+=1)L[j].c();l(o,"class","svelte-cd0075"),l(r,"class","status-dot svelte-cd0075"),N(r,"disconnected",!e[1]),l(s,"class","card-header svelte-cd0075"),l(y,"class","heading-label svelte-cd0075"),l(d,"class","heading-value svelte-cd0075"),l(c,"class","heading-display-compact svelte-cd0075"),l(w,"class","btn-mini ccw svelte-cd0075"),l(w,"title","Rotate Counter-Clockwise"),l(v,"class","btn-mini stop svelte-cd0075"),l(v,"title","Stop Rotation"),l(U,"class","btn-mini cw svelte-cd0075"),l(U,"title","Rotate Clockwise"),l(p,"class","controls-compact svelte-cd0075"),l(m,"class","heading-controls-row svelte-cd0075"),l(H,"offset","0%"),oe(H,"stop-color","rgba(79, 195, 247, 0.7)"),oe(H,"stop-opacity","1"),l(S,"offset","100%"),oe(S,"stop-color","rgba(79, 195, 247, 0)"),oe(S,"stop-opacity","0"),l(re,"id","beamGradient"),l(J,"cx","150"),l(J,"cy","150"),l(J,"r","140"),l(J,"fill","rgba(30, 64, 175, 0.15)"),l(J,"stroke","rgba(79, 195, 247, 0.4)"),l(J,"stroke-width","2"),l(q,"cx","150"),l(q,"cy","150"),l(q,"r","105"),l(q,"fill","none"),l(q,"stroke","rgba(79,195,247,0.2)"),l(q,"stroke-width","1"),l(q,"stroke-dasharray","3,3"),l(Z,"cx","150"),l(Z,"cy","150"),l(Z,"r","70"),l(Z,"fill","none"),l(Z,"stroke","rgba(79,195,247,0.2)"),l(Z,"stroke-width","1"),l(Z,"stroke-dasharray","3,3"),l(Q,"cx","150"),l(Q,"cy","150"),l(Q,"r","35"),l(Q,"fill","none"),l(Q,"stroke","rgba(79,195,247,0.2)"),l(Q,"stroke-width","1"),l(Q,"stroke-dasharray","3,3"),l(F,"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(F,"fill","url(#beamGradient)"),l(F,"opacity","0.85"),l(R,"x1","0"),l(R,"y1","0"),l(R,"x2",-Math.sin(15*Math.PI/180)*130),l(R,"y2",-Math.cos(15*Math.PI/180)*130),l(R,"stroke","#4fc3f7"),l(R,"stroke-width","2"),l(R,"opacity","0.9"),l(B,"x1","0"),l(B,"y1","0"),l(B,"x2",Math.sin(15*Math.PI/180)*130),l(B,"y2",-Math.cos(15*Math.PI/180)*130),l(B,"stroke","#4fc3f7"),l(B,"stroke-width","2"),l(B,"opacity","0.9"),l(fe,"points","0,-20 -8,5 0,0 8,5"),l(fe,"fill","#4fc3f7"),l(fe,"stroke","#0288d1"),l(fe,"stroke-width","2"),oe(fe,"filter","drop-shadow(0 0 10px rgba(79, 195, 247, 1))"),l(we,"transform","translate(0, -110)"),l(M,"transform",ge="rotate("+e[0]+")"),l(Y,"attributeName","r"),l(Y,"values","5;7;5"),l(Y,"dur","2s"),l(Y,"repeatCount","indefinite"),l(le,"cx","0"),l(le,"cy","0"),l(le,"r","5"),l(le,"fill","#f44336"),l(le,"stroke","#fff"),l(le,"stroke-width","2"),l(K,"attributeName","r"),l(K,"values","10;16;10"),l(K,"dur","2s"),l(K,"repeatCount","indefinite"),l(ve,"attributeName","opacity"),l(ve,"values","0.5;0;0.5"),l(ve,"dur","2s"),l(ve,"repeatCount","indefinite"),l(te,"cx","0"),l(te,"cy","0"),l(te,"r","10"),l(te,"fill","none"),l(te,"stroke","#f44336"),l(te,"stroke-width","1.5"),l(te,"opacity","0.5"),l(X,"transform","translate(150, 150)"),l($,"x","150"),l($,"y","20"),l($,"text-anchor","middle"),l($,"class","cardinal svelte-cd0075"),l(ne,"x","280"),l(ne,"y","155"),l(ne,"text-anchor","middle"),l(ne,"class","cardinal svelte-cd0075"),l(ye,"x","150"),l(ye,"y","285"),l(ye,"text-anchor","middle"),l(ye,"class","cardinal svelte-cd0075"),l(pe,"x","20"),l(pe,"y","155"),l(pe,"text-anchor","middle"),l(pe,"class","cardinal svelte-cd0075"),l(A,"viewBox","0 0 300 300"),l(A,"class","map-svg clickable-compass svelte-cd0075"),l(D,"class","map-container svelte-cd0075"),l(f,"class","metrics svelte-cd0075"),l(n,"class","card svelte-cd0075")},m(j,P){Xe(j,n,P),t(n,s),t(s,o),t(s,i),t(s,r),t(n,b),t(n,f),t(f,m),t(m,c),t(c,y),t(c,h),t(c,d),t(d,g),t(d,k),t(m,C),t(m,p),t(p,w),t(p,T),t(p,v),t(p,O),t(p,U),t(f,G),t(f,D),t(D,A),t(A,ie),t(ie,re),t(re,H),t(re,S),t(A,J),t(A,q),t(A,Z),t(A,Q),t(A,X),t(X,M),t(M,F),t(M,R),t(M,B),t(M,we),t(we,fe),t(X,le),t(le,Y),t(X,te),t(te,K),t(te,ve),t(A,$),t($,W),t(A,ne),t(ne,_e),t(A,ye),t(ye,Oe),t(A,pe),t(pe,ke);for(let E=0;E<4;E+=1)L[E]&&L[E].m(A,null);ce||(Te=[be(w,"click",e[3]),be(v,"click",e[4]),be(U,"click",e[2]),be(A,"click",e[5])],ce=!0)},p(j,[P]){if(P&2&&N(r,"disconnected",!j[1]),P&1&&I(g,j[0]),P&1&&ge!==(ge="rotate("+j[0]+")")&&l(M,"transform",ge),P&0){Ce=it([45,135,225,315]);let E;for(E=0;E<4;E+=1){const V=sl(j,Ce,E);L[E]?L[E].p(V,P):(L[E]=al(V),L[E].c(),L[E].m(A,null))}for(;E<4;E+=1)L[E].d(1)}},i:Fe,o:Fe,d(j){j&&qe(n),Nt(L,j),ce=!1,et(Te)}}}function Jl(e,n,s){let{status:o}=n,i=0,r=!1,b=0;async function f(){if(b<0||b>359){alert("Heading must be between 0 and 359");return}try{const d=(b-10+360)%360;await Se.rotator.setHeading(d)}catch(d){console.error("Failed to set heading:",d),alert("Failed to rotate")}}async function m(){try{await Se.rotator.rotateCW()}catch(d){console.error("Failed to rotate CW:",d)}}async function c(){try{await Se.rotator.rotateCCW()}catch(d){console.error("Failed to rotate CCW:",d)}}async function y(){try{await Se.rotator.stop()}catch(d){console.error("Failed to stop:",d)}}function h(d){const k=d.currentTarget.getBoundingClientRect(),C=k.width/2,p=k.height/2,w=d.clientX-k.left-C,T=d.clientY-k.top-p;let v=Math.atan2(w,-T)*(180/Math.PI);v<0&&(v+=360),b=Math.round(v/5)*5,f()}return e.$$set=d=>{"status"in d&&s(6,o=d.status)},e.$$.update=()=>{e.$$.dirty&64&&(o==null?void 0:o.heading)!==void 0&&(o==null?void 0:o.heading)!==null&&s(0,i=o.heading),e.$$.dirty&64&&s(1,r=(o==null?void 0:o.connected)||!1)},[i,r,m,c,y,h,o]}class Xl extends pt{constructor(n){super(),ht(this,n,Jl,Ul,rt,{status:6})}}function ol(e,n,s){const o=e.slice();return o[31]=n[s],o}function il(e){let n,s=e[31].label+"",o;return{c(){n=a("option"),o=_(s),n.__value=e[31].value,Ot(n,n.__value)},m(i,r){Xe(i,n,r),t(n,o)},p:Fe,d(i){i&&qe(n)}}}function rl(e){let n,s,o,i,r,b,f,m,c,y=e[6].toFixed(0)+"",h,d;return{c(){n=a("div"),s=a("h3"),s.textContent="Motors Moving...",o=u(),i=a("div"),r=a("div"),b=u(),f=a("div"),m=_(e[0]),c=_(" / 60 ("),h=_(y),d=_("%)"),l(s,"class","svelte-7un2em"),l(r,"class","progress-fill svelte-7un2em"),oe(r,"width",e[6]+"%"),l(i,"class","progress-bar svelte-7un2em"),l(f,"class","progress-text svelte-7un2em"),l(n,"class","progress-section svelte-7un2em")},m(g,k){Xe(g,n,k),t(n,s),t(n,o),t(n,i),t(i,r),t(n,b),t(n,f),t(f,m),t(f,c),t(f,h),t(f,d)},p(g,k){k[0]&64&&oe(r,"width",g[6]+"%"),k[0]&1&&I(m,g[0]),k[0]&64&&y!==(y=g[6].toFixed(0)+"")&&I(h,y)},d(g){g&&qe(n)}}}function Yl(e){let n,s,o,i,r,b,f,m,c,y,h,d,g=(e[1]/1e3).toFixed(3)+"",k,C,p,w,T,v,O,U,G,D,A,ie,re,H=e[10][e[2]]+"",S,J,q,Z,Q,X,M,F,R,B,we,fe,ge,le,Y,te,K,ve,$,W,ne,_e,ye,Oe,pe,ke,ce,Te,Ce=it(e[11]),L=[];for(let P=0;P0&&rl(e);return{c(){n=a("div"),s=a("div"),o=a("h2"),o.textContent="Ultrabeam VL2.3",i=u(),r=a("span"),b=u(),f=a("div"),m=a("div"),c=a("div"),y=a("div"),y.textContent="Frequency",h=u(),d=a("div"),k=_(g),C=_(" MHz"),p=u(),w=a("div"),T=a("div"),T.textContent="Band",v=u(),O=a("div"),U=_(e[7]),G=u(),D=a("div"),A=a("div"),A.textContent="Direction",ie=u(),re=a("div"),S=_(H),J=u(),q=a("div"),Z=a("h3"),Z.textContent="Auto Tracking",Q=u(),X=a("div"),M=a("label"),F=a("input"),R=u(),B=a("span"),B.textContent="Enable Auto-Track from Tuner",we=u(),fe=a("div"),ge=a("label"),ge.textContent="Threshold:",le=u(),Y=a("select");for(let P=0;P↓ + Retract Elements`,l(o,"class","svelte-7un2em"),l(r,"class","status-dot svelte-7un2em"),N(r,"disconnected",!e[9]),l(s,"class","card-header svelte-7un2em"),l(y,"class","status-label svelte-7un2em"),l(d,"class","status-value freq svelte-7un2em"),l(c,"class","status-item svelte-7un2em"),l(T,"class","status-label svelte-7un2em"),l(O,"class","status-value band svelte-7un2em"),l(w,"class","status-item svelte-7un2em"),l(A,"class","status-label svelte-7un2em"),l(re,"class","status-value direction svelte-7un2em"),l(D,"class","status-item svelte-7un2em"),l(m,"class","status-grid svelte-7un2em"),l(Z,"class","svelte-7un2em"),l(F,"type","checkbox"),l(F,"class","svelte-7un2em"),l(M,"class","toggle-label svelte-7un2em"),l(ge,"for","threshold-select"),l(ge,"class","svelte-7un2em"),l(Y,"id","threshold-select"),l(Y,"class","svelte-7un2em"),e[4]===void 0&&jt(()=>e[19].call(Y)),l(fe,"class","threshold-group svelte-7un2em"),l(ve,"class","dir-btn normal svelte-7un2em"),N(ve,"active",e[5]===0),l(W,"class","dir-btn rotate180 svelte-7un2em"),N(W,"active",e[5]===1),l(_e,"class","dir-btn bidir svelte-7un2em"),N(_e,"active",e[5]===2),l(K,"class","direction-buttons svelte-7un2em"),l(X,"class","auto-track-controls svelte-7un2em"),l(q,"class","control-section compact svelte-7un2em"),l(ke,"class","btn-danger svelte-7un2em"),l(pe,"class","actions svelte-7un2em"),l(f,"class","metrics svelte-7un2em"),l(n,"class","card svelte-7un2em")},m(P,E){Xe(P,n,E),t(n,s),t(s,o),t(s,i),t(s,r),t(n,b),t(n,f),t(f,m),t(m,c),t(c,y),t(c,h),t(c,d),t(d,k),t(d,C),t(m,p),t(m,w),t(w,T),t(w,v),t(w,O),t(O,U),t(m,G),t(m,D),t(D,A),t(D,ie),t(D,re),t(re,S),t(f,J),t(f,q),t(q,Z),t(q,Q),t(q,X),t(X,M),t(M,F),F.checked=e[3],t(M,R),t(M,B),t(X,we),t(X,fe),t(fe,ge),t(fe,le),t(fe,Y);for(let V=0;V0?j?j.p(P,E):(j=rl(P),j.c(),j.m(f,Oe)):j&&(j.d(1),j=null)},i:Fe,o:Fe,d(P){P&&qe(n),Nt(L,P),j&&j.d(),ce=!1,et(Te)}}}function Vl(e,n,s){let o,i,r,b,f,m,c,y,h,{status:d}=n;const g=["6M","10M","12M","15M","17M","20M","30M","40M"];function k(S,J){return J>=0&&J<=7?g[J]:S>=7e3&&S<=7300?"40M":S>=10100&&S<=10150?"30M":S>=14e3&&S<=14350?"20M":S>=18068&&S<=18168?"17M":S>=21e3&&S<=21450?"15M":S>=24890&&S<=24990?"12M":S>=28e3&&S<=29700?"10M":S>=5e4&&S<=54e3?"6M":"Unknown"}const C=["Normal","180°","Bi-Dir"],p=[{value:25,label:"25 kHz"},{value:50,label:"50 kHz"},{value:100,label:"100 kHz"}];let w=!0,T=25,v=0;async function O(){if(i!==0)try{await Se.ultrabeam.setFrequency(i,v),await Se.ultrabeam.setDirection(v)}catch(S){console.log("Direction change sent (may show code 30 if busy):",S)}}async function U(){try{await Se.ultrabeam.setAutoTrack(w,T)}catch(S){console.error("Failed to update auto-track:",S),alert("Failed to update auto-track settings")}}async function G(){if(confirm("Retract all antenna elements?"))try{await Se.ultrabeam.retract()}catch(S){console.error("Failed to retract:",S),alert("Failed to retract")}}function D(){w=this.checked,s(3,w)}function A(){T=wl(this),s(4,T),s(11,p)}const ie=()=>{s(5,v=0),O()},re=()=>{s(5,v=1),O()},H=()=>{s(5,v=2),O()};return e.$$set=S=>{"status"in S&&s(15,d=S.status)},e.$$.update=()=>{e.$$.dirty[0]&32768&&s(9,o=(d==null?void 0:d.connected)||!1),e.$$.dirty[0]&32768&&s(1,i=(d==null?void 0:d.frequency)||0),e.$$.dirty[0]&32768&&s(17,r=(d==null?void 0:d.band)||0),e.$$.dirty[0]&32768&&s(2,b=(d==null?void 0:d.direction)||0),e.$$.dirty[0]&32768&&s(8,f=(d==null?void 0:d.motors_moving)||0),e.$$.dirty[0]&32768&&s(16,m=(d==null?void 0:d.progress_total)||0),e.$$.dirty[0]&32768&&s(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&&s(7,y=k(i,r)),e.$$.dirty[0]&4&&s(5,v=b),e.$$.dirty[0]&131072,e.$$.dirty[0]&65537&&s(6,h=m>0?c/60*100:0)},[c,i,b,w,T,v,h,y,f,o,C,p,O,U,G,d,m,r,D,A,ie,re,H]}class Kl extends pt{constructor(n){super(),ht(this,n,Vl,Yl,rt,{status:15},null,[-1,-1])}}function Zl(e){var st,at,mt,St,ee,ae;let n,s,o,i,r,b,f,m,c,y,h=e[1]?"Connected":"Disconnected",d,g,k,C,p,w,T,v=e[5].sfi+"",O,U,G,D,A,ie=e[5].sunspots+"",re,H,S,J,q,Z=e[5].a_index+"",Q,X,M,F,R,B=e[5].k_index+"",we,fe,ge,le,Y,te=e[5].geomag+"",K,ve,$,W,ne,_e,ye=e[4].wind_speed.toFixed(1)+"",Oe,pe,ke,ce,Te,Ce=e[4].wind_gust.toFixed(1)+"",L,j,P,E,V,Me=e[4].temp.toFixed(1)+"",Ue,ot,Ee,je,tt,Le=e[4].feels_like.toFixed(1)+"",Ye,Ie,Re,Ae,ze,Ze=cl(e[2])+"",Ne,Qe,Be,Ve=e[2].toLocaleDateString()+"",Ge,x,se,lt,He,Ke,Tt,De,ct,$e,dt,Je,xe,nt,We,Ct,Pe,de;return Ke=new Ll({props:{status:(st=e[0])==null?void 0:st.webswitch}}),De=new Il({props:{status:(at=e[0])==null?void 0:at.power_genius}}),$e=new Gl({props:{status:(mt=e[0])==null?void 0:mt.tuner_genius}}),xe=new ql({props:{status:(St=e[0])==null?void 0:St.antenna_genius}}),We=new Kl({props:{status:(ee=e[0])==null?void 0:ee.ultrabeam}}),Pe=new Xl({props:{status:(ae=e[0])==null?void 0:ae.rotator_genius}}),{c(){n=a("div"),s=a("header"),o=a("div"),i=a("h1"),r=_(e[3]),b=_(" Shack"),f=u(),m=a("div"),c=a("span"),y=u(),d=_(h),g=u(),k=a("div"),C=a("div"),p=a("span"),w=_("SFI "),T=a("span"),O=_(v),U=u(),G=a("span"),D=_("Spots "),A=a("span"),re=_(ie),H=u(),S=a("span"),J=_("A "),q=a("span"),Q=_(Z),X=u(),M=a("span"),F=_("K "),R=a("span"),we=_(B),fe=u(),ge=a("span"),le=_("G "),Y=a("span"),K=_(te),ve=u(),$=a("div"),W=a("div"),ne=a("span"),_e=_("🌬️ "),Oe=_(ye),pe=_("m/s"),ke=u(),ce=a("span"),Te=_("💨 "),L=_(Ce),j=_("m/s"),P=u(),E=a("span"),V=_("🌡️ "),Ue=_(Me),ot=_("°C"),Ee=u(),je=a("span"),tt=_("→ "),Ye=_(Le),Ie=_("°C"),Re=u(),Ae=a("div"),ze=a("span"),Ne=_(Ze),Qe=u(),Be=a("span"),Ge=_(Ve),x=u(),se=a("main"),lt=a("div"),He=a("div"),wt(Ke.$$.fragment),Tt=u(),wt(De.$$.fragment),ct=u(),wt($e.$$.fragment),dt=u(),Je=a("div"),wt(xe.$$.fragment),nt=u(),wt(We.$$.fragment),Ct=u(),wt(Pe.$$.fragment),l(i,"class","svelte-18f70jl"),l(c,"class","status-indicator"),N(c,"status-online",e[1]),N(c,"status-offline",!e[1]),l(m,"class","connection-status svelte-18f70jl"),l(o,"class","header-left svelte-18f70jl"),l(T,"class","value svelte-18f70jl"),l(p,"class","solar-item svelte-18f70jl"),l(A,"class","value svelte-18f70jl"),l(G,"class","solar-item svelte-18f70jl"),l(q,"class","value svelte-18f70jl"),l(S,"class","solar-item svelte-18f70jl"),l(R,"class","value svelte-18f70jl"),l(M,"class","solar-item svelte-18f70jl"),l(Y,"class","value svelte-18f70jl"),l(ge,"class","solar-item svelte-18f70jl"),l(C,"class","solar-info svelte-18f70jl"),l(k,"class","header-center svelte-18f70jl"),l(ne,"title","Wind"),l(ce,"title","Gust"),l(E,"title","Temperature"),l(je,"title","Feels like"),l(W,"class","weather-info svelte-18f70jl"),l(ze,"class","time svelte-18f70jl"),l(Be,"class","date svelte-18f70jl"),l(Ae,"class","clock svelte-18f70jl"),l($,"class","header-right svelte-18f70jl"),l(s,"class","svelte-18f70jl"),l(He,"class","row svelte-18f70jl"),l(Je,"class","row svelte-18f70jl"),l(lt,"class","dashboard-grid svelte-18f70jl"),l(se,"class","svelte-18f70jl"),l(n,"class","app svelte-18f70jl")},m(z,me){Xe(z,n,me),t(n,s),t(s,o),t(o,i),t(i,r),t(i,b),t(o,f),t(o,m),t(m,c),t(m,y),t(m,d),t(s,g),t(s,k),t(k,C),t(C,p),t(p,w),t(p,T),t(T,O),t(C,U),t(C,G),t(G,D),t(G,A),t(A,re),t(C,H),t(C,S),t(S,J),t(S,q),t(q,Q),t(C,X),t(C,M),t(M,F),t(M,R),t(R,we),t(C,fe),t(C,ge),t(ge,le),t(ge,Y),t(Y,K),t(s,ve),t(s,$),t($,W),t(W,ne),t(ne,_e),t(ne,Oe),t(ne,pe),t(W,ke),t(W,ce),t(ce,Te),t(ce,L),t(ce,j),t(W,P),t(W,E),t(E,V),t(E,Ue),t(E,ot),t(W,Ee),t(W,je),t(je,tt),t(je,Ye),t(je,Ie),t($,Re),t($,Ae),t(Ae,ze),t(ze,Ne),t(Ae,Qe),t(Ae,Be),t(Be,Ge),t(n,x),t(n,se),t(se,lt),t(lt,He),ft(Ke,He,null),t(He,Tt),ft(De,He,null),t(He,ct),ft($e,He,null),t(lt,dt),t(lt,Je),ft(xe,Je,null),t(Je,nt),ft(We,Je,null),t(Je,Ct),ft(Pe,Je,null),de=!0},p(z,[me]){var Ut,Jt,Xt,Yt,Vt,Kt;(!de||me&8)&&I(r,z[3]),(!de||me&2)&&N(c,"status-online",z[1]),(!de||me&2)&&N(c,"status-offline",!z[1]),(!de||me&2)&&h!==(h=z[1]?"Connected":"Disconnected")&&I(d,h),(!de||me&32)&&v!==(v=z[5].sfi+"")&&I(O,v),(!de||me&32)&&ie!==(ie=z[5].sunspots+"")&&I(re,ie),(!de||me&32)&&Z!==(Z=z[5].a_index+"")&&I(Q,Z),(!de||me&32)&&B!==(B=z[5].k_index+"")&&I(we,B),(!de||me&32)&&te!==(te=z[5].geomag+"")&&I(K,te),(!de||me&16)&&ye!==(ye=z[4].wind_speed.toFixed(1)+"")&&I(Oe,ye),(!de||me&16)&&Ce!==(Ce=z[4].wind_gust.toFixed(1)+"")&&I(L,Ce),(!de||me&16)&&Me!==(Me=z[4].temp.toFixed(1)+"")&&I(Ue,Me),(!de||me&16)&&Le!==(Le=z[4].feels_like.toFixed(1)+"")&&I(Ye,Le),(!de||me&4)&&Ze!==(Ze=cl(z[2])+"")&&I(Ne,Ze),(!de||me&4)&&Ve!==(Ve=z[2].toLocaleDateString()+"")&&I(Ge,Ve);const Rt={};me&1&&(Rt.status=(Ut=z[0])==null?void 0:Ut.webswitch),Ke.$set(Rt);const zt={};me&1&&(zt.status=(Jt=z[0])==null?void 0:Jt.power_genius),De.$set(zt);const Gt={};me&1&&(Gt.status=(Xt=z[0])==null?void 0:Xt.tuner_genius),$e.$set(Gt);const Ht={};me&1&&(Ht.status=(Yt=z[0])==null?void 0:Yt.antenna_genius),xe.$set(Ht);const Wt={};me&1&&(Wt.status=(Vt=z[0])==null?void 0:Vt.ultrabeam),We.$set(Wt);const qt={};me&1&&(qt.status=(Kt=z[0])==null?void 0:Kt.rotator_genius),Pe.$set(qt)},i(z){de||(ut(Ke.$$.fragment,z),ut(De.$$.fragment,z),ut($e.$$.fragment,z),ut(xe.$$.fragment,z),ut(We.$$.fragment,z),ut(Pe.$$.fragment,z),de=!0)},o(z){gt(Ke.$$.fragment,z),gt(De.$$.fragment,z),gt($e.$$.fragment,z),gt(xe.$$.fragment,z),gt(We.$$.fragment,z),gt(Pe.$$.fragment,z),de=!1},d(z){z&&qe(n),vt(Ke),vt(De),vt($e),vt(xe),vt(We),vt(Pe)}}}function cl(e){return e.toTimeString().slice(0,8)}function Ql(e,n,s){let o,i,r=null,b=!1,f=new Date,m="F4BPO";const c=hl.subscribe(h=>{s(0,r=h)}),y=Dt.subscribe(h=>{s(1,b=h)});return _l(async()=>{xt.connect();try{const d=await Se.getConfig();d.callsign&&s(3,m=d.callsign)}catch(d){console.error("Failed to fetch config:",d)}const h=setInterval(()=>{s(2,f=new Date)},1e3);return()=>{clearInterval(h)}}),yl(()=>{xt.disconnect(),c(),y()}),e.$$.update=()=>{e.$$.dirty&1&&s(5,o=(r==null?void 0:r.solar)||{sfi:0,sunspots:0,a_index:0,k_index:0,geomag:"Unknown"}),e.$$.dirty&1&&s(4,i=(r==null?void 0:r.weather)||{wind_speed:0,wind_gust:0,temp:0,feels_like:0})},[r,b,f,m,i,o]}class $l extends pt{constructor(n){super(),ht(this,n,Ql,Zl,rt,{})}}new $l({target:document.getElementById("app")}); diff --git a/cmd/server/web/dist/assets/index-dhCTx3KU.css b/cmd/server/web/dist/assets/index-dhCTx3KU.css new file mode 100644 index 0000000..e75b9f0 --- /dev/null +++ b/cmd/server/web/dist/assets/index-dhCTx3KU.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-1h3han9{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-1h3han9{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-1h3han9{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.header-right.svelte-1h3han9{display:flex;align-items:center;gap:8px}.state-badge.svelte-1h3han9{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-1h3han9{background:#4caf5033;color:#4caf50}.state-badge.transmit.svelte-1h3han9{background:#ff980033;color:#ff9800;animation:svelte-1h3han9-pulse 1s infinite}@keyframes svelte-1h3han9-pulse{0%,to{opacity:1}50%{opacity:.7}}.status-dot.svelte-1h3han9{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-1h3han9{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-1h3han9{padding:16px;display:flex;flex-direction:column;gap:10px}.power-swr-row.svelte-1h3han9{display:flex;gap:16px;align-items:center}.power-section.svelte-1h3han9{flex:1;background:#0f172a99;padding:12px;border-radius:10px;border:1px solid rgba(79,195,247,.2)}.power-header.svelte-1h3han9{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.power-label-inline.svelte-1h3han9{font-size:12px;color:#ffffffb3;text-transform:uppercase;letter-spacing:.5px}.power-value-inline.svelte-1h3han9{font-size:22px;font-weight:600;color:#66bb6a}.power-bar-container.svelte-1h3han9{position:relative}.power-bar-bg.svelte-1h3han9{width:100%;height:28px;background:#0000004d;border-radius:14px;overflow:hidden;position:relative}.power-bar-fill.svelte-1h3han9{position:relative;height:100%;background:linear-gradient(90deg,#4caf50,#ffc107,#ff9800,#f44336);border-radius:14px;transition:width .3s ease}.power-bar-glow.svelte-1h3han9{position:absolute;top:0;right:0;width:20px;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.5));animation:svelte-1h3han9-shimmer 2s infinite}@keyframes svelte-1h3han9-shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.swr-circle-compact.svelte-1h3han9{width:90px;height:90px;border-radius:50%;background:radial-gradient(circle,rgba(79,195,247,.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.svelte-1h3han9{font-size:28px;font-weight:700;color:var(--swr-color)}.swr-label-compact.svelte-1h3han9{font-size:11px;color:#fff9;text-transform:uppercase;margin-top:2px}.temp-group.svelte-1h3han9{display:grid;grid-template-columns:1fr 1fr;gap:12px}.temp-item.svelte-1h3han9{display:flex;flex-direction:column;gap:4px;padding:12px;background:var(--bg-tertiary);border-radius:6px}.temp-value.svelte-1h3han9{font-size:32px;font-weight:300;line-height:1}.temp-label.svelte-1h3han9{font-size:10px;color:var(--text-muted);text-transform:uppercase}.temp-mini-bar.svelte-1h3han9{height:4px;background:#ffffff1a;border-radius:2px;overflow:hidden;margin-top:4px}.temp-mini-fill.svelte-1h3han9{height:100%;border-radius:2px;transition:width .3s ease}.params-grid.svelte-1h3han9{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.param-box.svelte-1h3han9{padding:8px;background:var(--bg-tertiary);border-radius:4px;text-align:center}.param-label.svelte-1h3han9{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.param-value.svelte-1h3han9{font-size:16px;font-weight:300;color:var(--text-primary);margin-top:2px}.band-display.svelte-1h3han9{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:8px;background:#4fc3f70d;border-radius:6px}.band-item.svelte-1h3han9{display:flex;justify-content:space-between;align-items:center}.band-label.svelte-1h3han9{font-size:11px;color:var(--text-muted)}.band-value.svelte-1h3han9{font-size:14px;font-weight:600;color:var(--accent-cyan)}.fan-control.svelte-1h3han9{display:flex;flex-direction:column;gap:6px}.control-label.svelte-1h3han9{font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}select.svelte-1h3han9{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-1h3han9:hover{border-color:var(--accent-cyan)}select.svelte-1h3han9:focus{border-color:var(--accent-cyan);box-shadow:0 0 0 2px #4fc3f733}.card.svelte-s5par0{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-s5par0{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-s5par0{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.header-right.svelte-s5par0{display:flex;align-items:center;gap:8px}.tuning-badge.svelte-s5par0{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-s5par0{background:#ff980033;color:#ff9800;animation:svelte-s5par0-pulse 1s infinite}@keyframes svelte-s5par0-pulse{0%,to{opacity:1}50%{opacity:.7}}.status-dot.svelte-s5par0{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-s5par0{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-s5par0{padding:16px;display:flex;flex-direction:column;gap:10px}.power-swr-row.svelte-s5par0{display:flex;gap:16px;align-items:center}.power-section.svelte-s5par0{flex:1;background:#0f172a99;padding:12px;border-radius:10px;border:1px solid rgba(79,195,247,.2)}.power-header.svelte-s5par0{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.power-label-inline.svelte-s5par0{font-size:12px;color:#ffffffb3;text-transform:uppercase;letter-spacing:.5px}.power-value-inline.svelte-s5par0{font-size:22px;font-weight:600;color:#66bb6a}.power-bar-container.svelte-s5par0{position:relative}.power-bar-bg.svelte-s5par0{width:100%;height:28px;background:#0000004d;border-radius:14px;overflow:hidden;position:relative}.power-bar-fill.svelte-s5par0{position:relative;height:100%;background:linear-gradient(90deg,#4caf50,#ffc107,#ff9800,#f44336);border-radius:14px;transition:width .3s ease}.power-bar-glow.svelte-s5par0{position:absolute;top:0;right:0;width:20px;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.5));animation:svelte-s5par0-shimmer 2s infinite}@keyframes svelte-s5par0-shimmer{0%{transform:translate(-100%)}to{transform:translate(100%)}}.swr-circle-compact.svelte-s5par0{width:90px;height:90px;border-radius:50%;background:radial-gradient(circle,rgba(79,195,247,.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.svelte-s5par0{font-size:28px;font-weight:700;color:var(--swr-color)}.swr-label-compact.svelte-s5par0{font-size:11px;color:#fff9;text-transform:uppercase;margin-top:2px}.capacitors.svelte-s5par0{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-s5par0{display:flex;flex-direction:column;align-items:center;gap:4px}.cap-value.svelte-s5par0{font-size:20px;font-weight:300;color:var(--accent-cyan);text-shadow:0 0 15px rgba(79,195,247,.5)}.cap-label.svelte-s5par0{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:1px}.freq-display.svelte-s5par0{display:grid;grid-template-columns:1fr 1fr;gap:12px}.freq-item.svelte-s5par0{padding:10px;background:var(--bg-tertiary);border-radius:6px;display:flex;flex-direction:column;gap:4px}.freq-label.svelte-s5par0{font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.freq-value.svelte-s5par0{font-size:16px;font-weight:300;color:var(--text-primary)}.freq-unit.svelte-s5par0{font-size:11px;color:var(--text-secondary);margin-left:2px}.controls.svelte-s5par0{display:grid;grid-template-columns:1fr 1fr;gap:8px}.control-btn.svelte-s5par0{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-s5par0:hover{border-color:var(--accent-cyan);transform:translateY(-1px)}.control-btn.active.svelte-s5par0{background:var(--accent-cyan);border-color:var(--accent-cyan);color:#000;box-shadow:0 0 15px #4fc3f780}.tune-btn.svelte-s5par0{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-s5par0:hover{transform:translateY(-2px);box-shadow:0 6px 16px #f4433680}.tune-btn.svelte-s5par0:active{transform:translateY(0)}.tune-icon.svelte-s5par0{font-size:16px}.card.svelte-vlabwb{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-vlabwb{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#4fc3f70d;border-bottom:1px solid #2d3748}h2.svelte-vlabwb{font-size:14px;font-weight:600;color:var(--accent-cyan);margin:0;letter-spacing:.5px}.status-dot.svelte-vlabwb{width:8px;height:8px;border-radius:50%;background:#4caf50;box-shadow:0 0 8px #4caf50}.status-dot.disconnected.svelte-vlabwb{background:#f44336;box-shadow:0 0 8px #f44336}.metrics.svelte-vlabwb{padding:16px;display:flex;flex-direction:column;gap:12px}.sources.svelte-vlabwb{display:grid;grid-template-columns:1fr 1fr;gap:8px}.source-item.svelte-vlabwb{padding:8px;background:var(--bg-tertiary);border-radius:4px;text-align:center}.source-label.svelte-vlabwb{font-size:12px;font-weight:600;color:var(--text-primary);text-transform:uppercase}.bands.svelte-vlabwb{display:grid;grid-template-columns:1fr 1fr;gap:8px}.band-item.svelte-vlabwb{padding:10px;background:#4fc3f71a;border:1px solid rgba(79,195,247,.3);border-radius:4px;text-align:center}.band-value.svelte-vlabwb{font-size:16px;font-weight:600;color:var(--accent-cyan)}.antennas.svelte-vlabwb{display:flex;flex-direction:column;gap:8px}.antenna-card.svelte-vlabwb{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.active-a.svelte-vlabwb{background:#4caf5033;border-color:#4caf50;box-shadow:0 0 20px #4caf504d}.antenna-card.active-b.svelte-vlabwb{background:#2196f333;border-color:#2196f3;box-shadow:0 0 20px #2196f34d}.antenna-card.tx.svelte-vlabwb{background:#f4433633!important;border-color:#f44336!important;box-shadow:0 0 20px #f4433666!important}.antenna-name.svelte-vlabwb{font-size:14px;font-weight:500;color:var(--text-primary)}.antenna-ports.svelte-vlabwb{display:flex;gap:6px}.port-btn.svelte-vlabwb{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-vlabwb:hover{border-color:var(--accent-cyan);transform:scale(1.05)}.port-btn.active.svelte-vlabwb{background:var(--accent-cyan);border-color:var(--accent-cyan);color:#000;box-shadow:0 0 12px #4fc3f780}.reboot-btn.svelte-vlabwb{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-vlabwb:hover{transform:translateY(-2px);box-shadow:0 6px 16px #ff980080}.reboot-btn.svelte-vlabwb:active{transform:translateY(0)}.reboot-icon.svelte-vlabwb{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-7un2em.svelte-7un2em{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-7un2em.svelte-7un2em{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-7un2em.svelte-7un2em{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-7un2em.svelte-7un2em{margin:0 0 12px;font-size:16px;font-weight:500;color:#4fc3f7}.status-dot.svelte-7un2em.svelte-7un2em{width:12px;height:12px;border-radius:50%;background:#4caf50;box-shadow:0 0 12px #4caf50cc;animation:svelte-7un2em-pulse 2s ease-in-out infinite}.status-dot.disconnected.svelte-7un2em.svelte-7un2em{background:#666;box-shadow:none;animation:none}@keyframes svelte-7un2em-pulse{0%,to{opacity:1}50%{opacity:.6}}.metrics.svelte-7un2em.svelte-7un2em{display:flex;flex-direction:column;gap:12px}.status-grid.svelte-7un2em.svelte-7un2em{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}.status-item.svelte-7un2em.svelte-7un2em{background:#0f172a99;padding:16px;border-radius:12px;border:1px solid rgba(79,195,247,.2)}.status-label.svelte-7un2em.svelte-7un2em{font-size:12px;color:#fff9;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px}.status-value.svelte-7un2em.svelte-7un2em{font-size:22px;font-weight:700;color:#4fc3f7;text-shadow:0 0 10px rgba(79,195,247,.5)}.status-value.freq.svelte-7un2em.svelte-7un2em{color:#66bb6a;font-size:22px;text-shadow:0 0 10px rgba(102,187,106,.5)}.status-value.band.svelte-7un2em.svelte-7un2em{color:#ffa726;text-shadow:0 0 10px rgba(255,167,38,.5)}.status-value.direction.svelte-7un2em.svelte-7un2em{color:#ab47bc;text-shadow:0 0 10px rgba(171,71,188,.5)}.control-section.svelte-7un2em.svelte-7un2em{background:#0f172a66;padding:12px;border-radius:12px;border:1px solid rgba(79,195,247,.2)}.control-section.compact.svelte-7un2em.svelte-7un2em{padding:16px}.auto-track-controls.svelte-7un2em.svelte-7un2em{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.toggle-label.svelte-7un2em.svelte-7un2em{display:flex;align-items:center;gap:8px;cursor:pointer;color:#fff;font-size:14px}.toggle-label.svelte-7un2em input[type=checkbox].svelte-7un2em{width:20px;height:20px;cursor:pointer}.threshold-group.svelte-7un2em.svelte-7un2em{display:flex;align-items:center;gap:8px}.threshold-group.svelte-7un2em label.svelte-7un2em{font-size:14px;color:#fffc;white-space:nowrap}.direction-buttons.svelte-7un2em.svelte-7un2em{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:12px}.dir-btn.svelte-7un2em.svelte-7un2em{padding:14px 20px;border:none;border-radius:10px;font-size:14px;font-weight:700;text-transform:uppercase;cursor:pointer;transition:all .3s;color:#fff;letter-spacing:.5px;box-shadow:0 4px 12px #0003}.dir-btn.normal.svelte-7un2em.svelte-7un2em{background:linear-gradient(135deg,#667eea,#764ba2)}.dir-btn.normal.svelte-7un2em.svelte-7un2em:hover{transform:translateY(-2px);box-shadow:0 6px 20px #667eea80}.dir-btn.normal.active.svelte-7un2em.svelte-7un2em{background:linear-gradient(135deg,#667eea,#764ba2);box-shadow:0 0 25px #667eeacc,0 6px 20px #667eea80;transform:translateY(-2px)}.dir-btn.rotate180.svelte-7un2em.svelte-7un2em{background:linear-gradient(135deg,#f093fb,#f5576c)}.dir-btn.rotate180.svelte-7un2em.svelte-7un2em:hover{transform:translateY(-2px);box-shadow:0 6px 20px #f5576c80}.dir-btn.rotate180.active.svelte-7un2em.svelte-7un2em{background:linear-gradient(135deg,#f093fb,#f5576c);box-shadow:0 0 25px #f5576ccc,0 6px 20px #f5576c80;transform:translateY(-2px)}.dir-btn.bidir.svelte-7un2em.svelte-7un2em{background:linear-gradient(135deg,#4facfe,#00f2fe)}.dir-btn.bidir.svelte-7un2em.svelte-7un2em:hover{transform:translateY(-2px);box-shadow:0 6px 20px #4facfe80}.dir-btn.bidir.active.svelte-7un2em.svelte-7un2em{background:linear-gradient(135deg,#4facfe,#00f2fe);box-shadow:0 0 25px #4facfecc,0 6px 20px #4facfe80;transform:translateY(-2px)}select.svelte-7un2em.svelte-7un2em{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-7un2em.svelte-7un2em:focus{outline:none;border-color:#4fc3f7;box-shadow:0 0 12px #4fc3f74d}button.svelte-7un2em.svelte-7un2em{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-7un2em.svelte-7un2em{background:linear-gradient(135deg,#f44336,#d32f2f);color:#fff;box-shadow:0 4px 16px #f4433666;width:100%}.btn-danger.svelte-7un2em.svelte-7un2em:hover{transform:translateY(-2px);box-shadow:0 6px 20px #f4433699}.icon.svelte-7un2em.svelte-7un2em{font-size:16px}.progress-section.svelte-7un2em.svelte-7un2em{background:#0f172a66;padding:12px;border-radius:12px;border:1px solid rgba(255,193,7,.3)}.progress-bar.svelte-7un2em.svelte-7un2em{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-7un2em.svelte-7un2em{height:100%;background:linear-gradient(90deg,#4fc3f7,#66bb6a);transition:width .3s ease;box-shadow:0 0 12px #4fc3f799}.progress-text.svelte-7un2em.svelte-7un2em{text-align:center;color:#4fc3f7;font-weight:600}.actions.svelte-7un2em.svelte-7un2em{display:flex;gap:12px}.app.svelte-18f70jl.svelte-18f70jl{min-height:100vh;display:flex;flex-direction:column}header.svelte-18f70jl.svelte-18f70jl{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-18f70jl.svelte-18f70jl{display:flex;align-items:center;gap:16px}h1.svelte-18f70jl.svelte-18f70jl{font-size:24px;font-weight:500;margin:0;color:#fff}.connection-status.svelte-18f70jl.svelte-18f70jl{display:flex;align-items:center;gap:8px;font-size:14px;padding:6px 12px;background:#0000004d;border-radius:16px}.header-center.svelte-18f70jl.svelte-18f70jl{flex:1;display:flex;justify-content:center}.solar-info.svelte-18f70jl.svelte-18f70jl{display:flex;gap:20px;font-size:14px}.solar-item.svelte-18f70jl.svelte-18f70jl{color:#ffffffe6;font-size:13px;font-weight:600;letter-spacing:.3px}.solar-item.svelte-18f70jl .value.svelte-18f70jl{font-weight:700;margin-left:4px;font-size:14px}.solar-item.svelte-18f70jl:nth-child(1) .value.svelte-18f70jl{color:#ffa726;text-shadow:0 0 8px rgba(255,167,38,.5)}.solar-item.svelte-18f70jl:nth-child(2) .value.svelte-18f70jl{color:#66bb6a;text-shadow:0 0 8px rgba(102,187,106,.5)}.solar-item.svelte-18f70jl:nth-child(3) .value.svelte-18f70jl{color:#42a5f5;text-shadow:0 0 8px rgba(66,165,245,.5)}.solar-item.svelte-18f70jl:nth-child(4) .value.svelte-18f70jl{color:#ef5350;text-shadow:0 0 8px rgba(239,83,80,.5)}.solar-item.svelte-18f70jl:nth-child(5) .value.svelte-18f70jl{color:#ab47bc;text-shadow:0 0 8px rgba(171,71,188,.5)}.header-right.svelte-18f70jl.svelte-18f70jl{display:flex;gap:20px;align-items:center}.weather-info.svelte-18f70jl.svelte-18f70jl{display:flex;gap:12px;font-size:14px;color:#ffffffe6}.clock.svelte-18f70jl.svelte-18f70jl{display:flex;flex-direction:column;align-items:flex-end}.time.svelte-18f70jl.svelte-18f70jl{font-size:18px;font-weight:500;color:#fff}.date.svelte-18f70jl.svelte-18f70jl{font-size:12px;color:#ffffffb3}main.svelte-18f70jl.svelte-18f70jl{flex:1;padding:24px;overflow-y:auto}.dashboard-grid.svelte-18f70jl.svelte-18f70jl{display:flex;flex-direction:column;gap:24px;max-width:1800px;margin:0 auto}.row.svelte-18f70jl.svelte-18f70jl{display:flex;gap:24px;flex-wrap:wrap}.row.svelte-18f70jl>*{flex:1;min-width:300px}@media (max-width: 1200px){.row.svelte-18f70jl.svelte-18f70jl{flex-direction:column}}@media (max-width: 768px){header.svelte-18f70jl.svelte-18f70jl{flex-direction:column;align-items:flex-start}.header-center.svelte-18f70jl.svelte-18f70jl,.header-right.svelte-18f70jl.svelte-18f70jl{width:100%;justify-content:flex-start}.solar-info.svelte-18f70jl.svelte-18f70jl{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 7756170..dedab0e 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 35d31a3..a3095ed 100644 --- a/internal/api/device_manager.go +++ b/internal/api/device_manager.go @@ -249,7 +249,6 @@ func (dm *DeviceManager) updateStatus() { // This prevents auto-track from using wrong direction before user changes it if !dm.ultrabeamDirectionSet { dm.ultrabeamDirection = ubStatus.Direction - log.Printf("Auto-track: Initialized direction from Ultrabeam: %d", dm.ultrabeamDirection) } } else { log.Printf("Ultrabeam error: %v", err) @@ -260,50 +259,45 @@ func (dm *DeviceManager) updateStatus() { 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 user's explicitly set direction, or fallback to current Ultrabeam direction - directionToUse := dm.ultrabeamDirection - if !dm.ultrabeamDirectionSet && status.Ultrabeam.Direction != 0 { - directionToUse = status.Ultrabeam.Direction + // Only do auto-track if frequency is in Ultrabeam range (40M-6M: 7000-54000 kHz) + // This prevents retraction when slice is closed (FreqA becomes 0) or on out-of-range bands + if tunerFreqKhz >= 7000 && tunerFreqKhz <= 54000 { + freqDiff := tunerFreqKhz - ultrabeamFreqKhz + if freqDiff < 0 { + freqDiff = -freqDiff } - // 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) + // Convert diff to Hz for comparison with threshold (which is in Hz) + freqDiffHz := freqDiff * 1000 - // 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 + // Don't send command if motors are already moving + if status.Ultrabeam.MotorsMoving == 0 { + if freqDiffHz >= dm.freqThreshold { + // Use user's explicitly set direction, or fallback to current Ultrabeam direction + directionToUse := dm.ultrabeamDirection + if !dm.ultrabeamDirectionSet && 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 + } + } } } } + // If out of range, simply skip auto-track but continue with status broadcast } // Solar Data (fetched every 15 minutes, cached) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 4ef1134..08a4aee 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -58,6 +58,7 @@ func (s *Server) SetupRoutes() *http.ServeMux { mux.HandleFunc("/api/ultrabeam/frequency", s.handleUltrabeamFrequency) mux.HandleFunc("/api/ultrabeam/retract", s.handleUltrabeamRetract) mux.HandleFunc("/api/ultrabeam/autotrack", s.handleUltrabeamAutoTrack) + mux.HandleFunc("/api/ultrabeam/direction", s.handleUltrabeamDirection) // Tuner endpoints mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate) @@ -66,6 +67,7 @@ func (s *Server) SetupRoutes() *http.ServeMux { // Antenna Genius endpoints mux.HandleFunc("/api/antenna/select", s.handleAntennaSelect) + mux.HandleFunc("/api/antenna/deselect", s.handleAntennaDeselect) mux.HandleFunc("/api/antenna/reboot", s.handleAntennaReboot) // Power Genius endpoints @@ -336,6 +338,33 @@ func (s *Server) handleAntennaSelect(w http.ResponseWriter, r *http.Request) { s.sendJSON(w, map[string]string{"status": "ok"}) } +func (s *Server) handleAntennaDeselect(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + Port int `json:"port"` + Antenna int `json:"antenna"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + log.Printf("Deselecting antenna %d from port %d", req.Antenna, req.Port) + if err := s.deviceManager.AntennaGenius().DeselectAntenna(req.Port, req.Antenna); err != nil { + log.Printf("Failed to deselect antenna: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + log.Printf("Successfully deselected antenna %d from port %d", req.Antenna, req.Port) + + s.sendJSON(w, map[string]string{"status": "ok"}) +} + func (s *Server) handleAntennaReboot(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -414,6 +443,9 @@ func (s *Server) handleUltrabeamFrequency(w http.ResponseWriter, r *http.Request return } + // Save direction for auto-track to use + s.deviceManager.SetUltrabeamDirection(req.Direction) + if err := s.deviceManager.Ultrabeam().SetFrequency(req.Frequency, req.Direction); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -457,6 +489,27 @@ func (s *Server) handleUltrabeamAutoTrack(w http.ResponseWriter, r *http.Request s.sendJSON(w, map[string]string{"status": "ok"}) } +func (s *Server) handleUltrabeamDirection(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + Direction int `json:"direction"` // 0=normal, 1=180°, 2=bi-dir + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // Just save the direction preference for auto-track to use + s.deviceManager.SetUltrabeamDirection(req.Direction) + + 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/antennagenius/antennagenius.go b/internal/devices/antennagenius/antennagenius.go index a650c9f..ffac5ed 100644 --- a/internal/devices/antennagenius/antennagenius.go +++ b/internal/devices/antennagenius/antennagenius.go @@ -178,21 +178,22 @@ func (c *Client) pollLoop() { func (c *Client) initialize() error { // Get antenna list + log.Println("AntennaGenius: Getting antenna list...") antennas, err := c.getAntennaList() if err != nil { return fmt.Errorf("failed to get antenna list: %w", err) } + log.Printf("AntennaGenius: Found %d antennas", len(antennas)) + for i, ant := range antennas { + log.Printf("AntennaGenius: Antenna %d: number=%d, name=%s", i, ant.Number, ant.Name) + } + c.antennasMu.Lock() c.antennas = antennas c.antennasMu.Unlock() - // Subscribe to port updates - if err := c.subscribeToPortUpdates(); err != nil { - return fmt.Errorf("failed to subscribe: %w", err) - } - - // Initialize status + // Initialize status BEFORE subscribing so parsePortStatus can update it c.statusMu.Lock() c.lastStatus = &Status{ PortA: &PortStatus{}, @@ -202,6 +203,23 @@ func (c *Client) initialize() error { } c.statusMu.Unlock() + log.Println("AntennaGenius: Status initialized, now subscribing to port updates...") + + // Subscribe to port updates (this will parse and update port status) + if err := c.subscribeToPortUpdates(); err != nil { + return fmt.Errorf("failed to subscribe: %w", err) + } + + // Request initial status for both ports + log.Println("AntennaGenius: Requesting additional port status...") + _, _ = c.sendCommand("port get 1") // Port A + _, _ = c.sendCommand("port get 2") // Port B + + c.statusMu.RLock() + log.Printf("AntennaGenius: Initialization complete - PortA.RxAnt=%d, PortB.RxAnt=%d", + c.lastStatus.PortA.RxAnt, c.lastStatus.PortB.RxAnt) + c.statusMu.RUnlock() + return nil } @@ -334,6 +352,7 @@ func (c *Client) parseAntennaLine(line string) Antenna { func (c *Client) subscribeToPortUpdates() error { resp, err := c.sendCommand("sub port all") if err != nil { + log.Printf("AntennaGenius: Failed to subscribe: %v", err) return err } @@ -347,6 +366,7 @@ func (c *Client) subscribeToPortUpdates() error { } } + log.Println("AntennaGenius: Subscription complete") return nil } @@ -437,6 +457,20 @@ func (c *Client) SetAntenna(port, antenna int) error { return err } +// DeselectAntenna deselects an antenna from a port (sets rxant=00) +// Command format: "C1|port set rxant=00" +func (c *Client) DeselectAntenna(port, antenna int) error { + cmd := fmt.Sprintf("port set %d rxant=00", port) + log.Printf("AntennaGenius: Sending deselect command: %s", cmd) + resp, err := c.sendCommand(cmd) + if err != nil { + log.Printf("AntennaGenius: Deselect failed: %v", err) + return err + } + log.Printf("AntennaGenius: Deselect response: %s", resp) + return nil +} + // Reboot reboots the device func (c *Client) Reboot() error { _, err := c.sendCommand("reboot") diff --git a/web/src/components/AntennaGenius.svelte b/web/src/components/AntennaGenius.svelte index 1d7d596..f6f055b 100644 --- a/web/src/components/AntennaGenius.svelte +++ b/web/src/components/AntennaGenius.svelte @@ -20,22 +20,49 @@ async function selectAntenna(port, antennaNum) { try { - await api.antenna.selectAntenna(port, antennaNum); + // Check if antenna is already selected on this port + const isAlreadySelected = (port === 1 && portA.rx_ant === antennaNum) || + (port === 2 && portB.rx_ant === antennaNum); + + if (isAlreadySelected) { + // Deselect: set rxant to 00 + console.log(`Deselecting antenna ${antennaNum} from port ${port}`); + await api.antenna.deselectAntenna(port, antennaNum); + } else { + // Select normally + console.log(`Selecting antenna ${antennaNum} on port ${port}`); + await api.antenna.selectAntenna(port, antennaNum); + } } catch (err) { - console.error('Failed to select antenna:', err); - alert('Failed to select antenna'); + console.error('Failed to select/deselect antenna:', err); + // No popup, just log the error } } + // Debug TX state - only log when tx state changes, not on every update + let lastTxStateA = false; + let lastTxStateB = false; + $: if (status && (portA.tx !== lastTxStateA || portB.tx !== lastTxStateB)) { + console.log('AntennaGenius TX state changed:', { + portA_tx: portA.tx, + portB_tx: portB.tx, + portA_tx_ant: portA.tx_ant, + portB_tx_ant: portB.tx_ant + }); + lastTxStateA = portA.tx; + lastTxStateB = portB.tx; + } + async function reboot() { if (!confirm('Are you sure you want to reboot the Antenna Genius?')) { return; } try { await api.antenna.reboot(); + console.log('Antenna Genius reboot command sent'); } catch (err) { console.error('Failed to reboot:', err); - alert('Failed to reboot'); + // No popup, just log } } @@ -220,12 +247,6 @@ transition: all 0.3s; } - .antenna-card.tx { - background: rgba(244, 67, 54, 0.2); - border-color: #f44336; - box-shadow: 0 0 20px rgba(244, 67, 54, 0.4); - } - .antenna-card.active-a { background: rgba(76, 175, 80, 0.2); border-color: #4caf50; @@ -238,6 +259,13 @@ box-shadow: 0 0 20px rgba(33, 150, 243, 0.3); } + /* TX must come AFTER active-a/active-b to override */ + .antenna-card.tx { + background: rgba(244, 67, 54, 0.2) !important; + border-color: #f44336 !important; + box-shadow: 0 0 20px rgba(244, 67, 54, 0.4) !important; + } + .antenna-name { font-size: 14px; font-weight: 500; diff --git a/web/src/components/PowerGenius.svelte b/web/src/components/PowerGenius.svelte index a8d75fa..1269fa5 100644 --- a/web/src/components/PowerGenius.svelte +++ b/web/src/components/PowerGenius.svelte @@ -6,6 +6,16 @@ $: powerForward = status?.power_forward || 0; $: powerReflected = status?.power_reflected || 0; $: swr = status?.swr || 1.0; + + // Debug logging + $: if (status) { + console.log('PowerGenius status update:', { + powerForward: status.power_forward, + swr: status.swr, + state: status.state, + connected: status.connected + }); + } $: voltage = status?.voltage || 0; $: vdd = status?.vdd || 0; $: current = status?.current || 0; @@ -30,7 +40,7 @@ await api.power.setFanMode(mode); } catch (err) { console.error('Failed to set fan mode:', err); - alert('Failed to set fan mode'); + // Removed alert popup - check console for errors } } @@ -40,7 +50,7 @@ await api.power.setOperate(operateValue); } catch (err) { console.error('Failed to toggle operate:', err); - alert('Failed to toggle operate mode'); + // Removed alert popup - check console for errors } } @@ -323,125 +333,6 @@ margin-top: 2px; } - .power-display { - display: flex; - flex-direction: column; - gap: 8px; - } - - .power-main { - text-align: center; - } - - .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-value .unit { - font-size: 20px; - color: var(--text-secondary); - margin-left: 4px; - } - - .power-label { - font-size: 11px; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 1px; - margin-top: 4px; - } - - .power-bar { - position: relative; - height: 8px; - background: var(--bg-tertiary); - border-radius: 4px; - overflow: hidden; - } - - .power-bar-fill { - position: relative; - height: 100%; - background: linear-gradient(90deg, #4caf50, #ffc107, #ff9800, #f44336); - border-radius: 4px; - 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%); } - } - - .power-scale { - display: flex; - justify-content: space-between; - font-size: 9px; - color: var(--text-muted); - margin-top: 4px; - } - - /* SWR Circle */ - .swr-container { - display: flex; - align-items: center; - gap: 10px; - } - - .swr-circle { - width: 80px; - height: 80px; - border-radius: 50%; - background: radial-gradient(circle, rgba(79, 195, 247, 0.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 { - font-size: 20px; - font-weight: 300; - color: var(--swr-color); - } - - .swr-label { - font-size: 10px; - color: var(--text-muted); - text-transform: uppercase; - } - - .swr-status { - flex: 1; - } - - .status-text { - font-size: 14px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .status-text.good { color: #4caf50; } - .status-text.ok { color: #ffc107; } - .status-text.warning { color: #ff9800; } - .status-text.danger { color: #f44336; } - /* Temperature */ .temp-group { display: grid; diff --git a/web/src/components/RotatorGenius.svelte b/web/src/components/RotatorGenius.svelte index bef2348..27c5917 100644 --- a/web/src/components/RotatorGenius.svelte +++ b/web/src/components/RotatorGenius.svelte @@ -359,22 +359,6 @@ gap: 8px; } - .heading-input { - background: var(--bg-tertiary); - color: var(--text-primary); - border: 1px solid var(--border-color); - border-radius: 4px; - padding: 10px 12px; - font-size: 14px; - outline: none; - transition: all 0.2s; - } - - .heading-input:focus { - border-color: var(--accent-cyan); - box-shadow: 0 0 0 2px rgba(79, 195, 247, 0.2); - } - .go-btn { padding: 10px 24px; border-radius: 4px; @@ -434,8 +418,4 @@ box-shadow: 0 6px 16px rgba(244, 67, 54, 0.5); } - .arrow { - font-size: 20px; - line-height: 1; - } diff --git a/web/src/components/TunerGenius.svelte b/web/src/components/TunerGenius.svelte index 6c16322..5dbf8d8 100644 --- a/web/src/components/TunerGenius.svelte +++ b/web/src/components/TunerGenius.svelte @@ -24,7 +24,7 @@ await api.tuner.autoTune(); } catch (err) { console.error('Failed to tune:', err); - alert('Failed to start tuning'); + // Removed alert popup - check console for errors } } @@ -33,7 +33,7 @@ await api.tuner.setBypass(value); } catch (err) { console.error('Failed to set bypass:', err); - alert('Failed to set bypass'); + // Removed alert popup - check console for errors } } @@ -42,7 +42,7 @@ await api.tuner.setOperate(value); } catch (err) { console.error('Failed to set operate:', err); - alert('Failed to set operate'); + // Removed alert popup - check console for errors } } @@ -307,59 +307,14 @@ margin-top: 2px; } - .power-display { - display: flex; - flex-direction: column; - gap: 8px; - } /* SWR Circle */ - .swr-container { - display: flex; - align-items: center; - gap: 10px; - } - .swr-circle { - width: 80px; - height: 80px; - border-radius: 50%; - background: radial-gradient(circle, rgba(79, 195, 247, 0.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 { - font-size: 20px; - font-weight: 300; - color: var(--swr-color); - } - .swr-label { - font-size: 10px; - color: var(--text-muted); - text-transform: uppercase; - } - .swr-status { - flex: 1; - } - .status-text { - font-size: 14px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - } - .status-text.good { color: #4caf50; } - .status-text.ok { color: #ffc107; } - .status-text.warning { color: #ff9800; } - .status-text.danger { color: #f44336; } /* Capacitors */ .capacitors { diff --git a/web/src/lib/api.js b/web/src/lib/api.js index b3c0d39..26d4d94 100644 --- a/web/src/lib/api.js +++ b/web/src/lib/api.js @@ -64,6 +64,10 @@ export const api = { method: 'POST', body: JSON.stringify({ port, antenna }), }), + deselectAntenna: (port, antenna) => request('/antenna/deselect', { + method: 'POST', + body: JSON.stringify({ port, antenna }), + }), reboot: () => request('/antenna/reboot', { method: 'POST' }), }, @@ -101,5 +105,9 @@ export const api = { method: 'POST', body: JSON.stringify({ enabled, threshold }), }), + setDirection: (direction) => request('/ultrabeam/direction', { + method: 'POST', + body: JSON.stringify({ direction }), + }), }, }; \ No newline at end of file