diff --git a/plans/to-do.md b/plans/to-do.md index b7b354ef..10cdb4bc 100644 --- a/plans/to-do.md +++ b/plans/to-do.md @@ -1,8 +1,9 @@ 1. Overseer personality swapping. -2. add faster way for admins to login, like an invisible button in top left or something -3. add discord bot typing thing for when someone requests a replay -4. change replay title for ones requested from discord, something other than "requester driving rover" -5. fix rover request spam queue cheat +2. make google tts the default everywhere but roverd +3. add faster way for admins to login, like an invisible button in top left or something +4. add discord bot typing thing for when someone requests a replay +5. change replay title for ones requested from discord, something other than "requester driving rover" +6. fix rover request spam queue cheat # relative pipe dreams: diff --git a/server/public/assets/index-CBy0XoKw.js b/server/public/assets/index-CXMfqWm7.js similarity index 96% rename from server/public/assets/index-CBy0XoKw.js rename to server/public/assets/index-CXMfqWm7.js index dcc0650f..7840820e 100644 --- a/server/public/assets/index-CBy0XoKw.js +++ b/server/public/assets/index-CXMfqWm7.js @@ -14,7 +14,7 @@ ${C}`}).join(` ---------------------------------------- `):""};return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"Overseer Control"}),r.jsx("div",{className:"flex gap-0.5 text-xs",children:r.jsx("button",{type:"button",onClick:n,disabled:!!l,className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:l?"Clearing...":"Clear Overseer History"})}),r.jsxs("div",{className:"surface flex flex-wrap gap-0.5 text-xs",children:[r.jsxs("span",{className:"surface-muted",children:["running: ",f.running?"yes":"no"]}),r.jsxs("span",{className:"surface-muted",children:["phase: ",f.phase||"--"]}),r.jsxs("span",{className:"surface-muted",children:["tick: ",f.tickCount??0]}),r.jsxs("span",{className:"surface-muted",children:["trigger: ",f.lastTriggerReason||"--"]}),r.jsxs("span",{className:"surface-muted",children:["name: ",m.name||"--"]}),r.jsxs("span",{className:"surface-muted",children:["model: ",m.model||"--"]}),r.jsxs("span",{className:"surface-muted",children:["observeOnly: ",m.observeOnly?"yes":"no"]}),r.jsxs("span",{className:"surface-muted",children:["decision: ",p.normalized||"--"]}),r.jsxs("span",{className:"surface-muted",children:["outcome: ",p.outcome||"--"]}),r.jsxs("span",{className:"surface-muted",children:["reason: ",p.reason||"--"]}),r.jsxs("span",{className:"surface-muted",children:["last gen: ",x.lastGenerationMs!=null?`${x.lastGenerationMs}ms`:"--"]})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",open:!0,children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Latest Context"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:z.stateUpdate||""})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",open:!0,children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Exact Model Input (messages[])"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:M()})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Exact System Prompt"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:z.systemPrompt||""})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Exact Transcript Rows"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:JSON.stringify(z.transcript||[],null,2)})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Tool Availability"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:JSON.stringify({available:z.availableTools,blocked:z.blockedTools},null,2)})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",open:!0,children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Exact Model Output"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:p.raw||""})]}),r.jsxs("details",{className:"surface text-xs text-slate-200",open:!0,children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Live Tool Calls"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:JSON.stringify(p.liveToolCalls||[],null,2)})]}),r.jsxs("div",{className:"surface text-xs text-slate-200",children:[r.jsxs("div",{children:["Normalized decision: ",p.normalized||"--"]}),r.jsxs("div",{children:["Model input at: ",z.modelInputAt?new Date(z.modelInputAt).toLocaleString():"n/a"]}),r.jsxs("div",{children:["Model output at: ",p.outputAt?new Date(p.outputAt).toLocaleString():"n/a"]})]}),g.message?r.jsxs("div",{className:"surface text-xs text-red-300",children:["Error: ",g.message]}):null,r.jsxs("details",{className:"surface text-xs text-slate-200",children:[r.jsx("summary",{className:"cursor-pointer select-none text-slate-300",children:"Recent Runs"}),r.jsx("pre",{className:"mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:JSON.stringify(t.history||[],null,2)})]}),r.jsx("button",{type:"button",className:"button-dark text-xs",onClick:()=>h(y=>!y),children:o?"Hide Full Payload":"Show Full Payload"}),o?r.jsx("pre",{className:"surface whitespace-pre-wrap break-words text-[0.72rem] text-slate-200",children:JSON.stringify(t,null,2)}):null]})}function II({health:t,roster:n=[]}){if(!t)return null;const l=t.replay||{sources:[],readyCount:0,totalCount:0},o=t.snapshots||{rovers:[],rooms:[]},h=x=>n.find(z=>String(z.id)===String(x))?.color||null,f=`${l.readyCount}/${l.totalCount} sources ready`,m=o.rovers.filter(x=>x.stale).length,p=o.rooms.filter(x=>x.stale).length;return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("div",{className:"panel-muted text-xs uppercase",children:"Health"}),r.jsxs("div",{className:"surface space-y-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Replay segments"}),r.jsx("span",{className:"text-slate-400",children:f})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Rover snapshots"}),r.jsxs("span",{className:m?"text-amber-300":"text-emerald-300",children:[o.rovers.length-m,"/",o.rovers.length," ok"]})]}),r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:"Room cameras"}),r.jsxs("span",{className:p?"text-amber-300":"text-emerald-300",children:[o.rooms.length-p,"/",o.rooms.length," ok"]})]})]}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:l.sources.map(x=>r.jsxs("div",{className:"flex items-center justify-between",children:[x.type==="rover"?r.jsx(A4,{name:x.label,color:h(x.id),fallback:x.id}):r.jsx("span",{children:x.label}),r.jsxs("span",{className:x.ready?"text-emerald-300":"text-amber-300",children:[x.recentCount,"/",x.neededCount]})]},`${x.type}:${x.id}`))}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:o.rovers.map(x=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx(A4,{name:x.name,color:h(x.id),fallback:x.id}),r.jsx("span",{className:x.stale?"text-amber-300":"text-emerald-300",children:x.stale?"stale":"ok"})]},`rover:${x.id}`))}),r.jsx("div",{className:"space-y-0.5 text-xs text-slate-300",children:o.rooms.map(x=>r.jsxs("div",{className:"flex items-center justify-between",children:[r.jsx("span",{children:x.name}),r.jsx("span",{className:x.stale?"text-amber-300":"text-emerald-300",children:x.error?"error":x.stale?"stale":"ok"})]},`room:${x.id}`))})]})}function PI({entries:t}){const n=t||[];return r.jsxs(F1,{title:"Admin IP log",meta:n.length,bodyClassName:"space-y-0.5 text-base",children:[r.jsx("div",{className:"h-64 overflow-y-auto font-mono text-xs",children:n.length===0?r.jsx("p",{children:"No admin log entries yet."}):n.slice().reverse().map(l=>r.jsxs("div",{children:[r.jsx("span",{className:"text-amber-400",children:l.ts?new Date(l.ts).toLocaleTimeString():"--"})," ",l.label&&r.jsxs("span",{className:"text-teal-400",children:["[",l.label,"]"]})," ",r.jsx("span",{className:"text-slate-200",children:l.message})," ",l.ip&&r.jsx("span",{className:"text-cyan-300",children:l.ip})," ",l.meta&&r.jsx("span",{className:"text-slate-500",children:JSON.stringify(l.meta)})]},l.id))}),r.jsx("p",{className:"text-xs text-slate-500",children:"Admin-only log stream; IPs never appear in user data."})]})}const QI=[{key:"open",label:"Open"},{key:"turns",label:"Turns"},{key:"admin",label:"Admin"},{key:"lockdown",label:"Lockdown"}];function XI(){const{session:t,lockRover:n,setMode:l,requestControl:o,setGlobalObjective:h,setAdminReason:f,rebootRover:m,rebootServer:p,setAudioLevels:x,setPrivateSafety:z,llmControl:g,overseerControl:M,adminLogs:y,llmCommentaryState:S,overseerControlState:A}=A8(),H=v.useMemo(()=>t?.roster??[],[t?.roster]),[C,w]=v.useState({}),[j,L]=v.useState({}),[B,E]=v.useState(!1),[O,T]=v.useState(!1),[k,U]=v.useState(!1),G=t?.health||null,Y=t?.globalObjective?.text||"",X=t?.globalObjective?.updatedAt||null,[d1,o1]=v.useState(Y),l1=t?.adminReason?.text||"",N=t?.adminReason?.updatedAt||null,[_,$]=v.useState(l1),W=t?.audioLevels||{},[K,F]=v.useState({hornGain:Number.isFinite(W.hornGain)?W.hornGain:1,ttsGain:Number.isFinite(W.ttsGain)?W.ttsGain:1,forwardGain:Number.isFinite(W.forwardGain)?W.forwardGain:1}),[D,c1]=v.useState({}),u1=t?.role==="admin"||t?.role==="lockdown"||t?.role==="lockdown-admin",p1=t?.role==="lockdown"||t?.role==="lockdown-admin",H1=t?.mode??"open",x1=async(h1,m1)=>{try{await n(h1,m1),w(B1=>({...B1,[h1]:m1}))}catch(B1){alert(B1.message)}},k1=async h1=>{const m1=h1.target.value;try{await l(m1)}catch(B1){alert(B1.message)}},J=async h1=>{try{await o(h1,{force:!0})}catch(m1){alert(m1.message)}},f1=async h1=>{if(!(!h1?.id||!window.confirm(`Reboot rover "${h1.name||h1.id}" now?`))){L(B1=>({...B1,[h1.id]:!0}));try{await m(h1.id)}catch(B1){alert(B1.message)}finally{L(B1=>({...B1,[h1.id]:!1}))}}},g1=async()=>{if(window.confirm("Reboot the server host now? This will disconnect all users.")){E(!0);try{await p()}catch(m1){alert(m1.message),E(!1)}}},z1=async()=>{if(window.confirm("Clear LLM commentary history now? This resets chat context, bot memory, and rover activity metrics for narration.")){T(!0);try{await g("clearHistory")}catch(m1){alert(m1.message)}finally{T(!1)}}},j1=async()=>{if(window.confirm("Clear Overseer Control history now?")){U(!0);try{await M("clearHistory")}catch(m1){alert(m1.message)}finally{U(!1)}}},D1=async()=>{try{await h(d1)}catch(h1){alert(h1.message)}},b1=async()=>{try{await h(null)}catch(h1){alert(h1.message)}},S1=async()=>{try{await f(_)}catch(h1){alert(h1.message)}},X1=async()=>{try{await f(null)}catch(h1){alert(h1.message)}},x2=h1=>m1=>{const B1=Number(m1.target.value);F(V2=>({...V2||{},[h1]:Number.isFinite(B1)?B1:1}))},i1=async()=>{try{await x(K)}catch(h1){alert(h1.message)}},r1=async()=>{window.dispatchEvent(new CustomEvent("buttonBox:rewardRunLocalTest",{detail:{rewardName:"Admin Mode",count:1e3,goal:1e3}}))};v.useEffect(()=>{o1(Y)},[Y]),v.useEffect(()=>{$(l1)},[l1]),v.useEffect(()=>{F({hornGain:Number.isFinite(W.hornGain)?W.hornGain:1,ttsGain:Number.isFinite(W.ttsGain)?W.ttsGain:1,forwardGain:Number.isFinite(W.forwardGain)?W.forwardGain:1})},[W.forwardGain,W.hornGain,W.ttsGain]),v.useEffect(()=>{const h1={};(H||[]).forEach(m1=>{m1?.private?.enabled&&(h1[m1.id]={speedLimitEnabled:!!m1?.private?.safety?.speedLimitEnabled,speedLimitMaxWheelSpeed:Number.isFinite(m1?.private?.safety?.speedLimitMaxWheelSpeed)?m1.private.safety.speedLimitMaxWheelSpeed:250,hardOvercurrentEnabled:!!m1?.private?.safety?.hardOvercurrentEnabled,overcurrentStopMs:Number.isFinite(m1?.private?.safety?.overcurrentStopMs)?m1.private.safety.overcurrentStopMs:300,hardBumpEnabled:!!m1?.private?.safety?.hardBumpEnabled,bumpBackoffSpeed:Number.isFinite(m1?.private?.safety?.bumpBackoffSpeed)?m1.private.safety.bumpBackoffSpeed:250,bumpBackoffMs:Number.isFinite(m1?.private?.safety?.bumpBackoffMs)?m1.private.safety.bumpBackoffMs:350,cliffEnabled:!!m1?.private?.safety?.cliffEnabled,cliffBackoffSpeed:Number.isFinite(m1?.private?.safety?.cliffBackoffSpeed)?m1.private.safety.cliffBackoffSpeed:250,cliffBackoffMs:Number.isFinite(m1?.private?.safety?.cliffBackoffMs)?m1.private.safety.cliffBackoffMs:500,triggerCooldownMs:Number.isFinite(m1?.private?.safety?.triggerCooldownMs)?m1.private.safety.triggerCooldownMs:800})}),c1(h1)},[H]);const v1=v.useMemo(()=>{const h1={};return H.forEach(m1=>{h1[m1.id]=C[m1.id]??m1.locked}),h1},[H,C]),y1=(h1,m1={})=>{c1(B1=>({...B1||{},[h1]:{...B1?.[h1]||{},...m1||{}}}))},O1=async h1=>{try{const m1=D?.[h1];if(!m1)return;await z(h1,m1)}catch(m1){alert(m1.message)}};if(!u1)return null;const A1=r.jsx("select",{value:H1,onChange:k1,className:"field-input text-sm",children:QI.map(h1=>r.jsx("option",{value:h1.key,children:h1.label},h1.key))});return r.jsxs(F1,{title:"Admin controls",actions:A1,bodyClassName:"space-y-0.5 text-base",children:[r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:g1,disabled:B,className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:B?"Server rebooting...":"Reboot Server"}),r.jsx("button",{type:"button",onClick:r1,className:"button-dark",children:"Test Reward Popup"})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Global audio levels"}),t?.audioLevels?.updatedAt?r.jsxs("span",{children:["Updated ",new Date(t.audioLevels.updatedAt).toLocaleString()]}):null]}),r.jsxs("label",{className:"grid gap-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Horn gain"}),r.jsxs("span",{children:[K.hornGain.toFixed(2),"x"]})]}),r.jsx("input",{type:"range",min:"0",max:"4",step:"0.01",value:K.hornGain,onChange:x2("hornGain"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"grid gap-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"TTS gain"}),r.jsxs("span",{children:[K.ttsGain.toFixed(2),"x"]})]}),r.jsx("input",{type:"range",min:"0",max:"4",step:"0.01",value:K.ttsGain,onChange:x2("ttsGain"),className:"w-full accent-emerald-500"})]}),r.jsxs("label",{className:"grid gap-0.5 text-xs text-slate-200",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsx("span",{children:"Forward gain"}),r.jsxs("span",{children:[K.forwardGain.toFixed(2),"x"]})]}),r.jsx("input",{type:"range",min:"0",max:"4",step:"0.01",value:K.forwardGain,onChange:x2("forwardGain"),className:"w-full accent-emerald-500"})]}),r.jsx("div",{className:"flex gap-0.5 text-xs",children:r.jsx("button",{type:"button",onClick:i1,className:"button-dark",children:"Apply audio levels"})})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Global objective"}),X?r.jsxs("span",{children:["Updated ",new Date(X).toLocaleString()]}):null]}),r.jsx("input",{type:"text",value:d1,onChange:h1=>o1(h1.target.value),placeholder:"Set a global objective",className:"field-input text-sm"}),r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:D1,className:"button-dark",children:"Set objective"}),r.jsx("button",{type:"button",onClick:b1,className:"button-danger",children:"Clear"})]})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[r.jsx("span",{children:"Admin mode reason"}),N?r.jsxs("span",{children:["Updated ",new Date(N).toLocaleString()]}):null]}),r.jsx("textarea",{value:_,onChange:h1=>$(h1.target.value),placeholder:"Set an admin mode reason",className:"field-input text-sm min-h-[3.5rem]"}),r.jsxs("div",{className:"flex gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:S1,className:"button-dark",children:"Set reason"}),r.jsx("button",{type:"button",onClick:X1,className:"button-danger",children:"Clear"})]})]}),r.jsx(UI,{roster:H,renderActions:h1=>r.jsxs("div",{className:"flex flex-wrap items-center gap-0.5 text-xs",children:[r.jsx("button",{type:"button",onClick:()=>x1(h1.id,!v1[h1.id]),className:"button-dark",children:v1[h1.id]?"Unlock":"Lock"}),r.jsx("button",{type:"button",onClick:()=>J(h1.id),className:"button-dark",children:"Force"}),r.jsx("button",{type:"button",onClick:()=>f1(h1),disabled:!!j[h1.id],className:"button-danger disabled:cursor-not-allowed disabled:opacity-60",children:j[h1.id]?"Rebooting...":"Reboot"}),h1?.private?.enabled?r.jsxs("div",{className:"w-full rounded border border-slate-700/70 p-0.5 space-y-0.5 text-[0.7rem]",children:[r.jsx("div",{className:"text-slate-300",children:"Private safety"}),r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!!D?.[h1.id]?.speedLimitEnabled,disabled:!p1,onChange:m1=>y1(h1.id,{speedLimitEnabled:!!m1.target.checked})}),r.jsx("span",{children:"Speed limit"})]}),r.jsx("input",{type:"number",min:"1",max:"500",className:"field-input text-xs w-20",disabled:!p1,value:D?.[h1.id]?.speedLimitMaxWheelSpeed??250,onChange:m1=>y1(h1.id,{speedLimitMaxWheelSpeed:Number(m1.target.value)||250})}),r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!!D?.[h1.id]?.hardOvercurrentEnabled,disabled:!p1,onChange:m1=>y1(h1.id,{hardOvercurrentEnabled:!!m1.target.checked})}),r.jsx("span",{children:"Hard overcurrent"})]}),r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!!D?.[h1.id]?.hardBumpEnabled,disabled:!p1,onChange:m1=>y1(h1.id,{hardBumpEnabled:!!m1.target.checked})}),r.jsx("span",{children:"Hard bump"})]}),r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!!D?.[h1.id]?.cliffEnabled,disabled:!p1,onChange:m1=>y1(h1.id,{cliffEnabled:!!m1.target.checked})}),r.jsx("span",{children:"Cliff safety"})]}),r.jsx("button",{type:"button",disabled:!p1,onClick:()=>O1(h1.id),className:"button-dark disabled:opacity-50",children:"Apply Safety"})]}):null]})}),r.jsx(II,{health:G,roster:H}),r.jsx(GI,{state:S,onClearHistory:z1,clearingHistory:O}),r.jsx(YI,{state:A,onClearHistory:j1,clearingHistory:k}),r.jsx(PI,{entries:y})]})}const KI=[{id:"driveForward",label:"Drive Forward",group:"Driving"},{id:"driveBackward",label:"Drive Backward",group:"Driving"},{id:"driveLeft",label:"Turn Left",group:"Driving"},{id:"driveRight",label:"Turn Right",group:"Driving"},{id:"boostModifier",label:"Boost Modifier",group:"Driving"},{id:"slowModifier",label:"Slow Modifier",group:"Driving"},{id:"auxMainForward",label:"Main Brush Forward",group:"Aux Motors"},{id:"auxMainReverse",label:"Main Brush Reverse",group:"Aux Motors"},{id:"auxSideForward",label:"Side Brush Forward",group:"Aux Motors"},{id:"auxSideReverse",label:"Side Brush Reverse",group:"Aux Motors"},{id:"auxVacuumFast",label:"Vacuum Max",group:"Aux Motors"},{id:"auxVacuumSlow",label:"Vacuum Low",group:"Aux Motors"},{id:"auxAllForward",label:"All Aux Forward",group:"Aux Motors"},{id:"cameraUp",label:"Camera Up",group:"Camera"},{id:"cameraDown",label:"Camera Down",group:"Camera"},{id:"nightVisionToggle",label:"Toggle Night Vision",group:"Camera"},{id:"videoFilterCycle",label:"Cycle Video Filter",group:"Camera"},{id:"hornHonk",label:"Horn (Hold)",group:"Audio"},{id:"micPtt",label:"Mic Push To Talk",group:"Audio"},{id:"driveMacro",label:"Drive Macro",group:"Macros"},{id:"dockMacro",label:"Dock Macro",group:"Macros"},{id:"chatFocus",label:"Toggle Chat",group:"Chat"},{id:"songNoteUp",label:"Song Note Up",group:"Audio"},{id:"songNoteDown",label:"Song Note Down",group:"Audio"},{id:"homeAssistantOn",label:"Room Controls On (Cycle)",group:"Room Controls"},{id:"homeAssistantOff",label:"Room Controls Off (Cycle)",group:"Room Controls"}];function JI(t){return t.reduce((n,l)=>((n[l.group]||(n[l.group]=[])).push(l),n),{})}function WI(t,n){const l=Number(t);return Number.isFinite(l)?Math.max(0,Math.min(500,l)):n}const jc=5,w8=500,H8=1,Fc=100;function tP(t,n){const l=Number(t);return Number.isFinite(l)?Math.max(jc,Math.min(w8,l)):n}function z7(t,n){const l=Number(t);return Number.isFinite(l)?Math.max(H8,Math.min(Fc,l)):n}function eP(t){const l=(z7(t,t)-H8)/(Fc-H8),o=w8-l*(w8-jc);return Math.round(o)}function cP(t){const n=tP(t,t),l=(w8-n)/(w8-jc),o=H8+l*(Fc-H8);return Math.round(o)}function aP(t){const[n,l]=v.useState(null),o=v.useCallback(m=>{Me(!0),l(m)},[]),h=v.useCallback(()=>{Me(!1),l(null)},[]),f=v.useCallback((m,p)=>{if(p.preventDefault(),p.stopPropagation(),p.key==="Escape"){h();return}const x=B8(p.key??"");x&&(t(m,x,p),h())},[h,t]);return v.useEffect(()=>()=>Me(!1),[]),{active:n,startCapture:o,cancel:h,captureFromEvent:f}}function ke({children:t}){return r.jsx("p",{className:"mx-auto w-full max-w-lg text-sm font-semibold text-white",children:t})}function W5({label:t,description:n,value:l,onChange:o,min:h=0,max:f=500,step:m=5}){return r.jsxs("label",{className:"mx-auto block w-full max-w-lg rounded bg-neutral-800/80 px-1.5 py-1",children:[r.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_4.75rem] items-start gap-1.5 text-sm text-white",children:[r.jsx("span",{className:"min-w-0 font-semibold text-white",children:t}),r.jsx("input",{type:"number",min:h,max:f,value:l,onChange:p=>o(p.target.value),className:"w-full rounded border border-neutral-600 bg-neutral-900 px-1 py-0.5 text-right text-xs font-mono text-white"})]}),n&&r.jsx("p",{className:"mt-0.5 text-xs leading-snug text-white",children:n}),r.jsxs("div",{className:"mt-1 flex items-center gap-1.5",children:[r.jsx("input",{type:"range",min:h,max:f,step:m,value:l,onChange:p=>o(p.target.value),className:"h-2 min-w-0 flex-1 accent-emerald-400"}),r.jsx("span",{className:"w-10 text-right text-xs font-mono text-white",children:l})]})]})}function nP({action:t,value:n,isActive:l,onCaptureStart:o,onCancel:h,onCaptureKeyDown:f}){return r.jsxs("div",{className:"mx-auto grid w-full max-w-lg grid-cols-[minmax(0,1fr)_auto] items-center gap-1.5 rounded bg-neutral-800/80 px-1.5 py-1 text-sm max-[420px]:grid-cols-1",children:[r.jsxs("div",{className:"min-w-0",children:[r.jsx("p",{className:"font-semibold leading-snug text-white",children:t.label}),r.jsx("p",{className:"mt-0.5 text-xs leading-snug text-white",children:B2(n)})]}),r.jsxs("div",{className:"flex flex-wrap items-center justify-end gap-1 max-[420px]:justify-start",children:[l&&r.jsx("button",{type:"button",onClick:h,className:"button-danger px-1 py-0.5 text-xs",children:"Cancel"}),r.jsx("button",{type:"button",onClick:o,onKeyDown:l?f:void 0,autoFocus:l,className:`${l?"rounded-md bg-emerald-500 px-1 py-0.5 text-emerald-950 hover:bg-emerald-400":"button-dark px-1 py-0.5"} text-xs font-medium transition-colors`,children:l?"Press a key...":"Change"})]})]})}function lP(){const{state:{keymap:t},actions:{updateKeyBinding:n,resetKeyBindings:l}}=H2(),{value:o,save:h}=E1("inputs",E4),f={...E4.keyboard,...o.keyboard??{}},m=v.useMemo(()=>typeof f.tiltSpeed=="number"?z7(f.tiltSpeed,E4.keyboard.tiltSpeed):typeof f.tiltIntervalMs=="number"?z7(cP(f.tiltIntervalMs),E4.keyboard.tiltSpeed):E4.keyboard.tiltSpeed,[f.tiltIntervalMs,f.tiltSpeed]),p=v.useMemo(()=>JI(KI),[]),{active:x,startCapture:z,cancel:g,captureFromEvent:M}=aP((H,C)=>{n(H,C)}),y=v.useCallback(H=>t?.[H]?.[0]||g8[H]?.[0]||"",[t]),S=v.useCallback((H,C)=>{const w=E4.keyboard,j=WI(C,w[H]);h(L=>({...L??{},keyboard:{...L?.keyboard??w,[H]:j}}))},[h]),A=v.useCallback(H=>{const C=E4.keyboard,w=z7(H,C.tiltSpeed),j=eP(w);h(L=>({...L??{},keyboard:{...L?.keyboard??C,tiltSpeed:w,tiltIntervalMs:j}}))},[h]);return r.jsxs(F1,{title:"Keyboard layout",meta:"Per-browser · click to change a binding",actions:r.jsx("button",{type:"button",onClick:()=>l(),className:"button-dark px-1 py-0.5 text-xs",children:"Reset defaults"}),bodyClassName:"space-y-2 p-1 text-sm",children:[r.jsxs("div",{className:"space-y-1",children:[r.jsx(ke,{children:"Keyboard speeds"}),r.jsxs("div",{className:"grid gap-1",children:[r.jsx(W5,{label:"Base speed",description:"Normal driving speed",value:f.baseSpeed,onChange:H=>S("baseSpeed",H)}),r.jsx(W5,{label:"Turbo speed",description:"Used when holding the boost modifier",value:f.turboSpeed,onChange:H=>S("turboSpeed",H)}),r.jsx(W5,{label:"Precision speed",description:"Used when holding the precision modifier",value:f.precisionSpeed,onChange:H=>S("precisionSpeed",H)})]})]}),r.jsxs("div",{className:"space-y-1",children:[r.jsx(ke,{children:"Camera tilt"}),r.jsx(W5,{label:"Tilt speed",description:"Higher = faster when holding tilt keys",value:m,onChange:A,min:1,max:100,step:1})]}),r.jsx("div",{className:"space-y-2",children:Object.entries(p).map(([H,C])=>r.jsxs("div",{className:"space-y-1",children:[r.jsx(ke,{children:H}),r.jsx("div",{className:"grid gap-1",children:C.map(w=>{const j=y(w.id),L=x===w.id;return r.jsx(nP,{action:w,value:j,isActive:L,onCancel:()=>g(),onCaptureStart:()=>z(w.id),onCaptureKeyDown:B=>M(w.id,B)},w.id)})})]},H))})]})}const r3=new Intl.NumberFormat(void 0,{maximumFractionDigits:2}),rP=[{id:"drive",label:"Drive stick",kind:"axisPair",section:"Driving",invertDefaults:{invertX:!1,invertY:!0}},{id:"cameraTilt",label:"Camera tilt",kind:"axis",section:"Camera",invertDefaults:{invert:!0}},{id:"mainBrush",label:"Main brush",kind:"axis",section:"Brushes",invertDefaults:{invert:!1}},{id:"sideBrush",label:"Side brush",kind:"axis",section:"Brushes",invertDefaults:{invert:!1}},{id:"vacuum",label:"Vacuum",kind:"button",section:"Aux buttons"},{id:"allAux",label:"All aux",kind:"button",section:"Aux buttons"},{id:"mainReverse",label:"Main reverse toggle",kind:"button",section:"Brush toggles"},{id:"sideReverse",label:"Side reverse toggle",kind:"button",section:"Brush toggles"},{id:"driveMacro",label:"Drive macro",kind:"button",section:"Mode macros"},{id:"dockMacro",label:"Dock macro",kind:"button",section:"Mode macros"},{id:"nightVisionToggle",label:"Night vision toggle",kind:"button",section:"Camera"}],Ti=.45,iP=.6;function f8({label:t,description:n,min:l,max:o,step:h,value:f,onChange:m}){return r.jsxs("label",{className:"mx-auto block w-full max-w-lg rounded bg-neutral-800/80 px-1.5 py-1",children:[r.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] items-start gap-1.5 text-sm text-white",children:[r.jsx("span",{className:"min-w-0 font-semibold text-white",children:t}),r.jsx("span",{className:"rounded bg-neutral-900 px-1 py-0.5 font-mono text-xs text-white",children:r3.format(f)})]}),n&&r.jsx("p",{className:"mt-0.5 text-xs leading-snug text-white",children:n}),r.jsx("input",{type:"range",min:l,max:o,step:h,value:f,onChange:p=>m(Number(p.target.value)),className:"mt-1 w-full accent-emerald-400"})]})}function sP(t){if(!t)return"Unassigned";if(t.kind==="axisPair"){const n=`${t.invertX?"X inv ":""}${t.invertY?"Y inv":""}`.trim();return`Axes ${t.x}/${t.y}${n?` (${n})`:""}`}if(t.kind==="axis")return`Axis ${t.index}${t.invert?" (inv)":""}`;if(t.kind==="button")return`Button ${t.index}`;if(t.kind==="buttonAxis")return`Button ${t.index} (analog)`;if(t.kind==="axisButton"){const n=t.direction<0?"<":">";return`Axis ${t.index} ${n} ${r3.format(t.threshold??.6)}`}return"Unassigned"}function oP(t){return t.reduce((n,l)=>((n[l.section]||(n[l.section]=[])).push(l),n),{})}function uP(t,n){if(!t||t.length===0)return null;if(n){const l=t.find(o=>o.signature===n);if(l)return l}return t[0]}function hP(t){return{axes:Array.from(t.axes??[]),buttons:(t.buttons??[]).map(n=>({pressed:!!n?.pressed,value:typeof n?.value=="number"?n.value:n?.pressed?1:0}))}}function dP(t,n,l){const o=(t.axes??[]).map((f,m)=>({index:m,delta:Math.abs((f??0)-(n.axes?.[m]??0)),value:f}));if(o.sort((f,m)=>m.delta-f.delta),l.kind==="axisPair"){const f=o.filter(m=>m.delta>Ti).slice(0,2);return f.length<2?null:{kind:"axisPair",x:f[0].index,y:f[1].index,...l.invertDefaults??{}}}const h=o.find(f=>f.delta>Ti);return h?{kind:"axis",index:h.index,...l.invertDefaults??{}}:null}function fP(t,n,l){const o=t.buttons??[];for(let f=0;fiP)return l.kind==="axis"?{kind:"buttonAxis",index:f}:{kind:"button",index:f}}const h=t.axes??[];for(let f=0;f.7&&p>.5)return l.kind==="axis"?{kind:"axis",index:f,...l.invertDefaults??{}}:{kind:"axisButton",index:f,direction:m>=0?1:-1,threshold:.6}}return null}function vP(t,n,l){if(!t)return null;if(l.kind==="axis"||l.kind==="axisPair"){const o=dP(t,n,l);if(o)return o}return fP(t,n,l)}function t7({children:t}){return r.jsx("p",{className:"mx-auto w-full max-w-lg text-sm font-semibold text-white",children:t})}function mP({action:t,source:n,isCapturing:l,onClear:o,onCapture:h,onInvert:f}){return r.jsxs("div",{className:"mx-auto grid w-full max-w-lg grid-cols-[minmax(0,1fr)_auto] items-center gap-1.5 rounded bg-neutral-800/80 px-1.5 py-1 text-sm max-[520px]:grid-cols-1",children:[r.jsxs("div",{className:"min-w-0",children:[r.jsx("p",{className:"font-semibold leading-snug text-white",children:t.label}),r.jsx("p",{className:"mt-0.5 text-xs leading-snug text-white",children:sP(n)})]}),r.jsxs("div",{className:"flex flex-wrap items-center justify-end gap-1 max-[520px]:justify-start",children:[t.kind==="axisPair"&&r.jsxs(r.Fragment,{children:[r.jsx("button",{type:"button",disabled:!n,onClick:()=>f(t,"invertX"),className:"button-dark px-1 py-0.5 text-xs font-medium disabled:opacity-50",children:"Invert X"}),r.jsx("button",{type:"button",disabled:!n,onClick:()=>f(t,"invertY"),className:"button-dark px-1 py-0.5 text-xs font-medium disabled:opacity-50",children:"Invert Y"})]}),t.kind==="axis"&&r.jsx("button",{type:"button",disabled:!n,onClick:()=>f(t),className:"button-dark px-1 py-0.5 text-xs font-medium disabled:opacity-50",children:"Invert"}),r.jsx("button",{type:"button",onClick:()=>o(t),className:"button-dark px-1 py-0.5 text-xs",children:"Clear"}),r.jsx("button",{type:"button",onClick:()=>h(t),className:`${l?"rounded-md bg-emerald-500 px-1 py-0.5 text-emerald-950 hover:bg-emerald-400":"button-dark px-1 py-0.5"} text-xs font-medium`,children:l?"Waiting...":"Capture"})]})]})}function pP(){const t=wY(),{value:n,save:l}=E1("gamepad",R3),[o,h]=v.useState(null),f=v.useRef(null),m=v.useMemo(()=>oP(rP),[]),p=v.useMemo(()=>uP(t.pads,n.activeSignature),[t.pads,n.activeSignature]),x=p?.signature??null,z=v.useMemo(()=>x?n?.profiles?.[x]??n?.defaults?.profile??i3:n?.defaults?.profile??i3,[x,n?.defaults?.profile,n?.profiles]);v.useEffect(()=>{!p||!x||n?.profiles?.[x]||l(C=>{const w=C??R3;if(w.profiles?.[x])return w;const j=w?.defaults?.profile??i3,L=no(p,j);return{...w,profiles:{...w.profiles??{},[x]:L}}})},[p,x,n?.profiles,l]),v.useEffect(()=>{f.current=null},[o,x]),v.useEffect(()=>{if(!o||!p)return;if(!f.current){f.current=hP(p);return}const C=vP(p,f.current,o);C&&(l(w=>{const j=w??R3,L=j.profiles?.[x]??j?.defaults?.profile??i3,B={...L,bindings:{...L.bindings??{},[o.id]:{...L.bindings?.[o.id]??{},kind:o.kind,sources:[C]}}};return{...j,profiles:{...j.profiles??{},[x]:B}}}),h(null))},[p,x,o,l]);const g=v.useCallback(C=>{l(w=>({...w??R3,activeSignature:C||null}))},[l]),M=v.useCallback(C=>{l(w=>{const j=w??R3,L=(x&&j.profiles?.[x])??j?.defaults?.profile??i3,B={...L,calibration:{...L.calibration??{},...C}};return x?{...j,profiles:{...j.profiles??{},[x]:B}}:{...j,defaults:{...j.defaults??{},profile:B}}})},[x,l]),y=v.useCallback((C,w)=>{l(j=>{const L=j??R3,B=(x&&L.profiles?.[x])??L?.defaults?.profile??i3,E=w(B.bindings?.[C]??{}),O={...B,bindings:{...B.bindings??{},[C]:E}};return x?{...L,profiles:{...L.profiles??{},[x]:O}}:{...L,defaults:{...L.defaults??{},profile:O}}})},[x,l]),S=v.useCallback(C=>{y(C.id,w=>({...w,sources:[]}))},[y]),A=v.useCallback((C,w)=>{y(C.id,j=>{const L=Array.isArray(j.sources)?[...j.sources]:[];if(!L[0])return j;const B={...L[0]};return w==="invertX"?B.invertX=!B.invertX:w==="invertY"?B.invertY=!B.invertY:B.invert=!B.invert,L[0]=B,{...j,sources:L}})},[y]),H=v.useMemo(()=>p?{outputs:lo(p,z)}:null,[p,z]);return r.jsxs(F1,{title:"Controller",meta:p?"Move sticks or press buttons to bind":"Connect a controller to configure.",bodyClassName:"space-y-2 p-1 text-sm",children:[o&&r.jsxs("p",{className:"mx-auto w-full max-w-lg rounded bg-emerald-950/50 px-1.5 py-1 text-sm text-white",children:["Capturing ",o.label,"..."]}),r.jsxs("div",{className:"space-y-1",children:[r.jsx(t7,{children:"Connected controller"}),t.pads.length===0?r.jsx("p",{className:"mx-auto w-full max-w-lg text-sm text-white",children:"No controller detected."}):r.jsxs("div",{className:"mx-auto grid w-full max-w-lg grid-cols-[minmax(0,1fr)_auto] items-center gap-1.5 rounded bg-neutral-800/80 px-1.5 py-1 text-sm max-[420px]:grid-cols-1",children:[r.jsx("select",{value:x??"",onChange:C=>g(C.target.value),className:"min-w-0 rounded border border-neutral-600 bg-neutral-900 px-1 py-0.5 text-sm text-white",children:t.pads.map(C=>r.jsx("option",{value:C.signature,children:C.id||"Unknown controller"},C.signature))}),r.jsx("span",{className:"rounded bg-neutral-900 px-1 py-0.5 text-xs text-white",children:p?.mapping??"unknown"})]})]}),r.jsxs("div",{className:"space-y-1",children:[r.jsx(t7,{children:"Calibration"}),r.jsxs("div",{className:"grid gap-1",children:[r.jsx(f8,{label:"Drive deadzone",description:"Ignore small drive stick drift",min:0,max:.6,step:.01,value:z.calibration?.driveDeadzone??.18,onChange:C=>M({driveDeadzone:C})}),r.jsx(f8,{label:"Camera deadzone",description:"Ignore small camera tilt drift",min:0,max:.4,step:.01,value:z.calibration?.cameraDeadzone??.08,onChange:C=>M({cameraDeadzone:C})}),r.jsx(f8,{label:"Aux deadzone",description:"Ignore small trigger noise",min:0,max:.4,step:.01,value:z.calibration?.auxDeadzone??.05,onChange:C=>M({auxDeadzone:C})}),r.jsx(f8,{label:"Side brush scale",description:"Scale side brush output",min:.3,max:1,step:.05,value:z.calibration?.auxSideScale??.55,onChange:C=>M({auxSideScale:C})}),r.jsxs("label",{className:"mx-auto block w-full max-w-lg rounded bg-neutral-800/80 px-1.5 py-1",children:[r.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] items-start gap-1.5 text-sm text-white",children:[r.jsx("span",{className:"min-w-0 font-semibold text-white",children:"Camera mode"}),r.jsxs("select",{value:z.calibration?.cameraMode??"absolute",onChange:C=>M({cameraMode:C.target.value}),className:"rounded border border-neutral-600 bg-neutral-900 px-1 py-0.5 text-sm text-white",children:[r.jsx("option",{value:"absolute",children:"Absolute"}),r.jsx("option",{value:"velocity",children:"Velocity"})]})]}),r.jsx("p",{className:"mt-0.5 text-xs leading-snug text-white",children:"Absolute maps stick to angle; velocity moves over time."})]}),r.jsx(f8,{label:"Camera sensitivity",description:"Velocity mode degrees per second",min:10,max:180,step:5,value:z.calibration?.cameraSensitivity??60,onChange:C=>M({cameraSensitivity:C})})]})]}),r.jsx("div",{className:"space-y-2",children:Object.entries(m).map(([C,w])=>r.jsxs("div",{className:"space-y-1",children:[r.jsx(t7,{children:C}),r.jsx("div",{className:"grid gap-1",children:w.map(j=>{const B=z.bindings?.[j.id]?.sources?.[0]??null;return r.jsx(mP,{action:j,source:B,isCapturing:o?.id===j.id,onClear:S,onCapture:h,onInvert:A},j.id)})})]},C))}),r.jsxs("div",{className:"space-y-1",children:[r.jsx(t7,{children:"Diagnostics"}),p?r.jsxs("div",{className:"mx-auto w-full max-w-lg space-y-1 rounded bg-neutral-900/70 px-1.5 py-1 text-xs text-white",children:[r.jsx("p",{className:"text-white",children:"Raw axes"}),r.jsx("div",{className:"grid grid-cols-2 gap-1",children:p.axes.map((C,w)=>r.jsxs("span",{className:"font-mono text-white",children:["A",w,": ",r3.format(C)]},`axis-${w}`))}),r.jsx("p",{className:"text-white",children:"Raw buttons"}),r.jsx("div",{className:"grid grid-cols-2 gap-1",children:p.buttons.map((C,w)=>r.jsxs("span",{className:"font-mono text-white",children:["B",w,": ",r3.format(C.value)," ",C.pressed?"●":""]},`btn-${w}`))}),H?.outputs&&r.jsxs(r.Fragment,{children:[r.jsx("p",{className:"text-white",children:"Mapped outputs"}),r.jsxs("div",{className:"grid grid-cols-2 gap-1",children:[r.jsxs("span",{className:"font-mono text-white",children:["Drive: ",r3.format(H.outputs.driveVector.x),","," ",r3.format(H.outputs.driveVector.y)]}),r.jsxs("span",{className:"font-mono text-white",children:["Camera: ",r3.format(H.outputs.cameraAxis)]}),r.jsxs("span",{className:"font-mono text-white",children:["Main: ",r3.format(H.outputs.auxAxis.main)]}),r.jsxs("span",{className:"font-mono text-white",children:["Side: ",r3.format(H.outputs.auxAxis.side)]})]})]})]}):r.jsx("p",{className:"mx-auto w-full max-w-lg text-sm text-white",children:"No controller detected."})]})]})}const gP={drive:"Drive wheels",aux:"Aux motors"};function qi(t){return Number.isFinite(t)?`${Math.round(t*100)}%`:"--"}function zP({value:t,color:n="bg-emerald-500"}){const l=`${Math.round(Math.max(0,Math.min(1,t))*100)}%`;return r.jsx("div",{className:"h-2 w-full overflow-hidden rounded bg-slate-800",children:r.jsx("div",{className:`h-full ${n}`,style:{width:l}})})}function xP(){const{state:{roverId:t},overcurrentLimiter:n}=H2(),l=v.useMemo(()=>i6.map(o=>o.key),[]);return r.jsx(F1,{title:"Overcurrent limiter",meta:n?.adminImmune?"Admin immune":"Active",bodyClassName:"space-y-0.5 text-sm",children:t?r.jsxs("div",{className:"space-y-0.5",children:[l.map(o=>{const h=n?.caps?.[o]?.cap??0,f=n?.overcurrent?.groups?.[o]??!1,m=n?.scales?.perGroup?.[o]??1;return r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs",children:[r.jsx("span",{className:"text-slate-200",children:gP[o]||o}),r.jsx("span",{className:f?"text-red-300":"text-slate-400",children:f?"overcurrent":"ok"})]}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-[0.7rem] text-slate-400",children:[r.jsx("span",{children:"Cap"}),r.jsx("span",{children:qi(h)})]}),r.jsx(zP,{value:h,color:"bg-amber-500"}),r.jsxs("div",{className:"flex items-center justify-between text-[0.7rem] text-slate-400",children:[r.jsx("span",{children:"Scale"}),r.jsx("span",{children:qi(m)})]})]})]},o)}),r.jsxs("div",{className:"text-[0.7rem] text-slate-400",children:[r.jsx("div",{children:`Down rate ${n?.config?.downRatePerSec}/s · Up rate ${n?.config?.upRatePerSec}/s`}),r.jsx("div",{children:`Release delay ${n?.config?.releaseDelaySec}s`}),r.jsx("div",{children:`Output rate ${n?.config?.outputRateMs}ms`})]})]}):r.jsx("p",{className:"text-xs text-slate-500",children:"Assign a rover to view limiter status."})})}const MP="pride-page-bg",j2="gap-0.5",E3="space-y-0.5",jo=v.createContext(null);function Fo(){const t=v.useContext(jo);if(!t)throw new Error("Tabs components must be used within a provider.");return t}const Di={primary:{base:"flex-1 px-0.5 py-0.5 text-sm font-medium focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-slate-500 rounded-md border border-slate-800",active:"bg-sky-600 text-white border-white",inactive:"bg-zinc-900 text-slate-300 hover:bg-sky-500 hover:text-white"}},Oi={none:"",pink:"bg-pink-500 text-white border-pink-200 hover:bg-pink-400",green:"bg-emerald-500 text-white border-emerald-200 hover:bg-emerald-400"},No="primary";function Nc(...t){return t.filter(Boolean).join(" ")}function kc({children:t,defaultTab:n,currentTab:l,onTabChange:o,variant:h=No}){const[f,m]=v.useState(n??null),[p,x]=v.useState([]),z=v.useCallback(S=>S?(x(A=>A.includes(S)?A:[...A,S]),()=>x(A=>A.includes(S)?A.filter(H=>H!==S):A)):()=>{},[]),g=l??f??p[0]??null;v.useEffect(()=>{n&&l===void 0&&m(n)},[n,l]);const M=v.useCallback(S=>{S&&(l===void 0&&m(S),o&&o(S))},[l,o]),y=v.useMemo(()=>({activeTab:g,setActiveTab:M,registerTab:z,variant:h}),[g,M,z,h]);return r.jsx(jo.Provider,{value:y,children:t})}function Rc({children:t,className:n=""}){return r.jsx("div",{className:Nc("panel-section overflow-hidden border border-neutral-500/60 bg-neutral-900/95 p-0.5",n),children:r.jsx("div",{className:`flex ${j2}`,children:t})})}function r4({id:t,children:n,className:l="",disabled:o=!1,highlight:h="none"}){const{activeTab:f,setActiveTab:m,registerTab:p,variant:x}=Fo();v.useEffect(()=>p(t),[t,p]);const z=Di[x]??Di[No],g=f===t,M=Oi[h]||Oi.none,y=Nc(z.base,M||(g?z.active:z.inactive),o?"cursor-not-allowed opacity-50":"",l);return r.jsx("button",{type:"button",className:y,onClick:()=>!o&&m(t),"aria-pressed":g,"aria-disabled":o,children:n})}function Ec({children:t,className:n=""}){return r.jsx("div",{className:Nc("mt-0",E3,n),children:t})}function i4({id:t,children:n,keepMounted:l=!1}){const{activeTab:o}=Fo(),h=o===t;return!h&&!l?null:!h&&l?r.jsx("div",{hidden:!0,"aria-hidden":"true",children:n}):r.jsx(r.Fragment,{children:n})}function bP(){const{session:t}=A8(),n=v.useMemo(()=>JSON.stringify(t??{},null,2),[t]);return r.jsx(F1,{title:"Session snapshot",className:"min-h-0",bodyClassName:"flex min-h-0 flex-col gap-0.5 text-xs",children:r.jsx("pre",{className:"min-h-0 flex-1 overflow-y-auto font-mono text-[0.7rem] text-lime-300",children:n})})}const _i=300,Ui=240;function yP(t,n){if(t===void 0)return"undefined";if(t===null)return"null";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return String(t);if(typeof t=="function")return"[Function]";if(t instanceof Error)return`${t.name}: ${t.message}`;if(t instanceof Date)return t.toISOString();if(n||(n=new WeakSet),typeof t=="object"){if(n.has(t))return"[Circular]";n.add(t)}try{return JSON.stringify(t,(l,o)=>typeof o=="function"?"[Function]":o instanceof Error?`${o.name}: ${o.message}`:o,0)}catch{return String(t)}}function CP(t){const l=t.map(o=>yP(o)).join(" ");return l.length>Ui?`${l.slice(0,Ui)}…`:l}function wP(){const t=X2(),[n,l]=v.useState([]),o=v.useMemo(()=>n.reduce((f,m)=>(m.direction==="in"&&(f.in+=1),m.direction==="out"&&(f.out+=1),f),{in:0,out:0}),[n]);v.useEffect(()=>{const f=(z,g,M)=>{l(y=>{const S=[...y,{id:`${Date.now()}-${Math.random().toString(16).slice(2)}`,direction:z,event:g,payload:CP(M),timestamp:new Date().toLocaleTimeString()}];return S.length>_i?S.slice(-_i):S})},m=(z,...g)=>f("in",z,g),p=(z,...g)=>f("out",z,g);t.onAny(m);let x=null;if(typeof t.onAnyOutgoing=="function")t.onAnyOutgoing(p),x=()=>t.offAnyOutgoing(p);else{const z=t.emit.bind(t);t.emit=(g,...M)=>(p(g,...M),z(g,...M)),x=()=>{t.emit=z}}return()=>{t.offAny(m),x&&x()}},[t]);const h=r.jsxs("div",{className:"flex items-center gap-0.5 text-xs text-slate-400",children:[r.jsxs("span",{children:["in ",o.in]}),r.jsxs("span",{children:["out ",o.out]}),r.jsx("button",{type:"button",className:"button-dark text-xs",onClick:()=>l([]),children:"Clear"})]});return r.jsxs(F1,{title:"Socket activity",actions:h,bodyClassName:"space-y-0.5 text-base",children:[r.jsx("div",{className:"h-64 overflow-y-auto font-mono text-xs",children:n.length===0?r.jsx("p",{children:"No socket activity yet."}):n.slice().reverse().map(f=>r.jsxs("div",{children:[r.jsx("span",{className:"text-slate-400",children:f.timestamp})," ",r.jsxs("span",{className:f.direction==="in"?"text-emerald-400":"text-amber-400",children:["[",f.direction.toUpperCase(),"]"]})," ",r.jsx("span",{className:"text-cyan-300",children:f.event})," ",r.jsx("span",{className:"text-slate-200",children:f.payload})]},f.id))}),r.jsx("p",{className:"text-xs text-slate-500",children:"Logs are collected only while this panel is open."})]})}function A7({label:t}){return t?r.jsx("span",{className:"rounded border border-white/40 px-1 text-[0.7rem] text-white",children:t}):null}const HP=[{key:"start",label:"Start OI"},{key:"safe",label:"Safe"},{key:"full",label:"Full"},{key:"passive",label:"Passive"},{key:"dock",label:"Dock"}],ko=[{key:"none",label:"Color"},{key:"grayscale",label:"Gray"},{key:"greenscale",label:"Green"}];function Zi(t){return ko.some(n=>n.key===t)?t:v0.colorFilter}function t0({children:t,className:n=""}){return r.jsx("label",{className:`mx-auto grid w-full max-w-lg grid-cols-[minmax(0,1fr)_auto] items-center gap-1.5 rounded bg-neutral-800/80 px-1.5 py-1 text-sm text-white max-[420px]:grid-cols-1 ${n}`,children:t})}function e7({children:t}){return r.jsx("p",{className:"mx-auto w-full max-w-lg text-xs leading-snug text-white",children:t})}function c7({label:t,value:n,disabled:l=!1,onChange:o}){return r.jsxs("label",{className:"mx-auto block w-full max-w-lg rounded bg-neutral-800/80 px-1.5 py-1 text-sm text-white",children:[r.jsxs("div",{className:"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-1.5",children:[r.jsx("span",{className:"min-w-0 font-semibold text-white",children:t}),r.jsxs("span",{className:"rounded bg-neutral-900 px-1 py-0.5 text-xs text-white",children:[Math.round(n*100),"%"]})]}),r.jsx("input",{type:"range",min:"0",max:"1",step:"0.01",value:n,onChange:o,className:"mt-1 w-full accent-emerald-500 disabled:opacity-50",disabled:l})]})}function Ro(){const{state:{keymap:t,roverId:n},actions:{sendOiCommand:l,setSensorStream:o}}=H2(),h=!!n,[f,m]=Mo(),p=X2(),{value:x,save:z}=E1("page",{hudMapDesktop:!1,connectionTransport:"websocket",swapMobileControlColumns:!1,driveMacroBackoffEnabled:!0}),{value:g,save:M}=E1("audio",o2),{value:y,save:S}=E1("video",v0),A=x?.connectionTransport||"websocket",H=!!x?.swapMobileControlColumns,C=typeof x?.driveMacroBackoffEnabled=="boolean"?x.driveMacroBackoffEnabled:!0,w=Number.isFinite(g?.masterVolume)?g.masterVolume:o2.masterVolume,j=Number.isFinite(g?.alertVolume)?g.alertVolume:o2.alertVolume,L=Number.isFinite(g?.roverVolume)?g.roverVolume:o2.roverVolume,B=typeof g?.mainBrushDuckEnabled=="boolean"?g.mainBrushDuckEnabled:typeof g?.autoLevelEnabled=="boolean"?g.autoLevelEnabled:o2.mainBrushDuckEnabled,E=Number.isFinite(g?.mainBrushDuckAmount)?Math.max(0,Math.min(1,g.mainBrushDuckAmount)):o2.mainBrushDuckAmount,O=Zi(y?.colorFilter),T=B2(t?.videoFilterCycle?.[0]),k=v.useMemo(()=>[{key:"start",label:"Enable stream",enable:!0},{key:"stop",label:"Disable stream",enable:!1}],[]),U=N=>{n&&o(N)},G=N=>{const _=N.target.value;z($=>({...$??{},connectionTransport:_})),p?.io?.opts&&(p.io.opts.transports=_==="polling"?["polling"]:["websocket","polling"],p.disconnect(),p.connect())},Y=N=>_=>{const $=Number(_.target.value),W=Number.isFinite($)?Math.max(0,Math.min(1,$)):0;M(K=>({...K??{},[N]:W}))},X=N=>{const _=!!N.target.checked;M($=>({...$??{},mainBrushDuckEnabled:_}))},d1=N=>{const _=!!N.target.checked;z($=>({...$??{},swapMobileControlColumns:_}))},o1=N=>{const _=!!N.target.checked;z($=>({...$??{},driveMacroBackoffEnabled:_}))},l1=N=>{const _=Zi(N.target.value);S($=>({...$??{},colorFilter:_}))};return r.jsxs(kc,{defaultTab:"keybindings",children:[r.jsxs(Rc,{children:[r.jsx(r4,{id:"keybindings",children:"Keybindings"}),r.jsx(r4,{id:"controller",children:"Controller"}),r.jsx(r4,{id:"page",children:"Page settings"}),r.jsx(r4,{id:"admin",children:"Admin"})]}),r.jsxs(Ec,{children:[r.jsx(i4,{id:"keybindings",children:r.jsx("div",{className:"space-y-0.5",children:r.jsx(lP,{})})}),r.jsx(i4,{id:"controller",children:r.jsx("div",{className:"space-y-0.5",children:r.jsx(pP,{})})}),r.jsx(i4,{id:"page",children:r.jsxs("div",{className:"grid gap-1.5 lg:grid-cols-2",children:[r.jsxs(F1,{title:"HUD",bodyClassName:"space-y-1 p-1 text-sm",children:[r.jsxs(t0,{className:"grid-cols-[auto_minmax(0,1fr)] max-[420px]:grid-cols-[auto_minmax(0,1fr)]",children:[r.jsx("input",{type:"checkbox",className:"h-3.5 w-3.5 accent-emerald-500",checked:f,onChange:N=>m(N.target.checked)}),r.jsx("span",{className:"font-semibold text-white",children:"Show top-down map in HUD (desktop)"})]}),r.jsx(e7,{children:"Mobile HUD keeps the map on by default."})]}),r.jsxs(F1,{title:"Video",bodyClassName:"space-y-1 p-1 text-sm",children:[r.jsxs(t0,{children:[r.jsx("span",{className:"font-semibold text-white",children:"Rover video filter"}),r.jsx("select",{value:O,onChange:l1,className:"field-input min-w-28 px-1 py-0.5 text-sm",children:ko.map(N=>r.jsx("option",{value:N.key,children:N.label},N.key))})]}),r.jsx(e7,{children:r.jsxs("span",{className:"inline-flex flex-wrap items-center gap-1",children:[r.jsx("span",{children:"Use"}),T?r.jsx(A7,{label:T}):null,r.jsx("span",{children:"to cycle filters. Applies only to rover camera pixels; HUD overlays stay full color."})]})})]}),r.jsx(F1,{title:"Mobile controls",bodyClassName:"space-y-1 p-1 text-sm",children:r.jsxs(t0,{className:"grid-cols-[auto_minmax(0,1fr)] max-[420px]:grid-cols-[auto_minmax(0,1fr)]",children:[r.jsx("input",{type:"checkbox",className:"h-3.5 w-3.5 accent-emerald-500",checked:H,onChange:d1}),r.jsx("span",{className:"font-semibold text-white",children:"Swap control columns (put joystick on the left)"})]})}),r.jsx(F1,{title:"Macros",bodyClassName:"space-y-1 p-1 text-sm",children:r.jsxs(t0,{className:"grid-cols-[auto_minmax(0,1fr)] max-[420px]:grid-cols-[auto_minmax(0,1fr)]",children:[r.jsx("input",{type:"checkbox",className:"h-3.5 w-3.5 accent-emerald-500",checked:C,onChange:o1}),r.jsx("span",{className:"font-semibold text-white",children:"Enable backward bump in drive macro"})]})}),r.jsxs(F1,{title:"Audio",className:"lg:col-span-2",bodyClassName:"space-y-1 p-1 text-sm",children:[r.jsx(c7,{label:"Master volume",value:w,onChange:Y("masterVolume")}),r.jsx(c7,{label:"Alert/page sounds",value:j,onChange:Y("alertVolume")}),r.jsx(c7,{label:"Rover audio",value:L,onChange:Y("roverVolume")}),r.jsxs(t0,{children:[r.jsx("span",{className:"font-semibold text-white",children:"Main brush ducking"}),r.jsx("input",{type:"checkbox",className:"h-3.5 w-3.5 accent-emerald-500",checked:B,onChange:X})]}),r.jsx(c7,{label:"Main brush duck amount",value:E,onChange:Y("mainBrushDuckAmount"),disabled:!B}),r.jsx(e7,{children:"Lowers rover audio only while the main brush is running."})]}),r.jsxs(F1,{title:"Connection",bodyClassName:"space-y-1 p-1 text-sm",children:[r.jsxs(t0,{children:[r.jsx("span",{className:"font-semibold text-white",children:"Transport"}),r.jsxs("select",{value:A,onChange:G,className:"field-input min-w-28 px-1 py-0.5 text-sm",children:[r.jsx("option",{value:"websocket",children:"WebSocket"}),r.jsx("option",{value:"polling",children:"Polling"})]})]}),r.jsx(e7,{children:"Switching reconnects your session."})]})]})}),r.jsx(i4,{id:"admin",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(F1,{title:"Manual OI commands",bodyClassName:"space-y-0.5 text-sm",children:r.jsx("div",{className:"flex flex-wrap gap-0.5",children:HP.map(N=>r.jsx("button",{type:"button",onClick:()=>l(N.key),disabled:!h,className:"button-dark text-xs disabled:opacity-30",children:N.label},N.key))})}),r.jsxs(F1,{title:"Sensor stream",bodyClassName:"space-y-0.5 text-sm",children:[r.jsx("div",{className:"flex gap-0.5",children:k.map(N=>r.jsx("button",{type:"button",onClick:()=>U(N.enable),disabled:!h,className:"flex-1 button-dark text-xs disabled:opacity-30",children:N.label},N.key))}),!h&&r.jsx("p",{className:"text-xs text-slate-500",children:"Assign a rover to toggle streams."})]}),r.jsx(Bo,{}),r.jsx(xP,{}),r.jsx(XI,{}),r.jsx(bP,{}),r.jsx(wP,{})]})})]})]})}const $i={desktop:{main:[{type:"list",title:"Chat and nicknames",items:["Set a nickname in the user list panel, on the bottom left of the page below the rover video.",{segments:["Toggle chat focus with ",{action:"chatFocus"},". Press ",{action:"chatFocus"}," again to send."]}]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button onscreen, or press ',{action:"driveMacro"}," on your keyboard to put the rover into driving mode."]},"Refer to the controls for the controls for the rover."]},{type:"list",title:"Docking the rover",items:['If the rover shows "Docking in Progress", it is already auto-seeking the dock.',"To dock manually, enter Docking Assist from the drive panel.",{segments:['Press "Enter Docking Assist", or press ',{action:"dockMacro"},"."]},"In assist mode, camera tilts down and driving speed is limited for precise alignment."]}],aside:[{type:"keyboard",title:"Keyboard controls",footnote:"Per-browser; adjust bindings and speeds in Settings → Keybindings.",groups:[{id:"movement",title:"Movement",items:[{action:"driveForward",label:"Forward"},{action:"driveBackward",label:"Backward"},{action:"driveLeft",label:"Turn left"},{action:"driveRight",label:"Turn right"},{action:"boostModifier",label:"Boost speed"},{action:"slowModifier",label:"Precision speed"}]},{id:"macros",title:"Rover modes & chat",items:[{action:"driveMacro",label:"Drive macro"},{action:"dockMacro",label:"Docking assist toggle"},{action:"chatFocus",label:"Chat focus"}]},{id:"camera",title:"Camera",items:[{action:"cameraUp",label:"Tilt up"},{action:"cameraDown",label:"Tilt down"},{action:"nightVisionToggle",label:"Toggle night vision"}]},{id:"motors",title:"Rover Aux Motors",items:[{action:"auxMainForward",label:"Main brush forward"},{action:"auxMainReverse",label:"Main brush reverse"},{action:"auxSideForward",label:"Side brush forward"},{action:"auxSideReverse",label:"Side brush reverse"},{action:"auxVacuumFast",label:"Vacuum max"},{action:"auxVacuumSlow",label:"Vacuum low"},{action:"auxAllForward",label:"All motors forward"}]},{id:"audio",title:"Audio / Song",items:[{action:"songNoteUp",label:"Song note up"},{action:"songNoteDown",label:"Song note down"}]},{id:"room-controls",title:"Room Controls",items:[{action:"homeAssistantOn",label:"Next room control on"},{action:"homeAssistantOff",label:"Next room control off (reverse)"}]}]},{type:"gamepad",title:"Gamepad / joystick",items:["Gamepad controls are fully configurable to handle inconsistent browser mappings.","Use Settings → Controller to bind inputs, calibrate deadzones, and view diagnostics.","If inputs behave oddly, the diagnostics panel will show raw axis/button values."]}]},"mobile-portrait":{main:[{type:"list",title:"Chat and nicknames",items:["Set a nickname in the user list panel below.","Tap in the chat box to send messages in the chat."]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button onscreen to put the rover into driving mode.']},"Look below the rover video. Use the joystick column to move the rover, and hold the aux buttons in the other control column."]},{type:"list",title:"Docking the rover",items:['If you see "Docking in Progress", the rover is currently auto-seeking the dock.',{segments:['For manual docking, press "Enter Docking Assist".']},"Assist mode tilts camera down and limits speed for precise alignment."]}]},"mobile-landscape":{main:[{type:"list",title:"Chat and nicknames",items:["Scroll down to see more of the page.","Set a nickname in the user list panel below.","Tap in the chat box to send messages in the chat."]},{type:"list",title:"Driving the rover",items:[{segments:['Press the "Start Driving" button, or the "Drive" button to put the rover into driving mode.']},"Use the joystick column beside the video feed to move the rover, and hold the aux buttons in the other control column."]},{type:"list",title:"Docking the rover",items:['If you see "Docking in Progress", the rover is currently auto-seeking the dock.',{segments:['For manual docking, press "Enter Docking Assist" (or "Dock" button).']},"Assist mode tilts camera down and limits speed for precise alignment."]}]}};function SP(t){return $i[t]||$i.desktop}function Eo({actionId:t,keymap:n}){const l=n?.[t]?.[0]??"";return r.jsx("span",{className:"rounded border border-slate-600 bg-slate-900/40 px-1 text-[0.7rem] text-slate-200",children:B2(l)})}function Gi(t,n){return t.map((l,o)=>typeof l=="string"?r.jsx("span",{children:l},`text-${o}`):l?.action?r.jsx(Eo,{actionId:l.action,keymap:n},`pill-${l.action}-${o}`):l?.text?r.jsx("span",{children:l.text},`span-${o}`):null)}function O7(t,n,l){if(typeof t=="string")return t;if(Array.isArray(t?.segments))return Gi(t.segments,n);if(t&&typeof t=="object"){if(typeof t.text=="string")return t.text;if(Array.isArray(t))return Gi(t,n)}return String(t??"")}function VP({hero:t,keymap:n}){return t?r.jsxs("div",{className:"surface space-y-0.5 px-0.5 py-0.5",children:[r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-0.5",children:[r.jsxs("div",{children:[r.jsx("p",{className:"text-sm font-semibold text-white",children:t.title}),t.subtitle&&r.jsx("p",{className:"text-xs text-slate-300",children:t.subtitle})]}),t.chips&&r.jsx("div",{className:"flex flex-wrap gap-0.5 text-[0.7rem] text-slate-200",children:t.chips.map(l=>r.jsx("span",{className:"rounded border border-slate-700 px-1 py-[2px]",children:l},l))})]}),t.bullets&&r.jsx("ul",{className:"space-y-0.5 text-[0.85rem] text-slate-200",children:t.bullets.map((l,o)=>{const h=Array.isArray(l?.segments)?`hero-${o}`:`hero-${o}`;return r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:O7(l,n)},h)})})]}):null}function LP({block:t,keymap:n}){return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs font-semibold text-slate-200",children:t.title}),r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:t.items.map((l,o)=>{const h=`item-${o}`;return r.jsx("li",{className:"surface-muted flex flex-wrap items-center gap-0.5 px-0.5 py-0.25",children:O7(l,n)},h)})})]})}function AP({block:t}){const n=t.tone==="info"?"border-cyan-500/40":"border-slate-700";return r.jsxs("div",{className:`surface space-y-0.5 border ${n} px-0.5 py-0.5`,children:[r.jsx("p",{className:"text-xs font-semibold text-slate-100",children:t.title}),t.body&&r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:t.body.map((l,o)=>r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:O7(l,{})},`callout-${o}`))})]})}function BP({group:t,keymap:n}){return r.jsxs("div",{className:"space-y-0.5 surface",children:[r.jsx("p",{className:"px-0.5 py-0.25 text-[0.75rem] font-semibold text-slate-200",children:t.title}),r.jsx("div",{className:"space-y-0.5 px-0.5 pb-0.25",children:t.items.map(l=>r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-0.5 py-0.25 text-[0.8rem]",children:[r.jsx("span",{className:"text-slate-200",children:l.label}),r.jsx(Eo,{actionId:l.action,keymap:n})]},l.action))})]})}function jP({block:t,keymap:n}){return t?r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-200",children:[r.jsx("span",{className:"font-semibold",children:t.title}),t.footnote&&r.jsx("span",{className:"text-[0.7rem] text-slate-400",children:t.footnote})]}),r.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-0.5",children:t.groups?.map(l=>r.jsx(BP,{group:l,keymap:n},l.id))})]}):null}function FP({block:t}){return t?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs font-semibold text-slate-200",children:t.title}),r.jsx("ul",{className:"space-y-0.5 text-[0.8rem] text-slate-300",children:t.items?.map((n,l)=>r.jsx("li",{className:"surface-muted px-0.5 py-0.25",children:O7(n,{})},`gamepad-${l}`))})]}):null}function Yi({block:t,keymap:n}){if(!t)return null;switch(t.type){case"list":return r.jsx(LP,{block:t,keymap:n});case"callout":return r.jsx(AP,{block:t});case"keyboard":return r.jsx(jP,{block:t,keymap:n});case"gamepad":return r.jsx(FP,{block:t});default:return null}}function To({layout:t,keymap:n}){const l=v.useMemo(()=>SP(t),[t]),o=n||{},h=l.main||[],f=l.aside||[];return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(VP,{hero:l.hero,keymap:o}),r.jsxs("div",{className:"grid gap-0.5",style:{gridTemplateColumns:"repeat(auto-fit, minmax(360px, 1fr))"},children:[r.jsx("div",{className:"space-y-0.5",children:h.map((m,p)=>r.jsx(Yi,{block:m,keymap:o},m.title||p))}),r.jsx("div",{className:"space-y-0.5",children:f.map((m,p)=>r.jsx(Yi,{block:m,keymap:o},m.title||p))})]})]})}function qo({layout:t,onOpenOverlay:n}){const{state:l}=H2(),o=r.jsx("button",{type:"button",onClick:n,className:"button-dark px-1 py-0.25 text-[0.75rem]",children:"Open full help"});return r.jsx(F1,{title:"Help",actions:o,bodyClassName:"space-y-0.5 text-sm",children:r.jsx(To,{layout:t,keymap:l?.keymap||{}})})}function _7({compact:t=!1}){const n=s1(M=>M.session?.role||null),{setNickname:l}=_2(),{value:o,save:h}=E1("profile",{nickname:""}),[f,m]=v.useState(o.nickname||""),[p,x]=v.useState(!1),z=n!=="spectator";v.useEffect(()=>{m(o.nickname||"")},[o.nickname]);async function g(M){if(M.preventDefault(),!z)return;const y=(f||"").trim().slice(0,32);if(y){x(!0);try{await l(y),h({nickname:y})}catch(S){alert(S.message)}finally{x(!1)}}}return r.jsxs("div",{className:"grid w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-stretch gap-0.5",children:[r.jsx("input",{className:"field-input flex-1 min-w-0",value:f,onChange:M=>m(M.target.value),onKeyDown:M=>{M.key==="Enter"&&(M.preventDefault(),g(M))},maxLength:32,placeholder:"Enter a nickname",disabled:!z}),r.jsx("button",{type:"button",onClick:g,disabled:!z||p,className:"button-dark h-full shrink-0 whitespace-nowrap px-0.5 py-0 disabled:opacity-50",children:p?"Saving…":t?"Set":"Save"})]})}const Ii=["kal","rms","slt","ksp","bdl"],Pi=["sfg","iob","iog","iol","iom","tpc","tpd","tpf"],NP=Array.from({length:10},(t,n)=>n*10),Qi=[.5,.75,1,1.25,1.5,1.75,2],e0=1;function U7({hideInput:t=!1,hideSpectatorNotice:n=!1,fillHeight:l=!1,allowSpectatorInput:o=!1,title:h="Chat and TTS",nicknameLayout:f="inline"}){const m=s1(J=>J.session?.role||null),p=s1(J=>J.session?.assignment?.roverId||null),x=s1(J=>J.session?.roster??[]),{messages:z,typing:g,sendMessage:M,registerInputRef:y,onInputFocus:S,onInputBlur:A,blurChat:H,setTypingActive:C}=wc(),{value:w,save:j}=E1("tts",{engine:"flite",voice:"rms",pitch:50,googlePitch:e0,googleSpeed:e0}),[L,B]=v.useState(""),[E,O]=v.useState(!1),[T,k]=v.useState(!1),[U,G]=v.useState(()=>w?.engine||"flite"),[Y,X]=v.useState(()=>w?.voice||"rms"),[d1,o1]=v.useState(()=>Number.isFinite(w?.pitch)?w.pitch:50),[l1,N]=v.useState(()=>Number.isFinite(w?.googlePitch)?w.googlePitch:e0),[_,$]=v.useState(()=>Number.isFinite(w?.googleSpeed)?w.googleSpeed:e0),W=m!=="spectator"||o,K=v.useRef(null),D=!!v.useMemo(()=>x.find(J=>String(J.id)===String(p))||null,[p,x])?.audio?.ttsEnabled,c1=v.useMemo(()=>z.slice(-200),[z]),u1=v.useMemo(()=>g||[],[g]);v.useEffect(()=>{K.current&&(K.current.scrollTop=K.current.scrollHeight)},[c1,u1]),v.useEffect(()=>{const J=w?.engine||"flite",f1=w?.voice||"rms",g1=Number.isFinite(w?.pitch)?w.pitch:50,z1=Number.isFinite(w?.googlePitch)?w.googlePitch:e0,j1=Number.isFinite(w?.googleSpeed)?w.googleSpeed:e0;U!==J&&G(J),Y!==f1&&X(f1),d1!==g1&&o1(g1),l1!==z1&&N(z1),_!==j1&&$(j1)},[U,l1,_,d1,w?.engine,w?.googlePitch,w?.googleSpeed,w?.pitch,w?.voice,Y]),v.useEffect(()=>{k(!!D)},[D]);const p1=v.useMemo(()=>!D||!T?null:U==="espeak"?{speak:!0,engine:U,pitch:d1}:U==="chromegtts"?{speak:!0,engine:U,voice:Y,pitch:l1,speed:_}:{speak:!0,engine:U,voice:Y},[U,l1,_,d1,T,D,Y]);async function H1(J){if(J.preventDefault(),!W||t)return;const g1=L.replace(/\\n/g,` -`).trim();if(g1){O(!0);try{await M(g1,p1),B(""),H(),C(!1)}catch(z1){alert(z1.message)}finally{O(!1)}}}const x1=l?"flex-1 min-h-0 overflow-y-auto":"h-48 overflow-y-auto",k1=f==="stacked";return r.jsxs(F1,{title:h,hideHeader:!h,fillHeight:l,bodyClassName:"space-y-0.5 text-base",children:[r.jsxs("div",{className:`overflow-y-auto space-y-0.5 px-0 ${x1}`,ref:K,children:[c1.length===0&&u1.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"No messages yet."}):c1.map(J=>r.jsx(bc,{message:J},J.id)),u1.map(J=>r.jsx(Is,{message:J},`typing-${J.typingId||J.id}`))]}),!t&&r.jsxs("form",{className:`flex items-stretch gap-0.5 ${k1?"flex-col":"flex-wrap"}`,onSubmit:H1,children:[r.jsx("div",{className:k1?"w-full":"w-[9rem] sm:w-[10rem] shrink-0",children:r.jsx(_7,{compact:!0})}),r.jsx("input",{className:`field-input ${k1?"w-full min-w-0":"flex-1 min-w-[10rem]"}`,value:L,onChange:J=>{const f1=J.target.value;B(f1),C(!!f1.trim())},onFocus:J=>{S(J),C(!!L.trim())},onBlur:J=>{A(J),C(!1)},onKeyDown:J=>{J.key==="Enter"&&!L.trim()&&(J.preventDefault(),H(),C(!1))},ref:J=>y(J,{target:"panel"}),placeholder:W?"Type a message…":n?"":"Spectators cannot chat",disabled:!W}),D&&r.jsxs("div",{className:`flex flex-wrap items-center gap-0.5 ${k1?"w-full":"basis-full sm:basis-auto"}`,children:[r.jsxs("label",{className:"flex items-center gap-0.5 text-xs text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:T,onChange:J=>k(J.target.checked),className:"accent-cyan-500"}),r.jsx("span",{children:"Speak"})]}),r.jsxs("select",{value:U,onChange:J=>{const f1=J.target.value,g1=f1==="chromegtts"&&!Pi.includes(Y)?"tpf":f1==="flite"&&!Ii.includes(Y)?"rms":Y;G(f1),g1!==Y&&X(g1),j(z1=>({...z1||{},engine:f1,voice:g1}))},className:"field-input text-xs",children:[r.jsx("option",{value:"flite",children:"flite"}),r.jsx("option",{value:"espeak",children:"espeak"}),r.jsx("option",{value:"chromegtts",children:"Google TTS"})]}),U==="flite"||U==="chromegtts"?r.jsxs(r.Fragment,{children:[r.jsx("select",{value:Y,onChange:J=>{const f1=J.target.value;X(f1),j(g1=>({...g1||{},voice:f1}))},className:"field-input text-xs",children:(U==="chromegtts"?Pi:Ii).map(J=>r.jsx("option",{value:J,children:J},J))}),U==="chromegtts"&&r.jsxs(r.Fragment,{children:[r.jsx("select",{value:l1,onChange:J=>{const f1=Number(J.target.value);N(f1),j(g1=>({...g1||{},googlePitch:f1}))},className:"field-input text-xs",children:Qi.map(J=>r.jsxs("option",{value:J,children:["pitch ",J.toFixed(2)]},`pitch-${J}`))}),r.jsx("select",{value:_,onChange:J=>{const f1=Number(J.target.value);$(f1),j(g1=>({...g1||{},googleSpeed:f1}))},className:"field-input text-xs",children:Qi.map(J=>r.jsxs("option",{value:J,children:["speed ",J.toFixed(2)]},`speed-${J}`))})]})]}):r.jsx("select",{value:d1,onChange:J=>{const f1=Number(J.target.value);o1(f1),j(g1=>({...g1||{},pitch:f1}))},className:"field-input text-xs",children:NP.map(J=>r.jsxs("option",{value:J,children:["pitch ",J]},J))})]}),r.jsx("button",{type:"submit",disabled:!W||E,className:`button-dark disabled:opacity-50 ${k1?"w-full h-8":"h-full self-stretch"}`,children:E?"...":"Send"})]})]})}function Tc({className:t=""}){const n=s1(l=>l.session?.socials??[]).slice(0,4);return n.length?r.jsx("div",{className:`grid grid-cols-2 grid-rows-2 gap-0.5 ${t}`,children:n.map(l=>r.jsx(T7,{id:l?.id||l?.key||l?.service||null},`${l?.id||l?.label||"social"}-${l?.url||""}`))}):null}function kP(){return r.jsx(F1,{title:"Links!",fillHeight:!0,bodyClassName:"flex flex-1 min-h-0 flex-col gap-0.5 text-base",children:r.jsx(Tc,{className:"flex-1 min-h-0"})})}function Re(...t){return t.filter(Boolean).join(" ")}function RP(t){const n=t?.batteryState?.percentDisplay;return n==null?"--":`${n}%`}function EP(t){return t?.batteryState?t.batteryState.urgentActive?"text-red-400":t.batteryState.warnActive?"text-amber-300":"text-emerald-300":"text-slate-400"}function TP(t){switch(t){case"admin":case"lockdown":case"lockdown-admin":return"text-amber-300";case"spectator":return"text-slate-400";default:return"text-sky-300"}}function qP(t,n){if(!t)return"";const l=t.nickname||t.socketId?.slice(0,6)||"unknown";return t.socketId&&t.socketId===n?`${l} (you)`:l}function Z7({title:t="Rovers"}){const n=s1(k=>k.session?.role||null),l=s1(k=>k.session?.roster??[]),o=s1(k=>k.session?.turnQueues??{}),h=s1(k=>k.session?.users??[]),f=s1(k=>k.session?.socketId||null),m=s1(k=>String(k.session?.assignment?.roverId||"").trim()),p=s1(k=>{const U=String(k.session?.assignment?.roverId||"").trim();return U?(k.session?.roster||[]).find(Y=>String(Y?.id)===U)?.name||U:""}),{requestControl:x,rebootOwnRover:z}=_2(),[g,M]=v.useState({}),[y,S]=v.useState(!1),[A,H]=v.useState(()=>Date.now()),C=v.useMemo(()=>n&&n!=="spectator",[n]),w=v.useMemo(()=>n==="admin"||n==="lockdown"||n==="lockdown-admin",[n]),j=v.useMemo(()=>Object.values(o||{}).some(k=>k?.deadline||k?.idleDeadline),[o]);v.useEffect(()=>{if(!j)return;const k=setInterval(()=>{H(Date.now())},1e3);return()=>clearInterval(k)},[j]);const L=v.useMemo(()=>{const k=new Set(l.map(G=>String(G.id))),U=Object.keys(o||{}).filter(G=>!k.has(String(G))).map(G=>({id:G,name:G,locked:!1,batteryState:null}));return[...l,...U]},[l,o]);async function B(k){if(k){M(U=>({...U,[k]:!0}));try{await x(k)}catch(U){alert(U.message)}finally{M(U=>({...U,[k]:!1}))}}}const E=k=>h.find(U=>U.socketId===k)||{socketId:k,nickname:null,role:null};async function O(){if(!(y||!window.confirm(`Reboot your rover "${p}" now?`))){S(!0);try{await z(),alert("Reboot command sent.")}catch(U){alert(U.message)}finally{S(!1)}}}const T=n!=="spectator"&&m?r.jsx("button",{type:"button",onClick:O,disabled:y,className:"button-dark bg-amber-700/80 text-amber-50 hover:bg-amber-600 disabled:opacity-40",title:"Reboot your assigned rover",children:y?"Rebooting...":"Reboot My Rover"}):null;return r.jsx(F1,{title:t,actions:T,bodyClassName:"space-y-0.5 text-sm",children:L.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"No rovers registered."}):r.jsx("ul",{className:"space-y-0.5 text-sm",children:L.map(k=>{const U=String(k.id),G=o?.[U]||null,Y=G?.queue||[],X=G?.idleDeadline||G?.deadline||null,d1=X&&X>A?Math.ceil((X-A)/1e3):X?0:null,o1=G?.current||null,l1=o1?Y.findIndex(x1=>x1===o1):-1,N=Y.length>1?l1>=0?Y[(l1+1)%Y.length]:Y[0]:null,_=!!(f&&o1&&o1===f),$=!!(f&&N&&N===f),W=d1!=null&&(_||$),K=!!(k?.private?.enabled&&k?.private?.open),F=!!(k?.private?.enabled&&!k?.private?.open),D=!!k.locked,c1=D&&!w&&!F,u1=k.lockReason?`locked: ${k.lockReason}`:"locked",p1=g[U]?"...":D&&!F?u1:"request",H1=C&&!c1&&!g[U];return r.jsxs("li",{className:Re("surface flex flex-wrap items-start justify-between gap-0.5",H1&&"cursor-pointer",D?"bg-red-900/40":K?"bg-amber-700/35 border border-amber-200/30":null),onClick:()=>{H1&&B(k.id)},children:[r.jsxs("div",{className:"flex min-w-0 flex-1 flex-col gap-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsxs("div",{className:"flex min-w-0 items-center gap-0.5",children:[r.jsxs("p",{className:"min-w-0 flex items-center gap-0.5 whitespace-nowrap text-slate-200",children:[r.jsx(A4,{rover:k,fallback:U}),k.description?r.jsx("span",{className:"min-w-0 flex-1 truncate text-[0.7rem] text-slate-400",children:k.description}):null]}),W?r.jsx("span",{className:"rounded bg-slate-800 px-1 text-[0.7rem] text-slate-200",children:_?`${d1}s left`:`Your turn in ${d1}s`}):null]}),r.jsx("span",{className:Re("text-[0.75rem] font-semibold",EP(k)),children:RP(k)})]}),Y.length===0?r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"No queue."}):r.jsx("div",{className:"flex flex-wrap items-center gap-0.5",children:Y.map((x1,k1)=>{const J=E(x1),f1=x1===o1,g1=!!(N&&x1===N&&!f1),z1=f1?"bg-sky-600 text-white ring-2 ring-amber-300":g1?"bg-emerald-700/60 text-emerald-100 ring-1 ring-emerald-300/70":"bg-slate-800 text-slate-200";return r.jsxs("span",{className:`flex items-center gap-0.5 rounded px-1 text-[0.7rem] ${z1}`,children:[r.jsx("span",{className:`${TP(J.role)} font-semibold`,children:qP(J,f)}),f1&&r.jsx("span",{className:"text-[0.65rem] text-slate-200",children:"now"}),g1&&r.jsx("span",{className:"text-[0.65rem] text-emerald-100",children:"next"})]},`${U}-${x1}-${k1}`)})})]}),C?r.jsx("button",{type:"button",onClick:x1=>{x1.stopPropagation(),B(k.id)},disabled:g[U]||c1,className:Re("button-dark disabled:opacity-40",D&&"bg-red-600/70 text-white hover:bg-red-600"),children:p1}):null]},k.id)})})})}function DP(t){switch(t){case"admin":case"lockdown":case"lockdown-admin":return"text-amber-300";case"spectator":return"text-slate-400";default:return"text-sky-300"}}function OP(t,n){if(!t)return"";const l=t.nickname||t.socketId?.slice(0,6)||"unknown";return t.socketId&&t.socketId===n?`${l} (you)`:l}function B7({hideNicknameForm:t=!1,hideHeader:n=!1,className:l="",fillHeight:o=!1,compact:h=!1}){const f=s1(M=>M.session?.role||null),m=s1(M=>M.session?.users??[]),p=s1(M=>M.session?.socketId||null),x=f!=="spectator",z=v.useMemo(()=>[...m].sort((M,y)=>M.socketId===p?-1:y.socketId===p?1:(M.nickname||"").localeCompare(y.nickname||"")),[p,m]),g=o?"flex-1 min-h-0 overflow-y-auto":h?"h-28 overflow-y-auto":"h-48 overflow-y-auto";return r.jsxs(F1,{title:n?"":"Users",hideHeader:n,fillHeight:o,className:l,bodyClassName:"space-y-0.5 text-base",children:[!t&&r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"grid gap-0.5 md:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]",children:[r.jsx("div",{className:"min-w-0",children:r.jsx("div",{className:"flex w-full items-center px-0 py-0",children:r.jsx(_7,{compact:h})})}),r.jsx(Tc,{})]}),!x&&r.jsx("p",{className:"text-xs text-slate-500",children:"Spectators cannot set nicknames."})]}),r.jsx("div",{className:`space-y-0.5 ${o?"flex flex-1 min-h-0 flex-col":""}`,children:r.jsx("div",{className:`flex flex-wrap content-start items-start gap-0.5 px-0 pb-0 ${g} ${h?"text-[0.8rem]":""}`,children:z.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"Waiting for users…"}):z.map(M=>r.jsx("span",{className:`rounded px-1 py-0.25 text-[0.7rem] ${DP(M.role)}`,children:OP(M,p)},M.socketId))})})]})}const _P=/^cu_[a-f0-9]{32}$/,u0="field-input w-full max-w-sm text-left focus:ring-emerald-500",j8="mx-auto w-full max-w-xl flex justify-center",l0="mx-auto flex w-full max-w-md flex-col items-center space-y-0.5 text-center";function UP(t){const n=String(t||"").trim();return n?n.length<=10?`${n.slice(0,2)}***${n.slice(-2)}`:`${n.slice(0,6)}...${n.slice(-6)}`:""}const Xi=8*1024*1024,r6=16e3,ZP={iceServers:[{urls:"stun:stun.l.google.com:19302"}],bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"};function Ki(t){let n="";for(let o=0;o{if(!t||t.iceGatheringState==="complete"){l();return}const o=setTimeout(()=>{t.removeEventListener("icegatheringstatechange",h),l()},n);function h(){t.iceGatheringState==="complete"&&(clearTimeout(o),t.removeEventListener("icegatheringstatechange",h),l())}t.addEventListener("icegatheringstatechange",h)})}function Ji(t){if(!t)return!1;const n=t.connectionState,l=t.iceConnectionState;return n==="connected"||l==="connected"||l==="completed"}function IP(t,n=1e4){return new Promise((l,o)=>{if(!t){o(new Error("Peer connection missing"));return}if(Ji(t)){l();return}const h=setTimeout(()=>{m(),o(new Error("Peer connection timeout"))},n),f=()=>{Ji(t)?(m(),l()):(t.connectionState==="failed"||t.connectionState==="closed"||t.iceConnectionState==="failed")&&(m(),o(new Error(`Peer connection ${t.connectionState||t.iceConnectionState}`)))};function m(){clearTimeout(h),t.removeEventListener("connectionstatechange",f),t.removeEventListener("iceconnectionstatechange",f)}t.addEventListener("connectionstatechange",f),t.addEventListener("iceconnectionstatechange",f)})}async function PP(t){if(!t?.getParameters||!t?.setParameters)return;const n=t.getParameters()||{},l=n.encodings&&n.encodings[0]||{};n.encodings=[{...l,maxBitrate:64e3,dtx:"disabled"}];try{await t.setParameters(n)}catch{}}function QP(t,n=6e3){return new Promise((l,o)=>{if(!t){o(new Error("Peer connection missing"));return}const h=Date.now();let f=-1;const m=setInterval(async()=>{if(Date.now()-h>n){clearInterval(m),o(new Error("WHIP connected but no outbound audio flow"));return}try{const p=t.getSenders().filter(x=>x.track?.kind==="audio");for(const x of p){const z=await x.getStats();for(const g of z.values()){if(g.type!=="outbound-rtp"||g.kind!=="audio")continue;const M=Number(g.bytesSent||0),y=Number(g.packetsSent||0);if(f<0)f=M;else if(M>f+200||y>5){clearInterval(m),l();return}}}}catch{}},250)})}function XP(t){const n=t.reduce((h,f)=>h+f.length,0),l=new Float32Array(n);let o=0;for(const h of t)l.set(h,o),o+=h.length;return l}function KP(t){const n=new Int16Array(t.length);for(let l=0;l{for(let g=0;gg&&n?.[g]||null,[n,g]),c1=!!(D&&D.state!=="offline"&&!D.error),u1=D?.source==="upload"&&D?.state==="playing",p1=D?.source==="mic-whip"&&(D?.state==="starting"||D?.state==="playing"),H1=!!(!w&&U.current&&(H||F)),x1=!w&&(j==="live"||j==="starting"),k1=w&&B==="recording",J=w&&B==="sending",f1=B2(p?.keymap?.micPtt?.[0])||"M",g1=v.useCallback(r1=>{const v1=r1==="clip"?"clip":"live";z(y1=>({...y1||{},pttMode:v1}))},[z]),z1=async()=>{if(!g){T("Take control of your rover first.");return}if(!M){T("Select an audio file first.");return}if(M.size>Xi){T(`File too large (max ${Xi} bytes).`);return}A(!0),T("");try{const r1=await M.arrayBuffer(),v1=Ki(new Uint8Array(r1));await l?.({roverId:g,name:M.name,mime:M.type||"",dataBase64:v1}),T("Upload playback started.")}catch(r1){T(r1?.message||"Failed to play upload.")}finally{A(!1)}},j1=async()=>{if(!g){T("Take control of your rover first.");return}A(!0),T("");try{await o?.(g),T("Upload playback stopped.")}catch(r1){T(r1?.message||"Failed to stop upload.")}finally{A(!1)}},D1=v.useCallback(async r1=>{const v1=String(r1||X.current||"").trim();if(Y.current=!1,L("idle"),G.current){try{G.current.getSenders().forEach(y1=>y1.track?.stop())}catch{}try{G.current.close()}catch{}}if(G.current=null,k.current)try{k.current.getTracks().forEach(y1=>y1.stop())}catch{}if(k.current=null,U.current=null,v1)try{await m?.(v1)}catch{}X.current=""},[m]),b1=v.useCallback(async r1=>{const v1=await h?.(r1),y1=String(v1?.whipUrl||"").trim(),O1=String(v1?.token||"").trim();if(!y1||!O1)throw new Error("WHIP endpoint unavailable");const A1=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,sampleRate:r6,echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1}});k.current=A1;const h1=A1.getAudioTracks()?.[0];if(U.current=h1||null,h1&&(h1.enabled=!!(H||F)),h1?.applyConstraints)try{await h1.applyConstraints({channelCount:1,sampleRate:r6,echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1})}catch{}const m1=new RTCPeerConnection(ZP);G.current=m1,m1.onconnectionstatechange=()=>{if(!Y.current)return;const m2=m1.connectionState;if(m2==="connected"){L("live");return}(m2==="failed"||m2==="disconnected"||m2==="closed")&&(L("error"),T(`WHIP transport ${m2}.`))},A1.getAudioTracks().forEach(m2=>{const h6=m1.addTrack(m2,A1);PP(h6)});const B1=await m1.createOffer({offerToReceiveAudio:!1,offerToReceiveVideo:!1});await m1.setLocalDescription(B1),await YP(m1,1800);const V2=await fetch(y1,{method:"POST",headers:{"Content-Type":"application/sdp",...GP(O1)},body:m1.localDescription?.sdp||B1.sdp});if(!V2.ok)throw new Error(`WHIP request failed: ${V2.status}`);const J2=await V2.text();await m1.setRemoteDescription({type:"answer",sdp:J2}),await IP(m1,1e4),await QP(m1,6e3),await f?.(r1)},[H,F,f,h]),S1=v.useCallback(async()=>{if(W.current=!1,N.current){try{N.current.disconnect()}catch{}N.current.onaudioprocess=null}if(l1.current)try{l1.current.disconnect()}catch{}if(_.current)try{_.current.disconnect()}catch{}if(d1.current)try{d1.current.getTracks().forEach(r1=>r1.stop())}catch{}if(o1.current)try{await o1.current.close()}catch{}$.current=[],N.current=null,l1.current=null,_.current=null,d1.current=null,o1.current=null,K.current=r6},[]),X1=v.useCallback(async()=>{if(d1.current&&o1.current&&N.current)return;E(h1=>h1==="recording"?h1:"arming");const r1=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,sampleRate:r6,echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1}}),v1=new AudioContext({latencyHint:"interactive"}),y1=v1.createMediaStreamSource(r1),O1=v1.createScriptProcessor(1024,1,1),A1=v1.createGain();A1.gain.value=0,O1.onaudioprocess=h1=>{if(!W.current)return;const m1=h1.inputBuffer.getChannelData(0);$.current.push(new Float32Array(m1))},y1.connect(O1),O1.connect(A1),A1.connect(v1.destination),d1.current=r1,o1.current=v1,l1.current=y1,N.current=O1,_.current=A1,K.current=v1.sampleRate||r6},[]),x2=v.useCallback(async({send:r1})=>{const v1=W.current;if(W.current=!1,!v1){r1||E("idle");return}const y1=$.current;if($.current=[],!r1){E("idle");return}if(!g){E("idle");return}if(!y1.length){E("idle");return}E("sending");try{const O1=XP(y1),A1=JP(O1,K.current||r6),h1=Ki(A1);await l?.({roverId:g,name:`ptt-${Date.now()}.wav`,mime:"audio/wav",dataBase64:h1}),E("idle")}catch(O1){E("error"),T(O1?.message||"Failed to send PTT clip.")}},[l,g]),i1=v.useCallback(async()=>{if(!g){T("Take control of your rover first.");return}await X1(),$.current=[],W.current=!0,E("recording")},[X1,g]);return v.useEffect(()=>{let r1=!1;async function v1(){if(w){await D1(X.current||g);return}if(!g){await D1(X.current);return}if(!(Y.current&&X.current===g)&&!(!H&&!F))try{L("starting"),T(""),Y.current=!0,X.current=g,await b1(g),r1||L("live")}catch(y1){r1||(L("error"),T(y1?.message||"Failed to start mic forwarding.")),await D1(g)}}return v1(),()=>{r1=!0}},[w,H,F,g,b1,D1]),v.useEffect(()=>{const r1=U.current;r1&&(r1.enabled=!!(!w&&(H||F)))},[w,H,F]),v.useEffect(()=>{let r1=!1;async function v1(){if(!w||!g){W.current&&await x2({send:!1}),E("idle");return}try{await X1(),!r1&&!W.current&&E("idle")}catch(y1){r1||(E("error"),T(y1?.message||"Microphone access failed for clip mode."))}}return v1(),()=>{r1=!0}},[w,X1,x2,g]),v.useEffect(()=>{let r1=!1;async function v1(){if(w){if(!g){W.current&&await x2({send:!1});return}if(F){if(!W.current)try{T(""),await i1()}catch(y1){r1||(E("error"),T(y1?.message||"Failed to start clip recording."))}}else W.current&&await x2({send:!0})}}return v1(),()=>{r1=!0}},[w,x2,F,g,i1]),v.useEffect(()=>()=>{D1(X.current||g),S1()},[g,D1,S1]),r.jsx(F1,{title:"Audio Controls",children:r.jsxs("div",{className:"grid gap-1",children:[r.jsx("section",{className:"surface",children:r.jsxs("div",{className:"flex items-center justify-center gap-0.5 py-0.25 text-xs text-slate-300",children:[r.jsx("span",{children:"Push-to-Talk Key"}),r.jsx(A7,{label:f1})]})}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-1 lg:grid-cols-2",children:[r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_auto_1fr]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"PTT Mode"}),r.jsx("p",{className:"text-xs text-slate-400 text-center",children:"Choose how holding your PTT key behaves: live mic stream, or record-and-send clip. If the live stream doesn't work, switch to clip mode."}),r.jsxs("div",{className:"grid grid-cols-2 gap-0.5",children:[r.jsx("button",{type:"button",className:`button-dark w-full text-sm ${w?"":"bg-emerald-500 text-white hover:bg-emerald-500"}`,onClick:()=>g1("live"),children:"Live Mic (WHIP)"}),r.jsx("button",{type:"button",className:`button-dark w-full text-sm ${w?"bg-emerald-500 text-white hover:bg-emerald-500":""}`,onClick:()=>g1("clip"),children:"Record Clip"})]})]})}),r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_1fr]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"PTT Clip Status"}),r.jsxs("div",{className:"grid gap-0.5 content-start",children:[r.jsx(M4,{label:"Mode",active:w,detail:w?"record clip":"live mic"}),r.jsx(M4,{label:"PTT hold",active:F,detail:F?"held":"released"}),r.jsx(M4,{label:"Recording",active:k1,detail:k1?"capturing":"idle"}),r.jsx(M4,{label:"Playback",active:w&&u1,detail:w&&u1?"playing":"idle"})]})]})})]}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-1 lg:grid-cols-2",children:[r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_auto_1fr_auto]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"Audio Upload"}),r.jsxs("label",{className:"mx-auto grid w-full max-w-sm gap-0.5 text-xs text-slate-300 text-center",children:[r.jsx("span",{children:"Audio file (mp3 / wav / ogg)"}),r.jsx("input",{className:`${u0} text-center`,type:"file",accept:".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg",disabled:S||!g,onChange:r1=>y(r1.target.files?.[0]||null)})]}),r.jsx("div",{className:"mx-auto w-full max-w-sm text-center",children:M?r.jsxs("div",{className:"surface-muted text-xs text-slate-300",children:[M.name," (",M.size," bytes)"]}):r.jsx("div",{className:"surface-muted text-xs text-slate-500",children:"No file selected"})}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:S||!g,onClick:z1,children:S?"Working...":"Play Upload"}),r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:S||!g,onClick:j1,children:"Stop"})]})]})}),r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_auto_1fr]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"Live Microphone"}),r.jsxs("label",{className:"surface-muted mx-auto flex w-full max-w-sm items-center justify-center gap-0.5 px-0.5 py-0.5 text-xs text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:H,disabled:!g||w,onChange:r1=>z(v1=>({...v1||{},openMicEnabled:!!r1.target.checked}))}),r.jsx("span",{children:w?"Open mic (live mode only)":"Open mic"})]}),r.jsxs("div",{className:"grid gap-0.5 content-start",children:[r.jsx(M4,{label:"WHIP link",active:x1,detail:j}),r.jsx(M4,{label:"Mic hot",active:H1,detail:H1?"transmitting":"muted"}),r.jsxs("div",{className:"surface-muted text-center text-xs text-slate-400",children:["Hold ",r.jsx(A7,{label:f1})," to talk"]})]})]})})]}),r.jsx("section",{className:"surface",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"Audio Status"}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-6",children:[r.jsx(M4,{label:"Forward pipe",active:c1,detail:c1?"connected":"offline"}),r.jsx(M4,{label:"Upload playback",active:u1,detail:u1?"playing":"idle"}),r.jsx(M4,{label:"Mic relay",active:p1,detail:p1?"active":"idle"}),r.jsx(M4,{label:"WHIP transport",active:x1,detail:j}),r.jsx(M4,{label:"Clip recording",active:k1,detail:k1?"recording":"idle"}),r.jsx(M4,{label:"Clip sending",active:J,detail:J?"sending":"idle"})]}),O?r.jsx("div",{className:"text-xs text-slate-400 text-center",children:O}):null]})})]})})}function tQ({pendingRequestId:t,currentStoredKey:n,nickname:l,requestVerification:o,applyIdentityKey:h,onMessage:f,fullWidth:m=!1}){const[p,x]=v.useState(0),[z,g]=v.useState(""),[M,y]=v.useState(!1),[S,A]=v.useState(!1),H=()=>{x(1),g(n),y(!1),f?.("")},C=()=>{x(0),y(!1)},w=async L=>{if(L.preventDefault(),!M){f?.("Please confirm your nickname agreement before sending.");return}if(p<3){f?.("Complete all request steps before sending.");return}A(!0),f?.("");try{const B=await h(z);await o?.(),g(B),x(0),y(!1),f?.("Verification request sent to lockdown admins.")}catch(B){f?.(B.message||"Failed to submit request.")}finally{A(!1)}},j=m?"w-full":j8;return t?r.jsx(F1,{title:"Verification",className:j,bodyClassName:"text-sm text-slate-300",children:r.jsxs("div",{className:l0,children:["Verification request pending: ",t]})}):p===0?r.jsx(F1,{title:"Verification",className:j,children:r.jsx("div",{className:l0,children:r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:H,disabled:S,children:"Request Verification"})})}):r.jsx(F1,{title:"Request verification",className:j,children:r.jsx("form",{onSubmit:w,children:r.jsxs("div",{className:l0,children:[r.jsxs("p",{className:"text-xs text-slate-500",children:["Step ",p," of 3"]}),p===1?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Confirm your nickname, which is used as part of the verification process. You can edit it here before requesting."}),r.jsx("div",{className:"mx-auto w-full max-w-sm",children:r.jsx(_7,{compact:!0})}),r.jsxs("div",{className:"surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center",children:["Current nickname: ",r.jsx("span",{className:"font-semibold",children:l||"(not set)"})]}),r.jsxs("label",{className:"flex items-center justify-center gap-0.5 text-xs text-slate-300 text-center",children:[r.jsx("input",{type:"checkbox",className:"accent-emerald-500",checked:M,onChange:L=>y(L.target.checked)}),r.jsx("span",{children:"I understand this nickname is tied to my verification."})]}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!l||!M,onClick:()=>x(2),children:"Continue"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:C,children:"Cancel"})]})]}):null,p===2?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Save your identity key in a safe place. You can use it to restore your identity in another browser."}),r.jsx("input",{className:u0,type:"password",name:"identity_key_request",autoComplete:"current-password",maxLength:35,value:z,onChange:L=>g(L.target.value.toLowerCase()),placeholder:"Identity key for this request"}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!z,onClick:()=>navigator.clipboard?.writeText(z),children:"Copy Key"}),r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!z,onClick:()=>x(3),children:"Continue"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>x(1),children:"Back"})]})]}):null,p===3?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Final step: confirm and send your verification request."}),r.jsx("input",{className:u0,type:"password",name:"identity_key_request_final",autoComplete:"current-password",maxLength:35,value:z,onChange:L=>g(L.target.value.toLowerCase()),placeholder:"Identity key for this request"}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"submit",className:"button-dark text-sm",disabled:S||!z,children:"Confirm Request"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>x(2),children:"Back"})]})]}):null]})})})}function eQ({currentStoredKey:t,applyIdentityKey:n,onMessage:l,fullWidth:o=!1}){const[h,f]=v.useState(0),[m,p]=v.useState(""),[x,z]=v.useState(!1),g=()=>{f(1),p(""),l?.("")},M=()=>{f(0),p("")},y=async A=>{if(A.preventDefault(),!(h<2)){z(!0),l?.("");try{await n(m),f(0),p(""),l?.("Identity key restored.")}catch(H){l?.(H.message||"Failed to restore key.")}finally{z(!1)}}},S=o?"w-full":j8;return r.jsx(F1,{title:"Identity key",className:S,children:r.jsxs("div",{className:l0,children:[r.jsxs("p",{className:"text-xs text-slate-500",children:["Current: ",UP(t)||"not set yet"]}),r.jsx("input",{className:u0,type:"password",name:"identity_key_current",autoComplete:"current-password",maxLength:35,value:t,readOnly:!0,placeholder:"Identity key"}),h===0?r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!t,onClick:()=>navigator.clipboard?.writeText(t),children:"Copy Key"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:g,disabled:x,children:"Restore Key"})]}):null,h===1?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Restoring your key should only be done when needed. Use this to move your identity to another browser."}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>f(2),children:"Continue"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:M,children:"Cancel"})]})]}):null,h===2?r.jsxs("form",{className:"space-y-0.5",onSubmit:y,children:[r.jsx("input",{className:u0,type:"password",name:"identity_key_restore",autoComplete:"current-password",maxLength:35,value:m,onChange:A=>p(A.target.value.toLowerCase()),placeholder:"Paste key to restore"}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"submit",className:"button-dark text-sm",disabled:x||!m,children:"Confirm Restore"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>f(1),children:"Back"})]})]}):null]})})}function cQ({requestableRovers:t=[],pendingRequests:n=[],requestPrivateRoverAccess:l,onMessage:o,fullWidth:h=!1}){const[f,m]=v.useState({}),p=v.useMemo(()=>{const g=new Map;return(n||[]).forEach(M=>{M?.roverId&&g.set(String(M.roverId),M)}),g},[n]),x=h?"w-full":j8,z=async g=>{if(g){m(M=>({...M,[g]:!0})),o?.("");try{(await l?.(g))?.existing?o?.("You already have a pending request for that rover."):o?.("Private rover access request sent to lockdown admins.")}catch(M){o?.(M.message||"Failed to send request.")}finally{m(M=>({...M,[g]:!1}))}}};return r.jsx(F1,{title:"Private rover access requests",className:x,bodyClassName:"text-sm text-slate-300",children:r.jsx("div",{className:l0,children:t.length===0?r.jsx("p",{className:"text-xs text-slate-500",children:"No closed private rovers are available to request right now."}):r.jsx("div",{className:"w-full space-y-0.5",children:t.map(g=>{const M=String(g.id),y=!!f[M],S=p.get(M);return r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-1 py-0.5",children:[r.jsxs("div",{className:"min-w-0 text-left",children:[r.jsx("p",{className:"truncate text-xs",children:r.jsx(A4,{rover:g,fallback:M,className:"max-w-full truncate align-middle"})}),r.jsx("p",{className:"truncate text-[0.7rem] text-slate-500",children:M})]}),S?r.jsx("span",{className:"rounded bg-amber-700/30 px-1 py-0.5 text-[0.7rem] text-amber-200",children:"Pending"}):r.jsx("button",{type:"button",onClick:()=>z(M),disabled:y,className:"button-dark text-xs disabled:opacity-50",children:y?"Sending...":"Request"})]},M)})})})})}function x7(t){return String(t||"").trim()}function aQ(t){const n=x7(t);return n?n.includes("DOCKINGRUNNING")?"Returning":n.includes("PAUSED")?"Paused":n.includes("HOUSECLEANINGRUNNING")?"Cleaning":n.includes("SPOTCLEANINGRUNNING")?"Spot cleaning":n.includes("STATE_START")?"Starting":n.includes("STATE_IDLE")||n.includes("STATE_STANDBY")?"Idle":n:"--"}function Do(t){return t==="good"?"bg-emerald-600 text-white":t==="warn"?"bg-amber-500 text-slate-900":t==="danger"?"bg-rose-600 text-white":"bg-slate-700 text-slate-100"}function a7({label:t,value:n,tone:l="muted",valueClass:o="",hideLabel:h=!1}){return r.jsxs("div",{className:`rounded-md px-1 py-0.75 text-center ${Do(l)}`,children:[h?null:r.jsx("div",{className:"text-[0.72rem] opacity-90",children:t}),r.jsx("div",{className:`text-sm font-semibold ${o}`,title:String(n||""),children:n})]})}function nQ({neato:t,onStart:n,onSendHome:l,onLocate:o,onClearErrors:h,onPowerCycle:f,fullWidth:m=!1}){const[p,x]=v.useState(""),z=m?"w-full":"w-full max-w-xl",g=!!t?.configured,M=!!(t?.enabled&&t?.connected),y=!!t?.telemetry?.extPowerPresent,S=!!t?.telemetry?.chargingActive,A=aQ(t?.telemetry?.uiState),H=x7(t?.telemetry?.robotState)||"--",C=t?.telemetry?.batteryPercent,w=Number.isFinite(C)?`${C}%`:"--",j=t?.telemetry?.batteryVoltage,L=Number.isFinite(j)?`${j.toFixed(2)} V`:"--",B=x7(t?.telemetry?.robotError)||"--",E=x7(t?.telemetry?.robotAlert)||"--",O=t?.controls||{},T=!!O?.start?.available,k=!!O?.sendHome?.available,U=!!O?.locate?.available,G=!!O?.clearErrors?.available,Y=!!O?.powerCycle?.available,X=g&&M&&T,d1=g&&M&&k,o1=g&&M&&U,l1=g&&M&&G,N=g&&M&&Y,_=async(D,c1)=>{if(c1){x(D);try{await c1()}catch{}finally{x("")}}},$=g?M?"Online":"Offline":"Not configured",W=g?M?"good":"danger":"warn",K=C==null?"muted":C>=60?"good":C>=25?"warn":"danger",F=y?"Docked":A!=="--"?A:"Away from dock";return r.jsx(F1,{title:"Neato Controls",className:z,bodyClassName:"text-sm text-slate-200",actions:r.jsx("span",{className:`inline-flex w-auto rounded px-1 py-0.25 text-xs font-semibold ${Do(W)}`,children:$}),children:r.jsxs("div",{className:"grid gap-0.5",children:[r.jsx("div",{className:"text-center",children:r.jsx("p",{className:"text-xs text-slate-400",children:"Control the autonomous Neato robovac. Be nice to him."})}),r.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-0.5",children:[r.jsxs("div",{className:"surface-muted grid gap-0.5",children:[r.jsx("p",{className:"text-xs text-slate-300 text-center",children:"Controls"}),r.jsx("button",{type:"button",disabled:!X||!!p,onClick:()=>_("start",n),className:"rounded-md border border-sky-300 bg-emerald-600 px-1 py-1 text-base font-semibold text-white transition hover:border-sky-500 hover:bg-emerald-500 disabled:opacity-50",children:p==="start"?"Starting...":"Start cleaning"}),r.jsx("button",{type:"button",disabled:!d1||!!p,onClick:()=>_("sendHome",l),className:"rounded-md border border-sky-300 bg-sky-600 px-1 py-1 text-base font-semibold text-white transition hover:border-sky-500 hover:bg-sky-500 disabled:opacity-50",children:p==="sendHome"?"Sending...":"Send to dock"}),r.jsxs("div",{className:"grid grid-cols-3 gap-0.5",children:[r.jsx("button",{type:"button",disabled:!o1||!!p,onClick:()=>_("locate",o),className:"w-full rounded-md border border-sky-300 bg-fuchsia-600 px-1 py-0.5 text-xs font-semibold text-white transition hover:border-sky-500 hover:bg-fuchsia-500 disabled:opacity-50",children:p==="locate"?"Playing...":"Play sound"}),r.jsx("button",{type:"button",disabled:!l1||!!p,onClick:()=>_("clearErrors",h),className:"w-full rounded-md border border-sky-300 bg-amber-500 px-1 py-0.5 text-xs font-semibold text-slate-900 transition hover:border-sky-500 hover:bg-amber-400 disabled:opacity-50",children:p==="clearErrors"?"Clearing...":"Clear errors"}),r.jsx("button",{type:"button",disabled:!N||!!p,onClick:()=>_("powerCycle",f),className:"w-full rounded-md border border-rose-300 bg-rose-600 px-1 py-0.5 text-xs font-semibold text-white transition hover:border-rose-500 hover:bg-rose-500 disabled:opacity-50",children:p==="powerCycle"?"Cycling...":"Power cycle"})]}),r.jsxs("div",{className:"surface-muted grid gap-0.5 pt-0.25",children:[r.jsx("p",{className:"text-xs text-slate-300 text-center",children:"Power status"}),r.jsxs("div",{className:"grid grid-cols-2 gap-0.5",children:[r.jsx(a7,{label:"Battery",value:w,tone:K}),r.jsx(a7,{label:"Voltage",value:L,tone:"muted"}),r.jsx(a7,{label:"Docked",value:y?"Docked":"Not docked",tone:y?"good":"muted",hideLabel:!0}),r.jsx(a7,{label:"Charging",value:S?"charging":"not charging",tone:S?"good":"muted",hideLabel:!0})]})]})]}),r.jsx("div",{className:"grid gap-0.5",children:r.jsxs("div",{className:"surface-muted grid gap-0.5",children:[r.jsx("p",{className:"text-xs text-slate-300 text-center",children:"Robot Status"}),r.jsxs("div",{className:"grid gap-0.5",children:[r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Robot state (raw)"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:H})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Basic state"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:F})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"UI state"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:A})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Robot error"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:B})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Robot alert"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:E})]})]})]})})]}),!g||!M||!T||!k||!U||!G||!Y?r.jsx("p",{className:"text-xs text-slate-400 text-center",children:g?M?"Waiting for required Neato entities in Home Assistant.":"Home Assistant is offline.":"Set homeAssistant.neato.device in server config to enable Neato controls."}):null]})})}function lQ(t){return t==="good"?"bg-emerald-600 text-white":t==="warn"?"bg-amber-500 text-slate-900":t==="danger"?"bg-rose-600 text-white":"bg-slate-700 text-slate-100"}function rQ(t){return t==="up"?"Up":t==="down"?"Down":t==="stopped"?"Transitioning...":t==="conflict"?"Conflict":"--"}function iQ({lift:t,onUp:n,onDown:l,fullWidth:o=!1}){const[h,f]=v.useState(""),[m,p]=v.useState(()=>Date.now()),x=o?"w-full":"w-full max-w-xl",z=!!t?.configured,g=!!(t?.enabled&&t?.connected),M=!!t?.busy,y=String(t?.target||"").toLowerCase(),S=String(t?.position||"").toLowerCase(),A=!!t?.availability?.upSwitch,H=!!t?.availability?.downSwitch,C=Number(t?.lastActionAt||0),w=Math.max(0,Number(t?.commandCooldownMs||0)),j=Math.max(0,C+w-m),L=!M&&j>0,B=M||L;v.useEffect(()=>{if(!B)return;const G=setInterval(()=>{p(Date.now())},100);return()=>clearInterval(G)},[B]),v.useEffect(()=>{p(Date.now())},[C,w,M]);const E=z?g?B?"Busy":"Ready":"Offline":"Not configured",O=z?g?B?"warn":"good":"danger":"warn",T=z&&g&&!B&&!h&&A&&H,k=v.useMemo(()=>Math.ceil(j/1e3),[j]),U=async(G,Y)=>{if(Y){f(G);try{await Y()}catch{}finally{f("")}}};return r.jsxs(F1,{title:"Lift Controls",className:`relative ${x}`,bodyClassName:"text-sm text-slate-200",actions:r.jsx("span",{className:`inline-flex w-auto rounded px-1 py-0.25 text-xs font-semibold ${lQ(O)}`,children:E}),children:[B?r.jsx("div",{className:"absolute inset-0 z-20 flex items-center justify-center rounded-md bg-slate-950/80 px-1.5 text-center",children:r.jsxs("div",{className:"space-y-0.25",children:[r.jsx("p",{className:"text-sm font-semibold text-slate-100",children:M?"Preparing to move":"Moving!"}),r.jsx("p",{className:"text-xs text-slate-300",children:"Controls are disabled while the lift is moving, otherwise its tiny brain would get confused."}),!M&&L?r.jsxs("p",{className:"text-xs text-slate-400",children:["About ",k,"s remaining."]}):null]})}):null,r.jsxs("div",{className:"grid gap-0.5",children:[r.jsx("div",{className:"text-center",children:r.jsx("p",{className:"text-xs text-slate-400",children:"Move the lift up and down. Please don't break anything..."})}),r.jsx("section",{className:"surface-muted px-0.5 py-0.5",children:r.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-0.5",children:[r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.75 text-center",children:[r.jsx("div",{className:"text-xs text-slate-300",children:"Position"}),r.jsx("div",{className:"text-base font-semibold text-slate-100",children:rQ(S)})]}),r.jsx("button",{type:"button",disabled:!T,onClick:()=>U("down",l),className:`button-dark w-full text-sm disabled:opacity-50 ${S==="down"||y==="down"?"bg-emerald-500 text-white hover:bg-emerald-500":""}`,children:h==="down"||y==="down"?"Lowering...":"Down"}),r.jsx("button",{type:"button",disabled:!T,onClick:()=>U("up",n),className:`button-dark w-full text-sm disabled:opacity-50 ${S==="up"||y==="up"?"bg-emerald-500 text-white hover:bg-emerald-500":""}`,children:h==="up"||y==="up"?"Raising...":"Up"})]})}),!z||!g||!A||!H?r.jsx("p",{className:"text-xs text-slate-400 text-center",children:z?g?"Waiting for required lift switch entities in Home Assistant.":"Home Assistant is offline.":"Set homeAssistant.lift.upSwitch and homeAssistant.lift.downSwitch in server config."}):null,t?.lastError?r.jsxs("p",{className:"text-xs text-rose-300 text-center",children:["Last error: ",t.lastError]}):null]})]})}function sQ(t){const n=String(t||"").trim();if(!n)return"";try{const l=new URL(n),o=String(l.protocol||"").toLowerCase();return o!=="http:"&&o!=="https:"?"":l.toString()}catch{return""}}function oQ({isVerified:t=!1,fullWidth:n=!1,onMessage:l}){const{value:o,save:h}=E1("profile",{nickname:"",profileImageUrl:""}),f=String(o?.profileImageUrl||"").trim(),[m,p]=v.useState(f),[x,z]=v.useState(!1);v.useEffect(()=>{p(f)},[f]);const g=v.useMemo(()=>sQ(m),[m]),M=String(m||"").trim()!==f,y=!!(t&&M&&(g||!String(m||"").trim())),S=n?"w-full":j8,A=async H=>{if(H.preventDefault(),!t){l?.("Verify your account before setting a profile image URL.");return}z(!0),l?.("");try{const C=g||"";h(w=>({...w||{},profileImageUrl:C})),l?.(C?"Profile image URL saved.":"Profile image URL cleared.")}finally{z(!1)}};return r.jsx(F1,{title:"Chat profile image URL",className:S,bodyClassName:"text-sm text-slate-300",children:r.jsxs("form",{className:l0,onSubmit:A,children:[r.jsx("p",{className:"text-xs text-slate-500",children:"Verified users can set a custom avatar for chat and Discord bridge messages."}),r.jsx("input",{className:u0,type:"url",name:"vip_profile_image_url",placeholder:"https://example.com/avatar.png",value:m,onChange:H=>p(H.target.value),disabled:!t||x}),!g&&String(m||"").trim()?r.jsx("p",{className:"text-xs text-amber-300",children:"Enter a valid http/https image URL."}):null,r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"submit",className:"button-dark text-sm disabled:opacity-50",disabled:!y||x,children:x?"Saving...":"Save URL"}),r.jsx("button",{type:"button",className:"button-dark text-sm disabled:opacity-50",disabled:!t||x||!f,onClick:()=>p(""),children:"Clear"})]})]})})}function Oo({isActive:t=!0}){const n=s1(o1=>o1.session),{identifySession:l,requestVerification:o,requestPrivateRoverAccess:h,playUploadedAudio:f,stopUploadedAudio:m,startMicWhip:p,readyMicWhip:x,stopMicWhip:z,neatoStart:g,neatoSendHome:M,neatoLocate:y,neatoClearErrors:S,neatoPowerCycle:A,liftUp:H,liftDown:C}=_2(),{value:w,save:j}=E1("identity",{cookieUserId:""}),{value:L}=E1("profile",{nickname:""}),B=v.useMemo(()=>(w?.cookieUserId||"").trim(),[w?.cookieUserId]),E=(L?.nickname||"").trim(),O=!!n?.isVerified,T=n?.verification?.pendingRequestId||null,k=n?.privateRoverAccess?.requestableRovers||[],U=n?.privateRoverAccess?.pendingRequests||[],G=String(n?.assignment?.roverId||"").trim(),[Y,X]=v.useState(""),d1=async o1=>{const l1=String(o1||"").trim().toLowerCase();if(!l1)throw new Error("Identity key required.");if(!_P.test(l1))throw new Error("Identity key must match format: cu_ + 32 lowercase hex chars.");return j(N=>({...N||{},cookieUserId:l1})),await l({cookieUserId:l1,nickname:E}),l1};return r.jsxs("section",{className:"space-y-2 text-base",children:[r.jsxs("div",{className:"grid gap-2 grid-cols-1 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,0.9fr)]",children:[O?null:r.jsx("div",{className:"lg:col-span-2",children:r.jsx(tQ,{pendingRequestId:T,currentStoredKey:B,nickname:E,requestVerification:o,applyIdentityKey:d1,onMessage:X,fullWidth:!0})}),r.jsx("div",{className:"lg:col-span-2",children:O?r.jsxs("div",{className:"space-y-2",children:[r.jsx(nQ,{neato:n?.neato||null,onStart:g,onSendHome:M,onLocate:y,onClearErrors:S,onPowerCycle:A,fullWidth:!0}),r.jsx(iQ,{lift:n?.lift||null,onUp:H,onDown:C,fullWidth:!0}),r.jsx(WP,{ownRoverId:G,audioForwardByRover:n?.audioForward||{},playUploadedAudio:f,stopUploadedAudio:m,startMicWhip:p,readyMicWhip:x,stopMicWhip:z})]}):r.jsx("section",{className:"surface h-full",children:r.jsx("div",{className:"flex h-full flex-col items-center justify-center text-center text-xs text-slate-400",children:"Verify your account to unlock VIP controls."})})}),r.jsx("div",{className:"lg:col-span-2",children:r.jsx(cQ,{requestableRovers:k,pendingRequests:U,requestPrivateRoverAccess:h,onMessage:X,fullWidth:!0})}),r.jsx("div",{className:"lg:col-span-2",children:r.jsx(eQ,{currentStoredKey:B,applyIdentityKey:d1,onMessage:X,fullWidth:!0})}),r.jsx("div",{className:"lg:col-span-2",children:r.jsx(oQ,{isVerified:O,onMessage:X,fullWidth:!0})})]}),Y?r.jsx("div",{className:j8,children:r.jsx("p",{className:"text-xs text-slate-400 text-center",children:Y})}):null]})}const uQ=420,hQ=1200,dQ={1:262,2:330,3:392,4:523};function j7(){const t=s1(H=>H.session?.buttonBox?.buttons??[]),n=X2(),{value:l}=E1("audio",o2),o=Number.isFinite(l?.masterVolume)?l.masterVolume:o2.masterVolume,h=Number.isFinite(l?.alertVolume)?l.alertVolume:o2.alertVolume,f=Math.max(0,Math.min(1,o*h)),m=v.useMemo(()=>{const H=Array.isArray(t)?t:[];return H.length===4?H:[1,2,3,4].map(C=>H.find(w=>Number(w?.id)===C)||{id:C,count:0,goal:0,rewardName:null,rewardId:null,rewardNumber:null,lastRewardAt:null})},[t]),[p,x]=v.useState({}),[z,g]=v.useState({}),M=v.useRef(new Map),y=v.useRef(new Map),S=v.useRef({}),A=v.useRef(null);return v.useEffect(()=>{const H={};m.forEach(w=>{H[w.id]=Number(w.lastRewardAt||0)});const C=S.current||{};m.forEach(w=>{const j=Number(w.id),L=H[j]||0,B=Number(C[j]||0);if(L>0&&L!==B){g(T=>({...T,[j]:!0}));const E=y.current.get(j);E&&clearTimeout(E);const O=setTimeout(()=>{g(T=>({...T,[j]:!1})),y.current.delete(j)},hQ);y.current.set(j,O)}}),S.current=H},[m]),v.useEffect(()=>{function H(w){const j=dQ[w];if(!j||f<=0)return;let L=A.current;if(!L){const T=window.AudioContext||window.webkitAudioContext;if(!T)return;L=new T,A.current=L}const B=L.createOscillator(),E=L.createGain();B.type="square",B.frequency.value=j;const O=.14*f;E.gain.setValueAtTime(1e-4,L.currentTime),E.gain.exponentialRampToValueAtTime(Math.max(1e-4,O),L.currentTime+.01),E.gain.exponentialRampToValueAtTime(1e-4,L.currentTime+.2),B.connect(E),E.connect(L.destination),B.start(),B.stop(L.currentTime+.21)}function C(w={}){const j=Number(w.buttonId);if(!Number.isFinite(j)||j<1||j>4)return;x(E=>({...E,[j]:!0}));const L=M.current.get(j);L&&clearTimeout(L);const B=setTimeout(()=>{x(E=>({...E,[j]:!1})),M.current.delete(j)},uQ);M.current.set(j,B),H(j)}return n.on("buttonBox:increment",C),()=>{n.off("buttonBox:increment",C)}},[f,n]),r.jsx(F1,{title:"Button Box",bodyClassName:"space-y-0.5 text-base",children:r.jsx("div",{className:"grid grid-cols-4 gap-0.5",children:m.map(H=>{const C=Number(H.id),w=Number.isFinite(H.count)?H.count:0,j=Number.isFinite(H.goal)?H.goal:0,L=typeof H.rewardName=="string"&&H.rewardName.trim()?H.rewardName.trim():"Unassigned",B=Number.isFinite(H.rewardNumber)?H.rewardNumber:"?",E=!!p[C],O=!!z[C];return r.jsx(Ps,{buttonId:C,count:w,goal:j,rewardNumber:B,rewardName:L,className:[E?"bg-cyan-900/45":"",O?"bg-fuchsia-900/45":""].filter(Boolean).join(" ")},C)})})})}function _o(){const{identifySession:t}=_2(),n=s1(z=>z.session?.overseerVote||null),{value:l}=E1("profile",{nickname:""}),{value:o}=E1("identity",{cookieUserId:""}),{value:h,save:f}=E1("overseerPreference",{enabled:!1}),m=!!h?.enabled,p=!!n?.running,x=async z=>{const g=!!z?.target?.checked;await f(M=>({...M||{},enabled:g})),await t({cookieUserId:String(o?.cookieUserId||"").trim(),nickname:String(l?.nickname||"").trim(),overseerEnabled:g})};return r.jsxs(F1,{title:"Overseer vote",bodyClassName:"space-y-0.5 text-center text-xs text-slate-200",children:[r.jsxs("label",{className:`flex items-center justify-center gap-1.5 rounded px-1 py-0.5 ${m?"bg-emerald-600/80 text-emerald-50":"bg-slate-600/70 text-slate-100"}`,children:[r.jsx("input",{type:"checkbox",checked:m,onChange:x}),r.jsxs("span",{children:["Your vote: ",m?"Yes":"No"]})]}),r.jsx("span",{className:"text-xs",children:"Enable a local LLM in chat"}),r.jsxs("div",{className:"mt-0.5 text-slate-400",children:["Yes!: ",Number(n?.yesCount||0),", No...: ",Number(n?.noCount||0)]}),r.jsx("div",{className:"mt-0.5 flex justify-center",children:r.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-[0.7rem] font-medium ${p?"bg-emerald-600/80 text-emerald-50":"bg-slate-600/70 text-slate-100"}`,children:p?"Running!":"Stopped by vote."})})]})}function fQ(){const{state:{roverId:t}}=H2(),l=K2(t)?.sensors||{};return r.jsx(F1,{title:"Roomba sensor view",children:r.jsx("div",{className:"aspect-square w-full",children:r.jsx(yo,{sensors:l})})})}function vQ(){const{state:{roverId:t,keymap:n,camera:l,horn:o},pipeline:h,actions:{setServoAngle:f,setNightVision:m,startHorn:p,stopHorn:x}}=H2(),z=m0(),g=uo(t),M=g.docked&&!g.driving,y=l?.config,S=!!(t&&l?.enabled&&y),A=!!(t&&h?.nightVision),H=h?.nightVisionState,C=!!(t&&h?.horn),w=o?.overheated,j=typeof y?.minAngle=="number"?y.minAngle:-30,L=typeof y?.maxAngle=="number"?y.maxAngle:30,B=typeof l?.angle=="number"?l.angle:typeof y?.homeAngle=="number"?y.homeAngle:(j+L)/2,E=B2(n?.nightVisionToggle?.[0]),O=B2(n?.hornHonk?.[0]),T=B2(n?.cameraUp?.[0]),k=B2(n?.cameraDown?.[0]),U=!!(!t||z.cameraLocked);return r.jsxs(F1,{hideHeader:!0,className:"h-full",bodyClassName:`grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto] ${j2}`,children:[r.jsx(ho,{layout:"desktop",expand:!0,driveDockState:g}),M?null:r.jsxs("div",{className:`${E3} p-0 text-sm text-slate-200`,children:[A&&r.jsx(fo,{nightVisionOn:H?.nightVisionOn,disabled:!t,onToggle:m,keyLabel:E}),C&&r.jsx(vo,{disabled:!t||w,onStart:p,onStop:x,keyLabel:O,active:o?.active,heat:o?.heat}),S&&r.jsx(mo,{value:B,min:j,max:L,label:"Camera tilt",disabled:U,onChange:f,keyDownLabel:k,keyUpLabel:T,className:`${E3} rounded-xl border-2 border-emerald-300/70 bg-emerald-900 px-1 py-1 text-emerald-50`,labelRowClass:"text-xs text-emerald-100",labelClass:"text-sm font-semibold",valueClass:"font-mono text-slate-100",sliderClass:"w-full",accentClass:"accent-emerald-400",endpointClass:"text-[0.7rem] text-emerald-100/80"})]})]})}function mQ({layout:t,onOpenHelpOverlay:n}){const[l,o]=v.useState("telemetry"),h=s1(H=>!!H.session?.isVerified),f=s1(H=>String(H.session?.assignment?.roverId||"").trim()),m=s1(H=>{const C=String(H.session?.assignment?.roverId||"").trim();return C&&H.session?.audioForward?.[C]||null}),{state:p}=H2(),{value:x}=E1("vipAudio",{openMicEnabled:!1,pttMode:"live"}),z=h?"bg-emerald-400":"bg-red-600",g=!!p?.mic?.pttActive,M=!!x?.openMicEnabled,y=x?.pttMode==="clip"?"clip":"live",S=!!(f&&h&&(y==="clip"?g:M||g)),A=!!(f&&h&&y==="clip"&&m?.source==="upload"&&m?.state==="playing");return r.jsx("section",{className:"text-base",children:r.jsxs(kc,{defaultTab:"telemetry",currentTab:l,onTabChange:o,children:[r.jsxs(Rc,{children:[r.jsx(r4,{id:"telemetry",children:"Controls"}),r.jsx(r4,{id:"vip",highlight:A?"green":S?"pink":"none",children:r.jsxs("span",{className:"inline-flex items-center gap-2",children:[r.jsx("span",{children:"VIP"}),r.jsx("span",{className:`inline-block h-3 w-3 rounded-full ${z}`,"aria-hidden":"true",title:h?"Verified":"Not verified"})]})}),r.jsx(r4,{id:"help",children:"Help"}),r.jsx(r4,{id:"settings",children:"Settings"})]}),r.jsxs(Ec,{children:[r.jsx(i4,{id:"telemetry",children:r.jsxs("div",{className:E3,children:[r.jsxs("div",{className:`grid items-stretch ${j2} grid-cols-[minmax(0,1.35fr)_minmax(0,0.95fr)]`,children:[r.jsx(fQ,{}),r.jsx(vQ,{})]}),r.jsxs("div",{className:`grid ${j2} grid-cols-[minmax(0,1fr)_minmax(0,0.9fr)_minmax(0,0.75fr)]`,children:[r.jsx(Z7,{}),r.jsx(xc,{panelId:"replay-sources-desktop",fillHeight:!0}),r.jsx(kP,{})]}),r.jsxs("div",{className:`grid items-stretch ${j2} grid-cols-[minmax(0,1.3fr)_minmax(0,0.22fr)] h-[14rem]`,children:[r.jsx(U7,{fillHeight:!0}),r.jsxs("div",{className:`grid min-h-0 ${j2} grid-rows-[auto_minmax(0,1fr)]`,children:[r.jsx(_o,{}),r.jsx(B7,{compact:!0,hideNicknameForm:!0,fillHeight:!0})]})]}),r.jsx(Ao,{}),r.jsx(j7,{}),r.jsx(Vc,{defaultOrientation:"horizontal",panelId:"rightpane-telemetry"})]})}),r.jsx(i4,{id:"vip",keepMounted:!0,children:r.jsx(Oo,{isActive:l==="vip"})}),r.jsx(i4,{id:"help",children:r.jsx(qo,{layout:t,onOpenOverlay:n})}),r.jsx(i4,{id:"settings",children:r.jsx(Ro,{})})]})]})})}const pQ=new Set(["admin","lockdown","lockdown-admin"]),gQ=new Set(["lockdown","lockdown-admin"]),zQ=new Set(["admin","lockdown"]);function xQ(t="admin"){return t==="lockdown"?{title:"Lockdown mode active",description:"Only the server owners can access the interface at this time."}:{title:"Admin mode active"}}function MQ(){const t=s1(g=>g.session?.mode||null),n=s1(g=>g.session?.role||null),l=s1(g=>g.session?.adminReason?.text||"");s1(g=>g.session?.adminReason?.updatedAt||null);const o=s1(g=>g.session?.timezone||"UTC"),h=zQ.has(t),f=t==="lockdown"?gQ.has(n):pQ.has(n),[m,p]=v.useState(()=>new Date),x=v.useMemo(()=>{try{return new Intl.DateTimeFormat("en-US",{timeZone:o,hour:"numeric",minute:"2-digit",second:"2-digit"}).format(m)}catch{return m.toLocaleTimeString()}},[m,o]);if(v.useEffect(()=>{const g=setInterval(()=>p(new Date),1e3);return()=>clearInterval(g)},[]),!h||f)return null;const z=xQ(t);return r.jsx("div",{className:"pointer-events-auto fixed inset-0 z-50 flex items-center justify-center bg-black px-0.5 py-0.5",children:r.jsxs("div",{className:"surface w-full max-w-md space-y-0.5 text-slate-100 shadow-2xl",children:[r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-lg font-semibold",children:z.title}),r.jsx("p",{className:"text-sm text-slate-300",children:z.description})]}),r.jsxs("div",{className:"surface-muted space-y-0.5",children:[r.jsx("p",{className:"text-[0.7rem] tracking-wide text-slate-400",children:"Reason for locking:"}),r.jsx("p",{className:"text-lg font-semibold text-slate-100",children:l||"No reason set."}),r.jsxs("p",{className:"text-center text-sm text-slate-300",children:["Server time: ",x]})]}),r.jsx("div",{className:"surface-muted",children:r.jsx(Bo,{})}),r.jsx("div",{className:"w-full justify-center items-center",children:r.jsx(T7,{id:"discord",label:"Join our Discord server for updates!"})}),"You can still use the chat while the server is locked:",r.jsx("div",{className:"max-h-80 overflow-y-auto",children:r.jsx(U7,{nicknameLayout:"stacked"})})]})})}const bQ="/assets/turn_alert-DVO8EZJa.mp3";function yQ(t,n=1){const l=v.useRef(null);return v.useEffect(()=>{l.current=new Audio(t),l.current.volume=n,l.current.load()},[t,n]),()=>{l.current&&(l.current.volume=n,l.current.currentTime=0,l.current.play().catch(()=>{}))}}function CQ(){const t=s1(g=>g.session?.socketId||null),n=s1(g=>g.session?.turnQueues||{}),l=s1(g=>g.session?.roster||[]),{pushAlert:o}=_2(),{value:h}=E1("audio",o2),f=Number.isFinite(h?.masterVolume)?h.masterVolume:o2.masterVolume,m=Number.isFinite(h?.alertVolume)?h.alertVolume:o2.alertVolume,p=Math.max(0,Math.min(1,f*m)),x=yQ(bQ,p),z=v.useRef(new Set);return v.useEffect(()=>{if(!t)return;const g=[];Object.entries(n).forEach(([M,y])=>{!y||!y.current||y.current!==t||z.current.has(M)||(g.push(M),z.current.add(M))}),Object.keys(n).forEach(M=>{(!n[M]||n[M].current!==t)&&z.current.delete(M)}),g.length!==0&&(g.forEach(M=>{const y=l.find(S=>String(S.id)===String(M))?.name||M;o({title:"Your turn!",message:`You now control ${y}.`,color:"#ff5722"})}),x())},[n,x,o,l,t]),null}function wQ({visible:t,mode:n,onEnterFullscreen:l,onDismiss:o}){if(!t)return null;const h=n==="pwa-hint";return r.jsx("div",{className:"fixed inset-0 z-40 flex items-center justify-center p-1 pointer-events-none bg-black/20",children:r.jsx("div",{className:"pointer-events-auto w-full max-w-sm surface center",children:r.jsxs("div",{className:"space-y-0.5 p-1 text-sm text-slate-100",children:[r.jsx("h2",{className:"text-base font-semibold text-white border-b border-slate-700",children:"Better in fullscreen!"}),h?r.jsxs("p",{className:"text-slate-300",children:["For fullscreen on iOS, open Safari's share menu and pick ",r.jsx("strong",{children:"Add to Home Screen"}),". Launching from the home screen then makes it fullscreen."]}):r.jsx("p",{className:"text-slate-300",children:"Enable fullscreen to free up more space for the video feed and controls. You can exit fullscreen at any time via the system back or home gesture."}),r.jsx("p",{className:"text-xs border-t border-b border-slate-700 p-0.5 text-blue-300 text-center",children:"This will only show once just to let you know. There is a fullscreen button in the bottom right for later use."}),r.jsxs("div",{className:"flex justify-end gap-0.5 pt-1 text-sm",children:[r.jsx("button",{type:"button",className:"rounded border border-slate-600 px-3 py-1 text-slate-200",onClick:o,children:h?"Got it":"Not now"}),!h&&r.jsx("button",{type:"button",className:"rounded bg-cyan-500 px-3 py-1 font-semibold text-black hover:bg-cyan-400",onClick:l,children:"Enter fullscreen"})]})]})})})}function HQ({side:t="right",onClick:n}){const l=t==="left"?"left-2":"right-2";return r.jsx("button",{type:"button",onClick:n,className:`fixed bottom-2 ${l} z-30 flex h-10 w-10 items-center justify-center rounded-full border border-slate-700 bg-black text-slate-200 transition hover:bg-zinc-900 active:scale-95`,"aria-label":"Enter fullscreen",title:"Enter fullscreen",children:r.jsx("svg",{viewBox:"0 0 24 24",width:"18",height:"18","aria-hidden":"true",className:"pointer-events-none",children:r.jsx("path",{d:"M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})})}const SQ=new Set(["mobile-portrait","mobile-landscape"]),VQ=()=>{if(typeof navigator>"u")return!1;const t=navigator.userAgent||navigator.platform||"";return/iphone|ipad|ipod/i.test(t)},Wi=()=>{if(typeof document>"u")return!1;const t=document;return!!(t.fullscreenEnabled||t.webkitFullscreenEnabled||t.mozFullScreenEnabled||t.msFullscreenEnabled)};function LQ(t){const{value:n,save:l,status:o}=E1("page",{fullscreenPromptShown:!1}),h=SQ.has(t),[f,m]=v.useState(!1),[p,x]=v.useState("native"),[z,g]=v.useState(!1),M=!!n?.fullscreenPromptShown,y=v.useMemo(()=>VQ(),[]),S=v.useMemo(()=>Wi(),[]),A=v.useCallback(()=>{o==="ready"&&l(L=>L?.fullscreenPromptShown?L:{...L||{},fullscreenPromptShown:!0})},[l,o]),H=v.useCallback(()=>{if(o!=="ready"){m(!1);return}if(!h){m(!1),g(!1);return}if(!z&&M){m(!1);return}if(y){x("pwa-hint"),m(!0);return}if(!Wi()){m(!1);return}if(typeof document<"u"&&document.fullscreenElement){m(!1);return}x("native"),m(!0)},[z,M,y,h,o]);v.useEffect(()=>{H()},[H]),v.useEffect(()=>{if(typeof document>"u")return;const L=()=>{if(document.fullscreenElement){g(!1),m(!1);return}H()};return document.addEventListener("fullscreenchange",L),document.addEventListener("webkitfullscreenchange",L),()=>{document.removeEventListener("fullscreenchange",L),document.removeEventListener("webkitfullscreenchange",L)}},[H]);const C=v.useCallback(()=>{A(),g(!1),m(!1)},[A]),w=v.useCallback(async()=>{if(!h||y||typeof document>"u")return!1;if(document.fullscreenElement)return!0;const L=document.documentElement;if(!L)return!1;const B=L.requestFullscreen||L.webkitRequestFullscreen||L.mozRequestFullScreen||L.msRequestFullscreen;if(!B)return!1;try{const E=B.call(L);return E&&typeof E.then=="function"&&await E,A(),g(!1),m(!1),!0}catch(E){return console.warn("Failed to enter fullscreen",E),!1}},[y,h,A]),j=v.useCallback(()=>{h&&(g(!0),x(y?"pwa-hint":"native"),m(!0))},[y,h]);return v.useEffect(()=>{h||(g(!1),m(!1))},[h]),{visible:f,mode:p,isIOS:y,nativeSupported:S,enterFullscreen:w,dismiss:C,showPrompt:j}}function AQ({visible:t,layout:n,onClose:l,showOnLoad:o,onToggleShowOnLoad:h}){if(!t)return null;const{state:f}=H2(),m=p=>{const x=!p.target.checked;h?.(x)};return r.jsx("div",{className:"fixed inset-0 z-30 flex items-center justify-center bg-black/80 px-0.5 py-0.5",children:r.jsxs("div",{className:"pointer-events-auto surface w-full max-w-4xl max-h-[90vh] overflow-hidden shadow-2xl",children:[r.jsxs("div",{className:"flex items-center justify-between border-b border-slate-700 px-0.5 py-0.25 text-sm text-slate-200",children:[r.jsx("span",{className:"font-semibold",children:"Help & controls"}),r.jsxs("div",{className:"flex items-center gap-0.5 text-[0.8rem] text-slate-300",children:[r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!o,onChange:m,className:"accent-cyan-500"}),r.jsx("span",{children:"Don't show again"})]}),r.jsx("button",{type:"button",onClick:l,className:"button-dark px-2 py-0.25 text-[0.8rem]",children:"Close"})]})]}),r.jsx("div",{className:"max-h-[85vh] overflow-y-auto p-0.5",children:r.jsx(To,{layout:n,keymap:f?.keymap||{}})})]})})}function c0({label:t,keyLabel:n}){return r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-0.5 py-0.35 text-[0.8rem] text-slate-200",children:[r.jsx("span",{children:t}),r.jsx(A7,{label:n})]})}function BQ({keymap:t}){return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-sm text-slate-200",children:'1. Press "Start Driving" put your rover into driving mode.'}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-sm text-slate-200",children:"2. Use the drive controls to move your rover:"}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(c0,{label:"Forward",keyLabel:B2(t?.driveForward?.[0])}),r.jsx(c0,{label:"Backward",keyLabel:B2(t?.driveBackward?.[0])}),r.jsx(c0,{label:"Turn Left",keyLabel:B2(t?.driveLeft?.[0])}),r.jsx(c0,{label:"Turn Right",keyLabel:B2(t?.driveRight?.[0])}),r.jsx(c0,{label:"Move faster",keyLabel:B2(t?.boostModifier?.[0])}),r.jsx(c0,{label:"Move slower",keyLabel:B2(t?.slowModifier?.[0])})]})]}),r.jsx("p",{className:"text-sm text-slate-200",children:'3. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.'})]})}function jQ(){return r.jsxs("div",{className:"space-y-0.5 text-sm text-slate-200",children:[r.jsx("p",{children:'1. Press "Start Driving" put your rover into driving mode.'}),r.jsx("p",{children:"2. Touch and hold in Joystick area to move."}),r.jsx("p",{children:"3. Use the other column for motor, horn, and camera controls."}),r.jsx("p",{children:'4. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.'})]})}function FQ({visible:t,layout:n,showOnLoad:l,onToggleShowOnLoad:o,onOpenHelp:h,onClose:f}){const{state:m}=H2(),p=n==="desktop",x=v.useMemo(()=>m?.keymap||{},[m?.keymap]);if(!t)return null;const z=g=>{const M=!g.target.checked;o?.(M)};return r.jsx("div",{className:"fixed inset-0 z-40 flex items-end justify-center bg-black/75 p-0.5 items-center",children:r.jsxs("div",{className:"pointer-events-auto surface w-full max-w-3xl overflow-hidden shadow-2xl",children:[r.jsx("div",{className:"flex items-center justify-between border-b border-slate-700 px-0.5 py-0.35 text-sm text-slate-200",children:r.jsx("span",{className:"font-semibold text-xl",children:"Welcome! To get started:"})}),r.jsxs("div",{className:`grid gap-0.5 p-0.5 ${p?"md:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)]":"grid-cols-1"}`,children:[r.jsx("section",{className:"space-y-0.5 border-b border-slate-700",children:p?r.jsx(BQ,{keymap:x}):r.jsx(jQ,{})}),r.jsxs("section",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-left",children:"Next..."}),r.jsxs("div",{className:"surface space-y-0.5 p-0.5 border-b border-slate-700",children:[r.jsx("p",{className:"text-xl font-semibold text-slate-200",children:"Set your nickname"}),r.jsx("p",{className:"text-sm font-semibold text-slate-200",children:"Nicknames are assigned randomly by default, you can change yours here."}),r.jsx(_7,{compact:!0})]}),r.jsxs("div",{className:"surface p-0.5",children:[r.jsx("p",{className:"text-xl font-semibold text-slate-200",children:"Join our Discord server!"}),r.jsx("p",{className:"text-sm font-semibold text-slate-200",children:"We have an active and welcoming community :3"}),r.jsx(T7,{id:"discord",label:"Join Discord"})]})]})]}),r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-0.5 border-t border-slate-700 px-0.5 py-0.35 text-[0.8rem]",children:[r.jsxs("label",{className:"flex items-center gap-0.5 text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:!l,onChange:z,className:"accent-cyan-500"}),r.jsx("span",{children:"Don't show again"})]}),r.jsx("div",{className:"flex items-center gap-0.5",children:r.jsx("button",{type:"button",onClick:f,className:"button-dark px-1 py-0.25 text-2xl bg-green-600 hover:bg-green-400 hover:border-green-100 border-green-400",children:"Got it! Let me in!"})})]})]})})}const NQ=`aroma +`).trim();if(g1){O(!0);try{await M(g1,p1),B(""),H(),C(!1)}catch(z1){alert(z1.message)}finally{O(!1)}}}const x1=l?"flex-1 min-h-0 overflow-y-auto":"h-48 overflow-y-auto",k1=f==="stacked";return r.jsxs(F1,{title:h,hideHeader:!h,fillHeight:l,bodyClassName:"space-y-0.5 text-base",children:[r.jsxs("div",{className:`overflow-y-auto space-y-0.5 px-0 ${x1}`,ref:K,children:[c1.length===0&&u1.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"No messages yet."}):c1.map(J=>r.jsx(bc,{message:J},J.id)),u1.map(J=>r.jsx(Is,{message:J},`typing-${J.typingId||J.id}`))]}),!t&&r.jsxs("form",{className:`flex items-stretch gap-0.5 ${k1?"flex-col":"flex-wrap"}`,onSubmit:H1,children:[r.jsx("div",{className:k1?"w-full":"w-[9rem] sm:w-[10rem] shrink-0",children:r.jsx(_7,{compact:!0})}),r.jsx("input",{className:`field-input ${k1?"w-full min-w-0":"flex-1 min-w-[10rem]"}`,value:L,onChange:J=>{const f1=J.target.value;B(f1),C(!!f1.trim())},onFocus:J=>{S(J),C(!!L.trim())},onBlur:J=>{A(J),C(!1)},onKeyDown:J=>{J.key==="Enter"&&!L.trim()&&(J.preventDefault(),H(),C(!1))},ref:J=>y(J,{target:"panel"}),placeholder:W?"Type a message…":n?"":"Spectators cannot chat",disabled:!W}),D&&r.jsxs("div",{className:`flex flex-wrap items-center gap-0.5 ${k1?"w-full":"basis-full sm:basis-auto"}`,children:[r.jsxs("label",{className:"flex items-center gap-0.5 text-xs text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:T,onChange:J=>k(J.target.checked),className:"accent-cyan-500"}),r.jsx("span",{children:"Speak"})]}),r.jsxs("select",{value:U,onChange:J=>{const f1=J.target.value,g1=f1==="chromegtts"&&!Pi.includes(Y)?"tpf":f1==="flite"&&!Ii.includes(Y)?"rms":Y;G(f1),g1!==Y&&X(g1),j(z1=>({...z1||{},engine:f1,voice:g1}))},className:"field-input text-xs",children:[r.jsx("option",{value:"flite",children:"flite"}),r.jsx("option",{value:"espeak",children:"espeak"}),r.jsx("option",{value:"chromegtts",children:"Google TTS (Local!!)"})]}),U==="flite"||U==="chromegtts"?r.jsxs(r.Fragment,{children:[r.jsx("select",{value:Y,onChange:J=>{const f1=J.target.value;X(f1),j(g1=>({...g1||{},voice:f1}))},className:"field-input text-xs",children:(U==="chromegtts"?Pi:Ii).map(J=>r.jsx("option",{value:J,children:J},J))}),U==="chromegtts"&&r.jsxs(r.Fragment,{children:[r.jsx("select",{value:l1,onChange:J=>{const f1=Number(J.target.value);N(f1),j(g1=>({...g1||{},googlePitch:f1}))},className:"field-input text-xs",children:Qi.map(J=>r.jsxs("option",{value:J,children:["pitch ",J.toFixed(2)]},`pitch-${J}`))}),r.jsx("select",{value:_,onChange:J=>{const f1=Number(J.target.value);$(f1),j(g1=>({...g1||{},googleSpeed:f1}))},className:"field-input text-xs",children:Qi.map(J=>r.jsxs("option",{value:J,children:["speed ",J.toFixed(2)]},`speed-${J}`))})]})]}):r.jsx("select",{value:d1,onChange:J=>{const f1=Number(J.target.value);o1(f1),j(g1=>({...g1||{},pitch:f1}))},className:"field-input text-xs",children:NP.map(J=>r.jsxs("option",{value:J,children:["pitch ",J]},J))})]}),r.jsx("button",{type:"submit",disabled:!W||E,className:`button-dark disabled:opacity-50 ${k1?"w-full h-8":"h-full self-stretch"}`,children:E?"...":"Send"})]})]})}function Tc({className:t=""}){const n=s1(l=>l.session?.socials??[]).slice(0,4);return n.length?r.jsx("div",{className:`grid grid-cols-2 grid-rows-2 gap-0.5 ${t}`,children:n.map(l=>r.jsx(T7,{id:l?.id||l?.key||l?.service||null},`${l?.id||l?.label||"social"}-${l?.url||""}`))}):null}function kP(){return r.jsx(F1,{title:"Links!",fillHeight:!0,bodyClassName:"flex flex-1 min-h-0 flex-col gap-0.5 text-base",children:r.jsx(Tc,{className:"flex-1 min-h-0"})})}function Re(...t){return t.filter(Boolean).join(" ")}function RP(t){const n=t?.batteryState?.percentDisplay;return n==null?"--":`${n}%`}function EP(t){return t?.batteryState?t.batteryState.urgentActive?"text-red-400":t.batteryState.warnActive?"text-amber-300":"text-emerald-300":"text-slate-400"}function TP(t){switch(t){case"admin":case"lockdown":case"lockdown-admin":return"text-amber-300";case"spectator":return"text-slate-400";default:return"text-sky-300"}}function qP(t,n){if(!t)return"";const l=t.nickname||t.socketId?.slice(0,6)||"unknown";return t.socketId&&t.socketId===n?`${l} (you)`:l}function Z7({title:t="Rovers"}){const n=s1(k=>k.session?.role||null),l=s1(k=>k.session?.roster??[]),o=s1(k=>k.session?.turnQueues??{}),h=s1(k=>k.session?.users??[]),f=s1(k=>k.session?.socketId||null),m=s1(k=>String(k.session?.assignment?.roverId||"").trim()),p=s1(k=>{const U=String(k.session?.assignment?.roverId||"").trim();return U?(k.session?.roster||[]).find(Y=>String(Y?.id)===U)?.name||U:""}),{requestControl:x,rebootOwnRover:z}=_2(),[g,M]=v.useState({}),[y,S]=v.useState(!1),[A,H]=v.useState(()=>Date.now()),C=v.useMemo(()=>n&&n!=="spectator",[n]),w=v.useMemo(()=>n==="admin"||n==="lockdown"||n==="lockdown-admin",[n]),j=v.useMemo(()=>Object.values(o||{}).some(k=>k?.deadline||k?.idleDeadline),[o]);v.useEffect(()=>{if(!j)return;const k=setInterval(()=>{H(Date.now())},1e3);return()=>clearInterval(k)},[j]);const L=v.useMemo(()=>{const k=new Set(l.map(G=>String(G.id))),U=Object.keys(o||{}).filter(G=>!k.has(String(G))).map(G=>({id:G,name:G,locked:!1,batteryState:null}));return[...l,...U]},[l,o]);async function B(k){if(k){M(U=>({...U,[k]:!0}));try{await x(k)}catch(U){alert(U.message)}finally{M(U=>({...U,[k]:!1}))}}}const E=k=>h.find(U=>U.socketId===k)||{socketId:k,nickname:null,role:null};async function O(){if(!(y||!window.confirm(`Reboot your rover "${p}" now?`))){S(!0);try{await z(),alert("Reboot command sent.")}catch(U){alert(U.message)}finally{S(!1)}}}const T=n!=="spectator"&&m?r.jsx("button",{type:"button",onClick:O,disabled:y,className:"button-dark bg-amber-700/80 text-amber-50 hover:bg-amber-600 disabled:opacity-40",title:"Reboot your assigned rover",children:y?"Rebooting...":"Reboot My Rover"}):null;return r.jsx(F1,{title:t,actions:T,bodyClassName:"space-y-0.5 text-sm",children:L.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"No rovers registered."}):r.jsx("ul",{className:"space-y-0.5 text-sm",children:L.map(k=>{const U=String(k.id),G=o?.[U]||null,Y=G?.queue||[],X=G?.idleDeadline||G?.deadline||null,d1=X&&X>A?Math.ceil((X-A)/1e3):X?0:null,o1=G?.current||null,l1=o1?Y.findIndex(x1=>x1===o1):-1,N=Y.length>1?l1>=0?Y[(l1+1)%Y.length]:Y[0]:null,_=!!(f&&o1&&o1===f),$=!!(f&&N&&N===f),W=d1!=null&&(_||$),K=!!(k?.private?.enabled&&k?.private?.open),F=!!(k?.private?.enabled&&!k?.private?.open),D=!!k.locked,c1=D&&!w&&!F,u1=k.lockReason?`locked: ${k.lockReason}`:"locked",p1=g[U]?"...":D&&!F?u1:"request",H1=C&&!c1&&!g[U];return r.jsxs("li",{className:Re("surface flex flex-wrap items-start justify-between gap-0.5",H1&&"cursor-pointer",D?"bg-red-900/40":K?"bg-amber-700/35 border border-amber-200/30":null),onClick:()=>{H1&&B(k.id)},children:[r.jsxs("div",{className:"flex min-w-0 flex-1 flex-col gap-0.5",children:[r.jsxs("div",{className:"flex items-center justify-between gap-0.5",children:[r.jsxs("div",{className:"flex min-w-0 items-center gap-0.5",children:[r.jsxs("p",{className:"min-w-0 flex items-center gap-0.5 whitespace-nowrap text-slate-200",children:[r.jsx(A4,{rover:k,fallback:U}),k.description?r.jsx("span",{className:"min-w-0 flex-1 truncate text-[0.7rem] text-slate-400",children:k.description}):null]}),W?r.jsx("span",{className:"rounded bg-slate-800 px-1 text-[0.7rem] text-slate-200",children:_?`${d1}s left`:`Your turn in ${d1}s`}):null]}),r.jsx("span",{className:Re("text-[0.75rem] font-semibold",EP(k)),children:RP(k)})]}),Y.length===0?r.jsx("p",{className:"text-[0.7rem] text-slate-500",children:"No queue."}):r.jsx("div",{className:"flex flex-wrap items-center gap-0.5",children:Y.map((x1,k1)=>{const J=E(x1),f1=x1===o1,g1=!!(N&&x1===N&&!f1),z1=f1?"bg-sky-600 text-white ring-2 ring-amber-300":g1?"bg-emerald-700/60 text-emerald-100 ring-1 ring-emerald-300/70":"bg-slate-800 text-slate-200";return r.jsxs("span",{className:`flex items-center gap-0.5 rounded px-1 text-[0.7rem] ${z1}`,children:[r.jsx("span",{className:`${TP(J.role)} font-semibold`,children:qP(J,f)}),f1&&r.jsx("span",{className:"text-[0.65rem] text-slate-200",children:"now"}),g1&&r.jsx("span",{className:"text-[0.65rem] text-emerald-100",children:"next"})]},`${U}-${x1}-${k1}`)})})]}),C?r.jsx("button",{type:"button",onClick:x1=>{x1.stopPropagation(),B(k.id)},disabled:g[U]||c1,className:Re("button-dark disabled:opacity-40",D&&"bg-red-600/70 text-white hover:bg-red-600"),children:p1}):null]},k.id)})})})}function DP(t){switch(t){case"admin":case"lockdown":case"lockdown-admin":return"text-amber-300";case"spectator":return"text-slate-400";default:return"text-sky-300"}}function OP(t,n){if(!t)return"";const l=t.nickname||t.socketId?.slice(0,6)||"unknown";return t.socketId&&t.socketId===n?`${l} (you)`:l}function B7({hideNicknameForm:t=!1,hideHeader:n=!1,className:l="",fillHeight:o=!1,compact:h=!1}){const f=s1(M=>M.session?.role||null),m=s1(M=>M.session?.users??[]),p=s1(M=>M.session?.socketId||null),x=f!=="spectator",z=v.useMemo(()=>[...m].sort((M,y)=>M.socketId===p?-1:y.socketId===p?1:(M.nickname||"").localeCompare(y.nickname||"")),[p,m]),g=o?"flex-1 min-h-0 overflow-y-auto":h?"h-28 overflow-y-auto":"h-48 overflow-y-auto";return r.jsxs(F1,{title:n?"":"Users",hideHeader:n,fillHeight:o,className:l,bodyClassName:"space-y-0.5 text-base",children:[!t&&r.jsxs("div",{className:"space-y-0.5",children:[r.jsxs("div",{className:"grid gap-0.5 md:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]",children:[r.jsx("div",{className:"min-w-0",children:r.jsx("div",{className:"flex w-full items-center px-0 py-0",children:r.jsx(_7,{compact:h})})}),r.jsx(Tc,{})]}),!x&&r.jsx("p",{className:"text-xs text-slate-500",children:"Spectators cannot set nicknames."})]}),r.jsx("div",{className:`space-y-0.5 ${o?"flex flex-1 min-h-0 flex-col":""}`,children:r.jsx("div",{className:`flex flex-wrap content-start items-start gap-0.5 px-0 pb-0 ${g} ${h?"text-[0.8rem]":""}`,children:z.length===0?r.jsx("p",{className:"text-sm text-slate-500",children:"Waiting for users…"}):z.map(M=>r.jsx("span",{className:`rounded px-1 py-0.25 text-[0.7rem] ${DP(M.role)}`,children:OP(M,p)},M.socketId))})})]})}const _P=/^cu_[a-f0-9]{32}$/,u0="field-input w-full max-w-sm text-left focus:ring-emerald-500",j8="mx-auto w-full max-w-xl flex justify-center",l0="mx-auto flex w-full max-w-md flex-col items-center space-y-0.5 text-center";function UP(t){const n=String(t||"").trim();return n?n.length<=10?`${n.slice(0,2)}***${n.slice(-2)}`:`${n.slice(0,6)}...${n.slice(-6)}`:""}const Xi=8*1024*1024,r6=16e3,ZP={iceServers:[{urls:"stun:stun.l.google.com:19302"}],bundlePolicy:"max-bundle",rtcpMuxPolicy:"require"};function Ki(t){let n="";for(let o=0;o{if(!t||t.iceGatheringState==="complete"){l();return}const o=setTimeout(()=>{t.removeEventListener("icegatheringstatechange",h),l()},n);function h(){t.iceGatheringState==="complete"&&(clearTimeout(o),t.removeEventListener("icegatheringstatechange",h),l())}t.addEventListener("icegatheringstatechange",h)})}function Ji(t){if(!t)return!1;const n=t.connectionState,l=t.iceConnectionState;return n==="connected"||l==="connected"||l==="completed"}function IP(t,n=1e4){return new Promise((l,o)=>{if(!t){o(new Error("Peer connection missing"));return}if(Ji(t)){l();return}const h=setTimeout(()=>{m(),o(new Error("Peer connection timeout"))},n),f=()=>{Ji(t)?(m(),l()):(t.connectionState==="failed"||t.connectionState==="closed"||t.iceConnectionState==="failed")&&(m(),o(new Error(`Peer connection ${t.connectionState||t.iceConnectionState}`)))};function m(){clearTimeout(h),t.removeEventListener("connectionstatechange",f),t.removeEventListener("iceconnectionstatechange",f)}t.addEventListener("connectionstatechange",f),t.addEventListener("iceconnectionstatechange",f)})}async function PP(t){if(!t?.getParameters||!t?.setParameters)return;const n=t.getParameters()||{},l=n.encodings&&n.encodings[0]||{};n.encodings=[{...l,maxBitrate:64e3,dtx:"disabled"}];try{await t.setParameters(n)}catch{}}function QP(t,n=6e3){return new Promise((l,o)=>{if(!t){o(new Error("Peer connection missing"));return}const h=Date.now();let f=-1;const m=setInterval(async()=>{if(Date.now()-h>n){clearInterval(m),o(new Error("WHIP connected but no outbound audio flow"));return}try{const p=t.getSenders().filter(x=>x.track?.kind==="audio");for(const x of p){const z=await x.getStats();for(const g of z.values()){if(g.type!=="outbound-rtp"||g.kind!=="audio")continue;const M=Number(g.bytesSent||0),y=Number(g.packetsSent||0);if(f<0)f=M;else if(M>f+200||y>5){clearInterval(m),l();return}}}}catch{}},250)})}function XP(t){const n=t.reduce((h,f)=>h+f.length,0),l=new Float32Array(n);let o=0;for(const h of t)l.set(h,o),o+=h.length;return l}function KP(t){const n=new Int16Array(t.length);for(let l=0;l{for(let g=0;gg&&n?.[g]||null,[n,g]),c1=!!(D&&D.state!=="offline"&&!D.error),u1=D?.source==="upload"&&D?.state==="playing",p1=D?.source==="mic-whip"&&(D?.state==="starting"||D?.state==="playing"),H1=!!(!w&&U.current&&(H||F)),x1=!w&&(j==="live"||j==="starting"),k1=w&&B==="recording",J=w&&B==="sending",f1=B2(p?.keymap?.micPtt?.[0])||"M",g1=v.useCallback(r1=>{const v1=r1==="clip"?"clip":"live";z(y1=>({...y1||{},pttMode:v1}))},[z]),z1=async()=>{if(!g){T("Take control of your rover first.");return}if(!M){T("Select an audio file first.");return}if(M.size>Xi){T(`File too large (max ${Xi} bytes).`);return}A(!0),T("");try{const r1=await M.arrayBuffer(),v1=Ki(new Uint8Array(r1));await l?.({roverId:g,name:M.name,mime:M.type||"",dataBase64:v1}),T("Upload playback started.")}catch(r1){T(r1?.message||"Failed to play upload.")}finally{A(!1)}},j1=async()=>{if(!g){T("Take control of your rover first.");return}A(!0),T("");try{await o?.(g),T("Upload playback stopped.")}catch(r1){T(r1?.message||"Failed to stop upload.")}finally{A(!1)}},D1=v.useCallback(async r1=>{const v1=String(r1||X.current||"").trim();if(Y.current=!1,L("idle"),G.current){try{G.current.getSenders().forEach(y1=>y1.track?.stop())}catch{}try{G.current.close()}catch{}}if(G.current=null,k.current)try{k.current.getTracks().forEach(y1=>y1.stop())}catch{}if(k.current=null,U.current=null,v1)try{await m?.(v1)}catch{}X.current=""},[m]),b1=v.useCallback(async r1=>{const v1=await h?.(r1),y1=String(v1?.whipUrl||"").trim(),O1=String(v1?.token||"").trim();if(!y1||!O1)throw new Error("WHIP endpoint unavailable");const A1=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,sampleRate:r6,echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1}});k.current=A1;const h1=A1.getAudioTracks()?.[0];if(U.current=h1||null,h1&&(h1.enabled=!!(H||F)),h1?.applyConstraints)try{await h1.applyConstraints({channelCount:1,sampleRate:r6,echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1})}catch{}const m1=new RTCPeerConnection(ZP);G.current=m1,m1.onconnectionstatechange=()=>{if(!Y.current)return;const m2=m1.connectionState;if(m2==="connected"){L("live");return}(m2==="failed"||m2==="disconnected"||m2==="closed")&&(L("error"),T(`WHIP transport ${m2}.`))},A1.getAudioTracks().forEach(m2=>{const h6=m1.addTrack(m2,A1);PP(h6)});const B1=await m1.createOffer({offerToReceiveAudio:!1,offerToReceiveVideo:!1});await m1.setLocalDescription(B1),await YP(m1,1800);const V2=await fetch(y1,{method:"POST",headers:{"Content-Type":"application/sdp",...GP(O1)},body:m1.localDescription?.sdp||B1.sdp});if(!V2.ok)throw new Error(`WHIP request failed: ${V2.status}`);const J2=await V2.text();await m1.setRemoteDescription({type:"answer",sdp:J2}),await IP(m1,1e4),await QP(m1,6e3),await f?.(r1)},[H,F,f,h]),S1=v.useCallback(async()=>{if(W.current=!1,N.current){try{N.current.disconnect()}catch{}N.current.onaudioprocess=null}if(l1.current)try{l1.current.disconnect()}catch{}if(_.current)try{_.current.disconnect()}catch{}if(d1.current)try{d1.current.getTracks().forEach(r1=>r1.stop())}catch{}if(o1.current)try{await o1.current.close()}catch{}$.current=[],N.current=null,l1.current=null,_.current=null,d1.current=null,o1.current=null,K.current=r6},[]),X1=v.useCallback(async()=>{if(d1.current&&o1.current&&N.current)return;E(h1=>h1==="recording"?h1:"arming");const r1=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,sampleRate:r6,echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1}}),v1=new AudioContext({latencyHint:"interactive"}),y1=v1.createMediaStreamSource(r1),O1=v1.createScriptProcessor(1024,1,1),A1=v1.createGain();A1.gain.value=0,O1.onaudioprocess=h1=>{if(!W.current)return;const m1=h1.inputBuffer.getChannelData(0);$.current.push(new Float32Array(m1))},y1.connect(O1),O1.connect(A1),A1.connect(v1.destination),d1.current=r1,o1.current=v1,l1.current=y1,N.current=O1,_.current=A1,K.current=v1.sampleRate||r6},[]),x2=v.useCallback(async({send:r1})=>{const v1=W.current;if(W.current=!1,!v1){r1||E("idle");return}const y1=$.current;if($.current=[],!r1){E("idle");return}if(!g){E("idle");return}if(!y1.length){E("idle");return}E("sending");try{const O1=XP(y1),A1=JP(O1,K.current||r6),h1=Ki(A1);await l?.({roverId:g,name:`ptt-${Date.now()}.wav`,mime:"audio/wav",dataBase64:h1}),E("idle")}catch(O1){E("error"),T(O1?.message||"Failed to send PTT clip.")}},[l,g]),i1=v.useCallback(async()=>{if(!g){T("Take control of your rover first.");return}await X1(),$.current=[],W.current=!0,E("recording")},[X1,g]);return v.useEffect(()=>{let r1=!1;async function v1(){if(w){await D1(X.current||g);return}if(!g){await D1(X.current);return}if(!(Y.current&&X.current===g)&&!(!H&&!F))try{L("starting"),T(""),Y.current=!0,X.current=g,await b1(g),r1||L("live")}catch(y1){r1||(L("error"),T(y1?.message||"Failed to start mic forwarding.")),await D1(g)}}return v1(),()=>{r1=!0}},[w,H,F,g,b1,D1]),v.useEffect(()=>{const r1=U.current;r1&&(r1.enabled=!!(!w&&(H||F)))},[w,H,F]),v.useEffect(()=>{let r1=!1;async function v1(){if(!w||!g){W.current&&await x2({send:!1}),E("idle");return}try{await X1(),!r1&&!W.current&&E("idle")}catch(y1){r1||(E("error"),T(y1?.message||"Microphone access failed for clip mode."))}}return v1(),()=>{r1=!0}},[w,X1,x2,g]),v.useEffect(()=>{let r1=!1;async function v1(){if(w){if(!g){W.current&&await x2({send:!1});return}if(F){if(!W.current)try{T(""),await i1()}catch(y1){r1||(E("error"),T(y1?.message||"Failed to start clip recording."))}}else W.current&&await x2({send:!0})}}return v1(),()=>{r1=!0}},[w,x2,F,g,i1]),v.useEffect(()=>()=>{D1(X.current||g),S1()},[g,D1,S1]),r.jsx(F1,{title:"Audio Controls",children:r.jsxs("div",{className:"grid gap-1",children:[r.jsx("section",{className:"surface",children:r.jsxs("div",{className:"flex items-center justify-center gap-0.5 py-0.25 text-xs text-slate-300",children:[r.jsx("span",{children:"Push-to-Talk Key"}),r.jsx(A7,{label:f1})]})}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-1 lg:grid-cols-2",children:[r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_auto_1fr]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"PTT Mode"}),r.jsx("p",{className:"text-xs text-slate-400 text-center",children:"Choose how holding your PTT key behaves: live mic stream, or record-and-send clip. If the live stream doesn't work, switch to clip mode."}),r.jsxs("div",{className:"grid grid-cols-2 gap-0.5",children:[r.jsx("button",{type:"button",className:`button-dark w-full text-sm ${w?"":"bg-emerald-500 text-white hover:bg-emerald-500"}`,onClick:()=>g1("live"),children:"Live Mic (WHIP)"}),r.jsx("button",{type:"button",className:`button-dark w-full text-sm ${w?"bg-emerald-500 text-white hover:bg-emerald-500":""}`,onClick:()=>g1("clip"),children:"Record Clip"})]})]})}),r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_1fr]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"PTT Clip Status"}),r.jsxs("div",{className:"grid gap-0.5 content-start",children:[r.jsx(M4,{label:"Mode",active:w,detail:w?"record clip":"live mic"}),r.jsx(M4,{label:"PTT hold",active:F,detail:F?"held":"released"}),r.jsx(M4,{label:"Recording",active:k1,detail:k1?"capturing":"idle"}),r.jsx(M4,{label:"Playback",active:w&&u1,detail:w&&u1?"playing":"idle"})]})]})})]}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-1 lg:grid-cols-2",children:[r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_auto_1fr_auto]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"Audio Upload"}),r.jsxs("label",{className:"mx-auto grid w-full max-w-sm gap-0.5 text-xs text-slate-300 text-center",children:[r.jsx("span",{children:"Audio file (mp3 / wav / ogg)"}),r.jsx("input",{className:`${u0} text-center`,type:"file",accept:".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg",disabled:S||!g,onChange:r1=>y(r1.target.files?.[0]||null)})]}),r.jsx("div",{className:"mx-auto w-full max-w-sm text-center",children:M?r.jsxs("div",{className:"surface-muted text-xs text-slate-300",children:[M.name," (",M.size," bytes)"]}):r.jsx("div",{className:"surface-muted text-xs text-slate-500",children:"No file selected"})}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:S||!g,onClick:z1,children:S?"Working...":"Play Upload"}),r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:S||!g,onClick:j1,children:"Stop"})]})]})}),r.jsx("section",{className:"surface h-full",children:r.jsxs("div",{className:"grid h-full gap-0.5 grid-rows-[auto_auto_1fr]",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"Live Microphone"}),r.jsxs("label",{className:"surface-muted mx-auto flex w-full max-w-sm items-center justify-center gap-0.5 px-0.5 py-0.5 text-xs text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:H,disabled:!g||w,onChange:r1=>z(v1=>({...v1||{},openMicEnabled:!!r1.target.checked}))}),r.jsx("span",{children:w?"Open mic (live mode only)":"Open mic"})]}),r.jsxs("div",{className:"grid gap-0.5 content-start",children:[r.jsx(M4,{label:"WHIP link",active:x1,detail:j}),r.jsx(M4,{label:"Mic hot",active:H1,detail:H1?"transmitting":"muted"}),r.jsxs("div",{className:"surface-muted text-center text-xs text-slate-400",children:["Hold ",r.jsx(A7,{label:f1})," to talk"]})]})]})})]}),r.jsx("section",{className:"surface",children:r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-sm text-slate-200 text-center",children:"Audio Status"}),r.jsxs("div",{className:"grid gap-0.5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-6",children:[r.jsx(M4,{label:"Forward pipe",active:c1,detail:c1?"connected":"offline"}),r.jsx(M4,{label:"Upload playback",active:u1,detail:u1?"playing":"idle"}),r.jsx(M4,{label:"Mic relay",active:p1,detail:p1?"active":"idle"}),r.jsx(M4,{label:"WHIP transport",active:x1,detail:j}),r.jsx(M4,{label:"Clip recording",active:k1,detail:k1?"recording":"idle"}),r.jsx(M4,{label:"Clip sending",active:J,detail:J?"sending":"idle"})]}),O?r.jsx("div",{className:"text-xs text-slate-400 text-center",children:O}):null]})})]})})}function tQ({pendingRequestId:t,currentStoredKey:n,nickname:l,requestVerification:o,applyIdentityKey:h,onMessage:f,fullWidth:m=!1}){const[p,x]=v.useState(0),[z,g]=v.useState(""),[M,y]=v.useState(!1),[S,A]=v.useState(!1),H=()=>{x(1),g(n),y(!1),f?.("")},C=()=>{x(0),y(!1)},w=async L=>{if(L.preventDefault(),!M){f?.("Please confirm your nickname agreement before sending.");return}if(p<3){f?.("Complete all request steps before sending.");return}A(!0),f?.("");try{const B=await h(z);await o?.(),g(B),x(0),y(!1),f?.("Verification request sent to lockdown admins.")}catch(B){f?.(B.message||"Failed to submit request.")}finally{A(!1)}},j=m?"w-full":j8;return t?r.jsx(F1,{title:"Verification",className:j,bodyClassName:"text-sm text-slate-300",children:r.jsxs("div",{className:l0,children:["Verification request pending: ",t]})}):p===0?r.jsx(F1,{title:"Verification",className:j,children:r.jsx("div",{className:l0,children:r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:H,disabled:S,children:"Request Verification"})})}):r.jsx(F1,{title:"Request verification",className:j,children:r.jsx("form",{onSubmit:w,children:r.jsxs("div",{className:l0,children:[r.jsxs("p",{className:"text-xs text-slate-500",children:["Step ",p," of 3"]}),p===1?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Confirm your nickname, which is used as part of the verification process. You can edit it here before requesting."}),r.jsx("div",{className:"mx-auto w-full max-w-sm",children:r.jsx(_7,{compact:!0})}),r.jsxs("div",{className:"surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center",children:["Current nickname: ",r.jsx("span",{className:"font-semibold",children:l||"(not set)"})]}),r.jsxs("label",{className:"flex items-center justify-center gap-0.5 text-xs text-slate-300 text-center",children:[r.jsx("input",{type:"checkbox",className:"accent-emerald-500",checked:M,onChange:L=>y(L.target.checked)}),r.jsx("span",{children:"I understand this nickname is tied to my verification."})]}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!l||!M,onClick:()=>x(2),children:"Continue"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:C,children:"Cancel"})]})]}):null,p===2?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Save your identity key in a safe place. You can use it to restore your identity in another browser."}),r.jsx("input",{className:u0,type:"password",name:"identity_key_request",autoComplete:"current-password",maxLength:35,value:z,onChange:L=>g(L.target.value.toLowerCase()),placeholder:"Identity key for this request"}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!z,onClick:()=>navigator.clipboard?.writeText(z),children:"Copy Key"}),r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!z,onClick:()=>x(3),children:"Continue"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>x(1),children:"Back"})]})]}):null,p===3?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Final step: confirm and send your verification request."}),r.jsx("input",{className:u0,type:"password",name:"identity_key_request_final",autoComplete:"current-password",maxLength:35,value:z,onChange:L=>g(L.target.value.toLowerCase()),placeholder:"Identity key for this request"}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"submit",className:"button-dark text-sm",disabled:S||!z,children:"Confirm Request"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>x(2),children:"Back"})]})]}):null]})})})}function eQ({currentStoredKey:t,applyIdentityKey:n,onMessage:l,fullWidth:o=!1}){const[h,f]=v.useState(0),[m,p]=v.useState(""),[x,z]=v.useState(!1),g=()=>{f(1),p(""),l?.("")},M=()=>{f(0),p("")},y=async A=>{if(A.preventDefault(),!(h<2)){z(!0),l?.("");try{await n(m),f(0),p(""),l?.("Identity key restored.")}catch(H){l?.(H.message||"Failed to restore key.")}finally{z(!1)}}},S=o?"w-full":j8;return r.jsx(F1,{title:"Identity key",className:S,children:r.jsxs("div",{className:l0,children:[r.jsxs("p",{className:"text-xs text-slate-500",children:["Current: ",UP(t)||"not set yet"]}),r.jsx("input",{className:u0,type:"password",name:"identity_key_current",autoComplete:"current-password",maxLength:35,value:t,readOnly:!0,placeholder:"Identity key"}),h===0?r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",disabled:!t,onClick:()=>navigator.clipboard?.writeText(t),children:"Copy Key"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:g,disabled:x,children:"Restore Key"})]}):null,h===1?r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-xs text-slate-400",children:"Restoring your key should only be done when needed. Use this to move your identity to another browser."}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>f(2),children:"Continue"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:M,children:"Cancel"})]})]}):null,h===2?r.jsxs("form",{className:"space-y-0.5",onSubmit:y,children:[r.jsx("input",{className:u0,type:"password",name:"identity_key_restore",autoComplete:"current-password",maxLength:35,value:m,onChange:A=>p(A.target.value.toLowerCase()),placeholder:"Paste key to restore"}),r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"submit",className:"button-dark text-sm",disabled:x||!m,children:"Confirm Restore"}),r.jsx("button",{type:"button",className:"button-dark text-sm",onClick:()=>f(1),children:"Back"})]})]}):null]})})}function cQ({requestableRovers:t=[],pendingRequests:n=[],requestPrivateRoverAccess:l,onMessage:o,fullWidth:h=!1}){const[f,m]=v.useState({}),p=v.useMemo(()=>{const g=new Map;return(n||[]).forEach(M=>{M?.roverId&&g.set(String(M.roverId),M)}),g},[n]),x=h?"w-full":j8,z=async g=>{if(g){m(M=>({...M,[g]:!0})),o?.("");try{(await l?.(g))?.existing?o?.("You already have a pending request for that rover."):o?.("Private rover access request sent to lockdown admins.")}catch(M){o?.(M.message||"Failed to send request.")}finally{m(M=>({...M,[g]:!1}))}}};return r.jsx(F1,{title:"Private rover access requests",className:x,bodyClassName:"text-sm text-slate-300",children:r.jsx("div",{className:l0,children:t.length===0?r.jsx("p",{className:"text-xs text-slate-500",children:"No closed private rovers are available to request right now."}):r.jsx("div",{className:"w-full space-y-0.5",children:t.map(g=>{const M=String(g.id),y=!!f[M],S=p.get(M);return r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-1 py-0.5",children:[r.jsxs("div",{className:"min-w-0 text-left",children:[r.jsx("p",{className:"truncate text-xs",children:r.jsx(A4,{rover:g,fallback:M,className:"max-w-full truncate align-middle"})}),r.jsx("p",{className:"truncate text-[0.7rem] text-slate-500",children:M})]}),S?r.jsx("span",{className:"rounded bg-amber-700/30 px-1 py-0.5 text-[0.7rem] text-amber-200",children:"Pending"}):r.jsx("button",{type:"button",onClick:()=>z(M),disabled:y,className:"button-dark text-xs disabled:opacity-50",children:y?"Sending...":"Request"})]},M)})})})})}function x7(t){return String(t||"").trim()}function aQ(t){const n=x7(t);return n?n.includes("DOCKINGRUNNING")?"Returning":n.includes("PAUSED")?"Paused":n.includes("HOUSECLEANINGRUNNING")?"Cleaning":n.includes("SPOTCLEANINGRUNNING")?"Spot cleaning":n.includes("STATE_START")?"Starting":n.includes("STATE_IDLE")||n.includes("STATE_STANDBY")?"Idle":n:"--"}function Do(t){return t==="good"?"bg-emerald-600 text-white":t==="warn"?"bg-amber-500 text-slate-900":t==="danger"?"bg-rose-600 text-white":"bg-slate-700 text-slate-100"}function a7({label:t,value:n,tone:l="muted",valueClass:o="",hideLabel:h=!1}){return r.jsxs("div",{className:`rounded-md px-1 py-0.75 text-center ${Do(l)}`,children:[h?null:r.jsx("div",{className:"text-[0.72rem] opacity-90",children:t}),r.jsx("div",{className:`text-sm font-semibold ${o}`,title:String(n||""),children:n})]})}function nQ({neato:t,onStart:n,onSendHome:l,onLocate:o,onClearErrors:h,onPowerCycle:f,fullWidth:m=!1}){const[p,x]=v.useState(""),z=m?"w-full":"w-full max-w-xl",g=!!t?.configured,M=!!(t?.enabled&&t?.connected),y=!!t?.telemetry?.extPowerPresent,S=!!t?.telemetry?.chargingActive,A=aQ(t?.telemetry?.uiState),H=x7(t?.telemetry?.robotState)||"--",C=t?.telemetry?.batteryPercent,w=Number.isFinite(C)?`${C}%`:"--",j=t?.telemetry?.batteryVoltage,L=Number.isFinite(j)?`${j.toFixed(2)} V`:"--",B=x7(t?.telemetry?.robotError)||"--",E=x7(t?.telemetry?.robotAlert)||"--",O=t?.controls||{},T=!!O?.start?.available,k=!!O?.sendHome?.available,U=!!O?.locate?.available,G=!!O?.clearErrors?.available,Y=!!O?.powerCycle?.available,X=g&&M&&T,d1=g&&M&&k,o1=g&&M&&U,l1=g&&M&&G,N=g&&M&&Y,_=async(D,c1)=>{if(c1){x(D);try{await c1()}catch{}finally{x("")}}},$=g?M?"Online":"Offline":"Not configured",W=g?M?"good":"danger":"warn",K=C==null?"muted":C>=60?"good":C>=25?"warn":"danger",F=y?"Docked":A!=="--"?A:"Away from dock";return r.jsx(F1,{title:"Neato Controls",className:z,bodyClassName:"text-sm text-slate-200",actions:r.jsx("span",{className:`inline-flex w-auto rounded px-1 py-0.25 text-xs font-semibold ${Do(W)}`,children:$}),children:r.jsxs("div",{className:"grid gap-0.5",children:[r.jsx("div",{className:"text-center",children:r.jsx("p",{className:"text-xs text-slate-400",children:"Control the autonomous Neato robovac. Be nice to him."})}),r.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-0.5",children:[r.jsxs("div",{className:"surface-muted grid gap-0.5",children:[r.jsx("p",{className:"text-xs text-slate-300 text-center",children:"Controls"}),r.jsx("button",{type:"button",disabled:!X||!!p,onClick:()=>_("start",n),className:"rounded-md border border-sky-300 bg-emerald-600 px-1 py-1 text-base font-semibold text-white transition hover:border-sky-500 hover:bg-emerald-500 disabled:opacity-50",children:p==="start"?"Starting...":"Start cleaning"}),r.jsx("button",{type:"button",disabled:!d1||!!p,onClick:()=>_("sendHome",l),className:"rounded-md border border-sky-300 bg-sky-600 px-1 py-1 text-base font-semibold text-white transition hover:border-sky-500 hover:bg-sky-500 disabled:opacity-50",children:p==="sendHome"?"Sending...":"Send to dock"}),r.jsxs("div",{className:"grid grid-cols-3 gap-0.5",children:[r.jsx("button",{type:"button",disabled:!o1||!!p,onClick:()=>_("locate",o),className:"w-full rounded-md border border-sky-300 bg-fuchsia-600 px-1 py-0.5 text-xs font-semibold text-white transition hover:border-sky-500 hover:bg-fuchsia-500 disabled:opacity-50",children:p==="locate"?"Playing...":"Play sound"}),r.jsx("button",{type:"button",disabled:!l1||!!p,onClick:()=>_("clearErrors",h),className:"w-full rounded-md border border-sky-300 bg-amber-500 px-1 py-0.5 text-xs font-semibold text-slate-900 transition hover:border-sky-500 hover:bg-amber-400 disabled:opacity-50",children:p==="clearErrors"?"Clearing...":"Clear errors"}),r.jsx("button",{type:"button",disabled:!N||!!p,onClick:()=>_("powerCycle",f),className:"w-full rounded-md border border-rose-300 bg-rose-600 px-1 py-0.5 text-xs font-semibold text-white transition hover:border-rose-500 hover:bg-rose-500 disabled:opacity-50",children:p==="powerCycle"?"Cycling...":"Power cycle"})]}),r.jsxs("div",{className:"surface-muted grid gap-0.5 pt-0.25",children:[r.jsx("p",{className:"text-xs text-slate-300 text-center",children:"Power status"}),r.jsxs("div",{className:"grid grid-cols-2 gap-0.5",children:[r.jsx(a7,{label:"Battery",value:w,tone:K}),r.jsx(a7,{label:"Voltage",value:L,tone:"muted"}),r.jsx(a7,{label:"Docked",value:y?"Docked":"Not docked",tone:y?"good":"muted",hideLabel:!0}),r.jsx(a7,{label:"Charging",value:S?"charging":"not charging",tone:S?"good":"muted",hideLabel:!0})]})]})]}),r.jsx("div",{className:"grid gap-0.5",children:r.jsxs("div",{className:"surface-muted grid gap-0.5",children:[r.jsx("p",{className:"text-xs text-slate-300 text-center",children:"Robot Status"}),r.jsxs("div",{className:"grid gap-0.5",children:[r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Robot state (raw)"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:H})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Basic state"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:F})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"UI state"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:A})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Robot error"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:B})]}),r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.5",children:[r.jsx("div",{className:"text-[0.72rem] text-slate-300",children:"Robot alert"}),r.jsx("div",{className:"font-mono text-sm text-slate-100 break-all",children:E})]})]})]})})]}),!g||!M||!T||!k||!U||!G||!Y?r.jsx("p",{className:"text-xs text-slate-400 text-center",children:g?M?"Waiting for required Neato entities in Home Assistant.":"Home Assistant is offline.":"Set homeAssistant.neato.device in server config to enable Neato controls."}):null]})})}function lQ(t){return t==="good"?"bg-emerald-600 text-white":t==="warn"?"bg-amber-500 text-slate-900":t==="danger"?"bg-rose-600 text-white":"bg-slate-700 text-slate-100"}function rQ(t){return t==="up"?"Up":t==="down"?"Down":t==="stopped"?"Transitioning...":t==="conflict"?"Conflict":"--"}function iQ({lift:t,onUp:n,onDown:l,fullWidth:o=!1}){const[h,f]=v.useState(""),[m,p]=v.useState(()=>Date.now()),x=o?"w-full":"w-full max-w-xl",z=!!t?.configured,g=!!(t?.enabled&&t?.connected),M=!!t?.busy,y=String(t?.target||"").toLowerCase(),S=String(t?.position||"").toLowerCase(),A=!!t?.availability?.upSwitch,H=!!t?.availability?.downSwitch,C=Number(t?.lastActionAt||0),w=Math.max(0,Number(t?.commandCooldownMs||0)),j=Math.max(0,C+w-m),L=!M&&j>0,B=M||L;v.useEffect(()=>{if(!B)return;const G=setInterval(()=>{p(Date.now())},100);return()=>clearInterval(G)},[B]),v.useEffect(()=>{p(Date.now())},[C,w,M]);const E=z?g?B?"Busy":"Ready":"Offline":"Not configured",O=z?g?B?"warn":"good":"danger":"warn",T=z&&g&&!B&&!h&&A&&H,k=v.useMemo(()=>Math.ceil(j/1e3),[j]),U=async(G,Y)=>{if(Y){f(G);try{await Y()}catch{}finally{f("")}}};return r.jsxs(F1,{title:"Lift Controls",className:`relative ${x}`,bodyClassName:"text-sm text-slate-200",actions:r.jsx("span",{className:`inline-flex w-auto rounded px-1 py-0.25 text-xs font-semibold ${lQ(O)}`,children:E}),children:[B?r.jsx("div",{className:"absolute inset-0 z-20 flex items-center justify-center rounded-md bg-slate-950/80 px-1.5 text-center",children:r.jsxs("div",{className:"space-y-0.25",children:[r.jsx("p",{className:"text-sm font-semibold text-slate-100",children:M?"Preparing to move":"Moving!"}),r.jsx("p",{className:"text-xs text-slate-300",children:"Controls are disabled while the lift is moving, otherwise its tiny brain would get confused."}),!M&&L?r.jsxs("p",{className:"text-xs text-slate-400",children:["About ",k,"s remaining."]}):null]})}):null,r.jsxs("div",{className:"grid gap-0.5",children:[r.jsx("div",{className:"text-center",children:r.jsx("p",{className:"text-xs text-slate-400",children:"Move the lift up and down. Please don't break anything..."})}),r.jsx("section",{className:"surface-muted px-0.5 py-0.5",children:r.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-0.5",children:[r.jsxs("div",{className:"rounded-md bg-slate-800 px-1 py-0.75 text-center",children:[r.jsx("div",{className:"text-xs text-slate-300",children:"Position"}),r.jsx("div",{className:"text-base font-semibold text-slate-100",children:rQ(S)})]}),r.jsx("button",{type:"button",disabled:!T,onClick:()=>U("down",l),className:`button-dark w-full text-sm disabled:opacity-50 ${S==="down"||y==="down"?"bg-emerald-500 text-white hover:bg-emerald-500":""}`,children:h==="down"||y==="down"?"Lowering...":"Down"}),r.jsx("button",{type:"button",disabled:!T,onClick:()=>U("up",n),className:`button-dark w-full text-sm disabled:opacity-50 ${S==="up"||y==="up"?"bg-emerald-500 text-white hover:bg-emerald-500":""}`,children:h==="up"||y==="up"?"Raising...":"Up"})]})}),!z||!g||!A||!H?r.jsx("p",{className:"text-xs text-slate-400 text-center",children:z?g?"Waiting for required lift switch entities in Home Assistant.":"Home Assistant is offline.":"Set homeAssistant.lift.upSwitch and homeAssistant.lift.downSwitch in server config."}):null,t?.lastError?r.jsxs("p",{className:"text-xs text-rose-300 text-center",children:["Last error: ",t.lastError]}):null]})]})}function sQ(t){const n=String(t||"").trim();if(!n)return"";try{const l=new URL(n),o=String(l.protocol||"").toLowerCase();return o!=="http:"&&o!=="https:"?"":l.toString()}catch{return""}}function oQ({isVerified:t=!1,fullWidth:n=!1,onMessage:l}){const{value:o,save:h}=E1("profile",{nickname:"",profileImageUrl:""}),f=String(o?.profileImageUrl||"").trim(),[m,p]=v.useState(f),[x,z]=v.useState(!1);v.useEffect(()=>{p(f)},[f]);const g=v.useMemo(()=>sQ(m),[m]),M=String(m||"").trim()!==f,y=!!(t&&M&&(g||!String(m||"").trim())),S=n?"w-full":j8,A=async H=>{if(H.preventDefault(),!t){l?.("Verify your account before setting a profile image URL.");return}z(!0),l?.("");try{const C=g||"";h(w=>({...w||{},profileImageUrl:C})),l?.(C?"Profile image URL saved.":"Profile image URL cleared.")}finally{z(!1)}};return r.jsx(F1,{title:"Chat profile image URL",className:S,bodyClassName:"text-sm text-slate-300",children:r.jsxs("form",{className:l0,onSubmit:A,children:[r.jsx("p",{className:"text-xs text-slate-500",children:"Verified users can set a custom avatar for chat and Discord bridge messages."}),r.jsx("input",{className:u0,type:"url",name:"vip_profile_image_url",placeholder:"https://example.com/avatar.png",value:m,onChange:H=>p(H.target.value),disabled:!t||x}),!g&&String(m||"").trim()?r.jsx("p",{className:"text-xs text-amber-300",children:"Enter a valid http/https image URL."}):null,r.jsxs("div",{className:"flex justify-center gap-0.5",children:[r.jsx("button",{type:"submit",className:"button-dark text-sm disabled:opacity-50",disabled:!y||x,children:x?"Saving...":"Save URL"}),r.jsx("button",{type:"button",className:"button-dark text-sm disabled:opacity-50",disabled:!t||x||!f,onClick:()=>p(""),children:"Clear"})]})]})})}function Oo({isActive:t=!0}){const n=s1(o1=>o1.session),{identifySession:l,requestVerification:o,requestPrivateRoverAccess:h,playUploadedAudio:f,stopUploadedAudio:m,startMicWhip:p,readyMicWhip:x,stopMicWhip:z,neatoStart:g,neatoSendHome:M,neatoLocate:y,neatoClearErrors:S,neatoPowerCycle:A,liftUp:H,liftDown:C}=_2(),{value:w,save:j}=E1("identity",{cookieUserId:""}),{value:L}=E1("profile",{nickname:""}),B=v.useMemo(()=>(w?.cookieUserId||"").trim(),[w?.cookieUserId]),E=(L?.nickname||"").trim(),O=!!n?.isVerified,T=n?.verification?.pendingRequestId||null,k=n?.privateRoverAccess?.requestableRovers||[],U=n?.privateRoverAccess?.pendingRequests||[],G=String(n?.assignment?.roverId||"").trim(),[Y,X]=v.useState(""),d1=async o1=>{const l1=String(o1||"").trim().toLowerCase();if(!l1)throw new Error("Identity key required.");if(!_P.test(l1))throw new Error("Identity key must match format: cu_ + 32 lowercase hex chars.");return j(N=>({...N||{},cookieUserId:l1})),await l({cookieUserId:l1,nickname:E}),l1};return r.jsxs("section",{className:"space-y-2 text-base",children:[r.jsxs("div",{className:"grid gap-2 grid-cols-1 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,0.9fr)]",children:[O?null:r.jsx("div",{className:"lg:col-span-2",children:r.jsx(tQ,{pendingRequestId:T,currentStoredKey:B,nickname:E,requestVerification:o,applyIdentityKey:d1,onMessage:X,fullWidth:!0})}),r.jsx("div",{className:"lg:col-span-2",children:O?r.jsxs("div",{className:"space-y-2",children:[r.jsx(nQ,{neato:n?.neato||null,onStart:g,onSendHome:M,onLocate:y,onClearErrors:S,onPowerCycle:A,fullWidth:!0}),r.jsx(iQ,{lift:n?.lift||null,onUp:H,onDown:C,fullWidth:!0}),r.jsx(WP,{ownRoverId:G,audioForwardByRover:n?.audioForward||{},playUploadedAudio:f,stopUploadedAudio:m,startMicWhip:p,readyMicWhip:x,stopMicWhip:z})]}):r.jsx("section",{className:"surface h-full",children:r.jsx("div",{className:"flex h-full flex-col items-center justify-center text-center text-xs text-slate-400",children:"Verify your account to unlock VIP controls."})})}),r.jsx("div",{className:"lg:col-span-2",children:r.jsx(cQ,{requestableRovers:k,pendingRequests:U,requestPrivateRoverAccess:h,onMessage:X,fullWidth:!0})}),r.jsx("div",{className:"lg:col-span-2",children:r.jsx(eQ,{currentStoredKey:B,applyIdentityKey:d1,onMessage:X,fullWidth:!0})}),r.jsx("div",{className:"lg:col-span-2",children:r.jsx(oQ,{isVerified:O,onMessage:X,fullWidth:!0})})]}),Y?r.jsx("div",{className:j8,children:r.jsx("p",{className:"text-xs text-slate-400 text-center",children:Y})}):null]})}const uQ=420,hQ=1200,dQ={1:262,2:330,3:392,4:523};function j7(){const t=s1(H=>H.session?.buttonBox?.buttons??[]),n=X2(),{value:l}=E1("audio",o2),o=Number.isFinite(l?.masterVolume)?l.masterVolume:o2.masterVolume,h=Number.isFinite(l?.alertVolume)?l.alertVolume:o2.alertVolume,f=Math.max(0,Math.min(1,o*h)),m=v.useMemo(()=>{const H=Array.isArray(t)?t:[];return H.length===4?H:[1,2,3,4].map(C=>H.find(w=>Number(w?.id)===C)||{id:C,count:0,goal:0,rewardName:null,rewardId:null,rewardNumber:null,lastRewardAt:null})},[t]),[p,x]=v.useState({}),[z,g]=v.useState({}),M=v.useRef(new Map),y=v.useRef(new Map),S=v.useRef({}),A=v.useRef(null);return v.useEffect(()=>{const H={};m.forEach(w=>{H[w.id]=Number(w.lastRewardAt||0)});const C=S.current||{};m.forEach(w=>{const j=Number(w.id),L=H[j]||0,B=Number(C[j]||0);if(L>0&&L!==B){g(T=>({...T,[j]:!0}));const E=y.current.get(j);E&&clearTimeout(E);const O=setTimeout(()=>{g(T=>({...T,[j]:!1})),y.current.delete(j)},hQ);y.current.set(j,O)}}),S.current=H},[m]),v.useEffect(()=>{function H(w){const j=dQ[w];if(!j||f<=0)return;let L=A.current;if(!L){const T=window.AudioContext||window.webkitAudioContext;if(!T)return;L=new T,A.current=L}const B=L.createOscillator(),E=L.createGain();B.type="square",B.frequency.value=j;const O=.14*f;E.gain.setValueAtTime(1e-4,L.currentTime),E.gain.exponentialRampToValueAtTime(Math.max(1e-4,O),L.currentTime+.01),E.gain.exponentialRampToValueAtTime(1e-4,L.currentTime+.2),B.connect(E),E.connect(L.destination),B.start(),B.stop(L.currentTime+.21)}function C(w={}){const j=Number(w.buttonId);if(!Number.isFinite(j)||j<1||j>4)return;x(E=>({...E,[j]:!0}));const L=M.current.get(j);L&&clearTimeout(L);const B=setTimeout(()=>{x(E=>({...E,[j]:!1})),M.current.delete(j)},uQ);M.current.set(j,B),H(j)}return n.on("buttonBox:increment",C),()=>{n.off("buttonBox:increment",C)}},[f,n]),r.jsx(F1,{title:"Button Box",bodyClassName:"space-y-0.5 text-base",children:r.jsx("div",{className:"grid grid-cols-4 gap-0.5",children:m.map(H=>{const C=Number(H.id),w=Number.isFinite(H.count)?H.count:0,j=Number.isFinite(H.goal)?H.goal:0,L=typeof H.rewardName=="string"&&H.rewardName.trim()?H.rewardName.trim():"Unassigned",B=Number.isFinite(H.rewardNumber)?H.rewardNumber:"?",E=!!p[C],O=!!z[C];return r.jsx(Ps,{buttonId:C,count:w,goal:j,rewardNumber:B,rewardName:L,className:[E?"bg-cyan-900/45":"",O?"bg-fuchsia-900/45":""].filter(Boolean).join(" ")},C)})})})}function _o(){const{identifySession:t}=_2(),n=s1(z=>z.session?.overseerVote||null),{value:l}=E1("profile",{nickname:""}),{value:o}=E1("identity",{cookieUserId:""}),{value:h,save:f}=E1("overseerPreference",{enabled:!1}),m=!!h?.enabled,p=!!n?.running,x=async z=>{const g=!!z?.target?.checked;await f(M=>({...M||{},enabled:g})),await t({cookieUserId:String(o?.cookieUserId||"").trim(),nickname:String(l?.nickname||"").trim(),overseerEnabled:g})};return r.jsxs(F1,{title:"Overseer vote",bodyClassName:"space-y-0.5 text-center text-xs text-slate-200",children:[r.jsxs("label",{className:`flex items-center justify-center gap-1.5 rounded px-1 py-0.5 ${m?"bg-emerald-600/80 text-emerald-50":"bg-slate-600/70 text-slate-100"}`,children:[r.jsx("input",{type:"checkbox",checked:m,onChange:x}),r.jsxs("span",{children:["Your vote: ",m?"Yes":"No"]})]}),r.jsx("span",{className:"text-xs",children:"Enable a local LLM in chat"}),r.jsxs("div",{className:"mt-0.5 text-slate-400",children:["Yes!: ",Number(n?.yesCount||0),", No...: ",Number(n?.noCount||0)]}),r.jsx("div",{className:"mt-0.5 flex justify-center",children:r.jsx("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-[0.7rem] font-medium ${p?"bg-emerald-600/80 text-emerald-50":"bg-slate-600/70 text-slate-100"}`,children:p?"Running!":"Stopped by vote."})})]})}function fQ(){const{state:{roverId:t}}=H2(),l=K2(t)?.sensors||{};return r.jsx(F1,{title:"Roomba sensor view",children:r.jsx("div",{className:"aspect-square w-full",children:r.jsx(yo,{sensors:l})})})}function vQ(){const{state:{roverId:t,keymap:n,camera:l,horn:o},pipeline:h,actions:{setServoAngle:f,setNightVision:m,startHorn:p,stopHorn:x}}=H2(),z=m0(),g=uo(t),M=g.docked&&!g.driving,y=l?.config,S=!!(t&&l?.enabled&&y),A=!!(t&&h?.nightVision),H=h?.nightVisionState,C=!!(t&&h?.horn),w=o?.overheated,j=typeof y?.minAngle=="number"?y.minAngle:-30,L=typeof y?.maxAngle=="number"?y.maxAngle:30,B=typeof l?.angle=="number"?l.angle:typeof y?.homeAngle=="number"?y.homeAngle:(j+L)/2,E=B2(n?.nightVisionToggle?.[0]),O=B2(n?.hornHonk?.[0]),T=B2(n?.cameraUp?.[0]),k=B2(n?.cameraDown?.[0]),U=!!(!t||z.cameraLocked);return r.jsxs(F1,{hideHeader:!0,className:"h-full",bodyClassName:`grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto] ${j2}`,children:[r.jsx(ho,{layout:"desktop",expand:!0,driveDockState:g}),M?null:r.jsxs("div",{className:`${E3} p-0 text-sm text-slate-200`,children:[A&&r.jsx(fo,{nightVisionOn:H?.nightVisionOn,disabled:!t,onToggle:m,keyLabel:E}),C&&r.jsx(vo,{disabled:!t||w,onStart:p,onStop:x,keyLabel:O,active:o?.active,heat:o?.heat}),S&&r.jsx(mo,{value:B,min:j,max:L,label:"Camera tilt",disabled:U,onChange:f,keyDownLabel:k,keyUpLabel:T,className:`${E3} rounded-xl border-2 border-emerald-300/70 bg-emerald-900 px-1 py-1 text-emerald-50`,labelRowClass:"text-xs text-emerald-100",labelClass:"text-sm font-semibold",valueClass:"font-mono text-slate-100",sliderClass:"w-full",accentClass:"accent-emerald-400",endpointClass:"text-[0.7rem] text-emerald-100/80"})]})]})}function mQ({layout:t,onOpenHelpOverlay:n}){const[l,o]=v.useState("telemetry"),h=s1(H=>!!H.session?.isVerified),f=s1(H=>String(H.session?.assignment?.roverId||"").trim()),m=s1(H=>{const C=String(H.session?.assignment?.roverId||"").trim();return C&&H.session?.audioForward?.[C]||null}),{state:p}=H2(),{value:x}=E1("vipAudio",{openMicEnabled:!1,pttMode:"live"}),z=h?"bg-emerald-400":"bg-red-600",g=!!p?.mic?.pttActive,M=!!x?.openMicEnabled,y=x?.pttMode==="clip"?"clip":"live",S=!!(f&&h&&(y==="clip"?g:M||g)),A=!!(f&&h&&y==="clip"&&m?.source==="upload"&&m?.state==="playing");return r.jsx("section",{className:"text-base",children:r.jsxs(kc,{defaultTab:"telemetry",currentTab:l,onTabChange:o,children:[r.jsxs(Rc,{children:[r.jsx(r4,{id:"telemetry",children:"Controls"}),r.jsx(r4,{id:"vip",highlight:A?"green":S?"pink":"none",children:r.jsxs("span",{className:"inline-flex items-center gap-2",children:[r.jsx("span",{children:"VIP"}),r.jsx("span",{className:`inline-block h-3 w-3 rounded-full ${z}`,"aria-hidden":"true",title:h?"Verified":"Not verified"})]})}),r.jsx(r4,{id:"help",children:"Help"}),r.jsx(r4,{id:"settings",children:"Settings"})]}),r.jsxs(Ec,{children:[r.jsx(i4,{id:"telemetry",children:r.jsxs("div",{className:E3,children:[r.jsxs("div",{className:`grid items-stretch ${j2} grid-cols-[minmax(0,1.35fr)_minmax(0,0.95fr)]`,children:[r.jsx(fQ,{}),r.jsx(vQ,{})]}),r.jsxs("div",{className:`grid ${j2} grid-cols-[minmax(0,1fr)_minmax(0,0.9fr)_minmax(0,0.75fr)]`,children:[r.jsx(Z7,{}),r.jsx(xc,{panelId:"replay-sources-desktop",fillHeight:!0}),r.jsx(kP,{})]}),r.jsxs("div",{className:`grid items-stretch ${j2} grid-cols-[minmax(0,1.3fr)_minmax(0,0.22fr)] h-[14rem]`,children:[r.jsx(U7,{fillHeight:!0}),r.jsxs("div",{className:`grid min-h-0 ${j2} grid-rows-[auto_minmax(0,1fr)]`,children:[r.jsx(_o,{}),r.jsx(B7,{compact:!0,hideNicknameForm:!0,fillHeight:!0})]})]}),r.jsx(Ao,{}),r.jsx(j7,{}),r.jsx(Vc,{defaultOrientation:"horizontal",panelId:"rightpane-telemetry"})]})}),r.jsx(i4,{id:"vip",keepMounted:!0,children:r.jsx(Oo,{isActive:l==="vip"})}),r.jsx(i4,{id:"help",children:r.jsx(qo,{layout:t,onOpenOverlay:n})}),r.jsx(i4,{id:"settings",children:r.jsx(Ro,{})})]})]})})}const pQ=new Set(["admin","lockdown","lockdown-admin"]),gQ=new Set(["lockdown","lockdown-admin"]),zQ=new Set(["admin","lockdown"]);function xQ(t="admin"){return t==="lockdown"?{title:"Lockdown mode active",description:"Only the server owners can access the interface at this time."}:{title:"Admin mode active"}}function MQ(){const t=s1(g=>g.session?.mode||null),n=s1(g=>g.session?.role||null),l=s1(g=>g.session?.adminReason?.text||"");s1(g=>g.session?.adminReason?.updatedAt||null);const o=s1(g=>g.session?.timezone||"UTC"),h=zQ.has(t),f=t==="lockdown"?gQ.has(n):pQ.has(n),[m,p]=v.useState(()=>new Date),x=v.useMemo(()=>{try{return new Intl.DateTimeFormat("en-US",{timeZone:o,hour:"numeric",minute:"2-digit",second:"2-digit"}).format(m)}catch{return m.toLocaleTimeString()}},[m,o]);if(v.useEffect(()=>{const g=setInterval(()=>p(new Date),1e3);return()=>clearInterval(g)},[]),!h||f)return null;const z=xQ(t);return r.jsx("div",{className:"pointer-events-auto fixed inset-0 z-50 flex items-center justify-center bg-black px-0.5 py-0.5",children:r.jsxs("div",{className:"surface w-full max-w-md space-y-0.5 text-slate-100 shadow-2xl",children:[r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-lg font-semibold",children:z.title}),r.jsx("p",{className:"text-sm text-slate-300",children:z.description})]}),r.jsxs("div",{className:"surface-muted space-y-0.5",children:[r.jsx("p",{className:"text-[0.7rem] tracking-wide text-slate-400",children:"Reason for locking:"}),r.jsx("p",{className:"text-lg font-semibold text-slate-100",children:l||"No reason set."}),r.jsxs("p",{className:"text-center text-sm text-slate-300",children:["Server time: ",x]})]}),r.jsx("div",{className:"surface-muted",children:r.jsx(Bo,{})}),r.jsx("div",{className:"w-full justify-center items-center",children:r.jsx(T7,{id:"discord",label:"Join our Discord server for updates!"})}),"You can still use the chat while the server is locked:",r.jsx("div",{className:"max-h-80 overflow-y-auto",children:r.jsx(U7,{nicknameLayout:"stacked"})})]})})}const bQ="/assets/turn_alert-DVO8EZJa.mp3";function yQ(t,n=1){const l=v.useRef(null);return v.useEffect(()=>{l.current=new Audio(t),l.current.volume=n,l.current.load()},[t,n]),()=>{l.current&&(l.current.volume=n,l.current.currentTime=0,l.current.play().catch(()=>{}))}}function CQ(){const t=s1(g=>g.session?.socketId||null),n=s1(g=>g.session?.turnQueues||{}),l=s1(g=>g.session?.roster||[]),{pushAlert:o}=_2(),{value:h}=E1("audio",o2),f=Number.isFinite(h?.masterVolume)?h.masterVolume:o2.masterVolume,m=Number.isFinite(h?.alertVolume)?h.alertVolume:o2.alertVolume,p=Math.max(0,Math.min(1,f*m)),x=yQ(bQ,p),z=v.useRef(new Set);return v.useEffect(()=>{if(!t)return;const g=[];Object.entries(n).forEach(([M,y])=>{!y||!y.current||y.current!==t||z.current.has(M)||(g.push(M),z.current.add(M))}),Object.keys(n).forEach(M=>{(!n[M]||n[M].current!==t)&&z.current.delete(M)}),g.length!==0&&(g.forEach(M=>{const y=l.find(S=>String(S.id)===String(M))?.name||M;o({title:"Your turn!",message:`You now control ${y}.`,color:"#ff5722"})}),x())},[n,x,o,l,t]),null}function wQ({visible:t,mode:n,onEnterFullscreen:l,onDismiss:o}){if(!t)return null;const h=n==="pwa-hint";return r.jsx("div",{className:"fixed inset-0 z-40 flex items-center justify-center p-1 pointer-events-none bg-black/20",children:r.jsx("div",{className:"pointer-events-auto w-full max-w-sm surface center",children:r.jsxs("div",{className:"space-y-0.5 p-1 text-sm text-slate-100",children:[r.jsx("h2",{className:"text-base font-semibold text-white border-b border-slate-700",children:"Better in fullscreen!"}),h?r.jsxs("p",{className:"text-slate-300",children:["For fullscreen on iOS, open Safari's share menu and pick ",r.jsx("strong",{children:"Add to Home Screen"}),". Launching from the home screen then makes it fullscreen."]}):r.jsx("p",{className:"text-slate-300",children:"Enable fullscreen to free up more space for the video feed and controls. You can exit fullscreen at any time via the system back or home gesture."}),r.jsx("p",{className:"text-xs border-t border-b border-slate-700 p-0.5 text-blue-300 text-center",children:"This will only show once just to let you know. There is a fullscreen button in the bottom right for later use."}),r.jsxs("div",{className:"flex justify-end gap-0.5 pt-1 text-sm",children:[r.jsx("button",{type:"button",className:"rounded border border-slate-600 px-3 py-1 text-slate-200",onClick:o,children:h?"Got it":"Not now"}),!h&&r.jsx("button",{type:"button",className:"rounded bg-cyan-500 px-3 py-1 font-semibold text-black hover:bg-cyan-400",onClick:l,children:"Enter fullscreen"})]})]})})})}function HQ({side:t="right",onClick:n}){const l=t==="left"?"left-2":"right-2";return r.jsx("button",{type:"button",onClick:n,className:`fixed bottom-2 ${l} z-30 flex h-10 w-10 items-center justify-center rounded-full border border-slate-700 bg-black text-slate-200 transition hover:bg-zinc-900 active:scale-95`,"aria-label":"Enter fullscreen",title:"Enter fullscreen",children:r.jsx("svg",{viewBox:"0 0 24 24",width:"18",height:"18","aria-hidden":"true",className:"pointer-events-none",children:r.jsx("path",{d:"M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})})}const SQ=new Set(["mobile-portrait","mobile-landscape"]),VQ=()=>{if(typeof navigator>"u")return!1;const t=navigator.userAgent||navigator.platform||"";return/iphone|ipad|ipod/i.test(t)},Wi=()=>{if(typeof document>"u")return!1;const t=document;return!!(t.fullscreenEnabled||t.webkitFullscreenEnabled||t.mozFullScreenEnabled||t.msFullscreenEnabled)};function LQ(t){const{value:n,save:l,status:o}=E1("page",{fullscreenPromptShown:!1}),h=SQ.has(t),[f,m]=v.useState(!1),[p,x]=v.useState("native"),[z,g]=v.useState(!1),M=!!n?.fullscreenPromptShown,y=v.useMemo(()=>VQ(),[]),S=v.useMemo(()=>Wi(),[]),A=v.useCallback(()=>{o==="ready"&&l(L=>L?.fullscreenPromptShown?L:{...L||{},fullscreenPromptShown:!0})},[l,o]),H=v.useCallback(()=>{if(o!=="ready"){m(!1);return}if(!h){m(!1),g(!1);return}if(!z&&M){m(!1);return}if(y){x("pwa-hint"),m(!0);return}if(!Wi()){m(!1);return}if(typeof document<"u"&&document.fullscreenElement){m(!1);return}x("native"),m(!0)},[z,M,y,h,o]);v.useEffect(()=>{H()},[H]),v.useEffect(()=>{if(typeof document>"u")return;const L=()=>{if(document.fullscreenElement){g(!1),m(!1);return}H()};return document.addEventListener("fullscreenchange",L),document.addEventListener("webkitfullscreenchange",L),()=>{document.removeEventListener("fullscreenchange",L),document.removeEventListener("webkitfullscreenchange",L)}},[H]);const C=v.useCallback(()=>{A(),g(!1),m(!1)},[A]),w=v.useCallback(async()=>{if(!h||y||typeof document>"u")return!1;if(document.fullscreenElement)return!0;const L=document.documentElement;if(!L)return!1;const B=L.requestFullscreen||L.webkitRequestFullscreen||L.mozRequestFullScreen||L.msRequestFullscreen;if(!B)return!1;try{const E=B.call(L);return E&&typeof E.then=="function"&&await E,A(),g(!1),m(!1),!0}catch(E){return console.warn("Failed to enter fullscreen",E),!1}},[y,h,A]),j=v.useCallback(()=>{h&&(g(!0),x(y?"pwa-hint":"native"),m(!0))},[y,h]);return v.useEffect(()=>{h||(g(!1),m(!1))},[h]),{visible:f,mode:p,isIOS:y,nativeSupported:S,enterFullscreen:w,dismiss:C,showPrompt:j}}function AQ({visible:t,layout:n,onClose:l,showOnLoad:o,onToggleShowOnLoad:h}){if(!t)return null;const{state:f}=H2(),m=p=>{const x=!p.target.checked;h?.(x)};return r.jsx("div",{className:"fixed inset-0 z-30 flex items-center justify-center bg-black/80 px-0.5 py-0.5",children:r.jsxs("div",{className:"pointer-events-auto surface w-full max-w-4xl max-h-[90vh] overflow-hidden shadow-2xl",children:[r.jsxs("div",{className:"flex items-center justify-between border-b border-slate-700 px-0.5 py-0.25 text-sm text-slate-200",children:[r.jsx("span",{className:"font-semibold",children:"Help & controls"}),r.jsxs("div",{className:"flex items-center gap-0.5 text-[0.8rem] text-slate-300",children:[r.jsxs("label",{className:"flex items-center gap-0.5",children:[r.jsx("input",{type:"checkbox",checked:!o,onChange:m,className:"accent-cyan-500"}),r.jsx("span",{children:"Don't show again"})]}),r.jsx("button",{type:"button",onClick:l,className:"button-dark px-2 py-0.25 text-[0.8rem]",children:"Close"})]})]}),r.jsx("div",{className:"max-h-[85vh] overflow-y-auto p-0.5",children:r.jsx(To,{layout:n,keymap:f?.keymap||{}})})]})})}function c0({label:t,keyLabel:n}){return r.jsxs("div",{className:"surface-muted flex items-center justify-between gap-0.5 px-0.5 py-0.35 text-[0.8rem] text-slate-200",children:[r.jsx("span",{children:t}),r.jsx(A7,{label:n})]})}function BQ({keymap:t}){return r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-sm text-slate-200",children:'1. Press "Start Driving" put your rover into driving mode.'}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-sm text-slate-200",children:"2. Use the drive controls to move your rover:"}),r.jsxs("div",{className:"space-y-0.5",children:[r.jsx(c0,{label:"Forward",keyLabel:B2(t?.driveForward?.[0])}),r.jsx(c0,{label:"Backward",keyLabel:B2(t?.driveBackward?.[0])}),r.jsx(c0,{label:"Turn Left",keyLabel:B2(t?.driveLeft?.[0])}),r.jsx(c0,{label:"Turn Right",keyLabel:B2(t?.driveRight?.[0])}),r.jsx(c0,{label:"Move faster",keyLabel:B2(t?.boostModifier?.[0])}),r.jsx(c0,{label:"Move slower",keyLabel:B2(t?.slowModifier?.[0])})]})]}),r.jsx("p",{className:"text-sm text-slate-200",children:'3. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.'})]})}function jQ(){return r.jsxs("div",{className:"space-y-0.5 text-sm text-slate-200",children:[r.jsx("p",{children:'1. Press "Start Driving" put your rover into driving mode.'}),r.jsx("p",{children:"2. Touch and hold in Joystick area to move."}),r.jsx("p",{children:"3. Use the other column for motor, horn, and camera controls."}),r.jsx("p",{children:'4. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.'})]})}function FQ({visible:t,layout:n,showOnLoad:l,onToggleShowOnLoad:o,onOpenHelp:h,onClose:f}){const{state:m}=H2(),p=n==="desktop",x=v.useMemo(()=>m?.keymap||{},[m?.keymap]);if(!t)return null;const z=g=>{const M=!g.target.checked;o?.(M)};return r.jsx("div",{className:"fixed inset-0 z-40 flex items-end justify-center bg-black/75 p-0.5 items-center",children:r.jsxs("div",{className:"pointer-events-auto surface w-full max-w-3xl overflow-hidden shadow-2xl",children:[r.jsx("div",{className:"flex items-center justify-between border-b border-slate-700 px-0.5 py-0.35 text-sm text-slate-200",children:r.jsx("span",{className:"font-semibold text-xl",children:"Welcome! To get started:"})}),r.jsxs("div",{className:`grid gap-0.5 p-0.5 ${p?"md:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)]":"grid-cols-1"}`,children:[r.jsx("section",{className:"space-y-0.5 border-b border-slate-700",children:p?r.jsx(BQ,{keymap:x}):r.jsx(jQ,{})}),r.jsxs("section",{className:"space-y-0.5",children:[r.jsx("p",{className:"text-left",children:"Next..."}),r.jsxs("div",{className:"surface space-y-0.5 p-0.5 border-b border-slate-700",children:[r.jsx("p",{className:"text-xl font-semibold text-slate-200",children:"Set your nickname"}),r.jsx("p",{className:"text-sm font-semibold text-slate-200",children:"Nicknames are assigned randomly by default, you can change yours here."}),r.jsx(_7,{compact:!0})]}),r.jsxs("div",{className:"surface p-0.5",children:[r.jsx("p",{className:"text-xl font-semibold text-slate-200",children:"Join our Discord server!"}),r.jsx("p",{className:"text-sm font-semibold text-slate-200",children:"We have an active and welcoming community :3"}),r.jsx(T7,{id:"discord",label:"Join Discord"})]})]})]}),r.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-0.5 border-t border-slate-700 px-0.5 py-0.35 text-[0.8rem]",children:[r.jsxs("label",{className:"flex items-center gap-0.5 text-slate-300",children:[r.jsx("input",{type:"checkbox",checked:!l,onChange:z,className:"accent-cyan-500"}),r.jsx("span",{children:"Don't show again"})]}),r.jsx("div",{className:"flex items-center gap-0.5",children:r.jsx("button",{type:"button",onClick:f,className:"button-dark px-1 py-0.25 text-2xl bg-green-600 hover:bg-green-400 hover:border-green-100 border-green-400",children:"Got it! Let me in!"})})]})]})})}const NQ=`aroma bagel batter beans diff --git a/server/public/index.html b/server/public/index.html index b29050a7..e53b6405 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -11,7 +11,7 @@ Roomba Rover - + diff --git a/webui/src/components/ChatPanel/index.jsx b/webui/src/components/ChatPanel/index.jsx index ba13ebed..cc37b92e 100644 --- a/webui/src/components/ChatPanel/index.jsx +++ b/webui/src/components/ChatPanel/index.jsx @@ -232,7 +232,7 @@ export default function ChatPanel({ > - + {engine === 'flite' || engine === 'chromegtts' ? ( <>