anthropair 2026.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # claude-collab
2
+ A collaborative Claude dashboard
3
+
4
+ A local node.js app with a dashboard interface that wraps the claude SDK for local coding tasks and provides a webrtc screen share with another person
package/bin/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ process.env.NODE_ENV = 'production';
3
+ import '../server/index.js';
Binary file
@@ -0,0 +1,59 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))o(r);new MutationObserver(r=>{for(const s of r)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function n(r){const s={};return r.integrity&&(s.integrity=r.integrity),r.referrerPolicy&&(s.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?s.credentials="include":r.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function o(r){if(r.ep)return;r.ep=!0;const s=n(r);fetch(r.href,s)}})();const x={chatMessages:[],tasks:[],fileTree:[],agentStatus:"idle",livekitRoom:null,pendingPermission:null,settings:K(),wsConnected:!1},T=new Map;function U(e){return e?x[e]:{...x}}function l(e,t){x[e]=t,_(e,t)}function V(e,t){return T.has(e)||T.set(e,new Set),T.get(e).add(t),()=>T.get(e).delete(t)}function _(e,t){const n=T.get(e);n&&n.forEach(o=>o(t))}function R(e,t){const n=[...x[e]||[],t];l(e,n)}function W(e,t,n){const o=(x[e]||[]).map(r=>r.id===t?{...r,...n}:r);l(e,o)}function K(){try{return JSON.parse(localStorage.getItem("claude-collab-settings")||"{}")}catch{return{}}}let g=null,N=null;const b=new Map;function D(){if(g&&g.readyState<=1)return;const t=`${location.protocol==="https:"?"wss:":"ws:"}//${location.host}/ws`;l("wsConnected",!1),I("connecting"),g=new WebSocket(t),g.onopen=()=>{console.log("[ws] connected"),l("wsConnected",!0),I("connected"),clearTimeout(N)},g.onmessage=n=>{let o;try{o=JSON.parse(n.data)}catch{console.warn("[ws] invalid message:",n.data);return}G(o)},g.onclose=()=>{console.log("[ws] disconnected"),l("wsConnected",!1),I("disconnected"),Q()},g.onerror=n=>{console.error("[ws] error:",n)}}function Q(){clearTimeout(N),N=setTimeout(()=>D(),2e3)}function I(e){const t=document.getElementById("connection-status");t&&(t.className=`status-dot ${e}`,t.title=e.charAt(0).toUpperCase()+e.slice(1))}function E(e){g&&g.readyState===WebSocket.OPEN?g.send(JSON.stringify(e)):console.warn("[ws] not connected, message dropped:",e)}function y(e,t){return b.has(e)||b.set(e,new Set),b.get(e).add(t),()=>b.get(e).delete(t)}function G(e){const t=b.get(e.type);t&&t.forEach(o=>o(e));const n=b.get("*");n&&n.forEach(o=>o(e))}function z(e,t){const n=document.createElement("div"),o=document.createElement("div");if(o.className=`tree-item ${e.type}`,o.innerHTML=`<span class="icon"></span><span class="name">${X(e.name)}</span>`,n.appendChild(o),e.type==="directory"){let r=!1,s=!1;const i=document.createElement("div");i.className="tree-children",i.style.display="none",n.appendChild(i),o.addEventListener("click",async()=>{if(s=!s,o.classList.toggle("expanded",s),i.style.display=s?"":"none",!r){r=!0,i.innerHTML='<div style="padding:4px 8px;color:var(--text-muted);font-size:12px;">Loading...</div>';try{const a=await t(e.path);i.innerHTML="";for(const d of a)i.appendChild(z(d,t))}catch{i.innerHTML='<div style="color:var(--danger);padding:4px 8px;font-size:12px;">Failed to load</div>'}}})}return n}function X(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}const Y=()=>document.getElementById("file-tree");function Z(){var e;C("."),(e=document.getElementById("refresh-files-btn"))==null||e.addEventListener("click",()=>{C(".")}),y("files:changed",()=>C("."))}async function C(e){try{const n=await(await fetch(`/api/files?path=${encodeURIComponent(e)}`)).json();return e==="."&&(l("fileTree",n),ee(n)),n}catch(t){return console.error("Failed to load file tree:",t),[]}}function ee(e){const t=Y();if(t){t.innerHTML="";for(const n of e)t.appendChild(z(n,C))}}function te(e,t){const n=document.createElement("div");n.className=`chat-msg ${e}`;const o=document.createElement("div");return o.className="msg-content",o.textContent=t,n.appendChild(o),n}const H=()=>document.getElementById("chat-messages"),j=()=>document.getElementById("chat-input"),ne=()=>document.getElementById("send-btn"),S=()=>document.getElementById("interrupt-btn");let u=null,h="";function oe(){var e,t,n;(e=ne())==null||e.addEventListener("click",M),(t=j())==null||t.addEventListener("keydown",o=>{o.key==="Enter"&&!o.shiftKey&&(o.preventDefault(),M())}),(n=S())==null||n.addEventListener("click",()=>{E({type:"agent:interrupt"})}),y("agent:text",se),y("agent:tool_call",re),y("agent:done",ie),y("agent:error",ae),y("agent:status",ce),y("agent:interrupted",le),y("agent:permission",de)}function M(){var n;const e=j(),t=(n=e==null?void 0:e.value)==null?void 0:n.trim();t&&($("user",t),R("chatMessages",{role:"user",content:t,ts:Date.now()}),E({type:"agent:send",message:t}),e.value="",e.focus(),l("agentStatus","thinking"),S().style.display="")}function O(e){const t=j();t&&(t.value=e,M())}function se(e){l("agentStatus","streaming"),u||(u=$("assistant",""),h=""),h+=e.text,u.querySelector(".msg-content").textContent=h,B()}function re(e){u||(u=$("assistant",""),h="");const t=document.createElement("div");t.className="tool-call",t.innerHTML=`
2
+ <div class="tool-call-header">&#9881; ${L(e.tool)}</div>
3
+ <div class="tool-call-body"><pre>${L(JSON.stringify(e.input,null,2))}</pre></div>
4
+ `,t.addEventListener("click",()=>t.classList.toggle("expanded")),u.appendChild(t),B()}function ie(e){var t;if(l("agentStatus","idle"),S().style.display="none",u&&e.cost){const n=document.createElement("div");n.className="msg-meta",n.style.cssText="font-size:11px;color:var(--text-muted);margin-top:6px;",n.textContent=`Cost: $${((t=e.cost)==null?void 0:t.toFixed(4))||"?"} | ${e.duration?(e.duration/1e3).toFixed(1)+"s":""}`,u.appendChild(n)}R("chatMessages",{role:"assistant",content:h,ts:Date.now()}),u=null,h=""}function ae(e){l("agentStatus","idle"),S().style.display="none",$("assistant",`Error: ${e.error}`).style.color="var(--danger)",u=null,h=""}function ce(e){l("agentStatus",e.status),e.status==="thinking"&&(S().style.display="")}function le(){if(l("agentStatus","idle"),S().style.display="none",u){const e=document.createElement("div");e.style.cssText="color:var(--warning);font-size:12px;margin-top:4px;",e.textContent="[interrupted]",u.appendChild(e)}u=null,h=""}function de(e){l("pendingPermission",e);const t=H(),n=document.createElement("div");n.className="permission-request",n.innerHTML=`
5
+ <div class="perm-title">Permission Required: ${L(e.tool||"Unknown tool")}</div>
6
+ <div class="perm-detail" style="font-size:12px;color:var(--text-muted);margin-bottom:8px;">
7
+ ${L(JSON.stringify(e.input||{},null,2))}
8
+ </div>
9
+ <div class="permission-buttons">
10
+ <button class="btn btn-sm btn-success perm-approve">Approve</button>
11
+ <button class="btn btn-sm btn-danger perm-deny">Deny</button>
12
+ </div>
13
+ `,n.querySelector(".perm-approve").addEventListener("click",()=>{E({type:"permission:respond",approved:!0}),n.remove(),l("pendingPermission",null)}),n.querySelector(".perm-deny").addEventListener("click",()=>{E({type:"permission:respond",approved:!1}),n.remove(),l("pendingPermission",null)}),t.appendChild(n),B()}function $(e,t){const n=te(e,t);return H().appendChild(n),B(),n}function B(){const e=H();e&&(e.scrollTop=e.scrollHeight)}function L(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}function ue(e,{onApprove:t,onReject:n}){const o=document.createElement("div");if(o.className="task-card",o.dataset.id=e.id,o.innerHTML=`
14
+ <div class="task-card-header">
15
+ <span class="badge ${e.status}">${e.status}</span>
16
+ <span style="font-size:11px;color:var(--text-muted);">${e.id}</span>
17
+ </div>
18
+ <div class="prompt-preview">${pe(e.prompt||"")}</div>
19
+ `,e.status==="review"){const r=document.createElement("div");r.className="task-card-actions";const s=document.createElement("button");s.className="btn btn-sm btn-success",s.textContent="Approve",s.addEventListener("click",()=>t(e.id));const i=document.createElement("button");i.className="btn btn-sm btn-danger",i.textContent="Reject",i.addEventListener("click",()=>n(e.id)),r.appendChild(s),r.appendChild(i),o.appendChild(r)}return o}function pe(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}const me=()=>document.getElementById("task-queue"),fe=()=>document.getElementById("task-count");function ge(){y("task:update",ye),V("tasks",ve)}function ye(e){U("tasks").find(o=>o.id===e.task.id)?W("tasks",e.task.id,e.task):R("tasks",e.task)}function ve(e){const t=me();if(!t)return;t.innerHTML="";for(const o of e)t.appendChild(ue(o,{onApprove:r=>E({type:"task:approve",taskId:r}),onReject:r=>E({type:"task:reject",taskId:r})}));const n=fe();if(n){const o=e.filter(r=>r.status!=="done"&&r.status!=="rejected").length;n.textContent=o}}const he="modulepreload",be=function(e){return"/"+e},A={},Ee=function(t,n,o){let r=Promise.resolve();if(n&&n.length>0){let i=function(c){return Promise.all(c.map(f=>Promise.resolve(f).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};document.getElementsByTagName("link");const a=document.querySelector("meta[property=csp-nonce]"),d=(a==null?void 0:a.nonce)||(a==null?void 0:a.getAttribute("nonce"));r=i(n.map(c=>{if(c=be(c),c in A)return;A[c]=!0;const f=c.endsWith(".css"),p=f?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${p}`))return;const m=document.createElement("link");if(m.rel=f?"stylesheet":he,f||(m.as="script"),m.crossOrigin="",m.href=c,d&&m.setAttribute("nonce",d),document.head.appendChild(m),f)return new Promise((w,k)=>{m.addEventListener("load",w),m.addEventListener("error",()=>k(new Error(`Unable to preload CSS for ${c}`)))})}))}function s(i){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=i,window.dispatchEvent(a),!a.defaultPrevented)throw i}return r.then(i=>{for(const a of i||[])a.status==="rejected"&&s(a.reason);return t().catch(s)})};let v=null;function Se(){var e,t;(e=document.getElementById("join-room-btn"))==null||e.addEventListener("click",we),(t=document.getElementById("share-screen-btn"))==null||t.addEventListener("click",ke)}async function we(){var n,o;const e=(o=(n=document.getElementById("room-input"))==null?void 0:n.value)==null?void 0:o.trim();if(!e)return;const t=`user-${Date.now().toString(36)}`;try{const s=await(await fetch("/api/livekit/token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({room:e,identity:t})})).json();if(s.error){console.error("LiveKit token error:",s.error),alert(s.error);return}const{Room:i,RoomEvent:a}=await Ee(async()=>{const{Room:d,RoomEvent:c}=await import("./livekit-client.esm-BK0XEXgq.js");return{Room:d,RoomEvent:c}},[]);v=new i,v.on(a.TrackSubscribed,(d,c,f)=>{if(d.kind==="video"){const p=d.attach();d.source==="screen_share"?(document.getElementById("remote-video-container").innerHTML="",document.getElementById("remote-video-container").appendChild(p),document.getElementById("screen-share-placeholder").style.display="none"):(document.getElementById("webcam-pip").innerHTML="",document.getElementById("webcam-pip").appendChild(p),document.getElementById("webcam-pip").style.display="block")}}),v.on(a.TrackUnsubscribed,d=>{d.detach().forEach(c=>c.remove())}),v.on(a.Disconnected,()=>{l("livekitRoom",null),document.getElementById("screen-share-placeholder").style.display="",document.getElementById("remote-video-container").innerHTML="",document.getElementById("webcam-pip").innerHTML="",document.getElementById("webcam-pip").style.display="none"}),await v.connect(s.wsUrl,s.token),l("livekitRoom",e),document.getElementById("screen-share-placeholder").textContent=`Connected to "${e}"`,console.log("[livekit] connected to room:",e)}catch(r){console.error("[livekit] failed to connect:",r),alert("Failed to connect to room: "+r.message)}}async function ke(){if(!v){alert("Join a room first");return}try{const e=v.localParticipant.isScreenShareEnabled;await v.localParticipant.setScreenShareEnabled(!e),document.getElementById("share-screen-btn").textContent=e?"Share Screen":"Stop Sharing"}catch(e){console.error("[livekit] screen share error:",e)}}const Te=()=>document.getElementById("task-buttons");let P=[];async function xe(){try{const t=await(await fetch("/api/files/content?path=client/config/buttons.json")).json();P=JSON.parse(t.content)}catch{P=Ce()}Le()}function Ce(){return[{id:"explain",label:"Explain",promptTemplate:"Explain the current code in {file}",defaultVisible:!0},{id:"fix",label:"Fix Bug",promptTemplate:"Find and fix bugs in this project",defaultVisible:!0},{id:"refactor",label:"Refactor",promptTemplate:"Suggest refactoring improvements",defaultVisible:!0},{id:"test",label:"Write Tests",promptTemplate:"Write tests for the current module",defaultVisible:!0},{id:"review",label:"Code Review",promptTemplate:"Review the recent changes and provide feedback",defaultVisible:!0},{id:"docs",label:"Add Docs",promptTemplate:"Add documentation comments to the code",defaultVisible:!1},{id:"optimize",label:"Optimize",promptTemplate:"Suggest performance optimizations",defaultVisible:!1}]}function Le(){const e=Te();if(!e)return;const n=U("settings").hiddenButtons||[];e.innerHTML="";for(const o of P){if(!o.defaultVisible&&n.includes(o.id)||n.includes(o.id))continue;const r=document.createElement("button");r.className="task-btn",r.textContent=o.label,r.title=o.promptTemplate,r.addEventListener("click",()=>{if(o.requiresInput){const s=prompt(`Input for "${o.label}":`);if(!s)return;O(o.promptTemplate.replace("{input}",s))}else O(o.promptTemplate)}),e.appendChild(r)}}function $e(){const e=document.getElementById("settings-btn");e&&e.addEventListener("click",Be)}async function Be(){if(document.querySelector(".modal-overlay"))return;const e=await fetch("/api/settings"),{settings:t}=await e.json(),n=document.createElement("div");n.className="modal-overlay",n.innerHTML=`
20
+ <div class="modal">
21
+ <div class="modal-header">
22
+ <h2>Settings</h2>
23
+ </div>
24
+ <form class="settings-form">
25
+ ${t.map(s=>`
26
+ <div class="form-group">
27
+ <label class="form-label" for="setting-${s.key}">${s.label}</label>
28
+ ${s.type==="select"?`
29
+ <select
30
+ class="form-input form-select"
31
+ id="setting-${s.key}"
32
+ data-key="${s.key}"
33
+ data-type="${s.type}"
34
+ >
35
+ ${(s.options||[]).map(i=>`
36
+ <option value="${i.value}" ${i.value===s.value?"selected":""}>${i.label}</option>
37
+ `).join("")}
38
+ </select>
39
+ `:`
40
+ <input
41
+ class="form-input"
42
+ id="setting-${s.key}"
43
+ data-key="${s.key}"
44
+ data-type="${s.type}"
45
+ type="${s.type==="secret"?"password":"text"}"
46
+ ${s.type==="secret"?`placeholder="${s.value}"`:`value="${s.value}"`}
47
+ autocomplete="off"
48
+ />
49
+ `}
50
+ ${s.restart?'<span class="form-hint">Requires restart</span>':""}
51
+ </div>
52
+ `).join("")}
53
+ <div class="modal-footer">
54
+ <button type="button" class="btn btn-cancel">Cancel</button>
55
+ <button type="submit" class="btn btn-primary">Save</button>
56
+ </div>
57
+ </form>
58
+ </div>
59
+ `,document.body.appendChild(n);const o=()=>n.remove();n.querySelector(".btn-cancel").addEventListener("click",o),n.addEventListener("click",s=>{s.target===n&&o()});const r=s=>{s.key==="Escape"&&(o(),document.removeEventListener("keydown",r))};document.addEventListener("keydown",r),n.querySelector(".settings-form").addEventListener("submit",async s=>{var f;s.preventDefault();const i=n.querySelectorAll(".form-input"),a={};for(const p of i){const m=p.dataset.key,w=p.dataset.type,k=p.value.trim();if(!(w==="secret"&&!k)){if(w==="text"||w==="select"){const F=((f=t.find(J=>J.key===m))==null?void 0:f.value)||"";if(k===F)continue}a[m]=k}}if(Object.keys(a).length===0){o();return}const c=await(await fetch("/api/settings",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();o(),c.restartNeeded&&alert("Settings saved. Some changes require a server restart to take effect.")})}D();Z();oe();ge();xe();Se();$e();const Ie=new URLSearchParams(location.search),q=Ie.get("room");if(q){const e=document.getElementById("room-input");e&&(e.value=q)}
@@ -0,0 +1 @@
1
+ *,*:before,*:after{box-sizing:border-box;margin:0;padding:0}:root{--bg: #12101a;--surface: #1a1724;--surface-2: #221e2d;--border: #38314a;--text: #ece6f0;--text-muted: #9e92ad;--primary: #c88ea6;--primary-hover: #dba4bc;--danger: #f85149;--success: #3fb950;--warning: #d29922;--radius: 6px;--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;--mono: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace}html,body{height:100%;font-family:var(--font);background:var(--bg);color:var(--text);font-size:14px;overflow:hidden}#app{display:grid;grid-template-columns:250px 1fr 350px;grid-template-rows:auto 1fr 200px;grid-template-areas:"toolbar toolbar toolbar" "filetree chat screenshare" "taskqueue taskqueue taskqueue";height:100vh;gap:1px;background:var(--border)}#toolbar{grid-area:toolbar;display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:var(--surface);border-bottom:1px solid var(--border)}.toolbar-left{display:flex;align-items:center;gap:12px}.toolbar-center,.toolbar-right{display:flex;align-items:center;gap:8px}.logo{font-size:16px;font-weight:600;color:var(--primary)}.logo-icon{width:24px;height:24px}.status-dot{width:8px;height:8px;border-radius:50%;display:inline-block}.status-dot.connected{background:var(--success)}.status-dot.disconnected{background:var(--danger)}.status-dot.connecting{background:var(--warning)}#room-input{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);padding:4px 10px;font-size:13px;width:160px}.panel{background:var(--surface);display:flex;flex-direction:column;overflow:hidden}.panel-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);border-bottom:1px solid var(--border);flex-shrink:0}.panel-body{flex:1;overflow-y:auto;padding:8px}#file-tree-panel{grid-area:filetree}#chat-panel{grid-area:chat}#screen-share-panel{grid-area:screenshare}#task-queue-panel{grid-area:taskqueue}.tree-item{display:flex;align-items:center;gap:6px;padding:3px 8px;cursor:pointer;border-radius:var(--radius);font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-item:hover{background:var(--surface-2)}.tree-item .icon{font-size:12px;width:16px;text-align:center;flex-shrink:0}.tree-children{padding-left:16px}.tree-item.directory .icon:before{content:"▸"}.tree-item.directory.expanded .icon:before{content:"▾"}.tree-item.file .icon:before{content:"📄";font-size:11px}#chat-messages{display:flex;flex-direction:column;gap:12px}.chat-msg{padding:8px 12px;border-radius:var(--radius);max-width:100%;font-size:13px;line-height:1.5;white-space:pre-wrap;word-break:break-word}.chat-msg.user{background:#2d1f3a;align-self:flex-end;border-bottom-right-radius:2px}.chat-msg.assistant{background:var(--surface-2);align-self:flex-start;border-bottom-left-radius:2px}.chat-msg .tool-call{background:#1a1721;border:1px solid var(--border);border-radius:var(--radius);padding:6px 10px;margin:6px 0;font-family:var(--mono);font-size:12px;cursor:pointer}.tool-call-header{display:flex;align-items:center;gap:6px;color:var(--warning);font-weight:600}.tool-call-body{display:none;margin-top:6px;color:var(--text-muted);overflow-x:auto}.tool-call.expanded .tool-call-body{display:block}.chat-input-row{display:flex;gap:8px;padding:8px 12px;border-top:1px solid var(--border);flex-shrink:0}#chat-input{flex:1;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);padding:8px 12px;font-family:var(--font);font-size:13px;resize:none}#chat-input:focus{outline:none;border-color:var(--primary)}.permission-request{background:#2d1b00;border:1px solid var(--warning);border-radius:var(--radius);padding:10px;margin:6px 0}.permission-request .perm-title{color:var(--warning);font-weight:600;margin-bottom:6px}.permission-buttons{display:flex;gap:8px;margin-top:8px}.task-buttons{display:flex;flex-wrap:wrap;gap:4px;padding:6px 12px;border-top:1px solid var(--border);flex-shrink:0}.task-btn{background:var(--surface-2);border:1px solid var(--border);border-radius:16px;color:var(--text-muted);padding:4px 12px;font-size:12px;cursor:pointer;transition:all .15s}.task-btn:hover{background:var(--border);color:var(--text)}.task-queue-body{display:flex;gap:8px;padding:8px;overflow-x:auto;flex-direction:row}.task-card{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);padding:10px;min-width:220px;max-width:300px;flex-shrink:0}.task-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.badge{background:var(--border);color:var(--text-muted);padding:2px 8px;border-radius:10px;font-size:11px}.badge.queued{background:var(--border)}.badge.running{background:#1f3a1f;color:var(--success)}.badge.review{background:#3d2800;color:var(--warning)}.badge.done{background:#1a2e1a;color:var(--success)}.badge.error{background:#3d1010;color:var(--danger)}.task-card .prompt-preview{font-size:12px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.task-card-actions{display:flex;gap:6px;margin-top:8px}#screen-share-container{position:relative;background:#000;padding:0}#remote-video-container{width:100%;height:100%}#remote-video-container video{width:100%;height:100%;object-fit:contain}#webcam-pip{position:absolute;bottom:8px;right:8px;width:120px;height:90px;border-radius:var(--radius);overflow:hidden;border:2px solid var(--border);display:none}#webcam-pip video{width:100%;height:100%;object-fit:cover}.placeholder-text{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:13px}.btn{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);padding:6px 14px;font-size:13px;cursor:pointer;transition:all .15s}.btn:hover{background:var(--border)}.btn-sm{padding:4px 10px;font-size:12px}.btn-xs{padding:2px 6px;font-size:11px}.btn-primary{background:var(--primary);border-color:var(--primary);color:#000;font-weight:600}.btn-primary:hover{background:var(--primary-hover)}.btn-danger{background:transparent;border-color:var(--danger);color:var(--danger)}.btn-danger:hover{background:#3d1010}.btn-success{background:transparent;border-color:var(--success);color:var(--success)}.btn-success:hover{background:#1a2e1a}.btn-icon{background:transparent;border:none;color:var(--text-muted);font-size:16px;padding:4px;cursor:pointer}.btn-icon:hover{color:var(--text)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:100}.modal{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;min-width:300px;max-width:500px}.modal h2{font-size:16px;margin-bottom:0}.modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border)}.settings-form{display:flex;flex-direction:column;gap:12px}.form-group{display:flex;flex-direction:column;gap:4px}.form-label{font-size:12px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.3px}.form-input{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);padding:8px 12px;font-family:var(--mono);font-size:13px}.form-input:focus{outline:none;border-color:var(--primary)}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%239e92ad' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:30px;cursor:pointer}.form-input::placeholder{color:var(--text-muted)}.form-hint{font-size:11px;color:var(--warning)}.modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:8px;padding-top:12px;border-top:1px solid var(--border)}.btn-cancel{background:var(--surface-2);border:1px solid var(--border);color:var(--text-muted)}.btn-cancel:hover{color:var(--text);background:var(--border)}